diff --git a/README.md b/README.md index 35ca4d4..d73104f 100644 --- a/README.md +++ b/README.md @@ -48,9 +48,14 @@ Polaris must be deployed in the `polaris` namespace with the dashboard component ## Installing -### Option 1: Headlamp Plugin Manager (Recommended) +The plugin is published on [Artifact Hub](https://artifacthub.io/packages/headlamp/polaris/headlamp-polaris-plugin). Install via the Headlamp UI: -The plugin is published on [Artifact Hub](https://artifacthub.io/packages/headlamp/polaris/headlamp-polaris-plugin). Configure Headlamp via Helm: +1. Go to **Settings → Plugins** +2. Click **Catalog** tab +3. Search for "Polaris" +4. Click **Install** + +Or configure Headlamp via Helm: ```yaml config: @@ -62,56 +67,6 @@ pluginsManager: url: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/download/v0.3.10/polaris-0.3.10.tar.gz ``` -Or install via the Headlamp UI: - -1. Go to **Settings → Plugins** -2. Click **Catalog** tab -3. Search for "Polaris" -4. Click **Install** - -### Option 2: Sidecar Container (Alternative) - -For detailed sidecar installation instructions, see [docs/DEPLOYMENT.md#installation-method-2-sidecar-container](docs/DEPLOYMENT.md#installation-method-2-sidecar-container). - -```yaml -sidecars: - - name: headlamp-plugin - image: node:lts-alpine - command: ['/bin/sh'] - args: - - -c - - | - npm install -g @kinvolk/headlamp-plugin - headlamp-plugin install --config /config/plugin.yml - tail -f /dev/null - volumeMounts: - - name: plugins - mountPath: /headlamp/plugins - - name: plugin-config - mountPath: /config -``` - -### Option 3: Manual Tarball Install - -Download the `.tar.gz` from the [GitHub releases page](https://github.com/privilegedescalation/headlamp-polaris-plugin/releases), then extract into Headlamp's plugin directory: - -```bash -wget https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/download/v0.3.10/polaris-0.3.10.tar.gz -tar xzf polaris-0.3.10.tar.gz -C /headlamp/plugins/ -``` - -### Option 4: Build from Source - -```bash -git clone https://github.com/privilegedescalation/headlamp-polaris-plugin.git -cd headlamp-polaris-plugin -npm install -npm run build -npx @kinvolk/headlamp-plugin extract . /headlamp/plugins -``` - -For complete installation instructions including Helm integration, FluxCD examples, and production deployment checklist, see **[docs/DEPLOYMENT.md](docs/DEPLOYMENT.md)**. - ## RBAC / Security Setup The plugin fetches audit data through the Kubernetes API server's **service proxy** sub-resource. The identity making the request (Headlamp's service account, or the user's own token in token-auth mode) must be granted: diff --git a/artifacthub-pkg.yml b/artifacthub-pkg.yml index 5cc2b34..7d1be52 100644 --- a/artifacthub-pkg.yml +++ b/artifacthub-pkg.yml @@ -1,4 +1,4 @@ -version: "0.7.0" +version: "0.7.1" name: headlamp-polaris displayName: Polaris createdAt: "2026-02-05T19:00:00Z" @@ -28,7 +28,7 @@ maintainers: - name: privilegedescalation email: "chris@farhood.org" annotations: - headlamp/plugin/archive-url: "https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/download/v0.7.0/headlamp-polaris-0.7.0.tar.gz" + headlamp/plugin/archive-url: "https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/download/v0.7.1/headlamp-polaris-0.7.1.tar.gz" headlamp/plugin/version-compat: ">=0.26" - headlamp/plugin/archive-checksum: sha256:69b7adf74416d72580981387b1861e2b5fcf7f96ae9f0620f85f623f8b6dab56 + headlamp/plugin/archive-checksum: sha256:5ea27f3beeab9730a13a752a0a7c2270eca83dcbbe813a6cabeb6a22fa9d5174 headlamp/plugin/distro-compat: in-cluster diff --git a/src/components/AppBarScoreBadge.test.tsx b/src/components/AppBarScoreBadge.test.tsx index 75616ca..dd55067 100644 --- a/src/components/AppBarScoreBadge.test.tsx +++ b/src/components/AppBarScoreBadge.test.tsx @@ -7,13 +7,7 @@ import { makeAuditData, makeResult } from '../test-utils'; // Mock Headlamp lib vi.mock('@kinvolk/headlamp-plugin/lib', () => ({ ApiProxy: { request: vi.fn() }, - K8s: { - useCluster: () => 'test-cluster', - }, - Router: { - createRouteURL: (name: string, params?: { cluster?: string }) => - `/c/${params?.cluster ?? 'default'}/${name}`, - }, + K8s: {}, })); vi.mock('@mui/material/styles', () => ({ @@ -31,6 +25,15 @@ vi.mock('react-router-dom', () => ({ useHistory: () => ({ push: mockPush }), })); +// Set window.location.pathname for cluster extraction +beforeEach(() => { + Object.defineProperty(window, 'location', { + value: { pathname: '/c/test-cluster/some-page' }, + writable: true, + }); + mockPush.mockClear(); +}); + const mockUsePolarisDataContext = vi.fn(); vi.mock('../api/PolarisDataContext', () => ({ usePolarisDataContext: () => mockUsePolarisDataContext(), @@ -97,7 +100,7 @@ describe('AppBarScoreBadge', () => { expect(button.style.backgroundColor).toBe('rgb(244, 67, 54)'); }); - it('navigates to /polaris on click', async () => { + it('navigates to /c//polaris on click', async () => { const user = userEvent.setup(); const data = makeAuditData([ makeResult({ @@ -120,6 +123,33 @@ describe('AppBarScoreBadge', () => { expect(mockPush).toHaveBeenCalledWith('/c/test-cluster/polaris'); }); + it('navigates to /polaris when no cluster in URL', async () => { + Object.defineProperty(window, 'location', { + value: { pathname: '/settings' }, + writable: true, + }); + const user = userEvent.setup(); + const data = makeAuditData([ + makeResult({ + Results: { + c1: { + ID: 'c1', + Message: '', + Details: [], + Success: true, + Severity: 'warning', + Category: 'X', + }, + }, + }), + ]); + mockUsePolarisDataContext.mockReturnValue({ data, loading: false }); + + render(); + await user.click(screen.getByRole('button')); + expect(mockPush).toHaveBeenCalledWith('/polaris'); + }); + it('has correct aria-label', () => { const data = makeAuditData([ makeResult({ diff --git a/src/components/AppBarScoreBadge.tsx b/src/components/AppBarScoreBadge.tsx index 02b6bbb..5da51a6 100644 --- a/src/components/AppBarScoreBadge.tsx +++ b/src/components/AppBarScoreBadge.tsx @@ -1,10 +1,21 @@ -import { K8s, Router } from '@kinvolk/headlamp-plugin/lib'; import { useTheme } from '@mui/material/styles'; import React from 'react'; import { useHistory } from 'react-router-dom'; import { computeScore, countResults } from '../api/polaris'; import { usePolarisDataContext } from '../api/PolarisDataContext'; +/** + * Extract the cluster name from the current browser URL. + * Headlamp cluster routes follow the pattern /c//... + * We read window.location.pathname directly because the AppBar renders + * outside the cluster route context, so useCluster() returns null and + * React Router's useLocation() may not reflect the cluster prefix. + */ +function getClusterFromUrl(): string | null { + const match = window.location.pathname.match(/\/c\/([^/]+)/); + return match ? match[1] : null; +} + /** * App bar badge showing cluster Polaris score * Clicking navigates to the overview dashboard @@ -13,7 +24,6 @@ export default function AppBarScoreBadge() { const theme = useTheme(); const { data, loading } = usePolarisDataContext(); const history = useHistory(); - const cluster = K8s.useCluster(); if (loading || !data) { return null; // Graceful degradation when Polaris unavailable @@ -36,7 +46,9 @@ export default function AppBarScoreBadge() { }; const handleClick = () => { - history.push(Router.createRouteURL('polaris', { cluster: cluster ?? '' })); + const cluster = getClusterFromUrl(); + const prefix = cluster ? `/c/${cluster}` : ''; + history.push(`${prefix}/polaris`); }; return ( diff --git a/src/index.tsx b/src/index.tsx index 5a90bbe..e5d068f 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -99,7 +99,7 @@ registerRoute({ }); // Register plugin settings -registerPluginSettings('headlamp-polaris', PolarisSettings, true); +registerPluginSettings('polaris', PolarisSettings, true); // Register details view section for supported controller types registerDetailsViewSection(({ resource }) => {