Skip to main content
Glama
WorkspacesPage.vue7.99 kB
<template> <div v-if="user && user.emailVerified" class="overflow-hidden flex flex-col gap-sm" > <!-- TITLE --> <div class="flex flex-col"> <div class="text-lg font-bold">{{ workspaceTitle }}</div> <div class="text-xs"> From here you can log into any of your workspaces. </div> </div> <!-- HELP BANNER--> <div :class=" clsx( 'p-xs border rounded', themeClasses( 'bg-shade-0 border-neutral-200', 'bg-neutral-800 border-neutral-700', ), ) " > If you have questions or need help, join us on <a class="text-action-500 dark:text-action-400 font-bold hover:underline" href="https://discord.gg/system-init" target="_blank" > Discord </a> or visit our <a class="text-action-500 dark:text-action-400 font-bold hover:underline" href="https://docs.systeminit.com" target="_blank" >docs site</a >. </div> <!-- TIER AND BILLING INFO --> <div class="flex flex-row gap-md"> <!-- Pricing Tier --> <!-- Basis full will be removed when we add the other cards in --> <InfoCard class="basis-full" title="Tier" helpTooltipText="Pricing Info" helpLink="https://www.systeminit.com/pricing" > <template #infoRow1> <div :class=" clsx( 'rounded text-sm px-xs py-2xs my-2xs w-fit', themeClasses( 'bg-success-600 text-shade-0', 'bg-success-500 text-shade-100', ), ) " > <!-- In the future we are going to need to check if they are outside the free tier as well --> {{ getSubscriptionTier }} </div> </template> <template v-if=" activeSubscriptionDetails?.isTrial && activeSubscriptionDetails.endingAt " #infoRow2 > Ends end of day <Timestamp :date="activeSubscriptionDetails.endingAt" /> </template> </InfoCard> </div> <!-- SEARCH --> <SiSearch placeholder="Search workspaces..." disableFilters @search="onSearch" /> <!-- WORKSPACES LIST --> <LoadStatus :requestStatus="loadWorkspacesReqStatus" loadingMessage="Loading Workspaces..." > <template #success> <div class="flex flex-col"> <div class="flex flex-row items-center"> <!-- TODO(Wendy) - this is where the filtering and sorting bar goes --> </div> <div class="grid grid-cols-4 gap-sm"> <div :class=" clsx( 'group/newworkspace', 'flex flex-col items-center justify-center gap-sm rounded border-2 border-dashed hover:border-solid cursor-pointer', themeClasses( 'border-action-200 hover:border-action-500 hover:bg-action-200 active:bg-action-400 active:border-shade-100', 'border-action-900 hover:border-action-400 hover:bg-action-900 active:bg-action-400 active:border-shade-0', ), ) " @click="createNewWorkspace" > <div :class=" clsx( 'rounded-lg p-xs text-shade-0 bg-action-400 group-hover/newworkspace:bg-action-600', ) " > <Icon name="git-branch-plus" rotate="left" size="lg" /> </div> <div :class=" clsx( 'select-none', themeClasses('group-active/newworkspace:text-shade-0', ''), ) " > Create New Workspace </div> </div> <WorkspaceLinkWidget v-for="workspace in filteredWorkspaces" :key="workspace.id" :workspaceId="workspace.id" /> </div> </div> </template> <template #uninitialized> <div> You will not be able to use System Initiative until you verify your email. </div> </template> </LoadStatus> </div> </template> <script lang="ts" setup> import { computed, watch, ref, onMounted } from "vue"; import { Icon, Timestamp, themeClasses, LoadStatus, SiSearch, } from "@si/vue-lib/design-system"; import * as _ from "lodash-es"; import clsx from "clsx"; import { useRouter } from "vue-router"; import { useHead } from "@vueuse/head"; import { useAuthStore } from "@/store/auth.store"; import { useWorkspacesStore, Workspace } from "@/store/workspaces.store"; import WorkspaceLinkWidget from "@/components/WorkspaceLinkWidget.vue"; import InfoCard from "@/components/InfoCard.vue"; const authStore = useAuthStore(); const workspacesStore = useWorkspacesStore(); const router = useRouter(); const workspaces = computed(() => workspacesStore.workspaces); function sortedWorkspaces(workspaces: Workspace[]): Workspace[] { return workspaces.sort((a, b) => { // 1. Sort by isDefault (true comes first) if (a.isDefault !== b.isDefault && a.creatorUserId === authStore.user?.id) { return a.isDefault ? -1 : 1; } // 2. Sort by isFavourite (true comes first) if ( a.isFavourite !== b.isFavourite && a.creatorUserId === authStore.user?.id ) { return a.isFavourite ? -1 : 1; } // 3. Sort by instanceEnvType (SI comes first, then REMOTE, then LOCAL) if (a.instanceEnvType !== b.instanceEnvType) { const envTypeOrder = { SI: 0, PRIVATE: 1, LOCAL: 2 }; return envTypeOrder[a.instanceEnvType] - envTypeOrder[b.instanceEnvType]; } // 4. If all above are equal, sort by displayName return a.displayName.localeCompare(b.displayName); }); } const user = computed(() => authStore.user); useHead({ title: "Workspaces" }); const loadWorkspacesReqStatus = workspacesStore.refreshWorkspaces(); const activeSubscriptionDetails = computed(() => authStore.activeSubscription); watch( () => authStore.userIsLoggedIn, async () => { if (authStore.userIsLoggedIn && !authStore.needsProfileUpdate) { await authStore.GET_ACTIVE_SUBSCRIPTION(); await authStore.CHECK_BILLING_DETAILS(); } }, { immediate: true }, ); onMounted(() => { if (!user.value || !user.value?.emailVerified) { return router.push({ name: "profile" }); } }); const workspaceCount = computed(() => workspaces.value.length); const workspaceTitle = computed(() => { if (workspaceCount.value > 1) return `${workspaceCount.value} Workspaces`; else if (workspaceCount.value === 1) return "One Workspace"; else return "Create A Workspace"; }); const createNewWorkspace = async () => { await router.push({ name: "workspace-settings", params: { workspaceId: "new" }, }); }; const getSubscriptionTier = computed(() => { if (activeSubscriptionDetails.value?.planCode === "si_internal") { return "SI INTERNAL"; } if (activeSubscriptionDetails.value?.isTrial) { return "30-DAY FREE TRIAL"; } else if (activeSubscriptionDetails.value?.exceededFreeTier) { return "MONTHLY SUBSCRIPTION"; } return "FREE SUBSCRIPTION"; }); const searchString = ref(""); const onSearch = (search: string) => { searchString.value = search.trim().toLocaleLowerCase(); }; const filteredWorkspaces = computed(() => { return sortedWorkspaces(filterWorkspacesBySearchString(workspaces.value)); }); const filterWorkspacesBySearchString = (workspaces: Workspace[]) => { return _.filter(workspaces, (w) => { if (w.displayName.toLowerCase().includes(searchString.value)) { return true; } return false; }); }; </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