@stacksjs/ts-cloud 0.7.105 → 0.7.108
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/cli.js +784 -785
- package/dist/bin/dashboard-server.js +292 -292
- package/dist/{chunk-dmzez5f8.js → chunk-33sy72r8.js} +52 -3
- package/dist/{chunk-vkd2y0fs.js → chunk-eg8ck25v.js} +858 -150
- package/dist/deploy/dashboard-bundle-encoding.test.d.ts +1 -0
- package/dist/deploy/dashboard-payload.d.ts +20 -0
- package/dist/deploy/dashboard-payload.test.d.ts +1 -0
- package/dist/deploy/dashboard-topology.d.ts +85 -0
- package/dist/deploy/dashboard-topology.test.d.ts +1 -0
- package/dist/deploy/egress-collection.d.ts +60 -0
- package/dist/deploy/index.d.ts +2 -0
- package/dist/deploy/index.js +14 -2
- package/dist/drivers/index.js +1 -1
- package/dist/index.js +2 -2
- package/dist/ui/404.html +1 -0
- package/dist/ui/access-denied.html +1 -1
- 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 +22 -9
- package/dist/ui/data/services.html +22 -9
- package/dist/ui/data/volumes.html +22 -9
- package/dist/ui/index.html +22 -9
- package/dist/ui/infrastructure/topology.html +2223 -0
- package/dist/ui/integrations.html +2 -2
- package/dist/ui/operations/alerts.html +22 -9
- package/dist/ui/operations/configuration.html +4 -4
- package/dist/ui/operations/jobs.html +22 -9
- package/dist/ui/operations/maintenance.html +4 -4
- package/dist/ui/operations/observability.html +22 -9
- 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 +22 -9
- package/dist/ui/security.html +2 -2
- package/dist/ui/server/actions.html +3 -3
- package/dist/ui/server/activity.html +1 -1
- 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 +22 -9
- package/dist/ui/server/logs.html +22 -9
- package/dist/ui/server/metrics.html +130 -14
- package/dist/ui/server/security.html +2 -2
- package/dist/ui/server/services.html +2 -2
- package/dist/ui/server/sites.html +22 -9
- 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 +3 -3
- 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/deployments.html +2 -2
- package/dist/ui/serverless/firewall.html +2 -2
- package/dist/ui/serverless/functions.html +4 -4
- package/dist/ui/serverless/logs.html +21 -8
- 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/functions/requestJson.ts +41 -0
- package/dist/ui-src/functions/useOpConfirm.ts +132 -0
- package/dist/ui-src/functions/usePolling.ts +98 -0
- package/dist/ui-src/functions/useTerminal.ts +79 -0
- package/dist/ui-src/pages/infrastructure/topology.stx +517 -0
- package/dist/ui-src/pages/partials/nav.stx +2 -0
- package/dist/ui-src/pages/server/metrics.stx +140 -6
- package/dist/ui-src/src/topology.ts +485 -0
- package/package.json +3 -3
|
@@ -47,7 +47,7 @@ import {
|
|
|
47
47
|
resolveSiteKind,
|
|
48
48
|
resolveUiSource,
|
|
49
49
|
siteInstallBase
|
|
50
|
-
} from "./chunk-
|
|
50
|
+
} from "./chunk-33sy72r8.js";
|
|
51
51
|
import {
|
|
52
52
|
artifactKey,
|
|
53
53
|
buildCloudFormationTemplate,
|
|
@@ -7209,6 +7209,15 @@ var dashboardPageRoutes = [
|
|
|
7209
7209
|
modes: ["server"],
|
|
7210
7210
|
adminOnly: true
|
|
7211
7211
|
},
|
|
7212
|
+
{
|
|
7213
|
+
id: "infrastructure.topology",
|
|
7214
|
+
path: "/infrastructure/topology",
|
|
7215
|
+
label: "Infrastructure map",
|
|
7216
|
+
group: "infrastructure",
|
|
7217
|
+
modes: ["server", "serverless"],
|
|
7218
|
+
adminOnly: true,
|
|
7219
|
+
legacyPaths: ["/topology"]
|
|
7220
|
+
},
|
|
7212
7221
|
{
|
|
7213
7222
|
id: "data.services",
|
|
7214
7223
|
path: "/data/services",
|
|
@@ -8876,6 +8885,554 @@ class WAFv2Client {
|
|
|
8876
8885
|
}
|
|
8877
8886
|
}
|
|
8878
8887
|
|
|
8888
|
+
// src/deploy/dashboard-topology.ts
|
|
8889
|
+
var SERVER_LAYERS = [
|
|
8890
|
+
{ index: 0, id: "clients", label: "Clients" },
|
|
8891
|
+
{ index: 1, id: "dns", label: "DNS & TLS" },
|
|
8892
|
+
{ index: 2, id: "edge", label: "Edge" },
|
|
8893
|
+
{ index: 3, id: "routing", label: "Routing" },
|
|
8894
|
+
{ index: 4, id: "apps", label: "Applications" },
|
|
8895
|
+
{ index: 5, id: "runtime", label: "Background runtime" },
|
|
8896
|
+
{ index: 6, id: "data", label: "Data services" },
|
|
8897
|
+
{ index: 7, id: "durability", label: "Durability" }
|
|
8898
|
+
];
|
|
8899
|
+
var SERVERLESS_LAYERS = [
|
|
8900
|
+
{ index: 0, id: "clients", label: "Clients" },
|
|
8901
|
+
{ index: 1, id: "dns", label: "DNS & TLS" },
|
|
8902
|
+
{ index: 2, id: "edge", label: "Edge" },
|
|
8903
|
+
{ index: 3, id: "entry", label: "Entry points" },
|
|
8904
|
+
{ index: 4, id: "compute", label: "Compute" },
|
|
8905
|
+
{ index: 5, id: "messaging", label: "Messaging & pooling" },
|
|
8906
|
+
{ index: 6, id: "data", label: "Data services" },
|
|
8907
|
+
{ index: 7, id: "durability", label: "Durability" }
|
|
8908
|
+
];
|
|
8909
|
+
var DATA_SERVICE_KINDS = [
|
|
8910
|
+
{ match: /^redis/, kind: "cache", label: "Redis" },
|
|
8911
|
+
{ match: /^(?:mysql|mariadb)/, kind: "database", label: "MySQL" },
|
|
8912
|
+
{ match: /^postgres/, kind: "database", label: "PostgreSQL" },
|
|
8913
|
+
{ match: /^meilisearch/, kind: "database", label: "Meilisearch" },
|
|
8914
|
+
{ match: /^vitess|^vtgate/, kind: "database", label: "Vitess" },
|
|
8915
|
+
{ match: /^memcached/, kind: "cache", label: "Memcached" }
|
|
8916
|
+
];
|
|
8917
|
+
var PROXY_SERVICES = /^(?:rpx-gateway|nginx|caddy|traefik|haproxy)$/;
|
|
8918
|
+
function text(value, fallback = "") {
|
|
8919
|
+
const out = value == null ? "" : String(value).trim();
|
|
8920
|
+
return out && out !== "-" && out !== "—" ? out : fallback;
|
|
8921
|
+
}
|
|
8922
|
+
function count(value) {
|
|
8923
|
+
return Array.isArray(value) ? value.length : 0;
|
|
8924
|
+
}
|
|
8925
|
+
function toneOf(status) {
|
|
8926
|
+
const value = text(status).toLowerCase();
|
|
8927
|
+
if (!value)
|
|
8928
|
+
return "unknown";
|
|
8929
|
+
if (["running", "live", "active", "available", "success", "ok", "healthy", "enabled"].includes(value))
|
|
8930
|
+
return "ok";
|
|
8931
|
+
if (["failed", "stopped", "unreachable", "error", "critical", "disabled"].includes(value))
|
|
8932
|
+
return "bad";
|
|
8933
|
+
if (["configured", "creating", "pending", "degraded", "shadowed", "warn", "unknown"].includes(value))
|
|
8934
|
+
return "warn";
|
|
8935
|
+
return "warn";
|
|
8936
|
+
}
|
|
8937
|
+
function pushLink(links, link, ids) {
|
|
8938
|
+
if (ids.has(link.source) && ids.has(link.target))
|
|
8939
|
+
links.push(link);
|
|
8940
|
+
}
|
|
8941
|
+
function buildServerTopology(data, context = {}) {
|
|
8942
|
+
const nodes = [];
|
|
8943
|
+
const links = [];
|
|
8944
|
+
const notes = [];
|
|
8945
|
+
const server = data.server ?? {};
|
|
8946
|
+
const sites = Array.isArray(data.sites) ? data.sites : [];
|
|
8947
|
+
const services = Array.isArray(data.services) ? data.services : [];
|
|
8948
|
+
const workers = Array.isArray(data.workers) ? data.workers : [];
|
|
8949
|
+
const security = data.security ?? {};
|
|
8950
|
+
const reachable = !!data._serverReachable && !data.metricsUnavailable;
|
|
8951
|
+
const box2 = "box";
|
|
8952
|
+
if (!reachable)
|
|
8953
|
+
notes.push("The compute box could not be probed, so live service state is missing; the shape comes from your cloud config.");
|
|
8954
|
+
nodes.push({
|
|
8955
|
+
id: "internet",
|
|
8956
|
+
label: "Internet",
|
|
8957
|
+
kind: "internet",
|
|
8958
|
+
layer: 0,
|
|
8959
|
+
status: "ok",
|
|
8960
|
+
sub: "Public traffic",
|
|
8961
|
+
facts: [],
|
|
8962
|
+
external: true
|
|
8963
|
+
});
|
|
8964
|
+
const domains2 = [
|
|
8965
|
+
...new Set(sites.map((site) => text(site.domain) || text(site.route).split("/")[0]).filter((domain) => domain && domain !== "internal" && domain !== "loopback"))
|
|
8966
|
+
];
|
|
8967
|
+
const certificates = Array.isArray(security.tlsCertificates) ? security.tlsCertificates : [];
|
|
8968
|
+
const expiringSoon = certificates.filter((cert) => Number(cert?.daysRemaining ?? 999) < 21).length;
|
|
8969
|
+
nodes.push({
|
|
8970
|
+
id: "dns",
|
|
8971
|
+
label: domains2.length === 1 ? domains2[0] : "Public domains",
|
|
8972
|
+
kind: "dns",
|
|
8973
|
+
layer: 1,
|
|
8974
|
+
status: expiringSoon ? "warn" : domains2.length ? "ok" : "unknown",
|
|
8975
|
+
sub: domains2.length > 1 ? `${domains2.length} domains` : "DNS & certificates",
|
|
8976
|
+
facts: [
|
|
8977
|
+
{ label: "Domains", value: domains2.length ? domains2.slice(0, 6).join(", ") : "none resolved" },
|
|
8978
|
+
{ label: "Certificates", value: certificates.length ? `${certificates.length} issued` : "none reported" },
|
|
8979
|
+
...expiringSoon ? [{ label: "Expiring", value: `${expiringSoon} within 21 days` }] : []
|
|
8980
|
+
],
|
|
8981
|
+
href: "/server/security",
|
|
8982
|
+
external: true
|
|
8983
|
+
});
|
|
8984
|
+
const firewall = security.firewall ?? {};
|
|
8985
|
+
const ports2 = Array.isArray(security.ports) ? security.ports : [];
|
|
8986
|
+
nodes.push({
|
|
8987
|
+
id: "firewall",
|
|
8988
|
+
label: "Host firewall",
|
|
8989
|
+
kind: "firewall",
|
|
8990
|
+
layer: 2,
|
|
8991
|
+
status: toneOf(firewall.status),
|
|
8992
|
+
sub: text(firewall.summary, "Inbound filtering"),
|
|
8993
|
+
facts: [
|
|
8994
|
+
{ label: "State", value: text(firewall.status, "unknown") },
|
|
8995
|
+
{ label: "Open ports", value: ports2.length ? ports2.map((port) => text(port.listen)).join(", ") : "not reported" }
|
|
8996
|
+
],
|
|
8997
|
+
href: "/server/firewall",
|
|
8998
|
+
group: box2
|
|
8999
|
+
});
|
|
9000
|
+
const proxyService = services.find((service3) => PROXY_SERVICES.test(text(service3.name)));
|
|
9001
|
+
nodes.push({
|
|
9002
|
+
id: "proxy",
|
|
9003
|
+
label: text(proxyService?.name, "Reverse proxy"),
|
|
9004
|
+
kind: "proxy",
|
|
9005
|
+
layer: 3,
|
|
9006
|
+
status: proxyService ? toneOf(proxyService.status) : "unknown",
|
|
9007
|
+
sub: "TLS termination & routing",
|
|
9008
|
+
facts: [
|
|
9009
|
+
{ label: "Service", value: text(proxyService?.name, "not detected") },
|
|
9010
|
+
{ label: "State", value: text(proxyService?.status, reachable ? "not running" : "not probed") },
|
|
9011
|
+
{ label: "Routes", value: `${sites.length} site${sites.length === 1 ? "" : "s"}` }
|
|
9012
|
+
],
|
|
9013
|
+
href: "/server/services",
|
|
9014
|
+
group: box2
|
|
9015
|
+
});
|
|
9016
|
+
for (const site of sites) {
|
|
9017
|
+
const id = `site:${text(site.name)}`;
|
|
9018
|
+
if (!text(site.name))
|
|
9019
|
+
continue;
|
|
9020
|
+
nodes.push({
|
|
9021
|
+
id,
|
|
9022
|
+
label: text(site.name),
|
|
9023
|
+
kind: "site",
|
|
9024
|
+
layer: 4,
|
|
9025
|
+
status: toneOf(site.status),
|
|
9026
|
+
sub: text(site.runtime, "runtime"),
|
|
9027
|
+
facts: [
|
|
9028
|
+
{ label: "Route", value: text(site.route, "internal") },
|
|
9029
|
+
{ label: "Runtime", value: text(site.runtime, "unknown") },
|
|
9030
|
+
{ label: "Delivery", value: text(site.deploy, "unknown") },
|
|
9031
|
+
{ label: "TLS", value: text(site.tls, "unknown") },
|
|
9032
|
+
...site.responseMs != null ? [{ label: "Response", value: `${Math.round(Number(site.responseMs))} ms` }] : [],
|
|
9033
|
+
...site.httpStatus != null ? [{ label: "HTTP", value: String(site.httpStatus) }] : []
|
|
9034
|
+
],
|
|
9035
|
+
href: "/server/sites",
|
|
9036
|
+
group: box2
|
|
9037
|
+
});
|
|
9038
|
+
}
|
|
9039
|
+
for (const worker2 of workers) {
|
|
9040
|
+
const id = `worker:${text(worker2.name)}`;
|
|
9041
|
+
if (!text(worker2.name))
|
|
9042
|
+
continue;
|
|
9043
|
+
nodes.push({
|
|
9044
|
+
id,
|
|
9045
|
+
label: text(worker2.name),
|
|
9046
|
+
kind: "worker",
|
|
9047
|
+
layer: 5,
|
|
9048
|
+
status: toneOf(worker2.status),
|
|
9049
|
+
sub: `${Number(worker2.processes ?? 1)} process${Number(worker2.processes ?? 1) === 1 ? "" : "es"}`,
|
|
9050
|
+
facts: [
|
|
9051
|
+
{ label: "Processes", value: String(worker2.processes ?? 1) },
|
|
9052
|
+
{ label: "State", value: text(worker2.status, "unknown") },
|
|
9053
|
+
...text(worker2.site) ? [{ label: "Site", value: text(worker2.site) }] : []
|
|
9054
|
+
],
|
|
9055
|
+
href: "/operations/jobs",
|
|
9056
|
+
group: box2
|
|
9057
|
+
});
|
|
9058
|
+
}
|
|
9059
|
+
const scheduler = data.serverScheduler ?? {};
|
|
9060
|
+
if (scheduler.enabled) {
|
|
9061
|
+
nodes.push({
|
|
9062
|
+
id: "scheduler",
|
|
9063
|
+
label: "Scheduler",
|
|
9064
|
+
kind: "scheduler",
|
|
9065
|
+
layer: 5,
|
|
9066
|
+
status: "ok",
|
|
9067
|
+
sub: "Cron dispatch",
|
|
9068
|
+
facts: [{ label: "Last run", value: text(scheduler.lastRun, "unknown") }],
|
|
9069
|
+
href: "/operations/jobs",
|
|
9070
|
+
group: box2
|
|
9071
|
+
});
|
|
9072
|
+
}
|
|
9073
|
+
const dataServices = services.map((service3) => {
|
|
9074
|
+
const name = text(service3.name);
|
|
9075
|
+
const match = DATA_SERVICE_KINDS.find((candidate) => candidate.match.test(name));
|
|
9076
|
+
return match ? { service: service3, name, ...match } : null;
|
|
9077
|
+
}).filter((entry) => !!entry);
|
|
9078
|
+
for (const entry of dataServices) {
|
|
9079
|
+
nodes.push({
|
|
9080
|
+
id: `data:${entry.name}`,
|
|
9081
|
+
label: entry.label,
|
|
9082
|
+
kind: entry.kind,
|
|
9083
|
+
layer: 6,
|
|
9084
|
+
status: toneOf(entry.service.status),
|
|
9085
|
+
sub: entry.name,
|
|
9086
|
+
facts: [
|
|
9087
|
+
{ label: "Unit", value: entry.name },
|
|
9088
|
+
{ label: "State", value: text(entry.service.status, "unknown") }
|
|
9089
|
+
],
|
|
9090
|
+
href: "/data/services",
|
|
9091
|
+
group: box2
|
|
9092
|
+
});
|
|
9093
|
+
}
|
|
9094
|
+
const backup = data.backup ?? {};
|
|
9095
|
+
if (backup.enabled) {
|
|
9096
|
+
nodes.push({
|
|
9097
|
+
id: "backup",
|
|
9098
|
+
label: "Backups",
|
|
9099
|
+
kind: "backup",
|
|
9100
|
+
layer: 7,
|
|
9101
|
+
status: "ok",
|
|
9102
|
+
sub: text(backup.destination, "destination"),
|
|
9103
|
+
facts: [
|
|
9104
|
+
{ label: "Destination", value: text(backup.destination, "unknown") },
|
|
9105
|
+
{ label: "Schedule", value: text(backup.schedule, "unknown") },
|
|
9106
|
+
{ label: "Retention", value: `${backup.retention ?? 0} copies` },
|
|
9107
|
+
{ label: "Last run", value: text(backup.last, "unknown") }
|
|
9108
|
+
],
|
|
9109
|
+
href: "/data/backups"
|
|
9110
|
+
});
|
|
9111
|
+
}
|
|
9112
|
+
const ids = new Set(nodes.map((node) => node.id));
|
|
9113
|
+
pushLink(links, { source: "internet", target: "dns", flow: "request", label: "https" }, ids);
|
|
9114
|
+
pushLink(links, { source: "dns", target: "firewall", flow: "request", label: "443/80" }, ids);
|
|
9115
|
+
pushLink(links, { source: "firewall", target: "proxy", flow: "request" }, ids);
|
|
9116
|
+
for (const site of sites) {
|
|
9117
|
+
const id = `site:${text(site.name)}`;
|
|
9118
|
+
pushLink(links, { source: "proxy", target: id, flow: "request", label: text(site.path) || undefined, status: toneOf(site.status) }, ids);
|
|
9119
|
+
}
|
|
9120
|
+
for (const worker2 of workers) {
|
|
9121
|
+
const id = `worker:${text(worker2.name)}`;
|
|
9122
|
+
const site = `site:${text(worker2.site)}`;
|
|
9123
|
+
if (ids.has(site))
|
|
9124
|
+
pushLink(links, { source: site, target: id, flow: "async", label: "queue" }, ids);
|
|
9125
|
+
else
|
|
9126
|
+
pushLink(links, { source: "proxy", target: id, flow: "async", label: "queue" }, ids);
|
|
9127
|
+
}
|
|
9128
|
+
for (const worker2 of workers) {
|
|
9129
|
+
pushLink(links, { source: "scheduler", target: `worker:${text(worker2.name)}`, flow: "async", label: "dispatch" }, ids);
|
|
9130
|
+
}
|
|
9131
|
+
const consumers = [...sites.map((site) => `site:${text(site.name)}`), ...workers.map((worker2) => `worker:${text(worker2.name)}`)];
|
|
9132
|
+
for (const entry of dataServices) {
|
|
9133
|
+
for (const consumer of consumers)
|
|
9134
|
+
pushLink(links, { source: consumer, target: `data:${entry.name}`, flow: "data", label: "localhost" }, ids);
|
|
9135
|
+
}
|
|
9136
|
+
if (dataServices.length && consumers.length)
|
|
9137
|
+
notes.push("Data-service edges show co-location on the box: every process on it can reach localhost. They are not a declared per-site binding.");
|
|
9138
|
+
for (const entry of dataServices) {
|
|
9139
|
+
pushLink(links, { source: `data:${entry.name}`, target: "backup", flow: "backup", label: "snapshot" }, ids);
|
|
9140
|
+
}
|
|
9141
|
+
for (const site of sites) {
|
|
9142
|
+
pushLink(links, { source: `site:${text(site.name)}`, target: "backup", flow: "backup" }, ids);
|
|
9143
|
+
}
|
|
9144
|
+
return {
|
|
9145
|
+
mode: "server",
|
|
9146
|
+
project: text(context.project) || text(data.project?.name) || text(server.name, "project"),
|
|
9147
|
+
environment: text(context.environment) || text(data.environment, "production"),
|
|
9148
|
+
provider: text(server.provider, "unknown"),
|
|
9149
|
+
region: text(server.region, "unknown"),
|
|
9150
|
+
generatedAt: new Date().toISOString(),
|
|
9151
|
+
layers: SERVER_LAYERS,
|
|
9152
|
+
groups: [
|
|
9153
|
+
{
|
|
9154
|
+
id: box2,
|
|
9155
|
+
label: text(server.name, "App server"),
|
|
9156
|
+
sub: [text(server.provider), text(server.region), text(server.ip)].filter(Boolean).join(" · ")
|
|
9157
|
+
}
|
|
9158
|
+
],
|
|
9159
|
+
nodes,
|
|
9160
|
+
links,
|
|
9161
|
+
notes
|
|
9162
|
+
};
|
|
9163
|
+
}
|
|
9164
|
+
function buildServerlessTopology(data, context = {}) {
|
|
9165
|
+
const nodes = [];
|
|
9166
|
+
const links = [];
|
|
9167
|
+
const notes = [];
|
|
9168
|
+
const app = data.app ?? {};
|
|
9169
|
+
const functions = Array.isArray(data.functions) ? data.functions : [];
|
|
9170
|
+
const queues = Array.isArray(data.queues) ? data.queues : [];
|
|
9171
|
+
const metrics = data.metrics ?? {};
|
|
9172
|
+
if (!functions.length)
|
|
9173
|
+
notes.push("No Lambda functions were read for this environment, so the compute layer is empty rather than assumed.");
|
|
9174
|
+
nodes.push({
|
|
9175
|
+
id: "internet",
|
|
9176
|
+
label: "Internet",
|
|
9177
|
+
kind: "internet",
|
|
9178
|
+
layer: 0,
|
|
9179
|
+
status: "ok",
|
|
9180
|
+
sub: "Public traffic",
|
|
9181
|
+
facts: [],
|
|
9182
|
+
external: true
|
|
9183
|
+
});
|
|
9184
|
+
const url = text(app.url);
|
|
9185
|
+
const host = url.replace(/^https?:\/\//, "").split("/")[0];
|
|
9186
|
+
nodes.push({
|
|
9187
|
+
id: "dns",
|
|
9188
|
+
label: host || "Application domain",
|
|
9189
|
+
kind: "dns",
|
|
9190
|
+
layer: 1,
|
|
9191
|
+
status: host ? "ok" : "unknown",
|
|
9192
|
+
sub: "DNS & certificates",
|
|
9193
|
+
facts: [{ label: "Endpoint", value: url || "not resolved" }],
|
|
9194
|
+
external: true
|
|
9195
|
+
});
|
|
9196
|
+
const waf = data.waf;
|
|
9197
|
+
if (waf) {
|
|
9198
|
+
nodes.push({
|
|
9199
|
+
id: "waf",
|
|
9200
|
+
label: "WAF",
|
|
9201
|
+
kind: "waf",
|
|
9202
|
+
layer: 2,
|
|
9203
|
+
status: toneOf(waf.status ?? "active"),
|
|
9204
|
+
sub: text(waf.scope, "Managed rules"),
|
|
9205
|
+
facts: [
|
|
9206
|
+
{ label: "Web ACL", value: text(waf.name, "unnamed") },
|
|
9207
|
+
...waf.blocked != null ? [{ label: "Blocked (24h)", value: String(waf.blocked) }] : [],
|
|
9208
|
+
{ label: "Rules", value: String(count(data.wafRules)) }
|
|
9209
|
+
],
|
|
9210
|
+
href: "/serverless/firewall"
|
|
9211
|
+
});
|
|
9212
|
+
}
|
|
9213
|
+
const assets = data.assetsInfo;
|
|
9214
|
+
if (assets) {
|
|
9215
|
+
nodes.push({
|
|
9216
|
+
id: "cdn",
|
|
9217
|
+
label: "Asset CDN",
|
|
9218
|
+
kind: "cdn",
|
|
9219
|
+
layer: 2,
|
|
9220
|
+
status: "ok",
|
|
9221
|
+
sub: text(assets.cdn, "CloudFront"),
|
|
9222
|
+
facts: [
|
|
9223
|
+
{ label: "Distribution", value: text(assets.cdn, "unknown") },
|
|
9224
|
+
{ label: "Custom domain", value: text(assets.customDomain, "none") }
|
|
9225
|
+
],
|
|
9226
|
+
href: "/serverless/assets"
|
|
9227
|
+
});
|
|
9228
|
+
nodes.push({
|
|
9229
|
+
id: "assets",
|
|
9230
|
+
label: "Asset bucket",
|
|
9231
|
+
kind: "storage",
|
|
9232
|
+
layer: 6,
|
|
9233
|
+
status: "ok",
|
|
9234
|
+
sub: text(assets.bucket, "S3"),
|
|
9235
|
+
facts: [
|
|
9236
|
+
{ label: "Bucket", value: text(assets.bucket, "unknown") },
|
|
9237
|
+
...assets.files ? [{ label: "Objects", value: String(assets.files) }] : [],
|
|
9238
|
+
...assets.sizeMb ? [{ label: "Size", value: `${assets.sizeMb} MB` }] : []
|
|
9239
|
+
],
|
|
9240
|
+
href: "/serverless/assets"
|
|
9241
|
+
});
|
|
9242
|
+
}
|
|
9243
|
+
const httpFunction = functions.find((fn) => text(fn.key).startsWith("http"));
|
|
9244
|
+
nodes.push({
|
|
9245
|
+
id: "gateway",
|
|
9246
|
+
label: "HTTP entry point",
|
|
9247
|
+
kind: "gateway",
|
|
9248
|
+
layer: 3,
|
|
9249
|
+
status: httpFunction ? "ok" : "unknown",
|
|
9250
|
+
sub: "Function URL / API Gateway",
|
|
9251
|
+
facts: [
|
|
9252
|
+
{ label: "Endpoint", value: url || "not resolved" },
|
|
9253
|
+
{ label: "Invocations (24h)", value: String(metrics.invocations ?? 0) },
|
|
9254
|
+
...metrics.errorRatePct != null ? [{ label: "Error rate", value: `${metrics.errorRatePct}%` }] : []
|
|
9255
|
+
],
|
|
9256
|
+
href: "/serverless/metrics"
|
|
9257
|
+
});
|
|
9258
|
+
for (const fn of functions) {
|
|
9259
|
+
const id = `fn:${text(fn.name)}`;
|
|
9260
|
+
if (!text(fn.name))
|
|
9261
|
+
continue;
|
|
9262
|
+
const errors = Number(fn.errors ?? 0);
|
|
9263
|
+
const invocations = Number(fn.invocations ?? 0);
|
|
9264
|
+
nodes.push({
|
|
9265
|
+
id,
|
|
9266
|
+
label: text(fn.key, text(fn.name)),
|
|
9267
|
+
kind: "function",
|
|
9268
|
+
layer: 4,
|
|
9269
|
+
status: errors > 0 ? invocations && errors / invocations > 0.02 ? "bad" : "warn" : invocations ? "ok" : "idle",
|
|
9270
|
+
sub: text(fn.runtime, "lambda"),
|
|
9271
|
+
facts: [
|
|
9272
|
+
{ label: "Function", value: text(fn.name) },
|
|
9273
|
+
{ label: "Memory", value: `${fn.memory ?? 0} MB` },
|
|
9274
|
+
{ label: "Timeout", value: `${fn.timeout ?? 0}s` },
|
|
9275
|
+
{ label: "Invocations (24h)", value: String(invocations) },
|
|
9276
|
+
{ label: "Errors (24h)", value: String(errors) },
|
|
9277
|
+
{ label: "p95", value: `${fn.p95 ?? 0} ms` },
|
|
9278
|
+
...fn.provisioned ? [{ label: "Provisioned", value: text(fn.provisioned) }] : []
|
|
9279
|
+
],
|
|
9280
|
+
href: "/serverless/functions"
|
|
9281
|
+
});
|
|
9282
|
+
}
|
|
9283
|
+
for (const queue2 of queues) {
|
|
9284
|
+
const id = `queue:${text(queue2.name)}`;
|
|
9285
|
+
if (!text(queue2.name))
|
|
9286
|
+
continue;
|
|
9287
|
+
const dlq = Number(queue2.dlq ?? 0);
|
|
9288
|
+
nodes.push({
|
|
9289
|
+
id,
|
|
9290
|
+
label: text(queue2.name),
|
|
9291
|
+
kind: "queue",
|
|
9292
|
+
layer: 5,
|
|
9293
|
+
status: dlq > 0 ? "bad" : Number(queue2.visible ?? 0) > 0 ? "warn" : "ok",
|
|
9294
|
+
sub: "SQS",
|
|
9295
|
+
facts: [
|
|
9296
|
+
{ label: "Visible", value: String(queue2.visible ?? 0) },
|
|
9297
|
+
{ label: "In flight", value: String(queue2.inFlight ?? 0) },
|
|
9298
|
+
{ label: "Processed (24h)", value: String(queue2.processed ?? 0) },
|
|
9299
|
+
{ label: "Dead letter", value: String(dlq) }
|
|
9300
|
+
],
|
|
9301
|
+
href: "/serverless/queues"
|
|
9302
|
+
});
|
|
9303
|
+
}
|
|
9304
|
+
const aurora = data.aurora;
|
|
9305
|
+
if (aurora) {
|
|
9306
|
+
nodes.push({
|
|
9307
|
+
id: "database",
|
|
9308
|
+
label: "Aurora Serverless",
|
|
9309
|
+
kind: "database",
|
|
9310
|
+
layer: 6,
|
|
9311
|
+
status: toneOf(aurora.status),
|
|
9312
|
+
sub: text(aurora.engine, "aurora"),
|
|
9313
|
+
facts: [
|
|
9314
|
+
{ label: "Cluster", value: text(aurora.id, "unknown") },
|
|
9315
|
+
{ label: "Capacity", value: `${aurora.currentAcu ?? 0} ACU (${aurora.minAcu ?? 0}–${aurora.maxAcu ?? 0})` },
|
|
9316
|
+
{ label: "Connections", value: String(aurora.connections ?? 0) }
|
|
9317
|
+
],
|
|
9318
|
+
href: "/serverless/data"
|
|
9319
|
+
});
|
|
9320
|
+
}
|
|
9321
|
+
const proxy = data.proxy;
|
|
9322
|
+
if (proxy) {
|
|
9323
|
+
nodes.push({
|
|
9324
|
+
id: "rds-proxy",
|
|
9325
|
+
label: "RDS Proxy",
|
|
9326
|
+
kind: "database",
|
|
9327
|
+
layer: 5,
|
|
9328
|
+
status: toneOf(proxy.status),
|
|
9329
|
+
sub: "Connection pooling",
|
|
9330
|
+
facts: [
|
|
9331
|
+
{ label: "Proxy", value: text(proxy.name, "unknown") },
|
|
9332
|
+
{ label: "Endpoint", value: text(proxy.endpoint, "unknown") },
|
|
9333
|
+
{ label: "Pooled", value: String(proxy.pooledConns ?? 0) }
|
|
9334
|
+
],
|
|
9335
|
+
href: "/serverless/data"
|
|
9336
|
+
});
|
|
9337
|
+
}
|
|
9338
|
+
const redis = data.redis;
|
|
9339
|
+
if (redis) {
|
|
9340
|
+
nodes.push({
|
|
9341
|
+
id: "cache",
|
|
9342
|
+
label: "ElastiCache",
|
|
9343
|
+
kind: "cache",
|
|
9344
|
+
layer: 6,
|
|
9345
|
+
status: toneOf(redis.status),
|
|
9346
|
+
sub: text(redis.node, "redis"),
|
|
9347
|
+
facts: [
|
|
9348
|
+
{ label: "Group", value: text(redis.id, "unknown") },
|
|
9349
|
+
{ label: "Hit rate", value: `${redis.hitRate ?? 0}%` }
|
|
9350
|
+
],
|
|
9351
|
+
href: "/serverless/data"
|
|
9352
|
+
});
|
|
9353
|
+
}
|
|
9354
|
+
const efs = data.efs;
|
|
9355
|
+
if (efs) {
|
|
9356
|
+
nodes.push({
|
|
9357
|
+
id: "efs",
|
|
9358
|
+
label: "EFS",
|
|
9359
|
+
kind: "filesystem",
|
|
9360
|
+
layer: 6,
|
|
9361
|
+
status: toneOf(efs.status),
|
|
9362
|
+
sub: text(efs.mount, "/mnt/local"),
|
|
9363
|
+
facts: [
|
|
9364
|
+
{ label: "File system", value: text(efs.id, "unknown") },
|
|
9365
|
+
{ label: "Mount", value: text(efs.mount, "unknown") },
|
|
9366
|
+
{ label: "Size", value: `${efs.sizeMb ?? 0} MB` }
|
|
9367
|
+
],
|
|
9368
|
+
href: "/data/volumes"
|
|
9369
|
+
});
|
|
9370
|
+
}
|
|
9371
|
+
const scheduler = data.scheduler ?? {};
|
|
9372
|
+
if (scheduler.enabled) {
|
|
9373
|
+
nodes.push({
|
|
9374
|
+
id: "scheduler",
|
|
9375
|
+
label: "Scheduler",
|
|
9376
|
+
kind: "scheduler",
|
|
9377
|
+
layer: 3,
|
|
9378
|
+
status: "ok",
|
|
9379
|
+
sub: text(scheduler.expression, "rate(1 minute)"),
|
|
9380
|
+
facts: [
|
|
9381
|
+
{ label: "Expression", value: text(scheduler.expression, "unknown") },
|
|
9382
|
+
{ label: "Last run", value: text(scheduler.lastRun, "unknown") }
|
|
9383
|
+
],
|
|
9384
|
+
href: "/operations/jobs"
|
|
9385
|
+
});
|
|
9386
|
+
}
|
|
9387
|
+
const ids = new Set(nodes.map((node) => node.id));
|
|
9388
|
+
const entry = ids.has("waf") ? "waf" : "gateway";
|
|
9389
|
+
pushLink(links, { source: "internet", target: "dns", flow: "request", label: "https" }, ids);
|
|
9390
|
+
pushLink(links, { source: "dns", target: entry, flow: "request" }, ids);
|
|
9391
|
+
pushLink(links, { source: "dns", target: "cdn", flow: "request", label: "assets" }, ids);
|
|
9392
|
+
pushLink(links, { source: "waf", target: "gateway", flow: "request", label: "allowed" }, ids);
|
|
9393
|
+
pushLink(links, { source: "cdn", target: "assets", flow: "data", label: "origin" }, ids);
|
|
9394
|
+
const httpFunctions = functions.filter((fn) => text(fn.key).startsWith("http"));
|
|
9395
|
+
const queueFunctions = functions.filter((fn) => text(fn.key).startsWith("queue"));
|
|
9396
|
+
const cliFunctions = functions.filter((fn) => text(fn.key).startsWith("cli"));
|
|
9397
|
+
for (const fn of httpFunctions)
|
|
9398
|
+
pushLink(links, { source: "gateway", target: `fn:${text(fn.name)}`, flow: "request", label: "invoke" }, ids);
|
|
9399
|
+
for (const fn of cliFunctions)
|
|
9400
|
+
pushLink(links, { source: "scheduler", target: `fn:${text(fn.name)}`, flow: "async", label: "schedule" }, ids);
|
|
9401
|
+
for (const queue2 of queues) {
|
|
9402
|
+
for (const fn of httpFunctions)
|
|
9403
|
+
pushLink(links, { source: `fn:${text(fn.name)}`, target: `queue:${text(queue2.name)}`, flow: "async", label: "publish" }, ids);
|
|
9404
|
+
for (const fn of queueFunctions)
|
|
9405
|
+
pushLink(links, { source: `queue:${text(queue2.name)}`, target: `fn:${text(fn.name)}`, flow: "async", label: "consume" }, ids);
|
|
9406
|
+
}
|
|
9407
|
+
const stateful = ["rds-proxy", "database", "cache", "efs", "assets"];
|
|
9408
|
+
for (const fn of functions) {
|
|
9409
|
+
const id = `fn:${text(fn.name)}`;
|
|
9410
|
+
if (ids.has("rds-proxy"))
|
|
9411
|
+
pushLink(links, { source: id, target: "rds-proxy", flow: "data", label: "pooled" }, ids);
|
|
9412
|
+
else
|
|
9413
|
+
pushLink(links, { source: id, target: "database", flow: "data", label: "sql" }, ids);
|
|
9414
|
+
for (const target of stateful.filter((candidate) => candidate !== "rds-proxy" && candidate !== "database"))
|
|
9415
|
+
pushLink(links, { source: id, target, flow: "data" }, ids);
|
|
9416
|
+
}
|
|
9417
|
+
pushLink(links, { source: "rds-proxy", target: "database", flow: "data", label: "sql" }, ids);
|
|
9418
|
+
return {
|
|
9419
|
+
mode: "serverless",
|
|
9420
|
+
project: text(context.project) || text(app.name, "application"),
|
|
9421
|
+
environment: text(context.environment) || text(app.env, "production"),
|
|
9422
|
+
provider: "aws",
|
|
9423
|
+
region: text(app.region, "unknown"),
|
|
9424
|
+
generatedAt: new Date().toISOString(),
|
|
9425
|
+
layers: SERVERLESS_LAYERS,
|
|
9426
|
+
groups: [],
|
|
9427
|
+
nodes,
|
|
9428
|
+
links,
|
|
9429
|
+
notes
|
|
9430
|
+
};
|
|
9431
|
+
}
|
|
9432
|
+
function buildTopology(mode, data, context = {}) {
|
|
9433
|
+
return mode === "serverless" ? buildServerlessTopology(data, context) : buildServerTopology(data, context);
|
|
9434
|
+
}
|
|
9435
|
+
|
|
8879
9436
|
// src/deploy/dashboard-data.ts
|
|
8880
9437
|
function pad(n) {
|
|
8881
9438
|
return String(n).padStart(2, "0");
|
|
@@ -9373,8 +9930,91 @@ async function resolveDashboardData(config, environment2) {
|
|
|
9373
9930
|
out.schedulerTasks = tasks;
|
|
9374
9931
|
} catch {}
|
|
9375
9932
|
}
|
|
9933
|
+
out.topology = buildServerlessTopology(out, { project: config.project.name, environment: environment2 });
|
|
9376
9934
|
return out;
|
|
9377
9935
|
}
|
|
9936
|
+
// src/deploy/egress-collection.ts
|
|
9937
|
+
var MAX_REPLAY_DAYS = 45;
|
|
9938
|
+
var FETCH_TIMEOUT_MS = 1e4;
|
|
9939
|
+
function finite(value) {
|
|
9940
|
+
const parsed = Number(value);
|
|
9941
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
|
|
9942
|
+
}
|
|
9943
|
+
function dayTimestamp(date) {
|
|
9944
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(date))
|
|
9945
|
+
return null;
|
|
9946
|
+
const parsed = Date.parse(`${date}T12:00:00.000Z`);
|
|
9947
|
+
return Number.isFinite(parsed) ? new Date(parsed).toISOString() : null;
|
|
9948
|
+
}
|
|
9949
|
+
function egressReportMetrics(report, options) {
|
|
9950
|
+
if (!report || typeof report !== "object")
|
|
9951
|
+
return [];
|
|
9952
|
+
const attributes = { source: options.name };
|
|
9953
|
+
const metrics = [];
|
|
9954
|
+
const at = options.now;
|
|
9955
|
+
const push = (name, value, unit, timestamp = at) => {
|
|
9956
|
+
if (value !== null)
|
|
9957
|
+
metrics.push({ name, value, unit, timestamp, attributes });
|
|
9958
|
+
};
|
|
9959
|
+
push("storage.egress.month", finite(report.monthBytes), "bytes");
|
|
9960
|
+
push("storage.egress.today", finite(report.todayBytes), "bytes");
|
|
9961
|
+
push("storage.egress.projected_month", finite(report.projectedMonthBytes), "bytes");
|
|
9962
|
+
const budget = finite(report.budgetBytes);
|
|
9963
|
+
if (budget !== null && budget > 0) {
|
|
9964
|
+
push("storage.egress.budget", budget, "bytes");
|
|
9965
|
+
const reported = report.budgetUsedPercent;
|
|
9966
|
+
const percent = typeof reported === "number" && Number.isFinite(reported) ? reported : finite(report.monthBytes) !== null ? Math.round(finite(report.monthBytes) / budget * 1000) / 10 : null;
|
|
9967
|
+
push("storage.egress.budget_used_percent", percent, "percent");
|
|
9968
|
+
}
|
|
9969
|
+
const limit = options.maxReplayDays ?? MAX_REPLAY_DAYS;
|
|
9970
|
+
const days = Array.isArray(report.days) ? report.days.slice(-limit) : [];
|
|
9971
|
+
for (const day of days) {
|
|
9972
|
+
const timestamp = dayTimestamp(String(day?.date ?? ""));
|
|
9973
|
+
const bytes = finite(day?.bytes);
|
|
9974
|
+
if (!timestamp || bytes === null)
|
|
9975
|
+
continue;
|
|
9976
|
+
metrics.push({ name: "storage.egress.day", value: bytes, unit: "bytes", timestamp, attributes });
|
|
9977
|
+
const downloads = finite(day?.downloads);
|
|
9978
|
+
if (downloads !== null)
|
|
9979
|
+
metrics.push({ name: "storage.egress.day_downloads", value: downloads, unit: "count", timestamp, attributes });
|
|
9980
|
+
}
|
|
9981
|
+
return metrics;
|
|
9982
|
+
}
|
|
9983
|
+
async function fetchEgressReport(endpoint, env = process.env, fetchImpl = fetch) {
|
|
9984
|
+
const token = endpoint.tokenEnv ? env[endpoint.tokenEnv] : undefined;
|
|
9985
|
+
const response = await fetchImpl(endpoint.url, {
|
|
9986
|
+
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
|
9987
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
9988
|
+
});
|
|
9989
|
+
if (!response.ok)
|
|
9990
|
+
throw new Error(`HTTP ${response.status}`);
|
|
9991
|
+
return await response.json();
|
|
9992
|
+
}
|
|
9993
|
+
async function collectEgressMetrics(endpoints, options) {
|
|
9994
|
+
const metrics = [];
|
|
9995
|
+
const errors = [];
|
|
9996
|
+
if (!Array.isArray(endpoints) || endpoints.length === 0)
|
|
9997
|
+
return { metrics, errors };
|
|
9998
|
+
const results = await Promise.all(endpoints.map(async (endpoint) => {
|
|
9999
|
+
if (!endpoint?.name || !endpoint?.url)
|
|
10000
|
+
return { endpoint, error: "egress endpoint needs both a name and a url" };
|
|
10001
|
+
try {
|
|
10002
|
+
const report = await fetchEgressReport(endpoint, options.env, options.fetchImpl);
|
|
10003
|
+
return { endpoint, report };
|
|
10004
|
+
} catch (error2) {
|
|
10005
|
+
return { endpoint, error: error2 instanceof Error ? error2.message : String(error2) };
|
|
10006
|
+
}
|
|
10007
|
+
}));
|
|
10008
|
+
for (const result of results) {
|
|
10009
|
+
const source = `egress:${result.endpoint?.name ?? "unnamed"}`;
|
|
10010
|
+
if ("error" in result && result.error) {
|
|
10011
|
+
errors.push({ source, message: result.error });
|
|
10012
|
+
continue;
|
|
10013
|
+
}
|
|
10014
|
+
metrics.push(...egressReportMetrics(result.report, { name: result.endpoint.name, now: options.now }));
|
|
10015
|
+
}
|
|
10016
|
+
return { metrics, errors };
|
|
10017
|
+
}
|
|
9378
10018
|
// src/deploy/local-dashboard-server.ts
|
|
9379
10019
|
import { createHash as createHash33 } from "node:crypto";
|
|
9380
10020
|
import { existsSync as existsSync20, mkdtempSync as mkdtempSync3, readdirSync as readdirSync8, rmSync as rmSync3, statSync as statSync6 } from "node:fs";
|
|
@@ -10238,7 +10878,7 @@ class NotificationRouter {
|
|
|
10238
10878
|
}
|
|
10239
10879
|
async send(channel2, body) {
|
|
10240
10880
|
const secret = credential(this.store.channelCredential(channel2.id));
|
|
10241
|
-
const
|
|
10881
|
+
const text2 = typeof body.notificationText === "string" ? body.notificationText : `[${String(body.alert?.severity ?? "info").toUpperCase()}] ${String(body.alert?.title ?? "ts-cloud alert")} · ${String(body.alert?.state ?? body.event)}`;
|
|
10242
10882
|
try {
|
|
10243
10883
|
if (channel2.kind === "email") {
|
|
10244
10884
|
if (!this.options.emailImpl)
|
|
@@ -10247,7 +10887,7 @@ class NotificationRouter {
|
|
|
10247
10887
|
to: channel2.config.to,
|
|
10248
10888
|
from: channel2.config.from,
|
|
10249
10889
|
subject: `[ts-cloud] ${String(body.alert?.title ?? body.event)}`,
|
|
10250
|
-
text: `${
|
|
10890
|
+
text: `${text2}
|
|
10251
10891
|
|
|
10252
10892
|
${JSON.stringify(body, null, 2)}`
|
|
10253
10893
|
});
|
|
@@ -10257,15 +10897,15 @@ ${JSON.stringify(body, null, 2)}`
|
|
|
10257
10897
|
let posted = body;
|
|
10258
10898
|
const headers = { "content-type": "application/json" };
|
|
10259
10899
|
if (channel2.kind === "slack")
|
|
10260
|
-
posted = { text };
|
|
10900
|
+
posted = { text: text2 };
|
|
10261
10901
|
if (channel2.kind === "discord")
|
|
10262
|
-
posted = { content:
|
|
10902
|
+
posted = { content: text2 };
|
|
10263
10903
|
if (channel2.kind === "teams")
|
|
10264
|
-
posted = { text };
|
|
10904
|
+
posted = { text: text2 };
|
|
10265
10905
|
if (channel2.kind === "telegram") {
|
|
10266
10906
|
const token = secret.botToken ?? secret.value;
|
|
10267
10907
|
url = `https://api.telegram.org/bot${token}/sendMessage`;
|
|
10268
|
-
posted = { chat_id: channel2.config.chatId, text };
|
|
10908
|
+
posted = { chat_id: channel2.config.chatId, text: text2 };
|
|
10269
10909
|
}
|
|
10270
10910
|
if (!url || !/^https:\/\//.test(url))
|
|
10271
10911
|
return { ok: false, error: "Channel endpoint is unavailable or not HTTPS." };
|
|
@@ -10392,9 +11032,9 @@ class HealthCheckRunner {
|
|
|
10392
11032
|
redirect: "manual"
|
|
10393
11033
|
});
|
|
10394
11034
|
const ttfbMs = performance.now() - requestStart;
|
|
10395
|
-
const
|
|
11035
|
+
const text2 = check.config.expectedBody ? await response.text() : "";
|
|
10396
11036
|
const expected = check.config.expectedStatuses ?? [200, 201, 202, 204];
|
|
10397
|
-
const ok = expected.includes(response.status) && (!check.config.expectedBody ||
|
|
11037
|
+
const ok = expected.includes(response.status) && (!check.config.expectedBody || text2.includes(check.config.expectedBody));
|
|
10398
11038
|
return {
|
|
10399
11039
|
checkId: check.id,
|
|
10400
11040
|
status: ok ? "healthy" : "unhealthy",
|
|
@@ -11758,9 +12398,9 @@ function inspectApplicationArchive(bytes, filename, options = {}) {
|
|
|
11758
12398
|
const header2 = bytes.subarray(offset, offset + 512);
|
|
11759
12399
|
if (header2.every((value) => value === 0))
|
|
11760
12400
|
break;
|
|
11761
|
-
const
|
|
11762
|
-
const name = `${
|
|
11763
|
-
const size = Number.parseInt(
|
|
12401
|
+
const text2 = (start, length) => new TextDecoder().decode(header2.subarray(start, start + length)).replace(/\0.*$/, "");
|
|
12402
|
+
const name = `${text2(345, 155)}${text2(345, 155) ? "/" : ""}${text2(0, 100)}`;
|
|
12403
|
+
const size = Number.parseInt(text2(124, 12).trim() || "0", 8);
|
|
11764
12404
|
const kind = String.fromCharCode(header2[156] ?? 0);
|
|
11765
12405
|
if (!safePath(name))
|
|
11766
12406
|
throw new Error(`Unsafe archive path: ${name}`);
|
|
@@ -16572,11 +17212,11 @@ function createApiV1Handler(options) {
|
|
|
16572
17212
|
try {
|
|
16573
17213
|
let body;
|
|
16574
17214
|
const readBody = async () => {
|
|
16575
|
-
const
|
|
16576
|
-
if (
|
|
17215
|
+
const text2 = await request.text();
|
|
17216
|
+
if (text2.length > 1024 * 1024)
|
|
16577
17217
|
throw new ApiServiceError("payload_too_large", "Request body exceeds 1 MB.", 413);
|
|
16578
17218
|
try {
|
|
16579
|
-
return JSON.parse(
|
|
17219
|
+
return JSON.parse(text2);
|
|
16580
17220
|
} catch {
|
|
16581
17221
|
throw new ApiServiceError("invalid_json", "Request body must be valid JSON.", 400);
|
|
16582
17222
|
}
|
|
@@ -17487,12 +18127,12 @@ data: ${JSON.stringify(event)}
|
|
|
17487
18127
|
const idempotencyKey = request.headers.get("idempotency-key") ?? "";
|
|
17488
18128
|
if (!idempotencyKey)
|
|
17489
18129
|
throw new ApiServiceError("idempotency_required", "Idempotency-Key is required for deployment requests.", 428);
|
|
17490
|
-
const
|
|
17491
|
-
if (
|
|
18130
|
+
const text2 = await request.text();
|
|
18131
|
+
if (text2.length > 1024 * 1024)
|
|
17492
18132
|
throw new ApiServiceError("payload_too_large", "Request body exceeds 1 MB.", 413);
|
|
17493
18133
|
let deployment;
|
|
17494
18134
|
try {
|
|
17495
|
-
deployment = JSON.parse(
|
|
18135
|
+
deployment = JSON.parse(text2);
|
|
17496
18136
|
} catch {
|
|
17497
18137
|
throw new ApiServiceError("invalid_json", "Request body must be valid JSON.", 400);
|
|
17498
18138
|
}
|
|
@@ -19112,10 +19752,10 @@ function normalizeScheduleExpression(value) {
|
|
|
19112
19752
|
description: `Cron ${expression.replace(/^cron\(|\)$/g, "")}`
|
|
19113
19753
|
};
|
|
19114
19754
|
}
|
|
19115
|
-
function nextScheduleRuns(expression, zone, from = new Date,
|
|
19755
|
+
function nextScheduleRuns(expression, zone, from = new Date, count2 = 5) {
|
|
19116
19756
|
const parsed = normalizeScheduleExpression(expression);
|
|
19117
19757
|
const validatedZone = validateTimezone(zone);
|
|
19118
|
-
const amount = Math.min(20, Math.max(1,
|
|
19758
|
+
const amount = Math.min(20, Math.max(1, count2));
|
|
19119
19759
|
const duration2 = rateMilliseconds(parsed.normalized);
|
|
19120
19760
|
if (duration2)
|
|
19121
19761
|
return Array.from({ length: amount }, (_, index) => new Date(from.getTime() + duration2 * (index + 1)).toISOString());
|
|
@@ -19137,9 +19777,9 @@ function nextScheduleRuns(expression, zone, from = new Date, count = 5) {
|
|
|
19137
19777
|
throw new Error("Schedule produced no runs within the bounded preview horizon.");
|
|
19138
19778
|
return runs;
|
|
19139
19779
|
}
|
|
19140
|
-
function previewSchedule(expression, zone = "UTC", from = new Date,
|
|
19780
|
+
function previewSchedule(expression, zone = "UTC", from = new Date, count2 = 5) {
|
|
19141
19781
|
const parsed = normalizeScheduleExpression(expression);
|
|
19142
|
-
const nextRuns = nextScheduleRuns(parsed.normalized, zone, from,
|
|
19782
|
+
const nextRuns = nextScheduleRuns(parsed.normalized, zone, from, count2);
|
|
19143
19783
|
const notes = parsed.kind === "cron" ? [
|
|
19144
19784
|
"Server cron and EventBridge use the same wall-clock preview; provider-specific extensions are retained only when supported."
|
|
19145
19785
|
] : ["Rate schedules use elapsed-time intervals and are not shifted by DST."];
|
|
@@ -19621,14 +20261,14 @@ class JobService {
|
|
|
19621
20261
|
for (const job2 of this.store.due(at.toISOString())) {
|
|
19622
20262
|
const due = job2.nextRunAt, next = nextScheduleRuns(job2.normalizedExpression, job2.timezone, at, 1)[0];
|
|
19623
20263
|
if (job2.missedRunPolicy === "catch_up") {
|
|
19624
|
-
let cursor3 = due,
|
|
19625
|
-
while (cursor3 <= at.toISOString() &&
|
|
20264
|
+
let cursor3 = due, count2 = 0;
|
|
20265
|
+
while (cursor3 <= at.toISOString() && count2 < 10) {
|
|
19626
20266
|
queued.push(this.enqueue(job2, {
|
|
19627
20267
|
trigger: cursor3 === due ? "scheduled" : "catch_up",
|
|
19628
20268
|
scheduledFor: cursor3
|
|
19629
20269
|
}));
|
|
19630
20270
|
cursor3 = nextScheduleRuns(job2.normalizedExpression, job2.timezone, new Date(cursor3), 1)[0];
|
|
19631
|
-
|
|
20271
|
+
count2++;
|
|
19632
20272
|
}
|
|
19633
20273
|
} else
|
|
19634
20274
|
queued.push(this.enqueue(job2, { trigger: "scheduled", scheduledFor: due }));
|
|
@@ -20441,14 +21081,14 @@ class BackupStore {
|
|
|
20441
21081
|
],
|
|
20442
21082
|
[policy3.retention.monthly, (date) => date.toISOString().slice(0, 7)]
|
|
20443
21083
|
];
|
|
20444
|
-
for (const [
|
|
21084
|
+
for (const [count2, bucket] of buckets) {
|
|
20445
21085
|
const seen = new Set;
|
|
20446
21086
|
for (const item of points) {
|
|
20447
21087
|
const key = bucket(new Date(item.pointInTime));
|
|
20448
21088
|
if (seen.has(key))
|
|
20449
21089
|
continue;
|
|
20450
21090
|
seen.add(key);
|
|
20451
|
-
if (seen.size <= Math.max(0,
|
|
21091
|
+
if (seen.size <= Math.max(0, count2 ?? 0))
|
|
20452
21092
|
protectedIds.add(item.id);
|
|
20453
21093
|
}
|
|
20454
21094
|
}
|
|
@@ -21730,7 +22370,7 @@ var value = (input, key) => {
|
|
|
21730
22370
|
const desired = input.desiredState;
|
|
21731
22371
|
return input[key] ?? (desired && typeof desired === "object" && !Array.isArray(desired) ? desired[key] : undefined);
|
|
21732
22372
|
};
|
|
21733
|
-
var
|
|
22373
|
+
var text2 = (input, key, fallback) => {
|
|
21734
22374
|
const result2 = value(input, key);
|
|
21735
22375
|
return result2 == null ? fallback : String(result2);
|
|
21736
22376
|
};
|
|
@@ -21746,7 +22386,7 @@ var strings2 = (input, key) => {
|
|
|
21746
22386
|
const result2 = value(input, key);
|
|
21747
22387
|
return Array.isArray(result2) ? result2.map(String) : [];
|
|
21748
22388
|
};
|
|
21749
|
-
var snapshotName = (id, input) =>
|
|
22389
|
+
var snapshotName = (id, input) => text2(input, "snapshotId") ?? `${id}-final-${new Date().toISOString().replace(/[^0-9]/g, "").slice(0, 14)}`;
|
|
21750
22390
|
function rdsObservation(instance2) {
|
|
21751
22391
|
if (!instance2)
|
|
21752
22392
|
throw new Error("RDS instance was not found.");
|
|
@@ -21780,19 +22420,19 @@ class AwsRdsTransport {
|
|
|
21780
22420
|
DBInstanceIdentifier: id,
|
|
21781
22421
|
DBInstanceClass: String(input.plan),
|
|
21782
22422
|
Engine: String(input.engine),
|
|
21783
|
-
MasterUsername:
|
|
22423
|
+
MasterUsername: text2(input, "username", "app"),
|
|
21784
22424
|
MasterUserPassword: credential2,
|
|
21785
|
-
DBName:
|
|
22425
|
+
DBName: text2(input, "database"),
|
|
21786
22426
|
AllocatedStorage: number2(input, "storageGb"),
|
|
21787
22427
|
VpcSecurityGroupIds: strings2(input, "securityGroupIds"),
|
|
21788
|
-
DBSubnetGroupName:
|
|
22428
|
+
DBSubnetGroupName: text2(input, "subnetGroup"),
|
|
21789
22429
|
BackupRetentionPeriod: number2(input, "backupRetentionDays") ?? 7,
|
|
21790
22430
|
MultiAZ: boolean2(input, "highAvailability"),
|
|
21791
|
-
EngineVersion:
|
|
22431
|
+
EngineVersion: text2(input, "engineVersion"),
|
|
21792
22432
|
PubliclyAccessible: boolean2(input, "publicExposure") ?? false,
|
|
21793
|
-
StorageType:
|
|
22433
|
+
StorageType: text2(input, "storageType", "gp3"),
|
|
21794
22434
|
StorageEncrypted: true,
|
|
21795
|
-
KmsKeyId:
|
|
22435
|
+
KmsKeyId: text2(input, "kmsKeyId"),
|
|
21796
22436
|
DeletionProtection: true,
|
|
21797
22437
|
Tags: [{ Key: "managed-by", Value: "ts-cloud" }]
|
|
21798
22438
|
});
|
|
@@ -21817,11 +22457,11 @@ class AwsRdsTransport {
|
|
|
21817
22457
|
const targetId = String(input.targetId), result2 = await this.client.restoreDBInstanceFromDBSnapshot({
|
|
21818
22458
|
DBInstanceIdentifier: targetId,
|
|
21819
22459
|
DBSnapshotIdentifier: String(input.backupId),
|
|
21820
|
-
DBInstanceClass:
|
|
21821
|
-
DBSubnetGroupName:
|
|
22460
|
+
DBInstanceClass: text2(input, "plan"),
|
|
22461
|
+
DBSubnetGroupName: text2(input, "subnetGroup"),
|
|
21822
22462
|
MultiAZ: boolean2(input, "highAvailability"),
|
|
21823
22463
|
PubliclyAccessible: false,
|
|
21824
|
-
StorageType:
|
|
22464
|
+
StorageType: text2(input, "storageType"),
|
|
21825
22465
|
VpcSecurityGroupIds: strings2(input, "securityGroupIds"),
|
|
21826
22466
|
DeletionProtection: true,
|
|
21827
22467
|
Tags: [{ Key: "managed-by", Value: "ts-cloud" }]
|
|
@@ -21852,10 +22492,10 @@ class AwsRdsTransport {
|
|
|
21852
22492
|
if (["resize", "version", "rotate", "expose"].includes(action)) {
|
|
21853
22493
|
const result2 = await this.client.modifyDBInstance({
|
|
21854
22494
|
DBInstanceIdentifier: id,
|
|
21855
|
-
DBInstanceClass:
|
|
22495
|
+
DBInstanceClass: text2(input, "plan"),
|
|
21856
22496
|
AllocatedStorage: number2(input, "storageGb"),
|
|
21857
22497
|
MasterUserPassword: action === "rotate" ? credential2 : undefined,
|
|
21858
|
-
EngineVersion:
|
|
22498
|
+
EngineVersion: text2(input, "engineVersion"),
|
|
21859
22499
|
MultiAZ: boolean2(input, "highAvailability"),
|
|
21860
22500
|
PubliclyAccessible: boolean2(input, "publicExposure"),
|
|
21861
22501
|
VpcSecurityGroupIds: strings2(input, "securityGroupIds"),
|
|
@@ -21900,15 +22540,15 @@ class AwsAuroraTransport {
|
|
|
21900
22540
|
await this.client.createDBCluster({
|
|
21901
22541
|
DBClusterIdentifier: id,
|
|
21902
22542
|
Engine: engine,
|
|
21903
|
-
EngineVersion:
|
|
21904
|
-
MasterUsername:
|
|
22543
|
+
EngineVersion: text2(input, "engineVersion"),
|
|
22544
|
+
MasterUsername: text2(input, "username", "app"),
|
|
21905
22545
|
MasterUserPassword: credential2,
|
|
21906
|
-
DatabaseName:
|
|
21907
|
-
DBSubnetGroupName:
|
|
22546
|
+
DatabaseName: text2(input, "database"),
|
|
22547
|
+
DBSubnetGroupName: text2(input, "subnetGroup"),
|
|
21908
22548
|
VpcSecurityGroupIds: strings2(input, "securityGroupIds"),
|
|
21909
22549
|
BackupRetentionPeriod: number2(input, "backupRetentionDays") ?? 7,
|
|
21910
22550
|
StorageEncrypted: true,
|
|
21911
|
-
KmsKeyId:
|
|
22551
|
+
KmsKeyId: text2(input, "kmsKeyId"),
|
|
21912
22552
|
DeletionProtection: true,
|
|
21913
22553
|
ServerlessV2ScalingConfiguration: {
|
|
21914
22554
|
MinCapacity: number2(input, "minCapacity") ?? 0.5,
|
|
@@ -21941,8 +22581,8 @@ class AwsAuroraTransport {
|
|
|
21941
22581
|
DBClusterIdentifier: targetId,
|
|
21942
22582
|
SnapshotIdentifier: String(input.backupId),
|
|
21943
22583
|
Engine: engine,
|
|
21944
|
-
EngineVersion:
|
|
21945
|
-
DBSubnetGroupName:
|
|
22584
|
+
EngineVersion: text2(input, "engineVersion"),
|
|
22585
|
+
DBSubnetGroupName: text2(input, "subnetGroup"),
|
|
21946
22586
|
VpcSecurityGroupIds: strings2(input, "securityGroupIds"),
|
|
21947
22587
|
DeletionProtection: true,
|
|
21948
22588
|
ServerlessV2ScalingConfiguration: {
|
|
@@ -21953,7 +22593,7 @@ class AwsAuroraTransport {
|
|
|
21953
22593
|
await this.client.createDBInstance({
|
|
21954
22594
|
DBInstanceIdentifier: `${targetId}-writer-1`,
|
|
21955
22595
|
DBClusterIdentifier: targetId,
|
|
21956
|
-
DBInstanceClass:
|
|
22596
|
+
DBInstanceClass: text2(input, "plan", "db.serverless"),
|
|
21957
22597
|
Engine: engine,
|
|
21958
22598
|
PubliclyAccessible: false
|
|
21959
22599
|
});
|
|
@@ -21982,7 +22622,7 @@ class AwsAuroraTransport {
|
|
|
21982
22622
|
MinCapacity: number2(input, "minCapacity") ?? 0.5,
|
|
21983
22623
|
MaxCapacity: number2(input, "maxCapacity") ?? 4
|
|
21984
22624
|
},
|
|
21985
|
-
EngineVersion:
|
|
22625
|
+
EngineVersion: text2(input, "engineVersion"),
|
|
21986
22626
|
MasterUserPassword: action === "rotate" ? credential2 : undefined,
|
|
21987
22627
|
ApplyImmediately: boolean2(input, "applyImmediately") ?? true
|
|
21988
22628
|
});
|
|
@@ -22019,10 +22659,10 @@ class AwsElastiCacheTransport {
|
|
|
22019
22659
|
engine: "redis",
|
|
22020
22660
|
cacheNodeType: String(input.plan),
|
|
22021
22661
|
numCacheNodes: number2(input, "nodes") ?? 1,
|
|
22022
|
-
engineVersion:
|
|
22662
|
+
engineVersion: text2(input, "engineVersion"),
|
|
22023
22663
|
port: number2(input, "port"),
|
|
22024
22664
|
securityGroupIds: strings2(input, "securityGroupIds"),
|
|
22025
|
-
subnetGroupName:
|
|
22665
|
+
subnetGroupName: text2(input, "subnetGroup"),
|
|
22026
22666
|
tags: [{ Key: "managed-by", Value: "ts-cloud" }]
|
|
22027
22667
|
});
|
|
22028
22668
|
return {
|
|
@@ -26144,8 +26784,8 @@ class Logger2 {
|
|
|
26144
26784
|
if (!input)
|
|
26145
26785
|
return input;
|
|
26146
26786
|
let out = input;
|
|
26147
|
-
out = out.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_,
|
|
26148
|
-
const label = styles2.underline(styles2.blue(
|
|
26787
|
+
out = out.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, text3, url) => {
|
|
26788
|
+
const label = styles2.underline(styles2.blue(text3));
|
|
26149
26789
|
const absFile = this.toAbsoluteFilePath(url);
|
|
26150
26790
|
if (absFile && this.shouldStyleConsole() && this.supportsHyperlinks()) {
|
|
26151
26791
|
const href = `file://${encodeURI(absFile)}`;
|
|
@@ -28453,20 +29093,20 @@ var init_dist = __esm(async () => {
|
|
|
28453
29093
|
};
|
|
28454
29094
|
config = await loadConfig2();
|
|
28455
29095
|
terminalStyles = {
|
|
28456
|
-
red: (
|
|
28457
|
-
green: (
|
|
28458
|
-
yellow: (
|
|
28459
|
-
blue: (
|
|
28460
|
-
magenta: (
|
|
28461
|
-
cyan: (
|
|
28462
|
-
white: (
|
|
28463
|
-
gray: (
|
|
28464
|
-
bgRed: (
|
|
28465
|
-
bgYellow: (
|
|
28466
|
-
bold: (
|
|
28467
|
-
dim: (
|
|
28468
|
-
italic: (
|
|
28469
|
-
underline: (
|
|
29096
|
+
red: (text3) => `\x1B[31m${text3}\x1B[0m`,
|
|
29097
|
+
green: (text3) => `\x1B[32m${text3}\x1B[0m`,
|
|
29098
|
+
yellow: (text3) => `\x1B[33m${text3}\x1B[0m`,
|
|
29099
|
+
blue: (text3) => `\x1B[34m${text3}\x1B[0m`,
|
|
29100
|
+
magenta: (text3) => `\x1B[35m${text3}\x1B[0m`,
|
|
29101
|
+
cyan: (text3) => `\x1B[36m${text3}\x1B[0m`,
|
|
29102
|
+
white: (text3) => `\x1B[37m${text3}\x1B[0m`,
|
|
29103
|
+
gray: (text3) => `\x1B[90m${text3}\x1B[0m`,
|
|
29104
|
+
bgRed: (text3) => `\x1B[41m${text3}\x1B[0m`,
|
|
29105
|
+
bgYellow: (text3) => `\x1B[43m${text3}\x1B[0m`,
|
|
29106
|
+
bold: (text3) => `\x1B[1m${text3}\x1B[0m`,
|
|
29107
|
+
dim: (text3) => `\x1B[2m${text3}\x1B[0m`,
|
|
29108
|
+
italic: (text3) => `\x1B[3m${text3}\x1B[0m`,
|
|
29109
|
+
underline: (text3) => `\x1B[4m${text3}\x1B[0m`,
|
|
28470
29110
|
reset: "\x1B[0m"
|
|
28471
29111
|
};
|
|
28472
29112
|
styles = terminalStyles;
|
|
@@ -28530,22 +29170,22 @@ var init_dist = __esm(async () => {
|
|
|
28530
29170
|
};
|
|
28531
29171
|
config2 = await loadConfig4();
|
|
28532
29172
|
terminalStyles2 = {
|
|
28533
|
-
red: (
|
|
28534
|
-
green: (
|
|
28535
|
-
yellow: (
|
|
28536
|
-
blue: (
|
|
28537
|
-
magenta: (
|
|
28538
|
-
cyan: (
|
|
28539
|
-
white: (
|
|
28540
|
-
gray: (
|
|
28541
|
-
bgRed: (
|
|
28542
|
-
bgYellow: (
|
|
28543
|
-
bgGray: (
|
|
28544
|
-
bold: (
|
|
28545
|
-
dim: (
|
|
28546
|
-
italic: (
|
|
28547
|
-
underline: (
|
|
28548
|
-
strikethrough: (
|
|
29173
|
+
red: (text3) => `\x1B[31m${text3}\x1B[0m`,
|
|
29174
|
+
green: (text3) => `\x1B[32m${text3}\x1B[0m`,
|
|
29175
|
+
yellow: (text3) => `\x1B[33m${text3}\x1B[0m`,
|
|
29176
|
+
blue: (text3) => `\x1B[34m${text3}\x1B[0m`,
|
|
29177
|
+
magenta: (text3) => `\x1B[35m${text3}\x1B[0m`,
|
|
29178
|
+
cyan: (text3) => `\x1B[36m${text3}\x1B[0m`,
|
|
29179
|
+
white: (text3) => `\x1B[37m${text3}\x1B[0m`,
|
|
29180
|
+
gray: (text3) => `\x1B[90m${text3}\x1B[0m`,
|
|
29181
|
+
bgRed: (text3) => `\x1B[41m${text3}\x1B[0m`,
|
|
29182
|
+
bgYellow: (text3) => `\x1B[43m${text3}\x1B[0m`,
|
|
29183
|
+
bgGray: (text3) => `\x1B[100m${text3}\x1B[0m`,
|
|
29184
|
+
bold: (text3) => `\x1B[1m${text3}\x1B[0m`,
|
|
29185
|
+
dim: (text3) => `\x1B[2m${text3}\x1B[0m`,
|
|
29186
|
+
italic: (text3) => `\x1B[3m${text3}\x1B[0m`,
|
|
29187
|
+
underline: (text3) => `\x1B[4m${text3}\x1B[0m`,
|
|
29188
|
+
strikethrough: (text3) => `\x1B[9m${text3}\x1B[0m`,
|
|
28549
29189
|
reset: "\x1B[0m"
|
|
28550
29190
|
};
|
|
28551
29191
|
styles2 = terminalStyles2;
|
|
@@ -28876,22 +29516,22 @@ class JsonFormatter3 {
|
|
|
28876
29516
|
}
|
|
28877
29517
|
}
|
|
28878
29518
|
var terminalStyles3 = {
|
|
28879
|
-
red: (
|
|
28880
|
-
green: (
|
|
28881
|
-
yellow: (
|
|
28882
|
-
blue: (
|
|
28883
|
-
magenta: (
|
|
28884
|
-
cyan: (
|
|
28885
|
-
white: (
|
|
28886
|
-
gray: (
|
|
28887
|
-
bgRed: (
|
|
28888
|
-
bgYellow: (
|
|
28889
|
-
bgGray: (
|
|
28890
|
-
bold: (
|
|
28891
|
-
dim: (
|
|
28892
|
-
italic: (
|
|
28893
|
-
underline: (
|
|
28894
|
-
strikethrough: (
|
|
29519
|
+
red: (text3) => `\x1B[31m${text3}\x1B[0m`,
|
|
29520
|
+
green: (text3) => `\x1B[32m${text3}\x1B[0m`,
|
|
29521
|
+
yellow: (text3) => `\x1B[33m${text3}\x1B[0m`,
|
|
29522
|
+
blue: (text3) => `\x1B[34m${text3}\x1B[0m`,
|
|
29523
|
+
magenta: (text3) => `\x1B[35m${text3}\x1B[0m`,
|
|
29524
|
+
cyan: (text3) => `\x1B[36m${text3}\x1B[0m`,
|
|
29525
|
+
white: (text3) => `\x1B[37m${text3}\x1B[0m`,
|
|
29526
|
+
gray: (text3) => `\x1B[90m${text3}\x1B[0m`,
|
|
29527
|
+
bgRed: (text3) => `\x1B[41m${text3}\x1B[0m`,
|
|
29528
|
+
bgYellow: (text3) => `\x1B[43m${text3}\x1B[0m`,
|
|
29529
|
+
bgGray: (text3) => `\x1B[100m${text3}\x1B[0m`,
|
|
29530
|
+
bold: (text3) => `\x1B[1m${text3}\x1B[0m`,
|
|
29531
|
+
dim: (text3) => `\x1B[2m${text3}\x1B[0m`,
|
|
29532
|
+
italic: (text3) => `\x1B[3m${text3}\x1B[0m`,
|
|
29533
|
+
underline: (text3) => `\x1B[4m${text3}\x1B[0m`,
|
|
29534
|
+
strikethrough: (text3) => `\x1B[9m${text3}\x1B[0m`,
|
|
28895
29535
|
reset: "\x1B[0m"
|
|
28896
29536
|
};
|
|
28897
29537
|
var styles3 = terminalStyles3;
|
|
@@ -29543,8 +30183,8 @@ class Logger3 {
|
|
|
29543
30183
|
if (!input)
|
|
29544
30184
|
return input;
|
|
29545
30185
|
let out = input;
|
|
29546
|
-
out = out.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_,
|
|
29547
|
-
const label = styles3.underline(styles3.blue(
|
|
30186
|
+
out = out.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, text3, url) => {
|
|
30187
|
+
const label = styles3.underline(styles3.blue(text3));
|
|
29548
30188
|
const absFile = this.toAbsoluteFilePath(url);
|
|
29549
30189
|
if (absFile && this.shouldStyleConsole() && this.supportsHyperlinks()) {
|
|
29550
30190
|
const href = `file://${encodeURI(absFile)}`;
|
|
@@ -38389,15 +39029,15 @@ function findPropertyArray(configText, start, end, property) {
|
|
|
38389
39029
|
const arrayStart = configText.indexOf("[", propertyStart);
|
|
38390
39030
|
return { propertyStart, start: arrayStart, end: findMatching(configText, arrayStart, "[", "]") };
|
|
38391
39031
|
}
|
|
38392
|
-
function findMatching(
|
|
39032
|
+
function findMatching(text3, start, open, close) {
|
|
38393
39033
|
let depth = 0;
|
|
38394
39034
|
let quote2;
|
|
38395
39035
|
let escaped = false;
|
|
38396
39036
|
let lineComment = false;
|
|
38397
39037
|
let blockComment = false;
|
|
38398
|
-
for (let index = start;index <
|
|
38399
|
-
const char =
|
|
38400
|
-
const next =
|
|
39038
|
+
for (let index = start;index < text3.length; index++) {
|
|
39039
|
+
const char = text3[index];
|
|
39040
|
+
const next = text3[index + 1];
|
|
38401
39041
|
if (lineComment) {
|
|
38402
39042
|
if (char === `
|
|
38403
39043
|
`)
|
|
@@ -38646,6 +39286,14 @@ EOF`,
|
|
|
38646
39286
|
`echo "INODEPCT=$(df -Pi / 2>/dev/null | awk 'NR==2{gsub("%","",$5);print $5}')"`,
|
|
38647
39287
|
`echo "NETRX=$(awk -F'[: ]+' 'NR>2 && $2!="lo"{sum+=$3} END{print sum+0}' /proc/net/dev 2>/dev/null)"`,
|
|
38648
39288
|
`echo "NETTX=$(awk -F'[: ]+' 'NR>2 && $2!="lo"{sum+=$11} END{print sum+0}' /proc/net/dev 2>/dev/null)"`,
|
|
39289
|
+
". /var/lib/ts-cloud/bandwidth-state 2>/dev/null || true",
|
|
39290
|
+
'echo "NETRXRATE=${RX_RATE:-0}"',
|
|
39291
|
+
'echo "NETTXRATE=${TX_RATE:-0}"',
|
|
39292
|
+
'echo "NETRXTODAY=${DAY_RX:-0}"',
|
|
39293
|
+
'echo "NETTXTODAY=${DAY_TX:-0}"',
|
|
39294
|
+
'echo "NETRXMONTH=${MONTH_RX:-0}"',
|
|
39295
|
+
'echo "NETTXMONTH=${MONTH_TX:-0}"',
|
|
39296
|
+
'echo "NETMONTHKEY=${MONTH_KEY:-}"',
|
|
38649
39297
|
`echo "PROCESSES=$(ps -e --no-headers 2>/dev/null | awk 'END{print NR+0}')"`,
|
|
38650
39298
|
`echo "UPTIME=$(uptime -p 2>/dev/null | sed 's/^up //' || echo unknown)"`,
|
|
38651
39299
|
`echo "UPTIME_SECONDS=$(cut -d' ' -f1 /proc/uptime 2>/dev/null || echo 0)"`,
|
|
@@ -38734,6 +39382,11 @@ function configuredRegion(config6) {
|
|
|
38734
39382
|
return resolveHetznerLocation(config6);
|
|
38735
39383
|
return config6.project.region ?? "us-east-1";
|
|
38736
39384
|
}
|
|
39385
|
+
function configuredBandwidthBudgetBytes(config6) {
|
|
39386
|
+
const monitoring = config6.infrastructure?.compute?.monitoring;
|
|
39387
|
+
const tb = Number(typeof monitoring === "object" ? monitoring?.alerts?.bandwidthTb ?? 0 : 0);
|
|
39388
|
+
return Number.isFinite(tb) && tb > 0 ? Math.round(tb * 1000 ** 4) : 0;
|
|
39389
|
+
}
|
|
38737
39390
|
function loadLocalState(config6, environment2) {
|
|
38738
39391
|
const statePath2 = join12(process.cwd(), "storage", "cloud", "state", `${config6.project.slug}-${environment2}.json`);
|
|
38739
39392
|
if (!existsSync17(statePath2))
|
|
@@ -39265,10 +39918,18 @@ function resolveConfigOnlyServerDashboardData(config6, environment2) {
|
|
|
39265
39918
|
inodeUsedPct: 0,
|
|
39266
39919
|
networkRxBytes: 0,
|
|
39267
39920
|
networkTxBytes: 0,
|
|
39921
|
+
networkRxBytesPerSec: 0,
|
|
39922
|
+
networkTxBytesPerSec: 0,
|
|
39923
|
+
networkRxBytesToday: 0,
|
|
39924
|
+
networkTxBytesToday: 0,
|
|
39925
|
+
networkRxBytesMonth: 0,
|
|
39926
|
+
networkTxBytesMonth: 0,
|
|
39927
|
+
networkMonthKey: "",
|
|
39268
39928
|
processes: 0,
|
|
39269
39929
|
uptimeSeconds: 0
|
|
39270
39930
|
},
|
|
39271
39931
|
metricsUnavailable: true,
|
|
39932
|
+
bandwidthBudgetBytes: configuredBandwidthBudgetBytes(config6),
|
|
39272
39933
|
services,
|
|
39273
39934
|
servicesDetail: services.map((s) => ({ ...s, since: UNKNOWN, memMb: 0, auto: true })),
|
|
39274
39935
|
backup: configuredBackup(config6),
|
|
@@ -39304,6 +39965,7 @@ function resolveConfigOnlyServerDashboardData(config6, environment2) {
|
|
|
39304
39965
|
};
|
|
39305
39966
|
out.diagnostics = diagnosticChecks(config6, out);
|
|
39306
39967
|
out.activity = activityFeed(out);
|
|
39968
|
+
out.topology = buildServerTopology(out, { project: config6.project.name, environment: environment2 });
|
|
39307
39969
|
return out;
|
|
39308
39970
|
}
|
|
39309
39971
|
async function resolveServerDashboardData(config6, environment2, options = {}) {
|
|
@@ -39367,6 +40029,13 @@ async function resolveServerDashboardData(config6, environment2, options = {}) {
|
|
|
39367
40029
|
inodeUsedPct: num(parsed.INODEPCT),
|
|
39368
40030
|
networkRxBytes: num(parsed.NETRX),
|
|
39369
40031
|
networkTxBytes: num(parsed.NETTX),
|
|
40032
|
+
networkRxBytesPerSec: num(parsed.NETRXRATE),
|
|
40033
|
+
networkTxBytesPerSec: num(parsed.NETTXRATE),
|
|
40034
|
+
networkRxBytesToday: num(parsed.NETRXTODAY),
|
|
40035
|
+
networkTxBytesToday: num(parsed.NETTXTODAY),
|
|
40036
|
+
networkRxBytesMonth: num(parsed.NETRXMONTH),
|
|
40037
|
+
networkTxBytesMonth: num(parsed.NETTXMONTH),
|
|
40038
|
+
networkMonthKey: parsed.NETMONTHKEY || "",
|
|
39370
40039
|
processes: num(parsed.PROCESSES),
|
|
39371
40040
|
uptimeSeconds: num(parsed.UPTIME_SECONDS)
|
|
39372
40041
|
};
|
|
@@ -39485,6 +40154,7 @@ async function resolveServerDashboardData(config6, environment2, options = {}) {
|
|
|
39485
40154
|
out._serverReachable = instanceCount > 0 && !!parsed;
|
|
39486
40155
|
out.diagnostics = diagnosticChecks(config6, out);
|
|
39487
40156
|
out.activity = activityFeed(out);
|
|
40157
|
+
out.topology = buildServerTopology(out, { project: config6.project.name, environment: environment2 });
|
|
39488
40158
|
return out;
|
|
39489
40159
|
}
|
|
39490
40160
|
|
|
@@ -40619,9 +41289,9 @@ function normalizeUser(raw) {
|
|
|
40619
41289
|
createdAt: typeof raw.createdAt === "string" ? raw.createdAt : undefined
|
|
40620
41290
|
};
|
|
40621
41291
|
}
|
|
40622
|
-
function parseUsersFile(
|
|
41292
|
+
function parseUsersFile(text3) {
|
|
40623
41293
|
try {
|
|
40624
|
-
const parsed = JSON.parse(
|
|
41294
|
+
const parsed = JSON.parse(text3);
|
|
40625
41295
|
if (!Array.isArray(parsed?.users))
|
|
40626
41296
|
return [];
|
|
40627
41297
|
return parsed.users.map(normalizeUser).filter((u) => u !== null);
|
|
@@ -41512,6 +42182,12 @@ function scopeCloudConfig(config6, user) {
|
|
|
41512
42182
|
};
|
|
41513
42183
|
}
|
|
41514
42184
|
|
|
42185
|
+
// src/deploy/dashboard-payload.ts
|
|
42186
|
+
function encodeDashboardPayload(data) {
|
|
42187
|
+
const nonAscii = new RegExp("[\\u007F-\\uFFFF]", "g");
|
|
42188
|
+
return JSON.stringify(data).replace(nonAscii, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`);
|
|
42189
|
+
}
|
|
42190
|
+
|
|
41515
42191
|
// src/deploy/dashboard-site-settings.ts
|
|
41516
42192
|
var MEMBER_EDITABLE_SITE_FIELDS = new Set([
|
|
41517
42193
|
"name",
|
|
@@ -41620,9 +42296,9 @@ class LoginThrottle {
|
|
|
41620
42296
|
const key2 = this.key(username, address);
|
|
41621
42297
|
const entry2 = this.attempts.get(key2);
|
|
41622
42298
|
const stale = !entry2 || now - entry2.last > this.windowMs;
|
|
41623
|
-
const
|
|
41624
|
-
const next = { count, last: now };
|
|
41625
|
-
if (
|
|
42299
|
+
const count2 = stale ? 1 : entry2.count + 1;
|
|
42300
|
+
const next = { count: count2, last: now };
|
|
42301
|
+
if (count2 >= this.maxAttempts)
|
|
41626
42302
|
next.until = now + this.lockoutMs;
|
|
41627
42303
|
this.attempts.set(key2, next);
|
|
41628
42304
|
}
|
|
@@ -41659,14 +42335,14 @@ function addFirewallPort(configText, port, existing = []) {
|
|
|
41659
42335
|
function removeFirewallPort(configText, port, existing = []) {
|
|
41660
42336
|
return setFirewallPorts({ configText, ports: normalizePorts(existing.filter((p) => p !== port)) });
|
|
41661
42337
|
}
|
|
41662
|
-
function findBlock(
|
|
41663
|
-
const body =
|
|
42338
|
+
function findBlock(text3, start, end, keyword, open) {
|
|
42339
|
+
const body = text3.slice(start, end);
|
|
41664
42340
|
const match = new RegExp(`\\b${keyword}\\s*:\\s*\\${open}`).exec(body);
|
|
41665
42341
|
if (!match)
|
|
41666
42342
|
return null;
|
|
41667
42343
|
const propStart = start + match.index;
|
|
41668
|
-
const openIdx =
|
|
41669
|
-
return { propStart, open: openIdx, close: findMatching(
|
|
42344
|
+
const openIdx = text3.indexOf(open, propStart);
|
|
42345
|
+
return { propStart, open: openIdx, close: findMatching(text3, openIdx, open, open === "{" ? "}" : "]") };
|
|
41670
42346
|
}
|
|
41671
42347
|
function renderPortsArray(ports2) {
|
|
41672
42348
|
return `allowedPorts: [${ports2.join(", ")}]`;
|
|
@@ -42310,21 +42986,21 @@ function removeSiteFromCloudConfig(input) {
|
|
|
42310
42986
|
const span = findSiteSpan(input.configText, sites.start, sites.end, name);
|
|
42311
42987
|
if (!span)
|
|
42312
42988
|
throw new Error(`Site '${name}' does not exist in cloud.config.ts`);
|
|
42313
|
-
const
|
|
42989
|
+
const text3 = input.configText;
|
|
42314
42990
|
let start = span.nameStart;
|
|
42315
|
-
while (start > 0 &&
|
|
42316
|
-
` && /\s/.test(
|
|
42991
|
+
while (start > 0 && text3[start - 1] !== `
|
|
42992
|
+
` && /\s/.test(text3[start - 1]))
|
|
42317
42993
|
start--;
|
|
42318
42994
|
let end = span.end + 1;
|
|
42319
|
-
if (
|
|
42995
|
+
if (text3[end] === ",")
|
|
42320
42996
|
end++;
|
|
42321
|
-
while (end <
|
|
42322
|
-
` && /\s/.test(
|
|
42997
|
+
while (end < text3.length && text3[end] !== `
|
|
42998
|
+
` && /\s/.test(text3[end]))
|
|
42323
42999
|
end++;
|
|
42324
|
-
if (
|
|
43000
|
+
if (text3[end] === `
|
|
42325
43001
|
`)
|
|
42326
43002
|
end++;
|
|
42327
|
-
return
|
|
43003
|
+
return text3.slice(0, start) + text3.slice(end);
|
|
42328
43004
|
}
|
|
42329
43005
|
function setSitePropertyInCloudConfig(input) {
|
|
42330
43006
|
const name = normalizeSiteName(input.siteName);
|
|
@@ -42334,22 +43010,22 @@ function setSitePropertyInCloudConfig(input) {
|
|
|
42334
43010
|
const span = findSiteSpan(input.configText, sites.start, sites.end, name);
|
|
42335
43011
|
if (!span)
|
|
42336
43012
|
throw new Error(`Site '${name}' does not exist in cloud.config.ts`);
|
|
42337
|
-
const
|
|
42338
|
-
const braceStart =
|
|
43013
|
+
const text3 = input.configText;
|
|
43014
|
+
const braceStart = text3.indexOf("{", span.nameStart);
|
|
42339
43015
|
const braceEnd = span.end;
|
|
42340
|
-
const body =
|
|
43016
|
+
const body = text3.slice(braceStart + 1, braceEnd);
|
|
42341
43017
|
const propMatch = new RegExp(`(^|[\\s,{])(${escapeRegExp(input.key)})\\s*:`, "m").exec(body);
|
|
42342
43018
|
if (propMatch) {
|
|
42343
43019
|
const keyStart = braceStart + 1 + propMatch.index + propMatch[1].length;
|
|
42344
|
-
const colon =
|
|
42345
|
-
const valueEnd = scanValueEnd(
|
|
42346
|
-
return `${
|
|
43020
|
+
const colon = text3.indexOf(":", keyStart);
|
|
43021
|
+
const valueEnd = scanValueEnd(text3, colon + 1, braceEnd);
|
|
43022
|
+
return `${text3.slice(0, keyStart)}${input.key}: ${input.valueText}${text3.slice(valueEnd)}`;
|
|
42347
43023
|
}
|
|
42348
|
-
const before =
|
|
43024
|
+
const before = text3.slice(0, braceEnd).trimEnd();
|
|
42349
43025
|
const sep5 = before.endsWith("{") || before.endsWith(",") ? "" : ",";
|
|
42350
43026
|
return `${before}${sep5}
|
|
42351
43027
|
${input.key}: ${input.valueText},
|
|
42352
|
-
${
|
|
43028
|
+
${text3.slice(braceEnd)}`;
|
|
42353
43029
|
}
|
|
42354
43030
|
function renderSslValue(ssl) {
|
|
42355
43031
|
if (ssl === false)
|
|
@@ -42392,15 +43068,15 @@ ${entries.map(([from, to]) => ` '${escapeSingle(from)}': '${escapeSingle(
|
|
|
42392
43068
|
`)}
|
|
42393
43069
|
}`;
|
|
42394
43070
|
}
|
|
42395
|
-
function scanValueEnd(
|
|
43071
|
+
function scanValueEnd(text3, start, limit2) {
|
|
42396
43072
|
let i = start;
|
|
42397
|
-
while (i < limit2 && /\s/.test(
|
|
43073
|
+
while (i < limit2 && /\s/.test(text3[i]))
|
|
42398
43074
|
i++;
|
|
42399
43075
|
let depth = 0;
|
|
42400
43076
|
let quote2;
|
|
42401
43077
|
let escaped = false;
|
|
42402
43078
|
for (;i < limit2; i++) {
|
|
42403
|
-
const c =
|
|
43079
|
+
const c = text3[i];
|
|
42404
43080
|
if (quote2) {
|
|
42405
43081
|
if (escaped) {
|
|
42406
43082
|
escaped = false;
|
|
@@ -42508,15 +43184,15 @@ function hasSite(configText, start, end, siteName) {
|
|
|
42508
43184
|
function escapeRegExp(value2) {
|
|
42509
43185
|
return value2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
42510
43186
|
}
|
|
42511
|
-
function findMatchingBrace(
|
|
43187
|
+
function findMatchingBrace(text3, start) {
|
|
42512
43188
|
let depth = 0;
|
|
42513
43189
|
let quote2;
|
|
42514
43190
|
let escaped = false;
|
|
42515
43191
|
let lineComment = false;
|
|
42516
43192
|
let blockComment = false;
|
|
42517
|
-
for (let index = start;index <
|
|
42518
|
-
const char =
|
|
42519
|
-
const next =
|
|
43193
|
+
for (let index = start;index < text3.length; index++) {
|
|
43194
|
+
const char = text3[index];
|
|
43195
|
+
const next = text3[index + 1];
|
|
42520
43196
|
if (lineComment) {
|
|
42521
43197
|
if (char === `
|
|
42522
43198
|
`)
|
|
@@ -42949,6 +43625,12 @@ async function collectNow(context) {
|
|
|
42949
43625
|
["host.disk.inode_used_percent", Number(metrics.inodeUsedPct), "percent"],
|
|
42950
43626
|
["host.network.rx", Number(metrics.networkRxBytes), "bytes"],
|
|
42951
43627
|
["host.network.tx", Number(metrics.networkTxBytes), "bytes"],
|
|
43628
|
+
["host.network.rx_rate", Number(metrics.networkRxBytesPerSec), "bytes_per_second"],
|
|
43629
|
+
["host.network.tx_rate", Number(metrics.networkTxBytesPerSec), "bytes_per_second"],
|
|
43630
|
+
["host.network.rx_today", Number(metrics.networkRxBytesToday), "bytes"],
|
|
43631
|
+
["host.network.tx_today", Number(metrics.networkTxBytesToday), "bytes"],
|
|
43632
|
+
["host.network.rx_month", Number(metrics.networkRxBytesMonth), "bytes"],
|
|
43633
|
+
["host.network.tx_month", Number(metrics.networkTxBytesMonth), "bytes"],
|
|
42952
43634
|
["host.processes", Number(metrics.processes), "count"],
|
|
42953
43635
|
["host.uptime", Number(metrics.uptimeSeconds), "seconds"]
|
|
42954
43636
|
];
|
|
@@ -43009,6 +43691,24 @@ async function collectNow(context) {
|
|
|
43009
43691
|
message: error2 instanceof Error ? error2.message : String(error2)
|
|
43010
43692
|
});
|
|
43011
43693
|
}
|
|
43694
|
+
{
|
|
43695
|
+
const monitoring = context.config.infrastructure?.compute?.monitoring;
|
|
43696
|
+
const endpoints = typeof monitoring === "object" ? monitoring?.egressEndpoints : undefined;
|
|
43697
|
+
const egress = await collectEgressMetrics(endpoints, { now: now.toISOString() });
|
|
43698
|
+
errors.push(...egress.errors);
|
|
43699
|
+
for (const metric of egress.metrics)
|
|
43700
|
+
records.push({
|
|
43701
|
+
...scope2,
|
|
43702
|
+
id: id(metric.attributes.source, metric.name, metric.timestamp),
|
|
43703
|
+
kind: "metric",
|
|
43704
|
+
source: `egress:${metric.attributes.source}`,
|
|
43705
|
+
name: metric.name,
|
|
43706
|
+
timestamp: metric.timestamp,
|
|
43707
|
+
value: metric.value,
|
|
43708
|
+
unit: metric.unit,
|
|
43709
|
+
attributes: metric.attributes
|
|
43710
|
+
});
|
|
43711
|
+
}
|
|
43012
43712
|
if (!context.lightweight)
|
|
43013
43713
|
try {
|
|
43014
43714
|
const inventory2 = await resolveRuntimeInventory(context.config, context.environment);
|
|
@@ -43343,7 +44043,7 @@ function dashboardMutationAuditPayload(method, path, input, status, durationMs)
|
|
|
43343
44043
|
};
|
|
43344
44044
|
}
|
|
43345
44045
|
var DUMMY_HASH = hashPassword("ts-cloud-dummy-password");
|
|
43346
|
-
function
|
|
44046
|
+
function text3(data, status = 200) {
|
|
43347
44047
|
return new Response(data, {
|
|
43348
44048
|
status,
|
|
43349
44049
|
headers: { "content-type": "text/plain; charset=utf-8" }
|
|
@@ -43387,8 +44087,8 @@ async function loadLocalEnv(cwd) {
|
|
|
43387
44087
|
for (const file of candidates) {
|
|
43388
44088
|
if (!existsSync20(file))
|
|
43389
44089
|
continue;
|
|
43390
|
-
const
|
|
43391
|
-
for (const line of
|
|
44090
|
+
const text4 = await readFile2(file, "utf8").catch(() => "");
|
|
44091
|
+
for (const line of text4.split(`
|
|
43392
44092
|
`)) {
|
|
43393
44093
|
const trimmed = line.trim();
|
|
43394
44094
|
if (!trimmed || trimmed.startsWith("#") || !trimmed.includes("="))
|
|
@@ -43614,12 +44314,19 @@ async function resolveLiveDashboardData(config6, environment2, options = {}) {
|
|
|
43614
44314
|
function preserveDashboardSiteSnapshot(previous, next) {
|
|
43615
44315
|
if (!previous)
|
|
43616
44316
|
return next;
|
|
43617
|
-
|
|
44317
|
+
const merged = {
|
|
43618
44318
|
...next,
|
|
43619
44319
|
...Array.isArray(previous.sites) ? { sites: previous.sites } : {},
|
|
43620
44320
|
...Array.isArray(previous.sitesDetail) ? { sitesDetail: previous.sitesDetail } : {},
|
|
43621
44321
|
...Array.isArray(previous.siteHealth) ? { siteHealth: previous.siteHealth } : {}
|
|
43622
44322
|
};
|
|
44323
|
+
if (merged.mode !== "serverless" && Array.isArray(merged.sites)) {
|
|
44324
|
+
merged.topology = buildServerTopology(merged, {
|
|
44325
|
+
project: merged.project?.name,
|
|
44326
|
+
environment: merged.environment
|
|
44327
|
+
});
|
|
44328
|
+
}
|
|
44329
|
+
return merged;
|
|
43623
44330
|
}
|
|
43624
44331
|
function resolveUiSourceDir(cwd) {
|
|
43625
44332
|
const candidates = [
|
|
@@ -43651,7 +44358,7 @@ async function buildLiveUi(cwd, data) {
|
|
|
43651
44358
|
stderr: "pipe",
|
|
43652
44359
|
env: {
|
|
43653
44360
|
...process.env,
|
|
43654
|
-
TSCLOUD_DASHBOARD_DATA:
|
|
44361
|
+
TSCLOUD_DASHBOARD_DATA: encodeDashboardPayload(data)
|
|
43655
44362
|
}
|
|
43656
44363
|
});
|
|
43657
44364
|
const [stdout, stderr, exitCode] = await Promise.all([
|
|
@@ -43862,6 +44569,7 @@ var PAGE_CAPABILITIES = {
|
|
|
43862
44569
|
"/server/actions": "runtime:restart",
|
|
43863
44570
|
"/server/database": "data:read",
|
|
43864
44571
|
"/server/firewall": "fleet:read",
|
|
44572
|
+
"/infrastructure/topology": "fleet:read",
|
|
43865
44573
|
"/server/security": "security:read",
|
|
43866
44574
|
"/security": "security:read",
|
|
43867
44575
|
"/integrations": "sources:read",
|
|
@@ -43934,10 +44642,10 @@ async function serveStatic(uiRoot, pathname) {
|
|
|
43934
44642
|
status: 404,
|
|
43935
44643
|
headers: { "content-type": "text/html; charset=utf-8" }
|
|
43936
44644
|
});
|
|
43937
|
-
return
|
|
44645
|
+
return text3("Not found", 404);
|
|
43938
44646
|
}
|
|
43939
44647
|
if (statSync6(file).isDirectory())
|
|
43940
|
-
return
|
|
44648
|
+
return text3("Not found", 404);
|
|
43941
44649
|
const body = await readFile2(file);
|
|
43942
44650
|
const type = contentTypes[extname3(file)] ?? "application/octet-stream";
|
|
43943
44651
|
return new Response(body, { headers: { "content-type": type } });
|
|
@@ -46101,7 +46809,7 @@ This link expires in one hour and can be used once.`
|
|
|
46101
46809
|
return json15({ ok: false, error: "The web terminal is disabled." }, 403);
|
|
46102
46810
|
if (activeServer.upgrade(req))
|
|
46103
46811
|
return;
|
|
46104
|
-
return
|
|
46812
|
+
return text3("WebSocket upgrade failed", 400);
|
|
46105
46813
|
}
|
|
46106
46814
|
if (url.pathname === "/api/health") {
|
|
46107
46815
|
const storage = controlPlane.store.health();
|
|
@@ -47055,9 +47763,9 @@ This replacement link expires ${created.invitation.expiresAt} and can be used on
|
|
|
47055
47763
|
return json15({ ok: false, error: "Port must be a number between 1 and 65535." }, 422);
|
|
47056
47764
|
if (typeof body.domain === "string" && body.domain.trim() && !isValidHostname(body.domain.trim()))
|
|
47057
47765
|
return json15({ ok: false, error: `Domain '${body.domain.trim()}' is not a valid hostname.` }, 422);
|
|
47058
|
-
let
|
|
47766
|
+
let text4 = await readFile2(configPath, "utf8");
|
|
47059
47767
|
const set = (key2, valueText) => {
|
|
47060
|
-
|
|
47768
|
+
text4 = setSitePropertyInCloudConfig({ configText: text4, siteName: name, key: key2, valueText });
|
|
47061
47769
|
};
|
|
47062
47770
|
if (body.ssl !== undefined) {
|
|
47063
47771
|
set("ssl", renderSslValue(body.ssl));
|
|
@@ -47086,7 +47794,7 @@ This replacement link expires ${created.invitation.expiresAt} and can be used on
|
|
|
47086
47794
|
set("port", String(Number(body.port)));
|
|
47087
47795
|
existing.port = Number(body.port);
|
|
47088
47796
|
}
|
|
47089
|
-
await writeFile4(configPath,
|
|
47797
|
+
await writeFile4(configPath, text4);
|
|
47090
47798
|
latestData = await resolveLiveDashboardData(config6, environment2);
|
|
47091
47799
|
latestDataByEnvironment.set(environment2, latestData);
|
|
47092
47800
|
return json15({ ok: true, configPath, site: name, data: latestData });
|
|
@@ -49599,7 +50307,7 @@ data: ${JSON.stringify({ cursor: position, at: new Date().toISOString() })}
|
|
|
49599
50307
|
});
|
|
49600
50308
|
const uiRoot = await uiRootFor(user, environment2);
|
|
49601
50309
|
if (!uiRoot)
|
|
49602
|
-
return
|
|
50310
|
+
return text3("ts-cloud dashboard UI is unavailable.", 503);
|
|
49603
50311
|
return serveStatic(uiRoot, url.pathname);
|
|
49604
50312
|
} catch (error2) {
|
|
49605
50313
|
if (options.verbose)
|
|
@@ -49657,4 +50365,4 @@ data: ${JSON.stringify({ cursor: position, at: new Date().toISOString() })}
|
|
|
49657
50365
|
};
|
|
49658
50366
|
return { server, url: `http://${host2}:${server.port}/` };
|
|
49659
50367
|
}
|
|
49660
|
-
export { defaultConfig4 as defaultConfig, getConfig, loadCloudConfig, config5 as config, encodeBase32, decodeBase32, hotp, totp, verifyTotp, matchTotpCounter, totpUri, AUTH_SESSION_IDLE_TTL_MS, AUTH_SESSION_ABSOLUTE_TTL_MS, AUTH_ACTION_TOKEN_TTL_MS, AUTH_MFA_CHALLENGE_TTL_MS, AUTH_OIDC_TRANSACTION_TTL_MS, AuthenticationStore, sendAuthenticationEmail, authEncryptionKeyFile, resolveAuthEncryptionKey, sanitizeOidcReturnPath, discoverOidcProvider, beginOidcAuthorization, completeOidcAuthorization, CONTROL_PLANE_SCHEMA_VERSION, controlPlaneMigrations, AUTHORIZATION_CAPABILITIES, roleCapabilities, scopeContains, authorizeOrganization, effectiveCapabilities, OptimisticConcurrencyError, InvalidOperationTransitionError, UnsupportedSchemaVersionError, controlPlaneDatabaseFile, MAX_CONTROL_PLANE_JSON_BYTES, MAX_CONTROL_PLANE_ERROR_BYTES, sanitizeControlPlaneValue, ControlPlaneStore, searchControlPlane, API_TOKEN_DEFAULT_TTL_MS, API_TOKEN_MAX_TTL_MS, API_IDEMPOTENCY_TTL_MS, AutomationIdentityStore, TsCloudApiError, TsCloudClient, parseCompose, exportCompose, diffCompose, listComposeTemplates, getComposeTemplate, renderComposeTemplate, parseComposeCatalog, planComposeTemplateUpgrade, composeProjectName, buildComposeRuntimeCommand, buildComposeScaleCommand, buildComposeLogsCommand, buildComposeShellCommand, QueueCancellationError, QueueTimeoutError, RetryableOperationError, DurableOperationQueue, DurableQueueWorker, PreviewEnvironmentStore, PreviewEnvironmentService, DEPLOYMENT_QUEUE_KINDS, resolveQueuedDeploymentCommand, createDeploymentQueueHandlers, unsupportedVolumeCapabilities, volumeCapabilities, validateMountPath, validateAttachment, VolumeService, createVolumeQueueHandlers, DockerNamedVolumeDriver, ServerPathVolumeDriver, CloudBlockVolumeDriver, VolumeStore, synchronizeComposeVolumes, completeComposeVolumeDeletion, ComposeApplicationStore, ComposeApplicationService, releaseStrategyCapabilities, assertReleaseStrategy, ReleaseStore, ReleaseService, releaseTrafficPlan, activateImmutableRelease, rollbackImmutableRelease, RELEASE_QUEUE_KINDS, createReleaseQueueHandlers, SourceConnectionStore, SourceProviderError, GithubSourceAdapter, GitlabSourceAdapter, BitbucketSourceAdapter, GiteaSourceAdapter, createSourceAdapter, normalizeSourceEvent, processSourceWebhook, webhookEndpoint, discoverGitRefs, cloneSourceBinding, testSourceConnection, syncSourceRepositories, listSourceReferences, reconcileSourceWebhook, removeSourceWebhook, API_VERSION, openApiDocument, ApiServiceError, requestHash, AutomationApiService, createApiV1Handler, SecurityPostureStore, SECRET_PATTERNS, PreDeployScanner, scanForSecrets, formatScanResults, SecurityScannerRunner, SecretFindingScanner, TrivyImageScanner, AwsIamCapabilityScanner, generateCycloneDxSbom, generateImageSbom, attachSbomToRelease, attachVulnerabilitySummary, createReleaseProvenance, attachProvenanceToRelease, verifyArtifactSignature, securityScope, ensureDefaultSecurityPolicies, recordPreDeploySecretScan, recordSkippedSecretScan, recordDashboardHostPosture, productionChangeReview, secureContainerRelease, runtimeId2 as runtimeId, unsupportedCapabilities, capabilities2 as capabilities, normalizeRuntimeStatus, redactRuntimeConfig, ageSeconds, bytes, discoverRuntimeInventory, ecsWorkloads, lambdaWorkloads, parseDockerInspect, dockerWorkloads, DockerDiscoveryAdapter, parseSystemdRecords, systemdWorkloads, SystemdDiscoveryAdapter, EcsRuntimeAdapter, LambdaRuntimeAdapter, createRuntimeAdapters, resolveRuntimeInventory, authorizeRuntimePath, DIAGNOSTIC_PRESETS, RuntimeOperationService, RuntimeStreamRegistry, DEFAULT_TELEMETRY_POLICY, normalizeTelemetryPolicy, telemetryPolicyKey, loadTelemetryPolicy, saveTelemetryPolicy, telemetryEstimatedMonthlyCost, redactTelemetryText, redactTelemetryValue, pathTemplate, telemetryCursor, telemetryPercentile, telemetryBucketLabel, TelemetryStore, AlertStore, AlertEvaluator, isQuietHours, NotificationRouter, HealthCheckRunner, evaluateTelemetryAlertRules, normalizeScheduleExpression, nextScheduleRuns, previewSchedule, jobProviderCapability, renderServerCron, eventBridgeScheduleInput, reconcileJobObservation, desiredState, ServerCronJobAdapter, EventBridgeJobAdapter, JobProviderReconciler, synchronizeConfiguredJobs, JobService, createJobQueueHandlers, JobStore, dataServiceCapabilities, DataServiceStore, connectionGuidance, DataServiceLifecycle, createDataServiceQueueHandlers, AwsRdsDataAdapter, AwsAuroraDataAdapter, AwsElastiCacheDataAdapter, ServerDataAdapter, ContainerDataAdapter, AwsRdsTransport, AwsAuroraTransport, AwsElastiCacheTransport, BunDockerRuntime, DockerDataTransport, EncryptedDataSecretStore, SystemFleetSshTransport, SshFleetDriver, createFleetQueueHandlers, FleetStore, FleetService, zeroCapacity, capacity, PlacementStore, PlacementService, RemoteBuildService, createRemoteBuildQueueHandlers, TransportRemoteBuildDriver, createServerBuildDriver, createEcsBuildDriver, createAsgBuildDriver, RegionStore, RegionService, createRegionQueueHandlers, AwsRegionalDriver, UnavailableRegionalDriver, canonicalJson, manifestDigest, documentDigest, publicKeyFingerprint, verifyUpdateSignature, updateCompatibility, MaintenanceStore, maintenanceWindowOpen, MaintenanceService, createMaintenanceQueueHandlers, TransportPlatformMaintenanceDriver, ControlPlaneCleanupDriver, TransportDisasterRecoveryDriver, ensureDashboardActor, initializeDashboardControlPlane, synchronizeDashboardUsers, trackDashboardOperation, dashboardPageRoutes, resolveLegacyDashboardRoute, routesForDashboard, deploySite, createStaticApiOriginDependencies, deployStaticApiOrigin, verifyStaticApiOrigin, estimateStaticApiOriginMonthlyCost, createExistingStaticFullStackDependencies, generateExistingStaticFullStackTemplate, deployExistingStaticFullStack, estimateExistingStaticFullStackMonthlyCost, hashContainerContext, createContainerImageDependencies, buildAndPushContainerImage, infraEnvFromOutputs, buildFunctionEnv, deployServerlessApp, redeployServerlessApp, rollbackServerlessApp, setMaintenance, runRemoteCommand, resolveDashboardData, validateBackupDestination, BackupStore, encryptBackup, decryptBackup, backupCredentialStatus, S3BackupDestinationAdapter, BackupCoordinator, createBackupQueueHandlers, AwsDatabaseBackupSource, compressBackup, decompressBackup, BunFilesystemArchiveRuntime, FilesystemBackupSource, LogicalDatabaseBackupSource, BunDockerVolumeRuntime, DockerVolumeBackupSource, ControlPlaneBackupSource, AwsInfrastructureBackupSource, ConfigurationStore, parseDotenv, serializeDotenv, LocalEncryptedConfigurationBackend, AwsSecretsManagerConfigurationBackend, AwsSsmConfigurationBackend, ExternalConfigurationBackend, ConfigurationService, synchronizeConfiguredConfiguration, sanitizeCloudConfig, dashboardActions, resolveDashboardAction, startLocalDashboardServer };
|
|
50368
|
+
export { defaultConfig4 as defaultConfig, getConfig, loadCloudConfig, config5 as config, encodeBase32, decodeBase32, hotp, totp, verifyTotp, matchTotpCounter, totpUri, AUTH_SESSION_IDLE_TTL_MS, AUTH_SESSION_ABSOLUTE_TTL_MS, AUTH_ACTION_TOKEN_TTL_MS, AUTH_MFA_CHALLENGE_TTL_MS, AUTH_OIDC_TRANSACTION_TTL_MS, AuthenticationStore, sendAuthenticationEmail, authEncryptionKeyFile, resolveAuthEncryptionKey, sanitizeOidcReturnPath, discoverOidcProvider, beginOidcAuthorization, completeOidcAuthorization, CONTROL_PLANE_SCHEMA_VERSION, controlPlaneMigrations, AUTHORIZATION_CAPABILITIES, roleCapabilities, scopeContains, authorizeOrganization, effectiveCapabilities, OptimisticConcurrencyError, InvalidOperationTransitionError, UnsupportedSchemaVersionError, controlPlaneDatabaseFile, MAX_CONTROL_PLANE_JSON_BYTES, MAX_CONTROL_PLANE_ERROR_BYTES, sanitizeControlPlaneValue, ControlPlaneStore, searchControlPlane, API_TOKEN_DEFAULT_TTL_MS, API_TOKEN_MAX_TTL_MS, API_IDEMPOTENCY_TTL_MS, AutomationIdentityStore, TsCloudApiError, TsCloudClient, parseCompose, exportCompose, diffCompose, listComposeTemplates, getComposeTemplate, renderComposeTemplate, parseComposeCatalog, planComposeTemplateUpgrade, composeProjectName, buildComposeRuntimeCommand, buildComposeScaleCommand, buildComposeLogsCommand, buildComposeShellCommand, QueueCancellationError, QueueTimeoutError, RetryableOperationError, DurableOperationQueue, DurableQueueWorker, PreviewEnvironmentStore, PreviewEnvironmentService, DEPLOYMENT_QUEUE_KINDS, resolveQueuedDeploymentCommand, createDeploymentQueueHandlers, unsupportedVolumeCapabilities, volumeCapabilities, validateMountPath, validateAttachment, VolumeService, createVolumeQueueHandlers, DockerNamedVolumeDriver, ServerPathVolumeDriver, CloudBlockVolumeDriver, VolumeStore, synchronizeComposeVolumes, completeComposeVolumeDeletion, ComposeApplicationStore, ComposeApplicationService, releaseStrategyCapabilities, assertReleaseStrategy, ReleaseStore, ReleaseService, releaseTrafficPlan, activateImmutableRelease, rollbackImmutableRelease, RELEASE_QUEUE_KINDS, createReleaseQueueHandlers, SourceConnectionStore, SourceProviderError, GithubSourceAdapter, GitlabSourceAdapter, BitbucketSourceAdapter, GiteaSourceAdapter, createSourceAdapter, normalizeSourceEvent, processSourceWebhook, webhookEndpoint, discoverGitRefs, cloneSourceBinding, testSourceConnection, syncSourceRepositories, listSourceReferences, reconcileSourceWebhook, removeSourceWebhook, API_VERSION, openApiDocument, ApiServiceError, requestHash, AutomationApiService, createApiV1Handler, SecurityPostureStore, SECRET_PATTERNS, PreDeployScanner, scanForSecrets, formatScanResults, SecurityScannerRunner, SecretFindingScanner, TrivyImageScanner, AwsIamCapabilityScanner, generateCycloneDxSbom, generateImageSbom, attachSbomToRelease, attachVulnerabilitySummary, createReleaseProvenance, attachProvenanceToRelease, verifyArtifactSignature, securityScope, ensureDefaultSecurityPolicies, recordPreDeploySecretScan, recordSkippedSecretScan, recordDashboardHostPosture, productionChangeReview, secureContainerRelease, runtimeId2 as runtimeId, unsupportedCapabilities, capabilities2 as capabilities, normalizeRuntimeStatus, redactRuntimeConfig, ageSeconds, bytes, discoverRuntimeInventory, ecsWorkloads, lambdaWorkloads, parseDockerInspect, dockerWorkloads, DockerDiscoveryAdapter, parseSystemdRecords, systemdWorkloads, SystemdDiscoveryAdapter, EcsRuntimeAdapter, LambdaRuntimeAdapter, createRuntimeAdapters, resolveRuntimeInventory, authorizeRuntimePath, DIAGNOSTIC_PRESETS, RuntimeOperationService, RuntimeStreamRegistry, DEFAULT_TELEMETRY_POLICY, normalizeTelemetryPolicy, telemetryPolicyKey, loadTelemetryPolicy, saveTelemetryPolicy, telemetryEstimatedMonthlyCost, redactTelemetryText, redactTelemetryValue, pathTemplate, telemetryCursor, telemetryPercentile, telemetryBucketLabel, TelemetryStore, AlertStore, AlertEvaluator, isQuietHours, NotificationRouter, HealthCheckRunner, evaluateTelemetryAlertRules, normalizeScheduleExpression, nextScheduleRuns, previewSchedule, jobProviderCapability, renderServerCron, eventBridgeScheduleInput, reconcileJobObservation, desiredState, ServerCronJobAdapter, EventBridgeJobAdapter, JobProviderReconciler, synchronizeConfiguredJobs, JobService, createJobQueueHandlers, JobStore, dataServiceCapabilities, DataServiceStore, connectionGuidance, DataServiceLifecycle, createDataServiceQueueHandlers, AwsRdsDataAdapter, AwsAuroraDataAdapter, AwsElastiCacheDataAdapter, ServerDataAdapter, ContainerDataAdapter, AwsRdsTransport, AwsAuroraTransport, AwsElastiCacheTransport, BunDockerRuntime, DockerDataTransport, EncryptedDataSecretStore, SystemFleetSshTransport, SshFleetDriver, createFleetQueueHandlers, FleetStore, FleetService, zeroCapacity, capacity, PlacementStore, PlacementService, RemoteBuildService, createRemoteBuildQueueHandlers, TransportRemoteBuildDriver, createServerBuildDriver, createEcsBuildDriver, createAsgBuildDriver, RegionStore, RegionService, createRegionQueueHandlers, AwsRegionalDriver, UnavailableRegionalDriver, canonicalJson, manifestDigest, documentDigest, publicKeyFingerprint, verifyUpdateSignature, updateCompatibility, MaintenanceStore, maintenanceWindowOpen, MaintenanceService, createMaintenanceQueueHandlers, TransportPlatformMaintenanceDriver, ControlPlaneCleanupDriver, TransportDisasterRecoveryDriver, ensureDashboardActor, initializeDashboardControlPlane, synchronizeDashboardUsers, trackDashboardOperation, dashboardPageRoutes, resolveLegacyDashboardRoute, routesForDashboard, deploySite, createStaticApiOriginDependencies, deployStaticApiOrigin, verifyStaticApiOrigin, estimateStaticApiOriginMonthlyCost, createExistingStaticFullStackDependencies, generateExistingStaticFullStackTemplate, deployExistingStaticFullStack, estimateExistingStaticFullStackMonthlyCost, hashContainerContext, createContainerImageDependencies, buildAndPushContainerImage, infraEnvFromOutputs, buildFunctionEnv, deployServerlessApp, redeployServerlessApp, rollbackServerlessApp, setMaintenance, runRemoteCommand, buildServerTopology, buildServerlessTopology, buildTopology, resolveDashboardData, egressReportMetrics, fetchEgressReport, collectEgressMetrics, validateBackupDestination, BackupStore, encryptBackup, decryptBackup, backupCredentialStatus, S3BackupDestinationAdapter, BackupCoordinator, createBackupQueueHandlers, AwsDatabaseBackupSource, compressBackup, decompressBackup, BunFilesystemArchiveRuntime, FilesystemBackupSource, LogicalDatabaseBackupSource, BunDockerVolumeRuntime, DockerVolumeBackupSource, ControlPlaneBackupSource, AwsInfrastructureBackupSource, ConfigurationStore, parseDotenv, serializeDotenv, LocalEncryptedConfigurationBackend, AwsSecretsManagerConfigurationBackend, AwsSsmConfigurationBackend, ExternalConfigurationBackend, ConfigurationService, synchronizeConfiguredConfiguration, sanitizeCloudConfig, dashboardActions, resolveDashboardAction, startLocalDashboardServer };
|