@stacksjs/ts-cloud 0.7.104 → 0.7.108

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.
Files changed (83) hide show
  1. package/dist/aws/index.js +2 -14
  2. package/dist/bin/cli.js +784 -785
  3. package/dist/bin/dashboard-server.js +291 -291
  4. package/dist/{chunk-dmzez5f8.js → chunk-33sy72r8.js} +52 -3
  5. package/dist/{chunk-5cz14833.js → chunk-ef2dwfrg.js} +3 -3
  6. package/dist/{chunk-gtz4jar2.js → chunk-eg8ck25v.js} +859 -151
  7. package/dist/{chunk-6hymgy61.js → chunk-gttvakpv.js} +1 -85
  8. package/dist/{chunk-adgz2c49.js → chunk-s9c2x5jv.js} +2 -2
  9. package/dist/deploy/dashboard-bundle-encoding.test.d.ts +1 -0
  10. package/dist/deploy/dashboard-payload.d.ts +20 -0
  11. package/dist/deploy/dashboard-payload.test.d.ts +1 -0
  12. package/dist/deploy/dashboard-topology.d.ts +85 -0
  13. package/dist/deploy/dashboard-topology.test.d.ts +1 -0
  14. package/dist/deploy/egress-collection.d.ts +60 -0
  15. package/dist/deploy/index.d.ts +2 -0
  16. package/dist/deploy/index.js +16 -4
  17. package/dist/dns/index.d.ts +0 -2
  18. package/dist/dns/index.js +2 -14
  19. package/dist/drivers/index.js +1 -1
  20. package/dist/index.d.ts +1 -2
  21. package/dist/index.js +5 -17
  22. package/dist/ui/404.html +1 -0
  23. package/dist/ui/access-denied.html +2 -2
  24. package/dist/ui/account/automation.html +4 -4
  25. package/dist/ui/account/security.html +2 -2
  26. package/dist/ui/applications/compose.html +3 -3
  27. package/dist/ui/applications/new.html +2 -2
  28. package/dist/ui/data/backups.html +22 -9
  29. package/dist/ui/data/services.html +22 -9
  30. package/dist/ui/data/volumes.html +22 -9
  31. package/dist/ui/index.html +22 -9
  32. package/dist/ui/infrastructure/topology.html +2223 -0
  33. package/dist/ui/integrations.html +2 -2
  34. package/dist/ui/operations/alerts.html +22 -9
  35. package/dist/ui/operations/configuration.html +4 -4
  36. package/dist/ui/operations/jobs.html +22 -9
  37. package/dist/ui/operations/maintenance.html +4 -4
  38. package/dist/ui/operations/observability.html +21 -8
  39. package/dist/ui/operations/previews.html +4 -4
  40. package/dist/ui/operations/queue.html +4 -4
  41. package/dist/ui/operations/regions.html +3 -3
  42. package/dist/ui/operations/releases.html +4 -4
  43. package/dist/ui/operations/workloads.html +22 -9
  44. package/dist/ui/security.html +2 -2
  45. package/dist/ui/server/actions.html +4 -4
  46. package/dist/ui/server/activity.html +2 -2
  47. package/dist/ui/server/capacity.html +4 -4
  48. package/dist/ui/server/database.html +4 -4
  49. package/dist/ui/server/deployments.html +4 -4
  50. package/dist/ui/server/diagnostics.html +2 -2
  51. package/dist/ui/server/firewall.html +3 -3
  52. package/dist/ui/server/fleet.html +22 -9
  53. package/dist/ui/server/logs.html +22 -9
  54. package/dist/ui/server/metrics.html +130 -14
  55. package/dist/ui/server/security.html +2 -2
  56. package/dist/ui/server/services.html +2 -2
  57. package/dist/ui/server/sites.html +21 -8
  58. package/dist/ui/server/ssh-keys.html +4 -4
  59. package/dist/ui/server/team.html +4 -4
  60. package/dist/ui/server/terminal.html +1 -1
  61. package/dist/ui/serverless/alarms.html +4 -4
  62. package/dist/ui/serverless/assets.html +2 -2
  63. package/dist/ui/serverless/cost.html +1 -1
  64. package/dist/ui/serverless/data.html +4 -4
  65. package/dist/ui/serverless/deployments.html +2 -2
  66. package/dist/ui/serverless/firewall.html +2 -2
  67. package/dist/ui/serverless/functions.html +4 -4
  68. package/dist/ui/serverless/logs.html +22 -9
  69. package/dist/ui/serverless/metrics.html +2 -2
  70. package/dist/ui/serverless/queues.html +4 -4
  71. package/dist/ui/serverless/secrets.html +4 -4
  72. package/dist/ui/serverless/traces.html +4 -4
  73. package/dist/ui/serverless.html +4 -4
  74. package/dist/ui-src/functions/requestJson.ts +41 -0
  75. package/dist/ui-src/functions/useOpConfirm.ts +132 -0
  76. package/dist/ui-src/functions/usePolling.ts +98 -0
  77. package/dist/ui-src/functions/useTerminal.ts +79 -0
  78. package/dist/ui-src/pages/infrastructure/topology.stx +517 -0
  79. package/dist/ui-src/pages/partials/nav.stx +2 -0
  80. package/dist/ui-src/pages/server/metrics.stx +140 -6
  81. package/dist/ui-src/src/topology.ts +485 -0
  82. package/package.json +3 -3
  83. package/dist/dns/mail-records.d.ts +0 -82
