GNU/_PAGE/maria/upbit_event.php
<?php
/* ======================================================
   마리아DB 데이터베이스 이벤트 통합 관제 (Dynamic)
   - DB: upbit_data
   - 목적: 실시간 카운트다운 및 동적 상태 모니터링
====================================================== */

error_reporting(E_ALL);
ini_set('display_errors', 1);
date_default_timezone_set('Asia/Seoul');

require_once '/home/www/DB/db_upbit.php';
$pdo = $db_upbit;
$DB_NAME = 'upbit_data';

/* 1. 전역 스케줄러 상태 및 서버 현재 시간 */
$status_stmt = $pdo->query("SHOW VARIABLES LIKE 'event_scheduler'");
$scheduler_status = $status_stmt->fetch(PDO::FETCH_ASSOC);
$is_scheduler_on = (strtoupper($scheduler_status['Value']) === 'ON');

/* 2. 이벤트 상세 정보 조회 */
$event_sql = "
    SELECT 
        EVENT_NAME, EVENT_DEFINITION, EVENT_TYPE, INTERVAL_VALUE, INTERVAL_FIELD,
        STATUS, LAST_EXECUTED, EXECUTE_AT, STARTS, DEFINER, EVENT_COMMENT
    FROM information_schema.EVENTS
    WHERE EVENT_SCHEMA = :db
    ORDER BY LAST_EXECUTED DESC, EVENT_NAME ASC
";
$stmt = $pdo->prepare($event_sql);
$stmt->execute(['db' => $DB_NAME]);
$events = $stmt->fetchAll(PDO::FETCH_ASSOC);

$summary = ['total' => count($events), 'enabled' => 0];
foreach ($events as $e) { if ($e['STATUS'] === 'ENABLED') $summary['enabled']++; }

// 헤더 부분 포함
require_once '/home/www/GNU/_PAGE/head.php';
?>

<title>MariaDB Dynamic Event Console</title>

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">

