/* ===================================================================== * MISSION CONTROL — Siber Sunucu DevOps İzleme Kokpiti * Express backend + sahte telemetri üretici + REST API * * Uçlar: * GET /metrics → anlık dalgalanan cpu/ram/disk/ağ + olaylar * GET /processes → 6 sahte prosesin running listesi * DELETE /processes/:id → bir prosesi durdurup listeden düşürür * GET /events → olay konsolu geçmişi (ön veri) * (statik) static /public → single-page kokpit * ===================================================================== */ 'use strict'; const path = require('path'); const express = require('express'); const app = express(); app.use(express.json()); app.use(express.static(path.join(__dirname, 'public'))); const PORT = process.env.PORT || 5000; const TICK_MS = 1000; // her saniye kalp atışı const EVENT_BUF = 60; // olay konsolu ring buffer boyutu const CPU_HISTORY_LEN = 30; // son 30 saniyelik CPU geçmişi (sunucu tarafı da tutar) /* --------------------------------------------------------------------- * Yardımcılar * ------------------------------------------------------------------- */ function clamp(v, lo, hi) { return v < lo ? lo : (v > hi ? hi : v); } function rnd(min, max) { return min + Math.random() * (max - min); } function formatTime(d) { const p = (n) => String(n).padStart(2, '0'); return `${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`; } /* --------------------------------------------------------------------- * Metro: rastgele dalgalanan sahte metrikler * ------------------------------------------------------------------- */ const metrics = { cpu: { value: 36, min: 4, max: 96, step: 8, chance: 0.07 }, memory: { usedMB: 3920, totalMB: 8192, step: 90, chance: 0.02, spikeTo: 7100 }, disk: { usedMB: 638, totalMB: 1024, step: 2, chance: 0.01, spikeTo: 995 }, network: { inMbps: 12.4, outMbps: 3.2, step: 4, min: 0.2, max: 70, chance: 0.12 }, ts: Date.now(), }; const cpuHistory = []; // son 30 sn cpu yüzdesi metrics.cpu.value = 36; function randomWalk(key) { const m = metrics[key]; const delta = rnd(-m.step, m.step); let v = m.value; if (Array.isArray(v)) v = v[0]; // güvenlik — network nesne değil return Number(clamp(v + delta, m.min ?? 0, m.max ?? 100).toFixed(1)); } /* Tüm metrikleri tek tıkta dalgalandır (1 saniyede 1) */ function tickMetrics() { // CPU — brownian benzeri yürüyüş + nadir spike metrics.cpu.value = clamp( Number((metrics.cpu.value + rnd(-metrics.cpu.step, metrics.cpu.step)).toFixed(1)), metrics.cpu.min, metrics.cpu.max ); if (Math.random() < metrics.cpu.chance) { metrics.cpu.value = clamp(metrics.cpu.value + rnd(25, 45), metrics.cpu.min, metrics.cpu.max); metrics.cpu.value = Number(metrics.cpu.value.toFixed(1)); logEvent('warn', 'CPU', `cpu spike detected (${metrics.cpu.value.toFixed(0)}%)`); } cpuHistory.push(metrics.cpu.value); if (cpuHistory.length > CPU_HISTORY_LEN) cpuHistory.shift(); // Memory — ılımlı dalgalanma + nadir yüksek ani bellecek const memDelta = rnd(-metrics.memory.step, metrics.memory.step); metrics.memory.usedMB = clamp(metrics.memory.usedMB + memDelta, 1500, 7800); if (Math.random() < metrics.memory.chance) { metrics.memory.usedMB = clamp(metrics.memory.usedMB + rnd(400, 1200), 1500, 7800); logEvent('warn', 'MEM', `memory usage climbing (${pctMemory()}%)`); } // Disk — yüksek sabit + cılız drift metrics.disk.usedMB = clamp( metrics.disk.usedMB + rnd(-metrics.disk.step, metrics.disk.step), 300, 1000 ); if (Math.random() < metrics.disk.chance * 0.3) { metrics.disk.usedMB = clamp(metrics.disk.usedMB + rnd(20, 60), 300, 1005); if (metrics.disk.usedMB > 990) logEvent('crit', 'DISK', 'disk near full — threshold 98%'); } // Ağ — sürekli iniş çıkış metrics.network.inMbps = clamp( Number((metrics.network.inMbps + rnd(-metrics.network.step, metrics.network.step)).toFixed(2)), metrics.network.min, metrics.network.max ); metrics.network.outMbps = clamp( Number((metrics.network.outMbps + rnd(-metrics.network.step * 0.6, metrics.network.step * 0.6)).toFixed(2)), 0.2, metrics.network.max ); metrics.ts = Date.now(); } function pctMemory() { return Math.round((metrics.memory.usedMB / metrics.memory.totalMB) * 100); } function pctDisk() { return Math.round((metrics.disk.usedMB / metrics.disk.totalMB) * 100); } function fmtGB(mb) { return (mb / 1024).toFixed(2) + ' GB'; } function fmtMBVal(mb) { return mb.toFixed(0) + ' MB'; } /* --------------------------------------------------------------------- * Metro: olay günlüğü (ring buffer) * ------------------------------------------------------------------- */ const events = []; function logEvent(type, tag, message) { events.push({ time: formatTime(new Date()), type: type || 'info', // ok | warn | crit | info tag: tag || 'SYS', message: String(message), }); if (events.length > EVENT_BUF) events.shift(); } /* Açılışta kokpiti dolduran başlangıç olayları */ function seedEvents() { logEvent('ok', 'nginx', 'request GET /api/v1/orders 200 OK (12ms)'); logEvent('ok', 'redis', 'cache hit key:user:5842 (0.4ms)'); logEvent('ok', 'postgresql', 'connection pool healthy (24/25 conn)'); logEvent('info', 'auth', 'token refresh for operator session'); logEvent('warn', 'analytics', 'slow query detected — 1.8s (threshold 1.5s)'); logEvent('info', 'SYS', 'mission-control uplink established'); logEvent('ok', 'payment', 'webhook acknowledged (evt_8f2a)'); } /* --------------------------------------------------------------------- * Metro: 6 sahte proses * ------------------------------------------------------------------- */ const processes = new Map(); // key: pid let _nextPid = 1101; function spawnDefaultProcesses() { const defs = [ { service: 'nginx', baseCpu: 14, maxCpu: 22, baseMem: 210 }, { service: 'postgresql', baseCpu: 22, maxCpu: 40, baseMem: 980 }, { service: 'redis', baseCpu: 8, maxCpu: 15, baseMem: 320 }, { service: 'auth-service', baseCpu: 16, maxCpu: 30, baseMem: 460 }, { service: 'payment-worker', baseCpu: 11, maxCpu: 26, baseMem: 540 }, { service: 'analytics', baseCpu: 24, maxCpu: 48, baseMem: 1280 }, ]; for (const d of defs) { const pid = _nextPid++; processes.set(pid, { pid, service: d.service, cpu: clamp(d.baseCpu + rnd(-4, 4), 1, d.maxCpu), memMB: clamp(d.baseMem + rnd(-30, 30), 20, d.baseMem + 200), status: 'running', baseCpu: d.baseCpu, maxCpu: d.maxCpu, baseMem: d.baseMem, }); } } /* Her saniye proseslerin cpu/mem değerlerini hafif oynat */ function tickProcesses() { for (const p of processes.values()) { if (p.status !== 'running') continue; const nc = clamp( Number((p.cpu + rnd(-6, 6)).toFixed(1)), 2, p.maxCpu + 12 ); if (Math.random() < 0.03 && p.cpu > p.maxCpu) { logEvent('warn', p.service, `cpu spike on ${p.service} (${nc.toFixed(0)}%)`); } p.cpu = nc; p.memMB = clamp( Number((p.memMB + rnd(-40, 40)).toFixed(0)), 25, p.baseMem + 300 ); } } function runningProcesses() { const list = [...processes.values()].filter(p => p.status === 'running'); return list.sort((a, b) => a.pid - b.pid); } /* --------------------------------------------------------------------- * Metro: seyrek otomatik arka plan olayları (canlılık hissi) * ------------------------------------------------------------------- */ function maybeAutoLog() { const roll = Math.random(); if (roll < 0.22) { const pool = [ ['ok', 'nginx', 'request GET /api/v1/health 200 OK (9ms)'], ['ok', 'nginx', 'request POST /api/v1/webhooks 201 Created (34ms)'], ['ok', 'redis', 'cache hit key:session:9912 (0.3ms)'], ['ok', 'redis', 'SET cache miss → stored key:flight:77 (0.6ms)'], ['ok', 'auth', 'login success op:sektor (2FA ok)'], ['info', 'payment', 'transaction 0x41c..f2 confirmed (12 confirmations)'], ['info', 'analytics', 'aggregation window closing… writing 12.4k rows'], ['ok', 'postgresql', 'checkpoint complete (wal flushed)'], ['info', 'nginx', 'reload config — zero downtime'], ['warn', 'auth', 'brute-force attempt blocked ip:203.0.113.99'], ]; const picked = pool[Math.floor(Math.random() * pool.length)]; logEvent(picked[0], picked[1], picked[2]); } else if (roll < 0.30) { // Nadiren bir ağ veya cpu normalleştirme bildirimi const type = Math.random() < 0.5 ? 'ok' : 'warn'; const tag = Math.random() < 0.5 ? 'NET' : 'CPU'; const msg = type === 'ok' ? `traffic normalized — in ${metrics.network.inMbps.toFixed(1)} Mbps` : `load average rising — cpu ${metrics.cpu.value.toFixed(0)}%`; logEvent(type, tag, msg); } } /* --------------------------------------------------------------------- * REST API uçları * ------------------------------------------------------------------- */ app.get('/metrics', (req, res) => { res.json({ ts: metrics.ts, cpu: Number(metrics.cpu.value), cpuHistory: cpuHistory.slice(), memory: { usedMB: Math.round(metrics.memory.usedMB), totalMB: metrics.memory.totalMB, percent: pctMemory(), usedText: fmtMBVal(metrics.memory.usedMB), }, disk: { usedMB: Math.round(metrics.disk.usedMB), totalMB: metrics.disk.totalMB, percent: pctDisk(), usedText: fmtMBVal(metrics.disk.usedMB), }, network: { inMbps: Number(metrics.network.inMbps), outMbps: Number(metrics.network.outMbps), }, processesRunning: runningProcesses().length, events: events.slice(-EVENT_BUF), uptimeSec: Math.round(process.uptime()), }); }); app.get('/processes', (req, res) => { res.json({ processes: runningProcesses() }); }); app.get('/events', (req, res) => { res.json({ events: events.slice(-EVENT_BUF) }); }); app.delete('/processes/:id', (req, res) => { const raw = req.params.id; const pid = Number(raw); let target = null; if (!Number.isNaN(pid)) { target = processes.get(pid) || null; } else { // servis adıyla da bulabiliriz for (const p of processes.values()) { if (p.service === raw && p.status === 'running') { target = p; break; } } } if (!target) { return res.status(404).json({ ok: false, error: 'process not found' }); } if (target.status !== 'running') { return res.status(409).json({ ok: false, error: 'process already stopped' }); } target.status = 'stopped'; // Prosesi haritadan kalıcı çıkar ki restart edilmemiş saysın // (isteğe göre durumda tutmak da yeterli; running listesinden düşer zaten) logEvent('crit', 'SYS', `process ${target.service} (pid ${target.pid}) terminated by operator`); res.json({ ok: true, pid: target.pid, service: target.service, status: 'stopped' }); }); /* --------------------------------------------------------------------- * Kalp atışı döngüsü + başlat * ------------------------------------------------------------------- */ spawnDefaultProcesses(); seedEvents(); for (let i = 0; i < CPU_HISTORY_LEN; i++) cpuHistory.push(metrics.cpu.value); // ön doldur setInterval(() => { tickMetrics(); tickProcesses(); maybeAutoLog(); }, TICK_MS); app.listen(PORT, () => { console.log('=================================================='); console.log(' MISSION CONTROL — uplink established'); console.log(` API + UI : http://localhost:${PORT}`); console.log(' endpoints : GET /metrics, GET /processes,'); console.log(' GET /events, DELETE /processes/:id'); console.log(` heartbeat : ${TICK_MS}ms`); console.log('=================================================='); });