@stacksjs/ts-cloud 0.5.26 → 0.5.28
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/dist/bin/cli.js +1223 -7366
- package/dist/deploy/dashboard-data-server.d.ts +27 -5
- package/dist/deploy/dashboard-database.d.ts +38 -0
- package/dist/deploy/dashboard-database.test.d.ts +1 -0
- package/dist/deploy/dashboard-operations.d.ts +48 -0
- package/dist/deploy/dashboard-operations.test.d.ts +1 -0
- package/dist/deploy/index.d.ts +1 -0
- package/dist/deploy/local-dashboard-server.d.ts +31 -0
- package/dist/deploy/management-dashboard.d.ts +21 -1
- package/dist/deploy/site-config-editor.d.ts +37 -0
- package/dist/deploy/site-target.d.ts +3 -1
- package/dist/deploy/ssh-config-editor.d.ts +22 -0
- package/dist/deploy/ssh-config-editor.test.d.ts +1 -0
- package/dist/drivers/factory.d.ts +4 -0
- package/dist/drivers/index.d.ts +1 -0
- package/dist/drivers/local-box/driver.d.ts +20 -0
- package/dist/drivers/shared/compute-deploy.d.ts +2 -0
- package/dist/drivers/shared/deploy-script.d.ts +28 -12
- package/dist/drivers/shared/rpx-gateway.d.ts +99 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3381 -210
- package/dist/ui/index.html +83 -1
- package/dist/ui/server/actions.html +1193 -0
- package/dist/ui/server/activity.html +1132 -0
- package/dist/ui/server/backups.html +57 -1
- package/dist/ui/server/database.html +1208 -0
- package/dist/ui/server/deployments.html +79 -1
- package/dist/ui/server/diagnostics.html +1132 -0
- package/dist/ui/server/logs.html +1165 -0
- package/dist/ui/server/metrics.html +1 -1
- package/dist/ui/server/security.html +1132 -0
- package/dist/ui/server/services.html +57 -1
- package/dist/ui/server/sites.html +141 -1
- package/dist/ui/server/ssh-keys.html +58 -1
- package/dist/ui/server/workers.html +57 -1
- package/dist/ui/serverless/assets.html +1 -1
- package/dist/ui/serverless/cost.html +1 -1
- package/dist/ui/serverless/data.html +1 -1
- package/dist/ui/serverless/deployments.html +1 -1
- package/dist/ui/serverless/firewall.html +1 -1
- package/dist/ui/serverless/functions.html +1 -1
- package/dist/ui/serverless/logs.html +1165 -0
- package/dist/ui/serverless/metrics.html +1 -1
- package/dist/ui/serverless/queues.html +1 -1
- package/dist/ui/serverless/scheduler.html +1 -1
- package/dist/ui/serverless/secrets.html +1 -1
- package/dist/ui/serverless.html +1 -1
- package/dist/ui-src/package.json +5 -0
- package/dist/ui-src/pages/index.stx +308 -0
- package/dist/ui-src/pages/partials/head.stx +294 -0
- package/dist/ui-src/pages/partials/nav.stx +54 -0
- package/dist/ui-src/pages/partials/op-confirm.stx +7 -0
- package/dist/ui-src/pages/server/actions.stx +60 -0
- package/dist/ui-src/pages/server/activity.stx +55 -0
- package/dist/ui-src/pages/server/backups.stx +91 -0
- package/dist/ui-src/pages/server/database.stx +119 -0
- package/dist/ui-src/pages/server/deployments.stx +106 -0
- package/dist/ui-src/pages/server/diagnostics.stx +88 -0
- package/dist/ui-src/pages/server/logs.stx +84 -0
- package/dist/ui-src/pages/server/metrics.stx +183 -0
- package/dist/ui-src/pages/server/security.stx +127 -0
- package/dist/ui-src/pages/server/services.stx +82 -0
- package/dist/ui-src/pages/server/sites.stx +143 -0
- package/dist/ui-src/pages/server/ssh-keys.stx +93 -0
- package/dist/ui-src/pages/server/workers.stx +99 -0
- package/dist/ui-src/pages/serverless/assets.stx +66 -0
- package/dist/ui-src/pages/serverless/cost.stx +149 -0
- package/dist/ui-src/pages/serverless/data.stx +76 -0
- package/dist/ui-src/pages/serverless/deployments.stx +56 -0
- package/dist/ui-src/pages/serverless/firewall.stx +58 -0
- package/dist/ui-src/pages/serverless/functions.stx +118 -0
- package/dist/ui-src/pages/serverless/logs.stx +84 -0
- package/dist/ui-src/pages/serverless/metrics.stx +76 -0
- package/dist/ui-src/pages/serverless/queues.stx +81 -0
- package/dist/ui-src/pages/serverless/scheduler.stx +60 -0
- package/dist/ui-src/pages/serverless/secrets.stx +55 -0
- package/dist/ui-src/pages/serverless.stx +222 -0
- package/dist/ui-src/src/charts.ts +17333 -0
- package/package.json +3 -3
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
<script server>
|
|
2
|
+
let __d = null
|
|
3
|
+
try { __d = JSON.parse(process.env.TSCLOUD_DASHBOARD_DATA || '') } catch { __d = null }
|
|
4
|
+
const app = __d?.app ?? { name: 'acme', env: 'production' }
|
|
5
|
+
|
|
6
|
+
const functions = __d?.functionsDetail ?? [
|
|
7
|
+
{
|
|
8
|
+
key: 'http', name: 'acme-production-http', runtime: 'provided.al2023', arch: 'x86_64',
|
|
9
|
+
memory: 1024, timeout: 28, ephemeral: 512, version: '$LATEST', concurrency: '—',
|
|
10
|
+
invocations: 171200, errors: 21, throttles: 0, p50: 32, p95: 86, p99: 180, maxMem: 184,
|
|
11
|
+
spark: [40, 52, 48, 61, 73, 66, 80, 72, 95, 88, 70, 64],
|
|
12
|
+
env: ['APP_ENV', 'ASSET_URL', 'TSCLOUD_CACHE_TABLE', 'DB_HOST', 'QUEUE_CONNECTION'],
|
|
13
|
+
recent: [
|
|
14
|
+
{ ts: '01:42:09', msg: 'GET / 200 31ms', err: false },
|
|
15
|
+
{ ts: '01:42:03', msg: 'GET /pricing 200 28ms', err: false },
|
|
16
|
+
{ ts: '01:41:58', msg: 'POST /checkout 500 timeout', err: true },
|
|
17
|
+
],
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
key: 'queue', name: 'acme-production-queue', runtime: 'provided.al2023', arch: 'x86_64',
|
|
21
|
+
memory: 1024, timeout: 120, ephemeral: 512, version: '$LATEST', concurrency: 10,
|
|
22
|
+
invocations: 12840, errors: 3, throttles: 0, p50: 120, p95: 240, p99: 410, maxMem: 256,
|
|
23
|
+
spark: [12, 18, 9, 22, 31, 28, 19, 24, 33, 27, 20, 14],
|
|
24
|
+
env: ['APP_ENV', 'QUEUE_CONNECTION', 'TSCLOUD_SQS_RECORD'],
|
|
25
|
+
recent: [
|
|
26
|
+
{ ts: '01:41:40', msg: 'job App\\Jobs\\SendInvoice ok 210ms', err: false },
|
|
27
|
+
{ ts: '01:40:12', msg: 'job App\\Jobs\\Resize failed → DLQ', err: true },
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
key: 'cli', name: 'acme-production-cli', runtime: 'provided.al2023', arch: 'x86_64',
|
|
32
|
+
memory: 1024, timeout: 900, ephemeral: 512, version: '$LATEST', concurrency: '—',
|
|
33
|
+
invocations: 1440, errors: 0, throttles: 0, p50: 800, p95: 1100, p99: 1400, maxMem: 312,
|
|
34
|
+
spark: [2, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1],
|
|
35
|
+
env: ['APP_ENV', 'TSCLOUD_SCHEDULER'],
|
|
36
|
+
recent: [
|
|
37
|
+
{ ts: '01:42:00', msg: 'schedule:run ok 740ms', err: false },
|
|
38
|
+
{ ts: '01:41:00', msg: 'schedule:run ok 690ms', err: false },
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
function fmt(n) { return n >= 1000 ? `${(n / 1000).toFixed(n >= 10000 ? 0 : 1)}k` : String(n) }
|
|
44
|
+
</script>
|
|
45
|
+
<!DOCTYPE html>
|
|
46
|
+
<html lang="en" data-theme="dark">
|
|
47
|
+
<head>
|
|
48
|
+
@include('../partials/head', { title: 'ts-cloud — Functions' })
|
|
49
|
+
</head>
|
|
50
|
+
<body>
|
|
51
|
+
@include('../partials/nav', { active: 'serverless', name: app.name, env: app.env })
|
|
52
|
+
|
|
53
|
+
<div class="wrap">
|
|
54
|
+
<div class="crumbs"><a href="/serverless">Serverless</a> <span class="sep">/</span> Functions</div>
|
|
55
|
+
<header class="page">
|
|
56
|
+
<div>
|
|
57
|
+
<h1>Functions</h1>
|
|
58
|
+
<p>The three Lambda functions sharing one artifact — HTTP, queue worker, and CLI/scheduler.</p>
|
|
59
|
+
</div>
|
|
60
|
+
</header>
|
|
61
|
+
|
|
62
|
+
@foreach (functions as fn)
|
|
63
|
+
<div class="section">
|
|
64
|
+
<h2><span class="tag">{{ fn.key }}</span> <span class="mono">{{ fn.name }}</span></h2>
|
|
65
|
+
<div class="cols">
|
|
66
|
+
<div class="panel">
|
|
67
|
+
<h3>Invocations (24h)</h3>
|
|
68
|
+
<div class="stat">{{ fmt(fn.invocations) }}</div>
|
|
69
|
+
<div class="spark">
|
|
70
|
+
@foreach (fn.spark as h)
|
|
71
|
+
<i style="height: {{ h }}%"></i>
|
|
72
|
+
@endforeach
|
|
73
|
+
</div>
|
|
74
|
+
<div style="display:flex; gap:24px; margin-top:16px; flex-wrap:wrap;">
|
|
75
|
+
<div><h3>Errors</h3><div style="font-size:18px;font-weight:700;margin-top:4px;">@if (fn.errors > 0)<span style="color:var(--bad)">{{ fn.errors }}</span>@else<span style="color:var(--txt3)">0</span>@endif</div></div>
|
|
76
|
+
<div><h3>Throttles</h3><div style="font-size:18px;font-weight:700;margin-top:4px;">{{ fn.throttles }}</div></div>
|
|
77
|
+
<div><h3>p50 / p95 / p99</h3><div class="mono" style="font-size:14px;margin-top:6px;">{{ fn.p50 }} / {{ fn.p95 }} / {{ fn.p99 }} ms</div></div>
|
|
78
|
+
<div><h3>Max memory</h3><div style="font-size:18px;font-weight:700;margin-top:4px;">{{ fn.maxMem }} <small style="font-size:12px;color:var(--txt2)">/ {{ fn.memory }} MB</small></div></div>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
<div class="panel">
|
|
82
|
+
<h3>Configuration</h3>
|
|
83
|
+
<div style="margin-top:10px;">
|
|
84
|
+
<div class="kv"><span>runtime</span> <b class="mono">{{ fn.runtime }}</b></div>
|
|
85
|
+
<div class="kv"><span>architecture</span> <b class="mono">{{ fn.arch }}</b></div>
|
|
86
|
+
<div class="kv"><span>memory</span> <b>{{ fn.memory }} MB</b></div>
|
|
87
|
+
<div class="kv"><span>timeout</span> <b>{{ fn.timeout }}s</b></div>
|
|
88
|
+
<div class="kv"><span>ephemeral /tmp</span> <b>{{ fn.ephemeral }} MB</b></div>
|
|
89
|
+
<div class="kv"><span>live version</span> <b class="mono">{{ fn.version }}</b></div>
|
|
90
|
+
<div class="kv"><span>reserved concurrency</span> <b>{{ fn.concurrency }}</b></div>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
<div class="cols" style="margin-top:16px;">
|
|
95
|
+
<div class="panel">
|
|
96
|
+
<h3>Recent activity</h3>
|
|
97
|
+
<div class="logs" style="margin-top:10px;">
|
|
98
|
+
@foreach (fn.recent as r)
|
|
99
|
+
<div>@if (r.err)<span class="ts">{{ r.ts }}</span> <span class="err">{{ r.msg }}</span>@else<span class="ts">{{ r.ts }}</span> {{ r.msg }}@endif</div>
|
|
100
|
+
@endforeach
|
|
101
|
+
</div>
|
|
102
|
+
<p class="note">Tail live with <span class="mono">cloud logs --function {{ fn.key }} --tail</span></p>
|
|
103
|
+
</div>
|
|
104
|
+
<div class="panel">
|
|
105
|
+
<h3>Environment ({{ fn.env.length }} vars)</h3>
|
|
106
|
+
<div style="margin-top:10px;">
|
|
107
|
+
@foreach (fn.env as e)
|
|
108
|
+
<div class="secret"><b class="mono">{{ e }}</b><span class="val">••••••••</span></div>
|
|
109
|
+
@endforeach
|
|
110
|
+
</div>
|
|
111
|
+
<p class="note">Pull/edit with <span class="mono">cloud env:pull --function {{ fn.key }}</span></p>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
@endforeach
|
|
116
|
+
</div>
|
|
117
|
+
</body>
|
|
118
|
+
</html>
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<script server>
|
|
2
|
+
let __d = null
|
|
3
|
+
try { __d = JSON.parse(process.env.TSCLOUD_DASHBOARD_DATA || '') } catch { __d = null }
|
|
4
|
+
const app = __d?.app ?? { name: 'acme', env: 'production' }
|
|
5
|
+
const initialLogs = __d?.serverlessLogs ?? []
|
|
6
|
+
const emptyReason = __d?.serverlessLogsEmptyReason ?? 'No function logs are available yet.'
|
|
7
|
+
</script>
|
|
8
|
+
<script client>
|
|
9
|
+
// Reactive aggregated CloudWatch logs across the functions, with level + function
|
|
10
|
+
// filtering and auto-refresh — seeded from build-time data (stx server→client
|
|
11
|
+
// bridge), then live-refreshed. Signals + directives only (no manual DOM).
|
|
12
|
+
const logs = state(initialLogs)
|
|
13
|
+
const level = state('all')
|
|
14
|
+
const source = state('all')
|
|
15
|
+
const sources = derived(() => [...new Set(logs().map(l => l.source).filter(Boolean))])
|
|
16
|
+
const filtered = derived(() => logs().filter(l =>
|
|
17
|
+
(level() === 'all' || l.level === level()) && (source() === 'all' || l.source === source())))
|
|
18
|
+
function tone(l) { return l === 'error' ? 'bad' : (l === 'warn' ? 'warn' : 'ok') }
|
|
19
|
+
async function refresh() {
|
|
20
|
+
try {
|
|
21
|
+
const res = await fetch('/api/dashboard-data')
|
|
22
|
+
if (!res.ok) return
|
|
23
|
+
const data = await res.json()
|
|
24
|
+
if (Array.isArray(data.serverlessLogs)) logs.set(data.serverlessLogs)
|
|
25
|
+
} catch {}
|
|
26
|
+
}
|
|
27
|
+
onMount(() => { refresh(); const id = setInterval(refresh, 8000); return () => clearInterval(id) })
|
|
28
|
+
</script>
|
|
29
|
+
<!DOCTYPE html>
|
|
30
|
+
<html lang="en" data-theme="dark">
|
|
31
|
+
<head>@include('../partials/head', { title: 'ts-cloud — Function logs' })</head>
|
|
32
|
+
<body>
|
|
33
|
+
@include('../partials/nav', { active: 'serverless', name: app.name, env: app.env })
|
|
34
|
+
<div class="wrap">
|
|
35
|
+
<div class="crumbs"><a href="/serverless">Serverless</a> <span class="sep">/</span> Logs</div>
|
|
36
|
+
<header class="page">
|
|
37
|
+
<div>
|
|
38
|
+
<h1>Function logs</h1>
|
|
39
|
+
<p>Aggregated CloudWatch logs across the {{ app.name }} functions ({{ app.env }}).</p>
|
|
40
|
+
</div>
|
|
41
|
+
<div class="cmds header-actions"><span class="accent cmd">cloud logs --env {{ app.env }}</span></div>
|
|
42
|
+
</header>
|
|
43
|
+
|
|
44
|
+
<div class="section">
|
|
45
|
+
<div class="panel">
|
|
46
|
+
<div class="filters">
|
|
47
|
+
<div class="segmented" role="group" aria-label="Filter log level">
|
|
48
|
+
<button type="button" :class="{ active: level() === 'all' }" @click="level.set('all')">All</button>
|
|
49
|
+
<button type="button" :class="{ active: level() === 'error' }" @click="level.set('error')">Errors</button>
|
|
50
|
+
<button type="button" :class="{ active: level() === 'warn' }" @click="level.set('warn')">Warnings</button>
|
|
51
|
+
<button type="button" :class="{ active: level() === 'info' }" @click="level.set('info')">Info</button>
|
|
52
|
+
</div>
|
|
53
|
+
<label class="select-filter">
|
|
54
|
+
<span>Function</span>
|
|
55
|
+
<select :value="source()" @change="source.set($event.target.value)">
|
|
56
|
+
<option value="all">All functions</option>
|
|
57
|
+
<template :for="s in sources()">
|
|
58
|
+
<option :value="s">{{ s }}</option>
|
|
59
|
+
</template>
|
|
60
|
+
</select>
|
|
61
|
+
</label>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<div class="log-list">
|
|
65
|
+
<template :for="log in filtered()">
|
|
66
|
+
<article class="log-row" :class="log.level">
|
|
67
|
+
<div class="log-meta">
|
|
68
|
+
<span class="pill" :class="tone(log.level)">{{ log.level }}</span>
|
|
69
|
+
<span class="mono">{{ log.source }}</span>
|
|
70
|
+
<span>{{ log.when }}</span>
|
|
71
|
+
</div>
|
|
72
|
+
<pre>{{ log.message }}</pre>
|
|
73
|
+
</article>
|
|
74
|
+
</template>
|
|
75
|
+
</div>
|
|
76
|
+
<div class="compact empty" @show="logs().length > 0 && filtered().length === 0"><strong>No matching logs</strong><span>Try a different level or function filter.</span></div>
|
|
77
|
+
<div class="empty" @show="logs().length === 0"><strong>No logs available</strong><span>{{ emptyReason }}</span></div>
|
|
78
|
+
|
|
79
|
+
<p class="note">Live CloudWatch logs (REPORT/START/END boilerplate stripped) across <span class="mono">http</span>, <span class="mono">queue</span>, <span class="mono">cli</span> and scheduler functions. Auto-refreshes every 8s.</p>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
</body>
|
|
84
|
+
</html>
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<script server>
|
|
2
|
+
let __d = null
|
|
3
|
+
try { __d = JSON.parse(process.env.TSCLOUD_DASHBOARD_DATA || '') } catch { __d = null }
|
|
4
|
+
const app = __d?.app ?? { name: 'acme', env: 'production' }
|
|
5
|
+
const window = '24h'
|
|
6
|
+
const totals = __d?.metricsTotals ?? { invocations: 184320, errors: 24, errorRatePct: 0.13, p95Ms: 92, throttles: 0, coldStartPct: 1.8, estCostUsd: 7.4, concurrency: 42 }
|
|
7
|
+
const perFn = __d?.metricsPerFn ?? [
|
|
8
|
+
{ key: 'http', invocations: 171200, errors: 21, throttles: 0, avgMs: 41, maxMs: 282, costUsd: 6.1 },
|
|
9
|
+
{ key: 'queue', invocations: 12840, errors: 3, throttles: 0, avgMs: 168, maxMs: 410, costUsd: 1.0 },
|
|
10
|
+
{ key: 'cli', invocations: 1440, errors: 0, throttles: 0, avgMs: 760, maxMs: 1400, costUsd: 0.3 },
|
|
11
|
+
]
|
|
12
|
+
const invSpark = __d?.invSpark ?? [60, 72, 68, 81, 95, 88, 102, 94, 120, 110, 90, 84, 78, 96, 130, 122, 100, 92, 88, 104, 140, 132, 108, 100]
|
|
13
|
+
function fmt(n) { return n >= 1000 ? `${(n / 1000).toFixed(n >= 10000 ? 0 : 1)}k` : String(n) }
|
|
14
|
+
function pct(v, max) { return Math.round((v / max) * 100) }
|
|
15
|
+
</script>
|
|
16
|
+
<!DOCTYPE html>
|
|
17
|
+
<html lang="en" data-theme="dark">
|
|
18
|
+
<head>
|
|
19
|
+
@include('../partials/head', { title: 'ts-cloud — Metrics' })
|
|
20
|
+
</head>
|
|
21
|
+
<body>
|
|
22
|
+
@include('../partials/nav', { active: 'serverless', name: app.name, env: app.env })
|
|
23
|
+
<div class="wrap">
|
|
24
|
+
<div class="crumbs"><a href="/serverless">Serverless</a> <span class="sep">/</span> Metrics</div>
|
|
25
|
+
<header class="page">
|
|
26
|
+
<div>
|
|
27
|
+
<h1>Metrics</h1>
|
|
28
|
+
<p>CloudWatch metrics across the function trio, last {{ window }}.</p>
|
|
29
|
+
</div>
|
|
30
|
+
</header>
|
|
31
|
+
|
|
32
|
+
<div class="grid cards">
|
|
33
|
+
<div class="panel"><h3>Invocations</h3><div class="stat">{{ fmt(totals.invocations) }}</div><p class="note">{{ totals.concurrency }} concurrent now</p></div>
|
|
34
|
+
<div class="panel"><h3>Error rate</h3><div class="stat">{{ totals.errorRatePct }}%</div><div class="bar"><i style="width: {{ Math.min(100, totals.errorRatePct * 10) }}%"></i></div></div>
|
|
35
|
+
<div class="panel"><h3>p95 latency</h3><div class="stat">{{ totals.p95Ms }} <small>ms</small></div><p class="note">{{ totals.coldStartPct }}% cold starts</p></div>
|
|
36
|
+
<div class="panel"><h3>Est. cost</h3><div class="stat">${{ totals.estCostUsd }}</div><p class="note">{{ totals.throttles }} throttles</p></div>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<div class="section">
|
|
40
|
+
<h2>Invocations over time</h2>
|
|
41
|
+
<div class="panel">
|
|
42
|
+
<div class="spark" style="height:96px">
|
|
43
|
+
@foreach (invSpark as h)
|
|
44
|
+
<i style="height: {{ pct(h, 140) }}%"></i>
|
|
45
|
+
@endforeach
|
|
46
|
+
</div>
|
|
47
|
+
<p class="note">hourly buckets · last {{ window }}</p>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<div class="section">
|
|
52
|
+
<h2>Per function</h2>
|
|
53
|
+
<div class="panel">
|
|
54
|
+
<table>
|
|
55
|
+
<thead><tr><th>Function</th><th>Invocations</th><th>Errors</th><th>Error rate</th><th>Throttles</th><th>Avg</th><th>Max</th><th>Est. cost</th></tr></thead>
|
|
56
|
+
<tbody>
|
|
57
|
+
@foreach (perFn as f)
|
|
58
|
+
<tr>
|
|
59
|
+
<td><span class="tag">{{ f.key }}</span></td>
|
|
60
|
+
<td>{{ fmt(f.invocations) }}</td>
|
|
61
|
+
<td>@if (f.errors > 0)<span class="bad pill">{{ f.errors }}</span>@else<span style="color:var(--txt3)">0</span>@endif</td>
|
|
62
|
+
<td class="mono">{{ (f.errors / f.invocations * 100).toFixed(2) }}%</td>
|
|
63
|
+
<td>{{ f.throttles }}</td>
|
|
64
|
+
<td class="mono">{{ f.avgMs }}ms</td>
|
|
65
|
+
<td class="mono">{{ f.maxMs }}ms</td>
|
|
66
|
+
<td style="color:var(--txt2)">${{ f.costUsd }}</td>
|
|
67
|
+
</tr>
|
|
68
|
+
@endforeach
|
|
69
|
+
</tbody>
|
|
70
|
+
</table>
|
|
71
|
+
<p class="note">Live: <span class="mono">cloud metrics --since {{ window }}</span> · open in console with <span class="mono">cloud metrics:dashboard</span></p>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
</body>
|
|
76
|
+
</html>
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<script server>
|
|
2
|
+
let __d = null
|
|
3
|
+
try { __d = JSON.parse(process.env.TSCLOUD_DASHBOARD_DATA || '') } catch { __d = null }
|
|
4
|
+
const app = __d?.app ?? { name: 'acme', env: 'production' }
|
|
5
|
+
const queues = __d?.queuesDetail ?? [
|
|
6
|
+
{ name: 'acme-production-default', short: 'default', visible: 0, inFlight: 2, delayed: 0, processed: 12830, oldestSec: 0, concurrency: 10, tries: 3, visTimeout: 720, dlq: 0 },
|
|
7
|
+
{ name: 'acme-production-emails', short: 'emails', visible: 4, inFlight: 1, delayed: 0, processed: 9210, oldestSec: 14, concurrency: 5, tries: 3, visTimeout: 720, dlq: 1 },
|
|
8
|
+
]
|
|
9
|
+
const dlqItems = __d?.dlqItems ?? [
|
|
10
|
+
{ id: 'b1f3…9a', queue: 'emails', job: 'App\\Jobs\\SendInvoice', failed: '2h ago', attempts: 3, error: 'SMTP timeout' },
|
|
11
|
+
]
|
|
12
|
+
function fmt(n) { return n >= 1000 ? `${(n / 1000).toFixed(n >= 10000 ? 0 : 1)}k` : String(n) }
|
|
13
|
+
</script>
|
|
14
|
+
<!DOCTYPE html>
|
|
15
|
+
<html lang="en" data-theme="dark">
|
|
16
|
+
<head>
|
|
17
|
+
@include('../partials/head', { title: 'ts-cloud — Queues' })
|
|
18
|
+
</head>
|
|
19
|
+
<body>
|
|
20
|
+
@include('../partials/nav', { active: 'serverless', name: app.name, env: app.env })
|
|
21
|
+
<div class="wrap">
|
|
22
|
+
<div class="crumbs"><a href="/serverless">Serverless</a> <span class="sep">/</span> Queues</div>
|
|
23
|
+
<header class="page">
|
|
24
|
+
<div>
|
|
25
|
+
<h1>Queues</h1>
|
|
26
|
+
<p>SQS queues feeding the queue worker (one job per invocation) with a shared dead-letter queue.</p>
|
|
27
|
+
</div>
|
|
28
|
+
</header>
|
|
29
|
+
|
|
30
|
+
<div class="section">
|
|
31
|
+
<div class="panel">
|
|
32
|
+
<table>
|
|
33
|
+
<thead><tr><th>Queue</th><th>Visible</th><th>In-flight</th><th>Oldest</th><th>Concurrency</th><th>Tries</th><th>Vis. timeout</th><th>Processed</th><th>DLQ</th></tr></thead>
|
|
34
|
+
<tbody>
|
|
35
|
+
@foreach (queues as q)
|
|
36
|
+
<tr>
|
|
37
|
+
<td><b>{{ q.short }}</b><div class="mono" style="font-size:11px;color:var(--txt3)">{{ q.name }}</div></td>
|
|
38
|
+
<td>{{ q.visible }}</td>
|
|
39
|
+
<td>{{ q.inFlight }}</td>
|
|
40
|
+
<td class="mono">{{ q.oldestSec }}s</td>
|
|
41
|
+
<td>{{ q.concurrency }}</td>
|
|
42
|
+
<td>{{ q.tries }}</td>
|
|
43
|
+
<td class="mono">{{ q.visTimeout }}s</td>
|
|
44
|
+
<td style="color:var(--txt2)">{{ fmt(q.processed) }}</td>
|
|
45
|
+
<td>@if (q.dlq > 0)<span class="bad pill">{{ q.dlq }}</span>@else<span style="color:var(--txt3)">0</span>@endif</td>
|
|
46
|
+
</tr>
|
|
47
|
+
@endforeach
|
|
48
|
+
</tbody>
|
|
49
|
+
</table>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<div class="section">
|
|
54
|
+
<h2>Dead-letter queue</h2>
|
|
55
|
+
<div class="panel">
|
|
56
|
+
@if (dlqItems.length === 0)
|
|
57
|
+
<p style="color:var(--txt3)">No failed messages. 🎉</p>
|
|
58
|
+
@else
|
|
59
|
+
<table>
|
|
60
|
+
<thead><tr><th>Message</th><th>Queue</th><th>Job</th><th>Attempts</th><th>Error</th><th>Failed</th><th></th></tr></thead>
|
|
61
|
+
<tbody>
|
|
62
|
+
@foreach (dlqItems as m)
|
|
63
|
+
<tr>
|
|
64
|
+
<td class="mono">{{ m.id }}</td>
|
|
65
|
+
<td>{{ m.queue }}</td>
|
|
66
|
+
<td class="mono">{{ m.job }}</td>
|
|
67
|
+
<td>{{ m.attempts }}</td>
|
|
68
|
+
<td><span style="color:var(--bad)">{{ m.error }}</span></td>
|
|
69
|
+
<td style="color:var(--txt3)">{{ m.failed }}</td>
|
|
70
|
+
<td><span class="cmd">cloud queue:redrive {{ m.queue }}</span></td>
|
|
71
|
+
</tr>
|
|
72
|
+
@endforeach
|
|
73
|
+
</tbody>
|
|
74
|
+
</table>
|
|
75
|
+
@endif
|
|
76
|
+
<p class="note">Inspect with <span class="mono">cloud queue:stats</span> · purge with <span class="mono">cloud queue:purge <name></span></p>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</body>
|
|
81
|
+
</html>
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<script server>
|
|
2
|
+
let __d = null
|
|
3
|
+
try { __d = JSON.parse(process.env.TSCLOUD_DASHBOARD_DATA || '') } catch { __d = null }
|
|
4
|
+
const app = __d?.app ?? { name: 'acme', env: 'production' }
|
|
5
|
+
const scheduler = { ...{ enabled: true, mode: 'sub-minute', expression: 'rate(1 minute)', lastRun: '36s ago', nextRun: '24s' }, ...(__d?.scheduler ?? {}) }
|
|
6
|
+
// Tasks Laravel's schedule:run would fire (illustrative — discovered from the app).
|
|
7
|
+
const tasks = __d?.schedulerTasks ?? [
|
|
8
|
+
{ command: 'emails:digest', cron: '0 8 * * *', desc: 'Daily 08:00', lastRun: '8h ago', lastStatus: 'ok' },
|
|
9
|
+
{ command: 'metrics:rollup', cron: '*/5 * * * *', desc: 'Every 5 min', lastRun: '3m ago', lastStatus: 'ok' },
|
|
10
|
+
{ command: 'subscriptions:renew', cron: '*/1 * * * *', desc: 'Every minute', lastRun: '36s ago', lastStatus: 'ok' },
|
|
11
|
+
{ command: 'cleanup:temp', cron: '30 3 * * *', desc: 'Daily 03:30', lastRun: '5h ago', lastStatus: 'failed' },
|
|
12
|
+
]
|
|
13
|
+
function tone(s) { return s === 'ok' ? 'ok' : (s === 'failed' ? 'bad' : 'warn') }
|
|
14
|
+
</script>
|
|
15
|
+
<!DOCTYPE html>
|
|
16
|
+
<html lang="en" data-theme="dark">
|
|
17
|
+
<head>
|
|
18
|
+
@include('../partials/head', { title: 'ts-cloud — Scheduler' })
|
|
19
|
+
</head>
|
|
20
|
+
<body>
|
|
21
|
+
@include('../partials/nav', { active: 'serverless', name: app.name, env: app.env })
|
|
22
|
+
<div class="wrap">
|
|
23
|
+
<div class="crumbs"><a href="/serverless">Serverless</a> <span class="sep">/</span> Scheduler</div>
|
|
24
|
+
<header class="page">
|
|
25
|
+
<div>
|
|
26
|
+
<h1>Scheduler</h1>
|
|
27
|
+
<p>An EventBridge rule invokes the CLI function with <span class="mono">schedule:run</span> every minute.</p>
|
|
28
|
+
</div>
|
|
29
|
+
</header>
|
|
30
|
+
|
|
31
|
+
<div class="grid cards">
|
|
32
|
+
<div class="panel"><h3>State</h3><div style="margin-top:10px">@if (scheduler.enabled)<span class="ok pill">on</span>@else<span class="pill warn">off</span>@endif</div></div>
|
|
33
|
+
<div class="panel"><h3>Mode</h3><div class="stat" style="font-size:20px">{{ scheduler.mode }}</div></div>
|
|
34
|
+
<div class="panel"><h3>Trigger</h3><div class="mono" style="margin-top:12px">{{ scheduler.expression }}</div></div>
|
|
35
|
+
<div class="panel"><h3>Last run</h3><div class="stat" style="font-size:20px">{{ scheduler.lastRun }}</div><p class="note">next in ~{{ scheduler.nextRun }}</p></div>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<div class="section">
|
|
39
|
+
<h2>Scheduled tasks</h2>
|
|
40
|
+
<div class="panel">
|
|
41
|
+
<table>
|
|
42
|
+
<thead><tr><th>Command</th><th>Schedule</th><th>Cron</th><th>Last run</th><th>Status</th></tr></thead>
|
|
43
|
+
<tbody>
|
|
44
|
+
@foreach (tasks as t)
|
|
45
|
+
<tr>
|
|
46
|
+
<td class="mono">{{ t.command }}</td>
|
|
47
|
+
<td>{{ t.desc }}</td>
|
|
48
|
+
<td class="mono" style="color:var(--txt3)">{{ t.cron }}</td>
|
|
49
|
+
<td style="color:var(--txt2)">{{ t.lastRun }}</td>
|
|
50
|
+
<td><span class="pill {{ tone(t.lastStatus) }}">{{ t.lastStatus }}</span></td>
|
|
51
|
+
</tr>
|
|
52
|
+
@endforeach
|
|
53
|
+
</tbody>
|
|
54
|
+
</table>
|
|
55
|
+
<p class="note">Sub-minute mode loops <span class="mono">schedule:run</span> within each invocation. Run manually: <span class="mono">cloud command "schedule:run"</span></p>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
</body>
|
|
60
|
+
</html>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<script server>
|
|
2
|
+
let __d = null
|
|
3
|
+
try { __d = JSON.parse(process.env.TSCLOUD_DASHBOARD_DATA || '') } catch { __d = null }
|
|
4
|
+
const app = __d?.app ?? { name: 'acme', env: 'production' }
|
|
5
|
+
// Secret keys (names only — values never leave Secrets Manager).
|
|
6
|
+
const secrets = __d?.secretsList ?? [
|
|
7
|
+
{ key: 'APP_KEY', source: 'acme/production/app-key', updated: '14d ago' },
|
|
8
|
+
{ key: 'DB_PASSWORD', source: 'acme/production/db', updated: '14d ago' },
|
|
9
|
+
{ key: 'STRIPE_SECRET', source: 'acme/production/stripe', updated: '3d ago' },
|
|
10
|
+
{ key: 'MAIL_PASSWORD', source: 'acme/production/mail', updated: '21d ago' },
|
|
11
|
+
]
|
|
12
|
+
</script>
|
|
13
|
+
<!DOCTYPE html>
|
|
14
|
+
<html lang="en" data-theme="dark">
|
|
15
|
+
<head>
|
|
16
|
+
@include('../partials/head', { title: 'ts-cloud — Secrets' })
|
|
17
|
+
</head>
|
|
18
|
+
<body>
|
|
19
|
+
@include('../partials/nav', { active: 'serverless', name: app.name, env: app.env })
|
|
20
|
+
<div class="wrap">
|
|
21
|
+
<div class="crumbs"><a href="/serverless">Serverless</a> <span class="sep">/</span> Secrets</div>
|
|
22
|
+
<header class="page">
|
|
23
|
+
<div>
|
|
24
|
+
<h1>Secrets</h1>
|
|
25
|
+
<p>Stored in AWS Secrets Manager and injected into the functions at deploy. Values are never displayed.</p>
|
|
26
|
+
</div>
|
|
27
|
+
</header>
|
|
28
|
+
|
|
29
|
+
<div class="section">
|
|
30
|
+
<div class="panel">
|
|
31
|
+
<table>
|
|
32
|
+
<thead><tr><th>Key</th><th>Secret source</th><th>Value</th><th>Updated</th><th></th></tr></thead>
|
|
33
|
+
<tbody>
|
|
34
|
+
@foreach (secrets as s)
|
|
35
|
+
<tr>
|
|
36
|
+
<td class="mono"><b>{{ s.key }}</b></td>
|
|
37
|
+
<td class="mono" style="color:var(--txt3)">{{ s.source }}</td>
|
|
38
|
+
<td class="mono" style="letter-spacing:2px;color:var(--txt3)">••••••••</td>
|
|
39
|
+
<td style="color:var(--txt3)">{{ s.updated }}</td>
|
|
40
|
+
<td><span class="cmd">cloud secrets:set {{ s.key }} <value></span></td>
|
|
41
|
+
</tr>
|
|
42
|
+
@endforeach
|
|
43
|
+
</tbody>
|
|
44
|
+
</table>
|
|
45
|
+
<div class="cmds" style="margin-top:14px;">
|
|
46
|
+
<span class="accent cmd">cloud secrets:set KEY value</span>
|
|
47
|
+
<span class="cmd">cloud secrets:list</span>
|
|
48
|
+
<span class="cmd">cloud env:pull</span>
|
|
49
|
+
</div>
|
|
50
|
+
<p class="note">Secrets are set from the CLI (the dashboard is a read-only static site). Rotate by re-setting; remove with <span class="mono">cloud secrets:delete KEY</span>.</p>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
</body>
|
|
55
|
+
</html>
|