Files
headlamp-kube-vip-plugin/src/components/OverviewPage.tsx
T
Chris Farhood 9b9c503521
CI / ci (push) Successful in 45s
Promote uat to main (#75)
Co-authored-by: Chris Farhood <chris@farhood.org>
Co-committed-by: Chris Farhood <chris@farhood.org>
2026-05-15 22:55:28 +00:00

272 lines
8.7 KiB
TypeScript

/**
* OverviewPage — main dashboard for the kube-vip plugin.
*
* Shows: deployment status, VIP mode, leader election, service/node counts,
* IP pool summary, and pod health.
*/
import {
Loader,
NameValueTable,
SectionBox,
SectionHeader,
SimpleTable,
StatusLabel,
} from '@kinvolk/headlamp-plugin/lib/CommonComponents';
import React from 'react';
import {
formatAge,
getServiceVIPs,
isControlPlaneNode,
isEgressEnabled,
isKubeVipService,
isPodReady,
KUBE_VIP_NAMESPACE,
phaseToStatus,
} from '../api/k8s';
import { useKubeVipContext } from '../api/KubeVipDataContext';
export default function OverviewPage() {
const {
kubeVipInstalled,
daemonSetStatus,
kubeVipPods,
cloudProviderPods,
loadBalancerServices,
nodes,
leases,
ipPools,
kubeVipConfig,
loading,
error,
refresh,
} = useKubeVipContext();
if (loading) {
return <Loader title="Loading kube-vip data..." />;
}
const controlPlaneNodes = nodes.filter(isControlPlaneNode);
const readyPods = kubeVipPods.filter(isPodReady);
const kubeVipManaged = loadBalancerServices.filter(isKubeVipService);
const egressEnabled = loadBalancerServices.filter(isEgressEnabled);
// Detect mode from config
const mode =
kubeVipConfig['bgp_enable'] === 'true'
? 'BGP'
: kubeVipConfig['vip_arp'] === 'true'
? 'ARP'
: kubeVipPods.length > 0
? 'Unknown'
: '—';
const cpEnabled = kubeVipConfig['cp_enable'] === 'true';
const svcEnabled = kubeVipConfig['svc_enable'] === 'true';
const controlPlaneVIP = kubeVipConfig['address'] ?? '—';
// Find leader from leases
const cpLease = leases.find(l => l.metadata.name.startsWith('plndr-cp-lock'));
const svcLease = leases.find(l => l.metadata.name.startsWith('plndr-svcs-lock'));
const leaderNode = cpLease?.spec?.holderIdentity ?? svcLease?.spec?.holderIdentity ?? '—';
return (
<>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '20px',
}}
>
<SectionHeader title="kube-vip — Overview" />
<button
onClick={refresh}
aria-label="Refresh kube-vip data"
style={{
padding: '6px 16px',
backgroundColor: 'transparent',
color: 'var(--mui-palette-primary-main, #1976d2)',
border: '1px solid var(--mui-palette-primary-main, #1976d2)',
borderRadius: '4px',
cursor: 'pointer',
fontSize: '13px',
fontWeight: 500,
}}
>
Refresh
</button>
</div>
{!kubeVipInstalled && (
<SectionBox title="kube-vip Not Detected">
<NameValueTable
rows={[
{
name: 'Status',
value: (
<StatusLabel status="error">
No kube-vip pods found in {KUBE_VIP_NAMESPACE}
</StatusLabel>
),
},
{
name: 'Install',
value: 'See https://kube-vip.io/docs/installation/',
},
]}
/>
</SectionBox>
)}
{error && (
<SectionBox title="Error">
<NameValueTable
rows={[{ name: 'Status', value: <StatusLabel status="error">{error}</StatusLabel> }]}
/>
</SectionBox>
)}
{kubeVipInstalled && (
<>
<SectionBox title="Deployment">
<NameValueTable
rows={[
{
name: 'Status',
value: (
<StatusLabel status={readyPods.length > 0 ? 'success' : 'error'}>
{readyPods.length > 0 ? 'Running' : 'Unhealthy'}
</StatusLabel>
),
},
{ name: 'Mode', value: mode },
{ name: 'Control Plane HA', value: cpEnabled ? 'Enabled' : 'Disabled' },
{ name: 'Service LoadBalancer', value: svcEnabled ? 'Enabled' : 'Disabled' },
...(cpEnabled ? [{ name: 'Control Plane VIP', value: controlPlaneVIP }] : []),
{ name: 'Leader', value: leaderNode },
{
name: 'Pods',
value: `${readyPods.length}/${kubeVipPods.length} ready`,
},
...(daemonSetStatus
? [
{
name: 'DaemonSet',
value: `${daemonSetStatus.numberReady ?? 0}/${
daemonSetStatus.desiredNumberScheduled ?? 0
} ready`,
},
]
: []),
...(cloudProviderPods.length > 0
? [
{
name: 'Cloud Provider',
value: (
<StatusLabel
status={cloudProviderPods.some(isPodReady) ? 'success' : 'warning'}
>
{cloudProviderPods.length} pod(s)
</StatusLabel>
),
},
]
: []),
]}
/>
</SectionBox>
<SectionBox title="Cluster Summary">
<NameValueTable
rows={[
{ name: 'Total Nodes', value: String(nodes.length) },
{ name: 'Control Plane Nodes', value: String(controlPlaneNodes.length) },
{ name: 'LoadBalancer Services', value: String(loadBalancerServices.length) },
{ name: 'kube-vip Managed', value: String(kubeVipManaged.length) },
...(egressEnabled.length > 0
? [
{
name: 'Egress Enabled',
value: String(egressEnabled.length),
},
]
: []),
{ name: 'IP Pools', value: String(ipPools.length) },
{ name: 'Leader Election Leases', value: String(leases.length) },
]}
/>
</SectionBox>
{ipPools.length > 0 && (
<SectionBox title="IP Pools">
<SimpleTable
columns={[
{ label: 'Name', getter: p => p.name },
{ label: 'Type', getter: p => p.type.toUpperCase() },
{ label: 'Value', getter: p => p.value },
{
label: 'Scope',
getter: p => (p.scope === 'namespace' ? p.namespace ?? '—' : 'Global'),
},
]}
data={ipPools}
/>
</SectionBox>
)}
{kubeVipPods.length > 0 && (
<SectionBox title="kube-vip Pods">
<SimpleTable
columns={[
{ label: 'Name', getter: p => p.metadata.name },
{ label: 'Node', getter: p => p.spec?.nodeName ?? '—' },
{
label: 'Status',
getter: p => (
<StatusLabel status={phaseToStatus(p.status?.phase)}>
{p.status?.phase ?? 'Unknown'}
</StatusLabel>
),
},
{
label: 'Ready',
getter: p => (isPodReady(p) ? 'Yes' : 'No'),
},
{ label: 'Age', getter: p => formatAge(p.metadata.creationTimestamp) },
]}
data={kubeVipPods}
/>
</SectionBox>
)}
{loadBalancerServices.length > 0 && (
<SectionBox title="LoadBalancer Services">
<SimpleTable
columns={[
{ label: 'Name', getter: s => s.metadata.name },
{ label: 'Namespace', getter: s => s.metadata.namespace ?? '—' },
{ label: 'VIP', getter: s => getServiceVIPs(s).join(', ') || '—' },
{
label: 'Ports',
getter: s =>
s.spec.ports
?.map(
(p: { port: number; protocol?: string }) =>
`${p.port}/${p.protocol ?? 'TCP'}`
)
.join(', ') ?? '—',
},
{ label: 'Age', getter: s => formatAge(s.metadata.creationTimestamp) },
]}
data={loadBalancerServices}
/>
</SectionBox>
)}
</>
)}
</>
);
}