Windows Agent (v1.1.0):
- Software-Inventar aus Windows-Registry (64-bit + 32-bit)
Sammelt Name, Version, Hersteller, InstallDate aller installierten Apps
Filtert SystemComponent-Einträge heraus, sortiert alphabetisch
Funktioniert unter SYSTEM-Account (kein winget nötig)
- Version auf 1.1.0 gehoben
Backend:
- customer_wau_rules Tabelle (Migration automatisch)
- WAU-Regeln pro Kunde (allow/block, winget-ID + Anzeigename)
- GET/POST/DELETE /api/v1/customers/{id}/wau/rules
- GET /api/v1/customers/{id}/wau/inventory (SQL-Aggregation aus Agenten-Daten)
- GET /api/v1/customers/{id}/wau/included (Plaintext für WAU)
- GET /api/v1/customers/{id}/wau/excluded (Plaintext für WAU)
Frontend:
- Kunden-Seite: Tab-Navigation (Agents | API-Keys | WAU)
- WAU-Tab pro Kunde: Allowlist + Blocklist mit Inline-Assign aus Inventar
Allow/Block-Buttons direkt in Inventar-Zeile, winget-ID vorgeschlagen
Installationsbefehl mit externer Backend-URL aus Systemeinstellungen
- Windows-Panel Software-Tab: Installiert-Liste aus Registry-Daten
- Windows-Panel WAU-Tab: Status (installiert/Task/letzter Lauf)
WAU installieren via MSI-Download + msiexec (synchron)
Jetzt ausführen, Log anzeigen
Gewünschten Zustand herstellen (Allowlist auf Client installieren)
136 lines
4.0 KiB
Go
136 lines
4.0 KiB
Go
package db
|
|
|
|
import (
|
|
"database/sql"
|
|
"encoding/json"
|
|
|
|
"github.com/cynfo/rmm-backend/models"
|
|
)
|
|
|
|
// ListWAURules gibt alle WAU-Regeln eines Kunden zurück
|
|
func (d *Database) ListWAURules(customerID int) ([]models.WAURule, error) {
|
|
rows, err := d.db.Query(`
|
|
SELECT id, customer_id, winget_id, display_name, rule_type, created_at
|
|
FROM customer_wau_rules
|
|
WHERE customer_id = $1
|
|
ORDER BY rule_type, lower(display_name)
|
|
`, customerID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var rules []models.WAURule
|
|
for rows.Next() {
|
|
var r models.WAURule
|
|
if err := rows.Scan(&r.ID, &r.CustomerID, &r.WingetID, &r.DisplayName, &r.RuleType, &r.CreatedAt); err != nil {
|
|
return nil, err
|
|
}
|
|
rules = append(rules, r)
|
|
}
|
|
if rules == nil {
|
|
rules = []models.WAURule{}
|
|
}
|
|
return rules, rows.Err()
|
|
}
|
|
|
|
// AddWAURule fügt eine neue Regel hinzu (upsert auf winget_id)
|
|
func (d *Database) AddWAURule(customerID int, wingetID, displayName, ruleType string) (*models.WAURule, error) {
|
|
var r models.WAURule
|
|
err := d.db.QueryRow(`
|
|
INSERT INTO customer_wau_rules (customer_id, winget_id, display_name, rule_type)
|
|
VALUES ($1, $2, $3, $4)
|
|
ON CONFLICT (customer_id, winget_id) DO UPDATE
|
|
SET display_name = EXCLUDED.display_name,
|
|
rule_type = EXCLUDED.rule_type
|
|
RETURNING id, customer_id, winget_id, display_name, rule_type, created_at
|
|
`, customerID, wingetID, displayName, ruleType).Scan(
|
|
&r.ID, &r.CustomerID, &r.WingetID, &r.DisplayName, &r.RuleType, &r.CreatedAt,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &r, nil
|
|
}
|
|
|
|
// DeleteWAURule löscht eine Regel (per ID, gehört zu customerID)
|
|
func (d *Database) DeleteWAURule(customerID, ruleID int) (bool, error) {
|
|
res, err := d.db.Exec(`
|
|
DELETE FROM customer_wau_rules WHERE id = $1 AND customer_id = $2
|
|
`, ruleID, customerID)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
n, _ := res.RowsAffected()
|
|
return n > 0, nil
|
|
}
|
|
|
|
// GetWAUList gibt alle winget-IDs eines bestimmten Typs zurück (für den Plaintext-Endpoint)
|
|
func (d *Database) GetWAUList(customerID int, ruleType string) ([]string, error) {
|
|
rows, err := d.db.Query(`
|
|
SELECT winget_id FROM customer_wau_rules
|
|
WHERE customer_id = $1 AND rule_type = $2
|
|
ORDER BY lower(winget_id)
|
|
`, customerID, ruleType)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var ids []string
|
|
for rows.Next() {
|
|
var id string
|
|
if err := rows.Scan(&id); err != nil {
|
|
return nil, err
|
|
}
|
|
ids = append(ids, id)
|
|
}
|
|
return ids, rows.Err()
|
|
}
|
|
|
|
// GetWAUSoftwareInventory aggregiert installierte Software aller Windows-Agents eines Kunden
|
|
func (d *Database) GetWAUSoftwareInventory(customerID int) ([]models.WAUSoftwareEntry, error) {
|
|
rows, err := d.db.Query(`
|
|
SELECT
|
|
app->>'name' AS name,
|
|
COALESCE(app->>'publisher', '') AS publisher,
|
|
COUNT(DISTINCT sd.agent_id) AS device_count,
|
|
jsonb_agg(DISTINCT app->>'version') FILTER (WHERE (app->>'version') != '') AS versions
|
|
FROM agents a
|
|
JOIN system_data sd ON sd.agent_id = a.id
|
|
CROSS JOIN jsonb_array_elements(sd.data_json->'windows'->'installed_software') AS app
|
|
WHERE a.customer_id = $1
|
|
AND sd.data_json ? 'windows'
|
|
AND jsonb_typeof(sd.data_json->'windows'->'installed_software') = 'array'
|
|
AND (app->>'name') IS NOT NULL
|
|
AND (app->>'name') != ''
|
|
GROUP BY app->>'name', app->>'publisher'
|
|
ORDER BY lower(app->>'name')
|
|
`, customerID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var entries []models.WAUSoftwareEntry
|
|
for rows.Next() {
|
|
var e models.WAUSoftwareEntry
|
|
var versionsJSON sql.NullString
|
|
|
|
if err := rows.Scan(&e.Name, &e.Publisher, &e.DeviceCount, &versionsJSON); err != nil {
|
|
return nil, err
|
|
}
|
|
if versionsJSON.Valid && versionsJSON.String != "" && versionsJSON.String != "null" {
|
|
json.Unmarshal([]byte(versionsJSON.String), &e.Versions)
|
|
}
|
|
if e.Versions == nil {
|
|
e.Versions = []string{}
|
|
}
|
|
entries = append(entries, e)
|
|
}
|
|
if entries == nil {
|
|
entries = []models.WAUSoftwareEntry{}
|
|
}
|
|
return entries, rows.Err()
|
|
}
|