refactor(webui): use central data grids and actions
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useMemo, useRef, useState } from "react";
|
import { useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { Check, RefreshCw } from "lucide-react";
|
import { Check, RefreshCw } from "lucide-react";
|
||||||
import type { ApiSettings } from "@govoplan/core-webui";
|
import type { ApiSettings } from "@govoplan/core-webui";
|
||||||
import { AdminPageLayout, Button, Card, StatusBadge, adminErrorMessage, formatDateTime, i18nMessage, mergeDeltaRows, useDeltaWatermarks } from "@govoplan/core-webui";
|
import { AdminPageLayout, Button, Card, DataGrid, StatusBadge, TableActionGroup, adminErrorMessage, formatDateTime, i18nMessage, mergeDeltaRows, useDeltaWatermarks, type DataGridColumn } from "@govoplan/core-webui";
|
||||||
import {
|
import {
|
||||||
approveConfigurationChangeRequest,
|
approveConfigurationChangeRequest,
|
||||||
fetchConfigurationChangesDelta,
|
fetchConfigurationChangesDelta,
|
||||||
@@ -72,6 +72,47 @@ export default function ConfigurationChangesPanel({ settings, canApprove }: {set
|
|||||||
}
|
}
|
||||||
|
|
||||||
const pending = useMemo(() => requests.filter((item) => item.status !== "applied" && item.status !== "rejected"), [requests]);
|
const pending = useMemo(() => requests.filter((item) => item.status !== "applied" && item.status !== "rejected"), [requests]);
|
||||||
|
const requestColumns: DataGridColumn<ConfigurationChangeRequest>[] = [
|
||||||
|
{
|
||||||
|
id: "setting",
|
||||||
|
header: "i18n:govoplan-admin.setting.fb449f71",
|
||||||
|
width: "minmax(220px, 1fr)",
|
||||||
|
minWidth: 180,
|
||||||
|
resizable: true,
|
||||||
|
sortable: true,
|
||||||
|
filterable: true,
|
||||||
|
value: (request) => `${request.label || request.key} ${request.key}`,
|
||||||
|
render: (request) => <div><strong>{request.label || request.key}</strong><span className="muted block">{request.key}</span></div>
|
||||||
|
},
|
||||||
|
{ id: "status", header: "i18n:govoplan-admin.status.bae7d5be", width: 150, sortable: true, filterable: true, value: (request) => request.status, render: (request) => <StatusBadge status={statusTone(request.status)} label={request.status} /> },
|
||||||
|
{ id: "requested", header: "i18n:govoplan-admin.requested.c26bf60f", width: 180, sortable: true, value: (request) => request.requested_at, render: (request) => formatDateTime(request.requested_at) },
|
||||||
|
{ id: "approvals", header: "i18n:govoplan-admin.approvals.deb9d03c", width: 110, sortable: true, value: (request) => request.approvals.length },
|
||||||
|
{ id: "target", header: "i18n:govoplan-admin.target.61ad50a9", width: "minmax(180px, .8fr)", minWidth: 160, resizable: true, filterable: true, value: (request) => targetLabel(request.target), render: (request) => targetLabel(request.target) },
|
||||||
|
{
|
||||||
|
id: "actions",
|
||||||
|
header: "i18n:govoplan-admin.action.97c89a4d",
|
||||||
|
width: 72,
|
||||||
|
sticky: "end",
|
||||||
|
align: "right",
|
||||||
|
render: (request) => <TableActionGroup actions={[{
|
||||||
|
id: "approve",
|
||||||
|
label: "i18n:govoplan-admin.approve.7b2c7f14",
|
||||||
|
icon: <Check size={16} aria-hidden="true" />,
|
||||||
|
applicable: canApprove && request.status === "pending_approval",
|
||||||
|
disabled: busyId === request.id,
|
||||||
|
onClick: () => void approve(request)
|
||||||
|
}]} />
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const historyColumns: DataGridColumn<ConfigurationChangeRecord>[] = [
|
||||||
|
{ id: "version", header: "i18n:govoplan-admin.version.2da600bf", width: 100, sortable: true, value: (record) => record.version, render: (record) => `#${record.version}` },
|
||||||
|
{ id: "setting", header: "i18n:govoplan-admin.setting.fb449f71", width: "minmax(220px, 1fr)", minWidth: 180, resizable: true, sortable: true, filterable: true, value: (record) => `${record.key} ${record.id}`, render: (record) => <div><strong>{record.key}</strong><span className="muted block">{record.id}</span></div> },
|
||||||
|
{ id: "status", header: "i18n:govoplan-admin.status.bae7d5be", width: 150, sortable: true, filterable: true, value: (record) => record.status, render: (record) => <StatusBadge status={statusTone(record.status)} label={record.status} /> },
|
||||||
|
{ id: "applied", header: "i18n:govoplan-admin.applied.a3e4a569", width: 180, sortable: true, value: (record) => record.created_at, render: (record) => formatDateTime(record.created_at) },
|
||||||
|
{ id: "approvers", header: "i18n:govoplan-admin.approvers.0e2de1fb", width: 120, sortable: true, value: (record) => record.approval_user_ids.length, render: (record) => record.approval_user_ids.length || "-" },
|
||||||
|
{ id: "target", header: "i18n:govoplan-admin.target.61ad50a9", width: "minmax(180px, .8fr)", minWidth: 160, resizable: true, filterable: true, value: (record) => targetLabel(record.target), render: (record) => targetLabel(record.target) }
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AdminPageLayout
|
<AdminPageLayout
|
||||||
@@ -83,67 +124,23 @@ export default function ConfigurationChangesPanel({ settings, canApprove }: {set
|
|||||||
actions={<Button onClick={() => void load()} disabled={loading}><RefreshCw size={16} /> i18n:govoplan-admin.reload.cce71553</Button>}>
|
actions={<Button onClick={() => void load()} disabled={loading}><RefreshCw size={16} /> i18n:govoplan-admin.reload.cce71553</Button>}>
|
||||||
|
|
||||||
<Card title="i18n:govoplan-admin.requests.f7194e6a">
|
<Card title="i18n:govoplan-admin.requests.f7194e6a">
|
||||||
<div className="admin-table-wrap">
|
<DataGrid
|
||||||
<table className="admin-table">
|
id="admin-configuration-change-requests"
|
||||||
<thead>
|
rows={pending}
|
||||||
<tr>
|
columns={requestColumns}
|
||||||
<th>i18n:govoplan-admin.setting.fb449f71</th>
|
getRowKey={(request) => request.id}
|
||||||
<th>i18n:govoplan-admin.status.bae7d5be</th>
|
emptyText="i18n:govoplan-admin.no_open_requests.580c95f9"
|
||||||
<th>i18n:govoplan-admin.requested.c26bf60f</th>
|
/>
|
||||||
<th>i18n:govoplan-admin.approvals.deb9d03c</th>
|
|
||||||
<th>i18n:govoplan-admin.target.61ad50a9</th>
|
|
||||||
<th className="actions">i18n:govoplan-admin.action.97c89a4d</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{pending.map((request) =>
|
|
||||||
<tr key={request.id}>
|
|
||||||
<td><strong>{request.label || request.key}</strong><span className="muted block">{request.key}</span></td>
|
|
||||||
<td><StatusBadge status={statusTone(request.status)} label={request.status} /></td>
|
|
||||||
<td>{formatDateTime(request.requested_at)}</td>
|
|
||||||
<td>{request.approvals.length}</td>
|
|
||||||
<td>{targetLabel(request.target)}</td>
|
|
||||||
<td className="actions">
|
|
||||||
{canApprove && request.status === "pending_approval" &&
|
|
||||||
<Button onClick={() => void approve(request)} disabled={busyId === request.id}><Check size={16} /> i18n:govoplan-admin.approve.7b2c7f14</Button>
|
|
||||||
}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
{!pending.length && <tr><td colSpan={6} className="muted">i18n:govoplan-admin.no_open_requests.580c95f9</td></tr>}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card title="i18n:govoplan-admin.history.90ccd649">
|
<Card title="i18n:govoplan-admin.history.90ccd649">
|
||||||
<div className="admin-table-wrap">
|
<DataGrid
|
||||||
<table className="admin-table">
|
id="admin-configuration-change-history"
|
||||||
<thead>
|
rows={history}
|
||||||
<tr>
|
columns={historyColumns}
|
||||||
<th>i18n:govoplan-admin.version.2da600bf</th>
|
getRowKey={(record) => record.id}
|
||||||
<th>i18n:govoplan-admin.setting.fb449f71</th>
|
emptyText="i18n:govoplan-admin.no_applied_configuration_changes.6a3ae4a7"
|
||||||
<th>i18n:govoplan-admin.status.bae7d5be</th>
|
/>
|
||||||
<th>i18n:govoplan-admin.applied.a3e4a569</th>
|
|
||||||
<th>i18n:govoplan-admin.approvers.0e2de1fb</th>
|
|
||||||
<th>i18n:govoplan-admin.target.61ad50a9</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{history.map((record) =>
|
|
||||||
<tr key={record.id}>
|
|
||||||
<td>#{record.version}</td>
|
|
||||||
<td><strong>{record.key}</strong><span className="muted block">{record.id}</span></td>
|
|
||||||
<td><StatusBadge status={statusTone(record.status)} label={record.status} /></td>
|
|
||||||
<td>{formatDateTime(record.created_at)}</td>
|
|
||||||
<td>{record.approval_user_ids.length || "-"}</td>
|
|
||||||
<td>{targetLabel(record.target)}</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
{!history.length && <tr><td colSpan={6} className="muted">i18n:govoplan-admin.no_applied_configuration_changes.6a3ae4a7</td></tr>}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</Card>
|
</Card>
|
||||||
</AdminPageLayout>);
|
</AdminPageLayout>);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { Check, Download, Play, RefreshCw } from "lucide-react";
|
import { Check, Download, Play, RefreshCw } from "lucide-react";
|
||||||
import type { ApiSettings } from "@govoplan/core-webui";
|
import type { ApiSettings } from "@govoplan/core-webui";
|
||||||
import { AdminPageLayout, Button, Card, StatusBadge, adminErrorMessage, i18nMessage } from "@govoplan/core-webui";
|
import { AdminPageLayout, Button, Card, DataGrid, StatusBadge, adminErrorMessage, i18nMessage, type DataGridColumn } from "@govoplan/core-webui";
|
||||||
import {
|
import {
|
||||||
applyConfigurationPackage,
|
applyConfigurationPackage,
|
||||||
createConfigurationChangeRequest,
|
createConfigurationChangeRequest,
|
||||||
@@ -273,89 +273,68 @@ function parseObject(text: string): {value: Record<string, unknown> | null;error
|
|||||||
|
|
||||||
function PackageDiagnostics({ diagnostics }: {diagnostics: ConfigurationPackageDiagnostic[];}) {
|
function PackageDiagnostics({ diagnostics }: {diagnostics: ConfigurationPackageDiagnostic[];}) {
|
||||||
if (diagnostics.length === 0) return <p className="muted">i18n:govoplan-admin.no_diagnostics.2b6e2630</p>;
|
if (diagnostics.length === 0) return <p className="muted">i18n:govoplan-admin.no_diagnostics.2b6e2630</p>;
|
||||||
|
const columns: DataGridColumn<ConfigurationPackageDiagnostic>[] = [
|
||||||
|
{ id: "severity", header: "i18n:govoplan-admin.severity.de314fa0", width: 130, sortable: true, filterable: true, value: (item) => item.severity, render: (item) => <StatusBadge status={diagnosticTone(item.severity)} label={item.severity} /> },
|
||||||
|
{ id: "code", header: "i18n:govoplan-admin.code.adac6937", width: 190, sortable: true, filterable: true, value: (item) => item.code, render: (item) => <code>{item.code}</code> },
|
||||||
|
{ id: "owner", header: "i18n:govoplan-admin.owner.89ff3122", width: 150, sortable: true, filterable: true, value: (item) => item.module_id || "-", render: (item) => item.module_id || "-" },
|
||||||
|
{ id: "object", header: "i18n:govoplan-admin.object.2883f191", width: 180, sortable: true, filterable: true, value: (item) => item.object_ref || "-", render: (item) => item.object_ref || "-" },
|
||||||
|
{ id: "message", header: "i18n:govoplan-admin.message.68f4145f", width: "minmax(260px, 1fr)", minWidth: 220, resizable: true, filterable: true, value: (item) => `${item.message} ${item.resolution || ""}`, render: (item) => <div>{item.message}{item.resolution ? <span className="muted block">{item.resolution}</span> : null}</div> }
|
||||||
|
];
|
||||||
return (
|
return (
|
||||||
<div className="admin-table-wrap">
|
<DataGrid
|
||||||
<table className="admin-table">
|
id="admin-configuration-package-diagnostics"
|
||||||
<thead><tr><th>i18n:govoplan-admin.severity.de314fa0</th><th>i18n:govoplan-admin.code.adac6937</th><th>i18n:govoplan-admin.owner.89ff3122</th><th>i18n:govoplan-admin.object.2883f191</th><th>i18n:govoplan-admin.message.68f4145f</th></tr></thead>
|
rows={diagnostics}
|
||||||
<tbody>
|
columns={columns}
|
||||||
{diagnostics.map((item, index) =>
|
getRowKey={(item, index) => `${item.code}-${index}`}
|
||||||
<tr key={`${item.code}-${index}`}>
|
/>);
|
||||||
<td><StatusBadge status={diagnosticTone(item.severity)} label={item.severity} /></td>
|
|
||||||
<td><code>{item.code}</code></td>
|
|
||||||
<td>{item.module_id || "-"}</td>
|
|
||||||
<td>{item.object_ref || "-"}</td>
|
|
||||||
<td>{item.message}{item.resolution ? <span className="muted block">{item.resolution}</span> : null}</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function RequiredData({ items }: {items: ConfigurationPackageRequiredData[];}) {
|
function RequiredData({ items }: {items: ConfigurationPackageRequiredData[];}) {
|
||||||
if (items.length === 0) return null;
|
if (items.length === 0) return null;
|
||||||
|
const columns: DataGridColumn<ConfigurationPackageRequiredData>[] = [
|
||||||
|
{ id: "key", header: "i18n:govoplan-admin.key.c67dd20e", width: "minmax(200px, 1fr)", minWidth: 170, resizable: true, sortable: true, filterable: true, value: (item) => item.key, render: (item) => <code>{item.key}</code> },
|
||||||
|
{ id: "label", header: "i18n:govoplan-admin.label.74341e3c", width: "minmax(180px, 1fr)", minWidth: 160, resizable: true, sortable: true, filterable: true, value: (item) => item.label },
|
||||||
|
{ id: "type", header: "i18n:govoplan-admin.type.3deb7456", width: 140, sortable: true, filterable: true, value: (item) => item.data_type },
|
||||||
|
{ id: "required", header: "i18n:govoplan-admin.required.eed6bfb4", width: 110, sortable: true, value: (item) => item.required, render: (item) => item.required ? "yes" : "no" },
|
||||||
|
{ id: "secret", header: "i18n:govoplan-admin.secret.f4e7a874", width: 100, sortable: true, value: (item) => item.secret, render: (item) => item.secret ? "yes" : "no" }
|
||||||
|
];
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h3>i18n:govoplan-admin.required_data.1b1c1b34</h3>
|
<h3>i18n:govoplan-admin.required_data.1b1c1b34</h3>
|
||||||
<div className="admin-table-wrap">
|
<DataGrid id="admin-configuration-package-required-data" rows={items} columns={columns} getRowKey={(item) => item.key} />
|
||||||
<table className="admin-table">
|
|
||||||
<thead><tr><th>i18n:govoplan-admin.key.c67dd20e</th><th>i18n:govoplan-admin.label.74341e3c</th><th>i18n:govoplan-admin.type.3deb7456</th><th>i18n:govoplan-admin.required.eed6bfb4</th><th>i18n:govoplan-admin.secret.f4e7a874</th></tr></thead>
|
|
||||||
<tbody>
|
|
||||||
{items.map((item) =>
|
|
||||||
<tr key={item.key}>
|
|
||||||
<td><code>{item.key}</code></td>
|
|
||||||
<td>{item.label}</td>
|
|
||||||
<td>{item.data_type}</td>
|
|
||||||
<td>{item.required ? "yes" : "no"}</td>
|
|
||||||
<td>{item.secret ? "yes" : "no"}</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</>);
|
</>);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function PackagePlan({ items }: {items: ConfigurationPackagePlanItem[];}) {
|
function PackagePlan({ items }: {items: ConfigurationPackagePlanItem[];}) {
|
||||||
if (items.length === 0) return <p className="muted">i18n:govoplan-admin.no_plan_items.7108c582</p>;
|
if (items.length === 0) return <p className="muted">i18n:govoplan-admin.no_plan_items.7108c582</p>;
|
||||||
|
const columns: DataGridColumn<ConfigurationPackagePlanItem>[] = [
|
||||||
|
{ id: "action", header: "i18n:govoplan-admin.action.97c89a4d", width: 130, sortable: true, filterable: true, value: (item) => item.action, render: (item) => <StatusBadge status={planTone(item.action)} label={item.action} /> },
|
||||||
|
{ id: "module", header: "i18n:govoplan-admin.module.b8ff0289", width: 170, sortable: true, filterable: true, value: (item) => item.module_id },
|
||||||
|
{ id: "fragment", header: "i18n:govoplan-admin.fragment.3f19d616", width: 170, sortable: true, filterable: true, value: (item) => item.fragment_type },
|
||||||
|
{ id: "id", header: "i18n:govoplan-admin.id.474ae526", width: 180, sortable: true, filterable: true, value: (item) => item.fragment_id || "-", render: (item) => item.fragment_id || "-" },
|
||||||
|
{ id: "summary", header: "i18n:govoplan-admin.summary.12b71c3e", width: "minmax(220px, 1fr)", minWidth: 180, resizable: true, filterable: true, value: (item) => item.summary || "-", render: (item) => item.summary || "-" }
|
||||||
|
];
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h3>i18n:govoplan-admin.plan.ae2f98a0</h3>
|
<h3>i18n:govoplan-admin.plan.ae2f98a0</h3>
|
||||||
<div className="admin-table-wrap">
|
<DataGrid id="admin-configuration-package-plan" rows={items} columns={columns} getRowKey={(item, index) => `${item.module_id}-${item.fragment_type}-${item.fragment_id ?? index}`} />
|
||||||
<table className="admin-table">
|
|
||||||
<thead><tr><th>i18n:govoplan-admin.action.97c89a4d</th><th>i18n:govoplan-admin.module.b8ff0289</th><th>i18n:govoplan-admin.fragment.3f19d616</th><th>i18n:govoplan-admin.id.474ae526</th><th>i18n:govoplan-admin.summary.12b71c3e</th></tr></thead>
|
|
||||||
<tbody>
|
|
||||||
{items.map((item, index) =>
|
|
||||||
<tr key={`${item.module_id}-${item.fragment_type}-${item.fragment_id ?? index}`}>
|
|
||||||
<td><StatusBadge status={planTone(item.action)} label={item.action} /></td>
|
|
||||||
<td>{item.module_id}</td>
|
|
||||||
<td>{item.fragment_type}</td>
|
|
||||||
<td>{item.fragment_id || "-"}</td>
|
|
||||||
<td>{item.summary || "-"}</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</>);
|
</>);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function ReferenceMap({ title, refs }: {title: string;refs: Record<string, string>;}) {
|
function ReferenceMap({ title, refs }: {title: string;refs: Record<string, string>;}) {
|
||||||
const entries = Object.entries(refs);
|
const entries = Object.entries(refs).map(([key, value]) => ({ key, value }));
|
||||||
if (entries.length === 0) return null;
|
if (entries.length === 0) return null;
|
||||||
|
const columns: DataGridColumn<{key: string;value: string;}>[] = [
|
||||||
|
{ id: "key", header: "i18n:govoplan-admin.key.c67dd20e", width: "minmax(220px, 1fr)", minWidth: 180, resizable: true, sortable: true, filterable: true, value: (item) => item.key, render: (item) => <code>{item.key}</code> },
|
||||||
|
{ id: "value", header: "Value", width: "minmax(220px, 1fr)", minWidth: 180, resizable: true, sortable: true, filterable: true, value: (item) => item.value }
|
||||||
|
];
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h3>{title}</h3>
|
<h3>{title}</h3>
|
||||||
<div className="admin-table-wrap">
|
<DataGrid id={`admin-configuration-package-refs-${title}`} rows={entries} columns={columns} getRowKey={(item) => item.key} />
|
||||||
<table className="admin-table">
|
|
||||||
<tbody>
|
|
||||||
{entries.map(([key, value]) => <tr key={key}><td><code>{key}</code></td><td>{value}</td></tr>)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</>);
|
</>);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import {
|
|||||||
type PermissionItem,
|
type PermissionItem,
|
||||||
type TenantAdminItem } from
|
type TenantAdminItem } from
|
||||||
"../../api/admin";
|
"../../api/admin";
|
||||||
import { AdminIconButton, AdminPageLayout, AdminSelectionList, adminErrorMessage, i18nMessage, useUnsavedDraftGuard } from "@govoplan/core-webui";
|
import { AdminIconButton, AdminPageLayout, AdminSelectionList, TableActionGroup, adminErrorMessage, i18nMessage, useUnsavedDraftGuard } from "@govoplan/core-webui";
|
||||||
|
|
||||||
const emptyDraft = {
|
const emptyDraft = {
|
||||||
slug: "",
|
slug: "",
|
||||||
@@ -187,11 +187,11 @@ export default function GovernanceTemplatesPanel({
|
|||||||
{ id: "status", header: "i18n:govoplan-admin.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-admin.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-admin.actions.c3cd636a", width: 150, sticky: "end", resizable: false, align: "right",
|
id: "actions", header: "i18n:govoplan-admin.actions.c3cd636a", width: 150, sticky: "end", resizable: false, align: "right",
|
||||||
render: (row) => <div className="admin-icon-actions">
|
render: (row) => <TableActionGroup actions={[
|
||||||
<AdminIconButton label={i18nMessage("i18n:govoplan-admin.inspect_value.9d5d1071", { value0: row.name })} icon={<Search />} onClick={() => setViewing(row)} />
|
{ id: "inspect", label: i18nMessage("i18n:govoplan-admin.inspect_value.9d5d1071", { value0: row.name }), icon: <Search />, onClick: () => setViewing(row) },
|
||||||
<AdminIconButton label={i18nMessage("i18n:govoplan-admin.edit_value.fad75899", { value0: row.name })} icon={<Pencil />} onClick={() => openEdit(row)} disabled={!canWrite} />
|
{ id: "edit", label: i18nMessage("i18n:govoplan-admin.edit_value.fad75899", { value0: row.name }), icon: <Pencil />, disabled: !canWrite, onClick: () => openEdit(row) },
|
||||||
<AdminIconButton label={i18nMessage("i18n:govoplan-admin.delete_value.4d18989e", { value0: row.name })} icon={<Trash2 />} variant="danger" onClick={() => setDeleting(row)} disabled={!canWrite} />
|
{ id: "delete", label: i18nMessage("i18n:govoplan-admin.delete_value.4d18989e", { value0: row.name }), icon: <Trash2 />, variant: "danger", disabled: !canWrite, onClick: () => setDeleting(row) }
|
||||||
</div>
|
]} />
|
||||||
}],
|
}],
|
||||||
[canWrite, kind, tenants]);
|
[canWrite, kind, tenants]);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import type { ApiSettings } from "@govoplan/core-webui";
|
import type { ApiSettings } from "@govoplan/core-webui";
|
||||||
import { AdminPageLayout, adminErrorMessage, Button, dispatchPlatformModulesChanged, StatusBadge, ToggleSwitch, i18nMessage, useUnsavedDraftGuard } from "@govoplan/core-webui";
|
import { AdminPageLayout, adminErrorMessage, Button, dispatchPlatformModulesChanged, MetricCard, StatusBadge, ToggleSwitch, i18nMessage, useUnsavedDraftGuard } from "@govoplan/core-webui";
|
||||||
import {
|
import {
|
||||||
cancelModuleInstallerRequest,
|
cancelModuleInstallerRequest,
|
||||||
clearModuleInstallPlan,
|
clearModuleInstallPlan,
|
||||||
@@ -323,11 +323,11 @@ export default function ModuleManagementPanel({ settings, canWrite, canAccessMai
|
|||||||
|
|
||||||
{catalog && <>
|
{catalog && <>
|
||||||
<div className="metric-grid module-management-metrics">
|
<div className="metric-grid module-management-metrics">
|
||||||
<ModuleMetric label="i18n:govoplan-admin.installed.7bb4405c" value={catalog.modules.length} detail="i18n:govoplan-admin.discovered_packages.660902de" />
|
<MetricCard label="i18n:govoplan-admin.installed.7bb4405c" value={catalog.modules.length} detail="i18n:govoplan-admin.discovered_packages.660902de" tone="info" />
|
||||||
<ModuleMetric label="i18n:govoplan-admin.active.a733b809" value={activeCount} detail="i18n:govoplan-admin.running_registry.5274b24b" />
|
<MetricCard label="i18n:govoplan-admin.active.a733b809" value={activeCount} detail="i18n:govoplan-admin.running_registry.5274b24b" tone="info" />
|
||||||
<ModuleMetric label="i18n:govoplan-admin.saved.c0ae8f6e" value={desiredCount} detail="i18n:govoplan-admin.startup_state.d1300be6" />
|
<MetricCard label="i18n:govoplan-admin.saved.c0ae8f6e" value={desiredCount} detail="i18n:govoplan-admin.startup_state.d1300be6" tone="info" />
|
||||||
<ModuleMetric label="i18n:govoplan-admin.drift.4876f7b9" value={pendingCount} detail={pendingCount ? "i18n:govoplan-admin.runtime_differs.43d1fd78" : "i18n:govoplan-admin.runtime_matches.a84afa48"} tone={pendingCount ? "warning" : "good"} />
|
<MetricCard label="i18n:govoplan-admin.drift.4876f7b9" value={pendingCount} detail={pendingCount ? "i18n:govoplan-admin.runtime_differs.43d1fd78" : "i18n:govoplan-admin.runtime_matches.a84afa48"} tone={pendingCount ? "warning" : "good"} />
|
||||||
<ModuleMetric label="i18n:govoplan-admin.maintenance.94de303b" value={maintenanceEnabled ? "i18n:govoplan-admin.on.e0049a66" : "i18n:govoplan-admin.off.e3de5ab0"} detail={canAccessMaintenance ? "i18n:govoplan-admin.bypass_allowed.4e347c27" : "i18n:govoplan-admin.bypass_denied.ab987400"} tone={maintenanceEnabled ? "warning" : "info"} />
|
<MetricCard label="i18n:govoplan-admin.maintenance.94de303b" value={maintenanceEnabled ? "i18n:govoplan-admin.on.e0049a66" : "i18n:govoplan-admin.off.e3de5ab0"} detail={canAccessMaintenance ? "i18n:govoplan-admin.bypass_allowed.4e347c27" : "i18n:govoplan-admin.bypass_denied.ab987400"} tone={maintenanceEnabled ? "warning" : "info"} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{dirty && <div className="module-management-notes">
|
{dirty && <div className="module-management-notes">
|
||||||
@@ -654,10 +654,6 @@ export default function ModuleManagementPanel({ settings, canWrite, canAccessMai
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function ModuleMetric({ label, value, detail, tone = "info" }: {label: string;value: string | number;detail: string;tone?: "info" | "good" | "warning";}) {
|
|
||||||
return <div className={`metric-card metric-${tone}`}><div className="metric-label">{label}</div><div className="metric-value">{value}</div><div className="metric-detail">{detail}</div></div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function LicenseStatus({ license }: {license: ModuleLicenseDiagnostics;}) {
|
function LicenseStatus({ license }: {license: ModuleLicenseDiagnostics;}) {
|
||||||
const label = license.license_id ? `${license.license_id}${license.subject ? ` · ${license.subject}` : ""}` : license.configured ? "i18n:govoplan-admin.configured_license.3e73f8f5" : "i18n:govoplan-admin.no_license_configured.693cea1a";
|
const label = license.license_id ? `${license.license_id}${license.subject ? ` · ${license.subject}` : ""}` : license.configured ? "i18n:govoplan-admin.configured_license.3e73f8f5" : "i18n:govoplan-admin.no_license_configured.693cea1a";
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user