guardia-itsm/static/index.html
DESKTOP-TKLFCPRython 64c27c3509 feat(itsm): G-1~G-12 확장 기능 + 하네스/봇/설치스크립트 구현
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>
2026-05-29 18:18:52 +09:00

636 lines
28 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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&#10;# 스크립트 내용 입력" 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>