@@ -25,6 +25,27 @@ const bytes = value => {
25
25
  if (n >= 1024 ** 2) return (n / 1024 ** 2).toFixed(1) + ' MB'
26
26
  return Math.round(n / 1024) + ' KB'
27
27
  }
28
+ const rate = value => {
29
+ const n = Number(value || 0)
30
+ if (n >= 1024 ** 3) return (n / 1024 ** 3).toFixed(2) + ' GB/s'
31
+ if (n >= 1024 ** 2) return (n / 1024 ** 2).toFixed(1) + ' MB/s'
32
+ if (n >= 1024) return Math.round(n / 1024) + ' KB/s'
33
+ return Math.round(n) + ' B/s'
34
+ }
35
+ const monthBytes = Number(m.networkRxBytesMonth || 0) + Number(m.networkTxBytesMonth || 0)
36
+ // Providers quote allowances in decimal TB and bill overage the same way, so the
37
+ // projection has to use the same unit the invoice will.
38
+ const bandwidthBudget = Number(__d?.bandwidthBudgetBytes || 0)
39
+ const bandwidthNote = (() => {
40
+ const now = new Date()
41
+ const dayOfMonth = now.getUTCDate()
42
+ const daysInMonth = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 1, 0)).getUTCDate()
43
+ const projected = dayOfMonth > 0 ? monthBytes / dayOfMonth * daysInMonth : monthBytes
44
+ const tb = value => (value / 1000 ** 4).toFixed(2) + ' TB'
45
+ if (!bandwidthBudget) return `On track for ${tb(projected)} by month end`
46
+ const pct = Math.round(monthBytes / bandwidthBudget * 100)
47
+ return `${pct}% of ${tb(bandwidthBudget)} — on track for ${tb(projected)}`
48
+ })()
28
49
  const memUsedGb = gb(m.memUsedMb)
29
50
  const memTotalGb = gb(m.memTotalMb)
30
51
  const memAvailableGb = gb(m.memAvailableMb || Math.max(0, m.memTotalMb - m.memUsedMb))
@@ -75,6 +96,10 @@ function historyParams() {
75
96
  'host.memory.used_percent', 'host.memory.used', 'host.memory.total',
76
97
  'host.disk.used_percent', 'host.swap.used_percent',
77
98
  'host.disk.inode_used_percent',
99
+ 'host.network.rx_rate', 'host.network.tx_rate',
100
+ 'storage.egress.day', 'storage.egress.month',
101
+ 'storage.egress.budget', 'storage.egress.budget_used_percent',
102
+ 'storage.egress.projected_month',
78
103
  ].join(','),
79
104
  aggregation: 'avg',
80
105
  bucketMs: String(bucketMs),
