diff --git a/README.md b/README.md index ecbd08e..4f90613 100644 --- a/README.md +++ b/README.md @@ -50,9 +50,9 @@ Edit `infrastructure.conf` to define your servers: ``` Production - root@prod-web-01 + root@prod-web-01 https://web01.example.com root@prod-db-01 - deploy@prod-app-01 + deploy@prod-app-01 https://app01.example.com:8080 Development deploy@dev-01 @@ -60,7 +60,8 @@ Development ``` - Group names are freeform labels (no indentation) -- Servers are indented with `USERNAME@HOSTNAME` +- Servers are indented with `USERNAME@HOSTNAME [URL]` +- An optional URL after the host adds a clickable link on the dashboard - Lines starting with `#` are comments ### 3. Install diff --git a/app/app.py b/app/app.py index 1932505..b344d60 100644 --- a/app/app.py +++ b/app/app.py @@ -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: diff --git a/app/gather_info.sh b/app/gather_info.sh index 5d847db..df8da0d 100755 --- a/app/gather_info.sh +++ b/app/gather_info.sh @@ -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}') diff --git a/app/static/style.css b/app/static/style.css index b21afdb..a2f72db 100644 --- a/app/static/style.css +++ b/app/static/style.css @@ -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; diff --git a/app/templates/index.html b/app/templates/index.html index cb2fdcb..f5c87db 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -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 @@
{{ server.hostname }} + {% if server.url %} + + {% endif %}
{{ server.primary_ip or 'No IP' }}
{{ sys.get('os_pretty', '') }}
@@ -69,6 +79,15 @@ {{ '%.0f'|format(root_disk.pct) }}% + {% if max_temp.val > 0 %} +
+ TEMP +
+
+
+ {{ '%.0f'|format(max_temp.val) }}° +
+ {% endif %} {% else %}
Unreachable
@@ -105,6 +124,21 @@ + + {% if temps %} +
+

Temperatures

+ + {% for sensor, value in temps.items() %} + + + + + {% endfor %} +
{{ sensor }}{{ value }}°C
+
+ {% endif %} +

Memory