Feature: Audit-Log - tracks all user actions with timestamps
This commit is contained in:
@@ -202,6 +202,20 @@ func (d *Database) migrate() error {
|
||||
uploaded_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
)`)
|
||||
|
||||
// Audit-Log Tabelle
|
||||
d.db.Exec(`CREATE TABLE IF NOT EXISTS audit_log (
|
||||
id SERIAL PRIMARY KEY,
|
||||
timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
user_name TEXT NOT NULL,
|
||||
action TEXT NOT NULL,
|
||||
target_type TEXT,
|
||||
target_id TEXT,
|
||||
target_name TEXT,
|
||||
details TEXT,
|
||||
source_ip TEXT
|
||||
)`)
|
||||
d.db.Exec(`CREATE INDEX IF NOT EXISTS idx_audit_log_timestamp ON audit_log(timestamp DESC)`)
|
||||
|
||||
// Retention Policy (90 Tage)
|
||||
d.setupRetention()
|
||||
|
||||
@@ -1108,6 +1122,81 @@ func (d *Database) UpdateAPIKeyLastUsed(key string) {
|
||||
}
|
||||
|
||||
// MigrateConfigAPIKeys migriert API-Keys aus der Config in die DB (einmalig)
|
||||
// --- Audit Log ---
|
||||
|
||||
func (d *Database) AddAuditLog(userName, action, targetType, targetID, targetName, details, sourceIP string) error {
|
||||
_, err := d.db.Exec(
|
||||
`INSERT INTO audit_log (user_name, action, target_type, target_id, target_name, details, source_ip) VALUES ($1, $2, $3, $4, $5, $6, $7)`,
|
||||
userName, action, targetType, targetID, targetName, details, sourceIP,
|
||||
)
|
||||
if err != nil {
|
||||
log.Printf("Audit-Log schreiben fehlgeschlagen: %v", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Database) GetAuditLogs(limit int, offset int, action string, agentID string) ([]models.AuditLogEntry, int, error) {
|
||||
if limit <= 0 {
|
||||
limit = 100
|
||||
}
|
||||
if limit > 500 {
|
||||
limit = 500
|
||||
}
|
||||
|
||||
where := []string{}
|
||||
args := []interface{}{}
|
||||
argIdx := 1
|
||||
|
||||
if action != "" {
|
||||
where = append(where, fmt.Sprintf("action = $%d", argIdx))
|
||||
args = append(args, action)
|
||||
argIdx++
|
||||
}
|
||||
if agentID != "" {
|
||||
where = append(where, fmt.Sprintf("target_id = $%d", argIdx))
|
||||
args = append(args, agentID)
|
||||
argIdx++
|
||||
}
|
||||
|
||||
whereClause := ""
|
||||
if len(where) > 0 {
|
||||
whereClause = "WHERE " + strings.Join(where, " AND ")
|
||||
}
|
||||
|
||||
// Total count
|
||||
var total int
|
||||
countQuery := "SELECT COUNT(*) FROM audit_log " + whereClause
|
||||
err := d.db.QueryRow(countQuery, args...).Scan(&total)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// Data
|
||||
query := fmt.Sprintf("SELECT id, timestamp, user_name, action, COALESCE(target_type,''), COALESCE(target_id,''), COALESCE(target_name,''), COALESCE(details,''), COALESCE(source_ip,'') FROM audit_log %s ORDER BY timestamp DESC LIMIT $%d OFFSET $%d", whereClause, argIdx, argIdx+1)
|
||||
args = append(args, limit, offset)
|
||||
|
||||
rows, err := d.db.Query(query, args...)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var entries []models.AuditLogEntry
|
||||
for rows.Next() {
|
||||
var e models.AuditLogEntry
|
||||
var ts time.Time
|
||||
if err := rows.Scan(&e.ID, &ts, &e.UserName, &e.Action, &e.TargetType, &e.TargetID, &e.TargetName, &e.Details, &e.SourceIP); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
e.Timestamp = ts.Format(time.RFC3339)
|
||||
entries = append(entries, e)
|
||||
}
|
||||
if entries == nil {
|
||||
entries = []models.AuditLogEntry{}
|
||||
}
|
||||
return entries, total, rows.Err()
|
||||
}
|
||||
|
||||
func (d *Database) MigrateConfigAPIKeys(configKeys []string) {
|
||||
for _, key := range configKeys {
|
||||
var exists bool
|
||||
|
||||
Reference in New Issue
Block a user