Add workspace DDL lock env var and maintenance mode UI (#19130)
## Summary - Add `WORKSPACE_SCHEMA_DDL_LOCKED` env-only boolean config variable that blocks all workspace schema DDL changes when set to `true`. This is intended for hot upgrades where logical replication cannot handle DDL changes. Enforced at two chokepoints: - `WorkspaceMigrationRunnerService.run` — blocks all metadata-driven DDL (object/field/index CRUD, app sync/uninstall, standard app sync, upgrade commands) - `WorkspaceDataSourceService.createWorkspaceDBSchema` / `deleteWorkspaceDBSchema` — blocks workspace creation (sign-up) and hard deletion. Uses a dedicated `WorkspaceDataSourceException` (not ForbiddenException) - Add maintenance mode feature with Admin Panel UI and user-facing banner: - **Backend**: `MaintenanceModeService` stores maintenance window (startAt, endAt, optional link) in `core.keyValuePair` as `CONFIG_VARIABLE`. Validates endAt > startAt. Uses `GraphQLISODateTime` scalar for date fields. Exposed via `clientConfig` REST endpoint and admin GraphQL mutations (`setMaintenanceMode`, `clearMaintenanceMode`) - **Admin Panel**: New "Maintenance Mode" section in Health tab with UTC datetime pickers and activate/deactivate controls - **Banner**: `InformationBannerMaintenance` displayed at the top of `DefaultLayout` for all users, using Temporal API for timezone-aware formatting with an optional "Learn more" link These two features are **independent** — the DDL lock is controlled via env var for operational use, while maintenance mode is a UI notification mechanism controlled from the admin panel.
This commit is contained in:
@@ -12,6 +12,8 @@ import { isConfigVariablesInDbEnabledState } from '@/client-config/states/isConf
|
||||
import { isDeveloperDefaultSignInPrefilledState } from '@/client-config/states/isDeveloperDefaultSignInPrefilledState';
|
||||
import { isClickHouseConfiguredState } from '@/client-config/states/isClickHouseConfiguredState';
|
||||
import { isCloudflareIntegrationEnabledState } from '@/client-config/states/isCloudflareIntegrationEnabledState';
|
||||
import { isDDLLockedState } from '@/client-config/states/isDDLLockedState';
|
||||
import { maintenanceModeState } from '@/client-config/states/maintenanceModeState';
|
||||
import { isEmailingDomainsEnabledState } from '@/client-config/states/isEmailingDomainsEnabledState';
|
||||
import { isEmailVerificationRequiredState } from '@/client-config/states/isEmailVerificationRequiredState';
|
||||
import { isGoogleCalendarEnabledState } from '@/client-config/states/isGoogleCalendarEnabledState';
|
||||
@@ -117,6 +119,10 @@ export const useClientConfig = (): UseClientConfigResult => {
|
||||
isClickHouseConfiguredState,
|
||||
);
|
||||
|
||||
const setIsDDLLocked = useSetAtomState(isDDLLockedState);
|
||||
|
||||
const setMaintenanceMode = useSetAtomState(maintenanceModeState);
|
||||
|
||||
const setAppVersion = useSetAtomState(appVersionState);
|
||||
|
||||
const fetchClientConfig = useCallback(async () => {
|
||||
@@ -195,6 +201,8 @@ export const useClientConfig = (): UseClientConfigResult => {
|
||||
clientConfig?.isCloudflareIntegrationEnabled,
|
||||
);
|
||||
setIsClickHouseConfigured(clientConfig?.isClickHouseConfigured ?? false);
|
||||
setIsDDLLocked(clientConfig?.isWorkspaceSchemaDDLLocked ?? false);
|
||||
setMaintenanceMode(clientConfig?.maintenance ?? null);
|
||||
} catch (err) {
|
||||
const error =
|
||||
err instanceof Error ? err : new Error('Failed to fetch client config');
|
||||
@@ -229,7 +237,9 @@ export const useClientConfig = (): UseClientConfigResult => {
|
||||
setIsEmailingDomainsEnabled,
|
||||
setIsClickHouseConfigured,
|
||||
setIsCloudflareIntegrationEnabled,
|
||||
setIsDDLLocked,
|
||||
setLabPublicFeatureFlags,
|
||||
setMaintenanceMode,
|
||||
setIsMicrosoftCalendarEnabled,
|
||||
setIsMicrosoftMessagingEnabled,
|
||||
setSentryConfig,
|
||||
|
||||
Reference in New Issue
Block a user