Release Admin v0.1.19 guided configuration packages
Module Package Release / publish-packages (push) Successful in 12s
Module Package Release / publish-packages (push) Successful in 12s
This commit is contained in:
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@govoplan/admin-webui",
|
||||
"version": "0.1.18",
|
||||
"version": "0.1.19",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "webui/src/index.ts",
|
||||
@@ -18,7 +18,7 @@
|
||||
"LICENSE"
|
||||
],
|
||||
"peerDependencies": {
|
||||
"@govoplan/core-webui": "^0.1.18",
|
||||
"@govoplan/core-webui": "^0.1.35",
|
||||
"lucide-react": "^1.23.0",
|
||||
"react": ">=19.2.7 <20",
|
||||
"react-dom": ">=19.2.7 <20",
|
||||
|
||||
+3
-4
@@ -4,14 +4,14 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "govoplan-admin"
|
||||
version = "0.1.18"
|
||||
version = "0.1.19"
|
||||
description = "GovOPlaN generic administration module."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
authors = [{ name = "GovOPlaN" }]
|
||||
dependencies = [
|
||||
"govoplan-core>=0.1.18",
|
||||
"govoplan-access>=0.1.18",
|
||||
"govoplan-core>=0.1.35",
|
||||
"govoplan-access>=0.1.20",
|
||||
]
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
@@ -22,4 +22,3 @@ govoplan_admin = ["py.typed"]
|
||||
|
||||
[project.entry-points."govoplan.modules"]
|
||||
admin = "govoplan_admin.backend.manifest:get_manifest"
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ ADMIN_ROLE_TEMPLATES = (
|
||||
manifest = ModuleManifest(
|
||||
id="admin",
|
||||
name="Admin",
|
||||
version="0.1.18",
|
||||
version="0.1.19",
|
||||
permissions=ADMIN_PERMISSIONS,
|
||||
role_templates=ADMIN_ROLE_TEMPLATES,
|
||||
required_capabilities=(
|
||||
@@ -168,11 +168,28 @@ manifest = ModuleManifest(
|
||||
title="Govern configuration and module lifecycle",
|
||||
summary="Admin owns reusable governance templates, configuration packages, and the operator-facing module lifecycle queue.",
|
||||
body=(
|
||||
"Configuration packages import or export module-owned configuration; they do not install software. Module catalog actions create reviewed install, update, activation, deactivation, or retirement requests for the trusted installer process. Governance templates materialize approved role and group structures through the owning Access contracts. Template assignment validation bulk-loads the selected tenants before mutation, and synchronization delegates one bounded versioned batch to Access instead of issuing per-tenant calls. Operators may preview or apply synchronization for up to 100 selected templates and 500 assignments. Every assignment returns an explicit outcome and provenance; missing tenants, protected memberships, role mappings, and module vetoes remain visible as blockers rather than being skipped. Retries are idempotent and preview/application runs are audited."
|
||||
"Configuration packages import or export module-owned configuration; they do not install software. The operator selects a package and tenant, runs preflight, and supplies only the deployment values declared by that package through generated fields. Editing the package, tenant, or supplied data makes the previous preflight stale and disables Apply until a new blocker-free preflight succeeds. Provider applies may commit independently, so the result states whether nothing changed, a retained database snapshot is the rollback path, or a partial apply requires recovery; the screen never promises atomic cross-module undo. Export selects the providers named by the package fragments, redacts secret requirements, and records source version, module versions, exporter, scope, and timestamp as provenance. Module catalog actions create reviewed install, update, activation, deactivation, or retirement requests for the trusted installer process. Governance templates materialize approved role and group structures through the owning Access contracts. Template assignment validation bulk-loads the selected tenants before mutation, and synchronization delegates one bounded versioned batch to Access instead of issuing per-tenant calls. Operators may preview or apply synchronization for up to 100 selected templates and 500 assignments. Every assignment returns an explicit outcome and provenance; missing tenants, protected memberships, role mappings, and module vetoes remain visible as blockers rather than being skipped. Retries are idempotent and preview/application runs are audited."
|
||||
),
|
||||
documentation_types=("admin",),
|
||||
audience=("system_admin", "operator", "module_admin"),
|
||||
related_modules=("access", "audit", "ops"),
|
||||
translations={
|
||||
"de": {
|
||||
"title": "Konfiguration und Modul-Lebenszyklus steuern",
|
||||
"summary": "Admin stellt wiederverwendbare Governance-Vorlagen, Konfigurationspakete und die Bedienoberfläche für den Modul-Lebenszyklus bereit.",
|
||||
"body": (
|
||||
"Konfigurationspakete importieren oder exportieren modul-eigene Konfiguration; sie installieren keine Software. "
|
||||
"Die ausführende Person wählt Paket und Mandant, startet die Vorprüfung und erfasst ausschließlich die vom Paket deklarierten Einsatzwerte in erzeugten Feldern. "
|
||||
"Eine Änderung an Paket, Mandant oder Eingabedaten macht die vorherige Vorprüfung ungültig und sperrt Anwenden, bis eine neue Vorprüfung ohne Blocker erfolgreich ist. "
|
||||
"Provider können ihre Änderungen unabhängig festschreiben. Das Ergebnis weist deshalb ausdrücklich aus, ob nichts geändert wurde, ob der aufbewahrte Datenbank-Snapshot der Rücksetzweg ist oder ob eine Teilanwendung Wiederherstellung verlangt; eine atomare modulübergreifende Rücknahme wird nicht zugesagt. "
|
||||
"Beim Export werden die in den Fragmenten genannten Provider ausgewählt, geheime Anforderungen geschwärzt und Quellversion, Modulversionen, exportierende Identität, Umfang und Zeitpunkt als Herkunftsnachweis festgehalten. "
|
||||
"Aktionen im Modulkatalog erzeugen geprüfte Installations-, Aktualisierungs-, Aktivierungs-, Deaktivierungs- oder Stilllegungsaufträge für den vertrauenswürdigen Installer-Prozess. "
|
||||
"Governance-Vorlagen materialisieren freigegebene Rollen- und Gruppenstrukturen über die zuständigen Access-Verträge. "
|
||||
"Die Validierung lädt ausgewählte Mandanten gesammelt; die Synchronisierung übergibt einen begrenzten, versionierten Stapel an Access. Bis zu 100 Vorlagen und 500 Zuordnungen werden je Lauf verarbeitet. "
|
||||
"Jede Zuordnung liefert Ergebnis und Herkunft; fehlende Mandanten, geschützte Mitgliedschaften, Rollenabbildungen und Modul-Vetos bleiben als Blocker sichtbar. Wiederholungen sind idempotent, Vorschau und Anwendung werden protokolliert."
|
||||
),
|
||||
}
|
||||
},
|
||||
metadata={
|
||||
"kind": "reference",
|
||||
"help_contexts": [
|
||||
@@ -181,7 +198,20 @@ manifest = ModuleManifest(
|
||||
"admin.configuration-packages",
|
||||
"admin.governance-templates",
|
||||
],
|
||||
"limitations": [
|
||||
"Configuration packages do not install modules or grant permissions.",
|
||||
"Generic rollback is snapshot recovery; providers may expose narrower compensation separately.",
|
||||
"Secret values are never included in export provenance or portable fragments.",
|
||||
],
|
||||
"operational_consequences": [
|
||||
"Changing package inputs invalidates the previous preflight and requires another review.",
|
||||
"A partial apply stops before later fragments and must be recovered before retrying.",
|
||||
"Retain the pre-apply database snapshot until post-apply verification is complete.",
|
||||
],
|
||||
"api_paths": [
|
||||
"/api/v1/admin/configuration-packages/dry-run",
|
||||
"/api/v1/admin/configuration-packages/apply",
|
||||
"/api/v1/admin/configuration-packages/export",
|
||||
"/api/v1/admin/system/governance-templates",
|
||||
"/api/v1/admin/system/governance-templates/synchronize",
|
||||
],
|
||||
|
||||
@@ -60,6 +60,30 @@ class InterfaceDocumentationContractTests(unittest.TestCase):
|
||||
},
|
||||
)
|
||||
|
||||
def test_configuration_package_help_explains_safety_boundaries(self) -> None:
|
||||
topic = next(
|
||||
item
|
||||
for item in manifest.documentation
|
||||
if item.id == "admin.governance-and-module-lifecycle"
|
||||
)
|
||||
|
||||
self.assertIn("previous preflight stale", topic.body)
|
||||
self.assertIn("partial apply requires recovery", topic.body)
|
||||
self.assertIn("provenance", topic.body)
|
||||
german = (topic.translations or {}).get("de", {})
|
||||
self.assertIn("vorherige Vorprüfung ungültig", german.get("body", ""))
|
||||
self.assertIn("Teilanwendung Wiederherstellung", german.get("body", ""))
|
||||
|
||||
metadata = topic.metadata or {}
|
||||
self.assertIn(
|
||||
"Generic rollback is snapshot recovery; providers may expose narrower compensation separately.",
|
||||
metadata.get("limitations", ()),
|
||||
)
|
||||
self.assertIn(
|
||||
"Changing package inputs invalidates the previous preflight and requires another review.",
|
||||
metadata.get("operational_consequences", ()),
|
||||
)
|
||||
|
||||
def test_module_administrator_has_only_tenant_module_permissions(self) -> None:
|
||||
permissions = {item.scope for item in manifest.permissions}
|
||||
template = next(item for item in manifest.role_templates if item.slug == "module_admin")
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@govoplan/admin-webui",
|
||||
"version": "0.1.18",
|
||||
"version": "0.1.19",
|
||||
"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.18",
|
||||
"@govoplan/core-webui": "^0.1.35",
|
||||
"lucide-react": "^1.23.0",
|
||||
"react": ">=19.2.7 <20",
|
||||
"react-dom": ">=19.2.7 <20",
|
||||
|
||||
@@ -27,6 +27,11 @@ for (const source of [changes, packages, templates, modules]) {
|
||||
assert.match(changes, /DataGrid/);
|
||||
assert.match(packages, /ReferenceSelect/);
|
||||
assert.match(packages, /manualReferences/);
|
||||
assert.match(packages, /reviewedFingerprint === currentFingerprint/);
|
||||
assert.match(packages, /RequiredDataInputs/);
|
||||
assert.match(packages, /type=\{item\.secret \? "password"/);
|
||||
assert.match(packages, /ConfigurationRollbackState/);
|
||||
assert.match(packages, /result\.provenance/);
|
||||
assert.match(templates, /TableActionGroup/);
|
||||
assert.match(modules, /StageRail/);
|
||||
assert.match(modules, /ActionBlockerHint/);
|
||||
|
||||
@@ -129,6 +129,26 @@ export type ConfigurationPackageFragment = {
|
||||
payload: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export type ConfigurationPackageRollback = {
|
||||
status: "blocked_before_apply" | "not_required" | "database_restore_required" | "partial_apply_requires_recovery";
|
||||
summary: string;
|
||||
recovery_action?: string | null;
|
||||
};
|
||||
|
||||
export type ConfigurationPackageExportProvenance = {
|
||||
exported_at: string;
|
||||
source_core_version: string;
|
||||
module_versions: Record<string, string>;
|
||||
tenant_id?: string | null;
|
||||
exporter_id?: string | null;
|
||||
selection: {
|
||||
scopes: string[];
|
||||
module_ids: string[];
|
||||
object_refs: string[];
|
||||
};
|
||||
redacted_secret_keys: string[];
|
||||
};
|
||||
|
||||
export type ConfigurationPackageRunPayload = {
|
||||
package: Record<string, unknown>;
|
||||
tenant_id?: string | null;
|
||||
@@ -963,6 +983,7 @@ export function applyConfigurationPackage(settings: ApiSettings, payload: Config
|
||||
diagnostics: ConfigurationPackageDiagnostic[];
|
||||
created_refs: Record<string, string>;
|
||||
updated_refs: Record<string, string>;
|
||||
rollback?: ConfigurationPackageRollback | null;
|
||||
}> {
|
||||
return apiFetch(settings, "/api/v1/admin/configuration-packages/apply", {
|
||||
method: "POST",
|
||||
@@ -979,6 +1000,7 @@ export function exportConfigurationPackage(settings: ApiSettings, payload: {
|
||||
fragments: ConfigurationPackageFragment[];
|
||||
data_requirements: ConfigurationPackageRequiredData[];
|
||||
diagnostics: ConfigurationPackageDiagnostic[];
|
||||
provenance?: ConfigurationPackageExportProvenance | null;
|
||||
}> {
|
||||
return apiFetch(settings, "/api/v1/admin/configuration-packages/export", {
|
||||
method: "POST",
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
type ConfigurationChangeRequest,
|
||||
type ConfigurationPackageDiagnostic,
|
||||
type ConfigurationPackagePlanItem,
|
||||
type ConfigurationPackageRollback,
|
||||
type ConfigurationPackageRequiredData } from
|
||||
"../../api/admin";
|
||||
import {
|
||||
@@ -63,6 +64,7 @@ type ApplyResult = {
|
||||
diagnostics: ConfigurationPackageDiagnostic[];
|
||||
created_refs: Record<string, string>;
|
||||
updated_refs: Record<string, string>;
|
||||
rollback?: ConfigurationPackageRollback | null;
|
||||
};
|
||||
|
||||
export default function ConfigurationPackagesPanel({ settings, auth, canWrite }: {settings: ApiSettings;auth: AuthInfo;canWrite: boolean;}) {
|
||||
@@ -74,6 +76,7 @@ export default function ConfigurationPackagesPanel({ settings, auth, canWrite }:
|
||||
const [changeRequestId, setChangeRequestId] = useState("");
|
||||
const [manualReferences, setManualReferences] = useState(false);
|
||||
const [dryRun, setDryRun] = useState<DryRunResult | null>(null);
|
||||
const [reviewedFingerprint, setReviewedFingerprint] = useState("");
|
||||
const [applyResult, setApplyResult] = useState<ApplyResult | null>(null);
|
||||
const [exportText, setExportText] = useState("");
|
||||
const [lastRequest, setLastRequest] = useState<ConfigurationChangeRequest | null>(null);
|
||||
@@ -86,6 +89,22 @@ export default function ConfigurationPackagesPanel({ settings, auth, canWrite }:
|
||||
const parsedPackage = useMemo(() => parseObject(packageText), [packageText]);
|
||||
const parsedSuppliedData = useMemo(() => parseObject(suppliedDataText), [suppliedDataText]);
|
||||
const canRun = Boolean(parsedPackage.value && parsedSuppliedData.value);
|
||||
const currentFingerprint = useMemo(
|
||||
() => packageRunFingerprint(parsedPackage.value, parsedSuppliedData.value, tenantId),
|
||||
[parsedPackage.value, parsedSuppliedData.value, tenantId]
|
||||
);
|
||||
const dryRunReady = Boolean(
|
||||
dryRun &&
|
||||
reviewedFingerprint === currentFingerprint &&
|
||||
!dryRun.diagnostics.some((item) => item.severity === "blocker")
|
||||
);
|
||||
const applyDisabledReason = !dryRun
|
||||
? "i18n:govoplan-admin.run_preflight_before_apply.c214a009"
|
||||
: reviewedFingerprint !== currentFingerprint
|
||||
? "i18n:govoplan-admin.preflight_is_stale.c214a010"
|
||||
: dryRun.diagnostics.some((item) => item.severity === "blocker")
|
||||
? "i18n:govoplan-admin.preflight_finished_with_blockers.7e2ca12b"
|
||||
: undefined;
|
||||
const tenantProvider = useMemo(
|
||||
() => createTenantReferenceProvider(settings),
|
||||
[settings.accessToken, settings.apiBaseUrl, settings.apiKey]
|
||||
@@ -132,6 +151,7 @@ export default function ConfigurationPackagesPanel({ settings, auth, canWrite }:
|
||||
try {
|
||||
const result = await dryRunConfigurationPackage(settings, runPayload(manifest, suppliedData));
|
||||
setDryRun(result);
|
||||
setReviewedFingerprint(packageRunFingerprint(manifest, suppliedData, tenantId));
|
||||
setMessage(result.diagnostics.some((item) => item.severity === "blocker") ? "i18n:govoplan-admin.preflight_finished_with_blockers.7e2ca12b" : "i18n:govoplan-admin.preflight_passed.c0c99055");
|
||||
} catch (err) {
|
||||
setError(adminErrorMessage(err));
|
||||
@@ -182,27 +202,31 @@ export default function ConfigurationPackagesPanel({ settings, auth, canWrite }:
|
||||
}
|
||||
}
|
||||
|
||||
async function exportAccessPackage() {
|
||||
async function exportPackage() {
|
||||
const manifest = requireParsedPackage();
|
||||
if (!manifest) return;
|
||||
const moduleIds = packageProviderModuleIds(manifest);
|
||||
setBusy("export");
|
||||
setError("");
|
||||
setMessage("");
|
||||
try {
|
||||
const result = await exportConfigurationPackage(settings, {
|
||||
tenant_id: tenantId.trim() || null,
|
||||
module_ids: ["access"],
|
||||
module_ids: moduleIds,
|
||||
scopes: tenantId.trim() ? ["tenant"] : ["system"]
|
||||
});
|
||||
const exported = {
|
||||
package_id: tenantId.trim() ? "govoplan.export.access-tenant" : "govoplan.export.access-system",
|
||||
name: tenantId.trim() ? "i18n:govoplan-admin.exported_tenant_access_configuration.9122c85c" : "i18n:govoplan-admin.exported_system_access_configuration.2e1c6f4c",
|
||||
package_id: `${String(manifest.package_id || "govoplan.configuration")}.export`,
|
||||
name: String(manifest.name || "i18n:govoplan-admin.export.f3e4fadb"),
|
||||
version: "0.1.0",
|
||||
required_modules: [{ module_id: "access" }],
|
||||
required_capabilities: ["configuration.provider", "access.configuration"],
|
||||
required_modules: moduleIds.map((module_id) => ({ module_id })),
|
||||
required_capabilities: ["configuration.provider", ...moduleIds.map((module_id) => `${module_id}.configuration`)],
|
||||
fragments: result.fragments,
|
||||
data_requirements: result.data_requirements
|
||||
data_requirements: result.data_requirements,
|
||||
provenance: result.provenance
|
||||
};
|
||||
setExportText(JSON.stringify(exported, null, 2));
|
||||
setMessage(result.diagnostics.some((item) => item.severity === "blocker") ? "i18n:govoplan-admin.export_finished_with_blockers.e2f70611" : "i18n:govoplan-admin.access_configuration_exported.098f200d");
|
||||
setMessage(result.diagnostics.some((item) => item.severity === "blocker") ? "i18n:govoplan-admin.export_finished_with_blockers.e2f70611" : "i18n:govoplan-admin.export.f3e4fadb");
|
||||
} catch (err) {
|
||||
setError(adminErrorMessage(err));
|
||||
} finally {
|
||||
@@ -210,6 +234,23 @@ export default function ConfigurationPackagesPanel({ settings, auth, canWrite }:
|
||||
}
|
||||
}
|
||||
|
||||
function setSuppliedDataValue(item: ConfigurationPackageRequiredData, rawValue: string | boolean) {
|
||||
const current = parsedSuppliedData.value ?? {};
|
||||
const next = { ...current };
|
||||
if (rawValue === "") {
|
||||
delete next[item.key];
|
||||
} else if (item.data_type === "boolean") {
|
||||
next[item.key] = Boolean(rawValue);
|
||||
} else if (item.data_type === "integer") {
|
||||
next[item.key] = Number.parseInt(String(rawValue), 10);
|
||||
} else if (item.data_type === "number") {
|
||||
next[item.key] = Number.parseFloat(String(rawValue));
|
||||
} else {
|
||||
next[item.key] = rawValue;
|
||||
}
|
||||
setSuppliedDataText(JSON.stringify(next, null, 2));
|
||||
}
|
||||
|
||||
function runPayload(manifest: Record<string, unknown>, suppliedData: Record<string, unknown>, requestId?: string | null) {
|
||||
return {
|
||||
package: manifest,
|
||||
@@ -306,14 +347,19 @@ export default function ConfigurationPackagesPanel({ settings, auth, canWrite }:
|
||||
<div className="button-row compact-actions">
|
||||
<Button onClick={() => void runDryRun()} disabled={!canRun || Boolean(busy)} disabledReason={busy ? ADMIN_INTERFACE_I18N.busy : !canRun ? ADMIN_INTERFACE_I18N.validJsonRequired : undefined}><Play size={16} /> i18n:govoplan-admin.dry_run.3d14659c</Button>
|
||||
<Button onClick={() => void requestApproval()} disabled={!canWrite || !parsedPackage.value || Boolean(busy)} disabledReason={busy ? ADMIN_INTERFACE_I18N.busy : !canWrite ? ADMIN_INTERFACE_I18N.writeRequired : !parsedPackage.value ? ADMIN_INTERFACE_I18N.packageRequired : undefined}><Check size={16} /> i18n:govoplan-admin.request_approval.6245aea1</Button>
|
||||
<Button variant="primary" onClick={() => setConfirmApply(true)} disabled={!canWrite || !canRun || Boolean(busy)} disabledReason={busy ? ADMIN_INTERFACE_I18N.busy : !canWrite ? ADMIN_INTERFACE_I18N.writeRequired : !canRun ? ADMIN_INTERFACE_I18N.validJsonRequired : undefined}><Check size={16} /> i18n:govoplan-admin.apply.cfea419c</Button>
|
||||
<Button onClick={() => void exportAccessPackage()} disabled={Boolean(busy)} disabledReason={busy ? ADMIN_INTERFACE_I18N.busy : undefined}><Download size={16} /> i18n:govoplan-admin.export_access.9a2eb91e</Button>
|
||||
<Button variant="primary" onClick={() => setConfirmApply(true)} disabled={!canWrite || !canRun || !dryRunReady || Boolean(busy)} disabledReason={busy ? ADMIN_INTERFACE_I18N.busy : !canWrite ? ADMIN_INTERFACE_I18N.writeRequired : !canRun ? ADMIN_INTERFACE_I18N.validJsonRequired : applyDisabledReason}><Check size={16} /> i18n:govoplan-admin.apply.cfea419c</Button>
|
||||
<Button onClick={() => void exportPackage()} disabled={!parsedPackage.value || Boolean(busy)} disabledReason={busy ? ADMIN_INTERFACE_I18N.busy : !parsedPackage.value ? ADMIN_INTERFACE_I18N.packageRequired : undefined}><Download size={16} /> i18n:govoplan-admin.export.f3e4fadb</Button>
|
||||
</div>
|
||||
{lastRequest && <p className="muted small-note">i18n:govoplan-admin.last_request.4508ef35 {lastRequest.id} ({lastRequest.status})</p>}
|
||||
</Card>
|
||||
|
||||
{dryRun && <Card title="i18n:govoplan-admin.preflight.8016a487">
|
||||
<PackageDiagnostics diagnostics={dryRun.diagnostics} />
|
||||
<RequiredDataInputs
|
||||
items={dryRun.required_data}
|
||||
values={parsedSuppliedData.value ?? {}}
|
||||
onChange={setSuppliedDataValue}
|
||||
/>
|
||||
<RequiredData items={dryRun.required_data} />
|
||||
<PackagePlan items={dryRun.plan} />
|
||||
</Card>}
|
||||
@@ -322,6 +368,7 @@ export default function ConfigurationPackagesPanel({ settings, auth, canWrite }:
|
||||
<PackageDiagnostics diagnostics={applyResult.diagnostics} />
|
||||
<ReferenceMap title="i18n:govoplan-admin.created.accf40c8" refs={applyResult.created_refs} />
|
||||
<ReferenceMap title="i18n:govoplan-admin.updated.f2f8570d" refs={applyResult.updated_refs} />
|
||||
{applyResult.rollback && <ConfigurationRollbackState rollback={applyResult.rollback} />}
|
||||
</Card>}
|
||||
|
||||
{exportText && <Card title="i18n:govoplan-admin.export.f3e4fadb">
|
||||
@@ -354,6 +401,87 @@ function parseObject(text: string): {value: Record<string, unknown> | null;error
|
||||
}
|
||||
}
|
||||
|
||||
function packageRunFingerprint(manifest: Record<string, unknown> | null, suppliedData: Record<string, unknown> | null, tenantId: string): string {
|
||||
if (!manifest || !suppliedData) return "";
|
||||
return JSON.stringify({ manifest, suppliedData, tenantId: tenantId.trim() || null });
|
||||
}
|
||||
|
||||
function packageProviderModuleIds(manifest: Record<string, unknown>): string[] {
|
||||
const fragments = Array.isArray(manifest.fragments) ? manifest.fragments : [];
|
||||
const moduleIds = fragments.flatMap((item) => {
|
||||
if (!item || typeof item !== "object" || Array.isArray(item)) return [];
|
||||
const moduleId = (item as Record<string, unknown>).module_id;
|
||||
return typeof moduleId === "string" && moduleId.trim() ? [moduleId.trim()] : [];
|
||||
});
|
||||
return Array.from(new Set(moduleIds.length > 0 ? moduleIds : ["access"])).sort();
|
||||
}
|
||||
|
||||
function RequiredDataInputs({ items, values, onChange }: {
|
||||
items: ConfigurationPackageRequiredData[];
|
||||
values: Record<string, unknown>;
|
||||
onChange: (item: ConfigurationPackageRequiredData, value: string | boolean) => void;
|
||||
}) {
|
||||
if (items.length === 0) return null;
|
||||
return (
|
||||
<div className="module-installer-request-grid">
|
||||
{items.map((item) => {
|
||||
const value = values[item.key];
|
||||
return (
|
||||
<FormField key={item.key} label={item.label} documentation={ADMIN_GOVERNANCE_DOCUMENTATION}>
|
||||
{item.data_type === "boolean" ? (
|
||||
<ToggleSwitch
|
||||
checked={Boolean(value)}
|
||||
onChange={(checked) => onChange(item, checked)}
|
||||
label={item.label}
|
||||
help={item.description || undefined}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<input
|
||||
type={item.secret ? "password" : item.data_type === "integer" || item.data_type === "number" ? "number" : "text"}
|
||||
value={value === undefined || value === null ? "" : String(value)}
|
||||
required={item.required}
|
||||
autoComplete={item.secret ? "off" : undefined}
|
||||
onChange={(event) => onChange(item, event.target.value)}
|
||||
/>
|
||||
{item.description ? <span className="muted small-note">{item.description}</span> : null}
|
||||
</>
|
||||
)}
|
||||
</FormField>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ConfigurationRollbackState({ rollback }: { rollback: ConfigurationPackageRollback }) {
|
||||
const status = rollback.status === "not_required" ? "success" : rollback.status === "blocked_before_apply" ? "inactive" : "warning";
|
||||
const copy = {
|
||||
blocked_before_apply: {
|
||||
label: "i18n:govoplan-admin.package_no_changes_applied.c214a001",
|
||||
summary: "i18n:govoplan-admin.package_blocked_before_apply.c214a002"
|
||||
},
|
||||
not_required: {
|
||||
label: "i18n:govoplan-admin.package_no_rollback_needed.c214a003",
|
||||
summary: "i18n:govoplan-admin.package_all_fragments_noop.c214a004"
|
||||
},
|
||||
database_restore_required: {
|
||||
label: "i18n:govoplan-admin.package_keep_database_snapshot.c214a005",
|
||||
summary: "i18n:govoplan-admin.package_snapshot_is_rollback.c214a006"
|
||||
},
|
||||
partial_apply_requires_recovery: {
|
||||
label: "i18n:govoplan-admin.package_partial_apply.c214a007",
|
||||
summary: "i18n:govoplan-admin.package_recover_before_retry.c214a008"
|
||||
}
|
||||
}[rollback.status];
|
||||
return (
|
||||
<div className="stack compact-stack">
|
||||
<StatusBadge status={status} label={copy.label} />
|
||||
<p>{copy.summary}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PackageDiagnostics({ diagnostics }: {diagnostics: ConfigurationPackageDiagnostic[];}) {
|
||||
if (diagnostics.length === 0) return <p className="muted">i18n:govoplan-admin.no_diagnostics.2b6e2630</p>;
|
||||
const columns: DataGridColumn<ConfigurationPackageDiagnostic>[] = [
|
||||
|
||||
@@ -2,6 +2,16 @@ import type { PlatformTranslations } from "@govoplan/core-webui";
|
||||
|
||||
export const generatedTranslations: PlatformTranslations = {
|
||||
"en": {
|
||||
"i18n:govoplan-admin.package_no_changes_applied.c214a001": "No changes applied",
|
||||
"i18n:govoplan-admin.package_blocked_before_apply.c214a002": "Preflight or the first provider blocked before configuration changed.",
|
||||
"i18n:govoplan-admin.package_no_rollback_needed.c214a003": "No rollback needed",
|
||||
"i18n:govoplan-admin.package_all_fragments_noop.c214a004": "All package fragments were already current and produced no changes.",
|
||||
"i18n:govoplan-admin.package_keep_database_snapshot.c214a005": "Keep the database snapshot",
|
||||
"i18n:govoplan-admin.package_snapshot_is_rollback.c214a006": "Provider-owned configuration changed. Retain the reviewed pre-apply database snapshot until verification is complete.",
|
||||
"i18n:govoplan-admin.package_partial_apply.c214a007": "Partial apply—recovery required",
|
||||
"i18n:govoplan-admin.package_recover_before_retry.c214a008": "A provider blocked after earlier changes committed. Restore the snapshot or complete an explicitly supported compensation before retrying.",
|
||||
"i18n:govoplan-admin.run_preflight_before_apply.c214a009": "Run preflight before applying this package.",
|
||||
"i18n:govoplan-admin.preflight_is_stale.c214a010": "Package, deployment data, or tenant changed. Run preflight again before applying.",
|
||||
"i18n:govoplan-admin.appearance_defaults": "Appearance defaults",
|
||||
"i18n:govoplan-admin.system_palette_default": "System palette default",
|
||||
"i18n:govoplan-admin.system_palette_default_help": "Validated default inherited by tenants and users without an explicit child choice.",
|
||||
@@ -539,6 +549,16 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-admin.working.049ac820": "Working..."
|
||||
},
|
||||
"de": {
|
||||
"i18n:govoplan-admin.package_no_changes_applied.c214a001": "Keine Änderungen angewendet",
|
||||
"i18n:govoplan-admin.package_blocked_before_apply.c214a002": "Die Vorprüfung oder der erste Provider hat blockiert, bevor Konfiguration geändert wurde.",
|
||||
"i18n:govoplan-admin.package_no_rollback_needed.c214a003": "Keine Rücksetzung erforderlich",
|
||||
"i18n:govoplan-admin.package_all_fragments_noop.c214a004": "Alle Paketfragmente waren bereits aktuell und haben nichts geändert.",
|
||||
"i18n:govoplan-admin.package_keep_database_snapshot.c214a005": "Datenbank-Snapshot aufbewahren",
|
||||
"i18n:govoplan-admin.package_snapshot_is_rollback.c214a006": "Modul-eigene Konfiguration wurde geändert. Der geprüfte Datenbank-Snapshot vor der Anwendung muss bis zum Abschluss der Verifikation erhalten bleiben.",
|
||||
"i18n:govoplan-admin.package_partial_apply.c214a007": "Teilanwendung – Wiederherstellung erforderlich",
|
||||
"i18n:govoplan-admin.package_recover_before_retry.c214a008": "Ein Provider hat nach bereits festgeschriebenen Änderungen blockiert. Vor einem neuen Versuch muss der Snapshot wiederhergestellt oder eine ausdrücklich unterstützte Kompensation abgeschlossen werden.",
|
||||
"i18n:govoplan-admin.run_preflight_before_apply.c214a009": "Vor dem Anwenden dieses Pakets muss die Vorprüfung ausgeführt werden.",
|
||||
"i18n:govoplan-admin.preflight_is_stale.c214a010": "Paket, Einsatzdaten oder Mandant wurden geändert. Vor dem Anwenden muss die Vorprüfung erneut ausgeführt werden.",
|
||||
"i18n:govoplan-admin.appearance_defaults": "Darstellungsstandards",
|
||||
"i18n:govoplan-admin.system_palette_default": "Systemstandard für die Farbpalette",
|
||||
"i18n:govoplan-admin.system_palette_default_help": "Geprüfter Standard für Mandanten und Benutzer ohne eigene Auswahl.",
|
||||
|
||||
Reference in New Issue
Block a user