mirror of
https://github.com/nocodb/nocodb.git
synced 2026-02-01 23:48:33 +00:00
refactor: sandbox to managed app remaining
This commit is contained in:
@@ -369,10 +369,10 @@ const duplicateProject = (base: BaseType) => {
|
||||
isDuplicateDlgOpen.value = true
|
||||
}
|
||||
|
||||
const isConvertToSandboxDlgOpen = ref(false)
|
||||
const isConvertToManagedAppDlgOpen = ref(false)
|
||||
|
||||
const convertToSandbox = () => {
|
||||
isConvertToSandboxDlgOpen.value = true
|
||||
const convertToManagedApp = () => {
|
||||
isConvertToManagedAppDlgOpen.value = true
|
||||
}
|
||||
|
||||
const tableDelete = () => {
|
||||
@@ -619,7 +619,7 @@ defineExpose({
|
||||
@click-menu="onClickMenu"
|
||||
@rename="enableEditMode()"
|
||||
@duplicate-project="duplicateProject($event)"
|
||||
@convert-to-sandbox="convertToSandbox"
|
||||
@convert-to-managed-app="convertToManagedApp"
|
||||
@copy-project-info="copyProjectInfo()"
|
||||
@open-erd-view="openErdView($event)"
|
||||
@open-base-settings="openBaseSettings($event)"
|
||||
@@ -680,7 +680,7 @@ defineExpose({
|
||||
@click-menu="onClickMenu"
|
||||
@rename="enableEditMode(true)"
|
||||
@duplicate-project="duplicateProject($event)"
|
||||
@convert-to-sandbox="convertToSandbox"
|
||||
@convert-to-managed-app="convertToManagedApp"
|
||||
@copy-project-info="copyProjectInfo()"
|
||||
@open-erd-view="openErdView($event)"
|
||||
@open-base-settings="openBaseSettings($event)"
|
||||
@@ -765,7 +765,7 @@ defineExpose({
|
||||
/>
|
||||
<DlgBaseDelete v-model:visible="isBaseDeleteDialogVisible" :base-id="base?.id" />
|
||||
<DlgBaseDuplicate v-if="selectedProjectToDuplicate" v-model="isDuplicateDlgOpen" :base="selectedProjectToDuplicate" />
|
||||
<DlgConvertToSandbox v-if="base?.id" v-model:visible="isConvertToSandboxDlgOpen" :base-id="base.id" />
|
||||
<DlgConvertToManagedApp v-if="base?.id" v-model:visible="isConvertToManagedAppDlgOpen" :base-id="base.id" />
|
||||
<GeneralModal v-model:visible="isErdModalOpen" size="large">
|
||||
<div class="h-[80vh]">
|
||||
<LazyDashboardSettingsErd :base-id="base?.id" :source-id="activeBaseId" />
|
||||
|
||||
@@ -11,7 +11,7 @@ const visible = defineModel<boolean>('visible', { required: true })
|
||||
const { $api } = useNuxtApp()
|
||||
const { t } = useI18n()
|
||||
|
||||
const initialSanboxFormState = ref<Record<string, any>>({
|
||||
const initialManagedAppFormState = ref<Record<string, any>>({
|
||||
title: '',
|
||||
description: '',
|
||||
category: '',
|
||||
@@ -22,13 +22,13 @@ const { base } = storeToRefs(useBase())
|
||||
|
||||
const basesStore = useBases()
|
||||
|
||||
const convertToSandbox = async (formState: Record<string, any>) => {
|
||||
const convertToManagedApp = async (formState: Record<string, any>) => {
|
||||
try {
|
||||
const response = await $api.internal.postOperation(
|
||||
base.value!.fk_workspace_id as string,
|
||||
props.baseId,
|
||||
{
|
||||
operation: 'sandboxCreate',
|
||||
operation: 'managedAppCreate',
|
||||
} as any,
|
||||
{
|
||||
title: formState.title,
|
||||
@@ -38,18 +38,18 @@ const convertToSandbox = async (formState: Record<string, any>) => {
|
||||
},
|
||||
)
|
||||
|
||||
message.success(t('msg.success.sandboxCreated'))
|
||||
message.success(t('msg.success.managedAppCreated'))
|
||||
visible.value = false
|
||||
|
||||
// Update the base with the sandbox_id from response
|
||||
if (response && response.sandbox_id) {
|
||||
// Update the base with the managed_app_id from response
|
||||
if (response && response.managed_app_id) {
|
||||
const currentBase = basesStore.bases.get(props.baseId)
|
||||
if (currentBase) {
|
||||
;(currentBase as any).sandbox_id = response.sandbox_id
|
||||
;(currentBase as any).managed_app_id = response.managed_app_id
|
||||
}
|
||||
}
|
||||
|
||||
// Reload base to ensure all sandbox data is loaded
|
||||
// Reload base to ensure all managed app data is loaded
|
||||
await basesStore.loadProject(props.baseId, true)
|
||||
} catch (e: any) {
|
||||
message.error(await extractSdkResponseErrorMsg(e))
|
||||
@@ -60,7 +60,7 @@ const { formState, isLoading, submit } = useProvideFormBuilderHelper({
|
||||
formSchema: [
|
||||
{
|
||||
type: FormBuilderInputType.Input,
|
||||
label: t('labels.sandboxTitle'),
|
||||
label: t('labels.managedAppTitle'),
|
||||
span: 24,
|
||||
model: 'title',
|
||||
placeholder: 'Enter a descriptive title',
|
||||
@@ -75,7 +75,7 @@ const { formState, isLoading, submit } = useProvideFormBuilderHelper({
|
||||
},
|
||||
{
|
||||
type: FormBuilderInputType.Textarea,
|
||||
label: t('labels.sandboxDescription'),
|
||||
label: t('labels.managedAppDescription'),
|
||||
span: 24,
|
||||
model: 'description',
|
||||
placeholder: "Describe your application's capabilities",
|
||||
@@ -83,7 +83,7 @@ const { formState, isLoading, submit } = useProvideFormBuilderHelper({
|
||||
},
|
||||
{
|
||||
type: FormBuilderInputType.Input,
|
||||
label: t('labels.sandboxCategory'),
|
||||
label: t('labels.managedAppCategory'),
|
||||
span: 12,
|
||||
model: 'category',
|
||||
placeholder: 'e.g., CRM, HR',
|
||||
@@ -91,7 +91,7 @@ const { formState, isLoading, submit } = useProvideFormBuilderHelper({
|
||||
},
|
||||
{
|
||||
type: FormBuilderInputType.Select,
|
||||
label: t('labels.sandboxVisibility'),
|
||||
label: t('labels.managedAppVisibility'),
|
||||
span: 12,
|
||||
model: 'visibility',
|
||||
category: FORM_BUILDER_NON_CATEGORIZED,
|
||||
@@ -104,9 +104,9 @@ const { formState, isLoading, submit } = useProvideFormBuilderHelper({
|
||||
},
|
||||
],
|
||||
onSubmit: async () => {
|
||||
return await convertToSandbox(formState.value)
|
||||
return await convertToManagedApp(formState.value)
|
||||
},
|
||||
initialState: initialSanboxFormState,
|
||||
initialState: initialManagedAppFormState,
|
||||
})
|
||||
|
||||
watch(visible, (isVisible) => {
|
||||
@@ -127,7 +127,7 @@ watch(visible, (isVisible) => {
|
||||
size="sm"
|
||||
height="auto"
|
||||
centered
|
||||
wrap-class-name="nc-modal-convert-to-sandbox "
|
||||
wrap-class-name="nc-modal-convert-to-managed-app "
|
||||
nc-modal-class-name="!p-0"
|
||||
>
|
||||
<div class="p-4 w-full flex items-center gap-3 border-b border-nc-border-gray-medium">
|
||||
@@ -135,7 +135,7 @@ watch(visible, (isVisible) => {
|
||||
<GeneralIcon icon="ncBox" class="w-5 h-5 text-white" />
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="font-semibold text-lg text-nc-content-gray-emphasis">Convert to Sandbox</div>
|
||||
<div class="font-semibold text-lg text-nc-content-gray-emphasis">Convert to Managed App</div>
|
||||
<div class="text-xs text-nc-content-gray-subtle2">{{ $t('labels.publishToAppStore') }}</div>
|
||||
</div>
|
||||
|
||||
@@ -169,14 +169,14 @@ watch(visible, (isVisible) => {
|
||||
<template #icon>
|
||||
<GeneralIcon icon="ncBox" />
|
||||
</template>
|
||||
Convert to sandbox
|
||||
Convert to Managed App
|
||||
</NcButton>
|
||||
</div>
|
||||
</NcModal>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.nc-modal-convert-to-sandbox {
|
||||
.nc-modal-convert-to-managed-app {
|
||||
.nc-modal {
|
||||
max-height: min(90vh, 540px) !important;
|
||||
height: min(90vh, 540px) !important;
|
||||
@@ -12,7 +12,7 @@ const { visibility, showShareModal } = storeToRefs(useShare())
|
||||
|
||||
const { activeTable } = storeToRefs(useTablesStore())
|
||||
|
||||
const { base, isSharedBase, isSandboxMaster } = storeToRefs(useBase())
|
||||
const { base, isSharedBase, isManagedAppMaster } = storeToRefs(useBase())
|
||||
|
||||
const { hideSharedBaseBtn } = storeToRefs(useConfigStore())
|
||||
|
||||
@@ -46,7 +46,7 @@ const copySharedBase = async () => {
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="!isSharedBase && !isSandboxMaster && isUIAllowed('baseShare') && visibility !== 'hidden' && (activeTable || base)"
|
||||
v-if="!isSharedBase && !isManagedAppMaster && isUIAllowed('baseShare') && visibility !== 'hidden' && (activeTable || base)"
|
||||
class="nc-share-base-button flex flex-col justify-center"
|
||||
data-testid="share-base-button"
|
||||
:data-sharetype="visibility"
|
||||
|
||||
@@ -232,7 +232,7 @@ onMounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!showEmptySkeleton && !isMobileMode" class="flex items-center gap-2">
|
||||
<SmartsheetTopbarSandboxStatus />
|
||||
<SmartsheetTopbarManagedAppStatus />
|
||||
<LazyGeneralShareProject />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -72,8 +72,8 @@ const topbarBreadcrumbItemWidth = computed(() => {
|
||||
<div class="flex items-center justify-end gap-2 flex-1">
|
||||
<GeneralApiLoader v-if="!isMobileMode && !activeScriptId && !activeDashboardId" />
|
||||
|
||||
<!-- Sandbox Status -->
|
||||
<LazySmartsheetTopbarSandboxStatus v-if="!isSharedBase && !isMobileMode" />
|
||||
<!-- Managed App Status -->
|
||||
<LazySmartsheetTopbarManagedAppStatus v-if="!isSharedBase && !isMobileMode" />
|
||||
|
||||
<NcButton
|
||||
v-if="
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
visible: boolean
|
||||
sandbox: any
|
||||
managedApp: any
|
||||
currentVersion: any
|
||||
initialTab?: 'publish' | 'fork' | 'deployments'
|
||||
}>()
|
||||
@@ -38,12 +38,12 @@ const isDraft = computed(() => props.currentVersion?.status === 'draft')
|
||||
|
||||
// Load versions
|
||||
const loadVersions = async () => {
|
||||
if (!props.sandbox?.id || !base.value?.fk_workspace_id) return
|
||||
if (!props.managedApp?.id || !base.value?.fk_workspace_id) return
|
||||
|
||||
try {
|
||||
const response = await $api.internal.getOperation(base.value.fk_workspace_id, base.value.id!, {
|
||||
operation: 'sandboxVersionsList',
|
||||
sandboxId: props.sandbox.id,
|
||||
operation: 'managedAppVersionsList',
|
||||
managedAppId: props.managedApp.id,
|
||||
} as any)
|
||||
if (response?.list) {
|
||||
versions.value = response.list
|
||||
@@ -55,13 +55,13 @@ const loadVersions = async () => {
|
||||
|
||||
// Load real deployment statistics
|
||||
const loadDeployments = async () => {
|
||||
if (!props.sandbox?.id || !base.value?.fk_workspace_id) return
|
||||
if (!props.managedApp?.id || !base.value?.fk_workspace_id) return
|
||||
|
||||
isLoadingDeployments.value = true
|
||||
try {
|
||||
const response = await $api.internal.getOperation(base.value.fk_workspace_id, base.value.id!, {
|
||||
operation: 'sandboxDeployments',
|
||||
sandboxId: props.sandbox.id,
|
||||
operation: 'managedAppDeployments',
|
||||
managedAppId: props.managedApp.id,
|
||||
} as any)
|
||||
if (response) {
|
||||
deploymentStats.value = response
|
||||
@@ -82,14 +82,14 @@ const publishCurrentDraft = async () => {
|
||||
base.value.fk_workspace_id,
|
||||
base.value.id,
|
||||
{
|
||||
operation: 'sandboxPublish',
|
||||
operation: 'managedAppPublish',
|
||||
},
|
||||
{
|
||||
sandboxVersionId: props.currentVersion.id,
|
||||
managedAppVersionId: props.currentVersion.id,
|
||||
},
|
||||
)
|
||||
|
||||
// Reload base to get updated sandbox version info
|
||||
// Reload base to get updated managed app version info
|
||||
if (base.value?.id) {
|
||||
await baseStore.loadProject()
|
||||
}
|
||||
@@ -105,7 +105,7 @@ const publishCurrentDraft = async () => {
|
||||
}
|
||||
|
||||
const createNewDraft = async () => {
|
||||
if (!base.value?.fk_workspace_id || !base.value?.id || !props.sandbox?.id) return
|
||||
if (!base.value?.fk_workspace_id || !base.value?.id || !props.managedApp?.id) return
|
||||
if (!forkForm.version) {
|
||||
message.error('Please provide a version')
|
||||
return
|
||||
@@ -117,15 +117,15 @@ const createNewDraft = async () => {
|
||||
base.value.fk_workspace_id,
|
||||
base.value.id,
|
||||
{
|
||||
operation: 'sandboxCreateDraft',
|
||||
operation: 'managedAppCreateDraft',
|
||||
},
|
||||
{
|
||||
sandboxId: props.sandbox.id,
|
||||
managedAppId: props.managedApp.id,
|
||||
version: forkForm.version,
|
||||
},
|
||||
)
|
||||
|
||||
// Reload base to get updated sandbox version info
|
||||
// Reload base to get updated managed app version info
|
||||
if (base.value?.id) {
|
||||
await baseStore.loadProject()
|
||||
}
|
||||
@@ -205,29 +205,29 @@ watch(
|
||||
<NcModal
|
||||
:visible="visible"
|
||||
size="lg"
|
||||
nc-modal-class-name="nc-modal-sandbox-management"
|
||||
nc-modal-class-name="nc-modal-managed-app-management"
|
||||
centered
|
||||
@update:visible="emit('update:visible', $event)"
|
||||
>
|
||||
<div class="flex flex-col h-full">
|
||||
<!-- Header with Tabs -->
|
||||
<div class="nc-sandbox-header">
|
||||
<div class="nc-managed-app-header">
|
||||
<div class="flex items-center gap-3 flex-1">
|
||||
<div class="nc-sandbox-icon">
|
||||
<div class="nc-managed-app-icon">
|
||||
<GeneralIcon icon="ncBox" class="h-5 w-5 text-white" />
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="text-lg font-semibold text-nc-content-gray-emphasis">Sandbox Management</div>
|
||||
<div class="text-lg font-semibold text-nc-content-gray-emphasis">Managed App Management</div>
|
||||
<div class="text-xs text-nc-content-gray-subtle2">Manage versions and track deployments</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tabs (Segmented Control) -->
|
||||
<div class="nc-sandbox-tabs">
|
||||
<div class="nc-managed-app-tabs">
|
||||
<div class="flex items-center">
|
||||
<div
|
||||
v-if="isDraft"
|
||||
class="nc-sandbox-tab"
|
||||
class="nc-managed-app-tab"
|
||||
:class="{ selected: activeTab === 'publish' }"
|
||||
@click="activeTab = 'publish'"
|
||||
>
|
||||
@@ -236,14 +236,14 @@ watch(
|
||||
</div>
|
||||
<div
|
||||
v-if="isPublished"
|
||||
class="nc-sandbox-tab"
|
||||
class="nc-managed-app-tab"
|
||||
:class="{ selected: activeTab === 'fork' }"
|
||||
@click="activeTab = 'fork'"
|
||||
>
|
||||
<GeneralIcon icon="ncGitBranch" class="h-4 w-4 flex-none opacity-75" />
|
||||
<span>Fork</span>
|
||||
</div>
|
||||
<div class="nc-sandbox-tab" :class="{ selected: activeTab === 'deployments' }" @click="activeTab = 'deployments'">
|
||||
<div class="nc-managed-app-tab" :class="{ selected: activeTab === 'deployments' }" @click="activeTab = 'deployments'">
|
||||
<GeneralIcon icon="ncServer" class="h-4 w-4 flex-none opacity-75" />
|
||||
<span>Deployments</span>
|
||||
</div>
|
||||
@@ -473,7 +473,7 @@ watch(
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div v-if="activeTab === 'publish' || activeTab === 'fork'" class="nc-sandbox-footer">
|
||||
<div v-if="activeTab === 'publish' || activeTab === 'fork'" class="nc-managed-app-footer">
|
||||
<div class="flex justify-end gap-2">
|
||||
<NcButton type="secondary" size="small" @click="emit('update:visible', false)"> Cancel </NcButton>
|
||||
|
||||
@@ -502,30 +502,30 @@ watch(
|
||||
</div>
|
||||
|
||||
<!-- Version Deployments Modal -->
|
||||
<SmartsheetTopbarSandboxVersionDeploymentsModal
|
||||
<SmartsheetTopbarManagedAppVersionDeploymentsModal
|
||||
v-model:visible="showVersionDeploymentsModal"
|
||||
:sandbox="sandbox"
|
||||
:managed-app="managedApp"
|
||||
:version="selectedVersion"
|
||||
/>
|
||||
</NcModal>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.nc-sandbox-header {
|
||||
.nc-managed-app-header {
|
||||
@apply flex items-center gap-4 px-4 py-3 border-b-1 border-nc-border-gray-medium;
|
||||
}
|
||||
|
||||
.nc-sandbox-icon {
|
||||
.nc-managed-app-icon {
|
||||
@apply w-10 h-10 rounded-xl flex items-center justify-center;
|
||||
background: linear-gradient(135deg, var(--nc-content-brand) 0%, var(--nc-content-blue-medium) 100%);
|
||||
box-shadow: 0 2px 4px rgba(51, 102, 255, 0.15);
|
||||
}
|
||||
|
||||
.nc-sandbox-tabs {
|
||||
.nc-managed-app-tabs {
|
||||
@apply flex bg-nc-bg-gray-medium rounded-lg p-1;
|
||||
}
|
||||
|
||||
.nc-sandbox-tab {
|
||||
.nc-managed-app-tab {
|
||||
@apply px-3 py-1.5 flex items-center gap-2 text-xs rounded-md select-none cursor-pointer;
|
||||
@apply text-nc-content-gray-subtle2 transition-all duration-200;
|
||||
|
||||
@@ -539,7 +539,7 @@ watch(
|
||||
}
|
||||
}
|
||||
|
||||
.nc-sandbox-footer {
|
||||
.nc-managed-app-footer {
|
||||
@apply px-6 py-3 border-t-1 border-nc-border-gray-medium;
|
||||
}
|
||||
|
||||
@@ -711,7 +711,7 @@ watch(
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
.nc-modal-sandbox-management {
|
||||
.nc-modal-managed-app-management {
|
||||
@apply !p-0;
|
||||
}
|
||||
</style>
|
||||
@@ -5,40 +5,40 @@ const { $api } = useNuxtApp()
|
||||
const isModalVisible = ref(false)
|
||||
const initialTab = ref<'publish' | 'fork' | 'deployments' | undefined>(undefined)
|
||||
|
||||
const sandbox = ref<any>(null)
|
||||
const managedApp = ref<any>(null)
|
||||
const currentVersion = ref<any>(null)
|
||||
|
||||
const isSandboxMaster = computed(() => !!(base.value as any)?.sandbox_master && !!(base.value as any)?.sandbox_id)
|
||||
const isManagedAppMaster = computed(() => !!(base.value as any)?.managed_app_master && !!(base.value as any)?.managed_app_id)
|
||||
|
||||
// Load sandbox info and current version
|
||||
const loadSandbox = async () => {
|
||||
if (!(base.value as any)?.sandbox_id || !base.value?.fk_workspace_id) return
|
||||
// Load managed app info and current version
|
||||
const loadManagedApp = async () => {
|
||||
if (!(base.value as any)?.managed_app_id || !base.value?.fk_workspace_id) return
|
||||
|
||||
try {
|
||||
const response = await $api.internal.getOperation(base.value.fk_workspace_id, base.value.id!, {
|
||||
operation: 'sandboxGet',
|
||||
operation: 'managedAppGet',
|
||||
baseId: base.value.id,
|
||||
} as any)
|
||||
if (response) {
|
||||
sandbox.value = response
|
||||
managedApp.value = response
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to load sandbox:', e)
|
||||
console.error('Failed to load managed app:', e)
|
||||
}
|
||||
}
|
||||
|
||||
// Load current version info
|
||||
const loadCurrentVersion = async () => {
|
||||
if (!base.value?.sandbox_version_id || !base.value?.fk_workspace_id) return
|
||||
if (!base.value?.managed_app_version_id || !base.value?.fk_workspace_id) return
|
||||
|
||||
try {
|
||||
// Get version details from versions list
|
||||
const response = await $api.internal.getOperation(base.value.fk_workspace_id, base.value.id!, {
|
||||
operation: 'sandboxVersionsList',
|
||||
sandboxId: (base.value as any).sandbox_id,
|
||||
operation: 'managedAppVersionsList',
|
||||
managedAppId: (base.value as any).managed_app_id,
|
||||
} as any)
|
||||
if (response?.list) {
|
||||
currentVersion.value = response.list.find((v: any) => v.id === base.value.sandbox_version_id)
|
||||
currentVersion.value = response.list.find((v: any) => v.id === base.value.managed_app_version_id)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to load current version:', e)
|
||||
@@ -51,20 +51,20 @@ const openModal = (tab?: 'publish' | 'fork' | 'deployments') => {
|
||||
}
|
||||
|
||||
const handlePublished = async () => {
|
||||
await loadSandbox()
|
||||
await loadManagedApp()
|
||||
await loadCurrentVersion()
|
||||
}
|
||||
|
||||
const handleForked = async () => {
|
||||
await loadSandbox()
|
||||
await loadManagedApp()
|
||||
await loadCurrentVersion()
|
||||
}
|
||||
|
||||
watch(
|
||||
() => (base.value as any)?.sandbox_id,
|
||||
async (sandboxId) => {
|
||||
if (sandboxId) {
|
||||
await loadSandbox()
|
||||
() => (base.value as any)?.managed_app_id,
|
||||
async (managedAppId) => {
|
||||
if (managedAppId) {
|
||||
await loadManagedApp()
|
||||
await loadCurrentVersion()
|
||||
}
|
||||
},
|
||||
@@ -73,13 +73,13 @@ watch(
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="isSandboxMaster" class="flex items-center gap-2">
|
||||
<div v-if="isManagedAppMaster" class="flex items-center gap-2">
|
||||
<!-- Version Badge (clickable to open modal) -->
|
||||
<div
|
||||
class="flex items-center gap-1.5 px-2.5 py-1 bg-nc-bg-gray-light rounded-md border-1 border-nc-border-gray-medium cursor-pointer hover:(bg-nc-bg-gray-medium border-nc-border-gray-dark) transition-colors"
|
||||
@click="openModal()"
|
||||
>
|
||||
<GeneralIcon icon="ncInfoSolid" class="w-3.5 h-3.5 text-nc-content-gray nc-sanbox-status-info-icon" />
|
||||
<GeneralIcon icon="ncInfoSolid" class="w-3.5 h-3.5 text-nc-content-gray nc-managed-app-status-info-icon" />
|
||||
<span class="text-xs font-mono font-semibold text-nc-content-gray-emphasis">v{{ currentVersion?.version || '1.0.0' }}</span>
|
||||
<div
|
||||
v-if="currentVersion?.status === 'draft'"
|
||||
@@ -96,10 +96,10 @@ watch(
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sandbox Modal -->
|
||||
<SmartsheetTopbarSandboxModal
|
||||
<!-- Managed App Modal -->
|
||||
<SmartsheetTopbarManagedAppModal
|
||||
v-model:visible="isModalVisible"
|
||||
:sandbox="sandbox"
|
||||
:managed-app="managedApp"
|
||||
:current-version="currentVersion"
|
||||
:initial-tab="initialTab"
|
||||
@published="handlePublished"
|
||||
@@ -108,7 +108,7 @@ watch(
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.nc-sanbox-status-info-icon path.nc-icon-inner) {
|
||||
:deep(.nc-managed-app-status-info-icon path.nc-icon-inner) {
|
||||
stroke: var(--nc-bg-gray-light) !important;
|
||||
}
|
||||
</style>
|
||||
@@ -3,7 +3,7 @@ import { DeploymentStatus } from 'nocodb-sdk'
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean
|
||||
sandbox: any
|
||||
managedApp: any
|
||||
version: any
|
||||
}>()
|
||||
|
||||
@@ -27,14 +27,14 @@ const logsPageSize = 10
|
||||
const isLoadingLogs = ref(false)
|
||||
|
||||
const loadDeployments = async (page = 1) => {
|
||||
if (!props.sandbox?.id || !props.version?.versionId || !base.value?.fk_workspace_id) return
|
||||
if (!props.managedApp?.id || !props.version?.versionId || !base.value?.fk_workspace_id) return
|
||||
|
||||
isLoading.value = true
|
||||
try {
|
||||
const offset = (page - 1) * pageSize
|
||||
const response = await $api.internal.getOperation(base.value.fk_workspace_id, base.value.id!, {
|
||||
operation: 'sandboxVersionDeployments',
|
||||
sandboxId: props.sandbox.id,
|
||||
operation: 'managedAppVersionDeployments',
|
||||
managedAppId: props.managedApp.id,
|
||||
versionId: props.version.versionId,
|
||||
limit: pageSize,
|
||||
offset,
|
||||
@@ -59,7 +59,7 @@ const loadDeploymentLogs = async (baseId: string, page = 1) => {
|
||||
try {
|
||||
const offset = (page - 1) * logsPageSize
|
||||
const response = await $api.internal.getOperation(base.value.fk_workspace_id, base.value.id!, {
|
||||
operation: 'sandboxDeploymentLogs',
|
||||
operation: 'managedAppDeploymentLogs',
|
||||
baseId,
|
||||
limit: logsPageSize,
|
||||
offset,
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
interface SandboxType {
|
||||
interface ManagedAppType {
|
||||
id: string
|
||||
title: string
|
||||
description?: string
|
||||
@@ -22,7 +22,7 @@ const visible = useVModel(props, 'visible', emit)
|
||||
const { $api } = useNuxtApp()
|
||||
const { t } = useI18n()
|
||||
|
||||
const sandboxes = ref<SandboxType[]>([])
|
||||
const managedApps = ref<ManagedAppType[]>([])
|
||||
const loading = ref(false)
|
||||
const installing = ref<string | null>(null)
|
||||
const searchQuery = ref('')
|
||||
@@ -30,10 +30,10 @@ const selectedCategory = ref<string | undefined>(undefined)
|
||||
|
||||
const categories = computed(() => {
|
||||
const cats = new Set<string>()
|
||||
sandboxes.value.forEach((sb) => {
|
||||
if (sb.category) {
|
||||
managedApps.value.forEach((ma) => {
|
||||
if (ma.category) {
|
||||
// Split comma-separated categories
|
||||
sb.category.split(',').forEach((cat) => {
|
||||
ma.category.split(',').forEach((cat) => {
|
||||
const trimmed = cat.trim()
|
||||
if (trimmed) cats.add(trimmed)
|
||||
})
|
||||
@@ -42,28 +42,28 @@ const categories = computed(() => {
|
||||
return Array.from(cats).sort()
|
||||
})
|
||||
|
||||
const filteredSandboxes = computed(() => {
|
||||
let filtered = sandboxes.value
|
||||
const filteredManagedApps = computed(() => {
|
||||
let filtered = managedApps.value
|
||||
|
||||
if (selectedCategory.value) {
|
||||
const selected = selectedCategory.value
|
||||
filtered = filtered.filter((sb) => {
|
||||
if (!sb.category) return false
|
||||
filtered = filtered.filter((ma) => {
|
||||
if (!ma.category) return false
|
||||
// Check if selected category exists in comma-separated list
|
||||
const categories = sb.category.split(',').map((c) => c.trim())
|
||||
const categories = ma.category.split(',').map((c) => c.trim())
|
||||
return categories.includes(selected)
|
||||
})
|
||||
}
|
||||
|
||||
if (searchQuery.value) {
|
||||
const query = searchQuery.value.toLowerCase()
|
||||
filtered = filtered.filter((sb) => searchCompare([sb.title, sb.description, sb.category], query))
|
||||
filtered = filtered.filter((ma) => searchCompare([ma.title, ma.description, ma.category], query))
|
||||
}
|
||||
|
||||
return filtered
|
||||
})
|
||||
|
||||
const loadSandboxes = async () => {
|
||||
const loadManagedApps = async () => {
|
||||
if (!props.workspaceId) {
|
||||
console.error('WorkspaceId is required')
|
||||
return
|
||||
@@ -77,10 +77,10 @@ const loadSandboxes = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await $api.internal.getOperation(props.workspaceId, NO_SCOPE, {
|
||||
operation: 'sandboxStoreList',
|
||||
operation: 'managedAppStoreList',
|
||||
})
|
||||
|
||||
sandboxes.value = response?.list || []
|
||||
managedApps.value = response?.list || []
|
||||
} catch (e: any) {
|
||||
console.error('API error:', e)
|
||||
message.error(await extractSdkResponseErrorMsg(e))
|
||||
@@ -89,23 +89,23 @@ const loadSandboxes = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const installSandbox = async (sandbox: SandboxType) => {
|
||||
installing.value = sandbox.id
|
||||
const installManagedApp = async (managedApp: ManagedAppType) => {
|
||||
installing.value = managedApp.id
|
||||
try {
|
||||
await $api.internal.postOperation(
|
||||
props.workspaceId,
|
||||
NO_SCOPE,
|
||||
{
|
||||
operation: 'sandboxInstall',
|
||||
operation: 'managedAppInstall',
|
||||
},
|
||||
{
|
||||
sandboxId: sandbox.id,
|
||||
managedAppId: managedApp.id,
|
||||
target_workspace_id: props.workspaceId,
|
||||
},
|
||||
)
|
||||
|
||||
message.success(t('msg.success.baseInstalled'))
|
||||
emit('installed', sandbox)
|
||||
emit('installed', managedApp)
|
||||
visible.value = false
|
||||
} catch (e: any) {
|
||||
message.error(await extractSdkResponseErrorMsg(e))
|
||||
@@ -129,7 +129,7 @@ watch(
|
||||
() => props.workspaceId,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
loadSandboxes()
|
||||
loadManagedApps()
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
@@ -177,8 +177,8 @@ watch(
|
||||
</div>
|
||||
|
||||
<!-- Results count -->
|
||||
<div v-if="!loading && filteredSandboxes.length > 0" class="mt-3 text-xs text-nc-content-gray-muted">
|
||||
{{ filteredSandboxes.length }} {{ filteredSandboxes.length === 1 ? 'app' : 'apps' }} available
|
||||
<div v-if="!loading && filteredManagedApps.length > 0" class="mt-3 text-xs text-nc-content-gray-muted">
|
||||
{{ filteredManagedApps.length }} {{ filteredManagedApps.length === 1 ? 'app' : 'apps' }} available
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -193,7 +193,7 @@ watch(
|
||||
</div>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div v-else-if="filteredSandboxes.length === 0" class="nc-app-market-empty">
|
||||
<div v-else-if="filteredManagedApps.length === 0" class="nc-app-market-empty">
|
||||
<div class="nc-empty-icon">
|
||||
<GeneralIcon icon="ncBox" class="h-10 w-10 text-nc-content-gray-muted" />
|
||||
</div>
|
||||
@@ -209,7 +209,7 @@ watch(
|
||||
|
||||
<!-- App List -->
|
||||
<div v-else class="nc-app-market-list">
|
||||
<div v-for="sandbox in filteredSandboxes" :key="sandbox.id" class="nc-app-item">
|
||||
<div v-for="managedApp in filteredManagedApps" :key="managedApp.id" class="nc-app-item">
|
||||
<div class="nc-app-item-content">
|
||||
<!-- App Icon & Info -->
|
||||
<div class="nc-app-info">
|
||||
@@ -218,10 +218,10 @@ watch(
|
||||
</div>
|
||||
<div class="nc-app-details">
|
||||
<div class="nc-app-title-row">
|
||||
<h3 class="nc-app-title">{{ sandbox.title }}</h3>
|
||||
<div v-if="sandbox.category" class="nc-app-categories">
|
||||
<h3 class="nc-app-title">{{ managedApp.title }}</h3>
|
||||
<div v-if="managedApp.category" class="nc-app-categories">
|
||||
<div
|
||||
v-for="cat in sandbox.category
|
||||
v-for="cat in managedApp.category
|
||||
.split(',')
|
||||
.map((c) => c.trim())
|
||||
.filter(Boolean)"
|
||||
@@ -236,20 +236,20 @@ watch(
|
||||
<p
|
||||
class="nc-app-description"
|
||||
:class="{
|
||||
'!text-nc-content-gray-muted': !sandbox.description,
|
||||
'!text-nc-content-gray-muted': !managedApp.description,
|
||||
}"
|
||||
>
|
||||
{{ sandbox.description || 'No description available' }}
|
||||
{{ managedApp.description || 'No description available' }}
|
||||
</p>
|
||||
<div class="nc-app-meta">
|
||||
<span class="nc-app-meta-item">
|
||||
<GeneralIcon icon="download" class="h-3.5 w-3.5" />
|
||||
<span class="font-medium">{{ formatInstallCount(sandbox.install_count || 0) }}</span>
|
||||
<span class="font-medium">{{ formatInstallCount(managedApp.install_count || 0) }}</span>
|
||||
<span class="text-nc-content-gray-muted">installs</span>
|
||||
</span>
|
||||
<span v-if="sandbox.version" class="nc-app-meta-item">
|
||||
<span v-if="managedApp.version" class="nc-app-meta-item">
|
||||
<GeneralIcon icon="gitCommit" class="h-3.5 w-3.5" />
|
||||
<span>v{{ sandbox.version }}</span>
|
||||
<span>v{{ managedApp.version }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -258,16 +258,16 @@ watch(
|
||||
<!-- Install Button -->
|
||||
<div class="nc-app-action">
|
||||
<NcButton
|
||||
:loading="installing === sandbox.id"
|
||||
:loading="installing === managedApp.id"
|
||||
:disabled="!!installing"
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="installSandbox(sandbox)"
|
||||
@click="installManagedApp(managedApp)"
|
||||
>
|
||||
<template #icon>
|
||||
<GeneralIcon icon="download" class="h-4 w-4" />
|
||||
</template>
|
||||
{{ installing === sandbox.id ? 'Installing...' : t('general.install') }}
|
||||
{{ installing === managedApp.id ? 'Installing...' : t('general.install') }}
|
||||
</NcButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@ interface Props {
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {})
|
||||
|
||||
const emit = defineEmits(['update:aiMode', 'update:mode', 'sandboxInstalled', 'close'])
|
||||
const emit = defineEmits(['update:aiMode', 'update:mode', 'managedAppInstalled', 'close'])
|
||||
|
||||
const aiMode = useVModel(props, 'aiMode', emit)
|
||||
|
||||
@@ -48,9 +48,9 @@ const selectMode = (mode: CreateMode) => {
|
||||
emit('update:mode', mode)
|
||||
}
|
||||
|
||||
const onSandboxInstalled = (sandbox: any) => {
|
||||
const onManagedAppInstalled = (managedApp: any) => {
|
||||
showAppMarket.value = false
|
||||
emit('sandboxInstalled', sandbox)
|
||||
emit('managedAppInstalled', managedApp)
|
||||
}
|
||||
|
||||
const onAppMarketClose = () => {
|
||||
@@ -93,7 +93,7 @@ onMounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="isFeatureEnabled(FEATURE_FLAG.SANDBOXES)"
|
||||
v-if="isFeatureEnabled(FEATURE_FLAG.MANAGED_APPS)"
|
||||
v-e="['c:base:market:create']"
|
||||
class="nc-create-base-market"
|
||||
@click="selectMode('market')"
|
||||
@@ -115,7 +115,7 @@ onMounted(() => {
|
||||
v-if="showAppMarket && workspaceId"
|
||||
:workspace-id="workspaceId"
|
||||
@close="onAppMarketClose"
|
||||
@installed="onSandboxInstalled"
|
||||
@installed="onManagedAppInstalled"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -38,7 +38,7 @@ const createManagedApp = async (formState: Record<string, any>) => {
|
||||
activeWorkspaceId.value as string,
|
||||
formState.baseId || NO_SCOPE,
|
||||
{
|
||||
operation: 'sandboxCreate',
|
||||
operation: 'managedAppCreate',
|
||||
} as any,
|
||||
{
|
||||
title: formState.title,
|
||||
@@ -59,18 +59,18 @@ const createManagedApp = async (formState: Record<string, any>) => {
|
||||
},
|
||||
)
|
||||
|
||||
message.success(t('msg.success.sandboxCreated'))
|
||||
message.success(t('msg.success.managedAppCreated'))
|
||||
visible.value = false
|
||||
|
||||
// Update the base with the sandbox_id from response
|
||||
if (response && response.sandbox_id && formState.baseId) {
|
||||
// Update the base with the managed_app_id from response
|
||||
if (response && response.managed_app_id && formState.baseId) {
|
||||
const currentBase = basesStore.bases.get(formState.baseId as string)
|
||||
if (currentBase) {
|
||||
;(currentBase as any).sandbox_id = response.sandbox_id
|
||||
;(currentBase as any).managed_app_id = response.managed_app_id
|
||||
}
|
||||
}
|
||||
|
||||
// Reload base to ensure all sandbox data is loaded
|
||||
// Reload base to ensure all managed app data is loaded
|
||||
if (formState.baseId) {
|
||||
await basesStore.loadProject(formState.baseId, true)
|
||||
} else {
|
||||
@@ -92,7 +92,7 @@ const { formState, isLoading, submit } = useProvideFormBuilderHelper({
|
||||
formSchema: [
|
||||
{
|
||||
type: FormBuilderInputType.Input,
|
||||
label: t('labels.sandboxTitle'),
|
||||
label: t('labels.managedAppTitle'),
|
||||
span: 24,
|
||||
model: 'title',
|
||||
placeholder: 'Enter a descriptive title',
|
||||
@@ -111,7 +111,7 @@ const { formState, isLoading, submit } = useProvideFormBuilderHelper({
|
||||
},
|
||||
{
|
||||
type: FormBuilderInputType.Textarea,
|
||||
label: t('labels.sandboxDescription'),
|
||||
label: t('labels.managedAppDescription'),
|
||||
span: 24,
|
||||
model: 'description',
|
||||
placeholder: "Describe your application's capabilities",
|
||||
@@ -149,11 +149,11 @@ const { formState, isLoading, submit } = useProvideFormBuilderHelper({
|
||||
equal: 'existing',
|
||||
},
|
||||
defaultValue: undefined,
|
||||
filterOption: (base) => base && !base?.sandbox_id,
|
||||
filterOption: (base) => base && !base?.managed_app_id,
|
||||
},
|
||||
{
|
||||
type: FormBuilderInputType.Input,
|
||||
label: t('labels.sandboxCategory'),
|
||||
label: t('labels.managedAppCategory'),
|
||||
span: 12,
|
||||
model: 'category',
|
||||
placeholder: 'e.g., CRM, HR',
|
||||
@@ -161,7 +161,7 @@ const { formState, isLoading, submit } = useProvideFormBuilderHelper({
|
||||
},
|
||||
{
|
||||
type: FormBuilderInputType.Select,
|
||||
label: t('labels.sandboxVisibility'),
|
||||
label: t('labels.managedAppVisibility'),
|
||||
span: 12,
|
||||
model: 'visibility',
|
||||
category: FORM_BUILDER_NON_CATEGORIZED,
|
||||
@@ -234,7 +234,7 @@ const { formState, isLoading, submit } = useProvideFormBuilderHelper({
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.nc-modal-convert-to-sandbox {
|
||||
.nc-modal-convert-to-managed-app {
|
||||
.nc-modal {
|
||||
max-height: min(90vh, 540px) !important;
|
||||
height: min(90vh, 540px) !important;
|
||||
|
||||
@@ -3,9 +3,9 @@ import rfdc from 'rfdc'
|
||||
const deepClone = rfdc()
|
||||
const FEATURES = [
|
||||
{
|
||||
id: 'sandboxes',
|
||||
title: 'Sandboxes',
|
||||
description: 'Allow users to create replicable sandbox environments',
|
||||
id: 'managed_apps',
|
||||
title: 'Managed Apps',
|
||||
description: 'Allow users to create replicable managed app environments',
|
||||
enabled: false,
|
||||
isEngineering: true,
|
||||
isAdvanced: true,
|
||||
|
||||
@@ -1173,11 +1173,11 @@
|
||||
"visibilityAndDataHandling": "Visibility & Data Handling",
|
||||
"visibilityConfigLabel": "Base specific additional configurations to customise data display & default behaviours.",
|
||||
"migrateToV3": "Migrate to V3",
|
||||
"sandbox": "Sandbox",
|
||||
"sandboxAppStore": "Sandbox App Store",
|
||||
"managedApp": "Managed App",
|
||||
"managedAppStore": "Managed App Store",
|
||||
"baseMustBeV3": "Base must be V3",
|
||||
"createSandbox": "Create Sandbox",
|
||||
"sandboxDetails": "Sandbox Details",
|
||||
"createManagedApp": "Create Managed App",
|
||||
"managedAppDetails": "Managed App Details",
|
||||
"publishToAppStore": "Publish to App Store",
|
||||
"publishChanges": "Publish Changes",
|
||||
"publishingChanges": "Publishing changes",
|
||||
@@ -1185,11 +1185,11 @@
|
||||
"updateDetails": "Update Details",
|
||||
"published": "Published",
|
||||
"draft": "Draft",
|
||||
"sandboxTitle": "Title",
|
||||
"sandboxDescription": "Description",
|
||||
"sandboxCategory": "Category",
|
||||
"sandboxTags": "Tags",
|
||||
"sandboxVisibility": "Visibility",
|
||||
"managedAppTitle": "Title",
|
||||
"managedAppDescription": "Description",
|
||||
"managedAppCategory": "Category",
|
||||
"managedAppTags": "Tags",
|
||||
"managedAppVisibility": "Visibility",
|
||||
"snapShotSubText": "Snapshots serve as comprehensive backups of your base, capturing its state at the time of creation. Restoring a snapshot creates a new instance of the base in the designated workspace.",
|
||||
"newSnapshot": "New Snapshot",
|
||||
"searchASnapshot": "Search a snapshot",
|
||||
@@ -2480,7 +2480,7 @@
|
||||
"deleteProject": "Do you want to delete the base?",
|
||||
"shareBasePrivate": "Generate publicly shareable readonly base",
|
||||
"shareBasePublic": "Anyone on the internet with this link can view",
|
||||
"noSandboxesFound": "No apps found in the marketplace",
|
||||
"noManagedAppsFound": "No apps found in the marketplace",
|
||||
"userInviteNoSMTP": "Looks like you have not configured mailer yet! Please copy above invite link and send it to",
|
||||
"dragDropHide": "Drag and drop fields here to hide",
|
||||
"formInput": "Enter form input label",
|
||||
@@ -2807,10 +2807,10 @@
|
||||
},
|
||||
"success": {
|
||||
"passwordSet": "Password set successfully",
|
||||
"sandboxCreated": "Sandbox created successfully",
|
||||
"sandboxUpdated": "Sandbox updated successfully",
|
||||
"sandboxPublished": "Sandbox published successfully",
|
||||
"sandboxUnpublished": "Sandbox unpublished successfully",
|
||||
"managedAppCreated": "Managed App created successfully",
|
||||
"managedAppUpdated": "Managed App updated successfully",
|
||||
"managedAppPublished": "Managed App published successfully",
|
||||
"managedAppUnpublished": "Managed App unpublished successfully",
|
||||
"changesPublished": "Changes published to all installations successfully",
|
||||
"mcpTokenDeleted": "MCP Token Deleted",
|
||||
"mcpTokenUpdated": "MCP Token Updated",
|
||||
|
||||
@@ -149,6 +149,10 @@ export enum ImportWorkerResponse {
|
||||
ERROR = 'error',
|
||||
}
|
||||
|
||||
export enum FeatureFlag {
|
||||
MANAGED_APP = 'MANAGED_APP',
|
||||
}
|
||||
|
||||
export enum ImportType {
|
||||
EXCEL = 'excel',
|
||||
CSV = 'csv',
|
||||
@@ -227,5 +231,4 @@ export enum NcBaseCreateMode {
|
||||
BUILD_WITH_AI = 'buildWithAi',
|
||||
FROM_APP_STORE = 'fromAppStore',
|
||||
MANAGED_APP = 'managedApp',
|
||||
SANDBOX_APP = 'sandboxApp',
|
||||
}
|
||||
|
||||
@@ -225,12 +225,12 @@ type NcProject = BaseType & {
|
||||
users?: User[]
|
||||
default_role?: ProjectRoles | string
|
||||
version?: BaseVersion
|
||||
// Sandbox fields
|
||||
sandbox_master?: boolean
|
||||
sandbox_id?: string
|
||||
sandbox_version_id?: string
|
||||
// Managed App fields
|
||||
managed_app_master?: boolean
|
||||
managed_app_id?: string
|
||||
managed_app_version_id?: string
|
||||
auto_update?: boolean
|
||||
sandbox_schema_locked?: boolean
|
||||
managed_app_schema_locked?: boolean
|
||||
}
|
||||
|
||||
interface UndoRedoAction {
|
||||
|
||||
@@ -18,7 +18,7 @@ export const useBase = defineStore('baseStore', () => {
|
||||
|
||||
const basesStore = useBases()
|
||||
|
||||
const isSandboxMaster = ref(false)
|
||||
const isManagedAppMaster = ref(false)
|
||||
|
||||
const baseId = computed(() => {
|
||||
// In shared base mode, use activeProjectId from basesStore which has the correct base ID
|
||||
@@ -342,7 +342,7 @@ export const useBase = defineStore('baseStore', () => {
|
||||
idUserMap,
|
||||
isPrivateBase,
|
||||
showBaseAccessRequestOverlay,
|
||||
isSandboxMaster,
|
||||
isManagedAppMaster,
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { MetaTable } from '~/utils/globals';
|
||||
|
||||
const up = async (knex: Knex) => {
|
||||
// Create sandboxes table
|
||||
await knex.schema.createTable(MetaTable.SANDBOXES, (table) => {
|
||||
await knex.schema.createTable(MetaTable.SANDBOXES_OLD, (table) => {
|
||||
table.string('id', 20).primary();
|
||||
table.string('fk_workspace_id', 20).notNullable();
|
||||
table.string('base_id', 20).notNullable().unique();
|
||||
@@ -44,7 +44,7 @@ const up = async (knex: Knex) => {
|
||||
});
|
||||
|
||||
// Create sandbox_versions table to store serialized schemas for each published version
|
||||
await knex.schema.createTable(MetaTable.SANDBOX_VERSIONS, (table) => {
|
||||
await knex.schema.createTable(MetaTable.SANDBOX_VERSIONS_OLD, (table) => {
|
||||
table.string('id', 20).primary();
|
||||
table.string('fk_workspace_id', 20).notNullable();
|
||||
table.string('fk_sandbox_id', 20).notNullable();
|
||||
@@ -92,11 +92,11 @@ const up = async (knex: Knex) => {
|
||||
// Is this base a sandbox master?
|
||||
table.boolean('sandbox_master').defaultTo(false);
|
||||
|
||||
// Points to SANDBOXES.id (for both master and installed instances)
|
||||
// Points to SANDBOXES_OLD.id (for both master and installed instances)
|
||||
table.string('sandbox_id', 20);
|
||||
|
||||
// Current version: for master=draft/published being worked on, for installed=installed version
|
||||
// Points to SANDBOX_VERSIONS.id
|
||||
// Points to SANDBOX_VERSIONS_OLD.id
|
||||
table.string('sandbox_version_id', 20);
|
||||
|
||||
// For installed instances: auto-update to new published versions
|
||||
@@ -115,60 +115,69 @@ const up = async (knex: Knex) => {
|
||||
});
|
||||
|
||||
// Create sandbox_deployment_logs table
|
||||
await knex.schema.createTable(MetaTable.SANDBOX_DEPLOYMENT_LOGS, (table) => {
|
||||
table.string('id', 20).primary();
|
||||
table.string('fk_workspace_id', 20).notNullable();
|
||||
await knex.schema.createTable(
|
||||
MetaTable.SANDBOX_DEPLOYMENT_LOGS_OLD,
|
||||
(table) => {
|
||||
table.string('id', 20).primary();
|
||||
table.string('fk_workspace_id', 20).notNullable();
|
||||
|
||||
// Installation reference
|
||||
table.string('base_id', 20).notNullable(); // The installed base
|
||||
table.string('fk_sandbox_id', 20).notNullable(); // The sandbox being deployed
|
||||
// Installation reference
|
||||
table.string('base_id', 20).notNullable(); // The installed base
|
||||
table.string('fk_sandbox_id', 20).notNullable(); // The sandbox being deployed
|
||||
|
||||
// Version tracking
|
||||
table.string('from_version_id', 20); // NULL for initial install
|
||||
table.string('to_version_id', 20).notNullable(); // Target version
|
||||
// Version tracking
|
||||
table.string('from_version_id', 20); // NULL for initial install
|
||||
table.string('to_version_id', 20).notNullable(); // Target version
|
||||
|
||||
// Deployment status: 'pending', 'in_progress', 'success', 'failed'
|
||||
table.string('status', 20).notNullable().defaultTo('pending');
|
||||
// Deployment status: 'pending', 'in_progress', 'success', 'failed'
|
||||
table.string('status', 20).notNullable().defaultTo('pending');
|
||||
|
||||
// Deployment type: 'install', 'update'
|
||||
table.string('deployment_type', 20).notNullable();
|
||||
// Deployment type: 'install', 'update'
|
||||
table.string('deployment_type', 20).notNullable();
|
||||
|
||||
// Deployment details
|
||||
table.text('error_message'); // Error message if failed
|
||||
table.text('deployment_log'); // Detailed logs (JSON or text)
|
||||
table.text('meta'); // Additional metadata (JSON)
|
||||
// Deployment details
|
||||
table.text('error_message'); // Error message if failed
|
||||
table.text('deployment_log'); // Detailed logs (JSON or text)
|
||||
table.text('meta'); // Additional metadata (JSON)
|
||||
|
||||
// Timestamps
|
||||
table.timestamps(true, true);
|
||||
table.timestamp('started_at');
|
||||
table.timestamp('completed_at');
|
||||
// Timestamps
|
||||
table.timestamps(true, true);
|
||||
table.timestamp('started_at');
|
||||
table.timestamp('completed_at');
|
||||
|
||||
// Indexes for performance
|
||||
table.index(
|
||||
['fk_workspace_id'],
|
||||
'nc_sandbox_deployment_logs_workspace_id_idx',
|
||||
);
|
||||
table.index(['base_id'], 'nc_sandbox_deployment_logs_base_id_idx');
|
||||
table.index(['fk_sandbox_id'], 'nc_sandbox_deployment_logs_sandbox_id_idx');
|
||||
table.index(
|
||||
['base_id', 'created_at'],
|
||||
'nc_sandbox_deployment_logs_base_created_idx',
|
||||
);
|
||||
table.index(['status'], 'nc_sandbox_deployment_logs_status_idx');
|
||||
table.index(
|
||||
['from_version_id'],
|
||||
'nc_sandbox_deployment_logs_from_version_idx',
|
||||
);
|
||||
table.index(['to_version_id'], 'nc_sandbox_deployment_logs_to_version_idx');
|
||||
});
|
||||
// Indexes for performance
|
||||
table.index(
|
||||
['fk_workspace_id'],
|
||||
'nc_sandbox_deployment_logs_workspace_id_idx',
|
||||
);
|
||||
table.index(['base_id'], 'nc_sandbox_deployment_logs_base_id_idx');
|
||||
table.index(
|
||||
['fk_sandbox_id'],
|
||||
'nc_sandbox_deployment_logs_sandbox_id_idx',
|
||||
);
|
||||
table.index(
|
||||
['base_id', 'created_at'],
|
||||
'nc_sandbox_deployment_logs_base_created_idx',
|
||||
);
|
||||
table.index(['status'], 'nc_sandbox_deployment_logs_status_idx');
|
||||
table.index(
|
||||
['from_version_id'],
|
||||
'nc_sandbox_deployment_logs_from_version_idx',
|
||||
);
|
||||
table.index(
|
||||
['to_version_id'],
|
||||
'nc_sandbox_deployment_logs_to_version_idx',
|
||||
);
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const down = async (knex: Knex) => {
|
||||
// Drop sandbox_deployment_logs table
|
||||
await knex.schema.dropTable(MetaTable.SANDBOX_DEPLOYMENT_LOGS);
|
||||
await knex.schema.dropTable(MetaTable.SANDBOX_DEPLOYMENT_LOGS_OLD);
|
||||
|
||||
// Drop sandbox_versions table
|
||||
await knex.schema.dropTable(MetaTable.SANDBOX_VERSIONS);
|
||||
await knex.schema.dropTable(MetaTable.SANDBOX_VERSIONS_OLD);
|
||||
|
||||
// Drop indexes from bases table
|
||||
await knex.schema.alterTable(MetaTable.PROJECT, (table) => {
|
||||
@@ -190,7 +199,7 @@ const down = async (knex: Knex) => {
|
||||
});
|
||||
|
||||
// Drop sandboxes table
|
||||
await knex.schema.dropTable(MetaTable.SANDBOXES);
|
||||
await knex.schema.dropTable(MetaTable.SANDBOXES_OLD);
|
||||
};
|
||||
|
||||
export { up, down };
|
||||
|
||||
Reference in New Issue
Block a user