Skip to main content
Glama
WorkspaceAdmin.vue24.3 kB
<template> <div class="overflow-scroll"> <template v-if="featureFlagsStore.ADMIN_PAGE"> <div> <h2 class="pb-md font-bold">Administration Dashboard</h2> <div class="pb-md"> <VButton iconRight="chevron--right" tone="action" variant="solid" @click="router.push({ name: 'rum-report' })" > View RUM Report </VButton> </div> <divider class="my-4" /> <h3 class="pb-md font-bold"> Create production workspace for an invited user </h3> <p>Create by User Email</p> <Stack> <ErrorMessage :requestStatus="createWorkspaceReqStatus" /> <VormInput v-model="workspace.userEmail" :maxLength="500" label="Email" placeholder="The email of the user to invite" required /> <VButton :requestStatus="createWorkspaceReqStatus" iconRight="chevron--right" loadingText="Creating..." tone="action" variant="solid" @click="createWorkspace()" > Create Workspace For User Email Address </VButton> </Stack> <p class="mt-4"> Create by User Id - this must be an ID that is in our auth-api database </p> <Stack> <ErrorMessage :requestStatus="createWorkspaceByUserIdReqStatus" /> <VormInput v-model="workspaceById.userId" :maxLength="500" label="User ID" placeholder="The user id of the user to invite" required /> <VButton :requestStatus="createWorkspaceByUserIdReqStatus" iconRight="chevron--right" loadingText="Creating..." tone="action" variant="solid" @click="createWorkspaceById()" > Create Workspace for Know SystemInit UserID </VButton> </Stack> </div> <divider class="my-4" /> <div> <h3 class="pb-md font-bold">User Accounts by Email</h3> <Stack> <VormInput v-model="usersByEmail" label="Email" placeholder="The email record to lookup" required /> <VButton :requestStatus="userRecordsByEmailReqStatus" :disabled="_.isEmpty(usersByEmail)" iconRight="chevron--right" loadingText="Searching..." tone="action" variant="solid" @click="getUserRecordsForEmail()" > Search for User Records </VButton> </Stack> <template v-if="userRecordsByEmailReqStatus.isPending"> <Icon name="loader" /> </template> <template v-else-if="userRecordsByEmailReqStatus.isError"> <ErrorMessage :requestStatus="userRecordsByEmailReqStatus" /> </template> <template v-else-if="userRecordsByEmailReqStatus.isSuccess"> <div class="relative"> <div class="text-lg font-bold">Currently Quarantined Users:</div> <table class="w-full divide-y divide-neutral-400 dark:divide-neutral-600 border-b border-neutral-400 dark:border-neutral-600" > <thead> <tr class="children:pb-xs children:px-md children:font-bold text-left text-xs uppercase" > <th scope="col">User ID</th> <th scope="col">Auth0 ID</th> <th scope="col">Email</th> <th scope="col">Is Quarantined?</th> <th scope="col">Is Suspended?</th> </tr> </thead> <tbody class="divide-y divide-neutral-300 dark:divide-neutral-700" > <tr v-for="userRecord in userRecords" :key="userRecord.id" class="children:px-md children:py-sm children:truncate text-sm font-medium text-neutral-800 dark:text-neutral-200" > <td class=""> <div class="xl:max-w-[800px] lg:max-w-[60vw] md:max-w-[50vw] sm:max-w-[40vw] max-w-[150px] truncate" > {{ userRecord.id }} </div> </td> <td class="normal-case"> {{ formatAuth0Signup(userRecord.auth0Id) }} </td> <td class="normal-case"> {{ userRecord.email }} </td> <td class="normal-case"> {{ hasDate(userRecord.quarantinedAt) }} </td> <td class="normal-case"> {{ hasDate(userRecord.suspendedAt) }} </td> </tr> </tbody> </table> </div> </template> </div> <divider class="my-4" /> <div> <h3 class="pb-md font-bold">Manage Account Quarantine</h3> <Stack> <ErrorMessage :requestStatus="setUserQuarantineReqStatus" /> <VormInput v-model="quarantineUserId" :maxLength="26" label="User ID" placeholder="The user id of the account to be managed" required /> <div class="flex gap-2"> <VButton :disabled="_.isEmpty(quarantineUserId)" :requestStatus="setUserQuarantineReqStatus" class="grow" icon="lock" tone="destructive" variant="solid" @click="setUserQuarantine(true)" > Quarantine Account </VButton> <VButton :disabled="_.isEmpty(quarantineUserId)" :requestStatus="setUserQuarantineReqStatus" class="grow" icon="lock-open" tone="success" variant="solid" @click="setUserQuarantine(false)" > Unquarantine Account </VButton> </div> </Stack> <template v-if="quarantinedUsersReqStatus.isPending"> <Icon name="loader" /> </template> <template v-else-if="quarantinedUsersReqStatus.isError"> <ErrorMessage :requestStatus="quarantinedUsersReqStatus" /> </template> <template v-else-if="quarantinedUsersReqStatus.isSuccess"> <div class="relative"> <div class="text-lg font-bold">Currently Quarantined Users:</div> <table class="w-full divide-y divide-neutral-400 dark:divide-neutral-600 border-b border-neutral-400 dark:border-neutral-600" > <thead> <tr class="children:pb-xs children:px-md children:font-bold text-left text-xs uppercase" > <th scope="col">User ID</th> <th scope="col">Email</th> <th scope="col">Quarantined At</th> </tr> </thead> <tbody class="divide-y divide-neutral-300 dark:divide-neutral-700" > <tr v-for="quarantinedUser in quarantinedUsers" :key="quarantinedUser.userId" class="children:px-md children:py-sm children:truncate text-sm font-medium text-neutral-800 dark:text-neutral-200" > <td class=""> <div class="xl:max-w-[800px] lg:max-w-[60vw] md:max-w-[50vw] sm:max-w-[40vw] max-w-[150px] truncate" > {{ quarantinedUser.userId }} </div> </td> <td class="normal-case"> {{ quarantinedUser.email }} </td> <td class="normal-case"> {{ quarantinedUser.quarantinedAt.toString() }} </td> </tr> </tbody> </table> </div> </template> </div> <divider class="my-4" /> <div> <h3 class="pb-md font-bold">Manage Workspace Quarantine</h3> <Stack> <ErrorMessage :requestStatus="setWorkspaceQuarantineReqStatus" /> <VormInput v-model="quarantineWorkspaceId" :maxLength="26" label="Workspace ID" placeholder="The id of the workspace to be managed" required /> <div class="flex gap-2"> <VButton :disabled="_.isEmpty(quarantineWorkspaceId)" :requestStatus="setWorkspaceQuarantineReqStatus" class="grow" icon="lock" tone="destructive" variant="solid" @click="setWorkspaceQuarantine(true)" > Quarantine Workspace </VButton> <VButton :disabled="_.isEmpty(quarantineWorkspaceId)" :requestStatus="setWorkspaceQuarantineReqStatus" class="grow" icon="lock-open" tone="success" variant="solid" @click="setWorkspaceQuarantine(false)" > Unquarantine Workspace </VButton> </div> </Stack> </div> <divider class="my-4" /> <div> <h3 class="pb-md font-bold">Manage User Suspensions</h3> <Stack> <ErrorMessage :requestStatus="setUserSuspensionReqStatus" /> <VormInput v-model="suspensionUserId" :maxLength="26" label="User ID" placeholder="The user id of the account to be managed" required /> <div class="flex gap-2"> <VButton :disabled="_.isEmpty(suspensionUserId)" :requestStatus="setUserSuspensionReqStatus" class="grow" icon="lock" tone="destructive" variant="solid" @click="setUserSuspension(true)" > Suspend Account </VButton> <VButton :disabled="_.isEmpty(suspensionUserId)" :requestStatus="setUserSuspensionReqStatus" class="grow" icon="lock-open" tone="success" variant="solid" @click="setUserSuspension(false)" > Unsuspend Account </VButton> </div> </Stack> <template v-if="suspendedUsersReqStatus.isPending"> <Icon name="loader" /> </template> <template v-else-if="suspendedUsersReqStatus.isError"> <ErrorMessage :requestStatus="suspendedUsersReqStatus" /> </template> <template v-else-if="suspendedUsersReqStatus.isSuccess"> <div class="relative"> <div class="text-lg font-bold">Currently Suspended Users:</div> <table class="w-full divide-y divide-neutral-400 dark:divide-neutral-600 border-b border-neutral-400 dark:border-neutral-600" > <thead> <tr class="children:pb-xs children:px-md children:font-bold text-left text-xs uppercase" > <th scope="col">User ID</th> <th scope="col">Email</th> <th scope="col">Suspended At</th> </tr> </thead> <tbody class="divide-y divide-neutral-300 dark:divide-neutral-700" > <tr v-for="suspendedUser in suspendedUsers" :key="suspendedUser.userId" class="children:px-md children:py-sm children:truncate text-sm font-medium text-neutral-800 dark:text-neutral-200" > <td class=""> <div class="xl:max-w-[800px] lg:max-w-[60vw] md:max-w-[50vw] sm:max-w-[40vw] max-w-[150px] truncate" > {{ suspendedUser.userId }} </div> </td> <td class="normal-case"> {{ suspendedUser.email }} </td> <td class="normal-case"> {{ suspendedUser.suspendedAt.toString() }} </td> </tr> </tbody> </table> </div> </template> </div> <divider class="my-4" /> <div> <h3 class="pb-md font-bold">Who owns this workspace?</h3> <Stack> <VormInput v-model="workspaceIdQuery" label="Workspace Id" placeholder="The workspace ID to lookup" required /> <div class="flex gap-2"> <VButton :disabled="_.isEmpty(workspaceIdQuery)" :requestStatus="getWorkspaceOwnerReqStatus" iconRight="chevron--right" loadingText="Querying..." tone="action" variant="solid" @click="getWorkspaceOwner()" > Get Workspace Owner </VButton> </div> </Stack> <template v-if="getWorkspaceOwnerReqStatus.isPending"> <Icon name="loader" /> </template> <template v-else-if="getWorkspaceOwnerReqStatus.isError"> <ErrorMessage :requestStatus="getWorkspaceOwnerReqStatus" /> </template> <template v-else-if="getWorkspaceOwnerReqStatus.isSuccess"> <div class="relative"> <div class="text-lg font-bold">Owner Details</div> <table v-if="workspaceOwner" class="w-full divide-y divide-neutral-400 dark:divide-neutral-600 border-b border-neutral-400 dark:border-neutral-600" > <thead> <tr class="children:pb-xs children:px-md children:font-bold text-left text-xs uppercase" > <th scope="col">First Name</th> <th scope="col">Last Name</th> <th scope="col">Email</th> <th scope="col">Display Name</th> <th scope="col">Instance Url</th> </tr> </thead> <tbody class="divide-y divide-neutral-300 dark:divide-neutral-700" > <tr class="children:px-md children:py-sm children:truncate text-sm font-medium text-neutral-800 dark:text-neutral-200" > <td class=""> <div class="xl:max-w-[800px] lg:max-w-[60vw] md:max-w-[50vw] sm:max-w-[40vw] max-w-[150px] truncate" > {{ workspaceOwner.firstName || "" }} </div> </td> <td class="normal-case"> {{ workspaceOwner.lastName || "" }} </td> <td class="normal-case"> {{ workspaceOwner.email }} </td> <td class="normal-case"> {{ workspaceOwner.displayName || "" }} </td> <td class="normal-case"> {{ workspaceOwner.instanceUrl }} </td> </tr> </tbody> </table> </div> </template> </div> <divider class="my-4" /> <div> <h3 class="pb-md font-bold">Generate Weekly Signup Report</h3> <Stack> <VormInput v-model="reportStartDate" label="Start Date" placeholder="The start date for the user signups report e.g. 2024-08-14" required /> <VormInput v-model="reportEndDate" label="End Date" placeholder="The end date for the user signups reporte.g. 2024-08-21" required /> <div class="flex gap-2"> <VButton :disabled="_.isEmpty(reportStartDate) || _.isEmpty(reportEndDate)" :requestStatus="setUserSignupsReqStatus" iconRight="chevron--right" loadingText="Generating Report..." tone="action" variant="solid" @click="getUserSignupsReport()" > Generate Report </VButton> </div> </Stack> <template v-if="setUserSignupsReqStatus.isPending"> <Icon name="loader" /> </template> <template v-else-if="setUserSignupsReqStatus.isError"> <ErrorMessage :requestStatus="setUserSignupsReqStatus" /> </template> <template v-else-if="setUserSignupsReqStatus.isSuccess"> <div class="relative"> <div class="text-lg font-bold">Signups:</div> <table class="w-full divide-y divide-neutral-400 dark:divide-neutral-600 border-b border-neutral-400 dark:border-neutral-600" > <thead> <tr class="children:pb-xs children:px-md children:font-bold text-left text-xs uppercase" > <th scope="col">First Name</th> <th scope="col">Last Name</th> <th scope="col">Email</th> <th scope="col">SignUp Method</th> <th scope="col">Discord</th> <th scope="col">GitHub</th> <th scope="col">Signup At</th> </tr> </thead> <tbody class="divide-y divide-neutral-300 dark:divide-neutral-700" > <tr v-for="user in userSignups" :key="user.email" class="children:px-md children:py-sm children:truncate text-sm font-medium text-neutral-800 dark:text-neutral-200" > <td class=""> <div class="xl:max-w-[800px] lg:max-w-[60vw] md:max-w-[50vw] sm:max-w-[40vw] max-w-[150px] truncate" > {{ user.firstName }} </div> </td> <td class="normal-case"> {{ user.lastName }} </td> <td class="normal-case"> {{ user.email }} </td> <td class="normal-case">{{ user.signupMethod }}</td> <td class="normal-case"> {{ user.discordUsername }} </td> <td class="normal-case"> {{ user.githubUsername }} </td> <td class="normal-case"> {{ formatDate(user.signupAt) }} </td> </tr> </tbody> </table> </div> </template> </div> </template> <template v-else> Feature not Enabled for account </template> </div> </template> <script lang="ts" setup> import * as _ from "lodash-es"; import { computed, onBeforeMount, onMounted, reactive, ref } from "vue"; import { Icon, VormInput, Stack, ErrorMessage, VButton, Divider, } from "@si/vue-lib/design-system"; import { useRouter } from "vue-router"; import { User, useAuthStore } from "@/store/auth.store"; import { useWorkspacesStore } from "@/store/workspaces.store"; import { useFeatureFlagsStore } from "@/store/feature_flags.store"; const authStore = useAuthStore(); const router = useRouter(); const workspacesStore = useWorkspacesStore(); const featureFlagsStore = useFeatureFlagsStore(); const invitedUserByEmail = { userEmail: "", }; const invitedUserById = { userId: "", }; const workspace = reactive(_.cloneDeep(invitedUserByEmail)); const workspaceById = reactive(_.cloneDeep(invitedUserById)); const createWorkspaceReqStatus = workspacesStore.getRequestStatus( "SETUP_PRODUCTION_WORKSPACE", ); const createWorkspace = async () => { await workspacesStore.SETUP_PRODUCTION_WORKSPACE(workspace.userEmail); }; const createWorkspaceByUserIdReqStatus = workspacesStore.getRequestStatus( "SETUP_PRODUCTION_WORKSPACE_BY_USER_ID", ); const createWorkspaceById = async () => { await workspacesStore.SETUP_PRODUCTION_WORKSPACE_BY_USER_ID( workspaceById.userId, ); }; // User quarantine state const quarantineUserId = ref(""); const setUserQuarantineReqStatus = authStore.getRequestStatus( "SET_USER_QUARANTINE", ); const setUserQuarantine = async (isQuarantined: boolean) => { await authStore.SET_USER_QUARANTINE(quarantineUserId.value, isQuarantined); }; // Workspace quarantine state const quarantineWorkspaceId = ref(""); const setWorkspaceQuarantineReqStatus = workspacesStore.getRequestStatus( "SET_WORKSPACE_QUARANTINE", ); const setWorkspaceQuarantine = async (isQuarantined: boolean) => { await workspacesStore.SET_WORKSPACE_QUARANTINE( quarantineWorkspaceId.value, isQuarantined, ); }; // User suspension state const suspensionUserId = ref(""); const setUserSuspensionReqStatus = authStore.getRequestStatus( "SET_USER_SUSPENSION", ); const setUserSuspension = async (isSuspended: boolean) => { await authStore.SET_USER_SUSPENSION(suspensionUserId.value, isSuspended); }; const quarantinedUsersReqStatus = authStore.getRequestStatus( "GET_QUARANTINED_USERS", ); const quarantinedUsers = computed(() => authStore.quarantinedUsers); const suspendedUsersReqStatus = authStore.getRequestStatus( "GET_SUSPENDED_USERS", ); const suspendedUsers = computed(() => authStore.suspendedUsers); onMounted(async () => { if (!authStore.userIsLoggedIn) return; if ( !authStore.user?.email?.includes("@systeminit.com") && !featureFlagsStore.ADMIN_PAGE ) { await router.push({ name: "workspaces", }); } }); function formatDate(date: string | Date): string { return new Date(date).toLocaleDateString(); } const reportStartDate = ref(""); const reportEndDate = ref(""); const setUserSignupsReqStatus = authStore.getRequestStatus( "GET_USER_SIGNUP_REPORT", ); // eslint-disable-next-line @typescript-eslint/no-explicit-any const userSignups = ref<any[]>([]); async function getUserSignupsReport() { if (!reportStartDate.value || !reportEndDate.value) return; const formattedStartDate = new Date(reportStartDate.value); const formattedEndDate = new Date(reportEndDate.value); await authStore.GET_USER_SIGNUP_REPORT(formattedStartDate, formattedEndDate); userSignups.value = authStore.userSignups; } const workspaceIdQuery = ref(""); const workspaceOwner = computed(() => workspacesStore.workspaceForOwner); async function getWorkspaceOwner() { if (!workspaceIdQuery.value) return; await workspacesStore.GET_WORKSPACE_OWNER(workspaceIdQuery.value); } const getWorkspaceOwnerReqStatus = workspacesStore.getRequestStatus( "GET_WORKSPACE_OWNER", ); const usersByEmail = ref(""); const userRecords = ref<User[]>([]); const userRecordsByEmailReqStatus = authStore.getRequestStatus( "GET_USERS_FOR_EMAIL", usersByEmail.value, ); async function getUserRecordsForEmail() { if (!usersByEmail.value) return; const users = await authStore.GET_USERS_FOR_EMAIL(usersByEmail.value); userRecords.value = users.result.data || []; } function formatAuth0Signup(signup: string) { const signupMethod = signup.split("|", 1); return signupMethod[0]; } function hasDate(date: string | undefined) { if (!date || date === "") return false; return true; } onBeforeMount(async () => { // eslint-disable-next-line @typescript-eslint/no-floating-promises authStore.GET_QUARANTINED_USERS(); // eslint-disable-next-line @typescript-eslint/no-floating-promises authStore.GET_SUSPENDED_USERS(); }); </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