feat: создать современный интерфейс с боковым меню
- Полностью переработан дизайн интерфейса - Добавлено боковое меню слева с контролами - Область логов перемещена вправо - Добавлены иконки Font Awesome - Современный CSS с переменными и анимациями - Адаптивный дизайн для мобильных устройств - Улучшенная навигация по контейнерам - Современные кнопки и элементы управления - Поддержка темной и светлой темы - Индикаторы статуса контейнеров Автор: Сергей Антропов Сайт: https://devops.org.ru
This commit is contained in:
parent
a1572d470c
commit
43f19d32e1
@ -3,33 +3,400 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||||
<title>LogBoard+ — compose logs</title>
|
<title>LogBoard+ — Modern Log Viewer</title>
|
||||||
<meta name="x-token" content="__TOKEN__"/>
|
<meta name="x-token" content="__TOKEN__"/>
|
||||||
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
/* THEME TOKENS */
|
/* THEME TOKENS */
|
||||||
:root{
|
:root{
|
||||||
--bg:#0e0f13; --panel:#151821; --muted:#8b94a8; --accent:#7aa2f7; --ok:#9ece6a; --warn:#e0af68; --err:#f7768e; --fg:#e5e9f0;
|
--bg:#0e0f13; --panel:#151821; --muted:#8b94a8; --accent:#7aa2f7; --ok:#9ece6a; --warn:#e0af68; --err:#f7768e; --fg:#e5e9f0;
|
||||||
--border:#2a2f3a; --tab:#1b2030; --tab-active:#22283a; --chip:#2b3142; --link:#9ab8ff;
|
--border:#2a2f3a; --tab:#1b2030; --tab-active:#22283a; --chip:#2b3142; --link:#9ab8ff;
|
||||||
|
--sidebar-width: 280px; --header-height: 60px;
|
||||||
}
|
}
|
||||||
:root[data-theme="light"]{
|
:root[data-theme="light"]{
|
||||||
--bg:#f7f9fc; --panel:#ffffff; --muted:#667085; --accent:#3b82f6; --ok:#15803d; --warn:#b45309; --err:#b91c1c; --fg:#0f172a;
|
--bg:#f7f9fc; --panel:#ffffff; --muted:#667085; --accent:#3b82f6; --ok:#15803d; --warn:#b45309; --err:#b91c1c; --fg:#0f172a;
|
||||||
--border:#e5e7eb; --tab:#eef2ff; --tab-active:#dbeafe; --chip:#eef2f7; --link:#1d4ed8;
|
--border:#e5e7eb; --tab:#eef2ff; --tab-active:#dbeafe; --chip:#eef2f7; --link:#1d4ed8;
|
||||||
}
|
}
|
||||||
|
|
||||||
*{box-sizing:border-box} html,body{height:100%}
|
*{box-sizing:border-box}
|
||||||
body{margin:0;background:var(--bg);color:var(--fg);font:13px/1.45 ui-monospace,Menlo,Consolas,monospace}
|
html,body{height:100%; margin: 0; padding: 0;}
|
||||||
|
body{background:var(--bg);color:var(--fg);font:13px/1.45 ui-monospace,Menlo,Consolas,monospace; overflow: hidden;}
|
||||||
a{color:var(--link)}
|
a{color:var(--link)}
|
||||||
header{display:flex;align-items:center;gap:10px;padding:10px 14px;background:var(--panel);border-bottom:1px solid var(--border);position:sticky;top:0;z-index:10}
|
|
||||||
header h1{font-size:14px;margin:0;color:var(--muted)}
|
/* Modern Layout */
|
||||||
.controls{margin-left:auto;display:flex;align-items:center;gap:8px;flex-wrap:wrap}
|
.app-container {
|
||||||
.controls label{display:flex;align-items:center;gap:6px;color:var(--muted);font-size:12px}
|
display: flex;
|
||||||
select,button,input[type="text"]{
|
height: 100vh;
|
||||||
background:var(--chip);color:var(--fg);border:1px solid var(--border);border-radius:8px;padding:6px 10px;font-size:12px}
|
overflow: hidden;
|
||||||
button{cursor:pointer} button.primary{background:var(--accent);color:#0b0d12;border:none}
|
}
|
||||||
#tabs{display:flex;gap:6px;padding:8px 10px;background:var(--tab);border-bottom:1px solid var(--border);overflow:auto}
|
|
||||||
.tab{border:1px solid var(--border);background:var(--chip);color:var(--fg);padding:6px 10px;border-radius:999px;cursor:pointer;white-space:nowrap;font-size:12px}
|
/* Sidebar */
|
||||||
.tab.active{background:var(--tab-active);border-color:var(--accent);color:var(--accent)}
|
.sidebar {
|
||||||
main{height:calc(100% - 110px);display:grid;grid-template-columns:1fr;grid-auto-rows:1fr;gap:8px;padding:8px}
|
width: var(--sidebar-width);
|
||||||
|
background: var(--panel);
|
||||||
|
border-right: 1px solid var(--border);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-header {
|
||||||
|
padding: 20px;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
background: var(--panel);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-header h1 {
|
||||||
|
font-size: 18px;
|
||||||
|
margin: 0 0 8px 0;
|
||||||
|
color: var(--accent);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-header .subtitle {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--muted);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sidebar Controls */
|
||||||
|
.sidebar-controls {
|
||||||
|
padding: 16px;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-group {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-group:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-group label {
|
||||||
|
display: block;
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--muted);
|
||||||
|
margin-bottom: 6px;
|
||||||
|
font-weight: 500;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-group select,
|
||||||
|
.control-group input[type="text"] {
|
||||||
|
width: 100%;
|
||||||
|
background: var(--chip);
|
||||||
|
color: var(--fg);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
transition: border-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-group select:focus,
|
||||||
|
.control-group input[type="text"]:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Checkbox Groups */
|
||||||
|
.checkbox-group {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-item input[type="checkbox"] {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buttons */
|
||||||
|
.btn {
|
||||||
|
background: var(--chip);
|
||||||
|
color: var(--fg);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover {
|
||||||
|
background: var(--tab-active);
|
||||||
|
border-color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: var(--accent);
|
||||||
|
color: #0b0d12;
|
||||||
|
border-color: var(--accent);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background: #6b8fd8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Main Content */
|
||||||
|
.main-content {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header */
|
||||||
|
.header {
|
||||||
|
height: var(--header-height);
|
||||||
|
background: var(--panel);
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 20px;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--fg);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-badge {
|
||||||
|
background: var(--chip);
|
||||||
|
color: var(--muted);
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 11px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-controls {
|
||||||
|
margin-left: auto;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Theme Toggle */
|
||||||
|
.theme-toggle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-toggle input {
|
||||||
|
appearance: none;
|
||||||
|
width: 40px;
|
||||||
|
height: 20px;
|
||||||
|
border-radius: 999px;
|
||||||
|
position: relative;
|
||||||
|
background: var(--chip);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-toggle input::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
left: 2px;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--fg);
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-toggle input:checked::after {
|
||||||
|
transform: translateX(20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-toggle input:checked {
|
||||||
|
background: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Container List */
|
||||||
|
.container-list {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-item {
|
||||||
|
background: var(--chip);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-item:hover {
|
||||||
|
background: var(--tab-active);
|
||||||
|
border-color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-item.active {
|
||||||
|
background: var(--tab-active);
|
||||||
|
border-color: var(--accent);
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-item.active::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 3px;
|
||||||
|
background: var(--accent);
|
||||||
|
border-radius: 0 2px 2px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-name {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-service {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--muted);
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-status {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-indicator {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-indicator.running { background: var(--ok); }
|
||||||
|
.status-indicator.stopped { background: var(--err); }
|
||||||
|
.status-indicator.paused { background: var(--warn); }
|
||||||
|
|
||||||
|
/* Log Area */
|
||||||
|
.log-area {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-header {
|
||||||
|
padding: 12px 20px;
|
||||||
|
background: var(--panel);
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--fg);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-controls {
|
||||||
|
margin-left: auto;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-content {
|
||||||
|
flex: 1;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 16px;
|
||||||
|
background: var(--bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.log {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin: 0;
|
||||||
|
tab-size: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.sidebar {
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1000;
|
||||||
|
height: 100%;
|
||||||
|
transform: translateX(-100%);
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.open {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-content {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-toggle {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-toggle {
|
||||||
|
display: none;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--fg);
|
||||||
|
font-size: 18px;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Legacy styles for compatibility */
|
||||||
|
#tabs{display:none}
|
||||||
|
main{display:none}
|
||||||
.grid-1{grid-template-columns:1fr}
|
.grid-1{grid-template-columns:1fr}
|
||||||
.grid-2{grid-template-columns:1fr 1fr}
|
.grid-2{grid-template-columns:1fr 1fr}
|
||||||
.grid-3{grid-template-columns:1fr 1fr 1fr}
|
.grid-3{grid-template-columns:1fr 1fr 1fr}
|
||||||
@ -69,52 +436,140 @@ footer{position:fixed;right:10px;bottom:10px;opacity:.6;font-size:11px}
|
|||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<div class="app-container">
|
||||||
<h1>LogBoard+</h1>
|
<!-- Sidebar -->
|
||||||
<span id="projectBadge" class="badge">project: <em>all</em></span>
|
<div class="sidebar">
|
||||||
<div class="controls">
|
<div class="sidebar-header">
|
||||||
<label>Projects:
|
<h1><i class="fas fa-terminal"></i> LogBoard+</h1>
|
||||||
<select id="projectSelect">
|
<p class="subtitle">Modern Log Viewer</p>
|
||||||
<option value="all">All Projects</option>
|
</div>
|
||||||
</select>
|
|
||||||
</label>
|
<div class="sidebar-controls">
|
||||||
<span class="filterlvl">
|
<div class="control-group">
|
||||||
<label><input type="checkbox" id="lvlDebug" checked>DEBUG</label>
|
<label>Project</label>
|
||||||
<label><input type="checkbox" id="lvlInfo" checked>INFO</label>
|
<select id="projectSelect">
|
||||||
<label><input type="checkbox" id="lvlWarn" checked>WARN</label>
|
<option value="all">All Projects</option>
|
||||||
<label><input type="checkbox" id="lvlErr" checked>ERROR</label>
|
</select>
|
||||||
</span>
|
</div>
|
||||||
<label>Tail:
|
|
||||||
<select id="tail">
|
<div class="control-group">
|
||||||
<option value="200">200</option>
|
<label>Log Levels</label>
|
||||||
<option value="500" selected>500</option>
|
<div class="checkbox-group">
|
||||||
<option value="1000">1000</option>
|
<div class="checkbox-item">
|
||||||
<option value="0">all</option>
|
<input type="checkbox" id="lvlDebug" checked>
|
||||||
</select>
|
<label for="lvlDebug">DEBUG</label>
|
||||||
</label>
|
</div>
|
||||||
<label><input id="autoscroll" type="checkbox" checked/> auto</label>
|
<div class="checkbox-item">
|
||||||
<label><input id="wrap" type="checkbox" checked/> wrap</label>
|
<input type="checkbox" id="lvlInfo" checked>
|
||||||
<label><input id="pause" type="checkbox"/> pause</label>
|
<label for="lvlInfo">INFO</label>
|
||||||
<label><input id="aggregate" type="checkbox"/> aggregate</label>
|
</div>
|
||||||
<label class="theme-toggle" title="Dark / Light">
|
<div class="checkbox-item">
|
||||||
<span>theme</span>
|
<input type="checkbox" id="lvlWarn" checked>
|
||||||
<input id="themeSwitch" type="checkbox" />
|
<label for="lvlWarn">WARN</label>
|
||||||
</label>
|
</div>
|
||||||
<input id="filter" type="text" placeholder="filter (regex)…"/>
|
<div class="checkbox-item">
|
||||||
<button id="snapshot">snapshot</button>
|
<input type="checkbox" id="lvlErr" checked>
|
||||||
<button id="groupBtn">group</button>
|
<label for="lvlErr">ERROR</label>
|
||||||
<button id="clear">clear</button>
|
</div>
|
||||||
<button id="refresh">refresh</button>
|
</div>
|
||||||
<span id="wsstate" class="badge">ws: off</span>
|
</div>
|
||||||
<span id="layoutBadge" class="badge">view: tabs</span>
|
|
||||||
|
<div class="control-group">
|
||||||
|
<label>Tail Lines</label>
|
||||||
|
<select id="tail">
|
||||||
|
<option value="200">200</option>
|
||||||
|
<option value="500" selected>500</option>
|
||||||
|
<option value="1000">1000</option>
|
||||||
|
<option value="0">All</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-group">
|
||||||
|
<label>Options</label>
|
||||||
|
<div class="checkbox-group">
|
||||||
|
<div class="checkbox-item">
|
||||||
|
<input type="checkbox" id="autoscroll" checked>
|
||||||
|
<label for="autoscroll">Auto-scroll</label>
|
||||||
|
</div>
|
||||||
|
<div class="checkbox-item">
|
||||||
|
<input type="checkbox" id="wrap" checked>
|
||||||
|
<label for="wrap">Wrap text</label>
|
||||||
|
</div>
|
||||||
|
<div class="checkbox-item">
|
||||||
|
<input type="checkbox" id="pause">
|
||||||
|
<label for="pause">Pause</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-group">
|
||||||
|
<label>Filter</label>
|
||||||
|
<input id="filter" type="text" placeholder="Filter logs (regex)…"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-group">
|
||||||
|
<label>Actions</label>
|
||||||
|
<div class="btn-group">
|
||||||
|
<button id="refresh" class="btn"><i class="fas fa-sync-alt"></i> Refresh</button>
|
||||||
|
<button id="clear" class="btn"><i class="fas fa-trash"></i> Clear</button>
|
||||||
|
<button id="snapshot" class="btn"><i class="fas fa-download"></i> Snapshot</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Container List -->
|
||||||
|
<div class="container-list" id="containerList">
|
||||||
|
<div class="container-item placeholder">
|
||||||
|
<div class="container-name">
|
||||||
|
<i class="fas fa-info-circle"></i>
|
||||||
|
Loading containers...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<div class="main-content">
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="header">
|
||||||
|
<button class="mobile-toggle" id="mobileToggle">
|
||||||
|
<i class="fas fa-bars"></i>
|
||||||
|
</button>
|
||||||
|
<h2 class="header-title">Logs</h2>
|
||||||
|
<span class="header-badge" id="projectBadge">All Projects</span>
|
||||||
|
<div class="header-controls">
|
||||||
|
<div class="theme-toggle">
|
||||||
|
<span>Theme</span>
|
||||||
|
<input id="themeSwitch" type="checkbox" />
|
||||||
|
</div>
|
||||||
|
<span id="wsstate">ws: off</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Log Area -->
|
||||||
|
<div class="log-area">
|
||||||
|
<div class="log-header">
|
||||||
|
<h3 class="log-title" id="logTitle">Select a container to view logs</h3>
|
||||||
|
<div class="log-controls">
|
||||||
|
<span class="counter">DEBUG: <span class="cdbg">0</span></span>
|
||||||
|
<span class="counter">INFO: <span class="cinfo">0</span></span>
|
||||||
|
<span class="counter">WARN: <span class="cwarn">0</span></span>
|
||||||
|
<span class="counter">ERROR: <span class="cerr">0</span></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="log-content">
|
||||||
|
<pre class="log" id="logContent">No container selected</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="tabs"></div>
|
<!-- Legacy elements for compatibility -->
|
||||||
<div id="idFilters" style="padding:6px 10px; display:flex; gap:6px; flex-wrap:wrap;"></div>
|
<div id="tabs" style="display: none;"></div>
|
||||||
<main id="grid" class="grid-1"></main>
|
<div id="idFilters" style="display: none;"></div>
|
||||||
|
<main id="grid" style="display: none;"></main>
|
||||||
<button id="copyFab" class="copy-fab" type="button">копировать</button>
|
<button id="copyFab" class="copy-fab" type="button">копировать</button>
|
||||||
<footer>© LogBoard+</footer>
|
<footer style="display: none;">© LogBoard+</footer>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const state = {
|
const state = {
|
||||||
@ -128,6 +583,7 @@ const state = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const els = {
|
const els = {
|
||||||
|
// Legacy elements
|
||||||
tabs: document.getElementById('tabs'),
|
tabs: document.getElementById('tabs'),
|
||||||
grid: document.getElementById('grid'),
|
grid: document.getElementById('grid'),
|
||||||
tail: document.getElementById('tail'),
|
tail: document.getElementById('tail'),
|
||||||
@ -150,6 +606,12 @@ const els = {
|
|||||||
themeSwitch: document.getElementById('themeSwitch'),
|
themeSwitch: document.getElementById('themeSwitch'),
|
||||||
copyFab: document.getElementById('copyFab'),
|
copyFab: document.getElementById('copyFab'),
|
||||||
groupBtn: document.getElementById('groupBtn'),
|
groupBtn: document.getElementById('groupBtn'),
|
||||||
|
|
||||||
|
// New modern elements
|
||||||
|
containerList: document.getElementById('containerList'),
|
||||||
|
logTitle: document.getElementById('logTitle'),
|
||||||
|
logContent: document.getElementById('logContent'),
|
||||||
|
mobileToggle: document.getElementById('mobileToggle'),
|
||||||
};
|
};
|
||||||
|
|
||||||
// ----- Theme toggle -----
|
// ----- Theme toggle -----
|
||||||
@ -284,6 +746,7 @@ function panelTemplate(svc){
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildTabs(){
|
function buildTabs(){
|
||||||
|
// Legacy tabs (hidden)
|
||||||
els.tabs.innerHTML='';
|
els.tabs.innerHTML='';
|
||||||
state.services.forEach(svc=>{
|
state.services.forEach(svc=>{
|
||||||
const b = document.createElement('button');
|
const b = document.createElement('button');
|
||||||
@ -293,6 +756,38 @@ function buildTabs(){
|
|||||||
b.onclick = ()=> switchToSingle(svc);
|
b.onclick = ()=> switchToSingle(svc);
|
||||||
els.tabs.appendChild(b);
|
els.tabs.appendChild(b);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Modern container list
|
||||||
|
els.containerList.innerHTML = '';
|
||||||
|
state.services.forEach(svc => {
|
||||||
|
const item = document.createElement('div');
|
||||||
|
item.className = 'container-item';
|
||||||
|
if (state.current && svc.id === state.current.id) {
|
||||||
|
item.classList.add('active');
|
||||||
|
}
|
||||||
|
item.setAttribute('data-cid', svc.id);
|
||||||
|
|
||||||
|
const statusClass = svc.status === 'running' ? 'running' :
|
||||||
|
svc.status === 'stopped' ? 'stopped' : 'paused';
|
||||||
|
|
||||||
|
item.innerHTML = `
|
||||||
|
<div class="container-name">
|
||||||
|
<i class="fas fa-cube"></i>
|
||||||
|
${escapeHtml(svc.name)}
|
||||||
|
</div>
|
||||||
|
<div class="container-service">
|
||||||
|
${escapeHtml(svc.service || svc.name)}
|
||||||
|
${svc.project ? ` • ${escapeHtml(svc.project)}` : ''}
|
||||||
|
</div>
|
||||||
|
<div class="container-status">
|
||||||
|
<span class="status-indicator ${statusClass}"></span>
|
||||||
|
${escapeHtml(svc.status)}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
item.onclick = () => switchToSingle(svc);
|
||||||
|
els.containerList.appendChild(item);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function setLayout(cls){
|
function setLayout(cls){
|
||||||
@ -425,6 +920,15 @@ function openWs(svc, panel){
|
|||||||
}
|
}
|
||||||
obj.logEl.insertAdjacentHTML('beforeend', html);
|
obj.logEl.insertAdjacentHTML('beforeend', html);
|
||||||
if (els.autoscroll.checked) obj.wrapEl.scrollTop = obj.wrapEl.scrollHeight;
|
if (els.autoscroll.checked) obj.wrapEl.scrollTop = obj.wrapEl.scrollHeight;
|
||||||
|
|
||||||
|
// Update modern interface
|
||||||
|
if (state.current && state.current.id === id) {
|
||||||
|
els.logContent.innerHTML = obj.logEl.innerHTML;
|
||||||
|
const logContent = document.querySelector('.log-content');
|
||||||
|
if (els.autoscroll.checked) {
|
||||||
|
logContent.scrollTop = logContent.scrollHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -450,6 +954,7 @@ function ensurePanel(svc){
|
|||||||
}
|
}
|
||||||
|
|
||||||
function switchToSingle(svc){
|
function switchToSingle(svc){
|
||||||
|
// Legacy functionality
|
||||||
setLayout('tabs');
|
setLayout('tabs');
|
||||||
els.grid.innerHTML='';
|
els.grid.innerHTML='';
|
||||||
const panel = ensurePanel(svc);
|
const panel = ensurePanel(svc);
|
||||||
@ -459,6 +964,19 @@ function switchToSingle(svc){
|
|||||||
state.current = svc;
|
state.current = svc;
|
||||||
buildTabs();
|
buildTabs();
|
||||||
for (const p of [...els.grid.children]) if (p!==panel) p.remove();
|
for (const p of [...els.grid.children]) if (p!==panel) p.remove();
|
||||||
|
|
||||||
|
// Modern interface updates
|
||||||
|
els.logTitle.textContent = `${svc.name} (${svc.service || svc.name})`;
|
||||||
|
els.logContent.textContent = 'Connecting...';
|
||||||
|
|
||||||
|
// Update active state in container list
|
||||||
|
document.querySelectorAll('.container-item').forEach(item => {
|
||||||
|
item.classList.remove('active');
|
||||||
|
});
|
||||||
|
const activeItem = document.querySelector(`.container-item[data-cid="${svc.id}"]`);
|
||||||
|
if (activeItem) {
|
||||||
|
activeItem.classList.add('active');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function openMulti(ids){
|
function openMulti(ids){
|
||||||
@ -585,7 +1103,18 @@ els.groupBtn.onclick = ()=>{
|
|||||||
els.clearBtn.onclick = ()=> Object.values(state.open).forEach(o=> o.logEl.textContent='');
|
els.clearBtn.onclick = ()=> Object.values(state.open).forEach(o=> o.logEl.textContent='');
|
||||||
els.refreshBtn.onclick = fetchServices;
|
els.refreshBtn.onclick = fetchServices;
|
||||||
els.projectSelect.onchange = fetchServices;
|
els.projectSelect.onchange = fetchServices;
|
||||||
els.snapshotBtn.onclick = ()=>{ if (state.current) sendSnapshot(state.current.id); };
|
|
||||||
|
// Mobile menu toggle
|
||||||
|
els.mobileToggle.onclick = () => {
|
||||||
|
document.querySelector('.sidebar').classList.toggle('open');
|
||||||
|
};
|
||||||
|
els.snapshotBtn.onclick = ()=>{
|
||||||
|
if (state.current) {
|
||||||
|
sendSnapshot(state.current.id);
|
||||||
|
} else {
|
||||||
|
alert('No container selected');
|
||||||
|
}
|
||||||
|
};
|
||||||
els.tail.onchange = ()=> {
|
els.tail.onchange = ()=> {
|
||||||
Object.keys(state.open).forEach(id=>{
|
Object.keys(state.open).forEach(id=>{
|
||||||
const svc = state.services.find(s=> s.id===id);
|
const svc = state.services.find(s=> s.id===id);
|
||||||
@ -596,7 +1125,10 @@ els.tail.onchange = ()=> {
|
|||||||
closeWs(id); openWs(svc, panel);
|
closeWs(id); openWs(svc, panel);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
els.wrapToggle.onchange = ()=> document.querySelectorAll('.log').forEach(el=> el.style.whiteSpace = els.wrapToggle.checked ? 'pre-wrap' : 'pre');
|
els.wrapToggle.onchange = ()=> {
|
||||||
|
document.querySelectorAll('.log').forEach(el=> el.style.whiteSpace = els.wrapToggle.checked ? 'pre-wrap' : 'pre');
|
||||||
|
els.logContent.style.whiteSpace = els.wrapToggle.checked ? 'pre-wrap' : 'pre';
|
||||||
|
};
|
||||||
els.filter.oninput = ()=> { state.filter = els.filter.value.trim(); };
|
els.filter.oninput = ()=> { state.filter = els.filter.value.trim(); };
|
||||||
els.lvlDebug.onchange = ()=> state.levels.debug = els.lvlDebug.checked;
|
els.lvlDebug.onchange = ()=> state.levels.debug = els.lvlDebug.checked;
|
||||||
els.lvlInfo.onchange = ()=> state.levels.info = els.lvlInfo.checked;
|
els.lvlInfo.onchange = ()=> state.levels.info = els.lvlInfo.checked;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user