Chassis, power, and thermal

GET  /api/bng/hardware                                 read_only
POST /api/bng/hardware/test                            read_only

GET /hardware returns BMC-derived platform state via IPMI. If IPMI has not been
configured, the response is simply {"configured": false} and nothing further is
attempted. When configured:

{
  "configured": true,
  "psus": [
    { "name": "PSU1", "status": "ok", "watts": 412, "vin_v": 230.0,
      "iout_a": 1.8, "fan_rpm": 8400, "temp_c": 38,
      "metrics": [ { "key": "Vin", "value": 230.0, "unit": "Volts" } ] }
  ],
  "fans":  [ { "name": "FAN1", "rpm": 8600, "status": "ok", "unit": "RPM" } ],
  "temps": { "CPU0": 47, "CPU1": 46, "Inlet": 22 },
  "alerts": [
    { "id": "sensor:FAN2", "kind": "sensor_status", "severity": "critical",
      "sensor": "FAN2", "value": 0.0, "unit": "RPM",
      "summary": "FAN2 reported lcr",
      "detail": "ipmitool flagged FAN2 as lcr (value 0.0 RPM)." }
  ]
}

Three things make this more useful than a raw ipmitool scrape. Power supplies
are regrouped.
IPMI presents each PSU as several independent sensor rows
(PSU1_Vin, PSU1_Iout, PSU1_Pout, PSU1_FAN); QuantumTouch regroups them
into one record per supply with correctly-united readings and convenience
scalars. Firmware heterogeneity is absorbed. Modern BMCs emit a ten-column
sensor table; older ones emit a three-column variant with the unit embedded in
the sensor name. Both are parsed. Alerts track the BMC's own thresholds
rather than hardcoded limits — each reading is cross-checked against the
threshold columns the BMC itself reports, so alerting follows the platform's
configuration.

POST /hardware/test is a one-shot connectivity probe using operator-supplied
credentials, used by the settings UI's "Test connection" control. It accepts
bmc_host, username, password, and interface (one of lanplus, lan, or
open), and it always returns HTTP 200, discriminating on an ok boolean
and a reason string (invalid_request, ipmitool_missing, timeout,
ipmitool_failed). This is deliberate so a UI can render a specific message
rather than a generic network error. Passwords are redacted from any error text
before it is returned.

The open interface addresses the local BMC in-band via /dev/ipmi0 and needs
neither host nor credentials; lan and lanplus are remote and require both.