260 lines
11 KiB
TypeScript
260 lines
11 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState, useRef } from "react";
|
|
import { useDictionary } from "@/lib/dictionary";
|
|
|
|
export default function TenantTelemetry() {
|
|
const { t } = useDictionary();
|
|
const [logs, setLogs] = useState<{ id: string; time: string; action: string; target: string; status: string }[]>([]);
|
|
const [activeConnections, setActiveConnections] = useState(342);
|
|
const [bandwidth, setBandwidth] = useState(1.2);
|
|
const [targetKillId, setTargetKillId] = useState("");
|
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
|
|
// WebGL-Style Waveform Simulation using Canvas for Zero-Error absolute performance
|
|
useEffect(() => {
|
|
const canvas = canvasRef.current;
|
|
if (!canvas) return;
|
|
const ctx = canvas.getContext("2d");
|
|
if (!ctx) return;
|
|
|
|
let animationFrameId: number;
|
|
let time = 0;
|
|
|
|
const resize = () => {
|
|
canvas.width = canvas.clientWidth;
|
|
canvas.height = canvas.clientHeight;
|
|
};
|
|
window.addEventListener("resize", resize);
|
|
resize();
|
|
|
|
const draw = () => {
|
|
time += 0.05;
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
|
|
const width = canvas.width;
|
|
const height = canvas.height;
|
|
const centerY = height / 2;
|
|
|
|
// Draw multiple waves
|
|
for (let i = 0; i < 2; i++) {
|
|
ctx.beginPath();
|
|
ctx.moveTo(0, centerY);
|
|
|
|
for (let x = 0; x < width; x++) {
|
|
const frequency = 0.015 + (i * 0.005);
|
|
const amplitude = 20 + (i * 10) + Math.sin(time + x * 0.02) * 5;
|
|
const y = Math.sin(x * frequency + time + i * 2) * amplitude;
|
|
ctx.lineTo(x, centerY + y);
|
|
}
|
|
|
|
// WhatsApp Emerald to Zoom Blue Gradient
|
|
const gradient = ctx.createLinearGradient(0, 0, width, 0);
|
|
gradient.addColorStop(0, "rgba(37, 211, 102, 0.5)"); // WhatsApp Green
|
|
gradient.addColorStop(1, "rgba(11, 92, 255, 0.5)"); // Zoom Blue
|
|
|
|
ctx.strokeStyle = gradient;
|
|
ctx.lineWidth = 2 + i;
|
|
ctx.stroke();
|
|
}
|
|
|
|
animationFrameId = requestAnimationFrame(draw);
|
|
};
|
|
draw();
|
|
|
|
return () => {
|
|
window.removeEventListener("resize", resize);
|
|
cancelAnimationFrame(animationFrameId);
|
|
};
|
|
}, []);
|
|
|
|
// Fetch real telemetry data from PANOPTICON backend
|
|
useEffect(() => {
|
|
const fetchTelemetry = async () => {
|
|
try {
|
|
const res = await fetch("/api/admin/telemetry");
|
|
if (res.ok) {
|
|
const data = await res.json();
|
|
// Transform quantum logs into UI log format
|
|
const formattedLogs = data.logs.map((log: any) => ({
|
|
id: log.id,
|
|
time: log.nanoTimestamp ? new Date(log.nanoTimestamp).toISOString().split("T")[1].substring(0, 8) : "00:00:00",
|
|
action: log.action,
|
|
target: log.targetId,
|
|
status: log.action.includes("KILL") ? "TERMINATED" : "OK",
|
|
}));
|
|
setLogs(formattedLogs);
|
|
|
|
setActiveConnections(data.stats.totalTelemetryRecords);
|
|
setBandwidth(parseFloat(data.stats.totalBandwidthBytes) / 1024 / 1024 / 1024);
|
|
}
|
|
} catch (err) {
|
|
console.error("PANOPTICON Sync Error:", err);
|
|
}
|
|
};
|
|
|
|
fetchTelemetry();
|
|
const interval = setInterval(fetchTelemetry, 2000);
|
|
return () => clearInterval(interval);
|
|
}, []);
|
|
|
|
const executeLiveKill = async () => {
|
|
if (!targetKillId) return;
|
|
|
|
// Spectactular Kill Trigger Effect
|
|
document.body.style.animation = "shake 0.5s cubic-bezier(.36,.07,.19,.97) both";
|
|
setTimeout(() => document.body.style.animation = "", 500);
|
|
|
|
const res = await fetch("/api/telemetry/live-kill", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
targetType: "USER",
|
|
targetId: targetKillId,
|
|
reason: "TENANT_ADMIN_OVERRIDE"
|
|
})
|
|
});
|
|
|
|
if (res.ok) {
|
|
setLogs(prev => [{
|
|
id: "KILL_" + Date.now(),
|
|
time: new Date().toISOString().split("T")[1].substring(0, 8),
|
|
action: "LIVE_KILL_EXECUTE",
|
|
target: targetKillId,
|
|
status: "TERMINATED"
|
|
}, ...prev]);
|
|
setTargetKillId("");
|
|
alert("XCU MoQ Ejection Berhasil. Sesi Staf Dihanguskan.");
|
|
} else {
|
|
alert("Akses Ditolak: Hanya Berlaku Untuk Grup Internal.");
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="min-h-screen bg-[#050914] text-gray-200 font-sans selection:bg-blue-500/30 overflow-hidden relative">
|
|
<style dangerouslySetInnerHTML={{__html: `
|
|
@keyframes shake {
|
|
10%, 90% { transform: translate3d(-1px, 0, 0); }
|
|
20%, 80% { transform: translate3d(2px, 0, 0); }
|
|
30%, 50%, 70% { transform: translate3d(-4px, 0, 0); }
|
|
40%, 60% { transform: translate3d(4px, 0, 0); }
|
|
}
|
|
.matrix-bg {
|
|
background-image:
|
|
linear-gradient(rgba(11, 92, 255, 0.03) 1px, transparent 1px),
|
|
linear-gradient(90deg, rgba(37, 211, 102, 0.03) 1px, transparent 1px);
|
|
background-size: 40px 40px;
|
|
}
|
|
`}} />
|
|
|
|
{/* Decorative Grid */}
|
|
<div className="absolute inset-0 matrix-bg pointer-events-none z-0"></div>
|
|
|
|
<div className="relative z-10 p-6 md:p-10 max-w-7xl mx-auto flex flex-col gap-8 h-screen">
|
|
|
|
{/* HEADER */}
|
|
<header className="flex justify-between items-center border-b border-white/10 pb-6">
|
|
<div>
|
|
<h1 className="text-3xl font-black tracking-tighter text-transparent bg-clip-text bg-gradient-to-r from-[#25D366] to-[#0b5cff]">
|
|
TENANT COMMAND CENTER
|
|
</h1>
|
|
<p className="text-sm font-mono text-green-400/80 uppercase tracking-widest mt-1">
|
|
JUMPA.ID • Sandboxed Yurisdiction
|
|
</p>
|
|
</div>
|
|
<div className="flex gap-4 items-center">
|
|
<div className="px-4 py-2 rounded-lg bg-blue-500/10 border border-blue-500/30 flex items-center gap-2 shadow-[0_0_15px_rgba(11,92,255,0.2)]">
|
|
<span className="w-2 h-2 rounded-full bg-blue-500 animate-pulse"></span>
|
|
<span className="font-mono text-blue-400 text-sm font-bold">GROUP STABLE</span>
|
|
</div>
|
|
<a href="/admin" className="px-4 py-2 rounded-lg bg-white/5 hover:bg-white/10 border border-white/10 text-sm font-bold transition-all">
|
|
Tutup Dasbor
|
|
</a>
|
|
</div>
|
|
</header>
|
|
|
|
{/* TOP METRICS */}
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
<div className="bg-black/40 backdrop-blur-xl border border-white/5 rounded-2xl p-6 relative overflow-hidden group">
|
|
<div className="absolute inset-0 bg-gradient-to-br from-green-500/10 to-transparent opacity-0 group-hover:opacity-100 transition-all duration-500"></div>
|
|
<p className="text-sm font-mono text-gray-500 uppercase">Active Staff Streams</p>
|
|
<p className="text-5xl font-black mt-2 text-white font-mono tracking-tighter">
|
|
{activeConnections.toLocaleString()}
|
|
</p>
|
|
</div>
|
|
<div className="bg-black/40 backdrop-blur-xl border border-white/5 rounded-2xl p-6 relative overflow-hidden group">
|
|
<div className="absolute inset-0 bg-gradient-to-br from-blue-500/10 to-transparent opacity-0 group-hover:opacity-100 transition-all duration-500"></div>
|
|
<p className="text-sm font-mono text-gray-500 uppercase">Group Bandwidth (Gbps)</p>
|
|
<p className="text-5xl font-black mt-2 text-transparent bg-clip-text bg-gradient-to-r from-white to-gray-500 font-mono tracking-tighter">
|
|
{bandwidth.toFixed(2)}
|
|
</p>
|
|
</div>
|
|
<div className="bg-black/40 backdrop-blur-xl border border-red-500/20 rounded-2xl p-6 relative shadow-[0_0_30px_rgba(255,0,0,0.1)]">
|
|
<p className="text-sm font-bold text-red-500 uppercase mb-4 flex items-center gap-2">
|
|
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M12 2L1 21h22M12 6l7.5 13h-15M11 10h2v5h-2M11 16h2v2h-2"/></svg>
|
|
GUILLOTINE (STAFF ONLY)
|
|
</p>
|
|
<div className="flex gap-2">
|
|
<input
|
|
type="text"
|
|
value={targetKillId}
|
|
onChange={(e) => setTargetKillId(e.target.value)}
|
|
placeholder="Target ID (Staf Anda)"
|
|
className="w-full bg-black/50 border border-red-500/30 rounded-lg px-3 py-2 text-sm font-mono focus:outline-none focus:border-red-500 text-white placeholder-gray-600"
|
|
/>
|
|
<button
|
|
onClick={executeLiveKill}
|
|
className="bg-red-600 hover:bg-red-500 text-white px-4 py-2 rounded-lg font-bold text-sm tracking-widest shadow-[0_0_15px_rgba(255,0,0,0.4)] transition-all hover:scale-105 active:scale-95"
|
|
>
|
|
KILL
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* VISUALIZATION & LOGS */}
|
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 flex-1 min-h-0">
|
|
|
|
{/* Waveform Canvas */}
|
|
<div className="lg:col-span-2 bg-black/40 backdrop-blur-xl border border-white/5 rounded-2xl relative overflow-hidden flex flex-col">
|
|
<div className="p-4 border-b border-white/5 flex justify-between items-center bg-black/40">
|
|
<p className="text-xs font-mono text-green-400">TENANT LOCAL XCU TELEMETRY</p>
|
|
<div className="flex gap-2">
|
|
<span className="w-2 h-2 rounded-full bg-green-500"></span>
|
|
<span className="w-2 h-2 rounded-full bg-blue-500"></span>
|
|
</div>
|
|
</div>
|
|
<div className="flex-1 relative">
|
|
<canvas ref={canvasRef} className="absolute inset-0 w-full h-full opacity-70 mix-blend-screen"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Hacker Log Stream */}
|
|
<div className="bg-black/60 backdrop-blur-xl border border-white/5 rounded-2xl flex flex-col overflow-hidden relative shadow-inner">
|
|
<div className="absolute inset-x-0 top-0 h-8 bg-gradient-to-b from-green-500/10 to-transparent z-10 pointer-events-none"></div>
|
|
<div className="p-4 border-b border-white/5 bg-[#0a0a0a]">
|
|
<p className="text-xs font-mono text-gray-400 flex items-center gap-2">
|
|
<span className="w-2 h-2 rounded-full bg-red-500 animate-ping"></span>
|
|
TENANT TRACE LOGS
|
|
</p>
|
|
</div>
|
|
<div className="flex-1 overflow-y-auto p-4 font-mono text-xs flex flex-col gap-1">
|
|
{logs.map((log, i) => (
|
|
<div key={log.id + i} className="flex gap-3 hover:bg-white/5 px-2 py-1 rounded transition-colors group">
|
|
<span className="text-gray-600">[{log.time}]</span>
|
|
<span className={log.status === 'TERMINATED' ? 'text-red-500 font-bold' : log.status === 'WARN' ? 'text-yellow-500' : 'text-green-400'}>
|
|
{log.action}
|
|
</span>
|
|
<span className="text-blue-300 ml-auto opacity-70 group-hover:opacity-100">{log.target}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|