mirror of
https://github.com/dpup/meshstream.git
synced 2026-05-05 21:12:31 +02:00
181 lines
5.1 KiB
TypeScript
181 lines
5.1 KiB
TypeScript
/**
|
|
* Activity status and color utilities for Meshtastic nodes
|
|
*/
|
|
|
|
// Different activity levels
|
|
export enum ActivityLevel {
|
|
RECENT = 'recent', // Very recently seen
|
|
ACTIVE = 'active', // Active but not super recent
|
|
INACTIVE = 'inactive' // Not active for a while
|
|
}
|
|
|
|
// Node types
|
|
export enum NodeType {
|
|
NODE = 'node',
|
|
GATEWAY = 'gateway'
|
|
}
|
|
|
|
// Allow different time thresholds for different node types in seconds
|
|
export const TIME_THRESHOLDS = {
|
|
[NodeType.NODE]: {
|
|
recent: 600, // 10 minutes
|
|
active: 1800, // 30 minutes
|
|
},
|
|
[NodeType.GATEWAY]: {
|
|
recent: 600, // 10 minutes
|
|
active: 1800, // 30 minutes
|
|
},
|
|
};
|
|
|
|
// Color schemes for different node types
|
|
export const COLORS = {
|
|
[NodeType.GATEWAY]: {
|
|
[ActivityLevel.RECENT]: {
|
|
fill: "#4ade80",
|
|
stroke: "#22c55e",
|
|
text: "#4ade80",
|
|
background: "bg-green-900/30",
|
|
textClass: "text-green-500",
|
|
bgClass: "bg-green-500",
|
|
statusDot: "bg-green-500"
|
|
},
|
|
[ActivityLevel.ACTIVE]: {
|
|
fill: "#16a34a",
|
|
stroke: "#15803d",
|
|
text: "#16a34a",
|
|
background: "bg-green-900/50",
|
|
textClass: "text-green-700",
|
|
bgClass: "bg-green-700",
|
|
statusDot: "bg-green-700"
|
|
},
|
|
[ActivityLevel.INACTIVE]: {
|
|
fill: "#9ca3af",
|
|
stroke: "#6b7280",
|
|
text: "#6b7280",
|
|
background: "bg-neutral-700/30",
|
|
textClass: "text-neutral-500",
|
|
bgClass: "bg-neutral-500",
|
|
statusDot: "bg-neutral-500"
|
|
},
|
|
},
|
|
[NodeType.NODE]: {
|
|
[ActivityLevel.RECENT]: {
|
|
"fill": "#93c5fd",
|
|
"stroke": "#60a5fa",
|
|
"text": "#93c5fd",
|
|
"background": "bg-blue-900/30",
|
|
"textClass": "text-blue-500",
|
|
"bgClass": "bg-blue-500",
|
|
"statusDot": "bg-blue-500"
|
|
},
|
|
[ActivityLevel.ACTIVE]: {
|
|
"fill": "#3b82f6",
|
|
"stroke": "#2563eb",
|
|
"text": "#3b82f6",
|
|
"background": "bg-blue-900/50",
|
|
"textClass": "text-blue-700",
|
|
"bgClass": "bg-blue-700",
|
|
"statusDot": "bg-blue-700"
|
|
},
|
|
[ActivityLevel.INACTIVE]: {
|
|
"fill": "#9ca3af",
|
|
"stroke": "#6b7280",
|
|
"text": "#6b7280",
|
|
"background": "bg-neutral-700/30",
|
|
"textClass": "text-neutral-500",
|
|
"bgClass": "bg-neutral-500",
|
|
"statusDot": "bg-neutral-500"
|
|
}
|
|
},
|
|
};
|
|
|
|
// Status text for different activity levels
|
|
export const STATUS_TEXT = {
|
|
[ActivityLevel.RECENT]: 'Active',
|
|
[ActivityLevel.ACTIVE]: 'Recent',
|
|
[ActivityLevel.INACTIVE]: 'Inactive',
|
|
};
|
|
|
|
/**
|
|
* Determines the activity level of a node based on its last heard time
|
|
*
|
|
* @param lastHeardTimestamp UNIX timestamp in seconds
|
|
* @param isGateway Whether the node is a gateway
|
|
* @returns The activity level (RECENT, ACTIVE, or INACTIVE)
|
|
*/
|
|
export function getActivityLevel(lastHeardTimestamp?: number, isGateway = false): ActivityLevel {
|
|
if (!lastHeardTimestamp) return ActivityLevel.INACTIVE;
|
|
|
|
const nodeType = isGateway ? NodeType.GATEWAY : NodeType.NODE;
|
|
const secondsSince = Math.floor(Date.now() / 1000) - lastHeardTimestamp;
|
|
|
|
if (secondsSince < TIME_THRESHOLDS[nodeType].recent) {
|
|
return ActivityLevel.RECENT;
|
|
} else if (secondsSince < TIME_THRESHOLDS[nodeType].active) {
|
|
return ActivityLevel.ACTIVE;
|
|
} else {
|
|
return ActivityLevel.INACTIVE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the color scheme for a node based on its activity level
|
|
*
|
|
* @param activityLevel The activity level
|
|
* @param isGateway Whether the node is a gateway
|
|
* @returns Color scheme object
|
|
*/
|
|
export function getNodeColors(activityLevel: ActivityLevel, isGateway = false): typeof COLORS[NodeType.NODE][ActivityLevel.RECENT] {
|
|
const nodeType = isGateway ? NodeType.GATEWAY : NodeType.NODE;
|
|
return COLORS[nodeType][activityLevel];
|
|
}
|
|
|
|
/**
|
|
* Returns the status text for an activity level
|
|
*
|
|
* @param activityLevel The activity level
|
|
* @returns Status text
|
|
*/
|
|
export function getStatusText(activityLevel: ActivityLevel): string {
|
|
return STATUS_TEXT[activityLevel];
|
|
}
|
|
|
|
/**
|
|
* Formats a "last seen" time difference in a human-readable format
|
|
*
|
|
* @param secondsAgo Number of seconds since the event
|
|
* @returns Human-readable time string (e.g., "2 minutes ago")
|
|
*/
|
|
export function formatLastSeen(secondsAgo: number): string {
|
|
if (secondsAgo < 60) {
|
|
return `${secondsAgo} seconds ago`;
|
|
} else if (secondsAgo < 3600) {
|
|
const minutes = Math.floor(secondsAgo / 60);
|
|
return `${minutes} minute${minutes > 1 ? 's' : ''} ago`;
|
|
} else if (secondsAgo < 86400) {
|
|
const hours = Math.floor(secondsAgo / 3600);
|
|
return `${hours} hour${hours > 1 ? 's' : ''} ago`;
|
|
} else {
|
|
const days = Math.floor(secondsAgo / 86400);
|
|
return `${days} day${days > 1 ? 's' : ''} ago`;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets style classes based on the activity level
|
|
*
|
|
* @param lastHeardTimestamp UNIX timestamp in seconds
|
|
* @param isGateway Whether the node is a gateway
|
|
* @returns Object with color classes for various UI elements
|
|
*/
|
|
export function getActivityStyles(lastHeardTimestamp?: number, isGateway = false) {
|
|
const activityLevel = getActivityLevel(lastHeardTimestamp, isGateway);
|
|
const colors = getNodeColors(activityLevel, isGateway);
|
|
const statusText = getStatusText(activityLevel);
|
|
|
|
return {
|
|
activityLevel,
|
|
statusText,
|
|
...colors
|
|
};
|
|
} |