rigscope 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/BUILD_ID +1 -1
- package/.next/app-path-routes-manifest.json +2 -2
- package/.next/build-manifest.json +2 -2
- package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +2 -2
- package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/administration/page_client-reference-manifest.js +1 -1
- package/.next/server/app/administration.html +1 -1
- package/.next/server/app/administration.rsc +2 -2
- package/.next/server/app/administration.segments/_full.segment.rsc +2 -2
- package/.next/server/app/administration.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/administration.segments/administration/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/alerts/page_client-reference-manifest.js +1 -1
- package/.next/server/app/alerts.html +1 -1
- package/.next/server/app/alerts.rsc +2 -2
- package/.next/server/app/alerts.segments/_full.segment.rsc +2 -2
- package/.next/server/app/alerts.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/alerts.segments/alerts/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/connections/page.js +1 -1
- package/.next/server/app/connections/page_client-reference-manifest.js +1 -1
- package/.next/server/app/connections.html +1 -1
- package/.next/server/app/connections.rsc +3 -3
- package/.next/server/app/connections.segments/_full.segment.rsc +3 -3
- package/.next/server/app/connections.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/connections.segments/connections/__PAGE__.segment.rsc +3 -3
- package/.next/server/app/history/page_client-reference-manifest.js +1 -1
- package/.next/server/app/history.html +1 -1
- package/.next/server/app/history.rsc +2 -2
- package/.next/server/app/history.segments/_full.segment.rsc +2 -2
- package/.next/server/app/history.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/history.segments/history/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +2 -2
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/index.segments/_full.segment.rsc +2 -2
- package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/server/app/login.html +1 -1
- package/.next/server/app/login.rsc +2 -2
- package/.next/server/app/login.segments/_full.segment.rsc +2 -2
- package/.next/server/app/login.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/machines/[machineId]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/processes/page_client-reference-manifest.js +1 -1
- package/.next/server/app/processes.html +1 -1
- package/.next/server/app/processes.rsc +2 -2
- package/.next/server/app/processes.segments/_full.segment.rsc +2 -2
- package/.next/server/app/processes.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/processes.segments/processes/__PAGE__.segment.rsc +2 -2
- package/.next/server/app-paths-manifest.json +2 -2
- package/.next/server/chunks/161.js +1 -1
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/static/chunks/939-92930fde48061e20.js +1 -0
- package/.next/static/chunks/app/connections/page-7d18b901f2338f20.js +1 -0
- package/ARCHITECTURE.md +18 -6
- package/CHANGELOG.md +35 -0
- package/dist/cli.cjs +762 -509
- package/dist/collector.cjs +581 -115
- package/dist/hub.cjs +7 -3
- package/dist/system.cjs +1257 -363
- package/docs/configuration.md +1 -0
- package/docs/installation.md +9 -3
- package/docs/monitoring.md +6 -3
- package/docs/operations.md +40 -13
- package/docs/system-installation.md +11 -4
- package/docs/telemetry-storage.md +80 -9
- package/package.json +1 -1
- package/packaging/collector.env.example +6 -0
- package/packaging/system/rigscope-collector.service.example +3 -0
- package/.next/static/chunks/939-514e554087419c71.js +0 -1
- package/.next/static/chunks/app/connections/page-75abd16fc495fcbd.js +0 -1
- /package/.next/static/{Jlbibe2_iFCljUO4-BUK7 → JbaLjudQSxrMUD_VHPrnD}/_buildManifest.js +0 -0
- /package/.next/static/{Jlbibe2_iFCljUO4-BUK7 → JbaLjudQSxrMUD_VHPrnD}/_ssgManifest.js +0 -0
package/docs/configuration.md
CHANGED
|
@@ -76,6 +76,7 @@ hashes; it reads passwords from stdin. Keep hashes private.
|
|
|
76
76
|
| `COLLECTOR_INTERVAL_MS` | Sampling interval: `1000` ms by default, minimum `100` ms. GPU polling runs at most once per second. |
|
|
77
77
|
| `COLLECTOR_HUBS` | Explicit destinations as JSON: `{url,token,sharing?,intervalMs?}`, up to eight. Otherwise the collector uses saved enrollment. |
|
|
78
78
|
| `COLLECTOR_HOSTNAME` | Display metadata; defaults to the OS hostname in real mode. |
|
|
79
|
+
| `COLLECTOR_QUEUE_MODE` | Where unacknowledged samples wait: `memory` by default (RAM, no queue written to disk, lost on collector restart) or `durable` (a SQLite outbox per destination that survives a collector restart, at one database write per sample). Bounds are identical: 10 minutes and 64 MiB per destination. |
|
|
79
80
|
|
|
80
81
|
### Sharing settings
|
|
81
82
|
|
package/docs/installation.md
CHANGED
|
@@ -156,7 +156,9 @@ tailscale serve --bg --tls-terminated-tcp=8081 \
|
|
|
156
156
|
```
|
|
157
157
|
|
|
158
158
|
This keeps accounts, history and collector credentials. It updates the local
|
|
159
|
-
collector's destination and moves its pending samples
|
|
159
|
+
collector's destination and moves its pending samples, which is only relevant in
|
|
160
|
+
`COLLECTOR_QUEUE_MODE=durable`: the default RAM queue drains with the collector.
|
|
161
|
+
Rigscope asks to stop
|
|
160
162
|
running components during conversion and resumes them afterward. See [Access changes](operations.md#access-changes)
|
|
161
163
|
for backup and queue checks.
|
|
162
164
|
|
|
@@ -290,8 +292,12 @@ npx --yes rigscope@latest install --boot
|
|
|
290
292
|
```
|
|
291
293
|
|
|
292
294
|
Both paths stop the owned services and reset the pre-canonical configuration and
|
|
293
|
-
history.
|
|
294
|
-
|
|
295
|
+
history. Upgrades that stay in the canonical format keep accounts, settings and
|
|
296
|
+
collector identities; the first one after the RAM-first release additionally
|
|
297
|
+
completes a one-time transition that discards machine-derived history and
|
|
298
|
+
pre-RAM-first durable collector queues once. Later upgrades preserve durable
|
|
299
|
+
history and queues. See
|
|
300
|
+
[telemetry storage](telemetry-storage.md#the-one-time-machine-data-transition).
|
|
295
301
|
|
|
296
302
|
| Alternative | Command |
|
|
297
303
|
| --- | --- |
|
package/docs/monitoring.md
CHANGED
|
@@ -26,8 +26,9 @@ the hub restarts. Users, credentials, enrollment, settings, alert records,
|
|
|
26
26
|
machine names/tags and inventory remain persistent.
|
|
27
27
|
|
|
28
28
|
In memory mode the collector's acknowledgement means the hub accepted the sample
|
|
29
|
-
into RAM. The collector
|
|
30
|
-
disconnect
|
|
29
|
+
into RAM. The collector queues pending samples in RAM and retries after a
|
|
30
|
+
disconnect, so it gains no disk work from the hub's choice and writes no queue
|
|
31
|
+
of its own. Deduplication
|
|
31
32
|
uses a compact per-run interval set for the hub process lifetime. Because both
|
|
32
33
|
history and receipts disappear together on restart, a replay cannot double-count
|
|
33
34
|
restored chart data.
|
|
@@ -38,7 +39,9 @@ transaction, and only then acknowledges. Replay deduplication uses durable
|
|
|
38
39
|
receipts whose lifetime is independent of chart retention (default seven days).
|
|
39
40
|
Replayed samples fill History without replacing current live readings or opening
|
|
40
41
|
alerts for past threshold crossings. A graceful shutdown flushes pending
|
|
41
|
-
summaries; after a crash the collector replays anything
|
|
42
|
+
summaries; after a crash the collector replays anything the hub never
|
|
43
|
+
acknowledged, provided the collector itself kept it: only
|
|
44
|
+
`COLLECTOR_QUEUE_MODE=durable` survives a collector restart.
|
|
42
45
|
|
|
43
46
|
Default memory history:
|
|
44
47
|
|
package/docs/operations.md
CHANGED
|
@@ -55,9 +55,26 @@ running, then checks:
|
|
|
55
55
|
The health timeout is 25 seconds; change it with `--timeout SECONDS`. A failed
|
|
56
56
|
start, migration or health check restores the previous runtime. It also restores
|
|
57
57
|
the paired database backup if the old runtime cannot read the new schema,
|
|
58
|
-
preserving the replaced database first
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
preserving the replaced database first — and additionally whenever the
|
|
59
|
+
one-time machine-data transition described below had already discarded rows,
|
|
60
|
+
even if the schema is compatible, because the loss there is the rows, not the
|
|
61
|
+
schema.
|
|
62
|
+
|
|
63
|
+
The first upgrade after the RAM-first release completes a one-time
|
|
64
|
+
machine-data transition: it discards the previous design's on-disk machine
|
|
65
|
+
history — telemetry rows and summaries, receipts, gaps, latest snapshots,
|
|
66
|
+
inventory and machine-derived alert history — plus pre-RAM-first durable
|
|
67
|
+
collector queues, once, even data previously opted into keeping. Machine
|
|
68
|
+
display names/tags (`machine_meta`), accounts, sessions, enrollment and grants,
|
|
69
|
+
settings, alert rules/jobs/webhook configuration and recordings are never
|
|
70
|
+
discarded. A version-independent `machine-data-transition.json` marker in the
|
|
71
|
+
state directory records completion; every later upgrade preserves durable
|
|
72
|
+
history and queues you opt back into (`COLLECTOR_QUEUE_MODE=durable`, durable
|
|
73
|
+
hub history). The upgrade output reports exactly what was discarded; a cleanup
|
|
74
|
+
that cannot finish is shown as a warning and retried by the next upgrade
|
|
75
|
+
instead of failing an otherwise healthy one. See
|
|
76
|
+
[telemetry storage](telemetry-storage.md#the-one-time-machine-data-transition).
|
|
77
|
+
Credentials and collector identities are always retained.
|
|
61
78
|
`release-state.json` records which backup belongs to each runtime. Upgrading a
|
|
62
79
|
stopped installation leaves it stopped and reports live checks as skipped.
|
|
63
80
|
|
|
@@ -67,7 +84,8 @@ release, stops the hub and collector completely, then removes
|
|
|
67
84
|
the old configuration and history and installing the latest release fresh. Back
|
|
68
85
|
up the state directory first if that history must be kept. There is no migration
|
|
69
86
|
to the canonical format and no rollback to that old shape. Future upgrades that
|
|
70
|
-
stay canonical keep accounts,
|
|
87
|
+
stay canonical keep accounts, settings and collector identities, and — apart
|
|
88
|
+
from the one-time transition above — durable history and queues.
|
|
71
89
|
|
|
72
90
|
### Roll back
|
|
73
91
|
|
|
@@ -192,7 +210,7 @@ The installer creates:
|
|
|
192
210
|
hub.env private EnvironmentFile (0600, no secrets printed)
|
|
193
211
|
collector.env private EnvironmentFile (0600)
|
|
194
212
|
collector/collector.json collector identity/enrollment (created by collector)
|
|
195
|
-
outbox/<sha256(url)[0:16]>/ per-destination
|
|
213
|
+
outbox/<sha256(url)[0:16]>/ per-destination queues, durable mode only
|
|
196
214
|
backups/ private pre-upgrade/pre-activation database copies
|
|
197
215
|
install-state.json canonical versions/schema backups, no credentials
|
|
198
216
|
run/ private runtime directory for a Unix socket
|
|
@@ -315,6 +333,9 @@ COLLECTOR_ENROLL_URL=ws://127.0.0.1:8081/api/collector/connect
|
|
|
315
333
|
COLLECTOR_ENROLL_TOKEN=<one-use grant>
|
|
316
334
|
COLLECTOR_MODE=real
|
|
317
335
|
COLLECTOR_STATE_FILE=/home/operator/.local/state/rigscope/collector/collector.json
|
|
336
|
+
# Optional: keep unacknowledged samples in a per-hub SQLite outbox so they
|
|
337
|
+
# survive a collector restart. Default is RAM (no queue written to disk).
|
|
338
|
+
# COLLECTOR_QUEUE_MODE=durable
|
|
318
339
|
```
|
|
319
340
|
|
|
320
341
|
Remove the one-use grant after enrollment and start or restart the collector. The
|
|
@@ -422,9 +443,11 @@ The database backup is not the whole installation. A restorable set is:
|
|
|
422
443
|
collectors) and the private hub environment file. These
|
|
423
444
|
contain credentials: protect and copy them only between private locations.
|
|
424
445
|
3. Collector identity/enrollment: `collector/collector.json`.
|
|
425
|
-
4. Collector
|
|
426
|
-
|
|
427
|
-
deduplicates by
|
|
446
|
+
4. Collector queues, `outbox/<destination-hash>/outbox.sqlite` per hub URL, exist
|
|
447
|
+
only under `COLLECTOR_QUEUE_MODE=durable` and may contain unacknowledged
|
|
448
|
+
telemetry. Replay is at-least-once; the hub deduplicates by
|
|
449
|
+
collector/run/sequence. The default RAM queue holds nothing on disk, so there
|
|
450
|
+
is nothing to back up and nothing to restore.
|
|
428
451
|
5. Optional operational metadata: `install-state.json`, units and the built
|
|
429
452
|
release. Keep the release matching the database schema for rollback.
|
|
430
453
|
6. Explicit recordings: the private `HUB_RECORDINGS_DIR` (default
|
|
@@ -482,7 +505,10 @@ use the component controls in [System installation](system-installation.md#syste
|
|
|
482
505
|
The collector uses only `/api/collector/connect` and the canonical record shape.
|
|
483
506
|
There is no old-endpoint normalization, outbox migration or old-peer payload
|
|
484
507
|
conversion. Reset unfinished installations and re-enroll their collectors;
|
|
485
|
-
canonical upgrades retain their identities
|
|
508
|
+
canonical upgrades retain their identities and credentials. Pending samples are
|
|
509
|
+
retained by every upgrade after the one-time machine-data transition, which
|
|
510
|
+
discards pre-RAM-first durable queues once; from then on
|
|
511
|
+
`COLLECTOR_QUEUE_MODE=durable` content survives upgrades.
|
|
486
512
|
|
|
487
513
|
## Measurement
|
|
488
514
|
|
|
@@ -549,10 +575,11 @@ absolute regular file; use the release's `node_modules` installed with
|
|
|
549
575
|
## Storage budgets and pressure recovery
|
|
550
576
|
|
|
551
577
|
Each Collector Hub connection owns policy filtering, cadence, inventory and its
|
|
552
|
-
|
|
553
|
-
current sharing policy before the connection starts;
|
|
554
|
-
reapplied to retained records. Endpoint upgrade
|
|
555
|
-
continue to use the existing
|
|
578
|
+
delivery queue submission. Under `COLLECTOR_QUEUE_MODE=durable`, recovered metrics
|
|
579
|
+
are sanitized with the current sharing policy before the connection starts;
|
|
580
|
+
capture-time cadence is not reapplied to retained records. Endpoint upgrade
|
|
581
|
+
recovery and aggregate budgets continue to use the existing queues. The default
|
|
582
|
+
RAM queue starts empty, so its bounds are enforced against RAM rather than disk. Hub startup restores stored telemetry,
|
|
556
583
|
cadence, inventory and metadata through Sample ingestion while retaining a
|
|
557
584
|
disconnected state until an authenticated Collector reconnects.
|
|
558
585
|
|
|
@@ -31,7 +31,8 @@ The installed `rigscope system` command and `dist/system.cjs` use the same code.
|
|
|
31
31
|
hub/enrollment.json rigscope-hub 0600
|
|
32
32
|
hub/run/hub.sock private Unix socket (remote mode)
|
|
33
33
|
collector/state.json rigscope-collector 0600
|
|
34
|
-
collector/outbox/ durable
|
|
34
|
+
collector/outbox/ collector queues, durable mode only
|
|
35
|
+
machine-data-transition.json root:root 0600, one-time machine-data transition marker
|
|
35
36
|
backups/ root:root 0700, pre-upgrade/pre-rollback SQLite copies
|
|
36
37
|
system-install.json root:root 0600, canonical component state/versions/backups/adoption
|
|
37
38
|
power-access.json root:root 0600, original counter modes for reversal
|
|
@@ -104,8 +105,14 @@ A pre-canonical system installation is reset, not migrated. `system install` and
|
|
|
104
105
|
units completely, then remove the old configuration and history and install
|
|
105
106
|
the canonical layout fresh. Back up `/var/lib/rigscope` first if that history
|
|
106
107
|
must be kept; there is no rollback to the old format. Later upgrades that stay
|
|
107
|
-
canonical keep accounts,
|
|
108
|
-
|
|
108
|
+
canonical keep accounts, settings and collector identities and rollback keeps
|
|
109
|
+
its paired database backup. The first such upgrade after the RAM-first release
|
|
110
|
+
also completes the one-time machine-data transition, discarding machine-derived
|
|
111
|
+
history and pre-RAM-first durable collector queues once — even data previously
|
|
112
|
+
opted into keeping — while preserving machine names/tags; a
|
|
113
|
+
`machine-data-transition.json` marker in `/var/lib/rigscope` makes every
|
|
114
|
+
subsequent upgrade preserve durable history and queues. See
|
|
115
|
+
[telemetry storage](telemetry-storage.md#the-one-time-machine-data-transition).
|
|
109
116
|
|
|
110
117
|
- With `--enable`, `--role local` initially enables both components.
|
|
111
118
|
- With `--enable`, `--role hub` initially enables only the hub.
|
|
@@ -131,7 +138,7 @@ identity and RAPL grant. With no destination it tries the colocated hub:
|
|
|
131
138
|
cleanly before collection until a destination is saved.
|
|
132
139
|
|
|
133
140
|
Configured remote destinations and credentials are never overwritten. Re-running
|
|
134
|
-
`enable collector` leaves its destination, token
|
|
141
|
+
`enable collector` leaves its destination, token and identity intact.
|
|
135
142
|
|
|
136
143
|
### Connecting to an independent hub
|
|
137
144
|
|
|
@@ -24,10 +24,30 @@ inventory. Only telemetry history is ephemeral by default.
|
|
|
24
24
|
| Routine telemetry writes | None | One transaction roughly every 5 seconds |
|
|
25
25
|
| Deduplication | Per-run in-RAM interval set | `sample_receipts` with an independent 7-day TTL |
|
|
26
26
|
|
|
27
|
-
The hub sends `delivery: "memory"` or `"durable"` in `welcome
|
|
28
|
-
|
|
29
|
-
cases, so delayed acknowledgements do not move sustained
|
|
30
|
-
the collectors.
|
|
27
|
+
The hub sends `delivery: "memory"` or `"durable"` in `welcome`: it describes the
|
|
28
|
+
hub's choice, not the collector's. Collectors retry and hold in-flight batches
|
|
29
|
+
the same way in both cases, so delayed acknowledgements do not move sustained
|
|
30
|
+
writes from the hub to the collectors.
|
|
31
|
+
|
|
32
|
+
## Collector delivery queue
|
|
33
|
+
|
|
34
|
+
Unacknowledged samples wait in a bounded queue per destination. Its retention is
|
|
35
|
+
independent of the hub's history mode.
|
|
36
|
+
|
|
37
|
+
| | `COLLECTOR_QUEUE_MODE=memory` (default) | `COLLECTOR_QUEUE_MODE=durable` |
|
|
38
|
+
| --- | --- | --- |
|
|
39
|
+
| Pending samples | RAM | SQLite file per destination |
|
|
40
|
+
| Routine collector disk writes | None | One insert, one in-flight update and one delete per sample |
|
|
41
|
+
| Collector restart | Pending samples are lost | Everything unacknowledged is replayed |
|
|
42
|
+
| Reported to the hub | `delivery.queueMode: "memory"` | `delivery.queueMode: "durable"` |
|
|
43
|
+
|
|
44
|
+
Both modes share the same bounds (10 minutes and 64 MiB per destination, 256 MiB
|
|
45
|
+
aggregate), the same live-slot priority, and the same coalesced `gap` records
|
|
46
|
+
that tell the hub "samples were captured here and dropped" rather than "no
|
|
47
|
+
samples were captured". Choose `durable` when a collector restart losing a few
|
|
48
|
+
minutes of samples matters more than a database write per sample; `rigscope`
|
|
49
|
+
reports `queueMode` under a connection's delivery facts so the difference is
|
|
50
|
+
visible without guessing the configuration.
|
|
31
51
|
|
|
32
52
|
### Memory history defaults
|
|
33
53
|
|
|
@@ -95,9 +115,9 @@ explicitly constructs the store in raw mode (used by tests and measurement).
|
|
|
95
115
|
also far below the collector's 30-second per-batch ack timeout. The
|
|
96
116
|
collector reserves enough slots for a full flush window of live samples,
|
|
97
117
|
plus two samples of headroom, and drains a waiting live
|
|
98
|
-
record as soon as an acknowledgement frees capacity. Its
|
|
99
|
-
|
|
100
|
-
do not turn into
|
|
118
|
+
record as soon as an acknowledgement frees capacity. Its delivery queue keeps
|
|
119
|
+
the existing 10-minute/64 MiB per-destination bounds in either mode, so
|
|
120
|
+
delayed acks do not turn into unbounded queueing or silent data movement.
|
|
101
121
|
|
|
102
122
|
Dedup receipts default to **7 days** and are independent of chart retention.
|
|
103
123
|
Shortening raw/10-second history can no longer let a replayed sample recreate
|
|
@@ -133,11 +153,50 @@ A recording is an explicit, bounded capture and is separate from chart history.
|
|
|
133
153
|
Recordings are not part of a database-only backup. If they matter, include the
|
|
134
154
|
recordings directory in the same private backup set as the hub database.
|
|
135
155
|
|
|
156
|
+
## The one-time machine-data transition
|
|
157
|
+
|
|
158
|
+
The first `rigscope upgrade` (or `rigscope system upgrade --apply`) after the
|
|
159
|
+
RAM-first release completes a one-time transition. It discards the previous
|
|
160
|
+
design's on-disk machine-derived data once, explicitly authorized for this
|
|
161
|
+
transition even for data an operator had opted into keeping:
|
|
162
|
+
|
|
163
|
+
- hub database: telemetry rows and their summaries, `sample_receipts`, `gaps`,
|
|
164
|
+
`machine_latest`, `series`, `inventory`, and the machine-derived alert
|
|
165
|
+
history (`alert_incidents`, `alert_machine_progress`,
|
|
166
|
+
`alert_observed_resources`);
|
|
167
|
+
- the collector's pre-RAM-first durable outbox tree.
|
|
168
|
+
|
|
169
|
+
It always preserves accounts and sessions, collector enrollment and grants,
|
|
170
|
+
settings, alert rules/jobs/webhook configuration, machine display names and
|
|
171
|
+
tags (`machine_meta`), and recordings.
|
|
172
|
+
|
|
173
|
+
The transition is recorded in a version-independent
|
|
174
|
+
`machine-data-transition.json` marker in the installation's state root, written
|
|
175
|
+
only after the activated release verified healthy. Every upgrade after that —
|
|
176
|
+
including upgrades to future versions — reads the marker and preserves durable
|
|
177
|
+
history and queues you opt back into, so `COLLECTOR_QUEUE_MODE=durable` and
|
|
178
|
+
durable hub history survive later upgrades intact. An installation created by
|
|
179
|
+
`install` (fresh or reset) is recorded as already transitioned.
|
|
180
|
+
|
|
181
|
+
Until the marker is written the transition is reversible: the queue tree is
|
|
182
|
+
renamed aside rather than deleted, and the upgrade's recorded pre-upgrade
|
|
183
|
+
database copy restores the cleared rows if activation fails — even when the
|
|
184
|
+
previous runtime could still read the schema, because the transition removed
|
|
185
|
+
rows, not schema. A cleanup that cannot finish never fails an otherwise healthy
|
|
186
|
+
upgrade: it is reported as a visible warning, the marker stays unwritten, and
|
|
187
|
+
the next upgrade retries it. The upgrade output states exactly how much was
|
|
188
|
+
discarded.
|
|
189
|
+
|
|
190
|
+
If you skipped the transition window (for example after re-registering an
|
|
191
|
+
installation with `rigscope install`) and want old rows gone, use the explicit
|
|
192
|
+
reset below.
|
|
193
|
+
|
|
136
194
|
## Reset old history
|
|
137
195
|
|
|
138
196
|
Switching an existing installation to memory mode does not read or rewrite old
|
|
139
|
-
telemetry rows, but they still occupy the database
|
|
140
|
-
|
|
197
|
+
telemetry rows, but they still occupy the database if the one-time transition
|
|
198
|
+
above has not run. To discard them explicitly while preserving users,
|
|
199
|
+
enrollment, settings and alert state:
|
|
141
200
|
|
|
142
201
|
```sh
|
|
143
202
|
rigscope stop
|
|
@@ -185,3 +244,15 @@ the history-array budget.
|
|
|
185
244
|
These numbers are development-host measurements, not a promised multiplier or
|
|
186
245
|
capacity guarantee. Re-run on the deployment host and payload before choosing
|
|
187
246
|
retention or budgets.
|
|
247
|
+
|
|
248
|
+
If an earlier transition attempt was interrupted, its staged collector queue is
|
|
249
|
+
kept for manual recovery; a retry does not delete another attempt's copy. If a
|
|
250
|
+
failed replacement runtime creates a new queue, rollback preserves that queue
|
|
251
|
+
separately and restores the original pending queue. An existing malformed or
|
|
252
|
+
unreadable transition marker blocks the upgrade before clearing data; repair
|
|
253
|
+
the marker rather than deleting it, which would request another reset.
|
|
254
|
+
|
|
255
|
+
Failure to write the completion marker is reported without failing a healthy
|
|
256
|
+
upgrade and leaves the transition pending. Failure to remove a staged queue
|
|
257
|
+
after the marker was written is also reported, but later upgrades still preserve
|
|
258
|
+
new history because completion has already been recorded.
|
package/package.json
CHANGED
|
@@ -8,6 +8,12 @@
|
|
|
8
8
|
# string, not a credential for any real deployment.
|
|
9
9
|
|
|
10
10
|
# COLLECTOR_MODE=real
|
|
11
|
+
# Unacknowledged samples wait in RAM by default, so routine delivery writes no
|
|
12
|
+
# queue to disk and a collector restart loses what was never acknowledged.
|
|
13
|
+
# Set this only when surviving a collector restart with unacknowledged samples
|
|
14
|
+
# is worth one database write per sample; it is bounded per destination to
|
|
15
|
+
# 10 minutes and 64 MiB.
|
|
16
|
+
# COLLECTOR_QUEUE_MODE=durable
|
|
11
17
|
# COLLECTOR_STATE_FILE=/home/operator/.local/state/rigscope/collector/collector.json
|
|
12
18
|
# COLLECTOR_INTERVAL_MS=1000
|
|
13
19
|
# COLLECTOR_HOSTNAME=rig-01
|
|
@@ -42,6 +42,9 @@ SupplementaryGroups=rigscope-rapl
|
|
|
42
42
|
Environment="COLLECTOR_MODE=real"
|
|
43
43
|
Environment="COLLECTOR_STATE_FILE=/var/lib/rigscope/collector/state.json"
|
|
44
44
|
Environment="COLLECTOR_HEALTH_FILE=/var/lib/rigscope/collector/health.json"
|
|
45
|
+
# Unacknowledged samples wait in RAM, so routine delivery writes no queue file.
|
|
46
|
+
# Uncomment to keep them in a per-hub SQLite outbox across collector restarts.
|
|
47
|
+
# Environment="COLLECTOR_QUEUE_MODE=durable"
|
|
45
48
|
ReadWritePaths="/var/lib/rigscope/collector"
|
|
46
49
|
ReadOnlyPaths="/opt/rigscope/current"
|
|
47
50
|
TimeoutStopSec=10s
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[939],{592:(e,t,r)=>{r.d(t,{U8:()=>n,Q_:()=>l,FZ:()=>T});let n=1024,i=Date.parse("2000-01-01T00:00:00Z"),s=Date.parse("2200-01-01T00:00:00Z"),a=["metrics","inventory","processes"],o=["cpu","memory","swap","gpus","disks","network"],u=["outbox-age","outbox-bytes","disk-budget","write-failure","rejected"];function l(e){let t=b(e,"collector message");switch(t.type){case"hello":{k(t,["type","collectorId","runId","bootId","hostname","capabilities","expectedIntervalMs"],"hello");let e=$(t.capabilities,"hello.capabilities",1,a.length).map(e=>A(e,a,"hello.capabilities"));if(new Set(e).size!==e.length||!e.includes("metrics"))throw Error("invalid hello: capabilities must be unique and include metrics");return{type:"hello",collectorId:E(t.collectorId,"collectorId"),runId:E(t.runId,"runId"),bootId:B(t.bootId,"bootId",0,128),hostname:B(t.hostname,"hostname",0,255),capabilities:e,expectedIntervalMs:P(t.expectedIntervalMs,"expectedIntervalMs",100,72e5)}}case"samples":{k(t,["type","mode","records"],"samples");let e=A(t.mode,["live","replay"],"samples.mode"),r=$(t.records,"samples.records",1,32).map((e,t)=>d(e,`records[${t}]`)),n=new Set;for(let e of r){let t=`${e.runId}\0${e.sequence}`;if(n.has(t))throw Error("invalid samples: duplicate record in batch");n.add(t)}return{type:"samples",mode:e,records:r}}case"inventory":return k(t,["type","capturedAt","inventory"],"inventory"),{type:"inventory",capturedAt:x(t.capturedAt,"capturedAt"),inventory:c(t.inventory)};case"status":return k(t,["type","capturedAt","delivery","failures"],"status"),{type:"status",capturedAt:x(t.capturedAt,"capturedAt"),delivery:function(e){let t=b(e,"delivery");if(k(t,["pendingRecords","pendingBytes","oldestPendingCapturedAt","inFlightRecords","droppedRecords","replaying","lastAckAt"],"delivery"),"boolean"!=typeof t.replaying)throw Error("invalid delivery: replaying");let r=(e,t)=>P(e,t,0,Number.MAX_SAFE_INTEGER);return{pendingRecords:r(t.pendingRecords,"pendingRecords"),pendingBytes:r(t.pendingBytes,"pendingBytes"),oldestPendingCapturedAt:v(t.oldestPendingCapturedAt,e=>x(e,"oldestPendingCapturedAt")),inFlightRecords:r(t.inFlightRecords,"inFlightRecords"),droppedRecords:r(t.droppedRecords,"droppedRecords"),replaying:t.replaying,lastAckAt:v(t.lastAckAt,e=>x(e,"lastAckAt"))}}(t.delivery),failures:$(t.failures,"failures",0,32).map(e=>{let t=b(e,"failure");return k(t,["metric","resourceId","reason"],"failure"),{metric:B(t.metric,"metric",1,64),resourceId:null===t.resourceId?null:I(t.resourceId),reason:B(t.reason,"reason",0,256)}})};case"detail.result":return k(t,["type","subscriptionId","capturedAt","snapshot"],"detail.result"),{type:"detail.result",subscriptionId:E(t.subscriptionId,"subscriptionId"),capturedAt:x(t.capturedAt,"capturedAt"),snapshot:function(e){let t=b(e,"process snapshot");if(k(t,["totalProcesses","truncated","inaccessible","processes"],"process snapshot"),"boolean"!=typeof t.truncated)throw Error("invalid process snapshot: truncated");let r=(e,t,r)=>v(e,e=>S(e,t,0,r));return{totalProcesses:P(t.totalProcesses,"totalProcesses",0,1e7),truncated:t.truncated,inaccessible:P(t.inaccessible,"inaccessible",0,1e7),processes:$(t.processes,"processes",0,6e3).map(e=>{let t=b(e,"process");k(t,["pid","ppid","startTicks","name","user","state","cpuPercent","memoryRssBytes","gpuMemoryBytes","ioReadBytesPerSec","ioWriteBytesPerSec","threads","unit"],"process",["command"]);let i={pid:P(t.pid,"pid",0,0x80000000),ppid:P(t.ppid,"ppid",0,0x80000000),startTicks:P(t.startTicks,"startTicks",0,Number.MAX_SAFE_INTEGER),name:B(t.name,"name",0,256),user:v(t.user,e=>B(e,"user",0,64)),state:B(t.state,"state",0,16),cpuPercent:r(t.cpuPercent,"cpuPercent",100*n),memoryRssBytes:v(t.memoryRssBytes,e=>P(e,"memoryRssBytes",0,Number.MAX_SAFE_INTEGER)),gpuMemoryBytes:v(t.gpuMemoryBytes,e=>P(e,"gpuMemoryBytes",0,Number.MAX_SAFE_INTEGER)),ioReadBytesPerSec:r(t.ioReadBytesPerSec,"ioReadBytesPerSec",1e13),ioWriteBytesPerSec:r(t.ioWriteBytesPerSec,"ioWriteBytesPerSec",1e13),threads:v(t.threads,e=>P(e,"threads",0,1e7)),unit:v(t.unit,e=>B(e,"unit",1,256))};return w(t,"command")&&(i.command=B(t.command,"command",0,4096)),i})}}(t.snapshot)};case"detail.ended":return k(t,["type","subscriptionId","reason"],"detail.ended"),{type:"detail.ended",subscriptionId:E(t.subscriptionId,"subscriptionId"),reason:A(t.reason,["expired","cancelled","withheld","unsupported","busy","failed"],"detail.ended.reason")};default:throw Error(`invalid collector message: unsupported type ${M(t.type)}`)}}function d(e,t="record"){let r=b(e,t);k(r,["collectorId","runId","bootId","sequence","capturedAt","kind","payload"],t);let i={collectorId:E(r.collectorId,"collectorId"),runId:E(r.runId,"runId"),bootId:B(r.bootId,"bootId",0,128),sequence:P(r.sequence,"sequence",0,Number.MAX_SAFE_INTEGER),capturedAt:x(r.capturedAt,"capturedAt")};if("metrics"===r.kind){let e;return{...i,kind:"metrics",payload:(k(e=b(r.payload,"metrics payload"),[...o],"metrics payload"),{cpu:y(e.cpu,"cpu",e=>{k(e,["status","usagePercent","perCore"],"cpu",["temperatureC","powerWatts","iowaitPercent","pressureSomePercent"]);let t={status:"ok",usagePercent:S(e.usagePercent,"usagePercent",0,100),perCore:$(e.perCore,"perCore",0,n).map(e=>null===e?null:S(e,"perCore",0,100))},r=g(e,"temperatureC",e=>S(e,"temperatureC",-50,200));void 0!==r&&(t.temperatureC=r);let i=g(e,"powerWatts",e=>S(e,"powerWatts",0,1e4));void 0!==i&&(t.powerWatts=i);let s=g(e,"iowaitPercent",e=>S(e,"iowaitPercent",0,100));void 0!==s&&(t.iowaitPercent=s);let a=g(e,"pressureSomePercent",e=>S(e,"pressureSomePercent",0,100));return void 0!==a&&(t.pressureSomePercent=a),t}),memory:y(e.memory,"memory",m),swap:y(e.swap,"swap",p),gpus:y(e.gpus,"gpus",e=>(k(e,["status","devices"],"gpus"),{status:"ok",devices:h($(e.devices,"gpus.devices",0,16).map(e=>f(e,"gpu",(e,t)=>{k(e,["id","status","utilizationPercent","memoryUsedBytes","memoryTotalBytes","temperatureC","powerWatts"],"gpu");let r=v(e.memoryUsedBytes,e=>P(e,"memoryUsedBytes",0,Number.MAX_SAFE_INTEGER)),n=v(e.memoryTotalBytes,e=>P(e,"memoryTotalBytes",0,Number.MAX_SAFE_INTEGER));if(null!==r&&null!==n&&r>n)throw Error("invalid gpu: memoryUsedBytes exceeds total");return{id:t,status:"ok",utilizationPercent:v(e.utilizationPercent,e=>S(e,"utilizationPercent",0,100)),memoryUsedBytes:r,memoryTotalBytes:n,temperatureC:v(e.temperatureC,e=>S(e,"temperatureC",-50,200)),powerWatts:v(e.powerWatts,e=>S(e,"powerWatts",0,1e4))}})))})),disks:y(e.disks,"disks",e=>{k(e,["status","filesystems","devices"],"disks",["pressureSomePercent","pressureFullPercent"]);let t={status:"ok",filesystems:h($(e.filesystems,"filesystems",0,64).map(e=>f(e,"filesystem",(e,t)=>{k(e,["id","status","usedBytes","totalBytes"],"filesystem",["inodesUsed","inodesTotal"]);let r=p({status:"ok",usedBytes:e.usedBytes,totalBytes:e.totalBytes}),n={id:t,status:"ok",usedBytes:r.usedBytes,totalBytes:r.totalBytes},i=g(e,"inodesUsed",e=>P(e,"inodesUsed",0,Number.MAX_SAFE_INTEGER));void 0!==i&&(n.inodesUsed=i);let s=g(e,"inodesTotal",e=>P(e,"inodesTotal",0,Number.MAX_SAFE_INTEGER));if(void 0!==s&&(n.inodesTotal=s),null!=i&&null!=s&&i>s)throw Error("invalid filesystem: inodesUsed exceeds inodesTotal");return n}))),devices:h($(e.devices,"disk devices",0,64).map(e=>f(e,"disk",(e,t)=>{k(e,["id","status","readBytesPerSec","writeBytesPerSec"],"disk",["readLatencyMs","writeLatencyMs","ioUtilizationPercent"]);let r={id:t,status:"ok",readBytesPerSec:S(e.readBytesPerSec,"readBytesPerSec",0,1e13),writeBytesPerSec:S(e.writeBytesPerSec,"writeBytesPerSec",0,1e13)},n=g(e,"readLatencyMs",e=>S(e,"readLatencyMs",0,864e5));void 0!==n&&(r.readLatencyMs=n);let i=g(e,"writeLatencyMs",e=>S(e,"writeLatencyMs",0,864e5));void 0!==i&&(r.writeLatencyMs=i);let s=g(e,"ioUtilizationPercent",e=>S(e,"ioUtilizationPercent",0,100));return void 0!==s&&(r.ioUtilizationPercent=s),r})))},r=g(e,"pressureSomePercent",e=>S(e,"pressureSomePercent",0,100));void 0!==r&&(t.pressureSomePercent=r);let n=g(e,"pressureFullPercent",e=>S(e,"pressureFullPercent",0,100));return void 0!==n&&(t.pressureFullPercent=n),t}),network:y(e.network,"network",e=>(k(e,["status","interfaces"],"network"),{status:"ok",interfaces:h($(e.interfaces,"interfaces",0,64).map(e=>f(e,"interface",(e,t)=>(k(e,["id","status","rxBytesPerSec","txBytesPerSec"],"interface"),{id:t,status:"ok",rxBytesPerSec:S(e.rxBytesPerSec,"rxBytesPerSec",0,1e13),txBytesPerSec:S(e.txBytesPerSec,"txBytesPerSec",0,1e13)}))))}))})}}if("gap"===r.kind){let e=b(r.payload,`${t}.payload`);k(e,["fromCapturedAt","toCapturedAt","droppedRecords","reason"],"gap payload");let n=x(e.fromCapturedAt,"fromCapturedAt"),s=x(e.toCapturedAt,"toCapturedAt");if(Date.parse(n)>Date.parse(s))throw Error("invalid gap: fromCapturedAt after toCapturedAt");return{...i,kind:"gap",payload:{fromCapturedAt:n,toCapturedAt:s,droppedRecords:P(e.droppedRecords,"droppedRecords",1,Number.MAX_SAFE_INTEGER),reason:A(e.reason,u,"gap.reason")}}}throw Error(`invalid ${t}: unsupported kind ${M(r.kind)}`)}function c(e){let t=b(e,"inventory");k(t,["os","cpu","memory","gpus","filesystems","disks","network","capabilities"],"inventory");let r=b(t.os,"inventory.os");k(r,["platform","release","distro","arch"],"inventory.os");let i=b(t.cpu,"inventory.cpu");k(i,["model","logicalCores"],"inventory.cpu");let s=b(t.memory,"inventory.memory");k(s,["totalBytes","swapTotalBytes"],"inventory.memory");let a=b(t.capabilities,"inventory.capabilities"),u=[...o,"processes"];k(a,[...u],"inventory.capabilities");let l={};for(let e of u){let t=b(a[e],`capability ${e}`);k(t,["status"],`capability ${e}`,["reason"]);let r={status:A(t.status,["supported","unsupported","withheld"],`capability ${e}`)};w(t,"reason")&&(r.reason=B(t.reason,"reason",0,256)),l[e]=r}let d=(e,t)=>v(e,e=>P(e,t,0,Number.MAX_SAFE_INTEGER));return{os:{platform:B(r.platform,"platform",0,64),release:B(r.release,"release",0,256),distro:v(r.distro,e=>B(e,"distro",0,256)),arch:B(r.arch,"arch",0,64)},cpu:{model:v(i.model,e=>B(e,"model",0,256)),logicalCores:P(i.logicalCores,"logicalCores",0,n)},memory:{totalBytes:d(s.totalBytes,"totalBytes"),swapTotalBytes:d(s.swapTotalBytes,"swapTotalBytes")},gpus:h($(t.gpus,"inventory.gpus",0,16).map(e=>{let t=b(e,"gpu inventory");k(t,["id","name","vendor","pciBusId","telemetry","memoryTotalBytes"],"gpu inventory",["reason"]);let r={id:I(t.id),name:B(t.name,"name",0,256),vendor:A(t.vendor,["nvidia","amd","intel","aspeed","other"],"vendor"),pciBusId:v(t.pciBusId,e=>B(e,"pciBusId",1,64)),telemetry:A(t.telemetry,["supported","unsupported"],"telemetry"),memoryTotalBytes:d(t.memoryTotalBytes,"memoryTotalBytes")};return w(t,"reason")&&(r.reason=B(t.reason,"reason",0,256)),r})),filesystems:h($(t.filesystems,"inventory.filesystems",0,64).map(e=>{let t=b(e,"filesystem inventory");return k(t,["id","mountPoint","device","fsType","totalBytes"],"filesystem inventory"),{id:I(t.id),mountPoint:B(t.mountPoint,"mountPoint",1,256),device:B(t.device,"device",0,256),fsType:B(t.fsType,"fsType",0,64),totalBytes:d(t.totalBytes,"totalBytes")}})),disks:h($(t.disks,"inventory.disks",0,64).map(e=>{let t=b(e,"disk inventory");if(k(t,["id","name","model","rotational","sizeBytes"],"disk inventory"),null!==t.rotational&&"boolean"!=typeof t.rotational)throw Error("invalid disk inventory: rotational");return{id:I(t.id),name:B(t.name,"name",1,64),model:v(t.model,e=>B(e,"model",0,256)),rotational:t.rotational,sizeBytes:d(t.sizeBytes,"sizeBytes")}})),network:h($(t.network,"inventory.network",0,64).map(e=>{let t=b(e,"interface inventory");if(k(t,["id","name","mac","addresses","virtual","state","speedMbps"],"interface inventory"),"boolean"!=typeof t.virtual)throw Error("invalid interface inventory: virtual");return{id:I(t.id),name:B(t.name,"name",1,64),mac:v(t.mac,e=>B(e,"mac",1,64)),addresses:$(t.addresses,"addresses",0,32).map(e=>{let t=b(e,"address");k(t,["family","address","prefix"],"address");let r=A(t.family,["ipv4","ipv6"],"family");return{family:r,address:B(t.address,"address",2,64),prefix:P(t.prefix,"prefix",0,"ipv4"===r?32:128)}}),virtual:t.virtual,state:A(t.state,["up","down","unknown"],"state"),speedMbps:v(t.speedMbps,e=>P(e,"speedMbps",0,1e7))}})),capabilities:l}}function p(e){k(e,["status","usedBytes","totalBytes"],"usage");let t=P(e.totalBytes,"totalBytes",0,Number.MAX_SAFE_INTEGER),r=P(e.usedBytes,"usedBytes",0,Number.MAX_SAFE_INTEGER);if(r>t)throw Error("invalid usage: usedBytes exceeds totalBytes");return{status:"ok",usedBytes:r,totalBytes:t}}function m(e){k(e,["status","usedBytes","totalBytes"],"memory",["pressureSomePercent","pressureFullPercent"]);let t=P(e.totalBytes,"totalBytes",0,Number.MAX_SAFE_INTEGER),r=P(e.usedBytes,"usedBytes",0,Number.MAX_SAFE_INTEGER);if(r>t)throw Error("invalid memory: usedBytes exceeds totalBytes");let n={status:"ok",usedBytes:r,totalBytes:t},i=g(e,"pressureSomePercent",e=>S(e,"pressureSomePercent",0,100));void 0!==i&&(n.pressureSomePercent=i);let s=g(e,"pressureFullPercent",e=>S(e,"pressureFullPercent",0,100));return void 0!==s&&(n.pressureFullPercent=s),n}function y(e,t,r){let n=b(e,t);if("ok"===n.status)return r(n);if("unavailable"===n.status)return k(n,["status","reason"],t),{status:"unavailable",reason:B(n.reason,"reason",0,256)};throw Error(`invalid ${t}: unsupported status ${M(n.status)}`)}function f(e,t,r){let n=b(e,t),i=I(n.id);if("ok"===n.status)return r(n,i);if("unavailable"===n.status)return k(n,["id","status","reason"],t),{id:i,status:"unavailable",reason:B(n.reason,"reason",0,256)};throw Error(`invalid ${t}: unsupported status ${M(n.status)}`)}function h(e){let t=new Set;for(let r of e){if(t.has(r.id))throw Error(`invalid resource list: duplicate id ${M(r.id)}`);t.add(r.id)}return e}function v(e,t){return null===e?null:t(e)}function g(e,t,r){if(w(e,t))return v(e[t],r)}function b(e,t){if(null===e||"object"!=typeof e||Array.isArray(e))throw Error(`invalid ${t}: expected object, got ${M(e)}`);return e}function k(e,t,r,n=[]){let i=new Set([...t,...n]);for(let t of Object.keys(e))if(!i.has(t))throw Error(`invalid ${r}: unexpected field "${t}"`);for(let n of t)if(void 0===e[n])throw Error(`invalid ${r}: missing field "${n}"`);for(let t of n)if(w(e,t)&&void 0===e[t])throw Error(`invalid ${r}: ${t} must not be undefined`)}function w(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function $(e,t,r,n){if(!Array.isArray(e))throw Error(`invalid ${t}: expected array`);if(e.length<r||e.length>n)throw Error(`invalid ${t}: length ${e.length} outside ${r}..${n}`);return e}function A(e,t,r){if("string"!=typeof e||!t.includes(e))throw Error(`invalid ${r}: unexpected value ${M(e)}`);return e}function B(e,t,r,n){if("string"!=typeof e)throw Error(`invalid ${t}: expected string, got ${M(e)}`);if(e.length<r||e.length>n)throw Error(`invalid ${t}: length ${e.length} outside ${r}..${n}`);return e}function E(e,t){return B(e,t,1,128)}function I(e){return B(e,"resource id",1,256)}function P(e,t,r,n){if("number"!=typeof e||!Number.isSafeInteger(e))throw Error(`invalid ${t}: expected safe integer, got ${M(e)}`);if(e<r||e>n)throw Error(`invalid ${t}: ${e} outside ${r}..${n}`);return e}function S(e,t,r,n){if("number"!=typeof e||!Number.isFinite(e))throw Error(`invalid ${t}: expected finite number, got ${M(e)}`);if(e<r||e>n)throw Error(`invalid ${t}: ${e} outside ${r}..${n}`);return e}function x(e,t){let r=B(e,t,1,64),n=Date.parse(r);if(!Number.isFinite(n)||n<i||n>s)throw Error(`invalid ${t}: not a parseable timestamp`);return r}function M(e){try{let t=JSON.stringify(e);if("string"==typeof t)return t.length<=80?t:`${t.slice(0,77)}...`}catch{}return String(e)}function T(e){let t=C(e,"hub snapshot");_(t,["hubRunId","machines"],"hub snapshot");let r=j(t.hubRunId,"hubRunId","hub snapshot");if(!Array.isArray(t.machines))throw Error("invalid snapshot machines");let n=new Set;return{hubRunId:r,machines:t.machines.map(e=>{let t=C(e,"machine");_(t,["machineId","collectorId","hostname","connected","lastReceivedAt"],"machine",["expectedIntervalMs","metrics","inventory","delivery","displayName","tags"]);let r=j(t.machineId,"machineId","machine");if(n.has(r))throw Error("duplicate snapshot machine");n.add(r);let i=j(t.collectorId,"collectorId","machine"),s=z(t.hostname,"hostname","machine",0,N);if("boolean"!=typeof t.connected)throw Error("invalid machine connection state");let a=null===t.lastReceivedAt?null:D(t.lastReceivedAt,"lastReceivedAt","machine"),o={},u=L(t,"expectedIntervalMs")?function(e,t,r){if(null===e)return null;if(void 0===e)throw Error(`invalid ${r}: ${t} must not be undefined`);var n=e,i=t,s=r;if("number"!=typeof n||!Number.isSafeInteger(n))throw Error(`invalid ${s}: ${i} must be a safe integer, got ${G(n)}`);if(n<100||n>72e5)throw Error(`invalid ${s}: ${i} ${n} outside ${100}..${72e5}`);return n}(t.expectedIntervalMs,"expectedIntervalMs","machine"):void 0;if(L(t,"metrics")){let{receivedAt:e,...r}=C(t.metrics,"received metrics"),n=d(r);if("metrics"!==n.kind||n.collectorId!==i)throw Error("conflicting machine metrics");if(o.metrics={...n,receivedAt:D(e,"receivedAt","metrics")},o.metrics.receivedAt!==a)throw Error("conflicting metrics receipt time")}if(L(t,"inventory")&&(o.inventory=c(t.inventory)),L(t,"delivery")){let e=l(t.delivery);if("status"!==e.type)throw Error("invalid delivery status");o.delivery=e}if(L(t,"displayName")&&(o.displayName=null===t.displayName?null:z(t.displayName,"displayName","machine",0,128)),L(t,"tags")){if(!Array.isArray(t.tags)||t.tags.length>32)throw Error("invalid machine tags");o.tags=t.tags.map(e=>z(e,"tag","machine",1,64))}return{machineId:r,collectorId:i,hostname:s,connected:t.connected,lastReceivedAt:a,...void 0===u?{}:{expectedIntervalMs:u},...o}})}}let N=255,R=Date.parse("2000-01-01T00:00:00Z"),F=Date.parse("2200-01-01T00:00:00Z");function C(e,t){if(null===e||"object"!=typeof e||Array.isArray(e))throw Error(`invalid ${t}: expected object, got ${G(e)}`);return e}function _(e,t,r,n=[]){let i=new Set([...t,...n]);for(let t of Object.keys(e))if(!i.has(t))throw Error(`invalid ${r}: unexpected field "${t}"`);for(let n of t)if(void 0===e[n])throw Error(`invalid ${r}: missing field "${n}"`);for(let t of n)if(L(e,t)&&void 0===e[t])throw Error(`invalid ${r}: ${t} must not be undefined`)}function L(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function j(e,t,r){return z(e,t,r,1,128)}function z(e,t,r,n,i){if("string"!=typeof e)throw Error(`invalid ${r}: ${t} must be a string, got ${G(e)}`);if(e.length<n||e.length>i)throw Error(`invalid ${r}: ${t} length ${e.length} outside ${n}..${i}`);return e}function D(e,t,r){let n=z(e,t,r,1,64),i=Date.parse(n);if(!Number.isFinite(i)||i<R||i>F)throw Error(`invalid ${r}: ${t} is not a parseable timestamp`);return n}function G(e){try{let t=JSON.stringify(e);if("string"==typeof t)return t.length<=80?t:`${t.slice(0,77)}...`}catch{}return String(e)}},4334:(e,t,r)=>{r.d(t,{K:()=>o,g:()=>s});var n=r(5887),i=r(7586);function s(e,t){let r=new Map;for(let s of t.machines){let a=s.metrics;if(!a)continue;let o=`${t.hubRunId}\0${a.collectorId}\0${a.runId}\0${a.sequence}`,u=e.get(s.machineId)??[],l=u.at(-1);if(l?.sampleId===o){r.set(s.machineId,u);continue}let d=o.slice(0,o.lastIndexOf("\0")+1),c=l?.sampleId.startsWith(d)?u:[],p=a.payload.memory;r.set(s.machineId,[...c,{sampleId:o,receivedAtLocal:Date.parse(a.receivedAt),cpuPercent:"ok"===a.payload.cpu.status?a.payload.cpu.usagePercent:null,memoryPercent:"ok"===p.status&&p.totalBytes>0?100*p.usedBytes/p.totalBytes:null,gapThresholdMs:(0,i.Cj)(s.expectedIntervalMs)}].slice(-n.yY))}return r}function a(e){return"number"==typeof e.gapThresholdMs&&Number.isFinite(e.gapThresholdMs)&&e.gapThresholdMs>0?e.gapThresholdMs:n.vz}function o(e,t=600,r=140){let i;if(e.length<2)return null;let s=e[0].receivedAtLocal,u=Math.max(1e3,e.at(-1).receivedAtLocal-s),l="";for(let o of e){if(null===o.cpuPercent){i=void 0;continue}let e=4+(o.receivedAtLocal-s)/u*(t-8),d=r-4-o.cpuPercent/100*(r-8),c=i?Math.min(a(i),a(o)):n.vz,p=i&&o.receivedAtLocal>=i.receivedAtLocal&&o.receivedAtLocal-i.receivedAtLocal<c;l+=`${p?"L":"M"}${e.toFixed(2)},${d.toFixed(2)} `,i=o}return l.trim()||null}},5887:(e,t,r)=>{r.d(t,{mq:()=>i,vz:()=>n,yY:()=>s});let n=15e3,i=9e4,s=180},7586:(e,t,r)=>{r.d(t,{$:()=>s,A4:()=>d,Cj:()=>i,F2:()=>m,L_:()=>p,fv:()=>c,l9:()=>u,nV:()=>l,vj:()=>a,z3:()=>o});var n=r(5887);function i(e){return null==e||"number"!=typeof e||!Number.isFinite(e)||e<=0?n.vz:Math.max(n.vz,3*e)}function s(e){if(null==e||!Number.isFinite(e)||e<0)return"—";if(e<6e4){let t=e/1e3;return Number.isInteger(t)?`${t} s`:`${t.toFixed(1)} s`}if(e<36e5){let t=e/6e4;return Number.isInteger(t)?`${t} min`:`${t.toFixed(1)} min`}let t=e/36e5;return Number.isInteger(t)?`${t} h`:`${t.toFixed(1)} h`}function a(e,t,r=i(e.expectedIntervalMs)){return function(e,t,r=n.vz){if(null===e)return"unknown";let i=Date.parse(e);return Number.isFinite(i)?t-i>=r?"stale":"live":"unknown"}(e.lastReceivedAt,t,r)}function o(e){if(null==e||!Number.isFinite(e)||e<0)return"—";let t=["B","KiB","MiB","GiB","TiB"],r=e,n=0;for(;r>=1024&&n<t.length-1;)r/=1024,n+=1;return 0===n?`${Math.round(r)} B`:`${r.toFixed(1)} ${t[n]}`}function u(e){return null!=e&&Number.isFinite(e)?`${e.toFixed(1)}%`:"—"}function l(e){if(null===e||!Number.isFinite(e))return"—";let t=Math.round(Math.max(0,e)/1e3);if(t<60)return`${t}s ago`;let r=Math.floor(t/60);if(r<60){let e=t%60;return 0===e?`${r}m ago`:`${r}m ${e}s ago`}let n=Math.floor(r/60);if(n<24){let e=r%60;return 0===e?`${n}h ago`:`${n}h ${e}m ago`}let i=Math.floor(n/24),s=n%24;return 0===s?`${i}d ago`:`${i}d ${s}h ago`}function d(e,t){if(null===e)return null;let r=Date.parse(e);return Number.isFinite(r)?t-r:null}function c(e){switch(e){case"ok":return"Live";case"degraded":return"Degraded";case"stale":return"Stale";case"disconnected":return"Disconnected";case"unseen":return"No sample yet"}}function p(e,t=n.mq){let r=e.metrics;if(!r)return{kind:"unknown"};let i=Date.parse(r.capturedAt),s=Date.parse(r.receivedAt);if(!Number.isFinite(i)||!Number.isFinite(s))return{kind:"unknown"};let a=i-s;return a>t?{kind:"ahead",skewMs:a}:a<-t?{kind:"behind",skewMs:-a}:{kind:"none"}}function m(e,t,r=i(e.expectedIntervalMs)){return{freshness:a(e,t,r),attention:function(e,t,r=i(e.expectedIntervalMs)){var n;let s=a(e,t,r);return e.metrics&&"unknown"!==s?"stale"===s?"stale":(n=e.metrics)&&("unavailable"===n.payload.cpu.status||"unavailable"===n.payload.memory.status)?"degraded":e.connected?"ok":"disconnected":"unseen"}(e,t,r),connection:e.connected?"connected":"disconnected",skew:p(e),thresholdMs:r}}},9939:(e,t,r)=>{r.d(t,{LiveProvider:()=>g,M:()=>v});var n=r(5155),i=r(2115);async function s(e){try{let t=await e.json();if(t&&"object"==typeof t&&"error"in t){let e=t.error;if("string"==typeof e)return e}}catch{}return e.statusText||null}function a(e){if(!e||"object"!=typeof e)return null;let t=e.username,r=e.csrfToken;if("string"!=typeof t||0===t.length||"string"!=typeof r||0===r.length)return null;let n=e.admin;return void 0!==n&&"boolean"!=typeof n?null:{username:t,csrfToken:r,...!0===n?{admin:!0}:{}}}async function o(){try{let e=await fetch("/api/auth/session",{cache:"no-store",signal:AbortSignal.timeout(5e3),headers:{Accept:"application/json"}});if(e.ok){try{let t=a(await e.json());if(t)return{kind:"signed-in",user:t}}catch{}return{kind:"error",detail:"unexpected session response"}}if(401===e.status)return{kind:"signed-out"};if(503===e.status)return{kind:"auth-unavailable",detail:await s(e)};return{kind:"error",detail:`unexpected status ${e.status}`}}catch{return{kind:"unreachable"}}}async function u(e,t){try{let r=await fetch("/api/auth/login",{method:"POST",cache:"no-store",signal:AbortSignal.timeout(5e3),headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify({username:e,password:t})});if(r.ok){try{let e=a(await r.json());if(e)return{kind:"signed-in",user:e}}catch{}return{kind:"error",detail:"unexpected sign-in response"}}if(401===r.status)return{kind:"invalid-credentials",detail:await s(r)};if(429===r.status)return{kind:"error",detail:"too many attempts — wait a moment and try again"};if(503===r.status)return{kind:"auth-unavailable",detail:await s(r)};return{kind:"error",detail:`unexpected status ${r.status}`}}catch{return{kind:"unreachable"}}}async function l(e){try{let t=await fetch("/api/auth/logout",{method:"POST",cache:"no-store",signal:AbortSignal.timeout(5e3),headers:{"X-CSRF-Token":e,Accept:"application/json"}});if(t.ok||401===t.status)return"ok";return t.status,{kind:"error",detail:`unexpected status ${t.status}`}}catch{return"unreachable"}}async function d(e=5e3){let t=new AbortController,r=setTimeout(()=>t.abort(),e);try{let e=await fetch("/api/live",{cache:"no-store",signal:t.signal,headers:{Accept:"text/event-stream"}});if(200===e.status)return{kind:"ok"};if(401===e.status)return{kind:"unauthorized"};if(403===e.status)return{kind:"forbidden"};return{kind:"error",detail:`unexpected status ${e.status}`}}catch{return{kind:"unreachable"}}finally{clearTimeout(r);try{t.abort()}catch{}}}var c=r(592);let p=["snapshot","update","reset"];class m{source=null;lastId=null;get lastEventId(){return this.lastId}get isOpen(){return null!==this.source&&2!==this.source.readyState}start(e,t){this.close();let r=new EventSource(e);this.source=r;let n=e=>n=>{if(this.source!==r||null===this.source)return;let i=n.lastEventId;"string"==typeof i&&i.length>0&&(this.lastId=i);try{t.onSnapshot((0,c.FZ)(JSON.parse(n.data)),e,i??"")}catch{this.close(),t.onError()}};for(let e of p)r.addEventListener(e,n(e));r.onerror=()=>{this.source===r&&(this.close(),t.onError())}}close(){this.source?.close(),this.source=null}}var y=r(4334);let f=[1e3,2e3,4e3,8e3,15e3],h=(0,i.createContext)(null);function v(){let e=(0,i.useContext)(h);if(!e)throw Error("useLive must be used inside <LiveProvider>");return e}function g({children:e}){let[t,r]=(0,i.useState)({kind:"restoring"}),[s,a]=(0,i.useState)("idle"),[c,p]=(0,i.useState)(null),[v,b]=(0,i.useState)([]),[k,w]=(0,i.useState)(null),[$,A]=(0,i.useState)(null),[B,E]=(0,i.useState)(null),[I,P]=(0,i.useState)(null),[S,x]=(0,i.useState)(new Map),M=(0,i.useRef)(!0),T=(0,i.useRef)(0),N=(0,i.useRef)(null),R=(0,i.useRef)(null),F=(0,i.useRef)(0),C=(0,i.useRef)(t),_=(0,i.useRef)(0),L=(0,i.useRef)(!1),j=(0,i.useRef)(!1),z=e=>{C.current=e,r(e)},D=()=>{null!==R.current&&(clearTimeout(R.current),R.current=null)},G=()=>{_.current+=1,T.current+=1,N.current?.close(),N.current=null,D()},X=()=>{L.current=!1,b([]),x(new Map),p(null),A(null),E(null),w(null)},O=e=>{let t=C.current;("signed-in"!==t.kind||t.user.csrfToken!==e.csrfToken)&&X(),z({kind:"signed-in",user:e})},U=()=>{if("signed-in"!==C.current.kind)return;G(),a(L.current?"reconnecting":"connecting");let e=new m;N.current=e,e.start("/api/live",{onSnapshot:(t,r,n)=>{M.current&&N.current===e&&(F.current=0,L.current=!0,p(t.hubRunId),b(t.machines),w(Date.now()),A(n||null),E(r),x(e=>(0,y.g)(e,t)),a("live"))},onError:()=>{M.current&&N.current===e&&W()}})},W=async()=>{G();let e=_.current;a("reconnecting");let t=await o();if(!M.current||e!==_.current)return;if("signed-in"===t.kind)O(t.user);else if("signed-out"===t.kind){z({kind:"signed-out"}),X(),a("expired");return}else if("auth-unavailable"===t.kind){z({kind:"auth-unavailable",detail:t.detail}),X(),a("idle");return}let r=Math.min(F.current+1,f.length+1);if(F.current=r,r>f.length){a("disconnected"),Z();return}a("reconnecting"),R.current=setTimeout(()=>{q()},f[r-1])},q=async()=>{if(!M.current||j.current)return;G();let e=_.current,t=await d();if(M.current&&e===_.current){if("forbidden"===t.kind){X(),a("no-permission");return}if("unauthorized"===t.kind){let t=await o();if(!M.current||e!==_.current)return;if("signed-in"===t.kind){O(t.user),U();return}if("signed-out"===t.kind){z({kind:"signed-out"}),X(),a("expired");return}if("auth-unavailable"===t.kind){z({kind:"auth-unavailable",detail:t.detail}),X(),a("idle");return}}U()}},Z=()=>{D(),R.current=setTimeout(()=>{J()},3e4)},J=async()=>{if(!M.current||j.current)return;G();let e=_.current,t=await o();if(M.current&&e===_.current){if("signed-in"===t.kind){O(t.user),U();return}if("signed-out"===t.kind){z({kind:"signed-out"}),X(),a("expired");return}if("auth-unavailable"===t.kind){z({kind:"auth-unavailable",detail:t.detail}),X(),a("idle");return}Z()}},K=async e=>{let t=_.current,r=await o();if(M.current&&T.current===e&&t===_.current){if("signed-in"===r.kind){O(r.user),F.current=0,U();return}if("signed-out"===r.kind){z({kind:"signed-out"}),a("idle");return}if("auth-unavailable"===r.kind){z({kind:"auth-unavailable",detail:r.detail}),a("idle");return}z({kind:"restore-failed",detail:"error"===r.kind?r.detail:null}),a("idle")}},Y=async(e,t)=>{G();let r=_.current,n=await u(e,t);return M.current&&r===_.current?("signed-in"===n.kind&&M.current&&(O(n.user),P(null),F.current=0,U()),n):{kind:"error",detail:"Session request superseded. Try again."}},Q=async()=>{let e=C.current;if("signed-in"!==e.kind||j.current)return;j.current=!0,G();let t=_.current;X(),a("idle");let r=await l(e.user.csrfToken);if(j.current=!1,!M.current||t!==_.current)return;if("ok"===r){z({kind:"signed-out"}),X(),a("idle"),P("Signed out.");return}let n=await o();if(M.current&&t===_.current){if("signed-in"===n.kind){O(n.user),P("Sign out failed — try again."),U();return}if("signed-out"===n.kind){z({kind:"signed-out"}),X(),a("idle"),P(null);return}if("auth-unavailable"===n.kind){z({kind:"auth-unavailable",detail:n.detail}),a("idle");return}z({kind:"restore-failed",detail:"error"===n.kind?n.detail:null}),a("idle")}};return(0,i.useEffect)(()=>(M.current=!0,T.current+=1,K(T.current),()=>{M.current=!1,G()}),[]),(0,n.jsx)(h.Provider,{value:{session:t,phase:s,hubRunId:c,machines:v,lastReceivedLocalMs:k,lastEventId:$,lastEventKind:B,notice:I,history:S,login:Y,signOut:Q,retryConnection:()=>{if(M.current&&!j.current){if("signed-in"===C.current.kind){F.current=0,q();return}T.current+=1,K(T.current)}},dismissNotice:()=>P(null)},children:e})}}}]);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[572],{3092:(e,s,t)=>{"use strict";t.d(s,{A:()=>n});let n=(0,t(8698).A)("ShieldCheck",[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z",key:"oel41y"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]])},3612:(e,s,t)=>{"use strict";t.d(s,{A:()=>n});let n=(0,t(8698).A)("Plus",[["path",{d:"M5 12h14",key:"1ays0h"}],["path",{d:"M12 5v14",key:"s699le"}]])},5773:(e,s,t)=>{"use strict";t.d(s,{ConnectionsView:()=>A});var n=t(5155),a=t(8500),l=t.n(a),r=t(438),d=t(3092),i=t(3612),c=t(7233),x=t(4173),o=t(3440),m=t(940),u=t(802),h=t(6576),g=t(6582),f=t(7586),p=t(9939),j=t(7348),N=t(2241);function b(e){if(null===e)return"—";let s=new Date(e);return Number.isFinite(s.getTime())?s.toLocaleString(void 0,{dateStyle:"medium",timeStyle:"medium"}):e}function v({capability:e}){var s;return(0,n.jsxs)("li",{className:"rounded-md border border-border bg-card-strong/30 px-2 py-1.5",children:[(0,n.jsxs)("div",{className:"flex items-center justify-between gap-2",children:[(0,n.jsx)("span",{className:"text-xs text-foreground",children:e.label}),(0,n.jsx)("span",{className:`tnum rounded-full px-1.5 py-0.5 text-xs ${"supported"===(s=e.status)?"bg-ok-soft text-ok":"unsupported"===s?"bg-warn-soft text-warn":"border border-border text-muted-foreground"}`,children:e.status})]}),e.reason?(0,n.jsx)("p",{className:"mt-1 text-xs text-muted-foreground",children:e.reason}):null]})}function y({view:e,now:s}){return(0,n.jsxs)("div",{className:"flex flex-wrap items-center gap-x-4 gap-y-2",children:[(0,n.jsxs)("span",{className:"tnum inline-flex items-center gap-1.5 text-sm",children:[(0,n.jsx)("span",{"aria-hidden":!0,className:`inline-block h-2 w-2 shrink-0 rounded-full ${e.connected?"bg-ok":"bg-crit"}`}),e.connected?"Connected":"Disconnected"]}),(0,n.jsxs)("span",{className:"tnum text-xs text-muted-foreground",children:["Interval ",null===e.cadenceMs?"unknown":(0,f.$)(e.cadenceMs)]}),(0,n.jsxs)("span",{className:"tnum text-xs text-muted-foreground",children:["Updated ",(0,f.nV)((0,f.A4)(e.lastReceivedAt,s))]}),"stale"===e.freshness?(0,n.jsx)(h.vw,{text:"stale",title:`No update for over ${(0,f.$)(e.thresholdMs)}`}):"unknown"===e.freshness?(0,n.jsx)(h.vw,{text:"no sample",title:"No receipt timestamp is available yet"}):null]})}function w({view:e}){if(null===e.delivery)return(0,n.jsx)("p",{className:"text-xs text-muted-foreground",children:"Delivery status unavailable."});let s=e.delivery;return(0,n.jsxs)("dl",{className:"grid gap-x-6 gap-y-2 sm:grid-cols-2",children:[(0,n.jsxs)("div",{className:"flex items-baseline justify-between gap-3",children:[(0,n.jsx)("dt",{className:"text-xs text-muted-foreground",children:"Backlog records"}),(0,n.jsx)("dd",{className:"tnum text-xs text-foreground",children:s.pendingRecords})]}),(0,n.jsxs)("div",{className:"flex items-baseline justify-between gap-3",children:[(0,n.jsx)("dt",{className:"text-xs text-muted-foreground",children:"Backlog bytes"}),(0,n.jsx)("dd",{className:"tnum text-xs text-foreground",children:(0,f.z3)(s.pendingBytes)})]}),(0,n.jsxs)("div",{className:"flex items-baseline justify-between gap-3",children:[(0,n.jsx)("dt",{className:"text-xs text-muted-foreground",children:"In-flight records"}),(0,n.jsx)("dd",{className:"tnum text-xs text-foreground",children:s.inFlightRecords})]}),(0,n.jsxs)("div",{className:"flex items-baseline justify-between gap-3",children:[(0,n.jsx)("dt",{className:"text-xs text-muted-foreground",children:"Oldest pending"}),(0,n.jsx)("dd",{className:"tnum text-right text-xs text-foreground",children:b(s.oldestPendingCapturedAt)})]}),(0,n.jsxs)("div",{className:"flex items-baseline justify-between gap-3",children:[(0,n.jsx)("dt",{className:"text-xs text-muted-foreground",children:"Last acknowledgment"}),(0,n.jsx)("dd",{className:"tnum text-right text-xs text-foreground",children:null===s.lastAckAt?"No acknowledgment yet":b(s.lastAckAt)})]}),(0,n.jsxs)("div",{className:"flex items-baseline justify-between gap-3",children:[(0,n.jsx)("dt",{className:"text-xs text-muted-foreground",children:"Dropped / gap records"}),(0,n.jsx)("dd",{className:"tnum text-xs text-foreground",children:s.droppedRecords})]}),(0,n.jsxs)("div",{className:"flex items-baseline justify-between gap-3",children:[(0,n.jsx)("dt",{className:"text-xs text-muted-foreground",children:"Replaying"}),(0,n.jsx)("dd",{className:"tnum text-xs text-foreground",children:s.replaying?"Yes":"No"})]}),(0,n.jsxs)("div",{className:"flex items-baseline justify-between gap-3",children:[(0,n.jsx)("dt",{className:"text-xs text-muted-foreground",children:"Status captured"}),(0,n.jsx)("dd",{className:"tnum text-right text-xs text-foreground",children:b(e.deliveryCapturedAt)})]})]})}function k({view:e,now:s}){return(0,n.jsx)(o.Z,{title:e.name,action:(0,n.jsx)(l(),{href:`/machines/${encodeURIComponent(e.machineId)}`,className:"text-xs text-accent hover:underline",children:"Open machine"}),children:(0,n.jsxs)("div",{className:"flex flex-col gap-5",children:[(0,n.jsx)(y,{view:e,now:s}),(0,n.jsxs)("section",{"aria-label":`${e.name} delivery`,children:[(0,n.jsxs)("h3",{className:"flex items-center gap-1.5 text-xs font-medium text-muted-foreground",children:[(0,n.jsx)(r.A,{className:"h-3.5 w-3.5","aria-hidden":!0}),"Delivery"]}),(0,n.jsx)("div",{className:"mt-2",children:(0,n.jsx)(w,{view:e})})]}),(0,n.jsxs)("section",{"aria-label":`${e.name} collection failures`,className:"border-t border-border pt-4",children:[(0,n.jsx)("h3",{className:"text-xs font-medium text-muted-foreground",children:"Collection failures"}),null===e.delivery?(0,n.jsx)("p",{className:"mt-2 text-xs text-muted-foreground",children:"Failure status unavailable."}):0===e.failures.length?(0,n.jsx)("p",{className:"mt-2 text-xs text-muted-foreground",children:"None"}):(0,n.jsx)("ul",{className:"mt-2 flex flex-col gap-1.5",children:e.failures.map((e,s)=>(0,n.jsxs)("li",{className:"text-xs",children:[(0,n.jsx)("span",{className:"text-foreground",children:e.metric}),e.resourceId?(0,n.jsxs)("span",{className:"tnum font-mono text-xs text-muted-foreground",children:[" \xb7 ",e.resourceId]}):null,(0,n.jsxs)("span",{className:"text-muted-foreground",children:[" — ",e.reason]})]},`${e.metric}\0${e.resourceId??""}\0${e.reason}\0${s}`))})]}),(0,n.jsxs)("section",{"aria-label":`${e.name} capabilities`,className:"border-t border-border pt-4",children:[(0,n.jsxs)("h3",{className:"flex items-center gap-1.5 text-xs font-medium text-muted-foreground",children:[(0,n.jsx)(d.A,{className:"h-3.5 w-3.5","aria-hidden":!0}),"Capabilities"]}),null===e.capabilities?(0,n.jsx)("p",{className:"mt-2 text-xs text-muted-foreground",children:"Capabilities unavailable."}):(0,n.jsx)("ul",{className:"mt-2 grid gap-1.5 sm:grid-cols-2 xl:grid-cols-3",children:e.capabilities.map(e=>(0,n.jsx)(v,{capability:e},e.key))})]})]})})}function A(){let{phase:e,machines:s,session:t}=(0,p.M)(),a=(0,g.x)(),r=(0,j.c)(),d=(0,N.cy)(s,r);return(0,n.jsx)(x.G,{title:"Connections",headerRight:(0,n.jsxs)(n.Fragment,{children:["signed-in"===t.kind&&t.user.admin?(0,n.jsxs)(l(),{href:"/administration#collectors",className:"inline-flex items-center gap-1.5 rounded-md border border-border px-2.5 py-1.5 text-xs hover:bg-card-strong",children:[(0,n.jsx)(i.A,{className:"h-3.5 w-3.5","aria-hidden":!0}),"Add machine"]}):null,(0,n.jsx)(m.$E,{}),(0,n.jsx)(u.B,{})]}),children:a||(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(g.E,{}),("live"===e||"reconnecting"===e||"disconnected"===e)&&0===s.length?(0,n.jsx)(o.Z,{title:"No collectors",className:"mt-8",children:(0,n.jsx)(o.p,{icon:c.A,note:"No collectors are connected to this hub in your authorized scope."})}):null,0===d.length&&"connecting"===e?(0,n.jsx)(o.Z,{title:"Waiting for collector status",className:"mt-8",children:(0,n.jsx)(o.p,{icon:c.A,note:"Waiting for the first snapshot…"})}):null,d.length>0?(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)("h2",{className:"mb-4 text-sm font-medium tracking-tight",children:"Collectors"}),(0,n.jsx)("div",{className:"grid gap-5 xl:grid-cols-[repeat(auto-fit,minmax(30rem,1fr))]",children:d.map(e=>(0,n.jsx)(k,{view:e,now:r},e.machineId))})]}):null]})})}},6351:(e,s,t)=>{Promise.resolve().then(t.bind(t,5773))}},e=>{e.O(0,[944,939,103,130,441,794,358],()=>e(e.s=6351)),_N_E=e.O()}]);
|
|
File without changes
|
|
File without changes
|