@@ -117,6 +142,27 @@ function liveBytes(value) {
117
142
  if (n >= 1024 ** 2) return (n / 1024 ** 2).toFixed(1) + ' MB'
118
143
  return Math.round(n / 1024) + ' KB'
119
144
  }
145
+ function liveRate(value) {
146
+ const n = Number(value || 0)
147
+ if (n >= 1024 ** 3) return (n / 1024 ** 3).toFixed(2) + ' GB/s'
148
+ if (n >= 1024 ** 2) return (n / 1024 ** 2).toFixed(1) + ' MB/s'
149
+ if (n >= 1024) return Math.round(n / 1024) + ' KB/s'
150
+ return Math.round(n) + ' B/s'
151
+ }
152
+ function liveMonthBytes() {
153
+ return Number(liveMetric().networkRxBytesMonth || 0) + Number(liveMetric().networkTxBytesMonth || 0)
154
+ }
155
+ function liveBandwidthNote() {
156
+ const used = liveMonthBytes()
157
+ const budget = Number(currentData()?.bandwidthBudgetBytes || 0)
158
+ const now = new Date()
159
+ const dayOfMonth = now.getUTCDate()
160
+ const daysInMonth = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 1, 0)).getUTCDate()
161
+ const projected = dayOfMonth > 0 ? used / dayOfMonth * daysInMonth : used
162
+ const tb = value => (value / 1000 ** 4).toFixed(2) + ' TB'
163
+ if (!budget) return `On track for ${tb(projected)} by month end`
164
+ return `${Math.round(used / budget * 100)}% of ${tb(budget)} — on track for ${tb(projected)}`
165
+ }
120
166
  const liveCards = derived(() => [
121
167
  { label: 'CPU utilization', pct: liveCpu(), detail: `${liveMetric().load} load across ${liveMetric().cpus} vCPU` },
122
168
  { label: 'Memory', pct: liveMemory(), detail: `${liveGb(liveMetric().memUsedMb)} / ${liveGb(liveMetric().memTotalMb)} GB` },
@@ -133,7 +179,50 @@ function ratioPoints(primary, numerator, denominator, multiplier = 100) {
133
179
  .filter(point => Number(den.get(point.timestamp)) > 0)
134
180
  .map(point => ({ ...point, value: Math.max(0, Math.min(100, Number(point.value) / Number(den.get(point.timestamp)) * multiplier)) }))
135
181
  }
182
+ const RATE_METRICS = { 'network-in': 'host.network.rx_rate', 'network-out': 'host.network.tx_rate' }
183
+ const BYTE_METRICS = { 'egress-daily': 'storage.egress.day' }
184
+ function isByteMetric(name) { return Object.hasOwn(BYTE_METRICS, name) }
185
+ function bytesLabel(value) {
186
+ const n = Number(value || 0)
187
+ if (n >= 1000 ** 4) return (n / 1000 ** 4).toFixed(2) + ' TB'
188
+ if (n >= 1000 ** 3) return (n / 1000 ** 3).toFixed(1) + ' GB'
189
+ if (n >= 1000 ** 2) return (n / 1000 ** 2).toFixed(1) + ' MB'
190
+ return Math.round(n / 1000) + ' KB'
191
+ }
192
+ function latestValue(name) {
193
+ const values = points(name)
194
+ return values.length ? Number(values.at(-1).value) : null
195
+ }
196
+ function egressBudget() { return latestValue('storage.egress.budget') }
197
+ function egressMonth() { return latestValue('storage.egress.month') }
198
+ function egressProjected() { return latestValue('storage.egress.projected_month') }
199
+ function egressPercent() { return latestValue('storage.egress.budget_used_percent') }
200
+ function hasEgress() { return points('storage.egress.day').length > 0 || egressMonth() != null }
201
+ function egressTone() {
202
+ const pct = egressPercent()
203
+ if (pct == null) return 'ok'
204
+ return pct >= 90 ? 'bad' : pct >= 75 ? 'warn' : 'ok'
205
+ }
206
+ function egressSummary() {
207
+ const month = egressMonth()
208
+ if (month == null) return 'No egress reported yet.'
209
+ const budget = egressBudget()
210
+ const projected = egressProjected()
211
+ const projectedNote = projected == null ? '' : ` — on track for ${bytesLabel(projected)}`
212
+ if (!budget) return `${bytesLabel(month)} this month${projectedNote}`
213
+ return `${bytesLabel(month)} of ${bytesLabel(budget)} (${egressPercent() ?? 0}%)${projectedNote}`
214
+ }
215
+ // A projection above the allowance is the whole point of the panel: it is the
216
+ // window in which the overrun is still avoidable.
217
+ function egressWillExceed() {
218
+ const budget = egressBudget()
219
+ const projected = egressProjected()
220
+ return !!budget && projected != null && projected > budget
221
+ }
222
+ function isRateMetric(name) { return Object.hasOwn(RATE_METRICS, name) }
136
223
  function metricPoints(name) {
224
+ if (isRateMetric(name)) return points(RATE_METRICS[name])
225
+ if (isByteMetric(name)) return points(BYTE_METRICS[name])
137
226
  if (name === 'cpu') return ratioPoints('host.cpu.used_percent', 'host.load', 'host.cpu.capacity')
138
227
  if (name === 'memory') return ratioPoints('host.memory.used_percent', 'host.memory.used', 'host.memory.total')
139
228
  if (name === 'disk') return points('host.disk.used_percent')
@@ -144,7 +233,9 @@ function metricPoints(name) {
144
233
  function linePath(metric) {
145
234
  const values = metricPoints(metric)
146
235
  if (!values.length) return ''
147
- const max = metric === 'load' ? Math.max(1, ...values.map(point => Number(point.value))) : 100
236
+ const max = metric === 'load' || isRateMetric(metric) || isByteMetric(metric)
237
+ ? Math.max(1, ...values.map(point => Number(point.value)))
238
+ : 100
148
239
  return values.map((point, index) => {
149
240
  const x = values.length === 1 ? 50 : index / (values.length - 1) * 100
150
241
  const y = 42 - Math.max(0, Math.min(1, Number(point.value) / max)) * 38
@@ -163,7 +254,12 @@ function peak(metric) {
163
254
  const values = metricPoints(metric)
164
255
  return values.length ? Math.max(...values.map(point => Number(point.value))) : null
165
256
  }
166
- function display(value, suffix = '%') { return value == null ? 'No data' : `${value.toFixed(value >= 10 ? 0 : 1)}${suffix}` }
257
+ function display(value, suffix = '%') {
258
+ if (value == null) return 'No data'
259
+ if (suffix === 'rate') return liveRate(value)
260
+ if (suffix === 'bytes') return bytesLabel(value)
261
+ return `${value.toFixed(value >= 10 ? 0 : 1)}${suffix}`
262
+ }
167
263
  function samples(metric) { return metricPoints(metric).length }
168
264
  const historyCards = [
169
265
  { key: 'cpu', label: 'CPU utilization', note: 'Actual busy time, with load-per-core fallback for older samples', tone: 'blue' },
@@ -172,6 +268,8 @@ const historyCards = [
172
268
  { key: 'swap', label: 'Swap usage', note: 'Swap pressure across the selected period', tone: 'violet' },
173
269
  { key: 'load', label: 'Load average', note: 'One-minute runnable and waiting task average', tone: 'cyan', suffix: '' },
174
270
  { key: 'inodes', label: 'Inode usage', note: 'Root filesystem inode capacity consumed', tone: 'rose' },
271
+ { key: 'network-out', label: 'Network out', note: 'Egress throughput — the metric providers meter and bill', tone: 'amber', suffix: 'rate' },
272
+ { key: 'network-in', label: 'Network in', note: 'Ingress throughput across all non-loopback interfaces', tone: 'blue', suffix: 'rate' },
175
273
  ]
176
274
  function setRange(value) { historyRange.set(value); refresh() }
177
275
  </script>
@@ -214,8 +312,9 @@ function setRange(value) { historyRange.set(value); refresh() }
214
312
  <div class="vital"><div class="vlabel">Memory available</div><div class="vval">{{ liveGb(liveMetric().memAvailableMb) }} <small>GB</small></div><div class="vnote">{{ liveGb(liveMetric().memCacheMb) }} GB cache</div></div>
215
313
  <div class="vital"><div class="vlabel">Disk inodes</div><div class="vval">{{ liveMetric().inodeUsedPct }}<small>% used</small></div><div class="vnote">Filesystem entry capacity</div></div>
216
314
  <div class="vital"><div class="vlabel">Processes</div><div class="vval">{{ liveMetric().processes }}</div><div class="vnote">Current process count</div></div>
217
- <div class="vital"><div class="vlabel">Network received</div><div class="vval">{{ liveBytes(liveMetric().networkRxBytes) }}</div><div class="vnote">Since host boot</div></div>
218
- <div class="vital"><div class="vlabel">Network sent</div><div class="vval">{{ liveBytes(liveMetric().networkTxBytes) }}</div><div class="vnote">Since host boot</div></div>
315
+ <div class="vital"><div class="vlabel">Network in</div><div class="vval">{{ liveRate(liveMetric().networkRxBytesPerSec) }}</div><div class="vnote">{{ liveBytes(liveMetric().networkRxBytesToday) }} today</div></div>
316
+ <div class="vital"><div class="vlabel">Network out</div><div class="vval">{{ liveRate(liveMetric().networkTxBytesPerSec) }}</div><div class="vnote">{{ liveBytes(liveMetric().networkTxBytesToday) }} today</div></div>
317
+ <div class="vital"><div class="vlabel">Bandwidth this month</div><div class="vval">{{ liveBytes(liveMonthBytes()) }}</div><div class="vnote">{{ liveBandwidthNote() }}</div></div>
219
318
  <div class="vital"><div class="vlabel">Disk free</div><div class="vval">{{ Math.max(0, Number(liveMetric().diskTotalGb) - Number(liveMetric().diskUsedGb)) }} <small>GB</small></div><div class="vnote">{{ liveMetric().diskTotalGb }} GB total</div></div>
220
319
  <div class="vital"><div class="vlabel">Swap</div><div class="vval">{{ liveSwap() }}<small>% used</small></div><div class="vnote">{{ liveGb(liveMetric().swapUsedMb) }} / {{ liveGb(liveMetric().swapTotalMb) }} GB</div></div>
221
320
  </div>
@@ -257,8 +356,9 @@ function setRange(value) { historyRange.set(value); refresh() }
257
356
  <div class="vital"><div class="vlabel">Memory available</div><div class="vval">{{ live ? memAvailableGb : 'Unavailable' }} @if (live)<small>GB</small>@endif</div><div class="vnote">{{ live ? `${gb(m.memCacheMb)} GB cache` : unavailableNote }}</div></div>
258
357
  <div class="vital"><div class="vlabel">Swap</div><div class="vval">{{ live ? swapPct : 'Unavailable' }}@if (live)<small>% used</small>@endif</div><div class="vnote">{{ live ? `${gb(m.swapUsedMb)} / ${gb(m.swapTotalMb)} GB` : unavailableNote }}</div></div>
259
358
  <div class="vital"><div class="vlabel">Disk inodes</div><div class="vval">{{ live ? m.inodeUsedPct : 'Unavailable' }}@if (live)<small>% used</small>@endif</div><div class="vnote">Filesystem entry capacity</div></div>
260
- <div class="vital"><div class="vlabel">Network received</div><div class="vval">{{ live ? bytes(m.networkRxBytes) : 'Unavailable' }}</div><div class="vnote">Since host boot</div></div>
261
- <div class="vital"><div class="vlabel">Network sent</div><div class="vval">{{ live ? bytes(m.networkTxBytes) : 'Unavailable' }}</div><div class="vnote">Since host boot</div></div>
359
+ <div class="vital"><div class="vlabel">Network in</div><div class="vval">{{ live ? rate(m.networkRxBytesPerSec) : 'Unavailable' }}</div><div class="vnote">{{ live ? `${bytes(m.networkRxBytesToday)} today` : unavailableNote }}</div></div>
360
+ <div class="vital"><div class="vlabel">Network out</div><div class="vval">{{ live ? rate(m.networkTxBytesPerSec) : 'Unavailable' }}</div><div class="vnote">{{ live ? `${bytes(m.networkTxBytesToday)} today` : unavailableNote }}</div></div>
361
+ <div class="vital"><div class="vlabel">Bandwidth this month</div><div class="vval">{{ live ? bytes(monthBytes) : 'Unavailable' }}</div><div class="vnote">{{ live ? bandwidthNote : unavailableNote }}</div></div>
262
362
  <div class="vital"><div class="vlabel">Processes</div><div class="vval">{{ live ? m.processes : 'Unavailable' }}</div><div class="vnote">Current process count</div></div>
263
363
  <div class="vital"><div class="vlabel">Disk free</div><div class="vval">{{ live ? diskFreeGb : 'Unavailable' }} @if (live)<small>GB</small>@endif</div><div class="vnote">{{ live ? `${m.diskTotalGb} GB total` : unavailableNote }}</div></div>
264
364
  </div>
@@ -291,10 +391,44 @@ function setRange(value) { historyRange.set(value); refresh() }
291
391
  </article>
292
392
  </template>
293
393
  </div>
394
+ <div class="egress-panel panel" :class="egressTone()" @show="hasEgress()">
395
+ <div class="egress-head">
396
+ <div>
397
+ <h3>Object storage egress</h3>
398
+ <p>Reported by the applications that serve the downloads — these bytes never cross this host's network interface.</p>
399
+ </div>
400
+ <strong>{{ egressSummary() }}</strong>
401
+ </div>
402
+ <div class="egress-track" @show="!!egressBudget()">
403
+ <i :style="'width:' + Math.min(100, egressPercent() ?? 0) + '%'"></i>
404
+ </div>
405
+ <p class="bad notice" role="status" @show="egressWillExceed()">
406
+ Projected to exceed the monthly allowance — {{ bytesLabel(egressProjected()) }} against {{ bytesLabel(egressBudget()) }}.
407
+ </p>
408
+ <svg viewBox="0 0 100 46" preserveAspectRatio="none" role="img" aria-label="Daily object storage egress">
409
+ <line x1="0" x2="100" y1="23" y2="23" class="history-gridline"></line>
410
+ <path :d="areaPath('egress-daily')" class="history-area"></path>
411
+ <path :d="linePath('egress-daily')" class="history-line"></path>
412
+ </svg>
413
+ <div class="history-meta">
414
+ <span>{{ samples('egress-daily') }} days</span>
415
+ <span>peak {{ display(peak('egress-daily'), 'bytes') }}</span>
416
+ </div>
417
+ </div>
294
418
  <p class="note">Retention, source freshness, comparisons, logs, and exports are available in <a href="/operations/observability">Observability</a>.</p>
295
419
  </div>
296
420
  </div>
297
421
  <style>
422
+ .egress-panel { margin-top:18px; padding:18px; }
423
+ .egress-head { display:flex; align-items:flex-start; justify-content:space-between; gap:16px; }
424
+ .egress-head h3 { margin:0; font-size:14px; }
425
+ .egress-head p { margin:4px 0 0; color:var(--txt3); font-size:11px; max-width:52ch; }
426
+ .egress-head strong { color:var(--txt); font-size:13px; white-space:nowrap; }
427
+ .egress-track { height:7px; margin:14px 0 4px; overflow:hidden; border-radius:999px; background:rgba(255,255,255,.07); }
428
+ .egress-track > i { display:block; height:100%; border-radius:999px; background:var(--ok); }
429
+ .egress-panel.warn .egress-track > i { background:var(--warn); }
430
+ .egress-panel.bad .egress-track > i { background:var(--bad); }
431
+ .egress-panel svg { width:100%; height:88px; margin-top:10px; }
298
432
  .host-vitals { grid-template-columns: repeat(4, minmax(0, 1fr)); }
299
433
  .live-gauges { grid-template-columns: repeat(4, minmax(0, 1fr)); }
300
434
  .live-bars { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:18px 24px; }