Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e55434f406 | ||
|
|
a889071b71 | ||
|
|
8a43b9b676 | ||
|
|
206873b62a |
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@govoplan/access-webui",
|
||||
"version": "0.1.19",
|
||||
"version": "0.1.23",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "webui/src/index.ts",
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "govoplan-access"
|
||||
version = "0.1.19"
|
||||
version = "0.1.23"
|
||||
description = "GovOPlaN access platform module with identity, auth, RBAC, and scope primitives."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
"""GovOPlaN access platform module."""
|
||||
|
||||
__version__ = "0.1.19"
|
||||
__version__ = "0.1.23"
|
||||
|
||||
@@ -850,6 +850,7 @@ class ConfigurationPackageApplyResponse(BaseModel):
|
||||
diagnostics: list[dict[str, Any]] = Field(default_factory=list)
|
||||
created_refs: dict[str, str] = Field(default_factory=dict)
|
||||
updated_refs: dict[str, str] = Field(default_factory=dict)
|
||||
rollback: dict[str, Any] | None = None
|
||||
|
||||
|
||||
class ConfigurationPackageExportRequest(BaseModel):
|
||||
@@ -865,6 +866,7 @@ class ConfigurationPackageExportResponse(BaseModel):
|
||||
fragments: list[dict[str, Any]] = Field(default_factory=list)
|
||||
data_requirements: list[dict[str, Any]] = Field(default_factory=list)
|
||||
diagnostics: list[dict[str, Any]] = Field(default_factory=list)
|
||||
provenance: dict[str, Any] | None = None
|
||||
|
||||
|
||||
class SystemSettingsItem(BaseModel):
|
||||
|
||||
@@ -1036,6 +1036,7 @@ def configuration_package_apply_endpoint(
|
||||
diagnostics=[item.to_dict() for item in result.diagnostics],
|
||||
created_refs=dict(result.created_refs),
|
||||
updated_refs=dict(result.updated_refs),
|
||||
rollback=result.rollback.to_dict() if result.rollback is not None else None,
|
||||
)
|
||||
|
||||
|
||||
@@ -1056,6 +1057,7 @@ def configuration_package_export_endpoint(
|
||||
fragments=[item.to_dict() for item in result.fragments],
|
||||
data_requirements=[item.to_dict() for item in result.data_requirements],
|
||||
diagnostics=[item.to_dict() for item in result.diagnostics],
|
||||
provenance=result.provenance.to_dict() if result.provenance is not None else None,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,326 @@
|
||||
"""German translations for public structured documentation metadata."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
GERMAN_STRUCTURED_TRANSLATIONS: dict[str, dict[str, Any]] = {'access.reference.admin-access-fields': {'fields': [{'admin_description': 'Auf dem Konto und den '
|
||||
'Mitglieder-Payloads '
|
||||
'gespeichert. Es muss '
|
||||
'normalisiert und '
|
||||
'eindeutig für das '
|
||||
'entsprechende '
|
||||
'Login-Konto sein.',
|
||||
'api_field': 'email',
|
||||
'api_path': '/api/v1/admin/users',
|
||||
'field_id': 'access.user.email',
|
||||
'label': 'E-Mail',
|
||||
'permission_scope': 'access:membership:create',
|
||||
'provenance': 'Mandantenmitgliedschaft oder '
|
||||
'Kontosuche.',
|
||||
'user_description': 'Die Adresse, die '
|
||||
'verwendet wird, um die '
|
||||
'Person zu '
|
||||
'identifizieren, wenn '
|
||||
'sie sich anmelden.',
|
||||
'validation': 'Muss eine gültige '
|
||||
'E-Mail-Adresse sein.'},
|
||||
{'admin_description': 'Wird, sofern verfügbar, '
|
||||
'in Benutzer- und '
|
||||
'Kontoantworten als '
|
||||
'display_name ausgegeben.',
|
||||
'api_field': 'display_name',
|
||||
'api_path': '/api/v1/admin/users',
|
||||
'field_id': 'access.user.display_name',
|
||||
'label': 'Anzeigename',
|
||||
'permission_scope': 'access:membership:update',
|
||||
'provenance': 'Profil der '
|
||||
'Mandantenmitgliedschaft.',
|
||||
'user_description': 'Der lesbare Name, der '
|
||||
'in Benutzerlisten und '
|
||||
'Bewertungsbildschirmen '
|
||||
'angezeigt wird.',
|
||||
'validation': 'Menschenlesbarer Text; Halten '
|
||||
'Sie ihn für Administratoren '
|
||||
'erkennbar.'},
|
||||
{'admin_description': 'Wird beim Aktualisieren '
|
||||
'einer Benutzer- oder '
|
||||
'Gruppenmitgliedschaft '
|
||||
'als group_ids übertragen.',
|
||||
'api_field': 'group_ids',
|
||||
'api_path': '/api/v1/admin/users/{user_id}',
|
||||
'field_id': 'access.user.groups',
|
||||
'label': 'Gruppen',
|
||||
'permission_scope': 'access:group:manage_members',
|
||||
'provenance': 'Benutzergruppenmitgliedschaftszeilen.',
|
||||
'user_description': 'Gemeinsame Zugriffsbündel, '
|
||||
'die Rollen für viele '
|
||||
'Personen gleichzeitig '
|
||||
'hinzufügen können.',
|
||||
'validation': 'Gruppen müssen zum gleichen '
|
||||
'Mandant gehören.'},
|
||||
{'admin_description': 'Wird bei '
|
||||
'Aktualisierungsanforderungen '
|
||||
'für Benutzer- und '
|
||||
'Gruppenrollen als role_ids '
|
||||
'übertragen.',
|
||||
'api_field': 'role_ids',
|
||||
'api_path': '/api/v1/admin/users/{user_id}',
|
||||
'field_id': 'access.user.roles',
|
||||
'label': 'Rollen',
|
||||
'permission_scope': 'access:role:assign',
|
||||
'provenance': 'Direkte Benutzerrollen plus '
|
||||
'Gruppenrollenvererbung.',
|
||||
'user_description': 'Direktzugangszuschüsse, '
|
||||
'die einer Person '
|
||||
'zugewiesen oder von '
|
||||
'Gruppen geerbt wurden.',
|
||||
'validation': 'Rollen müssen zuordenbar sein '
|
||||
'und dürfen das '
|
||||
'Delegationslimit der '
|
||||
'handelnden Person nicht '
|
||||
'überschreiten.'},
|
||||
{'admin_description': 'Bildet Scopes beim '
|
||||
'Erstellen eines '
|
||||
'API-Schlüssel zu und '
|
||||
'wird mit den aktuellen '
|
||||
'Berechtigungen des '
|
||||
'Besitzers geschnitten.',
|
||||
'api_field': 'scopes',
|
||||
'api_path': '/api/v1/admin/api-keys',
|
||||
'field_id': 'access.api_key.scopes',
|
||||
'label': 'Anwendungsbereiche',
|
||||
'permission_scope': 'access:api_key:create',
|
||||
'provenance': 'API-Schlüssel Grant plus '
|
||||
'Eigentümerdelegation.',
|
||||
'user_description': 'Die Aktionen, die ein '
|
||||
'API-Schlüssel ausführen '
|
||||
'kann.',
|
||||
'validation': 'Verwenden Sie möglichst enge '
|
||||
'Berechtigungsbereiche.'}]},
|
||||
'access.reference.personal-navigation': {'outcome': 'Die Seitenschiene des Benutzers spiegelt die '
|
||||
'persönlichen Präferenzen wider, während '
|
||||
'verschlossene und unzugängliche Einträge '
|
||||
'durch übergeordnete Richtlinien geregelt '
|
||||
'bleiben.'},
|
||||
'access.workflow.configuration-packages': {'limitations': ['Die Paketübernahme installiert keine '
|
||||
'fehlenden Module.',
|
||||
'Die anbieterübergreifende Übernahme ist keine atomar '
|
||||
'verteilte Transaktion.',
|
||||
'Generisches Rollback hängt von einem '
|
||||
'beibehaltenen '
|
||||
'vor der Übernahme erstellten Datenbank-Snapshot ab.'],
|
||||
'operational_consequences': ['Ein abgestandener oder '
|
||||
'blockierter Preflight '
|
||||
'muss vor der Anwendung '
|
||||
'erneut durchgeführt '
|
||||
'werden.',
|
||||
'Eine teilweise Anwendung '
|
||||
'erfordert eine '
|
||||
'Wiederherstellung, bevor '
|
||||
'das Paket erneut '
|
||||
'getestet wird.',
|
||||
'Geheimwerte bleiben '
|
||||
'außerhalb tragbarer '
|
||||
'Fragmente und '
|
||||
'Herkunft.']},
|
||||
'access.workflow.data-subject-request': {'limitations': ['Module ohne DSAR-Anbieter werden als '
|
||||
'Deckungslücken gemeldet.',
|
||||
'Globale Konten und Identitäten werden '
|
||||
'nicht automatisch gelöscht.']},
|
||||
'access.workflow.grant-user-access': {'outcome': 'Eine Person kann sich beim Mandant anmelden und '
|
||||
'erhält den beabsichtigten Zugang durch Gruppen '
|
||||
'und Rollen.',
|
||||
'prerequisites': ['Sie können Admin öffnen.',
|
||||
'Sie können Benutzer, Gruppen und Rollen '
|
||||
'lesen.',
|
||||
'Schreib- oder Zuweisungsaktionen '
|
||||
'erfordern übereinstimmende '
|
||||
'Verwaltungsberechtigungen.'],
|
||||
'result': 'Die Mitgliedschaft hat die beabsichtigten '
|
||||
'effektiven Berechtigungen und keine breiteren '
|
||||
'Rollen als nötig.',
|
||||
'steps': ['Öffnen Sie Admin und gehen Sie zu Benutzern.',
|
||||
'Finden Sie die bestehende Person oder erstellen '
|
||||
'Sie eine Mitgliedschaft mit ihrer E-Mail-Adresse '
|
||||
'und dem Anzeigenamen.',
|
||||
'Überprüfen Sie aktuelle Gruppen und direkte '
|
||||
'Rollen, bevor Sie etwas ändern.',
|
||||
'Fügen Sie die Person der kleinsten Gruppe hinzu, '
|
||||
'die den erforderlichen gemeinsamen Zugriff '
|
||||
'gewährt.',
|
||||
'Weisen Sie direkte Rollen nur zu, wenn eine '
|
||||
'Gruppe nicht mit dem Fall übereinstimmt.',
|
||||
'Speichern und überprüfen Sie eine '
|
||||
'Blockernachricht, bevor Sie einen System- oder '
|
||||
'Mandantbesitzer um Hilfe bitten.'],
|
||||
'verification': 'Öffnen Sie den Benutzer erneut und '
|
||||
'vergleichen Sie Gruppen, direkte Rollen '
|
||||
'und effektive Berechtigungen mit der '
|
||||
'Anforderung.'},
|
||||
'access.workflow.manage-api-keys': {'consequences': ['Der Widerruf lehnt nachfolgende Anfragen, '
|
||||
'die mit dem Schlüssel gestellt wurden, '
|
||||
'sofort ab.',
|
||||
'Durch das Entfernen von Berechtigungen vom '
|
||||
'Besitzer wird der effektive '
|
||||
'Schlüsselzugriff sofort eingeschränkt.'],
|
||||
'limitations': ['Ein einmaliges Geheimnis kann nach dem '
|
||||
'Schließen des Erstellungsdialogs nicht '
|
||||
'angezeigt oder wiederhergestellt werden.',
|
||||
'Ändern des Besitzers, Ablauf oder Scopes '
|
||||
'erfordert einen Ersatzschlüssel.',
|
||||
'Der Widerruf aktualisiert keine externen '
|
||||
'Clients; die Betreiber müssen bei Bedarf '
|
||||
'einen Ersatz installieren.'],
|
||||
'outcome': 'Der Automatisierungsclient verfügt über einen '
|
||||
'zeitlich begrenzten Berechtigungsnachweis, dessen '
|
||||
'effektiver Zugriff weder seine gespeicherten '
|
||||
'Berechtigungsbereiche noch die aktuellen '
|
||||
'Berechtigungen seines Besitzers überschreiten '
|
||||
'kann.',
|
||||
'prerequisites': ['Der Mandant erlaubt '
|
||||
'API-Anmeldeinformationen.',
|
||||
'Die handelnde Person kann API-Schlüssel '
|
||||
'erstellen oder widerrufen und jeden '
|
||||
'ausgewählten Bereich delegieren.',
|
||||
'Ein zugelassener externer Geheimmanager '
|
||||
'und rechenschaftspflichtiger Eigentümer '
|
||||
'sind bekannt.'],
|
||||
'steps': ['Wählen Sie den verantwortlichen Eigentümer und die '
|
||||
'engsten erforderlichen Berechtigungsbereiche.',
|
||||
'Legen Sie den kürzesten praktischen Ablauf fest, '
|
||||
'bevor Sie den Schlüssel erstellen.',
|
||||
'Übertragen Sie das einmalige Geheimnis direkt in '
|
||||
'den genehmigten Geheimmanager.',
|
||||
'Widerrufen Sie den Schlüssel, wenn sein Client, '
|
||||
'Eigentümer oder Zweck nicht mehr gültig ist.'],
|
||||
'verification': 'Laden Sie das Schlüsselverzeichnis neu, '
|
||||
'überprüfen Sie Eigentümer, Präfix, '
|
||||
'Berechtigungsumfang, Ablauf und Status und '
|
||||
'testen Sie dann den beabsichtigten Client, '
|
||||
'ohne geheimes Material in Nachweise zu '
|
||||
'kopieren.'},
|
||||
'access.workflow.manage-reusable-credentials': {'limitations': ['GovOPlaN kann ein konfiguriertes '
|
||||
'Geheimnis nicht anzeigen oder '
|
||||
'wiederherstellen.',
|
||||
'Eine leere Modul- oder '
|
||||
'Serverbeschränkung bedeutet '
|
||||
'jeden Wert, der nach '
|
||||
'Berechtigungsumfang zulässig '
|
||||
'ist.',
|
||||
'Das Löschen oder Leeren eines '
|
||||
'Geheimnisses schreibt keine '
|
||||
'abhängigen Verbindungsreferenzen '
|
||||
'neu.'],
|
||||
'outcome': 'Die Zugangsdaten bleiben '
|
||||
'schreibgeschützt und sind nur '
|
||||
'innerhalb seines aktiven '
|
||||
'Berechtigungsumfangs, Moduls, Servers '
|
||||
'und Autorisierungsgrenzen verwendbar.',
|
||||
'prerequisites': ['Der beabsichtigte '
|
||||
'Berechtigungsinhaber wird '
|
||||
'ausgewählt.',
|
||||
'Die handelnde Person kann '
|
||||
'Anmeldeinformationen lesen und '
|
||||
'hat Schreibautorität für '
|
||||
'Mutationen.',
|
||||
'Der externe '
|
||||
'Secret-Manager-Eigentümer und '
|
||||
'abhängige Verbindungen sind '
|
||||
'bekannt.'],
|
||||
'steps': ['Wählen Sie den engsten Besitzumfang '
|
||||
'und Anmeldetyp.',
|
||||
'Beschränken Sie Module und Server '
|
||||
'explizit, wenn eine breite Nutzung '
|
||||
'nicht beabsichtigt ist.',
|
||||
'Speichern Sie ein neues oder '
|
||||
'Ersatzgeheimnis, ohne zu erwarten, '
|
||||
'dass es erneut angezeigt wird.',
|
||||
'Überprüfen Sie abhängige Verbindungen '
|
||||
'vor der Deaktivierung, geheimen '
|
||||
'Löschung oder Löschung.'],
|
||||
'verification': 'Laden Sie die Liste der '
|
||||
'Zugangsdaten neu, bestätigen Sie '
|
||||
'deren Berechtigungsumfang und '
|
||||
'Verfügbarkeit und testen Sie '
|
||||
'dann jede beabsichtigte '
|
||||
'abhängige Verbindung, ohne das '
|
||||
'Geheimnis zu enthüllen.'},
|
||||
'access.workflow.manage-service-account-credentials': {'consequences': ['Rotation widerruft den '
|
||||
'vorherigen Nachweis in '
|
||||
'der gleichen '
|
||||
'Transaktion, die seinen '
|
||||
'Ersatz schafft.',
|
||||
'Der Widerruf, die '
|
||||
'Deaktivierung des Kontos '
|
||||
'und der Ruhestand lehnen '
|
||||
'betroffene '
|
||||
'Kundenanfragen sofort '
|
||||
'ab.',
|
||||
'Eine veraltete Revision '
|
||||
'wird abgelehnt, so dass '
|
||||
'ein gleichzeitiger '
|
||||
'Verwaltungswechsel nicht '
|
||||
'überschrieben wird.'],
|
||||
'limitations': ['Einmalige '
|
||||
'Anmeldegeheimnisse können '
|
||||
'nach dem Schließen des '
|
||||
'Erstellungsdialogs nicht '
|
||||
'angezeigt oder '
|
||||
'wiederhergestellt werden.',
|
||||
'Deaktivierung und eine '
|
||||
'reduzierte '
|
||||
'Berechtigungsumfangsobergrenze '
|
||||
'betreffen Clients sofort, '
|
||||
'schreiben ihre externe '
|
||||
'Konfiguration jedoch '
|
||||
'nicht neu.',
|
||||
'Der Ruhestand widerruft '
|
||||
'alle aktiven '
|
||||
'Anmeldeinformationen und '
|
||||
'erfordert ein neues '
|
||||
'Servicekonto für die '
|
||||
'spätere '
|
||||
'Wiederverwendung.'],
|
||||
'outcome': 'Der Automatisierungsprinzipal '
|
||||
'bleibt nicht interaktiv und '
|
||||
'kann sich nur durch einen '
|
||||
'aktiven Berechtigungsnachweis '
|
||||
'authentifizieren, dessen '
|
||||
'Gewährung innerhalb der '
|
||||
'aktuellen '
|
||||
'Berechtigungsumfangsobergrenze '
|
||||
'des Kontos liegt.',
|
||||
'prerequisites': ['Der Mandant erlaubt '
|
||||
'API-Anmeldeinformationen.',
|
||||
'Sie haben eine '
|
||||
'Service-Account-Schreibberechtigung '
|
||||
'und können jeden '
|
||||
'ausgewählten Bereich '
|
||||
'delegieren.'],
|
||||
'steps': ['Erstellen Sie ein Servicekonto '
|
||||
'und definieren Sie die engste '
|
||||
'Nutzumfangsobergrenze.',
|
||||
'Öffnen Sie das Konto und '
|
||||
'erstellen Sie einen '
|
||||
'Berechtigungsnachweis mit einem '
|
||||
'gleichen oder engeren '
|
||||
'Berechtigungsumfang.',
|
||||
'Notieren Sie das einmalige '
|
||||
'Geheimnis in einem externen '
|
||||
'Geheimmanager.',
|
||||
'Anmeldeinformationen vor Ablauf '
|
||||
'drehen und Anmeldeinformationen '
|
||||
'widerrufen, die nicht mehr '
|
||||
'verwendet werden.'],
|
||||
'verification': 'Die Verwaltungstabelle '
|
||||
'zeigt die erwartete '
|
||||
'Anzahl der aktiven '
|
||||
'Anmeldeinformationen, den '
|
||||
'Zeitstempel für die '
|
||||
'letzte Verwendung, die '
|
||||
'Revision und die '
|
||||
'Audit-Ereignisse, ohne '
|
||||
'geheimes Material '
|
||||
'preiszugeben.'}}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,10 @@ from types import SimpleNamespace
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
from govoplan_access.backend.api.v1.admin_schemas import (
|
||||
ConfigurationPackageApplyResponse,
|
||||
ConfigurationPackageExportResponse,
|
||||
)
|
||||
from govoplan_access.backend.api.v1.routes import _configuration_context
|
||||
from govoplan_core.core.infrastructure_capabilities import (
|
||||
InfrastructureCapabilityReceiptError,
|
||||
@@ -16,6 +20,39 @@ from govoplan_core.core.provider_governance import (
|
||||
|
||||
|
||||
class ConfigurationPackageContextTests(unittest.TestCase):
|
||||
def test_api_responses_preserve_rollback_and_redacted_export_provenance(self) -> None:
|
||||
applied = ConfigurationPackageApplyResponse(
|
||||
rollback={
|
||||
"status": "database_restore_required",
|
||||
"summary": "Snapshot is the generic rollback boundary.",
|
||||
"recovery_action": "Retain the snapshot.",
|
||||
}
|
||||
)
|
||||
exported = ConfigurationPackageExportResponse(
|
||||
provenance={
|
||||
"exported_at": "2026-08-22T12:00:00+00:00",
|
||||
"source_core_version": "0.1.35",
|
||||
"module_versions": {"forms": "0.1.20"},
|
||||
"tenant_id": "tenant-1",
|
||||
"exporter_id": "user-1",
|
||||
"selection": {
|
||||
"scopes": ["tenant"],
|
||||
"module_ids": ["forms"],
|
||||
"object_refs": [],
|
||||
},
|
||||
"redacted_secret_keys": ["credential_ref"],
|
||||
}
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
"database_restore_required",
|
||||
applied.model_dump()["rollback"]["status"],
|
||||
)
|
||||
self.assertEqual(
|
||||
["credential_ref"],
|
||||
exported.model_dump()["provenance"]["redacted_secret_keys"],
|
||||
)
|
||||
|
||||
def test_context_carries_operator_scopes_and_validated_infrastructure_receipt(self) -> None:
|
||||
receipt = SimpleNamespace(installation_id="deployment-1")
|
||||
principal = SimpleNamespace(
|
||||
|
||||
@@ -6,6 +6,18 @@ from govoplan_access.backend.manifest import manifest
|
||||
|
||||
|
||||
class InterfaceDocumentationContractTests(unittest.TestCase):
|
||||
def test_all_static_topics_have_complete_german_content(self) -> None:
|
||||
for topic in manifest.documentation:
|
||||
german = (topic.translations or {}).get("de", {})
|
||||
self.assertEqual(
|
||||
{"title", "summary", "body"},
|
||||
set(german),
|
||||
topic.id,
|
||||
)
|
||||
self.assertTrue(
|
||||
all(str(value).strip() for value in german.values()), topic.id
|
||||
)
|
||||
|
||||
def test_access_admin_topics_publish_stable_help_contexts(self) -> None:
|
||||
topics = {topic.id: topic for topic in manifest.documentation}
|
||||
|
||||
@@ -112,9 +124,7 @@ class InterfaceDocumentationContractTests(unittest.TestCase):
|
||||
)
|
||||
|
||||
def test_access_admin_surfaces_remain_declared(self) -> None:
|
||||
surface_ids = {
|
||||
surface.id for surface in manifest.frontend.view_surfaces
|
||||
}
|
||||
surface_ids = {surface.id for surface in manifest.frontend.view_surfaces}
|
||||
self.assertTrue(
|
||||
{
|
||||
"access.admin.system-roles",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@govoplan/access-webui",
|
||||
"version": "0.1.19",
|
||||
"version": "0.1.23",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -239,8 +239,10 @@ export default function SystemUsersPanel({
|
||||
<FormField label="i18n:govoplan-access.email.84add5b2"><input value={draft.email} disabled={editing !== "new"} onChange={(event) => setDraft({ ...draft, email: event.target.value })} /></FormField>
|
||||
<FormField label="i18n:govoplan-access.display_name.c7874aaa"><input value={draft.displayName} disabled={editing !== "new" && !canUpdate} onChange={(event) => setDraft({ ...draft, displayName: event.target.value })} /></FormField>
|
||||
{editing === "new" &&
|
||||
<FormField label="i18n:govoplan-access.initial_password.2278be8c">
|
||||
<FormField label="i18n:govoplan-access.initial_password.2278be8c" helpContextId="access.admin.system-users.initial-password" helpModuleId="access">
|
||||
<PasswordField
|
||||
helpContextId="access.admin.system-users.initial-password"
|
||||
helpModuleId="access"
|
||||
value={draft.password}
|
||||
placeholder="i18n:govoplan-access.leave_empty_to_generate.e58222d8"
|
||||
autoComplete="new-password"
|
||||
|
||||
@@ -249,6 +249,8 @@ export default function UsersPanel({ settings, auth, canCreate, canUpdate, canSu
|
||||
id: "revoke-session",
|
||||
label: "i18n:govoplan-access.revoke_session.5e551007",
|
||||
variant: "danger",
|
||||
helpContextId: "access.sessions.action.revoke",
|
||||
helpModuleId: "access",
|
||||
applicable: !row.current,
|
||||
disabled: busy || !canRevokeSessions,
|
||||
disabledReason: !canRevokeSessions ? "i18n:govoplan-access.session_revocation_permission_required.5e551016" : busy ? ACCESS_INTERFACE_I18N.operationInProgress : undefined,
|
||||
@@ -283,8 +285,10 @@ export default function UsersPanel({ settings, auth, canCreate, canUpdate, canSu
|
||||
<FormField label="i18n:govoplan-access.email.84add5b2"><input value={draft.email} disabled={editing !== "new"} onChange={(event) => setDraft({ ...draft, email: event.target.value })} /></FormField>
|
||||
<FormField label="i18n:govoplan-access.display_name.c7874aaa"><input value={draft.displayName} disabled={editing !== "new" && !canUpdate} onChange={(event) => setDraft({ ...draft, displayName: event.target.value })} /></FormField>
|
||||
{editing === "new" &&
|
||||
<FormField label="i18n:govoplan-access.initial_password.2278be8c">
|
||||
<FormField label="i18n:govoplan-access.initial_password.2278be8c" helpContextId="access.admin.tenant-users.initial-password" helpModuleId="access">
|
||||
<PasswordField
|
||||
helpContextId="access.admin.tenant-users.initial-password"
|
||||
helpModuleId="access"
|
||||
value={draft.password}
|
||||
placeholder="i18n:govoplan-access.leave_empty_to_generate.e58222d8"
|
||||
autoComplete="new-password"
|
||||
@@ -294,7 +298,7 @@ export default function UsersPanel({ settings, auth, canCreate, canUpdate, canSu
|
||||
}
|
||||
<FormField label="i18n:govoplan-access.membership_status.b77fc732"><select value={draft.isActive ? "active" : "inactive"} disabled={Boolean(editing && editing !== "new" && (!canSuspend || editing.is_last_active_owner))} onChange={(event) => setDraft({ ...draft, isActive: event.target.value === "active" })}><option value="active">i18n:govoplan-access.active.a733b809</option><option value="inactive">i18n:govoplan-access.inactive.09af574c</option></select></FormField>
|
||||
</FormGrid>
|
||||
{editing === "new" && <ToggleSwitch label="i18n:govoplan-access.require_password_change_when_account_settings_ar.69bce7a3" checked={draft.passwordResetRequired} onChange={(passwordResetRequired) => setDraft({ ...draft, passwordResetRequired })} />}
|
||||
{editing === "new" && <ToggleSwitch label="i18n:govoplan-access.require_password_change_when_account_settings_ar.69bce7a3" checked={draft.passwordResetRequired} helpContextId="access.admin.tenant-users.require-password-change" helpModuleId="access" onChange={(passwordResetRequired) => setDraft({ ...draft, passwordResetRequired })} />}
|
||||
{editing && editing !== "new" && editing.is_last_active_owner && <p className="admin-protection-note">i18n:govoplan-access.this_membership_is_the_tenant_s_last_active_oper.072b247f</p>}
|
||||
<ContentGrid columns={2} spacing="block" collapseAt="wide">
|
||||
<div><span className="form-label">i18n:govoplan-access.groups.ae9629f4</span><AdminSelectionList options={groups.filter((group) => group.is_active).map((group) => ({ id: group.id, label: group.name, description: group.description, disabled: !canManageGroups }))} selected={draft.groupIds} onChange={(groupIds) => setDraft({ ...draft, groupIds })} emptyText="i18n:govoplan-access.no_groups_exist_yet.9cd029f6" /></div>
|
||||
@@ -320,11 +324,11 @@ export default function UsersPanel({ settings, auth, canCreate, canUpdate, canSu
|
||||
</>}
|
||||
</Dialog>
|
||||
|
||||
<Dialog variant="administration" size="large" open={Boolean(revokingSession)} title="i18n:govoplan-access.revoke_session.5e551007" onClose={() => !busy && setRevokingSession(null)} className="" footer={<><Button onClick={() => setRevokingSession(null)} disabled={busy}>i18n:govoplan-access.cancel.77dfd213</Button><Button variant="danger" onClick={() => void revokeSelectedSession()} disabled={busy || !reauthorizationPassword} disabledReason={!reauthorizationPassword ? "i18n:govoplan-access.current_password_required.5e551019" : busy ? ACCESS_INTERFACE_I18N.operationInProgress : undefined}>i18n:govoplan-access.revoke_session.5e551007</Button></>}>
|
||||
<Dialog variant="administration" size="large" open={Boolean(revokingSession)} title="i18n:govoplan-access.revoke_session.5e551007" helpContextId="access.sessions.action.revoke" helpModuleId="access" onClose={() => !busy && setRevokingSession(null)} className="" footer={<><Button onClick={() => setRevokingSession(null)} disabled={busy}>i18n:govoplan-access.cancel.77dfd213</Button><Button variant="danger" helpContextId="access.sessions.action.revoke" helpModuleId="access" onClick={() => void revokeSelectedSession()} disabled={busy || !reauthorizationPassword} disabledReason={!reauthorizationPassword ? "i18n:govoplan-access.current_password_required.5e551019" : busy ? ACCESS_INTERFACE_I18N.operationInProgress : undefined}>i18n:govoplan-access.revoke_session.5e551007</Button></>}>
|
||||
{sessionError && <p className="admin-protection-note">{sessionError}</p>}
|
||||
<p>i18n:govoplan-access.admin_session_revocation_confirmation.5e551020</p>
|
||||
<FormField label="i18n:govoplan-access.current_password.5e551021">
|
||||
<PasswordField value={reauthorizationPassword} autoComplete="current-password" onValueChange={setReauthorizationPassword} />
|
||||
<FormField label="i18n:govoplan-access.current_password.5e551021" helpContextId="access.sessions.field.current-password" helpModuleId="access">
|
||||
<PasswordField helpContextId="access.sessions.field.current-password" helpModuleId="access" value={reauthorizationPassword} autoComplete="current-password" onValueChange={setReauthorizationPassword} />
|
||||
</FormField>
|
||||
</Dialog>
|
||||
|
||||
|
||||
@@ -132,6 +132,8 @@ export default function SessionSettingsPanel({
|
||||
id: "revoke",
|
||||
label: "i18n:govoplan-access.revoke_session.5e551007",
|
||||
variant: "danger",
|
||||
helpContextId: "access.sessions.action.revoke",
|
||||
helpModuleId: "access",
|
||||
applicable: !row.current,
|
||||
disabled: busy,
|
||||
disabledReason: busy
|
||||
@@ -208,6 +210,8 @@ export default function SessionSettingsPanel({
|
||||
destructiveActions={
|
||||
<Button
|
||||
variant="danger"
|
||||
helpContextId="access.sessions.action.revoke-others"
|
||||
helpModuleId="access"
|
||||
disabled={busy || sessions.filter((item) => !item.current).length === 0}
|
||||
disabledReason={
|
||||
busy
|
||||
@@ -243,6 +247,8 @@ export default function SessionSettingsPanel({
|
||||
confirmLabel="i18n:govoplan-access.revoke_session.5e551007"
|
||||
tone="danger"
|
||||
busy={busy}
|
||||
helpContextId="access.sessions.action.revoke"
|
||||
helpModuleId="access"
|
||||
onCancel={() => setRevoking(null)}
|
||||
onConfirm={() => void revokeOne()}
|
||||
/>
|
||||
@@ -253,6 +259,8 @@ export default function SessionSettingsPanel({
|
||||
confirmLabel="i18n:govoplan-access.revoke_all_other_sessions.5e551012"
|
||||
tone="danger"
|
||||
busy={busy}
|
||||
helpContextId="access.sessions.action.revoke-others"
|
||||
helpModuleId="access"
|
||||
onCancel={() => setRevokingOthers(false)}
|
||||
onConfirm={() => void revokeOthers()}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user