@stacksjs/ts-cloud 0.5.35 → 0.6.0
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/aws/cloudwatch.d.ts +2 -0
- package/dist/aws/xray.d.ts +45 -0
- package/dist/bin/cli.js +718 -701
- package/dist/deploy/dashboard-database.d.ts +25 -0
- package/dist/deploy/dashboard-operations.d.ts +7 -0
- package/dist/deploy/firewall-config-editor.d.ts +15 -0
- package/dist/deploy/firewall-config-editor.test.d.ts +1 -0
- package/dist/deploy/index.d.ts +1 -0
- package/dist/deploy/management-dashboard.d.ts +26 -3
- package/dist/deploy/serverless-operations.d.ts +150 -0
- package/dist/deploy/serverless-operations.test.d.ts +1 -0
- package/dist/deploy/site-config-editor.d.ts +6 -0
- package/dist/deploy/ssh-config-editor.d.ts +1 -0
- package/dist/deploy/terminal-session.d.ts +23 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1003 -33
- package/dist/ui/index.html +4 -4
- package/dist/ui/server/actions.html +34 -5
- package/dist/ui/server/activity.html +1 -1
- package/dist/ui/server/backups.html +4 -4
- package/dist/ui/server/database.html +27 -5
- package/dist/ui/server/deployments.html +4 -4
- package/dist/ui/server/diagnostics.html +1 -1
- package/dist/ui/server/firewall.html +1233 -0
- package/dist/ui/server/logs.html +4 -4
- package/dist/ui/server/metrics.html +1 -1
- package/dist/ui/server/security.html +1 -1
- package/dist/ui/server/services.html +4 -4
- package/dist/ui/server/sites.html +24 -5
- package/dist/ui/server/ssh-keys.html +4 -4
- package/dist/ui/server/terminal.html +1191 -0
- package/dist/ui/server/workers.html +4 -4
- package/dist/ui/serverless/alarms.html +1236 -0
- package/dist/ui/serverless/assets.html +53 -1
- package/dist/ui/serverless/cost.html +1 -1
- package/dist/ui/serverless/data.html +58 -1
- package/dist/ui/serverless/deployments.html +53 -1
- package/dist/ui/serverless/firewall.html +1 -1
- package/dist/ui/serverless/functions.html +63 -1
- package/dist/ui/serverless/logs.html +4 -4
- package/dist/ui/serverless/metrics.html +1 -1
- package/dist/ui/serverless/queues.html +94 -1
- package/dist/ui/serverless/scheduler.html +55 -1
- package/dist/ui/serverless/secrets.html +99 -1
- package/dist/ui/serverless/traces.html +1177 -0
- package/dist/ui/serverless.html +87 -1
- package/dist/ui-src/pages/index.stx +1 -1
- package/dist/ui-src/pages/partials/head.stx +6 -6
- package/dist/ui-src/pages/partials/nav.stx +4 -3
- package/dist/ui-src/pages/server/actions.stx +34 -1
- package/dist/ui-src/pages/server/backups.stx +1 -1
- package/dist/ui-src/pages/server/database.stx +32 -9
- package/dist/ui-src/pages/server/deployments.stx +2 -2
- package/dist/ui-src/pages/server/firewall.stx +134 -0
- package/dist/ui-src/pages/server/logs.stx +2 -2
- package/dist/ui-src/pages/server/metrics.stx +5 -5
- package/dist/ui-src/pages/server/security.stx +1 -1
- package/dist/ui-src/pages/server/services.stx +1 -1
- package/dist/ui-src/pages/server/sites.stx +24 -3
- package/dist/ui-src/pages/server/ssh-keys.stx +2 -2
- package/dist/ui-src/pages/server/terminal.stx +65 -0
- package/dist/ui-src/pages/server/workers.stx +1 -1
- package/dist/ui-src/pages/serverless/alarms.stx +153 -0
- package/dist/ui-src/pages/serverless/assets.stx +29 -2
- package/dist/ui-src/pages/serverless/cost.stx +4 -4
- package/dist/ui-src/pages/serverless/data.stx +56 -3
- package/dist/ui-src/pages/serverless/deployments.stx +30 -6
- package/dist/ui-src/pages/serverless/firewall.stx +1 -1
- package/dist/ui-src/pages/serverless/functions.stx +62 -4
- package/dist/ui-src/pages/serverless/logs.stx +2 -2
- package/dist/ui-src/pages/serverless/metrics.stx +5 -1
- package/dist/ui-src/pages/serverless/queues.stx +90 -26
- package/dist/ui-src/pages/serverless/scheduler.stx +31 -2
- package/dist/ui-src/pages/serverless/secrets.stx +94 -14
- package/dist/ui-src/pages/serverless/traces.stx +88 -0
- package/dist/ui-src/pages/serverless.stx +87 -24
- package/package.json +3 -3
|
@@ -0,0 +1,153 @@
|
|
|
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
|
+
// Alarms + presets are fetched live on mount; these seed the first render.
|
|
6
|
+
const alarmsList = __d?.alarmsList ?? [
|
|
7
|
+
{ AlarmName: 'acme-production-http-errors', StateValue: 'OK', MetricName: 'Errors', ComparisonOperator: 'GreaterThanThreshold', Threshold: 5, Namespace: 'AWS/Lambda' },
|
|
8
|
+
{ AlarmName: 'acme-production-http-p95', StateValue: 'ALARM', MetricName: 'Duration', ComparisonOperator: 'GreaterThanThreshold', Threshold: 500, Namespace: 'AWS/Lambda' },
|
|
9
|
+
{ AlarmName: 'acme-production-queue-throttles', StateValue: 'INSUFFICIENT_DATA', MetricName: 'Throttles', ComparisonOperator: 'GreaterThanThreshold', Threshold: 1, Namespace: 'AWS/Lambda' },
|
|
10
|
+
]
|
|
11
|
+
const presetsList = __d?.alarmsPresets ?? [
|
|
12
|
+
{ key: 'errors', label: 'Function errors', unit: 'count' },
|
|
13
|
+
{ key: 'p95', label: 'p95 latency (ms)', unit: 'ms' },
|
|
14
|
+
{ key: 'throttles', label: 'Throttles', unit: 'count' },
|
|
15
|
+
{ key: 'concurrency', label: 'Concurrent executions', unit: 'count' },
|
|
16
|
+
]
|
|
17
|
+
const initialAlarms = alarmsList
|
|
18
|
+
const initialPresets = presetsList
|
|
19
|
+
function tone(s) { return s === 'ALARM' ? 'bad' : (s === 'OK' ? 'ok' : 'warn') }
|
|
20
|
+
</script>
|
|
21
|
+
<script client>
|
|
22
|
+
// Alarms manager: list/create/delete CloudWatch alarms via /api/serverless/alarms.
|
|
23
|
+
const rows = state(initialAlarms)
|
|
24
|
+
const presets = state(initialPresets)
|
|
25
|
+
const preset = state(initialPresets[0] ? initialPresets[0].key : '')
|
|
26
|
+
const threshold = state('')
|
|
27
|
+
const msg = state('')
|
|
28
|
+
const msgOk = state(false)
|
|
29
|
+
const busy = state(false)
|
|
30
|
+
|
|
31
|
+
// Delete flow uses a typed-confirm (the alarm name) before firing.
|
|
32
|
+
const pendingDelete = state(null)
|
|
33
|
+
const typedDelete = state('')
|
|
34
|
+
const canDelete = derived(() => { const p = pendingDelete(); return !!p && typedDelete() === p })
|
|
35
|
+
|
|
36
|
+
function toneOf(s) { return s === 'ALARM' ? 'bad' : (s === 'OK' ? 'ok' : 'warn') }
|
|
37
|
+
|
|
38
|
+
async function refresh() {
|
|
39
|
+
try {
|
|
40
|
+
const r = await fetch('/api/serverless/alarms')
|
|
41
|
+
if (!r.ok) return
|
|
42
|
+
const x = await r.json()
|
|
43
|
+
if (Array.isArray(x.alarms)) rows.set(x.alarms)
|
|
44
|
+
if (Array.isArray(x.presets)) { presets.set(x.presets); if (!preset() && x.presets[0]) preset.set(x.presets[0].key) }
|
|
45
|
+
} catch {}
|
|
46
|
+
}
|
|
47
|
+
onMount(() => { refresh() })
|
|
48
|
+
|
|
49
|
+
async function createAlarm() {
|
|
50
|
+
const p = preset()
|
|
51
|
+
if (!p) { msg.set('Pick a preset.'); msgOk.set(false); return }
|
|
52
|
+
if (threshold() === '' || Number.isNaN(Number(threshold()))) { msg.set('Enter a threshold.'); msgOk.set(false); return }
|
|
53
|
+
busy.set(true); msg.set('Creating alarm...'); msgOk.set(false)
|
|
54
|
+
try {
|
|
55
|
+
const res = await fetch('/api/serverless/alarms', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ preset: p, threshold: Number(threshold()) }) })
|
|
56
|
+
const body = await res.json()
|
|
57
|
+
msg.set(body.ok ? (body.stdout || 'Alarm created.') : (body.error || 'Failed to create alarm.')); msgOk.set(!!body.ok)
|
|
58
|
+
if (body.ok) { threshold.set(''); refresh() }
|
|
59
|
+
} catch (e) { msg.set('Failed: ' + ((e && e.message) || e)); msgOk.set(false) }
|
|
60
|
+
finally { busy.set(false) }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function askDelete(name) { pendingDelete.set(name); typedDelete.set('') }
|
|
64
|
+
function cancelDelete() { pendingDelete.set(null); typedDelete.set('') }
|
|
65
|
+
async function confirmDelete() {
|
|
66
|
+
const name = pendingDelete()
|
|
67
|
+
if (!name || typedDelete() !== name) return
|
|
68
|
+
pendingDelete.set(null); typedDelete.set('')
|
|
69
|
+
msg.set('Deleting ' + name + '...'); msgOk.set(false)
|
|
70
|
+
try {
|
|
71
|
+
const res = await fetch('/api/serverless/alarms', { method: 'DELETE', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ name, confirm: name }) })
|
|
72
|
+
const body = await res.json()
|
|
73
|
+
msg.set(body.ok ? (body.stdout || 'Deleted.') : (body.error || 'Failed to delete.')); msgOk.set(!!body.ok)
|
|
74
|
+
if (body.ok) refresh()
|
|
75
|
+
} catch (e) { msg.set('Failed: ' + ((e && e.message) || e)); msgOk.set(false) }
|
|
76
|
+
}
|
|
77
|
+
</script>
|
|
78
|
+
<!DOCTYPE html>
|
|
79
|
+
<html lang="en" data-theme="dark">
|
|
80
|
+
<head>
|
|
81
|
+
@include('../partials/head', { title: 'ts-cloud · Alarms' })
|
|
82
|
+
</head>
|
|
83
|
+
<body>
|
|
84
|
+
@include('../partials/nav', { active: 'serverless', name: app.name, env: app.env })
|
|
85
|
+
<div class="wrap">
|
|
86
|
+
<div class="crumbs"><a href="/serverless">Serverless</a> <span class="sep">/</span> Alarms</div>
|
|
87
|
+
<header class="page">
|
|
88
|
+
<div>
|
|
89
|
+
<h1>Alarms</h1>
|
|
90
|
+
<p>Arm CloudWatch alarms on function metrics so a paging error rate or latency spike notifies you before customers do.</p>
|
|
91
|
+
</div>
|
|
92
|
+
</header>
|
|
93
|
+
|
|
94
|
+
<div class="section">
|
|
95
|
+
<div class="panel">
|
|
96
|
+
<table>
|
|
97
|
+
<thead><tr><th>Alarm</th><th>Metric</th><th>Condition</th><th>State</th><th class="table-actions">Actions</th></tr></thead>
|
|
98
|
+
<tbody>
|
|
99
|
+
<template :for="a in rows()">
|
|
100
|
+
<tr>
|
|
101
|
+
<td class="mono"><b>{{ a.AlarmName }}</b></td>
|
|
102
|
+
<td class="mono" style="color:var(--txt3)">{{ a.MetricName }}</td>
|
|
103
|
+
<td class="mono" style="color:var(--txt2)">{{ a.ComparisonOperator }} {{ a.Threshold }}</td>
|
|
104
|
+
<td><span class="pill {{ toneOf(a.StateValue) }}">{{ a.StateValue }}</span></td>
|
|
105
|
+
<td class="table-actions">
|
|
106
|
+
<div class="row-actions" style="justify-content:flex-end">
|
|
107
|
+
<button class="btn danger sm" type="button" @click="askDelete(a.AlarmName)">Delete</button>
|
|
108
|
+
</div>
|
|
109
|
+
</td>
|
|
110
|
+
</tr>
|
|
111
|
+
</template>
|
|
112
|
+
<tr @show="rows().length === 0">
|
|
113
|
+
<td colspan="5"><div class="compact empty"><strong>No alarms armed</strong><span>Create one below to get notified when a function metric crosses a threshold.</span></div></td>
|
|
114
|
+
</tr>
|
|
115
|
+
</tbody>
|
|
116
|
+
</table>
|
|
117
|
+
|
|
118
|
+
<div class="op-confirm" @show="pendingDelete() !== null" style="margin-top:14px">
|
|
119
|
+
<span>Type <b class="mono">{{ pendingDelete() }}</b> to delete this alarm:</span>
|
|
120
|
+
<input class="op-confirm-input" :value="typedDelete()" @input="typedDelete.set($event.target.value)" @keydown.enter="confirmDelete()" placeholder="confirm" autocomplete="off">
|
|
121
|
+
<button class="btn danger sm" :disabled="!canDelete()" @click="confirmDelete()">Delete</button>
|
|
122
|
+
<button class="btn ghost sm" @click="cancelDelete()">Cancel</button>
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
|
|
127
|
+
<div class="section">
|
|
128
|
+
<h2>Create alarm</h2>
|
|
129
|
+
<div class="panel">
|
|
130
|
+
<div class="form-grid">
|
|
131
|
+
<div class="field-block">
|
|
132
|
+
<label>Metric preset</label>
|
|
133
|
+
<select :value="preset()" @change="preset.set($event.target.value)">
|
|
134
|
+
<template :for="p in presets()">
|
|
135
|
+
<option value="{{ p.key }}">{{ p.label }}</option>
|
|
136
|
+
</template>
|
|
137
|
+
</select>
|
|
138
|
+
</div>
|
|
139
|
+
<div class="field-block">
|
|
140
|
+
<label>Threshold</label>
|
|
141
|
+
<input type="number" :value="threshold()" @input="threshold.set($event.target.value)" placeholder="5" autocomplete="off">
|
|
142
|
+
</div>
|
|
143
|
+
<div class="form-actions">
|
|
144
|
+
<button class="btn" type="button" :disabled="busy()" @click="createAlarm()">Create</button>
|
|
145
|
+
<span class="msg" :class="{ ok: msgOk(), bad: msg() && !msgOk() }">{{ msg() }}</span>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
<p class="note">Alarms fire when the chosen metric crosses the threshold. Manage them live with <span class="mono">cloud alarms</span>.</p>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
</body>
|
|
153
|
+
</html>
|
|
@@ -12,10 +12,33 @@ const recent = __d?.assetsRecent ?? [
|
|
|
12
12
|
{ path: '/a4d63d2/logo.svg', size: '4 KB', type: 'image/svg+xml' },
|
|
13
13
|
]
|
|
14
14
|
</script>
|
|
15
|
+
<script client>
|
|
16
|
+
// assets:invalidate runs live against AWS through /api/serverless/operations/run.
|
|
17
|
+
const opOutput = state('')
|
|
18
|
+
const opShown = state(false)
|
|
19
|
+
const pending = state(null)
|
|
20
|
+
const typed = state('')
|
|
21
|
+
const confirmTok = derived(() => { const p = pending(); return p ? p.confirm : '' })
|
|
22
|
+
const confirmVerb = derived(() => { const p = pending(); return p ? p.verb : '' })
|
|
23
|
+
const canRun = derived(() => { const p = pending(); return !!p && typed() === p.confirm })
|
|
24
|
+
function askOp(operation, confirm, verb) { pending.set({ operation, confirm, verb }); typed.set('') }
|
|
25
|
+
function cancelOp() { pending.set(null) }
|
|
26
|
+
async function runOp() {
|
|
27
|
+
const p = pending()
|
|
28
|
+
if (!p || typed() !== p.confirm) return
|
|
29
|
+
pending.set(null); typed.set('')
|
|
30
|
+
opShown.set(true); opOutput.set('Running ' + p.operation + '...')
|
|
31
|
+
try {
|
|
32
|
+
const res = await fetch('/api/serverless/operations/run', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ operation: p.operation, confirm: p.confirm }) })
|
|
33
|
+
const body = await res.json()
|
|
34
|
+
opOutput.set((body.ok ? 'OK ' : 'FAILED ') + (body.command || p.operation) + '\n\n' + (body.stdout || '') + (body.error ? '\n' + body.error : ''))
|
|
35
|
+
} catch (e) { opOutput.set('FAILED ' + p.operation + '\n\n' + ((e && e.message) || e)) }
|
|
36
|
+
}
|
|
37
|
+
</script>
|
|
15
38
|
<!DOCTYPE html>
|
|
16
39
|
<html lang="en" data-theme="dark">
|
|
17
40
|
<head>
|
|
18
|
-
@include('../partials/head', { title: 'ts-cloud
|
|
41
|
+
@include('../partials/head', { title: 'ts-cloud · Assets & CDN' })
|
|
19
42
|
</head>
|
|
20
43
|
<body>
|
|
21
44
|
@include('../partials/nav', { active: 'serverless', name: app.name, env: app.env })
|
|
@@ -27,10 +50,14 @@ const recent = __d?.assetsRecent ?? [
|
|
|
27
50
|
<p>Versioned static assets uploaded to S3 and served via CloudFront; <span class="mono">ASSET_URL</span> is injected into the app.</p>
|
|
28
51
|
</div>
|
|
29
52
|
<div class="header-actions">
|
|
30
|
-
<
|
|
53
|
+
<button class="btn sm" type="button" @click="askOp('assets:invalidate', 'invalidate', 'invalidate the CDN cache')">Invalidate CDN cache</button>
|
|
31
54
|
</div>
|
|
32
55
|
</header>
|
|
33
56
|
|
|
57
|
+
<div class="section">
|
|
58
|
+
@include('../partials/op-confirm')
|
|
59
|
+
</div>
|
|
60
|
+
|
|
34
61
|
<div class="grid cards">
|
|
35
62
|
<div class="panel"><h3>Files</h3><div class="stat">{{ assets.files }}</div></div>
|
|
36
63
|
<div class="panel"><h3>Size</h3><div class="stat">{{ assets.sizeMb }} <small>MB</small></div></div>
|
|
@@ -17,7 +17,7 @@ const summary = __d?.costSummary ?? {
|
|
|
17
17
|
const services = __d?.costServices ?? [
|
|
18
18
|
{ name: 'Lambda (compute)', usd: 41.20, note: 'GB-seconds across http/queue/cli' },
|
|
19
19
|
{ name: 'Provisioned concurrency', usd: 28.60, note: '2 envs always-warm' },
|
|
20
|
-
{ name: 'Aurora Serverless v2', usd: 19.80, note: '0.5
|
|
20
|
+
{ name: 'Aurora Serverless v2', usd: 19.80, note: '0.5-4 ACU' },
|
|
21
21
|
{ name: 'API Gateway (HTTP)', usd: 11.30, note: '4.7M requests' },
|
|
22
22
|
{ name: 'CloudFront + S3 assets', usd: 8.10, note: 'egress + storage' },
|
|
23
23
|
{ name: 'ElastiCache (redis)', usd: 6.40, note: 'cache.t4g.micro' },
|
|
@@ -46,7 +46,7 @@ const maxTrend = Math.max(...trend)
|
|
|
46
46
|
<!DOCTYPE html>
|
|
47
47
|
<html lang="en" data-theme="dark">
|
|
48
48
|
<head>
|
|
49
|
-
@include('../partials/head', { title: 'ts-cloud
|
|
49
|
+
@include('../partials/head', { title: 'ts-cloud - Cost' })
|
|
50
50
|
</head>
|
|
51
51
|
<body>
|
|
52
52
|
@include('../partials/nav', { active: 'serverless', name: app.name, env: app.env })
|
|
@@ -55,7 +55,7 @@ const maxTrend = Math.max(...trend)
|
|
|
55
55
|
<header class="page">
|
|
56
56
|
<div>
|
|
57
57
|
<h1>Cost analysis</h1>
|
|
58
|
-
<p>Spend by service and function
|
|
58
|
+
<p>Spend by service and function - {{ period.label }}.</p>
|
|
59
59
|
</div>
|
|
60
60
|
</header>
|
|
61
61
|
|
|
@@ -137,7 +137,7 @@ const maxTrend = Math.max(...trend)
|
|
|
137
137
|
<h2>Optimize</h2>
|
|
138
138
|
<div class="panel">
|
|
139
139
|
<p class="note" style="margin-top:0">Biggest lever: <b style="color:var(--txt)">{{ services[0].name }}</b> at ${{ services[0].usd.toFixed(2) }}.</p>
|
|
140
|
-
<p class="note">• Right-size memory
|
|
140
|
+
<p class="note">• Right-size memory - over-provisioned MB inflates GB-seconds.</p>
|
|
141
141
|
<p class="note">• Drop unused <span class="mono">provisionedConcurrency</span> for ping-based <span class="mono">warm</span> if cold starts are tolerable.</p>
|
|
142
142
|
<p class="note">• Tighten Aurora <span class="mono">db-scale</span> min ACU off-peak.</p>
|
|
143
143
|
<p class="note">Full breakdown: <span class="mono">cloud cost --env {{ app.env }}</span></p>
|
|
@@ -7,11 +7,39 @@ const proxy = __d?.proxy ?? { name: 'acme-production-proxy', endpoint: 'acme-pro
|
|
|
7
7
|
const redis = __d?.redis ?? { id: 'acme-production-redis', node: 'cache.t4g.micro', engine: 'redis 7.1', hitRate: 96.4, status: 'available' }
|
|
8
8
|
const efs = __d?.efs ?? { id: 'fs-0abc', mount: '/mnt/local', sizeMb: 312, status: 'available' }
|
|
9
9
|
function tone(s) { return s === 'available' ? 'ok' : 'warn' }
|
|
10
|
+
const hasAurora = !!(__d?.aurora ?? true)
|
|
11
|
+
const seedMin = aurora?.minAcu ?? 0.5
|
|
12
|
+
const seedMax = aurora?.maxAcu ?? 4
|
|
13
|
+
</script>
|
|
14
|
+
<script client>
|
|
15
|
+
// db:scale runs live against AWS through /api/serverless/operations/run.
|
|
16
|
+
const opOutput = state('')
|
|
17
|
+
const opShown = state(false)
|
|
18
|
+
const pending = state(null)
|
|
19
|
+
const typed = state('')
|
|
20
|
+
const scaleMin = state(String(seedMin ?? ''))
|
|
21
|
+
const scaleMax = state(String(seedMax ?? ''))
|
|
22
|
+
const confirmTok = derived(() => { const p = pending(); return p ? p.confirm : '' })
|
|
23
|
+
const confirmVerb = derived(() => { const p = pending(); return p ? p.verb : '' })
|
|
24
|
+
const canRun = derived(() => { const p = pending(); return !!p && typed() === p.confirm })
|
|
25
|
+
function askOp(operation, confirm, verb) { pending.set({ operation, confirm, verb }); typed.set('') }
|
|
26
|
+
function cancelOp() { pending.set(null) }
|
|
27
|
+
async function runOp() {
|
|
28
|
+
const p = pending()
|
|
29
|
+
if (!p || typed() !== p.confirm) return
|
|
30
|
+
pending.set(null); typed.set('')
|
|
31
|
+
opShown.set(true); opOutput.set('Running ' + p.operation + '...')
|
|
32
|
+
try {
|
|
33
|
+
const res = await fetch('/api/serverless/operations/run', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ operation: p.operation, confirm: p.confirm, min: Number(scaleMin()), max: Number(scaleMax()) }) })
|
|
34
|
+
const body = await res.json()
|
|
35
|
+
opOutput.set((body.ok ? 'OK ' : 'FAILED ') + (body.command || p.operation) + '\n\n' + (body.stdout || '') + (body.error ? '\n' + body.error : ''))
|
|
36
|
+
} catch (e) { opOutput.set('FAILED ' + p.operation + '\n\n' + ((e && e.message) || e)) }
|
|
37
|
+
}
|
|
10
38
|
</script>
|
|
11
39
|
<!DOCTYPE html>
|
|
12
40
|
<html lang="en" data-theme="dark">
|
|
13
41
|
<head>
|
|
14
|
-
@include('../partials/head', { title: 'ts-cloud
|
|
42
|
+
@include('../partials/head', { title: 'ts-cloud · Data' })
|
|
15
43
|
</head>
|
|
16
44
|
<body>
|
|
17
45
|
@include('../partials/nav', { active: 'serverless', name: app.name, env: app.env })
|
|
@@ -20,7 +48,7 @@ function tone(s) { return s === 'available' ? 'ok' : 'warn' }
|
|
|
20
48
|
<header class="page">
|
|
21
49
|
<div>
|
|
22
50
|
<h1>Data services</h1>
|
|
23
|
-
<p>Managed data attached to the functions over a private VPC
|
|
51
|
+
<p>Managed data attached to the functions over a private VPC: database, connection proxy, cache, and shared filesystem.</p>
|
|
24
52
|
</div>
|
|
25
53
|
</header>
|
|
26
54
|
|
|
@@ -37,11 +65,36 @@ function tone(s) { return s === 'available' ? 'ok' : 'warn' }
|
|
|
37
65
|
<h3>Capacity (ACU)</h3>
|
|
38
66
|
<div class="stat">{{ aurora.currentAcu }} <small>now</small></div>
|
|
39
67
|
<div class="bar"><i style="width: {{ Math.round((aurora.currentAcu / aurora.maxAcu) * 100) }}%"></i></div>
|
|
40
|
-
<p class="note">range {{ aurora.minAcu }}
|
|
68
|
+
<p class="note">range {{ aurora.minAcu }} to {{ aurora.maxAcu }} ACU · rescale with <span class="mono">cloud serverless:db-scale {{ aurora.minAcu }} {{ aurora.maxAcu }}</span></p>
|
|
41
69
|
</div>
|
|
42
70
|
</div>
|
|
43
71
|
</div>
|
|
44
72
|
|
|
73
|
+
<div class="section">
|
|
74
|
+
<h2>Scale Aurora capacity</h2>
|
|
75
|
+
@if (hasAurora)
|
|
76
|
+
<div class="panel">
|
|
77
|
+
<div class="form-grid">
|
|
78
|
+
<div class="field-block">
|
|
79
|
+
<label>Min ACUs</label>
|
|
80
|
+
<input type="number" step="0.5" min="0.5" :value="scaleMin()" @input="scaleMin.set($event.target.value)" placeholder="{{ seedMin }}" autocomplete="off">
|
|
81
|
+
</div>
|
|
82
|
+
<div class="field-block">
|
|
83
|
+
<label>Max ACUs</label>
|
|
84
|
+
<input type="number" step="0.5" min="0.5" :value="scaleMax()" @input="scaleMax.set($event.target.value)" placeholder="{{ seedMax }}" autocomplete="off">
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
<div class="form-actions">
|
|
88
|
+
<button class="btn sm" type="button" @click="askOp('db:scale', 'scale', 'scale the Aurora cluster')">Scale</button>
|
|
89
|
+
</div>
|
|
90
|
+
@include('../partials/op-confirm')
|
|
91
|
+
<p class="note">Adjusts the Aurora Serverless v2 capacity range. New writes settle into the range within a few seconds.</p>
|
|
92
|
+
</div>
|
|
93
|
+
@else
|
|
94
|
+
<p class="note">Aurora Serverless is not attached to this app.</p>
|
|
95
|
+
@endif
|
|
96
|
+
</div>
|
|
97
|
+
|
|
45
98
|
<div class="cols section">
|
|
46
99
|
<div>
|
|
47
100
|
<h2>RDS Proxy</h2>
|
|
@@ -10,10 +10,33 @@ const deployments = __d?.deploymentsDetail ?? [
|
|
|
10
10
|
]
|
|
11
11
|
function tone(s) { return s === 'success' ? 'ok' : (s === 'rolled-back' ? 'warn' : 'bad') }
|
|
12
12
|
</script>
|
|
13
|
+
<script client>
|
|
14
|
+
// Rollback + redeploy run live against AWS through /api/serverless/operations/run.
|
|
15
|
+
const opOutput = state('')
|
|
16
|
+
const opShown = state(false)
|
|
17
|
+
const pending = state(null)
|
|
18
|
+
const typed = state('')
|
|
19
|
+
const confirmTok = derived(() => { const p = pending(); return p ? p.confirm : '' })
|
|
20
|
+
const confirmVerb = derived(() => { const p = pending(); return p ? p.verb : '' })
|
|
21
|
+
const canRun = derived(() => { const p = pending(); return !!p && typed() === p.confirm })
|
|
22
|
+
function askOp(operation, confirm, verb) { pending.set({ operation, confirm, verb }); typed.set('') }
|
|
23
|
+
function cancelOp() { pending.set(null) }
|
|
24
|
+
async function runOp() {
|
|
25
|
+
const p = pending()
|
|
26
|
+
if (!p || typed() !== p.confirm) return
|
|
27
|
+
pending.set(null); typed.set('')
|
|
28
|
+
opShown.set(true); opOutput.set('Running ' + p.operation + '...')
|
|
29
|
+
try {
|
|
30
|
+
const res = await fetch('/api/serverless/operations/run', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ operation: p.operation, confirm: p.confirm }) })
|
|
31
|
+
const body = await res.json()
|
|
32
|
+
opOutput.set((body.ok ? 'OK ' : 'FAILED ') + (body.command || p.operation) + '\n\n' + (body.stdout || '') + (body.error ? '\n' + body.error : ''))
|
|
33
|
+
} catch (e) { opOutput.set('FAILED ' + p.operation + '\n\n' + ((e && e.message) || e)) }
|
|
34
|
+
}
|
|
35
|
+
</script>
|
|
13
36
|
<!DOCTYPE html>
|
|
14
37
|
<html lang="en" data-theme="dark">
|
|
15
38
|
<head>
|
|
16
|
-
@include('../partials/head', { title: 'ts-cloud
|
|
39
|
+
@include('../partials/head', { title: 'ts-cloud · Deployments' })
|
|
17
40
|
</head>
|
|
18
41
|
<body>
|
|
19
42
|
@include('../partials/nav', { active: 'serverless', name: app.name, env: app.env })
|
|
@@ -24,9 +47,9 @@ function tone(s) { return s === 'success' ? 'ok' : (s === 'rolled-back' ? 'warn'
|
|
|
24
47
|
<h1>Deployments</h1>
|
|
25
48
|
<p>Every deploy publishes an immutable artifact; activation is atomic and rollback flips back instantly.</p>
|
|
26
49
|
</div>
|
|
27
|
-
<div class="
|
|
28
|
-
<
|
|
29
|
-
<
|
|
50
|
+
<div class="header-actions">
|
|
51
|
+
<button class="btn danger sm" type="button" @click="askOp('rollback', 'rollback', 'roll back to the previous build')">Roll back</button>
|
|
52
|
+
<button class="btn sm" type="button" @click="askOp('redeploy', 'redeploy', 'redeploy the current build')">Redeploy</button>
|
|
30
53
|
</div>
|
|
31
54
|
</header>
|
|
32
55
|
|
|
@@ -40,7 +63,7 @@ function tone(s) { return s === 'success' ? 'ok' : (s === 'rolled-back' ? 'warn'
|
|
|
40
63
|
<td class="mono">{{ d.sha }}</td>
|
|
41
64
|
<td><span class="pill {{ tone(d.status) }}">{{ d.status }}</span></td>
|
|
42
65
|
<td class="mono">v{{ d.version }}</td>
|
|
43
|
-
<td class="mono" style="font-size:11px;color:var(--txt3)">@if (d.hooks.length === 0)
|
|
66
|
+
<td class="mono" style="font-size:11px;color:var(--txt3)">@if (d.hooks.length === 0)-@else{{ d.hooks.join(' · ') }}@endif</td>
|
|
44
67
|
<td style="color:var(--txt2)">{{ d.by }}</td>
|
|
45
68
|
<td class="mono">{{ d.took }}</td>
|
|
46
69
|
<td style="color:var(--txt3)">{{ d.when }}</td>
|
|
@@ -48,7 +71,8 @@ function tone(s) { return s === 'success' ? 'ok' : (s === 'rolled-back' ? 'warn'
|
|
|
48
71
|
@endforeach
|
|
49
72
|
</tbody>
|
|
50
73
|
</table>
|
|
51
|
-
|
|
74
|
+
@include('../partials/op-confirm')
|
|
75
|
+
<p class="note">Rollback restores the previous build's code and env and flips the live alias back instantly. Redeploy re-activates the current build without rebuilding.</p>
|
|
52
76
|
</div>
|
|
53
77
|
</div>
|
|
54
78
|
</div>
|
|
@@ -13,7 +13,7 @@ function fmt(n) { return n >= 1000 ? `${(n / 1000).toFixed(n >= 10000 ? 0 : 1)}k
|
|
|
13
13
|
<!DOCTYPE html>
|
|
14
14
|
<html lang="en" data-theme="dark">
|
|
15
15
|
<head>
|
|
16
|
-
@include('../partials/head', { title: 'ts-cloud
|
|
16
|
+
@include('../partials/head', { title: 'ts-cloud - Firewall' })
|
|
17
17
|
</head>
|
|
18
18
|
<body>
|
|
19
19
|
@include('../partials/nav', { active: 'serverless', name: app.name, env: app.env })
|
|
@@ -6,7 +6,7 @@ const app = __d?.app ?? { name: 'acme', env: 'production' }
|
|
|
6
6
|
const functions = __d?.functionsDetail ?? [
|
|
7
7
|
{
|
|
8
8
|
key: 'http', name: 'acme-production-http', runtime: 'provided.al2023', arch: 'x86_64',
|
|
9
|
-
memory: 1024, timeout: 28, ephemeral: 512, version: '$LATEST', concurrency: '
|
|
9
|
+
memory: 1024, timeout: 28, ephemeral: 512, version: '$LATEST', concurrency: '-',
|
|
10
10
|
invocations: 171200, errors: 21, throttles: 0, p50: 32, p95: 86, p99: 180, maxMem: 184,
|
|
11
11
|
spark: [40, 52, 48, 61, 73, 66, 80, 72, 95, 88, 70, 64],
|
|
12
12
|
env: ['APP_ENV', 'ASSET_URL', 'TSCLOUD_CACHE_TABLE', 'DB_HOST', 'QUEUE_CONNECTION'],
|
|
@@ -29,7 +29,7 @@ const functions = __d?.functionsDetail ?? [
|
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
key: 'cli', name: 'acme-production-cli', runtime: 'provided.al2023', arch: 'x86_64',
|
|
32
|
-
memory: 1024, timeout: 900, ephemeral: 512, version: '$LATEST', concurrency: '
|
|
32
|
+
memory: 1024, timeout: 900, ephemeral: 512, version: '$LATEST', concurrency: '-',
|
|
33
33
|
invocations: 1440, errors: 0, throttles: 0, p50: 800, p95: 1100, p99: 1400, maxMem: 312,
|
|
34
34
|
spark: [2, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1],
|
|
35
35
|
env: ['APP_ENV', 'TSCLOUD_SCHEDULER'],
|
|
@@ -41,11 +41,40 @@ const functions = __d?.functionsDetail ?? [
|
|
|
41
41
|
]
|
|
42
42
|
|
|
43
43
|
function fmt(n) { return n >= 1000 ? `${(n / 1000).toFixed(n >= 10000 ? 0 : 1)}k` : String(n) }
|
|
44
|
+
const modes = (__d?.functionsDetail ?? functions).map(f => f.key)
|
|
45
|
+
const initialMode = modes[0] ?? 'http'
|
|
46
|
+
</script>
|
|
47
|
+
<script client>
|
|
48
|
+
// Per-function config runs live against AWS through /api/serverless/functions/config.
|
|
49
|
+
const mode = state(initialMode)
|
|
50
|
+
const mem = state('1024')
|
|
51
|
+
const to = state('28')
|
|
52
|
+
const opOutput = state('')
|
|
53
|
+
const opShown = state(false)
|
|
54
|
+
const pending = state(null)
|
|
55
|
+
const typed = state('')
|
|
56
|
+
const confirmTok = derived(() => { const p = pending(); return p ? p.confirm : '' })
|
|
57
|
+
const confirmVerb = derived(() => { const p = pending(); return p ? p.verb : '' })
|
|
58
|
+
const canRun = derived(() => { const p = pending(); return !!p && typed() === p.confirm })
|
|
59
|
+
function askOp(operation, confirm, verb) { pending.set({ operation, confirm, verb }); typed.set('') }
|
|
60
|
+
function cancelOp() { pending.set(null) }
|
|
61
|
+
function applyConfig() { const m = mode(); askOp(m, m, 'update the ' + m + ' function') }
|
|
62
|
+
async function runOp() {
|
|
63
|
+
const p = pending()
|
|
64
|
+
if (!p || typed() !== p.confirm) return
|
|
65
|
+
pending.set(null); typed.set('')
|
|
66
|
+
opShown.set(true); opOutput.set('Updating ' + p.operation + '...')
|
|
67
|
+
try {
|
|
68
|
+
const res = await fetch('/api/serverless/functions/config', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ mode: p.operation, memory: Number(mem()), timeout: Number(to()), confirm: p.confirm }) })
|
|
69
|
+
const body = await res.json()
|
|
70
|
+
opOutput.set((body.ok ? 'OK ' : 'FAILED ') + (body.command || p.operation) + '\n\n' + (body.stdout || '') + (body.error ? '\n' + body.error : ''))
|
|
71
|
+
} catch (e) { opOutput.set('FAILED ' + p.operation + '\n\n' + ((e && e.message) || e)) }
|
|
72
|
+
}
|
|
44
73
|
</script>
|
|
45
74
|
<!DOCTYPE html>
|
|
46
75
|
<html lang="en" data-theme="dark">
|
|
47
76
|
<head>
|
|
48
|
-
@include('../partials/head', { title: 'ts-cloud
|
|
77
|
+
@include('../partials/head', { title: 'ts-cloud · Functions' })
|
|
49
78
|
</head>
|
|
50
79
|
<body>
|
|
51
80
|
@include('../partials/nav', { active: 'serverless', name: app.name, env: app.env })
|
|
@@ -55,7 +84,7 @@ function fmt(n) { return n >= 1000 ? `${(n / 1000).toFixed(n >= 10000 ? 0 : 1)}k
|
|
|
55
84
|
<header class="page">
|
|
56
85
|
<div>
|
|
57
86
|
<h1>Functions</h1>
|
|
58
|
-
<p>The three Lambda functions sharing one artifact
|
|
87
|
+
<p>The three Lambda functions sharing one artifact - HTTP, queue worker, and CLI/scheduler.</p>
|
|
59
88
|
</div>
|
|
60
89
|
</header>
|
|
61
90
|
|
|
@@ -113,6 +142,35 @@ function fmt(n) { return n >= 1000 ? `${(n / 1000).toFixed(n >= 10000 ? 0 : 1)}k
|
|
|
113
142
|
</div>
|
|
114
143
|
</div>
|
|
115
144
|
@endforeach
|
|
145
|
+
|
|
146
|
+
<div class="section">
|
|
147
|
+
<h2>Configure function</h2>
|
|
148
|
+
<div class="panel">
|
|
149
|
+
<div class="form-grid">
|
|
150
|
+
<div class="field-block">
|
|
151
|
+
<label>Function</label>
|
|
152
|
+
<select :value="mode()" @change="mode.set($event.target.value)">
|
|
153
|
+
@foreach (modes as m)
|
|
154
|
+
<option value="{{ m }}">{{ m }}</option>
|
|
155
|
+
@endforeach
|
|
156
|
+
</select>
|
|
157
|
+
</div>
|
|
158
|
+
<div class="field-block">
|
|
159
|
+
<label>Memory (MB)</label>
|
|
160
|
+
<input type="number" min="128" max="10240" :value="mem()" @input="mem.set($event.target.value)" placeholder="1024" autocomplete="off">
|
|
161
|
+
</div>
|
|
162
|
+
<div class="field-block">
|
|
163
|
+
<label>Timeout (s)</label>
|
|
164
|
+
<input type="number" min="1" max="900" :value="to()" @input="to.set($event.target.value)" placeholder="28" autocomplete="off">
|
|
165
|
+
</div>
|
|
166
|
+
<div class="form-actions">
|
|
167
|
+
<button class="btn" type="button" @click="applyConfig()">Apply</button>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
@include('../partials/op-confirm')
|
|
171
|
+
<p class="note">Memory is 128 to 10240 MB, timeout is 1 to 900 s. Changes are applied to the live function immediately via <span class="mono">cloud functions:config</span>.</p>
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
116
174
|
</div>
|
|
117
175
|
</body>
|
|
118
176
|
</html>
|
|
@@ -7,7 +7,7 @@ const emptyReason = __d?.serverlessLogsEmptyReason ?? 'No function logs are avai
|
|
|
7
7
|
</script>
|
|
8
8
|
<script client>
|
|
9
9
|
// Reactive aggregated CloudWatch logs across the functions, with level + function
|
|
10
|
-
// filtering and auto-refresh
|
|
10
|
+
// filtering and auto-refresh - seeded from build-time data (stx server→client
|
|
11
11
|
// bridge), then live-refreshed. Signals + directives only (no manual DOM).
|
|
12
12
|
const logs = state(initialLogs)
|
|
13
13
|
const level = state('all')
|
|
@@ -28,7 +28,7 @@ onMount(() => { refresh(); const id = setInterval(refresh, 8000); return () => c
|
|
|
28
28
|
</script>
|
|
29
29
|
<!DOCTYPE html>
|
|
30
30
|
<html lang="en" data-theme="dark">
|
|
31
|
-
<head>@include('../partials/head', { title: 'ts-cloud
|
|
31
|
+
<head>@include('../partials/head', { title: 'ts-cloud - Function logs' })</head>
|
|
32
32
|
<body>
|
|
33
33
|
@include('../partials/nav', { active: 'serverless', name: app.name, env: app.env })
|
|
34
34
|
<div class="wrap">
|
|
@@ -16,7 +16,7 @@ function pct(v, max) { return Math.round((v / max) * 100) }
|
|
|
16
16
|
<!DOCTYPE html>
|
|
17
17
|
<html lang="en" data-theme="dark">
|
|
18
18
|
<head>
|
|
19
|
-
@include('../partials/head', { title: 'ts-cloud
|
|
19
|
+
@include('../partials/head', { title: 'ts-cloud · Metrics' })
|
|
20
20
|
</head>
|
|
21
21
|
<body>
|
|
22
22
|
@include('../partials/nav', { active: 'serverless', name: app.name, env: app.env })
|
|
@@ -27,6 +27,10 @@ function pct(v, max) { return Math.round((v / max) * 100) }
|
|
|
27
27
|
<h1>Metrics</h1>
|
|
28
28
|
<p>CloudWatch metrics across the function trio, last {{ window }}.</p>
|
|
29
29
|
</div>
|
|
30
|
+
<div class="header-actions">
|
|
31
|
+
<a class="btn ghost sm" href="/serverless/traces">Traces →</a>
|
|
32
|
+
<a class="btn ghost sm" href="/serverless/alarms">Alarms →</a>
|
|
33
|
+
</div>
|
|
30
34
|
</header>
|
|
31
35
|
|
|
32
36
|
<div class="grid cards">
|