This commit is contained in:
henrygd
2026-04-19 21:44:21 -04:00
parent ea19ef6334
commit e71ffd4d2a
15 changed files with 136 additions and 113 deletions

View File

@@ -48,7 +48,7 @@ type Agent struct {
keys []gossh.PublicKey // SSH public keys keys []gossh.PublicKey // SSH public keys
smartManager *SmartManager // Manages SMART data smartManager *SmartManager // Manages SMART data
systemdManager *systemdManager // Manages systemd services systemdManager *systemdManager // Manages systemd services
probeManager *ProbeManager // Manages network probes probeManager *ProbeManager // Manages network probes
} }
// NewAgent creates a new agent with the given data directory for persisting data. // NewAgent creates a new agent with the given data directory for persisting data.
@@ -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())

View File

@@ -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)
}

View File

@@ -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
} }
} }

View File

@@ -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...
) )

View File

@@ -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
} }

View File

@@ -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"
) )
@@ -174,9 +175,10 @@ type Details struct {
// Final data structure to return to the hub // Final data structure to return to the hub
type CombinedData struct { type CombinedData struct {
Stats Stats `json:"stats" cbor:"0,keyasint"` Stats Stats `json:"stats" cbor:"0,keyasint"`
Info Info `json:"info" cbor:"1,keyasint"` Info Info `json:"info" cbor:"1,keyasint"`
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"`
} }

View File

@@ -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.

View File

@@ -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))

View File

@@ -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)
} // }
} // }

View File

@@ -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)
} }

View File

@@ -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 {

View File

@@ -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

View File

@@ -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>
) )

View File

@@ -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">

View File

@@ -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)