[download_packages.sh — 인터넷 서버에서 실행] - OS별 패키지 다운로드 (ubuntu .deb / centos .rpm) - pip wheel 전체 다운로드 (-r requirements.txt) - Tomcat 9 tar.gz, Ollama 바이너리, Chart.js 다운로드 - Ollama 모델(llama3.1:8b, codellama:7b) 압축 패키지 - Docker 이미지 tar (docker_package.sh 연동) - setup/offline/ 에 계층적으로 저장, README.md 자동 생성 [설치 스크립트 오프라인 지원 강화] - setup_ubuntu.sh: OFFLINE_PKG_DIR 환경변수 지원 (.deb 로컬 설치) - setup_centos.sh: OFFLINE_PKG_DIR 환경변수 지원 (.rpm 로컬 설치) - 기존 TOMCAT_MIRROR=file://..., OLLAMA_INSTALL=offline 유지 [Chart.js 오프라인 폴백] - index.html: CDN 실패 시 /static/chart.umd.min.js 로컬 폴백 - db_init.py: 오프라인 패키지에서 chart.umd.min.js 자동 복사 폐쇄망 설치 절차: 인터넷 서버: bash setup/download_packages.sh all USB 복사: tar -czf offline.tar.gz setup/offline/ 폐쇄망 설치: OFFLINE_PKG_DIR=./offline/ubuntu bash setup/setup_ubuntu.sh Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
756 lines
32 KiB
HTML
756 lines
32 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ko">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>GUARDiA ITSM</title>
|
||
<link rel="stylesheet" href="/static/style.css">
|
||
<!-- Chart.js — 대시보드 차트 (CDN 실패 시 로컬 폴백) -->
|
||
<script>
|
||
(function() {
|
||
var s = document.createElement('script');
|
||
s.src = 'https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js';
|
||
s.crossOrigin = 'anonymous';
|
||
s.onerror = function() {
|
||
// 폐쇄망 폴백: /static/chart.umd.min.js (오프라인 설치 시 복사됨)
|
||
var local = document.createElement('script');
|
||
local.src = '/static/chart.umd.min.js';
|
||
local.onerror = function() { window._chartjsUnavailable = true; };
|
||
document.head.appendChild(local);
|
||
};
|
||
document.head.appendChild(s);
|
||
})();
|
||
</script>
|
||
<!-- F-4: PWA -->
|
||
<link rel="manifest" href="/static/manifest.json">
|
||
<meta name="theme-color" content="#4f8ef7">
|
||
<meta name="mobile-web-app-capable" content="yes">
|
||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||
<meta name="apple-mobile-web-app-title" content="GUARDiA">
|
||
<link rel="apple-touch-icon" href="/static/icons/icon-192.png">
|
||
</head>
|
||
<body>
|
||
<!-- FOUC 방지: body 첫 번째 자식으로 테마를 즉시 적용 -->
|
||
<script>document.body.dataset.theme = localStorage.getItem("guardia_theme") || "dark";</script>
|
||
<div id="app">
|
||
|
||
<!-- ── Sidebar ───────────────────────────────────── -->
|
||
<aside id="sidebar">
|
||
<div id="sidebar-logo">
|
||
<div class="logo-icon">G</div>
|
||
<div>
|
||
<div class="logo-title">GUARDiA ITSM</div>
|
||
<div class="logo-sub">인프라 자동화 플랫폼</div>
|
||
</div>
|
||
</div>
|
||
|
||
<nav id="sidebar-nav">
|
||
<div class="nav-item active" data-view="dashboard">
|
||
<span class="nav-icon">📊</span> 대시보드
|
||
</div>
|
||
<div class="nav-item" data-view="board">
|
||
<span class="nav-icon">🗂️</span> 칸반 보드
|
||
</div>
|
||
<div class="nav-item" data-view="list">
|
||
<span class="nav-icon">📋</span> SR 목록
|
||
</div>
|
||
<div class="nav-item" data-view="audit">
|
||
<span class="nav-icon">🔐</span> 감사 로그
|
||
</div>
|
||
<div class="nav-item" data-view="cmdb">
|
||
<span class="nav-icon">🖥️</span> CMDB
|
||
</div>
|
||
<div class="nav-item" data-view="kb">
|
||
<span class="nav-icon">📚</span> 기술 문서 KB
|
||
</div>
|
||
<div class="nav-separator"></div>
|
||
<a class="nav-item nav-link-ext" href="/incidents">
|
||
<span class="nav-icon">🚨</span> 장애 관리
|
||
</a>
|
||
<a class="nav-item nav-link-ext" href="/ssl">
|
||
<span class="nav-icon">🔑</span> SSL 관리
|
||
</a>
|
||
<a class="nav-item nav-link-ext" href="/pm">
|
||
<span class="nav-icon">🔧</span> PM 점검
|
||
</a>
|
||
<a class="nav-item nav-link-ext" href="/oncall">
|
||
<span class="nav-icon">📞</span> 온콜 관리
|
||
</a>
|
||
<a class="nav-item nav-link-ext" href="/batch">
|
||
<span class="nav-icon">⚙️</span> 배치 작업
|
||
</a>
|
||
<a class="nav-item nav-link-ext" href="/vibe">
|
||
<span class="nav-icon">🎸</span> 바이브 코딩
|
||
</a>
|
||
<a class="nav-item nav-link-ext" href="/si">
|
||
<span class="nav-icon">🏗️</span> SI 프로젝트
|
||
</a>
|
||
<a class="nav-item nav-link-ext" href="/agents">
|
||
<span class="nav-icon">🤖</span> AI 에이전트
|
||
</a>
|
||
<div class="nav-separator"></div>
|
||
<div class="nav-item" data-view="institutions">
|
||
<span class="nav-icon">🏢</span> 기관 관리
|
||
</div>
|
||
<div class="nav-item" data-view="scripts">
|
||
<span class="nav-icon">📜</span> 스크립트 관리
|
||
</div>
|
||
<div class="nav-item" data-view="timetable">
|
||
<span class="nav-icon">📅</span> 작업 타임테이블
|
||
</div>
|
||
<div class="nav-separator"></div>
|
||
<a class="nav-item nav-link-ext" href="/license" id="nav-license">
|
||
<span class="nav-icon">🔏</span> 라이선스 관리
|
||
</a>
|
||
</nav>
|
||
|
||
<div id="sidebar-footer">
|
||
<div class="status-dot online"></div>
|
||
<span>시스템 정상</span>
|
||
</div>
|
||
|
||
<!-- 사용자 정보 + 로그아웃 -->
|
||
<div id="sidebar-user">
|
||
<div id="sidebar-user-info">
|
||
<div id="user-display-name" style="font-size:13px;color:var(--text-bright);font-weight:600"></div>
|
||
<div id="user-role-badge"></div>
|
||
</div>
|
||
<button class="btn-logout" onclick="logout()" title="로그아웃">⏏</button>
|
||
</div>
|
||
<!-- 테마 전환 -->
|
||
<div id="theme-switcher">
|
||
<div class="theme-switcher-label">테마</div>
|
||
<div class="theme-swatches">
|
||
<button class="theme-swatch" data-theme="dark" onclick="applyTheme('dark')">
|
||
<span class="theme-swatch-icon">🌑</span>
|
||
<span class="theme-swatch-name">다크</span>
|
||
</button>
|
||
<button class="theme-swatch" data-theme="light" onclick="applyTheme('light')">
|
||
<span class="theme-swatch-icon">☀️</span>
|
||
<span class="theme-swatch-name">라이트</span>
|
||
</button>
|
||
<button class="theme-swatch" data-theme="midnight" onclick="applyTheme('midnight')">
|
||
<span class="theme-swatch-icon">🌌</span>
|
||
<span class="theme-swatch-name">미드나잇</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<!-- SSE 연결 상태 표시 -->
|
||
<div id="sse-status" title="실시간 연결 상태">
|
||
<span id="sse-dot" class="sse-dot grey"></span>
|
||
<span id="sse-label" style="font-size:10px;color:var(--text-muted)">연결 중…</span>
|
||
</div>
|
||
</aside>
|
||
|
||
<!-- ── Main ──────────────────────────────────────── -->
|
||
<main id="main">
|
||
|
||
<!-- Top bar -->
|
||
<header id="topbar">
|
||
<h1 id="page-title">대시보드</h1>
|
||
<div id="topbar-actions">
|
||
<div id="topbar-refresh-indicator" class="refresh-indicator hidden">
|
||
<span class="refresh-dot"></span><span style="font-size:11px">데이터 갱신됨</span>
|
||
</div>
|
||
<button class="btn btn-primary" id="btn-new-sr">+ 새 SR</button>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- Views -->
|
||
<div id="view-dashboard" class="view active">
|
||
<!-- KPI 카드 행 -->
|
||
<div class="stats-row" id="stats-row"><!-- filled by JS --></div>
|
||
|
||
<!-- ── 대시보드 탭 ── -->
|
||
<div class="dash-tab-nav" id="dash-tab-nav">
|
||
<button class="dash-tab active" data-tab="ops" onclick="switchDashTab('ops')">📊 운영 현황</button>
|
||
<button class="dash-tab" data-tab="infra" onclick="switchDashTab('infra')">🖥️ 인프라</button>
|
||
<button class="dash-tab" data-tab="security" onclick="switchDashTab('security')">🔒 보안</button>
|
||
<button class="dash-tab" data-tab="ai" onclick="switchDashTab('ai')">🤖 AI 인사이트</button>
|
||
</div>
|
||
|
||
<!-- ═══ 탭 1: 운영 현황 ═══ -->
|
||
<div class="dash-tab-panel active" id="dash-panel-ops">
|
||
<div class="chart-grid-2">
|
||
<!-- SR 상태 도넛 -->
|
||
<div class="card chart-card">
|
||
<div class="card-header">SR 상태 분포</div>
|
||
<div class="card-body chart-body">
|
||
<canvas id="chart-sr-status"></canvas>
|
||
</div>
|
||
</div>
|
||
<!-- 우선순위 바 -->
|
||
<div class="card chart-card">
|
||
<div class="card-header">우선순위별 SR</div>
|
||
<div class="card-body chart-body">
|
||
<canvas id="chart-sr-priority"></canvas>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- 7일 SR 추이 -->
|
||
<div class="card" style="margin-top:14px">
|
||
<div class="card-header" style="display:flex;align-items:center;justify-content:space-between">
|
||
<span>📈 최근 7일 SR 추이</span>
|
||
<button class="btn btn-secondary" style="font-size:11px;padding:3px 8px"
|
||
onclick="loadDashboardCharts()">새로고침</button>
|
||
</div>
|
||
<div class="card-body" style="padding:14px 16px">
|
||
<canvas id="chart-sr-trend" style="max-height:200px"></canvas>
|
||
</div>
|
||
</div>
|
||
<!-- 엔지니어 워크로드 + 최근 SR -->
|
||
<div class="chart-grid-2" style="margin-top:14px">
|
||
<div class="card chart-card">
|
||
<div class="card-header">👷 엔지니어 워크로드</div>
|
||
<div class="card-body" id="workload-body">
|
||
<div style="color:var(--text-muted);font-size:13px;padding:8px">로딩 중…</div>
|
||
</div>
|
||
</div>
|
||
<div class="card chart-card">
|
||
<div class="card-header">최근 SR</div>
|
||
<div class="card-body" id="recent-list" style="overflow-y:auto;max-height:260px"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ═══ 탭 2: 인프라 ═══ -->
|
||
<div class="dash-tab-panel" id="dash-panel-infra">
|
||
<div class="chart-grid-2">
|
||
<!-- 기관별 SR 수평 바 -->
|
||
<div class="card chart-card">
|
||
<div class="card-header">기관별 SR 현황 (Top 10)</div>
|
||
<div class="card-body chart-body">
|
||
<canvas id="chart-inst-sr"></canvas>
|
||
</div>
|
||
</div>
|
||
<!-- 서버 OS 분포 -->
|
||
<div class="card chart-card">
|
||
<div class="card-header">서버 OS 분포</div>
|
||
<div class="card-body chart-body">
|
||
<canvas id="chart-os-dist"></canvas>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- CMDB 서버 상태 그리드 -->
|
||
<div class="card" style="margin-top:14px">
|
||
<div class="card-header">서버 헬스 현황</div>
|
||
<div class="card-body" id="server-health-grid"
|
||
style="display:flex;flex-wrap:wrap;gap:8px;padding:14px 16px">
|
||
<div style="color:var(--text-muted);font-size:13px">로딩 중…</div>
|
||
</div>
|
||
</div>
|
||
<!-- SSL 만료 현황 -->
|
||
<div class="card" style="margin-top:14px">
|
||
<div class="card-header">🔐 SSL 인증서 만료 현황</div>
|
||
<div class="card-body chart-body" style="max-height:220px">
|
||
<canvas id="chart-ssl-expiry"></canvas>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ═══ 탭 3: 보안 ═══ -->
|
||
<div class="dash-tab-panel" id="dash-panel-security">
|
||
<div class="chart-grid-2">
|
||
<!-- 취약점 심각도 바 -->
|
||
<div class="card chart-card">
|
||
<div class="card-header">취약점 심각도 분포</div>
|
||
<div class="card-body chart-body">
|
||
<canvas id="chart-vuln-severity"></canvas>
|
||
</div>
|
||
</div>
|
||
<!-- 패치율 도넛 -->
|
||
<div class="card chart-card">
|
||
<div class="card-header">패치 적용 현황</div>
|
||
<div class="card-body chart-body">
|
||
<canvas id="chart-patch-rate"></canvas>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- PAM 세션 + 감사 로그 -->
|
||
<div class="chart-grid-2" style="margin-top:14px">
|
||
<div class="card chart-card">
|
||
<div class="card-header">🔑 PAM 세션 현황</div>
|
||
<div class="card-body" id="pam-status-body"
|
||
style="padding:14px 16px;font-size:13px">로딩 중…</div>
|
||
</div>
|
||
<div class="card chart-card">
|
||
<div class="card-header">📋 최근 감사 로그</div>
|
||
<div class="card-body" id="audit-body"
|
||
style="overflow-y:auto;max-height:260px"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ═══ 탭 4: AI 인사이트 ═══ -->
|
||
<div class="dash-tab-panel" id="dash-panel-ai">
|
||
<div class="chart-grid-2">
|
||
<!-- AI 티켓 분류 분포 -->
|
||
<div class="card chart-card">
|
||
<div class="card-header">🤖 AI 티켓 분류 현황</div>
|
||
<div class="card-body chart-body">
|
||
<canvas id="chart-ai-category"></canvas>
|
||
</div>
|
||
</div>
|
||
<!-- 이상 탐지 추이 -->
|
||
<div class="card chart-card">
|
||
<div class="card-header">⚡ 이상 탐지 추이 (7일)</div>
|
||
<div class="card-body chart-body">
|
||
<canvas id="chart-anomaly-trend"></canvas>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- 예측 유지보수 + 학습 루프 -->
|
||
<div class="chart-grid-2" style="margin-top:14px">
|
||
<div class="card chart-card">
|
||
<div class="card-header">🔧 예측 유지보수 위험 서버</div>
|
||
<div class="card-body" id="predictive-body"
|
||
style="padding:14px 16px;font-size:13px">로딩 중…</div>
|
||
</div>
|
||
<div class="card chart-card">
|
||
<div class="card-header">📚 학습 루프 현황</div>
|
||
<div class="card-body" id="learning-body"
|
||
style="padding:14px 16px;font-size:13px">로딩 중…</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div><!-- /view-dashboard -->
|
||
|
||
<div id="view-board" class="view">
|
||
<div id="kanban-board">
|
||
<!-- columns rendered by JS -->
|
||
</div>
|
||
</div>
|
||
|
||
<div id="view-list" class="view">
|
||
<div class="list-toolbar">
|
||
<input type="text" id="search-input" placeholder="SR 제목 검색…" class="search-box">
|
||
<select id="filter-status" class="filter-select">
|
||
<option value="">전체 상태</option>
|
||
<option value="RECEIVED">접수</option>
|
||
<option value="PARSED">파싱 완료</option>
|
||
<option value="PENDING_APPROVAL">승인 대기</option>
|
||
<option value="APPROVED">승인됨</option>
|
||
<option value="IN_PROGRESS">진행 중</option>
|
||
<option value="PENDING_PM_VALIDATION">PM 검증 대기</option>
|
||
<option value="COMPLETED">완료</option>
|
||
<option value="FAILED_ROLLBACK">롤백 실패</option>
|
||
<option value="REJECTED">반려</option>
|
||
</select>
|
||
<select id="filter-type" class="filter-select">
|
||
<option value="">전체 유형</option>
|
||
<option value="DEPLOY">배포</option>
|
||
<option value="RESTART">재기동</option>
|
||
<option value="LOG">로그</option>
|
||
<option value="INQUIRY">문의</option>
|
||
<option value="OTHER">기타</option>
|
||
</select>
|
||
</div>
|
||
<table class="sr-table" id="sr-table">
|
||
<thead>
|
||
<tr>
|
||
<th>SR ID</th><th>유형</th><th>제목</th>
|
||
<th>상태</th><th>우선순위</th><th>담당자</th><th>요청자</th><th>생성일</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="sr-tbody"></tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div id="view-audit" class="view">
|
||
<div class="audit-header-row">
|
||
<span class="audit-title">감사 로그 (SHA-256 해시 체인)</span>
|
||
<button class="btn btn-secondary" id="btn-verify">체인 무결성 검증</button>
|
||
<span id="verify-result"></span>
|
||
</div>
|
||
<table class="sr-table" id="audit-table">
|
||
<thead>
|
||
<tr>
|
||
<th>#</th><th>SR</th><th>행위자</th><th>액션</th><th>내용</th>
|
||
<th>해시 (앞 12자)</th><th>시각</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="audit-tbody"></tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div id="view-cmdb" class="view">
|
||
<div class="cmdb-grid" id="cmdb-grid"><!-- filled by JS --></div>
|
||
</div>
|
||
|
||
<!-- KB 뷰 -->
|
||
<div id="view-kb" class="view">
|
||
<div class="list-toolbar" style="gap:10px">
|
||
<input type="text" id="kb-search-input" placeholder="증상 또는 키워드 검색… (예: OOM, SSL, connection pool)"
|
||
class="search-box" style="flex:1">
|
||
<select id="kb-category-filter" class="filter-select">
|
||
<option value="">전체 카테고리</option>
|
||
<option value="JAVA">JAVA</option>
|
||
<option value="MIDDLEWARE">MIDDLEWARE</option>
|
||
<option value="DB">DB</option>
|
||
<option value="WEB">WEB</option>
|
||
<option value="OS">OS</option>
|
||
<option value="SECURITY">SECURITY</option>
|
||
</select>
|
||
<button class="btn btn-primary" onclick="searchKB()">검색</button>
|
||
</div>
|
||
<div id="kb-results"><!-- filled by JS --></div>
|
||
</div>
|
||
|
||
<!-- 기관 관리 뷰 -->
|
||
<div id="view-institutions" class="view">
|
||
<div class="list-toolbar">
|
||
<input type="text" id="inst-search" placeholder="기관명 검색…" class="search-box" oninput="filterInstitutions()">
|
||
<select id="inst-region-filter" class="filter-select" onchange="filterInstitutions()">
|
||
<option value="">전체 지역</option>
|
||
<option value="서울">서울</option>
|
||
<option value="세종">세종</option>
|
||
<option value="부산">부산</option>
|
||
<option value="대전">대전</option>
|
||
<option value="기타">기타</option>
|
||
</select>
|
||
<button class="btn btn-primary" id="btn-new-inst" onclick="openInstModal()">+ 기관 등록</button>
|
||
</div>
|
||
<table class="sr-table" id="inst-table">
|
||
<thead>
|
||
<tr>
|
||
<th>기관코드</th><th>기관명</th><th>지역</th><th>계약 만료</th>
|
||
<th>SLA</th><th>서버</th><th>담당자</th><th>상태</th><th>관리</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="inst-tbody"></tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<!-- 스크립트 관리 뷰 -->
|
||
<div id="view-scripts" class="view">
|
||
<div class="list-toolbar">
|
||
<input type="text" id="script-search" placeholder="스크립트명·설명·태그 검색…" class="search-box" oninput="filterScripts()">
|
||
<select id="script-category-filter" class="filter-select" onchange="filterScripts()">
|
||
<option value="">전체 카테고리</option>
|
||
<option value="SM">SM (일상운영)</option>
|
||
<option value="REGULAR">정기점검</option>
|
||
<option value="ADHOC">수시점검</option>
|
||
<option value="DEPLOY">배포</option>
|
||
<option value="SECURITY">보안</option>
|
||
<option value="MONITORING">모니터링</option>
|
||
</select>
|
||
<select id="script-layer-filter" class="filter-select" onchange="filterScripts()">
|
||
<option value="">전체 레이어</option>
|
||
<option value="WEB">WEB</option>
|
||
<option value="WAS">WAS</option>
|
||
<option value="DB">DB</option>
|
||
<option value="ALL">공통</option>
|
||
</select>
|
||
<button class="btn btn-primary" onclick="openScriptModal()">+ 스크립트 등록</button>
|
||
</div>
|
||
<div id="script-list-body"></div>
|
||
</div>
|
||
|
||
<!-- 작업 타임테이블 뷰 -->
|
||
<div id="view-timetable" class="view">
|
||
<div class="list-toolbar">
|
||
<input type="text" id="tt-search" placeholder="제목·내용 검색…" class="search-box" oninput="filterTimetable()">
|
||
<select id="tt-type-filter" class="filter-select" onchange="filterTimetable()">
|
||
<option value="">전체 유형</option>
|
||
<option value="REGULAR_CHECK">정기점검</option>
|
||
<option value="PM">예방정비</option>
|
||
<option value="SR">SR작업</option>
|
||
<option value="ADHOC">수시점검</option>
|
||
<option value="DEPLOY">배포</option>
|
||
<option value="EMERGENCY">긴급대응</option>
|
||
</select>
|
||
<select id="tt-status-filter" class="filter-select" onchange="filterTimetable()">
|
||
<option value="">전체 결과</option>
|
||
<option value="PENDING">예정</option>
|
||
<option value="SUCCESS">완료</option>
|
||
<option value="FAILED">실패</option>
|
||
<option value="PARTIAL">부분완료</option>
|
||
<option value="CANCELLED">취소</option>
|
||
</select>
|
||
<button class="btn btn-primary" onclick="openTimetableModal()">+ 작업 등록</button>
|
||
<button class="btn btn-secondary" onclick="exportTimetableExcel()" title="Excel 다운로드">📥 Excel</button>
|
||
</div>
|
||
<table class="sr-table" id="tt-table">
|
||
<thead>
|
||
<tr>
|
||
<th>유형</th><th>제목</th><th>기관</th><th>처리예정</th>
|
||
<th>완료</th><th>결과상태</th><th>담당자</th><th>SR</th><th>관리</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="tt-tbody"></tbody>
|
||
</table>
|
||
</div>
|
||
|
||
</main>
|
||
</div>
|
||
|
||
<!-- ── SR Detail Modal ────────────────────────────── -->
|
||
<div id="modal-overlay" class="hidden">
|
||
<div id="modal">
|
||
<button class="modal-close" id="modal-close-btn">×</button>
|
||
<div id="modal-body"><!-- filled by JS --></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── New SR Modal ───────────────────────────────── -->
|
||
<div id="new-sr-overlay" class="hidden">
|
||
<div id="new-sr-modal">
|
||
<button class="modal-close" id="new-sr-close">×</button>
|
||
<h2>새 SR 생성</h2>
|
||
<form id="new-sr-form">
|
||
<label>제목 <input type="text" name="title" required></label>
|
||
<label>설명 <textarea name="description" rows="3"></textarea></label>
|
||
<div class="form-row">
|
||
<label>유형
|
||
<select name="sr_type">
|
||
<option value="OTHER">기타</option>
|
||
<option value="DEPLOY">배포</option>
|
||
<option value="RESTART">재기동</option>
|
||
<option value="LOG">로그</option>
|
||
<option value="INQUIRY">문의</option>
|
||
</select>
|
||
</label>
|
||
<label>우선순위
|
||
<select name="priority">
|
||
<option value="MEDIUM">보통</option>
|
||
<option value="CRITICAL">긴급</option>
|
||
<option value="HIGH">높음</option>
|
||
<option value="LOW">낮음</option>
|
||
</select>
|
||
</label>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>요청자 <input type="text" name="requested_by" required></label>
|
||
<label>기관코드 <input type="text" name="inst_code" placeholder="MOF / MOIS / MSS"></label>
|
||
</div>
|
||
<label>대상 서버 <input type="text" name="target_server" placeholder="예: MOF-WAS-01"></label>
|
||
<div class="file-upload-area">
|
||
<label class="file-upload-label">
|
||
<span class="file-upload-icon">📎</span>
|
||
<span id="file-upload-text">첨부파일 선택 (선택사항, 최대 10개 · 파일당 20 MB)</span>
|
||
<input type="file" id="sr-file-input" multiple accept=".pdf,.txt,.log,.csv,.png,.jpg,.jpeg,.xlsx,.docx,.zip,.sh,.sql,.json,.yaml,.md" style="display:none">
|
||
</label>
|
||
<div id="file-preview-list"></div>
|
||
</div>
|
||
<button type="submit" class="btn btn-primary" style="width:100%;margin-top:8px">생성</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── AI 명령 채팅 패널 ──────────────────────────── -->
|
||
<div id="ai-chat-panel" class="hidden">
|
||
<div id="ai-chat-header">
|
||
<span>🤖 GUARDiA AI 어시스턴트</span>
|
||
<button id="ai-chat-close" title="닫기">×</button>
|
||
</div>
|
||
<div id="ai-chat-messages"></div>
|
||
<div id="ai-chat-suggestions"></div>
|
||
<div id="ai-chat-input-row">
|
||
<input type="text" id="ai-chat-input"
|
||
placeholder="자연어로 명령하세요… 예: 승인 대기 SR 목록 보여줘">
|
||
<button id="ai-chat-send">전송</button>
|
||
</div>
|
||
</div>
|
||
<button id="ai-chat-fab" title="AI 명령 어시스턴트">🤖</button>
|
||
|
||
<!-- ── 기관 등록/수정 모달 ────────────────────────── -->
|
||
<div id="inst-modal-overlay" class="hidden">
|
||
<div id="inst-modal" class="modal-wide">
|
||
<button class="modal-close" onclick="closeInstModal()">×</button>
|
||
<h2 id="inst-modal-title">기관 등록</h2>
|
||
<form id="inst-form" onsubmit="submitInstForm(event)">
|
||
<div class="form-row">
|
||
<label>기관코드 <input type="text" name="inst_code" placeholder="MOF" required></label>
|
||
<label>기관명 <input type="text" name="inst_name" required></label>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>기관유형 <input type="text" name="org_type" placeholder="중앙행정기관"></label>
|
||
<label>지역
|
||
<select name="region">
|
||
<option value="">선택</option>
|
||
<option value="서울">서울</option>
|
||
<option value="세종">세종</option>
|
||
<option value="부산">부산</option>
|
||
<option value="대전">대전</option>
|
||
<option value="기타">기타</option>
|
||
</select>
|
||
</label>
|
||
</div>
|
||
<label>주소 <input type="text" name="address"></label>
|
||
<div class="form-row">
|
||
<label>대표 전화 <input type="text" name="phone" placeholder="044-000-0000"></label>
|
||
<label>SLA (시간) <input type="number" name="sla_hours" value="4" min="1" max="72"></label>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>계약 시작 <input type="date" name="contract_start"></label>
|
||
<label>계약 만료 <input type="date" name="contract_end"></label>
|
||
</div>
|
||
<label>담당 PM <input type="text" name="contact_pm"></label>
|
||
<label>메모 <textarea name="note" rows="2"></textarea></label>
|
||
<button type="submit" class="btn btn-primary" style="width:100%;margin-top:8px">저장</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── 담당자 등록/수정 모달 ──────────────────────── -->
|
||
<div id="contact-modal-overlay" class="hidden">
|
||
<div id="contact-modal">
|
||
<button class="modal-close" onclick="closeContactModal()">×</button>
|
||
<h2 id="contact-modal-title">담당자 등록</h2>
|
||
<form id="contact-form" onsubmit="submitContactForm(event)">
|
||
<div class="form-row">
|
||
<label>이름 <input type="text" name="contact_name" required></label>
|
||
<label>역할
|
||
<select name="role">
|
||
<option value="MANAGER">담당자(관리)</option>
|
||
<option value="ENGINEER">엔지니어</option>
|
||
<option value="PM">PM</option>
|
||
<option value="SECURITY">보안담당</option>
|
||
<option value="HELPDESK">헬프데스크</option>
|
||
</select>
|
||
</label>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>부서 <input type="text" name="dept"></label>
|
||
<label>직책 <input type="text" name="position"></label>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>이메일 <input type="email" name="email" placeholder="user@agency.go.kr"></label>
|
||
<label>직통 전화 <input type="text" name="phone"></label>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>휴대폰 <input type="text" name="mobile" placeholder="010-0000-0000"></label>
|
||
<label style="align-items:center;gap:8px">
|
||
주 담당자
|
||
<input type="checkbox" name="is_primary" style="width:auto;margin-top:4px">
|
||
</label>
|
||
</div>
|
||
<label>메모 <textarea name="note" rows="2"></textarea></label>
|
||
<button type="submit" class="btn btn-primary" style="width:100%;margin-top:8px">저장</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── 스크립트 등록/수정 모달 ───────────────────── -->
|
||
<div id="script-modal-overlay" class="hidden">
|
||
<div id="script-modal" class="modal-wide modal-xlarge">
|
||
<button class="modal-close" onclick="closeScriptModal()">×</button>
|
||
<h2 id="script-modal-title">스크립트 등록</h2>
|
||
<form id="script-form" onsubmit="submitScriptForm(event)">
|
||
<label>스크립트명 <input type="text" name="script_name" required></label>
|
||
<div class="form-row">
|
||
<label>카테고리
|
||
<select name="category">
|
||
<option value="SM">SM (일상운영)</option>
|
||
<option value="REGULAR">정기점검</option>
|
||
<option value="ADHOC">수시점검</option>
|
||
<option value="DEPLOY">배포</option>
|
||
<option value="SECURITY">보안</option>
|
||
<option value="MONITORING">모니터링</option>
|
||
</select>
|
||
</label>
|
||
<label>세부분류 <input type="text" name="sub_category" placeholder="DISK_CHECK"></label>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>대상 레이어
|
||
<select name="target_layer">
|
||
<option value="ALL">전체 (ALL)</option>
|
||
<option value="WEB">WEB</option>
|
||
<option value="WAS">WAS</option>
|
||
<option value="DB">DB</option>
|
||
<option value="ESB">ESB</option>
|
||
</select>
|
||
</label>
|
||
<label>OS 유형
|
||
<select name="os_type">
|
||
<option value="LINUX">LINUX</option>
|
||
<option value="WINDOWS">WINDOWS</option>
|
||
<option value="ALL">ALL</option>
|
||
</select>
|
||
</label>
|
||
</div>
|
||
<label>설명 <textarea name="description" rows="2" required></textarea></label>
|
||
<label>스크립트 내용
|
||
<textarea name="script_body" rows="10" class="code-textarea" placeholder="#!/bin/bash # 스크립트 내용 입력" required></textarea>
|
||
</label>
|
||
<label>태그 (쉼표 구분) <input type="text" name="tags" placeholder="health,check,linux"></label>
|
||
<div class="form-row" style="align-items:center">
|
||
<label style="flex-direction:row;align-items:center;gap:8px">
|
||
<input type="checkbox" name="is_dangerous" style="width:auto"> 위험 명령 포함
|
||
</label>
|
||
<label style="flex-direction:row;align-items:center;gap:8px">
|
||
<input type="checkbox" name="requires_approval" style="width:auto"> 실행 전 승인 필요
|
||
</label>
|
||
</div>
|
||
<button type="submit" class="btn btn-primary" style="width:100%;margin-top:8px">저장</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── 타임테이블 등록/수정 모달 ─────────────────── -->
|
||
<div id="tt-modal-overlay" class="hidden">
|
||
<div id="tt-modal" class="modal-wide">
|
||
<button class="modal-close" onclick="closeTimetableModal()">×</button>
|
||
<h2 id="tt-modal-title">작업 등록</h2>
|
||
<form id="tt-form" onsubmit="submitTimetableForm(event)">
|
||
<div class="form-row">
|
||
<label>작업유형
|
||
<select name="work_type" required>
|
||
<option value="REGULAR_CHECK">정기점검</option>
|
||
<option value="PM">예방정비</option>
|
||
<option value="SR">SR작업</option>
|
||
<option value="ADHOC">수시점검</option>
|
||
<option value="DEPLOY">배포</option>
|
||
<option value="EMERGENCY">긴급대응</option>
|
||
</select>
|
||
</label>
|
||
<label>기관
|
||
<select name="inst_id" id="tt-inst-select">
|
||
<option value="">선택 안 함</option>
|
||
</select>
|
||
</label>
|
||
</div>
|
||
<label>제목 <input type="text" name="title" required></label>
|
||
<div class="form-row">
|
||
<label>처리예정 <input type="datetime-local" name="scheduled_at" required></label>
|
||
<label>SR번호 <input type="text" name="sr_id" placeholder="SR-YYYYMMDD-XXXXXX"></label>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>시작일시 <input type="datetime-local" name="started_at"></label>
|
||
<label>완료일시 <input type="datetime-local" name="completed_at"></label>
|
||
</div>
|
||
<label>처리내용 <textarea name="content" rows="3" required></textarea></label>
|
||
<label>명령어/쉘 스크립트
|
||
<select name="script_id" id="tt-script-select" onchange="fillScriptBody(this)">
|
||
<option value="">직접 입력</option>
|
||
</select>
|
||
</label>
|
||
<label>명령어 직접 입력 <textarea name="command_or_shell" rows="3" class="code-textarea"></textarea></label>
|
||
<label>처리결과 <textarea name="result" rows="3"></textarea></label>
|
||
<div class="form-row">
|
||
<label>결과상태
|
||
<select name="result_status">
|
||
<option value="PENDING">예정</option>
|
||
<option value="SUCCESS">완료</option>
|
||
<option value="FAILED">실패</option>
|
||
<option value="PARTIAL">부분완료</option>
|
||
<option value="CANCELLED">취소</option>
|
||
</select>
|
||
</label>
|
||
<label>담당자 <input type="text" name="assignee" placeholder="사번 또는 이름"></label>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>검토자 <input type="text" name="reviewer" placeholder="PM 사번 또는 이름"></label>
|
||
<label>비고 <input type="text" name="note"></label>
|
||
</div>
|
||
<button type="submit" class="btn btn-primary" style="width:100%;margin-top:8px">저장</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<script src="/static/app.js"></script>
|
||
</body>
|
||
</html>
|