G-1: 메신저 Webhook Relay + _send_to_room 실제 httpx 호출 구현 G-2: POST /api/tasks/bulk SR 대량작업 엔드포인트 (최대 100건) G-3: 라이선스 만료 알림 스케줄러 (매일 09:00 KST) G-4: 체험판 upgrade_banner 필드 + license.py 배너 로직 G-5: core/auto_rca.py + incidents/problem auto-rca 엔드포인트 G-6: core/deploy_impact.py + vibe impact-analysis 엔드포인트 G-7: core/ticket_classifier.py + SR 생성 시 AI 분류 + ai-suggestion API G-8: VulnPatchRecord 모델 + vuln_scan 패치추적 4개 엔드포인트 G-9: core/jira_sync.py + gateway Jira/Confluence 연동 엔드포인트 G-10: core/push_notify.py + routers/push.py + PushSubscription 모델 G-11: approvals 다중승인 (위임/서명/기한초과/마감연장) G-12: alembic.ini + migrations/ + cicd/migrate_to_postgres.sh 하네스: guardia-orchestrator 확장기능 Phase 반영 봇명령어: /sr /status /license /bulk 슬래시 명령어 추가 설치스크립트: setup/ (Ubuntu, CentOS, RHEL, Windows) --test 옵션 포함 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
636 lines
28 KiB
HTML
636 lines
28 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">
|
||
<!-- 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">
|
||
<div class="stats-row" id="stats-row">
|
||
<!-- filled by JS -->
|
||
</div>
|
||
<div class="dashboard-grid">
|
||
<div class="card" id="recent-list-card">
|
||
<div class="card-header">최근 SR</div>
|
||
<div class="card-body" id="recent-list"></div>
|
||
</div>
|
||
<div class="card" id="status-chart-card">
|
||
<div class="card-header">상태별 현황</div>
|
||
<div class="card-body" id="status-chart"></div>
|
||
</div>
|
||
</div>
|
||
<!-- 엔지니어 워크로드 패널 -->
|
||
<div class="card" id="workload-card" style="margin-top:18px">
|
||
<div class="card-header" style="display:flex;align-items:center;justify-content:space-between">
|
||
<span>👷 엔지니어 워크로드</span>
|
||
<button class="btn btn-secondary" style="font-size:12px;padding:4px 10px"
|
||
onclick="loadWorkload()">새로고침</button>
|
||
</div>
|
||
<div class="card-body" id="workload-body">
|
||
<div style="color:var(--text-muted);font-size:13px;padding:12px 18px">로딩 중…</div>
|
||
</div>
|
||
</div>
|
||
<!-- 7일 SR 추이 차트 (ADMIN/PM 전용 — JS 제어) -->
|
||
<div id="trend-chart-card" class="card" style="margin-top:18px;display:none">
|
||
<div class="card-header" style="display:flex;align-items:center;justify-content:space-between">
|
||
<span>📈 최근 7일 SR 추이</span>
|
||
<span id="trend-last-updated" style="font-size:11px;color:var(--text-muted)"></span>
|
||
</div>
|
||
<div class="card-body" style="padding:16px 18px">
|
||
<div id="trend-chart" class="trend-chart"></div>
|
||
<div class="trend-legend">
|
||
<div class="trend-legend-item">
|
||
<div class="trend-legend-dot" style="background:#818cf8"></div><span>생성</span>
|
||
</div>
|
||
<div class="trend-legend-item">
|
||
<div class="trend-legend-dot" style="background:#34d399"></div><span>완료</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- 7일 추이 차트 (ADMIN/PM 전용 — JS가 표시 여부 제어) -->
|
||
<div id="trend-chart-card" class="card" style="margin-top:16px;display:none">
|
||
<div class="card-header" style="display:flex;align-items:center;justify-content:space-between">
|
||
<span>📈 최근 7일 SR 추이</span>
|
||
<span id="trend-last-updated" style="font-size:11px;color:var(--text-muted)"></span>
|
||
</div>
|
||
<div class="card-body" style="padding:16px">
|
||
<div id="trend-chart" class="trend-chart"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<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>
|