Files
DokkuStatus/templates/apps_table.html
Peter Stockings dc20afd0f3 Show recent logs
2025-12-23 23:00:29 +11:00

467 lines
18 KiB
HTML

{% macro gauge(label, pct, subtitle, value_text=None) %}
{% set p = pct if pct is not none else 0 %}
{% if p < 0 %}{% set p=0 %}{% endif %} {% if p> 100 %}{% set p = 100 %}{% endif %}
{% if p < 60 %} {% set col="#00ff88" %} {% set status="OK" %} {% elif p < 85 %} {% set col="#ffb86c" %} {% set
status="WARN" %} {% else %} {% set col="#ff5555" %} {% set status="CRIT" %} {% endif %} {% set txt=value_text if
value_text else (p|round(0)|int ~ "%" ) %} <div style="
border: 2px solid {{ col }};
background: rgba({{ '0, 255, 136' if p < 60 else ('255, 184, 108' if p < 85 else '255, 85, 85') }}, 0.05);
padding: 16px;
position: relative;
transition: all 0.3s ease;
">
<!-- Status indicator -->
<div style="
position: absolute;
top: 8px;
right: 8px;
font-size: 9px;
font-weight: 700;
letter-spacing: 1px;
color: {{ col }};
padding: 2px 6px;
border: 1px solid {{ col }};
background: rgba({{ '0, 255, 136' if p < 60 else ('255, 184, 108' if p < 85 else '255, 85, 85') }}, 0.1);
">
[{{ status }}]
</div>
<!-- Label -->
<div style="
font-size: 11px;
font-weight: 700;
letter-spacing: 2px;
color: #8b949e;
text-transform: uppercase;
margin-bottom: 8px;
">
> {{ label }}
</div>
<!-- Value -->
<div style="
font-size: 28px;
font-weight: 700;
color: {{ col }};
margin-bottom: 8px;
text-shadow: 0 0 10px rgba({{ '0, 255, 136' if p < 60 else ('255, 184, 108' if p < 85 else '255, 85, 85') }}, 0.5);
">
{{ txt }}
</div>
<!-- Subtitle -->
<div style="
font-size: 11px;
color: #8b949e;
margin-bottom: 12px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
">
{{ subtitle }}
</div>
<!-- Progress bar -->
<div style="
height: 6px;
background: #30363d;
position: relative;
overflow: hidden;
">
<div style="
height: 100%;
width: {{ p }}%;
background: {{ col }};
box-shadow: 0 0 10px {{ col }};
transition: width 0.5s ease;
"></div>
</div>
<!-- ASCII bar representation -->
<div style="
font-size: 10px;
color: {{ col }};
margin-top: 8px;
font-family: 'JetBrains Mono', monospace;
letter-spacing: 0;
">
{% set blocks = (p / 5)|round(0)|int %}
{% set empty = 20 - blocks %}
[{% for i in range(blocks) %}█{% endfor %}{% for i in range(empty) %}░{% endfor %}]
</div>
</div>
{% endmacro %}
<h2>[ LIVE METRICS ]</h2>
<div
style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 20px; margin: 0 0 32px 0;">
{{ gauge("CPU", data.gauges.cpu_total_pct, "Sum of container CPU% (clamped)") }}
{{ gauge("RAM", data.gauges.ram_pct, "All containers vs host RAM", (data.gauges.ram_used_h ~ " / " ~
data.gauges.ram_total_h)) }}
{{ gauge("DOCKER_DISK", data.gauges.docker_images_pct, "Images used vs total store") }}
</div>
<h2>[ SYSTEM INFO ]</h2>
<div
style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; margin: 0 0 32px 0;">
<div style="
border: 2px solid #30363d;
background: rgba(0, 217, 255, 0.02);
padding: 20px;
transition: all 0.3s ease;
" onmouseover="this.style.borderColor='#00d9ff'; this.style.boxShadow='0 0 20px rgba(0, 217, 255, 0.3)';"
onmouseout="this.style.borderColor='#30363d'; this.style.boxShadow='none';">
<div style="
color: #00d9ff;
font-size: 10px;
font-weight: 700;
letter-spacing: 2px;
text-transform: uppercase;
margin-bottom: 12px;
">
[HOST]
</div>
<div style="font-size: 18px; font-weight: 700; color: #c9d1d9; margin-bottom: 8px;">
{{ data.system.name or "—" }}
</div>
<div style="color: #8b949e; font-size: 12px; line-height: 1.6;">
{{ data.system.operating_system }}<br>
Kernel: {{ data.system.kernel_version }}
</div>
</div>
<div style="
border: 2px solid #30363d;
background: rgba(0, 255, 136, 0.02);
padding: 20px;
transition: all 0.3s ease;
" onmouseover="this.style.borderColor='#00ff88'; this.style.boxShadow='0 0 20px rgba(0, 255, 136, 0.3)';"
onmouseout="this.style.borderColor='#30363d'; this.style.boxShadow='none';">
<div style="
color: #00ff88;
font-size: 10px;
font-weight: 700;
letter-spacing: 2px;
text-transform: uppercase;
margin-bottom: 12px;
">
[COMPUTE]
</div>
<div style="font-size: 14px; font-weight: 600; color: #c9d1d9; margin-bottom: 6px;">
<span style="color: #00ff88; font-size: 20px; font-weight: 700;">{{ data.system.cpus or "—"
}}</span> CPUs
</div>
<div style="font-size: 14px; font-weight: 600; color: #c9d1d9;">
<span style="color: #00ff88; font-size: 20px; font-weight: 700;">{{ data.system.mem_total_h or "—"
}}</span> RAM
</div>
</div>
<div style="
border: 2px solid #30363d;
background: rgba(255, 184, 108, 0.02);
padding: 20px;
transition: all 0.3s ease;
" onmouseover="this.style.borderColor='#ffb86c'; this.style.boxShadow='0 0 20px rgba(255, 184, 108, 0.3)';"
onmouseout="this.style.borderColor='#30363d'; this.style.boxShadow='none';">
<div style="
color: #ffb86c;
font-size: 10px;
font-weight: 700;
letter-spacing: 2px;
text-transform: uppercase;
margin-bottom: 12px;
">
[DOCKER]
</div>
<div style="font-size: 12px; color: #c9d1d9; margin-bottom: 6px; line-height: 1.7;">
Engine: <span style="color: #ffb86c; font-weight: 700;">{{ data.system.server_version or "—"
}}</span>
</div>
<div style="font-size: 12px; color: #c9d1d9; margin-bottom: 6px; line-height: 1.7;">
Images: <span style="color: #ffb86c; font-weight: 700;">{{ data.system.images or "—" }}</span>
</div>
<div style="font-size: 12px; color: #c9d1d9; line-height: 1.7;">
Containers: <span style="color: #00ff88; font-weight: 700;">{{ data.system.containers_running or "—"
}}</span> up / <span style="color: #8b949e; font-weight: 700;">{{ data.system.containers_stopped
or "—" }}</span> down
</div>
</div>
</div>
<h3>DOCKER DISK USAGE</h3>
<div style="overflow-x: auto; margin-bottom: 24px;">
<table>
<thead>
<tr>
<th>TYPE</th>
<th>TOTAL</th>
<th>ACTIVE</th>
<th>SIZE</th>
<th>RECLAIMABLE</th>
</tr>
</thead>
<tbody>
{% for typ, r in data.system.system_df.items() %}
<tr>
<td><span style="color: #00d9ff; font-weight: 700;">[{{ typ }}]</span></td>
<td>{{ r.total }}</td>
<td>{{ r.active }}</td>
<td>{{ r.size }}</td>
<td>{{ r.reclaimable }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div style="
font-size: 11px;
color: #8b949e;
margin-bottom: 32px;
font-weight: 500;
">
<span style="color: #00d9ff;">[TIMESTAMP]</span> {{ data.generated_at }}
</div>
{% if data.warnings %}
<div style="
margin: 24px 0;
padding: 16px 20px;
border: 2px solid #ffb86c;
background: rgba(255, 184, 108, 0.05);
position: relative;
">
<div style="
position: absolute;
top: -12px;
left: 16px;
background: #161b22;
padding: 0 8px;
color: #ffb86c;
font-size: 11px;
font-weight: 700;
letter-spacing: 2px;
">
[!! WARNINGS !!]
</div>
<ul style="margin: 8px 0 0 0; padding-left: 24px; color: #ffb86c;">
{% for w in data.warnings %}
<li style="margin: 6px 0; font-size: 13px;">{{ w }}</li>
{% endfor %}
</ul>
</div>
{% endif %}
<h2>[ APPLICATIONS ]</h2>
<div style="overflow-x: auto; margin-bottom: 32px;">
<table>
<thead>
<tr>
<th>APP</th>
<th>URL</th>
<th>STATUS</th>
<th>CPU</th>
<th>RAM</th>
<th>RESTARTS</th>
<th>IMAGE</th>
</tr>
</thead>
<tbody>
{% for r in data.apps %}
<tr>
<td><span style="color: #00d9ff; font-weight: 700;">{{ r.app }}</span></td>
<td>
<a href="{{ r.url }}" style="
color: #00ff88;
text-decoration: none;
transition: all 0.2s ease;
" onmouseover="this.style.color='#00d9ff'; this.style.textShadow='0 0 10px rgba(0, 217, 255, 0.5)';"
onmouseout="this.style.color='#00ff88'; this.style.textShadow='none';">
{{ r.url }}
</a>
</td>
<td>
<span style="
padding: 3px 8px;
background: rgba(0, 255, 136, 0.1);
color: #00ff88;
border: 1px solid #00ff88;
font-size: 10px;
font-weight: 700;
letter-spacing: 1px;
">
{{ r.status }}
</span>
</td>
<td style="color: #00ff88; font-weight: 600;">{{ r.cpu or "—" }}</td>
<td style="font-size: 12px;">
{% if r.mem_used %}
{{ r.mem_used }} / {{ r.mem_limit }}
<span style="color: #00d9ff; font-weight: 700;">({{ r.mem_pct }})</span>
{% else %} — {% endif %}
</td>
<td style="
color: {% if r.restarts >= 3 %}#ff5555{% else %}#8b949e{% endif %};
font-weight: 700;
">
{{ r.restarts }}
</td>
<td style="color: #8b949e; font-size: 11px;">{{ r.image }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% if data.infra %}
<h2>[ INFRASTRUCTURE ]</h2>
<div style="overflow-x: auto;">
<table>
<thead>
<tr>
<th>CONTAINER</th>
<th>STATUS</th>
<th>CPU</th>
<th>RAM</th>
<th>RESTARTS</th>
<th>IMAGE</th>
</tr>
</thead>
<tbody>
{% for r in data.infra %}
<tr>
<td><span style="color: #ffb86c; font-weight: 700;">{{ r.container }}</span></td>
<td>
<span style="
padding: 3px 8px;
background: rgba(0, 255, 136, 0.1);
color: #00ff88;
border: 1px solid #00ff88;
font-size: 10px;
font-weight: 700;
letter-spacing: 1px;
">
{{ r.status }}
</span>
</td>
<td style="color: #00ff88; font-weight: 600;">{{ r.cpu or "—" }}</td>
<td style="font-size: 12px;">
{% if r.mem_used %}
{{ r.mem_used }} / {{ r.mem_limit }}
<span style="color: #00d9ff; font-weight: 700;">({{ r.mem_pct }})</span>
{% else %} — {% endif %}
</td>
<td style="
color: {% if r.restarts >= 3 %}#ff5555{% else %}#8b949e{% endif %};
font-weight: 700;
">
{{ r.restarts }}
</td>
<td style="color: #8b949e; font-size: 11px;">{{ r.image }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
<!-- APPLICATION LOGS SECTION -->
<h2 style="margin-top: 32px;">[ APPLICATION LOGS ]</h2>
{% for r in data.apps %}
<div style="
border: 2px solid #30363d;
margin-bottom: 16px;
background: rgba(0, 217, 255, 0.02);
">
<!-- Header -->
<div style="
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
cursor: pointer;
" onclick="toggleLogs('logs-{{ loop.index }}')">
<div style="font-weight: 700; color: #00d9ff;">[{{ r.app }}]</div>
<button style="
background: rgba(0, 217, 255, 0.1);
border: 1px solid #00d9ff;
color: #00d9ff;
padding: 4px 12px;
font-family: 'JetBrains Mono', monospace;
font-size: 10px;
font-weight: 700;
cursor: pointer;
pointer-events: none;
">[EXPAND]</button>
</div>
<!-- Collapsible logs -->
<div id="logs-{{ loop.index }}" style="display: none; border-top: 2px solid #30363d;">
<div style="padding: 16px; background: #0a0e17;">
<div style="display: flex; justify-content: space-between; margin-bottom: 12px;">
<div style="color: #00d9ff; font-size: 10px; font-weight: 700; letter-spacing: 2px;">
[LAST 50 LINES]
</div>
<button onclick="event.stopPropagation(); copyLogs('logs-content-{{ loop.index }}')" style="
background: rgba(0, 255, 136, 0.1);
border: 1px solid #00ff88;
color: #00ff88;
padding: 4px 10px;
font-family: 'JetBrains Mono', monospace;
font-size: 10px;
font-weight: 700;
cursor: pointer;
" onmouseover="this.style.background='rgba(0, 255, 136, 0.2)';"
onmouseout="this.style.background='rgba(0, 255, 136, 0.1)';">
COPY
</button>
</div>
<div id="logs-content-{{ loop.index }}" style="
background: #000;
border: 1px solid #30363d;
padding: 12px;
max-height: 400px;
overflow-y: auto;
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
line-height: 1.6;
">
{% if r.logs %}
{% for log in r.logs %}
<div
style="color: {% if log.level == 'error' %}#ff5555{% elif log.level == 'warn' %}#ffb86c{% else %}#8b949e{% endif %};">
{{ log.text }}</div>
{% endfor %}
{% else %}
<div style="color: #8b949e;">[no logs available]</div>
{% endif %}
</div>
</div>
</div>
</div>
{% endfor %}
<script>
function toggleLogs(id) {
const el = document.getElementById(id);
el.style.display = el.style.display === 'none' ? 'block' : 'none';
}
function copyLogs(id) {
const el = document.getElementById(id);
navigator.clipboard.writeText(el.innerText).then(() => {
const btn = event.target;
const orig = btn.textContent;
btn.textContent = 'COPIED!';
btn.style.background = 'rgba(0, 255, 136, 0.3)';
setTimeout(() => {
btn.textContent = orig;
btn.style.background = 'rgba(0, 255, 136, 0.1)';
}, 1500);
});
}
</script>