Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Dashboard ambient layer, comrade presence, Mission Deck and War Room, Emberwake supply chain, spread/docs publishing, fleet policy and modules API, CI docker mining, and refreshed USB pack.
130 lines
3.9 KiB
TypeScript
130 lines
3.9 KiB
TypeScript
import type { Agent } from '../types';
|
|
import type { FleetGroup } from './fleetGroups';
|
|
import { FLEET_GROUP_COLORS, primaryGroupForAgent } from './fleetGroups';
|
|
|
|
export interface MapPoint {
|
|
id: string;
|
|
kind: 'agent' | 'comrade';
|
|
x: number;
|
|
y: number;
|
|
label: string;
|
|
color?: string;
|
|
online?: boolean;
|
|
}
|
|
|
|
export const COMRADE_DOT_COLOR = '#ffb020';
|
|
|
|
export const HASHRATE_SPIKE_RATIO = 1.25;
|
|
export const HASHRATE_SPIKE_MIN_DELTA = 50;
|
|
|
|
export function hashString(seed: string): number {
|
|
let h = 0;
|
|
for (let i = 0; i < seed.length; i++) {
|
|
h = (h * 31 + seed.charCodeAt(i)) >>> 0;
|
|
}
|
|
return h;
|
|
}
|
|
|
|
/** Stable pseudo-random position from a string seed (percent coords). */
|
|
export function hashPosition(seed: string, margin = 12): { x: number; y: number } {
|
|
const h = hashString(seed);
|
|
const range = 100 - margin * 2;
|
|
return {
|
|
x: margin + ((h % 1000) / 1000) * range,
|
|
y: margin + (((h >>> 10) % 1000) / 1000) * range,
|
|
};
|
|
}
|
|
|
|
export function groupClusterCenter(
|
|
groupIndex: number,
|
|
totalGroups: number,
|
|
margin = 14,
|
|
): { x: number; y: number } {
|
|
const angle = (groupIndex / Math.max(totalGroups, 1)) * Math.PI * 2 - Math.PI / 2;
|
|
const cx = 50 + Math.cos(angle) * 30;
|
|
const cy = 50 + Math.sin(angle) * 30;
|
|
return {
|
|
x: Math.max(margin, Math.min(100 - margin, cx)),
|
|
y: Math.max(margin, Math.min(100 - margin, cy)),
|
|
};
|
|
}
|
|
|
|
export function agentMapPosition(
|
|
agent: Agent,
|
|
group: FleetGroup | undefined,
|
|
groupIndex: number,
|
|
totalGroups: number,
|
|
agentIndexInCluster: number,
|
|
clusterSize: number,
|
|
): { x: number; y: number } {
|
|
const seed = agent.hostname || agent.name || agent.id;
|
|
if (group) {
|
|
const center = groupClusterCenter(groupIndex, totalGroups);
|
|
const jitter = hashPosition(`${group.id}:${agent.id}`, 0);
|
|
const spread = Math.min(9, 2.5 + clusterSize * 0.7);
|
|
const angle = (agentIndexInCluster / Math.max(clusterSize, 1)) * Math.PI * 2;
|
|
return {
|
|
x: center.x + Math.cos(angle) * spread + (jitter.x - 50) * 0.06,
|
|
y: center.y + Math.sin(angle) * spread + (jitter.y - 50) * 0.06,
|
|
};
|
|
}
|
|
return hashPosition(seed);
|
|
}
|
|
|
|
export function agentAccentColor(agentId: string, allIds: string[], groupColor?: string): string {
|
|
if (groupColor) return groupColor;
|
|
const idx = allIds.indexOf(agentId);
|
|
return FLEET_GROUP_COLORS[idx % FLEET_GROUP_COLORS.length] ?? FLEET_GROUP_COLORS[0];
|
|
}
|
|
|
|
export function hashrateSpiked(prev: number | undefined, current: number): boolean {
|
|
if (current <= 0) return false;
|
|
if (prev === undefined || prev <= 0) return current >= HASHRATE_SPIKE_MIN_DELTA;
|
|
const delta = current - prev;
|
|
return delta >= HASHRATE_SPIKE_MIN_DELTA && current >= prev * HASHRATE_SPIKE_RATIO;
|
|
}
|
|
|
|
export function layoutAgentPoints(agents: Agent[], groups: FleetGroup[]): MapPoint[] {
|
|
const groupsWithAgents = groups.filter((g) => agents.some((a) => g.agentIds.includes(a.id)));
|
|
|
|
return agents.map((agent) => {
|
|
const pg = primaryGroupForAgent(groups, agent.id);
|
|
const groupIndex = pg ? groupsWithAgents.findIndex((g) => g.id === pg.id) : -1;
|
|
const clusterAgents = pg ? agents.filter((a) => pg.agentIds.includes(a.id)) : [];
|
|
const agentIndexInCluster = pg ? clusterAgents.findIndex((a) => a.id === agent.id) : 0;
|
|
const pos = agentMapPosition(
|
|
agent,
|
|
pg,
|
|
groupIndex >= 0 ? groupIndex : 0,
|
|
groupsWithAgents.length || 1,
|
|
agentIndexInCluster,
|
|
clusterAgents.length,
|
|
);
|
|
|
|
return {
|
|
id: agent.id,
|
|
kind: 'agent' as const,
|
|
x: pos.x,
|
|
y: pos.y,
|
|
label: agent.name,
|
|
color: pg?.color,
|
|
online: agent.status === 'online',
|
|
};
|
|
});
|
|
}
|
|
|
|
export function layoutComradePoints(users: string[]): MapPoint[] {
|
|
return users.map((user) => {
|
|
const pos = hashPosition(`comrade:${user}`, 8);
|
|
return {
|
|
id: `comrade:${user}`,
|
|
kind: 'comrade' as const,
|
|
x: pos.x,
|
|
y: pos.y,
|
|
label: user,
|
|
color: COMRADE_DOT_COLOR,
|
|
online: true,
|
|
};
|
|
});
|
|
}
|