diff --git a/assets/page.erb b/assets/page.erb index 61172d3..ba37226 100644 --- a/assets/page.erb +++ b/assets/page.erb @@ -367,6 +367,12 @@ margin: 1.5rem 0 0.5rem; grid-column: 1 / -1; } + + .stack { + display: flex; + flex-direction: column; + gap: 1rem; + } diff --git a/bin/malpd b/bin/malpd index 7ff6fba..1914929 100755 --- a/bin/malpd +++ b/bin/malpd @@ -63,6 +63,41 @@ def read_smart(name) result end +def read_filesystems + out = `findmnt -J -b -o SOURCE,TARGET,FSTYPE,SIZE,USED 2>/dev/null` + return [] if out.empty? + data = JSON.parse(out) rescue nil + return [] unless data + + skip_fstypes = %w[tmpfs devtmpfs efivarfs] + result = [] + seen = {} + stack = Array(data["filesystems"]).dup + until stack.empty? + node = stack.shift + stack.concat(node["children"]) if node["children"] + source = node["source"].to_s + fstype = node["fstype"].to_s + target = node["target"].to_s + next unless source.start_with?("/dev/") + next if skip_fstypes.include?(fstype) + size = node["size"].to_i + next if size <= 0 + key = [source, target] + next if seen[key] + seen[key] = true + result << { + "type" => "filesystem", + "source" => source, + "mount" => target, + "fstype" => fstype, + "size" => size, + "used" => node["used"].to_i + } + end + result +end + def handle_info(conn) info = [] @@ -89,6 +124,8 @@ def handle_info(conn) info << entry end + info.concat(read_filesystems) + if conn conn.puts JSON.generate(info) else diff --git a/cgi-bin/malp.rb b/cgi-bin/malp.rb index 588526b..e6e2aad 100755 --- a/cgi-bin/malp.rb +++ b/cgi-bin/malp.rb @@ -7,6 +7,18 @@ require "json" require "securerandom" require "socket" +def human_capacity(bytes) + units = ["B", "KB", "MB", "GB", "TB", "PB"] + i = 0 + size = bytes.to_f + while size >= 1000 && i < units.length - 1 + size /= 1000 + i += 1 + end + precision = i <= 3 ? 0 : 1 + "%g %s" % [size.round(precision), units[i]] +end + ASSETS_DIR = File.join(__dir__, "../assets") DATA_DIR = File.join(__dir__, "../data") SESSIONS_FILE = File.join(DATA_DIR, "sessions.txt") @@ -145,9 +157,14 @@ if cgi.params.key?("content") stats end - info.each do |entry| - case entry["type"] - when "drive" + drives = info.select { |e| e["type"] == "drive" } + filesystems = info.select { |e| e["type"] == "filesystem" } + + if drives.any? + html << %(
) + html << %(
Storage Drives
) + html << %(
) + drives.each do |entry| dt = entry["drivetype"] name = CGI.escapeHTML(entry["name"].to_s) model = CGI.escapeHTML(entry["model"]) @@ -169,7 +186,7 @@ if cgi.params.key?("content") title = name.empty? ? model : "#{name} ยท #{model}" html << <<~HTML -
+
#{title} #{capacity} #{dt.upcase}
@@ -177,6 +194,42 @@ if cgi.params.key?("content")
HTML end + html << %(
) + html << %(
) + end + + if filesystems.any? + html << %(
) + html << %() + html << %(
) + filesystems.each do |entry| + mount = CGI.escapeHTML(entry["mount"].to_s) + source = CGI.escapeHTML(entry["source"].to_s) + fstype = CGI.escapeHTML(entry["fstype"].to_s) + size_b = entry["size"].to_i + used_b = entry["used"].to_i + pct_f = size_b > 0 ? (used_b * 100.0 / size_b) : 0.0 + pct = pct_f.round + cls = pct_f >= 90 ? "bad" : pct_f >= 75 ? "warn" : "ok" + used_h = CGI.escapeHTML(human_capacity(used_b)) + size_h = CGI.escapeHTML(human_capacity(size_b)) + + html << <<~HTML +
+
+ #{mount} #{fstype} + #{pct}% +
+
+ #{source} + #{used_h} / #{size_h} +
+
+
+ HTML + end + html << %(
) + html << %(
) end cgi.out("type" => "text/html", "charset" => "UTF-8") do