containers update + docker file build
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 3s
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 3s
This commit is contained in:
parent
4a2a9eba30
commit
dac64ac41a
@ -4,7 +4,8 @@
|
||||
"scripts": {
|
||||
"dev": "tsx watch src/index.ts",
|
||||
"build": "tsc",
|
||||
"start": "node dist/index.js"
|
||||
"start": "node dist/index.js",
|
||||
"build": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hono/node-server": "^1.14.3",
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
import Docker from 'dockerode';
|
||||
|
||||
export function createDockerClient() {
|
||||
return new Docker();
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import { createDockerClient } from './client.js';
|
||||
import type { DockerContainer, Networks } from './types.js';
|
||||
import createDockerClient from "../utils/client.js";
|
||||
import type { DockerContainer, Networks } from "./types.js";
|
||||
|
||||
const docker = createDockerClient();
|
||||
|
||||
@ -21,8 +21,8 @@ export async function listContainers(): Promise<DockerContainer[]> {
|
||||
})),
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('Error listing containers:', error);
|
||||
throw new Error('Failed to list Docker containers');
|
||||
console.error("Error listing containers:", error);
|
||||
throw new Error("Failed to list Docker containers");
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,10 +35,13 @@ export async function getContainerStats(containerId: string) {
|
||||
const currSys = stats.cpu_stats.system_cpu_usage;
|
||||
const cpuDelta = currCpu - prevCpu;
|
||||
const sysDelta = currSys - prevSys;
|
||||
const numCpus =
|
||||
stats.cpu_stats.online_cpus ||
|
||||
(stats.cpu_stats.cpu_usage.percpu_usage
|
||||
? stats.cpu_stats.cpu_usage.percpu_usage.length
|
||||
: 1);
|
||||
const cpuPercent =
|
||||
sysDelta > 0
|
||||
? (cpuDelta / sysDelta) * stats.cpu_stats.cpu_usage.percpu_usage.length * 100.0
|
||||
: 0.0;
|
||||
sysDelta > 0 ? (cpuDelta / sysDelta) * numCpus * 100.0 : 0.0;
|
||||
const memUsage = stats.memory_stats.usage;
|
||||
const memLimit = stats.memory_stats.limit;
|
||||
const memPercent = memLimit > 0 ? (memUsage / memLimit) * 100.0 : 0.0;
|
||||
@ -58,8 +61,8 @@ export async function getContainerStats(containerId: string) {
|
||||
Array.isArray(stats.blkio_stats.io_service_bytes_recursive)
|
||||
) {
|
||||
for (const entry of stats.blkio_stats.io_service_bytes_recursive) {
|
||||
if (entry.op === 'Read') blkRead += entry.value;
|
||||
if (entry.op === 'Write') blkWrite += entry.value;
|
||||
if (entry.op === "Read") blkRead += entry.value;
|
||||
if (entry.op === "Write") blkWrite += entry.value;
|
||||
}
|
||||
}
|
||||
return {
|
||||
@ -82,19 +85,26 @@ export async function streamContainerStats(
|
||||
): Promise<() => void> {
|
||||
const container = docker.getContainer(containerId);
|
||||
try {
|
||||
const stream = (await container.stats({ stream: true })) as import('stream').Readable;
|
||||
stream.on('data', (chunk: Buffer) => {
|
||||
const stream = (await container.stats({
|
||||
stream: true,
|
||||
})) as import("stream").Readable;
|
||||
stream.on("data", (chunk: Buffer) => {
|
||||
const stats = JSON.parse(chunk.toString());
|
||||
|
||||
const prevCpu = stats.precpu_stats.cpu_usage.total_usage;
|
||||
const prevSys = stats.precpu_stats.system_cpu_usage;
|
||||
const currCpu = stats.cpu_stats.cpu_usage.total_usage;
|
||||
const currSys = stats.cpu_stats.system_cpu_usage;
|
||||
const cpuDelta = currCpu - prevCpu;
|
||||
const sysDelta = currSys - prevSys;
|
||||
|
||||
const numCpus =
|
||||
stats.cpu_stats.online_cpus ||
|
||||
(stats.cpu_stats.cpu_usage.percpu_usage
|
||||
? stats.cpu_stats.cpu_usage.percpu_usage.length
|
||||
: 1);
|
||||
const cpuPercent =
|
||||
sysDelta > 0
|
||||
? (cpuDelta / sysDelta) * stats.cpu_stats.cpu_usage.percpu_usage.length * 100.0
|
||||
: 0.0;
|
||||
sysDelta > 0 ? (cpuDelta / sysDelta) * numCpus * 100.0 : 0.0;
|
||||
const memUsage = stats.memory_stats.usage;
|
||||
const memLimit = stats.memory_stats.limit;
|
||||
const memPercent = memLimit > 0 ? (memUsage / memLimit) * 100.0 : 0.0;
|
||||
@ -114,8 +124,8 @@ export async function streamContainerStats(
|
||||
Array.isArray(stats.blkio_stats.io_service_bytes_recursive)
|
||||
) {
|
||||
for (const entry of stats.blkio_stats.io_service_bytes_recursive) {
|
||||
if (entry.op === 'Read') blkRead += entry.value;
|
||||
if (entry.op === 'Write') blkWrite += entry.value;
|
||||
if (entry.op === "Read") blkRead += entry.value;
|
||||
if (entry.op === "Write") blkWrite += entry.value;
|
||||
}
|
||||
}
|
||||
callback({
|
||||
@ -131,14 +141,17 @@ export async function streamContainerStats(
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
});
|
||||
stream.on('error', (err: Error) => {
|
||||
stream.on("error", (err: Error) => {
|
||||
console.error(`Error streaming stats for container ${containerId}:`, err);
|
||||
});
|
||||
return () => {
|
||||
stream.destroy();
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`Error getting stats stream for container ${containerId}:`, error);
|
||||
console.error(
|
||||
`Error getting stats stream for container ${containerId}:`,
|
||||
error
|
||||
);
|
||||
throw new Error(`Failed to get stats stream for container ${containerId}`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,75 +1,88 @@
|
||||
import { serve } from '@hono/node-server'
|
||||
import { Hono } from 'hono'
|
||||
import { createNodeWebSocket } from '@hono/node-ws'
|
||||
import { serve } from "@hono/node-server";
|
||||
import { serveStatic } from "@hono/node-server/serve-static";
|
||||
import { createNodeWebSocket } from "@hono/node-ws";
|
||||
import { Hono } from "hono";
|
||||
import { readFileSync } from "node:fs";
|
||||
import {
|
||||
registerContainerClient,
|
||||
registerStatsClient,
|
||||
registerCombinedClient,
|
||||
registerContainerClient,
|
||||
registerSingleContainerClient,
|
||||
removeContainerClient,
|
||||
removeStatsClient,
|
||||
registerStatsClient,
|
||||
removeCombinedClient,
|
||||
removeContainerClient,
|
||||
removeSingleContainerClient,
|
||||
stopAllStreams
|
||||
} from './ws/manager.js';
|
||||
removeStatsClient,
|
||||
stopAllStreams,
|
||||
} from "./ws/manager.js";
|
||||
|
||||
const app = new Hono();
|
||||
|
||||
const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app });
|
||||
|
||||
app.get('/', (c) => c.text('WS Docker Stats server (ESM) is running.'))
|
||||
app.use("/*", serveStatic({ root: "./public" }));
|
||||
|
||||
app.get("/", (c) => c.text("WS Docker Stats server (ESM) is running."));
|
||||
|
||||
// Containers-only endpoint
|
||||
app.get('/ws/containers', upgradeWebSocket(c => ({
|
||||
app.get(
|
||||
"/ws/containers",
|
||||
upgradeWebSocket((c) => ({
|
||||
onOpen(_, ws) {
|
||||
console.log('WebSocket containers connection opened');
|
||||
console.log("WebSocket containers connection opened");
|
||||
registerContainerClient(ws);
|
||||
},
|
||||
onClose(_, ws) {
|
||||
console.log('❌ WS containers client disconnected');
|
||||
console.log("❌ WS containers client disconnected");
|
||||
removeContainerClient(ws);
|
||||
},
|
||||
onError(err) {
|
||||
console.error('❌ WS containers error:', err);
|
||||
}
|
||||
})))
|
||||
console.error("❌ WS containers error:", err);
|
||||
},
|
||||
}))
|
||||
);
|
||||
|
||||
// Stats-only endpoint
|
||||
app.get('/ws/stats', upgradeWebSocket(c => ({
|
||||
app.get(
|
||||
"/ws/stats",
|
||||
upgradeWebSocket((c) => ({
|
||||
onOpen(_, ws) {
|
||||
console.log('WebSocket stats connection opened');
|
||||
console.log("WebSocket stats connection opened");
|
||||
registerStatsClient(ws);
|
||||
},
|
||||
onClose(_, ws) {
|
||||
console.log('❌ WS stats client disconnected');
|
||||
console.log("❌ WS stats client disconnected");
|
||||
removeStatsClient(ws);
|
||||
},
|
||||
onError(err) {
|
||||
console.error('❌ WS stats error:', err);
|
||||
}
|
||||
})))
|
||||
console.error("❌ WS stats error:", err);
|
||||
},
|
||||
}))
|
||||
);
|
||||
|
||||
// Combined endpoint (explicit)
|
||||
app.get('/ws/combined', upgradeWebSocket(c => ({
|
||||
app.get(
|
||||
"/ws/combined",
|
||||
upgradeWebSocket((c) => ({
|
||||
onOpen(_, ws) {
|
||||
console.log('WebSocket combined connection opened');
|
||||
console.log("WebSocket combined connection opened");
|
||||
registerCombinedClient(ws);
|
||||
},
|
||||
onClose(_, ws) {
|
||||
console.log('❌ WS combined client disconnected');
|
||||
console.log("❌ WS combined client disconnected");
|
||||
removeCombinedClient(ws);
|
||||
},
|
||||
onError(err) {
|
||||
console.error('❌ WS combined error:', err);
|
||||
}
|
||||
})))
|
||||
console.error("❌ WS combined error:", err);
|
||||
},
|
||||
}))
|
||||
);
|
||||
|
||||
// Single container endpoint
|
||||
app.get('/ws/container', upgradeWebSocket(c => {
|
||||
const containerId = c.req.query('id');
|
||||
app.get(
|
||||
"/ws/container",
|
||||
upgradeWebSocket((c) => {
|
||||
const containerId = c.req.query("id");
|
||||
if (!containerId) {
|
||||
throw new Error('Container ID is required');
|
||||
throw new Error("Container ID is required");
|
||||
}
|
||||
return {
|
||||
onOpen(_, ws) {
|
||||
@ -82,31 +95,39 @@ app.get('/ws/container', upgradeWebSocket(c => {
|
||||
},
|
||||
onError(err) {
|
||||
console.error(`❌ WS error for container ${containerId}:`, err);
|
||||
}
|
||||
},
|
||||
};
|
||||
}))
|
||||
|
||||
const server = serve({
|
||||
fetch: app.fetch,
|
||||
port: 3000
|
||||
}, (info) => {
|
||||
console.log(`Server is running on http://localhost:${info.port}`)
|
||||
})
|
||||
);
|
||||
|
||||
app.get("*", (c) => {
|
||||
return c.html(readFileSync("./public/index.html", "utf-8"));
|
||||
});
|
||||
|
||||
const server = serve(
|
||||
{
|
||||
fetch: app.fetch,
|
||||
port: 3000,
|
||||
},
|
||||
(info) => {
|
||||
console.log(`Server is running on http://localhost:${info.port}`);
|
||||
}
|
||||
);
|
||||
|
||||
injectWebSocket(server);
|
||||
|
||||
process.on('SIGTERM', () => cleanupAndExit());
|
||||
process.on('SIGINT', () => cleanupAndExit());
|
||||
process.on("SIGTERM", () => cleanupAndExit());
|
||||
process.on("SIGINT", () => cleanupAndExit());
|
||||
|
||||
function cleanupAndExit() {
|
||||
console.log('Shutting down gracefully...');
|
||||
console.log("Shutting down gracefully...");
|
||||
stopAllStreams();
|
||||
server.close(() => {
|
||||
console.log('Server closed');
|
||||
console.log("Server closed");
|
||||
process.exit(0);
|
||||
});
|
||||
setTimeout(() => {
|
||||
console.log('Forcing exit after timeout');
|
||||
console.log("Forcing exit after timeout");
|
||||
process.exit(1);
|
||||
}, 5000);
|
||||
}
|
||||
@ -1,9 +1,13 @@
|
||||
import type { WSContext } from 'hono/ws';
|
||||
import { listContainers, streamContainerStats } from '../docker/containers.js';
|
||||
import { sendContainerInfo, sendStats, sendCombined, sendToSingleContainer } from './sender.js';
|
||||
import type { ContainerInfo, ContainerStats } from './types.js';
|
||||
import type { DockerContainer } from '../docker/types.js';
|
||||
|
||||
import type { WSContext } from "hono/ws";
|
||||
import { listContainers, streamContainerStats } from "../docker/containers.js";
|
||||
import type { DockerContainer } from "../docker/types.js";
|
||||
import {
|
||||
sendCombined,
|
||||
sendContainerInfo,
|
||||
sendStats,
|
||||
sendToSingleContainer,
|
||||
} from "./sender.js";
|
||||
import type { ContainerInfo, ContainerStats } from "./types.js";
|
||||
|
||||
const containerClients = new Set<WSContext<WebSocket>>();
|
||||
const statsClients = new Set<WSContext<WebSocket>>();
|
||||
@ -11,18 +15,29 @@ const combinedClients = new Set<WSContext<WebSocket>>();
|
||||
const singleContainerClients = new Map<string, Set<WSContext<WebSocket>>>();
|
||||
const activeStreams = new Map<string, () => void>();
|
||||
|
||||
|
||||
const containerCache = new Map<string, {
|
||||
const containerCache = new Map<
|
||||
string,
|
||||
{
|
||||
data: DockerContainer | null;
|
||||
lastUpdated: number;
|
||||
}>();
|
||||
}
|
||||
>();
|
||||
const CACHE_TTL = 2000;
|
||||
|
||||
// Add a map to collect all container info
|
||||
const allContainerInfo = new Map<string, ContainerInfo>();
|
||||
let containerUpdateTimer: NodeJS.Timeout | null = null;
|
||||
|
||||
export function registerContainerClient(ws: WSContext<WebSocket>) {
|
||||
const wasEmpty = allClientsEmpty();
|
||||
containerClients.add(ws);
|
||||
if (wasEmpty) setupStreams();
|
||||
|
||||
// Send current container data immediately if available
|
||||
if (allContainerInfo.size > 0) {
|
||||
const containers = Array.from(allContainerInfo.values());
|
||||
sendContainerInfo(new Set([ws]), containers);
|
||||
}
|
||||
}
|
||||
|
||||
export function registerStatsClient(ws: WSContext<WebSocket>) {
|
||||
@ -37,7 +52,10 @@ export function registerCombinedClient(ws: WSContext<WebSocket>) {
|
||||
if (wasEmpty) setupStreams();
|
||||
}
|
||||
|
||||
export function registerSingleContainerClient(ws: WSContext<WebSocket>, containerId: string) {
|
||||
export function registerSingleContainerClient(
|
||||
ws: WSContext<WebSocket>,
|
||||
containerId: string
|
||||
) {
|
||||
if (!singleContainerClients.has(containerId)) {
|
||||
singleContainerClients.set(containerId, new Set());
|
||||
}
|
||||
@ -48,7 +66,6 @@ export function registerSingleContainerClient(ws: WSContext<WebSocket>, containe
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function removeContainerClient(ws: WSContext<WebSocket>) {
|
||||
containerClients.delete(ws);
|
||||
checkAndCleanup();
|
||||
@ -64,7 +81,10 @@ export function removeCombinedClient(ws: WSContext<WebSocket>) {
|
||||
checkAndCleanup();
|
||||
}
|
||||
|
||||
export function removeSingleContainerClient(ws: WSContext<WebSocket>, containerId: string) {
|
||||
export function removeSingleContainerClient(
|
||||
ws: WSContext<WebSocket>,
|
||||
containerId: string
|
||||
) {
|
||||
const clients = singleContainerClients.get(containerId);
|
||||
if (clients) {
|
||||
clients.delete(ws);
|
||||
@ -76,10 +96,12 @@ export function removeSingleContainerClient(ws: WSContext<WebSocket>, containerI
|
||||
}
|
||||
|
||||
function allClientsEmpty() {
|
||||
return containerClients.size === 0 &&
|
||||
return (
|
||||
containerClients.size === 0 &&
|
||||
statsClients.size === 0 &&
|
||||
combinedClients.size === 0 &&
|
||||
singleContainerClients.size === 0;
|
||||
singleContainerClients.size === 0
|
||||
);
|
||||
}
|
||||
|
||||
function checkAndCleanup() {
|
||||
@ -94,37 +116,105 @@ async function setupStreams() {
|
||||
const containers = await listContainers();
|
||||
|
||||
for (const container of containers) {
|
||||
const containerInfo: ContainerInfo = {
|
||||
id: container.id,
|
||||
name:
|
||||
container.names?.[0]?.replace("/", "") ?? container.id.slice(0, 12),
|
||||
image: container.image,
|
||||
state: container.state,
|
||||
status: container.status,
|
||||
};
|
||||
allContainerInfo.set(container.id, containerInfo);
|
||||
|
||||
containerCache.set(container.id, {
|
||||
data: container,
|
||||
lastUpdated: Date.now()
|
||||
lastUpdated: Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
// Send all containers to clients
|
||||
if (containerClients.size > 0) {
|
||||
sendContainerInfo(
|
||||
containerClients,
|
||||
Array.from(allContainerInfo.values())
|
||||
);
|
||||
}
|
||||
|
||||
// Setup periodic container list updates
|
||||
if (!containerUpdateTimer) {
|
||||
containerUpdateTimer = setInterval(updateAllContainers, 5000);
|
||||
}
|
||||
|
||||
for (const container of containers) {
|
||||
if (activeStreams.has(container.id)) continue;
|
||||
setupStreamForContainer(container.id);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error setting up streams:', error);
|
||||
console.error("Error setting up streams:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async function getContainerData(containerId: string): Promise<DockerContainer | null> {
|
||||
async function updateAllContainers() {
|
||||
if (allClientsEmpty()) {
|
||||
if (containerUpdateTimer) {
|
||||
clearInterval(containerUpdateTimer);
|
||||
containerUpdateTimer = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const containers = await listContainers();
|
||||
|
||||
// Update container cache and allContainerInfo
|
||||
const now = Date.now();
|
||||
for (const container of containers) {
|
||||
containerCache.set(container.id, {
|
||||
data: container,
|
||||
lastUpdated: now,
|
||||
});
|
||||
|
||||
const containerInfo: ContainerInfo = {
|
||||
id: container.id,
|
||||
name:
|
||||
container.names?.[0]?.replace("/", "") ?? container.id.slice(0, 12),
|
||||
image: container.image,
|
||||
state: container.state,
|
||||
status: container.status,
|
||||
};
|
||||
allContainerInfo.set(container.id, containerInfo);
|
||||
}
|
||||
|
||||
// Send all containers to clients
|
||||
if (containerClients.size > 0) {
|
||||
sendContainerInfo(
|
||||
containerClients,
|
||||
Array.from(allContainerInfo.values())
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating containers:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async function getContainerData(
|
||||
containerId: string
|
||||
): Promise<DockerContainer | null> {
|
||||
const cached = containerCache.get(containerId);
|
||||
const now = Date.now();
|
||||
|
||||
if (cached && (now - cached.lastUpdated < CACHE_TTL)) {
|
||||
if (cached && now - cached.lastUpdated < CACHE_TTL) {
|
||||
return cached.data;
|
||||
}
|
||||
|
||||
try {
|
||||
const containers = await listContainers();
|
||||
const containerData = containers.find(c => c.id === containerId) || null;
|
||||
const containerData = containers.find((c) => c.id === containerId) || null;
|
||||
|
||||
for (const container of containers) {
|
||||
containerCache.set(container.id, {
|
||||
data: container,
|
||||
lastUpdated: now
|
||||
lastUpdated: now,
|
||||
});
|
||||
}
|
||||
|
||||
@ -146,38 +236,46 @@ async function setupStreamForContainer(containerId: string) {
|
||||
return;
|
||||
}
|
||||
|
||||
const stopStream = await streamContainerStats(containerId, async (stats: ContainerStats) => {
|
||||
const stopStream = await streamContainerStats(
|
||||
containerId,
|
||||
async (stats: ContainerStats) => {
|
||||
if (allClientsEmpty()) {
|
||||
stopAllStreams();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const freshContainerData = await getContainerData(containerId);
|
||||
|
||||
const containerInfo: ContainerInfo = freshContainerData ? {
|
||||
const containerInfo: ContainerInfo = freshContainerData
|
||||
? {
|
||||
id: freshContainerData.id,
|
||||
name: freshContainerData.names?.[0]?.replace('/', '') ?? freshContainerData.id.slice(0, 12),
|
||||
name:
|
||||
freshContainerData.names?.[0]?.replace("/", "") ??
|
||||
freshContainerData.id.slice(0, 12),
|
||||
image: freshContainerData.image,
|
||||
state: freshContainerData.state,
|
||||
status: freshContainerData.status,
|
||||
} : {
|
||||
}
|
||||
: {
|
||||
id: containerData.id,
|
||||
name: containerData.names?.[0]?.replace('/', '') ?? containerData.id.slice(0, 12),
|
||||
name:
|
||||
containerData.names?.[0]?.replace("/", "") ??
|
||||
containerData.id.slice(0, 12),
|
||||
image: containerData.image,
|
||||
state: 'exited',
|
||||
status: 'Container stopped',
|
||||
state: "exited",
|
||||
status: "Container stopped",
|
||||
};
|
||||
|
||||
// Update the containerInfo in our map
|
||||
allContainerInfo.set(containerId, containerInfo);
|
||||
|
||||
if (!freshContainerData && !singleContainerClients.has(containerId)) {
|
||||
stopStreamForContainer(containerId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (containerClients.size > 0) {
|
||||
sendContainerInfo(containerClients, containerInfo);
|
||||
}
|
||||
// We don't send containerInfo here anymore, it's sent by updateAllContainers
|
||||
|
||||
if (statsClients.size > 0) {
|
||||
sendStats(statsClients, stats);
|
||||
}
|
||||
@ -186,13 +284,22 @@ async function setupStreamForContainer(containerId: string) {
|
||||
}
|
||||
const singleClients = singleContainerClients.get(containerId);
|
||||
if (singleClients && singleClients.size > 0) {
|
||||
sendToSingleContainer(singleContainerClients, containerId, containerInfo, stats);
|
||||
sendToSingleContainer(
|
||||
singleContainerClients,
|
||||
containerId,
|
||||
containerInfo,
|
||||
stats
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
activeStreams.set(containerId, stopStream);
|
||||
} catch (error) {
|
||||
console.error(`Failed to setup stream for container ${containerId}:`, error);
|
||||
console.error(
|
||||
`Failed to setup stream for container ${containerId}:`,
|
||||
error
|
||||
);
|
||||
|
||||
containerCache.delete(containerId);
|
||||
}
|
||||
@ -205,6 +312,7 @@ function stopStreamForContainer(containerId: string) {
|
||||
activeStreams.delete(containerId);
|
||||
|
||||
containerCache.delete(containerId);
|
||||
allContainerInfo.delete(containerId);
|
||||
}
|
||||
}
|
||||
|
||||
@ -214,4 +322,10 @@ export function stopAllStreams() {
|
||||
}
|
||||
activeStreams.clear();
|
||||
containerCache.clear();
|
||||
allContainerInfo.clear();
|
||||
|
||||
if (containerUpdateTimer) {
|
||||
clearInterval(containerUpdateTimer);
|
||||
containerUpdateTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,12 +1,21 @@
|
||||
import type { WSContext } from 'hono/ws';
|
||||
import type { ContainerInfo, ContainerStats } from './types.js';
|
||||
import type { WSContext } from "hono/ws";
|
||||
import type { ContainerInfo, ContainerStats } from "./types.js";
|
||||
|
||||
export function sendContainerInfo(containerClients: Set<WSContext<WebSocket>>, containerInfo: ContainerInfo) {
|
||||
const payload = JSON.stringify({ containers: [containerInfo] });
|
||||
export function sendContainerInfo(
|
||||
containerClients: Set<WSContext<WebSocket>>,
|
||||
containerInfo: ContainerInfo | ContainerInfo[]
|
||||
) {
|
||||
const containers = Array.isArray(containerInfo)
|
||||
? containerInfo
|
||||
: [containerInfo];
|
||||
const payload = JSON.stringify({ containers });
|
||||
sendToClientSet(containerClients, payload);
|
||||
}
|
||||
|
||||
export function sendStats(statsClients: Set<WSContext<WebSocket>>, stats: ContainerStats) {
|
||||
export function sendStats(
|
||||
statsClients: Set<WSContext<WebSocket>>,
|
||||
stats: ContainerStats
|
||||
) {
|
||||
const payload = JSON.stringify({ stats: [stats] });
|
||||
sendToClientSet(statsClients, payload);
|
||||
}
|
||||
@ -18,7 +27,7 @@ export function sendCombined(
|
||||
) {
|
||||
const payload = JSON.stringify({
|
||||
containers: [containerInfo],
|
||||
stats: [stats]
|
||||
stats: [stats],
|
||||
});
|
||||
sendToClientSet(combinedClients, payload);
|
||||
}
|
||||
@ -31,7 +40,7 @@ export function sendToSingleContainer(
|
||||
) {
|
||||
const combinedData = {
|
||||
...containerInfo,
|
||||
...stats
|
||||
...stats,
|
||||
};
|
||||
const payload = JSON.stringify({ container: combinedData });
|
||||
const clients = singleContainerClients.get(containerId);
|
||||
@ -40,13 +49,16 @@ export function sendToSingleContainer(
|
||||
}
|
||||
}
|
||||
|
||||
export function sendToClientSet(clients: Set<WSContext<WebSocket>>, payload: string) {
|
||||
export function sendToClientSet(
|
||||
clients: Set<WSContext<WebSocket>>,
|
||||
payload: string
|
||||
) {
|
||||
for (const ws of clients) {
|
||||
if (ws.readyState === 1) {
|
||||
try {
|
||||
ws.send(payload);
|
||||
} catch (err) {
|
||||
console.error('Error sending to client:', err);
|
||||
console.error("Error sending to client:", err);
|
||||
clients.delete(ws);
|
||||
}
|
||||
} else {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user