POSA_LEAKSMS/php/setup_wizard.php
cw2007 e0f0f0963a feat: cafe24 관리자 UI "Slate NOC Console" 디자인시스템 통합 + 적대리뷰 18건 반영
관리자 사이트 7페이지의 파편화된 인라인 스타일(색상 리터럴 160개, 페이지별
<style>)을 단일 디자인시스템으로 통합.

신규 php/assets/app.css — "Slate NOC Console":
- CSS 변수 단일 토큰(라이트/다크 prefers-color-scheme/인쇄 강제라이트).
- 한글 시스템폰트 우선(폐쇄망 웹폰트 미로드 → 오프라인 일관 렌더), 외부 리소스 0.
- 상태 3중 인코딩(색+도형[점/좌측레일/글리프]+텍스트) — 색맹·흑백 인쇄 대비.
- WCAG AA 대비(토큰 쌍 실측), 본문/캡션 하한 .8125rem.

7페이지 리팩터(dashboard/login/monthly_report/security_evidence/setup_wizard/
setup_mfa/blocked): 인라인 <style> 제거→app.css 링크, PHP 색 삼항→클래스,
생 hex 0, PHP 로직 무변경. make_deploy_bundles.py: TEXT_EXTS += .css.

적대적 다차원 리뷰(미학·CSS아키텍처·통합일관성·a11y·slop) 확정 18건 반영:
- [HIGH] .ok 배너 셀렉터가 .badge.ok/.quick-tile.ok 와 충돌 → 정상 배지가
  사각형으로 깨지던 것 .banner-ok 네임스페이스로 수정.
- 자동새로고침 일시정지 토글(WCAG 2.2.1)+스크롤/details 복원, 차트 임계선+
  최신값 마커, 다크 헤더 슬레이트화, 그림자 절제, 정상 레일 중립화(예외만 컬러),
  지표칩 중립화+저습 .dry 분리, 진단 severity 텍스트 라벨, forced-colors 포커스,
  <main> 랜드마크, role=alert/status, ink-3 대비 상향, 인쇄 칩색 제거 등.

검증: 7페이지 php -l clean, 생 hex 0, 외부참조 0. 라이트/다크 헤드리스 렌더 확인.
2026-07-04 22:32:16 +09:00

118 lines
5.2 KiB
PHP

<?php
// =============================================================================
// setup_wizard.php - 설치/운영 점검 화면 (v2606, SHT30 온습도)
// =============================================================================
require_once __DIR__ . '/config.php';
require_once __DIR__ . '/sms_send.php';
require_once __DIR__ . '/ops_checks.php';
ini_set('session.cookie_httponly', 1);
ini_set('session.cookie_secure', 1);
ini_set('session.cookie_samesite', 'Strict');
ini_set('session.use_strict_mode', 1);
session_start();
if (empty($_SESSION['logged_in']) || $_SESSION['logged_in'] !== true) {
header('Location: login.php');
exit;
}
admin_enforce_session_timeout(); // 유휴 + 절대 수명 타임아웃(ops_checks → admin_security 경유 로드)
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
$checks = collect_ops_checks();
$sms_test_result = null;
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'send_test_sms') {
if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'] ?? '')) {
http_response_code(400);
echo 'Invalid request';
exit;
}
$message = "[온습도 모니터링 테스트]\n"
. "설치/운영 점검 문자입니다.\n"
. "시각: " . date('Y-m-d H:i:s');
$results = [];
foreach (SMS_RECIPIENTS as $receiver) {
$digits = preg_replace('/\D+/', '', $receiver);
$results[] = ['receiver' => $digits, 'result' => send_sms_cafe24($digits, $message)];
}
$sms_test_result = $results;
}
$failed_count = ops_failed_count($checks);
?>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>설치 점검 - 온습도 모니터링 <?= htmlspecialchars(APP_VERSION) ?></title>
<link rel="stylesheet" href="assets/app.css">
<style>
/* 설치 점검 전용 잔여 스타일 — 값은 app.css 토큰(var)만 사용 */
.wiz-summary { margin-bottom: var(--sp-5); }
.wiz-summary strong { font-size: 1.25rem; }
.check-list { display: grid; gap: var(--sp-3); margin-bottom: var(--sp-5); }
/* 점검 카드 = .panel + 좌측레일(도형). 상태 텍스트는 .check-ok/.check-fail(글리프+색) */
.check-list .panel { border-left: 6px solid var(--normal); }
.check-list .panel.is-fail { border-left-color: var(--leak); background: var(--leak-bg); }
.wiz-actions { display: flex; gap: var(--sp-2); flex-wrap: wrap; margin: var(--sp-5) 0; }
.wiz-actions button {
display: inline-block; background: var(--primary); color: var(--on-primary);
border: 0; border-radius: var(--r-sm); padding: 10px 16px;
font-size: .8125rem; font-weight: 700; font-family: var(--font); cursor: pointer;
}
.wiz-actions button:hover { background: var(--primary-600); }
@media print { .wiz-actions { display: none; } }
</style>
</head>
<body>
<header>
<h1>설치/운영 점검 <?= htmlspecialchars(APP_VERSION) ?></h1>
<div class="top-actions">
<a href="dashboard.php">대시보드</a>
<a href="security_evidence.php">보안 증적</a>
</div>
</header>
<main class="container">
<section class="panel wiz-summary">
<strong class="<?= $failed_count === 0 ? 'check-ok' : 'check-fail' ?>"><?= $failed_count === 0 ? '전체 점검 통과' : "확인 필요 {$failed_count}건" ?></strong>
<p class="caption">운영 반영 전 이 화면에서 설정, DB, 권한, SMS를 확인합니다.</p>
</section>
<div class="check-list">
<?php foreach ($checks as $check): ?>
<section class="panel<?= $check['ok'] ? '' : ' is-fail' ?>">
<h2><?= htmlspecialchars($check['category']) ?> · <?= htmlspecialchars($check['label']) ?> · <span class="<?= $check['ok'] ? 'check-ok' : 'check-fail' ?>"><?= $check['ok'] ? '정상' : '확인 필요' ?></span></h2>
<p class="caption"><?= htmlspecialchars($check['detail']) ?></p>
<?php if (!$check['ok'] && $check['fix'] !== ''): ?>
<p class="caption">조치: <?= htmlspecialchars($check['fix']) ?></p>
<?php endif; ?>
<?php if (($check['evidence'] ?? '') !== ''): ?>
<p class="caption">증적: <?= htmlspecialchars($check['evidence']) ?></p>
<?php endif; ?>
</section>
<?php endforeach; ?>
</div>
<form method="POST" class="wiz-actions">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
<button type="submit" name="action" value="send_test_sms">테스트 SMS 발송</button>
</form>
<?php if ($sms_test_result !== null): ?>
<section class="panel">
<h2>테스트 SMS 결과</h2>
<?php foreach ($sms_test_result as $row): ?>
<p class="caption"><?= htmlspecialchars(mask_phone($row['receiver'])) ?> · <span class="<?= $row['result']['success'] ? 'check-ok' : 'check-fail' ?>"><?= $row['result']['success'] ? '성공' : '실패' ?></span> · <?= htmlspecialchars($row['result']['message']) ?></p>
<?php endforeach; ?>
</section>
<?php endif; ?>
</main>
</body>
</html>