feat: Agent zeigt Warnung wenn er nicht den Kunden-Key nutzt
- last_api_key Spalte in agents, wird bei Heartbeat gesetzt - GetAgentsByCustomer liefert last_api_key mit - Frontend: rot + Warnsymbol wenn Key abweicht vom Kunden-Agent-Key - Grau wenn noch kein Heartbeat mit Key-Info
This commit is contained in:
parent
20a976767b
commit
4023a14fe1
@ -252,6 +252,11 @@ func (h *Handler) heartbeat(w http.ResponseWriter, r *http.Request) {
|
|||||||
h.db.UpdateAgentVersion(req.AgentID, req.AgentVersion)
|
h.db.UpdateAgentVersion(req.AgentID, req.AgentVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Genutzten API-Key speichern (aus Middleware-Kontext)
|
||||||
|
if keyInfo := GetAPIKeyFromContext(r); keyInfo != nil {
|
||||||
|
h.db.UpdateAgentAPIKey(req.AgentID, keyInfo.Key)
|
||||||
|
}
|
||||||
|
|
||||||
if err := h.db.SaveSystemData(req.AgentID, &req.SystemData); err != nil {
|
if err := h.db.SaveSystemData(req.AgentID, &req.SystemData); err != nil {
|
||||||
if strings.Contains(err.Error(), "nicht gefunden") {
|
if strings.Contains(err.Error(), "nicht gefunden") {
|
||||||
writeError(w, http.StatusNotFound, "Agent nicht gefunden")
|
writeError(w, http.StatusNotFound, "Agent nicht gefunden")
|
||||||
|
|||||||
@ -140,6 +140,7 @@ func (d *Database) migrate() error {
|
|||||||
d.db.Exec("ALTER TABLE agents ADD COLUMN IF NOT EXISTS agent_version TEXT NOT NULL DEFAULT ''")
|
d.db.Exec("ALTER TABLE agents ADD COLUMN IF NOT EXISTS agent_version TEXT NOT NULL DEFAULT ''")
|
||||||
d.db.Exec("ALTER TABLE agents ADD COLUMN IF NOT EXISTS update_requested BOOLEAN NOT NULL DEFAULT FALSE")
|
d.db.Exec("ALTER TABLE agents ADD COLUMN IF NOT EXISTS update_requested BOOLEAN NOT NULL DEFAULT FALSE")
|
||||||
d.db.Exec("ALTER TABLE agents ADD COLUMN IF NOT EXISTS platform TEXT NOT NULL DEFAULT 'freebsd'")
|
d.db.Exec("ALTER TABLE agents ADD COLUMN IF NOT EXISTS platform TEXT NOT NULL DEFAULT 'freebsd'")
|
||||||
|
d.db.Exec("ALTER TABLE agents ADD COLUMN IF NOT EXISTS last_api_key TEXT NOT NULL DEFAULT ''")
|
||||||
|
|
||||||
// Agent-Firmware Tabelle (Multi-Plattform)
|
// Agent-Firmware Tabelle (Multi-Plattform)
|
||||||
d.db.Exec(`CREATE TABLE IF NOT EXISTS agent_firmware (
|
d.db.Exec(`CREATE TABLE IF NOT EXISTS agent_firmware (
|
||||||
@ -627,6 +628,10 @@ func (d *Database) UpdateAgentVersion(id, version string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Database) UpdateAgentAPIKey(id, apiKey string) {
|
||||||
|
d.db.Exec("UPDATE agents SET last_api_key = $1 WHERE id = $2", apiKey, id)
|
||||||
|
}
|
||||||
|
|
||||||
// Update-Request Flag setzen/lesen/loeschen
|
// Update-Request Flag setzen/lesen/loeschen
|
||||||
func (d *Database) SetUpdateRequest(id string, requested bool) error {
|
func (d *Database) SetUpdateRequest(id string, requested bool) error {
|
||||||
_, err := d.db.Exec("UPDATE agents SET update_requested = $1 WHERE id = $2", requested, id)
|
_, err := d.db.Exec("UPDATE agents SET update_requested = $1 WHERE id = $2", requested, id)
|
||||||
@ -1099,7 +1104,7 @@ func (d *Database) UnassignAgentCustomer(agentID string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Database) GetAgentsByCustomer(customerID int) ([]models.AgentResponse, error) {
|
func (d *Database) GetAgentsByCustomer(customerID int) ([]models.AgentResponse, error) {
|
||||||
rows, err := d.db.Query("SELECT id, name, hostname, ip, opnsense_version, registered_at, last_heartbeat, customer_id, agent_version FROM agents WHERE customer_id = $1 ORDER BY name", customerID)
|
rows, err := d.db.Query("SELECT id, name, hostname, ip, opnsense_version, registered_at, last_heartbeat, customer_id, agent_version, last_api_key FROM agents WHERE customer_id = $1 ORDER BY name", customerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -1110,7 +1115,7 @@ func (d *Database) GetAgentsByCustomer(customerID int) ([]models.AgentResponse,
|
|||||||
var a models.Agent
|
var a models.Agent
|
||||||
var lastHB sql.NullTime
|
var lastHB sql.NullTime
|
||||||
var custID sql.NullInt64
|
var custID sql.NullInt64
|
||||||
if err := rows.Scan(&a.ID, &a.Name, &a.Hostname, &a.IP, &a.OPNsenseVersion, &a.RegisteredAt, &lastHB, &custID, &a.AgentVersion); err != nil {
|
if err := rows.Scan(&a.ID, &a.Name, &a.Hostname, &a.IP, &a.OPNsenseVersion, &a.RegisteredAt, &lastHB, &custID, &a.AgentVersion, &a.LastAPIKey); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if lastHB.Valid {
|
if lastHB.Valid {
|
||||||
|
|||||||
@ -15,6 +15,7 @@ type Agent struct {
|
|||||||
LastHeartbeat *time.Time `json:"last_heartbeat,omitempty"`
|
LastHeartbeat *time.Time `json:"last_heartbeat,omitempty"`
|
||||||
CustomerID *int `json:"customer_id,omitempty"`
|
CustomerID *int `json:"customer_id,omitempty"`
|
||||||
UpdateRequested bool `json:"update_requested"`
|
UpdateRequested bool `json:"update_requested"`
|
||||||
|
LastAPIKey string `json:"last_api_key,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AgentResponse erweitert Agent um den berechneten Status
|
// AgentResponse erweitert Agent um den berechneten Status
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import api from '../api/client'
|
import api from '../api/client'
|
||||||
import { copyToClipboard } from '../utils/clipboard'
|
import { copyToClipboard } from '../utils/clipboard'
|
||||||
import { Plus, Trash2, Edit2, X, Check, ChevronDown, ChevronRight, Copy, Key, Monitor } from 'lucide-react'
|
import { Plus, Trash2, Edit2, X, Check, ChevronDown, ChevronRight, Copy, Key, Monitor, AlertTriangle } from 'lucide-react'
|
||||||
|
|
||||||
const PERM_LABELS = {
|
const PERM_LABELS = {
|
||||||
agent: { label: 'Agent', color: 'text-blue-400 bg-blue-900/30 border-blue-700/50' },
|
agent: { label: 'Agent', color: 'text-blue-400 bg-blue-900/30 border-blue-700/50' },
|
||||||
@ -232,16 +232,32 @@ function CustomerKeyPanel({ customerId }) {
|
|||||||
{agents.length === 0 ? (
|
{agents.length === 0 ? (
|
||||||
<div className="text-xs text-gray-600">Keine Agents zugeordnet</div>
|
<div className="text-xs text-gray-600">Keine Agents zugeordnet</div>
|
||||||
) : (
|
) : (
|
||||||
agents.map((a) => (
|
agents.map((a) => {
|
||||||
<div key={a.id} className="flex items-center gap-2.5">
|
const wrongKey = agentKey && a.last_api_key && a.last_api_key !== agentKey.key
|
||||||
|
const unknownKey = agentKey && !a.last_api_key
|
||||||
|
return (
|
||||||
|
<div key={a.id} className={`flex items-center gap-2.5 rounded px-1.5 py-0.5 -mx-1.5 ${wrongKey ? 'bg-red-900/20' : ''}`}>
|
||||||
<span className={`w-1.5 h-1.5 rounded-full flex-shrink-0 ${
|
<span className={`w-1.5 h-1.5 rounded-full flex-shrink-0 ${
|
||||||
a.status === 'online' ? 'bg-green-400' :
|
a.status === 'online' ? 'bg-green-400' :
|
||||||
a.status === 'stale' ? 'bg-yellow-400' : 'bg-gray-600'
|
a.status === 'stale' ? 'bg-yellow-400' : 'bg-gray-600'
|
||||||
}`} />
|
}`} />
|
||||||
<span className="text-xs text-gray-300 flex-1 min-w-0 truncate">{a.name}</span>
|
<span className={`text-xs flex-1 min-w-0 truncate ${wrongKey ? 'text-red-300' : 'text-gray-300'}`}>
|
||||||
|
{a.name}
|
||||||
|
</span>
|
||||||
<span className="text-[10px] text-gray-600 font-mono flex-shrink-0">{a.agent_version || '—'}</span>
|
<span className="text-[10px] text-gray-600 font-mono flex-shrink-0">{a.agent_version || '—'}</span>
|
||||||
|
{wrongKey && (
|
||||||
|
<span title={`Falscher Key: ${a.last_api_key?.substring(0,8)}...`}>
|
||||||
|
<AlertTriangle className="w-3 h-3 text-red-400 flex-shrink-0" />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{unknownKey && (
|
||||||
|
<span title="Noch kein Heartbeat mit Key-Info">
|
||||||
|
<AlertTriangle className="w-3 h-3 text-gray-600 flex-shrink-0" />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
))
|
)
|
||||||
|
})
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user