Compare commits

..

8 Commits

Author SHA1 Message Date
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
9 changed files with 129 additions and 108 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/
+1
View File
@@ -2,3 +2,4 @@ node_modules/
dist/
.headlamp-plugin/
.mcp.json
*.tar.gz
+3 -3
View File
@@ -1,4 +1,4 @@
version: 0.0.5
version: 0.0.6
name: polaris-headlamp-plugin
displayName: Polaris
createdAt: "2026-02-05T19:00:00Z"
@@ -22,7 +22,7 @@ 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/polaris-headlamp-plugin/releases/download/v0.0.6/polaris-headlamp-plugin-0.0.6.tar.gz"
headlamp/plugin/version-compat: ">=0.26"
headlamp/plugin/archive-checksum: sha256:afc57a1e869898b0197364e568205426f32572b703c638246463bb5c7898f4d2
headlamp/plugin/archive-checksum: sha256:12e72b6a64e3f1c73f542b6328d56391c2cc2906a9a9d7eff58fbf27f14a8680
headlamp/plugin/distro-compat: in-cluster
+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.6",
"description": "Headlamp plugin for Fairwinds Polaris audit results",
"scripts": {
"start": "headlamp-plugin start",
+7
View File
@@ -93,6 +93,13 @@ export function countResults(data: AuditData): ResultCounts {
// --- 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
+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>
);
}
+64 -101
View File
@@ -1,4 +1,10 @@
import { Loader, SectionBox, SectionHeader } from '@kinvolk/headlamp-plugin/lib/CommonComponents';
import {
Loader,
NameValueTable,
SectionBox,
SectionHeader,
StatusLabel,
} from '@kinvolk/headlamp-plugin/lib/CommonComponents';
import React from 'react';
import {
AuditData,
@@ -6,118 +12,66 @@ import {
countResults,
getRefreshInterval,
ResultCounts,
setRefreshInterval,
usePolarisData,
} from '../api/polaris';
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 interval = getRefreshInterval();
const { data, loading, error } = usePolarisData(interval);
if (loading) {
@@ -128,16 +82,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 +101,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>
)}
</>
+8 -1
View File
@@ -1,5 +1,10 @@
import { registerRoute, registerSidebarEntry } from '@kinvolk/headlamp-plugin/lib';
import {
registerPluginSettings,
registerRoute,
registerSidebarEntry,
} from '@kinvolk/headlamp-plugin/lib';
import React from 'react';
import PolarisSettings from './components/PolarisSettings';
import PolarisView from './components/PolarisView';
registerSidebarEntry({
@@ -17,3 +22,5 @@ registerRoute({
exact: true,
component: () => <PolarisView />,
});
registerPluginSettings('polaris-headlamp-plugin', PolarisSettings, true);