Migrate Tenancy interface patterns
This commit is contained in:
@@ -19,3 +19,9 @@ The `@govoplan/tenancy-webui` package contributes `system-tenants` and
|
|||||||
module owns the `/admin` shell but does not import these panels. Historical
|
module owns the `/admin` shell but does not import these panels. Historical
|
||||||
`access.admin.*` surface identifiers remain stable so existing saved Views keep
|
`access.admin.*` surface identifiers remain stable so existing saved Views keep
|
||||||
working after the ownership move.
|
working after the ownership move.
|
||||||
|
|
||||||
|
The tenant registry and active-tenant settings follow the shared interface
|
||||||
|
pattern contract documented in
|
||||||
|
[`docs/INTERFACE_PATTERN_MIGRATION.md`](docs/INTERFACE_PATTERN_MIGRATION.md).
|
||||||
|
Manifest-provided documentation topics back contextual help for tenant fields,
|
||||||
|
governance limits, permission blockers, and lifecycle consequences.
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# Tenancy Interface Pattern Migration
|
||||||
|
|
||||||
|
This document records the bounded migration of Tenancy-owned WebUI surfaces to
|
||||||
|
the GovOPlaN interface pattern language. Core owns the shared components and the
|
||||||
|
Admin host; Tenancy owns the behavior and documentation described here.
|
||||||
|
|
||||||
|
## Surface Inventory
|
||||||
|
|
||||||
|
| Surface | Archetype | Consequence class | Contract |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `tenancy.admin.system-tenants` | Administration directory and list-detail | Create, configure, suspend | Shared admin layout, DataGrid, stable row actions, adaptive create/edit dialog, lifecycle confirmation, contextual help |
|
||||||
|
| Tenant details dialog | Read-only evidence/detail | None | Stable labels, effective governance provenance, retained object counts |
|
||||||
|
| `tenancy.admin.tenant-settings` | Effective configuration | Configure | Shared admin layout, language selection, dirty-state guard, permission blocker, contextual help |
|
||||||
|
|
||||||
|
## Consequence And Availability Rules
|
||||||
|
|
||||||
|
- Creating a tenant establishes a new data and administration boundary and
|
||||||
|
provisions a protected initial owner.
|
||||||
|
- Tenant slugs are immutable after creation.
|
||||||
|
- System policy caps tenant governance overrides. A tenant can narrow an
|
||||||
|
allowance but cannot loosen a system denial.
|
||||||
|
- Suspension retains tenant-owned records and audit evidence. The active
|
||||||
|
tenant cannot be suspended until the operator changes context.
|
||||||
|
- Unavailable actions remain visible when they belong to the surface and state
|
||||||
|
the missing permission, inapplicable state, responsible actor, and
|
||||||
|
destination where applicable.
|
||||||
|
- Dirty dialogs and settings use the shared unsaved-change guard. Consequential
|
||||||
|
suspension continues to use the shared destructive confirmation dialog.
|
||||||
|
|
||||||
|
## State And Accessibility Evidence
|
||||||
|
|
||||||
|
The panels use Core loading, error, success, empty, disabled-action, blocker,
|
||||||
|
dialog, and status components. Row actions reserve a stable three-action area,
|
||||||
|
retain translated accessible labels, and do not disappear for row-specific
|
||||||
|
permission or lifecycle states. Dialog order follows identity, ownership,
|
||||||
|
locale/status, description, and governed capabilities. Shared dialogs own focus
|
||||||
|
containment and restoration, and the existing Admin shell provides responsive
|
||||||
|
composition.
|
||||||
|
|
||||||
|
Stable help references are contributed through the module manifest for the
|
||||||
|
tenant registry, tenant settings, lifecycle actions, and individual fields.
|
||||||
|
The WebUI structural test and backend documentation-contract test prevent those
|
||||||
|
references and state explanations from silently regressing.
|
||||||
@@ -6,7 +6,7 @@ from govoplan_core.core.access import (
|
|||||||
CAPABILITY_AUTH_TENANT_CONTEXT_SWITCHER,
|
CAPABILITY_AUTH_TENANT_CONTEXT_SWITCHER,
|
||||||
CAPABILITY_TENANCY_TENANT_RESOLVER,
|
CAPABILITY_TENANCY_TENANT_RESOLVER,
|
||||||
)
|
)
|
||||||
from govoplan_core.core.modules import DocumentationTopic, FrontendModule, ModuleContext, ModuleManifest
|
from govoplan_core.core.modules import DocumentationLink, DocumentationTopic, FrontendModule, ModuleContext, ModuleManifest
|
||||||
from govoplan_core.core.provider_governance import declared_module_architecture
|
from govoplan_core.core.provider_governance import declared_module_architecture
|
||||||
from govoplan_core.core.views import ViewSurface
|
from govoplan_core.core.views import ViewSurface
|
||||||
|
|
||||||
@@ -51,7 +51,10 @@ manifest = ModuleManifest(
|
|||||||
documentation_types=("user",),
|
documentation_types=("user",),
|
||||||
audience=("user", "tenant_admin"),
|
audience=("user", "tenant_admin"),
|
||||||
related_modules=("access",),
|
related_modules=("access",),
|
||||||
metadata={"kind": "reference"},
|
metadata={
|
||||||
|
"kind": "reference",
|
||||||
|
"help_contexts": ["tenancy.current-context", "tenancy.selector"],
|
||||||
|
},
|
||||||
),
|
),
|
||||||
DocumentationTopic(
|
DocumentationTopic(
|
||||||
id="tenancy.lifecycle-and-settings",
|
id="tenancy.lifecycle-and-settings",
|
||||||
@@ -61,7 +64,49 @@ manifest = ModuleManifest(
|
|||||||
documentation_types=("admin",),
|
documentation_types=("admin",),
|
||||||
audience=("system_admin", "tenant_admin", "operator"),
|
audience=("system_admin", "tenant_admin", "operator"),
|
||||||
related_modules=("access", "admin", "audit"),
|
related_modules=("access", "admin", "audit"),
|
||||||
metadata={"kind": "reference"},
|
links=(
|
||||||
|
DocumentationLink(label="Tenant administration", href="/admin", kind="runtime"),
|
||||||
|
DocumentationLink(label="Tenant registry API", href="/api/v1/admin/tenants", kind="api"),
|
||||||
|
DocumentationLink(label="Tenant settings API", href="/api/v1/admin/tenant/settings", kind="api"),
|
||||||
|
),
|
||||||
|
metadata={
|
||||||
|
"kind": "reference",
|
||||||
|
"help_contexts": [
|
||||||
|
"tenancy.admin.system-tenants",
|
||||||
|
"tenancy.admin.tenant-settings",
|
||||||
|
"tenancy.admin.lifecycle",
|
||||||
|
"tenancy.admin.blocked",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
DocumentationTopic(
|
||||||
|
id="tenancy.reference.admin-fields",
|
||||||
|
title="Tenant administration fields and consequences",
|
||||||
|
summary="Tenant identity, ownership, locale, governance overrides, and lifecycle state have different mutation and recovery consequences.",
|
||||||
|
body="A tenant slug is immutable after creation and identifies the administrative boundary. The initial owner receives the protected tenant-owner role. Locale and enabled languages are bounded by system language packages. Governance overrides may narrow a system allowance but cannot loosen a system denial. Suspension keeps tenant-owned data and audit evidence while preventing normal use; an operator must switch away from the active tenant before suspending it.",
|
||||||
|
documentation_types=("admin",),
|
||||||
|
audience=("system_admin", "tenant_admin", "operator"),
|
||||||
|
related_modules=("access", "admin", "audit"),
|
||||||
|
links=(
|
||||||
|
DocumentationLink(label="Tenant administration", href="/admin", kind="runtime"),
|
||||||
|
DocumentationLink(label="Tenant registry API", href="/api/v1/admin/tenants", kind="api"),
|
||||||
|
),
|
||||||
|
metadata={
|
||||||
|
"kind": "reference",
|
||||||
|
"help_contexts": [
|
||||||
|
"tenancy.field.slug",
|
||||||
|
"tenancy.field.initial-owner",
|
||||||
|
"tenancy.field.locale",
|
||||||
|
"tenancy.field.languages",
|
||||||
|
"tenancy.field.governance",
|
||||||
|
"tenancy.action.suspend",
|
||||||
|
],
|
||||||
|
"consequence_classes": {
|
||||||
|
"create": "Creates a new tenant boundary and provisions its protected initial owner.",
|
||||||
|
"update": "Changes tenant-local identity, locale, or governance configuration.",
|
||||||
|
"suspend": "Blocks normal tenant use while retaining data and audit evidence.",
|
||||||
|
},
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
frontend=FrontendModule(
|
frontend=FrontendModule(
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from govoplan_tenancy.backend.manifest import manifest
|
||||||
|
|
||||||
|
|
||||||
|
class TenancyInterfaceDocumentationContractTests(unittest.TestCase):
|
||||||
|
def test_tenancy_admin_surfaces_remain_declared(self) -> None:
|
||||||
|
frontend = manifest.frontend
|
||||||
|
self.assertIsNotNone(frontend)
|
||||||
|
surfaces = {surface.id for surface in frontend.view_surfaces} # type: ignore[union-attr]
|
||||||
|
self.assertEqual(
|
||||||
|
{
|
||||||
|
"tenancy.admin.system-tenants",
|
||||||
|
"tenancy.admin.tenant-settings",
|
||||||
|
},
|
||||||
|
surfaces,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_tenancy_topics_publish_stable_help_contexts(self) -> None:
|
||||||
|
topics = {topic.id: topic for topic in manifest.documentation}
|
||||||
|
self.assertIn("tenancy.current-context", topics)
|
||||||
|
self.assertIn("tenancy.lifecycle-and-settings", topics)
|
||||||
|
self.assertIn("tenancy.reference.admin-fields", topics)
|
||||||
|
|
||||||
|
lifecycle_contexts = set(topics["tenancy.lifecycle-and-settings"].metadata["help_contexts"])
|
||||||
|
self.assertIn("tenancy.admin.system-tenants", lifecycle_contexts)
|
||||||
|
self.assertIn("tenancy.admin.tenant-settings", lifecycle_contexts)
|
||||||
|
self.assertIn("tenancy.action.suspend", topics["tenancy.reference.admin-fields"].metadata["help_contexts"])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
+2
-1
@@ -7,7 +7,8 @@
|
|||||||
"module": "src/index.ts",
|
"module": "src/index.ts",
|
||||||
"types": "src/index.ts",
|
"types": "src/index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test:tenancy-admin": "node scripts/test-tenancy-admin-structure.mjs"
|
"test:tenancy-admin": "node scripts/test-tenancy-admin-structure.mjs",
|
||||||
|
"test:interface-patterns": "node scripts/test-interface-pattern-language.mjs"
|
||||||
},
|
},
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { readFileSync } from "node:fs";
|
||||||
|
|
||||||
|
function source(path) {
|
||||||
|
return readFileSync(new URL(path, import.meta.url), "utf8");
|
||||||
|
}
|
||||||
|
|
||||||
|
function assert(condition, message) {
|
||||||
|
if (!condition) throw new Error(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tenants = source("../src/features/admin/TenantsPanel.tsx");
|
||||||
|
const settings = source("../src/features/admin/TenantSettingsPanel.tsx");
|
||||||
|
const patterns = source("../src/features/admin/interfacePatterns.ts");
|
||||||
|
const moduleSource = source("../src/module.ts");
|
||||||
|
const translations = source("../src/i18n/generatedTranslations.ts");
|
||||||
|
|
||||||
|
assert(tenants.includes("DocumentationHelpLink") && settings.includes("DocumentationHelpLink"), "Both Tenancy admin surfaces expose contextual documentation");
|
||||||
|
assert(tenants.includes("ActionBlockerHint") && settings.includes("ActionBlockerHint"), "Read-only Tenancy states identify the actor, action, and destination");
|
||||||
|
assert(tenants.includes("disabledReason") && settings.includes("disabledReason"), "Disabled Tenancy actions explain their state");
|
||||||
|
assert(tenants.includes("requestDiscard(closeEditor)"), "The tenant editor uses the shared unsaved-change guard when closing");
|
||||||
|
assert(settings.includes("requestDiscard(() => void load())"), "Tenant settings protect dirty state when reloading");
|
||||||
|
assert(tenants.includes("ConfirmDialog") && tenants.includes("confirmSuspend"), "Tenant suspension remains explicitly confirmed");
|
||||||
|
assert(tenants.includes("minimumSlots={3}"), "Tenant row actions reserve stable keyboard and visual positions");
|
||||||
|
assert(!tenants.includes("applicable:"), "Row-specific unavailable actions stay visible with an explanation");
|
||||||
|
assert(patterns.includes('topicId: "tenancy.lifecycle-and-settings"') && patterns.includes('topicId: "tenancy.reference.admin-fields"'), "Tenancy uses stable manifest-backed help references");
|
||||||
|
assert(moduleSource.includes('version: "0.1.8"'), "The WebUI contribution reports the module release version");
|
||||||
|
assert(moduleSource.includes('label: "i18n:govoplan-tenancy.tenants.1f7ae776"') && moduleSource.includes('label: "i18n:govoplan-tenancy.tenant_general_settings.db1c3ba8"'), "View-surface labels are localized");
|
||||||
|
assert(translations.includes('"i18n:govoplan-tenancy.tenant_administration_is_read_only.7c9a1011"'), "Availability explanations are present in the translation catalog");
|
||||||
|
|
||||||
|
console.log("Tenancy surfaces satisfy the recorded interface pattern-language contract.");
|
||||||
@@ -1,10 +1,25 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import type { ApiSettings } from "@govoplan/core-webui";
|
import {
|
||||||
import { Button } from "@govoplan/core-webui";
|
ActionBlockerHint,
|
||||||
import { Card } from "@govoplan/core-webui";
|
AdminPageLayout,
|
||||||
import { FormField } from "@govoplan/core-webui";
|
AdminSelectionList,
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
DocumentationHelpLink,
|
||||||
|
FormField,
|
||||||
|
adminErrorMessage,
|
||||||
|
useDeltaWatermarks,
|
||||||
|
useUnsavedChanges,
|
||||||
|
useUnsavedDraftGuard,
|
||||||
|
type ApiSettings
|
||||||
|
} from "@govoplan/core-webui";
|
||||||
import { fetchTenantSettingsDelta, updateTenantSettings, type TenantSettingsDeltaSections, type TenantSettingsItem } from "../../api/tenancy";
|
import { fetchTenantSettingsDelta, updateTenantSettings, type TenantSettingsDeltaSections, type TenantSettingsItem } from "../../api/tenancy";
|
||||||
import { AdminPageLayout, AdminSelectionList, adminErrorMessage, useDeltaWatermarks, useUnsavedDraftGuard } from "@govoplan/core-webui";
|
import {
|
||||||
|
TENANCY_ADMIN_DOCUMENTATION,
|
||||||
|
TENANCY_FIELD_DOCUMENTATION,
|
||||||
|
TENANCY_INTERFACE_I18N,
|
||||||
|
tenantMutationDisabledReason
|
||||||
|
} from "./interfacePatterns";
|
||||||
|
|
||||||
const DELTA_KEY = "tenancy:tenant-settings";
|
const DELTA_KEY = "tenancy:tenant-settings";
|
||||||
|
|
||||||
@@ -31,6 +46,7 @@ export default function TenantSettingsPanel({
|
|||||||
|
|
||||||
|
|
||||||
}: {settings: ApiSettings;canWrite: boolean;onAuthRefresh: () => Promise<void>;}) {
|
}: {settings: ApiSettings;canWrite: boolean;onAuthRefresh: () => Promise<void>;}) {
|
||||||
|
const { requestDiscard } = useUnsavedChanges();
|
||||||
const [draft, setDraft] = useState<TenantSettingsItem>(fallback);
|
const [draft, setDraft] = useState<TenantSettingsItem>(fallback);
|
||||||
const [savedDraft, setSavedDraft] = useState<TenantSettingsItem>(fallback);
|
const [savedDraft, setSavedDraft] = useState<TenantSettingsItem>(fallback);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
@@ -40,6 +56,13 @@ export default function TenantSettingsPanel({
|
|||||||
const { getDeltaWatermark, setDeltaWatermark, resetDeltaWatermark } = useDeltaWatermarks();
|
const { getDeltaWatermark, setDeltaWatermark, resetDeltaWatermark } = useDeltaWatermarks();
|
||||||
const defaultLocaleOptions = localeOptions(draft.default_locale, draft.enabled_language_codes);
|
const defaultLocaleOptions = localeOptions(draft.default_locale, draft.enabled_language_codes);
|
||||||
const dirty = tenantSettingsDraftKey(draft) !== tenantSettingsDraftKey(savedDraft);
|
const dirty = tenantSettingsDraftKey(draft) !== tenantSettingsDraftKey(savedDraft);
|
||||||
|
const saveDisabledReason = tenantMutationDisabledReason({
|
||||||
|
busy,
|
||||||
|
permitted: canWrite,
|
||||||
|
complete: Boolean(draft.default_locale.trim() && draft.enabled_language_codes.length),
|
||||||
|
changed: dirty,
|
||||||
|
permissionReason: TENANCY_INTERFACE_I18N.settingsWriteRequired
|
||||||
|
});
|
||||||
|
|
||||||
useUnsavedDraftGuard({
|
useUnsavedDraftGuard({
|
||||||
dirty,
|
dirty,
|
||||||
@@ -108,11 +131,26 @@ export default function TenantSettingsPanel({
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
error={error}
|
error={error}
|
||||||
success={success}
|
success={success}
|
||||||
actions={<><Button onClick={() => void load()} disabled={loading}>i18n:govoplan-tenancy.reload.cce71553</Button><Button variant="primary" onClick={() => void save()} disabled={!canWrite || busy || !draft.default_locale.trim()}>{busy ? "i18n:govoplan-tenancy.saving.ae7e8875" : "i18n:govoplan-tenancy.save_general_settings.5c90f8c4"}</Button></>}>
|
actions={<><DocumentationHelpLink reference={TENANCY_ADMIN_DOCUMENTATION} /><Button onClick={() => requestDiscard(() => void load())} disabled={loading || busy} disabledReason={loading ? TENANCY_INTERFACE_I18N.loading : busy ? TENANCY_INTERFACE_I18N.busy : undefined}>i18n:govoplan-tenancy.reload.cce71553</Button><Button variant="primary" onClick={() => void save()} disabled={Boolean(saveDisabledReason)} disabledReason={saveDisabledReason}>{busy ? "i18n:govoplan-tenancy.saving.ae7e8875" : "i18n:govoplan-tenancy.save_general_settings.5c90f8c4"}</Button></>}>
|
||||||
|
|
||||||
|
{!canWrite && <ActionBlockerHint
|
||||||
|
reason={{
|
||||||
|
summary: TENANCY_INTERFACE_I18N.readOnlySummary,
|
||||||
|
requiredAction: TENANCY_INTERFACE_I18N.settingsPermissionGuidance,
|
||||||
|
actor: TENANCY_INTERFACE_I18N.administratorActor,
|
||||||
|
target: TENANCY_INTERFACE_I18N.tenantSettingsTarget
|
||||||
|
}}
|
||||||
|
labels={{
|
||||||
|
requiredAction: TENANCY_INTERFACE_I18N.requiredActionLabel,
|
||||||
|
actor: TENANCY_INTERFACE_I18N.actorLabel,
|
||||||
|
target: TENANCY_INTERFACE_I18N.targetLabel
|
||||||
|
}}
|
||||||
|
documentation={TENANCY_ADMIN_DOCUMENTATION}
|
||||||
|
/>}
|
||||||
|
|
||||||
<div className="admin-settings-form">
|
<div className="admin-settings-form">
|
||||||
<Card title="i18n:govoplan-tenancy.locale.8970f0e6">
|
<Card title="i18n:govoplan-tenancy.locale.8970f0e6">
|
||||||
<FormField label="i18n:govoplan-tenancy.tenant_locale.8fc19914" help="i18n:govoplan-tenancy.used_as_this_tenant_s_locale_default_for_tenant_.cf298b8b">
|
<FormField label="i18n:govoplan-tenancy.tenant_locale.8fc19914" help={!canWrite ? TENANCY_INTERFACE_I18N.settingsWriteRequired : "i18n:govoplan-tenancy.used_as_this_tenant_s_locale_default_for_tenant_.cf298b8b"} documentation={TENANCY_FIELD_DOCUMENTATION}>
|
||||||
<select value={draft.default_locale} disabled={!canWrite || busy || defaultLocaleOptions.length === 0} onChange={(event) => setDraft({ ...draft, default_locale: event.target.value })}>
|
<select value={draft.default_locale} disabled={!canWrite || busy || defaultLocaleOptions.length === 0} onChange={(event) => setDraft({ ...draft, default_locale: event.target.value })}>
|
||||||
{defaultLocaleOptions.map((code) => {
|
{defaultLocaleOptions.map((code) => {
|
||||||
const language = draft.available_languages.find((item) => item.code === code);
|
const language = draft.available_languages.find((item) => item.code === code);
|
||||||
@@ -128,7 +166,7 @@ export default function TenantSettingsPanel({
|
|||||||
selected={draft.enabled_language_codes}
|
selected={draft.enabled_language_codes}
|
||||||
onChange={setEnabledLanguages}
|
onChange={setEnabledLanguages}
|
||||||
/>
|
/>
|
||||||
<p className="muted small-note">i18n:govoplan-tenancy.tenant_languages_help</p>
|
<p className="muted small-note"><span>i18n:govoplan-tenancy.tenant_languages_help</span>{" "}<span>{TENANCY_INTERFACE_I18N.defaultLanguageRequired}</span></p>
|
||||||
<dl className="detail-list">
|
<dl className="detail-list">
|
||||||
<div><dt>i18n:govoplan-tenancy.tenant.3ca93c78</dt><dd>{draft.name || "-"}</dd></div>
|
<div><dt>i18n:govoplan-tenancy.tenant.3ca93c78</dt><dd>{draft.name || "-"}</dd></div>
|
||||||
<div><dt>i18n:govoplan-tenancy.slug.094da9b9</dt><dd>{draft.slug || "-"}</dd></div>
|
<div><dt>i18n:govoplan-tenancy.slug.094da9b9</dt><dd>{draft.slug || "-"}</dd></div>
|
||||||
|
|||||||
@@ -1,16 +1,35 @@
|
|||||||
import { useEffect, useMemo, useRef, useState } from "react";
|
import { useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { Pencil, Plus, Search, Trash2 } from "lucide-react";
|
import { Pencil, Plus, Search, Trash2 } from "lucide-react";
|
||||||
import type { ApiSettings, AuthInfo } from "@govoplan/core-webui";
|
import type { ApiSettings, AuthInfo, TenantAdminItem } from "@govoplan/core-webui";
|
||||||
|
import {
|
||||||
|
ActionBlockerHint,
|
||||||
|
AdminIconButton,
|
||||||
|
AdminPageLayout,
|
||||||
|
Button,
|
||||||
|
ConfirmDialog,
|
||||||
|
DataGrid,
|
||||||
|
Dialog,
|
||||||
|
DocumentationHelpLink,
|
||||||
|
FormField,
|
||||||
|
StatusBadge,
|
||||||
|
TableActionGroup,
|
||||||
|
adminErrorMessage,
|
||||||
|
formatAdminDateTime as formatDateTime,
|
||||||
|
i18nMessage,
|
||||||
|
useDeltaWatermarks,
|
||||||
|
usePlatformLanguage,
|
||||||
|
useUnsavedChanges,
|
||||||
|
useUnsavedDraftGuard,
|
||||||
|
type DataGridColumn
|
||||||
|
} from "@govoplan/core-webui";
|
||||||
import { createTenant, fetchSystemSettings, fetchTenantOwnerCandidates, fetchTenantsDelta, updateTenant, type SystemSettingsItem, type TenantOwnerCandidate } from "../../api/tenancy";
|
import { createTenant, fetchSystemSettings, fetchTenantOwnerCandidates, fetchTenantsDelta, updateTenant, type SystemSettingsItem, type TenantOwnerCandidate } from "../../api/tenancy";
|
||||||
import type { TenantAdminItem } from "@govoplan/core-webui";
|
|
||||||
import { Button } from "@govoplan/core-webui";
|
|
||||||
import { DataGrid, type DataGridColumn } from "@govoplan/core-webui";
|
|
||||||
import { Dialog } from "@govoplan/core-webui";
|
|
||||||
import { FormField } from "@govoplan/core-webui";
|
|
||||||
import { StatusBadge } from "@govoplan/core-webui";
|
|
||||||
import { ConfirmDialog } from "@govoplan/core-webui";
|
|
||||||
import { AdminIconButton, AdminPageLayout, TableActionGroup, adminErrorMessage, formatAdminDateTime as formatDateTime, i18nMessage, useDeltaWatermarks, useUnsavedDraftGuard } from "@govoplan/core-webui";
|
|
||||||
import { loadDeltaRows } from "./utils/deltaRows";
|
import { loadDeltaRows } from "./utils/deltaRows";
|
||||||
|
import {
|
||||||
|
TENANCY_ADMIN_DOCUMENTATION,
|
||||||
|
TENANCY_FIELD_DOCUMENTATION,
|
||||||
|
TENANCY_INTERFACE_I18N,
|
||||||
|
tenantMutationDisabledReason
|
||||||
|
} from "./interfacePatterns";
|
||||||
|
|
||||||
type OverrideValue = "inherit" | "allow" | "deny";
|
type OverrideValue = "inherit" | "allow" | "deny";
|
||||||
type TenantDraft = {
|
type TenantDraft = {
|
||||||
@@ -64,6 +83,8 @@ export default function TenantsPanel({
|
|||||||
|
|
||||||
|
|
||||||
}: {settings: ApiSettings;auth: AuthInfo;canCreate: boolean;canUpdate: boolean;canSuspend: boolean;onAuthRefresh: () => Promise<void>;}) {
|
}: {settings: ApiSettings;auth: AuthInfo;canCreate: boolean;canUpdate: boolean;canSuspend: boolean;onAuthRefresh: () => Promise<void>;}) {
|
||||||
|
const { translateText } = usePlatformLanguage();
|
||||||
|
const { requestDiscard } = useUnsavedChanges();
|
||||||
const [tenants, setTenants] = useState<TenantAdminItem[]>([]);
|
const [tenants, setTenants] = useState<TenantAdminItem[]>([]);
|
||||||
const [systemSettings, setSystemSettings] = useState<SystemSettingsItem | null>(null);
|
const [systemSettings, setSystemSettings] = useState<SystemSettingsItem | null>(null);
|
||||||
const [ownerCandidates, setOwnerCandidates] = useState<TenantOwnerCandidate[]>([]);
|
const [ownerCandidates, setOwnerCandidates] = useState<TenantOwnerCandidate[]>([]);
|
||||||
@@ -144,6 +165,11 @@ export default function TenantsPanel({
|
|||||||
setSavedDraftKey(draftKey(emptyDraft));
|
setSavedDraftKey(draftKey(emptyDraft));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function requestCloseEditor() {
|
||||||
|
if (busy) return;
|
||||||
|
requestDiscard(closeEditor);
|
||||||
|
}
|
||||||
|
|
||||||
async function save(): Promise<boolean> {
|
async function save(): Promise<boolean> {
|
||||||
setBusy(true);
|
setBusy(true);
|
||||||
setError("");
|
setError("");
|
||||||
@@ -164,7 +190,7 @@ export default function TenantsPanel({
|
|||||||
...governance
|
...governance
|
||||||
});
|
});
|
||||||
const selectedOwner = ownerCandidates.find((candidate) => candidate.account_id === draft.ownerAccountId);
|
const selectedOwner = ownerCandidates.find((candidate) => candidate.account_id === draft.ownerAccountId);
|
||||||
setSuccess(i18nMessage("i18n:govoplan-tenancy.tenant_value_created_with_value_as_owner.1c18b6fb", { value0: created.name, value1: selectedOwner?.display_name || selectedOwner?.email || "i18n:govoplan-tenancy.the_selected_account.1211bfb9" }));
|
setSuccess(i18nMessage("i18n:govoplan-tenancy.tenant_value_created_with_value_as_owner.1c18b6fb", { value0: created.name, value1: selectedOwner?.display_name || selectedOwner?.email || translateText("i18n:govoplan-tenancy.the_selected_account.1211bfb9") }));
|
||||||
await onAuthRefresh();
|
await onAuthRefresh();
|
||||||
} else if (editing) {
|
} else if (editing) {
|
||||||
const payload: Parameters<typeof updateTenant>[2] = {};
|
const payload: Parameters<typeof updateTenant>[2] = {};
|
||||||
@@ -214,10 +240,17 @@ export default function TenantsPanel({
|
|||||||
const systemAllowsCustomRoles = systemSettings?.allow_tenant_custom_roles !== false;
|
const systemAllowsCustomRoles = systemSettings?.allow_tenant_custom_roles !== false;
|
||||||
const systemAllowsApiKeys = systemSettings?.allow_tenant_api_keys !== false;
|
const systemAllowsApiKeys = systemSettings?.allow_tenant_api_keys !== false;
|
||||||
const systemDeniedGovernance = [
|
const systemDeniedGovernance = [
|
||||||
systemAllowsCustomGroups ? "" : "i18n:govoplan-tenancy.custom_groups.453a605c",
|
systemAllowsCustomGroups ? "" : translateText("i18n:govoplan-tenancy.custom_groups.453a605c"),
|
||||||
systemAllowsCustomRoles ? "" : "i18n:govoplan-tenancy.custom_roles.d48dc976",
|
systemAllowsCustomRoles ? "" : translateText("i18n:govoplan-tenancy.custom_roles.d48dc976"),
|
||||||
systemAllowsApiKeys ? "" : "i18n:govoplan-tenancy.api_keys.94fcf3c2"].
|
systemAllowsApiKeys ? "" : translateText("i18n:govoplan-tenancy.api_keys.94fcf3c2")
|
||||||
filter(Boolean).join(", ");
|
].filter(Boolean).join(", ");
|
||||||
|
const saveDisabledReason = tenantMutationDisabledReason({
|
||||||
|
busy,
|
||||||
|
permitted: editing === "new" ? canCreate : canUpdate,
|
||||||
|
complete: Boolean(draft.name.trim() && draft.slug.trim() && (editing !== "new" || draft.ownerAccountId)),
|
||||||
|
changed: editing === "new" || dirty,
|
||||||
|
permissionReason: editing === "new" ? TENANCY_INTERFACE_I18N.createRequired : TENANCY_INTERFACE_I18N.updateRequired
|
||||||
|
});
|
||||||
const columns = useMemo<DataGridColumn<TenantAdminItem>[]>(() => [
|
const columns = useMemo<DataGridColumn<TenantAdminItem>[]>(() => [
|
||||||
{ id: "name", header: "i18n:govoplan-tenancy.tenant.3ca93c78", width: "minmax(210px, 1fr)", minWidth: 190, resizable: true, sticky: "start", sortable: true, filterable: true, value: (row) => `${row.name} ${row.slug}`, render: (row) => <div><strong>{row.name}</strong><div className="muted small-note">{row.slug}</div></div> },
|
{ id: "name", header: "i18n:govoplan-tenancy.tenant.3ca93c78", width: "minmax(210px, 1fr)", minWidth: 190, resizable: true, sticky: "start", sortable: true, filterable: true, value: (row) => `${row.name} ${row.slug}`, render: (row) => <div><strong>{row.name}</strong><div className="muted small-note">{row.slug}</div></div> },
|
||||||
{ id: "users", header: "i18n:govoplan-tenancy.users.57f2b181", width: 100, resizable: false, sortable: true, filterable: true, filterType: "integer", value: (row) => row.counts.users ?? 0, render: (row) => `${row.counts.active_users ?? 0}/${row.counts.users ?? 0}` },
|
{ id: "users", header: "i18n:govoplan-tenancy.users.57f2b181", width: 100, resizable: false, sortable: true, filterable: true, filterType: "integer", value: (row) => row.counts.users ?? 0, render: (row) => `${row.counts.active_users ?? 0}/${row.counts.users ?? 0}` },
|
||||||
@@ -228,10 +261,10 @@ export default function TenantsPanel({
|
|||||||
{ id: "status", header: "i18n:govoplan-tenancy.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: "status", header: "i18n:govoplan-tenancy.status.bae7d5be", width: 120, resizable: false, sortable: true, filterable: true, value: (row) => row.is_active ? "active" : "inactive", render: (row) => <StatusBadge status={row.is_active ? "active" : "inactive"} /> },
|
||||||
{ id: "actions", header: "i18n:govoplan-tenancy.actions.c3cd636a", width: 150, sticky: "end", resizable: false, align: "right", render: (row) => <TableActionGroup actions={[
|
{ id: "actions", header: "i18n:govoplan-tenancy.actions.c3cd636a", width: 150, sticky: "end", resizable: false, align: "right", render: (row) => <TableActionGroup actions={[
|
||||||
{ id: "inspect", label: i18nMessage("i18n:govoplan-tenancy.inspect_value.9d5d1071", { value0: row.name }), icon: <Search />, onClick: () => setViewing(row) },
|
{ id: "inspect", label: i18nMessage("i18n:govoplan-tenancy.inspect_value.9d5d1071", { value0: row.name }), icon: <Search />, onClick: () => setViewing(row) },
|
||||||
{ id: "edit", label: i18nMessage("i18n:govoplan-tenancy.edit_value.fad75899", { value0: row.name }), icon: <Pencil />, disabled: !canUpdate, onClick: () => openEdit(row) },
|
{ id: "edit", label: i18nMessage("i18n:govoplan-tenancy.edit_value.fad75899", { value0: row.name }), icon: <Pencil />, disabled: !canUpdate || busy, disabledReason: busy ? TENANCY_INTERFACE_I18N.busy : !canUpdate ? TENANCY_INTERFACE_I18N.updateRequired : undefined, onClick: () => openEdit(row) },
|
||||||
{ id: "suspend", label: i18nMessage("i18n:govoplan-tenancy.suspend_value.03a74b32", { value0: row.name }), icon: <Trash2 />, variant: "danger", applicable: row.is_active, disabled: !canSuspend || row.id === activeTenantId, onClick: () => setConfirmSuspend(row) }
|
{ id: "suspend", label: i18nMessage("i18n:govoplan-tenancy.suspend_value.03a74b32", { value0: row.name }), icon: <Trash2 />, variant: "danger", disabled: !canSuspend || row.id === activeTenantId || !row.is_active || busy, disabledReason: busy ? TENANCY_INTERFACE_I18N.busy : !canSuspend ? TENANCY_INTERFACE_I18N.suspendRequired : row.id === activeTenantId ? TENANCY_INTERFACE_I18N.activeTenant : !row.is_active ? TENANCY_INTERFACE_I18N.alreadySuspended : undefined, onClick: () => setConfirmSuspend(row) }
|
||||||
]} /> }],
|
]} minimumSlots={3} /> }],
|
||||||
[activeTenantId, canSuspend, canUpdate]);
|
[activeTenantId, busy, canSuspend, canUpdate]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -241,28 +274,43 @@ export default function TenantsPanel({
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
error={error}
|
error={error}
|
||||||
success={success}
|
success={success}
|
||||||
actions={<><Button onClick={() => void load()} disabled={loading}>i18n:govoplan-tenancy.reload.cce71553</Button><AdminIconButton label="i18n:govoplan-tenancy.add_tenant.b8e32af0" icon={<Plus />} variant="primary" onClick={openCreate} disabled={!canCreate} /></>}>
|
actions={<><DocumentationHelpLink reference={TENANCY_ADMIN_DOCUMENTATION} /><Button onClick={() => void load()} disabled={loading || busy} disabledReason={loading ? TENANCY_INTERFACE_I18N.loading : busy ? TENANCY_INTERFACE_I18N.busy : undefined}>i18n:govoplan-tenancy.reload.cce71553</Button><AdminIconButton label="i18n:govoplan-tenancy.add_tenant.b8e32af0" icon={<Plus />} variant="primary" onClick={openCreate} disabled={!canCreate || busy} disabledReason={busy ? TENANCY_INTERFACE_I18N.busy : !canCreate ? TENANCY_INTERFACE_I18N.createRequired : undefined} /></>}>
|
||||||
|
|
||||||
|
{!canCreate && !canUpdate && !canSuspend && <ActionBlockerHint
|
||||||
|
reason={{
|
||||||
|
summary: TENANCY_INTERFACE_I18N.readOnlySummary,
|
||||||
|
requiredAction: TENANCY_INTERFACE_I18N.registryPermissionGuidance,
|
||||||
|
actor: TENANCY_INTERFACE_I18N.administratorActor,
|
||||||
|
target: TENANCY_INTERFACE_I18N.tenantRegistryTarget
|
||||||
|
}}
|
||||||
|
labels={{
|
||||||
|
requiredAction: TENANCY_INTERFACE_I18N.requiredActionLabel,
|
||||||
|
actor: TENANCY_INTERFACE_I18N.actorLabel,
|
||||||
|
target: TENANCY_INTERFACE_I18N.targetLabel
|
||||||
|
}}
|
||||||
|
documentation={TENANCY_ADMIN_DOCUMENTATION}
|
||||||
|
/>}
|
||||||
|
|
||||||
<div className="admin-table-surface"><DataGrid id="admin-tenants-v3" rows={tenants} columns={columns} initialFit="container" getRowKey={(row) => row.id} emptyText="i18n:govoplan-tenancy.no_tenants_found.72d04cf4" /></div>
|
<div className="admin-table-surface"><DataGrid id="admin-tenants-v3" rows={tenants} columns={columns} initialFit="container" getRowKey={(row) => row.id} emptyText="i18n:govoplan-tenancy.no_tenants_found.72d04cf4" /></div>
|
||||||
</AdminPageLayout>
|
</AdminPageLayout>
|
||||||
|
|
||||||
<Dialog open={editing !== null} title={editing === "new" ? "i18n:govoplan-tenancy.create_tenant.4dbd55d9" : "i18n:govoplan-tenancy.edit_tenant.e2ba43f9"} onClose={() => !busy && setEditing(null)} className="admin-dialog admin-dialog-wide" footer={<><Button onClick={() => setEditing(null)} disabled={busy}>i18n:govoplan-tenancy.cancel.77dfd213</Button><Button variant="primary" onClick={() => void save()} disabled={!(editing === "new" ? canCreate : canUpdate) || busy || !draft.name.trim() || !draft.slug.trim() || editing === "new" && !draft.ownerAccountId}>{busy ? "i18n:govoplan-tenancy.saving.56a2285c" : "i18n:govoplan-tenancy.save_tenant.9eb2ac74"}</Button></>}>
|
<Dialog open={editing !== null} title={editing === "new" ? "i18n:govoplan-tenancy.create_tenant.4dbd55d9" : "i18n:govoplan-tenancy.edit_tenant.e2ba43f9"} onClose={requestCloseEditor} className="admin-dialog admin-dialog-wide" footer={<><Button onClick={requestCloseEditor} disabled={busy} disabledReason={busy ? TENANCY_INTERFACE_I18N.busy : undefined}>i18n:govoplan-tenancy.cancel.77dfd213</Button><Button variant="primary" onClick={() => void save()} disabled={Boolean(saveDisabledReason)} disabledReason={saveDisabledReason}>{busy ? "i18n:govoplan-tenancy.saving.56a2285c" : "i18n:govoplan-tenancy.save_tenant.9eb2ac74"}</Button></>}>
|
||||||
<div className="admin-form-grid two-columns">
|
<div className="admin-form-grid two-columns">
|
||||||
<FormField label="i18n:govoplan-tenancy.name.709a2322"><input value={draft.name} disabled={editing !== "new" && !canUpdate} onChange={(event) => setDraft({ ...draft, name: event.target.value })} /></FormField>
|
<FormField label="i18n:govoplan-tenancy.name.709a2322" help={editing !== "new" && !canUpdate ? TENANCY_INTERFACE_I18N.updateRequired : undefined} documentation={TENANCY_FIELD_DOCUMENTATION}><input value={draft.name} disabled={editing !== "new" && !canUpdate} onChange={(event) => setDraft({ ...draft, name: event.target.value })} /></FormField>
|
||||||
<FormField label="i18n:govoplan-tenancy.slug.094da9b9"><input value={draft.slug} disabled={editing !== "new" || !canCreate} onChange={(event) => setDraft({ ...draft, slug: event.target.value })} /></FormField>
|
<FormField label="i18n:govoplan-tenancy.slug.094da9b9" help={editing !== "new" ? "i18n:govoplan-tenancy.the_tenant_slug_is_immutable_after_creation.7c9a1025" : !canCreate ? TENANCY_INTERFACE_I18N.createRequired : undefined} documentation={TENANCY_FIELD_DOCUMENTATION}><input value={draft.slug} disabled={editing !== "new" || !canCreate} onChange={(event) => setDraft({ ...draft, slug: event.target.value })} /></FormField>
|
||||||
{editing === "new" && <FormField label="i18n:govoplan-tenancy.initial_tenant_owner.682291a9"><select value={draft.ownerAccountId} onChange={(event) => setDraft({ ...draft, ownerAccountId: event.target.value })}>{ownerCandidates.map((candidate) => <option key={candidate.account_id} value={candidate.account_id}>{candidate.display_name ? i18nMessage("i18n:govoplan-tenancy.value_value.c189e8bc", { value0: candidate.display_name, value1: candidate.email }) : candidate.email}</option>)}</select></FormField>}
|
{editing === "new" && <FormField label="i18n:govoplan-tenancy.initial_tenant_owner.682291a9" documentation={TENANCY_FIELD_DOCUMENTATION}><select value={draft.ownerAccountId} onChange={(event) => setDraft({ ...draft, ownerAccountId: event.target.value })}>{ownerCandidates.map((candidate) => <option key={candidate.account_id} value={candidate.account_id}>{candidate.display_name ? i18nMessage("i18n:govoplan-tenancy.value_value.c189e8bc", { value0: candidate.display_name, value1: candidate.email }) : candidate.email}</option>)}</select></FormField>}
|
||||||
<FormField label="i18n:govoplan-tenancy.default_locale.b99d021f"><input value={draft.defaultLocale} disabled={editing !== "new" && !canUpdate} onChange={(event) => setDraft({ ...draft, defaultLocale: event.target.value })} /></FormField>
|
<FormField label="i18n:govoplan-tenancy.default_locale.b99d021f" help={editing !== "new" && !canUpdate ? TENANCY_INTERFACE_I18N.updateRequired : undefined} documentation={TENANCY_FIELD_DOCUMENTATION}><input value={draft.defaultLocale} disabled={editing !== "new" && !canUpdate} onChange={(event) => setDraft({ ...draft, defaultLocale: event.target.value })} /></FormField>
|
||||||
{editing !== "new" && <FormField label="i18n:govoplan-tenancy.status.bae7d5be"><select value={draft.isActive ? "active" : "inactive"} disabled={!canSuspend} onChange={(event) => setDraft({ ...draft, isActive: event.target.value === "active" })}><option value="active">i18n:govoplan-tenancy.active.a733b809</option><option value="inactive">i18n:govoplan-tenancy.suspended.794696a7</option></select></FormField>}
|
{editing !== "new" && <FormField label="i18n:govoplan-tenancy.status.bae7d5be" help={!canSuspend ? TENANCY_INTERFACE_I18N.suspendRequired : undefined} documentation={TENANCY_ADMIN_DOCUMENTATION}><select value={draft.isActive ? "active" : "inactive"} disabled={!canSuspend} onChange={(event) => setDraft({ ...draft, isActive: event.target.value === "active" })}><option value="active">i18n:govoplan-tenancy.active.a733b809</option><option value="inactive">i18n:govoplan-tenancy.suspended.794696a7</option></select></FormField>}
|
||||||
<FormField label="i18n:govoplan-tenancy.description.55f8ebc8"><textarea rows={4} value={draft.description} disabled={editing !== "new" && !canUpdate} onChange={(event) => setDraft({ ...draft, description: event.target.value })} /></FormField>
|
<FormField label="i18n:govoplan-tenancy.description.55f8ebc8" help={editing !== "new" && !canUpdate ? TENANCY_INTERFACE_I18N.updateRequired : undefined} documentation={TENANCY_FIELD_DOCUMENTATION}><textarea rows={4} value={draft.description} disabled={editing !== "new" && !canUpdate} onChange={(event) => setDraft({ ...draft, description: event.target.value })} /></FormField>
|
||||||
</div>
|
</div>
|
||||||
<h3>i18n:govoplan-tenancy.system_governance_overrides.97cdf3ce</h3>
|
<h3>i18n:govoplan-tenancy.system_governance_overrides.97cdf3ce</h3>
|
||||||
<div className="admin-form-grid two-columns">
|
<div className="admin-form-grid two-columns">
|
||||||
<GovernanceSelect disabled={editing !== "new" && !canUpdate} allowDisabled={!systemAllowsCustomGroups} label="i18n:govoplan-tenancy.custom_tenant_groups.570ee603" value={draft.customGroups} onChange={(customGroups) => setDraft({ ...draft, customGroups })} />
|
<GovernanceSelect disabled={editing !== "new" && !canUpdate} disabledReason={editing !== "new" && !canUpdate ? TENANCY_INTERFACE_I18N.updateRequired : undefined} allowDisabled={!systemAllowsCustomGroups} label="i18n:govoplan-tenancy.custom_tenant_groups.570ee603" value={draft.customGroups} onChange={(customGroups) => setDraft({ ...draft, customGroups })} />
|
||||||
<GovernanceSelect disabled={editing !== "new" && !canUpdate} allowDisabled={!systemAllowsCustomRoles} label="i18n:govoplan-tenancy.custom_tenant_roles.a738c37c" value={draft.customRoles} onChange={(customRoles) => setDraft({ ...draft, customRoles })} />
|
<GovernanceSelect disabled={editing !== "new" && !canUpdate} disabledReason={editing !== "new" && !canUpdate ? TENANCY_INTERFACE_I18N.updateRequired : undefined} allowDisabled={!systemAllowsCustomRoles} label="i18n:govoplan-tenancy.custom_tenant_roles.a738c37c" value={draft.customRoles} onChange={(customRoles) => setDraft({ ...draft, customRoles })} />
|
||||||
<GovernanceSelect disabled={editing !== "new" && !canUpdate} allowDisabled={!systemAllowsApiKeys} label="i18n:govoplan-tenancy.tenant_api_keys.4b1d81f8" value={draft.apiKeys} onChange={(apiKeys) => setDraft({ ...draft, apiKeys })} />
|
<GovernanceSelect disabled={editing !== "new" && !canUpdate} disabledReason={editing !== "new" && !canUpdate ? TENANCY_INTERFACE_I18N.updateRequired : undefined} allowDisabled={!systemAllowsApiKeys} label="i18n:govoplan-tenancy.tenant_api_keys.4b1d81f8" value={draft.apiKeys} onChange={(apiKeys) => setDraft({ ...draft, apiKeys })} />
|
||||||
</div>
|
</div>
|
||||||
<p className="muted small-note">i18n:govoplan-tenancy.inherit_follows_the_current_system_setting_expli.60d4d868</p>
|
<p className="muted small-note">i18n:govoplan-tenancy.inherit_follows_the_current_system_setting_expli.60d4d868</p>
|
||||||
{systemDeniedGovernance && <p className="muted small-note">i18n:govoplan-tenancy.explicit_allow_is_unavailable_for.8d05fd4a {systemDeniedGovernance} i18n:govoplan-tenancy.because_the_current_system_setting_denies_it.3f59c244</p>}
|
{systemDeniedGovernance && <p className="muted small-note"><span>i18n:govoplan-tenancy.explicit_allow_is_unavailable_for.8d05fd4a</span>{" "}{systemDeniedGovernance}{" "}<span>i18n:govoplan-tenancy.because_the_current_system_setting_denies_it.3f59c244</span></p>}
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<Dialog open={Boolean(viewing)} title="i18n:govoplan-tenancy.tenant_details.5976ba72" onClose={() => setViewing(null)} className="admin-dialog admin-dialog-wide" footer={<Button onClick={() => setViewing(null)}>i18n:govoplan-tenancy.close.bbfa773e</Button>}>
|
<Dialog open={Boolean(viewing)} title="i18n:govoplan-tenancy.tenant_details.5976ba72" onClose={() => setViewing(null)} className="admin-dialog admin-dialog-wide" footer={<Button onClick={() => setViewing(null)}>i18n:govoplan-tenancy.close.bbfa773e</Button>}>
|
||||||
@@ -270,10 +318,10 @@ export default function TenantsPanel({
|
|||||||
<div><dt>i18n:govoplan-tenancy.tenant.3ca93c78</dt><dd>{viewing.name}</dd></div><div><dt>i18n:govoplan-tenancy.slug.094da9b9</dt><dd>{viewing.slug}</dd></div>
|
<div><dt>i18n:govoplan-tenancy.tenant.3ca93c78</dt><dd>{viewing.name}</dd></div><div><dt>i18n:govoplan-tenancy.slug.094da9b9</dt><dd>{viewing.slug}</dd></div>
|
||||||
<div><dt>i18n:govoplan-tenancy.status.bae7d5be</dt><dd>{viewing.is_active ? "i18n:govoplan-tenancy.active.a733b809" : "i18n:govoplan-tenancy.suspended.794696a7"}</dd></div><div><dt>i18n:govoplan-tenancy.default_locale.b99d021f</dt><dd>{viewing.default_locale}</dd></div>
|
<div><dt>i18n:govoplan-tenancy.status.bae7d5be</dt><dd>{viewing.is_active ? "i18n:govoplan-tenancy.active.a733b809" : "i18n:govoplan-tenancy.suspended.794696a7"}</dd></div><div><dt>i18n:govoplan-tenancy.default_locale.b99d021f</dt><dd>{viewing.default_locale}</dd></div>
|
||||||
<div><dt>i18n:govoplan-tenancy.created.accf40c8</dt><dd>{formatDateTime(viewing.created_at)}</dd></div><div><dt>i18n:govoplan-tenancy.updated.f2f8570d</dt><dd>{formatDateTime(viewing.updated_at)}</dd></div>
|
<div><dt>i18n:govoplan-tenancy.created.accf40c8</dt><dd>{formatDateTime(viewing.created_at)}</dd></div><div><dt>i18n:govoplan-tenancy.updated.f2f8570d</dt><dd>{formatDateTime(viewing.updated_at)}</dd></div>
|
||||||
<div><dt>i18n:govoplan-tenancy.custom_groups.1f7b7c8f</dt><dd>{viewing.effective_governance.allow_custom_groups ? "i18n:govoplan-tenancy.allowed.77c7b490" : "i18n:govoplan-tenancy.denied.63b16bd4"} ({fromOverride(viewing.allow_custom_groups)})</dd></div>
|
<div><dt>i18n:govoplan-tenancy.custom_groups.1f7b7c8f</dt><dd>{viewing.effective_governance.allow_custom_groups ? "i18n:govoplan-tenancy.allowed.77c7b490" : "i18n:govoplan-tenancy.denied.63b16bd4"} ({overrideLabel(fromOverride(viewing.allow_custom_groups))})</dd></div>
|
||||||
<div><dt>i18n:govoplan-tenancy.custom_roles.e78ef63d</dt><dd>{viewing.effective_governance.allow_custom_roles ? "i18n:govoplan-tenancy.allowed.77c7b490" : "i18n:govoplan-tenancy.denied.63b16bd4"} ({fromOverride(viewing.allow_custom_roles)})</dd></div>
|
<div><dt>i18n:govoplan-tenancy.custom_roles.e78ef63d</dt><dd>{viewing.effective_governance.allow_custom_roles ? "i18n:govoplan-tenancy.allowed.77c7b490" : "i18n:govoplan-tenancy.denied.63b16bd4"} ({overrideLabel(fromOverride(viewing.allow_custom_roles))})</dd></div>
|
||||||
<div><dt>i18n:govoplan-tenancy.api_keys.94fcf3c2</dt><dd>{viewing.effective_governance.allow_api_keys ? "i18n:govoplan-tenancy.allowed.77c7b490" : "i18n:govoplan-tenancy.denied.63b16bd4"} ({fromOverride(viewing.allow_api_keys)})</dd></div>
|
<div><dt>i18n:govoplan-tenancy.api_keys.94fcf3c2</dt><dd>{viewing.effective_governance.allow_api_keys ? "i18n:govoplan-tenancy.allowed.77c7b490" : "i18n:govoplan-tenancy.denied.63b16bd4"} ({overrideLabel(fromOverride(viewing.allow_api_keys))})</dd></div>
|
||||||
<div><dt>i18n:govoplan-tenancy.objects.72a83add</dt><dd>{viewing.counts.users ?? 0} i18n:govoplan-tenancy.users.81651889 {viewing.counts.groups ?? 0} i18n:govoplan-tenancy.groups.07551586 {viewing.counts.campaigns ?? 0} i18n:govoplan-tenancy.campaigns.2282ffeb {viewing.counts.files ?? 0} files</dd></div>
|
<div><dt>i18n:govoplan-tenancy.objects.72a83add</dt><dd>{viewing.counts.users ?? 0}{" "}<span>i18n:govoplan-tenancy.users.81651889</span>{" "}{viewing.counts.groups ?? 0}{" "}<span>i18n:govoplan-tenancy.groups.07551586</span>{" "}{viewing.counts.campaigns ?? 0}{" "}<span>i18n:govoplan-tenancy.campaigns.2282ffeb</span>{" "}{viewing.counts.files ?? 0}{" "}<span>i18n:govoplan-tenancy.files_lowercase.7c9a1026</span></dd></div>
|
||||||
</dl>{viewing.description && <p>{viewing.description}</p>}</>}
|
</dl>{viewing.description && <p>{viewing.description}</p>}</>}
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
@@ -282,8 +330,14 @@ export default function TenantsPanel({
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function GovernanceSelect({ label, value, onChange, disabled = false, allowDisabled = false }: {label: string;value: OverrideValue;onChange: (value: OverrideValue) => void;disabled?: boolean;allowDisabled?: boolean;}) {
|
function GovernanceSelect({ label, value, onChange, disabled = false, disabledReason, allowDisabled = false }: {label: string;value: OverrideValue;onChange: (value: OverrideValue) => void;disabled?: boolean;disabledReason?: string;allowDisabled?: boolean;}) {
|
||||||
return <FormField label={label}><select value={value} disabled={disabled} onChange={(event) => onChange(event.target.value as OverrideValue)}><option value="inherit">i18n:govoplan-tenancy.inherit_system_setting.7f125156</option><option value="allow" disabled={allowDisabled}>i18n:govoplan-tenancy.allow_when_system_allows.4c5178cb</option><option value="deny">i18n:govoplan-tenancy.explicitly_deny.17ad945a</option></select></FormField>;
|
return <FormField label={label} help={disabledReason ?? (allowDisabled ? TENANCY_INTERFACE_I18N.governanceSystemLimit : undefined)} documentation={TENANCY_FIELD_DOCUMENTATION}><select value={value} disabled={disabled} onChange={(event) => onChange(event.target.value as OverrideValue)}><option value="inherit">i18n:govoplan-tenancy.inherit_system_setting.7f125156</option><option value="allow" disabled={allowDisabled}>i18n:govoplan-tenancy.allow_when_system_allows.4c5178cb</option><option value="deny">i18n:govoplan-tenancy.explicitly_deny.17ad945a</option></select></FormField>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function overrideLabel(value: OverrideValue): string {
|
||||||
|
if (value === "allow") return TENANCY_INTERFACE_I18N.allowLabel;
|
||||||
|
if (value === "deny") return TENANCY_INTERFACE_I18N.denyLabel;
|
||||||
|
return TENANCY_INTERFACE_I18N.inheritLabel;
|
||||||
}
|
}
|
||||||
|
|
||||||
function draftKey(draft: TenantDraft): string {
|
function draftKey(draft: TenantDraft): string {
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import type { DocumentationHelpReference } from "@govoplan/core-webui";
|
||||||
|
|
||||||
|
export const TENANCY_ADMIN_DOCUMENTATION = {
|
||||||
|
topicId: "tenancy.lifecycle-and-settings",
|
||||||
|
documentationType: "admin"
|
||||||
|
} satisfies DocumentationHelpReference;
|
||||||
|
|
||||||
|
export const TENANCY_FIELD_DOCUMENTATION = {
|
||||||
|
topicId: "tenancy.reference.admin-fields",
|
||||||
|
documentationType: "admin"
|
||||||
|
} satisfies DocumentationHelpReference;
|
||||||
|
|
||||||
|
export const TENANCY_INTERFACE_I18N = {
|
||||||
|
loading: "i18n:govoplan-tenancy.tenant_information_is_loading.7c9a1001",
|
||||||
|
busy: "i18n:govoplan-tenancy.a_tenant_change_is_in_progress.7c9a1002",
|
||||||
|
createRequired: "i18n:govoplan-tenancy.tenant_creation_permission_is_required.7c9a1003",
|
||||||
|
updateRequired: "i18n:govoplan-tenancy.tenant_update_permission_is_required.7c9a1004",
|
||||||
|
suspendRequired: "i18n:govoplan-tenancy.tenant_suspension_permission_is_required.7c9a1005",
|
||||||
|
settingsWriteRequired: "i18n:govoplan-tenancy.tenant_settings_write_permission_is_required.7c9a1006",
|
||||||
|
completeRequiredFields: "i18n:govoplan-tenancy.complete_all_required_tenant_fields_before_saving.7c9a1007",
|
||||||
|
noPendingChanges: "i18n:govoplan-tenancy.make_a_change_before_saving.7c9a1008",
|
||||||
|
activeTenant: "i18n:govoplan-tenancy.switch_to_another_tenant_before_suspending_the_active_tenant.7c9a1009",
|
||||||
|
alreadySuspended: "i18n:govoplan-tenancy.this_tenant_is_already_suspended.7c9a1010",
|
||||||
|
readOnlySummary: "i18n:govoplan-tenancy.tenant_administration_is_read_only.7c9a1011",
|
||||||
|
requiredActionLabel: "i18n:govoplan-tenancy.required_action.7c9a1012",
|
||||||
|
actorLabel: "i18n:govoplan-tenancy.responsible_actor.7c9a1013",
|
||||||
|
targetLabel: "i18n:govoplan-tenancy.destination.7c9a1014",
|
||||||
|
administratorActor: "i18n:govoplan-tenancy.a_system_or_tenant_owner_with_the_required_permission.7c9a1015",
|
||||||
|
tenantRegistryTarget: "i18n:govoplan-tenancy.administration_tenants.7c9a1016",
|
||||||
|
tenantSettingsTarget: "i18n:govoplan-tenancy.administration_tenant_settings.7c9a1017",
|
||||||
|
registryPermissionGuidance: "i18n:govoplan-tenancy.request_a_tenant_management_permission_or_contact_a_system_owner.7c9a1018",
|
||||||
|
settingsPermissionGuidance: "i18n:govoplan-tenancy.request_tenant_settings_write_permission_or_contact_a_tenant_owner.7c9a1019",
|
||||||
|
defaultLanguageRequired: "i18n:govoplan-tenancy.the_default_language_must_remain_enabled.7c9a1020",
|
||||||
|
governanceSystemLimit: "i18n:govoplan-tenancy.system_policy_prevents_this_tenant_from_enabling_the_capability.7c9a1021",
|
||||||
|
inheritLabel: "i18n:govoplan-tenancy.inherit.7c9a1022",
|
||||||
|
allowLabel: "i18n:govoplan-tenancy.allow.7c9a1023",
|
||||||
|
denyLabel: "i18n:govoplan-tenancy.deny.7c9a1024"
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export function tenantMutationDisabledReason({
|
||||||
|
busy,
|
||||||
|
permitted,
|
||||||
|
complete = true,
|
||||||
|
changed = true,
|
||||||
|
permissionReason
|
||||||
|
}: {
|
||||||
|
busy: boolean;
|
||||||
|
permitted: boolean;
|
||||||
|
complete?: boolean;
|
||||||
|
changed?: boolean;
|
||||||
|
permissionReason: string;
|
||||||
|
}): string | undefined {
|
||||||
|
if (busy) return TENANCY_INTERFACE_I18N.busy;
|
||||||
|
if (!permitted) return permissionReason;
|
||||||
|
if (!complete) return TENANCY_INTERFACE_I18N.completeRequiredFields;
|
||||||
|
if (!changed) return TENANCY_INTERFACE_I18N.noPendingChanges;
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
@@ -2,79 +2,131 @@ import type { PlatformTranslations } from "@govoplan/core-webui";
|
|||||||
|
|
||||||
export const generatedTranslations: PlatformTranslations = {
|
export const generatedTranslations: PlatformTranslations = {
|
||||||
"en": {
|
"en": {
|
||||||
|
"i18n:govoplan-tenancy.tenant_information_is_loading.7c9a1001": "Tenant information is loading.",
|
||||||
|
"i18n:govoplan-tenancy.a_tenant_change_is_in_progress.7c9a1002": "A tenant change is in progress.",
|
||||||
|
"i18n:govoplan-tenancy.tenant_creation_permission_is_required.7c9a1003": "Tenant creation permission is required.",
|
||||||
|
"i18n:govoplan-tenancy.tenant_update_permission_is_required.7c9a1004": "Tenant update permission is required.",
|
||||||
|
"i18n:govoplan-tenancy.tenant_suspension_permission_is_required.7c9a1005": "Tenant suspension permission is required.",
|
||||||
|
"i18n:govoplan-tenancy.tenant_settings_write_permission_is_required.7c9a1006": "Tenant settings write permission is required.",
|
||||||
|
"i18n:govoplan-tenancy.complete_all_required_tenant_fields_before_saving.7c9a1007": "Complete all required tenant fields before saving.",
|
||||||
|
"i18n:govoplan-tenancy.make_a_change_before_saving.7c9a1008": "Make a change before saving.",
|
||||||
|
"i18n:govoplan-tenancy.switch_to_another_tenant_before_suspending_the_active_tenant.7c9a1009": "Switch to another tenant before suspending the active tenant.",
|
||||||
|
"i18n:govoplan-tenancy.this_tenant_is_already_suspended.7c9a1010": "This tenant is already suspended.",
|
||||||
|
"i18n:govoplan-tenancy.tenant_administration_is_read_only.7c9a1011": "Tenant administration is read-only.",
|
||||||
|
"i18n:govoplan-tenancy.required_action.7c9a1012": "Required action",
|
||||||
|
"i18n:govoplan-tenancy.responsible_actor.7c9a1013": "Responsible actor",
|
||||||
|
"i18n:govoplan-tenancy.destination.7c9a1014": "Destination",
|
||||||
|
"i18n:govoplan-tenancy.a_system_or_tenant_owner_with_the_required_permission.7c9a1015": "A system or tenant owner with the required permission",
|
||||||
|
"i18n:govoplan-tenancy.administration_tenants.7c9a1016": "Administration > Tenants",
|
||||||
|
"i18n:govoplan-tenancy.administration_tenant_settings.7c9a1017": "Administration > Tenant settings",
|
||||||
|
"i18n:govoplan-tenancy.request_a_tenant_management_permission_or_contact_a_system_owner.7c9a1018": "Request a tenant-management permission or contact a system owner.",
|
||||||
|
"i18n:govoplan-tenancy.request_tenant_settings_write_permission_or_contact_a_tenant_owner.7c9a1019": "Request tenant-settings write permission or contact a tenant owner.",
|
||||||
|
"i18n:govoplan-tenancy.the_default_language_must_remain_enabled.7c9a1020": "The default language must remain enabled.",
|
||||||
|
"i18n:govoplan-tenancy.system_policy_prevents_this_tenant_from_enabling_the_capability.7c9a1021": "System policy prevents this tenant from enabling the capability.",
|
||||||
|
"i18n:govoplan-tenancy.inherit.7c9a1022": "inherit",
|
||||||
|
"i18n:govoplan-tenancy.allow.7c9a1023": "allow",
|
||||||
|
"i18n:govoplan-tenancy.deny.7c9a1024": "deny",
|
||||||
|
"i18n:govoplan-tenancy.the_tenant_slug_is_immutable_after_creation.7c9a1025": "The tenant slug is immutable after creation.",
|
||||||
|
"i18n:govoplan-tenancy.files_lowercase.7c9a1026": "files",
|
||||||
"i18n:govoplan-tenancy.actions.c3cd636a": "Actions",
|
"i18n:govoplan-tenancy.actions.c3cd636a": "Actions",
|
||||||
"i18n:govoplan-tenancy.active.a733b809": "Active",
|
"i18n:govoplan-tenancy.active.a733b809": "Active",
|
||||||
"i18n:govoplan-tenancy.add_tenant.b8e32af0": "Add tenant",
|
"i18n:govoplan-tenancy.add_tenant.b8e32af0": "Add tenant",
|
||||||
"i18n:govoplan-tenancy.allow_when_system_allows.4c5178cb": "Allow when system allows",
|
"i18n:govoplan-tenancy.allow_when_system_allows.4c5178cb": "Erlauben, wenn systemweit zulässig",
|
||||||
"i18n:govoplan-tenancy.allowed.77c7b490": "Allowed",
|
"i18n:govoplan-tenancy.allowed.77c7b490": "Erlaubt",
|
||||||
"i18n:govoplan-tenancy.api_keys.94fcf3c2": "API keys",
|
"i18n:govoplan-tenancy.api_keys.94fcf3c2": "API keys",
|
||||||
"i18n:govoplan-tenancy.available.7c62a142": "Available",
|
"i18n:govoplan-tenancy.available.7c62a142": "Available",
|
||||||
"i18n:govoplan-tenancy.because_the_current_system_setting_denies_it.3f59c244": "because the current system setting denies it.",
|
"i18n:govoplan-tenancy.because_the_current_system_setting_denies_it.3f59c244": "da die aktuelle Systemeinstellung dies verweigert.",
|
||||||
"i18n:govoplan-tenancy.campaigns.01a23a28": "Campaigns",
|
"i18n:govoplan-tenancy.campaigns.01a23a28": "Campaigns",
|
||||||
"i18n:govoplan-tenancy.campaigns.2282ffeb": "campaigns,",
|
"i18n:govoplan-tenancy.campaigns.2282ffeb": "Kampagnen,",
|
||||||
"i18n:govoplan-tenancy.cancel.77dfd213": "Cancel",
|
"i18n:govoplan-tenancy.cancel.77dfd213": "Cancel",
|
||||||
"i18n:govoplan-tenancy.close.bbfa773e": "Close",
|
"i18n:govoplan-tenancy.close.bbfa773e": "Close",
|
||||||
"i18n:govoplan-tenancy.create_and_govern_tenant_spaces_suspension_retai.1b76d377": "Create and govern tenant spaces. Suspension retains campaigns, files and audit evidence; the tenant backing the current session cannot be suspended.",
|
"i18n:govoplan-tenancy.create_and_govern_tenant_spaces_suspension_retai.1b76d377": "Mandantenbereiche erstellen und verwalten. Eine Sperrung bewahrt Kampagnen, Dateien und Nachweise; der Mandant der aktuellen Sitzung kann nicht gesperrt werden.",
|
||||||
"i18n:govoplan-tenancy.create_tenant.4dbd55d9": "Create tenant",
|
"i18n:govoplan-tenancy.create_tenant.4dbd55d9": "Create tenant",
|
||||||
"i18n:govoplan-tenancy.created.accf40c8": "Created",
|
"i18n:govoplan-tenancy.created.accf40c8": "Created",
|
||||||
"i18n:govoplan-tenancy.custom_groups.1f7b7c8f": "Custom groups",
|
"i18n:govoplan-tenancy.custom_groups.1f7b7c8f": "Eigene Gruppen",
|
||||||
"i18n:govoplan-tenancy.custom_groups.453a605c": "custom groups",
|
"i18n:govoplan-tenancy.custom_groups.453a605c": "eigene Gruppen",
|
||||||
"i18n:govoplan-tenancy.custom_roles.d48dc976": "custom roles",
|
"i18n:govoplan-tenancy.custom_roles.d48dc976": "eigene Rollen",
|
||||||
"i18n:govoplan-tenancy.custom_roles.e78ef63d": "Custom roles",
|
"i18n:govoplan-tenancy.custom_roles.e78ef63d": "Eigene Rollen",
|
||||||
"i18n:govoplan-tenancy.custom_tenant_groups.570ee603": "Custom tenant groups",
|
"i18n:govoplan-tenancy.custom_tenant_groups.570ee603": "Eigene Mandantengruppen",
|
||||||
"i18n:govoplan-tenancy.custom_tenant_roles.a738c37c": "Custom tenant roles",
|
"i18n:govoplan-tenancy.custom_tenant_roles.a738c37c": "Eigene Mandantenrollen",
|
||||||
"i18n:govoplan-tenancy.default_locale.b99d021f": "Default locale",
|
"i18n:govoplan-tenancy.default_locale.b99d021f": "Default locale",
|
||||||
"i18n:govoplan-tenancy.denied.63b16bd4": "Denied",
|
"i18n:govoplan-tenancy.denied.63b16bd4": "Verweigert",
|
||||||
"i18n:govoplan-tenancy.description.55f8ebc8": "Description",
|
"i18n:govoplan-tenancy.description.55f8ebc8": "Description",
|
||||||
"i18n:govoplan-tenancy.edit_tenant.e2ba43f9": "Edit tenant",
|
"i18n:govoplan-tenancy.edit_tenant.e2ba43f9": "Edit tenant",
|
||||||
"i18n:govoplan-tenancy.edit_value.fad75899": "Edit {value0}",
|
"i18n:govoplan-tenancy.edit_value.fad75899": "{value0} bearbeiten",
|
||||||
"i18n:govoplan-tenancy.explicit_allow_is_unavailable_for.8d05fd4a": "Explicit allow is unavailable for",
|
"i18n:govoplan-tenancy.explicit_allow_is_unavailable_for.8d05fd4a": "Eine ausdrückliche Erlaubnis ist nicht verfügbar für",
|
||||||
"i18n:govoplan-tenancy.explicitly_deny.17ad945a": "Explicitly deny",
|
"i18n:govoplan-tenancy.explicitly_deny.17ad945a": "Ausdrücklich verweigern",
|
||||||
"i18n:govoplan-tenancy.files.6ce6c512": "Files",
|
"i18n:govoplan-tenancy.files.6ce6c512": "Files",
|
||||||
"i18n:govoplan-tenancy.general.9239ee2c": "General",
|
"i18n:govoplan-tenancy.general.9239ee2c": "General",
|
||||||
"i18n:govoplan-tenancy.groups.07551586": "groups,",
|
"i18n:govoplan-tenancy.groups.07551586": "Gruppen,",
|
||||||
"i18n:govoplan-tenancy.groups.ae9629f4": "Groups",
|
"i18n:govoplan-tenancy.groups.ae9629f4": "Groups",
|
||||||
"i18n:govoplan-tenancy.inherit_follows_the_current_system_setting_expli.60d4d868": "Inherit follows the current system setting. Explicit deny narrows access; explicit allow is valid only while the system setting allows it.",
|
"i18n:govoplan-tenancy.inherit_follows_the_current_system_setting_expli.60d4d868": "Vererben folgt der aktuellen Systemeinstellung. Eine ausdrückliche Verweigerung schränkt ein; eine ausdrückliche Erlaubnis gilt nur, solange sie systemweit zulässig ist.",
|
||||||
"i18n:govoplan-tenancy.inherit_system_setting.7f125156": "Inherit system setting",
|
"i18n:govoplan-tenancy.inherit_system_setting.7f125156": "Systemeinstellung vererben",
|
||||||
"i18n:govoplan-tenancy.initial_tenant_owner.682291a9": "Initial tenant owner",
|
"i18n:govoplan-tenancy.initial_tenant_owner.682291a9": "Anfängliche Mandanteneigentümerschaft",
|
||||||
"i18n:govoplan-tenancy.inspect_value.9d5d1071": "Inspect {value0}",
|
"i18n:govoplan-tenancy.inspect_value.9d5d1071": "{value0} anzeigen",
|
||||||
"i18n:govoplan-tenancy.locale.8970f0e6": "Locale",
|
"i18n:govoplan-tenancy.locale.8970f0e6": "Locale",
|
||||||
"i18n:govoplan-tenancy.name.709a2322": "Name",
|
"i18n:govoplan-tenancy.name.709a2322": "Name",
|
||||||
"i18n:govoplan-tenancy.no_tenants_found.72d04cf4": "No tenants found.",
|
"i18n:govoplan-tenancy.no_tenants_found.72d04cf4": "Keine Mandanten gefunden.",
|
||||||
"i18n:govoplan-tenancy.objects.72a83add": "Objects",
|
"i18n:govoplan-tenancy.objects.72a83add": "Objekte",
|
||||||
"i18n:govoplan-tenancy.reload.cce71553": "Reload",
|
"i18n:govoplan-tenancy.reload.cce71553": "Reload",
|
||||||
"i18n:govoplan-tenancy.save_general_settings.5c90f8c4": "Save general settings",
|
"i18n:govoplan-tenancy.save_general_settings.5c90f8c4": "Allgemeine Einstellungen speichern",
|
||||||
"i18n:govoplan-tenancy.save_tenant.9eb2ac74": "Save tenant",
|
"i18n:govoplan-tenancy.save_tenant.9eb2ac74": "Save tenant",
|
||||||
"i18n:govoplan-tenancy.saving.56a2285c": "Saving…",
|
"i18n:govoplan-tenancy.saving.56a2285c": "Speichern…",
|
||||||
"i18n:govoplan-tenancy.saving.ae7e8875": "Saving...",
|
"i18n:govoplan-tenancy.saving.ae7e8875": "Speichern...",
|
||||||
"i18n:govoplan-tenancy.settings_for_the_active_tenant_context.ad267b86": "Settings for the active tenant context.",
|
"i18n:govoplan-tenancy.settings_for_the_active_tenant_context.ad267b86": "Einstellungen für den aktiven Mandantenkontext.",
|
||||||
"i18n:govoplan-tenancy.slug.094da9b9": "Slug",
|
"i18n:govoplan-tenancy.slug.094da9b9": "Slug",
|
||||||
"i18n:govoplan-tenancy.status.bae7d5be": "Status",
|
"i18n:govoplan-tenancy.status.bae7d5be": "Status",
|
||||||
"i18n:govoplan-tenancy.suspend_tenant.151d283a": "Suspend tenant",
|
"i18n:govoplan-tenancy.suspend_tenant.151d283a": "Mandant sperren",
|
||||||
"i18n:govoplan-tenancy.suspend_value.03a74b32": "Suspend {value0}",
|
"i18n:govoplan-tenancy.suspend_value.03a74b32": "{value0} sperren",
|
||||||
"i18n:govoplan-tenancy.suspend_value_existing_data_remains_retained_but.19bccd78": "Suspend {value0}? Existing data remains retained, but its members cannot use the tenant.",
|
"i18n:govoplan-tenancy.suspend_value_existing_data_remains_retained_but.19bccd78": "{value0} sperren? Bestehende Daten bleiben erhalten, aber Mitglieder können den Mandanten nicht mehr verwenden.",
|
||||||
"i18n:govoplan-tenancy.suspended.794696a7": "Suspended",
|
"i18n:govoplan-tenancy.suspended.794696a7": "Gesperrt",
|
||||||
"i18n:govoplan-tenancy.system_governance_overrides.97cdf3ce": "System governance overrides",
|
"i18n:govoplan-tenancy.system_governance_overrides.97cdf3ce": "Systemvorgaben",
|
||||||
"i18n:govoplan-tenancy.tenancy": "Tenancy",
|
"i18n:govoplan-tenancy.tenancy": "Tenancy",
|
||||||
"i18n:govoplan-tenancy.tenant.3ca93c78": "Tenant",
|
"i18n:govoplan-tenancy.tenant.3ca93c78": "Tenant",
|
||||||
"i18n:govoplan-tenancy.tenant_api_keys.4b1d81f8": "Tenant API keys",
|
"i18n:govoplan-tenancy.tenant_api_keys.4b1d81f8": "Tenant API keys",
|
||||||
"i18n:govoplan-tenancy.tenant_details.5976ba72": "Tenant details",
|
"i18n:govoplan-tenancy.tenant_details.5976ba72": "Mandantendetails",
|
||||||
"i18n:govoplan-tenancy.tenant_general_settings.db1c3ba8": "Tenant general settings",
|
"i18n:govoplan-tenancy.tenant_general_settings.db1c3ba8": "Allgemeine Mandanteneinstellungen",
|
||||||
"i18n:govoplan-tenancy.tenant_general_settings_saved.485e7681": "Tenant general settings saved.",
|
"i18n:govoplan-tenancy.tenant_general_settings_saved.485e7681": "Allgemeine Mandanteneinstellungen gespeichert.",
|
||||||
"i18n:govoplan-tenancy.tenant_languages_help": "Tenant languages can only be selected from languages enabled by the system. Users can choose from the tenant-enabled set.",
|
"i18n:govoplan-tenancy.tenant_languages_help": "Tenant languages can only be selected from languages enabled by the system. Users can choose from the tenant-enabled set.",
|
||||||
"i18n:govoplan-tenancy.tenant_locale.8fc19914": "Tenant locale",
|
"i18n:govoplan-tenancy.tenant_locale.8fc19914": "Mandantensprache",
|
||||||
"i18n:govoplan-tenancy.tenant_value_created_with_value_as_owner.1c18b6fb": "Tenant {value0} created with {value1} as Owner.",
|
"i18n:govoplan-tenancy.tenant_value_created_with_value_as_owner.1c18b6fb": "Mandant {value0} mit {value1} als Eigentümerschaft erstellt.",
|
||||||
"i18n:govoplan-tenancy.tenant_value_updated.25b2c855": "Tenant {value0} updated.",
|
"i18n:govoplan-tenancy.tenant_value_updated.25b2c855": "Mandant {value0} aktualisiert.",
|
||||||
"i18n:govoplan-tenancy.tenants.1f7ae776": "Tenants",
|
"i18n:govoplan-tenancy.tenants.1f7ae776": "Tenants",
|
||||||
"i18n:govoplan-tenancy.the_selected_account.1211bfb9": "the selected account",
|
"i18n:govoplan-tenancy.the_selected_account.1211bfb9": "das ausgewählte Konto",
|
||||||
"i18n:govoplan-tenancy.updated.f2f8570d": "Updated",
|
"i18n:govoplan-tenancy.updated.f2f8570d": "Updated",
|
||||||
"i18n:govoplan-tenancy.used_as_this_tenant_s_locale_default_for_tenant_.cf298b8b": "Used as this tenant's locale default for tenant-aware views and future formatting defaults.",
|
"i18n:govoplan-tenancy.used_as_this_tenant_s_locale_default_for_tenant_.cf298b8b": "Wird als Standardsprache dieses Mandanten für mandantenbezogene Ansichten und Formatierungen verwendet.",
|
||||||
"i18n:govoplan-tenancy.users.57f2b181": "Users",
|
"i18n:govoplan-tenancy.users.57f2b181": "Users",
|
||||||
"i18n:govoplan-tenancy.users.81651889": "users,",
|
"i18n:govoplan-tenancy.users.81651889": "Benutzer,",
|
||||||
"i18n:govoplan-tenancy.value_suspended.31731a28": "{value0} suspended.",
|
"i18n:govoplan-tenancy.value_suspended.31731a28": "{value0} gesperrt.",
|
||||||
"i18n:govoplan-tenancy.value_value.c189e8bc": "{value0} ({value1})"
|
"i18n:govoplan-tenancy.value_value.c189e8bc": "{value0} ({value1})"
|
||||||
},
|
},
|
||||||
"de": {
|
"de": {
|
||||||
|
"i18n:govoplan-tenancy.tenant_information_is_loading.7c9a1001": "Mandanteninformationen werden geladen.",
|
||||||
|
"i18n:govoplan-tenancy.a_tenant_change_is_in_progress.7c9a1002": "Eine Mandantenänderung wird gerade ausgeführt.",
|
||||||
|
"i18n:govoplan-tenancy.tenant_creation_permission_is_required.7c9a1003": "Die Berechtigung zum Erstellen von Mandanten ist erforderlich.",
|
||||||
|
"i18n:govoplan-tenancy.tenant_update_permission_is_required.7c9a1004": "Die Berechtigung zum Bearbeiten von Mandanten ist erforderlich.",
|
||||||
|
"i18n:govoplan-tenancy.tenant_suspension_permission_is_required.7c9a1005": "Die Berechtigung zum Sperren von Mandanten ist erforderlich.",
|
||||||
|
"i18n:govoplan-tenancy.tenant_settings_write_permission_is_required.7c9a1006": "Die Schreibberechtigung für Mandanteneinstellungen ist erforderlich.",
|
||||||
|
"i18n:govoplan-tenancy.complete_all_required_tenant_fields_before_saving.7c9a1007": "Füllen Sie vor dem Speichern alle erforderlichen Mandantenfelder aus.",
|
||||||
|
"i18n:govoplan-tenancy.make_a_change_before_saving.7c9a1008": "Nehmen Sie vor dem Speichern eine Änderung vor.",
|
||||||
|
"i18n:govoplan-tenancy.switch_to_another_tenant_before_suspending_the_active_tenant.7c9a1009": "Wechseln Sie zu einem anderen Mandanten, bevor Sie den aktiven Mandanten sperren.",
|
||||||
|
"i18n:govoplan-tenancy.this_tenant_is_already_suspended.7c9a1010": "Dieser Mandant ist bereits gesperrt.",
|
||||||
|
"i18n:govoplan-tenancy.tenant_administration_is_read_only.7c9a1011": "Die Mandantenverwaltung ist schreibgeschützt.",
|
||||||
|
"i18n:govoplan-tenancy.required_action.7c9a1012": "Erforderliche Aktion",
|
||||||
|
"i18n:govoplan-tenancy.responsible_actor.7c9a1013": "Zuständige Stelle",
|
||||||
|
"i18n:govoplan-tenancy.destination.7c9a1014": "Ziel",
|
||||||
|
"i18n:govoplan-tenancy.a_system_or_tenant_owner_with_the_required_permission.7c9a1015": "Eine System- oder Mandanteneigentümerschaft mit der erforderlichen Berechtigung",
|
||||||
|
"i18n:govoplan-tenancy.administration_tenants.7c9a1016": "Administration > Mandanten",
|
||||||
|
"i18n:govoplan-tenancy.administration_tenant_settings.7c9a1017": "Administration > Mandanteneinstellungen",
|
||||||
|
"i18n:govoplan-tenancy.request_a_tenant_management_permission_or_contact_a_system_owner.7c9a1018": "Fordern Sie eine Mandantenverwaltungsberechtigung an oder wenden Sie sich an eine Systemeigentümerschaft.",
|
||||||
|
"i18n:govoplan-tenancy.request_tenant_settings_write_permission_or_contact_a_tenant_owner.7c9a1019": "Fordern Sie die Schreibberechtigung für Mandanteneinstellungen an oder wenden Sie sich an eine Mandanteneigentümerschaft.",
|
||||||
|
"i18n:govoplan-tenancy.the_default_language_must_remain_enabled.7c9a1020": "Die Standardsprache muss aktiviert bleiben.",
|
||||||
|
"i18n:govoplan-tenancy.system_policy_prevents_this_tenant_from_enabling_the_capability.7c9a1021": "Die Systemrichtlinie verhindert, dass dieser Mandant die Funktion aktiviert.",
|
||||||
|
"i18n:govoplan-tenancy.inherit.7c9a1022": "vererbt",
|
||||||
|
"i18n:govoplan-tenancy.allow.7c9a1023": "erlauben",
|
||||||
|
"i18n:govoplan-tenancy.deny.7c9a1024": "verweigern",
|
||||||
|
"i18n:govoplan-tenancy.the_tenant_slug_is_immutable_after_creation.7c9a1025": "Der Mandanten-Slug kann nach der Erstellung nicht geändert werden.",
|
||||||
|
"i18n:govoplan-tenancy.files_lowercase.7c9a1026": "Dateien",
|
||||||
"i18n:govoplan-tenancy.actions.c3cd636a": "Aktionen",
|
"i18n:govoplan-tenancy.actions.c3cd636a": "Aktionen",
|
||||||
"i18n:govoplan-tenancy.active.a733b809": "Aktiv",
|
"i18n:govoplan-tenancy.active.a733b809": "Aktiv",
|
||||||
"i18n:govoplan-tenancy.add_tenant.b8e32af0": "Mandant hinzufügen",
|
"i18n:govoplan-tenancy.add_tenant.b8e32af0": "Mandant hinzufügen",
|
||||||
|
|||||||
+3
-3
@@ -53,7 +53,7 @@ const adminSections: AdminSectionsUiCapability = {
|
|||||||
export const tenancyModule: PlatformWebModule = {
|
export const tenancyModule: PlatformWebModule = {
|
||||||
id: "tenancy",
|
id: "tenancy",
|
||||||
label: "i18n:govoplan-tenancy.tenancy",
|
label: "i18n:govoplan-tenancy.tenancy",
|
||||||
version: "1.0.0",
|
version: "0.1.8",
|
||||||
optionalDependencies: ["access"],
|
optionalDependencies: ["access"],
|
||||||
translations: {
|
translations: {
|
||||||
en: generatedTranslations.en,
|
en: generatedTranslations.en,
|
||||||
@@ -64,14 +64,14 @@ export const tenancyModule: PlatformWebModule = {
|
|||||||
id: "tenancy.admin.system-tenants",
|
id: "tenancy.admin.system-tenants",
|
||||||
moduleId: "tenancy",
|
moduleId: "tenancy",
|
||||||
kind: "section",
|
kind: "section",
|
||||||
label: "System tenants",
|
label: "i18n:govoplan-tenancy.tenants.1f7ae776",
|
||||||
order: 10
|
order: 10
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "tenancy.admin.tenant-settings",
|
id: "tenancy.admin.tenant-settings",
|
||||||
moduleId: "tenancy",
|
moduleId: "tenancy",
|
||||||
kind: "section",
|
kind: "section",
|
||||||
label: "Tenant settings",
|
label: "i18n:govoplan-tenancy.tenant_general_settings.db1c3ba8",
|
||||||
order: 90
|
order: 90
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user