@monster-orange/den 0.2.8 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1547 -0
- package/README.md +4 -2
- package/bin/den +2 -1
- package/docs/distributions.md +507 -4
- package/make/den.mk +19 -4
- package/make/journeys.mk +2 -1
- package/make/local_dev.mk +6 -13
- package/make/secrets-1password.mk +18 -0
- package/make/test.mk +1 -1
- package/package.json +9 -2
- package/schemas/den.plugin.v1.json +156 -0
- package/schemas/den.theme.v1.json +269 -0
- package/scripts/auth-configure.ts +16 -8
- package/scripts/heal-config.ts +0 -5
- package/scripts/setup-1password.ts +41 -19
- package/scripts/setup-secrets.ts +67 -101
- package/src/pillars/api/global-setup.ts +1 -1
- package/src/pillars/journeys/sandbox.ts +23 -18
- package/src/pillars/journeys/tools/browser-session.ts +2 -2
- package/src/scripts/setup-default-workspace.ts +2 -2
- package/src/shared/auth/headless-login.ts +1 -1
- package/src/shared/auth/sudo-vault.ts +93 -31
- package/src/shared/auth/token-providers/index.ts +7 -3
- package/src/shared/brand.ts +7 -0
- package/src/shared/config-error.ts +33 -3
- package/src/shared/constants.ts +3 -24
- package/src/shared/den-config.ts +141 -5
- package/src/shared/edge/config.ts +147 -21
- package/src/shared/edge/exec.ts +31 -0
- package/src/shared/edge/included.ts +169 -0
- package/src/shared/edge/render.ts +69 -14
- package/src/shared/edge/runtime.ts +128 -121
- package/src/shared/edge/status.ts +68 -8
- package/src/shared/edge/tls.ts +328 -0
- package/src/shared/edge/types.ts +25 -9
- package/src/shared/environment-manager/default-workspace.ts +16 -1
- package/src/shared/environment-manager/health.ts +73 -38
- package/src/shared/environment-manager/scope.ts +168 -0
- package/src/shared/environment-manager/service-runner.ts +789 -43
- package/src/shared/environment-manager/up-targets.ts +132 -0
- package/src/shared/health/rollup.ts +48 -0
- package/src/shared/open-url.ts +21 -2
- package/src/shared/org-sources.ts +78 -59
- package/src/shared/org.ts +53 -5
- package/src/shared/plugins/manifest.ts +406 -0
- package/src/shared/plugins/store.ts +358 -0
- package/src/shared/process/legacy-adopt.ts +285 -0
- package/src/shared/process/process-group.ts +202 -0
- package/src/shared/process/spawn-streaming.ts +7 -0
- package/src/shared/secrets/doppler.ts +1 -1
- package/src/shared/secrets/index.ts +279 -50
- package/src/shared/secrets/none.ts +56 -0
- package/src/shared/secrets/strategies.ts +202 -0
- package/src/shared/secrets/types.ts +1 -1
- package/src/shared/smoke/git-remote.ts +40 -0
- package/src/shared/stacks/manifest-commands.ts +1 -1
- package/src/shared/stacks/namespace.ts +178 -0
- package/src/shared/stacks/owners.ts +303 -0
- package/src/shared/stacks/render.ts +8 -3
- package/src/shared/stacks/runtime.ts +100 -16
- package/src/shared/stacks/status.ts +63 -12
- package/src/shared/strategies/registry.ts +89 -0
- package/src/shared/theme/contract.ts +538 -0
- package/src/shared/types/index.ts +12 -3
- package/src/ui/cli/create.ts +24 -7
- package/src/ui/cli/doctor-command.ts +131 -0
- package/src/ui/cli/edge-command.ts +56 -10
- package/src/ui/cli/flag-parser.ts +7 -2
- package/src/ui/cli/health-display.ts +25 -0
- package/src/ui/cli/index.ts +548 -60
- package/src/ui/cli/notice.ts +20 -0
- package/src/ui/cli/plugin-command.ts +849 -0
- package/src/ui/cli/stack-command.ts +63 -2
- package/src/ui/cli/test-runner.ts +11 -9
- package/src/ui/cli/theme-command.ts +136 -0
- package/src/ui/cli/web-command.ts +47 -23
- package/src/ui/mcp/api.ts +28 -2
- package/src/ui/mcp/scope.ts +22 -1
- package/src/ui/mcp/tools.ts +50 -7
- package/src/ui/server/appearance-prefs.ts +95 -0
- package/src/ui/server/daemon-reaper.ts +29 -1
- package/src/ui/server/engine-stamp.ts +83 -0
- package/src/ui/server/github-service.ts +23 -44
- package/src/ui/server/graph-page.ts +20 -0
- package/src/ui/server/graph-prefs.ts +44 -1
- package/src/ui/server/http-errors.ts +293 -0
- package/src/ui/server/http-helpers.ts +22 -3
- package/src/ui/server/index.ts +532 -51
- package/src/ui/server/public/appearance.js +531 -0
- package/src/ui/server/public/edge-page.js +248 -0
- package/src/ui/server/public/edge.html +251 -0
- package/src/ui/server/public/environments.html +65 -83
- package/src/ui/server/public/error-page.js +247 -0
- package/src/ui/server/public/error.html +260 -0
- package/src/ui/server/public/graph-model.js +332 -14
- package/src/ui/server/public/graph.css +141 -47
- package/src/ui/server/public/graph.js +223 -44
- package/src/ui/server/public/header.js +396 -600
- package/src/ui/server/public/health-fit.js +248 -0
- package/src/ui/server/public/health-rollup.js +128 -0
- package/src/ui/server/public/i18n/es.js +287 -0
- package/src/ui/server/public/i18n/ja.js +276 -0
- package/src/ui/server/public/index.html +139 -438
- package/src/ui/server/public/local-dev.html +3468 -4044
- package/src/ui/server/public/logs.html +71 -208
- package/src/ui/server/public/panel.js +14 -2
- package/src/ui/server/public/release.html +175 -389
- package/src/ui/server/public/services.html +596 -770
- package/src/ui/server/public/settings-page.js +759 -0
- package/src/ui/server/public/settings-registry.js +521 -0
- package/src/ui/server/public/settings.html +590 -0
- package/src/ui/server/public/shell.js +3869 -0
- package/src/ui/server/public/topbar.js +1413 -0
- package/src/ui/server/public/version-label.js +12 -0
- package/src/ui/server/public/workspace-picker.js +74 -34
- package/src/ui/server/readonly-routes.ts +12 -1
- package/src/ui/server/services-list.ts +94 -17
- package/src/ui/server/settings-distribution.ts +74 -0
- package/src/ui/server/shell-prefs.ts +192 -0
- package/src/ui/server/theme-routes.ts +292 -0
- package/src/ui/server/theme-runtime.ts +407 -0
- package/src/ui/server/ui-assets.ts +16 -1
- package/src/ui/server/ui-language.ts +50 -0
- package/src/ui/server/workspace-routes.ts +45 -3
- package/themes/den/den-theme.json +72 -0
- package/themes/den/favicon.ico +0 -0
- package/themes/den/mark.svg +6 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Edge page (0.3.0-dev.30, PL-SPEC §10): a built-in shell page in the
|
|
3
|
+
* Ship group, in the rail only when /api/edge reports the edge configured
|
|
4
|
+
* (shell.js asks /api/edge?probe=1). Read-only: what `<cli> edge status`
|
|
5
|
+
* and `<cli> edge routes` show, from /api/edge:
|
|
6
|
+
*
|
|
7
|
+
* the container and its ports · the TLS strategy (edge.tls: none,
|
|
8
|
+
* mkcert or files; with its CA, and whether it was set, mapped from the
|
|
9
|
+
* legacy keys, or implicit) · CA trust · the cert and its names · the
|
|
10
|
+
* domains · Den's own routes · the routers in the included dynamic files
|
|
11
|
+
* (`edge include`), each with its source file: Traefik serves them, Den
|
|
12
|
+
* doesn't generate them (a consumer's review) · the routers the stacks
|
|
13
|
+
* wrote (`stack-*.yml`, 0.3.0-dev.32), source "stack" with the file.
|
|
14
|
+
*
|
|
15
|
+
* CA trust reads "not needed: fronted" when edge.publish is false (no host
|
|
16
|
+
* ports: a proxy in front owns browser trust, the BOOT-1 rule), neutral,
|
|
17
|
+
* never a red "not trusted" (0.3.0-dev.32). Under tls `files` the cert
|
|
18
|
+
* names are the ones the included certs should cover (0.3.0-dev.32).
|
|
19
|
+
*
|
|
20
|
+
* No trust/untrust or up/down here: those stay CLI commands.
|
|
21
|
+
*
|
|
22
|
+
* The page header is the prototype's .ph (R20). edgeView(status) is pure
|
|
23
|
+
* (HTML from /api/edge's JSON), so tests need no server.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
function esc(v) {
|
|
27
|
+
return String(v ?? "").replace(
|
|
28
|
+
/[&<>"']/g,
|
|
29
|
+
(c) =>
|
|
30
|
+
({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[
|
|
31
|
+
c
|
|
32
|
+
],
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** A link for a URL the edge serves (http/https only; anything else is text). */
|
|
37
|
+
function link(url, text) {
|
|
38
|
+
return /^https?:\/\//.test(String(url))
|
|
39
|
+
? `<a href="${esc(url)}" target="_blank" rel="noopener noreferrer">${esc(text ?? url)}</a>`
|
|
40
|
+
: esc(text ?? url);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Where the TLS strategy came from, in a line. */
|
|
44
|
+
export function tlsNote(tls) {
|
|
45
|
+
if (!tls) return "";
|
|
46
|
+
if (tls.from === "implicit" || tls.implicit)
|
|
47
|
+
return "implicit: edge.tls is not set (mkcert with a local CA, deprecated)";
|
|
48
|
+
if (tls.from === "legacy")
|
|
49
|
+
return "from the legacy keys (caroot / issue_cert), mapped";
|
|
50
|
+
return "set in edge.tls";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function tile(k, v, s, bad) {
|
|
54
|
+
return (
|
|
55
|
+
`<div class="tile${bad ? " bad" : ""}"><div class="k">${esc(k)}</div>` +
|
|
56
|
+
`<div class="v">${esc(v)}</div><div class="s">${s}</div></div>`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function table(cols, rows, empty) {
|
|
61
|
+
if (!rows.length) return `<p class="none">${esc(empty)}</p>`;
|
|
62
|
+
return (
|
|
63
|
+
`<div class="tablewrap"><table class="svc"><thead><tr>` +
|
|
64
|
+
cols.map((c) => `<th scope="col">${esc(c)}</th>`).join("") +
|
|
65
|
+
`</tr></thead><tbody>` +
|
|
66
|
+
rows
|
|
67
|
+
.map((r) => `<tr>${r.map((c) => `<td>${c}</td>`).join("")}</tr>`)
|
|
68
|
+
.join("") +
|
|
69
|
+
`</tbody></table></div>`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The cert-names card's heading. Under tls `files` Den issues nothing: the
|
|
75
|
+
* names are the ones the included certs should cover (a consumer's nit,
|
|
76
|
+
* 0.3.0-dev.32). mkcert (and none): Den's own cert, issued or planned.
|
|
77
|
+
*/
|
|
78
|
+
export function certHeading(strategy, issued) {
|
|
79
|
+
if (strategy === "files") return "Names the included certs should cover";
|
|
80
|
+
return `Certificate names${issued ? "" : " (planned)"}`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** The page's content (below the header) from /api/edge's JSON. */
|
|
84
|
+
export function edgeView(st) {
|
|
85
|
+
if (!st || st.configured === false)
|
|
86
|
+
return (
|
|
87
|
+
`<div class="empty"><b>No edge configured.</b> ` +
|
|
88
|
+
`${esc(st?.error ?? "")}</div>`
|
|
89
|
+
);
|
|
90
|
+
const tls = st.tls ?? { strategy: "none" };
|
|
91
|
+
const none = tls.strategy === "none";
|
|
92
|
+
const ports = st.ports
|
|
93
|
+
? none
|
|
94
|
+
? `:${st.ports.http} (http only)`
|
|
95
|
+
: `:${st.ports.http} / :${st.ports.https}`
|
|
96
|
+
: "not published";
|
|
97
|
+
// Fronted: no host ports (edge.publish: false). The proxy in front
|
|
98
|
+
// terminates TLS for browsers, so this machine's CA trust is moot.
|
|
99
|
+
const fronted = !st.ports;
|
|
100
|
+
const trust = none
|
|
101
|
+
? "—"
|
|
102
|
+
: fronted
|
|
103
|
+
? "not needed: fronted"
|
|
104
|
+
: st.caTrusted === null || st.caTrusted === undefined
|
|
105
|
+
? "unknown"
|
|
106
|
+
: st.caTrusted
|
|
107
|
+
? "trusted"
|
|
108
|
+
: "not trusted";
|
|
109
|
+
const included = Array.isArray(st.included) ? st.included : [];
|
|
110
|
+
const stacks = Array.isArray(st.stacks) ? st.stacks : [];
|
|
111
|
+
const errors = [
|
|
112
|
+
...(Array.isArray(st.includedErrors) ? st.includedErrors : []),
|
|
113
|
+
...(Array.isArray(st.stackErrors) ? st.stackErrors : []),
|
|
114
|
+
];
|
|
115
|
+
const certNames = Array.isArray(st.certNames) ? st.certNames : [];
|
|
116
|
+
const tiles = [
|
|
117
|
+
tile(
|
|
118
|
+
"Container",
|
|
119
|
+
st.up ? "up" : "down",
|
|
120
|
+
`<span class="mono">${esc(st.container)}</span>: ${esc(st.state)}`,
|
|
121
|
+
!st.up,
|
|
122
|
+
),
|
|
123
|
+
tile(
|
|
124
|
+
"Ports",
|
|
125
|
+
ports,
|
|
126
|
+
`network <span class="mono">${esc(st.network)}</span>`,
|
|
127
|
+
false,
|
|
128
|
+
),
|
|
129
|
+
tile("TLS", tls.strategy, esc(tlsNote(tls)), false),
|
|
130
|
+
tile(
|
|
131
|
+
"CA trust",
|
|
132
|
+
trust,
|
|
133
|
+
none
|
|
134
|
+
? "http only: no CA"
|
|
135
|
+
: fronted
|
|
136
|
+
? "a proxy in front owns browser trust"
|
|
137
|
+
: tls.ca
|
|
138
|
+
? `<span class="mono">${esc(tls.ca)}</span>`
|
|
139
|
+
: "no CA set",
|
|
140
|
+
st.caTrusted === false && !fronted,
|
|
141
|
+
),
|
|
142
|
+
tile(
|
|
143
|
+
"Certificate",
|
|
144
|
+
none ? "—" : st.certIssued ? "issued" : "not issued",
|
|
145
|
+
none
|
|
146
|
+
? "http only: no cert"
|
|
147
|
+
: tls.strategy === "files"
|
|
148
|
+
? "from the included config"
|
|
149
|
+
: `${certNames.length} name${certNames.length === 1 ? "" : "s"}`,
|
|
150
|
+
false,
|
|
151
|
+
),
|
|
152
|
+
];
|
|
153
|
+
const scheme = none ? "http://" : "https://";
|
|
154
|
+
const routes = (st.routes ?? []).map((r) => [
|
|
155
|
+
`<span class="mono">${esc(r.name)}</span>`,
|
|
156
|
+
link(r.url),
|
|
157
|
+
`<span class="mono">${esc(r.upstream)}</span>`,
|
|
158
|
+
`<span class="src">den</span>`,
|
|
159
|
+
]);
|
|
160
|
+
// Like Den's own rows: the URL with its scheme (https:// when the router
|
|
161
|
+
// has TLS or is on websecure), and the upstream URL when its file
|
|
162
|
+
// defines the service's loadBalancer, else the service's name
|
|
163
|
+
// (0.3.0-dev.33, a consumer's dogfood finding).
|
|
164
|
+
const routerRow = (r, src) => {
|
|
165
|
+
const scheme = (r.secure ?? r.tls) ? "https://" : "http://";
|
|
166
|
+
return [
|
|
167
|
+
`<span class="mono">${esc(r.name)}</span>`,
|
|
168
|
+
r.hosts?.length
|
|
169
|
+
? r.hosts.map((h) => link(scheme + h)).join("<br>")
|
|
170
|
+
: `<span class="mono rule">${esc(r.rule)}</span>`,
|
|
171
|
+
`<span class="mono">${esc(r.upstream || r.service)}</span>`,
|
|
172
|
+
src,
|
|
173
|
+
];
|
|
174
|
+
};
|
|
175
|
+
const inc = included.map((r) =>
|
|
176
|
+
routerRow(
|
|
177
|
+
r,
|
|
178
|
+
`<span class="src" title="included dynamic file">${esc(r.file)}</span>`,
|
|
179
|
+
),
|
|
180
|
+
);
|
|
181
|
+
// The stacks' own route files (stack up writes them): source "stack",
|
|
182
|
+
// marked with the file.
|
|
183
|
+
const stk = stacks.map((r) =>
|
|
184
|
+
routerRow(
|
|
185
|
+
r,
|
|
186
|
+
`<span class="src" title="written by stack up">stack</span> ` +
|
|
187
|
+
`<span class="mono sub">${esc(r.file)}</span>`,
|
|
188
|
+
),
|
|
189
|
+
);
|
|
190
|
+
return (
|
|
191
|
+
`<div class="sum">${tiles.join("")}</div>` +
|
|
192
|
+
`<section aria-labelledby="edge-h-routes"><h2 class="sec" id="edge-h-routes">Routes</h2>` +
|
|
193
|
+
table(
|
|
194
|
+
["Name", "URL", "Upstream", "Source"],
|
|
195
|
+
[...routes, ...inc, ...stk],
|
|
196
|
+
"No routes.",
|
|
197
|
+
) +
|
|
198
|
+
(errors.length
|
|
199
|
+
? `<ul class="errs">` +
|
|
200
|
+
errors
|
|
201
|
+
.map(
|
|
202
|
+
(e) =>
|
|
203
|
+
`<li><span class="mono">${esc(e.file)}</span>: not read (${esc(e.error)})</li>`,
|
|
204
|
+
)
|
|
205
|
+
.join("") +
|
|
206
|
+
`</ul>`
|
|
207
|
+
: "") +
|
|
208
|
+
`<p class="note">Den's own routes, then the routers in the included dynamic files (edge include), each with its source file, then the routers the stacks wrote (stack).</p>` +
|
|
209
|
+
`</section>` +
|
|
210
|
+
`<section aria-labelledby="edge-h-domains"><h2 class="sec" id="edge-h-domains">Domains</h2>` +
|
|
211
|
+
`<ul class="names">` +
|
|
212
|
+
(st.domains ?? [])
|
|
213
|
+
.map(
|
|
214
|
+
(d) =>
|
|
215
|
+
`<li>${link(scheme + d, d)} <span class="mono sub">+ *.${esc(d)}</span></li>`,
|
|
216
|
+
)
|
|
217
|
+
.join("") +
|
|
218
|
+
`</ul></section>` +
|
|
219
|
+
(none
|
|
220
|
+
? ""
|
|
221
|
+
: `<section aria-labelledby="edge-h-cert"><h2 class="sec" id="edge-h-cert">${esc(certHeading(tls.strategy, st.certIssued))}</h2>` +
|
|
222
|
+
(certNames.length
|
|
223
|
+
? `<ul class="names">${certNames.map((n) => `<li class="mono">${esc(n)}</li>`).join("")}</ul>`
|
|
224
|
+
: `<p class="none">None: the included config holds the certs.</p>`) +
|
|
225
|
+
`</section>`)
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** The header's sub line: org / container · state. */
|
|
230
|
+
export function edgeSub(st) {
|
|
231
|
+
if (!st || st.configured === false) return "Not configured";
|
|
232
|
+
return `${st.org} / ${st.container} · ${st.state}`;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** Fetch /api/edge and draw the page into `root` (the header's sub line too). */
|
|
236
|
+
export async function mountEdge(root, sub, fetchFn = fetch) {
|
|
237
|
+
let st;
|
|
238
|
+
try {
|
|
239
|
+
const r = await fetchFn("/api/edge");
|
|
240
|
+
st = await r.json();
|
|
241
|
+
} catch (e) {
|
|
242
|
+
st = { configured: false, error: (e && e.message) || "unreachable" };
|
|
243
|
+
}
|
|
244
|
+
if (sub) sub.textContent = edgeSub(st);
|
|
245
|
+
root.innerHTML = edgeView(st);
|
|
246
|
+
root.removeAttribute("aria-busy");
|
|
247
|
+
return st;
|
|
248
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<script>
|
|
7
|
+
// Mode before first paint: yours (the server fills it in below),
|
|
8
|
+
// else the system's, else the theme's default, else dark.
|
|
9
|
+
(function () {
|
|
10
|
+
var m = "@@DEN_MODE@@";
|
|
11
|
+
if (m !== "dark" && m !== "light") {
|
|
12
|
+
var q = function (s) {
|
|
13
|
+
return (
|
|
14
|
+
window.matchMedia &&
|
|
15
|
+
matchMedia("(prefers-color-scheme: " + s + ")").matches
|
|
16
|
+
);
|
|
17
|
+
};
|
|
18
|
+
m = q("light")
|
|
19
|
+
? "light"
|
|
20
|
+
: q("dark")
|
|
21
|
+
? "dark"
|
|
22
|
+
: "@@DEN_DEFAULT_MODE@@";
|
|
23
|
+
}
|
|
24
|
+
document.documentElement.dataset.mode =
|
|
25
|
+
m === "light" ? "light" : "dark";
|
|
26
|
+
})();
|
|
27
|
+
</script>
|
|
28
|
+
<link rel="stylesheet" href="/theme.css" id="theme-css" />
|
|
29
|
+
<title>__DEN_TITLE__ · Edge</title>
|
|
30
|
+
<style>
|
|
31
|
+
/* The Edge page (PL-SPEC §10): the prototype's (den-shell.html) page
|
|
32
|
+
header (.ph, R20), summary tiles (.sum .tile), section headings
|
|
33
|
+
(h2.sec) and table (.tablewrap table.svc), ported as they are; --r
|
|
34
|
+
is --radius, --r-btn is --radius-button. Read-only. */
|
|
35
|
+
*,
|
|
36
|
+
*::before,
|
|
37
|
+
*::after {
|
|
38
|
+
box-sizing: border-box;
|
|
39
|
+
}
|
|
40
|
+
html,
|
|
41
|
+
body {
|
|
42
|
+
height: 100%;
|
|
43
|
+
margin: 0;
|
|
44
|
+
background: var(--bg);
|
|
45
|
+
color: var(--text);
|
|
46
|
+
}
|
|
47
|
+
body {
|
|
48
|
+
display: flex;
|
|
49
|
+
flex-direction: column;
|
|
50
|
+
overflow: hidden;
|
|
51
|
+
}
|
|
52
|
+
/* .main */
|
|
53
|
+
html body.den-has-shell > #den-content {
|
|
54
|
+
overflow: auto;
|
|
55
|
+
min-width: 0;
|
|
56
|
+
padding: 20px 24px 32px 24px;
|
|
57
|
+
font: 14px/1.45 var(--sans);
|
|
58
|
+
color: var(--text);
|
|
59
|
+
display: block;
|
|
60
|
+
}
|
|
61
|
+
#den-content :focus-visible {
|
|
62
|
+
outline: 2px solid var(--accent);
|
|
63
|
+
outline-offset: 2px;
|
|
64
|
+
}
|
|
65
|
+
a {
|
|
66
|
+
color: var(--accent-text);
|
|
67
|
+
}
|
|
68
|
+
/* .ph: the page header */
|
|
69
|
+
.ph {
|
|
70
|
+
display: flex;
|
|
71
|
+
align-items: flex-end;
|
|
72
|
+
gap: 16px;
|
|
73
|
+
flex-wrap: wrap;
|
|
74
|
+
margin-bottom: 16px;
|
|
75
|
+
}
|
|
76
|
+
.ph h1 {
|
|
77
|
+
font: 600 22px/1.2 var(--display);
|
|
78
|
+
margin: 0;
|
|
79
|
+
text-wrap: balance;
|
|
80
|
+
}
|
|
81
|
+
.ph .sub {
|
|
82
|
+
color: var(--muted);
|
|
83
|
+
font-size: 13px;
|
|
84
|
+
margin-top: 4px;
|
|
85
|
+
}
|
|
86
|
+
/* .sum, .tile */
|
|
87
|
+
.sum {
|
|
88
|
+
display: grid;
|
|
89
|
+
grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
|
|
90
|
+
gap: 10px;
|
|
91
|
+
margin-bottom: 22px;
|
|
92
|
+
}
|
|
93
|
+
.tile {
|
|
94
|
+
background: var(--surface);
|
|
95
|
+
border: 1px solid var(--border);
|
|
96
|
+
border-radius: var(--radius);
|
|
97
|
+
padding: 12px 14px;
|
|
98
|
+
min-width: 0;
|
|
99
|
+
}
|
|
100
|
+
.tile .k {
|
|
101
|
+
font: 11px var(--mono);
|
|
102
|
+
text-transform: uppercase;
|
|
103
|
+
letter-spacing: 0.07em;
|
|
104
|
+
color: var(--faint);
|
|
105
|
+
}
|
|
106
|
+
.tile .v {
|
|
107
|
+
font: 600 22px var(--display);
|
|
108
|
+
font-variant-numeric: tabular-nums;
|
|
109
|
+
margin-top: 2px;
|
|
110
|
+
}
|
|
111
|
+
.tile .s {
|
|
112
|
+
color: var(--muted);
|
|
113
|
+
font-size: 12.5px;
|
|
114
|
+
overflow-wrap: anywhere;
|
|
115
|
+
}
|
|
116
|
+
.tile.bad {
|
|
117
|
+
border-color: color-mix(in srgb, var(--bad) 45%, var(--border));
|
|
118
|
+
}
|
|
119
|
+
.tile.bad .v {
|
|
120
|
+
color: var(--bad);
|
|
121
|
+
}
|
|
122
|
+
section {
|
|
123
|
+
margin-bottom: 22px;
|
|
124
|
+
}
|
|
125
|
+
h2.sec {
|
|
126
|
+
font: 600 13px var(--mono);
|
|
127
|
+
text-transform: uppercase;
|
|
128
|
+
letter-spacing: 0.07em;
|
|
129
|
+
color: var(--muted);
|
|
130
|
+
margin: 0 0 10px;
|
|
131
|
+
}
|
|
132
|
+
/* .tablewrap, table.svc */
|
|
133
|
+
.tablewrap {
|
|
134
|
+
overflow-x: auto;
|
|
135
|
+
border: 1px solid var(--border);
|
|
136
|
+
border-radius: var(--radius);
|
|
137
|
+
background: var(--surface);
|
|
138
|
+
}
|
|
139
|
+
table.svc {
|
|
140
|
+
width: 100%;
|
|
141
|
+
border-collapse: collapse;
|
|
142
|
+
font-size: 13.5px;
|
|
143
|
+
}
|
|
144
|
+
table.svc th {
|
|
145
|
+
text-align: left;
|
|
146
|
+
font: 11px var(--mono);
|
|
147
|
+
text-transform: uppercase;
|
|
148
|
+
letter-spacing: 0.07em;
|
|
149
|
+
color: var(--faint);
|
|
150
|
+
padding: 8px 12px;
|
|
151
|
+
border-bottom: 1px solid var(--border);
|
|
152
|
+
font-weight: 400;
|
|
153
|
+
}
|
|
154
|
+
table.svc td {
|
|
155
|
+
padding: 7px 12px;
|
|
156
|
+
border-bottom: 1px solid var(--border);
|
|
157
|
+
white-space: nowrap;
|
|
158
|
+
vertical-align: top;
|
|
159
|
+
}
|
|
160
|
+
table.svc tr:last-child td {
|
|
161
|
+
border-bottom: 0;
|
|
162
|
+
}
|
|
163
|
+
.mono {
|
|
164
|
+
font-family: var(--mono);
|
|
165
|
+
font-size: 12.5px;
|
|
166
|
+
}
|
|
167
|
+
.rule {
|
|
168
|
+
white-space: normal;
|
|
169
|
+
overflow-wrap: anywhere;
|
|
170
|
+
color: var(--muted);
|
|
171
|
+
}
|
|
172
|
+
/* The source tag: like the rail's distribution tag (.tagx). */
|
|
173
|
+
.src {
|
|
174
|
+
font: 11px var(--mono);
|
|
175
|
+
color: var(--faint);
|
|
176
|
+
border: 1px dashed var(--border-strong);
|
|
177
|
+
border-radius: 4px;
|
|
178
|
+
padding: 0 4px;
|
|
179
|
+
}
|
|
180
|
+
.names {
|
|
181
|
+
list-style: none;
|
|
182
|
+
margin: 0;
|
|
183
|
+
padding: 0;
|
|
184
|
+
display: grid;
|
|
185
|
+
gap: 4px;
|
|
186
|
+
}
|
|
187
|
+
.names .sub {
|
|
188
|
+
color: var(--faint);
|
|
189
|
+
}
|
|
190
|
+
.note,
|
|
191
|
+
.none {
|
|
192
|
+
color: var(--muted);
|
|
193
|
+
font-size: 12.5px;
|
|
194
|
+
margin: 8px 0 0;
|
|
195
|
+
}
|
|
196
|
+
.errs {
|
|
197
|
+
margin: 8px 0 0;
|
|
198
|
+
padding-left: 18px;
|
|
199
|
+
color: var(--warn);
|
|
200
|
+
font-size: 12.5px;
|
|
201
|
+
}
|
|
202
|
+
.empty {
|
|
203
|
+
border: 1px dashed var(--border-strong);
|
|
204
|
+
border-radius: var(--radius);
|
|
205
|
+
padding: 28px;
|
|
206
|
+
color: var(--muted);
|
|
207
|
+
max-width: 640px;
|
|
208
|
+
}
|
|
209
|
+
.empty b {
|
|
210
|
+
color: var(--text);
|
|
211
|
+
}
|
|
212
|
+
</style>
|
|
213
|
+
</head>
|
|
214
|
+
<body>
|
|
215
|
+
<!-- The shell: top bar and rail, the same on every page (shell.js). -->
|
|
216
|
+
<div id="den-shell" data-page="edge"></div>
|
|
217
|
+
<script src="/shell.js"></script>
|
|
218
|
+
|
|
219
|
+
<!-- The page's one main landmark: everything after the shell. -->
|
|
220
|
+
<main id="den-content">
|
|
221
|
+
<div class="ph">
|
|
222
|
+
<div>
|
|
223
|
+
<h1>Edge</h1>
|
|
224
|
+
<div class="sub" id="edge-sub">Reading the edge…</div>
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
227
|
+
<div id="edge-body" aria-busy="true" aria-live="polite"></div>
|
|
228
|
+
|
|
229
|
+
<script type="module">
|
|
230
|
+
import {
|
|
231
|
+
applyDenConfig,
|
|
232
|
+
initChangelog,
|
|
233
|
+
initHealth,
|
|
234
|
+
initLogBadge,
|
|
235
|
+
initWorkspacePill,
|
|
236
|
+
} from "/header.js";
|
|
237
|
+
import { mountEdge } from "/edge-page.js";
|
|
238
|
+
void Promise.all([
|
|
239
|
+
initHealth(),
|
|
240
|
+
initWorkspacePill(),
|
|
241
|
+
applyDenConfig(),
|
|
242
|
+
initChangelog(),
|
|
243
|
+
]).then(initLogBadge);
|
|
244
|
+
void mountEdge(
|
|
245
|
+
document.getElementById("edge-body"),
|
|
246
|
+
document.getElementById("edge-sub"),
|
|
247
|
+
);
|
|
248
|
+
</script>
|
|
249
|
+
</main>
|
|
250
|
+
</body>
|
|
251
|
+
</html>
|