// Fleet Dashboard — World Map (Acme Bank light theme)
// Light map bg, white cluster cards, green brand accents
/* --- Cluster Card (HTML-rendered, light theme) --- */
function ClusterCard({ cluster, mode, placementData, upgradeData }) {
let borderColor = '#059669';
let dimmed = false;
let glowColor = null;
let badge = null;
let extraInfo = null;
const healthColor = cluster.health === 'healthy' ? '#059669'
: cluster.health === 'degraded' ? '#D97706' : '#DC2626';
if (mode === 'placement' && placementData) {
const boundList = placementData.boundClusters || [placementData.boundCluster];
if (boundList.includes(cluster.id)) {
borderColor = '#059669';
glowColor = '#059669';
badge = { label: '✓ BOUND', color: '#065F46', bg: '#D1FAE5' };
extraInfo = placementData.resources[0];
} else if (placementData.rejectedClusters.includes(cluster.id)) {
borderColor = '#DC2626';
dimmed = true;
badge = { label: '✗ NOT SELECTED', color: '#991B1B', bg: '#FEE2E2' };
extraInfo = placementData.rejectReason;
}
}
if (mode === 'upgrade' && upgradeData) {
const stage = upgradeData.stages.find(s => s.cluster === cluster.id);
if (stage) {
if (stage.status === 'completed') {
borderColor = '#059669';
badge = { label: `✓ Stage ${stage.id} Complete · ${stage.duration}`, color: '#065F46', bg: '#D1FAE5' };
} else if (stage.status === 'waiting-approval') {
borderColor = '#D97706';
glowColor = '#D97706';
badge = { label: `⏸ Stage ${stage.id} · Awaiting Approval`, color: '#92400E', bg: '#FEF3C7' };
} else if (stage.status === 'in-progress') {
borderColor = '#1D4ED8';
glowColor = '#3B82F6';
badge = { label: `▶ Stage ${stage.id} · In Progress`, color: '#1D4ED8', bg: '#DBEAFE' };
} else {
borderColor = FC.chromeSubtle;
dimmed = true;
badge = { label: `⏳ Stage ${stage.id} · Queued`, color: FC.chromeSubtle, bg: '#F3F4F6' };
}
}
}
const cardStyles = {
width: 178,
padding: '14px 16px',
background: dimmed ? 'rgba(255,255,255,0.75)' : '#FFFFFF',
border: `1.5px ${dimmed ? 'dashed' : 'solid'} ${borderColor}${dimmed ? '88' : ''}`,
borderRadius: 12,
opacity: dimmed ? 0.55 : 1,
transition: 'opacity 0.4s, box-shadow 0.4s',
pointerEvents: 'auto',
boxShadow: '0 1px 3px rgba(0,0,0,0.08), 0 2px 8px rgba(0,0,0,0.04)',
};
if (glowColor) {
cardStyles.boxShadow = `0 1px 3px rgba(0,0,0,0.08), 0 0 20px ${glowColor}18, 0 0 40px ${glowColor}08`;
}
return (
{/* Region label */}
{cluster.regionLabel}
{/* Cluster name + health */}
{/* Version + nodes */}
v{cluster.k8sVersion} · {cluster.nodeCount} nodes
{/* Labels */}
{cluster.region}
{/* Badge */}
{badge && (
{badge.label}
)}
{/* Extra info */}
{extraInfo && (
↳ {extraInfo}
)}
);
}
/* --- Fleet Hub Badge (HTML, light theme) --- */
function FleetHubBadge({ fleetHub = FLEET_HUB }) {
return (
{fleetHub.name}
Fleet Hub · Azure
);
}
/* --- World Map (composite, light theme) --- */
function WorldMap({ mode, placementData, upgradeData, clusters = CLUSTERS, fleetHub = FLEET_HUB, children }) {
const outerRef = React.useRef(null);
const [mapSize, setMapSize] = React.useState({ w: 0, h: 0 });
React.useLayoutEffect(() => {
const el = outerRef.current;
if (!el) return;
function measure() {
const { width: cw, height: ch } = el.getBoundingClientRect();
if (cw === 0 || ch === 0) return;
const aspect = 2; // 1000:500
let w, h;
if (cw / ch > aspect) { h = ch; w = ch * aspect; }
else { w = cw; h = cw / aspect; }
setMapSize({ w, h });
}
measure();
const ro = new ResizeObserver(measure);
ro.observe(el);
return () => ro.disconnect();
}, []);
const pct = (svgX, svgY) => ({
left: `${(svgX / 1000) * 100}%`,
top: `${(svgY / 500) * 100}%`,
});
function lineProps(cluster) {
if (mode === 'placement' && placementData) {
const boundList = placementData.boundClusters || [placementData.boundCluster];
const bound = boundList.includes(cluster.id);
return {
stroke: bound ? '#059669' : FC.chromeSubtle,
strokeWidth: bound ? 1.6 : 0.7,
strokeDasharray: bound ? 'none' : '6 4',
opacity: bound ? 0.5 : 0.2,
isBound: bound,
};
}
if (mode === 'upgrade' && upgradeData) {
const stage = upgradeData.stages.find(s => s.cluster === cluster.id);
const done = stage && stage.status === 'completed';
return {
stroke: done ? '#059669' : FC.chromeSubtle,
strokeWidth: done ? 1.4 : 0.7,
strokeDasharray: done ? 'none' : '6 4',
opacity: done ? 0.45 : 0.2,
isBound: false,
};
}
return { stroke: FC.primary, strokeWidth: 0.6, strokeDasharray: '4 6', opacity: 0.2, isBound: false };
}
const hx = fleetHub.x, hy = fleetHub.y;
return (
{mapSize.w > 0 && (
{/* Layer 1: SVG Background */}
{/* Layer 2: HTML Cluster Cards */}
{/* Fleet Hub */}
{/* Cluster cards — offset horizontally to prevent overlap */}
{clusters.map(c => {
const xPct = c.x / 1000;
const tx = xPct < 0.4 ? '-70%' : xPct > 0.6 ? '-30%' : '-50%';
return (
);
})}
{/* Layer 3: View-specific overlays */}
{children && (
{children}
)}
)}
);
}
Object.assign(window, { WorldMap, ClusterCard, FleetHubBadge });