feat(admin): configure system navigation defaults

This commit is contained in:
2026-08-20 01:47:03 +02:00
parent 8a75c82a5d
commit a1d7b649aa
5 changed files with 60 additions and 6 deletions
@@ -75,6 +75,11 @@ from govoplan_core.core.module_installer_notifications import (
installer_notification_subject, installer_notification_subject,
) )
from govoplan_core.core.module_license import module_license_decision, module_license_diagnostics 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 ( from govoplan_core.core.module_package_catalog import (
record_module_package_catalog_acceptance, record_module_package_catalog_acceptance,
validate_module_package_catalog, validate_module_package_catalog,
@@ -153,6 +158,7 @@ SYSTEM_SETTINGS_SECTIONS = (
"languages", "languages",
"privacy_retention_policy", "privacy_retention_policy",
"maintenance_mode", "maintenance_mode",
"navigation",
"settings", "settings",
) )
@@ -288,6 +294,7 @@ def _system_settings_item(session: Session) -> SystemSettingsItem:
policy = privacy_policy_from_settings(item) policy = privacy_policy_from_settings(item)
maintenance_mode = saved_maintenance_mode(session) maintenance_mode = saved_maintenance_mode(session)
i18n_payload = system_i18n_payload(item) i18n_payload = system_i18n_payload(item)
navigation = navigation_preferences_from_settings(item.settings)
return SystemSettingsItem( return SystemSettingsItem(
default_locale=item.default_locale, default_locale=item.default_locale,
allow_tenant_custom_groups=item.allow_tenant_custom_groups, 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"], available_languages=i18n_payload["available_languages"],
enabled_language_codes=i18n_payload["enabled_languages"], enabled_language_codes=i18n_payload["enabled_languages"],
settings=item.settings or {}, 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"], "privacy_retention_policy": payload["privacy_retention_policy"],
"maintenance_mode": payload["maintenance_mode"], "maintenance_mode": payload["maintenance_mode"],
"navigation": payload["navigation"],
"settings": payload["settings"], "settings": payload["settings"],
} }
@@ -1810,6 +1819,15 @@ def write_system_settings(
available_languages=available_languages, available_languages=available_languages,
enabled_language_codes=enabled_language_codes, 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: if payload.privacy_retention_policy is not None:
set_privacy_policy(item, payload.privacy_retention_policy.model_dump(mode="json")) set_privacy_policy(item, payload.privacy_retention_policy.model_dump(mode="json"))
if payload.maintenance_mode is not None: if payload.maintenance_mode is not None:
+3 -1
View File
@@ -5,7 +5,7 @@ from typing import Any, Literal
from pydantic import BaseModel, ConfigDict, Field 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.i18n import REFERENCE_LANGUAGE_CODE
from govoplan_core.privacy.schemas import PrivacyRetentionPolicyItem from govoplan_core.privacy.schemas import PrivacyRetentionPolicyItem
@@ -50,6 +50,7 @@ class SystemSettingsItem(BaseModel):
available_languages: list[LanguagePackageItem] = Field(default_factory=list) available_languages: list[LanguagePackageItem] = Field(default_factory=list)
enabled_language_codes: list[str] = Field(default_factory=list) enabled_language_codes: list[str] = Field(default_factory=list)
settings: dict[str, Any] = Field(default_factory=dict) settings: dict[str, Any] = Field(default_factory=dict)
navigation: NavigationPreferencesPayload | None = None
class SystemSettingsDeltaResponse(BaseModel): class SystemSettingsDeltaResponse(BaseModel):
@@ -73,6 +74,7 @@ class SystemSettingsUpdateRequest(BaseModel):
maintenance_mode: MaintenanceModeItem | None = None maintenance_mode: MaintenanceModeItem | None = None
available_languages: list[LanguagePackageItem] | None = None available_languages: list[LanguagePackageItem] | None = None
enabled_language_codes: list[str] | None = None enabled_language_codes: list[str] | None = None
navigation: NavigationPreferencesPayload | None = None
change_request_id: str | None = None change_request_id: str | None = None
+15
View File
@@ -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( DocumentationTopic(
id="admin.module-lifecycle-workflow", id="admin.module-lifecycle-workflow",
title="Plan and supervise module lifecycle changes", title="Plan and supervise module lifecycle changes",
+4 -1
View File
@@ -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"; import { apiDownload, apiFetch, apiPath, apiQuery } from "@govoplan/core-webui";
export { fetchAdminOverview, fetchPermissionCatalog, fetchTenants } from "@govoplan/core-webui"; export { fetchAdminOverview, fetchPermissionCatalog, fetchTenants } from "@govoplan/core-webui";
export type { export type {
@@ -33,6 +33,7 @@ export type SystemSettingsItem = {
available_languages: LanguagePackage[]; available_languages: LanguagePackage[];
enabled_language_codes: string[]; enabled_language_codes: string[];
settings: Record<string, unknown>; settings: Record<string, unknown>;
navigation: NavigationPreferences | null;
}; };
export type SystemSettingsDeltaSections = Partial<{ export type SystemSettingsDeltaSections = Partial<{
@@ -42,6 +43,7 @@ export type SystemSettingsDeltaSections = Partial<{
privacy_retention_policy: SystemSettingsItem["privacy_retention_policy"]; privacy_retention_policy: SystemSettingsItem["privacy_retention_policy"];
maintenance_mode: SystemSettingsItem["maintenance_mode"]; maintenance_mode: SystemSettingsItem["maintenance_mode"];
settings: SystemSettingsItem["settings"]; settings: SystemSettingsItem["settings"];
navigation: SystemSettingsItem["navigation"];
}>; }>;
export type SystemSettingsUpdatePayload = { export type SystemSettingsUpdatePayload = {
@@ -53,6 +55,7 @@ export type SystemSettingsUpdatePayload = {
maintenance_mode?: MaintenanceMode | null; maintenance_mode?: MaintenanceMode | null;
available_languages?: LanguagePackage[] | null; available_languages?: LanguagePackage[] | null;
enabled_language_codes?: string[] | null; enabled_language_codes?: string[] | null;
navigation?: NavigationPreferences | null;
change_request_id?: string | null; change_request_id?: string | null;
}; };
@@ -5,7 +5,7 @@ import { Card } from "@govoplan/core-webui";
import { FormField } from "@govoplan/core-webui"; import { FormField } from "@govoplan/core-webui";
import { ToggleSwitch } 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 { 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"; import { ADMIN_GOVERNANCE_DOCUMENTATION, ADMIN_INTERFACE_I18N, mutationDisabledReason } from "./interfacePatterns";
const DELTA_KEY = "admin:system-settings"; const DELTA_KEY = "admin:system-settings";
@@ -43,10 +43,13 @@ const fallback: SystemSettingsItem = {
{ code: "en", label: "English", native_label: "English" } { code: "en", label: "English", native_label: "English" }
], ],
enabled_language_codes: ["de", "en"], enabled_language_codes: ["de", "en"],
settings: {} settings: {},
navigation: null
}; };
export default function SystemSettingsPanel({ settings, canWrite, canAccessMaintenance }: {settings: ApiSettings;canWrite: boolean;canAccessMaintenance: boolean;}) { export default function SystemSettingsPanel({ settings, canWrite, canAccessMaintenance }: {settings: ApiSettings;canWrite: boolean;canAccessMaintenance: boolean;}) {
const modules = usePlatformModules();
const navigationItems = configurableNavigationItemsForModules(modules);
const [draft, setDraft] = useState<SystemSettingsItem>(fallback); const [draft, setDraft] = useState<SystemSettingsItem>(fallback);
const [savedDraft, setSavedDraft] = useState<SystemSettingsItem>(fallback); const [savedDraft, setSavedDraft] = useState<SystemSettingsItem>(fallback);
const [loading, setLoading] = useState(true); 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, allow_tenant_api_keys: draft.allow_tenant_api_keys,
maintenance_mode: draft.maintenance_mode, maintenance_mode: draft.maintenance_mode,
available_languages: draft.available_languages, available_languages: draft.available_languages,
enabled_language_codes: draft.enabled_language_codes enabled_language_codes: draft.enabled_language_codes,
navigation: draft.navigation
}); });
setDraft(saved); setDraft(saved);
setSavedDraft(saved); setSavedDraft(saved);
resetDeltaWatermark(DELTA_KEY); resetDeltaWatermark(DELTA_KEY);
dispatchPlatformModulesChanged();
setSuccess("i18n:govoplan-admin.system_settings_saved.dac4f17b"); setSuccess("i18n:govoplan-admin.system_settings_saved.dac4f17b");
return true; return true;
} catch (err) {setError(adminErrorMessage(err));return false;} finally } catch (err) {setError(adminErrorMessage(err));return false;} finally
@@ -187,6 +192,15 @@ export default function SystemSettingsPanel({ settings, canWrite, canAccessMaint
</div> </div>
<p className="muted small-note">i18n:govoplan-admin.these_settings_are_enforced_by_the_backend_centr.8112ed6f</p> <p className="muted small-note">i18n:govoplan-admin.these_settings_are_enforced_by_the_backend_centr.8112ed6f</p>
</Card> </Card>
<Card title="System navigation order">
<NavigationPreferenceEditor
items={navigationItems}
value={draft.navigation}
onChange={(navigation) => setDraft({ ...draft, navigation })}
scope="system"
disabled={!canWrite || busy}
/>
</Card>
<Card title="i18n:govoplan-admin.maintenance_mode.98cca5c6"> <Card title="i18n:govoplan-admin.maintenance_mode.98cca5c6">
<div className="settings-list"> <div className="settings-list">
<ToggleSwitch <ToggleSwitch
@@ -232,7 +246,8 @@ function systemSettingsDraftKey(item: SystemSettingsItem): string {
allow_tenant_api_keys: item.allow_tenant_api_keys, allow_tenant_api_keys: item.allow_tenant_api_keys,
maintenance_mode: item.maintenance_mode, maintenance_mode: item.maintenance_mode,
available_languages: item.available_languages, available_languages: item.available_languages,
enabled_language_codes: item.enabled_language_codes enabled_language_codes: item.enabled_language_codes,
navigation: item.navigation
}); });
} }
@@ -244,6 +259,7 @@ function applySystemSettingsSections(item: SystemSettingsItem, sections: SystemS
...(sections.languages ?? {}), ...(sections.languages ?? {}),
...(sections.privacy_retention_policy ? { privacy_retention_policy: sections.privacy_retention_policy } : {}), ...(sections.privacy_retention_policy ? { privacy_retention_policy: sections.privacy_retention_policy } : {}),
...(sections.maintenance_mode ? { maintenance_mode: sections.maintenance_mode } : {}), ...(sections.maintenance_mode ? { maintenance_mode: sections.maintenance_mode } : {}),
...(sections.navigation !== undefined ? { navigation: sections.navigation } : {}),
...(sections.settings ? { settings: sections.settings } : {}) ...(sections.settings ? { settings: sections.settings } : {})
}; };
} }