관리자 사이트 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. 라이트/다크 헤드리스 렌더 확인.
118 lines
5.2 KiB
PHP
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>
|