mirror of
https://github.com/henrygd/beszel.git
synced 2026-04-21 04:01:50 +02:00
updates
This commit is contained in:
@@ -182,6 +182,11 @@ func (a *Agent) gatherStats(options common.DataRequestOptions) *system.CombinedD
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if a.probeManager != nil {
|
||||||
|
data.Probes = a.probeManager.GetResults()
|
||||||
|
slog.Debug("Probes", "data", data.Probes)
|
||||||
|
}
|
||||||
|
|
||||||
// skip updating systemd services if cache time is not the default 60sec interval
|
// skip updating systemd services if cache time is not the default 60sec interval
|
||||||
if a.systemdManager != nil && cacheTimeMs == defaultDataCacheTimeMs {
|
if a.systemdManager != nil && cacheTimeMs == defaultDataCacheTimeMs {
|
||||||
totalCount := uint16(a.systemdManager.getServiceStatsCount())
|
totalCount := uint16(a.systemdManager.getServiceStatsCount())
|
||||||
|
|||||||
@@ -53,7 +53,6 @@ func NewHandlerRegistry() *HandlerRegistry {
|
|||||||
registry.Register(common.GetSmartData, &GetSmartDataHandler{})
|
registry.Register(common.GetSmartData, &GetSmartDataHandler{})
|
||||||
registry.Register(common.GetSystemdInfo, &GetSystemdInfoHandler{})
|
registry.Register(common.GetSystemdInfo, &GetSystemdInfoHandler{})
|
||||||
registry.Register(common.SyncNetworkProbes, &SyncNetworkProbesHandler{})
|
registry.Register(common.SyncNetworkProbes, &SyncNetworkProbesHandler{})
|
||||||
registry.Register(common.GetNetworkProbeResults, &GetNetworkProbeResultsHandler{})
|
|
||||||
|
|
||||||
return registry
|
return registry
|
||||||
}
|
}
|
||||||
@@ -222,14 +221,3 @@ func (h *SyncNetworkProbesHandler) Handle(hctx *HandlerContext) error {
|
|||||||
slog.Info("network probes synced", "count", len(configs))
|
slog.Info("network probes synced", "count", len(configs))
|
||||||
return hctx.SendResponse("ok", hctx.RequestID)
|
return hctx.SendResponse("ok", hctx.RequestID)
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
// GetNetworkProbeResultsHandler handles probe results request from hub
|
|
||||||
type GetNetworkProbeResultsHandler struct{}
|
|
||||||
|
|
||||||
func (h *GetNetworkProbeResultsHandler) Handle(hctx *HandlerContext) error {
|
|
||||||
results := hctx.Agent.probeManager.GetResults()
|
|
||||||
return hctx.SendResponse(results, hctx.RequestID)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -120,10 +120,10 @@ func (pm *ProbeManager) GetResults() map[string]probe.Result {
|
|||||||
}
|
}
|
||||||
|
|
||||||
results[key] = probe.Result{
|
results[key] = probe.Result{
|
||||||
AvgMs: avg,
|
avg, // average latency in ms
|
||||||
MinMs: math.Round(minMs*100) / 100,
|
math.Round(minMs*100) / 100, // min latency in ms
|
||||||
MaxMs: math.Round(maxMs*100) / 100,
|
math.Round(maxMs*100) / 100, // max latency in ms
|
||||||
Loss: math.Round(float64(lossCount)/float64(count)*10000) / 100,
|
math.Round(float64(lossCount)/float64(count)*10000) / 100, // packet loss percentage
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,8 +24,6 @@ const (
|
|||||||
GetSystemdInfo
|
GetSystemdInfo
|
||||||
// Sync network probe configuration to agent
|
// Sync network probe configuration to agent
|
||||||
SyncNetworkProbes
|
SyncNetworkProbes
|
||||||
// Request network probe results from agent
|
|
||||||
GetNetworkProbeResults
|
|
||||||
// Add new actions here...
|
// Add new actions here...
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package probe
|
package probe
|
||||||
|
|
||||||
import "fmt"
|
import "strconv"
|
||||||
|
|
||||||
// Config defines a network probe task sent from hub to agent.
|
// Config defines a network probe task sent from hub to agent.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
@@ -11,18 +11,21 @@ type Config struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Result holds aggregated probe results for a single target.
|
// Result holds aggregated probe results for a single target.
|
||||||
type Result struct {
|
//
|
||||||
AvgMs float64 `cbor:"0,keyasint" json:"avg"`
|
// 0: avg latency in ms
|
||||||
MinMs float64 `cbor:"1,keyasint" json:"min"`
|
//
|
||||||
MaxMs float64 `cbor:"2,keyasint" json:"max"`
|
// 1: min latency in ms
|
||||||
Loss float64 `cbor:"3,keyasint" json:"loss"` // packet loss %
|
//
|
||||||
}
|
// 2: max latency in ms
|
||||||
|
//
|
||||||
|
// 3: packet loss percentage (0-100)
|
||||||
|
type Result []float64
|
||||||
|
|
||||||
// Key returns the map key used for this probe config (e.g. "icmp:1.1.1.1", "tcp:host:443", "http:https://example.com").
|
// Key returns the map key used for this probe config (e.g. "icmp:1.1.1.1", "tcp:host:443", "http:https://example.com").
|
||||||
func (c Config) Key() string {
|
func (c Config) Key() string {
|
||||||
switch c.Protocol {
|
switch c.Protocol {
|
||||||
case "tcp":
|
case "tcp":
|
||||||
return c.Protocol + ":" + c.Target + ":" + fmt.Sprintf("%d", c.Port)
|
return c.Protocol + ":" + c.Target + ":" + strconv.FormatUint(uint64(c.Port), 10)
|
||||||
default:
|
default:
|
||||||
return c.Protocol + ":" + c.Target
|
return c.Protocol + ":" + c.Target
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/henrygd/beszel/internal/entities/container"
|
"github.com/henrygd/beszel/internal/entities/container"
|
||||||
|
"github.com/henrygd/beszel/internal/entities/probe"
|
||||||
"github.com/henrygd/beszel/internal/entities/systemd"
|
"github.com/henrygd/beszel/internal/entities/systemd"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -179,4 +180,5 @@ type CombinedData struct {
|
|||||||
Containers []*container.Stats `json:"container" cbor:"2,keyasint"`
|
Containers []*container.Stats `json:"container" cbor:"2,keyasint"`
|
||||||
SystemdServices []*systemd.Service `json:"systemd,omitempty" cbor:"3,keyasint,omitempty"`
|
SystemdServices []*systemd.Service `json:"systemd,omitempty" cbor:"3,keyasint,omitempty"`
|
||||||
Details *Details `cbor:"4,keyasint,omitempty"`
|
Details *Details `cbor:"4,keyasint,omitempty"`
|
||||||
|
Probes map[string]probe.Result `cbor:"5,keyasint,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,27 @@
|
|||||||
package hub
|
package hub
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/henrygd/beszel/internal/entities/probe"
|
||||||
|
"github.com/henrygd/beszel/internal/hub/systems"
|
||||||
"github.com/pocketbase/pocketbase/core"
|
"github.com/pocketbase/pocketbase/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
func bindNetworkProbesEvents(h *Hub) {
|
func bindNetworkProbesEvents(h *Hub) {
|
||||||
|
// on create, make sure the id is set to a stable hash
|
||||||
|
h.OnRecordCreate("network_probes").BindFunc(func(e *core.RecordEvent) error {
|
||||||
|
systemID := e.Record.GetString("system")
|
||||||
|
config := &probe.Config{
|
||||||
|
Target: e.Record.GetString("target"),
|
||||||
|
Protocol: e.Record.GetString("protocol"),
|
||||||
|
Port: uint16(e.Record.GetInt("port")),
|
||||||
|
Interval: uint16(e.Record.GetInt("interval")),
|
||||||
|
}
|
||||||
|
key := config.Key()
|
||||||
|
id := systems.MakeStableHashId(systemID, key)
|
||||||
|
e.Record.Set("id", id)
|
||||||
|
return e.Next()
|
||||||
|
})
|
||||||
|
|
||||||
// sync probe to agent on creation
|
// sync probe to agent on creation
|
||||||
h.OnRecordAfterCreateSuccess("network_probes").BindFunc(func(e *core.RecordEvent) error {
|
h.OnRecordAfterCreateSuccess("network_probes").BindFunc(func(e *core.RecordEvent) error {
|
||||||
systemID := e.Record.GetString("system")
|
systemID := e.Record.GetString("system")
|
||||||
@@ -17,6 +34,7 @@ func bindNetworkProbesEvents(h *Hub) {
|
|||||||
h.syncProbesToAgent(systemID)
|
h.syncProbesToAgent(systemID)
|
||||||
return e.Next()
|
return e.Next()
|
||||||
})
|
})
|
||||||
|
// TODO: if enabled changes, sync to agent
|
||||||
}
|
}
|
||||||
|
|
||||||
// syncProbesToAgent fetches enabled probes for a system and sends them to the agent.
|
// syncProbesToAgent fetches enabled probes for a system and sends them to the agent.
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import (
|
|||||||
"github.com/henrygd/beszel/internal/hub/ws"
|
"github.com/henrygd/beszel/internal/hub/ws"
|
||||||
|
|
||||||
"github.com/henrygd/beszel/internal/entities/container"
|
"github.com/henrygd/beszel/internal/entities/container"
|
||||||
|
"github.com/henrygd/beszel/internal/entities/probe"
|
||||||
"github.com/henrygd/beszel/internal/entities/smart"
|
"github.com/henrygd/beszel/internal/entities/smart"
|
||||||
"github.com/henrygd/beszel/internal/entities/system"
|
"github.com/henrygd/beszel/internal/entities/system"
|
||||||
"github.com/henrygd/beszel/internal/entities/systemd"
|
"github.com/henrygd/beszel/internal/entities/systemd"
|
||||||
@@ -29,6 +30,7 @@ import (
|
|||||||
"github.com/lxzan/gws"
|
"github.com/lxzan/gws"
|
||||||
"github.com/pocketbase/dbx"
|
"github.com/pocketbase/dbx"
|
||||||
"github.com/pocketbase/pocketbase/core"
|
"github.com/pocketbase/pocketbase/core"
|
||||||
|
"github.com/pocketbase/pocketbase/tools/types"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -167,11 +169,6 @@ func (sys *System) update() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch and save network probe results
|
|
||||||
if sys.hasEnabledProbes() {
|
|
||||||
go sys.fetchAndSaveProbeResults()
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,6 +240,12 @@ func (sys *System) createRecords(data *system.CombinedData) (*core.Record, error
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if data.Probes != nil {
|
||||||
|
if err := updateNetworkProbesRecords(txApp, data.Probes, sys.Id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// update system record (do this last because it triggers alerts and we need above records to be inserted first)
|
// update system record (do this last because it triggers alerts and we need above records to be inserted first)
|
||||||
systemRecord.Set("status", up)
|
systemRecord.Set("status", up)
|
||||||
systemRecord.Set("info", data.Info)
|
systemRecord.Set("info", data.Info)
|
||||||
@@ -294,7 +297,7 @@ func createSystemdStatsRecords(app core.App, data []*systemd.Service, systemId s
|
|||||||
for i, service := range data {
|
for i, service := range data {
|
||||||
suffix := fmt.Sprintf("%d", i)
|
suffix := fmt.Sprintf("%d", i)
|
||||||
valueStrings = append(valueStrings, fmt.Sprintf("({:id%[1]s}, {:system}, {:name%[1]s}, {:state%[1]s}, {:sub%[1]s}, {:cpu%[1]s}, {:cpuPeak%[1]s}, {:memory%[1]s}, {:memPeak%[1]s}, {:updated})", suffix))
|
valueStrings = append(valueStrings, fmt.Sprintf("({:id%[1]s}, {:system}, {:name%[1]s}, {:state%[1]s}, {:sub%[1]s}, {:cpu%[1]s}, {:cpuPeak%[1]s}, {:memory%[1]s}, {:memPeak%[1]s}, {:updated})", suffix))
|
||||||
params["id"+suffix] = makeStableHashId(systemId, service.Name)
|
params["id"+suffix] = MakeStableHashId(systemId, service.Name)
|
||||||
params["name"+suffix] = service.Name
|
params["name"+suffix] = service.Name
|
||||||
params["state"+suffix] = service.State
|
params["state"+suffix] = service.State
|
||||||
params["sub"+suffix] = service.Sub
|
params["sub"+suffix] = service.Sub
|
||||||
@@ -311,6 +314,28 @@ func createSystemdStatsRecords(app core.App, data []*systemd.Service, systemId s
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateNetworkProbesRecords(app core.App, data map[string]probe.Result, systemId string) error {
|
||||||
|
if len(data) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
collectionName := "network_probes"
|
||||||
|
for key := range data {
|
||||||
|
probe := data[key]
|
||||||
|
id := MakeStableHashId(systemId, key)
|
||||||
|
params := dbx.Params{
|
||||||
|
// "system": systemId,
|
||||||
|
"latency": probe[0],
|
||||||
|
"loss": probe[3],
|
||||||
|
"updated": time.Now().UTC().Format(types.DefaultDateLayout),
|
||||||
|
}
|
||||||
|
_, err := app.DB().Update(collectionName, params, dbx.HashExp{"id": id}).Execute()
|
||||||
|
if err != nil {
|
||||||
|
app.Logger().Warn("Failed to update network probe record", "system", systemId, "probe", key, "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// createContainerRecords creates container records
|
// createContainerRecords creates container records
|
||||||
func createContainerRecords(app core.App, data []*container.Stats, systemId string) error {
|
func createContainerRecords(app core.App, data []*container.Stats, systemId string) error {
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
@@ -545,7 +570,7 @@ func (sys *System) FetchSmartDataFromAgent() (map[string]smart.SmartData, error)
|
|||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeStableHashId(strings ...string) string {
|
func MakeStableHashId(strings ...string) string {
|
||||||
hash := fnv.New32a()
|
hash := fnv.New32a()
|
||||||
for _, str := range strings {
|
for _, str := range strings {
|
||||||
hash.Write([]byte(str))
|
hash.Write([]byte(str))
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ import (
|
|||||||
|
|
||||||
"github.com/henrygd/beszel/internal/common"
|
"github.com/henrygd/beszel/internal/common"
|
||||||
"github.com/henrygd/beszel/internal/entities/probe"
|
"github.com/henrygd/beszel/internal/entities/probe"
|
||||||
"github.com/pocketbase/dbx"
|
|
||||||
"github.com/pocketbase/pocketbase/core"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// SyncNetworkProbes sends probe configurations to the agent.
|
// SyncNetworkProbes sends probe configurations to the agent.
|
||||||
@@ -19,41 +17,41 @@ func (sys *System) SyncNetworkProbes(configs []probe.Config) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FetchNetworkProbeResults fetches probe results from the agent.
|
// FetchNetworkProbeResults fetches probe results from the agent.
|
||||||
func (sys *System) FetchNetworkProbeResults() (map[string]probe.Result, error) {
|
// func (sys *System) FetchNetworkProbeResults() (map[string]probe.Result, error) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
// ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
defer cancel()
|
// defer cancel()
|
||||||
var results map[string]probe.Result
|
// var results map[string]probe.Result
|
||||||
err := sys.request(ctx, common.GetNetworkProbeResults, nil, &results)
|
// err := sys.request(ctx, common.GetNetworkProbeResults, nil, &results)
|
||||||
return results, err
|
// return results, err
|
||||||
}
|
// }
|
||||||
|
|
||||||
// hasEnabledProbes returns true if this system has any enabled network probes.
|
// hasEnabledProbes returns true if this system has any enabled network probes.
|
||||||
func (sys *System) hasEnabledProbes() bool {
|
// func (sys *System) hasEnabledProbes() bool {
|
||||||
count, err := sys.manager.hub.CountRecords("network_probes",
|
// count, err := sys.manager.hub.CountRecords("network_probes",
|
||||||
dbx.NewExp("system = {:system} AND enabled = true", dbx.Params{"system": sys.Id}))
|
// dbx.NewExp("system = {:system} AND enabled = true", dbx.Params{"system": sys.Id}))
|
||||||
return err == nil && count > 0
|
// return err == nil && count > 0
|
||||||
}
|
// }
|
||||||
|
|
||||||
// fetchAndSaveProbeResults fetches probe results and saves them to the database.
|
// fetchAndSaveProbeResults fetches probe results and saves them to the database.
|
||||||
func (sys *System) fetchAndSaveProbeResults() {
|
// func (sys *System) fetchAndSaveProbeResults() {
|
||||||
hub := sys.manager.hub
|
// hub := sys.manager.hub
|
||||||
|
|
||||||
results, err := sys.FetchNetworkProbeResults()
|
// results, err := sys.FetchNetworkProbeResults()
|
||||||
if err != nil || len(results) == 0 {
|
// if err != nil || len(results) == 0 {
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
collection, err := hub.FindCachedCollectionByNameOrId("network_probe_stats")
|
// collection, err := hub.FindCachedCollectionByNameOrId("network_probe_stats")
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
record := core.NewRecord(collection)
|
// record := core.NewRecord(collection)
|
||||||
record.Set("system", sys.Id)
|
// record.Set("system", sys.Id)
|
||||||
record.Set("stats", results)
|
// record.Set("stats", results)
|
||||||
record.Set("type", "1m")
|
// record.Set("type", "1m")
|
||||||
|
|
||||||
if err := hub.SaveNoValidate(record); err != nil {
|
// if err := hub.SaveNoValidate(record); err != nil {
|
||||||
hub.Logger().Warn("failed to save probe stats", "system", sys.Id, "err", err)
|
// hub.Logger().Warn("failed to save probe stats", "system", sys.Id, "err", err)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -7,21 +7,10 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/henrygd/beszel/internal/common"
|
"github.com/henrygd/beszel/internal/common"
|
||||||
"github.com/henrygd/beszel/internal/entities/container"
|
|
||||||
"github.com/henrygd/beszel/internal/entities/probe"
|
|
||||||
"github.com/henrygd/beszel/internal/entities/system"
|
|
||||||
"github.com/pocketbase/pocketbase/core"
|
"github.com/pocketbase/pocketbase/core"
|
||||||
"github.com/pocketbase/pocketbase/tools/subscriptions"
|
"github.com/pocketbase/pocketbase/tools/subscriptions"
|
||||||
)
|
)
|
||||||
|
|
||||||
// realtimePayload wraps system data with optional network probe results for realtime broadcast.
|
|
||||||
type realtimePayload struct {
|
|
||||||
Stats system.Stats `json:"stats"`
|
|
||||||
Info system.Info `json:"info"`
|
|
||||||
Containers []*container.Stats `json:"container"`
|
|
||||||
Probes map[string]probe.Result `json:"probes,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type subscriptionInfo struct {
|
type subscriptionInfo struct {
|
||||||
subscription string
|
subscription string
|
||||||
connectedClients uint8
|
connectedClients uint8
|
||||||
@@ -153,27 +142,16 @@ func (sm *SystemManager) startRealtimeWorker() {
|
|||||||
// fetchRealtimeDataAndNotify fetches realtime data for all active subscriptions and notifies the clients.
|
// fetchRealtimeDataAndNotify fetches realtime data for all active subscriptions and notifies the clients.
|
||||||
func (sm *SystemManager) fetchRealtimeDataAndNotify() {
|
func (sm *SystemManager) fetchRealtimeDataAndNotify() {
|
||||||
for systemId, info := range activeSubscriptions {
|
for systemId, info := range activeSubscriptions {
|
||||||
sys, err := sm.GetSystem(systemId)
|
system, err := sm.GetSystem(systemId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
data, err := sys.fetchDataFromAgent(common.DataRequestOptions{CacheTimeMs: 1000})
|
data, err := system.fetchDataFromAgent(common.DataRequestOptions{CacheTimeMs: 1000})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
payload := realtimePayload{
|
bytes, err := json.Marshal(data)
|
||||||
Stats: data.Stats,
|
|
||||||
Info: data.Info,
|
|
||||||
Containers: data.Containers,
|
|
||||||
}
|
|
||||||
// Fetch network probe results (lightweight in-memory read on agent)
|
|
||||||
if sys.hasEnabledProbes() {
|
|
||||||
if probes, err := sys.FetchNetworkProbeResults(); err == nil && len(probes) > 0 {
|
|
||||||
payload.Probes = probes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bytes, err := json.Marshal(payload)
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
notify(sm.hub, info.subscription, bytes)
|
notify(sm.hub, info.subscription, bytes)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ func (sys *System) saveSmartDevices(smartData map[string]smart.SmartData) error
|
|||||||
|
|
||||||
func (sys *System) upsertSmartDeviceRecord(collection *core.Collection, deviceKey string, device smart.SmartData) error {
|
func (sys *System) upsertSmartDeviceRecord(collection *core.Collection, deviceKey string, device smart.SmartData) error {
|
||||||
hub := sys.manager.hub
|
hub := sys.manager.hub
|
||||||
recordID := makeStableHashId(sys.Id, deviceKey)
|
recordID := MakeStableHashId(sys.Id, deviceKey)
|
||||||
|
|
||||||
record, err := hub.FindRecordById(collection, recordID)
|
record, err := hub.FindRecordById(collection, recordID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ func TestGetSystemdServiceId(t *testing.T) {
|
|||||||
serviceName := "nginx.service"
|
serviceName := "nginx.service"
|
||||||
|
|
||||||
// Call multiple times and ensure same result
|
// Call multiple times and ensure same result
|
||||||
id1 := makeStableHashId(systemId, serviceName)
|
id1 := MakeStableHashId(systemId, serviceName)
|
||||||
id2 := makeStableHashId(systemId, serviceName)
|
id2 := MakeStableHashId(systemId, serviceName)
|
||||||
id3 := makeStableHashId(systemId, serviceName)
|
id3 := MakeStableHashId(systemId, serviceName)
|
||||||
|
|
||||||
assert.Equal(t, id1, id2)
|
assert.Equal(t, id1, id2)
|
||||||
assert.Equal(t, id2, id3)
|
assert.Equal(t, id2, id3)
|
||||||
@@ -29,10 +29,10 @@ func TestGetSystemdServiceId(t *testing.T) {
|
|||||||
serviceName1 := "nginx.service"
|
serviceName1 := "nginx.service"
|
||||||
serviceName2 := "apache.service"
|
serviceName2 := "apache.service"
|
||||||
|
|
||||||
id1 := makeStableHashId(systemId1, serviceName1)
|
id1 := MakeStableHashId(systemId1, serviceName1)
|
||||||
id2 := makeStableHashId(systemId2, serviceName1)
|
id2 := MakeStableHashId(systemId2, serviceName1)
|
||||||
id3 := makeStableHashId(systemId1, serviceName2)
|
id3 := MakeStableHashId(systemId1, serviceName2)
|
||||||
id4 := makeStableHashId(systemId2, serviceName2)
|
id4 := MakeStableHashId(systemId2, serviceName2)
|
||||||
|
|
||||||
// All IDs should be different
|
// All IDs should be different
|
||||||
assert.NotEqual(t, id1, id2)
|
assert.NotEqual(t, id1, id2)
|
||||||
@@ -56,14 +56,14 @@ func TestGetSystemdServiceId(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
id := makeStableHashId(tc.systemId, tc.serviceName)
|
id := MakeStableHashId(tc.systemId, tc.serviceName)
|
||||||
// FNV-32 produces 8 hex characters
|
// FNV-32 produces 8 hex characters
|
||||||
assert.Len(t, id, 8, "ID should be 8 characters for systemId='%s', serviceName='%s'", tc.systemId, tc.serviceName)
|
assert.Len(t, id, 8, "ID should be 8 characters for systemId='%s', serviceName='%s'", tc.systemId, tc.serviceName)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("hexadecimal output", func(t *testing.T) {
|
t.Run("hexadecimal output", func(t *testing.T) {
|
||||||
id := makeStableHashId("test-system", "test-service")
|
id := MakeStableHashId("test-system", "test-service")
|
||||||
assert.NotEmpty(t, id)
|
assert.NotEmpty(t, id)
|
||||||
|
|
||||||
// Should only contain hexadecimal characters
|
// Should only contain hexadecimal characters
|
||||||
|
|||||||
@@ -110,9 +110,16 @@ export function getProbeColumns(longestName = 0, longestTarget = 0): ColumnDef<N
|
|||||||
if (val === undefined) {
|
if (val === undefined) {
|
||||||
return <span className="ms-1.5 text-muted-foreground">-</span>
|
return <span className="ms-1.5 text-muted-foreground">-</span>
|
||||||
}
|
}
|
||||||
|
let color = "bg-green-500"
|
||||||
|
if (val > 200) {
|
||||||
|
color = "bg-yellow-500"
|
||||||
|
}
|
||||||
|
if (!val || val > 2000) {
|
||||||
|
color = "bg-red-500"
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<span className="ms-1.5 tabular-nums flex gap-2 items-center">
|
<span className="ms-1.5 tabular-nums flex gap-2 items-center">
|
||||||
<span className={cn("shrink-0 size-2 rounded-full", val > 100 ? "bg-yellow-500" : "bg-green-500")} />
|
<span className={cn("shrink-0 size-2 rounded-full", color)} />
|
||||||
{decimalString(val, val < 100 ? 2 : 1).toLocaleString()} ms
|
{decimalString(val, val < 100 ? 2 : 1).toLocaleString()} ms
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
@@ -128,9 +135,13 @@ export function getProbeColumns(longestName = 0, longestTarget = 0): ColumnDef<N
|
|||||||
if (val === undefined) {
|
if (val === undefined) {
|
||||||
return <span className="ms-1.5 text-muted-foreground">-</span>
|
return <span className="ms-1.5 text-muted-foreground">-</span>
|
||||||
}
|
}
|
||||||
|
let color = "bg-green-500"
|
||||||
|
if (val > 0) {
|
||||||
|
color = val > 20 ? "bg-red-500" : "bg-yellow-500"
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<span className="ms-1.5 tabular-nums flex gap-2 items-center">
|
<span className="ms-1.5 tabular-nums flex gap-2 items-center">
|
||||||
<span className={cn("shrink-0 size-2 rounded-full", val > 0 ? "bg-yellow-500" : "bg-green-500")} />
|
<span className={cn("shrink-0 size-2 rounded-full", color)} />
|
||||||
{val}%
|
{val}%
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import { cn, getVisualStringWidth, useBrowserStorage } from "@/lib/utils"
|
|||||||
import type { NetworkProbeRecord } from "@/types"
|
import type { NetworkProbeRecord } from "@/types"
|
||||||
import { AddProbeDialog } from "./probe-dialog"
|
import { AddProbeDialog } from "./probe-dialog"
|
||||||
|
|
||||||
const NETWORK_PROBE_FIELDS = "id,name,system,target,protocol,port,interval,enabled,updated"
|
const NETWORK_PROBE_FIELDS = "id,name,system,target,protocol,port,interval,latency,loss,enabled,updated"
|
||||||
|
|
||||||
export default function NetworkProbesTableNew({ systemId }: { systemId?: string }) {
|
export default function NetworkProbesTableNew({ systemId }: { systemId?: string }) {
|
||||||
const loadTime = Date.now()
|
const loadTime = Date.now()
|
||||||
@@ -174,10 +174,6 @@ export default function NetworkProbesTableNew({ systemId }: { systemId?: string
|
|||||||
const rows = table.getRowModel().rows
|
const rows = table.getRowModel().rows
|
||||||
const visibleColumns = table.getVisibleLeafColumns()
|
const visibleColumns = table.getVisibleLeafColumns()
|
||||||
|
|
||||||
if (!data.length && !globalFilter) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="@container w-full px-3 py-5 sm:py-6 sm:px-6">
|
<Card className="@container w-full px-3 py-5 sm:py-6 sm:px-6">
|
||||||
<CardHeader className="p-0 mb-3 sm:mb-4">
|
<CardHeader className="p-0 mb-3 sm:mb-4">
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ export function AddProbeDialog({ systemId }: { systemId: string }) {
|
|||||||
protocol,
|
protocol,
|
||||||
port: protocol === "tcp" ? Number(port) : 0,
|
port: protocol === "tcp" ? Number(port) : 0,
|
||||||
interval: Number(probeInterval),
|
interval: Number(probeInterval),
|
||||||
|
enabled: true,
|
||||||
})
|
})
|
||||||
resetForm()
|
resetForm()
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
|
|||||||
Reference in New Issue
Block a user