feat(apps): per-app deploy/activity timeline
Every deploy across all four source kinds now writes a workload-scoped
event via a shared plugin.EmitDeployEvent helper (replacing the inline
emit duplicated in static/dockerfile, standardizing static's metadata
key site_id->workload_id, and adding emission to image+compose which
were silent). New indexed event_log.workload_id column, EventLogFilter
.WorkloadID, and GET /api/workloads/{id}/events (id pinned from path).
Frontend: a forge "Activity" panel on /apps/[id] reusing EventLogEntry,
live SSE prepend filtered by workload_id, load-more pagination, an
All/Errors severity filter, and a shared toEventLogEntry mapper. en/ru
i18n parity.
Security: compose's failure status emits a generic reason instead of raw
`docker compose up` output, which can echo app secrets and egresses to
operator webhooks (NotificationURL + event-trigger actions); full detail
stays only in the returned error. Rune-safe 256-rune status cap.
Reviewed: go + typescript APPROVE; security HIGH fixed.
This commit is contained in:
@@ -2,14 +2,12 @@ package static
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/moby/moby/api/types/mount"
|
||||
@@ -543,11 +541,13 @@ func dispatchSiteNotification(deps plugin.Deps, w plugin.Workload, domain, statu
|
||||
})
|
||||
}
|
||||
|
||||
// publishEvent emits a static_site_status event on the bus AND
|
||||
// persists an event_log row so the dashboard's audit trail picks it
|
||||
// up. Message format ("Static site \"%s\": %s") is preserved verbatim
|
||||
// from the legacy Manager.publishEvent so log scrapers and operator-
|
||||
// configured event triggers keep matching.
|
||||
// publishEvent emits a static_site_status event on the bus (drives the
|
||||
// dashboard's per-site status pill) AND records a workload-scoped deploy
|
||||
// event in the audit log. The audit InsertEvent + bus publish is
|
||||
// centralised in plugin.EmitDeployEvent so the message/metadata shape and
|
||||
// per-workload timeline are identical across all source kinds. This
|
||||
// standardises the metadata key from the legacy "site_id" to "workload_id";
|
||||
// no consumer reads the old key (verified repo-wide).
|
||||
func publishEvent(deps plugin.Deps, w plugin.Workload, status string) {
|
||||
deps.Events.Publish(events.Event{
|
||||
Type: events.EventStaticSiteStatus,
|
||||
@@ -558,47 +558,7 @@ func publishEvent(deps plugin.Deps, w plugin.Workload, status string) {
|
||||
},
|
||||
})
|
||||
|
||||
severity := "info"
|
||||
if strings.HasPrefix(status, "failed") {
|
||||
severity = "error"
|
||||
}
|
||||
message := fmt.Sprintf("Static site %q: %s", w.Name, status)
|
||||
|
||||
// Build metadata via json.Marshal so workload names containing
|
||||
// quotes or backslashes don't produce invalid JSON for downstream
|
||||
// log-scan consumers.
|
||||
metaBytes, err := json.Marshal(map[string]string{
|
||||
"site_id": w.ID,
|
||||
"site_name": w.Name,
|
||||
"status": status,
|
||||
})
|
||||
if err != nil {
|
||||
slog.Error("static site: marshal event metadata", "error", err)
|
||||
metaBytes = []byte("{}")
|
||||
}
|
||||
metadata := string(metaBytes)
|
||||
|
||||
evt, err := deps.Store.InsertEvent(store.EventLog{
|
||||
Source: "static_site",
|
||||
Severity: severity,
|
||||
Message: message,
|
||||
Metadata: metadata,
|
||||
})
|
||||
if err != nil {
|
||||
slog.Error("static site: failed to persist event log", "error", err)
|
||||
return
|
||||
}
|
||||
deps.Events.Publish(events.Event{
|
||||
Type: events.EventLog,
|
||||
Payload: events.EventLogPayload{
|
||||
ID: evt.ID,
|
||||
Source: "static_site",
|
||||
Severity: severity,
|
||||
Message: message,
|
||||
Metadata: metadata,
|
||||
CreatedAt: evt.CreatedAt,
|
||||
},
|
||||
})
|
||||
plugin.EmitDeployEvent(deps, w, "static_site", status)
|
||||
}
|
||||
|
||||
// removeContainerByName mirrors the legacy helper: enumerate Docker's
|
||||
|
||||
Reference in New Issue
Block a user