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>
This commit is contained in:
2026-02-06 21:49:17 -05:00
parent 57250a995d
commit 8b319c0c8a
+89 -77
View File
@@ -1,4 +1,11 @@
import { Loader, SectionBox, SectionHeader } from '@kinvolk/headlamp-plugin/lib/CommonComponents'; import {
HeaderLabel,
Loader,
NameValueTable,
SectionBox,
SectionHeader,
StatusLabel,
} from '@kinvolk/headlamp-plugin/lib/CommonComponents';
import React from 'react'; import React from 'react';
import { import {
AuditData, AuditData,
@@ -17,94 +24,81 @@ const INTERVAL_OPTIONS = [
{ label: '30 minutes', value: 1800 }, { label: '30 minutes', value: 1800 },
]; ];
function scoreStatus(score: number): 'success' | 'warning' | 'error' {
if (score >= 80) return 'success';
if (score >= 50) return 'warning';
return 'error';
}
function RefreshSettings(props: { interval: number; onChange: (seconds: number) => void }) { function RefreshSettings(props: { interval: number; onChange: (seconds: number) => void }) {
return ( return (
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}> <HeaderLabel
<label htmlFor="polaris-refresh-interval">Refresh interval:</label> label="Refresh interval"
<select value={INTERVAL_OPTIONS.find(o => o.value === props.interval)?.label ?? ''}
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 OverviewSection(props: { data: AuditData; counts: ResultCounts }) { function OverviewSection(props: { data: AuditData; counts: ResultCounts }) {
const score = computeScore(props.counts); const score = computeScore(props.counts);
const status = scoreStatus(score);
return ( return (
<> <>
<SectionBox title="Score"> <SectionBox title="Score">
<ScoreBadge score={score} /> <NameValueTable
rows={[
{
name: 'Cluster Score',
value: (
<StatusLabel status={status}>
{score}%
</StatusLabel>
),
},
]}
/>
</SectionBox> </SectionBox>
<SectionBox title="Check Summary"> <SectionBox title="Check Summary">
<div <NameValueTable
style={{ rows={[
display: 'flex', { name: 'Total Checks', value: String(props.counts.total) },
justifyContent: 'center', {
flexWrap: 'wrap', name: 'Pass',
gap: '16px', value: (
}} <StatusLabel status="success">
> {props.counts.pass}
<StatCard label="Total" value={props.counts.total} /> </StatusLabel>
<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> name: 'Warning',
value: (
<StatusLabel status="warning">
{props.counts.warning}
</StatusLabel>
),
},
{
name: 'Danger',
value: (
<StatusLabel status="error">
{props.counts.danger}
</StatusLabel>
),
},
]}
/>
</SectionBox> </SectionBox>
<SectionBox title="Cluster Info"> <SectionBox title="Cluster Info">
<div <NameValueTable
style={{ rows={[
display: 'flex', { name: 'Nodes', value: String(props.data.ClusterInfo.Nodes) },
justifyContent: 'center', { name: 'Pods', value: String(props.data.ClusterInfo.Pods) },
flexWrap: 'wrap', { name: 'Namespaces', value: String(props.data.ClusterInfo.Namespaces) },
gap: '16px', { name: 'Controllers', value: String(props.data.ClusterInfo.Controllers) },
}} ]}
> />
<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>
</SectionBox> </SectionBox>
</> </>
); );
@@ -137,7 +131,18 @@ export default function PolarisView() {
{error && ( {error && (
<SectionBox title="Error"> <SectionBox title="Error">
<div style={{ padding: '16px', color: '#f44336' }}>{error}</div> <NameValueTable
rows={[
{
name: 'Status',
value: (
<StatusLabel status="error">
{error}
</StatusLabel>
),
},
]}
/>
</SectionBox> </SectionBox>
)} )}
@@ -145,7 +150,14 @@ export default function PolarisView() {
{!data && !error && ( {!data && !error && (
<SectionBox title="No Data"> <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> </SectionBox>
)} )}
</> </>