Adopt shared WebUI structural primitives
This commit is contained in:
@@ -31,9 +31,14 @@ through declared capabilities or metadata.
|
||||
|
||||
## Interaction evidence
|
||||
|
||||
- `AdminPageLayout`, `TreeSubnav`, `DataGrid`, `Dialog`, `ConfirmDialog`,
|
||||
- `WorkspaceLayout`, headerless `PageLayout`, `AdminPageLayout`, `TreeSubnav`,
|
||||
`DataGrid`, `Dialog`, `ConfirmDialog`,
|
||||
`TableActionGroup`, `PasswordField`, `ActionBlockerHint`, and
|
||||
`DocumentationHelpLink` come from Core.
|
||||
- The administration tree and its contributed panels now share Core-owned pane
|
||||
sizing, scrolling, content inset, responsive collapse, region labels, and
|
||||
contextual-help identity; Access no longer carries a raw workspace or page
|
||||
frame exception.
|
||||
- Dialog focus trapping and restoration, disabled-action tooltips, keyboard
|
||||
ordering, responsive grid overflow, and alert semantics therefore inherit
|
||||
the tested Core behavior.
|
||||
|
||||
@@ -12,6 +12,7 @@ import type {
|
||||
} from "@govoplan/core-webui";
|
||||
import { fetchShellAuth } from "@govoplan/core-webui";
|
||||
import { ActionBlockerHint } from "@govoplan/core-webui";
|
||||
import { PageLayout, WorkspaceLayout } from "@govoplan/core-webui";
|
||||
import { PageScrollViewport } from "@govoplan/core-webui";
|
||||
import {
|
||||
TreeSubnav,
|
||||
@@ -322,15 +323,26 @@ export default function AdminPage({
|
||||
const contributionContext = { settings, auth, onAuthChange, refreshAuth, availableSections: available, selectSection };
|
||||
|
||||
return (
|
||||
<div className="workspace module-workspace">
|
||||
<WorkspaceLayout
|
||||
className="module-workspace"
|
||||
primary={(
|
||||
<TreeSubnav
|
||||
active={active}
|
||||
nodes={adminTree}
|
||||
onSelect={selectSection}
|
||||
ariaLabel="i18n:govoplan-access.admin.4e7afebc"
|
||||
/>
|
||||
<section className="workspace-content">
|
||||
<div className="content-pad workspace-data-page">
|
||||
)}
|
||||
primaryLabel="i18n:govoplan-access.admin.4e7afebc"
|
||||
contentLabel="i18n:govoplan-access.admin.4e7afebc"
|
||||
documentationType="admin"
|
||||
>
|
||||
<PageLayout
|
||||
title="i18n:govoplan-access.admin.4e7afebc"
|
||||
mode="workspace"
|
||||
showHeader={false}
|
||||
documentationType="admin"
|
||||
>
|
||||
{contributedSection && contributedSection.render(contributionContext)}
|
||||
{!contributedSection && active === "system-mail-servers" && (
|
||||
<MailProfilesPanel settings={settings} scopeType="system" canWriteProfiles={hasScope(auth, "system:settings:write")} canManageCredentials={hasScope(auth, "system:settings:write")} canWritePolicy={hasScope(auth, "system:settings:write")} />
|
||||
@@ -368,9 +380,8 @@ export default function AdminPage({
|
||||
{!contributedSection && active === "tenant-group-credentials" && <CredentialEnvelopesPanel settings={settings} scopeType="group" canWrite={hasAnyScope(auth, ["admin:settings:write", "access:credential:write"])} />}
|
||||
{!contributedSection && active === "tenant-user-file-connectors" && <FileConnectorsPanel settings={settings} scopeType="user" canWrite={hasAnyScope(auth, ["files:file:admin", "admin:settings:write"])} />}
|
||||
{!contributedSection && active === "tenant-group-file-connectors" && <FileConnectorsPanel settings={settings} scopeType="group" canWrite={hasAnyScope(auth, ["files:file:admin", "admin:settings:write"])} />}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</PageLayout>
|
||||
</WorkspaceLayout>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DescriptionItem, DescriptionList } from "@govoplan/core-webui";
|
||||
import { ContentGrid, DescriptionItem, DescriptionList } from "@govoplan/core-webui";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Pencil, Plus, Search, Trash2 } from "lucide-react";
|
||||
import type { FormGrid, ApiSettings, AuthInfo } from "@govoplan/core-webui";
|
||||
@@ -158,10 +158,10 @@ export default function GroupsPanel({ settings, auth, canDefine, canManageMember
|
||||
<FormField label="i18n:govoplan-access.status.bae7d5be"><select value={draft.isActive ? "active" : "inactive"} disabled={!canDefine || editing !== "new" && Boolean(editing?.system_required)} onChange={(event) => setDraft({ ...draft, isActive: event.target.value === "active" })}><option value="active">i18n:govoplan-access.active.a733b809</option><option value="inactive">i18n:govoplan-access.inactive.09af574c</option></select></FormField>
|
||||
<FormField label="i18n:govoplan-access.description.55f8ebc8"><textarea rows={3} value={draft.description} disabled={!canDefine || editing !== "new" && Boolean(editing?.system_template_id)} onChange={(event) => setDraft({ ...draft, description: event.target.value })} /></FormField>
|
||||
</FormGrid>
|
||||
<div className="admin-assignment-grid">
|
||||
<ContentGrid columns={2} spacing="block" collapseAt="wide">
|
||||
<div><span className="form-label">i18n:govoplan-access.members.1cb449c1</span><AdminSelectionList options={users.map((user) => ({ id: user.id, label: user.display_name || user.email, description: user.email, disabled: !canManageMembers || !user.is_active || !user.account_is_active }))} selected={draft.memberIds} onChange={(memberIds) => setDraft({ ...draft, memberIds })} emptyText="i18n:govoplan-access.no_tenant_users_exist.96b4a88a" /></div>
|
||||
<div><span className="form-label">i18n:govoplan-access.inherited_roles.8def9f05</span><AdminSelectionList options={roles.map((role) => ({ id: role.id, label: role.name, description: role.description, disabled: !canAssignRoles }))} selected={draft.roleIds} onChange={(roleIds) => setDraft({ ...draft, roleIds })} emptyText="i18n:govoplan-access.no_assignable_roles_exist.a4c268c2" /></div>
|
||||
</div>
|
||||
</ContentGrid>
|
||||
<p className="muted small-note">i18n:govoplan-access.the_backend_evaluates_the_resulting_access_graph.f318a7dc</p>
|
||||
</Dialog>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DescriptionItem, DescriptionList } from "@govoplan/core-webui";
|
||||
import { ContentGrid, DescriptionItem, DescriptionList } from "@govoplan/core-webui";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Search, Pencil, Plus, Trash2 } from "lucide-react";
|
||||
import type { FormGrid, ApiSettings } from "@govoplan/core-webui";
|
||||
@@ -250,10 +250,10 @@ export default function SystemUsersPanel({
|
||||
}
|
||||
<FormField label="i18n:govoplan-access.account_status.8dd86c6d"><select value={draft.isActive ? "active" : "inactive"} disabled={Boolean(editing && editing !== "new" && (!canSuspend || editing.memberships.some((membership) => membership.is_last_active_owner)))} onChange={(event) => setDraft({ ...draft, isActive: event.target.value === "active" })}><option value="active">i18n:govoplan-access.active.a733b809</option><option value="inactive">i18n:govoplan-access.inactive.09af574c</option></select></FormField>
|
||||
</FormGrid>
|
||||
<div className="admin-assignment-grid">
|
||||
<ContentGrid columns={2} spacing="block" collapseAt="wide">
|
||||
<div><span className="form-label">i18n:govoplan-access.system_roles.a9461aa6</span><AdminSelectionList options={roles.map((role) => ({ id: role.id, label: role.name, description: role.description, disabled: !canAssignRoles }))} selected={draft.roleIds} onChange={(roleIds) => setDraft({ ...draft, roleIds })} /></div>
|
||||
<div><span className="form-label">i18n:govoplan-access.tenant_memberships.451de736</span><AdminSelectionList options={tenants.map((tenant) => ({ id: tenant.id, label: tenant.name, description: tenant.slug, disabled: !canManageMemberships || Boolean(draft.memberships.find((item) => item.tenant_id === tenant.id)?.is_last_active_owner) }))} selected={draft.memberships.map((item) => item.tenant_id)} onChange={setMembershipSelection} /></div>
|
||||
</div>
|
||||
</ContentGrid>
|
||||
{editing && editing !== "new" && editing.memberships.some((membership) => membership.is_last_active_owner) && <p className="admin-protection-note">i18n:govoplan-access.this_account_is_the_last_active_operational_owne.5087839f</p>}
|
||||
<p className="muted small-note">i18n:govoplan-access.removing_a_tenant_checkbox_suspends_that_members.7c6df77d</p>
|
||||
</Dialog>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DescriptionItem, DescriptionList } from "@govoplan/core-webui";
|
||||
import { ContentGrid, DescriptionItem, DescriptionList } from "@govoplan/core-webui";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { KeyRound, Pencil, Plus, Search, Trash2 } from "lucide-react";
|
||||
import type { FormGrid, ApiSettings, AuthInfo } from "@govoplan/core-webui";
|
||||
@@ -225,10 +225,10 @@ export default function UsersPanel({ settings, auth, canCreate, canUpdate, canSu
|
||||
</FormGrid>
|
||||
{editing === "new" && <ToggleSwitch label="i18n:govoplan-access.require_password_change_when_account_settings_ar.69bce7a3" checked={draft.passwordResetRequired} onChange={(passwordResetRequired) => setDraft({ ...draft, passwordResetRequired })} />}
|
||||
{editing && editing !== "new" && editing.is_last_active_owner && <p className="admin-protection-note">i18n:govoplan-access.this_membership_is_the_tenant_s_last_active_oper.072b247f</p>}
|
||||
<div className="admin-assignment-grid">
|
||||
<ContentGrid columns={2} spacing="block" collapseAt="wide">
|
||||
<div><span className="form-label">i18n:govoplan-access.groups.ae9629f4</span><AdminSelectionList options={groups.filter((group) => group.is_active).map((group) => ({ id: group.id, label: group.name, description: group.description, disabled: !canManageGroups }))} selected={draft.groupIds} onChange={(groupIds) => setDraft({ ...draft, groupIds })} emptyText="i18n:govoplan-access.no_groups_exist_yet.9cd029f6" /></div>
|
||||
<div><span className="form-label">i18n:govoplan-access.direct_roles.c4db7156</span><AdminSelectionList options={roles.map((role) => ({ id: role.id, label: role.name, description: role.description, disabled: !canAssignRoles }))} selected={draft.roleIds} onChange={(roleIds) => setDraft({ ...draft, roleIds })} emptyText="i18n:govoplan-access.no_assignable_roles_exist.a4c268c2" /></div>
|
||||
</div>
|
||||
</ContentGrid>
|
||||
<p className="muted small-note">i18n:govoplan-access.group_roles_and_direct_roles_are_combined_the_ba.a43c2f16</p>
|
||||
</Dialog>
|
||||
|
||||
@@ -251,7 +251,7 @@ export default function UsersPanel({ settings, auth, canCreate, canUpdate, canSu
|
||||
<DescriptionItem term={<>i18n:govoplan-access.function_facts.848b32cc</>}>{accessExplanation.function_facts.length}</DescriptionItem>
|
||||
</DescriptionList>
|
||||
<h3>i18n:govoplan-access.role_sources.6f42a672</h3>
|
||||
{accessExplanation.role_sources.length ? <div className="admin-assignment-grid">
|
||||
{accessExplanation.role_sources.length ? <ContentGrid columns={2} spacing="block" collapseAt="wide">
|
||||
{accessExplanation.role_sources.map((source) => <div key={sourceKey(source)}>
|
||||
<strong>{source.role_name}</strong>
|
||||
<div className="muted small-note">
|
||||
@@ -263,7 +263,7 @@ export default function UsersPanel({ settings, auth, canCreate, canUpdate, canSu
|
||||
<AccessExplanationLinks organizationHref={organizationHrefForSource(source)} idmHref={idmHrefForSource(source)} />
|
||||
<div className="admin-scope-list">{source.permissions.map((scope) => <code key={scope}>{scope}</code>)}</div>
|
||||
</div>)}
|
||||
</div> : <p className="muted small-note">i18n:govoplan-access.no_role_sources_found.91013aa5</p>}
|
||||
</ContentGrid> : <p className="muted small-note">i18n:govoplan-access.no_role_sources_found.91013aa5</p>}
|
||||
<h3>i18n:govoplan-access.function_facts.848b32cc</h3>
|
||||
{accessExplanation.function_facts.length ? <DescriptionList>
|
||||
{accessExplanation.function_facts.map((fact) => <DescriptionItem key={fact.assignment_id} term={fact.function_name || fact.function_id}>
|
||||
|
||||
Reference in New Issue
Block a user