245 lines
9.5 KiB
HTML
245 lines
9.5 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}시스템 로그 - Dell Server Info{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<style>
|
|
/* 전체 레이아웃 */
|
|
.editor-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: 600px;
|
|
background: #1e1e1e;
|
|
border: 1px solid #333;
|
|
border-radius: 6px;
|
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3);
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* 툴바 (헤더) */
|
|
.editor-toolbar {
|
|
background-color: #252526;
|
|
border-bottom: 1px solid #333;
|
|
padding: 0.5rem 1rem;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
flex-wrap: wrap;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
/* 에디터 본문 */
|
|
#monaco-editor-root {
|
|
flex: 1;
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
/* 로딩 인디케이터 */
|
|
.editor-loading {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
height: 100%;
|
|
color: #d4d4d4;
|
|
font-size: 1.1rem;
|
|
background: #1e1e1e;
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container py-4">
|
|
<!-- Header -->
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
<div>
|
|
<h2 class="fw-bold mb-1">
|
|
<i class="bi bi-terminal text-dark me-2"></i>시스템 로그
|
|
</h2>
|
|
<p class="text-muted mb-0 small">최근 생성된 1000줄의 시스템 로그를 실시간으로 확인합니다.</p>
|
|
</div>
|
|
<div>
|
|
<a href="{{ url_for('admin.admin_panel') }}" class="btn btn-outline-secondary">
|
|
<i class="bi bi-arrow-left me-1"></i>돌아가기
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="editor-container">
|
|
<!-- Toolbar -->
|
|
<div class="editor-toolbar">
|
|
<div class="d-flex gap-2 align-items-center flex-wrap">
|
|
<div class="input-group input-group-sm" style="width: 250px;">
|
|
<span class="input-group-text bg-dark border-secondary text-light"><i
|
|
class="bi bi-search"></i></span>
|
|
<input type="text" id="logSearch" class="form-control bg-dark border-secondary text-light"
|
|
placeholder="검색어 입력...">
|
|
</div>
|
|
|
|
<div class="btn-group btn-group-sm" role="group">
|
|
<input type="checkbox" class="btn-check" id="checkInfo" checked autocomplete="off">
|
|
<label class="btn btn-outline-secondary text-light" for="checkInfo">INFO</label>
|
|
|
|
<input type="checkbox" class="btn-check" id="checkWarn" checked autocomplete="off">
|
|
<label class="btn btn-outline-warning" for="checkWarn">WARN</label>
|
|
|
|
<input type="checkbox" class="btn-check" id="checkError" checked autocomplete="off">
|
|
<label class="btn btn-outline-danger" for="checkError">ERROR</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="d-flex gap-2">
|
|
<button class="btn btn-sm btn-outline-light" id="btnScrollBottom">
|
|
<i class="bi bi-arrow-down-circle me-1"></i>맨 아래로
|
|
</button>
|
|
<a href="{{ url_for('admin.view_logs') }}" class="btn btn-primary btn-sm">
|
|
<i class="bi bi-arrow-clockwise me-1"></i>새로고침
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Editor Area -->
|
|
<div id="monaco-editor-root">
|
|
<div class="editor-loading">
|
|
<div class="spinner-border text-light me-3" role="status"></div>
|
|
<div>로그 뷰어를 불러오는 중...</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<!-- Monaco Editor Loader -->
|
|
<script src="https://unpkg.com/monaco-editor@0.45.0/min/vs/loader.js"></script>
|
|
|
|
<script>
|
|
// 서버에서 전달된 로그 데이터 (Python list -> JS array)
|
|
// tojson safe 필터 사용
|
|
const allLogs = {{ logs | tojson | safe }};
|
|
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
if (typeof require === 'undefined') {
|
|
document.querySelector('.editor-loading').innerHTML =
|
|
'<div class="text-danger"><i class="bi bi-exclamation-triangle me-2"></i>Monaco Editor를 로드할 수 없습니다.</div>';
|
|
return;
|
|
}
|
|
|
|
require.config({ paths: { 'vs': 'https://unpkg.com/monaco-editor@0.45.0/min/vs' } });
|
|
|
|
require(['vs/editor/editor.main'], function () {
|
|
var container = document.getElementById('monaco-editor-root');
|
|
container.innerHTML = ''; // 로딩 제거
|
|
|
|
// 1. 커스텀 로그 언어 정의 (간단한 하이라이팅)
|
|
monaco.languages.register({ id: 'simpleLog' });
|
|
monaco.languages.setMonarchTokensProvider('simpleLog', {
|
|
tokenizer: {
|
|
root: [
|
|
[/\[INFO\]|INFO:/, 'info-token'],
|
|
[/\[WARNING\]|\[WARN\]|WARNING:|WARN:/, 'warn-token'],
|
|
[/\[ERROR\]|ERROR:|Traceback/, 'error-token'],
|
|
[/\[DEBUG\]|DEBUG:/, 'debug-token'],
|
|
[/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3}/, 'date-token'],
|
|
[/".*?"/, 'string']
|
|
]
|
|
}
|
|
});
|
|
|
|
// 2. 테마 정의
|
|
monaco.editor.defineTheme('logTheme', {
|
|
base: 'vs-dark',
|
|
inherit: true,
|
|
rules: [
|
|
{ token: 'info-token', foreground: '4ec9b0' },
|
|
{ token: 'warn-token', foreground: 'cca700', fontStyle: 'bold' },
|
|
{ token: 'error-token', foreground: 'f44747', fontStyle: 'bold' },
|
|
{ token: 'debug-token', foreground: '808080' },
|
|
{ token: 'date-token', foreground: '569cd6' },
|
|
],
|
|
colors: {
|
|
'editor.background': '#1e1e1e'
|
|
}
|
|
});
|
|
|
|
// 3. 에디터 생성
|
|
var editor = monaco.editor.create(container, {
|
|
value: allLogs.join('\n'),
|
|
language: 'simpleLog',
|
|
theme: 'logTheme',
|
|
readOnly: true,
|
|
automaticLayout: true,
|
|
minimap: { enabled: true },
|
|
fontSize: 13,
|
|
lineHeight: 19, // 밀도 조절
|
|
scrollBeyondLastLine: false,
|
|
lineNumbers: 'on',
|
|
wordWrap: 'on',
|
|
renderLineHighlight: 'all',
|
|
contextmenu: false,
|
|
padding: { top: 10, bottom: 10 }
|
|
});
|
|
|
|
// 4. 필터링 로직
|
|
function updateLogs() {
|
|
const query = document.getElementById('logSearch').value.toLowerCase();
|
|
const showInfo = document.getElementById('checkInfo').checked;
|
|
const showWarn = document.getElementById('checkWarn').checked;
|
|
const showError = document.getElementById('checkError').checked;
|
|
|
|
const filtered = allLogs.filter(line => {
|
|
const lower = line.toLowerCase();
|
|
|
|
// 레벨 체크 (매우 단순화)
|
|
let levelMatch = false;
|
|
|
|
const isError = lower.includes('[error]') || lower.includes('error:') || lower.includes('traceback');
|
|
const isWarn = lower.includes('[warning]') || lower.includes('[warn]') || lower.includes('warn:');
|
|
const isInfo = lower.includes('[info]') || lower.includes('info:');
|
|
|
|
if (isError) {
|
|
if (showError) levelMatch = true;
|
|
} else if (isWarn) {
|
|
if (showWarn) levelMatch = true;
|
|
} else if (isInfo) {
|
|
if (showInfo) levelMatch = true;
|
|
} else {
|
|
// 레벨 키워드가 없는 줄은 기본적으로 표시 (맥락 유지)
|
|
levelMatch = true;
|
|
}
|
|
|
|
if (!levelMatch) return false;
|
|
|
|
// 검색어 체크
|
|
if (query && !lower.includes(query)) return false;
|
|
|
|
return true;
|
|
});
|
|
|
|
// 현재 스크롤 위치 저장? 아니면 항상 아래로? -> 보통 필터링하면 아래로 가는게 편함
|
|
const currentModel = editor.getModel();
|
|
if (currentModel) {
|
|
currentModel.setValue(filtered.join('\n'));
|
|
}
|
|
// editor.revealLine(editor.getModel().getLineCount());
|
|
}
|
|
|
|
// 이벤트 연결
|
|
document.getElementById('logSearch').addEventListener('keyup', updateLogs);
|
|
document.getElementById('checkInfo').addEventListener('change', updateLogs);
|
|
document.getElementById('checkWarn').addEventListener('change', updateLogs);
|
|
document.getElementById('checkError').addEventListener('change', updateLogs);
|
|
|
|
// 맨 아래로 버튼
|
|
document.getElementById('btnScrollBottom').addEventListener('click', function () {
|
|
editor.revealLine(editor.getModel().getLineCount());
|
|
});
|
|
|
|
// 초기 스크롤 (약간의 지연 후)
|
|
setTimeout(() => {
|
|
editor.revealLine(editor.getModel().getLineCount());
|
|
}, 100);
|
|
});
|
|
});
|
|
</script>
|
|
{% endblock %} |