@tapflowio/relay 0.21.0 → 0.23.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.
Files changed (182) hide show
  1. package/dist/RelayServer.d.ts +20 -0
  2. package/dist/RelayServer.d.ts.map +1 -1
  3. package/dist/RelayServer.js +124 -16
  4. package/dist/RelayServer.js.map +1 -1
  5. package/dist/SessionManager.d.ts +4 -1
  6. package/dist/SessionManager.d.ts.map +1 -1
  7. package/dist/SessionManager.js +7 -0
  8. package/dist/SessionManager.js.map +1 -1
  9. package/dist/api/auth.d.ts.map +1 -1
  10. package/dist/api/auth.js +2 -1
  11. package/dist/api/auth.js.map +1 -1
  12. package/dist/api/passwordReset.d.ts +6 -2
  13. package/dist/api/passwordReset.d.ts.map +1 -1
  14. package/dist/api/passwordReset.js +21 -6
  15. package/dist/api/passwordReset.js.map +1 -1
  16. package/dist/api/team.d.ts +2 -1
  17. package/dist/api/team.d.ts.map +1 -1
  18. package/dist/api/team.js +7 -5
  19. package/dist/api/team.js.map +1 -1
  20. package/dist/index.d.ts +3 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +2 -1
  23. package/dist/index.js.map +1 -1
  24. package/dist/lib/clientAddress.d.ts +15 -0
  25. package/dist/lib/clientAddress.d.ts.map +1 -1
  26. package/dist/lib/clientAddress.js +21 -0
  27. package/dist/lib/clientAddress.js.map +1 -1
  28. package/dist/lib/config.d.ts +2 -0
  29. package/dist/lib/config.d.ts.map +1 -1
  30. package/dist/lib/config.js +6 -0
  31. package/dist/lib/config.js.map +1 -1
  32. package/dist/lib/lanAddress.d.ts +10 -0
  33. package/dist/lib/lanAddress.d.ts.map +1 -1
  34. package/dist/lib/lanAddress.js +13 -0
  35. package/dist/lib/lanAddress.js.map +1 -1
  36. package/dist/lib/proxyConfig.d.ts +14 -2
  37. package/dist/lib/proxyConfig.d.ts.map +1 -1
  38. package/dist/lib/proxyConfig.js +36 -13
  39. package/dist/lib/proxyConfig.js.map +1 -1
  40. package/dist/lib/publicUrl.d.ts +31 -1
  41. package/dist/lib/publicUrl.d.ts.map +1 -1
  42. package/dist/lib/publicUrl.js +51 -9
  43. package/dist/lib/publicUrl.js.map +1 -1
  44. package/dist/server.js +10 -2
  45. package/dist/server.js.map +1 -1
  46. package/package.json +5 -5
  47. package/public/assets/AppCenter-OTDuqwSf.js +21 -0
  48. package/public/assets/AppCenter-OTDuqwSf.js.br +0 -0
  49. package/public/assets/AppCenter-OTDuqwSf.js.gz +0 -0
  50. package/public/assets/{Default-D1inA8sU.js → Default-D9GLpWlN.js} +1 -1
  51. package/public/assets/Default-D9GLpWlN.js.br +0 -0
  52. package/public/assets/Default-D9GLpWlN.js.gz +0 -0
  53. package/public/assets/{Invite-ChaVEWvP.js → Invite-Bhiam8En.js} +1 -1
  54. package/public/assets/Invite-Bhiam8En.js.br +0 -0
  55. package/public/assets/Invite-Bhiam8En.js.gz +0 -0
  56. package/public/assets/MacResources-DObzVJxQ.js +3 -0
  57. package/public/assets/MacResources-DObzVJxQ.js.br +0 -0
  58. package/public/assets/MacResources-DObzVJxQ.js.gz +0 -0
  59. package/public/assets/{NotFound-BoDDivK5.js → NotFound-Dz_k_Mzf.js} +1 -1
  60. package/public/assets/NotFound-Dz_k_Mzf.js.br +0 -0
  61. package/public/assets/NotFound-Dz_k_Mzf.js.gz +0 -0
  62. package/public/assets/QASession-2crubOLY.js +150 -0
  63. package/public/assets/QASession-2crubOLY.js.br +0 -0
  64. package/public/assets/QASession-2crubOLY.js.gz +0 -0
  65. package/public/assets/{ResetPassword-Ut9BFums.js → ResetPassword-EUm12ojo.js} +1 -1
  66. package/public/assets/ResetPassword-EUm12ojo.js.br +0 -0
  67. package/public/assets/ResetPassword-EUm12ojo.js.gz +0 -0
  68. package/public/assets/{Setup-CoHwaxo7.js → Setup-Fc_0RW0a.js} +1 -1
  69. package/public/assets/Setup-Fc_0RW0a.js.br +0 -0
  70. package/public/assets/Setup-Fc_0RW0a.js.gz +0 -0
  71. package/public/assets/Team-Ac5aOGgx.js +6 -0
  72. package/public/assets/Team-Ac5aOGgx.js.br +0 -0
  73. package/public/assets/Team-Ac5aOGgx.js.gz +0 -0
  74. package/public/assets/Tokens-pEnCy-7l.js +1 -0
  75. package/public/assets/Tokens-pEnCy-7l.js.br +0 -0
  76. package/public/assets/Tokens-pEnCy-7l.js.gz +0 -0
  77. package/public/assets/alert-dialog-DhOTocMh.js +1 -0
  78. package/public/assets/alert-dialog-DhOTocMh.js.br +0 -0
  79. package/public/assets/alert-dialog-DhOTocMh.js.gz +0 -0
  80. package/public/assets/{badge-CikXk90N.js → badge-B4gbViMs.js} +1 -1
  81. package/public/assets/badge-B4gbViMs.js.br +0 -0
  82. package/public/assets/badge-B4gbViMs.js.gz +0 -0
  83. package/public/assets/build-format-DKGajT96.js +11 -0
  84. package/public/assets/build-format-DKGajT96.js.br +0 -0
  85. package/public/assets/build-format-DKGajT96.js.gz +0 -0
  86. package/public/assets/dialog-DEL1xxKJ.js +11 -0
  87. package/public/assets/dialog-DEL1xxKJ.js.br +0 -0
  88. package/public/assets/dialog-DEL1xxKJ.js.gz +0 -0
  89. package/public/assets/index-Bxs7CdEi.css +1 -0
  90. package/public/assets/index-Bxs7CdEi.css.br +0 -0
  91. package/public/assets/index-Bxs7CdEi.css.gz +0 -0
  92. package/public/assets/index-COIlCLCW.js +218 -0
  93. package/public/assets/index-COIlCLCW.js.br +0 -0
  94. package/public/assets/index-COIlCLCW.js.gz +0 -0
  95. package/public/assets/pencil-CPaoEKFm.js +6 -0
  96. package/public/assets/pencil-CPaoEKFm.js.br +0 -0
  97. package/public/assets/pencil-CPaoEKFm.js.gz +0 -0
  98. package/public/assets/publicLink-D6zKS_wu.js +1 -0
  99. package/public/assets/publicLink-D6zKS_wu.js.br +0 -0
  100. package/public/assets/publicLink-D6zKS_wu.js.gz +0 -0
  101. package/public/assets/resource-health-Ca4-5Rde.js +1 -0
  102. package/public/assets/resource-health-Ca4-5Rde.js.br +0 -0
  103. package/public/assets/resource-health-Ca4-5Rde.js.gz +0 -0
  104. package/public/assets/{table-dbLmBL_w.js → table-CSeGVnii.js} +1 -1
  105. package/public/assets/table-CSeGVnii.js.br +0 -0
  106. package/public/assets/table-CSeGVnii.js.gz +0 -0
  107. package/public/assets/trash-CD9Y9G-i.js +11 -0
  108. package/public/assets/trash-CD9Y9G-i.js.br +0 -0
  109. package/public/assets/trash-CD9Y9G-i.js.gz +0 -0
  110. package/public/assets/vendor-charts-YPhPkjLw.js +5 -0
  111. package/public/assets/vendor-charts-YPhPkjLw.js.br +0 -0
  112. package/public/assets/vendor-charts-YPhPkjLw.js.gz +0 -0
  113. package/public/assets/vendor-react-C5H_1fOn.js +49 -0
  114. package/public/assets/vendor-react-C5H_1fOn.js.br +0 -0
  115. package/public/assets/vendor-react-C5H_1fOn.js.gz +0 -0
  116. package/public/assets/{vendor-router-BFibuab1.js → vendor-router-CnmrRwJF.js} +1 -1
  117. package/public/assets/vendor-router-CnmrRwJF.js.br +0 -0
  118. package/public/assets/vendor-router-CnmrRwJF.js.gz +0 -0
  119. package/public/index.html +4 -4
  120. package/public/index.html.br +0 -0
  121. package/public/index.html.gz +0 -0
  122. package/public/assets/AppCenter-BkSQFKRv.js +0 -21
  123. package/public/assets/AppCenter-BkSQFKRv.js.br +0 -0
  124. package/public/assets/AppCenter-BkSQFKRv.js.gz +0 -0
  125. package/public/assets/Default-D1inA8sU.js.br +0 -0
  126. package/public/assets/Default-D1inA8sU.js.gz +0 -0
  127. package/public/assets/Invite-ChaVEWvP.js.br +0 -0
  128. package/public/assets/Invite-ChaVEWvP.js.gz +0 -0
  129. package/public/assets/MacResources-CLChFyJW.js +0 -1
  130. package/public/assets/MacResources-CLChFyJW.js.br +0 -0
  131. package/public/assets/MacResources-CLChFyJW.js.gz +0 -0
  132. package/public/assets/NotFound-BoDDivK5.js.br +0 -0
  133. package/public/assets/NotFound-BoDDivK5.js.gz +0 -0
  134. package/public/assets/QASession-DICynZa0.js +0 -140
  135. package/public/assets/QASession-DICynZa0.js.br +0 -0
  136. package/public/assets/QASession-DICynZa0.js.gz +0 -0
  137. package/public/assets/ResetPassword-Ut9BFums.js.br +0 -0
  138. package/public/assets/ResetPassword-Ut9BFums.js.gz +0 -0
  139. package/public/assets/Setup-CoHwaxo7.js.br +0 -0
  140. package/public/assets/Setup-CoHwaxo7.js.gz +0 -0
  141. package/public/assets/Team-DaVY1DGm.js +0 -6
  142. package/public/assets/Team-DaVY1DGm.js.br +0 -0
  143. package/public/assets/Team-DaVY1DGm.js.gz +0 -0
  144. package/public/assets/Tokens-_KZJ7AZ9.js +0 -1
  145. package/public/assets/Tokens-_KZJ7AZ9.js.br +0 -0
  146. package/public/assets/Tokens-_KZJ7AZ9.js.gz +0 -0
  147. package/public/assets/alert-dialog-Cc_pQpe6.js +0 -1
  148. package/public/assets/alert-dialog-Cc_pQpe6.js.br +0 -0
  149. package/public/assets/alert-dialog-Cc_pQpe6.js.gz +0 -0
  150. package/public/assets/badge-CikXk90N.js.br +0 -0
  151. package/public/assets/badge-CikXk90N.js.gz +0 -0
  152. package/public/assets/build-format-DsApFPHy.js +0 -11
  153. package/public/assets/build-format-DsApFPHy.js.br +0 -0
  154. package/public/assets/build-format-DsApFPHy.js.gz +0 -0
  155. package/public/assets/dialog-B4Wg7TCg.js +0 -11
  156. package/public/assets/dialog-B4Wg7TCg.js.br +0 -0
  157. package/public/assets/dialog-B4Wg7TCg.js.gz +0 -0
  158. package/public/assets/index-CBfjqtE-.js +0 -198
  159. package/public/assets/index-CBfjqtE-.js.br +0 -0
  160. package/public/assets/index-CBfjqtE-.js.gz +0 -0
  161. package/public/assets/index-ClHUQm-F.css +0 -1
  162. package/public/assets/index-ClHUQm-F.css.br +0 -0
  163. package/public/assets/index-ClHUQm-F.css.gz +0 -0
  164. package/public/assets/pencil-DMswV31e.js +0 -6
  165. package/public/assets/pencil-DMswV31e.js.br +0 -0
  166. package/public/assets/pencil-DMswV31e.js.gz +0 -0
  167. package/public/assets/table-dbLmBL_w.js.br +0 -0
  168. package/public/assets/table-dbLmBL_w.js.gz +0 -0
  169. package/public/assets/tabs-pPYCRfmT.js +0 -1
  170. package/public/assets/tabs-pPYCRfmT.js.br +0 -0
  171. package/public/assets/tabs-pPYCRfmT.js.gz +0 -0
  172. package/public/assets/trash-VgeHmI8N.js +0 -11
  173. package/public/assets/trash-VgeHmI8N.js.br +0 -0
  174. package/public/assets/trash-VgeHmI8N.js.gz +0 -0
  175. package/public/assets/vendor-charts-DyX5CBcf.js +0 -5
  176. package/public/assets/vendor-charts-DyX5CBcf.js.br +0 -0
  177. package/public/assets/vendor-charts-DyX5CBcf.js.gz +0 -0
  178. package/public/assets/vendor-react-AJpf4YZ0.js +0 -49
  179. package/public/assets/vendor-react-AJpf4YZ0.js.br +0 -0
  180. package/public/assets/vendor-react-AJpf4YZ0.js.gz +0 -0
  181. package/public/assets/vendor-router-BFibuab1.js.br +0 -0
  182. package/public/assets/vendor-router-BFibuab1.js.gz +0 -0
