Compare commits

...

17 Commits

Author SHA1 Message Date
gitea-actions[bot] 6e914ad71f ci: update artifact hub metadata for v0.0.7 2026-02-07 05:11:57 +00:00
Chris Farhood d923e655fe fix: correct GitHub API URLs in release workflow
Two GitHub API URLs still had the old repo name (polaris-headlamp-plugin
instead of headlamp-polaris-plugin), causing the GitHub release step to
target the wrong repository.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 00:10:02 -05:00
gitea-actions[bot] a1ef628fb5 ci: update artifact hub metadata for v0.0.7 2026-02-07 04:58:47 +00:00
Chris Farhood b8129a0dbb chore: bump version to 0.0.7
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 23:57:44 -05:00
Chris Farhood 7facb9be10 Merge pull request 'feat: add per-namespace detail pages with sidebar sub-items' (#12) from feature/polaris-detail-view into main
Reviewed-on: farhoodliquor/headlamp-polaris-plugin#12
2026-02-07 04:42:22 +00:00
Chris Farhood 1b86407d8b refactor: precompute resource counts and add return type annotation
Avoid recalculating per-resource counts 3x per table row by precomputing
them into a Map. Add explicit ResultCounts return type to resourceCounts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 23:38:10 -05:00
Chris Farhood 40df014b6b style: fix import sorting and prettier formatting
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 23:30:22 -05:00
Chris Farhood b217a8119e feat: add per-namespace detail pages with dynamic sidebar sub-items
Add drill-down namespace views under the Polaris sidebar entry. Each
namespace gets a sidebar sub-item registered dynamically from audit data,
linking to /polaris/:namespace with a score summary and per-resource table.

Introduces a shared PolarisDataContext so the sidebar registrar and view
components share a single data fetch. Also updates the Artifact Hub
repository ID.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 23:09:40 -05:00
Chris Farhood 818f4bc9cb chore: update repo URLs after rename to headlamp-polaris-plugin
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 22:21:05 -05:00
gitea-actions[bot] a6a1280e4f ci: update artifact hub metadata for v0.0.6 2026-02-07 03:03:21 +00:00
Chris Farhood 7351d88997 chore: bump version to 0.0.6
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 22:02:05 -05:00
Chris Farhood 071aab4f7e Merge pull request 'fix: use native Headlamp components for consistent styling' (#11) from fix/native-headlamp-styling into main
Reviewed-on: farhoodliquor/polaris-headlamp-plugin#11
2026-02-07 03:01:18 +00:00
Chris Farhood 40544429f4 fix: remove tarball from repo and gitignore *.tar.gz
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 22:00:15 -05:00
Chris Farhood 1f110a2846 style: fix prettier formatting
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 21:59:16 -05:00
Chris Farhood 672caec903 ci: add build step to CI workflow
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 21:56:13 -05:00
Chris Farhood b10d09fd41 feat: move refresh interval to plugin settings
Register plugin settings via registerPluginSettings so the refresh
interval is configurable from Headlamp's plugin config page instead
of being embedded in the main view header.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 21:55:13 -05:00
Chris Farhood 8b319c0c8a fix: use native Headlamp components for consistent styling
Replace inline-styled divs and native HTML elements with Headlamp's
built-in NameValueTable, StatusLabel, and HeaderLabel components so the
plugin matches the look and feel of native pages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 21:49:17 -05:00
15 changed files with 383 additions and 135 deletions
+3
View File
@@ -17,6 +17,9 @@ jobs:
- name: Install dependencies
run: npm ci
- name: Build
run: npx @kinvolk/headlamp-plugin build
- name: Lint
run: npx eslint --ext .ts,.tsx src/
+5 -5
View File
@@ -18,7 +18,7 @@ jobs:
- name: Check if release is already finalized
run: |
VERSION=${GITHUB_REF_NAME#v}
TARBALL_URL="https://github.com/cpfarhood/polaris-headlamp-plugin/releases/download/${GITHUB_REF_NAME}/polaris-headlamp-plugin-${VERSION}.tar.gz"
TARBALL_URL="https://github.com/cpfarhood/headlamp-polaris-plugin/releases/download/${GITHUB_REF_NAME}/polaris-headlamp-plugin-${VERSION}.tar.gz"
HTTP_CODE=$(curl -sL -o /tmp/release.tar.gz -w "%{http_code}" "$TARBALL_URL" 2>/dev/null)
if [ "$HTTP_CODE" = "200" ]; then
ACTUAL="sha256:$(sha256sum /tmp/release.tar.gz | awk '{print $1}')"
@@ -115,7 +115,7 @@ jobs:
continue-on-error: true
run: |
[ "$SKIP_BUILD" = "true" ] && exit 0
GH_API="https://api.github.com/repos/cpfarhood/polaris-headlamp-plugin"
GH_API="https://api.github.com/repos/cpfarhood/headlamp-polaris-plugin"
# Create release or fetch existing one
BODY=$(curl -s -X POST \
-H "Authorization: token ${{ secrets.GH_PAT }}" \
@@ -153,7 +153,7 @@ jobs:
curl -sf -X POST \
-H "Authorization: token ${{ secrets.GH_PAT }}" \
-H "Content-Type: application/gzip" \
"https://uploads.github.com/repos/cpfarhood/polaris-headlamp-plugin/releases/${RELEASE_ID}/assets?name=${TARBALL}" \
"https://uploads.github.com/repos/cpfarhood/headlamp-polaris-plugin/releases/${RELEASE_ID}/assets?name=${TARBALL}" \
--data-binary "@${TARBALL}"
echo "GitHub release updated with same tarball"
@@ -163,7 +163,7 @@ jobs:
VERSION=${GITHUB_REF_NAME#v}
git checkout main
sed -i "s|headlamp/plugin/archive-checksum:.*|headlamp/plugin/archive-checksum: sha256:${CHECKSUM}|" artifacthub-pkg.yml
sed -i "s|headlamp/plugin/archive-url:.*|headlamp/plugin/archive-url: \"https://github.com/cpfarhood/polaris-headlamp-plugin/releases/download/${GITHUB_REF_NAME}/polaris-headlamp-plugin-${VERSION}.tar.gz\"|" artifacthub-pkg.yml
sed -i "s|headlamp/plugin/archive-url:.*|headlamp/plugin/archive-url: \"https://github.com/cpfarhood/headlamp-polaris-plugin/releases/download/${GITHUB_REF_NAME}/polaris-headlamp-plugin-${VERSION}.tar.gz\"|" artifacthub-pkg.yml
sed -i "s|^version:.*|version: ${VERSION}|" artifacthub-pkg.yml
git config user.name "gitea-actions[bot]"
git config user.email "gitea-actions[bot]@git.farh.net"
@@ -178,7 +178,7 @@ jobs:
git tag -f ${GITHUB_REF_NAME}
git push -f origin ${GITHUB_REF_NAME}
# Also push to GitHub directly to avoid waiting for mirror sync
git remote add github https://x-access-token:${{ secrets.GH_PAT }}@github.com/cpfarhood/polaris-headlamp-plugin.git 2>/dev/null || true
git remote add github https://x-access-token:${{ secrets.GH_PAT }}@github.com/cpfarhood/headlamp-polaris-plugin.git 2>/dev/null || true
git push github main 2>/dev/null || true
git push -f github ${GITHUB_REF_NAME} 2>/dev/null || true
echo "Tag ${GITHUB_REF_NAME} aligned with updated metadata"
+1
View File
@@ -2,3 +2,4 @@ node_modules/
dist/
.headlamp-plugin/
.mcp.json
*.tar.gz
+7 -7
View File
@@ -40,14 +40,14 @@ Headlamp will fetch and install the plugin on startup.
### Option 2: Docker init container
The plugin ships as a container image at `git.farh.net/farhoodliquor/polaris-headlamp-plugin`.
The plugin ships as a container image at `git.farh.net/farhoodliquor/headlamp-polaris-plugin`.
Add it as an init container in your Headlamp Helm values:
```yaml
initContainers:
- name: polaris-plugin
image: git.farh.net/farhoodliquor/polaris-headlamp-plugin:v0.0.1
image: git.farh.net/farhoodliquor/headlamp-polaris-plugin:v0.0.1
command: ["sh", "-c", "cp -r /plugins/* /headlamp/plugins/"]
volumeMounts:
- name: plugins
@@ -64,7 +64,7 @@ volumeMounts:
### Option 3: Manual tarball install
Download the `.tar.gz` from the [GitHub releases page](https://github.com/cpfarhood/polaris-headlamp-plugin/releases) or the [Gitea releases page](https://git.farh.net/farhoodliquor/polaris-headlamp-plugin/releases), then extract into Headlamp's plugin directory:
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 polaris-headlamp-plugin-0.0.1.tar.gz -C /headlamp/plugins/
@@ -112,7 +112,7 @@ subjects:
### Setup
```bash
git clone https://github.com/cpfarhood/polaris-headlamp-plugin.git
git clone https://github.com/cpfarhood/headlamp-polaris-plugin.git
cd polaris-headlamp-plugin
npm install
```
@@ -193,7 +193,7 @@ This triggers two CI pipelines:
**Gitea Actions** (`.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/polaris-headlamp-plugin:{tag}` and `:latest`
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
**GitHub Actions** (`.github/workflows/release.yml`):
@@ -220,8 +220,8 @@ When releasing a new version, update `artifacthub-pkg.yml`:
## Links
- [Artifact Hub](https://artifacthub.io/packages/headlamp/polaris-headlamp-plugin/polaris-headlamp-plugin)
- [GitHub (mirror)](https://github.com/cpfarhood/polaris-headlamp-plugin)
- [Gitea (source of truth)](https://git.farh.net/farhoodliquor/polaris-headlamp-plugin)
- [GitHub (mirror)](https://github.com/cpfarhood/headlamp-polaris-plugin)
- [Gitea (source of truth)](https://git.farh.net/farhoodliquor/headlamp-polaris-plugin)
- [Headlamp](https://headlamp.dev/)
- [Fairwinds Polaris](https://polaris.docs.fairwinds.com/)
+5 -5
View File
@@ -1,10 +1,10 @@
version: 0.0.5
version: 0.0.7
name: polaris-headlamp-plugin
displayName: Polaris
createdAt: "2026-02-05T19:00:00Z"
description: Surfaces Fairwinds Polaris audit results inside the Headlamp UI.
license: MIT
homeURL: "https://github.com/cpfarhood/polaris-headlamp-plugin"
homeURL: "https://github.com/cpfarhood/headlamp-polaris-plugin"
category: security
keywords:
- polaris
@@ -15,14 +15,14 @@ keywords:
- kubernetes
links:
- name: Source
url: "https://github.com/cpfarhood/polaris-headlamp-plugin"
url: "https://github.com/cpfarhood/headlamp-polaris-plugin"
- name: Polaris
url: "https://polaris.docs.fairwinds.com/"
maintainers:
- name: cpfarhood
email: "chris@farhood.org"
annotations:
headlamp/plugin/archive-url: "https://github.com/cpfarhood/polaris-headlamp-plugin/releases/download/v0.0.5/polaris-headlamp-plugin-0.0.5.tar.gz"
headlamp/plugin/archive-url: "https://github.com/cpfarhood/headlamp-polaris-plugin/releases/download/v0.0.7/polaris-headlamp-plugin-0.0.7.tar.gz"
headlamp/plugin/version-compat: ">=0.26"
headlamp/plugin/archive-checksum: sha256:afc57a1e869898b0197364e568205426f32572b703c638246463bb5c7898f4d2
headlamp/plugin/archive-checksum: sha256:760b3229e9eed5817269b05f639c8ce596ebc9acec737d46071349ec102bf345
headlamp/plugin/distro-compat: in-cluster
+1 -1
View File
@@ -1,4 +1,4 @@
repositoryID: fb4c3789-de2b-4667-8fff-34f22e5648da
repositoryID: fc3397f6-a75a-4950-ab50-da75c08a8089
owners:
- name: cpfarhood
email: "chris@farhood.org"
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "polaris-headlamp-plugin",
"version": "0.0.3",
"version": "0.0.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "polaris-headlamp-plugin",
"version": "0.0.3",
"version": "0.0.5",
"devDependencies": {
"@kinvolk/headlamp-plugin": "^0.13.0"
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "polaris-headlamp-plugin",
"version": "0.0.5",
"version": "0.0.7",
"description": "Headlamp plugin for Fairwinds Polaris audit results",
"scripts": {
"start": "headlamp-plugin start",
+25
View File
@@ -0,0 +1,25 @@
import React from 'react';
import { AuditData, getRefreshInterval, usePolarisData } from './polaris';
interface PolarisDataContextValue {
data: AuditData | null;
loading: boolean;
error: string | null;
}
const PolarisDataContext = React.createContext<PolarisDataContextValue | null>(null);
export function PolarisDataProvider(props: { children: React.ReactNode }) {
const interval = getRefreshInterval();
const state = usePolarisData(interval);
return <PolarisDataContext.Provider value={state}>{props.children}</PolarisDataContext.Provider>;
}
export function usePolarisDataContext(): PolarisDataContextValue {
const ctx = React.useContext(PolarisDataContext);
if (ctx === null) {
throw new Error('usePolarisDataContext must be used within a PolarisDataProvider');
}
return ctx;
}
+29 -2
View File
@@ -77,9 +77,9 @@ function countResultSet(rs: ResultSet, counts: ResultCounts): void {
}
}
export function countResults(data: AuditData): ResultCounts {
function countResultItems(results: Result[]): ResultCounts {
const counts: ResultCounts = { total: 0, pass: 0, warning: 0, danger: 0 };
for (const result of data.Results) {
for (const result of results) {
countResultSet(result.Results, counts);
if (result.PodResult) {
countResultSet(result.PodResult.Results, counts);
@@ -91,8 +91,35 @@ export function countResults(data: AuditData): ResultCounts {
return counts;
}
export function countResults(data: AuditData): ResultCounts {
return countResultItems(data.Results);
}
export function countResultsForItems(results: Result[]): ResultCounts {
return countResultItems(results);
}
export function getNamespaces(data: AuditData): string[] {
const namespaces = new Set<string>();
for (const result of data.Results) {
namespaces.add(result.Namespace);
}
return Array.from(namespaces).sort();
}
export function filterResultsByNamespace(data: AuditData, namespace: string): Result[] {
return data.Results.filter(r => r.Namespace === namespace);
}
// --- Settings ---
export const INTERVAL_OPTIONS = [
{ label: '1 minute', value: 60 },
{ label: '5 minutes', value: 300 },
{ label: '10 minutes', value: 600 },
{ label: '30 minutes', value: 1800 },
];
const STORAGE_KEY = 'polaris-plugin-refresh-interval';
const DEFAULT_INTERVAL_SECONDS = 300; // 5 minutes
@@ -0,0 +1,29 @@
import { registerSidebarEntry } from '@kinvolk/headlamp-plugin/lib';
import React from 'react';
import { getNamespaces } from '../api/polaris';
import { usePolarisDataContext } from '../api/PolarisDataContext';
const registeredNamespaces = new Set<string>();
export default function DynamicSidebarRegistrar() {
const { data } = usePolarisDataContext();
React.useEffect(() => {
if (!data) return;
const namespaces = getNamespaces(data);
for (const ns of namespaces) {
if (registeredNamespaces.has(ns)) continue;
registeredNamespaces.add(ns);
registerSidebarEntry({
parent: 'polaris',
name: `polaris-ns-${ns}`,
label: ns,
url: `/polaris/${ns}`,
icon: 'mdi:folder-outline',
});
}
}, [data]);
return null;
}
+139
View File
@@ -0,0 +1,139 @@
import {
Loader,
NameValueTable,
SectionBox,
SectionHeader,
SimpleTable,
StatusLabel,
} from '@kinvolk/headlamp-plugin/lib/CommonComponents';
import React from 'react';
import { useParams } from 'react-router-dom';
import {
computeScore,
countResultsForItems,
filterResultsByNamespace,
Result,
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';
}
function resourceCounts(result: Result): ResultCounts {
return countResultsForItems([result]);
}
export default function NamespaceDetailView() {
const { namespace } = useParams<{ namespace: string }>();
const { data, loading, error } = usePolarisDataContext();
if (loading) {
return <Loader title={`Loading Polaris data for ${namespace}...`} />;
}
if (error) {
return (
<>
<SectionHeader title={`Polaris — ${namespace}`} />
<SectionBox title="Error">
<NameValueTable
rows={[
{
name: 'Status',
value: <StatusLabel status="error">{error}</StatusLabel>,
},
]}
/>
</SectionBox>
</>
);
}
if (!data) {
return (
<>
<SectionHeader title={`Polaris — ${namespace}`} />
<SectionBox title="No Data">
<NameValueTable rows={[{ name: 'Status', value: 'No Polaris audit results found.' }]} />
</SectionBox>
</>
);
}
const results = filterResultsByNamespace(data, namespace);
const counts = countResultsForItems(results);
const score = computeScore(counts);
const status = scoreStatus(score);
const countsPerResource = new Map<string, ResultCounts>();
for (const r of results) {
countsPerResource.set(`${r.Namespace}/${r.Kind}/${r.Name}`, resourceCounts(r));
}
function getResourceCounts(row: Result): ResultCounts {
return countsPerResource.get(`${row.Namespace}/${row.Kind}/${row.Name}`) ?? resourceCounts(row);
}
return (
<>
<SectionHeader title={`Polaris — ${namespace}`} />
<SectionBox title="Namespace Score">
<NameValueTable
rows={[
{
name: 'Score',
value: <StatusLabel status={status}>{score}%</StatusLabel>,
},
{ name: 'Total Checks', value: String(counts.total) },
{
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>,
},
]}
/>
</SectionBox>
<SectionBox title="Resources">
<SimpleTable
columns={[
{ label: 'Name', getter: (row: Result) => row.Name },
{ label: 'Kind', getter: (row: Result) => row.Kind },
{
label: 'Pass',
getter: (row: Result) => (
<StatusLabel status="success">{getResourceCounts(row).pass}</StatusLabel>
),
},
{
label: 'Warning',
getter: (row: Result) => (
<StatusLabel status="warning">{getResourceCounts(row).warning}</StatusLabel>
),
},
{
label: 'Danger',
getter: (row: Result) => (
<StatusLabel status="error">{getResourceCounts(row).danger}</StatusLabel>
),
},
]}
data={results}
emptyMessage={`No resources found in namespace "${namespace}".`}
/>
</SectionBox>
</>
);
}
+40
View File
@@ -0,0 +1,40 @@
import { NameValueTable, SectionBox } from '@kinvolk/headlamp-plugin/lib/CommonComponents';
import React from 'react';
import { getRefreshInterval, INTERVAL_OPTIONS, setRefreshInterval } from '../api/polaris';
interface PluginSettingsProps {
data?: { [key: string]: string | number | boolean };
onDataChange?: (data: { [key: string]: string | number | boolean }) => void;
}
export default function PolarisSettings(props: PluginSettingsProps) {
const { data, onDataChange } = props;
const currentInterval = (data?.refreshInterval as number) ?? getRefreshInterval();
function handleChange(e: React.ChangeEvent<HTMLSelectElement>) {
const seconds = Number(e.target.value);
setRefreshInterval(seconds);
onDataChange?.({ ...data, refreshInterval: seconds });
}
return (
<SectionBox title="Polaris Settings">
<NameValueTable
rows={[
{
name: 'Refresh Interval',
value: (
<select value={currentInterval} onChange={handleChange}>
{INTERVAL_OPTIONS.map(opt => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</select>
),
},
]}
/>
</SectionBox>
);
}
+66 -110
View File
@@ -1,124 +1,71 @@
import { Loader, SectionBox, SectionHeader } from '@kinvolk/headlamp-plugin/lib/CommonComponents';
import React from 'react';
import {
AuditData,
computeScore,
countResults,
getRefreshInterval,
ResultCounts,
setRefreshInterval,
usePolarisData,
} from '../api/polaris';
Loader,
NameValueTable,
SectionBox,
SectionHeader,
StatusLabel,
} from '@kinvolk/headlamp-plugin/lib/CommonComponents';
import React from 'react';
import { AuditData, computeScore, countResults, ResultCounts } from '../api/polaris';
import { usePolarisDataContext } from '../api/PolarisDataContext';
const INTERVAL_OPTIONS = [
{ label: '1 minute', value: 60 },
{ label: '5 minutes', value: 300 },
{ label: '10 minutes', value: 600 },
{ label: '30 minutes', value: 1800 },
];
function RefreshSettings(props: { interval: number; onChange: (seconds: number) => void }) {
return (
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<label htmlFor="polaris-refresh-interval">Refresh interval:</label>
<select
id="polaris-refresh-interval"
value={props.interval}
onChange={e => props.onChange(Number(e.target.value))}
>
{INTERVAL_OPTIONS.map(opt => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</select>
</div>
);
}
function StatCard(props: { label: string; value: number; color?: string }) {
return (
<div
style={{
padding: '16px 24px',
textAlign: 'center',
minWidth: '120px',
}}
>
<div
style={{
fontSize: '2rem',
fontWeight: 'bold',
color: props.color,
}}
>
{props.value}
</div>
<div style={{ fontSize: '0.875rem', opacity: 0.8 }}>{props.label}</div>
</div>
);
}
function ScoreBadge(props: { score: number }) {
const color = props.score >= 80 ? '#4caf50' : props.score >= 50 ? '#ff9800' : '#f44336';
return (
<div style={{ textAlign: 'center', marginBottom: '16px' }}>
<div style={{ fontSize: '3rem', fontWeight: 'bold', color }}>{props.score}%</div>
<div style={{ fontSize: '0.875rem', opacity: 0.8 }}>Cluster Score</div>
</div>
);
function scoreStatus(score: number): 'success' | 'warning' | 'error' {
if (score >= 80) return 'success';
if (score >= 50) return 'warning';
return 'error';
}
function OverviewSection(props: { data: AuditData; counts: ResultCounts }) {
const score = computeScore(props.counts);
const status = scoreStatus(score);
return (
<>
<SectionBox title="Score">
<ScoreBadge score={score} />
<NameValueTable
rows={[
{
name: 'Cluster Score',
value: <StatusLabel status={status}>{score}%</StatusLabel>,
},
]}
/>
</SectionBox>
<SectionBox title="Check Summary">
<div
style={{
display: 'flex',
justifyContent: 'center',
flexWrap: 'wrap',
gap: '16px',
}}
>
<StatCard label="Total" value={props.counts.total} />
<StatCard label="Pass" value={props.counts.pass} color="#4caf50" />
<StatCard label="Warning" value={props.counts.warning} color="#ff9800" />
<StatCard label="Danger" value={props.counts.danger} color="#f44336" />
</div>
<NameValueTable
rows={[
{ name: 'Total Checks', value: String(props.counts.total) },
{
name: 'Pass',
value: <StatusLabel status="success">{props.counts.pass}</StatusLabel>,
},
{
name: 'Warning',
value: <StatusLabel status="warning">{props.counts.warning}</StatusLabel>,
},
{
name: 'Danger',
value: <StatusLabel status="error">{props.counts.danger}</StatusLabel>,
},
]}
/>
</SectionBox>
<SectionBox title="Cluster Info">
<div
style={{
display: 'flex',
justifyContent: 'center',
flexWrap: 'wrap',
gap: '16px',
}}
>
<StatCard label="Nodes" value={props.data.ClusterInfo.Nodes} />
<StatCard label="Pods" value={props.data.ClusterInfo.Pods} />
<StatCard label="Namespaces" value={props.data.ClusterInfo.Namespaces} />
<StatCard label="Controllers" value={props.data.ClusterInfo.Controllers} />
</div>
<NameValueTable
rows={[
{ name: 'Nodes', value: String(props.data.ClusterInfo.Nodes) },
{ name: 'Pods', value: String(props.data.ClusterInfo.Pods) },
{ name: 'Namespaces', value: String(props.data.ClusterInfo.Namespaces) },
{ name: 'Controllers', value: String(props.data.ClusterInfo.Controllers) },
]}
/>
</SectionBox>
</>
);
}
export default function PolarisView() {
const [interval, setInterval] = React.useState(getRefreshInterval);
function handleIntervalChange(seconds: number) {
setInterval(seconds);
setRefreshInterval(seconds);
}
const { data, loading, error } = usePolarisData(interval);
const { data, loading, error } = usePolarisDataContext();
if (loading) {
return <Loader title="Loading Polaris audit data..." />;
@@ -128,16 +75,18 @@ export default function PolarisView() {
return (
<>
<SectionHeader
title="Polaris"
actions={[
<RefreshSettings key="refresh" interval={interval} onChange={handleIntervalChange} />,
]}
/>
<SectionHeader title="Polaris" />
{error && (
<SectionBox title="Error">
<div style={{ padding: '16px', color: '#f44336' }}>{error}</div>
<NameValueTable
rows={[
{
name: 'Status',
value: <StatusLabel status="error">{error}</StatusLabel>,
},
]}
/>
</SectionBox>
)}
@@ -145,7 +94,14 @@ export default function PolarisView() {
{!data && !error && (
<SectionBox title="No Data">
<div style={{ padding: '16px' }}>No Polaris audit results found.</div>
<NameValueTable
rows={[
{
name: 'Status',
value: 'No Polaris audit results found.',
},
]}
/>
</SectionBox>
)}
</>
+30 -2
View File
@@ -1,5 +1,13 @@
import { registerRoute, registerSidebarEntry } from '@kinvolk/headlamp-plugin/lib';
import {
registerPluginSettings,
registerRoute,
registerSidebarEntry,
} from '@kinvolk/headlamp-plugin/lib';
import React from 'react';
import { PolarisDataProvider } from './api/PolarisDataContext';
import DynamicSidebarRegistrar from './components/DynamicSidebarRegistrar';
import NamespaceDetailView from './components/NamespaceDetailView';
import PolarisSettings from './components/PolarisSettings';
import PolarisView from './components/PolarisView';
registerSidebarEntry({
@@ -15,5 +23,25 @@ registerRoute({
sidebar: 'polaris',
name: 'polaris',
exact: true,
component: () => <PolarisView />,
component: () => (
<PolarisDataProvider>
<DynamicSidebarRegistrar />
<PolarisView />
</PolarisDataProvider>
),
});
registerRoute({
path: '/polaris/:namespace',
sidebar: 'polaris',
name: 'polaris-namespace',
exact: true,
component: () => (
<PolarisDataProvider>
<DynamicSidebarRegistrar />
<NamespaceDetailView />
</PolarisDataProvider>
),
});
registerPluginSettings('polaris-headlamp-plugin', PolarisSettings, true);