Skip to main content
Glama
WorkspaceImportModal.vue14.5 kB
<template> <Modal ref="modalRef" :noExit="blockExit" noAutoFocus size="xl" title="Import Workspace" > <Stack> <template v-if="successfulImport"> <p>This workspace has been replaced!</p> <VButton icon="refresh" @click="refreshHandler">Reload</VButton> </template> <template v-else> <template v-if="!approvalInFlight"> <p> You are about to import a workspace. Please note that all data currently in the local workspace will be overwritten and replaced with the contents of this workspace. </p> <p> To continue, please select the workspace you would like to import, and confirm the loss of local data: </p> <ErrorMessage :requestStatus="loadExportsReqStatus" /> <template v-if="loadExportsReqStatus.isSuccess"> <VormInput v-model="selectedExportId" label="Select workspace" placeholder="- Select a workspace -" required requiredMessage="Select a workspace to continue" type="dropdown" :disabled="workspaceImportOptions.length < 1" :options="workspaceImportOptions" /> </template> <template v-else-if="loadExportsReqStatus.isPending"> <VormInput label="Select workspace" type="container"> <Inline alignY="center"> <Icon name="loader" /> <div>Loading your workspace exports</div> </Inline> </VormInput> </template> <VormInput v-model="confirmedDataLoss" noLabel required requiredMessage="You must check this box to continue" type="checkbox" > I understand my local workspace data will be overwritten </VormInput> <ErrorMessage :requestStatus="importReqStatus" /> <ErrorMessage :message="workspaceStore.importError" /> <VButton :label=" requiresVoting ? 'Begin Approval Process' : 'Import workspace' " :loading="workspaceStore.importLoading" :loadingText=" requiresVoting ? 'Beginning Approval Process' : 'Importing...' " :requestStatus=" requiresVoting ? beginApprovalProcessReqStatus : importReqStatus " icon="cloud-download" @click=" requiresVoting ? beginApprovalHandler() : importWorkspaceHandler() " /> </template> <template v-if="approvalInFlight && !allApproved"> <div :class=" clsx( 'p-sm flex items-center gap-3', !importedByYou && 'border-b dark:border-neutral-500', ) " > <UserIcon v-if="importUser" :user="importUser" /> <div> <template v-if="importedByYou">You have</template> <template v-else> <span class="italic">{{ importUser?.name }}</span> has </template> clicked the Import workspace button. <template v-if="importedByYou"> There are other users online in this workspace, so they will get the chance to reject the import workspace workflow. </template> <template v-else> <div class="pt-4"> <span class="text-sm"> The following workspace will be imported: </span> <ul class="text-xs indent-4"> <li> Workspace Name: {{ workspaceStore.workspaceImportSummary ?.importedWorkspaceName }} </li> <li> Created At: {{ workspaceStore.workspaceImportSummary ?.workspaceExportCreatedAt }} </li> <li> Created By: {{ workspaceStore.workspaceImportSummary ?.workspaceExportCreatedBy }} </li> </ul> </div> </template> <div class="flex w-full text-xs justify-center pt-4 gap-xs"> <Icon name="tools" size="sm" tone="warning" /> Importing a workspace means replacing all the change sets in this workspace. They cannot be recovered. If you want to save current work please export your workspace now. </div> </div> </div> <div> <template v-if="importedByYou"> <div class="flex w-full justify-center pb-2"> <VButton icon="tools" label="Override vote and apply" loadingText="Importing Workspace" size="sm" tone="success" @click="importWorkspaceHandler" /> </div> <div class="text-sm pb-2 italic text-center w-full text-neutral-400 border-b dark:border-neutral-500" > <template v-if="!allUsersVoted" >Waiting on other users in the change set to vote.</template > </div> <div class="pt-2"> <div v-for="(user, index) in presenceStore.users" :key="index" class="flex items-center pr-sm justify-center gap-4" > <div class="min-w-0"> <UserCard :user="user" hideChangeSetInfo /> </div> <Icon v-if=" workspaceStore.workspaceApprovals[user.pk] === 'Approve' " class="text-success-400" name="thumbs-up" size="lg" /> <Icon v-else-if=" workspaceStore.workspaceApprovals[user.pk] === 'Reject' " class="text-destructive-500 dark:text-destructive-600" name="thumbs-down" size="lg" /> </div> </div> </template> <template v-else> <template v-if="!successfullyVoted"> <div class="flex w-full justify-center pt-2 gap-xs"> <VButton icon="thumbs-up" label="Go ahead" loadingText="Approving" tone="success" variant="ghost" @click="importApprovalVote('Approve')" /> <VButton icon="thumbs-down" label="No" loadingText="Rejecting" tone="error" variant="ghost" @click="importApprovalVote('Reject')" /> </div> </template> <template v-if="successfullyVoted"> <div class="flex gap-4 w-full p-xs"> <Icon name="lock" size="lg" tone="warning" /> <span class="text-sm align-middle"> This workspace locked until all users in it have voted on the import or {{ importUser?.name }} has taken further action. </span> </div> </template> </template> </div> </template> <template v-if="importFlow"> <template v-if="importReqStatus.isPending || workspaceStore.importLoading" > <LoadingMessage> Importing your workspace! <template #moreContent> <p class="italic text-sm"> Please do not refresh while this in progress. </p> </template> </LoadingMessage> </template> </template> <template v-if="rejectedWorkflow && importedByYou"> <span class="text-sm pb-2"> One of the users in this workspace has rejected the import. You can either override and continue the import, above or 'Cancel' the import flow </span> <VButton label="Cancel Import Flow" loadingText="Cancelling Import Flow" size="sm" tone="warning" variant="ghost" @click="cancelApprovalHandler()" /> </template> </template> </Stack> </Modal> </template> <script lang="ts" setup> import * as _ from "lodash-es"; import { ErrorMessage, Icon, Inline, LoadingMessage, Modal, Stack, VButton, VormInput, useModal, useValidatedInputGroup, } from "@si/vue-lib/design-system"; import { computed, ref, watch } from "vue"; import clsx from "clsx"; import { useModuleStore, RemoteModuleSummary } from "@/store/module.store"; import { usePresenceStore } from "@/store/presence.store"; import UserIcon from "@/components/layout/navbar/UserIcon.vue"; import { useAuthStore } from "@/store/auth.store"; import UserCard from "@/components/layout/navbar/UserCard.vue"; import { useWorkspacesStore } from "@/store/workspaces.store"; const authStore = useAuthStore(); const presenceStore = usePresenceStore(); const workspaceStore = useWorkspacesStore(); const requiresVoting = computed(() => presenceStore.users.length > 0); const modalRef = ref<InstanceType<typeof Modal>>(); const { open: openModal, close } = useModal(modalRef); const { validationMethods } = useValidatedInputGroup(); const moduleStore = useModuleStore(); const loadExportsReqStatus = moduleStore.getRequestStatus( "LIST_WORKSPACE_EXPORTS", ); const importReqStatus = workspaceStore.getRequestStatus( "BEGIN_WORKSPACE_IMPORT", ); const exportsList = ref<RemoteModuleSummary[]>([]); const selectedExportId = ref<string>(); const confirmedDataLoss = ref(false); const hasSkippedEdges = ref(false); const hasSkippedAttributes = ref(false); const allUsersVoted = ref<boolean>(); const importFlow = ref(false); const successfullyVoted = ref<boolean>(); const rejectedWorkflow = ref<boolean>(); const allApproved = ref<boolean>(false); const successfulImport = ref<boolean>(); function resetState() { importFlow.value = false; rejectedWorkflow.value = false; allApproved.value = false; successfullyVoted.value = false; allUsersVoted.value = false; successfulImport.value = false; } async function open() { selectedExportId.value = undefined; confirmedDataLoss.value = false; const exportResponse = await moduleStore.LIST_WORKSPACE_EXPORTS(); if (exportResponse.result.success) { exportsList.value = exportResponse.result.data.modules.map((workspace) => ({ ...workspace, hash: workspace.latestHash, hashCreatedAt: workspace.latestHashCreatedAt, })); } hasSkippedEdges.value = false; hasSkippedAttributes.value = false; resetState(); openModal(); } async function importApprovalVote(vote: string) { await workspaceStore.IMPORT_WORKSPACE_VOTE(vote); successfullyVoted.value = true; } const beginApprovalProcessReqStatus = workspaceStore.getRequestStatus( "BEGIN_APPROVAL_PROCESS", ); async function beginApprovalHandler() { if (selectedExportId.value) { await workspaceStore.BEGIN_APPROVAL_PROCESS(selectedExportId.value); allApproved.value = false; } } async function cancelApprovalHandler() { await workspaceStore.CANCEL_APPROVAL_PROCESS(); modalRef.value?.close(); resetState(); } async function importWorkspaceHandler() { if (validationMethods.hasError()) return; if (!selectedExportId.value) return; allApproved.value = true; importFlow.value = true; rejectedWorkflow.value = false; await workspaceStore.BEGIN_WORKSPACE_IMPORT(selectedExportId.value); } const importUser = computed( () => presenceStore.usersById[ workspaceStore.workspaceImportSummary?.importRequestedByUserPk || "" ], ); const importedByYou = computed( () => workspaceStore.workspaceImportSummary?.importRequestedByUserPk === authStore.user?.pk, ); const approvalInFlight = computed( () => !!workspaceStore.workspaceImportSummary, ); watch(approvalInFlight, () => { if (approvalInFlight.value) { modalRef.value?.open(); } }); const importFinished = computed(() => !!workspaceStore.importCompletedAt); watch( () => !!workspaceStore.importCompletedAt, () => { if (importFinished.value) { successfulImport.value = true; } }, ); const importCancelled = computed(() => !!workspaceStore.importCancelledAt); watch( () => !!workspaceStore.importCancelledAt, () => { if (importCancelled.value) { modalRef.value?.close(); resetState(); } }, ); function refreshHandler() { window.location.reload(); } watch( () => workspaceStore.workspaceApprovals, () => { if (!importedByYou.value) return; if ( _.values(workspaceStore.workspaceApprovals).length !== presenceStore.users.length + 1 // This is the number of other users + the person who triggered the merge ) return; if ( _.every( _.values(workspaceStore.workspaceApprovals), (a) => a === "Approve", ) ) { importWorkspaceHandler(); } else { rejectedWorkflow.value = true; allUsersVoted.value = true; } }, { deep: true, }, ); const blockExit = computed(() => { if (approvalInFlight.value) { return !importedByYou.value; } return ( importReqStatus.value.isPending || importReqStatus.value.isSuccess || workspaceStore.importLoading ); }); const workspaceImportOptions = computed(() => { if (!exportsList.value) return []; return exportsList.value.map((item) => ({ value: item.id, label: `${item.name} ${item.createdAt}`, })); }); defineExpose({ open, close }); </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