mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-23 20:25:56 +00:00
Canonicalize node header web interface links
This commit is contained in:
@@ -2363,12 +2363,12 @@ Saved web-interface launch affordances must also stay on a shared primitive
|
||||
instead of page-local table columns or one-off external-link anchors.
|
||||
`frontend-modern/src/components/shared/WebInterfaceNameLink.tsx` owns the
|
||||
resource-name link shell, new-tab safety attributes, row-click/key propagation
|
||||
containment, fallback text, and accessible launch labels. Workload guest rows
|
||||
standalone machine rows, and Proxmox node rows compose that primitive so a saved
|
||||
or inferred URL is opened by clicking the resource name on every comparable
|
||||
runtime table. Runtime/platform tables must not add separate `Web` columns,
|
||||
page-local external-link anchors, or duplicated new-tab safety handling for
|
||||
that launch affordance.
|
||||
containment, fallback text, and accessible launch labels. Workload guest rows,
|
||||
grouped node headers, standalone machine rows, and Proxmox node rows compose
|
||||
that primitive so a saved or inferred URL is opened by clicking the resource
|
||||
name on every comparable runtime table. Runtime/platform tables must not add
|
||||
separate `Web` columns, page-local external-link anchors, or duplicated new-tab
|
||||
safety handling for that launch affordance.
|
||||
Shared-template drift enforcement is registry-backed:
|
||||
`frontend-modern/scripts/shared-template-registry.json` is the canonical list of
|
||||
standardized repeated affordances, required consumers, and forbidden local
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
"export": "WebInterfaceNameLink"
|
||||
},
|
||||
"requiredConsumers": [
|
||||
{
|
||||
"path": "src/components/shared/NodeGroupHeader.tsx"
|
||||
},
|
||||
{
|
||||
"path": "src/components/Workloads/GuestRow.tsx"
|
||||
},
|
||||
@@ -25,6 +28,10 @@
|
||||
"path": "src/components/Workloads/GuestRow.tsx",
|
||||
"patterns": ["target=\"_blank\"", "rel=\"noopener noreferrer\""]
|
||||
},
|
||||
{
|
||||
"path": "src/components/shared/NodeGroupHeader.tsx",
|
||||
"patterns": ["target=\"_blank\"", "rel=\"noopener noreferrer\""]
|
||||
},
|
||||
{
|
||||
"path": "src/features/standalone/AgentsMachinesTable.tsx",
|
||||
"patterns": [
|
||||
@@ -53,6 +60,7 @@
|
||||
],
|
||||
"proof": [
|
||||
"src/components/shared/SharedPrimitives.guardrails.test.ts",
|
||||
"src/components/shared/__tests__/NodeGroupHeader.test.tsx",
|
||||
"src/features/standalone/__tests__/AgentsMachinesTable.test.tsx",
|
||||
"src/components/Workloads/__tests__/GuestRow.test.tsx",
|
||||
"src/features/proxmox/__tests__/ProxmoxNodesTable.test.tsx",
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
getGroupedTableRowCellClass,
|
||||
getGroupedTableRowClass,
|
||||
} from './groupedTableRowPresentation';
|
||||
import { WebInterfaceNameLink } from './WebInterfaceNameLink';
|
||||
|
||||
interface NodeGroupHeaderProps {
|
||||
node: Node;
|
||||
@@ -51,16 +52,12 @@ export const NodeGroupHeader: Component<NodeGroupHeaderProps> = (props) => {
|
||||
ariaLabel={nodeStatus().label}
|
||||
size="xs"
|
||||
/>
|
||||
<a
|
||||
href={nodeUrl()}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
<WebInterfaceNameLink
|
||||
name={displayName()}
|
||||
url={nodeUrl()}
|
||||
class="transition-colors duration-150 hover:text-sky-600 dark:hover:text-sky-400"
|
||||
title={`Open ${props.node.name} web interface`}
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
>
|
||||
{displayName()}
|
||||
</a>
|
||||
/>
|
||||
<Show when={showActualName()}>
|
||||
<span class="text-[10px] text-muted">({props.node.name})</span>
|
||||
</Show>
|
||||
|
||||
@@ -650,7 +650,8 @@ describe('shared primitive guardrails', () => {
|
||||
expect(storageGroupRowSource).not.toContain('kind="scope"');
|
||||
expect(unifiedResourceHostTableCardSource).not.toContain('kind="scope"');
|
||||
|
||||
expect(nodeGroupHeaderSource).toContain('event.stopPropagation()');
|
||||
expect(nodeGroupHeaderSource).toContain('WebInterfaceNameLink');
|
||||
expect(webInterfaceNameLinkSource).toContain('event.stopPropagation()');
|
||||
});
|
||||
|
||||
it('keeps upgrade navigation split across shell, runtime, and utility owners', () => {
|
||||
@@ -942,18 +943,22 @@ describe('shared primitive guardrails', () => {
|
||||
expect(webInterfaceNameLinkSource).toContain('Open web interface for');
|
||||
expect(registeredRule?.requiredConsumers?.map((consumer) => consumer.path)).toEqual(
|
||||
expect.arrayContaining([
|
||||
'src/components/shared/NodeGroupHeader.tsx',
|
||||
'src/components/Workloads/GuestRow.tsx',
|
||||
'src/features/standalone/AgentsMachinesTable.tsx',
|
||||
'src/features/proxmox/ProxmoxNodesTable.tsx',
|
||||
]),
|
||||
);
|
||||
|
||||
expect(nodeGroupHeaderSource).toContain('WebInterfaceNameLink');
|
||||
expect(guestRowSource).toContain('WebInterfaceNameLink');
|
||||
expect(agentsMachinesTableSource).toContain('WebInterfaceNameLink');
|
||||
expect(proxmoxNodesTableSource).toContain('WebInterfaceNameLink');
|
||||
expect(nodeGroupHeaderSource).not.toContain('target="_blank"');
|
||||
expect(guestRowSource).not.toContain('target="_blank"');
|
||||
expect(agentsMachinesTableSource).not.toContain('target="_blank"');
|
||||
expect(proxmoxNodesTableSource).not.toContain('target="_blank"');
|
||||
expect(nodeGroupHeaderSource).not.toContain('rel="noopener noreferrer"');
|
||||
expect(agentsMachinesTableSource).not.toContain('AgentMachineWebLinkCell');
|
||||
expect(agentsMachinesTableSource).not.toContain('data-agent-machine-web-link');
|
||||
expect(proxmoxNodesTableSource).not.toContain('data-proxmox-host-web-link');
|
||||
|
||||
@@ -46,5 +46,10 @@ describe('NodeGroupHeader', () => {
|
||||
expect(screen.getByText('PVE 9.1.9')).toBeInTheDocument();
|
||||
expect(screen.getByText('62°C')).toBeInTheDocument();
|
||||
expect(screen.getByText('2h')).toBeInTheDocument();
|
||||
|
||||
const link = screen.getByRole('link', { name: 'Open web interface for pve1' });
|
||||
expect(link).toHaveAttribute('href', 'https://pve1:8006');
|
||||
expect(link).toHaveAttribute('target', '_blank');
|
||||
expect(link).toHaveAttribute('rel', 'noopener noreferrer');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user