mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-05-18 23:41:06 +00:00
parent
eac7dfe9ef
commit
2b9b1ec573
3 changed files with 77 additions and 10 deletions
|
|
@ -767,6 +767,12 @@ stopping at disconnected toast notifications.
|
|||
That same runtime owner also defines the feature-default contract for TrueNAS:
|
||||
the API-backed integration is on by default, and `PULSE_ENABLE_TRUENAS` is an
|
||||
explicit opt-out switch rather than a required bootstrap toggle.
|
||||
That same TrueNAS monitoring boundary owns system identity compatibility for
|
||||
`/system/info`. `internal/truenas/client.go` must tolerate provider-version
|
||||
drift in non-identity display fields such as `buildtime`, including structured
|
||||
date/value wrappers, and still preserve the canonical hostname, version,
|
||||
machine ID, capacity, and poll-health path instead of failing connection tests
|
||||
or background refreshes during JSON decoding.
|
||||
That same monitoring boundary now also owns live TrueNAS disk temperatures.
|
||||
`internal/truenas/client.go` and `internal/truenas/provider.go` must ingest
|
||||
`disk.temperatures` from the TrueNAS API, fall back to modern
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@ func (c *Client) GetSystemInfo(ctx context.Context) (*SystemInfo, error) {
|
|||
machineID = strings.TrimSpace(response.Hostname)
|
||||
}
|
||||
|
||||
build := strings.TrimSpace(response.BuildTime)
|
||||
build := strings.TrimSpace(response.BuildTime.String())
|
||||
if build == "" {
|
||||
build = strings.TrimSpace(response.Version)
|
||||
}
|
||||
|
|
@ -2868,15 +2868,30 @@ func normalizeFingerprint(fingerprint string) (string, error) {
|
|||
}
|
||||
|
||||
type systemInfoResponse struct {
|
||||
Hostname string `json:"hostname"`
|
||||
Version string `json:"version"`
|
||||
BuildTime string `json:"buildtime"`
|
||||
UptimeSeconds int64 `json:"uptime_seconds"`
|
||||
SystemSerial string `json:"system_serial"`
|
||||
SystemVendor string `json:"system_manufacturer"`
|
||||
Cores int `json:"cores"`
|
||||
PhysicalCores int `json:"physical_cores"`
|
||||
Physmem int64 `json:"physmem"`
|
||||
Hostname string `json:"hostname"`
|
||||
Version string `json:"version"`
|
||||
BuildTime textResponseField `json:"buildtime"`
|
||||
UptimeSeconds int64 `json:"uptime_seconds"`
|
||||
SystemSerial string `json:"system_serial"`
|
||||
SystemVendor string `json:"system_manufacturer"`
|
||||
Cores int `json:"cores"`
|
||||
PhysicalCores int `json:"physical_cores"`
|
||||
Physmem int64 `json:"physmem"`
|
||||
}
|
||||
|
||||
type textResponseField string
|
||||
|
||||
func (f textResponseField) String() string {
|
||||
return string(f)
|
||||
}
|
||||
|
||||
func (f *textResponseField) UnmarshalJSON(data []byte) error {
|
||||
value, err := parseTextResponseField(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*f = textResponseField(value)
|
||||
return nil
|
||||
}
|
||||
|
||||
type poolResponse struct {
|
||||
|
|
@ -3068,6 +3083,34 @@ func readStringAny(record map[string]any, keys ...string) string {
|
|||
return ""
|
||||
}
|
||||
|
||||
func parseTextResponseField(raw json.RawMessage) (string, error) {
|
||||
trimmed := bytes.TrimSpace(raw)
|
||||
if len(trimmed) == 0 || bytes.Equal(trimmed, []byte("null")) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
var decoded any
|
||||
decoder := json.NewDecoder(bytes.NewReader(trimmed))
|
||||
decoder.UseNumber()
|
||||
if err := decoder.Decode(&decoded); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return textFromDecodedAny(decoded), nil
|
||||
}
|
||||
|
||||
func textFromDecodedAny(value any) string {
|
||||
switch typed := value.(type) {
|
||||
case string:
|
||||
return strings.TrimSpace(typed)
|
||||
case json.Number:
|
||||
return strings.TrimSpace(typed.String())
|
||||
case map[string]any:
|
||||
return readStringAny(typed, "rawvalue", "value", "parsed", "$date", "date", "datetime", "text", "string")
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func readStringSliceAny(record map[string]any, keys ...string) []string {
|
||||
if record == nil {
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -172,6 +172,24 @@ func TestClientAuthHeaderBasic(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGetSystemInfoAcceptsStructuredBuildTime(t *testing.T) {
|
||||
server := newMockServer(t, map[string]apiResponse{
|
||||
"/api/v2.0/system/info": {
|
||||
body: `{"hostname":"nas","version":"TrueNAS-SCALE-25.10.3.1","buildtime":{"$date":"2026-05-14T18:24:01+02:00"},"uptime_seconds":1}`,
|
||||
},
|
||||
}, nil)
|
||||
t.Cleanup(server.Close)
|
||||
|
||||
client := mustClientForServer(t, server.URL, ClientConfig{APIKey: "test-key"})
|
||||
system, err := client.GetSystemInfo(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("GetSystemInfo() error = %v", err)
|
||||
}
|
||||
if system.Build != "2026-05-14T18:24:01+02:00" {
|
||||
t.Fatalf("Build = %q, want structured buildtime date", system.Build)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTestConnectionSuccessAndFailure(t *testing.T) {
|
||||
successServer := newMockServer(t, map[string]apiResponse{
|
||||
"/api/v2.0/system/info": {body: `{"hostname":"nas","version":"v","buildtime":"b","uptime_seconds":1}`},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue