Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a6a1280e4f | |||
| 7351d88997 | |||
| 071aab4f7e | |||
| 40544429f4 | |||
| 1f110a2846 | |||
| 672caec903 | |||
| b10d09fd41 | |||
| 8b319c0c8a |
@@ -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/
|
||||
|
||||
|
||||
@@ -2,3 +2,4 @@ node_modules/
|
||||
dist/
|
||||
.headlamp-plugin/
|
||||
.mcp.json
|
||||
*.tar.gz
|
||||
|
||||
+3
-3
@@ -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
|
||||
|
||||
Generated
+2
-2
@@ -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
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user