Add temperature monitoring and optional server URL links to dashboard
All checks were successful
Build-Publish / build (linux/amd64) (push) Successful in 5s
Build-Publish / build (linux/arm64) (push) Successful in 11s
Build-Publish / create-manifest (push) Successful in 2s
Build-Publish / publish-template (push) Successful in 7s

This commit is contained in:
j
2026-03-07 23:08:57 +13:00
parent d8338bbf82
commit df29cd88de
5 changed files with 96 additions and 4 deletions

View File

@@ -35,6 +35,7 @@ class Server(db.Model):
username = db.Column(db.String(255), nullable=False)
hostname = db.Column(db.String(255), nullable=False)
primary_ip = db.Column(db.String(45), default='')
url = db.Column(db.String(1024), default='')
is_online = db.Column(db.Boolean, default=False)
last_collected = db.Column(db.DateTime, nullable=True)
details = db.Column(db.JSON, nullable=True)
@@ -55,13 +56,16 @@ def parse_infrastructure_conf():
if line[0] not in (' ', '\t'):
current_group = line.strip()
else:
entry = line.strip()
parts = line.strip().split(None, 1)
entry = parts[0] if parts else ''
url = parts[1] if len(parts) > 1 else ''
if '@' in entry:
user, host = entry.split('@', 1)
servers.append({
'group': current_group or 'Default',
'username': user.strip(),
'hostname': host.strip(),
'url': url.strip(),
})
except FileNotFoundError:
logger.error("infrastructure.conf not found at %s", INFRA_CONF_PATH)
@@ -204,6 +208,7 @@ def collect_all():
db.session.add(server)
server.group_name = entry['group']
server.url = entry.get('url', '')
server.is_online = result.get('is_online', False)
server.last_collected = datetime.now(timezone.utc)
server.details = result
@@ -271,6 +276,7 @@ def api_servers():
'username': s.username,
'hostname': s.hostname,
'primary_ip': s.primary_ip,
'url': s.url,
'is_online': s.is_online,
'last_collected': s.last_collected.isoformat() if s.last_collected else None,
'details': s.details,
@@ -327,6 +333,21 @@ def format_uptime(seconds):
return f"{hours}h {minutes}m"
@app.template_filter('temp_color')
def temp_color(temp_c):
try:
t = float(temp_c)
except (TypeError, ValueError):
return '#64748b'
if t >= 90:
return '#ef4444'
if t >= 75:
return '#f97316'
if t >= 60:
return '#eab308'
return '#22c55e'
@app.template_filter('usage_color')
def usage_color(percent):
try:

View File

@@ -45,6 +45,29 @@ else
echo "usage_percent=0.0"
fi
# Temperatures - try sensors (lm-sensors), fall back to thermal zones
echo "[temperatures]"
if command -v sensors &>/dev/null; then
sensors 2>/dev/null | awk -F'[: ]+' '
/^[^ ]/ { chip=$1 }
/°C/ {
label=$1
gsub(/^ +| +$/, "", label)
match($0, /[+-]([0-9]+\.[0-9]+)°C/, m)
if (m[1]+0 > 0) print chip "/" label "=" m[1]
}
'
else
for tz in /sys/class/thermal/thermal_zone*; do
[ -f "$tz/temp" ] || continue
type=$(cat "$tz/type" 2>/dev/null || echo "unknown")
temp_mc=$(cat "$tz/temp" 2>/dev/null || echo 0)
temp_c=$((temp_mc / 1000))
temp_frac=$(( (temp_mc % 1000) / 100 ))
[ "$temp_c" -gt 0 ] 2>/dev/null && echo "${type}=${temp_c}.${temp_frac}"
done
fi
echo "[memory]"
total_kb=$(grep MemTotal /proc/meminfo 2>/dev/null | awk '{print $2}')
available_kb=$(grep MemAvailable /proc/meminfo 2>/dev/null | awk '{print $2}')

View File

@@ -121,6 +121,19 @@ main {
white-space: nowrap;
}
.server-link {
color: #3b82f6;
text-decoration: none;
font-size: 0.9rem;
flex-shrink: 0;
opacity: 0.7;
transition: opacity 0.2s;
}
.server-link:hover {
opacity: 1;
}
.server-ip {
font-size: 0.8rem;
color: #94a3b8;

View File

@@ -23,6 +23,13 @@
{% set sys = d.get('system', {}) if d.get('system') else {} %}
{% set cpu = d.get('cpu', {}) if d.get('cpu') else {} %}
{% set mem = d.get('memory', {}) if d.get('memory') else {} %}
{% set temps = d.get('temperatures', {}) if d.get('temperatures') else {} %}
{% set max_temp = namespace(val=0.0) %}
{% for k, v in temps.items() %}
{% if v|float > max_temp.val %}
{% set max_temp.val = v|float %}
{% endif %}
{% endfor %}
{% set cpu_pct = cpu.get('usage_percent', '0')|float %}
{% set mem_pct = mem.get('usage_percent', '0')|float %}
{% set disk_usages = d.get('disk_usage', []) if d.get('disk_usage') else [] %}
@@ -42,6 +49,9 @@
<div class="card-header">
<span class="status-dot {% if server.is_online %}online{% else %}offline{% endif %}"></span>
<span class="server-name">{{ server.hostname }}</span>
{% if server.url %}
<a href="{{ server.url }}" class="server-link" target="_blank" rel="noopener" onclick="event.stopPropagation();" title="{{ server.url }}">&#x2197;</a>
{% endif %}
</div>
<div class="server-ip">{{ server.primary_ip or 'No IP' }}</div>
<div class="server-os">{{ sys.get('os_pretty', '') }}</div>
@@ -69,6 +79,15 @@
</div>
<span class="usage-pct">{{ '%.0f'|format(root_disk.pct) }}%</span>
</div>
{% if max_temp.val > 0 %}
<div class="usage-row">
<span class="usage-label">TEMP</span>
<div class="usage-bar-bg">
<div class="usage-bar-fill" style="width: {{ [max_temp.val, 100.0]|min }}%; background: {{ max_temp.val|temp_color }};"></div>
</div>
<span class="usage-pct">{{ '%.0f'|format(max_temp.val) }}°</span>
</div>
{% endif %}
</div>
{% else %}
<div class="offline-label">Unreachable</div>
@@ -105,6 +124,21 @@
</table>
</div>
<!-- Temperatures -->
{% if temps %}
<div class="detail-section">
<h4>Temperatures</h4>
<table>
{% for sensor, value in temps.items() %}
<tr>
<td>{{ sensor }}</td>
<td style="color: {{ value|float|temp_color }}">{{ value }}°C</td>
</tr>
{% endfor %}
</table>
</div>
{% endif %}
<!-- Memory -->
<div class="detail-section">
<h4>Memory</h4>