Two defects found while running /wiki-compile, both from tooling assuming
a path instead of resolving it.
1. Plaintext Syncro API keys. Mike's and Howard's live PSA keys were
copy-pasted into three command files, a script, and two catalog docs --
despite both already being vaulted at msp-tools/syncro and
msp-tools/syncro-howard. Replaced with reads from the SOPS vault via a
new sourced helper, .claude/scripts/syncro-env.sh. Write paths fail
closed; read-only paths degrade to skipped enrichment rather than a
wrong key. Per-user mapping is unchanged, so Syncro attribution is too.
2. Hardcoded repo root. wiki-compile/wiki-lint/inject-standards and
gen_b64.py hardcoded D:/claudetools; this machine is C:/claudetools.
syncro.md also read ~/.claude/identity.json before the repo copy -- the
same bug that made remediation-tool's consent-audit report a fully
consented tenant as RED. Root now resolves from the script's own
location, with identity.json claudetools_root as the override.
get-identity.sh had a chicken-and-egg bug: it read ${CLAUDETOOLS_ROOT:-.}
but never set it, so every caller had to already know the root. It now
self-resolves and exports CLAUDETOOLS_ROOT + VAULT_ROOT.
Verified: all three command setup blocks authenticate against live Syncro;
get-identity.sh works from any cwd and honors a pre-set root; gps-rmm
autoenroll resolves its key from the vault. security-review: no findings.
NOTE: both keys remain valid in git history. Rotation in the Syncro portal
is the required follow-up -- this commit does not resolve that exposure.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
206 lines
9.6 KiB
Python
206 lines
9.6 KiB
Python
"""Generate base64 of the WatchdogAlertsSection component."""
|
|
import base64, os, sys
|
|
|
|
BT = chr(96)
|
|
BULLET = "\xb7"
|
|
|
|
lines = [
|
|
"\n",
|
|
"// WatchdogAlertsSection\n",
|
|
"\n",
|
|
'type WdogStatus = "active" | "acknowledged" | "resolved";\n',
|
|
"\n",
|
|
"function wdogStatus(alert: WatchdogAlert): WdogStatus {\n",
|
|
' if (alert.resolved_at) return "resolved";\n',
|
|
' if (alert.acknowledged_at) return "acknowledged";\n',
|
|
' return "active";\n',
|
|
"}\n",
|
|
"\n",
|
|
"function WatchdogAlertsSection() {\n",
|
|
" const queryClient = useQueryClient();\n",
|
|
" const { toast } = useToast();\n",
|
|
" const [showAll, setShowAll] = useState(false);\n",
|
|
" const [expandedLogId, setExpandedLogId] = useState<string | null>(null);\n",
|
|
"\n",
|
|
" const { data: allAlerts = [], isLoading } = useQuery({\n",
|
|
' queryKey: ["watchdog-alerts"],\n',
|
|
" queryFn: () => watchdogAlertsApi.list().then((r) => r.data),\n",
|
|
" refetchInterval: 30000,\n",
|
|
" });\n",
|
|
"\n",
|
|
" const alerts = showAll ? allAlerts : allAlerts.filter((a) => !a.resolved_at);\n",
|
|
"\n",
|
|
" const acknowledgeMutation = useMutation({\n",
|
|
" mutationFn: (id: string) => watchdogAlertsApi.acknowledge(id),\n",
|
|
" onSuccess: () => {\n",
|
|
' queryClient.invalidateQueries({ queryKey: ["watchdog-alerts"] });\n',
|
|
' toast({ type: "success", title: "Alert acknowledged" });\n',
|
|
" },\n",
|
|
" onError: (err: Error) =>\n",
|
|
' toast({ type: "error", title: "Could not acknowledge", message: err.message }),\n',
|
|
" });\n",
|
|
"\n",
|
|
" const resolveMutation = useMutation({\n",
|
|
" mutationFn: (id: string) => watchdogAlertsApi.resolve(id),\n",
|
|
" onSuccess: () => {\n",
|
|
' queryClient.invalidateQueries({ queryKey: ["watchdog-alerts"] });\n',
|
|
' toast({ type: "success", title: "Alert resolved" });\n',
|
|
" },\n",
|
|
" onError: (err: Error) =>\n",
|
|
' toast({ type: "error", title: "Could not resolve", message: err.message }),\n',
|
|
" });\n",
|
|
"\n",
|
|
" const deleteMutation = useMutation({\n",
|
|
" mutationFn: (id: string) => watchdogAlertsApi.delete(id),\n",
|
|
" onSuccess: () => {\n",
|
|
' queryClient.invalidateQueries({ queryKey: ["watchdog-alerts"] });\n',
|
|
' toast({ type: "success", title: "Alert deleted" });\n',
|
|
" },\n",
|
|
" onError: (err: Error) =>\n",
|
|
' toast({ type: "error", title: "Could not delete", message: err.message }),\n',
|
|
" });\n",
|
|
"\n",
|
|
" const isMutating =\n",
|
|
" acknowledgeMutation.isPending ||\n",
|
|
" resolveMutation.isPending ||\n",
|
|
" deleteMutation.isPending;\n",
|
|
"\n",
|
|
' const activeCount = allAlerts.filter((a) => wdogStatus(a) === "active").length;\n',
|
|
"\n",
|
|
" return (\n",
|
|
" <Card>\n",
|
|
" <CardHeader>\n",
|
|
' <div className="flex items-center justify-between gap-3">\n',
|
|
' <CardTitle className="flex items-center gap-2">\n',
|
|
' <AlertTriangle className="h-4 w-4 text-amber-500" />\n',
|
|
" Watchdog Alerts\n",
|
|
" {activeCount > 0 && (\n",
|
|
' <span className="ml-1 rounded-full bg-amber-500/15 px-2 py-0.5 text-xs font-medium text-amber-600 dark:text-amber-400">\n',
|
|
" {activeCount} active\n",
|
|
" </span>\n",
|
|
" )}\n",
|
|
" </CardTitle>\n",
|
|
" <button\n",
|
|
' className="text-xs text-[hsl(var(--muted-foreground))] hover:text-[hsl(var(--foreground))] underline-offset-2 hover:underline"\n',
|
|
" onClick={() => setShowAll((v) => !v)}\n",
|
|
" >\n",
|
|
' {showAll ? "Active only" : "Show all"}\n',
|
|
" </button>\n",
|
|
" </div>\n",
|
|
" </CardHeader>\n",
|
|
" <CardContent>\n",
|
|
" {isLoading && (\n",
|
|
' <p className="py-6 text-center text-sm text-[hsl(var(--muted-foreground))]">Loading...</p>\n',
|
|
" )}\n",
|
|
" {!isLoading && alerts.length === 0 && (\n",
|
|
' <p className="py-6 text-center text-sm text-[hsl(var(--muted-foreground))]">\n',
|
|
' {showAll ? "No watchdog alerts recorded." : "No active watchdog alerts."}\n',
|
|
" </p>\n",
|
|
" )}\n",
|
|
' <div className="space-y-3">\n',
|
|
" {alerts.map((alert) => {\n",
|
|
" const status = wdogStatus(alert);\n",
|
|
" const logExpanded = expandedLogId === alert.id;\n",
|
|
" const statusColor =\n",
|
|
' status === "active"\n',
|
|
' ? "text-red-600 dark:text-red-400"\n',
|
|
' : status === "acknowledged"\n',
|
|
' ? "text-amber-600 dark:text-amber-400"\n',
|
|
' : "text-[hsl(var(--muted-foreground))]";\n',
|
|
"\n",
|
|
" return (\n",
|
|
" <div\n",
|
|
" key={alert.id}\n",
|
|
' className="rounded-lg border border-[hsl(var(--border))] p-4 space-y-2"\n',
|
|
" >\n",
|
|
' <div className="flex items-start justify-between gap-4">\n',
|
|
' <div className="space-y-0.5">\n',
|
|
' <div className="flex items-center gap-2">\n',
|
|
" <span className={" + BT + "text-xs font-semibold uppercase tracking-wider ${statusColor}" + BT + "}>\n",
|
|
" {status}\n",
|
|
" </span>\n",
|
|
' <span className="text-xs text-[hsl(var(--muted-foreground))]">\n',
|
|
" " + BULLET + " {alert.restart_attempts} restart attempt\n",
|
|
' {alert.restart_attempts !== 1 ? "s" : ""}\n',
|
|
" </span>\n",
|
|
" </div>\n",
|
|
' <p className="text-xs text-[hsl(var(--muted-foreground))]">\n',
|
|
' Agent ID:{" "}\n',
|
|
' <span className="font-mono" title={alert.agent_id}>\n',
|
|
" {alert.agent_id.slice(0, 8)}…\n",
|
|
" </span>\n",
|
|
' {" "}' + BULLET + '{" "}\n',
|
|
" Triggered: {formatRelative(alert.triggered_at)}\n",
|
|
" {alert.acknowledged_at && (\n",
|
|
' <>{" "}' + BULLET + '{" "}Ack: {formatRelative(alert.acknowledged_at)}</>\n',
|
|
" )}\n",
|
|
" {alert.resolved_at && (\n",
|
|
' <>{" "}' + BULLET + '{" "}Resolved: {formatRelative(alert.resolved_at)}</>\n',
|
|
" )}\n",
|
|
" </p>\n",
|
|
" {alert.last_error && (\n",
|
|
' <p className="text-sm text-[hsl(var(--foreground))]">\n',
|
|
' <span className="font-medium">Error:</span> {alert.last_error}\n',
|
|
" </p>\n",
|
|
" )}\n",
|
|
" </div>\n",
|
|
"\n",
|
|
' <div className="flex shrink-0 items-center gap-1.5">\n',
|
|
' {status === "active" && (\n',
|
|
' <Button size="sm" variant="secondary" disabled={isMutating}\n',
|
|
" onClick={() => acknowledgeMutation.mutate(alert.id)}>\n",
|
|
" Acknowledge\n",
|
|
" </Button>\n",
|
|
" )}\n",
|
|
' {status !== "resolved" && (\n',
|
|
' <Button size="sm" variant="secondary" disabled={isMutating}\n',
|
|
" onClick={() => resolveMutation.mutate(alert.id)}>\n",
|
|
" Resolve\n",
|
|
" </Button>\n",
|
|
" )}\n",
|
|
' <Button size="sm" variant="ghost" disabled={isMutating}\n',
|
|
" onClick={() => deleteMutation.mutate(alert.id)}>\n",
|
|
" Delete\n",
|
|
" </Button>\n",
|
|
" </div>\n",
|
|
" </div>\n",
|
|
"\n",
|
|
" {alert.log_tail && (\n",
|
|
" <div>\n",
|
|
" <button\n",
|
|
' className="flex items-center gap-1 text-xs text-[hsl(var(--muted-foreground))] hover:text-[hsl(var(--foreground))]"\n',
|
|
" onClick={() => setExpandedLogId(logExpanded ? null : alert.id)}\n",
|
|
" >\n",
|
|
' {logExpanded ? <ChevronDown className="h-3 w-3" /> : <ChevronRight className="h-3 w-3" />}\n',
|
|
" Agent log tail\n",
|
|
" </button>\n",
|
|
" {logExpanded && (\n",
|
|
' <pre className="mt-1 max-h-48 overflow-auto rounded bg-[hsl(var(--muted))]/50 p-2 text-xs font-mono text-[hsl(var(--foreground))]">\n',
|
|
" {alert.log_tail}\n",
|
|
" </pre>\n",
|
|
" )}\n",
|
|
" </div>\n",
|
|
" )}\n",
|
|
" </div>\n",
|
|
" );\n",
|
|
" })}\n",
|
|
" </div>\n",
|
|
" </CardContent>\n",
|
|
" </Card>\n",
|
|
" );\n",
|
|
"}\n",
|
|
"\n",
|
|
]
|
|
|
|
component = "".join(lines)
|
|
b64 = base64.b64encode(component.encode("utf-8")).decode("ascii")
|
|
|
|
# Write next to this script — the repo root is machine-specific, never hardcode it.
|
|
out_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "component.b64")
|
|
with open(out_path, "w") as f:
|
|
f.write(b64)
|
|
|
|
print(f"Component: {len(component)} chars")
|
|
print(f"Base64: {len(b64)} chars")
|
|
print("Written to component.b64")
|