Add Mail card

This commit is contained in:
Josh Holtrop 2026-04-20 22:19:50 -04:00
parent b94343722f
commit ca45a90930
2 changed files with 115 additions and 46 deletions

View File

@ -4,6 +4,7 @@ require "socket"
require "fileutils"
require "json"
require "shellwords"
require "etc"
SOCKET_PATH = "/run/malpd/malpd.sock"
@ -212,6 +213,33 @@ def read_server_info
info
end
def read_mail
users = []
["root", "josh"].each do |user|
begin
home = Etc.getpwnam(user).dir
rescue ArgumentError
next
end
count = 0
mbox = ["/var/mail/#{user}", "/var/spool/mail/#{user}"].find { |p| File.exist?(p) }
if mbox
content = File.read(mbox) rescue ""
count = content.scan(/^From /).size
end
users << {
"user" => user,
"count" => count,
"dead_letter" => File.exist?(File.join(home, "dead.letter"))
}
end
{ "type" => "mail", "users" => users }
end
def read_ups
out = `apcaccess 2>/dev/null`
return nil unless $?.success? && !out.empty?
@ -279,6 +307,7 @@ def handle_info(conn)
info.concat(read_virtual_machines)
info << read_server_info
info << read_mail
if (ups = read_ups)
info << ups

View File

@ -369,7 +369,10 @@ if cgi.params.key?("content")
end
upses = info.select { |e| e["type"] == "ups" }
if drives.any? || upses.any?
mail = info.find { |e| e["type"] == "mail" }
mail_visible = mail && (mail["users"] || []).any?
if drives.any? || upses.any? || mail_visible
html << %(<div style="display:grid;grid-template-columns:2fr 1fr;gap:1rem;align-items:start;margin-top:1rem;">)
end
@ -411,56 +414,93 @@ if cgi.params.key?("content")
html << %(</div>)
end
upses.each do |ups|
status = ups["status"].to_s
online = status.include?("ONLINE")
card_cls = online ? "ok" : "bad"
model = ups["model"] ? "APC " + CGI.escapeHTML(ups["model"]) : "UPS"
stats = []
stats << [online ? "ok" : "bad", CGI.escapeHTML(status)] unless status.empty?
if (charge = ups["charge"])
cls = charge >= 80 ? "ok" : charge >= 50 ? "warn" : "bad"
stats << [cls, "Charge #{charge.round}%"]
end
if (timeleft = ups["timeleft"])
cls = timeleft >= 10 ? "ok" : timeleft >= 5 ? "warn" : "bad"
stats << [cls, "#{timeleft.round(1)} min left"]
end
if (load = ups["load"])
cls = load >= 90 ? "bad" : load >= 70 ? "warn" : "ok"
stats << [cls, "Load #{load.round}%"]
end
if (lv = ups["line_voltage"])
stats << ["ok", "Line #{lv.round} V"]
end
if (bv = ups["battery_voltage"])
stats << ["ok", "Batt #{bv.round(1)} V"]
end
stats_html = stats.map { |c, txt|
%(<span class="stat #{c}">#{txt}</span>)
}.join
stats_block = stats.empty? ? "" :
%(<div class="drive-stats" style="margin-top:0.6rem;">#{stats_html}</div>)
if upses.any? || mail_visible
html << %(<div>)
html << %(<div class="section-label">UPS</div>)
html << <<~HTML
<div class="card #{card_cls}">
<div class="card-header">
<span class="card-title">#{model}</span>
<span class="badge #{card_cls}">#{online ? "Online" : CGI.escapeHTML(status)}</span>
upses.each do |ups|
status = ups["status"].to_s
online = status.include?("ONLINE")
card_cls = online ? "ok" : "bad"
model = ups["model"] ? "APC " + CGI.escapeHTML(ups["model"]) : "UPS"
stats = []
stats << [online ? "ok" : "bad", CGI.escapeHTML(status)] unless status.empty?
if (charge = ups["charge"])
cls = charge >= 80 ? "ok" : charge >= 50 ? "warn" : "bad"
stats << [cls, "Charge #{charge.round}%"]
end
if (timeleft = ups["timeleft"])
cls = timeleft >= 10 ? "ok" : timeleft >= 5 ? "warn" : "bad"
stats << [cls, "#{timeleft.round(1)} min left"]
end
if (load = ups["load"])
cls = load >= 90 ? "bad" : load >= 70 ? "warn" : "ok"
stats << [cls, "Load #{load.round}%"]
end
if (lv = ups["line_voltage"])
stats << ["ok", "Line #{lv.round} V"]
end
if (bv = ups["battery_voltage"])
stats << ["ok", "Batt #{bv.round(1)} V"]
end
stats_html = stats.map { |c, txt|
%(<span class="stat #{c}">#{txt}</span>)
}.join
stats_block = stats.empty? ? "" :
%(<div class="drive-stats" style="margin-top:0.6rem;">#{stats_html}</div>)
html << %(<div class="section-label">UPS</div>)
html << <<~HTML
<div class="card #{card_cls}">
<div class="card-header">
<span class="card-title">#{model}</span>
<span class="badge #{card_cls}">#{online ? "Online" : CGI.escapeHTML(status)}</span>
</div>
#{stats_block}
</div>
#{stats_block}
</div>
HTML
HTML
end
if mail_visible
users = mail["users"]
has_warn = users.any? { |u| u["count"].to_i > 0 || u["dead_letter"] }
mail_card_cls = has_warn ? "warn" : "ok"
html << %(<div class="section-label">Mail</div>)
html << %(<div class="card #{mail_card_cls}"><div class="card-header"><span class="card-title">Local Mail</span></div><div class="subitems">)
users.each do |u|
count = u["count"].to_i
cls = count > 0 ? "warn" : "ok"
label = count == 1 ? "1 message" : "#{count} messages"
html << <<~HTML
<div class="subitem">
<div class="subitem-left"><div class="dot #{cls}"></div><span class="subitem-name">#{CGI.escapeHTML(u["user"].to_s)}</span></div>
<span class="subitem-value #{cls}">#{label}</span>
</div>
HTML
end
html << %(</div>)
dead = users.select { |u| u["dead_letter"] }
if dead.any?
stats_html = dead.map { |u|
%(<span class="stat warn">dead.letter in ~#{CGI.escapeHTML(u["user"].to_s)}</span>)
}.join
html << %(<div class="drive-stats" style="margin-top:0.6rem;">#{stats_html}</div>)
end
html << %(</div>)
end
html << %(</div>)
end
if drives.any? || upses.any?
if drives.any? || upses.any? || mail_visible
html << %(</div>)
end