Files
nmon/internal/api/interfaces.go
jleung f85c3ef781
Some checks failed
ci / lint (push) Has been cancelled
ci / test (ubuntu-24.04-arm) (push) Has been cancelled
ci / test (ubuntu-latest) (push) Has been cancelled
ci / postgres (push) Has been cancelled
ci / openapi (push) Has been cancelled
M3: Go API per UI contract, embedded UI, openapi compat check, nmon-devtools, live gate + screenshots; M4 WP1 reachability and dependency suppression
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lx9FNLG2CqeBbVGGd9bFsP
2026-10-22 20:34:14 +00:00

79 lines
2.7 KiB
Go

package api
import (
"context"
"net/http"
"github.com/danielgtaylor/huma/v2"
"github.com/v10networks/nmon/internal/store"
)
// InterfaceHit is one interface search result with its device and live rates.
type InterfaceHit struct {
ID string `json:"id" doc:"Interface ULID."`
Device DeviceRef `json:"device"`
IfName string `json:"ifname"`
IfAlias string `json:"ifalias"`
IfDescr string `json:"ifdescr"`
OperStatus int64 `json:"oper_status"`
AdminStatus int64 `json:"admin_status"`
SpeedBPS int64 `json:"speed_bps" doc:"Link speed in bits per second (ifHighSpeed when set)."`
InBPS *float64 `json:"in_bps" nullable:"true"`
OutBPS *float64 `json:"out_bps" nullable:"true"`
}
// InterfaceList is a page of InterfaceHit.
type InterfaceList struct {
Items []InterfaceHit `json:"items"`
Page int64 `json:"page"`
PerPage int64 `json:"per_page"`
Total int64 `json:"total"`
}
func (s *server) registerInterfaces(api huma.API) {
huma.Register(api, huma.Operation{
OperationID: "list-interfaces", Method: http.MethodGet, Path: "/api/v1/interfaces", Tags: []string{"interfaces"},
Summary: "Search interfaces by name or alias",
Description: "q matches ifname and ifalias, case-insensitively, across all devices. Rates are null when unknown or unreliable.",
}, s.listInterfaces)
}
func (s *server) listInterfaces(ctx context.Context, in *struct {
Q string `query:"q" doc:"Substring of ifname or ifalias."`
PageParams
}) (*struct{ Body InterfaceList }, error) {
rows, total, err := s.Store.SearchInterfaceHits(ctx, in.Q, store.Page{Offset: in.offset(), Limit: in.limit()})
if err != nil {
return nil, s.storeErr(err)
}
out := InterfaceList{Items: make([]InterfaceHit, 0, len(rows)), Page: in.pageNo(), PerPage: in.limit(), Total: total}
var rates map[string]*ifRates
if len(rows) > 0 {
ids := make([]string, len(rows))
for i, r := range rows {
ids[i] = r.ID
}
rates, _ = s.liveRates(ctx, `iface=~`+quote(regexAlt(ids)), false)
}
for i := range rows {
r := &rows[i]
inB, outB := rates[r.ID].bps(&r.Interface)
out.Items = append(out.Items, InterfaceHit{
ID: r.ID, Device: DeviceRef{Slug: r.DeviceSlug, Name: r.DeviceName}, IfName: r.IfName, IfAlias: r.IfAlias,
IfDescr: r.IfDescr, OperStatus: r.OperStatus, AdminStatus: r.AdminStatus, SpeedBPS: speedBPS(&r.Interface),
InBPS: inB, OutBPS: outB,
})
}
return &struct{ Body InterfaceList }{out}, nil
}
// speedBPS is the link speed in bits per second: ifHighSpeed (Mbit/s) when the
// device reports it, since ifSpeed saturates at 4.29 Gbit/s.
func speedBPS(i *store.Interface) int64 {
if i.IfHighSpeed > 0 {
return i.IfHighSpeed * 1_000_000
}
return i.IfSpeed
}