4 Commits
Author SHA1 Message Date
zemion a9b840f66d fix(ui): anchor help to headings and document grouped navigation
Verified with the coordinated workspace changes by devkit full run
2026-09-08T225814-186389-0000-3e3ed7cd (all seven phases passed).
This shared UI pass does not mark the individual module reviews complete.
2026-09-09 02:03:30 +02:00
zemion f401b134e3 Release govoplan-admin v0.1.23: unify interface contracts and documentation
Module Package Release / publish-packages (push) Successful in 12s
2026-09-08 01:32:23 +02:00
zemion 3b45064069 fix(webui): classify consequential administration controls
Module Package Release / publish-packages (push) Successful in 12s
2026-08-24 11:36:32 +02:00
zemion c71ef013f1 docs: complete German structured documentation
Module Package Release / publish-packages (push) Successful in 11s
2026-08-24 01:15:31 +02:00
15 changed files with 84 additions and 35 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@govoplan/admin-webui",
"version": "0.1.20",
"version": "0.1.23",
"private": true,
"type": "module",
"main": "webui/src/index.ts",
@@ -18,7 +18,7 @@
"LICENSE"
],
"peerDependencies": {
"@govoplan/core-webui": "^0.1.35",
"@govoplan/core-webui": "^0.1.45",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
"react-dom": ">=19.2.7 <20",
+2 -2
View File
@@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
[project]
name = "govoplan-admin"
version = "0.1.20"
version = "0.1.23"
description = "GovOPlaN generic administration module."
readme = "README.md"
requires-python = ">=3.12"
authors = [{ name = "GovOPlaN" }]
dependencies = [
"govoplan-core>=0.1.35",
"govoplan-core>=0.1.45",
"govoplan-access>=0.1.20",
]
+1 -6
View File
@@ -88,7 +88,6 @@ from govoplan_core.core.module_installer_notifications import (
)
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,
)
@@ -1885,11 +1884,7 @@ def write_system_settings(
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,
payload.navigation.model_dump(mode="json") 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"))
@@ -0,0 +1,31 @@
"""German translations for public structured documentation metadata."""
from __future__ import annotations
from typing import Any
GERMAN_STRUCTURED_TRANSLATIONS: dict[str, dict[str, Any]] = {'admin.governance-and-module-lifecycle': {'limitations': ['Konfigurationspakete installieren '
'keine Module oder gewähren '
'Berechtigungen.',
'Generisches Rollback ist Snapshot '
'Recovery; Anbieter können eine engere '
'Vergütung separat aussetzen.',
'Geheimwerte werden niemals in '
'Exportprovenienz oder tragbare '
'Fragmente aufgenommen.'],
'operational_consequences': ['Das Ändern von '
'Paketeingaben ungültig '
'macht den vorherigen '
'Preflight und erfordert '
'eine weitere Überprüfung.',
'Eine teilweise Anwendung '
'stoppt vor späteren '
'Fragmenten und muss vor '
'dem erneuten Versuch '
'wiederhergestellt werden.',
'Bewahren Sie den '
'Pre-Apply-Datenbank-Snapshot '
'auf, bis die Überprüfung '
'nach der Anwendung '
'abgeschlossen ist.']}}
+19 -2
View File
@@ -1,5 +1,8 @@
from __future__ import annotations
from govoplan_core.core.modules import with_documentation_structured_translations
from govoplan_admin.backend.german_structured_documentation import GERMAN_STRUCTURED_TRANSLATIONS
from govoplan_admin.backend.db import models as admin_models # noqa: F401 - populate Admin ORM metadata
from govoplan_core.core.access import (
CAPABILITY_AUTH_PERMISSION_EVALUATOR,
@@ -78,7 +81,7 @@ ADMIN_ROLE_TEMPLATES = (
manifest = ModuleManifest(
id="admin",
name="Admin",
version="0.1.20",
version="0.1.23",
permissions=ADMIN_PERMISSIONS,
role_templates=ADMIN_ROLE_TEMPLATES,
required_capabilities=(
@@ -150,7 +153,9 @@ manifest = ModuleManifest(
id="admin.workspace",
title="Use the administration workspace",
summary="The administration workspace shows only the sections supplied by enabled modules and allowed by the current account's permissions.",
body="System and tenant administration share one workspace. Available sections can include settings, configuration changes and packages, governance templates, groups, and module lifecycle controls. A missing section normally means that its owning module is disabled or the current account lacks the required authority. System appearance settings select a validated palette default. Changing the separate palette lock additionally requires policy-write authority; a system lock suppresses tenant and personal palette choices, while an unlocked default remains inheritable and overridable. The separate advanced-override policy is disabled by default and also requires policy-write authority. Enabling it permits tenants to inherit or narrow access to Core's versioned, accessibility-validated personal accent, surface, and status editor; palette locks continue to win.",
body="The documentation book belongs beside each administration heading, including module and "
"language management; operational actions remain in the action row. "
"System and tenant administration share one workspace. Available sections can include settings, configuration changes and packages, governance templates, groups, and module lifecycle controls. A missing section normally means that its owning module is disabled or the current account lacks the required authority. System appearance settings select a validated palette default. Changing the separate palette lock additionally requires policy-write authority; a system lock suppresses tenant and personal palette choices, while an unlocked default remains inheritable and overridable. The separate advanced-override policy is disabled by default and also requires policy-write authority. Enabling it permits tenants to inherit or narrow access to Core's versioned, accessibility-validated personal accent, surface, and status editor; palette locks continue to win. Opening lists and creation or edit dialogs does not save changes. If an authorized section reports a load failure, reload after deployment and report the frontend diagnostic; do not broaden permissions or recreate records to work around a render failure.",
documentation_types=("admin", "user"),
audience=("tenant_admin", "system_admin", "operator"),
translations={
@@ -161,6 +166,9 @@ manifest = ModuleManifest(
"Konto berechtigt ist."
),
"body": (
"Das Dokumentationsbuch steht neben der jeweiligen Verwaltungsüberschrift, auch bei Modul- "
"und Sprachverwaltung; ausführbare Aktionen bleiben in der Aktionszeile. "
"Das Öffnen von Listen sowie Anlage- oder Bearbeitungsdialogen speichert keine Änderungen. Meldet ein berechtigter Bereich einen Ladefehler, laden Sie nach der Bereitstellung neu und melden Sie die Oberflächendiagnose; erweitern Sie keine Berechtigungen und legen Sie keine Datensätze erneut an, um einen Darstellungsfehler zu umgehen. "
"System- und Mandantenadministration teilen sich einen Arbeitsbereich. Verfügbare Bereiche können Einstellungen, "
"Konfigurationsänderungen und -pakete, Governance-Vorlagen, Gruppen und die Steuerung des Modul-Lebenszyklus umfassen. "
"Fehlt ein Bereich, ist normalerweise sein zuständiges Modul deaktiviert oder das aktuelle Konto besitzt nicht die "
@@ -325,6 +333,8 @@ manifest = ModuleManifest(
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."
" Use the shared layout editor to drag modules and separators, add or restore available modules, remove entries from the rail, and label groups. Keyboard handles support Space, arrows, Enter, and Escape; arrow buttons also move individual entries. Collapsed rails retain group dividers. Changes stay in the page draft until Save, and Use inherited layout restores live module defaults rather than copying them. Removing a navigation entry never uninstalls a module or deletes its records."
" Standard grouping needs no saved system configuration: Work; Services and cases; Records and documents; Communication; Meetings and decisions; Data and assurance; People and responsibility. Empty or unauthorized groups stay absent. Expanded rails show headings without divider lines; collapsed rails replace headings with horizontal group boundaries. The editor distinguishes inheritance, custom grouping and an explicitly flat layout. A deliberately flat layout is not silently reset. Use inherited layout and Save removes only the system override; it does not erase tenant or personal preferences. Personal over tenant over system over standard remains the baseline, and higher-level visibility locks remain enforced."
),
documentation_types=("admin",),
audience=("system_admin",),
@@ -343,6 +353,8 @@ manifest = ModuleManifest(
"Verschiebung. Das Entfernen der Systempräferenz stellt die Modulstandardwerte wieder her. Navigationsrichtlinien ändern "
"nur die Darstellung; Modulberechtigung, View-Richtlinie und Zugriffsrechte bestimmen weiterhin, ob eine Oberfläche "
"erreichbar ist."
" Im gemeinsamen Anordnungseditor ziehen Sie Module und Trennlinien, ergänzen verfügbare Module, entfernen Einträge aus der Leiste und benennen Gruppen. Tastaturgriffe unterstützen Leertaste, Pfeile, Eingabe und Escape; Pfeilschaltflächen verschieben einzelne Einträge ebenfalls. Eingeklappte Leisten behalten Gruppentrenner. Änderungen bleiben bis zum Speichern im Seitenentwurf. Geerbte Anordnung verwenden stellt die aktuellen Modulstandardwerte wieder her, statt sie zu kopieren. Das Entfernen eines Navigationseintrags deinstalliert kein Modul und löscht keine Datensätze."
" Die Standardgruppierung benötigt keine gespeicherte Systemkonfiguration: Arbeit; Leistungen und Vorgänge; Akten und Dokumente; Kommunikation; Termine und Entscheidungen; Daten und Qualitätssicherung; Personen und Verantwortung. Leere oder nicht berechtigte Gruppen bleiben verborgen. Ausgeklappte Leisten zeigen Überschriften ohne Trennlinien; eingeklappt werden daraus horizontale Gruppengrenzen. Der Editor unterscheidet Vererbung, eigene Gruppierung und eine ausdrücklich ungegliederte Anordnung. Eine bewusst ungegliederte Anordnung wird nicht stillschweigend zurückgesetzt. Geerbte Anordnung verwenden und Speichern entfernt nur die Systemanpassung, nicht Mandanten- oder persönliche Einstellungen. Die Rangfolge persönlich vor Mandant vor System vor Standard bleibt erhalten; übergeordnete Sichtbarkeitssperren bleiben wirksam."
),
}
},
@@ -559,5 +571,10 @@ manifest = ModuleManifest(
)
manifest = with_documentation_structured_translations(
manifest, locale="de", translations=GERMAN_STRUCTURED_TRANSLATIONS
)
def get_manifest() -> ModuleManifest:
return manifest
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@govoplan/admin-webui",
"version": "0.1.20",
"version": "0.1.23",
"private": true,
"type": "module",
"main": "src/index.ts",
@@ -17,7 +17,7 @@
"test:interface-patterns": "node scripts/test-interface-pattern-language.mjs"
},
"peerDependencies": {
"@govoplan/core-webui": "^0.1.35",
"@govoplan/core-webui": "^0.1.45",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
"react-dom": ">=19.2.7 <20",
@@ -30,7 +30,7 @@ export default function AdminOverviewPanel({ settings, onSelect, availableSectio
some((value) => value !== null && value !== undefined));
return (
<AdminPageLayout title="i18n:govoplan-admin.administration.b8be3d12" description="i18n:govoplan-admin.system_wide_governance_and_tenant_local_access_m.cda72499" loading={loading} error={error} actions={<><DocumentationHelpLink reference={ADMIN_WORKSPACE_DOCUMENTATION} /><Button onClick={() => void load()} disabled={loading} disabledReason={loading ? ADMIN_INTERFACE_I18N.loading : undefined}>i18n:govoplan-admin.reload.cce71553</Button></>}>
<AdminPageLayout title="i18n:govoplan-admin.administration.b8be3d12" titleHelp={<DocumentationHelpLink reference={ADMIN_WORKSPACE_DOCUMENTATION} />} description="i18n:govoplan-admin.system_wide_governance_and_tenant_local_access_m.cda72499" loading={loading} error={error} actions={<><Button onClick={() => void load()} disabled={loading} disabledReason={loading ? ADMIN_INTERFACE_I18N.loading : undefined}>i18n:govoplan-admin.reload.cce71553</Button></>}>
{overview && <>
{hasSystemMetrics && <>
<div className="admin-overview-section-label">i18n:govoplan-admin.system.bc0792d8</div>
@@ -63,7 +63,7 @@ export default function AdminOverviewPanel({ settings, onSelect, availableSectio
{availableSections.has("system-users") && <AreaLink title="i18n:govoplan-admin.users.57f2b181" text="i18n:govoplan-admin.global_accounts_tenant_memberships_and_system_ro.939ed01f" onClick={() => onSelect("system-users")} />}
{availableSections.has("system-file-connectors") && <AreaLink title="i18n:govoplan-admin.file_connections.1e362326" text="i18n:govoplan-admin.reusable_file_server_connections_credentials_and.8e5c7d43" onClick={() => onSelect("system-file-connectors")} />}
{availableSections.has("system-mail-servers") && <AreaLink title="i18n:govoplan-admin.mail_servers.d627326a" text="i18n:govoplan-admin.reusable_encrypted_smtp_imap_profiles_and_mail_p.31f150d1" onClick={() => onSelect("system-mail-servers")} />}
{availableSections.has("system-retention") && <AreaLink title="i18n:govoplan-admin.retention.c7199d9e" text="i18n:govoplan-admin.instance_privacy_retention_policy_and_lower_leve.b8d14069" onClick={() => onSelect("system-retention")} />}
{availableSections.has("system-retention") && <AreaLink title="i18n:govoplan-admin.retention.c7199d9e" text="i18n:govoplan-admin.instance_privacy_retention_policy_and_lower_leve.b8d14069" helpRiskReviewed="standard" onClick={() => onSelect("system-retention")} />}
{availableSections.has("system-view-policy") && <AreaLink title="View policy" text="Limit View actions, definitions, and surfaces across the instance." onClick={() => onSelect("system-view-policy")} />}
</div>
</Card>}
@@ -84,7 +84,7 @@ export default function AdminOverviewPanel({ settings, onSelect, availableSectio
{availableSections.has("tenant-file-connectors") && <AreaLink title="i18n:govoplan-admin.file_connections.1e362326" text="i18n:govoplan-admin.reusable_file_server_connections_credentials_and.8e5c7d43" onClick={() => onSelect("tenant-file-connectors")} />}
{availableSections.has("tenant-mail-servers") && <AreaLink title="i18n:govoplan-admin.mail_servers.d627326a" text="i18n:govoplan-admin.reusable_encrypted_smtp_imap_profiles_and_mail_p.31f150d1" onClick={() => onSelect("tenant-mail-servers")} />}
{availableSections.has("tenant-api-keys") && <AreaLink title="i18n:govoplan-admin.api_keys.94fcf3c2" text="i18n:govoplan-admin.scoped_automation_credentials_capped_by_owner_pe.b3e20e54" onClick={() => onSelect("tenant-api-keys")} />}
{availableSections.has("tenant-retention") && <AreaLink title="i18n:govoplan-admin.retention.c7199d9e" text="i18n:govoplan-admin.tenant_level_privacy_retention_limits_inherited_.48f4989d" onClick={() => onSelect("tenant-retention")} />}
{availableSections.has("tenant-retention") && <AreaLink title="i18n:govoplan-admin.retention.c7199d9e" text="i18n:govoplan-admin.tenant_level_privacy_retention_limits_inherited_.48f4989d" helpRiskReviewed="standard" onClick={() => onSelect("tenant-retention")} />}
{availableSections.has("tenant-modules") && <AreaLink title="Modules" text="Choose modules made available to this tenant by system policy." onClick={() => onSelect("tenant-modules")} />}
{availableSections.has("tenant-view-policy") && <AreaLink title="View policy" text="Narrow inherited View actions and available surfaces for this tenant." onClick={() => onSelect("tenant-view-policy")} />}
{availableSections.has("tenant-settings") && <AreaLink title="i18n:govoplan-admin.general.9239ee2c" text="i18n:govoplan-admin.tenant_locale_and_tenant_specific_settings.ac49c83b" onClick={() => onSelect("tenant-settings")} />}
@@ -96,7 +96,7 @@ export default function AdminOverviewPanel({ settings, onSelect, availableSectio
<div className="admin-overview-grid">
{availableSections.has("tenant-group-file-connectors") && <AreaLink title="i18n:govoplan-admin.file_connections.1e362326" text="i18n:govoplan-admin.group_file_connector_policy_limits" onClick={() => onSelect("tenant-group-file-connectors")} />}
{availableSections.has("tenant-group-mail-servers") && <AreaLink title="i18n:govoplan-admin.mail_servers.d627326a" text="i18n:govoplan-admin.group_mail_server_policy_limits" onClick={() => onSelect("tenant-group-mail-servers")} />}
{availableSections.has("tenant-group-retention") && <AreaLink title="i18n:govoplan-admin.retention.c7199d9e" text="i18n:govoplan-admin.group_retention_policy_limits" onClick={() => onSelect("tenant-group-retention")} />}
{availableSections.has("tenant-group-retention") && <AreaLink title="i18n:govoplan-admin.retention.c7199d9e" text="i18n:govoplan-admin.group_retention_policy_limits" helpRiskReviewed="standard" onClick={() => onSelect("tenant-group-retention")} />}
{availableSections.has("group-view-policy") && <AreaLink title="View policy" text="Set View limits for a selected group." onClick={() => onSelect("group-view-policy")} />}
</div>
</Card>}
@@ -105,7 +105,7 @@ export default function AdminOverviewPanel({ settings, onSelect, availableSectio
<div className="admin-overview-grid">
{availableSections.has("tenant-user-file-connectors") && <AreaLink title="i18n:govoplan-admin.file_connections.1e362326" text="i18n:govoplan-admin.user_file_connector_policy_limits" onClick={() => onSelect("tenant-user-file-connectors")} />}
{availableSections.has("tenant-user-mail-servers") && <AreaLink title="i18n:govoplan-admin.mail_servers.d627326a" text="i18n:govoplan-admin.user_mail_server_policy_limits" onClick={() => onSelect("tenant-user-mail-servers")} />}
{availableSections.has("tenant-user-retention") && <AreaLink title="i18n:govoplan-admin.retention.c7199d9e" text="i18n:govoplan-admin.user_retention_policy_limits" onClick={() => onSelect("tenant-user-retention")} />}
{availableSections.has("tenant-user-retention") && <AreaLink title="i18n:govoplan-admin.retention.c7199d9e" text="i18n:govoplan-admin.user_retention_policy_limits" helpRiskReviewed="standard" onClick={() => onSelect("tenant-user-retention")} />}
{availableSections.has("user-view-policy") && <AreaLink title="View policy" text="Set View limits for a selected user." onClick={() => onSelect("user-view-policy")} />}
</div>
</Card>}
@@ -124,6 +124,6 @@ function hasAnySection(sections: ReadonlySet<string>, candidates: readonly strin
return candidates.some((section) => sections.has(section));
}
function AreaLink({ title, text, onClick }: {title: string;text: string;onClick: () => void;}) {
return <button className="admin-overview-link" onClick={onClick}><strong>{title}</strong><span>{text}</span></button>;
function AreaLink({ title, text, helpRiskReviewed, onClick }: {title: string;text: string;helpRiskReviewed?: "standard";onClick: () => void;}) {
return <button className="admin-overview-link" data-help-risk-reviewed={helpRiskReviewed} onClick={onClick}><strong>{title}</strong><span>{text}</span></button>;
}
@@ -128,11 +128,12 @@ export default function ConfigurationChangesPanel({ settings, canApprove }: {set
<>
<AdminPageLayout
title="i18n:govoplan-admin.configuration_changes.82933bbb"
titleHelp={<DocumentationHelpLink reference={ADMIN_GOVERNANCE_DOCUMENTATION} />}
description="i18n:govoplan-admin.safety_controlled_configuration_requests_approva.e8259509"
loading={loading}
error={error}
success={message}
actions={<><DocumentationHelpLink reference={ADMIN_GOVERNANCE_DOCUMENTATION} /><Button onClick={() => void load()} disabled={loading || Boolean(busyId)} disabledReason={loading ? ADMIN_INTERFACE_I18N.loading : busyId ? ADMIN_INTERFACE_I18N.busy : undefined}><RefreshCw size={16} /> i18n:govoplan-admin.reload.cce71553</Button></>}>
actions={<><Button onClick={() => void load()} disabled={loading || Boolean(busyId)} disabledReason={loading ? ADMIN_INTERFACE_I18N.loading : busyId ? ADMIN_INTERFACE_I18N.busy : undefined}><RefreshCw size={16} /> i18n:govoplan-admin.reload.cce71553</Button></>}>
<Card title="i18n:govoplan-admin.requests.f7194e6a">
<DataGrid
@@ -280,11 +280,12 @@ export default function ConfigurationPackagesPanel({ settings, auth, canWrite }:
<>
<AdminPageLayout
title="i18n:govoplan-admin.configuration_packages.eb2f05f1"
titleHelp={<DocumentationHelpLink reference={ADMIN_GOVERNANCE_DOCUMENTATION} />}
description="i18n:govoplan-admin.import_preflight_approve_apply_and_export_module.29aec929"
loading={loading}
error={error || parsedPackage.error || parsedSuppliedData.error || ""}
success={message}
actions={<><DocumentationHelpLink reference={ADMIN_GOVERNANCE_DOCUMENTATION} /><Button onClick={() => void loadCatalog()} disabled={loading || Boolean(busy)} disabledReason={loading ? ADMIN_INTERFACE_I18N.loading : busy ? ADMIN_INTERFACE_I18N.busy : undefined}><RefreshCw size={16} /> i18n:govoplan-admin.reload.cce71553</Button></>}>
actions={<><Button onClick={() => void loadCatalog()} disabled={loading || Boolean(busy)} disabledReason={loading ? ADMIN_INTERFACE_I18N.loading : busy ? ADMIN_INTERFACE_I18N.busy : undefined}><RefreshCw size={16} /> i18n:govoplan-admin.reload.cce71553</Button></>}>
<Card title="i18n:govoplan-admin.catalog.4a88d27b">
<div className="button-row compact-actions">
@@ -1,8 +1,8 @@
import { DescriptionItem, DescriptionList } from "@govoplan/core-webui";
import { DescriptionItem, DescriptionList, FormGrid } from "@govoplan/core-webui";
import { MetricGrid } from "@govoplan/core-webui";
import { useEffect, useMemo, useState } from "react";
import { Download, Eye, Play, Plus, RefreshCw, Search, ShieldCheck } from "lucide-react";
import type { FormGrid, ApiSettings, DataGridColumn } from "@govoplan/core-webui";
import type { ApiSettings, DataGridColumn } from "@govoplan/core-webui";
import {
AdminIconButton,
AdminPageLayout,
@@ -1,7 +1,7 @@
import { DescriptionItem, DescriptionList } from "@govoplan/core-webui";
import { DescriptionItem, DescriptionList, FormGrid } from "@govoplan/core-webui";
import { useEffect, useMemo, useState } from "react";
import { Search, Pencil, Plus, RefreshCw, Trash2 } from "lucide-react";
import type { FormGrid, ApiSettings } from "@govoplan/core-webui";
import type { ApiSettings } from "@govoplan/core-webui";
import { Button } from "@govoplan/core-webui";
import { ConfirmDialog } from "@govoplan/core-webui";
import { DataGrid, type DataGridColumn } from "@govoplan/core-webui";
@@ -225,11 +225,12 @@ export default function GovernanceTemplatesPanel({
<>
<AdminPageLayout
title={title}
titleHelp={<DocumentationHelpLink reference={ADMIN_GOVERNANCE_DOCUMENTATION} />}
description={description}
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><AdminIconButton label={kind === "group" ? "i18n:govoplan-admin.add_group_template.b74d8f0f" : "i18n:govoplan-admin.add_tenant_role.fcd904ea"} icon={<Plus />} variant="primary" onClick={openCreate} disabled={!canWrite} disabledReason={!canWrite ? ADMIN_INTERFACE_I18N.governanceWriteRequired : undefined} /></>}>
actions={<><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><AdminIconButton label={kind === "group" ? "i18n:govoplan-admin.add_group_template.b74d8f0f" : "i18n:govoplan-admin.add_tenant_role.fcd904ea"} icon={<Plus />} variant="primary" onClick={openCreate} disabled={!canWrite} disabledReason={!canWrite ? ADMIN_INTERFACE_I18N.governanceWriteRequired : undefined} /></>}>
<div className="admin-table-surface">
<DataGrid id={`admin-system-${kind}-templates-v3`} rows={items} columns={columns} initialFit="container" getRowKey={(row) => row.id} emptyText={i18nMessage("i18n:govoplan-admin.no_central_value_templates_found.081149fa", { value0: kind })} />
@@ -192,12 +192,12 @@ export default function LanguagePackagesPanel({
return (
<AdminPageLayout
title="i18n:govoplan-admin.language_package_administration.lp001"
titleHelp={<DocumentationHelpLink reference={ADMIN_GOVERNANCE_DOCUMENTATION} />}
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>
</>}
@@ -1,6 +1,6 @@
import { MetricGrid } from "@govoplan/core-webui";
import { ActionToolbar, MetricGrid } from "@govoplan/core-webui";
import { useEffect, useMemo, useState } from "react";
import type { ActionToolbar, ApiSettings } from "@govoplan/core-webui";
import type { ApiSettings } from "@govoplan/core-webui";
import { ActionBlockerHint, AdminPageLayout, adminErrorMessage, Button, ConfirmDialog, dispatchPlatformModulesChanged, DocumentationHelpLink, formatDateTime, MetricCard, SegmentedControl, StageRail, StatusBadge, ToggleSwitch, i18nMessage, useUnsavedDraftGuard, type FormatDateTimeOptions } from "@govoplan/core-webui";
import { Check, Clock, FileText, Pencil, Send } from "lucide-react";
import {
@@ -401,11 +401,12 @@ export default function ModuleManagementPanel({ settings, canWrite, canAccessMai
<>
<AdminPageLayout
title="i18n:govoplan-admin.system_modules.629539f2"
titleHelp={<DocumentationHelpLink reference={MODULE_LIFECYCLE_DOCUMENTATION} />}
description="i18n:govoplan-admin.installed_modules_active_runtime_state_and_saved.f33907dd"
loading={loading}
error={error}
success={success}
actions={<><DocumentationHelpLink reference={MODULE_LIFECYCLE_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()} disabled={saveDisabled} disabledReason={saveDisabledReason}>{busy ? "i18n:govoplan-admin.working.049ac820" : saveLabel}</Button></>}>
actions={<><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()} disabled={saveDisabled} disabledReason={saveDisabledReason}>{busy ? "i18n:govoplan-admin.working.049ac820" : saveLabel}</Button></>}>
{catalog && <>
<MetricGrid columns={5} minimum="compact">
@@ -695,7 +696,7 @@ export default function ModuleManagementPanel({ settings, canWrite, canAccessMai
<label><span>i18n:govoplan-admin.action.97c89a4d</span><select value={item.action} disabled={!canWrite || planBusy} onChange={(event) => updatePlanItem(index, { action: event.target.value as ModuleInstallPlanItem["action"] })}><option value="install">i18n:govoplan-admin.install.fd6c3ebf</option><option value="update">i18n:govoplan-admin.update.503a059f</option><option value="uninstall">i18n:govoplan-admin.uninstall.a735da1d</option></select></label>
<label><span>i18n:govoplan-admin.module.b8ff0289</span><input value={item.module_id} disabled={!canWrite || planBusy} onChange={(event) => updatePlanItem(index, { module_id: event.target.value })} placeholder="files" /></label>
<label><span>i18n:govoplan-admin.python_package.34591c71</span><input value={item.python_package ?? ""} disabled={!canWrite || planBusy} onChange={(event) => updatePlanItem(index, { python_package: event.target.value })} placeholder="govoplan-files" /></label>
<ToggleSwitch label="i18n:govoplan-admin.destroy_data.34557c3c" checked={Boolean(item.destroy_data)} onChange={(checked) => updatePlanItem(index, { destroy_data: checked })} disabled={!canWrite || planBusy || item.action !== "uninstall"} help={!canWrite ? ADMIN_INTERFACE_I18N.writeRequired : planBusy ? ADMIN_INTERFACE_I18N.busy : item.action !== "uninstall" ? ADMIN_INTERFACE_I18N.uninstallOnly : undefined} />
<ToggleSwitch label="i18n:govoplan-admin.destroy_data.34557c3c" helpContextId="admin.module-lifecycle.plan" helpModuleId="admin" checked={Boolean(item.destroy_data)} onChange={(checked) => updatePlanItem(index, { destroy_data: checked })} disabled={!canWrite || planBusy || item.action !== "uninstall"} help={!canWrite ? ADMIN_INTERFACE_I18N.writeRequired : planBusy ? ADMIN_INTERFACE_I18N.busy : item.action !== "uninstall" ? ADMIN_INTERFACE_I18N.uninstallOnly : undefined} />
<ToggleSwitch label="i18n:govoplan-admin.data_safety_reviewed.b4774edc" checked={Boolean(item.data_safety_acknowledged)} onChange={(checked) => updatePlanItem(index, { data_safety_acknowledged: checked })} disabled={!canWrite || planBusy || item.action === "uninstall"} help={!canWrite ? ADMIN_INTERFACE_I18N.writeRequired : planBusy ? ADMIN_INTERFACE_I18N.busy : item.action === "uninstall" ? ADMIN_INTERFACE_I18N.installUpdateOnly : undefined} />
<label className="wide"><span>i18n:govoplan-admin.python_ref.1af35d0d</span><input value={item.python_ref ?? ""} disabled={!canWrite || planBusy || item.action === "uninstall"} onChange={(event) => updatePlanItem(index, { python_ref: event.target.value })} placeholder="govoplan-files @ git+ssh://...@v0.1.4" /></label>
<label><span>i18n:govoplan-admin.webui_package.19645536</span><input value={item.webui_package ?? ""} disabled={!canWrite || planBusy} onChange={(event) => updatePlanItem(index, { webui_package: event.target.value })} placeholder="@govoplan/files-webui" /></label>
@@ -82,11 +82,12 @@ export default function SystemSettingsPanel({ settings, canWrite, canAccessMaint
return (
<AdminPageLayout
title="i18n:govoplan-admin.system_general_settings.7cd662ed"
titleHelp={<DocumentationHelpLink reference={ADMIN_GOVERNANCE_DOCUMENTATION} />}
description="i18n:govoplan-admin.instance_wide_defaults_and_tenant_governance_cap.096a4f97"
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></>}>
actions={<><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.tenant_administration_capabilities.5d265972">
@@ -100,6 +101,7 @@ export default function SystemSettingsPanel({ settings, canWrite, canAccessMaint
<Card title="System navigation order">
<NavigationPreferenceEditor
items={navigationItems}
productAreas={modules.flatMap((module) => module.productAreas ?? [])}
value={draft.navigation}
onChange={(navigation) => setDraft({ ...draft, navigation })}
scope="system"
@@ -211,6 +211,7 @@ export default function TenantModuleManagementPanel({ settings, scope, canWrite
return (
<AdminPageLayout
title={labels.title}
titleHelp={<DocumentationHelpLink reference={DOCUMENTATION} label="Open module governance documentation" />}
description={labels.description}
loading={loading}
error={error}
@@ -231,7 +232,6 @@ export default function TenantModuleManagementPanel({ settings, scope, canWrite
<Button variant="primary" onClick={() => void save()} disabled={!canWrite || !dirty || busy}>
<Save size={16} /> {busy ? "Saving..." : "Save"}
</Button>
<DocumentationHelpLink reference={DOCUMENTATION} label="Open module governance documentation" />
</>
}
>