Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 36291a57c1 | |||
| e8a3e1c18f | |||
| 158b59dfb1 | |||
| 0fcd4dc06f | |||
| e8cb9d4fb2 | |||
| fcf93f438d | |||
| f0ff4ee51d | |||
| c9e1fb287f | |||
| 11ecf362a3 | |||
| 24edb7eb8a |
20
README.md
20
README.md
@@ -1,5 +1,9 @@
|
||||
# GovOPlaN Admin
|
||||
|
||||
<!-- govoplan-repository-type:start -->
|
||||
**Repository type:** module (platform).
|
||||
<!-- govoplan-repository-type:end -->
|
||||
|
||||
`govoplan-admin` owns generic system administration API and WebUI contributions
|
||||
during the GovOPlaN module split.
|
||||
|
||||
@@ -43,3 +47,19 @@ Package mutation is intentionally not executed inside the FastAPI request. The
|
||||
admin UI records operator intent and queues or renders commands for the trusted
|
||||
installer process described in
|
||||
`/mnt/DATA/git/govoplan-core/docs/MODULE_ARCHITECTURE.md`.
|
||||
|
||||
## Package Surfaces
|
||||
|
||||
The admin UI intentionally exposes two different package concepts:
|
||||
|
||||
- **Configuration packages** are import/export bundles for module-owned
|
||||
configuration data. They support dry-run diagnostics, approval, apply, and
|
||||
export workflows without installing Python or WebUI packages.
|
||||
- **Module package catalog** and **operator install plan** live under
|
||||
**Modules**. They describe approved release artifacts, install/update/remove
|
||||
plans, preflight blockers, maintenance-mode requirements, installer-daemon
|
||||
requests, and rollback visibility.
|
||||
|
||||
These surfaces should stay separate in navigation and copy. If future UI work
|
||||
combines them visually, it must still preserve the operator distinction between
|
||||
configuration mutation and package installation.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@govoplan/admin-webui",
|
||||
"version": "0.1.6",
|
||||
"version": "0.1.8",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "webui/src/index.ts",
|
||||
@@ -18,7 +18,7 @@
|
||||
"LICENSE"
|
||||
],
|
||||
"peerDependencies": {
|
||||
"@govoplan/core-webui": "^0.1.6",
|
||||
"@govoplan/core-webui": "^0.1.9",
|
||||
"lucide-react": "^1.23.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
|
||||
@@ -4,14 +4,14 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "govoplan-admin"
|
||||
version = "0.1.6"
|
||||
version = "0.1.8"
|
||||
description = "GovOPlaN generic administration module."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
authors = [{ name = "GovOPlaN" }]
|
||||
dependencies = [
|
||||
"govoplan-core>=0.1.6",
|
||||
"govoplan-access>=0.1.6",
|
||||
"govoplan-core>=0.1.8",
|
||||
"govoplan-access>=0.1.8",
|
||||
]
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
|
||||
@@ -31,6 +31,7 @@ from govoplan_core.core.configuration_control import (
|
||||
)
|
||||
from govoplan_core.core.module_management import (
|
||||
PROTECTED_MODULES,
|
||||
ModuleInstallPlan,
|
||||
ModuleInstallPlanItem,
|
||||
ModuleManagementError,
|
||||
configured_enabled_modules,
|
||||
@@ -49,6 +50,7 @@ from govoplan_core.core.module_installer import (
|
||||
default_installer_runtime_dir,
|
||||
list_module_installer_runs,
|
||||
list_module_installer_requests,
|
||||
module_install_catalog_companion_module_ids,
|
||||
module_installer_daemon_status,
|
||||
module_install_preflight,
|
||||
module_installer_lock_status,
|
||||
@@ -57,6 +59,12 @@ from govoplan_core.core.module_installer import (
|
||||
read_module_installer_request,
|
||||
retry_module_installer_request,
|
||||
)
|
||||
from govoplan_core.core.module_installer_notifications import (
|
||||
emit_module_installer_notification,
|
||||
installer_notification_body,
|
||||
installer_notification_priority,
|
||||
installer_notification_subject,
|
||||
)
|
||||
from govoplan_core.core.module_license import module_license_decision, module_license_diagnostics
|
||||
from govoplan_core.core.module_package_catalog import record_module_package_catalog_acceptance, validate_module_package_catalog
|
||||
from govoplan_core.core.maintenance import MAINTENANCE_ACCESS_SCOPE, MaintenanceMode, saved_maintenance_mode, save_maintenance_mode
|
||||
@@ -145,6 +153,11 @@ def _request_registry(request: Request) -> PlatformRegistry:
|
||||
return registry
|
||||
|
||||
|
||||
def _optional_request_registry(request: Request) -> PlatformRegistry | None:
|
||||
registry = getattr(request.app.state, "govoplan_registry", None)
|
||||
return registry if isinstance(registry, PlatformRegistry) else None
|
||||
|
||||
|
||||
def _request_lifecycle(request: Request) -> ModuleLifecycleManager:
|
||||
lifecycle = getattr(request.app.state, "govoplan_lifecycle", None)
|
||||
if not isinstance(lifecycle, ModuleLifecycleManager):
|
||||
@@ -152,6 +165,29 @@ def _request_lifecycle(request: Request) -> ModuleLifecycleManager:
|
||||
return lifecycle
|
||||
|
||||
|
||||
def _emit_installer_request_notification(
|
||||
*,
|
||||
session: Session,
|
||||
registry: PlatformRegistry | None,
|
||||
tenant_id: str | None,
|
||||
request: dict[str, object],
|
||||
event_kind: str,
|
||||
recipient_id: str | None = None,
|
||||
) -> None:
|
||||
status_value = str(request.get("status") or event_kind.rsplit(".", 1)[-1])
|
||||
emit_module_installer_notification(
|
||||
session=session,
|
||||
registry=registry,
|
||||
tenant_id=tenant_id,
|
||||
request=request,
|
||||
event_kind=event_kind,
|
||||
subject=installer_notification_subject(event_kind, request),
|
||||
body_text=installer_notification_body(event_kind, request),
|
||||
recipient_id=recipient_id,
|
||||
priority=installer_notification_priority(status_value),
|
||||
)
|
||||
|
||||
|
||||
def _http_admin_error(exc: Exception) -> HTTPException:
|
||||
if isinstance(exc, AdminConflictError):
|
||||
return HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(exc))
|
||||
@@ -429,55 +465,96 @@ def _webui_root() -> Path | None:
|
||||
|
||||
|
||||
def _upsert_install_plan_item(session: Session, item: ModuleInstallPlanItem):
|
||||
return _upsert_install_plan_items(session, [item])
|
||||
|
||||
|
||||
def _upsert_install_plan_items(session: Session, items: list[ModuleInstallPlanItem]):
|
||||
existing = saved_module_install_plan(session)
|
||||
replacement_ids = {item.module_id for item in items if item.status == "planned"}
|
||||
retained = [
|
||||
current
|
||||
for current in existing.items
|
||||
if not (current.status == "planned" and current.module_id == item.module_id)
|
||||
if not (current.status == "planned" and current.module_id in replacement_ids)
|
||||
]
|
||||
return save_module_install_plan(session, [*retained, item])
|
||||
return save_module_install_plan(session, [*retained, *items])
|
||||
|
||||
|
||||
def _catalog_plan_item(module_id: str, available_module_ids: set[str]) -> tuple[ModuleInstallPlanItem, dict[str, object]]:
|
||||
result = validate_module_package_catalog()
|
||||
def _catalog_plan_item(
|
||||
module_id: str,
|
||||
available_module_ids: set[str],
|
||||
*,
|
||||
validation: dict[str, object] | None = None,
|
||||
) -> tuple[ModuleInstallPlanItem, dict[str, object]]:
|
||||
result = validation or validate_module_package_catalog()
|
||||
_require_valid_module_catalog(result)
|
||||
raw_item = _catalog_install_or_update_item(result, module_id)
|
||||
action = _catalog_plan_action(raw_item, module_id, available_module_ids)
|
||||
license_decision = module_license_decision(_catalog_license_features(raw_item))
|
||||
_require_catalog_action_license(license_decision, action=action, module_id=module_id)
|
||||
return ModuleInstallPlanItem(
|
||||
module_id=str(raw_item["module_id"]),
|
||||
action=action,
|
||||
source="catalog",
|
||||
catalog=_catalog_plan_metadata(result),
|
||||
python_package=_catalog_string(raw_item, "python_package"),
|
||||
python_ref=_catalog_string(raw_item, "python_ref"),
|
||||
webui_package=_catalog_string(raw_item, "webui_package"),
|
||||
webui_ref=_catalog_string(raw_item, "webui_ref"),
|
||||
notes=_catalog_plan_notes(raw_item, license_decision),
|
||||
), result
|
||||
|
||||
|
||||
def _require_valid_module_catalog(result: dict[str, object]) -> None:
|
||||
if not result.get("valid"):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
||||
detail=str(result.get("error") or "Module package catalog is invalid."),
|
||||
)
|
||||
|
||||
|
||||
def _catalog_install_or_update_item(result: dict[str, object], module_id: str) -> dict[str, object]:
|
||||
for raw_item in result.get("modules", []):
|
||||
if not isinstance(raw_item, dict):
|
||||
continue
|
||||
if raw_item.get("module_id") != module_id or raw_item.get("action") not in {"install", "update"}:
|
||||
continue
|
||||
raw_action = raw_item.get("action")
|
||||
action = "update" if raw_action == "update" or module_id in available_module_ids else "install"
|
||||
license_decision = module_license_decision(_catalog_license_features(raw_item))
|
||||
if not license_decision.get("allowed"):
|
||||
missing = license_decision.get("missing_features")
|
||||
missing_text = ", ".join(str(item) for item in missing) if isinstance(missing, list) else "required feature"
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=f"License does not allow {action} for {module_id}: {missing_text}.",
|
||||
)
|
||||
notes = raw_item.get("notes") if isinstance(raw_item.get("notes"), str) else None
|
||||
if license_decision.get("reason") and license_decision.get("missing_features"):
|
||||
prefix = f"{notes}\n" if notes else ""
|
||||
notes = f"{prefix}License warning: {license_decision['reason']}"
|
||||
return ModuleInstallPlanItem(
|
||||
module_id=str(raw_item["module_id"]),
|
||||
action=action,
|
||||
source="catalog",
|
||||
catalog=_catalog_plan_metadata(result),
|
||||
python_package=raw_item.get("python_package") if isinstance(raw_item.get("python_package"), str) else None,
|
||||
python_ref=raw_item.get("python_ref") if isinstance(raw_item.get("python_ref"), str) else None,
|
||||
webui_package=raw_item.get("webui_package") if isinstance(raw_item.get("webui_package"), str) else None,
|
||||
webui_ref=raw_item.get("webui_ref") if isinstance(raw_item.get("webui_ref"), str) else None,
|
||||
notes=notes,
|
||||
), result
|
||||
return raw_item
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Catalog install/update entry not found: {module_id}")
|
||||
|
||||
|
||||
def _catalog_plan_action(raw_item: dict[str, object], module_id: str, available_module_ids: set[str]) -> str:
|
||||
return "update" if raw_item.get("action") == "update" or module_id in available_module_ids else "install"
|
||||
|
||||
|
||||
def _require_catalog_action_license(
|
||||
license_decision: dict[str, object],
|
||||
*,
|
||||
action: str,
|
||||
module_id: str,
|
||||
) -> None:
|
||||
if license_decision.get("allowed"):
|
||||
return
|
||||
missing = license_decision.get("missing_features")
|
||||
missing_text = ", ".join(str(item) for item in missing) if isinstance(missing, list) else "required feature"
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=f"License does not allow {action} for {module_id}: {missing_text}.",
|
||||
)
|
||||
|
||||
|
||||
def _catalog_plan_notes(raw_item: dict[str, object], license_decision: dict[str, object]) -> str | None:
|
||||
notes = _catalog_string(raw_item, "notes")
|
||||
if not (license_decision.get("reason") and license_decision.get("missing_features")):
|
||||
return notes
|
||||
prefix = f"{notes}\n" if notes else ""
|
||||
return f"{prefix}License warning: {license_decision['reason']}"
|
||||
|
||||
|
||||
def _catalog_string(raw_item: dict[str, object], field: str) -> str | None:
|
||||
value = raw_item.get(field)
|
||||
return value if isinstance(value, str) else None
|
||||
|
||||
|
||||
def _catalog_plan_metadata(validation: dict[str, object]) -> dict[str, object]:
|
||||
metadata: dict[str, object] = {
|
||||
"source": validation.get("source") or validation.get("path"),
|
||||
@@ -651,6 +728,7 @@ def read_module_install_request_detail(
|
||||
@router.post("/system/modules/install-requests", response_model=ModuleInstallerRequestItem)
|
||||
def create_module_install_request(
|
||||
payload: ModuleInstallerRequestCreateRequest,
|
||||
request_context: Request,
|
||||
session: Session = Depends(get_session),
|
||||
principal: ApiPrincipal = Depends(require_scope("system:settings:write")),
|
||||
):
|
||||
@@ -663,8 +741,17 @@ def create_module_install_request(
|
||||
request = queue_module_installer_request(
|
||||
runtime_dir=runtime_dir,
|
||||
requested_by=principal.user.id,
|
||||
tenant_id=principal.tenant_id,
|
||||
options=payload.options.model_dump(exclude_none=True),
|
||||
)
|
||||
_emit_installer_request_notification(
|
||||
session=session,
|
||||
registry=_optional_request_registry(request_context),
|
||||
tenant_id=principal.tenant_id,
|
||||
request=request,
|
||||
event_kind="module_installer.request.queued",
|
||||
recipient_id=principal.user.id,
|
||||
)
|
||||
audit_from_principal(
|
||||
session,
|
||||
principal,
|
||||
@@ -686,6 +773,7 @@ def create_module_install_request(
|
||||
@router.post("/system/modules/install-requests/{request_id}/cancel", response_model=ModuleInstallerRequestItem)
|
||||
def cancel_module_install_request(
|
||||
request_id: str,
|
||||
request_context: Request,
|
||||
session: Session = Depends(get_session),
|
||||
principal: ApiPrincipal = Depends(require_scope("system:settings:write")),
|
||||
):
|
||||
@@ -703,6 +791,14 @@ def cancel_module_install_request(
|
||||
)
|
||||
except ModuleInstallerError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_CONTENT, detail=str(exc)) from exc
|
||||
_emit_installer_request_notification(
|
||||
session=session,
|
||||
registry=_optional_request_registry(request_context),
|
||||
tenant_id=str(request.get("tenant_id") or principal.tenant_id),
|
||||
request=request,
|
||||
event_kind="module_installer.request.cancelled",
|
||||
recipient_id=str(request.get("requested_by") or principal.user.id),
|
||||
)
|
||||
audit_from_principal(
|
||||
session,
|
||||
principal,
|
||||
@@ -724,6 +820,7 @@ def cancel_module_install_request(
|
||||
@router.post("/system/modules/install-requests/{request_id}/retry", response_model=ModuleInstallerRequestItem)
|
||||
def retry_module_install_request(
|
||||
request_id: str,
|
||||
request_context: Request,
|
||||
session: Session = Depends(get_session),
|
||||
principal: ApiPrincipal = Depends(require_scope("system:settings:write")),
|
||||
):
|
||||
@@ -741,6 +838,14 @@ def retry_module_install_request(
|
||||
)
|
||||
except ModuleInstallerError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_CONTENT, detail=str(exc)) from exc
|
||||
_emit_installer_request_notification(
|
||||
session=session,
|
||||
registry=_optional_request_registry(request_context),
|
||||
tenant_id=str(request.get("tenant_id") or principal.tenant_id),
|
||||
request=request,
|
||||
event_kind="module_installer.request.queued",
|
||||
recipient_id=principal.user.id,
|
||||
)
|
||||
audit_from_principal(
|
||||
session,
|
||||
principal,
|
||||
@@ -801,7 +906,19 @@ def plan_module_install_from_catalog(
|
||||
lifecycle = getattr(request.app.state, "govoplan_lifecycle", None)
|
||||
available = dict(lifecycle.available_modules) if isinstance(lifecycle, ModuleLifecycleManager) else available_module_manifests(ignore_load_errors=True)
|
||||
item, validation = _catalog_plan_item(module_id, set(available))
|
||||
plan = _upsert_install_plan_item(session, item)
|
||||
existing = saved_module_install_plan(session)
|
||||
retained = [
|
||||
current
|
||||
for current in existing.items
|
||||
if not (current.status == "planned" and current.module_id == item.module_id)
|
||||
]
|
||||
candidate_plan = ModuleInstallPlan(items=tuple([*retained, item]))
|
||||
companion_ids = module_install_catalog_companion_module_ids(candidate_plan, available, validation=validation)
|
||||
companion_items = [
|
||||
_catalog_plan_item(companion_id, set(available), validation=validation)[0]
|
||||
for companion_id in companion_ids
|
||||
]
|
||||
plan = _upsert_install_plan_items(session, [item, *companion_items])
|
||||
record_module_package_catalog_acceptance(validation)
|
||||
except ModuleManagementError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_CONTENT, detail=str(exc)) from exc
|
||||
@@ -816,6 +933,7 @@ def plan_module_install_from_catalog(
|
||||
module_id=module_id,
|
||||
outcome="planned",
|
||||
items=[item.as_dict() for item in plan.items],
|
||||
companion_module_ids=list(companion_ids),
|
||||
catalog_validation={
|
||||
"valid": validation.get("valid"),
|
||||
"channel": validation.get("channel"),
|
||||
@@ -825,7 +943,10 @@ def plan_module_install_from_catalog(
|
||||
)
|
||||
session.commit()
|
||||
planned_action = "update" if item.action == "update" else "install"
|
||||
return _module_install_plan_response(session, request, notes=[f"Catalog {planned_action} entry planned for {module_id}."])
|
||||
notes = [f"Catalog {planned_action} entry planned for {module_id}."]
|
||||
if companion_ids:
|
||||
notes.append("Required companion catalog entries added: " + ", ".join(companion_ids))
|
||||
return _module_install_plan_response(session, request, notes=notes)
|
||||
|
||||
|
||||
@router.post("/system/modules/{module_id}/uninstall-plan", response_model=ModuleInstallPlanResponse)
|
||||
|
||||
@@ -3,61 +3,10 @@ from __future__ import annotations
|
||||
from datetime import datetime
|
||||
from typing import Any, Literal
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from govoplan_core.api.v1.schemas import DeltaDeletedItem
|
||||
|
||||
|
||||
RETENTION_DAY_KEYS = (
|
||||
"raw_campaign_json_retention_days",
|
||||
"generated_eml_retention_days",
|
||||
"stored_report_detail_retention_days",
|
||||
"mock_mailbox_retention_days",
|
||||
"audit_detail_retention_days",
|
||||
)
|
||||
|
||||
|
||||
RETENTION_POLICY_FIELD_KEYS = (
|
||||
"store_raw_campaign_json",
|
||||
*RETENTION_DAY_KEYS,
|
||||
"audit_detail_level",
|
||||
)
|
||||
|
||||
|
||||
def default_allow_lower_level_limits() -> dict[str, bool]:
|
||||
return {key: True for key in RETENTION_POLICY_FIELD_KEYS}
|
||||
|
||||
|
||||
def normalize_allow_lower_level_limits(value: Any, *, fill_defaults: bool) -> dict[str, bool] | None:
|
||||
if value in (None, ""):
|
||||
return default_allow_lower_level_limits() if fill_defaults else None
|
||||
if not isinstance(value, dict):
|
||||
raise ValueError("allow_lower_level_limits must be an object")
|
||||
normalized = default_allow_lower_level_limits() if fill_defaults else {}
|
||||
for key, allowed in value.items():
|
||||
clean_key = str(key)
|
||||
if clean_key not in RETENTION_POLICY_FIELD_KEYS:
|
||||
raise ValueError(f"Unknown retention policy field: {clean_key}")
|
||||
normalized[clean_key] = bool(allowed)
|
||||
return normalized
|
||||
|
||||
|
||||
class PrivacyRetentionPolicyItem(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
store_raw_campaign_json: bool = True
|
||||
raw_campaign_json_retention_days: int | None = Field(default=None, ge=0)
|
||||
generated_eml_retention_days: int | None = Field(default=None, ge=0)
|
||||
stored_report_detail_retention_days: int | None = Field(default=None, ge=0)
|
||||
mock_mailbox_retention_days: int | None = Field(default=None, ge=0)
|
||||
audit_detail_retention_days: int | None = Field(default=None, ge=0)
|
||||
audit_detail_level: Literal["full", "redacted", "minimal"] = "full"
|
||||
allow_lower_level_limits: dict[str, bool] = Field(default_factory=default_allow_lower_level_limits)
|
||||
|
||||
@field_validator("allow_lower_level_limits", mode="before")
|
||||
@classmethod
|
||||
def _normalize_allow_lower_level_limits(cls, value: Any) -> Any:
|
||||
return normalize_allow_lower_level_limits(value, fill_defaults=True)
|
||||
from govoplan_core.privacy.schemas import PrivacyRetentionPolicyItem
|
||||
|
||||
|
||||
class MaintenanceModeItem(BaseModel):
|
||||
@@ -182,6 +131,7 @@ class ModuleInstallPlanItem(BaseModel):
|
||||
python_ref: str | None = Field(default=None, max_length=1000)
|
||||
webui_package: str | None = Field(default=None, max_length=200)
|
||||
webui_ref: str | None = Field(default=None, max_length=1000)
|
||||
data_safety_acknowledged: bool = False
|
||||
destroy_data: bool = False
|
||||
status: Literal["planned", "applied", "blocked"] = "planned"
|
||||
notes: str | None = Field(default=None, max_length=1000)
|
||||
@@ -201,6 +151,63 @@ class ModuleInstallChecklistItem(BaseModel):
|
||||
detail: str | None = None
|
||||
|
||||
|
||||
class ModuleInstallTargetItem(BaseModel):
|
||||
module_id: str
|
||||
action: Literal["install", "update", "uninstall"]
|
||||
source: Literal["manual", "catalog"]
|
||||
current_version: str | None = None
|
||||
target_version: str | None = None
|
||||
python_package: str | None = None
|
||||
python_ref: str | None = None
|
||||
webui_package: str | None = None
|
||||
webui_ref: str | None = None
|
||||
migration_safety: Literal["automatic", "requires_review", "forward_only", "destructive"] = "automatic"
|
||||
migration_notes: str | None = None
|
||||
current_version_min: str | None = None
|
||||
current_version_max_exclusive: str | None = None
|
||||
bridge_release: bool = False
|
||||
bridge_notes: str | None = None
|
||||
allow_downgrade: bool = False
|
||||
allow_same_version: bool = False
|
||||
recovery_tested: bool = False
|
||||
recovery_notes: str | None = None
|
||||
data_safety_acknowledged: bool = False
|
||||
|
||||
|
||||
class ModuleMigrationPlanStep(BaseModel):
|
||||
module_id: str
|
||||
action: Literal["install", "update", "uninstall"]
|
||||
phase: Literal["upgrade", "retirement"]
|
||||
source: Literal["manifest", "catalog", "pending"]
|
||||
has_migration_metadata: bool = False
|
||||
metadata_pending: bool = False
|
||||
migration_safety: Literal["automatic", "requires_review", "forward_only", "destructive"] = "automatic"
|
||||
current_version: str | None = None
|
||||
target_version: str | None = None
|
||||
reason: str | None = None
|
||||
|
||||
|
||||
class ModuleMigrationTaskPlanItem(BaseModel):
|
||||
module_id: str
|
||||
task_id: str
|
||||
phase: Literal["pre_migration_check", "pre_migration_prepare", "post_migration_backfill", "post_migration_verify"]
|
||||
summary: str
|
||||
task_version: str = "1"
|
||||
safety: Literal["automatic", "requires_review", "forward_only", "destructive"] = "automatic"
|
||||
idempotent: bool = True
|
||||
timeout_seconds: int | None = None
|
||||
source: Literal["manifest", "catalog", "pending"] = "manifest"
|
||||
has_executor: bool = False
|
||||
metadata_pending: bool = False
|
||||
|
||||
|
||||
class ModuleMigrationExecutionPlan(BaseModel):
|
||||
enabled_modules: list[str] = Field(default_factory=list)
|
||||
requires_database_migration: bool = False
|
||||
steps: list[ModuleMigrationPlanStep] = Field(default_factory=list)
|
||||
tasks: list[ModuleMigrationTaskPlanItem] = Field(default_factory=list)
|
||||
|
||||
|
||||
class ModuleInstallPreflightResponse(BaseModel):
|
||||
allowed: bool
|
||||
maintenance_mode: bool
|
||||
@@ -210,6 +217,8 @@ class ModuleInstallPreflightResponse(BaseModel):
|
||||
rollback_commands: list[str] = Field(default_factory=list)
|
||||
issues: list[ModuleInstallPreflightIssue] = Field(default_factory=list)
|
||||
checklist: list[ModuleInstallChecklistItem] = Field(default_factory=list)
|
||||
target_plan: list[ModuleInstallTargetItem] = Field(default_factory=list)
|
||||
migration_plan: ModuleMigrationExecutionPlan = Field(default_factory=ModuleMigrationExecutionPlan)
|
||||
|
||||
|
||||
class ModuleInstallPlanResponse(BaseModel):
|
||||
@@ -328,6 +337,20 @@ class ModulePackageCatalogItem(BaseModel):
|
||||
description: str | None = None
|
||||
version: str | None = None
|
||||
action: Literal["install", "update", "uninstall"] = "install"
|
||||
dependencies: list[str] = Field(default_factory=list)
|
||||
optional_dependencies: list[str] = Field(default_factory=list)
|
||||
migration_safety: Literal["automatic", "requires_review", "forward_only", "destructive"] = "automatic"
|
||||
migration_notes: str | None = None
|
||||
migration_after: list[str] = Field(default_factory=list)
|
||||
migration_before: list[str] = Field(default_factory=list)
|
||||
current_version_min: str | None = None
|
||||
current_version_max_exclusive: str | None = None
|
||||
bridge_release: bool = False
|
||||
bridge_notes: str | None = None
|
||||
allow_downgrade: bool = False
|
||||
allow_same_version: bool = False
|
||||
recovery_tested: bool = False
|
||||
recovery_notes: str | None = None
|
||||
python_package: str | None = None
|
||||
python_ref: str | None = None
|
||||
webui_package: str | None = None
|
||||
|
||||
@@ -3,7 +3,8 @@ from __future__ import annotations
|
||||
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, CAPABILITY_AUTH_PRINCIPAL_RESOLVER
|
||||
from govoplan_core.core.module_guards import persistent_table_uninstall_guard
|
||||
from govoplan_core.core.modules import MigrationSpec, ModuleContext, ModuleManifest
|
||||
from govoplan_core.core.modules import FrontendModule, MigrationSpec, ModuleContext, ModuleManifest
|
||||
from govoplan_core.core.views import ViewSurface
|
||||
from govoplan_core.db.base import Base
|
||||
|
||||
|
||||
@@ -17,9 +18,22 @@ def _route_factory(context: ModuleContext):
|
||||
manifest = ModuleManifest(
|
||||
id="admin",
|
||||
name="Admin",
|
||||
version="0.1.6",
|
||||
version="0.1.8",
|
||||
required_capabilities=(CAPABILITY_AUTH_PRINCIPAL_RESOLVER, CAPABILITY_AUTH_PERMISSION_EVALUATOR),
|
||||
route_factory=_route_factory,
|
||||
frontend=FrontendModule(
|
||||
module_id="admin",
|
||||
package_name="@govoplan/admin-webui",
|
||||
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-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),
|
||||
ViewSurface(id="admin.section.system-groups", module_id="admin", kind="section", label="Group templates", order=50),
|
||||
ViewSurface(id="admin.section.system-modules", module_id="admin", kind="section", label="Modules", order=85),
|
||||
),
|
||||
),
|
||||
migration_spec=MigrationSpec(module_id="admin", metadata=Base.metadata),
|
||||
uninstall_guard_providers=(
|
||||
persistent_table_uninstall_guard(
|
||||
|
||||
1
tests/__init__.py
Normal file
1
tests/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""GovOPlaN Admin backend tests."""
|
||||
108
tests/test_catalog_plan.py
Normal file
108
tests/test_catalog_plan.py
Normal file
@@ -0,0 +1,108 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
from fastapi import HTTPException
|
||||
|
||||
from govoplan_admin.backend.api.v1.routes import _catalog_plan_item
|
||||
|
||||
|
||||
class CatalogPlanItemTests(unittest.TestCase):
|
||||
def test_builds_catalog_item_and_preserves_catalog_metadata(self) -> None:
|
||||
validation: dict[str, object] = {
|
||||
"valid": True,
|
||||
"source": "https://catalog.example.test/modules.json",
|
||||
"source_type": "remote",
|
||||
"channel": "stable",
|
||||
"signed": True,
|
||||
"trusted": True,
|
||||
"modules": [
|
||||
"ignored",
|
||||
{"module_id": "other", "action": "install"},
|
||||
{
|
||||
"module_id": "calendar",
|
||||
"action": "install",
|
||||
"python_package": "govoplan-calendar",
|
||||
"python_ref": 42,
|
||||
"webui_package": "@govoplan/calendar-webui",
|
||||
"notes": "Catalog note",
|
||||
"license_features": ["calendar.sync", "", 7],
|
||||
},
|
||||
],
|
||||
}
|
||||
decision = {
|
||||
"allowed": True,
|
||||
"reason": "Feature expires soon.",
|
||||
"missing_features": ["calendar.future"],
|
||||
}
|
||||
|
||||
with patch(
|
||||
"govoplan_admin.backend.api.v1.routes.module_license_decision",
|
||||
return_value=decision,
|
||||
) as license_decision:
|
||||
item, returned_validation = _catalog_plan_item(
|
||||
"calendar",
|
||||
{"calendar"},
|
||||
validation=validation,
|
||||
)
|
||||
|
||||
self.assertIs(returned_validation, validation)
|
||||
self.assertEqual(item.module_id, "calendar")
|
||||
self.assertEqual(item.action, "update")
|
||||
self.assertEqual(item.source, "catalog")
|
||||
self.assertEqual(item.python_package, "govoplan-calendar")
|
||||
self.assertIsNone(item.python_ref)
|
||||
self.assertEqual(item.webui_package, "@govoplan/calendar-webui")
|
||||
self.assertIsNone(item.webui_ref)
|
||||
self.assertEqual(item.notes, "Catalog note\nLicense warning: Feature expires soon.")
|
||||
self.assertEqual(item.catalog["source"], "https://catalog.example.test/modules.json")
|
||||
self.assertEqual(item.catalog["channel"], "stable")
|
||||
self.assertTrue(item.catalog["signed"])
|
||||
license_decision.assert_called_once_with(["calendar.sync", "7"])
|
||||
|
||||
def test_rejects_catalog_action_when_license_is_missing(self) -> None:
|
||||
validation: dict[str, object] = {
|
||||
"valid": True,
|
||||
"modules": [{"module_id": "calendar", "action": "install", "license_features": ["calendar.sync"]}],
|
||||
}
|
||||
with patch(
|
||||
"govoplan_admin.backend.api.v1.routes.module_license_decision",
|
||||
return_value={"allowed": False, "missing_features": ["calendar.sync", "calendar.write"]},
|
||||
), self.assertRaises(HTTPException) as raised:
|
||||
_catalog_plan_item("calendar", set(), validation=validation)
|
||||
|
||||
self.assertEqual(raised.exception.status_code, 403)
|
||||
self.assertEqual(
|
||||
raised.exception.detail,
|
||||
"License does not allow install for calendar: calendar.sync, calendar.write.",
|
||||
)
|
||||
|
||||
def test_rejects_invalid_or_missing_catalog_entries(self) -> None:
|
||||
with self.subTest("invalid catalog"), self.assertRaises(HTTPException) as invalid:
|
||||
_catalog_plan_item(
|
||||
"calendar",
|
||||
set(),
|
||||
validation={"valid": False, "error": "Signature is invalid."},
|
||||
)
|
||||
self.assertEqual(invalid.exception.status_code, 422)
|
||||
self.assertEqual(invalid.exception.detail, "Signature is invalid.")
|
||||
|
||||
with self.subTest("entry not found"), self.assertRaises(HTTPException) as missing:
|
||||
_catalog_plan_item(
|
||||
"calendar",
|
||||
set(),
|
||||
validation={
|
||||
"valid": True,
|
||||
"modules": [
|
||||
{"module_id": "calendar", "action": "remove"},
|
||||
{"module_id": "other", "action": "install"},
|
||||
],
|
||||
},
|
||||
)
|
||||
self.assertEqual(missing.exception.status_code, 404)
|
||||
self.assertEqual(missing.exception.detail, "Catalog install/update entry not found: calendar")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@govoplan/admin-webui",
|
||||
"version": "0.1.6",
|
||||
"version": "0.1.8",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
@@ -13,7 +13,7 @@
|
||||
}
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@govoplan/core-webui": "^0.1.6",
|
||||
"@govoplan/core-webui": "^0.1.9",
|
||||
"lucide-react": "^1.23.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
|
||||
@@ -1,74 +1,16 @@
|
||||
import type { ApiSettings, DeltaDeletedItem } from "@govoplan/core-webui";
|
||||
import { apiFetch } from "@govoplan/core-webui";
|
||||
|
||||
export type PermissionItem = {
|
||||
scope: string;
|
||||
label: string;
|
||||
description: string;
|
||||
category: string;
|
||||
level: "tenant" | "system";
|
||||
system_template_id?: string | null;
|
||||
system_required?: boolean;
|
||||
};
|
||||
|
||||
export type AdminOverview = {
|
||||
active_tenant_id: string;
|
||||
active_tenant_name: string;
|
||||
tenant_count?: number | null;
|
||||
system_account_count?: number | null;
|
||||
system_group_template_count?: number | null;
|
||||
system_role_template_count?: number | null;
|
||||
user_count: number;
|
||||
active_user_count: number;
|
||||
group_count: number;
|
||||
role_count: number;
|
||||
active_api_key_count: number;
|
||||
capabilities: string[];
|
||||
};
|
||||
|
||||
export type TenantAdminItem = {
|
||||
id: string;
|
||||
slug: string;
|
||||
name: string;
|
||||
description?: string | null;
|
||||
default_locale: string;
|
||||
settings: Record<string, unknown>;
|
||||
allow_custom_groups?: boolean | null;
|
||||
allow_custom_roles?: boolean | null;
|
||||
allow_api_keys?: boolean | null;
|
||||
effective_governance: Record<string, boolean>;
|
||||
is_active: boolean;
|
||||
counts: Record<string, number>;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
};
|
||||
|
||||
export type PrivacyRetentionPolicyFieldKey =
|
||||
| "store_raw_campaign_json"
|
||||
| "raw_campaign_json_retention_days"
|
||||
| "generated_eml_retention_days"
|
||||
| "stored_report_detail_retention_days"
|
||||
| "mock_mailbox_retention_days"
|
||||
| "audit_detail_retention_days"
|
||||
| "audit_detail_level";
|
||||
|
||||
export type PrivacyRetentionLimitPermissions = Record<PrivacyRetentionPolicyFieldKey, boolean>;
|
||||
export type PrivacyRetentionLimitPermissionPatch = Partial<PrivacyRetentionLimitPermissions>;
|
||||
|
||||
export type PrivacyRetentionPolicy = {
|
||||
store_raw_campaign_json: boolean;
|
||||
raw_campaign_json_retention_days?: number | null;
|
||||
generated_eml_retention_days?: number | null;
|
||||
stored_report_detail_retention_days?: number | null;
|
||||
mock_mailbox_retention_days?: number | null;
|
||||
audit_detail_retention_days?: number | null;
|
||||
audit_detail_level: "full" | "redacted" | "minimal";
|
||||
allow_lower_level_limits: PrivacyRetentionLimitPermissions;
|
||||
};
|
||||
|
||||
export type PrivacyRetentionPolicyPatch = Partial<Omit<PrivacyRetentionPolicy, "allow_lower_level_limits">> & {
|
||||
allow_lower_level_limits?: PrivacyRetentionLimitPermissionPatch;
|
||||
};
|
||||
import type { ApiSettings, DeltaDeletedItem, PrivacyRetentionPolicy } from "@govoplan/core-webui";
|
||||
import { apiFetch, apiGetList, apiPath, apiQuery } from "@govoplan/core-webui";
|
||||
export { fetchAdminOverview, fetchPermissionCatalog, fetchTenants } from "@govoplan/core-webui";
|
||||
export type {
|
||||
AdminOverview,
|
||||
PrivacyRetentionLimitPermissionPatch,
|
||||
PrivacyRetentionLimitPermissions,
|
||||
PrivacyRetentionPolicy,
|
||||
PrivacyRetentionPolicyFieldKey,
|
||||
PrivacyRetentionPolicyPatch,
|
||||
PermissionItem,
|
||||
TenantAdminItem
|
||||
} from "@govoplan/core-webui";
|
||||
|
||||
export type MaintenanceMode = {
|
||||
enabled: boolean;
|
||||
@@ -192,10 +134,7 @@ type DeltaResponseFields = {
|
||||
};
|
||||
|
||||
function deltaSuffix(options: { since?: string | null; limit?: number } = {}): string {
|
||||
const params = new URLSearchParams();
|
||||
if (options.since) params.set("since", options.since);
|
||||
if (options.limit) params.set("limit", String(options.limit));
|
||||
return params.toString() ? `?${params.toString()}` : "";
|
||||
return apiQuery(options);
|
||||
}
|
||||
|
||||
export type SystemSettingsDeltaResponse = {
|
||||
@@ -249,13 +188,14 @@ export type ModuleCatalogResponse = {
|
||||
|
||||
export type ModuleInstallPlanItem = {
|
||||
module_id: string;
|
||||
action: "install" | "uninstall";
|
||||
action: "install" | "update" | "uninstall";
|
||||
source: "manual" | "catalog";
|
||||
catalog?: Record<string, unknown> | null;
|
||||
python_package?: string | null;
|
||||
python_ref?: string | null;
|
||||
webui_package?: string | null;
|
||||
webui_ref?: string | null;
|
||||
data_safety_acknowledged: boolean;
|
||||
destroy_data: boolean;
|
||||
status: "planned" | "applied" | "blocked";
|
||||
notes?: string | null;
|
||||
@@ -275,6 +215,63 @@ export type ModuleInstallChecklistItem = {
|
||||
detail?: string | null;
|
||||
};
|
||||
|
||||
export type ModuleInstallTargetItem = {
|
||||
module_id: string;
|
||||
action: "install" | "update" | "uninstall";
|
||||
source: "manual" | "catalog";
|
||||
current_version?: string | null;
|
||||
target_version?: string | null;
|
||||
python_package?: string | null;
|
||||
python_ref?: string | null;
|
||||
webui_package?: string | null;
|
||||
webui_ref?: string | null;
|
||||
migration_safety: "automatic" | "requires_review" | "forward_only" | "destructive";
|
||||
migration_notes?: string | null;
|
||||
current_version_min?: string | null;
|
||||
current_version_max_exclusive?: string | null;
|
||||
bridge_release: boolean;
|
||||
bridge_notes?: string | null;
|
||||
allow_downgrade: boolean;
|
||||
allow_same_version: boolean;
|
||||
recovery_tested: boolean;
|
||||
recovery_notes?: string | null;
|
||||
data_safety_acknowledged: boolean;
|
||||
};
|
||||
|
||||
export type ModuleMigrationPlanStep = {
|
||||
module_id: string;
|
||||
action: "install" | "update" | "uninstall";
|
||||
phase: "upgrade" | "retirement";
|
||||
source: "manifest" | "catalog" | "pending";
|
||||
has_migration_metadata: boolean;
|
||||
metadata_pending: boolean;
|
||||
migration_safety: "automatic" | "requires_review" | "forward_only" | "destructive";
|
||||
current_version?: string | null;
|
||||
target_version?: string | null;
|
||||
reason?: string | null;
|
||||
};
|
||||
|
||||
export type ModuleMigrationTaskPlanItem = {
|
||||
module_id: string;
|
||||
task_id: string;
|
||||
phase: "pre_migration_check" | "pre_migration_prepare" | "post_migration_backfill" | "post_migration_verify";
|
||||
summary: string;
|
||||
task_version: string;
|
||||
safety: "automatic" | "requires_review" | "forward_only" | "destructive";
|
||||
idempotent: boolean;
|
||||
timeout_seconds?: number | null;
|
||||
source: "manifest" | "catalog" | "pending";
|
||||
has_executor: boolean;
|
||||
metadata_pending: boolean;
|
||||
};
|
||||
|
||||
export type ModuleMigrationExecutionPlan = {
|
||||
enabled_modules: string[];
|
||||
requires_database_migration: boolean;
|
||||
steps: ModuleMigrationPlanStep[];
|
||||
tasks: ModuleMigrationTaskPlanItem[];
|
||||
};
|
||||
|
||||
export type ModuleInstallPreflight = {
|
||||
allowed: boolean;
|
||||
maintenance_mode: boolean;
|
||||
@@ -284,6 +281,8 @@ export type ModuleInstallPreflight = {
|
||||
rollback_commands: string[];
|
||||
issues: ModuleInstallPreflightIssue[];
|
||||
checklist: ModuleInstallChecklistItem[];
|
||||
target_plan: ModuleInstallTargetItem[];
|
||||
migration_plan: ModuleMigrationExecutionPlan;
|
||||
};
|
||||
|
||||
export type ModuleInstallPlanResponse = {
|
||||
@@ -389,7 +388,21 @@ export type ModulePackageCatalogItem = {
|
||||
name: string;
|
||||
description?: string | null;
|
||||
version?: string | null;
|
||||
action: "install" | "uninstall";
|
||||
action: "install" | "update" | "uninstall";
|
||||
dependencies: string[];
|
||||
optional_dependencies: string[];
|
||||
migration_safety: "automatic" | "requires_review" | "forward_only" | "destructive";
|
||||
migration_notes?: string | null;
|
||||
migration_after: string[];
|
||||
migration_before: string[];
|
||||
current_version_min?: string | null;
|
||||
current_version_max_exclusive?: string | null;
|
||||
bridge_release: boolean;
|
||||
bridge_notes?: string | null;
|
||||
allow_downgrade: boolean;
|
||||
allow_same_version: boolean;
|
||||
recovery_tested: boolean;
|
||||
recovery_notes?: string | null;
|
||||
python_package?: string | null;
|
||||
python_ref?: string | null;
|
||||
webui_package?: string | null;
|
||||
@@ -467,20 +480,6 @@ export type GovernanceTemplateItem = {
|
||||
updated_at: string;
|
||||
};
|
||||
|
||||
export function fetchAdminOverview(settings: ApiSettings): Promise<AdminOverview> {
|
||||
return apiFetch(settings, "/api/v1/admin/overview");
|
||||
}
|
||||
|
||||
export async function fetchPermissionCatalog(settings: ApiSettings): Promise<PermissionItem[]> {
|
||||
const response = await apiFetch<{ permissions: PermissionItem[] }>(settings, "/api/v1/admin/permissions");
|
||||
return response.permissions;
|
||||
}
|
||||
|
||||
export async function fetchTenants(settings: ApiSettings): Promise<TenantAdminItem[]> {
|
||||
const response = await apiFetch<{ tenants: TenantAdminItem[] }>(settings, "/api/v1/admin/tenants");
|
||||
return response.tenants;
|
||||
}
|
||||
|
||||
export function fetchSystemSettings(settings: ApiSettings): Promise<SystemSettingsItem> {
|
||||
return apiFetch(settings, "/api/v1/admin/system/settings");
|
||||
}
|
||||
@@ -510,19 +509,11 @@ export function fetchModuleInstallPlan(settings: ApiSettings): Promise<ModuleIns
|
||||
}
|
||||
|
||||
export function fetchModuleInstallerRuns(settings: ApiSettings, options: { page_size?: number; cursor?: string | null } = {}): Promise<ModuleInstallerRunListResponse> {
|
||||
const params = new URLSearchParams();
|
||||
if (options.page_size) params.set("page_size", String(options.page_size));
|
||||
if (options.cursor) params.set("cursor", options.cursor);
|
||||
const suffix = params.toString() ? `?${params.toString()}` : "";
|
||||
return apiFetch(settings, `/api/v1/admin/system/modules/install-runs${suffix}`);
|
||||
return apiFetch(settings, apiPath("/api/v1/admin/system/modules/install-runs", options));
|
||||
}
|
||||
|
||||
export function fetchModuleInstallerRequests(settings: ApiSettings, options: { page_size?: number; cursor?: string | null } = {}): Promise<ModuleInstallerRequestListResponse> {
|
||||
const params = new URLSearchParams();
|
||||
if (options.page_size) params.set("page_size", String(options.page_size));
|
||||
if (options.cursor) params.set("cursor", options.cursor);
|
||||
const suffix = params.toString() ? `?${params.toString()}` : "";
|
||||
return apiFetch(settings, `/api/v1/admin/system/modules/install-requests${suffix}`);
|
||||
return apiFetch(settings, apiPath("/api/v1/admin/system/modules/install-requests", options));
|
||||
}
|
||||
|
||||
export function createModuleInstallerRequest(settings: ApiSettings, options: ModuleInstallerRequestOptions): Promise<ModuleInstallerRequestItem> {
|
||||
@@ -564,9 +555,7 @@ export function clearModuleInstallPlan(settings: ApiSettings): Promise<ModuleIns
|
||||
}
|
||||
|
||||
export async function fetchGovernanceTemplates(settings: ApiSettings, kind?: "group" | "role"): Promise<GovernanceTemplateItem[]> {
|
||||
const suffix = kind ? `?kind=${encodeURIComponent(kind)}` : "";
|
||||
const response = await apiFetch<{ templates: GovernanceTemplateItem[] }>(settings, `/api/v1/admin/system/governance-templates${suffix}`);
|
||||
return response.templates;
|
||||
return apiGetList<GovernanceTemplateItem, "templates">(settings, "/api/v1/admin/system/governance-templates", "templates", { kind });
|
||||
}
|
||||
|
||||
export function createGovernanceTemplate(settings: ApiSettings, payload: Omit<GovernanceTemplateItem, "id" | "created_at" | "updated_at" | "effective_permission_count"> & { change_request_id?: string | null }): Promise<GovernanceTemplateItem> {
|
||||
@@ -578,8 +567,7 @@ export function updateGovernanceTemplate(settings: ApiSettings, templateId: stri
|
||||
}
|
||||
|
||||
export function deleteGovernanceTemplate(settings: ApiSettings, templateId: string, changeRequestId?: string | null): Promise<void> {
|
||||
const suffix = changeRequestId ? `?change_request_id=${encodeURIComponent(changeRequestId)}` : "";
|
||||
return apiFetch(settings, `/api/v1/admin/system/governance-templates/${templateId}${suffix}`, { method: "DELETE" });
|
||||
return apiFetch(settings, apiPath(`/api/v1/admin/system/governance-templates/${templateId}`, { change_request_id: changeRequestId }), { method: "DELETE" });
|
||||
}
|
||||
|
||||
export function fetchConfigurationChanges(settings: ApiSettings): Promise<{ requests: ConfigurationChangeRequest[]; history: ConfigurationChangeRecord[] }> {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import type { ApiSettings } from "@govoplan/core-webui";
|
||||
import { fetchAdminOverview, type AdminOverview } from "../../api/admin";
|
||||
import { Card } from "@govoplan/core-webui";
|
||||
import { Card, MetricCard } from "@govoplan/core-webui";
|
||||
import { Button } from "@govoplan/core-webui";
|
||||
import { AdminPageLayout, adminErrorMessage } from "@govoplan/core-webui";
|
||||
|
||||
@@ -43,7 +43,7 @@ export default function AdminOverviewPanel({ settings, onSelect, availableSectio
|
||||
{hasAnySection(availableSections, platformSectionIds) && <Card title="ADMINISTRATION">
|
||||
<div className="admin-overview-grid">
|
||||
{availableSections.has("system-modules") && <AreaLink title="i18n:govoplan-admin.modules.04e9462c" text="i18n:govoplan-admin.installed_modules_runtime_state_and_startup_stat.38dd7028" onClick={() => onSelect("system-modules")} />}
|
||||
{availableSections.has("system-configuration-packages") && <AreaLink title="i18n:govoplan-admin.packages.0a999012" text="i18n:govoplan-admin.preflight_approve_apply_and_export_configuration.276bd0f8" onClick={() => onSelect("system-configuration-packages")} />}
|
||||
{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-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")} />}
|
||||
@@ -116,7 +116,7 @@ function hasAnySection(sections: ReadonlySet<string>, candidates: readonly strin
|
||||
}
|
||||
|
||||
function Metric({ title, value, text }: {title: string;value: string | number;text: string;}) {
|
||||
return <Card title={title}><strong className="module-big-number">{value}</strong><p className="muted">{text}</p></Card>;
|
||||
return <MetricCard label={title} value={value} detail={text} />;
|
||||
}
|
||||
|
||||
function AreaLink({ title, text, onClick }: {title: string;text: string;onClick: () => void;}) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Check, RefreshCw } from "lucide-react";
|
||||
import type { ApiSettings } from "@govoplan/core-webui";
|
||||
import { AdminPageLayout, Button, Card, StatusBadge, adminErrorMessage, formatDateTime, i18nMessage, mergeDeltaRows, useDeltaWatermarks } from "@govoplan/core-webui";
|
||||
import { AdminPageLayout, Button, Card, DataGrid, StatusBadge, TableActionGroup, adminErrorMessage, formatDateTime, i18nMessage, mergeDeltaRows, useDeltaWatermarks, type DataGridColumn } from "@govoplan/core-webui";
|
||||
import {
|
||||
approveConfigurationChangeRequest,
|
||||
fetchConfigurationChangesDelta,
|
||||
@@ -72,6 +72,47 @@ export default function ConfigurationChangesPanel({ settings, canApprove }: {set
|
||||
}
|
||||
|
||||
const pending = useMemo(() => requests.filter((item) => item.status !== "applied" && item.status !== "rejected"), [requests]);
|
||||
const requestColumns: DataGridColumn<ConfigurationChangeRequest>[] = [
|
||||
{
|
||||
id: "setting",
|
||||
header: "i18n:govoplan-admin.setting.fb449f71",
|
||||
width: "minmax(220px, 1fr)",
|
||||
minWidth: 180,
|
||||
resizable: true,
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
value: (request) => `${request.label || request.key} ${request.key}`,
|
||||
render: (request) => <div><strong>{request.label || request.key}</strong><span className="muted block">{request.key}</span></div>
|
||||
},
|
||||
{ id: "status", header: "i18n:govoplan-admin.status.bae7d5be", width: 150, sortable: true, filterable: true, value: (request) => request.status, render: (request) => <StatusBadge status={statusTone(request.status)} label={request.status} /> },
|
||||
{ id: "requested", header: "i18n:govoplan-admin.requested.c26bf60f", width: 180, sortable: true, value: (request) => request.requested_at, render: (request) => formatDateTime(request.requested_at) },
|
||||
{ id: "approvals", header: "i18n:govoplan-admin.approvals.deb9d03c", width: 110, sortable: true, value: (request) => request.approvals.length },
|
||||
{ id: "target", header: "i18n:govoplan-admin.target.61ad50a9", width: "minmax(180px, .8fr)", minWidth: 160, resizable: true, filterable: true, value: (request) => targetLabel(request.target), render: (request) => targetLabel(request.target) },
|
||||
{
|
||||
id: "actions",
|
||||
header: "i18n:govoplan-admin.action.97c89a4d",
|
||||
width: 72,
|
||||
sticky: "end",
|
||||
align: "right",
|
||||
render: (request) => <TableActionGroup actions={[{
|
||||
id: "approve",
|
||||
label: "i18n:govoplan-admin.approve.7b2c7f14",
|
||||
icon: <Check size={16} aria-hidden="true" />,
|
||||
applicable: canApprove && request.status === "pending_approval",
|
||||
disabled: busyId === request.id,
|
||||
onClick: () => void approve(request)
|
||||
}]} />
|
||||
}
|
||||
];
|
||||
|
||||
const historyColumns: DataGridColumn<ConfigurationChangeRecord>[] = [
|
||||
{ id: "version", header: "i18n:govoplan-admin.version.2da600bf", width: 100, sortable: true, value: (record) => record.version, render: (record) => `#${record.version}` },
|
||||
{ id: "setting", header: "i18n:govoplan-admin.setting.fb449f71", width: "minmax(220px, 1fr)", minWidth: 180, resizable: true, sortable: true, filterable: true, value: (record) => `${record.key} ${record.id}`, render: (record) => <div><strong>{record.key}</strong><span className="muted block">{record.id}</span></div> },
|
||||
{ id: "status", header: "i18n:govoplan-admin.status.bae7d5be", width: 150, sortable: true, filterable: true, value: (record) => record.status, render: (record) => <StatusBadge status={statusTone(record.status)} label={record.status} /> },
|
||||
{ id: "applied", header: "i18n:govoplan-admin.applied.a3e4a569", width: 180, sortable: true, value: (record) => record.created_at, render: (record) => formatDateTime(record.created_at) },
|
||||
{ id: "approvers", header: "i18n:govoplan-admin.approvers.0e2de1fb", width: 120, sortable: true, value: (record) => record.approval_user_ids.length, render: (record) => record.approval_user_ids.length || "-" },
|
||||
{ id: "target", header: "i18n:govoplan-admin.target.61ad50a9", width: "minmax(180px, .8fr)", minWidth: 160, resizable: true, filterable: true, value: (record) => targetLabel(record.target), render: (record) => targetLabel(record.target) }
|
||||
];
|
||||
|
||||
return (
|
||||
<AdminPageLayout
|
||||
@@ -83,67 +124,23 @@ export default function ConfigurationChangesPanel({ settings, canApprove }: {set
|
||||
actions={<Button onClick={() => void load()} disabled={loading}><RefreshCw size={16} /> i18n:govoplan-admin.reload.cce71553</Button>}>
|
||||
|
||||
<Card title="i18n:govoplan-admin.requests.f7194e6a">
|
||||
<div className="admin-table-wrap">
|
||||
<table className="admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>i18n:govoplan-admin.setting.fb449f71</th>
|
||||
<th>i18n:govoplan-admin.status.bae7d5be</th>
|
||||
<th>i18n:govoplan-admin.requested.c26bf60f</th>
|
||||
<th>i18n:govoplan-admin.approvals.deb9d03c</th>
|
||||
<th>i18n:govoplan-admin.target.61ad50a9</th>
|
||||
<th className="actions">i18n:govoplan-admin.action.97c89a4d</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{pending.map((request) =>
|
||||
<tr key={request.id}>
|
||||
<td><strong>{request.label || request.key}</strong><span className="muted block">{request.key}</span></td>
|
||||
<td><StatusBadge status={statusTone(request.status)} label={request.status} /></td>
|
||||
<td>{formatDateTime(request.requested_at)}</td>
|
||||
<td>{request.approvals.length}</td>
|
||||
<td>{targetLabel(request.target)}</td>
|
||||
<td className="actions">
|
||||
{canApprove && request.status === "pending_approval" &&
|
||||
<Button onClick={() => void approve(request)} disabled={busyId === request.id}><Check size={16} /> i18n:govoplan-admin.approve.7b2c7f14</Button>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
{!pending.length && <tr><td colSpan={6} className="muted">i18n:govoplan-admin.no_open_requests.580c95f9</td></tr>}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<DataGrid
|
||||
id="admin-configuration-change-requests"
|
||||
rows={pending}
|
||||
columns={requestColumns}
|
||||
getRowKey={(request) => request.id}
|
||||
emptyText="i18n:govoplan-admin.no_open_requests.580c95f9"
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<Card title="i18n:govoplan-admin.history.90ccd649">
|
||||
<div className="admin-table-wrap">
|
||||
<table className="admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>i18n:govoplan-admin.version.2da600bf</th>
|
||||
<th>i18n:govoplan-admin.setting.fb449f71</th>
|
||||
<th>i18n:govoplan-admin.status.bae7d5be</th>
|
||||
<th>i18n:govoplan-admin.applied.a3e4a569</th>
|
||||
<th>i18n:govoplan-admin.approvers.0e2de1fb</th>
|
||||
<th>i18n:govoplan-admin.target.61ad50a9</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{history.map((record) =>
|
||||
<tr key={record.id}>
|
||||
<td>#{record.version}</td>
|
||||
<td><strong>{record.key}</strong><span className="muted block">{record.id}</span></td>
|
||||
<td><StatusBadge status={statusTone(record.status)} label={record.status} /></td>
|
||||
<td>{formatDateTime(record.created_at)}</td>
|
||||
<td>{record.approval_user_ids.length || "-"}</td>
|
||||
<td>{targetLabel(record.target)}</td>
|
||||
</tr>
|
||||
)}
|
||||
{!history.length && <tr><td colSpan={6} className="muted">i18n:govoplan-admin.no_applied_configuration_changes.6a3ae4a7</td></tr>}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<DataGrid
|
||||
id="admin-configuration-change-history"
|
||||
rows={history}
|
||||
columns={historyColumns}
|
||||
getRowKey={(record) => record.id}
|
||||
emptyText="i18n:govoplan-admin.no_applied_configuration_changes.6a3ae4a7"
|
||||
/>
|
||||
</Card>
|
||||
</AdminPageLayout>);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { Check, Download, Play, RefreshCw } from "lucide-react";
|
||||
import type { ApiSettings } from "@govoplan/core-webui";
|
||||
import { AdminPageLayout, Button, Card, StatusBadge, adminErrorMessage, i18nMessage } from "@govoplan/core-webui";
|
||||
import { AdminPageLayout, Button, Card, DataGrid, StatusBadge, adminErrorMessage, i18nMessage, type DataGridColumn } from "@govoplan/core-webui";
|
||||
import {
|
||||
applyConfigurationPackage,
|
||||
createConfigurationChangeRequest,
|
||||
@@ -273,89 +273,68 @@ function parseObject(text: string): {value: Record<string, unknown> | null;error
|
||||
|
||||
function PackageDiagnostics({ diagnostics }: {diagnostics: ConfigurationPackageDiagnostic[];}) {
|
||||
if (diagnostics.length === 0) return <p className="muted">i18n:govoplan-admin.no_diagnostics.2b6e2630</p>;
|
||||
const columns: DataGridColumn<ConfigurationPackageDiagnostic>[] = [
|
||||
{ id: "severity", header: "i18n:govoplan-admin.severity.de314fa0", width: 130, sortable: true, filterable: true, value: (item) => item.severity, render: (item) => <StatusBadge status={diagnosticTone(item.severity)} label={item.severity} /> },
|
||||
{ id: "code", header: "i18n:govoplan-admin.code.adac6937", width: 190, sortable: true, filterable: true, value: (item) => item.code, render: (item) => <code>{item.code}</code> },
|
||||
{ id: "owner", header: "i18n:govoplan-admin.owner.89ff3122", width: 150, sortable: true, filterable: true, value: (item) => item.module_id || "-", render: (item) => item.module_id || "-" },
|
||||
{ id: "object", header: "i18n:govoplan-admin.object.2883f191", width: 180, sortable: true, filterable: true, value: (item) => item.object_ref || "-", render: (item) => item.object_ref || "-" },
|
||||
{ id: "message", header: "i18n:govoplan-admin.message.68f4145f", width: "minmax(260px, 1fr)", minWidth: 220, resizable: true, filterable: true, value: (item) => `${item.message} ${item.resolution || ""}`, render: (item) => <div>{item.message}{item.resolution ? <span className="muted block">{item.resolution}</span> : null}</div> }
|
||||
];
|
||||
return (
|
||||
<div className="admin-table-wrap">
|
||||
<table className="admin-table">
|
||||
<thead><tr><th>i18n:govoplan-admin.severity.de314fa0</th><th>i18n:govoplan-admin.code.adac6937</th><th>i18n:govoplan-admin.owner.89ff3122</th><th>i18n:govoplan-admin.object.2883f191</th><th>i18n:govoplan-admin.message.68f4145f</th></tr></thead>
|
||||
<tbody>
|
||||
{diagnostics.map((item, index) =>
|
||||
<tr key={`${item.code}-${index}`}>
|
||||
<td><StatusBadge status={diagnosticTone(item.severity)} label={item.severity} /></td>
|
||||
<td><code>{item.code}</code></td>
|
||||
<td>{item.module_id || "-"}</td>
|
||||
<td>{item.object_ref || "-"}</td>
|
||||
<td>{item.message}{item.resolution ? <span className="muted block">{item.resolution}</span> : null}</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>);
|
||||
<DataGrid
|
||||
id="admin-configuration-package-diagnostics"
|
||||
rows={diagnostics}
|
||||
columns={columns}
|
||||
getRowKey={(item, index) => `${item.code}-${index}`}
|
||||
/>);
|
||||
|
||||
}
|
||||
|
||||
function RequiredData({ items }: {items: ConfigurationPackageRequiredData[];}) {
|
||||
if (items.length === 0) return null;
|
||||
const columns: DataGridColumn<ConfigurationPackageRequiredData>[] = [
|
||||
{ id: "key", header: "i18n:govoplan-admin.key.c67dd20e", width: "minmax(200px, 1fr)", minWidth: 170, resizable: true, sortable: true, filterable: true, value: (item) => item.key, render: (item) => <code>{item.key}</code> },
|
||||
{ id: "label", header: "i18n:govoplan-admin.label.74341e3c", width: "minmax(180px, 1fr)", minWidth: 160, resizable: true, sortable: true, filterable: true, value: (item) => item.label },
|
||||
{ id: "type", header: "i18n:govoplan-admin.type.3deb7456", width: 140, sortable: true, filterable: true, value: (item) => item.data_type },
|
||||
{ id: "required", header: "i18n:govoplan-admin.required.eed6bfb4", width: 110, sortable: true, value: (item) => item.required, render: (item) => item.required ? "yes" : "no" },
|
||||
{ id: "secret", header: "i18n:govoplan-admin.secret.f4e7a874", width: 100, sortable: true, value: (item) => item.secret, render: (item) => item.secret ? "yes" : "no" }
|
||||
];
|
||||
return (
|
||||
<>
|
||||
<h3>i18n:govoplan-admin.required_data.1b1c1b34</h3>
|
||||
<div className="admin-table-wrap">
|
||||
<table className="admin-table">
|
||||
<thead><tr><th>i18n:govoplan-admin.key.c67dd20e</th><th>i18n:govoplan-admin.label.74341e3c</th><th>i18n:govoplan-admin.type.3deb7456</th><th>i18n:govoplan-admin.required.eed6bfb4</th><th>i18n:govoplan-admin.secret.f4e7a874</th></tr></thead>
|
||||
<tbody>
|
||||
{items.map((item) =>
|
||||
<tr key={item.key}>
|
||||
<td><code>{item.key}</code></td>
|
||||
<td>{item.label}</td>
|
||||
<td>{item.data_type}</td>
|
||||
<td>{item.required ? "yes" : "no"}</td>
|
||||
<td>{item.secret ? "yes" : "no"}</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<DataGrid id="admin-configuration-package-required-data" rows={items} columns={columns} getRowKey={(item) => item.key} />
|
||||
</>);
|
||||
|
||||
}
|
||||
|
||||
function PackagePlan({ items }: {items: ConfigurationPackagePlanItem[];}) {
|
||||
if (items.length === 0) return <p className="muted">i18n:govoplan-admin.no_plan_items.7108c582</p>;
|
||||
const columns: DataGridColumn<ConfigurationPackagePlanItem>[] = [
|
||||
{ id: "action", header: "i18n:govoplan-admin.action.97c89a4d", width: 130, sortable: true, filterable: true, value: (item) => item.action, render: (item) => <StatusBadge status={planTone(item.action)} label={item.action} /> },
|
||||
{ id: "module", header: "i18n:govoplan-admin.module.b8ff0289", width: 170, sortable: true, filterable: true, value: (item) => item.module_id },
|
||||
{ id: "fragment", header: "i18n:govoplan-admin.fragment.3f19d616", width: 170, sortable: true, filterable: true, value: (item) => item.fragment_type },
|
||||
{ id: "id", header: "i18n:govoplan-admin.id.474ae526", width: 180, sortable: true, filterable: true, value: (item) => item.fragment_id || "-", render: (item) => item.fragment_id || "-" },
|
||||
{ id: "summary", header: "i18n:govoplan-admin.summary.12b71c3e", width: "minmax(220px, 1fr)", minWidth: 180, resizable: true, filterable: true, value: (item) => item.summary || "-", render: (item) => item.summary || "-" }
|
||||
];
|
||||
return (
|
||||
<>
|
||||
<h3>i18n:govoplan-admin.plan.ae2f98a0</h3>
|
||||
<div className="admin-table-wrap">
|
||||
<table className="admin-table">
|
||||
<thead><tr><th>i18n:govoplan-admin.action.97c89a4d</th><th>i18n:govoplan-admin.module.b8ff0289</th><th>i18n:govoplan-admin.fragment.3f19d616</th><th>i18n:govoplan-admin.id.474ae526</th><th>i18n:govoplan-admin.summary.12b71c3e</th></tr></thead>
|
||||
<tbody>
|
||||
{items.map((item, index) =>
|
||||
<tr key={`${item.module_id}-${item.fragment_type}-${item.fragment_id ?? index}`}>
|
||||
<td><StatusBadge status={planTone(item.action)} label={item.action} /></td>
|
||||
<td>{item.module_id}</td>
|
||||
<td>{item.fragment_type}</td>
|
||||
<td>{item.fragment_id || "-"}</td>
|
||||
<td>{item.summary || "-"}</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<DataGrid id="admin-configuration-package-plan" rows={items} columns={columns} getRowKey={(item, index) => `${item.module_id}-${item.fragment_type}-${item.fragment_id ?? index}`} />
|
||||
</>);
|
||||
|
||||
}
|
||||
|
||||
function ReferenceMap({ title, refs }: {title: string;refs: Record<string, string>;}) {
|
||||
const entries = Object.entries(refs);
|
||||
const entries = Object.entries(refs).map(([key, value]) => ({ key, value }));
|
||||
if (entries.length === 0) return null;
|
||||
const columns: DataGridColumn<{key: string;value: string;}>[] = [
|
||||
{ id: "key", header: "i18n:govoplan-admin.key.c67dd20e", width: "minmax(220px, 1fr)", minWidth: 180, resizable: true, sortable: true, filterable: true, value: (item) => item.key, render: (item) => <code>{item.key}</code> },
|
||||
{ id: "value", header: "Value", width: "minmax(220px, 1fr)", minWidth: 180, resizable: true, sortable: true, filterable: true, value: (item) => item.value }
|
||||
];
|
||||
return (
|
||||
<>
|
||||
<h3>{title}</h3>
|
||||
<div className="admin-table-wrap">
|
||||
<table className="admin-table">
|
||||
<tbody>
|
||||
{entries.map(([key, value]) => <tr key={key}><td><code>{key}</code></td><td>{value}</td></tr>)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<DataGrid id={`admin-configuration-package-refs-${title}`} rows={entries} columns={columns} getRowKey={(item) => item.key} />
|
||||
</>);
|
||||
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
type PermissionItem,
|
||||
type TenantAdminItem } from
|
||||
"../../api/admin";
|
||||
import { AdminIconButton, AdminPageLayout, AdminSelectionList, adminErrorMessage, joinLabels, i18nMessage, useUnsavedDraftGuard } from "@govoplan/core-webui";
|
||||
import { AdminIconButton, AdminPageLayout, AdminSelectionList, TableActionGroup, adminErrorMessage, i18nMessage, useUnsavedDraftGuard } from "@govoplan/core-webui";
|
||||
|
||||
const emptyDraft = {
|
||||
slug: "",
|
||||
@@ -54,6 +54,7 @@ export default function GovernanceTemplatesPanel({
|
||||
const [error, setError] = useState("");
|
||||
const [success, setSuccess] = useState("");
|
||||
const dirty = editing !== null && draftKey(draft) !== savedDraftKey;
|
||||
const permissionsByScope = useMemo(() => new Map(permissions.map((permission) => [permission.scope, permission])), [permissions]);
|
||||
|
||||
useUnsavedDraftGuard({
|
||||
dirty,
|
||||
@@ -186,11 +187,11 @@ export default function GovernanceTemplatesPanel({
|
||||
{ id: "status", header: "i18n:govoplan-admin.status.bae7d5be", width: 120, resizable: false, sortable: true, filterable: true, value: (row) => row.is_active ? "active" : "inactive", render: (row) => <StatusBadge status={row.is_active ? "active" : "inactive"} /> },
|
||||
{
|
||||
id: "actions", header: "i18n:govoplan-admin.actions.c3cd636a", width: 150, sticky: "end", resizable: false, align: "right",
|
||||
render: (row) => <div className="admin-icon-actions">
|
||||
<AdminIconButton label={i18nMessage("i18n:govoplan-admin.inspect_value.9d5d1071", { value0: row.name })} icon={<Search />} onClick={() => setViewing(row)} />
|
||||
<AdminIconButton label={i18nMessage("i18n:govoplan-admin.edit_value.fad75899", { value0: row.name })} icon={<Pencil />} onClick={() => openEdit(row)} disabled={!canWrite} />
|
||||
<AdminIconButton label={i18nMessage("i18n:govoplan-admin.delete_value.4d18989e", { value0: row.name })} icon={<Trash2 />} variant="danger" onClick={() => setDeleting(row)} disabled={!canWrite} />
|
||||
</div>
|
||||
render: (row) => <TableActionGroup actions={[
|
||||
{ id: "inspect", label: i18nMessage("i18n:govoplan-admin.inspect_value.9d5d1071", { value0: row.name }), icon: <Search />, onClick: () => setViewing(row) },
|
||||
{ id: "edit", label: i18nMessage("i18n:govoplan-admin.edit_value.fad75899", { value0: row.name }), icon: <Pencil />, disabled: !canWrite, onClick: () => openEdit(row) },
|
||||
{ id: "delete", label: i18nMessage("i18n:govoplan-admin.delete_value.4d18989e", { value0: row.name }), icon: <Trash2 />, variant: "danger", disabled: !canWrite, onClick: () => setDeleting(row) }
|
||||
]} />
|
||||
}],
|
||||
[canWrite, kind, tenants]);
|
||||
|
||||
@@ -238,7 +239,7 @@ export default function GovernanceTemplatesPanel({
|
||||
</Dialog>
|
||||
|
||||
<Dialog open={Boolean(viewing)} title={viewing?.name || "i18n:govoplan-admin.template_details.d5d75e4d"} onClose={() => setViewing(null)} className="admin-dialog admin-dialog-wide" footer={<Button onClick={() => setViewing(null)}>i18n:govoplan-admin.close.bbfa773e</Button>}>
|
||||
{viewing && <dl className="admin-details-grid"><div><dt>i18n:govoplan-admin.kind.e00ac23f</dt><dd>{viewing.kind}</dd></div><div><dt>i18n:govoplan-admin.slug.094da9b9</dt><dd>{viewing.slug}</dd></div><div><dt>i18n:govoplan-admin.status.bae7d5be</dt><dd>{viewing.is_active ? "i18n:govoplan-admin.active.a733b809" : "i18n:govoplan-admin.inactive.09af574c"}</dd></div><div><dt>i18n:govoplan-admin.tenants.1f7ae776</dt><dd>{viewing.assignments.length || "i18n:govoplan-admin.none.6eef6648"}</dd></div><div><dt>i18n:govoplan-admin.description.55f8ebc8</dt><dd>{viewing.description || "—"}</dd></div><div><dt>i18n:govoplan-admin.permissions.d06d5557</dt><dd>{viewing.permissions.length ? joinLabels(viewing.permissions.map((name) => ({ name }))) : "—"}</dd></div></dl>}
|
||||
{viewing && <dl className="admin-details-grid"><div><dt>i18n:govoplan-admin.kind.e00ac23f</dt><dd>{viewing.kind}</dd></div><div><dt>i18n:govoplan-admin.slug.094da9b9</dt><dd>{viewing.slug}</dd></div><div><dt>i18n:govoplan-admin.status.bae7d5be</dt><dd>{viewing.is_active ? "i18n:govoplan-admin.active.a733b809" : "i18n:govoplan-admin.inactive.09af574c"}</dd></div><div><dt>i18n:govoplan-admin.tenants.1f7ae776</dt><dd>{viewing.assignments.length || "i18n:govoplan-admin.none.6eef6648"}</dd></div><div><dt>i18n:govoplan-admin.description.55f8ebc8</dt><dd>{viewing.description || "—"}</dd></div><div><dt>i18n:govoplan-admin.permissions.d06d5557</dt><dd>{viewing.permissions.length ? <PermissionDetails scopes={viewing.permissions} permissionsByScope={permissionsByScope} /> : "—"}</dd></div></dl>}
|
||||
</Dialog>
|
||||
|
||||
<ConfirmDialog open={Boolean(deleting)} title={kind === "group" ? "i18n:govoplan-admin.delete_group_template.8745d842" : "i18n:govoplan-admin.delete_tenant_role.4efa0813"} message={i18nMessage("i18n:govoplan-admin.delete_value_removal_is_blocked_while_a_material.d4eba73d", { value0: deleting?.name })} confirmLabel={kind === "group" ? "i18n:govoplan-admin.delete_template.399bf72a" : "i18n:govoplan-admin.delete_tenant_role.4efa0813"} tone="danger" busy={busy} onCancel={() => setDeleting(null)} onConfirm={() => void remove()} />
|
||||
@@ -249,3 +250,31 @@ export default function GovernanceTemplatesPanel({
|
||||
function draftKey(draft: typeof emptyDraft): string {
|
||||
return JSON.stringify(draft);
|
||||
}
|
||||
|
||||
function PermissionDetails({ scopes, permissionsByScope }: {scopes: string[];permissionsByScope: ReadonlyMap<string, PermissionItem>;}) {
|
||||
const groups = groupPermissionScopes(scopes, permissionsByScope);
|
||||
return (
|
||||
<div className="admin-permission-details">
|
||||
{groups.map((group) => <section key={group.module}>
|
||||
<strong>{group.module}</strong>
|
||||
<ul>
|
||||
{group.permissions.map((permission) => <li key={permission.scope}>
|
||||
<span>{permission.label}</span>
|
||||
<code>{permission.scope}</code>
|
||||
</li>)}
|
||||
</ul>
|
||||
</section>)}
|
||||
</div>);
|
||||
}
|
||||
|
||||
function groupPermissionScopes(scopes: string[], permissionsByScope: ReadonlyMap<string, PermissionItem>) {
|
||||
const groups = new Map<string, {scope: string;label: string}[]>();
|
||||
for (const scope of [...scopes].sort()) {
|
||||
const moduleId = scope.split(":", 1)[0] || "other";
|
||||
const permission = permissionsByScope.get(scope);
|
||||
const group = groups.get(moduleId) ?? [];
|
||||
group.push({ scope, label: permission?.label || scope });
|
||||
groups.set(moduleId, group);
|
||||
}
|
||||
return [...groups.entries()].map(([module, permissions]) => ({ module, permissions }));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import type { ApiSettings } from "@govoplan/core-webui";
|
||||
import { AdminPageLayout, adminErrorMessage, Button, dispatchPlatformModulesChanged, StatusBadge, ToggleSwitch, i18nMessage, useUnsavedDraftGuard } from "@govoplan/core-webui";
|
||||
import { AdminPageLayout, adminErrorMessage, Button, dispatchPlatformModulesChanged, formatDateTime, MetricCard, StatusBadge, ToggleSwitch, i18nMessage, useUnsavedDraftGuard, type FormatDateTimeOptions } from "@govoplan/core-webui";
|
||||
import {
|
||||
cancelModuleInstallerRequest,
|
||||
clearModuleInstallPlan,
|
||||
@@ -26,6 +26,9 @@ import {
|
||||
type ModuleInstallerRunListResponse,
|
||||
type ModuleInstallPlanItem,
|
||||
type ModuleInstallPlanResponse,
|
||||
type ModuleInstallTargetItem,
|
||||
type ModuleMigrationPlanStep,
|
||||
type ModuleMigrationTaskPlanItem,
|
||||
type ModuleLicenseDiagnostics,
|
||||
type ModulePackageCatalogResponse,
|
||||
type ModulePackageCatalogItem } from
|
||||
@@ -320,11 +323,11 @@ export default function ModuleManagementPanel({ settings, canWrite, canAccessMai
|
||||
|
||||
{catalog && <>
|
||||
<div className="metric-grid module-management-metrics">
|
||||
<ModuleMetric label="i18n:govoplan-admin.installed.7bb4405c" value={catalog.modules.length} detail="i18n:govoplan-admin.discovered_packages.660902de" />
|
||||
<ModuleMetric label="i18n:govoplan-admin.active.a733b809" value={activeCount} detail="i18n:govoplan-admin.running_registry.5274b24b" />
|
||||
<ModuleMetric label="i18n:govoplan-admin.saved.c0ae8f6e" value={desiredCount} detail="i18n:govoplan-admin.startup_state.d1300be6" />
|
||||
<ModuleMetric label="i18n:govoplan-admin.drift.4876f7b9" value={pendingCount} detail={pendingCount ? "i18n:govoplan-admin.runtime_differs.43d1fd78" : "i18n:govoplan-admin.runtime_matches.a84afa48"} tone={pendingCount ? "warning" : "good"} />
|
||||
<ModuleMetric label="i18n:govoplan-admin.maintenance.94de303b" value={maintenanceEnabled ? "i18n:govoplan-admin.on.e0049a66" : "i18n:govoplan-admin.off.e3de5ab0"} detail={canAccessMaintenance ? "i18n:govoplan-admin.bypass_allowed.4e347c27" : "i18n:govoplan-admin.bypass_denied.ab987400"} tone={maintenanceEnabled ? "warning" : "info"} />
|
||||
<MetricCard label="i18n:govoplan-admin.installed.7bb4405c" value={catalog.modules.length} detail="i18n:govoplan-admin.discovered_packages.660902de" tone="info" />
|
||||
<MetricCard label="i18n:govoplan-admin.active.a733b809" value={activeCount} detail="i18n:govoplan-admin.running_registry.5274b24b" tone="info" />
|
||||
<MetricCard label="i18n:govoplan-admin.saved.c0ae8f6e" value={desiredCount} detail="i18n:govoplan-admin.startup_state.d1300be6" tone="info" />
|
||||
<MetricCard label="i18n:govoplan-admin.drift.4876f7b9" value={pendingCount} detail={pendingCount ? "i18n:govoplan-admin.runtime_differs.43d1fd78" : "i18n:govoplan-admin.runtime_matches.a84afa48"} tone={pendingCount ? "warning" : "good"} />
|
||||
<MetricCard label="i18n:govoplan-admin.maintenance.94de303b" value={maintenanceEnabled ? "i18n:govoplan-admin.on.e0049a66" : "i18n:govoplan-admin.off.e3de5ab0"} detail={canAccessMaintenance ? "i18n:govoplan-admin.bypass_allowed.4e347c27" : "i18n:govoplan-admin.bypass_denied.ab987400"} tone={maintenanceEnabled ? "warning" : "info"} />
|
||||
</div>
|
||||
|
||||
{dirty && <div className="module-management-notes">
|
||||
@@ -352,8 +355,8 @@ export default function ModuleManagementPanel({ settings, canWrite, canAccessMai
|
||||
<div className="module-management-meta">
|
||||
<ModuleStatus module={module} desiredEnabled={desiredEnabled} />
|
||||
{module.protected && <StatusBadge status="locked" label="i18n:govoplan-admin.locked.a798882f" />}
|
||||
{module.frontend_package && <span>{module.frontend_package}</span>}
|
||||
{module.migration_module_id && <span>i18n:govoplan-admin.db.e355c23a {module.migration_module_id}</span>}
|
||||
<span>i18n:govoplan-admin.webui_package.19645536 <code>{module.frontend_package ?? "none"}</code></span>
|
||||
<span>i18n:govoplan-admin.db.e355c23a <code>{module.migration_module_id ?? "none"}</code></span>
|
||||
</div>
|
||||
<div className="module-management-details">
|
||||
<span>i18n:govoplan-admin.requires.a4fc9357 {module.dependencies.length ? module.dependencies.join(", ") : "none"}</span>
|
||||
@@ -414,15 +417,25 @@ export default function ModuleManagementPanel({ settings, canWrite, canAccessMai
|
||||
{item.python_package && <span>{item.python_package}</span>}
|
||||
{item.webui_package && <span>{item.webui_package}</span>}
|
||||
{item.tags.length > 0 && <span>{item.tags.join(", ")}</span>}
|
||||
{item.dependencies.length > 0 && <span>i18n:govoplan-admin.dependencies.9f4f78d1 {item.dependencies.join(", ")}</span>}
|
||||
{item.migration_safety !== "automatic" && <span>i18n:govoplan-admin.migration_safety.729bdb3c {migrationSafetyLabel(item.migration_safety)}</span>}
|
||||
{item.migration_after.length > 0 && <span>i18n:govoplan-admin.after.695c89be {item.migration_after.join(", ")}</span>}
|
||||
{item.migration_before.length > 0 && <span>i18n:govoplan-admin.before.a11cf31f {item.migration_before.join(", ")}</span>}
|
||||
{item.bridge_release && <span>i18n:govoplan-admin.bridge_release.176cf241</span>}
|
||||
{(item.current_version_min || item.current_version_max_exclusive) && <span>i18n:govoplan-admin.current_window.73e25c9f {currentWindowLabel(item)}</span>}
|
||||
{item.recovery_tested && <span>i18n:govoplan-admin.recovery_tested.e831e2f1</span>}
|
||||
{item.license_features.length > 0 && <span>i18n:govoplan-admin.license.de13bf1a {item.license_features.join(", ")}</span>}
|
||||
{item.license_missing_features.length > 0 && <span>i18n:govoplan-admin.missing.feb2bbaa {item.license_missing_features.join(", ")}</span>}
|
||||
{item.provides_interfaces.length > 0 && <span>i18n:govoplan-admin.provides.221b70d9 {providedInterfacesLabel(item)}</span>}
|
||||
{item.requires_interfaces.length > 0 && <span>i18n:govoplan-admin.requires.a4fc9357 {requiredInterfacesLabel(item)}</span>}
|
||||
</div>
|
||||
{item.description && <p className="module-package-catalog-description">{item.description}</p>}
|
||||
{item.migration_notes && <p className="module-package-catalog-description">{item.migration_notes}</p>}
|
||||
{item.bridge_notes && <p className="module-package-catalog-description">{item.bridge_notes}</p>}
|
||||
{item.recovery_notes && <p className="module-package-catalog-description">{item.recovery_notes}</p>}
|
||||
{item.license_reason && <p className={`module-package-catalog-description${item.license_allowed ? "" : " alert warning"}`}>{item.license_reason}</p>}
|
||||
</div>
|
||||
<Button onClick={() => void addCatalogItem(item)} disabled={!canWrite || planBusy || !packageCatalog.valid || !item.license_allowed || item.action !== "install"}>i18n:govoplan-admin.plan_install.e82bffe6</Button>
|
||||
<Button onClick={() => void addCatalogItem(item)} disabled={!canWrite || planBusy || !packageCatalog.valid || !item.license_allowed || !["install", "update"].includes(item.action)}>{catalogPlanButtonLabel(item, catalog)}</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>}
|
||||
@@ -450,6 +463,43 @@ export default function ModuleManagementPanel({ settings, canWrite, canAccessMai
|
||||
<span>{installPlan.preflight.restart_required ? "i18n:govoplan-admin.restart_reload_required_after_package_changes.3a99cd63" : "i18n:govoplan-admin.no_package_restart_pending.efe6b8ce"}</span>
|
||||
{installPlan.preflight.frontend_rebuild_required && <span>i18n:govoplan-admin.webui_rebuild_required.010ce49f</span>}
|
||||
</div>
|
||||
{installPlan.preflight.target_plan.length > 0 && <div className="module-install-checklist">
|
||||
<strong>i18n:govoplan-admin.target_plan.524ea0c8</strong>
|
||||
{installPlan.preflight.target_plan.map((target) =>
|
||||
<div key={`${target.module_id}-${target.action}`} className={`module-install-checklist-item status-${targetStatus(target)}`}>
|
||||
<strong>{target.module_id} - {target.action}</strong>
|
||||
<span>{targetVersionLabel(target)}</span>
|
||||
<p>{migrationSafetyLabel(target.migration_safety)}{target.data_safety_acknowledged ? " - i18n:govoplan-admin.acknowledged.d08d7c6d" : ""}</p>
|
||||
{target.bridge_release && <p>i18n:govoplan-admin.bridge_release.176cf241</p>}
|
||||
{target.recovery_tested && <p>i18n:govoplan-admin.recovery_tested.e831e2f1</p>}
|
||||
{target.python_ref && <p>{target.python_ref}</p>}
|
||||
{target.migration_notes && <p>{target.migration_notes}</p>}
|
||||
{target.bridge_notes && <p>{target.bridge_notes}</p>}
|
||||
{target.recovery_notes && <p>{target.recovery_notes}</p>}
|
||||
</div>
|
||||
)}
|
||||
</div>}
|
||||
{(installPlan.preflight.migration_plan.steps.length > 0 || installPlan.preflight.migration_plan.tasks.length > 0) && <div className="module-install-checklist">
|
||||
<strong>i18n:govoplan-admin.migration_plan.f42b9d90</strong>
|
||||
<span>i18n:govoplan-admin.enabled_modules.3c38e9ff {installPlan.preflight.migration_plan.enabled_modules.join(", ")}</span>
|
||||
{installPlan.preflight.migration_plan.steps.map((step, index) =>
|
||||
<div key={`${step.module_id}-${step.phase}-${index}`} className={`module-install-checklist-item status-${migrationStepStatus(step)}`}>
|
||||
<strong>{index + 1}. {step.module_id} - {migrationPhaseLabel(step.phase)}</strong>
|
||||
<span>{migrationSourceLabel(step.source)}{step.metadata_pending ? " - i18n:govoplan-admin.metadata_pending.25190d87" : ""}</span>
|
||||
<p>{migrationSafetyLabel(step.migration_safety)}</p>
|
||||
{step.reason && <p>{step.reason}</p>}
|
||||
</div>
|
||||
)}
|
||||
{installPlan.preflight.migration_plan.tasks.length > 0 && <strong>i18n:govoplan-admin.migration_tasks.a4410d3a</strong>}
|
||||
{installPlan.preflight.migration_plan.tasks.map((task) =>
|
||||
<div key={`${task.module_id}-${task.task_id}-${task.phase}`} className={`module-install-checklist-item status-${migrationTaskStatus(task)}`}>
|
||||
<strong>{task.module_id} - {migrationTaskPhaseLabel(task.phase)}</strong>
|
||||
<span>{task.summary}</span>
|
||||
<p>{migrationSourceLabel(task.source)}{task.metadata_pending ? " - i18n:govoplan-admin.executor_pending.7633225b" : ""}{task.has_executor ? " - i18n:govoplan-admin.executor_available.507de429" : ""}</p>
|
||||
<p>{migrationSafetyLabel(task.safety)} - {task.idempotent ? "i18n:govoplan-admin.idempotent.47a0f2d6" : "i18n:govoplan-admin.not_idempotent.d3014c71"} - i18n:govoplan-admin.task_version.f0f26b92 {task.task_version}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>}
|
||||
{installPlan.preflight.issues.length > 0 && <div className="module-install-preflight-issues">
|
||||
{installPlan.preflight.issues.map((issue, index) =>
|
||||
<div key={`${issue.code}-${issue.module_id ?? "global"}-${index}`} className={`module-install-preflight-issue severity-${issue.severity}`}>
|
||||
@@ -479,10 +529,11 @@ export default function ModuleManagementPanel({ settings, canWrite, canAccessMai
|
||||
{item.catalog?.sequence !== null && item.catalog?.sequence !== undefined && <StatusBadge status="inactive" label={i18nMessage("i18n:govoplan-admin.seq_value.0ae5fa47", { value0: item.catalog.sequence })} />}
|
||||
{item.catalog?.key_id && <StatusBadge status="inactive" label={i18nMessage("i18n:govoplan-admin.key_value.847b914f", { value0: String(item.catalog.key_id) })} />}
|
||||
</div>
|
||||
<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="uninstall">i18n:govoplan-admin.uninstall.a735da1d</option></select></label>
|
||||
<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"} />
|
||||
<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"} />
|
||||
<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>
|
||||
<label className="wide"><span>i18n:govoplan-admin.webui_ref.cbae3559</span><input value={item.webui_ref ?? ""} disabled={!canWrite || planBusy || item.action === "uninstall"} onChange={(event) => updatePlanItem(index, { webui_ref: event.target.value })} placeholder="git+ssh://...#v0.1.4" /></label>
|
||||
@@ -545,10 +596,10 @@ export default function ModuleManagementPanel({ settings, canWrite, canAccessMai
|
||||
<StatusBadge status={statusTone(request.status)} label={request.status} />
|
||||
</div>
|
||||
<div className="module-management-details">
|
||||
<span>i18n:govoplan-admin.created.0c78dab1 {formatDateTime(request.created_at)}</span>
|
||||
<span>i18n:govoplan-admin.started.fe3824e9 {formatDateTime(request.started_at)}</span>
|
||||
<span>i18n:govoplan-admin.finished.4b52fe3f {formatDateTime(request.finished_at)}</span>
|
||||
{request.cancelled_at && <span>i18n:govoplan-admin.cancelled.8c9dcfa8 {formatDateTime(request.cancelled_at)}</span>}
|
||||
<span>i18n:govoplan-admin.created.0c78dab1 {formatDateTime(request.created_at, ADMIN_DATE_TIME_OPTIONS)}</span>
|
||||
<span>i18n:govoplan-admin.started.fe3824e9 {formatDateTime(request.started_at, ADMIN_DATE_TIME_OPTIONS)}</span>
|
||||
<span>i18n:govoplan-admin.finished.4b52fe3f {formatDateTime(request.finished_at, ADMIN_DATE_TIME_OPTIONS)}</span>
|
||||
{request.cancelled_at && <span>i18n:govoplan-admin.cancelled.8c9dcfa8 {formatDateTime(request.cancelled_at, ADMIN_DATE_TIME_OPTIONS)}</span>}
|
||||
{request.retry_of && <span>i18n:govoplan-admin.retry_of.3a6bb304 {request.retry_of}</span>}
|
||||
{traceId(request.trace) && <span>i18n:govoplan-admin.trace.04a75036 <code>{traceId(request.trace)}</code></span>}
|
||||
</div>
|
||||
@@ -588,8 +639,8 @@ export default function ModuleManagementPanel({ settings, canWrite, canAccessMai
|
||||
{run.request_id && <span>i18n:govoplan-admin.request.43c2e7a7 {run.request_id}</span>}
|
||||
{traceId(run.trace) && <span>i18n:govoplan-admin.trace.04a75036 <code>{traceId(run.trace)}</code></span>}
|
||||
<span>i18n:govoplan-admin.commands.f6a34aed {run.commands_count}</span>
|
||||
<span>i18n:govoplan-admin.started.fe3824e9 {formatDateTime(run.started_at)}</span>
|
||||
<span>i18n:govoplan-admin.finished.4b52fe3f {formatDateTime(run.finished_at)}</span>
|
||||
<span>i18n:govoplan-admin.started.fe3824e9 {formatDateTime(run.started_at, ADMIN_DATE_TIME_OPTIONS)}</span>
|
||||
<span>i18n:govoplan-admin.finished.4b52fe3f {formatDateTime(run.finished_at, ADMIN_DATE_TIME_OPTIONS)}</span>
|
||||
</div>
|
||||
{run.error && <p className="module-installer-run-error">{run.error}</p>}
|
||||
</div>
|
||||
@@ -603,10 +654,6 @@ export default function ModuleManagementPanel({ settings, canWrite, canAccessMai
|
||||
|
||||
}
|
||||
|
||||
function ModuleMetric({ label, value, detail, tone = "info" }: {label: string;value: string | number;detail: string;tone?: "info" | "good" | "warning";}) {
|
||||
return <div className={`metric-card metric-${tone}`}><div className="metric-label">{label}</div><div className="metric-value">{value}</div><div className="metric-detail">{detail}</div></div>;
|
||||
}
|
||||
|
||||
function LicenseStatus({ license }: {license: ModuleLicenseDiagnostics;}) {
|
||||
const label = license.license_id ? `${license.license_id}${license.subject ? ` · ${license.subject}` : ""}` : license.configured ? "i18n:govoplan-admin.configured_license.3e73f8f5" : "i18n:govoplan-admin.no_license_configured.693cea1a";
|
||||
return (
|
||||
@@ -618,7 +665,7 @@ function LicenseStatus({ license }: {license: ModuleLicenseDiagnostics;}) {
|
||||
</div>
|
||||
<p className="module-package-catalog-description">{label}</p>
|
||||
<div className="module-management-details">
|
||||
<span>i18n:govoplan-admin.valid.b374b8f9 {formatDateTime(license.valid_from)} to {formatDateTime(license.valid_until)}</span>
|
||||
<span>i18n:govoplan-admin.valid.b374b8f9 {formatDateTime(license.valid_from, ADMIN_DATE_TIME_OPTIONS)} to {formatDateTime(license.valid_until, ADMIN_DATE_TIME_OPTIONS)}</span>
|
||||
<span>i18n:govoplan-admin.features.5df81ffa {license.features.length ? license.features.join(", ") : "none"}</span>
|
||||
{license.required_features.length > 0 && <span>i18n:govoplan-admin.required.d5793988 {license.required_features.join(", ")}</span>}
|
||||
{license.missing_features.length > 0 && <span>i18n:govoplan-admin.missing.feb2bbaa {license.missing_features.join(", ")}</span>}
|
||||
@@ -644,6 +691,65 @@ function requiredInterfacesLabel(item: ModulePackageCatalogItem): string {
|
||||
}).join(", ");
|
||||
}
|
||||
|
||||
function migrationSafetyLabel(value: ModulePackageCatalogItem["migration_safety"] | ModuleInstallTargetItem["migration_safety"]): string {
|
||||
if (value === "requires_review") return "i18n:govoplan-admin.requires_review.8ba0b8c6";
|
||||
if (value === "forward_only") return "i18n:govoplan-admin.forward_only.6c107a46";
|
||||
if (value === "destructive") return "i18n:govoplan-admin.destructive.0051026c";
|
||||
return "i18n:govoplan-admin.automatic.d9a7260f";
|
||||
}
|
||||
|
||||
function targetStatus(target: ModuleInstallTargetItem): "done" | "warning" | "blocked" {
|
||||
if (target.migration_safety === "destructive" && !target.data_safety_acknowledged) return "blocked";
|
||||
if (target.migration_safety === "forward_only" && !target.data_safety_acknowledged) return "blocked";
|
||||
if (target.migration_safety !== "automatic") return "warning";
|
||||
return "done";
|
||||
}
|
||||
|
||||
function targetVersionLabel(target: ModuleInstallTargetItem): string {
|
||||
const current = target.current_version ?? "none";
|
||||
const next = target.action === "uninstall" ? "removed" : target.target_version ?? "unknown";
|
||||
return `${current} -> ${next}`;
|
||||
}
|
||||
|
||||
function migrationPhaseLabel(value: ModuleMigrationPlanStep["phase"]): string {
|
||||
if (value === "retirement") return "i18n:govoplan-admin.retirement.6b680dd2";
|
||||
return "i18n:govoplan-admin.upgrade.12c5007d";
|
||||
}
|
||||
|
||||
function migrationTaskPhaseLabel(value: ModuleMigrationTaskPlanItem["phase"]): string {
|
||||
if (value === "pre_migration_check") return "i18n:govoplan-admin.pre_migration_check.f654164a";
|
||||
if (value === "pre_migration_prepare") return "i18n:govoplan-admin.pre_migration_prepare.d18fdd26";
|
||||
if (value === "post_migration_backfill") return "i18n:govoplan-admin.post_migration_backfill.21043d74";
|
||||
return "i18n:govoplan-admin.post_migration_verify.6af9f28b";
|
||||
}
|
||||
|
||||
function migrationSourceLabel(value: ModuleMigrationPlanStep["source"] | ModuleMigrationTaskPlanItem["source"]): string {
|
||||
if (value === "catalog") return "i18n:govoplan-admin.catalog_metadata.9b96c41a";
|
||||
if (value === "pending") return "i18n:govoplan-admin.pending_metadata.a24cfeb0";
|
||||
return "i18n:govoplan-admin.manifest_metadata.b9ae362a";
|
||||
}
|
||||
|
||||
function migrationStepStatus(step: ModuleMigrationPlanStep): "done" | "pending" | "warning" | "blocked" {
|
||||
if (step.metadata_pending) return "warning";
|
||||
if (step.migration_safety !== "automatic") return "warning";
|
||||
return "pending";
|
||||
}
|
||||
|
||||
function migrationTaskStatus(task: ModuleMigrationTaskPlanItem): "done" | "pending" | "warning" | "blocked" {
|
||||
if (!task.idempotent) return "blocked";
|
||||
if (!task.metadata_pending && !task.has_executor) return "blocked";
|
||||
if (task.safety === "forward_only" || task.safety === "destructive") return "warning";
|
||||
if (task.metadata_pending || task.safety === "requires_review") return "warning";
|
||||
return "pending";
|
||||
}
|
||||
|
||||
function currentWindowLabel(item: Pick<ModulePackageCatalogItem, "current_version_min" | "current_version_max_exclusive">): string {
|
||||
return [
|
||||
item.current_version_min ? `>=${item.current_version_min}` : "",
|
||||
item.current_version_max_exclusive ? `<${item.current_version_max_exclusive}` : "",
|
||||
].filter(Boolean).join(" ") || "any";
|
||||
}
|
||||
|
||||
function ModuleStatus({ module, desiredEnabled }: {module: ModuleCatalogItem;desiredEnabled: boolean;}) {
|
||||
if (module.current_enabled && !desiredEnabled) return <StatusBadge status="warning" label="i18n:govoplan-admin.will_disable.14bb193a" />;
|
||||
if (!module.current_enabled && desiredEnabled) return <StatusBadge status="warning" label="i18n:govoplan-admin.will_enable.5d52d70b" />;
|
||||
@@ -651,6 +757,12 @@ function ModuleStatus({ module, desiredEnabled }: {module: ModuleCatalogItem;des
|
||||
return <StatusBadge status="inactive" label="i18n:govoplan-admin.inactive.09af574c" />;
|
||||
}
|
||||
|
||||
function catalogPlanButtonLabel(item: ModulePackageCatalogItem, catalog: ModuleCatalogResponse | null): string {
|
||||
if (item.action === "update") return "i18n:govoplan-admin.plan_update.86e6857a";
|
||||
const installed = catalog?.modules.some((module) => module.id === item.module_id && module.installed) ?? false;
|
||||
return installed ? "i18n:govoplan-admin.plan_update.86e6857a" : "i18n:govoplan-admin.plan_install.e82bffe6";
|
||||
}
|
||||
|
||||
function sameSet(left: ReadonlySet<string>, right: ReadonlySet<string>) {
|
||||
if (left.size !== right.size) return false;
|
||||
for (const item of left) {
|
||||
@@ -669,6 +781,7 @@ function emptyPlanItem(): ModuleInstallPlanItem {
|
||||
python_ref: "",
|
||||
webui_package: "",
|
||||
webui_ref: "",
|
||||
data_safety_acknowledged: false,
|
||||
destroy_data: false,
|
||||
status: "planned",
|
||||
notes: ""
|
||||
@@ -683,9 +796,10 @@ function normalizePlanItems(items: ModuleInstallPlanItem[]): ModuleInstallPlanIt
|
||||
source: item.source === "catalog" ? "catalog" : "manual",
|
||||
catalog: item.source === "catalog" ? item.catalog ?? null : null,
|
||||
python_package: cleanOptional(item.python_package),
|
||||
python_ref: item.action === "install" ? cleanOptional(item.python_ref) : null,
|
||||
python_ref: item.action !== "uninstall" ? cleanOptional(item.python_ref) : null,
|
||||
webui_package: cleanOptional(item.webui_package),
|
||||
webui_ref: item.action === "install" ? cleanOptional(item.webui_ref) : null,
|
||||
webui_ref: item.action !== "uninstall" ? cleanOptional(item.webui_ref) : null,
|
||||
data_safety_acknowledged: item.action !== "uninstall" && Boolean(item.data_safety_acknowledged),
|
||||
destroy_data: item.action === "uninstall" && Boolean(item.destroy_data),
|
||||
status: item.status,
|
||||
notes: cleanOptional(item.notes)
|
||||
@@ -701,10 +815,10 @@ function cleanOptional(value?: string | null): string | null {
|
||||
function planValidationError(items: ModuleInstallPlanItem[]): string {
|
||||
for (const item of normalizePlanItems(items)) {
|
||||
if (!item.module_id) return "i18n:govoplan-admin.every_plan_item_needs_a_module_id.23a7f206";
|
||||
if (item.action === "install" && !item.python_ref) return i18nMessage("i18n:govoplan-admin.value_needs_a_python_package_reference.950b898e", { value0: item.module_id });
|
||||
if (item.action === "install" && item.python_ref && !item.python_package) return i18nMessage("i18n:govoplan-admin.value_needs_a_python_package_name_for_rollback.6c89ed75", { value0: item.module_id });
|
||||
if (item.action !== "uninstall" && !item.python_ref) return i18nMessage("i18n:govoplan-admin.value_needs_a_python_package_reference.950b898e", { value0: item.module_id });
|
||||
if (item.action !== "uninstall" && item.python_ref && !item.python_package) return i18nMessage("i18n:govoplan-admin.value_needs_a_python_package_name_for_rollback.6c89ed75", { value0: item.module_id });
|
||||
if (item.action === "uninstall" && !item.python_package) return i18nMessage("i18n:govoplan-admin.value_needs_a_python_package_name_for_uninstall.004ba6fa", { value0: item.module_id });
|
||||
if (item.action === "install" && Boolean(item.webui_package) !== Boolean(item.webui_ref)) return i18nMessage("i18n:govoplan-admin.value_needs_both_webui_package_and_webui_referen.d2bab073", { value0: item.module_id });
|
||||
if (item.action !== "uninstall" && Boolean(item.webui_package) !== Boolean(item.webui_ref)) return i18nMessage("i18n:govoplan-admin.value_needs_both_webui_package_and_webui_referen.d2bab073", { value0: item.module_id });
|
||||
if (item.action === "uninstall" && item.webui_ref && !item.webui_package) return i18nMessage("i18n:govoplan-admin.value_has_a_webui_reference_but_no_webui_package.18f44149", { value0: item.module_id });
|
||||
}
|
||||
return "";
|
||||
@@ -733,12 +847,16 @@ function licenseLabel(license: ModuleLicenseDiagnostics): string {
|
||||
return "i18n:govoplan-admin.unsigned.e91344ea";
|
||||
}
|
||||
|
||||
function formatDateTime(value?: string | null): string {
|
||||
if (!value) return "i18n:govoplan-admin.not_recorded.9925ee3c";
|
||||
const date = new Date(value);
|
||||
if (Number.isNaN(date.getTime())) return value;
|
||||
return date.toLocaleString();
|
||||
}
|
||||
const ADMIN_DATE_TIME_OPTIONS: FormatDateTimeOptions = {
|
||||
fallback: "i18n:govoplan-admin.not_recorded.9925ee3c",
|
||||
year: "numeric",
|
||||
month: "numeric",
|
||||
day: "numeric",
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
second: "numeric",
|
||||
timeZoneName: undefined
|
||||
};
|
||||
|
||||
function traceId(value?: Record<string, unknown> | null): string {
|
||||
const correlationId = value?.correlation_id;
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Card } from "@govoplan/core-webui";
|
||||
import { FormField } from "@govoplan/core-webui";
|
||||
import { ToggleSwitch } from "@govoplan/core-webui";
|
||||
import { fetchSystemSettingsDelta, updateSystemSettings, type LanguagePackage, type PrivacyRetentionLimitPermissions, type PrivacyRetentionPolicy, type SystemSettingsDeltaSections, type SystemSettingsItem } from "../../api/admin";
|
||||
import { AdminPageLayout, adminErrorMessage, useDeltaWatermarks, useUnsavedDraftGuard } from "@govoplan/core-webui";
|
||||
import { AdminPageLayout, AdminSelectionList, adminErrorMessage, useDeltaWatermarks, useUnsavedDraftGuard } from "@govoplan/core-webui";
|
||||
|
||||
const DELTA_KEY = "admin:system-settings";
|
||||
|
||||
@@ -112,10 +112,8 @@ export default function SystemSettingsPanel({ settings, canWrite, canAccessMaint
|
||||
{setBusy(false);}
|
||||
}
|
||||
|
||||
function toggleLanguage(code: string, checked: boolean) {
|
||||
const enabled = new Set(draft.enabled_language_codes);
|
||||
if (checked) enabled.add(code);
|
||||
else enabled.delete(code);
|
||||
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 });
|
||||
@@ -159,18 +157,11 @@ export default function SystemSettingsPanel({ settings, canWrite, canAccessMaint
|
||||
</FormField>
|
||||
</Card>
|
||||
<Card title="i18n:govoplan-admin.language_packages">
|
||||
<div className="settings-list">
|
||||
{draft.available_languages.map((item) => (
|
||||
<label className="admin-inline-check" key={item.code}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={draft.enabled_language_codes.includes(item.code)}
|
||||
disabled={!canWrite || busy || item.code === draft.default_locale}
|
||||
onChange={(event) => toggleLanguage(item.code, event.target.checked)} />
|
||||
<span><strong>{item.code.toUpperCase()}</strong> {languageOptionLabel(item)}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
<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}
|
||||
/>
|
||||
<div className="form-grid">
|
||||
<FormField label="i18n:govoplan-admin.language_code">
|
||||
<input value={packageDraft.code} disabled={!canWrite || busy} placeholder="fr" onChange={(event) => setPackageDraft({ ...packageDraft, code: event.target.value })} />
|
||||
|
||||
@@ -6,13 +6,15 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-admin.action.97c89a4d": "Action",
|
||||
"i18n:govoplan-admin.actions.c3cd636a": "Actions",
|
||||
"i18n:govoplan-admin.activate_installs.731d22a1": "Activate installs",
|
||||
"i18n:govoplan-admin.acknowledged.d08d7c6d": "Acknowledged",
|
||||
"i18n:govoplan-admin.active_and_total_memberships.c0c20f10": "Active and total memberships.",
|
||||
"i18n:govoplan-admin.active_tenant_automation_credentials.240c659e": "Active tenant automation credentials.",
|
||||
"i18n:govoplan-admin.active_tenant.dfadf0aa": "Active tenant:",
|
||||
"i18n:govoplan-admin.active.a733b809": "Active",
|
||||
"i18n:govoplan-admin.after.695c89be": "After:",
|
||||
"i18n:govoplan-admin.add_group_template.b74d8f0f": "Add group template",
|
||||
"i18n:govoplan-admin.add_plan_item.fa55f028": "Add plan item",
|
||||
"i18n:govoplan-admin.add_tenant_role.fcd904ea": "Add tenant role",
|
||||
"i18n:govoplan-admin.add_tenant_role.fcd904ea": "Add role template",
|
||||
"i18n:govoplan-admin.admin.4e7afebc": "Admin",
|
||||
"i18n:govoplan-admin.administration.b8be3d12": "Administration",
|
||||
"i18n:govoplan-admin.platform_administration": "Platform administration",
|
||||
@@ -34,9 +36,12 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-admin.audit.fa1703dd": "Audit",
|
||||
"i18n:govoplan-admin.available.7c62a142": "Available",
|
||||
"i18n:govoplan-admin.blocked.99613c74": "Blocked",
|
||||
"i18n:govoplan-admin.before.a11cf31f": "Before:",
|
||||
"i18n:govoplan-admin.bridge_release.176cf241": "Bridge release",
|
||||
"i18n:govoplan-admin.build_webui.ed5ef1fd": " --build-webui",
|
||||
"i18n:govoplan-admin.build_webui.fe8ccad7": "Build WebUI",
|
||||
"i18n:govoplan-admin.bypass_allowed.4e347c27": "Bypass allowed",
|
||||
"i18n:govoplan-admin.catalog_metadata.9b96c41a": "Catalog metadata",
|
||||
"i18n:govoplan-admin.bypass_denied.ab987400": "Bypass denied",
|
||||
"i18n:govoplan-admin.cached_from.d73c5b2a": "· cached from",
|
||||
"i18n:govoplan-admin.cancel.77dfd213": "Cancel",
|
||||
@@ -70,10 +75,13 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-admin.create_group_template.72407248": "Create group template",
|
||||
"i18n:govoplan-admin.create_request_and_apply.da79e153": "Create request and apply",
|
||||
"i18n:govoplan-admin.create_suspend_and_govern_tenant_spaces.77992a39": "Create, suspend and govern tenant spaces.",
|
||||
"i18n:govoplan-admin.create_tenant_role.f58db104": "Create tenant role",
|
||||
"i18n:govoplan-admin.create_tenant_role.f58db104": "Create role template",
|
||||
"i18n:govoplan-admin.created.0c78dab1": "Created:",
|
||||
"i18n:govoplan-admin.created.accf40c8": "Created",
|
||||
"i18n:govoplan-admin.current_window.73e25c9f": "Current window:",
|
||||
"i18n:govoplan-admin.automatic.d9a7260f": "Automatic",
|
||||
"i18n:govoplan-admin.daemon_execution.cc0fad8d": "Daemon execution",
|
||||
"i18n:govoplan-admin.data_safety_reviewed.b4774edc": "Data safety reviewed",
|
||||
"i18n:govoplan-admin.daemon_offline.314f19a4": "Daemon offline",
|
||||
"i18n:govoplan-admin.daemon_value.e54f5a31": "Daemon: {value0}",
|
||||
"i18n:govoplan-admin.db_backup_command.5bded3ac": "DB backup command",
|
||||
@@ -92,11 +100,13 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-admin.defaults_for_newly_created_tenants.9d0f4ccd": "Defaults for newly created tenants",
|
||||
"i18n:govoplan-admin.delete_group_template.8745d842": "Delete group template",
|
||||
"i18n:govoplan-admin.delete_template.399bf72a": "Delete template",
|
||||
"i18n:govoplan-admin.delete_tenant_role.4efa0813": "Delete tenant role",
|
||||
"i18n:govoplan-admin.delete_tenant_role.4efa0813": "Delete role template",
|
||||
"i18n:govoplan-admin.delete_value_removal_is_blocked_while_a_material.d4eba73d": "Delete {value0}? Removal is blocked while a materialized tenant definition still has members or assignments.",
|
||||
"i18n:govoplan-admin.delete_value.4d18989e": "Delete {value0}",
|
||||
"i18n:govoplan-admin.dependencies.9f4f78d1": "Dependencies:",
|
||||
"i18n:govoplan-admin.dependents.072665c4": "Dependents:",
|
||||
"i18n:govoplan-admin.description.55f8ebc8": "Description",
|
||||
"i18n:govoplan-admin.destructive.0051026c": "Destructive",
|
||||
"i18n:govoplan-admin.destroy_data.34557c3c": "Destroy data",
|
||||
"i18n:govoplan-admin.disabled.f4f4473d": "Disabled",
|
||||
"i18n:govoplan-admin.discovered_packages.660902de": "Discovered packages",
|
||||
@@ -105,9 +115,12 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-admin.dry_run_change_request_created_value_enable_main.49826d07": "Dry-run change request created: {value0}. Enable maintenance mode, then apply the request.",
|
||||
"i18n:govoplan-admin.dry_run.3d14659c": "Dry-run",
|
||||
"i18n:govoplan-admin.edit_group_template.9bc72d21": "Edit group template",
|
||||
"i18n:govoplan-admin.edit_tenant_role.c15a260d": "Edit tenant role",
|
||||
"i18n:govoplan-admin.edit_tenant_role.c15a260d": "Edit role template",
|
||||
"i18n:govoplan-admin.edit_value.fad75899": "Edit {value0}",
|
||||
"i18n:govoplan-admin.enable_maintenance_mode.75f98d57": "Enable maintenance mode",
|
||||
"i18n:govoplan-admin.enabled_modules.3c38e9ff": "Enabled modules:",
|
||||
"i18n:govoplan-admin.executor_available.507de429": "Executor available",
|
||||
"i18n:govoplan-admin.executor_pending.7633225b": "Executor pending",
|
||||
"i18n:govoplan-admin.enabled.df174a3f": "Enabled",
|
||||
"i18n:govoplan-admin.enabling_maintenance_mode_requires_system_mainte.062d3968": "Enabling maintenance mode requires system:maintenance:access.",
|
||||
"i18n:govoplan-admin.enabling.2b8e03e6": "Enabling...",
|
||||
@@ -121,6 +134,7 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-admin.exported_tenant_access_configuration.9122c85c": "Exported tenant access configuration",
|
||||
"i18n:govoplan-admin.features.5df81ffa": "Features:",
|
||||
"i18n:govoplan-admin.file_connections.1e362326": "File connections",
|
||||
"i18n:govoplan-admin.forward_only.6c107a46": "Forward-only",
|
||||
"i18n:govoplan-admin.finished.4b52fe3f": "Finished:",
|
||||
"i18n:govoplan-admin.fragment.3f19d616": "Fragment",
|
||||
"i18n:govoplan-admin.front_office.d9dcfee1": "Front office",
|
||||
@@ -142,6 +156,7 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-admin.history.90ccd649": "History",
|
||||
"i18n:govoplan-admin.id.474ae526": "Id",
|
||||
"i18n:govoplan-admin.import_preflight_approve_apply_and_export_module.29aec929": "Import, preflight, approve, apply and export module-owned configuration packages.",
|
||||
"i18n:govoplan-admin.idempotent.47a0f2d6": "Idempotent",
|
||||
"i18n:govoplan-admin.inactive.09af574c": "Inactive",
|
||||
"i18n:govoplan-admin.inspect_value.9d5d1071": "Inspect {value0}",
|
||||
"i18n:govoplan-admin.install.fd6c3ebf": "Install",
|
||||
@@ -169,6 +184,7 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-admin.license.de13bf1a": "License:",
|
||||
"i18n:govoplan-admin.locked.a798882f": "Locked",
|
||||
"i18n:govoplan-admin.mail_servers.d627326a": "Mail servers",
|
||||
"i18n:govoplan-admin.manifest_metadata.b9ae362a": "Manifest metadata",
|
||||
"i18n:govoplan-admin.maintenance_message.ca62571f": "Maintenance message",
|
||||
"i18n:govoplan-admin.maintenance_mode_enabled_apply_the_module_state_.dda13e5c": "Maintenance mode enabled. Apply the module-state request when ready.",
|
||||
"i18n:govoplan-admin.maintenance_mode_is_off_package_changes_should_b.1d262629": "Maintenance mode is off. Package changes should be applied only after enabling maintenance mode.",
|
||||
@@ -177,6 +193,10 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-admin.maintenance.94de303b": "Maintenance",
|
||||
"i18n:govoplan-admin.membership_status_groups_and_direct_roles_in_the.ab6c10b6": "Membership status, groups and direct roles in the active tenant.",
|
||||
"i18n:govoplan-admin.message.68f4145f": "Message",
|
||||
"i18n:govoplan-admin.metadata_pending.25190d87": "Metadata pending",
|
||||
"i18n:govoplan-admin.migration_plan.f42b9d90": "Migration plan",
|
||||
"i18n:govoplan-admin.migration_safety.729bdb3c": "Migration safety:",
|
||||
"i18n:govoplan-admin.migration_tasks.a4410d3a": "Migration tasks",
|
||||
"i18n:govoplan-admin.minimal_office_access.af48f49a": "Minimal office access",
|
||||
"i18n:govoplan-admin.missing_entitlement.0cafa611": "Missing entitlement",
|
||||
"i18n:govoplan-admin.missing.feb2bbaa": "Missing:",
|
||||
@@ -204,6 +224,7 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-admin.none.6eef6648": "None",
|
||||
"i18n:govoplan-admin.not_available.d1a17af1": "Not available",
|
||||
"i18n:govoplan-admin.not_configured.811931bb": "Not configured",
|
||||
"i18n:govoplan-admin.not_idempotent.d3014c71": "Not idempotent",
|
||||
"i18n:govoplan-admin.not_recorded.9925ee3c": "not recorded",
|
||||
"i18n:govoplan-admin.notes.70440046": "Notes",
|
||||
"i18n:govoplan-admin.object.2883f191": "Object",
|
||||
@@ -223,8 +244,14 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-admin.package.7431e3df": "Package",
|
||||
"i18n:govoplan-admin.packages.0a999012": "Packages",
|
||||
"i18n:govoplan-admin.pending.c515ec74": " pending",
|
||||
"i18n:govoplan-admin.pending_metadata.a24cfeb0": "Pending metadata",
|
||||
"i18n:govoplan-admin.permissions.d06d5557": "Permissions",
|
||||
"i18n:govoplan-admin.plan_install.e82bffe6": "Plan install",
|
||||
"i18n:govoplan-admin.plan_update.86e6857a": "Plan update",
|
||||
"i18n:govoplan-admin.post_migration_backfill.21043d74": "Post-migration backfill",
|
||||
"i18n:govoplan-admin.post_migration_verify.6af9f28b": "Post-migration verify",
|
||||
"i18n:govoplan-admin.pre_migration_check.f654164a": "Pre-migration check",
|
||||
"i18n:govoplan-admin.pre_migration_prepare.d18fdd26": "Pre-migration prepare",
|
||||
"i18n:govoplan-admin.plan_package_installs_and_removals_here_then_app.4aeb03bf": "Plan package installs and removals here, then apply the rendered commands from an operator shell during maintenance mode.",
|
||||
"i18n:govoplan-admin.plan_uninstall.e62804ab": "Plan uninstall",
|
||||
"i18n:govoplan-admin.plan.ae2f98a0": "Plan",
|
||||
@@ -241,6 +268,7 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-admin.queued_daemon_handoffs_created_from_the_admin_ui.d7ae7914": "Queued daemon handoffs created from the admin UI or CLI.",
|
||||
"i18n:govoplan-admin.queueing_installer_requests_requires_maintenance.ae81f9ac": "Queueing installer requests requires maintenance access.",
|
||||
"i18n:govoplan-admin.recent_supervised_installer_records_and_the_curr.29860245": "Recent supervised installer records and the current package-install lock.",
|
||||
"i18n:govoplan-admin.recovery_tested.e831e2f1": "Recovery tested",
|
||||
"i18n:govoplan-admin.registered_tenant_spaces.61e17d70": "Registered tenant spaces.",
|
||||
"i18n:govoplan-admin.reload.cce71553": "Reload",
|
||||
"i18n:govoplan-admin.remove.e963907d": "Remove",
|
||||
@@ -253,10 +281,12 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-admin.required.d5793988": "Required:",
|
||||
"i18n:govoplan-admin.required.eed6bfb4": "Required",
|
||||
"i18n:govoplan-admin.requires.a4fc9357": "Requires:",
|
||||
"i18n:govoplan-admin.requires_review.8ba0b8c6": "Review required",
|
||||
"i18n:govoplan-admin.restart_commands.035997f9": "Restart commands",
|
||||
"i18n:govoplan-admin.restart_reload_required_after_package_changes.3a99cd63": "Restart/reload required after package changes",
|
||||
"i18n:govoplan-admin.restrict_authenticated_api_access_to_maintenance.2bc47195": "Restrict authenticated API access to maintenance operators",
|
||||
"i18n:govoplan-admin.retention.c7199d9e": "Retention",
|
||||
"i18n:govoplan-admin.retirement.6b680dd2": "Retirement",
|
||||
"i18n:govoplan-admin.retry_of.3a6bb304": "Retry of:",
|
||||
"i18n:govoplan-admin.retry.9f5cd8a2": "Retry",
|
||||
"i18n:govoplan-admin.reusable_encrypted_smtp_imap_profiles_and_mail_p.31f150d1": "Reusable encrypted SMTP/IMAP profiles and mail policy.",
|
||||
@@ -272,7 +302,7 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-admin.save_plan.842dc280": "Save plan",
|
||||
"i18n:govoplan-admin.save_settings.913aba9f": "Save settings",
|
||||
"i18n:govoplan-admin.save_template.0885fab2": "Save template",
|
||||
"i18n:govoplan-admin.save_tenant_role.8fc0d37d": "Save tenant role",
|
||||
"i18n:govoplan-admin.save_tenant_role.8fc0d37d": "Save role template",
|
||||
"i18n:govoplan-admin.save_the_install_plan_before_queueing_a_daemon_r.3ba00691": "Save the install plan before queueing a daemon request.",
|
||||
"i18n:govoplan-admin.saved.c0ae8f6e": "Saved",
|
||||
"i18n:govoplan-admin.saving.56a2285c": "Saving…",
|
||||
@@ -302,6 +332,8 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-admin.system_settings_saved.dac4f17b": "System settings saved.",
|
||||
"i18n:govoplan-admin.system_wide_governance_and_tenant_local_access_m.cda72499": "System-wide governance and tenant-local access management, separated by scope and enforced by the backend.",
|
||||
"i18n:govoplan-admin.system.bc0792d8": "System",
|
||||
"i18n:govoplan-admin.target_plan.524ea0c8": "Target plan",
|
||||
"i18n:govoplan-admin.task_version.f0f26b92": "Task version",
|
||||
"i18n:govoplan-admin.target.61ad50a9": "Target",
|
||||
"i18n:govoplan-admin.template_details.d5d75e4d": "Template details",
|
||||
"i18n:govoplan-admin.tenant_administration_capabilities.5d265972": "Tenant administration capabilities",
|
||||
@@ -315,9 +347,9 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-admin.tenant_locale_and_tenant_specific_settings.ac49c83b": "Tenant locale and tenant-specific settings.",
|
||||
"i18n:govoplan-admin.tenant_memberships_and_inherited_roles.16eda9f6": "Tenant memberships and inherited roles.",
|
||||
"i18n:govoplan-admin.tenant_permission_bundles_and_system_managed_rol.bb563bea": "Tenant permission bundles and system-managed role copies.",
|
||||
"i18n:govoplan-admin.tenant_permissions.246294bc": "Tenant permissions",
|
||||
"i18n:govoplan-admin.tenant_role.6b53115d": "Tenant role",
|
||||
"i18n:govoplan-admin.tenant_roles.51aca82d": "Tenant roles",
|
||||
"i18n:govoplan-admin.tenant_permissions.246294bc": "Permissions",
|
||||
"i18n:govoplan-admin.tenant_role.6b53115d": "Role template",
|
||||
"i18n:govoplan-admin.tenant_roles.51aca82d": "Role templates",
|
||||
"i18n:govoplan-admin.tenant_users.cb800b38": "Tenant users",
|
||||
"i18n:govoplan-admin.tenants.1f7ae776": "Tenants",
|
||||
"i18n:govoplan-admin.these_settings_are_enforced_by_the_backend_centr.8112ed6f": "These settings are enforced by the backend. Central groups and tenant roles remain available even when local creation is disabled.",
|
||||
@@ -328,6 +360,8 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-admin.unlocked.b3da7025": "Unlocked",
|
||||
"i18n:govoplan-admin.unsigned.e91344ea": "Unsigned",
|
||||
"i18n:govoplan-admin.untrusted.cdc7838a": "Untrusted",
|
||||
"i18n:govoplan-admin.upgrade.12c5007d": "Upgrade",
|
||||
"i18n:govoplan-admin.update.503a059f": "Update",
|
||||
"i18n:govoplan-admin.update_enabled_modules.9b7ae790": "Update enabled modules",
|
||||
"i18n:govoplan-admin.updated.f2f8570d": "Updated",
|
||||
"i18n:govoplan-admin.user_file_connector_policy_limits": "File connector policy limits for user-owned spaces.",
|
||||
@@ -361,13 +395,15 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-admin.action.97c89a4d": "Action",
|
||||
"i18n:govoplan-admin.actions.c3cd636a": "Aktionen",
|
||||
"i18n:govoplan-admin.activate_installs.731d22a1": "Installationen aktivieren",
|
||||
"i18n:govoplan-admin.acknowledged.d08d7c6d": "Bestaetigt",
|
||||
"i18n:govoplan-admin.active_and_total_memberships.c0c20f10": "Active and total memberships.",
|
||||
"i18n:govoplan-admin.active_tenant_automation_credentials.240c659e": "Active tenant automation credentials.",
|
||||
"i18n:govoplan-admin.active_tenant.dfadf0aa": "Active tenant:",
|
||||
"i18n:govoplan-admin.active.a733b809": "Aktiv",
|
||||
"i18n:govoplan-admin.after.695c89be": "Nach:",
|
||||
"i18n:govoplan-admin.add_group_template.b74d8f0f": "Add group template",
|
||||
"i18n:govoplan-admin.add_plan_item.fa55f028": "Add plan item",
|
||||
"i18n:govoplan-admin.add_tenant_role.fcd904ea": "Add tenant role",
|
||||
"i18n:govoplan-admin.add_tenant_role.fcd904ea": "Rollenvorlage hinzufügen",
|
||||
"i18n:govoplan-admin.admin.4e7afebc": "Administration",
|
||||
"i18n:govoplan-admin.administration.b8be3d12": "Administration",
|
||||
"i18n:govoplan-admin.platform_administration": "Plattformadministration",
|
||||
@@ -389,9 +425,12 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-admin.audit.fa1703dd": "Audit",
|
||||
"i18n:govoplan-admin.available.7c62a142": "Verfügbar",
|
||||
"i18n:govoplan-admin.blocked.99613c74": "Blockiert",
|
||||
"i18n:govoplan-admin.before.a11cf31f": "Vor:",
|
||||
"i18n:govoplan-admin.bridge_release.176cf241": "Brueckenrelease",
|
||||
"i18n:govoplan-admin.build_webui.ed5ef1fd": " --build-webui",
|
||||
"i18n:govoplan-admin.build_webui.fe8ccad7": "WebUI bauen",
|
||||
"i18n:govoplan-admin.bypass_allowed.4e347c27": "Bypass allowed",
|
||||
"i18n:govoplan-admin.catalog_metadata.9b96c41a": "Katalogmetadaten",
|
||||
"i18n:govoplan-admin.bypass_denied.ab987400": "Bypass denied",
|
||||
"i18n:govoplan-admin.cached_from.d73c5b2a": "· cached from",
|
||||
"i18n:govoplan-admin.cancel.77dfd213": "Abbrechen",
|
||||
@@ -425,10 +464,13 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-admin.create_group_template.72407248": "Create group template",
|
||||
"i18n:govoplan-admin.create_request_and_apply.da79e153": "Create request and apply",
|
||||
"i18n:govoplan-admin.create_suspend_and_govern_tenant_spaces.77992a39": "Create, suspend and govern tenant spaces.",
|
||||
"i18n:govoplan-admin.create_tenant_role.f58db104": "Create tenant role",
|
||||
"i18n:govoplan-admin.create_tenant_role.f58db104": "Rollenvorlage erstellen",
|
||||
"i18n:govoplan-admin.created.0c78dab1": "Created:",
|
||||
"i18n:govoplan-admin.created.accf40c8": "Erstellt",
|
||||
"i18n:govoplan-admin.current_window.73e25c9f": "Aktuelles Fenster:",
|
||||
"i18n:govoplan-admin.automatic.d9a7260f": "Automatisch",
|
||||
"i18n:govoplan-admin.daemon_execution.cc0fad8d": "Daemon-Ausführung",
|
||||
"i18n:govoplan-admin.data_safety_reviewed.b4774edc": "Datensicherheit geprueft",
|
||||
"i18n:govoplan-admin.daemon_offline.314f19a4": "Daemon offline",
|
||||
"i18n:govoplan-admin.daemon_value.e54f5a31": "Daemon: {value0}",
|
||||
"i18n:govoplan-admin.db_backup_command.5bded3ac": "DB-Backup-Befehl",
|
||||
@@ -447,11 +489,13 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-admin.defaults_for_newly_created_tenants.9d0f4ccd": "Standards für neu erstellte Mandanten",
|
||||
"i18n:govoplan-admin.delete_group_template.8745d842": "Delete group template",
|
||||
"i18n:govoplan-admin.delete_template.399bf72a": "Delete template",
|
||||
"i18n:govoplan-admin.delete_tenant_role.4efa0813": "Delete tenant role",
|
||||
"i18n:govoplan-admin.delete_tenant_role.4efa0813": "Rollenvorlage löschen",
|
||||
"i18n:govoplan-admin.delete_value_removal_is_blocked_while_a_material.d4eba73d": "Delete {value0}? Removal is blocked while a materialized tenant definition still has members or assignments.",
|
||||
"i18n:govoplan-admin.delete_value.4d18989e": "Delete {value0}",
|
||||
"i18n:govoplan-admin.dependencies.9f4f78d1": "Abhaengigkeiten:",
|
||||
"i18n:govoplan-admin.dependents.072665c4": "Dependents:",
|
||||
"i18n:govoplan-admin.description.55f8ebc8": "Beschreibung",
|
||||
"i18n:govoplan-admin.destructive.0051026c": "Destruktiv",
|
||||
"i18n:govoplan-admin.destroy_data.34557c3c": "Daten zerstören",
|
||||
"i18n:govoplan-admin.disabled.f4f4473d": "Disabled",
|
||||
"i18n:govoplan-admin.discovered_packages.660902de": "Discovered packages",
|
||||
@@ -460,9 +504,12 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-admin.dry_run_change_request_created_value_enable_main.49826d07": "Dry-run change request created: {value0}. Enable maintenance mode, then apply the request.",
|
||||
"i18n:govoplan-admin.dry_run.3d14659c": "Dry-run",
|
||||
"i18n:govoplan-admin.edit_group_template.9bc72d21": "Edit group template",
|
||||
"i18n:govoplan-admin.edit_tenant_role.c15a260d": "Edit tenant role",
|
||||
"i18n:govoplan-admin.edit_tenant_role.c15a260d": "Rollenvorlage bearbeiten",
|
||||
"i18n:govoplan-admin.edit_value.fad75899": "Edit {value0}",
|
||||
"i18n:govoplan-admin.enable_maintenance_mode.75f98d57": "Enable maintenance mode",
|
||||
"i18n:govoplan-admin.enabled_modules.3c38e9ff": "Aktive Module:",
|
||||
"i18n:govoplan-admin.executor_available.507de429": "Ausfuehrer verfuegbar",
|
||||
"i18n:govoplan-admin.executor_pending.7633225b": "Ausfuehrer ausstehend",
|
||||
"i18n:govoplan-admin.enabled.df174a3f": "Aktiviert",
|
||||
"i18n:govoplan-admin.enabling_maintenance_mode_requires_system_mainte.062d3968": "Enabling maintenance mode requires system:maintenance:access.",
|
||||
"i18n:govoplan-admin.enabling.2b8e03e6": "Enabling...",
|
||||
@@ -476,6 +523,7 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-admin.exported_tenant_access_configuration.9122c85c": "Exported tenant access configuration",
|
||||
"i18n:govoplan-admin.features.5df81ffa": "Features:",
|
||||
"i18n:govoplan-admin.file_connections.1e362326": "Dateiverbindungen",
|
||||
"i18n:govoplan-admin.forward_only.6c107a46": "Nur vorwaerts",
|
||||
"i18n:govoplan-admin.finished.4b52fe3f": "Finished:",
|
||||
"i18n:govoplan-admin.fragment.3f19d616": "Fragment",
|
||||
"i18n:govoplan-admin.front_office.d9dcfee1": "Front office",
|
||||
@@ -497,6 +545,7 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-admin.history.90ccd649": "History",
|
||||
"i18n:govoplan-admin.id.474ae526": "Id",
|
||||
"i18n:govoplan-admin.import_preflight_approve_apply_and_export_module.29aec929": "Modul-eigene Konfigurationspakete importieren, prüfen, freigeben, anwenden und exportieren.",
|
||||
"i18n:govoplan-admin.idempotent.47a0f2d6": "Idempotent",
|
||||
"i18n:govoplan-admin.inactive.09af574c": "Inaktiv",
|
||||
"i18n:govoplan-admin.inspect_value.9d5d1071": "Inspect {value0}",
|
||||
"i18n:govoplan-admin.install.fd6c3ebf": "Installieren",
|
||||
@@ -524,6 +573,7 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-admin.license.de13bf1a": "License:",
|
||||
"i18n:govoplan-admin.locked.a798882f": "Gesperrt",
|
||||
"i18n:govoplan-admin.mail_servers.d627326a": "Mail servers",
|
||||
"i18n:govoplan-admin.manifest_metadata.b9ae362a": "Manifestmetadaten",
|
||||
"i18n:govoplan-admin.maintenance_message.ca62571f": "Wartungsmeldung",
|
||||
"i18n:govoplan-admin.maintenance_mode_enabled_apply_the_module_state_.dda13e5c": "Maintenance mode enabled. Apply the module-state request when ready.",
|
||||
"i18n:govoplan-admin.maintenance_mode_is_off_package_changes_should_b.1d262629": "Maintenance mode is off. Package changes should be applied only after enabling maintenance mode.",
|
||||
@@ -532,6 +582,10 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-admin.maintenance.94de303b": "Wartung",
|
||||
"i18n:govoplan-admin.membership_status_groups_and_direct_roles_in_the.ab6c10b6": "Membership status, groups and direct roles in the active tenant.",
|
||||
"i18n:govoplan-admin.message.68f4145f": "Nachricht",
|
||||
"i18n:govoplan-admin.metadata_pending.25190d87": "Metadaten ausstehend",
|
||||
"i18n:govoplan-admin.migration_plan.f42b9d90": "Migrationsplan",
|
||||
"i18n:govoplan-admin.migration_safety.729bdb3c": "Migrationssicherheit:",
|
||||
"i18n:govoplan-admin.migration_tasks.a4410d3a": "Migrationsaufgaben",
|
||||
"i18n:govoplan-admin.minimal_office_access.af48f49a": "Minimal office access",
|
||||
"i18n:govoplan-admin.missing_entitlement.0cafa611": "Missing entitlement",
|
||||
"i18n:govoplan-admin.missing.feb2bbaa": "Missing:",
|
||||
@@ -559,6 +613,7 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-admin.none.6eef6648": "Keine",
|
||||
"i18n:govoplan-admin.not_available.d1a17af1": "Not available",
|
||||
"i18n:govoplan-admin.not_configured.811931bb": "Nicht konfiguriert",
|
||||
"i18n:govoplan-admin.not_idempotent.d3014c71": "Nicht idempotent",
|
||||
"i18n:govoplan-admin.not_recorded.9925ee3c": "not recorded",
|
||||
"i18n:govoplan-admin.notes.70440046": "Notes",
|
||||
"i18n:govoplan-admin.object.2883f191": "Object",
|
||||
@@ -578,8 +633,14 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-admin.package.7431e3df": "Package",
|
||||
"i18n:govoplan-admin.packages.0a999012": "Pakete",
|
||||
"i18n:govoplan-admin.pending.c515ec74": " pending",
|
||||
"i18n:govoplan-admin.pending_metadata.a24cfeb0": "Ausstehende Metadaten",
|
||||
"i18n:govoplan-admin.permissions.d06d5557": "Berechtigungen",
|
||||
"i18n:govoplan-admin.plan_install.e82bffe6": "Plan install",
|
||||
"i18n:govoplan-admin.plan_update.86e6857a": "Aktualisierung planen",
|
||||
"i18n:govoplan-admin.post_migration_backfill.21043d74": "Nach-Migration Backfill",
|
||||
"i18n:govoplan-admin.post_migration_verify.6af9f28b": "Nach-Migration Pruefung",
|
||||
"i18n:govoplan-admin.pre_migration_check.f654164a": "Vor-Migration Check",
|
||||
"i18n:govoplan-admin.pre_migration_prepare.d18fdd26": "Vor-Migration Vorbereitung",
|
||||
"i18n:govoplan-admin.plan_package_installs_and_removals_here_then_app.4aeb03bf": "Plan package installs and removals here, then apply the rendered commands from an operator shell during maintenance mode.",
|
||||
"i18n:govoplan-admin.plan_uninstall.e62804ab": "Plan uninstall",
|
||||
"i18n:govoplan-admin.plan.ae2f98a0": "Plan",
|
||||
@@ -596,6 +657,7 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-admin.queued_daemon_handoffs_created_from_the_admin_ui.d7ae7914": "Queued daemon handoffs created from the admin UI or CLI.",
|
||||
"i18n:govoplan-admin.queueing_installer_requests_requires_maintenance.ae81f9ac": "Das Einreihen von Installer-Anfragen erfordert Wartungszugriff.",
|
||||
"i18n:govoplan-admin.recent_supervised_installer_records_and_the_curr.29860245": "Aktuelle überwachte Installer-Datensätze und die aktuelle Paketinstallationssperre.",
|
||||
"i18n:govoplan-admin.recovery_tested.e831e2f1": "Recovery getestet",
|
||||
"i18n:govoplan-admin.registered_tenant_spaces.61e17d70": "Registered tenant spaces.",
|
||||
"i18n:govoplan-admin.reload.cce71553": "Neu laden",
|
||||
"i18n:govoplan-admin.remove.e963907d": "Entfernen",
|
||||
@@ -608,10 +670,12 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-admin.required.d5793988": "Required:",
|
||||
"i18n:govoplan-admin.required.eed6bfb4": "Required",
|
||||
"i18n:govoplan-admin.requires.a4fc9357": "Requires:",
|
||||
"i18n:govoplan-admin.requires_review.8ba0b8c6": "Pruefung erforderlich",
|
||||
"i18n:govoplan-admin.restart_commands.035997f9": "Neustartbefehle",
|
||||
"i18n:govoplan-admin.restart_reload_required_after_package_changes.3a99cd63": "Restart/reload required after package changes",
|
||||
"i18n:govoplan-admin.restrict_authenticated_api_access_to_maintenance.2bc47195": "Authentifizierten API-Zugriff auf Wartungsoperatoren beschränken",
|
||||
"i18n:govoplan-admin.retention.c7199d9e": "Retention",
|
||||
"i18n:govoplan-admin.retirement.6b680dd2": "Stilllegung",
|
||||
"i18n:govoplan-admin.retry_of.3a6bb304": "Retry of:",
|
||||
"i18n:govoplan-admin.retry.9f5cd8a2": "Erneut versuchen",
|
||||
"i18n:govoplan-admin.reusable_encrypted_smtp_imap_profiles_and_mail_p.31f150d1": "Reusable encrypted SMTP/IMAP profiles and mail policy.",
|
||||
@@ -627,7 +691,7 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-admin.save_plan.842dc280": "Save plan",
|
||||
"i18n:govoplan-admin.save_settings.913aba9f": "Einstellungen speichern",
|
||||
"i18n:govoplan-admin.save_template.0885fab2": "Save template",
|
||||
"i18n:govoplan-admin.save_tenant_role.8fc0d37d": "Save tenant role",
|
||||
"i18n:govoplan-admin.save_tenant_role.8fc0d37d": "Rollenvorlage speichern",
|
||||
"i18n:govoplan-admin.save_the_install_plan_before_queueing_a_daemon_r.3ba00691": "Speichern Sie den Installationsplan, bevor eine Daemon-Anfrage eingereiht wird.",
|
||||
"i18n:govoplan-admin.saved.c0ae8f6e": "Gespeichert",
|
||||
"i18n:govoplan-admin.saving.56a2285c": "Saving…",
|
||||
@@ -657,6 +721,8 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-admin.system_settings_saved.dac4f17b": "Systemeinstellungen gespeichert.",
|
||||
"i18n:govoplan-admin.system_wide_governance_and_tenant_local_access_m.cda72499": "System-wide governance and tenant-local access management, separated by scope and enforced by the backend.",
|
||||
"i18n:govoplan-admin.system.bc0792d8": "System",
|
||||
"i18n:govoplan-admin.target_plan.524ea0c8": "Zielplan",
|
||||
"i18n:govoplan-admin.task_version.f0f26b92": "Aufgabenversion",
|
||||
"i18n:govoplan-admin.target.61ad50a9": "Target",
|
||||
"i18n:govoplan-admin.template_details.d5d75e4d": "Template details",
|
||||
"i18n:govoplan-admin.tenant_administration_capabilities.5d265972": "Tenant administration capabilities",
|
||||
@@ -670,9 +736,9 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-admin.tenant_locale_and_tenant_specific_settings.ac49c83b": "Tenant locale and tenant-specific settings.",
|
||||
"i18n:govoplan-admin.tenant_memberships_and_inherited_roles.16eda9f6": "Tenant memberships and inherited roles.",
|
||||
"i18n:govoplan-admin.tenant_permission_bundles_and_system_managed_rol.bb563bea": "Tenant permission bundles and system-managed role copies.",
|
||||
"i18n:govoplan-admin.tenant_permissions.246294bc": "Tenant permissions",
|
||||
"i18n:govoplan-admin.tenant_role.6b53115d": "Tenant role",
|
||||
"i18n:govoplan-admin.tenant_roles.51aca82d": "Mandantenrollen",
|
||||
"i18n:govoplan-admin.tenant_permissions.246294bc": "Berechtigungen",
|
||||
"i18n:govoplan-admin.tenant_role.6b53115d": "Rollenvorlage",
|
||||
"i18n:govoplan-admin.tenant_roles.51aca82d": "Rollenvorlagen",
|
||||
"i18n:govoplan-admin.tenant_users.cb800b38": "Mandantenbenutzer",
|
||||
"i18n:govoplan-admin.tenants.1f7ae776": "Mandanten",
|
||||
"i18n:govoplan-admin.these_settings_are_enforced_by_the_backend_centr.8112ed6f": "Diese Einstellungen werden vom Backend erzwungen. Zentrale Gruppen und Mandantenrollen bleiben verfügbar, auch wenn lokale Erstellung deaktiviert ist.",
|
||||
@@ -683,6 +749,8 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-admin.unlocked.b3da7025": "Entsperrt",
|
||||
"i18n:govoplan-admin.unsigned.e91344ea": "Unsigniert",
|
||||
"i18n:govoplan-admin.untrusted.cdc7838a": "Nicht vertrauenswürdig",
|
||||
"i18n:govoplan-admin.upgrade.12c5007d": "Upgrade",
|
||||
"i18n:govoplan-admin.update.503a059f": "Aktualisieren",
|
||||
"i18n:govoplan-admin.update_enabled_modules.9b7ae790": "Update enabled modules",
|
||||
"i18n:govoplan-admin.updated.f2f8570d": "Aktualisiert",
|
||||
"i18n:govoplan-admin.user_file_connector_policy_limits": "Dateiverbindungsrichtlinien fuer benutzereigene Bereiche.",
|
||||
|
||||
@@ -19,6 +19,7 @@ const adminSections: AdminSectionsUiCapability = {
|
||||
sections: [
|
||||
{
|
||||
id: "overview",
|
||||
surfaceId: "admin.section.overview",
|
||||
label: "i18n:govoplan-admin.overview.0efc2e6b",
|
||||
group: "ROOT",
|
||||
order: 0,
|
||||
@@ -31,6 +32,7 @@ const adminSections: AdminSectionsUiCapability = {
|
||||
},
|
||||
{
|
||||
id: "system-settings",
|
||||
surfaceId: "admin.section.system-settings",
|
||||
label: "i18n:govoplan-admin.general.9239ee2c",
|
||||
group: "SYSTEM",
|
||||
order: 10,
|
||||
@@ -43,6 +45,7 @@ const adminSections: AdminSectionsUiCapability = {
|
||||
},
|
||||
{
|
||||
id: "system-configuration-changes",
|
||||
surfaceId: "admin.section.system-configuration-changes",
|
||||
label: "i18n:govoplan-admin.changes.8aa57de6",
|
||||
group: "SYSTEM",
|
||||
order: 20,
|
||||
@@ -54,7 +57,8 @@ const adminSections: AdminSectionsUiCapability = {
|
||||
},
|
||||
{
|
||||
id: "system-configuration-packages",
|
||||
label: "i18n:govoplan-admin.packages.0a999012",
|
||||
surfaceId: "admin.section.system-configuration-packages",
|
||||
label: "i18n:govoplan-admin.configuration_packages.eb2f05f1",
|
||||
group: "SYSTEM",
|
||||
order: 30,
|
||||
allOf: ["system:settings:read"],
|
||||
@@ -65,6 +69,7 @@ const adminSections: AdminSectionsUiCapability = {
|
||||
},
|
||||
{
|
||||
id: "system-role-templates",
|
||||
surfaceId: "admin.section.system-role-templates",
|
||||
label: "i18n:govoplan-admin.tenant_roles.51aca82d",
|
||||
group: "SYSTEM",
|
||||
order: 40,
|
||||
@@ -78,6 +83,7 @@ const adminSections: AdminSectionsUiCapability = {
|
||||
},
|
||||
{
|
||||
id: "system-modules",
|
||||
surfaceId: "admin.section.system-modules",
|
||||
label: "i18n:govoplan-admin.modules.04e9462c",
|
||||
group: "SYSTEM",
|
||||
order: 85,
|
||||
@@ -90,6 +96,7 @@ const adminSections: AdminSectionsUiCapability = {
|
||||
},
|
||||
{
|
||||
id: "system-groups",
|
||||
surfaceId: "admin.section.system-groups",
|
||||
label: "i18n:govoplan-admin.groups.ae9629f4",
|
||||
group: "SYSTEM",
|
||||
order: 50,
|
||||
@@ -110,6 +117,15 @@ export const adminModule: PlatformWebModule = {
|
||||
version: "1.0.0",
|
||||
dependencies: ["access"],
|
||||
translations,
|
||||
viewSurfaces: [
|
||||
{ id: "admin.section.overview", moduleId: "admin", kind: "section", label: "Administration overview", order: 0 },
|
||||
{ id: "admin.section.system-settings", moduleId: "admin", kind: "section", label: "System settings", order: 10 },
|
||||
{ id: "admin.section.system-configuration-changes", moduleId: "admin", kind: "section", label: "Configuration changes", order: 20 },
|
||||
{ id: "admin.section.system-configuration-packages", moduleId: "admin", kind: "section", label: "Configuration packages", order: 30 },
|
||||
{ id: "admin.section.system-role-templates", moduleId: "admin", kind: "section", label: "Role templates", order: 40 },
|
||||
{ id: "admin.section.system-groups", moduleId: "admin", kind: "section", label: "Group templates", order: 50 },
|
||||
{ id: "admin.section.system-modules", moduleId: "admin", kind: "section", label: "Modules", order: 85 }
|
||||
],
|
||||
uiCapabilities: {
|
||||
"admin.sections": adminSections
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user