@stacksjs/ts-cloud 0.7.63 → 0.7.64
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 +1556 -1284
- package/dist/{chunk-0zbqbrqg.js → chunk-0z7ry8fm.js} +889 -89
- package/dist/{chunk-zvk0f89t.js → chunk-28vh1h91.js} +1 -1
- package/dist/{chunk-t092y37p.js → chunk-3c6pp063.js} +519 -143
- package/dist/{chunk-tq46tnxv.js → chunk-ecsyc3p0.js} +4 -1
- package/dist/{chunk-rxhqny6z.js → chunk-zcvdzvmz.js} +2 -2
- package/dist/deploy/dashboard-data-server.d.ts +27 -1
- package/dist/deploy/index.js +4 -4
- package/dist/deploy/telemetry-collection.d.ts +2 -0
- package/dist/drivers/hetzner/client.d.ts +84 -2
- package/dist/drivers/hetzner/host-optimization.d.ts +33 -0
- package/dist/drivers/hetzner/monitoring.d.ts +46 -0
- package/dist/drivers/hetzner/resize-remote.d.ts +38 -0
- package/dist/drivers/hetzner/resize-state.d.ts +24 -0
- package/dist/drivers/hetzner/resize.d.ts +86 -0
- package/dist/drivers/index.d.ts +8 -0
- package/dist/drivers/index.js +35 -3
- package/dist/drivers/shared/compute-deploy.d.ts +15 -0
- package/dist/drivers/shared/ufw.d.ts +4 -2
- package/dist/index.js +4 -4
- package/dist/ui/account/automation.html +4 -4
- package/dist/ui/account/security.html +2 -2
- package/dist/ui/applications/compose.html +4 -4
- package/dist/ui/applications/new.html +2 -2
- package/dist/ui/data/backups.html +4 -4
- package/dist/ui/data/services.html +4 -4
- package/dist/ui/data/volumes.html +4 -4
- package/dist/ui/index.html +12 -7
- package/dist/ui/integrations.html +2 -2
- package/dist/ui/operations/alerts.html +4 -4
- package/dist/ui/operations/configuration.html +4 -4
- package/dist/ui/operations/jobs.html +4 -4
- package/dist/ui/operations/maintenance.html +3 -3
- package/dist/ui/operations/observability.html +12 -6
- package/dist/ui/operations/previews.html +4 -4
- package/dist/ui/operations/queue.html +4 -4
- package/dist/ui/operations/regions.html +4 -4
- package/dist/ui/operations/releases.html +4 -4
- package/dist/ui/operations/workloads.html +4 -4
- package/dist/ui/server/actions.html +4 -4
- package/dist/ui/server/activity.html +2 -2
- package/dist/ui/server/capacity.html +4 -4
- package/dist/ui/server/database.html +4 -4
- package/dist/ui/server/deployments.html +4 -4
- package/dist/ui/server/diagnostics.html +2 -2
- package/dist/ui/server/firewall.html +4 -4
- package/dist/ui/server/fleet.html +4 -4
- package/dist/ui/server/logs.html +4 -4
- package/dist/ui/server/metrics.html +203 -3
- package/dist/ui/server/services.html +2 -2
- package/dist/ui/server/sites.html +102 -6
- package/dist/ui/server/ssh-keys.html +4 -4
- package/dist/ui/server/team.html +4 -4
- package/dist/ui/server/terminal.html +2 -2
- package/dist/ui/serverless/alarms.html +4 -4
- package/dist/ui/serverless/assets.html +2 -2
- package/dist/ui/serverless/cost.html +2 -2
- package/dist/ui/serverless/data.html +4 -4
- package/dist/ui/serverless/firewall.html +2 -2
- package/dist/ui/serverless/functions.html +4 -4
- package/dist/ui/serverless/logs.html +4 -4
- package/dist/ui/serverless/metrics.html +2 -2
- package/dist/ui/serverless/queues.html +4 -4
- package/dist/ui/serverless/secrets.html +4 -4
- package/dist/ui/serverless/traces.html +4 -4
- package/dist/ui/serverless.html +4 -4
- package/dist/ui-src/pages/index.stx +12 -10
- package/dist/ui-src/pages/operations/observability.stx +6 -4
- package/dist/ui-src/pages/server/metrics.stx +262 -106
- package/dist/ui-src/pages/server/sites.stx +98 -6
- package/package.json +3 -3
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
step,
|
|
10
10
|
success,
|
|
11
11
|
warn
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-28vh1h91.js";
|
|
13
13
|
import {
|
|
14
14
|
AwsBackupClient,
|
|
15
15
|
CloudWatchLogsClient,
|
|
@@ -45,7 +45,7 @@ import {
|
|
|
45
45
|
resolveSiteKind,
|
|
46
46
|
resolveUiSource,
|
|
47
47
|
siteInstallBase
|
|
48
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-0z7ry8fm.js";
|
|
49
49
|
import {
|
|
50
50
|
artifactKey,
|
|
51
51
|
buildCloudFormationTemplate,
|
|
@@ -67,7 +67,7 @@ import {
|
|
|
67
67
|
resolveStatePath,
|
|
68
68
|
setStateDir,
|
|
69
69
|
statePath
|
|
70
|
-
} from "./chunk-
|
|
70
|
+
} from "./chunk-ecsyc3p0.js";
|
|
71
71
|
import {
|
|
72
72
|
SSMClient
|
|
73
73
|
} from "./chunk-703nkybg.js";
|
|
@@ -8317,7 +8317,7 @@ async function deployServerlessApp(config, environment2, opts = {}) {
|
|
|
8317
8317
|
}
|
|
8318
8318
|
if (imageMode) {
|
|
8319
8319
|
step("Building + pushing container image");
|
|
8320
|
-
const { buildAndPushServerlessImage } = await import("./chunk-
|
|
8320
|
+
const { buildAndPushServerlessImage } = await import("./chunk-zcvdzvmz.js");
|
|
8321
8321
|
const built = await buildAndPushServerlessImage({
|
|
8322
8322
|
app,
|
|
8323
8323
|
projectRoot,
|
|
@@ -38367,28 +38367,183 @@ var PROBED_SERVICES = [
|
|
|
38367
38367
|
"meilisearch"
|
|
38368
38368
|
];
|
|
38369
38369
|
var UNKNOWN = "-";
|
|
38370
|
-
function
|
|
38371
|
-
|
|
38370
|
+
function sharedBoxProbeScript(projectFilter) {
|
|
38371
|
+
const source = String.raw`
|
|
38372
|
+
import { basename, join } from 'node:path'
|
|
38373
|
+
import { existsSync, readdirSync, readFileSync, readlinkSync, statSync } from 'node:fs'
|
|
38374
|
+
|
|
38375
|
+
const fragmentsRoot = '/etc/rpx/sites.d'
|
|
38376
|
+
const certsRoot = '/etc/rpx/certs'
|
|
38377
|
+
const projectFilter = ${JSON.stringify(projectFilter ?? "")}
|
|
38378
|
+
const configuredRoutes = []
|
|
38379
|
+
if (existsSync(fragmentsRoot)) {
|
|
38380
|
+
for (const file of readdirSync(fragmentsRoot).filter(name => name.endsWith('.json')).sort()) {
|
|
38381
|
+
try {
|
|
38382
|
+
const fragment = JSON.parse(readFileSync(join(fragmentsRoot, file), 'utf8'))
|
|
38383
|
+
const project = String(fragment.slug || basename(file, '.json'))
|
|
38384
|
+
if (projectFilter && project !== projectFilter) continue
|
|
38385
|
+
for (const route of Array.isArray(fragment.proxies) ? fragment.proxies : []) {
|
|
38386
|
+
if (!route || !route.to) continue
|
|
38387
|
+
const path = route.path || '/'
|
|
38388
|
+
const staticRoot = typeof route.static === 'string' ? route.static : route.static?.dir
|
|
38389
|
+
const kind = route.redirect ? 'redirect' : staticRoot ? 'static' : 'app'
|
|
38390
|
+
const install = staticRoot || ''
|
|
38391
|
+
const installName = install.match(/^\/var\/www\/(.+?)\/current(?:\/|$)/)?.[1]
|
|
38392
|
+
const siteName = installName?.startsWith(project + '-') ? installName.slice(project.length + 1) : String(route.id || route.to)
|
|
38393
|
+
const href = 'https://' + route.to + (path === '/' ? '' : path)
|
|
38394
|
+
configuredRoutes.push({
|
|
38395
|
+
name: siteName,
|
|
38396
|
+
project,
|
|
38397
|
+
route: path === '/' ? route.to : route.to + path,
|
|
38398
|
+
href,
|
|
38399
|
+
domain: route.to,
|
|
38400
|
+
path,
|
|
38401
|
+
kind,
|
|
38402
|
+
runtime: kind === 'app' ? 'service' : kind,
|
|
38403
|
+
deploy: kind === 'app' ? 'service' : kind === 'static' ? 'server static' : 'redirect',
|
|
38404
|
+
tls: 'https',
|
|
38405
|
+
root: install || (Array.isArray(route.from) ? route.from.join(', ') : route.from || 'gateway'),
|
|
38406
|
+
branch: kind === 'static' ? 'build artifact' : 'main',
|
|
38407
|
+
install,
|
|
38408
|
+
})
|
|
38409
|
+
}
|
|
38410
|
+
}
|
|
38411
|
+
catch {}
|
|
38412
|
+
}
|
|
38413
|
+
}
|
|
38414
|
+
|
|
38415
|
+
const routes = await Promise.all(configuredRoutes.map(async route => {
|
|
38416
|
+
const started = performance.now()
|
|
38417
|
+
let httpStatus = 0
|
|
38418
|
+
const probe = async (href) => {
|
|
38419
|
+
const response = await fetch(href, {
|
|
38420
|
+
redirect: 'manual',
|
|
38421
|
+
headers: { 'user-agent': 'ts-cloud-monitor/1.0' },
|
|
38422
|
+
signal: AbortSignal.timeout(6000),
|
|
38423
|
+
})
|
|
38424
|
+
const status = response.status
|
|
38425
|
+
await response.body?.cancel()
|
|
38426
|
+
return status
|
|
38427
|
+
}
|
|
38428
|
+
try {
|
|
38429
|
+
httpStatus = await probe(route.href)
|
|
38430
|
+
if (httpStatus === 404 && route.kind === 'app') {
|
|
38431
|
+
const healthHref = route.href.replace(/\/$/, '') + '/health'
|
|
38432
|
+
httpStatus = await probe(healthHref)
|
|
38433
|
+
}
|
|
38434
|
+
}
|
|
38435
|
+
catch {}
|
|
38436
|
+
const responseMs = Math.max(0, Math.round(performance.now() - started))
|
|
38437
|
+
let tlsDaysRemaining
|
|
38438
|
+
const cert = join(certsRoot, route.domain + '.crt')
|
|
38439
|
+
if (existsSync(cert)) {
|
|
38440
|
+
const result = Bun.spawnSync(['openssl', 'x509', '-in', cert, '-noout', '-enddate'])
|
|
38441
|
+
const expiry = result.success ? result.stdout.toString().trim().replace(/^notAfter=/, '') : ''
|
|
38442
|
+
const expiresAt = Date.parse(expiry)
|
|
38443
|
+
if (Number.isFinite(expiresAt)) tlsDaysRemaining = Math.floor((expiresAt - Date.now()) / 86400000)
|
|
38444
|
+
}
|
|
38445
|
+
let release
|
|
38446
|
+
let deployedAt
|
|
38447
|
+
if (route.install) {
|
|
38448
|
+
try {
|
|
38449
|
+
release = basename(readlinkSync(route.install))
|
|
38450
|
+
deployedAt = statSync(route.install).mtime.toISOString()
|
|
38451
|
+
}
|
|
38452
|
+
catch {}
|
|
38453
|
+
}
|
|
38454
|
+
const status = httpStatus === 0 || httpStatus >= 500 ? 'failed' : httpStatus === 404 ? 'degraded' : 'live'
|
|
38455
|
+
const { install, ...site } = route
|
|
38456
|
+
return {
|
|
38457
|
+
...site,
|
|
38458
|
+
status,
|
|
38459
|
+
httpStatus,
|
|
38460
|
+
responseMs,
|
|
38461
|
+
tlsDaysRemaining,
|
|
38462
|
+
checkedAt: new Date().toISOString(),
|
|
38463
|
+
release,
|
|
38464
|
+
deployedAt,
|
|
38465
|
+
}
|
|
38466
|
+
}))
|
|
38467
|
+
for (const route of routes) console.log('DISCOVERED_SITE=' + Buffer.from(JSON.stringify(route)).toString('base64'))
|
|
38468
|
+
|
|
38469
|
+
const serviceOutput = Bun.spawnSync([
|
|
38470
|
+
'systemctl', 'list-units', '--type=service', '--state=running', '--no-legend', '--plain',
|
|
38471
|
+
]).stdout.toString()
|
|
38472
|
+
for (const line of serviceOutput.split('\n')) {
|
|
38473
|
+
const unit = line.trim().split(/\s+/)[0]
|
|
38474
|
+
if (!unit) continue
|
|
38475
|
+
const properties = Bun.spawnSync([
|
|
38476
|
+
'systemctl',
|
|
38477
|
+
'show',
|
|
38478
|
+
unit,
|
|
38479
|
+
'--property=WorkingDirectory,ExecStart,MemoryCurrent,UnitFileState,ActiveEnterTimestamp',
|
|
38480
|
+
]).stdout.toString()
|
|
38481
|
+
const values = Object.fromEntries(properties.split('\n').filter(Boolean).map(value => {
|
|
38482
|
+
const separator = value.indexOf('=')
|
|
38483
|
+
return separator < 0 ? [value, ''] : [value.slice(0, separator), value.slice(separator + 1)]
|
|
38484
|
+
}))
|
|
38485
|
+
if (!/^\/var\/www\/|^\/opt\/ts-cloud\//.test(values.WorkingDirectory || '') && !/\/var\/www\/|\/opt\/ts-cloud\//.test(values.ExecStart || '')) continue
|
|
38486
|
+
console.log([
|
|
38487
|
+
'SVC',
|
|
38488
|
+
unit,
|
|
38489
|
+
'active',
|
|
38490
|
+
values.MemoryCurrent || '0',
|
|
38491
|
+
values.UnitFileState || '-',
|
|
38492
|
+
values.ActiveEnterTimestamp || '-',
|
|
38493
|
+
].join('='))
|
|
38494
|
+
}
|
|
38495
|
+
`;
|
|
38496
|
+
return `echo '${Buffer.from(source).toString("base64")}' | base64 -d | bun -`;
|
|
38497
|
+
}
|
|
38498
|
+
function metricsScript(options = {}) {
|
|
38499
|
+
const commands = [
|
|
38372
38500
|
"set +e",
|
|
38373
38501
|
'echo "CPUS=$(nproc 2>/dev/null || echo 1)"',
|
|
38502
|
+
`read TS_TOTAL_1 TS_IDLE_1 <<EOF
|
|
38503
|
+
$(awk '/^cpu /{idle=$5+$6; total=0; for(i=2;i<=NF;i++) total+=$i; print total, idle; exit}' /proc/stat 2>/dev/null)
|
|
38504
|
+
EOF`,
|
|
38505
|
+
"sleep 0.2",
|
|
38506
|
+
`read TS_TOTAL_2 TS_IDLE_2 <<EOF
|
|
38507
|
+
$(awk '/^cpu /{idle=$5+$6; total=0; for(i=2;i<=NF;i++) total+=$i; print total, idle; exit}' /proc/stat 2>/dev/null)
|
|
38508
|
+
EOF`,
|
|
38509
|
+
`echo "CPUUSED=$(awk -v t1="$TS_TOTAL_1" -v i1="$TS_IDLE_1" -v t2="$TS_TOTAL_2" -v i2="$TS_IDLE_2" 'BEGIN { dt=t2-t1; di=i2-i1; if (dt>0) printf "%.1f", 100*(dt-di)/dt; else print 0 }')"`,
|
|
38374
38510
|
`echo "LOAD=$(cut -d' ' -f1 /proc/loadavg 2>/dev/null || echo 0)"`,
|
|
38511
|
+
`echo "LOAD5=$(cut -d' ' -f2 /proc/loadavg 2>/dev/null || echo 0)"`,
|
|
38512
|
+
`echo "LOAD15=$(cut -d' ' -f3 /proc/loadavg 2>/dev/null || echo 0)"`,
|
|
38375
38513
|
`echo "MEMTOTAL=$(free -m 2>/dev/null | awk '/^Mem:/{print $2}')"`,
|
|
38376
38514
|
`echo "MEMUSED=$(free -m 2>/dev/null | awk '/^Mem:/{print $3}')"`,
|
|
38515
|
+
`echo "MEMAVAILABLE=$(free -m 2>/dev/null | awk '/^Mem:/{print $7}')"`,
|
|
38516
|
+
`echo "MEMCACHE=$(free -m 2>/dev/null | awk '/^Mem:/{print $6}')"`,
|
|
38517
|
+
`echo "SWAPTOTAL=$(free -m 2>/dev/null | awk '/^Swap:/{print $2}')"`,
|
|
38518
|
+
`echo "SWAPUSED=$(free -m 2>/dev/null | awk '/^Swap:/{print $3}')"`,
|
|
38377
38519
|
`echo "DISKPCT=$(df -P / 2>/dev/null | awk 'NR==2{gsub("%","",$5);print $5}')"`,
|
|
38378
38520
|
`echo "DISKUSEDG=$(df -BG / 2>/dev/null | awk 'NR==2{gsub("G","",$3);print $3}')"`,
|
|
38379
38521
|
`echo "DISKTOTG=$(df -BG / 2>/dev/null | awk 'NR==2{gsub("G","",$2);print $2}')"`,
|
|
38522
|
+
`echo "INODEPCT=$(df -Pi / 2>/dev/null | awk 'NR==2{gsub("%","",$5);print $5}')"`,
|
|
38523
|
+
`echo "NETRX=$(awk -F'[: ]+' 'NR>2 && $2!="lo"{sum+=$3} END{print sum+0}' /proc/net/dev 2>/dev/null)"`,
|
|
38524
|
+
`echo "NETTX=$(awk -F'[: ]+' 'NR>2 && $2!="lo"{sum+=$11} END{print sum+0}' /proc/net/dev 2>/dev/null)"`,
|
|
38525
|
+
`echo "PROCESSES=$(ps -e --no-headers 2>/dev/null | awk 'END{print NR+0}')"`,
|
|
38380
38526
|
`echo "UPTIME=$(uptime -p 2>/dev/null | sed 's/^up //' || echo unknown)"`,
|
|
38381
|
-
|
|
38382
|
-
|
|
38383
|
-
"true"
|
|
38527
|
+
`echo "UPTIME_SECONDS=$(cut -d' ' -f1 /proc/uptime 2>/dev/null || echo 0)"`,
|
|
38528
|
+
'echo "OS=$(. /etc/os-release 2>/dev/null; echo "$PRETTY_NAME")"'
|
|
38384
38529
|
];
|
|
38530
|
+
if (options.includeServices !== false)
|
|
38531
|
+
commands.push(`for s in ${PROBED_SERVICES.join(" ")}; do st=$(systemctl is-active "$s" 2>/dev/null); if [ -n "$st" ] && [ "$st" != "inactive" ] && [ "$st" != "unknown" ]; then mem=$(systemctl show "$s" -p MemoryCurrent --value 2>/dev/null); en=$(systemctl is-enabled "$s" 2>/dev/null); since=$(systemctl show "$s" -p ActiveEnterTimestamp --value 2>/dev/null); echo "SVC=$s=$st=$mem=$en=$since"; fi; done`);
|
|
38532
|
+
commands.push(sharedBoxProbeScript(options.projectFilter), "true");
|
|
38533
|
+
return commands;
|
|
38385
38534
|
}
|
|
38386
38535
|
function parseBlock(output) {
|
|
38387
|
-
const kv = { services: [] };
|
|
38536
|
+
const kv = { services: [], discoveredSites: [] };
|
|
38388
38537
|
for (const line of output.split(`
|
|
38389
38538
|
`)) {
|
|
38390
38539
|
const l = line.trim();
|
|
38391
|
-
if (l.startsWith("
|
|
38540
|
+
if (l.startsWith("DISCOVERED_SITE=")) {
|
|
38541
|
+
try {
|
|
38542
|
+
const site = JSON.parse(Buffer.from(l.slice("DISCOVERED_SITE=".length), "base64").toString("utf8"));
|
|
38543
|
+
if (site?.domain && site?.route)
|
|
38544
|
+
kv.discoveredSites.push(site);
|
|
38545
|
+
} catch {}
|
|
38546
|
+
} else if (l.startsWith("SVC=")) {
|
|
38392
38547
|
const [, name, status, memBytes, enabled, since] = /^SVC=([^=]+)=([^=]+)(?:=([^=]*)(?:=([^=]*)(?:=(.*))?)?)?$/.exec(l) ?? [];
|
|
38393
38548
|
if (name) {
|
|
38394
38549
|
kv.services.push({
|
|
@@ -38574,6 +38729,30 @@ function configuredSites(config6) {
|
|
|
38574
38729
|
};
|
|
38575
38730
|
});
|
|
38576
38731
|
}
|
|
38732
|
+
function mergeDiscoveredSites(configured, discovered, ownerSlug) {
|
|
38733
|
+
const routes = new Map(configured.map((site) => [String(site.route), { ...site, project: ownerSlug, managedBy: "owner" }]));
|
|
38734
|
+
for (const site of discovered) {
|
|
38735
|
+
const current = routes.get(site.route);
|
|
38736
|
+
const name = current?.name ?? `${site.project}:${site.name}`;
|
|
38737
|
+
routes.set(site.route, {
|
|
38738
|
+
...current,
|
|
38739
|
+
...site,
|
|
38740
|
+
name,
|
|
38741
|
+
kind: current?.kind ?? site.kind,
|
|
38742
|
+
type: current?.type ?? site.kind,
|
|
38743
|
+
runtime: current?.runtime ?? site.runtime,
|
|
38744
|
+
deploy: current?.deploy ?? site.deploy,
|
|
38745
|
+
root: current?.root ?? site.root,
|
|
38746
|
+
branch: current?.branch ?? site.branch,
|
|
38747
|
+
managedBy: site.project === ownerSlug ? "owner" : "attached"
|
|
38748
|
+
});
|
|
38749
|
+
}
|
|
38750
|
+
return [...routes.values()].sort((a, b) => {
|
|
38751
|
+
if (a.managedBy !== b.managedBy)
|
|
38752
|
+
return a.managedBy === "owner" ? -1 : 1;
|
|
38753
|
+
return String(a.route).localeCompare(String(b.route));
|
|
38754
|
+
});
|
|
38755
|
+
}
|
|
38577
38756
|
function shellSingleQuote(value2) {
|
|
38578
38757
|
return `'${value2.replaceAll("'", "'\\''")}'`;
|
|
38579
38758
|
}
|
|
@@ -38944,12 +39123,24 @@ function resolveConfigOnlyServerDashboardData(config6, environment2) {
|
|
|
38944
39123
|
},
|
|
38945
39124
|
systemMetrics: {
|
|
38946
39125
|
load: 0,
|
|
39126
|
+
load5: 0,
|
|
39127
|
+
load15: 0,
|
|
39128
|
+
cpuUsedPct: 0,
|
|
38947
39129
|
cpus: Math.max(1, Number(config6.infrastructure?.compute?.instances ?? 1)),
|
|
38948
39130
|
memUsedMb: 0,
|
|
38949
39131
|
memTotalMb: 0,
|
|
39132
|
+
memAvailableMb: 0,
|
|
39133
|
+
memCacheMb: 0,
|
|
39134
|
+
swapUsedMb: 0,
|
|
39135
|
+
swapTotalMb: 0,
|
|
38950
39136
|
diskUsedPct: 0,
|
|
38951
39137
|
diskUsedGb: 0,
|
|
38952
|
-
diskTotalGb: diskSize
|
|
39138
|
+
diskTotalGb: diskSize,
|
|
39139
|
+
inodeUsedPct: 0,
|
|
39140
|
+
networkRxBytes: 0,
|
|
39141
|
+
networkTxBytes: 0,
|
|
39142
|
+
processes: 0,
|
|
39143
|
+
uptimeSeconds: 0
|
|
38953
39144
|
},
|
|
38954
39145
|
metricsUnavailable: true,
|
|
38955
39146
|
services,
|
|
@@ -38980,6 +39171,7 @@ function resolveConfigOnlyServerDashboardData(config6, environment2) {
|
|
|
38980
39171
|
envKeys: Object.keys(site.env ?? site.environment ?? {})
|
|
38981
39172
|
};
|
|
38982
39173
|
}),
|
|
39174
|
+
siteHealth: [],
|
|
38983
39175
|
sshKeys: describeSshKeys(config6.infrastructure?.compute?.sshKeys ?? []),
|
|
38984
39176
|
_serverReachable: false,
|
|
38985
39177
|
_metricsStatus: "unavailable"
|
|
@@ -38988,7 +39180,7 @@ function resolveConfigOnlyServerDashboardData(config6, environment2) {
|
|
|
38988
39180
|
out.activity = activityFeed(out);
|
|
38989
39181
|
return out;
|
|
38990
39182
|
}
|
|
38991
|
-
async function resolveServerDashboardData(config6, environment2) {
|
|
39183
|
+
async function resolveServerDashboardData(config6, environment2, options = {}) {
|
|
38992
39184
|
if (!config6.infrastructure?.compute)
|
|
38993
39185
|
return null;
|
|
38994
39186
|
let driver = null;
|
|
@@ -39007,7 +39199,10 @@ async function resolveServerDashboardData(config6, environment2) {
|
|
|
39007
39199
|
if (targets.length) {
|
|
39008
39200
|
const result2 = await driver.runRemoteDeploy({
|
|
39009
39201
|
targets: [targets[0]],
|
|
39010
|
-
commands: metricsScript(
|
|
39202
|
+
commands: metricsScript({
|
|
39203
|
+
includeServices: !options.telemetryOnly,
|
|
39204
|
+
projectFilter: config6.cloud?.attachTo ? config6.project.slug : undefined
|
|
39205
|
+
}),
|
|
39011
39206
|
comment: `ts-cloud dashboard:build ${config6.project.slug}`,
|
|
39012
39207
|
tags: { Project: config6.project.slug, Environment: environment2, Role: "app" }
|
|
39013
39208
|
});
|
|
@@ -39029,19 +39224,32 @@ async function resolveServerDashboardData(config6, environment2) {
|
|
|
39029
39224
|
out._metricsStatus = "live";
|
|
39030
39225
|
out.systemMetrics = {
|
|
39031
39226
|
load: num(parsed.LOAD),
|
|
39227
|
+
load5: num(parsed.LOAD5),
|
|
39228
|
+
load15: num(parsed.LOAD15),
|
|
39229
|
+
cpuUsedPct: num(parsed.CPUUSED),
|
|
39032
39230
|
cpus: num(parsed.CPUS, 1),
|
|
39033
39231
|
memUsedMb: num(parsed.MEMUSED),
|
|
39034
39232
|
memTotalMb: num(parsed.MEMTOTAL, 1),
|
|
39233
|
+
memAvailableMb: num(parsed.MEMAVAILABLE),
|
|
39234
|
+
memCacheMb: num(parsed.MEMCACHE),
|
|
39235
|
+
swapUsedMb: num(parsed.SWAPUSED),
|
|
39236
|
+
swapTotalMb: num(parsed.SWAPTOTAL),
|
|
39035
39237
|
diskUsedPct: num(parsed.DISKPCT),
|
|
39036
39238
|
diskUsedGb: num(parsed.DISKUSEDG),
|
|
39037
|
-
diskTotalGb: num(parsed.DISKTOTG, 1)
|
|
39239
|
+
diskTotalGb: num(parsed.DISKTOTG, 1),
|
|
39240
|
+
inodeUsedPct: num(parsed.INODEPCT),
|
|
39241
|
+
networkRxBytes: num(parsed.NETRX),
|
|
39242
|
+
networkTxBytes: num(parsed.NETTX),
|
|
39243
|
+
processes: num(parsed.PROCESSES),
|
|
39244
|
+
uptimeSeconds: num(parsed.UPTIME_SECONDS)
|
|
39038
39245
|
};
|
|
39039
39246
|
if (parsed.services.length) {
|
|
39040
|
-
|
|
39247
|
+
const services = [...new Map(parsed.services.map((service16) => [service16.name, service16])).values()];
|
|
39248
|
+
out.services = services.map((s) => ({
|
|
39041
39249
|
name: s.name,
|
|
39042
39250
|
status: s.status === "active" ? "running" : s.status
|
|
39043
39251
|
}));
|
|
39044
|
-
out.servicesDetail =
|
|
39252
|
+
out.servicesDetail = services.map((s) => ({
|
|
39045
39253
|
name: s.name,
|
|
39046
39254
|
status: s.status === "active" ? "running" : s.status,
|
|
39047
39255
|
since: s.since && s.since !== UNKNOWN ? s.since : out.server.uptime,
|
|
@@ -39049,64 +39257,100 @@ async function resolveServerDashboardData(config6, environment2) {
|
|
|
39049
39257
|
auto: s.enabled ? !["disabled", "static", "masked"].includes(s.enabled) : true
|
|
39050
39258
|
}));
|
|
39051
39259
|
}
|
|
39052
|
-
|
|
39053
|
-
|
|
39054
|
-
|
|
39055
|
-
|
|
39056
|
-
|
|
39057
|
-
|
|
39058
|
-
|
|
39059
|
-
|
|
39060
|
-
|
|
39061
|
-
|
|
39062
|
-
|
|
39063
|
-
|
|
39064
|
-
|
|
39065
|
-
|
|
39066
|
-
|
|
39067
|
-
|
|
39068
|
-
|
|
39260
|
+
if (parsed.discoveredSites.length) {
|
|
39261
|
+
out.sitesDetail = mergeDiscoveredSites(out.sitesDetail, parsed.discoveredSites, config6.project.slug);
|
|
39262
|
+
out.sites = out.sitesDetail.map((site) => ({
|
|
39263
|
+
name: site.name,
|
|
39264
|
+
project: site.project,
|
|
39265
|
+
managedBy: site.managedBy,
|
|
39266
|
+
route: site.route,
|
|
39267
|
+
href: site.href,
|
|
39268
|
+
domain: site.domain,
|
|
39269
|
+
path: site.path,
|
|
39270
|
+
kind: site.kind,
|
|
39271
|
+
type: site.type,
|
|
39272
|
+
runtime: site.runtime,
|
|
39273
|
+
deploy: site.deploy,
|
|
39274
|
+
tls: site.tls,
|
|
39275
|
+
status: site.status,
|
|
39276
|
+
httpStatus: site.httpStatus,
|
|
39277
|
+
responseMs: site.responseMs,
|
|
39278
|
+
tlsDaysRemaining: site.tlsDaysRemaining,
|
|
39279
|
+
checkedAt: site.checkedAt
|
|
39280
|
+
}));
|
|
39281
|
+
out.siteHealth = out.sitesDetail.map((site) => ({
|
|
39282
|
+
name: site.name,
|
|
39283
|
+
project: site.project,
|
|
39284
|
+
route: site.route,
|
|
39285
|
+
status: site.status,
|
|
39286
|
+
httpStatus: site.httpStatus,
|
|
39287
|
+
responseMs: site.responseMs,
|
|
39288
|
+
tlsDaysRemaining: site.tlsDaysRemaining,
|
|
39289
|
+
checkedAt: site.checkedAt
|
|
39290
|
+
}));
|
|
39069
39291
|
}
|
|
39292
|
+
}
|
|
39293
|
+
if (driver && targets.length && !options.telemetryOnly) {
|
|
39294
|
+
await Promise.all([
|
|
39295
|
+
(async () => {
|
|
39296
|
+
try {
|
|
39297
|
+
const siteNames = Object.keys(config6.sites ?? {});
|
|
39298
|
+
const historyResult = await driver.runRemoteDeploy({
|
|
39299
|
+
targets: [targets[0]],
|
|
39300
|
+
commands: deployHistoryScript(siteNames, config6.project.slug),
|
|
39301
|
+
comment: `ts-cloud dashboard:deploy-history ${config6.project.slug}`,
|
|
39302
|
+
tags: { Project: config6.project.slug, Environment: environment2, Role: "app" }
|
|
39303
|
+
});
|
|
39304
|
+
const historyOutput = historyResult.perInstance?.[0]?.output ?? "";
|
|
39305
|
+
const records = parseDeployHistory(historyOutput, config6.sites);
|
|
39306
|
+
out.serverDeployments = records.slice(0, 5);
|
|
39307
|
+
out.serverDeploymentsDetail = records.slice(0, 50);
|
|
39308
|
+
out.deploymentsEmptyReason = records.length ? undefined : "No deployment history was found on the server yet. Deploy again to populate this timeline.";
|
|
39309
|
+
} catch {
|
|
39310
|
+
out.deploymentsEmptyReason = "Deployment history could not be read from the server.";
|
|
39311
|
+
}
|
|
39312
|
+
})(),
|
|
39313
|
+
(async () => {
|
|
39314
|
+
try {
|
|
39315
|
+
const logsResult = await driver.runRemoteDeploy({
|
|
39316
|
+
targets: [targets[0]],
|
|
39317
|
+
commands: serverLogsScript(config6),
|
|
39318
|
+
comment: `ts-cloud dashboard:server-logs ${config6.project.slug}`,
|
|
39319
|
+
tags: { Project: config6.project.slug, Environment: environment2, Role: "app" }
|
|
39320
|
+
});
|
|
39321
|
+
const logsOutput = logsResult.perInstance?.[0]?.output ?? "";
|
|
39322
|
+
const records = parseServerLogs(logsOutput);
|
|
39323
|
+
out.serverLogs = records;
|
|
39324
|
+
out.serverLogsEmptyReason = records.length ? undefined : "No recent journal entries were found for the managed server units.";
|
|
39325
|
+
} catch {
|
|
39326
|
+
out.serverLogsEmptyReason = "Server logs could not be read from the box.";
|
|
39327
|
+
}
|
|
39328
|
+
})(),
|
|
39329
|
+
(async () => {
|
|
39330
|
+
try {
|
|
39331
|
+
const securityResult = await driver.runRemoteDeploy({
|
|
39332
|
+
targets: [targets[0]],
|
|
39333
|
+
commands: securityScript(config6),
|
|
39334
|
+
comment: `ts-cloud dashboard:security ${config6.project.slug}`,
|
|
39335
|
+
tags: { Project: config6.project.slug, Environment: environment2, Role: "app" }
|
|
39336
|
+
});
|
|
39337
|
+
const securityOutput = securityResult.perInstance?.[0]?.output ?? "";
|
|
39338
|
+
const liveSecurity = parseServerSecurity(securityOutput);
|
|
39339
|
+
const declaredSecurity = configuredSecurity(config6);
|
|
39340
|
+
if (!liveSecurity.ports.length)
|
|
39341
|
+
liveSecurity.ports = declaredSecurity.ports;
|
|
39342
|
+
if (!liveSecurity.tlsCertificates.length)
|
|
39343
|
+
liveSecurity.tlsCertificates = declaredSecurity.tlsCertificates;
|
|
39344
|
+
out.security = liveSecurity;
|
|
39345
|
+
out.securityEmptyReason = undefined;
|
|
39346
|
+
} catch {
|
|
39347
|
+
out.securityEmptyReason = "Server security checks could not be read from the box.";
|
|
39348
|
+
}
|
|
39349
|
+
})()
|
|
39350
|
+
]);
|
|
39070
39351
|
} else if (driver && instanceCount === 0) {
|
|
39071
39352
|
out.deploymentsEmptyReason = "No app server target was found for this environment.";
|
|
39072
39353
|
}
|
|
39073
|
-
if (driver && targets.length) {
|
|
39074
|
-
try {
|
|
39075
|
-
const logsResult = await driver.runRemoteDeploy({
|
|
39076
|
-
targets: [targets[0]],
|
|
39077
|
-
commands: serverLogsScript(config6),
|
|
39078
|
-
comment: `ts-cloud dashboard:server-logs ${config6.project.slug}`,
|
|
39079
|
-
tags: { Project: config6.project.slug, Environment: environment2, Role: "app" }
|
|
39080
|
-
});
|
|
39081
|
-
const logsOutput = logsResult.perInstance?.[0]?.output ?? "";
|
|
39082
|
-
const records = parseServerLogs(logsOutput);
|
|
39083
|
-
out.serverLogs = records;
|
|
39084
|
-
out.serverLogsEmptyReason = records.length ? undefined : "No recent journal entries were found for the managed server units.";
|
|
39085
|
-
} catch {
|
|
39086
|
-
out.serverLogsEmptyReason = "Server logs could not be read from the box.";
|
|
39087
|
-
}
|
|
39088
|
-
}
|
|
39089
|
-
if (driver && targets.length) {
|
|
39090
|
-
try {
|
|
39091
|
-
const securityResult = await driver.runRemoteDeploy({
|
|
39092
|
-
targets: [targets[0]],
|
|
39093
|
-
commands: securityScript(config6),
|
|
39094
|
-
comment: `ts-cloud dashboard:security ${config6.project.slug}`,
|
|
39095
|
-
tags: { Project: config6.project.slug, Environment: environment2, Role: "app" }
|
|
39096
|
-
});
|
|
39097
|
-
const securityOutput = securityResult.perInstance?.[0]?.output ?? "";
|
|
39098
|
-
const liveSecurity = parseServerSecurity(securityOutput);
|
|
39099
|
-
const declaredSecurity = configuredSecurity(config6);
|
|
39100
|
-
if (!liveSecurity.ports.length)
|
|
39101
|
-
liveSecurity.ports = declaredSecurity.ports;
|
|
39102
|
-
if (!liveSecurity.tlsCertificates.length)
|
|
39103
|
-
liveSecurity.tlsCertificates = declaredSecurity.tlsCertificates;
|
|
39104
|
-
out.security = liveSecurity;
|
|
39105
|
-
out.securityEmptyReason = undefined;
|
|
39106
|
-
} catch {
|
|
39107
|
-
out.securityEmptyReason = "Server security checks could not be read from the box.";
|
|
39108
|
-
}
|
|
39109
|
-
}
|
|
39110
39354
|
const sshKeys = config6.infrastructure?.compute?.sshKeys ?? config6.infrastructure?.ssh?.keys ?? config6.infrastructure?.sshKeys;
|
|
39111
39355
|
if (Array.isArray(sshKeys) && sshKeys.length) {
|
|
39112
39356
|
out.sshKeys = describeSshKeys(sshKeys);
|
|
@@ -41982,7 +42226,9 @@ async function collectNow(context) {
|
|
|
41982
42226
|
const scope2 = { projectId: context.projectId, environmentId: context.environmentId };
|
|
41983
42227
|
const mode = resolveDeploymentMode(context.config);
|
|
41984
42228
|
try {
|
|
41985
|
-
const data = (mode === "serverless" ? await resolveDashboardData(context.config, context.environment) : await resolveServerDashboardData(context.config, context.environment
|
|
42229
|
+
const data = (mode === "serverless" ? await resolveDashboardData(context.config, context.environment) : await resolveServerDashboardData(context.config, context.environment, {
|
|
42230
|
+
telemetryOnly: context.lightweight
|
|
42231
|
+
})) ?? {};
|
|
41986
42232
|
if (mode === "serverless") {
|
|
41987
42233
|
for (const fn of data.functionsDetail ?? []) {
|
|
41988
42234
|
const resourceId = resourceBySlug(context.controlPlane, context.projectId, context.environmentId, fn.key);
|
|
@@ -42151,14 +42397,30 @@ async function collectNow(context) {
|
|
|
42151
42397
|
if (data.metricsUnavailable || !metrics)
|
|
42152
42398
|
errors.push({ source: "host", message: "The host metrics probe is unavailable." });
|
|
42153
42399
|
else {
|
|
42400
|
+
const memoryUsedPercent = Number(metrics.memTotalMb) > 0 ? Number(metrics.memUsedMb) / Number(metrics.memTotalMb) * 100 : 0;
|
|
42401
|
+
const swapUsedPercent = Number(metrics.swapTotalMb) > 0 ? Number(metrics.swapUsedMb) / Number(metrics.swapTotalMb) * 100 : 0;
|
|
42154
42402
|
const values = [
|
|
42155
42403
|
["host.load", Number(metrics.load), "load"],
|
|
42404
|
+
["host.load.5m", Number(metrics.load5), "load"],
|
|
42405
|
+
["host.load.15m", Number(metrics.load15), "load"],
|
|
42156
42406
|
["host.cpu.capacity", Number(metrics.cpus), "count"],
|
|
42407
|
+
["host.cpu.used_percent", Number(metrics.cpuUsedPct), "percent"],
|
|
42157
42408
|
["host.memory.used", Number(metrics.memUsedMb) * 1024 * 1024, "bytes"],
|
|
42158
42409
|
["host.memory.total", Number(metrics.memTotalMb) * 1024 * 1024, "bytes"],
|
|
42410
|
+
["host.memory.available", Number(metrics.memAvailableMb) * 1024 * 1024, "bytes"],
|
|
42411
|
+
["host.memory.cache", Number(metrics.memCacheMb) * 1024 * 1024, "bytes"],
|
|
42412
|
+
["host.memory.used_percent", memoryUsedPercent, "percent"],
|
|
42413
|
+
["host.swap.used", Number(metrics.swapUsedMb) * 1024 * 1024, "bytes"],
|
|
42414
|
+
["host.swap.total", Number(metrics.swapTotalMb) * 1024 * 1024, "bytes"],
|
|
42415
|
+
["host.swap.used_percent", swapUsedPercent, "percent"],
|
|
42159
42416
|
["host.disk.used_percent", Number(metrics.diskUsedPct), "percent"],
|
|
42160
42417
|
["host.disk.used", Number(metrics.diskUsedGb) * 1024 ** 3, "bytes"],
|
|
42161
|
-
["host.disk.total", Number(metrics.diskTotalGb) * 1024 ** 3, "bytes"]
|
|
42418
|
+
["host.disk.total", Number(metrics.diskTotalGb) * 1024 ** 3, "bytes"],
|
|
42419
|
+
["host.disk.inode_used_percent", Number(metrics.inodeUsedPct), "percent"],
|
|
42420
|
+
["host.network.rx", Number(metrics.networkRxBytes), "bytes"],
|
|
42421
|
+
["host.network.tx", Number(metrics.networkTxBytes), "bytes"],
|
|
42422
|
+
["host.processes", Number(metrics.processes), "count"],
|
|
42423
|
+
["host.uptime", Number(metrics.uptimeSeconds), "seconds"]
|
|
42162
42424
|
];
|
|
42163
42425
|
for (const [name, value2, unit] of values)
|
|
42164
42426
|
if (Number.isFinite(value2))
|
|
@@ -42172,6 +42434,43 @@ async function collectNow(context) {
|
|
|
42172
42434
|
value: value2,
|
|
42173
42435
|
unit
|
|
42174
42436
|
});
|
|
42437
|
+
for (const site of data.siteHealth ?? []) {
|
|
42438
|
+
const project = String(site.project ?? context.config.project.slug);
|
|
42439
|
+
const displayName = String(site.name);
|
|
42440
|
+
const siteName = displayName.startsWith(`${project}:`) ? displayName.slice(project.length + 1) : displayName;
|
|
42441
|
+
const source = `site:${project}:${siteName}`;
|
|
42442
|
+
const resourceId = resourceBySlug(context.controlPlane, context.projectId, context.environmentId, displayName);
|
|
42443
|
+
const base = {
|
|
42444
|
+
...scope2,
|
|
42445
|
+
resourceId,
|
|
42446
|
+
source,
|
|
42447
|
+
timestamp: site.checkedAt || now.toISOString(),
|
|
42448
|
+
host: site.route,
|
|
42449
|
+
attributes: {
|
|
42450
|
+
project,
|
|
42451
|
+
site: siteName,
|
|
42452
|
+
route: String(site.route),
|
|
42453
|
+
status: String(site.status)
|
|
42454
|
+
}
|
|
42455
|
+
};
|
|
42456
|
+
const siteValues = [
|
|
42457
|
+
["site.health.up", site.status === "live" ? 1 : 0, "boolean"],
|
|
42458
|
+
["site.http.status", Number(site.httpStatus), "status"],
|
|
42459
|
+
["site.response.time", Number(site.responseMs), "ms"]
|
|
42460
|
+
];
|
|
42461
|
+
if (site.tlsDaysRemaining != null)
|
|
42462
|
+
siteValues.push(["site.tls.days_remaining", Number(site.tlsDaysRemaining), "days"]);
|
|
42463
|
+
for (const [name, value2, unit] of siteValues)
|
|
42464
|
+
if (Number.isFinite(value2))
|
|
42465
|
+
records.push({
|
|
42466
|
+
...base,
|
|
42467
|
+
id: id(source, name, base.timestamp),
|
|
42468
|
+
kind: "metric",
|
|
42469
|
+
name,
|
|
42470
|
+
value: value2,
|
|
42471
|
+
unit
|
|
42472
|
+
});
|
|
42473
|
+
}
|
|
42175
42474
|
}
|
|
42176
42475
|
}
|
|
42177
42476
|
} catch (error2) {
|
|
@@ -42180,73 +42479,74 @@ async function collectNow(context) {
|
|
|
42180
42479
|
message: error2 instanceof Error ? error2.message : String(error2)
|
|
42181
42480
|
});
|
|
42182
42481
|
}
|
|
42183
|
-
|
|
42184
|
-
|
|
42185
|
-
|
|
42186
|
-
|
|
42187
|
-
|
|
42188
|
-
for (const workload of inventory2.workloads) {
|
|
42189
|
-
const resourceId = resourceBySlug(context.controlPlane, context.projectId, context.environmentId, workload.links.service);
|
|
42190
|
-
const base = {
|
|
42191
|
-
...scope2,
|
|
42192
|
-
resourceId,
|
|
42193
|
-
source: `runtime:${workload.provider}`,
|
|
42194
|
-
timestamp: now.toISOString(),
|
|
42195
|
-
workloadId: workload.id,
|
|
42196
|
-
releaseId: workload.links.release,
|
|
42197
|
-
attributes: { status: workload.status, provider: workload.provider, service: workload.links.service ?? "" }
|
|
42198
|
-
};
|
|
42199
|
-
records.push({
|
|
42200
|
-
...base,
|
|
42201
|
-
id: id(workload.id, "health", now.toISOString()),
|
|
42202
|
-
kind: "metric",
|
|
42203
|
-
name: "health.up",
|
|
42204
|
-
value: workload.status === "running" ? 1 : 0,
|
|
42205
|
-
unit: "boolean"
|
|
42206
|
-
});
|
|
42207
|
-
records.push({
|
|
42208
|
-
...base,
|
|
42209
|
-
id: id(workload.id, "restarts", now.toISOString()),
|
|
42210
|
-
kind: "metric",
|
|
42211
|
-
name: "saturation.restarts",
|
|
42212
|
-
value: workload.restartCount ?? 0,
|
|
42213
|
-
unit: "count"
|
|
42482
|
+
if (!context.lightweight)
|
|
42483
|
+
try {
|
|
42484
|
+
const inventory2 = await resolveRuntimeInventory(context.config, context.environment);
|
|
42485
|
+
const runtime3 = new RuntimeOperationService(context.config, context.environment, {
|
|
42486
|
+
inventory: async () => inventory2
|
|
42214
42487
|
});
|
|
42215
|
-
|
|
42488
|
+
for (const workload of inventory2.workloads) {
|
|
42489
|
+
const resourceId = resourceBySlug(context.controlPlane, context.projectId, context.environmentId, workload.links.service);
|
|
42490
|
+
const base = {
|
|
42491
|
+
...scope2,
|
|
42492
|
+
resourceId,
|
|
42493
|
+
source: `runtime:${workload.provider}`,
|
|
42494
|
+
timestamp: now.toISOString(),
|
|
42495
|
+
workloadId: workload.id,
|
|
42496
|
+
releaseId: workload.links.release,
|
|
42497
|
+
attributes: { status: workload.status, provider: workload.provider, service: workload.links.service ?? "" }
|
|
42498
|
+
};
|
|
42216
42499
|
records.push({
|
|
42217
42500
|
...base,
|
|
42218
|
-
id: id(workload.id, "
|
|
42501
|
+
id: id(workload.id, "health", now.toISOString()),
|
|
42219
42502
|
kind: "metric",
|
|
42220
|
-
name: "
|
|
42221
|
-
value: workload.
|
|
42222
|
-
unit: "
|
|
42503
|
+
name: "health.up",
|
|
42504
|
+
value: workload.status === "running" ? 1 : 0,
|
|
42505
|
+
unit: "boolean"
|
|
42223
42506
|
});
|
|
42224
|
-
|
|
42225
|
-
|
|
42226
|
-
|
|
42227
|
-
|
|
42228
|
-
|
|
42229
|
-
|
|
42230
|
-
|
|
42231
|
-
resourceId,
|
|
42232
|
-
source: `runtime:${workload.provider}`,
|
|
42233
|
-
name: `${workload.links.service ?? workload.name}.log`,
|
|
42234
|
-
timestamp: line.timestamp,
|
|
42235
|
-
message: line.message,
|
|
42236
|
-
workloadId: workload.id
|
|
42237
|
-
}));
|
|
42238
|
-
} catch (error2) {
|
|
42239
|
-
errors.push({
|
|
42240
|
-
source: `runtime:${workload.provider}`,
|
|
42241
|
-
message: error2 instanceof Error ? error2.message : String(error2)
|
|
42507
|
+
records.push({
|
|
42508
|
+
...base,
|
|
42509
|
+
id: id(workload.id, "restarts", now.toISOString()),
|
|
42510
|
+
kind: "metric",
|
|
42511
|
+
name: "saturation.restarts",
|
|
42512
|
+
value: workload.restartCount ?? 0,
|
|
42513
|
+
unit: "count"
|
|
42242
42514
|
});
|
|
42515
|
+
if (workload.resources?.memoryBytes != null)
|
|
42516
|
+
records.push({
|
|
42517
|
+
...base,
|
|
42518
|
+
id: id(workload.id, "memory", now.toISOString()),
|
|
42519
|
+
kind: "metric",
|
|
42520
|
+
name: "runtime.memory.used",
|
|
42521
|
+
value: workload.resources.memoryBytes,
|
|
42522
|
+
unit: "bytes"
|
|
42523
|
+
});
|
|
42524
|
+
if (!workload.capabilities.logs.supported)
|
|
42525
|
+
continue;
|
|
42526
|
+
try {
|
|
42527
|
+
const result2 = await runtime3.logs(workload.id, { limit: 100, since: new Date(now.getTime() - 5 * 60000) });
|
|
42528
|
+
for (const line of result2.lines)
|
|
42529
|
+
records.push(...telemetryRecordsFromLog({
|
|
42530
|
+
...scope2,
|
|
42531
|
+
resourceId,
|
|
42532
|
+
source: `runtime:${workload.provider}`,
|
|
42533
|
+
name: `${workload.links.service ?? workload.name}.log`,
|
|
42534
|
+
timestamp: line.timestamp,
|
|
42535
|
+
message: line.message,
|
|
42536
|
+
workloadId: workload.id
|
|
42537
|
+
}));
|
|
42538
|
+
} catch (error2) {
|
|
42539
|
+
errors.push({
|
|
42540
|
+
source: `runtime:${workload.provider}`,
|
|
42541
|
+
message: error2 instanceof Error ? error2.message : String(error2)
|
|
42542
|
+
});
|
|
42543
|
+
}
|
|
42243
42544
|
}
|
|
42545
|
+
for (const source of inventory2.sources.filter((source2) => source2.status !== "fresh"))
|
|
42546
|
+
errors.push({ source: source.id, message: source.message ?? `Runtime source is ${source.status}.` });
|
|
42547
|
+
} catch (error2) {
|
|
42548
|
+
errors.push({ source: "runtime", message: error2 instanceof Error ? error2.message : String(error2) });
|
|
42244
42549
|
}
|
|
42245
|
-
for (const source of inventory2.sources.filter((source2) => source2.status !== "fresh"))
|
|
42246
|
-
errors.push({ source: source.id, message: source.message ?? `Runtime source is ${source.status}.` });
|
|
42247
|
-
} catch (error2) {
|
|
42248
|
-
errors.push({ source: "runtime", message: error2 instanceof Error ? error2.message : String(error2) });
|
|
42249
|
-
}
|
|
42250
42550
|
for (const event of context.controlPlane.listEvents({ projectId: context.projectId, limit: 1000 })) {
|
|
42251
42551
|
const payload2 = event.payload;
|
|
42252
42552
|
records.push({
|
|
@@ -42293,8 +42593,9 @@ async function collectNow(context) {
|
|
|
42293
42593
|
}
|
|
42294
42594
|
async function collectDashboardTelemetry(context) {
|
|
42295
42595
|
const key2 = `${context.projectId}:${context.environmentId ?? "all"}:${context.environment}`;
|
|
42596
|
+
const tieredKey = `${key2}:${context.lightweight ? "lightweight" : "full"}`;
|
|
42296
42597
|
const ttl = resolveDeploymentMode(context.config) === "serverless" ? 5 * 60000 : 60000;
|
|
42297
|
-
const result2 = await collectionCache.getOrCreate(
|
|
42598
|
+
const result2 = await collectionCache.getOrCreate(tieredKey, ttl, !!context.force, () => collectNow(context));
|
|
42298
42599
|
return { ...result2.value, cached: result2.cached };
|
|
42299
42600
|
}
|
|
42300
42601
|
function invalidateDashboardTelemetryCache(projectId) {
|
|
@@ -42634,7 +42935,23 @@ function isTrustedMutationRequest(req) {
|
|
|
42634
42935
|
if (site === "cross-site")
|
|
42635
42936
|
return false;
|
|
42636
42937
|
const origin = req.headers.get("origin");
|
|
42637
|
-
|
|
42938
|
+
if (!origin)
|
|
42939
|
+
return true;
|
|
42940
|
+
const requestUrl = new URL(req.url);
|
|
42941
|
+
if (origin === requestUrl.origin)
|
|
42942
|
+
return true;
|
|
42943
|
+
if (site !== "same-origin")
|
|
42944
|
+
return false;
|
|
42945
|
+
try {
|
|
42946
|
+
const originUrl = new URL(origin);
|
|
42947
|
+
const forwardedHost = (req.headers.get("x-forwarded-host") ?? req.headers.get("host") ?? requestUrl.host).split(",")[0].trim();
|
|
42948
|
+
const forwardedProto = req.headers.get("x-forwarded-proto")?.split(",")[0]?.trim();
|
|
42949
|
+
if (!/^[A-Za-z0-9.-]+(?::\d{1,5})?$/.test(forwardedHost) || originUrl.host !== forwardedHost)
|
|
42950
|
+
return false;
|
|
42951
|
+
return !forwardedProto || originUrl.protocol === `${forwardedProto}:`;
|
|
42952
|
+
} catch {
|
|
42953
|
+
return false;
|
|
42954
|
+
}
|
|
42638
42955
|
}
|
|
42639
42956
|
var ORGANIZATION_ROLES = new Set(["owner", "admin", "deployer", "operator", "viewer", "auditor"]);
|
|
42640
42957
|
var AUTHORIZATION_SCOPES = new Set(["organization", "project", "environment", "resource"]);
|
|
@@ -43109,7 +43426,52 @@ async function startLocalDashboardServer(options = {}) {
|
|
|
43109
43426
|
const configPath = resolveCloudConfigPath(cwd);
|
|
43110
43427
|
const initialData = await resolveLiveDashboardData(config6, defaultEnvironment);
|
|
43111
43428
|
const latestDataByEnvironment = new Map([[defaultEnvironment, initialData]]);
|
|
43429
|
+
const liveDataRefreshesByEnvironment = new Map;
|
|
43430
|
+
const refreshLatestDashboardData = (environment2) => {
|
|
43431
|
+
const running = liveDataRefreshesByEnvironment.get(environment2);
|
|
43432
|
+
if (running)
|
|
43433
|
+
return running;
|
|
43434
|
+
const refresh = resolveLiveDashboardData(config6, environment2).then((data) => {
|
|
43435
|
+
const previous = latestDataByEnvironment.get(environment2);
|
|
43436
|
+
latestDataByEnvironment.set(environment2, data);
|
|
43437
|
+
if (previous?._serverReachable !== data._serverReachable || previous?._metricsStatus !== data._metricsStatus || (previous?.sitesDetail?.length ?? 0) !== (data.sitesDetail?.length ?? 0))
|
|
43438
|
+
clearUiCache();
|
|
43439
|
+
return data;
|
|
43440
|
+
}).finally(() => liveDataRefreshesByEnvironment.delete(environment2));
|
|
43441
|
+
liveDataRefreshesByEnvironment.set(environment2, refresh);
|
|
43442
|
+
return refresh;
|
|
43443
|
+
};
|
|
43112
43444
|
const packagedUi = resolveUiSource(cwd);
|
|
43445
|
+
const telemetryPreference = process.env.TS_CLOUD_DASHBOARD_TELEMETRY?.trim();
|
|
43446
|
+
const backgroundTelemetryEnabled = options.box && telemetryPreference !== "0" && (telemetryPreference === "1" || !config6.cloud?.attachTo);
|
|
43447
|
+
if (backgroundTelemetryEnabled) {
|
|
43448
|
+
let telemetryCollectionRunning = false;
|
|
43449
|
+
const collectHostHistory = async () => {
|
|
43450
|
+
if (telemetryCollectionRunning)
|
|
43451
|
+
return;
|
|
43452
|
+
telemetryCollectionRunning = true;
|
|
43453
|
+
try {
|
|
43454
|
+
const environmentRecord = controlPlane.environments.get(defaultEnvironment);
|
|
43455
|
+
await collectDashboardTelemetry({
|
|
43456
|
+
controlPlane: controlPlane.store,
|
|
43457
|
+
projectId: controlPlane.project.id,
|
|
43458
|
+
environmentId: environmentRecord?.id,
|
|
43459
|
+
config: config6,
|
|
43460
|
+
environment: defaultEnvironment,
|
|
43461
|
+
force: true,
|
|
43462
|
+
lightweight: true
|
|
43463
|
+
});
|
|
43464
|
+
} catch (error2) {
|
|
43465
|
+
if (options.verbose)
|
|
43466
|
+
console.warn(` ts-cloud dashboard: background telemetry collection failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
43467
|
+
} finally {
|
|
43468
|
+
telemetryCollectionRunning = false;
|
|
43469
|
+
}
|
|
43470
|
+
};
|
|
43471
|
+
collectHostHistory();
|
|
43472
|
+
const telemetryTimer = setInterval(() => void collectHostHistory(), 60000);
|
|
43473
|
+
telemetryTimer.unref?.();
|
|
43474
|
+
}
|
|
43113
43475
|
const uiCache = new Map;
|
|
43114
43476
|
const uiBuilds = new Map;
|
|
43115
43477
|
const ownedUiRoots = new Set;
|
|
@@ -43683,6 +44045,7 @@ async function startLocalDashboardServer(options = {}) {
|
|
|
43683
44045
|
const server = Bun.serve({
|
|
43684
44046
|
hostname: host2,
|
|
43685
44047
|
port,
|
|
44048
|
+
idleTimeout: 30,
|
|
43686
44049
|
websocket: {
|
|
43687
44050
|
open(ws) {
|
|
43688
44051
|
if (!terminalEnabled) {
|
|
@@ -43727,8 +44090,7 @@ async function startLocalDashboardServer(options = {}) {
|
|
|
43727
44090
|
const environment2 = resolveDashboardEnvironment(availableEnvironments, defaultEnvironment, requestedEnvironment);
|
|
43728
44091
|
let latestData = latestDataByEnvironment.get(environment2);
|
|
43729
44092
|
if (!latestData) {
|
|
43730
|
-
latestData = await
|
|
43731
|
-
latestDataByEnvironment.set(environment2, latestData);
|
|
44093
|
+
latestData = await refreshLatestDashboardData(environment2);
|
|
43732
44094
|
}
|
|
43733
44095
|
try {
|
|
43734
44096
|
if (!isTrustedMutationRequest(req))
|
|
@@ -47021,10 +47383,22 @@ This replacement link expires ${created.invitation.expiresAt} and can be used on
|
|
|
47021
47383
|
environmentId: environmentRecord?.id,
|
|
47022
47384
|
config: config6,
|
|
47023
47385
|
environment: environment2,
|
|
47024
|
-
force
|
|
47386
|
+
force,
|
|
47387
|
+
lightweight: !force
|
|
47025
47388
|
});
|
|
47389
|
+
const collectionSnapshot = () => {
|
|
47390
|
+
const saved = controlPlane.store.getSetting(`telemetry.collection:${controlPlane.project.id}:${environmentRecord?.id ?? "all"}`) ?? {};
|
|
47391
|
+
return {
|
|
47392
|
+
generatedAt: String(saved.generatedAt ?? new Date().toISOString()),
|
|
47393
|
+
cached: true,
|
|
47394
|
+
statuses: Array.isArray(saved.statuses) ? saved.statuses : [],
|
|
47395
|
+
errors: Array.isArray(saved.errors) ? saved.errors : [],
|
|
47396
|
+
policy: saved.policy ?? loadTelemetryPolicy(controlPlane.store, controlPlane.project.id)
|
|
47397
|
+
};
|
|
47398
|
+
};
|
|
47026
47399
|
if (url.pathname === "/api/telemetry/status" && req.method === "GET") {
|
|
47027
|
-
|
|
47400
|
+
collect(false);
|
|
47401
|
+
const collection2 = collectionSnapshot();
|
|
47028
47402
|
const scopedStatuses = environmentWide ? collection2.statuses : telemetry2.status(controlPlane.project.id, environmentRecord?.id, collection2.policy.rawDays, collection2.policy.samplingRate, readableResources.map((resource2) => resource2.id));
|
|
47029
47403
|
const estimatedMonthlyBytes = scopedStatuses.reduce((sum, source) => sum + source.estimatedDailyBytes * 30, 0);
|
|
47030
47404
|
return json15({
|
|
@@ -47053,7 +47427,8 @@ This replacement link expires ${created.invitation.expiresAt} and can be used on
|
|
|
47053
47427
|
return json15({ ok: true, policy: policy3 });
|
|
47054
47428
|
}
|
|
47055
47429
|
if (url.pathname === "/api/telemetry/query" && req.method === "GET") {
|
|
47056
|
-
|
|
47430
|
+
collect(false);
|
|
47431
|
+
const collection2 = collectionSnapshot();
|
|
47057
47432
|
return json15({
|
|
47058
47433
|
ok: true,
|
|
47059
47434
|
collection: { generatedAt: collection2.generatedAt, cached: collection2.cached },
|
|
@@ -47061,7 +47436,7 @@ This replacement link expires ${created.invitation.expiresAt} and can be used on
|
|
|
47061
47436
|
});
|
|
47062
47437
|
}
|
|
47063
47438
|
if (url.pathname === "/api/telemetry/tail" && req.method === "GET") {
|
|
47064
|
-
|
|
47439
|
+
collect(false);
|
|
47065
47440
|
const encoder = new TextEncoder;
|
|
47066
47441
|
const initialTo = new Date;
|
|
47067
47442
|
const baseQuery = queryFrom({
|
|
@@ -47142,7 +47517,8 @@ data: ${JSON.stringify({ cursor: position, at: new Date().toISOString() })}
|
|
|
47142
47517
|
});
|
|
47143
47518
|
}
|
|
47144
47519
|
if (url.pathname === "/api/telemetry/series" && req.method === "GET") {
|
|
47145
|
-
|
|
47520
|
+
collect(false);
|
|
47521
|
+
const collection2 = collectionSnapshot();
|
|
47146
47522
|
const aggregation = ["sum", "avg", "min", "max", "count", "p50", "p90", "p95", "p99"].includes(String(urlInput.aggregation)) ? urlInput.aggregation : "avg";
|
|
47147
47523
|
const query = queryFrom({ ...urlInput, kinds: "metric", limit: 5000 });
|
|
47148
47524
|
const series = telemetry2.series({
|
|
@@ -48363,8 +48739,8 @@ data: ${JSON.stringify({ cursor: position, at: new Date().toISOString() })}
|
|
|
48363
48739
|
});
|
|
48364
48740
|
}
|
|
48365
48741
|
if (url.pathname === "/api/dashboard-data") {
|
|
48366
|
-
latestData = await
|
|
48367
|
-
|
|
48742
|
+
latestData = latestDataByEnvironment.get(environment2) ?? await refreshLatestDashboardData(environment2);
|
|
48743
|
+
refreshLatestDashboardData(environment2);
|
|
48368
48744
|
return json15(scopeDashboardData(latestData, { user: scopedUser, slug: config6.project.slug }));
|
|
48369
48745
|
}
|
|
48370
48746
|
if (url.pathname === "/api/users" && req.method === "GET")
|