@prism-d1/cli 1.6.0 → 1.7.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/dist/src/aws/connect.d.ts +1 -1
- package/dist/src/aws/connect.d.ts.map +1 -1
- package/dist/src/aws/connect.js.map +1 -1
- package/dist/src/commands/_top/dashboard.d.ts.map +1 -1
- package/dist/src/commands/_top/dashboard.js +22 -3
- package/dist/src/commands/_top/dashboard.js.map +1 -1
- package/dist/src/dashboard/actions.d.ts +112 -0
- package/dist/src/dashboard/actions.d.ts.map +1 -0
- package/dist/src/dashboard/actions.js +242 -0
- package/dist/src/dashboard/actions.js.map +1 -0
- package/dist/src/dashboard/aws-identity.d.ts +115 -0
- package/dist/src/dashboard/aws-identity.d.ts.map +1 -0
- package/dist/src/dashboard/aws-identity.js +240 -0
- package/dist/src/dashboard/aws-identity.js.map +1 -0
- package/dist/src/dashboard/page.d.ts +1 -1
- package/dist/src/dashboard/page.d.ts.map +1 -1
- package/dist/src/dashboard/page.js +23 -7
- package/dist/src/dashboard/page.js.map +1 -1
- package/dist/src/dashboard/server.d.ts +27 -6
- package/dist/src/dashboard/server.d.ts.map +1 -1
- package/dist/src/dashboard/server.js +75 -10
- package/dist/src/dashboard/server.js.map +1 -1
- package/dist/src/dashboard/setup-api.d.ts +63 -0
- package/dist/src/dashboard/setup-api.d.ts.map +1 -0
- package/dist/src/dashboard/setup-api.js +66 -0
- package/dist/src/dashboard/setup-api.js.map +1 -0
- package/dist/src/dashboard/setup-page.d.ts +16 -0
- package/dist/src/dashboard/setup-page.d.ts.map +1 -0
- package/dist/src/dashboard/setup-page.js +347 -0
- package/dist/src/dashboard/setup-page.js.map +1 -0
- package/dist/src/dashboard/setup.d.ts +2 -0
- package/dist/src/dashboard/setup.d.ts.map +1 -1
- package/dist/src/dashboard/setup.js +1 -1
- package/dist/src/dashboard/setup.js.map +1 -1
- package/dist/src/utils/aws.d.ts +2 -0
- package/dist/src/utils/aws.d.ts.map +1 -1
- package/dist/src/utils/aws.js +1 -1
- package/dist/src/utils/aws.js.map +1 -1
- package/dist/src/utils/exec.d.ts +1 -1
- package/dist/src/utils/exec.d.ts.map +1 -1
- package/dist/src/utils/exec.js +3 -1
- package/dist/src/utils/exec.js.map +1 -1
- package/package.json +1 -1
|
@@ -8,7 +8,11 @@
|
|
|
8
8
|
* cookie on every request (compared in constant time);
|
|
9
9
|
* - the Host header must be 127.0.0.1:<port> or localhost:<port>, which
|
|
10
10
|
* defeats DNS rebinding (a rebound name arrives with its own Host);
|
|
11
|
-
* - GET
|
|
11
|
+
* - GET for every read route; no CORS headers; strict CSP.
|
|
12
|
+
* - POST only for the Setup page's action routes (postApi), and each POST
|
|
13
|
+
* also needs: an Origin (or, without one, a Referer) of this server; the
|
|
14
|
+
* per-run anti-CSRF token embedded in the page, in the X-Prism-CSRF header;
|
|
15
|
+
* and Content-Type application/json with a body of at most 16 KB.
|
|
12
16
|
*
|
|
13
17
|
* Why a minted token and not a platform auth mechanism: the server is
|
|
14
18
|
* reachable only from this machine, the audience is the developer running it,
|
|
@@ -64,6 +68,39 @@ function send(res, status, type, body, extra = {}, nonce) {
|
|
|
64
68
|
}
|
|
65
69
|
/** /report/<id> or /report/<id>.md; anything else (slashes, dots, %-escapes) does not match. */
|
|
66
70
|
const REPORT_ROUTE = /^\/report\/([A-Za-z0-9_-]{1,120})(\.md)?$/;
|
|
71
|
+
export const CSRF_HEADER = 'x-prism-csrf';
|
|
72
|
+
export const MAX_BODY = 16 * 1024;
|
|
73
|
+
const own = (o, k) => o && Object.prototype.hasOwnProperty.call(o, k) ? o[k] : undefined;
|
|
74
|
+
function readBody(req) {
|
|
75
|
+
return new Promise((resolve, reject) => {
|
|
76
|
+
let size = 0;
|
|
77
|
+
const chunks = [];
|
|
78
|
+
const onData = (c) => {
|
|
79
|
+
size += c.length;
|
|
80
|
+
// Too big: stop buffering and answer 413 (sent with Connection: close); the rest is drained, not kept.
|
|
81
|
+
if (size > MAX_BODY) {
|
|
82
|
+
req.off('data', onData);
|
|
83
|
+
req.resume();
|
|
84
|
+
resolve(null);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
chunks.push(c);
|
|
88
|
+
};
|
|
89
|
+
req.on('data', onData);
|
|
90
|
+
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
|
|
91
|
+
req.on('error', reject);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
/** The request's Origin, else its Referer, must be this server (either Host spelling). */
|
|
95
|
+
function sameOrigin(req, port) {
|
|
96
|
+
const allowed = [`http://${BIND_HOST}:${port}`, `http://localhost:${port}`];
|
|
97
|
+
const origin = req.headers.origin;
|
|
98
|
+
if (typeof origin === 'string')
|
|
99
|
+
return allowed.includes(origin);
|
|
100
|
+
const ref = req.headers.referer;
|
|
101
|
+
return typeof ref === 'string' && allowed.some((a) => ref === a || ref.startsWith(a + '/'));
|
|
102
|
+
}
|
|
103
|
+
const json = (res, status, body) => send(res, status, 'application/json; charset=utf-8', JSON.stringify(body));
|
|
67
104
|
export function createHandler(opts) {
|
|
68
105
|
return async (req, res) => {
|
|
69
106
|
try {
|
|
@@ -72,16 +109,17 @@ export function createHandler(opts) {
|
|
|
72
109
|
if (host !== `${BIND_HOST}:${port}` && host !== `localhost:${port}`) {
|
|
73
110
|
return send(res, 403, 'text/plain; charset=utf-8', 'Forbidden: unexpected Host header\n');
|
|
74
111
|
}
|
|
75
|
-
if (req.method !== 'GET') {
|
|
76
|
-
return send(res, 405, 'text/plain; charset=utf-8', 'Method Not Allowed\n', { Allow: 'GET' });
|
|
77
|
-
}
|
|
78
112
|
const url = new URL(req.url || '/', `http://${BIND_HOST}:${port}`);
|
|
113
|
+
const post = own(opts.postApi, url.pathname);
|
|
114
|
+
if (post ? req.method !== 'POST' : req.method !== 'GET') {
|
|
115
|
+
return send(res, 405, 'text/plain; charset=utf-8', 'Method Not Allowed\n', { Allow: post ? 'POST' : 'GET' });
|
|
116
|
+
}
|
|
79
117
|
const name = cookieName(port);
|
|
80
118
|
const cookie = readCookie(req, name);
|
|
81
119
|
const cookieOk = cookie !== null && safeEqual(cookie, opts.token);
|
|
82
120
|
// First load: exchange the URL token for the cookie, then drop it from the URL.
|
|
83
121
|
const qToken = url.searchParams.get('token');
|
|
84
|
-
if (url.pathname === '/' && qToken !== null) {
|
|
122
|
+
if (!post && url.pathname === '/' && qToken !== null) {
|
|
85
123
|
if (!safeEqual(qToken, opts.token))
|
|
86
124
|
return send(res, 401, 'text/plain; charset=utf-8', 'Unauthorized\n');
|
|
87
125
|
return send(res, 303, 'text/plain; charset=utf-8', 'See Other\n', {
|
|
@@ -92,14 +130,39 @@ export function createHandler(opts) {
|
|
|
92
130
|
if (!cookieOk) {
|
|
93
131
|
return send(res, 401, 'text/plain; charset=utf-8', 'Unauthorized: open the URL printed by prism-cli dashboard\n');
|
|
94
132
|
}
|
|
133
|
+
if (post) {
|
|
134
|
+
if (!sameOrigin(req, port))
|
|
135
|
+
return json(res, 403, { error: 'Forbidden: missing or foreign Origin' });
|
|
136
|
+
const csrf = req.headers[CSRF_HEADER];
|
|
137
|
+
if (!opts.csrf || typeof csrf !== 'string' || !safeEqual(csrf, opts.csrf))
|
|
138
|
+
return json(res, 403, { error: 'Forbidden: missing or wrong CSRF token' });
|
|
139
|
+
const type = String(req.headers['content-type'] || '').split(';')[0].trim().toLowerCase();
|
|
140
|
+
if (type !== 'application/json')
|
|
141
|
+
return json(res, 415, { error: 'Content-Type must be application/json' });
|
|
142
|
+
const raw = await readBody(req);
|
|
143
|
+
if (raw === null)
|
|
144
|
+
return send(res, 413, 'application/json; charset=utf-8', JSON.stringify({ error: 'request body too large' }), { Connection: 'close' });
|
|
145
|
+
let body;
|
|
146
|
+
try {
|
|
147
|
+
body = JSON.parse(raw);
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return json(res, 400, { error: 'invalid JSON' });
|
|
151
|
+
}
|
|
152
|
+
const r = await post(body);
|
|
153
|
+
return json(res, r.status, r.json);
|
|
154
|
+
}
|
|
95
155
|
if (url.pathname === '/') {
|
|
96
156
|
const nonce = randomBytes(16).toString('base64');
|
|
97
|
-
return send(res, 200, 'text/html; charset=utf-8', renderPage(nonce), {}, nonce);
|
|
157
|
+
return send(res, 200, 'text/html; charset=utf-8', renderPage(nonce, opts.csrf), {}, nonce);
|
|
98
158
|
}
|
|
99
159
|
if (url.pathname === '/api/data') {
|
|
100
160
|
const payload = await opts.getPayload();
|
|
101
161
|
return send(res, 200, 'application/json; charset=utf-8', JSON.stringify(payload));
|
|
102
162
|
}
|
|
163
|
+
const extra = own(opts.getApi, url.pathname);
|
|
164
|
+
if (extra)
|
|
165
|
+
return json(res, 200, await extra());
|
|
103
166
|
const m = REPORT_ROUTE.exec(url.pathname);
|
|
104
167
|
if (m && opts.getReport) {
|
|
105
168
|
const report = await opts.getReport(m[1]);
|
|
@@ -127,8 +190,9 @@ export function createHandler(opts) {
|
|
|
127
190
|
/** Starts the server on 127.0.0.1. `port` 0 (default) picks a free port. */
|
|
128
191
|
export function startServer(opts) {
|
|
129
192
|
const token = opts.token || newToken();
|
|
193
|
+
const csrf = newToken();
|
|
130
194
|
let port = 0;
|
|
131
|
-
const server = createServer(createHandler({ token, getPort: () => port, getPayload: opts.getPayload, getReport: opts.getReport }));
|
|
195
|
+
const server = createServer(createHandler({ token, csrf, getPort: () => port, getPayload: opts.getPayload, getReport: opts.getReport, getApi: opts.getApi, postApi: opts.postApi }));
|
|
132
196
|
return new Promise((resolve, reject) => {
|
|
133
197
|
server.once('error', reject);
|
|
134
198
|
server.listen(opts.port ?? 0, BIND_HOST, () => {
|
|
@@ -139,19 +203,19 @@ export function startServer(opts) {
|
|
|
139
203
|
}
|
|
140
204
|
port = addr.port;
|
|
141
205
|
resolve({
|
|
142
|
-
server, port, token,
|
|
206
|
+
server, port, token, csrf,
|
|
143
207
|
url: `http://${BIND_HOST}:${port}/?token=${token}`,
|
|
144
208
|
close: () => new Promise((r) => server.close(() => r())),
|
|
145
209
|
});
|
|
146
210
|
});
|
|
147
211
|
});
|
|
148
212
|
}
|
|
149
|
-
/** Memoises the payload for `ttlMs` and shares one in-flight build between requests. */
|
|
213
|
+
/** Memoises the payload for `ttlMs` and shares one in-flight build between requests. `invalidate()` forces the next build. */
|
|
150
214
|
export function cachedPayload(build, ttlMs = 30_000) {
|
|
151
215
|
let at = 0;
|
|
152
216
|
let pending = null;
|
|
153
217
|
let value;
|
|
154
|
-
|
|
218
|
+
const get = () => {
|
|
155
219
|
if (value !== undefined && Date.now() - at < ttlMs)
|
|
156
220
|
return Promise.resolve(value);
|
|
157
221
|
if (!pending) {
|
|
@@ -159,5 +223,6 @@ export function cachedPayload(build, ttlMs = 30_000) {
|
|
|
159
223
|
}
|
|
160
224
|
return pending;
|
|
161
225
|
};
|
|
226
|
+
return Object.assign(get, { invalidate: () => { at = 0; } });
|
|
162
227
|
}
|
|
163
228
|
//# sourceMappingURL=server.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../../src/dashboard/server.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../../src/dashboard/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { type ActionId, type ActionRunner, type ApiResult } from './actions.js';
|
|
2
|
+
import { type CheckNowDeps, type IdentityDeps } from './aws-identity.js';
|
|
3
|
+
/** Commands the page shows as Copy only: each writes to AWS, needs a terminal, or both. */
|
|
4
|
+
export declare const COPY_ONLY: {
|
|
5
|
+
enableSecurity: string;
|
|
6
|
+
addDeveloper: string;
|
|
7
|
+
codeReview: string;
|
|
8
|
+
threatModel: string;
|
|
9
|
+
pentest: string;
|
|
10
|
+
awsConnect: string;
|
|
11
|
+
};
|
|
12
|
+
export interface SetupApiDeps {
|
|
13
|
+
runner: ActionRunner;
|
|
14
|
+
repoPaths(): Promise<string[]>;
|
|
15
|
+
identity: IdentityDeps;
|
|
16
|
+
/** Where repo-less actions run, for the confirm dialog. */
|
|
17
|
+
cwd: string;
|
|
18
|
+
refresh(): Promise<unknown>;
|
|
19
|
+
checkAws?: CheckNowDeps['aws'];
|
|
20
|
+
now?: () => Date;
|
|
21
|
+
}
|
|
22
|
+
export declare function setupApi(d: SetupApiDeps): {
|
|
23
|
+
getApi: {
|
|
24
|
+
'/api/setup': () => Promise<{
|
|
25
|
+
actions: {
|
|
26
|
+
id: ActionId;
|
|
27
|
+
label: string;
|
|
28
|
+
command: string;
|
|
29
|
+
repo: boolean;
|
|
30
|
+
url: boolean;
|
|
31
|
+
confirm: boolean;
|
|
32
|
+
readOnlyAws: boolean;
|
|
33
|
+
timeoutSec: number;
|
|
34
|
+
available: boolean;
|
|
35
|
+
}[];
|
|
36
|
+
repoIds: {
|
|
37
|
+
[k: string]: string;
|
|
38
|
+
};
|
|
39
|
+
/** `cd <repo>` prefixes for the copy-only per-repo commands. */
|
|
40
|
+
repoCd: {
|
|
41
|
+
[k: string]: string;
|
|
42
|
+
};
|
|
43
|
+
cwd: string;
|
|
44
|
+
copy: {
|
|
45
|
+
gitleaks: string;
|
|
46
|
+
enableSecurity: string;
|
|
47
|
+
addDeveloper: string;
|
|
48
|
+
codeReview: string;
|
|
49
|
+
threatModel: string;
|
|
50
|
+
pentest: string;
|
|
51
|
+
awsConnect: string;
|
|
52
|
+
};
|
|
53
|
+
aws: import("./aws-identity.js").AwsIdentityView;
|
|
54
|
+
}>;
|
|
55
|
+
'/api/action/status': () => Promise<import("./actions.js").ActionStatus>;
|
|
56
|
+
'/api/recheck': () => Promise<unknown>;
|
|
57
|
+
};
|
|
58
|
+
postApi: {
|
|
59
|
+
'/api/action': (body: unknown) => Promise<ApiResult>;
|
|
60
|
+
'/api/check-now': (body: unknown) => Promise<ApiResult>;
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
//# sourceMappingURL=setup-api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup-api.d.ts","sourceRoot":"","sources":["../../../src/dashboard/setup-api.ts"],"names":[],"mappings":"AAUA,OAAO,EAA2C,KAAK,QAAQ,EAAE,KAAK,YAAY,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AACzH,OAAO,EAA6C,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAIpH,2FAA2F;AAC3F,eAAO,MAAM,SAAS;;;;;;;CAOrB,CAAC;AAEF,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,YAAY,CAAC;IACrB,SAAS,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC/B,QAAQ,EAAE,YAAY,CAAC;IACvB,2DAA2D;IAC3D,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5B,QAAQ,CAAC,EAAE,YAAY,CAAC,KAAK,CAAC,CAAC;IAC/B,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB;AAED,wBAAgB,QAAQ,CAAC,CAAC,EAAE,YAAY;;;;;;;;;;;;;;;;;YAe9B,gEAAgE;;;;;;;;;;;;;;;;;;;;8BAW9C,OAAO,KAAG,OAAO,CAAC,SAAS,CAAC;iCACzB,OAAO,KAAG,OAAO,CAAC,SAAS,CAAC;;EAc1D"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Routes behind the Setup page:
|
|
3
|
+
* GET /api/setup action catalog, repo ids, copy-only commands, AWS identity (no AWS call)
|
|
4
|
+
* GET /api/action/status the current or last action and its redacted output
|
|
5
|
+
* GET /api/recheck rebuilds the dashboard data now (status checks)
|
|
6
|
+
* POST /api/action start an allowlisted action
|
|
7
|
+
* POST /api/check-now one read-only sts get-caller-identity
|
|
8
|
+
* Account ids and ARNs appear only here and in the page, never in `dashboard --json`.
|
|
9
|
+
*/
|
|
10
|
+
import { gitleaksInstallHint, shellPath } from './setup.js';
|
|
11
|
+
import { ACTIONS, displayCommand, redact, repoId } from './actions.js';
|
|
12
|
+
import { checkNow, checkNowAws, resolveAwsIdentity } from './aws-identity.js';
|
|
13
|
+
import { loadConnection } from '../aws/connection.js';
|
|
14
|
+
import { tildeHome } from './payload.js';
|
|
15
|
+
/** Commands the page shows as Copy only: each writes to AWS, needs a terminal, or both. */
|
|
16
|
+
export const COPY_ONLY = {
|
|
17
|
+
enableSecurity: 'prism-cli enable security',
|
|
18
|
+
addDeveloper: 'prism-cli team add-developer <email>',
|
|
19
|
+
codeReview: 'prism-cli security code-review',
|
|
20
|
+
threatModel: 'prism-cli security threat-model',
|
|
21
|
+
pentest: 'prism-cli security pentest',
|
|
22
|
+
awsConnect: 'prism-cli aws connect',
|
|
23
|
+
};
|
|
24
|
+
export function setupApi(d) {
|
|
25
|
+
const catalog = () => Object.keys(ACTIONS).map((id) => {
|
|
26
|
+
const a = ACTIONS[id];
|
|
27
|
+
return {
|
|
28
|
+
id, label: a.label, command: displayCommand(a.args), repo: !!a.repo, url: !!a.url, confirm: !!a.confirm,
|
|
29
|
+
readOnlyAws: !!a.readOnlyAws, timeoutSec: a.timeoutMs / 1000, available: d.runner.available(id),
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
return {
|
|
33
|
+
getApi: {
|
|
34
|
+
'/api/setup': async () => {
|
|
35
|
+
const paths = await d.repoPaths();
|
|
36
|
+
return {
|
|
37
|
+
actions: catalog(),
|
|
38
|
+
repoIds: Object.fromEntries(paths.map((p) => [p, repoId(p)])),
|
|
39
|
+
/** `cd <repo>` prefixes for the copy-only per-repo commands. */
|
|
40
|
+
repoCd: Object.fromEntries(paths.map((p) => [p, `cd ${shellPath(p)}`])),
|
|
41
|
+
cwd: tildeHome(d.cwd, d.identity.home),
|
|
42
|
+
copy: { ...COPY_ONLY, gitleaks: gitleaksInstallHint() },
|
|
43
|
+
aws: resolveAwsIdentity(d.identity),
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
'/api/action/status': async () => d.runner.status(),
|
|
47
|
+
'/api/recheck': async () => d.refresh(),
|
|
48
|
+
},
|
|
49
|
+
postApi: {
|
|
50
|
+
'/api/action': (body) => d.runner.start(body),
|
|
51
|
+
'/api/check-now': (body) => {
|
|
52
|
+
if (!body || typeof body !== 'object' || Array.isArray(body) || Object.keys(body).length) {
|
|
53
|
+
return Promise.resolve({ status: 400, json: { error: 'check-now takes an empty JSON object' } });
|
|
54
|
+
}
|
|
55
|
+
return d.runner.exclusive(() => checkNow({
|
|
56
|
+
identity: resolveAwsIdentity(d.identity),
|
|
57
|
+
saved: loadConnection(d.identity.home, d.identity.repoRoot),
|
|
58
|
+
aws: d.checkAws ?? checkNowAws,
|
|
59
|
+
now: d.now ?? (() => new Date()),
|
|
60
|
+
redact,
|
|
61
|
+
}));
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=setup-api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup-api.js","sourceRoot":"","sources":["../../../src/dashboard/setup-api.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Setup view and the theme toggle, as CSS and JS strings concatenated into
|
|
3
|
+
* page.ts's single document (same nonce, same el()/txt()/pill() helpers).
|
|
4
|
+
*
|
|
5
|
+
* Every value from the server is placed with textContent or an attribute.
|
|
6
|
+
* Run buttons only send an action id (plus a repo id or the collector URL);
|
|
7
|
+
* the server maps the id to a fixed argv. Each Run shows the exact command in
|
|
8
|
+
* a confirm dialog first. AWS-writing commands are Copy only.
|
|
9
|
+
*/
|
|
10
|
+
/** Runs in <head> before first paint: applies a saved Light/Dark choice so the page does not flash. */
|
|
11
|
+
export declare const THEME_BOOT = "(function(){try{var t=localStorage.getItem('prism-theme');if(t==='light'||t==='dark')document.documentElement.setAttribute('data-theme',t);}catch(e){}})();";
|
|
12
|
+
/** The header toggle: Light, Dark or System (no data-theme; follows prefers-color-scheme). Saved in localStorage only. */
|
|
13
|
+
export declare const THEME_SCRIPT = "\nconst THEME_KEY = 'prism-theme';\nfunction storedTheme() { try { const t = localStorage.getItem(THEME_KEY); return t === 'light' || t === 'dark' ? t : 'system'; } catch (e) { return 'system'; } }\nfunction applyTheme(t) {\n const root = document.documentElement;\n if (t === 'light' || t === 'dark') root.setAttribute('data-theme', t); else root.removeAttribute('data-theme');\n for (const b of document.querySelectorAll('[data-theme-set]')) b.setAttribute('aria-pressed', String(b.getAttribute('data-theme-set') === t));\n}\nfunction setTheme(t) {\n const v = t === 'light' || t === 'dark' ? t : 'system';\n try { if (v === 'system') localStorage.removeItem(THEME_KEY); else localStorage.setItem(THEME_KEY, v); } catch (e) { /* storage off: applies for this page only */ }\n applyTheme(v);\n}\nfor (const b of document.querySelectorAll('[data-theme-set]')) b.addEventListener('click', () => setTheme(b.getAttribute('data-theme-set')));\napplyTheme(storedTheme());\n";
|
|
14
|
+
export declare const SETUP_STYLE = "\n[hidden] { display:none !important; }\nnav.tabs { display:flex; gap:4px; }\nnav.tabs a { color:var(--muted); text-decoration:none; padding:4px 10px; border-radius:6px; border:1px solid transparent; }\nnav.tabs a[aria-current=\"page\"] { color:var(--text); background:var(--bg); border-color:var(--line); font-weight:600; }\na:focus-visible, button:focus-visible, input:focus-visible, summary:focus-visible { outline:2px solid var(--accent); outline-offset:1px; }\n.theme { display:inline-flex; border:1px solid var(--line); border-radius:6px; overflow:hidden; }\n.theme button { border:none; border-radius:0; background:var(--card); color:var(--muted); padding:4px 9px; }\n.theme button[aria-pressed=\"true\"] { background:var(--bg); color:var(--text); font-weight:600; }\n.srow { padding:8px 0; border-top:1px solid var(--line); overflow-wrap:anywhere; } .srow:first-of-type { border-top:none; }\n.srow-head { display:flex; gap:8px; align-items:baseline; flex-wrap:wrap; }\n.srow-ctl { display:flex; gap:8px; flex-wrap:wrap; align-items:center; margin-top:6px; }\n.cmdline { display:inline-flex; gap:6px; align-items:center; flex-wrap:wrap; max-width:100%; } .cmdline code { white-space:normal; overflow-wrap:anywhere; }\n.badge { font-size:11px; padding:0 6px; border-radius:8px; border:1px solid var(--line); color:var(--muted); background:var(--raised); white-space:nowrap; }\nbutton.run { background:var(--accent); border-color:var(--accent); color:var(--card); font-weight:600; }\nbutton.run.danger { background:var(--card); border-color:var(--bad); color:var(--bad); }\nbutton:disabled { opacity:.55; cursor:not-allowed; }\ninput.url { font:inherit; padding:4px 8px; border:1px solid var(--line); border-radius:6px; background:var(--bg); color:var(--text); min-width:min(320px,100%); flex:1; }\ninput.url::placeholder { color:var(--muted); }\n.msg { color:var(--bad); font-size:12px; }\nol.steps { margin:4px 0; padding-left:20px; } ol.steps li { padding:3px 0; overflow-wrap:anywhere; }\ndl.kv { display:grid; grid-template-columns:8em minmax(0,1fr); gap:2px 10px; font-size:13px; margin:6px 0; overflow-wrap:anywhere; } dl.kv dt { color:var(--muted); } dl.kv dd { margin:0; }\ndetails.copies > summary { cursor:pointer; color:var(--muted); font-size:12px; padding:4px 0; } details.copies > div { padding:3px 0; }\ndialog.confirm { background:var(--card); color:var(--text); border:1px solid var(--line); border-radius:10px; padding:16px 18px; width:min(640px, 92vw); box-shadow:0 10px 30px rgb(0 0 0 / .35); }\ndialog.confirm::backdrop { background:rgb(0 0 0 / .5); }\ndialog.confirm h2 { margin-bottom:6px; } dialog.confirm p { margin:6px 0; }\ndialog.confirm .actions { display:flex; gap:8px; justify-content:flex-end; margin-top:14px; }\npre.cmd, pre.out { font-family:ui-monospace, SFMono-Regular, Menlo, monospace; font-size:12px; background:var(--bg); color:var(--text); border:1px solid var(--line); border-radius:6px; padding:8px 10px; white-space:pre-wrap; overflow-wrap:anywhere; margin:6px 0; }\npre.out { max-height:420px; overflow:auto; }\nlabel.extra { display:flex; gap:6px; align-items:flex-start; margin-top:10px; color:var(--bad); font-weight:600; }\n";
|
|
15
|
+
export declare const SETUP_SCRIPT = "\nconst CSRF = (document.querySelector('meta[name=\"prism-csrf\"]') || {}).content || '';\nlet SETUP = null, POLL = null, DIALOG = null, CHECK = null, URL_DRAFT = '';\nconst OUT = { status: el('div', { class: 'srow-head' }), pre: el('pre', { class: 'out', 'aria-live': 'polite' }) };\nconst ACTION_PANEL = el('section', { class: 'card wide', hidden: '' }, el('h2', {}, 'Action output'), OUT.status, OUT.pre,\n el('p', { class: 'note' }, 'Secrets are masked. Each run is logged to ~/.prism/logs/dashboard-actions.log (time, action, repo, exit code).'));\n\nfunction currentView() { return location.hash === '#setup' ? 'setup' : 'dashboard'; }\nfunction showView() {\n const v = currentView();\n document.getElementById('main').hidden = v !== 'dashboard';\n document.getElementById('setup').hidden = v !== 'setup';\n for (const a of document.querySelectorAll('[data-view]')) {\n if (a.getAttribute('data-view') === v) a.setAttribute('aria-current', 'page'); else a.removeAttribute('aria-current');\n }\n}\nwindow.addEventListener('hashchange', showView);\nshowView();\n\n/** GET, or POST JSON with the CSRF header. referrerPolicy same-origin so the browser sends this page's Origin. */\nasync function api(path, body) {\n const init = body === undefined ? { credentials: 'same-origin', cache: 'no-store' } : {\n method: 'POST', credentials: 'same-origin', cache: 'no-store', referrerPolicy: 'same-origin',\n headers: { 'Content-Type': 'application/json', 'X-Prism-CSRF': CSRF }, body: JSON.stringify(body),\n };\n const res = await fetch(path, init);\n let json = null;\n try { json = await res.json(); } catch (e) { json = null; }\n return { ok: res.ok, status: res.status, json: json || { error: 'HTTP ' + res.status } };\n}\n\nfunction ago(iso) {\n const t = Date.parse(iso);\n if (isNaN(t)) return 'unknown';\n const s = Math.max(0, (Date.now() - t) / 1000);\n for (const [n, w] of [[86400, 'day'], [3600, 'hour'], [60, 'minute']]) {\n if (s >= n) { const k = Math.floor(s / n); return k + ' ' + w + (k === 1 ? '' : 's') + ' ago'; }\n }\n return 'just now';\n}\n\nfunction copyButton(cmd) {\n const b = el('button', { type: 'button', title: 'Copy to the clipboard' }, 'Copy');\n b.addEventListener('click', async () => {\n try { await navigator.clipboard.writeText(cmd); b.textContent = 'Copied'; } catch (e) { b.textContent = 'Select the text to copy'; }\n setTimeout(() => { b.textContent = 'Copy'; }, 1500);\n });\n return el('span', { class: 'cmdline' }, el('code', {}, cmd), b);\n}\nconst copyOnly = (cmd, why) => el('div', { class: 'srow-ctl' }, copyButton(cmd), why ? el('span', { class: 'badge' }, why) : null);\n\n/** Client-side copy of the server's collector URL rule; the server checks again. */\nfunction urlProblem(s) {\n if (!s) return 'enter the collector URL';\n if (s.length > 2048) return 'the URL is longer than 2048 characters';\n let u;\n try { u = new URL(s); } catch (e) { return 'not a valid URL'; }\n if (u.protocol !== 'https:') return 'the URL must use https';\n if (!u.hostname) return 'the URL has no hostname';\n if (u.username || u.password) return 'the URL must not contain a user name or password';\n return null;\n}\n\nconst actionDef = (id) => SETUP && (SETUP.actions || []).find((a) => a.id === id);\n\n/** A Run button for an allowlisted action; null when the action is not available here. */\nfunction runButton(id, o) {\n const a = actionDef(id);\n if (!a || !a.available) return null;\n o = o || {};\n const b = el('button', { type: 'button', class: 'run' + (a.confirm ? ' danger' : '') }, o.label || 'Run');\n b.addEventListener('click', () => {\n let url;\n if (a.url) {\n url = o.urlInput ? o.urlInput.value : '';\n const p = urlProblem(url);\n if (p) { if (o.msg) o.msg.textContent = p; return; }\n if (o.msg) o.msg.textContent = '';\n }\n confirmRun(a, { repo: o.repo, where: o.where, url });\n });\n return b;\n}\n\nfunction confirmRun(a, o) {\n if (!DIALOG) { DIALOG = el('dialog', { class: 'confirm', 'aria-labelledby': 'confirm-title' }); document.body.append(DIALOG); }\n const cmd = a.command + (o.url ? ' ' + o.url : '');\n const run = el('button', { type: 'button', class: 'run' + (a.confirm ? ' danger' : '') }, 'Run');\n const cancel = el('button', { type: 'button' }, 'Cancel');\n const kids = [el('h2', { id: 'confirm-title' }, a.label), el('p', {}, 'This will run:'), el('pre', { class: 'cmd' }, cmd),\n el('p', { class: 'fix' }, 'in ' + txt(o.where || (SETUP && SETUP.cwd) || '')),\n el('p', { class: 'note' }, a.readOnlyAws ? 'Read-only in AWS: describe, get and list calls only. Nothing in AWS is changed.' : 'Changes setup on this machine only. No AWS calls.'),\n el('p', { class: 'note' }, 'It runs without a terminal, so it cannot ask questions; it is stopped after ' + Math.max(1, Math.round(a.timeoutSec / 60)) + ' min.')];\n if (a.confirm) {\n const cb = el('input', { type: 'checkbox' });\n run.disabled = true;\n cb.addEventListener('change', () => { run.disabled = !cb.checked; });\n kids.push(el('label', { class: 'extra' }, cb, 'Yes, remove the scheduled codeburn sync push from this machine'));\n }\n kids.push(el('div', { class: 'actions' }, cancel, run));\n DIALOG.replaceChildren(...kids);\n cancel.addEventListener('click', () => DIALOG.close());\n run.addEventListener('click', () => { DIALOG.close(); startAction(a, o); });\n DIALOG.showModal();\n cancel.focus();\n}\n\nasync function startAction(a, o) {\n const body = { action: a.id };\n if (a.repo) body.repo = o.repo;\n if (a.url) body.url = o.url;\n if (a.confirm) body.confirm = true;\n ACTION_PANEL.hidden = false;\n OUT.status.replaceChildren(pill('warn', 'starting'), el('code', {}, a.command));\n OUT.pre.textContent = '';\n let r;\n try { r = await api('/api/action', body); } catch (e) { r = { ok: false, json: { error: e.message } }; }\n if (!r.ok) { OUT.status.replaceChildren(pill('error', 'refused'), txt(r.json.error)); return; }\n showStatus(r.json);\n ACTION_PANEL.scrollIntoView({ block: 'nearest' });\n poll();\n}\n\nfunction showStatus(s) {\n if (!s || s.state === 'idle') return;\n ACTION_PANEL.hidden = false;\n const p = s.state === 'running' ? pill('warn', 'running') : s.exitCode === 0 ? pill('ok', 'exit 0') : pill('error', s.timedOut ? 'timed out' : 'exit ' + s.exitCode);\n OUT.status.replaceChildren(p, el('code', {}, txt(s.command)), el('span', { class: 'fix' }, (s.repo ? 'in ' + txt(s.repo) + ' \u00B7 ' : '') + 'started ' + when(s.startedAt)));\n OUT.pre.textContent = txt(s.output) || (s.state === 'running' ? '(no output yet)' : '(no output)');\n OUT.pre.scrollTop = OUT.pre.scrollHeight;\n}\n\nfunction poll() {\n clearTimeout(POLL);\n POLL = setTimeout(async () => {\n try {\n const r = await api('/api/action/status');\n if (r.ok) showStatus(r.json);\n if (r.ok && r.json.state !== 'running') { load(); return; }\n } catch (e) { /* retry */ }\n poll();\n }, 1000);\n}\n\nfunction srow(status, label, detail, ...ctl) {\n const c = ctl.filter(Boolean);\n return el('div', { class: 'srow' }, el('div', { class: 'srow-head' }, status, el('b', {}, label)),\n detail ? el('div', { class: 'fix' }, detail) : null, c.length ? el('div', { class: 'srow-ctl' }, c) : null);\n}\n\nfunction machineSection(data) {\n const s = el('section', { class: 'card' }, el('h2', {}, 'This machine'));\n for (const i of data.machine.setup) {\n if (i.id === 'codeburn-sync' || i.id === 'aws-credentials') continue;\n let ctl = null;\n if (i.id === 'codewatch-global' && i.status !== 'ok') {\n const elsewhere = (data.machine.globalHooks || {}).state === 'elsewhere';\n ctl = elsewhere ? copyOnly('prism-cli enable codewatch --global --force', 'replaces another global hooks path')\n : runButton('codewatch-global', { label: 'Enable for every repo' });\n } else if (i.fix) {\n ctl = copyOnly(i.fix, i.id === 'gitleaks' ? 'installs software' : i.id === 'codeburn' ? 'asks before installing' : null);\n }\n s.append(srow(pill(i.status), i.label, i.detail, ctl));\n }\n s.append(el('p', { class: 'note' }, 'AWS credentials: see the AWS section.'));\n return s;\n}\n\nconst CW_PILL = { ok: 'ok', warn: 'warn', missing: 'missing', 'opted-out': 'skipped' };\nfunction reposSection(data) {\n const s = el('section', { class: 'card wide' }, el('h2', {}, 'Repos'));\n const recheck = el('button', { type: 'button' }, 'Re-check status');\n recheck.addEventListener('click', async () => { recheck.disabled = true; try { await api('/api/recheck'); } catch (e) { /* load() reports */ } await load(); });\n s.append(el('div', { class: 'srow-ctl' }, recheck, el('span', { class: 'fix' }, 'Codewatch hooks and Security Agent setup per repo, from local files.')));\n const ids = SETUP.repoIds || {}, cds = SETUP.repoCd || {}, cp = SETUP.copy || {};\n const rowOf = (r) => {\n const cw = el('div', {}, pill(CW_PILL[r.status] || 'missing', r.status === 'opted-out' ? 'opted out' : r.status + (r.status === 'ok' && r.global ? ' (global)' : '')), ' ',\n el('span', { class: 'fix' }, r.status === 'ok' ? 'reviewer ' + txt(r.reviewer || 'none') : txt(r.detail)));\n if ((r.status === 'missing' || r.status === 'warn') && ids[r.path]) cw.append(el('div', { class: 'srow-ctl' }, runButton('codewatch-repo', { repo: ids[r.path], where: r.path, label: 'Enable codewatch' })));\n const sec = r.security || { status: 'missing' };\n const sc = el('div', {}, pill(sec.status === 'missing' ? 'missing' : 'ok', sec.status === 'in-use' ? 'in use' : sec.status));\n const cd = cds[r.path] ? cds[r.path] + ' && ' : '';\n if (sec.status === 'missing') sc.append(copyOnly(sec.fix || cd + cp.enableSecurity, 'AWS'));\n else sc.append(el('details', { class: 'copies' }, el('summary', {}, 'Security Agent commands (AWS, copy only)'),\n [cp.codeReview, cp.threatModel, cp.pentest].map((c) => el('div', {}, copyButton(cd + c)))));\n return [repoName(r), cw, sc];\n };\n const rows = data.overview.setup || [];\n const shown = rows.filter((r) => !r.collapsed), hidden = rows.filter((r) => r.collapsed);\n if (shown.length) s.append(table(['Repo', 'Codewatch', 'Security Agent'], shown.map(rowOf)));\n else if (!hidden.length) s.append(el('p', { class: 'empty' }, 'No git repositories found under ' + txt(data.scope.root_display) + '.'));\n if (hidden.length) s.append(el('details', { class: 'missing-group', 'data-key': 'setup-view:other' },\n el('summary', {}, hidden.length + ' other repo' + (hidden.length === 1 ? '' : 's') + ': not set up'), table(['Repo', 'Codewatch', 'Security Agent'], hidden.map(rowOf))));\n return s;\n}\n\nfunction syncSection(data) {\n const s = el('section', { class: 'card' }, el('h2', {}, 'Usage sync (codeburn)'));\n const item = data.machine.setup.find((i) => i.id === 'codeburn-sync');\n const mode = (data.machine.codeburnSync || {}).mode || 'local';\n if (item) s.append(srow(pill(item.status), item.label, item.detail));\n if (item && item.help) s.append(helpBlock(item.help));\n s.append(el('p', { class: 'fix' }, 'Sync mode: ' + mode));\n s.append(srow(null, 'Local only', 'codeburn usage stays on this machine; no endpoint and no schedule.', runButton('codeburn-local', { label: 'Keep usage local' })));\n const input = el('input', { type: 'url', class: 'url', placeholder: 'collector URL (https, from your platform team)', maxlength: '2048', 'aria-label': 'Collector URL', spellcheck: 'false', autocomplete: 'off' });\n input.value = URL_DRAFT;\n input.addEventListener('input', () => { URL_DRAFT = input.value; });\n const msg = el('span', { class: 'msg', role: 'status' });\n s.append(srow(null, \"Sync to my team's PRISM\", 'Sets up codeburn sync for the collector URL and schedules the push. codeburn opens a browser to sign in.',\n input, runButton('codeburn-team', { label: 'Sync to team', urlInput: input, msg }), msg));\n if (actionDef('codeburn-systemd') && actionDef('codeburn-systemd').available) {\n s.append(srow(null, 'Use a systemd user timer', mode === 'team' ? 'Schedules the sync push with a systemd user timer; your crontab is not touched.' : 'Team mode only: set up sync to your team first.',\n mode === 'team' ? runButton('codeburn-systemd', { label: 'Use systemd timer' }) : null));\n }\n s.append(srow(null, 'Remove the sync schedule', 'Removes the scheduled sync push (cron entry or systemd timer). codeburn and its sync config stay.', runButton('codeburn-remove', { label: 'Remove schedule' })));\n s.append(srow(null, 'Status', 'codeburn, sync and schedule status.', runButton('codeburn-status', { label: 'Show status' })));\n s.append(srow(null, 'Platform team: add a developer', 'Creates a telemetry account in AWS (Cognito), so it is copy only.', copyOnly((SETUP.copy || {}).addDeveloper, 'AWS write')));\n return s;\n}\n\nconst STEP_PILL = { used: ['ok', 'used'], skipped: ['skipped', 'skipped'], 'not-set': ['info', 'not set'], 'n/a': ['skipped', 'n/a'] };\nfunction kv(pairs) { return el('dl', { class: 'kv' }, pairs.filter(Boolean).flatMap(([k, v]) => [el('dt', {}, k), el('dd', {}, v)])); }\n\nfunction checkResult(r) {\n if (!r) return null;\n if (!r.ok) return el('div', {}, pill('error', 'refused'), ' ', txt(r.json.error));\n const j = r.json;\n if (j.ok) {\n const m = j.matches === true ? pill('ok', 'matches the saved account') : j.matches === false ? pill('error', 'does not match saved account ' + txt(j.savedAccount)) : pill('info', 'no saved account');\n return el('div', {}, pill('ok', 'credentials work'), kv([['Account', txt(j.account)], ['Caller', txt(j.arn)], ['Region', txt(j.region || 'not set')], ['Saved', m]]), el('p', { class: 'note' }, txt(j.note)));\n }\n return el('div', {}, pill('error', j.expired ? 'expired or invalid' : 'check failed'), ' ', el('span', {}, txt(j.error)),\n el('div', { class: 'help' }, 'Fixes:', el('ul', { class: 'help-steps' }, (j.fixes || []).map((f) => el('li', {}, /^(aws|prism-cli) /.test(f) ? copyButton(f) : txt(f))))));\n}\n\nfunction awsSection(a) {\n const s = el('section', { class: 'card wide' }, el('h2', {}, 'AWS'));\n s.append(el('p', { class: 'note' }, 'From the environment and local config files only: no AWS call until you press Check now. Only setting names are read from ~/.aws; secret values are never read or shown.'));\n s.append(el('div', { class: 'srow' }, el('div', {}, 'prism-cli would use ', el('b', {}, a.profile ? 'profile ' + txt(a.profile) : txt(a.source)), ' \u00B7 ', el('span', { class: 'badge' }, txt(a.credentialLabel))),\n el('div', { class: 'fix' }, 'chosen by ' + txt(a.source) + ' \u00B7 region ' + (a.region ? txt(a.region.value) + ' (' + txt(a.region.source) + ')' : 'not set'))));\n for (const w of a.warnings || []) s.append(el('div', { class: 'callout warn', role: 'note' }, txt(w)));\n s.append(el('h3', {}, 'Resolution order'), el('ol', { class: 'steps' }, (a.steps || []).map((st) => {\n const p = STEP_PILL[st.state] || ['skipped', st.state];\n return el('li', {}, el('b', {}, txt(st.label)), ' ', pill(p[0], p[1]), ' ', el('span', { class: 'fix' }, txt(st.detail)));\n })));\n s.append(el('h3', {}, 'Saved aws connect'));\n if (a.saved) {\n const v = a.saved;\n s.append(kv([['Account', el('span', {}, txt(v.account), v.alias ? el('span', { class: 'tag' }, txt(v.alias)) : null)], ['Profile', txt(v.profile) + (v.inUse ? '' : ' (not in use)')],\n ['Region', txt(v.region)], ['Verified', v.verifiedAt ? el('span', { title: when(v.verifiedAt) }, ago(v.verifiedAt)) : 'unknown'], ['File', txt(v.path) + ' (' + v.scope + ')'],\n ['Admin / org', 'not stored by aws connect (it prints them when it runs)']]));\n } else s.append(el('p', { class: 'empty-line' }, 'None saved. ', copyButton((SETUP.copy || {}).awsConnect)));\n const btn = el('button', { type: 'button', class: 'run' }, 'Check now');\n const out = el('div', { 'aria-live': 'polite' }, checkResult(CHECK));\n btn.addEventListener('click', async () => {\n btn.disabled = true;\n out.replaceChildren(el('span', { class: 'fix' }, 'checking...'));\n try { CHECK = await api('/api/check-now', {}); } catch (e) { CHECK = { ok: false, json: { error: e.message } }; }\n out.replaceChildren(checkResult(CHECK));\n btn.disabled = false;\n if (CHECK.ok && CHECK.json.updated) load();\n });\n s.append(el('h3', {}, 'Check now'), el('div', { class: 'srow-ctl' }, btn, el('span', { class: 'fix' }, 'One read-only call: aws sts get-caller-identity, as aws connect does it.')), out);\n s.append(el('h3', {}, 'Read-only AWS commands'),\n srow(null, 'Account check', 'Every call is a describe, get or list; the result is saved to .prism/results/.', runButton('aws-check', { label: 'Run aws check' })),\n srow(null, 'Team stack info', 'Reads the PRISM stack outputs (collector URL, user pool).', runButton('team-info', { label: 'Run team info' })));\n const cp = SETUP.copy || {};\n s.append(el('h3', {}, 'Copy only (writes to AWS or needs a terminal)'),\n srow(null, 'Choose and save the AWS profile', 'Asks questions on a terminal.', copyOnly(cp.awsConnect, 'terminal')),\n srow(null, 'Set up the Security Agent in a repo', 'Creates AWS resources.', copyOnly(cp.enableSecurity, 'AWS write')),\n srow(null, 'Add a developer telemetry account', 'Creates a Cognito user.', copyOnly(cp.addDeveloper, 'AWS write')),\n srow(null, 'Security Agent runs', 'Uploads code and starts AWS jobs.', copyOnly(cp.codeReview, 'AWS write'), copyOnly(cp.threatModel, 'AWS write'), copyOnly(cp.pentest, 'AWS write')));\n return s;\n}\n\nasync function renderSetup(data) {\n try { const r = await api('/api/setup'); if (r.ok) SETUP = r.json; } catch (e) { /* keep the last good copy */ }\n const root = document.getElementById('setup');\n const openState = new Map([...root.querySelectorAll('details[data-key]')].map((d) => [d.getAttribute('data-key'), d.open]));\n root.replaceChildren();\n if (!SETUP) { root.append(el('section', { class: 'card wide' }, pill('error'), ' could not load the setup data')); return; }\n root.append(machineSection(data), syncSection(data), reposSection(data), awsSection(SETUP.aws || {}), ACTION_PANEL);\n for (const d of root.querySelectorAll('details[data-key]')) { const k = d.getAttribute('data-key'); if (openState.has(k)) d.open = openState.get(k); }\n}\n// A reload while an action runs picks its output back up.\napi('/api/action/status').then((r) => { if (r.ok && r.json.state === 'running') { showStatus(r.json); poll(); } }).catch(() => {});\n";
|
|
16
|
+
//# sourceMappingURL=setup-page.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup-page.d.ts","sourceRoot":"","sources":["../../../src/dashboard/setup-page.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,uGAAuG;AACvG,eAAO,MAAM,UAAU,gKAAgK,CAAC;AAExL,0HAA0H;AAC1H,eAAO,MAAM,YAAY,m9BAexB,CAAC;AAEF,eAAO,MAAM,WAAW,inGA8BvB,CAAC;AAEF,eAAO,MAAM,YAAY,qpkBA8RxB,CAAC"}
|