mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-11 14:00:29 +00:00
Centralize frontend policy summaries
This commit is contained in:
@@ -1,12 +1,9 @@
|
||||
import { For, Show, type Component } from 'solid-js';
|
||||
import type { IntelligencePolicyPostureSummary } from '@/types/aiIntelligence';
|
||||
import {
|
||||
RESOURCE_POLICY_REDACTION_ORDER,
|
||||
RESOURCE_POLICY_ROUTING_ORDER,
|
||||
RESOURCE_POLICY_SENSITIVITY_ORDER,
|
||||
getResourceRedactionHintLabel,
|
||||
getResourceRoutingScopeLabel,
|
||||
getResourceSensitivityLabel,
|
||||
getResourcePolicyRedactionSummaries,
|
||||
getResourcePolicyRoutingSummaries,
|
||||
getResourcePolicySensitivitySummaries,
|
||||
} from '@/utils/resourcePolicyPresentation';
|
||||
|
||||
interface ResourcePolicySummaryProps {
|
||||
@@ -18,6 +15,9 @@ interface ResourcePolicySummaryProps {
|
||||
export const ResourcePolicySummary: Component<ResourcePolicySummaryProps> = (props) => {
|
||||
const posture = () => props.posture;
|
||||
const className = () => props.class?.trim() ?? '';
|
||||
const sensitivitySummaries = () => getResourcePolicySensitivitySummaries(posture());
|
||||
const routingSummaries = () => getResourcePolicyRoutingSummaries(posture());
|
||||
const redactionSummaries = () => getResourcePolicyRedactionSummaries(posture());
|
||||
|
||||
return (
|
||||
<Show when={posture()}>
|
||||
@@ -33,50 +33,32 @@ export const ResourcePolicySummary: Component<ResourcePolicySummaryProps> = (pro
|
||||
</div>
|
||||
|
||||
<dl class="mt-3 grid grid-cols-2 gap-2 text-sm">
|
||||
<For each={RESOURCE_POLICY_SENSITIVITY_ORDER}>
|
||||
{(sensitivity) => {
|
||||
const count = () => value().sensitivity_counts?.[sensitivity] ?? 0;
|
||||
return (
|
||||
<div class="rounded-md bg-surface px-3 py-2">
|
||||
<dt class="text-xs uppercase tracking-wide text-muted">
|
||||
{getResourceSensitivityLabel(sensitivity)}
|
||||
</dt>
|
||||
<dd class="mt-1 font-semibold text-base-content">{count()}</dd>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
<For each={sensitivitySummaries()}>
|
||||
{(item) => (
|
||||
<div class="rounded-md bg-surface px-3 py-2">
|
||||
<dt class="text-xs uppercase tracking-wide text-muted">{item.label}</dt>
|
||||
<dd class="mt-1 font-semibold text-base-content">{item.count}</dd>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
<For each={RESOURCE_POLICY_ROUTING_ORDER}>
|
||||
{(scope) => {
|
||||
const count = () => value().routing_counts?.[scope] ?? 0;
|
||||
return (
|
||||
<div class="rounded-md bg-surface px-3 py-2">
|
||||
<dt class="text-xs uppercase tracking-wide text-muted">
|
||||
{getResourceRoutingScopeLabel(scope)}
|
||||
</dt>
|
||||
<dd class="mt-1 font-semibold text-base-content">{count()}</dd>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
<For each={routingSummaries()}>
|
||||
{(item) => (
|
||||
<div class="rounded-md bg-surface px-3 py-2">
|
||||
<dt class="text-xs uppercase tracking-wide text-muted">{item.label}</dt>
|
||||
<dd class="mt-1 font-semibold text-base-content">{item.count}</dd>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</dl>
|
||||
|
||||
<Show
|
||||
when={RESOURCE_POLICY_REDACTION_ORDER.some(
|
||||
(hint) => (value().redaction_counts?.[hint] ?? 0) > 0,
|
||||
)}
|
||||
>
|
||||
<Show when={redactionSummaries().length > 0}>
|
||||
<div class="mt-2 flex flex-wrap gap-1">
|
||||
<For each={RESOURCE_POLICY_REDACTION_ORDER}>
|
||||
{(hint) => {
|
||||
const count = value().redaction_counts?.[hint] ?? 0;
|
||||
if (!count) return null;
|
||||
return (
|
||||
<span class="inline-flex items-center rounded bg-surface-alt px-1.5 py-0.5 text-[10px]">
|
||||
{getResourceRedactionHintLabel(hint)} {count}
|
||||
</span>
|
||||
);
|
||||
}}
|
||||
<For each={redactionSummaries()}>
|
||||
{(item) => (
|
||||
<span class="inline-flex items-center rounded bg-surface-alt px-1.5 py-0.5 text-[10px]">
|
||||
{item.label} {item.count}
|
||||
</span>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
@@ -4,6 +4,9 @@ import {
|
||||
RESOURCE_POLICY_REDACTION_ORDER,
|
||||
RESOURCE_POLICY_ROUTING_ORDER,
|
||||
RESOURCE_POLICY_SENSITIVITY_ORDER,
|
||||
getResourcePolicyRedactionSummaries,
|
||||
getResourcePolicyRoutingSummaries,
|
||||
getResourcePolicySensitivitySummaries,
|
||||
getResourcePolicyRedactionLabels,
|
||||
getResourceRedactionHintLabel,
|
||||
getResourceRoutingScopeLabel,
|
||||
@@ -31,6 +34,56 @@ describe('resourcePolicyPresentation utils', () => {
|
||||
).toEqual(['Hostname', 'IP Address']);
|
||||
});
|
||||
|
||||
it('formats canonical policy count summaries', () => {
|
||||
expect(
|
||||
getResourcePolicySensitivitySummaries({
|
||||
total_resources: 3,
|
||||
sensitivity_counts: {
|
||||
public: 1,
|
||||
internal: 2,
|
||||
},
|
||||
routing_counts: {},
|
||||
}),
|
||||
).toEqual([
|
||||
{ label: 'Public', count: 1 },
|
||||
{ label: 'Internal', count: 2 },
|
||||
{ label: 'Sensitive', count: 0 },
|
||||
{ label: 'Restricted', count: 0 },
|
||||
]);
|
||||
|
||||
expect(
|
||||
getResourcePolicyRoutingSummaries({
|
||||
total_resources: 3,
|
||||
sensitivity_counts: {},
|
||||
routing_counts: {
|
||||
'cloud-summary': 1,
|
||||
'local-first': 2,
|
||||
},
|
||||
}),
|
||||
).toEqual([
|
||||
{ label: 'Cloud Summary', count: 1 },
|
||||
{ label: 'Local First', count: 2 },
|
||||
{ label: 'Local Only', count: 0 },
|
||||
]);
|
||||
|
||||
expect(
|
||||
getResourcePolicyRedactionSummaries({
|
||||
total_resources: 3,
|
||||
sensitivity_counts: {},
|
||||
routing_counts: {
|
||||
'cloud-summary': 1,
|
||||
},
|
||||
redaction_counts: {
|
||||
hostname: 2,
|
||||
path: 1,
|
||||
},
|
||||
}),
|
||||
).toEqual([
|
||||
{ label: 'Hostname', count: 2 },
|
||||
{ label: 'Path', count: 1 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('exports canonical policy ordering', () => {
|
||||
expect(RESOURCE_POLICY_SENSITIVITY_ORDER).toEqual([
|
||||
'public',
|
||||
|
||||
@@ -4,6 +4,7 @@ import type {
|
||||
ResourceRoutingScope,
|
||||
ResourceSensitivity,
|
||||
} from '@/types/resource';
|
||||
import type { IntelligencePolicyPostureSummary } from '@/types/aiIntelligence';
|
||||
|
||||
type PolicyBadgePresentation = {
|
||||
label: string;
|
||||
@@ -11,6 +12,11 @@ type PolicyBadgePresentation = {
|
||||
className: string;
|
||||
};
|
||||
|
||||
export type ResourcePolicyCountSummary = {
|
||||
label: string;
|
||||
count: number;
|
||||
};
|
||||
|
||||
export const RESOURCE_POLICY_SENSITIVITY_ORDER: ResourceSensitivity[] = [
|
||||
'public',
|
||||
'internal',
|
||||
@@ -106,3 +112,55 @@ export const getResourceRedactionHintLabel = (hint?: ResourceRedactionHint): str
|
||||
|
||||
export const getResourcePolicyRedactionLabels = (policy?: ResourcePolicy): string[] =>
|
||||
(policy?.routing.redact ?? []).map((hint) => getResourceRedactionHintLabel(hint));
|
||||
|
||||
const buildCountSummaries = <T extends string>(
|
||||
counts: Partial<Record<T, number>> | undefined,
|
||||
order: readonly T[],
|
||||
labelFn: (value: T) => string,
|
||||
includeZeroCounts: boolean,
|
||||
): ResourcePolicyCountSummary[] => {
|
||||
if (!counts) return [];
|
||||
|
||||
const summaries: ResourcePolicyCountSummary[] = [];
|
||||
for (const value of order) {
|
||||
const count = counts[value] ?? 0;
|
||||
if (!includeZeroCounts && count <= 0) {
|
||||
continue;
|
||||
}
|
||||
summaries.push({
|
||||
label: labelFn(value),
|
||||
count,
|
||||
});
|
||||
}
|
||||
return summaries;
|
||||
};
|
||||
|
||||
export const getResourcePolicySensitivitySummaries = (
|
||||
posture?: IntelligencePolicyPostureSummary | null,
|
||||
): ResourcePolicyCountSummary[] =>
|
||||
buildCountSummaries(
|
||||
posture?.sensitivity_counts,
|
||||
RESOURCE_POLICY_SENSITIVITY_ORDER,
|
||||
getResourceSensitivityLabel,
|
||||
true,
|
||||
);
|
||||
|
||||
export const getResourcePolicyRoutingSummaries = (
|
||||
posture?: IntelligencePolicyPostureSummary | null,
|
||||
): ResourcePolicyCountSummary[] =>
|
||||
buildCountSummaries(
|
||||
posture?.routing_counts,
|
||||
RESOURCE_POLICY_ROUTING_ORDER,
|
||||
getResourceRoutingScopeLabel,
|
||||
true,
|
||||
);
|
||||
|
||||
export const getResourcePolicyRedactionSummaries = (
|
||||
posture?: IntelligencePolicyPostureSummary | null,
|
||||
): ResourcePolicyCountSummary[] =>
|
||||
buildCountSummaries(
|
||||
posture?.redaction_counts,
|
||||
RESOURCE_POLICY_REDACTION_ORDER,
|
||||
getResourceRedactionHintLabel,
|
||||
false,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user