Compare commits

..

9 Commits

Author SHA1 Message Date
gitea-actions[bot] 2a85f2a3d1 ci: update artifact hub metadata for v0.1.3 2026-02-07 19:51:35 +00:00
Chris Farhood c4e3c20a41 chore: bump version to 0.1.3
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 14:49:58 -05:00
Chris Farhood 50caae256d fix: skipped display, namespace link crash, overview redesign
- Fix skipped count showing empty by rendering as plain text instead
  of StatusLabel with empty status (which renders near-invisible)
- Fix namespace link crash by using Router.createRouteURL to generate
  cluster-prefixed URLs with react-router-dom Link, instead of
  Headlamp's Link component which crashes on plugin-registered routes
- Redesign overview page with PercentageCircle score chart and
  PercentageBar check distribution for a better visual experience

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 14:45:39 -05:00
Chris Farhood 3784b9b1c8 docs: update README for consolidated dashboard and current architecture
Remove references to deleted Full Audit page and DynamicSidebarRegistrar.
Add Namespaces page, skipped checks, test commands, and NamespacesListView
to project structure. Fix stale version numbers in install examples.
Consolidate CI/release docs to match single Gitea Actions workflow.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 14:35:30 -05:00
gitea-actions[bot] 6760841b22 ci: update artifact hub metadata for v0.1.2 2026-02-07 19:21:46 +00:00
Chris Farhood ce32783fe6 chore: bump version to 0.1.2
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 14:20:45 -05:00
Chris Farhood 3b0287bf19 Merge pull request 'feat: consolidate dashboard pages, fix namespace links, add tests' (#18) from feat/consolidate-dashboard-fix-namespace-links into main
Reviewed-on: farhoodliquor/headlamp-polaris-plugin#18
2026-02-07 14:20:00 -05:00
Chris Farhood 101b663867 style: fix prettier formatting in NamespacesListView
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 14:17:38 -05:00
Chris Farhood 6281dbfa5e feat: consolidate dashboard pages, fix namespace links, add tests
Merge Overview and Full Audit into a single dashboard page that always
shows the skipped check count. Fix namespace link 404s by using
Headlamp's Link component (which generates cluster-prefixed URLs)
instead of raw react-router-dom Link. Add vitest unit tests for all
polaris.ts utility functions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 14:10:08 -05:00
10 changed files with 373 additions and 118 deletions
+27 -32
View File
@@ -8,10 +8,10 @@ A [Headlamp](https://headlamp.dev/) plugin that surfaces [Fairwinds Polaris](htt
Adds a **Polaris** top-level sidebar section to Headlamp with the following views:
- **Overview** -- cluster score as a percentage (color-coded green/amber/red), check summary (pass/warning/danger counts), and cluster info (nodes, pods, namespaces, controllers)
- **Full Audit** -- same as overview but includes skipped checks in the totals
- **Namespace drill-down** -- per-namespace score, check counts, and a resource table showing pass/warning/danger per workload. Namespace entries appear dynamically in the sidebar based on live audit data.
- **External link** -- quick jump to the native Polaris dashboard via the Kubernetes service proxy
- **Overview** -- cluster score as a percentage (color-coded green/amber/red), check summary (pass/warning/danger/skipped counts), and cluster info (nodes, pods, namespaces, controllers)
- **Namespaces** -- table of all namespaces with per-namespace score, pass/warning/danger/skipped counts; click a namespace to drill down
- **Namespace detail** -- per-namespace score, check counts, and a resource table showing pass/warning/danger per workload
- **External link** -- quick jump to the native Polaris dashboard via the Kubernetes service proxy (from namespace detail view)
Data is fetched from the Polaris dashboard API through the Kubernetes service proxy (`/api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json`). The plugin is read-only -- it never writes to the cluster.
@@ -52,7 +52,7 @@ Add it as an init container in your Headlamp Helm values:
```yaml
initContainers:
- name: polaris-plugin
image: git.farh.net/farhoodliquor/headlamp-polaris-plugin:v0.0.1
image: git.farh.net/farhoodliquor/headlamp-polaris-plugin:latest
command: ["sh", "-c", "cp -r /plugins/* /headlamp/plugins/"]
volumeMounts:
- name: plugins
@@ -72,7 +72,7 @@ volumeMounts:
Download the `.tar.gz` from the [GitHub releases page](https://github.com/cpfarhood/headlamp-polaris-plugin/releases) or the [Gitea releases page](https://git.farh.net/farhoodliquor/headlamp-polaris-plugin/releases), then extract into Headlamp's plugin directory:
```bash
tar xzf headlamp-polaris-plugin-0.0.1.tar.gz -C /headlamp/plugins/
tar xzf headlamp-polaris-plugin-<version>.tar.gz -C /headlamp/plugins/
```
### Option 4: Build from source
@@ -172,10 +172,13 @@ npm run build # outputs dist/main.js
npm run package # creates headlamp-polaris-plugin-<version>.tar.gz
```
### Type-check
### Type-check, lint, format, and test
```bash
npm run tsc
npm run tsc # type-check without emitting
npm run lint # eslint
npm run format:check # prettier check
npm test # vitest unit tests
```
## Project Structure
@@ -186,12 +189,14 @@ src/
api/
polaris.ts -- TypeScript types (AuditData schema), usePolarisData hook,
countResults utilities, refresh interval settings.
polaris.test.ts -- Unit tests for utility functions (vitest).
PolarisDataContext.tsx -- React context provider; shared data fetch across views.
components/
DashboardView.tsx -- Overview / Full Audit page (score, check summary, cluster info).
DashboardView.tsx -- Overview page (score, check summary with skipped, cluster info).
NamespacesListView.tsx -- Namespace list with scores and links to detail views.
NamespaceDetailView.tsx -- Per-namespace drill-down with resource table.
DynamicSidebarRegistrar.tsx -- Registers sidebar entries dynamically from audit namespaces.
PolarisSettings.tsx -- Plugin settings page (refresh interval selector).
vitest.config.mts -- Vitest configuration (jsdom environment).
```
## Data Source
@@ -215,48 +220,38 @@ AuditData
Results{} -- container-level check results
```
Each check in a `ResultSet` has `Success` (bool) and `Severity` (`"warning"`, `"danger"`, or `"ignore"`). The cluster score is computed client-side as `pass / total * 100`.
Each check in a `ResultSet` has `Success` (bool) and `Severity` (`"warning"`, `"danger"`, or `"ignore"`). Checks with `Severity: "ignore"` and `Success: false` are counted as skipped. The cluster score is computed client-side as `pass / total * 100`.
## Releasing
Releases are automated via CI. To cut a release:
```bash
# Bump version in package.json and artifacthub-pkg.yml, then:
git add package.json package-lock.json artifacthub-pkg.yml
git commit -m "chore: bump version to 0.0.2"
git tag v0.0.2
git push origin main v0.0.2
# Bump version in package.json and artifacthub-pkg.yml (version + archive-url), then:
git add package.json artifacthub-pkg.yml
git commit -m "chore: bump version to X.Y.Z"
git tag vX.Y.Z
git push origin main vX.Y.Z
```
This triggers two CI pipelines:
**Gitea Actions** (`.gitea/workflows/release.yaml`):
This triggers the **Gitea Actions** release workflow (`.gitea/workflows/release.yaml`):
1. Build the plugin in a `node:20` container
2. Package a `.tar.gz` tarball
3. Build and push a Docker image to `git.farh.net/farhoodliquor/headlamp-polaris-plugin:{tag}` and `:latest`
4. Create a Gitea release with the tarball attached
5. Create a GitHub release with the same tarball (for Artifact Hub)
6. Update `artifacthub-pkg.yml` checksum on main and force-move the tag to match
**GitHub Actions** (`.github/workflows/release.yml`):
1. Build and package the plugin
2. Create a GitHub release with the tarball attached (required for Artifact Hub)
The Gitea repo push-mirrors to GitHub automatically, so both pipelines trigger from a single `git push`.
A guard step prevents infinite loops: if the release tarball checksum already matches the metadata, the build is skipped.
### CI secrets
| Secret | Where | Purpose |
|---|---|---|
| `REGISTRY_TOKEN` | Gitea | Personal access token with `package:write` scope for Docker image push |
| `GH_PAT` | Gitea | GitHub personal access token for creating GitHub releases |
The Gitea release uses the built-in `github.token`. The GitHub release uses the default `GITHUB_TOKEN` with `contents: write` permission.
### Updating Artifact Hub
When releasing a new version, update `artifacthub-pkg.yml`:
- `version` field
- `headlamp/plugin/archive-url` annotation (update the version in the download URL)
- `headlamp/plugin/archive-checksum` annotation (SHA256 of the new tarball, printed by the CI build)
The Gitea release uses the built-in `github.token`. The `archive-checksum` in `artifacthub-pkg.yml` is updated automatically by the release workflow.
## Links
+3 -3
View File
@@ -1,4 +1,4 @@
version: 0.1.1
version: 0.1.3
name: headlamp-polaris-plugin
displayName: Polaris
createdAt: "2026-02-05T19:00:00Z"
@@ -28,7 +28,7 @@ maintainers:
- name: cpfarhood
email: "chris@farhood.org"
annotations:
headlamp/plugin/archive-url: "https://github.com/cpfarhood/headlamp-polaris-plugin/releases/download/v0.1.1/headlamp-polaris-plugin-0.1.1.tar.gz"
headlamp/plugin/archive-url: "https://github.com/cpfarhood/headlamp-polaris-plugin/releases/download/v0.1.3/headlamp-polaris-plugin-0.1.3.tar.gz"
headlamp/plugin/version-compat: ">=0.26"
headlamp/plugin/archive-checksum: sha256:5a73bbbcfce3d6158c54409e7673b2d08d8e60c21d11fd0114e33e8eb8eb58b5
headlamp/plugin/archive-checksum: sha256:ddb92d40475d9c2ee1e755f4c83744529d86a1318dea729e6de8b4360d7890c7
headlamp/plugin/distro-compat: in-cluster
+9 -3
View File
@@ -23,6 +23,9 @@ npx tsc --noEmit
# Lint
npx eslint src/
# Run tests
npm test
```
## Architecture
@@ -32,15 +35,18 @@ src/
├── index.tsx # Entry point: registers sidebar entries + routes
├── api/
│ ├── polaris.ts # Types (AuditData schema), usePolarisData hook, countResults utilities, refresh settings
│ ├── polaris.test.ts # Unit tests for utility functions (vitest)
│ └── PolarisDataContext.tsx # React context provider for shared data fetch
└── components/
├── DashboardView.tsx # Overview / Full Audit page (score, check summary, cluster info)
├── DashboardView.tsx # Overview page (score, check summary with skipped count, cluster info)
├── NamespacesListView.tsx # Namespace list with scores and links to detail views
├── NamespaceDetailView.tsx # Per-namespace drill-down with resource table
├── DynamicSidebarRegistrar.tsx # Registers namespace sidebar entries from live audit data
└── PolarisSettings.tsx # Plugin settings (refresh interval selector)
```
Top-level sidebar section at `/polaris` with sub-routes for full audit (`/polaris/full-audit`) and per-namespace views (`/polaris/ns/:namespace`). Data is fetched via `ApiProxy.request` to the Polaris dashboard service proxy and refreshed on a user-configurable interval (stored in localStorage under `polaris-plugin-refresh-interval`, default 5 minutes). Score is computed from result counts (pass/total).
Top-level sidebar section at `/polaris` with sub-routes for namespaces list (`/polaris/namespaces`) and per-namespace views (`/polaris/ns/:namespace`). Data is fetched via `ApiProxy.request` to the Polaris dashboard service proxy and refreshed on a user-configurable interval (stored in localStorage under `polaris-plugin-refresh-interval`, default 5 minutes). Score is computed from result counts (pass/total). Skipped checks are always displayed in summaries.
**Sidebar limitation**: Headlamp's sidebar only supports 2-level nesting (parent → children). The `Collapse` component is driven by route-based selection, not click-to-toggle, so 3-level hierarchies don't expand properly. Namespace navigation is handled via the in-content table on the Namespaces page instead.
## Security / RBAC Requirements
+4 -2
View File
@@ -1,6 +1,6 @@
{
"name": "headlamp-polaris-plugin",
"version": "0.1.1",
"version": "0.1.3",
"description": "Headlamp plugin for Fairwinds Polaris audit results",
"scripts": {
"start": "headlamp-plugin start",
@@ -10,7 +10,9 @@
"lint": "eslint --ext .ts,.tsx src/",
"lint:fix": "eslint --ext .ts,.tsx --fix src/",
"format": "prettier --write src/",
"format:check": "prettier --check src/"
"format:check": "prettier --check src/",
"test": "vitest run",
"test:watch": "vitest"
},
"devDependencies": {
"@kinvolk/headlamp-plugin": "^0.13.0"
+264
View File
@@ -0,0 +1,264 @@
import { describe, expect, it, vi } from 'vitest';
vi.mock('@kinvolk/headlamp-plugin/lib', () => ({
ApiProxy: { request: vi.fn() },
}));
import {
AuditData,
computeScore,
countResults,
countResultsForItems,
filterResultsByNamespace,
getNamespaces,
Result,
ResultCounts,
} from './polaris';
// --- Fixtures ---
function makeResult(overrides: Partial<Result> = {}): Result {
return {
Name: 'my-deploy',
Namespace: 'default',
Kind: 'Deployment',
Results: {},
CreatedTime: '2025-01-01T00:00:00Z',
...overrides,
};
}
function makeAuditData(results: Result[]): AuditData {
return {
PolarisOutputVersion: '1.0',
AuditTime: '2025-01-01T00:00:00Z',
SourceType: 'Cluster',
SourceName: 'test',
DisplayName: 'test',
ClusterInfo: { Version: '1.28', Nodes: 3, Pods: 10, Namespaces: 2, Controllers: 5 },
Results: results,
};
}
// --- computeScore ---
describe('computeScore', () => {
it('returns 0 when total is 0', () => {
const counts: ResultCounts = { total: 0, pass: 0, warning: 0, danger: 0, skipped: 0 };
expect(computeScore(counts)).toBe(0);
});
it('returns 100 when all checks pass', () => {
const counts: ResultCounts = { total: 10, pass: 10, warning: 0, danger: 0, skipped: 0 };
expect(computeScore(counts)).toBe(100);
});
it('rounds to nearest integer', () => {
const counts: ResultCounts = { total: 3, pass: 1, warning: 1, danger: 1, skipped: 0 };
expect(computeScore(counts)).toBe(33);
});
it('includes skipped in total denominator', () => {
const counts: ResultCounts = { total: 10, pass: 5, warning: 2, danger: 1, skipped: 2 };
expect(computeScore(counts)).toBe(50);
});
});
// --- countResults / countResultsForItems ---
describe('countResults', () => {
it('returns zero counts for empty results', () => {
const data = makeAuditData([]);
const counts = countResults(data);
expect(counts).toEqual({ total: 0, pass: 0, warning: 0, danger: 0, skipped: 0 });
});
it('counts top-level result set entries', () => {
const result = makeResult({
Results: {
check1: {
ID: 'check1',
Message: 'ok',
Details: [],
Success: true,
Severity: 'warning',
Category: 'Security',
},
check2: {
ID: 'check2',
Message: 'bad',
Details: [],
Success: false,
Severity: 'danger',
Category: 'Security',
},
},
});
const counts = countResults(makeAuditData([result]));
expect(counts.total).toBe(2);
expect(counts.pass).toBe(1);
expect(counts.danger).toBe(1);
expect(counts.warning).toBe(0);
expect(counts.skipped).toBe(0);
});
it('counts skipped (severity=ignore, success=false) entries', () => {
const result = makeResult({
Results: {
skipped1: {
ID: 'skipped1',
Message: 'skipped',
Details: [],
Success: false,
Severity: 'ignore',
Category: 'Security',
},
},
});
const counts = countResults(makeAuditData([result]));
expect(counts.total).toBe(1);
expect(counts.skipped).toBe(1);
expect(counts.pass).toBe(0);
});
it('counts PodResult and ContainerResults', () => {
const result = makeResult({
Results: {
top: {
ID: 'top',
Message: 'ok',
Details: [],
Success: true,
Severity: 'warning',
Category: 'Reliability',
},
},
PodResult: {
Name: 'pod-1',
Results: {
podCheck: {
ID: 'podCheck',
Message: 'warn',
Details: [],
Success: false,
Severity: 'warning',
Category: 'Reliability',
},
},
ContainerResults: [
{
Name: 'container-1',
Results: {
containerCheck: {
ID: 'containerCheck',
Message: 'danger',
Details: [],
Success: false,
Severity: 'danger',
Category: 'Security',
},
},
},
],
},
});
const counts = countResults(makeAuditData([result]));
expect(counts.total).toBe(3);
expect(counts.pass).toBe(1);
expect(counts.warning).toBe(1);
expect(counts.danger).toBe(1);
});
it('aggregates across multiple results', () => {
const r1 = makeResult({
Name: 'deploy-a',
Results: {
c1: {
ID: 'c1',
Message: '',
Details: [],
Success: true,
Severity: 'warning',
Category: 'X',
},
},
});
const r2 = makeResult({
Name: 'deploy-b',
Results: {
c2: {
ID: 'c2',
Message: '',
Details: [],
Success: false,
Severity: 'warning',
Category: 'X',
},
},
});
const counts = countResults(makeAuditData([r1, r2]));
expect(counts.total).toBe(2);
expect(counts.pass).toBe(1);
expect(counts.warning).toBe(1);
});
});
describe('countResultsForItems', () => {
it('works on a subset of results', () => {
const results: Result[] = [
makeResult({
Results: {
a: {
ID: 'a',
Message: '',
Details: [],
Success: false,
Severity: 'danger',
Category: 'X',
},
},
}),
];
const counts = countResultsForItems(results);
expect(counts.danger).toBe(1);
expect(counts.total).toBe(1);
});
});
// --- getNamespaces ---
describe('getNamespaces', () => {
it('returns empty array for no results', () => {
expect(getNamespaces(makeAuditData([]))).toEqual([]);
});
it('returns sorted unique namespaces', () => {
const data = makeAuditData([
makeResult({ Namespace: 'beta' }),
makeResult({ Namespace: 'alpha' }),
makeResult({ Namespace: 'beta' }),
makeResult({ Namespace: 'gamma' }),
]);
expect(getNamespaces(data)).toEqual(['alpha', 'beta', 'gamma']);
});
});
// --- filterResultsByNamespace ---
describe('filterResultsByNamespace', () => {
it('returns only results matching the namespace', () => {
const data = makeAuditData([
makeResult({ Name: 'a', Namespace: 'ns1' }),
makeResult({ Name: 'b', Namespace: 'ns2' }),
makeResult({ Name: 'c', Namespace: 'ns1' }),
]);
const filtered = filterResultsByNamespace(data, 'ns1');
expect(filtered).toHaveLength(2);
expect(filtered.map(r => r.Name)).toEqual(['a', 'c']);
});
it('returns empty array for non-existent namespace', () => {
const data = makeAuditData([makeResult({ Namespace: 'ns1' })]);
expect(filterResultsByNamespace(data, 'ns-missing')).toEqual([]);
});
});
+37 -50
View File
@@ -1,70 +1,60 @@
import {
Loader,
NameValueTable,
PercentageBar,
PercentageCircle,
SectionBox,
SectionHeader,
StatusLabel,
} from '@kinvolk/headlamp-plugin/lib/CommonComponents';
import React from 'react';
import { AuditData, countResults, ResultCounts } from '../api/polaris';
import { AuditData, computeScore, countResults, ResultCounts } from '../api/polaris';
import { usePolarisDataContext } from '../api/PolarisDataContext';
function scoreStatus(score: number): 'success' | 'warning' | 'error' {
if (score >= 80) return 'success';
if (score >= 50) return 'warning';
return 'error';
}
const COLORS = {
pass: '#4caf50',
warning: '#ff9800',
danger: '#f44336',
skipped: '#9e9e9e',
};
function OverviewSection(props: {
data: AuditData;
counts: ResultCounts;
includeSkipped: boolean;
}) {
const { counts, includeSkipped } = props;
function OverviewSection(props: { data: AuditData; counts: ResultCounts }) {
const { counts } = props;
const score = computeScore(counts);
const displayTotal = includeSkipped ? counts.total : counts.total - counts.skipped;
const displayPass = counts.pass;
const score = displayTotal === 0 ? 0 : Math.round((displayPass / displayTotal) * 100);
const status = scoreStatus(score);
const summaryRows: { name: string; value: React.ReactNode }[] = [
{ name: 'Total Checks', value: String(displayTotal) },
{
name: 'Pass',
value: <StatusLabel status="success">{counts.pass}</StatusLabel>,
},
{
name: 'Warning',
value: <StatusLabel status="warning">{counts.warning}</StatusLabel>,
},
{
name: 'Danger',
value: <StatusLabel status="error">{counts.danger}</StatusLabel>,
},
const chartData = [
{ name: 'Pass', value: counts.pass, fill: COLORS.pass },
{ name: 'Warning', value: counts.warning, fill: COLORS.warning },
{ name: 'Danger', value: counts.danger, fill: COLORS.danger },
{ name: 'Skipped', value: counts.skipped, fill: COLORS.skipped },
];
if (includeSkipped) {
summaryRows.push({
name: 'Skipped',
value: <StatusLabel status="">{counts.skipped}</StatusLabel>,
});
}
return (
<>
<SectionBox title="Score">
<SectionBox title="Cluster Score">
<PercentageCircle data={chartData} total={counts.total} label={`${score}%`} />
</SectionBox>
<SectionBox title="Check Distribution">
<PercentageBar data={chartData} total={counts.total} />
<NameValueTable
rows={[
{ name: 'Total Checks', value: String(counts.total) },
{
name: 'Cluster Score',
value: <StatusLabel status={status}>{score}%</StatusLabel>,
name: 'Pass',
value: <StatusLabel status="success">{counts.pass}</StatusLabel>,
},
{
name: 'Warning',
value: <StatusLabel status="warning">{counts.warning}</StatusLabel>,
},
{
name: 'Danger',
value: <StatusLabel status="error">{counts.danger}</StatusLabel>,
},
{ name: 'Skipped', value: String(counts.skipped) },
]}
/>
</SectionBox>
<SectionBox title="Check Summary">
<NameValueTable rows={summaryRows} />
</SectionBox>
<SectionBox title="Cluster Info">
<NameValueTable
rows={[
@@ -79,9 +69,8 @@ function OverviewSection(props: {
);
}
export default function DashboardView(props: { includeSkipped: boolean }) {
export default function DashboardView() {
const { data, loading, error } = usePolarisDataContext();
const title = props.includeSkipped ? 'Polaris — Full Audit' : 'Polaris — Overview';
if (loading) {
return <Loader title="Loading Polaris audit data..." />;
@@ -91,7 +80,7 @@ export default function DashboardView(props: { includeSkipped: boolean }) {
return (
<>
<SectionHeader title={title} />
<SectionHeader title="Polaris — Overview" />
{error && (
<SectionBox title="Error">
@@ -106,9 +95,7 @@ export default function DashboardView(props: { includeSkipped: boolean }) {
</SectionBox>
)}
{data && counts && (
<OverviewSection data={data} counts={counts} includeSkipped={props.includeSkipped} />
)}
{data && counts && <OverviewSection data={data} counts={counts} />}
{!data && !error && (
<SectionBox title="No Data">
+4
View File
@@ -118,6 +118,10 @@ export default function NamespaceDetailView() {
name: 'Danger',
value: <StatusLabel status="error">{counts.danger}</StatusLabel>,
},
{
name: 'Skipped',
value: String(counts.skipped),
},
]}
/>
</SectionBox>
+16 -7
View File
@@ -1,3 +1,4 @@
import { Router } from '@kinvolk/headlamp-plugin/lib';
import {
Loader,
NameValueTable,
@@ -28,6 +29,7 @@ interface NamespaceRow {
pass: number;
warning: number;
danger: number;
skipped: number;
}
export default function NamespacesListView() {
@@ -77,6 +79,7 @@ export default function NamespacesListView() {
pass: counts.pass,
warning: counts.warning,
danger: counts.danger,
skipped: counts.skipped,
};
});
@@ -89,7 +92,13 @@ export default function NamespacesListView() {
{
label: 'Namespace',
getter: (row: NamespaceRow) => (
<Link to={`/polaris/ns/${row.namespace}`}>{row.namespace}</Link>
<Link
to={Router.createRouteURL('polaris-namespace', {
namespace: row.namespace,
})}
>
{row.namespace}
</Link>
),
},
{
@@ -100,9 +109,7 @@ export default function NamespacesListView() {
},
{
label: 'Pass',
getter: (row: NamespaceRow) => (
<StatusLabel status="success">{row.pass}</StatusLabel>
),
getter: (row: NamespaceRow) => <StatusLabel status="success">{row.pass}</StatusLabel>,
},
{
label: 'Warning',
@@ -112,9 +119,11 @@ export default function NamespacesListView() {
},
{
label: 'Danger',
getter: (row: NamespaceRow) => (
<StatusLabel status="error">{row.danger}</StatusLabel>
),
getter: (row: NamespaceRow) => <StatusLabel status="error">{row.danger}</StatusLabel>,
},
{
label: 'Skipped',
getter: (row: NamespaceRow) => String(row.skipped),
},
]}
data={rows}
+1 -21
View File
@@ -28,14 +28,6 @@ registerSidebarEntry({
icon: 'mdi:view-dashboard',
});
registerSidebarEntry({
parent: 'polaris',
name: 'polaris-full',
label: 'Full Audit',
url: '/polaris/full-audit',
icon: 'mdi:clipboard-text-search',
});
registerSidebarEntry({
parent: 'polaris',
name: 'polaris-namespaces',
@@ -53,19 +45,7 @@ registerRoute({
exact: true,
component: () => (
<PolarisDataProvider>
<DashboardView includeSkipped={false} />
</PolarisDataProvider>
),
});
registerRoute({
path: '/polaris/full-audit',
sidebar: 'polaris-full',
name: 'polaris-full-audit',
exact: true,
component: () => (
<PolarisDataProvider>
<DashboardView includeSkipped />
<DashboardView />
</PolarisDataProvider>
),
});
+8
View File
@@ -0,0 +1,8 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'jsdom',
},
});