feat(admin): separate language package lifecycle
This commit is contained in:
@@ -129,16 +129,18 @@ manifest = ModuleManifest(
|
||||
title="Manage reference language and language packages",
|
||||
summary="German is the reference and new-installation default while configured tenant and user language choices remain effective.",
|
||||
body=(
|
||||
"German is the first-class product acceptance language and the default for a new system or tenant. English remains installed as the source-code fallback. System administrators control available and enabled language packages and the system default; tenant and user choices can select only languages allowed above them. Existing explicit preferences are preserved when this baseline is introduced. Disabling a language must not discard translated content or silently rewrite a stored preference."
|
||||
"German is the first-class product acceptance language and the default for a new system or tenant. English remains installed as the source-code fallback. The dedicated Language packages administration surface separates package installation, activation, deactivation, uninstall eligibility, and the default for newly created tenants from general system settings. Its compatibility matrix derives each package state from the installed module ID, exact module version, and available runtime translation catalog; a missing catalog is shown as incompatible rather than treated as translated. German, English, and the active default cannot be uninstalled. System administrators control available and enabled language packages and the system default; tenant and user choices can select only languages allowed above them. Existing explicit preferences are preserved when this baseline is introduced. Disabling a language must not discard translated content or silently rewrite a stored preference."
|
||||
),
|
||||
documentation_types=("admin",),
|
||||
audience=("system_admin", "tenant_admin"),
|
||||
related_modules=("tenancy", "access", "docs"),
|
||||
metadata={
|
||||
"kind": "reference",
|
||||
"route": "/admin?section=system-language-packages",
|
||||
"screen": "Language packages",
|
||||
"help_contexts": [
|
||||
"admin.system-settings.locale",
|
||||
"admin.system-settings.languages",
|
||||
"admin.system-language-packages",
|
||||
"admin.system-language-packages.compatibility",
|
||||
],
|
||||
},
|
||||
),
|
||||
@@ -229,6 +231,7 @@ manifest = ModuleManifest(
|
||||
view_surfaces=(
|
||||
ViewSurface(id="admin.section.overview", module_id="admin", kind="section", label="Administration overview", order=0),
|
||||
ViewSurface(id="admin.section.system-settings", module_id="admin", kind="section", label="System settings", order=10),
|
||||
ViewSurface(id="admin.section.system-language-packages", module_id="admin", kind="section", label="Language packages", order=15),
|
||||
ViewSurface(id="admin.section.system-configuration-changes", module_id="admin", kind="section", label="Configuration changes", order=20),
|
||||
ViewSurface(id="admin.section.system-configuration-packages", module_id="admin", kind="section", label="Configuration packages", order=30),
|
||||
ViewSurface(id="admin.section.system-role-templates", module_id="admin", kind="section", label="Role templates", order=40),
|
||||
|
||||
@@ -16,6 +16,10 @@ class InterfaceDocumentationContractTests(unittest.TestCase):
|
||||
"admin.configuration-packages",
|
||||
"admin.governance-templates",
|
||||
},
|
||||
"admin.language-reference-and-packages": {
|
||||
"admin.system-language-packages",
|
||||
"admin.system-language-packages.compatibility",
|
||||
},
|
||||
"admin.module-lifecycle-workflow": {
|
||||
"admin.module-lifecycle",
|
||||
"admin.module-lifecycle.queue-blocker",
|
||||
@@ -44,6 +48,7 @@ class InterfaceDocumentationContractTests(unittest.TestCase):
|
||||
{
|
||||
"admin.section.overview",
|
||||
"admin.section.system-settings",
|
||||
"admin.section.system-language-packages",
|
||||
"admin.section.system-configuration-changes",
|
||||
"admin.section.system-configuration-packages",
|
||||
"admin.section.system-role-templates",
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def test_language_lifecycle_has_a_dedicated_admin_section() -> None:
|
||||
module_source = (ROOT / "webui/src/module.ts").read_text(encoding="utf-8")
|
||||
panel_source = (
|
||||
ROOT / "webui/src/features/admin/LanguagePackagesPanel.tsx"
|
||||
).read_text(encoding="utf-8")
|
||||
general_source = (
|
||||
ROOT / "webui/src/features/admin/SystemSettingsPanel.tsx"
|
||||
).read_text(encoding="utf-8")
|
||||
|
||||
assert 'id: "system-language-packages"' in module_source
|
||||
assert 'surfaceId: "admin.section.system-language-packages"' in module_source
|
||||
assert "data-language-package-lifecycle" in panel_source
|
||||
assert "module.version" in panel_source
|
||||
assert "module.translations?.[language.code]" in panel_source
|
||||
assert "uninstallPackage" in panel_source
|
||||
assert "setPackageActive" in panel_source
|
||||
assert "default_locale" in panel_source
|
||||
assert "language_packages" not in general_source
|
||||
assert "available_languages:" not in general_source
|
||||
|
||||
|
||||
def test_language_lifecycle_names_every_required_state() -> None:
|
||||
source = (
|
||||
ROOT / "webui/src/features/admin/LanguagePackagesPanel.tsx"
|
||||
).read_text(encoding="utf-8")
|
||||
translations = (
|
||||
ROOT / "webui/src/i18n/generatedTranslations.ts"
|
||||
).read_text(encoding="utf-8")
|
||||
|
||||
for marker in (
|
||||
"installed.7bb4405c",
|
||||
"available.7c8f1005",
|
||||
"active.a733b809",
|
||||
"inactive.09af574c",
|
||||
"incompatible.lp001",
|
||||
"uninstallable.lp001",
|
||||
):
|
||||
assert marker in source
|
||||
for locale_marker in ('"en": {', '"de": {'):
|
||||
assert locale_marker in translations
|
||||
assert translations.count("language_package_administration.lp001") == 2
|
||||
@@ -48,6 +48,7 @@ export default function AdminOverviewPanel({ settings, onSelect, availableSectio
|
||||
{availableSections.has("system-tenant-modules") && <AreaLink title="Tenant modules" text="Set per-tenant availability, forced modules, and current selection." onClick={() => onSelect("system-tenant-modules")} />}
|
||||
{availableSections.has("system-configuration-packages") && <AreaLink title="i18n:govoplan-admin.configuration_packages.eb2f05f1" text="i18n:govoplan-admin.preflight_approve_apply_and_export_configuration.276bd0f8" onClick={() => onSelect("system-configuration-packages")} />}
|
||||
{availableSections.has("system-settings") && <AreaLink title="i18n:govoplan-admin.maintenance.94de303b" text="i18n:govoplan-admin.instance_defaults_and_tenant_governance_capabili.99d6b2fa" onClick={() => onSelect("system-settings")} />}
|
||||
{availableSections.has("system-language-packages") && <AreaLink title="i18n:govoplan-admin.language_packages" text="i18n:govoplan-admin.language_package_administration_overview.lp001" onClick={() => onSelect("system-language-packages")} />}
|
||||
{availableSections.has("system-configuration-changes") && <AreaLink title="i18n:govoplan-admin.changes.8aa57de6" text="i18n:govoplan-admin.configuration_requests_approvals_and_version_his.19f37335" onClick={() => onSelect("system-configuration-changes")} />}
|
||||
{availableSections.has("system-audit") && <AreaLink title="i18n:govoplan-admin.audit.fa1703dd" text="i18n:govoplan-admin.system_level_administrative_history.49f76723" onClick={() => onSelect("system-audit")} />}
|
||||
</div>
|
||||
@@ -113,7 +114,7 @@ export default function AdminOverviewPanel({ settings, onSelect, availableSectio
|
||||
|
||||
}
|
||||
|
||||
const platformSectionIds = ["system-modules", "system-tenant-modules", "system-configuration-packages", "system-settings", "system-configuration-changes", "system-audit"];
|
||||
const platformSectionIds = ["system-modules", "system-tenant-modules", "system-configuration-packages", "system-settings", "system-language-packages", "system-configuration-changes", "system-audit"];
|
||||
const globalSectionIds = ["system-tenants", "system-roles", "system-role-templates", "system-groups", "system-users", "system-file-connectors", "system-mail-servers", "system-retention", "system-view-policy"];
|
||||
const tenantSectionIds = ["tenant-roles", "tenant-groups", "tenant-users", "tenant-file-connectors", "tenant-mail-servers", "tenant-api-keys", "tenant-retention", "tenant-view-policy", "tenant-modules", "tenant-settings", "tenant-audit"];
|
||||
const groupSectionIds = ["tenant-group-file-connectors", "tenant-group-mail-servers", "tenant-group-retention", "group-view-policy"];
|
||||
|
||||
@@ -0,0 +1,330 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import type { ApiSettings, PlatformWebModule } from "@govoplan/core-webui";
|
||||
import {
|
||||
AdminPageLayout,
|
||||
Button,
|
||||
Card,
|
||||
DocumentationHelpLink,
|
||||
FormField,
|
||||
FormGrid,
|
||||
StatusBadge,
|
||||
adminErrorMessage,
|
||||
i18nMessage,
|
||||
useDeltaWatermarks,
|
||||
usePlatformModules,
|
||||
useUnsavedDraftGuard
|
||||
} from "@govoplan/core-webui";
|
||||
import {
|
||||
fetchSystemSettingsDelta,
|
||||
updateSystemSettings,
|
||||
type LanguagePackage,
|
||||
type SystemSettingsItem
|
||||
} from "../../api/admin";
|
||||
import {
|
||||
ADMIN_GOVERNANCE_DOCUMENTATION,
|
||||
ADMIN_INTERFACE_I18N,
|
||||
mutationDisabledReason
|
||||
} from "./interfacePatterns";
|
||||
import {
|
||||
SYSTEM_SETTINGS_FALLBACK,
|
||||
applySystemSettingsSections,
|
||||
languageOptionLabel,
|
||||
normalizeLanguageCode
|
||||
} from "./systemSettingsModel";
|
||||
|
||||
const DELTA_KEY = "admin:language-packages";
|
||||
const BUILT_IN_LANGUAGE_CODES = new Set(["de", "en"]);
|
||||
|
||||
type LanguageLifecycle = {
|
||||
code: string;
|
||||
package: LanguagePackage | null;
|
||||
catalogCount: number;
|
||||
installed: boolean;
|
||||
active: boolean;
|
||||
uninstallable: boolean;
|
||||
};
|
||||
|
||||
export default function LanguagePackagesPanel({
|
||||
settings,
|
||||
canWrite
|
||||
}: {
|
||||
settings: ApiSettings;
|
||||
canWrite: boolean;
|
||||
}) {
|
||||
const modules = usePlatformModules();
|
||||
const [draft, setDraft] = useState<SystemSettingsItem>(SYSTEM_SETTINGS_FALLBACK);
|
||||
const [savedDraft, setSavedDraft] = useState<SystemSettingsItem>(SYSTEM_SETTINGS_FALLBACK);
|
||||
const [packageDraft, setPackageDraft] = useState({ code: "", label: "", nativeLabel: "" });
|
||||
const [selectedCode, setSelectedCode] = useState("de");
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
const [success, setSuccess] = useState("");
|
||||
const { getDeltaWatermark, setDeltaWatermark, resetDeltaWatermark } = useDeltaWatermarks();
|
||||
const dirty = languageDraftKey(draft) !== languageDraftKey(savedDraft);
|
||||
const lifecycle = useMemo(
|
||||
() => languageLifecycle(draft, modules),
|
||||
[draft, modules]
|
||||
);
|
||||
const selectedLifecycle = lifecycle.find((item) => item.code === selectedCode) ?? lifecycle[0];
|
||||
|
||||
useUnsavedDraftGuard({
|
||||
dirty,
|
||||
onSave: save,
|
||||
onDiscard: () => {
|
||||
setDraft(savedDraft);
|
||||
setPackageDraft({ code: "", label: "", nativeLabel: "" });
|
||||
}
|
||||
});
|
||||
|
||||
async function load() {
|
||||
setLoading(true);
|
||||
setError("");
|
||||
try {
|
||||
const wasDirty = languageDraftKey(draft) !== languageDraftKey(savedDraft);
|
||||
const response = await fetchSystemSettingsDelta(settings, {
|
||||
since: getDeltaWatermark(DELTA_KEY)
|
||||
});
|
||||
setDeltaWatermark(DELTA_KEY, response.watermark);
|
||||
if (response.full && response.item) {
|
||||
setSavedDraft(response.item);
|
||||
if (!wasDirty) setDraft(response.item);
|
||||
} else if (response.changed_sections.length) {
|
||||
setSavedDraft((current) => applySystemSettingsSections(current, response.sections));
|
||||
if (!wasDirty) {
|
||||
setDraft((current) => applySystemSettingsSections(current, response.sections));
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
setError(adminErrorMessage(err));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
resetDeltaWatermark(DELTA_KEY);
|
||||
void load();
|
||||
}, [settings.accessToken, settings.apiBaseUrl, resetDeltaWatermark]);
|
||||
|
||||
useEffect(() => {
|
||||
if (lifecycle.length && !lifecycle.some((item) => item.code === selectedCode)) {
|
||||
setSelectedCode(lifecycle[0].code);
|
||||
}
|
||||
}, [lifecycle, selectedCode]);
|
||||
|
||||
async function save(): Promise<boolean> {
|
||||
setBusy(true);
|
||||
setError("");
|
||||
setSuccess("");
|
||||
try {
|
||||
const saved = await updateSystemSettings(settings, {
|
||||
default_locale: draft.default_locale,
|
||||
allow_tenant_custom_groups: draft.allow_tenant_custom_groups,
|
||||
allow_tenant_custom_roles: draft.allow_tenant_custom_roles,
|
||||
allow_tenant_api_keys: draft.allow_tenant_api_keys,
|
||||
available_languages: draft.available_languages,
|
||||
enabled_language_codes: draft.enabled_language_codes
|
||||
});
|
||||
setDraft(saved);
|
||||
setSavedDraft(saved);
|
||||
resetDeltaWatermark(DELTA_KEY);
|
||||
setSuccess("i18n:govoplan-admin.language_package_settings_saved.lp001");
|
||||
return true;
|
||||
} catch (err) {
|
||||
setError(adminErrorMessage(err));
|
||||
return false;
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
}
|
||||
|
||||
function installPackage(code: string, label?: string, nativeLabel?: string) {
|
||||
const normalized = normalizeLanguageCode(code);
|
||||
if (!normalized || draft.available_languages.some((item) => item.code === normalized)) return;
|
||||
const resolvedLabel = label?.trim() || normalized.toUpperCase();
|
||||
setDraft({
|
||||
...draft,
|
||||
available_languages: [
|
||||
...draft.available_languages,
|
||||
{
|
||||
code: normalized,
|
||||
label: resolvedLabel,
|
||||
native_label: nativeLabel?.trim() || resolvedLabel
|
||||
}
|
||||
]
|
||||
});
|
||||
setSelectedCode(normalized);
|
||||
}
|
||||
|
||||
function installDraftPackage() {
|
||||
const code = normalizeLanguageCode(packageDraft.code);
|
||||
if (!code || !packageDraft.label.trim()) return;
|
||||
installPackage(code, packageDraft.label, packageDraft.nativeLabel);
|
||||
setPackageDraft({ code: "", label: "", nativeLabel: "" });
|
||||
}
|
||||
|
||||
function setPackageActive(code: string, active: boolean) {
|
||||
const enabled = new Set(draft.enabled_language_codes);
|
||||
if (active) enabled.add(code);
|
||||
else enabled.delete(code);
|
||||
setDraft({
|
||||
...draft,
|
||||
enabled_language_codes: draft.available_languages
|
||||
.map((item) => item.code)
|
||||
.filter((item) => enabled.has(item))
|
||||
});
|
||||
}
|
||||
|
||||
function uninstallPackage(code: string) {
|
||||
if (BUILT_IN_LANGUAGE_CODES.has(code) || code === draft.default_locale) return;
|
||||
setDraft({
|
||||
...draft,
|
||||
available_languages: draft.available_languages.filter((item) => item.code !== code),
|
||||
enabled_language_codes: draft.enabled_language_codes.filter((item) => item !== code)
|
||||
});
|
||||
}
|
||||
|
||||
const enabledDefaultOptions = draft.available_languages.filter((item) =>
|
||||
draft.enabled_language_codes.includes(item.code)
|
||||
);
|
||||
|
||||
return (
|
||||
<AdminPageLayout
|
||||
title="i18n:govoplan-admin.language_package_administration.lp001"
|
||||
description="i18n:govoplan-admin.language_package_administration_description.lp001"
|
||||
loading={loading}
|
||||
error={error}
|
||||
success={success}
|
||||
actions={<>
|
||||
<DocumentationHelpLink reference={ADMIN_GOVERNANCE_DOCUMENTATION} />
|
||||
<Button onClick={() => void load()} disabled={loading || busy} disabledReason={loading ? ADMIN_INTERFACE_I18N.loading : busy ? ADMIN_INTERFACE_I18N.busy : undefined}>i18n:govoplan-admin.reload.cce71553</Button>
|
||||
<Button variant="primary" onClick={() => void save()} disabledReason={mutationDisabledReason({ busy, permitted: canWrite, changed: dirty })}>{busy ? "i18n:govoplan-admin.saving.56a2285c" : "i18n:govoplan-admin.save_settings.913aba9f"}</Button>
|
||||
</>}
|
||||
>
|
||||
<div className="admin-settings-form" data-language-package-lifecycle>
|
||||
<Card title="i18n:govoplan-admin.defaults_for_newly_created_tenants.9d0f4ccd">
|
||||
<FormField label="i18n:govoplan-admin.default_locale.b99d021f" documentation={ADMIN_GOVERNANCE_DOCUMENTATION}>
|
||||
<select
|
||||
value={draft.default_locale}
|
||||
onChange={(event) => setDraft({ ...draft, default_locale: event.target.value })}
|
||||
disabled={!canWrite || busy || enabledDefaultOptions.length === 0}
|
||||
>
|
||||
{enabledDefaultOptions.map((item) => <option key={item.code} value={item.code}>{languageOptionLabel(item)}</option>)}
|
||||
</select>
|
||||
</FormField>
|
||||
<p className="muted small-note">i18n:govoplan-admin.default_language_inheritance_help.lp001</p>
|
||||
</Card>
|
||||
|
||||
<Card title="i18n:govoplan-admin.language_packages">
|
||||
<div className="module-management-list">
|
||||
{lifecycle.map((item) => (
|
||||
<div className="module-management-row" key={item.code}>
|
||||
<div className="module-management-main">
|
||||
<div className="module-management-title">
|
||||
<strong>{item.package ? languageOptionLabel(item.package) : item.code.toUpperCase()}</strong>
|
||||
<code>{item.code}</code>
|
||||
</div>
|
||||
<div className="module-management-meta">
|
||||
<StatusBadge status={item.installed ? "success" : "info"} label={item.installed ? "i18n:govoplan-admin.installed.7bb4405c" : "i18n:govoplan-admin.available.7c8f1005"} />
|
||||
{item.installed && <StatusBadge status={item.active ? "active" : "inactive"} label={item.active ? "i18n:govoplan-admin.active.a733b809" : "i18n:govoplan-admin.inactive.09af574c"} />}
|
||||
<StatusBadge status={item.catalogCount ? "success" : "warning"} label={item.catalogCount ? "i18n:govoplan-admin.compatible.lp001" : "i18n:govoplan-admin.incompatible.lp001"} />
|
||||
{item.uninstallable && <StatusBadge status="warning" label="i18n:govoplan-admin.uninstallable.lp001" />}
|
||||
</div>
|
||||
<div className="module-management-details">
|
||||
<span>{i18nMessage("i18n:govoplan-admin.module_version_catalogs.lp001", { value0: item.catalogCount })}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="module-management-toggle">
|
||||
{!item.installed && <Button onClick={() => installPackage(item.code)} disabledReason={mutationDisabledReason({ busy, permitted: canWrite })}>i18n:govoplan-admin.install_language_package</Button>}
|
||||
{item.installed && <Button onClick={() => setPackageActive(item.code, !item.active)} disabledReason={item.active && item.code === draft.default_locale ? "i18n:govoplan-admin.default_language_must_remain_active.lp001" : mutationDisabledReason({ busy, permitted: canWrite })}>{item.active ? "i18n:govoplan-admin.deactivate.lp001" : "i18n:govoplan-admin.activate.lp001"}</Button>}
|
||||
{item.uninstallable && <Button variant="danger" onClick={() => uninstallPackage(item.code)} disabledReason={mutationDisabledReason({ busy, permitted: canWrite })}>i18n:govoplan-admin.uninstall.a735da1d</Button>}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<FormGrid columns={3} collapseAt="standard" className="">
|
||||
<FormField label="i18n:govoplan-admin.language_code">
|
||||
<input value={packageDraft.code} disabled={!canWrite || busy} placeholder="fr" onChange={(event) => setPackageDraft({ ...packageDraft, code: event.target.value })} />
|
||||
</FormField>
|
||||
<FormField label="i18n:govoplan-admin.language_name">
|
||||
<input value={packageDraft.label} disabled={!canWrite || busy} placeholder="i18n:govoplan-admin.language_name_placeholder" onChange={(event) => setPackageDraft({ ...packageDraft, label: event.target.value })} />
|
||||
</FormField>
|
||||
<FormField label="i18n:govoplan-admin.native_language_name">
|
||||
<input value={packageDraft.nativeLabel} disabled={!canWrite || busy} placeholder="i18n:govoplan-admin.native_language_name_placeholder" onChange={(event) => setPackageDraft({ ...packageDraft, nativeLabel: event.target.value })} />
|
||||
</FormField>
|
||||
</FormGrid>
|
||||
<div className="button-row compact-actions">
|
||||
<Button onClick={installDraftPackage} disabledReason={mutationDisabledReason({ busy, permitted: canWrite, complete: Boolean(normalizeLanguageCode(packageDraft.code) && packageDraft.label.trim()) })}>i18n:govoplan-admin.install_language_package</Button>
|
||||
</div>
|
||||
<p className="muted small-note">i18n:govoplan-admin.language_packages_help</p>
|
||||
</Card>
|
||||
|
||||
<Card title="i18n:govoplan-admin.module_version_compatibility.lp001">
|
||||
{selectedLifecycle && <FormField label="i18n:govoplan-admin.language_packages">
|
||||
<select value={selectedLifecycle.code} onChange={(event) => setSelectedCode(event.target.value)}>
|
||||
{lifecycle.map((item) => <option key={item.code} value={item.code}>{item.code.toUpperCase()}</option>)}
|
||||
</select>
|
||||
</FormField>}
|
||||
{selectedLifecycle && modules.length > 0 ? (
|
||||
<div className="module-management-list">
|
||||
{modules.map((module) => <ModuleLanguageState key={module.id} module={module} language={selectedLifecycle} />)}
|
||||
</div>
|
||||
) : <p className="muted">i18n:govoplan-admin.no_module_language_catalogs.lp001</p>}
|
||||
</Card>
|
||||
</div>
|
||||
</AdminPageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
function ModuleLanguageState({ module, language }: { module: PlatformWebModule; language: LanguageLifecycle }) {
|
||||
const compatible = Boolean(module.translations?.[language.code]);
|
||||
const state = language.installed && compatible
|
||||
? "i18n:govoplan-admin.installed.7bb4405c"
|
||||
: compatible
|
||||
? "i18n:govoplan-admin.available.7c8f1005"
|
||||
: "i18n:govoplan-admin.incompatible.lp001";
|
||||
return (
|
||||
<div className="module-management-row">
|
||||
<div className="module-management-main">
|
||||
<div className="module-management-title">
|
||||
<strong>{module.label}</strong>
|
||||
<code>{module.id}</code>
|
||||
<span>v{module.version}</span>
|
||||
</div>
|
||||
<div className="module-management-meta">
|
||||
<StatusBadge status={compatible ? (language.installed ? "success" : "info") : "warning"} label={state} />
|
||||
{language.installed && compatible && <StatusBadge status={language.active ? "active" : "inactive"} label={language.active ? "i18n:govoplan-admin.active.a733b809" : "i18n:govoplan-admin.inactive.09af574c"} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function languageLifecycle(item: SystemSettingsItem, modules: PlatformWebModule[]): LanguageLifecycle[] {
|
||||
const installed = new Map(item.available_languages.map((language) => [language.code, language]));
|
||||
const codes = new Set(installed.keys());
|
||||
modules.forEach((module) => Object.keys(module.translations ?? {}).forEach((code) => codes.add(code)));
|
||||
return [...codes].sort().map((code) => {
|
||||
const languagePackage = installed.get(code) ?? null;
|
||||
const catalogCount = modules.filter((module) => Boolean(module.translations?.[code])).length;
|
||||
return {
|
||||
code,
|
||||
package: languagePackage,
|
||||
catalogCount,
|
||||
installed: Boolean(languagePackage),
|
||||
active: item.enabled_language_codes.includes(code),
|
||||
uninstallable: Boolean(
|
||||
languagePackage && !BUILT_IN_LANGUAGE_CODES.has(code) && code !== item.default_locale
|
||||
)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function languageDraftKey(item: SystemSettingsItem): string {
|
||||
return JSON.stringify({
|
||||
default_locale: item.default_locale,
|
||||
available_languages: item.available_languages,
|
||||
enabled_language_codes: item.enabled_language_codes
|
||||
});
|
||||
}
|
||||
@@ -1,73 +1,32 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import type { FormGrid, ApiSettings } from "@govoplan/core-webui";
|
||||
import type { ApiSettings } from "@govoplan/core-webui";
|
||||
import { Button } from "@govoplan/core-webui";
|
||||
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, NavigationPreferenceEditor, adminErrorMessage, configurableNavigationItemsForModules, dispatchPlatformModulesChanged, useDeltaWatermarks, usePlatformModules, useUnsavedDraftGuard } from "@govoplan/core-webui";
|
||||
import { fetchSystemSettingsDelta, updateSystemSettings, type SystemSettingsItem } from "../../api/admin";
|
||||
import { AdminPageLayout, DocumentationHelpLink, NavigationPreferenceEditor, adminErrorMessage, configurableNavigationItemsForModules, dispatchPlatformModulesChanged, useDeltaWatermarks, usePlatformModules, useUnsavedDraftGuard } from "@govoplan/core-webui";
|
||||
import { ADMIN_GOVERNANCE_DOCUMENTATION, ADMIN_INTERFACE_I18N, mutationDisabledReason } from "./interfacePatterns";
|
||||
import { SYSTEM_SETTINGS_FALLBACK, applySystemSettingsSections } from "./systemSettingsModel";
|
||||
|
||||
const DELTA_KEY = "admin:system-settings";
|
||||
|
||||
const defaultAllowLowerLevelLimits: PrivacyRetentionLimitPermissions = {
|
||||
store_raw_campaign_json: true,
|
||||
raw_campaign_json_retention_days: true,
|
||||
generated_eml_retention_days: true,
|
||||
stored_report_detail_retention_days: true,
|
||||
mock_mailbox_retention_days: true,
|
||||
audit_detail_retention_days: true,
|
||||
audit_detail_level: true
|
||||
};
|
||||
|
||||
const defaultPrivacyPolicy: PrivacyRetentionPolicy = {
|
||||
store_raw_campaign_json: true,
|
||||
raw_campaign_json_retention_days: null,
|
||||
generated_eml_retention_days: null,
|
||||
stored_report_detail_retention_days: null,
|
||||
mock_mailbox_retention_days: null,
|
||||
audit_detail_retention_days: null,
|
||||
audit_detail_level: "full",
|
||||
allow_lower_level_limits: defaultAllowLowerLevelLimits
|
||||
};
|
||||
|
||||
const fallback: SystemSettingsItem = {
|
||||
default_locale: "de",
|
||||
allow_tenant_custom_groups: true,
|
||||
allow_tenant_custom_roles: true,
|
||||
allow_tenant_api_keys: true,
|
||||
privacy_retention_policy: defaultPrivacyPolicy,
|
||||
maintenance_mode: { enabled: false, message: "" },
|
||||
available_languages: [
|
||||
{ code: "de", label: "German", native_label: "Deutsch" },
|
||||
{ code: "en", label: "English", native_label: "English" }
|
||||
],
|
||||
enabled_language_codes: ["de", "en"],
|
||||
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<SystemSettingsItem>(fallback);
|
||||
const [savedDraft, setSavedDraft] = useState<SystemSettingsItem>(fallback);
|
||||
const [draft, setDraft] = useState<SystemSettingsItem>(SYSTEM_SETTINGS_FALLBACK);
|
||||
const [savedDraft, setSavedDraft] = useState<SystemSettingsItem>(SYSTEM_SETTINGS_FALLBACK);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
const [success, setSuccess] = useState("");
|
||||
const [packageDraft, setPackageDraft] = useState({ code: "", label: "", nativeLabel: "" });
|
||||
const { getDeltaWatermark, setDeltaWatermark, resetDeltaWatermark } = useDeltaWatermarks();
|
||||
const defaultLocaleOptions = localeOptions(draft.default_locale, draft.enabled_language_codes);
|
||||
const dirty = systemSettingsDraftKey(draft) !== systemSettingsDraftKey(savedDraft);
|
||||
|
||||
useUnsavedDraftGuard({
|
||||
dirty,
|
||||
onSave: save,
|
||||
onDiscard: () => {
|
||||
setDraft(savedDraft);
|
||||
setPackageDraft({ code: "", label: "", nativeLabel: "" });
|
||||
}
|
||||
onDiscard: () => setDraft(savedDraft)
|
||||
});
|
||||
|
||||
async function load() {
|
||||
@@ -104,8 +63,6 @@ export default function SystemSettingsPanel({ settings, canWrite, canAccessMaint
|
||||
allow_tenant_custom_roles: draft.allow_tenant_custom_roles,
|
||||
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,
|
||||
navigation: draft.navigation
|
||||
});
|
||||
setDraft(saved);
|
||||
@@ -118,30 +75,6 @@ export default function SystemSettingsPanel({ settings, canWrite, canAccessMaint
|
||||
{setBusy(false);}
|
||||
}
|
||||
|
||||
function setEnabledLanguages(selected: string[]) {
|
||||
const enabled = new Set(selected);
|
||||
const nextEnabled = draft.available_languages.map((item) => item.code).filter((item) => enabled.has(item));
|
||||
const defaultLocale = nextEnabled.includes(draft.default_locale) ? draft.default_locale : (nextEnabled[0] ?? draft.default_locale);
|
||||
setDraft({ ...draft, enabled_language_codes: nextEnabled, default_locale: defaultLocale });
|
||||
}
|
||||
|
||||
function installLanguagePackage() {
|
||||
const code = normalizeLanguageCode(packageDraft.code);
|
||||
if (!code || !packageDraft.label.trim()) return;
|
||||
const nextPackage: LanguagePackage = {
|
||||
code,
|
||||
label: packageDraft.label.trim(),
|
||||
native_label: packageDraft.nativeLabel.trim() || packageDraft.label.trim()
|
||||
};
|
||||
const existing = new Set(draft.available_languages.map((item) => item.code));
|
||||
const available = existing.has(code)
|
||||
? draft.available_languages.map((item) => item.code === code ? nextPackage : item)
|
||||
: [...draft.available_languages, nextPackage];
|
||||
const enabled = draft.enabled_language_codes.includes(code) ? draft.enabled_language_codes : [...draft.enabled_language_codes, code];
|
||||
setDraft({ ...draft, available_languages: available, enabled_language_codes: enabled, default_locale: draft.default_locale || code });
|
||||
setPackageDraft({ code: "", label: "", nativeLabel: "" });
|
||||
}
|
||||
|
||||
return (
|
||||
<AdminPageLayout
|
||||
title="i18n:govoplan-admin.system_general_settings.7cd662ed"
|
||||
@@ -152,38 +85,6 @@ export default function SystemSettingsPanel({ settings, canWrite, canAccessMaint
|
||||
actions={<><DocumentationHelpLink reference={ADMIN_GOVERNANCE_DOCUMENTATION} /><Button onClick={() => void load()} disabled={loading || busy} disabledReason={loading ? ADMIN_INTERFACE_I18N.loading : busy ? ADMIN_INTERFACE_I18N.busy : undefined}>i18n:govoplan-admin.reload.cce71553</Button><Button variant="primary" onClick={() => void save()} disabledReason={mutationDisabledReason({ busy, permitted: canWrite, changed: dirty })}>{busy ? "i18n:govoplan-admin.saving.56a2285c" : "i18n:govoplan-admin.save_settings.913aba9f"}</Button></>}>
|
||||
|
||||
<div className="admin-settings-form">
|
||||
<Card title="i18n:govoplan-admin.defaults_for_newly_created_tenants.9d0f4ccd">
|
||||
<FormField label="i18n:govoplan-admin.default_locale.b99d021f" documentation={ADMIN_GOVERNANCE_DOCUMENTATION}>
|
||||
<select value={draft.default_locale} onChange={(event) => setDraft({ ...draft, default_locale: event.target.value })} disabled={!canWrite || busy || defaultLocaleOptions.length === 0}>
|
||||
{defaultLocaleOptions.map((code) => {
|
||||
const language = draft.available_languages.find((item) => item.code === code);
|
||||
return <option key={code} value={code}>{languageOptionLabel(language ?? { code, label: code.toUpperCase() })}</option>;
|
||||
})}
|
||||
</select>
|
||||
</FormField>
|
||||
</Card>
|
||||
<Card title="i18n:govoplan-admin.language_packages">
|
||||
<AdminSelectionList
|
||||
options={draft.available_languages.map((item) => ({ id: item.code, label: item.code.toUpperCase(), description: languageOptionLabel(item), disabled: !canWrite || busy || item.code === draft.default_locale }))}
|
||||
selected={draft.enabled_language_codes}
|
||||
onChange={setEnabledLanguages}
|
||||
/>
|
||||
<FormGrid columns={1} collapseAt="standard" className="">
|
||||
<FormField label="i18n:govoplan-admin.language_code">
|
||||
<input value={packageDraft.code} disabled={!canWrite || busy} placeholder="fr" onChange={(event) => setPackageDraft({ ...packageDraft, code: event.target.value })} />
|
||||
</FormField>
|
||||
<FormField label="i18n:govoplan-admin.language_name">
|
||||
<input value={packageDraft.label} disabled={!canWrite || busy} placeholder="i18n:govoplan-admin.language_name_placeholder" onChange={(event) => setPackageDraft({ ...packageDraft, label: event.target.value })} />
|
||||
</FormField>
|
||||
<FormField label="i18n:govoplan-admin.native_language_name">
|
||||
<input value={packageDraft.nativeLabel} disabled={!canWrite || busy} placeholder="i18n:govoplan-admin.native_language_name_placeholder" onChange={(event) => setPackageDraft({ ...packageDraft, nativeLabel: event.target.value })} />
|
||||
</FormField>
|
||||
<div className="button-row compact-actions">
|
||||
<Button onClick={installLanguagePackage} disabledReason={mutationDisabledReason({ busy, permitted: canWrite, complete: Boolean(normalizeLanguageCode(packageDraft.code) && packageDraft.label.trim()) })}>i18n:govoplan-admin.install_language_package</Button>
|
||||
</div>
|
||||
</FormGrid>
|
||||
<p className="muted small-note">i18n:govoplan-admin.language_packages_help</p>
|
||||
</Card>
|
||||
<Card title="i18n:govoplan-admin.tenant_administration_capabilities.5d265972">
|
||||
<div className="settings-list">
|
||||
<ToggleSwitch checked={draft.allow_tenant_custom_groups} onChange={(checked) => setDraft({ ...draft, allow_tenant_custom_groups: checked })} disabled={!canWrite || busy} help={!canWrite ? ADMIN_INTERFACE_I18N.writeRequired : busy ? ADMIN_INTERFACE_I18N.busy : undefined} label="i18n:govoplan-admin.allow_tenant_defined_groups_by_default.32099d5a" />
|
||||
@@ -226,40 +127,12 @@ export default function SystemSettingsPanel({ settings, canWrite, canAccessMaint
|
||||
|
||||
}
|
||||
|
||||
function normalizeLanguageCode(value: string): string {
|
||||
return value.trim().toLowerCase().replace(/_/g, "-").split(/[^a-z0-9]+/).filter(Boolean).join("-");
|
||||
}
|
||||
|
||||
function localeOptions(current: string, enabled: string[]): string[] {
|
||||
return [...new Set([current, ...enabled].filter((item) => item && item.trim()))];
|
||||
}
|
||||
|
||||
function languageOptionLabel(language: LanguagePackage): string {
|
||||
return `${language.code.toUpperCase()} - ${language.native_label || language.label}`;
|
||||
}
|
||||
|
||||
function systemSettingsDraftKey(item: SystemSettingsItem): string {
|
||||
return JSON.stringify({
|
||||
default_locale: item.default_locale,
|
||||
allow_tenant_custom_groups: item.allow_tenant_custom_groups,
|
||||
allow_tenant_custom_roles: item.allow_tenant_custom_roles,
|
||||
allow_tenant_api_keys: item.allow_tenant_api_keys,
|
||||
maintenance_mode: item.maintenance_mode,
|
||||
available_languages: item.available_languages,
|
||||
enabled_language_codes: item.enabled_language_codes,
|
||||
navigation: item.navigation
|
||||
});
|
||||
}
|
||||
|
||||
function applySystemSettingsSections(item: SystemSettingsItem, sections: SystemSettingsDeltaSections): SystemSettingsItem {
|
||||
return {
|
||||
...item,
|
||||
...(sections.defaults ?? {}),
|
||||
...(sections.tenant_capabilities ?? {}),
|
||||
...(sections.languages ?? {}),
|
||||
...(sections.privacy_retention_policy ? { privacy_retention_policy: sections.privacy_retention_policy } : {}),
|
||||
...(sections.maintenance_mode ? { maintenance_mode: sections.maintenance_mode } : {}),
|
||||
...(sections.navigation !== undefined ? { navigation: sections.navigation } : {}),
|
||||
...(sections.settings ? { settings: sections.settings } : {})
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import type {
|
||||
PrivacyRetentionLimitPermissions,
|
||||
PrivacyRetentionPolicy,
|
||||
SystemSettingsDeltaSections,
|
||||
SystemSettingsItem
|
||||
} from "../../api/admin";
|
||||
|
||||
const defaultAllowLowerLevelLimits: PrivacyRetentionLimitPermissions = {
|
||||
store_raw_campaign_json: true,
|
||||
raw_campaign_json_retention_days: true,
|
||||
generated_eml_retention_days: true,
|
||||
stored_report_detail_retention_days: true,
|
||||
mock_mailbox_retention_days: true,
|
||||
audit_detail_retention_days: true,
|
||||
audit_detail_level: true
|
||||
};
|
||||
|
||||
const defaultPrivacyPolicy: PrivacyRetentionPolicy = {
|
||||
store_raw_campaign_json: true,
|
||||
raw_campaign_json_retention_days: null,
|
||||
generated_eml_retention_days: null,
|
||||
stored_report_detail_retention_days: null,
|
||||
mock_mailbox_retention_days: null,
|
||||
audit_detail_retention_days: null,
|
||||
audit_detail_level: "full",
|
||||
allow_lower_level_limits: defaultAllowLowerLevelLimits
|
||||
};
|
||||
|
||||
export const SYSTEM_SETTINGS_FALLBACK: SystemSettingsItem = {
|
||||
default_locale: "de",
|
||||
allow_tenant_custom_groups: true,
|
||||
allow_tenant_custom_roles: true,
|
||||
allow_tenant_api_keys: true,
|
||||
privacy_retention_policy: defaultPrivacyPolicy,
|
||||
maintenance_mode: { enabled: false, message: "" },
|
||||
available_languages: [
|
||||
{ code: "de", label: "German", native_label: "Deutsch" },
|
||||
{ code: "en", label: "English", native_label: "English" }
|
||||
],
|
||||
enabled_language_codes: ["de", "en"],
|
||||
settings: {},
|
||||
navigation: null
|
||||
};
|
||||
|
||||
export function applySystemSettingsSections(
|
||||
item: SystemSettingsItem,
|
||||
sections: SystemSettingsDeltaSections
|
||||
): SystemSettingsItem {
|
||||
return {
|
||||
...item,
|
||||
...(sections.defaults ?? {}),
|
||||
...(sections.tenant_capabilities ?? {}),
|
||||
...(sections.languages ?? {}),
|
||||
...(sections.privacy_retention_policy
|
||||
? { privacy_retention_policy: sections.privacy_retention_policy }
|
||||
: {}),
|
||||
...(sections.maintenance_mode ? { maintenance_mode: sections.maintenance_mode } : {}),
|
||||
...(sections.navigation !== undefined ? { navigation: sections.navigation } : {}),
|
||||
...(sections.settings ? { settings: sections.settings } : {})
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeLanguageCode(value: string): string {
|
||||
return value
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/_/g, "-")
|
||||
.split(/[^a-z0-9]+/)
|
||||
.filter(Boolean)
|
||||
.join("-");
|
||||
}
|
||||
|
||||
export function languageOptionLabel(language: {
|
||||
code: string;
|
||||
label: string;
|
||||
native_label?: string | null;
|
||||
}): string {
|
||||
return `${language.code.toUpperCase()} - ${language.native_label || language.label}`;
|
||||
}
|
||||
@@ -219,6 +219,20 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-admin.language_name_placeholder": "French",
|
||||
"i18n:govoplan-admin.language_packages": "Language packages",
|
||||
"i18n:govoplan-admin.language_packages_help": "Installed packages define which language codes can be enabled. A package only becomes fully translated when matching module translation catalogs are present.",
|
||||
"i18n:govoplan-admin.language_package_administration.lp001": "Language packages and tenant defaults",
|
||||
"i18n:govoplan-admin.language_package_administration_description.lp001": "Install and activate language packages, choose the default for new tenants, and inspect exact module-version compatibility.",
|
||||
"i18n:govoplan-admin.language_package_administration_overview.lp001": "Package lifecycle, default tenant language, and module-version compatibility.",
|
||||
"i18n:govoplan-admin.language_package_settings_saved.lp001": "Language package settings saved.",
|
||||
"i18n:govoplan-admin.default_language_inheritance_help.lp001": "New tenants inherit this active language; tenant and user preferences can select only languages enabled above them.",
|
||||
"i18n:govoplan-admin.compatible.lp001": "Compatible",
|
||||
"i18n:govoplan-admin.incompatible.lp001": "Incompatible",
|
||||
"i18n:govoplan-admin.uninstallable.lp001": "Uninstallable",
|
||||
"i18n:govoplan-admin.module_version_catalogs.lp001": "{value0} module-version catalogs provide this language.",
|
||||
"i18n:govoplan-admin.activate.lp001": "Activate",
|
||||
"i18n:govoplan-admin.deactivate.lp001": "Deactivate",
|
||||
"i18n:govoplan-admin.default_language_must_remain_active.lp001": "The default language must remain active.",
|
||||
"i18n:govoplan-admin.module_version_compatibility.lp001": "Module-version compatibility",
|
||||
"i18n:govoplan-admin.no_module_language_catalogs.lp001": "No installed module language catalogs are available. Package and default-language controls remain usable.",
|
||||
"i18n:govoplan-admin.native_language_name": "Native language name",
|
||||
"i18n:govoplan-admin.native_language_name_placeholder": "Francais",
|
||||
"i18n:govoplan-admin.defaults_for_newly_created_tenants.9d0f4ccd": "Defaults for newly created tenants",
|
||||
@@ -732,6 +746,20 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-admin.language_name_placeholder": "Franzoesisch",
|
||||
"i18n:govoplan-admin.language_packages": "Sprachpakete",
|
||||
"i18n:govoplan-admin.language_packages_help": "Installierte Pakete legen fest, welche Sprachcodes aktiviert werden koennen. Ein Paket ist erst vollstaendig uebersetzt, wenn passende Uebersetzungskataloge der Module vorhanden sind.",
|
||||
"i18n:govoplan-admin.language_package_administration.lp001": "Sprachpakete und Mandantenstandards",
|
||||
"i18n:govoplan-admin.language_package_administration_description.lp001": "Sprachpakete installieren und aktivieren, den Standard fuer neue Mandanten waehlen und die exakte Modulversions-Kompatibilitaet pruefen.",
|
||||
"i18n:govoplan-admin.language_package_administration_overview.lp001": "Paketlebenszyklus, Standard-Mandantensprache und Modulversions-Kompatibilitaet.",
|
||||
"i18n:govoplan-admin.language_package_settings_saved.lp001": "Sprachpaket-Einstellungen gespeichert.",
|
||||
"i18n:govoplan-admin.default_language_inheritance_help.lp001": "Neue Mandanten erben diese aktive Sprache; Mandanten- und Benutzereinstellungen koennen nur oberhalb aktivierte Sprachen waehlen.",
|
||||
"i18n:govoplan-admin.compatible.lp001": "Kompatibel",
|
||||
"i18n:govoplan-admin.incompatible.lp001": "Inkompatibel",
|
||||
"i18n:govoplan-admin.uninstallable.lp001": "Deinstallierbar",
|
||||
"i18n:govoplan-admin.module_version_catalogs.lp001": "{value0} Modulversions-Kataloge stellen diese Sprache bereit.",
|
||||
"i18n:govoplan-admin.activate.lp001": "Aktivieren",
|
||||
"i18n:govoplan-admin.deactivate.lp001": "Deaktivieren",
|
||||
"i18n:govoplan-admin.default_language_must_remain_active.lp001": "Die Standardsprache muss aktiv bleiben.",
|
||||
"i18n:govoplan-admin.module_version_compatibility.lp001": "Modulversions-Kompatibilitaet",
|
||||
"i18n:govoplan-admin.no_module_language_catalogs.lp001": "Es sind keine Sprachkataloge installierter Module verfuegbar. Paket- und Standardspracheinstellungen bleiben nutzbar.",
|
||||
"i18n:govoplan-admin.native_language_name": "Nativer Sprachname",
|
||||
"i18n:govoplan-admin.native_language_name_placeholder": "Francais",
|
||||
"i18n:govoplan-admin.defaults_for_newly_created_tenants.9d0f4ccd": "Standards für neu erstellte Mandanten",
|
||||
|
||||
@@ -11,4 +11,5 @@ export {
|
||||
export { default as GovernanceTemplatesPanel } from "./features/admin/GovernanceTemplatesPanel";
|
||||
export { default as TenantModuleManagementPanel } from "./features/admin/TenantModuleManagementPanel";
|
||||
export { default as SystemSettingsPanel } from "./features/admin/SystemSettingsPanel";
|
||||
export { default as LanguagePackagesPanel } from "./features/admin/LanguagePackagesPanel";
|
||||
export type { PlatformWebModule, PlatformNavItem, PlatformRouteContribution, PlatformRouteContext } from "@govoplan/core-webui";
|
||||
|
||||
@@ -11,6 +11,7 @@ const GovernanceTemplatesPanel = lazy(() => import("./features/admin/GovernanceT
|
||||
const ModuleManagementPanel = lazy(() => import("./features/admin/ModuleManagementPanel"));
|
||||
const TenantModuleManagementPanel = lazy(() => import("./features/admin/TenantModuleManagementPanel"));
|
||||
const SystemSettingsPanel = lazy(() => import("./features/admin/SystemSettingsPanel"));
|
||||
const LanguagePackagesPanel = lazy(() => import("./features/admin/LanguagePackagesPanel"));
|
||||
const DataSubjectRequestsPanel = lazy(() => import("./features/admin/DataSubjectRequestsPanel"));
|
||||
|
||||
const translations = {
|
||||
@@ -50,6 +51,20 @@ const adminSections: AdminSectionsUiCapability = {
|
||||
canAccessMaintenance: hasScope(auth, "system:maintenance:access")
|
||||
})
|
||||
},
|
||||
{
|
||||
id: "system-language-packages",
|
||||
moduleId: "admin",
|
||||
kind: "settings",
|
||||
surfaceId: "admin.section.system-language-packages",
|
||||
label: "i18n:govoplan-admin.language_packages",
|
||||
group: "SYSTEM",
|
||||
order: 15,
|
||||
allOf: ["system:settings:read"],
|
||||
render: ({ settings, auth }) => createElement(LanguagePackagesPanel, {
|
||||
settings,
|
||||
canWrite: hasScope(auth, "system:settings:write")
|
||||
})
|
||||
},
|
||||
{
|
||||
id: "system-configuration-changes",
|
||||
moduleId: "admin",
|
||||
@@ -184,6 +199,7 @@ export const adminModule: PlatformWebModule = {
|
||||
viewSurfaces: [
|
||||
{ id: "admin.section.overview", moduleId: "admin", kind: "section", label: "i18n:govoplan-admin.overview.0efc2e6b", order: 0 },
|
||||
{ id: "admin.section.system-settings", moduleId: "admin", kind: "section", label: "i18n:govoplan-admin.system_general_settings.7cd662ed", order: 10 },
|
||||
{ id: "admin.section.system-language-packages", moduleId: "admin", kind: "section", label: "i18n:govoplan-admin.language_packages", order: 15 },
|
||||
{ id: "admin.section.system-configuration-changes", moduleId: "admin", kind: "section", label: "i18n:govoplan-admin.configuration_changes.82933bbb", order: 20 },
|
||||
{ id: "admin.section.system-configuration-packages", moduleId: "admin", kind: "section", label: "i18n:govoplan-admin.configuration_packages.eb2f05f1", order: 30 },
|
||||
{ id: "admin.section.system-role-templates", moduleId: "admin", kind: "section", label: "i18n:govoplan-admin.tenant_roles.51aca82d", order: 40 },
|
||||
|
||||
Reference in New Issue
Block a user