<?php
include_once('./_common.php');
if (!defined('_GNUBOARD_')) exit;
if (!$is_admin) exit;
/**
* UPBIT DAEMON MONITORING PAGE
* 경로: /home/www/UPBIT/monitoring/daemon.php
* 대상: /home/www/UPBIT/daemon/daemon_*.php
*/
date_default_timezone_set('Asia/Seoul');
// [설정] 감시 대상 디렉토리 정의
$DAEMON_DIR = '/home/www/DATA/UPBIT/daemon/target_day';
$TARGET_DIR = '/home/www/DATA/UPBIT/daemon/target'; // 주인님이 추가하신 보석 같은 경로
$msg = '';
// ==========================
// DB 연결
// ==========================
require '/home/www/DB/db_upbit.php';
$pdo = $db_upbit;
// ==========================
// d_name 가져오기 함수
// ==========================
function get_daemon_dname($file) {
global $pdo;
// daemon_abc_123.php → d_id = daemon_abc_123 (확장자 제거)
$d_id = preg_replace('/\.php$/', '', $file);
$sql = "SELECT d_name FROM daemon_record WHERE d_id = :id LIMIT 1";
$stmt = $pdo->prepare($sql);
$stmt->execute([':id' => $d_id]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
return $row['d_name'] ?? null; // NULL이면 그대로
}
// ==========================
// d_kind 가져오기 함수 (FORM 컬럼)
// ==========================
function get_daemon_kind($file) {
global $pdo;
$d_id = preg_replace('/\.php$/', '', $file);
$sql = "SELECT d_kind FROM daemon_record WHERE d_id = :id LIMIT 1";
$stmt = $pdo->prepare($sql);
$stmt->execute([':id' => $d_id]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
return $row['d_kind'] ?? '';
}
function is_valid_daemon_file($file) {
return (bool)preg_match('/^daemon_[a-zA-Z0-9_\-]+\.php$/', $file);
}
function whoami_web() {
$out = [];
@exec('whoami', $out);
return $out[0] ?? 'unknown';
}
function find_proc($file) {
// user,pid,cmd 한 줄만 잡기
$pattern = "php .*{$file}";
$cmd = "ps -eo user,pid,cmd | grep " . escapeshellarg($pattern) . " | grep -v grep";
$out = [];
exec($cmd, $out);
if (empty($out)) return null;
// 첫 줄 파싱
$cols = preg_split('/\s+/', trim($out[0]), 3);
return [
'user' => $cols[0] ?? '-',
'pid' => $cols[1] ?? '-',
'cmd' => $cols[2] ?? '',
'raw' => $out[0],
];
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['start'])) {
$file = basename($_POST['start']);
// 두 디렉토리 중 파일이 존재하는 정확한 경로 확인
$path = (file_exists($DAEMON_DIR . '/' . $file)) ? $DAEMON_DIR . '/' . $file : $TARGET_DIR . '/' . $file;
if (is_valid_daemon_file($file) && is_file($path)) {
$proc = find_proc($file);
if (!$proc) {
$cmdRun = "nohup php " . escapeshellarg($path) . " > /dev/null 2>&1 &";
exec($cmdRun, $o, $rc);
$msg = "STARTED : {$file} (rc={$rc})";
} else {
$msg = "ALREADY RUNNING : {$file} (user={$proc['user']} pid={$proc['pid']})";
}
} else {
$msg = "INVALID FILE : {$file}";
}
}
if (isset($_POST['stop'])) {
$file = basename($_POST['stop']);
if (is_valid_daemon_file($file)) {
$proc = find_proc($file);
// pkill 시도
$cmdKill = "pkill -f " . escapeshellarg($file);
exec($cmdKill, $o, $rc);
$after = find_proc($file);
$webUser = whoami_web();
$beforeInfo = $proc ? "before(user={$proc['user']} pid={$proc['pid']})" : "before(none)";
$afterInfo = $after ? "after(user={$after['user']} pid={$after['pid']})" : "after(none)";
$msg = "STOP TRY : {$file} | webUser={$webUser} | {$beforeInfo} | rc={$rc} | {$afterInfo}";
} else {
$msg = "INVALID FILE : {$file}";
}
}
// [추가] d_name 업데이트 로직
if (isset($_POST['update_name_btn'])) {
$file = basename($_POST['target_file']);
$new_name = trim($_POST['new_d_name']);
// ID 추출
$d_id = preg_replace('/\.php$/', '', $file);
// DB UPDATE
$sql = "UPDATE daemon_record SET d_name = :nm WHERE d_id = :id";
$stmt = $pdo->prepare($sql);
$stmt->execute([':nm' => $new_name, ':id' => $d_id]);
$msg = "NAME UPDATED : {$file} -> " . htmlspecialchars($new_name);
}
}
// ==========================
// 파일 스캔 로직 (지정된 모든 디렉토리)
// ==========================
$daemons = [];
$dirs_to_scan = [$DAEMON_DIR, $TARGET_DIR];
foreach ($dirs_to_scan as $dir) {
if (is_dir($dir)) {
foreach (scandir($dir) as $f) {
if ($f === '.' || $f === '..') continue;
if (!is_valid_daemon_file($f)) continue;
$daemons[] = $f;
}
}
}
sort($daemons);
// ==========================
// [추가 로직] 디렉토리 감시 상태 스캔
// ==========================
$dir_watch_status = [];
$BASE_DIR_TO_SCAN = '/home/www/DATA/UPBIT/daemon'; // 상위 디렉토리 스캔
if (is_dir($BASE_DIR_TO_SCAN)) {
$dir_iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($BASE_DIR_TO_SCAN, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($dir_iterator as $info) {
if ($info->isDir()) {
$sub_dir = $dir_iterator->getSubPathName();
$full_sub_dir = $BASE_DIR_TO_SCAN . '/' . $sub_dir;
$has_off_file = false;
if (is_dir($full_sub_dir)) {
foreach (scandir($full_sub_dir) as $f) {
if (strpos($f, 'OFF_') === 0) {
$has_off_file = true;
break;
}
}
}
$dir_watch_status[] = ['path' => $sub_dir, 'status' => $has_off_file ? 'OFF' : 'ON'];
}
}
usort($dir_watch_status, function($a, $b) { return strcmp($a['path'], $b['path']); });
}
function get_status($file) {
$proc = find_proc($file);
if ($proc) {
return [
'status' => 'RUNNING',
'pid' => $proc['pid'],
'user' => $proc['user'],
'color' => '#2ecc71'
];
}
return [
'status' => 'STOPPED',
'pid' => '-',
'user' => '-',
'color' => '#ff4757'
];
}
// ==========================
// d_name 파싱: 첫/가운데/마지막 토큰
// ==========================
function parse_dname_tokens($d_name) {
if ($d_name === null || $d_name === '') {
return [null, null, null];
}
$parts = explode('_', $d_name);
if (count($parts) === 1) {
return [null, $parts[0], null];
} elseif (count($parts) === 2) {
return [$parts[0], null, $parts[1]];
} else {
$first = $parts[0];
$last = $parts[count($parts) - 1];
$middle = implode('_', array_slice($parts, 1, -1));
return [$first, $middle, $last];
}
}
// 헤더 부분 포함
require_once '/home/www/GNU/_PAGE/head.php';
?>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="utf-8">
<title>UPBIT DAEMON MONITORING</title>
</head>
<body>
<link rel="stylesheet" type="text/css" href="./daemon.css">
<div class="page-loader" id="pageLoader"><div class="spinner"></div></div>
<div class="header-area">
<h2><i class="fa-solid fa-explosion"></i> DAEMON MONITORING - TRADING</h2>
<div class="search-container">
<input type="text" id="daemonSearch" class="search-input" placeholder="데몬 파일명 또는 KIND 검색...">
</div>
</div>
<?php if ($msg): ?>
<div class="notice"><?= htmlspecialchars($msg) ?></div>
<?php endif; ?>
<!-- [메인 감시 데몬 테이블] -->
<table>
<thead>
<tr>
<th>KIND</th>
<th>FORM</th>
<th>DAEMON FILE</th>
<th>STATUS</th>
<th>PID</th>
<th>PROC USER</th>
<th>START</th>
<th>STOP</th>
<th>NAME EDIT</th>
</tr>
</thead>
<tbody id="daemonTableBody">
<?php if (empty($daemons)): ?>
<tr><td colspan="9" style="text-align:center; padding:50px; color:var(--text-dim);">NO DAEMON FOUND</td></tr>
<?php else: ?>
<?php foreach ($daemons as $d):
$st = get_status($d);
$raw_name = get_daemon_dname($d);
list($name_first, $name_center, $name_last) = parse_dname_tokens($raw_name);
$form_val = get_daemon_kind($d);
?>
<tr>
<td>
<?php if ($name_first): ?>
<span class="badge-kind badge-kind-first"><?= htmlspecialchars($name_first) ?></span>
<?php endif; ?>
<?php if ($name_center): ?>
<span class="kind-center-text"><?= htmlspecialchars($name_center) ?></span>
<?php endif; ?>
<?php if ($name_last): ?>
<span class="badge-kind badge-kind-last"><?= htmlspecialchars($name_last) ?></span>
<?php endif; ?>
</td>
<td style="color:var(--text-dim)"><?= htmlspecialchars($form_val) ?></td>
<td style="font-family: monospace; color: #60a5fa;" class="file-name"><?= htmlspecialchars($d) ?></td>
<td>
<?php if ($st['status'] === 'RUNNING'): ?>
<span class="status-running"><?= $st['status'] ?></span>
<?php else: ?>
<span style="color:var(--danger); font-weight:700;"><?= $st['status'] ?></span>
<?php endif; ?>
</td>
<td><code><?= htmlspecialchars($st['pid']) ?></code></td>
<td><?= htmlspecialchars($st['user']) ?></td>
<td>
<form method="post">
<input type="hidden" name="start" value="<?= htmlspecialchars($d) ?>">
<button class="start">START</button>
</form>
</td>
<td>
<?php if ($st['status'] === 'RUNNING'): ?>
<form method="post">
<input type="hidden" name="stop" value="<?= htmlspecialchars($d) ?>">
<button class="stop">STOP</button>
</form>
<?php endif; ?>
</td>
<td>
<form method="post">
<input type="hidden" name="target_file" value="<?= htmlspecialchars($d) ?>">
<input type="text" name="new_d_name" value="<?= htmlspecialchars($raw_name) ?>" class="input-edit" placeholder="d_name">
<button name="update_name_btn" class="btn-save">SAVE</button>
</form>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
<!-- [추가] 디렉토리 감시 가동 상태 (가로 박스형 그리드 리스트) -->
<div class="header-area" style="margin-top: 60px;">
<h2>DIRECTORY WATCH STATUS</h2>
</div>
<div class="dir-status-list">
<?php if (empty($dir_watch_status)): ?>
<div class="dir-status-box" style="grid-column: 1/-1; justify-content: center; color: var(--text-dim);">
스캔된 디렉토리가 없습니다.
</div>
<?php else: ?>
<?php foreach ($dir_watch_status as $dir): ?>
<div class="dir-status-box">
<div class="dir-info-part">
<span class="dir-path-text">📁 <?= htmlspecialchars($dir['path']) ?></span>
<span class="dir-reason-text">
<?= $dir['status'] === 'OFF' ? '🚫 Found OFF_ file' : '✅ Active monitoring' ?>
</span>
</div>
<div style="margin-top: 10px; display: flex; justify-content: flex-end;">
<?php if ($dir['status'] === 'OFF'): ?>
<span class="dir-status-off">WATCHER OFF</span>
<?php else: ?>
<span class="dir-status-on">WATCHER ON</span>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
<!-- [페이지 이동 버튼] -->
<div class="nav-action-area">
<a href="<?php echo G5_URL; ?>/_PAGE/monitoring/upbit/OFF_daemon/OFF_daemon.php" class="btn-nav">
<i class="fa-solid fa-toggle-on"></i> 감시 온/오프 설정 페이지로 이동
</a>
</div>
<script>
window.addEventListener('load', function() {
const loader = document.getElementById('pageLoader');
if (loader) loader.classList.add('hidden');
document.body.classList.add('loaded');
});
// 실시간 검색 기능
document.getElementById('daemonSearch').addEventListener('keyup', function() {
const filter = this.value.toLowerCase();
const rows = document.querySelectorAll('#daemonTableBody tr');
rows.forEach(row => {
// 행 내부의 전체 텍스트 내용을 합쳐서 검색 (KIND, 파일명 등 모두 포함)
const text = row.textContent.toLowerCase();
if (text.includes(filter)) {
row.style.display = '';
} else {
row.style.display = 'none';
}
});
});
</script>
</body>
</html>
<?php require_once '/home/www/GNU/_PAGE/tail.php'; ?>