Updated agent to include ping in heartbeat.

This commit is contained in:
2026-02-03 15:24:11 +00:00
parent bebaaf1367
commit 70d0e808f8
7 changed files with 83 additions and 14 deletions

View File

@@ -144,11 +144,16 @@ func shipLogs(ctx context.Context, apiClient *client.Client, cfg *config.Config)
func reportHost(ctx context.Context, apiClient *client.Client, cfg *config.Config) error { func reportHost(ctx context.Context, apiClient *client.Client, cfg *config.Config) error {
info := host.Detect() info := host.Detect()
var pingPtr *int
if pingMs, err := apiClient.Ping(ctx); err == nil {
pingPtr = &pingMs
}
return retry(ctx, []time.Duration{250 * time.Millisecond, time.Second, 2 * time.Second}, func() error { return retry(ctx, []time.Duration{250 * time.Millisecond, time.Second, 2 * time.Second}, func() error {
return apiClient.UpdateHost(ctx, cfg.ServerID, client.HeartbeatRequest{ return apiClient.UpdateHost(ctx, cfg.ServerID, client.HeartbeatRequest{
Host: info.Hostname, Host: info.Hostname,
IPv4: info.IPv4, IPv4: info.IPv4,
IPv6: info.IPv6, IPv6: info.IPv6,
PingMs: pingPtr,
}) })
}) })
} }

View File

@@ -2,7 +2,7 @@
"server_url": "https://keywarden.dev.ntbx.io/api/v1", "server_url": "https://keywarden.dev.ntbx.io/api/v1",
"server_id": "4", "server_id": "4",
"server_ca_path": "", "server_ca_path": "",
"sync_interval_seconds": 30, "sync_interval_seconds": 5,
"log_batch_size": 500, "log_batch_size": 500,
"state_dir": "/var/lib/keywarden-agent", "state_dir": "/var/lib/keywarden-agent",
"account_policy": { "account_policy": {

View File

@@ -8,7 +8,9 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"net"
"net/http" "net/http"
"net/url"
"os" "os"
"strings" "strings"
"time" "time"
@@ -22,6 +24,10 @@ const defaultTimeout = 15 * time.Second
type Client struct { type Client struct {
baseURL string baseURL string
http *http.Client http *http.Client
tlsCfg *tls.Config
scheme string
host string
addr string
} }
func New(cfg *config.Config) (*Client, error) { func New(cfg *config.Config) (*Client, error) {
@@ -62,7 +68,36 @@ func New(cfg *config.Config) (*Client, error) {
Transport: transport, Transport: transport,
} }
return &Client{baseURL: baseURL, http: httpClient}, nil parsed, err := url.Parse(baseURL)
if err != nil {
return nil, fmt.Errorf("parse server url: %w", err)
}
if parsed.Host == "" {
return nil, errors.New("server url missing host")
}
scheme := parsed.Scheme
if scheme == "" {
scheme = "https"
}
host := parsed.Hostname()
port := parsed.Port()
if port == "" {
if scheme == "http" {
port = "80"
} else {
port = "443"
}
}
addr := net.JoinHostPort(host, port)
return &Client{
baseURL: baseURL,
http: httpClient,
tlsCfg: tlsConfig,
scheme: scheme,
host: host,
addr: addr,
}, nil
} }
type EnrollRequest struct { type EnrollRequest struct {
@@ -293,9 +328,10 @@ func (c *Client) SendLogBatch(ctx context.Context, serverID string, payload []by
} }
type HeartbeatRequest struct { type HeartbeatRequest struct {
Host string `json:"host,omitempty"` Host string `json:"host,omitempty"`
IPv4 string `json:"ipv4,omitempty"` IPv4 string `json:"ipv4,omitempty"`
IPv6 string `json:"ipv6,omitempty"` IPv6 string `json:"ipv6,omitempty"`
PingMs *int `json:"ping_ms,omitempty"`
} }
func (c *Client) UpdateHost(ctx context.Context, serverID string, reqBody HeartbeatRequest) error { func (c *Client) UpdateHost(ctx context.Context, serverID string, reqBody HeartbeatRequest) error {
@@ -318,3 +354,29 @@ func (c *Client) UpdateHost(ctx context.Context, serverID string, reqBody Heartb
} }
return nil return nil
} }
func (c *Client) Ping(ctx context.Context) (int, error) {
if c.addr == "" {
return 0, errors.New("server address not configured")
}
start := time.Now()
dialer := &net.Dialer{Timeout: defaultTimeout}
if c.scheme == "http" {
conn, err := dialer.DialContext(ctx, "tcp", c.addr)
if err != nil {
return 0, err
}
_ = conn.Close()
return int(time.Since(start).Milliseconds()), nil
}
cfg := c.tlsCfg.Clone()
if cfg.ServerName == "" && c.host != "" {
cfg.ServerName = c.host
}
conn, err := tls.DialWithDialer(dialer, "tcp", c.addr, cfg)
if err != nil {
return 0, err
}
_ = conn.Close()
return int(time.Since(start).Milliseconds()), nil
}

Binary file not shown.

View File

@@ -70,7 +70,7 @@
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<span class="font-semibold text-gray-500">Last heartbeat</span> <span class="font-semibold text-gray-500">Last heartbeat</span>
<span class="font-medium text-gray-900"> <span class="font-medium text-gray-900">
{% if server_status.heartbeat_at %}{{ server_status.heartbeat_at|date:"M j, Y H:i" }}{% else %}—{% endif %} {% if server_status.heartbeat_at %}{{ server_status.heartbeat_at|date:"M j, Y H:i:s" }}{% else %}—{% endif %}
</span> </span>
</div> </div>
</div> </div>

View File

@@ -69,7 +69,7 @@
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<span class="font-semibold text-gray-500">Last heartbeat</span> <span class="font-semibold text-gray-500">Last heartbeat</span>
<span class="font-medium text-gray-900"> <span class="font-medium text-gray-900">
{% if item.status.heartbeat_at %}{{ item.status.heartbeat_at|date:"M j, Y H:i" }}{% else %}—{% endif %} {% if item.status.heartbeat_at %}{{ item.status.heartbeat_at|date:"M j, Y H:i:s" }}{% else %}—{% endif %}
</span> </span>
</div> </div>
</div> </div>

View File

@@ -189,16 +189,18 @@ def _format_age_short(delta: timedelta) -> str:
if seconds < 60: if seconds < 60:
return f"{seconds}s" return f"{seconds}s"
minutes = seconds // 60 minutes = seconds // 60
rem_seconds = seconds % 60
if minutes < 60: if minutes < 60:
return f"{minutes}m" return f"{minutes}m {rem_seconds}s"
hours = minutes // 60 hours = minutes // 60
rem_minutes = minutes % 60
if hours < 48: if hours < 48:
return f"{hours}h" return f"{hours}h {rem_minutes}m {rem_seconds}s"
days = hours // 24 days = hours // 24
if days < 14: if days < 14:
return f"{days}d" return f"{days}d {hours % 24}h"
weeks = days // 7 weeks = days // 7
return f"{weeks}w" return f"{weeks}w {days % 7}d"
def _build_server_status(server: Server, now): def _build_server_status(server: Server, now):