Files
headlamp-polaris-plugin/src/components/DashboardView.tsx
T
DevContainer User 514de78ba7 fix: comprehensive code quality, theming, and test coverage improvements
- Fix ExemptionManager apiVersion bug (apps/batch resources used wrong API path)
- Replace resource: any with proper KubeResource interface (strict TypeScript)
- Replace all var(--mui-palette-*) CSS variables with useTheme() + theme.palette.*
- Replace custom drawer with MUI Drawer component (proper a11y and theming)
- Replace alert() calls with StatusLabel-based inline feedback
- Add PolarisErrorBoundary wrapping all registered plugin components
- Export getPolarisApiPath/isFullUrl from polaris.ts, deduplicate in PolarisSettings
- Fix PolarisDataContext test mock missing triggerRefresh
- Fix DashboardView test SimpleTable mock using any
- Remove dead NamespaceDetailView (replaced by drawer), unused MockPolarisProvider,
  unused getSeverityColor export
- Add tests for InlineAuditSection, AppBarScoreBadge, topIssues, checkMapping (32 new)
- Update CLAUDE.md, CHANGELOG.md, README.md for v0.6.0

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 16:59:50 +00:00

198 lines
5.9 KiB
TypeScript

import {
Loader,
NameValueTable,
PercentageBar,
PercentageCircle,
SectionBox,
SectionHeader,
SimpleTable,
StatusLabel,
} from '@kinvolk/headlamp-plugin/lib/CommonComponents';
import { useTheme } from '@mui/material/styles';
import React from 'react';
import { getSeverityStatus } from '../api/checkMapping';
import { AuditData, computeScore, countResults, ResultCounts } from '../api/polaris';
import { usePolarisDataContext } from '../api/PolarisDataContext';
import { getTopIssues, TopIssue } from '../api/topIssues';
const COLORS = {
pass: '#4caf50',
warning: '#ff9800',
danger: '#f44336',
skipped: '#9e9e9e',
};
function OverviewSection(props: { data: AuditData; counts: ResultCounts }) {
const { counts } = props;
const score = computeScore(counts);
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 },
];
return (
<>
<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: '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="Cluster Info">
<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>
</>
);
}
function formatAuditTime(auditTime: string): string {
const date = new Date(auditTime);
const now = new Date();
const diffMs = now.getTime() - date.getTime();
const diffMins = Math.floor(diffMs / 60000);
if (diffMins < 1) return 'just now';
if (diffMins < 60) return `${diffMins} minute${diffMins > 1 ? 's' : ''} ago`;
const diffHours = Math.floor(diffMins / 60);
if (diffHours < 24) return `${diffHours} hour${diffHours > 1 ? 's' : ''} ago`;
const diffDays = Math.floor(diffHours / 24);
return `${diffDays} day${diffDays > 1 ? 's' : ''} ago`;
}
export default function DashboardView() {
const theme = useTheme();
const { data, loading, error, refresh } = usePolarisDataContext();
if (loading) {
return <Loader title="Loading Polaris audit data..." />;
}
const counts = data ? countResults(data) : null;
const topIssues = data ? getTopIssues(data) : [];
return (
<>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '20px',
}}
>
<SectionHeader title="Polaris — Overview" />
{data && (
<div style={{ display: 'flex', gap: '16px', alignItems: 'center' }}>
<span style={{ fontSize: '14px', color: theme.palette.text.secondary }}>
Last updated: {formatAuditTime(data.AuditTime)}
</span>
<button
onClick={refresh}
style={{
padding: '6px 16px',
backgroundColor: 'transparent',
color: theme.palette.primary.main,
border: `1px solid ${theme.palette.primary.main}`,
borderRadius: '4px',
cursor: 'pointer',
fontSize: '13px',
fontWeight: 500,
display: 'inline-flex',
alignItems: 'center',
gap: '4px',
}}
>
<span>🔄</span>
<span>Refresh</span>
</button>
</div>
)}
</div>
{error && (
<SectionBox title="Error">
<NameValueTable
rows={[
{
name: 'Status',
value: <StatusLabel status="error">{error}</StatusLabel>,
},
]}
/>
</SectionBox>
)}
{data && counts && (
<>
<OverviewSection data={data} counts={counts} />
{topIssues.length > 0 && (
<SectionBox title="Top Issues">
<SimpleTable
columns={[
{ label: 'Check', getter: (issue: TopIssue) => issue.checkName },
{ label: 'Category', getter: (issue: TopIssue) => issue.category },
{
label: 'Severity',
getter: (issue: TopIssue) => (
<StatusLabel status={getSeverityStatus(issue.severity)}>
{issue.severity}
</StatusLabel>
),
},
{
label: 'Affected Workloads',
getter: (issue: TopIssue) => String(issue.count),
},
]}
data={topIssues}
/>
</SectionBox>
)}
</>
)}
{!data && !error && (
<SectionBox title="No Data">
<NameValueTable
rows={[
{
name: 'Status',
value: 'No Polaris audit results found.',
},
]}
/>
</SectionBox>
)}
</>
);
}