Files
nmon/internal/api/interfaces.go

81 lines
2.9 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"`
Description string `json:"description" doc:"Operator-owned port description (CSV import); discovery never overwrites it."`
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, ifalias and description, 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, ifalias or description."`
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, Description: r.Description,
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
}