Compare commits

..

7 Commits

Author SHA1 Message Date
Gandalf the Greybeard 4d80c2a541 fix: update package-lock.json to satisfy lodash override
The package.json override requires lodash >=4.18.0, but the lockfile
had 4.17.23. Regenerated lockfile with npm install --include=dev.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-26 21:30:04 +00:00
Gandalf the Greybeard ecd1e4db29 fix: override lodash >=4.18.0 to patch code injection vulnerability
GHSA-r5fr-rjxr-66jc is a code injection vulnerability in lodash
below 4.18.0. The vulnerable transitive dependency comes through
@kinvolk/headlamp-plugin.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-23 10:58:22 +00:00
privilegedescalation-engineer[bot] 823e590513 release: v1.1.0 (#49)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-21 20:52:49 +00:00
privilegedescalation-engineer[bot] 3cc0094842 fix: pass pr_number to dual-approval-check workflow (#47)
Companion PR to privilegedescalation/.github#81

Co-authored-by: Hugh Hackman <hugh@paperclip.ing>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-15 03:34:55 +00:00
privilegedescalation-cto[bot] 161d817e6c Merge pull request #48 from privilegedescalation/fix/e2e-heading-selectors
fix(e2e): use specific regex for overview heading
2026-04-15 02:29:23 +00:00
Paperclip 375f43265d fix(e2e): use specific regex for overview heading
The /intel.gpu/i regex was too broad and could match multiple headings
on the overview page, causing strict mode violations in Playwright.

Use /Intel GPU — Overview/i to match only the actual page heading,
which contains 'Intel GPU' before 'Overview'.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-14 23:45:47 +00:00
privilegedescalation-engineer[bot] b81f25ad74 fix: combine E2E infrastructure fixes (selectors + metrics heading + timeout) (#45)
QA + CTO approved. CI + E2E passing. E2E test fix PR — UAT via automated suite. Merged by CEO.
2026-04-11 14:05:48 +00:00
7 changed files with 624 additions and 490 deletions
+2
View File
@@ -16,3 +16,5 @@ jobs:
dual-approval:
uses: privilegedescalation/.github/.github/workflows/dual-approval-check.yaml@main
secrets: inherit
with:
pr_number: ${{ github.event.pull_request.number }}
+3 -3
View File
@@ -1,4 +1,4 @@
version: "1.0.0"
version: "1.1.0"
name: headlamp-intel-gpu
displayName: Intel GPU
description: >-
@@ -99,7 +99,7 @@ screenshots:
url: https://raw.githubusercontent.com/privilegedescalation/headlamp-intel-gpu-plugin/main/docs/screenshots/03-metrics.svg
annotations:
headlamp/plugin/archive-url: "https://github.com/privilegedescalation/headlamp-intel-gpu-plugin/releases/download/v1.0.0/intel-gpu-1.0.0.tar.gz"
headlamp/plugin/archive-checksum: sha256:93d6c531e7c12440c9625138f0645fc0c3521b574d0089492759699b324943f0
headlamp/plugin/archive-url: "https://github.com/privilegedescalation/headlamp-intel-gpu-plugin/releases/download/v1.1.0/intel-gpu-1.1.0.tar.gz"
headlamp/plugin/archive-checksum: sha256:e212381f38c331383604b06f6552997fcba5c8b42a3bd828e3b43ed3e5028448
headlamp/plugin/version-compat: ">=0.20.0"
headlamp/plugin/distro-compat: "in-cluster,web,app"
+7 -7
View File
@@ -19,14 +19,14 @@ test.describe('Intel GPU plugin smoke tests', () => {
// Should navigate to the overview route
await expect(page).toHaveURL(/\/intel-gpu$/);
await expect(page.getByRole('heading', { name: /intel.gpu/i })).toBeVisible();
await expect(page.getByRole('heading', { name: /Intel GPU — Overview/i })).toBeVisible();
});
test('overview page renders GPU device list or empty state', async ({ page }) => {
await page.goto('/c/main/intel-gpu');
// Overview heading should be present
await expect(page.getByRole('heading', { name: /intel.gpu/i })).toBeVisible({
await expect(page.getByRole('heading', { name: /Intel GPU — Overview/i })).toBeVisible({
timeout: 15_000,
});
@@ -43,7 +43,7 @@ test.describe('Intel GPU plugin smoke tests', () => {
test('device plugins page renders or shows empty state', async ({ page }) => {
await page.goto('/c/main/intel-gpu/device-plugins');
await expect(page.getByRole('heading', { name: /device plugin/i })).toBeVisible({
await expect(page.getByRole('heading', { name: /Intel GPU — Device Plugins/i })).toBeVisible({
timeout: 15_000,
});
@@ -61,18 +61,18 @@ test.describe('Intel GPU plugin smoke tests', () => {
// not after clicking the parent entry from the overview. Test route
// accessibility via direct navigation — each route must render its heading.
await page.goto('/c/main/intel-gpu');
await expect(page.getByRole('heading', { name: /intel.gpu/i })).toBeVisible({
await expect(page.getByRole('heading', { name: /Intel GPU — Overview/i })).toBeVisible({
timeout: 15_000,
});
await page.goto('/c/main/intel-gpu/nodes');
await expect(page.getByRole('heading', { name: /intel gpu.*nodes/i })).toBeVisible({ timeout: 15_000 });
await expect(page.getByRole('heading', { name: /Intel GPU — Nodes/i })).toBeVisible({ timeout: 15_000 });
await page.goto('/c/main/intel-gpu/pods');
await expect(page.getByRole('heading', { name: /pod/i })).toBeVisible({ timeout: 15_000 });
await expect(page.getByRole('heading', { name: /Intel GPU — Pods/i })).toBeVisible({ timeout: 15_000 });
await page.goto('/c/main/intel-gpu/metrics');
await expect(page.getByRole('heading', { name: /metric/i })).toBeVisible({ timeout: 15_000 });
await expect(page.getByRole('heading', { name: /Intel GPU — Metrics/i })).toBeVisible({ timeout: 15_000 });
});
test('plugin settings page shows intel-gpu plugin entry', async ({ page }) => {
+601 -471
View File
File diff suppressed because it is too large Load Diff
+3 -2
View File
@@ -1,6 +1,6 @@
{
"name": "intel-gpu",
"version": "1.0.0",
"version": "1.1.0",
"description": "Headlamp plugin for Intel GPU device plugin visibility and monitoring",
"repository": {
"type": "git",
@@ -44,6 +44,7 @@
},
"overrides": {
"tar": "^7.5.11",
"undici": "^7.24.3"
"undici": "^7.24.3",
"lodash": ">=4.18.0"
}
}
+3 -1
View File
@@ -106,11 +106,13 @@ describe('MetricsPage', () => {
vi.clearAllMocks();
});
it('shows loader when ctxLoading=true', () => {
it('shows loader when ctxLoading=true but heading is visible immediately', () => {
vi.mocked(useIntelGpuContext).mockReturnValue(makeContext({ loading: true }));
// fetchGpuMetrics should never be called in loading state
vi.mocked(fetchGpuMetrics).mockResolvedValue(null);
render(<MetricsPage />);
// Heading renders immediately, loader appears below it while waiting for context
expect(screen.getByText('Intel GPU — Metrics')).toBeInTheDocument();
expect(screen.getByTestId('loader')).toHaveTextContent('Loading Intel GPU data...');
});
+5 -6
View File
@@ -230,10 +230,6 @@ export default function MetricsPage() {
};
}, [ctxLoading, fetchSeq]);
if (ctxLoading) {
return <Loader title="Loading Intel GPU data..." />;
}
return (
<>
<div
@@ -247,7 +243,7 @@ export default function MetricsPage() {
<SectionHeader title="Intel GPU — Metrics" />
<button
onClick={() => void doFetch()}
disabled={fetching}
disabled={fetching || ctxLoading}
aria-label="Refresh metrics"
style={{
padding: '6px 16px',
@@ -255,15 +251,18 @@ export default function MetricsPage() {
color: 'var(--mui-palette-primary-main, #0071c5)',
border: '1px solid var(--mui-palette-primary-main, #0071c5)',
borderRadius: '4px',
cursor: 'pointer',
cursor: fetching || ctxLoading ? 'not-allowed' : 'pointer',
fontSize: '13px',
fontWeight: 500,
opacity: fetching || ctxLoading ? 0.6 : 1,
}}
>
{fetching ? 'Refreshing…' : 'Refresh'}
</button>
</div>
{ctxLoading && <Loader title="Loading Intel GPU data..." />}
<MetricRequirements />
{fetching && !metrics && <Loader title="Querying Prometheus for GPU metrics..." />}