import {
Loader,
NameValueTable,
SectionBox,
SectionHeader,
SimpleTable,
StatusLabel,
} from '@kinvolk/headlamp-plugin/lib/CommonComponents';
import React, { useEffect, useState } from 'react';
import { useHistory, useLocation } from 'react-router-dom';
import {
computeScore,
countResultsForItems,
filterResultsByNamespace,
getNamespaces,
getPolarisProxyUrl,
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';
}
interface NamespaceRow {
namespace: string;
score: number;
pass: number;
warning: number;
danger: number;
skipped: number;
}
function resourceCounts(result: Result): ResultCounts {
return countResultsForItems([result]);
}
interface NamespaceDetailPanelProps {
namespace: string;
onClose: () => void;
}
function NamespaceDetailPanel({ namespace, onClose }: NamespaceDetailPanelProps) {
const [isMaximized, setIsMaximized] = React.useState(false);
const { data, loading, error } = usePolarisDataContext();
if (loading) {
return (
);
}
if (error) {
return (
{error},
},
]}
/>
);
}
if (!data) {
return (
);
}
const results = filterResultsByNamespace(data, namespace);
const counts = countResultsForItems(results);
const score = computeScore(counts);
const status = scoreStatus(score);
const countsPerResource = new Map();
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);
}
// Generate a unique class name for this drawer to avoid conflicts
const drawerClass = `polaris-namespace-drawer-${namespace}`;
return (
<>
Polaris — {namespace}
View in Polaris Dashboard
),
},
]}
/>
{score}%,
},
{ name: 'Total Checks', value: String(counts.total) },
{
name: 'Pass',
value: {counts.pass},
},
{
name: 'Warning',
value: {counts.warning},
},
{
name: 'Danger',
value: {counts.danger},
},
{
name: 'Skipped',
value: (
{counts.skipped}
),
},
]}
/>
row.Name },
{ label: 'Kind', getter: (row: Result) => row.Kind },
{
label: 'Pass',
getter: (row: Result) => (
{getResourceCounts(row).pass}
),
},
{
label: 'Warning',
getter: (row: Result) => (
{getResourceCounts(row).warning}
),
},
{
label: 'Danger',
getter: (row: Result) => (
{getResourceCounts(row).danger}
),
},
]}
data={results}
emptyMessage={`No resources found in namespace "${namespace}".`}
/>
>
);
}
export default function NamespacesListView() {
const location = useLocation();
const history = useHistory();
const { data, loading, error } = usePolarisDataContext();
// Initialize from URL hash
const [selectedNamespace, setSelectedNamespace] = useState(
location.hash.slice(1) || null
);
// Sync drawer state when URL hash changes (browser back/forward)
useEffect(() => {
const hashNs = location.hash.slice(1);
setSelectedNamespace(hashNs || null);
}, [location.hash]);
const openNamespace = (ns: string) => {
setSelectedNamespace(ns);
history.push(`${location.pathname}#${ns}`);
};
const closeNamespace = () => {
setSelectedNamespace(null);
history.push(location.pathname);
};
// Handle keyboard navigation (Escape key closes drawer)
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape' && selectedNamespace) {
closeNamespace();
}
};
if (selectedNamespace) {
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedNamespace]);
if (loading) {
return ;
}
if (error) {
return (
<>
{error},
},
]}
/>
>
);
}
if (!data) {
return (
<>
>
);
}
const namespaces = getNamespaces(data);
const rows: NamespaceRow[] = namespaces.map(ns => {
const results = filterResultsByNamespace(data, ns);
const counts = countResultsForItems(results);
const score = computeScore(counts);
return {
namespace: ns,
score,
pass: counts.pass,
warning: counts.warning,
danger: counts.danger,
skipped: counts.skipped,
};
});
return (
<>
(
),
},
{
label: 'Score',
getter: (row: NamespaceRow) => (
{row.score}%
),
},
{
label: 'Pass',
getter: (row: NamespaceRow) => {row.pass},
},
{
label: 'Warning',
getter: (row: NamespaceRow) => (
{row.warning}
),
},
{
label: 'Danger',
getter: (row: NamespaceRow) => {row.danger},
},
{
label: 'Skipped',
getter: (row: NamespaceRow) => String(row.skipped),
},
]}
data={rows}
emptyMessage="No namespaces found in Polaris audit data."
/>
{selectedNamespace && (
<>
>
)}
>
);
}