Adopt shared WebUI layout primitives
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useMemo, useRef, useState } from "react";
|
import { useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { Plus, Search, Trash2 } from "lucide-react";
|
import { Plus, Search, Trash2 } from "lucide-react";
|
||||||
import type { ApiSettings, AuthInfo } from "@govoplan/core-webui";
|
import type { FormGrid, ApiSettings, AuthInfo } from "@govoplan/core-webui";
|
||||||
import { createApiKey, fetchApiKeysDelta, fetchPermissionCatalog, fetchUsersDelta, revokeApiKey, type ApiKeyAdminItem, type PermissionItem, type UserAdminItem } from "../../api/admin";
|
import { createApiKey, fetchApiKeysDelta, fetchPermissionCatalog, fetchUsersDelta, revokeApiKey, type ApiKeyAdminItem, type PermissionItem, type UserAdminItem } from "../../api/admin";
|
||||||
import { Button } from "@govoplan/core-webui";
|
import { Button } from "@govoplan/core-webui";
|
||||||
import { DataGrid, type DataGridColumn } from "@govoplan/core-webui";
|
import { DataGrid, type DataGridColumn } from "@govoplan/core-webui";
|
||||||
@@ -156,16 +156,16 @@ export default function ApiKeysPanel({ settings, auth, canCreate, canRevoke }: {
|
|||||||
<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} 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></>}>
|
<Dialog variant="administration" size="wide" open={creating} title="i18n:govoplan-access.create_api_key.d7b30388" onClose={() => !busy && setCreating(false)} className="" 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">
|
<FormGrid columns={2} gap="small" collapseAt="workspace" className="">
|
||||||
<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>
|
||||||
<FormField label="i18n:govoplan-access.expiry.ba8f571e"><DateTimeField value={draft.expiresAt} onChange={(value) => setDraft({ ...draft, expiresAt: value })} /></FormField>
|
<FormField label="i18n:govoplan-access.expiry.ba8f571e"><DateTimeField value={draft.expiresAt} onChange={(value) => setDraft({ ...draft, expiresAt: value })} /></FormField>
|
||||||
</div>
|
</FormGrid>
|
||||||
<div className="form-field"><span className="form-label">i18n:govoplan-access.allowed_scopes.d94515ff</span><AdminSelectionList options={allowedPermissions.map((permission) => ({ id: permission.scope, label: permission.label, description: i18nMessage("i18n:govoplan-access.value_value.0e2772ed", { value0: permission.scope, value1: permission.description }) }))} selected={draft.scopes} onChange={(scopes) => setDraft({ ...draft, scopes })} emptyText="i18n:govoplan-access.the_selected_user_has_no_tenant_permissions_avai.96985ec7" /></div>
|
<div className="form-field"><span className="form-label">i18n:govoplan-access.allowed_scopes.d94515ff</span><AdminSelectionList options={allowedPermissions.map((permission) => ({ id: permission.scope, label: permission.label, description: i18nMessage("i18n:govoplan-access.value_value.0e2772ed", { value0: permission.scope, value1: permission.description }) }))} selected={draft.scopes} onChange={(scopes) => setDraft({ ...draft, scopes })} emptyText="i18n:govoplan-access.the_selected_user_has_no_tenant_permissions_avai.96985ec7" /></div>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<Dialog open={Boolean(viewing)} title="i18n:govoplan-access.api_key_details.f70c16be" onClose={() => setViewing(null)} className="admin-dialog admin-dialog-wide" footer={<Button onClick={() => setViewing(null)}>i18n:govoplan-access.close.bbfa773e</Button>}>
|
<Dialog variant="administration" size="wide" open={Boolean(viewing)} title="i18n:govoplan-access.api_key_details.f70c16be" onClose={() => setViewing(null)} className="" footer={<Button onClick={() => setViewing(null)}>i18n:govoplan-access.close.bbfa773e</Button>}>
|
||||||
{viewing && <><dl className="admin-details-grid">
|
{viewing && <><dl className="admin-details-grid">
|
||||||
<div><dt>i18n:govoplan-access.name.709a2322</dt><dd>{viewing.name}</dd></div><div><dt>i18n:govoplan-access.prefix.90eceb01</dt><dd>{viewing.prefix}…</dd></div>
|
<div><dt>i18n:govoplan-access.name.709a2322</dt><dd>{viewing.name}</dd></div><div><dt>i18n:govoplan-access.prefix.90eceb01</dt><dd>{viewing.prefix}…</dd></div>
|
||||||
<div><dt>i18n:govoplan-access.owner.89ff3122</dt><dd>{viewing.user_email}</dd></div><div><dt>i18n:govoplan-access.status.bae7d5be</dt><dd>{viewing.revoked_at ? "i18n:govoplan-access.revoked.85f17ac0" : "i18n:govoplan-access.active.a733b809"}</dd></div>
|
<div><dt>i18n:govoplan-access.owner.89ff3122</dt><dd>{viewing.user_email}</dd></div><div><dt>i18n:govoplan-access.status.bae7d5be</dt><dd>{viewing.revoked_at ? "i18n:govoplan-access.revoked.85f17ac0" : "i18n:govoplan-access.active.a733b809"}</dd></div>
|
||||||
@@ -174,7 +174,7 @@ export default function ApiKeysPanel({ settings, auth, canCreate, canRevoke }: {
|
|||||||
</dl><h3>i18n:govoplan-access.scopes.c23540e5</h3><div className="admin-scope-list">{viewing.scopes.map((scope) => <code key={scope}>{scope}</code>)}</div></>}
|
</dl><h3>i18n:govoplan-access.scopes.c23540e5</h3><div className="admin-scope-list">{viewing.scopes.map((scope) => <code key={scope}>{scope}</code>)}</div></>}
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<Dialog open={Boolean(secret)} title="i18n:govoplan-access.api_key_secret.00b16050" onClose={() => setSecret(null)} className="admin-dialog" footer={<Button variant="primary" onClick={() => setSecret(null)}>i18n:govoplan-access.i_have_recorded_it.7522da18</Button>}>
|
<Dialog variant="administration" size="large" open={Boolean(secret)} title="i18n:govoplan-access.api_key_secret.00b16050" onClose={() => setSecret(null)} className="" footer={<Button variant="primary" onClick={() => setSecret(null)}>i18n:govoplan-access.i_have_recorded_it.7522da18</Button>}>
|
||||||
{secret && <><p>i18n:govoplan-access.the_secret_for.c60737ef <strong>{secret.name}</strong> i18n:govoplan-access.is_shown_once.af2b1235</p><code className="admin-secret">{secret.value}</code><p className="muted small-note">i18n:govoplan-access.store_it_in_a_secret_manager_only_its_prefix_and.796ac588</p></>}
|
{secret && <><p>i18n:govoplan-access.the_secret_for.c60737ef <strong>{secret.name}</strong> i18n:govoplan-access.is_shown_once.af2b1235</p><code className="admin-secret">{secret.value}</code><p className="muted small-note">i18n:govoplan-access.store_it_in_a_secret_manager_only_its_prefix_and.796ac588</p></>}
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useMemo, useRef, useState } from "react";
|
import { useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { Pencil, Plus, Trash2 } from "lucide-react";
|
import { Pencil, Plus, Trash2 } from "lucide-react";
|
||||||
import type { ApiSettings, AuthInfo, OrganizationFunctionPickerUiCapability, OrganizationFunctionSelection } from "@govoplan/core-webui";
|
import type { FormGrid, ApiSettings, AuthInfo, OrganizationFunctionPickerUiCapability, OrganizationFunctionSelection } from "@govoplan/core-webui";
|
||||||
import {
|
import {
|
||||||
createExternalFunctionRoleMapping,
|
createExternalFunctionRoleMapping,
|
||||||
deleteExternalFunctionRoleMapping,
|
deleteExternalFunctionRoleMapping,
|
||||||
@@ -295,11 +295,11 @@ export default function ExternalFunctionRoleMappingsPanel({
|
|||||||
</div>
|
</div>
|
||||||
</AdminPageLayout>
|
</AdminPageLayout>
|
||||||
|
|
||||||
<Dialog
|
<Dialog variant="administration" size="large"
|
||||||
open={editing !== null}
|
open={editing !== null}
|
||||||
title={editing === "new" ? "i18n:govoplan-access.create_function_role_mapping.3718168d" : "i18n:govoplan-access.edit_function_role_mapping.91ee75af"}
|
title={editing === "new" ? "i18n:govoplan-access.create_function_role_mapping.3718168d" : "i18n:govoplan-access.edit_function_role_mapping.91ee75af"}
|
||||||
onClose={() => !busy && closeEditor()}
|
onClose={() => !busy && closeEditor()}
|
||||||
className="admin-dialog"
|
className=""
|
||||||
footer={
|
footer={
|
||||||
<>
|
<>
|
||||||
<Button onClick={closeEditor} disabled={busy} disabledReason={busy ? ACCESS_INTERFACE_I18N.operationInProgress : undefined}>i18n:govoplan-access.cancel.77dfd213</Button>
|
<Button onClick={closeEditor} disabled={busy} disabledReason={busy ? ACCESS_INTERFACE_I18N.operationInProgress : undefined}>i18n:govoplan-access.cancel.77dfd213</Button>
|
||||||
@@ -309,7 +309,7 @@ export default function ExternalFunctionRoleMappingsPanel({
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="admin-form-grid">
|
<FormGrid columns={1} gap="small" collapseAt="workspace" className="">
|
||||||
<FormField label="i18n:govoplan-access.function_id.e5e08937">
|
<FormField label="i18n:govoplan-access.function_id.e5e08937">
|
||||||
{functionPicker.renderPicker({
|
{functionPicker.renderPicker({
|
||||||
settings,
|
settings,
|
||||||
@@ -327,7 +327,7 @@ export default function ExternalFunctionRoleMappingsPanel({
|
|||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</FormField>
|
</FormField>
|
||||||
</div>
|
</FormGrid>
|
||||||
<p className="muted small-note">i18n:govoplan-access.function_role_mapping_help.0cf9996a</p>
|
<p className="muted small-note">i18n:govoplan-access.function_role_mapping_help.0cf9996a</p>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useMemo, useRef, useState } from "react";
|
import { useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { Pencil, Plus, Search, Trash2 } from "lucide-react";
|
import { Pencil, Plus, Search, Trash2 } from "lucide-react";
|
||||||
import type { ApiSettings, AuthInfo } from "@govoplan/core-webui";
|
import type { FormGrid, ApiSettings, AuthInfo } from "@govoplan/core-webui";
|
||||||
import { createGroup, fetchGroupsDelta, fetchRolesDelta, fetchUsersDelta, updateGroup, type GroupSummary, type RoleSummary, type UserAdminItem } from "../../api/admin";
|
import { createGroup, fetchGroupsDelta, fetchRolesDelta, fetchUsersDelta, updateGroup, type GroupSummary, type RoleSummary, type UserAdminItem } from "../../api/admin";
|
||||||
import { Button } from "@govoplan/core-webui";
|
import { Button } from "@govoplan/core-webui";
|
||||||
import { DataGrid, type DataGridColumn } from "@govoplan/core-webui";
|
import { DataGrid, type DataGridColumn } from "@govoplan/core-webui";
|
||||||
@@ -149,14 +149,14 @@ export default function GroupsPanel({ settings, auth, canDefine, canManageMember
|
|||||||
<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} 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></>}>
|
<Dialog variant="administration" size="wide" open={editing !== null} title={editing === "new" ? "i18n:govoplan-access.create_group.5a0b1c17" : "i18n:govoplan-access.edit_group.edb57d8e"} onClose={() => !busy && setEditing(null)} className="" 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">
|
<FormGrid columns={2} gap="small" collapseAt="workspace" className="">
|
||||||
<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>
|
||||||
<FormField label="i18n:govoplan-access.slug.094da9b9"><input value={draft.slug} disabled={editing !== "new" || !canDefine} onChange={(event) => setDraft({ ...draft, slug: event.target.value })} /></FormField>
|
<FormField label="i18n:govoplan-access.slug.094da9b9"><input value={draft.slug} disabled={editing !== "new" || !canDefine} onChange={(event) => setDraft({ ...draft, slug: event.target.value })} /></FormField>
|
||||||
<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.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>
|
<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>
|
||||||
</div>
|
</FormGrid>
|
||||||
<div className="admin-assignment-grid">
|
<div className="admin-assignment-grid">
|
||||||
<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.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><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>
|
||||||
@@ -164,7 +164,7 @@ export default function GroupsPanel({ settings, auth, canDefine, canManageMember
|
|||||||
<p className="muted small-note">i18n:govoplan-access.the_backend_evaluates_the_resulting_access_graph.f318a7dc</p>
|
<p className="muted small-note">i18n:govoplan-access.the_backend_evaluates_the_resulting_access_graph.f318a7dc</p>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<Dialog open={Boolean(viewing)} title="i18n:govoplan-access.group_details.df844ae3" onClose={() => setViewing(null)} className="admin-dialog admin-dialog-wide" footer={<Button onClick={() => setViewing(null)}>i18n:govoplan-access.close.bbfa773e</Button>}>
|
<Dialog variant="administration" size="wide" open={Boolean(viewing)} title="i18n:govoplan-access.group_details.df844ae3" onClose={() => setViewing(null)} className="" footer={<Button onClick={() => setViewing(null)}>i18n:govoplan-access.close.bbfa773e</Button>}>
|
||||||
{viewing && <dl className="admin-details-grid">
|
{viewing && <dl className="admin-details-grid">
|
||||||
<div><dt>i18n:govoplan-access.group.171a0606</dt><dd>{viewing.name}</dd></div><div><dt>i18n:govoplan-access.slug.094da9b9</dt><dd>{viewing.slug}</dd></div>
|
<div><dt>i18n:govoplan-access.group.171a0606</dt><dd>{viewing.name}</dd></div><div><dt>i18n:govoplan-access.slug.094da9b9</dt><dd>{viewing.slug}</dd></div>
|
||||||
<div><dt>i18n:govoplan-access.status.bae7d5be</dt><dd>{viewing.is_active ? "i18n:govoplan-access.active.a733b809" : "i18n:govoplan-access.inactive.09af574c"}</dd></div><div><dt>i18n:govoplan-access.management.63cecca6</dt><dd>{viewing.system_template_id ? i18nMessage("i18n:govoplan-access.system_managed_value.eb564eb1", { value0: viewing.system_required ? "i18n:govoplan-access.required.2e5396fd" : "i18n:govoplan-access.available.ce372771" }) : "i18n:govoplan-access.tenant_managed.843eed93"}</dd></div>
|
<div><dt>i18n:govoplan-access.status.bae7d5be</dt><dd>{viewing.is_active ? "i18n:govoplan-access.active.a733b809" : "i18n:govoplan-access.inactive.09af574c"}</dd></div><div><dt>i18n:govoplan-access.management.63cecca6</dt><dd>{viewing.system_template_id ? i18nMessage("i18n:govoplan-access.system_managed_value.eb564eb1", { value0: viewing.system_required ? "i18n:govoplan-access.required.2e5396fd" : "i18n:govoplan-access.available.ce372771" }) : "i18n:govoplan-access.tenant_managed.843eed93"}</dd></div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useMemo, useRef, useState } from "react";
|
import { useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { Pencil, Plus, Search, Trash2 } from "lucide-react";
|
import { Pencil, Plus, Search, Trash2 } from "lucide-react";
|
||||||
import type { ApiSettings, AuthInfo } from "@govoplan/core-webui";
|
import type { FormGrid, ApiSettings, AuthInfo } from "@govoplan/core-webui";
|
||||||
import { createRole, deleteRole, fetchPermissionCatalog, fetchRolesDelta, updateRole, type PermissionItem, type RoleSummary } from "../../api/admin";
|
import { createRole, deleteRole, fetchPermissionCatalog, fetchRolesDelta, updateRole, type PermissionItem, type RoleSummary } from "../../api/admin";
|
||||||
import { Button } from "@govoplan/core-webui";
|
import { Button } from "@govoplan/core-webui";
|
||||||
import { DataGrid, type DataGridColumn } from "@govoplan/core-webui";
|
import { DataGrid, type DataGridColumn } from "@govoplan/core-webui";
|
||||||
@@ -138,20 +138,20 @@ export default function RolesPanel({ settings, auth, canDefine, onAuthRefresh }:
|
|||||||
<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} 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></>}>
|
<Dialog variant="administration" size="wide" open={editing !== null} title={editing === "new" ? "i18n:govoplan-access.create_role.db859bad" : "i18n:govoplan-access.edit_role.61dd63e9"} onClose={() => !busy && setEditing(null)} className="" 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">
|
<FormGrid columns={2} gap="small" collapseAt="workspace" className="">
|
||||||
<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>
|
||||||
<FormField label="i18n:govoplan-access.description.55f8ebc8"><textarea rows={3} value={draft.description} onChange={(event) => setDraft({ ...draft, description: event.target.value })} /></FormField>
|
<FormField label="i18n:govoplan-access.description.55f8ebc8"><textarea rows={3} value={draft.description} onChange={(event) => setDraft({ ...draft, description: event.target.value })} /></FormField>
|
||||||
{editing !== "new" && <FormField label="i18n:govoplan-access.assignable.a88debc5"><select value={draft.isAssignable ? "yes" : "no"} onChange={(event) => setDraft({ ...draft, isAssignable: event.target.value === "yes" })}><option value="yes">i18n:govoplan-access.yes.5397e058</option><option value="no">i18n:govoplan-access.no.816c52fd</option></select></FormField>}
|
{editing !== "new" && <FormField label="i18n:govoplan-access.assignable.a88debc5"><select value={draft.isAssignable ? "yes" : "no"} onChange={(event) => setDraft({ ...draft, isAssignable: event.target.value === "yes" })}><option value="yes">i18n:govoplan-access.yes.5397e058</option><option value="no">i18n:govoplan-access.no.816c52fd</option></select></FormField>}
|
||||||
</div>
|
</FormGrid>
|
||||||
<div className="admin-permission-groups">{permissionGroups.map(([category, items]) => {
|
<div className="admin-permission-groups">{permissionGroups.map(([category, items]) => {
|
||||||
const scopes = items.map((permission) => permission.scope);
|
const scopes = items.map((permission) => permission.scope);
|
||||||
return <fieldset key={category} className="admin-permission-group"><legend>{category}</legend><AdminSelectionList options={items.map((permission) => ({ id: permission.scope, label: permission.label, description: <>{permission.description}<code>{permission.scope}</code></> }))} selected={draft.permissions.filter((scope) => scopes.includes(scope))} onChange={(selected) => setPermissionGroup(scopes, selected)} /></fieldset>;
|
return <fieldset key={category} className="admin-permission-group"><legend>{category}</legend><AdminSelectionList options={items.map((permission) => ({ id: permission.scope, label: permission.label, description: <>{permission.description}<code>{permission.scope}</code></> }))} selected={draft.permissions.filter((scope) => scopes.includes(scope))} onChange={(selected) => setPermissionGroup(scopes, selected)} /></fieldset>;
|
||||||
})}</div>
|
})}</div>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<Dialog open={Boolean(viewing)} title="i18n:govoplan-access.role_details.a16b5d9f" onClose={() => setViewing(null)} className="admin-dialog admin-dialog-wide" footer={<Button onClick={() => setViewing(null)}>i18n:govoplan-access.close.bbfa773e</Button>}>
|
<Dialog variant="administration" size="wide" open={Boolean(viewing)} title="i18n:govoplan-access.role_details.a16b5d9f" onClose={() => setViewing(null)} className="" footer={<Button onClick={() => setViewing(null)}>i18n:govoplan-access.close.bbfa773e</Button>}>
|
||||||
{viewing && <><dl className="admin-details-grid">
|
{viewing && <><dl className="admin-details-grid">
|
||||||
<div><dt>i18n:govoplan-access.role.c3f104d1</dt><dd>{viewing.name}</dd></div><div><dt>i18n:govoplan-access.slug.094da9b9</dt><dd>{viewing.slug}</dd></div>
|
<div><dt>i18n:govoplan-access.role.c3f104d1</dt><dd>{viewing.name}</dd></div><div><dt>i18n:govoplan-access.slug.094da9b9</dt><dd>{viewing.slug}</dd></div>
|
||||||
<div><dt>i18n:govoplan-access.type.3deb7456</dt><dd>{viewing.is_builtin ? "i18n:govoplan-access.built_in.20f409cc" : viewing.system_template_id ? i18nMessage("i18n:govoplan-access.system_managed_value.eb564eb1", { value0: viewing.system_required ? "i18n:govoplan-access.required.2e5396fd" : "i18n:govoplan-access.available.ce372771" }) : "i18n:govoplan-access.tenant_custom.4307081e"}</dd></div><div><dt>i18n:govoplan-access.assignable.a88debc5</dt><dd>{viewing.is_assignable ? "i18n:govoplan-access.yes.5397e058" : "i18n:govoplan-access.no.816c52fd"}</dd></div>
|
<div><dt>i18n:govoplan-access.type.3deb7456</dt><dd>{viewing.is_builtin ? "i18n:govoplan-access.built_in.20f409cc" : viewing.system_template_id ? i18nMessage("i18n:govoplan-access.system_managed_value.eb564eb1", { value0: viewing.system_required ? "i18n:govoplan-access.required.2e5396fd" : "i18n:govoplan-access.available.ce372771" }) : "i18n:govoplan-access.tenant_custom.4307081e"}</dd></div><div><dt>i18n:govoplan-access.assignable.a88debc5</dt><dd>{viewing.is_assignable ? "i18n:govoplan-access.yes.5397e058" : "i18n:govoplan-access.no.816c52fd"}</dd></div>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
ShieldOff,
|
ShieldOff,
|
||||||
Trash2
|
Trash2
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import {
|
import { FormGrid, ActionToolbar,
|
||||||
AdminIconButton,
|
AdminIconButton,
|
||||||
AdminPageLayout,
|
AdminPageLayout,
|
||||||
AdminSelectionList,
|
AdminSelectionList,
|
||||||
@@ -455,28 +455,28 @@ export default function ServiceAccountsPanel({
|
|||||||
</div>
|
</div>
|
||||||
</AdminPageLayout>
|
</AdminPageLayout>
|
||||||
|
|
||||||
<Dialog
|
<Dialog variant="administration" size="wide"
|
||||||
open={Boolean(accountEditor)}
|
open={Boolean(accountEditor)}
|
||||||
title={accountEditor === "create" ? "Create service account" : "Edit service account"}
|
title={accountEditor === "create" ? "Create service account" : "Edit service account"}
|
||||||
onClose={() => !busy && setAccountEditor(null)}
|
onClose={() => !busy && setAccountEditor(null)}
|
||||||
className="admin-dialog admin-dialog-wide"
|
className=""
|
||||||
footer={<><Button onClick={() => setAccountEditor(null)} disabled={busy}>Cancel</Button><Button variant="primary" onClick={() => void saveAccount()} disabled={!canWrite || busy || !accountDraft.name.trim()}>{busy ? "Saving..." : "Save"}</Button></>}
|
footer={<><Button onClick={() => setAccountEditor(null)} disabled={busy}>Cancel</Button><Button variant="primary" onClick={() => void saveAccount()} disabled={!canWrite || busy || !accountDraft.name.trim()}>{busy ? "Saving..." : "Save"}</Button></>}
|
||||||
>
|
>
|
||||||
<div className="admin-form-grid two-columns">
|
<FormGrid columns={2} gap="small" collapseAt="workspace" className="">
|
||||||
<FormField label="Name"><input value={accountDraft.name} onChange={(event) => setAccountDraft({ ...accountDraft, name: event.target.value })} /></FormField>
|
<FormField label="Name"><input value={accountDraft.name} onChange={(event) => setAccountDraft({ ...accountDraft, name: event.target.value })} /></FormField>
|
||||||
<FormField label="Description"><input value={accountDraft.description} onChange={(event) => setAccountDraft({ ...accountDraft, description: event.target.value })} /></FormField>
|
<FormField label="Description"><input value={accountDraft.description} onChange={(event) => setAccountDraft({ ...accountDraft, description: event.target.value })} /></FormField>
|
||||||
</div>
|
</FormGrid>
|
||||||
<div className="form-field">
|
<div className="form-field">
|
||||||
<span className="form-label">Scope ceiling</span>
|
<span className="form-label">Scope ceiling</span>
|
||||||
<AdminSelectionList options={grantablePermissions.map((permission) => ({ id: permission.scope, label: permission.label, description: `${permission.scope} - ${permission.description}` }))} selected={accountDraft.scopes} onChange={(scopes) => setAccountDraft({ ...accountDraft, scopes })} emptyText="No tenant scopes can be delegated by your current account." />
|
<AdminSelectionList options={grantablePermissions.map((permission) => ({ id: permission.scope, label: permission.label, description: `${permission.scope} - ${permission.description}` }))} selected={accountDraft.scopes} onChange={(scopes) => setAccountDraft({ ...accountDraft, scopes })} emptyText="No tenant scopes can be delegated by your current account." />
|
||||||
</div>
|
</div>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<Dialog
|
<Dialog variant="administration" size="wide"
|
||||||
open={Boolean(managing)}
|
open={Boolean(managing)}
|
||||||
title={managing?.name ?? "Service account"}
|
title={managing?.name ?? "Service account"}
|
||||||
onClose={() => !busy && setManaging(null)}
|
onClose={() => !busy && setManaging(null)}
|
||||||
className="admin-dialog admin-dialog-wide"
|
className=""
|
||||||
footer={<Button onClick={() => setManaging(null)} disabled={busy}>Close</Button>}
|
footer={<Button onClick={() => setManaging(null)} disabled={busy}>Close</Button>}
|
||||||
>
|
>
|
||||||
{managing && <>
|
{managing && <>
|
||||||
@@ -486,15 +486,15 @@ export default function ServiceAccountsPanel({
|
|||||||
<MetricCard label="Scope ceiling" value={managing.scope_ceiling.length} />
|
<MetricCard label="Scope ceiling" value={managing.scope_ceiling.length} />
|
||||||
<MetricCard label="Revision" value={managing.revision} />
|
<MetricCard label="Revision" value={managing.revision} />
|
||||||
</div>
|
</div>
|
||||||
<div className="admin-toolbar-row">
|
<ActionToolbar className="admin-toolbar-row">
|
||||||
<Button onClick={openEditAccount} disabled={!canWrite || busy}><Pencil aria-hidden="true" /> Edit</Button>
|
<Button onClick={openEditAccount} disabled={!canWrite || busy}><Pencil aria-hidden="true" /> Edit</Button>
|
||||||
<Button onClick={() => void setActive(!managing.is_active)} disabled={!canWrite || busy}>{managing.is_active ? <ShieldOff aria-hidden="true" /> : <RefreshCw aria-hidden="true" />} {managing.is_active ? "Deactivate" : "Activate"}</Button>
|
<Button onClick={() => void setActive(!managing.is_active)} disabled={!canWrite || busy}>{managing.is_active ? <ShieldOff aria-hidden="true" /> : <RefreshCw aria-hidden="true" />} {managing.is_active ? "Deactivate" : "Activate"}</Button>
|
||||||
<Button variant="danger" onClick={() => setRetiring(true)} disabled={!canWrite || busy || !managing.is_active}><Trash2 aria-hidden="true" /> Retire</Button>
|
<Button variant="danger" onClick={() => setRetiring(true)} disabled={!canWrite || busy || !managing.is_active}><Trash2 aria-hidden="true" /> Retire</Button>
|
||||||
<AdminIconButton label="Create credential" icon={<KeyRound />} variant="primary" onClick={() => openCredentialEditor("create")} disabled={!canWrite || !managing.is_active} disabledReason={!canWrite ? ACCESS_INTERFACE_I18N.writePermissionRequired : !managing.is_active ? "Activate the service account first." : undefined} />
|
<AdminIconButton label="Create credential" icon={<KeyRound />} variant="primary" onClick={() => openCredentialEditor("create")} disabled={!canWrite || !managing.is_active} disabledReason={!canWrite ? ACCESS_INTERFACE_I18N.writePermissionRequired : !managing.is_active ? "Activate the service account first." : undefined} />
|
||||||
</div>
|
</ActionToolbar>
|
||||||
<div className="admin-toolbar-row">
|
<ActionToolbar className="admin-toolbar-row">
|
||||||
<ToggleSwitch label="Show revoked credentials" checked={showRevoked} onChange={setShowRevoked} />
|
<ToggleSwitch label="Show revoked credentials" checked={showRevoked} onChange={setShowRevoked} />
|
||||||
</div>
|
</ActionToolbar>
|
||||||
<div className="admin-table-surface">
|
<div className="admin-table-surface">
|
||||||
<DataGrid id="admin-service-account-credentials-v1" rows={visibleCredentials} columns={credentialColumns} initialFit="container" getRowKey={(row) => row.id} emptyText="No credentials found." />
|
<DataGrid id="admin-service-account-credentials-v1" rows={visibleCredentials} columns={credentialColumns} initialFit="container" getRowKey={(row) => row.id} emptyText="No credentials found." />
|
||||||
</div>
|
</div>
|
||||||
@@ -502,25 +502,25 @@ export default function ServiceAccountsPanel({
|
|||||||
</>}
|
</>}
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<Dialog
|
<Dialog variant="administration" size="wide"
|
||||||
open={Boolean(credentialEditor)}
|
open={Boolean(credentialEditor)}
|
||||||
title={credentialEditor?.mode === "rotate" ? "Rotate credential" : "Create credential"}
|
title={credentialEditor?.mode === "rotate" ? "Rotate credential" : "Create credential"}
|
||||||
onClose={() => !busy && setCredentialEditor(null)}
|
onClose={() => !busy && setCredentialEditor(null)}
|
||||||
className="admin-dialog admin-dialog-wide"
|
className=""
|
||||||
footer={<><Button onClick={() => setCredentialEditor(null)} disabled={busy}>Cancel</Button><Button variant="primary" onClick={() => void saveCredential()} disabled={!canWrite || busy || !credentialDraft.name.trim() || credentialDraft.scopes.length === 0}>{busy ? "Saving..." : credentialEditor?.mode === "rotate" ? "Rotate" : "Create"}</Button></>}
|
footer={<><Button onClick={() => setCredentialEditor(null)} disabled={busy}>Cancel</Button><Button variant="primary" onClick={() => void saveCredential()} disabled={!canWrite || busy || !credentialDraft.name.trim() || credentialDraft.scopes.length === 0}>{busy ? "Saving..." : credentialEditor?.mode === "rotate" ? "Rotate" : "Create"}</Button></>}
|
||||||
>
|
>
|
||||||
{credentialEditor?.mode === "rotate" && <p className="muted small-note">Rotation creates a new secret and revokes the previous credential in the same transaction.</p>}
|
{credentialEditor?.mode === "rotate" && <p className="muted small-note">Rotation creates a new secret and revokes the previous credential in the same transaction.</p>}
|
||||||
<div className="admin-form-grid two-columns">
|
<FormGrid columns={2} gap="small" collapseAt="workspace" className="">
|
||||||
<FormField label="Name"><input value={credentialDraft.name} onChange={(event) => setCredentialDraft({ ...credentialDraft, name: event.target.value })} /></FormField>
|
<FormField label="Name"><input value={credentialDraft.name} onChange={(event) => setCredentialDraft({ ...credentialDraft, name: event.target.value })} /></FormField>
|
||||||
<FormField label="Expiry"><DateTimeField value={credentialDraft.expiresAt} onChange={(value) => setCredentialDraft({ ...credentialDraft, expiresAt: value })} /></FormField>
|
<FormField label="Expiry"><DateTimeField value={credentialDraft.expiresAt} onChange={(value) => setCredentialDraft({ ...credentialDraft, expiresAt: value })} /></FormField>
|
||||||
</div>
|
</FormGrid>
|
||||||
<div className="form-field">
|
<div className="form-field">
|
||||||
<span className="form-label">Credential scopes</span>
|
<span className="form-label">Credential scopes</span>
|
||||||
<AdminSelectionList options={credentialPermissions.map((permission) => ({ id: permission.scope, label: permission.label, description: `${permission.scope} - ${permission.description}` }))} selected={credentialDraft.scopes} onChange={(scopes) => setCredentialDraft({ ...credentialDraft, scopes })} emptyText="The service account has no credential scopes available." />
|
<AdminSelectionList options={credentialPermissions.map((permission) => ({ id: permission.scope, label: permission.label, description: `${permission.scope} - ${permission.description}` }))} selected={credentialDraft.scopes} onChange={(scopes) => setCredentialDraft({ ...credentialDraft, scopes })} emptyText="The service account has no credential scopes available." />
|
||||||
</div>
|
</div>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<Dialog open={Boolean(secret)} title="Service-account secret" onClose={() => setSecret(null)} className="admin-dialog" footer={<Button variant="primary" onClick={() => setSecret(null)}>I have recorded it</Button>}>
|
<Dialog variant="administration" size="large" open={Boolean(secret)} title="Service-account secret" onClose={() => setSecret(null)} className="" footer={<Button variant="primary" onClick={() => setSecret(null)}>I have recorded it</Button>}>
|
||||||
{secret && <><p>The secret for <strong>{secret.name}</strong> is shown once.</p><code className="admin-secret">{secret.value}</code><p className="muted small-note">Store it in a secret manager. GovOPlaN retains only a one-way hash and the visible prefix.</p></>}
|
{secret && <><p>The secret for <strong>{secret.name}</strong> is shown once.</p><code className="admin-secret">{secret.value}</code><p className="muted small-note">Store it in a secret manager. GovOPlaN retains only a one-way hash and the visible prefix.</p></>}
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useMemo, useRef, useState } from "react";
|
import { useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { Pencil, Plus, Search, Trash2 } from "lucide-react";
|
import { Pencil, Plus, Search, Trash2 } from "lucide-react";
|
||||||
import type { ApiSettings } from "@govoplan/core-webui";
|
import type { FormGrid, ApiSettings } from "@govoplan/core-webui";
|
||||||
import {
|
import {
|
||||||
createSystemRole,
|
createSystemRole,
|
||||||
deleteSystemRole,
|
deleteSystemRole,
|
||||||
@@ -248,19 +248,19 @@ export default function SystemRolesPanel({
|
|||||||
</div>
|
</div>
|
||||||
</AdminPageLayout>
|
</AdminPageLayout>
|
||||||
|
|
||||||
<Dialog
|
<Dialog variant="administration" size="wide"
|
||||||
open={editing !== null}
|
open={editing !== null}
|
||||||
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=""
|
||||||
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></>}>
|
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">
|
<FormGrid columns={2} gap="small" collapseAt="workspace" className="">
|
||||||
<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>
|
||||||
<FormField label="i18n:govoplan-access.assignable.a88debc5"><select value={draft.isAssignable ? "yes" : "no"} onChange={(event) => setDraft({ ...draft, isAssignable: event.target.value === "yes" })}><option value="yes">i18n:govoplan-access.yes.5397e058</option><option value="no">i18n:govoplan-access.no.816c52fd</option></select></FormField>
|
<FormField label="i18n:govoplan-access.assignable.a88debc5"><select value={draft.isAssignable ? "yes" : "no"} onChange={(event) => setDraft({ ...draft, isAssignable: event.target.value === "yes" })}><option value="yes">i18n:govoplan-access.yes.5397e058</option><option value="no">i18n:govoplan-access.no.816c52fd</option></select></FormField>
|
||||||
<FormField label="i18n:govoplan-access.description.55f8ebc8"><textarea rows={3} value={draft.description} onChange={(event) => setDraft({ ...draft, description: event.target.value })} /></FormField>
|
<FormField label="i18n:govoplan-access.description.55f8ebc8"><textarea rows={3} value={draft.description} onChange={(event) => setDraft({ ...draft, description: event.target.value })} /></FormField>
|
||||||
</div>
|
</FormGrid>
|
||||||
<div className="form-field">
|
<div className="form-field">
|
||||||
<span className="form-label">i18n:govoplan-access.system_permissions.53ff0ab2</span>
|
<span className="form-label">i18n:govoplan-access.system_permissions.53ff0ab2</span>
|
||||||
<AdminSelectionList
|
<AdminSelectionList
|
||||||
@@ -272,7 +272,7 @@ export default function SystemRolesPanel({
|
|||||||
</div>
|
</div>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<Dialog open={Boolean(viewing)} title={viewing?.name || "i18n:govoplan-access.system_role_details.3d6a8f15"} onClose={() => setViewing(null)} className="admin-dialog admin-dialog-wide" footer={<Button onClick={() => setViewing(null)}>i18n:govoplan-access.close.bbfa773e</Button>}>
|
<Dialog variant="administration" size="wide" open={Boolean(viewing)} title={viewing?.name || "i18n:govoplan-access.system_role_details.3d6a8f15"} onClose={() => setViewing(null)} className="" footer={<Button onClick={() => setViewing(null)}>i18n:govoplan-access.close.bbfa773e</Button>}>
|
||||||
{viewing && <dl className="admin-details-grid"><div><dt>i18n:govoplan-access.slug.094da9b9</dt><dd>{viewing.slug}</dd></div><div><dt>i18n:govoplan-access.protected.28531336</dt><dd>{viewing.slug === "system_owner" ? "i18n:govoplan-access.yes.5397e058" : "i18n:govoplan-access.no.816c52fd"}</dd></div><div><dt>i18n:govoplan-access.assignable.a88debc5</dt><dd>{viewing.is_assignable ? "i18n:govoplan-access.yes.5397e058" : "i18n:govoplan-access.no.816c52fd"}</dd></div><div><dt>i18n:govoplan-access.account_assignments.f5a91f2a</dt><dd>{viewing.user_assignments}</dd></div><div><dt>i18n:govoplan-access.description.55f8ebc8</dt><dd>{viewing.description || "—"}</dd></div><div><dt>i18n:govoplan-access.effective_permissions.17c0fe8a</dt><dd>{viewing.effective_permission_count}</dd></div><div><dt>i18n:govoplan-access.assigned_scopes.c7b09b12</dt><dd>{viewing.permissions.length ? joinLabels(viewing.permissions.map((name) => ({ name }))) : "—"}</dd></div></dl>}
|
{viewing && <dl className="admin-details-grid"><div><dt>i18n:govoplan-access.slug.094da9b9</dt><dd>{viewing.slug}</dd></div><div><dt>i18n:govoplan-access.protected.28531336</dt><dd>{viewing.slug === "system_owner" ? "i18n:govoplan-access.yes.5397e058" : "i18n:govoplan-access.no.816c52fd"}</dd></div><div><dt>i18n:govoplan-access.assignable.a88debc5</dt><dd>{viewing.is_assignable ? "i18n:govoplan-access.yes.5397e058" : "i18n:govoplan-access.no.816c52fd"}</dd></div><div><dt>i18n:govoplan-access.account_assignments.f5a91f2a</dt><dd>{viewing.user_assignments}</dd></div><div><dt>i18n:govoplan-access.description.55f8ebc8</dt><dd>{viewing.description || "—"}</dd></div><div><dt>i18n:govoplan-access.effective_permissions.17c0fe8a</dt><dd>{viewing.effective_permission_count}</dd></div><div><dt>i18n:govoplan-access.assigned_scopes.c7b09b12</dt><dd>{viewing.permissions.length ? joinLabels(viewing.permissions.map((name) => ({ name }))) : "—"}</dd></div></dl>}
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useMemo, useRef, useState } from "react";
|
import { useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { Search, Pencil, Plus, Trash2 } from "lucide-react";
|
import { Search, Pencil, Plus, Trash2 } from "lucide-react";
|
||||||
import type { ApiSettings } from "@govoplan/core-webui";
|
import type { FormGrid, ApiSettings } from "@govoplan/core-webui";
|
||||||
import { Button } from "@govoplan/core-webui";
|
import { Button } from "@govoplan/core-webui";
|
||||||
import { ConfirmDialog } from "@govoplan/core-webui";
|
import { ConfirmDialog } from "@govoplan/core-webui";
|
||||||
import { DataGrid, type DataGridColumn } from "@govoplan/core-webui";
|
import { DataGrid, type DataGridColumn } from "@govoplan/core-webui";
|
||||||
@@ -233,8 +233,8 @@ export default function SystemUsersPanel({
|
|||||||
<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} 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></>}>
|
<Dialog variant="administration" size="wide" 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="" 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">
|
<FormGrid columns={2} gap="small" collapseAt="workspace" className="">
|
||||||
<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>
|
||||||
{editing === "new" &&
|
{editing === "new" &&
|
||||||
@@ -248,7 +248,7 @@ export default function SystemUsersPanel({
|
|||||||
</FormField>
|
</FormField>
|
||||||
}
|
}
|
||||||
<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>
|
<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>
|
||||||
</div>
|
</FormGrid>
|
||||||
<div className="admin-assignment-grid">
|
<div className="admin-assignment-grid">
|
||||||
<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.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><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>
|
||||||
@@ -257,11 +257,11 @@ export default function SystemUsersPanel({
|
|||||||
<p className="muted small-note">i18n:govoplan-access.removing_a_tenant_checkbox_suspends_that_members.7c6df77d</p>
|
<p className="muted small-note">i18n:govoplan-access.removing_a_tenant_checkbox_suspends_that_members.7c6df77d</p>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<Dialog open={Boolean(viewing)} title="i18n:govoplan-access.global_account_details.0a0cf240" onClose={() => setViewing(null)} className="admin-dialog admin-dialog-wide" footer={<Button onClick={() => setViewing(null)}>i18n:govoplan-access.close.bbfa773e</Button>}>
|
<Dialog variant="administration" size="wide" open={Boolean(viewing)} title="i18n:govoplan-access.global_account_details.0a0cf240" onClose={() => setViewing(null)} className="" footer={<Button onClick={() => setViewing(null)}>i18n:govoplan-access.close.bbfa773e</Button>}>
|
||||||
{viewing && <dl className="admin-details-grid"><div><dt>i18n:govoplan-access.account.85dfa32c</dt><dd>{viewing.email}</dd></div><div><dt>i18n:govoplan-access.display_name.c7874aaa</dt><dd>{viewing.display_name || "—"}</dd></div><div><dt>i18n:govoplan-access.status.bae7d5be</dt><dd>{viewing.is_active ? "i18n:govoplan-access.active.a733b809" : "i18n:govoplan-access.inactive.09af574c"}</dd></div><div><dt>i18n:govoplan-access.last_login.43dab84f</dt><dd>{formatDateTime(viewing.last_login_at)}</dd></div><div><dt>i18n:govoplan-access.system_roles.a9461aa6</dt><dd>{joinLabels(viewing.roles)}</dd></div><div><dt>i18n:govoplan-access.tenants.1f7ae776</dt><dd>{viewing.memberships.map((item) => item.tenant_name).join(", ") || "—"}</dd></div></dl>}
|
{viewing && <dl className="admin-details-grid"><div><dt>i18n:govoplan-access.account.85dfa32c</dt><dd>{viewing.email}</dd></div><div><dt>i18n:govoplan-access.display_name.c7874aaa</dt><dd>{viewing.display_name || "—"}</dd></div><div><dt>i18n:govoplan-access.status.bae7d5be</dt><dd>{viewing.is_active ? "i18n:govoplan-access.active.a733b809" : "i18n:govoplan-access.inactive.09af574c"}</dd></div><div><dt>i18n:govoplan-access.last_login.43dab84f</dt><dd>{formatDateTime(viewing.last_login_at)}</dd></div><div><dt>i18n:govoplan-access.system_roles.a9461aa6</dt><dd>{joinLabels(viewing.roles)}</dd></div><div><dt>i18n:govoplan-access.tenants.1f7ae776</dt><dd>{viewing.memberships.map((item) => item.tenant_name).join(", ") || "—"}</dd></div></dl>}
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<Dialog open={Boolean(temporaryPassword)} title="i18n:govoplan-access.temporary_password.62d60628" onClose={() => setTemporaryPassword(null)} className="admin-dialog" footer={<Button variant="primary" onClick={() => setTemporaryPassword(null)}>i18n:govoplan-access.i_have_recorded_it.7522da18</Button>}>
|
<Dialog variant="administration" size="large" open={Boolean(temporaryPassword)} title="i18n:govoplan-access.temporary_password.62d60628" onClose={() => setTemporaryPassword(null)} className="" footer={<Button variant="primary" onClick={() => setTemporaryPassword(null)}>i18n:govoplan-access.i_have_recorded_it.7522da18</Button>}>
|
||||||
{temporaryPassword && <><p>i18n:govoplan-access.this_is_shown_once_for.b0f0f526 <strong>{temporaryPassword.email}</strong>.</p><code className="admin-secret">{temporaryPassword.value}</code></>}
|
{temporaryPassword && <><p>i18n:govoplan-access.this_is_shown_once_for.b0f0f526 <strong>{temporaryPassword.email}</strong>.</p><code className="admin-secret">{temporaryPassword.value}</code></>}
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useMemo, useRef, useState } from "react";
|
import { useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { KeyRound, Pencil, Plus, Search, Trash2 } from "lucide-react";
|
import { KeyRound, Pencil, Plus, Search, Trash2 } from "lucide-react";
|
||||||
import type { ApiSettings, AuthInfo } from "@govoplan/core-webui";
|
import type { FormGrid, ApiSettings, AuthInfo } from "@govoplan/core-webui";
|
||||||
import { createUser, fetchGroupsDelta, fetchRolesDelta, fetchUserAccessExplanation, fetchUsersDelta, updateUser, type AccessRoleSourceItem, type FunctionFactExplanationItem, type GroupSummary, type RoleSummary, type UserAccessExplanationResponse, type UserAdminItem } from "../../api/admin";
|
import { createUser, fetchGroupsDelta, fetchRolesDelta, fetchUserAccessExplanation, fetchUsersDelta, updateUser, type AccessRoleSourceItem, type FunctionFactExplanationItem, type GroupSummary, type RoleSummary, type UserAccessExplanationResponse, type UserAdminItem } from "../../api/admin";
|
||||||
import { Button } from "@govoplan/core-webui";
|
import { Button } from "@govoplan/core-webui";
|
||||||
import { DataGrid, type DataGridColumn } from "@govoplan/core-webui";
|
import { DataGrid, type DataGridColumn } from "@govoplan/core-webui";
|
||||||
@@ -206,8 +206,8 @@ export default function UsersPanel({ settings, auth, canCreate, canUpdate, canSu
|
|||||||
<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} 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></>}>
|
<Dialog variant="administration" size="wide" 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="" 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">
|
<FormGrid columns={2} gap="small" collapseAt="workspace" className="">
|
||||||
<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>
|
||||||
{editing === "new" &&
|
{editing === "new" &&
|
||||||
@@ -221,7 +221,7 @@ export default function UsersPanel({ settings, auth, canCreate, canUpdate, canSu
|
|||||||
</FormField>
|
</FormField>
|
||||||
}
|
}
|
||||||
<FormField label="i18n:govoplan-access.membership_status.b77fc732"><select value={draft.isActive ? "active" : "inactive"} disabled={Boolean(editing && editing !== "new" && (!canSuspend || editing.is_last_active_owner))} onChange={(event) => setDraft({ ...draft, isActive: event.target.value === "active" })}><option value="active">i18n:govoplan-access.active.a733b809</option><option value="inactive">i18n:govoplan-access.inactive.09af574c</option></select></FormField>
|
<FormField label="i18n:govoplan-access.membership_status.b77fc732"><select value={draft.isActive ? "active" : "inactive"} disabled={Boolean(editing && editing !== "new" && (!canSuspend || editing.is_last_active_owner))} onChange={(event) => setDraft({ ...draft, isActive: event.target.value === "active" })}><option value="active">i18n:govoplan-access.active.a733b809</option><option value="inactive">i18n:govoplan-access.inactive.09af574c</option></select></FormField>
|
||||||
</div>
|
</FormGrid>
|
||||||
{editing === "new" && <ToggleSwitch label="i18n:govoplan-access.require_password_change_when_account_settings_ar.69bce7a3" checked={draft.passwordResetRequired} onChange={(passwordResetRequired) => setDraft({ ...draft, passwordResetRequired })} />}
|
{editing === "new" && <ToggleSwitch label="i18n:govoplan-access.require_password_change_when_account_settings_ar.69bce7a3" checked={draft.passwordResetRequired} 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>}
|
{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">
|
<div className="admin-assignment-grid">
|
||||||
@@ -231,7 +231,7 @@ export default function UsersPanel({ settings, auth, canCreate, canUpdate, canSu
|
|||||||
<p className="muted small-note">i18n:govoplan-access.group_roles_and_direct_roles_are_combined_the_ba.a43c2f16</p>
|
<p className="muted small-note">i18n:govoplan-access.group_roles_and_direct_roles_are_combined_the_ba.a43c2f16</p>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<Dialog open={Boolean(viewing)} title="i18n:govoplan-access.tenant_user_details.fbab9079" onClose={() => setViewing(null)} className="admin-dialog admin-dialog-wide" footer={<Button onClick={() => setViewing(null)}>i18n:govoplan-access.close.bbfa773e</Button>}>
|
<Dialog variant="administration" size="wide" open={Boolean(viewing)} title="i18n:govoplan-access.tenant_user_details.fbab9079" onClose={() => setViewing(null)} className="" footer={<Button onClick={() => setViewing(null)}>i18n:govoplan-access.close.bbfa773e</Button>}>
|
||||||
{viewing && <dl className="admin-details-grid">
|
{viewing && <dl className="admin-details-grid">
|
||||||
<div><dt>i18n:govoplan-access.user.9f8a2389</dt><dd>{viewing.display_name || viewing.email}</dd></div><div><dt>i18n:govoplan-access.email.84add5b2</dt><dd>{viewing.email}</dd></div>
|
<div><dt>i18n:govoplan-access.user.9f8a2389</dt><dd>{viewing.display_name || viewing.email}</dd></div><div><dt>i18n:govoplan-access.email.84add5b2</dt><dd>{viewing.email}</dd></div>
|
||||||
<div><dt>i18n:govoplan-access.membership.53bc9670</dt><dd>{viewing.is_active ? "i18n:govoplan-access.active.a733b809" : "i18n:govoplan-access.inactive.09af574c"}</dd></div><div><dt>i18n:govoplan-access.global_account.e1b00cf5</dt><dd>{viewing.account_is_active ? "i18n:govoplan-access.active.a733b809" : "i18n:govoplan-access.inactive.09af574c"}</dd></div>
|
<div><dt>i18n:govoplan-access.membership.53bc9670</dt><dd>{viewing.is_active ? "i18n:govoplan-access.active.a733b809" : "i18n:govoplan-access.inactive.09af574c"}</dd></div><div><dt>i18n:govoplan-access.global_account.e1b00cf5</dt><dd>{viewing.account_is_active ? "i18n:govoplan-access.active.a733b809" : "i18n:govoplan-access.inactive.09af574c"}</dd></div>
|
||||||
@@ -240,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} disabledReason={accessExplanationLoading ? ACCESS_INTERFACE_I18N.loading : undefined}>i18n:govoplan-access.close.bbfa773e</Button>}>
|
<Dialog variant="administration" size="wide" open={Boolean(explaining)} title="i18n:govoplan-access.access_explanation.75ee7f62" onClose={() => { if (!accessExplanationLoading) { setExplaining(null); setAccessExplanation(null); } }} className="" 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">
|
||||||
@@ -282,7 +282,7 @@ export default function UsersPanel({ settings, auth, canCreate, canUpdate, canSu
|
|||||||
</>}
|
</>}
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<Dialog open={Boolean(temporaryPassword)} title="i18n:govoplan-access.temporary_password.62d60628" onClose={() => setTemporaryPassword(null)} className="admin-dialog" footer={<Button variant="primary" onClick={() => setTemporaryPassword(null)}>i18n:govoplan-access.i_have_recorded_it.7522da18</Button>}>
|
<Dialog variant="administration" size="large" open={Boolean(temporaryPassword)} title="i18n:govoplan-access.temporary_password.62d60628" onClose={() => setTemporaryPassword(null)} className="" footer={<Button variant="primary" onClick={() => setTemporaryPassword(null)}>i18n:govoplan-access.i_have_recorded_it.7522da18</Button>}>
|
||||||
{temporaryPassword && <><p>i18n:govoplan-access.this_is_shown_once_for.b0f0f526 <strong>{temporaryPassword.email}</strong>.</p><code className="admin-secret">{temporaryPassword.password}</code><p className="muted small-note">i18n:govoplan-access.transmit_it_through_a_separate_secure_channel.ab892c7b</p></>}
|
{temporaryPassword && <><p>i18n:govoplan-access.this_is_shown_once_for.b0f0f526 <strong>{temporaryPassword.email}</strong>.</p><code className="admin-secret">{temporaryPassword.password}</code><p className="muted small-note">i18n:govoplan-access.transmit_it_through_a_separate_secure_channel.ab892c7b</p></>}
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user