From ca45a90930a5bee6b9cca389a3449e44720c7c79 Mon Sep 17 00:00:00 2001 From: Josh Holtrop Date: Mon, 20 Apr 2026 22:19:50 -0400 Subject: [PATCH] Add Mail card --- bin/malpd | 29 +++++++++++ cgi-bin/malp.rb | 132 +++++++++++++++++++++++++++++++----------------- 2 files changed, 115 insertions(+), 46 deletions(-) diff --git a/bin/malpd b/bin/malpd index 53ab682..1122fc4 100755 --- a/bin/malpd +++ b/bin/malpd @@ -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 diff --git a/cgi-bin/malp.rb b/cgi-bin/malp.rb index 745eb56..293d228 100755 --- a/cgi-bin/malp.rb +++ b/cgi-bin/malp.rb @@ -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 << %(
) end @@ -411,56 +414,93 @@ if cgi.params.key?("content") html << %(
) 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| - %(#{txt}) - }.join - stats_block = stats.empty? ? "" : - %(
#{stats_html}
) - + if upses.any? || mail_visible html << %(
) - html << %() - html << <<~HTML -
-
- #{model} - #{online ? "Online" : CGI.escapeHTML(status)} + + 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| + %(#{txt}) + }.join + stats_block = stats.empty? ? "" : + %(
#{stats_html}
) + + html << %() + html << <<~HTML +
+
+ #{model} + #{online ? "Online" : CGI.escapeHTML(status)} +
+ #{stats_block}
- #{stats_block} -
- 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 << %() + html << %(
Local Mail
) + + users.each do |u| + count = u["count"].to_i + cls = count > 0 ? "warn" : "ok" + label = count == 1 ? "1 message" : "#{count} messages" + html << <<~HTML +
+
#{CGI.escapeHTML(u["user"].to_s)}
+ #{label} +
+ HTML + end + + html << %(
) + + dead = users.select { |u| u["dead_letter"] } + if dead.any? + stats_html = dead.map { |u| + %(dead.letter in ~#{CGI.escapeHTML(u["user"].to_s)}) + }.join + html << %(
#{stats_html}
) + end + + html << %(
) + end + html << %(
) end - if drives.any? || upses.any? + if drives.any? || upses.any? || mail_visible html << %(
) end