|
|
|
|
@ -124,7 +124,7 @@ func (h *Handler) ListContainers(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
IPAddress string `json:"ip_address"`
|
|
|
|
|
Container *agentv1.ContainerInfo `json:"container"`
|
|
|
|
|
}
|
|
|
|
|
var out []containerDTO
|
|
|
|
|
out := make([]containerDTO, 0)
|
|
|
|
|
for _, agent := range h.registry.List() {
|
|
|
|
|
for _, c := range agent.Containers {
|
|
|
|
|
out = append(out, containerDTO{
|
|
|
|
|
@ -193,7 +193,7 @@ func (h *Handler) ListImages(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
CreatedAt int64 `json:"created_at"`
|
|
|
|
|
IsOrphan bool `json:"is_orphan"`
|
|
|
|
|
}
|
|
|
|
|
var out []imageDTO
|
|
|
|
|
out := make([]imageDTO, 0)
|
|
|
|
|
for _, agent := range h.registry.List() {
|
|
|
|
|
for _, img := range agent.Images {
|
|
|
|
|
out = append(out, imageDTO{
|
|
|
|
|
@ -247,7 +247,7 @@ func (h *Handler) ListVolumes(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
Mountpoint string `json:"mountpoint"`
|
|
|
|
|
IsOrphan bool `json:"is_orphan"`
|
|
|
|
|
}
|
|
|
|
|
var out []volumeDTO
|
|
|
|
|
out := make([]volumeDTO, 0)
|
|
|
|
|
for _, agent := range h.registry.List() {
|
|
|
|
|
for _, vol := range agent.Volumes {
|
|
|
|
|
out = append(out, volumeDTO{
|
|
|
|
|
@ -301,7 +301,7 @@ func (h *Handler) ListNetworks(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
Scope string `json:"scope"`
|
|
|
|
|
IsOrphan bool `json:"is_orphan"`
|
|
|
|
|
}
|
|
|
|
|
var out []networkDTO
|
|
|
|
|
out := make([]networkDTO, 0)
|
|
|
|
|
for _, agent := range h.registry.List() {
|
|
|
|
|
for _, net := range agent.Networks {
|
|
|
|
|
out = append(out, networkDTO{
|
|
|
|
|
@ -753,6 +753,129 @@ func (h *Handler) UpdateNow(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
jsonOK(w, map[string]string{"command_id": cmdID})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Stats ─────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
type processDTO struct {
|
|
|
|
|
Pid uint32 `json:"pid"`
|
|
|
|
|
Name string `json:"name"`
|
|
|
|
|
Cmd string `json:"cmd"`
|
|
|
|
|
CpuPct float64 `json:"cpu_pct"`
|
|
|
|
|
MemRss uint64 `json:"mem_rss"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type netIfaceDTO struct {
|
|
|
|
|
Name string `json:"name"`
|
|
|
|
|
BytesRecv uint64 `json:"bytes_recv"`
|
|
|
|
|
BytesSent uint64 `json:"bytes_sent"`
|
|
|
|
|
BytesRecvRate uint64 `json:"bytes_recv_rate"`
|
|
|
|
|
BytesSentRate uint64 `json:"bytes_sent_rate"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type diskDTO struct {
|
|
|
|
|
Path string `json:"path"`
|
|
|
|
|
Total uint64 `json:"total"`
|
|
|
|
|
Used uint64 `json:"used"`
|
|
|
|
|
Free uint64 `json:"free"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type statsSnapshotDTO struct {
|
|
|
|
|
CpuPct float64 `json:"cpu_pct"`
|
|
|
|
|
CpuPerCore []float64 `json:"cpu_per_core"`
|
|
|
|
|
MemTotal uint64 `json:"mem_total"`
|
|
|
|
|
MemUsed uint64 `json:"mem_used"`
|
|
|
|
|
MemAvailable uint64 `json:"mem_available"`
|
|
|
|
|
NetInterfaces []netIfaceDTO `json:"net_interfaces"`
|
|
|
|
|
Processes []processDTO `json:"processes"`
|
|
|
|
|
Disks []diskDTO `json:"disks"`
|
|
|
|
|
Timestamp int64 `json:"timestamp"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func protoStatsToDTO(s *agentv1.StatsSnapshot) *statsSnapshotDTO {
|
|
|
|
|
if s == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
dto := &statsSnapshotDTO{
|
|
|
|
|
CpuPct: s.CpuPct,
|
|
|
|
|
CpuPerCore: s.CpuPerCore,
|
|
|
|
|
MemTotal: s.MemTotal,
|
|
|
|
|
MemUsed: s.MemUsed,
|
|
|
|
|
MemAvailable: s.MemAvailable,
|
|
|
|
|
Timestamp: s.Timestamp,
|
|
|
|
|
}
|
|
|
|
|
if dto.CpuPerCore == nil {
|
|
|
|
|
dto.CpuPerCore = []float64{}
|
|
|
|
|
}
|
|
|
|
|
for _, iface := range s.NetInterfaces {
|
|
|
|
|
dto.NetInterfaces = append(dto.NetInterfaces, netIfaceDTO{
|
|
|
|
|
Name: iface.Name,
|
|
|
|
|
BytesRecv: iface.BytesRecv,
|
|
|
|
|
BytesSent: iface.BytesSent,
|
|
|
|
|
BytesRecvRate: iface.BytesRecvRate,
|
|
|
|
|
BytesSentRate: iface.BytesSentRate,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
if dto.NetInterfaces == nil {
|
|
|
|
|
dto.NetInterfaces = []netIfaceDTO{}
|
|
|
|
|
}
|
|
|
|
|
for _, p := range s.Processes {
|
|
|
|
|
dto.Processes = append(dto.Processes, processDTO{
|
|
|
|
|
Pid: p.Pid,
|
|
|
|
|
Name: p.Name,
|
|
|
|
|
Cmd: p.Cmd,
|
|
|
|
|
CpuPct: p.CpuPct,
|
|
|
|
|
MemRss: p.MemRss,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
if dto.Processes == nil {
|
|
|
|
|
dto.Processes = []processDTO{}
|
|
|
|
|
}
|
|
|
|
|
for _, d := range s.Disks {
|
|
|
|
|
dto.Disks = append(dto.Disks, diskDTO{
|
|
|
|
|
Path: d.Path,
|
|
|
|
|
Total: d.Total,
|
|
|
|
|
Used: d.Used,
|
|
|
|
|
Free: d.Free,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
if dto.Disks == nil {
|
|
|
|
|
dto.Disks = []diskDTO{}
|
|
|
|
|
}
|
|
|
|
|
return dto
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ListStats handles GET /api/v1/stats — returns system stats for all connected agents.
|
|
|
|
|
func (h *Handler) ListStats(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
type statsDTO struct {
|
|
|
|
|
AgentID string `json:"agent_id"`
|
|
|
|
|
Hostname string `json:"hostname"`
|
|
|
|
|
Alias string `json:"alias"`
|
|
|
|
|
IPAddress string `json:"ip_address"`
|
|
|
|
|
Stats *statsSnapshotDTO `json:"stats"`
|
|
|
|
|
}
|
|
|
|
|
out := make([]statsDTO, 0)
|
|
|
|
|
for _, agent := range h.registry.List() {
|
|
|
|
|
out = append(out, statsDTO{
|
|
|
|
|
AgentID: agent.ID,
|
|
|
|
|
Hostname: agent.Hostname,
|
|
|
|
|
Alias: agent.Alias,
|
|
|
|
|
IPAddress: agent.IPAddress,
|
|
|
|
|
Stats: protoStatsToDTO(agent.Stats),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
jsonOK(w, out)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GetAgentStats handles GET /api/v1/agents/{agentID}/stats — returns stats for a single agent.
|
|
|
|
|
func (h *Handler) GetAgentStats(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
agentID := chi.URLParam(r, "agentID")
|
|
|
|
|
state, ok := h.registry.Get(agentID)
|
|
|
|
|
if !ok {
|
|
|
|
|
http.Error(w, "agent not connected", http.StatusNotFound)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
jsonOK(w, protoStatsToDTO(state.Stats))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func jsonOK(w http.ResponseWriter, v any) {
|
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
|
json.NewEncoder(w).Encode(v)
|
|
|
|
|
|