From b78fb9469e342e710811e9926568cda378f6dad5 Mon Sep 17 00:00:00 2001 From: Josh Holtrop Date: Sun, 19 Apr 2026 22:40:26 -0400 Subject: [PATCH] Add UPS status card --- bin/malpd | 41 ++++++++++++++++++++++++++++++++++++++++ cgi-bin/malp.rb | 50 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) diff --git a/bin/malpd b/bin/malpd index 607ae65..ae9024a 100755 --- a/bin/malpd +++ b/bin/malpd @@ -166,6 +166,43 @@ def read_virtual_machines end end +def read_ups + out = `apcaccess 2>/dev/null` + return nil unless $?.success? && !out.empty? + + raw = {} + out.each_line do |line| + next unless line.include?(":") + key, value = line.split(":", 2).map(&:strip) + raw[key] = value + end + + result = { "type" => "ups" } + result["model"] = raw["MODEL"] if raw["MODEL"] + result["status"] = raw["STATUS"] if raw["STATUS"] + + if raw["BCHARGE"] + result["charge"] = raw["BCHARGE"].to_f + end + if raw["TIMELEFT"] + result["timeleft"] = raw["TIMELEFT"].to_f + end + if raw["LINEV"] + result["line_voltage"] = raw["LINEV"].to_f + end + if raw["LOADPCT"] + result["load"] = raw["LOADPCT"].to_f + end + if raw["NOMBATTV"] + result["battery_voltage_nom"] = raw["NOMBATTV"].to_f + end + if raw["BATTV"] + result["battery_voltage"] = raw["BATTV"].to_f + end + + result +end + def handle_info(conn) info = [] @@ -195,6 +232,10 @@ def handle_info(conn) info.concat(read_filesystems) info.concat(read_virtual_machines) + if (ups = read_ups) + info << ups + end + if conn conn.puts JSON.generate(info) else diff --git a/cgi-bin/malp.rb b/cgi-bin/malp.rb index 0240915..fe7e89a 100755 --- a/cgi-bin/malp.rb +++ b/cgi-bin/malp.rb @@ -346,6 +346,56 @@ if cgi.params.key?("content") html << %() end + upses = info.select { |e| e["type"] == "ups" } + 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 << <<~HTML +
+
+ #{model} + #{online ? "Online" : CGI.escapeHTML(status)} +
+ #{stats_block} +
+ HTML + html << %(
) + end + cgi.out("type" => "text/html", "charset" => "UTF-8") do html end