Migrate Access administration patterns
This commit is contained in:
@@ -83,6 +83,9 @@ instead of importing access ORM models or backend dependency internals.
|
|||||||
|
|
||||||
The detailed module boundary and serialization fields are documented in
|
The detailed module boundary and serialization fields are documented in
|
||||||
[docs/ACCESS_MODULE_BOUNDARY.md](docs/ACCESS_MODULE_BOUNDARY.md).
|
[docs/ACCESS_MODULE_BOUNDARY.md](docs/ACCESS_MODULE_BOUNDARY.md).
|
||||||
|
The Access-owned administration surfaces, consequence classes, shared control
|
||||||
|
contract, contextual-help references, and verification evidence are recorded
|
||||||
|
in [docs/INTERFACE_PATTERN_MIGRATION.md](docs/INTERFACE_PATTERN_MIGRATION.md).
|
||||||
|
|
||||||
For scheduled and event-driven work, Access provides
|
For scheduled and event-driven work, Access provides
|
||||||
`auth.automationPrincipalProvider`. Automation records store only an owner
|
`auth.automationPrincipalProvider`. Automation records store only an owner
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
# Access interface pattern migration
|
||||||
|
|
||||||
|
This document records the Access-owned surfaces covered by the platform
|
||||||
|
interface pattern language. Shared primitives remain owned by Core and
|
||||||
|
optional Mail, Files, Organizations, IDM, and Docs behavior is consumed only
|
||||||
|
through declared capabilities or metadata.
|
||||||
|
|
||||||
|
## Surface inventory
|
||||||
|
|
||||||
|
| Surface | Archetype | Authority and state model |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `/admin` tree and unavailable state | Tree-navigated administration workspace | The effective principal and active View determine which branches exist. A missing administration grant is an explained blocker, not an empty route. |
|
||||||
|
| System and tenant users | Server-authoritative directory plus list/detail editor | Delta reads refresh accounts and memberships. Create, update, assignment, suspension, and final-owner safeguards remain independent permissions. |
|
||||||
|
| System and tenant roles | Governed definition directory | Built-in and system-managed definitions remain visible but immutable. Assigned roles cannot be deleted. |
|
||||||
|
| Tenant groups | Governed definition and membership editor | Definition, membership, and role-assignment rights remain independent. Required system groups cannot be deactivated. |
|
||||||
|
| Tenant API keys | Immutable-secret lifecycle directory | A key is created once, its secret is shown once, and revocation is consequential and confirmed. |
|
||||||
|
| Function mappings | Governed cross-module mapping editor | Organizations supplies function choices, IDM supplies accepted facts, and Access maps facts to assignable roles. |
|
||||||
|
| Credential scopes | Adaptive configuration panel | Core owns the reusable credential manager. Access supplies system, tenant, group, and user ownership choices. |
|
||||||
|
| Mail and Files scope panels | Optional capability host | Access supplies owner selection; the owning module supplies configuration UI. A missing capability names the required module, actor, and destination. |
|
||||||
|
|
||||||
|
## Consequence classes
|
||||||
|
|
||||||
|
- Reload, inspect, filter, select, and open-help actions are reversible.
|
||||||
|
- User, group, role, mapping, and credential edits are governed mutations and
|
||||||
|
expose permission or validation blockers before submission.
|
||||||
|
- Account or membership deactivation, group deactivation, role deletion,
|
||||||
|
mapping deletion, API-key revocation, and credential deletion are
|
||||||
|
consequential actions and use the shared confirmation contract.
|
||||||
|
- Secret values and temporary passwords are never placed in list rows or
|
||||||
|
persistent notices. One-time values remain inside dedicated dialogs.
|
||||||
|
|
||||||
|
## Interaction evidence
|
||||||
|
|
||||||
|
- `AdminPageLayout`, `TreeSubnav`, `DataGrid`, `Dialog`, `ConfirmDialog`,
|
||||||
|
`TableActionGroup`, `PasswordField`, `ActionBlockerHint`, and
|
||||||
|
`DocumentationHelpLink` come from Core.
|
||||||
|
- Dialog focus trapping and restoration, disabled-action tooltips, keyboard
|
||||||
|
ordering, responsive grid overflow, and alert semantics therefore inherit
|
||||||
|
the tested Core behavior.
|
||||||
|
- All Access-owned labels added by this migration are present in the English
|
||||||
|
and German module catalogs.
|
||||||
|
- The WebUI structural test rejects browser-native confirmation calls, private
|
||||||
|
sibling imports, missing contextual-help references, and unexplained
|
||||||
|
optional-module blockers.
|
||||||
|
|
||||||
|
## Documentation contexts
|
||||||
|
|
||||||
|
- `access.workflow.grant-user-access` covers the user, group, and role path.
|
||||||
|
- `access.reference.admin-access-fields` covers accounts, roles, API keys, and
|
||||||
|
reusable credentials.
|
||||||
|
- `access.reference.external-function-role-mappings` explains the
|
||||||
|
Organizations, IDM, and Access responsibility split.
|
||||||
|
- Files and Mail blockers link to documentation supplied by the owning module.
|
||||||
@@ -300,6 +300,12 @@ ACCESS_DOCUMENTATION: tuple[DocumentationTopic, ...] = (
|
|||||||
configuration_keys=("access_governance",),
|
configuration_keys=("access_governance",),
|
||||||
metadata={
|
metadata={
|
||||||
"kind": "workflow",
|
"kind": "workflow",
|
||||||
|
"help_contexts": [
|
||||||
|
"access.admin.users",
|
||||||
|
"access.admin.groups",
|
||||||
|
"access.admin.roles",
|
||||||
|
"access.admin.blocked",
|
||||||
|
],
|
||||||
"outcome": "A person can sign in to the tenant and receives the intended access through groups and roles.",
|
"outcome": "A person can sign in to the tenant and receives the intended access through groups and roles.",
|
||||||
"prerequisites": [
|
"prerequisites": [
|
||||||
"You can open Admin.",
|
"You can open Admin.",
|
||||||
@@ -362,6 +368,15 @@ ACCESS_DOCUMENTATION: tuple[DocumentationTopic, ...] = (
|
|||||||
configuration_keys=("access_governance",),
|
configuration_keys=("access_governance",),
|
||||||
metadata={
|
metadata={
|
||||||
"kind": "reference",
|
"kind": "reference",
|
||||||
|
"help_contexts": [
|
||||||
|
"access.admin.system-users",
|
||||||
|
"access.admin.system-roles",
|
||||||
|
"access.admin.tenant-users",
|
||||||
|
"access.admin.tenant-groups",
|
||||||
|
"access.admin.tenant-roles",
|
||||||
|
"access.admin.api-keys",
|
||||||
|
"access.credentials",
|
||||||
|
],
|
||||||
"route": "/admin",
|
"route": "/admin",
|
||||||
"screen": "Admin",
|
"screen": "Admin",
|
||||||
"section": "Users, groups, roles, and API keys",
|
"section": "Users, groups, roles, and API keys",
|
||||||
@@ -458,6 +473,10 @@ ACCESS_DOCUMENTATION: tuple[DocumentationTopic, ...] = (
|
|||||||
),
|
),
|
||||||
metadata={
|
metadata={
|
||||||
"kind": "reference",
|
"kind": "reference",
|
||||||
|
"help_contexts": [
|
||||||
|
"access.admin.function-mappings",
|
||||||
|
"access.explanation",
|
||||||
|
],
|
||||||
"route": "/admin",
|
"route": "/admin",
|
||||||
"api_path": "/api/v1/admin/external-function-role-mappings",
|
"api_path": "/api/v1/admin/external-function-role-mappings",
|
||||||
"explanation_api_path": "/api/v1/admin/users/{user_id}/access-explanation",
|
"explanation_api_path": "/api/v1/admin/users/{user_id}/access-explanation",
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from govoplan_access.backend.manifest import manifest
|
||||||
|
|
||||||
|
|
||||||
|
class InterfaceDocumentationContractTests(unittest.TestCase):
|
||||||
|
def test_access_admin_topics_publish_stable_help_contexts(self) -> None:
|
||||||
|
topics = {topic.id: topic for topic in manifest.documentation}
|
||||||
|
|
||||||
|
expected_contexts = {
|
||||||
|
"access.workflow.grant-user-access": {
|
||||||
|
"access.admin.users",
|
||||||
|
"access.admin.groups",
|
||||||
|
"access.admin.roles",
|
||||||
|
"access.admin.blocked",
|
||||||
|
},
|
||||||
|
"access.reference.admin-access-fields": {
|
||||||
|
"access.admin.system-users",
|
||||||
|
"access.admin.system-roles",
|
||||||
|
"access.admin.tenant-users",
|
||||||
|
"access.admin.tenant-groups",
|
||||||
|
"access.admin.tenant-roles",
|
||||||
|
"access.admin.api-keys",
|
||||||
|
"access.credentials",
|
||||||
|
},
|
||||||
|
"access.reference.external-function-role-mappings": {
|
||||||
|
"access.admin.function-mappings",
|
||||||
|
"access.explanation",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for topic_id, expected in expected_contexts.items():
|
||||||
|
self.assertIn(topic_id, topics)
|
||||||
|
metadata = topics[topic_id].metadata or {}
|
||||||
|
self.assertTrue(
|
||||||
|
expected.issubset(set(metadata.get("help_contexts", ()))),
|
||||||
|
topic_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_access_admin_surfaces_remain_declared(self) -> None:
|
||||||
|
surface_ids = {
|
||||||
|
surface.id for surface in manifest.frontend.view_surfaces
|
||||||
|
}
|
||||||
|
self.assertTrue(
|
||||||
|
{
|
||||||
|
"access.admin.system-roles",
|
||||||
|
"access.admin.system-users",
|
||||||
|
"access.admin.system-credentials",
|
||||||
|
"access.admin.tenant-roles",
|
||||||
|
"access.admin.tenant-function-mappings",
|
||||||
|
"access.admin.tenant-groups",
|
||||||
|
"access.admin.tenant-users",
|
||||||
|
"access.admin.tenant-credentials",
|
||||||
|
"access.admin.tenant-api-keys",
|
||||||
|
"access.admin.group-credentials",
|
||||||
|
"access.admin.user-credentials",
|
||||||
|
"access.settings.credentials",
|
||||||
|
}.issubset(surface_ids)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
@@ -3,6 +3,9 @@
|
|||||||
"version": "0.1.11",
|
"version": "0.1.11",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"test:interface-patterns": "node scripts/test-interface-pattern-language.mjs"
|
||||||
|
},
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"module": "src/index.ts",
|
"module": "src/index.ts",
|
||||||
"types": "src/index.ts",
|
"types": "src/index.ts",
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { readFileSync } from "node:fs";
|
||||||
|
import { resolve } from "node:path";
|
||||||
|
|
||||||
|
const root = resolve(import.meta.dirname, "..");
|
||||||
|
const read = (path) => readFileSync(resolve(root, path), "utf8");
|
||||||
|
|
||||||
|
const adminPage = read("src/features/admin/AdminPage.tsx");
|
||||||
|
const users = read("src/features/admin/UsersPanel.tsx");
|
||||||
|
const groups = read("src/features/admin/GroupsPanel.tsx");
|
||||||
|
const roles = read("src/features/admin/RolesPanel.tsx");
|
||||||
|
const systemUsers = read("src/features/admin/SystemUsersPanel.tsx");
|
||||||
|
const systemRoles = read("src/features/admin/SystemRolesPanel.tsx");
|
||||||
|
const apiKeys = read("src/features/admin/ApiKeysPanel.tsx");
|
||||||
|
const mappings = read("src/features/admin/ExternalFunctionRoleMappingsPanel.tsx");
|
||||||
|
const credentials = read("src/features/admin/CredentialEnvelopesPanel.tsx");
|
||||||
|
const files = read("src/features/admin/FileConnectorsPanel.tsx");
|
||||||
|
const mail = read("src/features/admin/MailProfilesPanel.tsx");
|
||||||
|
const moduleSource = read("src/module.ts");
|
||||||
|
const surfaces = [users, groups, roles, systemUsers, systemRoles, apiKeys, mappings];
|
||||||
|
const allAdminSource = [adminPage, credentials, files, mail, ...surfaces].join("\n");
|
||||||
|
|
||||||
|
assert.match(adminPage, /TreeSubnav/);
|
||||||
|
assert.match(adminPage, /ActionBlockerHint/);
|
||||||
|
assert.match(adminPage, /ACCESS_WORKFLOW_DOCUMENTATION/);
|
||||||
|
|
||||||
|
for (const source of surfaces) {
|
||||||
|
assert.match(source, /AdminPageLayout/);
|
||||||
|
assert.match(source, /DataGrid/);
|
||||||
|
assert.match(source, /DocumentationHelpLink/);
|
||||||
|
assert.match(source, /disabledReason/);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const source of [users, groups, roles, systemUsers, systemRoles, apiKeys, mappings]) {
|
||||||
|
assert.match(source, /ConfirmDialog/);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.match(credentials, /CredentialEnvelopeManager/);
|
||||||
|
assert.match(credentials, /DocumentationHelpLink/);
|
||||||
|
assert.match(files, /usePlatformUiCapability<FilesConnectorsUiCapability>/);
|
||||||
|
assert.match(files, /ActionBlockerHint/);
|
||||||
|
assert.match(mail, /usePlatformUiCapability<MailProfilesUiCapability>/);
|
||||||
|
assert.match(mail, /ActionBlockerHint/);
|
||||||
|
assert.match(moduleSource, /translations,/);
|
||||||
|
assert.match(moduleSource, /version: "0\.1\.11"/);
|
||||||
|
|
||||||
|
assert.doesNotMatch(allAdminSource, /window\.(alert|confirm|prompt)\s*\(/);
|
||||||
|
assert.doesNotMatch(allAdminSource, /@govoplan\/(files|mail|organizations|idm)-webui\//);
|
||||||
|
|
||||||
|
console.log("Access interface pattern-language checks passed.");
|
||||||
@@ -11,7 +11,7 @@ import type {
|
|||||||
OrganizationFunctionPickerUiCapability
|
OrganizationFunctionPickerUiCapability
|
||||||
} from "@govoplan/core-webui";
|
} from "@govoplan/core-webui";
|
||||||
import { fetchShellAuth } from "@govoplan/core-webui";
|
import { fetchShellAuth } from "@govoplan/core-webui";
|
||||||
import { Card } from "@govoplan/core-webui";
|
import { ActionBlockerHint } from "@govoplan/core-webui";
|
||||||
import { PageScrollViewport } from "@govoplan/core-webui";
|
import { PageScrollViewport } from "@govoplan/core-webui";
|
||||||
import {
|
import {
|
||||||
TreeSubnav,
|
TreeSubnav,
|
||||||
@@ -28,6 +28,10 @@ import ApiKeysPanel from "./ApiKeysPanel";
|
|||||||
import FileConnectorsPanel from "./FileConnectorsPanel";
|
import FileConnectorsPanel from "./FileConnectorsPanel";
|
||||||
import MailProfilesPanel from "./MailProfilesPanel";
|
import MailProfilesPanel from "./MailProfilesPanel";
|
||||||
import CredentialEnvelopesPanel from "./CredentialEnvelopesPanel";
|
import CredentialEnvelopesPanel from "./CredentialEnvelopesPanel";
|
||||||
|
import {
|
||||||
|
ACCESS_INTERFACE_I18N,
|
||||||
|
ACCESS_WORKFLOW_DOCUMENTATION
|
||||||
|
} from "./interfacePatterns";
|
||||||
import {
|
import {
|
||||||
isViewSurfaceVisible,
|
isViewSurfaceVisible,
|
||||||
useEffectiveView,
|
useEffectiveView,
|
||||||
@@ -45,6 +49,7 @@ type OrderedAdminNavItem = {
|
|||||||
kind?: "management" | "settings";
|
kind?: "management" | "settings";
|
||||||
};
|
};
|
||||||
type AdminNavGroup = {
|
type AdminNavGroup = {
|
||||||
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
items: OrderedAdminNavItem[];
|
items: OrderedAdminNavItem[];
|
||||||
};
|
};
|
||||||
@@ -222,9 +227,22 @@ export default function AdminPage({
|
|||||||
return (
|
return (
|
||||||
<PageScrollViewport>
|
<PageScrollViewport>
|
||||||
<div className="content-pad">
|
<div className="content-pad">
|
||||||
<Card title="i18n:govoplan-access.administration_unavailable.b86d4cb5">
|
<ActionBlockerHint
|
||||||
<p>i18n:govoplan-access.your_current_roles_do_not_grant_administrative_a.6eafee69</p>
|
tone="warning"
|
||||||
</Card>
|
reason={{
|
||||||
|
summary: "i18n:govoplan-access.administration_unavailable.b86d4cb5",
|
||||||
|
details: "i18n:govoplan-access.your_current_roles_do_not_grant_administrative_a.6eafee69",
|
||||||
|
requiredAction: ACCESS_INTERFACE_I18N.requestAdministrationAccess,
|
||||||
|
actor: ACCESS_INTERFACE_I18N.accessAdministrator,
|
||||||
|
target: ACCESS_INTERFACE_I18N.accessAdministration
|
||||||
|
}}
|
||||||
|
labels={{
|
||||||
|
requiredAction: ACCESS_INTERFACE_I18N.requiredAction,
|
||||||
|
actor: ACCESS_INTERFACE_I18N.actor,
|
||||||
|
target: ACCESS_INTERFACE_I18N.destinationLabel
|
||||||
|
}}
|
||||||
|
documentation={ACCESS_WORKFLOW_DOCUMENTATION}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</PageScrollViewport>
|
</PageScrollViewport>
|
||||||
);
|
);
|
||||||
@@ -232,7 +250,8 @@ export default function AdminPage({
|
|||||||
|
|
||||||
const adminNavGroups: AdminNavGroup[] = [
|
const adminNavGroups: AdminNavGroup[] = [
|
||||||
{
|
{
|
||||||
title: "ADMINISTRATION",
|
id: "administration",
|
||||||
|
title: "i18n:govoplan-access.admin.4e7afebc",
|
||||||
items: sortNavItems([
|
items: sortNavItems([
|
||||||
...contributedNavItems(contributedSections, available, "ROOT"),
|
...contributedNavItems(contributedSections, available, "ROOT"),
|
||||||
visibleNavItem(available, "system-modules", "i18n:govoplan-access.modules.04e9462c", 10),
|
visibleNavItem(available, "system-modules", "i18n:govoplan-access.modules.04e9462c", 10),
|
||||||
@@ -243,7 +262,8 @@ export default function AdminPage({
|
|||||||
])
|
])
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "GLOBAL",
|
id: "global",
|
||||||
|
title: "i18n:govoplan-access.global",
|
||||||
items: sortNavItems([
|
items: sortNavItems([
|
||||||
visibleNavItem(available, "system-roles", "i18n:govoplan-access.system_roles.a9461aa6", 20),
|
visibleNavItem(available, "system-roles", "i18n:govoplan-access.system_roles.a9461aa6", 20),
|
||||||
visibleNavItem(available, "system-role-templates", "i18n:govoplan-access.tenant_role_templates", 30),
|
visibleNavItem(available, "system-role-templates", "i18n:govoplan-access.tenant_role_templates", 30),
|
||||||
@@ -257,7 +277,8 @@ export default function AdminPage({
|
|||||||
])
|
])
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "TENANT",
|
id: "tenant",
|
||||||
|
title: "i18n:govoplan-access.tenant.3ca93c78",
|
||||||
items: sortNavItems([
|
items: sortNavItems([
|
||||||
visibleNavItem(available, "tenant-roles", "i18n:govoplan-access.roles.47dcc27d", 10),
|
visibleNavItem(available, "tenant-roles", "i18n:govoplan-access.roles.47dcc27d", 10),
|
||||||
visibleNavItem(available, "tenant-function-role-mappings", "i18n:govoplan-access.function_role_mappings.2b64e9c3", 20),
|
visibleNavItem(available, "tenant-function-role-mappings", "i18n:govoplan-access.function_role_mappings.2b64e9c3", 20),
|
||||||
@@ -271,7 +292,8 @@ export default function AdminPage({
|
|||||||
])
|
])
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "GROUP",
|
id: "group",
|
||||||
|
title: "i18n:govoplan-access.group.171a0606",
|
||||||
items: sortNavItems([
|
items: sortNavItems([
|
||||||
visibleNavItem(available, "tenant-group-file-connectors", "i18n:govoplan-access.file_connections.1e362326", 10),
|
visibleNavItem(available, "tenant-group-file-connectors", "i18n:govoplan-access.file_connections.1e362326", 10),
|
||||||
visibleNavItem(available, "tenant-group-mail-servers", "i18n:govoplan-access.mail_servers.d627326a", 20),
|
visibleNavItem(available, "tenant-group-mail-servers", "i18n:govoplan-access.mail_servers.d627326a", 20),
|
||||||
@@ -280,7 +302,8 @@ export default function AdminPage({
|
|||||||
])
|
])
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "USER",
|
id: "user",
|
||||||
|
title: "i18n:govoplan-access.user.9f8a2389",
|
||||||
items: sortNavItems([
|
items: sortNavItems([
|
||||||
visibleNavItem(available, "tenant-user-file-connectors", "i18n:govoplan-access.file_connections.1e362326", 10),
|
visibleNavItem(available, "tenant-user-file-connectors", "i18n:govoplan-access.file_connections.1e362326", 10),
|
||||||
visibleNavItem(available, "tenant-user-mail-servers", "i18n:govoplan-access.mail_servers.d627326a", 20),
|
visibleNavItem(available, "tenant-user-mail-servers", "i18n:govoplan-access.mail_servers.d627326a", 20),
|
||||||
@@ -398,13 +421,13 @@ function adminNavigationTree(
|
|||||||
byModule.set(moduleId, [...(byModule.get(moduleId) ?? []), item]);
|
byModule.set(moduleId, [...(byModule.get(moduleId) ?? []), item]);
|
||||||
}
|
}
|
||||||
children.push({
|
children.push({
|
||||||
branchId: `admin-${group.title.toLowerCase()}-settings`,
|
branchId: `admin-${group.id}-settings`,
|
||||||
label: "i18n:govoplan-core.settings.c7f73bb5",
|
label: "i18n:govoplan-core.settings.c7f73bb5",
|
||||||
defaultExpanded: false,
|
defaultExpanded: false,
|
||||||
children: [...byModule.entries()]
|
children: [...byModule.entries()]
|
||||||
.sort(([left], [right]) => left.localeCompare(right))
|
.sort(([left], [right]) => left.localeCompare(right))
|
||||||
.map(([moduleId, items]) => ({
|
.map(([moduleId, items]) => ({
|
||||||
branchId: `admin-${group.title.toLowerCase()}-settings-${moduleId}`,
|
branchId: `admin-${group.id}-settings-${moduleId}`,
|
||||||
label: moduleLabel(moduleId),
|
label: moduleLabel(moduleId),
|
||||||
defaultExpanded: items.some(
|
defaultExpanded: items.some(
|
||||||
(item) => item.id === "system-settings"
|
(item) => item.id === "system-settings"
|
||||||
@@ -414,16 +437,20 @@ function adminNavigationTree(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
branchId: `admin-${group.title.toLowerCase()}`,
|
branchId: `admin-${group.id}`,
|
||||||
label: group.title,
|
label: group.title,
|
||||||
defaultExpanded: group.title === "ADMINISTRATION",
|
defaultExpanded: group.id === "administration",
|
||||||
children
|
children
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function moduleLabel(moduleId: string): string {
|
function moduleLabel(moduleId: string): string {
|
||||||
if (moduleId === "platform") return "Platform";
|
if (moduleId === "platform") return "i18n:govoplan-access.platform_administration";
|
||||||
|
if (moduleId === "access") return "i18n:govoplan-access.access.2f81a22d";
|
||||||
|
if (moduleId === "admin") return "i18n:govoplan-access.admin.4e7afebc";
|
||||||
|
if (moduleId === "files") return "i18n:govoplan-access.files.6ce6c512";
|
||||||
|
if (moduleId === "mail") return "i18n:govoplan-access.mail_servers.d627326a";
|
||||||
return moduleId
|
return moduleId
|
||||||
.split(/[-_]/)
|
.split(/[-_]/)
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
|
|||||||
@@ -10,9 +10,10 @@ import { DateTimeField } from "@govoplan/core-webui";
|
|||||||
import { StatusBadge } from "@govoplan/core-webui";
|
import { StatusBadge } from "@govoplan/core-webui";
|
||||||
import { ToggleSwitch } from "@govoplan/core-webui";
|
import { ToggleSwitch } from "@govoplan/core-webui";
|
||||||
import { ConfirmDialog } from "@govoplan/core-webui";
|
import { ConfirmDialog } from "@govoplan/core-webui";
|
||||||
import { AdminIconButton, AdminPageLayout, AdminSelectionList, TableActionGroup, adminErrorMessage, formatAdminDateTime as formatDateTime, useDeltaWatermarks } from "@govoplan/core-webui";
|
import { AdminIconButton, AdminPageLayout, AdminSelectionList, DocumentationHelpLink, TableActionGroup, adminErrorMessage, formatAdminDateTime as formatDateTime, useDeltaWatermarks } from "@govoplan/core-webui";
|
||||||
import { scopeGrants, i18nMessage, useUnsavedDraftGuard } from "@govoplan/core-webui";
|
import { scopeGrants, i18nMessage, useUnsavedDraftGuard } from "@govoplan/core-webui";
|
||||||
import { loadDeltaRows } from "./utils/deltaRows";
|
import { loadDeltaRows } from "./utils/deltaRows";
|
||||||
|
import { ACCESS_INTERFACE_I18N, ACCESS_REFERENCE_DOCUMENTATION, saveDisabledReason } from "./interfacePatterns";
|
||||||
|
|
||||||
function defaultDraft(userId: string) {
|
function defaultDraft(userId: string) {
|
||||||
return { name: "", userId, scopes: ["campaign:read"], expiresAt: "" };
|
return { name: "", userId, scopes: ["campaign:read"], expiresAt: "" };
|
||||||
@@ -102,7 +103,7 @@ export default function ApiKeysPanel({ settings, auth, canCreate, canRevoke }: {
|
|||||||
{ id: "expires", header: "i18n:govoplan-access.expires.a99be3da", width: 180, minWidth: 150, resizable: true, sortable: true, value: (row) => row.expires_at || "", render: (row) => row.expires_at ? formatDateTime(row.expires_at) : "i18n:govoplan-access.no_expiry.39d436aa" },
|
{ id: "expires", header: "i18n:govoplan-access.expires.a99be3da", width: 180, minWidth: 150, resizable: true, sortable: true, value: (row) => row.expires_at || "", render: (row) => row.expires_at ? formatDateTime(row.expires_at) : "i18n:govoplan-access.no_expiry.39d436aa" },
|
||||||
{ id: "actions", header: "i18n:govoplan-access.actions.c3cd636a", width: 108, sticky: "end", resizable: false, align: "right", render: (row) => <TableActionGroup actions={[
|
{ id: "actions", header: "i18n:govoplan-access.actions.c3cd636a", width: 108, sticky: "end", resizable: false, align: "right", render: (row) => <TableActionGroup actions={[
|
||||||
{ id: "inspect", label: i18nMessage("i18n:govoplan-access.inspect_value.9d5d1071", { value0: row.name }), icon: <Search />, onClick: () => setViewing(row) },
|
{ id: "inspect", label: i18nMessage("i18n:govoplan-access.inspect_value.9d5d1071", { value0: row.name }), icon: <Search />, onClick: () => setViewing(row) },
|
||||||
{ id: "revoke", label: i18nMessage("i18n:govoplan-access.revoke_value.34640d6a", { value0: row.name }), icon: <Trash2 />, variant: "danger", applicable: !row.revoked_at, disabled: !canRevoke, onClick: () => setRevoking(row) }
|
{ id: "revoke", label: i18nMessage("i18n:govoplan-access.revoke_value.34640d6a", { value0: row.name }), icon: <Trash2 />, variant: "danger", applicable: !row.revoked_at, disabled: !canRevoke, disabledReason: row.revoked_at ? "i18n:govoplan-access.revoked.85f17ac0" : !canRevoke ? ACCESS_INTERFACE_I18N.writePermissionRequired : undefined, onClick: () => setRevoking(row) }
|
||||||
]} /> }],
|
]} /> }],
|
||||||
[canRevoke]);
|
[canRevoke]);
|
||||||
|
|
||||||
@@ -151,11 +152,11 @@ export default function ApiKeysPanel({ settings, auth, canCreate, canRevoke }: {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<AdminPageLayout title="i18n:govoplan-access.tenant_api_keys.4b1d81f8" description="i18n:govoplan-access.tenant_scoped_automation_credentials_are_capped_.9059dcae" loading={loading} error={error} success={success} actions={<><ToggleSwitch label="i18n:govoplan-access.show_revoked.b4265807" checked={showRevoked} onChange={setShowRevoked} /><Button onClick={() => void load()} disabled={loading}>i18n:govoplan-access.reload.cce71553</Button><AdminIconButton label="i18n:govoplan-access.add_api_key.725d9988" icon={<Plus />} variant="primary" onClick={openCreate} disabled={!canCreate || !users.length} /></>}>
|
<AdminPageLayout title="i18n:govoplan-access.tenant_api_keys.4b1d81f8" description="i18n:govoplan-access.tenant_scoped_automation_credentials_are_capped_.9059dcae" loading={loading} error={error} success={success} actions={<><DocumentationHelpLink reference={ACCESS_REFERENCE_DOCUMENTATION} /><ToggleSwitch label="i18n:govoplan-access.show_revoked.b4265807" checked={showRevoked} onChange={setShowRevoked} /><Button onClick={() => void load()} disabled={loading} disabledReason={loading ? ACCESS_INTERFACE_I18N.loading : undefined}>i18n:govoplan-access.reload.cce71553</Button><AdminIconButton label="i18n:govoplan-access.add_api_key.725d9988" icon={<Plus />} variant="primary" onClick={openCreate} disabled={!canCreate || !users.length} disabledReason={!canCreate ? ACCESS_INTERFACE_I18N.createPermissionRequired : !users.length ? ACCESS_INTERFACE_I18N.selectUserAndScopes : undefined} /></>}>
|
||||||
<div className="admin-table-surface"><DataGrid id="admin-api-keys-v3" rows={keys} columns={columns} initialFit="container" getRowKey={(row) => row.id} emptyText="i18n:govoplan-access.no_api_keys_found.1f377128" /></div>
|
<div className="admin-table-surface"><DataGrid id="admin-api-keys-v3" rows={keys} columns={columns} initialFit="container" getRowKey={(row) => row.id} emptyText="i18n:govoplan-access.no_api_keys_found.1f377128" /></div>
|
||||||
</AdminPageLayout>
|
</AdminPageLayout>
|
||||||
|
|
||||||
<Dialog open={creating} title="i18n:govoplan-access.create_api_key.d7b30388" onClose={() => !busy && setCreating(false)} className="admin-dialog admin-dialog-wide" footer={<><Button onClick={() => setCreating(false)} disabled={busy}>i18n:govoplan-access.cancel.77dfd213</Button><Button variant="primary" onClick={() => void save()} disabled={busy || !draft.name.trim() || !draft.userId || !draft.scopes.length}>{busy ? "i18n:govoplan-access.creating.94d7d8ee" : "i18n:govoplan-access.create_key.e028cb09"}</Button></>}>
|
<Dialog open={creating} title="i18n:govoplan-access.create_api_key.d7b30388" onClose={() => !busy && setCreating(false)} className="admin-dialog admin-dialog-wide" footer={<><Button onClick={() => setCreating(false)} disabled={busy} disabledReason={busy ? ACCESS_INTERFACE_I18N.operationInProgress : undefined}>i18n:govoplan-access.cancel.77dfd213</Button><Button variant="primary" onClick={() => void save()} disabledReason={saveDisabledReason({ busy, permitted: canCreate, complete: Boolean(draft.name.trim() && draft.userId && draft.scopes.length) })}>{busy ? "i18n:govoplan-access.creating.94d7d8ee" : "i18n:govoplan-access.create_key.e028cb09"}</Button></>}>
|
||||||
<div className="admin-form-grid two-columns">
|
<div className="admin-form-grid two-columns">
|
||||||
<FormField label="i18n:govoplan-access.name.709a2322"><input value={draft.name} onChange={(event) => setDraft({ ...draft, name: event.target.value })} /></FormField>
|
<FormField label="i18n:govoplan-access.name.709a2322"><input value={draft.name} onChange={(event) => setDraft({ ...draft, name: event.target.value })} /></FormField>
|
||||||
<FormField label="i18n:govoplan-access.owner.89ff3122"><select value={draft.userId} onChange={(event) => {const userId = event.target.value;const user = users.find((item) => item.id === userId);const allowed = new Set(permissions.filter((permission) => user?.effective_scopes.some((scope) => scopeGrants(scope, permission.scope))).map((permission) => permission.scope));setDraft({ ...draft, userId, scopes: draft.scopes.filter((scope) => allowed.has(scope)) });}}><option value="">i18n:govoplan-access.select_user.b8a1d9de</option>{users.map((user) => <option key={user.id} value={user.id}>{user.display_name || user.email} — {user.email}</option>)}</select></FormField>
|
<FormField label="i18n:govoplan-access.owner.89ff3122"><select value={draft.userId} onChange={(event) => {const userId = event.target.value;const user = users.find((item) => item.id === userId);const allowed = new Set(permissions.filter((permission) => user?.effective_scopes.some((scope) => scopeGrants(scope, permission.scope))).map((permission) => permission.scope));setDraft({ ...draft, userId, scopes: draft.scopes.filter((scope) => allowed.has(scope)) });}}><option value="">i18n:govoplan-access.select_user.b8a1d9de</option>{users.map((user) => <option key={user.id} value={user.id}>{user.display_name || user.email} — {user.email}</option>)}</select></FormField>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from "react";
|
|||||||
import {
|
import {
|
||||||
AdminPageLayout,
|
AdminPageLayout,
|
||||||
CredentialEnvelopeManager,
|
CredentialEnvelopeManager,
|
||||||
|
DocumentationHelpLink,
|
||||||
adminErrorMessage,
|
adminErrorMessage,
|
||||||
useDeltaWatermarks,
|
useDeltaWatermarks,
|
||||||
type ApiSettings,
|
type ApiSettings,
|
||||||
@@ -15,6 +16,10 @@ import {
|
|||||||
type UserAdminItem
|
type UserAdminItem
|
||||||
} from "../../api/admin";
|
} from "../../api/admin";
|
||||||
import { loadDeltaRows } from "./utils/deltaRows";
|
import { loadDeltaRows } from "./utils/deltaRows";
|
||||||
|
import {
|
||||||
|
ACCESS_INTERFACE_I18N,
|
||||||
|
CREDENTIAL_DOCUMENTATION
|
||||||
|
} from "./interfacePatterns";
|
||||||
|
|
||||||
type ScopeType = Extract<MailProfileScope, "system" | "tenant" | "user" | "group">;
|
type ScopeType = Extract<MailProfileScope, "system" | "tenant" | "user" | "group">;
|
||||||
|
|
||||||
@@ -116,13 +121,14 @@ export default function CredentialEnvelopesPanel({
|
|||||||
description={scopeDescription(scopeType)}
|
description={scopeDescription(scopeType)}
|
||||||
loading={loadingTargets}
|
loading={loadingTargets}
|
||||||
error={targetError}
|
error={targetError}
|
||||||
|
actions={<DocumentationHelpLink reference={CREDENTIAL_DOCUMENTATION} />}
|
||||||
>
|
>
|
||||||
<CredentialEnvelopeManager
|
<CredentialEnvelopeManager
|
||||||
settings={settings}
|
settings={settings}
|
||||||
scopeType={scopeType}
|
scopeType={scopeType}
|
||||||
targetOptions={targets}
|
targetOptions={targets}
|
||||||
targetLabel={scopeType === "group" ? "Group" : "User"}
|
targetLabel={scopeType === "group" ? "i18n:govoplan-access.group.171a0606" : "i18n:govoplan-access.user.9f8a2389"}
|
||||||
title="Reusable credentials"
|
title={ACCESS_INTERFACE_I18N.reusableCredentials}
|
||||||
canWrite={canWrite}
|
canWrite={canWrite}
|
||||||
/>
|
/>
|
||||||
</AdminPageLayout>
|
</AdminPageLayout>
|
||||||
@@ -130,18 +136,20 @@ export default function CredentialEnvelopesPanel({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function scopeTitle(scopeType: ScopeType): string {
|
function scopeTitle(scopeType: ScopeType): string {
|
||||||
if (scopeType === "system") return "System credentials";
|
if (scopeType === "system") return ACCESS_INTERFACE_I18N.systemCredentials;
|
||||||
if (scopeType === "tenant") return "Tenant credentials";
|
if (scopeType === "tenant") return ACCESS_INTERFACE_I18N.tenantCredentials;
|
||||||
if (scopeType === "group") return "Group credentials";
|
if (scopeType === "group") return ACCESS_INTERFACE_I18N.groupCredentials;
|
||||||
return "User credentials";
|
return ACCESS_INTERFACE_I18N.userCredentials;
|
||||||
}
|
}
|
||||||
|
|
||||||
function scopeDescription(scopeType: ScopeType): string {
|
function scopeDescription(scopeType: ScopeType): string {
|
||||||
if (scopeType === "system") {
|
if (scopeType === "system") {
|
||||||
return "Instance credentials that can be inherited by tenants and limited to selected modules or servers.";
|
return ACCESS_INTERFACE_I18N.systemCredentialDescription;
|
||||||
}
|
}
|
||||||
if (scopeType === "tenant") {
|
if (scopeType === "tenant") {
|
||||||
return "Tenant credentials shared by Mail, Files, Calendar, Addresses, and other permitted modules.";
|
return ACCESS_INTERFACE_I18N.tenantCredentialDescription;
|
||||||
}
|
}
|
||||||
return `Reusable credentials owned by the selected ${scopeType}.`;
|
return scopeType === "group"
|
||||||
|
? ACCESS_INTERFACE_I18N.groupCredentialDescription
|
||||||
|
: ACCESS_INTERFACE_I18N.userCredentialDescription;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,10 @@ import { DataGrid, type DataGridColumn } from "@govoplan/core-webui";
|
|||||||
import { Dialog } from "@govoplan/core-webui";
|
import { Dialog } from "@govoplan/core-webui";
|
||||||
import { FormField } from "@govoplan/core-webui";
|
import { FormField } from "@govoplan/core-webui";
|
||||||
import { ConfirmDialog } from "@govoplan/core-webui";
|
import { ConfirmDialog } from "@govoplan/core-webui";
|
||||||
import { AdminIconButton, AdminPageLayout, TableActionGroup, adminErrorMessage, formatAdminDateTime as formatDateTime } from "@govoplan/core-webui";
|
import { AdminIconButton, AdminPageLayout, DocumentationHelpLink, TableActionGroup, adminErrorMessage, formatAdminDateTime as formatDateTime } from "@govoplan/core-webui";
|
||||||
import { i18nMessage, useDeltaWatermarks, useUnsavedDraftGuard } from "@govoplan/core-webui";
|
import { i18nMessage, useDeltaWatermarks, useUnsavedDraftGuard } from "@govoplan/core-webui";
|
||||||
import { loadDeltaRows } from "./utils/deltaRows";
|
import { loadDeltaRows } from "./utils/deltaRows";
|
||||||
|
import { ACCESS_INTERFACE_I18N, FUNCTION_MAPPING_DOCUMENTATION, saveDisabledReason } from "./interfacePatterns";
|
||||||
|
|
||||||
const emptyDraft = {
|
const emptyDraft = {
|
||||||
sourceModule: "organizations",
|
sourceModule: "organizations",
|
||||||
@@ -258,8 +259,8 @@ export default function ExternalFunctionRoleMappingsPanel({
|
|||||||
resizable: false,
|
resizable: false,
|
||||||
align: "right",
|
align: "right",
|
||||||
render: (row) => <TableActionGroup actions={[
|
render: (row) => <TableActionGroup actions={[
|
||||||
{ id: "edit", label: i18nMessage("i18n:govoplan-access.edit_value.fad75899", { value0: row.function_id }), icon: <Pencil />, disabled: !canWrite, onClick: () => openEdit(row) },
|
{ id: "edit", label: i18nMessage("i18n:govoplan-access.edit_value.fad75899", { value0: row.function_id }), icon: <Pencil />, disabled: !canWrite, disabledReason: !canWrite ? ACCESS_INTERFACE_I18N.writePermissionRequired : undefined, onClick: () => openEdit(row) },
|
||||||
{ id: "delete", label: i18nMessage("i18n:govoplan-access.delete_value.4d18989e", { value0: row.function_id }), icon: <Trash2 />, variant: "danger", disabled: !canWrite, onClick: () => setDeleting(row) }
|
{ id: "delete", label: i18nMessage("i18n:govoplan-access.delete_value.4d18989e", { value0: row.function_id }), icon: <Trash2 />, variant: "danger", disabled: !canWrite, disabledReason: !canWrite ? ACCESS_INTERFACE_I18N.writePermissionRequired : undefined, onClick: () => setDeleting(row) }
|
||||||
]} />
|
]} />
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -276,8 +277,9 @@ export default function ExternalFunctionRoleMappingsPanel({
|
|||||||
success={success}
|
success={success}
|
||||||
actions={
|
actions={
|
||||||
<>
|
<>
|
||||||
<Button onClick={() => void load()} disabled={loading}>i18n:govoplan-access.reload.cce71553</Button>
|
<DocumentationHelpLink reference={FUNCTION_MAPPING_DOCUMENTATION} />
|
||||||
<AdminIconButton label="i18n:govoplan-access.add_function_role_mapping.1bc376ac" icon={<Plus />} variant="primary" onClick={openCreate} disabled={!canWrite || !assignableRoles.length} />
|
<Button onClick={() => void load()} disabled={loading} disabledReason={loading ? ACCESS_INTERFACE_I18N.loading : undefined}>i18n:govoplan-access.reload.cce71553</Button>
|
||||||
|
<AdminIconButton label="i18n:govoplan-access.add_function_role_mapping.1bc376ac" icon={<Plus />} variant="primary" onClick={openCreate} disabled={!canWrite || !assignableRoles.length} disabledReason={!canWrite ? ACCESS_INTERFACE_I18N.createPermissionRequired : !assignableRoles.length ? ACCESS_INTERFACE_I18N.selectAssignableRole : undefined} />
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -300,8 +302,8 @@ export default function ExternalFunctionRoleMappingsPanel({
|
|||||||
className="admin-dialog"
|
className="admin-dialog"
|
||||||
footer={
|
footer={
|
||||||
<>
|
<>
|
||||||
<Button onClick={closeEditor} disabled={busy}>i18n:govoplan-access.cancel.77dfd213</Button>
|
<Button onClick={closeEditor} disabled={busy} disabledReason={busy ? ACCESS_INTERFACE_I18N.operationInProgress : undefined}>i18n:govoplan-access.cancel.77dfd213</Button>
|
||||||
<Button variant="primary" onClick={() => void save()} disabled={!canWrite || busy || !draft.functionId.trim() || !draft.roleId}>
|
<Button variant="primary" onClick={() => void save()} disabledReason={saveDisabledReason({ busy, permitted: canWrite, complete: Boolean(draft.functionId.trim() && draft.roleId) })}>
|
||||||
{busy ? "i18n:govoplan-access.saving.56a2285c" : "i18n:govoplan-access.save_mapping.a4ac90e9"}
|
{busy ? "i18n:govoplan-access.saving.56a2285c" : "i18n:govoplan-access.save_mapping.a4ac90e9"}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import type { ApiSettings, FileConnectorScope, FileConnectorTargetOption, FilesConnectorsUiCapability } from "@govoplan/core-webui";
|
import type { ApiSettings, FileConnectorScope, FileConnectorTargetOption, FilesConnectorsUiCapability } from "@govoplan/core-webui";
|
||||||
import { AdminPageLayout, Card, adminErrorMessage, useDeltaWatermarks, usePlatformUiCapability } from "@govoplan/core-webui";
|
import { ActionBlockerHint, AdminPageLayout, adminErrorMessage, useDeltaWatermarks, usePlatformUiCapability } from "@govoplan/core-webui";
|
||||||
import { fetchGroupsDelta, fetchUsersDelta, type GroupSummary, type UserAdminItem } from "../../api/admin";
|
import { fetchGroupsDelta, fetchUsersDelta, type GroupSummary, type UserAdminItem } from "../../api/admin";
|
||||||
import { loadDeltaRows } from "./utils/deltaRows";
|
import { loadDeltaRows } from "./utils/deltaRows";
|
||||||
|
import {
|
||||||
|
ACCESS_INTERFACE_I18N,
|
||||||
|
FILE_CONNECTOR_DOCUMENTATION
|
||||||
|
} from "./interfacePatterns";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
settings: ApiSettings;
|
settings: ApiSettings;
|
||||||
@@ -82,9 +86,21 @@ export default function FileConnectorsPanel({ settings, scopeType, canWrite }: P
|
|||||||
if (!FileConnectorScopeManager) {
|
if (!FileConnectorScopeManager) {
|
||||||
return (
|
return (
|
||||||
<AdminPageLayout title={labels.title} description={labels.description}>
|
<AdminPageLayout title={labels.title} description={labels.description}>
|
||||||
<Card title="i18n:govoplan-access.files_module_unavailable.0ee90db1">
|
<ActionBlockerHint
|
||||||
<p className="muted">i18n:govoplan-access.install_and_enable_the_files_module_to_manage_fi.f842c153</p>
|
reason={{
|
||||||
</Card>
|
summary: "i18n:govoplan-access.files_module_unavailable.0ee90db1",
|
||||||
|
details: "i18n:govoplan-access.install_and_enable_the_files_module_to_manage_fi.f842c153",
|
||||||
|
requiredAction: ACCESS_INTERFACE_I18N.installFiles,
|
||||||
|
actor: ACCESS_INTERFACE_I18N.systemModuleAdministrator,
|
||||||
|
target: ACCESS_INTERFACE_I18N.moduleManagement
|
||||||
|
}}
|
||||||
|
labels={{
|
||||||
|
requiredAction: ACCESS_INTERFACE_I18N.requiredAction,
|
||||||
|
actor: ACCESS_INTERFACE_I18N.actor,
|
||||||
|
target: ACCESS_INTERFACE_I18N.destinationLabel
|
||||||
|
}}
|
||||||
|
documentation={FILE_CONNECTOR_DOCUMENTATION}
|
||||||
|
/>
|
||||||
</AdminPageLayout>);
|
</AdminPageLayout>);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,13 @@ import { Dialog } from "@govoplan/core-webui";
|
|||||||
import { FormField } from "@govoplan/core-webui";
|
import { FormField } from "@govoplan/core-webui";
|
||||||
import { StatusBadge } from "@govoplan/core-webui";
|
import { StatusBadge } from "@govoplan/core-webui";
|
||||||
import { ConfirmDialog } from "@govoplan/core-webui";
|
import { ConfirmDialog } from "@govoplan/core-webui";
|
||||||
import { AdminIconButton, AdminPageLayout, AdminSelectionList, TableActionGroup, adminErrorMessage, formatAdminDateTime as formatDateTime, joinLabels, i18nMessage, useDeltaWatermarks, useUnsavedDraftGuard } from "@govoplan/core-webui";
|
import { AdminIconButton, AdminPageLayout, AdminSelectionList, DocumentationHelpLink, TableActionGroup, adminErrorMessage, formatAdminDateTime as formatDateTime, joinLabels, i18nMessage, useDeltaWatermarks, useUnsavedDraftGuard } from "@govoplan/core-webui";
|
||||||
import { loadDeltaRows } from "./utils/deltaRows";
|
import { loadDeltaRows } from "./utils/deltaRows";
|
||||||
|
import {
|
||||||
|
ACCESS_INTERFACE_I18N,
|
||||||
|
ACCESS_WORKFLOW_DOCUMENTATION,
|
||||||
|
saveDisabledReason
|
||||||
|
} from "./interfacePatterns";
|
||||||
|
|
||||||
const emptyDraft = { slug: "", name: "", description: "", isActive: true, memberIds: [] as string[], roleIds: [] as string[] };
|
const emptyDraft = { slug: "", name: "", description: "", isActive: true, memberIds: [] as string[], roleIds: [] as string[] };
|
||||||
|
|
||||||
@@ -133,18 +138,18 @@ export default function GroupsPanel({ settings, auth, canDefine, canManageMember
|
|||||||
{ id: "status", header: "i18n:govoplan-access.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-access.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-access.actions.c3cd636a", width: 150, sticky: "end", resizable: false, align: "right", render: (row) => <TableActionGroup actions={[
|
{ id: "actions", header: "i18n:govoplan-access.actions.c3cd636a", width: 150, sticky: "end", resizable: false, align: "right", render: (row) => <TableActionGroup actions={[
|
||||||
{ id: "inspect", label: i18nMessage("i18n:govoplan-access.inspect_value.9d5d1071", { value0: row.name }), icon: <Search />, onClick: () => setViewing(row) },
|
{ id: "inspect", label: i18nMessage("i18n:govoplan-access.inspect_value.9d5d1071", { value0: row.name }), icon: <Search />, onClick: () => setViewing(row) },
|
||||||
{ id: "edit", label: i18nMessage("i18n:govoplan-access.edit_value.fad75899", { value0: row.name }), icon: <Pencil />, disabled: !(canDefine || canManageMembers || canAssignRoles), onClick: () => openEdit(row) },
|
{ id: "edit", label: i18nMessage("i18n:govoplan-access.edit_value.fad75899", { value0: row.name }), icon: <Pencil />, disabled: !(canDefine || canManageMembers || canAssignRoles), disabledReason: !(canDefine || canManageMembers || canAssignRoles) ? ACCESS_INTERFACE_I18N.updatePermissionRequired : undefined, onClick: () => openEdit(row) },
|
||||||
{ id: "deactivate", label: i18nMessage("i18n:govoplan-access.deactivate_value.a276a667", { value0: row.name }), icon: <Trash2 />, variant: "danger", applicable: row.is_active, disabled: !canDefine || Boolean(row.system_required), onClick: () => setDeactivating(row) }
|
{ id: "deactivate", label: i18nMessage("i18n:govoplan-access.deactivate_value.a276a667", { value0: row.name }), icon: <Trash2 />, variant: "danger", applicable: row.is_active, disabled: !canDefine || Boolean(row.system_required), disabledReason: !row.is_active ? "i18n:govoplan-access.inactive.09af574c" : !canDefine ? ACCESS_INTERFACE_I18N.writePermissionRequired : row.system_required ? ACCESS_INTERFACE_I18N.systemManagedObject : undefined, onClick: () => setDeactivating(row) }
|
||||||
]} /> }],
|
]} /> }],
|
||||||
[canAssignRoles, canDefine, canManageMembers]);
|
[canAssignRoles, canDefine, canManageMembers]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<AdminPageLayout title="i18n:govoplan-access.tenant_groups.47e6cc05" description="i18n:govoplan-access.groups_provide_shared_file_spaces_and_inherited_.27f05309" loading={loading} error={error} success={success} actions={<><Button onClick={() => void load()} disabled={loading}>i18n:govoplan-access.reload.cce71553</Button><AdminIconButton label="i18n:govoplan-access.add_group.2fca464f" icon={<Plus />} variant="primary" onClick={openCreate} disabled={!canDefine} /></>}>
|
<AdminPageLayout title="i18n:govoplan-access.tenant_groups.47e6cc05" description="i18n:govoplan-access.groups_provide_shared_file_spaces_and_inherited_.27f05309" loading={loading} error={error} success={success} actions={<><DocumentationHelpLink reference={ACCESS_WORKFLOW_DOCUMENTATION} /><Button onClick={() => void load()} disabled={loading} disabledReason={loading ? ACCESS_INTERFACE_I18N.loading : undefined}>i18n:govoplan-access.reload.cce71553</Button><AdminIconButton label="i18n:govoplan-access.add_group.2fca464f" icon={<Plus />} variant="primary" onClick={openCreate} disabled={!canDefine} disabledReason={!canDefine ? ACCESS_INTERFACE_I18N.createPermissionRequired : undefined} /></>}>
|
||||||
<div className="admin-table-surface"><DataGrid id="admin-groups-v3" rows={groups} columns={columns} initialFit="container" getRowKey={(row) => row.id} emptyText="i18n:govoplan-access.no_groups_found.627ca913" /></div>
|
<div className="admin-table-surface"><DataGrid id="admin-groups-v3" rows={groups} columns={columns} initialFit="container" getRowKey={(row) => row.id} emptyText="i18n:govoplan-access.no_groups_found.627ca913" /></div>
|
||||||
</AdminPageLayout>
|
</AdminPageLayout>
|
||||||
|
|
||||||
<Dialog open={editing !== null} title={editing === "new" ? "i18n:govoplan-access.create_group.5a0b1c17" : "i18n:govoplan-access.edit_group.edb57d8e"} onClose={() => !busy && setEditing(null)} className="admin-dialog admin-dialog-wide" footer={<><Button onClick={() => setEditing(null)} disabled={busy}>i18n:govoplan-access.cancel.77dfd213</Button><Button variant="primary" onClick={() => void save()} disabled={busy || !draft.name.trim() || !draft.slug.trim() || (editing === "new" ? !canDefine : !(canDefine || canManageMembers || canAssignRoles))}>{busy ? "i18n:govoplan-access.saving.56a2285c" : "i18n:govoplan-access.save_group.36ca6865"}</Button></>}>
|
<Dialog open={editing !== null} title={editing === "new" ? "i18n:govoplan-access.create_group.5a0b1c17" : "i18n:govoplan-access.edit_group.edb57d8e"} onClose={() => !busy && setEditing(null)} className="admin-dialog admin-dialog-wide" footer={<><Button onClick={() => setEditing(null)} disabled={busy} disabledReason={busy ? ACCESS_INTERFACE_I18N.operationInProgress : undefined}>i18n:govoplan-access.cancel.77dfd213</Button><Button variant="primary" onClick={() => void save()} disabledReason={saveDisabledReason({ busy, permitted: editing === "new" ? canDefine : canDefine || canManageMembers || canAssignRoles, complete: Boolean(draft.name.trim() && draft.slug.trim()) })}>{busy ? "i18n:govoplan-access.saving.56a2285c" : "i18n:govoplan-access.save_group.36ca6865"}</Button></>}>
|
||||||
{editing !== "new" && editing?.system_template_id && <p className="admin-managed-notice">i18n:govoplan-access.this_group_definition_is_managed_by_the_system_n.640b235e</p>}
|
{editing !== "new" && editing?.system_template_id && <p className="admin-managed-notice">i18n:govoplan-access.this_group_definition_is_managed_by_the_system_n.640b235e</p>}
|
||||||
<div className="admin-form-grid two-columns">
|
<div className="admin-form-grid two-columns">
|
||||||
<FormField label="i18n:govoplan-access.name.709a2322"><input value={draft.name} disabled={!canDefine || editing !== "new" && Boolean(editing?.system_template_id)} onChange={(event) => setDraft({ ...draft, name: event.target.value })} /></FormField>
|
<FormField label="i18n:govoplan-access.name.709a2322"><input value={draft.name} disabled={!canDefine || editing !== "new" && Boolean(editing?.system_template_id)} onChange={(event) => setDraft({ ...draft, name: event.target.value })} /></FormField>
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import type { ApiSettings, MailProfileScope, MailProfilesUiCapability, MailProfileTargetOption } from "@govoplan/core-webui";
|
import type { ApiSettings, MailProfileScope, MailProfilesUiCapability, MailProfileTargetOption } from "@govoplan/core-webui";
|
||||||
import { fetchGroupsDelta, fetchUsersDelta, type GroupSummary, type UserAdminItem } from "../../api/admin";
|
import { fetchGroupsDelta, fetchUsersDelta, type GroupSummary, type UserAdminItem } from "../../api/admin";
|
||||||
import { Card } from "@govoplan/core-webui";
|
import { ActionBlockerHint, AdminPageLayout, adminErrorMessage, useDeltaWatermarks } from "@govoplan/core-webui";
|
||||||
import { AdminPageLayout, adminErrorMessage, useDeltaWatermarks } from "@govoplan/core-webui";
|
|
||||||
import { usePlatformUiCapability } from "@govoplan/core-webui";
|
import { usePlatformUiCapability } from "@govoplan/core-webui";
|
||||||
import { loadDeltaRows } from "./utils/deltaRows";
|
import { loadDeltaRows } from "./utils/deltaRows";
|
||||||
|
import {
|
||||||
|
ACCESS_INTERFACE_I18N,
|
||||||
|
MAIL_PROFILE_DOCUMENTATION
|
||||||
|
} from "./interfacePatterns";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
settings: ApiSettings;
|
settings: ApiSettings;
|
||||||
@@ -106,9 +109,21 @@ export default function MailProfilesPanel({ settings, scopeType, canWriteProfile
|
|||||||
if (!MailProfileScopeManager) {
|
if (!MailProfileScopeManager) {
|
||||||
return (
|
return (
|
||||||
<AdminPageLayout title={labels.title} description={labels.description}>
|
<AdminPageLayout title={labels.title} description={labels.description}>
|
||||||
<Card title="i18n:govoplan-access.mail_module_unavailable.b4e95104">
|
<ActionBlockerHint
|
||||||
<p className="muted">i18n:govoplan-access.install_and_enable_the_mail_module_to_manage_mai.a8ad5b3a</p>
|
reason={{
|
||||||
</Card>
|
summary: "i18n:govoplan-access.mail_module_unavailable.b4e95104",
|
||||||
|
details: "i18n:govoplan-access.install_and_enable_the_mail_module_to_manage_mai.a8ad5b3a",
|
||||||
|
requiredAction: ACCESS_INTERFACE_I18N.installMail,
|
||||||
|
actor: ACCESS_INTERFACE_I18N.systemModuleAdministrator,
|
||||||
|
target: ACCESS_INTERFACE_I18N.moduleManagement
|
||||||
|
}}
|
||||||
|
labels={{
|
||||||
|
requiredAction: ACCESS_INTERFACE_I18N.requiredAction,
|
||||||
|
actor: ACCESS_INTERFACE_I18N.actor,
|
||||||
|
target: ACCESS_INTERFACE_I18N.destinationLabel
|
||||||
|
}}
|
||||||
|
documentation={MAIL_PROFILE_DOCUMENTATION}
|
||||||
|
/>
|
||||||
</AdminPageLayout>);
|
</AdminPageLayout>);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ import { Dialog } from "@govoplan/core-webui";
|
|||||||
import { FormField } from "@govoplan/core-webui";
|
import { FormField } from "@govoplan/core-webui";
|
||||||
import { StatusBadge } from "@govoplan/core-webui";
|
import { StatusBadge } from "@govoplan/core-webui";
|
||||||
import { ConfirmDialog } from "@govoplan/core-webui";
|
import { ConfirmDialog } from "@govoplan/core-webui";
|
||||||
import { AdminIconButton, AdminPageLayout, AdminSelectionList, TableActionGroup, adminErrorMessage } from "@govoplan/core-webui";
|
import { AdminIconButton, AdminPageLayout, AdminSelectionList, DocumentationHelpLink, TableActionGroup, adminErrorMessage } from "@govoplan/core-webui";
|
||||||
import { hasTenantWildcard, i18nMessage, useDeltaWatermarks, useUnsavedDraftGuard } from "@govoplan/core-webui";
|
import { hasTenantWildcard, i18nMessage, useDeltaWatermarks, useUnsavedDraftGuard } from "@govoplan/core-webui";
|
||||||
import { loadDeltaRows } from "./utils/deltaRows";
|
import { loadDeltaRows } from "./utils/deltaRows";
|
||||||
|
import { ACCESS_INTERFACE_I18N, ACCESS_WORKFLOW_DOCUMENTATION, saveDisabledReason } from "./interfacePatterns";
|
||||||
|
|
||||||
const emptyDraft = { slug: "", name: "", description: "", permissions: [] as string[], isAssignable: true };
|
const emptyDraft = { slug: "", name: "", description: "", permissions: [] as string[], isAssignable: true };
|
||||||
|
|
||||||
@@ -126,18 +127,18 @@ export default function RolesPanel({ settings, auth, canDefine, onAuthRefresh }:
|
|||||||
{ id: "type", header: "i18n:govoplan-access.type.3deb7456", width: 140, resizable: false, sortable: true, filterable: true, value: (row) => row.is_builtin ? "built-in" : row.system_template_id ? "system-managed" : "custom", render: (row) => <StatusBadge status={row.is_builtin ? "built" : "active"} label={row.is_builtin ? "i18n:govoplan-access.built_in.20f409cc" : row.system_template_id ? "i18n:govoplan-access.system.bc0792d8" : "i18n:govoplan-access.custom.081ae3fd"} /> },
|
{ id: "type", header: "i18n:govoplan-access.type.3deb7456", width: 140, resizable: false, sortable: true, filterable: true, value: (row) => row.is_builtin ? "built-in" : row.system_template_id ? "system-managed" : "custom", render: (row) => <StatusBadge status={row.is_builtin ? "built" : "active"} label={row.is_builtin ? "i18n:govoplan-access.built_in.20f409cc" : row.system_template_id ? "i18n:govoplan-access.system.bc0792d8" : "i18n:govoplan-access.custom.081ae3fd"} /> },
|
||||||
{ id: "actions", header: "i18n:govoplan-access.actions.c3cd636a", width: 150, sticky: "end", resizable: false, align: "right", render: (row) => <TableActionGroup actions={[
|
{ id: "actions", header: "i18n:govoplan-access.actions.c3cd636a", width: 150, sticky: "end", resizable: false, align: "right", render: (row) => <TableActionGroup actions={[
|
||||||
{ id: "inspect", label: i18nMessage("i18n:govoplan-access.inspect_value.9d5d1071", { value0: row.name }), icon: <Search />, onClick: () => setViewing(row) },
|
{ id: "inspect", label: i18nMessage("i18n:govoplan-access.inspect_value.9d5d1071", { value0: row.name }), icon: <Search />, onClick: () => setViewing(row) },
|
||||||
{ id: "edit", label: i18nMessage("i18n:govoplan-access.edit_value.fad75899", { value0: row.name }), icon: <Pencil />, applicable: !row.is_builtin && !row.system_template_id, disabled: !canDefine, onClick: () => openEdit(row) },
|
{ id: "edit", label: i18nMessage("i18n:govoplan-access.edit_value.fad75899", { value0: row.name }), icon: <Pencil />, applicable: !row.is_builtin && !row.system_template_id, disabled: !canDefine, disabledReason: row.is_builtin || row.system_template_id ? ACCESS_INTERFACE_I18N.systemManagedObject : !canDefine ? ACCESS_INTERFACE_I18N.writePermissionRequired : undefined, onClick: () => openEdit(row) },
|
||||||
{ id: "delete", label: i18nMessage("i18n:govoplan-access.delete_value.4d18989e", { value0: row.name }), icon: <Trash2 />, variant: "danger", applicable: !row.is_builtin && !row.system_template_id, disabled: !canDefine || row.user_assignments + row.group_assignments > 0, onClick: () => setDeleting(row) }
|
{ id: "delete", label: i18nMessage("i18n:govoplan-access.delete_value.4d18989e", { value0: row.name }), icon: <Trash2 />, variant: "danger", applicable: !row.is_builtin && !row.system_template_id, disabled: !canDefine || row.user_assignments + row.group_assignments > 0, disabledReason: row.is_builtin || row.system_template_id ? ACCESS_INTERFACE_I18N.systemManagedObject : !canDefine ? ACCESS_INTERFACE_I18N.writePermissionRequired : row.user_assignments + row.group_assignments > 0 ? ACCESS_INTERFACE_I18N.assignedObjectCannotBeDeleted : undefined, onClick: () => setDeleting(row) }
|
||||||
]} /> }],
|
]} /> }],
|
||||||
[canDefine, permissions]);
|
[canDefine, permissions]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<AdminPageLayout title="i18n:govoplan-access.tenant_roles.51aca82d" description="i18n:govoplan-access.roles_are_explicit_tenant_permission_bundles_bui.ce55fcaa" loading={loading} error={error} success={success} actions={<><Button onClick={() => void load()} disabled={loading}>i18n:govoplan-access.reload.cce71553</Button><AdminIconButton label="i18n:govoplan-access.add_role.d8d5d55c" icon={<Plus />} variant="primary" onClick={openCreate} disabled={!canDefine} /></>}>
|
<AdminPageLayout title="i18n:govoplan-access.tenant_roles.51aca82d" description="i18n:govoplan-access.roles_are_explicit_tenant_permission_bundles_bui.ce55fcaa" loading={loading} error={error} success={success} actions={<><DocumentationHelpLink reference={ACCESS_WORKFLOW_DOCUMENTATION} /><Button onClick={() => void load()} disabled={loading} disabledReason={loading ? ACCESS_INTERFACE_I18N.loading : undefined}>i18n:govoplan-access.reload.cce71553</Button><AdminIconButton label="i18n:govoplan-access.add_role.d8d5d55c" icon={<Plus />} variant="primary" onClick={openCreate} disabled={!canDefine} disabledReason={!canDefine ? ACCESS_INTERFACE_I18N.createPermissionRequired : undefined} /></>}>
|
||||||
<div className="admin-table-surface"><DataGrid id="admin-roles-v3" rows={roles} columns={columns} initialFit="container" getRowKey={(row) => row.id} emptyText="i18n:govoplan-access.no_roles_found.70f7c0c9" /></div>
|
<div className="admin-table-surface"><DataGrid id="admin-roles-v3" rows={roles} columns={columns} initialFit="container" getRowKey={(row) => row.id} emptyText="i18n:govoplan-access.no_roles_found.70f7c0c9" /></div>
|
||||||
</AdminPageLayout>
|
</AdminPageLayout>
|
||||||
|
|
||||||
<Dialog open={editing !== null} title={editing === "new" ? "i18n:govoplan-access.create_role.db859bad" : "i18n:govoplan-access.edit_role.61dd63e9"} onClose={() => !busy && setEditing(null)} className="admin-dialog admin-dialog-wide" footer={<><Button onClick={() => setEditing(null)} disabled={busy}>i18n:govoplan-access.cancel.77dfd213</Button><Button variant="primary" onClick={() => void save()} disabled={!canDefine || busy || !draft.name.trim() || !draft.slug.trim()}>{busy ? "i18n:govoplan-access.saving.56a2285c" : "i18n:govoplan-access.save_role.16fe10d1"}</Button></>}>
|
<Dialog open={editing !== null} title={editing === "new" ? "i18n:govoplan-access.create_role.db859bad" : "i18n:govoplan-access.edit_role.61dd63e9"} onClose={() => !busy && setEditing(null)} className="admin-dialog admin-dialog-wide" footer={<><Button onClick={() => setEditing(null)} disabled={busy} disabledReason={busy ? ACCESS_INTERFACE_I18N.operationInProgress : undefined}>i18n:govoplan-access.cancel.77dfd213</Button><Button variant="primary" onClick={() => void save()} disabledReason={saveDisabledReason({ busy, permitted: canDefine, complete: Boolean(draft.name.trim() && draft.slug.trim()) })}>{busy ? "i18n:govoplan-access.saving.56a2285c" : "i18n:govoplan-access.save_role.16fe10d1"}</Button></>}>
|
||||||
<div className="admin-form-grid two-columns">
|
<div className="admin-form-grid two-columns">
|
||||||
<FormField label="i18n:govoplan-access.name.709a2322"><input value={draft.name} onChange={(event) => setDraft({ ...draft, name: event.target.value })} /></FormField>
|
<FormField label="i18n:govoplan-access.name.709a2322"><input value={draft.name} onChange={(event) => setDraft({ ...draft, name: event.target.value })} /></FormField>
|
||||||
<FormField label="i18n:govoplan-access.slug.094da9b9"><input value={draft.slug} disabled={editing !== "new"} onChange={(event) => setDraft({ ...draft, slug: event.target.value })} /></FormField>
|
<FormField label="i18n:govoplan-access.slug.094da9b9"><input value={draft.slug} disabled={editing !== "new"} onChange={(event) => setDraft({ ...draft, slug: event.target.value })} /></FormField>
|
||||||
|
|||||||
@@ -16,8 +16,9 @@ import { DataGrid, type DataGridColumn } from "@govoplan/core-webui";
|
|||||||
import { Dialog } from "@govoplan/core-webui";
|
import { Dialog } from "@govoplan/core-webui";
|
||||||
import { FormField } from "@govoplan/core-webui";
|
import { FormField } from "@govoplan/core-webui";
|
||||||
import { StatusBadge } from "@govoplan/core-webui";
|
import { StatusBadge } from "@govoplan/core-webui";
|
||||||
import { AdminIconButton, AdminPageLayout, AdminSelectionList, TableActionGroup, adminErrorMessage, joinLabels, i18nMessage, useDeltaWatermarks, useUnsavedDraftGuard } from "@govoplan/core-webui";
|
import { AdminIconButton, AdminPageLayout, AdminSelectionList, DocumentationHelpLink, TableActionGroup, adminErrorMessage, joinLabels, i18nMessage, useDeltaWatermarks, useUnsavedDraftGuard } from "@govoplan/core-webui";
|
||||||
import { loadDeltaRows } from "./utils/deltaRows";
|
import { loadDeltaRows } from "./utils/deltaRows";
|
||||||
|
import { ACCESS_INTERFACE_I18N, ACCESS_REFERENCE_DOCUMENTATION, saveDisabledReason } from "./interfacePatterns";
|
||||||
|
|
||||||
const emptyDraft = {
|
const emptyDraft = {
|
||||||
slug: "",
|
slug: "",
|
||||||
@@ -225,8 +226,8 @@ export default function SystemRolesPanel({
|
|||||||
const protectedOwner = row.slug === "system_owner";
|
const protectedOwner = row.slug === "system_owner";
|
||||||
return <TableActionGroup actions={[
|
return <TableActionGroup actions={[
|
||||||
{ id: "inspect", label: i18nMessage("i18n:govoplan-access.inspect_value.9d5d1071", { value0: row.name }), icon: <Search />, onClick: () => setViewing(row) },
|
{ id: "inspect", label: i18nMessage("i18n:govoplan-access.inspect_value.9d5d1071", { value0: row.name }), icon: <Search />, onClick: () => setViewing(row) },
|
||||||
{ id: "edit", label: i18nMessage("i18n:govoplan-access.edit_value.fad75899", { value0: row.name }), icon: <Pencil />, applicable: !protectedOwner, disabled: !canWrite, onClick: () => openEdit(row) },
|
{ id: "edit", label: i18nMessage("i18n:govoplan-access.edit_value.fad75899", { value0: row.name }), icon: <Pencil />, applicable: !protectedOwner, disabled: !canWrite, disabledReason: protectedOwner ? ACCESS_INTERFACE_I18N.systemManagedObject : !canWrite ? ACCESS_INTERFACE_I18N.writePermissionRequired : undefined, onClick: () => openEdit(row) },
|
||||||
{ id: "delete", label: i18nMessage("i18n:govoplan-access.delete_value.4d18989e", { value0: row.name }), icon: <Trash2 />, variant: "danger", applicable: !protectedOwner, disabled: !canWrite || row.user_assignments > 0, onClick: () => setDeleting(row) }
|
{ id: "delete", label: i18nMessage("i18n:govoplan-access.delete_value.4d18989e", { value0: row.name }), icon: <Trash2 />, variant: "danger", applicable: !protectedOwner, disabled: !canWrite || row.user_assignments > 0, disabledReason: protectedOwner ? ACCESS_INTERFACE_I18N.systemManagedObject : !canWrite ? ACCESS_INTERFACE_I18N.writePermissionRequired : row.user_assignments > 0 ? ACCESS_INTERFACE_I18N.assignedObjectCannotBeDeleted : undefined, onClick: () => setDeleting(row) }
|
||||||
]} />;
|
]} />;
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
@@ -240,7 +241,7 @@ export default function SystemRolesPanel({
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
error={error}
|
error={error}
|
||||||
success={success}
|
success={success}
|
||||||
actions={<><Button onClick={() => void load()} disabled={loading}>i18n:govoplan-access.reload.cce71553</Button><AdminIconButton label="i18n:govoplan-access.add_system_role.f9ef262b" icon={<Plus />} variant="primary" onClick={openCreate} disabled={!canWrite} /></>}>
|
actions={<><DocumentationHelpLink reference={ACCESS_REFERENCE_DOCUMENTATION} /><Button onClick={() => void load()} disabled={loading} disabledReason={loading ? ACCESS_INTERFACE_I18N.loading : undefined}>i18n:govoplan-access.reload.cce71553</Button><AdminIconButton label="i18n:govoplan-access.add_system_role.f9ef262b" icon={<Plus />} variant="primary" onClick={openCreate} disabled={!canWrite} disabledReason={!canWrite ? ACCESS_INTERFACE_I18N.createPermissionRequired : undefined} /></>}>
|
||||||
|
|
||||||
<div className="admin-table-surface">
|
<div className="admin-table-surface">
|
||||||
<DataGrid id="admin-system-role-definitions-v4" rows={roles} columns={columns} initialFit="container" getRowKey={(row) => row.id} emptyText="i18n:govoplan-access.no_system_roles_found.051cf727" />
|
<DataGrid id="admin-system-role-definitions-v4" rows={roles} columns={columns} initialFit="container" getRowKey={(row) => row.id} emptyText="i18n:govoplan-access.no_system_roles_found.051cf727" />
|
||||||
@@ -252,7 +253,7 @@ export default function SystemRolesPanel({
|
|||||||
title={editing === "new" ? "i18n:govoplan-access.create_system_role.a1e40b25" : "i18n:govoplan-access.edit_system_role.6ebb7cb0"}
|
title={editing === "new" ? "i18n:govoplan-access.create_system_role.a1e40b25" : "i18n:govoplan-access.edit_system_role.6ebb7cb0"}
|
||||||
onClose={() => !busy && setEditing(null)}
|
onClose={() => !busy && setEditing(null)}
|
||||||
className="admin-dialog admin-dialog-wide"
|
className="admin-dialog admin-dialog-wide"
|
||||||
footer={<><Button onClick={() => setEditing(null)} disabled={busy}>i18n:govoplan-access.cancel.77dfd213</Button><Button variant="primary" onClick={() => void save()} disabled={busy || !draft.name.trim() || !draft.slug.trim()}>{busy ? "i18n:govoplan-access.saving.56a2285c" : "i18n:govoplan-access.save_role.16fe10d1"}</Button></>}>
|
footer={<><Button onClick={() => setEditing(null)} disabled={busy} disabledReason={busy ? ACCESS_INTERFACE_I18N.operationInProgress : undefined}>i18n:govoplan-access.cancel.77dfd213</Button><Button variant="primary" onClick={() => void save()} disabledReason={saveDisabledReason({ busy, permitted: canWrite, complete: Boolean(draft.name.trim() && draft.slug.trim()) })}>{busy ? "i18n:govoplan-access.saving.56a2285c" : "i18n:govoplan-access.save_role.16fe10d1"}</Button></>}>
|
||||||
|
|
||||||
<div className="admin-form-grid two-columns">
|
<div className="admin-form-grid two-columns">
|
||||||
<FormField label="i18n:govoplan-access.name.709a2322"><input value={draft.name} onChange={(event) => setDraft({ ...draft, name: event.target.value })} /></FormField>
|
<FormField label="i18n:govoplan-access.name.709a2322"><input value={draft.name} onChange={(event) => setDraft({ ...draft, name: event.target.value })} /></FormField>
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ import {
|
|||||||
type SystemMembershipDraft,
|
type SystemMembershipDraft,
|
||||||
type TenantAdminItem
|
type TenantAdminItem
|
||||||
} from "../../api/admin";
|
} from "../../api/admin";
|
||||||
import { AdminIconButton, AdminPageLayout, AdminSelectionList, TableActionGroup, adminErrorMessage, formatAdminDateTime as formatDateTime, joinLabels, i18nMessage, mergeDeltaRows, useDeltaWatermarks, useUnsavedDraftGuard } from "@govoplan/core-webui";
|
import { AdminIconButton, AdminPageLayout, AdminSelectionList, DocumentationHelpLink, TableActionGroup, adminErrorMessage, formatAdminDateTime as formatDateTime, joinLabels, i18nMessage, mergeDeltaRows, useDeltaWatermarks, useUnsavedDraftGuard } from "@govoplan/core-webui";
|
||||||
|
import { ACCESS_INTERFACE_I18N, ACCESS_REFERENCE_DOCUMENTATION, saveDisabledReason } from "./interfacePatterns";
|
||||||
|
|
||||||
const emptyDraft = {
|
const emptyDraft = {
|
||||||
email: "",
|
email: "",
|
||||||
@@ -214,8 +215,8 @@ export default function SystemUsersPanel({
|
|||||||
{ id: "last_login", header: "i18n:govoplan-access.last_login.43dab84f", width: 180, minWidth: 150, resizable: true, sortable: true, value: (row) => row.last_login_at || "", render: (row) => formatDateTime(row.last_login_at) },
|
{ id: "last_login", header: "i18n:govoplan-access.last_login.43dab84f", width: 180, minWidth: 150, resizable: true, sortable: true, value: (row) => row.last_login_at || "", render: (row) => formatDateTime(row.last_login_at) },
|
||||||
{ id: "actions", header: "i18n:govoplan-access.actions.c3cd636a", width: 150, sticky: "end", resizable: false, align: "right", render: (row) => <TableActionGroup actions={[
|
{ id: "actions", header: "i18n:govoplan-access.actions.c3cd636a", width: 150, sticky: "end", resizable: false, align: "right", render: (row) => <TableActionGroup actions={[
|
||||||
{ id: "inspect", label: i18nMessage("i18n:govoplan-access.inspect_value.9d5d1071", { value0: row.email }), icon: <Search />, onClick: () => setViewing(row) },
|
{ id: "inspect", label: i18nMessage("i18n:govoplan-access.inspect_value.9d5d1071", { value0: row.email }), icon: <Search />, onClick: () => setViewing(row) },
|
||||||
{ id: "edit", label: i18nMessage("i18n:govoplan-access.edit_value.fad75899", { value0: row.email }), icon: <Pencil />, disabled: !(canUpdate || canSuspend || canAssignRoles || canManageMemberships), onClick: () => openEdit(row) },
|
{ id: "edit", label: i18nMessage("i18n:govoplan-access.edit_value.fad75899", { value0: row.email }), icon: <Pencil />, disabled: !(canUpdate || canSuspend || canAssignRoles || canManageMemberships), disabledReason: !(canUpdate || canSuspend || canAssignRoles || canManageMemberships) ? ACCESS_INTERFACE_I18N.updatePermissionRequired : undefined, onClick: () => openEdit(row) },
|
||||||
{ id: "deactivate", label: i18nMessage("i18n:govoplan-access.deactivate_value.a276a667", { value0: row.email }), icon: <Trash2 />, variant: "danger", applicable: row.is_active, disabled: !canSuspend || row.memberships.some((membership) => membership.is_last_active_owner), onClick: () => setDeactivating(row) }
|
{ id: "deactivate", label: i18nMessage("i18n:govoplan-access.deactivate_value.a276a667", { value0: row.email }), icon: <Trash2 />, variant: "danger", applicable: row.is_active, disabled: !canSuspend || row.memberships.some((membership) => membership.is_last_active_owner), disabledReason: !row.is_active ? "i18n:govoplan-access.inactive.09af574c" : !canSuspend ? ACCESS_INTERFACE_I18N.updatePermissionRequired : row.memberships.some((membership) => membership.is_last_active_owner) ? ACCESS_INTERFACE_I18N.lastOwnerCannotBeDeactivated : undefined, onClick: () => setDeactivating(row) }
|
||||||
]} /> }],
|
]} /> }],
|
||||||
[canAssignRoles, canManageMemberships, canSuspend, canUpdate]);
|
[canAssignRoles, canManageMemberships, canSuspend, canUpdate]);
|
||||||
|
|
||||||
@@ -227,12 +228,12 @@ export default function SystemUsersPanel({
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
error={error}
|
error={error}
|
||||||
success={success}
|
success={success}
|
||||||
actions={<><Button onClick={() => void load()} disabled={loading}>i18n:govoplan-access.reload.cce71553</Button><AdminIconButton label="i18n:govoplan-access.add_global_account.18e4df22" icon={<Plus />} variant="primary" onClick={openCreate} disabled={!canCreate} /></>}>
|
actions={<><DocumentationHelpLink reference={ACCESS_REFERENCE_DOCUMENTATION} /><Button onClick={() => void load()} disabled={loading} disabledReason={loading ? ACCESS_INTERFACE_I18N.loading : undefined}>i18n:govoplan-access.reload.cce71553</Button><AdminIconButton label="i18n:govoplan-access.add_global_account.18e4df22" icon={<Plus />} variant="primary" onClick={openCreate} disabled={!canCreate} disabledReason={!canCreate ? ACCESS_INTERFACE_I18N.createPermissionRequired : undefined} /></>}>
|
||||||
|
|
||||||
<div className="admin-table-surface"><DataGrid id="admin-system-users-v3" rows={accounts} columns={columns} initialFit="container" getRowKey={(row) => row.account_id} emptyText="i18n:govoplan-access.no_global_accounts_found.29d96a9e" /></div>
|
<div className="admin-table-surface"><DataGrid id="admin-system-users-v3" rows={accounts} columns={columns} initialFit="container" getRowKey={(row) => row.account_id} emptyText="i18n:govoplan-access.no_global_accounts_found.29d96a9e" /></div>
|
||||||
</AdminPageLayout>
|
</AdminPageLayout>
|
||||||
|
|
||||||
<Dialog open={editing !== null} title={editing === "new" ? "i18n:govoplan-access.create_global_account.e821f016" : "i18n:govoplan-access.edit_global_account.d13b8485"} onClose={() => !busy && setEditing(null)} className="admin-dialog admin-dialog-wide" footer={<><Button onClick={() => setEditing(null)} disabled={busy}>i18n:govoplan-access.cancel.77dfd213</Button><Button variant="primary" onClick={() => void save()} disabled={busy || !draft.email.trim() || (editing === "new" ? !canCreate : !(canUpdate || canSuspend || canAssignRoles || canManageMemberships))}>{busy ? "i18n:govoplan-access.saving.56a2285c" : "i18n:govoplan-access.save_account.0b761f5c"}</Button></>}>
|
<Dialog open={editing !== null} title={editing === "new" ? "i18n:govoplan-access.create_global_account.e821f016" : "i18n:govoplan-access.edit_global_account.d13b8485"} onClose={() => !busy && setEditing(null)} className="admin-dialog admin-dialog-wide" footer={<><Button onClick={() => setEditing(null)} disabled={busy} disabledReason={busy ? ACCESS_INTERFACE_I18N.operationInProgress : undefined}>i18n:govoplan-access.cancel.77dfd213</Button><Button variant="primary" onClick={() => void save()} disabledReason={saveDisabledReason({ busy, permitted: editing === "new" ? canCreate : canUpdate || canSuspend || canAssignRoles || canManageMemberships, complete: Boolean(draft.email.trim()) })}>{busy ? "i18n:govoplan-access.saving.56a2285c" : "i18n:govoplan-access.save_account.0b761f5c"}</Button></>}>
|
||||||
<div className="admin-form-grid two-columns">
|
<div className="admin-form-grid two-columns">
|
||||||
<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.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>
|
<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>
|
||||||
|
|||||||
@@ -10,9 +10,14 @@ import { PasswordField } from "@govoplan/core-webui";
|
|||||||
import { StatusBadge } from "@govoplan/core-webui";
|
import { StatusBadge } from "@govoplan/core-webui";
|
||||||
import { ToggleSwitch } from "@govoplan/core-webui";
|
import { ToggleSwitch } from "@govoplan/core-webui";
|
||||||
import { ConfirmDialog } from "@govoplan/core-webui";
|
import { ConfirmDialog } from "@govoplan/core-webui";
|
||||||
import { AdminIconButton, AdminPageLayout, AdminSelectionList, TableActionGroup, adminErrorMessage, formatAdminDateTime as formatDateTime, joinLabels } from "@govoplan/core-webui";
|
import { AdminIconButton, AdminPageLayout, AdminSelectionList, DocumentationHelpLink, TableActionGroup, adminErrorMessage, formatAdminDateTime as formatDateTime, joinLabels } from "@govoplan/core-webui";
|
||||||
import { hasTenantWildcard, i18nMessage, useDeltaWatermarks, useUnsavedDraftGuard } from "@govoplan/core-webui";
|
import { hasTenantWildcard, i18nMessage, useDeltaWatermarks, useUnsavedDraftGuard } from "@govoplan/core-webui";
|
||||||
import { loadDeltaRows } from "./utils/deltaRows";
|
import { loadDeltaRows } from "./utils/deltaRows";
|
||||||
|
import {
|
||||||
|
ACCESS_INTERFACE_I18N,
|
||||||
|
ACCESS_WORKFLOW_DOCUMENTATION,
|
||||||
|
saveDisabledReason
|
||||||
|
} from "./interfacePatterns";
|
||||||
|
|
||||||
const emptyDraft = {
|
const emptyDraft = {
|
||||||
email: "",
|
email: "",
|
||||||
@@ -190,18 +195,18 @@ export default function UsersPanel({ settings, auth, canCreate, canUpdate, canSu
|
|||||||
{ id: "actions", header: "i18n:govoplan-access.actions.c3cd636a", width: 190, sticky: "end", resizable: false, align: "right", render: (row) => <TableActionGroup actions={[
|
{ id: "actions", header: "i18n:govoplan-access.actions.c3cd636a", width: 190, sticky: "end", resizable: false, align: "right", render: (row) => <TableActionGroup actions={[
|
||||||
{ id: "inspect", label: i18nMessage("i18n:govoplan-access.inspect_value.9d5d1071", { value0: row.email }), icon: <Search />, onClick: () => setViewing(row) },
|
{ id: "inspect", label: i18nMessage("i18n:govoplan-access.inspect_value.9d5d1071", { value0: row.email }), icon: <Search />, onClick: () => setViewing(row) },
|
||||||
{ id: "explain", label: i18nMessage("i18n:govoplan-access.explain_access_for_value.3af96e47", { value0: row.email }), icon: <KeyRound />, onClick: () => void openAccessExplanation(row) },
|
{ id: "explain", label: i18nMessage("i18n:govoplan-access.explain_access_for_value.3af96e47", { value0: row.email }), icon: <KeyRound />, onClick: () => void openAccessExplanation(row) },
|
||||||
{ id: "edit", label: i18nMessage("i18n:govoplan-access.edit_value.fad75899", { value0: row.email }), icon: <Pencil />, disabled: !(canUpdate || canSuspend || canManageGroups || canAssignRoles), onClick: () => openEdit(row) },
|
{ id: "edit", label: i18nMessage("i18n:govoplan-access.edit_value.fad75899", { value0: row.email }), icon: <Pencil />, disabled: !(canUpdate || canSuspend || canManageGroups || canAssignRoles), disabledReason: !(canUpdate || canSuspend || canManageGroups || canAssignRoles) ? ACCESS_INTERFACE_I18N.updatePermissionRequired : undefined, onClick: () => openEdit(row) },
|
||||||
{ id: "deactivate", label: i18nMessage("i18n:govoplan-access.deactivate_value.a276a667", { value0: row.email }), icon: <Trash2 />, variant: "danger", applicable: row.is_active, disabled: !canSuspend || row.is_last_active_owner, onClick: () => setDeactivating(row) }
|
{ id: "deactivate", label: i18nMessage("i18n:govoplan-access.deactivate_value.a276a667", { value0: row.email }), icon: <Trash2 />, variant: "danger", applicable: row.is_active, disabled: !canSuspend || row.is_last_active_owner, disabledReason: !row.is_active ? "i18n:govoplan-access.inactive.09af574c" : !canSuspend ? ACCESS_INTERFACE_I18N.updatePermissionRequired : row.is_last_active_owner ? ACCESS_INTERFACE_I18N.lastOwnerCannotBeDeactivated : undefined, onClick: () => setDeactivating(row) }
|
||||||
]} /> }],
|
]} /> }],
|
||||||
[canAssignRoles, canManageGroups, canSuspend, canUpdate, settings]);
|
[canAssignRoles, canManageGroups, canSuspend, canUpdate, settings]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<AdminPageLayout title="i18n:govoplan-access.tenant_users.cb800b38" description="i18n:govoplan-access.manage_memberships_groups_and_direct_roles_in_th.25af86bb" loading={loading} error={error} success={success} actions={<><Button onClick={() => void load()} disabled={loading}>i18n:govoplan-access.reload.cce71553</Button><AdminIconButton label="i18n:govoplan-access.add_tenant_user.36f37ce7" icon={<Plus />} variant="primary" onClick={openCreate} disabled={!canCreate} /></>}>
|
<AdminPageLayout title="i18n:govoplan-access.tenant_users.cb800b38" description="i18n:govoplan-access.manage_memberships_groups_and_direct_roles_in_th.25af86bb" loading={loading} error={error} success={success} actions={<><DocumentationHelpLink reference={ACCESS_WORKFLOW_DOCUMENTATION} /><Button onClick={() => void load()} disabled={loading} disabledReason={loading ? ACCESS_INTERFACE_I18N.loading : undefined}>i18n:govoplan-access.reload.cce71553</Button><AdminIconButton label="i18n:govoplan-access.add_tenant_user.36f37ce7" icon={<Plus />} variant="primary" onClick={openCreate} disabled={!canCreate} disabledReason={!canCreate ? ACCESS_INTERFACE_I18N.createPermissionRequired : undefined} /></>}>
|
||||||
<div className="admin-table-surface"><DataGrid id="admin-users-v3" rows={users} columns={columns} initialFit="container" getRowKey={(row) => row.id} emptyText="i18n:govoplan-access.no_tenant_users_found.74bb615f" /></div>
|
<div className="admin-table-surface"><DataGrid id="admin-users-v3" rows={users} columns={columns} initialFit="container" getRowKey={(row) => row.id} emptyText="i18n:govoplan-access.no_tenant_users_found.74bb615f" /></div>
|
||||||
</AdminPageLayout>
|
</AdminPageLayout>
|
||||||
|
|
||||||
<Dialog open={editing !== null} title={editing === "new" ? "i18n:govoplan-access.add_tenant_user.36f37ce7" : "i18n:govoplan-access.edit_tenant_user.99121a61"} onClose={() => !busy && setEditing(null)} className="admin-dialog admin-dialog-wide" footer={<><Button onClick={() => setEditing(null)} disabled={busy}>i18n:govoplan-access.cancel.77dfd213</Button><Button variant="primary" onClick={() => void save()} disabled={busy || !draft.email.trim() || (editing === "new" ? !canCreate : !(canUpdate || canSuspend || canManageGroups || canAssignRoles))}>{busy ? "i18n:govoplan-access.saving.56a2285c" : "i18n:govoplan-access.save_user.0d071b89"}</Button></>}>
|
<Dialog open={editing !== null} title={editing === "new" ? "i18n:govoplan-access.add_tenant_user.36f37ce7" : "i18n:govoplan-access.edit_tenant_user.99121a61"} onClose={() => !busy && setEditing(null)} className="admin-dialog admin-dialog-wide" footer={<><Button onClick={() => setEditing(null)} disabled={busy} disabledReason={busy ? ACCESS_INTERFACE_I18N.operationInProgress : undefined}>i18n:govoplan-access.cancel.77dfd213</Button><Button variant="primary" onClick={() => void save()} disabledReason={saveDisabledReason({ busy, permitted: editing === "new" ? canCreate : canUpdate || canSuspend || canManageGroups || canAssignRoles, complete: Boolean(draft.email.trim()) })}>{busy ? "i18n:govoplan-access.saving.56a2285c" : "i18n:govoplan-access.save_user.0d071b89"}</Button></>}>
|
||||||
<div className="admin-form-grid two-columns">
|
<div className="admin-form-grid two-columns">
|
||||||
<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.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>
|
<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>
|
||||||
@@ -235,7 +240,7 @@ export default function UsersPanel({ settings, auth, canCreate, canUpdate, canSu
|
|||||||
</dl>}
|
</dl>}
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<Dialog open={Boolean(explaining)} title="i18n:govoplan-access.access_explanation.75ee7f62" onClose={() => { if (!accessExplanationLoading) { setExplaining(null); setAccessExplanation(null); } }} className="admin-dialog admin-dialog-wide" footer={<Button onClick={() => { setExplaining(null); setAccessExplanation(null); }} disabled={accessExplanationLoading}>i18n:govoplan-access.close.bbfa773e</Button>}>
|
<Dialog open={Boolean(explaining)} title="i18n:govoplan-access.access_explanation.75ee7f62" onClose={() => { if (!accessExplanationLoading) { setExplaining(null); setAccessExplanation(null); } }} className="admin-dialog admin-dialog-wide" footer={<Button onClick={() => { setExplaining(null); setAccessExplanation(null); }} disabled={accessExplanationLoading} disabledReason={accessExplanationLoading ? ACCESS_INTERFACE_I18N.loading : undefined}>i18n:govoplan-access.close.bbfa773e</Button>}>
|
||||||
{accessExplanationLoading && <p className="muted small-note">i18n:govoplan-access.loading_access_explanation.04a7c934</p>}
|
{accessExplanationLoading && <p className="muted small-note">i18n:govoplan-access.loading_access_explanation.04a7c934</p>}
|
||||||
{accessExplanation && <>
|
{accessExplanation && <>
|
||||||
<dl className="admin-details-grid">
|
<dl className="admin-details-grid">
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import type { DocumentationHelpReference } from "@govoplan/core-webui";
|
||||||
|
|
||||||
|
export const ACCESS_WORKFLOW_DOCUMENTATION = {
|
||||||
|
topicId: "access.workflow.grant-user-access",
|
||||||
|
documentationType: "admin"
|
||||||
|
} satisfies DocumentationHelpReference;
|
||||||
|
|
||||||
|
export const ACCESS_REFERENCE_DOCUMENTATION = {
|
||||||
|
topicId: "access.reference.admin-access-fields",
|
||||||
|
documentationType: "admin"
|
||||||
|
} satisfies DocumentationHelpReference;
|
||||||
|
|
||||||
|
export const FUNCTION_MAPPING_DOCUMENTATION = {
|
||||||
|
topicId: "access.reference.external-function-role-mappings",
|
||||||
|
documentationType: "admin"
|
||||||
|
} satisfies DocumentationHelpReference;
|
||||||
|
|
||||||
|
export const CREDENTIAL_DOCUMENTATION = {
|
||||||
|
contextId: "access.credentials",
|
||||||
|
documentationType: "admin"
|
||||||
|
} satisfies DocumentationHelpReference;
|
||||||
|
|
||||||
|
export const FILE_CONNECTOR_DOCUMENTATION = {
|
||||||
|
topicId: "files.governed-connectors-and-provenance",
|
||||||
|
documentationType: "admin"
|
||||||
|
} satisfies DocumentationHelpReference;
|
||||||
|
|
||||||
|
export const MAIL_PROFILE_DOCUMENTATION = {
|
||||||
|
topicId: "mail.profiles-and-policy",
|
||||||
|
documentationType: "admin"
|
||||||
|
} satisfies DocumentationHelpReference;
|
||||||
|
|
||||||
|
export const ACCESS_INTERFACE_I18N = {
|
||||||
|
loading: "i18n:govoplan-access.administration_data_is_loading.4af2c001",
|
||||||
|
createPermissionRequired: "i18n:govoplan-access.create_permission_is_required.4af2c002",
|
||||||
|
updatePermissionRequired: "i18n:govoplan-access.update_or_assignment_permission_is_required.4af2c003",
|
||||||
|
writePermissionRequired: "i18n:govoplan-access.write_permission_is_required.4af2c004",
|
||||||
|
completeRequiredFields: "i18n:govoplan-access.complete_the_required_fields_before_saving.4af2c005",
|
||||||
|
selectUserAndScopes: "i18n:govoplan-access.select_an_account_and_at_least_one_scope.4af2c006",
|
||||||
|
selectAssignableRole: "i18n:govoplan-access.select_an_assignable_role_before_creating_a_mapping.4af2c007",
|
||||||
|
assignedObjectCannotBeDeleted: "i18n:govoplan-access.remove_existing_assignments_before_deleting.4af2c008",
|
||||||
|
lastOwnerCannotBeDeactivated: "i18n:govoplan-access.assign_another_operational_owner_before_deactivating.4af2c009",
|
||||||
|
systemManagedObject: "i18n:govoplan-access.this_definition_is_managed_by_the_system.4af2c010",
|
||||||
|
operationInProgress: "i18n:govoplan-access.an_access_administration_operation_is_in_progress.4af2c011",
|
||||||
|
requiredAction: "i18n:govoplan-access.required_action.4af2c012",
|
||||||
|
actor: "i18n:govoplan-access.who_can_fix_it.4af2c013",
|
||||||
|
destinationLabel: "i18n:govoplan-access.where_to_go.4af2c014",
|
||||||
|
requestAdministrationAccess: "i18n:govoplan-access.request_an_administrative_role_for_the_required_scope.4af2c015",
|
||||||
|
accessAdministrator: "i18n:govoplan-access.a_tenant_owner_or_system_access_administrator.4af2c016",
|
||||||
|
accessAdministration: "i18n:govoplan-access.admin_users_groups_and_roles.4af2c017",
|
||||||
|
installFiles: "i18n:govoplan-access.install_and_enable_the_files_module.4af2c018",
|
||||||
|
installMail: "i18n:govoplan-access.install_and_enable_the_mail_module.4af2c019",
|
||||||
|
systemModuleAdministrator: "i18n:govoplan-access.a_system_module_administrator.4af2c020",
|
||||||
|
moduleManagement: "i18n:govoplan-access.admin_modules.4af2c021",
|
||||||
|
reusableCredentials: "i18n:govoplan-access.reusable_credentials.4af2c022",
|
||||||
|
systemCredentials: "i18n:govoplan-access.system_credentials.4af2c023",
|
||||||
|
tenantCredentials: "i18n:govoplan-access.tenant_credentials.4af2c024",
|
||||||
|
groupCredentials: "i18n:govoplan-access.group_credentials.4af2c025",
|
||||||
|
userCredentials: "i18n:govoplan-access.user_credentials.4af2c026",
|
||||||
|
systemCredentialDescription: "i18n:govoplan-access.instance_credentials_can_be_inherited_and_governed.4af2c027",
|
||||||
|
tenantCredentialDescription: "i18n:govoplan-access.tenant_credentials_can_be_shared_with_permitted_modules.4af2c028",
|
||||||
|
groupCredentialDescription: "i18n:govoplan-access.reusable_credentials_owned_by_the_selected_group.4af2c029",
|
||||||
|
userCredentialDescription: "i18n:govoplan-access.reusable_credentials_owned_by_the_selected_user.4af2c030"
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export function saveDisabledReason({
|
||||||
|
busy,
|
||||||
|
permitted,
|
||||||
|
complete
|
||||||
|
}: {
|
||||||
|
busy: boolean;
|
||||||
|
permitted: boolean;
|
||||||
|
complete: boolean;
|
||||||
|
}): string | undefined {
|
||||||
|
if (busy) return ACCESS_INTERFACE_I18N.operationInProgress;
|
||||||
|
if (!permitted) return ACCESS_INTERFACE_I18N.writePermissionRequired;
|
||||||
|
if (!complete) return ACCESS_INTERFACE_I18N.completeRequiredFields;
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
@@ -2,6 +2,36 @@ import type { PlatformTranslations } from "@govoplan/core-webui";
|
|||||||
|
|
||||||
export const generatedTranslations: PlatformTranslations = {
|
export const generatedTranslations: PlatformTranslations = {
|
||||||
"en": {
|
"en": {
|
||||||
|
"i18n:govoplan-access.administration_data_is_loading.4af2c001": "Administration data is loading.",
|
||||||
|
"i18n:govoplan-access.create_permission_is_required.4af2c002": "Create permission is required for this action.",
|
||||||
|
"i18n:govoplan-access.update_or_assignment_permission_is_required.4af2c003": "Update or assignment permission is required for this action.",
|
||||||
|
"i18n:govoplan-access.write_permission_is_required.4af2c004": "Write permission is required for this action.",
|
||||||
|
"i18n:govoplan-access.complete_the_required_fields_before_saving.4af2c005": "Complete the required fields before saving.",
|
||||||
|
"i18n:govoplan-access.select_an_account_and_at_least_one_scope.4af2c006": "Select an account and at least one allowed scope.",
|
||||||
|
"i18n:govoplan-access.select_an_assignable_role_before_creating_a_mapping.4af2c007": "Create an assignable role before adding a function mapping.",
|
||||||
|
"i18n:govoplan-access.remove_existing_assignments_before_deleting.4af2c008": "Remove existing assignments before deleting this definition.",
|
||||||
|
"i18n:govoplan-access.assign_another_operational_owner_before_deactivating.4af2c009": "Assign another operational owner before deactivating this account or membership.",
|
||||||
|
"i18n:govoplan-access.this_definition_is_managed_by_the_system.4af2c010": "This protected definition is managed by the system.",
|
||||||
|
"i18n:govoplan-access.an_access_administration_operation_is_in_progress.4af2c011": "An access administration operation is in progress.",
|
||||||
|
"i18n:govoplan-access.required_action.4af2c012": "Required action",
|
||||||
|
"i18n:govoplan-access.who_can_fix_it.4af2c013": "Who can fix it",
|
||||||
|
"i18n:govoplan-access.where_to_go.4af2c014": "Where to go",
|
||||||
|
"i18n:govoplan-access.request_an_administrative_role_for_the_required_scope.4af2c015": "Request an administrative role for the required scope.",
|
||||||
|
"i18n:govoplan-access.a_tenant_owner_or_system_access_administrator.4af2c016": "A tenant owner or system access administrator",
|
||||||
|
"i18n:govoplan-access.admin_users_groups_and_roles.4af2c017": "Admin > Users, Groups, or Roles",
|
||||||
|
"i18n:govoplan-access.install_and_enable_the_files_module.4af2c018": "Install and enable the Files module.",
|
||||||
|
"i18n:govoplan-access.install_and_enable_the_mail_module.4af2c019": "Install and enable the Mail module.",
|
||||||
|
"i18n:govoplan-access.a_system_module_administrator.4af2c020": "A system module administrator",
|
||||||
|
"i18n:govoplan-access.admin_modules.4af2c021": "Admin > Modules",
|
||||||
|
"i18n:govoplan-access.reusable_credentials.4af2c022": "Reusable credentials",
|
||||||
|
"i18n:govoplan-access.system_credentials.4af2c023": "System credentials",
|
||||||
|
"i18n:govoplan-access.tenant_credentials.4af2c024": "Tenant credentials",
|
||||||
|
"i18n:govoplan-access.group_credentials.4af2c025": "Group credentials",
|
||||||
|
"i18n:govoplan-access.user_credentials.4af2c026": "User credentials",
|
||||||
|
"i18n:govoplan-access.instance_credentials_can_be_inherited_and_governed.4af2c027": "Instance credentials can be inherited by tenants and limited to selected modules or servers.",
|
||||||
|
"i18n:govoplan-access.tenant_credentials_can_be_shared_with_permitted_modules.4af2c028": "Tenant credentials can be shared with Mail, Files, Calendar, Addresses, and other permitted modules.",
|
||||||
|
"i18n:govoplan-access.reusable_credentials_owned_by_the_selected_group.4af2c029": "Reusable credentials owned by the selected group.",
|
||||||
|
"i18n:govoplan-access.reusable_credentials_owned_by_the_selected_user.4af2c030": "Reusable credentials owned by the selected user.",
|
||||||
"i18n:govoplan-access.a_role_may_contain_only_permissions_held_by_the_.a7ee5e45": "A role may contain only permissions held by the administrator defining it. The protected system:* wildcard is reserved for System owner.",
|
"i18n:govoplan-access.a_role_may_contain_only_permissions_held_by_the_.a7ee5e45": "A role may contain only permissions held by the administrator defining it. The protected system:* wildcard is reserved for System owner.",
|
||||||
"i18n:govoplan-access.access_updated_for_value.87f22245": "Access updated for {value0}.",
|
"i18n:govoplan-access.access_updated_for_value.87f22245": "Access updated for {value0}.",
|
||||||
"i18n:govoplan-access.access_explanation.75ee7f62": "Access explanation",
|
"i18n:govoplan-access.access_explanation.75ee7f62": "Access explanation",
|
||||||
@@ -352,6 +382,36 @@ export const generatedTranslations: PlatformTranslations = {
|
|||||||
"i18n:govoplan-access.your_current_roles_do_not_grant_administrative_a.6eafee69": "Your current roles do not grant administrative access."
|
"i18n:govoplan-access.your_current_roles_do_not_grant_administrative_a.6eafee69": "Your current roles do not grant administrative access."
|
||||||
},
|
},
|
||||||
"de": {
|
"de": {
|
||||||
|
"i18n:govoplan-access.administration_data_is_loading.4af2c001": "Administrationsdaten werden geladen.",
|
||||||
|
"i18n:govoplan-access.create_permission_is_required.4af2c002": "Für diese Aktion ist die Berechtigung zum Erstellen erforderlich.",
|
||||||
|
"i18n:govoplan-access.update_or_assignment_permission_is_required.4af2c003": "Für diese Aktion ist eine Berechtigung zum Ändern oder Zuweisen erforderlich.",
|
||||||
|
"i18n:govoplan-access.write_permission_is_required.4af2c004": "Für diese Aktion ist eine Schreibberechtigung erforderlich.",
|
||||||
|
"i18n:govoplan-access.complete_the_required_fields_before_saving.4af2c005": "Füllen Sie vor dem Speichern die Pflichtfelder aus.",
|
||||||
|
"i18n:govoplan-access.select_an_account_and_at_least_one_scope.4af2c006": "Wählen Sie ein Konto und mindestens einen zulässigen Geltungsbereich aus.",
|
||||||
|
"i18n:govoplan-access.select_an_assignable_role_before_creating_a_mapping.4af2c007": "Erstellen Sie vor dem Hinzufügen einer Funktionszuordnung eine zuweisbare Rolle.",
|
||||||
|
"i18n:govoplan-access.remove_existing_assignments_before_deleting.4af2c008": "Entfernen Sie bestehende Zuweisungen, bevor Sie diese Definition löschen.",
|
||||||
|
"i18n:govoplan-access.assign_another_operational_owner_before_deactivating.4af2c009": "Weisen Sie einen anderen betriebsfähigen Eigentümer zu, bevor Sie dieses Konto oder diese Mitgliedschaft deaktivieren.",
|
||||||
|
"i18n:govoplan-access.this_definition_is_managed_by_the_system.4af2c010": "Diese geschützte Definition wird vom System verwaltet.",
|
||||||
|
"i18n:govoplan-access.an_access_administration_operation_is_in_progress.4af2c011": "Eine Zugriffsverwaltungsaktion wird gerade ausgeführt.",
|
||||||
|
"i18n:govoplan-access.required_action.4af2c012": "Erforderliche Aktion",
|
||||||
|
"i18n:govoplan-access.who_can_fix_it.4af2c013": "Wer kann das beheben",
|
||||||
|
"i18n:govoplan-access.where_to_go.4af2c014": "Zuständige Stelle",
|
||||||
|
"i18n:govoplan-access.request_an_administrative_role_for_the_required_scope.4af2c015": "Fordern Sie eine administrative Rolle für den benötigten Bereich an.",
|
||||||
|
"i18n:govoplan-access.a_tenant_owner_or_system_access_administrator.4af2c016": "Ein Mandanteneigentümer oder System-Zugriffsadministrator",
|
||||||
|
"i18n:govoplan-access.admin_users_groups_and_roles.4af2c017": "Administration > Benutzer, Gruppen oder Rollen",
|
||||||
|
"i18n:govoplan-access.install_and_enable_the_files_module.4af2c018": "Installieren und aktivieren Sie das Dateimodul.",
|
||||||
|
"i18n:govoplan-access.install_and_enable_the_mail_module.4af2c019": "Installieren und aktivieren Sie das Mailmodul.",
|
||||||
|
"i18n:govoplan-access.a_system_module_administrator.4af2c020": "Ein System-Moduladministrator",
|
||||||
|
"i18n:govoplan-access.admin_modules.4af2c021": "Administration > Module",
|
||||||
|
"i18n:govoplan-access.reusable_credentials.4af2c022": "Wiederverwendbare Zugangsdaten",
|
||||||
|
"i18n:govoplan-access.system_credentials.4af2c023": "System-Zugangsdaten",
|
||||||
|
"i18n:govoplan-access.tenant_credentials.4af2c024": "Mandanten-Zugangsdaten",
|
||||||
|
"i18n:govoplan-access.group_credentials.4af2c025": "Gruppen-Zugangsdaten",
|
||||||
|
"i18n:govoplan-access.user_credentials.4af2c026": "Benutzer-Zugangsdaten",
|
||||||
|
"i18n:govoplan-access.instance_credentials_can_be_inherited_and_governed.4af2c027": "Instanzweite Zugangsdaten können von Mandanten geerbt und auf ausgewählte Module oder Server begrenzt werden.",
|
||||||
|
"i18n:govoplan-access.tenant_credentials_can_be_shared_with_permitted_modules.4af2c028": "Mandanten-Zugangsdaten können mit Mail, Dateien, Kalender, Adressen und anderen erlaubten Modulen geteilt werden.",
|
||||||
|
"i18n:govoplan-access.reusable_credentials_owned_by_the_selected_group.4af2c029": "Wiederverwendbare Zugangsdaten der ausgewählten Gruppe.",
|
||||||
|
"i18n:govoplan-access.reusable_credentials_owned_by_the_selected_user.4af2c030": "Wiederverwendbare Zugangsdaten des ausgewählten Benutzers.",
|
||||||
"i18n:govoplan-access.a_role_may_contain_only_permissions_held_by_the_.a7ee5e45": "A role may contain only permissions held by the administrator defining it. The protected system:* wildcard is reserved for System owner.",
|
"i18n:govoplan-access.a_role_may_contain_only_permissions_held_by_the_.a7ee5e45": "A role may contain only permissions held by the administrator defining it. The protected system:* wildcard is reserved for System owner.",
|
||||||
"i18n:govoplan-access.access_updated_for_value.87f22245": "Access updated for {value0}.",
|
"i18n:govoplan-access.access_updated_for_value.87f22245": "Access updated for {value0}.",
|
||||||
"i18n:govoplan-access.access_explanation.75ee7f62": "Zugriffserklaerung",
|
"i18n:govoplan-access.access_explanation.75ee7f62": "Zugriffserklaerung",
|
||||||
|
|||||||
+13
-13
@@ -12,18 +12,18 @@ const translations = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const accessAdminSurfaces = [
|
const accessAdminSurfaces = [
|
||||||
{ id: "access.admin.system-roles", moduleId: "access", kind: "section" as const, label: "System roles", order: 20 },
|
{ id: "access.admin.system-roles", moduleId: "access", kind: "section" as const, label: "i18n:govoplan-access.system_roles.a9461aa6", order: 20 },
|
||||||
{ id: "access.admin.system-users", moduleId: "access", kind: "section" as const, label: "System users", order: 50 },
|
{ id: "access.admin.system-users", moduleId: "access", kind: "section" as const, label: "i18n:govoplan-access.central_users.91ac1b51", order: 50 },
|
||||||
{ id: "access.admin.system-credentials", moduleId: "access", kind: "section" as const, label: "System credentials", order: 80 },
|
{ id: "access.admin.system-credentials", moduleId: "access", kind: "section" as const, label: "i18n:govoplan-access.system_credentials.4af2c023", order: 80 },
|
||||||
{ id: "access.admin.tenant-roles", moduleId: "access", kind: "section" as const, label: "Tenant roles", order: 10 },
|
{ id: "access.admin.tenant-roles", moduleId: "access", kind: "section" as const, label: "i18n:govoplan-access.tenant_roles.51aca82d", order: 10 },
|
||||||
{ id: "access.admin.tenant-function-mappings", moduleId: "access", kind: "section" as const, label: "Function mappings", order: 20 },
|
{ id: "access.admin.tenant-function-mappings", moduleId: "access", kind: "section" as const, label: "i18n:govoplan-access.function_role_mappings.2b64e9c3", order: 20 },
|
||||||
{ id: "access.admin.tenant-groups", moduleId: "access", kind: "section" as const, label: "Tenant groups", order: 30 },
|
{ id: "access.admin.tenant-groups", moduleId: "access", kind: "section" as const, label: "i18n:govoplan-access.tenant_groups.47e6cc05", order: 30 },
|
||||||
{ id: "access.admin.tenant-users", moduleId: "access", kind: "section" as const, label: "Tenant users", order: 40 },
|
{ id: "access.admin.tenant-users", moduleId: "access", kind: "section" as const, label: "i18n:govoplan-access.tenant_users.cb800b38", order: 40 },
|
||||||
{ id: "access.admin.tenant-credentials", moduleId: "access", kind: "section" as const, label: "Tenant credentials", order: 70 },
|
{ id: "access.admin.tenant-credentials", moduleId: "access", kind: "section" as const, label: "i18n:govoplan-access.tenant_credentials.4af2c024", order: 70 },
|
||||||
{ id: "access.admin.tenant-api-keys", moduleId: "access", kind: "section" as const, label: "Tenant API keys", order: 80 },
|
{ id: "access.admin.tenant-api-keys", moduleId: "access", kind: "section" as const, label: "i18n:govoplan-access.tenant_api_keys.4b1d81f8", order: 80 },
|
||||||
{ id: "access.admin.group-credentials", moduleId: "access", kind: "section" as const, label: "Group credentials", order: 30 },
|
{ id: "access.admin.group-credentials", moduleId: "access", kind: "section" as const, label: "i18n:govoplan-access.group_credentials.4af2c025", order: 30 },
|
||||||
{ id: "access.admin.user-credentials", moduleId: "access", kind: "section" as const, label: "User credentials", order: 30 },
|
{ id: "access.admin.user-credentials", moduleId: "access", kind: "section" as const, label: "i18n:govoplan-access.user_credentials.4af2c026", order: 30 },
|
||||||
{ id: "access.settings.credentials", moduleId: "access", kind: "section" as const, label: "Personal credentials", order: 30 }
|
{ id: "access.settings.credentials", moduleId: "access", kind: "section" as const, label: "i18n:govoplan-access.reusable_credentials.4af2c022", order: 30 }
|
||||||
];
|
];
|
||||||
|
|
||||||
function renderAdminRoute({ settings, auth, onAuthChange }: PlatformRouteContext) {
|
function renderAdminRoute({ settings, auth, onAuthChange }: PlatformRouteContext) {
|
||||||
@@ -36,7 +36,7 @@ function renderAdminRoute({ settings, auth, onAuthChange }: PlatformRouteContext
|
|||||||
export const accessModule: PlatformWebModule = {
|
export const accessModule: PlatformWebModule = {
|
||||||
id: "access",
|
id: "access",
|
||||||
label: "i18n:govoplan-access.access.2f81a22d",
|
label: "i18n:govoplan-access.access.2f81a22d",
|
||||||
version: "1.0.0",
|
version: "0.1.11",
|
||||||
translations,
|
translations,
|
||||||
viewSurfaces: accessAdminSurfaces,
|
viewSurfaces: accessAdminSurfaces,
|
||||||
navItems: [
|
navItems: [
|
||||||
|
|||||||
Reference in New Issue
Block a user