diff --git a/src/govoplan_admin/backend/api/v1/routes.py b/src/govoplan_admin/backend/api/v1/routes.py index 00a37a3..d47ffd7 100644 --- a/src/govoplan_admin/backend/api/v1/routes.py +++ b/src/govoplan_admin/backend/api/v1/routes.py @@ -75,6 +75,11 @@ from govoplan_core.core.module_installer_notifications import ( installer_notification_subject, ) from govoplan_core.core.module_license import module_license_decision, module_license_diagnostics +from govoplan_core.core.navigation import ( + NavigationPreferences, + navigation_preferences_from_settings, + update_navigation_preferences, +) from govoplan_core.core.module_package_catalog import ( record_module_package_catalog_acceptance, validate_module_package_catalog, @@ -153,6 +158,7 @@ SYSTEM_SETTINGS_SECTIONS = ( "languages", "privacy_retention_policy", "maintenance_mode", + "navigation", "settings", ) @@ -288,6 +294,7 @@ def _system_settings_item(session: Session) -> SystemSettingsItem: policy = privacy_policy_from_settings(item) maintenance_mode = saved_maintenance_mode(session) i18n_payload = system_i18n_payload(item) + navigation = navigation_preferences_from_settings(item.settings) return SystemSettingsItem( default_locale=item.default_locale, allow_tenant_custom_groups=item.allow_tenant_custom_groups, @@ -298,6 +305,7 @@ def _system_settings_item(session: Session) -> SystemSettingsItem: available_languages=i18n_payload["available_languages"], enabled_language_codes=i18n_payload["enabled_languages"], settings=item.settings or {}, + navigation=navigation.as_dict() if navigation is not None else None, ) @@ -316,6 +324,7 @@ def _system_settings_sections(item: SystemSettingsItem) -> dict[str, Any]: }, "privacy_retention_policy": payload["privacy_retention_policy"], "maintenance_mode": payload["maintenance_mode"], + "navigation": payload["navigation"], "settings": payload["settings"], } @@ -1810,6 +1819,15 @@ def write_system_settings( available_languages=available_languages, enabled_language_codes=enabled_language_codes, ) + if "navigation" in payload.model_fields_set: + item.settings = update_navigation_preferences( + item.settings, + NavigationPreferences( + order=tuple(payload.navigation.order), + hidden=tuple(payload.navigation.hidden), + locked=tuple(payload.navigation.locked), + ) if payload.navigation is not None else None, + ) if payload.privacy_retention_policy is not None: set_privacy_policy(item, payload.privacy_retention_policy.model_dump(mode="json")) if payload.maintenance_mode is not None: diff --git a/src/govoplan_admin/backend/api/v1/schemas.py b/src/govoplan_admin/backend/api/v1/schemas.py index 847d40e..1e8a526 100644 --- a/src/govoplan_admin/backend/api/v1/schemas.py +++ b/src/govoplan_admin/backend/api/v1/schemas.py @@ -5,7 +5,7 @@ from typing import Any, Literal from pydantic import BaseModel, ConfigDict, Field -from govoplan_core.api.v1.schemas import DeltaDeletedItem +from govoplan_core.api.v1.schemas import DeltaDeletedItem, NavigationPreferencesPayload from govoplan_core.i18n import REFERENCE_LANGUAGE_CODE from govoplan_core.privacy.schemas import PrivacyRetentionPolicyItem @@ -50,6 +50,7 @@ class SystemSettingsItem(BaseModel): available_languages: list[LanguagePackageItem] = Field(default_factory=list) enabled_language_codes: list[str] = Field(default_factory=list) settings: dict[str, Any] = Field(default_factory=dict) + navigation: NavigationPreferencesPayload | None = None class SystemSettingsDeltaResponse(BaseModel): @@ -73,6 +74,7 @@ class SystemSettingsUpdateRequest(BaseModel): maintenance_mode: MaintenanceModeItem | None = None available_languages: list[LanguagePackageItem] | None = None enabled_language_codes: list[str] | None = None + navigation: NavigationPreferencesPayload | None = None change_request_id: str | None = None diff --git a/src/govoplan_admin/backend/manifest.py b/src/govoplan_admin/backend/manifest.py index b890dc5..61a2903 100644 --- a/src/govoplan_admin/backend/manifest.py +++ b/src/govoplan_admin/backend/manifest.py @@ -142,6 +142,21 @@ manifest = ModuleManifest( ], }, ), + DocumentationTopic( + id="admin.system-navigation", + title="Set the system navigation baseline", + summary="System administrators define the instance-wide side-rail order, visibility baseline, and entries that lower scopes must keep visible.", + body=( + "The system navigation setting starts from module-declared defaults. Tenant settings take precedence over the system order and visibility, and personal settings take precedence over both. A system lock prevents tenant and user preferences from hiding an entry but does not prevent them from moving it. Removing the system preference restores module defaults. Navigation policy changes presentation only: module entitlement, View policy, and permissions still decide whether a surface is accessible." + ), + documentation_types=("admin",), + audience=("system_admin",), + related_modules=("tenancy", "access", "views"), + metadata={ + "kind": "reference", + "help_contexts": ["admin.system-settings"], + }, + ), DocumentationTopic( id="admin.module-lifecycle-workflow", title="Plan and supervise module lifecycle changes", diff --git a/webui/src/api/admin.ts b/webui/src/api/admin.ts index 6b11d11..48624db 100644 --- a/webui/src/api/admin.ts +++ b/webui/src/api/admin.ts @@ -1,4 +1,4 @@ -import type { ApiSettings, DeltaDeletedItem, PrivacyRetentionPolicy } from "@govoplan/core-webui"; +import type { ApiSettings, DeltaDeletedItem, NavigationPreferences, PrivacyRetentionPolicy } from "@govoplan/core-webui"; import { apiDownload, apiFetch, apiPath, apiQuery } from "@govoplan/core-webui"; export { fetchAdminOverview, fetchPermissionCatalog, fetchTenants } from "@govoplan/core-webui"; export type { @@ -33,6 +33,7 @@ export type SystemSettingsItem = { available_languages: LanguagePackage[]; enabled_language_codes: string[]; settings: Record; + navigation: NavigationPreferences | null; }; export type SystemSettingsDeltaSections = Partial<{ @@ -42,6 +43,7 @@ export type SystemSettingsDeltaSections = Partial<{ privacy_retention_policy: SystemSettingsItem["privacy_retention_policy"]; maintenance_mode: SystemSettingsItem["maintenance_mode"]; settings: SystemSettingsItem["settings"]; + navigation: SystemSettingsItem["navigation"]; }>; export type SystemSettingsUpdatePayload = { @@ -53,6 +55,7 @@ export type SystemSettingsUpdatePayload = { maintenance_mode?: MaintenanceMode | null; available_languages?: LanguagePackage[] | null; enabled_language_codes?: string[] | null; + navigation?: NavigationPreferences | null; change_request_id?: string | null; }; diff --git a/webui/src/features/admin/SystemSettingsPanel.tsx b/webui/src/features/admin/SystemSettingsPanel.tsx index a7f687f..4509292 100644 --- a/webui/src/features/admin/SystemSettingsPanel.tsx +++ b/webui/src/features/admin/SystemSettingsPanel.tsx @@ -5,7 +5,7 @@ import { Card } from "@govoplan/core-webui"; import { FormField } from "@govoplan/core-webui"; import { ToggleSwitch } from "@govoplan/core-webui"; import { fetchSystemSettingsDelta, updateSystemSettings, type LanguagePackage, type PrivacyRetentionLimitPermissions, type PrivacyRetentionPolicy, type SystemSettingsDeltaSections, type SystemSettingsItem } from "../../api/admin"; -import { AdminPageLayout, AdminSelectionList, DocumentationHelpLink, adminErrorMessage, useDeltaWatermarks, useUnsavedDraftGuard } from "@govoplan/core-webui"; +import { AdminPageLayout, AdminSelectionList, DocumentationHelpLink, NavigationPreferenceEditor, adminErrorMessage, configurableNavigationItemsForModules, dispatchPlatformModulesChanged, useDeltaWatermarks, usePlatformModules, useUnsavedDraftGuard } from "@govoplan/core-webui"; import { ADMIN_GOVERNANCE_DOCUMENTATION, ADMIN_INTERFACE_I18N, mutationDisabledReason } from "./interfacePatterns"; const DELTA_KEY = "admin:system-settings"; @@ -43,10 +43,13 @@ const fallback: SystemSettingsItem = { { code: "en", label: "English", native_label: "English" } ], enabled_language_codes: ["de", "en"], - settings: {} + settings: {}, + navigation: null }; export default function SystemSettingsPanel({ settings, canWrite, canAccessMaintenance }: {settings: ApiSettings;canWrite: boolean;canAccessMaintenance: boolean;}) { + const modules = usePlatformModules(); + const navigationItems = configurableNavigationItemsForModules(modules); const [draft, setDraft] = useState(fallback); const [savedDraft, setSavedDraft] = useState(fallback); const [loading, setLoading] = useState(true); @@ -102,11 +105,13 @@ export default function SystemSettingsPanel({ settings, canWrite, canAccessMaint allow_tenant_api_keys: draft.allow_tenant_api_keys, maintenance_mode: draft.maintenance_mode, available_languages: draft.available_languages, - enabled_language_codes: draft.enabled_language_codes + enabled_language_codes: draft.enabled_language_codes, + navigation: draft.navigation }); setDraft(saved); setSavedDraft(saved); resetDeltaWatermark(DELTA_KEY); + dispatchPlatformModulesChanged(); setSuccess("i18n:govoplan-admin.system_settings_saved.dac4f17b"); return true; } catch (err) {setError(adminErrorMessage(err));return false;} finally @@ -187,6 +192,15 @@ export default function SystemSettingsPanel({ settings, canWrite, canAccessMaint

i18n:govoplan-admin.these_settings_are_enforced_by_the_backend_centr.8112ed6f

+ + setDraft({ ...draft, navigation })} + scope="system" + disabled={!canWrite || busy} + /> +