@skillcdn/console 0.1.5 → 0.1.7
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/README.md +50 -2
- package/SKILLCDN.md +29 -0
- package/dist/cli/cli.d.ts +3 -0
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +61 -0
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +5 -0
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/main.js +2 -0
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/serve.d.ts +37 -0
- package/dist/cli/serve.d.ts.map +1 -0
- package/dist/cli/serve.js +218 -0
- package/dist/cli/serve.js.map +1 -0
- package/dist/console.css +33 -10
- package/dist/console.d.ts +6 -0
- package/dist/console.d.ts.map +1 -1
- package/dist/console.js +78 -13
- package/dist/console.js.map +1 -1
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +5 -1
- package/dist/data.js.map +1 -1
- package/dist/i18n/en.d.ts +4 -0
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +4 -0
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/ko.d.ts.map +1 -1
- package/dist/i18n/ko.js +4 -0
- package/dist/i18n/ko.js.map +1 -1
- package/dist/schemas.d.ts +1 -0
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +5 -0
- package/dist/schemas.js.map +1 -1
- package/dist/web.d.ts +16 -0
- package/dist/web.d.ts.map +1 -0
- package/dist/web.js +149 -0
- package/dist/web.js.map +1 -0
- package/docs/specs/cli.md +92 -0
- package/docs/specs/rest.md +220 -0
- package/package.json +14 -3
- package/skills/building-a-console/SKILL.md +79 -0
- package/skills/working-the-board/SKILL.md +78 -0
- package/src/api.ts +235 -0
- package/src/cli/cli.ts +1114 -0
- package/src/cli/credentials.ts +68 -0
- package/src/cli/directory.ts +59 -0
- package/src/cli/format.ts +284 -0
- package/src/cli/help.ts +326 -0
- package/src/cli/main.ts +39 -0
- package/src/cli/serve.ts +276 -0
- package/src/cli/terminal.ts +28 -0
- package/src/client.ts +533 -0
- package/src/components/board.tsx +112 -0
- package/src/components/connect.tsx +126 -0
- package/src/components/decision-form.tsx +136 -0
- package/src/components/decision-list.tsx +278 -0
- package/src/components/documents.tsx +569 -0
- package/src/components/event-feed.tsx +129 -0
- package/src/components/markdown.tsx +72 -0
- package/src/components/people.tsx +59 -0
- package/src/components/projects.tsx +364 -0
- package/src/components/runs.tsx +175 -0
- package/src/components/shell.tsx +284 -0
- package/src/components/sign-in.tsx +61 -0
- package/src/components/skills.tsx +78 -0
- package/src/components/task-form.tsx +229 -0
- package/src/components/task-view.tsx +272 -0
- package/src/components/tokens.tsx +223 -0
- package/src/components/ui.tsx +219 -0
- package/src/connect-page.tsx +148 -0
- package/src/connect.ts +27 -0
- package/src/console.tsx +1166 -0
- package/src/data.ts +493 -0
- package/src/docs-page.tsx +334 -0
- package/src/documents.ts +76 -0
- package/src/i18n/en.ts +537 -0
- package/src/i18n/index.ts +76 -0
- package/src/i18n/ko.ts +511 -0
- package/src/index.ts +142 -0
- package/src/limits.ts +61 -0
- package/src/router.ts +295 -0
- package/src/routes.ts +106 -0
- package/src/schemas.ts +776 -0
- package/src/styles/console.css +2432 -0
- package/src/text.ts +33 -0
- package/src/use-action.ts +37 -0
- package/src/vocabulary.ts +109 -0
- package/src/web.ts +170 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
import { Readable } from "node:stream";
|
|
3
|
+
import { pipeline } from "node:stream/promises";
|
|
4
|
+
// A console of a person's own, served from their machine (ADR-0014): the files of a build, and
|
|
5
|
+
// everything under /api/ carried to the organization's console with the token the command
|
|
6
|
+
// keeps. The page and the API share one origin on the loopback, so the page needs no token and
|
|
7
|
+
// the organization's console no allowance: it sees a token's requests, as it sees the command's.
|
|
8
|
+
/** Where the command listens when nothing is said: next to the api role's 11199 and the dev UI's 11198. */
|
|
9
|
+
export const DEFAULT_SERVE_PORT = 11197;
|
|
10
|
+
/** The one address the command listens on. */
|
|
11
|
+
export const LOOPBACK = "127.0.0.1";
|
|
12
|
+
/** Headers that belong to one connection and never travel over the next. */
|
|
13
|
+
const HOP_BY_HOP = new Set([
|
|
14
|
+
"connection",
|
|
15
|
+
"keep-alive",
|
|
16
|
+
"proxy-authenticate",
|
|
17
|
+
"proxy-authorization",
|
|
18
|
+
"proxy-connection",
|
|
19
|
+
"te",
|
|
20
|
+
"trailer",
|
|
21
|
+
"transfer-encoding",
|
|
22
|
+
"upgrade",
|
|
23
|
+
]);
|
|
24
|
+
/**
|
|
25
|
+
* What of the browser's request does not go to the console: the loopback's host, anything of a
|
|
26
|
+
* session (the token is the credential), what names the page's origin (a token's request needs
|
|
27
|
+
* none), and the encodings the browser takes, since what comes back is passed on decoded.
|
|
28
|
+
*/
|
|
29
|
+
const NOT_FORWARDED = new Set([
|
|
30
|
+
"host",
|
|
31
|
+
"cookie",
|
|
32
|
+
"authorization",
|
|
33
|
+
"origin",
|
|
34
|
+
"referer",
|
|
35
|
+
"accept-encoding",
|
|
36
|
+
]);
|
|
37
|
+
/** What of the console's answer does not go to the browser: a cookie would be the loopback's, and the body arrives decoded. */
|
|
38
|
+
const NOT_ANSWERED = new Set([
|
|
39
|
+
"set-cookie",
|
|
40
|
+
"content-encoding",
|
|
41
|
+
"content-length",
|
|
42
|
+
]);
|
|
43
|
+
const json = (status, code, message) => new Response(JSON.stringify({ error: { code, message } }), {
|
|
44
|
+
status,
|
|
45
|
+
headers: { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" },
|
|
46
|
+
});
|
|
47
|
+
const plain = (status, message) => new Response(`${message}\n`, {
|
|
48
|
+
status,
|
|
49
|
+
headers: { "content-type": "text/plain; charset=utf-8", "cache-control": "no-store" },
|
|
50
|
+
});
|
|
51
|
+
/**
|
|
52
|
+
* Whether a request to the API comes from the console's own page, or from no page at all.
|
|
53
|
+
* Browsers say which site a request comes from, and one from another site is that site's
|
|
54
|
+
* request made with the person's standing: refused. A browser too old to say names the page's
|
|
55
|
+
* origin on a request that changes something, which is compared instead. A request that says
|
|
56
|
+
* nothing of either is not a browser's.
|
|
57
|
+
*/
|
|
58
|
+
function fromOwnPage(request, origin) {
|
|
59
|
+
const site = request.headers.get("sec-fetch-site");
|
|
60
|
+
if (site !== null) {
|
|
61
|
+
return site === "same-origin" || site === "none";
|
|
62
|
+
}
|
|
63
|
+
const from = request.headers.get("origin");
|
|
64
|
+
return from === null || from === origin;
|
|
65
|
+
}
|
|
66
|
+
async function forward(request, url, upstream, options) {
|
|
67
|
+
const headers = new Headers();
|
|
68
|
+
request.headers.forEach((value, name) => {
|
|
69
|
+
if (!HOP_BY_HOP.has(name) && !NOT_FORWARDED.has(name) && !name.startsWith("sec-")) {
|
|
70
|
+
headers.set(name, value);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
headers.set("authorization", `Bearer ${options.token}`);
|
|
74
|
+
headers.set("accept-encoding", "identity");
|
|
75
|
+
const withBody = request.method !== "GET" && request.method !== "HEAD";
|
|
76
|
+
let answer;
|
|
77
|
+
try {
|
|
78
|
+
answer = await options.fetch(`${upstream}${url.pathname}${url.search}`, {
|
|
79
|
+
method: request.method,
|
|
80
|
+
headers,
|
|
81
|
+
body: withBody ? request.body : undefined,
|
|
82
|
+
redirect: "manual",
|
|
83
|
+
signal: request.signal,
|
|
84
|
+
// A streamed body has to be declared as such.
|
|
85
|
+
...(withBody ? { duplex: "half" } : {}),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return json(502, "network", "The console could not be reached.");
|
|
90
|
+
}
|
|
91
|
+
const out = new Headers();
|
|
92
|
+
answer.headers.forEach((value, name) => {
|
|
93
|
+
if (!HOP_BY_HOP.has(name) && !NOT_ANSWERED.has(name)) {
|
|
94
|
+
out.set(name, value);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
return new Response(answer.body, { status: answer.status, headers: out });
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* What the command answers: the API carried to the console with the token, for the page's own
|
|
101
|
+
* requests; the build's files and its page for everything else, when there is a build; and
|
|
102
|
+
* nothing to a request whose host is not this loopback, which is a page elsewhere whose name
|
|
103
|
+
* was made to resolve here.
|
|
104
|
+
*/
|
|
105
|
+
export function createServeHandler(options) {
|
|
106
|
+
const hosts = new Set([
|
|
107
|
+
`${LOOPBACK}:${options.port}`,
|
|
108
|
+
`localhost:${options.port}`,
|
|
109
|
+
`[::1]:${options.port}`,
|
|
110
|
+
]);
|
|
111
|
+
const upstream = new URL(options.upstream).origin;
|
|
112
|
+
return async (request) => {
|
|
113
|
+
const url = new URL(request.url);
|
|
114
|
+
if (!hosts.has(url.host)) {
|
|
115
|
+
return plain(421, "This address is not the console's.");
|
|
116
|
+
}
|
|
117
|
+
if (url.pathname === "/api" || url.pathname.startsWith("/api/")) {
|
|
118
|
+
if (!fromOwnPage(request, `http://${url.host}`)) {
|
|
119
|
+
return json(403, "auth.forbidden_origin", "This request must come from the console's own pages.");
|
|
120
|
+
}
|
|
121
|
+
return forward(request, url, upstream, options);
|
|
122
|
+
}
|
|
123
|
+
if (options.web !== undefined) {
|
|
124
|
+
const answer = await options.web.respond(request);
|
|
125
|
+
if (answer !== undefined) {
|
|
126
|
+
return answer;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return plain(404, options.web === undefined
|
|
130
|
+
? "Nothing is served here but the API, under /api/."
|
|
131
|
+
: "Nothing here.");
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
async function answer(handler, req, res) {
|
|
135
|
+
// A browser that goes away takes its request with it, a stream of events included.
|
|
136
|
+
const controller = new AbortController();
|
|
137
|
+
res.on("close", () => {
|
|
138
|
+
if (!res.writableFinished) {
|
|
139
|
+
controller.abort();
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
const method = req.method ?? "GET";
|
|
143
|
+
const withBody = method !== "GET" && method !== "HEAD";
|
|
144
|
+
let request;
|
|
145
|
+
try {
|
|
146
|
+
const headers = new Headers();
|
|
147
|
+
for (const [name, value] of Object.entries(req.headers)) {
|
|
148
|
+
if (Array.isArray(value)) {
|
|
149
|
+
for (const each of value) {
|
|
150
|
+
headers.append(name, each);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
else if (value !== undefined) {
|
|
154
|
+
headers.set(name, value);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
request = new Request(`http://${req.headers.host ?? LOOPBACK}${req.url ?? "/"}`, {
|
|
158
|
+
method,
|
|
159
|
+
headers,
|
|
160
|
+
body: withBody ? Readable.toWeb(req) : undefined,
|
|
161
|
+
signal: controller.signal,
|
|
162
|
+
...(withBody ? { duplex: "half" } : {}),
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
res.writeHead(400, { "content-type": "text/plain; charset=utf-8" });
|
|
167
|
+
res.end("The request was not understood.\n");
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
let response;
|
|
171
|
+
try {
|
|
172
|
+
response = await handler(request);
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
response = plain(500, "Something went wrong.");
|
|
176
|
+
}
|
|
177
|
+
res.statusCode = response.status;
|
|
178
|
+
response.headers.forEach((value, name) => {
|
|
179
|
+
res.setHeader(name, value);
|
|
180
|
+
});
|
|
181
|
+
if (response.body === null || method === "HEAD") {
|
|
182
|
+
res.end();
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
try {
|
|
186
|
+
await pipeline(Readable.fromWeb(response.body), res);
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
// The browser went away before the answer ended: nothing is owed to it.
|
|
190
|
+
res.destroy();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/** Listens on the loopback, and only there, and answers every request through the handler. */
|
|
194
|
+
export const listenOn = (handlerFor, port) => new Promise((resolve, reject) => {
|
|
195
|
+
let handler;
|
|
196
|
+
const server = createServer((req, res) => {
|
|
197
|
+
void answer(handler ?? (() => Promise.resolve(plain(503, "Not ready."))), req, res);
|
|
198
|
+
});
|
|
199
|
+
server.once("error", reject);
|
|
200
|
+
server.listen(port, LOOPBACK, () => {
|
|
201
|
+
server.off("error", reject);
|
|
202
|
+
const address = server.address();
|
|
203
|
+
const bound = typeof address === "object" && address !== null ? address.port : port;
|
|
204
|
+
handler = handlerFor(bound);
|
|
205
|
+
const closed = new Promise((done) => {
|
|
206
|
+
server.once("close", () => done());
|
|
207
|
+
});
|
|
208
|
+
resolve({
|
|
209
|
+
url: `http://${LOOPBACK}:${bound}/`,
|
|
210
|
+
closed,
|
|
211
|
+
close: () => new Promise((done, fail) => {
|
|
212
|
+
server.closeAllConnections();
|
|
213
|
+
server.close((error) => (error === undefined ? done() : fail(error)));
|
|
214
|
+
}),
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
//# sourceMappingURL=serve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serve.js","sourceRoot":"","sources":["../../src/cli/serve.ts"],"names":[],"mappings":"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"}
|
package/dist/console.css
CHANGED
|
@@ -146,8 +146,8 @@
|
|
|
146
146
|
--sc-width-aside: 18rem;
|
|
147
147
|
--sc-column-width: 13.5rem;
|
|
148
148
|
--sc-header-height: 3.75rem;
|
|
149
|
-
--sc-brand-size:
|
|
150
|
-
--sc-brand-size-compact:
|
|
149
|
+
--sc-brand-size: 1rem;
|
|
150
|
+
--sc-brand-size-compact: 0.875rem;
|
|
151
151
|
--sc-control-size: 2.5rem;
|
|
152
152
|
--sc-focus-ring: 2px solid var(--sc-color-accent);
|
|
153
153
|
--sc-transition: 120ms ease-out;
|
|
@@ -1631,7 +1631,7 @@ table {
|
|
|
1631
1631
|
position: relative;
|
|
1632
1632
|
display: grid;
|
|
1633
1633
|
place-items: center;
|
|
1634
|
-
height:
|
|
1634
|
+
height: 6.5rem;
|
|
1635
1635
|
}
|
|
1636
1636
|
|
|
1637
1637
|
/* The light behind the mark, and the rings that leave it. */
|
|
@@ -1653,8 +1653,8 @@ table {
|
|
|
1653
1653
|
position: relative;
|
|
1654
1654
|
display: grid;
|
|
1655
1655
|
place-items: center;
|
|
1656
|
-
width:
|
|
1657
|
-
height:
|
|
1656
|
+
width: 3.5rem;
|
|
1657
|
+
height: 3.5rem;
|
|
1658
1658
|
border: 1px solid var(--sc-color-border-strong);
|
|
1659
1659
|
border-radius: 28%;
|
|
1660
1660
|
background: var(--sc-color-bg);
|
|
@@ -1689,14 +1689,14 @@ table {
|
|
|
1689
1689
|
}
|
|
1690
1690
|
|
|
1691
1691
|
.sc-sign-in-symbol {
|
|
1692
|
-
width:
|
|
1693
|
-
height:
|
|
1692
|
+
width: 2rem;
|
|
1693
|
+
height: 2rem;
|
|
1694
1694
|
}
|
|
1695
1695
|
|
|
1696
1696
|
.sc-sign-in-wordmark {
|
|
1697
1697
|
display: block;
|
|
1698
1698
|
max-width: 12rem;
|
|
1699
|
-
height: 1.
|
|
1699
|
+
height: 1.25rem;
|
|
1700
1700
|
margin-inline: auto;
|
|
1701
1701
|
}
|
|
1702
1702
|
|
|
@@ -2332,6 +2332,25 @@ table {
|
|
|
2332
2332
|
gap: var(--sc-space-2);
|
|
2333
2333
|
}
|
|
2334
2334
|
|
|
2335
|
+
/* Under the wide layout a task's facts come first: changing its state and reaching its forms is
|
|
2336
|
+
what a phone is opened for, and two to a row they stay short. A document keeps its text first. */
|
|
2337
|
+
@media (max-width: 63.99rem) {
|
|
2338
|
+
.sc-task .sc-task-aside {
|
|
2339
|
+
order: -1;
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2342
|
+
.sc-task .sc-task-aside .sc-facts {
|
|
2343
|
+
grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
/* Where there is no pointer to hover with, the control on a card is as tall as a finger needs. */
|
|
2348
|
+
@media (hover: none) {
|
|
2349
|
+
.sc-card-move select {
|
|
2350
|
+
min-height: 2rem;
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2335
2354
|
/* Small screens. */
|
|
2336
2355
|
|
|
2337
2356
|
@media (max-width: 40rem) {
|
|
@@ -2358,12 +2377,15 @@ table {
|
|
|
2358
2377
|
display: none;
|
|
2359
2378
|
}
|
|
2360
2379
|
|
|
2380
|
+
/* Tabs that do not fit wrap, so that none is hidden off the edge. */
|
|
2361
2381
|
.sc-tabs {
|
|
2362
2382
|
padding-inline: var(--sc-space-4);
|
|
2383
|
+
overflow: visible;
|
|
2363
2384
|
}
|
|
2364
2385
|
|
|
2365
2386
|
.sc-tabs-inner {
|
|
2366
|
-
|
|
2387
|
+
flex-wrap: wrap;
|
|
2388
|
+
gap: 0 var(--sc-space-4);
|
|
2367
2389
|
}
|
|
2368
2390
|
|
|
2369
2391
|
.sc-menu-summary .sc-person-login {
|
|
@@ -2374,7 +2396,8 @@ table {
|
|
|
2374
2396
|
padding: var(--sc-space-5) var(--sc-space-4) var(--sc-space-7);
|
|
2375
2397
|
}
|
|
2376
2398
|
|
|
2377
|
-
.sc-page-title
|
|
2399
|
+
.sc-page-title,
|
|
2400
|
+
.sc-task-title {
|
|
2378
2401
|
font-size: var(--sc-text-xl);
|
|
2379
2402
|
}
|
|
2380
2403
|
|
package/dist/console.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { type SkillListProps } from "./components/skills.js";
|
|
|
11
11
|
import { type TaskViewProps } from "./components/task-view.js";
|
|
12
12
|
import { type TokenListProps } from "./components/tokens.js";
|
|
13
13
|
import { type LanguagePack } from "./i18n/index.js";
|
|
14
|
+
import { type Route } from "./router.js";
|
|
14
15
|
/** The components a custom console may replace, each with the props the default one takes. */
|
|
15
16
|
export interface ConsoleComponents {
|
|
16
17
|
readonly Shell: ComponentType<ShellProps>;
|
|
@@ -48,5 +49,10 @@ export interface ConsoleApp {
|
|
|
48
49
|
/** Renders the console into `container`. Returns the way to take it down again. */
|
|
49
50
|
mount(container: Element): () => void;
|
|
50
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* The pages a console that holds a token does not offer: the tokens themselves and
|
|
54
|
+
* configuring, which are a person's own doing on the console's own pages, signed in.
|
|
55
|
+
*/
|
|
56
|
+
export declare function onlyOnOwnPages(route: Route): boolean;
|
|
51
57
|
export declare function createConsole(config?: ConsoleConfig): ConsoleApp;
|
|
52
58
|
//# sourceMappingURL=console.d.ts.map
|
package/dist/console.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"console.d.ts","sourceRoot":"","sources":["../src/console.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,aAAa,EAAyD,MAAM,OAAO,CAAC;AAElG,OAAO,EACL,KAAK,aAAa,EAenB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAS,KAAK,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAG/D,OAAO,EAA8B,KAAK,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AACnG,OAAO,EAAa,KAAK,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5E,OAAO,EAAc,KAAK,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC1E,OAAO,EAEL,KAAK,eAAe,EAGpB,KAAK,gBAAgB,EACtB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,KAAK,KAAK,EAIV,KAAK,UAAU,EAChB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAU,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,EAAa,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAExE,OAAO,EAAY,KAAK,aAAa,EAAE,MAAM,2BAA2B,CAAC;AACzE,OAAO,EAAkC,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAK7F,OAAO,EAIL,KAAK,YAAY,EAElB,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"console.d.ts","sourceRoot":"","sources":["../src/console.tsx"],"names":[],"mappings":"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"}
|
package/dist/console.js
CHANGED
|
@@ -139,6 +139,50 @@ const projectOf = (route) => "project" in route ? route.project : undefined;
|
|
|
139
139
|
function Ground(props) {
|
|
140
140
|
return (_jsx("div", { className: "sc-shell", children: _jsx("main", { className: "sc-main", children: props.children }) }));
|
|
141
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* Whether the console holds a token rather than a session: a person's own console, served from
|
|
144
|
+
* their machine with the token the command keeps (ADR-0014). The API says so with `me.agent`.
|
|
145
|
+
*/
|
|
146
|
+
function holdsToken(data) {
|
|
147
|
+
return data.me !== undefined && data.me.agent !== null;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* The pages a console that holds a token does not offer: the tokens themselves and
|
|
151
|
+
* configuring, which are a person's own doing on the console's own pages, signed in.
|
|
152
|
+
*/
|
|
153
|
+
export function onlyOnOwnPages(route) {
|
|
154
|
+
return (route.name === "new-project" ||
|
|
155
|
+
route.name === "settings" ||
|
|
156
|
+
route.name === "agents" ||
|
|
157
|
+
route.name === "new-token" ||
|
|
158
|
+
route.name === "person-agents" ||
|
|
159
|
+
route.name === "connect");
|
|
160
|
+
}
|
|
161
|
+
function NotWithToken() {
|
|
162
|
+
const words = useWords().common.notWithToken;
|
|
163
|
+
return _jsx(EmptyState, { title: words.title, body: words.body });
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Where a console that holds a token keeps the person's choice of language: the browser's own
|
|
167
|
+
* storage, which may be absent or refuse, and then the browser's languages decide.
|
|
168
|
+
*/
|
|
169
|
+
const LANGUAGE_KEY = "sc-language";
|
|
170
|
+
function storedLanguage() {
|
|
171
|
+
try {
|
|
172
|
+
return globalThis.localStorage?.getItem(LANGUAGE_KEY) ?? undefined;
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
function storeLanguage(tag) {
|
|
179
|
+
try {
|
|
180
|
+
globalThis.localStorage?.setItem(LANGUAGE_KEY, tag);
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
// A browser that keeps nothing: the choice holds for this page.
|
|
184
|
+
}
|
|
185
|
+
}
|
|
142
186
|
/** A link that reads as a button: where a form is, for instance. */
|
|
143
187
|
function LinkButton(props) {
|
|
144
188
|
return (_jsx("a", { className: `sc-button sc-button-${props.variant ?? "secondary"}`, href: props.href, children: props.children }));
|
|
@@ -148,10 +192,11 @@ function ProjectsPage(props) {
|
|
|
148
192
|
const { data, components, navigation, making } = props;
|
|
149
193
|
const { busy, error, act } = useAction();
|
|
150
194
|
const href = (project) => projectHref(project.key);
|
|
151
|
-
|
|
195
|
+
const configures = !holdsToken(data);
|
|
196
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "sc-page-head", children: [_jsx("h1", { className: "sc-page-title", children: words.title }), !making && configures && (_jsx(LinkButton, { href: PATHS.newProject, variant: "primary", children: words.newProject }))] }), _jsx("p", { className: "sc-lead", children: words.lead }), error !== undefined && _jsx(Callout, { tone: "danger", children: error }), making && (_jsx("section", { className: "sc-panel sc-panel-form", "aria-label": words.form.aria, children: _jsx(ProjectForm, { busy: busy, onSubmit: (input) => void act(async () => {
|
|
152
197
|
const made = await data.actions.createProject(input);
|
|
153
198
|
navigation.replace(projectHref(made.key));
|
|
154
|
-
}), onCancel: () => navigation.back(PATHS.projects) }) })), _jsx(components.ProjectList, { projects: data.projects, projectHref: href, onOpen: (project) => navigation.go(href(project)), empty: _jsx(EmptyState, { title: words.empty.title, body: words.empty.body, action: _jsx(LinkButton, { href: PATHS.newProject, children: words.newProject }) }) })] }));
|
|
199
|
+
}), onCancel: () => navigation.back(PATHS.projects) }) })), _jsx(components.ProjectList, { projects: data.projects, projectHref: href, onOpen: (project) => navigation.go(href(project)), empty: _jsx(EmptyState, { title: words.empty.title, body: words.empty.body, action: configures ? (_jsx(LinkButton, { href: PATHS.newProject, children: words.newProject })) : undefined }) })] }));
|
|
155
200
|
}
|
|
156
201
|
function TaskPage(props) {
|
|
157
202
|
const words = useWords();
|
|
@@ -256,10 +301,11 @@ function ProjectPage(props) {
|
|
|
256
301
|
}
|
|
257
302
|
if (route.name === "members") {
|
|
258
303
|
const me = data.me?.person ?? undefined;
|
|
304
|
+
const configures = owner && !holdsToken(data);
|
|
259
305
|
const members = words.projects.members;
|
|
260
|
-
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "sc-page-head", children: _jsx("h1", { className: "sc-page-title", children: members.title }) }), _jsxs("p", { className: "sc-lead", children: [project.visibility === "workspace" ? members.leadWorkspace : members.leadPrivate, " ", members.leadRoles] }), error !== undefined && _jsx(Callout, { tone: "danger", children: error }), _jsx(components.MemberList, { members: data.members, people: data.people, me: me, busy: busy, onAdd:
|
|
306
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "sc-page-head", children: _jsx("h1", { className: "sc-page-title", children: members.title }) }), _jsxs("p", { className: "sc-lead", children: [project.visibility === "workspace" ? members.leadWorkspace : members.leadPrivate, " ", members.leadRoles] }), error !== undefined && _jsx(Callout, { tone: "danger", children: error }), _jsx(components.MemberList, { members: data.members, people: data.people, me: me, busy: busy, onAdd: configures ? (input) => void act(() => data.actions.addMember(input)) : undefined, onChangeRole: configures
|
|
261
307
|
? (member, role) => void act(() => data.actions.updateMember(member.person.id, { role }))
|
|
262
|
-
: undefined, onRemove:
|
|
308
|
+
: undefined, onRemove: configures
|
|
263
309
|
? (member) => void act(() => data.actions.removeMember(member.person.id))
|
|
264
310
|
: undefined, empty: _jsx(EmptyState, { title: members.empty.title, body: project.visibility === "workspace" ? members.empty.workspace : members.empty.private }) })] }));
|
|
265
311
|
}
|
|
@@ -317,7 +363,7 @@ function WorkspacePage(props) {
|
|
|
317
363
|
const { busy, error, act } = useAction();
|
|
318
364
|
if (route.name === "people") {
|
|
319
365
|
const me = data.me?.person ?? undefined;
|
|
320
|
-
const administrator = me?.role === "admin";
|
|
366
|
+
const administrator = me?.role === "admin" && !holdsToken(data);
|
|
321
367
|
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "sc-page-head", children: _jsx("h1", { className: "sc-page-title", children: words.people.title }) }), _jsx("p", { className: "sc-lead", children: words.people.lead }), error !== undefined && _jsx(Callout, { tone: "danger", children: error }), _jsx(components.PeopleList, { people: data.people, me: me, busy: busy, onChangeRole: administrator
|
|
322
368
|
? (person, role) => void act(() => data.actions.updatePerson(person.id, { role }))
|
|
323
369
|
: undefined, agentsHref: administrator ? (person) => personAgentsHref(person.id) : undefined })] }));
|
|
@@ -352,8 +398,12 @@ export function createConsole(config = {}) {
|
|
|
352
398
|
const data = useConsoleData(client, projectKey);
|
|
353
399
|
const failure = signInFailureOf(location.search);
|
|
354
400
|
const title = data.me?.workspace.name ?? fallbackTitle;
|
|
401
|
+
const viaToken = holdsToken(data);
|
|
355
402
|
// The language: the person's choice, else the browser's, else the first the console speaks.
|
|
356
|
-
|
|
403
|
+
// A console that holds a token cannot keep the choice on the person, and keeps it in the
|
|
404
|
+
// browser instead.
|
|
405
|
+
const [ownChoice, setOwnChoice] = useState(storedLanguage);
|
|
406
|
+
const chosen = viaToken ? (ownChoice ?? data.me?.language) : data.me?.language;
|
|
357
407
|
const pack = useMemo(() => chooseLanguage(chosen, typeof navigator === "undefined" ? [] : navigator.languages, languages), [chosen]);
|
|
358
408
|
const words = pack.messages;
|
|
359
409
|
// An address that moved is shown at its new one, in its place.
|
|
@@ -389,11 +439,15 @@ export function createConsole(config = {}) {
|
|
|
389
439
|
label: words.nav.people,
|
|
390
440
|
current: route.name === "people" || route.name === "person-agents",
|
|
391
441
|
},
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
442
|
+
...(viaToken
|
|
443
|
+
? []
|
|
444
|
+
: [
|
|
445
|
+
{
|
|
446
|
+
href: PATHS.agents,
|
|
447
|
+
label: words.nav.agents,
|
|
448
|
+
current: route.name === "agents" || route.name === "new-token",
|
|
449
|
+
},
|
|
450
|
+
]),
|
|
397
451
|
];
|
|
398
452
|
const nav = projectKey === undefined
|
|
399
453
|
? workspaceNav
|
|
@@ -431,7 +485,7 @@ export function createConsole(config = {}) {
|
|
|
431
485
|
label: words.nav.members,
|
|
432
486
|
current: route.name === "members",
|
|
433
487
|
},
|
|
434
|
-
...(project?.role === "owner"
|
|
488
|
+
...(project?.role === "owner" && !viaToken
|
|
435
489
|
? [
|
|
436
490
|
{
|
|
437
491
|
href: projectHref(projectKey, "settings"),
|
|
@@ -445,6 +499,9 @@ export function createConsole(config = {}) {
|
|
|
445
499
|
if (!data.loaded || route.name === "moved") {
|
|
446
500
|
page = (_jsx("div", { className: "sc-loading", children: _jsx(Spinner, { label: words.common.loadingWorkspace }) }));
|
|
447
501
|
}
|
|
502
|
+
else if (viaToken && onlyOnOwnPages(route)) {
|
|
503
|
+
page = _jsx(NotWithToken, {});
|
|
504
|
+
}
|
|
448
505
|
else if (route.name === "projects" || route.name === "new-project") {
|
|
449
506
|
page = (_jsx(ProjectsPage, { data: data, components: components, navigation: navigation, making: route.name === "new-project" }));
|
|
450
507
|
}
|
|
@@ -466,7 +523,15 @@ export function createConsole(config = {}) {
|
|
|
466
523
|
tag: candidate.tag,
|
|
467
524
|
label: candidate.label,
|
|
468
525
|
current: candidate.tag === pack.tag,
|
|
469
|
-
})), onLanguage: (tag) =>
|
|
526
|
+
})), onLanguage: (tag) => {
|
|
527
|
+
if (viaToken) {
|
|
528
|
+
storeLanguage(tag);
|
|
529
|
+
setOwnChoice(tag);
|
|
530
|
+
}
|
|
531
|
+
else {
|
|
532
|
+
void data.actions.updateMe({ language: tag }).catch(() => undefined);
|
|
533
|
+
}
|
|
534
|
+
}, person: data.me.person, live: projectKey === undefined ? undefined : data.live, wide: route.name === "board", onNavigate: (href) => navigation.go(href), onSignOut: viaToken ? undefined : () => void data.actions.signOut(), children: page }));
|
|
470
535
|
}
|
|
471
536
|
return _jsx(LanguageContext.Provider, { value: pack, children: content });
|
|
472
537
|
}
|