Add tiered LOTL mining onion and fleet recon so agents can fallback across execution tiers while operators see spread and vuln posture in Crucible. Includes triple-onion chain, spread cred graph, and full Go/TS/E2E test validation.
This commit is contained in:
85
server/internal/db/agents_list.go
Normal file
85
server/internal/db/agents_list.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"crypto-miner-server/internal/models"
|
||||
)
|
||||
|
||||
// AgentListFilter holds optional filters for paginated agent queries.
|
||||
type AgentListFilter struct {
|
||||
Limit int // 0 = no limit (return all matching rows)
|
||||
Offset int
|
||||
Status string // "online", "offline", or "" for any
|
||||
Subnet string // e.g. "10.0.0.x" — matched against agents.ip prefix
|
||||
}
|
||||
|
||||
// subnetToIPPrefix converts UI subnet labels to SQL LIKE patterns.
|
||||
func subnetToIPPrefix(subnet string) string {
|
||||
subnet = strings.TrimSpace(subnet)
|
||||
if subnet == "" {
|
||||
return ""
|
||||
}
|
||||
if strings.HasSuffix(subnet, ".x") {
|
||||
return strings.TrimSuffix(subnet, ".x") + ".%"
|
||||
}
|
||||
if strings.HasSuffix(subnet, "%") {
|
||||
return subnet
|
||||
}
|
||||
parts := strings.Split(subnet, ".")
|
||||
if len(parts) >= 3 {
|
||||
return fmt.Sprintf("%s.%s.%s.%%", parts[0], parts[1], parts[2])
|
||||
}
|
||||
return subnet + "%"
|
||||
}
|
||||
|
||||
func (d *Database) agentListWhere(f AgentListFilter) (clause string, args []interface{}) {
|
||||
var where []string
|
||||
if f.Status != "" {
|
||||
where = append(where, "status = ?")
|
||||
args = append(args, f.Status)
|
||||
}
|
||||
if prefix := subnetToIPPrefix(f.Subnet); prefix != "" {
|
||||
where = append(where, "ip LIKE ?")
|
||||
args = append(args, prefix)
|
||||
}
|
||||
if len(where) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
return " WHERE " + strings.Join(where, " AND "), args
|
||||
}
|
||||
|
||||
// ListAgentsFiltered returns agents matching optional status/subnet filters.
|
||||
// When Limit > 0, results are paginated with Offset.
|
||||
func (d *Database) ListAgentsFiltered(f AgentListFilter) ([]*models.Agent, error) {
|
||||
where, args := d.agentListWhere(f)
|
||||
query := `SELECT ` + agentSelectCols + ` FROM agents` + where + ` ORDER BY last_seen DESC`
|
||||
if f.Limit > 0 {
|
||||
query += ` LIMIT ? OFFSET ?`
|
||||
args = append(args, f.Limit, f.Offset)
|
||||
}
|
||||
rows, err := d.Query(query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var agents []*models.Agent
|
||||
for rows.Next() {
|
||||
a, err := d.scanAgent(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
agents = append(agents, a)
|
||||
}
|
||||
return agents, rows.Err()
|
||||
}
|
||||
|
||||
// CountAgentsFiltered returns the number of agents matching filter criteria (ignores Limit/Offset).
|
||||
func (d *Database) CountAgentsFiltered(f AgentListFilter) (int, error) {
|
||||
where, args := d.agentListWhere(f)
|
||||
var n int
|
||||
err := d.QueryRow(`SELECT COUNT(*) FROM agents`+where, args...).Scan(&n)
|
||||
return n, err
|
||||
}
|
||||
Reference in New Issue
Block a user