Fix security warnings
This commit is contained in:
186
supabase/functions/docker-metrics/index.ts
Normal file
186
supabase/functions/docker-metrics/index.ts
Normal file
@@ -0,0 +1,186 @@
|
||||
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
|
||||
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.39.3';
|
||||
|
||||
const DOCKER_SOCKET = '/var/run/docker.sock';
|
||||
const HOST_PROC = '/host/proc';
|
||||
|
||||
// Helper to make Unix socket HTTP requests
|
||||
async function dockerRequest(path: string) {
|
||||
const conn = await Deno.connect({ transport: "unix", path: DOCKER_SOCKET });
|
||||
|
||||
const request = `GET ${path} HTTP/1.1\r\nHost: localhost\r\n\r\n`;
|
||||
await conn.write(new TextEncoder().encode(request));
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
const buffer = new Uint8Array(65536);
|
||||
const n = await conn.read(buffer);
|
||||
conn.close();
|
||||
|
||||
if (!n) throw new Error('No response');
|
||||
|
||||
const response = decoder.decode(buffer.subarray(0, n));
|
||||
const [, body] = response.split('\r\n\r\n');
|
||||
|
||||
return JSON.parse(body);
|
||||
}
|
||||
|
||||
// Read host metrics from /proc
|
||||
async function getHostMetrics() {
|
||||
try {
|
||||
// CPU info
|
||||
const cpuInfo = await Deno.readTextFile(`${HOST_PROC}/stat`);
|
||||
const cpuLine = cpuInfo.split('\n')[0];
|
||||
const cpuValues = cpuLine.split(/\s+/).slice(1).map(Number);
|
||||
const total = cpuValues.reduce((a, b) => a + b, 0);
|
||||
const idle = cpuValues[3];
|
||||
const cpuUsage = ((total - idle) / total) * 100;
|
||||
|
||||
// Memory info
|
||||
const memInfo = await Deno.readTextFile(`${HOST_PROC}/meminfo`);
|
||||
const memLines = memInfo.split('\n');
|
||||
const memTotal = parseInt(memLines.find(l => l.startsWith('MemTotal'))?.split(/\s+/)[1] || '0');
|
||||
const memFree = parseInt(memLines.find(l => l.startsWith('MemFree'))?.split(/\s+/)[1] || '0');
|
||||
const memAvailable = parseInt(memLines.find(l => l.startsWith('MemAvailable'))?.split(/\s+/)[1] || '0');
|
||||
|
||||
// Uptime
|
||||
const uptime = await Deno.readTextFile(`${HOST_PROC}/uptime`);
|
||||
const uptimeSeconds = parseFloat(uptime.split(' ')[0]);
|
||||
|
||||
return {
|
||||
cpu: { usage: cpuUsage.toFixed(2) },
|
||||
memory: {
|
||||
total: memTotal,
|
||||
free: memFree,
|
||||
available: memAvailable,
|
||||
used: memTotal - memFree,
|
||||
usage: ((memTotal - memAvailable) / memTotal * 100).toFixed(2)
|
||||
},
|
||||
uptime: uptimeSeconds
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error reading host metrics:', error);
|
||||
return {
|
||||
cpu: { usage: 0 },
|
||||
memory: { total: 0, free: 0, available: 0, used: 0, usage: 0 },
|
||||
uptime: 0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
serve(async (req) => {
|
||||
const upgrade = req.headers.get("upgrade") || "";
|
||||
|
||||
if (upgrade.toLowerCase() !== "websocket") {
|
||||
return new Response("Expected WebSocket", { status: 400 });
|
||||
}
|
||||
|
||||
try {
|
||||
const authHeader = req.headers.get('Authorization');
|
||||
if (!authHeader) {
|
||||
return new Response("Missing authorization", { status: 401 });
|
||||
}
|
||||
|
||||
const supabase = createClient(
|
||||
Deno.env.get('SUPABASE_URL') ?? '',
|
||||
Deno.env.get('SUPABASE_ANON_KEY') ?? '',
|
||||
{ global: { headers: { Authorization: authHeader } } }
|
||||
);
|
||||
|
||||
const { data: { user }, error: userError } = await supabase.auth.getUser();
|
||||
if (userError || !user) {
|
||||
return new Response("Unauthorized", { status: 401 });
|
||||
}
|
||||
|
||||
const { socket, response } = Deno.upgradeWebSocket(req);
|
||||
|
||||
let intervalId: number;
|
||||
|
||||
socket.onopen = () => {
|
||||
console.log("WebSocket metrics connection opened");
|
||||
|
||||
// Send metrics every 2 seconds
|
||||
intervalId = setInterval(async () => {
|
||||
try {
|
||||
const hostMetrics = await getHostMetrics();
|
||||
const containers = await dockerRequest('/containers/json');
|
||||
|
||||
const containerStats = [];
|
||||
for (const container of containers) {
|
||||
try {
|
||||
const stats = await dockerRequest(`/containers/${container.Id}/stats?stream=false`);
|
||||
containerStats.push({
|
||||
id: container.Id,
|
||||
name: container.Names[0].replace(/^\//, ''),
|
||||
cpu: calculateCPUPercent(stats),
|
||||
memory: calculateMemoryUsage(stats),
|
||||
network: calculateNetworkIO(stats),
|
||||
blockIO: calculateBlockIO(stats)
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(`Error getting stats for ${container.Id}:`, e);
|
||||
}
|
||||
}
|
||||
|
||||
socket.send(JSON.stringify({
|
||||
type: 'metrics',
|
||||
timestamp: new Date().toISOString(),
|
||||
host: hostMetrics,
|
||||
containers: containerStats
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('Error sending metrics:', error);
|
||||
}
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
socket.onclose = () => {
|
||||
console.log("WebSocket connection closed");
|
||||
if (intervalId) clearInterval(intervalId);
|
||||
};
|
||||
|
||||
socket.onerror = (e) => {
|
||||
console.error("WebSocket error:", e);
|
||||
if (intervalId) clearInterval(intervalId);
|
||||
};
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error("WebSocket setup error:", error);
|
||||
return new Response("Internal error", { status: 500 });
|
||||
}
|
||||
});
|
||||
|
||||
// Helper functions to calculate stats
|
||||
function calculateCPUPercent(stats: any): string {
|
||||
const cpuDelta = stats.cpu_stats.cpu_usage.total_usage - stats.precpu_stats.cpu_usage.total_usage;
|
||||
const systemDelta = stats.cpu_stats.system_cpu_usage - stats.precpu_stats.system_cpu_usage;
|
||||
const cpuPercent = (cpuDelta / systemDelta) * stats.cpu_stats.online_cpus * 100;
|
||||
return cpuPercent.toFixed(2);
|
||||
}
|
||||
|
||||
function calculateMemoryUsage(stats: any): { used: number; limit: number; percent: string } {
|
||||
const used = stats.memory_stats.usage - (stats.memory_stats.stats?.cache || 0);
|
||||
const limit = stats.memory_stats.limit;
|
||||
const percent = (used / limit * 100).toFixed(2);
|
||||
return { used, limit, percent };
|
||||
}
|
||||
|
||||
function calculateNetworkIO(stats: any): { rx: number; tx: number } {
|
||||
const networks = stats.networks || {};
|
||||
let rx = 0, tx = 0;
|
||||
for (const net of Object.values(networks) as any[]) {
|
||||
rx += net.rx_bytes || 0;
|
||||
tx += net.tx_bytes || 0;
|
||||
}
|
||||
return { rx, tx };
|
||||
}
|
||||
|
||||
function calculateBlockIO(stats: any): { read: number; write: number } {
|
||||
const io = stats.blkio_stats.io_service_bytes_recursive || [];
|
||||
let read = 0, write = 0;
|
||||
for (const entry of io) {
|
||||
if (entry.op === 'Read') read += entry.value;
|
||||
if (entry.op === 'Write') write += entry.value;
|
||||
}
|
||||
return { read, write };
|
||||
}
|
||||
Reference in New Issue
Block a user