@stacksjs/ts-cloud 0.7.63 → 0.7.65
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-t092y37p.js → chunk-267f6qd0.js} +525 -145
- package/dist/{chunk-zvk0f89t.js → chunk-28vh1h91.js} +1 -1
- 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/security/pre-deploy-scanner.test.d.ts +1 -0
- package/dist/ui/access-denied.html +2 -2
- 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 +13 -7
- 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/security.html +2 -2
- package/dist/ui/server/actions.html +3 -3
- 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/firewall.html +3 -3
- 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 +3 -3
- 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,
|
|
@@ -36946,7 +36946,7 @@ var SECRET_PATTERNS = [
|
|
|
36946
36946
|
},
|
|
36947
36947
|
{
|
|
36948
36948
|
name: "AWS Secret Access Key",
|
|
36949
|
-
pattern: /(?:aws_secret_access_key|aws_secret_key|secret_access_key|secretAccessKey)
|
|
36949
|
+
pattern: /(?:aws_secret_access_key|aws_secret_key|secret_access_key|secretAccessKey)['"]?\s*[=:]\s*['"]?([A-Za-z0-9/+=]{40})['"]?/gi,
|
|
36950
36950
|
severity: "critical",
|
|
36951
36951
|
description: "AWS Secret Access Key detected"
|
|
36952
36952
|
},
|
|
@@ -37289,7 +37289,11 @@ class PreDeployScanner {
|
|
|
37289
37289
|
if (this.isLikelyPlaceholder(match[0], contextLine)) {
|
|
37290
37290
|
continue;
|
|
37291
37291
|
}
|
|
37292
|
-
|
|
37292
|
+
const candidate2 = match[1] || match[0];
|
|
37293
|
+
if (pattern.name === "AWS Secret Key (Generic)" && /^[a-f0-9]{40}$/i.test(candidate2)) {
|
|
37294
|
+
continue;
|
|
37295
|
+
}
|
|
37296
|
+
if (this.isLowEntropy(candidate2)) {
|
|
37293
37297
|
continue;
|
|
37294
37298
|
}
|
|
37295
37299
|
findings.push({
|
|
@@ -38367,28 +38371,183 @@ var PROBED_SERVICES = [
|
|
|
38367
38371
|
"meilisearch"
|
|
38368
38372
|
];
|
|
38369
38373
|
var UNKNOWN = "-";
|
|
38370
|
-
function
|
|
38371
|
-
|
|
38374
|
+
function sharedBoxProbeScript(projectFilter) {
|
|
38375
|
+
const source = String.raw`
|
|
38376
|
+
import { basename, join } from 'node:path'
|
|
38377
|
+
import { existsSync, readdirSync, readFileSync, readlinkSync, statSync } from 'node:fs'
|
|
38378
|
+
|
|
38379
|
+
const fragmentsRoot = '/etc/rpx/sites.d'
|
|
38380
|
+
const certsRoot = '/etc/rpx/certs'
|
|
38381
|
+
const projectFilter = ${JSON.stringify(projectFilter ?? "")}
|
|
38382
|
+
const configuredRoutes = []
|
|
38383
|
+
if (existsSync(fragmentsRoot)) {
|
|
38384
|
+
for (const file of readdirSync(fragmentsRoot).filter(name => name.endsWith('.json')).sort()) {
|
|
38385
|
+
try {
|
|
38386
|
+
const fragment = JSON.parse(readFileSync(join(fragmentsRoot, file), 'utf8'))
|
|
38387
|
+
const project = String(fragment.slug || basename(file, '.json'))
|
|
38388
|
+
if (projectFilter && project !== projectFilter) continue
|
|
38389
|
+
for (const route of Array.isArray(fragment.proxies) ? fragment.proxies : []) {
|
|
38390
|
+
if (!route || !route.to) continue
|
|
38391
|
+
const path = route.path || '/'
|
|
38392
|
+
const staticRoot = typeof route.static === 'string' ? route.static : route.static?.dir
|
|
38393
|
+
const kind = route.redirect ? 'redirect' : staticRoot ? 'static' : 'app'
|
|
38394
|
+
const install = staticRoot || ''
|
|
38395
|
+
const installName = install.match(/^\/var\/www\/(.+?)\/current(?:\/|$)/)?.[1]
|
|
38396
|
+
const siteName = installName?.startsWith(project + '-') ? installName.slice(project.length + 1) : String(route.id || route.to)
|
|
38397
|
+
const href = 'https://' + route.to + (path === '/' ? '' : path)
|
|
38398
|
+
configuredRoutes.push({
|
|
38399
|
+
name: siteName,
|
|
38400
|
+
project,
|
|
38401
|
+
route: path === '/' ? route.to : route.to + path,
|
|
38402
|
+
href,
|
|
38403
|
+
domain: route.to,
|
|
38404
|
+
path,
|
|
38405
|
+
kind,
|
|
38406
|
+
runtime: kind === 'app' ? 'service' : kind,
|
|
38407
|
+
deploy: kind === 'app' ? 'service' : kind === 'static' ? 'server static' : 'redirect',
|
|
38408
|
+
tls: 'https',
|
|
38409
|
+
root: install || (Array.isArray(route.from) ? route.from.join(', ') : route.from || 'gateway'),
|
|
38410
|
+
branch: kind === 'static' ? 'build artifact' : 'main',
|
|
38411
|
+
install,
|
|
38412
|
+
})
|
|
38413
|
+
}
|
|
38414
|
+
}
|
|
38415
|
+
catch {}
|
|
38416
|
+
}
|
|
38417
|
+
}
|
|
38418
|
+
|
|
38419
|
+
const routes = await Promise.all(configuredRoutes.map(async route => {
|
|
38420
|
+
const started = performance.now()
|
|
38421
|
+
let httpStatus = 0
|
|
38422
|
+
const probe = async (href) => {
|
|
38423
|
+
const response = await fetch(href, {
|
|
38424
|
+
redirect: 'manual',
|
|
38425
|
+
headers: { 'user-agent': 'ts-cloud-monitor/1.0' },
|
|
38426
|
+
signal: AbortSignal.timeout(6000),
|
|
38427
|
+
})
|
|
38428
|
+
const status = response.status
|
|
38429
|
+
await response.body?.cancel()
|
|
38430
|
+
return status
|
|
38431
|
+
}
|
|
38432
|
+
try {
|
|
38433
|
+
httpStatus = await probe(route.href)
|
|
38434
|
+
if (httpStatus === 404 && route.kind === 'app') {
|
|
38435
|
+
const healthHref = route.href.replace(/\/$/, '') + '/health'
|
|
38436
|
+
httpStatus = await probe(healthHref)
|
|
38437
|
+
}
|
|
38438
|
+
}
|
|
38439
|
+
catch {}
|
|
38440
|
+
const responseMs = Math.max(0, Math.round(performance.now() - started))
|
|
38441
|
+
let tlsDaysRemaining
|
|
38442
|
+
const cert = join(certsRoot, route.domain + '.crt')
|
|
38443
|
+
if (existsSync(cert)) {
|
|
38444
|
+
const result = Bun.spawnSync(['openssl', 'x509', '-in', cert, '-noout', '-enddate'])
|
|
38445
|
+
const expiry = result.success ? result.stdout.toString().trim().replace(/^notAfter=/, '') : ''
|
|
38446
|
+
const expiresAt = Date.parse(expiry)
|
|
38447
|
+
if (Number.isFinite(expiresAt)) tlsDaysRemaining = Math.floor((expiresAt - Date.now()) / 86400000)
|
|
38448
|
+
}
|
|
38449
|
+
let release
|
|
38450
|
+
let deployedAt
|
|
38451
|
+
if (route.install) {
|
|
38452
|
+
try {
|
|
38453
|
+
release = basename(readlinkSync(route.install))
|
|
38454
|
+
deployedAt = statSync(route.install).mtime.toISOString()
|
|
38455
|
+
}
|
|
38456
|
+
catch {}
|
|
38457
|
+
}
|
|
38458
|
+
const status = httpStatus === 0 || httpStatus >= 500 ? 'failed' : httpStatus === 404 ? 'degraded' : 'live'
|
|
38459
|
+
const { install, ...site } = route
|
|
38460
|
+
return {
|
|
38461
|
+
...site,
|
|
38462
|
+
status,
|
|
38463
|
+
httpStatus,
|
|
38464
|
+
responseMs,
|
|
38465
|
+
tlsDaysRemaining,
|
|
38466
|
+
checkedAt: new Date().toISOString(),
|
|
38467
|
+
release,
|
|
38468
|
+
deployedAt,
|
|
38469
|
+
}
|
|
38470
|
+
}))
|
|
38471
|
+
for (const route of routes) console.log('DISCOVERED_SITE=' + Buffer.from(JSON.stringify(route)).toString('base64'))
|
|
38472
|
+
|
|
38473
|
+
const serviceOutput = Bun.spawnSync([
|
|
38474
|
+
'systemctl', 'list-units', '--type=service', '--state=running', '--no-legend', '--plain',
|
|
38475
|
+
]).stdout.toString()
|
|
38476
|
+
for (const line of serviceOutput.split('\n')) {
|
|
38477
|
+
const unit = line.trim().split(/\s+/)[0]
|
|
38478
|
+
if (!unit) continue
|
|
38479
|
+
const properties = Bun.spawnSync([
|
|
38480
|
+
'systemctl',
|
|
38481
|
+
'show',
|
|
38482
|
+
unit,
|
|
38483
|
+
'--property=WorkingDirectory,ExecStart,MemoryCurrent,UnitFileState,ActiveEnterTimestamp',
|
|
38484
|
+
]).stdout.toString()
|
|
38485
|
+
const values = Object.fromEntries(properties.split('\n').filter(Boolean).map(value => {
|
|
38486
|
+
const separator = value.indexOf('=')
|
|
38487
|
+
return separator < 0 ? [value, ''] : [value.slice(0, separator), value.slice(separator + 1)]
|
|
38488
|
+
}))
|
|
38489
|
+
if (!/^\/var\/www\/|^\/opt\/ts-cloud\//.test(values.WorkingDirectory || '') && !/\/var\/www\/|\/opt\/ts-cloud\//.test(values.ExecStart || '')) continue
|
|
38490
|
+
console.log([
|
|
38491
|
+
'SVC',
|
|
38492
|
+
unit,
|
|
38493
|
+
'active',
|
|
38494
|
+
values.MemoryCurrent || '0',
|
|
38495
|
+
values.UnitFileState || '-',
|
|
38496
|
+
values.ActiveEnterTimestamp || '-',
|
|
38497
|
+
].join('='))
|
|
38498
|
+
}
|
|
38499
|
+
`;
|
|
38500
|
+
return `echo '${Buffer.from(source).toString("base64")}' | base64 -d | bun -`;
|
|
38501
|
+
}
|
|
38502
|
+
function metricsScript(options = {}) {
|
|
38503
|
+
const commands = [
|
|
38372
38504
|
"set +e",
|
|
38373
38505
|
'echo "CPUS=$(nproc 2>/dev/null || echo 1)"',
|
|
38506
|
+
`read TS_TOTAL_1 TS_IDLE_1 <<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
|
+
"sleep 0.2",
|
|
38510
|
+
`read TS_TOTAL_2 TS_IDLE_2 <<EOF
|
|
38511
|
+
$(awk '/^cpu /{idle=$5+$6; total=0; for(i=2;i<=NF;i++) total+=$i; print total, idle; exit}' /proc/stat 2>/dev/null)
|
|
38512
|
+
EOF`,
|
|
38513
|
+
`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
38514
|
`echo "LOAD=$(cut -d' ' -f1 /proc/loadavg 2>/dev/null || echo 0)"`,
|
|
38515
|
+
`echo "LOAD5=$(cut -d' ' -f2 /proc/loadavg 2>/dev/null || echo 0)"`,
|
|
38516
|
+
`echo "LOAD15=$(cut -d' ' -f3 /proc/loadavg 2>/dev/null || echo 0)"`,
|
|
38375
38517
|
`echo "MEMTOTAL=$(free -m 2>/dev/null | awk '/^Mem:/{print $2}')"`,
|
|
38376
38518
|
`echo "MEMUSED=$(free -m 2>/dev/null | awk '/^Mem:/{print $3}')"`,
|
|
38519
|
+
`echo "MEMAVAILABLE=$(free -m 2>/dev/null | awk '/^Mem:/{print $7}')"`,
|
|
38520
|
+
`echo "MEMCACHE=$(free -m 2>/dev/null | awk '/^Mem:/{print $6}')"`,
|
|
38521
|
+
`echo "SWAPTOTAL=$(free -m 2>/dev/null | awk '/^Swap:/{print $2}')"`,
|
|
38522
|
+
`echo "SWAPUSED=$(free -m 2>/dev/null | awk '/^Swap:/{print $3}')"`,
|
|
38377
38523
|
`echo "DISKPCT=$(df -P / 2>/dev/null | awk 'NR==2{gsub("%","",$5);print $5}')"`,
|
|
38378
38524
|
`echo "DISKUSEDG=$(df -BG / 2>/dev/null | awk 'NR==2{gsub("G","",$3);print $3}')"`,
|
|
38379
38525
|
`echo "DISKTOTG=$(df -BG / 2>/dev/null | awk 'NR==2{gsub("G","",$2);print $2}')"`,
|
|
38526
|
+
`echo "INODEPCT=$(df -Pi / 2>/dev/null | awk 'NR==2{gsub("%","",$5);print $5}')"`,
|
|
38527
|
+
`echo "NETRX=$(awk -F'[: ]+' 'NR>2 && $2!="lo"{sum+=$3} END{print sum+0}' /proc/net/dev 2>/dev/null)"`,
|
|
38528
|
+
`echo "NETTX=$(awk -F'[: ]+' 'NR>2 && $2!="lo"{sum+=$11} END{print sum+0}' /proc/net/dev 2>/dev/null)"`,
|
|
38529
|
+
`echo "PROCESSES=$(ps -e --no-headers 2>/dev/null | awk 'END{print NR+0}')"`,
|
|
38380
38530
|
`echo "UPTIME=$(uptime -p 2>/dev/null | sed 's/^up //' || echo unknown)"`,
|
|
38381
|
-
|
|
38382
|
-
|
|
38383
|
-
"true"
|
|
38531
|
+
`echo "UPTIME_SECONDS=$(cut -d' ' -f1 /proc/uptime 2>/dev/null || echo 0)"`,
|
|
38532
|
+
'echo "OS=$(. /etc/os-release 2>/dev/null; echo "$PRETTY_NAME")"'
|
|
38384
38533
|
];
|
|
38534
|
+
if (options.includeServices !== false)
|
|
38535
|
+
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`);
|
|
38536
|
+
commands.push(sharedBoxProbeScript(options.projectFilter), "true");
|
|
38537
|
+
return commands;
|
|
38385
38538
|
}
|
|
38386
38539
|
function parseBlock(output) {
|
|
38387
|
-
const kv = { services: [] };
|
|
38540
|
+
const kv = { services: [], discoveredSites: [] };
|
|
38388
38541
|
for (const line of output.split(`
|
|
38389
38542
|
`)) {
|
|
38390
38543
|
const l = line.trim();
|
|
38391
|
-
if (l.startsWith("
|
|
38544
|
+
if (l.startsWith("DISCOVERED_SITE=")) {
|
|
38545
|
+
try {
|
|
38546
|
+
const site = JSON.parse(Buffer.from(l.slice("DISCOVERED_SITE=".length), "base64").toString("utf8"));
|
|
38547
|
+
if (site?.domain && site?.route)
|
|
38548
|
+
kv.discoveredSites.push(site);
|
|
38549
|
+
} catch {}
|
|
38550
|
+
} else if (l.startsWith("SVC=")) {
|
|
38392
38551
|
const [, name, status, memBytes, enabled, since] = /^SVC=([^=]+)=([^=]+)(?:=([^=]*)(?:=([^=]*)(?:=(.*))?)?)?$/.exec(l) ?? [];
|
|
38393
38552
|
if (name) {
|
|
38394
38553
|
kv.services.push({
|
|
@@ -38574,6 +38733,30 @@ function configuredSites(config6) {
|
|
|
38574
38733
|
};
|
|
38575
38734
|
});
|
|
38576
38735
|
}
|
|
38736
|
+
function mergeDiscoveredSites(configured, discovered, ownerSlug) {
|
|
38737
|
+
const routes = new Map(configured.map((site) => [String(site.route), { ...site, project: ownerSlug, managedBy: "owner" }]));
|
|
38738
|
+
for (const site of discovered) {
|
|
38739
|
+
const current = routes.get(site.route);
|
|
38740
|
+
const name = current?.name ?? `${site.project}:${site.name}`;
|
|
38741
|
+
routes.set(site.route, {
|
|
38742
|
+
...current,
|
|
38743
|
+
...site,
|
|
38744
|
+
name,
|
|
38745
|
+
kind: current?.kind ?? site.kind,
|
|
38746
|
+
type: current?.type ?? site.kind,
|
|
38747
|
+
runtime: current?.runtime ?? site.runtime,
|
|
38748
|
+
deploy: current?.deploy ?? site.deploy,
|
|
38749
|
+
root: current?.root ?? site.root,
|
|
38750
|
+
branch: current?.branch ?? site.branch,
|
|
38751
|
+
managedBy: site.project === ownerSlug ? "owner" : "attached"
|
|
38752
|
+
});
|
|
38753
|
+
}
|
|
38754
|
+
return [...routes.values()].sort((a, b) => {
|
|
38755
|
+
if (a.managedBy !== b.managedBy)
|
|
38756
|
+
return a.managedBy === "owner" ? -1 : 1;
|
|
38757
|
+
return String(a.route).localeCompare(String(b.route));
|
|
38758
|
+
});
|
|
38759
|
+
}
|
|
38577
38760
|
function shellSingleQuote(value2) {
|
|
38578
38761
|
return `'${value2.replaceAll("'", "'\\''")}'`;
|
|
38579
38762
|
}
|
|
@@ -38944,12 +39127,24 @@ function resolveConfigOnlyServerDashboardData(config6, environment2) {
|
|
|
38944
39127
|
},
|
|
38945
39128
|
systemMetrics: {
|
|
38946
39129
|
load: 0,
|
|
39130
|
+
load5: 0,
|
|
39131
|
+
load15: 0,
|
|
39132
|
+
cpuUsedPct: 0,
|
|
38947
39133
|
cpus: Math.max(1, Number(config6.infrastructure?.compute?.instances ?? 1)),
|
|
38948
39134
|
memUsedMb: 0,
|
|
38949
39135
|
memTotalMb: 0,
|
|
39136
|
+
memAvailableMb: 0,
|
|
39137
|
+
memCacheMb: 0,
|
|
39138
|
+
swapUsedMb: 0,
|
|
39139
|
+
swapTotalMb: 0,
|
|
38950
39140
|
diskUsedPct: 0,
|
|
38951
39141
|
diskUsedGb: 0,
|
|
38952
|
-
diskTotalGb: diskSize
|
|
39142
|
+
diskTotalGb: diskSize,
|
|
39143
|
+
inodeUsedPct: 0,
|
|
39144
|
+
networkRxBytes: 0,
|
|
39145
|
+
networkTxBytes: 0,
|
|
39146
|
+
processes: 0,
|
|
39147
|
+
uptimeSeconds: 0
|
|
38953
39148
|
},
|
|
38954
39149
|
metricsUnavailable: true,
|
|
38955
39150
|
services,
|
|
@@ -38980,6 +39175,7 @@ function resolveConfigOnlyServerDashboardData(config6, environment2) {
|
|
|
38980
39175
|
envKeys: Object.keys(site.env ?? site.environment ?? {})
|
|
38981
39176
|
};
|
|
38982
39177
|
}),
|
|
39178
|
+
siteHealth: [],
|
|
38983
39179
|
sshKeys: describeSshKeys(config6.infrastructure?.compute?.sshKeys ?? []),
|
|
38984
39180
|
_serverReachable: false,
|
|
38985
39181
|
_metricsStatus: "unavailable"
|
|
@@ -38988,7 +39184,7 @@ function resolveConfigOnlyServerDashboardData(config6, environment2) {
|
|
|
38988
39184
|
out.activity = activityFeed(out);
|
|
38989
39185
|
return out;
|
|
38990
39186
|
}
|
|
38991
|
-
async function resolveServerDashboardData(config6, environment2) {
|
|
39187
|
+
async function resolveServerDashboardData(config6, environment2, options = {}) {
|
|
38992
39188
|
if (!config6.infrastructure?.compute)
|
|
38993
39189
|
return null;
|
|
38994
39190
|
let driver = null;
|
|
@@ -39007,7 +39203,10 @@ async function resolveServerDashboardData(config6, environment2) {
|
|
|
39007
39203
|
if (targets.length) {
|
|
39008
39204
|
const result2 = await driver.runRemoteDeploy({
|
|
39009
39205
|
targets: [targets[0]],
|
|
39010
|
-
commands: metricsScript(
|
|
39206
|
+
commands: metricsScript({
|
|
39207
|
+
includeServices: !options.telemetryOnly,
|
|
39208
|
+
projectFilter: config6.cloud?.attachTo ? config6.project.slug : undefined
|
|
39209
|
+
}),
|
|
39011
39210
|
comment: `ts-cloud dashboard:build ${config6.project.slug}`,
|
|
39012
39211
|
tags: { Project: config6.project.slug, Environment: environment2, Role: "app" }
|
|
39013
39212
|
});
|
|
@@ -39029,19 +39228,32 @@ async function resolveServerDashboardData(config6, environment2) {
|
|
|
39029
39228
|
out._metricsStatus = "live";
|
|
39030
39229
|
out.systemMetrics = {
|
|
39031
39230
|
load: num(parsed.LOAD),
|
|
39231
|
+
load5: num(parsed.LOAD5),
|
|
39232
|
+
load15: num(parsed.LOAD15),
|
|
39233
|
+
cpuUsedPct: num(parsed.CPUUSED),
|
|
39032
39234
|
cpus: num(parsed.CPUS, 1),
|
|
39033
39235
|
memUsedMb: num(parsed.MEMUSED),
|
|
39034
39236
|
memTotalMb: num(parsed.MEMTOTAL, 1),
|
|
39237
|
+
memAvailableMb: num(parsed.MEMAVAILABLE),
|
|
39238
|
+
memCacheMb: num(parsed.MEMCACHE),
|
|
39239
|
+
swapUsedMb: num(parsed.SWAPUSED),
|
|
39240
|
+
swapTotalMb: num(parsed.SWAPTOTAL),
|
|
39035
39241
|
diskUsedPct: num(parsed.DISKPCT),
|
|
39036
39242
|
diskUsedGb: num(parsed.DISKUSEDG),
|
|
39037
|
-
diskTotalGb: num(parsed.DISKTOTG, 1)
|
|
39243
|
+
diskTotalGb: num(parsed.DISKTOTG, 1),
|
|
39244
|
+
inodeUsedPct: num(parsed.INODEPCT),
|
|
39245
|
+
networkRxBytes: num(parsed.NETRX),
|
|
39246
|
+
networkTxBytes: num(parsed.NETTX),
|
|
39247
|
+
processes: num(parsed.PROCESSES),
|
|
39248
|
+
uptimeSeconds: num(parsed.UPTIME_SECONDS)
|
|
39038
39249
|
};
|
|
39039
39250
|
if (parsed.services.length) {
|
|
39040
|
-
|
|
39251
|
+
const services = [...new Map(parsed.services.map((service16) => [service16.name, service16])).values()];
|
|
39252
|
+
out.services = services.map((s) => ({
|
|
39041
39253
|
name: s.name,
|
|
39042
39254
|
status: s.status === "active" ? "running" : s.status
|
|
39043
39255
|
}));
|
|
39044
|
-
out.servicesDetail =
|
|
39256
|
+
out.servicesDetail = services.map((s) => ({
|
|
39045
39257
|
name: s.name,
|
|
39046
39258
|
status: s.status === "active" ? "running" : s.status,
|
|
39047
39259
|
since: s.since && s.since !== UNKNOWN ? s.since : out.server.uptime,
|
|
@@ -39049,64 +39261,100 @@ async function resolveServerDashboardData(config6, environment2) {
|
|
|
39049
39261
|
auto: s.enabled ? !["disabled", "static", "masked"].includes(s.enabled) : true
|
|
39050
39262
|
}));
|
|
39051
39263
|
}
|
|
39052
|
-
|
|
39053
|
-
|
|
39054
|
-
|
|
39055
|
-
|
|
39056
|
-
|
|
39057
|
-
|
|
39058
|
-
|
|
39059
|
-
|
|
39060
|
-
|
|
39061
|
-
|
|
39062
|
-
|
|
39063
|
-
|
|
39064
|
-
|
|
39065
|
-
|
|
39066
|
-
|
|
39067
|
-
|
|
39068
|
-
|
|
39264
|
+
if (parsed.discoveredSites.length) {
|
|
39265
|
+
out.sitesDetail = mergeDiscoveredSites(out.sitesDetail, parsed.discoveredSites, config6.project.slug);
|
|
39266
|
+
out.sites = out.sitesDetail.map((site) => ({
|
|
39267
|
+
name: site.name,
|
|
39268
|
+
project: site.project,
|
|
39269
|
+
managedBy: site.managedBy,
|
|
39270
|
+
route: site.route,
|
|
39271
|
+
href: site.href,
|
|
39272
|
+
domain: site.domain,
|
|
39273
|
+
path: site.path,
|
|
39274
|
+
kind: site.kind,
|
|
39275
|
+
type: site.type,
|
|
39276
|
+
runtime: site.runtime,
|
|
39277
|
+
deploy: site.deploy,
|
|
39278
|
+
tls: site.tls,
|
|
39279
|
+
status: site.status,
|
|
39280
|
+
httpStatus: site.httpStatus,
|
|
39281
|
+
responseMs: site.responseMs,
|
|
39282
|
+
tlsDaysRemaining: site.tlsDaysRemaining,
|
|
39283
|
+
checkedAt: site.checkedAt
|
|
39284
|
+
}));
|
|
39285
|
+
out.siteHealth = out.sitesDetail.map((site) => ({
|
|
39286
|
+
name: site.name,
|
|
39287
|
+
project: site.project,
|
|
39288
|
+
route: site.route,
|
|
39289
|
+
status: site.status,
|
|
39290
|
+
httpStatus: site.httpStatus,
|
|
39291
|
+
responseMs: site.responseMs,
|
|
39292
|
+
tlsDaysRemaining: site.tlsDaysRemaining,
|
|
39293
|
+
checkedAt: site.checkedAt
|
|
39294
|
+
}));
|
|
39069
39295
|
}
|
|
39296
|
+
}
|
|
39297
|
+
if (driver && targets.length && !options.telemetryOnly) {
|
|
39298
|
+
await Promise.all([
|
|
39299
|
+
(async () => {
|
|
39300
|
+
try {
|
|
39301
|
+
const siteNames = Object.keys(config6.sites ?? {});
|
|
39302
|
+
const historyResult = await driver.runRemoteDeploy({
|
|
39303
|
+
targets: [targets[0]],
|
|
39304
|
+
commands: deployHistoryScript(siteNames, config6.project.slug),
|
|
39305
|
+
comment: `ts-cloud dashboard:deploy-history ${config6.project.slug}`,
|
|
39306
|
+
tags: { Project: config6.project.slug, Environment: environment2, Role: "app" }
|
|
39307
|
+
});
|
|
39308
|
+
const historyOutput = historyResult.perInstance?.[0]?.output ?? "";
|
|
39309
|
+
const records = parseDeployHistory(historyOutput, config6.sites);
|
|
39310
|
+
out.serverDeployments = records.slice(0, 5);
|
|
39311
|
+
out.serverDeploymentsDetail = records.slice(0, 50);
|
|
39312
|
+
out.deploymentsEmptyReason = records.length ? undefined : "No deployment history was found on the server yet. Deploy again to populate this timeline.";
|
|
39313
|
+
} catch {
|
|
39314
|
+
out.deploymentsEmptyReason = "Deployment history could not be read from the server.";
|
|
39315
|
+
}
|
|
39316
|
+
})(),
|
|
39317
|
+
(async () => {
|
|
39318
|
+
try {
|
|
39319
|
+
const logsResult = await driver.runRemoteDeploy({
|
|
39320
|
+
targets: [targets[0]],
|
|
39321
|
+
commands: serverLogsScript(config6),
|
|
39322
|
+
comment: `ts-cloud dashboard:server-logs ${config6.project.slug}`,
|
|
39323
|
+
tags: { Project: config6.project.slug, Environment: environment2, Role: "app" }
|
|
39324
|
+
});
|
|
39325
|
+
const logsOutput = logsResult.perInstance?.[0]?.output ?? "";
|
|
39326
|
+
const records = parseServerLogs(logsOutput);
|
|
39327
|
+
out.serverLogs = records;
|
|
39328
|
+
out.serverLogsEmptyReason = records.length ? undefined : "No recent journal entries were found for the managed server units.";
|
|
39329
|
+
} catch {
|
|
39330
|
+
out.serverLogsEmptyReason = "Server logs could not be read from the box.";
|
|
39331
|
+
}
|
|
39332
|
+
})(),
|
|
39333
|
+
(async () => {
|
|
39334
|
+
try {
|
|
39335
|
+
const securityResult = await driver.runRemoteDeploy({
|
|
39336
|
+
targets: [targets[0]],
|
|
39337
|
+
commands: securityScript(config6),
|
|
39338
|
+
comment: `ts-cloud dashboard:security ${config6.project.slug}`,
|
|
39339
|
+
tags: { Project: config6.project.slug, Environment: environment2, Role: "app" }
|
|
39340
|
+
});
|
|
39341
|
+
const securityOutput = securityResult.perInstance?.[0]?.output ?? "";
|
|
39342
|
+
const liveSecurity = parseServerSecurity(securityOutput);
|
|
39343
|
+
const declaredSecurity = configuredSecurity(config6);
|
|
39344
|
+
if (!liveSecurity.ports.length)
|
|
39345
|
+
liveSecurity.ports = declaredSecurity.ports;
|
|
39346
|
+
if (!liveSecurity.tlsCertificates.length)
|
|
39347
|
+
liveSecurity.tlsCertificates = declaredSecurity.tlsCertificates;
|
|
39348
|
+
out.security = liveSecurity;
|
|
39349
|
+
out.securityEmptyReason = undefined;
|
|
39350
|
+
} catch {
|
|
39351
|
+
out.securityEmptyReason = "Server security checks could not be read from the box.";
|
|
39352
|
+
}
|
|
39353
|
+
})()
|
|
39354
|
+
]);
|
|
39070
39355
|
} else if (driver && instanceCount === 0) {
|
|
39071
39356
|
out.deploymentsEmptyReason = "No app server target was found for this environment.";
|
|
39072
39357
|
}
|
|
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
39358
|
const sshKeys = config6.infrastructure?.compute?.sshKeys ?? config6.infrastructure?.ssh?.keys ?? config6.infrastructure?.sshKeys;
|
|
39111
39359
|
if (Array.isArray(sshKeys) && sshKeys.length) {
|
|
39112
39360
|
out.sshKeys = describeSshKeys(sshKeys);
|
|
@@ -41982,7 +42230,9 @@ async function collectNow(context) {
|
|
|
41982
42230
|
const scope2 = { projectId: context.projectId, environmentId: context.environmentId };
|
|
41983
42231
|
const mode = resolveDeploymentMode(context.config);
|
|
41984
42232
|
try {
|
|
41985
|
-
const data = (mode === "serverless" ? await resolveDashboardData(context.config, context.environment) : await resolveServerDashboardData(context.config, context.environment
|
|
42233
|
+
const data = (mode === "serverless" ? await resolveDashboardData(context.config, context.environment) : await resolveServerDashboardData(context.config, context.environment, {
|
|
42234
|
+
telemetryOnly: context.lightweight
|
|
42235
|
+
})) ?? {};
|
|
41986
42236
|
if (mode === "serverless") {
|
|
41987
42237
|
for (const fn of data.functionsDetail ?? []) {
|
|
41988
42238
|
const resourceId = resourceBySlug(context.controlPlane, context.projectId, context.environmentId, fn.key);
|
|
@@ -42151,14 +42401,30 @@ async function collectNow(context) {
|
|
|
42151
42401
|
if (data.metricsUnavailable || !metrics)
|
|
42152
42402
|
errors.push({ source: "host", message: "The host metrics probe is unavailable." });
|
|
42153
42403
|
else {
|
|
42404
|
+
const memoryUsedPercent = Number(metrics.memTotalMb) > 0 ? Number(metrics.memUsedMb) / Number(metrics.memTotalMb) * 100 : 0;
|
|
42405
|
+
const swapUsedPercent = Number(metrics.swapTotalMb) > 0 ? Number(metrics.swapUsedMb) / Number(metrics.swapTotalMb) * 100 : 0;
|
|
42154
42406
|
const values = [
|
|
42155
42407
|
["host.load", Number(metrics.load), "load"],
|
|
42408
|
+
["host.load.5m", Number(metrics.load5), "load"],
|
|
42409
|
+
["host.load.15m", Number(metrics.load15), "load"],
|
|
42156
42410
|
["host.cpu.capacity", Number(metrics.cpus), "count"],
|
|
42411
|
+
["host.cpu.used_percent", Number(metrics.cpuUsedPct), "percent"],
|
|
42157
42412
|
["host.memory.used", Number(metrics.memUsedMb) * 1024 * 1024, "bytes"],
|
|
42158
42413
|
["host.memory.total", Number(metrics.memTotalMb) * 1024 * 1024, "bytes"],
|
|
42414
|
+
["host.memory.available", Number(metrics.memAvailableMb) * 1024 * 1024, "bytes"],
|
|
42415
|
+
["host.memory.cache", Number(metrics.memCacheMb) * 1024 * 1024, "bytes"],
|
|
42416
|
+
["host.memory.used_percent", memoryUsedPercent, "percent"],
|
|
42417
|
+
["host.swap.used", Number(metrics.swapUsedMb) * 1024 * 1024, "bytes"],
|
|
42418
|
+
["host.swap.total", Number(metrics.swapTotalMb) * 1024 * 1024, "bytes"],
|
|
42419
|
+
["host.swap.used_percent", swapUsedPercent, "percent"],
|
|
42159
42420
|
["host.disk.used_percent", Number(metrics.diskUsedPct), "percent"],
|
|
42160
42421
|
["host.disk.used", Number(metrics.diskUsedGb) * 1024 ** 3, "bytes"],
|
|
42161
|
-
["host.disk.total", Number(metrics.diskTotalGb) * 1024 ** 3, "bytes"]
|
|
42422
|
+
["host.disk.total", Number(metrics.diskTotalGb) * 1024 ** 3, "bytes"],
|
|
42423
|
+
["host.disk.inode_used_percent", Number(metrics.inodeUsedPct), "percent"],
|
|
42424
|
+
["host.network.rx", Number(metrics.networkRxBytes), "bytes"],
|
|
42425
|
+
["host.network.tx", Number(metrics.networkTxBytes), "bytes"],
|
|
42426
|
+
["host.processes", Number(metrics.processes), "count"],
|
|
42427
|
+
["host.uptime", Number(metrics.uptimeSeconds), "seconds"]
|
|
42162
42428
|
];
|
|
42163
42429
|
for (const [name, value2, unit] of values)
|
|
42164
42430
|
if (Number.isFinite(value2))
|
|
@@ -42172,6 +42438,43 @@ async function collectNow(context) {
|
|
|
42172
42438
|
value: value2,
|
|
42173
42439
|
unit
|
|
42174
42440
|
});
|
|
42441
|
+
for (const site of data.siteHealth ?? []) {
|
|
42442
|
+
const project = String(site.project ?? context.config.project.slug);
|
|
42443
|
+
const displayName = String(site.name);
|
|
42444
|
+
const siteName = displayName.startsWith(`${project}:`) ? displayName.slice(project.length + 1) : displayName;
|
|
42445
|
+
const source = `site:${project}:${siteName}`;
|
|
42446
|
+
const resourceId = resourceBySlug(context.controlPlane, context.projectId, context.environmentId, displayName);
|
|
42447
|
+
const base = {
|
|
42448
|
+
...scope2,
|
|
42449
|
+
resourceId,
|
|
42450
|
+
source,
|
|
42451
|
+
timestamp: site.checkedAt || now.toISOString(),
|
|
42452
|
+
host: site.route,
|
|
42453
|
+
attributes: {
|
|
42454
|
+
project,
|
|
42455
|
+
site: siteName,
|
|
42456
|
+
route: String(site.route),
|
|
42457
|
+
status: String(site.status)
|
|
42458
|
+
}
|
|
42459
|
+
};
|
|
42460
|
+
const siteValues = [
|
|
42461
|
+
["site.health.up", site.status === "live" ? 1 : 0, "boolean"],
|
|
42462
|
+
["site.http.status", Number(site.httpStatus), "status"],
|
|
42463
|
+
["site.response.time", Number(site.responseMs), "ms"]
|
|
42464
|
+
];
|
|
42465
|
+
if (site.tlsDaysRemaining != null)
|
|
42466
|
+
siteValues.push(["site.tls.days_remaining", Number(site.tlsDaysRemaining), "days"]);
|
|
42467
|
+
for (const [name, value2, unit] of siteValues)
|
|
42468
|
+
if (Number.isFinite(value2))
|
|
42469
|
+
records.push({
|
|
42470
|
+
...base,
|
|
42471
|
+
id: id(source, name, base.timestamp),
|
|
42472
|
+
kind: "metric",
|
|
42473
|
+
name,
|
|
42474
|
+
value: value2,
|
|
42475
|
+
unit
|
|
42476
|
+
});
|
|
42477
|
+
}
|
|
42175
42478
|
}
|
|
42176
42479
|
}
|
|
42177
42480
|
} catch (error2) {
|
|
@@ -42180,73 +42483,74 @@ async function collectNow(context) {
|
|
|
42180
42483
|
message: error2 instanceof Error ? error2.message : String(error2)
|
|
42181
42484
|
});
|
|
42182
42485
|
}
|
|
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"
|
|
42486
|
+
if (!context.lightweight)
|
|
42487
|
+
try {
|
|
42488
|
+
const inventory2 = await resolveRuntimeInventory(context.config, context.environment);
|
|
42489
|
+
const runtime3 = new RuntimeOperationService(context.config, context.environment, {
|
|
42490
|
+
inventory: async () => inventory2
|
|
42214
42491
|
});
|
|
42215
|
-
|
|
42492
|
+
for (const workload of inventory2.workloads) {
|
|
42493
|
+
const resourceId = resourceBySlug(context.controlPlane, context.projectId, context.environmentId, workload.links.service);
|
|
42494
|
+
const base = {
|
|
42495
|
+
...scope2,
|
|
42496
|
+
resourceId,
|
|
42497
|
+
source: `runtime:${workload.provider}`,
|
|
42498
|
+
timestamp: now.toISOString(),
|
|
42499
|
+
workloadId: workload.id,
|
|
42500
|
+
releaseId: workload.links.release,
|
|
42501
|
+
attributes: { status: workload.status, provider: workload.provider, service: workload.links.service ?? "" }
|
|
42502
|
+
};
|
|
42216
42503
|
records.push({
|
|
42217
42504
|
...base,
|
|
42218
|
-
id: id(workload.id, "
|
|
42505
|
+
id: id(workload.id, "health", now.toISOString()),
|
|
42219
42506
|
kind: "metric",
|
|
42220
|
-
name: "
|
|
42221
|
-
value: workload.
|
|
42222
|
-
unit: "
|
|
42507
|
+
name: "health.up",
|
|
42508
|
+
value: workload.status === "running" ? 1 : 0,
|
|
42509
|
+
unit: "boolean"
|
|
42223
42510
|
});
|
|
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)
|
|
42511
|
+
records.push({
|
|
42512
|
+
...base,
|
|
42513
|
+
id: id(workload.id, "restarts", now.toISOString()),
|
|
42514
|
+
kind: "metric",
|
|
42515
|
+
name: "saturation.restarts",
|
|
42516
|
+
value: workload.restartCount ?? 0,
|
|
42517
|
+
unit: "count"
|
|
42242
42518
|
});
|
|
42519
|
+
if (workload.resources?.memoryBytes != null)
|
|
42520
|
+
records.push({
|
|
42521
|
+
...base,
|
|
42522
|
+
id: id(workload.id, "memory", now.toISOString()),
|
|
42523
|
+
kind: "metric",
|
|
42524
|
+
name: "runtime.memory.used",
|
|
42525
|
+
value: workload.resources.memoryBytes,
|
|
42526
|
+
unit: "bytes"
|
|
42527
|
+
});
|
|
42528
|
+
if (!workload.capabilities.logs.supported)
|
|
42529
|
+
continue;
|
|
42530
|
+
try {
|
|
42531
|
+
const result2 = await runtime3.logs(workload.id, { limit: 100, since: new Date(now.getTime() - 5 * 60000) });
|
|
42532
|
+
for (const line of result2.lines)
|
|
42533
|
+
records.push(...telemetryRecordsFromLog({
|
|
42534
|
+
...scope2,
|
|
42535
|
+
resourceId,
|
|
42536
|
+
source: `runtime:${workload.provider}`,
|
|
42537
|
+
name: `${workload.links.service ?? workload.name}.log`,
|
|
42538
|
+
timestamp: line.timestamp,
|
|
42539
|
+
message: line.message,
|
|
42540
|
+
workloadId: workload.id
|
|
42541
|
+
}));
|
|
42542
|
+
} catch (error2) {
|
|
42543
|
+
errors.push({
|
|
42544
|
+
source: `runtime:${workload.provider}`,
|
|
42545
|
+
message: error2 instanceof Error ? error2.message : String(error2)
|
|
42546
|
+
});
|
|
42547
|
+
}
|
|
42243
42548
|
}
|
|
42549
|
+
for (const source of inventory2.sources.filter((source2) => source2.status !== "fresh"))
|
|
42550
|
+
errors.push({ source: source.id, message: source.message ?? `Runtime source is ${source.status}.` });
|
|
42551
|
+
} catch (error2) {
|
|
42552
|
+
errors.push({ source: "runtime", message: error2 instanceof Error ? error2.message : String(error2) });
|
|
42244
42553
|
}
|
|
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
42554
|
for (const event of context.controlPlane.listEvents({ projectId: context.projectId, limit: 1000 })) {
|
|
42251
42555
|
const payload2 = event.payload;
|
|
42252
42556
|
records.push({
|
|
@@ -42293,8 +42597,9 @@ async function collectNow(context) {
|
|
|
42293
42597
|
}
|
|
42294
42598
|
async function collectDashboardTelemetry(context) {
|
|
42295
42599
|
const key2 = `${context.projectId}:${context.environmentId ?? "all"}:${context.environment}`;
|
|
42600
|
+
const tieredKey = `${key2}:${context.lightweight ? "lightweight" : "full"}`;
|
|
42296
42601
|
const ttl = resolveDeploymentMode(context.config) === "serverless" ? 5 * 60000 : 60000;
|
|
42297
|
-
const result2 = await collectionCache.getOrCreate(
|
|
42602
|
+
const result2 = await collectionCache.getOrCreate(tieredKey, ttl, !!context.force, () => collectNow(context));
|
|
42298
42603
|
return { ...result2.value, cached: result2.cached };
|
|
42299
42604
|
}
|
|
42300
42605
|
function invalidateDashboardTelemetryCache(projectId) {
|
|
@@ -42634,7 +42939,23 @@ function isTrustedMutationRequest(req) {
|
|
|
42634
42939
|
if (site === "cross-site")
|
|
42635
42940
|
return false;
|
|
42636
42941
|
const origin = req.headers.get("origin");
|
|
42637
|
-
|
|
42942
|
+
if (!origin)
|
|
42943
|
+
return true;
|
|
42944
|
+
const requestUrl = new URL(req.url);
|
|
42945
|
+
if (origin === requestUrl.origin)
|
|
42946
|
+
return true;
|
|
42947
|
+
if (site !== "same-origin")
|
|
42948
|
+
return false;
|
|
42949
|
+
try {
|
|
42950
|
+
const originUrl = new URL(origin);
|
|
42951
|
+
const forwardedHost = (req.headers.get("x-forwarded-host") ?? req.headers.get("host") ?? requestUrl.host).split(",")[0].trim();
|
|
42952
|
+
const forwardedProto = req.headers.get("x-forwarded-proto")?.split(",")[0]?.trim();
|
|
42953
|
+
if (!/^[A-Za-z0-9.-]+(?::\d{1,5})?$/.test(forwardedHost) || originUrl.host !== forwardedHost)
|
|
42954
|
+
return false;
|
|
42955
|
+
return !forwardedProto || originUrl.protocol === `${forwardedProto}:`;
|
|
42956
|
+
} catch {
|
|
42957
|
+
return false;
|
|
42958
|
+
}
|
|
42638
42959
|
}
|
|
42639
42960
|
var ORGANIZATION_ROLES = new Set(["owner", "admin", "deployer", "operator", "viewer", "auditor"]);
|
|
42640
42961
|
var AUTHORIZATION_SCOPES = new Set(["organization", "project", "environment", "resource"]);
|
|
@@ -43109,7 +43430,52 @@ async function startLocalDashboardServer(options = {}) {
|
|
|
43109
43430
|
const configPath = resolveCloudConfigPath(cwd);
|
|
43110
43431
|
const initialData = await resolveLiveDashboardData(config6, defaultEnvironment);
|
|
43111
43432
|
const latestDataByEnvironment = new Map([[defaultEnvironment, initialData]]);
|
|
43433
|
+
const liveDataRefreshesByEnvironment = new Map;
|
|
43434
|
+
const refreshLatestDashboardData = (environment2) => {
|
|
43435
|
+
const running = liveDataRefreshesByEnvironment.get(environment2);
|
|
43436
|
+
if (running)
|
|
43437
|
+
return running;
|
|
43438
|
+
const refresh = resolveLiveDashboardData(config6, environment2).then((data) => {
|
|
43439
|
+
const previous = latestDataByEnvironment.get(environment2);
|
|
43440
|
+
latestDataByEnvironment.set(environment2, data);
|
|
43441
|
+
if (previous?._serverReachable !== data._serverReachable || previous?._metricsStatus !== data._metricsStatus || (previous?.sitesDetail?.length ?? 0) !== (data.sitesDetail?.length ?? 0))
|
|
43442
|
+
clearUiCache();
|
|
43443
|
+
return data;
|
|
43444
|
+
}).finally(() => liveDataRefreshesByEnvironment.delete(environment2));
|
|
43445
|
+
liveDataRefreshesByEnvironment.set(environment2, refresh);
|
|
43446
|
+
return refresh;
|
|
43447
|
+
};
|
|
43112
43448
|
const packagedUi = resolveUiSource(cwd);
|
|
43449
|
+
const telemetryPreference = process.env.TS_CLOUD_DASHBOARD_TELEMETRY?.trim();
|
|
43450
|
+
const backgroundTelemetryEnabled = options.box && telemetryPreference !== "0" && (telemetryPreference === "1" || !config6.cloud?.attachTo);
|
|
43451
|
+
if (backgroundTelemetryEnabled) {
|
|
43452
|
+
let telemetryCollectionRunning = false;
|
|
43453
|
+
const collectHostHistory = async () => {
|
|
43454
|
+
if (telemetryCollectionRunning)
|
|
43455
|
+
return;
|
|
43456
|
+
telemetryCollectionRunning = true;
|
|
43457
|
+
try {
|
|
43458
|
+
const environmentRecord = controlPlane.environments.get(defaultEnvironment);
|
|
43459
|
+
await collectDashboardTelemetry({
|
|
43460
|
+
controlPlane: controlPlane.store,
|
|
43461
|
+
projectId: controlPlane.project.id,
|
|
43462
|
+
environmentId: environmentRecord?.id,
|
|
43463
|
+
config: config6,
|
|
43464
|
+
environment: defaultEnvironment,
|
|
43465
|
+
force: true,
|
|
43466
|
+
lightweight: true
|
|
43467
|
+
});
|
|
43468
|
+
} catch (error2) {
|
|
43469
|
+
if (options.verbose)
|
|
43470
|
+
console.warn(` ts-cloud dashboard: background telemetry collection failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
43471
|
+
} finally {
|
|
43472
|
+
telemetryCollectionRunning = false;
|
|
43473
|
+
}
|
|
43474
|
+
};
|
|
43475
|
+
collectHostHistory();
|
|
43476
|
+
const telemetryTimer = setInterval(() => void collectHostHistory(), 60000);
|
|
43477
|
+
telemetryTimer.unref?.();
|
|
43478
|
+
}
|
|
43113
43479
|
const uiCache = new Map;
|
|
43114
43480
|
const uiBuilds = new Map;
|
|
43115
43481
|
const ownedUiRoots = new Set;
|
|
@@ -43683,6 +44049,7 @@ async function startLocalDashboardServer(options = {}) {
|
|
|
43683
44049
|
const server = Bun.serve({
|
|
43684
44050
|
hostname: host2,
|
|
43685
44051
|
port,
|
|
44052
|
+
idleTimeout: 30,
|
|
43686
44053
|
websocket: {
|
|
43687
44054
|
open(ws) {
|
|
43688
44055
|
if (!terminalEnabled) {
|
|
@@ -43727,8 +44094,7 @@ async function startLocalDashboardServer(options = {}) {
|
|
|
43727
44094
|
const environment2 = resolveDashboardEnvironment(availableEnvironments, defaultEnvironment, requestedEnvironment);
|
|
43728
44095
|
let latestData = latestDataByEnvironment.get(environment2);
|
|
43729
44096
|
if (!latestData) {
|
|
43730
|
-
latestData = await
|
|
43731
|
-
latestDataByEnvironment.set(environment2, latestData);
|
|
44097
|
+
latestData = await refreshLatestDashboardData(environment2);
|
|
43732
44098
|
}
|
|
43733
44099
|
try {
|
|
43734
44100
|
if (!isTrustedMutationRequest(req))
|
|
@@ -47021,10 +47387,22 @@ This replacement link expires ${created.invitation.expiresAt} and can be used on
|
|
|
47021
47387
|
environmentId: environmentRecord?.id,
|
|
47022
47388
|
config: config6,
|
|
47023
47389
|
environment: environment2,
|
|
47024
|
-
force
|
|
47390
|
+
force,
|
|
47391
|
+
lightweight: !force
|
|
47025
47392
|
});
|
|
47393
|
+
const collectionSnapshot = () => {
|
|
47394
|
+
const saved = controlPlane.store.getSetting(`telemetry.collection:${controlPlane.project.id}:${environmentRecord?.id ?? "all"}`) ?? {};
|
|
47395
|
+
return {
|
|
47396
|
+
generatedAt: String(saved.generatedAt ?? new Date().toISOString()),
|
|
47397
|
+
cached: true,
|
|
47398
|
+
statuses: Array.isArray(saved.statuses) ? saved.statuses : [],
|
|
47399
|
+
errors: Array.isArray(saved.errors) ? saved.errors : [],
|
|
47400
|
+
policy: saved.policy ?? loadTelemetryPolicy(controlPlane.store, controlPlane.project.id)
|
|
47401
|
+
};
|
|
47402
|
+
};
|
|
47026
47403
|
if (url.pathname === "/api/telemetry/status" && req.method === "GET") {
|
|
47027
|
-
|
|
47404
|
+
collect(false);
|
|
47405
|
+
const collection2 = collectionSnapshot();
|
|
47028
47406
|
const scopedStatuses = environmentWide ? collection2.statuses : telemetry2.status(controlPlane.project.id, environmentRecord?.id, collection2.policy.rawDays, collection2.policy.samplingRate, readableResources.map((resource2) => resource2.id));
|
|
47029
47407
|
const estimatedMonthlyBytes = scopedStatuses.reduce((sum, source) => sum + source.estimatedDailyBytes * 30, 0);
|
|
47030
47408
|
return json15({
|
|
@@ -47053,7 +47431,8 @@ This replacement link expires ${created.invitation.expiresAt} and can be used on
|
|
|
47053
47431
|
return json15({ ok: true, policy: policy3 });
|
|
47054
47432
|
}
|
|
47055
47433
|
if (url.pathname === "/api/telemetry/query" && req.method === "GET") {
|
|
47056
|
-
|
|
47434
|
+
collect(false);
|
|
47435
|
+
const collection2 = collectionSnapshot();
|
|
47057
47436
|
return json15({
|
|
47058
47437
|
ok: true,
|
|
47059
47438
|
collection: { generatedAt: collection2.generatedAt, cached: collection2.cached },
|
|
@@ -47061,7 +47440,7 @@ This replacement link expires ${created.invitation.expiresAt} and can be used on
|
|
|
47061
47440
|
});
|
|
47062
47441
|
}
|
|
47063
47442
|
if (url.pathname === "/api/telemetry/tail" && req.method === "GET") {
|
|
47064
|
-
|
|
47443
|
+
collect(false);
|
|
47065
47444
|
const encoder = new TextEncoder;
|
|
47066
47445
|
const initialTo = new Date;
|
|
47067
47446
|
const baseQuery = queryFrom({
|
|
@@ -47142,7 +47521,8 @@ data: ${JSON.stringify({ cursor: position, at: new Date().toISOString() })}
|
|
|
47142
47521
|
});
|
|
47143
47522
|
}
|
|
47144
47523
|
if (url.pathname === "/api/telemetry/series" && req.method === "GET") {
|
|
47145
|
-
|
|
47524
|
+
collect(false);
|
|
47525
|
+
const collection2 = collectionSnapshot();
|
|
47146
47526
|
const aggregation = ["sum", "avg", "min", "max", "count", "p50", "p90", "p95", "p99"].includes(String(urlInput.aggregation)) ? urlInput.aggregation : "avg";
|
|
47147
47527
|
const query = queryFrom({ ...urlInput, kinds: "metric", limit: 5000 });
|
|
47148
47528
|
const series = telemetry2.series({
|
|
@@ -48363,8 +48743,8 @@ data: ${JSON.stringify({ cursor: position, at: new Date().toISOString() })}
|
|
|
48363
48743
|
});
|
|
48364
48744
|
}
|
|
48365
48745
|
if (url.pathname === "/api/dashboard-data") {
|
|
48366
|
-
latestData = await
|
|
48367
|
-
|
|
48746
|
+
latestData = latestDataByEnvironment.get(environment2) ?? await refreshLatestDashboardData(environment2);
|
|
48747
|
+
refreshLatestDashboardData(environment2);
|
|
48368
48748
|
return json15(scopeDashboardData(latestData, { user: scopedUser, slug: config6.project.slug }));
|
|
48369
48749
|
}
|
|
48370
48750
|
if (url.pathname === "/api/users" && req.method === "GET")
|