640 lines
25 KiB
HTML
640 lines
25 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid py-4">
|
|
|
|
{# 플래시 메시지 #}
|
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
|
{% if messages %}
|
|
<div class="position-fixed top-0 end-0 p-3" style="z-index: 1050">
|
|
{% for cat, msg in messages %}
|
|
<div class="alert alert-{{ cat }} alert-dismissible fade show shadow-lg" role="alert">
|
|
<i class="bi bi-{{ 'check-circle' if cat == 'success' else 'exclamation-triangle' }} me-2"></i>
|
|
{{ msg }}
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
{% endwith %}
|
|
|
|
{# 헤더 섹션 #}
|
|
<div class="row mb-4">
|
|
<div class="col">
|
|
<h2 class="fw-bold mb-1">
|
|
<i class="bi bi-server text-primary me-2"></i>
|
|
서버 관리 대시보드
|
|
</h2>
|
|
<p class="text-muted mb-0">IP 처리 및 파일 관리를 위한 통합 관리 도구</p>
|
|
</div>
|
|
</div>
|
|
|
|
{# 메인 작업 영역 #}
|
|
<div class="row g-4 mb-4">
|
|
{# IP 처리 카드 #}
|
|
<div class="col-lg-6">
|
|
<div class="card border shadow-sm h-100">
|
|
<div class="card-header bg-primary text-white border-0 py-2">
|
|
<h6 class="mb-0 fw-semibold">
|
|
<i class="bi bi-hdd-network me-2"></i>
|
|
IP 처리
|
|
</h6>
|
|
</div>
|
|
<div class="card-body p-4">
|
|
<form id="ipForm" method="post" action="{{ url_for('main.process_ips') }}">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
|
|
{# 스크립트 선택 #}
|
|
<div class="mb-3">
|
|
<label for="script" class="form-label">스크립트 선택</label>
|
|
<select id="script" name="script" class="form-select" required>
|
|
<option value="">스크립트를 선택하세요</option>
|
|
{% for script in scripts %}
|
|
<option value="{{ script }}">{{ script }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
|
|
{# XML 파일 선택 (조건부) #}
|
|
<div class="mb-3" id="xmlFileGroup" style="display:none;">
|
|
<label for="xmlFile" class="form-label">XML 파일 선택</label>
|
|
<select id="xmlFile" name="xmlFile" class="form-select">
|
|
<option value="">XML 파일 선택</option>
|
|
{% for xml_file in xml_files %}
|
|
<option value="{{ xml_file }}">{{ xml_file }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
|
|
{# IP 주소 입력 #}
|
|
<div class="mb-3">
|
|
<label for="ips" class="form-label">
|
|
IP 주소 (각 줄에 하나)
|
|
<span class="badge bg-secondary ms-2" id="ipLineCount">0 대설정</span>
|
|
</label>
|
|
<textarea id="ips" name="ips" rows="4" class="form-control font-monospace"
|
|
placeholder="예: 192.168.1.1 192.168.1.2 192.168.1.3" required></textarea>
|
|
</div>
|
|
|
|
<button type="submit" class="btn btn-primary w-100">
|
|
처리
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{# 공유 작업 카드 #}
|
|
<div class="col-lg-6">
|
|
<div class="card border shadow-sm h-100">
|
|
<div class="card-header bg-success text-white border-0 py-2">
|
|
<h6 class="mb-0 fw-semibold">
|
|
<i class="bi bi-share me-2"></i>
|
|
공유 작업
|
|
</h6>
|
|
</div>
|
|
<div class="card-body p-4">
|
|
<form id="sharedForm" method="post">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
|
|
<div class="mb-3">
|
|
<label for="server_list_content" class="form-label">
|
|
서버 리스트 (덮어쓰기)
|
|
<span class="badge bg-secondary ms-2" id="serverLineCount">0 대설정</span>
|
|
</label>
|
|
<textarea id="server_list_content" name="server_list_content" rows="8"
|
|
class="form-control font-monospace" style="font-size: 0.95rem;"
|
|
placeholder="서버 리스트를 입력하세요..."></textarea>
|
|
</div>
|
|
|
|
<div class="d-flex gap-2">
|
|
<button type="submit" formaction="{{ url_for('utils.update_server_list') }}"
|
|
class="btn btn-outline-primary">
|
|
MAC to Excel
|
|
</button>
|
|
<button type="submit" formaction="{{ url_for('utils.update_guid_list') }}"
|
|
class="btn btn-success">
|
|
GUID to Excel
|
|
</button>
|
|
<button type="submit" formaction="{{ url_for('utils.update_gpu_list') }}"
|
|
class="btn btn-success">
|
|
GPU to Excel
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{# 진행바 #}
|
|
<div class="row mb-4">
|
|
<div class="col">
|
|
<div class="card border-0 shadow-sm">
|
|
<div class="card-body p-3">
|
|
<div class="d-flex align-items-center mb-2">
|
|
<i class="bi bi-activity text-primary me-2"></i>
|
|
<span class="fw-semibold">처리 진행률</span>
|
|
</div>
|
|
<div class="progress" style="height: 25px;">
|
|
<div id="progressBar" class="progress-bar progress-bar-striped progress-bar-animated bg-success"
|
|
role="progressbar" style="width:0%;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
|
|
<span class="fw-semibold">0%</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{# 파일 관리 도구 #}
|
|
<div class="row mb-4">
|
|
<div class="col">
|
|
<div class="card border shadow-sm">
|
|
<div class="card-header bg-light border-0 py-2">
|
|
<h6 class="mb-0">
|
|
<i class="bi bi-tools me-2"></i>
|
|
파일 관리 도구
|
|
</h6>
|
|
</div>
|
|
|
|
<div class="card-body p-4 file-tools">
|
|
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 row-cols-xxl-5 g-3 align-items-end">
|
|
|
|
<!-- ZIP 다운로드 -->
|
|
<div class="col">
|
|
<label class="form-label text-nowrap">ZIP 다운로드</label>
|
|
<form method="post" action="{{ url_for('main.download_zip') }}">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
<div class="input-group">
|
|
<input type="text" class="form-control" name="zip_filename" placeholder="파일명" required>
|
|
<button class="btn btn-primary" type="submit">다운로드</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- 파일 백업 -->
|
|
<div class="col">
|
|
<label class="form-label text-nowrap">파일 백업</label>
|
|
<form method="post" action="{{ url_for('main.backup_files') }}">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
<div class="input-group">
|
|
<input type="text" class="form-control" name="backup_prefix" placeholder="PO로 시작">
|
|
<button class="btn btn-success" type="submit">백업</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- MAC 파일 이동 -->
|
|
<div class="col">
|
|
<label class="form-label text-nowrap">MAC 파일 이동</label>
|
|
<form id="macMoveForm" method="post" action="{{ url_for('utils.move_mac_files') }}">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
<button class="btn btn-warning w-100" type="submit">MAC Move</button>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- GUID 파일 이동 -->
|
|
<div class="col">
|
|
<label class="form-label text-nowrap">GUID 파일 이동</label>
|
|
<form id="guidMoveForm" method="post" action="{{ url_for('utils.move_guid_files') }}">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
<button class="btn btn-info w-100" type="submit">GUID Move</button>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- GPU 파일 이동 -->
|
|
<div class="col">
|
|
<label class="form-label text-nowrap">GPU 파일 이동</label>
|
|
<form id="gpuMoveForm" method="post" action="{{ url_for('utils.move_gpu_files') }}">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
<button class="btn btn-info w-100" type="submit">GPU Move</button>
|
|
</form>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{# 처리된 파일 목록 #}
|
|
<div class="row mb-4 processed-list">
|
|
<div class="col">
|
|
<div class="card border shadow-sm">
|
|
<div class="card-header bg-light border-0 py-2">
|
|
<h6 class="mb-0">
|
|
<i class="bi bi-files me-2"></i>
|
|
처리된 파일 목록
|
|
</h6>
|
|
</div>
|
|
<div class="card-body p-4">
|
|
{% if files_to_display and files_to_display|length > 0 %}
|
|
<div class="row g-3">
|
|
{% for file_info in files_to_display %}
|
|
<div class="col-auto">
|
|
<div class="file-card-compact border rounded p-2 text-center">
|
|
<a href="{{ url_for('main.download_file', filename=file_info.file) }}"
|
|
class="text-decoration-none text-dark fw-semibold d-block mb-2 text-nowrap px-2"
|
|
download title="{{ file_info.name or file_info.file }}">
|
|
{{ file_info.name or file_info.file }}
|
|
</a>
|
|
<div class="file-card-buttons">
|
|
<button type="button" class="btn btn-sm btn-outline btn-view-processed flex-fill"
|
|
data-bs-toggle="modal" data-bs-target="#fileViewModal"
|
|
data-folder="idrac_info"
|
|
data-filename="{{ file_info.file }}">
|
|
보기
|
|
</button>
|
|
<form action="{{ url_for('main.delete_file', filename=file_info.file) }}"
|
|
method="post" class="d-inline">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
<button type="submit" class="btn btn-sm btn-outline btn-delete-processed"
|
|
onclick="return confirm('삭제하시겠습니까?');">
|
|
삭제
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% else %}
|
|
<div class="text-center py-5">
|
|
<i class="bi bi-inbox fs-1 text-muted mb-3"></i>
|
|
<p class="text-muted mb-0">표시할 파일이 없습니다.</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{# 백업된 파일 목록 #}
|
|
<div class="row backup-list">
|
|
<div class="col">
|
|
<div class="card border shadow-sm">
|
|
<div class="card-header bg-light border-0 py-2">
|
|
<h6 class="mb-0">
|
|
<i class="bi bi-archive me-2"></i>
|
|
백업된 파일 목록
|
|
</h6>
|
|
</div>
|
|
<div class="card-body p-4">
|
|
{% if backup_files and backup_files|length > 0 %}
|
|
<div class="list-group">
|
|
{% for date, info in backup_files.items() %}
|
|
<div class="list-group-item border rounded mb-2 p-0 overflow-hidden">
|
|
<div class="d-flex justify-content-between align-items-center p-3 bg-light">
|
|
<div class="d-flex align-items-center">
|
|
<i class="bi bi-calendar3 text-primary me-2"></i>
|
|
<strong>{{ date }}</strong>
|
|
<span class="badge bg-primary ms-3">{{ info.count }} 파일</span>
|
|
</div>
|
|
<button class="btn btn-sm btn-outline-secondary" type="button"
|
|
data-bs-toggle="collapse" data-bs-target="#collapse-{{ loop.index }}"
|
|
aria-expanded="false">
|
|
<i class="bi bi-chevron-down"></i>
|
|
</button>
|
|
</div>
|
|
<div id="collapse-{{ loop.index }}" class="collapse">
|
|
<div class="p-3">
|
|
<div class="row g-3">
|
|
{% for file in info.files %}
|
|
<div class="col-auto">
|
|
<div class="file-card-compact border rounded p-2 text-center">
|
|
<a href="{{ url_for('main.download_backup_file', date=date, filename=file) }}"
|
|
class="text-decoration-none text-dark fw-semibold d-block mb-2 text-nowrap px-2"
|
|
download title="{{ file }}">
|
|
{{ file.rsplit('.', 1)[0] }}
|
|
</a>
|
|
<div class="file-card-single-button">
|
|
<button type="button" class="btn btn-sm btn-outline btn-view-backup w-100"
|
|
data-bs-toggle="modal" data-bs-target="#fileViewModal"
|
|
data-folder="backup"
|
|
data-date="{{ date }}"
|
|
data-filename="{{ file }}">
|
|
보기
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% else %}
|
|
<div class="text-center py-5">
|
|
<i class="bi bi-inbox fs-1 text-muted mb-3"></i>
|
|
<p class="text-muted mb-0">백업된 파일이 없습니다.</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
{# 파일 보기 모달 #}
|
|
<div class="modal fade" id="fileViewModal" tabindex="-1" aria-labelledby="fileViewModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-xl modal-dialog-scrollable">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="fileViewModalLabel">
|
|
<i class="bi bi-file-text me-2"></i>
|
|
파일 보기
|
|
</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="닫기"></button>
|
|
</div>
|
|
<div class="modal-body bg-light">
|
|
<pre id="fileViewContent" class="mb-0 p-3 bg-white border rounded font-monospace"
|
|
style="white-space:pre-wrap;word-break:break-word;max-height:70vh;">불러오는 중...</pre>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
|
<i class="bi bi-x-circle me-1"></i>닫기
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<style>
|
|
/* ===== 공통 파일 카드 컴팩트 스타일 ===== */
|
|
.file-card-compact {
|
|
transition: all 0.2s ease;
|
|
background: #fff;
|
|
min-width: 120px;
|
|
max-width: 200px;
|
|
}
|
|
|
|
.file-card-compact:hover {
|
|
box-shadow: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.1);
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
.file-card-compact a {
|
|
font-size: 0.9rem;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
max-width: 180px;
|
|
}
|
|
|
|
/* ===== 목록별 버튼 분리 규칙 ===== */
|
|
|
|
/* 처리된 파일 목록 전용 컨테이너(보기/삭제 2열) */
|
|
.processed-list .file-card-buttons {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: .5rem;
|
|
}
|
|
|
|
/* 보기(처리된) */
|
|
.processed-list .btn-view-processed {
|
|
border-color: #3b82f6;
|
|
color: #1d4ed8;
|
|
padding: .425rem .6rem;
|
|
font-size: .8125rem;
|
|
font-weight: 600;
|
|
}
|
|
.processed-list .btn-view-processed:hover {
|
|
background: rgba(59,130,246,.08);
|
|
}
|
|
|
|
/* 삭제(처리된) — 더 작게 */
|
|
.processed-list .btn-delete-processed {
|
|
border-color: #ef4444;
|
|
color: #b91c1c;
|
|
padding: .3rem .5rem;
|
|
font-size: .75rem;
|
|
font-weight: 600;
|
|
}
|
|
.processed-list .btn-delete-processed:hover {
|
|
background: rgba(239,68,68,.08);
|
|
}
|
|
|
|
/* 백업 파일 목록 전용 컨테이너(단일 버튼) */
|
|
.backup-list .file-card-single-button {
|
|
display: flex;
|
|
margin-top: .25rem;
|
|
}
|
|
|
|
/* 보기(백업) — 강조 색상 */
|
|
.backup-list .btn-view-backup {
|
|
width: 100%;
|
|
border-color: #10b981;
|
|
color: #047857;
|
|
padding: .45rem .75rem;
|
|
font-size: .8125rem;
|
|
font-weight: 700;
|
|
}
|
|
.backup-list .btn-view-backup:hover {
|
|
background: rgba(16,185,129,.08);
|
|
}
|
|
|
|
/* ===== 백업 파일 날짜 헤더 ===== */
|
|
.list-group-item .bg-light {
|
|
transition: background-color 0.2s ease;
|
|
}
|
|
.list-group-item:hover .bg-light {
|
|
background-color: #e9ecef !important;
|
|
}
|
|
|
|
/* ===== 진행바 애니메이션 ===== */
|
|
.progress {
|
|
border-radius: 10px;
|
|
overflow: hidden;
|
|
}
|
|
.progress-bar {
|
|
transition: width 0.6s ease;
|
|
}
|
|
|
|
/* ===== 반응형 텍스트 ===== */
|
|
@media (max-width: 768px) {
|
|
.card-body {
|
|
padding: 1.5rem !important;
|
|
}
|
|
}
|
|
|
|
/* ===== 스크롤바 스타일링(모달) ===== */
|
|
.modal-body pre::-webkit-scrollbar { width: 8px; height: 8px; }
|
|
.modal-body pre::-webkit-scrollbar-track { background: #f1f1f1; border-radius: 4px; }
|
|
.modal-body pre::-webkit-scrollbar-thumb { background: #888; border-radius: 4px; }
|
|
.modal-body pre::-webkit-scrollbar-thumb:hover { background: #555; }
|
|
</style>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// 스크립트 선택 시 XML 드롭다운 토글
|
|
// ─────────────────────────────────────────────────────────────
|
|
const TARGET_SCRIPT = "02-set_config.py";
|
|
const scriptSelect = document.getElementById('script');
|
|
const xmlGroup = document.getElementById('xmlFileGroup');
|
|
|
|
function toggleXml() {
|
|
if (!scriptSelect || !xmlGroup) return;
|
|
if (scriptSelect.value === TARGET_SCRIPT) {
|
|
xmlGroup.style.display = 'block';
|
|
xmlGroup.classList.add('fade-in');
|
|
} else {
|
|
xmlGroup.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
if (scriptSelect) {
|
|
toggleXml();
|
|
scriptSelect.addEventListener('change', toggleXml);
|
|
}
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// 파일 보기 모달
|
|
// ─────────────────────────────────────────────────────────────
|
|
const modalEl = document.getElementById('fileViewModal');
|
|
const titleEl = document.getElementById('fileViewModalLabel');
|
|
const contentEl = document.getElementById('fileViewContent');
|
|
|
|
if (modalEl) {
|
|
modalEl.addEventListener('show.bs.modal', async (ev) => {
|
|
const btn = ev.relatedTarget;
|
|
const folder = btn?.getAttribute('data-folder') || '';
|
|
const date = btn?.getAttribute('data-date') || '';
|
|
const filename = btn?.getAttribute('data-filename') || '';
|
|
|
|
titleEl.innerHTML = `<i class="bi bi-file-text me-2"></i>${filename || '파일'}`;
|
|
contentEl.textContent = '불러오는 중...';
|
|
|
|
const params = new URLSearchParams();
|
|
if (folder) params.set('folder', folder);
|
|
if (date) params.set('date', date);
|
|
if (filename) params.set('filename', filename);
|
|
|
|
try {
|
|
const res = await fetch(`/view_file?${params.toString()}`, { cache: 'no-store' });
|
|
if (!res.ok) throw new Error('HTTP ' + res.status);
|
|
|
|
const data = await res.json();
|
|
contentEl.textContent = data?.content ?? '(빈 파일)';
|
|
} catch (e) {
|
|
contentEl.textContent = '파일을 불러오지 못했습니다: ' + (e?.message || e);
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// 진행바 업데이트
|
|
// ─────────────────────────────────────────────────────────────
|
|
window.updateProgress = function (val) {
|
|
const bar = document.getElementById('progressBar');
|
|
if (!bar) return;
|
|
const v = Math.max(0, Math.min(100, Number(val) || 0));
|
|
bar.style.width = v + '%';
|
|
bar.setAttribute('aria-valuenow', v);
|
|
bar.innerHTML = `<span class="fw-semibold">${v}%</span>`;
|
|
};
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// CSRF 토큰
|
|
// ─────────────────────────────────────────────────────────────
|
|
const csrfToken = document.querySelector('input[name="csrf_token"]')?.value || '';
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// 공통 POST 함수
|
|
// ─────────────────────────────────────────────────────────────
|
|
async function postFormAndHandle(url) {
|
|
const res = await fetch(url, {
|
|
method: 'POST',
|
|
credentials: 'same-origin',
|
|
headers: {
|
|
'X-CSRFToken': csrfToken,
|
|
'Accept': 'application/json, text/html;q=0.9,*/*;q=0.8',
|
|
},
|
|
});
|
|
|
|
const ct = (res.headers.get('content-type') || '').toLowerCase();
|
|
|
|
if (ct.includes('application/json')) {
|
|
const data = await res.json();
|
|
if (data.success === false) {
|
|
throw new Error(data.error || ('HTTP ' + res.status));
|
|
}
|
|
return data;
|
|
}
|
|
|
|
return { success: true, html: true };
|
|
}
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// MAC 파일 이동
|
|
// ─────────────────────────────────────────────────────────────
|
|
const macForm = document.getElementById('macMoveForm');
|
|
if (macForm) {
|
|
macForm.addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
const btn = macForm.querySelector('button');
|
|
const originalHtml = btn.innerHTML;
|
|
btn.disabled = true;
|
|
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>처리 중...';
|
|
try {
|
|
await postFormAndHandle(macForm.action);
|
|
location.reload();
|
|
} catch (err) {
|
|
alert('MAC 이동 중 오류: ' + (err?.message || err));
|
|
btn.disabled = false;
|
|
btn.innerHTML = originalHtml;
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// GUID 파일 이동
|
|
// ─────────────────────────────────────────────────────────────
|
|
const guidForm = document.getElementById('guidMoveForm');
|
|
if (guidForm) {
|
|
guidForm.addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
const btn = guidForm.querySelector('button');
|
|
const originalHtml = btn.innerHTML;
|
|
btn.disabled = true;
|
|
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>처리 중...';
|
|
try {
|
|
await postFormAndHandle(guidForm.action);
|
|
location.reload();
|
|
} catch (err) {
|
|
alert('GUID 이동 중 오류: ' + (err?.message || err));
|
|
btn.disabled = false;
|
|
btn.innerHTML = originalHtml;
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// 알림 자동 닫기
|
|
// ─────────────────────────────────────────────────────────────
|
|
setTimeout(() => {
|
|
document.querySelectorAll('.alert').forEach(alert => {
|
|
const bsAlert = new bootstrap.Alert(alert);
|
|
bsAlert.close();
|
|
});
|
|
}, 5000);
|
|
|
|
});
|
|
</script>
|
|
|
|
<!-- 외부 script.js 파일 (IP 폼 처리 로직 포함) -->
|
|
<script src="{{ url_for('static', filename='script.js') }}"></script>
|
|
{% endblock %} |