Numex yayın: public/app.js
This commit is contained in:
parent
fd751022ee
commit
aeef1271f1
484
public/app.js
Normal file
484
public/app.js
Normal file
|
|
@ -0,0 +1,484 @@
|
||||||
|
/* =====================================================================
|
||||||
|
MISSION CONTROL — frontend mantığı
|
||||||
|
Saf HTML5 canvas (harici kütüphane yok):
|
||||||
|
· 3 dolan (gauge) göstergesi — CPU, MEMORY, NET
|
||||||
|
· disk yüzük göstergesi
|
||||||
|
· 30 saniyelik akan neon CPU çizgi grafiği (izgara + glow)
|
||||||
|
· canlı proses tablosu + KİLL (DELETE)
|
||||||
|
· akan olay konsolu (ok/warn/crit)
|
||||||
|
===================================================================== */
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/* ---------- kısa yardımcılar ---------- */
|
||||||
|
const $ = (id) => document.getElementById(id);
|
||||||
|
const clampV = (v, lo, hi) => v < lo ? lo : (v > hi ? hi : v);
|
||||||
|
const mapRange = (v, inMin, inMax, outMin, outMax) =>
|
||||||
|
outMin + ((v - inMin) / (inMax - inMin)) * (outMax - outMin);
|
||||||
|
|
||||||
|
const sys = {
|
||||||
|
cpu: 0,
|
||||||
|
cpuHistory: [], // son 30 sn cpu % (frontend ring)
|
||||||
|
memPct: 0, memUsedMB: 0,
|
||||||
|
diskPct: 0, diskUsedMB: 0,
|
||||||
|
netIn: 0, netOut: 0,
|
||||||
|
rowMap: new Map(), // pid -> <tr>
|
||||||
|
lastLogTs: 0, // olay konsolu için karşılaştırma
|
||||||
|
lastCpuTick: Date.now(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const HISTORY_LEN = 30; // saniye
|
||||||
|
const LOG_MAX = 40; // konsol satır limiti
|
||||||
|
|
||||||
|
/* =====================================================================
|
||||||
|
GAUGE ÇİZİMİ (dairesel/dolan)
|
||||||
|
value 0..1, renk scale'le birlikte değişir
|
||||||
|
===================================================================== */
|
||||||
|
const GAUGE_KIND_TO_COLORS = {
|
||||||
|
cpu: ['#00e5ff', '#ffb020', '#ff3550'],
|
||||||
|
mem: ['#39ff88', '#ffb020', '#ff3550'],
|
||||||
|
net: ['#00e5ff', '#39ff88', '#ff3550'],
|
||||||
|
};
|
||||||
|
const GAUGE_CENTER = { cpu: $('gaugeCpu'), mem: $('gaugeMem'), net: $('gaugeNet') };
|
||||||
|
|
||||||
|
function colorForWarning(c, i) {
|
||||||
|
// alt sınır yeşil/camgöbeği değil, % value'ya göre seçim yapalım
|
||||||
|
if (c.warn >= 0.9) return '#ff3550';
|
||||||
|
if (c.warn >= 0.65) return '#ffb020';
|
||||||
|
return c.base;
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawArcBar(canvas, frac, baseColor, tick) {
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
ctx.clearRect(0, 0, canvas._logW || canvas.width, canvas._logH || canvas.height);
|
||||||
|
const w = canvas._logW || canvas.width, h = canvas._logH || canvas.height;
|
||||||
|
const cx = w / 2, cy = h / 2;
|
||||||
|
const R = Math.min(w, h) / 2 - 20;
|
||||||
|
ctx.clearRect(0, 0, w, h);
|
||||||
|
|
||||||
|
const start = Math.PI * 0.75; // başlangıç (220°)
|
||||||
|
const end = Math.PI * 0.25; // bitiş
|
||||||
|
const sweep = Math.PI * 1.5; // 270° dairesel yay
|
||||||
|
const fracC = clampV(frac, 0, 1);
|
||||||
|
const angleFrom = start;
|
||||||
|
const angleTo = start + sweep * fracC;
|
||||||
|
|
||||||
|
// taban kemer
|
||||||
|
ctx.lineWidth = 13; ctx.lineCap = 'round';
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(cx, cy, R, start, end, false);
|
||||||
|
ctx.strokeStyle = 'rgba(80, 120, 160, 0.18)';
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
// dolan kemer degrade
|
||||||
|
const safeColor = colorForWarning({
|
||||||
|
base: baseColor, warn: fracC
|
||||||
|
}, 0);
|
||||||
|
const grad = ctx.createLinearGradient(cx - R, cy - R, cx + R, cy);
|
||||||
|
grad.addColorStop(0, safeColor);
|
||||||
|
grad.addColorStop(1, lighten(safeColor, 0.35));
|
||||||
|
|
||||||
|
ctx.shadowColor = safeColor;
|
||||||
|
ctx.shadowBlur = 12;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(cx, cy, R, angleFrom, angleTo, false);
|
||||||
|
ctx.strokeStyle = grad;
|
||||||
|
ctx.lineWidth = 13;
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.shadowBlur = 0;
|
||||||
|
|
||||||
|
// uçta parlak "damla" nokta
|
||||||
|
const tipX = cx + Math.cos(angleTo) * R;
|
||||||
|
const tipY = cy + Math.sin(angleTo) * R;
|
||||||
|
ctx.beginPath(); ctx.arc(tipX, tipY, 7, 0, Math.PI * 2);
|
||||||
|
ctx.fillStyle = '#ffffff';
|
||||||
|
ctx.fill();
|
||||||
|
ctx.shadowColor = safeColor; ctx.shadowBlur = 14;
|
||||||
|
ctx.fillStyle = safeColor;
|
||||||
|
ctx.beginPath(); ctx.arc(tipX, tipY, 5, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
ctx.shadowBlur = 0;
|
||||||
|
|
||||||
|
// merkezde yüzde
|
||||||
|
ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
|
||||||
|
ctx.font = 'bold 34px Consolas, monospace';
|
||||||
|
ctx.fillStyle = safeColor;
|
||||||
|
ctx.fillText(String(Math.round(fracC * 100)) + '%', cx, cy - 10);
|
||||||
|
ctx.font = '10px Consolas, monospace';
|
||||||
|
ctx.fillStyle = 'rgba(120,190,255,0.5)';
|
||||||
|
ctx.fillText(tick, cx, cy + 18);
|
||||||
|
}
|
||||||
|
|
||||||
|
function lighten(hex, amt) {
|
||||||
|
const c = hex.replace('#', '');
|
||||||
|
const r = parseInt(c.slice(0, 2), 16), g = parseInt(c.slice(2, 4), 16), b = parseInt(c.slice(4, 6), 16);
|
||||||
|
const L = (v) => Math.round(clampV(v + (255 - v) * amt, 0, 255));
|
||||||
|
return '#' + [L(r), L(g), L(b)].map(x => x.toString(16).padStart(2, '0')).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =====================================================================
|
||||||
|
DISK YÜZÜK GÖSTERGESİ (tam çember yüzdelik kullanım)
|
||||||
|
===================================================================== */
|
||||||
|
function drawDiskGauge(canvas, frac) {
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
const w = canvas._logW || canvas.width, h = canvas._logH || canvas.height;
|
||||||
|
const cx = w / 2, cy = h / 2;
|
||||||
|
const outer = Math.min(w, h) / 2 - 10;
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
const f = clampV(frac, 0, 1);
|
||||||
|
// renk: %70 altı yeşil, %70-90 amber, üstü kırmızı
|
||||||
|
const color = f > 0.9 ? '#ff3550' : (f > 0.7 ? '#ffb020' : '#39ff88');
|
||||||
|
|
||||||
|
// taban
|
||||||
|
ctx.lineWidth = 12; ctx.lineCap = 'round';
|
||||||
|
ctx.beginPath(); ctx.arc(cx, cy, outer, 0, Math.PI * 2);
|
||||||
|
ctx.strokeStyle = 'rgba(80,120,160,0.16)'; ctx.stroke();
|
||||||
|
|
||||||
|
// dolan çember (yukarıdan başla)
|
||||||
|
ctx.shadowColor = color; ctx.shadowBlur = 10;
|
||||||
|
ctx.beginPath(); ctx.arc(cx, cy, outer, -Math.PI / 2, -Math.PI / 2 + Math.PI * 2 * f);
|
||||||
|
ctx.strokeStyle = color; ctx.lineWidth = 12; ctx.stroke();
|
||||||
|
ctx.shadowBlur = 0;
|
||||||
|
|
||||||
|
// uç nokta
|
||||||
|
const a = -Math.PI / 2 + Math.PI * 2 * f;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(cx + Math.cos(a) * outer, cy + Math.sin(a) * outer, 6, 0, Math.PI * 2);
|
||||||
|
ctx.fillStyle = '#fff'; ctx.fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =====================================================================
|
||||||
|
CPU GRAFİK — akan neon çizgi, izgara, degradeli dolgu
|
||||||
|
===================================================================== */
|
||||||
|
const chartCanvas = $('cpuChart');
|
||||||
|
let gridReady = false;
|
||||||
|
|
||||||
|
function drawChartBG(ctx, W, H) {
|
||||||
|
if (gridReady) return; // izgarayı sadece 1 kez çiz (arka katman)
|
||||||
|
// dikey çizgiler
|
||||||
|
const cols = 6, rows = 5;
|
||||||
|
ctx.strokeStyle = 'rgba(0,180,255,0.08)';
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
for (let i = 0; i <= cols; i++) {
|
||||||
|
const x = Math.round(mapRange(i, 0, cols, 14, W - 10));
|
||||||
|
ctx.beginPath(); ctx.moveTo(x, 8); ctx.lineTo(x, H - 20); ctx.stroke();
|
||||||
|
}
|
||||||
|
for (let j = 0; j <= rows; j++) {
|
||||||
|
const y = Math.round(mapRange(j, 0, rows, 8, H - 20));
|
||||||
|
ctx.beginPath(); ctx.moveTo(14, y); ctx.lineTo(W - 10, y); ctx.stroke();
|
||||||
|
}
|
||||||
|
// y eksen etiketleri
|
||||||
|
ctx.fillStyle = 'rgba(120,190,255,0.4)';
|
||||||
|
ctx.font = '10px Consolas, monospace'; ctx.textAlign = 'left'; ctx.textBaseline = 'bottom';
|
||||||
|
ctx.fillText('100%', 14, 8);
|
||||||
|
ctx.fillStyle = 'rgba(120,190,255,0.25)';
|
||||||
|
ctx.fillText('cpu load', W - 70, 14);
|
||||||
|
gridReady = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pushHistoryAndDraw() {
|
||||||
|
const c = chartCanvas.getContext('2d');
|
||||||
|
const W = chartCanvas._logW || chartCanvas.width;
|
||||||
|
const H = chartCanvas._logH || chartCanvas.height;
|
||||||
|
c.clearRect(0, 0, chartCanvas.width, chartCanvas.height);
|
||||||
|
drawChartBG(c, W, H);
|
||||||
|
|
||||||
|
if (sys.cpuHistory.length < 2) return;
|
||||||
|
|
||||||
|
const plotW = W - 24, plotH = H - 28;
|
||||||
|
const n = HISTORY_LEN, maxVal = 100;
|
||||||
|
|
||||||
|
// verinin son n noktası
|
||||||
|
const arr = sys.cpuHistory.slice(-n);
|
||||||
|
const px = (i) => 14 + (i / Math.max(1, n - 1)) * plotW;
|
||||||
|
const py = (v) => 8 + (1 - clampV(v, 0, maxVal) / maxVal) * plotH;
|
||||||
|
|
||||||
|
// degradeli dolgu
|
||||||
|
const col = arr[arr.length - 1] > 85 ? '#ff3550' : '#00e5ff';
|
||||||
|
c.beginPath();
|
||||||
|
c.moveTo(px(0), py(arr[0]));
|
||||||
|
for (let i = 1; i < arr.length; i++) c.lineTo(px(i), py(arr[i]));
|
||||||
|
c.lineTo(px(arr.length - 1), H - 12);
|
||||||
|
c.lineTo(px(0), H - 12);
|
||||||
|
c.closePath();
|
||||||
|
const g = c.createLinearGradient(0, 8, 0, H - 12);
|
||||||
|
g.addColorStop(0, 'rgba(0,229,255,0.28)');
|
||||||
|
g.addColorStop(1, 'rgba(0,229,255,0.0)');
|
||||||
|
c.fillStyle = col[0] === '#ff' ? 'rgba(255,53,80,0.22)' : g;
|
||||||
|
c.fill();
|
||||||
|
|
||||||
|
// neon çizgi
|
||||||
|
c.beginPath();
|
||||||
|
c.moveTo(px(0), py(arr[0]));
|
||||||
|
for (let i = 1; i < arr.length; i++) c.lineTo(px(i), py(arr[i]));
|
||||||
|
c.strokeStyle = col;
|
||||||
|
c.lineWidth = 2.5;
|
||||||
|
c.shadowColor = col; c.shadowBlur = 12;
|
||||||
|
c.stroke();
|
||||||
|
c.shadowBlur = 0;
|
||||||
|
|
||||||
|
// son nokta glow damlası
|
||||||
|
const lx = px(arr.length - 1), ly = py(arr[arr.length - 1]);
|
||||||
|
c.beginPath(); c.arc(lx, ly, 5, 0, Math.PI * 2);
|
||||||
|
c.fillStyle = '#fff'; c.fill();
|
||||||
|
c.shadowColor = col; c.shadowBlur = 10;
|
||||||
|
c.fillStyle = col;
|
||||||
|
c.beginPath(); c.arc(lx, ly, 3.5, 0, Math.PI * 2); c.fill();
|
||||||
|
c.shadowBlur = 0;
|
||||||
|
|
||||||
|
// %80 eşik çizgisi (kehribar kesikli)
|
||||||
|
const tY = py(80);
|
||||||
|
c.save();
|
||||||
|
c.strokeStyle = 'rgba(255,176,32,0.4)';
|
||||||
|
c.setLineDash([5, 4]); c.lineWidth = 1;
|
||||||
|
c.shadowBlur = 0;
|
||||||
|
c.beginPath(); c.moveTo(14, tY); c.lineTo(W - 10, tY); c.stroke();
|
||||||
|
c.restore();
|
||||||
|
|
||||||
|
// /dross: değer etiketi üst solda
|
||||||
|
c.fillStyle = col; c.font = 'bold 13px Consolas, monospace';
|
||||||
|
c.textAlign = 'left'; c.textBaseline = 'top';
|
||||||
|
c.fillText('CPU ' + Math.max(0, arr[arr.length - 1]).toFixed(1) + '%', 18, 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =====================================================================
|
||||||
|
METRİKLERİ ÇEK & RENDER
|
||||||
|
===================================================================== */
|
||||||
|
async function refreshMetrics() {
|
||||||
|
let data;
|
||||||
|
try {
|
||||||
|
const r = await fetch('/metrics');
|
||||||
|
data = await r.json();
|
||||||
|
} catch (e) {
|
||||||
|
setServerDown();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sys.cpu = data.cpu;
|
||||||
|
sys.memPct = data.memory.percent;
|
||||||
|
sys.memUsedMB = data.memory.usedMB;
|
||||||
|
sys.diskPct = data.disk.percent;
|
||||||
|
sys.diskUsedMB = data.disk.usedMB;
|
||||||
|
sys.netIn = data.network.inMbps;
|
||||||
|
sys.netOut = data.network.outMbps;
|
||||||
|
sys.processesRunning = data.processesRunning;
|
||||||
|
|
||||||
|
// gauge güncelle
|
||||||
|
drawArcBar(GAUGE_CENTER.cpu, sys.cpu / 100, '#00e5ff', 'consolidated');
|
||||||
|
drawArcBar(GAUGE_CENTER.mem, sys.memPct / 100, '#39ff88', 'of 8192 MB');
|
||||||
|
drawArcBar(GAUGE_CENTER.net, (clampV(sys.netIn, 0, 80) / 80), '#00e5ff', 'ingress Mbps');
|
||||||
|
drawDiskGauge($('diskGauge'), sys.diskPct / 100);
|
||||||
|
|
||||||
|
// etiketler
|
||||||
|
$('cpuPct').textContent = Math.round(sys.cpu) + '%';
|
||||||
|
$('memPct').textContent = sys.memPct + '%';
|
||||||
|
$('memText').textContent = fmtMB(sys.memUsedMB);
|
||||||
|
$('netVal').textContent = sys.netIn.toFixed(1);
|
||||||
|
$('netIn').textContent = sys.netIn.toFixed(1);
|
||||||
|
$('netOut').textContent = sys.netOut.toFixed(1);
|
||||||
|
$('diskScore').textContent = sys.diskPct;
|
||||||
|
$('diskText').textContent = fmtMB(sys.diskUsedMB);
|
||||||
|
|
||||||
|
// istatistik üst
|
||||||
|
$('serverStatus').textContent = data.uptimeSec ? 'ONLINE' : 'ONLINE';
|
||||||
|
$('runCount').textContent = data.processesRunning;
|
||||||
|
$('uptime').textContent = fmtUp(data.uptimeSec);
|
||||||
|
$('procBadge').textContent = data.processesRunning + ' online';
|
||||||
|
|
||||||
|
// canvas cpu geçmişini besle
|
||||||
|
if (data.cpuHistory && Array.isArray(data.cpuHistory)) {
|
||||||
|
sys.cpuHistory = data.cpuHistory.slice();
|
||||||
|
} else {
|
||||||
|
sys.cpuHistory.push(sys.cpu);
|
||||||
|
if (sys.cpuHistory.length > HISTORY_LEN) sys.cpuHistory.shift();
|
||||||
|
}
|
||||||
|
pushHistoryAndDraw();
|
||||||
|
|
||||||
|
// olayları işle
|
||||||
|
if (data.events) consumeEvents(data.events);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setServerDown() {
|
||||||
|
$('serverStatus').textContent = 'OFFLINE';
|
||||||
|
$('serverStatus').style.color = 'var(--red)';
|
||||||
|
}
|
||||||
|
|
||||||
|
function fmtMB(mb) {
|
||||||
|
if (mb >= 1024) return (mb / 1024).toFixed(2) + ' GB';
|
||||||
|
return mb.toFixed(0) + ' MB';
|
||||||
|
}
|
||||||
|
function fmtUp(sec) {
|
||||||
|
if (!sec) return '—';
|
||||||
|
const m = Math.floor(sec / 60), s = sec % 60;
|
||||||
|
return m + 'm ' + String(s).padStart(2, '0') + 's';
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =====================================================================
|
||||||
|
OLAY KONSOLU
|
||||||
|
===================================================================== */
|
||||||
|
const consoleEl = $('logConsole');
|
||||||
|
const seenLog = new Set(); // aynı olayı tekrar basmasın (sürece)
|
||||||
|
|
||||||
|
function consumeEvents(events) {
|
||||||
|
for (const e of events) {
|
||||||
|
const key = e.time + '|' + e.tag + '|' + e.message;
|
||||||
|
if (seenLog.has(key)) continue;
|
||||||
|
seenLog.add(key);
|
||||||
|
if (seenLog.size > 120) seenLog.clear();
|
||||||
|
appendLog(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendLog(e) {
|
||||||
|
const line = document.createElement('div');
|
||||||
|
line.className = 'line type-' + (e.type || 'info');
|
||||||
|
const t = document.createElement('span'); t.className = 't'; t.textContent = e.time + ' ▸';
|
||||||
|
const tag = document.createElement('span'); tag.className = 'tag'; tag.textContent = e.tag.toUpperCase();
|
||||||
|
const msg = document.createElement('span'); msg.className = 'msg'; msg.textContent = e.message;
|
||||||
|
line.appendChild(t); line.appendChild(tag); line.appendChild(msg);
|
||||||
|
consoleEl.prepend(line);
|
||||||
|
|
||||||
|
// max liste buda
|
||||||
|
while (consoleEl.children.length > LOG_MAX) consoleEl.removeChild(consoleEl.lastChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =====================================================================
|
||||||
|
PROSES TABLOSU
|
||||||
|
===================================================================== */
|
||||||
|
const procBody = $('procBody');
|
||||||
|
|
||||||
|
async function loadProcesses() {
|
||||||
|
let data;
|
||||||
|
try { const r = await fetch('/processes'); data = await r.json(); }
|
||||||
|
catch (err) { return; }
|
||||||
|
const list = data.processes || [];
|
||||||
|
|
||||||
|
// mevcut gösterilen pids
|
||||||
|
const shownPids = new Set();
|
||||||
|
for (const p of list) {
|
||||||
|
shownPids.add(p.pid);
|
||||||
|
if (sys.rowMap.has(p.pid)) {
|
||||||
|
updateRow(p, sys.rowMap.get(p.pid));
|
||||||
|
} else {
|
||||||
|
renderRow(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// listede artık yoksa (kill/durable) kaldır
|
||||||
|
for (const pid of [...sys.rowMap.keys()]) {
|
||||||
|
if (!shownPids.has(pid)) {
|
||||||
|
const row = sys.rowMap.get(pid);
|
||||||
|
row.remove();
|
||||||
|
sys.rowMap.delete(pid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$('procEmpty').classList.toggle('hidden', list.length > 0);
|
||||||
|
$('procBadge').textContent = list.length + ' online';
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderRow(p) {
|
||||||
|
const row = document.createElement('tr');
|
||||||
|
row.dataset.pid = p.pid;
|
||||||
|
let html = '';
|
||||||
|
html += '<td class="pid">' + p.pid + '</td>';
|
||||||
|
html += '<td class="svc">' + escapeHtml(p.service) + '</td>';
|
||||||
|
html += '<td class="cpu-cell">' + p.cpu.toFixed(1) + '%</td>';
|
||||||
|
html += '<td class="mem-cell">' + fmtMB(p.memMB) + '</td>';
|
||||||
|
html += '<td><span class="status-pill">RUNNING</span></td>';
|
||||||
|
html += '<td class="td-action"><button class="kill" data-pid="' + p.pid + '">✕ KILL</button></td>';
|
||||||
|
row.innerHTML = html;
|
||||||
|
procBody.appendChild(row);
|
||||||
|
sys.rowMap.set(p.pid, row);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateRow(p, row) {
|
||||||
|
const cells = row.querySelectorAll('td');
|
||||||
|
if (cells.length >= 4) {
|
||||||
|
cells[2].textContent = p.cpu.toFixed(1) + '%';
|
||||||
|
cells[2].classList.toggle('hot', p.cpu > 85 || p.cpu > (p.maxCpu || 90));
|
||||||
|
cells[3].textContent = fmtMB(p.memMB);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeHtml(x) { return String(x).replace(/[&<>"]/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c])); }
|
||||||
|
|
||||||
|
/* KİLL — DELETE isteği → satırı sil */
|
||||||
|
async function killProcess(pid, btn) {
|
||||||
|
if (btn) { btn.disabled = true; btn.textContent = '…'; }
|
||||||
|
try {
|
||||||
|
const res = await fetch('/processes/' + encodeURIComponent(pid), { method: 'DELETE' });
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.ok) {
|
||||||
|
const row = sys.rowMap.get(data.pid);
|
||||||
|
if (row) { row.remove(); sys.rowMap.delete(data.pid); }
|
||||||
|
appendLog({ time: tsNow(), type: 'crit', tag: 'SYS', message: 'process ' + data.service + ' terminated by operator (pid ' + data.pid + ')' });
|
||||||
|
$('procEmpty').classList.toggle('hidden', sys.rowMap.size > 0);
|
||||||
|
} else {
|
||||||
|
if (btn) { btn.disabled = false; btn.textContent = '✕ KILL'; }
|
||||||
|
appendLog({ time: tsNow(), type: 'warn', tag: 'SYS', message: 'kill failed — ' + (data.error || 'unknown') });
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (btn) { btn.disabled = false; btn.textContent = '✕ KILL'; }
|
||||||
|
appendLog({ time: tsNow(), type: 'warn', tag: 'ERR', message: 'network error on terminate' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function tsNow() {
|
||||||
|
const d = new Date(), p = (n) => String(n).padStart(2, '0');
|
||||||
|
return p(d.getHours()) + ':' + p(d.getMinutes()) + ':' + p(d.getSeconds());
|
||||||
|
}
|
||||||
|
|
||||||
|
/* event delegation — dinamik butonlar için */
|
||||||
|
function bindKillButtons() {
|
||||||
|
procBody.addEventListener('click', (e) => {
|
||||||
|
const btn = e.target.closest('.kill');
|
||||||
|
if (!btn) return;
|
||||||
|
e.stopPropagation();
|
||||||
|
killProcess(btn.dataset.pid, btn);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =====================================================================
|
||||||
|
GÖREV DÖNGÜSÜ (1 sn'de 1)
|
||||||
|
===================================================================== */
|
||||||
|
function tick() {
|
||||||
|
refreshMetrics(); // /metrics → gauge + grafik + olaylar
|
||||||
|
loadProcesses(); // /processes → tablo yansıt
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
// kartuş değerleri sıfır göster → hızlı ilk boya
|
||||||
|
drawArcBar(GAUGE_CENTER.cpu, 0, '#00e5ff', 'consolidated');
|
||||||
|
drawArcBar(GAUGE_CENTER.mem, 0, '#39ff88', 'of 8192 MB');
|
||||||
|
drawArcBar(GAUGE_CENTER.net, 0, '#00e5ff', 'ingress Mbps');
|
||||||
|
drawDiskGauge($('diskGauge'), 0);
|
||||||
|
pushHistoryAndDraw();
|
||||||
|
|
||||||
|
bindKillButtons();
|
||||||
|
tick();
|
||||||
|
setInterval(tick, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Canvas'ları yüksek dpi çözünürlüğe uyarla (keskin çizgi, boyutlar tutarlı kalır)
|
||||||
|
function setupHiDPI() {
|
||||||
|
const dpr = window.devicePixelRatio || 1;
|
||||||
|
const setCanvas = (c, logW, logH) => {
|
||||||
|
c.width = Math.round(logW * dpr);
|
||||||
|
c.height = Math.round(logH * dpr);
|
||||||
|
c.style.width = (c.id === 'cpuChart' ? '100%' : logW + 'px');
|
||||||
|
c.style.height = logH + 'px';
|
||||||
|
// DRAW fonksiyonları çizim alanını CSS/cihaz px cinsinden bilsin:
|
||||||
|
// CSS koordinatlarına geri dönmek için scale store
|
||||||
|
c._logW = logW; c._logH = logH; c._dpr = dpr;
|
||||||
|
const ctx = c.getContext('2d');
|
||||||
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
||||||
|
};
|
||||||
|
setCanvas(chartCanvas, 980, 240);
|
||||||
|
setCanvas(GAUGE_CENTER.cpu, 220, 220);
|
||||||
|
setCanvas(GAUGE_CENTER.mem, 220, 220);
|
||||||
|
setCanvas(GAUGE_CENTER.net, 220, 220);
|
||||||
|
setCanvas($('diskGauge'), 200, 200);
|
||||||
|
gridReady = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
setupHiDPI();
|
||||||
|
init();
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user