<?php
include_once('./_common.php');
if (!defined('_GNUBOARD_')) exit;
if (!$is_admin) exit;
/**
* UPBIT ALL DAEMON NAME LISTER (DB Integrated & Control)
* 경로: /home/www/GNU/_PAGE/monitoring/upbit/daemon_list.php
* 기능: DB 정보, 실시간 리소스, PID 출력 및 실행/중단 제어 기능 통합
*/
// ==================================================
// ✅ 실시간 데이터 갱신 처리부 (AJAX 요청 - 최상단 배치) 🏛️
// ==================================================
if (isset($_GET['mode']) && $_GET['mode'] === 'realtime_update') {
header('Content-Type: application/json; charset=utf-8');
// DB 연결 (AJAX용)
$db_data = null;
try {
require_once '/home/www/DB/db_upbit.php';
$db_data = $pdo;
} catch (Exception $e) { }
// 함수 정의 (AJAX 요청 시에도 사용 가능하도록)
if (!function_exists('get_daemon_resources_ajax')) {
function get_daemon_resources_ajax($filename) {
$pattern = "php .*{$filename}";
$cmd = "ps -eo %cpu,%mem,etimes,pid,cmd | grep " . escapeshellarg($pattern) . " | grep -v grep";
exec($cmd, $out);
if (empty($out)) return ['cpu' => 0, 'mem' => 0, 'uptime' => 0, 'pid' => '-', 'active' => false];
$cols = preg_split('/\s+/', trim($out[0]));
return [
'cpu' => (float)($cols[0] ?? 0),
'mem' => (float)($cols[1] ?? 0),
'uptime' => $cols[2] ?? 0,
'pid' => $cols[3] ?? '-',
'active' => true
];
}
}
// DB 레코드 로드
$records = [];
if ($db_data) {
try {
$stmt = $db_data->query("SELECT * FROM daemon_record");
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { $records[$row['d_id']] = $row; }
} catch (Exception $e) { }
}
$DAEMON_ROOT = '/home/www/DATA';
$daemon_updates = [];
// 전체 합계 변수
$total_daemon_cpu = 0;
$total_daemon_mem = 0;
// 카운트 변수
$active_cnt = 0;
$stopped_cnt = 0;
$total_cnt = 0;
if (is_dir($DAEMON_ROOT)) {
$dir = new RecursiveDirectoryIterator($DAEMON_ROOT, FilesystemIterator::SKIP_DOTS);
$it = new RecursiveIteratorIterator($dir);
$temp_daemons = [];
foreach ($it as $file) {
if ($file->isFile() && strpos($file->getFilename(), 'daemon_') === 0 && substr($file->getFilename(), -4) === '.php') {
$filename = $file->getFilename();
$d_id = preg_replace('/\.php$/', '', $filename);
$res = get_daemon_resources_ajax($filename); // 함수 호출
$db_row = $records[$d_id] ?? null;
$display_name = $db_row['d_name'] ?? preg_replace('/^daemon_/', '', $d_id);
$temp_daemons[] = [
'd_id' => $d_id,
'name' => $display_name,
'res' => $res,
'db_heartbeat' => $db_row['d_heartbeat'] ?? '-'
];
}
}
// 정렬
usort($temp_daemons, function($a, $b) { return strcmp($a['name'], $b['name']); });
foreach($temp_daemons as $i => $d) {
$total_cnt++;
if($d['res']['active']) $active_cnt++; else $stopped_cnt++;
// 전체 사용량 합산
$total_daemon_cpu += $d['res']['cpu'];
$total_daemon_mem += $d['res']['mem'];
// uptime 포맷팅
$seconds = $d['res']['uptime'];
$uptime_str = '0초';
if ($d['res']['active'] && $seconds >= 0) {
$days = floor($seconds / 86400);
$hours = floor(($seconds % 86400) / 3600);
$mins = floor(($seconds % 3600) / 60);
$secs = $seconds % 60;
$res_arr = [];
if ($days > 0) $res_arr[] = $days . "일";
if ($hours > 0) $res_arr[] = $hours . "시";
if ($mins > 0) $res_arr[] = $mins . "분";
$res_arr[] = $secs . "초";
$uptime_str = implode(" ", $res_arr);
}
$daemon_updates[] = [
'idx' => $i,
'd_id' => $d['d_id'],
'cpu' => $d['res']['cpu'],
'mem' => $d['res']['mem'],
'pid' => $d['res']['pid'],
'uptime' => $uptime_str,
'heartbeat' => $d['db_heartbeat'],
'active' => $d['res']['active']
];
}
}
echo json_encode([
'daemon_total_cpu' => round($total_daemon_cpu, 1),
'daemon_total_mem' => round($total_daemon_mem, 1),
'daemons' => $daemon_updates,
'summary' => [$active_cnt, $stopped_cnt, $total_cnt]
]);
exit;
}
date_default_timezone_set('Asia/Seoul');
require_once '/home/www/GNU/_PAGE/head.php';
// --- [환경 설정 및 DB 연결] ---
$DAEMON_ROOT = '/home/www/DATA';
$daemons_data = [];
$msg = '';
$db_data = null;
try {
require_once '/home/www/DB/db_upbit.php';
$db_data = $pdo;
} catch (Exception $e) { }
// ==================================================
// ✅ 데몬 제어 처리부 (POST)
// ==================================================
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['action']) && $_POST['action'] === 'start' && isset($_POST['target_path'])) {
$targetFile = $_POST['target_path'];
$fullPath = $DAEMON_ROOT . '/' . $targetFile;
if (is_file($fullPath)) {
$cmdRun = "nohup php " . escapeshellarg($fullPath) . " > /dev/null 2>&1 &";
exec($cmdRun, $o, $rc);
$msg = "실행 완료: " . basename($targetFile);
}
}
if (isset($_POST['action']) && $_POST['action'] === 'stop' && isset($_POST['target_file'])) {
$targetFileName = $_POST['target_file'];
$cmdKill = "pkill -f " . escapeshellarg($targetFileName);
exec($cmdKill, $o, $rc);
$msg = "종료 완료: " . $targetFileName;
}
}
$keyword_map = [
'upbit' => ['label' => '업비트', 'color' => '#0052ff'],
'stats' => ['label' => '통계', 'color' => '#f59e0b'],
'Ticker' => ['label' => '플랫폼', 'color' => '#3b82f6'],
'user' => ['label' => '개인자산', 'color' => '#ec4899'],
'Best' => ['label' => '베스트 코인', 'color' => '#fbbf24'],
'trading' => ['label' => '매매', 'color' => '#10b981'],
'day' => ['label' => '특정일', 'color' => '#8b5cf6'],
'multi' => ['label' => '종합', 'color' => '#06b6d4'],
'watchman' => ['label' => '감시', 'color' => '#a78bfa'],
'main' => ['label' => '기본', 'color' => '#38bdf8'],
'sub' => ['label' => '하위', 'color' => '#94a3b8'],
'direction' => ['label' => '방향', 'color' => '#f43f5e'],
'buy' => ['label' => '매수', 'color' => '#10b981'],
'sell' => ['label' => '매도', 'color' => '#ef4444'],
];
function get_all_daemon_records($pdo) {
$records = [];
if (!$pdo) return $records;
try {
$stmt = $pdo->query("SELECT * FROM daemon_record");
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { $records[$row['d_id']] = $row; }
} catch (Exception $e) { }
return $records;
}
function format_uptime($seconds) {
if (!$seconds || $seconds < 0) return '-';
$days = floor($seconds / 86400);
$hours = floor(($seconds % 86400) / 3600);
$mins = floor(($seconds % 3600) / 60);
$secs = $seconds % 60;
$res = [];
if ($days > 0) $res[] = $days . "일";
if ($hours > 0) $res[] = $hours . "시";
if ($mins > 0) $res[] = $mins . "분";
$res[] = $secs . "초";
return implode(" ", $res);
}
function get_daemon_resources($filename) {
$pattern = "php .*{$filename}";
$cmd = "ps -eo %cpu,%mem,etimes,pid,cmd | grep " . escapeshellarg($pattern) . " | grep -v grep";
exec($cmd, $out);
if (empty($out)) return ['cpu' => 0, 'mem' => 0, 'uptime' => 0, 'pid' => '-', 'active' => false];
$cols = preg_split('/\s+/', trim($out[0]));
return ['cpu' => (float)($cols[0] ?? 0), 'mem' => (float)($cols[1] ?? 0), 'uptime' => $cols[2] ?? 0, 'pid' => $cols[3] ?? '-', 'active' => true];
}
function get_daemon_tags($filename) {
global $keyword_map;
$tags = [];
foreach ($keyword_map as $key => $meta) {
if (stripos($filename, $key) !== false) $tags[] = $meta;
}
return $tags;
}
// 메인 리스트 로직
$db_records = get_all_daemon_records($db_data);
if (is_dir($DAEMON_ROOT)) {
try {
$directory = new RecursiveDirectoryIterator($DAEMON_ROOT, FilesystemIterator::SKIP_DOTS);
$iterator = new RecursiveIteratorIterator($directory);
foreach ($iterator as $file) {
if ($file->isFile()) {
$filename = $file->getFilename();
if (strpos($filename, 'daemon_') === 0 && substr($filename, -4) === '.php') {
$d_id = preg_replace('/\.php$/', '', $filename);
$db_row = $db_records[$d_id] ?? null;
$tags = get_daemon_tags($filename);
$res = get_daemon_resources($filename);
$display_name = $db_row['d_name'] ?? preg_replace('/^daemon_/', '', $d_id);
$daemons_data[] = [
'd_id' => $d_id,
'filename' => $filename,
'name' => $display_name,
'path' => $iterator->getSubPath(),
'subpath_file' => $iterator->getSubPathName(),
'tags' => $tags,
'cpu' => $res['cpu'],
'mem' => $res['mem'],
'pid' => $res['pid'],
'uptime' => format_uptime($res['uptime']),
'active' => $res['active'],
'db_status' => $db_row['d_status'] ?? ($res['active'] ? 'RUNNING' : 'STOPPED'),
'db_heartbeat' => $db_row['d_heartbeat'] ?? '-',
'db_kind' => $db_row['d_kind'] ?? '-',
'db_memo' => $db_row['d_memo'] ?? '-',
];
}
}
}
} catch (Exception $e) { }
}
usort($daemons_data, function($a, $b) { return strcmp($a['name'], $b['name']); });
$total_count = count($daemons_data);
$active_count = 0;
foreach($daemons_data as $dd) if($dd['active']) $active_count++;
$stopped_count = $total_count - $active_count;
?>
<link href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/static/pretendard.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/apexcharts"></script>
<link rel="stylesheet" type="text/css" href="./setting.css">
<div class="daemon-name-container">
<div class="header-section">
<div class="list-title">
<i class="fa-solid fa-microchip"></i>
<div>
데몬 커맨드 센터
<div style="font-size: 14px; color: #475569; margin-top: 5px; font-weight: 500;">실시간 모니터링 & 프로세스 제어 시스템</div>
</div>
</div>
<div style="display:flex; align-items:flex-end;">
<div class="total-status-box">
<div class="total-chart-row">
<span class="total-chart-label">
<span>데몬 전체 CPU 합계</span>
<span id="txtTotalCpu" style="color:#f43f5e">0%</span>
</span>
<div id="totalDaemonCpuBar" style="height: 40px;"></div>
</div>
<div class="total-chart-row">
<span class="total-chart-label">
<span>데몬 전체 메모리 합계</span>
<span id="txtTotalMem" style="color:#8b5cf6">0%</span>
</span>
<div id="totalDaemonMemBar" style="height: 40px;"></div>
</div>
</div>
<div class="summary-chart-box">
<div class="summary-chart-label">시스템 상태 요약</div>
<div id="summaryChart"></div>
</div>
</div>
</div>
<?php if ($msg): ?>
<div class="msg-notice"><i class="fa-solid fa-bolt"></i> <?=htmlspecialchars($msg)?></div>
<?php endif; ?>
<div class="daemon-grid">
<?php foreach ($daemons_data as $idx => $d): ?>
<div class="daemon-card <?= $d['active'] ? 'active' : 'stopped' ?>" id="card_<?= $d['d_id'] ?>">
<div class="card-top">
<div>
<div class="tag-area">
<?php if (empty($d['tags'])): ?>
<span class="tag-badge" style="background: #334155;">System</span>
<?php else: ?>
<?php foreach ($d['tags'] as $t): ?>
<span class="tag-badge" style="background: <?=$t['color']?>;"><?=htmlspecialchars($t['label'])?></span>
<?php endforeach; ?>
<?php endif; ?>
<span class="tag-badge" style="background: #6366f1;"><?=$d['db_kind']?></span>
</div>
<div class="daemon-display-name">
<div class="status-pulse"></div>
<?= htmlspecialchars($d['name']) ?>
</div>
<div class="daemon-file-id">UUID: <?= htmlspecialchars($d['d_id']) ?></div>
</div>
<div style="text-align: right;">
<span style="font-size: 11px; font-weight: 800; color: <?= $d['active'] ? 'var(--success)' : 'var(--danger)' ?>">
<?= $d['active'] ? '정상 작동' : '중지됨' ?>
</span>
</div>
</div>
<div class="resource-charts">
<div class="chart-item">
<div id="cpuChart_<?=$idx?>"></div>
<div class="chart-label">
CPU <span id="txtCpuVal_<?=$idx?>" style="color:#3b82f6; font-family:'JetBrains Mono';"><?= $d['cpu'] ?>%</span>
</div>
</div>
<div class="chart-item">
<div id="memChart_<?=$idx?>"></div>
<div class="chart-label">
MEM <span id="txtMemVal_<?=$idx?>" style="color:#10b981; font-family:'JetBrains Mono';"><?= $d['mem'] ?>%</span>
</div>
</div>
</div>
<div class="info-area">
<div class="info-box">
<span class="info-label">프로세스 ID</span>
<span class="info-val pid" id="pid_<?=$idx?>"># <?= $d['pid'] ?></span>
</div>
<div class="info-box">
<span class="info-label">가동 시간</span>
<span class="info-val <?= $d['active'] ? 'active' : '' ?>" id="uptime_<?=$idx?>"><?= $d['active'] ? $d['uptime'] : '0s' ?></span>
</div>
<div class="info-box" style="grid-column: span 2;">
<span class="info-label">최근 하트비트 신호</span>
<span class="info-val" style="color:var(--warning)" id="heartbeat_<?=$idx?>">
<i class="fa-solid fa-wave-square" style="font-size: 10px;"></i> <?= $d['db_heartbeat'] ?>
</span>
</div>
</div>
<div class="control-area">
<?php if (!$d['active']): ?>
<form method="post" style="width: 100%;">
<input type="hidden" name="action" value="start">
<input type="hidden" name="target_path" value="<?=htmlspecialchars($d['subpath_file'])?>">
<button type="submit" class="btn-ctl btn-start">
<i class="fa-solid fa-play"></i> 데몬 시작
</button>
</form>
<?php else: ?>
<form method="post" style="width: 100%;">
<input type="hidden" name="action" value="stop">
<input type="hidden" name="target_file" value="<?=htmlspecialchars($d['filename'])?>">
<button type="submit" class="btn-ctl btn-stop">
<i class="fa-solid fa-power-off"></i> 강제 종료
</button>
</form>
<?php endif; ?>
</div>
<div class="daemon-detail-box">
<div class="detail-item">
<span class="detail-label">파일 경로</span>
<span class="detail-val">/<?= htmlspecialchars($d['path']) ?>/<?= htmlspecialchars($d['filename']) ?></span>
</div>
<div class="detail-item" style="margin-bottom: 0;">
<span class="detail-label">시스템 메모</span>
<span class="detail-val" style="font-family: 'Pretendard';"><?= htmlspecialchars($d['db_memo']) ?></span>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<script>
// 개별 데몬 차트 (원형)
const circleOptions = (value, color) => ({
series: [value],
chart: { height: 140, type: 'radialBar', animations: { enabled: true, easing: 'easeinout', speed: 800 } },
plotOptions: {
radialBar: {
hollow: { size: '50%' },
dataLabels: { name: { show: false }, value: { offsetY: 5, fontSize: '14px', fontWeight: '900', color: '#fff', formatter: (val) => val + '%' } },
track: { background: '#1e293b' }
}
},
colors: [color], stroke: { lineCap: 'round' }, tooltip: { enabled: true, theme: 'dark' }
});
// 전체 데몬 합계 차트 (막대 Bar 100% 기준)
const totalBarOptions = (value, color, maxVal=100) => ({
series: [{ data: [value] }],
chart: { type: 'bar', height: 40, sparkline: { enabled: true }, animations: { enabled: true } },
plotOptions: {
bar: { horizontal: true, barHeight: '100%', borderRadius: 4, colors: { backgroundBarColors: ['#1e293b'] } }
},
colors: [color],
xaxis: { min: 0, max: maxVal }, // 100% 기준
tooltip: { theme: 'dark', x: { show: false }, y: { formatter: function(val) { return val + "%" } } }
});
const charts = {};
let totalCpuBar, totalMemBar, summaryChart;
document.addEventListener('DOMContentLoaded', function() {
// 1. 데몬 전체 합계 막대 차트 초기화
totalCpuBar = new ApexCharts(document.querySelector("#totalDaemonCpuBar"), totalBarOptions(0, '#f43f5e', 100));
totalCpuBar.render();
totalMemBar = new ApexCharts(document.querySelector("#totalDaemonMemBar"), totalBarOptions(0, '#8b5cf6', 100));
totalMemBar.render();
// 2. 개별 데몬 차트 초기화
<?php foreach ($daemons_data as $idx => $d): ?>
charts['cpu_<?=$idx?>'] = new ApexCharts(document.querySelector("#cpuChart_<?=$idx?>"), circleOptions(<?=$d['cpu']?>, '#3b82f6'));
charts['cpu_<?=$idx?>'].render();
charts['mem_<?=$idx?>'] = new ApexCharts(document.querySelector("#memChart_<?=$idx?>"), circleOptions(<?=$d['mem']?>, '#10b981'));
charts['mem_<?=$idx?>'].render();
<?php endforeach; ?>
// 3. 요약 차트
const summaryOptions = {
series: [{ name: '데몬 상태', data: [<?=$active_count?>, <?=$stopped_count?>, <?=$total_count?>] }],
chart: { type: 'bar', height: 120, toolbar: {show: false}, animations: {enabled: true} },
plotOptions: { bar: { borderRadius: 6, horizontal: true, distributed: true, barHeight: '60%' } },
dataLabels: { enabled: true, style: { fontWeight: '900' } },
xaxis: { categories: ['가동중', '중지됨', '전체'], labels: { show: false }, axisBorder: { show: false } },
yaxis: { labels: { style: { colors: '#64748b', fontWeight: '800' } } },
grid: { show: false }, colors: ['#10b981', '#ef4444', '#3b82f6'], legend: { show: false },
tooltip: { theme: 'dark', style: { fontSize: '12px' } }
};
summaryChart = new ApexCharts(document.querySelector("#summaryChart"), summaryOptions);
summaryChart.render();
// 4. 실시간 갱신 시작 (5초)
setInterval(updateDashboard, 5000);
updateDashboard();
});
function updateDashboard() {
fetch('?mode=realtime_update', { cache: "no-cache" })
.then(res => res.json())
.then(data => {
// 전체 데몬 합계 막대 & 텍스트 업데이트
totalCpuBar.updateSeries([{ data: [data.daemon_total_cpu] }]);
totalMemBar.updateSeries([{ data: [data.daemon_total_mem] }]);
document.getElementById('txtTotalCpu').innerText = data.daemon_total_cpu + '%';
document.getElementById('txtTotalMem').innerText = data.daemon_total_mem + '%';
// 요약 업데이트
summaryChart.updateSeries([{ data: data.summary }]);
// 개별 차트 및 텍스트 업데이트
data.daemons.forEach(d => {
const idx = d.idx;
if (charts['cpu_' + idx]) charts['cpu_' + idx].updateSeries([d.cpu]);
if (charts['mem_' + idx]) charts['mem_' + idx].updateSeries([d.mem]);
document.getElementById('txtCpuVal_' + idx).innerText = d.cpu + '%';
document.getElementById('txtMemVal_' + idx).innerText = d.mem + '%';
const pidEl = document.getElementById('pid_' + idx);
if (pidEl) pidEl.innerText = '# ' + d.pid;
const uptimeEl = document.getElementById('uptime_' + idx);
if (uptimeEl) {
uptimeEl.innerText = d.active ? d.uptime : '0초';
uptimeEl.className = 'info-val ' + (d.active ? 'active' : '');
}
const hbEl = document.getElementById('heartbeat_' + idx);
if (hbEl) hbEl.innerHTML = '<i class="fa-solid fa-wave-square" style="font-size: 10px;"></i> ' + d.heartbeat;
});
})
.catch(err => console.error(err));
}
</script>
<?php require_once '/home/www/GNU/_PAGE/tail.php'; ?>