@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
|
@@ -3,6 +3,29 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
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" />
|
|
6
29
|
<title>__DEN_TITLE__ · Environments</title>
|
|
7
30
|
<style>
|
|
8
31
|
*,
|
|
@@ -12,65 +35,17 @@
|
|
|
12
35
|
margin: 0;
|
|
13
36
|
padding: 0;
|
|
14
37
|
}
|
|
15
|
-
:root {
|
|
16
|
-
--bg: #0d1117;
|
|
17
|
-
--surface: #161b22;
|
|
18
|
-
--border: #30363d;
|
|
19
|
-
--text: #c9d1d9;
|
|
20
|
-
--muted: #8b949e;
|
|
21
|
-
--accent: #58a6ff;
|
|
22
|
-
--green: #3fb950;
|
|
23
|
-
--red: #f85149;
|
|
24
|
-
--yellow: #d29922;
|
|
25
|
-
--mono: "SF Mono", "Cascadia Code", "Fira Code", "Menlo", monospace;
|
|
26
|
-
}
|
|
27
38
|
html,
|
|
28
39
|
body {
|
|
29
40
|
min-height: 100%;
|
|
30
41
|
background: var(--bg);
|
|
31
42
|
color: var(--text);
|
|
32
|
-
font-family:
|
|
43
|
+
font-family: var(--sans);
|
|
33
44
|
font-size: 13px;
|
|
34
45
|
}
|
|
35
|
-
header {
|
|
36
|
-
display: flex;
|
|
37
|
-
align-items: center;
|
|
38
|
-
gap: 10px;
|
|
39
|
-
padding: 7px 14px;
|
|
40
|
-
background: var(--surface);
|
|
41
|
-
border-bottom: 1px solid var(--border);
|
|
42
|
-
flex-wrap: wrap;
|
|
43
|
-
}
|
|
44
|
-
header h1 {
|
|
45
|
-
font-family: var(--mono);
|
|
46
|
-
font-size: 14px;
|
|
47
|
-
font-weight: 600;
|
|
48
|
-
letter-spacing: -0.02em;
|
|
49
|
-
}
|
|
50
46
|
.h-spacer {
|
|
51
47
|
flex: 1;
|
|
52
48
|
}
|
|
53
|
-
nav {
|
|
54
|
-
display: flex;
|
|
55
|
-
gap: 2px;
|
|
56
|
-
flex-wrap: wrap;
|
|
57
|
-
}
|
|
58
|
-
nav a {
|
|
59
|
-
color: var(--muted);
|
|
60
|
-
text-decoration: none;
|
|
61
|
-
padding: 3px 10px;
|
|
62
|
-
border-radius: 4px;
|
|
63
|
-
font-size: 12px;
|
|
64
|
-
font-weight: 500;
|
|
65
|
-
}
|
|
66
|
-
nav a:hover {
|
|
67
|
-
color: var(--text);
|
|
68
|
-
background: var(--border);
|
|
69
|
-
}
|
|
70
|
-
nav a.active {
|
|
71
|
-
color: var(--text);
|
|
72
|
-
background: var(--bg);
|
|
73
|
-
}
|
|
74
49
|
.controls {
|
|
75
50
|
display: flex;
|
|
76
51
|
align-items: center;
|
|
@@ -96,7 +71,7 @@
|
|
|
96
71
|
button:hover {
|
|
97
72
|
border-color: var(--accent);
|
|
98
73
|
}
|
|
99
|
-
main {
|
|
74
|
+
.page-main {
|
|
100
75
|
padding: 12px 14px;
|
|
101
76
|
overflow-x: auto;
|
|
102
77
|
}
|
|
@@ -142,7 +117,7 @@
|
|
|
142
117
|
text-decoration: none;
|
|
143
118
|
}
|
|
144
119
|
.cell a:hover {
|
|
145
|
-
color: var(--accent);
|
|
120
|
+
color: var(--accent-text);
|
|
146
121
|
text-decoration: underline;
|
|
147
122
|
}
|
|
148
123
|
.cell .ms {
|
|
@@ -162,13 +137,13 @@
|
|
|
162
137
|
display: inline-block;
|
|
163
138
|
}
|
|
164
139
|
.dot.ok {
|
|
165
|
-
background: var(--
|
|
140
|
+
background: var(--ok);
|
|
166
141
|
}
|
|
167
142
|
.dot.err {
|
|
168
|
-
background: var(--
|
|
143
|
+
background: var(--bad);
|
|
169
144
|
}
|
|
170
145
|
.dot.slow {
|
|
171
|
-
background: var(--
|
|
146
|
+
background: var(--warn);
|
|
172
147
|
}
|
|
173
148
|
.empty {
|
|
174
149
|
color: var(--muted);
|
|
@@ -198,13 +173,13 @@
|
|
|
198
173
|
color: var(--muted);
|
|
199
174
|
}
|
|
200
175
|
.rel .ok {
|
|
201
|
-
color: var(--
|
|
176
|
+
color: var(--ok);
|
|
202
177
|
}
|
|
203
178
|
.rel .warn {
|
|
204
|
-
color: var(--
|
|
179
|
+
color: var(--warn);
|
|
205
180
|
}
|
|
206
181
|
.rel .bad {
|
|
207
|
-
color: var(--
|
|
182
|
+
color: var(--bad);
|
|
208
183
|
}
|
|
209
184
|
a.open {
|
|
210
185
|
color: var(--muted);
|
|
@@ -215,7 +190,7 @@
|
|
|
215
190
|
}
|
|
216
191
|
a.open:hover,
|
|
217
192
|
a.open:focus-visible {
|
|
218
|
-
color: var(--accent);
|
|
193
|
+
color: var(--accent-text);
|
|
219
194
|
background: var(--border);
|
|
220
195
|
}
|
|
221
196
|
select {
|
|
@@ -229,26 +204,12 @@
|
|
|
229
204
|
</style>
|
|
230
205
|
</head>
|
|
231
206
|
<body>
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
width="16"
|
|
236
|
-
height="16"
|
|
237
|
-
alt=""
|
|
238
|
-
style="flex-shrink: 0"
|
|
239
|
-
/>
|
|
240
|
-
<h1>__DEN_TITLE__</h1>
|
|
241
|
-
<div class="h-spacer"></div>
|
|
242
|
-
<nav>
|
|
243
|
-
<a href="/">Local Dev</a>
|
|
244
|
-
<a href="/services">Services</a>
|
|
245
|
-
<a href="/environments" class="active">Environments</a>
|
|
246
|
-
<a href="/release">Releases</a>
|
|
247
|
-
<a href="/logs">Logs</a>
|
|
248
|
-
<a href="/tests">Tests</a>
|
|
249
|
-
</nav>
|
|
250
|
-
</header>
|
|
207
|
+
<!-- The shell: top bar and rail, the same on every page (shell.js). -->
|
|
208
|
+
<div id="den-shell" data-page="environments"></div>
|
|
209
|
+
<script src="/shell.js"></script>
|
|
251
210
|
|
|
211
|
+
<!-- The page's one main landmark: everything after the shell. -->
|
|
212
|
+
<main id="den-content">
|
|
252
213
|
<div class="controls">
|
|
253
214
|
<button id="refresh">Re-check</button>
|
|
254
215
|
<label class="note"
|
|
@@ -265,7 +226,7 @@
|
|
|
265
226
|
>
|
|
266
227
|
</div>
|
|
267
228
|
|
|
268
|
-
<main>
|
|
229
|
+
<div class="page-main">
|
|
269
230
|
<table>
|
|
270
231
|
<thead id="head"></thead>
|
|
271
232
|
<tbody id="body">
|
|
@@ -274,9 +235,24 @@
|
|
|
274
235
|
</tr>
|
|
275
236
|
</tbody>
|
|
276
237
|
</table>
|
|
277
|
-
</
|
|
238
|
+
</div>
|
|
278
239
|
|
|
279
240
|
<script type="module">
|
|
241
|
+
// The shell's behaviour, as on every page (header.js also mounts the
|
|
242
|
+
// appearance controls).
|
|
243
|
+
import {
|
|
244
|
+
applyDenConfig,
|
|
245
|
+
initChangelog,
|
|
246
|
+
initHealth,
|
|
247
|
+
initLogBadge,
|
|
248
|
+
initWorkspacePill,
|
|
249
|
+
} from "/header.js";
|
|
250
|
+
void Promise.all([
|
|
251
|
+
initHealth(),
|
|
252
|
+
initWorkspacePill(),
|
|
253
|
+
applyDenConfig(),
|
|
254
|
+
initChangelog(),
|
|
255
|
+
]).then(initLogBadge);
|
|
280
256
|
const SLOW_MS = 1500;
|
|
281
257
|
const POLL_MS = 60_000;
|
|
282
258
|
const esc = (s) =>
|
|
@@ -349,6 +325,7 @@
|
|
|
349
325
|
status.textContent = "checking…";
|
|
350
326
|
const envs = await fetch("/api/environments")
|
|
351
327
|
.then((r) => r.json())
|
|
328
|
+
.then((v) => (Array.isArray(v) ? v : ["local"]))
|
|
352
329
|
.catch(() => ["local"]);
|
|
353
330
|
const [results, orgs, relCfg] = await Promise.all([
|
|
354
331
|
Promise.all(
|
|
@@ -360,9 +337,12 @@
|
|
|
360
337
|
.catch(() => []),
|
|
361
338
|
),
|
|
362
339
|
),
|
|
363
|
-
|
|
340
|
+
// The org of every repo comes from the catalog (/api/orgs?repos=1):
|
|
341
|
+
// /api/health rows never carry one, so reading r.org put every
|
|
342
|
+
// service under "no org" (0.3.0-dev.14).
|
|
343
|
+
fetch("/api/orgs?repos=1")
|
|
364
344
|
.then((r) => r.json())
|
|
365
|
-
.catch(() => []),
|
|
345
|
+
.catch(() => ({ orgs: [], repos: {} })),
|
|
366
346
|
fetch("/api/release/config")
|
|
367
347
|
.then((r) => r.json())
|
|
368
348
|
.catch(() => ({ environments: [] })),
|
|
@@ -384,8 +364,9 @@
|
|
|
384
364
|
}),
|
|
385
365
|
);
|
|
386
366
|
const orgInfo = Object.fromEntries(
|
|
387
|
-
(Array.isArray(orgs) ? orgs : []).map((o) => [o.id, o]),
|
|
367
|
+
(Array.isArray(orgs?.orgs) ? orgs.orgs : []).map((o) => [o.id, o]),
|
|
388
368
|
);
|
|
369
|
+
const orgOfRepo = orgs?.repos ?? {};
|
|
389
370
|
const filterSel = document.getElementById("org-filter");
|
|
390
371
|
const saved = (() => {
|
|
391
372
|
try {
|
|
@@ -414,7 +395,7 @@
|
|
|
414
395
|
repo: r.repo,
|
|
415
396
|
service: r.service,
|
|
416
397
|
label: r.label,
|
|
417
|
-
org: r.
|
|
398
|
+
org: orgOfRepo[r.repo] || "",
|
|
418
399
|
isEntrypoint: r.isEntrypoint,
|
|
419
400
|
idx: r.idx ?? 1e9,
|
|
420
401
|
byEnv: {},
|
|
@@ -492,5 +473,6 @@
|
|
|
492
473
|
if (!document.hidden) load();
|
|
493
474
|
}, POLL_MS);
|
|
494
475
|
</script>
|
|
476
|
+
</main>
|
|
495
477
|
</body>
|
|
496
478
|
</html>
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The error page (R26a, 0.3.0-dev.38): what a failed page request gets
|
|
3
|
+
* inside the shell frame (rail, top bar, status chip). The server fills
|
|
4
|
+
* #den-error (a JSON script) with what failed; this draws it.
|
|
5
|
+
*
|
|
6
|
+
* 404 "Page not found" the path asked for; "Did you mean …" from the
|
|
7
|
+
* command palette's own matcher (topbar.js
|
|
8
|
+
* rankCommands) over the shell's pages; links to
|
|
9
|
+
* Workspaces and Search (⌘K); the list of pages;
|
|
10
|
+
* for /plugins/<id>/…, that the plugin isn't
|
|
11
|
+
* installed, by name
|
|
12
|
+
* 422 "Config is invalid" each validation problem (file, key path,
|
|
13
|
+
* message), `<cli> validate`, "Reload once fixed",
|
|
14
|
+
* and links to Logs and Settings (config-free)
|
|
15
|
+
* 500 "Something went wrong" the method and route template (never query
|
|
16
|
+
* values), the time, the request id; Retry and
|
|
17
|
+
* Copy details. No stack: the server log has it
|
|
18
|
+
* under the same request id.
|
|
19
|
+
*
|
|
20
|
+
* On an engine mismatch (MD-2) the shell's banner leads the page with the
|
|
21
|
+
* restart message, and a 404 says the page may be new in that version.
|
|
22
|
+
*
|
|
23
|
+
* suggestPage and detailsText are pure, for tests.
|
|
24
|
+
*/
|
|
25
|
+
import { rankCommands } from "/topbar.js";
|
|
26
|
+
|
|
27
|
+
// Its words are the shell's catalog (shell.js MESSAGES, R19: the err.*
|
|
28
|
+
// keys), so the page is in the page's language. "Copy details" is a
|
|
29
|
+
// report: it stays in English, like the server log it points at.
|
|
30
|
+
|
|
31
|
+
function esc(v) {
|
|
32
|
+
return String(v ?? "").replace(
|
|
33
|
+
/[&<>"']/g,
|
|
34
|
+
(c) =>
|
|
35
|
+
({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[
|
|
36
|
+
c
|
|
37
|
+
],
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The closest page to `pathname` by the palette's ranking: the whole slug
|
|
43
|
+
* first, then each of its words, then shorter prefixes of it (two letters
|
|
44
|
+
* at least), so a near-miss and a stray word both find something. `items`
|
|
45
|
+
* are palette rows ({ label, keywords, href }). Null when nothing matches.
|
|
46
|
+
*/
|
|
47
|
+
export function suggestPage(pathname, items, rank = rankCommands) {
|
|
48
|
+
const slug = String(pathname || "")
|
|
49
|
+
.split("/")
|
|
50
|
+
.filter(Boolean)
|
|
51
|
+
.pop()
|
|
52
|
+
?.replace(/\.html?$/i, "")
|
|
53
|
+
.toLowerCase();
|
|
54
|
+
if (!slug) return null;
|
|
55
|
+
const tries = [slug, ...slug.split(/[-_.\s]+/).filter((w) => w.length > 1)];
|
|
56
|
+
for (let n = slug.length - 1; n >= 2; n--) tries.push(slug.slice(0, n));
|
|
57
|
+
for (const q of tries) {
|
|
58
|
+
const hit = rank(items, q, { limit: 1 })[0];
|
|
59
|
+
if (hit) return hit;
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** "Copy details": what a report needs, nothing more (no stack). */
|
|
65
|
+
export function detailsText(d, page = "") {
|
|
66
|
+
return [
|
|
67
|
+
`Den ${d.version ?? "unknown"}`,
|
|
68
|
+
`Status ${d.status}`,
|
|
69
|
+
d.status === 500
|
|
70
|
+
? `Route ${d.method ?? "GET"} ${d.route ?? ""}`
|
|
71
|
+
: `Path ${d.path ?? ""}`,
|
|
72
|
+
`Request ${d.requestId}`,
|
|
73
|
+
`Time ${d.time}`,
|
|
74
|
+
...(page ? [`Page ${page}`] : []),
|
|
75
|
+
...(d.engine ? [d.engine.message] : []),
|
|
76
|
+
].join("\n");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function pageItems(sh) {
|
|
80
|
+
const st = sh.getState?.() ?? {};
|
|
81
|
+
const rows = (sh.orderedNav?.() ?? sh.nav?.() ?? [])
|
|
82
|
+
.filter((n) => !n.when || n.when(st))
|
|
83
|
+
.map((n) => ({
|
|
84
|
+
label: sh.t(n.label),
|
|
85
|
+
keywords: [n.id, ...(n.keywords ?? [])],
|
|
86
|
+
href: n.href,
|
|
87
|
+
}));
|
|
88
|
+
rows.push({
|
|
89
|
+
label: sh.t("n.settings"),
|
|
90
|
+
keywords: ["settings", "preferences"],
|
|
91
|
+
href: "/settings",
|
|
92
|
+
});
|
|
93
|
+
return rows;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function readData() {
|
|
97
|
+
try {
|
|
98
|
+
return JSON.parse(document.getElementById("den-error")?.textContent ?? "");
|
|
99
|
+
} catch {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function copy(text, btn, t) {
|
|
105
|
+
try {
|
|
106
|
+
await navigator.clipboard.writeText(text);
|
|
107
|
+
} catch {
|
|
108
|
+
const ta = document.createElement("textarea");
|
|
109
|
+
ta.value = text;
|
|
110
|
+
document.body.appendChild(ta);
|
|
111
|
+
ta.select();
|
|
112
|
+
try {
|
|
113
|
+
document.execCommand("copy");
|
|
114
|
+
} catch {
|
|
115
|
+
/* nothing more to try */
|
|
116
|
+
}
|
|
117
|
+
ta.remove();
|
|
118
|
+
}
|
|
119
|
+
const label = btn.textContent;
|
|
120
|
+
btn.textContent = t("err.copied");
|
|
121
|
+
setTimeout(() => (btn.textContent = label), 1500);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function mountErrorPage(root) {
|
|
125
|
+
const sh = window.denShell;
|
|
126
|
+
const d = readData();
|
|
127
|
+
if (!root || !sh || !d) return;
|
|
128
|
+
const t = sh.t;
|
|
129
|
+
const header = (title, sub) =>
|
|
130
|
+
`<div class="ph"><div><h1>${esc(title)}</h1>` +
|
|
131
|
+
`<div class="sub">${sub}</div></div></div>`;
|
|
132
|
+
|
|
133
|
+
if (d.status === 404) {
|
|
134
|
+
document.title = document.title.replace(/Error$/, t("err.404.title"));
|
|
135
|
+
const items = pageItems(sh);
|
|
136
|
+
const hit = suggestPage(d.path, items);
|
|
137
|
+
const lines = [];
|
|
138
|
+
if (d.plugin) {
|
|
139
|
+
const name = esc(d.plugin.name || d.plugin.id);
|
|
140
|
+
lines.push(
|
|
141
|
+
`<p class="lead">${t(
|
|
142
|
+
d.plugin.installed
|
|
143
|
+
? "err.404.pluginNoPage"
|
|
144
|
+
: d.plugin.locked
|
|
145
|
+
? "err.404.pluginLocked"
|
|
146
|
+
: "err.404.plugin",
|
|
147
|
+
{ name: `<b>${name}</b>` },
|
|
148
|
+
)}</p>`,
|
|
149
|
+
);
|
|
150
|
+
} else lines.push(`<p class="lead">${esc(t("err.404.lead"))}</p>`);
|
|
151
|
+
if (d.engine)
|
|
152
|
+
lines.push(
|
|
153
|
+
`<p>${esc(t("err.404.stale", { onDisk: d.engine.onDisk ?? "?", loaded: d.engine.loaded ?? "?" }))}</p>`,
|
|
154
|
+
);
|
|
155
|
+
if (hit)
|
|
156
|
+
lines.push(
|
|
157
|
+
`<p class="den-err-mean">${t("err.404.mean", {
|
|
158
|
+
page: `<a href="${esc(hit.href)}">${esc(hit.label)}</a>`,
|
|
159
|
+
})}</p>`,
|
|
160
|
+
);
|
|
161
|
+
root.innerHTML =
|
|
162
|
+
header(
|
|
163
|
+
t("err.404.title"),
|
|
164
|
+
// The path sits where the language puts it ("Nothing at {path}").
|
|
165
|
+
esc(t("err.404.sub", { path: "\u0000" })).replace(
|
|
166
|
+
"\u0000",
|
|
167
|
+
`<span class="mono">${esc(d.path)}</span>`,
|
|
168
|
+
),
|
|
169
|
+
) +
|
|
170
|
+
`<div class="empty">${lines.join("")}` +
|
|
171
|
+
`<div class="acts"><a class="btn primary" href="/local-dev">${esc(t("err.404.workspaces"))}</a>` +
|
|
172
|
+
`<button type="button" class="btn" id="den-err-search">${esc(t("err.404.search"))} <kbd>⌘K</kbd></button></div>` +
|
|
173
|
+
`<h2 class="sec">${esc(t("err.404.pages"))}</h2><ul class="pages">` +
|
|
174
|
+
items
|
|
175
|
+
.map((i) => `<li><a href="${esc(i.href)}">${esc(i.label)}</a></li>`)
|
|
176
|
+
.join("") +
|
|
177
|
+
`</ul></div>`;
|
|
178
|
+
root.querySelector("#den-err-search")?.addEventListener("click", () => {
|
|
179
|
+
document.getElementById("den-search")?.click();
|
|
180
|
+
});
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (d.status === 422) {
|
|
185
|
+
// R26a's 422 (0.3.0-dev.40): each problem (file, key path, message),
|
|
186
|
+
// the command that shows them in a terminal, and "Reload once fixed".
|
|
187
|
+
// Settings and Logs are config-free, so the rail reaches the fix.
|
|
188
|
+
document.title = document.title.replace(/Error$/, t("err.422.title"));
|
|
189
|
+
const issues =
|
|
190
|
+
Array.isArray(d.issues) && d.issues.length
|
|
191
|
+
? d.issues
|
|
192
|
+
: [{ file: "", key: "", message: d.message ?? "" }];
|
|
193
|
+
const cmd = `${d.cli || "den"} validate`;
|
|
194
|
+
root.innerHTML =
|
|
195
|
+
header(t("err.422.title"), esc(t("err.422.sub"))) +
|
|
196
|
+
`<div class="empty wide"><p class="lead">${esc(t("err.422.lead"))}</p>` +
|
|
197
|
+
`<div class="tablewrap"><table class="svc" id="den-err-issues"><thead><tr>` +
|
|
198
|
+
`<th>${esc(t("err.422.file"))}</th><th>${esc(t("err.422.key"))}</th><th>${esc(t("err.422.problem"))}</th>` +
|
|
199
|
+
`</tr></thead><tbody>` +
|
|
200
|
+
issues
|
|
201
|
+
.map(
|
|
202
|
+
(i) =>
|
|
203
|
+
`<tr><td class="mono">${esc(i.file)}</td><td class="mono"><b>${esc(i.key)}</b></td><td>${esc(i.message)}</td></tr>`,
|
|
204
|
+
)
|
|
205
|
+
.join("") +
|
|
206
|
+
`</tbody></table></div>` +
|
|
207
|
+
`<p>${esc(t("err.422.cmd"))}</p>` +
|
|
208
|
+
`<pre class="code" id="den-err-cmd">${esc(cmd)}</pre>` +
|
|
209
|
+
`<dl class="facts">` +
|
|
210
|
+
`<dt>${esc(t("err.500.id"))}</dt><dd class="mono" id="den-err-id">${esc(d.requestId)}</dd>` +
|
|
211
|
+
`</dl><div class="acts">` +
|
|
212
|
+
`<button type="button" class="btn primary" id="den-err-retry">${esc(t("err.422.reload"))}</button>` +
|
|
213
|
+
`<a class="btn" href="/logs">${esc(t("err.422.logs"))}</a>` +
|
|
214
|
+
`<a class="btn" href="/settings">${esc(t("err.422.settings"))}</a>` +
|
|
215
|
+
`</div></div>`;
|
|
216
|
+
root
|
|
217
|
+
.querySelector("#den-err-retry")
|
|
218
|
+
?.addEventListener("click", () => location.reload());
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
document.title = document.title.replace(/Error$/, t("err.500.title"));
|
|
223
|
+
const what = `${d.method ?? "GET"} ${d.route ?? ""}`;
|
|
224
|
+
const when = new Date(d.time);
|
|
225
|
+
root.innerHTML =
|
|
226
|
+
header(
|
|
227
|
+
t("err.500.title"),
|
|
228
|
+
`<span class="mono">${esc(t("err.500.sub", { what }))}</span>`,
|
|
229
|
+
) +
|
|
230
|
+
`<div class="empty"><p class="lead">${esc(t("err.500.lead"))}</p>` +
|
|
231
|
+
`<dl class="facts">` +
|
|
232
|
+
`<dt>${esc(t("err.500.what"))}</dt><dd class="mono">${esc(what)}</dd>` +
|
|
233
|
+
`<dt>${esc(t("err.500.time"))}</dt><dd><time datetime="${esc(d.time)}">${esc(isNaN(when) ? d.time : when.toLocaleString(sh.lang?.() && sh.lang() !== "en" ? sh.lang() : undefined))}</time></dd>` +
|
|
234
|
+
`<dt>${esc(t("err.500.id"))}</dt><dd class="mono" id="den-err-id">${esc(d.requestId)}</dd>` +
|
|
235
|
+
`<dt>${esc(t("err.500.version"))}</dt><dd class="mono">${esc(d.version ?? "unknown")}</dd>` +
|
|
236
|
+
`</dl><div class="acts">` +
|
|
237
|
+
`<button type="button" class="btn primary" id="den-err-retry">${esc(t("err.retry"))}</button>` +
|
|
238
|
+
`<button type="button" class="btn" id="den-err-copy">${esc(t("err.copy"))}</button>` +
|
|
239
|
+
`</div></div>`;
|
|
240
|
+
root
|
|
241
|
+
.querySelector("#den-err-retry")
|
|
242
|
+
?.addEventListener("click", () => location.reload());
|
|
243
|
+
const copyBtn = root.querySelector("#den-err-copy");
|
|
244
|
+
copyBtn?.addEventListener("click", () =>
|
|
245
|
+
copy(detailsText(d, location.pathname), copyBtn, t),
|
|
246
|
+
);
|
|
247
|
+
}
|