@tapflowio/relay 0.20.0 → 0.21.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/RelayServer.d.ts +31 -0
- package/dist/RelayServer.d.ts.map +1 -1
- package/dist/RelayServer.js +159 -22
- package/dist/RelayServer.js.map +1 -1
- package/dist/api/auth.d.ts +2 -2
- package/dist/api/auth.d.ts.map +1 -1
- package/dist/api/auth.js +22 -33
- package/dist/api/auth.js.map +1 -1
- package/dist/lib/adminAccount.d.ts +24 -0
- package/dist/lib/adminAccount.d.ts.map +1 -0
- package/dist/lib/adminAccount.js +61 -0
- package/dist/lib/adminAccount.js.map +1 -0
- package/dist/lib/adminBootstrap.d.ts +48 -0
- package/dist/lib/adminBootstrap.d.ts.map +1 -0
- package/dist/lib/adminBootstrap.js +68 -0
- package/dist/lib/adminBootstrap.js.map +1 -0
- package/dist/lib/buildTickets.d.ts +50 -0
- package/dist/lib/buildTickets.d.ts.map +1 -0
- package/dist/lib/buildTickets.js +47 -0
- package/dist/lib/buildTickets.js.map +1 -0
- package/dist/lib/config.d.ts +4 -4
- package/dist/lib/loadEnvFile.d.ts +1 -1
- package/dist/lib/loadEnvFile.d.ts.map +1 -1
- package/dist/lib/loadEnvFile.js +11 -1
- package/dist/lib/loadEnvFile.js.map +1 -1
- package/dist/server.js +22 -0
- package/dist/server.js.map +1 -1
- package/package.json +6 -6
- package/public/assets/{AppCenter-Dg4dscW-.js → AppCenter-BkSQFKRv.js} +5 -5
- package/public/assets/AppCenter-BkSQFKRv.js.br +0 -0
- package/public/assets/AppCenter-BkSQFKRv.js.gz +0 -0
- package/public/assets/{Default-Dxi8aZ1t.js → Default-D1inA8sU.js} +1 -1
- package/public/assets/Default-D1inA8sU.js.br +0 -0
- package/public/assets/Default-D1inA8sU.js.gz +0 -0
- package/public/assets/{Invite-0EwoClX6.js → Invite-ChaVEWvP.js} +1 -1
- package/public/assets/Invite-ChaVEWvP.js.br +0 -0
- package/public/assets/Invite-ChaVEWvP.js.gz +0 -0
- package/public/assets/MacResources-CLChFyJW.js +1 -0
- package/public/assets/MacResources-CLChFyJW.js.br +0 -0
- package/public/assets/MacResources-CLChFyJW.js.gz +0 -0
- package/public/assets/NotFound-BoDDivK5.js.gz +0 -0
- package/public/assets/QASession-DICynZa0.js +140 -0
- package/public/assets/QASession-DICynZa0.js.br +0 -0
- package/public/assets/QASession-DICynZa0.js.gz +0 -0
- package/public/assets/{ResetPassword-CXU2bRry.js → ResetPassword-Ut9BFums.js} +1 -1
- package/public/assets/ResetPassword-Ut9BFums.js.br +0 -0
- package/public/assets/ResetPassword-Ut9BFums.js.gz +0 -0
- package/public/assets/{Setup-XXChRxeL.js → Setup-CoHwaxo7.js} +1 -1
- package/public/assets/Setup-CoHwaxo7.js.br +0 -0
- package/public/assets/Setup-CoHwaxo7.js.gz +0 -0
- package/public/assets/{Team-D0bXpmEY.js → Team-DaVY1DGm.js} +2 -2
- package/public/assets/Team-DaVY1DGm.js.br +0 -0
- package/public/assets/Team-DaVY1DGm.js.gz +0 -0
- package/public/assets/Tokens-_KZJ7AZ9.js +1 -0
- package/public/assets/Tokens-_KZJ7AZ9.js.br +0 -0
- package/public/assets/Tokens-_KZJ7AZ9.js.gz +0 -0
- package/public/assets/{alert-dialog-CgatMXjB.js → alert-dialog-Cc_pQpe6.js} +1 -1
- package/public/assets/alert-dialog-Cc_pQpe6.js.br +0 -0
- package/public/assets/alert-dialog-Cc_pQpe6.js.gz +0 -0
- package/public/assets/{badge-B6Nf3A0Y.js → badge-CikXk90N.js} +1 -1
- package/public/assets/badge-CikXk90N.js.br +0 -0
- package/public/assets/badge-CikXk90N.js.gz +0 -0
- package/public/assets/{build-format-BFC8gLoh.js → build-format-DsApFPHy.js} +3 -3
- package/public/assets/build-format-DsApFPHy.js.br +0 -0
- package/public/assets/build-format-DsApFPHy.js.gz +0 -0
- package/public/assets/{dialog-DK9a3b4z.js → dialog-B4Wg7TCg.js} +3 -3
- package/public/assets/dialog-B4Wg7TCg.js.br +0 -0
- package/public/assets/dialog-B4Wg7TCg.js.gz +0 -0
- package/public/assets/index-CBfjqtE-.js +198 -0
- package/public/assets/index-CBfjqtE-.js.br +0 -0
- package/public/assets/index-CBfjqtE-.js.gz +0 -0
- package/public/assets/index-ClHUQm-F.css.gz +0 -0
- package/public/assets/{pencil-BeLNLEKe.js → pencil-DMswV31e.js} +2 -2
- package/public/assets/pencil-DMswV31e.js.br +0 -0
- package/public/assets/pencil-DMswV31e.js.gz +0 -0
- package/public/assets/{table-DW57-o1p.js → table-dbLmBL_w.js} +1 -1
- package/public/assets/table-dbLmBL_w.js.br +0 -0
- package/public/assets/table-dbLmBL_w.js.gz +0 -0
- package/public/assets/{tabs-Xlttuqxh.js → tabs-pPYCRfmT.js} +1 -1
- package/public/assets/tabs-pPYCRfmT.js.br +0 -0
- package/public/assets/tabs-pPYCRfmT.js.gz +0 -0
- package/public/assets/tinyh264.worker-DiJ50Hai.js.gz +0 -0
- package/public/assets/{trash-2-CGxISX8x.js → trash-VgeHmI8N.js} +4 -4
- package/public/assets/trash-VgeHmI8N.js.br +0 -0
- package/public/assets/trash-VgeHmI8N.js.gz +0 -0
- package/public/assets/vendor-charts-DyX5CBcf.js.gz +0 -0
- package/public/assets/vendor-react-AJpf4YZ0.js.gz +0 -0
- package/public/assets/vendor-router-BFibuab1.js +12 -0
- package/public/assets/vendor-router-BFibuab1.js.br +0 -0
- package/public/assets/vendor-router-BFibuab1.js.gz +0 -0
- package/public/favicon.svg.gz +0 -0
- package/public/index.html +2 -2
- package/public/index.html.br +0 -0
- package/public/index.html.gz +0 -0
- package/public/logo-dark.svg.gz +0 -0
- package/public/logo.svg.gz +0 -0
- package/public/assets/AppCenter-Dg4dscW-.js.br +0 -0
- package/public/assets/Default-Dxi8aZ1t.js.br +0 -0
- package/public/assets/Invite-0EwoClX6.js.br +0 -0
- package/public/assets/MacResources-Dy1M3EqK.js +0 -1
- package/public/assets/MacResources-Dy1M3EqK.js.br +0 -0
- package/public/assets/QASession-DqMj8JL1.js +0 -140
- package/public/assets/QASession-DqMj8JL1.js.br +0 -0
- package/public/assets/ResetPassword-CXU2bRry.js.br +0 -0
- package/public/assets/Setup-XXChRxeL.js.br +0 -0
- package/public/assets/Team-D0bXpmEY.js.br +0 -0
- package/public/assets/Tokens-D0YNHS5e.js +0 -1
- package/public/assets/Tokens-D0YNHS5e.js.br +0 -0
- package/public/assets/alert-dialog-CgatMXjB.js.br +0 -0
- package/public/assets/badge-B6Nf3A0Y.js.br +0 -0
- package/public/assets/build-format-BFC8gLoh.js.br +0 -0
- package/public/assets/dialog-DK9a3b4z.js.br +0 -0
- package/public/assets/index-9vdYIm7s.js +0 -210
- package/public/assets/index-9vdYIm7s.js.br +0 -0
- package/public/assets/pencil-BeLNLEKe.js.br +0 -0
- package/public/assets/table-DW57-o1p.js.br +0 -0
- package/public/assets/tabs-Xlttuqxh.js.br +0 -0
- package/public/assets/trash-2-CGxISX8x.js.br +0 -0
- package/public/assets/vendor-router-ChFy8CZ1.js +0 -12
- package/public/assets/vendor-router-ChFy8CZ1.js.br +0 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create the first Admin from the environment, once, at boot.
|
|
3
|
+
*
|
|
4
|
+
* **Why this exists at all.** `POST /api/v1/auth/init` requires `resolveClient(req).isLocal`, which
|
|
5
|
+
* stops a public instance being claimed by a stranger between first boot and the owner setting a
|
|
6
|
+
* password. A container is always behind its bridge gateway, so that check can never pass there —
|
|
7
|
+
* measured against the published image: the call answers 403 from the host's own browser and from
|
|
8
|
+
* the LAN alike. The error text points at `tapflow admin init`, but the image is relay-only by
|
|
9
|
+
* design (`pnpm deploy --filter @tapflowio/relay`) and carries no CLI. A Docker install therefore
|
|
10
|
+
* had no way to make its first account at all.
|
|
11
|
+
*
|
|
12
|
+
* This path does not go through HTTP, so the `isLocal` gate is not weakened — it is simply not on
|
|
13
|
+
* this road. Whoever can set the container's environment already controls the install.
|
|
14
|
+
*
|
|
15
|
+
* **Idempotent and silent when there is nothing to do.** It runs on every boot, and an install that
|
|
16
|
+
* already has an owner must not accumulate a log line per restart, nor have its account touched.
|
|
17
|
+
*/
|
|
18
|
+
export interface BootstrapLogger {
|
|
19
|
+
info(message: string): void;
|
|
20
|
+
warn(message: string): void;
|
|
21
|
+
error(message: string): void;
|
|
22
|
+
}
|
|
23
|
+
export interface BootstrapEnv {
|
|
24
|
+
TAPFLOW_ADMIN_EMAIL?: string;
|
|
25
|
+
TAPFLOW_ADMIN_PASSWORD?: string;
|
|
26
|
+
}
|
|
27
|
+
/** Thrown when the environment asks for a first account and cannot get one. Fatal at boot. */
|
|
28
|
+
export declare class AdminBootstrapError extends Error {
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* **Misconfiguration refuses to boot, and the first draft had this backwards.**
|
|
32
|
+
*
|
|
33
|
+
* That draft warned and carried on, reasoning that these variables are first-run-only and a typo in
|
|
34
|
+
* them must not keep a running relay down. Review pointed out that the reasoning describes a state
|
|
35
|
+
* this function cannot reach: `isInitialized()` returns above every one of these branches, so an
|
|
36
|
+
* install that already has an owner never gets here whatever its environment says. The only state
|
|
37
|
+
* that does reach them is *the operator asked for a first account, there is none, and the settings
|
|
38
|
+
* are wrong* — and serving anyway leaves the install ownerless and claimable by anything that can
|
|
39
|
+
* reach loopback, indefinitely, behind one line of boot spam.
|
|
40
|
+
*
|
|
41
|
+
* Refusing costs nothing, which is what makes the trade one-sided: an ownerless relay cannot be
|
|
42
|
+
* logged into by anyone legitimate either, so there is no working service to lose, and a container
|
|
43
|
+
* that will not stay up is far louder than a warning.
|
|
44
|
+
*
|
|
45
|
+
* Neither the password nor its length is ever written to the log.
|
|
46
|
+
*/
|
|
47
|
+
export declare function bootstrapAdminFromEnv(env: BootstrapEnv, logger: BootstrapLogger): void;
|
|
48
|
+
//# sourceMappingURL=adminBootstrap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adminBootstrap.d.ts","sourceRoot":"","sources":["../../src/lib/adminBootstrap.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CAC7B;AAED,MAAM,WAAW,YAAY;IAC3B,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,sBAAsB,CAAC,EAAE,MAAM,CAAA;CAChC;AAED,8FAA8F;AAC9F,qBAAa,mBAAoB,SAAQ,KAAK;CAAG;AAEjD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,YAAY,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAoDtF"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { isInitialized, createAdminAccount } from './adminAccount.js';
|
|
2
|
+
/** Thrown when the environment asks for a first account and cannot get one. Fatal at boot. */
|
|
3
|
+
export class AdminBootstrapError extends Error {
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* **Misconfiguration refuses to boot, and the first draft had this backwards.**
|
|
7
|
+
*
|
|
8
|
+
* That draft warned and carried on, reasoning that these variables are first-run-only and a typo in
|
|
9
|
+
* them must not keep a running relay down. Review pointed out that the reasoning describes a state
|
|
10
|
+
* this function cannot reach: `isInitialized()` returns above every one of these branches, so an
|
|
11
|
+
* install that already has an owner never gets here whatever its environment says. The only state
|
|
12
|
+
* that does reach them is *the operator asked for a first account, there is none, and the settings
|
|
13
|
+
* are wrong* — and serving anyway leaves the install ownerless and claimable by anything that can
|
|
14
|
+
* reach loopback, indefinitely, behind one line of boot spam.
|
|
15
|
+
*
|
|
16
|
+
* Refusing costs nothing, which is what makes the trade one-sided: an ownerless relay cannot be
|
|
17
|
+
* logged into by anyone legitimate either, so there is no working service to lose, and a container
|
|
18
|
+
* that will not stay up is far louder than a warning.
|
|
19
|
+
*
|
|
20
|
+
* Neither the password nor its length is ever written to the log.
|
|
21
|
+
*/
|
|
22
|
+
export function bootstrapAdminFromEnv(env, logger) {
|
|
23
|
+
const email = env.TAPFLOW_ADMIN_EMAIL?.trim();
|
|
24
|
+
const password = env.TAPFLOW_ADMIN_PASSWORD;
|
|
25
|
+
// Neither set is the ordinary case — every install that is not a fresh container.
|
|
26
|
+
if (!email && !password)
|
|
27
|
+
return;
|
|
28
|
+
if (isInitialized())
|
|
29
|
+
return;
|
|
30
|
+
const fail = (message) => {
|
|
31
|
+
logger.error(message);
|
|
32
|
+
throw new AdminBootstrapError(message);
|
|
33
|
+
};
|
|
34
|
+
if (!email || !password) {
|
|
35
|
+
const missing = email ? 'TAPFLOW_ADMIN_PASSWORD' : 'TAPFLOW_ADMIN_EMAIL';
|
|
36
|
+
// `return` so the compiler narrows both to `string` below — `fail` returning `never` is not
|
|
37
|
+
// enough on its own when it is called as a statement.
|
|
38
|
+
return fail(`${missing} is not set, so the first admin account could not be created. ` +
|
|
39
|
+
'Set both TAPFLOW_ADMIN_EMAIL and TAPFLOW_ADMIN_PASSWORD, or neither.');
|
|
40
|
+
}
|
|
41
|
+
let result;
|
|
42
|
+
try {
|
|
43
|
+
result = createAdminAccount(email, password);
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
// **A second container on the same volume is a race, not a misconfiguration.** `users.email` is
|
|
47
|
+
// `UNIQUE`, so two replicas booting together have one of them lose the INSERT. The install ends
|
|
48
|
+
// up with an owner either way, which is the outcome that was asked for — carry on rather than
|
|
49
|
+
// crash-looping whichever replica arrived second.
|
|
50
|
+
if (isInitialized()) {
|
|
51
|
+
logger.warn('Another process created the first admin account while this one was starting.');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
throw err;
|
|
55
|
+
}
|
|
56
|
+
switch (result) {
|
|
57
|
+
case 'password-too-short':
|
|
58
|
+
return fail('TAPFLOW_ADMIN_PASSWORD is shorter than 8 characters, so the first admin account could not ' +
|
|
59
|
+
'be created. Set a longer one.');
|
|
60
|
+
case 'missing-fields':
|
|
61
|
+
// Unreachable: both are non-empty by the checks above. Handled so a later change to
|
|
62
|
+
// `createAdminAccount` cannot silently fall through to "created".
|
|
63
|
+
return fail('The first admin account could not be created: the configured email or password was empty.');
|
|
64
|
+
case 'ok':
|
|
65
|
+
logger.info(`Created the first admin account for ${email} from the environment.`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=adminBootstrap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adminBootstrap.js","sourceRoot":"","sources":["../../src/lib/adminBootstrap.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AA8BrE,8FAA8F;AAC9F,MAAM,OAAO,mBAAoB,SAAQ,KAAK;CAAG;AAEjD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,qBAAqB,CAAC,GAAiB,EAAE,MAAuB;IAC9E,MAAM,KAAK,GAAG,GAAG,CAAC,mBAAmB,EAAE,IAAI,EAAE,CAAA;IAC7C,MAAM,QAAQ,GAAG,GAAG,CAAC,sBAAsB,CAAA;IAE3C,kFAAkF;IAClF,IAAI,CAAC,KAAK,IAAI,CAAC,QAAQ;QAAE,OAAM;IAE/B,IAAI,aAAa,EAAE;QAAE,OAAM;IAE3B,MAAM,IAAI,GAAG,CAAC,OAAe,EAAS,EAAE;QACtC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;QACrB,MAAM,IAAI,mBAAmB,CAAC,OAAO,CAAC,CAAA;IACxC,CAAC,CAAA;IAED,IAAI,CAAC,KAAK,IAAI,CAAC,QAAQ,EAAE,CAAC;QACxB,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC,CAAC,qBAAqB,CAAA;QACxE,4FAA4F;QAC5F,sDAAsD;QACtD,OAAO,IAAI,CACT,GAAG,OAAO,gEAAgE;YAC1E,sEAAsE,CACvE,CAAA;IACH,CAAC;IAED,IAAI,MAAM,CAAA;IACV,IAAI,CAAC;QACH,MAAM,GAAG,kBAAkB,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAA;IAC9C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,gGAAgG;QAChG,gGAAgG;QAChG,8FAA8F;QAC9F,kDAAkD;QAClD,IAAI,aAAa,EAAE,EAAE,CAAC;YACpB,MAAM,CAAC,IAAI,CAAC,8EAA8E,CAAC,CAAA;YAC3F,OAAM;QACR,CAAC;QACD,MAAM,GAAG,CAAA;IACX,CAAC;IAED,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,oBAAoB;YACvB,OAAO,IAAI,CACT,4FAA4F;gBAC5F,+BAA+B,CAChC,CAAA;QACH,KAAK,gBAAgB;YACnB,oFAAoF;YACpF,kEAAkE;YAClE,OAAO,IAAI,CAAC,2FAA2F,CAAC,CAAA;QAC1G,KAAK,IAAI;YACP,MAAM,CAAC,IAAI,CAAC,uCAAuC,KAAK,wBAAwB,CAAC,CAAA;IACrF,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single-use, short-lived credentials that let an agent download one build over HTTP.
|
|
3
|
+
*
|
|
4
|
+
* **Why a ticket rather than a scope on the agent's PAT.** The relay used to hand the agent its own
|
|
5
|
+
* filesystem path and let it open the file, which works only when the two share a disk. Opening
|
|
6
|
+
* `/uploads/` to the `agent` scope would have given every agent token the whole upload directory,
|
|
7
|
+
* and a `builds/:id` route reachable with that scope gives it the whole builds table one row at a
|
|
8
|
+
* time — the id is chosen by whoever calls. A ticket is minted where session ownership has *already*
|
|
9
|
+
* been checked, so the permission is the one build that request is about.
|
|
10
|
+
*
|
|
11
|
+
* It also removes an authentication problem rather than adding one: `tapflow start` runs an agent on
|
|
12
|
+
* loopback with **no token at all** (the relay accepts loopback agents unauthenticated), so any
|
|
13
|
+
* design that required a PAT would have broken every all-in-one install.
|
|
14
|
+
*
|
|
15
|
+
* **What is actually enforced**, stated exactly because the previous draft claimed more: 32 bytes of
|
|
16
|
+
* entropy, one use, one build, and `TICKET_TTL_MS`. There is no binding to a particular agent — the
|
|
17
|
+
* relay does not record the agent socket's address, so it cannot check one at HTTP time, and a field
|
|
18
|
+
* that looks like a check but is not is worse than its absence.
|
|
19
|
+
*/
|
|
20
|
+
export interface BuildTicket {
|
|
21
|
+
buildId: number;
|
|
22
|
+
filePath: string;
|
|
23
|
+
expiresAt: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Longer than every caller's deadline, so a ticket never expires under a request that is still
|
|
27
|
+
* being awaited: `flow-runner` allows 120s (`RelayClient.ts`), `mcp-server` 120s after this change.
|
|
28
|
+
* A download that outlives this was already going to fail its caller first.
|
|
29
|
+
*/
|
|
30
|
+
export declare const TICKET_TTL_MS = 180000;
|
|
31
|
+
/** Exported for the sweep test, which asserts the store fills before asserting it empties. */
|
|
32
|
+
export declare class BuildTicketStore {
|
|
33
|
+
private readonly tickets;
|
|
34
|
+
/** Test-only view. Asserting a sweep emptied the map means nothing without asserting it filled. */
|
|
35
|
+
get size(): number;
|
|
36
|
+
mint(buildId: number, filePath: string, now?: number): string;
|
|
37
|
+
/**
|
|
38
|
+
* Resolves and **consumes** a ticket. Distinguishes "never existed / already used" from "expired"
|
|
39
|
+
* so the agent can say which — a download that fails has three causes a user acts on differently,
|
|
40
|
+
* and collapsing them is the defect this whole change exists to stop repeating.
|
|
41
|
+
*/
|
|
42
|
+
redeem(ticket: string, now?: number): BuildTicket | 'unknown' | 'expired';
|
|
43
|
+
/**
|
|
44
|
+
* Drops what nobody redeemed. Without it the map grows for the life of the process: a session
|
|
45
|
+
* owner who presses install repeatedly mints a ticket each time and only the used ones are
|
|
46
|
+
* removed.
|
|
47
|
+
*/
|
|
48
|
+
sweep(now?: number): void;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=buildTickets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buildTickets.d.ts","sourceRoot":"","sources":["../../src/lib/buildTickets.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;CAClB;AAOD;;;;GAIG;AACH,eAAO,MAAM,aAAa,SAAU,CAAA;AAEpC,8FAA8F;AAC9F,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiC;IAEzD,mGAAmG;IACnG,IAAI,IAAI,IAAI,MAAM,CAA6B;IAE/C,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,SAAa,GAAG,MAAM;IAOjE;;;;OAIG;IACH,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,SAAa,GAAG,WAAW,GAAG,SAAS,GAAG,SAAS;IAO7E;;;;OAIG;IACH,KAAK,CAAC,GAAG,SAAa,GAAG,IAAI;CAK9B"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
// **No `bytes` here.** A first version carried one described as what the agent compares against —
|
|
3
|
+
// and nothing read it: the route stats the file for its own `Content-Length`, and the agent compares
|
|
4
|
+
// against `buildBytes` from the WebSocket payload. The paragraph above says a field that looks like a
|
|
5
|
+
// check but is not is worse than its absence; this was one, twenty lines below that sentence.
|
|
6
|
+
/**
|
|
7
|
+
* Longer than every caller's deadline, so a ticket never expires under a request that is still
|
|
8
|
+
* being awaited: `flow-runner` allows 120s (`RelayClient.ts`), `mcp-server` 120s after this change.
|
|
9
|
+
* A download that outlives this was already going to fail its caller first.
|
|
10
|
+
*/
|
|
11
|
+
export const TICKET_TTL_MS = 180_000;
|
|
12
|
+
/** Exported for the sweep test, which asserts the store fills before asserting it empties. */
|
|
13
|
+
export class BuildTicketStore {
|
|
14
|
+
tickets = new Map();
|
|
15
|
+
/** Test-only view. Asserting a sweep emptied the map means nothing without asserting it filled. */
|
|
16
|
+
get size() { return this.tickets.size; }
|
|
17
|
+
mint(buildId, filePath, now = Date.now()) {
|
|
18
|
+
this.sweep(now);
|
|
19
|
+
const ticket = crypto.randomBytes(32).toString('hex');
|
|
20
|
+
this.tickets.set(ticket, { buildId, filePath, expiresAt: now + TICKET_TTL_MS });
|
|
21
|
+
return ticket;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Resolves and **consumes** a ticket. Distinguishes "never existed / already used" from "expired"
|
|
25
|
+
* so the agent can say which — a download that fails has three causes a user acts on differently,
|
|
26
|
+
* and collapsing them is the defect this whole change exists to stop repeating.
|
|
27
|
+
*/
|
|
28
|
+
redeem(ticket, now = Date.now()) {
|
|
29
|
+
const found = this.tickets.get(ticket);
|
|
30
|
+
if (!found)
|
|
31
|
+
return 'unknown';
|
|
32
|
+
this.tickets.delete(ticket);
|
|
33
|
+
return found.expiresAt <= now ? 'expired' : found;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Drops what nobody redeemed. Without it the map grows for the life of the process: a session
|
|
37
|
+
* owner who presses install repeatedly mints a ticket each time and only the used ones are
|
|
38
|
+
* removed.
|
|
39
|
+
*/
|
|
40
|
+
sweep(now = Date.now()) {
|
|
41
|
+
for (const [ticket, rec] of this.tickets) {
|
|
42
|
+
if (rec.expiresAt <= now)
|
|
43
|
+
this.tickets.delete(ticket);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=buildTickets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buildTickets.js","sourceRoot":"","sources":["../../src/lib/buildTickets.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAA;AA2B3B,kGAAkG;AAClG,qGAAqG;AACrG,sGAAsG;AACtG,8FAA8F;AAE9F;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,OAAO,CAAA;AAEpC,8FAA8F;AAC9F,MAAM,OAAO,gBAAgB;IACV,OAAO,GAAG,IAAI,GAAG,EAAuB,CAAA;IAEzD,mGAAmG;IACnG,IAAI,IAAI,KAAa,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAA,CAAC,CAAC;IAE/C,IAAI,CAAC,OAAe,EAAE,QAAgB,EAAE,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;QACtD,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QACf,MAAM,MAAM,GAAG,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;QACrD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,GAAG,aAAa,EAAE,CAAC,CAAA;QAC/E,OAAO,MAAM,CAAA;IACf,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,MAAc,EAAE,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;QACrC,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;QACtC,IAAI,CAAC,KAAK;YAAE,OAAO,SAAS,CAAA;QAC5B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QAC3B,OAAO,KAAK,CAAC,SAAS,IAAI,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAA;IACnD,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;QACpB,KAAK,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACzC,IAAI,GAAG,CAAC,SAAS,IAAI,GAAG;gBAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QACvD,CAAC;IACH,CAAC;CACF"}
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -75,15 +75,15 @@ export declare const config: {
|
|
|
75
75
|
publicUrl?: string | undefined;
|
|
76
76
|
} | null;
|
|
77
77
|
tls: {
|
|
78
|
+
mode: "import-cert";
|
|
79
|
+
certPath: string;
|
|
80
|
+
keyPath: string;
|
|
81
|
+
} | {
|
|
78
82
|
mode: "byo-api-token";
|
|
79
83
|
domain: string;
|
|
80
84
|
dnsProvider: string;
|
|
81
85
|
publishAddress?: boolean | undefined;
|
|
82
86
|
address?: string | undefined;
|
|
83
|
-
} | {
|
|
84
|
-
mode: "import-cert";
|
|
85
|
-
certPath: string;
|
|
86
|
-
keyPath: string;
|
|
87
87
|
} | null;
|
|
88
88
|
smtp: {
|
|
89
89
|
host: string;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare function loadDataDirEnv(dataDir: string): string | null;
|
|
1
|
+
export declare function loadDataDirEnv(dataDir: string, warn?: (m: string) => void): string | null;
|
|
2
2
|
//# sourceMappingURL=loadEnvFile.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loadEnvFile.d.ts","sourceRoot":"","sources":["../../src/lib/loadEnvFile.ts"],"names":[],"mappings":"AAMA,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"loadEnvFile.d.ts","sourceRoot":"","sources":["../../src/lib/loadEnvFile.ts"],"names":[],"mappings":"AAMA,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAA6B,GAAG,MAAM,GAAG,IAAI,CAoBjH"}
|
package/dist/lib/loadEnvFile.js
CHANGED
|
@@ -3,11 +3,21 @@ import path from 'path';
|
|
|
3
3
|
// gitignore된 <dataDir>/.env 에서 자격 증명(JWT_SECRET·SMTP·DNS/ACME 토큰 등)을 로드한다.
|
|
4
4
|
// config.load()가 secret을 읽기 전에 호출되어 .env가 모든 비밀의 기본 경로가 된다(#287에서 시작).
|
|
5
5
|
// process.loadEnvFile 은 기존 process.env 값을 안 덮으므로 ambient(셸) 가 항상 우선.
|
|
6
|
-
export function loadDataDirEnv(dataDir) {
|
|
6
|
+
export function loadDataDirEnv(dataDir, warn = (m) => console.warn(m)) {
|
|
7
7
|
const envPath = path.join(dataDir, '.env');
|
|
8
8
|
if (!fs.existsSync(envPath))
|
|
9
9
|
return null;
|
|
10
10
|
try {
|
|
11
|
+
// **Everything else that writes a secret into dataDir sets 0600 and this file trusted its
|
|
12
|
+
// caller.** `config.ts` writes `jwt-secret` with `{ mode: 0o600 }` and chmods it; `tapflow init`
|
|
13
|
+
// does the same for this file. The relay-only image has no CLI, so a container operator writes
|
|
14
|
+
// it by hand under their own umask — 0644 — and the admin bootstrap now tells them to put a
|
|
15
|
+
// plaintext password in it. Warn rather than refuse: an unreadable-by-others file is the goal,
|
|
16
|
+
// but a relay that will not start because of a permission bit helps nobody.
|
|
17
|
+
const mode = fs.statSync(envPath).mode & 0o777;
|
|
18
|
+
if (mode & 0o077) {
|
|
19
|
+
warn(`${envPath} is readable by other users (mode ${mode.toString(8)}). Run: chmod 600 ${envPath}`);
|
|
20
|
+
}
|
|
11
21
|
process.loadEnvFile(envPath);
|
|
12
22
|
return envPath;
|
|
13
23
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loadEnvFile.js","sourceRoot":"","sources":["../../src/lib/loadEnvFile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAA;AACnB,OAAO,IAAI,MAAM,MAAM,CAAA;AAEvB,2EAA2E;AAC3E,uEAAuE;AACvE,qEAAqE;AACrE,MAAM,UAAU,cAAc,CAAC,OAAe;
|
|
1
|
+
{"version":3,"file":"loadEnvFile.js","sourceRoot":"","sources":["../../src/lib/loadEnvFile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAA;AACnB,OAAO,IAAI,MAAM,MAAM,CAAA;AAEvB,2EAA2E;AAC3E,uEAAuE;AACvE,qEAAqE;AACrE,MAAM,UAAU,cAAc,CAAC,OAAe,EAAE,OAA4B,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IAChG,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;IAC1C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAA;IACxC,IAAI,CAAC;QACH,0FAA0F;QAC1F,iGAAiG;QACjG,+FAA+F;QAC/F,4FAA4F;QAC5F,+FAA+F;QAC/F,4EAA4E;QAC5E,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,IAAI,GAAG,KAAK,CAAA;QAC9C,IAAI,IAAI,GAAG,KAAK,EAAE,CAAC;YACjB,IAAI,CAAC,GAAG,OAAO,qCAAqC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,qBAAqB,OAAO,EAAE,CAAC,CAAA;QACrG,CAAC;QACD,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,CAAA;QAC5B,OAAO,OAAO,CAAA;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,gDAAgD;QAChD,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC"}
|
package/dist/server.js
CHANGED
|
@@ -3,6 +3,7 @@ import { initDb } from './db.js';
|
|
|
3
3
|
import { RelayServer } from './RelayServer.js';
|
|
4
4
|
import { config, loadedEnvPath } from './lib/config.js';
|
|
5
5
|
import { buildCorsOrigins, proxyWithoutPublicUrlWarning } from './lib/proxyConfig.js';
|
|
6
|
+
import { bootstrapAdminFromEnv, AdminBootstrapError } from './lib/adminBootstrap.js';
|
|
6
7
|
import { createCertProvider, resolveRelayDisplayHost } from './lib/cert/index.js';
|
|
7
8
|
import { startTlsBackgroundTasks } from './lib/tlsTasks.js';
|
|
8
9
|
import { createLogger } from '@tapflowio/agent-core';
|
|
@@ -14,6 +15,27 @@ if (loadedEnvPath)
|
|
|
14
15
|
const dbPath = path.join(dataDir, 'tapflow.db');
|
|
15
16
|
const uploadsDir = path.join(dataDir, 'uploads');
|
|
16
17
|
initDb(dbPath);
|
|
18
|
+
// After the schema exists and before anything is served: a container cannot reach the HTTP
|
|
19
|
+
// bootstrap, because that one requires a local client and a container is always behind its bridge.
|
|
20
|
+
//
|
|
21
|
+
// **Fatal on failure.** The only way to get here with a problem is "an admin was asked for, there is
|
|
22
|
+
// none, and the settings are wrong" — serving that would leave the install ownerless and claimable
|
|
23
|
+
// from loopback. See `lib/adminBootstrap.ts`.
|
|
24
|
+
try {
|
|
25
|
+
bootstrapAdminFromEnv(process.env, logger);
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
// The reason is already logged where it was decided; re-logging it here printed every refusal
|
|
29
|
+
// twice. Anything that is not a refusal has no message of its own yet, so that one is logged.
|
|
30
|
+
if (!(err instanceof AdminBootstrapError))
|
|
31
|
+
logger.error(err instanceof Error ? err.message : String(err));
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
// **The password does not outlive its use.** It is read once here, and every `spawnSync` in
|
|
35
|
+
// `api/builds.ts` — twelve of them, over user-uploaded archives — inherits `process.env` without
|
|
36
|
+
// passing one explicitly. Nothing reads these names again; `<dataDir>/.env` is re-read each boot.
|
|
37
|
+
delete process.env.TAPFLOW_ADMIN_PASSWORD;
|
|
38
|
+
delete process.env.TAPFLOW_ADMIN_EMAIL;
|
|
17
39
|
const corsOrigins = buildCorsOrigins(config, port);
|
|
18
40
|
const proxyWarning = proxyWithoutPublicUrlWarning(config);
|
|
19
41
|
if (proxyWarning)
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tapflowio/relay",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "WebSocket relay server for tapflow — NAT traversal, session routing, JWT auth, bundled dashboard",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"tapflow",
|
|
@@ -49,11 +49,11 @@
|
|
|
49
49
|
"better-sqlite3": "^12.11.1",
|
|
50
50
|
"busboy": "^1.6.0",
|
|
51
51
|
"jsonwebtoken": "^9.0.3",
|
|
52
|
-
"nodemailer": "^9.
|
|
52
|
+
"nodemailer": "^9.1.1",
|
|
53
53
|
"ws": "^8.21.3",
|
|
54
|
-
"zod": "^4.4
|
|
55
|
-
"@tapflowio/agent-core": "0.
|
|
56
|
-
"@tapflowio/protocol": "0.
|
|
54
|
+
"zod": "^4.5.4",
|
|
55
|
+
"@tapflowio/agent-core": "0.21.0",
|
|
56
|
+
"@tapflowio/protocol": "0.21.0"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@types/better-sqlite3": "^7.6.13",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"@types/nodemailer": "^8.0.1",
|
|
64
64
|
"@types/ws": "^8.0.0",
|
|
65
65
|
"typescript": "^5.0.0",
|
|
66
|
-
"vitest": "^4.1.
|
|
66
|
+
"vitest": "^4.1.11",
|
|
67
67
|
"@tapflowio/test-utils": "0.0.0"
|
|
68
68
|
},
|
|
69
69
|
"scripts": {
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import{i as r,j as e}from"./vendor-react-AJpf4YZ0.js";import{W as C,B as y,L as b,a4 as k,J,I as M,S as Q,f as W}from"./index-
|
|
2
|
-
* @license lucide-react v1.
|
|
1
|
+
import{i as r,j as e}from"./vendor-react-AJpf4YZ0.js";import{W as C,B as y,L as b,a4 as k,J,I as M,S as Q,f as W}from"./index-CBfjqtE-.js";import{e as Y,S as K,P as X,g as ee,h as se,i as ae,b as te,d as le,s as ne,u as re}from"./build-format-DsApFPHy.js";import{D as O,f as L,a as U,d as E,e as V,S as A,i as _,j as B,g as z,h as i,C as ce}from"./dialog-B4Wg7TCg.js";import{P as ie,T as oe}from"./trash-VgeHmI8N.js";import{B as T}from"./badge-CikXk90N.js";import{A as de,c as ue,f as he,g as xe,d as pe,e as me,b as fe,a as ge}from"./alert-dialog-Cc_pQpe6.js";import{b as je,c as ve}from"./vendor-router-BFibuab1.js";/**
|
|
2
|
+
* @license lucide-react v1.41.0 - ISC
|
|
3
3
|
*
|
|
4
4
|
* This source code is licensed under the ISC license.
|
|
5
5
|
* See the LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/const Ne=[["path",{d:"M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83z",key:"zw3jo"}],["path",{d:"M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 12",key:"1wduqc"}],["path",{d:"M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 17",key:"kqbvx6"}]],Se=C("layers",Ne);/**
|
|
7
|
-
* @license lucide-react v1.
|
|
7
|
+
* @license lucide-react v1.41.0 - ISC
|
|
8
8
|
*
|
|
9
9
|
* This source code is licensed under the ISC license.
|
|
10
10
|
* See the LICENSE file in the root directory of this source tree.
|
|
11
11
|
*/const ye=[["path",{d:"M11 21.73a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73z",key:"1a0edw"}],["path",{d:"M12 22V12",key:"d0xqtd"}],["polyline",{points:"3.29 7 12 12 20.71 7",key:"ousv84"}],["path",{d:"m7.5 4.27 9 5.15",key:"1c824w"}]],we=C("package",ye);/**
|
|
12
|
-
* @license lucide-react v1.
|
|
12
|
+
* @license lucide-react v1.41.0 - ISC
|
|
13
13
|
*
|
|
14
14
|
* This source code is licensed under the ISC license.
|
|
15
15
|
* See the LICENSE file in the root directory of this source tree.
|
|
16
16
|
*/const be=[["path",{d:"M10 2h4",key:"n1abiw"}],["path",{d:"M4.6 11a8 8 0 0 0 1.7 8.7 8 8 0 0 0 8.7 1.7",key:"10he05"}],["path",{d:"M7.4 7.4a8 8 0 0 1 10.3 1 8 8 0 0 1 .9 10.2",key:"15f7sh"}],["path",{d:"m2 2 20 20",key:"1ooewy"}],["path",{d:"M12 12v-2",key:"fwoke6"}]],ke=C("timer-off",be);/**
|
|
17
|
-
* @license lucide-react v1.
|
|
17
|
+
* @license lucide-react v1.41.0 - ISC
|
|
18
18
|
*
|
|
19
19
|
* This source code is licensed under the ISC license.
|
|
20
20
|
* See the LICENSE file in the root directory of this source tree.
|
|
Binary file
|
|
Binary file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{i,j as e}from"./vendor-react-AJpf4YZ0.js";import{ai as X,a6 as Z,ad as S,a5 as y,ah as I,C as g,c as j,d as v,a as w,L as m,I as h,S as M,h as O,a4 as n,B as u,E as ee,J as se,H as ae,a2 as C,_ as $,a3 as p}from"./index-9vdYIm7s.js";import{B as re}from"./badge-B6Nf3A0Y.js";import{A as te,h as oe,c as ne,f as ie,g as le,d as ce,e as de,b as me,a as he}from"./alert-dialog-CgatMXjB.js";import{P as W}from"./pencil-BeLNLEKe.js";import"./vendor-router-ChFy8CZ1.js";const ue=C({teamName:p().min(1),logo:$(File).nullable().optional()}),pe=C({displayName:p().min(1),avatar:$(File).nullable().optional()}),fe=C({currentPassword:p().min(1),newPassword:p().min(8),confirmPassword:p()}).refine(f=>f.newPassword===f.confirmPassword,{message:"Passwords do not match",path:["confirmPassword"]});function Ne(){const{resolvedTheme:f}=X(),J=f==="dark"?"/logo-dark.svg":"/logo.svg",{user:o}=Z(),b=(o==null?void 0:o.role)==="Admin",N=(o==null?void 0:o.role)==="Admin"||(o==null?void 0:o.role)==="Developer",[V,P]=i.useState(null),A=i.useRef(null),d=S({resolver:y(ue),mode:"onBlur",defaultValues:{teamName:"",logo:null}});i.useEffect(()=>{b&&fetch("/api/v1/settings",{credentials:"include"}).then(s=>s.ok?s.json():null).then(s=>{s&&(d.reset({teamName:s.team_name,logo:null}),P(s.logo_url))})},[b]);async function Q(s){const a=new FormData;a.append("team_name",s.teamName),s.logo&&a.append("logo",s.logo);try{if(!(await fetch("/api/v1/settings",{method:"PATCH",credentials:"include",body:a})).ok){n.error("Failed to update workspace");return}n.success("Workspace updated")}catch{n.error("Failed to update workspace")}}const[k,F]=i.useState(null),D=i.useRef(null),c=S({resolver:y(pe),mode:"onBlur",defaultValues:{displayName:"",avatar:null}});i.useEffect(()=>{o&&(c.reset({displayName:o.displayName??"",avatar:null}),F(o.avatarUrl??null))},[o]);async function Y(s){const a=new FormData;a.append("display_name",s.displayName),s.avatar&&a.append("avatar",s.avatar);try{if(!(await fetch("/api/v1/profile",{method:"PATCH",credentials:"include",body:a})).ok){n.error("Failed to update profile");return}n.success("Profile updated")}catch{n.error("Failed to update profile")}}const t=S({resolver:y(fe),mode:"onBlur"});async function q(s){try{const a=await fetch("/api/v1/auth/change-password",{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({currentPassword:s.currentPassword,newPassword:s.newPassword})});if(!a.ok){const r=await a.json();t.setError("root",{message:r.error??"Failed to change password"});return}t.reset(),n.success("Password changed")}catch{t.setError("root",{message:"Network error"})}}const[T,E]=i.useState([]),[B,L]=i.useState({}),[U,z]=i.useState({}),[R,_]=i.useState({});i.useEffect(()=>{N&&fetch("/api/v1/apps",{credentials:"include"}).then(s=>s.ok?s.json():null).then(s=>{if(!s)return;E(s.items);const a={};s.items.forEach(r=>{a[r.id]=r.name}),L(a)})},[N]);async function G(s){_(a=>({...a,[s]:!0}));try{if(!(await fetch(`/api/v1/apps/${s}`,{method:"DELETE",credentials:"include"})).ok){n.error("Failed to delete app");return}E(r=>r.filter(l=>l.id!==s)),n.success("App deleted")}catch{n.error("Failed to delete app")}finally{_(a=>({...a,[s]:!1}))}}async function K(s){z(a=>({...a,[s]:!0}));try{if(!(await fetch(`/api/v1/apps/${s}`,{method:"PATCH",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:B[s]})})).ok){n.error("Failed to update app");return}n.success("App updated")}catch{n.error("Failed to update app")}finally{z(a=>({...a,[s]:!1}))}}const x=I({control:c.control,name:"displayName"});return e.jsxs("div",{className:"flex flex-col gap-6 max-w-[900px] mx-auto w-full p-6",children:[e.jsx("h1",{className:"text-xl font-semibold tracking-display-sm",children:"Settings"}),b&&e.jsxs(g,{children:[e.jsx(j,{children:e.jsx(v,{children:"Workspace"})}),e.jsx(w,{children:e.jsxs("form",{onSubmit:d.handleSubmit(Q),className:"flex flex-col gap-4",children:[e.jsxs("div",{className:"grid gap-2",children:[e.jsx(m,{htmlFor:"team-name",children:"Team name"}),e.jsx(h,{id:"team-name",placeholder:"My QA Team",...d.register("teamName")}),d.formState.errors.teamName&&e.jsx("p",{className:"text-sm text-destructive",children:d.formState.errors.teamName.message})]}),e.jsx(M,{}),e.jsxs("div",{className:"grid gap-2",children:[e.jsxs(m,{children:["Logo ",e.jsx("span",{className:"text-muted-foreground text-xs",children:"(png · jpg, max 2MB)"})]}),e.jsx(O,{name:"logo",control:d.control,render:({field:s})=>e.jsxs("div",{className:"relative w-16 h-16",children:[e.jsx("img",{src:V??J,alt:"Workspace logo",className:"w-16 h-16 rounded-lg object-contain"}),e.jsx("button",{type:"button","aria-label":"Change logo",onClick:()=>{var a;return(a=A.current)==null?void 0:a.click()},className:"absolute bottom-0 right-0 w-6 h-6 rounded-full bg-background border border-border shadow-sm flex items-center justify-center hover:bg-accent transition-colors",children:e.jsx(W,{className:"w-3 h-3","aria-hidden":"true"})}),e.jsx("input",{ref:A,type:"file",accept:".png,.jpg,.jpeg",className:"hidden",onChange:a=>{var l;const r=(l=a.target.files)==null?void 0:l[0];if(r&&r.size>2*1024*1024){n.error("Image must be 2MB or less");return}r&&(s.onChange(r),P(URL.createObjectURL(r)))}})]})})]}),e.jsx("div",{className:"flex justify-end",children:e.jsx(u,{type:"submit",size:"sm",disabled:d.formState.isSubmitting,children:d.formState.isSubmitting?"Saving…":"Save changes"})})]})})]}),e.jsxs(g,{children:[e.jsx(j,{children:e.jsx(v,{children:"Profile"})}),e.jsx(w,{children:e.jsxs("form",{onSubmit:c.handleSubmit(Y),className:"flex flex-col gap-4",children:[e.jsxs("div",{className:"grid gap-2",children:[e.jsx(m,{htmlFor:"display-name",children:"Nickname"}),e.jsx(h,{id:"display-name",placeholder:"Your name",...c.register("displayName")}),c.formState.errors.displayName&&e.jsx("p",{className:"text-sm text-destructive",children:c.formState.errors.displayName.message})]}),e.jsx(M,{}),e.jsxs("div",{className:"grid gap-2",children:[e.jsxs(m,{children:["Avatar ",e.jsx("span",{className:"text-muted-foreground text-xs",children:"(png · jpg, max 2MB)"})]}),e.jsx(O,{name:"avatar",control:c.control,render:({field:s})=>{var a;return e.jsxs("div",{className:"relative w-14 h-14",children:[k?e.jsx("img",{src:k,alt:"Profile avatar",className:"w-14 h-14 rounded-full object-cover border"}):e.jsx("div",{className:"w-14 h-14 rounded-full flex items-center justify-center text-lg font-medium",style:(()=>{const r=ee(x||(o==null?void 0:o.email)||"");return{backgroundColor:r.bg,color:r.fg}})(),children:((a=x==null?void 0:x[0])==null?void 0:a.toUpperCase())??"?"}),e.jsx("button",{type:"button","aria-label":"Change avatar",onClick:()=>{var r;return(r=D.current)==null?void 0:r.click()},className:"absolute bottom-0 right-0 w-6 h-6 rounded-full bg-background border border-border shadow-sm flex items-center justify-center hover:bg-accent transition-colors",children:e.jsx(W,{className:"w-3 h-3","aria-hidden":"true"})}),e.jsx("input",{ref:D,type:"file",accept:".png,.jpg,.jpeg",className:"hidden",onChange:r=>{var H;const l=(H=r.target.files)==null?void 0:H[0];if(l&&l.size>2*1024*1024){n.error("Image must be 2MB or less");return}l&&(s.onChange(l),F(URL.createObjectURL(l)))}})]})}})]}),e.jsx("div",{className:"flex justify-end",children:e.jsx(u,{type:"submit",size:"sm",disabled:c.formState.isSubmitting,children:c.formState.isSubmitting?"Saving…":"Save changes"})})]})})]}),e.jsxs(g,{children:[e.jsx(j,{children:e.jsx(v,{children:"Password"})}),e.jsx(w,{children:e.jsxs("form",{onSubmit:t.handleSubmit(q),className:"flex flex-col gap-4",children:[e.jsxs("div",{className:"grid gap-2",children:[e.jsx(m,{htmlFor:"current-password",children:"Current password"}),e.jsx(h,{id:"current-password",type:"password",...t.register("currentPassword")}),t.formState.errors.currentPassword&&e.jsx("p",{className:"text-sm text-destructive",children:t.formState.errors.currentPassword.message})]}),e.jsxs("div",{className:"grid gap-2",children:[e.jsx(m,{htmlFor:"new-password",children:"New password"}),e.jsx(h,{id:"new-password",type:"password",...t.register("newPassword")}),t.formState.errors.newPassword&&e.jsx("p",{className:"text-sm text-destructive",children:t.formState.errors.newPassword.message})]}),e.jsxs("div",{className:"grid gap-2",children:[e.jsx(m,{htmlFor:"confirm-password",children:"Confirm new password"}),e.jsx(h,{id:"confirm-password",type:"password",...t.register("confirmPassword")}),t.formState.errors.confirmPassword&&e.jsx("p",{className:"text-sm text-destructive",children:t.formState.errors.confirmPassword.message})]}),t.formState.errors.root&&e.jsx("p",{className:"text-sm text-destructive",children:t.formState.errors.root.message}),e.jsx("div",{className:"flex justify-end",children:e.jsx(u,{type:"submit",size:"sm",disabled:t.formState.isSubmitting,children:t.formState.isSubmitting?"Saving…":"Change password"})})]})})]}),N&&T.length>0&&e.jsxs(g,{children:[e.jsx(j,{children:e.jsx(v,{children:"Apps"})}),e.jsx(w,{children:e.jsx("div",{className:"flex flex-col divide-y divide-border",children:T.map(s=>e.jsxs("div",{className:"flex flex-col gap-2 py-4 first:pt-0 last:pb-0",children:[e.jsx(m,{htmlFor:`app-${s.id}`,children:"App Name"}),e.jsx(h,{id:`app-${s.id}`,value:B[s.id]??"",onChange:a=>L(r=>({...r,[s.id]:a.target.value}))}),e.jsxs("div",{className:"flex flex-col gap-1.5 mt-1",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("span",{className:"text-xs text-muted-foreground font-mono",children:s.bundle_id_key}),e.jsx(re,{tone:s.platform==="ios"?"ios":s.platform==="android"?"android":void 0,variant:s.platform==="both"?"secondary":void 0,className:"capitalize",children:s.platform})]}),e.jsxs("div",{className:"flex items-center justify-end gap-2",children:[e.jsx(u,{size:"sm",variant:"outline",disabled:U[s.id],onClick:()=>K(s.id),children:U[s.id]?"Saving…":"Save"}),e.jsxs(te,{children:[e.jsx(oe,{asChild:!0,children:e.jsx(u,{size:"sm",variant:"destructive",disabled:R[s.id],children:R[s.id]?"Deleting…":"Delete"})}),e.jsxs(ne,{children:[e.jsxs(ie,{children:[e.jsx(le,{children:"Delete app?"}),e.jsxs(ce,{children:["This will permanently delete ",e.jsx("strong",{children:s.name})," and all its builds. This action cannot be undone."]})]}),e.jsxs(de,{children:[e.jsx(me,{className:"w-24",children:"Cancel"}),e.jsx(he,{className:se(ae({variant:"destructive"}),"w-24"),onClick:()=>G(s.id),children:"Delete"})]})]})]})]})]})]},s.id))})})]})]})}export{Ne as DefaultSettings};
|
|
1
|
+
import{i,j as e}from"./vendor-react-AJpf4YZ0.js";import{ai as X,a6 as Z,ad as S,a5 as y,ah as I,C as g,c as j,d as v,a as w,L as m,I as h,S as M,h as O,a4 as n,B as u,E as ee,J as se,H as ae,a2 as C,_ as $,a3 as p}from"./index-CBfjqtE-.js";import{B as re}from"./badge-CikXk90N.js";import{A as te,h as oe,c as ne,f as ie,g as le,d as ce,e as de,b as me,a as he}from"./alert-dialog-Cc_pQpe6.js";import{P as W}from"./pencil-DMswV31e.js";import"./vendor-router-BFibuab1.js";const ue=C({teamName:p().min(1),logo:$(File).nullable().optional()}),pe=C({displayName:p().min(1),avatar:$(File).nullable().optional()}),fe=C({currentPassword:p().min(1),newPassword:p().min(8),confirmPassword:p()}).refine(f=>f.newPassword===f.confirmPassword,{message:"Passwords do not match",path:["confirmPassword"]});function Ne(){const{resolvedTheme:f}=X(),J=f==="dark"?"/logo-dark.svg":"/logo.svg",{user:o}=Z(),b=(o==null?void 0:o.role)==="Admin",N=(o==null?void 0:o.role)==="Admin"||(o==null?void 0:o.role)==="Developer",[V,P]=i.useState(null),A=i.useRef(null),d=S({resolver:y(ue),mode:"onBlur",defaultValues:{teamName:"",logo:null}});i.useEffect(()=>{b&&fetch("/api/v1/settings",{credentials:"include"}).then(s=>s.ok?s.json():null).then(s=>{s&&(d.reset({teamName:s.team_name,logo:null}),P(s.logo_url))})},[b]);async function Q(s){const a=new FormData;a.append("team_name",s.teamName),s.logo&&a.append("logo",s.logo);try{if(!(await fetch("/api/v1/settings",{method:"PATCH",credentials:"include",body:a})).ok){n.error("Failed to update workspace");return}n.success("Workspace updated")}catch{n.error("Failed to update workspace")}}const[k,F]=i.useState(null),D=i.useRef(null),c=S({resolver:y(pe),mode:"onBlur",defaultValues:{displayName:"",avatar:null}});i.useEffect(()=>{o&&(c.reset({displayName:o.displayName??"",avatar:null}),F(o.avatarUrl??null))},[o]);async function Y(s){const a=new FormData;a.append("display_name",s.displayName),s.avatar&&a.append("avatar",s.avatar);try{if(!(await fetch("/api/v1/profile",{method:"PATCH",credentials:"include",body:a})).ok){n.error("Failed to update profile");return}n.success("Profile updated")}catch{n.error("Failed to update profile")}}const t=S({resolver:y(fe),mode:"onBlur"});async function q(s){try{const a=await fetch("/api/v1/auth/change-password",{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({currentPassword:s.currentPassword,newPassword:s.newPassword})});if(!a.ok){const r=await a.json();t.setError("root",{message:r.error??"Failed to change password"});return}t.reset(),n.success("Password changed")}catch{t.setError("root",{message:"Network error"})}}const[T,E]=i.useState([]),[B,L]=i.useState({}),[U,z]=i.useState({}),[R,_]=i.useState({});i.useEffect(()=>{N&&fetch("/api/v1/apps",{credentials:"include"}).then(s=>s.ok?s.json():null).then(s=>{if(!s)return;E(s.items);const a={};s.items.forEach(r=>{a[r.id]=r.name}),L(a)})},[N]);async function G(s){_(a=>({...a,[s]:!0}));try{if(!(await fetch(`/api/v1/apps/${s}`,{method:"DELETE",credentials:"include"})).ok){n.error("Failed to delete app");return}E(r=>r.filter(l=>l.id!==s)),n.success("App deleted")}catch{n.error("Failed to delete app")}finally{_(a=>({...a,[s]:!1}))}}async function K(s){z(a=>({...a,[s]:!0}));try{if(!(await fetch(`/api/v1/apps/${s}`,{method:"PATCH",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:B[s]})})).ok){n.error("Failed to update app");return}n.success("App updated")}catch{n.error("Failed to update app")}finally{z(a=>({...a,[s]:!1}))}}const x=I({control:c.control,name:"displayName"});return e.jsxs("div",{className:"flex flex-col gap-6 max-w-[900px] mx-auto w-full p-6",children:[e.jsx("h1",{className:"text-xl font-semibold tracking-display-sm",children:"Settings"}),b&&e.jsxs(g,{children:[e.jsx(j,{children:e.jsx(v,{children:"Workspace"})}),e.jsx(w,{children:e.jsxs("form",{onSubmit:d.handleSubmit(Q),className:"flex flex-col gap-4",children:[e.jsxs("div",{className:"grid gap-2",children:[e.jsx(m,{htmlFor:"team-name",children:"Team name"}),e.jsx(h,{id:"team-name",placeholder:"My QA Team",...d.register("teamName")}),d.formState.errors.teamName&&e.jsx("p",{className:"text-sm text-destructive",children:d.formState.errors.teamName.message})]}),e.jsx(M,{}),e.jsxs("div",{className:"grid gap-2",children:[e.jsxs(m,{children:["Logo ",e.jsx("span",{className:"text-muted-foreground text-xs",children:"(png · jpg, max 2MB)"})]}),e.jsx(O,{name:"logo",control:d.control,render:({field:s})=>e.jsxs("div",{className:"relative w-16 h-16",children:[e.jsx("img",{src:V??J,alt:"Workspace logo",className:"w-16 h-16 rounded-lg object-contain"}),e.jsx("button",{type:"button","aria-label":"Change logo",onClick:()=>{var a;return(a=A.current)==null?void 0:a.click()},className:"absolute bottom-0 right-0 w-6 h-6 rounded-full bg-background border border-border shadow-sm flex items-center justify-center hover:bg-accent transition-colors",children:e.jsx(W,{className:"w-3 h-3","aria-hidden":"true"})}),e.jsx("input",{ref:A,type:"file",accept:".png,.jpg,.jpeg",className:"hidden",onChange:a=>{var l;const r=(l=a.target.files)==null?void 0:l[0];if(r&&r.size>2*1024*1024){n.error("Image must be 2MB or less");return}r&&(s.onChange(r),P(URL.createObjectURL(r)))}})]})})]}),e.jsx("div",{className:"flex justify-end",children:e.jsx(u,{type:"submit",size:"sm",disabled:d.formState.isSubmitting,children:d.formState.isSubmitting?"Saving…":"Save changes"})})]})})]}),e.jsxs(g,{children:[e.jsx(j,{children:e.jsx(v,{children:"Profile"})}),e.jsx(w,{children:e.jsxs("form",{onSubmit:c.handleSubmit(Y),className:"flex flex-col gap-4",children:[e.jsxs("div",{className:"grid gap-2",children:[e.jsx(m,{htmlFor:"display-name",children:"Nickname"}),e.jsx(h,{id:"display-name",placeholder:"Your name",...c.register("displayName")}),c.formState.errors.displayName&&e.jsx("p",{className:"text-sm text-destructive",children:c.formState.errors.displayName.message})]}),e.jsx(M,{}),e.jsxs("div",{className:"grid gap-2",children:[e.jsxs(m,{children:["Avatar ",e.jsx("span",{className:"text-muted-foreground text-xs",children:"(png · jpg, max 2MB)"})]}),e.jsx(O,{name:"avatar",control:c.control,render:({field:s})=>{var a;return e.jsxs("div",{className:"relative w-14 h-14",children:[k?e.jsx("img",{src:k,alt:"Profile avatar",className:"w-14 h-14 rounded-full object-cover border"}):e.jsx("div",{className:"w-14 h-14 rounded-full flex items-center justify-center text-lg font-medium",style:(()=>{const r=ee(x||(o==null?void 0:o.email)||"");return{backgroundColor:r.bg,color:r.fg}})(),children:((a=x==null?void 0:x[0])==null?void 0:a.toUpperCase())??"?"}),e.jsx("button",{type:"button","aria-label":"Change avatar",onClick:()=>{var r;return(r=D.current)==null?void 0:r.click()},className:"absolute bottom-0 right-0 w-6 h-6 rounded-full bg-background border border-border shadow-sm flex items-center justify-center hover:bg-accent transition-colors",children:e.jsx(W,{className:"w-3 h-3","aria-hidden":"true"})}),e.jsx("input",{ref:D,type:"file",accept:".png,.jpg,.jpeg",className:"hidden",onChange:r=>{var H;const l=(H=r.target.files)==null?void 0:H[0];if(l&&l.size>2*1024*1024){n.error("Image must be 2MB or less");return}l&&(s.onChange(l),F(URL.createObjectURL(l)))}})]})}})]}),e.jsx("div",{className:"flex justify-end",children:e.jsx(u,{type:"submit",size:"sm",disabled:c.formState.isSubmitting,children:c.formState.isSubmitting?"Saving…":"Save changes"})})]})})]}),e.jsxs(g,{children:[e.jsx(j,{children:e.jsx(v,{children:"Password"})}),e.jsx(w,{children:e.jsxs("form",{onSubmit:t.handleSubmit(q),className:"flex flex-col gap-4",children:[e.jsxs("div",{className:"grid gap-2",children:[e.jsx(m,{htmlFor:"current-password",children:"Current password"}),e.jsx(h,{id:"current-password",type:"password",...t.register("currentPassword")}),t.formState.errors.currentPassword&&e.jsx("p",{className:"text-sm text-destructive",children:t.formState.errors.currentPassword.message})]}),e.jsxs("div",{className:"grid gap-2",children:[e.jsx(m,{htmlFor:"new-password",children:"New password"}),e.jsx(h,{id:"new-password",type:"password",...t.register("newPassword")}),t.formState.errors.newPassword&&e.jsx("p",{className:"text-sm text-destructive",children:t.formState.errors.newPassword.message})]}),e.jsxs("div",{className:"grid gap-2",children:[e.jsx(m,{htmlFor:"confirm-password",children:"Confirm new password"}),e.jsx(h,{id:"confirm-password",type:"password",...t.register("confirmPassword")}),t.formState.errors.confirmPassword&&e.jsx("p",{className:"text-sm text-destructive",children:t.formState.errors.confirmPassword.message})]}),t.formState.errors.root&&e.jsx("p",{className:"text-sm text-destructive",children:t.formState.errors.root.message}),e.jsx("div",{className:"flex justify-end",children:e.jsx(u,{type:"submit",size:"sm",disabled:t.formState.isSubmitting,children:t.formState.isSubmitting?"Saving…":"Change password"})})]})})]}),N&&T.length>0&&e.jsxs(g,{children:[e.jsx(j,{children:e.jsx(v,{children:"Apps"})}),e.jsx(w,{children:e.jsx("div",{className:"flex flex-col divide-y divide-border",children:T.map(s=>e.jsxs("div",{className:"flex flex-col gap-2 py-4 first:pt-0 last:pb-0",children:[e.jsx(m,{htmlFor:`app-${s.id}`,children:"App Name"}),e.jsx(h,{id:`app-${s.id}`,value:B[s.id]??"",onChange:a=>L(r=>({...r,[s.id]:a.target.value}))}),e.jsxs("div",{className:"flex flex-col gap-1.5 mt-1",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("span",{className:"text-xs text-muted-foreground font-mono",children:s.bundle_id_key}),e.jsx(re,{tone:s.platform==="ios"?"ios":s.platform==="android"?"android":void 0,variant:s.platform==="both"?"secondary":void 0,className:"capitalize",children:s.platform})]}),e.jsxs("div",{className:"flex items-center justify-end gap-2",children:[e.jsx(u,{size:"sm",variant:"outline",disabled:U[s.id],onClick:()=>K(s.id),children:U[s.id]?"Saving…":"Save"}),e.jsxs(te,{children:[e.jsx(oe,{asChild:!0,children:e.jsx(u,{size:"sm",variant:"destructive",disabled:R[s.id],children:R[s.id]?"Deleting…":"Delete"})}),e.jsxs(ne,{children:[e.jsxs(ie,{children:[e.jsx(le,{children:"Delete app?"}),e.jsxs(ce,{children:["This will permanently delete ",e.jsx("strong",{children:s.name})," and all its builds. This action cannot be undone."]})]}),e.jsxs(de,{children:[e.jsx(me,{className:"w-24",children:"Cancel"}),e.jsx(he,{className:se(ae({variant:"destructive"}),"w-24"),onClick:()=>G(s.id),children:"Delete"})]})]})]})]})]})]},s.id))})})]})]})}export{Ne as DefaultSettings};
|
|
Binary file
|
|
Binary file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{i as n,j as e}from"./vendor-react-AJpf4YZ0.js";import{ad as I,a5 as A,ah as L,C as b,c as y,d as C,b as k,a as M,L as d,I as h,h as T,E as U,S as _,B as D,a2 as O,a3 as x,_ as Y}from"./index-
|
|
1
|
+
import{i as n,j as e}from"./vendor-react-AJpf4YZ0.js";import{ad as I,a5 as A,ah as L,C as b,c as y,d as C,b as k,a as M,L as d,I as h,h as T,E as U,S as _,B as D,a2 as O,a3 as x,_ as Y}from"./index-CBfjqtE-.js";import{b as z,c as H}from"./vendor-router-BFibuab1.js";import{P as V}from"./pencil-DMswV31e.js";const W=O({displayName:x().optional(),avatar:Y(File).nullable().optional(),password:x().min(8),confirm:x()}).refine(o=>o.password===o.confirm,{message:"Passwords do not match",path:["confirm"]});function K(){const o=z(),[S]=H(),c=S.get("token")??"",[f,m]=n.useState("loading"),[P,F]=n.useState(""),[v,R]=n.useState(null),j=n.useRef(null),{register:p,handleSubmit:B,control:g,setError:u,formState:{errors:t,isSubmitting:w}}=I({resolver:A(W),mode:"onBlur",defaultValues:{displayName:"",avatar:null}}),l=L({control:g,name:"displayName"})??"";n.useEffect(()=>{if(!c){m("invalid");return}fetch(`/api/v1/invitations/verify?token=${c}`).then(a=>a.ok?a.json():Promise.reject()).then(a=>{F(a.role),m("valid")}).catch(()=>m("invalid"))},[c]);async function E(a){var r;try{const s=new FormData;if(s.append("token",c),s.append("password",a.password),(r=a.displayName)!=null&&r.trim()&&s.append("display_name",a.displayName.trim()),a.avatar&&s.append("avatar",a.avatar),!(await fetch("/api/v1/invitations/accept",{method:"POST",body:s})).ok){u("root",{message:"Failed to accept invitation"});return}o("/app-center",{replace:!0})}catch{u("root",{message:"Network error. Please try again."})}}return f==="loading"?e.jsx("div",{className:"flex min-h-svh items-center justify-center"}):f==="invalid"?e.jsx("div",{className:"bg-mesh-gradient flex min-h-svh items-center justify-center overflow-hidden p-4",children:e.jsx(b,{level:4,className:"w-full max-w-sm text-center",children:e.jsxs(y,{children:[e.jsx(C,{children:"Invitation expired"}),e.jsx(k,{children:"This invite link is invalid or has expired. Ask your admin for a new one."})]})})}):e.jsx("div",{className:"bg-mesh-gradient flex min-h-svh items-center justify-center overflow-hidden p-4",children:e.jsxs(b,{level:4,className:"w-full max-w-sm",children:[e.jsxs(y,{className:"text-center",children:[e.jsx(C,{children:"Set up your account"}),e.jsxs(k,{children:["You're joining as ",e.jsx("strong",{children:P})]})]}),e.jsx(M,{children:e.jsxs("form",{onSubmit:B(E),className:"flex flex-col gap-4",children:[e.jsxs("div",{className:"grid gap-2",children:[e.jsxs(d,{htmlFor:"display-name",children:["Nickname ",e.jsx("span",{className:"text-muted-foreground text-xs",children:"(optional)"})]}),e.jsx(h,{id:"display-name",placeholder:"Your name",...p("displayName")})]}),e.jsxs("div",{className:"grid gap-2",children:[e.jsxs(d,{children:["Avatar ",e.jsx("span",{className:"text-muted-foreground text-xs",children:"(optional, png · jpg, max 2MB)"})]}),e.jsx(T,{name:"avatar",control:g,render:({field:a})=>{var r;return e.jsxs("div",{className:"relative w-14 h-14",children:[v?e.jsx("img",{src:v,alt:"Avatar preview",className:"w-14 h-14 rounded-full object-cover border"}):e.jsx("div",{className:"w-14 h-14 rounded-full flex items-center justify-center text-lg font-medium",style:(()=>{const s=U(l);return{backgroundColor:s.bg,color:s.fg}})(),children:((r=l==null?void 0:l[0])==null?void 0:r.toUpperCase())??"?"}),e.jsx("button",{type:"button","aria-label":"Change avatar",onClick:()=>{var s;return(s=j.current)==null?void 0:s.click()},className:"absolute bottom-0 right-0 w-6 h-6 rounded-full bg-background border border-border shadow-sm flex items-center justify-center hover:bg-accent transition-colors",children:e.jsx(V,{className:"w-3 h-3","aria-hidden":"true"})}),e.jsx("input",{ref:j,type:"file",accept:"image/png,image/jpeg",className:"hidden",onChange:s=>{var N;const i=(N=s.target.files)==null?void 0:N[0];if(i){if(i.size>2*1024*1024){u("avatar",{message:"Max 2MB for avatar"});return}a.onChange(i),R(URL.createObjectURL(i))}}})]})}}),t.avatar&&e.jsx("p",{className:"text-sm text-destructive",children:t.avatar.message})]}),e.jsx(_,{}),e.jsxs("div",{className:"grid gap-2",children:[e.jsx(d,{htmlFor:"password",children:"Password"}),e.jsx(h,{id:"password",type:"password",...p("password")}),t.password&&e.jsx("p",{className:"text-sm text-destructive",children:t.password.message})]}),e.jsxs("div",{className:"grid gap-2",children:[e.jsx(d,{htmlFor:"confirm",children:"Confirm password"}),e.jsx(h,{id:"confirm",type:"password",...p("confirm")}),t.confirm&&e.jsx("p",{className:"text-sm text-destructive",children:t.confirm.message})]}),t.root&&e.jsx("p",{className:"text-sm text-destructive",children:t.root.message}),e.jsx(D,{type:"submit",disabled:w,className:"w-full",children:w?"Creating account…":"Create account"})]})})]})})}export{K as Invite};
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{i,j as e}from"./vendor-react-AJpf4YZ0.js";import{u as Z,T as K,b as ee,c as te}from"./tabs-pPYCRfmT.js";import{a7 as se,M as ne}from"./index-CBfjqtE-.js";import{u as oe,h as re,g as ae,e as ie,c as le,G as ce,A as de,m as W,d as ue,a as he,b as me,L as fe,B as xe,l as pe,P as ge,f as je}from"./vendor-charts-DyX5CBcf.js";import"./vendor-router-BFibuab1.js";const ve={cpu:{label:"CPU",color:"#60a5fa"},mem:{label:"RAM",color:"#a78bfa"}},z={"1h":"1h","6h":"6h","24h":"24h","7d":"7d"},be={"1h":36e5,"6h":216e5,"24h":864e5,"7d":6048e5},ye={"1h":6e5,"6h":36e5,"24h":108e5,"7d":864e5};function ke(o,p){const n=new Date(o);return p==="7d"?`${n.getMonth()+1}/${n.getDate()}`:`${String(n.getHours()).padStart(2,"0")}:${String(n.getMinutes()).padStart(2,"0")}`}function Le(){const[o,p]=i.useState([]),[n,l]=i.useState([]),[a,d]=i.useState(null),[u,h]=i.useState("24h"),[w,b]=i.useState([]),[y,$]=i.useState(()=>Date.now()),[L,T]=i.useState(!1),{setNode:N}=se();i.useEffect(()=>(N(e.jsx("span",{className:"text-sm font-medium",children:"Mac Resources"})),()=>N(null)),[N]);const E=i.useCallback(t=>{t.type==="agents:listed"&&p(t.sessions??[])},[]),{send:M,connected:m}=Z(E);i.useEffect(()=>{if(!m)return;M({type:"agents:list"});const t=setInterval(()=>M({type:"agents:list"}),1e4);return()=>clearInterval(t)},[m,M]),i.useEffect(()=>{fetch("/api/v1/agents",{credentials:"include"}).then(t=>t.ok?t.json():[]).then(l)},[]);const g=o.map(t=>t.agentName).filter(Boolean),c=[...new Set([...g,...n])],S=new Set(g);i.useEffect(()=>{!a&&c.length>0&&d(c[0])},[c.join(",")]),i.useEffect(()=>{a&&(T(!0),fetch(`/api/v1/agents/${encodeURIComponent(a)}/resources?range=${u}`,{credentials:"include"}).then(t=>t.ok?t.json():[]).then(t=>{b(t),$(Date.now())}).finally(()=>T(!1)))},[a,u]);const k=w.map(t=>({time:t.recorded_at,cpu:Math.round(t.cpu_percent*10)/10,mem:Math.round(t.mem_percent*10)/10}));return e.jsxs("div",{className:"flex h-full min-h-0",children:[e.jsx("h1",{className:"sr-only",children:"Mac Resources"}),e.jsxs("aside",{"aria-labelledby":"macs-heading",className:"w-64 shrink-0 border-r flex flex-col gap-1 p-3 overflow-y-auto",children:[e.jsx("h2",{id:"macs-heading",className:"px-2 pb-1 font-mono text-xs font-medium text-muted-foreground uppercase tracking-wider",children:"Macs"}),c.length===0?e.jsx("span",{className:"px-2 text-sm text-muted-foreground",children:m?"No agents yet.":"Connecting…"}):c.map(t=>{const j=S.has(t),f=a===t;return e.jsxs("button",{onClick:()=>d(t),"aria-current":f?"true":void 0,className:["flex items-center gap-2 rounded-md px-2 py-2 text-left text-sm hover:bg-accent transition-colors",f?"bg-accent font-medium":""].join(" "),children:[e.jsx("span",{className:`inline-block h-1.5 w-1.5 shrink-0 rounded-full ${j?"bg-emerald-400":"bg-muted-foreground/40"}`,"aria-hidden":"true"}),e.jsx("span",{className:"truncate min-w-0",children:t}),e.jsx("span",{className:"sr-only",children:j?"Online":"Offline"})]},t)})]}),e.jsx("div",{className:"flex-1 min-w-0 overflow-auto",children:a?e.jsxs("div",{className:"flex flex-col gap-6 p-6",children:[e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsx("h2",{className:"text-base font-semibold",children:a}),e.jsx(K,{value:u,onValueChange:t=>h(t),children:e.jsx(ee,{children:Object.keys(z).map(t=>e.jsx(te,{value:t,children:z[t]},t))})})]}),L?e.jsx("div",{className:"flex h-40 items-center justify-center text-sm text-muted-foreground",children:"Loading…"}):k.length===0?e.jsx("div",{className:"flex h-40 items-center justify-center text-sm text-muted-foreground",children:"No data yet for this range. Data is collected every minute while the agent is connected."}):e.jsxs("div",{className:"flex flex-col gap-6",children:[e.jsx(Y,{title:"CPU %",color:"cpu",data:k,dataKey:"cpu",range:u,now:y}),e.jsx(Y,{title:"RAM %",color:"mem",data:k,dataKey:"mem",range:u,now:y})]})]}):e.jsxs("div",{className:"flex h-full items-center justify-center gap-2 text-muted-foreground",children:[e.jsx(ne,{className:"h-8 w-8"}),e.jsx("p",{className:"text-sm",children:"Select a Mac to view resource history."})]})})]})}const A=o=>new Date(o.time).getTime(),H=o=>new Date(o.time).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",hourCycle:"h23"}),I=o=>`${Math.round(o*10)/10}%`,we=je(A).left,x={top:8,right:24,bottom:24,left:40},X=16;function Y({title:o,color:p,data:n,dataKey:l,range:a,now:d}){const u=ve[p].color;return e.jsxs("div",{className:"rounded-lg border p-4 flex flex-col gap-3",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("span",{className:"inline-block h-2.5 w-2.5 rounded-full",style:{background:u}}),e.jsx("h2",{className:"text-sm font-medium",children:o})]}),e.jsx("div",{className:"relative h-[220px] w-full",children:e.jsx(ge,{children:({width:h,height:w})=>h>0&&w>0?e.jsx(Ne,{width:h,height:w,data:n,dataKey:l,hex:u,range:a,now:d,label:o}):null})})]})}function Ne({width:o,height:p,data:n,dataKey:l,hex:a,range:d,now:u,label:h}){const{showTooltip:w,hideTooltip:b,tooltipData:y,tooltipLeft:$,tooltipTop:L}=oe(),T=`chart-hint-${l}`,[N,E]=i.useState(null),M=Math.max(0,n.length-1),m=N===null?M:Math.min(N,M),g=o-x.left-x.right,c=p-x.top-x.bottom,S=ye[d],k=u,t=k-be[d],j=re({domain:[t,k],range:[0,Math.max(0,g)]}),f=ae({domain:[0,100],range:[c,X]}),O=Math.floor(k/S)*S,P=Math.floor((O-t)/S)+1,q=Array.from({length:P},(s,r)=>new Date(O-(P-1-r)*S)),_=`fill-${l}`,B=`plot-${l}`,R=s=>{const r=n[s];r&&(E(s),w({tooltipData:r,tooltipLeft:j(A(r)),tooltipTop:f(r[l])}))},J=s=>{if(n.length===0)return;if(s.key==="Escape"){b();return}const r=m,v=s.key==="ArrowLeft"||s.key==="ArrowDown"?Math.max(0,r-1):s.key==="ArrowRight"||s.key==="ArrowUp"?Math.min(n.length-1,r+1):s.key==="Home"?0:s.key==="End"?n.length-1:null;v!==null&&(s.preventDefault(),R(v))},G=s=>{const r=pe(s);if(!r)return;const v=j.invert(r.x-x.left).getTime(),U=we(n,v),C=n[U-1],D=n[U],V=C&&D?v-A(C)<A(D)-v?C:D:C??D;V&&R(n.indexOf(V))},F=n[n.length-1],Q=s=>`${h}, ${H(s)}, ${I(s[l])}`;return e.jsxs(e.Fragment,{children:[e.jsxs("svg",{width:o,height:p,role:"group","aria-label":F?`${h}, last ${d}. Latest ${I(F[l])}.`:`${h}, last ${d}. No samples.`,children:[e.jsx(ie,{id:_,from:a,to:a,fromOpacity:.3,toOpacity:0,fromOffset:"5%",toOffset:"95%"}),e.jsx("clipPath",{id:B,children:e.jsx("rect",{x:0,y:0,width:Math.max(0,g),height:Math.max(0,c)})}),e.jsxs(le,{left:x.left,top:x.top,children:[e.jsx(ce,{scale:f,width:g,tickValues:[0,25,50,75,100],strokeDasharray:"3 3",stroke:"hsl(var(--border))"}),e.jsxs("g",{clipPath:`url(#${B})`,children:[e.jsx(de,{data:n,x:s=>j(A(s)),y:s=>f(s[l]),yScale:f,curve:W,fill:`url(#${_})`}),e.jsx(ue,{data:n,x:s=>j(A(s)),y:s=>f(s[l]),curve:W,stroke:a,strokeWidth:1.5})]}),e.jsx(he,{top:c,scale:j,tickValues:q,tickFormat:s=>ke(new Date(+s).toISOString(),d),hideAxisLine:!0,hideTicks:!0,tickLength:0,tickLabelProps:(s,r,v)=>({fontSize:11,fill:"currentColor",textAnchor:r===0?"start":r===v.length-1?"end":"middle",dy:6,className:"fill-muted-foreground"})}),e.jsx(me,{scale:f,tickValues:[0,25,50,75,100],tickFormat:s=>`${s}%`,hideAxisLine:!0,hideTicks:!0,tickLabelProps:()=>({fontSize:11,fill:"currentColor",textAnchor:"end",dx:-4,dy:3,className:"fill-muted-foreground"})}),y&&e.jsxs("g",{style:{transition:"transform 0.25s ease-out",transform:`translateX(${$??0}px)`},pointerEvents:"none",children:[e.jsx(fe,{from:{x:0,y:X},to:{x:0,y:c},stroke:"hsl(var(--border))",strokeWidth:1}),e.jsx("circle",{cx:0,cy:0,r:3,fill:a,stroke:"hsl(var(--background))",strokeWidth:1.5,style:{transition:"transform 0.25s ease-out",transform:`translateY(${L??0}px)`}})]}),e.jsx(xe,{x:0,y:0,width:Math.max(0,g),height:Math.max(0,c),fill:"transparent",tabIndex:0,role:"slider","aria-label":`${h} samples`,"aria-valuemin":0,"aria-valuemax":Math.max(0,n.length-1),"aria-valuenow":m,"aria-valuetext":n[m]?Q(n[m]):void 0,"aria-describedby":T,className:"outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring",onFocus:()=>R(m),onBlur:b,onKeyDown:J,onMouseMove:G,onMouseLeave:b,onTouchMove:G,onTouchEnd:b,onTouchCancel:b})]})]}),e.jsx("p",{id:T,className:"sr-only",children:"Use the arrow keys to read individual samples. Escape hides the reading."}),y&&e.jsxs("div",{"aria-hidden":"true",className:"pointer-events-none absolute top-0 left-0 whitespace-nowrap rounded-lg border bg-background px-3 py-2 text-xs text-foreground shadow-md",style:{transform:`translate(${($??0)+x.left}px, ${(L??0)+x.top}px) translate(${($??0)>g*.6?"calc(-100% - 12px)":"12px"}, -50%)`,transition:"transform 0.25s ease-out"},children:[e.jsxs("p",{className:"mb-1",children:["Date:"," ",H(y)]}),e.jsxs("p",{children:[h,": ",I(y[l])]})]})]})}export{Ne as AreaChartInner,Le as MacResources};
|
|
Binary file
|
|
Binary file
|
|
Binary file
|