Add container summary on card face, fix df parsing for wrapped lines, collect OS info, faster startup and refresh
This commit is contained in:
@@ -249,7 +249,7 @@ def collect_all():
|
|||||||
|
|
||||||
|
|
||||||
def collector_loop():
|
def collector_loop():
|
||||||
time.sleep(10) # Let the app start up
|
time.sleep(2) # Brief pause to let Flask start
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
collect_all()
|
collect_all()
|
||||||
|
|||||||
@@ -186,13 +186,18 @@ gather_container_stats() {
|
|||||||
echo "mem_percent=$mem_pct"
|
echo "mem_percent=$mem_pct"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Disk (rootfs)
|
# Disk (rootfs) - merge wrapped lines, find the / mount
|
||||||
local disk_info
|
local disk_info
|
||||||
disk_info=$($exec_cmd df -B1 / 2>/dev/null | tail -1)
|
disk_info=$($exec_cmd df -B1 / 2>/dev/null | awk 'NR>1{line=line $0" "} END{print line}')
|
||||||
if [ -n "$disk_info" ]; then
|
if [ -n "$disk_info" ]; then
|
||||||
echo "disk_total=$(echo "$disk_info" | awk '{print $2}')"
|
# Extract numbers - find the fields: total used avail percent
|
||||||
echo "disk_used=$(echo "$disk_info" | awk '{print $3}')"
|
local d_total d_used d_pct
|
||||||
echo "disk_percent=$(echo "$disk_info" | awk '{gsub(/%/,""); print $5}')"
|
d_total=$(echo "$disk_info" | awk '{for(i=1;i<=NF;i++) if($i+0>0 && $i !~ /%/){print $i; exit}}')
|
||||||
|
d_used=$(echo "$disk_info" | awk '{for(i=1;i<=NF;i++) if($i+0>0 && $i !~ /%/){n++; if(n==2){print $i; exit}}}')
|
||||||
|
d_pct=$(echo "$disk_info" | grep -oE '[0-9]+%' | head -1 | tr -d '%')
|
||||||
|
[ -n "$d_total" ] && echo "disk_total=$d_total"
|
||||||
|
[ -n "$d_used" ] && echo "disk_used=$d_used"
|
||||||
|
[ -n "$d_pct" ] && echo "disk_percent=$d_pct"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# IP
|
# IP
|
||||||
@@ -200,6 +205,11 @@ gather_container_stats() {
|
|||||||
ip=$($exec_cmd hostname -I 2>/dev/null | awk '{print $1}')
|
ip=$($exec_cmd hostname -I 2>/dev/null | awk '{print $1}')
|
||||||
[ -n "$ip" ] && echo "ip=$ip"
|
[ -n "$ip" ] && echo "ip=$ip"
|
||||||
|
|
||||||
|
# OS
|
||||||
|
local os_pretty
|
||||||
|
os_pretty=$($exec_cmd cat /etc/os-release 2>/dev/null | grep PRETTY_NAME | cut -d= -f2 | tr -d '"')
|
||||||
|
[ -n "$os_pretty" ] && echo "os=$os_pretty"
|
||||||
|
|
||||||
# Uptime
|
# Uptime
|
||||||
local uptime_s
|
local uptime_s
|
||||||
uptime_s=$($exec_cmd cut -d' ' -f1 /proc/uptime 2>/dev/null | cut -d. -f1)
|
uptime_s=$($exec_cmd cut -d' ' -f1 /proc/uptime 2>/dev/null | cut -d. -f1)
|
||||||
@@ -217,8 +227,11 @@ _sudo() {
|
|||||||
|
|
||||||
# Proxmox LXC (pct)
|
# Proxmox LXC (pct)
|
||||||
if command -v pct &>/dev/null; then
|
if command -v pct &>/dev/null; then
|
||||||
_sudo pct list 2>/dev/null | tail -n +2 | while read -r vmid status _ name _; do
|
_sudo pct list 2>/dev/null | tail -n +2 | while read -r line; do
|
||||||
[ -z "$vmid" ] && continue
|
[ -z "$line" ] && continue
|
||||||
|
vmid=$(echo "$line" | awk '{print $1}')
|
||||||
|
status=$(echo "$line" | awk '{print $2}')
|
||||||
|
name=$(echo "$line" | awk '{print $NF}')
|
||||||
echo "[container:pct-${vmid}]"
|
echo "[container:pct-${vmid}]"
|
||||||
echo "type=lxc"
|
echo "type=lxc"
|
||||||
echo "platform=proxmox"
|
echo "platform=proxmox"
|
||||||
|
|||||||
@@ -327,6 +327,63 @@ main {
|
|||||||
color: #475569;
|
color: #475569;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --- Container Summary (on card face) --- */
|
||||||
|
|
||||||
|
.ct-summary-list {
|
||||||
|
margin-top: 10px;
|
||||||
|
padding-top: 8px;
|
||||||
|
border-top: 1px solid #334155;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ct-summary-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: #94a3b8;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-dot-sm {
|
||||||
|
width: 5px;
|
||||||
|
height: 5px;
|
||||||
|
border-radius: 50%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-dot-sm.online {
|
||||||
|
background: #22c55e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-dot-sm.offline {
|
||||||
|
background: #ef4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ct-summary-name {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #cbd5e1;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ct-summary-os {
|
||||||
|
color: #64748b;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ct-summary-ip {
|
||||||
|
color: #64748b;
|
||||||
|
font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace;
|
||||||
|
font-size: 0.65rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-left: auto;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* --- Container / VM Sub-cards --- */
|
/* --- Container / VM Sub-cards --- */
|
||||||
|
|
||||||
.container-grid {
|
.container-grid {
|
||||||
|
|||||||
@@ -63,6 +63,8 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% set containers = d.get('container', []) if d.get('container') else [] %}
|
||||||
|
|
||||||
{% if server.is_online %}
|
{% if server.is_online %}
|
||||||
<div class="usage-bars">
|
<div class="usage-bars">
|
||||||
<div class="usage-row">
|
<div class="usage-row">
|
||||||
@@ -96,6 +98,20 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
{% if containers %}
|
||||||
|
<div class="ct-summary-list">
|
||||||
|
{% for ct in containers %}
|
||||||
|
{% set ct_up = ct.get('status', '')|lower in ['running', 'started'] %}
|
||||||
|
<div class="ct-summary-item">
|
||||||
|
<span class="status-dot-sm {% if ct_up %}online{% else %}offline{% endif %}"></span>
|
||||||
|
<span class="ct-summary-name">{{ ct.get('name', ct.get('id', '?')) }}</span>
|
||||||
|
{% if ct.get('os') %}<span class="ct-summary-os">{{ ct.get('os') }}</span>{% endif %}
|
||||||
|
{% if ct.get('ip') %}<span class="ct-summary-ip">{{ ct.get('ip') }}</span>{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="offline-label">Unreachable</div>
|
<div class="offline-label">Unreachable</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -254,7 +270,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Containers / VMs -->
|
<!-- Containers / VMs -->
|
||||||
{% set containers = d.get('container', []) if d.get('container') else [] %}
|
|
||||||
{% if containers %}
|
{% if containers %}
|
||||||
<div class="detail-section wide">
|
<div class="detail-section wide">
|
||||||
<h4>Containers & VMs</h4>
|
<h4>Containers & VMs</h4>
|
||||||
@@ -396,16 +411,10 @@
|
|||||||
if (card) toggleDetails(card);
|
if (card) toggleDetails(card);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-refresh without full page reload if a card is expanded,
|
// If no servers have data yet, refresh quickly; otherwise every 60s
|
||||||
// otherwise do a simple reload
|
const hasData = document.querySelectorAll('.server-card').length > 0;
|
||||||
setInterval(function() {
|
const refreshMs = hasData ? 60000 : 5000;
|
||||||
if (document.querySelector('.server-card.expanded')) {
|
setInterval(function() { location.reload(); }, refreshMs);
|
||||||
// A card is open - reload page preserving hash
|
|
||||||
location.reload();
|
|
||||||
} else {
|
|
||||||
location.reload();
|
|
||||||
}
|
|
||||||
}, 60000);
|
|
||||||
|
|
||||||
// Restore state on load
|
// Restore state on load
|
||||||
restoreExpanded();
|
restoreExpanded();
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ services:
|
|||||||
- ${SSH_KEY_PATH}:/app/ssh_key:ro
|
- ${SSH_KEY_PATH}:/app/ssh_key:ro
|
||||||
- ${CONFIG_PATH}/infrastructure.conf:/app/infrastructure.conf:ro
|
- ${CONFIG_PATH}/infrastructure.conf:/app/infrastructure.conf:ro
|
||||||
- app_data:/app/data
|
- app_data:/app/data
|
||||||
|
stop_signal: SIGINT
|
||||||
|
stop_grace_period: 5s
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:5000/')"]
|
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:5000/')"]
|
||||||
|
|||||||
Reference in New Issue
Block a user