diff --git a/.gitignore b/.gitignore index 7adaaf1344..a544be5c5b 100644 --- a/.gitignore +++ b/.gitignore @@ -104,4 +104,5 @@ result # Temp migrate-colors.js -antd.variable.css \ No newline at end of file +antd.variable.css +CLAUDE.md \ No newline at end of file diff --git a/packages/nc-gui/assets/nc-icons/check-2.svg b/packages/nc-gui/assets/nc-icons/check-2.svg index 70a7d34c26..1f25818710 100644 --- a/packages/nc-gui/assets/nc-icons/check-2.svg +++ b/packages/nc-gui/assets/nc-icons/check-2.svg @@ -1,7 +1,7 @@ - - + + diff --git a/packages/nc-gui/components/dashboard/TreeView/ProjectNode.vue b/packages/nc-gui/components/dashboard/TreeView/ProjectNode.vue index b31124afc2..2013abb2a8 100644 --- a/packages/nc-gui/components/dashboard/TreeView/ProjectNode.vue +++ b/packages/nc-gui/components/dashboard/TreeView/ProjectNode.vue @@ -369,12 +369,6 @@ const duplicateProject = (base: BaseType) => { isDuplicateDlgOpen.value = true } -const isConvertToManagedAppDlgOpen = ref(false) - -const convertToManagedApp = () => { - isConvertToManagedAppDlgOpen.value = true -} - const tableDelete = () => { isTableDeleteDialogVisible.value = true $e('c:table:delete') @@ -619,7 +613,6 @@ defineExpose({ @click-menu="onClickMenu" @rename="enableEditMode()" @duplicate-project="duplicateProject($event)" - @convert-to-managed-app="convertToManagedApp" @copy-project-info="copyProjectInfo()" @open-erd-view="openErdView($event)" @open-base-settings="openBaseSettings($event)" @@ -680,7 +673,6 @@ defineExpose({ @click-menu="onClickMenu" @rename="enableEditMode(true)" @duplicate-project="duplicateProject($event)" - @convert-to-managed-app="convertToManagedApp" @copy-project-info="copyProjectInfo()" @open-erd-view="openErdView($event)" @open-base-settings="openBaseSettings($event)" @@ -765,7 +757,6 @@ defineExpose({ /> -
diff --git a/packages/nc-gui/components/dlg/AirtableImport.vue b/packages/nc-gui/components/dlg/AirtableImport.vue index a4e69def26..128699dbca 100644 --- a/packages/nc-gui/components/dlg/AirtableImport.vue +++ b/packages/nc-gui/components/dlg/AirtableImport.vue @@ -407,8 +407,8 @@ const collapseKey = ref('')
diff --git a/packages/nc-gui/components/dlg/ConvertToManagedApp.vue b/packages/nc-gui/components/dlg/ConvertToManagedApp.vue deleted file mode 100644 index 7afbb5e6a6..0000000000 --- a/packages/nc-gui/components/dlg/ConvertToManagedApp.vue +++ /dev/null @@ -1,185 +0,0 @@ - - - - - diff --git a/packages/nc-gui/components/dlg/QuickImport.vue b/packages/nc-gui/components/dlg/QuickImport.vue index daac056455..9231402ee4 100644 --- a/packages/nc-gui/components/dlg/QuickImport.vue +++ b/packages/nc-gui/components/dlg/QuickImport.vue @@ -1036,8 +1036,8 @@ watch(
diff --git a/packages/nc-gui/components/dlg/Table/Create.vue b/packages/nc-gui/components/dlg/Table/Create.vue index b9b613e590..6329652499 100644 --- a/packages/nc-gui/components/dlg/Table/Create.vue +++ b/packages/nc-gui/components/dlg/Table/Create.vue @@ -488,8 +488,8 @@ watch(_baseId, () => { diff --git a/packages/nc-gui/components/nc/Modal.vue b/packages/nc-gui/components/nc/Modal.vue index 7bcd3dc6b1..253bbd30aa 100644 --- a/packages/nc-gui/components/nc/Modal.vue +++ b/packages/nc-gui/components/nc/Modal.vue @@ -149,7 +149,7 @@ if (stopEventPropogation.value) {
-const props = defineProps<{ - visible: boolean - managedApp: any - currentVersion: any - initialTab?: 'publish' | 'fork' | 'deployments' -}>() - -const emit = defineEmits(['update:visible', 'published', 'forked']) - -const { $api } = useNuxtApp() -const baseStore = useBase() -const { base } = storeToRefs(baseStore) - -const activeTab = ref('publish') -const isLoading = ref(false) -const isLoadingDeployments = ref(false) -const versions = ref([]) -const deploymentStats = ref(null) - -// Version deployments modal -const showVersionDeploymentsModal = ref(false) -const selectedVersion = ref(null) - -// Publish form (for draft versions) -const publishForm = reactive({ - releaseNotes: '', -}) - -// Fork form (for creating new draft from published) -const forkForm = reactive({ - version: '', - releaseNotes: '', -}) - -const isPublished = computed(() => props.currentVersion?.status === 'published') -const isDraft = computed(() => props.currentVersion?.status === 'draft') - -// Load versions -const loadVersions = async () => { - 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: 'managedAppVersionsList', - managedAppId: props.managedApp.id, - } as any) - if (response?.list) { - versions.value = response.list - } - } catch (e: any) { - console.error('Failed to load versions:', e) - } -} - -// Load real deployment statistics -const loadDeployments = async () => { - 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: 'managedAppDeployments', - managedAppId: props.managedApp.id, - } as any) - if (response) { - deploymentStats.value = response - } - } catch (e: any) { - console.error('Failed to load deployments:', e) - } finally { - isLoadingDeployments.value = false - } -} - -const publishCurrentDraft = async () => { - if (!base.value?.fk_workspace_id || !base.value?.id || !props.currentVersion?.id) return - - isLoading.value = true - try { - await $api.internal.postOperation( - base.value.fk_workspace_id, - base.value.id, - { - operation: 'managedAppPublish', - }, - { - managedAppVersionId: props.currentVersion.id, - }, - ) - - // Reload base to get updated managed app version info - if (base.value?.id) { - await baseStore.loadProject() - } - - message.success(`Version ${props.currentVersion.version} published successfully!`) - emit('published') - emit('update:visible', false) - } catch (e: any) { - message.error(await extractSdkResponseErrorMsg(e)) - } finally { - isLoading.value = false - } -} - -const createNewDraft = async () => { - if (!base.value?.fk_workspace_id || !base.value?.id || !props.managedApp?.id) return - if (!forkForm.version) { - message.error('Please provide a version') - return - } - - isLoading.value = true - try { - await $api.internal.postOperation( - base.value.fk_workspace_id, - base.value.id, - { - operation: 'managedAppCreateDraft', - }, - { - managedAppId: props.managedApp.id, - version: forkForm.version, - }, - ) - - // Reload base to get updated managed app version info - if (base.value?.id) { - await baseStore.loadProject() - } - - message.success(`New draft version ${forkForm.version} created successfully!`) - emit('forked') - emit('update:visible', false) - } catch (e: any) { - message.error(await extractSdkResponseErrorMsg(e)) - } finally { - isLoading.value = false - } -} - -const formatDate = (dateString: string) => { - if (!dateString) return 'N/A' - const date = new Date(dateString) - return new Intl.DateTimeFormat('en-US', { - year: 'numeric', - month: 'short', - day: 'numeric', - hour: '2-digit', - minute: '2-digit', - }).format(date) -} - -const suggestNextVersion = () => { - if (!props.currentVersion?.version) { - forkForm.version = '1.0.0' - return - } - - const currentVersion = props.currentVersion.version - const versionParts = currentVersion.split('.') - if (versionParts.length === 3) { - // Increment minor version for new draft - versionParts[1] = String(Number(versionParts[1]) + 1) - versionParts[2] = '0' - forkForm.version = versionParts.join('.') - } else { - forkForm.version = '' - } -} - -const openVersionDeploymentsModal = (version: any) => { - selectedVersion.value = version - showVersionDeploymentsModal.value = true -} - -watch( - () => props.visible, - async (val) => { - if (val) { - // Use initialTab if provided, otherwise default based on version status - if (props.initialTab) { - activeTab.value = props.initialTab - } else if (isDraft.value) { - activeTab.value = 'publish' - } else if (isPublished.value) { - activeTab.value = 'fork' - } else { - // Fallback to deployments if version status is unclear - activeTab.value = 'deployments' - } - - await loadVersions() - await loadDeployments() - if (!isDraft.value && !props.initialTab) { - suggestNextVersion() - } - } - }, -) - - - - - - - diff --git a/packages/nc-gui/components/smartsheet/topbar/ManagedAppStatus/index.vue b/packages/nc-gui/components/smartsheet/topbar/ManagedAppStatus/index.vue new file mode 100644 index 0000000000..7c3f5c658a --- /dev/null +++ b/packages/nc-gui/components/smartsheet/topbar/ManagedAppStatus/index.vue @@ -0,0 +1,3 @@ + diff --git a/packages/nc-gui/components/smartsheet/topbar/ManagedAppVersionDeploymentsModal.vue b/packages/nc-gui/components/smartsheet/topbar/ManagedAppVersionDeploymentsModal.vue deleted file mode 100644 index 5c0ea46d02..0000000000 --- a/packages/nc-gui/components/smartsheet/topbar/ManagedAppVersionDeploymentsModal.vue +++ /dev/null @@ -1,513 +0,0 @@ - - - - - diff --git a/packages/nc-gui/components/workspace/project/create/ManagedApp.vue b/packages/nc-gui/components/workspace/project/create/ManagedApp.vue index a97e1dea62..93a297098d 100644 --- a/packages/nc-gui/components/workspace/project/create/ManagedApp.vue +++ b/packages/nc-gui/components/workspace/project/create/ManagedApp.vue @@ -1,251 +1,16 @@ - - - - diff --git a/packages/nc-gui/lang/en.json b/packages/nc-gui/lang/en.json index c5dffab4dc..f9434b997f 100644 --- a/packages/nc-gui/lang/en.json +++ b/packages/nc-gui/lang/en.json @@ -302,6 +302,7 @@ "refresh": "Refresh", "reset": "Reset", "install": "Install", + "installed": "Installed", "show": "Show", "access": "Access", "visibility": "Visibility", @@ -1185,6 +1186,9 @@ "updateDetails": "Update Details", "published": "Published", "draft": "Draft", + "live": "Live", + "upToDate": "Up to date", + "updateAvailable": "Update available", "managedAppTitle": "Title", "managedAppDescription": "Description", "managedAppCategory": "Category", diff --git a/packages/nc-gui/lib/types.ts b/packages/nc-gui/lib/types.ts index 13686fd3da..28fa190500 100644 --- a/packages/nc-gui/lib/types.ts +++ b/packages/nc-gui/lib/types.ts @@ -229,6 +229,8 @@ type NcProject = BaseType & { managed_app_master?: boolean managed_app_id?: string managed_app_version_id?: string + managed_app_version?: string + managed_app_published_at?: string auto_update?: boolean managed_app_schema_locked?: boolean } diff --git a/packages/nc-gui/store/base.ts b/packages/nc-gui/store/base.ts index 8816bf6849..e96916aae8 100644 --- a/packages/nc-gui/store/base.ts +++ b/packages/nc-gui/store/base.ts @@ -18,8 +18,16 @@ export const useBase = defineStore('baseStore', () => { const basesStore = useBases() + const managedApp = ref(null) + + const managedAppVersions = ref([]) + + const managedAppVersionsInfo = computed(() => {}) + const isManagedAppMaster = ref(false) + const isManagedAppInstaller = ref(false) + const baseId = computed(() => { // In shared base mode, use activeProjectId from basesStore which has the correct base ID if (route.value.params.typeOrId === 'base') { @@ -268,6 +276,10 @@ export const useBase = defineStore('baseStore', () => { return `${basUrl}${projectPage ? `?page=${projectPage}` : ''}` } + const loadManagedApp = async () => {} + + const loadCurrentVersion = async () => {} + watch( () => route.value.params.baseType, (n) => { @@ -343,6 +355,12 @@ export const useBase = defineStore('baseStore', () => { isPrivateBase, showBaseAccessRequestOverlay, isManagedAppMaster, + isManagedAppInstaller, + managedApp, + loadManagedApp, + loadCurrentVersion, + managedAppVersions, + managedAppVersionsInfo, } }) diff --git a/packages/nc-gui/utils/baseUtils.ts b/packages/nc-gui/utils/baseUtils.ts index be9cb05fbe..e9963f882f 100644 --- a/packages/nc-gui/utils/baseUtils.ts +++ b/packages/nc-gui/utils/baseUtils.ts @@ -104,3 +104,20 @@ export const extractAiBaseCreateQueryParams = (query: any) => { return searchQuery } + +export const suggestManagedAppNextVersion = (currentVersion?: string) => { + if (!currentVersion) { + return '1.0.0' + } + + const versionParts = currentVersion.split('.') + + if (versionParts.length === 3) { + // Increment minor version for new draft + versionParts[1] = String(Number(versionParts[1]) + 1) + versionParts[2] = '0' + return versionParts.join('.') + } else { + return '' + } +} diff --git a/packages/nocodb/src/controllers/internal/operationScopes.ts b/packages/nocodb/src/controllers/internal/operationScopes.ts index d845f5cfd6..b6e7603125 100644 --- a/packages/nocodb/src/controllers/internal/operationScopes.ts +++ b/packages/nocodb/src/controllers/internal/operationScopes.ts @@ -127,8 +127,9 @@ export const OPERATION_SCOPES = { // Managed App Operations managedAppStoreList: 'org', - managedAppList: 'workspace', managedAppGet: 'org', + managedAppVersionsList: 'org', + managedAppList: 'workspace', managedAppCreate: 'workspace', managedAppUpdate: 'base', managedAppDelete: 'base', @@ -137,7 +138,6 @@ export const OPERATION_SCOPES = { managedAppUnpublish: 'base', managedAppInstall: 'workspace', managedAppGetUpdates: 'base', - managedAppVersionsList: 'base', managedAppInstallationsList: 'base', managedAppDeployments: 'base', managedAppVersionDeployments: 'base', diff --git a/packages/nocodb/src/models/Base.ts b/packages/nocodb/src/models/Base.ts index 8974a4d720..cc168f81e5 100644 --- a/packages/nocodb/src/models/Base.ts +++ b/packages/nocodb/src/models/Base.ts @@ -59,6 +59,9 @@ export default class Base implements BaseType { managed_app_id?: string; // Points to MANAGED_APPS (for both master and installed instances) managed_app_version_id?: string; // Current version ID from MANAGED_APP_VERSIONS auto_update?: boolean; // For installed instances: auto-update to new published versions + // managed app info (populated fields) + managed_app_version?: string; // Current version string + managed_app_published_at?: string; // When this version was published managed_app_schema_locked?: boolean; // Computed: whether schema modifications are allowed constructor(base: Partial) { @@ -69,8 +72,8 @@ export default class Base implements BaseType { return base && new Base(base); } - public static async computeSchemaLocked(_base: Base): Promise { - return false; + public static async populateManagedAppInfo(_base: Base): Promise { + return; } public static async createProject( @@ -289,9 +292,8 @@ export default class Base implements BaseType { } const base = this.castType(baseData); - // Compute managed_app_schema_locked if (base && base.managed_app_id) { - base.managed_app_schema_locked = await this.computeSchemaLocked(base); + await this.populateManagedAppInfo(base); } return base; @@ -370,9 +372,8 @@ export default class Base implements BaseType { if (baseData) { const base = this.castType(baseData); - // Compute managed_app_schema_locked if (base.managed_app_id) { - base.managed_app_schema_locked = await this.computeSchemaLocked(base); + await this.populateManagedAppInfo(base); } await base.getSources(includeConfig, ncMeta);