Skip to main content
Glama
WorkspaceAuthTokensPage.vue8.29 kB
<template> <LoadStatus :requestStatus="workspaceStatus"> <template #uninitialized> <div>You must log in to view workspace status.</div> </template> <template #success> <div class="overflow-hidden"> <WorkspacePageHeader :title="`${workspace.displayName} > API Tokens`" subtitle="From here you can manage API tokens for your workspace. Enter the name and expiration of the token below and click the Generate API Token button" > <RouterLink :to="{ name: 'workspace-settings', params: { workspaceId } }" > <IconButton class="flex-none" icon="settings" iconIdleTone="shade" size="lg" tooltip="Settings" tooltipPlacement="top" /> </RouterLink> </WorkspacePageHeader> <Stack> <ErrorMessage :asyncState="createAuthToken" /> <form class="flex flex-row flex-wrap items-center justify-center gap-md" > <VormInput v-model="createAuthTokenName" inlineLabel label="Token Name" required placeholder="A name for your token." :regex="ALLOWED_INPUT_REGEX" @keydown.enter.prevent="onFormSubmit" /> <VormInput v-model="createAuthTokenExpiration" inlineLabel label="Expiration" required placeholder="60m, 48h, 3d, 1y, etc." type="time-string" :maxLength="99" @keydown.enter.prevent="onFormSubmit" /> <VButton :disabled="validationState.isError" :loading="createAuthToken.isLoading.value" loadingText="Creating ..." tone="action" variant="solid" @click="onFormSubmit" > Generate API Token </VButton> </form> <ErrorMessage :asyncState="authTokens" /> <AuthTokenList :workspace="workspace" :authTokens="activeTokens" active @renamed="renameToken" @revoked="revokeToken" /> <AuthTokenList :workspace="workspace" :authTokens="inactiveTokens" @renamed="renameToken" @revoked="revokeToken" /> </Stack> </div> <Modal ref="tokenDisplayModalRef" size="lg" title="Token Generated"> <ErrorMessage v-if="tokenCopied" class="rounded-md text-md p-xs" icon="check-circle" tone="success" variant="block" > <b>Token copied!</b> We are only showing you the value of this token once. Store it somewhere secure, please. </ErrorMessage> <ErrorMessage v-else class="rounded-md text-md p-xs" icon="alert-circle" tone="info" variant="block" > We are only showing you the value of this token once. Store it somewhere secure, please. </ErrorMessage> <div class="flex flex-row items-center mt-sm gap-xs"> <VormInput :modelValue="createAuthToken.state.value" class="flex-grow text-sm" disabled noLabel type="text" /> <IconButton class="flex-none" icon="clipboard-copy" tooltip="Copy token to clipboard" tooltipPlacement="right" @click="copyToken" /> </div> </Modal> </template> </LoadStatus> </template> <script lang="ts" setup> import * as _ from "lodash-es"; import { computed, ref, onMounted } from "vue"; import { VormInput, Stack, VButton, LoadStatus, ErrorMessage, Modal, IconButton, useValidatedInputGroup, } from "@si/vue-lib/design-system"; import { useAsyncState, useIntervalFn } from "@vueuse/core"; import { apiData } from "@si/vue-lib/pinia"; import { useHead } from "@vueuse/head"; import { useRouter } from "vue-router"; import { useWorkspacesStore, WorkspaceId } from "@/store/workspaces.store"; import { useAuthStore } from "@/store/auth.store"; import WorkspacePageHeader from "@/components/WorkspacePageHeader.vue"; import { AuthToken, useAuthTokensApi } from "@/store/authTokens.store"; import AuthTokenList from "@/components/AuthTokenList.vue"; import { ALLOWED_INPUT_REGEX } from "@/lib/validations"; useHead({ title: "API Tokens" }); const workspacesStore = useWorkspacesStore(); const router = useRouter(); const authStore = useAuthStore(); const api = useAuthTokensApi(); const props = defineProps<{ workspaceId: WorkspaceId; }>(); // Fetch the workspace (by fetching all workspaces) const workspaceStatus = workspacesStore.refreshWorkspaces(); const workspace = computed( () => workspacesStore.workspacesById[props.workspaceId], ); /** The list of tokens */ const authTokens = useAsyncState( async () => { const { authTokens } = await apiData( api.FETCH_AUTH_TOKENS(props.workspaceId), ); return _.keyBy(authTokens, "id"); }, undefined, { shallow: false }, ); // This pokes the computed values to check if any tokens have expired every 5 seconds const EXPIRATION_CHECK_INTERVAL = 5000; const now = ref(Date.now()); useIntervalFn(() => { now.value = Date.now(); }, EXPIRATION_CHECK_INTERVAL); // The list of all of the tokens to display, along with whether or not they are expired const listedTokens = computed(() => { return ( _.reverse( _.sortBy(_.values(authTokens.state.value), "createdAt"), ) as Array<AuthToken> ).map((token) => { const d = new Date(token.expiresAt as unknown as string); const isExpired = d.getTime() < now.value; const isActive = !isExpired && !token.revokedAt; return { token, isExpired, isActive }; }); }); // The list of all active tokens const activeTokens = computed(() => { return listedTokens.value.filter((o) => o.isActive); }); // The list of all inactive tokens (expired or revoked) const inactiveTokens = computed(() => { return listedTokens.value.filter((o) => activeTokens.value.indexOf(o) === -1); }); /** Action to create auth token. Sets .state when done. */ const createAuthToken = useAsyncState( async () => { if (_.isEmpty(createAuthTokenName.value)) return; const { authToken, token } = await apiData( api.CREATE_AUTH_TOKEN( props.workspaceId, createAuthTokenName.value, createAuthTokenExpiration.value, ), ); if (authTokens.state.value) { authTokens.state.value[authToken.id] = authToken; } tokenCopied.value = false; createAuthTokenName.value = ""; createAuthTokenExpiration.value = ""; validationMethods.resetAll(); tokenDisplayModalRef.value?.open(); return token; }, undefined, { immediate: false, resetOnExecute: false }, ); /** Name of token to create */ const createAuthTokenName = ref(""); /** Expiration time of token to create */ const createAuthTokenExpiration = ref(""); /** Token modal */ const tokenDisplayModalRef = ref<InstanceType<typeof Modal> | null>(null); const tokenCopied = ref(false); async function copyToken() { if (createAuthToken.state.value) { await navigator.clipboard?.writeText(createAuthToken.state.value); } tokenCopied.value = true; } const { validationState, validationMethods } = useValidatedInputGroup(); const onFormSubmit = async () => { if (validationMethods.hasError()) return; await createAuthToken.execute(); }; // eslint-disable-next-line @typescript-eslint/no-unused-vars const renameToken = (id: string, newName: string) => { // TODO(Wendy) - renaming tokens! // @renamed="(newName) => (authToken.name = newName)" }; const revokeToken = (id: string) => { // TODO(Wendy) - revoking tokens! if (authTokens.state.value) { authTokens.state.value[id].revokedAt = new Date(); } }; const user = computed(() => authStore.user); onMounted(() => { if (!user.value || !user.value?.emailVerified) { return router.push({ name: "profile" }); } }); </script>

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/systeminit/si'

If you have feedback or need assistance with the MCP directory API, please join our Discord server