<style>
    :root {
        --bg-color: #0b0f1a;
        --card-bg: #161b2a;
        --border-color: #2d3343;
        --accent-blue: #38bdf8;
        --accent-green: #4ade80;
        --accent-rose: #f43f5e;
        --accent-yellow: #fbbf24;
        --text-main: #e2e8f0;
        --text-dim: #94a3b8;
    }

    body { background: var(--bg-color); color: var(--text-main); font-family: 'Inter', 'Malgun Gothic', sans-serif; padding-bottom: 40px; margin: 0; font-size: 16px; overflow-x: hidden; }

    /* [동적 효과] 진입 애니메이션 */
    @keyframes fadeInUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
    .entrance-ani { animation: fadeInUp 0.6s ease backwards; }

    /* [동적 효과] 펄스 애니메이션 (상태 표시) */
    @keyframes pulse-green {
        0% { box-shadow: 0 0 0 0 rgba(74, 222, 128, 0.4); }
        70% { box-shadow: 0 0 0 10px rgba(74, 222, 128, 0); }
        100% { box-shadow: 0 0 0 0 rgba(74, 222, 128, 0); }
    }

    .header-area { display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px; }
    h1 { font-size: 32px; color: var(--accent-blue); margin: 0; letter-spacing: -1px; padding-left: 20px; }

    .filter-box { background: var(--card-bg); border: 1px solid var(--border-color); padding: 12px 18px; border-radius: 6px; color: white; width: 350px; outline: none; font-size: 16px; transition: 0.3s; }
    .filter-box:focus { border-color: var(--accent-blue); box-shadow: 0 0 10px rgba(56, 189, 248, 0.2); }

    .btn-refresh { background: linear-gradient(135deg, #0ea5e9, #2563eb); color: white; border: none; padding: 12px 24px; border-radius: 6px; cursor: pointer; font-weight: bold; font-size: 15px; transition: 0.3s; }
    .btn-refresh:hover { transform: scale(1.05); filter: brightness(1.1); }

    .summary-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; margin-bottom: 30px; }
    .summary-card { background: var(--card-bg); padding: 25px; border-radius: 7px; border: 1px solid var(--border-color); transition: 0.3s; }
    .summary-card:hover { border-color: var(--accent-blue); transform: translateY(-5px); }
    .summary-label { font-size: 14px; color: var(--text-dim); margin-bottom: 10px; display: block; }
    .summary-value { font-size: 28px; font-weight: 800; color: #fff; }

    .table-wrapper { background: var(--card-bg); border-radius: 8px; border: 1px solid var(--border-color); overflow: hidden; box-shadow: 0 20px 50px rgba(0,0,0,0.5); }
    table { width: 100%; border-collapse: collapse; font-size: 15px; }
    th { background: #1e2538; color: var(--text-dim); padding: 18px 15px; text-align: left; font-size: 13px; text-transform: uppercase; }
    td { padding: 18px 15px; border-bottom: 1px solid var(--border-color); }
    tr.table-row:hover td { background: rgba(56, 189, 248, 0.05); }

    .status-badge { padding: 4px 12px; border-radius: 9px; font-size: 12px; font-weight: bold; position: relative; }
    .status-enabled { background: rgba(74, 222, 128, 0.1); color: var(--accent-green); border: 1px solid var(--accent-green); animation: pulse-green 2s infinite; }
    .status-disabled { background: rgba(244, 63, 94, 0.1); color: var(--accent-rose); border: 1px solid var(--accent-rose); }

    .btn-view { background: #2d3343; color: var(--accent-blue); border: 1px solid var(--accent-blue); padding: 6px 12px; border-radius: 5px; cursor: pointer; font-size: 14px; transition: 0.2s; }
    .btn-view:hover { background: var(--accent-blue); color: #000; }

    .font-mono { font-family: 'JetBrains Mono', monospace; font-size: 14px; }
    
    /* SQL Detail Ani */
    .sql-detail-row { display: none; background: #090c14 !important; }
    .sql-box { background: #000; border: 1px solid var(--border-color); padding: 15px; border-radius: 6px; color: #4ade80; font-family: 'JetBrains Mono', monospace; white-space: pre-wrap; font-size: 13px; line-height: 1.6; }

    /* Countdown Styling */
    .countdown-timer { font-weight: bold; color: var(--accent-yellow); }
    .expired { color: var(--accent-rose); }

    .header-area, .summary-grid, .table-wrapper { margin:30px 50px 0px 50px; }

    /* 전체 스크롤바 스타일 */
    ::-webkit-scrollbar { width: 10px; }
    ::-webkit-scrollbar-track { background: #0d1117; }
    ::-webkit-scrollbar-thumb { background: #30363d; border-radius: 6px; border: 2px solid #0d1117; }
    ::-webkit-scrollbar-thumb:hover { background: #8b949e; }    
</style>

<body>

<div class="header-area entrance-ani">
    <h1><i class="fa-solid fa-clock-rotate-left"></i> 마리아DB 이벤트 통합 관제 <span>[<?= $DB_NAME ?>]</span></h1>
    <div style="display: flex; gap: 15px;">
        <input type="text" id="eventSearch" class="filter-box" placeholder="이벤트명 또는 내용 검색..." onkeyup="filterEvents()">
        <button class="btn-refresh" onclick="location.reload()">🔄 새로고침</button>
    </div>
</div>

<div class="summary-grid">
    <div class="summary-card entrance-ani" style="animation-delay: 0.1s;">
        <span class="summary-label">전역 스케줄러 상태</span>
        <span class="summary-value counter" data-target="<?= $is_scheduler_on ? 1 : 0 ?>" style="color: <?= $is_scheduler_on ? 'var(--accent-green)' : 'var(--accent-rose)' ?>">
            <?= $is_scheduler_on ? '● RUNNING' : '■ STOPPED' ?>
        </span>
    </div>
    <div class="summary-card entrance-ani" style="animation-delay: 0.2s;">
        <span class="summary-label">전체 등록 이벤트</span>
        <span class="summary-value counter" data-target="<?= $summary['total'] ?>">0</span><span class="summary-value">건</span>
    </div>
    <div class="summary-card entrance-ani" style="animation-delay: 0.3s;">
        <span class="summary-label">활성화됨 (ENABLED)</span>
        <span class="summary-value counter" data-target="<?= $summary['enabled'] ?>" style="color: var(--accent-green);">0</span><span class="summary-value" style="color: var(--accent-green);">건</span>
    </div>
</div>

<div class="table-wrapper entrance-ani" style="animation-delay: 0.4s;">
    <table id="eventTable">
        <thead>
            <tr>
                <th>상태</th>
                <th>이벤트 명세</th>
                <th>유형 / 주기</th>
                <th style="text-align: center;">최종 실행</th>
                <th style="text-align: center;">다음 실행까지 (Live)</th>
                <th style="text-align: center;">액션</th>
            </tr>
        </thead>
        <tbody>
            <?php foreach ($events as $index => $e): 
                $is_enabled = ($e['STATUS'] === 'ENABLED');
                $interval = ($e['EVENT_TYPE'] === 'RECURRING') ? "EVERY " . $e['INTERVAL_VALUE'] . " " . $e['INTERVAL_FIELD'] : "ONE-TIME";
                
                // JS로 넘겨줄 다음 실행 타임스탬프 계산
                $next_run_ts = 0;
                if ($is_enabled) {
                    $next_run_ts = ($e['EVENT_TYPE'] === 'RECURRING') ? strtotime($e['STARTS']) : strtotime($e['EXECUTE_AT']);
                    // 이미 STARTS가 과거라면 현재 주기 계산 로직이 필요하나, 단순화를 위해 STARTS/EXECUTE_AT 기준으로 전달
                }
            ?>
            <tr class="event-row" data-name="<?= strtolower($e['EVENT_NAME']) ?>" data-desc="<?= strtolower(htmlspecialchars($e['EVENT_COMMENT'])) ?>">
                <td>
                    <span class="status-badge <?= $is_enabled ? 'status-enabled' : 'status-disabled' ?>">
                        <?= $e['STATUS'] ?>
                    </span>
                </td>
                <td>
                    <strong style="color: var(--accent-blue); font-size: 16px;"><?= $e['EVENT_NAME'] ?></strong><br>
                    <span style="font-size: 12px; color: #64748b;"><?= htmlspecialchars($e['EVENT_COMMENT'] ?: '설명 없음') ?></span>
                </td>
                <td style="font-size: 13px; color: var(--text-dim);">
                    <?= $e['EVENT_TYPE'] ?><br>
                    <span class="font-mono" style="color:#cbd5e1"><?= $interval ?></span>
                </td>
                <td style="text-align: center;" class="font-mono"><?= $e['LAST_EXECUTED'] ?: '-' ?></td>
                <td style="text-align: center;" class="font-mono">
                    <?php if ($is_enabled && $next_run_ts): ?>
                        <span class="countdown-timer" data-timestamp="<?= $next_run_ts ?>">계산 중...</span>
                    <?php else: ?>
                        <span style="color:#475569">-</span>
                    <?php endif; ?>
                </td>
                <td style="text-align: center;">
                    <button class="btn-view" onclick="toggleSql('<?= $e['EVENT_NAME'] ?>')">SQL 보기</button>
                </td>
            </tr>
            <tr id="sql-<?= $e['EVENT_NAME'] ?>" class="sql-detail-row">
                <td colspan="6">
                    <div class="sql-container">
                        <div class="sql-box"><?= htmlspecialchars($e['EVENT_DEFINITION']) ?></div>
                    </div>
                </td>
            </tr>
            <?php endforeach; ?>
        </tbody>
    </table>
</div>

<script>
/* 1. 실시간 카운트다운 로직 */
function updateCountdowns() {
    const now = Math.floor(Date.now() / 1000);
    document.querySelectorAll('.countdown-timer').forEach(el => {
        const target = parseInt(el.getAttribute('data-timestamp'));
        const diff = target - now;

        if (diff <= 0) {
            el.innerHTML = "임박/실행 중";
            el.classList.add('expired');
        } else {
            const days = Math.floor(diff / 86400);
            const hrs = Math.floor((diff % 86400) / 3600);
            const mins = Math.floor((diff % 3600) / 60);
            const secs = diff % 60;

            let str = "";
            if (days > 0) str += days + "일 ";
            str += String(hrs).padStart(2, '0') + ":" + 
                   String(mins).padStart(2, '0') + ":" + 
                   String(secs).padStart(2, '0');
            el.innerHTML = str;
        }
    });
}
setInterval(updateCountdowns, 1000);

/* 2. 숫자 카운트업 효과 */
function countUp() {
    document.querySelectorAll('.counter').forEach(el => {
        const target = parseInt(el.getAttribute('data-target'));
        if (isNaN(target)) return;
        let count = 0;
        const speed = target / 20; 
        const timer = setInterval(() => {
            count += speed;
            if (count >= target) {
                el.innerText = target;
                clearInterval(timer);
            } else {
                el.innerText = Math.floor(count);
            }
        }, 30);
    });
}

/* 3. 필터링 및 기타 기능 */
function filterEvents() {
    const input = document.getElementById('eventSearch');
    const filter = input.value.toLowerCase();
    const rows = document.querySelectorAll('#eventTable tbody tr.event-row');
    rows.forEach(row => {
        const name = row.getAttribute('data-name');
        const desc = row.getAttribute('data-desc');
        row.style.display = (name.includes(filter) || desc.includes(filter)) ? "" : "none";
    });
}

function toggleSql(eventName) {
    const target = document.getElementById('sql-' + eventName);
    if (!target) return;
    target.style.display = (target.style.display === 'table-row') ? 'none' : 'table-row';
}

document.addEventListener('DOMContentLoaded', () => {
    updateCountdowns();
    countUp();
});
</script>
</body>
</html>

<?php require_once '/home/www/GNU/_PAGE/tail.php'; ?>