@hazem3500/palette 0.9.110 → 0.9.112
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/NODE.md +90 -0
- package/README.md +25 -0
- package/dist/{account-tokens-LBRVZAEJ.js → account-tokens-YO6MIZFF.js} +1 -1
- package/dist/chunk-CF4GJN3B.js +1 -0
- package/dist/index.js +1 -1
- package/dist/node.mjs +382 -298
- package/dist/postgres/008_webhook_outbox.sql +14 -0
- package/dist/public-env.d.ts +4 -0
- package/dist/react.js +1 -1
- package/dist/server.d.ts +1 -0
- package/dist/server.js +366 -289
- package/dist/setup.js +1 -1
- package/dist/{snapshot-3PSQKXUV.js → snapshot-YYM5GW6I.js} +1 -1
- package/dist/snapshot.d.ts +3 -2
- package/package.json +1 -1
- package/server/index.ts +75 -79
- package/server/migrations/0022_webhook_outbox.sql +14 -0
- package/server/node-webhooks.ts +5 -0
- package/server/proxy-auth.ts +2 -0
- package/server/public-env.ts +4 -0
- package/server/setup-client.txt +55 -55
- package/server/tokens.ts +14 -0
- package/server/webhooks.ts +98 -13
- package/dist/chunk-U55ASP2Y.js +0 -1
package/NODE.md
CHANGED
|
@@ -166,3 +166,93 @@ To roll back, restore the backup. Redeploying the old image is not enough: the u
|
|
|
166
166
|
The API's storage contract is `prepare().bind().first()/all()/run()` plus atomic `batch()`. `server/postgres.ts` implements it with a PostgreSQL pool, parameterized statements, and one transaction per batch. `server/postgres/001_initial.sql` mirrors the SQLite schema and its quota, revision, retention, and deletion triggers; later numbered files, such as `005_snapshots.sql` for element snapshots (`bytea`), mirror later D1 migrations. Add a numbered PostgreSQL migration whenever the D1 schema changes. Existing D1 migrations and the Worker entry point stay separate. The Node server runs the same request handler; transport and storage are the only new adapters.
|
|
167
167
|
|
|
168
168
|
The Node process runs retention cleanup after requests, as the self-hosted Worker does. It handles `SIGTERM` by closing its HTTP listener and database pool. Database connection failures prevent startup; request errors return the existing API error format. Rate limits use the direct socket IP by default, and never trust a client's `X-Forwarded-For`. Behind trusted reverse proxies, set `PALETTE_TRUSTED_PROXY_HOPS` (older name `PALETTE_PROXY_HOPS`, 0 to 8) to the number of `X-Forwarded-For` entries those proxies append before the app. Most proxies append one; Google Cloud's external Application Load Balancer appends two (see [Deploy behind Google Cloud IAP](#deploy-behind-google-cloud-iap)). The client address is then that many entries from the right; entries further left are ignored. If the header has fewer entries, or that entry isn't an IP address, the socket IP is used. Check your load balancer’s forwarding behavior for the correct value, and only set it when requests can't bypass those proxies. Client-supplied `CF-Connecting-IP` is ignored. The Cloudflare Worker uses Cloudflare's `CF-Connecting-IP`.
|
|
169
|
+
|
|
170
|
+
### Proxy access tokens (operator opt-in)
|
|
171
|
+
|
|
172
|
+
Proxy access tokens are off by default. To enable management and use, keep
|
|
173
|
+
`PALETTE_PROXY_AUTH=gcp-iap`, `PALETTE_PROXY_AUDIENCE` set to your IAP audience,
|
|
174
|
+
and your existing `PALETTE_AUTH_PATH_PREFIX` (for example `/backoffice`); additionally set
|
|
175
|
+
`PALETTE_PROXY_ACCESS_TOKENS=true` (Worker vars or Node environment). The legacy
|
|
176
|
+
`KOMO_PROXY_ACCESS_TOKENS` alias works when the Palette setting is unset.
|
|
177
|
+
Preserve your existing public path and proxy routing configuration.
|
|
178
|
+
|
|
179
|
+
Mint, list and revoke require a verified Google account's valid project session
|
|
180
|
+
issued through the proxy. A PAT cannot manage tokens. Mint requests must supply
|
|
181
|
+
`projects` as a nonempty explicit grant, `expiresInDays` from 1 to 365, and at
|
|
182
|
+
least one of `comments:read` / `comments:write`. The account UI offers these
|
|
183
|
+
constraints; the list response adds `policy: { explicitProjects: true,
|
|
184
|
+
maxLifetimeDays: 365 }`. `config.tokens` advertises the opt-in.
|
|
185
|
+
|
|
186
|
+
The operator opts into credentials that remain usable independently of IAP access
|
|
187
|
+
until they expire or are revoked; removing IAP access alone does not revoke a PAT.
|
|
188
|
+
|
|
189
|
+
Existing tokens with no expiry, a lifetime over 365 days, or no explicit project
|
|
190
|
+
grant are refused behind the proxy even after opt-in. Bounded, explicitly scoped
|
|
191
|
+
existing tokens remain usable. Nonproxy token policy is unchanged. Snapshot GET
|
|
192
|
+
requires `comments:read`, the granted project and an allowed Origin. No deployment
|
|
193
|
+
is enabled by this configuration example.
|
|
194
|
+
|
|
195
|
+
### Webhook delivery and snapshot readiness
|
|
196
|
+
|
|
197
|
+
Apply PostgreSQL migration 008 (automatic at Node startup) or D1 migration
|
|
198
|
+
0022 before running this version. Comment creation, replies and resolution
|
|
199
|
+
commit their selected webhook into `webhook_outbox` in the same transaction as
|
|
200
|
+
the mutation. No destination is configured by default. The outbox pins the URL,
|
|
201
|
+
encrypted signing secret, event ID and exact JSON bytes at creation. Changing or
|
|
202
|
+
disabling project webhook settings affects future events; queued events retain
|
|
203
|
+
their destination. Keep old encryption keys available until those entries drain.
|
|
204
|
+
|
|
205
|
+
Node drains every 10 seconds. Workers must configure a recurring Cron Trigger
|
|
206
|
+
(for example every minute); the exported Worker `scheduled` handler drains the
|
|
207
|
+
outbox. Custom runtimes can import `drainWebhooks` from the server export and
|
|
208
|
+
call `await drainWebhooks(env)` periodically. Request `waitUntil` only accelerates
|
|
209
|
+
these committed deliveries. Worker deployments must supply their existing
|
|
210
|
+
`PALETTE_WEBHOOK_SEND` transport; it must reject non-2xx responses, reject within
|
|
211
|
+
5 seconds, validate DNS against private addresses, pin the validated address,
|
|
212
|
+
and never follow redirects. No unsafe fetch transport is installed implicitly.
|
|
213
|
+
The built-in Node transport enforces public DNS/IP pinning and bounded DNS/send
|
|
214
|
+
timeouts, and now rejects all non-2xx responses.
|
|
215
|
+
|
|
216
|
+
Each drain claims at most 20 entries, one at a time, with an atomic conditional
|
|
217
|
+
UPDATE and a 30-second lease. Six attempts maximum, including interrupted
|
|
218
|
+
attempts; failed attempts wait 10, 20, 40, 80 and 160 seconds (plus scheduling
|
|
219
|
+
latency). Expired claims can be recovered by another process. Completion uses
|
|
220
|
+
the claim token so a stale worker cannot acknowledge a newer claim. The drain
|
|
221
|
+
stops waiting for an attempt after 5 seconds. Custom transports must enforce
|
|
222
|
+
that bound themselves: a Promise timeout cannot cancel their network effects.
|
|
223
|
+
Delivery is at least once: a crash after remote acceptance can cause duplicates.
|
|
224
|
+
Deduplicate by `X-Palette-Delivery` (legacy `X-Komo-Delivery` is identical).
|
|
225
|
+
Each attempt signs the stored body with a fresh current Unix-second timestamp;
|
|
226
|
+
header aliases and the `timestamp + "." + body` HMAC format are unchanged.
|
|
227
|
+
|
|
228
|
+
Operators can inspect `id,project,attempts,state,next_attempt,claim_until` in
|
|
229
|
+
`webhook_outbox`; attempt/lease times are Unix milliseconds.
|
|
230
|
+
`delivered` and `exhausted` are retained; no automatic purge or
|
|
231
|
+
external delivery-management endpoint is provided. After fixing a receiver,
|
|
232
|
+
explicitly reset an exhausted entry's `attempts=0,state='pending',next_attempt=0,
|
|
233
|
+
claim_until=0,claim_token=''` to replay its same ID/body. Do not reset an active
|
|
234
|
+
lease. Purge retained entries using your database retention policy; bodies may
|
|
235
|
+
contain comment content. Outbox rows are independent of thread deletion.
|
|
236
|
+
|
|
237
|
+
`config.snapshots.readiness: true` advertises this protocol; clients omit new
|
|
238
|
+
intent fields and terminal reports on older servers.
|
|
239
|
+
`POST /threads` accepts optional boolean `snapshotIntent`. Its `comment.created`
|
|
240
|
+
event gains `snapshotStatus: 'pending'` for true or `'skipped'` for false.
|
|
241
|
+
Omitting intent leaves status unknown and preserves the existing event fields,
|
|
242
|
+
so older clients can still upload later without claiming an intentional skip.
|
|
243
|
+
Legacy intent is never inferred from an image 404. There is no new snapshot event.
|
|
244
|
+
|
|
245
|
+
Authenticated `GET /threads/:id/snapshot` returns `{status,kinds}`. `status` is
|
|
246
|
+
`pending`, `ready`, `skipped`, `failed`, or `unknown` (legacy/imported threads).
|
|
247
|
+
`kinds` lists stored `image`/`html` captures. Any successfully stored kind means
|
|
248
|
+
ready; it does not promise both kinds. The upload transaction sets ready.
|
|
249
|
+
Pending capture older than ten minutes reads as failed, bounding abandoned
|
|
250
|
+
client captures. A later successful upload can still make it ready.
|
|
251
|
+
`POST /threads/:id/snapshot` with JSON `{status:'skipped'}` or `{status:'failed'}`
|
|
252
|
+
terminates a pending capture; clients report intentional omissions and upload
|
|
253
|
+
failures this way. A failed status report falls back to the ten-minute deadline.
|
|
254
|
+
Deleting the final stored kind makes status skipped. Kind-specific GET routes
|
|
255
|
+
retain their existing 404 semantics. Snapshot status reads use existing
|
|
256
|
+
project/repo/branch authentication and PAT `comments:read`; writes retain guest
|
|
257
|
+
consent and author/reviewer checks and PAT `comments:write`. No token enablement
|
|
258
|
+
policy changes are included.
|
package/README.md
CHANGED
|
@@ -924,3 +924,28 @@ To: #side > nav · index 0 · inside · component SideNav
|
|
|
924
924
|
The hidden original's `display` is not proposed as a style change. The CLI reads these lines into `geometry` DesignEdit changes with `mode` (`size`, `flex-basis` or `span`), the values, `layout` and `effects`, and moves into `move` changes (`target`, `label`, `from` and `to` with `parent`, `index`, `position` and any `component`), telling agents to move the element's markup in source. Earlier proposals with `Move (translate)` or `Move (offset)` lines still parse and render as `translate`/`offset` changes; the current UI no longer produces free-form moves.
|
|
925
925
|
|
|
926
926
|
Supported targets are HTML boxes and SVG roots in normal flow, flex or grid, or relatively, absolutely, fixed or sticky positioned, through positive axis-aligned 2D scale chains and normal, reflow or explicitly scaled Frames. Pointer deltas are converted to local CSS pixels, and scroll during pointer capture is accounted for. Only boxes that can't be previewed are excluded, each with its own reason in the panel and a **Select parent** action that picks the nearest resizable ancestor: inline text boxes, SVG shapes inside an SVG, `display: contents` or unrendered elements, table cells and rows, boxes split across lines or columns, rotated/skewed/3D/mirrored chains, and geometry animations in progress. A running animation only counts when it moves or sizes the box and Palette did not start it; it is named after 400ms, as a dot by the Layout title, so brief host transitions never show and the panel never reflows. This is deliberately not a universal layout editor.
|
|
927
|
+
|
|
928
|
+
### Proxy access tokens (operator opt-in)
|
|
929
|
+
|
|
930
|
+
Proxy access tokens are off by default. To enable management and use, keep
|
|
931
|
+
`PALETTE_PROXY_AUTH=gcp-iap`, `PALETTE_PROXY_AUDIENCE` set to your IAP audience,
|
|
932
|
+
and your existing `PALETTE_AUTH_PATH_PREFIX` (for example `/backoffice`); additionally set
|
|
933
|
+
`PALETTE_PROXY_ACCESS_TOKENS=true` (Worker vars or Node environment). The legacy
|
|
934
|
+
`KOMO_PROXY_ACCESS_TOKENS` alias works when the Palette setting is unset.
|
|
935
|
+
Preserve your existing public path and proxy routing configuration.
|
|
936
|
+
|
|
937
|
+
Mint, list and revoke require a verified Google account's valid project session
|
|
938
|
+
issued through the proxy. A PAT cannot manage tokens. Mint requests must supply
|
|
939
|
+
`projects` as a nonempty explicit grant, `expiresInDays` from 1 to 365, and at
|
|
940
|
+
least one of `comments:read` / `comments:write`. The account UI offers these
|
|
941
|
+
constraints; the list response adds `policy: { explicitProjects: true,
|
|
942
|
+
maxLifetimeDays: 365 }`. `config.tokens` advertises the opt-in.
|
|
943
|
+
|
|
944
|
+
The operator opts into credentials that remain usable independently of IAP access
|
|
945
|
+
until they expire or are revoked; removing IAP access alone does not revoke a PAT.
|
|
946
|
+
|
|
947
|
+
Existing tokens with no expiry, a lifetime over 365 days, or no explicit project
|
|
948
|
+
grant are refused behind the proxy even after opt-in. Bounded, explicitly scoped
|
|
949
|
+
existing tokens remain usable. Nonproxy token policy is unchanged. Snapshot GET
|
|
950
|
+
requires `comments:read`, the granted project and an allowed Origin. No deployment
|
|
951
|
+
is enabled by this configuration example.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as e}from"./chunk-DU7H4TEX.js";import"./chunk-5HZI7HRR.js";import{E as t,F as n,G as o,y as a}from"./chunk-4HBBEVFK.js";var i=864e5,r=[["7 days",7],["30 days",30],["90 days",90],["1 year",365],["Never",null]],s={"comments:read":"Read","comments:write":"Write"},p=[["year",365*i],["month",30*i],["week",7*i],["day",i],["hour",36e5],["minute",6e4]];function d(e,t=Date.now()){let n=e-t,o=new Intl.RelativeTimeFormat(void 0,{numeric:"auto",style:"short"});for(let[e,t]of p)if(Math.abs(n)>=t)return o.format(Math.round(n/t),e);return"just now"}var l=e=>new Intl.DateTimeFormat(void 0,{dateStyle:"medium",timeStyle:"short"}).format(e),c=0;function u(i,p,u){let k=t("div","account account-tokens");k.setAttribute("role","region");let f=t("style");f.textContent=".account-layer .account.account-tokens{padding-top:16px;gap:16px}\n.api-tokens-head{display:flex;align-items:center;gap:6px;margin-right:36px;min-height:30px}\n.api-tokens-head h2{margin:0;font-size:15px;font-weight:500;letter-spacing:-.01em}\n.api-tokens-head h2:focus{outline:none}\n.account-layer .account .api-tokens-back{border-radius:8px;color:#bbb}\n.api-tokens-back svg{rotate:90deg;width:16px;height:16px}\n.api-tokens-back:hover{background:color-mix(in oklab,var(--mm-ink) 7%,transparent);color:var(--mm-ink)}\n.api-tokens-lede,.api-tokens-note{margin:0;font-size:12px;line-height:1.5;color:#999}\n.api-tokens-note code,.api-token-id{font:11px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;color:#aaa;overflow-wrap:anywhere}\n.api-tokens-list{list-style:none;margin:0;padding:0;display:grid;gap:8px}\n.api-token,.api-tokens-sheet,.api-tokens-empty{display:grid;gap:8px;padding:14px;border-radius:14px;background:#ffffff05;box-shadow:inset 0 0 0 1px #ffffff09;font-size:12px;color:#ddd}\n.api-tokens-empty{gap:4px;color:#999;text-align:center;padding:20px 14px}\n.api-tokens-empty strong{color:#ddd;font-weight:500}\n.api-token-row{display:flex;align-items:center;gap:8px;min-width:0}\n.api-token-row strong{font-weight:500;color:#eee;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}\n.api-token-chips{display:flex;flex-wrap:wrap;gap:4px}\n.api-token-chip{padding:0 6px;border-radius:5px;background:color-mix(in oklab,var(--mm-ink) 9%,transparent);color:color-mix(in oklab,var(--mm-ink) 78%,transparent);font-size:10px;font-weight:500;line-height:18px;white-space:nowrap}\n.api-token-chip[data-tone=warn]{background:#f1b77d1f;color:#f1b77d}\n.api-token-meta{display:flex;flex-wrap:wrap;gap:2px 10px;margin:0;color:#909090;font-size:11px}\n.api-token-meta div{display:flex;gap:4px}\n.api-token-meta dd{margin:0;color:#bbb}\n.api-token-meta time{cursor:default}\n.account-layer .account .api-token-revoke{margin-left:auto;padding:4px 8px;min-height:26px;border-radius:7px;font-size:11px;background:transparent;color:#fda29b}\n.api-token-revoke:hover,.api-tokens-actions .api-tokens-danger:hover{background:#fda29b14}\n.api-token-confirm{display:grid;gap:8px;padding-top:8px;box-shadow:0 -1px #ffffff0b}\n.api-token-confirm p,.api-tokens-sheet p{margin:0;line-height:1.5;color:#bbb}\n.api-tokens-actions{display:flex;justify-content:flex-end;gap:8px}\n.account-layer .account .api-tokens-actions button{width:auto;min-height:32px;padding:6px 12px;border-radius:9px;font-size:12px}\n.account-layer .account .api-tokens-actions .api-tokens-danger{background:#fda29b1a;color:#fda29b}\n.api-tokens-sheet{gap:14px;padding:16px}\n.api-tokens-sheet h3{margin:0;font-size:13px;font-weight:500;color:#eee}\n.api-tokens-sheet fieldset{border:0;margin:0;padding:0;display:grid;gap:8px;min-width:0}\n.api-tokens-sheet legend{padding:0;margin-bottom:8px;font-size:12px;color:#999}\n.account-layer .account .api-tokens-sheet input[type=text]{min-height:38px;padding:8px 10px;font-size:13px;border-radius:10px}\n.api-tokens-choices{display:flex;flex-wrap:wrap;gap:6px}\n.api-tokens-choice{position:relative;display:inline-flex;align-items:center;gap:6px;padding:6px 10px;border-radius:9px;background:#ffffff06;box-shadow:inset 0 0 0 1px #ffffff0b;color:#bbb;cursor:pointer;font-size:12px;line-height:16px}\n.api-tokens-choice:hover{color:#eee}\n.account-layer .account .api-tokens-choice input{position:absolute;inset:0;width:100%;height:100%;min-height:0;margin:0;opacity:0;cursor:pointer}\n.api-tokens-choice:has(input:checked){background:#bda6ef24;box-shadow:inset 0 0 0 1px #bda6ef80;color:#eee}\n.api-tokens-choice:has(input:focus-visible){outline:2px solid #bda6ef;outline-offset:2px}\n.api-tokens-choice small{color:#888;font-size:10px}\n.api-tokens-sheet p.api-tokens-error,.api-tokens-error{margin:0;color:#fda29b;font-size:12px;line-height:1.5}\n.api-tokens-error:empty{display:none}\n.api-tokens-secret{display:flex;gap:8px;align-items:center}\n.api-tokens-secret code{flex:1;min-width:0;padding:8px 10px;border-radius:10px;background:#00000040;box-shadow:inset 0 0 0 1px #ffffff0b;font:11px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;color:#eee;overflow-wrap:anywhere;user-select:all}\n.account-layer .account .api-tokens-secret button{width:auto;min-height:34px;padding:0 12px;font-size:12px}\n.api-tokens-secret svg{width:14px;height:14px}\n.api-tokens-warning{display:flex;gap:7px;align-items:flex-start}\n.api-tokens-warning svg{flex:none;width:13px;height:13px;margin-top:2px;color:#f1b77d}\n.api-tokens-skeleton{height:84px;border-radius:14px;background:linear-gradient(90deg,#ffffff05,#ffffff0d,#ffffff05) 0 0/200% 100%;animation:api-tokens-shimmer 1.4s linear infinite}\n@keyframes api-tokens-shimmer{to{background-position:-200% 0}}\n@media (prefers-reduced-motion:reduce){.api-tokens-skeleton{animation:none}}";let h=t("div","api-tokens-head"),m=o("Back to account",()=>z(),"icon api-tokens-back","chevron"),g=t("h2","","API tokens");g.id="api-tokens-"+ ++c,g.tabIndex=-1,g.dataset.focusKey="api-tokens-title",k.setAttribute("aria-labelledby",g.id),h.append(m,g);let x=t("p","api-tokens-lede","Let bots and CI read or write comments as you. Treat tokens like passwords."),y=t("p","sr-only");y.setAttribute("role","status"),y.setAttribute("aria-live","polite");let b=t("div"),v=t("div"),w=t("div","morphing-menu__tooltip sidebar-tooltip");w.setAttribute("aria-hidden","true"),k.append(f,h,x,b,v,y,w),m.dataset.focusKey="api-tokens-back";let A=null,C=null,T=!1,$=!1,E="",j=null,S=new Map,N=e=>{y.textContent="",setTimeout(()=>y.textContent=e,30)},I=e=>{a()||e.animate([{opacity:0,transform:"translateY(4px)"},{opacity:1,transform:"none"}],{duration:250,easing:"cubic-bezier(.22,1,.36,1)"})},K=e=>requestAnimationFrame(()=>k.querySelector(`[data-focus-key="${e}"]`)?.focus()),R=t=>t instanceof e&&404===t.status?"This Palette server doesn’t support API tokens.":t instanceof Error?t.message:"Something went wrong. Try again.";k.focusStart=()=>K("api-tokens-title"),k.dispose=()=>{q(),w.remove()};let z=()=>{k.dispose(),u()};k.addEventListener("keydown",e=>{if("Escape"===e.key)if(e.stopPropagation(),e.preventDefault(),E){let e=E;E="",B(),K(`api-tokens-revoke-${e}`)}else j?K("api-tokens-done"):T?H():z()});let L=0,q=()=>{clearTimeout(L),delete w.dataset.open};k.addEventListener("pointerover",e=>{let t=e.target.closest?.("[data-tip]");if(!t)return q();clearTimeout(L),L=window.setTimeout(()=>{if(!t.isConnected)return;let e=k.getRootNode();w.parentNode!==e&&e instanceof ShadowRoot&&e.append(w);let n=t.getBoundingClientRect();w.textContent=t.dataset.tip??"";let o=w.offsetWidth/2;w.style.left=`${Math.max(8+o,Math.min(document.documentElement.clientWidth-8-o,n.left+n.width/2))}px`,w.style.top=n.top-8-w.offsetHeight+"px",w.dataset.open="true"},w.dataset.open?0:400)}),k.addEventListener("pointerleave",k.dispose),k.addEventListener("pointerdown",k.dispose);let D=(e,n,o)=>{let a=t("div"),i=t("dd");if(null===n)i.textContent=o;else{let e=t("time","",d(n));e.dateTime=new Date(n).toISOString(),e.dataset.tip=l(n),i.append(e,t("span","sr-only",` (${l(n)})`))}return a.append(t("dt","",e),i),a},M=(e,n,o)=>{let a=t("span","api-token-chip",e);return n&&(a.dataset.tone=n),o&&(a.dataset.tip=o),a},P=e=>e?1===e.length&&e[0]===p?"This project":1===e.length?e[0]:`${e.length} projects`:"All projects";function F(n){let a=t("li","api-token");a.dataset.token=n.id;let r=t("div","api-token-row"),p=t("strong","",n.name);p.id=`api-token-${n.id}-${g.id}`,a.setAttribute("aria-labelledby",p.id),r.append(p),n.expired&&r.append(M("Expired","warn"));let d=o(`Revoke ${n.name}`,()=>{E=n.id,S.delete(n.id),B(),K(`api-tokens-keep-${n.id}`)},"api-token-revoke");d.textContent="Revoke",d.removeAttribute("title"),d.dataset.focusKey=`api-tokens-revoke-${n.id}`,d.disabled=E===n.id,r.append(d);let l=t("div","api-token-chips");for(let e of n.scopes)l.append(M(s[e]??e,"",e));l.append(M(P(n.projects),"",n.projects?.join(", ")??"Every project you can access"));let c=t("dl","api-token-meta");c.append(D("Created",n.createdAt,""),D("Last used",n.lastUsedAt,"Never"),D(n.expired?"Expired":"Expires",n.expiresAt,"Never")),a.append(r,t("code","api-token-id",`${n.prefix}…${n.last4}`),l,c);let u=S.get(n.id);if(u){let e=t("p","api-tokens-error",u);e.setAttribute("role","alert"),a.append(e)}if(E===n.id){let r=t("div","api-token-confirm");r.setAttribute("role","group"),r.setAttribute("aria-label",`Revoke ${n.name}`);let s=t("div","api-tokens-actions"),p=o("Keep token",()=>{E="",B(),K(`api-tokens-revoke-${n.id}`)},"secondary");p.dataset.focusKey=`api-tokens-keep-${n.id}`;let d=o("Revoke token",()=>{!async function(t){if(!A)return;let n=A,o=n.tokens.findIndex(e=>e.id===t.id);if(o<0)return;n.tokens.splice(o,1),E="",B();let a=n.tokens[o]??n.tokens[o-1];K(a?`api-tokens-revoke-${a.id}`:"api-tokens-new");try{await i.request(`account/tokens/${encodeURIComponent(t.id)}`,"DELETE"),N(`Revoked ${t.name}.`)}catch(a){if(a instanceof e&&404===a.status)return void N(`Revoked ${t.name}.`);if(A!==n)return;n.tokens.splice(Math.min(o,n.tokens.length),0,t),S.set(t.id,`Couldn’t revoke: ${R(a)}`),B(),N(`Couldn’t revoke ${t.name}.`),K(`api-tokens-revoke-${t.id}`)}}(n)},"secondary api-tokens-danger");d.dataset.focusKey=`api-tokens-confirm-${n.id}`,s.append(p,d),r.append(t("p","","Anything using this token stops working right away. This can’t be undone."),s),a.append(r),I(r)}return a}function H(){T=!1,B(),K("api-tokens-new")}let Y="";function B(){q();let a=A?j?"secret":T?"form":"list":C?"error":"loading",d=[];if(A&&"list"===a){let e=o("New token",()=>{T=!0,B(),K("api-tokens-name")},"secondary");e.replaceChildren(n("plus"),document.createTextNode("New token")),e.removeAttribute("title"),e.dataset.focusKey="api-tokens-new",d.push(e)}else A&&"form"===a?d.push(function(n){let a=t("form","api-tokens-sheet");a.noValidate=!0,a.setAttribute("aria-labelledby",`${g.id}-new`);let d=t("h3","","New token");d.id=`${g.id}-new`;let l=t("label","account-name-label","Name"),c=t("input");c.type="text",c.maxLength=60,c.required=!0,c.autocomplete="off",c.placeholder="e.g. Deploy bot",c.dataset.focusKey="api-tokens-name",l.append(c);let u=(e,n,o,a)=>{let i=t("fieldset"),r=t("div","api-tokens-choices");for(let[e,i,s,p]of a){let a=t("label","api-tokens-choice",i),d=t("input");d.type=n,d.name=o,d.value=e,d.checked=s,d.dataset.focusKey=`api-tokens-${o}-${e}`,p&&a.append(t("small","",p)),a.prepend(d),r.append(a)}return i.append(t("legend","",e),r),i},k=u("Access","checkbox","scope",n.scopes.map(e=>[e,s[e]?`${s[e]} comments`:e,"comments:read"===e])),f=n.projects.includes(p),h=u("Projects","radio","projects",[...f?[["this","This project",!0]]:[],["all","All your projects",!f]]),m=u("Expires","radio","expiry",r.map(([e,t])=>[String(t),e,30===t])),x=t("p","api-tokens-error");x.setAttribute("role","alert");let y=t("div","api-tokens-actions"),b=o("Cancel",H,"secondary");b.dataset.focusKey="api-tokens-cancel";let v=o("Create token",()=>{},"primary");return v.type="submit",v.dataset.focusKey="api-tokens-create",y.append(b,v),a.append(d,l,k,h,m,x,y),a.addEventListener("submit",async t=>{if(t.preventDefault(),$)return;let o=[...a.querySelectorAll("input[name=scope]:checked")].map(e=>e.value),r=a.querySelector("input[name=expiry]:checked")?.value,s=c.value.trim()?o.length?null:k.querySelector("input"):c;if(x.textContent=c.value.trim()?o.length?"":"Choose read, write, or both.":"Name your token.",s)s.focus();else{$=!0,v.disabled=b.disabled=!0,v.setAttribute("aria-busy","true");try{let e=await i.request("account/tokens","POST",{name:c.value.trim(),scopes:o,expiresInDays:r&&"null"!==r?Number(r):null,projects:"this"===a.querySelector("input[name=projects]:checked")?.value?[p]:[]});if(A!==n)return;n.tokens.unshift(e.item),T=!1,j=e,B(),N("Token created. Copy it now. You won’t see it again."),K("api-tokens-copy")}catch(t){x.textContent=t instanceof e&&429===t.status?"You’ve created several tokens recently. Try again later.":R(t),v.focus()}finally{$=!1,v.disabled=b.disabled=!1,v.removeAttribute("aria-busy")}}}),a}(A)):j&&d.push(function(e){let a=t("section","api-tokens-sheet");a.setAttribute("aria-labelledby",`${g.id}-secret`);let i=t("h3","",`Copy “${e.item.name}”`);i.id=`${g.id}-secret`;let r=t("p","api-tokens-warning");r.append(n("info"),t("span","","You won’t see this token again. Store it in a secret manager, never in source control."));let s=t("div","api-tokens-secret"),p=t("code","",e.token);p.setAttribute("aria-label","New API token"),p.tabIndex=0,p.dataset.focusKey="api-tokens-secret";let d=o("Copy token",async()=>{try{await navigator.clipboard.writeText(e.token),d.replaceChildren(n("check"),document.createTextNode("Copied")),N("Token copied to the clipboard.")}catch{getSelection()?.selectAllChildren(p),N("Couldn’t copy. The token is selected; copy it with your keyboard.")}},"primary");d.replaceChildren(n("copy"),document.createTextNode("Copy")),d.removeAttribute("title"),d.dataset.focusKey="api-tokens-copy",s.append(p,d);let l=t("div","api-tokens-actions"),c=o("Done",()=>{j=null,B(),K("api-tokens-new")},"secondary");return c.dataset.focusKey="api-tokens-done",l.append(c),a.append(i,r,s,l),a}(j));if(a!==Y&&d[0]&&I(d[0]),Y=a,b.replaceChildren(...d),v.setAttribute("aria-busy",String(!A&&!C)),!A){if(C){let n=t("p","api-tokens-error",R(C));n.setAttribute("role","alert");let a=C instanceof e&&[403,404].includes(C.status),i=o("Try again",()=>{U()},"secondary");i.dataset.focusKey="api-tokens-retry",v.replaceChildren(n,...a?[]:[i])}else{let e=t("div","api-tokens-list");e.setAttribute("aria-hidden","true"),e.append(t("div","api-tokens-skeleton"),t("div","api-tokens-skeleton"));let n=t("span","sr-only","Loading API tokens");n.setAttribute("role","status"),v.replaceChildren(e,n)}return}if(!A.tokens.length){let e=t("p","api-tokens-empty");return e.append(t("strong","","No tokens yet"),t("span","","Create one for a bot, a script, or a CI job.")),void v.replaceChildren(e)}let l=t("ul","api-tokens-list");l.setAttribute("aria-label","Your API tokens"),l.append(...A.tokens.map(F));let c=t("p","api-tokens-note");c.append(document.createTextNode("Use a token as "),t("code","","PALETTE_TOKEN"),document.createTextNode(" with the palette CLI.")),v.replaceChildren(l,c)}async function U(){C=null,A=null,B();try{let t=await i.request("account/tokens");if(!Array.isArray(t.tokens)||!Array.isArray(t.projects))throw new e(0,"Couldn’t read your tokens. Try again.");A={tokens:t.tokens,projects:t.projects,scopes:Array.isArray(t.scopes)?t.scopes:["comments:read","comments:write"]}}catch(e){C=e instanceof Error?e:new Error(String(e))}B()}return U(),k}export{u as accountTokens,d as relativeTime};
|
|
1
|
+
import{a as e}from"./chunk-DU7H4TEX.js";import"./chunk-5HZI7HRR.js";import{E as t,F as n,G as o,y as a}from"./chunk-4HBBEVFK.js";var i=864e5,r=[["7 days",7],["30 days",30],["90 days",90],["1 year",365],["Never",null]],s={"comments:read":"Read","comments:write":"Write"},p=[["year",365*i],["month",30*i],["week",7*i],["day",i],["hour",36e5],["minute",6e4]];function d(e,t=Date.now()){let n=e-t,o=new Intl.RelativeTimeFormat(void 0,{numeric:"auto",style:"short"});for(let[e,t]of p)if(Math.abs(n)>=t)return o.format(Math.round(n/t),e);return"just now"}var l=e=>new Intl.DateTimeFormat(void 0,{dateStyle:"medium",timeStyle:"short"}).format(e),c=0;function u(i,p,u){let k=t("div","account account-tokens");k.setAttribute("role","region");let f=t("style");f.textContent=".account-layer .account.account-tokens{padding-top:16px;gap:16px}\n.api-tokens-head{display:flex;align-items:center;gap:6px;margin-right:36px;min-height:30px}\n.api-tokens-head h2{margin:0;font-size:15px;font-weight:500;letter-spacing:-.01em}\n.api-tokens-head h2:focus{outline:none}\n.account-layer .account .api-tokens-back{border-radius:8px;color:#bbb}\n.api-tokens-back svg{rotate:90deg;width:16px;height:16px}\n.api-tokens-back:hover{background:color-mix(in oklab,var(--mm-ink) 7%,transparent);color:var(--mm-ink)}\n.api-tokens-lede,.api-tokens-note{margin:0;font-size:12px;line-height:1.5;color:#999}\n.api-tokens-note code,.api-token-id{font:11px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;color:#aaa;overflow-wrap:anywhere}\n.api-tokens-list{list-style:none;margin:0;padding:0;display:grid;gap:8px}\n.api-token,.api-tokens-sheet,.api-tokens-empty{display:grid;gap:8px;padding:14px;border-radius:14px;background:#ffffff05;box-shadow:inset 0 0 0 1px #ffffff09;font-size:12px;color:#ddd}\n.api-tokens-empty{gap:4px;color:#999;text-align:center;padding:20px 14px}\n.api-tokens-empty strong{color:#ddd;font-weight:500}\n.api-token-row{display:flex;align-items:center;gap:8px;min-width:0}\n.api-token-row strong{font-weight:500;color:#eee;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}\n.api-token-chips{display:flex;flex-wrap:wrap;gap:4px}\n.api-token-chip{padding:0 6px;border-radius:5px;background:color-mix(in oklab,var(--mm-ink) 9%,transparent);color:color-mix(in oklab,var(--mm-ink) 78%,transparent);font-size:10px;font-weight:500;line-height:18px;white-space:nowrap}\n.api-token-chip[data-tone=warn]{background:#f1b77d1f;color:#f1b77d}\n.api-token-meta{display:flex;flex-wrap:wrap;gap:2px 10px;margin:0;color:#909090;font-size:11px}\n.api-token-meta div{display:flex;gap:4px}\n.api-token-meta dd{margin:0;color:#bbb}\n.api-token-meta time{cursor:default}\n.account-layer .account .api-token-revoke{margin-left:auto;padding:4px 8px;min-height:26px;border-radius:7px;font-size:11px;background:transparent;color:#fda29b}\n.api-token-revoke:hover,.api-tokens-actions .api-tokens-danger:hover{background:#fda29b14}\n.api-token-confirm{display:grid;gap:8px;padding-top:8px;box-shadow:0 -1px #ffffff0b}\n.api-token-confirm p,.api-tokens-sheet p{margin:0;line-height:1.5;color:#bbb}\n.api-tokens-actions{display:flex;justify-content:flex-end;gap:8px}\n.account-layer .account .api-tokens-actions button{width:auto;min-height:32px;padding:6px 12px;border-radius:9px;font-size:12px}\n.account-layer .account .api-tokens-actions .api-tokens-danger{background:#fda29b1a;color:#fda29b}\n.api-tokens-sheet{gap:14px;padding:16px}\n.api-tokens-sheet h3{margin:0;font-size:13px;font-weight:500;color:#eee}\n.api-tokens-sheet fieldset{border:0;margin:0;padding:0;display:grid;gap:8px;min-width:0}\n.api-tokens-sheet legend{padding:0;margin-bottom:8px;font-size:12px;color:#999}\n.account-layer .account .api-tokens-sheet input[type=text]{min-height:38px;padding:8px 10px;font-size:13px;border-radius:10px}\n.api-tokens-choices{display:flex;flex-wrap:wrap;gap:6px}\n.api-tokens-choice{position:relative;display:inline-flex;align-items:center;gap:6px;padding:6px 10px;border-radius:9px;background:#ffffff06;box-shadow:inset 0 0 0 1px #ffffff0b;color:#bbb;cursor:pointer;font-size:12px;line-height:16px}\n.api-tokens-choice:hover{color:#eee}\n.account-layer .account .api-tokens-choice input{position:absolute;inset:0;width:100%;height:100%;min-height:0;margin:0;opacity:0;cursor:pointer}\n.api-tokens-choice:has(input:checked){background:#bda6ef24;box-shadow:inset 0 0 0 1px #bda6ef80;color:#eee}\n.api-tokens-choice:has(input:focus-visible){outline:2px solid #bda6ef;outline-offset:2px}\n.api-tokens-choice small{color:#888;font-size:10px}\n.api-tokens-sheet p.api-tokens-error,.api-tokens-error{margin:0;color:#fda29b;font-size:12px;line-height:1.5}\n.api-tokens-error:empty{display:none}\n.api-tokens-secret{display:flex;gap:8px;align-items:center}\n.api-tokens-secret code{flex:1;min-width:0;padding:8px 10px;border-radius:10px;background:#00000040;box-shadow:inset 0 0 0 1px #ffffff0b;font:11px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;color:#eee;overflow-wrap:anywhere;user-select:all}\n.account-layer .account .api-tokens-secret button{width:auto;min-height:34px;padding:0 12px;font-size:12px}\n.api-tokens-secret svg{width:14px;height:14px}\n.api-tokens-warning{display:flex;gap:7px;align-items:flex-start}\n.api-tokens-warning svg{flex:none;width:13px;height:13px;margin-top:2px;color:#f1b77d}\n.api-tokens-skeleton{height:84px;border-radius:14px;background:linear-gradient(90deg,#ffffff05,#ffffff0d,#ffffff05) 0 0/200% 100%;animation:api-tokens-shimmer 1.4s linear infinite}\n@keyframes api-tokens-shimmer{to{background-position:-200% 0}}\n@media (prefers-reduced-motion:reduce){.api-tokens-skeleton{animation:none}}";let m=t("div","api-tokens-head"),h=o("Back to account",()=>z(),"icon api-tokens-back","chevron"),g=t("h2","","API tokens");g.id="api-tokens-"+ ++c,g.tabIndex=-1,g.dataset.focusKey="api-tokens-title",k.setAttribute("aria-labelledby",g.id),m.append(h,g);let x=t("p","api-tokens-lede","Let bots and CI read or write comments as you. Treat tokens like passwords."),y=t("p","sr-only");y.setAttribute("role","status"),y.setAttribute("aria-live","polite");let b=t("div"),v=t("div"),w=t("div","morphing-menu__tooltip sidebar-tooltip");w.setAttribute("aria-hidden","true"),k.append(f,m,x,b,v,y,w),h.dataset.focusKey="api-tokens-back";let A=null,C=null,T=!1,$=!1,j="",E=null,S=new Map,N=e=>{y.textContent="",setTimeout(()=>y.textContent=e,30)},I=e=>{a()||e.animate([{opacity:0,transform:"translateY(4px)"},{opacity:1,transform:"none"}],{duration:250,easing:"cubic-bezier(.22,1,.36,1)"})},K=e=>requestAnimationFrame(()=>k.querySelector(`[data-focus-key="${e}"]`)?.focus()),R=t=>t instanceof e&&404===t.status?"This Palette server doesn’t support API tokens.":t instanceof Error?t.message:"Something went wrong. Try again.";k.focusStart=()=>K("api-tokens-title"),k.dispose=()=>{P(),w.remove()};let z=()=>{k.dispose(),u()};k.addEventListener("keydown",e=>{if("Escape"===e.key)if(e.stopPropagation(),e.preventDefault(),j){let e=j;j="",B(),K(`api-tokens-revoke-${e}`)}else E?K("api-tokens-done"):T?H():z()});let L=0,P=()=>{clearTimeout(L),delete w.dataset.open};k.addEventListener("pointerover",e=>{let t=e.target.closest?.("[data-tip]");if(!t)return P();clearTimeout(L),L=window.setTimeout(()=>{if(!t.isConnected)return;let e=k.getRootNode();w.parentNode!==e&&e instanceof ShadowRoot&&e.append(w);let n=t.getBoundingClientRect();w.textContent=t.dataset.tip??"";let o=w.offsetWidth/2;w.style.left=`${Math.max(8+o,Math.min(document.documentElement.clientWidth-8-o,n.left+n.width/2))}px`,w.style.top=n.top-8-w.offsetHeight+"px",w.dataset.open="true"},w.dataset.open?0:400)}),k.addEventListener("pointerleave",k.dispose),k.addEventListener("pointerdown",k.dispose);let q=(e,n,o)=>{let a=t("div"),i=t("dd");if(null===n)i.textContent=o;else{let e=t("time","",d(n));e.dateTime=new Date(n).toISOString(),e.dataset.tip=l(n),i.append(e,t("span","sr-only",` (${l(n)})`))}return a.append(t("dt","",e),i),a},D=(e,n,o)=>{let a=t("span","api-token-chip",e);return n&&(a.dataset.tone=n),o&&(a.dataset.tip=o),a},M=e=>e?1===e.length&&e[0]===p?"This project":1===e.length?e[0]:`${e.length} projects`:"All projects";function F(n){let a=t("li","api-token");a.dataset.token=n.id;let r=t("div","api-token-row"),p=t("strong","",n.name);p.id=`api-token-${n.id}-${g.id}`,a.setAttribute("aria-labelledby",p.id),r.append(p),n.expired&&r.append(D("Expired","warn"));let d=o(`Revoke ${n.name}`,()=>{j=n.id,S.delete(n.id),B(),K(`api-tokens-keep-${n.id}`)},"api-token-revoke");d.textContent="Revoke",d.removeAttribute("title"),d.dataset.focusKey=`api-tokens-revoke-${n.id}`,d.disabled=j===n.id,r.append(d);let l=t("div","api-token-chips");for(let e of n.scopes)l.append(D(s[e]??e,"",e));l.append(D(M(n.projects),"",n.projects?.join(", ")??"Every project you can access"));let c=t("dl","api-token-meta");c.append(q("Created",n.createdAt,""),q("Last used",n.lastUsedAt,"Never"),q(n.expired?"Expired":"Expires",n.expiresAt,"Never")),a.append(r,t("code","api-token-id",`${n.prefix}…${n.last4}`),l,c);let u=S.get(n.id);if(u){let e=t("p","api-tokens-error",u);e.setAttribute("role","alert"),a.append(e)}if(j===n.id){let r=t("div","api-token-confirm");r.setAttribute("role","group"),r.setAttribute("aria-label",`Revoke ${n.name}`);let s=t("div","api-tokens-actions"),p=o("Keep token",()=>{j="",B(),K(`api-tokens-revoke-${n.id}`)},"secondary");p.dataset.focusKey=`api-tokens-keep-${n.id}`;let d=o("Revoke token",()=>{!async function(t){if(!A)return;let n=A,o=n.tokens.findIndex(e=>e.id===t.id);if(o<0)return;n.tokens.splice(o,1),j="",B();let a=n.tokens[o]??n.tokens[o-1];K(a?`api-tokens-revoke-${a.id}`:"api-tokens-new");try{await i.request(`account/tokens/${encodeURIComponent(t.id)}`,"DELETE"),N(`Revoked ${t.name}.`)}catch(a){if(a instanceof e&&404===a.status)return void N(`Revoked ${t.name}.`);if(A!==n)return;n.tokens.splice(Math.min(o,n.tokens.length),0,t),S.set(t.id,`Couldn’t revoke: ${R(a)}`),B(),N(`Couldn’t revoke ${t.name}.`),K(`api-tokens-revoke-${t.id}`)}}(n)},"secondary api-tokens-danger");d.dataset.focusKey=`api-tokens-confirm-${n.id}`,s.append(p,d),r.append(t("p","","Anything using this token stops working right away. This can’t be undone."),s),a.append(r),I(r)}return a}function H(){T=!1,B(),K("api-tokens-new")}let Y="";function B(){P();let a=A?E?"secret":T?"form":"list":C?"error":"loading",d=[];if(A&&"list"===a){let e=o("New token",()=>{T=!0,B(),K("api-tokens-name")},"secondary");e.replaceChildren(n("plus"),document.createTextNode("New token")),e.removeAttribute("title"),e.dataset.focusKey="api-tokens-new",d.push(e)}else A&&"form"===a?d.push(function(n){let a=t("form","api-tokens-sheet");a.noValidate=!0,a.setAttribute("aria-labelledby",`${g.id}-new`);let d=t("h3","","New token");d.id=`${g.id}-new`;let l=t("label","account-name-label","Name"),c=t("input");c.type="text",c.maxLength=60,c.required=!0,c.autocomplete="off",c.placeholder="e.g. Deploy bot",c.dataset.focusKey="api-tokens-name",l.append(c);let u=(e,n,o,a)=>{let i=t("fieldset"),r=t("div","api-tokens-choices");for(let[e,i,s,p]of a){let a=t("label","api-tokens-choice",i),d=t("input");d.type=n,d.name=o,d.value=e,d.checked=s,d.dataset.focusKey=`api-tokens-${o}-${e}`,p&&a.append(t("small","",p)),a.prepend(d),r.append(a)}return i.append(t("legend","",e),r),i},k=u("Access","checkbox","scope",n.scopes.map(e=>[e,s[e]?`${s[e]} comments`:e,"comments:read"===e])),f=n.projects.includes(p),m=u("Projects","radio","projects",n.policy?.explicitProjects?n.projects.map(e=>[e,e===p?"This project":e,e===(f?p:n.projects[0])]):[...f?[["this","This project",!0]]:[],["all","All your projects",!f]]),h=u("Expires","radio","expiry",r.filter(([,e])=>!n.policy||null!==e&&e<=n.policy.maxLifetimeDays).map(([e,t])=>[String(t),e,30===t])),x=t("p","api-tokens-error");x.setAttribute("role","alert");let y=t("div","api-tokens-actions"),b=o("Cancel",H,"secondary");b.dataset.focusKey="api-tokens-cancel";let v=o("Create token",()=>{},"primary");return v.type="submit",v.dataset.focusKey="api-tokens-create",y.append(b,v),a.append(d,l,k,m,h,x,y),a.addEventListener("submit",async t=>{if(t.preventDefault(),$)return;let o=[...a.querySelectorAll("input[name=scope]:checked")].map(e=>e.value),r=a.querySelector("input[name=expiry]:checked")?.value,s=c.value.trim()?o.length?null:k.querySelector("input"):c;if(x.textContent=c.value.trim()?o.length?"":"Choose read, write, or both.":"Name your token.",s)s.focus();else{$=!0,v.disabled=b.disabled=!0,v.setAttribute("aria-busy","true");try{let e=await i.request("account/tokens","POST",{name:c.value.trim(),scopes:o,expiresInDays:r&&"null"!==r?Number(r):null,projects:n.policy?.explicitProjects?[a.querySelector("input[name=projects]:checked")?.value].filter(e=>!!e):"this"===a.querySelector("input[name=projects]:checked")?.value?[p]:[]});if(A!==n)return;n.tokens.unshift(e.item),T=!1,E=e,B(),N("Token created. Copy it now. You won’t see it again."),K("api-tokens-copy")}catch(t){x.textContent=t instanceof e&&429===t.status?"You’ve created several tokens recently. Try again later.":R(t),v.focus()}finally{$=!1,v.disabled=b.disabled=!1,v.removeAttribute("aria-busy")}}}),a}(A)):E&&d.push(function(e){let a=t("section","api-tokens-sheet");a.setAttribute("aria-labelledby",`${g.id}-secret`);let i=t("h3","",`Copy “${e.item.name}”`);i.id=`${g.id}-secret`;let r=t("p","api-tokens-warning");r.append(n("info"),t("span","","You won’t see this token again. Store it in a secret manager, never in source control."));let s=t("div","api-tokens-secret"),p=t("code","",e.token);p.setAttribute("aria-label","New API token"),p.tabIndex=0,p.dataset.focusKey="api-tokens-secret";let d=o("Copy token",async()=>{try{await navigator.clipboard.writeText(e.token),d.replaceChildren(n("check"),document.createTextNode("Copied")),N("Token copied to the clipboard.")}catch{getSelection()?.selectAllChildren(p),N("Couldn’t copy. The token is selected; copy it with your keyboard.")}},"primary");d.replaceChildren(n("copy"),document.createTextNode("Copy")),d.removeAttribute("title"),d.dataset.focusKey="api-tokens-copy",s.append(p,d);let l=t("div","api-tokens-actions"),c=o("Done",()=>{E=null,B(),K("api-tokens-new")},"secondary");return c.dataset.focusKey="api-tokens-done",l.append(c),a.append(i,r,s,l),a}(E));if(a!==Y&&d[0]&&I(d[0]),Y=a,b.replaceChildren(...d),v.setAttribute("aria-busy",String(!A&&!C)),!A){if(C){let n=t("p","api-tokens-error",R(C));n.setAttribute("role","alert");let a=C instanceof e&&[403,404].includes(C.status),i=o("Try again",()=>{U()},"secondary");i.dataset.focusKey="api-tokens-retry",v.replaceChildren(n,...a?[]:[i])}else{let e=t("div","api-tokens-list");e.setAttribute("aria-hidden","true"),e.append(t("div","api-tokens-skeleton"),t("div","api-tokens-skeleton"));let n=t("span","sr-only","Loading API tokens");n.setAttribute("role","status"),v.replaceChildren(e,n)}return}if(!A.tokens.length){let e=t("p","api-tokens-empty");return e.append(t("strong","","No tokens yet"),t("span","","Create one for a bot, a script, or a CI job.")),void v.replaceChildren(e)}let l=t("ul","api-tokens-list");l.setAttribute("aria-label","Your API tokens"),l.append(...A.tokens.map(F));let c=t("p","api-tokens-note");c.append(document.createTextNode("Use a token as "),t("code","","PALETTE_TOKEN"),document.createTextNode(" with the palette CLI.")),v.replaceChildren(l,c)}async function U(){C=null,A=null,B();try{let t=await i.request("account/tokens");if(!Array.isArray(t.tokens)||!Array.isArray(t.projects))throw new e(0,"Couldn’t read your tokens. Try again.");A={...t,scopes:Array.isArray(t.scopes)?t.scopes:["comments:read","comments:write"]}}catch(e){C=e instanceof Error?e:new Error(String(e))}B()}return U(),k}export{u as accountTokens,d as relativeTime};
|