Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
24edb7eb8a | ||
|
|
8c82d5b4f8 | ||
|
|
f362f3806b | ||
|
|
30a0281c66 | ||
|
|
970625edff | ||
|
|
0f2a9beca7 |
+276
@@ -0,0 +1,276 @@
|
|||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*.egg-info/
|
||||||
|
.pytest_cache/
|
||||||
|
.mypy_cache/
|
||||||
|
.ruff_cache/
|
||||||
|
.venv/
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
node_modules/
|
||||||
|
webui/node_modules/
|
||||||
|
webui/dist/
|
||||||
|
*.tsbuildinfo
|
||||||
|
.component-test-build/
|
||||||
|
.module-test-build/
|
||||||
|
.policy-test-build/
|
||||||
|
.template-preview-test-build/
|
||||||
|
.import-test-build/
|
||||||
|
webui/.component-test-build/
|
||||||
|
webui/.module-test-build/
|
||||||
|
webui/.policy-test-build/
|
||||||
|
webui/.template-preview-test-build/
|
||||||
|
webui/.import-test-build/
|
||||||
|
|
||||||
|
# GovOPlaN shared ignore rules from govoplan-core
|
||||||
|
# ---> Node
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
# Dependency directories
|
||||||
|
jspm_packages/
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
web_modules/
|
||||||
|
# TypeScript cache
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
# Optional stylelint cache
|
||||||
|
.stylelintcache
|
||||||
|
# Microbundle cache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
# dotenv environment variable files
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
# Next.js build output
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
# Gatsby files
|
||||||
|
.cache/
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
# public
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
# vuepress v2.x temp and cache directory
|
||||||
|
.temp
|
||||||
|
.cache
|
||||||
|
# vitepress build output
|
||||||
|
**/.vitepress/dist
|
||||||
|
# vitepress cache directory
|
||||||
|
**/.vitepress/cache
|
||||||
|
# Docusaurus cache and generated files
|
||||||
|
.docusaurus
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
# TernJS port file
|
||||||
|
.tern-port
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
.vscode-test
|
||||||
|
# yarn v2
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
||||||
|
# Local WebUI test/build scratch directories
|
||||||
|
# ---> Python
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
*$py.class
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
develop-eggs/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
share/python-wheels/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py,cover
|
||||||
|
.hypothesis/
|
||||||
|
cover/
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
# PyBuilder
|
||||||
|
.pybuilder/
|
||||||
|
target/
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
# pyenv
|
||||||
|
# For a library or package, you might want to ignore these files since the code is
|
||||||
|
# intended to run in multiple environments; otherwise, check them in:
|
||||||
|
# .python-version
|
||||||
|
# pipenv
|
||||||
|
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||||
|
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||||
|
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||||
|
# install all needed dependencies.
|
||||||
|
#Pipfile.lock
|
||||||
|
# UV
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
||||||
|
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||||
|
# commonly ignored for libraries.
|
||||||
|
#uv.lock
|
||||||
|
# poetry
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||||
|
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||||
|
# commonly ignored for libraries.
|
||||||
|
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||||
|
#poetry.lock
|
||||||
|
# pdm
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||||
|
#pdm.lock
|
||||||
|
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||||
|
# in version control.
|
||||||
|
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
||||||
|
.pdm.toml
|
||||||
|
.pdm-python
|
||||||
|
.pdm-build/
|
||||||
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||||
|
__pypackages__/
|
||||||
|
# Celery stuff
|
||||||
|
celerybeat-schedule
|
||||||
|
celerybeat.pid
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
# mypy
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
# pytype static type analyzer
|
||||||
|
.pytype/
|
||||||
|
# Cython debug symbols
|
||||||
|
cython_debug/
|
||||||
|
# PyCharm
|
||||||
|
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||||
|
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||||
|
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||||
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
|
#.idea/
|
||||||
|
# Ruff stuff:
|
||||||
|
# PyPI configuration file
|
||||||
|
.pypirc
|
||||||
|
# ---> VisualStudioCode
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
!.vscode/*.code-snippets
|
||||||
|
# Local History for Visual Studio Code
|
||||||
|
.history/
|
||||||
|
# Built Visual Studio Code Extensions
|
||||||
|
*.vsix
|
||||||
|
*.db
|
||||||
|
# GovOPlaN local runtime state
|
||||||
|
runtime/
|
||||||
|
# GovOPlaN WebUI test output
|
||||||
@@ -3,9 +3,10 @@
|
|||||||
`govoplan-admin` owns generic system administration API and WebUI contributions
|
`govoplan-admin` owns generic system administration API and WebUI contributions
|
||||||
during the GovOPlaN module split.
|
during the GovOPlaN module split.
|
||||||
|
|
||||||
This repository owns the live `governance_templates` and
|
This repository owns the live `admin_governance_templates` and
|
||||||
`governance_template_assignments` tables while preserving their historical
|
`admin_governance_template_assignments` tables. Core migrations rename the
|
||||||
table names. It contributes the stable
|
historical unprefixed governance tables for existing development databases. It
|
||||||
|
contributes the stable
|
||||||
`/api/v1/admin/system/governance-templates` routes and owns governance-template
|
`/api/v1/admin/system/governance-templates` routes and owns governance-template
|
||||||
CRUD plus materialization into access-owned tenant groups and roles.
|
CRUD plus materialization into access-owned tenant groups and roles.
|
||||||
|
|
||||||
@@ -23,3 +24,22 @@ section entries through core's `admin.sections` UI capability:
|
|||||||
|
|
||||||
The route shell in access collects those sections at runtime, applies their
|
The route shell in access collects those sections at runtime, applies their
|
||||||
scope requirements, and renders them without importing admin package internals.
|
scope requirements, and renders them without importing admin package internals.
|
||||||
|
|
||||||
|
## Module Lifecycle Administration
|
||||||
|
|
||||||
|
The admin module owns the operator surfaces for module lifecycle management:
|
||||||
|
|
||||||
|
- installed/enabled/desired module state
|
||||||
|
- runtime activation and deactivation of installed modules
|
||||||
|
- signed catalog install planning
|
||||||
|
- non-destructive uninstall planning, with explicit `destroy_data` retirement
|
||||||
|
options where a module provides a retirement provider
|
||||||
|
- installer preflight status, maintenance-mode blockers, migration/restart
|
||||||
|
checklist entries, and rendered operator commands
|
||||||
|
- installer daemon request queue, cancellation/retry, recent run summaries,
|
||||||
|
rollback status, run IDs, request IDs, and trace IDs
|
||||||
|
|
||||||
|
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`.
|
||||||
|
|||||||
+3
-3
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@govoplan/admin-webui",
|
"name": "@govoplan/admin-webui",
|
||||||
"version": "0.1.5",
|
"version": "0.1.7",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "webui/src/index.ts",
|
"main": "webui/src/index.ts",
|
||||||
@@ -18,8 +18,8 @@
|
|||||||
"LICENSE"
|
"LICENSE"
|
||||||
],
|
],
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@govoplan/core-webui": "^0.1.5",
|
"@govoplan/core-webui": "^0.1.7",
|
||||||
"lucide-react": "^0.555.0",
|
"lucide-react": "^1.23.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-router-dom": "^7.1.1"
|
"react-router-dom": "^7.1.1"
|
||||||
|
|||||||
+3
-3
@@ -4,14 +4,14 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "govoplan-admin"
|
name = "govoplan-admin"
|
||||||
version = "0.1.5"
|
version = "0.1.7"
|
||||||
description = "GovOPlaN generic administration module."
|
description = "GovOPlaN generic administration module."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
authors = [{ name = "GovOPlaN" }]
|
authors = [{ name = "GovOPlaN" }]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"govoplan-core>=0.1.5",
|
"govoplan-core>=0.1.7",
|
||||||
"govoplan-access>=0.1.5",
|
"govoplan-access>=0.1.7",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.setuptools.packages.find]
|
[tool.setuptools.packages.find]
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
Metadata-Version: 2.4
|
|
||||||
Name: govoplan-admin
|
|
||||||
Version: 0.1.4
|
|
||||||
Summary: GovOPlaN generic administration module.
|
|
||||||
Author: GovOPlaN
|
|
||||||
Requires-Python: >=3.12
|
|
||||||
Description-Content-Type: text/markdown
|
|
||||||
Requires-Dist: govoplan-core>=0.1.4
|
|
||||||
Requires-Dist: govoplan-access>=0.1.4
|
|
||||||
|
|
||||||
# GovOPlaN Admin
|
|
||||||
|
|
||||||
`govoplan-admin` owns generic system administration API and WebUI contributions
|
|
||||||
during the GovOPlaN module split.
|
|
||||||
|
|
||||||
This repository owns the live `governance_templates` and
|
|
||||||
`governance_template_assignments` tables while preserving their historical
|
|
||||||
table names. It contributes the stable
|
|
||||||
`/api/v1/admin/system/governance-templates` routes and owns governance-template
|
|
||||||
CRUD plus materialization into access-owned tenant groups and roles.
|
|
||||||
|
|
||||||
## WebUI Package
|
|
||||||
|
|
||||||
The repository root and `webui/` directory both expose the package
|
|
||||||
`@govoplan/admin-webui`. The package does not contribute the `/admin` route
|
|
||||||
itself; `govoplan-access` owns the route shell. Instead, admin contributes
|
|
||||||
section entries through core's `admin.sections` UI capability:
|
|
||||||
|
|
||||||
- overview
|
|
||||||
- system settings
|
|
||||||
- governance template tenant roles
|
|
||||||
- governance template groups
|
|
||||||
|
|
||||||
The route shell in access collects those sections at runtime, applies their
|
|
||||||
scope requirements, and renders them without importing admin package internals.
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
README.md
|
|
||||||
pyproject.toml
|
|
||||||
src/govoplan_admin/__init__.py
|
|
||||||
src/govoplan_admin/py.typed
|
|
||||||
src/govoplan_admin.egg-info/PKG-INFO
|
|
||||||
src/govoplan_admin.egg-info/SOURCES.txt
|
|
||||||
src/govoplan_admin.egg-info/dependency_links.txt
|
|
||||||
src/govoplan_admin.egg-info/entry_points.txt
|
|
||||||
src/govoplan_admin.egg-info/requires.txt
|
|
||||||
src/govoplan_admin.egg-info/top_level.txt
|
|
||||||
src/govoplan_admin/backend/__init__.py
|
|
||||||
src/govoplan_admin/backend/governance.py
|
|
||||||
src/govoplan_admin/backend/manifest.py
|
|
||||||
src/govoplan_admin/backend/api/__init__.py
|
|
||||||
src/govoplan_admin/backend/api/v1/__init__.py
|
|
||||||
src/govoplan_admin/backend/api/v1/routes.py
|
|
||||||
src/govoplan_admin/backend/api/v1/schemas.py
|
|
||||||
src/govoplan_admin/backend/db/__init__.py
|
|
||||||
src/govoplan_admin/backend/db/models.py
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
[govoplan.modules]
|
|
||||||
admin = govoplan_admin.backend.manifest:get_manifest
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
govoplan-core>=0.1.4
|
|
||||||
govoplan-access>=0.1.4
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
govoplan_admin
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,8 @@ from typing import Any, Literal
|
|||||||
|
|
||||||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||||||
|
|
||||||
|
from govoplan_core.api.v1.schemas import DeltaDeletedItem
|
||||||
|
|
||||||
|
|
||||||
RETENTION_DAY_KEYS = (
|
RETENTION_DAY_KEYS = (
|
||||||
"raw_campaign_json_retention_days",
|
"raw_campaign_json_retention_days",
|
||||||
@@ -65,6 +67,14 @@ class MaintenanceModeItem(BaseModel):
|
|||||||
message: str | None = Field(default=None, max_length=500)
|
message: str | None = Field(default=None, max_length=500)
|
||||||
|
|
||||||
|
|
||||||
|
class LanguagePackageItem(BaseModel):
|
||||||
|
model_config = ConfigDict(extra="forbid")
|
||||||
|
|
||||||
|
code: str = Field(min_length=1, max_length=20)
|
||||||
|
label: str = Field(min_length=1, max_length=100)
|
||||||
|
native_label: str | None = Field(default=None, max_length=100)
|
||||||
|
|
||||||
|
|
||||||
class AdminOverviewResponse(BaseModel):
|
class AdminOverviewResponse(BaseModel):
|
||||||
active_tenant_id: str
|
active_tenant_id: str
|
||||||
active_tenant_name: str
|
active_tenant_name: str
|
||||||
@@ -87,9 +97,21 @@ class SystemSettingsItem(BaseModel):
|
|||||||
allow_tenant_api_keys: bool = True
|
allow_tenant_api_keys: bool = True
|
||||||
privacy_retention_policy: PrivacyRetentionPolicyItem = Field(default_factory=PrivacyRetentionPolicyItem)
|
privacy_retention_policy: PrivacyRetentionPolicyItem = Field(default_factory=PrivacyRetentionPolicyItem)
|
||||||
maintenance_mode: MaintenanceModeItem = Field(default_factory=MaintenanceModeItem)
|
maintenance_mode: MaintenanceModeItem = Field(default_factory=MaintenanceModeItem)
|
||||||
|
available_languages: list[LanguagePackageItem] = Field(default_factory=list)
|
||||||
|
enabled_language_codes: list[str] = Field(default_factory=list)
|
||||||
settings: dict[str, Any] = Field(default_factory=dict)
|
settings: dict[str, Any] = Field(default_factory=dict)
|
||||||
|
|
||||||
|
|
||||||
|
class SystemSettingsDeltaResponse(BaseModel):
|
||||||
|
item: SystemSettingsItem | None = None
|
||||||
|
sections: dict[str, Any] = Field(default_factory=dict)
|
||||||
|
changed_sections: list[str] = Field(default_factory=list)
|
||||||
|
deleted: list[DeltaDeletedItem] = Field(default_factory=list)
|
||||||
|
watermark: str | None = None
|
||||||
|
has_more: bool = False
|
||||||
|
full: bool = False
|
||||||
|
|
||||||
|
|
||||||
class SystemSettingsUpdateRequest(BaseModel):
|
class SystemSettingsUpdateRequest(BaseModel):
|
||||||
model_config = ConfigDict(extra="forbid")
|
model_config = ConfigDict(extra="forbid")
|
||||||
|
|
||||||
@@ -99,6 +121,9 @@ class SystemSettingsUpdateRequest(BaseModel):
|
|||||||
allow_tenant_api_keys: bool
|
allow_tenant_api_keys: bool
|
||||||
privacy_retention_policy: PrivacyRetentionPolicyItem | None = None
|
privacy_retention_policy: PrivacyRetentionPolicyItem | None = None
|
||||||
maintenance_mode: MaintenanceModeItem | None = None
|
maintenance_mode: MaintenanceModeItem | None = None
|
||||||
|
available_languages: list[LanguagePackageItem] | None = None
|
||||||
|
enabled_language_codes: list[str] | None = None
|
||||||
|
change_request_id: str | None = None
|
||||||
|
|
||||||
|
|
||||||
class ModuleCatalogItem(BaseModel):
|
class ModuleCatalogItem(BaseModel):
|
||||||
@@ -143,17 +168,21 @@ class ModuleStateUpdateRequest(BaseModel):
|
|||||||
model_config = ConfigDict(extra="forbid")
|
model_config = ConfigDict(extra="forbid")
|
||||||
|
|
||||||
enabled_modules: list[str] = Field(default_factory=list)
|
enabled_modules: list[str] = Field(default_factory=list)
|
||||||
|
change_request_id: str | None = None
|
||||||
|
|
||||||
|
|
||||||
class ModuleInstallPlanItem(BaseModel):
|
class ModuleInstallPlanItem(BaseModel):
|
||||||
model_config = ConfigDict(extra="forbid")
|
model_config = ConfigDict(extra="forbid")
|
||||||
|
|
||||||
module_id: str = Field(min_length=1, max_length=120)
|
module_id: str = Field(min_length=1, max_length=120)
|
||||||
action: Literal["install", "uninstall"]
|
action: Literal["install", "update", "uninstall"]
|
||||||
|
source: Literal["manual", "catalog"] = "manual"
|
||||||
|
catalog: dict[str, Any] | None = None
|
||||||
python_package: str | None = Field(default=None, max_length=200)
|
python_package: str | None = Field(default=None, max_length=200)
|
||||||
python_ref: str | None = Field(default=None, max_length=1000)
|
python_ref: str | None = Field(default=None, max_length=1000)
|
||||||
webui_package: str | None = Field(default=None, max_length=200)
|
webui_package: str | None = Field(default=None, max_length=200)
|
||||||
webui_ref: str | None = Field(default=None, max_length=1000)
|
webui_ref: str | None = Field(default=None, max_length=1000)
|
||||||
|
data_safety_acknowledged: bool = False
|
||||||
destroy_data: bool = False
|
destroy_data: bool = False
|
||||||
status: Literal["planned", "applied", "blocked"] = "planned"
|
status: Literal["planned", "applied", "blocked"] = "planned"
|
||||||
notes: str | None = Field(default=None, max_length=1000)
|
notes: str | None = Field(default=None, max_length=1000)
|
||||||
@@ -173,6 +202,63 @@ class ModuleInstallChecklistItem(BaseModel):
|
|||||||
detail: str | None = None
|
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):
|
class ModuleInstallPreflightResponse(BaseModel):
|
||||||
allowed: bool
|
allowed: bool
|
||||||
maintenance_mode: bool
|
maintenance_mode: bool
|
||||||
@@ -182,6 +268,8 @@ class ModuleInstallPreflightResponse(BaseModel):
|
|||||||
rollback_commands: list[str] = Field(default_factory=list)
|
rollback_commands: list[str] = Field(default_factory=list)
|
||||||
issues: list[ModuleInstallPreflightIssue] = Field(default_factory=list)
|
issues: list[ModuleInstallPreflightIssue] = Field(default_factory=list)
|
||||||
checklist: list[ModuleInstallChecklistItem] = 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):
|
class ModuleInstallPlanResponse(BaseModel):
|
||||||
@@ -213,6 +301,9 @@ class ModuleInstallerRunSummary(BaseModel):
|
|||||||
status: str
|
status: str
|
||||||
started_at: str | None = None
|
started_at: str | None = None
|
||||||
finished_at: str | None = None
|
finished_at: str | None = None
|
||||||
|
request_id: str | None = None
|
||||||
|
requested_by: str | None = None
|
||||||
|
trace: dict[str, Any] | None = None
|
||||||
rollback_status: str | None = None
|
rollback_status: str | None = None
|
||||||
supervisor_status: str | None = None
|
supervisor_status: str | None = None
|
||||||
error: str | None = None
|
error: str | None = None
|
||||||
@@ -224,6 +315,9 @@ class ModuleInstallerRunSummary(BaseModel):
|
|||||||
class ModuleInstallerRunListResponse(BaseModel):
|
class ModuleInstallerRunListResponse(BaseModel):
|
||||||
runs: list[ModuleInstallerRunSummary] = Field(default_factory=list)
|
runs: list[ModuleInstallerRunSummary] = Field(default_factory=list)
|
||||||
lock: ModuleInstallerLockStatus
|
lock: ModuleInstallerLockStatus
|
||||||
|
cursor: str | None = None
|
||||||
|
next_cursor: str | None = None
|
||||||
|
full: bool = False
|
||||||
|
|
||||||
|
|
||||||
class ModuleInstallerRequestOptions(BaseModel):
|
class ModuleInstallerRequestOptions(BaseModel):
|
||||||
@@ -256,6 +350,7 @@ class ModuleInstallerRequestItem(BaseModel):
|
|||||||
cancelled_at: str | None = None
|
cancelled_at: str | None = None
|
||||||
cancelled_by: str | None = None
|
cancelled_by: str | None = None
|
||||||
retry_of: str | None = None
|
retry_of: str | None = None
|
||||||
|
trace: dict[str, Any] | None = None
|
||||||
error: str | None = None
|
error: str | None = None
|
||||||
record_path: str | None = None
|
record_path: str | None = None
|
||||||
options: dict[str, Any] = Field(default_factory=dict)
|
options: dict[str, Any] = Field(default_factory=dict)
|
||||||
@@ -264,6 +359,9 @@ class ModuleInstallerRequestItem(BaseModel):
|
|||||||
class ModuleInstallerRequestListResponse(BaseModel):
|
class ModuleInstallerRequestListResponse(BaseModel):
|
||||||
requests: list[ModuleInstallerRequestItem] = Field(default_factory=list)
|
requests: list[ModuleInstallerRequestItem] = Field(default_factory=list)
|
||||||
daemon: ModuleInstallerDaemonStatus
|
daemon: ModuleInstallerDaemonStatus
|
||||||
|
cursor: str | None = None
|
||||||
|
next_cursor: str | None = None
|
||||||
|
full: bool = False
|
||||||
|
|
||||||
|
|
||||||
class ModuleInstallerRequestCreateRequest(BaseModel):
|
class ModuleInstallerRequestCreateRequest(BaseModel):
|
||||||
@@ -272,16 +370,44 @@ class ModuleInstallerRequestCreateRequest(BaseModel):
|
|||||||
options: ModuleInstallerRequestOptions = Field(default_factory=ModuleInstallerRequestOptions)
|
options: ModuleInstallerRequestOptions = Field(default_factory=ModuleInstallerRequestOptions)
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleInterfaceProviderItem(BaseModel):
|
||||||
|
name: str
|
||||||
|
version: str
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleInterfaceRequirementItem(BaseModel):
|
||||||
|
name: str
|
||||||
|
version_min: str | None = None
|
||||||
|
version_max_exclusive: str | None = None
|
||||||
|
optional: bool = False
|
||||||
|
|
||||||
|
|
||||||
class ModulePackageCatalogItem(BaseModel):
|
class ModulePackageCatalogItem(BaseModel):
|
||||||
module_id: str
|
module_id: str
|
||||||
name: str
|
name: str
|
||||||
description: str | None = None
|
description: str | None = None
|
||||||
version: str | None = None
|
version: str | None = None
|
||||||
action: Literal["install", "uninstall"] = "install"
|
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_package: str | None = None
|
||||||
python_ref: str | None = None
|
python_ref: str | None = None
|
||||||
webui_package: str | None = None
|
webui_package: str | None = None
|
||||||
webui_ref: str | None = None
|
webui_ref: str | None = None
|
||||||
|
provides_interfaces: list[ModuleInterfaceProviderItem] = Field(default_factory=list)
|
||||||
|
requires_interfaces: list[ModuleInterfaceRequirementItem] = Field(default_factory=list)
|
||||||
license_features: list[str] = Field(default_factory=list)
|
license_features: list[str] = Field(default_factory=list)
|
||||||
license_allowed: bool = True
|
license_allowed: bool = True
|
||||||
license_enforced: bool = False
|
license_enforced: bool = False
|
||||||
@@ -291,6 +417,27 @@ class ModulePackageCatalogItem(BaseModel):
|
|||||||
tags: list[str] = Field(default_factory=list)
|
tags: list[str] = Field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleLicenseDiagnostics(BaseModel):
|
||||||
|
configured: bool = False
|
||||||
|
valid: bool = True
|
||||||
|
path: str | None = None
|
||||||
|
license_id: str | None = None
|
||||||
|
subject: str | None = None
|
||||||
|
features: list[str] = Field(default_factory=list)
|
||||||
|
valid_from: str | None = None
|
||||||
|
valid_until: str | None = None
|
||||||
|
signed: bool = False
|
||||||
|
trusted: bool = False
|
||||||
|
key_id: str | None = None
|
||||||
|
enforced: bool = False
|
||||||
|
allowed: bool = True
|
||||||
|
required_features: list[str] = Field(default_factory=list)
|
||||||
|
missing_features: list[str] = Field(default_factory=list)
|
||||||
|
expires_in_days: int | None = None
|
||||||
|
reason: str | None = None
|
||||||
|
guidance: list[str] = Field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
class ModulePackageCatalogResponse(BaseModel):
|
class ModulePackageCatalogResponse(BaseModel):
|
||||||
modules: list[ModulePackageCatalogItem] = Field(default_factory=list)
|
modules: list[ModulePackageCatalogItem] = Field(default_factory=list)
|
||||||
configured: bool = False
|
configured: bool = False
|
||||||
@@ -298,13 +445,17 @@ class ModulePackageCatalogResponse(BaseModel):
|
|||||||
path: str | None = None
|
path: str | None = None
|
||||||
source: str | None = None
|
source: str | None = None
|
||||||
source_type: str | None = None
|
source_type: str | None = None
|
||||||
|
cache_used: bool = False
|
||||||
|
cache_path: str | None = None
|
||||||
channel: str | None = None
|
channel: str | None = None
|
||||||
sequence: int | None = None
|
sequence: int | None = None
|
||||||
generated_at: str | None = None
|
generated_at: str | None = None
|
||||||
|
not_before: str | None = None
|
||||||
expires_at: str | None = None
|
expires_at: str | None = None
|
||||||
signed: bool = False
|
signed: bool = False
|
||||||
trusted: bool = False
|
trusted: bool = False
|
||||||
key_id: str | None = None
|
key_id: str | None = None
|
||||||
|
license: ModuleLicenseDiagnostics = Field(default_factory=ModuleLicenseDiagnostics)
|
||||||
warnings: list[str] = Field(default_factory=list)
|
warnings: list[str] = Field(default_factory=list)
|
||||||
error: str | None = None
|
error: str | None = None
|
||||||
|
|
||||||
@@ -313,6 +464,7 @@ class ModuleInstallPlanUpdateRequest(BaseModel):
|
|||||||
model_config = ConfigDict(extra="forbid")
|
model_config = ConfigDict(extra="forbid")
|
||||||
|
|
||||||
items: list[ModuleInstallPlanItem] = Field(default_factory=list)
|
items: list[ModuleInstallPlanItem] = Field(default_factory=list)
|
||||||
|
change_request_id: str | None = None
|
||||||
|
|
||||||
|
|
||||||
class GovernanceAssignment(BaseModel):
|
class GovernanceAssignment(BaseModel):
|
||||||
@@ -338,6 +490,14 @@ class GovernanceTemplateListResponse(BaseModel):
|
|||||||
templates: list[GovernanceTemplateItem]
|
templates: list[GovernanceTemplateItem]
|
||||||
|
|
||||||
|
|
||||||
|
class GovernanceTemplateListDeltaResponse(BaseModel):
|
||||||
|
templates: list[GovernanceTemplateItem] = Field(default_factory=list)
|
||||||
|
deleted: list[DeltaDeletedItem] = Field(default_factory=list)
|
||||||
|
watermark: str | None = None
|
||||||
|
has_more: bool = False
|
||||||
|
full: bool = False
|
||||||
|
|
||||||
|
|
||||||
class GovernanceTemplateCreateRequest(BaseModel):
|
class GovernanceTemplateCreateRequest(BaseModel):
|
||||||
model_config = ConfigDict(extra="forbid")
|
model_config = ConfigDict(extra="forbid")
|
||||||
|
|
||||||
@@ -348,6 +508,7 @@ class GovernanceTemplateCreateRequest(BaseModel):
|
|||||||
permissions: list[str] = Field(default_factory=list)
|
permissions: list[str] = Field(default_factory=list)
|
||||||
is_active: bool = True
|
is_active: bool = True
|
||||||
assignments: list[GovernanceAssignment] = Field(default_factory=list)
|
assignments: list[GovernanceAssignment] = Field(default_factory=list)
|
||||||
|
change_request_id: str | None = None
|
||||||
|
|
||||||
|
|
||||||
class GovernanceTemplateUpdateRequest(BaseModel):
|
class GovernanceTemplateUpdateRequest(BaseModel):
|
||||||
@@ -358,3 +519,4 @@ class GovernanceTemplateUpdateRequest(BaseModel):
|
|||||||
permissions: list[str] = Field(default_factory=list)
|
permissions: list[str] = Field(default_factory=list)
|
||||||
is_active: bool = True
|
is_active: bool = True
|
||||||
assignments: list[GovernanceAssignment] = Field(default_factory=list)
|
assignments: list[GovernanceAssignment] = Field(default_factory=list)
|
||||||
|
change_request_id: str | None = None
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -13,7 +13,7 @@ def new_uuid() -> str:
|
|||||||
|
|
||||||
|
|
||||||
class GovernanceTemplate(Base, TimestampMixin):
|
class GovernanceTemplate(Base, TimestampMixin):
|
||||||
__tablename__ = "governance_templates"
|
__tablename__ = "admin_governance_templates"
|
||||||
__table_args__ = (UniqueConstraint("kind", "slug", name="uq_governance_templates_kind_slug"),)
|
__table_args__ = (UniqueConstraint("kind", "slug", name="uq_governance_templates_kind_slug"),)
|
||||||
|
|
||||||
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
||||||
@@ -26,12 +26,12 @@ class GovernanceTemplate(Base, TimestampMixin):
|
|||||||
|
|
||||||
|
|
||||||
class GovernanceTemplateAssignment(Base, TimestampMixin):
|
class GovernanceTemplateAssignment(Base, TimestampMixin):
|
||||||
__tablename__ = "governance_template_assignments"
|
__tablename__ = "admin_governance_template_assignments"
|
||||||
__table_args__ = (UniqueConstraint("template_id", "tenant_id", name="uq_governance_template_tenant"),)
|
__table_args__ = (UniqueConstraint("template_id", "tenant_id", name="uq_governance_template_tenant"),)
|
||||||
|
|
||||||
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
||||||
template_id: Mapped[str] = mapped_column(ForeignKey("governance_templates.id", ondelete="CASCADE"), nullable=False, index=True)
|
template_id: Mapped[str] = mapped_column(ForeignKey("admin_governance_templates.id", ondelete="CASCADE"), nullable=False, index=True)
|
||||||
tenant_id: Mapped[str] = mapped_column(ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False, index=True)
|
tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True)
|
||||||
mode: Mapped[str] = mapped_column(String(20), default="available", nullable=False)
|
mode: Mapped[str] = mapped_column(String(20), default="available", nullable=False)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from govoplan_core.core.access import (
|
|||||||
)
|
)
|
||||||
from govoplan_core.core.runtime import get_registry
|
from govoplan_core.core.runtime import get_registry
|
||||||
from govoplan_core.security.permissions import validate_tenant_permissions
|
from govoplan_core.security.permissions import validate_tenant_permissions
|
||||||
from govoplan_tenancy.backend.db.models import Tenant
|
from govoplan_core.tenancy.scope import Tenant
|
||||||
|
|
||||||
TEMPLATE_KINDS = {"group", "role"}
|
TEMPLATE_KINDS = {"group", "role"}
|
||||||
ASSIGNMENT_MODES = {"available", "required"}
|
ASSIGNMENT_MODES = {"available", "required"}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from govoplan_admin.backend.db import models as admin_models # noqa: F401 - populate Admin ORM metadata
|
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.module_guards import persistent_table_uninstall_guard
|
||||||
from govoplan_core.core.modules import MigrationSpec, ModuleContext, ModuleManifest
|
from govoplan_core.core.modules import MigrationSpec, ModuleContext, ModuleManifest
|
||||||
from govoplan_core.db.base import Base
|
from govoplan_core.db.base import Base
|
||||||
@@ -16,8 +17,8 @@ def _route_factory(context: ModuleContext):
|
|||||||
manifest = ModuleManifest(
|
manifest = ModuleManifest(
|
||||||
id="admin",
|
id="admin",
|
||||||
name="Admin",
|
name="Admin",
|
||||||
version="0.1.5",
|
version="0.1.7",
|
||||||
dependencies=("access",),
|
required_capabilities=(CAPABILITY_AUTH_PRINCIPAL_RESOLVER, CAPABILITY_AUTH_PERMISSION_EVALUATOR),
|
||||||
route_factory=_route_factory,
|
route_factory=_route_factory,
|
||||||
migration_spec=MigrationSpec(module_id="admin", metadata=Base.metadata),
|
migration_spec=MigrationSpec(module_id="admin", metadata=Base.metadata),
|
||||||
uninstall_guard_providers=(
|
uninstall_guard_providers=(
|
||||||
|
|||||||
+3
-3
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@govoplan/admin-webui",
|
"name": "@govoplan/admin-webui",
|
||||||
"version": "0.1.5",
|
"version": "0.1.7",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
@@ -13,8 +13,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@govoplan/core-webui": "^0.1.5",
|
"@govoplan/core-webui": "^0.1.7",
|
||||||
"lucide-react": "^0.555.0",
|
"lucide-react": "^1.23.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-router-dom": "^7.1.1"
|
"react-router-dom": "^7.1.1"
|
||||||
|
|||||||
+342
-15
@@ -1,4 +1,4 @@
|
|||||||
import type { ApiSettings } from "@govoplan/core-webui";
|
import type { ApiSettings, DeltaDeletedItem } from "@govoplan/core-webui";
|
||||||
import { apiFetch } from "@govoplan/core-webui";
|
import { apiFetch } from "@govoplan/core-webui";
|
||||||
|
|
||||||
export type PermissionItem = {
|
export type PermissionItem = {
|
||||||
@@ -75,6 +75,12 @@ export type MaintenanceMode = {
|
|||||||
message?: string | null;
|
message?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type LanguagePackage = {
|
||||||
|
code: string;
|
||||||
|
label: string;
|
||||||
|
native_label?: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
export type SystemSettingsItem = {
|
export type SystemSettingsItem = {
|
||||||
default_locale: string;
|
default_locale: string;
|
||||||
allow_tenant_custom_groups: boolean;
|
allow_tenant_custom_groups: boolean;
|
||||||
@@ -82,9 +88,20 @@ export type SystemSettingsItem = {
|
|||||||
allow_tenant_api_keys: boolean;
|
allow_tenant_api_keys: boolean;
|
||||||
privacy_retention_policy: PrivacyRetentionPolicy;
|
privacy_retention_policy: PrivacyRetentionPolicy;
|
||||||
maintenance_mode: MaintenanceMode;
|
maintenance_mode: MaintenanceMode;
|
||||||
|
available_languages: LanguagePackage[];
|
||||||
|
enabled_language_codes: string[];
|
||||||
settings: Record<string, unknown>;
|
settings: Record<string, unknown>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type SystemSettingsDeltaSections = Partial<{
|
||||||
|
defaults: Pick<SystemSettingsItem, "default_locale">;
|
||||||
|
tenant_capabilities: Pick<SystemSettingsItem, "allow_tenant_custom_groups" | "allow_tenant_custom_roles" | "allow_tenant_api_keys">;
|
||||||
|
languages: Pick<SystemSettingsItem, "available_languages" | "enabled_language_codes">;
|
||||||
|
privacy_retention_policy: SystemSettingsItem["privacy_retention_policy"];
|
||||||
|
maintenance_mode: SystemSettingsItem["maintenance_mode"];
|
||||||
|
settings: SystemSettingsItem["settings"];
|
||||||
|
}>;
|
||||||
|
|
||||||
export type SystemSettingsUpdatePayload = {
|
export type SystemSettingsUpdatePayload = {
|
||||||
default_locale: string;
|
default_locale: string;
|
||||||
allow_tenant_custom_groups: boolean;
|
allow_tenant_custom_groups: boolean;
|
||||||
@@ -92,8 +109,106 @@ export type SystemSettingsUpdatePayload = {
|
|||||||
allow_tenant_api_keys: boolean;
|
allow_tenant_api_keys: boolean;
|
||||||
privacy_retention_policy?: PrivacyRetentionPolicy | null;
|
privacy_retention_policy?: PrivacyRetentionPolicy | null;
|
||||||
maintenance_mode?: MaintenanceMode | null;
|
maintenance_mode?: MaintenanceMode | null;
|
||||||
|
available_languages?: LanguagePackage[] | null;
|
||||||
|
enabled_language_codes?: string[] | null;
|
||||||
|
change_request_id?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ConfigurationChangeRequest = {
|
||||||
|
id: string;
|
||||||
|
key: string;
|
||||||
|
label?: string;
|
||||||
|
target?: Record<string, unknown>;
|
||||||
|
dry_run: boolean;
|
||||||
|
requested_by: string;
|
||||||
|
requested_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
status: string;
|
||||||
|
approvals: Array<Record<string, unknown>>;
|
||||||
|
plan: Record<string, unknown>;
|
||||||
|
value_preview?: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ConfigurationChangeRecord = {
|
||||||
|
id: string;
|
||||||
|
version: number;
|
||||||
|
key: string;
|
||||||
|
target?: Record<string, unknown>;
|
||||||
|
actor_user_id: string;
|
||||||
|
approval_request_id?: string | null;
|
||||||
|
approval_user_ids: string[];
|
||||||
|
before?: unknown;
|
||||||
|
after?: unknown;
|
||||||
|
rollback_value?: unknown;
|
||||||
|
status: string;
|
||||||
|
created_at: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ConfigurationPackageDiagnostic = {
|
||||||
|
severity: "blocker" | "warning" | "info";
|
||||||
|
code: string;
|
||||||
|
message: string;
|
||||||
|
module_id?: string | null;
|
||||||
|
object_ref?: string | null;
|
||||||
|
resolution?: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ConfigurationPackageRequiredData = {
|
||||||
|
key: string;
|
||||||
|
label: string;
|
||||||
|
data_type: string;
|
||||||
|
required: boolean;
|
||||||
|
secret: boolean;
|
||||||
|
description?: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ConfigurationPackagePlanItem = {
|
||||||
|
action: "create" | "update" | "bind" | "skip" | "blocked" | "noop";
|
||||||
|
module_id: string;
|
||||||
|
fragment_type: string;
|
||||||
|
fragment_id?: string | null;
|
||||||
|
summary?: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ConfigurationPackageFragment = {
|
||||||
|
module_id: string;
|
||||||
|
fragment_type: string;
|
||||||
|
fragment_id?: string | null;
|
||||||
|
payload: Record<string, unknown>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ConfigurationPackageRunPayload = {
|
||||||
|
package: Record<string, unknown>;
|
||||||
|
tenant_id?: string | null;
|
||||||
|
supplied_data?: Record<string, unknown>;
|
||||||
|
change_request_id?: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
type DeltaResponseFields = {
|
||||||
|
deleted: DeltaDeletedItem[];
|
||||||
|
watermark?: string | null;
|
||||||
|
has_more: boolean;
|
||||||
|
full: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
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()}` : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SystemSettingsDeltaResponse = {
|
||||||
|
item?: SystemSettingsItem | null;
|
||||||
|
sections: SystemSettingsDeltaSections;
|
||||||
|
changed_sections: string[];
|
||||||
|
} & DeltaResponseFields;
|
||||||
|
|
||||||
|
export type ConfigurationChangesDeltaResponse = {
|
||||||
|
requests: ConfigurationChangeRequest[];
|
||||||
|
history: ConfigurationChangeRecord[];
|
||||||
|
} & DeltaResponseFields;
|
||||||
|
|
||||||
export type ModuleCatalogItem = {
|
export type ModuleCatalogItem = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -134,11 +249,14 @@ export type ModuleCatalogResponse = {
|
|||||||
|
|
||||||
export type ModuleInstallPlanItem = {
|
export type ModuleInstallPlanItem = {
|
||||||
module_id: string;
|
module_id: string;
|
||||||
action: "install" | "uninstall";
|
action: "install" | "update" | "uninstall";
|
||||||
|
source: "manual" | "catalog";
|
||||||
|
catalog?: Record<string, unknown> | null;
|
||||||
python_package?: string | null;
|
python_package?: string | null;
|
||||||
python_ref?: string | null;
|
python_ref?: string | null;
|
||||||
webui_package?: string | null;
|
webui_package?: string | null;
|
||||||
webui_ref?: string | null;
|
webui_ref?: string | null;
|
||||||
|
data_safety_acknowledged: boolean;
|
||||||
destroy_data: boolean;
|
destroy_data: boolean;
|
||||||
status: "planned" | "applied" | "blocked";
|
status: "planned" | "applied" | "blocked";
|
||||||
notes?: string | null;
|
notes?: string | null;
|
||||||
@@ -158,6 +276,63 @@ export type ModuleInstallChecklistItem = {
|
|||||||
detail?: string | null;
|
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 = {
|
export type ModuleInstallPreflight = {
|
||||||
allowed: boolean;
|
allowed: boolean;
|
||||||
maintenance_mode: boolean;
|
maintenance_mode: boolean;
|
||||||
@@ -167,6 +342,8 @@ export type ModuleInstallPreflight = {
|
|||||||
rollback_commands: string[];
|
rollback_commands: string[];
|
||||||
issues: ModuleInstallPreflightIssue[];
|
issues: ModuleInstallPreflightIssue[];
|
||||||
checklist: ModuleInstallChecklistItem[];
|
checklist: ModuleInstallChecklistItem[];
|
||||||
|
target_plan: ModuleInstallTargetItem[];
|
||||||
|
migration_plan: ModuleMigrationExecutionPlan;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ModuleInstallPlanResponse = {
|
export type ModuleInstallPlanResponse = {
|
||||||
@@ -196,6 +373,9 @@ export type ModuleInstallerRunSummary = {
|
|||||||
status: string;
|
status: string;
|
||||||
started_at?: string | null;
|
started_at?: string | null;
|
||||||
finished_at?: string | null;
|
finished_at?: string | null;
|
||||||
|
request_id?: string | null;
|
||||||
|
requested_by?: string | null;
|
||||||
|
trace?: Record<string, unknown> | null;
|
||||||
rollback_status?: string | null;
|
rollback_status?: string | null;
|
||||||
supervisor_status?: string | null;
|
supervisor_status?: string | null;
|
||||||
error?: string | null;
|
error?: string | null;
|
||||||
@@ -207,6 +387,9 @@ export type ModuleInstallerRunSummary = {
|
|||||||
export type ModuleInstallerRunListResponse = {
|
export type ModuleInstallerRunListResponse = {
|
||||||
runs: ModuleInstallerRunSummary[];
|
runs: ModuleInstallerRunSummary[];
|
||||||
lock: ModuleInstallerLockStatus;
|
lock: ModuleInstallerLockStatus;
|
||||||
|
cursor?: string | null;
|
||||||
|
next_cursor?: string | null;
|
||||||
|
full?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ModuleInstallerRequestOptions = {
|
export type ModuleInstallerRequestOptions = {
|
||||||
@@ -235,6 +418,7 @@ export type ModuleInstallerRequestItem = {
|
|||||||
cancelled_at?: string | null;
|
cancelled_at?: string | null;
|
||||||
cancelled_by?: string | null;
|
cancelled_by?: string | null;
|
||||||
retry_of?: string | null;
|
retry_of?: string | null;
|
||||||
|
trace?: Record<string, unknown> | null;
|
||||||
error?: string | null;
|
error?: string | null;
|
||||||
record_path?: string | null;
|
record_path?: string | null;
|
||||||
options: Record<string, unknown>;
|
options: Record<string, unknown>;
|
||||||
@@ -243,6 +427,21 @@ export type ModuleInstallerRequestItem = {
|
|||||||
export type ModuleInstallerRequestListResponse = {
|
export type ModuleInstallerRequestListResponse = {
|
||||||
requests: ModuleInstallerRequestItem[];
|
requests: ModuleInstallerRequestItem[];
|
||||||
daemon: ModuleInstallerDaemonStatus;
|
daemon: ModuleInstallerDaemonStatus;
|
||||||
|
cursor?: string | null;
|
||||||
|
next_cursor?: string | null;
|
||||||
|
full?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ModuleInterfaceProviderItem = {
|
||||||
|
name: string;
|
||||||
|
version: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ModuleInterfaceRequirementItem = {
|
||||||
|
name: string;
|
||||||
|
version_min?: string | null;
|
||||||
|
version_max_exclusive?: string | null;
|
||||||
|
optional: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ModulePackageCatalogItem = {
|
export type ModulePackageCatalogItem = {
|
||||||
@@ -250,11 +449,27 @@ export type ModulePackageCatalogItem = {
|
|||||||
name: string;
|
name: string;
|
||||||
description?: string | null;
|
description?: string | null;
|
||||||
version?: 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_package?: string | null;
|
||||||
python_ref?: string | null;
|
python_ref?: string | null;
|
||||||
webui_package?: string | null;
|
webui_package?: string | null;
|
||||||
webui_ref?: string | null;
|
webui_ref?: string | null;
|
||||||
|
provides_interfaces: ModuleInterfaceProviderItem[];
|
||||||
|
requires_interfaces: ModuleInterfaceRequirementItem[];
|
||||||
license_features: string[];
|
license_features: string[];
|
||||||
license_allowed: boolean;
|
license_allowed: boolean;
|
||||||
license_enforced: boolean;
|
license_enforced: boolean;
|
||||||
@@ -264,6 +479,27 @@ export type ModulePackageCatalogItem = {
|
|||||||
tags: string[];
|
tags: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ModuleLicenseDiagnostics = {
|
||||||
|
configured: boolean;
|
||||||
|
valid: boolean;
|
||||||
|
path?: string | null;
|
||||||
|
license_id?: string | null;
|
||||||
|
subject?: string | null;
|
||||||
|
features: string[];
|
||||||
|
valid_from?: string | null;
|
||||||
|
valid_until?: string | null;
|
||||||
|
signed: boolean;
|
||||||
|
trusted: boolean;
|
||||||
|
key_id?: string | null;
|
||||||
|
enforced: boolean;
|
||||||
|
allowed: boolean;
|
||||||
|
required_features: string[];
|
||||||
|
missing_features: string[];
|
||||||
|
expires_in_days?: number | null;
|
||||||
|
reason?: string | null;
|
||||||
|
guidance: string[];
|
||||||
|
};
|
||||||
|
|
||||||
export type ModulePackageCatalogResponse = {
|
export type ModulePackageCatalogResponse = {
|
||||||
modules: ModulePackageCatalogItem[];
|
modules: ModulePackageCatalogItem[];
|
||||||
configured: boolean;
|
configured: boolean;
|
||||||
@@ -271,13 +507,17 @@ export type ModulePackageCatalogResponse = {
|
|||||||
path?: string | null;
|
path?: string | null;
|
||||||
source?: string | null;
|
source?: string | null;
|
||||||
source_type?: string | null;
|
source_type?: string | null;
|
||||||
|
cache_used: boolean;
|
||||||
|
cache_path?: string | null;
|
||||||
channel?: string | null;
|
channel?: string | null;
|
||||||
sequence?: number | null;
|
sequence?: number | null;
|
||||||
generated_at?: string | null;
|
generated_at?: string | null;
|
||||||
|
not_before?: string | null;
|
||||||
expires_at?: string | null;
|
expires_at?: string | null;
|
||||||
signed: boolean;
|
signed: boolean;
|
||||||
trusted: boolean;
|
trusted: boolean;
|
||||||
key_id?: string | null;
|
key_id?: string | null;
|
||||||
|
license: ModuleLicenseDiagnostics;
|
||||||
warnings: string[];
|
warnings: string[];
|
||||||
error?: string | null;
|
error?: string | null;
|
||||||
};
|
};
|
||||||
@@ -319,6 +559,11 @@ export function fetchSystemSettings(settings: ApiSettings): Promise<SystemSettin
|
|||||||
return apiFetch(settings, "/api/v1/admin/system/settings");
|
return apiFetch(settings, "/api/v1/admin/system/settings");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function fetchSystemSettingsDelta(settings: ApiSettings, options: { since?: string | null; limit?: number } = {}): Promise<SystemSettingsDeltaResponse> {
|
||||||
|
const suffix = deltaSuffix(options);
|
||||||
|
return apiFetch(settings, `/api/v1/admin/system/settings/delta${suffix}`);
|
||||||
|
}
|
||||||
|
|
||||||
export function updateSystemSettings(settings: ApiSettings, payload: SystemSettingsUpdatePayload): Promise<SystemSettingsItem> {
|
export function updateSystemSettings(settings: ApiSettings, payload: SystemSettingsUpdatePayload): Promise<SystemSettingsItem> {
|
||||||
return apiFetch(settings, "/api/v1/admin/system/settings", { method: "PATCH", body: JSON.stringify(payload) });
|
return apiFetch(settings, "/api/v1/admin/system/settings", { method: "PATCH", body: JSON.stringify(payload) });
|
||||||
}
|
}
|
||||||
@@ -327,10 +572,10 @@ export function fetchModuleCatalog(settings: ApiSettings): Promise<ModuleCatalog
|
|||||||
return apiFetch(settings, "/api/v1/admin/system/modules");
|
return apiFetch(settings, "/api/v1/admin/system/modules");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateModuleState(settings: ApiSettings, enabledModules: string[]): Promise<ModuleCatalogResponse> {
|
export function updateModuleState(settings: ApiSettings, enabledModules: string[], changeRequestId?: string | null): Promise<ModuleCatalogResponse> {
|
||||||
return apiFetch(settings, "/api/v1/admin/system/modules", {
|
return apiFetch(settings, "/api/v1/admin/system/modules", {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
body: JSON.stringify({ enabled_modules: enabledModules })
|
body: JSON.stringify({ enabled_modules: enabledModules, change_request_id: changeRequestId ?? null })
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,12 +583,20 @@ export function fetchModuleInstallPlan(settings: ApiSettings): Promise<ModuleIns
|
|||||||
return apiFetch(settings, "/api/v1/admin/system/modules/install-plan");
|
return apiFetch(settings, "/api/v1/admin/system/modules/install-plan");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchModuleInstallerRuns(settings: ApiSettings): Promise<ModuleInstallerRunListResponse> {
|
export function fetchModuleInstallerRuns(settings: ApiSettings, options: { page_size?: number; cursor?: string | null } = {}): Promise<ModuleInstallerRunListResponse> {
|
||||||
return apiFetch(settings, "/api/v1/admin/system/modules/install-runs");
|
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}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchModuleInstallerRequests(settings: ApiSettings): Promise<ModuleInstallerRequestListResponse> {
|
export function fetchModuleInstallerRequests(settings: ApiSettings, options: { page_size?: number; cursor?: string | null } = {}): Promise<ModuleInstallerRequestListResponse> {
|
||||||
return apiFetch(settings, "/api/v1/admin/system/modules/install-requests");
|
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}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createModuleInstallerRequest(settings: ApiSettings, options: ModuleInstallerRequestOptions): Promise<ModuleInstallerRequestItem> {
|
export function createModuleInstallerRequest(settings: ApiSettings, options: ModuleInstallerRequestOptions): Promise<ModuleInstallerRequestItem> {
|
||||||
@@ -373,10 +626,10 @@ export function planModuleUninstall(settings: ApiSettings, moduleId: string): Pr
|
|||||||
return apiFetch(settings, `/api/v1/admin/system/modules/${encodeURIComponent(moduleId)}/uninstall-plan`, { method: "POST" });
|
return apiFetch(settings, `/api/v1/admin/system/modules/${encodeURIComponent(moduleId)}/uninstall-plan`, { method: "POST" });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateModuleInstallPlan(settings: ApiSettings, items: ModuleInstallPlanItem[]): Promise<ModuleInstallPlanResponse> {
|
export function updateModuleInstallPlan(settings: ApiSettings, items: ModuleInstallPlanItem[], changeRequestId?: string | null): Promise<ModuleInstallPlanResponse> {
|
||||||
return apiFetch(settings, "/api/v1/admin/system/modules/install-plan", {
|
return apiFetch(settings, "/api/v1/admin/system/modules/install-plan", {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
body: JSON.stringify({ items })
|
body: JSON.stringify({ items, change_request_id: changeRequestId ?? null })
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -390,14 +643,88 @@ export async function fetchGovernanceTemplates(settings: ApiSettings, kind?: "gr
|
|||||||
return response.templates;
|
return response.templates;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createGovernanceTemplate(settings: ApiSettings, payload: Omit<GovernanceTemplateItem, "id" | "created_at" | "updated_at" | "effective_permission_count">): Promise<GovernanceTemplateItem> {
|
export function createGovernanceTemplate(settings: ApiSettings, payload: Omit<GovernanceTemplateItem, "id" | "created_at" | "updated_at" | "effective_permission_count"> & { change_request_id?: string | null }): Promise<GovernanceTemplateItem> {
|
||||||
return apiFetch(settings, "/api/v1/admin/system/governance-templates", { method: "POST", body: JSON.stringify(payload) });
|
return apiFetch(settings, "/api/v1/admin/system/governance-templates", { method: "POST", body: JSON.stringify(payload) });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateGovernanceTemplate(settings: ApiSettings, templateId: string, payload: Omit<GovernanceTemplateItem, "id" | "kind" | "slug" | "created_at" | "updated_at" | "effective_permission_count">): Promise<GovernanceTemplateItem> {
|
export function updateGovernanceTemplate(settings: ApiSettings, templateId: string, payload: Omit<GovernanceTemplateItem, "id" | "kind" | "slug" | "created_at" | "updated_at" | "effective_permission_count"> & { change_request_id?: string | null }): Promise<GovernanceTemplateItem> {
|
||||||
return apiFetch(settings, `/api/v1/admin/system/governance-templates/${templateId}`, { method: "PATCH", body: JSON.stringify(payload) });
|
return apiFetch(settings, `/api/v1/admin/system/governance-templates/${templateId}`, { method: "PATCH", body: JSON.stringify(payload) });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteGovernanceTemplate(settings: ApiSettings, templateId: string): Promise<void> {
|
export function deleteGovernanceTemplate(settings: ApiSettings, templateId: string, changeRequestId?: string | null): Promise<void> {
|
||||||
return apiFetch(settings, `/api/v1/admin/system/governance-templates/${templateId}`, { method: "DELETE" });
|
const suffix = changeRequestId ? `?change_request_id=${encodeURIComponent(changeRequestId)}` : "";
|
||||||
|
return apiFetch(settings, `/api/v1/admin/system/governance-templates/${templateId}${suffix}`, { method: "DELETE" });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchConfigurationChanges(settings: ApiSettings): Promise<{ requests: ConfigurationChangeRequest[]; history: ConfigurationChangeRecord[] }> {
|
||||||
|
return apiFetch(settings, "/api/v1/admin/configuration-changes");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchConfigurationChangesDelta(settings: ApiSettings, options: { since?: string | null; limit?: number } = {}): Promise<ConfigurationChangesDeltaResponse> {
|
||||||
|
const suffix = deltaSuffix(options);
|
||||||
|
return apiFetch(settings, `/api/v1/admin/configuration-changes/delta${suffix}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createConfigurationChangeRequest(settings: ApiSettings, payload: {
|
||||||
|
key: string;
|
||||||
|
value?: unknown;
|
||||||
|
dry_run?: boolean;
|
||||||
|
target?: Record<string, unknown>;
|
||||||
|
reason?: string | null;
|
||||||
|
}): Promise<ConfigurationChangeRequest> {
|
||||||
|
const response = await apiFetch<{ request: ConfigurationChangeRequest }>(settings, "/api/v1/admin/configuration-change-requests", {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
});
|
||||||
|
return response.request;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function approveConfigurationChangeRequest(settings: ApiSettings, requestId: string, reason?: string | null): Promise<ConfigurationChangeRequest> {
|
||||||
|
const response = await apiFetch<{ request: ConfigurationChangeRequest }>(settings, `/api/v1/admin/configuration-change-requests/${encodeURIComponent(requestId)}/approve`, {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({ reason: reason ?? null })
|
||||||
|
});
|
||||||
|
return response.request;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchConfigurationPackageCatalogValidation(settings: ApiSettings): Promise<{ validation: Record<string, unknown> }> {
|
||||||
|
return apiFetch(settings, "/api/v1/admin/configuration-packages/catalog");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function dryRunConfigurationPackage(settings: ApiSettings, payload: ConfigurationPackageRunPayload): Promise<{
|
||||||
|
diagnostics: ConfigurationPackageDiagnostic[];
|
||||||
|
required_data: ConfigurationPackageRequiredData[];
|
||||||
|
plan: ConfigurationPackagePlanItem[];
|
||||||
|
}> {
|
||||||
|
return apiFetch(settings, "/api/v1/admin/configuration-packages/dry-run", {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyConfigurationPackage(settings: ApiSettings, payload: ConfigurationPackageRunPayload): Promise<{
|
||||||
|
diagnostics: ConfigurationPackageDiagnostic[];
|
||||||
|
created_refs: Record<string, string>;
|
||||||
|
updated_refs: Record<string, string>;
|
||||||
|
}> {
|
||||||
|
return apiFetch(settings, "/api/v1/admin/configuration-packages/apply", {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function exportConfigurationPackage(settings: ApiSettings, payload: {
|
||||||
|
tenant_id?: string | null;
|
||||||
|
scopes?: string[];
|
||||||
|
module_ids?: string[];
|
||||||
|
object_refs?: string[];
|
||||||
|
}): Promise<{
|
||||||
|
fragments: ConfigurationPackageFragment[];
|
||||||
|
data_requirements: ConfigurationPackageRequiredData[];
|
||||||
|
diagnostics: ConfigurationPackageDiagnostic[];
|
||||||
|
}> {
|
||||||
|
return apiFetch(settings, "/api/v1/admin/configuration-packages/export", {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { Card } from "@govoplan/core-webui";
|
|||||||
import { Button } from "@govoplan/core-webui";
|
import { Button } from "@govoplan/core-webui";
|
||||||
import { AdminPageLayout, adminErrorMessage } from "@govoplan/core-webui";
|
import { AdminPageLayout, adminErrorMessage } from "@govoplan/core-webui";
|
||||||
|
|
||||||
export default function AdminOverviewPanel({ settings, onSelect, availableSections }: { settings: ApiSettings; onSelect: (section: string) => void; availableSections: ReadonlySet<string> }) {
|
export default function AdminOverviewPanel({ settings, onSelect, availableSections }: {settings: ApiSettings;onSelect: (section: string) => void;availableSections: ReadonlySet<string>;}) {
|
||||||
const [overview, setOverview] = useState<AdminOverview | null>(null);
|
const [overview, setOverview] = useState<AdminOverview | null>(null);
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
@@ -13,81 +13,112 @@ export default function AdminOverviewPanel({ settings, onSelect, availableSectio
|
|||||||
async function load() {
|
async function load() {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError("");
|
setError("");
|
||||||
try { setOverview(await fetchAdminOverview(settings)); }
|
try {setOverview(await fetchAdminOverview(settings));}
|
||||||
catch (err) { setError(adminErrorMessage(err)); }
|
catch (err) {setError(adminErrorMessage(err));} finally
|
||||||
finally { setLoading(false); }
|
{setLoading(false);}
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => { void load(); }, [settings.accessToken, settings.apiBaseUrl]);
|
useEffect(() => {void load();}, [settings.accessToken, settings.apiBaseUrl]);
|
||||||
|
|
||||||
const hasSystemMetrics = Boolean(overview && [
|
const hasSystemMetrics = Boolean(overview && [
|
||||||
overview.tenant_count,
|
overview.tenant_count,
|
||||||
overview.system_account_count,
|
overview.system_account_count,
|
||||||
overview.system_group_template_count,
|
overview.system_group_template_count,
|
||||||
overview.system_role_template_count
|
overview.system_role_template_count].
|
||||||
].some((value) => value !== null && value !== undefined));
|
some((value) => value !== null && value !== undefined));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AdminPageLayout title="Administration" description="System-wide governance and tenant-local access management, separated by scope and enforced by the backend." loading={loading} error={error} actions={<Button onClick={() => void load()} disabled={loading}>Reload</Button>}>
|
<AdminPageLayout title="i18n:govoplan-admin.administration.b8be3d12" description="i18n:govoplan-admin.system_wide_governance_and_tenant_local_access_m.cda72499" loading={loading} error={error} actions={<Button onClick={() => void load()} disabled={loading}>i18n:govoplan-admin.reload.cce71553</Button>}>
|
||||||
{overview && <>
|
{overview && <>
|
||||||
{hasSystemMetrics && <>
|
{hasSystemMetrics && <>
|
||||||
<div className="admin-overview-section-label">System</div>
|
<div className="admin-overview-section-label">i18n:govoplan-admin.system.bc0792d8</div>
|
||||||
<div className="metric-grid">
|
<div className="metric-grid">
|
||||||
<Metric title="Tenants" value={overview.tenant_count ?? "—"} text="Registered tenant spaces." />
|
<Metric title="i18n:govoplan-admin.tenants.1f7ae776" value={overview.tenant_count ?? "—"} text="i18n:govoplan-admin.registered_tenant_spaces.61e17d70" />
|
||||||
<Metric title="Users" value={overview.system_account_count ?? "—"} text="Global login accounts across all tenants." />
|
<Metric title="i18n:govoplan-admin.users.57f2b181" value={overview.system_account_count ?? "—"} text="i18n:govoplan-admin.global_login_accounts_across_all_tenants.2aab129d" />
|
||||||
<Metric title="Central groups" value={overview.system_group_template_count ?? "—"} text="System-governed group definitions." />
|
<Metric title="i18n:govoplan-admin.central_groups.5c9b5b66" value={overview.system_group_template_count ?? "—"} text="i18n:govoplan-admin.system_governed_group_definitions.de8e5dc9" />
|
||||||
<Metric title="Tenant roles" value={overview.system_role_template_count ?? "—"} text="Centrally governed tenant roles." />
|
<Metric title="i18n:govoplan-admin.tenant_roles.51aca82d" value={overview.system_role_template_count ?? "—"} text="i18n:govoplan-admin.centrally_governed_tenant_roles.797c689b" />
|
||||||
</div>
|
</div>
|
||||||
</>}
|
</>}
|
||||||
|
|
||||||
{hasSystemArea(availableSections) && <Card title="System administration">
|
{hasAnySection(availableSections, platformSectionIds) && <Card title="ADMINISTRATION">
|
||||||
<div className="admin-overview-grid">
|
<div className="admin-overview-grid">
|
||||||
{availableSections.has("system-settings") && <AreaLink title="General" text="Instance defaults and tenant governance capabilities." onClick={() => onSelect("system-settings")} />}
|
{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-tenants") && <AreaLink title="Tenants" text="Create, suspend and govern tenant spaces." onClick={() => onSelect("system-tenants")} />}
|
{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-roles") && <AreaLink title="System roles" text="Instance-wide roles assigned directly to global accounts." onClick={() => onSelect("system-roles")} />}
|
{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-role-templates") && <AreaLink title="Tenant roles" text="Centrally governed tenant roles and their availability across tenants." onClick={() => onSelect("system-role-templates")} />}
|
{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-groups") && <AreaLink title="Groups" text="Group definitions made available or required in selected tenants." onClick={() => onSelect("system-groups")} />}
|
{availableSections.has("system-audit") && <AreaLink title="i18n:govoplan-admin.audit.fa1703dd" text="i18n:govoplan-admin.system_level_administrative_history.49f76723" onClick={() => onSelect("system-audit")} />}
|
||||||
{availableSections.has("system-users") && <AreaLink title="Users" text="Global accounts, tenant memberships and system-role assignments." onClick={() => onSelect("system-users")} />}
|
|
||||||
{availableSections.has("system-mail-servers") && <AreaLink title="Mail servers" text="Reusable encrypted SMTP/IMAP profiles and mail policy." onClick={() => onSelect("system-mail-servers")} />}
|
|
||||||
{availableSections.has("system-retention") && <AreaLink title="Retention" text="Instance privacy retention policy and lower-level override permissions." onClick={() => onSelect("system-retention")} />}
|
|
||||||
{availableSections.has("system-modules") && <AreaLink title="Modules" text="Installed modules, runtime state and startup state." onClick={() => onSelect("system-modules")} />}
|
|
||||||
{availableSections.has("system-audit") && <AreaLink title="Audit" text="System-level administrative history." onClick={() => onSelect("system-audit")} />}
|
|
||||||
</div>
|
</div>
|
||||||
</Card>}
|
</Card>}
|
||||||
|
|
||||||
<div className="admin-overview-section-label">Active tenant: {overview.active_tenant_name}</div>
|
{hasAnySection(availableSections, globalSectionIds) && <Card title="GLOBAL">
|
||||||
<div className="metric-grid">
|
|
||||||
<Metric title="Tenant users" value={`${overview.active_user_count}/${overview.user_count}`} text="Active and total memberships." />
|
|
||||||
<Metric title="Groups" value={overview.group_count} text="Tenant-local and centrally managed groups." />
|
|
||||||
<Metric title="Roles" value={overview.role_count} text="Tenant-local and centrally managed roles." />
|
|
||||||
<Metric title="API keys" value={overview.active_api_key_count} text="Active tenant automation credentials." />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Card title="Tenant administration">
|
|
||||||
<div className="admin-overview-grid">
|
<div className="admin-overview-grid">
|
||||||
{availableSections.has("tenant-settings") && <AreaLink title="General" text="Tenant locale and tenant-specific settings." onClick={() => onSelect("tenant-settings")} />}
|
{availableSections.has("system-tenants") && <AreaLink title="i18n:govoplan-admin.tenants.1f7ae776" text="i18n:govoplan-admin.create_suspend_and_govern_tenant_spaces.77992a39" onClick={() => onSelect("system-tenants")} />}
|
||||||
{availableSections.has("tenant-roles") && <AreaLink title="Roles" text="Tenant permission bundles and system-managed role copies." onClick={() => onSelect("tenant-roles")} />}
|
{availableSections.has("system-roles") && <AreaLink title="i18n:govoplan-admin.system_roles.a9461aa6" text="i18n:govoplan-admin.instance_wide_roles_assigned_directly_to_global_.91050488" onClick={() => onSelect("system-roles")} />}
|
||||||
{availableSections.has("tenant-groups") && <AreaLink title="Groups" text="Tenant memberships and inherited roles." onClick={() => onSelect("tenant-groups")} />}
|
{availableSections.has("system-role-templates") && <AreaLink title="i18n:govoplan-admin.tenant_roles.51aca82d" text="i18n:govoplan-admin.centrally_governed_tenant_roles_and_their_availa.d879d5d1" onClick={() => onSelect("system-role-templates")} />}
|
||||||
{availableSections.has("tenant-users") && <AreaLink title="Users" text="Membership status, groups and direct roles in the active tenant." onClick={() => onSelect("tenant-users")} />}
|
{availableSections.has("system-groups") && <AreaLink title="i18n:govoplan-admin.central_groups.5c9b5b66" text="i18n:govoplan-admin.group_definitions_made_available_or_required_in_.55402e16" onClick={() => onSelect("system-groups")} />}
|
||||||
{availableSections.has("tenant-mail-servers") && <AreaLink title="Mail servers" text="Reusable encrypted SMTP/IMAP profiles and mail policy." onClick={() => onSelect("tenant-mail-servers")} />}
|
{availableSections.has("system-users") && <AreaLink title="i18n:govoplan-admin.users.57f2b181" text="i18n:govoplan-admin.global_accounts_tenant_memberships_and_system_ro.939ed01f" onClick={() => onSelect("system-users")} />}
|
||||||
{availableSections.has("tenant-retention") && <AreaLink title="Retention" text="Tenant-level privacy retention limits inherited by owned objects." onClick={() => onSelect("tenant-retention")} />}
|
{availableSections.has("system-file-connectors") && <AreaLink title="i18n:govoplan-admin.file_connections.1e362326" text="i18n:govoplan-admin.reusable_file_server_connections_credentials_and.8e5c7d43" onClick={() => onSelect("system-file-connectors")} />}
|
||||||
{availableSections.has("tenant-api-keys") && <AreaLink title="API keys" text="Scoped automation credentials capped by owner permissions." onClick={() => onSelect("tenant-api-keys")} />}
|
{availableSections.has("system-mail-servers") && <AreaLink title="i18n:govoplan-admin.mail_servers.d627326a" text="i18n:govoplan-admin.reusable_encrypted_smtp_imap_profiles_and_mail_p.31f150d1" onClick={() => onSelect("system-mail-servers")} />}
|
||||||
{availableSections.has("tenant-audit") && <AreaLink title="Audit" text="Tenant-level administrative history only." onClick={() => onSelect("tenant-audit")} />}
|
{availableSections.has("system-retention") && <AreaLink title="i18n:govoplan-admin.retention.c7199d9e" text="i18n:govoplan-admin.instance_privacy_retention_policy_and_lower_leve.b8d14069" onClick={() => onSelect("system-retention")} />}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>}
|
||||||
|
|
||||||
|
<div className="admin-overview-section-label">i18n:govoplan-admin.active_tenant.dfadf0aa {overview.active_tenant_name}</div>
|
||||||
|
<div className="metric-grid">
|
||||||
|
<Metric title="i18n:govoplan-admin.tenant_users.cb800b38" value={`${overview.active_user_count}/${overview.user_count}`} text="i18n:govoplan-admin.active_and_total_memberships.c0c20f10" />
|
||||||
|
<Metric title="i18n:govoplan-admin.groups.ae9629f4" value={overview.group_count} text="i18n:govoplan-admin.tenant_local_and_centrally_managed_groups.4a6296b5" />
|
||||||
|
<Metric title="i18n:govoplan-admin.roles.47dcc27d" value={overview.role_count} text="i18n:govoplan-admin.tenant_local_and_centrally_managed_roles.4995a7b2" />
|
||||||
|
<Metric title="i18n:govoplan-admin.api_keys.94fcf3c2" value={overview.active_api_key_count} text="i18n:govoplan-admin.active_tenant_automation_credentials.240c659e" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{hasAnySection(availableSections, tenantSectionIds) && <Card title="TENANT">
|
||||||
|
<div className="admin-overview-grid">
|
||||||
|
{availableSections.has("tenant-roles") && <AreaLink title="i18n:govoplan-admin.roles.47dcc27d" text="i18n:govoplan-admin.tenant_permission_bundles_and_system_managed_rol.bb563bea" onClick={() => onSelect("tenant-roles")} />}
|
||||||
|
{availableSections.has("tenant-groups") && <AreaLink title="i18n:govoplan-admin.groups.ae9629f4" text="i18n:govoplan-admin.tenant_memberships_and_inherited_roles.16eda9f6" onClick={() => onSelect("tenant-groups")} />}
|
||||||
|
{availableSections.has("tenant-users") && <AreaLink title="i18n:govoplan-admin.users.57f2b181" text="i18n:govoplan-admin.membership_status_groups_and_direct_roles_in_the.ab6c10b6" onClick={() => onSelect("tenant-users")} />}
|
||||||
|
{availableSections.has("tenant-file-connectors") && <AreaLink title="i18n:govoplan-admin.file_connections.1e362326" text="i18n:govoplan-admin.reusable_file_server_connections_credentials_and.8e5c7d43" onClick={() => onSelect("tenant-file-connectors")} />}
|
||||||
|
{availableSections.has("tenant-mail-servers") && <AreaLink title="i18n:govoplan-admin.mail_servers.d627326a" text="i18n:govoplan-admin.reusable_encrypted_smtp_imap_profiles_and_mail_p.31f150d1" onClick={() => onSelect("tenant-mail-servers")} />}
|
||||||
|
{availableSections.has("tenant-api-keys") && <AreaLink title="i18n:govoplan-admin.api_keys.94fcf3c2" text="i18n:govoplan-admin.scoped_automation_credentials_capped_by_owner_pe.b3e20e54" onClick={() => onSelect("tenant-api-keys")} />}
|
||||||
|
{availableSections.has("tenant-retention") && <AreaLink title="i18n:govoplan-admin.retention.c7199d9e" text="i18n:govoplan-admin.tenant_level_privacy_retention_limits_inherited_.48f4989d" onClick={() => onSelect("tenant-retention")} />}
|
||||||
|
{availableSections.has("tenant-settings") && <AreaLink title="i18n:govoplan-admin.general.9239ee2c" text="i18n:govoplan-admin.tenant_locale_and_tenant_specific_settings.ac49c83b" onClick={() => onSelect("tenant-settings")} />}
|
||||||
|
{availableSections.has("tenant-audit") && <AreaLink title="i18n:govoplan-admin.audit.fa1703dd" text="i18n:govoplan-admin.tenant_level_administrative_history_only.55495c3c" onClick={() => onSelect("tenant-audit")} />}
|
||||||
|
</div>
|
||||||
|
</Card>}
|
||||||
|
|
||||||
|
{hasAnySection(availableSections, groupSectionIds) && <Card title="GROUP">
|
||||||
|
<div className="admin-overview-grid">
|
||||||
|
{availableSections.has("tenant-group-file-connectors") && <AreaLink title="i18n:govoplan-admin.file_connections.1e362326" text="i18n:govoplan-admin.group_file_connector_policy_limits" onClick={() => onSelect("tenant-group-file-connectors")} />}
|
||||||
|
{availableSections.has("tenant-group-mail-servers") && <AreaLink title="i18n:govoplan-admin.mail_servers.d627326a" text="i18n:govoplan-admin.group_mail_server_policy_limits" onClick={() => onSelect("tenant-group-mail-servers")} />}
|
||||||
|
{availableSections.has("tenant-group-retention") && <AreaLink title="i18n:govoplan-admin.retention.c7199d9e" text="i18n:govoplan-admin.group_retention_policy_limits" onClick={() => onSelect("tenant-group-retention")} />}
|
||||||
|
</div>
|
||||||
|
</Card>}
|
||||||
|
|
||||||
|
{hasAnySection(availableSections, userSectionIds) && <Card title="USER">
|
||||||
|
<div className="admin-overview-grid">
|
||||||
|
{availableSections.has("tenant-user-file-connectors") && <AreaLink title="i18n:govoplan-admin.file_connections.1e362326" text="i18n:govoplan-admin.user_file_connector_policy_limits" onClick={() => onSelect("tenant-user-file-connectors")} />}
|
||||||
|
{availableSections.has("tenant-user-mail-servers") && <AreaLink title="i18n:govoplan-admin.mail_servers.d627326a" text="i18n:govoplan-admin.user_mail_server_policy_limits" onClick={() => onSelect("tenant-user-mail-servers")} />}
|
||||||
|
{availableSections.has("tenant-user-retention") && <AreaLink title="i18n:govoplan-admin.retention.c7199d9e" text="i18n:govoplan-admin.user_retention_policy_limits" onClick={() => onSelect("tenant-user-retention")} />}
|
||||||
|
</div>
|
||||||
|
</Card>}
|
||||||
</>}
|
</>}
|
||||||
</AdminPageLayout>
|
</AdminPageLayout>);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasSystemArea(sections: ReadonlySet<string>): boolean {
|
const platformSectionIds = ["system-modules", "system-configuration-packages", "system-settings", "system-configuration-changes", "system-audit"];
|
||||||
return ["system-settings", "system-tenants", "system-roles", "system-role-templates", "system-groups", "system-users", "system-mail-servers", "system-retention", "system-modules", "system-audit"].some((section) => sections.has(section));
|
const globalSectionIds = ["system-tenants", "system-roles", "system-role-templates", "system-groups", "system-users", "system-file-connectors", "system-mail-servers", "system-retention"];
|
||||||
|
const tenantSectionIds = ["tenant-roles", "tenant-groups", "tenant-users", "tenant-file-connectors", "tenant-mail-servers", "tenant-api-keys", "tenant-retention", "tenant-settings", "tenant-audit"];
|
||||||
|
const groupSectionIds = ["tenant-group-file-connectors", "tenant-group-mail-servers", "tenant-group-retention"];
|
||||||
|
const userSectionIds = ["tenant-user-file-connectors", "tenant-user-mail-servers", "tenant-user-retention"];
|
||||||
|
|
||||||
|
function hasAnySection(sections: ReadonlySet<string>, candidates: readonly string[]): boolean {
|
||||||
|
return candidates.some((section) => sections.has(section));
|
||||||
}
|
}
|
||||||
|
|
||||||
function Metric({ title, value, text }: { title: string; value: string | number; text: string }) {
|
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 <Card title={title}><strong className="module-big-number">{value}</strong><p className="muted">{text}</p></Card>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function AreaLink({ title, text, onClick }: { title: string; text: string; onClick: () => void }) {
|
function AreaLink({ title, text, onClick }: {title: string;text: string;onClick: () => void;}) {
|
||||||
return <button className="admin-overview-link" onClick={onClick}><strong>{title}</strong><span>{text}</span></button>;
|
return <button className="admin-overview-link" onClick={onClick}><strong>{title}</strong><span>{text}</span></button>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,170 @@
|
|||||||
|
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 {
|
||||||
|
approveConfigurationChangeRequest,
|
||||||
|
fetchConfigurationChangesDelta,
|
||||||
|
type ConfigurationChangeRecord,
|
||||||
|
type ConfigurationChangeRequest } from
|
||||||
|
"../../api/admin";
|
||||||
|
|
||||||
|
const DELTA_KEY = "admin:configuration-changes";
|
||||||
|
|
||||||
|
export default function ConfigurationChangesPanel({ settings, canApprove }: {settings: ApiSettings;canApprove: boolean;}) {
|
||||||
|
const [requests, setRequests] = useState<ConfigurationChangeRequest[]>([]);
|
||||||
|
const [history, setHistory] = useState<ConfigurationChangeRecord[]>([]);
|
||||||
|
const requestsRef = useRef<ConfigurationChangeRequest[]>([]);
|
||||||
|
const historyRef = useRef<ConfigurationChangeRecord[]>([]);
|
||||||
|
const { getDeltaWatermark, setDeltaWatermark, resetDeltaWatermark } = useDeltaWatermarks();
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [busyId, setBusyId] = useState("");
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
const [message, setMessage] = useState("");
|
||||||
|
|
||||||
|
async function load() {
|
||||||
|
setLoading(true);
|
||||||
|
setError("");
|
||||||
|
try {
|
||||||
|
let nextWatermark = getDeltaWatermark(DELTA_KEY);
|
||||||
|
let nextRequests = requestsRef.current;
|
||||||
|
let nextHistory = historyRef.current;
|
||||||
|
let hasMore = false;
|
||||||
|
do {
|
||||||
|
const payload = await fetchConfigurationChangesDelta(settings, { since: nextWatermark });
|
||||||
|
nextRequests = payload.full ? payload.requests : mergeDeltaRows(nextRequests, payload.requests, payload.deleted, (request) => request.id, { sort: sortConfigurationRequests });
|
||||||
|
nextHistory = payload.full ? payload.history : mergeDeltaRows(nextHistory, payload.history, payload.deleted, (record) => record.id, { sort: sortConfigurationHistory });
|
||||||
|
nextWatermark = payload.watermark ?? null;
|
||||||
|
hasMore = payload.has_more;
|
||||||
|
} while (hasMore);
|
||||||
|
requestsRef.current = nextRequests;
|
||||||
|
historyRef.current = nextHistory;
|
||||||
|
setRequests(nextRequests);
|
||||||
|
setHistory(nextHistory);
|
||||||
|
setDeltaWatermark(DELTA_KEY, nextWatermark);
|
||||||
|
} catch (err) {
|
||||||
|
setError(adminErrorMessage(err));
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
requestsRef.current = [];
|
||||||
|
historyRef.current = [];
|
||||||
|
resetDeltaWatermark(DELTA_KEY);
|
||||||
|
void load();
|
||||||
|
}, [settings.accessToken, settings.apiBaseUrl, resetDeltaWatermark]);
|
||||||
|
|
||||||
|
async function approve(request: ConfigurationChangeRequest) {
|
||||||
|
setBusyId(request.id);
|
||||||
|
setError("");
|
||||||
|
setMessage("");
|
||||||
|
try {
|
||||||
|
await approveConfigurationChangeRequest(settings, request.id);
|
||||||
|
setMessage(i18nMessage("i18n:govoplan-admin.approved_value.52da808b", { value0: request.key }));
|
||||||
|
await load();
|
||||||
|
} catch (err) {
|
||||||
|
setError(adminErrorMessage(err));
|
||||||
|
} finally {
|
||||||
|
setBusyId("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const pending = useMemo(() => requests.filter((item) => item.status !== "applied" && item.status !== "rejected"), [requests]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AdminPageLayout
|
||||||
|
title="i18n:govoplan-admin.configuration_changes.82933bbb"
|
||||||
|
description="i18n:govoplan-admin.safety_controlled_configuration_requests_approva.e8259509"
|
||||||
|
loading={loading}
|
||||||
|
error={error}
|
||||||
|
success={message}
|
||||||
|
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>
|
||||||
|
</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>
|
||||||
|
</Card>
|
||||||
|
</AdminPageLayout>);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function statusTone(status: string): string {
|
||||||
|
if (status === "approved" || status === "applied") return "success";
|
||||||
|
if (status === "pending_approval") return "warning";
|
||||||
|
if (status === "rejected") return "error";
|
||||||
|
return "inactive";
|
||||||
|
}
|
||||||
|
|
||||||
|
function targetLabel(target?: Record<string, unknown>): string {
|
||||||
|
if (!target || !Object.keys(target).length) return "-";
|
||||||
|
return Object.entries(target).map(([key, value]) => `${key}: ${String(value)}`).join(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortConfigurationRequests(left: ConfigurationChangeRequest, right: ConfigurationChangeRequest): number {
|
||||||
|
return new Date(right.updated_at || right.requested_at).getTime() - new Date(left.updated_at || left.requested_at).getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortConfigurationHistory(left: ConfigurationChangeRecord, right: ConfigurationChangeRecord): number {
|
||||||
|
return right.version - left.version;
|
||||||
|
}
|
||||||
@@ -0,0 +1,374 @@
|
|||||||
|
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 {
|
||||||
|
applyConfigurationPackage,
|
||||||
|
createConfigurationChangeRequest,
|
||||||
|
dryRunConfigurationPackage,
|
||||||
|
exportConfigurationPackage,
|
||||||
|
fetchConfigurationPackageCatalogValidation,
|
||||||
|
type ConfigurationChangeRequest,
|
||||||
|
type ConfigurationPackageDiagnostic,
|
||||||
|
type ConfigurationPackagePlanItem,
|
||||||
|
type ConfigurationPackageRequiredData } from
|
||||||
|
"../../api/admin";
|
||||||
|
|
||||||
|
const SAMPLE_ACCESS_PACKAGE = {
|
||||||
|
package_id: "govoplan.access.minimal-office",
|
||||||
|
name: "i18n:govoplan-admin.minimal_office_access.af48f49a",
|
||||||
|
version: "0.1.0",
|
||||||
|
required_modules: [{ module_id: "access" }],
|
||||||
|
required_capabilities: ["configuration.provider", "access.configuration"],
|
||||||
|
fragments: [
|
||||||
|
{
|
||||||
|
module_id: "access",
|
||||||
|
fragment_type: "roles",
|
||||||
|
payload: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
slug: "case-clerk",
|
||||||
|
name: "i18n:govoplan-admin.case_clerk.b78a314a",
|
||||||
|
description: "i18n:govoplan-admin.handles_incoming_administrative_work.dc80c349",
|
||||||
|
permissions: ["admin:users:read"]
|
||||||
|
}]
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
module_id: "access",
|
||||||
|
fragment_type: "groups",
|
||||||
|
payload: { items: [{ slug: "front-office", name: "i18n:govoplan-admin.front_office.d9dcfee1" }] }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
module_id: "access",
|
||||||
|
fragment_type: "group_role_assignments",
|
||||||
|
payload: { items: [{ group: "front-office", role: "case-clerk" }] }
|
||||||
|
}]
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
type DryRunResult = {
|
||||||
|
diagnostics: ConfigurationPackageDiagnostic[];
|
||||||
|
required_data: ConfigurationPackageRequiredData[];
|
||||||
|
plan: ConfigurationPackagePlanItem[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type ApplyResult = {
|
||||||
|
diagnostics: ConfigurationPackageDiagnostic[];
|
||||||
|
created_refs: Record<string, string>;
|
||||||
|
updated_refs: Record<string, string>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function ConfigurationPackagesPanel({ settings, canWrite }: {settings: ApiSettings;canWrite: boolean;}) {
|
||||||
|
const [catalogValidation, setCatalogValidation] = useState<Record<string, unknown> | null>(null);
|
||||||
|
const [packageText, setPackageText] = useState(() => JSON.stringify(SAMPLE_ACCESS_PACKAGE, null, 2));
|
||||||
|
const [tenantId, setTenantId] = useState("");
|
||||||
|
const [suppliedDataText, setSuppliedDataText] = useState("{}");
|
||||||
|
const [changeRequestId, setChangeRequestId] = useState("");
|
||||||
|
const [dryRun, setDryRun] = useState<DryRunResult | null>(null);
|
||||||
|
const [applyResult, setApplyResult] = useState<ApplyResult | null>(null);
|
||||||
|
const [exportText, setExportText] = useState("");
|
||||||
|
const [lastRequest, setLastRequest] = useState<ConfigurationChangeRequest | null>(null);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [busy, setBusy] = useState("");
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
const [message, setMessage] = useState("");
|
||||||
|
|
||||||
|
const parsedPackage = useMemo(() => parseObject(packageText), [packageText]);
|
||||||
|
const parsedSuppliedData = useMemo(() => parseObject(suppliedDataText), [suppliedDataText]);
|
||||||
|
const canRun = Boolean(parsedPackage.value && parsedSuppliedData.value);
|
||||||
|
|
||||||
|
async function loadCatalog() {
|
||||||
|
setLoading(true);
|
||||||
|
setError("");
|
||||||
|
try {
|
||||||
|
const response = await fetchConfigurationPackageCatalogValidation(settings);
|
||||||
|
setCatalogValidation(response.validation);
|
||||||
|
} catch (err) {
|
||||||
|
setError(adminErrorMessage(err));
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {void loadCatalog();}, [settings.accessToken, settings.apiBaseUrl]);
|
||||||
|
|
||||||
|
async function runDryRun() {
|
||||||
|
const manifest = requireParsedPackage();
|
||||||
|
const suppliedData = requireParsedSuppliedData();
|
||||||
|
if (!manifest || !suppliedData) return;
|
||||||
|
setBusy("dry-run");
|
||||||
|
setError("");
|
||||||
|
setMessage("");
|
||||||
|
setApplyResult(null);
|
||||||
|
try {
|
||||||
|
const result = await dryRunConfigurationPackage(settings, runPayload(manifest, suppliedData));
|
||||||
|
setDryRun(result);
|
||||||
|
setMessage(result.diagnostics.some((item) => item.severity === "blocker") ? "i18n:govoplan-admin.preflight_finished_with_blockers.7e2ca12b" : "i18n:govoplan-admin.preflight_passed.c0c99055");
|
||||||
|
} catch (err) {
|
||||||
|
setError(adminErrorMessage(err));
|
||||||
|
} finally {
|
||||||
|
setBusy("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function requestApproval() {
|
||||||
|
const manifest = requireParsedPackage();
|
||||||
|
if (!manifest) return;
|
||||||
|
setBusy("approval");
|
||||||
|
setError("");
|
||||||
|
setMessage("");
|
||||||
|
try {
|
||||||
|
const request = await createConfigurationChangeRequest(settings, {
|
||||||
|
key: "configuration_packages.apply",
|
||||||
|
value: manifest,
|
||||||
|
dry_run: true,
|
||||||
|
target: tenantId.trim() ? { tenant_id: tenantId.trim() } : {},
|
||||||
|
reason: "i18n:govoplan-admin.configuration_package_apply.c270e5f3"
|
||||||
|
});
|
||||||
|
setLastRequest(request);
|
||||||
|
setChangeRequestId(request.id);
|
||||||
|
setMessage(i18nMessage("i18n:govoplan-admin.change_request_created_value.4af6d3d2", { value0: request.id }));
|
||||||
|
} catch (err) {
|
||||||
|
setError(adminErrorMessage(err));
|
||||||
|
} finally {
|
||||||
|
setBusy("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function applyPackage() {
|
||||||
|
const manifest = requireParsedPackage();
|
||||||
|
const suppliedData = requireParsedSuppliedData();
|
||||||
|
if (!manifest || !suppliedData) return;
|
||||||
|
setBusy("apply");
|
||||||
|
setError("");
|
||||||
|
setMessage("");
|
||||||
|
try {
|
||||||
|
const result = await applyConfigurationPackage(settings, runPayload(manifest, suppliedData, changeRequestId.trim() || null));
|
||||||
|
setApplyResult(result);
|
||||||
|
setMessage(result.diagnostics.some((item) => item.severity === "blocker") ? "i18n:govoplan-admin.apply_finished_with_blockers.78487a12" : "i18n:govoplan-admin.package_applied.63782ce7");
|
||||||
|
} catch (err) {
|
||||||
|
setError(adminErrorMessage(err));
|
||||||
|
} finally {
|
||||||
|
setBusy("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function exportAccessPackage() {
|
||||||
|
setBusy("export");
|
||||||
|
setError("");
|
||||||
|
setMessage("");
|
||||||
|
try {
|
||||||
|
const result = await exportConfigurationPackage(settings, {
|
||||||
|
tenant_id: tenantId.trim() || null,
|
||||||
|
module_ids: ["access"],
|
||||||
|
scopes: tenantId.trim() ? ["tenant"] : ["system"]
|
||||||
|
});
|
||||||
|
const exported = {
|
||||||
|
package_id: tenantId.trim() ? "govoplan.export.access-tenant" : "govoplan.export.access-system",
|
||||||
|
name: tenantId.trim() ? "i18n:govoplan-admin.exported_tenant_access_configuration.9122c85c" : "i18n:govoplan-admin.exported_system_access_configuration.2e1c6f4c",
|
||||||
|
version: "0.1.0",
|
||||||
|
required_modules: [{ module_id: "access" }],
|
||||||
|
required_capabilities: ["configuration.provider", "access.configuration"],
|
||||||
|
fragments: result.fragments,
|
||||||
|
data_requirements: result.data_requirements
|
||||||
|
};
|
||||||
|
setExportText(JSON.stringify(exported, null, 2));
|
||||||
|
setMessage(result.diagnostics.some((item) => item.severity === "blocker") ? "i18n:govoplan-admin.export_finished_with_blockers.e2f70611" : "i18n:govoplan-admin.access_configuration_exported.098f200d");
|
||||||
|
} catch (err) {
|
||||||
|
setError(adminErrorMessage(err));
|
||||||
|
} finally {
|
||||||
|
setBusy("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function runPayload(manifest: Record<string, unknown>, suppliedData: Record<string, unknown>, requestId?: string | null) {
|
||||||
|
return {
|
||||||
|
package: manifest,
|
||||||
|
tenant_id: tenantId.trim() || null,
|
||||||
|
supplied_data: suppliedData,
|
||||||
|
change_request_id: requestId ?? null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function requireParsedPackage(): Record<string, unknown> | null {
|
||||||
|
if (!parsedPackage.value) {
|
||||||
|
setError(parsedPackage.error || "i18n:govoplan-admin.package_json_must_be_an_object.db825bb3");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return parsedPackage.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function requireParsedSuppliedData(): Record<string, unknown> | null {
|
||||||
|
if (!parsedSuppliedData.value) {
|
||||||
|
setError(parsedSuppliedData.error || "i18n:govoplan-admin.supplied_data_json_must_be_an_object.b265eb92");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return parsedSuppliedData.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AdminPageLayout
|
||||||
|
title="i18n:govoplan-admin.configuration_packages.eb2f05f1"
|
||||||
|
description="i18n:govoplan-admin.import_preflight_approve_apply_and_export_module.29aec929"
|
||||||
|
loading={loading}
|
||||||
|
error={error || parsedPackage.error || parsedSuppliedData.error || ""}
|
||||||
|
success={message}
|
||||||
|
actions={<Button onClick={() => void loadCatalog()} disabled={loading || Boolean(busy)}><RefreshCw size={16} /> i18n:govoplan-admin.reload.cce71553</Button>}>
|
||||||
|
|
||||||
|
<Card title="i18n:govoplan-admin.catalog.4a88d27b">
|
||||||
|
<div className="button-row compact-actions">
|
||||||
|
<StatusBadge status={catalogValidation?.valid ? "success" : catalogValidation?.configured ? "warning" : "inactive"} label={catalogValidation?.valid ? "i18n:govoplan-admin.valid.a4aefa35" : catalogValidation?.configured ? "i18n:govoplan-admin.needs_attention.a126722e" : "i18n:govoplan-admin.not_configured.811931bb"} />
|
||||||
|
{catalogValidation?.signed !== undefined && <StatusBadge status={catalogValidation.signed ? "success" : "inactive"} label={catalogValidation.signed ? "i18n:govoplan-admin.signed.6e3665d8" : "i18n:govoplan-admin.unsigned.e91344ea"} />}
|
||||||
|
{catalogValidation?.trusted !== undefined && <StatusBadge status={catalogValidation.trusted ? "success" : "warning"} label={catalogValidation.trusted ? "i18n:govoplan-admin.trusted.99f7ed54" : "i18n:govoplan-admin.untrusted.cdc7838a"} />}
|
||||||
|
</div>
|
||||||
|
<pre className="code-panel module-install-plan-commands">{JSON.stringify(catalogValidation ?? {}, null, 2)}</pre>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card title="i18n:govoplan-admin.package.7431e3df">
|
||||||
|
<div className="module-installer-request-grid">
|
||||||
|
<label className="wide"><span>i18n:govoplan-admin.tenant_id.59eba244</span><input value={tenantId} onChange={(event) => setTenantId(event.target.value)} placeholder="active tenant" /></label>
|
||||||
|
<label className="wide"><span>i18n:govoplan-admin.change_request_id.96ee3239</span><input value={changeRequestId} onChange={(event) => setChangeRequestId(event.target.value)} placeholder="cfgreq-..." /></label>
|
||||||
|
<label className="wide"><span>i18n:govoplan-admin.package_json.a2b10f38</span><textarea rows={18} value={packageText} onChange={(event) => setPackageText(event.target.value)} /></label>
|
||||||
|
<label className="wide"><span>i18n:govoplan-admin.supplied_data_json.6932bfb7</span><textarea rows={6} value={suppliedDataText} onChange={(event) => setSuppliedDataText(event.target.value)} /></label>
|
||||||
|
</div>
|
||||||
|
<div className="button-row compact-actions">
|
||||||
|
<Button onClick={() => void runDryRun()} disabled={!canRun || Boolean(busy)}><Play size={16} /> i18n:govoplan-admin.dry_run.3d14659c</Button>
|
||||||
|
<Button onClick={() => void requestApproval()} disabled={!canWrite || !parsedPackage.value || Boolean(busy)}><Check size={16} /> i18n:govoplan-admin.request_approval.6245aea1</Button>
|
||||||
|
<Button variant="primary" onClick={() => void applyPackage()} disabled={!canWrite || !canRun || Boolean(busy)}><Check size={16} /> i18n:govoplan-admin.apply.cfea419c</Button>
|
||||||
|
<Button onClick={() => void exportAccessPackage()} disabled={Boolean(busy)}><Download size={16} /> i18n:govoplan-admin.export_access.9a2eb91e</Button>
|
||||||
|
</div>
|
||||||
|
{lastRequest && <p className="muted small-note">i18n:govoplan-admin.last_request.4508ef35 {lastRequest.id} ({lastRequest.status})</p>}
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{dryRun && <Card title="i18n:govoplan-admin.preflight.8016a487">
|
||||||
|
<PackageDiagnostics diagnostics={dryRun.diagnostics} />
|
||||||
|
<RequiredData items={dryRun.required_data} />
|
||||||
|
<PackagePlan items={dryRun.plan} />
|
||||||
|
</Card>}
|
||||||
|
|
||||||
|
{applyResult && <Card title="i18n:govoplan-admin.apply_result.0fde1c3c">
|
||||||
|
<PackageDiagnostics diagnostics={applyResult.diagnostics} />
|
||||||
|
<ReferenceMap title="i18n:govoplan-admin.created.accf40c8" refs={applyResult.created_refs} />
|
||||||
|
<ReferenceMap title="i18n:govoplan-admin.updated.f2f8570d" refs={applyResult.updated_refs} />
|
||||||
|
</Card>}
|
||||||
|
|
||||||
|
{exportText && <Card title="i18n:govoplan-admin.export.f3e4fadb">
|
||||||
|
<textarea className="code-panel module-install-plan-commands" rows={16} value={exportText} onChange={(event) => setExportText(event.target.value)} />
|
||||||
|
</Card>}
|
||||||
|
</AdminPageLayout>);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseObject(text: string): {value: Record<string, unknown> | null;error: string;} {
|
||||||
|
try {
|
||||||
|
const value = JSON.parse(text) as unknown;
|
||||||
|
if (!value || typeof value !== "object" || Array.isArray(value)) return { value: null, error: "i18n:govoplan-admin.json_must_be_an_object.848569c9" };
|
||||||
|
return { value: value as Record<string, unknown>, error: "" };
|
||||||
|
} catch (err) {
|
||||||
|
return { value: null, error: err instanceof Error ? err.message : "i18n:govoplan-admin.invalid_json.01ccb74f" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function PackageDiagnostics({ diagnostics }: {diagnostics: ConfigurationPackageDiagnostic[];}) {
|
||||||
|
if (diagnostics.length === 0) return <p className="muted">i18n:govoplan-admin.no_diagnostics.2b6e2630</p>;
|
||||||
|
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>);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function RequiredData({ items }: {items: ConfigurationPackageRequiredData[];}) {
|
||||||
|
if (items.length === 0) return null;
|
||||||
|
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>
|
||||||
|
</>);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function PackagePlan({ items }: {items: ConfigurationPackagePlanItem[];}) {
|
||||||
|
if (items.length === 0) return <p className="muted">i18n:govoplan-admin.no_plan_items.7108c582</p>;
|
||||||
|
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>
|
||||||
|
</>);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function ReferenceMap({ title, refs }: {title: string;refs: Record<string, string>;}) {
|
||||||
|
const entries = Object.entries(refs);
|
||||||
|
if (entries.length === 0) return null;
|
||||||
|
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>
|
||||||
|
</>);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function diagnosticTone(severity: ConfigurationPackageDiagnostic["severity"]): string {
|
||||||
|
if (severity === "blocker") return "error";
|
||||||
|
if (severity === "warning") return "warning";
|
||||||
|
return "inactive";
|
||||||
|
}
|
||||||
|
|
||||||
|
function planTone(action: ConfigurationPackagePlanItem["action"]): string {
|
||||||
|
if (action === "blocked") return "error";
|
||||||
|
if (action === "skip" || action === "noop") return "inactive";
|
||||||
|
if (action === "update" || action === "bind") return "warning";
|
||||||
|
return "success";
|
||||||
|
}
|
||||||
@@ -17,9 +17,9 @@ import {
|
|||||||
type GovernanceAssignment,
|
type GovernanceAssignment,
|
||||||
type GovernanceTemplateItem,
|
type GovernanceTemplateItem,
|
||||||
type PermissionItem,
|
type PermissionItem,
|
||||||
type TenantAdminItem
|
type TenantAdminItem } from
|
||||||
} from "../../api/admin";
|
"../../api/admin";
|
||||||
import { AdminIconButton, AdminPageLayout, AdminSelectionList, adminErrorMessage, joinLabels } from "@govoplan/core-webui";
|
import { AdminIconButton, AdminPageLayout, AdminSelectionList, adminErrorMessage, joinLabels, i18nMessage, useUnsavedDraftGuard } from "@govoplan/core-webui";
|
||||||
|
|
||||||
const emptyDraft = {
|
const emptyDraft = {
|
||||||
slug: "",
|
slug: "",
|
||||||
@@ -35,12 +35,12 @@ export default function GovernanceTemplatesPanel({
|
|||||||
kind,
|
kind,
|
||||||
canWrite,
|
canWrite,
|
||||||
onAuthRefresh
|
onAuthRefresh
|
||||||
}: {
|
|
||||||
settings: ApiSettings;
|
|
||||||
kind: "group" | "role";
|
|
||||||
canWrite: boolean;
|
|
||||||
onAuthRefresh: () => Promise<void>;
|
|
||||||
}) {
|
}: {settings: ApiSettings;kind: "group" | "role";canWrite: boolean;onAuthRefresh: () => Promise<void>;}) {
|
||||||
const [items, setItems] = useState<GovernanceTemplateItem[]>([]);
|
const [items, setItems] = useState<GovernanceTemplateItem[]>([]);
|
||||||
const [tenants, setTenants] = useState<TenantAdminItem[]>([]);
|
const [tenants, setTenants] = useState<TenantAdminItem[]>([]);
|
||||||
const [permissions, setPermissions] = useState<PermissionItem[]>([]);
|
const [permissions, setPermissions] = useState<PermissionItem[]>([]);
|
||||||
@@ -48,10 +48,18 @@ export default function GovernanceTemplatesPanel({
|
|||||||
const [viewing, setViewing] = useState<GovernanceTemplateItem | null>(null);
|
const [viewing, setViewing] = useState<GovernanceTemplateItem | null>(null);
|
||||||
const [deleting, setDeleting] = useState<GovernanceTemplateItem | null>(null);
|
const [deleting, setDeleting] = useState<GovernanceTemplateItem | null>(null);
|
||||||
const [draft, setDraft] = useState(emptyDraft);
|
const [draft, setDraft] = useState(emptyDraft);
|
||||||
|
const [savedDraftKey, setSavedDraftKey] = useState(draftKey(emptyDraft));
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [busy, setBusy] = useState(false);
|
const [busy, setBusy] = useState(false);
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
const [success, setSuccess] = useState("");
|
const [success, setSuccess] = useState("");
|
||||||
|
const dirty = editing !== null && draftKey(draft) !== savedDraftKey;
|
||||||
|
|
||||||
|
useUnsavedDraftGuard({
|
||||||
|
dirty,
|
||||||
|
onSave: save,
|
||||||
|
onDiscard: closeEditor
|
||||||
|
});
|
||||||
|
|
||||||
async function load() {
|
async function load() {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -60,34 +68,43 @@ export default function GovernanceTemplatesPanel({
|
|||||||
const [nextItems, nextTenants, nextPermissions] = await Promise.all([
|
const [nextItems, nextTenants, nextPermissions] = await Promise.all([
|
||||||
fetchGovernanceTemplates(settings, kind),
|
fetchGovernanceTemplates(settings, kind),
|
||||||
fetchTenants(settings),
|
fetchTenants(settings),
|
||||||
kind === "role" ? fetchPermissionCatalog(settings) : Promise.resolve([])
|
kind === "role" ? fetchPermissionCatalog(settings) : Promise.resolve([])]
|
||||||
]);
|
);
|
||||||
setItems(nextItems);
|
setItems(nextItems);
|
||||||
setTenants(nextTenants);
|
setTenants(nextTenants);
|
||||||
setPermissions(nextPermissions.filter((item) => item.level === "tenant"));
|
setPermissions(nextPermissions.filter((item) => item.level === "tenant"));
|
||||||
} catch (err) { setError(adminErrorMessage(err)); }
|
} catch (err) {setError(adminErrorMessage(err));} finally
|
||||||
finally { setLoading(false); }
|
{setLoading(false);}
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => { void load(); }, [settings.accessToken, settings.apiBaseUrl, kind]);
|
useEffect(() => {void load();}, [settings.accessToken, settings.apiBaseUrl, kind]);
|
||||||
|
|
||||||
function openCreate() {
|
function openCreate() {
|
||||||
setDraft(emptyDraft);
|
setDraft(emptyDraft);
|
||||||
|
setSavedDraftKey(draftKey(emptyDraft));
|
||||||
setEditing("new");
|
setEditing("new");
|
||||||
}
|
}
|
||||||
|
|
||||||
function openEdit(item: GovernanceTemplateItem) {
|
function openEdit(item: GovernanceTemplateItem) {
|
||||||
setDraft({
|
const nextDraft = {
|
||||||
slug: item.slug,
|
slug: item.slug,
|
||||||
name: item.name,
|
name: item.name,
|
||||||
description: item.description || "",
|
description: item.description || "",
|
||||||
isActive: item.is_active,
|
isActive: item.is_active,
|
||||||
permissions: item.permissions,
|
permissions: item.permissions,
|
||||||
assignments: item.assignments
|
assignments: item.assignments
|
||||||
});
|
};
|
||||||
|
setDraft(nextDraft);
|
||||||
|
setSavedDraftKey(draftKey(nextDraft));
|
||||||
setEditing(item);
|
setEditing(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function closeEditor() {
|
||||||
|
setEditing(null);
|
||||||
|
setDraft(emptyDraft);
|
||||||
|
setSavedDraftKey(draftKey(emptyDraft));
|
||||||
|
}
|
||||||
|
|
||||||
function assignmentMode(tenantId: string): "none" | "available" | "required" {
|
function assignmentMode(tenantId: string): "none" | "available" | "required" {
|
||||||
return draft.assignments.find((item) => item.tenant_id === tenantId)?.mode ?? "none";
|
return draft.assignments.find((item) => item.tenant_id === tenantId)?.mode ?? "none";
|
||||||
}
|
}
|
||||||
@@ -95,13 +112,13 @@ export default function GovernanceTemplatesPanel({
|
|||||||
function setAssignment(tenantId: string, mode: "none" | "available" | "required") {
|
function setAssignment(tenantId: string, mode: "none" | "available" | "required") {
|
||||||
setDraft((current) => ({
|
setDraft((current) => ({
|
||||||
...current,
|
...current,
|
||||||
assignments: mode === "none"
|
assignments: mode === "none" ?
|
||||||
? current.assignments.filter((item) => item.tenant_id !== tenantId)
|
current.assignments.filter((item) => item.tenant_id !== tenantId) :
|
||||||
: [...current.assignments.filter((item) => item.tenant_id !== tenantId), { tenant_id: tenantId, mode }]
|
[...current.assignments.filter((item) => item.tenant_id !== tenantId), { tenant_id: tenantId, mode }]
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function save() {
|
async function save(): Promise<boolean> {
|
||||||
setBusy(true);
|
setBusy(true);
|
||||||
setError("");
|
setError("");
|
||||||
try {
|
try {
|
||||||
@@ -115,7 +132,7 @@ export default function GovernanceTemplatesPanel({
|
|||||||
is_active: draft.isActive,
|
is_active: draft.isActive,
|
||||||
assignments: draft.assignments
|
assignments: draft.assignments
|
||||||
});
|
});
|
||||||
setSuccess(`${kind === "group" ? "Group" : "Role"} template created.`);
|
setSuccess(i18nMessage("i18n:govoplan-admin.value_template_created.d68a5166", { value0: kind === "group" ? "i18n:govoplan-admin.group.171a0606" : "i18n:govoplan-admin.role.c3f104d1" }));
|
||||||
} else if (editing) {
|
} else if (editing) {
|
||||||
await updateGovernanceTemplate(settings, editing.id, {
|
await updateGovernanceTemplate(settings, editing.id, {
|
||||||
name: draft.name,
|
name: draft.name,
|
||||||
@@ -124,13 +141,14 @@ export default function GovernanceTemplatesPanel({
|
|||||||
is_active: draft.isActive,
|
is_active: draft.isActive,
|
||||||
assignments: draft.assignments
|
assignments: draft.assignments
|
||||||
});
|
});
|
||||||
setSuccess(`${draft.name} updated and synchronized to assigned tenants.`);
|
setSuccess(i18nMessage("i18n:govoplan-admin.value_updated_and_synchronized_to_assigned_tenan.d136eef2", { value0: draft.name }));
|
||||||
}
|
}
|
||||||
setEditing(null);
|
setEditing(null);
|
||||||
await load();
|
await load();
|
||||||
await onAuthRefresh();
|
await onAuthRefresh();
|
||||||
} catch (err) { setError(adminErrorMessage(err)); }
|
return true;
|
||||||
finally { setBusy(false); }
|
} catch (err) {setError(adminErrorMessage(err));return false;} finally
|
||||||
|
{setBusy(false);}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function remove() {
|
async function remove() {
|
||||||
@@ -139,47 +157,47 @@ export default function GovernanceTemplatesPanel({
|
|||||||
setError("");
|
setError("");
|
||||||
try {
|
try {
|
||||||
await deleteGovernanceTemplate(settings, deleting.id);
|
await deleteGovernanceTemplate(settings, deleting.id);
|
||||||
setSuccess(`${deleting.name} deleted.`);
|
setSuccess(i18nMessage("i18n:govoplan-admin.value_deleted.3c4bf574", { value0: deleting.name }));
|
||||||
setDeleting(null);
|
setDeleting(null);
|
||||||
await load();
|
await load();
|
||||||
await onAuthRefresh();
|
await onAuthRefresh();
|
||||||
} catch (err) { setError(adminErrorMessage(err)); }
|
} catch (err) {setError(adminErrorMessage(err));} finally
|
||||||
finally { setBusy(false); }
|
{setBusy(false);}
|
||||||
}
|
}
|
||||||
|
|
||||||
const columns = useMemo<DataGridColumn<GovernanceTemplateItem>[]>(() => [
|
const columns = useMemo<DataGridColumn<GovernanceTemplateItem>[]>(() => [
|
||||||
{
|
{
|
||||||
id: "template", header: kind === "group" ? "Group template" : "Tenant role", width: "minmax(240px, 1.2fr)", minWidth: 210, resizable: true, sticky: "start", sortable: true, filterable: true,
|
id: "template", header: kind === "group" ? "i18n:govoplan-admin.group_template.973e0fa6" : "i18n:govoplan-admin.tenant_role.6b53115d", width: "minmax(240px, 1.2fr)", minWidth: 210, resizable: true, sticky: "start", sortable: true, filterable: true,
|
||||||
value: (row) => `${row.name} ${row.slug}`,
|
value: (row) => `${row.name} ${row.slug}`,
|
||||||
render: (row) => <div><strong>{row.name}</strong><div className="muted small-note">{row.slug}</div></div>
|
render: (row) => <div><strong>{row.name}</strong><div className="muted small-note">{row.slug}</div></div>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "tenants", header: "Tenant availability", width: 320, minWidth: 210, maxWidth: 640, resizable: true, fill: true, sortable: true, filterable: true,
|
id: "tenants", header: "i18n:govoplan-admin.tenant_availability.067a4f31", width: 320, minWidth: 210, maxWidth: 640, resizable: true, fill: true, sortable: true, filterable: true,
|
||||||
value: (row) => row.assignments.map((assignment) => tenants.find((tenant) => tenant.id === assignment.tenant_id)?.name || assignment.tenant_id).join(", ") || "—",
|
value: (row) => row.assignments.map((assignment) => tenants.find((tenant) => tenant.id === assignment.tenant_id)?.name || assignment.tenant_id).join(", ") || "—",
|
||||||
render: (row) => row.assignments.length ? row.assignments.map((assignment) => {
|
render: (row) => row.assignments.length ? row.assignments.map((assignment) => {
|
||||||
const tenant = tenants.find((item) => item.id === assignment.tenant_id);
|
const tenant = tenants.find((item) => item.id === assignment.tenant_id);
|
||||||
return `${tenant?.name || assignment.tenant_id} (${assignment.mode})`;
|
return i18nMessage("i18n:govoplan-admin.value_value.c189e8bc", { value0: tenant?.name || assignment.tenant_id, value1: assignment.mode });
|
||||||
}).join(", ") : "—"
|
}).join(", ") : "—"
|
||||||
},
|
},
|
||||||
...(kind === "role" ? [{
|
...(kind === "role" ? [{
|
||||||
id: "permissions", header: "Permissions", width: 120, resizable: false, sortable: true, filterable: true, filterType: "integer" as const,
|
id: "permissions", header: "i18n:govoplan-admin.permissions.d06d5557", width: 120, resizable: false, sortable: true, filterable: true, filterType: "integer" as const,
|
||||||
value: (row: GovernanceTemplateItem) => row.effective_permission_count
|
value: (row: GovernanceTemplateItem) => row.effective_permission_count
|
||||||
}] : []),
|
}] : []),
|
||||||
{ id: "status", header: "Status", 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: "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: "Actions", width: 150, sticky: "end", resizable: false, align: "right",
|
id: "actions", header: "i18n:govoplan-admin.actions.c3cd636a", width: 150, sticky: "end", resizable: false, align: "right",
|
||||||
render: (row) => <div className="admin-icon-actions">
|
render: (row) => <div className="admin-icon-actions">
|
||||||
<AdminIconButton label={`Inspect ${row.name}`} icon={<Search />} onClick={() => setViewing(row)} />
|
<AdminIconButton label={i18nMessage("i18n:govoplan-admin.inspect_value.9d5d1071", { value0: row.name })} icon={<Search />} onClick={() => setViewing(row)} />
|
||||||
<AdminIconButton label={`Edit ${row.name}`} icon={<Pencil />} onClick={() => openEdit(row)} disabled={!canWrite} />
|
<AdminIconButton label={i18nMessage("i18n:govoplan-admin.edit_value.fad75899", { value0: row.name })} icon={<Pencil />} onClick={() => openEdit(row)} disabled={!canWrite} />
|
||||||
<AdminIconButton label={`Delete ${row.name}`} icon={<Trash2 />} variant="danger" onClick={() => setDeleting(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>
|
</div>
|
||||||
}
|
}],
|
||||||
], [canWrite, kind, tenants]);
|
[canWrite, kind, tenants]);
|
||||||
|
|
||||||
const title = kind === "group" ? "Central groups" : "Tenant roles";
|
const title = kind === "group" ? "i18n:govoplan-admin.central_groups.5c9b5b66" : "i18n:govoplan-admin.tenant_roles.51aca82d";
|
||||||
const description = kind === "group"
|
const description = kind === "group" ?
|
||||||
? "Centrally defined group identities that are provisioned into selected tenants as available or required definitions. Membership remains tenant-local."
|
"i18n:govoplan-admin.centrally_defined_group_identities_that_are_prov.7761fce9" :
|
||||||
: "Centrally governed tenant roles that are provisioned into selected tenants as available or required definitions.";
|
"i18n:govoplan-admin.centrally_governed_tenant_roles_that_are_provisi.8739fca3";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -189,41 +207,45 @@ export default function GovernanceTemplatesPanel({
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
error={error}
|
error={error}
|
||||||
success={success}
|
success={success}
|
||||||
actions={<><Button onClick={() => void load()} disabled={loading}>Reload</Button><AdminIconButton label={kind === "group" ? "Add group template" : "Add tenant role"} icon={<Plus />} variant="primary" onClick={openCreate} disabled={!canWrite} /></>}
|
actions={<><Button onClick={() => void load()} disabled={loading}>i18n:govoplan-admin.reload.cce71553</Button><AdminIconButton label={kind === "group" ? "i18n:govoplan-admin.add_group_template.b74d8f0f" : "i18n:govoplan-admin.add_tenant_role.fcd904ea"} icon={<Plus />} variant="primary" onClick={openCreate} disabled={!canWrite} /></>}>
|
||||||
>
|
|
||||||
<div className="admin-table-surface">
|
<div className="admin-table-surface">
|
||||||
<DataGrid id={`admin-system-${kind}-templates-v3`} rows={items} columns={columns} initialFit="container" getRowKey={(row) => row.id} emptyText={`No central ${kind} templates found.`} />
|
<DataGrid id={`admin-system-${kind}-templates-v3`} rows={items} columns={columns} initialFit="container" getRowKey={(row) => row.id} emptyText={i18nMessage("i18n:govoplan-admin.no_central_value_templates_found.081149fa", { value0: kind })} />
|
||||||
</div>
|
</div>
|
||||||
</AdminPageLayout>
|
</AdminPageLayout>
|
||||||
|
|
||||||
<Dialog
|
<Dialog
|
||||||
open={editing !== null}
|
open={editing !== null}
|
||||||
title={editing === "new" ? (kind === "group" ? "Create group template" : "Create tenant role") : (kind === "group" ? "Edit group template" : "Edit tenant role")}
|
title={editing === "new" ? kind === "group" ? "i18n:govoplan-admin.create_group_template.72407248" : "i18n:govoplan-admin.create_tenant_role.f58db104" : kind === "group" ? "i18n:govoplan-admin.edit_group_template.9bc72d21" : "i18n:govoplan-admin.edit_tenant_role.c15a260d"}
|
||||||
onClose={() => !busy && setEditing(null)}
|
onClose={() => !busy && setEditing(null)}
|
||||||
className="admin-dialog admin-dialog-wide"
|
className="admin-dialog admin-dialog-wide"
|
||||||
footer={<><Button onClick={() => setEditing(null)} disabled={busy}>Cancel</Button><Button variant="primary" onClick={() => void save()} disabled={busy || !draft.name.trim() || !draft.slug.trim()}>{busy ? "Saving…" : kind === "group" ? "Save template" : "Save tenant role"}</Button></>}
|
footer={<><Button onClick={() => setEditing(null)} disabled={busy}>i18n:govoplan-admin.cancel.77dfd213</Button><Button variant="primary" onClick={() => void save()} disabled={busy || !draft.name.trim() || !draft.slug.trim()}>{busy ? "i18n:govoplan-admin.saving.56a2285c" : kind === "group" ? "i18n:govoplan-admin.save_template.0885fab2" : "i18n:govoplan-admin.save_tenant_role.8fc0d37d"}</Button></>}>
|
||||||
>
|
|
||||||
<div className="admin-form-grid two-columns">
|
<div className="admin-form-grid two-columns">
|
||||||
<FormField label="Name"><input value={draft.name} onChange={(event) => setDraft({ ...draft, name: event.target.value })} /></FormField>
|
<FormField label="i18n:govoplan-admin.name.709a2322"><input value={draft.name} onChange={(event) => setDraft({ ...draft, name: event.target.value })} /></FormField>
|
||||||
<FormField label="Slug"><input value={draft.slug} disabled={editing !== "new"} onChange={(event) => setDraft({ ...draft, slug: event.target.value })} /></FormField>
|
<FormField label="i18n:govoplan-admin.slug.094da9b9"><input value={draft.slug} disabled={editing !== "new"} onChange={(event) => setDraft({ ...draft, slug: event.target.value })} /></FormField>
|
||||||
<FormField label="Status"><select value={draft.isActive ? "active" : "inactive"} onChange={(event) => setDraft({ ...draft, isActive: event.target.value === "active" })}><option value="active">Active</option><option value="inactive">Inactive</option></select></FormField>
|
<FormField label="i18n:govoplan-admin.status.bae7d5be"><select value={draft.isActive ? "active" : "inactive"} onChange={(event) => setDraft({ ...draft, isActive: event.target.value === "active" })}><option value="active">i18n:govoplan-admin.active.a733b809</option><option value="inactive">i18n:govoplan-admin.inactive.09af574c</option></select></FormField>
|
||||||
<FormField label="Description"><textarea rows={3} value={draft.description} onChange={(event) => setDraft({ ...draft, description: event.target.value })} /></FormField>
|
<FormField label="i18n:govoplan-admin.description.55f8ebc8"><textarea rows={3} value={draft.description} onChange={(event) => setDraft({ ...draft, description: event.target.value })} /></FormField>
|
||||||
</div>
|
</div>
|
||||||
{kind === "role" && <div className="form-field"><span className="form-label">Tenant permissions</span><AdminSelectionList options={permissions.map((permission) => ({ id: permission.scope, label: permission.label, description: permission.description }))} selected={draft.permissions} onChange={(next) => setDraft({ ...draft, permissions: next })} /></div>}
|
{kind === "role" && <div className="form-field"><span className="form-label">i18n:govoplan-admin.tenant_permissions.246294bc</span><AdminSelectionList options={permissions.map((permission) => ({ id: permission.scope, label: permission.label, description: permission.description }))} selected={draft.permissions} onChange={(next) => setDraft({ ...draft, permissions: next })} /></div>}
|
||||||
<div className="form-field">
|
<div className="form-field">
|
||||||
<span className="form-label">Tenant availability</span>
|
<span className="form-label">i18n:govoplan-admin.tenant_availability.067a4f31</span>
|
||||||
<div className="admin-selection-list admin-governance-mode">
|
<div className="admin-selection-list admin-governance-mode">
|
||||||
{tenants.map((tenant) => <div className="admin-tenant-assignment-row" key={tenant.id}><span><strong>{tenant.name}</strong><small>{tenant.slug}</small></span><select value={assignmentMode(tenant.id)} onChange={(event) => setAssignment(tenant.id, event.target.value as "none" | "available" | "required")}><option value="none">Not available</option><option value="available">Available</option><option value="required">Required</option></select></div>)}
|
{tenants.map((tenant) => <div className="admin-tenant-assignment-row" key={tenant.id}><span><strong>{tenant.name}</strong><small>{tenant.slug}</small></span><select value={assignmentMode(tenant.id)} onChange={(event) => setAssignment(tenant.id, event.target.value as "none" | "available" | "required")}><option value="none">i18n:govoplan-admin.not_available.d1a17af1</option><option value="available">i18n:govoplan-admin.available.7c62a142</option><option value="required">i18n:govoplan-admin.required.eed6bfb4</option></select></div>)}
|
||||||
</div>
|
</div>
|
||||||
<p className="muted small-note">Required means the definition must remain present and system-controlled. It does not automatically assign users or grant permissions.</p>
|
<p className="muted small-note">i18n:govoplan-admin.required_means_the_definition_must_remain_presen.8462063c</p>
|
||||||
</div>
|
</div>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<Dialog open={Boolean(viewing)} title={viewing?.name || "Template details"} onClose={() => setViewing(null)} className="admin-dialog admin-dialog-wide" footer={<Button onClick={() => setViewing(null)}>Close</Button>}>
|
<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>Kind</dt><dd>{viewing.kind}</dd></div><div><dt>Slug</dt><dd>{viewing.slug}</dd></div><div><dt>Status</dt><dd>{viewing.is_active ? "Active" : "Inactive"}</dd></div><div><dt>Tenants</dt><dd>{viewing.assignments.length || "None"}</dd></div><div><dt>Description</dt><dd>{viewing.description || "—"}</dd></div><div><dt>Permissions</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 ? joinLabels(viewing.permissions.map((name) => ({ name }))) : "—"}</dd></div></dl>}
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<ConfirmDialog open={Boolean(deleting)} title={kind === "group" ? "Delete group template" : "Delete tenant role"} message={`Delete ${deleting?.name}? Removal is blocked while a materialized tenant definition still has members or assignments.`} confirmLabel={kind === "group" ? "Delete template" : "Delete tenant role"} tone="danger" busy={busy} onCancel={() => setDeleting(null)} onConfirm={() => void remove()} />
|
<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()} />
|
||||||
</>
|
</>);
|
||||||
);
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function draftKey(draft: typeof emptyDraft): string {
|
||||||
|
return JSON.stringify(draft);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,24 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import type { ApiSettings } from "@govoplan/core-webui";
|
import type { ApiSettings } from "@govoplan/core-webui";
|
||||||
import { AdminPageLayout, adminErrorMessage, Button, dispatchPlatformModulesChanged, StatusBadge, ToggleSwitch } from "@govoplan/core-webui";
|
import { AdminPageLayout, adminErrorMessage, Button, dispatchPlatformModulesChanged, StatusBadge, ToggleSwitch, i18nMessage, useUnsavedDraftGuard } from "@govoplan/core-webui";
|
||||||
import {
|
import {
|
||||||
cancelModuleInstallerRequest,
|
cancelModuleInstallerRequest,
|
||||||
clearModuleInstallPlan,
|
clearModuleInstallPlan,
|
||||||
|
createConfigurationChangeRequest,
|
||||||
createModuleInstallerRequest,
|
createModuleInstallerRequest,
|
||||||
fetchModuleCatalog,
|
fetchModuleCatalog,
|
||||||
fetchModuleInstallPlan,
|
fetchModuleInstallPlan,
|
||||||
fetchModuleInstallerRequests,
|
fetchModuleInstallerRequests,
|
||||||
fetchModuleInstallerRuns,
|
fetchModuleInstallerRuns,
|
||||||
fetchModulePackageCatalog,
|
fetchModulePackageCatalog,
|
||||||
|
fetchSystemSettings,
|
||||||
planModuleInstallFromCatalog,
|
planModuleInstallFromCatalog,
|
||||||
planModuleUninstall,
|
planModuleUninstall,
|
||||||
retryModuleInstallerRequest,
|
retryModuleInstallerRequest,
|
||||||
updateModuleInstallPlan,
|
updateModuleInstallPlan,
|
||||||
updateModuleState,
|
updateModuleState,
|
||||||
|
updateSystemSettings,
|
||||||
|
type ConfigurationChangeRequest,
|
||||||
type ModuleCatalogItem,
|
type ModuleCatalogItem,
|
||||||
type ModuleCatalogResponse,
|
type ModuleCatalogResponse,
|
||||||
type ModuleInstallerRequestListResponse,
|
type ModuleInstallerRequestListResponse,
|
||||||
@@ -22,21 +26,27 @@ import {
|
|||||||
type ModuleInstallerRunListResponse,
|
type ModuleInstallerRunListResponse,
|
||||||
type ModuleInstallPlanItem,
|
type ModuleInstallPlanItem,
|
||||||
type ModuleInstallPlanResponse,
|
type ModuleInstallPlanResponse,
|
||||||
|
type ModuleInstallTargetItem,
|
||||||
|
type ModuleMigrationPlanStep,
|
||||||
|
type ModuleMigrationTaskPlanItem,
|
||||||
|
type ModuleLicenseDiagnostics,
|
||||||
type ModulePackageCatalogResponse,
|
type ModulePackageCatalogResponse,
|
||||||
type ModulePackageCatalogItem
|
type ModulePackageCatalogItem } from
|
||||||
} from "../../api/admin";
|
"../../api/admin";
|
||||||
|
|
||||||
export default function ModuleManagementPanel({ settings, canWrite, canAccessMaintenance }: { settings: ApiSettings; canWrite: boolean; canAccessMaintenance: boolean }) {
|
export default function ModuleManagementPanel({ settings, canWrite, canAccessMaintenance }: {settings: ApiSettings;canWrite: boolean;canAccessMaintenance: boolean;}) {
|
||||||
const [catalog, setCatalog] = useState<ModuleCatalogResponse | null>(null);
|
const [catalog, setCatalog] = useState<ModuleCatalogResponse | null>(null);
|
||||||
const [installPlan, setInstallPlan] = useState<ModuleInstallPlanResponse | null>(null);
|
const [installPlan, setInstallPlan] = useState<ModuleInstallPlanResponse | null>(null);
|
||||||
const [installerRuns, setInstallerRuns] = useState<ModuleInstallerRunListResponse | null>(null);
|
const [installerRuns, setInstallerRuns] = useState<ModuleInstallerRunListResponse | null>(null);
|
||||||
const [installerRequests, setInstallerRequests] = useState<ModuleInstallerRequestListResponse | null>(null);
|
const [installerRequests, setInstallerRequests] = useState<ModuleInstallerRequestListResponse | null>(null);
|
||||||
const [packageCatalog, setPackageCatalog] = useState<ModulePackageCatalogResponse | null>(null);
|
const [packageCatalog, setPackageCatalog] = useState<ModulePackageCatalogResponse | null>(null);
|
||||||
const [draftEnabled, setDraftEnabled] = useState<Set<string> | null>(null);
|
const [draftEnabled, setDraftEnabled] = useState<Set<string> | null>(null);
|
||||||
|
const [moduleStateRequest, setModuleStateRequest] = useState<ModuleStateRequestState | null>(null);
|
||||||
const [draftPlanItems, setDraftPlanItems] = useState<ModuleInstallPlanItem[]>([]);
|
const [draftPlanItems, setDraftPlanItems] = useState<ModuleInstallPlanItem[]>([]);
|
||||||
const [requestOptions, setRequestOptions] = useState<InstallerRequestFormOptions>(() => defaultInstallerRequestOptions());
|
const [requestOptions, setRequestOptions] = useState<InstallerRequestFormOptions>(() => defaultInstallerRequestOptions());
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [busy, setBusy] = useState(false);
|
const [busy, setBusy] = useState(false);
|
||||||
|
const [maintenanceBusy, setMaintenanceBusy] = useState(false);
|
||||||
const [planBusy, setPlanBusy] = useState(false);
|
const [planBusy, setPlanBusy] = useState(false);
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
const [success, setSuccess] = useState("");
|
const [success, setSuccess] = useState("");
|
||||||
@@ -50,8 +60,8 @@ export default function ModuleManagementPanel({ settings, canWrite, canAccessMai
|
|||||||
fetchModuleInstallPlan(settings),
|
fetchModuleInstallPlan(settings),
|
||||||
fetchModuleInstallerRuns(settings),
|
fetchModuleInstallerRuns(settings),
|
||||||
fetchModuleInstallerRequests(settings),
|
fetchModuleInstallerRequests(settings),
|
||||||
fetchModulePackageCatalog(settings)
|
fetchModulePackageCatalog(settings)]
|
||||||
]);
|
);
|
||||||
setCatalog(loaded);
|
setCatalog(loaded);
|
||||||
setInstallPlan(loadedPlan);
|
setInstallPlan(loadedPlan);
|
||||||
setInstallerRuns(loadedRuns);
|
setInstallerRuns(loadedRuns);
|
||||||
@@ -66,7 +76,7 @@ export default function ModuleManagementPanel({ settings, canWrite, canAccessMai
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => { void load(); }, [settings.accessToken, settings.apiBaseUrl]);
|
useEffect(() => {void load();}, [settings.accessToken, settings.apiBaseUrl]);
|
||||||
|
|
||||||
const desired = draftEnabled ?? new Set(catalog?.desired_enabled ?? []);
|
const desired = draftEnabled ?? new Set(catalog?.desired_enabled ?? []);
|
||||||
const dirty = Boolean(catalog && !sameSet(desired, new Set(catalog.desired_enabled)));
|
const dirty = Boolean(catalog && !sameSet(desired, new Set(catalog.desired_enabled)));
|
||||||
@@ -76,37 +86,100 @@ export default function ModuleManagementPanel({ settings, canWrite, canAccessMai
|
|||||||
const maintenanceEnabled = Boolean(catalog?.maintenance_mode.enabled || installPlan?.maintenance_mode.enabled);
|
const maintenanceEnabled = Boolean(catalog?.maintenance_mode.enabled || installPlan?.maintenance_mode.enabled);
|
||||||
const planDirty = Boolean(installPlan && JSON.stringify(normalizePlanItems(draftPlanItems)) !== JSON.stringify(normalizePlanItems(installPlan.items)));
|
const planDirty = Boolean(installPlan && JSON.stringify(normalizePlanItems(draftPlanItems)) !== JSON.stringify(normalizePlanItems(installPlan.items)));
|
||||||
const planValid = planValidationError(draftPlanItems) === "";
|
const planValid = planValidationError(draftPlanItems) === "";
|
||||||
|
useUnsavedDraftGuard({
|
||||||
|
dirty: dirty || planDirty,
|
||||||
|
onSave: saveDirtyChanges,
|
||||||
|
onDiscard: () => {
|
||||||
|
if (catalog) setDraftEnabled(new Set(catalog.desired_enabled));
|
||||||
|
if (installPlan) setDraftPlanItems(installPlan.items);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const saveLabel = maintenanceEnabled ?
|
||||||
|
moduleStateRequest ? "i18n:govoplan-admin.apply_approved_request.ab218d9a" : "i18n:govoplan-admin.create_request_and_apply.da79e153" :
|
||||||
|
moduleStateRequest ? "i18n:govoplan-admin.waiting_for_maintenance.60d00019" : "i18n:govoplan-admin.create_dry_run_request.05b9cbdb";
|
||||||
|
const saveDisabled = !canWrite || !dirty || busy || !maintenanceEnabled && Boolean(moduleStateRequest);
|
||||||
|
|
||||||
function setModuleDesired(module: ModuleCatalogItem, enabled: boolean) {
|
function setModuleDesired(module: ModuleCatalogItem, enabled: boolean) {
|
||||||
if (module.protected) return;
|
if (module.protected) return;
|
||||||
setSuccess("");
|
setSuccess("");
|
||||||
|
setModuleStateRequest(null);
|
||||||
setDraftEnabled((current) => {
|
setDraftEnabled((current) => {
|
||||||
const next = new Set(current ?? catalog?.desired_enabled ?? []);
|
const next = new Set(current ?? catalog?.desired_enabled ?? []);
|
||||||
if (enabled) next.add(module.id);
|
if (enabled) next.add(module.id);else
|
||||||
else next.delete(module.id);
|
next.delete(module.id);
|
||||||
return next;
|
return next;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function save() {
|
async function save(): Promise<boolean> {
|
||||||
if (!draftEnabled) return;
|
if (!draftEnabled) return false;
|
||||||
|
const enabledModules = Array.from(draftEnabled).sort();
|
||||||
|
const payloadKey = moduleStatePayloadKey(enabledModules);
|
||||||
setBusy(true);
|
setBusy(true);
|
||||||
setError("");
|
setError("");
|
||||||
setSuccess("");
|
setSuccess("");
|
||||||
try {
|
try {
|
||||||
const updated = await updateModuleState(settings, Array.from(draftEnabled).sort());
|
let request = moduleStateRequest?.payloadKey === payloadKey ? moduleStateRequest.request : null;
|
||||||
|
if (!request) {
|
||||||
|
request = await createConfigurationChangeRequest(settings, {
|
||||||
|
key: "module_management.desired_enabled",
|
||||||
|
value: { enabled_modules: enabledModules },
|
||||||
|
dry_run: true,
|
||||||
|
target: { scope: "system" },
|
||||||
|
reason: "i18n:govoplan-admin.update_enabled_modules.9b7ae790"
|
||||||
|
});
|
||||||
|
setModuleStateRequest({ request, payloadKey });
|
||||||
|
}
|
||||||
|
if (!maintenanceEnabled) {
|
||||||
|
setSuccess(i18nMessage("i18n:govoplan-admin.dry_run_change_request_created_value_enable_main.49826d07", { value0: request.id }));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const updated = await updateModuleState(settings, enabledModules, request.id);
|
||||||
setCatalog(updated);
|
setCatalog(updated);
|
||||||
setDraftEnabled(new Set(updated.desired_enabled));
|
setDraftEnabled(new Set(updated.desired_enabled));
|
||||||
setSuccess(updated.notes[0] ?? "Module state saved.");
|
setModuleStateRequest(null);
|
||||||
|
setSuccess(updated.notes[0] ?? "i18n:govoplan-admin.module_state_saved.e3e22105");
|
||||||
dispatchPlatformModulesChanged();
|
dispatchPlatformModulesChanged();
|
||||||
|
return true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(adminErrorMessage(err));
|
setError(adminErrorMessage(err));
|
||||||
|
return false;
|
||||||
} finally {
|
} finally {
|
||||||
setBusy(false);
|
setBusy(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function savePlan() {
|
async function enableMaintenanceMode() {
|
||||||
|
setMaintenanceBusy(true);
|
||||||
|
setError("");
|
||||||
|
setSuccess("");
|
||||||
|
try {
|
||||||
|
const current = await fetchSystemSettings(settings);
|
||||||
|
const updated = await updateSystemSettings(settings, {
|
||||||
|
default_locale: current.default_locale,
|
||||||
|
allow_tenant_custom_groups: current.allow_tenant_custom_groups,
|
||||||
|
allow_tenant_custom_roles: current.allow_tenant_custom_roles,
|
||||||
|
allow_tenant_api_keys: current.allow_tenant_api_keys,
|
||||||
|
maintenance_mode: {
|
||||||
|
enabled: true,
|
||||||
|
message: current.maintenance_mode.message || "i18n:govoplan-admin.module_maintenance_in_progress.f6395152"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setCatalog((value) => value ? { ...value, maintenance_mode: updated.maintenance_mode } : value);
|
||||||
|
setInstallPlan((value) => value ? {
|
||||||
|
...value,
|
||||||
|
maintenance_mode: updated.maintenance_mode,
|
||||||
|
preflight: value.preflight ? { ...value.preflight, maintenance_mode: updated.maintenance_mode.enabled } : value.preflight
|
||||||
|
} : value);
|
||||||
|
setSuccess("i18n:govoplan-admin.maintenance_mode_enabled_apply_the_module_state_.dda13e5c");
|
||||||
|
} catch (err) {
|
||||||
|
setError(adminErrorMessage(err));
|
||||||
|
} finally {
|
||||||
|
setMaintenanceBusy(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function savePlan(): Promise<boolean> {
|
||||||
setPlanBusy(true);
|
setPlanBusy(true);
|
||||||
setError("");
|
setError("");
|
||||||
setSuccess("");
|
setSuccess("");
|
||||||
@@ -114,14 +187,23 @@ export default function ModuleManagementPanel({ settings, canWrite, canAccessMai
|
|||||||
const updated = await updateModuleInstallPlan(settings, normalizePlanItems(draftPlanItems));
|
const updated = await updateModuleInstallPlan(settings, normalizePlanItems(draftPlanItems));
|
||||||
setInstallPlan(updated);
|
setInstallPlan(updated);
|
||||||
setDraftPlanItems(updated.items);
|
setDraftPlanItems(updated.items);
|
||||||
setSuccess(updated.notes[0] ?? "Module install plan saved.");
|
setSuccess(updated.notes[0] ?? "i18n:govoplan-admin.module_install_plan_saved.5d43d944");
|
||||||
|
return true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(adminErrorMessage(err));
|
setError(adminErrorMessage(err));
|
||||||
|
return false;
|
||||||
} finally {
|
} finally {
|
||||||
setPlanBusy(false);
|
setPlanBusy(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function saveDirtyChanges(): Promise<boolean> {
|
||||||
|
let ok = true;
|
||||||
|
if (dirty) ok = await save() && ok;
|
||||||
|
if (planDirty) ok = await savePlan() && ok;
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
async function clearPlan() {
|
async function clearPlan() {
|
||||||
setPlanBusy(true);
|
setPlanBusy(true);
|
||||||
setError("");
|
setError("");
|
||||||
@@ -130,7 +212,7 @@ export default function ModuleManagementPanel({ settings, canWrite, canAccessMai
|
|||||||
const updated = await clearModuleInstallPlan(settings);
|
const updated = await clearModuleInstallPlan(settings);
|
||||||
setInstallPlan(updated);
|
setInstallPlan(updated);
|
||||||
setDraftPlanItems([]);
|
setDraftPlanItems([]);
|
||||||
setSuccess(updated.notes[0] ?? "Module install plan cleared.");
|
setSuccess(updated.notes[0] ?? "i18n:govoplan-admin.module_install_plan_cleared.afa2698d");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(adminErrorMessage(err));
|
setError(adminErrorMessage(err));
|
||||||
} finally {
|
} finally {
|
||||||
@@ -155,7 +237,7 @@ export default function ModuleManagementPanel({ settings, canWrite, canAccessMai
|
|||||||
const updated = await planModuleInstallFromCatalog(settings, item.module_id);
|
const updated = await planModuleInstallFromCatalog(settings, item.module_id);
|
||||||
setInstallPlan(updated);
|
setInstallPlan(updated);
|
||||||
setDraftPlanItems(updated.items);
|
setDraftPlanItems(updated.items);
|
||||||
setSuccess(updated.notes[0] ?? "Catalog install entry planned.");
|
setSuccess(updated.notes[0] ?? "i18n:govoplan-admin.catalog_install_entry_planned.13b23f4f");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(adminErrorMessage(err));
|
setError(adminErrorMessage(err));
|
||||||
} finally {
|
} finally {
|
||||||
@@ -171,7 +253,7 @@ export default function ModuleManagementPanel({ settings, canWrite, canAccessMai
|
|||||||
const updated = await planModuleUninstall(settings, module.id);
|
const updated = await planModuleUninstall(settings, module.id);
|
||||||
setInstallPlan(updated);
|
setInstallPlan(updated);
|
||||||
setDraftPlanItems(updated.items);
|
setDraftPlanItems(updated.items);
|
||||||
setSuccess(updated.notes[0] ?? "Package uninstall planned.");
|
setSuccess(updated.notes[0] ?? "i18n:govoplan-admin.package_uninstall_planned.56e8566d");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(adminErrorMessage(err));
|
setError(adminErrorMessage(err));
|
||||||
} finally {
|
} finally {
|
||||||
@@ -186,11 +268,11 @@ export default function ModuleManagementPanel({ settings, canWrite, canAccessMai
|
|||||||
setSuccess("");
|
setSuccess("");
|
||||||
try {
|
try {
|
||||||
const request = await createModuleInstallerRequest(settings, normalizeRequestOptions(requestOptions, installPlan));
|
const request = await createModuleInstallerRequest(settings, normalizeRequestOptions(requestOptions, installPlan));
|
||||||
setSuccess(`Installer request queued: ${request.request_id}`);
|
setSuccess(i18nMessage("i18n:govoplan-admin.installer_request_queued_value.00726abb", { value0: request.request_id }));
|
||||||
const [loadedRuns, loadedRequests] = await Promise.all([
|
const [loadedRuns, loadedRequests] = await Promise.all([
|
||||||
fetchModuleInstallerRuns(settings),
|
fetchModuleInstallerRuns(settings),
|
||||||
fetchModuleInstallerRequests(settings)
|
fetchModuleInstallerRequests(settings)]
|
||||||
]);
|
);
|
||||||
setInstallerRuns(loadedRuns);
|
setInstallerRuns(loadedRuns);
|
||||||
setInstallerRequests(loadedRequests);
|
setInstallerRequests(loadedRequests);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -206,7 +288,7 @@ export default function ModuleManagementPanel({ settings, canWrite, canAccessMai
|
|||||||
setSuccess("");
|
setSuccess("");
|
||||||
try {
|
try {
|
||||||
const request = await cancelModuleInstallerRequest(settings, requestId);
|
const request = await cancelModuleInstallerRequest(settings, requestId);
|
||||||
setSuccess(`Installer request cancelled: ${request.request_id}`);
|
setSuccess(i18nMessage("i18n:govoplan-admin.installer_request_cancelled_value.1d6d099d", { value0: request.request_id }));
|
||||||
setInstallerRequests(await fetchModuleInstallerRequests(settings));
|
setInstallerRequests(await fetchModuleInstallerRequests(settings));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(adminErrorMessage(err));
|
setError(adminErrorMessage(err));
|
||||||
@@ -221,7 +303,7 @@ export default function ModuleManagementPanel({ settings, canWrite, canAccessMai
|
|||||||
setSuccess("");
|
setSuccess("");
|
||||||
try {
|
try {
|
||||||
const request = await retryModuleInstallerRequest(settings, requestId);
|
const request = await retryModuleInstallerRequest(settings, requestId);
|
||||||
setSuccess(`Installer request queued: ${request.request_id}`);
|
setSuccess(i18nMessage("i18n:govoplan-admin.installer_request_queued_value.00726abb", { value0: request.request_id }));
|
||||||
setInstallerRequests(await fetchModuleInstallerRequests(settings));
|
setInstallerRequests(await fetchModuleInstallerRequests(settings));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(adminErrorMessage(err));
|
setError(adminErrorMessage(err));
|
||||||
@@ -232,22 +314,33 @@ export default function ModuleManagementPanel({ settings, canWrite, canAccessMai
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<AdminPageLayout
|
<AdminPageLayout
|
||||||
title="System modules"
|
title="i18n:govoplan-admin.system_modules.629539f2"
|
||||||
description="Installed modules, active runtime state, and saved startup state."
|
description="i18n:govoplan-admin.installed_modules_active_runtime_state_and_saved.f33907dd"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
error={error}
|
error={error}
|
||||||
success={success}
|
success={success}
|
||||||
actions={<><Button onClick={() => void load()} disabled={loading || busy}>Reload</Button><Button variant="primary" onClick={() => void save()} disabled={!canWrite || !dirty || busy}>{busy ? "Applying..." : "Save and apply"}</Button></>}
|
actions={<><Button onClick={() => void load()} disabled={loading || busy}>i18n:govoplan-admin.reload.cce71553</Button><Button variant="primary" onClick={() => void save()} disabled={saveDisabled}>{busy ? "i18n:govoplan-admin.working.049ac820" : saveLabel}</Button></>}>
|
||||||
>
|
|
||||||
{catalog && <>
|
{catalog && <>
|
||||||
<div className="metric-grid module-management-metrics">
|
<div className="metric-grid module-management-metrics">
|
||||||
<ModuleMetric label="Installed" value={catalog.modules.length} detail="Discovered packages" />
|
<ModuleMetric label="i18n:govoplan-admin.installed.7bb4405c" value={catalog.modules.length} detail="i18n:govoplan-admin.discovered_packages.660902de" />
|
||||||
<ModuleMetric label="Active" value={activeCount} detail="Running registry" />
|
<ModuleMetric label="i18n:govoplan-admin.active.a733b809" value={activeCount} detail="i18n:govoplan-admin.running_registry.5274b24b" />
|
||||||
<ModuleMetric label="Saved" value={desiredCount} detail="Startup state" />
|
<ModuleMetric label="i18n:govoplan-admin.saved.c0ae8f6e" value={desiredCount} detail="i18n:govoplan-admin.startup_state.d1300be6" />
|
||||||
<ModuleMetric label="Drift" value={pendingCount} detail={pendingCount ? "Runtime differs" : "Runtime matches"} tone={pendingCount ? "warning" : "good"} />
|
<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="Maintenance" value={maintenanceEnabled ? "On" : "Off"} detail={canAccessMaintenance ? "Bypass allowed" : "Bypass denied"} tone={maintenanceEnabled ? "warning" : "info"} />
|
<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"} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{dirty && <div className="module-management-notes">
|
||||||
|
<p className="alert warning">i18n:govoplan-admin.changing_enabled_modules_requires_a_dry_run_chan.10b6f5ed</p>
|
||||||
|
{moduleStateRequest && <p>i18n:govoplan-admin.change_request_ready.b17e8c57 <code>{moduleStateRequest.request.id}</code></p>}
|
||||||
|
{!maintenanceEnabled && <div className="button-row compact-actions">
|
||||||
|
<Button onClick={() => void enableMaintenanceMode()} disabled={!canWrite || !canAccessMaintenance || maintenanceBusy}>
|
||||||
|
{maintenanceBusy ? "i18n:govoplan-admin.enabling.2b8e03e6" : "i18n:govoplan-admin.enable_maintenance_mode.75f98d57"}
|
||||||
|
</Button>
|
||||||
|
</div>}
|
||||||
|
{!maintenanceEnabled && !canAccessMaintenance && <p className="alert warning">i18n:govoplan-admin.enabling_maintenance_mode_requires_system_mainte.062d3968</p>}
|
||||||
|
</div>}
|
||||||
|
|
||||||
<div className="module-management-list">
|
<div className="module-management-list">
|
||||||
{catalog.modules.map((module) => {
|
{catalog.modules.map((module) => {
|
||||||
const desiredEnabled = desired.has(module.id);
|
const desiredEnabled = desired.has(module.id);
|
||||||
@@ -261,27 +354,27 @@ export default function ModuleManagementPanel({ settings, canWrite, canAccessMai
|
|||||||
</div>
|
</div>
|
||||||
<div className="module-management-meta">
|
<div className="module-management-meta">
|
||||||
<ModuleStatus module={module} desiredEnabled={desiredEnabled} />
|
<ModuleStatus module={module} desiredEnabled={desiredEnabled} />
|
||||||
{module.protected && <StatusBadge status="locked" label="Locked" />}
|
{module.protected && <StatusBadge status="locked" label="i18n:govoplan-admin.locked.a798882f" />}
|
||||||
{module.frontend_package && <span>{module.frontend_package}</span>}
|
{module.frontend_package && <span>{module.frontend_package}</span>}
|
||||||
{module.migration_module_id && <span>DB: {module.migration_module_id}</span>}
|
{module.migration_module_id && <span>i18n:govoplan-admin.db.e355c23a {module.migration_module_id}</span>}
|
||||||
</div>
|
</div>
|
||||||
<div className="module-management-details">
|
<div className="module-management-details">
|
||||||
<span>Requires: {module.dependencies.length ? module.dependencies.join(", ") : "none"}</span>
|
<span>i18n:govoplan-admin.requires.a4fc9357 {module.dependencies.length ? module.dependencies.join(", ") : "none"}</span>
|
||||||
<span>Optional: {module.optional_dependencies.length ? module.optional_dependencies.join(", ") : "none"}</span>
|
<span>i18n:govoplan-admin.optional.166b8369 {module.optional_dependencies.length ? module.optional_dependencies.join(", ") : "none"}</span>
|
||||||
<span>Dependents: {module.dependents.length ? module.dependents.join(", ") : "none"}</span>
|
<span>i18n:govoplan-admin.dependents.072665c4 {module.dependents.length ? module.dependents.join(", ") : "none"}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="module-management-toggle">
|
<div className="module-management-toggle">
|
||||||
<ToggleSwitch
|
<ToggleSwitch
|
||||||
label={desiredEnabled ? "Enabled" : "Disabled"}
|
label={desiredEnabled ? "i18n:govoplan-admin.enabled.df174a3f" : "i18n:govoplan-admin.disabled.f4f4473d"}
|
||||||
checked={desiredEnabled}
|
checked={desiredEnabled}
|
||||||
disabled={!canWrite || module.protected || busy}
|
disabled={!canWrite || module.protected || busy}
|
||||||
onChange={(checked) => setModuleDesired(module, checked)}
|
onChange={(checked) => setModuleDesired(module, checked)} />
|
||||||
/>
|
|
||||||
{module.install_uninstall_supported && <Button onClick={() => void addUninstallPlan(module)} disabled={!canWrite || planBusy || module.current_enabled || module.desired_enabled}>Plan uninstall</Button>}
|
{module.install_uninstall_supported && <Button onClick={() => void addUninstallPlan(module)} disabled={!canWrite || planBusy || module.current_enabled || module.desired_enabled}>i18n:govoplan-admin.plan_uninstall.e62804ab</Button>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>);
|
||||||
);
|
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -292,26 +385,27 @@ export default function ModuleManagementPanel({ settings, canWrite, canAccessMai
|
|||||||
{packageCatalog && <div className="module-package-catalog">
|
{packageCatalog && <div className="module-package-catalog">
|
||||||
<div className="module-install-plan-heading">
|
<div className="module-install-plan-heading">
|
||||||
<div>
|
<div>
|
||||||
<h2>Package catalog</h2>
|
<h2>i18n:govoplan-admin.package_catalog.b36e6d31</h2>
|
||||||
<p>Approved package references that can be added to the operator install plan.</p>
|
<p>i18n:govoplan-admin.approved_package_references_that_can_be_added_to.cc911c16</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="button-row compact-actions">
|
<div className="button-row compact-actions">
|
||||||
{packageCatalog.channel && <StatusBadge status="inactive" label={`Channel: ${packageCatalog.channel}`} />}
|
{packageCatalog.channel && <StatusBadge status="inactive" label={i18nMessage("i18n:govoplan-admin.channel_value.d3894efe", { value0: packageCatalog.channel })} />}
|
||||||
{packageCatalog.sequence !== null && packageCatalog.sequence !== undefined && <StatusBadge status="inactive" label={`Seq ${packageCatalog.sequence}`} />}
|
{packageCatalog.sequence !== null && packageCatalog.sequence !== undefined && <StatusBadge status="inactive" label={i18nMessage("i18n:govoplan-admin.seq_value.0ae5fa47", { value0: packageCatalog.sequence })} />}
|
||||||
<StatusBadge
|
<StatusBadge
|
||||||
status={packageCatalog.trusted ? "success" : packageCatalog.signed ? "warning" : "inactive"}
|
status={packageCatalog.trusted ? "success" : packageCatalog.signed ? "warning" : "inactive"}
|
||||||
label={packageCatalog.trusted ? "Signed and trusted" : packageCatalog.signed ? "Signed, untrusted" : "Unsigned"}
|
label={packageCatalog.trusted ? "i18n:govoplan-admin.signed_and_trusted.bee31302" : packageCatalog.signed ? "i18n:govoplan-admin.signed_untrusted.864ed50c" : "i18n:govoplan-admin.unsigned.e91344ea"} />
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{(packageCatalog.source || packageCatalog.path) && <p className="module-package-catalog-description">Catalog {packageCatalog.source_type ?? "source"}: <code>{packageCatalog.source ?? packageCatalog.path}</code></p>}
|
{(packageCatalog.source || packageCatalog.path) && <p className="module-package-catalog-description">i18n:govoplan-admin.catalog.4a88d27b {packageCatalog.source_type ?? "source"}: <code>{packageCatalog.source ?? packageCatalog.path}</code>{packageCatalog.cache_used && packageCatalog.cache_path ? <> i18n:govoplan-admin.cached_from.d73c5b2a <code>{packageCatalog.cache_path}</code></> : null}</p>}
|
||||||
{(packageCatalog.generated_at || packageCatalog.expires_at) && <p className="module-package-catalog-description">Generated: {packageCatalog.generated_at ?? "unknown"} · Expires: {packageCatalog.expires_at ?? "unknown"}</p>}
|
{(packageCatalog.generated_at || packageCatalog.not_before || packageCatalog.expires_at) && <p className="module-package-catalog-description">i18n:govoplan-admin.generated.a2edf57c {packageCatalog.generated_at ?? "unknown"} i18n:govoplan-admin.valid_after.c615c873 {packageCatalog.not_before ?? "now"} i18n:govoplan-admin.expires.2d21b7de {packageCatalog.expires_at ?? "unknown"}</p>}
|
||||||
|
<LicenseStatus license={packageCatalog.license} />
|
||||||
{packageCatalog.error && <p className="alert warning">{packageCatalog.error}</p>}
|
{packageCatalog.error && <p className="alert warning">{packageCatalog.error}</p>}
|
||||||
{packageCatalog.warnings.map((warning) => <p key={warning} className="alert warning">{warning}</p>)}
|
{packageCatalog.warnings.map((warning) => <p key={warning} className="alert warning">{warning}</p>)}
|
||||||
{!packageCatalog.configured && <div className="module-install-plan-empty">No package catalog configured. Set GOVOPLAN_MODULE_PACKAGE_CATALOG or GOVOPLAN_MODULE_PACKAGE_CATALOG_URL to enable curated install entries.</div>}
|
{!packageCatalog.configured && <div className="module-install-plan-empty">i18n:govoplan-admin.no_package_catalog_configured_set_govoplan_modul.2c5fceb3</div>}
|
||||||
{packageCatalog.configured && packageCatalog.modules.length === 0 && !packageCatalog.error && <div className="module-install-plan-empty">Package catalog is configured but contains no entries.</div>}
|
{packageCatalog.configured && packageCatalog.modules.length === 0 && !packageCatalog.error && <div className="module-install-plan-empty">i18n:govoplan-admin.package_catalog_is_configured_but_contains_no_en.b6b053cb</div>}
|
||||||
{packageCatalog.modules.length > 0 && <div className="module-package-catalog-list">
|
{packageCatalog.modules.length > 0 && <div className="module-package-catalog-list">
|
||||||
{packageCatalog.modules.map((item) => (
|
{packageCatalog.modules.map((item) =>
|
||||||
<div key={`${item.module_id}-${item.version ?? "latest"}`} className="module-package-catalog-row">
|
<div key={`${item.module_id}-${item.version ?? "latest"}`} className="module-package-catalog-row">
|
||||||
<div className="module-management-main">
|
<div className="module-management-main">
|
||||||
<div className="module-management-title">
|
<div className="module-management-title">
|
||||||
@@ -323,75 +417,131 @@ export default function ModuleManagementPanel({ settings, canWrite, canAccessMai
|
|||||||
{item.python_package && <span>{item.python_package}</span>}
|
{item.python_package && <span>{item.python_package}</span>}
|
||||||
{item.webui_package && <span>{item.webui_package}</span>}
|
{item.webui_package && <span>{item.webui_package}</span>}
|
||||||
{item.tags.length > 0 && <span>{item.tags.join(", ")}</span>}
|
{item.tags.length > 0 && <span>{item.tags.join(", ")}</span>}
|
||||||
{item.license_features.length > 0 && <span>License: {item.license_features.join(", ")}</span>}
|
{item.dependencies.length > 0 && <span>i18n:govoplan-admin.dependencies.9f4f78d1 {item.dependencies.join(", ")}</span>}
|
||||||
{item.license_missing_features.length > 0 && <span>Missing: {item.license_missing_features.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>
|
</div>
|
||||||
{item.description && <p className="module-package-catalog-description">{item.description}</p>}
|
{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>}
|
{item.license_reason && <p className={`module-package-catalog-description${item.license_allowed ? "" : " alert warning"}`}>{item.license_reason}</p>}
|
||||||
</div>
|
</div>
|
||||||
<Button onClick={() => void addCatalogItem(item)} disabled={!canWrite || planBusy || !packageCatalog.valid || !item.license_allowed || item.action !== "install"}>Plan install</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>
|
||||||
))}
|
)}
|
||||||
</div>}
|
</div>}
|
||||||
</div>}
|
</div>}
|
||||||
|
|
||||||
{installPlan && <div className="module-install-plan-panel">
|
{installPlan && <div className="module-install-plan-panel">
|
||||||
<div className="module-install-plan-heading">
|
<div className="module-install-plan-heading">
|
||||||
<div>
|
<div>
|
||||||
<h2>Operator install plan</h2>
|
<h2>i18n:govoplan-admin.operator_install_plan.b203aabc</h2>
|
||||||
<p>Plan package installs and removals here, then apply the rendered commands from an operator shell during maintenance mode.</p>
|
<p>i18n:govoplan-admin.plan_package_installs_and_removals_here_then_app.4aeb03bf</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="button-row compact-actions">
|
<div className="button-row compact-actions">
|
||||||
<Button onClick={addPlanItem} disabled={!canWrite || planBusy}>Add plan item</Button>
|
<Button onClick={addPlanItem} disabled={!canWrite || planBusy}>i18n:govoplan-admin.add_plan_item.fa55f028</Button>
|
||||||
<Button onClick={() => void clearPlan()} disabled={!canWrite || planBusy || draftPlanItems.length === 0}>Clear</Button>
|
<Button onClick={() => void clearPlan()} disabled={!canWrite || planBusy || draftPlanItems.length === 0}>i18n:govoplan-admin.clear.719ea396</Button>
|
||||||
<Button variant="primary" onClick={() => void savePlan()} disabled={!canWrite || planBusy || !planDirty || !planValid}>{planBusy ? "Saving..." : "Save plan"}</Button>
|
<Button variant="primary" onClick={() => void savePlan()} disabled={!canWrite || planBusy || !planDirty || !planValid}>{planBusy ? "i18n:govoplan-admin.saving.ae7e8875" : "i18n:govoplan-admin.save_plan.842dc280"}</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{planValidationError(draftPlanItems) && <p className="alert warning">{planValidationError(draftPlanItems)}</p>}
|
{planValidationError(draftPlanItems) && <p className="alert warning">{planValidationError(draftPlanItems)}</p>}
|
||||||
{!maintenanceEnabled && <p className="alert warning">Maintenance mode is off. Package changes should be applied only after enabling maintenance mode.</p>}
|
{!maintenanceEnabled && <p className="alert warning">i18n:govoplan-admin.maintenance_mode_is_off_package_changes_should_b.1d262629</p>}
|
||||||
|
|
||||||
{installPlan.preflight && <div className={`module-install-preflight ${installPlan.preflight.allowed ? "is-allowed" : "is-blocked"}`}>
|
{installPlan.preflight && <div className={`module-install-preflight ${installPlan.preflight.allowed ? "is-allowed" : "is-blocked"}`}>
|
||||||
<div className="module-install-preflight-title">
|
<div className="module-install-preflight-title">
|
||||||
<strong>{installPlan.preflight.allowed ? "Installer preflight passed" : "Installer preflight blocked"}</strong>
|
<strong>{installPlan.preflight.allowed ? "i18n:govoplan-admin.installer_preflight_passed.6d5f8060" : "i18n:govoplan-admin.installer_preflight_blocked.cf83bf79"}</strong>
|
||||||
<span>{installPlan.preflight.restart_required ? "Restart/reload required after package changes" : "No package restart pending"}</span>
|
<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>WebUI rebuild required</span>}
|
{installPlan.preflight.frontend_rebuild_required && <span>i18n:govoplan-admin.webui_rebuild_required.010ce49f</span>}
|
||||||
</div>
|
</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.length > 0 && <div className="module-install-preflight-issues">
|
||||||
{installPlan.preflight.issues.map((issue, index) => (
|
{installPlan.preflight.issues.map((issue, index) =>
|
||||||
<div key={`${issue.code}-${issue.module_id ?? "global"}-${index}`} className={`module-install-preflight-issue severity-${issue.severity}`}>
|
<div key={`${issue.code}-${issue.module_id ?? "global"}-${index}`} className={`module-install-preflight-issue severity-${issue.severity}`}>
|
||||||
<strong>{issue.severity}</strong>
|
<strong>{issue.severity}</strong>
|
||||||
<span>{issue.module_id ? `${issue.module_id}: ` : ""}{issue.message}</span>
|
<span>{issue.module_id ? `${issue.module_id}: ` : ""}{issue.message}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
)}
|
||||||
</div>}
|
</div>}
|
||||||
{installPlan.preflight.checklist.length > 0 && <div className="module-install-checklist">
|
{installPlan.preflight.checklist.length > 0 && <div className="module-install-checklist">
|
||||||
{installPlan.preflight.checklist.map((item) => (
|
{installPlan.preflight.checklist.map((item) =>
|
||||||
<div key={item.id} className={`module-install-checklist-item status-${item.status}`}>
|
<div key={item.id} className={`module-install-checklist-item status-${item.status}`}>
|
||||||
<strong>{item.label}</strong>
|
<strong>{item.label}</strong>
|
||||||
<span>{item.status}</span>
|
<span>{item.status}</span>
|
||||||
{item.detail && <p>{item.detail}</p>}
|
{item.detail && <p>{item.detail}</p>}
|
||||||
</div>
|
</div>
|
||||||
))}
|
)}
|
||||||
</div>}
|
</div>}
|
||||||
</div>}
|
</div>}
|
||||||
|
|
||||||
<div className="module-install-plan-list">
|
<div className="module-install-plan-list">
|
||||||
{draftPlanItems.length === 0 && <div className="module-install-plan-empty">No package changes planned.</div>}
|
{draftPlanItems.length === 0 && <div className="module-install-plan-empty">i18n:govoplan-admin.no_package_changes_planned.f12d8b33</div>}
|
||||||
{draftPlanItems.map((item, index) => (
|
{draftPlanItems.map((item, index) =>
|
||||||
<div className="module-install-plan-row" key={`${item.module_id}-${index}`}>
|
<div className="module-install-plan-row" key={`${item.module_id}-${index}`}>
|
||||||
<label><span>Action</span><select value={item.action} disabled={!canWrite || planBusy} onChange={(event) => updatePlanItem(index, { action: event.target.value as ModuleInstallPlanItem["action"] })}><option value="install">Install</option><option value="uninstall">Uninstall</option></select></label>
|
<div className="module-install-plan-row-meta">
|
||||||
<label><span>Module</span><input value={item.module_id} disabled={!canWrite || planBusy} onChange={(event) => updatePlanItem(index, { module_id: event.target.value })} placeholder="files" /></label>
|
<StatusBadge status={item.source === "catalog" ? "success" : "inactive"} label={item.source === "catalog" ? "i18n:govoplan-admin.catalog.4a88d27b" : "i18n:govoplan-admin.manual.17a486ca"} />
|
||||||
<label><span>Python package</span><input value={item.python_package ?? ""} disabled={!canWrite || planBusy} onChange={(event) => updatePlanItem(index, { python_package: event.target.value })} placeholder="govoplan-files" /></label>
|
{item.catalog?.channel && <StatusBadge status="inactive" label={i18nMessage("i18n:govoplan-admin.channel_value.d3894efe", { value0: String(item.catalog.channel) })} />}
|
||||||
<ToggleSwitch label="Destroy data" checked={Boolean(item.destroy_data)} onChange={(checked) => updatePlanItem(index, { destroy_data: checked })} disabled={!canWrite || planBusy || item.action !== "uninstall"} />
|
{item.catalog?.sequence !== null && item.catalog?.sequence !== undefined && <StatusBadge status="inactive" label={i18nMessage("i18n:govoplan-admin.seq_value.0ae5fa47", { value0: item.catalog.sequence })} />}
|
||||||
<label className="wide"><span>Python ref</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>
|
{item.catalog?.key_id && <StatusBadge status="inactive" label={i18nMessage("i18n:govoplan-admin.key_value.847b914f", { value0: String(item.catalog.key_id) })} />}
|
||||||
<label><span>WebUI package</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>WebUI ref</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>
|
|
||||||
<label><span>Status</span><select value={item.status} disabled={!canWrite || planBusy} onChange={(event) => updatePlanItem(index, { status: event.target.value as ModuleInstallPlanItem["status"] })}><option value="planned">Planned</option><option value="applied">Applied</option><option value="blocked">Blocked</option></select></label>
|
|
||||||
<label className="wide"><span>Notes</span><input value={item.notes ?? ""} disabled={!canWrite || planBusy} onChange={(event) => updatePlanItem(index, { notes: event.target.value })} /></label>
|
|
||||||
<div className="module-install-plan-actions"><Button onClick={() => setDraftPlanItems((items) => items.filter((_, itemIndex) => itemIndex !== index))} disabled={!canWrite || planBusy}>Remove</Button></div>
|
|
||||||
</div>
|
</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="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>
|
||||||
|
<label><span>i18n:govoplan-admin.status.bae7d5be</span><select value={item.status} disabled={!canWrite || planBusy} onChange={(event) => updatePlanItem(index, { status: event.target.value as ModuleInstallPlanItem["status"] })}><option value="planned">i18n:govoplan-admin.planned.9cbe42aa</option><option value="applied">i18n:govoplan-admin.applied.a3e4a569</option><option value="blocked">i18n:govoplan-admin.blocked.99613c74</option></select></label>
|
||||||
|
<label className="wide"><span>i18n:govoplan-admin.notes.70440046</span><input value={item.notes ?? ""} disabled={!canWrite || planBusy} onChange={(event) => updatePlanItem(index, { notes: event.target.value })} /></label>
|
||||||
|
<div className="module-install-plan-actions"><Button onClick={() => setDraftPlanItems((items) => items.filter((_, itemIndex) => itemIndex !== index))} disabled={!canWrite || planBusy}>i18n:govoplan-admin.remove.e963907d</Button></div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<pre className="code-panel module-install-plan-commands">{installerCommand(installPlan.preflight?.frontend_rebuild_required ?? false)}</pre>
|
<pre className="code-panel module-install-plan-commands">{installerCommand(installPlan.preflight?.frontend_rebuild_required ?? false)}</pre>
|
||||||
@@ -401,26 +551,26 @@ export default function ModuleManagementPanel({ settings, canWrite, canAccessMai
|
|||||||
<div className="module-installer-request-panel">
|
<div className="module-installer-request-panel">
|
||||||
<div className="module-install-plan-heading">
|
<div className="module-install-plan-heading">
|
||||||
<div>
|
<div>
|
||||||
<h2>Daemon execution</h2>
|
<h2>i18n:govoplan-admin.daemon_execution.cc0fad8d</h2>
|
||||||
<p>Queue the saved plan for a separate installer daemon process.</p>
|
<p>i18n:govoplan-admin.queue_the_saved_plan_for_a_separate_installer_da.92d4c96e</p>
|
||||||
</div>
|
</div>
|
||||||
<Button variant="primary" onClick={() => void queueInstallerRequest()} disabled={!canWrite || !canAccessMaintenance || planBusy || planDirty || !maintenanceEnabled || !installPlan.preflight?.allowed}>
|
<Button variant="primary" onClick={() => void queueInstallerRequest()} disabled={!canWrite || !canAccessMaintenance || planBusy || planDirty || !maintenanceEnabled || !installPlan.preflight?.allowed}>
|
||||||
Queue supervised run
|
i18n:govoplan-admin.queue_supervised_run.a53f248c
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{planDirty && <p className="alert warning">Save the install plan before queueing a daemon request.</p>}
|
{planDirty && <p className="alert warning">i18n:govoplan-admin.save_the_install_plan_before_queueing_a_daemon_r.3ba00691</p>}
|
||||||
{!maintenanceEnabled && <p className="alert warning">Maintenance mode must be enabled before queueing a daemon request.</p>}
|
{!maintenanceEnabled && <p className="alert warning">i18n:govoplan-admin.maintenance_mode_must_be_enabled_before_queueing.d233a32f</p>}
|
||||||
{!canAccessMaintenance && <p className="alert warning">Queueing installer requests requires maintenance access.</p>}
|
{!canAccessMaintenance && <p className="alert warning">i18n:govoplan-admin.queueing_installer_requests_requires_maintenance.ae81f9ac</p>}
|
||||||
<div className="module-installer-request-grid">
|
<div className="module-installer-request-grid">
|
||||||
<ToggleSwitch label="Run migrations" checked={requestOptions.migrateDatabase} onChange={(checked) => setRequestOptions((current) => ({ ...current, migrateDatabase: checked }))} disabled={!canWrite || planBusy} />
|
<ToggleSwitch label="i18n:govoplan-admin.run_migrations.db6e0ce2" checked={requestOptions.migrateDatabase} onChange={(checked) => setRequestOptions((current) => ({ ...current, migrateDatabase: checked }))} disabled={!canWrite || planBusy} />
|
||||||
<ToggleSwitch label="Build WebUI" checked={requestOptions.buildWebui} onChange={(checked) => setRequestOptions((current) => ({ ...current, buildWebui: checked }))} disabled={!canWrite || planBusy} />
|
<ToggleSwitch label="i18n:govoplan-admin.build_webui.fe8ccad7" checked={requestOptions.buildWebui} onChange={(checked) => setRequestOptions((current) => ({ ...current, buildWebui: checked }))} disabled={!canWrite || planBusy} />
|
||||||
<ToggleSwitch label="Activate installs" checked={requestOptions.activateInstalledModules} onChange={(checked) => setRequestOptions((current) => ({ ...current, activateInstalledModules: checked }))} disabled={!canWrite || planBusy} />
|
<ToggleSwitch label="i18n:govoplan-admin.activate_installs.731d22a1" checked={requestOptions.activateInstalledModules} onChange={(checked) => setRequestOptions((current) => ({ ...current, activateInstalledModules: checked }))} disabled={!canWrite || planBusy} />
|
||||||
<ToggleSwitch label="Drop uninstalls from startup" checked={requestOptions.removeUninstalledModulesFromDesired} onChange={(checked) => setRequestOptions((current) => ({ ...current, removeUninstalledModulesFromDesired: checked }))} disabled={!canWrite || planBusy} />
|
<ToggleSwitch label="i18n:govoplan-admin.drop_uninstalls_from_startup.0e2a5c5b" checked={requestOptions.removeUninstalledModulesFromDesired} onChange={(checked) => setRequestOptions((current) => ({ ...current, removeUninstalledModulesFromDesired: checked }))} disabled={!canWrite || planBusy} />
|
||||||
<label><span>Health URLs</span><textarea value={requestOptions.healthUrls} disabled={!canWrite || planBusy} onChange={(event) => setRequestOptions((current) => ({ ...current, healthUrls: event.target.value }))} placeholder="http://127.0.0.1:8000/health" /></label>
|
<label><span>i18n:govoplan-admin.health_urls.3b86647c</span><textarea value={requestOptions.healthUrls} disabled={!canWrite || planBusy} onChange={(event) => setRequestOptions((current) => ({ ...current, healthUrls: event.target.value }))} placeholder="http://127.0.0.1:8000/health" /></label>
|
||||||
<label><span>Restart commands</span><textarea value={requestOptions.restartCommands} disabled={!canWrite || planBusy} onChange={(event) => setRequestOptions((current) => ({ ...current, restartCommands: event.target.value }))} placeholder="systemctl restart govoplan.service" /></label>
|
<label><span>i18n:govoplan-admin.restart_commands.035997f9</span><textarea value={requestOptions.restartCommands} disabled={!canWrite || planBusy} onChange={(event) => setRequestOptions((current) => ({ ...current, restartCommands: event.target.value }))} placeholder="systemctl restart govoplan.service" /></label>
|
||||||
<label><span>DB backup command</span><textarea value={requestOptions.databaseBackupCommand} disabled={!canWrite || planBusy} onChange={(event) => setRequestOptions((current) => ({ ...current, databaseBackupCommand: event.target.value }))} placeholder={"pg_dump --format=custom \"$GOVOPLAN_DATABASE_URL\" > \"$GOVOPLAN_DATABASE_BACKUP_PATH\""} /></label>
|
<label><span>i18n:govoplan-admin.db_backup_command.5bded3ac</span><textarea value={requestOptions.databaseBackupCommand} disabled={!canWrite || planBusy} onChange={(event) => setRequestOptions((current) => ({ ...current, databaseBackupCommand: event.target.value }))} placeholder={"pg_dump --format=custom \"$GOVOPLAN_DATABASE_URL\" > \"$GOVOPLAN_DATABASE_BACKUP_PATH\""} /></label>
|
||||||
<label><span>DB restore command</span><textarea value={requestOptions.databaseRestoreCommand} disabled={!canWrite || planBusy} onChange={(event) => setRequestOptions((current) => ({ ...current, databaseRestoreCommand: event.target.value }))} placeholder={"pg_restore --clean --if-exists --dbname \"$GOVOPLAN_DATABASE_URL\" \"$GOVOPLAN_DATABASE_BACKUP_PATH\""} /></label>
|
<label><span>i18n:govoplan-admin.db_restore_command.de11eb01</span><textarea value={requestOptions.databaseRestoreCommand} disabled={!canWrite || planBusy} onChange={(event) => setRequestOptions((current) => ({ ...current, databaseRestoreCommand: event.target.value }))} placeholder={"pg_restore --clean --if-exists --dbname \"$GOVOPLAN_DATABASE_URL\" \"$GOVOPLAN_DATABASE_BACKUP_PATH\""} /></label>
|
||||||
<label><span>DB restore-check command</span><textarea value={requestOptions.databaseRestoreCheckCommand} disabled={!canWrite || planBusy} onChange={(event) => setRequestOptions((current) => ({ ...current, databaseRestoreCheckCommand: event.target.value }))} placeholder={"pg_restore --list \"$GOVOPLAN_DATABASE_BACKUP_PATH\" >/dev/null"} /></label>
|
<label><span>i18n:govoplan-admin.db_restore_check_command.750ae936</span><textarea value={requestOptions.databaseRestoreCheckCommand} disabled={!canWrite || planBusy} onChange={(event) => setRequestOptions((current) => ({ ...current, databaseRestoreCheckCommand: event.target.value }))} placeholder={"pg_restore --list \"$GOVOPLAN_DATABASE_BACKUP_PATH\" >/dev/null"} /></label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>}
|
</div>}
|
||||||
@@ -428,17 +578,17 @@ export default function ModuleManagementPanel({ settings, canWrite, canAccessMai
|
|||||||
{installerRequests && <div className="module-installer-runs">
|
{installerRequests && <div className="module-installer-runs">
|
||||||
<div className="module-installer-runs-heading">
|
<div className="module-installer-runs-heading">
|
||||||
<div>
|
<div>
|
||||||
<h2>Installer requests</h2>
|
<h2>i18n:govoplan-admin.installer_requests.b88db439</h2>
|
||||||
<p>Queued daemon handoffs created from the admin UI or CLI.</p>
|
<p>i18n:govoplan-admin.queued_daemon_handoffs_created_from_the_admin_ui.d7ae7914</p>
|
||||||
</div>
|
</div>
|
||||||
<StatusBadge
|
<StatusBadge
|
||||||
status={installerRequests.daemon.running ? statusTone(installerRequests.daemon.status) : "inactive"}
|
status={installerRequests.daemon.running ? statusTone(installerRequests.daemon.status) : "inactive"}
|
||||||
label={installerRequests.daemon.running ? `Daemon: ${installerRequests.daemon.status}` : "Daemon offline"}
|
label={installerRequests.daemon.running ? i18nMessage("i18n:govoplan-admin.daemon_value.e54f5a31", { value0: installerRequests.daemon.status }) : "i18n:govoplan-admin.daemon_offline.314f19a4"} />
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{installerRequests.requests.length === 0 && <div className="module-install-plan-empty">No installer requests recorded yet.</div>}
|
{installerRequests.requests.length === 0 && <div className="module-install-plan-empty">i18n:govoplan-admin.no_installer_requests_recorded_yet.17e630fa</div>}
|
||||||
{installerRequests.requests.length > 0 && <div className="module-installer-run-list">
|
{installerRequests.requests.length > 0 && <div className="module-installer-run-list">
|
||||||
{installerRequests.requests.map((request) => (
|
{installerRequests.requests.map((request) =>
|
||||||
<div key={request.request_id} className="module-installer-run-row">
|
<div key={request.request_id} className="module-installer-run-row">
|
||||||
<div className="module-installer-run-main">
|
<div className="module-installer-run-main">
|
||||||
<div className="module-installer-run-title">
|
<div className="module-installer-run-title">
|
||||||
@@ -446,70 +596,175 @@ export default function ModuleManagementPanel({ settings, canWrite, canAccessMai
|
|||||||
<StatusBadge status={statusTone(request.status)} label={request.status} />
|
<StatusBadge status={statusTone(request.status)} label={request.status} />
|
||||||
</div>
|
</div>
|
||||||
<div className="module-management-details">
|
<div className="module-management-details">
|
||||||
<span>Created: {formatDateTime(request.created_at)}</span>
|
<span>i18n:govoplan-admin.created.0c78dab1 {formatDateTime(request.created_at)}</span>
|
||||||
<span>Started: {formatDateTime(request.started_at)}</span>
|
<span>i18n:govoplan-admin.started.fe3824e9 {formatDateTime(request.started_at)}</span>
|
||||||
<span>Finished: {formatDateTime(request.finished_at)}</span>
|
<span>i18n:govoplan-admin.finished.4b52fe3f {formatDateTime(request.finished_at)}</span>
|
||||||
{request.cancelled_at && <span>Cancelled: {formatDateTime(request.cancelled_at)}</span>}
|
{request.cancelled_at && <span>i18n:govoplan-admin.cancelled.8c9dcfa8 {formatDateTime(request.cancelled_at)}</span>}
|
||||||
{request.retry_of && <span>Retry of: {request.retry_of}</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>
|
</div>
|
||||||
{request.error && <p className="module-installer-run-error">{request.error}</p>}
|
{request.error && <p className="module-installer-run-error">{request.error}</p>}
|
||||||
</div>
|
</div>
|
||||||
<div className="module-installer-run-actions">
|
<div className="module-installer-run-actions">
|
||||||
{request.status === "queued" && <Button onClick={() => void cancelInstallerRequest(request.request_id)} disabled={!canWrite || !canAccessMaintenance || !maintenanceEnabled || planBusy}>Cancel</Button>}
|
{request.status === "queued" && <Button onClick={() => void cancelInstallerRequest(request.request_id)} disabled={!canWrite || !canAccessMaintenance || !maintenanceEnabled || planBusy}>i18n:govoplan-admin.cancel.77dfd213</Button>}
|
||||||
{(request.status === "failed" || request.status === "cancelled") && <Button onClick={() => void retryInstallerRequest(request.request_id)} disabled={!canWrite || !canAccessMaintenance || !maintenanceEnabled || planBusy}>Retry</Button>}
|
{(request.status === "failed" || request.status === "cancelled") && <Button onClick={() => void retryInstallerRequest(request.request_id)} disabled={!canWrite || !canAccessMaintenance || !maintenanceEnabled || planBusy}>i18n:govoplan-admin.retry.9f5cd8a2</Button>}
|
||||||
{request.record_path && <code>{request.record_path}</code>}
|
{request.record_path && <code>{request.record_path}</code>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
)}
|
||||||
</div>}
|
</div>}
|
||||||
</div>}
|
</div>}
|
||||||
|
|
||||||
{installerRuns && <div className="module-installer-runs">
|
{installerRuns && <div className="module-installer-runs">
|
||||||
<div className="module-installer-runs-heading">
|
<div className="module-installer-runs-heading">
|
||||||
<div>
|
<div>
|
||||||
<h2>Installer runs</h2>
|
<h2>i18n:govoplan-admin.installer_runs.e9e344e1</h2>
|
||||||
<p>Recent supervised installer records and the current package-install lock.</p>
|
<p>i18n:govoplan-admin.recent_supervised_installer_records_and_the_curr.29860245</p>
|
||||||
</div>
|
</div>
|
||||||
<StatusBadge status={installerRuns.lock.locked ? "warning" : "success"} label={installerRuns.lock.locked ? "Locked" : "Unlocked"} />
|
<StatusBadge status={installerRuns.lock.locked ? "warning" : "success"} label={installerRuns.lock.locked ? "i18n:govoplan-admin.locked.a798882f" : "i18n:govoplan-admin.unlocked.b3da7025"} />
|
||||||
</div>
|
</div>
|
||||||
{installerRuns.runs.length === 0 && <div className="module-install-plan-empty">No installer runs recorded yet.</div>}
|
{installerRuns.runs.length === 0 && <div className="module-install-plan-empty">i18n:govoplan-admin.no_installer_runs_recorded_yet.f8b61964</div>}
|
||||||
{installerRuns.runs.length > 0 && <div className="module-installer-run-list">
|
{installerRuns.runs.length > 0 && <div className="module-installer-run-list">
|
||||||
{installerRuns.runs.map((run) => (
|
{installerRuns.runs.map((run) =>
|
||||||
<div key={run.run_id} className="module-installer-run-row">
|
<div key={run.run_id} className="module-installer-run-row">
|
||||||
<div className="module-installer-run-main">
|
<div className="module-installer-run-main">
|
||||||
<div className="module-installer-run-title">
|
<div className="module-installer-run-title">
|
||||||
<strong>{run.run_id}</strong>
|
<strong>{run.run_id}</strong>
|
||||||
<StatusBadge status={statusTone(run.status)} label={run.status} />
|
<StatusBadge status={statusTone(run.status)} label={run.status} />
|
||||||
{run.supervisor_status && <StatusBadge status={statusTone(run.supervisor_status)} label={`Supervisor: ${run.supervisor_status}`} />}
|
{run.supervisor_status && <StatusBadge status={statusTone(run.supervisor_status)} label={i18nMessage("i18n:govoplan-admin.supervisor_value.d9f0c8eb", { value0: run.supervisor_status })} />}
|
||||||
{run.rollback_status && <StatusBadge status={statusTone(run.rollback_status)} label={`Rollback: ${run.rollback_status}`} />}
|
{run.rollback_status && <StatusBadge status={statusTone(run.rollback_status)} label={i18nMessage("i18n:govoplan-admin.rollback_value.47e9c00b", { value0: run.rollback_status })} />}
|
||||||
</div>
|
</div>
|
||||||
<div className="module-management-details">
|
<div className="module-management-details">
|
||||||
<span>Modules: {run.planned_modules.length ? run.planned_modules.join(", ") : "none"}</span>
|
<span>i18n:govoplan-admin.modules.2dc3b299 {run.planned_modules.length ? run.planned_modules.join(", ") : "none"}</span>
|
||||||
<span>Commands: {run.commands_count}</span>
|
{run.request_id && <span>i18n:govoplan-admin.request.43c2e7a7 {run.request_id}</span>}
|
||||||
<span>Started: {formatDateTime(run.started_at)}</span>
|
{traceId(run.trace) && <span>i18n:govoplan-admin.trace.04a75036 <code>{traceId(run.trace)}</code></span>}
|
||||||
<span>Finished: {formatDateTime(run.finished_at)}</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>
|
||||||
</div>
|
</div>
|
||||||
{run.error && <p className="module-installer-run-error">{run.error}</p>}
|
{run.error && <p className="module-installer-run-error">{run.error}</p>}
|
||||||
</div>
|
</div>
|
||||||
<code>{run.record_path}</code>
|
<code>{run.record_path}</code>
|
||||||
</div>
|
</div>
|
||||||
))}
|
)}
|
||||||
</div>}
|
</div>}
|
||||||
</div>}
|
</div>}
|
||||||
</>}
|
</>}
|
||||||
</AdminPageLayout>
|
</AdminPageLayout>);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function ModuleMetric({ label, value, detail, tone = "info" }: { label: string; value: string | number; detail: string; tone?: "info" | "good" | "warning" }) {
|
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>;
|
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 ModuleStatus({ module, desiredEnabled }: { module: ModuleCatalogItem; desiredEnabled: boolean }) {
|
function LicenseStatus({ license }: {license: ModuleLicenseDiagnostics;}) {
|
||||||
if (module.current_enabled && !desiredEnabled) return <StatusBadge status="warning" label="Will disable" />;
|
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";
|
||||||
if (!module.current_enabled && desiredEnabled) return <StatusBadge status="warning" label="Will enable" />;
|
return (
|
||||||
if (module.current_enabled) return <StatusBadge status="success" label="Active" />;
|
<div className="module-license-status">
|
||||||
return <StatusBadge status="inactive" label="Inactive" />;
|
<div className="button-row compact-actions">
|
||||||
|
<StatusBadge status={licenseTone(license)} label={licenseLabel(license)} />
|
||||||
|
<StatusBadge status={license.enforced ? "warning" : "inactive"} label={license.enforced ? "i18n:govoplan-admin.enforced.93ff909c" : "i18n:govoplan-admin.observe_only.c6a1f8bc"} />
|
||||||
|
{license.key_id && <StatusBadge status="inactive" label={i18nMessage("i18n:govoplan-admin.key_value.847b914f", { value0: license.key_id })} />}
|
||||||
|
</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.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>}
|
||||||
|
</div>
|
||||||
|
{license.reason && <p className={`module-package-catalog-description${license.allowed ? "" : " alert warning"}`}>{license.reason}</p>}
|
||||||
|
{license.guidance.map((item) => <p key={item} className="module-package-catalog-description">{item}</p>)}
|
||||||
|
</div>);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function providedInterfacesLabel(item: ModulePackageCatalogItem): string {
|
||||||
|
return item.provides_interfaces.map((provided) => `${provided.name}@${provided.version}`).join(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
function requiredInterfacesLabel(item: ModulePackageCatalogItem): string {
|
||||||
|
return item.requires_interfaces.map((required) => {
|
||||||
|
const range = [
|
||||||
|
required.version_min ? `>=${required.version_min}` : "",
|
||||||
|
required.version_max_exclusive ? `<${required.version_max_exclusive}` : "",
|
||||||
|
].filter(Boolean).join(" ");
|
||||||
|
const suffix = range ? ` ${range}` : "";
|
||||||
|
return `${required.optional ? "optional " : ""}${required.name}${suffix}`;
|
||||||
|
}).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" />;
|
||||||
|
if (module.current_enabled) return <StatusBadge status="success" label="i18n:govoplan-admin.active.a733b809" />;
|
||||||
|
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>) {
|
function sameSet(left: ReadonlySet<string>, right: ReadonlySet<string>) {
|
||||||
@@ -524,10 +779,13 @@ function emptyPlanItem(): ModuleInstallPlanItem {
|
|||||||
return {
|
return {
|
||||||
module_id: "",
|
module_id: "",
|
||||||
action: "install",
|
action: "install",
|
||||||
|
source: "manual",
|
||||||
|
catalog: null,
|
||||||
python_package: "",
|
python_package: "",
|
||||||
python_ref: "",
|
python_ref: "",
|
||||||
webui_package: "",
|
webui_package: "",
|
||||||
webui_ref: "",
|
webui_ref: "",
|
||||||
|
data_safety_acknowledged: false,
|
||||||
destroy_data: false,
|
destroy_data: false,
|
||||||
status: "planned",
|
status: "planned",
|
||||||
notes: ""
|
notes: ""
|
||||||
@@ -535,19 +793,22 @@ function emptyPlanItem(): ModuleInstallPlanItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function normalizePlanItems(items: ModuleInstallPlanItem[]): ModuleInstallPlanItem[] {
|
function normalizePlanItems(items: ModuleInstallPlanItem[]): ModuleInstallPlanItem[] {
|
||||||
return items
|
return items.
|
||||||
.map((item) => ({
|
map((item) => ({
|
||||||
module_id: item.module_id.trim(),
|
module_id: item.module_id.trim(),
|
||||||
action: item.action,
|
action: item.action,
|
||||||
|
source: item.source === "catalog" ? "catalog" : "manual",
|
||||||
|
catalog: item.source === "catalog" ? item.catalog ?? null : null,
|
||||||
python_package: cleanOptional(item.python_package),
|
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_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),
|
destroy_data: item.action === "uninstall" && Boolean(item.destroy_data),
|
||||||
status: item.status,
|
status: item.status,
|
||||||
notes: cleanOptional(item.notes)
|
notes: cleanOptional(item.notes)
|
||||||
}))
|
})).
|
||||||
.filter((item) => item.module_id || item.python_package || item.python_ref || item.webui_package || item.webui_ref || item.notes);
|
filter((item) => item.module_id || item.python_package || item.python_ref || item.webui_package || item.webui_ref || item.notes);
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanOptional(value?: string | null): string | null {
|
function cleanOptional(value?: string | null): string | null {
|
||||||
@@ -557,12 +818,12 @@ function cleanOptional(value?: string | null): string | null {
|
|||||||
|
|
||||||
function planValidationError(items: ModuleInstallPlanItem[]): string {
|
function planValidationError(items: ModuleInstallPlanItem[]): string {
|
||||||
for (const item of normalizePlanItems(items)) {
|
for (const item of normalizePlanItems(items)) {
|
||||||
if (!item.module_id) return "Every plan item needs a module id.";
|
if (!item.module_id) return "i18n:govoplan-admin.every_plan_item_needs_a_module_id.23a7f206";
|
||||||
if (item.action === "install" && !item.python_ref) return `${item.module_id} needs a Python package reference.`;
|
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 === "install" && item.python_ref && !item.python_package) return `${item.module_id} needs a Python package name for rollback.`;
|
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 `${item.module_id} needs a Python package name for uninstall.`;
|
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 `${item.module_id} needs both WebUI package and WebUI reference, or neither.`;
|
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 `${item.module_id} has a WebUI reference but no WebUI package.`;
|
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 "";
|
return "";
|
||||||
}
|
}
|
||||||
@@ -575,20 +836,49 @@ function statusTone(status: string): string {
|
|||||||
return "inactive";
|
return "inactive";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function licenseTone(license: ModuleLicenseDiagnostics): string {
|
||||||
|
if (!license.valid || !license.allowed || license.missing_features.length > 0) return "error";
|
||||||
|
if (!license.configured || !license.trusted || license.expires_in_days !== null && license.expires_in_days !== undefined && license.expires_in_days <= 30) return "warning";
|
||||||
|
return "success";
|
||||||
|
}
|
||||||
|
|
||||||
|
function licenseLabel(license: ModuleLicenseDiagnostics): string {
|
||||||
|
if (!license.configured) return "i18n:govoplan-admin.no_license.eb4bfe19";
|
||||||
|
if (!license.valid) return "i18n:govoplan-admin.invalid_license.f16bf749";
|
||||||
|
if (license.missing_features.length > 0) return "i18n:govoplan-admin.missing_entitlement.0cafa611";
|
||||||
|
if (license.trusted) return "i18n:govoplan-admin.valid_and_trusted.73cf89bc";
|
||||||
|
if (license.signed) return "i18n:govoplan-admin.signed_untrusted.864ed50c";
|
||||||
|
return "i18n:govoplan-admin.unsigned.e91344ea";
|
||||||
|
}
|
||||||
|
|
||||||
function formatDateTime(value?: string | null): string {
|
function formatDateTime(value?: string | null): string {
|
||||||
if (!value) return "not recorded";
|
if (!value) return "i18n:govoplan-admin.not_recorded.9925ee3c";
|
||||||
const date = new Date(value);
|
const date = new Date(value);
|
||||||
if (Number.isNaN(date.getTime())) return value;
|
if (Number.isNaN(date.getTime())) return value;
|
||||||
return date.toLocaleString();
|
return date.toLocaleString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function traceId(value?: Record<string, unknown> | null): string {
|
||||||
|
const correlationId = value?.correlation_id;
|
||||||
|
return typeof correlationId === "string" && correlationId.trim() ? correlationId : "";
|
||||||
|
}
|
||||||
|
|
||||||
function installerCommand(frontendRebuildRequired: boolean): string {
|
function installerCommand(frontendRebuildRequired: boolean): string {
|
||||||
const buildFlag = frontendRebuildRequired ? " --build-webui" : "";
|
const buildFlag = frontendRebuildRequired ? " --build-webui" : "";
|
||||||
return [
|
return [
|
||||||
"govoplan-module-installer --format shell",
|
"govoplan-module-installer --format shell",
|
||||||
`govoplan-module-installer --daemon --health-url http://127.0.0.1:8000/health --restart-command '<restart govoplan server>'`,
|
`govoplan-module-installer --daemon --health-url http://127.0.0.1:8000/health --restart-command '<restart govoplan server>'`,
|
||||||
`govoplan-module-installer --enqueue-supervised --migrate${buildFlag} --health-url http://127.0.0.1:8000/health --restart-command '<restart govoplan server>'`
|
`govoplan-module-installer --enqueue-supervised --migrate${buildFlag} --health-url http://127.0.0.1:8000/health --restart-command '<restart govoplan server>'`].
|
||||||
].join("\n");
|
join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
type ModuleStateRequestState = {
|
||||||
|
request: ConfigurationChangeRequest;
|
||||||
|
payloadKey: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function moduleStatePayloadKey(enabledModules: string[]): string {
|
||||||
|
return JSON.stringify(enabledModules);
|
||||||
}
|
}
|
||||||
|
|
||||||
type InstallerRequestFormOptions = {
|
type InstallerRequestFormOptions = {
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ import { Button } from "@govoplan/core-webui";
|
|||||||
import { Card } from "@govoplan/core-webui";
|
import { Card } from "@govoplan/core-webui";
|
||||||
import { FormField } from "@govoplan/core-webui";
|
import { FormField } from "@govoplan/core-webui";
|
||||||
import { ToggleSwitch } from "@govoplan/core-webui";
|
import { ToggleSwitch } from "@govoplan/core-webui";
|
||||||
import { fetchSystemSettings, updateSystemSettings, type PrivacyRetentionLimitPermissions, type PrivacyRetentionPolicy, type SystemSettingsItem } from "../../api/admin";
|
import { fetchSystemSettingsDelta, updateSystemSettings, type LanguagePackage, type PrivacyRetentionLimitPermissions, type PrivacyRetentionPolicy, type SystemSettingsDeltaSections, type SystemSettingsItem } from "../../api/admin";
|
||||||
import { AdminPageLayout, adminErrorMessage } from "@govoplan/core-webui";
|
import { AdminPageLayout, adminErrorMessage, useDeltaWatermarks, useUnsavedDraftGuard } from "@govoplan/core-webui";
|
||||||
|
|
||||||
|
const DELTA_KEY = "admin:system-settings";
|
||||||
|
|
||||||
const defaultAllowLowerLevelLimits: PrivacyRetentionLimitPermissions = {
|
const defaultAllowLowerLevelLimits: PrivacyRetentionLimitPermissions = {
|
||||||
store_raw_campaign_json: true,
|
store_raw_campaign_json: true,
|
||||||
@@ -35,86 +37,220 @@ const fallback: SystemSettingsItem = {
|
|||||||
allow_tenant_api_keys: true,
|
allow_tenant_api_keys: true,
|
||||||
privacy_retention_policy: defaultPrivacyPolicy,
|
privacy_retention_policy: defaultPrivacyPolicy,
|
||||||
maintenance_mode: { enabled: false, message: "" },
|
maintenance_mode: { enabled: false, message: "" },
|
||||||
|
available_languages: [
|
||||||
|
{ code: "en", label: "English", native_label: "English" },
|
||||||
|
{ code: "de", label: "German", native_label: "Deutsch" }
|
||||||
|
],
|
||||||
|
enabled_language_codes: ["en", "de"],
|
||||||
settings: {}
|
settings: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function SystemSettingsPanel({ settings, canWrite, canAccessMaintenance }: { settings: ApiSettings; canWrite: boolean; canAccessMaintenance: boolean }) {
|
export default function SystemSettingsPanel({ settings, canWrite, canAccessMaintenance }: {settings: ApiSettings;canWrite: boolean;canAccessMaintenance: boolean;}) {
|
||||||
const [draft, setDraft] = useState<SystemSettingsItem>(fallback);
|
const [draft, setDraft] = useState<SystemSettingsItem>(fallback);
|
||||||
|
const [savedDraft, setSavedDraft] = useState<SystemSettingsItem>(fallback);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [busy, setBusy] = useState(false);
|
const [busy, setBusy] = useState(false);
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
const [success, setSuccess] = useState("");
|
const [success, setSuccess] = useState("");
|
||||||
|
const [packageDraft, setPackageDraft] = useState({ code: "", label: "", nativeLabel: "" });
|
||||||
|
const { getDeltaWatermark, setDeltaWatermark, resetDeltaWatermark } = useDeltaWatermarks();
|
||||||
|
const defaultLocaleOptions = localeOptions(draft.default_locale, draft.enabled_language_codes);
|
||||||
|
const dirty = systemSettingsDraftKey(draft) !== systemSettingsDraftKey(savedDraft);
|
||||||
|
|
||||||
|
useUnsavedDraftGuard({
|
||||||
|
dirty,
|
||||||
|
onSave: save,
|
||||||
|
onDiscard: () => {
|
||||||
|
setDraft(savedDraft);
|
||||||
|
setPackageDraft({ code: "", label: "", nativeLabel: "" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
async function load() {
|
async function load() {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError("");
|
setError("");
|
||||||
try {
|
try {
|
||||||
const loaded = await fetchSystemSettings(settings);
|
const wasDirty = systemSettingsDraftKey(draft) !== systemSettingsDraftKey(savedDraft);
|
||||||
setDraft(loaded);
|
const response = await fetchSystemSettingsDelta(settings, { since: getDeltaWatermark(DELTA_KEY) });
|
||||||
|
setDeltaWatermark(DELTA_KEY, response.watermark);
|
||||||
|
if (response.full && response.item) {
|
||||||
|
setSavedDraft(response.item);
|
||||||
|
if (!wasDirty) setDraft(response.item);
|
||||||
|
} else if (response.changed_sections.length) {
|
||||||
|
setSavedDraft((current) => applySystemSettingsSections(current, response.sections));
|
||||||
|
if (!wasDirty) setDraft((current) => applySystemSettingsSections(current, response.sections));
|
||||||
}
|
}
|
||||||
catch (err) { setError(adminErrorMessage(err)); }
|
}
|
||||||
finally { setLoading(false); }
|
catch (err) {setError(adminErrorMessage(err));} finally
|
||||||
|
{setLoading(false);}
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => { void load(); }, [settings.accessToken, settings.apiBaseUrl]);
|
useEffect(() => {
|
||||||
|
resetDeltaWatermark(DELTA_KEY);
|
||||||
|
void load();
|
||||||
|
}, [settings.accessToken, settings.apiBaseUrl, resetDeltaWatermark]);
|
||||||
|
|
||||||
async function save() {
|
async function save(): Promise<boolean> {
|
||||||
setBusy(true);
|
setBusy(true);
|
||||||
setError("");
|
setError("");
|
||||||
try {
|
try {
|
||||||
setDraft(await updateSystemSettings(settings, {
|
const saved = await updateSystemSettings(settings, {
|
||||||
default_locale: draft.default_locale,
|
default_locale: draft.default_locale,
|
||||||
allow_tenant_custom_groups: draft.allow_tenant_custom_groups,
|
allow_tenant_custom_groups: draft.allow_tenant_custom_groups,
|
||||||
allow_tenant_custom_roles: draft.allow_tenant_custom_roles,
|
allow_tenant_custom_roles: draft.allow_tenant_custom_roles,
|
||||||
allow_tenant_api_keys: draft.allow_tenant_api_keys,
|
allow_tenant_api_keys: draft.allow_tenant_api_keys,
|
||||||
maintenance_mode: draft.maintenance_mode
|
maintenance_mode: draft.maintenance_mode,
|
||||||
}));
|
available_languages: draft.available_languages,
|
||||||
setSuccess("System settings saved.");
|
enabled_language_codes: draft.enabled_language_codes
|
||||||
} catch (err) { setError(adminErrorMessage(err)); }
|
});
|
||||||
finally { setBusy(false); }
|
setDraft(saved);
|
||||||
|
setSavedDraft(saved);
|
||||||
|
resetDeltaWatermark(DELTA_KEY);
|
||||||
|
setSuccess("i18n:govoplan-admin.system_settings_saved.dac4f17b");
|
||||||
|
return true;
|
||||||
|
} catch (err) {setError(adminErrorMessage(err));return false;} finally
|
||||||
|
{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);
|
||||||
|
const nextEnabled = draft.available_languages.map((item) => item.code).filter((item) => enabled.has(item));
|
||||||
|
const defaultLocale = nextEnabled.includes(draft.default_locale) ? draft.default_locale : (nextEnabled[0] ?? draft.default_locale);
|
||||||
|
setDraft({ ...draft, enabled_language_codes: nextEnabled, default_locale: defaultLocale });
|
||||||
|
}
|
||||||
|
|
||||||
|
function installLanguagePackage() {
|
||||||
|
const code = normalizeLanguageCode(packageDraft.code);
|
||||||
|
if (!code || !packageDraft.label.trim()) return;
|
||||||
|
const nextPackage: LanguagePackage = {
|
||||||
|
code,
|
||||||
|
label: packageDraft.label.trim(),
|
||||||
|
native_label: packageDraft.nativeLabel.trim() || packageDraft.label.trim()
|
||||||
|
};
|
||||||
|
const existing = new Set(draft.available_languages.map((item) => item.code));
|
||||||
|
const available = existing.has(code)
|
||||||
|
? draft.available_languages.map((item) => item.code === code ? nextPackage : item)
|
||||||
|
: [...draft.available_languages, nextPackage];
|
||||||
|
const enabled = draft.enabled_language_codes.includes(code) ? draft.enabled_language_codes : [...draft.enabled_language_codes, code];
|
||||||
|
setDraft({ ...draft, available_languages: available, enabled_language_codes: enabled, default_locale: draft.default_locale || code });
|
||||||
|
setPackageDraft({ code: "", label: "", nativeLabel: "" });
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AdminPageLayout
|
<AdminPageLayout
|
||||||
title="System general settings"
|
title="i18n:govoplan-admin.system_general_settings.7cd662ed"
|
||||||
description="Instance-wide defaults and tenant governance capabilities. Retention policy management has its own system section."
|
description="i18n:govoplan-admin.instance_wide_defaults_and_tenant_governance_cap.096a4f97"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
error={error}
|
error={error}
|
||||||
success={success}
|
success={success}
|
||||||
actions={<><Button onClick={() => void load()} disabled={loading}>Reload</Button><Button variant="primary" onClick={() => void save()} disabled={!canWrite || busy}>{busy ? "Saving…" : "Save settings"}</Button></>}
|
actions={<><Button onClick={() => void load()} disabled={loading}>i18n:govoplan-admin.reload.cce71553</Button><Button variant="primary" onClick={() => void save()} disabled={!canWrite || busy}>{busy ? "i18n:govoplan-admin.saving.56a2285c" : "i18n:govoplan-admin.save_settings.913aba9f"}</Button></>}>
|
||||||
>
|
|
||||||
<div className="admin-settings-form">
|
<div className="admin-settings-form">
|
||||||
<Card title="Defaults for newly created tenants">
|
<Card title="i18n:govoplan-admin.defaults_for_newly_created_tenants.9d0f4ccd">
|
||||||
<FormField label="Default locale"><input value={draft.default_locale} onChange={(event) => setDraft({ ...draft, default_locale: event.target.value })} /></FormField>
|
<FormField label="i18n:govoplan-admin.default_locale.b99d021f">
|
||||||
|
<select value={draft.default_locale} onChange={(event) => setDraft({ ...draft, default_locale: event.target.value })} disabled={!canWrite || busy || defaultLocaleOptions.length === 0}>
|
||||||
|
{defaultLocaleOptions.map((code) => {
|
||||||
|
const language = draft.available_languages.find((item) => item.code === code);
|
||||||
|
return <option key={code} value={code}>{languageOptionLabel(language ?? { code, label: code.toUpperCase() })}</option>;
|
||||||
|
})}
|
||||||
|
</select>
|
||||||
|
</FormField>
|
||||||
</Card>
|
</Card>
|
||||||
<Card title="Tenant administration capabilities">
|
<Card title="i18n:govoplan-admin.language_packages">
|
||||||
<div className="settings-list">
|
<div className="settings-list">
|
||||||
<ToggleSwitch checked={draft.allow_tenant_custom_groups} onChange={(checked) => setDraft({ ...draft, allow_tenant_custom_groups: checked })} label="Allow tenant-defined groups by default" />
|
{draft.available_languages.map((item) => (
|
||||||
<ToggleSwitch checked={draft.allow_tenant_custom_roles} onChange={(checked) => setDraft({ ...draft, allow_tenant_custom_roles: checked })} label="Allow tenant-defined roles by default" />
|
<label className="admin-inline-check" key={item.code}>
|
||||||
<ToggleSwitch checked={draft.allow_tenant_api_keys} onChange={(checked) => setDraft({ ...draft, allow_tenant_api_keys: checked })} label="Allow tenant API keys by default" />
|
<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>
|
</div>
|
||||||
<p className="muted small-note">These settings are enforced by the backend. Central groups and tenant roles remain available even when local creation is disabled.</p>
|
<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 })} />
|
||||||
|
</FormField>
|
||||||
|
<FormField label="i18n:govoplan-admin.language_name">
|
||||||
|
<input value={packageDraft.label} disabled={!canWrite || busy} placeholder="i18n:govoplan-admin.language_name_placeholder" onChange={(event) => setPackageDraft({ ...packageDraft, label: event.target.value })} />
|
||||||
|
</FormField>
|
||||||
|
<FormField label="i18n:govoplan-admin.native_language_name">
|
||||||
|
<input value={packageDraft.nativeLabel} disabled={!canWrite || busy} placeholder="i18n:govoplan-admin.native_language_name_placeholder" onChange={(event) => setPackageDraft({ ...packageDraft, nativeLabel: event.target.value })} />
|
||||||
|
</FormField>
|
||||||
|
<div className="button-row compact-actions">
|
||||||
|
<Button onClick={installLanguagePackage} disabled={!canWrite || busy || !normalizeLanguageCode(packageDraft.code) || !packageDraft.label.trim()}>i18n:govoplan-admin.install_language_package</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className="muted small-note">i18n:govoplan-admin.language_packages_help</p>
|
||||||
</Card>
|
</Card>
|
||||||
<Card title="Maintenance mode">
|
<Card title="i18n:govoplan-admin.tenant_administration_capabilities.5d265972">
|
||||||
|
<div className="settings-list">
|
||||||
|
<ToggleSwitch checked={draft.allow_tenant_custom_groups} onChange={(checked) => setDraft({ ...draft, allow_tenant_custom_groups: checked })} label="i18n:govoplan-admin.allow_tenant_defined_groups_by_default.32099d5a" />
|
||||||
|
<ToggleSwitch checked={draft.allow_tenant_custom_roles} onChange={(checked) => setDraft({ ...draft, allow_tenant_custom_roles: checked })} label="i18n:govoplan-admin.allow_tenant_defined_roles_by_default.b1f79ee9" />
|
||||||
|
<ToggleSwitch checked={draft.allow_tenant_api_keys} onChange={(checked) => setDraft({ ...draft, allow_tenant_api_keys: checked })} label="i18n:govoplan-admin.allow_tenant_api_keys_by_default.58a6cf17" />
|
||||||
|
</div>
|
||||||
|
<p className="muted small-note">i18n:govoplan-admin.these_settings_are_enforced_by_the_backend_centr.8112ed6f</p>
|
||||||
|
</Card>
|
||||||
|
<Card title="i18n:govoplan-admin.maintenance_mode.98cca5c6">
|
||||||
<div className="settings-list">
|
<div className="settings-list">
|
||||||
<ToggleSwitch
|
<ToggleSwitch
|
||||||
checked={draft.maintenance_mode.enabled}
|
checked={draft.maintenance_mode.enabled}
|
||||||
disabled={!canWrite || !canAccessMaintenance}
|
disabled={!canWrite || !canAccessMaintenance}
|
||||||
onChange={(checked) => setDraft({ ...draft, maintenance_mode: { ...draft.maintenance_mode, enabled: checked } })}
|
onChange={(checked) => setDraft({ ...draft, maintenance_mode: { ...draft.maintenance_mode, enabled: checked } })}
|
||||||
label="Restrict authenticated API access to maintenance operators"
|
label="i18n:govoplan-admin.restrict_authenticated_api_access_to_maintenance.2bc47195" />
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<FormField label="Maintenance message">
|
<FormField label="i18n:govoplan-admin.maintenance_message.ca62571f">
|
||||||
<textarea
|
<textarea
|
||||||
rows={3}
|
rows={3}
|
||||||
value={draft.maintenance_mode.message ?? ""}
|
value={draft.maintenance_mode.message ?? ""}
|
||||||
disabled={!canWrite}
|
disabled={!canWrite}
|
||||||
onChange={(event) => setDraft({ ...draft, maintenance_mode: { ...draft.maintenance_mode, message: event.target.value } })}
|
onChange={(event) => setDraft({ ...draft, maintenance_mode: { ...draft.maintenance_mode, message: event.target.value } })} />
|
||||||
/>
|
|
||||||
</FormField>
|
</FormField>
|
||||||
<p className="muted small-note">Changing the maintenance-mode flag requires system:maintenance:access. Login remains available so an operator can sign in during maintenance.</p>
|
<p className="muted small-note">i18n:govoplan-admin.changing_the_maintenance_mode_flag_requires_syst.4492050f</p>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</AdminPageLayout>
|
</AdminPageLayout>);
|
||||||
);
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeLanguageCode(value: string): string {
|
||||||
|
return value.trim().toLowerCase().replace(/_/g, "-").split(/[^a-z0-9]+/).filter(Boolean).join("-");
|
||||||
|
}
|
||||||
|
|
||||||
|
function localeOptions(current: string, enabled: string[]): string[] {
|
||||||
|
return [...new Set([current, ...enabled].filter((item) => item && item.trim()))];
|
||||||
|
}
|
||||||
|
|
||||||
|
function languageOptionLabel(language: LanguagePackage): string {
|
||||||
|
return `${language.code.toUpperCase()} - ${language.native_label || language.label}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function systemSettingsDraftKey(item: SystemSettingsItem): string {
|
||||||
|
return JSON.stringify({
|
||||||
|
default_locale: item.default_locale,
|
||||||
|
allow_tenant_custom_groups: item.allow_tenant_custom_groups,
|
||||||
|
allow_tenant_custom_roles: item.allow_tenant_custom_roles,
|
||||||
|
allow_tenant_api_keys: item.allow_tenant_api_keys,
|
||||||
|
maintenance_mode: item.maintenance_mode,
|
||||||
|
available_languages: item.available_languages,
|
||||||
|
enabled_language_codes: item.enabled_language_codes
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function applySystemSettingsSections(item: SystemSettingsItem, sections: SystemSettingsDeltaSections): SystemSettingsItem {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
...(sections.defaults ?? {}),
|
||||||
|
...(sections.tenant_capabilities ?? {}),
|
||||||
|
...(sections.languages ?? {}),
|
||||||
|
...(sections.privacy_retention_policy ? { privacy_retention_policy: sections.privacy_retention_policy } : {}),
|
||||||
|
...(sections.maintenance_mode ? { maintenance_mode: sections.maintenance_mode } : {}),
|
||||||
|
...(sections.settings ? { settings: sections.settings } : {})
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,782 @@
|
|||||||
|
import type { PlatformTranslations } from "@govoplan/core-webui";
|
||||||
|
|
||||||
|
export const generatedTranslations: PlatformTranslations = {
|
||||||
|
"en": {
|
||||||
|
"i18n:govoplan-admin.access_configuration_exported.098f200d": "Access configuration exported.",
|
||||||
|
"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.admin.4e7afebc": "Admin",
|
||||||
|
"i18n:govoplan-admin.administration.b8be3d12": "Administration",
|
||||||
|
"i18n:govoplan-admin.platform_administration": "Platform administration",
|
||||||
|
"i18n:govoplan-admin.alert_warning.804f617a": " alert warning",
|
||||||
|
"i18n:govoplan-admin.allow_tenant_api_keys_by_default.58a6cf17": "Allow tenant API keys by default",
|
||||||
|
"i18n:govoplan-admin.allow_tenant_defined_groups_by_default.32099d5a": "Allow tenant-defined groups by default",
|
||||||
|
"i18n:govoplan-admin.allow_tenant_defined_roles_by_default.b1f79ee9": "Allow tenant-defined roles by default",
|
||||||
|
"i18n:govoplan-admin.api_keys.94fcf3c2": "API keys",
|
||||||
|
"i18n:govoplan-admin.applied.a3e4a569": "Applied",
|
||||||
|
"i18n:govoplan-admin.apply_approved_request.ab218d9a": "Apply approved request",
|
||||||
|
"i18n:govoplan-admin.apply_finished_with_blockers.78487a12": "Apply finished with blockers.",
|
||||||
|
"i18n:govoplan-admin.apply_result.0fde1c3c": "Apply result",
|
||||||
|
"i18n:govoplan-admin.apply.cfea419c": "Apply",
|
||||||
|
"i18n:govoplan-admin.approvals.deb9d03c": "Approvals",
|
||||||
|
"i18n:govoplan-admin.approve.7b2c7f14": "Approve",
|
||||||
|
"i18n:govoplan-admin.approved_package_references_that_can_be_added_to.cc911c16": "Approved package references that can be added to the operator install plan.",
|
||||||
|
"i18n:govoplan-admin.approved_value.52da808b": "Approved {value0}.",
|
||||||
|
"i18n:govoplan-admin.approvers.0e2de1fb": "Approvers",
|
||||||
|
"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",
|
||||||
|
"i18n:govoplan-admin.cancelled.8c9dcfa8": "Cancelled:",
|
||||||
|
"i18n:govoplan-admin.case_clerk.b78a314a": "Case clerk",
|
||||||
|
"i18n:govoplan-admin.catalog_install_entry_planned.13b23f4f": "Catalog install entry planned.",
|
||||||
|
"i18n:govoplan-admin.catalog.4a88d27b": "Catalog",
|
||||||
|
"i18n:govoplan-admin.manual.17a486ca": "Manual",
|
||||||
|
"i18n:govoplan-admin.central_groups.5c9b5b66": "Central groups",
|
||||||
|
"i18n:govoplan-admin.centrally_defined_group_identities_that_are_prov.7761fce9": "Centrally defined group identities that are provisioned into selected tenants as available or required definitions. Membership remains tenant-local.",
|
||||||
|
"i18n:govoplan-admin.centrally_governed_tenant_roles_and_their_availa.d879d5d1": "Centrally governed tenant roles and their availability across tenants.",
|
||||||
|
"i18n:govoplan-admin.centrally_governed_tenant_roles_that_are_provisi.8739fca3": "Centrally governed tenant roles that are provisioned into selected tenants as available or required definitions.",
|
||||||
|
"i18n:govoplan-admin.centrally_governed_tenant_roles.797c689b": "Centrally governed tenant roles.",
|
||||||
|
"i18n:govoplan-admin.change_request_created_value.4af6d3d2": "Change request created: {value0}",
|
||||||
|
"i18n:govoplan-admin.change_request_id.96ee3239": "Change request id",
|
||||||
|
"i18n:govoplan-admin.change_request_ready.b17e8c57": "Change request ready:",
|
||||||
|
"i18n:govoplan-admin.changes.8aa57de6": "Changes",
|
||||||
|
"i18n:govoplan-admin.changing_enabled_modules_requires_a_dry_run_chan.10b6f5ed": "Changing enabled modules requires a dry-run change request and active maintenance mode.",
|
||||||
|
"i18n:govoplan-admin.changing_the_maintenance_mode_flag_requires_syst.4492050f": "Changing the maintenance-mode flag requires system:maintenance:access. Login remains available so an operator can sign in during maintenance.",
|
||||||
|
"i18n:govoplan-admin.channel_value.d3894efe": "Channel: {value0}",
|
||||||
|
"i18n:govoplan-admin.clear.719ea396": "Clear",
|
||||||
|
"i18n:govoplan-admin.close.bbfa773e": "Close",
|
||||||
|
"i18n:govoplan-admin.code.adac6937": "Code",
|
||||||
|
"i18n:govoplan-admin.commands.f6a34aed": "Commands:",
|
||||||
|
"i18n:govoplan-admin.configuration_changes.82933bbb": "Configuration changes",
|
||||||
|
"i18n:govoplan-admin.configuration_package_apply.c270e5f3": "Configuration package apply",
|
||||||
|
"i18n:govoplan-admin.configuration_packages.eb2f05f1": "Configuration packages",
|
||||||
|
"i18n:govoplan-admin.configuration_requests_approvals_and_version_his.19f37335": "Configuration requests, approvals and version history.",
|
||||||
|
"i18n:govoplan-admin.configured_license.3e73f8f5": "Configured license",
|
||||||
|
"i18n:govoplan-admin.create_dry_run_request.05b9cbdb": "Create dry-run request",
|
||||||
|
"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.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",
|
||||||
|
"i18n:govoplan-admin.db_restore_check_command.750ae936": "DB restore-check command",
|
||||||
|
"i18n:govoplan-admin.db_restore_command.de11eb01": "DB restore command",
|
||||||
|
"i18n:govoplan-admin.db.e355c23a": "DB:",
|
||||||
|
"i18n:govoplan-admin.default_locale.b99d021f": "Default locale",
|
||||||
|
"i18n:govoplan-admin.install_language_package": "Install language package",
|
||||||
|
"i18n:govoplan-admin.language_code": "Language code",
|
||||||
|
"i18n:govoplan-admin.language_name": "Language name",
|
||||||
|
"i18n:govoplan-admin.language_name_placeholder": "French",
|
||||||
|
"i18n:govoplan-admin.language_packages": "Language packages",
|
||||||
|
"i18n:govoplan-admin.language_packages_help": "Installed packages define which language codes can be enabled. A package only becomes fully translated when matching module translation catalogs are present.",
|
||||||
|
"i18n:govoplan-admin.native_language_name": "Native language name",
|
||||||
|
"i18n:govoplan-admin.native_language_name_placeholder": "Francais",
|
||||||
|
"i18n:govoplan-admin.defaults_for_newly_created_tenants.9d0f4ccd": "Defaults for newly created tenants",
|
||||||
|
"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_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",
|
||||||
|
"i18n:govoplan-admin.drift.4876f7b9": "Drift",
|
||||||
|
"i18n:govoplan-admin.drop_uninstalls_from_startup.0e2a5c5b": "Drop uninstalls from startup",
|
||||||
|
"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_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...",
|
||||||
|
"i18n:govoplan-admin.enforced.93ff909c": "Enforced",
|
||||||
|
"i18n:govoplan-admin.every_plan_item_needs_a_module_id.23a7f206": "Every plan item needs a module id.",
|
||||||
|
"i18n:govoplan-admin.expires.2d21b7de": "· Expires:",
|
||||||
|
"i18n:govoplan-admin.export_access.9a2eb91e": "Export access",
|
||||||
|
"i18n:govoplan-admin.export_finished_with_blockers.e2f70611": "Export finished with blockers.",
|
||||||
|
"i18n:govoplan-admin.export.f3e4fadb": "Export",
|
||||||
|
"i18n:govoplan-admin.exported_system_access_configuration.2e1c6f4c": "Exported system access configuration",
|
||||||
|
"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",
|
||||||
|
"i18n:govoplan-admin.general.9239ee2c": "General",
|
||||||
|
"i18n:govoplan-admin.generated.a2edf57c": "Generated:",
|
||||||
|
"i18n:govoplan-admin.global": "Global",
|
||||||
|
"i18n:govoplan-admin.global_accounts_tenant_memberships_and_system_ro.939ed01f": "Global accounts, tenant memberships and system-role assignments.",
|
||||||
|
"i18n:govoplan-admin.global_login_accounts_across_all_tenants.2aab129d": "Global login accounts across all tenants.",
|
||||||
|
"i18n:govoplan-admin.govoplan_module_installer_format_shell.226d1450": "govoplan-module-installer --format shell",
|
||||||
|
"i18n:govoplan-admin.group_definitions_made_available_or_required_in_.55402e16": "Group definitions made available or required in selected tenants.",
|
||||||
|
"i18n:govoplan-admin.group_file_connector_policy_limits": "File connector policy limits for group-owned spaces.",
|
||||||
|
"i18n:govoplan-admin.group_mail_server_policy_limits": "Mail server policy limits for group-owned work.",
|
||||||
|
"i18n:govoplan-admin.group_retention_policy_limits": "Retention limits for group-owned records.",
|
||||||
|
"i18n:govoplan-admin.group_template.973e0fa6": "Group template",
|
||||||
|
"i18n:govoplan-admin.group.171a0606": "Group",
|
||||||
|
"i18n:govoplan-admin.groups.ae9629f4": "Groups",
|
||||||
|
"i18n:govoplan-admin.handles_incoming_administrative_work.dc80c349": "Handles incoming administrative work.",
|
||||||
|
"i18n:govoplan-admin.health_urls.3b86647c": "Health URLs",
|
||||||
|
"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",
|
||||||
|
"i18n:govoplan-admin.installed_modules_active_runtime_state_and_saved.f33907dd": "Installed modules, active runtime state, and saved startup state.",
|
||||||
|
"i18n:govoplan-admin.installed_modules_runtime_state_and_startup_stat.38dd7028": "Installed modules, runtime state and startup state.",
|
||||||
|
"i18n:govoplan-admin.installed.7bb4405c": "Installed",
|
||||||
|
"i18n:govoplan-admin.installer_preflight_blocked.cf83bf79": "Installer preflight blocked",
|
||||||
|
"i18n:govoplan-admin.installer_preflight_passed.6d5f8060": "Installer preflight passed",
|
||||||
|
"i18n:govoplan-admin.installer_request_cancelled_value.1d6d099d": "Installer request cancelled: {value0}",
|
||||||
|
"i18n:govoplan-admin.installer_request_queued_value.00726abb": "Installer request queued: {value0}",
|
||||||
|
"i18n:govoplan-admin.installer_requests.b88db439": "Installer requests",
|
||||||
|
"i18n:govoplan-admin.installer_runs.e9e344e1": "Installer runs",
|
||||||
|
"i18n:govoplan-admin.instance_defaults_and_tenant_governance_capabili.99d6b2fa": "Instance defaults and tenant governance capabilities.",
|
||||||
|
"i18n:govoplan-admin.instance_privacy_retention_policy_and_lower_leve.b8d14069": "Instance privacy retention policy and lower-level override permissions.",
|
||||||
|
"i18n:govoplan-admin.instance_wide_defaults_and_tenant_governance_cap.096a4f97": "Instance-wide defaults and tenant governance capabilities. Retention policy management has its own system section.",
|
||||||
|
"i18n:govoplan-admin.instance_wide_roles_assigned_directly_to_global_.91050488": "Instance-wide roles assigned directly to global accounts.",
|
||||||
|
"i18n:govoplan-admin.invalid_json.01ccb74f": "Invalid JSON.",
|
||||||
|
"i18n:govoplan-admin.invalid_license.f16bf749": "Invalid license",
|
||||||
|
"i18n:govoplan-admin.json_must_be_an_object.848569c9": "JSON must be an object.",
|
||||||
|
"i18n:govoplan-admin.key_value.847b914f": "Key: {value0}",
|
||||||
|
"i18n:govoplan-admin.key.c67dd20e": "Key",
|
||||||
|
"i18n:govoplan-admin.kind.e00ac23f": "Kind",
|
||||||
|
"i18n:govoplan-admin.label.74341e3c": "Label",
|
||||||
|
"i18n:govoplan-admin.last_request.4508ef35": "Last request:",
|
||||||
|
"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.",
|
||||||
|
"i18n:govoplan-admin.maintenance_mode_must_be_enabled_before_queueing.d233a32f": "Maintenance mode must be enabled before queueing a daemon request.",
|
||||||
|
"i18n:govoplan-admin.maintenance_mode.98cca5c6": "Maintenance mode",
|
||||||
|
"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:",
|
||||||
|
"i18n:govoplan-admin.module_install_plan_cleared.afa2698d": "Module install plan cleared.",
|
||||||
|
"i18n:govoplan-admin.module_install_plan_saved.5d43d944": "Module install plan saved.",
|
||||||
|
"i18n:govoplan-admin.module_maintenance_in_progress.f6395152": "Module maintenance in progress.",
|
||||||
|
"i18n:govoplan-admin.module_state_saved.e3e22105": "Module state saved.",
|
||||||
|
"i18n:govoplan-admin.module.b8ff0289": "Module",
|
||||||
|
"i18n:govoplan-admin.modules.04e9462c": "Modules",
|
||||||
|
"i18n:govoplan-admin.modules.2dc3b299": "Modules:",
|
||||||
|
"i18n:govoplan-admin.name.709a2322": "Name",
|
||||||
|
"i18n:govoplan-admin.needs_attention.a126722e": "Needs attention",
|
||||||
|
"i18n:govoplan-admin.no_applied_configuration_changes.6a3ae4a7": "No applied configuration changes.",
|
||||||
|
"i18n:govoplan-admin.no_central_value_templates_found.081149fa": "No central {value0} templates found.",
|
||||||
|
"i18n:govoplan-admin.no_diagnostics.2b6e2630": "No diagnostics.",
|
||||||
|
"i18n:govoplan-admin.no_installer_requests_recorded_yet.17e630fa": "No installer requests recorded yet.",
|
||||||
|
"i18n:govoplan-admin.no_installer_runs_recorded_yet.f8b61964": "No installer runs recorded yet.",
|
||||||
|
"i18n:govoplan-admin.no_license_configured.693cea1a": "No license configured",
|
||||||
|
"i18n:govoplan-admin.no_license.eb4bfe19": "No license",
|
||||||
|
"i18n:govoplan-admin.no_open_requests.580c95f9": "No open requests.",
|
||||||
|
"i18n:govoplan-admin.no_package_catalog_configured_set_govoplan_modul.2c5fceb3": "No package catalog configured. Set GOVOPLAN_MODULE_PACKAGE_CATALOG or GOVOPLAN_MODULE_PACKAGE_CATALOG_URL to enable curated install entries.",
|
||||||
|
"i18n:govoplan-admin.no_package_changes_planned.f12d8b33": "No package changes planned.",
|
||||||
|
"i18n:govoplan-admin.no_package_restart_pending.efe6b8ce": "No package restart pending",
|
||||||
|
"i18n:govoplan-admin.no_plan_items.7108c582": "No plan items.",
|
||||||
|
"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",
|
||||||
|
"i18n:govoplan-admin.observe_only.c6a1f8bc": "Observe-only",
|
||||||
|
"i18n:govoplan-admin.off.e3de5ab0": "Off",
|
||||||
|
"i18n:govoplan-admin.on.e0049a66": "On",
|
||||||
|
"i18n:govoplan-admin.operator_install_plan.b203aabc": "Operator install plan",
|
||||||
|
"i18n:govoplan-admin.optional.166b8369": "Optional:",
|
||||||
|
"i18n:govoplan-admin.overview.0efc2e6b": "Overview",
|
||||||
|
"i18n:govoplan-admin.owner.89ff3122": "Owner",
|
||||||
|
"i18n:govoplan-admin.package_applied.63782ce7": "Package applied.",
|
||||||
|
"i18n:govoplan-admin.package_catalog_is_configured_but_contains_no_en.b6b053cb": "Package catalog is configured but contains no entries.",
|
||||||
|
"i18n:govoplan-admin.package_catalog.b36e6d31": "Package catalog",
|
||||||
|
"i18n:govoplan-admin.package_json_must_be_an_object.db825bb3": "Package JSON must be an object.",
|
||||||
|
"i18n:govoplan-admin.package_json.a2b10f38": "Package JSON",
|
||||||
|
"i18n:govoplan-admin.package_uninstall_planned.56e8566d": "Package uninstall planned.",
|
||||||
|
"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",
|
||||||
|
"i18n:govoplan-admin.planned.9cbe42aa": "Planned",
|
||||||
|
"i18n:govoplan-admin.preflight_approve_apply_and_export_configuration.276bd0f8": "Preflight, approve, apply and export configuration packages.",
|
||||||
|
"i18n:govoplan-admin.preflight_finished_with_blockers.7e2ca12b": "Preflight finished with blockers.",
|
||||||
|
"i18n:govoplan-admin.preflight_passed.c0c99055": "Preflight passed.",
|
||||||
|
"i18n:govoplan-admin.preflight.8016a487": "Preflight",
|
||||||
|
"i18n:govoplan-admin.provides.221b70d9": "Provides:",
|
||||||
|
"i18n:govoplan-admin.python_package.34591c71": "Python package",
|
||||||
|
"i18n:govoplan-admin.python_ref.1af35d0d": "Python ref",
|
||||||
|
"i18n:govoplan-admin.queue_supervised_run.a53f248c": "Queue supervised run",
|
||||||
|
"i18n:govoplan-admin.queue_the_saved_plan_for_a_separate_installer_da.92d4c96e": "Queue the saved plan for a separate installer daemon process.",
|
||||||
|
"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",
|
||||||
|
"i18n:govoplan-admin.request.43c2e7a7": "Request:",
|
||||||
|
"i18n:govoplan-admin.request_approval.6245aea1": "Request approval",
|
||||||
|
"i18n:govoplan-admin.requested.c26bf60f": "Requested",
|
||||||
|
"i18n:govoplan-admin.requests.f7194e6a": "Requests",
|
||||||
|
"i18n:govoplan-admin.required_data.1b1c1b34": "Required data",
|
||||||
|
"i18n:govoplan-admin.required_means_the_definition_must_remain_presen.8462063c": "Required means the definition must remain present and system-controlled. It does not automatically assign users or grant permissions.",
|
||||||
|
"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.",
|
||||||
|
"i18n:govoplan-admin.reusable_file_server_connections_credentials_and.8e5c7d43": "Reusable file server connections, credentials and policy.",
|
||||||
|
"i18n:govoplan-admin.role.c3f104d1": "Role",
|
||||||
|
"i18n:govoplan-admin.roles.47dcc27d": "Roles",
|
||||||
|
"i18n:govoplan-admin.rollback_value.47e9c00b": "Rollback: {value0}",
|
||||||
|
"i18n:govoplan-admin.run_migrations.db6e0ce2": "Run migrations",
|
||||||
|
"i18n:govoplan-admin.running_registry.5274b24b": "Running registry",
|
||||||
|
"i18n:govoplan-admin.runtime_differs.43d1fd78": "Runtime differs",
|
||||||
|
"i18n:govoplan-admin.runtime_matches.a84afa48": "Runtime matches",
|
||||||
|
"i18n:govoplan-admin.safety_controlled_configuration_requests_approva.e8259509": "Safety-controlled configuration requests, approvals and rollback history.",
|
||||||
|
"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_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…",
|
||||||
|
"i18n:govoplan-admin.saving.ae7e8875": "Saving...",
|
||||||
|
"i18n:govoplan-admin.scoped_automation_credentials_capped_by_owner_pe.b3e20e54": "Scoped automation credentials capped by owner permissions.",
|
||||||
|
"i18n:govoplan-admin.secret.f4e7a874": "Secret",
|
||||||
|
"i18n:govoplan-admin.seq_value.0ae5fa47": "Seq {value0}",
|
||||||
|
"i18n:govoplan-admin.setting.fb449f71": "Setting",
|
||||||
|
"i18n:govoplan-admin.severity.de314fa0": "Severity",
|
||||||
|
"i18n:govoplan-admin.signed_and_trusted.bee31302": "Signed and trusted",
|
||||||
|
"i18n:govoplan-admin.signed_untrusted.864ed50c": "Signed, untrusted",
|
||||||
|
"i18n:govoplan-admin.signed.6e3665d8": "Signed",
|
||||||
|
"i18n:govoplan-admin.slug.094da9b9": "Slug",
|
||||||
|
"i18n:govoplan-admin.started.fe3824e9": "Started:",
|
||||||
|
"i18n:govoplan-admin.startup_state.d1300be6": "Startup state",
|
||||||
|
"i18n:govoplan-admin.status.bae7d5be": "Status",
|
||||||
|
"i18n:govoplan-admin.summary.12b71c3e": "Summary",
|
||||||
|
"i18n:govoplan-admin.supervisor_value.d9f0c8eb": "Supervisor: {value0}",
|
||||||
|
"i18n:govoplan-admin.supplied_data_json_must_be_an_object.b265eb92": "Supplied data JSON must be an object.",
|
||||||
|
"i18n:govoplan-admin.supplied_data_json.6932bfb7": "Supplied data JSON",
|
||||||
|
"i18n:govoplan-admin.system_administration.b151acea": "System administration",
|
||||||
|
"i18n:govoplan-admin.system_general_settings.7cd662ed": "System general settings",
|
||||||
|
"i18n:govoplan-admin.system_governed_group_definitions.de8e5dc9": "System-governed group definitions.",
|
||||||
|
"i18n:govoplan-admin.system_level_administrative_history.49f76723": "System-level administrative history.",
|
||||||
|
"i18n:govoplan-admin.system_modules.629539f2": "System modules",
|
||||||
|
"i18n:govoplan-admin.system_roles.a9461aa6": "System roles",
|
||||||
|
"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",
|
||||||
|
"i18n:govoplan-admin.tenant_administration.ffab51f1": "Tenant administration",
|
||||||
|
"i18n:govoplan-admin.tenant_availability.067a4f31": "Tenant availability",
|
||||||
|
"i18n:govoplan-admin.tenant_id.59eba244": "Tenant id",
|
||||||
|
"i18n:govoplan-admin.tenant_level_administrative_history_only.55495c3c": "Tenant-level administrative history only.",
|
||||||
|
"i18n:govoplan-admin.tenant_level_privacy_retention_limits_inherited_.48f4989d": "Tenant-level privacy retention limits inherited by owned objects.",
|
||||||
|
"i18n:govoplan-admin.tenant_local_and_centrally_managed_groups.4a6296b5": "Tenant-local and centrally managed groups.",
|
||||||
|
"i18n:govoplan-admin.tenant_local_and_centrally_managed_roles.4995a7b2": "Tenant-local and centrally managed roles.",
|
||||||
|
"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_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.",
|
||||||
|
"i18n:govoplan-admin.trace.04a75036": "Trace:",
|
||||||
|
"i18n:govoplan-admin.trusted.99f7ed54": "Trusted",
|
||||||
|
"i18n:govoplan-admin.type.3deb7456": "Type",
|
||||||
|
"i18n:govoplan-admin.uninstall.a735da1d": "Uninstall",
|
||||||
|
"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.",
|
||||||
|
"i18n:govoplan-admin.user_mail_server_policy_limits": "Mail server policy limits for user-owned work.",
|
||||||
|
"i18n:govoplan-admin.user_retention_policy_limits": "Retention limits for user-owned records.",
|
||||||
|
"i18n:govoplan-admin.users.57f2b181": "Users",
|
||||||
|
"i18n:govoplan-admin.valid_after.c615c873": "· Valid after:",
|
||||||
|
"i18n:govoplan-admin.valid_and_trusted.73cf89bc": "Valid and trusted",
|
||||||
|
"i18n:govoplan-admin.valid.a4aefa35": "Valid",
|
||||||
|
"i18n:govoplan-admin.valid.b374b8f9": "Valid:",
|
||||||
|
"i18n:govoplan-admin.value_deleted.3c4bf574": "{value0} deleted.",
|
||||||
|
"i18n:govoplan-admin.value_has_a_webui_reference_but_no_webui_package.18f44149": "{value0} has a WebUI reference but no WebUI package.",
|
||||||
|
"i18n:govoplan-admin.value_needs_a_python_package_name_for_rollback.6c89ed75": "{value0} needs a Python package name for rollback.",
|
||||||
|
"i18n:govoplan-admin.value_needs_a_python_package_name_for_uninstall.004ba6fa": "{value0} needs a Python package name for uninstall.",
|
||||||
|
"i18n:govoplan-admin.value_needs_a_python_package_reference.950b898e": "{value0} needs a Python package reference.",
|
||||||
|
"i18n:govoplan-admin.value_needs_both_webui_package_and_webui_referen.d2bab073": "{value0} needs both WebUI package and WebUI reference, or neither.",
|
||||||
|
"i18n:govoplan-admin.value_template_created.d68a5166": "{value0} template created.",
|
||||||
|
"i18n:govoplan-admin.value_updated_and_synchronized_to_assigned_tenan.d136eef2": "{value0} updated and synchronized to assigned tenants.",
|
||||||
|
"i18n:govoplan-admin.value_value.c189e8bc": "{value0} ({value1})",
|
||||||
|
"i18n:govoplan-admin.version.2da600bf": "Version",
|
||||||
|
"i18n:govoplan-admin.waiting_for_maintenance.60d00019": "Waiting for maintenance",
|
||||||
|
"i18n:govoplan-admin.webui_package.19645536": "WebUI package",
|
||||||
|
"i18n:govoplan-admin.webui_rebuild_required.010ce49f": "WebUI rebuild required",
|
||||||
|
"i18n:govoplan-admin.webui_ref.cbae3559": "WebUI ref",
|
||||||
|
"i18n:govoplan-admin.will_disable.14bb193a": "Will disable",
|
||||||
|
"i18n:govoplan-admin.will_enable.5d52d70b": "Will enable",
|
||||||
|
"i18n:govoplan-admin.working.049ac820": "Working..."
|
||||||
|
},
|
||||||
|
"de": {
|
||||||
|
"i18n:govoplan-admin.access_configuration_exported.098f200d": "Access configuration exported.",
|
||||||
|
"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.admin.4e7afebc": "Administration",
|
||||||
|
"i18n:govoplan-admin.administration.b8be3d12": "Administration",
|
||||||
|
"i18n:govoplan-admin.platform_administration": "Plattformadministration",
|
||||||
|
"i18n:govoplan-admin.alert_warning.804f617a": " alert warning",
|
||||||
|
"i18n:govoplan-admin.allow_tenant_api_keys_by_default.58a6cf17": "Mandanten-API-Schlüssel standardmäßig erlauben",
|
||||||
|
"i18n:govoplan-admin.allow_tenant_defined_groups_by_default.32099d5a": "Mandantendefinierte Gruppen standardmäßig erlauben",
|
||||||
|
"i18n:govoplan-admin.allow_tenant_defined_roles_by_default.b1f79ee9": "Mandantendefinierte Rollen standardmäßig erlauben",
|
||||||
|
"i18n:govoplan-admin.api_keys.94fcf3c2": "API-Schlüssel",
|
||||||
|
"i18n:govoplan-admin.applied.a3e4a569": "Angewendet",
|
||||||
|
"i18n:govoplan-admin.apply_approved_request.ab218d9a": "Apply approved request",
|
||||||
|
"i18n:govoplan-admin.apply_finished_with_blockers.78487a12": "Apply finished with blockers.",
|
||||||
|
"i18n:govoplan-admin.apply_result.0fde1c3c": "Apply result",
|
||||||
|
"i18n:govoplan-admin.apply.cfea419c": "Anwenden",
|
||||||
|
"i18n:govoplan-admin.approvals.deb9d03c": "Approvals",
|
||||||
|
"i18n:govoplan-admin.approve.7b2c7f14": "Freigeben",
|
||||||
|
"i18n:govoplan-admin.approved_package_references_that_can_be_added_to.cc911c16": "Approved package references that can be added to the operator install plan.",
|
||||||
|
"i18n:govoplan-admin.approved_value.52da808b": "Approved {value0}.",
|
||||||
|
"i18n:govoplan-admin.approvers.0e2de1fb": "Approvers",
|
||||||
|
"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",
|
||||||
|
"i18n:govoplan-admin.cancelled.8c9dcfa8": "Cancelled:",
|
||||||
|
"i18n:govoplan-admin.case_clerk.b78a314a": "Case clerk",
|
||||||
|
"i18n:govoplan-admin.catalog_install_entry_planned.13b23f4f": "Catalog install entry planned.",
|
||||||
|
"i18n:govoplan-admin.catalog.4a88d27b": "Katalog",
|
||||||
|
"i18n:govoplan-admin.manual.17a486ca": "Manuell",
|
||||||
|
"i18n:govoplan-admin.central_groups.5c9b5b66": "Central groups",
|
||||||
|
"i18n:govoplan-admin.centrally_defined_group_identities_that_are_prov.7761fce9": "Centrally defined group identities that are provisioned into selected tenants as available or required definitions. Membership remains tenant-local.",
|
||||||
|
"i18n:govoplan-admin.centrally_governed_tenant_roles_and_their_availa.d879d5d1": "Centrally governed tenant roles and their availability across tenants.",
|
||||||
|
"i18n:govoplan-admin.centrally_governed_tenant_roles_that_are_provisi.8739fca3": "Centrally governed tenant roles that are provisioned into selected tenants as available or required definitions.",
|
||||||
|
"i18n:govoplan-admin.centrally_governed_tenant_roles.797c689b": "Centrally governed tenant roles.",
|
||||||
|
"i18n:govoplan-admin.change_request_created_value.4af6d3d2": "Change request created: {value0}",
|
||||||
|
"i18n:govoplan-admin.change_request_id.96ee3239": "Change request id",
|
||||||
|
"i18n:govoplan-admin.change_request_ready.b17e8c57": "Change request ready:",
|
||||||
|
"i18n:govoplan-admin.changes.8aa57de6": "Änderungen",
|
||||||
|
"i18n:govoplan-admin.changing_enabled_modules_requires_a_dry_run_chan.10b6f5ed": "Changing enabled modules requires a dry-run change request and active maintenance mode.",
|
||||||
|
"i18n:govoplan-admin.changing_the_maintenance_mode_flag_requires_syst.4492050f": "Changing the maintenance-mode flag requires system:maintenance:access. Login remains available so an operator can sign in during maintenance.",
|
||||||
|
"i18n:govoplan-admin.channel_value.d3894efe": "Channel: {value0}",
|
||||||
|
"i18n:govoplan-admin.clear.719ea396": "Leeren",
|
||||||
|
"i18n:govoplan-admin.close.bbfa773e": "Schließen",
|
||||||
|
"i18n:govoplan-admin.code.adac6937": "Code",
|
||||||
|
"i18n:govoplan-admin.commands.f6a34aed": "Commands:",
|
||||||
|
"i18n:govoplan-admin.configuration_changes.82933bbb": "Konfigurationsänderungen",
|
||||||
|
"i18n:govoplan-admin.configuration_package_apply.c270e5f3": "Configuration package apply",
|
||||||
|
"i18n:govoplan-admin.configuration_packages.eb2f05f1": "Konfigurationspakete",
|
||||||
|
"i18n:govoplan-admin.configuration_requests_approvals_and_version_his.19f37335": "Configuration requests, approvals and version history.",
|
||||||
|
"i18n:govoplan-admin.configured_license.3e73f8f5": "Configured license",
|
||||||
|
"i18n:govoplan-admin.create_dry_run_request.05b9cbdb": "Create dry-run request",
|
||||||
|
"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.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",
|
||||||
|
"i18n:govoplan-admin.db_restore_check_command.750ae936": "DB-Wiederherstellungsprüfung",
|
||||||
|
"i18n:govoplan-admin.db_restore_command.de11eb01": "DB-Wiederherstellungsbefehl",
|
||||||
|
"i18n:govoplan-admin.db.e355c23a": "DB:",
|
||||||
|
"i18n:govoplan-admin.default_locale.b99d021f": "Standardsprache",
|
||||||
|
"i18n:govoplan-admin.install_language_package": "Sprachpaket installieren",
|
||||||
|
"i18n:govoplan-admin.language_code": "Sprachcode",
|
||||||
|
"i18n:govoplan-admin.language_name": "Sprachname",
|
||||||
|
"i18n:govoplan-admin.language_name_placeholder": "Franzoesisch",
|
||||||
|
"i18n:govoplan-admin.language_packages": "Sprachpakete",
|
||||||
|
"i18n:govoplan-admin.language_packages_help": "Installierte Pakete legen fest, welche Sprachcodes aktiviert werden koennen. Ein Paket ist erst vollstaendig uebersetzt, wenn passende Uebersetzungskataloge der Module vorhanden sind.",
|
||||||
|
"i18n:govoplan-admin.native_language_name": "Nativer Sprachname",
|
||||||
|
"i18n:govoplan-admin.native_language_name_placeholder": "Francais",
|
||||||
|
"i18n:govoplan-admin.defaults_for_newly_created_tenants.9d0f4ccd": "Standards für neu erstellte Mandanten",
|
||||||
|
"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_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",
|
||||||
|
"i18n:govoplan-admin.drift.4876f7b9": "Drift",
|
||||||
|
"i18n:govoplan-admin.drop_uninstalls_from_startup.0e2a5c5b": "Deinstallationen aus dem Start entfernen",
|
||||||
|
"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_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...",
|
||||||
|
"i18n:govoplan-admin.enforced.93ff909c": "Erzwungen",
|
||||||
|
"i18n:govoplan-admin.every_plan_item_needs_a_module_id.23a7f206": "Every plan item needs a module id.",
|
||||||
|
"i18n:govoplan-admin.expires.2d21b7de": "· Expires:",
|
||||||
|
"i18n:govoplan-admin.export_access.9a2eb91e": "Export access",
|
||||||
|
"i18n:govoplan-admin.export_finished_with_blockers.e2f70611": "Export finished with blockers.",
|
||||||
|
"i18n:govoplan-admin.export.f3e4fadb": "Export",
|
||||||
|
"i18n:govoplan-admin.exported_system_access_configuration.2e1c6f4c": "Exported system access configuration",
|
||||||
|
"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",
|
||||||
|
"i18n:govoplan-admin.general.9239ee2c": "Allgemein",
|
||||||
|
"i18n:govoplan-admin.generated.a2edf57c": "Generated:",
|
||||||
|
"i18n:govoplan-admin.global": "Global",
|
||||||
|
"i18n:govoplan-admin.global_accounts_tenant_memberships_and_system_ro.939ed01f": "Global accounts, tenant memberships and system-role assignments.",
|
||||||
|
"i18n:govoplan-admin.global_login_accounts_across_all_tenants.2aab129d": "Global login accounts across all tenants.",
|
||||||
|
"i18n:govoplan-admin.govoplan_module_installer_format_shell.226d1450": "govoplan-module-installer --format shell",
|
||||||
|
"i18n:govoplan-admin.group_definitions_made_available_or_required_in_.55402e16": "Group definitions made available or required in selected tenants.",
|
||||||
|
"i18n:govoplan-admin.group_file_connector_policy_limits": "Dateiverbindungsrichtlinien fuer gruppeneigene Bereiche.",
|
||||||
|
"i18n:govoplan-admin.group_mail_server_policy_limits": "Mailserver-Richtlinien fuer gruppeneigene Arbeit.",
|
||||||
|
"i18n:govoplan-admin.group_retention_policy_limits": "Aufbewahrungslimits fuer gruppeneigene Datensaetze.",
|
||||||
|
"i18n:govoplan-admin.group_template.973e0fa6": "Group template",
|
||||||
|
"i18n:govoplan-admin.group.171a0606": "Group",
|
||||||
|
"i18n:govoplan-admin.groups.ae9629f4": "Gruppen",
|
||||||
|
"i18n:govoplan-admin.handles_incoming_administrative_work.dc80c349": "Handles incoming administrative work.",
|
||||||
|
"i18n:govoplan-admin.health_urls.3b86647c": "Health-URLs",
|
||||||
|
"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",
|
||||||
|
"i18n:govoplan-admin.installed_modules_active_runtime_state_and_saved.f33907dd": "Installed modules, active runtime state, and saved startup state.",
|
||||||
|
"i18n:govoplan-admin.installed_modules_runtime_state_and_startup_stat.38dd7028": "Installed modules, runtime state and startup state.",
|
||||||
|
"i18n:govoplan-admin.installed.7bb4405c": "Installed",
|
||||||
|
"i18n:govoplan-admin.installer_preflight_blocked.cf83bf79": "Installer preflight blocked",
|
||||||
|
"i18n:govoplan-admin.installer_preflight_passed.6d5f8060": "Installer preflight passed",
|
||||||
|
"i18n:govoplan-admin.installer_request_cancelled_value.1d6d099d": "Installer request cancelled: {value0}",
|
||||||
|
"i18n:govoplan-admin.installer_request_queued_value.00726abb": "Installer request queued: {value0}",
|
||||||
|
"i18n:govoplan-admin.installer_requests.b88db439": "Installer-Anfragen",
|
||||||
|
"i18n:govoplan-admin.installer_runs.e9e344e1": "Installer-Läufe",
|
||||||
|
"i18n:govoplan-admin.instance_defaults_and_tenant_governance_capabili.99d6b2fa": "Instance defaults and tenant governance capabilities.",
|
||||||
|
"i18n:govoplan-admin.instance_privacy_retention_policy_and_lower_leve.b8d14069": "Instance privacy retention policy and lower-level override permissions.",
|
||||||
|
"i18n:govoplan-admin.instance_wide_defaults_and_tenant_governance_cap.096a4f97": "Instanzweite Standards und Governance-Funktionen für Mandanten. Die Aufbewahrungsrichtlinien werden in einem eigenen Systembereich verwaltet.",
|
||||||
|
"i18n:govoplan-admin.instance_wide_roles_assigned_directly_to_global_.91050488": "Instance-wide roles assigned directly to global accounts.",
|
||||||
|
"i18n:govoplan-admin.invalid_json.01ccb74f": "Invalid JSON.",
|
||||||
|
"i18n:govoplan-admin.invalid_license.f16bf749": "Invalid license",
|
||||||
|
"i18n:govoplan-admin.json_must_be_an_object.848569c9": "JSON must be an object.",
|
||||||
|
"i18n:govoplan-admin.key_value.847b914f": "Key: {value0}",
|
||||||
|
"i18n:govoplan-admin.key.c67dd20e": "Key",
|
||||||
|
"i18n:govoplan-admin.kind.e00ac23f": "Kind",
|
||||||
|
"i18n:govoplan-admin.label.74341e3c": "Label",
|
||||||
|
"i18n:govoplan-admin.last_request.4508ef35": "Last request:",
|
||||||
|
"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.",
|
||||||
|
"i18n:govoplan-admin.maintenance_mode_must_be_enabled_before_queueing.d233a32f": "Der Wartungsmodus muss aktiviert sein, bevor eine Daemon-Anfrage eingereiht wird.",
|
||||||
|
"i18n:govoplan-admin.maintenance_mode.98cca5c6": "Wartungsmodus",
|
||||||
|
"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:",
|
||||||
|
"i18n:govoplan-admin.module_install_plan_cleared.afa2698d": "Module install plan cleared.",
|
||||||
|
"i18n:govoplan-admin.module_install_plan_saved.5d43d944": "Module install plan saved.",
|
||||||
|
"i18n:govoplan-admin.module_maintenance_in_progress.f6395152": "Module maintenance in progress.",
|
||||||
|
"i18n:govoplan-admin.module_state_saved.e3e22105": "Module state saved.",
|
||||||
|
"i18n:govoplan-admin.module.b8ff0289": "Modul",
|
||||||
|
"i18n:govoplan-admin.modules.04e9462c": "Module",
|
||||||
|
"i18n:govoplan-admin.modules.2dc3b299": "Modules:",
|
||||||
|
"i18n:govoplan-admin.name.709a2322": "Name",
|
||||||
|
"i18n:govoplan-admin.needs_attention.a126722e": "Benötigt Aufmerksamkeit",
|
||||||
|
"i18n:govoplan-admin.no_applied_configuration_changes.6a3ae4a7": "No applied configuration changes.",
|
||||||
|
"i18n:govoplan-admin.no_central_value_templates_found.081149fa": "No central {value0} templates found.",
|
||||||
|
"i18n:govoplan-admin.no_diagnostics.2b6e2630": "No diagnostics.",
|
||||||
|
"i18n:govoplan-admin.no_installer_requests_recorded_yet.17e630fa": "Noch keine Installer-Anfragen aufgezeichnet.",
|
||||||
|
"i18n:govoplan-admin.no_installer_runs_recorded_yet.f8b61964": "Noch keine Installer-Läufe aufgezeichnet.",
|
||||||
|
"i18n:govoplan-admin.no_license_configured.693cea1a": "No license configured",
|
||||||
|
"i18n:govoplan-admin.no_license.eb4bfe19": "No license",
|
||||||
|
"i18n:govoplan-admin.no_open_requests.580c95f9": "No open requests.",
|
||||||
|
"i18n:govoplan-admin.no_package_catalog_configured_set_govoplan_modul.2c5fceb3": "No package catalog configured. Set GOVOPLAN_MODULE_PACKAGE_CATALOG or GOVOPLAN_MODULE_PACKAGE_CATALOG_URL to enable curated install entries.",
|
||||||
|
"i18n:govoplan-admin.no_package_changes_planned.f12d8b33": "No package changes planned.",
|
||||||
|
"i18n:govoplan-admin.no_package_restart_pending.efe6b8ce": "No package restart pending",
|
||||||
|
"i18n:govoplan-admin.no_plan_items.7108c582": "No plan items.",
|
||||||
|
"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",
|
||||||
|
"i18n:govoplan-admin.observe_only.c6a1f8bc": "Nur beobachten",
|
||||||
|
"i18n:govoplan-admin.off.e3de5ab0": "Off",
|
||||||
|
"i18n:govoplan-admin.on.e0049a66": "On",
|
||||||
|
"i18n:govoplan-admin.operator_install_plan.b203aabc": "Operator install plan",
|
||||||
|
"i18n:govoplan-admin.optional.166b8369": "Optional:",
|
||||||
|
"i18n:govoplan-admin.overview.0efc2e6b": "Übersicht",
|
||||||
|
"i18n:govoplan-admin.owner.89ff3122": "Eigentümer",
|
||||||
|
"i18n:govoplan-admin.package_applied.63782ce7": "Package applied.",
|
||||||
|
"i18n:govoplan-admin.package_catalog_is_configured_but_contains_no_en.b6b053cb": "Package catalog is configured but contains no entries.",
|
||||||
|
"i18n:govoplan-admin.package_catalog.b36e6d31": "Package catalog",
|
||||||
|
"i18n:govoplan-admin.package_json_must_be_an_object.db825bb3": "Package JSON must be an object.",
|
||||||
|
"i18n:govoplan-admin.package_json.a2b10f38": "Package JSON",
|
||||||
|
"i18n:govoplan-admin.package_uninstall_planned.56e8566d": "Package uninstall planned.",
|
||||||
|
"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",
|
||||||
|
"i18n:govoplan-admin.planned.9cbe42aa": "Geplant",
|
||||||
|
"i18n:govoplan-admin.preflight_approve_apply_and_export_configuration.276bd0f8": "Preflight, approve, apply and export configuration packages.",
|
||||||
|
"i18n:govoplan-admin.preflight_finished_with_blockers.7e2ca12b": "Preflight finished with blockers.",
|
||||||
|
"i18n:govoplan-admin.preflight_passed.c0c99055": "Preflight passed.",
|
||||||
|
"i18n:govoplan-admin.preflight.8016a487": "Preflight",
|
||||||
|
"i18n:govoplan-admin.provides.221b70d9": "Provides:",
|
||||||
|
"i18n:govoplan-admin.python_package.34591c71": "Python-Paket",
|
||||||
|
"i18n:govoplan-admin.python_ref.1af35d0d": "Python-Referenz",
|
||||||
|
"i18n:govoplan-admin.queue_supervised_run.a53f248c": "Queue supervised run",
|
||||||
|
"i18n:govoplan-admin.queue_the_saved_plan_for_a_separate_installer_da.92d4c96e": "Den gespeicherten Plan für einen separaten Installer-Daemon-Prozess einreihen.",
|
||||||
|
"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",
|
||||||
|
"i18n:govoplan-admin.request.43c2e7a7": "Anfrage:",
|
||||||
|
"i18n:govoplan-admin.request_approval.6245aea1": "Request approval",
|
||||||
|
"i18n:govoplan-admin.requested.c26bf60f": "Requested",
|
||||||
|
"i18n:govoplan-admin.requests.f7194e6a": "Requests",
|
||||||
|
"i18n:govoplan-admin.required_data.1b1c1b34": "Required data",
|
||||||
|
"i18n:govoplan-admin.required_means_the_definition_must_remain_presen.8462063c": "Required means the definition must remain present and system-controlled. It does not automatically assign users or grant permissions.",
|
||||||
|
"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.",
|
||||||
|
"i18n:govoplan-admin.reusable_file_server_connections_credentials_and.8e5c7d43": "Reusable file server connections, credentials and policy.",
|
||||||
|
"i18n:govoplan-admin.role.c3f104d1": "Role",
|
||||||
|
"i18n:govoplan-admin.roles.47dcc27d": "Roles",
|
||||||
|
"i18n:govoplan-admin.rollback_value.47e9c00b": "Rollback: {value0}",
|
||||||
|
"i18n:govoplan-admin.run_migrations.db6e0ce2": "Migrationen ausführen",
|
||||||
|
"i18n:govoplan-admin.running_registry.5274b24b": "Running registry",
|
||||||
|
"i18n:govoplan-admin.runtime_differs.43d1fd78": "Runtime differs",
|
||||||
|
"i18n:govoplan-admin.runtime_matches.a84afa48": "Runtime matches",
|
||||||
|
"i18n:govoplan-admin.safety_controlled_configuration_requests_approva.e8259509": "Safety-controlled configuration requests, approvals and rollback history.",
|
||||||
|
"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_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…",
|
||||||
|
"i18n:govoplan-admin.saving.ae7e8875": "Saving...",
|
||||||
|
"i18n:govoplan-admin.scoped_automation_credentials_capped_by_owner_pe.b3e20e54": "Scoped automation credentials capped by owner permissions.",
|
||||||
|
"i18n:govoplan-admin.secret.f4e7a874": "Secret",
|
||||||
|
"i18n:govoplan-admin.seq_value.0ae5fa47": "Seq {value0}",
|
||||||
|
"i18n:govoplan-admin.setting.fb449f71": "Setting",
|
||||||
|
"i18n:govoplan-admin.severity.de314fa0": "Severity",
|
||||||
|
"i18n:govoplan-admin.signed_and_trusted.bee31302": "Signed and trusted",
|
||||||
|
"i18n:govoplan-admin.signed_untrusted.864ed50c": "Signiert, nicht vertrauenswürdig",
|
||||||
|
"i18n:govoplan-admin.signed.6e3665d8": "Signiert",
|
||||||
|
"i18n:govoplan-admin.slug.094da9b9": "Slug",
|
||||||
|
"i18n:govoplan-admin.started.fe3824e9": "Started:",
|
||||||
|
"i18n:govoplan-admin.startup_state.d1300be6": "Startup state",
|
||||||
|
"i18n:govoplan-admin.status.bae7d5be": "Status",
|
||||||
|
"i18n:govoplan-admin.summary.12b71c3e": "Summary",
|
||||||
|
"i18n:govoplan-admin.supervisor_value.d9f0c8eb": "Supervisor: {value0}",
|
||||||
|
"i18n:govoplan-admin.supplied_data_json_must_be_an_object.b265eb92": "Supplied data JSON must be an object.",
|
||||||
|
"i18n:govoplan-admin.supplied_data_json.6932bfb7": "Supplied data JSON",
|
||||||
|
"i18n:govoplan-admin.system_administration.b151acea": "System administration",
|
||||||
|
"i18n:govoplan-admin.system_general_settings.7cd662ed": "Allgemeine Systemeinstellungen",
|
||||||
|
"i18n:govoplan-admin.system_governed_group_definitions.de8e5dc9": "System-governed group definitions.",
|
||||||
|
"i18n:govoplan-admin.system_level_administrative_history.49f76723": "System-level administrative history.",
|
||||||
|
"i18n:govoplan-admin.system_modules.629539f2": "System modules",
|
||||||
|
"i18n:govoplan-admin.system_roles.a9461aa6": "System roles",
|
||||||
|
"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",
|
||||||
|
"i18n:govoplan-admin.tenant_administration.ffab51f1": "Tenant administration",
|
||||||
|
"i18n:govoplan-admin.tenant_availability.067a4f31": "Tenant availability",
|
||||||
|
"i18n:govoplan-admin.tenant_id.59eba244": "Tenant id",
|
||||||
|
"i18n:govoplan-admin.tenant_level_administrative_history_only.55495c3c": "Tenant-level administrative history only.",
|
||||||
|
"i18n:govoplan-admin.tenant_level_privacy_retention_limits_inherited_.48f4989d": "Tenant-level privacy retention limits inherited by owned objects.",
|
||||||
|
"i18n:govoplan-admin.tenant_local_and_centrally_managed_groups.4a6296b5": "Tenant-local and centrally managed groups.",
|
||||||
|
"i18n:govoplan-admin.tenant_local_and_centrally_managed_roles.4995a7b2": "Tenant-local and centrally managed roles.",
|
||||||
|
"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_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.",
|
||||||
|
"i18n:govoplan-admin.trace.04a75036": "Trace:",
|
||||||
|
"i18n:govoplan-admin.trusted.99f7ed54": "Vertrauenswürdig",
|
||||||
|
"i18n:govoplan-admin.type.3deb7456": "Typ",
|
||||||
|
"i18n:govoplan-admin.uninstall.a735da1d": "Deinstallieren",
|
||||||
|
"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.",
|
||||||
|
"i18n:govoplan-admin.user_mail_server_policy_limits": "Mailserver-Richtlinien fuer benutzereigene Arbeit.",
|
||||||
|
"i18n:govoplan-admin.user_retention_policy_limits": "Aufbewahrungslimits fuer benutzereigene Datensaetze.",
|
||||||
|
"i18n:govoplan-admin.users.57f2b181": "Benutzer",
|
||||||
|
"i18n:govoplan-admin.valid_after.c615c873": "· Valid after:",
|
||||||
|
"i18n:govoplan-admin.valid_and_trusted.73cf89bc": "Valid and trusted",
|
||||||
|
"i18n:govoplan-admin.valid.a4aefa35": "Gültig",
|
||||||
|
"i18n:govoplan-admin.valid.b374b8f9": "Valid:",
|
||||||
|
"i18n:govoplan-admin.value_deleted.3c4bf574": "{value0} deleted.",
|
||||||
|
"i18n:govoplan-admin.value_has_a_webui_reference_but_no_webui_package.18f44149": "{value0} has a WebUI reference but no WebUI package.",
|
||||||
|
"i18n:govoplan-admin.value_needs_a_python_package_name_for_rollback.6c89ed75": "{value0} needs a Python package name for rollback.",
|
||||||
|
"i18n:govoplan-admin.value_needs_a_python_package_name_for_uninstall.004ba6fa": "{value0} needs a Python package name for uninstall.",
|
||||||
|
"i18n:govoplan-admin.value_needs_a_python_package_reference.950b898e": "{value0} needs a Python package reference.",
|
||||||
|
"i18n:govoplan-admin.value_needs_both_webui_package_and_webui_referen.d2bab073": "{value0} needs both WebUI package and WebUI reference, or neither.",
|
||||||
|
"i18n:govoplan-admin.value_template_created.d68a5166": "{value0} template created.",
|
||||||
|
"i18n:govoplan-admin.value_updated_and_synchronized_to_assigned_tenan.d136eef2": "{value0} updated and synchronized to assigned tenants.",
|
||||||
|
"i18n:govoplan-admin.value_value.c189e8bc": "{value0} ({value1})",
|
||||||
|
"i18n:govoplan-admin.version.2da600bf": "Version",
|
||||||
|
"i18n:govoplan-admin.waiting_for_maintenance.60d00019": "Waiting for maintenance",
|
||||||
|
"i18n:govoplan-admin.webui_package.19645536": "WebUI-Paket",
|
||||||
|
"i18n:govoplan-admin.webui_rebuild_required.010ce49f": "WebUI rebuild required",
|
||||||
|
"i18n:govoplan-admin.webui_ref.cbae3559": "WebUI-Referenz",
|
||||||
|
"i18n:govoplan-admin.will_disable.14bb193a": "Wird deaktiviert",
|
||||||
|
"i18n:govoplan-admin.will_enable.5d52d70b": "Wird aktiviert",
|
||||||
|
"i18n:govoplan-admin.working.049ac820": "Working..."
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -2,6 +2,7 @@ export { default } from "./module";
|
|||||||
export * from "./module";
|
export * from "./module";
|
||||||
export * from "./api/admin";
|
export * from "./api/admin";
|
||||||
export { default as AdminOverviewPanel } from "./features/admin/AdminOverviewPanel";
|
export { default as AdminOverviewPanel } from "./features/admin/AdminOverviewPanel";
|
||||||
|
export { default as ConfigurationPackagesPanel } from "./features/admin/ConfigurationPackagesPanel";
|
||||||
export { default as GovernanceTemplatesPanel } from "./features/admin/GovernanceTemplatesPanel";
|
export { default as GovernanceTemplatesPanel } from "./features/admin/GovernanceTemplatesPanel";
|
||||||
export { default as SystemSettingsPanel } from "./features/admin/SystemSettingsPanel";
|
export { default as SystemSettingsPanel } from "./features/admin/SystemSettingsPanel";
|
||||||
export type { PlatformWebModule, PlatformNavItem, PlatformRouteContribution, PlatformRouteContext } from "@govoplan/core-webui";
|
export type { PlatformWebModule, PlatformNavItem, PlatformRouteContribution, PlatformRouteContext } from "@govoplan/core-webui";
|
||||||
|
|||||||
+39
-8
@@ -1,17 +1,25 @@
|
|||||||
import { createElement, lazy } from "react";
|
import { createElement, lazy } from "react";
|
||||||
import type { AdminSectionsUiCapability, PlatformWebModule } from "@govoplan/core-webui";
|
import type { AdminSectionsUiCapability, PlatformWebModule } from "@govoplan/core-webui";
|
||||||
import { adminReadScopes, hasScope } from "@govoplan/core-webui";
|
import { adminReadScopes, hasScope } from "@govoplan/core-webui";
|
||||||
|
import { generatedTranslations } from "./i18n/generatedTranslations";
|
||||||
|
|
||||||
const AdminOverviewPanel = lazy(() => import("./features/admin/AdminOverviewPanel"));
|
const AdminOverviewPanel = lazy(() => import("./features/admin/AdminOverviewPanel"));
|
||||||
|
const ConfigurationChangesPanel = lazy(() => import("./features/admin/ConfigurationChangesPanel"));
|
||||||
|
const ConfigurationPackagesPanel = lazy(() => import("./features/admin/ConfigurationPackagesPanel"));
|
||||||
const GovernanceTemplatesPanel = lazy(() => import("./features/admin/GovernanceTemplatesPanel"));
|
const GovernanceTemplatesPanel = lazy(() => import("./features/admin/GovernanceTemplatesPanel"));
|
||||||
const ModuleManagementPanel = lazy(() => import("./features/admin/ModuleManagementPanel"));
|
const ModuleManagementPanel = lazy(() => import("./features/admin/ModuleManagementPanel"));
|
||||||
const SystemSettingsPanel = lazy(() => import("./features/admin/SystemSettingsPanel"));
|
const SystemSettingsPanel = lazy(() => import("./features/admin/SystemSettingsPanel"));
|
||||||
|
|
||||||
|
const translations = {
|
||||||
|
en: generatedTranslations.en,
|
||||||
|
de: generatedTranslations.de
|
||||||
|
};
|
||||||
|
|
||||||
const adminSections: AdminSectionsUiCapability = {
|
const adminSections: AdminSectionsUiCapability = {
|
||||||
sections: [
|
sections: [
|
||||||
{
|
{
|
||||||
id: "overview",
|
id: "overview",
|
||||||
label: "Overview",
|
label: "i18n:govoplan-admin.overview.0efc2e6b",
|
||||||
group: "ROOT",
|
group: "ROOT",
|
||||||
order: 0,
|
order: 0,
|
||||||
anyOf: adminReadScopes,
|
anyOf: adminReadScopes,
|
||||||
@@ -23,7 +31,7 @@ const adminSections: AdminSectionsUiCapability = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "system-settings",
|
id: "system-settings",
|
||||||
label: "General",
|
label: "i18n:govoplan-admin.general.9239ee2c",
|
||||||
group: "SYSTEM",
|
group: "SYSTEM",
|
||||||
order: 10,
|
order: 10,
|
||||||
allOf: ["system:settings:read"],
|
allOf: ["system:settings:read"],
|
||||||
@@ -33,9 +41,31 @@ const adminSections: AdminSectionsUiCapability = {
|
|||||||
canAccessMaintenance: hasScope(auth, "system:maintenance:access")
|
canAccessMaintenance: hasScope(auth, "system:maintenance:access")
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "system-configuration-changes",
|
||||||
|
label: "i18n:govoplan-admin.changes.8aa57de6",
|
||||||
|
group: "SYSTEM",
|
||||||
|
order: 20,
|
||||||
|
allOf: ["system:settings:read"],
|
||||||
|
render: ({ settings, auth }) => createElement(ConfigurationChangesPanel, {
|
||||||
|
settings,
|
||||||
|
canApprove: hasScope(auth, "system:settings:write") || hasScope(auth, "system:governance:write")
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "system-configuration-packages",
|
||||||
|
label: "i18n:govoplan-admin.packages.0a999012",
|
||||||
|
group: "SYSTEM",
|
||||||
|
order: 30,
|
||||||
|
allOf: ["system:settings:read"],
|
||||||
|
render: ({ settings, auth }) => createElement(ConfigurationPackagesPanel, {
|
||||||
|
settings,
|
||||||
|
canWrite: hasScope(auth, "system:settings:write")
|
||||||
|
})
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "system-role-templates",
|
id: "system-role-templates",
|
||||||
label: "Tenant roles",
|
label: "i18n:govoplan-admin.tenant_roles.51aca82d",
|
||||||
group: "SYSTEM",
|
group: "SYSTEM",
|
||||||
order: 40,
|
order: 40,
|
||||||
allOf: ["system:governance:read"],
|
allOf: ["system:governance:read"],
|
||||||
@@ -48,7 +78,7 @@ const adminSections: AdminSectionsUiCapability = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "system-modules",
|
id: "system-modules",
|
||||||
label: "Modules",
|
label: "i18n:govoplan-admin.modules.04e9462c",
|
||||||
group: "SYSTEM",
|
group: "SYSTEM",
|
||||||
order: 85,
|
order: 85,
|
||||||
allOf: ["system:settings:read"],
|
allOf: ["system:settings:read"],
|
||||||
@@ -60,7 +90,7 @@ const adminSections: AdminSectionsUiCapability = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "system-groups",
|
id: "system-groups",
|
||||||
label: "Groups",
|
label: "i18n:govoplan-admin.groups.ae9629f4",
|
||||||
group: "SYSTEM",
|
group: "SYSTEM",
|
||||||
order: 50,
|
order: 50,
|
||||||
allOf: ["system:governance:read"],
|
allOf: ["system:governance:read"],
|
||||||
@@ -70,15 +100,16 @@ const adminSections: AdminSectionsUiCapability = {
|
|||||||
canWrite: hasScope(auth, "system:governance:write"),
|
canWrite: hasScope(auth, "system:governance:write"),
|
||||||
onAuthRefresh: refreshAuth
|
onAuthRefresh: refreshAuth
|
||||||
})
|
})
|
||||||
}
|
}]
|
||||||
]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const adminModule: PlatformWebModule = {
|
export const adminModule: PlatformWebModule = {
|
||||||
id: "admin",
|
id: "admin",
|
||||||
label: "Admin",
|
label: "i18n:govoplan-admin.admin.4e7afebc",
|
||||||
version: "1.0.0",
|
version: "1.0.0",
|
||||||
dependencies: ["access"],
|
dependencies: ["access"],
|
||||||
|
translations,
|
||||||
uiCapabilities: {
|
uiCapabilities: {
|
||||||
"admin.sections": adminSections
|
"admin.sections": adminSections
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user