@@ -1,15 +1,57 @@
1
- // #6 — 초대/공개 링크의 신뢰 base URL.
2
- // `req.headers.host`/`x-forwarded-proto`는 클라이언트가 조작할 수 있어(Host 인젝션) 피싱 링크가
3
- // 정상 메일로 발송될 수 있다. 따라서 Host 헤더 대신 설정값에서만 base를 도출한다.
4
- // 우선순위: 터널 공개 URL → 설정된 relay URL(ws→http 변환) → localhost 폴백.
5
- export function buildInviteBaseUrl(cfg) {
6
- if (cfg.tunnel?.publicUrl)
7
- return stripTrailingSlash(cfg.tunnel.publicUrl);
1
+ /**
2
+ * Every public base this relay is known by, most preferred first: the tunnel (as started, or as configured
3
+ * when no entry point reports one), then `relay.url`. CORS allowlists all of them. Each is an address the
4
+ * operator gave this relay, and dropping `relay.url` whenever a tunnel came up would 403 the dashboard
5
+ * behind a proxy that rewrites Host.
6
+ */
7
+ export function resolvePublicBaseUrls(cfg, tunnel) {
8
+ const bases = [];
9
+ const tunnelUrl = tunnel ? tunnel.publicUrl : cfg.tunnel?.publicUrl;
10
+ if (tunnelUrl)
11
+ bases.push(stripTrailingSlash(tunnelUrl));
8
12
  if (cfg.relay.url) {
9
13
  const http = cfg.relay.url.replace(/^ws:\/\//i, 'http://').replace(/^wss:\/\//i, 'https://');
10
- return stripTrailingSlash(http);
14
+ bases.push(stripTrailingSlash(http));
11
15
  }
12
- return `http://localhost:${cfg.local.port}`;
16
+ return bases;
17
+ }
18
+ /** The base a teammate's browser should use, or null when neither a tunnel nor `relay.url` gives one. */
19
+ export function resolvePublicBaseUrl(cfg, tunnel) {
20
+ return resolvePublicBaseUrls(cfg, tunnel)[0] ?? null;
21
+ }
22
+ /** Base for links in outgoing mail. Called with one argument, the result is what it has always been. */
23
+ export function buildInviteBaseUrl(cfg, tunnel) {
24
+ return resolvePublicBaseUrl(cfg, tunnel) ?? `http://localhost:${cfg.local.port}`;
25
+ }
26
+ /** Where an agent should connect, as ws/wss, from `relay.url` alone. */
27
+ export function resolveAgentRelayUrl(cfg) {
28
+ if (!cfg.relay.url)
29
+ return null;
30
+ const ws = cfg.relay.url.replace(/^http:\/\//i, 'ws://').replace(/^https:\/\//i, 'wss://');
31
+ return stripTrailingSlash(ws);
32
+ }
33
+ const UNOPENABLE_HOSTS = new Set(['localhost', '::1', '0.0.0.0', '::']);
34
+ /**
35
+ * Null for an address a teammate cannot open: loopback in any spelling the URL parser leaves (`127.x`,
36
+ * `::1`, `::ffff:7f00:1`, a trailing-dot `localhost.`), a bind-all address (`0.0.0.0`, `::`), or one that
37
+ * does not parse. `relay.url` is legitimately `ws://localhost:4000` for a co-located CLI — the MCP docs
38
+ * give that value for the same variable — and handing it to the dashboard would replace a working browser
39
+ * origin with localhost. Applied only to what the dashboard receives; mail and CORS keep the unfiltered value.
40
+ */
41
+ export function forTeammates(url) {
42
+ if (url === null)
43
+ return null;
44
+ let hostname;
45
+ try {
46
+ hostname = new URL(url).hostname;
47
+ }
48
+ catch {
49
+ return null;
50
+ }
51
+ const host = hostname.replace(/^\[|\]$/g, '').replace(/\.$/, '');
52
+ if (UNOPENABLE_HOSTS.has(host) || /^127\./.test(host) || /^::ffff:(127\.|7f[0-9a-f]{2}:)/i.test(host))
53
+ return null;
54
+ return url;
13
55
  }
14
56
  function stripTrailingSlash(u) {
15
57
  return u.replace(/\/+$/, '');
@@ -1 +1 @@
1
- {"version":3,"file":"publicUrl.js","sourceRoot":"","sources":["../../src/lib/publicUrl.ts"],"names":[],"mappings":"AAEA,8BAA8B;AAC9B,2EAA2E;AAC3E,qDAAqD;AACrD,8DAA8D;AAC9D,MAAM,UAAU,kBAAkB,CAAC,GAAsD;IACvF,IAAI,GAAG,CAAC,MAAM,EAAE,SAAS;QAAE,OAAO,kBAAkB,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;IAC1E,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;QAClB,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC,OAAO,CAAC,YAAY,EAAE,UAAU,CAAC,CAAA;QAC5F,OAAO,kBAAkB,CAAC,IAAI,CAAC,CAAA;IACjC,CAAC;IACD,OAAO,oBAAoB,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,CAAA;AAC7C,CAAC;AAED,SAAS,kBAAkB,CAAC,CAAS;IACnC,OAAO,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;AAC9B,CAAC"}
1
+ {"version":3,"file":"publicUrl.js","sourceRoot":"","sources":["../../src/lib/publicUrl.ts"],"names":[],"mappings":"AAkCA;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,GAAoB,EAAE,MAAsB;IAChF,MAAM,KAAK,GAAa,EAAE,CAAA;IAC1B,MAAM,SAAS,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,EAAE,SAAS,CAAA;IACnE,IAAI,SAAS;QAAE,KAAK,CAAC,IAAI,CAAC,kBAAkB,CAAC,SAAS,CAAC,CAAC,CAAA;IACxD,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;QAClB,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC,OAAO,CAAC,YAAY,EAAE,UAAU,CAAC,CAAA;QAC5F,KAAK,CAAC,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAA;IACtC,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,yGAAyG;AACzG,MAAM,UAAU,oBAAoB,CAAC,GAAoB,EAAE,MAAsB;IAC/E,OAAO,qBAAqB,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAA;AACtD,CAAC;AAED,wGAAwG;AACxG,MAAM,UAAU,kBAAkB,CAAC,GAAoB,EAAE,MAAsB;IAC7E,OAAO,oBAAoB,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,oBAAoB,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,CAAA;AAClF,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,oBAAoB,CAAC,GAAiC;IACpE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG;QAAE,OAAO,IAAI,CAAA;IAC/B,MAAM,EAAE,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAA;IAC1F,OAAO,kBAAkB,CAAC,EAAE,CAAC,CAAA;AAC/B,CAAC;AAED,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC,CAAA;AAEvE;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,GAAkB;IAC7C,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,IAAI,CAAA;IAC7B,IAAI,QAAgB,CAAA;IACpB,IAAI,CAAC;QACH,QAAQ,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAA;IAClC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;IACD,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;IAChE,IAAI,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,iCAAiC,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAA;IAClH,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,SAAS,kBAAkB,CAAC,CAAS;IACnC,OAAO,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;AAC9B,CAAC"}
package/dist/server.js CHANGED
@@ -2,7 +2,8 @@ import path from 'path';
2
2
  import { initDb } from './db.js';
3
3
  import { RelayServer } from './RelayServer.js';
4
4
  import { config, loadedEnvPath } from './lib/config.js';
5
- import { buildCorsOrigins, proxyWithoutPublicUrlWarning } from './lib/proxyConfig.js';
5
+ import { buildCorsOrigins, containerWithoutPublicUrlWarning, proxyWithoutPublicUrlWarning } from './lib/proxyConfig.js';
6
+ import { runningInContainer } from './lib/lanAddress.js';
6
7
  import { bootstrapAdminFromEnv, AdminBootstrapError } from './lib/adminBootstrap.js';
7
8
  import { createCertProvider, resolveRelayDisplayHost } from './lib/cert/index.js';
8
9
  import { startTlsBackgroundTasks } from './lib/tlsTasks.js';
@@ -40,6 +41,9 @@ const corsOrigins = buildCorsOrigins(config, port);
40
41
  const proxyWarning = proxyWithoutPublicUrlWarning(config);
41
42
  if (proxyWarning)
42
43
  logger.warn(proxyWarning);
44
+ const containerWarning = containerWithoutPublicUrlWarning(config, runningInContainer());
45
+ if (containerWarning)
46
+ logger.warn(containerWarning);
43
47
  async function main() {
44
48
  let tls;
45
49
  let provider = null;
@@ -54,7 +58,11 @@ async function main() {
54
58
  logger.info('TLS disabled — serving HTTP. Secure-context features (e.g. WebCodecs hardware decode) require HTTPS; ' +
55
59
  'configure tls in tapflow.config.json to enable.');
56
60
  }
57
- const server = new RelayServer({ port, uploadsDir, wsBackpressureBytes: config.local.wsBackpressureBytes, trustedProxies: config.local.trustedProxies, corsOrigins, tls });
61
+ // No `tunnel` option: this entry point starts no tunnel, so it has no runtime state to report and the
62
+ // relay trusts config — an operator may run the tunnel alongside. See lib/publicUrl.ts.
63
+ // The tunnel listener opens only when its port is named: it is loopback-only, so inside a container only
64
+ // something sharing the relay's network namespace (a sidecar, host networking) could reach it at all.
65
+ const server = new RelayServer({ port, uploadsDir, wsBackpressureBytes: config.local.wsBackpressureBytes, trustedProxies: config.local.trustedProxies, corsOrigins, tls, tunnelPort: config.local.tunnelPort ?? undefined });
58
66
  await server.start();
59
67
  logger.info(`tapflow relay running at ${tls ? 'https' : 'http'}://${displayHost}:${port}`);
60
68
  const stopTls = provider ? startTlsBackgroundTasks(provider, server, config.tls) : null;
@@ -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.21.0",
3
+ "version": "0.23.0",
4
4
  "description": "WebSocket relay server for tapflow — NAT traversal, session routing, JWT auth, bundled dashboard",
5
5
  "keywords": [
6
6
  "tapflow",
@@ -51,15 +51,15 @@
51
51
  "jsonwebtoken": "^9.0.3",
52
52
  "nodemailer": "^9.1.1",
53
53
  "ws": "^8.21.3",
54
- "zod": "^4.5.4",
55
- "@tapflowio/agent-core": "0.21.0",
56
- "@tapflowio/protocol": "0.21.0"
54
+ "zod": "^4.6.5",
55
+ "@tapflowio/agent-core": "0.23.0",
56
+ "@tapflowio/protocol": "0.23.0"
57
57
  },
58
58
  "devDependencies": {
59
59
  "@types/better-sqlite3": "^7.6.13",
60
60
  "@types/busboy": "^1.5.4",
61
61
  "@types/jsonwebtoken": "^9.0.10",
62
- "@types/node": "^22.20.1",
62
+ "@types/node": "^22.20.2",
63
63
  "@types/nodemailer": "^8.0.1",
64
64
  "@types/ws": "^8.0.0",
65
65
  "typescript": "^5.0.0",
@@ -0,0 +1,21 @@
1
+ import{i as r,j as e}from"./vendor-react-C5H_1fOn.js";import{a1 as C,B as y,L as b,a9 as D,W as K,I as M,S as X,f as ee}from"./index-COIlCLCW.js";import{e as se,S as ae,P as te,g as le,h as ne,i as re,b as ie,d as ce,s as oe,u as de}from"./build-format-DKGajT96.js";import{D as O,f as L,a as U,d as E,e as V,S as A,i as _,j as z,g as B,h as c,C as ue}from"./dialog-DEL1xxKJ.js";import{P as he,T as xe}from"./trash-CD9Y9G-i.js";import{B as T}from"./badge-B4gbViMs.js";import{A as pe,c as me,f as fe,g as ge,d as je,e as ve,b as Ne,a as Se}from"./alert-dialog-DhOTocMh.js";import{b as ye,c as we}from"./vendor-router-CnmrRwJF.js";/**
2
+ * @license lucide-react v1.45.0 - ISC
3
+ *
4
+ * This source code is licensed under the ISC license.
5
+ * See the LICENSE file in the root directory of this source tree.
6
+ */const $={name:"layers",size:24,node:[["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"}]],aliases:["layers-3"]};$.node;const be=C($);/**
7
+ * @license lucide-react v1.45.0 - ISC
8
+ *
9
+ * This source code is licensed under the ISC license.
10
+ * See the LICENSE file in the root directory of this source tree.
11
+ */const q={name:"package",size:24,node:[["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"}]]};q.node;const De=C(q);/**
12
+ * @license lucide-react v1.45.0 - ISC
13
+ *
14
+ * This source code is licensed under the ISC license.
15
+ * See the LICENSE file in the root directory of this source tree.
16
+ */const H={name:"timer-off",size:24,node:[["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"}]]};H.node;const ke=C(H);/**
17
+ * @license lucide-react v1.45.0 - ISC
18
+ *
19
+ * This source code is licensed under the ISC license.
20
+ * See the LICENSE file in the root directory of this source tree.
21
+ */const Z={name:"upload",size:24,node:[["path",{d:"M12 3v12",key:"1x0j5s"}],["path",{d:"m17 8-5-5-5 5",key:"7q97r8"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}]]};Z.node;const Ce=C(Z);function Ae({onSuccess:s,appId:l}){const[h,i]=r.useState(!1),[t,g]=r.useState(null),[o,x]=r.useState("none"),j=r.useRef(null);function p(n){if(n.preventDefault(),!t)return;const d=new FormData;d.append("file",t),o!=="none"&&d.append("status",o),l&&d.append("app_id",String(l));const w=fetch("/api/v1/builds",{method:"POST",credentials:"include",body:d}).then(async u=>{if(!u.ok){const m=await u.json().catch(()=>({}));throw new Error(m.error??"Upload failed. Check the file format.")}s()});D.promise(w,{loading:"Uploading build…",success:"Build uploaded",error:u=>u instanceof Error?u.message:"Failed to upload build"}),i(!1),g(null),x("none")}function N(n){i(n),n||(g(null),x("none"))}return e.jsxs(O,{open:h,onOpenChange:N,children:[e.jsx(L,{asChild:!0,children:e.jsxs(y,{size:"sm",children:[e.jsx(Ce,{className:"mr-2 h-4 w-4"}),"Upload build"]})}),e.jsxs(U,{className:"sm:max-w-md",children:[e.jsx(E,{children:e.jsx(V,{children:"Upload build"})}),e.jsxs("form",{onSubmit:p,className:"flex flex-col gap-4 pt-2",children:[e.jsxs("div",{className:"grid gap-2",children:[e.jsx(b,{children:"File"}),e.jsx("div",{className:"flex h-24 cursor-pointer items-center justify-center rounded-md border-2 border-dashed text-sm text-muted-foreground hover:border-primary",onClick:()=>{var n;return(n=j.current)==null?void 0:n.click()},onDragOver:n=>n.preventDefault(),onDrop:n=>{n.preventDefault();const d=n.dataTransfer.files[0];d&&g(d)},children:t?t.name:"Click or drag to upload"}),e.jsx("input",{ref:j,type:"file",accept:".zip,.apk,.tgz,.tar.gz,application/gzip,application/x-gzip",className:"hidden",onChange:n=>{var d;return g(((d=n.target.files)==null?void 0:d[0])??null)}}),e.jsxs("p",{className:"text-xs text-muted-foreground",children:["iOS: ",e.jsx("code",{children:".app.zip"})," or ",e.jsx("code",{children:".tar.gz"})," / ",e.jsx("code",{children:".tgz"})," (e.g. an EAS simulator build)  · Android: ",e.jsx("code",{children:".apk"})]})]}),e.jsxs("div",{className:"grid gap-2",children:[e.jsxs(b,{children:["Status ",e.jsx("span",{className:"text-muted-foreground",children:"(optional)"})]}),e.jsxs(A,{value:o,onValueChange:x,children:[e.jsx(_,{children:e.jsx(z,{})}),e.jsxs(B,{children:[e.jsx(c,{value:"none",children:"None"}),e.jsx(c,{value:"Backlog",children:"Backlog"}),e.jsx(c,{value:"In Progress",children:"In Progress"}),e.jsx(c,{value:"Done",children:"Done"}),e.jsx(c,{value:"Rejected",children:"Rejected"})]})]})]}),e.jsxs("div",{className:"flex justify-end gap-2",children:[e.jsx(y,{type:"button",variant:"outline",className:"w-24",onClick:()=>i(!1),children:"Cancel"}),e.jsx(y,{type:"submit",className:"w-24",disabled:!t,children:"Upload"})]})]})]})]})}function k({className:s,...l}){return e.jsx("span",{className:K("font-mono text-xs tabular-nums tracking-tight",s),...l})}function _e({onSuccess:s}){const[l,h]=r.useState(!1),[i,t]=r.useState(""),[g,o]=r.useState(""),[x,j]=r.useState("ios"),[p,N]=r.useState(""),[n,d]=r.useState(!1);async function w(u){u.preventDefault(),N(""),d(!0);try{const m=await se({name:i.trim(),bundle_id_key:g.trim(),platform:x});if(!m){N("Failed to create app");return}if("error"in m){N(m.error);return}h(!1),t(""),o(""),j("ios"),D.success("App created"),s()}catch{D.error("Failed to create app — check your network")}finally{d(!1)}}return e.jsxs(O,{open:l,onOpenChange:h,children:[e.jsx(L,{asChild:!0,children:e.jsxs("button",{className:"flex w-full items-center gap-1.5 rounded-md px-2 py-1.5 text-left text-sm text-muted-foreground hover:bg-accent hover:text-foreground transition-colors",children:[e.jsx(he,{className:"size-3.5 shrink-0"}),e.jsx("span",{children:"Add App"})]})}),e.jsxs(U,{className:"sm:max-w-sm",children:[e.jsx(E,{children:e.jsx(V,{children:"Add App"})}),e.jsxs("form",{onSubmit:w,className:"flex flex-col gap-4 pt-2",children:[e.jsxs("div",{className:"flex flex-col gap-1.5",children:[e.jsx(b,{htmlFor:"app-name",children:"Name"}),e.jsx(M,{id:"app-name",value:i,onChange:u=>t(u.target.value),placeholder:"My App",required:!0})]}),e.jsxs("div",{className:"flex flex-col gap-1.5",children:[e.jsx(b,{htmlFor:"bundle-id",children:"Bundle ID"}),e.jsx(M,{id:"bundle-id",value:g,onChange:u=>o(u.target.value),placeholder:"com.example.myapp",required:!0})]}),e.jsxs("div",{className:"flex flex-col gap-1.5",children:[e.jsx(b,{htmlFor:"platform",children:"Platform"}),e.jsxs(A,{value:x,onValueChange:u=>j(u),children:[e.jsx(_,{id:"platform",children:e.jsx(z,{})}),e.jsxs(B,{children:[e.jsx(c,{value:"ios",children:"iOS"}),e.jsx(c,{value:"android",children:"Android"}),e.jsx(c,{value:"both",children:"Both"})]})]})]}),p&&e.jsx("p",{className:"text-sm text-destructive",children:p}),e.jsx(y,{type:"submit",disabled:n,className:"w-full",children:n?"Creating…":"Create App"})]})]})]})}function ze({apps:s,selectedAppId:l,onSelect:h,onAdd:i}){return e.jsxs("aside",{className:"w-64 shrink-0 border-r flex flex-col gap-1 p-3 overflow-y-auto",children:[e.jsx("span",{className:"px-2 pb-1 font-mono text-xs font-medium text-muted-foreground uppercase tracking-wider",children:"Apps"}),s.length===0&&e.jsx("span",{className:"px-2 text-sm text-muted-foreground",children:"No apps yet"}),s.map(t=>e.jsxs("button",{onClick:()=>h(t.id),className:["flex flex-col gap-0.5 rounded-md px-2 py-2 text-left text-sm hover:bg-accent overflow-hidden",t.id===l?"bg-accent font-medium":""].join(" "),children:[e.jsx("span",{className:"truncate",children:t.name}),e.jsx(k,{className:"text-muted-foreground truncate",children:t.bundle_id_key})]},t.id)),e.jsx("div",{className:"mt-1 border-t pt-1",children:e.jsx(_e,{onSuccess:i})})]})}function Be(s){const l=/[zZ]|[+-]\d{2}:?\d{2}$/.test(s),i=new Date(l?s:s.replace(" ","T")+"Z").getTime()-Date.now();if(i<=0)return{label:"Deleting…",urgent:!0};const t=Math.floor(i/36e5);return t<1?{label:"Deletes in < 1h",urgent:!0}:t<24?{label:`Deletes in ${t}h`,urgent:t<6}:{label:`Deletes in ${Math.floor(t/24)}d`,urgent:!1}}function Ie({build:s,isLast:l,onNavigate:h,onStatusChange:i,onScheduleDeletion:t,onCancelDeletion:g}){const[o,x]=r.useState(!1),j=s.status_label==="Done",p=s.delete_after?Be(s.delete_after):null;function N(n){i(s.id,n==="none"?null:n)}return e.jsxs(e.Fragment,{children:[e.jsx(pe,{open:o,onOpenChange:x,children:e.jsxs(me,{children:[e.jsxs(fe,{children:[e.jsx(ge,{children:"Schedule deletion?"}),e.jsx(je,{children:"Build files will be deleted after 7 days. You can cancel the scheduled deletion anytime before then."})]}),e.jsxs(ve,{children:[e.jsx(Ne,{children:"Cancel"}),e.jsx(Se,{onClick:()=>{t(s.id),x(!1)},children:"Schedule deletion"})]})]})}),e.jsxs("div",{className:["flex items-center gap-3 px-4 py-3",l?"":"border-b"].join(" "),children:[e.jsxs("div",{className:"flex-1 min-w-0",children:[e.jsxs("div",{className:"flex items-center gap-2 flex-wrap",children:[e.jsxs("span",{className:"text-sm font-medium",children:["build ",e.jsx(k,{children:s.build_number??"—"})]}),e.jsx(T,{tone:s.platform==="ios"?"ios":"android",className:"text-xs capitalize",children:s.platform}),s.status_label&&e.jsx(T,{tone:ae[s.status_label],className:"text-xs",children:s.status_label}),p&&e.jsx(T,{tone:p.urgent?"rejected":"backlog",className:"text-xs",children:p.label})]}),e.jsxs("div",{style:{display:"flex",flexDirection:"row",gap:"14px",marginTop:"0.7rem",alignItems:"center"},className:"text-xs text-muted-foreground",children:[s.uploader&&e.jsxs(e.Fragment,{children:[e.jsx("span",{children:s.uploader}),e.jsx(X,{orientation:"vertical",className:"h-3"})]}),e.jsx(k,{children:new Date(s.uploaded_at).toLocaleDateString()})]})]}),e.jsxs(A,{value:s.status_label??"none",onValueChange:N,children:[e.jsx(_,{className:"h-9 w-32 text-xs",children:e.jsx(z,{})}),e.jsxs(B,{children:[e.jsx(c,{value:"none",children:e.jsx("span",{className:"text-muted-foreground",children:"—"})}),e.jsx(c,{value:"Backlog",children:"Backlog"}),e.jsx(c,{value:"In Progress",children:"In Progress"}),e.jsx(c,{value:"Done",children:"Done"}),e.jsx(c,{value:"Rejected",children:"Rejected"})]})]}),p?e.jsx(y,{size:"icon-sm",variant:"outline",onClick:()=>g(s.id),title:"Cancel scheduled deletion",children:e.jsx(ke,{className:"h-3.5 w-3.5"})}):e.jsx(y,{size:"icon-sm",variant:"destructive",onClick:()=>x(!0),title:"Schedule deletion",children:e.jsx(xe,{className:"h-3.5 w-3.5"})}),e.jsxs(y,{size:"sm",onClick:()=>h(s.id),disabled:j,children:[e.jsx(te,{className:"mr-1.5 h-3.5 w-3.5"}),"Start QA"]})]})]})}function Pe({versionName:s,builds:l,isOpen:h,onToggle:i,onNavigate:t,onStatusChange:g,onScheduleDeletion:o,onCancelDeletion:x}){return e.jsxs("div",{className:"rounded-lg shadow-card-2 overflow-hidden",children:[e.jsxs("button",{className:"flex w-full items-center gap-2 px-4 py-3 text-left hover:bg-accent/50 bg-card",onClick:i,children:[h?e.jsx(ue,{className:"h-4 w-4 shrink-0 text-muted-foreground"}):e.jsx(ee,{className:"h-4 w-4 shrink-0 text-muted-foreground"}),e.jsx(k,{className:"font-medium text-sm text-foreground",children:s}),e.jsxs("span",{className:"text-xs text-muted-foreground",children:["(",l.length," build",l.length>1?"s":"",")"]})]}),h&&e.jsx("div",{className:"border-t bg-card",children:l.map((j,p)=>e.jsx(Ie,{build:j,isLast:p===l.length-1,onNavigate:t,onStatusChange:g,onScheduleDeletion:o,onCancelDeletion:x},j.id))})]})}function Ve(){const s=ye(),[l,h]=we(),[i,t]=r.useState([]),[g,o]=r.useState([]),[x,j]=r.useState(""),[p,N]=r.useState("all"),[n,d]=r.useState(!0),[w,u]=r.useState(new Set),m=l.get("appId")?Number(l.get("appId")):null,F=i.find(a=>a.id===m)??null,I=r.useCallback(async()=>{const a=await le();t(a),!l.get("appId")&&a.length>0&&h({appId:String(a[0].id)},{replace:!0})},[l,h]),P=r.useCallback(async()=>{if(!m){o([]),d(!1);return}d(!0);try{const a=await ne({appId:m,search:x,statusFilter:p});o(a),a.length>0&&w.size===0&&u(new Set([a[0].version_name??"Unversioned"]))}finally{d(!1)}},[m,x,p]);r.useEffect(()=>{I()},[]),r.useEffect(()=>{P()},[P]);function G(a){a!==m&&(o([]),u(new Set)),h({appId:String(a)})}function Q(a){u(v=>{const f=new Set(v);return f.has(a)?f.delete(a):f.add(a),f})}async function W(a,v){await de(a,v),o(f=>f.map(S=>S.id===a?{...S,status_label:v}:S))}async function Y(a){try{const v=await oe(a);o(f=>f.map(S=>S.id===a?{...S,delete_after:v}:S))}catch{D.error("Failed to schedule deletion")}}async function J(a){try{await ce(a),o(v=>v.map(f=>f.id===a?{...f,delete_after:null}:f))}catch{D.error("Failed to cancel scheduled deletion")}}const R=re(g);return e.jsxs("div",{className:"flex h-full gap-0",children:[e.jsx(ze,{apps:i,selectedAppId:m,onSelect:G,onAdd:I}),e.jsxs("div",{className:"flex-1 flex flex-col gap-4 p-4 overflow-y-auto min-w-0",children:[e.jsxs("div",{className:"flex items-center justify-between flex-wrap gap-2",children:[e.jsx("h1",{className:"text-xl font-semibold tracking-display-sm",children:F?F.name:"App Center"}),e.jsx(Ae,{onSuccess:()=>{I(),P()},appId:m})]}),m?e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"flex flex-wrap gap-2",children:[e.jsx(ie,{placeholder:"Search version…",value:x,onChange:j}),e.jsxs(A,{value:p,onValueChange:N,children:[e.jsx(_,{className:"h-8 w-36",children:e.jsx(z,{placeholder:"Status"})}),e.jsxs(B,{children:[e.jsx(c,{value:"all",children:"All statuses"}),e.jsx(c,{value:"Backlog",children:"Backlog"}),e.jsx(c,{value:"In Progress",children:"In Progress"}),e.jsx(c,{value:"Done",children:"Done"}),e.jsx(c,{value:"Rejected",children:"Rejected"})]})]})]}),n?e.jsx("p",{className:"text-sm text-muted-foreground",children:"Loading…"}):R.length===0?e.jsxs("div",{className:"flex-1 flex flex-col items-center justify-center gap-2 text-center",children:[e.jsx(De,{className:"w-8 h-8 text-muted-foreground/40"}),e.jsx("p",{className:"text-sm font-medium",children:"No builds yet"}),e.jsx("p",{className:"text-sm text-muted-foreground",children:"Upload the first build to get started."})]}):e.jsx("div",{className:"flex flex-col gap-2",children:R.map(({versionName:a,builds:v})=>e.jsx(Pe,{versionName:a,builds:v,isOpen:w.has(a),onToggle:()=>Q(a),onNavigate:f=>s(`/app-center/build?id=${f}`),onStatusChange:W,onScheduleDeletion:Y,onCancelDeletion:J},a))})]}):e.jsxs("div",{className:"flex-1 flex flex-col items-center justify-center gap-2 text-center",children:[e.jsx(be,{className:"w-8 h-8 text-muted-foreground/40"}),e.jsx("p",{className:"text-sm font-medium",children:"No app selected"}),e.jsx("p",{className:"text-sm text-muted-foreground",children:"Choose an app from the sidebar to view its builds."})]})]})]})}export{Ve as AppCenter};
@@ -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-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};
1
+ import{i,j as e}from"./vendor-react-C5H_1fOn.js";import{an as X,ab as Z,ai as S,aa as y,am as I,C as g,c as j,d as v,a as w,L as m,I as u,S as H,h as M,a9 as n,B as h,N as ee,W as se,Q as ae,a7 as C,_ as $,a8 as p}from"./index-COIlCLCW.js";import{B as re}from"./badge-B4gbViMs.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 ue}from"./alert-dialog-DhOTocMh.js";import{P as O}from"./pencil-CPaoEKFm.js";import"./vendor-router-CnmrRwJF.js";const he=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(),V=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",[J,P]=i.useState(null),A=i.useRef(null),d=S({resolver:y(he),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(u,{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(H,{}),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(M,{name:"logo",control:d.control,render:({field:s})=>e.jsxs("div",{className:"relative w-16 h-16",children:[e.jsx("img",{src:J??V,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(O,{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(h,{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(u,{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(H,{}),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(M,{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(O,{className:"w-3 h-3","aria-hidden":"true"})}),e.jsx("input",{ref:D,type:"file",accept:".png,.jpg,.jpeg",className:"hidden",onChange:r=>{var W;const l=(W=r.target.files)==null?void 0:W[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(h,{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(u,{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(u,{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(u,{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(h,{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(u,{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(h,{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(h,{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(ue,{className:se(ae({variant:"destructive"}),"w-24"),onClick:()=>G(s.id),children:"Delete"})]})]})]})]})]})]},s.id))})})]})]})}export{Ne as DefaultSettings};
@@ -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-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};
1
+ import{i as n,j as e}from"./vendor-react-C5H_1fOn.js";import{ai as A,aa as E,am 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,N as U,S as _,B as D,a7 as O,a8 as x,_ as Y}from"./index-COIlCLCW.js";import{b as z,c as H}from"./vendor-router-CnmrRwJF.js";import{P as V}from"./pencil-CPaoEKFm.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}}=A({resolver:E(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(s=>s.ok?s.json():Promise.reject()).then(s=>{F(s.role),m("valid")}).catch(()=>m("invalid"))},[c]);async function I(s){var r;try{const a=new FormData;if(a.append("token",c),a.append("password",s.password),(r=s.displayName)!=null&&r.trim()&&a.append("display_name",s.displayName.trim()),s.avatar&&a.append("avatar",s.avatar),!(await fetch("/api/v1/invitations/accept",{method:"POST",body:a})).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(I),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:s})=>{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 a=U(l);return{backgroundColor:a.bg,color:a.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 a;return(a=j.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(V,{className:"w-3 h-3","aria-hidden":"true"})}),e.jsx("input",{ref:j,type:"file",accept:"image/png,image/jpeg",className:"hidden",onChange:a=>{var N;const i=(N=a.target.files)==null?void 0:N[0];if(i){if(i.size>2*1024*1024){u("avatar",{message:"Max 2MB for avatar"});return}s.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};
@@ -0,0 +1,3 @@
1
+ import{i as l,j as e}from"./vendor-react-C5H_1fOn.js";import{R as ge,u as ve,T as je,b as ye,c as we}from"./resource-health-Ca4-5Rde.js";import{ac as be,M as ke}from"./index-COIlCLCW.js";import{t as Me,i as ie,j as Ne,h as Se,g as $e,e as Ae,c as Ee,G as Le,A as Te,m as le,d as Re,a as Ce,b as De,L as Oe,B as Ie,f as fe,l as Pe,P as _e}from"./vendor-charts-YPhPkjLw.js";import"./vendor-router-CnmrRwJF.js";const Ue=n=>(document.addEventListener("visibilitychange",n),()=>document.removeEventListener("visibilitychange",n));function Fe(){return l.useSyncExternalStore(Ue,()=>document.visibilityState!=="hidden",()=>!0)}function Be(n,h){const[i,a]=l.useState(()=>Date.now());return l.useEffect(()=>{if(!h)return;a(Date.now());const o=setInterval(()=>a(Date.now()),n);return()=>clearInterval(o)},[n,h]),i}const Q={"1h":36e5,"6h":216e5,"24h":864e5,"7d":6048e5},He={"1h":Ne.every(10),"6h":ie,"24h":ie.every(3),"7d":Me};function Ge(n,h,i){return He[i].range(new Date(n),new Date(h+1))}function Xe(n,h){const i=new Date(n);return h==="7d"?`${i.getMonth()+1}/${i.getDate()}`:`${String(i.getHours()).padStart(2,"0")}:${String(i.getMinutes()).padStart(2,"0")}`}const Ve=2e3;function We(n){return Math.min(1e4,Math.max(1e3,Q[n]/Ve))}const ze={"1h":6e4,"6h":6e4,"24h":3e5,"7d":9e5},Z=n=>Math.round(n*10)/10;function Ye(n,h,i){let a;for(const c of n){const d=c.agentName===h?c.resources:void 0;d&&(!a||d.reportedAt>a.reportedAt)&&(a=d)}if(!a||i-a.reportedAt>ge)return null;const o=c=>Number.isFinite(c)?Z(c):null;return{cpu:o(a.cpuPercent),mem:o(a.memUsedMB/a.memTotalMB*100)}}const xe={cpu:{label:"CPU",color:"#60a5fa"},mem:{label:"RAM",color:"#a78bfa"}},ce={"1h":"1h","6h":"6h","24h":"24h","7d":"7d"};function at(){const[n,h]=l.useState([]),[i,a]=l.useState([]),[o,c]=l.useState(null),[d,$]=l.useState("24h"),[g,k]=l.useState(null),A=Fe(),I=Be(We(d),A),{setNode:E}=be();l.useEffect(()=>(E(e.jsx("span",{className:"text-sm font-medium",children:"Mac Resources"})),()=>E(null)),[E]);const P=l.useCallback(s=>{s.type==="agents:listed"&&h(s.sessions??[])},[]),{send:_,connected:U}=ve(P);l.useEffect(()=>{if(!U)return;_({type:"agents:list"});const s=setInterval(()=>_({type:"agents:list"}),1e4);return()=>clearInterval(s)},[U,_]),l.useEffect(()=>{fetch("/api/v1/agents",{credentials:"include"}).then(s=>s.ok?s.json():[]).then(a)},[]);const G=n.map(s=>s.agentName).filter(Boolean),M=[...new Set([...G,...i])],R=new Set(G);l.useEffect(()=>{!o&&M.length>0&&c(M[0])},[M.join(",")]);const u=o?`${o}
2
+ ${d}`:null,v=l.useRef(null);l.useEffect(()=>{if(!o||!A)return;const s=`${o}
3
+ ${d}`,T=ze[d],w=new AbortController;let F=0;const B=()=>{const V=++F,H=()=>!w.signal.aborted&&V===F;fetch(`/api/v1/agents/${encodeURIComponent(o)}/resources?range=${d}`,{credentials:"include",signal:w.signal}).then(x=>{if(!x.ok)throw new Error(`HTTP ${x.status}`);return x.json()}).then(x=>{H()&&(v.current={key:s,at:Date.now()},k({key:s,points:x}))}).catch(()=>{H()&&(v.current={key:s,at:Date.now()},k(x=>(x==null?void 0:x.key)===s?x:{key:s,points:[]}))})},b=v.current,N=(b==null?void 0:b.key)===s?Math.max(0,T-(Date.now()-b.at)):0;let f;const r=()=>{B(),f=setInterval(B,T)},C=N>0?setTimeout(r,N):void 0;return N===0&&r(),()=>{w.abort(),clearTimeout(C),clearInterval(f)}},[o,d,A]);const L=g!==null&&g.key===u,X=u!==null&&!L,y=(L?g.points:[]).map(s=>({time:s.recorded_at,cpu:Z(s.cpu_percent),mem:Z(s.mem_percent)})),m=o?Ye(n,o,I):null;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"}),M.length===0?e.jsx("span",{className:"px-2 text-sm text-muted-foreground",children:U?"No agents yet.":"Connecting…"}):M.map(s=>{const T=R.has(s),w=o===s;return e.jsxs("button",{onClick:()=>c(s),"aria-current":w?"true":void 0,className:["flex items-center gap-2 rounded-md px-2 py-2 text-left text-sm hover:bg-accent transition-colors",w?"bg-accent font-medium":""].join(" "),children:[e.jsx("span",{className:`inline-block h-1.5 w-1.5 shrink-0 rounded-full ${T?"bg-emerald-400":"bg-muted-foreground/40"}`,"aria-hidden":"true"}),e.jsx("span",{className:"truncate min-w-0",children:s}),e.jsx("span",{className:"sr-only",children:T?"Online":"Offline"})]},s)})]}),e.jsx("div",{className:"flex-1 min-w-0 overflow-auto",children:o?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:o}),e.jsx(je,{value:d,onValueChange:s=>$(s),children:e.jsx(ye,{children:Object.keys(ce).map(s=>e.jsx(we,{value:s,children:ce[s]},s))})})]}),X?e.jsx("div",{className:"flex h-40 items-center justify-center text-sm text-muted-foreground",children:"Loading…"}):y.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(me,{title:"CPU %",color:"cpu",data:y,dataKey:"cpu",range:d,now:I,live:(m==null?void 0:m.cpu)??null}),e.jsx(me,{title:"RAM %",color:"mem",data:y,dataKey:"mem",range:d,now:I,live:(m==null?void 0:m.mem)??null})]})]}):e.jsxs("div",{className:"flex h-full items-center justify-center gap-2 text-muted-foreground",children:[e.jsx(ke,{className:"h-8 w-8"}),e.jsx("p",{className:"text-sm",children:"Select a Mac to view resource history."})]})})]})}const ee=n=>new Date(n.time).getTime(),de=n=>new Date(n).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",hourCycle:"h23"}),z=n=>`${Math.round(n*10)/10}%`,Ke=fe(ee).left,qe=fe(n=>n.t).left,j={top:8,right:24,bottom:24,left:40},ue=16,he=6,Je=20,Qe=40;function me({title:n,color:h,data:i,dataKey:a,range:o,now:c,live:d}){const $=xe[h].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:$}}),e.jsx("h2",{className:"text-sm font-medium",children:n})]}),e.jsx("div",{className:"relative h-[220px] w-full",children:e.jsx(_e,{children:({width:g,height:k})=>g>0&&k>0?e.jsx(Ze,{width:g,height:k,data:i,dataKey:a,hex:$,range:o,now:c,live:d,label:n}):null})})]})}function Ze({width:n,height:h,data:i,dataKey:a,hex:o,range:c,now:d,live:$=null,label:g}){const k=`chart-hint-${a}`,[A,I]=l.useState(null),[E,P]=l.useState(!1),[_,U]=l.useState(null),[G,M]=l.useState(!1),R=G?_:null,u=n-j.left-j.right,v=h-j.top-j.bottom,L=d,X=L-Q[c],y=Se({domain:[X,L],range:[0,Math.max(0,u)]}),m=$e({domain:[0,100],range:[v,ue]}),s=u>0?Je/u*Q[c]:0,T=Ge(X-s,L+s,c),w=u>0?Math.min(.5,Qe/u):0,F=`fill-${a}`,B=`plot-${a}`,b=`axis-fade-${a}`,N=i[i.length-1],f=$!==null&&N?{t:Math.max(L,ee(N)+1),v:$,live:!0}:null,r=i.map(t=>({t:ee(t),v:t[a]}));f&&r.push(f);const C=t=>Math.min(y(t),u),V=f?C(f.t):0,H=f?m(f.v):0,x=Math.max(0,i.length-1),D=A===null?Math.max(0,r.length-1):A==="live"?f?r.length-1:x:Math.min(Ke(i,A),x),S=E?r[D]:void 0,Y=S?C(S.t):0,te=S?m(S.v):0,se=xe[a].label,ne=t=>`${se}, ${de(t.t)}, ${t.live?"latest, ":""}${z(t.v)}`,K=t=>{const p=r[t];p&&(I(p.live?"live":p.t),P(!0),U({now:t,text:ne(p)}))},W=()=>P(!1);l.useEffect(()=>{if(!E)return;const t=p=>{p.key==="Escape"&&P(!1)};return document.addEventListener("keydown",t),()=>document.removeEventListener("keydown",t)},[E]);const pe=t=>{if(r.length===0)return;const p=D,O=t.key==="ArrowLeft"||t.key==="ArrowDown"?Math.max(0,p-1):t.key==="ArrowRight"||t.key==="ArrowUp"?Math.min(r.length-1,p+1):t.key==="Home"?0:t.key==="End"?r.length-1:null;O!==null&&(t.preventDefault(),K(O))},oe=t=>{const p=Pe(t);if(!p)return;const O=p.x-j.left,ae=qe(r,y.invert(O).getTime()),re=[r[ae-1],r[ae],f].reduce((q,J)=>J&&(!q||Math.abs(C(J.t)-O)<=Math.abs(C(q.t)-O))?J:q,void 0);re&&K(r.indexOf(re))};return e.jsxs(e.Fragment,{children:[e.jsxs("svg",{width:n,height:h,role:"group","aria-label":f?`${g}, last ${c}. Now ${z(f.v)}.`:N?`${g}, last ${c}. Latest ${z(N[a])}.`:`${g}, last ${c}. No samples.`,children:[e.jsx(Ae,{id:F,from:o,to:o,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,u),height:Math.max(0,v)})}),e.jsxs("linearGradient",{id:`${b}-grad`,gradientUnits:"userSpaceOnUse",x1:0,y1:0,x2:Math.max(0,u),y2:0,children:[e.jsx("stop",{offset:0,stopColor:"white",stopOpacity:0}),e.jsx("stop",{offset:w,stopColor:"white",stopOpacity:1}),e.jsx("stop",{offset:1-w,stopColor:"white",stopOpacity:1}),e.jsx("stop",{offset:1,stopColor:"white",stopOpacity:0})]}),e.jsx("mask",{id:b,maskUnits:"userSpaceOnUse",maskContentUnits:"userSpaceOnUse",x:0,y:v,width:Math.max(0,u),height:j.bottom*2,children:e.jsx("rect",{x:0,y:v,width:Math.max(0,u),height:j.bottom*2,fill:`url(#${b}-grad)`})}),e.jsxs(Ee,{left:j.left,top:j.top,children:[e.jsx(Le,{scale:m,width:u,tickValues:[0,25,50,75,100],strokeDasharray:"3 3",stroke:"hsl(var(--border))"}),e.jsxs("g",{clipPath:`url(#${B})`,children:[e.jsx(Te,{data:r,x:t=>y(t.t),y:t=>m(t.v),yScale:m,curve:le,fill:`url(#${F})`}),e.jsx(Re,{data:r,x:t=>y(t.t),y:t=>m(t.v),curve:le,stroke:o,strokeWidth:1.5})]}),e.jsx("g",{mask:`url(#${b})`,children:e.jsx(Ce,{top:v,scale:y,tickValues:T,tickFormat:t=>Xe(+t,c),hideAxisLine:!0,hideTicks:!0,tickLength:0,tickLabelProps:()=>({fontSize:11,fill:"currentColor",textAnchor:"middle",dy:6,className:"fill-muted-foreground"})})}),e.jsx(De,{scale:m,tickValues:[0,25,50,75,100],tickFormat:t=>`${t}%`,hideAxisLine:!0,hideTicks:!0,tickLabelProps:()=>({fontSize:11,fill:"currentColor",textAnchor:"end",dx:-4,dy:3,className:"fill-muted-foreground"})}),f&&e.jsxs("g",{className:"live-head","aria-hidden":"true",pointerEvents:"none",children:[e.jsx("circle",{cx:V,cy:H,r:he,fill:o,fillOpacity:.2}),e.jsx("circle",{cx:V,cy:H,r:3,fill:o,stroke:"hsl(var(--background))",strokeWidth:1.5})]}),S&&e.jsxs("g",{style:{transition:"transform 0.25s ease-out",transform:`translateX(${Y}px)`},pointerEvents:"none",children:[e.jsx(Oe,{from:{x:0,y:ue},to:{x:0,y:v},stroke:"hsl(var(--border))",strokeWidth:1}),e.jsx("circle",{cx:0,cy:0,r:3,fill:o,stroke:"hsl(var(--background))",strokeWidth:1.5,style:{transition:"transform 0.25s ease-out",transform:`translateY(${te}px)`}})]}),e.jsx(Ie,{x:0,y:0,width:Math.max(0,u)+he,height:Math.max(0,v),fill:"transparent",tabIndex:0,role:"slider","aria-label":`${g} samples`,"aria-valuemin":0,"aria-valuemax":Math.max(0,r.length-1),"aria-valuenow":R?Math.min(R.now,Math.max(0,r.length-1)):D,"aria-valuetext":R?R.text:r[D]?ne(r[D]):void 0,"aria-describedby":k,className:"outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring",onFocus:()=>{M(!0),K(D)},onBlur:()=>{W(),M(!1)},onKeyDown:pe,onMouseMove:oe,onMouseLeave:W,onTouchMove:oe,onTouchEnd:W,onTouchCancel:W})]})]}),e.jsx("p",{id:k,className:"sr-only",children:"Use the arrow keys to read individual samples, and End for the latest value. Escape hides the reading."}),S&&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(${Y+j.left}px, ${te+j.top}px) translate(${Y>u*.6?"calc(-100% - 12px)":"12px"}, -50%)`,transition:"transform 0.25s ease-out"},children:[e.jsxs("p",{className:"mb-1",children:["Date: ",de(S.t)]}),e.jsxs("p",{children:[se,": ",z(S.v)]})]})]})}export{Ze as AreaChartInner,at as MacResources};
@@ -1 +1 @@
1
- import{j as e}from"./vendor-react-AJpf4YZ0.js";function r({code:s,message:t}){return e.jsx("div",{className:"flex min-h-svh items-center justify-center bg-background p-4",children:e.jsxs("div",{className:"flex items-center gap-4",children:[e.jsx("span",{className:"text-lg font-semibold tracking-tight text-foreground",children:s}),e.jsx("span",{className:"h-6 w-px bg-border","aria-hidden":"true"}),e.jsx("span",{className:"text-sm text-muted-foreground",children:t})]})})}function a(){return e.jsx(r,{code:404,message:"page not found"})}export{a as NotFound};
1
+ import{j as e}from"./vendor-react-C5H_1fOn.js";function r({code:s,message:t}){return e.jsx("div",{className:"flex min-h-svh items-center justify-center bg-background p-4",children:e.jsxs("div",{className:"flex items-center gap-4",children:[e.jsx("span",{className:"text-lg font-semibold tracking-tight text-foreground",children:s}),e.jsx("span",{className:"h-6 w-px bg-border","aria-hidden":"true"}),e.jsx("span",{className:"text-sm text-muted-foreground",children:t})]})})}function a(){return e.jsx(r,{code:404,message:"page not found"})}export{a as NotFound};