<?php
if (!defined('_GNUBOARD_')) exit;
// =================================================================
// 1. [구멍 뚫기] 오빠의 UPBIT DB (upbit_data) 연결
// =================================================================
include_once('/home/www/DB/db_upbit.php');
if (!isset($db_upbit)) {
die("오빠! db_upbit.php 파일은 불렀는데 연결이 안 됐어! 파일 다시 확인해줘!");
}
$db_upbit = $pdo; // ★ UPBIT 전용 DB 핸들러
include_once("{$board_skin_path}/db_update.php");
// [필수] CSS 파일 로드
add_stylesheet('<link rel="stylesheet" href="'.$board_skin_url.'/style.css?v='.time().'">', 0);
add_stylesheet('<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">', 0);
add_stylesheet('<link rel="stylesheet" href="'.$board_skin_url.'/style.write.css?v='.time().'">', 0);
// [변수 초기화]
$w = $_REQUEST['w'] ?? '';
$wr_id = $_REQUEST['wr_id'] ?? '';
// [값 할당] 규칙: wr_subject(데몬명), sel_event(테이블명)
$cur_daemon = $write['wr_subject'] ?? '';
$cur_table = $write['sel_event'] ?? '';
$cur_ver = $write['x2_ver'] ?? '';
// 버전 자동 할당
if (!$cur_ver && $w == '') {
$row_ver = sql_fetch(" select x2_ver from {$write_table} order by wr_id desc limit 1 ");
$cur_ver = $row_ver['x2_ver'] ?? '';
}
// 외부 DB(upbit)에서 테이블 목록 가져오기 (sel_event용)
$db_tables = [];
if (isset($db_upbit) && $db_upbit) {
try {
$res_tables = $db_upbit->query("SHOW TABLES");
if ($res_tables) {
while ($row_table = $res_tables->fetch(PDO::FETCH_NUM)) {
$db_tables[] = $row_table[0];
}
}
} catch (Exception $e) { }
}
// [추가 기능] 이미 등록된 데몬명 확인 (중복 제외 로직)
$used_daemons = [];
// 현재 테이블에서 사용 중인 wr_subject 목록을 가져온다.
$sql_used = " select distinct wr_subject from {$write_table} ";
$res_used = sql_query($sql_used);
while ($row_u = sql_fetch_array($res_used)) {
// 수정 모드($w='u')일 때, 내가 지금 수정하고 있는 이 글의 데몬명($cur_daemon)은 제외 목록에 넣으면 안 됨.
if ($w == 'u' && $cur_daemon == $row_u['wr_subject']) {
continue;
}
$used_daemons[] = $row_u['wr_subject'];
}
// 데몬 파일 목록 스캔 (wr_subject용)
$daemon_list = [];
$daemon_path = '/home/www/DATA/UPBIT/daemon';
if (is_dir($daemon_path) && is_readable($daemon_path)) {
try {
$di = new RecursiveDirectoryIterator($daemon_path, RecursiveDirectoryIterator::SKIP_DOTS);
$it = new RecursiveIteratorIterator($di);
foreach ($it as $file) {
if ($file->isFile() && $file->getExtension() == 'php') {
$fn = $file->getFilename();
if (strpos($fn, 'daemon_') === 0) {
// ★ 핵심 수정: 이미 DB에 등록된 데몬명($used_daemons)에 포함되어 있다면 리스트에 넣지 않는다.
if (!in_array($fn, $used_daemons)) {
$daemon_list[] = $fn;
}
}
}
}
$daemon_list = array_unique($daemon_list);
sort($daemon_list);
} catch (Exception $e) { }
}
$row_status = sql_fetch(" SELECT @@event_scheduler as status ");
$scheduler_state = isset($row_status['status']) ? strtoupper($row_status['status']) : 'UNKNOWN';
$state_color = ($scheduler_state == 'ON') ? '#00d4ff' : '#ff2d55';
$has_data = ($w == 'u' && (trim($write['wr_content'] ?? '') || trim($write['x2_content'] ?? '')));
?>
<div id="WRITE_WRAP">
<canvas id="space-canvas"></canvas>
<article id="WRITE">
<form name="fwrite" id="fwrite" action="<?php echo $action_url; ?>" onsubmit="return fwrite_submit(this);" method="post" enctype="multipart/form-data" autocomplete="off">
<input type="hidden" name="w" value="<?php echo $w; ?>">
<input type="hidden" name="bo_table" value="<?php echo $bo_table; ?>">
<input type="hidden" name="wr_id" value="<?php echo $wr_id; ?>">
<input type="hidden" name="sca" value="<?php echo $sca; ?>">
<div class="write-header">
<div class="write-header__title-group">
<h2 class="write-header__title">
<i class="fa-solid fa-microchip"></i> DAEMON CONTROL
</h2>
<span class="write-header__status-badge" style="color:<?php echo $state_color; ?>; border-color:<?php echo $state_color; ?>; box-shadow:0 0 10px <?php echo $state_color; ?>40;">
SCHEDULER : <?php echo $scheduler_state; ?>
</span>
</div>
<div class="write-header__btn-group">
<button type="button" class="btn-toggle-clean btn-submit--small" style="background:#0f172a; color:#fff;" onclick="history.back();">CANCEL</button>
<button type="submit" class="btn-submit btn-submit--small" id="btn_submit_top">SAVE</button>
</div>
</div>
<table class="write-table">
<tr>
<td class="td-label"><i class="fa-solid fa-sliders"></i> 설정 (Config)</td>
<td class="td-content">
<div class="form-row">
<div class="form-group">
<span class="form-label-mini" style="color:#00d4ff;">관리여부</span>
<label class="toggle-btn">
<input type="checkbox" name="x2_run" value="1" <?php echo (isset($write['x2_run']) && $write['x2_run'] || $w=='') ? 'checked' : ''; ?>>
<span class="slider"></span>
</label>
</div>
<div class="form-divider"></div>
<?php if ($is_category) { ?>
<span class="form-label-mini">거래소.</span>
<select name="ca_name" required class="select-base">
<option value="">거래소 선택</option>
<?php echo $category_option; ?>
</select>
<?php } ?>
<span class="form-label-mini">/ 종류.</span>
<select name="x2_ca2" class="select-base">
<option value="">종류 선택</option>
<?php
$opts = explode('|', $board['bo_1']);
foreach($opts as $o) {
$o = trim($o); if(!$o) continue;
echo '<option value="'.$o.'" '.($write['x2_ca2'] == $o ? 'selected' : '').'>'.$o.'</option>';
}
?>
</select>
<span class="form-label-mini">/ 형태.</span>
<select name="x2_ca3" class="select-base">
<option value="">형태 선택</option>
<?php
$opts = explode('|', $board['bo_2']);
foreach($opts as $o) {
$o = trim($o); if(!$o) continue;
echo '<option value="'.$o.'" '.($write['x2_ca3'] == $o ? 'selected' : '').'>'.$o.'</option>';
}
?>
</select>
<div class="form-group ml-auto">
<span class="form-label-mini">/ VER.</span>
<input type="text" name="x2_ver" value="<?php echo $cur_ver; ?>" class="input-ver">
</div>
</div>
</td>
</tr>
<tr>
<td class="td-label"><i class="fa-solid fa-robot"></i> 대상 (Target)</td>
<td class="td-content">
<div class="form-row">
<div class="form-group">
<span class="form-label-mini">데몬명.</span>
<span class="target-label target-label--blue">DAEMON</span>
<select name="wr_subject" id="wr_subject" class="select-target" required>
<option value="">:: 데몬 선택 ::</option>
<?php
foreach($daemon_list as $d) {
$selected = ($cur_daemon == $d) ? 'selected' : '';
echo '<option value="'.$d.'" '.$selected.'>'.$d.'</option>';
}
?>
</select>
</div>
<div class="form-divider"></div>
<div class="form-group">
<span class="form-label-mini">테이블명.</span>
<span class="target-label target-label--orange">TABLE</span>
<select name="sel_event" id="sel_event" class="select-target">
<option value="">:: 테이블 선택 ::</option>
<?php
foreach($db_tables as $t) {
$selected = ($cur_table == $t) ? 'selected' : '';
echo '<option value="'.$t.'" '.$selected.'>'.$t.'</option>';
}
?>
</select>
</div>
<div class="form-divider"></div>
<div class="form-group">
<span class="form-label-mini">식별명.</span>
<input type="text" name="wr_subject_kor" value="<?php echo $write['wr_subject_kor'] ?? ''; ?>" class="input-kor" placeholder="한글 식별명">
</div>
</div>
</td>
</tr>
<tr>
<td class="td-label"><i class="fa-regular fa-note-sticky"></i> 메모 (Memo)</td>
<td class="td-content">
<input type="text" name="wr_1" value="<?php echo $write['wr_1'] ?? ''; ?>" class="input-full" placeholder="간단한 메모 입력">
</td>
</tr>
<tr>
<td class="td-label"><i class="fa-solid fa-tags"></i> 태그 (Tag)</td>
<td class="td-content">
<input type="text" name="x2_tag" value="<?php echo $write['x2_tag'] ?? ''; ?>" class="input-full" placeholder="#TAG1 #TAG2">
</td>
</tr>
</table>
<div class="dynamic-result-area">
<div class="res-item"><span>[STATUS]</span> <b id="res_state">-</b></div>
<div class="res-item"><span>[DAEMON]</span> <b id="res_daemon">-</b></div>
<div class="res-item"><span>[TABLE]</span> <b id="res_table">-</b></div>
<div class="res-item"><span>[EXCHANGE]</span> <b id="res_exchange">-</b></div>
<div class="ml-auto res-item"><span>[UPDATED]</span> <b id="res_time" style="color:#fff;">-</b></div>
</div>
<button type="button" class="btn-toggle-clean" onclick="toggleExtra()">
상세 파라미터 / 콘텐츠 관리 <i class="fa-solid fa-chevron-down" id="toggle_icon"></i>
</button>
<div id="extra_area" style="display:<?php echo $has_data ? 'block' : 'none'; ?>; border-top:1px solid var(--border-color);">
<table class="write-table">
<tr>
<td class="td-label">상세 설명</td>
<td class="td-content--editor">
<?php echo $editor_html; ?>
</td>
</tr>
<tr>
<td class="td-label">추가 설정 (JSON)</td>
<td class="td-content">
<textarea name="x2_content" class="textarea-json"><?php echo get_text($write['x2_content'] ?? '', 0); ?></textarea>
</td>
</tr>
<tr>
<td class="td-label">로그 기록 (Log)</td>
<td class="td-content">
<textarea name="x2_content_2" class="textarea-json"><?php echo get_text($write['x2_content_2'] ?? '', 0); ?></textarea>
</td>
</tr>
</table>
</div>
<div class="write-footer">
<button type="submit" class="btn-submit" id="btn_submit_bottom">SAVE CONFIG</button>
</div>
</form>
</article>
</div>
<script>
$(function() {
function updateUI() {
const isRun = $('input[name="x2_run"]').is(':checked');
const daemonName = $('#wr_subject').val();
const tableName = $('#sel_event').val();
const exchange = $('select[name="ca_name"]').val();
$('#res_state').text(isRun ? 'RUNNING' : 'STOPPED').css('color', isRun ? '#00d4ff' : '#ff2d55');
$('#res_daemon').text(daemonName || 'NONE').css('color', daemonName ? '#00d4ff' : '#64748b');
$('#res_table').text(tableName || 'NONE').css('color', tableName ? '#f59e0b' : '#64748b');
$('#res_exchange').text(exchange || '-');
$('#res_time').text(new Date().toLocaleTimeString());
}
$('#wr_subject, #sel_event, input[name="x2_run"], select[name="ca_name"]').on('change', updateUI);
updateUI();
const canvas = document.getElementById('space-canvas');
if(canvas) {
const ctx = canvas.getContext('2d');
let stars = [];
let animationFrameId;
function init() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
stars = Array.from({length:100}, () => ({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
s: Math.random() * 1.5 + 0.5,
sp: Math.random() * 0.3 + 0.1
}));
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "rgba(255, 255, 255, 0.4)";
stars.forEach(s => {
ctx.beginPath();
ctx.arc(s.x, s.y, s.s, 0, Math.PI*2);
ctx.fill();
s.y += s.sp;
if(s.y > canvas.height) s.y = 0;
});
animationFrameId = requestAnimationFrame(draw);
}
init();
draw();
window.addEventListener('resize', () => {
if(animationFrameId) cancelAnimationFrame(animationFrameId);
init();
draw();
});
}
});
function toggleExtra() {
const area = $('#extra_area');
const icon = $('#toggle_icon');
area.slideToggle(function(){
if(area.is(':visible')) icon.removeClass('fa-chevron-down').addClass('fa-chevron-up');
else icon.removeClass('fa-chevron-up').addClass('fa-chevron-down');
});
}
function fwrite_submit(f) {
<?php echo isset($editor_js) ? $editor_js : ''; ?>
if(!f.wr_subject.value) {
alert("데몬명(wr_subject)을 선택해야 합니다.");
return false;
}
const btnTop = document.getElementById("btn_submit_top");
const btnBot = document.getElementById("btn_submit_bottom");
if(btnTop) btnTop.disabled = true;
if(btnBot) btnBot.disabled = true;
return true;
}
</script>