<?php
/**
* Server Stats API Logic
* 이 섹션은 서버 사이드에서 실행되어 실제 리소스 정보를 JSON으로 반환합니다.
*/
if (isset($_GET['action']) && $_GET['action'] === 'get_stats') {
header('Content-Type: application/json');
// 1. CPU Usage (%)
$cpu_load = sys_getloadavg();
$cpu_count = (int)shell_exec('nproc') ?: 1;
$cpu_usage = floor(($cpu_load[0] / $cpu_count) * 100);
if ($cpu_usage > 100) $cpu_usage = 100;
// 2. RAM Usage
$free = shell_exec('free -m');
$free = (string)trim($free);
$free_arr = explode("\n", $free);
$mem = explode(" ", preg_replace("/\s+/", " ", $free_arr[1]));
$mem_total = (int)($mem[1] ?? 1);
$mem_used = (int)($mem[2] ?? 0);
$ram_usage = floor(($mem_used / $mem_total) * 100);
// 3. Disk Usage
function get_disk_info($path) {
if (!is_dir($path)) return ['total' => 1, 'used' => 0];
$total_bytes = @disk_total_space($path);
if ($total_bytes === false || $total_bytes == 0) {
$df_output = shell_exec("df -P $path | tail -1 | awk '{print $2 \" \" $3}'");
if ($df_output) {
$parts = explode(" ", trim($df_output));
$total_gb = floor(($parts[0] ?? 0) / 1024 / 1024);
$used_gb = floor(($parts[1] ?? 0) / 1024 / 1024);
return ['total' => max(1, $total_gb), 'used' => $used_gb];
}
return ['total' => 1, 'used' => 0];
}
$total = floor($total_bytes / (1024 * 1024 * 1024));
$free = floor(@disk_free_space($path) / (1024 * 1024 * 1024));
$used = max(0, $total - $free);
return ['total' => max(1, $total), 'used' => $used];
}
$disk_root = get_disk_info('/');
$disk_data = get_disk_info('/data');
// 4. System Vitality
$uptime_raw = shell_exec("cut -d. -f1 /proc/uptime") ?: 0;
$uptime_days = floor($uptime_raw / 86400);
$proc_count = (int)shell_exec("ps -e | wc -l");
// 5. Database Connections
$db_conns = (int)shell_exec("netstat -an | grep ':3306' | grep 'ESTABLISHED' | wc -l");
if (!$db_conns) {
$db_conns = (int)shell_exec("ss -ant | grep ':3306' | grep 'ESTAB' | wc -l");
}
$db_active = $db_conns ?: ((int)shell_exec("pgrep -f mariadbd | wc -l") ? 1 : 0);
echo json_encode([
'cpu' => $cpu_usage,
'ram' => $ram_usage,
'disk_root' => $disk_root,
'disk_data' => $disk_data,
'mem_total' => $mem_total,
'mem_used' => $mem_used,
'uptime_days' => $uptime_days,
'proc_count' => $proc_count,
'db_conns' => $db_active,
'load_idx' => floor($cpu_load[0] * 100 / $cpu_count)
]);
exit;
}
// 헤더 부분 포함
require_once '/home/www/GNU/_PAGE/head.php';
?>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>코어 모니터링 시스템 V4 - 전문가 모드</title>
<script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/lucide@latest"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;900&family=JetBrains+Mono:wght@400;700&family=Noto+Sans+KR:wght@300;700;900&display=swap');
:root {
--neon-cyan: #00f2ff;
--neon-blue: #0066ff;
--neon-purple: #bc13fe;
--neon-rose: #ff0055;
--neon-amber: #ffaa00;
}
body {
font-family: 'Noto Sans KR', sans-serif;
background-color: #00040a;
color: #e2e8f0;
overflow-x: hidden;
width: 100%;
}
.orbitron { font-family: 'Orbitron', sans-serif; }
.mono { font-family: 'JetBrains Mono', monospace; }
.bg-grid {
background-image:
linear-gradient(to right, rgba(0, 242, 255, 0.05) 1px, transparent 1px),
linear-gradient(to bottom, rgba(0, 242, 255, 0.05) 1px, transparent 1px);
background-size: 50px 50px;
mask-image: radial-gradient(circle at 50% 50%, black, transparent 80%);
}
.glow-text-cyan { text-shadow: 0 0 10px rgba(0, 242, 255, 0.7); }
.glow-text-purple { text-shadow: 0 0 10px rgba(188, 19, 254, 0.7); }
.glow-text-rose { text-shadow: 0 0 10px rgba(255, 0, 85, 0.7); }
.glow-text-amber { text-shadow: 0 0 10px rgba(255, 170, 0, 0.7); }
@keyframes border-rotate {
0% { border-color: rgba(0, 242, 255, 0.2); }
50% { border-color: rgba(0, 242, 255, 0.8); }
100% { border-color: rgba(0, 242, 255, 0.2); }
}
.neon-border { border-width: 2px; animation: border-rotate 4s infinite; }
@keyframes data-pulse {
0% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.05); opacity: 0.8; }
100% { transform: scale(1); opacity: 1; }
}
.animate-data { animation: data-pulse 0.5s ease-out; }
.spin-slow { animation: spin 15s linear infinite; }
@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
@keyframes card-in {
from { opacity: 0; transform: perspective(1000px) rotateX(10deg) translateY(30px); }
to { opacity: 1; transform: perspective(1000px) rotateX(0deg) translateY(0); }
}
.animate-card { animation: card-in 0.8s cubic-bezier(0.16, 1, 0.3, 1) forwards; }
/* 페이지 스크롤바 스타일 */
::-webkit-scrollbar {
width: 7px;
}
::-webkit-scrollbar-track {
background: #020617;
}
::-webkit-scrollbar-thumb {
background: rgba(99, 102, 241, 0.5);
border-radius: 6px;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(99, 102, 241, 0.7);
}
</style>
</head>
<body class="selection:bg-cyan-500/30 text-left">
<div id="root"></div>
<script type="text/babel">
const { useState, useEffect, useMemo } = React;
const Icon = ({ name, size = 20, className = "" }) => {
useEffect(() => { if (window.lucide) window.lucide.createIcons(); }, [name]);
return <i data-lucide={name} style={{ width: size, height: size }} className={className}></i>;
};
const StatusGauge = ({ data, type = "disk" }) => {
const [isExpanded, setIsExpanded] = useState(false);
const [pulse, setPulse] = useState(false);
const percentage = type === "disk"
? Math.floor((data.used / (data.total || 1)) * 100)
: Math.floor(data.usage);
const isCritical = percentage >= 90;
const isWarning = percentage >= 80 && percentage < 90;
const colors = {
disk: { hex: "#00f2ff", class: "cyan-400", bg: "bg-cyan-500", glow: "glow-text-cyan" },
cpu: { hex: "#bc13fe", class: "purple-500", bg: "bg-purple-600", glow: "glow-text-purple" },
ram: { hex: "#0066ff", class: "blue-500", bg: "bg-blue-600", glow: "glow-text-cyan" },
health: { hex: "#10b981", class: "emerald-500", bg: "bg-emerald-600", glow: "glow-text-cyan" },
db: { hex: "#ff0055", class: "rose-500", bg: "bg-rose-600", glow: "glow-text-rose" }
};
const theme = colors[type] || colors.disk;
let currentColorClass = theme.class;
let currentGlowClass = theme.glow;
let statusLabel = "정상";
if (isCritical) {
currentColorClass = "rose-500";
currentGlowClass = "glow-text-rose";
statusLabel = "임계치 초과";
} else if (isWarning) {
currentColorClass = "amber-500";
statusLabel = "부하 주의";
}
useEffect(() => {
setPulse(true);
const timer = setTimeout(() => setPulse(false), 500);
return () => clearTimeout(timer);
}, [percentage]);
const radius = 60;
const circumference = 2 * Math.PI * radius;
const strokeDashoffset = circumference - (Math.min(100, percentage) / 100) * circumference;
return (
<div className="animate-card group">
<div
onClick={() => setIsExpanded(!isExpanded)}
className={`relative cursor-pointer bg-slate-950/85 backdrop-blur-md rounded-2xl p-8 border border-white/10 transition-all duration-500 hover:scale-[1.01] active:scale-95 ${isCritical ? 'shadow-[0_0_40px_rgba(255,0,85,0.2)]' : 'shadow-[0_0_20px_rgba(0,242,255,0.05)]'}`}
>
<div className={`absolute top-0 left-0 w-12 h-12 border-t-4 border-l-4 border-${currentColorClass}/40 rounded-tl-2xl opacity-0 group-hover:opacity-100 transition-opacity`}></div>
<div className="flex justify-between items-start mb-8 text-left">
<div className="flex items-center gap-5">
<div className={`relative p-5 rounded-2xl bg-black border border-${currentColorClass}/30`}>
<Icon name={data.icon || "activity"} size={32} className={`text-${currentColorClass}`} />
<div className={`absolute -inset-1 rounded-2xl bg-${currentColorClass}/10 animate-pulse`}></div>
</div>
<div className="text-left">
<h3 className={`text-2xl font-black tracking-tight group-hover:text-white transition-colors ${pulse ? 'animate-data' : ''}`}>{data.name}</h3>
<p className="text-sm mono text-slate-400 uppercase tracking-widest mt-1">{data.subtitle}</p>
</div>
</div>
<div className={`px-3 py-1 rounded-lg text-xs font-black border border-${currentColorClass}/40 bg-black text-${currentColorClass} orbitron tracking-widest`}>
{type === "health" || type === "db" ? "활성화" : statusLabel}
</div>
</div>
<div className="relative flex justify-center items-center py-4 mb-8">
<div className="relative w-52 h-52">
<div className="absolute inset-0 border-[6px] border-slate-900 rounded-full"></div>
<div className="absolute inset-3 border border-dashed border-slate-800 rounded-full spin-slow"></div>
<svg className="w-full h-full transform -rotate-90" viewBox="0 0 160 160">
<circle className="text-slate-900" strokeWidth="8" stroke="currentColor" fill="transparent" r={radius} cx="80" cy="80" />
<circle
className={`text-${currentColorClass} transition-all duration-1000 cubic-bezier(0.34, 1.56, 0.64, 1)`}
strokeWidth="10"
strokeDasharray={circumference}
strokeDashoffset={strokeDashoffset}
strokeLinecap="butt"
stroke="currentColor"
fill="transparent"
r={radius}
cx="80"
cy="80"
style={{ filter: `drop-shadow(0 0 12px ${theme.hex})` }}
/>
</svg>
<div className="absolute inset-0 flex flex-col items-center justify-center text-center px-4 overflow-hidden">
<span className={`text-6xl font-black orbitron tracking-tighter transition-all duration-300 ${pulse ? 'scale-105' : ''} ${isCritical ? 'text-rose-500 glow-text-rose' : `text-white ${currentGlowClass}`}`}>
{percentage}<span className="text-2xl font-light opacity-50 ml-1">{type === "health" || type === "db" ? "" : "%"}</span>
</span>
{type === "health" && <span className="text-xs font-bold text-slate-500 uppercase mt-2 tracking-widest">가동 일수</span>}
{type === "db" && <span className="text-xs font-bold text-slate-500 uppercase mt-2 tracking-widest">현재 접속</span>}
</div>
</div>
</div>
{/* 가로 막대 차트 */}
<div className="mb-8 px-2 text-left">
<div className="flex justify-between items-center text-xs font-black orbitron mb-3 tracking-widest text-slate-400">
<span>자원 로드율</span>
<span className={`text-${currentColorClass}`}>{percentage}{type === "health" || type === "db" ? "pt" : "%"}</span>
</div>
<div className="w-full h-3.5 bg-slate-900 rounded-full overflow-hidden p-1 border border-white/5 shadow-inner">
<div
className={`h-full ${isCritical ? 'bg-rose-600 shadow-[0_0_15px_rgba(255,0,85,0.6)]' : theme.bg + ' shadow-[0_0_15px_rgba(0,242,255,0.4)]'} rounded-full transition-all duration-1000 ease-out`}
style={{ width: `${Math.min(100, percentage)}%` }}
></div>
</div>
</div>
<div className="grid grid-cols-2 gap-4 mono text-left">
<div className="bg-slate-900/50 p-5 rounded-2xl border border-white/5">
<div className="text-slate-500 text-xs mb-2 font-bold uppercase tracking-widest text-left">
{type === "disk" ? "사용 용량" : type === "health" ? "프로세스" : type === "db" ? "총 연결" : "현재 부하"}
</div>
<div className={`text-2xl font-bold text-white text-left`}>
{type === "disk" ? Math.floor(data.used) : type === "health" ? data.procs : type === "db" ? data.usage : percentage}
<span className="text-xs font-normal text-slate-500 ml-1.5 uppercase">
{type === "disk" ? "GB" : type === "health" ? "개" : type === "db" ? "명" : "%"}
</span>
</div>
</div>
<div className="bg-slate-900/50 p-5 rounded-2xl border border-white/5">
<div className="text-slate-500 text-xs mb-2 font-bold uppercase tracking-widest text-left">
{type === "disk" ? "전체 용량" : "상태"}
</div>
<div className={`text-2xl font-bold text-white text-left`}>
{type === "disk" ? Math.floor(data.total) : "안정적"}
<span className="text-xs font-normal text-slate-500 ml-1.5 uppercase">
{type === "disk" ? "GB" : ""}
</span>
</div>
</div>
</div>
</div>
</div>
);
};
const App = () => {
const [lastUpdated, setLastUpdated] = useState(new Date());
const [isSimulated, setIsSimulated] = useState(false);
const [systemStats, setSystemStats] = useState({
cpu: { name: 'CPU 사용률', subtitle: '시스템 부하', usage: 0, icon: 'cpu' },
ram: { name: '메모리', subtitle: 'RAM 사용량', usage: 0, usedRaw: 0, totalRaw: 0, icon: 'layers' },
health: { name: '시스템 상태', subtitle: '가동 시간 정보', usage: 0, procs: 0, icon: 'heart-pulse' },
db: { name: '데이터베이스', subtitle: 'MariaDB 연결', usage: 0, icon: 'database' }
});
const [volumes, setVolumes] = useState([
{ id: 'vol_root', name: '루트 디스크', subtitle: '시스템 파티션', mount: '/', total: 1, used: 0, type: 'SSD_FLASH', icon: 'hard-drive' },
{ id: 'vol_data', name: '데이터 볼륨', subtitle: '데이터 파티션', mount: '/data', total: 1, used: 0, type: 'BLOCK_STR', icon: 'database' }
]);
const simulateStats = () => {
setSystemStats(prev => ({
cpu: { ...prev.cpu, usage: Math.max(5, Math.min(100, (prev.cpu.usage || 5) + (Math.random() * 10 - 5))) },
ram: { ...prev.ram, usage: Math.max(30, Math.min(95, (prev.ram.usage || 45) + (Math.random() * 2 - 1))) },
health: { ...prev.health, usage: 14, procs: 156 },
db: { ...prev.db, usage: 42 }
}));
setVolumes(vols => vols.map(v => ({
...v,
total: v.total <= 1 ? (v.id === 'vol_root' ? 49 : 196) : v.total,
used: Math.max(1, Math.min(v.total, (v.used || (v.id === 'vol_root' ? 9 : 82)) + (Math.random() * 0.1 - 0.05)))
})));
setLastUpdated(new Date());
};
const fetchStats = async () => {
if (window.location.protocol === 'blob:' || window.location.hostname === '') {
setIsSimulated(true);
simulateStats();
return;
}
try {
const response = await fetch(`${window.location.origin}${window.location.pathname}?action=get_stats`);
if (!response.ok) throw new Error("API Offline");
const data = await response.json();
setSystemStats({
cpu: { ...systemStats.cpu, usage: data.cpu },
ram: { ...systemStats.ram, usage: data.ram, usedRaw: data.mem_used, totalRaw: data.mem_total },
health: { ...systemStats.health, usage: data.uptime_days, procs: data.proc_count },
db: { ...systemStats.db, usage: data.db_conns }
});
setVolumes([
{ ...volumes[0], total: data.disk_root.total, used: data.disk_root.used },
{ ...volumes[1], total: data.disk_data.total, used: data.disk_data.used }
]);
setLastUpdated(new Date());
setIsSimulated(false);
} catch (error) {
setIsSimulated(true);
simulateStats();
}
};
useEffect(() => {
fetchStats();
const interval = setInterval(fetchStats, 5000);
return () => clearInterval(interval);
}, []);
return (
<div className="min-h-screen py-12 relative overflow-hidden flex flex-col items-center w-full">
<div className="bg-grid absolute inset-0 z-0"></div>
<div className="w-full px-[50px] relative z-10 text-left">
<header className="mb-16 flex flex-col lg:flex-row lg:items-end justify-between gap-8 text-left">
<div className="space-y-4 text-left">
<div className="flex items-center gap-3">
<div className="px-3 py-1 bg-cyan-500 text-black text-xs font-black orbitron rounded-md">올드보이-노드</div>
<span className="mono text-sm text-cyan-400 uppercase tracking-widest font-bold">시스템 연결 완료</span>
</div>
<h1 className="text-5xl font-black tracking-tighter orbitron text-white uppercase text-left">
System Diagnosis
</h1>
</div>
<div className="flex items-center gap-8 bg-slate-900/40 p-6 rounded-2xl border border-white/10 backdrop-blur-xl">
<div className="text-right">
<p className="text-xs mono text-slate-400 uppercase mb-2 tracking-widest font-bold text-right">
{isSimulated ? "시뮬레이션 모드 실행 중" : "최근 업데이트 정보"}
</p>
<p className="text-2xl font-black orbitron text-cyan-400 tracking-tighter text-right">
{lastUpdated.toLocaleTimeString('ko-KR', { hour12: false })}
</p>
</div>
</div>
</header>
{/* 모니터링 매트릭스 (3열 그리드) */}
<div className="mb-16 text-left">
<div className="flex items-center gap-4 mb-10">
<div className="h-0.5 flex-1 bg-gradient-to-r from-transparent via-cyan-500/40 to-transparent"></div>
<h2 className="orbitron text-sm font-black text-cyan-400 tracking-[0.5em] uppercase px-4">시스템 모니터링 매트릭스</h2>
<div className="h-0.5 flex-1 bg-gradient-to-r from-transparent via-cyan-500/40 to-transparent"></div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-10">
{/* 핵심 자원 단위 */}
<StatusGauge type="cpu" data={systemStats.cpu} />
<StatusGauge type="ram" data={systemStats.ram} />
{/* 스토리지 파티션 */}
<StatusGauge type="disk" data={volumes[0]} />
<StatusGauge type="disk" data={volumes[1]} />
{/* 데이터베이스 및 생존성 */}
<StatusGauge type="db" data={systemStats.db} />
<StatusGauge type="health" data={systemStats.health} />
</div>
</div>
<footer className="mt-24 pt-10 border-t border-white/10 flex flex-col md:flex-row justify-between items-center gap-6 opacity-60">
<div className="flex gap-12 orbitron text-xs tracking-[0.4em] uppercase font-bold text-left">
<span>보안 관리자 노드</span>
<span>뉴럴 로직 V4.2.0</span>
</div>
<div className="flex items-center gap-3">
<span className={`w-3 h-3 rounded-full animate-pulse ${isSimulated ? 'bg-amber-500' : 'bg-cyan-500'}`}></span>
<span className="mono text-xs font-bold uppercase tracking-widest">{isSimulated ? '시뮬레이션' : '서버 연결 중'}</span>
</div>
</footer>
</div>
</div>
);
};
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
</script>
</body>
</html>
<?php require_once '/home/www/GNU/_PAGE/tail.php';?>