Merge remote-tracking branch 'origin/main' into fix/workloads-backup-pill-width

This commit is contained in:
rcourtman
2026-10-05 16:42:46 +01:00
4 changed files with 104 additions and 3 deletions
@@ -1215,7 +1215,10 @@ render through `KubernetesNameText`, which splits off the trailing segment
part that tells siblings apart (`checkout-api-6d…-x7k2p`, `prod…-03`), with the
full name on the title. Pod names, the Pods table's Node column and controller
names use it. Deployment names are chosen by people, so they keep plain end
truncation.
truncation. Head and tail are flex items, so a browser copy put a line break
between them and a pasted `kubectl logs` argument broke; a copy whose selection
sits inside one name now writes the name without the break, and wider
selections such as a whole row copy as the browser builds them.
With one cluster in view, every Kubernetes scope cell repeated the cluster
name and crowded out the namespace that differs between rows, so the tables
show the namespace alone (`Cluster` for cluster-scoped objects) through
@@ -0,0 +1,37 @@
{
"version": 1,
"base_sha": "834fcaeb366b2644bfa5c081e81bb2f286c34e55",
"verified_at": "2026-10-05T15:03:48Z",
"result": "passed",
"changed_paths": [
"frontend-modern/src/features/kubernetes/KubernetesNameText.tsx"
],
"content_sha256": {
"frontend-modern/src/features/kubernetes/KubernetesNameText.tsx": "0d293feee3fc1f1bdda26000013b44199963a8054fb18990cfc7dc62363e03d1"
},
"routes": [
"/kubernetes/pods"
],
"viewports": [
{
"width": 1440,
"height": 900
},
{
"width": 768,
"height": 1024
}
],
"states": [
"Private mock-mode stack built from this tree (backend :7745, Vite :5265), headless Chromium via Playwright with clipboard read/write granted.",
"Pods table Pod name cron-nightly-backfill-28918234, Pods table Node column prod-euw1-k8s-03 and Workload controllers name nightly-backfill-28918234 at both widths. At 768 px the pod and controller heads truncate (cron-ni\u2026 -28918234) and the tails stay visible, unchanged from before the fix.",
"Before the fix (component swapped back via HMR) every one of the six copies put a newline between head and tail in the clipboard, e.g. cron-nightly-backfill\\n-28918234. After the fix all six clipboard reads equal the bare name.",
"A whole-row selection still copies as the browser builds it (cells on separate lines), so wider selections are not rewritten. No page errors.",
"Not verified: Firefox or Safari clipboard serialization, or a pasted kubectl command against a real cluster."
],
"interactions": [
"Programmatic Range over each name wrapper, then a real ControlOrMeta+C keypress, then navigator.clipboard.readText().",
"Real mouse drag from the first to the last character of each name, then ControlOrMeta+C and a clipboard read; selection.toString() still holds the newline while the clipboard does not.",
"Range over the whole cron-nightly-backfill pod row, ControlOrMeta+C, clipboard read to confirm the handler leaves it alone."
]
}
@@ -11,8 +11,23 @@ import { splitKubernetesNameTail } from './kubernetesPageModel';
*/
export const KubernetesNameText: Component<{ name: string; class?: string }> = (props) => {
const parts = () => splitKubernetesNameTail(props.name);
// The head and tail are flex items, so a plain copy inserts a line break
// between them and a pasted `kubectl logs` breaks. A selection inside one
// name copies without it; wider selections copy as the browser builds them.
const copyWithoutBreak = (event: ClipboardEvent & { currentTarget: HTMLSpanElement }) => {
const selection = window.getSelection();
if (!selection || selection.rangeCount === 0 || !event.clipboardData) return;
if (!event.currentTarget.contains(selection.getRangeAt(0).commonAncestorContainer)) return;
event.clipboardData.setData('text/plain', selection.toString().replace(/\r?\n/g, ''));
event.preventDefault();
};
return (
<span class={`flex min-w-0 ${props.class ?? ''}`} title={props.name} data-kubernetes-name>
<span
class={`flex min-w-0 ${props.class ?? ''}`}
title={props.name}
data-kubernetes-name
onCopy={copyWithoutBreak}
>
<span class="truncate">{parts().head}</span>
<span class="max-w-full shrink-0 truncate" data-kubernetes-name-tail>
{parts().tail}
@@ -1,5 +1,5 @@
import { cleanup, render, screen } from '@solidjs/testing-library';
import { afterEach, describe, expect, it } from 'vitest';
import { afterEach, describe, expect, it, vi } from 'vitest';
import type { Resource } from '@/types/resource';
import { KubernetesPodsTable } from '../KubernetesPodsTable';
@@ -152,6 +152,52 @@ describe('KubernetesPodsTable', () => {
).toBeNull();
});
it('copies a generated name without a break between its head and tail', () => {
const { container } = render(() => (
<KubernetesPodsTable
resources={[
makeResource({
id: 'cron-nightly-backfill-28918234',
kubernetes: {
clusterId: 'prod-euw1',
namespace: 'batch',
nodeName: 'prod-euw1-k8s-02',
podName: 'cron-nightly-backfill-28918234',
podPhase: 'Running',
},
}),
]}
emptyIcon={<span />}
emptyTitle="No pods"
emptyDescription="No pods"
showToolbar={false}
/>
));
// Head and tail are flex items, so a browser copy would put a line break
// between them; a selection inside one name copies as the name.
const podName = screen.getByText(kubernetesName('cron-nightly-backfill-28918234'));
expect(podName.querySelector('[data-kubernetes-name-tail]')).toHaveTextContent('-28918234');
const copy = (selected: Node) => {
const range = document.createRange();
range.selectNodeContents(selected);
window.getSelection()?.removeAllRanges();
window.getSelection()?.addRange(range);
const setData = vi.fn();
const event = new Event('copy', { bubbles: true, cancelable: true });
Object.defineProperty(event, 'clipboardData', { value: { setData } });
podName.dispatchEvent(event);
return { setData, prevented: event.defaultPrevented };
};
const inside = copy(podName);
expect(inside.setData).toHaveBeenCalledWith('text/plain', 'cron-nightly-backfill-28918234');
expect(inside.prevented).toBe(true);
// A selection wider than the name (a whole row) is left to the browser.
const wider = copy(container);
expect(wider.setData).not.toHaveBeenCalled();
expect(wider.prevented).toBe(false);
});
it('renders pod rows with status mapped from podPhase + container readiness, attention rows first', () => {
render(() => (
<KubernetesPodsTable