Move identity support into identity card

This commit is contained in:
rcourtman
2026-03-23 10:17:29 +00:00
parent b85c4473e8
commit ca221f2e59
4 changed files with 83 additions and 86 deletions
@@ -432,13 +432,15 @@ while deeper machine detail remains available on demand.
That host-details section now reads as a simple vertical stack of detail cards
instead of a wrapped card grid, so the opened state stays linear instead of
feeling like a second dashboard.
Within that summary shell, current-state facts now stay in the primary summary
while platform IDs, quick links, platform-signal badges, aliases, IPs, and
tags render inline as supporting rows, so first read remains status-first and
identity-first instead of mixing state with hidden supporting action surfaces.
Those rows stay label-first and only expand when a specific value, like alias
overflow, needs its own reveal, so the summary answers the main resource
question before deeper metadata appears.
Within that summary shell, current-state facts now stay operational: platform
IDs, quick links, and platform-signal badges remain with runtime status,
while aliases, IPs, and tags live only under the dedicated `Identity` card.
That keeps first read status-first while still preserving canonical identity
metadata on the same top-level summary surface instead of mixing identity
support details into current-state chrome.
The identity-side rows stay label-first and only expand when a specific value,
like alias overflow, needs its own reveal, so the summary answers the main
resource question before deeper metadata appears.
When the identity side has no owned rows or supporting labels yet, the sparse
fallback now stays terse (`No identity metadata yet.`) so empty state chrome
does not read heavier than the data it is standing in for.
@@ -149,7 +149,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
</span>
</div>
</Show>
<Show when={drawer.hasRuntimeOperationalContext() || drawer.hasIdentitySupportContext()}>
<Show when={drawer.hasRuntimeOperationalContext()}>
<div class="mt-2 rounded border border-dashed border-border bg-surface-hover p-3">
<div class="space-y-2.5">
<Show when={drawer.hasRuntimeOperationalContext()}>
@@ -199,74 +199,6 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
</Show>
</div>
</Show>
<Show when={drawer.hasIdentitySupportContext()}>
<div class="space-y-1.5">
<Show when={resource.identity?.ips && resource.identity.ips.length > 0}>
<div class="flex flex-col gap-1">
<span class="text-muted">IP Addresses</span>
<div class="flex flex-wrap gap-1">
<For each={resource.identity?.ips ?? []}>
{(ip) => (
<span
class="inline-flex items-center rounded bg-blue-100 px-1.5 py-0.5 text-[10px] text-blue-700 dark:bg-blue-900 dark:text-blue-200"
title={ip}
>
{ip}
</span>
)}
</For>
</div>
</div>
</Show>
<Show when={resource.tags && resource.tags.length > 0}>
<div class="flex items-center justify-between gap-2">
<span class="text-muted">Tags</span>
<TagBadges tags={resource.tags} maxVisible={6} />
</div>
</Show>
<Show when={drawer.identityAliasValues().length > 0}>
<Show
when={drawer.hasAliasOverflow()}
fallback={
<div class="flex flex-col gap-1">
<span class="text-muted">Aliases</span>
<div class="flex flex-wrap gap-1">
<For each={drawer.aliasPreviewValues()}>
{(value) => (
<span
class="inline-flex items-center rounded bg-surface-alt px-1.5 py-0.5 text-[10px]"
title={value}
>
{value}
</span>
)}
</For>
</div>
</div>
}
>
<details class="rounded border border-border bg-surface px-2 py-1.5">
<summary class="flex cursor-pointer list-none items-center justify-between text-[10px] font-medium text-muted">
<span>Aliases</span>
<span class="text-muted">{drawer.identityAliasValues().length}</span>
</summary>
<div class="mt-2 flex flex-wrap gap-1 border-t border-border pt-2">
<For each={drawer.identityAliasValues()}>
{(value) => (
<span
class="inline-flex items-center rounded bg-surface-alt px-1.5 py-0.5 text-[10px]"
title={value}
>
{value}
</span>
)}
</For>
</div>
</details>
</Show>
</Show>
</div>
</Show>
</div>
</div>
</Show>
@@ -288,6 +220,70 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
</div>
)}
</For>
<Show when={drawer.identityIpValues().length > 0}>
<div class="flex flex-col gap-1">
<span class="text-muted">IP Addresses</span>
<div class="flex flex-wrap gap-1">
<For each={drawer.identityIpValues()}>
{(ip) => (
<span
class="inline-flex items-center rounded bg-blue-100 px-1.5 py-0.5 text-[10px] text-blue-700 dark:bg-blue-900 dark:text-blue-200"
title={ip}
>
{ip}
</span>
)}
</For>
</div>
</div>
</Show>
<Show when={resource.tags && resource.tags.length > 0}>
<div class="flex items-center justify-between gap-2">
<span class="text-muted">Tags</span>
<TagBadges tags={resource.tags} maxVisible={6} />
</div>
</Show>
<Show when={drawer.identityAliasValues().length > 0}>
<Show
when={drawer.hasAliasOverflow()}
fallback={
<div class="flex flex-col gap-1">
<span class="text-muted">Aliases</span>
<div class="flex flex-wrap gap-1">
<For each={drawer.aliasPreviewValues()}>
{(value) => (
<span
class="inline-flex items-center rounded bg-surface-alt px-1.5 py-0.5 text-[10px]"
title={value}
>
{value}
</span>
)}
</For>
</div>
</div>
}
>
<details class="rounded border border-border bg-surface px-2 py-1.5">
<summary class="flex cursor-pointer list-none items-center justify-between text-[10px] font-medium text-muted">
<span>Aliases</span>
<span class="text-muted">{drawer.identityAliasValues().length}</span>
</summary>
<div class="mt-2 flex flex-wrap gap-1 border-t border-border pt-2">
<For each={drawer.identityAliasValues()}>
{(value) => (
<span
class="inline-flex items-center rounded bg-surface-alt px-1.5 py-0.5 text-[10px]"
title={value}
>
{value}
</span>
)}
</For>
</div>
</details>
</Show>
</Show>
<Show when={!drawer.identityCardHasRichData()}>
<div class="rounded border border-dashed bg-surface-hover px-2 py-1.5 text-[10px] ">
No identity metadata yet.
@@ -275,6 +275,8 @@ describe('ResourceDetailDrawer change history section', () => {
expect(
screen.getByTestId('resource-change-history-section').querySelector('.mt-3.grid.gap-2'),
).toBeNull();
const currentStateSection = screen.getByTestId('resource-current-state-section');
const identitySection = screen.getByTestId('resource-identity-section');
expect(screen.queryByText('Host details')).toBeNull();
expect(screen.queryByText('Service details')).toBeNull();
expect(screen.queryByText('Supporting context')).toBeNull();
@@ -306,7 +308,8 @@ describe('ResourceDetailDrawer change history section', () => {
expect(screen.queryByText('Details')).toBeNull();
expect(screen.queryByRole('button', { name: 'Show details' })).toBeNull();
expect(screen.getByText('Platform ID')).toBeInTheDocument();
expect(screen.getByText('Tags')).toBeInTheDocument();
expect(within(identitySection).getByText('Tags')).toBeInTheDocument();
expect(within(currentStateSection).queryByText('Tags')).toBeNull();
expect(
within(changeHistorySection).queryByText('Filterable event history for this resource.'),
).toBeNull();
@@ -420,7 +423,8 @@ describe('ResourceDetailDrawer change history section', () => {
render(() => <ResourceDetailDrawer resource={resource} />);
expect(screen.getByText('Aliases')).toBeInTheDocument();
expect(within(screen.getByTestId('resource-identity-section')).getByText('Aliases')).toBeInTheDocument();
expect(within(screen.getByTestId('resource-current-state-section')).queryByText('Aliases')).toBeNull();
});
it('renders timeline history without surfacing unsupported capability or relationship facets', async () => {
@@ -355,11 +355,12 @@ export const useResourceDetailDrawerDerivedState = (
});
const identityAliasValues = createMemo(() => getResourceIdentityAliases(resource));
const identityIpValues = createMemo(() => resource.identity?.ips ?? []);
const primaryIdentityRows = createMemo(() => getPrimaryResourceIdentityRows(resource));
const identityCardHasRichData = createMemo(
() =>
primaryIdentityRows().length > 0 ||
(resource.identity?.ips?.length || 0) > 0 ||
identityIpValues().length > 0 ||
(resource.tags?.length || 0) > 0 ||
identityAliasValues().length > 0,
);
@@ -369,12 +370,6 @@ export const useResourceDetailDrawerDerivedState = (
const hasAliasOverflow = createMemo(
() => identityAliasValues().length > ALIAS_COLLAPSE_THRESHOLD,
);
const hasIdentitySupportContext = createMemo(
() =>
(resource.identity?.ips?.length ?? 0) > 0 ||
(resource.tags?.length ?? 0) > 0 ||
identityAliasValues().length > 0,
);
const hasMergedSources = createMemo(() => mergedSources().length > 1);
const discoveryConfig = createMemo(() => toDiscoveryConfig(resource));
const discoveryContextSummary = createMemo(() => {
@@ -585,12 +580,12 @@ export const useResourceDetailDrawerDerivedState = (
mergedSources,
sourceStatus,
sourceSummary,
identityIpValues,
identityAliasValues,
primaryIdentityRows,
identityCardHasRichData,
aliasPreviewValues,
hasAliasOverflow,
hasIdentitySupportContext,
hasMergedSources,
discoveryConfig,
discoveryContextSummary,