@nodegx/mcp 0.2.1 → 0.2.3

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.
@@ -13801,8 +13801,8 @@ var init_nodegx = __esm({
13801
13801
  // The header works. What it cannot do is make a file private on behalf of
13802
13802
  // nobody, and an author reading "supported" would not expect that.
13803
13803
  "files.private": degraded(
13804
- "A private upload is locked to the person who made it, so a file uploaded by anything other than a signed-in user \u2014 an admin tool, a cloud function \u2014 ends up private with no owner, and stays readable by everyone.",
13805
- "BCN-007 live, security enforced: uploaded as a signed-in user, the plain URL is 403 for anonymous AND for a second signed-in user. Uploaded with the master key, the identical request with X-NodeGX-File-Private: true serves 200 to anonymous \u2014 files.ts::upload sets owner=null for a non-user principal"
13804
+ "A private upload is locked to the person who made it, so a file uploaded by anything other than a signed-in user \u2014 an admin tool, a cloud function \u2014 ends up private with no owner, and stays readable by everyone. Upload File's Readers names roles that may read it too, such as role:member; a file with Readers is never readable by everyone.",
13805
+ "BCN-007 live, security enforced: uploaded as a signed-in user, the plain URL is 403 for anonymous AND for a second signed-in user. Uploaded with the master key, the identical request with X-NodeGX-File-Private: true serves 200 to anonymous \u2014 files.ts::upload sets owner=null for a non-user principal. P121-TWO001-MEMBERSPHOTO: X-NodeGX-File-Readers adds read-only role entries to the file's ACL; uploaded with the master key and role:crew, anonymous and a user without the role are 403 (files-http.test.ts)"
13806
13806
  ),
13807
13807
  "files.progress": supported("XHR upload path reports progress"),
13808
13808
  "auth.password": supported("nodegx-backend/src/server/users.ts"),
@@ -29028,6 +29028,195 @@ var init_resolveBackend = __esm({
29028
29028
  }
29029
29029
  });
29030
29030
 
29031
+ // ../noodl-runtime/src/api/backends/SessionStore.ts
29032
+ function ambientStorage() {
29033
+ if (typeof localStorage === "undefined") return void 0;
29034
+ return localStorage;
29035
+ }
29036
+ function ambientBroadcaster() {
29037
+ if (typeof window === "undefined" || !window || typeof window.addEventListener !== "function") return void 0;
29038
+ return window;
29039
+ }
29040
+ function parseSessionKey(appId) {
29041
+ return "Parse/" + appId + "/currentUser";
29042
+ }
29043
+ function parseSessionStore(appId) {
29044
+ const cacheKey = String(appId);
29045
+ let store = _parseStores.get(cacheKey);
29046
+ if (store === void 0) {
29047
+ store = new SessionStore({ key: parseSessionKey(appId) });
29048
+ _parseStores.set(cacheKey, store);
29049
+ }
29050
+ return store;
29051
+ }
29052
+ var _tabCounter, SessionStore, _parseStores;
29053
+ var init_SessionStore = __esm({
29054
+ "../noodl-runtime/src/api/backends/SessionStore.ts"() {
29055
+ _tabCounter = 0;
29056
+ SessionStore = class {
29057
+ constructor(options) {
29058
+ this.key = options.key;
29059
+ this.lockKey = options.key + ".refresh-lock";
29060
+ this.injectedStorage = options.storage;
29061
+ this.injectedBroadcaster = options.broadcaster;
29062
+ this.lockTtlMs = options.lockTtlMs !== void 0 ? options.lockTtlMs : 1e4;
29063
+ this.now = options.now || Date.now;
29064
+ this.tabId = options.tabId || "tab-" + ++_tabCounter + "-" + Math.random().toString(36).slice(2);
29065
+ }
29066
+ /**
29067
+ * Resolved per access, not captured at construction.
29068
+ *
29069
+ * The code this replaced was a bare `localStorage[key]` evaluated on every
29070
+ * request, so a page (or a suite) that swapped the global took effect
29071
+ * immediately. Capturing it once would look identical until something did —
29072
+ * and something does: `parse-wire-adapter.test.ts` replaces
29073
+ * `globalThis.localStorage` between cases, and caching broke it. Found by
29074
+ * running the suite that was not this task's.
29075
+ */
29076
+ get storage() {
29077
+ if (this.injectedStorage !== void 0) return this.injectedStorage || void 0;
29078
+ return ambientStorage();
29079
+ }
29080
+ get broadcaster() {
29081
+ if (this.injectedBroadcaster !== void 0) return this.injectedBroadcaster || void 0;
29082
+ return ambientBroadcaster();
29083
+ }
29084
+ /** False under SSR and in the cloud runtime. Nothing here throws when it is. */
29085
+ get available() {
29086
+ return this.storage !== void 0;
29087
+ }
29088
+ // ── The session ──────────────────────────────────────────────────────────
29089
+ /** The stored text, or `undefined`. */
29090
+ readRaw() {
29091
+ const storage = this.storage;
29092
+ if (!storage) return void 0;
29093
+ const value2 = storage[this.key];
29094
+ return value2 === void 0 || value2 === null ? void 0 : String(value2);
29095
+ }
29096
+ /**
29097
+ * The stored session, or `undefined` when there is none *or it will not
29098
+ * parse*.
29099
+ *
29100
+ * Unparseable is treated as absent rather than raised, which is what all four
29101
+ * of the replaced call sites did — a corrupt entry must not stop an app from
29102
+ * loading, and the user can always sign in again.
29103
+ */
29104
+ read() {
29105
+ const raw = this.readRaw();
29106
+ if (raw === void 0) return void 0;
29107
+ try {
29108
+ return JSON.parse(raw);
29109
+ } catch (e) {
29110
+ return void 0;
29111
+ }
29112
+ }
29113
+ write(session) {
29114
+ const storage = this.storage;
29115
+ if (!storage) return;
29116
+ storage[this.key] = JSON.stringify(session);
29117
+ }
29118
+ clear() {
29119
+ const storage = this.storage;
29120
+ if (!storage) return;
29121
+ delete storage[this.key];
29122
+ }
29123
+ /**
29124
+ * Read and write any key in the same storage, with the same absent-storage
29125
+ * tolerance.
29126
+ *
29127
+ * Here for Parse's installation id, which lives beside the session under
29128
+ * `Parse/<appId>/installationId` and is the only other thing `userservice.ts`
29129
+ * kept in local storage. It is not session state, so it gets no method of its
29130
+ * own — but it does need the SSR guard, and duplicating that guard is how the
29131
+ * four copies above happened in the first place.
29132
+ */
29133
+ readKey(key) {
29134
+ const storage = this.storage;
29135
+ if (!storage) return void 0;
29136
+ const value2 = storage[key];
29137
+ return value2 === void 0 || value2 === null ? void 0 : String(value2);
29138
+ }
29139
+ writeKey(key, value2) {
29140
+ const storage = this.storage;
29141
+ if (!storage) return;
29142
+ storage[key] = value2;
29143
+ }
29144
+ // ── Cross-tab ────────────────────────────────────────────────────────────
29145
+ /**
29146
+ * Hear about session changes made by **other** tabs.
29147
+ *
29148
+ * The `storage` event fires only in tabs that did not make the change, which
29149
+ * is exactly the semantics wanted and is why this is preferred to
29150
+ * `BroadcastChannel`: no echo to filter out, no availability question, and it
29151
+ * comes free with the storage already in use.
29152
+ *
29153
+ * Returns an unsubscribe function. Returns a no-op one when there is no
29154
+ * broadcaster, so callers never branch.
29155
+ */
29156
+ onExternalChange(handler) {
29157
+ const broadcaster = this.broadcaster;
29158
+ if (!broadcaster) return () => {
29159
+ };
29160
+ const listener = (event) => {
29161
+ if (event && event.key !== null && event.key !== this.key) return;
29162
+ handler(this.read());
29163
+ };
29164
+ broadcaster.addEventListener("storage", listener);
29165
+ return () => broadcaster.removeEventListener("storage", listener);
29166
+ }
29167
+ /**
29168
+ * Try to become the tab that refreshes.
29169
+ *
29170
+ * Read, write, **read back**. `localStorage` writes are serialised per origin,
29171
+ * so in the racing case both tabs write and both read back and exactly one
29172
+ * sees its own id — and the loser discovers that *before issuing any request*,
29173
+ * because the whole sequence is synchronous with no `await` in it. That is
29174
+ * what keeps the cost of a lost race at one tab doing nothing rather than two
29175
+ * tabs spending the same rotating refresh token.
29176
+ *
29177
+ * It is not a mutex and BCN-006-LIFECYCLE-DESIGN §5 does not claim it is. The
29178
+ * residual risk is a same-millisecond interleaving across processes on an
29179
+ * implementation that does not serialise writes; the cost if it happens is one
29180
+ * wasted refresh, and the follower's wait-then-adopt behaviour means the user
29181
+ * still ends up with the session the winner wrote.
29182
+ *
29183
+ * **The lock expires.** A tab closed mid-refresh must not hold it forever —
29184
+ * that would be worse than having no lock at all.
29185
+ */
29186
+ tryAcquireRefreshLock() {
29187
+ const storage = this.storage;
29188
+ if (!storage) return false;
29189
+ const now = this.now();
29190
+ const existing = this.readLock();
29191
+ if (existing && existing.owner !== this.tabId && existing.expiresAt > now) return false;
29192
+ storage[this.lockKey] = JSON.stringify({ owner: this.tabId, expiresAt: now + this.lockTtlMs });
29193
+ const confirmed = this.readLock();
29194
+ return confirmed !== void 0 && confirmed.owner === this.tabId;
29195
+ }
29196
+ /** Release only our own lock — never one another tab has since taken. */
29197
+ releaseRefreshLock() {
29198
+ const storage = this.storage;
29199
+ if (!storage) return;
29200
+ const existing = this.readLock();
29201
+ if (existing && existing.owner !== this.tabId) return;
29202
+ delete storage[this.lockKey];
29203
+ }
29204
+ readLock() {
29205
+ const raw = this.readKey(this.lockKey);
29206
+ if (raw === void 0) return void 0;
29207
+ try {
29208
+ const parsed = JSON.parse(raw);
29209
+ if (!parsed || typeof parsed.owner !== "string" || typeof parsed.expiresAt !== "number") return void 0;
29210
+ return parsed;
29211
+ } catch (e) {
29212
+ return void 0;
29213
+ }
29214
+ }
29215
+ };
29216
+ _parseStores = /* @__PURE__ */ new Map();
29217
+ }
29218
+ });
29219
+
29031
29220
  // ../nodegx-backend-contract/realtime.ts
29032
29221
  var init_realtime2 = __esm({
29033
29222
  "../nodegx-backend-contract/realtime.ts"() {
@@ -29065,6 +29254,7 @@ var DEFAULT_TIMING, DEFAULT_HOST, RealtimeSubscription;
29065
29254
  var init_RealtimeSubscription = __esm({
29066
29255
  "../noodl-runtime/src/api/backends/realtime/RealtimeSubscription.ts"() {
29067
29256
  init_resolveBackend_pure();
29257
+ init_SessionStore();
29068
29258
  init_realtime2();
29069
29259
  DEFAULT_TIMING = REALTIME_TIMING;
29070
29260
  DEFAULT_HOST = {};
@@ -29305,9 +29495,20 @@ var init_RealtimeSubscription = __esm({
29305
29495
  * fallback.
29306
29496
  */
29307
29497
  get token() {
29308
- if (isParseWireType(this.handle.type)) return this.handle.sessionToken || "";
29498
+ if (isParseWireType(this.handle.type)) return this.handle.sessionToken || this.storedSession() || "";
29309
29499
  return this.handle.sessionToken || this.handle.publicToken || "";
29310
29500
  }
29501
+ /**
29502
+ * 🔴 P121-TWO005-LIVEANON. The signed-in user's session, where a Parse-wire app keeps it: the session store, keyed by
29503
+ * the app id in `publicToken` — the same place `ParseWireAdapter` reads it from for every request
29504
+ * (`readStoredCurrentUser`). Nothing sets `handle.sessionToken` on this wire, so reading the handle alone made every
29505
+ * subscription anonymous for a signed-in member, and a collection only members may read refused it ("denies find
29506
+ * to anonymous") on every retry. SBR-011's arm passed a session ON THE HANDLE, which a real app never has.
29507
+ */
29508
+ storedSession() {
29509
+ const stored2 = parseSessionStore(this.handle.publicToken).read();
29510
+ return stored2 && typeof stored2.sessionToken === "string" ? stored2.sessionToken : "";
29511
+ }
29311
29512
  _armDeadline(generation) {
29312
29513
  this._clearTimer("_deadlineTimer");
29313
29514
  this._deadlineTimer = this._setTimeout(() => {
@@ -29851,12 +30052,11 @@ var init_SseTransport = __esm({
29851
30052
  helloEvent: "connected",
29852
30053
  changeEvents: () => ["change"],
29853
30054
  resyncEvent: "resync",
29854
- subscribeRequest(base, token, clientId, subscriptions) {
30055
+ subscribeRequest(base, _token, clientId, subscriptions) {
29855
30056
  const body = subscriptions.map(
29856
30057
  (subscription) => subscription.where === void 0 ? { collection: subscription.collection } : { collection: subscription.collection, filter: subscription.where }
29857
30058
  );
29858
30059
  const headers = { "content-type": "application/json" };
29859
- if (token) headers["authorization"] = "Bearer " + token;
29860
30060
  return {
29861
30061
  url: base + "/realtime/subscriptions",
29862
30062
  init: {
@@ -30701,195 +30901,6 @@ var init_AdapterEvents = __esm({
30701
30901
  }
30702
30902
  });
30703
30903
 
30704
- // ../noodl-runtime/src/api/backends/SessionStore.ts
30705
- function ambientStorage() {
30706
- if (typeof localStorage === "undefined") return void 0;
30707
- return localStorage;
30708
- }
30709
- function ambientBroadcaster() {
30710
- if (typeof window === "undefined" || !window || typeof window.addEventListener !== "function") return void 0;
30711
- return window;
30712
- }
30713
- function parseSessionKey(appId) {
30714
- return "Parse/" + appId + "/currentUser";
30715
- }
30716
- function parseSessionStore(appId) {
30717
- const cacheKey = String(appId);
30718
- let store = _parseStores.get(cacheKey);
30719
- if (store === void 0) {
30720
- store = new SessionStore({ key: parseSessionKey(appId) });
30721
- _parseStores.set(cacheKey, store);
30722
- }
30723
- return store;
30724
- }
30725
- var _tabCounter, SessionStore, _parseStores;
30726
- var init_SessionStore = __esm({
30727
- "../noodl-runtime/src/api/backends/SessionStore.ts"() {
30728
- _tabCounter = 0;
30729
- SessionStore = class {
30730
- constructor(options) {
30731
- this.key = options.key;
30732
- this.lockKey = options.key + ".refresh-lock";
30733
- this.injectedStorage = options.storage;
30734
- this.injectedBroadcaster = options.broadcaster;
30735
- this.lockTtlMs = options.lockTtlMs !== void 0 ? options.lockTtlMs : 1e4;
30736
- this.now = options.now || Date.now;
30737
- this.tabId = options.tabId || "tab-" + ++_tabCounter + "-" + Math.random().toString(36).slice(2);
30738
- }
30739
- /**
30740
- * Resolved per access, not captured at construction.
30741
- *
30742
- * The code this replaced was a bare `localStorage[key]` evaluated on every
30743
- * request, so a page (or a suite) that swapped the global took effect
30744
- * immediately. Capturing it once would look identical until something did —
30745
- * and something does: `parse-wire-adapter.test.ts` replaces
30746
- * `globalThis.localStorage` between cases, and caching broke it. Found by
30747
- * running the suite that was not this task's.
30748
- */
30749
- get storage() {
30750
- if (this.injectedStorage !== void 0) return this.injectedStorage || void 0;
30751
- return ambientStorage();
30752
- }
30753
- get broadcaster() {
30754
- if (this.injectedBroadcaster !== void 0) return this.injectedBroadcaster || void 0;
30755
- return ambientBroadcaster();
30756
- }
30757
- /** False under SSR and in the cloud runtime. Nothing here throws when it is. */
30758
- get available() {
30759
- return this.storage !== void 0;
30760
- }
30761
- // ── The session ──────────────────────────────────────────────────────────
30762
- /** The stored text, or `undefined`. */
30763
- readRaw() {
30764
- const storage = this.storage;
30765
- if (!storage) return void 0;
30766
- const value2 = storage[this.key];
30767
- return value2 === void 0 || value2 === null ? void 0 : String(value2);
30768
- }
30769
- /**
30770
- * The stored session, or `undefined` when there is none *or it will not
30771
- * parse*.
30772
- *
30773
- * Unparseable is treated as absent rather than raised, which is what all four
30774
- * of the replaced call sites did — a corrupt entry must not stop an app from
30775
- * loading, and the user can always sign in again.
30776
- */
30777
- read() {
30778
- const raw = this.readRaw();
30779
- if (raw === void 0) return void 0;
30780
- try {
30781
- return JSON.parse(raw);
30782
- } catch (e) {
30783
- return void 0;
30784
- }
30785
- }
30786
- write(session) {
30787
- const storage = this.storage;
30788
- if (!storage) return;
30789
- storage[this.key] = JSON.stringify(session);
30790
- }
30791
- clear() {
30792
- const storage = this.storage;
30793
- if (!storage) return;
30794
- delete storage[this.key];
30795
- }
30796
- /**
30797
- * Read and write any key in the same storage, with the same absent-storage
30798
- * tolerance.
30799
- *
30800
- * Here for Parse's installation id, which lives beside the session under
30801
- * `Parse/<appId>/installationId` and is the only other thing `userservice.ts`
30802
- * kept in local storage. It is not session state, so it gets no method of its
30803
- * own — but it does need the SSR guard, and duplicating that guard is how the
30804
- * four copies above happened in the first place.
30805
- */
30806
- readKey(key) {
30807
- const storage = this.storage;
30808
- if (!storage) return void 0;
30809
- const value2 = storage[key];
30810
- return value2 === void 0 || value2 === null ? void 0 : String(value2);
30811
- }
30812
- writeKey(key, value2) {
30813
- const storage = this.storage;
30814
- if (!storage) return;
30815
- storage[key] = value2;
30816
- }
30817
- // ── Cross-tab ────────────────────────────────────────────────────────────
30818
- /**
30819
- * Hear about session changes made by **other** tabs.
30820
- *
30821
- * The `storage` event fires only in tabs that did not make the change, which
30822
- * is exactly the semantics wanted and is why this is preferred to
30823
- * `BroadcastChannel`: no echo to filter out, no availability question, and it
30824
- * comes free with the storage already in use.
30825
- *
30826
- * Returns an unsubscribe function. Returns a no-op one when there is no
30827
- * broadcaster, so callers never branch.
30828
- */
30829
- onExternalChange(handler) {
30830
- const broadcaster = this.broadcaster;
30831
- if (!broadcaster) return () => {
30832
- };
30833
- const listener = (event) => {
30834
- if (event && event.key !== null && event.key !== this.key) return;
30835
- handler(this.read());
30836
- };
30837
- broadcaster.addEventListener("storage", listener);
30838
- return () => broadcaster.removeEventListener("storage", listener);
30839
- }
30840
- /**
30841
- * Try to become the tab that refreshes.
30842
- *
30843
- * Read, write, **read back**. `localStorage` writes are serialised per origin,
30844
- * so in the racing case both tabs write and both read back and exactly one
30845
- * sees its own id — and the loser discovers that *before issuing any request*,
30846
- * because the whole sequence is synchronous with no `await` in it. That is
30847
- * what keeps the cost of a lost race at one tab doing nothing rather than two
30848
- * tabs spending the same rotating refresh token.
30849
- *
30850
- * It is not a mutex and BCN-006-LIFECYCLE-DESIGN §5 does not claim it is. The
30851
- * residual risk is a same-millisecond interleaving across processes on an
30852
- * implementation that does not serialise writes; the cost if it happens is one
30853
- * wasted refresh, and the follower's wait-then-adopt behaviour means the user
30854
- * still ends up with the session the winner wrote.
30855
- *
30856
- * **The lock expires.** A tab closed mid-refresh must not hold it forever —
30857
- * that would be worse than having no lock at all.
30858
- */
30859
- tryAcquireRefreshLock() {
30860
- const storage = this.storage;
30861
- if (!storage) return false;
30862
- const now = this.now();
30863
- const existing = this.readLock();
30864
- if (existing && existing.owner !== this.tabId && existing.expiresAt > now) return false;
30865
- storage[this.lockKey] = JSON.stringify({ owner: this.tabId, expiresAt: now + this.lockTtlMs });
30866
- const confirmed = this.readLock();
30867
- return confirmed !== void 0 && confirmed.owner === this.tabId;
30868
- }
30869
- /** Release only our own lock — never one another tab has since taken. */
30870
- releaseRefreshLock() {
30871
- const storage = this.storage;
30872
- if (!storage) return;
30873
- const existing = this.readLock();
30874
- if (existing && existing.owner !== this.tabId) return;
30875
- delete storage[this.lockKey];
30876
- }
30877
- readLock() {
30878
- const raw = this.readKey(this.lockKey);
30879
- if (raw === void 0) return void 0;
30880
- try {
30881
- const parsed = JSON.parse(raw);
30882
- if (!parsed || typeof parsed.owner !== "string" || typeof parsed.expiresAt !== "number") return void 0;
30883
- return parsed;
30884
- } catch (e) {
30885
- return void 0;
30886
- }
30887
- }
30888
- };
30889
- _parseStores = /* @__PURE__ */ new Map();
30890
- }
30891
- });
30892
-
30893
30904
  // ../noodl-runtime/src/api/backends/fileRef.ts
30894
30905
  function jwtExpiry(token) {
30895
30906
  if (typeof token !== "string") return void 0;
@@ -31023,6 +31034,13 @@ function withStatus(err, status) {
31023
31034
  if (status !== void 0 && status > 0) envelope.status = status;
31024
31035
  return envelope;
31025
31036
  }
31037
+ function uploadHeaders(options) {
31038
+ const headers = {};
31039
+ if (options.private) headers["X-NodeGX-File-Private"] = "true";
31040
+ const readers = typeof options.readers === "string" ? options.readers.trim() : "";
31041
+ if (readers) headers["X-NodeGX-File-Readers"] = readers;
31042
+ return Object.keys(headers).length ? headers : void 0;
31043
+ }
31026
31044
  function _removeProtectedFields(data, className) {
31027
31045
  const _data = Object.assign({}, data);
31028
31046
  _protectedFields._common.forEach((f) => delete _data[f]);
@@ -31440,10 +31458,10 @@ var init_ParseWireAdapter = __esm({
31440
31458
  content: options.file,
31441
31459
  // Dead — `_makeRequest` never reads `contentType`. See the module note.
31442
31460
  contentType: options.file.type,
31443
- // BAK-006 follow-up: the Upload File node's Private input. The backend
31444
- // reads this exact header (files.ts, case-insensitively) to ACL the
31445
- // upload to its owner instead of leaving it public.
31446
- headers: options.private ? { "X-NodeGX-File-Private": "true" } : void 0,
31461
+ // BAK-006 follow-up: the Upload File node's Private input, and (P121
31462
+ // MEMBERSPHOTO) its Readers. The backend reads these headers to ACL the
31463
+ // upload to its owner, and to those roles, instead of leaving it public.
31464
+ headers: uploadHeaders(options),
31447
31465
  // BCN-007 step 1. This used to be
31448
31466
  // `Object.assign({}, options.data, response)` cast to `{name, url}` —
31449
31467
  // whatever the backend sent, spread over a caller-supplied object no
@@ -65013,16 +65031,19 @@ var require_tokens = __commonJS({
65013
65031
  // ../nodegx-backend/build/admin/app.js.txt
65014
65032
  var require_app_js = __commonJS({
65015
65033
  "../nodegx-backend/build/admin/app.js.txt"(exports2, module2) {
65016
- module2.exports = `"use strict";(()=>{var En,be,Yr,ol,vt,qr,Xr,Zr,Qr,Ci,vi,ki,sl,tn={},eo=[],al=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i,_n=Array.isArray;function et(e,n){for(var i in n)e[i]=n[i];return e}function Si(e){e&&e.parentNode&&e.parentNode.removeChild(e)}function ll(e,n,i){var r,o,s,l={};for(s in n)s=="key"?r=n[s]:s=="ref"?o=n[s]:l[s]=n[s];if(arguments.length>2&&(l.children=arguments.length>3?En.call(arguments,2):i),typeof e=="function"&&e.defaultProps!=null)for(s in e.defaultProps)l[s]===void 0&&(l[s]=e.defaultProps[s]);return Tn(e,l,r,o,null)}function Tn(e,n,i,r,o){var s={type:e,props:n,key:i,ref:r,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:o??++Yr,__i:-1,__u:0};return o==null&&be.vnode!=null&&be.vnode(s),s}function W(e){return e.children}function xn(e,n){this.props=e,this.context=n}function Dt(e,n){if(n==null)return e.__?Dt(e.__,e.__i+1):null;for(var i;n<e.__k.length;n++)if((i=e.__k[n])!=null&&i.__e!=null)return i.__e;return typeof e.type=="function"?Dt(e):null}function to(e){var n,i;if((e=e.__)!=null&&e.__c!=null){for(e.__e=e.__c.base=null,n=0;n<e.__k.length;n++)if((i=e.__k[n])!=null&&i.__e!=null){e.__e=e.__c.base=i.__e;break}return to(e)}}function $r(e){(!e.__d&&(e.__d=!0)&&vt.push(e)&&!Rn.__r++||qr!=be.debounceRendering)&&((qr=be.debounceRendering)||Xr)(Rn)}function Rn(){for(var e,n,i,r,o,s,l,a=1;vt.length;)vt.length>a&&vt.sort(Zr),e=vt.shift(),a=vt.length,e.__d&&(i=void 0,r=void 0,o=(r=(n=e).__v).__e,s=[],l=[],n.__P&&((i=et({},r)).__v=r.__v+1,be.vnode&&be.vnode(i),Ti(n.__P,i,r,n.__n,n.__P.namespaceURI,32&r.__u?[o]:null,s,o??Dt(r),!!(32&r.__u),l),i.__v=r.__v,i.__.__k[i.__i]=i,ro(s,i,l),r.__e=r.__=null,i.__e!=o&&to(i)));Rn.__r=0}function no(e,n,i,r,o,s,l,a,c,u,p){var d,f,b,g,k,y,T,w=r&&r.__k||eo,x=n.length;for(c=cl(i,n,w,c,x),d=0;d<x;d++)(b=i.__k[d])!=null&&(f=b.__i==-1?tn:w[b.__i]||tn,b.__i=d,y=Ti(e,b,f,o,s,l,a,c,u,p),g=b.__e,b.ref&&f.ref!=b.ref&&(f.ref&&xi(f.ref,null,b),p.push(b.ref,b.__c||g,b)),k==null&&g!=null&&(k=g),(T=!!(4&b.__u))||f.__k===b.__k?c=io(b,c,e,T):typeof b.type=="function"&&y!==void 0?c=y:g&&(c=g.nextSibling),b.__u&=-7);return i.__e=k,c}function cl(e,n,i,r,o){var s,l,a,c,u,p=i.length,d=p,f=0;for(e.__k=new Array(o),s=0;s<o;s++)(l=n[s])!=null&&typeof l!="boolean"&&typeof l!="function"?(typeof l=="string"||typeof l=="number"||typeof l=="bigint"||l.constructor==String?l=e.__k[s]=Tn(null,l,null,null,null):_n(l)?l=e.__k[s]=Tn(W,{children:l},null,null,null):l.constructor===void 0&&l.__b>0?l=e.__k[s]=Tn(l.type,l.props,l.key,l.ref?l.ref:null,l.__v):e.__k[s]=l,c=s+f,l.__=e,l.__b=e.__b+1,a=null,(u=l.__i=dl(l,i,c,d))!=-1&&(d--,(a=i[u])&&(a.__u|=2)),a==null||a.__v==null?(u==-1&&(o>p?f--:o<p&&f++),typeof l.type!="function"&&(l.__u|=4)):u!=c&&(u==c-1?f--:u==c+1?f++:(u>c?f--:f++,l.__u|=4))):e.__k[s]=null;if(d)for(s=0;s<p;s++)(a=i[s])!=null&&(2&a.__u)==0&&(a.__e==r&&(r=Dt(a)),so(a,a));return r}function io(e,n,i,r){var o,s;if(typeof e.type=="function"){for(o=e.__k,s=0;o&&s<o.length;s++)o[s]&&(o[s].__=e,n=io(o[s],n,i,r));return n}e.__e!=n&&(r&&(n&&e.type&&!n.parentNode&&(n=Dt(e)),i.insertBefore(e.__e,n||null)),n=e.__e);do n=n&&n.nextSibling;while(n!=null&&n.nodeType==8);return n}function dl(e,n,i,r){var o,s,l,a=e.key,c=e.type,u=n[i],p=u!=null&&(2&u.__u)==0;if(u===null&&a==null||p&&a==u.key&&c==u.type)return i;if(r>(p?1:0)){for(o=i-1,s=i+1;o>=0||s<n.length;)if((u=n[l=o>=0?o--:s++])!=null&&(2&u.__u)==0&&a==u.key&&c==u.type)return l}return-1}function zr(e,n,i){n[0]=="-"?e.setProperty(n,i??""):e[n]=i==null?"":typeof i!="number"||al.test(n)?i:i+"px"}function Sn(e,n,i,r,o){var s,l;e:if(n=="style")if(typeof i=="string")e.style.cssText=i;else{if(typeof r=="string"&&(e.style.cssText=r=""),r)for(n in r)i&&n in i||zr(e.style,n,"");if(i)for(n in i)r&&i[n]==r[n]||zr(e.style,n,i[n])}else if(n[0]=="o"&&n[1]=="n")s=n!=(n=n.replace(Qr,"$1")),l=n.toLowerCase(),n=l in e||n=="onFocusOut"||n=="onFocusIn"?l.slice(2):n.slice(2),e.l||(e.l={}),e.l[n+s]=i,i?r?i.u=r.u:(i.u=Ci,e.addEventListener(n,s?ki:vi,s)):e.removeEventListener(n,s?ki:vi,s);else{if(o=="http://www.w3.org/2000/svg")n=n.replace(/xlink(H|:h)/,"h").replace(/sName$/,"s");else if(n!="width"&&n!="height"&&n!="href"&&n!="list"&&n!="form"&&n!="tabIndex"&&n!="download"&&n!="rowSpan"&&n!="colSpan"&&n!="role"&&n!="popover"&&n in e)try{e[n]=i??"";break e}catch{}typeof i=="function"||(i==null||i===!1&&n[4]!="-"?e.removeAttribute(n):e.setAttribute(n,n=="popover"&&i==1?"":i))}}function Jr(e){return function(n){if(this.l){var i=this.l[n.type+e];if(n.t==null)n.t=Ci++;else if(n.t<i.u)return;return i(be.event?be.event(n):n)}}}function Ti(e,n,i,r,o,s,l,a,c,u){var p,d,f,b,g,k,y,T,w,x,E,C,_,N,v,R,A,U=n.type;if(n.constructor!==void 0)return null;128&i.__u&&(c=!!(32&i.__u),s=[a=n.__e=i.__e]),(p=be.__b)&&p(n);e:if(typeof U=="function")try{if(T=n.props,w="prototype"in U&&U.prototype.render,x=(p=U.contextType)&&r[p.__c],E=p?x?x.props.value:p.__:r,i.__c?y=(d=n.__c=i.__c).__=d.__E:(w?n.__c=d=new U(T,E):(n.__c=d=new xn(T,E),d.constructor=U,d.render=pl),x&&x.sub(d),d.state||(d.state={}),d.__n=r,f=d.__d=!0,d.__h=[],d._sb=[]),w&&d.__s==null&&(d.__s=d.state),w&&U.getDerivedStateFromProps!=null&&(d.__s==d.state&&(d.__s=et({},d.__s)),et(d.__s,U.getDerivedStateFromProps(T,d.__s))),b=d.props,g=d.state,d.__v=n,f)w&&U.getDerivedStateFromProps==null&&d.componentWillMount!=null&&d.componentWillMount(),w&&d.componentDidMount!=null&&d.__h.push(d.componentDidMount);else{if(w&&U.getDerivedStateFromProps==null&&T!==b&&d.componentWillReceiveProps!=null&&d.componentWillReceiveProps(T,E),n.__v==i.__v||!d.__e&&d.shouldComponentUpdate!=null&&d.shouldComponentUpdate(T,d.__s,E)===!1){for(n.__v!=i.__v&&(d.props=T,d.state=d.__s,d.__d=!1),n.__e=i.__e,n.__k=i.__k,n.__k.some(function(X){X&&(X.__=n)}),C=0;C<d._sb.length;C++)d.__h.push(d._sb[C]);d._sb=[],d.__h.length&&l.push(d);break e}d.componentWillUpdate!=null&&d.componentWillUpdate(T,d.__s,E),w&&d.componentDidUpdate!=null&&d.__h.push(function(){d.componentDidUpdate(b,g,k)})}if(d.context=E,d.props=T,d.__P=e,d.__e=!1,_=be.__r,N=0,w){for(d.state=d.__s,d.__d=!1,_&&_(n),p=d.render(d.props,d.state,d.context),v=0;v<d._sb.length;v++)d.__h.push(d._sb[v]);d._sb=[]}else do d.__d=!1,_&&_(n),p=d.render(d.props,d.state,d.context),d.state=d.__s;while(d.__d&&++N<25);d.state=d.__s,d.getChildContext!=null&&(r=et(et({},r),d.getChildContext())),w&&!f&&d.getSnapshotBeforeUpdate!=null&&(k=d.getSnapshotBeforeUpdate(b,g)),R=p,p!=null&&p.type===W&&p.key==null&&(R=oo(p.props.children)),a=no(e,_n(R)?R:[R],n,i,r,o,s,l,a,c,u),d.base=n.__e,n.__u&=-161,d.__h.length&&l.push(d),y&&(d.__E=d.__=null)}catch(X){if(n.__v=null,c||s!=null)if(X.then){for(n.__u|=c?160:128;a&&a.nodeType==8&&a.nextSibling;)a=a.nextSibling;s[s.indexOf(a)]=null,n.__e=a}else{for(A=s.length;A--;)Si(s[A]);wi(n)}else n.__e=i.__e,n.__k=i.__k,X.then||wi(n);be.__e(X,n,i)}else s==null&&n.__v==i.__v?(n.__k=i.__k,n.__e=i.__e):a=n.__e=ul(i.__e,n,i,r,o,s,l,c,u);return(p=be.diffed)&&p(n),128&n.__u?void 0:a}function wi(e){e&&e.__c&&(e.__c.__e=!0),e&&e.__k&&e.__k.forEach(wi)}function ro(e,n,i){for(var r=0;r<i.length;r++)xi(i[r],i[++r],i[++r]);be.__c&&be.__c(n,e),e.some(function(o){try{e=o.__h,o.__h=[],e.some(function(s){s.call(o)})}catch(s){be.__e(s,o.__v)}})}function oo(e){return typeof e!="object"||e==null||e.__b&&e.__b>0?e:_n(e)?e.map(oo):et({},e)}function ul(e,n,i,r,o,s,l,a,c){var u,p,d,f,b,g,k,y=i.props||tn,T=n.props,w=n.type;if(w=="svg"?o="http://www.w3.org/2000/svg":w=="math"?o="http://www.w3.org/1998/Math/MathML":o||(o="http://www.w3.org/1999/xhtml"),s!=null){for(u=0;u<s.length;u++)if((b=s[u])&&"setAttribute"in b==!!w&&(w?b.localName==w:b.nodeType==3)){e=b,s[u]=null;break}}if(e==null){if(w==null)return document.createTextNode(T);e=document.createElementNS(o,w,T.is&&T),a&&(be.__m&&be.__m(n,s),a=!1),s=null}if(w==null)y===T||a&&e.data==T||(e.data=T);else{if(s=s&&En.call(e.childNodes),!a&&s!=null)for(y={},u=0;u<e.attributes.length;u++)y[(b=e.attributes[u]).name]=b.value;for(u in y)if(b=y[u],u!="children"){if(u=="dangerouslySetInnerHTML")d=b;else if(!(u in T)){if(u=="value"&&"defaultValue"in T||u=="checked"&&"defaultChecked"in T)continue;Sn(e,u,null,b,o)}}for(u in T)b=T[u],u=="children"?f=b:u=="dangerouslySetInnerHTML"?p=b:u=="value"?g=b:u=="checked"?k=b:a&&typeof b!="function"||y[u]===b||Sn(e,u,b,y[u],o);if(p)a||d&&(p.__html==d.__html||p.__html==e.innerHTML)||(e.innerHTML=p.__html),n.__k=[];else if(d&&(e.innerHTML=""),no(n.type=="template"?e.content:e,_n(f)?f:[f],n,i,r,w=="foreignObject"?"http://www.w3.org/1999/xhtml":o,s,l,s?s[0]:i.__k&&Dt(i,0),a,c),s!=null)for(u=s.length;u--;)Si(s[u]);a||(u="value",w=="progress"&&g==null?e.removeAttribute("value"):g!=null&&(g!==e[u]||w=="progress"&&!g||w=="option"&&g!=y[u])&&Sn(e,u,g,y[u],o),u="checked",k!=null&&k!=e[u]&&Sn(e,u,k,y[u],o))}return e}function xi(e,n,i){try{if(typeof e=="function"){var r=typeof e.__u=="function";r&&e.__u(),r&&n==null||(e.__u=e(n))}else e.current=n}catch(o){be.__e(o,i)}}function so(e,n,i){var r,o;if(be.unmount&&be.unmount(e),(r=e.ref)&&(r.current&&r.current!=e.__e||xi(r,null,n)),(r=e.__c)!=null){if(r.componentWillUnmount)try{r.componentWillUnmount()}catch(s){be.__e(s,n)}r.base=r.__P=null}if(r=e.__k)for(o=0;o<r.length;o++)r[o]&&so(r[o],n,i||typeof e.type!="function");i||Si(e.__e),e.__c=e.__=e.__e=void 0}function pl(e,n,i){return this.constructor(e,i)}function ao(e,n,i){var r,o,s,l;n==document&&(n=document.documentElement),be.__&&be.__(e,n),o=(r=typeof i=="function")?null:i&&i.__k||n.__k,s=[],l=[],Ti(n,e=(!r&&i||n).__k=ll(W,null,[e]),o||tn,tn,n.namespaceURI,!r&&i?[i]:o?null:n.firstChild?En.call(n.childNodes):null,s,!r&&i?i:o?o.__e:n.firstChild,r,l),ro(s,e,l)}En=eo.slice,be={__e:function(e,n,i,r){for(var o,s,l;n=n.__;)if((o=n.__c)&&!o.__)try{if((s=o.constructor)&&s.getDerivedStateFromError!=null&&(o.setState(s.getDerivedStateFromError(e)),l=o.__d),o.componentDidCatch!=null&&(o.componentDidCatch(e,r||{}),l=o.__d),l)return o.__E=o}catch(a){e=a}throw e}},Yr=0,ol=function(e){return e!=null&&e.constructor===void 0},xn.prototype.setState=function(e,n){var i;i=this.__s!=null&&this.__s!=this.state?this.__s:this.__s=et({},this.state),typeof e=="function"&&(e=e(et({},i),this.props)),e&&et(i,e),e!=null&&this.__v&&(n&&this._sb.push(n),$r(this))},xn.prototype.forceUpdate=function(e){this.__v&&(this.__e=!0,e&&this.__h.push(e),$r(this))},xn.prototype.render=W,vt=[],Xr=typeof Promise=="function"?Promise.prototype.then.bind(Promise.resolve()):setTimeout,Zr=function(e,n){return e.__v.__b-n.__v.__b},Rn.__r=0,Qr=/(PointerCapture)$|Capture$/i,Ci=0,vi=Jr(!1),ki=Jr(!0),sl=0;var nn,xe,Ri,lo,Pn=0,bo=[],Ee=be,co=Ee.__b,uo=Ee.__r,po=Ee.diffed,mo=Ee.__c,fo=Ee.unmount,ho=Ee.__;function _i(e,n){Ee.__h&&Ee.__h(xe,e,Pn||n),Pn=0;var i=xe.__H||(xe.__H={__:[],__h:[]});return e>=i.__.length&&i.__.push({}),i.__[e]}function m(e){return Pn=1,ml(vo,e)}function ml(e,n,i){var r=_i(nn++,2);if(r.t=e,!r.__c&&(r.__=[i?i(n):vo(void 0,n),function(a){var c=r.__N?r.__N[0]:r.__[0],u=r.t(c,a);c!==u&&(r.__N=[u,r.__[1]],r.__c.setState({}))}],r.__c=xe,!xe.__f)){var o=function(a,c,u){if(!r.__c.__H)return!0;var p=r.__c.__H.__.filter(function(f){return!!f.__c});if(p.every(function(f){return!f.__N}))return!s||s.call(this,a,c,u);var d=r.__c.props!==a;return p.forEach(function(f){if(f.__N){var b=f.__[0];f.__=f.__N,f.__N=void 0,b!==f.__[0]&&(d=!0)}}),s&&s.call(this,a,c,u)||d};xe.__f=!0;var s=xe.shouldComponentUpdate,l=xe.componentWillUpdate;xe.componentWillUpdate=function(a,c,u){if(this.__e){var p=s;s=void 0,o(a,c,u),s=p}l&&l.call(this,a,c,u)},xe.shouldComponentUpdate=o}return r.__N||r.__}function j(e,n){var i=_i(nn++,3);!Ee.__s&&yo(i.__H,n)&&(i.__=e,i.u=n,xe.__H.__h.push(i))}function ae(e){return Pn=5,Ot(function(){return{current:e}},[])}function Ot(e,n){var i=_i(nn++,7);return yo(i.__H,n)&&(i.__=e(),i.__H=n,i.__h=e),i.__}function fl(){for(var e;e=bo.shift();)if(e.__P&&e.__H)try{e.__H.__h.forEach(An),e.__H.__h.forEach(Ei),e.__H.__h=[]}catch(n){e.__H.__h=[],Ee.__e(n,e.__v)}}Ee.__b=function(e){xe=null,co&&co(e)},Ee.__=function(e,n){e&&n.__k&&n.__k.__m&&(e.__m=n.__k.__m),ho&&ho(e,n)},Ee.__r=function(e){uo&&uo(e),nn=0;var n=(xe=e.__c).__H;n&&(Ri===xe?(n.__h=[],xe.__h=[],n.__.forEach(function(i){i.__N&&(i.__=i.__N),i.u=i.__N=void 0})):(n.__h.forEach(An),n.__h.forEach(Ei),n.__h=[],nn=0)),Ri=xe},Ee.diffed=function(e){po&&po(e);var n=e.__c;n&&n.__H&&(n.__H.__h.length&&(bo.push(n)!==1&&lo===Ee.requestAnimationFrame||((lo=Ee.requestAnimationFrame)||hl)(fl)),n.__H.__.forEach(function(i){i.u&&(i.__H=i.u),i.u=void 0})),Ri=xe=null},Ee.__c=function(e,n){n.some(function(i){try{i.__h.forEach(An),i.__h=i.__h.filter(function(r){return!r.__||Ei(r)})}catch(r){n.some(function(o){o.__h&&(o.__h=[])}),n=[],Ee.__e(r,i.__v)}}),mo&&mo(e,n)},Ee.unmount=function(e){fo&&fo(e);var n,i=e.__c;i&&i.__H&&(i.__H.__.forEach(function(r){try{An(r)}catch(o){n=o}}),i.__H=void 0,n&&Ee.__e(n,i.__v))};var go=typeof requestAnimationFrame=="function";function hl(e){var n,i=function(){clearTimeout(r),go&&cancelAnimationFrame(n),setTimeout(e)},r=setTimeout(i,35);go&&(n=requestAnimationFrame(i))}function An(e){var n=xe,i=e.__c;typeof i=="function"&&(e.__c=void 0,i()),xe=n}function Ei(e){var n=xe;e.__c=e.__(),xe=n}function yo(e,n){return!e||e.length!==n.length||n.some(function(i,r){return i!==e[r]})}function vo(e,n){return typeof n=="function"?n(e):n}function tt(e){let n=e,i=new Set,r=()=>i.forEach(o=>o(n));return{get:()=>n,set(o){n={...n,...o},r()},replace(o){n=o,r()},subscribe(o){return i.add(o),()=>{i.delete(o)}}}}function ze(e){let[n,i]=m(e.get());return j(()=>{let r=e.subscribe(i);return i(e.get()),r},[e]),n}var _e=tt({credential:null,whoami:null,readonly:!1,features:{},loginError:null,booting:!0});function te(){return ze(_e)}var Ln="nodegx.admin.token",Pi="nodegx.admin.person";function ut(e){return e?e.kind==="session"?{"x-parse-session-token":e.value}:{authorization:"Bearer "+e.value}:{}}function gl(e){return e?(e.kind==="session"?"token=":"authToken=")+I(e.value):""}function ko(e){if(!e)return null;try{let n=JSON.parse(e);if(n&&(n.kind==="token"||n.kind==="session")&&typeof n.value=="string"&&n.value)return{kind:n.kind,value:n.value}}catch{}return e.charAt(0)==="{"?null:{kind:"token",value:e}}function wo(e){try{e.kind==="session"?(localStorage.setItem(Pi,JSON.stringify(e)),sessionStorage.removeItem(Ln)):sessionStorage.setItem(Ln,JSON.stringify(e))}catch{}}function Co(){try{sessionStorage.removeItem(Ln)}catch{}try{localStorage.removeItem(Pi)}catch{}}function bl(){try{let e=ko(sessionStorage.getItem(Ln));if(e)return e}catch{}try{let e=ko(localStorage.getItem(Pi));return e&&e.kind==="session"?e:null}catch{return null}}var Ft=class extends Error{constructor(n,i){super(n),this.status=i}};async function S(e,n,i,r){return(await Ii(e,n,i,r)).body}async function Ii(e,n,i,r){let{credential:o,whoami:s}=_e.get(),l={...ut(o),...r||{}};i!==void 0&&(l["content-type"]="application/json");let a=await fetch(n,{method:e,headers:l,body:i!==void 0?JSON.stringify(i):void 0}),c=await a.json().catch(()=>null);if(a.status===401)throw s&&Nn("Your sign-in was rejected. Sign in again."),new Ft("Unauthorized",401);if(a.status>=400){let u=c&&(c.error||c.message)||"HTTP "+a.status;throw new Ft(u,a.status)}return{body:c,headers:a.headers}}function I(e){return encodeURIComponent(String(e))}async function rn(e,n){_e.set({credential:e});let i=await S("GET","/_admin/whoami");n&&e&&wo(e),_e.set({whoami:i,readonly:!!i.readonly,features:i.features||{},loginError:null,booting:!1})}async function So(){let e=await S("GET","/_admin/whoami");return _e.set({whoami:e,readonly:!!e.readonly,features:e.features||{}}),e}function Nn(e){on();let{credential:n}=_e.get();n&&n.kind==="session"&&fetch("/logout",{method:"POST",headers:ut(n)}).catch(()=>{}),Co(),_e.set({credential:null,whoami:null,readonly:!1,features:{},loginError:e||null,booting:!1})}function yl(e){let n=/^#token=([^&]+)(?:&(.*))?$/.exec(e||""),i=!n&&/^#route=/.test(e||"")?(e||"").slice(1):null;if(!n&&i===null)return null;let r=null;if(n){r=n[1];try{r=decodeURIComponent(r)}catch{}}let o=null,s=n?n[2]:i;if(s){let l=new URLSearchParams(s).get("route")||"";/^\\/(?!\\/)[^#\\s]*$/.test(l)&&(o=l)}return{token:r,route:o}}async function To(){let e=yl(location.hash||""),n=null;if(e){try{history.replaceState(null,"",location.pathname+location.search)}catch{location.hash=""}e.token&&(n={kind:"token",value:e.token},wo(n)),e.route&&(location.hash="#"+e.route)}if(n||(n=bl()),n)try{await rn(n,!0);return}catch{Co()}_e.set({credential:null});try{await rn(null,!1)}catch{_e.set({booting:!1})}}async function xo(e,n){_e.set({loginError:null});try{return await rn({kind:"token",value:e},n),!0}catch(i){let r=i;return _e.set({credential:null,loginError:r.status===429?r.message:'That credential was rejected. It is the "adminToken" in the backend\u2019s secrets.json, or the value passed to --token.'}),!1}}async function Ro(e,n,i){_e.set({loginError:null,credential:null});try{let r=await S("POST","/_admin/login",{email:e,password:n});return await rn({kind:"session",value:r.sessionToken},i),!0}catch(r){let o=r;return _e.set({credential:null,loginError:o.message==="Unauthorized"?"That email and password were not accepted.":o.message}),!1}}async function Eo(e,n){let i=await S("POST","/_admin/setup",{email:e,password:n});await rn({kind:"session",value:i.sessionToken},!0)}var dt=tt({state:"off",collection:null}),In=null,Ai=null;function _o(e,n){on();let{credential:i,features:r}=_e.get();if(!e||!r.realtime)return;let o=new EventSource("/realtime?"+gl(i));In=o,dt.set({state:"connecting",collection:e}),o.addEventListener("connected",a=>{try{Ai=JSON.parse(a.data).clientId}catch{return}S("POST","/realtime/subscriptions",{clientId:Ai,subscriptions:[{collection:e}]}).then(()=>dt.set({state:"live"})).catch(()=>dt.set({state:"off"}))});let s=null,l=()=>{s&&clearTimeout(s),s=setTimeout(n,180)};o.addEventListener("change",l),o.addEventListener("resync",l),o.addEventListener("error",()=>dt.set({state:"reconnecting"}))}function on(){In&&(In.close(),In=null),Ai=null,dt.get().state!=="off"&&dt.set({state:"off",collection:null})}var Li="nodegx.admin.theme";function vl(){try{let e=localStorage.getItem(Li);return e==="light"||e==="dark"?e:"auto"}catch{return"auto"}}function kl(){try{return window.matchMedia&&window.matchMedia("(prefers-color-scheme: light)").matches?"light":"dark"}catch{return"dark"}}function wl(e){return e==="auto"?kl():e}var Dn=tt({preference:"auto",theme:"dark"});function Ni(e){let n=wl(e);document.documentElement.setAttribute("data-theme",n),Dn.set({preference:e,theme:n})}function Ao(){Ni(vl());try{window.matchMedia("(prefers-color-scheme: light)").addEventListener("change",()=>{Dn.get().preference==="auto"&&Ni("auto")})}catch{}}function Cl(e){try{e==="auto"?localStorage.removeItem(Li):localStorage.setItem(Li,e)}catch{}Ni(e)}function Po(){Cl(Dn.get().theme==="dark"?"light":"dark")}function Io(){return ze(Dn)}var Sl=[{label:"Data",entries:[{id:"collections",label:"Collections",feature:"collections"},{id:"schema",label:"Schema",feature:"schema"}]},{label:"People",entries:[{id:"users",label:"Users",feature:"users"},{id:"roles",label:"Roles",feature:"roles"},{id:"signin",label:"Sign-in",feature:"auth"}]},{label:"Access",entries:[{id:"permissions",label:"Permissions",feature:"permissions"},{id:"keys",label:"API keys",feature:"apiKeys"}]},{label:"Automation",entries:[{id:"triggers",label:"Triggers",feature:"triggers"},{id:"workflows",label:"Workflows",feature:"workflows"},{id:"runs",label:"Runs",feature:"executions"}]},{label:"Storage",entries:[{id:"files",label:"Files",feature:"files"}]},{label:"Settings",entries:[{id:"email",label:"Email",feature:"email"},{id:"backups",label:"Backups",feature:"backups"},{id:"secrets",label:"Secrets",feature:"secrets"},{id:"search",label:"Search",feature:"search"},{id:"server",label:"Server",feature:"ops"}]},{label:"Activity",entries:[{id:"audit",label:"Activity",feature:"ops"}]}];function Lo(e){return Sl.map(n=>({label:n.label,entries:n.entries.filter(i=>!!e[i.feature])})).filter(n=>n.entries.length>0)}var Tl={executions:"runs"};function xl(e){let n=(e||"").replace(/^#\\/?/,"");if(!n||n.startsWith("token="))return{view:"",params:[],query:{}};let i=n.indexOf("?"),r=i<0?n:n.slice(0,i),o={};i>=0&&new URLSearchParams(n.slice(i+1)).forEach((a,c)=>{o[c]=a});let s=r.split("/").map(a=>{try{return decodeURIComponent(a)}catch{return a}});return{view:Tl[s[0]]||s[0],params:s.slice(1).filter(a=>a!==""),query:o}}function No(e){let n=(e||"").replace(/^#\\/?/,""),i=n.indexOf("?");return i<0?n:n.slice(0,i)}function ne(e,...n){let i=n.filter(r=>r!=null&&r!=="");return"#/"+[e,...i].map(encodeURIComponent).join("/")}function Fn(e,n,i){let r=ne(e,...n),o=new URLSearchParams;for(let l of Object.keys(i)){let a=i[l];a!=null&&a!==""&&o.set(l,a)}let s=o.toString();return s?r+"?"+s:r}function Z(e,...n){let i=ne(e,...n);location.hash!==i&&(location.hash=i)}function sn(e,...n){let i=ne(e,...n);try{history.replaceState(null,"",location.pathname+location.search+i)}catch{location.hash=i}window.dispatchEvent(new HashChangeEvent("hashchange"))}function On(){return xl(location.hash)}function Do(){let[e,n]=m(On);return j(()=>{let i=()=>n(On());return window.addEventListener("hashchange",i),()=>window.removeEventListener("hashchange",i)},[]),e}var Rl=0;function t(e,n,i,r,o,s){n||(n={});var l,a,c=n;if("ref"in c)for(a in c={},n)a=="ref"?l=n[a]:c[a]=n[a];var u={type:e,props:c,key:i,ref:l,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:--Rl,__i:-1,__u:0,__source:o,__self:s};if(typeof e=="function"&&(l=e.defaultProps))for(a in l)c[a]===void 0&&(c[a]=l[a]);return be.vnode&&be.vnode(u),u}function K({kind:e,children:n,title:i}){return t("span",{class:"chip"+(e?" "+e:""),title:i,children:n})}function J({kind:e,children:n,style:i}){return t("div",{class:"notice"+(e?" "+e:""),style:i,children:n})}function P({kind:e,tiny:n,children:i,class:r,className:o,...s}){let l=["btn",e||"",n?"tiny":"",String(r||o||"")].filter(Boolean).join(" ");return t("button",{type:"button",...s,class:l,children:i})}function M(e){let{readonly:n}=te();return n?t(P,{...e,disabled:!0,title:"Your access to this backend is read-only, so this cannot make changes."}):t(P,{...e})}function ce({title:e,subtitle:n,children:i}){return t("div",{children:[t("h1",{children:e}),n?t("p",{class:"sub",children:n}):null,i]})}function V({children:e,style:n,class:i}){return t("div",{class:"row"+(i?" "+i:""),style:n,children:e})}function pe({children:e,style:n,id:i,class:r}){return t("div",{class:"card"+(r?" "+r:""),style:n,id:i,children:e})}function me(){return t("span",{style:"flex:1"})}function G({h:e=12}){return t("div",{style:"height:"+e+"px"})}function fe({children:e,style:n}){return t("p",{class:"sub",style:n,children:e})}function H({children:e}){return t("span",{class:"hint",children:e})}function kt({children:e}){return t("b",{class:"hi",children:e})}function $({label:e,children:n,style:i}){return t("label",{class:"field",style:i,children:[e,n]})}function Fe({checked:e,onChange:n,children:i,disabled:r,style:o}){return t("label",{class:"check",style:o,children:[t("input",{type:"checkbox",checked:e,disabled:r,onChange:s=>n(s.target.checked)}),i]})}function re({checked:e,onChange:n,children:i,disabled:r,id:o,label:s}){return t("label",{class:"switch",children:[t("input",{type:"checkbox",id:o,"aria-label":s,checked:e,disabled:r,onChange:l=>n(l.target.checked)}),t("span",{class:"switch-track","aria-hidden":"true"}),i]})}function ye({columns:e,rows:n,renderRow:i,empty:r,class:o,head:s}){return n.length?t("div",{class:"scroller",children:t("table",{class:o,children:[t("thead",{children:s||t("tr",{children:e.map((l,a)=>t("th",{children:l},a))})}),t("tbody",{children:n.map((l,a)=>i(l,a))})]})}):t("div",{class:"scroller",children:r||t("div",{class:"empty",children:"Nothing here yet."})})}function Je({children:e}){return t("td",{class:"actions",children:e})}function He({label:e,children:n,open:i}){let[r,o]=m(!!i);return t("div",{class:"disclose",children:[t("div",{class:"disclose-head",role:"button",tabIndex:0,"aria-expanded":r,onClick:()=>o(!r),onKeyDown:s=>{(s.key==="Enter"||s.key===" ")&&(s.preventDefault(),o(!r))},children:[t("span",{class:"jx-twist",children:r?"\u25BE":"\u25B8"}),t("span",{children:e})]}),r?t("div",{class:"disclose-body",children:n}):null]})}function El(e){return e==null?"jx-null":typeof e=="number"?"jx-num":typeof e=="boolean"?"jx-bool":"jx-str"}function _l(e){return e===null?"null":e===void 0?"undefined":typeof e=="string"?'"'+e+'"':String(e)}function an({value:e,name:n,depth:i=0,openTo:r=1}){let o=Array.isArray(e),s=e!==null&&typeof e=="object"&&!o,[l,a]=m(i<r);if(!o&&!s)return t("div",{class:"jx-node",children:t("div",{class:"jx-line",children:[t("span",{class:"jx-twist",children:" "}),n!=null?t("span",{class:"jx-key",children:[n,":"]}):null,t("span",{class:"jx-val "+El(e),children:_l(e)})]})});let c=o?e.map((p,d)=>({k:String(d),v:p})):Object.keys(e).map(p=>({k:p,v:e[p]})),u=o?"["+c.length+(c.length===1?" item]":" items]"):"{"+c.length+(c.length===1?" field}":" fields}");return t("div",{class:"jx-node",children:[t("div",{class:"jx-line",onClick:()=>a(!l),children:[t("span",{class:"jx-twist",children:l?"\u25BE":"\u25B8"}),n!=null?t("span",{class:"jx-key",children:[n,":"]}):null,t("span",{class:"jx-meta",children:u})]}),l?t("div",{class:"jx-children",children:c.map(p=>t(an,{value:p.v,name:p.k,depth:i+1,openTo:r},p.k))}):null]})}async function Ye(e,n="Copied"){try{if(navigator.clipboard&&navigator.clipboard.writeText)return await navigator.clipboard.writeText(e),!0}catch{}return!1}var wt=tt({items:[]}),Al=1;function ge(e,n){let i=Al++,r=()=>{let o=wt.get().items;o.some(s=>s.id===i)&&(wt.set({items:o.filter(s=>s.id!==i)}),n&&n())};return wt.set({items:[...wt.get().items,{id:i,render:()=>e(r),onClose:n}]}),{close:r}}function Di(){let{items:e}=ze(wt);return t("div",{id:"modal-root",children:e.map(n=>t(Pl,{modal:n},n.id))})}function Pl({modal:e}){let n=()=>wt.set({items:wt.get().items.filter(i=>i.id!==e.id)});return j(()=>{let i=r=>{r.key==="Escape"&&(r.preventDefault(),n(),e.onClose&&e.onClose())};return window.addEventListener("keydown",i),()=>window.removeEventListener("keydown",i)},[e.id]),t("div",{class:"backdrop",onClick:i=>{i.target===i.currentTarget&&(n(),e.onClose&&e.onClose())},children:e.render(n)})}function ve({title:e,wide:n,children:i,actions:r,autoFocus:o=!0}){let s=ae(null);return j(()=>{if(!o||!s.current)return;let l=s.current.querySelector("input:not([type=checkbox]), select, textarea, button.btn");l&&setTimeout(()=>l.focus(),0)},[]),t("div",{class:"modal"+(n?" wide":""),role:"dialog","aria-modal":"true","aria-label":e,ref:s,children:[t("h3",{children:e}),i,r?t("div",{class:"foot",children:r}):null]})}function Ge(e,n,i,r,o="Delete"){ge(s=>t(Il,{title:e,warning:n,expected:i,verb:o,close:s,onConfirm:r}))}function Il(e){let[n,i]=m(""),r=n===e.expected;return t(ve,{title:e.title,actions:t(W,{children:[t(P,{onClick:e.close,children:"Cancel"}),t(P,{kind:"danger",disabled:!r,onClick:()=>{r&&(e.close(),e.onConfirm())},children:e.verb})]}),children:[t("div",{class:"notice bad",children:e.warning}),t("p",{class:"sub",children:['Type "',e.expected,'" to confirm. This cannot be undone.']}),t("input",{type:"text",placeholder:e.expected,"aria-label":"Type "+e.expected+" to confirm",style:"width:100%;margin-top:8px",value:n,onInput:o=>i(o.target.value)})]})}function we(e,n,i,r="Delete"){ge(o=>t(ve,{title:e,autoFocus:!1,actions:t(W,{children:[t(P,{onClick:o,children:"Cancel"}),t(P,{kind:"danger",onClick:()=>{o(),i()},children:r})]}),children:t("div",{class:"notice bad",children:n})}))}var ln=tt({items:[]}),Ll=1;function O(e,n=""){let i=Ll++;ln.set({items:[...ln.get().items,{id:i,message:e,kind:n}]}),setTimeout(()=>Oo(i),n==="bad"?12e3:4500)}function D(e){let n=e;O(n&&n.message?n.message:String(e),"bad")}function Oo(e){ln.set({items:ln.get().items.filter(n=>n.id!==e)})}function Oi(){let{items:e}=ze(ln);return t("div",{id:"toasts","aria-live":"polite",children:e.map(n=>t("div",{class:"toast"+(n.kind?" "+n.kind:""),onClick:()=>Oo(n.id),children:n.message},n.id))})}function Ce(e){let[n,i]=m(""),[r,o]=m(!1),[s,l]=m([]),[a,c]=m(!1),[u,p]=m(0),d=ae(null),f=ae(0),b=ae(null),g=ae(null),k=ae(!1),y=v=>{let R=++f.current;c(!0),e.fetch(v).then(A=>{R===f.current&&(g.current=v,l(A),p(0),c(!1),k.current&&(k.current=!1,C(A,v)))}).catch(()=>{R===f.current&&(l([]),c(!1))})};j(()=>{if(r)return d.current&&clearTimeout(d.current),d.current=setTimeout(()=>y(n),150),()=>{d.current&&clearTimeout(d.current)}},[n,r]),j(()=>{e.autoFocus&&b.current&&b.current.focus()},[]);let T=s.some(v=>e.label(v).toLowerCase()===n.trim().toLowerCase()),w=v=>!!e.onCreate&&v!==""&&(!e.createWhen||e.createWhen(v)),x=w(n.trim())&&!T&&!a,E=s.length+(x?1:0),C=(v,R)=>{if(v.length)e.onPick(v[0]);else if(e.onCreate&&w(R.trim()))e.onCreate(R.trim());else return;o(!1),i("")},_=v=>{v<s.length?(e.onPick(s[v]),o(!1),i("")):x&&e.onCreate&&(e.onCreate(n.trim()),o(!1),i(""))},N=v=>{if(!r&&(v.key==="ArrowDown"||v.key==="Enter")){o(!0);return}r&&(v.key==="ArrowDown"?(v.preventDefault(),p(E?(u+1)%E:0)):v.key==="ArrowUp"?(v.preventDefault(),p(E?(u-1+E)%E:0)):v.key==="Enter"?(v.preventDefault(),a||g.current!==n?k.current=!0:E&&_(u)):v.key==="Escape"&&(v.preventDefault(),k.current=!1,o(!1)))};return e.value&&!r?t("div",{class:"picker picked",id:e.id,children:[t("span",{class:"picker-value",title:e.detail?e.detail(e.value):void 0,children:e.label(e.value)}),e.disabled?null:t("button",{type:"button",class:"picker-clear","aria-label":"Clear",onClick:()=>e.onPick(null),children:"\u2715"})]}):t("div",{class:"picker",id:e.id,children:[t("input",{ref:b,type:"search",role:"combobox","aria-expanded":r,"aria-autocomplete":"list",placeholder:e.placeholder||"Type to search\u2026",disabled:e.disabled,value:n,onFocus:()=>{e.openOnFocus!==!1&&o(!0)},onBlur:()=>setTimeout(()=>o(!1),120),onInput:v=>{i(v.currentTarget.value),o(!0)},onKeyDown:N}),r?t("div",{class:"picker-list",role:"listbox",children:[a&&!s.length?t("div",{class:"picker-empty",children:"Searching\u2026"}):null,!a&&!E?t("div",{class:"picker-empty",children:e.emptyText||"No match."}):null,s.map((v,R)=>t("div",{role:"option","aria-selected":R===u,class:"picker-row"+(R===u?" active":""),onMouseDown:A=>{A.preventDefault(),_(R)},onMouseEnter:()=>p(R),children:[t("span",{class:"picker-label",children:e.label(v)}),e.detail?t("span",{class:"picker-detail",children:e.detail(v)}):null]},e.keyOf(v))),x?t("div",{role:"option","aria-selected":u===s.length,class:"picker-row picker-create"+(u===s.length?" active":""),onMouseDown:v=>{v.preventDefault(),_(s.length)},onMouseEnter:()=>p(s.length),children:e.createLabel?e.createLabel(n.trim()):'Create "'+n.trim()+'"\u2026'}):null]}):null]})}function Ie(e){let[n,i]=m(""),[r,o]=m(null),s=a=>{let c=a.trim();if(c){if(e.validate){let u=e.validate(c);if(u){o(u);return}}if(e.normalise&&(c=e.normalise(c)),o(null),e.items.indexOf(c)!==-1){i("");return}e.onChange([...e.items,c]),i("")}},l=a=>e.onChange(e.items.filter(c=>c!==a));return t("div",{class:"chips",id:e.id,children:[t("div",{class:"chips-set",children:[e.items.map(a=>t("span",{class:"chip accent",children:[e.label?e.label(a):a,e.disabled?null:t("button",{type:"button",class:"chip-x","aria-label":"Remove "+(e.label?e.label(a):a),onClick:()=>l(a),children:"\u2715"})]},a)),e.items.length?null:t("span",{class:"hint",children:"None yet."})]}),e.disabled?null:e.suggest?t(Ce,{fetch:a=>e.suggest(a).then(c=>c.filter(u=>e.items.indexOf(u)===-1)),label:a=>e.label?e.label(a):a,keyOf:a=>a,value:null,onPick:a=>{a&&s(a)},placeholder:e.placeholder||e.addLabel||"Add\u2026"}):t("div",{class:"chips-add",children:[t("input",{type:"text",placeholder:e.placeholder||"Add\u2026","aria-label":e.addLabel||"Add",value:n,onInput:a=>{i(a.currentTarget.value),r&&o(null)},onKeyDown:a=>{a.key==="Enter"||a.key===","?(a.preventDefault(),s(n)):a.key==="Backspace"&&!n&&e.items.length&&l(e.items[e.items.length-1])}}),t("button",{type:"button",class:"btn tiny",onClick:()=>s(n),children:e.addLabel||"Add"}),r?t("span",{class:"chips-problem",children:r}):null]})]})}function Xe(e){let n=(o,s)=>e.onChange(e.rows.map((l,a)=>a===o?s:l)),i=o=>e.onChange(e.rows.filter((s,l)=>l!==o)),r=e.rows.length>(e.min||0);return t("div",{class:"list-editor",id:e.id,children:[e.head,e.rows.map((o,s)=>t("div",{class:"list-row",children:[e.renderRow(o,l=>n(s,l),s),e.disabled?null:t("button",{type:"button",class:"btn tiny list-x",title:e.removeTitle||"Remove this row","aria-label":e.removeTitle||"Remove this row",disabled:!r,onClick:()=>i(s),children:"\u2715"})]},s)),e.disabled?null:t("button",{type:"button",class:"btn tiny list-add",onClick:()=>e.onChange([...e.rows,e.blank()]),children:e.addLabel||"+ Add"})]})}function pt(e){return e.indexOf("role:")===0}function Fi(e){return pt(e)?e.slice(5):e}function Mi(e){if(e==null||e==="")return{mode:"public"};let n=typeof e=="object"&&!Array.isArray(e)?e:{},i=[],r=n["*"]||{};i.push({key:"*",read:r.read===!0,write:r.write===!0});let o=Object.keys(n).filter(s=>s!=="*");return o.filter(pt).concat(o.filter(s=>!pt(s))).forEach(s=>{let l=n[s]||{};i.push({key:s,read:l.read===!0,write:l.write===!0})}),{mode:"rows",rows:i}}function Fo(e){if(e.mode==="public")return null;let n={};return e.rows.forEach(i=>{if(!i.key||!i.read&&!i.write)return;let r={};i.read&&(r.read=!0),i.write&&(r.write=!0),n[i.key]=r}),n}function Bi(e){return e.mode==="rows"&&!e.rows.some(n=>n.read||n.write)}function Mo(){return{mode:"rows",rows:[{key:"*",read:!0,write:!0}]}}function Hi(e,n){return e.mode!=="rows"||!n||e.rows.some(i=>i.key===n)?e:{mode:"rows",rows:e.rows.concat([{key:n,read:!0,write:!1}])}}function ji(e,n,i){return e.mode!=="rows"?e:{mode:"rows",rows:e.rows.map(r=>r.key===n?{...r,...i}:r)}}function Bo(e,n){return e.mode!=="rows"||n==="*"?e:{mode:"rows",rows:e.rows.filter(i=>i.key!==n)}}function Ho(e,n){let i=Mi(e);return i.mode==="public"?"Everyone":Bi(i)?"No one":i.rows.filter(r=>r.read||r.write).map(r=>(r.key==="*"?"everyone":pt(r.key)?Fi(r.key):n?n(r.key):"a person")+" ("+(r.read&&r.write?"read, write":r.read?"read":"write")+")").join(", ")}function ke(e){return e==null?"":typeof e=="object"?JSON.stringify(e):String(e)}function he(e){if(!e)return"\u2014";let n=new Date(typeof e=="number"?e:String(e));return isNaN(n.getTime())?String(e):n.toLocaleString()}function je(e,n=Date.now()){if(!e)return"never";let i=new Date(typeof e=="number"?e:String(e));if(isNaN(i.getTime()))return String(e);let r=Math.max(0,Math.round((n-i.getTime())/1e3));if(r<45)return"just now";let o=Math.round(r/60);if(o<60)return o+" min ago";let s=Math.round(o/60);if(s<24)return s+(s===1?" hour ago":" hours ago");let l=Math.round(s/24);return l<31?l+(l===1?" day ago":" days ago"):i.toLocaleDateString()}function nt(e){return typeof e=="string"&&e.length>12?e.slice(0,8)+"\u2026":String(e)}var jo=/^[a-zA-Z][a-zA-Z0-9_]*$/,Nl=["objectId","createdAt","updatedAt","ACL"],Dl=["ABORT","ACTION","ADD","AFTER","ALL","ALTER","ANALYZE","AND","AS","ASC","ATTACH","AUTOINCREMENT","BEFORE","BEGIN","BETWEEN","BY","CASCADE","CASE","CAST","CHECK","COLLATE","COLUMN","COMMIT","CONFLICT","CONSTRAINT","CREATE","CROSS","CURRENT_DATE","CURRENT_TIME","CURRENT_TIMESTAMP","DATABASE","DEFAULT","DEFERRABLE","DEFERRED","DELETE","DESC","DETACH","DISTINCT","DROP","EACH","ELSE","END","ESCAPE","EXCEPT","EXCLUSIVE","EXISTS","EXPLAIN","FAIL","FOR","FOREIGN","FROM","FULL","GLOB","GROUP","HAVING","IF","IGNORE","IMMEDIATE","IN","INDEX","INDEXED","INITIALLY","INNER","INSERT","INSTEAD","INTERSECT","INTO","IS","ISNULL","JOIN","KEY","LEFT","LIKE","LIMIT","MATCH","NATURAL","NO","NOT","NOTNULL","NULL","OF","OFFSET","ON","OR","ORDER","OUTER","PLAN","PRAGMA","PRIMARY","QUERY","RAISE","RECURSIVE","REFERENCES","REGEXP","REINDEX","RELEASE","RENAME","REPLACE","RESTRICT","RIGHT","ROLLBACK","ROW","SAVEPOINT","SELECT","SET","TABLE","TEMP","TEMPORARY","THEN","TO","TRANSACTION","TRIGGER","UNION","UNIQUE","UPDATE","USING","VACUUM","VALUES","VIEW","VIRTUAL","WHEN","WHERE","WITH","WITHOUT"];function Wo(e){return Dl.indexOf(e.toUpperCase())!==-1}function Wi(e,n){return e?jo.test(e)?e.length>64?"A collection name is at most 64 characters.":Wo(e)?'"'+e+'" is a word the database keeps for itself. Try '+e+"s or My"+e+".":n.indexOf(e)!==-1?"There is already a collection called "+e+".":null:"A collection name starts with a letter, then letters, digits or _ only.":"The collection needs a name."}function Ue(e){return e==="objectId"||e==="createdAt"||e==="updatedAt"}function Uo(e,n,i){return Ue(n)||e==="_User"&&!!i&&Object.prototype.hasOwnProperty.call(i,n)}function Mt(e,n,i){return e?jo.test(e)?Nl.indexOf(e)!==-1?'"'+e+'" is reserved by the backend.':e.length>64?'"'+e+'" is too long: at most 64 characters.':Wo(e)?'"'+e+'" is a word the database keeps for itself. Try '+e+"Value or the"+e.charAt(0).toUpperCase()+e.slice(1)+".":n&&n.indexOf(e)!==-1?'There is already a field called "'+e+'".':null:'"'+e+'": start with a letter, then letters, digits or _ only.':i+" needs a name."}function it(e){if(e&&typeof e=="object"&&!Array.isArray(e)){let n=e;if(n.__type==="Date")return n.iso;if(n.__type==="Pointer"||n.__type==="Object")return n.objectId}return e}function Ui(e,n){return n==null?n:e.type==="Date"?{__type:"Date",iso:n}:e.type==="Pointer"?{__type:"Pointer",className:e.targetClass,objectId:n}:n}function mt(e){return e<1024?e+" B":e<1024*1024?(e/1024).toFixed(1)+" KB":(e/1024/1024).toFixed(1)+" MB"}function rt(e){let n=/^[0-9a-f]{8,}[-_](.+)$/i.exec(e);return n?n[1]:e}function Mn(e,n){if(n==="ACL")return Ho(e);let i=it(e);if(i==null||i==="")return"";if(n==="Boolean")return i?"\u2713":"\u2717";if(n==="Date")return he(i);if(n==="Pointer")return"\u2192 "+nt(i);if(n==="File"&&i&&typeof i=="object")return"\u25A4 "+rt(String(i.name||""));if(n==="GeoPoint"&&i&&typeof i=="object"){let r=i;return typeof r.latitude=="number"?r.latitude.toFixed(4)+", "+Number(r.longitude).toFixed(4):""}return Array.isArray(i)?i.map(r=>r&&typeof r=="object"?ke(r):String(r)).join(", "):i&&typeof i=="object"?Object.keys(i).map(r=>r+": "+ke(i[r])).join(" \xB7 "):ke(i)}function Ko(e){return typeof e=="number"?"Number":typeof e=="boolean"?"Boolean":Array.isArray(e)?"Array":e&&typeof e=="object"?"Object":"String"}function Bt(e){let n=new Date(String(e));if(isNaN(n.getTime()))return"";let i=r=>(r<10?"0":"")+r;return n.getFullYear()+"-"+i(n.getMonth()+1)+"-"+i(n.getDate())+"T"+i(n.getHours())+":"+i(n.getMinutes())}function Bn(e){let n=typeof e.username=="string"&&e.username?e.username:"",i=typeof e.email=="string"&&e.email?e.email:"";return n&&i?n+" \xB7 "+i:n||i||"(no username)"}var Vo=["","running","success","error"];function cn(e){return e==="success"?"ok":e==="error"||e==="timed out"?"bad":"warn"}function Ki(e){let n=e||{},i=n.metadata||{};return i.engineStatus==="cancelled"||i.cancelled===!0?"cancelled":i.timedOut===!0?"timed out":n.status||""}function Go(e){return e==="success"?"ok":e==="error"?"bad":e==="skipped"?"":"warn"}function Vi(e){if(Array.isArray(e))return e;let n=e||{};return n.executions||n.results||[]}function qo(e){let n=e||{},i=n.steps||[],r=[],o={};for(let a=0;a<i.length;a++){let c=i[a]||{};if(c.status!=="error")continue;let p=(c.outputData||{}).detail,d=p&&typeof p=="object"&&!Array.isArray(p)?p:null,f={index:a,step:c.nodeName||c.nodeId||"step "+(a+1),type:c.nodeType||"",message:c.errorMessage||"",detail:d},b=f.step+" "+f.message+" "+JSON.stringify(f.detail);o[b]||(o[b]=!0,r.push(f))}let s=n.modelCost&&typeof n.modelCost=="object"?n.modelCost:null,l=n.metadata||{};return{id:n.id||"",status:n.status||"",workflow:n.workflowName||n.workflowId||"",trigger:n.triggerType||"",triggerSource:l.triggerSource||"",startedAt:n.startedAt,durationMs:n.durationMs,errorMessage:n.errorMessage||"",costLine:s&&s.line?String(s.line):"",capped:!!l.recordCapped,stepCount:i.length,failures:r}}function dn(e){let n=e==null?"":typeof e=="object"?JSON.stringify(e):String(e);return/[",\\n]/.test(n)?'"'+n.replace(/"/g,'""')+'"':n}function $o(e){let n=[],i=[],r="",o=!1;for(let s=0;s<e.length;s++){let l=e[s];o?l==='"'&&e[s+1]==='"'?(r+='"',s++):l==='"'?o=!1:r+=l:l==='"'?o=!0:l===","?(i.push(r),r=""):l===\`
65034
+ module2.exports = `"use strict";(()=>{var En,be,Yr,sl,kt,qr,Xr,Zr,Qr,Ci,ki,vi,al,tn={},eo=[],ll=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i,_n=Array.isArray;function et(e,n){for(var i in n)e[i]=n[i];return e}function Si(e){e&&e.parentNode&&e.parentNode.removeChild(e)}function cl(e,n,i){var r,o,s,l={};for(s in n)s=="key"?r=n[s]:s=="ref"?o=n[s]:l[s]=n[s];if(arguments.length>2&&(l.children=arguments.length>3?En.call(arguments,2):i),typeof e=="function"&&e.defaultProps!=null)for(s in e.defaultProps)l[s]===void 0&&(l[s]=e.defaultProps[s]);return Tn(e,l,r,o,null)}function Tn(e,n,i,r,o){var s={type:e,props:n,key:i,ref:r,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:o??++Yr,__i:-1,__u:0};return o==null&&be.vnode!=null&&be.vnode(s),s}function W(e){return e.children}function xn(e,n){this.props=e,this.context=n}function Dt(e,n){if(n==null)return e.__?Dt(e.__,e.__i+1):null;for(var i;n<e.__k.length;n++)if((i=e.__k[n])!=null&&i.__e!=null)return i.__e;return typeof e.type=="function"?Dt(e):null}function to(e){var n,i;if((e=e.__)!=null&&e.__c!=null){for(e.__e=e.__c.base=null,n=0;n<e.__k.length;n++)if((i=e.__k[n])!=null&&i.__e!=null){e.__e=e.__c.base=i.__e;break}return to(e)}}function $r(e){(!e.__d&&(e.__d=!0)&&kt.push(e)&&!Rn.__r++||qr!=be.debounceRendering)&&((qr=be.debounceRendering)||Xr)(Rn)}function Rn(){for(var e,n,i,r,o,s,l,a=1;kt.length;)kt.length>a&&kt.sort(Zr),e=kt.shift(),a=kt.length,e.__d&&(i=void 0,r=void 0,o=(r=(n=e).__v).__e,s=[],l=[],n.__P&&((i=et({},r)).__v=r.__v+1,be.vnode&&be.vnode(i),Ti(n.__P,i,r,n.__n,n.__P.namespaceURI,32&r.__u?[o]:null,s,o??Dt(r),!!(32&r.__u),l),i.__v=r.__v,i.__.__k[i.__i]=i,ro(s,i,l),r.__e=r.__=null,i.__e!=o&&to(i)));Rn.__r=0}function no(e,n,i,r,o,s,l,a,c,u,p){var d,f,b,g,v,y,T,w=r&&r.__k||eo,x=n.length;for(c=dl(i,n,w,c,x),d=0;d<x;d++)(b=i.__k[d])!=null&&(f=b.__i==-1?tn:w[b.__i]||tn,b.__i=d,y=Ti(e,b,f,o,s,l,a,c,u,p),g=b.__e,b.ref&&f.ref!=b.ref&&(f.ref&&xi(f.ref,null,b),p.push(b.ref,b.__c||g,b)),v==null&&g!=null&&(v=g),(T=!!(4&b.__u))||f.__k===b.__k?c=io(b,c,e,T):typeof b.type=="function"&&y!==void 0?c=y:g&&(c=g.nextSibling),b.__u&=-7);return i.__e=v,c}function dl(e,n,i,r,o){var s,l,a,c,u,p=i.length,d=p,f=0;for(e.__k=new Array(o),s=0;s<o;s++)(l=n[s])!=null&&typeof l!="boolean"&&typeof l!="function"?(typeof l=="string"||typeof l=="number"||typeof l=="bigint"||l.constructor==String?l=e.__k[s]=Tn(null,l,null,null,null):_n(l)?l=e.__k[s]=Tn(W,{children:l},null,null,null):l.constructor===void 0&&l.__b>0?l=e.__k[s]=Tn(l.type,l.props,l.key,l.ref?l.ref:null,l.__v):e.__k[s]=l,c=s+f,l.__=e,l.__b=e.__b+1,a=null,(u=l.__i=ul(l,i,c,d))!=-1&&(d--,(a=i[u])&&(a.__u|=2)),a==null||a.__v==null?(u==-1&&(o>p?f--:o<p&&f++),typeof l.type!="function"&&(l.__u|=4)):u!=c&&(u==c-1?f--:u==c+1?f++:(u>c?f--:f++,l.__u|=4))):e.__k[s]=null;if(d)for(s=0;s<p;s++)(a=i[s])!=null&&(2&a.__u)==0&&(a.__e==r&&(r=Dt(a)),so(a,a));return r}function io(e,n,i,r){var o,s;if(typeof e.type=="function"){for(o=e.__k,s=0;o&&s<o.length;s++)o[s]&&(o[s].__=e,n=io(o[s],n,i,r));return n}e.__e!=n&&(r&&(n&&e.type&&!n.parentNode&&(n=Dt(e)),i.insertBefore(e.__e,n||null)),n=e.__e);do n=n&&n.nextSibling;while(n!=null&&n.nodeType==8);return n}function ul(e,n,i,r){var o,s,l,a=e.key,c=e.type,u=n[i],p=u!=null&&(2&u.__u)==0;if(u===null&&a==null||p&&a==u.key&&c==u.type)return i;if(r>(p?1:0)){for(o=i-1,s=i+1;o>=0||s<n.length;)if((u=n[l=o>=0?o--:s++])!=null&&(2&u.__u)==0&&a==u.key&&c==u.type)return l}return-1}function zr(e,n,i){n[0]=="-"?e.setProperty(n,i??""):e[n]=i==null?"":typeof i!="number"||ll.test(n)?i:i+"px"}function Sn(e,n,i,r,o){var s,l;e:if(n=="style")if(typeof i=="string")e.style.cssText=i;else{if(typeof r=="string"&&(e.style.cssText=r=""),r)for(n in r)i&&n in i||zr(e.style,n,"");if(i)for(n in i)r&&i[n]==r[n]||zr(e.style,n,i[n])}else if(n[0]=="o"&&n[1]=="n")s=n!=(n=n.replace(Qr,"$1")),l=n.toLowerCase(),n=l in e||n=="onFocusOut"||n=="onFocusIn"?l.slice(2):n.slice(2),e.l||(e.l={}),e.l[n+s]=i,i?r?i.u=r.u:(i.u=Ci,e.addEventListener(n,s?vi:ki,s)):e.removeEventListener(n,s?vi:ki,s);else{if(o=="http://www.w3.org/2000/svg")n=n.replace(/xlink(H|:h)/,"h").replace(/sName$/,"s");else if(n!="width"&&n!="height"&&n!="href"&&n!="list"&&n!="form"&&n!="tabIndex"&&n!="download"&&n!="rowSpan"&&n!="colSpan"&&n!="role"&&n!="popover"&&n in e)try{e[n]=i??"";break e}catch{}typeof i=="function"||(i==null||i===!1&&n[4]!="-"?e.removeAttribute(n):e.setAttribute(n,n=="popover"&&i==1?"":i))}}function Jr(e){return function(n){if(this.l){var i=this.l[n.type+e];if(n.t==null)n.t=Ci++;else if(n.t<i.u)return;return i(be.event?be.event(n):n)}}}function Ti(e,n,i,r,o,s,l,a,c,u){var p,d,f,b,g,v,y,T,w,x,R,C,_,N,k,E,P,U=n.type;if(n.constructor!==void 0)return null;128&i.__u&&(c=!!(32&i.__u),s=[a=n.__e=i.__e]),(p=be.__b)&&p(n);e:if(typeof U=="function")try{if(T=n.props,w="prototype"in U&&U.prototype.render,x=(p=U.contextType)&&r[p.__c],R=p?x?x.props.value:p.__:r,i.__c?y=(d=n.__c=i.__c).__=d.__E:(w?n.__c=d=new U(T,R):(n.__c=d=new xn(T,R),d.constructor=U,d.render=ml),x&&x.sub(d),d.state||(d.state={}),d.__n=r,f=d.__d=!0,d.__h=[],d._sb=[]),w&&d.__s==null&&(d.__s=d.state),w&&U.getDerivedStateFromProps!=null&&(d.__s==d.state&&(d.__s=et({},d.__s)),et(d.__s,U.getDerivedStateFromProps(T,d.__s))),b=d.props,g=d.state,d.__v=n,f)w&&U.getDerivedStateFromProps==null&&d.componentWillMount!=null&&d.componentWillMount(),w&&d.componentDidMount!=null&&d.__h.push(d.componentDidMount);else{if(w&&U.getDerivedStateFromProps==null&&T!==b&&d.componentWillReceiveProps!=null&&d.componentWillReceiveProps(T,R),n.__v==i.__v||!d.__e&&d.shouldComponentUpdate!=null&&d.shouldComponentUpdate(T,d.__s,R)===!1){for(n.__v!=i.__v&&(d.props=T,d.state=d.__s,d.__d=!1),n.__e=i.__e,n.__k=i.__k,n.__k.some(function(X){X&&(X.__=n)}),C=0;C<d._sb.length;C++)d.__h.push(d._sb[C]);d._sb=[],d.__h.length&&l.push(d);break e}d.componentWillUpdate!=null&&d.componentWillUpdate(T,d.__s,R),w&&d.componentDidUpdate!=null&&d.__h.push(function(){d.componentDidUpdate(b,g,v)})}if(d.context=R,d.props=T,d.__P=e,d.__e=!1,_=be.__r,N=0,w){for(d.state=d.__s,d.__d=!1,_&&_(n),p=d.render(d.props,d.state,d.context),k=0;k<d._sb.length;k++)d.__h.push(d._sb[k]);d._sb=[]}else do d.__d=!1,_&&_(n),p=d.render(d.props,d.state,d.context),d.state=d.__s;while(d.__d&&++N<25);d.state=d.__s,d.getChildContext!=null&&(r=et(et({},r),d.getChildContext())),w&&!f&&d.getSnapshotBeforeUpdate!=null&&(v=d.getSnapshotBeforeUpdate(b,g)),E=p,p!=null&&p.type===W&&p.key==null&&(E=oo(p.props.children)),a=no(e,_n(E)?E:[E],n,i,r,o,s,l,a,c,u),d.base=n.__e,n.__u&=-161,d.__h.length&&l.push(d),y&&(d.__E=d.__=null)}catch(X){if(n.__v=null,c||s!=null)if(X.then){for(n.__u|=c?160:128;a&&a.nodeType==8&&a.nextSibling;)a=a.nextSibling;s[s.indexOf(a)]=null,n.__e=a}else{for(P=s.length;P--;)Si(s[P]);wi(n)}else n.__e=i.__e,n.__k=i.__k,X.then||wi(n);be.__e(X,n,i)}else s==null&&n.__v==i.__v?(n.__k=i.__k,n.__e=i.__e):a=n.__e=pl(i.__e,n,i,r,o,s,l,c,u);return(p=be.diffed)&&p(n),128&n.__u?void 0:a}function wi(e){e&&e.__c&&(e.__c.__e=!0),e&&e.__k&&e.__k.forEach(wi)}function ro(e,n,i){for(var r=0;r<i.length;r++)xi(i[r],i[++r],i[++r]);be.__c&&be.__c(n,e),e.some(function(o){try{e=o.__h,o.__h=[],e.some(function(s){s.call(o)})}catch(s){be.__e(s,o.__v)}})}function oo(e){return typeof e!="object"||e==null||e.__b&&e.__b>0?e:_n(e)?e.map(oo):et({},e)}function pl(e,n,i,r,o,s,l,a,c){var u,p,d,f,b,g,v,y=i.props||tn,T=n.props,w=n.type;if(w=="svg"?o="http://www.w3.org/2000/svg":w=="math"?o="http://www.w3.org/1998/Math/MathML":o||(o="http://www.w3.org/1999/xhtml"),s!=null){for(u=0;u<s.length;u++)if((b=s[u])&&"setAttribute"in b==!!w&&(w?b.localName==w:b.nodeType==3)){e=b,s[u]=null;break}}if(e==null){if(w==null)return document.createTextNode(T);e=document.createElementNS(o,w,T.is&&T),a&&(be.__m&&be.__m(n,s),a=!1),s=null}if(w==null)y===T||a&&e.data==T||(e.data=T);else{if(s=s&&En.call(e.childNodes),!a&&s!=null)for(y={},u=0;u<e.attributes.length;u++)y[(b=e.attributes[u]).name]=b.value;for(u in y)if(b=y[u],u!="children"){if(u=="dangerouslySetInnerHTML")d=b;else if(!(u in T)){if(u=="value"&&"defaultValue"in T||u=="checked"&&"defaultChecked"in T)continue;Sn(e,u,null,b,o)}}for(u in T)b=T[u],u=="children"?f=b:u=="dangerouslySetInnerHTML"?p=b:u=="value"?g=b:u=="checked"?v=b:a&&typeof b!="function"||y[u]===b||Sn(e,u,b,y[u],o);if(p)a||d&&(p.__html==d.__html||p.__html==e.innerHTML)||(e.innerHTML=p.__html),n.__k=[];else if(d&&(e.innerHTML=""),no(n.type=="template"?e.content:e,_n(f)?f:[f],n,i,r,w=="foreignObject"?"http://www.w3.org/1999/xhtml":o,s,l,s?s[0]:i.__k&&Dt(i,0),a,c),s!=null)for(u=s.length;u--;)Si(s[u]);a||(u="value",w=="progress"&&g==null?e.removeAttribute("value"):g!=null&&(g!==e[u]||w=="progress"&&!g||w=="option"&&g!=y[u])&&Sn(e,u,g,y[u],o),u="checked",v!=null&&v!=e[u]&&Sn(e,u,v,y[u],o))}return e}function xi(e,n,i){try{if(typeof e=="function"){var r=typeof e.__u=="function";r&&e.__u(),r&&n==null||(e.__u=e(n))}else e.current=n}catch(o){be.__e(o,i)}}function so(e,n,i){var r,o;if(be.unmount&&be.unmount(e),(r=e.ref)&&(r.current&&r.current!=e.__e||xi(r,null,n)),(r=e.__c)!=null){if(r.componentWillUnmount)try{r.componentWillUnmount()}catch(s){be.__e(s,n)}r.base=r.__P=null}if(r=e.__k)for(o=0;o<r.length;o++)r[o]&&so(r[o],n,i||typeof e.type!="function");i||Si(e.__e),e.__c=e.__=e.__e=void 0}function ml(e,n,i){return this.constructor(e,i)}function ao(e,n,i){var r,o,s,l;n==document&&(n=document.documentElement),be.__&&be.__(e,n),o=(r=typeof i=="function")?null:i&&i.__k||n.__k,s=[],l=[],Ti(n,e=(!r&&i||n).__k=cl(W,null,[e]),o||tn,tn,n.namespaceURI,!r&&i?[i]:o?null:n.firstChild?En.call(n.childNodes):null,s,!r&&i?i:o?o.__e:n.firstChild,r,l),ro(s,e,l)}En=eo.slice,be={__e:function(e,n,i,r){for(var o,s,l;n=n.__;)if((o=n.__c)&&!o.__)try{if((s=o.constructor)&&s.getDerivedStateFromError!=null&&(o.setState(s.getDerivedStateFromError(e)),l=o.__d),o.componentDidCatch!=null&&(o.componentDidCatch(e,r||{}),l=o.__d),l)return o.__E=o}catch(a){e=a}throw e}},Yr=0,sl=function(e){return e!=null&&e.constructor===void 0},xn.prototype.setState=function(e,n){var i;i=this.__s!=null&&this.__s!=this.state?this.__s:this.__s=et({},this.state),typeof e=="function"&&(e=e(et({},i),this.props)),e&&et(i,e),e!=null&&this.__v&&(n&&this._sb.push(n),$r(this))},xn.prototype.forceUpdate=function(e){this.__v&&(this.__e=!0,e&&this.__h.push(e),$r(this))},xn.prototype.render=W,kt=[],Xr=typeof Promise=="function"?Promise.prototype.then.bind(Promise.resolve()):setTimeout,Zr=function(e,n){return e.__v.__b-n.__v.__b},Rn.__r=0,Qr=/(PointerCapture)$|Capture$/i,Ci=0,ki=Jr(!1),vi=Jr(!0),al=0;var nn,xe,Ri,lo,Pn=0,bo=[],Ee=be,co=Ee.__b,uo=Ee.__r,po=Ee.diffed,mo=Ee.__c,fo=Ee.unmount,ho=Ee.__;function _i(e,n){Ee.__h&&Ee.__h(xe,e,Pn||n),Pn=0;var i=xe.__H||(xe.__H={__:[],__h:[]});return e>=i.__.length&&i.__.push({}),i.__[e]}function m(e){return Pn=1,fl(ko,e)}function fl(e,n,i){var r=_i(nn++,2);if(r.t=e,!r.__c&&(r.__=[i?i(n):ko(void 0,n),function(a){var c=r.__N?r.__N[0]:r.__[0],u=r.t(c,a);c!==u&&(r.__N=[u,r.__[1]],r.__c.setState({}))}],r.__c=xe,!xe.__f)){var o=function(a,c,u){if(!r.__c.__H)return!0;var p=r.__c.__H.__.filter(function(f){return!!f.__c});if(p.every(function(f){return!f.__N}))return!s||s.call(this,a,c,u);var d=r.__c.props!==a;return p.forEach(function(f){if(f.__N){var b=f.__[0];f.__=f.__N,f.__N=void 0,b!==f.__[0]&&(d=!0)}}),s&&s.call(this,a,c,u)||d};xe.__f=!0;var s=xe.shouldComponentUpdate,l=xe.componentWillUpdate;xe.componentWillUpdate=function(a,c,u){if(this.__e){var p=s;s=void 0,o(a,c,u),s=p}l&&l.call(this,a,c,u)},xe.shouldComponentUpdate=o}return r.__N||r.__}function j(e,n){var i=_i(nn++,3);!Ee.__s&&yo(i.__H,n)&&(i.__=e,i.u=n,xe.__H.__h.push(i))}function ae(e){return Pn=5,Ot(function(){return{current:e}},[])}function Ot(e,n){var i=_i(nn++,7);return yo(i.__H,n)&&(i.__=e(),i.__H=n,i.__h=e),i.__}function hl(){for(var e;e=bo.shift();)if(e.__P&&e.__H)try{e.__H.__h.forEach(An),e.__H.__h.forEach(Ei),e.__H.__h=[]}catch(n){e.__H.__h=[],Ee.__e(n,e.__v)}}Ee.__b=function(e){xe=null,co&&co(e)},Ee.__=function(e,n){e&&n.__k&&n.__k.__m&&(e.__m=n.__k.__m),ho&&ho(e,n)},Ee.__r=function(e){uo&&uo(e),nn=0;var n=(xe=e.__c).__H;n&&(Ri===xe?(n.__h=[],xe.__h=[],n.__.forEach(function(i){i.__N&&(i.__=i.__N),i.u=i.__N=void 0})):(n.__h.forEach(An),n.__h.forEach(Ei),n.__h=[],nn=0)),Ri=xe},Ee.diffed=function(e){po&&po(e);var n=e.__c;n&&n.__H&&(n.__H.__h.length&&(bo.push(n)!==1&&lo===Ee.requestAnimationFrame||((lo=Ee.requestAnimationFrame)||gl)(hl)),n.__H.__.forEach(function(i){i.u&&(i.__H=i.u),i.u=void 0})),Ri=xe=null},Ee.__c=function(e,n){n.some(function(i){try{i.__h.forEach(An),i.__h=i.__h.filter(function(r){return!r.__||Ei(r)})}catch(r){n.some(function(o){o.__h&&(o.__h=[])}),n=[],Ee.__e(r,i.__v)}}),mo&&mo(e,n)},Ee.unmount=function(e){fo&&fo(e);var n,i=e.__c;i&&i.__H&&(i.__H.__.forEach(function(r){try{An(r)}catch(o){n=o}}),i.__H=void 0,n&&Ee.__e(n,i.__v))};var go=typeof requestAnimationFrame=="function";function gl(e){var n,i=function(){clearTimeout(r),go&&cancelAnimationFrame(n),setTimeout(e)},r=setTimeout(i,35);go&&(n=requestAnimationFrame(i))}function An(e){var n=xe,i=e.__c;typeof i=="function"&&(e.__c=void 0,i()),xe=n}function Ei(e){var n=xe;e.__c=e.__(),xe=n}function yo(e,n){return!e||e.length!==n.length||n.some(function(i,r){return i!==e[r]})}function ko(e,n){return typeof n=="function"?n(e):n}function tt(e){let n=e,i=new Set,r=()=>i.forEach(o=>o(n));return{get:()=>n,set(o){n={...n,...o},r()},replace(o){n=o,r()},subscribe(o){return i.add(o),()=>{i.delete(o)}}}}function Je(e){let[n,i]=m(e.get());return j(()=>{let r=e.subscribe(i);return i(e.get()),r},[e]),n}var _e=tt({credential:null,whoami:null,readonly:!1,features:{},loginError:null,booting:!0});function te(){return Je(_e)}var Ln="nodegx.admin.token",Pi="nodegx.admin.person";function ut(e){return e?e.kind==="session"?{"x-parse-session-token":e.value}:{authorization:"Bearer "+e.value}:{}}function bl(e){return e?(e.kind==="session"?"token=":"authToken=")+I(e.value):""}function vo(e){if(!e)return null;try{let n=JSON.parse(e);if(n&&(n.kind==="token"||n.kind==="session")&&typeof n.value=="string"&&n.value)return{kind:n.kind,value:n.value}}catch{}return e.charAt(0)==="{"?null:{kind:"token",value:e}}function wo(e){try{e.kind==="session"?(localStorage.setItem(Pi,JSON.stringify(e)),sessionStorage.removeItem(Ln)):sessionStorage.setItem(Ln,JSON.stringify(e))}catch{}}function Co(){try{sessionStorage.removeItem(Ln)}catch{}try{localStorage.removeItem(Pi)}catch{}}function yl(){try{let e=vo(sessionStorage.getItem(Ln));if(e)return e}catch{}try{let e=vo(localStorage.getItem(Pi));return e&&e.kind==="session"?e:null}catch{return null}}var Ft=class extends Error{constructor(n,i){super(n),this.status=i}};async function S(e,n,i,r){return(await Ii(e,n,i,r)).body}async function Ii(e,n,i,r){let{credential:o,whoami:s}=_e.get(),l={...ut(o),...r||{}};i!==void 0&&(l["content-type"]="application/json");let a=await fetch(n,{method:e,headers:l,body:i!==void 0?JSON.stringify(i):void 0}),c=await a.json().catch(()=>null);if(a.status===401)throw s&&Nn("Your sign-in was rejected. Sign in again."),new Ft("Unauthorized",401);if(a.status>=400){let u=c&&(c.error||c.message)||"HTTP "+a.status;throw new Ft(u,a.status)}return{body:c,headers:a.headers}}function I(e){return encodeURIComponent(String(e))}async function rn(e,n){_e.set({credential:e});let i=await S("GET","/_admin/whoami");n&&e&&wo(e),_e.set({whoami:i,readonly:!!i.readonly,features:i.features||{},loginError:null,booting:!1})}async function So(){let e=await S("GET","/_admin/whoami");return _e.set({whoami:e,readonly:!!e.readonly,features:e.features||{}}),e}function Nn(e){on();let{credential:n}=_e.get();n&&n.kind==="session"&&fetch("/logout",{method:"POST",headers:ut(n)}).catch(()=>{}),Co(),_e.set({credential:null,whoami:null,readonly:!1,features:{},loginError:e||null,booting:!1})}function kl(e){let n=/^#token=([^&]+)(?:&(.*))?$/.exec(e||""),i=!n&&/^#route=/.test(e||"")?(e||"").slice(1):null;if(!n&&i===null)return null;let r=null;if(n){r=n[1];try{r=decodeURIComponent(r)}catch{}}let o=null,s=n?n[2]:i;if(s){let l=new URLSearchParams(s).get("route")||"";/^\\/(?!\\/)[^#\\s]*$/.test(l)&&(o=l)}return{token:r,route:o}}async function To(){let e=kl(location.hash||""),n=null;if(e){try{history.replaceState(null,"",location.pathname+location.search)}catch{location.hash=""}e.token&&(n={kind:"token",value:e.token},wo(n)),e.route&&(location.hash="#"+e.route)}if(n||(n=yl()),n)try{await rn(n,!0);return}catch{Co()}_e.set({credential:null});try{await rn(null,!1)}catch{_e.set({booting:!1})}}async function xo(e,n){_e.set({loginError:null});try{return await rn({kind:"token",value:e},n),!0}catch(i){let r=i;return _e.set({credential:null,loginError:r.status===429?r.message:'That credential was rejected. It is the "adminToken" in the backend\u2019s secrets.json, or the value passed to --token.'}),!1}}async function Ro(e,n,i){_e.set({loginError:null,credential:null});try{let r=await S("POST","/_admin/login",{email:e,password:n});return await rn({kind:"session",value:r.sessionToken},i),!0}catch(r){let o=r;return _e.set({credential:null,loginError:o.message==="Unauthorized"?"That email and password were not accepted.":o.message}),!1}}async function Eo(e,n){let i=await S("POST","/_admin/setup",{email:e,password:n});await rn({kind:"session",value:i.sessionToken},!0)}var dt=tt({state:"off",collection:null}),In=null,Ai=null;function _o(e,n){on();let{credential:i,features:r}=_e.get();if(!e||!r.realtime)return;let o=new EventSource("/realtime?"+bl(i));In=o,dt.set({state:"connecting",collection:e}),o.addEventListener("connected",a=>{try{Ai=JSON.parse(a.data).clientId}catch{return}S("POST","/realtime/subscriptions",{clientId:Ai,subscriptions:[{collection:e}]}).then(()=>dt.set({state:"live"})).catch(()=>dt.set({state:"off"}))});let s=null,l=()=>{s&&clearTimeout(s),s=setTimeout(n,180)};o.addEventListener("change",l),o.addEventListener("resync",l),o.addEventListener("error",()=>dt.set({state:"reconnecting"}))}function on(){In&&(In.close(),In=null),Ai=null,dt.get().state!=="off"&&dt.set({state:"off",collection:null})}var Li="nodegx.admin.theme";function vl(){try{let e=localStorage.getItem(Li);return e==="light"||e==="dark"?e:"auto"}catch{return"auto"}}function wl(){try{return window.matchMedia&&window.matchMedia("(prefers-color-scheme: light)").matches?"light":"dark"}catch{return"dark"}}function Cl(e){return e==="auto"?wl():e}var Dn=tt({preference:"auto",theme:"dark"});function Ni(e){let n=Cl(e);document.documentElement.setAttribute("data-theme",n),Dn.set({preference:e,theme:n})}function Ao(){Ni(vl());try{window.matchMedia("(prefers-color-scheme: light)").addEventListener("change",()=>{Dn.get().preference==="auto"&&Ni("auto")})}catch{}}function Sl(e){try{e==="auto"?localStorage.removeItem(Li):localStorage.setItem(Li,e)}catch{}Ni(e)}function Po(){Sl(Dn.get().theme==="dark"?"light":"dark")}function Io(){return Je(Dn)}var Tl=[{label:"Data",entries:[{id:"collections",label:"Collections",feature:"collections"},{id:"schema",label:"Schema",feature:"schema"}]},{label:"People",entries:[{id:"users",label:"Users",feature:"users"},{id:"roles",label:"Roles",feature:"roles"},{id:"signin",label:"Sign-in",feature:"auth"}]},{label:"Access",entries:[{id:"permissions",label:"Permissions",feature:"permissions"},{id:"keys",label:"API keys",feature:"apiKeys"}]},{label:"Automation",entries:[{id:"triggers",label:"Triggers",feature:"triggers"},{id:"workflows",label:"Workflows",feature:"workflows"},{id:"runs",label:"Runs",feature:"executions"}]},{label:"Storage",entries:[{id:"files",label:"Files",feature:"files"}]},{label:"Settings",entries:[{id:"email",label:"Email",feature:"email"},{id:"backups",label:"Backups",feature:"backups"},{id:"secrets",label:"Secrets",feature:"secrets"},{id:"search",label:"Search",feature:"search"},{id:"server",label:"Server",feature:"ops"}]},{label:"Activity",entries:[{id:"audit",label:"Activity",feature:"ops"}]}];function Lo(e){return Tl.map(n=>({label:n.label,entries:n.entries.filter(i=>!!e[i.feature])})).filter(n=>n.entries.length>0)}var xl={executions:"runs"};function Rl(e){let n=(e||"").replace(/^#\\/?/,"");if(!n||n.startsWith("token="))return{view:"",params:[],query:{}};let i=n.indexOf("?"),r=i<0?n:n.slice(0,i),o={};i>=0&&new URLSearchParams(n.slice(i+1)).forEach((a,c)=>{o[c]=a});let s=r.split("/").map(a=>{try{return decodeURIComponent(a)}catch{return a}});return{view:xl[s[0]]||s[0],params:s.slice(1).filter(a=>a!==""),query:o}}function No(e){let n=(e||"").replace(/^#\\/?/,""),i=n.indexOf("?");return i<0?n:n.slice(0,i)}function ne(e,...n){let i=n.filter(r=>r!=null&&r!=="");return"#/"+[e,...i].map(encodeURIComponent).join("/")}function Fn(e,n,i){let r=ne(e,...n),o=new URLSearchParams;for(let l of Object.keys(i)){let a=i[l];a!=null&&a!==""&&o.set(l,a)}let s=o.toString();return s?r+"?"+s:r}function Z(e,...n){let i=ne(e,...n);location.hash!==i&&(location.hash=i)}function sn(e,...n){let i=ne(e,...n);try{history.replaceState(null,"",location.pathname+location.search+i)}catch{location.hash=i}window.dispatchEvent(new HashChangeEvent("hashchange"))}function On(){return Rl(location.hash)}function Do(){let[e,n]=m(On);return j(()=>{let i=()=>n(On());return window.addEventListener("hashchange",i),()=>window.removeEventListener("hashchange",i)},[]),e}var El=0;function t(e,n,i,r,o,s){n||(n={});var l,a,c=n;if("ref"in c)for(a in c={},n)a=="ref"?l=n[a]:c[a]=n[a];var u={type:e,props:c,key:i,ref:l,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:--El,__i:-1,__u:0,__source:o,__self:s};if(typeof e=="function"&&(l=e.defaultProps))for(a in l)c[a]===void 0&&(c[a]=l[a]);return be.vnode&&be.vnode(u),u}function K({kind:e,children:n,title:i}){return t("span",{class:"chip"+(e?" "+e:""),title:i,children:n})}function J({kind:e,children:n,style:i}){return t("div",{class:"notice"+(e?" "+e:""),style:i,children:n})}function A({kind:e,tiny:n,children:i,class:r,className:o,...s}){let l=["btn",e||"",n?"tiny":"",String(r||o||"")].filter(Boolean).join(" ");return t("button",{type:"button",...s,class:l,children:i})}function B(e){let{readonly:n}=te();return n?t(A,{...e,disabled:!0,title:"Your access to this backend is read-only, so this cannot make changes."}):t(A,{...e})}function ce({title:e,subtitle:n,children:i}){return t("div",{children:[t("h1",{children:e}),n?t("p",{class:"sub",children:n}):null,i]})}function V({children:e,style:n,class:i}){return t("div",{class:"row"+(i?" "+i:""),style:n,children:e})}function pe({children:e,style:n,id:i,class:r}){return t("div",{class:"card"+(r?" "+r:""),style:n,id:i,children:e})}function me(){return t("span",{style:"flex:1"})}function G({h:e=12}){return t("div",{style:"height:"+e+"px"})}function fe({children:e,style:n}){return t("p",{class:"sub",style:n,children:e})}function M({children:e}){return t("span",{class:"hint",children:e})}function vt({children:e}){return t("b",{class:"hi",children:e})}function $({label:e,children:n,style:i}){return t("label",{class:"field",style:i,children:[e,n]})}function Fe({checked:e,onChange:n,children:i,disabled:r,style:o}){return t("label",{class:"check",style:o,children:[t("input",{type:"checkbox",checked:e,disabled:r,onChange:s=>n(s.target.checked)}),i]})}function re({checked:e,onChange:n,children:i,disabled:r,id:o,label:s}){return t("label",{class:"switch",children:[t("input",{type:"checkbox",id:o,"aria-label":s,checked:e,disabled:r,onChange:l=>n(l.target.checked)}),t("span",{class:"switch-track","aria-hidden":"true"}),i]})}function ye({columns:e,rows:n,renderRow:i,empty:r,class:o,head:s}){return n.length?t("div",{class:"scroller",children:t("table",{class:o,children:[t("thead",{children:s||t("tr",{children:e.map((l,a)=>t("th",{children:l},a))})}),t("tbody",{children:n.map((l,a)=>i(l,a))})]})}):t("div",{class:"scroller",children:r||t("div",{class:"empty",children:"Nothing here yet."})})}function Ye({children:e}){return t("td",{class:"actions",children:e})}function He({label:e,children:n,open:i}){let[r,o]=m(!!i);return t("div",{class:"disclose",children:[t("div",{class:"disclose-head",role:"button",tabIndex:0,"aria-expanded":r,onClick:()=>o(!r),onKeyDown:s=>{(s.key==="Enter"||s.key===" ")&&(s.preventDefault(),o(!r))},children:[t("span",{class:"jx-twist",children:r?"\u25BE":"\u25B8"}),t("span",{children:e})]}),r?t("div",{class:"disclose-body",children:n}):null]})}function _l(e){return e==null?"jx-null":typeof e=="number"?"jx-num":typeof e=="boolean"?"jx-bool":"jx-str"}function Al(e){return e===null?"null":e===void 0?"undefined":typeof e=="string"?'"'+e+'"':String(e)}function an({value:e,name:n,depth:i=0,openTo:r=1}){let o=Array.isArray(e),s=e!==null&&typeof e=="object"&&!o,[l,a]=m(i<r);if(!o&&!s)return t("div",{class:"jx-node",children:t("div",{class:"jx-line",children:[t("span",{class:"jx-twist",children:" "}),n!=null?t("span",{class:"jx-key",children:[n,":"]}):null,t("span",{class:"jx-val "+_l(e),children:Al(e)})]})});let c=o?e.map((p,d)=>({k:String(d),v:p})):Object.keys(e).map(p=>({k:p,v:e[p]})),u=o?"["+c.length+(c.length===1?" item]":" items]"):"{"+c.length+(c.length===1?" field}":" fields}");return t("div",{class:"jx-node",children:[t("div",{class:"jx-line",onClick:()=>a(!l),children:[t("span",{class:"jx-twist",children:l?"\u25BE":"\u25B8"}),n!=null?t("span",{class:"jx-key",children:[n,":"]}):null,t("span",{class:"jx-meta",children:u})]}),l?t("div",{class:"jx-children",children:c.map(p=>t(an,{value:p.v,name:p.k,depth:i+1,openTo:r},p.k))}):null]})}async function Ge(e,n="Copied"){try{if(navigator.clipboard&&navigator.clipboard.writeText)return await navigator.clipboard.writeText(e),!0}catch{}return!1}var wt=tt({items:[]}),Pl=1;function ge(e,n){let i=Pl++,r=()=>{let o=wt.get().items;o.some(s=>s.id===i)&&(wt.set({items:o.filter(s=>s.id!==i)}),n&&n())};return wt.set({items:[...wt.get().items,{id:i,render:()=>e(r),onClose:n}]}),{close:r}}function Di(){let{items:e}=Je(wt);return t("div",{id:"modal-root",children:e.map(n=>t(Il,{modal:n},n.id))})}function Il({modal:e}){let n=()=>wt.set({items:wt.get().items.filter(i=>i.id!==e.id)});return j(()=>{let i=r=>{r.key==="Escape"&&(r.preventDefault(),n(),e.onClose&&e.onClose())};return window.addEventListener("keydown",i),()=>window.removeEventListener("keydown",i)},[e.id]),t("div",{class:"backdrop",onClick:i=>{i.target===i.currentTarget&&(n(),e.onClose&&e.onClose())},children:e.render(n)})}function ke({title:e,wide:n,children:i,actions:r,autoFocus:o=!0}){let s=ae(null);return j(()=>{if(!o||!s.current)return;let l=s.current.querySelector("input:not([type=checkbox]), select, textarea, button.btn");l&&setTimeout(()=>l.focus(),0)},[]),t("div",{class:"modal"+(n?" wide":""),role:"dialog","aria-modal":"true","aria-label":e,ref:s,children:[t("h3",{children:e}),i,r?t("div",{class:"foot",children:r}):null]})}function qe(e,n,i,r,o="Delete"){ge(s=>t(Ll,{title:e,warning:n,expected:i,verb:o,close:s,onConfirm:r}))}function Ll(e){let[n,i]=m(""),r=n===e.expected;return t(ke,{title:e.title,actions:t(W,{children:[t(A,{onClick:e.close,children:"Cancel"}),t(A,{kind:"danger",disabled:!r,onClick:()=>{r&&(e.close(),e.onConfirm())},children:e.verb})]}),children:[t("div",{class:"notice bad",children:e.warning}),t("p",{class:"sub",children:['Type "',e.expected,'" to confirm. This cannot be undone.']}),t("input",{type:"text",placeholder:e.expected,"aria-label":"Type "+e.expected+" to confirm",style:"width:100%;margin-top:8px",value:n,onInput:o=>i(o.target.value)})]})}function we(e,n,i,r="Delete"){ge(o=>t(ke,{title:e,autoFocus:!1,actions:t(W,{children:[t(A,{onClick:o,children:"Cancel"}),t(A,{kind:"danger",onClick:()=>{o(),i()},children:r})]}),children:t("div",{class:"notice bad",children:n})}))}var ln=tt({items:[]}),Nl=1;function D(e,n=""){let i=Nl++;ln.set({items:[...ln.get().items,{id:i,message:e,kind:n}]}),setTimeout(()=>Oo(i),n==="bad"?12e3:4500)}function O(e){let n=e;D(n&&n.message?n.message:String(e),"bad")}function Oo(e){ln.set({items:ln.get().items.filter(n=>n.id!==e)})}function Oi(){let{items:e}=Je(ln);return t("div",{id:"toasts","aria-live":"polite",children:e.map(n=>t("div",{class:"toast"+(n.kind?" "+n.kind:""),onClick:()=>Oo(n.id),children:n.message},n.id))})}function Ce(e){let[n,i]=m(""),[r,o]=m(!1),[s,l]=m([]),[a,c]=m(!1),[u,p]=m(0),d=ae(null),f=ae(0),b=ae(null),g=ae(null),v=ae(!1),y=k=>{let E=++f.current;c(!0),e.fetch(k).then(P=>{E===f.current&&(g.current=k,l(P),p(0),c(!1),v.current&&(v.current=!1,C(P,k)))}).catch(()=>{E===f.current&&(l([]),c(!1))})};j(()=>{if(r)return d.current&&clearTimeout(d.current),d.current=setTimeout(()=>y(n),150),()=>{d.current&&clearTimeout(d.current)}},[n,r]),j(()=>{e.autoFocus&&b.current&&b.current.focus()},[]);let T=s.some(k=>e.label(k).toLowerCase()===n.trim().toLowerCase()),w=k=>!!e.onCreate&&k!==""&&(!e.createWhen||e.createWhen(k)),x=w(n.trim())&&!T&&!a,R=s.length+(x?1:0),C=(k,E)=>{if(k.length)e.onPick(k[0]);else if(e.onCreate&&w(E.trim()))e.onCreate(E.trim());else return;o(!1),i("")},_=k=>{k<s.length?(e.onPick(s[k]),o(!1),i("")):x&&e.onCreate&&(e.onCreate(n.trim()),o(!1),i(""))},N=k=>{if(!r&&(k.key==="ArrowDown"||k.key==="Enter")){o(!0);return}r&&(k.key==="ArrowDown"?(k.preventDefault(),p(R?(u+1)%R:0)):k.key==="ArrowUp"?(k.preventDefault(),p(R?(u-1+R)%R:0)):k.key==="Enter"?(k.preventDefault(),a||g.current!==n?v.current=!0:R&&_(u)):k.key==="Escape"&&(k.preventDefault(),v.current=!1,o(!1)))};return e.value&&!r?t("div",{class:"picker picked",id:e.id,children:[t("span",{class:"picker-value",title:e.detail?e.detail(e.value):void 0,children:e.label(e.value)}),e.disabled?null:t("button",{type:"button",class:"picker-clear","aria-label":"Clear",onClick:()=>e.onPick(null),children:"\u2715"})]}):t("div",{class:"picker",id:e.id,children:[t("input",{ref:b,type:"search",role:"combobox","aria-expanded":r,"aria-autocomplete":"list",placeholder:e.placeholder||"Type to search\u2026",disabled:e.disabled,value:n,onFocus:()=>{e.openOnFocus!==!1&&o(!0)},onBlur:()=>setTimeout(()=>o(!1),120),onInput:k=>{i(k.currentTarget.value),o(!0)},onKeyDown:N}),r?t("div",{class:"picker-list",role:"listbox",children:[a&&!s.length?t("div",{class:"picker-empty",children:"Searching\u2026"}):null,!a&&!R?t("div",{class:"picker-empty",children:e.emptyText||"No match."}):null,s.map((k,E)=>t("div",{role:"option","aria-selected":E===u,class:"picker-row"+(E===u?" active":""),onMouseDown:P=>{P.preventDefault(),_(E)},onMouseEnter:()=>p(E),children:[t("span",{class:"picker-label",children:e.label(k)}),e.detail?t("span",{class:"picker-detail",children:e.detail(k)}):null]},e.keyOf(k))),x?t("div",{role:"option","aria-selected":u===s.length,class:"picker-row picker-create"+(u===s.length?" active":""),onMouseDown:k=>{k.preventDefault(),_(s.length)},onMouseEnter:()=>p(s.length),children:e.createLabel?e.createLabel(n.trim()):'Create "'+n.trim()+'"\u2026'}):null]}):null]})}function Ie(e){let[n,i]=m(""),[r,o]=m(null),s=a=>{let c=a.trim();if(c){if(e.validate){let u=e.validate(c);if(u){o(u);return}}if(e.normalise&&(c=e.normalise(c)),o(null),e.items.indexOf(c)!==-1){i("");return}e.onChange([...e.items,c]),i("")}},l=a=>e.onChange(e.items.filter(c=>c!==a));return t("div",{class:"chips",id:e.id,children:[t("div",{class:"chips-set",children:[e.items.map(a=>t("span",{class:"chip accent",children:[e.label?e.label(a):a,e.disabled?null:t("button",{type:"button",class:"chip-x","aria-label":"Remove "+(e.label?e.label(a):a),onClick:()=>l(a),children:"\u2715"})]},a)),e.items.length?null:t("span",{class:"hint",children:"None yet."})]}),e.disabled?null:e.suggest?t(Ce,{fetch:a=>e.suggest(a).then(c=>c.filter(u=>e.items.indexOf(u)===-1)),label:a=>e.label?e.label(a):a,keyOf:a=>a,value:null,onPick:a=>{a&&s(a)},placeholder:e.placeholder||e.addLabel||"Add\u2026"}):t("div",{class:"chips-add",children:[t("input",{type:"text",placeholder:e.placeholder||"Add\u2026","aria-label":e.addLabel||"Add",value:n,onInput:a=>{i(a.currentTarget.value),r&&o(null)},onKeyDown:a=>{a.key==="Enter"||a.key===","?(a.preventDefault(),s(n)):a.key==="Backspace"&&!n&&e.items.length&&l(e.items[e.items.length-1])}}),t("button",{type:"button",class:"btn tiny",onClick:()=>s(n),children:e.addLabel||"Add"}),r?t("span",{class:"chips-problem",children:r}):null]})]})}function Xe(e){let n=(o,s)=>e.onChange(e.rows.map((l,a)=>a===o?s:l)),i=o=>e.onChange(e.rows.filter((s,l)=>l!==o)),r=e.rows.length>(e.min||0);return t("div",{class:"list-editor",id:e.id,children:[e.head,e.rows.map((o,s)=>t("div",{class:"list-row",children:[e.renderRow(o,l=>n(s,l),s),e.disabled?null:t("button",{type:"button",class:"btn tiny list-x",title:e.removeTitle||"Remove this row","aria-label":e.removeTitle||"Remove this row",disabled:!r,onClick:()=>i(s),children:"\u2715"})]},s)),e.disabled?null:t("button",{type:"button",class:"btn tiny list-add",onClick:()=>e.onChange([...e.rows,e.blank()]),children:e.addLabel||"+ Add"})]})}function pt(e){return e.indexOf("role:")===0}function Fi(e){return pt(e)?e.slice(5):e}function Mi(e){if(e==null||e==="")return{mode:"public"};let n=typeof e=="object"&&!Array.isArray(e)?e:{},i=[],r=n["*"]||{};i.push({key:"*",read:r.read===!0,write:r.write===!0});let o=Object.keys(n).filter(s=>s!=="*");return o.filter(pt).concat(o.filter(s=>!pt(s))).forEach(s=>{let l=n[s]||{};i.push({key:s,read:l.read===!0,write:l.write===!0})}),{mode:"rows",rows:i}}function Fo(e){if(e.mode==="public")return null;let n={};return e.rows.forEach(i=>{if(!i.key||!i.read&&!i.write)return;let r={};i.read&&(r.read=!0),i.write&&(r.write=!0),n[i.key]=r}),n}function Bi(e){return e.mode==="rows"&&!e.rows.some(n=>n.read||n.write)}function Mo(){return{mode:"rows",rows:[{key:"*",read:!0,write:!0}]}}function Hi(e,n){return e.mode!=="rows"||!n||e.rows.some(i=>i.key===n)?e:{mode:"rows",rows:e.rows.concat([{key:n,read:!0,write:!1}])}}function ji(e,n,i){return e.mode!=="rows"?e:{mode:"rows",rows:e.rows.map(r=>r.key===n?{...r,...i}:r)}}function Bo(e,n){return e.mode!=="rows"||n==="*"?e:{mode:"rows",rows:e.rows.filter(i=>i.key!==n)}}function Ho(e,n){let i=Mi(e);return i.mode==="public"?"Everyone":Bi(i)?"No one":i.rows.filter(r=>r.read||r.write).map(r=>(r.key==="*"?"everyone":pt(r.key)?Fi(r.key):n?n(r.key):"a person")+" ("+(r.read&&r.write?"read, write":r.read?"read":"write")+")").join(", ")}function ve(e){return e==null?"":typeof e=="object"?JSON.stringify(e):String(e)}function he(e){if(!e)return"\u2014";let n=new Date(typeof e=="number"?e:String(e));return isNaN(n.getTime())?String(e):n.toLocaleString()}function je(e,n=Date.now()){if(!e)return"never";let i=new Date(typeof e=="number"?e:String(e));if(isNaN(i.getTime()))return String(e);let r=Math.max(0,Math.round((n-i.getTime())/1e3));if(r<45)return"just now";let o=Math.round(r/60);if(o<60)return o+" min ago";let s=Math.round(o/60);if(s<24)return s+(s===1?" hour ago":" hours ago");let l=Math.round(s/24);return l<31?l+(l===1?" day ago":" days ago"):i.toLocaleDateString()}function nt(e){return typeof e=="string"&&e.length>12?e.slice(0,8)+"\u2026":String(e)}var jo=/^[a-zA-Z][a-zA-Z0-9_]*$/,Dl=["objectId","createdAt","updatedAt","ACL"],Ol=["ABORT","ACTION","ADD","AFTER","ALL","ALTER","ANALYZE","AND","AS","ASC","ATTACH","AUTOINCREMENT","BEFORE","BEGIN","BETWEEN","BY","CASCADE","CASE","CAST","CHECK","COLLATE","COLUMN","COMMIT","CONFLICT","CONSTRAINT","CREATE","CROSS","CURRENT_DATE","CURRENT_TIME","CURRENT_TIMESTAMP","DATABASE","DEFAULT","DEFERRABLE","DEFERRED","DELETE","DESC","DETACH","DISTINCT","DROP","EACH","ELSE","END","ESCAPE","EXCEPT","EXCLUSIVE","EXISTS","EXPLAIN","FAIL","FOR","FOREIGN","FROM","FULL","GLOB","GROUP","HAVING","IF","IGNORE","IMMEDIATE","IN","INDEX","INDEXED","INITIALLY","INNER","INSERT","INSTEAD","INTERSECT","INTO","IS","ISNULL","JOIN","KEY","LEFT","LIKE","LIMIT","MATCH","NATURAL","NO","NOT","NOTNULL","NULL","OF","OFFSET","ON","OR","ORDER","OUTER","PLAN","PRAGMA","PRIMARY","QUERY","RAISE","RECURSIVE","REFERENCES","REGEXP","REINDEX","RELEASE","RENAME","REPLACE","RESTRICT","RIGHT","ROLLBACK","ROW","SAVEPOINT","SELECT","SET","TABLE","TEMP","TEMPORARY","THEN","TO","TRANSACTION","TRIGGER","UNION","UNIQUE","UPDATE","USING","VACUUM","VALUES","VIEW","VIRTUAL","WHEN","WHERE","WITH","WITHOUT"];function Wo(e){return Ol.indexOf(e.toUpperCase())!==-1}function Wi(e,n){return e?jo.test(e)?e.length>64?"A collection name is at most 64 characters.":Wo(e)?'"'+e+'" is a word the database keeps for itself. Try '+e+"s or My"+e+".":n.indexOf(e)!==-1?"There is already a collection called "+e+".":null:"A collection name starts with a letter, then letters, digits or _ only.":"The collection needs a name."}function Ue(e){return e==="objectId"||e==="createdAt"||e==="updatedAt"}function Uo(e,n,i){return Ue(n)||e==="_User"&&!!i&&Object.prototype.hasOwnProperty.call(i,n)}function Mt(e,n,i){return e?jo.test(e)?Dl.indexOf(e)!==-1?'"'+e+'" is reserved by the backend.':e.length>64?'"'+e+'" is too long: at most 64 characters.':Wo(e)?'"'+e+'" is a word the database keeps for itself. Try '+e+"Value or the"+e.charAt(0).toUpperCase()+e.slice(1)+".":n&&n.indexOf(e)!==-1?'There is already a field called "'+e+'".':null:'"'+e+'": start with a letter, then letters, digits or _ only.':i+" needs a name."}function it(e){if(e&&typeof e=="object"&&!Array.isArray(e)){let n=e;if(n.__type==="Date")return n.iso;if(n.__type==="Pointer"||n.__type==="Object")return n.objectId}return e}function Ui(e,n){return n==null?n:e.type==="Date"?{__type:"Date",iso:n}:e.type==="Pointer"?{__type:"Pointer",className:e.targetClass,objectId:n}:n}function mt(e){return e<1024?e+" B":e<1024*1024?(e/1024).toFixed(1)+" KB":(e/1024/1024).toFixed(1)+" MB"}function rt(e){let n=/^[0-9a-f]{8,}[-_](.+)$/i.exec(e);return n?n[1]:e}function Mn(e,n){if(n==="ACL")return Ho(e);let i=it(e);if(i==null||i==="")return"";if(n==="Boolean")return i?"\u2713":"\u2717";if(n==="Date")return he(i);if(n==="Pointer")return"\u2192 "+nt(i);if(n==="File"&&i&&typeof i=="object")return"\u25A4 "+rt(String(i.name||""));if(n==="GeoPoint"&&i&&typeof i=="object"){let r=i;return typeof r.latitude=="number"?r.latitude.toFixed(4)+", "+Number(r.longitude).toFixed(4):""}return Array.isArray(i)?i.map(r=>r&&typeof r=="object"?ve(r):String(r)).join(", "):i&&typeof i=="object"?Object.keys(i).map(r=>r+": "+ve(i[r])).join(" \xB7 "):ve(i)}function Ko(e){return typeof e=="number"?"Number":typeof e=="boolean"?"Boolean":Array.isArray(e)?"Array":e&&typeof e=="object"?"Object":"String"}function Bt(e){let n=new Date(String(e));if(isNaN(n.getTime()))return"";let i=r=>(r<10?"0":"")+r;return n.getFullYear()+"-"+i(n.getMonth()+1)+"-"+i(n.getDate())+"T"+i(n.getHours())+":"+i(n.getMinutes())}function Bn(e){let n=typeof e.username=="string"&&e.username?e.username:"",i=typeof e.email=="string"&&e.email?e.email:"";return n&&i?n+" \xB7 "+i:n||i||"(no username)"}var Vo=["","running","success","error"];function cn(e){return e==="success"?"ok":e==="error"||e==="timed out"?"bad":"warn"}function Ki(e){let n=e||{},i=n.metadata||{};return i.engineStatus==="cancelled"||i.cancelled===!0?"cancelled":i.timedOut===!0?"timed out":n.status||""}function Go(e){return e==="success"?"ok":e==="error"?"bad":e==="skipped"?"":"warn"}function Vi(e){if(Array.isArray(e))return e;let n=e||{};return n.executions||n.results||[]}function qo(e){let n=e||{},i=n.steps||[],r=[],o={};for(let a=0;a<i.length;a++){let c=i[a]||{};if(c.status!=="error")continue;let p=(c.outputData||{}).detail,d=p&&typeof p=="object"&&!Array.isArray(p)?p:null,f={index:a,step:c.nodeName||c.nodeId||"step "+(a+1),type:c.nodeType||"",message:c.errorMessage||"",detail:d},b=f.step+" "+f.message+" "+JSON.stringify(f.detail);o[b]||(o[b]=!0,r.push(f))}let s=n.modelCost&&typeof n.modelCost=="object"?n.modelCost:null,l=n.metadata||{};return{id:n.id||"",status:n.status||"",workflow:n.workflowName||n.workflowId||"",trigger:n.triggerType||"",triggerSource:l.triggerSource||"",startedAt:n.startedAt,durationMs:n.durationMs,errorMessage:n.errorMessage||"",costLine:s&&s.line?String(s.line):"",capped:!!l.recordCapped,stepCount:i.length,failures:r}}function dn(e){let n=e==null?"":typeof e=="object"?JSON.stringify(e):String(e);return/[",\\n]/.test(n)?'"'+n.replace(/"/g,'""')+'"':n}function $o(e){let n=[],i=[],r="",o=!1;for(let s=0;s<e.length;s++){let l=e[s];o?l==='"'&&e[s+1]==='"'?(r+='"',s++):l==='"'?o=!1:r+=l:l==='"'?o=!0:l===","?(i.push(r),r=""):l===\`
65017
65035
  \`||l==="\\r"?(l==="\\r"&&e[s+1]===\`
65018
- \`&&s++,i.push(r),n.push(i),i=[],r=""):r+=l}return(r.length||i.length)&&(i.push(r),n.push(i)),n.filter(s=>!(s.length===1&&s[0]===""))}var zo=[{id:"text",label:"Text"},{id:"number",label:"Number"},{id:"boolean",label:"Yes / no"},{id:"date",label:"Date"},{id:"json",label:"JSON"}];function Ht(e){return!e||typeof e!="object"||Array.isArray(e)?[]:Object.keys(e).map(n=>{let i=e[n];return typeof i=="boolean"?{key:n,type:"boolean",raw:i}:typeof i=="number"?{key:n,type:"number",raw:String(i)}:typeof i=="string"?/^\\d{4}-\\d{2}-\\d{2}T/.test(i)&&!isNaN(new Date(i).getTime())?{key:n,type:"date",raw:Bt(i)}:{key:n,type:"text",raw:i}:{key:n,type:"json",raw:i===void 0?"":JSON.stringify(i)}})}function Ct(e){let n={};return e.forEach((i,r)=>{let o=i.key.trim();if(!o){if(i.raw===""||i.raw===!1)return;throw new Error("Row "+(r+1)+" needs a name.")}if(o in n)throw new Error('"'+o+'" is listed twice.');switch(i.type){case"boolean":n[o]=i.raw===!0;break;case"number":{let s=String(i.raw).trim();if(s==="")throw new Error('"'+o+'" needs a number.');let l=Number(s);if(isNaN(l))throw new Error('"'+o+'" must be a number.');n[o]=l;break}case"date":{let s=String(i.raw).trim();if(!s)throw new Error('"'+o+'" needs a date.');let l=new Date(s);if(isNaN(l.getTime()))throw new Error('"'+o+'" is not a date.');n[o]=l.toISOString();break}case"json":{let s=String(i.raw).trim();if(!s)throw new Error('"'+o+'" needs a value.');try{n[o]=JSON.parse(s)}catch(l){throw new Error('"'+o+'" is not valid JSON: '+l.message)}break}default:n[o]=String(i.raw)}}),n}function St(e){return t(Xe,{id:e.id,rows:e.rows,onChange:e.onChange,blank:()=>({key:"",type:"text",raw:""}),addLabel:"+ Add a value",disabled:e.disabled,renderRow:(n,i)=>t(W,{children:[t("input",{type:"text",class:"kv-key",placeholder:e.keyPlaceholder||"name","aria-label":"Name",value:n.key,disabled:e.disabled,onInput:r=>i({...n,key:r.currentTarget.value})}),t("select",{class:"kv-type","aria-label":"Type",value:n.type,disabled:e.disabled,onChange:r=>{let o=r.currentTarget.value;i({...n,type:o,raw:o==="boolean"?n.raw===!0:n.type==="boolean"?"":n.raw})},children:zo.map(r=>t("option",{value:r.id,children:r.label},r.id))}),t(Ol,{row:n,update:i,disabled:e.disabled})]})})}function Ol({row:e,update:n,disabled:i}){let[r,o]=m(!1);switch(e.type){case"boolean":return t("label",{class:"check kv-value",children:[t("input",{type:"checkbox",checked:e.raw===!0,disabled:i,onChange:s=>n({...e,raw:s.currentTarget.checked})}),e.raw===!0?"Yes":"No"]});case"number":return t("input",{type:"number",step:"any",class:"kv-value","aria-label":"Value",value:String(e.raw),disabled:i,onInput:s=>n({...e,raw:s.currentTarget.value})});case"date":return t("input",{type:"datetime-local",class:"kv-value","aria-label":"Value",value:String(e.raw),disabled:i,onInput:s=>n({...e,raw:s.currentTarget.value})});case"json":return t("input",{type:"text",class:"kv-value mono","aria-label":"Value",placeholder:'["a", "b"] or { "k": 1 }',value:String(e.raw),disabled:i,onFocus:()=>o(!0),onBlur:()=>o(!1),onInput:s=>n({...e,raw:s.currentTarget.value})});default:return t("input",{type:"text",class:"kv-value","aria-label":"Value",value:String(e.raw),disabled:i,onInput:s=>n({...e,raw:s.currentTarget.value})})}}var Gi='a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';function Se(e){let n=ae(null),i=ae(null);return j(()=>{i.current=document.activeElement;let r=n.current;if(r){let s=r.querySelector(".drawer-body");(s&&s.querySelector(Gi)||r.querySelector(Gi)||r).focus()}let o=s=>{if(s.key==="Escape"){s.preventDefault(),e.onClose();return}if(s.key!=="Tab"||!r)return;let l=Array.from(r.querySelectorAll(Gi)).filter(u=>!u.hidden&&u.getAttribute("aria-hidden")!=="true");if(!l.length){s.preventDefault(),r.focus();return}let a=l[0],c=l[l.length-1];s.shiftKey&&document.activeElement===a?(s.preventDefault(),c.focus()):!s.shiftKey&&document.activeElement===c?(s.preventDefault(),a.focus()):r.contains(document.activeElement)||(s.preventDefault(),a.focus())};return window.addEventListener("keydown",o),()=>{window.removeEventListener("keydown",o);let s=i.current;s&&typeof s.focus=="function"&&s.focus()}},[]),t("div",{class:"drawer-scrim",onClick:r=>{r.target===r.currentTarget&&e.onClose()},children:t("div",{class:"drawer"+(e.wide?" wide":""),role:"dialog","aria-modal":"true","aria-label":e.title,tabIndex:-1,ref:n,children:[t("div",{class:"drawer-head",children:[t("div",{children:[t("h3",{children:e.title}),e.subtitle?t("div",{class:"sub",style:"margin:0",children:e.subtitle}):null]}),t("button",{type:"button",class:"btn tiny","aria-label":"Close",onClick:e.onClose,children:"\u2715"})]}),t("div",{class:"drawer-body",children:e.children}),e.footer?t("div",{class:"drawer-foot",children:e.footer}):null]})})}function qe({children:e,title:n="Danger zone"}){return t("div",{class:"danger-zone",children:[t("div",{class:"danger-zone-title",children:n}),e]})}function Ze(e){return t("div",{class:"danger-action",children:[t("div",{class:"danger-why",children:e.why}),t(M,{kind:"danger",onClick:()=>Ge(e.title,e.warning,e.expected,e.onConfirm,e.verb||e.label),children:e.label})]})}function oe({children:e,icon:n,action:i,docs:r}){let o=i&&i.write!==!1?M:P;return t("div",{class:"empty-state",children:[n?t("div",{class:"empty-icon","aria-hidden":"true",children:n}):null,t("div",{class:"empty-text",children:e}),i?t(o,{kind:"primary",onClick:i.onClick,children:i.label}):null,r?t("a",{class:"empty-docs",href:r.href,target:"_blank",rel:"noreferrer",children:r.label}):null]})}var $i={text:[{id:"is",label:"is",arity:"one"},{id:"isNot",label:"is not",arity:"one"},{id:"contains",label:"contains",arity:"one"},{id:"startsWith",label:"starts with",arity:"one"},{id:"empty",label:"is empty",arity:"none"},{id:"notEmpty",label:"is not empty",arity:"none"},{id:"oneOf",label:"is one of",arity:"many"}],number:[{id:"eq",label:"=",arity:"one"},{id:"ne",label:"\u2260",arity:"one"},{id:"lt",label:"<",arity:"one"},{id:"lte",label:"\u2264",arity:"one"},{id:"gt",label:">",arity:"one"},{id:"gte",label:"\u2265",arity:"one"},{id:"between",label:"is between",arity:"two"},{id:"empty",label:"is empty",arity:"none"}],boolean:[{id:"yes",label:"is yes",arity:"none"},{id:"no",label:"is no",arity:"none"}],date:[{id:"on",label:"is",arity:"one"},{id:"before",label:"is before",arity:"one"},{id:"after",label:"is after",arity:"one"},{id:"between",label:"is between",arity:"two"},{id:"within",label:"is within",arity:"window"},{id:"empty",label:"is empty",arity:"none"}],link:[{id:"is",label:"is",arity:"one"},{id:"empty",label:"is empty",arity:"none"}],list:[{id:"contains",label:"contains",arity:"one"},{id:"empty",label:"is empty",arity:"none"}],location:[{id:"near",label:"is within",arity:"near"}],choice:[{id:"is",label:"is",arity:"one"}]};function zi(e){let n=$i[e.kind];if(!e.ops)return n;let i=e.ops.map(r=>n.find(o=>o.id===r)).filter(r=>!!r);return i.length?i:n}var Wn=[{id:"today",label:"today"},{id:"week",label:"this week"},{id:"month",label:"this month"},{id:"past7",label:"the past 7 days"},{id:"past30",label:"the past 30 days"}];function Jo(e){switch(e){case"String":return"text";case"Number":return"number";case"Boolean":return"boolean";case"Date":return"date";case"Pointer":return"link";case"Array":return"list";case"GeoPoint":return"location";default:return null}}function Un(e,n){return $i[e].find(i=>i.id===n)}function jt(e){let n=zi(e)[0],i={kind:"cond",field:e.name,op:n.id};return Xo(i,n)}function Yo(e,n,i){let r=Un(n,i)||$i[n][0];return Xo({kind:"cond",field:e.field,op:r.id,value:r.arity==="one"||r.arity==="two"?e.value:void 0},r)}function Xo(e,n){return n.arity==="one"&&e.value===void 0&&(e.value=""),n.arity==="two"&&(e.value===void 0&&(e.value=""),e.value2=""),n.arity==="many"&&(e.values=[]),n.arity==="window"&&(e.value="today"),n.arity==="near"&&(e.lat="",e.lng="",e.value="10"),e}function De(e){let n=/^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(e);if(!n)throw new Error('"'+e+'" is not a date.');return new Date(Number(n[1]),Number(n[2])-1,Number(n[3]))}function Re(e,n){return new Date(e.getFullYear(),e.getMonth(),e.getDate()+n)}function Tt(e){let n=i=>(i<10?"0":"")+i;return e.getFullYear()+"-"+n(e.getMonth()+1)+"-"+n(e.getDate())}function Hn(e){return e.getHours()===0&&e.getMinutes()===0&&e.getSeconds()===0&&e.getMilliseconds()===0}function Wt(e,n){let i=new Date(n.getFullYear(),n.getMonth(),n.getDate());switch(e){case"today":return[i,Re(i,1)];case"week":{let r=Re(i,-((i.getDay()+6)%7));return[r,Re(r,7)]}case"month":return[new Date(i.getFullYear(),i.getMonth(),1),new Date(i.getFullYear(),i.getMonth()+1,1)];case"past7":return[Re(i,-6),Re(i,1)];case"past30":return[Re(i,-29),Re(i,1)];default:throw new Error('"'+e+'" is not a date window.')}}var ft=e=>({__type:"Date",iso:e.toISOString()});function Fl(e){return e.replace(/[.*+?^\${}()|[\\]\\\\]/g,"\\\\$&")}function xt(e,n){let i=String(n===void 0?"":n).trim(),r=Number(i);if(i===""||isNaN(r))throw new Error(e+" needs a number.");return r}function Rt(e,n){if(n===void 0||n==="")throw new Error(e+" needs a value.");return n}function un(e,n){return{$or:[{[e]:n},{[e]:{$exists:!1}}]}}function Ji(e,n,i=new Date){let r=n.find(s=>s.name===e.field);if(!r)throw new Error('There is no field called "'+e.field+'".');let o=e.field;switch(r.kind){case"text":switch(e.op){case"is":return{[o]:{$eq:e.value||""}};case"isNot":return un(o,{$ne:e.value||""});case"contains":return{[o]:{contains:Rt(o,e.value)}};case"startsWith":return{[o]:{$regex:"^"+Fl(Rt(o,e.value))}};case"empty":return un(o,{$eq:""});case"notEmpty":return{$and:[{[o]:{$exists:!0}},{[o]:{$ne:""}}]};case"oneOf":if(!e.values||!e.values.length)throw new Error(o+" needs at least one value.");return{[o]:{$in:e.values.slice()}}}break;case"number":{let s={eq:"$eq",lt:"$lt",lte:"$lte",gt:"$gt",gte:"$gte"};if(e.op==="ne")return un(o,{$ne:xt(o,e.value)});if(s[e.op])return{[o]:{[s[e.op]]:xt(o,e.value)}};if(e.op==="between")return{[o]:{$gte:xt(o,e.value),$lte:xt(o,e.value2)}};if(e.op==="empty")return{[o]:{$exists:!1}};break}case"boolean":if(e.op==="yes")return{[o]:{$eq:!0}};if(e.op==="no")return un(o,{$eq:!1});break;case"date":{if(e.op==="empty")return{[o]:{$exists:!1}};if(e.op==="within"){let[l,a]=Wt(e.value||"today",i);return{[o]:{$gte:ft(l),$lt:ft(a)}}}let s=De(Rt(o,e.value));if(e.op==="on")return{[o]:{$gte:ft(s),$lt:ft(Re(s,1))}};if(e.op==="before")return{[o]:{$lt:ft(s)}};if(e.op==="after")return{[o]:{$gte:ft(Re(s,1))}};if(e.op==="between"){let l=De(Rt(o,e.value2)),[a,c]=s<=l?[s,l]:[l,s];return{[o]:{$gte:ft(a),$lt:ft(Re(c,1))}}}break}case"link":if(e.op==="empty")return{[o]:{$exists:!1}};if(e.op==="is")return{[o]:{$eq:{__type:"Pointer",className:r.targetClass,objectId:Rt(o,e.value)}}};break;case"list":if(e.op==="contains")return{[o]:{contains:JSON.stringify(Rt(o,e.value))}};if(e.op==="empty")return un(o,{$eq:[]});break;case"location":if(e.op==="near")return{[o]:{$nearSphere:{__type:"GeoPoint",latitude:xt(o+" latitude",e.lat),longitude:xt(o+" longitude",e.lng)},$maxDistanceInKilometers:xt(o+" distance",e.value)}};break;case"choice":if(e.op==="is")return{[o]:{$eq:Rt(o,e.value)}};break}throw new Error('"'+e.op+'" is not something '+o+" can be asked.")}function Yi(e,n,i=new Date){let r=e.items.map(o=>o.kind==="group"?Yi(o,n,i):Ji(o,n,i)).filter(o=>o!==null);return r.length?r.length===1?r[0]:{[e.conj==="or"?"$or":"$and"]:r}:null}var Ne=e=>!!e&&typeof e=="object"&&!Array.isArray(e);function jn(e){let n=Ne(e)&&e.__type==="Date"?e.iso:typeof e=="string"?e:null;if(typeof n!="string")return null;let i=new Date(n);return isNaN(i.getTime())?null:i}function Ml(e){if(!Ne(e))return null;let n=Object.keys(e);if(n.length!==1||n[0].charAt(0)==="$")return null;let i=e[n[0]];return Ne(i)&&Object.keys(i).length===1&&i.$exists===!1?n[0]:null}function Bl(e,n,i){if(e.length!==2||!Ne(e[0]))return null;let r=Ml(e[1]),o=Object.keys(e[0]);if(!r||o.length!==1||o[0]!==r)return null;let s=n.find(c=>c.name===r);if(!s)return null;let l=e[0][r];if(s.kind==="list")return Ne(l)&&Object.keys(l).length===1&&Array.isArray(l.$eq)&&l.$eq.length===0?{kind:"cond",field:r,op:"empty"}:null;let a=Zo(s,l,i);return a?s.kind==="text"&&a.op==="is"&&a.value===""?{kind:"cond",field:r,op:"empty"}:s.kind==="text"&&Ne(l)&&"$ne"in l||s.kind==="number"&&Ne(l)&&"$ne"in l||s.kind==="boolean"&&a.op==="no"?a:null:null}function Hl(e,n){if(e.length!==2||!Ne(e[0])||!Ne(e[1]))return null;let i=Object.keys(e[0]),r=Object.keys(e[1]);if(i.length!==1||r.length!==1||i[0]!==r[0])return null;let o=n.find(a=>a.name===i[0]);if(!o||o.kind!=="text")return null;let s=e[0][i[0]],l=e[1][r[0]];return Ne(s)&&Object.keys(s).length===1&&s.$exists===!0&&Ne(l)&&Object.keys(l).length===1&&l.$ne===""?{kind:"cond",field:i[0],op:"notEmpty"}:null}function jl(e){let n="";for(let i=0;i<e.length;i++){let r=e.charAt(i);if(r==="\\\\"){let o=e.charAt(i+1);if(!/[.*+?^\${}()|[\\]\\\\]/.test(o))return null;n+=o,i++}else{if(/[.*+?^\${}()|[\\]]/.test(r))return null;n+=r}}return n}function Wl(e,n,i){let r=Object.keys(n).sort().join(",");if(r==="$exists"&&n.$exists===!1)return{kind:"cond",field:e,op:"empty"};if(r==="$lt"){let o=jn(n.$lt);return o&&Hn(o)?{kind:"cond",field:e,op:"before",value:Tt(o)}:null}if(r==="$gte"){let o=jn(n.$gte);return o&&Hn(o)?{kind:"cond",field:e,op:"after",value:Tt(Re(o,-1))}:null}if(r==="$gte,$lt"){let o=jn(n.$gte),s=jn(n.$lt);if(!o||!s||!Hn(o)||!Hn(s)||s<=o)return null;for(let l of Wn){let[a,c]=Wt(l.id,i);if(a.getTime()===o.getTime()&&c.getTime()===s.getTime())return{kind:"cond",field:e,op:"within",value:l.id}}return Tt(Re(o,1))===Tt(s)?{kind:"cond",field:e,op:"on",value:Tt(o)}:{kind:"cond",field:e,op:"between",value:Tt(o),value2:Tt(Re(s,-1))}}return null}function Zo(e,n,i){let r=e.name,o=Ne(n)&&Object.keys(n).some(a=>a.charAt(0)==="$"||a==="contains")?n:{$eq:n},s=Object.keys(o).sort().join(","),l=(a,c={})=>({kind:"cond",field:r,op:a,...c});switch(e.kind){case"text":if(s==="$eq"&&typeof o.$eq=="string")return l("is",{value:o.$eq});if(s==="$ne"&&typeof o.$ne=="string")return l("isNot",{value:o.$ne});if(s==="contains"&&typeof o.contains=="string")return l("contains",{value:o.contains});if(s==="$regex"&&typeof o.$regex=="string"&&o.$regex.charAt(0)==="^"){let a=jl(o.$regex.slice(1));return a===null?null:l("startsWith",{value:a})}return s==="$in"&&Array.isArray(o.$in)&&o.$in.every(a=>typeof a=="string")?l("oneOf",{values:o.$in.slice()}):s==="$exists"&&o.$exists===!1?l("empty"):null;case"number":{let a={$eq:"eq",$ne:"ne",$lt:"lt",$lte:"lte",$gt:"gt",$gte:"gte"};return a[s]&&typeof o[s]=="number"?l(a[s],{value:String(o[s])}):s==="$gte,$lte"&&typeof o.$gte=="number"&&typeof o.$lte=="number"?l("between",{value:String(o.$gte),value2:String(o.$lte)}):s==="$exists"&&o.$exists===!1?l("empty"):null}case"boolean":return s==="$eq"&&typeof o.$eq=="boolean"?l(o.$eq?"yes":"no"):null;case"date":return Wl(r,o,i);case"link":{if(s==="$exists"&&o.$exists===!1)return l("empty");if(s!=="$eq")return null;let a=o.$eq;return Ne(a)&&a.__type==="Pointer"&&typeof a.objectId=="string"?l("is",{value:a.objectId}):typeof a=="string"?l("is",{value:a}):null}case"list":if(s==="contains"&&typeof o.contains=="string")try{let a=JSON.parse(o.contains);return typeof a=="string"?l("contains",{value:a}):null}catch{return null}return null;case"choice":return s==="$eq"&&typeof o.$eq=="string"?l("is",{value:o.$eq}):null;case"location":{if(s!=="$maxDistanceInKilometers,$nearSphere")return null;let a=o.$nearSphere;return!Ne(a)||typeof a.latitude!="number"||typeof a.longitude!="number"||typeof o.$maxDistanceInKilometers!="number"?null:l("near",{lat:String(a.latitude),lng:String(a.longitude),value:String(o.$maxDistanceInKilometers)})}}return null}function Ul(e,n,i){let r=[];for(let o of e){let s=Kn(o,n,i);if(!s)return null;s.items.length===1&&s.items[0].kind==="cond"?r.push(s.items[0]):r.push(s)}return r}function Kn(e,n,i=new Date){if(e==null)return{kind:"group",conj:"and",items:[]};if(!Ne(e))return null;let r=Object.keys(e);if(!r.length)return{kind:"group",conj:"and",items:[]};if(r.length===1&&(r[0]==="$and"||r[0]==="$or")){let s=e[r[0]];if(!Array.isArray(s))return null;let l=r[0]==="$or"?Bl(s,n,i):Hl(s,n);if(l)return{kind:"group",conj:"and",items:[l]};let a=Ul(s,n,i);return a?{kind:"group",conj:r[0]==="$or"?"or":"and",items:a}:null}let o=[];for(let s of r){if(s==="$and"||s==="$or"){let c=Kn({[s]:e[s]},n,i);if(!c)return null;c.items.length===1&&c.items[0].kind==="cond"?o.push(c.items[0]):o.push(c);continue}let l=n.find(c=>c.name===s);if(!l)return null;let a=Zo(l,e[s],i);if(!a)return null;o.push(a)}return{kind:"group",conj:"and",items:o}}function qi(e){if(!e)return"\u2026";try{return De(e).toLocaleDateString(void 0,{day:"numeric",month:"short",year:"numeric"})}catch{return e}}function Kl(e,n,i){let r=n.find(a=>a.name===e.field);if(!r)return e.field;let o=Un(r.kind,e.op),s=o?o.label:e.op,l=e.value===void 0||e.value===""?"\u2026":e.value;switch(o?o.arity:"none"){case"none":return e.field+" "+s;case"many":return e.field+" "+s+" "+((e.values||[]).join(", ")||"\u2026");case"window":return e.field+" "+s+" "+(Wn.find(a=>a.id===e.value)||{label:l}).label;case"near":return e.field+" is within "+l+" km of "+(e.lat||"\u2026")+", "+(e.lng||"\u2026");case"two":return e.field+" "+s+" "+(r.kind==="date"?qi(e.value):l)+" and "+(r.kind==="date"?qi(e.value2):e.value2||"\u2026");default:return r.kind==="date"?e.field+" "+s+" "+qi(e.value):r.kind==="link"&&i&&e.value?e.field+" "+s+" "+i(e.field,e.value):r.kind==="choice"&&e.value?e.field+" "+s+" "+((r.options||[]).find(a=>a.value===e.value)||{label:l}).label:e.field+" "+s+" "+l}}function Et(e,n,i){return e.items.map(o=>o.kind==="group"?"("+Et(o,n,i)+")":Kl(o,n,i)).join(e.conj==="or"?" or ":" and ")}async function Vl(e){let n=await S("GET","/admin/roles"),i=e.trim().toLowerCase();return(n.roles||[]).map(r=>r.name).filter(r=>!i||r.toLowerCase().includes(i))}async function Gl(e){return(await S("GET","/admin/users?limit=20&q="+I(e.trim()))).users||[]}function Ut({state:e,onChange:n,disabled:i,collection:r}){let[o,s]=m({}),l=e.mode==="rows"?e.rows.filter(p=>p.key!=="*"&&!pt(p.key)).map(p=>p.key):[];j(()=>{l.filter(p=>!o[p]).forEach(p=>S("GET","/admin/users/"+I(p)).then(d=>s(f=>({...f,[p]:Bn(d)}))).catch(()=>s(d=>({...d,[p]:"a person who no longer exists"}))))},[l.join(",")]);let a=r?t("a",{class:"acl-more",href:"#/permissions/"+I(r),children:"This collection\u2019s permissions also apply \u2192"}):null;if(e.mode==="public")return t("div",{class:"acl-card",children:[t("div",{class:"acl-state",children:[t("b",{children:"Everyone"}),t("span",{class:"hint",children:"This record has no restrictions of its own: whoever the collection\u2019s permissions let in can see and change it."})]}),i?null:t("button",{type:"button",class:"btn tiny",onClick:()=>n(Mo()),children:"Restrict who can see this"}),a]});let c=p=>p.key==="*"?"Everyone":pt(p.key)?Fi(p.key):o[p.key]||"\u2026",u=p=>p.key==="*"?"":pt(p.key)?"role":"person";return t("div",{class:"acl-card",children:[Bi(e)?t("div",{class:"acl-state",children:[t("b",{children:"No one"}),t("span",{class:"hint",children:"Nothing is granted, so only the admin credential can see or change this record."})]}):null,t("table",{class:"acl-table",children:[t("thead",{children:t("tr",{children:[t("th",{children:"Who"}),t("th",{children:"Can see"}),t("th",{children:"Can change"}),t("th",{})]})}),t("tbody",{children:e.rows.map(p=>t("tr",{"data-acl-key":p.key==="*"?"everyone":u(p),children:[t("td",{children:[c(p)," ",u(p)?t("span",{class:"chip type",children:u(p)}):null]}),t("td",{children:t("input",{type:"checkbox","aria-label":c(p)+" can see",checked:p.read,disabled:i,onChange:d=>n(ji(e,p.key,{read:d.currentTarget.checked}))})}),t("td",{children:t("input",{type:"checkbox","aria-label":c(p)+" can change",checked:p.write,disabled:i,onChange:d=>n(ji(e,p.key,{write:d.currentTarget.checked}))})}),t("td",{children:p.key==="*"||i?null:t("button",{type:"button",class:"chip-x","aria-label":"Remove "+c(p),onClick:()=>n(Bo(e,p.key)),children:"\u2715"})})]},p.key))})]}),t("span",{class:"hint",children:"\u201CSigned-in users\u201D is not something a record can name: a record grants everyone, a role, or a person."}),i?null:t("div",{class:"acl-add",children:[t(Ce,{fetch:p=>Vl(p).then(d=>d.filter(f=>!e.rows.some(b=>b.key==="role:"+f))),label:p=>p,keyOf:p=>p,value:null,placeholder:"Add a role\u2026",emptyText:"No other role matches.",onPick:p=>p&&n(Hi(e,"role:"+p))}),t(Ce,{fetch:p=>Gl(p).then(d=>d.filter(f=>!e.rows.some(b=>b.key===f.objectId))),label:p=>Bn(p),keyOf:p=>p.objectId,value:null,placeholder:"Add a person\u2026",emptyText:"No other person matches.",onPick:p=>{p&&(s(d=>({...d,[p.objectId]:Bn(p)})),n(Hi(e,p.objectId)))}}),t("button",{type:"button",class:"btn tiny",onClick:()=>n({mode:"public"}),children:"Remove the restrictions"})]}),a]})}var Vn=e=>!!e&&typeof e=="object"&&!Array.isArray(e);function ql(e){if(!e.length)return"text";let n=typeof e[0],i=n==="number"?"number":n==="boolean"?"boolean":n==="string"?"text":null;return i&&e.every(r=>typeof r==typeof e[0])?i:null}function We(e,n){let i=it(n);switch(e.type){case"Boolean":return i===!0;case"Number":return i==null?"":String(i);case"Date":return i?Bt(i):"";case"Pointer":return i==null?"":String(i);case"Array":{let r=Array.isArray(i)?i:[],o=ql(r);return o?{t:"list",itemType:o,items:r.map(s=>o==="boolean"?s===!0:String(s)),json:null}:{t:"list",itemType:"text",items:[],json:JSON.stringify(r,null,2)}}case"Object":return{t:"kv",rows:Vn(i)?Ht(i):[],json:null};case"GeoPoint":return Vn(i)&&typeof i.latitude=="number"?{t:"geo",lat:String(i.latitude),lng:String(i.longitude)}:{t:"geo",lat:"",lng:""};case"File":return{t:"file",file:Vn(i)&&typeof i.url=="string"?{__type:"File",name:String(i.name||""),url:i.url}:null};case"ACL":return{t:"acl",acl:Mi(n)};default:return i==null?"":typeof i=="object"?JSON.stringify(i):String(i)}}function Qo(e,n){let i=n.trim();if(i)try{return JSON.parse(i)}catch(r){throw new Error(e.name+" is not valid JSON: "+r.message)}}function $e(e,n){switch(e.type){case"Boolean":return n===!0;case"Number":{let i=String(n).trim();if(i==="")return;let r=Number(i);if(isNaN(r))throw new Error(e.name+" must be a number.");return r}case"Date":{let i=String(n);if(!i)return;let r=new Date(i);if(isNaN(r.getTime()))throw new Error(e.name+" is not a date.");return r.toISOString()}case"Pointer":return String(n)||void 0;case"Array":{let i=n;if(i.json!==null){let r=Qo(e,i.json);if(r!==void 0&&!Array.isArray(r))throw new Error(e.name+" must be a list, written [ \u2026 ].");return r}return i.items.length?i.items.map((r,o)=>{if(i.itemType==="boolean")return r===!0;let s=String(r);if(i.itemType==="number"){let l=Number(s.trim());if(s.trim()===""||isNaN(l))throw new Error(e.name+": item "+(o+1)+" must be a number.");return l}return s}):void 0}case"Object":{let i=n;if(i.json!==null){let r=Qo(e,i.json);if(r!==void 0&&!Vn(r))throw new Error(e.name+" must be an object, written { \u2026 }.");return r}if(!i.rows.length)return;try{return Ct(i.rows)}catch(r){throw new Error(e.name+": "+r.message)}}case"GeoPoint":{let i=n,r=i.lat.trim(),o=i.lng.trim();if(!r&&!o)return;let s=Number(r),l=Number(o);if(!r||!o||isNaN(s)||isNaN(l))throw new Error(e.name+" needs both a latitude and a longitude.");if(s<-90||s>90)throw new Error(e.name+": latitude is between -90 and 90.");if(l<-180||l>180)throw new Error(e.name+": longitude is between -180 and 180.");return{__type:"GeoPoint",latitude:s,longitude:l}}case"File":{let i=n;return i.file?{__type:"File",name:i.file.name,url:i.file.url}:void 0}case"ACL":return Fo(n.acl);default:return String(n)}}function ht(e){let n=[];for(let r of Object.keys(e)){if(Ue(r)||r==="ACL")continue;let o=e[r];typeof o=="string"&&o&&n.push(o)}let i=String(e.objectId||"");return{objectId:i,label:n[0]||nt(i),detail:n[1]||""}}async function Kt(e,n){let r=((await S("GET","/api/"+I(e)+"?limit=200&sort="+I('["-createdAt"]'))).results||[]).map(ht),o=n.trim().toLowerCase();return(o?r.filter(s=>(s.label+" "+s.detail).toLowerCase().includes(o)):r).slice(0,20)}function Vt({col:e,raw:n,onChange:i,autoFocus:r,onCommit:o,onCancel:s,disabled:l,id:a}){let c=d=>{!o&&!s||(d.key==="Enter"&&o&&!(d.target instanceof HTMLTextAreaElement)?(d.preventDefault(),o()):d.key==="Escape"&&s&&(d.preventDefault(),s()))},p={id:a,disabled:l,autoFocus:r,onKeyDown:c,onBlur:()=>{o&&o()},"aria-label":e.name};switch(e.type){case"Boolean":return t("label",{class:"switch",children:[t("input",{type:"checkbox",role:"switch",...p,onBlur:void 0,checked:n===!0,onChange:d=>i(d.currentTarget.checked)}),t("span",{class:"switch-track","aria-hidden":"true"}),n===!0?"Yes":"No"]});case"Number":return t("input",{type:"number",step:e.whole?1:"any",min:e.min,max:e.max,...p,value:String(n),title:"\u2325\u2191 / \u2325\u2193 nudges by 10",onKeyDown:d=>{if(d.altKey&&(d.key==="ArrowUp"||d.key==="ArrowDown")){d.preventDefault();let f=Number(String(n).trim()||"0");isNaN(f)||i(String(f+(d.key==="ArrowUp"?10:-10)));return}c(d)},onInput:d=>i(d.currentTarget.value)});case"Date":return t(zl,{col:e,raw:String(n),onChange:i,common:p,inline:!!o});case"Pointer":return t(Jl,{col:e,raw:String(n),onChange:i,onCommit:o,disabled:l,autoFocus:r});case"Array":return t(Yl,{col:e,raw:n,onChange:i,disabled:l});case"Object":return t(Xl,{col:e,raw:n,onChange:i,disabled:l});case"GeoPoint":return t(Zl,{col:e,raw:n,onChange:i,disabled:l});case"File":return t(Ql,{col:e,raw:n,onChange:i,disabled:l});case"ACL":return t(Ut,{state:n.acl,onChange:d=>i({t:"acl",acl:d}),disabled:l});default:{let d=String(n);if(e.allowed&&e.allowed.length){let f=e.allowed.map(String).indexOf(d)!==-1||d==="";return t("select",{...p,value:d,onChange:b=>i(b.currentTarget.value),children:[t("option",{value:"",children:"\u2014 none \u2014"}),e.allowed.map(b=>t("option",{value:String(b),children:String(b)},String(b))),f?null:t("option",{value:d,children:d+" (not one of the choices)"})]})}return d.indexOf(\`
65019
- \`)!==-1&&!o?t("textarea",{id:a,disabled:l,autoFocus:r,rows:4,"aria-label":e.name,value:d,onInput:f=>i(f.currentTarget.value)}):t("input",{type:e.looksLike==="email"?"email":e.looksLike==="url"?"url":"text",maxLength:e.maxLength,...p,value:d,onInput:f=>i(f.currentTarget.value)})}}}function $l(){try{return Intl.DateTimeFormat().resolvedOptions().timeZone||""}catch{return""}}function zl({col:e,raw:n,onChange:i,common:r,inline:o}){let[s,l]=n?n.split("T"):["",""],a=(c,u)=>i(c?c+"T"+(u||"00:00"):"");return o?t("input",{type:"datetime-local",...r,value:n,onInput:c=>i(c.currentTarget.value)}):t("div",{class:"field-line date-control",children:[t("input",{type:"date",id:r.id,disabled:r.disabled,autoFocus:r.autoFocus,"aria-label":e.name+" date",value:s||"",onInput:c=>a(c.currentTarget.value,l)}),t("input",{type:"time",disabled:r.disabled,"aria-label":e.name+" time",value:l||"",onInput:c=>a(s||Bt(new Date().toISOString()).split("T")[0],c.currentTarget.value)}),t("button",{type:"button",class:"btn tiny",disabled:r.disabled,onClick:()=>i(Bt(new Date().toISOString())),children:"Now"}),n?t("button",{type:"button",class:"btn tiny",disabled:r.disabled,"aria-label":"Clear "+e.name,onClick:()=>i(""),children:"\u2715"}):null,t("span",{class:"hint",children:$l()})]})}function Jl({col:e,raw:n,onChange:i,onCommit:r,disabled:o,autoFocus:s}){let[l,a]=m(n?{objectId:n,label:nt(n),detail:""}:null);return j(()=>{if(!n){a(null);return}l&&l.objectId===n&&l.label!==nt(n)||e.targetClass&&S("GET","/api/"+I(e.targetClass)+"/"+I(n)).then(c=>a(ht(c))).catch(()=>a({objectId:n,label:nt(n),detail:""}))},[n,e.targetClass]),t("div",{class:"field-line",children:[t(Ce,{fetch:c=>e.targetClass?Kt(e.targetClass,c):Promise.resolve([]),label:c=>c.label,detail:c=>c.detail,keyOf:c=>c.objectId,value:l,disabled:o,autoFocus:s,placeholder:"Search "+(e.targetClass||"records")+"\u2026",emptyText:"No "+(e.targetClass||"record")+" matches.",onPick:c=>{a(c),i(c?c.objectId:""),r&&setTimeout(r,0)}}),n&&e.targetClass&&!r?t("a",{class:"btn tiny",href:"#/collections/"+I(e.targetClass)+"/"+I(n),title:"Open this "+e.targetClass,children:"open \u2197"}):null]})}function Gn({on:e,onToggle:n,disabled:i}){return t("button",{type:"button",class:"btn tiny json-toggle",disabled:i,onClick:n,children:e?"Back to the form":"Edit as JSON"})}function Yl({col:e,raw:n,onChange:i,disabled:r}){let[o,s]=m(null);return n.json!==null?t("div",{class:"json-edit",children:[t("textarea",{rows:5,spellcheck:!1,disabled:r,"aria-label":e.name+" as JSON",value:n.json,onInput:l=>i({...n,json:l.currentTarget.value})}),t("div",{class:"field-line",children:[t(Gn,{on:!0,disabled:r,onToggle:()=>{try{let l=$e(e,n),a=We(e,l===void 0?[]:l);if(a.json!==null)return s("This list holds values a row cannot edit (objects, lists, or mixed types). It stays JSON.");s(null),i(a)}catch(l){s(l.message)}}}),o?t("span",{class:"chips-problem",children:o}):null]})]}):t("div",{class:"struct-edit",children:[t(Xe,{rows:n.items,onChange:l=>i({...n,items:l}),blank:()=>n.itemType==="boolean"?!1:"",addLabel:"+ Add an item",disabled:r,renderRow:(l,a,c)=>n.itemType==="boolean"?t("label",{class:"check",children:[t("input",{type:"checkbox","aria-label":e.name+" item "+(c+1),checked:l===!0,disabled:r,onChange:u=>a(u.currentTarget.checked)}),l===!0?"Yes":"No"]}):t("input",{type:n.itemType==="number"?"number":"text",step:"any",class:"kv-value","aria-label":e.name+" item "+(c+1),value:String(l),disabled:r,onInput:u=>a(u.currentTarget.value)})}),t("div",{class:"field-line",children:[t("select",{"aria-label":e.name+" item type",value:n.itemType,disabled:r||n.items.length>0,title:n.items.length?"Remove the items to change what the list holds.":void 0,onChange:l=>i({...n,itemType:l.currentTarget.value}),children:[t("option",{value:"text",children:"A list of text"}),t("option",{value:"number",children:"A list of numbers"}),t("option",{value:"boolean",children:"A list of yes / no"})]}),t(Gn,{on:!1,disabled:r,onToggle:()=>i({...n,json:JSON.stringify(es(()=>$e(e,n))||[],null,2)})})]})]})}function es(e){try{return e()}catch{return}}function Xl({col:e,raw:n,onChange:i,disabled:r}){let[o,s]=m(null);return n.json!==null?t("div",{class:"json-edit",children:[t("textarea",{rows:5,spellcheck:!1,disabled:r,"aria-label":e.name+" as JSON",value:n.json,onInput:l=>i({...n,json:l.currentTarget.value})}),t("div",{class:"field-line",children:[t(Gn,{on:!0,disabled:r,onToggle:()=>{try{let l=$e(e,n);s(null),i({t:"kv",rows:Ht(l||{}),json:null})}catch(l){s(l.message)}}}),o?t("span",{class:"chips-problem",children:o}):null]})]}):t("div",{class:"struct-edit",children:[t(St,{rows:n.rows,onChange:l=>i({...n,rows:l}),disabled:r}),t("div",{class:"field-line",children:t(Gn,{on:!1,disabled:r,onToggle:()=>i({...n,json:JSON.stringify(es(()=>$e(e,n))||{},null,2)})})})]})}function Zl({col:e,raw:n,onChange:i,disabled:r}){let[o,s]=m(null),l=()=>{if(!navigator.geolocation)return s("This browser cannot tell where you are.");navigator.geolocation.getCurrentPosition(a=>{s(null),i({t:"geo",lat:a.coords.latitude.toFixed(6),lng:a.coords.longitude.toFixed(6)})},a=>s(a.message||"Your location was not shared."))};return t("div",{class:"field-line geo-control",children:[t("input",{type:"number",step:"any",min:-90,max:90,placeholder:"Latitude","aria-label":e.name+" latitude",value:n.lat,disabled:r,onInput:a=>i({...n,lat:a.currentTarget.value})}),t("input",{type:"number",step:"any",min:-180,max:180,placeholder:"Longitude","aria-label":e.name+" longitude",value:n.lng,disabled:r,onInput:a=>i({...n,lng:a.currentTarget.value})}),t("button",{type:"button",class:"btn tiny",disabled:r,onClick:l,children:"Use my location"}),o?t("span",{class:"chips-problem",children:o}):null]})}function Xi(e){let n=/^[a-z]+:\\/\\/[^/]+(\\/.*)$/i.exec(e);return n?n[1]:e}function Ql({col:e,raw:n,onChange:i,disabled:r}){let[o,s]=m(!1),[l,a]=m(null),[c,u]=m(null),[p,d]=m(!1),f=ae(null),b=async w=>{s(!0),a(null);try{let x={...ut(_e.get().credential),"content-type":w.type||"application/octet-stream"},E=await fetch("/files/"+I(w.name),{method:"POST",headers:x,body:w}),C=await E.json().catch(()=>null);if(E.status>=400||!C||!C.url)throw new Error(C&&(C.error||C.message)||"The upload was refused (HTTP "+E.status+").");u(w.size),i({t:"file",file:{__type:"File",name:C.name,url:C.url}})}catch(x){a(x.message)}finally{s(!1)}},g=w=>{w&&w[0]&&b(w[0])},[k,y]=m(null),T=n.file?n.file.name:"";if(j(()=>{y(null),T&&S("GET","/files/"+I(T)+"/sign").then(w=>y(Xi(w.url))).catch(()=>y(n.file?Xi(n.file.url):null))},[T]),n.file){let w=/\\.(png|jpe?g|gif|webp|svg|avif)$/i.test(n.file.name),x=k||Xi(n.file.url);return t("div",{class:"file-card",children:[w&&k?t("img",{class:"file-thumb",src:k,alt:""}):t("span",{class:"file-icon","aria-hidden":"true",children:"\u25A4"}),t("span",{class:"file-text",children:[t("b",{children:rt(n.file.name)}),c!==null?t("span",{class:"hint",children:mt(c)}):null]}),t("a",{class:"btn tiny",href:x,download:rt(n.file.name),target:"_blank",rel:"noopener",children:"Download"}),r?null:t("button",{type:"button",class:"btn tiny","aria-label":"Remove "+e.name,onClick:()=>i({t:"file",file:null}),children:"\u2715"})]})}return t("div",{class:"dropzone"+(p?" over":""),role:"button",tabIndex:0,"aria-label":"Upload "+e.name,onClick:()=>!r&&f.current&&f.current.click(),onKeyDown:w=>{(w.key==="Enter"||w.key===" ")&&f.current&&(w.preventDefault(),f.current.click())},onDragOver:w=>{w.preventDefault(),d(!0)},onDragLeave:()=>d(!1),onDrop:w=>{w.preventDefault(),d(!1),r||g(w.dataTransfer?w.dataTransfer.files:null)},children:[t("input",{ref:f,type:"file",hidden:!0,disabled:r,onChange:w=>g(w.currentTarget.files)}),o?"Uploading\u2026":"Drop a file here, or click to choose one",l?t("span",{class:"chips-problem",children:l}):null]})}function ts({collection:e,id:n,col:i,disabled:r}){let[o,s]=m(null),[l,a]=m(null),c=i.targetClass||"",u=()=>{if(!c)return;let d={$relatedTo:{object:{__type:"Pointer",className:e,objectId:n},key:i.name}};S("GET","/api/"+I(c)+"?limit=200&where="+I(JSON.stringify(d))).then(f=>s((f.results||[]).map(ht))).catch(f=>a(f.message))};j(u,[e,n,i.name]);let p=(d,f)=>S("PUT","/classes/"+I(e)+"/"+I(n),{[i.name]:{__op:d,objects:[{__type:"Pointer",className:c,objectId:f}]}}).then(()=>{a(null),u()}).catch(b=>a(b.message));return c?t("div",{class:"chips relation",children:[t("div",{class:"chips-set",children:[(o||[]).map(d=>t("span",{class:"chip accent",children:[t("a",{href:"#/collections/"+I(c)+"/"+I(d.objectId),children:d.label}),r?null:t("button",{type:"button",class:"chip-x","aria-label":"Unlink "+d.label,onClick:()=>p("RemoveRelation",d.objectId),children:"\u2715"})]},d.objectId)),o&&!o.length?t("span",{class:"hint",children:"Nothing linked yet."}):null,o?null:t("span",{class:"hint",children:"Loading\u2026"})]}),r?null:t(Ce,{fetch:d=>Kt(c,d).then(f=>f.filter(b=>!(o||[]).some(g=>g.objectId===b.objectId))),label:d=>d.label,detail:d=>d.detail,keyOf:d=>d.objectId,value:null,placeholder:"Link a "+c+"\u2026",emptyText:"No other "+c+" matches.",onPick:d=>{d&&p("AddRelation",d.objectId)}}),t("span",{class:"hint",children:"Linking and unlinking are saved as you go."}),l?t("span",{class:"chips-problem",children:l}):null]}):t("span",{class:"hint",children:"This relation has no target collection."})}function gt({group:e,onChange:n,fields:i,depth:r=0,flat:o=!1}){if(!i.length)return null;let s=(c,u)=>n({...e,items:e.items.map((p,d)=>d===c?u:p)}),l=c=>n({...e,items:e.items.filter((u,p)=>p!==c)}),a=c=>n({...e,items:[...e.items,c]});return t("div",{class:"filter-group"+(r?" nested":""),children:[e.items.map((c,u)=>t("div",{class:"filter-line",children:[t("span",{class:"filter-conj",children:u===0?r?"":"Where":u===1&&!o?t("select",{"aria-label":"And or or",value:e.conj,onChange:p=>n({...e,conj:p.currentTarget.value}),children:[t("option",{value:"and",children:"and"}),t("option",{value:"or",children:"or"})]}):e.conj}),c.kind==="cond"?t(ec,{cond:c,fields:i,onChange:p=>s(u,p)}):t(gt,{group:c,fields:i,depth:r+1,onChange:p=>p.items.length?s(u,p):l(u)}),t("button",{type:"button",class:"btn tiny list-x","aria-label":"Remove this condition",title:"Remove",onClick:()=>l(u),children:"\u2715"})]},u)),t("div",{class:"filter-adds",children:[t("button",{type:"button",class:"btn tiny",onClick:()=>a(jt(i[0])),children:"+ Add condition"}),r<1&&!o?t("button",{type:"button",class:"btn tiny",onClick:()=>a({kind:"group",conj:e.conj==="and"?"or":"and",items:[jt(i[0])]}),children:"+ Add group"}):null]})]})}function ec({cond:e,fields:n,onChange:i}){let r=n.find(c=>c.name===e.field)||n[0],o=zi(r),s=Un(r.kind,e.op)||o[0],l=(c,u,p="text",d)=>t("input",{type:p,step:p==="number"?"any":void 0,class:"filter-value","aria-label":u,placeholder:d,value:e[c]||"",onInput:f=>i({...e,[c]:f.currentTarget.value})}),a=r.kind==="number"?"number":r.kind==="date"?"date":"text";return t("span",{class:"filter-cond",children:[t("select",{"aria-label":"Field",value:r.name,onChange:c=>{let u=n.find(p=>p.name===c.currentTarget.value);i(u.kind===r.kind?{...e,field:u.name,value:u.kind==="link"?"":e.value}:jt(u))},children:n.map(c=>t("option",{value:c.name,children:c.name},c.name))}),t("select",{"aria-label":"Operator",value:s.id,onChange:c=>i(Yo(e,r.kind,c.currentTarget.value)),children:o.map(c=>t("option",{value:c.id,children:c.label},c.id))}),s.arity==="one"&&r.kind==="link"?t(tc,{field:r,id:e.value||"",onPick:c=>i({...e,value:c})}):null,s.arity==="one"&&r.kind==="choice"?t("select",{class:"filter-value","aria-label":r.name+" value",value:e.value||"",onChange:c=>i({...e,value:c.currentTarget.value}),children:[t("option",{value:"",children:"pick\u2026"}),(r.options||[]).map(c=>t("option",{value:c.value,children:c.label},c.value))]}):null,s.arity==="one"&&r.kind!=="link"&&r.kind!=="choice"?l("value",r.name+" value",a,r.kind==="list"?"an item":void 0):null,s.arity==="two"?t(W,{children:[l("value",r.name+" from",a),t("span",{class:"hint",children:"and"}),l("value2",r.name+" to",a)]}):null,s.arity==="many"?t(Ie,{items:e.values||[],onChange:c=>i({...e,values:c}),placeholder:"Type a value, Enter",addLabel:"Add"}):null,s.arity==="window"?t("select",{"aria-label":r.name+" window",value:e.value||"today",onChange:c=>i({...e,value:c.currentTarget.value}),children:Wn.map(c=>t("option",{value:c.id,children:c.label},c.id))}):null,s.arity==="near"?t(W,{children:[l("value",r.name+" distance in km","number"),t("span",{class:"hint",children:"km of"}),l("lat",r.name+" latitude","number","latitude"),l("lng",r.name+" longitude","number","longitude")]}):null]})}function tc({field:e,id:n,onPick:i}){let[r,o]=m(null);return j(()=>{if(!n||!e.targetClass)return o(null);r&&r.objectId===n||S("GET","/api/"+I(e.targetClass)+"/"+I(n)).then(s=>o(ht(s))).catch(()=>o({objectId:n,label:"a record that no longer exists",detail:""}))},[n,e.targetClass]),t(Ce,{fetch:s=>e.targetClass?Kt(e.targetClass,s):Promise.resolve([]),label:s=>s.label,detail:s=>s.detail,keyOf:s=>s.objectId,value:r,placeholder:"Pick a "+(e.targetClass||"record")+"\u2026",onPick:s=>{o(s),i(s?s.objectId:"")}})}var fn=[1,5,10,15,30],Zi=[{id:"minutes",label:"Every few minutes"},{id:"hourly",label:"Every hour"},{id:"daily",label:"Every day"},{id:"weekly",label:"Every week"},{id:"monthly",label:"Every month"},{id:"custom",label:"Custom"}],Qi=[{n:1,short:"Mon",long:"Monday"},{n:2,short:"Tue",long:"Tuesday"},{n:3,short:"Wed",long:"Wednesday"},{n:4,short:"Thu",long:"Thursday"},{n:5,short:"Fri",long:"Friday"},{n:6,short:"Sat",long:"Saturday"},{n:0,short:"Sun",long:"Sunday"}],is={mode:"daily",everyMinutes:15,minute:0,time:"09:00",days:[1,2,3,4,5],dayOfMonth:1,custom:""},nc={"@yearly":"0 0 1 1 *","@annually":"0 0 1 1 *","@monthly":"0 0 1 * *","@weekly":"0 0 * * 0","@daily":"0 0 * * *","@midnight":"0 0 * * *","@hourly":"0 * * * *","@minutely":"* * * * *"},mn=/^\\d+$/,ic=/^(\\d{1,2}):(\\d{2})$/;function ns(e){return(e<10?"0":"")+e}function Gt(e){let n=ic.exec(e.trim());if(!n)return null;let i=Number(n[1]),r=Number(n[2]);return i>23||r>59?null:[i,r]}function hn(e){let n=new Set;return e.map(i=>i===7?0:i).filter(i=>i>=0&&i<=6&&!n.has(i)&&!!n.add(i)).sort((i,r)=>(i===0?7:i)-(r===0?7:r))}function qn(e){switch(e.mode){case"minutes":return fn.indexOf(e.everyMinutes)===-1?"Pick how many minutes.":null;case"hourly":return Number.isInteger(e.minute)&&e.minute>=0&&e.minute<=59?null:"Pick a minute past the hour (0\u201359).";case"daily":return Gt(e.time)?null:"Pick a time of day.";case"weekly":return Gt(e.time)?e.days.length?null:"Pick at least one day.":"Pick a time of day.";case"monthly":return Gt(e.time)?Number.isInteger(e.dayOfMonth)&&e.dayOfMonth>=1&&e.dayOfMonth<=31?null:"Pick a day of the month (1\u201331).":"Pick a time of day.";default:return e.custom.trim()?null:"Type a cron expression."}}function er(e){if(qn(e))return"";switch(e.mode){case"minutes":return"*/"+e.everyMinutes+" * * * *";case"hourly":return e.minute+" * * * *";case"daily":{let[n,i]=Gt(e.time);return i+" "+n+" * * *"}case"weekly":{let[n,i]=Gt(e.time),r=hn(e.days).slice().sort((o,s)=>o-s);return i+" "+n+" * * "+r.join(",")}case"monthly":{let[n,i]=Gt(e.time);return i+" "+n+" "+e.dayOfMonth+" * *"}default:return e.custom.trim()}}function rc(e){let n=[];for(let i of e.split(",")){let r=i.trim(),o=/^(\\d+)-(\\d+)$/.exec(r);if(o){let s=Number(o[1]),l=Number(o[2]);if(s>l||l>7)return null;for(let a=s;a<=l;a++)n.push(a)}else if(mn.test(r)&&Number(r)<=7)n.push(Number(r));else return null}return n.length?hn(n):null}function qt(e){let n=(e||"").trim(),i={...is,custom:n};if(!n)return{...i,mode:"custom"};let r={...i,mode:"custom"},o=n.startsWith("@")?nc[n.toLowerCase()]:n;if(!o)return r;let s=o.split(/\\s+/);if(s.length!==5)return r;let[l,a,c,u,p]=s;if(u!=="*")return r;if(a==="*"&&c==="*"&&p==="*"){if(l==="*")return{...i,mode:"minutes",everyMinutes:1};let f=/^\\*\\/(\\d+)$/.exec(l);return f&&fn.indexOf(Number(f[1]))!==-1?{...i,mode:"minutes",everyMinutes:Number(f[1])}:mn.test(l)&&Number(l)<=59?{...i,mode:"hourly",minute:Number(l)}:r}if(!mn.test(l)||!mn.test(a)||Number(l)>59||Number(a)>23)return r;let d=ns(Number(a))+":"+ns(Number(l));if(c==="*"&&p==="*")return{...i,mode:"daily",time:d};if(c==="*"){let f=rc(p);return f?{...i,mode:"weekly",time:d,days:f}:r}return p==="*"&&mn.test(c)&&Number(c)>=1&&Number(c)<=31?{...i,mode:"monthly",time:d,dayOfMonth:Number(c)}:r}var oc=250;function _t(e){let[n,i]=m(()=>e.value?qt(e.value):qt("0 9 * * *")),[r,o]=m(null),[s,l]=m(!1),a=ae(null),c=ae(0),u=er(n),p=qn(n);j(()=>{a.current!==u&&(a.current=u,e.onChange(u))},[u]),j(()=>{e.value!==a.current&&(!e.value&&a.current!==null||i(e.value?qt(e.value):qt("0 9 * * *")))},[e.value]),j(()=>{if(!u){o(null);return}let g=++c.current;l(!0);let k=setTimeout(()=>{e.preview(u).then(y=>{g===c.current&&(o(y),l(!1))}).catch(y=>{g===c.current&&(o({valid:!1,error:y.message,words:null,next:[]}),l(!1))})},oc);return()=>clearTimeout(k)},[u]);let d=g=>i({...n,...g}),f=!!e.disabled,b="sched-mode-"+(e.id||"x");return t("div",{class:"sched",id:e.id,children:[t("div",{class:"sched-modes",role:"radiogroup","aria-label":"How often",children:Zi.map(g=>t("label",{class:"sched-mode"+(n.mode===g.id?" on":""),children:[t("input",{type:"radio",name:b,value:g.id,checked:n.mode===g.id,disabled:f,onChange:()=>d(g.id==="custom"&&u?{mode:g.id,custom:u}:{mode:g.id})}),g.label]},g.id))}),t("div",{class:"sched-controls",children:[n.mode==="minutes"?t("div",{class:"field-line",children:[t("span",{children:"Every"}),t("select",{"aria-label":"Minutes",value:String(n.everyMinutes),disabled:f,onChange:g=>d({everyMinutes:Number(g.currentTarget.value)}),children:fn.map(g=>t("option",{value:String(g),children:g===1?"minute":g+" minutes"},g))})]}):null,n.mode==="hourly"?t("div",{class:"field-line",children:[t("span",{children:"Every hour at"}),t("input",{type:"number",min:0,max:59,step:1,"aria-label":"Minute past the hour",class:"sched-minute",value:String(n.minute),disabled:f,onInput:g=>d({minute:Number(g.currentTarget.value)})}),t("span",{children:"minutes past"})]}):null,n.mode==="daily"?t("div",{class:"field-line",children:[t("span",{children:"Every day at"}),t("input",{type:"time","aria-label":"Time of day",value:n.time,disabled:f,onInput:g=>d({time:g.currentTarget.value})})]}):null,n.mode==="weekly"?t(W,{children:[t("div",{class:"sched-days",role:"group","aria-label":"Days of the week",children:Qi.map(g=>{let k=n.days.indexOf(g.n)!==-1;return t("label",{class:"sched-day"+(k?" on":""),title:g.long,children:[t("input",{type:"checkbox","aria-label":g.long,checked:k,disabled:f,onChange:y=>d({days:hn(y.currentTarget.checked?[...n.days,g.n]:n.days.filter(T=>T!==g.n))})}),g.short]},g.n)})}),t("div",{class:"field-line",children:[t("span",{children:"at"}),t("input",{type:"time","aria-label":"Time of day",value:n.time,disabled:f,onInput:g=>d({time:g.currentTarget.value})})]})]}):null,n.mode==="monthly"?t("div",{class:"field-line",children:[t("span",{children:"On day"}),t("input",{type:"number",min:1,max:31,step:1,"aria-label":"Day of the month",class:"sched-minute",value:String(n.dayOfMonth),disabled:f,onInput:g=>d({dayOfMonth:Number(g.currentTarget.value)})}),t("span",{children:"of every month at"}),t("input",{type:"time","aria-label":"Time of day",value:n.time,disabled:f,onInput:g=>d({time:g.currentTarget.value})}),n.dayOfMonth>28?t(H,{children:["Months without day ",n.dayOfMonth," are skipped."]}):null]}):null,n.mode==="custom"?t("div",{class:"field",children:[t("input",{type:"text",class:"mono","aria-label":"Cron expression",placeholder:"minute hour day-of-month month day-of-week",value:n.custom,disabled:f,onInput:g=>d({custom:g.currentTarget.value})}),t(H,{children:["Five fields: minute (0\u201359), hour (0\u201323), day of month (1\u201331), month (1\u201312 or jan\u2013dec), day of week (0\u20137 or sun\u2013sat, Sunday is 0). ",t("span",{class:"mono",children:"*"})," is any, ",t("span",{class:"mono",children:"*/5"})," every fifth, ",t("span",{class:"mono",children:"1-5"})," a range, ",t("span",{class:"mono",children:"1,3,5"})," a list. Presets: ",t("span",{class:"mono",children:"@hourly @daily @weekly @monthly"}),"."]})]}):null]}),t("div",{class:"sched-readout","aria-live":"polite",children:p?t(J,{kind:"warn",children:p}):r&&!r.valid?t(J,{kind:"bad",children:r.error||"That is not a schedule this backend can read."}):r?t(W,{children:[t("div",{class:"sched-words",children:r.words||t("span",{class:"hint",children:"No plain reading of this expression \u2014 the times below are what it does."})}),t("div",{class:"sched-next",children:[t("b",{children:"Next runs"}),t("ol",{children:r.next.map(g=>t("li",{children:new Date(g).toLocaleString()},g))})]}),t(H,{children:["Times are the backend\u2019s local time",r.timezone?", "+r.timezone:"",".",s?t(K,{children:" updating\u2026"}):null]})]}):t(H,{children:s?"Working out the next runs\u2026":""})})]})}var yt=[{id:"text",label:"Text",line:"words, names, descriptions",icon:"Aa",storage:"String"},{id:"number",label:"Number",line:"amounts, counts, prices",icon:"#",storage:"Number"},{id:"yesno",label:"Yes / No",line:"a switch",icon:"\u25D0",storage:"Boolean"},{id:"date",label:"Date & time",line:"when something happened",icon:"\u25F7",storage:"Date"},{id:"choice",label:"Choice",line:"one of a list you define",icon:"\u2630",storage:"String"},{id:"link",label:"Link",line:"points at one record in another collection",icon:"\u2192",storage:"Pointer"},{id:"links",label:"Links",line:"points at many records in another collection",icon:"\u21C9",storage:"Relation"},{id:"file",label:"Picture or file",line:"an upload",icon:"\u25A3",storage:"File"},{id:"location",label:"Location",line:"a point on the map",icon:"\u2316",storage:"GeoPoint"},{id:"list",label:"List",line:"several values",icon:"\u2261",storage:"Array"},{id:"anything",label:"Anything",line:"structured data for developers",icon:"{}",storage:"Object"}];function At(e){return yt.find(n=>n.id===e)||yt[0]}function rs(e){let n=e.trim().toLowerCase();return n?yt.filter(i=>(i.label+" "+i.line+" "+i.storage).toLowerCase().indexOf(n)!==-1):yt}function os(e,n){return(n||[]).filter(i=>i.declared&&i.rule.field===e).map(i=>i.rule)}function tr(e,n){return e.type==="String"&&os(e.name,n).some(i=>i.oneOf)?At("choice"):yt.find(i=>i.storage===e.type&&i.id!=="choice")||{id:"anything",label:e.type,line:"",icon:"?",storage:e.type}}function ss(e,n){return!n||!n.length?e:e.map(i=>{let r=os(i.name,n);if(!r.length)return i;let o={...i};return r.forEach(s=>{s.oneOf&&(o.allowed=s.oneOf),s.min!==void 0&&(o.min=s.min),s.max!==void 0&&(o.max=s.max),s.maxLength!==void 0&&(o.maxLength=s.maxLength),s.looksLike&&(o.looksLike=s.looksLike),s.whole&&(o.whole=!0)}),o})}function as(e="text"){return{kind:e,name:"",description:"",required:!1,unique:!1,dflt:"",boolDefaultSet:!1,boolDefault:!1,maxLength:"",looksLike:"",min:"",max:"",whole:!1,values:[],target:""}}function $t(e){return e==="text"||e==="number"||e==="yesno"||e==="choice"}function ls(e){return e==="text"||e==="number"||e==="date"||e==="choice"||e==="link"}var bt=e=>{let n=e.trim();if(!n)return null;let i=Number(n);return isNaN(i)?NaN:i};function cs(e,n){let i=e.name.trim(),r=Mt(i,n.taken,"The field");if(r)return r;let o=At(e.kind),s=e.required&&n.records>0?"The fill value":"The default";if((e.kind==="link"||e.kind==="links")&&!e.target)return"Choose the collection it points at.";if((e.kind==="link"||e.kind==="links")&&n.collections.indexOf(e.target)===-1)return"There is no collection called "+e.target+".";if(e.kind==="choice"){if(!e.values.length)return"A choice needs at least one value.";if(e.dflt&&e.values.indexOf(e.dflt)===-1)return s+" must be one of the choices."}if(e.kind==="number"){let l=bt(e.min),a=bt(e.max);if(l!==null&&isNaN(l))return"The minimum must be a number.";if(a!==null&&isNaN(a))return"The maximum must be a number.";if(l!==null&&a!==null&&l>a)return"The minimum is above the maximum.";let c=bt(e.dflt);if(c!==null&&isNaN(c))return s+" must be a number.";if(c!==null&&e.whole&&!Number.isInteger(c))return s+" must be a whole number.";if(c!==null&&l!==null&&c<l)return s+" is below the minimum.";if(c!==null&&a!==null&&c>a)return s+" is above the maximum.";if(e.whole&&l!==null&&!Number.isInteger(l))return"With whole numbers only, the minimum is a whole number too.";if(e.whole&&a!==null&&!Number.isInteger(a))return"With whole numbers only, the maximum is a whole number too."}if(e.kind==="text"){let l=bt(e.maxLength);if(l!==null&&(isNaN(l)||!Number.isInteger(l)||l<1))return"The max length is a whole number of at least 1.";if(l!==null&&e.dflt.length>l)return s+" is longer than the max length."}return e.required&&n.records>0&&!$t(o.id)?"A required "+o.label+" can only be added while the collection is empty: the "+Ke(n.records)+" already here would have nothing in it.":e.required&&n.records>0&&!sc(e)?"Say what the "+Ke(n.records)+" already in the collection get: a required field needs a value in each of them.":null}function Ke(e){return e===1?"1 record":e+" records"}function ds(e,n){return $t(e.kind)?e.required?n>0?{disabled:!1,label:"Fill the "+Ke(n)+" already here with",why:"Once, now. It is not a default: a new record without this field is refused.",fill:!0}:{disabled:!0,why:"A required field has no default: every record must say it.",label:"Default",fill:!1}:{disabled:!1,why:"",label:"Default",fill:!1}:{disabled:!0,why:"",label:"Default",fill:!1}}function sc(e){return e.kind==="yesno"?e.boolDefaultSet:$t(e.kind)?e.dflt.trim()!=="":!1}function us(e,n=0){let i=At(e.kind),r={name:e.name.trim(),type:i.storage};if((e.kind==="link"||e.kind==="links")&&(r.targetClass=e.target),e.required&&(r.required=!0),e.description.trim()&&(r.description=e.description.trim()),e.kind==="yesno")e.boolDefaultSet&&(r.defaultValue=e.boolDefault);else if(e.kind==="number"){let o=bt(e.dflt);o!==null&&!isNaN(o)&&(r.defaultValue=o)}else(e.kind==="text"||e.kind==="choice")&&e.dflt.trim()&&(r.defaultValue=e.dflt.trim());return e.required&&n>0&&r.defaultValue!==void 0&&(r.fillExisting=r.defaultValue,delete r.defaultValue),r}function ps(e){let n=e.name.trim(),i=[];if(e.kind==="choice"&&i.push({field:n,oneOf:e.values.slice()}),e.kind==="text"){let r=bt(e.maxLength);r!==null&&!isNaN(r)&&i.push({field:n,maxLength:r}),e.looksLike&&i.push({field:n,looksLike:e.looksLike})}if(e.kind==="number"){let r=bt(e.min),o=bt(e.max),s={field:n};r!==null&&!isNaN(r)&&(s.min=r),o!==null&&!isNaN(o)&&(s.max=o),(s.min!==void 0||s.max!==void 0)&&i.push(s),e.whole&&i.push({field:n,whole:!0})}return i}function ms(e,n,i){return(e||[]).filter(o=>o.declared&&o.rule.field!==n).map(o=>o.rule).concat(i)}function fs(e){return e.defaultValue===void 0||e.defaultValue===null?"":e.type==="Boolean"?e.defaultValue===!0?"Yes":"No":String(e.defaultValue)}var ac=[25,50,100,250],gn={kind:"group",conj:"and",items:[]},lc=["Object","Array","ACL","File","GeoPoint"],cc=["String","Number","Boolean","Date","Pointer"];function dc(e){try{return localStorage.getItem(e)}catch{return null}}function uc(e,n){try{n===null?localStorage.removeItem(e):localStorage.setItem(e,n)}catch{}}function pc(e){let n=[{name:"objectId",kind:"text"}];return(e&&e.columns||[]).forEach(i=>{if(Ue(i.name)||i.name==="ACL")return;let r=Jo(i.type);r&&n.push({name:i.name,kind:r,targetClass:i.targetClass})}),n.concat([{name:"createdAt",kind:"date"},{name:"updatedAt",kind:"date"}])}function gs(e,n){let i=0,r=[];return e.items.forEach(o=>{if(o.kind==="group"){let s=gs(o,n);i+=s.waiting,s.group.items.length&&r.push(s.group);return}try{Ji(o,n),r.push(o)}catch{i++}}),{group:{...e,items:r},waiting:i}}function bs(e){return e.items.reduce((n,i)=>n.concat(i.kind==="group"?bs(i):i.op==="is"?[i]:[]),[])}function mc(e,n,i){let r=e.findIndex(l=>l===n||l==="-"+n),o=r===-1?null:e[r],s=o===null?n:o===n?"-"+n:null;return i?r===-1?e.concat([n]):s?e.map((l,a)=>a===r?s:l):e.filter((l,a)=>a!==r):s?[s]:[]}function ys({params:e}){let{features:n,readonly:i,whoami:r}=te(),o=r?r.backend.id:"",[s,l]=m(null),[a,c]=m(""),[u,p]=m(""),[d,f]=m(gn),[b,g]=m(gn),[k,y]=m(null),[T,w]=m([]),[x,E]=m(50),[C,_]=m(!1),[N,v]=m(0),[R,A]=m({}),[U,X]=m(null),[B,q]=m(null),[h,L]=m([]),[Q,se]=m(""),[Me,Ae]=m(null),le=s?Object.keys(s):[],ie=e[0]&&le.indexOf(e[0])!==-1?e[0]:"",z=e[1]||"",de=s&&ie?s[ie]:void 0,ue=Ot(()=>pc(de),[de]),Be="nodegx.admin.columns."+o+"."+ie,Oe=ae(null),Qe=ae(null),st=ae(0);j(()=>(S("GET","/admin/schema").then(F=>{let Y={};(F.tables||[]).filter(ee=>ee.name.charAt(0)!=="_").forEach(ee=>Y[ee.name]={...ee,columns:ss(ee.columns||[],ee.checks)}),l(Y)}).catch(D),()=>on()),[]),j(()=>{if(!s)return;let F=Object.keys(s);F.length&&(!e[0]||F.indexOf(e[0])===-1)&&sn("collections",F[0])},[s,e[0]]),j(()=>{Oe.current&&clearTimeout(Oe.current),Oe.current=setTimeout(()=>p(a),250)},[a]),j(()=>{Qe.current&&clearTimeout(Qe.current),Qe.current=setTimeout(()=>g(d),250)},[d]),j(()=>{v(0),A({}),f(gn),g(gn),y(null),w([]),se(""),X(null);let F=dc(Be);Ae(F?JSON.parse(F):null),ie&&hi()},[ie]);let{group:Nt,waiting:mi}=Ot(()=>gs(b,ue),[b,ue]),[Br,Hr]=m({});j(()=>{bs(Nt).forEach(F=>{let Y=ue.find(ee=>ee.name===F.field);!Y||Y.kind!=="link"||!Y.targetClass||!F.value||Br[F.value]||S("GET","/api/"+I(Y.targetClass)+"/"+I(F.value)).then(ee=>Hr(Te=>({...Te,[F.value]:ht(ee).label}))).catch(()=>Hr(ee=>({...ee,[F.value]:"a record that no longer exists"})))})},[JSON.stringify(Nt)]);let Cn=Ot(()=>hc(de,U?U.rows:[]),[de,U]),[jr,Ja]=m({});j(()=>{!U||!de||(de.columns||[]).filter(F=>F.type==="Pointer"&&F.targetClass).forEach(F=>{let Y=Array.from(new Set(U.rows.map(ee=>it(ee[F.name])).filter(ee=>typeof ee=="string"&&!jr[ee])));Y.length&&S("GET","/api/"+I(F.targetClass)+"?limit="+Y.length+"&where="+I(JSON.stringify({objectId:{$in:Y}}))).then(ee=>{let Te={};(ee.results||[]).forEach(Le=>Te[Le.objectId]=ht(Le).label),Ja(Le=>({...Le,...Te}))}).catch(()=>{})})},[U,de]);let fi=Ot(()=>gc(Cn,Me),[Cn,Me]);function Ya(){let F=[],Y=u.trim();if(Y&&de){let Te=[{objectId:{contains:Y}}];(de.columns||[]).forEach(Le=>{Le.type==="String"&&Te.push({[Le.name]:{contains:Y}})}),F.push({$or:Te})}let ee=Yi(Nt,ue);return ee&&F.push(ee),k&&F.push(k),F.length?F.length===1?F[0]:{$and:F}:null}function Wr(F,Y){let ee=["limit="+F,"count=1","skip="+Y,"sort="+I(JSON.stringify(T.length?T:["-createdAt"]))],Te=Ya();return Te&&ee.push("where="+I(JSON.stringify(Te))),"/api/"+I(ie)+"?"+ee.join("&")}function at(){if(!ie)return;let F=++st.current;q(null),S("GET",Wr(x,N)).then(Y=>{if(F!==st.current)return;let ee=Y.results||[];X({rows:ee,total:Y.count!==void 0?Y.count:ee.length}),A(Te=>{let Le={};return ee.forEach(ct=>{Te[ct.objectId]&&(Le[ct.objectId]=!0)}),Le})}).catch(Y=>{F===st.current&&q(Y.message)})}function hi(){S("GET","/admin/views/"+I(ie)).then(F=>L(F.views||[])).catch(()=>L([]))}j(()=>{at()},[ie,u,JSON.stringify(Nt),JSON.stringify(k),JSON.stringify(T),N,x]);let Ur=ae(at);Ur.current=at,j(()=>{C&&ie?_o(ie,()=>Ur.current()):on()},[C,ie]);let Qt=U?U.total:0,lt=U?U.rows:[];if(j(()=>{let F=Y=>{if(Y.key!=="ArrowLeft"&&Y.key!=="ArrowRight"||Y.altKey||Y.metaKey||Y.ctrlKey||Y.shiftKey)return;let ee=Y.target;ee&&(ee.closest('input, textarea, select, [contenteditable], [role="combobox"]')||ee.isContentEditable)||document.querySelector('.drawer, [role="dialog"]')||(Y.key==="ArrowLeft"&&N>0&&v(Math.max(0,N-x)),Y.key==="ArrowRight"&&N+lt.length<Qt&&v(N+x))};return window.addEventListener("keydown",F),()=>window.removeEventListener("keydown",F)},[N,x,lt.length,Qt]),s&&!le.length)return t(hs,{children:t(oe,{icon:"\u25A6",action:{label:"Create one in Schema",onClick:()=>Z("schema"),write:!1},children:"This backend has no collections yet."})});let en=Object.keys(R),Xa=!!(u.trim()||Nt.items.length||k);function Kr(F){f(F),v(0),se("")}function Vr(F){Ae(F),uc(Be,F?JSON.stringify(F):null)}function Za(F){se(F);let Y=h.find(Te=>Te.name===F),ee=Y&&Y.filter?Y.filter:gn;f(ee),g(ee),y(null),w(Y?Y.sort:[]),Y&&Y.columns.length&&Vr(Y.columns),v(0)}function Qa(){ge(F=>t(yc,{initial:Q,taken:h.map(Y=>Y.name),close:F,save:Y=>S("PUT","/admin/views/"+I(ie)+"/"+I(Y),{filter:d.items.length?d:null,sort:T,columns:fi.map(ee=>ee.name)}).then(()=>{O("View \u201C"+Y+"\u201D saved.","ok"),se(Y),hi(),F()})}))}function el(){let F=Q;we("Delete the view \u201C"+F+"\u201D","It is removed for everyone who administers this backend. The records are not touched.",()=>{S("DELETE","/admin/views/"+I(ie)+"/"+I(F)).then(()=>{O("View deleted.","ok"),se(""),hi()}).catch(D)})}function tl(){we("Delete "+en.length+" record(s)","They are removed from "+ie+" permanently.",()=>{en.reduce((F,Y)=>F.then(()=>S("DELETE","/api/"+I(ie)+"/"+I(Y))),Promise.resolve()).then(()=>O(en.length+" record(s) deleted.","ok")).catch(D).then(()=>{A({}),at()})})}function Gr(F,Y){if(!F.length)return O("Nothing to export.","bad");let ee=Cn,Te=[ee.map(yi=>dn(yi.name)).join(",")];F.forEach(yi=>Te.push(ee.map(rl=>dn(it(yi[rl.name]))).join(",")));let Le=URL.createObjectURL(new Blob([Te.join(\`
65020
- \`)],{type:"text/csv"})),ct=document.createElement("a");ct.href=Le,ct.download=ie+Y+".csv",document.body.appendChild(ct),ct.click(),ct.remove(),setTimeout(()=>URL.revokeObjectURL(Le),1e3)}async function nl(){let F=[];for(let ee=0;ee<2e5;ee+=500){let Le=(await S("GET",Wr(500,ee))).results||[];if(F.push(...Le),Le.length<500)break}Gr(F,"")}function il(F){let Y;try{Y=F.trim()?JSON.parse(F):null}catch(Te){return q("The advanced filter is not valid JSON: "+Te.message)}if(q(null),Y===null)return y(null);let ee=Kn(Y,ue);if(ee&&ee.items.length){let Te=d.items.length?{kind:"group",conj:"and",items:[...d.items,...ee.conj==="and"?ee.items:[ee]]}:ee;f(Te),g(Te),y(null),O("The filter is now in rows above.","ok")}else y(Y);v(0)}let gi=Et(Nt,ue,(F,Y)=>Br[Y]||"\u2026"),bi=u.trim();return t(hs,{children:[t(V,{children:[t($,{label:"Collection",children:t("select",{value:ie,"aria-label":"Collection",onChange:F=>Z("collections",F.currentTarget.value),children:le.map(F=>t("option",{value:F,children:F},F))})}),t($,{label:"View",children:t("select",{"aria-label":"View",value:Q,onChange:F=>Za(F.currentTarget.value),children:[t("option",{value:"",children:"All records"}),h.map(F=>t("option",{value:F.name,children:F.name},F.name))]})}),t($,{label:"Search",children:t("input",{type:"search",placeholder:"Search text fields\u2026",style:"min-width:200px",value:a,onInput:F=>{c(F.currentTarget.value),v(0)}})}),t(P,{onClick:at,children:"Refresh"}),n.realtime?t("label",{class:"check",children:[t("input",{type:"checkbox",checked:C,onChange:F=>_(F.currentTarget.checked)})," Live"]}):null,t(me,{}),en.length?t(M,{tiny:!0,kind:"danger",onClick:tl,children:["Delete ",en.length," selected"]}):null,t(M,{kind:"primary",onClick:()=>Z("collections",ie,"new"),children:"New record"})]}),t(G,{h:8}),t("div",{class:"filter-box",children:[d.items.length?t(gt,{group:d,fields:ue,onChange:Kr}):t("button",{type:"button",class:"btn tiny",onClick:()=>Kr({kind:"group",conj:"and",items:[jt(ue[1]||ue[0])]}),children:"+ Filter"}),mi?t(H,{children:mi===1?"One condition has no value yet; it is not applied.":mi+" conditions have no value yet; they are not applied."}):null,k?t(J,{kind:"accent",children:["An advanced filter is applied as well: ",t("code",{children:JSON.stringify(k)})," ",t(P,{tiny:!0,onClick:()=>y(null),children:"Clear it"})]}):null,t(V,{children:[t(P,{tiny:!0,onClick:()=>ge(F=>t(bc,{all:Cn,shown:fi.map(Y=>Y.name),close:F,onApply:Vr})),children:"Columns"}),t(M,{tiny:!0,onClick:Qa,children:"Save view\u2026"}),Q?t(M,{tiny:!0,kind:"danger",onClick:el,children:"Delete view"}):null,t(me,{}),t(P,{tiny:!0,onClick:()=>Gr(lt,"-page"),children:"Export this page"}),t(P,{tiny:!0,onClick:()=>nl().catch(D),children:"Export all records"}),t(M,{tiny:!0,onClick:()=>ge(F=>t(vc,{collection:ie,table:de,close:F,onDone:at})),children:"Import CSV"})]}),t(He,{label:"Advanced (JSON)",children:t(V,{children:[t(fc,{onApply:il}),t(H,{children:"Enter applies. Anything the rows can say moves into the rows."})]})})]}),t(G,{h:8}),B?t(J,{kind:"bad",children:B}):null,U?t(W,{children:[t(V,{children:[t("span",{class:"result-sentence",children:[t("b",{children:[Qt," ",Qt===1?"record":"records"]}),gi?" where "+gi:"",bi?gi?", matching \u201C"+bi+"\u201D":" matching \u201C"+bi+"\u201D":"",k?" (and the advanced filter)":""]}),t(me,{}),lt.length?t(H,{children:["Showing ",N+1,"\u2013",N+lt.length]}):null,t("select",{"aria-label":"Records per page",value:String(x),onChange:F=>{E(Number(F.currentTarget.value)),v(0)},children:ac.map(F=>t("option",{value:String(F),children:[F," a page"]},F))}),t(P,{tiny:!0,disabled:!(N>0),onClick:()=>v(Math.max(0,N-x)),title:"Previous page (\u2190)",children:"Prev"}),t(P,{tiny:!0,disabled:N+lt.length>=Qt,onClick:()=>v(N+x),title:"Next page (\u2192)",children:"Next"})]}),t(G,{h:8}),lt.length?t(kc,{collection:ie,rows:lt,columns:fi,sort:T,onSort:(F,Y)=>{w(mc(T,F,Y)),v(0)},selected:R,setSelected:A,readonly:i,onOpen:F=>Z("collections",ie,F.objectId),onDeleted:at,onChanged:F=>X(Y=>Y&&{...Y,rows:Y.rows.map(ee=>ee.objectId===F.objectId?F:ee)}),labels:jr}):t("div",{class:"scroller",children:Xa?t(oe,{children:"No records match."}):t(oe,{icon:"\uFF0B",action:{label:"Add the first one",onClick:()=>Z("collections",ie,"new")},children:"No records yet."})})]}):null,z&&s&&ie?t(Sc,{collection:ie,table:de,id:z==="new"?null:z,onClose:()=>Z("collections",ie),onSaved:()=>{Z("collections",ie),at()}},z):null]})}function hs({children:e}){return t("div",{children:[t("h1",{children:"Collections"}),t("p",{class:"sub",children:"Find records by saying it in rows, sort by a column, and open a record to change it. Live updates arrive over the backend\u2019s own SSE stream."}),e]})}function fc({onApply:e}){let[n,i]=m("");return t("input",{type:"text",placeholder:'{"status":"open"}',style:"min-width:280px","aria-label":"Advanced filter (JSON)",value:n,onInput:r=>i(r.currentTarget.value),onKeyDown:r=>{r.key==="Enter"&&(r.preventDefault(),e(n),i(""))}})}function hc(e,n){let i=[{name:"objectId",type:"String"}];(e&&e.columns||[]).forEach(o=>{!Ue(o.name)&&o.name!=="ACL"&&i.push(o)});let r=i.map(o=>o.name).concat(["createdAt","updatedAt","ACL"]);return n.forEach(o=>{Object.keys(o).forEach(s=>{r.indexOf(s)===-1&&(r.push(s),i.push({name:s,type:Ko(o[s])}))})}),i.concat([{name:"createdAt",type:"Date"},{name:"updatedAt",type:"Date"},{name:"ACL",type:"ACL"}])}function gc(e,n){if(!n)return e.filter(o=>o.name!=="objectId"&&o.name!=="updatedAt"&&o.name!=="ACL");let i={};e.forEach(o=>i[o.name]=o);let r=n.filter(o=>i[o]).map(o=>i[o]);return r.length?r:e.filter(o=>o.name!=="objectId")}function bc({all:e,shown:n,close:i,onApply:r}){let[o,s]=m(()=>n.concat(e.map(f=>f.name).filter(f=>n.indexOf(f)===-1))),[l,a]=m(()=>{let f={};return n.forEach(b=>f[b]=!0),f}),[c,u]=m(null),p=(f,b)=>{if(b<0||b>=o.length||f===b)return;let g=o.slice(),[k]=g.splice(f,1);g.splice(b,0,k),s(g)},d=f=>(e.find(b=>b.name===f)||{type:""}).type;return t(ve,{title:"Columns",actions:t(W,{children:[t(P,{onClick:()=>{r(null),i()},children:"Reset"}),t(me,{}),t(P,{onClick:i,children:"Cancel"}),t(P,{kind:"primary",onClick:()=>{let f=o.filter(b=>l[b]);if(!f.length)return O("Show at least one column.","bad");r(f),i()},children:"Apply"})]}),children:[t("p",{class:"sub",children:"Tick the columns to show; drag, or use \u2191 \u2193, to put them in order. Remembered for this collection in this browser."}),t("div",{class:"columns-list",children:o.map((f,b)=>t("div",{class:"columns-row"+(c===b?" dragging":""),draggable:!0,onDragStart:()=>u(b),onDragOver:g=>{g.preventDefault(),c!==null&&c!==b&&(p(c,b),u(b))},onDragEnd:()=>u(null),children:[t("span",{class:"drag-handle","aria-hidden":"true",children:"\u283F"}),t("label",{class:"check",children:[t("input",{type:"checkbox",checked:!!l[f],onChange:g=>a({...l,[f]:g.currentTarget.checked})}),f," ",t("span",{class:"th-type",children:d(f)})]}),t(me,{}),t("button",{type:"button",class:"btn tiny","aria-label":"Move "+f+" up",disabled:b===0,onClick:()=>p(b,b-1),children:"\u2191"}),t("button",{type:"button",class:"btn tiny","aria-label":"Move "+f+" down",disabled:b===o.length-1,onClick:()=>p(b,b+1),children:"\u2193"})]},f))})]})}function yc({initial:e,taken:n,close:i,save:r}){let[o,s]=m(e),[l,a]=m(null),c=()=>{let u=o.trim();if(!u)return a("A view needs a name.");r(u).catch(p=>a(p.message))};return t(ve,{title:"Save view",actions:t(W,{children:[t(P,{onClick:i,children:"Cancel"}),t(P,{kind:"primary",onClick:c,children:"Save"})]}),children:[t("p",{class:"sub",children:"The filter, the sort and the columns, kept by the backend so everyone who administers it can open them."}),t($,{label:"Name",children:t("input",{type:"text",value:o,onInput:u=>{s(u.currentTarget.value),a(null)},onKeyDown:u=>{u.key==="Enter"&&c()}})}),n.indexOf(o.trim())!==-1?t(H,{children:"There is a view with this name; saving replaces it."}):null,l?t(J,{kind:"bad",children:l}):null]})}function vc({collection:e,table:n,close:i,onDone:r}){let[o,s]=m(null),[l,a]=m(""),[c,u]=m([]),[p,d]=m(null),[f,b]=m(!1),[g,k]=m(null),y=ae(null),T=["objectId"].concat((n&&n.columns||[]).filter(v=>!Ue(v.name)&&v.name!=="ACL"&&v.type!=="Relation").map(v=>v.name)),w=v=>((n&&n.columns||[]).find(R=>R.name===v)||{type:v==="objectId"?"id":""}).type,x=()=>{if(!o)return"";let v=c.map((A,U)=>A?U:-1).filter(A=>A!==-1),R=[v.map(A=>dn(c[A])).join(",")];return o.slice(1).forEach(A=>R.push(v.map(U=>dn(A[U]===void 0?"":A[U])).join(","))),R.join(\`
65021
- \`)},E=v=>S("POST","/admin/import/"+I(e),{format:"csv",content:x(),dryRun:v});j(()=>{if(!o||!c.some(Boolean))return d(null);let v=setTimeout(()=>{E(!0).then(d).catch(R=>k(R.message))},250);return()=>clearTimeout(v)},[o,c.join("|")]);let C=v=>{k(null),a(v.name);let R=new FileReader;R.onload=()=>{let A=$o(String(R.result||""));if(A.length<2)return k("That file has no rows under its header.");s(A),u(A[0].map(U=>T.find(X=>X.toLowerCase()===U.trim().toLowerCase())||""))},R.readAsText(v)},_=()=>{b(!0),E(!1).then(v=>{if(!v.applied)throw new Error(v.error||"Nothing was imported.");O(v.created+" added, "+v.updated+" updated"+(v.rejected.length?", "+v.rejected.length+" rejected":"")+".","ok"),r(),i()}).catch(v=>k(v.message)).then(()=>b(!1))},N=o?o[0]:[];return t(ve,{title:"Import CSV into "+e,wide:!0,actions:t(W,{children:[t(P,{onClick:i,children:"Cancel"}),t(P,{kind:"primary",disabled:f||!p||p.created+p.updated===0,onClick:_,children:p?"Import "+(p.created+p.updated)+" record(s)":"Import"})]}),children:[t(V,{children:[t(P,{onClick:()=>y.current&&y.current.click(),children:l?"Choose another file":"Choose a CSV file"}),t("input",{ref:y,type:"file",accept:".csv,text/csv",hidden:!0,onChange:v=>{let R=v.currentTarget.files;R&&R[0]&&C(R[0])}}),l?t(H,{children:[l," \xB7 ",o?o.length-1:0," rows"]}):t(H,{children:"The first row names the columns."})]}),o?t(W,{children:[t(G,{h:8}),t("div",{class:"scroller",children:t("table",{class:"grid import-preview",children:[t("thead",{children:t("tr",{children:N.map((v,R)=>t("th",{children:[t("div",{class:"hint",children:v}),t("select",{"aria-label":"Field for "+v,value:c[R]||"",onChange:A=>u(c.map((U,X)=>X===R?A.currentTarget.value:U)),children:[t("option",{value:"",children:"(skip)"}),T.map(A=>t("option",{value:A,children:[A," ",w(A)?"\xB7 "+w(A):""]},A))]})]},R))})}),t("tbody",{children:o.slice(1,21).map((v,R)=>t("tr",{children:N.map((A,U)=>t("td",{class:c[U]?"":"shy",children:v[U]},U))},R))})]})}),o.length>21?t(H,{children:["The first 20 of ",o.length-1," rows."]}):null,p?t(J,{kind:p.rejected.length?"warn":"",style:"margin-top:10px",children:[p.created," will be added",p.updated?", "+p.updated+" updated":"",p.rejected.length?", "+p.rejected.length+" rejected:":".",p.rejected.slice(0,5).map(v=>t("div",{class:"hint",children:["Row ",v.row+2,": ",v.errors.join("; ")]},v.row))]}):null]}):null,g?t(J,{kind:"bad",style:"margin-top:10px",children:g}):null]})}function kc({collection:e,rows:n,columns:i,sort:r,onSort:o,selected:s,setSelected:l,readonly:a,onOpen:c,onDeleted:u,onChanged:p,labels:d}){let f=n.every(y=>s[y.objectId]),b=ae(null),g=y=>{b.current&&clearTimeout(b.current),b.current=setTimeout(()=>c(y),230)},k=()=>{b.current&&clearTimeout(b.current),b.current=null};return t("div",{class:"scroller",children:t("table",{class:"grid",children:[t("thead",{children:t("tr",{children:[t("th",{class:"pick",children:t("input",{type:"checkbox",title:"Select all on this page",checked:f,onChange:y=>{let T=y.currentTarget.checked;l(w=>{let x={...w};return n.forEach(E=>{T?x[E.objectId]=!0:delete x[E.objectId]}),x})}})}),i.map(y=>{let T=r.findIndex(E=>E===y.name||E==="-"+y.name),w=T===-1?null:r[T].charAt(0)==="-"?"desc":"asc",x=cc.indexOf(y.type)!==-1||Ue(y.name);return t("th",{class:x?"sortable":"","aria-sort":w==="asc"?"ascending":w==="desc"?"descending":void 0,title:x?"Sort by "+y.name+" (\u21E7 to add to the sort)":void 0,onClick:x?E=>o(y.name,E.shiftKey):void 0,children:[y.name," ",t("span",{class:"th-type",children:y.type}),w?t("span",{class:"sort-mark",children:[w==="asc"?"\u25B2":"\u25BC",r.length>1?T+1:""]}):null]},y.name)}),t("th",{})]})}),t("tbody",{children:n.map(y=>t("tr",{class:"clickable",onClick:()=>g(y),children:[t("td",{class:"pick",onClick:T=>T.stopPropagation(),children:t("input",{type:"checkbox",checked:!!s[y.objectId],onChange:T=>{let w=T.currentTarget.checked;l(x=>{let E={...x};return w?E[y.objectId]=!0:delete E[y.objectId],E})}})}),i.map(T=>t(wc,{collection:e,row:y,col:T,readonly:a,onChanged:p,cancelOpen:k,labels:d},T.name)),t("td",{class:"actions",onClick:T=>T.stopPropagation(),children:t(M,{tiny:!0,kind:"danger",onClick:()=>we("Delete record","This removes "+e+"/"+y.objectId+" permanently.",()=>{S("DELETE","/api/"+I(e)+"/"+I(y.objectId)).then(()=>{O("Record deleted.","ok"),u()}).catch(D)}),children:"Delete"})})]},y.objectId))})]})})}function wc({collection:e,row:n,col:i,readonly:r,onChanged:o,cancelOpen:s,labels:l}){let[a,c]=m(!1),[u,p]=m(""),d=ae(!1),f=!r&&!Ue(i.name)&&i.type!=="Relation",b=i.type==="Pointer"?it(n[i.name]):null,g=i.name==="objectId"?nt(n.objectId):typeof b=="string"&&b?"\u2192 "+(l[b]||"\u2026"):Mn(n[i.name],i.type),k=i.name==="objectId"?String(n.objectId):g,y=i.type==="ACL"&&(n.ACL===null||n.ACL===void 0);function T(E){let C={},_=E===void 0?null:Ui(i,E);return C[i.name]=_,S("PUT","/api/"+I(e)+"/"+I(n.objectId),C).then(N=>{let v={...n,[i.name]:_};N&&N.updatedAt&&(v.updatedAt=N.updatedAt),o(v),O(i.name+" saved.","ok")})}function w(E){if(E.stopPropagation(),s(),!(a||!f)){if(i.type==="Boolean"){T(it(n[i.name])!==!0).catch(D);return}if(lc.indexOf(i.type)!==-1){ge(C=>t(Cc,{collection:e,col:i,value:n[i.name],close:C,save:T}));return}p(We(i,n[i.name])),d.current=!1,c(!0)}}function x(E,C){if(d.current||(d.current=!0,c(!1),!E))return;let _;try{_=$e(i,C===void 0?u:C)}catch(v){D(v);return}let N=it(n[i.name]);_===N||_===void 0&&N==null||T(_).catch(D)}return a?t("td",{class:"cell-edit",onClick:E=>E.stopPropagation(),children:t(Vt,{col:i,raw:u,onChange:E=>p(E),autoFocus:!0,onCommit:()=>x(!0),onCancel:()=>x(!1)})}):t("td",{class:(f?"editable":"locked")+(y?" shy":""),title:f?k+" \u2014 double-click to edit":k,onDblClick:w,children:g})}function Cc({collection:e,col:n,value:i,close:r,save:o}){let[s,l]=m(We(n,i));return t(ve,{title:n.type==="ACL"?"Who can see this record":"Edit "+n.name,actions:t(W,{children:[t(P,{onClick:r,children:"Cancel"}),t(P,{kind:"primary",onClick:()=>{let a;try{a=$e(n,s)}catch(c){return D(c)}o(a).then(r).catch(D)},children:"Save"})]}),children:n.type==="ACL"?t(Ut,{state:s.acl,onChange:a=>l({t:"acl",acl:a}),collection:e}):t(nr,{col:n,children:t(Vt,{col:n,raw:s,onChange:l,autoFocus:!0})})})}function nr({col:e,children:n}){let i=e.type==="Pointer"||e.type==="Relation";return t("div",{class:"field",children:[t("span",{class:"field-head",children:[t("b",{children:e.name}),e.required?t("span",{class:"req",children:"required"}):null,t("span",{class:"chip type",children:e.type+(i&&e.targetClass?" \u2192 "+e.targetClass:"")})]}),n]})}function Sc({collection:e,table:n,id:i,onClose:r,onSaved:o}){let s=i!==null,[l,a]=m(s?null:{}),[c,u]=m({}),[p,d]=m(null),[f,b]=m(!1),g=n&&n.columns||[],k=g.filter(C=>!Ue(C.name)&&C.name!=="ACL"&&C.type!=="Relation"),y=g.filter(C=>C.type==="Relation"),T={name:"ACL",type:"ACL"};j(()=>{if(!s){let C={};k.forEach(_=>C[_.name]=We(_,_.defaultValue)),C.ACL=We(T,null),u(C);return}S("GET","/api/"+I(e)+"/"+I(i)).then(C=>{a(C);let _={};k.forEach(N=>_[N.name]=We(N,C[N.name])),_.ACL=We(T,C.ACL),u(_)}).catch(C=>{b(!0),D(C)})},[e,i]);let w=C=>c[C.name]===void 0?We(C,void 0):c[C.name];function x(){let C={};try{k.forEach(R=>{let A=$e(R,w(R));if(A===void 0||A===""){if(R.required&&R.type!=="Boolean")throw new Error(R.name+" is required.");A===""&&R.type==="String"&&s?C[R.name]="":s&&l&&l[R.name]!==null&&l[R.name]!==void 0&&(C[R.name]=null);return}C[R.name]=Ui(R,A)});let N=$e(T,w(T)),v=s&&l?l.ACL===void 0?null:l.ACL:null;JSON.stringify(N)!==JSON.stringify(v)&&(C.ACL=N)}catch(N){d(N.message);return}let _="/api/"+I(e);(s?S("PUT",_+"/"+I(i),C):S("POST",_,C)).then(N=>{if(O(s?"Record saved.":"Record created.","ok"),!s&&y.length&&N&&N.objectId){Z("collections",e,N.objectId);return}o()}).catch(N=>d(N.message))}let E=s?"Edit "+e+" / "+nt(i):"New record in "+e;return t(Se,{title:E,wide:!0,subtitle:s?"objectId, createdAt and updatedAt are kept by the backend.":void 0,onClose:r,footer:t(W,{children:[t(P,{onClick:r,children:"Cancel"}),t(M,{kind:"primary",disabled:f||s&&!l,onClick:x,children:s?"Save":"Create"})]}),children:[f?t(J,{kind:"bad",children:["That record is not in ",e," any more."]}):s&&!l?t(H,{children:"Loading\u2026"}):t(W,{children:[!k.length&&!y.length?t(J,{children:[e," has no fields yet."," ",t(P,{tiny:!0,onClick:()=>Z("schema",e),children:"Add fields in Schema"})]}):null,t("div",{class:"form-grid",onInput:()=>d(null),children:[k.map((C,_)=>t(nr,{col:C,children:t(Vt,{col:C,raw:w(C),onChange:N=>u(v=>({...v,[C.name]:N})),autoFocus:_===0})},C.name)),y.map(C=>t(nr,{col:C,children:s?t(ts,{collection:e,id:i,col:C}):t(H,{children:"Links are made once the record exists \u2014 Create opens it."})},C.name))]}),t("div",{class:"drawer-section",children:"Who can see this record"}),t(Ut,{state:w(T).acl,onChange:C=>u(_=>({..._,ACL:{t:"acl",acl:C}})),collection:e})]}),p?t(J,{kind:"bad",style:"margin-top:12px",children:p}):null,s&&l?t(He,{label:"Raw record",children:t("textarea",{readOnly:!0,"aria-label":"Raw record",value:JSON.stringify(l,null,2),style:"min-height:200px"})}):null]})}async function $n(e,n){if(e==="_User"){let o=await S("GET","/admin/users?limit=1");return o&&o.total||0}let i="?count=1&limit=0"+(n?"&where="+I(JSON.stringify(n)):""),r=await S("GET","/api/"+I(e)+i);return r&&r.count||0}function ks({params:e}){let{readonly:n,whoami:i}=te(),r=i?i.accountColumns:void 0,[o,s]=m(null),l=o?o.map(f=>f.name):[],a=e[0]||"",c=e[1]==="new-field"?a:"",u=c&&o?o.find(f=>f.name===c):void 0;function p(){S("GET","/admin/schema").then(f=>s(f.tables||[])).catch(D)}j(p,[]),j(()=>{if(!o||!a)return;let f=document.getElementById("schema-"+a);f&&f.scrollIntoView({block:"start",behavior:"smooth"})},[o,a]);function d(){ge(f=>t(Oc,{names:l,close:f,onCreated:p}))}return t(ce,{title:"Schema",subtitle:"Your collections and their fields. Click a field name to rename it, or its kind to change it.",children:[t(V,{children:[t(M,{kind:"primary",onClick:d,children:"New collection"}),t(P,{onClick:p,children:"Refresh"})]}),t(G,{}),o&&!o.length?t(oe,{icon:"\u25A6",action:{label:"Create the first one",onClick:d},children:"No collections yet. A collection is a table of records \u2014 Products, Orders, Members."}):null,(o||[]).map(f=>t(Rc,{t:f,names:l,readonly:n,accountColumns:r,hit:f.name===a,reload:p},f.name)),u?t(Cs,{t:u,names:l,onClose:()=>Z("schema",c),onAdded:p}):null]})}function Tc(e){return(e.indexes||[]).filter(n=>n.declared!==!1).length}function xc({col:e,checks:n}){let i=tr(e,n),r=e.type==="Pointer"||e.type==="Relation";return t("span",{class:"kind",children:[t("span",{class:"kind-icon","aria-hidden":"true",children:i.icon}),i.label,r&&e.targetClass?" \u2192 "+Ve(e.targetClass):"",t("span",{class:"chip type",children:e.type})]})}function Rc({t:e,names:n,readonly:i,accountColumns:r,hit:o,reload:s}){let l=e.name.charAt(0)==="_",a=e.columns||[],c=a.filter(C=>!Ue(C.name)),p=[{name:"objectId",type:"String",note:"unique id, set by the backend"},{name:"createdAt",type:"Date",note:"set by the backend"},{name:"updatedAt",type:"Date",note:"set by the backend"}].concat(c),d=e.indexes||[],f=(e.checks||[]).filter(C=>C.declared),b=(e.checks||[]).filter(C=>!C.declared),[g,k]=m(null);j(()=>{l||$n(e.name).then(k).catch(()=>k(null))},[e.name,a.length]);function y(){Z("schema",e.name,"new-field")}function T(){ge(C=>t(Bc,{t:e,close:C,onApplied:s}))}function w(){S("POST","/admin/schema",{action:"deleteTable",table:e.name}).then(C=>{C&&C.deleted===!1?O('The backend reported that "'+e.name+'" was not deleted.',"bad"):O('Collection "'+e.name+'" deleted.',"ok"),s()}).catch(D)}function x(){Ec(e.name).then(C=>{O(Ke(C)+" deleted from "+e.name+".","ok"),k(0)}).catch(D)}function E(C){let _=f.filter(N=>N.name!==C.name).map(N=>N.rule);S("POST","/admin/schema",{action:"setChecks",table:e.name,checks:_}).then(()=>O("Rule removed: "+C.description+".","ok")).catch(D).then(s)}return t("div",{class:"card"+(o?" hit":""),id:"schema-"+e.name,children:[t(V,{children:[t(kt,{children:Ve(e.name)}),l?t(K,{kind:"warn",children:e.name==="_User"?"accounts":"system"}):null,t(K,{children:[a.length," field(s)"]}),t(K,{children:[Tc(e)," index(es)"]}),g!==null?t(K,{children:Ke(g)}):null,t(me,{}),t(P,{tiny:!0,onClick:()=>e.name==="_User"?Z("users"):Z("collections",e.name),children:e.name==="_User"?"Open people":"Open records"}),t(M,{tiny:!0,onClick:y,children:"Add field"}),t(M,{tiny:!0,onClick:T,children:"Indexes"})]}),e.description?t("div",{class:"shy collection-description",children:e.description}):null,t(G,{h:8}),t(ye,{columns:["Field","Kind","Required","Default",""],rows:p,renderRow:C=>{let _=!!C.note||Uo(e.name,C.name,r),N=_||i,v=f.some(R=>R.rule.field?R.rule.field===C.name:(R.rule.exactlyOne||R.rule.allOrNone||[]).indexOf(C.name)!==-1);return t("tr",{class:_?"sys":void 0,children:[t("td",{children:[N?C.name:t(Ac,{t:e,c:C,reload:s}),C.description?t("div",{class:"shy",children:C.description}):null]}),t("td",{children:N||C.type==="Relation"||v?t(xc,{col:C,checks:e.checks}):t(Ic,{t:e,c:C,reload:s})}),t("td",{children:C.required?"\u2713":""}),t("td",{class:"shy",children:C.note||fs(C)}),t("td",{class:"actions",children:N?null:t(_c,{t:e,c:C,reload:s})})]},C.name)}}),c.length?null:t(V,{style:"margin-top:8px",children:[t(H,{children:"No fields of your own yet."}),t(M,{tiny:!0,kind:"primary",onClick:y,children:"+ Add a field"})]}),f.length||b.length?t("div",{class:"rules",children:[t("div",{class:"field-head",children:[t("b",{children:"Rules"}),t(H,{children:"Every record must satisfy these; the backend refuses a write that breaks one."})]}),t("ul",{class:"rules-list",children:[f.map(C=>t("li",{class:"rule",children:[t("span",{children:C.description}),C.built?null:t(K,{kind:"bad",children:"not enforced"}),i?null:t("button",{type:"button",class:"chip-x","aria-label":"Remove the rule: "+C.description,title:"Remove this rule",onClick:()=>E(C),children:"\u2715"})]},C.name)),b.map(C=>t("li",{class:"rule",children:[t("span",{children:C.name}),t(K,{kind:"warn",children:"not declared"})]},C.name))]})]}):null,d.length?t(W,{children:[t(G,{h:8}),t(ye,{columns:["Index","Fields","Unique","Order","State"],rows:d,renderRow:(C,_)=>t("tr",{children:[t("td",{children:C.name}),t("td",{children:(C.fields||[]).join(", ")}),t("td",{children:C.unique?"yes":""}),t("td",{children:C.order==="desc"?"newest / largest first":"ascending"}),t("td",{children:C.declared===!1?t(K,{kind:"warn",children:"not declared"}):C.built?t(K,{kind:"ok",children:"built"}):t(K,{kind:"bad",children:"missing"})})]},C.name||_)})]}):null,l||i?null:t(qe,{children:[t(Ze,{label:"Empty collection",why:"Delete every record in "+e.name+(g!==null?" ("+Ke(g)+")":"")+". The fields, rules and indexes stay.",title:"Empty collection "+e.name,warning:(g!==null?Ke(g)+" in":"Every record in")+' "'+e.name+'" '+(g===1?"is":"are")+" destroyed. The collection itself stays, with its fields.",expected:e.name,onConfirm:x,verb:"Empty"}),t(Ze,{label:"Delete collection",why:"Destroy "+e.name+" and every record in it. Permissions and triggers that name it are not removed and will start failing.",title:"Delete collection "+e.name,warning:'Every record in "'+e.name+'" is destroyed along with the collection. Permissions and triggers that reference it are NOT removed and will start failing.',expected:e.name,onConfirm:w})]})]})}async function Ec(e){let n=0;for(let i=0;i<1e4;i++){let r=await S("GET","/api/"+I(e)+"?limit=200&keys=objectId"),o=(r&&r.results||[]).map(s=>s.objectId).filter(Boolean);if(!o.length)return n;await S("POST","/api/_batch",{operations:o.map(s=>({method:"delete",collection:e,objectId:s}))}),n+=o.length}return n}function _c({t:e,c:n,reload:i}){let[r,o]=m(!1);function s(){o(!0),(e.name==="_User"?Promise.resolve(null):$n(e.name,{[n.name]:{$exists:!0}}).catch(()=>null)).then(a=>{o(!1);let c=a===null?"Records holding a value in it lose it.":a===0?"No record holds a value in it.":Ke(a)+" hold"+(a===1?"s":"")+" a value in it, and that value is lost.";Ge("Drop the field "+n.name,c+' App logic that reads "'+n.name+'" will break. A rule or an index that reads it must be removed first.',n.name,()=>{S("POST","/admin/schema",{action:"dropColumn",table:e.name,column:n.name}).then(u=>O(u&&u.dropped===!1?'"'+n.name+'" was already gone.':"Dropped "+n.name+" from "+Ve(e.name)+".","ok")).catch(D).then(i)},"Drop")})}return t("button",{type:"button",class:"chip-x","aria-label":"Drop the field "+n.name,title:"Drop "+n.name,disabled:r,onClick:s,children:"\u2715"})}function Ac({t:e,c:n,reload:i}){let[r,o]=m(!1),[s,l]=m(n.name),a=ae(!1);function c(u){if(a.current)return;a.current=!0,o(!1);let p=s.trim();if(!u||p===n.name)return;let d=(e.columns||[]).map(b=>b.name).filter(b=>b!==n.name),f=Mt(p,d,"The field");if(f){O(f,"bad");return}S("POST","/admin/schema",{action:"renameColumn",table:e.name,oldName:n.name,newName:p}).then(()=>O("Renamed "+n.name+" to "+p+". Update any app logic that uses the old name.","ok")).catch(D).then(i)}return r?t("input",{type:"text",value:s,style:"width:160px","aria-label":"New name for "+n.name,autoFocus:!0,onInput:u=>l(u.currentTarget.value),onKeyDown:u=>{u.key==="Enter"?(u.preventDefault(),c(!0)):u.key==="Escape"&&(u.preventDefault(),c(!1))},onBlur:()=>c(!0)}):t("button",{type:"button",class:"inline-edit",title:"Rename "+n.name,onClick:()=>{l(n.name),a.current=!1,o(!0)},children:n.name})}var Pc=yt.filter(e=>e.id!=="choice"&&e.id!=="links");function Ic({t:e,c:n,reload:i}){let r=tr(n,e.checks);return t("select",{class:"inline-type",title:"Change the kind of "+n.name,"aria-label":"Kind of "+n.name,value:r.id,onChange:o=>{let s=o.currentTarget,l=At(s.value);s.value=r.id;let a=l.storage;a!==n.type&&$n(e.name,{[n.name]:{$exists:!0}}).catch(()=>null).then(c=>ge(u=>t(ve,{title:"Change "+n.name+" to "+l.label+"?",autoFocus:!1,actions:t(W,{children:[t(P,{onClick:u,children:"Cancel"}),t(P,{kind:"primary",onClick:()=>{u(),S("POST","/admin/schema",{action:"changeColumnType",table:e.name,column:n.name,type:a}).then(p=>{let d=p&&p.convertedValues||0;O(n.name+" is now "+l.label+(d?" \u2014 "+d+" stored value(s) converted.":"."),"ok")}).catch(D).then(i)},children:"Change kind"})]}),children:[t("p",{children:[c===null?"Values":c===0?"No record holds a value yet; nothing is converted. Values":Ke(c)+" hold a value in "+Ve(e.name)+"."+n.name+", and each one is converted. Values"," stored as"," ",r.label," (",n.type,") become ",l.label," (",a,")."]}),t(J,{kind:"warn",children:"A conversion can lose information: text that is not a number becomes 0, and a list or object turned into text stays text."})]})))},children:Pc.filter(o=>!(o.id==="link"&&n.type!=="Pointer")).map(o=>t("option",{value:o.id,children:o.label+" ("+o.storage+")"+(o.id==="link"&&n.targetClass?" \u2192 "+n.targetClass:"")},o.id))})}function vs(){return{name:"",type:"String",target:"",required:!1,dflt:""}}function Lc(e,n){let i=e.name.trim(),r=Mt(i,n,"Each field");if(r)throw new Error(r);let o={name:i,type:e.type};if(e.type==="Pointer"||e.type==="Relation"){if(!e.target)throw new Error(i+": choose the collection it points at.");o.targetClass=e.target}e.required&&(o.required=!0);let s=e.dflt.trim();if(s&&!e.required&&["String","Number","Boolean"].indexOf(e.type)!==-1)if(e.type==="Number"){if(isNaN(Number(s)))throw new Error(i+": the default must be a number.");o.defaultValue=Number(s)}else e.type==="Boolean"?o.defaultValue=s==="yes":o.defaultValue=s;return o}var Nc=yt.filter(e=>e.id!=="choice");function Dc({line:e,update:n,names:i,autoFocus:r}){let o=e.type==="Pointer"||e.type==="Relation",s=["String","Number","Boolean"].indexOf(e.type)!==-1,l="A required field has no default: every record must say it.";return t(W,{children:[t("input",{type:"text",placeholder:"fieldName","aria-label":"Field name",value:e.name,autoFocus:r,onInput:a=>n({...e,name:a.currentTarget.value})}),t("select",{"aria-label":"Kind",value:e.type,onChange:a=>n({...e,type:a.currentTarget.value}),children:Nc.map(a=>t("option",{value:a.storage,children:a.label+" ("+a.storage+")"},a.id))}),o?t("select",{"aria-label":"Points at",value:e.target,onChange:a=>n({...e,target:a.currentTarget.value}),children:[t("option",{value:"",children:"\u2192 choose a collection"}),i.map(a=>t("option",{value:a,children:["\u2192 ",Ve(a)]},a))]}):null,t("label",{class:"check",children:[t("input",{type:"checkbox",checked:e.required,onChange:a=>n({...e,required:a.currentTarget.checked})})," Required"]}),s?e.type==="Boolean"?t("select",{"aria-label":"Default",value:e.required?"":e.dflt,disabled:e.required,title:e.required?l:void 0,onChange:a=>n({...e,dflt:a.currentTarget.value}),children:[t("option",{value:"",children:e.required?"no default (required)":"no default"}),t("option",{value:"yes",children:"default: Yes"}),t("option",{value:"no",children:"default: No"})]}):t("input",{type:e.type==="Number"?"number":"text",step:"any",placeholder:e.required?"no default: every record must say it":"default (optional)","aria-label":"Default",title:e.required?l:void 0,disabled:e.required,value:e.required?"":e.dflt,onInput:a=>n({...e,dflt:a.currentTarget.value})}):null]})}function Oc({names:e,close:n,onCreated:i}){let[r,o]=m(""),[s,l]=m([vs()]),[a,c]=m(null),u=r?Wi(r.trim(),e):null;function p(){let d=r.trim(),f=[];try{let b=Wi(d,e);if(b)throw new Error(b);s.forEach(g=>{g.name.trim()&&f.push(Lc(g,f.map(k=>k.name)))})}catch(b){c(b.message);return}S("POST","/admin/schema",{action:"createTable",table:d,columns:f}).then(()=>{n(),O("Collection "+d+" created.","ok"),i()}).catch(b=>c(b.message))}return t(ve,{title:"New collection",wide:!0,actions:t(W,{children:[t(P,{onClick:n,children:"Cancel"}),t(P,{kind:"primary",onClick:p,children:"Create collection"})]}),children:t("div",{onInput:()=>c(null),children:[t("div",{class:"field",children:[t("b",{children:"Name"}),t("input",{type:"text",placeholder:"e.g. Products, Orders, Members",style:"width:100%","aria-label":"Collection name",value:r,onInput:d=>o(d.currentTarget.value)}),u?t("div",{class:"chips-problem",children:u}):null]}),t(G,{}),t("div",{class:"field-head",children:[t("b",{children:"Fields"}),t(H,{children:"More can be added later, and a Choice (one of a list) is added from the collection's card. objectId, createdAt and updatedAt come for free."})]}),t(G,{h:8}),t(Xe,{rows:s,onChange:l,blank:vs,addLabel:"+ Add field",removeTitle:"Remove this field",renderRow:(d,f,b)=>t(Dc,{line:d,update:f,names:e,autoFocus:b>0&&b===s.length-1})}),a?t(J,{kind:"bad",style:"margin-top:12px",children:a}):null]})})}function Ve(e){return e==="_User"?"Users":e}function ws({t:e,names:n,close:i,onAdded:r}){return t(Cs,{t:e,names:n,onClose:i,onAdded:r})}function Fc({onPick:e,autoFocus:n}){let[i,r]=m(""),o=rs(i);return t("div",{class:"kind-picker",children:[t("input",{type:"text",class:"kind-search",placeholder:"What does it hold? e.g. a number, a link, a picture","aria-label":"Search kinds",autoFocus:n,value:i,onInput:s=>r(s.currentTarget.value),onKeyDown:s=>{s.key==="Enter"&&o.length===1&&(s.preventDefault(),e(o[0].id))}}),t("div",{class:"tiles kinds",role:"radiogroup","aria-label":"Kind of field",children:[o.map(s=>t("label",{class:"tile",children:[t("input",{type:"radio",name:"kind",value:s.id,"aria-label":s.label,onChange:()=>e(s.id)}),t("span",{class:"kind-icon","aria-hidden":"true",children:s.icon}),t("b",{children:s.label}),t("span",{class:"sub",children:s.line}),t("span",{class:"chip type",children:s.storage})]},s.id)),o.length?null:t(H,{children:['Nothing matches "',i,'". Try text, number, date, link, picture.']})]})]})}function Mc({draft:e,update:n,names:i,taken:r,records:o}){let s=At(e.kind),l=e.name?Mt(e.name.trim(),r,"The field"):null,a=ds(e,o),c=p=>n({...e,...p}),u=(p,d,f={})=>t($,{label:p,children:t("input",{type:"text","aria-label":p,value:e[d],onInput:b=>c({[d]:b.currentTarget.value}),...f})});return t("div",{class:"field-options",children:[t("div",{class:"field",children:[t("b",{children:"Name"}),t("input",{type:"text",placeholder:"e.g. price, dueDate, ownerName","aria-label":"Field name",autoFocus:!0,value:e.name,onInput:p=>c({name:p.currentTarget.value})}),l?t("div",{class:"chips-problem",children:l}):t(H,{children:"Letters, digits and _ \u2014 this is the name app logic reads it by."})]}),u("Description (optional)","description",{placeholder:"One line about what goes here"}),t("div",{class:"when-body",id:"kind-options",children:[e.kind==="text"?t(W,{children:[t($,{label:a.label,children:[t("input",{type:"text","aria-label":a.label,value:e.dflt,disabled:a.disabled,placeholder:a.disabled?"no default":"optional",onInput:p=>c({dflt:p.currentTarget.value})}),a.why?t(H,{children:a.why}):null]}),t("div",{class:"field-line",children:[u("Max length","maxLength",{type:"number",min:1,step:1,placeholder:"any",class:"short"}),t($,{label:"Must look like",children:t("select",{"aria-label":"Must look like",value:e.looksLike,onChange:p=>c({looksLike:p.currentTarget.value}),children:[t("option",{value:"",children:"anything"}),t("option",{value:"email",children:"an email address"}),t("option",{value:"url",children:"a web address"})]})})]})]}):null,e.kind==="number"?t(W,{children:[t($,{label:a.label,children:[t("input",{type:"number",step:"any","aria-label":a.label,value:e.dflt,disabled:a.disabled,placeholder:a.disabled?"no default":"optional",onInput:p=>c({dflt:p.currentTarget.value})}),a.why?t(H,{children:a.why}):null]}),t("div",{class:"field-line",children:[u("At least","min",{type:"number",step:"any",placeholder:"no minimum",class:"short"}),u("At most","max",{type:"number",step:"any",placeholder:"no maximum",class:"short"})]}),t(re,{checked:e.whole,onChange:p=>c({whole:p}),label:"Whole numbers only",children:"Whole numbers only"})]}):null,e.kind==="yesno"?t(W,{children:[t(re,{checked:e.boolDefaultSet,disabled:a.disabled,onChange:p=>c({boolDefaultSet:p}),label:a.fill?a.label:"Has a default",children:a.fill?a.label+"\u2026":"New records start with a default"}),e.boolDefaultSet&&!a.disabled?t(re,{checked:e.boolDefault,onChange:p=>c({boolDefault:p}),label:a.fill?"Fill value":"Default value",children:[a.fill?"Fill with":"Default",": ",e.boolDefault?"Yes":"No"]}):null,a.why?t(H,{children:a.why}):null]}):null,e.kind==="date"?t(H,{children:"No options. The backend stamps createdAt and updatedAt on every record for you."}):null,e.kind==="choice"?t(W,{children:[t($,{label:"The choices",children:t(Ie,{id:"choice-values",items:e.values,onChange:p=>c({values:p,dflt:p.indexOf(e.dflt)===-1?"":e.dflt}),placeholder:"Type a value and press Enter",addLabel:"Add",validate:p=>p.length>200?"A choice is at most 200 characters.":null})}),t($,{label:a.label,children:[t("select",{"aria-label":a.label,value:e.dflt,disabled:a.disabled||!e.values.length,onChange:p=>c({dflt:p.currentTarget.value}),children:[t("option",{value:"",children:a.fill?"choose a value\u2026":"no default"}),e.values.map(p=>t("option",{value:p,children:p},p))]}),a.why?t(H,{children:a.why}):null]})]}):null,e.kind==="link"||e.kind==="links"?t($,{label:"Points at",children:[t("select",{"aria-label":"Points at",value:e.target,onChange:p=>c({target:p.currentTarget.value}),children:[t("option",{value:"",children:"choose a collection\u2026"}),i.map(p=>t("option",{value:p,children:Ve(p)},p))]}),e.target?t(H,{children:[e.kind==="link"?"Each record links to one "+Ve(e.target)+".":"Each record links to many "+Ve(e.target)+"."," When the ",Ve(e.target)," is deleted, the link stays and points at nothing."]}):null]}):null,e.kind==="file"?t(H,{children:"No options here. What can be uploaded, and how big, is set on the Files page for the whole backend."}):null,e.kind==="location"||e.kind==="list"||e.kind==="anything"?t(H,{children:"No options."}):null]}),t("div",{class:"drawer-section",children:"Rules"}),t("div",{class:"when-body",children:[t(re,{checked:e.required,onChange:p=>c({required:p}),label:"Required",children:"Required \u2014 every record must say it"}),e.required&&o>0&&$t(e.kind)?t(H,{children:[Ke(o)," already here: say what they get, once, in the box above. New records must say it themselves."]}):null,e.required&&o>0&&!$t(e.kind)?t(J,{kind:"warn",children:["A required ",s.label," can only be added while the collection is empty: the ",Ke(o)," already here would have nothing in it."]}):null,ls(e.kind)?t(re,{checked:e.unique,onChange:p=>c({unique:p}),label:"Must be unique",children:"Must be unique \u2014 no two records share a value"}):null]})]})}function Cs({t:e,names:n,onClose:i,onAdded:r}){let[o,s]=m(null),[l,a]=m(0),[c,u]=m(null),[p,d]=m(!1),f=(e.columns||[]).map(k=>k.name);j(()=>{$n(e.name).then(a).catch(()=>a(0))},[e.name]);function b(){if(!o)return;let k=cs(o,{taken:f,collections:n,records:l});if(k){u(k);return}let y=us(o,l),T=ps(o);d(!0),S("POST","/admin/schema",{action:"addColumn",table:e.name,column:y}).then(()=>T.length?S("POST","/admin/schema",{action:"setChecks",table:e.name,checks:ms(e.checks,y.name,T)}):null).then(()=>o.unique?S("POST","/admin/schema",{action:"setIndexes",table:e.name,indexes:(e.indexes||[]).filter(w=>w.declared!==!1).map(w=>({fields:w.fields,unique:w.unique,order:w.order})).concat([{fields:[y.name],unique:!0}])}):null).then(()=>{O(y.name+" added to "+Ve(e.name)+".","ok"),r(),i()}).catch(w=>{d(!1),u(w.message),r()})}let g=o?At(o.kind):null;return t(Se,{title:"Add a field to "+Ve(e.name),subtitle:o&&g?g.label+" \u2014 "+g.line:"What kind of thing does it hold?",onClose:i,wide:!0,footer:t(W,{children:[t(P,{onClick:i,children:"Cancel"}),o?t(W,{children:[t(P,{onClick:()=>s(null),children:"Change kind"}),t(M,{kind:"primary",disabled:p,onClick:b,children:"Add field"})]}):null]}),children:t("div",{onInput:()=>u(null),children:[o?t(W,{children:[t(V,{class:"kind-chosen",children:[t("span",{class:"kind-icon","aria-hidden":"true",children:g.icon}),t("b",{children:g.label}),t(fe,{style:"margin:0",children:g.line}),t("span",{class:"chip type",children:g.storage})]}),t(Mc,{draft:o,update:s,names:n,taken:f,records:l})]}):t(Fc,{autoFocus:!0,onPick:k=>s(as(k))}),c?t(J,{kind:"bad",style:"margin-top:12px",children:c}):null]})})}function Bc({t:e,close:n,onApplied:i}){let r=(e.columns||[]).map(p=>p.name).filter(p=>p!=="createdAt"&&p!=="updatedAt");r.indexOf("objectId")===-1&&r.unshift("objectId");let[o,s]=m((e.indexes||[]).filter(p=>p.declared!==!1).map(p=>({first:p.fields[0]||r[0],second:p.fields[1]||"",third:p.fields[2]||"",unique:!!p.unique,order:p.order==="desc"?"desc":"asc"}))),[l,a]=m(null);function c(){let p;try{p=o.map(d=>{let f=[d.first,d.second,d.third].filter(Boolean);if(f.length!==f.filter((g,k)=>f.indexOf(g)===k).length)throw new Error("An index lists each field once.");let b={fields:f};return d.unique&&(b.unique=!0),d.order==="desc"&&(b.order="desc"),b})}catch(d){a(d.message);return}S("POST","/admin/schema",{action:"setIndexes",table:e.name,indexes:p}).then(d=>{n();let f=d&&d.indexesCreated||[],b=d&&d.indexesDropped||[];O("Indexes applied: "+f.length+" created, "+b.length+" dropped.","ok"),i()}).catch(d=>a(d.message))}let u=({value:p,optional:d,onChange:f,label:b})=>t("select",{"aria-label":b,value:p,onChange:g=>f(g.currentTarget.value),children:[d?t("option",{value:"",children:d}):null,r.map(g=>t("option",{value:g,children:g},g))]});return t(ve,{title:"Indexes on "+Ve(e.name),wide:!0,autoFocus:!1,actions:t(W,{children:[t(P,{onClick:n,children:"Cancel"}),t(P,{kind:"primary",onClick:c,children:"Apply"})]}),children:[t(fe,{children:"An index makes lookups on its fields fast. Unique also refuses a second record with the same values. createdAt and updatedAt are always indexed. Removing a row drops that index."}),t(Xe,{rows:o,onChange:p=>{s(p),a(null)},blank:()=>({first:r[0],second:"",third:"",unique:!1,order:"asc"}),addLabel:"+ Add index",removeTitle:"Drop this index",renderRow:(p,d)=>t(W,{children:[t(u,{label:"First field",value:p.first,onChange:f=>d({...p,first:f})}),t(u,{label:"Then",value:p.second,optional:"then\u2026 (optional)",onChange:f=>d({...p,second:f})}),t(u,{label:"Then",value:p.third,optional:"then\u2026 (optional)",onChange:f=>d({...p,third:f})}),t("label",{class:"check",children:[t("input",{type:"checkbox",checked:p.unique,onChange:f=>d({...p,unique:f.currentTarget.checked})})," Unique"]}),t("select",{"aria-label":"Order",value:p.order,onChange:f=>d({...p,order:f.currentTarget.value}),children:[t("option",{value:"asc",children:"Ascending"}),t("option",{value:"desc",children:"Descending"})]})]})}),l?t(J,{kind:"bad",style:"margin-top:12px",children:l}):null]})}var Hc=[{value:null,label:"No access to the manager",sub:"Signs in to your app only. Most people."},{value:"readonly",label:"Can look, not change",sub:"Opens this manager and sees everything \u2014 every write is refused. For a client, or support."},{value:"full",label:"Full admin",sub:"Everything, here and in your app: every rule lets them through."}];function jc(e){return e==="full"?"admin":e==="readonly"?"read-only admin":null}var zt=50;function Pe(e){return typeof e.username=="string"&&e.username?e.username:typeof e.email=="string"&&e.email?e.email:"Someone with no name"}function Ss({params:e}){let{features:n}=te(),[i,r]=m(null),[o,s]=m([]),[l,a]=m([]),[c,u]=m(""),[p,d]=m(""),[f,b]=m(""),[g,k]=m(0),y=ae(null),T=e[0]||"";function w(){let v=new URLSearchParams({limit:String(zt),skip:String(g)});p.trim()&&v.set("q",p.trim()),f&&v.set("status",f),S("GET","/admin/users?"+v.toString()).then(r).catch(D)}function x(){n.roles&&S("GET","/admin/roles").then(v=>s((v.roles||[]).map(R=>R.name))).catch(()=>s([]))}j(()=>{x(),S("GET","/admin/schema").then(v=>a((v.tables||[]).map(R=>R.name))).catch(()=>a([]))},[]),j(w,[p,f,g]),j(()=>{y.current&&clearTimeout(y.current),y.current=setTimeout(()=>{k(0),d(c)},250)},[c]);let E=i?i.columns:[];function C(){ge(v=>t(ws,{t:{name:"_User",columns:(i?i.columns:[]).concat([{name:"username",type:"String"},{name:"email",type:"String"}])},names:l,close:v,onAdded:w}))}let _=!!(p.trim()||f),N=T==="new"||T==="invite";return t(ce,{title:"Users",subtitle:"The people who can sign in to this backend.",children:[t(V,{children:[t(M,{kind:"primary",onClick:()=>Z("users","new"),children:"Add user"}),n.email?t(M,{onClick:()=>Z("users","invite"),children:"Invite by email"}):null,t(M,{onClick:C,children:"Add a field"}),t(me,{}),t("input",{type:"search",class:"users-search","aria-label":"Search people",placeholder:"Search by name or email\u2026",value:c,onInput:v=>u(v.currentTarget.value)}),t("select",{"aria-label":"Show",value:f,onChange:v=>{k(0),b(v.currentTarget.value)},children:[t("option",{value:"",children:"Everyone"}),t("option",{value:"disabled",children:"Disabled"}),t("option",{value:"unverified",children:"Email not verified"}),t("option",{value:"admins",children:"Backend admins"})]})]}),t(G,{}),i?t(ye,{columns:["Person","Verified","Roles","Signed in","Joined"].concat(E.map(v=>v.name)),rows:i.users,empty:_?t(oe,{icon:"\u{1F50D}",children:"Nobody matches."}):t(oe,{icon:"\u{1F464}",action:{label:"Add the first person",onClick:()=>Z("users","new")},children:"No users yet. Add someone, or let people sign up from your app."}),renderRow:v=>t("tr",{class:"clickable"+(v.objectId===T?" hit":"")+(v.disabled?" revoked":""),onClick:()=>Z("users",v.objectId),children:[t("td",{children:t("div",{class:"person",children:[t("span",{class:"avatar","aria-hidden":"true",children:Pe(v).charAt(0).toUpperCase()}),t("span",{class:"person-text",children:[t("b",{children:Pe(v)}),v.disabled?t(W,{children:[" ",t(K,{kind:"bad",children:"disabled"})]}):null,v.adminAccess?t(W,{children:[" ",t(K,{kind:v.adminAccess==="full"?"accent":"warn",children:jc(v.adminAccess)})]}):null,v.email&&v.email!==v.username?t("span",{class:"hint",children:v.email}):null]})]})}),t("td",{children:v.emailVerified?t(K,{kind:"ok",children:"verified"}):v.email?t(K,{children:"not yet"}):t("span",{class:"hint",children:"no email"})}),t("td",{children:v.roles.map(R=>t(K,{kind:"accent",children:R},R))}),t("td",{title:v.lastSessionAt?"Newest sign-in still active: "+he(v.lastSessionAt):void 0,children:v.sessions?je(v.lastSessionAt)+(v.sessions>1?" \xB7 "+v.sessions+" devices":""):t("span",{class:"hint",children:"not signed in"})}),t("td",{title:he(v.createdAt),children:je(v.createdAt)}),E.map(R=>t("td",{children:Mn(v[R.name],R.type)},R.name))]},v.objectId)}):null,i&&i.total>zt?t(V,{style:"margin-top:8px",children:[t(H,{children:[g+1,"\u2013",Math.min(g+zt,i.total)," of ",i.total]}),t(P,{tiny:!0,disabled:g===0,onClick:()=>k(Math.max(0,g-zt)),children:"Previous"}),t(P,{tiny:!0,disabled:g+zt>=i.total,onClick:()=>k(g+zt),children:"Next"})]}):null,N?t(Wc,{invite:T==="invite",columns:E,roleNames:o,onClose:()=>Z("users"),onCreated:v=>{w(),Z("users",v)}},T):T?t(Uc,{id:T,columns:E,roleNames:o,onChanged:w,onClose:()=>Z("users")},T):null]})}function zn(e=n=>Math.floor(Math.random()*n)){let n="abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ23456789",i=[];for(let r=0;r<4;r++){let o="";for(let s=0;s<4;s++)o+=n.charAt(e(n.length));i.push(o)}return i.join("-")}function Jn(e){let n=typeof crypto<"u"?crypto:null;if(n&&n.getRandomValues){let i=new Uint32Array(1);return n.getRandomValues(i),i[0]%e}return Math.floor(Math.random()*e)}function Ts({columns:e,raw:n,onChange:i}){return e.length?t(W,{children:e.map(r=>t("div",{class:"field",children:[t("span",{class:"field-head",children:[t("b",{children:r.name})," ",t("span",{class:"chip type",children:r.type})]}),t(Vt,{col:r,raw:n[r.name]!==void 0?n[r.name]:We(r,void 0),onChange:o=>i(r.name,o)})]},r.name))}):null}function xs(e,n,i){let r={};for(let o of e){if(n[o.name]===void 0)continue;let s=$e(o,n[o.name]);i&&JSON.stringify(We(o,i[o.name]))===JSON.stringify(n[o.name])||(r[o.name]=s===void 0?null:o.type==="Date"?{__type:"Date",iso:s}:s)}return r}function Wc({invite:e,columns:n,roleNames:i,onClose:r,onCreated:o}){let{features:s,whoami:l}=te(),[a,c]=m(""),[u,p]=m(""),[d,f]=m(""),[b,g]=m(!1),[k,y]=m([]),[T,w]=m({}),[x,E]=m(!1),[C,_]=m(null),[N,v]=m(null),[R,A]=m(!1),U=l&&l.accountColumns||{};j(()=>{e&&S("GET","/auth/providers").then(q=>_(!!(q.magicLink&&q.magicLink.enabled))).catch(()=>_(null))},[e]);let X=!R&&a.trim()!==""&&(e?u.trim()!==""&&C!==!1:d!=="");function B(){let q;try{q=xs(n,T,null)}catch(L){v(L.message);return}A(!0),v(null);let h={username:a.trim(),email:u.trim()||void 0,roles:k,properties:q};e?h.invite=!0:(h.password=d,h.sendVerification=x),S("POST","/admin/users",h).then(L=>{A(!1),e&&!L.invited?O(Pe(h)+" was added, but the invitation did not go: "+(L.inviteError||"the mail failed."),"bad"):e?O("Invitation sent to "+u.trim()+".","ok"):x&&L.verificationSent===!1?O("Added. The verification email did not go: "+(L.verificationError||"the mail failed."),"bad"):O(Pe(h)+" added.","ok"),o(L.objectId)}).catch(L=>{A(!1),v(L.message)})}return t(Se,{title:e?"Invite someone by email":"Add a user",subtitle:e?"They get a sign-in link and choose how to sign in from then on.":void 0,onClose:r,footer:t(W,{children:[t(P,{onClick:r,children:"Cancel"}),t(M,{kind:"primary",disabled:!X,onClick:B,children:e?"Send invitation":"Add user"})]}),children:[e&&C===!1?t(W,{children:[t(J,{kind:"warn",children:["An invitation is a magic sign-in link, and magic links are switched off on this backend."," ",t("a",{href:"#/signin",onClick:()=>Z("signin"),children:"Turn them on under Sign-in"}),", then come back."]}),t(G,{})]}):null,t("div",{class:"form-grid",onInput:()=>v(null),children:[t($,{label:"Username",children:t("input",{type:"text",autoFocus:!0,placeholder:"How they sign in \u2014 e.g. ann",value:a,onInput:q=>c(q.currentTarget.value)})}),t($,{label:e?"Email":"Email (optional)",children:t("input",{type:"email",placeholder:"ann@example.com",value:u,onInput:q=>p(q.currentTarget.value)})}),e?null:t("div",{class:"field",children:[t("span",{class:"field-head",title:U.password,children:t("b",{children:"Password"})}),t(V,{children:[t("input",{type:b?"text":"password","aria-label":"Password",autoComplete:"new-password",value:d,style:"flex:1",onInput:q=>f(q.currentTarget.value)}),t(P,{tiny:!0,onClick:()=>{f(zn(Jn)),g(!0)},children:"Generate"}),t(P,{tiny:!0,onClick:()=>g(!b),children:b?"Hide":"Show"})]}),t(H,{children:"Stored hashed. Give it to them yourself \u2014 it is not emailed."})]}),s.roles?t("div",{class:"field",children:[t("span",{class:"field-head",children:t("b",{children:"Roles"})}),t(Ie,{items:k,onChange:y,suggest:q=>Promise.resolve(i.filter(h=>h.toLowerCase().indexOf(q.trim().toLowerCase())!==-1)),placeholder:i.length?"Add a role\u2026":"No roles yet \u2014 make them on the Roles page"})]}):null,t(Ts,{columns:n,raw:T,onChange:(q,h)=>w({...T,[q]:h})}),!e&&s.email?t(Fe,{checked:x,disabled:!u.trim(),onChange:E,children:"Send them an email to verify their address"}):null]}),N?t(J,{kind:"bad",style:"margin-top:12px",children:N}):null]})}function Uc({id:e,columns:n,roleNames:i,onChanged:r,onClose:o}){let{features:s,whoami:l}=te(),a=l&&l.accountColumns||{},[c,u]=m(null),[p,d]=m(!1),[f,b]=m(null),[g,k]=m(""),[y,T]=m(""),[w,x]=m({}),[E,C]=m(""),[_,N]=m(!1),[v,R]=m(null),[A,U]=m(!1);function X(z){u(z),k(z.username||""),T(z.email||"");let de={};n.forEach(ue=>de[ue.name]=We(ue,z[ue.name])),x(de)}function B(){S("GET","/admin/users/"+I(e)).then(z=>X(z.user)).catch(z=>{z.status===404?d(!0):D(z)}),S("GET","/admin/users/"+I(e)+"/identities").then(z=>b(z.identities||[])).catch(()=>b([]))}j(B,[e,n.map(z=>z.name).join()]);function q(z,de){return U(!0),S("PUT","/admin/users/"+I(e),z).then(ue=>{U(!1),X(ue.user),r(),O(de+(ue.sessionsRevoked?" Signed out of "+ue.sessionsRevoked+" session"+(ue.sessionsRevoked===1?"":"s")+".":""),"ok")}).catch(ue=>{U(!1),R(ue.message)})}if(p)return t(Se,{title:"Nobody here",onClose:o,children:t(oe,{icon:"\u{1F464}",children:"This person is not on this backend any more."})});if(!c)return t(Se,{title:"Loading\u2026",onClose:o,children:t(H,{children:"Loading\u2026"})});let h=Pe(c);function L(){if(!c)return;let z;try{z=xs(n,w,c)}catch(ue){R(ue.message);return}let de={};if(g.trim()!==(c.username||"")&&(de.username=g.trim()),y.trim()!==(c.email||"")&&(de.email=y.trim()),Object.keys(z).length&&(de.properties=z),!Object.keys(de).length){O("Nothing has changed.","ok");return}q(de,"Saved.")}function Q(z){if(!c)return;let de=z.filter(Oe=>c.roles.indexOf(Oe)===-1),ue=c.roles.filter(Oe=>z.indexOf(Oe)===-1),Be=de.map(Oe=>S("POST","/admin/roles/"+I(Oe)+"/users",{userId:e})).concat(ue.map(Oe=>S("DELETE","/admin/roles/"+I(Oe)+"/users/"+I(e))));Promise.all(Be).then(()=>{O(de.length?h+" is now in "+de.join(", ")+".":h+" is no longer in "+ue.join(", ")+".","ok"),B(),r()}).catch(D)}function se(z){!c||z===c.adminAccess||(z==="full"?we("Make "+h+" a full admin",h+" can then open this manager and change anything on it, and every rule in your app lets them through. Only another full admin can take it back.",()=>q({adminAccess:"full"},h+" is now a full admin."),"Make full admin"):z==="readonly"?q({adminAccess:"readonly"},h+" can now open the manager, and change nothing."):q({adminAccess:null},h+" can no longer open the manager."))}function Me(z){z?we("Disable sign-in for "+h,h+" is signed out everywhere and cannot sign in again \u2014 by password, sign-in link or provider \u2014 until you switch this back. Their account, records and roles stay as they are.",()=>q({disabled:!0},h+" can no longer sign in."),"Disable sign-in"):q({disabled:!1},h+" can sign in again.")}function Ae(){we("Sign "+h+" out everywhere","Every device "+h+" is signed in on is signed out. They can sign straight back in.",()=>S("DELETE","/admin/users/"+I(e)+"/sessions").then(z=>{O("Signed out of "+z.sessionsRevoked+" session"+(z.sessionsRevoked===1?"":"s")+".","ok"),B(),r()}).catch(D),"Sign out everywhere")}function le(){!c||!c.email||S("POST","/requestPasswordReset",{email:c.email}).then(()=>O("A reset link was requested for "+c.email+". That endpoint answers the same whether or not the mail went; the Email page shows sends.","ok")).catch(D)}function ie(){S("DELETE","/admin/users/"+I(e)).then(()=>{O(h+" deleted.","ok"),r(),o()}).catch(D)}return t(Se,{title:h,subtitle:c.email&&c.email!==h?c.email:void 0,wide:!0,onClose:o,footer:t(W,{children:[t(P,{onClick:()=>Ye(c.objectId).then(z=>O(z?"Id copied.":"The browser refused the clipboard.",z?"ok":"bad")),children:"Copy id"}),t(me,{}),t(P,{onClick:o,children:"Close"})]}),children:t("div",{onInput:()=>R(null),children:[c.disabled?t(W,{children:[t(J,{kind:"bad",children:["Sign-in is disabled for ",h,"."]}),t(G,{})]}):null,t("h3",{class:"drawer-section",children:"Details"}),t("div",{class:"form-grid",children:[t($,{label:"Username",children:t("input",{type:"text",title:a.username,value:g,onInput:z=>k(z.currentTarget.value)})}),t($,{label:"Email",children:t("input",{type:"email",title:a.email,value:y,onInput:z=>T(z.currentTarget.value)})}),t(Ts,{columns:n,raw:w,onChange:(z,de)=>x({...w,[z]:de})})]}),t(V,{style:"margin-top:8px",children:[t(M,{kind:"primary",disabled:A,onClick:L,children:"Save details"}),t("span",{title:a.emailVerified,children:t(Fe,{checked:c.emailVerified,disabled:A||!c.email,onChange:z=>q({emailVerified:z},z?"Marked verified.":"Marked not verified."),children:"Email verified"})})]}),v?t(J,{kind:"bad",style:"margin-top:12px",children:v}):null,s.roles?t(W,{children:[t("h3",{class:"drawer-section",children:"Roles"}),t(Ie,{id:"person-roles",items:c.roles,onChange:Q,suggest:z=>Promise.resolve(i.filter(de=>de.toLowerCase().indexOf(z.trim().toLowerCase())!==-1)),placeholder:i.length?"Add a role\u2026":"No roles yet \u2014 make them on the Roles page"})]}):null,t("h3",{class:"drawer-section",children:"Backend access"}),l&&l.person&&l.person.id===e?t(J,{style:"margin-bottom:8px",children:"This is you. Another full admin can change your access; you cannot change your own."}):null,t("div",{class:"tiles access-tiles",role:"radiogroup","aria-label":"Backend access",title:a.adminAccess,children:Hc.map(z=>{let de=(c.adminAccess||null)===z.value,ue=!!(l&&l.person&&l.person.id===e);return t("label",{class:"tile"+(de?" on":""),children:[t("input",{type:"radio",name:"admin-access",value:z.value||"",checked:de,disabled:A||ue,onChange:()=>se(z.value)}),t("b",{children:z.label}),t("span",{class:"sub",children:z.sub})]},String(z.value))})}),t("h3",{class:"drawer-section",children:"Sign-in"}),t("p",{class:"sub",children:[c.hasPassword?"Signs in with a password":"No password",f&&f.length?" and "+f.map(z=>z.displayName).join(", "):"","."]}),f&&f.length?t(ye,{columns:["Signs in with","Address","Last used"],rows:f,renderRow:z=>t("tr",{children:[t("td",{children:z.displayName}),t("td",{children:z.email||""}),t("td",{title:he(z.lastLoginAt),children:je(z.lastLoginAt)})]},z.objectId)}):null,t("div",{class:"field",style:"margin-top:8px",children:[t("span",{class:"field-head",title:a.password,children:t("b",{children:"Set a new password"})}),t(V,{children:[t("input",{type:_?"text":"password","aria-label":"New password",autoComplete:"new-password",value:E,style:"flex:1",onInput:z=>C(z.currentTarget.value)}),t(P,{tiny:!0,onClick:()=>{C(zn(Jn)),N(!0)},children:"Generate"}),t(M,{tiny:!0,kind:"primary",disabled:A||!E,onClick:()=>q({password:E},"Password changed.").then(()=>C("")),children:"Set password"})]}),t(H,{children:"Setting one signs them out everywhere. Give it to them yourself \u2014 it is not emailed."})]}),s.email&&c.email?t(V,{style:"margin-top:8px",children:t(M,{tiny:!0,onClick:le,children:"Email them a reset link"})}):null,t("h3",{class:"drawer-section",children:"Sessions"}),t(V,{children:[t("span",{children:c.sessions?"Signed in on "+c.sessions+" device"+(c.sessions===1?"":"s")+" \xB7 newest "+je(c.lastSessionAt):"Not signed in anywhere."}),t(me,{}),t(M,{tiny:!0,disabled:!c.sessions,onClick:Ae,children:"Sign out everywhere"})]}),t("h3",{class:"drawer-section",children:"Status"}),t("span",{title:a.disabled,children:t(Fe,{checked:c.disabled,disabled:A,onChange:Me,children:"Disable sign-in"})}),t(H,{children:"Keeps the account, its records and roles. Signs them out, and refuses every way back in until switched off."}),t(G,{h:16}),t(qe,{children:t(Ze,{label:"Delete user",why:"Removes the account, signs them out everywhere and takes them out of every role. Records they made stay.",title:"Delete "+h,warning:"This removes "+h+" for good. To stop them signing in but keep the account, use Disable sign-in instead.",expected:c.username||h,onConfirm:ie})})]})})}var rr=[["find","list"],["get","open"],["create","create"],["update","change"],["delete","delete"]],Rs=[["upload","upload"],["read","open"],["delete","delete"]];function or(e,n){if(e===void 0)return!1;let i="role:"+n;return Array.isArray(e)?e.indexOf(i)!==-1:e===i}function ir(e,n,i){return e?n.map(([r])=>r).filter(r=>or(e[r],i)):[]}function Yn(e,n){if(!e)return[];let i=[],r=ir(e.defaults&&e.defaults.permissions,rr,n);r.length&&i.push({kind:"defaults",ops:r});let o=e.collections||{};for(let a of Object.keys(o).sort()){let c=o[a],u=ir(c&&c.permissions,rr,n);u.length&&i.push({kind:"collection",name:a,ops:u})}let s=e.functions||{};for(let a of Object.keys(s).sort()){let c=s[a];c&&or(c.call,n)&&i.push({kind:"function",name:a,ops:["call"]})}let l=ir(e.files,Rs,n);return l.length&&i.push({kind:"files",ops:l}),or(e.signup,n)&&i.push({kind:"signup",ops:["signup"]}),i}function Es(e){return e.reduce((n,i)=>n+i.ops.length,0)}function Kc(e,n){let r=(e==="files"?Rs:rr).find(([o])=>o===n);return r?r[1]:n==="call"?"call":n==="signup"?"sign up":n}function Vc(e){switch(e.kind){case"collection":return e.name||"";case"defaults":return"Every collection without a rule of its own";case"function":return"The "+e.name+" function";case"files":return"Files";case"signup":return"Signing up"}}function sr(e){return Vc(e)+": "+e.ops.map(n=>Kc(e.kind,n)).join(", ")}function _s(e){let n={defaults:!1,collections:0,functions:0,other:0};for(let i of e)i.kind==="defaults"?n.defaults=!0:i.kind==="collection"?n.collections++:i.kind==="function"?n.functions++:n.other++;return n}function As(e){return e.kind==="collection"&&e.name?"#/permissions/"+encodeURIComponent(e.name):e.kind==="function"?"#/permissions/functions":"#/permissions"}var Gc=/^[a-zA-Z0-9_-]+$/,Is=280,Ps=/^[^\\s@,;]+@[^\\s@,;]+\\.[^\\s@,;]+$/;function qc(e,n){let i=e.trim();if(!i)return null;let r=Array.from(new Set(i.replace(/[a-zA-Z0-9_-]/g,"").split("")));if(r.length)return"A role name cannot contain "+r.map(l=>l===" "?"a space":"\u201C"+l+"\u201D").join(", ")+". Use letters, digits, - and _.";if(!Gc.test(i))return"Use letters, digits, - and _.";let o=n.find(s=>s.toLowerCase()===i.toLowerCase());return o?"There is already a role called "+o+".":null}function $c(e,n){if(!e)return"No one yet";let i=n.slice(0,3),r=e-i.length;return i.length?r<=0?i.length===1?i[0]:i.slice(0,-1).join(", ")+" and "+i[i.length-1]:i.join(", ")+" and "+r+" more":e===1?"1 person":e+" people"}function zc(e){if(!e.length)return"Nothing yet";let n=_s(e),i=[];return n.defaults&&i.push("every collection by default"),n.collections&&i.push(n.collections+(n.collections===1?" collection":" collections")),n.functions&&i.push(n.functions+(n.functions===1?" function":" functions")),n.other&&i.push(n.other+(n.other===1?" other rule":" other rules")),i.join(" \xB7 ")}function Ls({params:e}){let[n,i]=m(null),[r,o]=m(null),s=e[0]||"";function l(){S("GET","/admin/roles").then(u=>i(u.roles||[])).catch(D),S("GET","/admin/permissions").then(u=>o(u.config||{})).catch(()=>o({}))}j(l,[]);function a(){ge(u=>t(Jc,{taken:(n||[]).map(p=>p.name),close:u,onCreated:p=>{l(),Z("roles",p)}}))}let c=n&&s&&n.find(u=>u.name===s)||null;return t(ce,{title:"Roles",subtitle:"A role is a group of people. Permissions give a role something to do; everyone in it can then do that.",children:[t(V,{children:[t(M,{kind:"primary",onClick:a,children:"New role"}),t(P,{onClick:l,children:"Refresh"})]}),t(G,{}),n&&s&&!c?t(J,{kind:"warn",children:["There is no role called ",s,". It may have been deleted."]}):null,n?t(ye,{columns:["Role","Members","Used in","Created"],rows:n,empty:t(oe,{icon:"\u{1F465}",action:{label:"New role",onClick:a},children:"No roles yet. A role groups people so a permission can name all of them at once."}),renderRow:u=>{let p=(u.users||[]).length,d=u.names||[];return t("tr",{class:"clickable"+(u.name===s?" hit":""),onClick:()=>Z("roles",u.name),children:[t("td",{children:t("span",{class:"person-text",children:[t("b",{children:u.name}),u.description?t("span",{class:"hint",children:u.description}):null]})}),t("td",{children:t("div",{class:"person",children:[d.length?t("span",{class:"avatars","aria-hidden":"true",children:d.map(f=>t("span",{class:"avatar",children:f.charAt(0).toUpperCase()},f))}):null,t("span",{class:p?void 0:"hint",children:[p?t("b",{children:p}):null,p?" \xB7 ":null,$c(p,d)]})]})}),t("td",{class:r&&!Yn(r,u.name).length?"hint":void 0,children:r?zc(Yn(r,u.name)):""}),t("td",{title:he(u.createdAt),children:u.createdAt?je(u.createdAt):""})]},u.name)}}):null,c?t(Yc,{role:c,config:r,onChanged:l,onClose:()=>Z("roles")},c.name):null]})}function Jc({taken:e,close:n,onCreated:i}){let[r,o]=m(""),[s,l]=m(""),[a,c]=m(!1),u=qc(r,e),p=!a&&r.trim()!==""&&!u;function d(){p&&(c(!0),S("POST","/admin/roles",{name:r.trim(),description:s.trim()||void 0}).then(()=>{n(),O("Role "+r.trim()+" created.","ok"),i(r.trim())}).catch(f=>{c(!1),D(f)}))}return t(ve,{title:"New role",actions:t(W,{children:[t(P,{onClick:n,children:"Cancel"}),t(P,{kind:"primary",disabled:!p,onClick:d,children:"Create role"})]}),children:[t($,{label:"Name",children:t("input",{type:"text","aria-label":"Role name",placeholder:"editors",value:r,"aria-invalid":u?"true":void 0,onInput:f=>o(f.currentTarget.value),onKeyDown:f=>{f.key==="Enter"&&d()}})}),u?t(J,{kind:"bad",children:u}):t(H,{children:"Letters, digits, - and _. Permissions will name it, so pick the word you would say: editors, billing, support."}),t($,{label:"What it is for (optional)",children:t("input",{type:"text","aria-label":"What it is for",placeholder:"People who can change the catalogue",maxLength:Is,value:s,onInput:f=>l(f.currentTarget.value)})})]})}function Yc({role:e,config:n,onChanged:i,onClose:r}){let{readonly:o,features:s}=te(),[l,a]=m(null),[c,u]=m(e.description||""),[p,d]=m(""),[f,b]=m(null),[g,k]=m(!1),[y,T]=m(null),w=Yn(n,e.name),x=Es(w),E=!!s.email&&y===!0;function C(){S("GET","/admin/users?limit=200&role="+I(e.name)).then(h=>a(h.users||[])).catch(D)}j(C,[e.name]),j(()=>{s.email&&S("GET","/auth/providers").then(h=>T(!!(h.magicLink&&h.magicLink.enabled))).catch(()=>T(null))},[]);function _(){C(),i()}let N=(l||[]).map(h=>h.objectId);async function v(h){return((await S("GET","/admin/users?limit=20&q="+I(h.trim()))).users||[]).filter(Q=>N.indexOf(Q.objectId)===-1)}function R(h){h&&S("POST","/admin/roles/"+I(e.name)+"/users",{userId:h.objectId}).then(()=>{O(Pe(h)+" added to "+e.name+".","ok"),_()}).catch(D)}function A(h){let L=Pe(h);we("Remove "+L+" from "+e.name+"?","Their account stays. They stop being able to do what "+e.name+" can do.",()=>{S("DELETE","/admin/roles/"+I(e.name)+"/users/"+I(h.objectId)).then(()=>{O(L+" removed from "+e.name+".","ok"),_()}).catch(D)},"Remove")}function U(){S("PUT","/admin/roles/"+I(e.name),{description:c.trim()}).then(()=>{O("Saved.","ok"),i()}).catch(D)}async function X(h){let L=h.split(/[\\s,;]+/).map(ue=>ue.trim()).filter(Boolean);if(!L.length)return;k(!0);let Q=[],se=[],Me=[],Ae=[],le=[],ie=new Set;for(let ue of L){let Be=ue.toLowerCase();if(!ie.has(Be)){if(ie.add(Be),!Ps.test(Be)){Ae.push(ue);continue}try{let Qe=((await S("GET","/admin/users?limit=20&q="+I(Be))).users||[]).find(st=>(st.email||"").toLowerCase()===Be);if(Qe)N.indexOf(Qe.objectId)!==-1?se.push(Pe(Qe)):(await S("POST","/admin/roles/"+I(e.name)+"/users",{userId:Qe.objectId}),Q.push(Pe(Qe)));else if(!E)le.push(ue+" (no one has that address, and invitations are off)");else{let st=await S("POST","/admin/users",{username:Be,email:Be,invite:!0,roles:[e.name]});st.invited?Me.push(Be):le.push(ue+" (added, but the invitation did not go: "+(st.inviteError||"the mail failed")+")")}}catch(Oe){le.push(ue+" ("+Oe.message+")")}}}k(!1);let z=[],de=[];de.push(Q.length+" added"),(E||Me.length)&&de.push(Me.length+" invited"),z.push(de.join(", ")+"."),se.length&&z.push("Already in "+e.name+": "+se.join(", ")+"."),Ae.length&&z.push("Not an email address: "+Ae.join(", ")+".");for(let ue of le)z.push("Not added: "+ue+".");b(z),!le.length&&!Ae.length&&d(""),O(z[0],le.length?"bad":"ok"),_()}let B=h=>h===1?"1 person":h+" people",q=(x?x+(x===1?" rule names ":" rules name ")+e.name+" ("+w.map(sr).join("; ")+") and will stop matching anyone.":"No rule names "+e.name+", so deleting it changes no permission.")+" "+(l&&l.length?B(l.length)+" will no longer be in it; their accounts stay.":"No one is in it.");return t(Se,{title:e.name,subtitle:e.description||"A role",onClose:r,wide:!0,children:[t("div",{class:"drawer-section",children:["Members",l?" ("+l.length+")":""]}),l?t(ye,{class:"members-table",columns:["Person","Email","Joined",""],rows:l,empty:t(oe,{icon:"\u{1F464}",children:["No one is in ",e.name," yet."]}),renderRow:h=>t("tr",{"data-member":Pe(h),children:[t("td",{children:t("div",{class:"person",children:[t("span",{class:"avatar","aria-hidden":"true",children:Pe(h).charAt(0).toUpperCase()}),t("b",{children:Pe(h)}),h.disabled?t(K,{kind:"bad",children:"disabled"}):null]})}),t("td",{children:h.email||t("span",{class:"hint",children:"no email"})}),t("td",{title:he(h.createdAt),children:je(h.createdAt)}),t("td",{children:o?null:t("button",{type:"button",class:"chip-x","aria-label":"Remove "+Pe(h)+" from "+e.name,onClick:()=>A(h),children:"\u2715"})})]},h.objectId)}):null,o?null:t(W,{children:[t($,{label:"Add people",children:t(Ce,{id:"role-add-person",fetch:v,label:Pe,detail:h=>h.email&&h.email!==h.username?h.email:"",keyOf:h=>h.objectId,value:null,openOnFocus:!1,onPick:R,placeholder:"Type a name or an email\u2026",emptyText:"No one else matches.",onCreate:E?X:void 0,createWhen:h=>Ps.test(h.toLowerCase()),createLabel:h=>"Invite "+h+" to "+e.name+" by email"})}),t($,{label:"Add several by email",children:t("textarea",{"aria-label":"Email addresses",rows:3,placeholder:\`ann@example.com, bob@example.com
65022
- (one per line, or separated by commas)\`,value:p,onInput:h=>d(h.currentTarget.value)})}),t(V,{children:[t(M,{tiny:!0,disabled:g||!p.trim(),onClick:()=>X(p),children:g?"Adding\u2026":"Add them"}),t(H,{children:E?"Someone with an account is added. Anyone else is sent an invitation and joins "+e.name+" when the account is made.":s.email?"Someone with an account is added. Invitations are off: turn on sign-in links under Sign-in to invite people who have no account.":"Someone with an account is added. Email is not set up, so people without an account cannot be invited."})]}),f?t(J,{kind:f.length>1&&f.some(h=>h.indexOf("Not ")===0)?"warn":"",style:"margin-top:8px",children:t("span",{class:"bulk-report",children:f.map(h=>t("span",{children:h},h))})}):null]}),t("div",{class:"drawer-section",children:["What ",e.name," can do"]}),n?w.length?t("ul",{class:"role-uses",children:w.map(h=>t("li",{children:t("a",{href:As(h),children:sr(h)})},h.kind+":"+(h.name||"")))}):t("p",{class:"hint",children:["Nothing yet \u2014 ",t("a",{href:"#/permissions",children:["give ",e.name," something in Permissions \u2192"]})]}):null,t(H,{children:["A record can also be shared with ",e.name," on its own (Collections \u2192 a record \u2192 Who can see this); those are not counted here."]}),t("div",{class:"drawer-section",children:"About"}),t($,{label:"What it is for",children:t("input",{type:"text","aria-label":"What it is for",maxLength:Is,disabled:o,value:c,placeholder:"Say who belongs here",onInput:h=>u(h.currentTarget.value)})}),t(V,{children:[t(M,{tiny:!0,disabled:c.trim()===(e.description||""),onClick:U,children:"Save"}),t(me,{}),t(H,{children:["Created ",he(e.createdAt)]})]}),t(qe,{children:t(Ze,{label:"Delete role",why:q,title:"Delete "+e.name,warning:q,expected:e.name,onConfirm:()=>{S("DELETE","/admin/roles/"+I(e.name)).then(()=>{O("Role "+e.name+" deleted.","ok"),i(),Z("roles")}).catch(D)}})})]})}var Xn=[{id:"google",label:"Google",line:"Sign in with a Google account",icon:"G"},{id:"github",label:"GitHub",line:"Sign in with a GitHub account",icon:"\u2325"},{id:"oidc",label:"Another OpenID Connect provider",line:"Okta, Auth0, Keycloak, Entra ID, GitLab, Authentik\u2026",icon:"\u26BF"}],Xc=[{id:"identity",scopes:["openid","email"],label:"Who they are and their email address (needed to sign in)",locked:!0},{id:"profile",scopes:["profile"],label:"Their name and profile picture",locked:!1},{id:"github-identity",scopes:["read:user","user:email"],label:"Their GitHub account and its email address (needed to sign in)",locked:!0}];function bn(e){let n=[],i=new Set;for(let r of Xc){let o=r.scopes.filter(s=>e.includes(s));o.length&&(n.push({id:r.id,scopes:o,label:r.label,locked:r.locked}),o.forEach(s=>i.add(s)))}for(let r of e)i.has(r)||(i.add(r),n.push({id:"raw:"+r,scopes:[r],label:r,locked:!1}));return n}function Zc(e,n,i){let r=new Set;for(let o of n)(o.locked||i.includes(o.id))&&o.scopes.forEach(s=>r.add(s));return e.filter(o=>r.has(o))}var Qc=/^[a-z0-9][a-z0-9-]{0,31}$/,ed=["exchange","magic-link","callback","start","providers"];function td(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,32).replace(/-+$/g,"")}function ar(e){return e.idEdited?e.id.trim():e.tile!=="oidc"?e.tile:td(e.displayName)}function lr(e,n){return e?Qc.test(e)?ed.includes(e)?"\u201C"+e+"\u201D is a reserved word on this backend. Pick another id.":n.includes(e)?"There is already a provider called "+e+".":null:"An id is lowercase letters, digits and dashes, up to 32 characters, starting with a letter or digit.":"The provider needs an id \u2014 it is the part of the callback URL that names it."}function cr(e,n){let i=bn(n&&n.scopes||[]);return{tile:e,displayName:e==="oidc"?"":n&&n.displayName||e,issuer:e==="oidc"?"":n&&n.issuer||"",id:"",idEdited:!1,clientId:"",clientSecret:"",ticked:i.filter(r=>!r.locked).map(r=>r.id),enabled:!0,allowSignup:!0}}function nd(e,n){if(e.kind==="github")return"github";let i=n.google;return i&&e.issuer&&e.issuer===i.issuer?"google":"oidc"}function Ns(e,n){let i=nd(e,n),r=e.scopes||[],o=bn(r);return{tile:i,displayName:e.displayName||"",issuer:e.issuer||"",id:e.id,idEdited:!0,clientId:e.clientId||"",clientSecret:"",ticked:o.filter(s=>!s.locked).map(s=>s.id),enabled:e.enabled!==!1,allowSignup:e.allowSignup!==!1}}function dr(e,n,i){return i&&i.scopes&&i.scopes.length?i.scopes:n&&n.scopes||[]}function Ds(e,n,i){let r=dr(e,n,i),o={displayName:e.displayName.trim(),issuer:e.tile==="github"?"":e.issuer.trim(),clientId:e.clientId.trim(),scopes:Zc(r,bn(r),e.ticked),enabled:e.enabled,allowSignup:e.allowSignup};return o.preset=e.tile,e.clientSecret&&(o.clientSecret=e.clientSecret),o}function Os(e,n,i){if(!e.displayName.trim())return"Give the sign-in button a label \u2014 what it says to your users.";if(e.tile==="oidc"){let r=e.issuer.trim();if(!r)return"The issuer URL is where this provider publishes its OpenID settings \u2014 it is on their developer page.";if(!/^https?:\\/\\/[^\\s/]+/.test(r))return"The issuer is an https:// URL, e.g. https://login.example.com."}if(!n){let r=lr(ar(e),i);if(r)return r}return e.clientId.trim()?!n&&!e.clientSecret?"Paste the client secret from step 2.":null:"Paste the client id from step 2."}function Fs(e){return e==="google"?["Open Google Cloud \u2192 APIs & Services \u2192 Credentials.","Create credentials \u2192 OAuth client ID \u2192 application type Web application.","Under Authorised redirect URIs, press Add URI and paste the callback URL above.","Press Create and keep the Client ID and Client secret it shows for step 3."]:e==="github"?["Open GitHub \u2192 Settings \u2192 Developer settings \u2192 OAuth Apps (an OAuth App, not a GitHub App).","Press New OAuth App and give it your app\u2019s name and home page.","Paste the callback URL above into Authorization callback URL, then Register.","Press Generate a new client secret and keep the Client ID and the secret for step 3."]:["In your provider\u2019s admin console make a new application of the OpenID Connect / OAuth 2 web kind.","Paste the callback URL above where it asks for the redirect URI (it may say sign-in redirect or callback).","Allow the authorization code flow; PKCE is used, so a client secret is still required by most providers.","Keep the Client ID and Client secret it shows for step 3."]}function Ms(e){return e.ready?null:e.kind!=="github"&&!(e.issuer||"").trim()?"Step 1: the issuer URL":(e.clientId||"").trim()?e.hasClientSecret?null:"Step 3: the client secret":"Step 3: the client id"}function Zn(e){let n=e.trim();if(!n)return"Type an origin.";if(!/^https?:\\/\\//i.test(n))return"An origin starts with https:// (or http://localhost while you develop) \u2014 e.g. https://"+n.replace(/^\\/+/,"").split("/")[0];let i;try{i=new URL(n)}catch{return"That is not a URL this browser can read."}return i.hostname?i.protocol==="http:"&&!id(i.hostname)?"Plain http:// is only allowed for localhost \u2014 anywhere else the sign-in result would travel unencrypted. Use https://.":null:"An origin needs a host, e.g. https://app.example.com."}function id(e){let n=e.toLowerCase();return n==="localhost"||n==="127.0.0.1"||n==="[::1]"||n.endsWith(".localhost")}function Qn(e){try{return new URL(e.trim()).origin}catch{return e.trim()}}function rd(e,n){return e?e.replace("{id}",encodeURIComponent(n||"\u2026")):""}function Bs({params:e}){let{readonly:n}=te(),[i,r]=m(null),[o,s]=m(0),l=e[0]||null;function a(){S("GET","/admin/auth").then(g=>{r(g||{}),s(k=>k+1)}).catch(D)}j(()=>{a()},[]);function c(g){Ye(g).then(k=>O(k?"Callback URL copied.":"Could not copy \u2014 select the URL in the table instead.",k?"ok":"bad"))}function u(g){Ge("Remove provider",'Users with no password and no other provider will not be able to sign in. Their linked identities are kept, so re-adding "'+g.id+'" restores access.',g.id,()=>{S("DELETE","/admin/auth/providers/"+I(g.id)).then(()=>{O("Provider removed.","ok"),a()}).catch(D)})}let d=(i&&i.config||{}).providers||[],f=l&&l!=="new"&&d.find(g=>g.id===l)||null,b=!!i&&(l==="new"||!!f);return t(ce,{title:"Sign-in",subtitle:"How the people using your app sign in: Google, GitHub or any OpenID Connect provider, and links by email.",children:i?t("div",{children:[i.baseUrl&&i.baseUrl.usedFallback?t(J,{kind:"warn",children:i.baseUrl.warning}):null,t(V,{children:[t(M,{kind:"primary",onClick:()=>Z("signin","new"),children:"Add provider"}),t(P,{onClick:a,children:"Refresh"})]}),t(G,{}),t(ye,{columns:["Provider","Kind","Status","Callback URL to register",""],rows:d,empty:t(oe,{icon:"\u26BF",action:{label:"Add provider",onClick:()=>Z("signin","new")},children:"No sign-in providers yet. Add Google, GitHub or any OpenID Connect provider \u2014 three steps, starting with the URL they will ask you for."}),renderRow:g=>{let k=Ms(g);return t("tr",{children:[t("td",{children:[t("b",{children:g.displayName||g.id}),t("div",{class:"sub",children:g.id})]}),t("td",{children:g.kind==="github"?"GitHub":"OpenID Connect"}),t("td",{children:[g.enabled?t(K,{kind:"ok",children:"enabled"}):t(K,{children:"disabled"}),g.ready?t(K,{kind:"ok",children:"ready"}):t(K,{kind:"warn",title:g.notReadyReason||void 0,children:"not ready"}),k?t("div",{class:"sub step-missing",children:"Missing \u2014 "+k}):null]}),t("td",{children:t("code",{style:"word-break:break-all",children:g.callbackUrl})}),t("td",{class:"actions",children:[t(P,{tiny:!0,onClick:()=>c(g.callbackUrl||""),children:"Copy URL"}),t(M,{tiny:!0,onClick:()=>Z("signin",g.id),children:"Edit"}),t(M,{tiny:!0,kind:"danger",onClick:()=>u(g),children:"Remove"})]})]},g.id)}}),t(od,{data:i,readonly:n,onSaved:a},o),b?t(ad,{existing:f,presets:i.presets||{},callbackUrlTemplate:i.callbackUrlTemplate,baseUrlWarning:i.baseUrl&&i.baseUrl.usedFallback&&i.baseUrl.warning||null,taken:d.map(g=>g.id),onClose:()=>Z("signin"),onSaved:()=>{a(),Z("signin")}},l||"new"):null]}):null})}function od({data:e,readonly:n,onSaved:i}){let r=e.config||{},o=r.magicLink||{},[s,l]=m(!!o.enabled),[a,c]=m(String(o.ttlMinutes===void 0?15:o.ttlMinutes)),[u,p]=m(o.allowSignup!==!1),[d,f]=m((r.linking||{}).autoLinkVerifiedEmail!==!1),[b,g]=m(r.redirectAllowList||[]),[k,y]=m(String((r.sessions||{}).idleDays??30));function T(){S("PUT","/admin/auth",{magicLink:{enabled:s,ttlMinutes:Number(a)||15,allowSignup:u},redirectAllowList:b,linking:{autoLinkVerifiedEmail:d},sessions:{idleDays:Number(k)}}).then(()=>{O("Sign-in policy saved.","ok"),i()}).catch(D)}return t("div",{children:[t("h2",{children:"Magic links"}),t(pe,{children:[!e.magicLinkReady&&e.magicLinkNotReadyReason?t(J,{kind:"warn",children:e.magicLinkNotReadyReason}):null,t(re,{checked:s,disabled:n,onChange:l,children:"People may sign in with a link sent to their email"}),t("div",{class:"grid2",style:"margin-top:10px",children:t($,{label:"A link works for (minutes)",children:t("input",{type:"number",min:1,max:1440,value:a,disabled:n,onInput:w=>c(w.currentTarget.value)})})}),t(V,{style:"margin-top:10px",children:t(re,{checked:u,disabled:n,onChange:p,children:"An address nobody has used yet may create an account"})})]}),t("h2",{children:"Where your app lives"}),t(pe,{id:"app-origins",children:[t(fe,{style:"margin:0 0 8px",children:"The web addresses your app is served from, e.g. https://app.example.com. After signing in, a person is only ever sent back to one of these or to this backend itself \u2014 that is what stops the sign-in callback being used to send people (and their fresh session) somewhere else. If your app is on another address than this backend and it is not listed here, every sign-in is refused before it starts."}),t(Ie,{items:b,onChange:g,disabled:n,validate:Zn,normalise:Qn,placeholder:"https://app.example.com",addLabel:"Add address"})]}),t("h2",{children:"Staying signed in"}),t(pe,{id:"session-length",children:[t("div",{class:"grid2",children:t($,{label:"A sign-in ends after this many days unused",children:t("input",{type:"number",min:1,max:3650,value:k,disabled:n,onInput:w=>y(w.currentTarget.value)})})}),t(fe,{style:"margin:8px 0 0",children:"A person stays signed in while they keep using your app. Each visit starts the count of days again. A sign-in left on a lost phone or a shared computer stops working once it has gone unused for this many days. A new number also applies to people who are already signed in."})]}),t("h2",{children:"Account linking"}),t(pe,{children:[t(re,{checked:d,disabled:n,onChange:f,children:"A provider sign-in joins an existing account with the same verified email address"}),t(fe,{style:"margin:8px 0 0",children:"Only a provider-verified address ever matches, and if the local account had never verified its own address, its password and sessions are revoked at the moment of linking \u2014 that is what stops someone registering your address before you do and keeping access afterwards. Every such event is in the audit trail as auth.link.credentials-revoked. Off means such a sign-in is refused rather than linked."})]}),t(V,{style:"margin-top:12px",children:t(M,{kind:"primary",onClick:T,children:"Save policy"})})]})}var sd=[{n:1,label:"Which"},{n:2,label:"Register with them"},{n:3,label:"Paste back"}];function ad({existing:e,presets:n,callbackUrlTemplate:i,baseUrlWarning:r,taken:o,onClose:s,onSaved:l}){let[a,c]=m(e?3:1),[u,p]=m(()=>e?Ns(e,n):cr("google",n.google)),[d,f]=m(!1),[b,g]=m(!1),k=n[u.tile],y=e?e.id:ar(u),T=e&&e.callbackUrl?e.callbackUrl:rd(i,y),w=h=>p({...u,...h}),x=o.filter(h=>!e||h!==e.id);function E(h){if(e)return;let L=cr(h,n[h]);p({...L,enabled:u.enabled,allowSignup:u.allowSignup})}let C=(()=>{if(!u.displayName.trim())return"Give the sign-in button a label \u2014 what it says to your users.";if(u.tile==="oidc"){let h=u.issuer.trim();if(!h)return"The issuer URL is where this provider publishes its OpenID settings \u2014 it is on their developer page.";if(!/^https?:\\/\\/[^\\s/]+/.test(h))return"The issuer is an https:// URL, e.g. https://login.example.com."}return null})(),_=e?null:lr(y,x),N=Os(u,e,x);function v(){if(N){O(N,"bad");return}f(!0),S("PUT","/admin/auth/providers/"+I(y),Ds(u,k,e)).then(h=>{let L=h&&h.provider,Q=!!(L&&L.ready);O(Q?(L.displayName||y)+" is ready. Its callback URL is registered? Then people can sign in with it.":"Saved, but not usable yet: "+(L&&L.notReadyReason||"something is missing."),Q?"ok":"bad"),l()}).catch(h=>{f(!1),D(h)})}function R(){Ye(T).then(h=>{h?(g(!0),O("Callback URL copied.","ok"),setTimeout(()=>g(!1),2500)):O("The browser refused the clipboard. Select the URL and copy it by hand.","bad")})}let A=dr(u,k,e),U=bn(A),X=Xn.find(h=>h.id===u.tile)||Xn[0],B=h=>L=>w({[h]:L.currentTarget.value}),q=t(V,{children:[a>1?t(P,{onClick:()=>c(a-1),children:"Back"}):null,t(me,{}),a<3?t(P,{kind:"primary",onClick:()=>c(a+1),disabled:a===1?!!C:!!_,title:a===1?C||void 0:_||void 0,children:"Next"}):t(M,{kind:"primary",onClick:v,disabled:d||!!N,title:N||void 0,children:e?"Save":"Add "+(u.displayName.trim()||"provider")}),t(P,{onClick:s,children:"Cancel"})]});return t(Se,{title:e?"Edit "+(e.displayName||e.id):"Add a way to sign in",onClose:s,wide:!0,footer:q,children:[t("ol",{class:"wizard-steps","aria-label":"Steps",children:sd.map(h=>t("li",{class:h.n===a?"current":h.n<a||e?"done":"",children:t("button",{type:"button",disabled:h.n>a&&!e,onClick:()=>c(h.n),"aria-current":h.n===a?"step":void 0,children:[t("span",{class:"num","aria-hidden":"true",children:h.n}),t("span",{children:h.label})]})},h.n))}),a===1?t("div",{class:"wizard-step",id:"step-which",children:[t("div",{class:"tiles providers",role:"radiogroup","aria-label":"Which provider",children:Xn.map(h=>t("label",{class:"tile"+(u.tile===h.id?" on":""),children:[t("input",{type:"radio",name:"provider-tile",value:h.id,checked:u.tile===h.id,disabled:!!e,"aria-label":h.label,onChange:()=>E(h.id)}),t("b",{children:h.label}),t("span",{class:"sub",children:h.line})]},h.id))}),t("div",{class:"when-body",children:[t($,{label:"What the sign-in button says",children:t("input",{type:"text",id:"wiz-label",placeholder:u.tile==="oidc"?"e.g. Okta, Company SSO":"",value:u.displayName,onInput:B("displayName")})}),u.tile==="oidc"?t($,{label:"Issuer URL",children:t("input",{type:"text",id:"wiz-issuer",placeholder:"https://login.example.com",value:u.issuer,onInput:B("issuer")})}):null,u.tile==="oidc"?t(H,{children:k&&k.note||"Any OpenID Connect issuer works by configuration alone."}):null,C&&(u.displayName||u.issuer)?t(H,{children:C}):null]})]}):null,a===2?t("div",{class:"wizard-step",id:"step-register",children:[r?t(J,{kind:"warn",children:r}):null,t("div",{class:"field",children:[t("span",{class:"field-head",children:t("b",{children:X.label+" will ask for a redirect URL. It is this one:"})}),t("div",{class:"secret-box mono callback-url",id:"wiz-callback","data-callback":T,children:T}),t(V,{style:"margin-top:8px",children:[t(P,{kind:"primary",onClick:R,children:b?"Copied \u2713":"Copy"}),k&&k.consoleUrl?t("a",{class:"btn",href:k.consoleUrl,target:"_blank",rel:"noreferrer",children:"Open "+X.label+"\u2019s console \u2197"}):null]})]}),t("div",{class:"field",children:[t("span",{class:"field-head",children:t("b",{children:"Then, with them"})}),t("ol",{class:"wizard-list",id:"wiz-register-steps",children:Fs(u.tile).map((h,L)=>t("li",{children:h},L))})]}),e?null:t(He,{label:"Advanced: the id in the URL",children:[t($,{label:"Provider id",children:t("input",{type:"text",id:"wiz-id",value:y,onInput:h=>w({id:h.currentTarget.value,idEdited:!0})})}),t(H,{children:_||"It names this provider in the callback URL and in your app. Changing it later means registering a new URL."})]})]}):null,a===3?t("div",{class:"wizard-step",id:"step-paste",children:[t("div",{class:"grid2",children:[t($,{label:"Client id",children:t("input",{type:"text",id:"wiz-client-id",value:u.clientId,onInput:B("clientId")})}),t($,{label:"Client secret",children:t("input",{type:"password",id:"wiz-client-secret",placeholder:e&&e.hasClientSecret?"(unchanged)":"(not set)",value:u.clientSecret,onInput:B("clientSecret")})})]}),t("div",{class:"field",children:[t("span",{class:"field-head",children:[t("b",{children:"What we may read"}),t(H,{children:"The ones sign-in needs are on and cannot be turned off."})]}),t("div",{class:"scope-boxes",id:"wiz-scopes",children:[U.map(h=>t(Fe,{checked:h.locked||u.ticked.includes(h.id),disabled:h.locked,onChange:L=>w({ticked:L?[...u.ticked,h.id]:u.ticked.filter(Q=>Q!==h.id)}),children:t("span",{"data-scopes":h.scopes.join(" "),children:h.label})},h.id)),U.length?null:t(H,{children:"This provider asks for nothing beyond signing in."})]})]}),t("div",{class:"when-body",children:[t(re,{checked:u.allowSignup,onChange:h=>w({allowSignup:h}),children:"Someone new may create an account by signing in this way"}),t(re,{checked:u.enabled,onChange:h=>w({enabled:h}),children:"Shown on the sign-in page"})]}),N&&u.clientId?t(H,{children:N}):null]}):null]})}function Hs(e){if(typeof e=="string")return e;let n=Array.isArray(e.path?.hops)?e.path.hops.length:0;return\`Whoever reaches it (through \${n} link\${n===1?"":"s"})\`}var Pt=["find","get","create","update","delete"],ur="role:";function ld(e){return\`\${ur}\${e}\`}function pr(e){if(!e.startsWith(ur))return null;let n=e.slice(ur.length);return n.length>0?n:null}var mr={inherit:!1,anyone:!1,signedIn:!1,roles:[],unknown:[]},js={...mr,inherit:!0};function fr(e){return e==null?[]:Array.isArray(e)?e:[e]}function Jt(e){if(e==null)return{...js};let n={inherit:!1,anyone:!1,signedIn:!1,roles:[],unknown:[]};for(let i of fr(e)){if(typeof i!="string"){n.unknown.push(i);continue}if(i==="public")n.anyone=!0;else if(i==="authenticated")n.signedIn=!0;else{if(i==="nobody")continue;{let r=pr(i);r!==null?n.roles.includes(r)||n.roles.push(r):n.unknown.push(i)}}}return n}function ei(e){if(e.inherit)return;let n=[];e.anyone&&n.push("public"),e.signedIn&&n.push("authenticated");for(let i of e.roles)n.push(ld(i));for(let i of e.unknown)n.push(i);return n.length===0?"nobody":n.length===1?n[0]:n}function hr(e){return!e.inherit&&!e.anyone&&!e.signedIn&&e.roles.length===0&&e.unknown.length===0}function Ws(){return{...mr,anyone:!0}}function yn(){return{...mr}}function Us(){return{...js}}function Ks(e,n){return{...Gs(e),signedIn:n}}function Vs(e,n,i){let r=Gs(e),o=r.roles.filter(s=>s!==n);return i&&o.push(n),{...r,roles:o}}function Gs(e){return{...e,inherit:!1,anyone:!1}}var ot=[{op:"find",word:"List",verb:"list"},{op:"get",word:"Open one",verb:"open"},{op:"create",word:"Create",verb:"create"},{op:"update",word:"Change",verb:"change"},{op:"delete",word:"Delete",verb:"delete"}],gr=[{op:"upload",word:"Upload",verb:"upload"},{op:"read",word:"Open",verb:"open"},{op:"delete",word:"Delete",verb:"delete"}];function cd(e){let n=ot.find(i=>i.op===e)||gr.find(i=>i.op===e);return n?n.verb:e==="call"?"call":e}function ti(e,n){if(e.inherit)return n?"Default ("+ti(n)+")":"Default";if(e.anyone)return"Everyone";let i=[];return e.signedIn&&i.push("Signed in"),i.push(...e.roles,...e.unknown.map(Hs)),i.length?i.join(", "):"No one"}function vn(e,n){return ti(Jt(e),n===void 0?void 0:Jt(n))}function It(e,n){let i={};for(let r of n)i[r]=Jt(e?e[r]:void 0);return i}function Yt(e,n){let i={};for(let r of n){let o=ei(e[r]);o!==void 0&&(i[r]=o)}return i}function qs(e,n){return n.every(i=>e[i].inherit)}function Lt(e){let n=[];for(let i of e)for(let r of fr(i)){let o=typeof r=="string"?pr(r):null;o!==null&&n.indexOf(o)===-1&&n.push(o)}return n}function Xt(e,n){return n.map(i=>ei(e[i]))}function br(e,n){return n==="default"?{checked:e.inherit,implied:!1}:e.inherit?{checked:!1,implied:!1}:n==="everyone"?{checked:e.anyone,implied:!1}:n==="noOne"?{checked:hr(e),implied:e.anyone}:n==="signedIn"?{checked:e.signedIn||e.anyone,implied:e.anyone}:{checked:e.roles.indexOf(n.role)!==-1||e.anyone,implied:e.anyone}}function yr(e,n,i){return n==="default"?i?Us():yn():n==="noOne"?i?yn():e.inherit?yn():e:n==="everyone"?i?Ws():yn():n==="signedIn"?Ks(e,i):Vs(e,n.role,i)}function $s(e,n,i){return n.every(r=>e[r].roles.indexOf(i)===-1)}var zs=[{id:"public-read",label:"Public read, signed-in write",sentence:"Anyone can list and open records. Only signed-in people can create, change or delete them."},{id:"signed-in",label:"Signed-in only",sentence:"Only signed-in people can see or change anything."},{id:"owner",label:"Only the owner",sentence:"Signed-in people can create records, and each person sees and changes only the records they created."},{id:"role-only",label:"One role only",sentence:"Only people in the role you pick can see or change anything.",needsRole:!0},{id:"locked",label:"Locked",sentence:"No one can reach this collection except the admin credential."}];function Js(e,n){let i=r=>({find:r,get:r,create:r,update:r,delete:r});switch(e){case"public-read":return{permissions:{find:"public",get:"public",create:"authenticated",update:"authenticated",delete:"authenticated"},creatorOwns:!1};case"signed-in":return{permissions:i("authenticated"),creatorOwns:!1};case"owner":return{permissions:i("authenticated"),creatorOwns:!0};case"role-only":return{permissions:i("role:"+(n||"")),creatorOwns:!1};case"locked":return{permissions:i("nobody"),creatorOwns:!1}}}function vr(e){return e.filter(n=>!n.startsWith("_")).sort((n,i)=>n.localeCompare(i))}function Ys(e,n){if(!e||!e.permissions&&e.creatorOwns===void 0)return"The defaults";let i=e.permissions||{},r=ot.filter(s=>i[s.op]!==void 0).map(s=>s.verb+": "+vn(i[s.op]));e.creatorOwns!==void 0&&r.push(e.creatorOwns?"records belong to their creator":"records are shared");let o=ot.filter(s=>i[s.op]===void 0).length;return o&&o<ot.length&&r.push(o+" from the defaults"),r.join(" \xB7 ")||"The defaults"}function Xs(e){let n=Jt(e);return n.anyone?"public":hr(n)||n.inherit?"nobody":"other"}var Zs=[["off","Run every time"],["key","Replay on a repeated key"],["key-body","Replay on key + body"],["required","Require a key, then replay"],["required-body","Require a key + body"]];function dd(e){return!e||!e.enabled?"off":e.requireKey?e.hashBody?"required-body":"required":e.hashBody?"key-body":"key"}function ud(e){switch(e){case"off":return;case"key":return{enabled:!0};case"key-body":return{enabled:!0,hashBody:!0};case"required":return{enabled:!0,requireKey:!0};case"required-body":return{enabled:!0,requireKey:!0,hashBody:!0}}}function ni(e){return{call:Jt(e.configured===null?void 0:e.configured),runAs:e.runAs==="system"?"system":"",ownLimit:e.rateLimit!==null,perMinute:e.rateLimit?e.rateLimit.ratePerMinute:60,burst:e.rateLimit?e.rateLimit.burst:30,timeoutSeconds:e.timeoutMs===null?"":String(e.timeoutMs/1e3),idempotency:dd(e.idempotency)}}function Qs(e){if(e.ownLimit&&(!Number.isFinite(e.perMinute)||e.perMinute<0||!Number.isFinite(e.burst)||e.burst<0))return"A limit is a number of calls per minute and a burst, each 0 or more (0 and 0 means no limit at all).";if(e.timeoutSeconds.trim()!==""){let n=Number(e.timeoutSeconds);if(!Number.isFinite(n)||n<0)return"The time limit is a number of seconds, 0 or more (0 means no limit)."}return null}function ii(e){let n={},i=ei(e.call);i!==void 0&&(n.call=i),e.runAs==="system"&&(n.runAs="system"),e.ownLimit&&(n.rateLimit={ratePerMinute:e.perMinute,burst:e.burst}),e.timeoutSeconds.trim()!==""&&(n.timeoutMs=Math.round(Number(e.timeoutSeconds)*1e3));let r=ud(e.idempotency);return r&&(n.idempotency=r),Object.keys(n).length?n:void 0}function ea(e,n){let i=[];return e.deployed||i.push({kind:"warn",label:"not deployed",why:"A rule guards the name "+e.name+", but no deployed function answers to it. It grants nothing until one does."}),e.source==="graph"&&i.push({kind:"",label:"from the graph",why:"No rule of its own: the graph\u2019s Allow Unauthenticated port decides ("+(e.allowNoAuth?"ticked, so everyone":"unticked, so signed in")+")."}),e.graphRefusesAnonymous&&i.push({kind:"bad",label:"fails for signed-out callers",why:"This rule lets a signed-out caller through, but the function\u2019s Request node does not have Allow Unauthenticated ticked, so the call reaches the graph and fails there. Tick the port on the canvas, or set this to Signed in."}),e.rateLimitSource==="public-write-default"&&n&&i.push({kind:"accent",label:n.ratePerMinute+"/min default",why:"Anyone can call it and it writes records, and it has no limit of its own, so the public-write default applies: "+n.ratePerMinute+" a minute, burst "+n.burst+", per caller."}),e.rateLimitSource==="declared"&&e.effectiveRateLimit===null&&i.push({kind:"accent",label:"no limit, on purpose",why:"Its limit is 0 and 0, which means no budget of its own \u2014 the opt-out for a provider\u2019s webhook."}),i}function ta(e,n,i,r){let o=cd(n),s=r.allowed&&r.recordAllowed===!1,l=r.allowed&&!s?" may ":" may not ",a=n==="call"?"call "+i:o+" "+i;return e+l+a+(s?": the collection allows it, but that record\u2019s sharing does not.":".")}function na(e){return"The rule for it is "+vn(e.rule)+(e.source==="graph"?" (from the graph\u2019s Allow Unauthenticated port)":"")+"."}var ri=["upload","read","delete"];async function pd(){let[e,n,i]=await Promise.all([S("GET","/admin/permissions"),S("GET","/admin/roles").catch(()=>({roles:[]})),S("GET","/admin/schema").catch(()=>({tables:[]}))]);return{config:e.config||{},etag:e.etag||"",enforced:e.enforced!==!1,loopback:e.loopback===!0,roles:(n.roles||[]).map(r=>r.name),tables:(i.tables||[]).map(r=>r.name)}}function kr(e,n){return S("PUT","/admin/permissions",e,n?{"If-Match":n}:void 0)}function Cr(){let[e,n]=m(null),i=()=>{pd().then(n).catch(D)};return j(i,[]),[e,i,n]}function ra({params:e}){let n=e[0]||"";return n==="functions"?t(gd,{}):n?t(hd,{name:n},n):t(fd,{})}function Sr({enforced:e,switchBelow:n}){return e?t(J,{children:"Enforcement is on. Every rule below is applied to every request."}):n?t(J,{kind:"warn",children:"The rules below are not enforced yet. They take effect when you turn on Enforce my rules on this machine, under this notice."}):t(J,{kind:"warn",children:"Dev-open is on. While it is, this backend enforces nothing and everyone can do everything. The rules below say what will happen once Enforce my rules on this machine is on, at the top of the Permissions page. Dev-open only ever runs on a backend bound to this machine."})}var Tr=[{col:"everyone",word:"Everyone"},{col:"signedIn",word:"Signed in"},{col:"noOne",word:"No one"}];function wr(e){if(e==="default")return"Default";let n=Tr.find(i=>i.col===e);return n?n.word:e.role}function oi({rows:e,matrix:n,onChange:i,roles:r,onRoles:o,allRoles:s,defaults:l,disabled:a,id:c}){let u=e.map(b=>b.op),p=[...l?["default"]:[],...Tr.map(b=>b.col),...r.map(b=>({role:b}))],d=s.filter(b=>r.indexOf(b)===-1);function f(b,g){let k=n[b],y=br(k,g),T=e.find(x=>x.op===b).word,w=g==="default"||g==="everyone"||g==="signedIn"||g==="noOne"?g:"role-"+g.role;return t("td",{class:"perm-cell"+(y.implied?" implied":""),children:[t("input",{type:"checkbox","aria-label":T+": "+wr(g),"data-op":b,"data-col":w,checked:y.checked,disabled:a||y.implied,onChange:x=>i({...n,[b]:yr(k,g,x.target.checked)})}),g==="default"&&l?t("span",{class:"perm-default-words",children:ti(l[b])}):null]},w)}return t("div",{class:"scroller perm-scroller",children:t("table",{class:"perm-matrix",id:c,children:[t("thead",{children:t("tr",{children:[t("th",{class:"perm-op",children:"Who can\u2026"}),p.map(b=>{if(typeof b=="string")return t("th",{children:wr(b)},b);let g=$s(n,u,b.role);return t("th",{class:"perm-role",children:t("span",{class:"chip accent",children:[b.role,a?null:t("button",{type:"button",class:"chip-x","aria-label":"Remove the "+b.role+" column",title:g?"Remove this column":"Untick "+b.role+" in every row first",disabled:!g,onClick:()=>o(r.filter(k=>k!==b.role)),children:"\u2715"})]})},"role-"+b.role)}),t("th",{class:"perm-add",children:a?null:t(Ce,{id:c+"-add-role",fetch:b=>Promise.resolve(d.filter(g=>g.toLowerCase().includes(b.trim().toLowerCase()))),label:b=>b,keyOf:b=>b,value:null,onPick:b=>{b&&r.indexOf(b)===-1&&o(r.concat([b]))},placeholder:"+ role",emptyText:d.length?"No role matches.":s.length?"Every role is already a column.":"No roles yet \u2014 make one under Roles."})})]})}),t("tbody",{children:e.map(b=>t("tr",{"data-op":b.op,children:[t("td",{class:"perm-op",children:[t("b",{children:b.word})," ",t("span",{class:"hint mono",children:b.op})]}),p.map(g=>f(b.op,g)),t("td",{})]},b.op))})]})})}function md({sel:e,onChange:n,roles:i,onRoles:r,allRoles:o,inheritWord:s,disabled:l,id:a}){let c=[...s?["default"]:[],...Tr.map(p=>p.col),...i.map(p=>({role:p}))],u=o.filter(p=>i.indexOf(p)===-1);return t("div",{class:"rule-boxes",id:a,children:[c.map(p=>{let d=br(e,p),f=p==="default"?s||"Default":wr(p),b=typeof p=="string"?p:"role-"+p.role;return t("label",{class:"check"+(d.implied?" implied":""),children:[t("input",{type:"checkbox","data-col":b,checked:d.checked,disabled:l||d.implied,onChange:g=>n(yr(e,p,g.target.checked))}),typeof p=="string"?f:t("span",{class:"chip accent",children:f}),typeof p!="string"&&!l&&e.roles.indexOf(p.role)===-1?t("button",{type:"button",class:"chip-x","aria-label":"Remove "+p.role,onClick:()=>r(i.filter(g=>g!==p.role)),children:"\u2715"}):null]},b)}),l?null:t("span",{class:"rule-add",children:t(Ce,{id:a+"-add-role",fetch:p=>Promise.resolve(u.filter(d=>d.toLowerCase().includes(p.trim().toLowerCase()))),label:p=>p,keyOf:p=>p,value:null,onPick:p=>{p&&i.indexOf(p)===-1&&r(i.concat([p]))},placeholder:"+ role",emptyText:u.length?"No role matches.":"No other roles."})})]})}function xr({kind:e,targets:n,initialTarget:i,enforced:r}){let[o,s]=m("user"),[l,a]=m(null),[c,u]=m("find"),[p,d]=m(i||n[0]||""),[f,b]=m(null),[g,k]=m(null),[y,T]=m(""),[w,x]=m(!1);j(()=>{!p&&n.length&&d(i||n[0])},[n.join(\`
65023
- \`)]);let E=e==="collection"&&(c==="get"||c==="update"||c==="delete"),C=p!==""&&(o==="anonymous"||l!==null),_=o==="anonymous"?"Someone signed out":l?Pe(l):"someone";async function N(R){return(await S("GET","/admin/users?limit=20&q="+I(R.trim()))).users||[]}async function v(){if(C){x(!0);try{let A={principal:o==="anonymous"?{kind:"anonymous"}:{kind:"user",userId:l.objectId}};e==="function"?A.functionName=p:(A.collection=p,A.op=c,E&&f&&(A.record=await S("GET","/api/"+I(p)+"/"+I(f.objectId))));let U=await S("POST","/admin/permissions/check",A);k(U),T(ta(_,e==="function"?"call":c,p+(E&&f?" ("+f.label+")":""),U))}catch(R){D(R)}x(!1)}}return t(pe,{class:"tryas",id:"tryas",children:[t(V,{class:"tryas-line",children:[t(kt,{children:"Try it"}),t("span",{children:"as"}),t("select",{"aria-label":"Try as",value:o,onChange:R=>s(R.currentTarget.value),children:[t("option",{value:"user",children:"a person\u2026"}),t("option",{value:"anonymous",children:"someone signed out"})]}),o==="user"?t("span",{class:"tryas-person",children:t(Ce,{id:"tryas-person",fetch:N,label:Pe,detail:R=>R.email&&R.email!==R.username?R.email:"",keyOf:R=>R.objectId,value:l,onPick:a,placeholder:"Type a name or an email\u2026",emptyText:"No one matches."})}):null,t("span",{children:"doing"}),e==="function"?t("b",{children:"Call"}):t("select",{"aria-label":"Doing",value:c,onChange:R=>u(R.currentTarget.value),children:ot.map(R=>t("option",{value:R.op,children:R.word},R.op))}),t("span",{children:"on"}),t("select",{"aria-label":"On",value:p,onChange:R=>d(R.currentTarget.value),children:n.map(R=>t("option",{value:R,children:R},R))}),E?t("span",{class:"tryas-record",children:t(Ce,{id:"tryas-record",fetch:R=>p?Kt(p,R):Promise.resolve([]),label:R=>R.label,detail:R=>R.detail,keyOf:R=>R.objectId,value:f,onPick:b,placeholder:"any record, or pick one\u2026",emptyText:"No record matches."})}):null,t(P,{tiny:!0,kind:"primary",disabled:!C||w,onClick:v,children:w?"Asking\u2026":"Ask"})]}),g?t("div",{class:"verdict "+(g.allowed&&g.recordAllowed!==!1?"ok":"bad"),"aria-live":"polite",children:[t("b",{children:y}),t("span",{class:"hint",children:na(g)}),t("span",{class:"hint mono",children:g.reason}),r?null:t("span",{class:"hint",children:"Dev-open is on, so right now this backend would let it through anyway. This is what happens once enforcement is on."})]}):null]})}function ia(e,n){return{...e,defaults:{permissions:Yt(n.defaults,Pt),creatorOwns:n.owns},files:Yt(n.files,ri),signup:n.signup}}function fd(){let{readonly:e}=te(),[n,i,r]=Cr(),[o,s]=m(null),[l,a]=m([]),[c,u]=m([]),[p,d]=m(null),[f,b]=m(!1),[g,k]=m("");function y(A){let U=A.config,X=It(U.defaults&&U.defaults.permissions||{},Pt),B=It(U.files||{},ri),q={defaults:X,owns:!!(U.defaults&&U.defaults.creatorOwns),files:B,signup:U.signup===void 0?"nobody":U.signup};s(q),k(JSON.stringify(ia(U,q))),a(Lt(Xt(X,Pt))),u(Lt(Xt(B,ri))),d(null)}if(j(()=>{n&&y(n)},[n&&n.etag]),!n||!o)return t(ce,{title:"Permissions"});let T=n.config,w=vr(n.tables),x=n.tables.filter(A=>A.startsWith("_")).length,E=()=>ia(T,o),C=JSON.stringify(E())!==g,_=Pt.some(A=>o.defaults[A].inherit)||ri.some(A=>o.files[A].inherit);function N(A){b(!0),kr({...T,devOpen:!A},n.etag).then(()=>(O(A?"Your rules are now enforced on this machine.":"Dev-open is on: this backend enforces nothing until you turn it off.","ok"),i(),So().catch(()=>{}))).catch(U=>{U&&U.status===412?d(String(U.message)):D(U)}).then(()=>b(!1))}function v(){if(_){O("Every row of the defaults and of Files needs an answer.","bad");return}b(!0),kr(E(),n.etag).then(A=>{O("Permissions saved.","ok"),r({...n,config:A.config,etag:A.etag})}).catch(A=>{A&&A.status===412?d(String(A.message)):D(A)}).then(()=>b(!1))}let R=Xs(o.signup);return t(ce,{title:"Permissions",subtitle:"Who may list, open, create, change and delete what. Tick boxes; nothing here is typed.",children:[t(Sr,{enforced:n.enforced,switchBelow:n.loopback}),n.loopback?t(V,{style:"margin-top:10px",children:[t(re,{id:"enforce-rules",checked:n.enforced,disabled:e||f||C,onChange:N,children:"Enforce my rules on this machine"}),C?t(H,{children:"Save or undo your changes to the rules first."}):null]}):null,t(xr,{kind:"collection",targets:w,enforced:n.enforced}),t(G,{}),p?t(J,{kind:"bad",children:[p," ",t(P,{tiny:!0,onClick:i,children:"Reload"})]}):null,t("h2",{children:"Any collection without rules of its own"}),t(pe,{id:"defaults",children:[t(oi,{id:"defaults-matrix",rows:ot,matrix:o.defaults,onChange:A=>s({...o,defaults:A}),roles:l,onRoles:a,allRoles:n.roles,disabled:e}),t(G,{h:8}),t(re,{id:"defaults-owns",checked:o.owns,disabled:e,onChange:A=>s({...o,owns:A}),children:"Records belong to their creator"}),t("div",{class:"switch-why",children:"When a signed-in user creates a record, only they (and the roles allowed above) can see and change it."})]}),t("h2",{children:"Sign-up"}),t(pe,{id:"signup",children:[t($,{label:"Who can make an account",children:t("select",{"aria-label":"Who can make an account",disabled:e,value:R,onChange:A=>{let U=A.currentTarget.value;s({...o,signup:U==="public"?"public":U==="nobody"?"nobody":T.signup||"nobody"})},children:[t("option",{value:"public",children:"Anyone can sign up"}),t("option",{value:"nobody",children:"Nobody \u2014 accounts are made here, under Users"}),R==="other"?t("option",{value:"other",children:["As stored: ",vn(T.signup)]}):null]})}),t(H,{children:"Signing in with a link or a provider makes an account too, when Sign-in allows it."})]}),t("h2",{children:"Files"}),t(pe,{id:"files",children:[t(oi,{id:"files-matrix",rows:gr,matrix:o.files,onChange:A=>s({...o,files:A}),roles:c,onRoles:u,allRoles:n.roles,disabled:e}),t(H,{children:"Open means fetching a file by its address. A signed address always opens, whoever asks."})]}),t(V,{children:[t(M,{kind:"primary",disabled:!C||f,onClick:v,children:f?"Saving\u2026":"Save"}),t(P,{disabled:!C,onClick:()=>y(n),children:"Undo changes"}),t(me,{}),t(H,{children:C?"Unsaved changes.":"Saved."})]}),t("h2",{children:"Collections"}),t(ye,{columns:["Collection","Rules",""],rows:w,empty:t(oe,{action:{label:"Create a collection in Schema",onClick:()=>Z("schema"),write:!1},children:"No collections to set permissions on yet."}),renderRow:A=>{let U=(T.collections||{})[A],X=!!U&&(!!U.permissions||U.creatorOwns!==void 0);return t("tr",{class:"clickable","data-collection":A,onClick:()=>Z("permissions",A),children:[t("td",{children:t("b",{children:A})}),t("td",{class:X?void 0:"hint",children:Ys(U,T.defaults&&T.defaults.permissions||{})}),t("td",{children:X?t(K,{kind:"accent",children:"its own rules"}):t(K,{children:"defaults"})})]},A)}}),t(H,{children:[x?"The backend\u2019s own tables (accounts, roles, sessions, keys, audit) are locked to the admin credential and cannot be given rules. ":"",t("a",{href:"#/permissions/functions",children:"Who can call each function \u2192"})]})]})}function hd({name:e}){let{readonly:n}=te(),[i,,r]=Cr(),[o,s]=m(null),[l,a]=m([]),[c,u]=m(!0),[p,d]=m(!1),[f,b]=m(""),[g,k]=m(!1),y=Pt;function T(X){let B=(X.config.collections||{})[e],q=!!B&&(!!B.permissions||B.creatorOwns!==void 0),h=It(B&&B.permissions,y);s(h),a(Lt(Xt(h,y))),u(!q),d(B&&B.creatorOwns!==void 0?!!B.creatorOwns:!!(X.config.defaults&&X.config.defaults.creatorOwns))}if(j(()=>{i&&T(i)},[i&&i.etag]),!i||!o)return t(ce,{title:e});let w=i.config,x=It(w.defaults&&w.defaults.permissions||{},y),E=vr(i.tables),C=(w.collections||{})[e];if(e.startsWith("_"))return t(ce,{title:e,children:[t(J,{kind:"warn",children:[e," is one of the backend\u2019s own tables. It is locked to the admin credential and cannot be given rules."]}),t("a",{href:"#/permissions",children:"\u2190 Permissions"})]});let _=c?{}:{permissions:Yt(o,y),creatorOwns:p},N=C&&(C.permissions||C.creatorOwns!==void 0)?{permissions:Yt(It(C.permissions,y),y),creatorOwns:C.creatorOwns!==void 0?!!C.creatorOwns:!!(w.defaults&&w.defaults.creatorOwns)}:{},v=JSON.stringify(_)!==JSON.stringify(N),R=!c&&qs(o,y);function A(X){let B=Js(X,f),q=It(B.permissions,y);u(!1),s(q),a(Lt(Xt(q,y))),d(!!B.creatorOwns)}function U(){k(!0);let X=q=>S("GET","/admin/permissions").then(h=>{O(q,"ok"),r({...i,config:h.config||{},etag:h.etag||""})});(c?S("DELETE","/admin/permissions/collections/"+I(e)).then(()=>X(e+" now uses the defaults.")):S("PUT","/admin/permissions/collections/"+I(e),{permissions:Yt(o,y),creatorOwns:p}).then(()=>X("Permissions saved for "+e+"."))).catch(D).then(()=>k(!1))}return t(ce,{title:e,subtitle:"Who may list, open, create, change and delete "+e+" records.",children:[t("a",{href:"#/permissions",children:"\u2190 All collections"}),t(G,{}),t(Sr,{enforced:i.enforced}),t(xr,{kind:"collection",targets:E.indexOf(e)===-1?[e].concat(E):E,initialTarget:e,enforced:i.enforced}),t(G,{}),E.indexOf(e)===-1?t(J,{kind:"warn",children:["There is no collection called ",e," in the schema. Rules for it apply once one exists."]}):null,t(pe,{id:"collection-rules",children:[t(V,{children:[t(re,{id:"use-defaults",checked:c,disabled:n,onChange:u,children:"Use the defaults"}),t(me,{}),c?null:t("span",{class:"tpl-line",children:[t("span",{class:"hint",children:"Start from\u2026"}),zs.map(X=>t(P,{tiny:!0,title:X.sentence,disabled:n||X.needsRole&&!f,onClick:()=>A(X.id),children:X.label},X.id)),t("select",{"aria-label":"Role for the template",disabled:n,value:f,onChange:X=>b(X.currentTarget.value),children:[t("option",{value:"",children:"role\u2026"}),i.roles.map(X=>t("option",{value:X,children:X},X))]})]})]}),t(G,{h:8}),c?t(W,{children:[t(H,{children:["These are the backend\u2019s defaults. Turn the switch off to give ",e," rules of its own."]}),t(oi,{id:"collection-matrix",rows:ot,matrix:x,onChange:()=>{},roles:Lt(Xt(x,y)),onRoles:()=>{},allRoles:i.roles,disabled:!0}),t(G,{h:8}),t(re,{checked:!!(w.defaults&&w.defaults.creatorOwns),disabled:!0,onChange:()=>{},children:"Records belong to their creator"})]}):t(W,{children:[t(oi,{id:"collection-matrix",rows:ot,matrix:o,onChange:s,roles:l,onRoles:a,allRoles:i.roles,defaults:x,disabled:n}),t(G,{h:8}),t(re,{id:"collection-owns",checked:p,disabled:n,onChange:d,children:"Records belong to their creator"}),t("div",{class:"switch-why",children:"When a signed-in user creates a record, only they (and the roles allowed above) can see and change it."})]}),t(G,{h:8}),t(V,{children:[t(M,{kind:"primary",disabled:!v||g,onClick:U,children:g?"Saving\u2026":"Save"}),t(P,{disabled:!v,onClick:()=>T(i),children:"Undo changes"}),t(me,{}),t(H,{children:R?"Every row is on Default: saving stores no rule of its own, only the creator setting.":v?"Unsaved changes.":"Saved."})]})]})]})}function gd(){let{readonly:e}=te(),[n,i,r]=Cr(),[o,s]=m(null),[l,a]=m(null);function c(){S("GET","/admin/permissions/functions").then(s).catch(D)}if(j(c,[n&&n.etag]),!n||!o)return t(ce,{title:"Functions"});function u(d,f){let b={...n.config.functions||{}};return f?b[d]=f:delete b[d],kr({...n.config,functions:b},n.etag).then(g=>{O(d+" saved.","ok"),a(null),r({...n,config:g.config,etag:g.etag})}).catch(g=>{g&&g.status===412?a(String(g.message)):D(g)})}let p=o.functions.map(d=>d.name);return t(ce,{title:"Functions",subtitle:"Who can call each function, what it runs as, its budget, its time limit, and what a repeated delivery does.",children:[t("a",{href:"#/permissions",children:"\u2190 Permissions"}),t(G,{}),t(Sr,{enforced:n.enforced}),t(xr,{kind:"function",targets:p,enforced:n.enforced}),t(G,{}),l?t(J,{kind:"bad",children:[l," ",t(P,{tiny:!0,onClick:i,children:"Reload"})]}):null,t(H,{children:["Every function also shares one budget",o.classRateLimit?" ("+o.classRateLimit.ratePerMinute+" a minute, burst "+o.classRateLimit.burst+")":"","; a limit here can only tighten it. Without a time limit of its own a function gets ",o.defaultTimeoutMs/1e3," seconds.",o.idempotency.available?" A replayed answer is kept for "+o.idempotency.ttlHours+" hours.":" Duplicate deliveries cannot be replayed on this backend: its store is not available."]}),t(G,{}),o.functions.length?o.functions.map(d=>t(bd,{row:d,allRoles:n.roles,publicWriteDefault:o.publicWriteDefaultRateLimit,idempotencyAvailable:o.idempotency.available,readonly:e,onSave:f=>u(d.name,f)},d.name+":"+JSON.stringify(d))):t(oe,{icon:"\u0192",children:"No functions yet. Deploy a project with a Request node, or a rule appears here once one is written."})]})}function bd({row:e,allRoles:n,publicWriteDefault:i,idempotencyAvailable:r,readonly:o,onSave:s}){let[l,a]=m(()=>ni(e)),[c,u]=m(()=>Lt([e.configured===null?void 0:e.configured])),[p,d]=m(!1),f=ea(e,i),b=Qs(l),g=JSON.stringify(ii(l)||null)!==JSON.stringify(ii(ni(e))||null),k="From the graph ("+(e.allowNoAuth?"everyone":"signed in")+")";function y(){if(b){O(b,"bad");return}d(!0),s(ii(l)).then(()=>d(!1))}function T(){we("Forget the rules for "+e.name+"?","It goes back to what the graph declares"+(e.deployed?"":", and since nothing is deployed under that name, the row disappears")+".",()=>{d(!0),s(void 0).then(()=>d(!1))},"Forget")}return t(pe,{class:"fn-card",id:"fn-"+e.name,children:[t(V,{children:[t(kt,{children:e.name}),e.workflow?t("span",{class:"hint",children:e.workflow}):null,f.map(w=>t(K,{kind:w.kind,title:w.why,children:w.label},w.label)),t(me,{}),t("span",{class:"hint",children:["Who can call now: ",vn(e.call)]})]}),f.filter(w=>w.kind==="bad"||w.kind==="warn").map(w=>t(H,{children:w.why},w.label)),t(G,{h:8}),t($,{label:"Who can call",children:t(md,{id:"fn-call-"+e.name,sel:l.call,onChange:w=>a({...l,call:w}),roles:c,onRoles:u,allRoles:n,inheritWord:k,disabled:o})}),t("div",{class:"fn-fields",children:[t($,{label:"Runs as",children:t("select",{"aria-label":"Runs as",disabled:o,value:l.runAs,onChange:w=>a({...l,runAs:w.currentTarget.value}),children:[t("option",{value:"",children:"Not said"}),t("option",{value:"system",children:"The system"})]})}),t($,{label:"Its own limit",children:t("span",{class:"field-line",children:[t(re,{label:"Its own limit",checked:l.ownLimit,disabled:o,onChange:w=>a({...l,ownLimit:w})}),l.ownLimit?t(W,{children:[t("input",{type:"number",min:0,"aria-label":"Calls per minute",disabled:o,value:l.perMinute,onInput:w=>a({...l,perMinute:Number(w.currentTarget.value)})}),t("span",{class:"hint",children:"a minute, burst"}),t("input",{type:"number",min:0,"aria-label":"Burst",disabled:o,value:l.burst,onInput:w=>a({...l,burst:Number(w.currentTarget.value)})})]}):t("span",{class:"hint",children:"the shared budget only"})]})}),t($,{label:"Time limit (seconds)",children:t("input",{type:"number",min:0,step:"any","aria-label":"Time limit in seconds",placeholder:"the default",disabled:o,value:l.timeoutSeconds,onInput:w=>a({...l,timeoutSeconds:w.currentTarget.value})})}),t($,{label:"Duplicate deliveries",children:t("select",{"aria-label":"Duplicate deliveries",disabled:o||!r,value:l.idempotency,onChange:w=>a({...l,idempotency:w.currentTarget.value}),children:Zs.map(([w,x])=>t("option",{value:w,children:x},w))})})]}),b?t(J,{kind:"bad",children:b}):null,t(V,{children:[t(M,{tiny:!0,kind:"primary",disabled:!g||p||!!b,onClick:y,children:p?"Saving\u2026":"Save"}),t(P,{tiny:!0,disabled:!g,onClick:()=>a(ni(e)),children:"Undo"}),e.source==="configured"||e.runAs||e.rateLimit||e.timeoutMs!==null||e.idempotency?t(M,{tiny:!0,disabled:p,onClick:T,children:"Forget its rules"}):null,t(me,{}),t(H,{children:g?"Unsaved changes.":"Saved."})]})]})}var oa={read:!1,write:!1,anyFunction:!1,functions:[]};function sa(e){let n=[];return e.read&&e.write?n.push("classes:*"):e.read?n.push("classes:read"):e.write&&n.push("classes:write"),e.anyFunction?n.push("functions:*"):e.functions.forEach(i=>n.push("functions:"+i)),n}function Rr(e){let n={read:!1,write:!1,anyFunction:!1,functions:[]};return(e||[]).forEach(i=>{if(i==="classes:*")n.read=n.write=!0;else if(i==="classes:read")n.read=!0;else if(i==="classes:write")n.write=!0;else if(i==="functions:*")n.anyFunction=!0;else if(i.indexOf("functions:")===0&&i.length>10){let r=i.slice(10);n.functions.indexOf(r)===-1&&n.functions.push(r)}}),n}function Er(e){return!e.read&&!e.write&&!e.anyFunction&&e.functions.length===0}function aa(e){let n=Rr(e),i=[];return n.read&&i.push("read data"),n.write&&i.push("write data"),n.anyFunction?i.push("any function"):n.functions.length===1?i.push("1 function"):n.functions.length>1&&i.push(n.functions.length+" functions"),i}var la="X-NodeGX-Api-Key";function _r(e){return String(e.username||e.email||e.objectId)}function ca({params:e}){let{whoami:n}=te(),i=!!(n&&n.security&&n.security.devOpen),[r,o]=m(null),[s,l]=m([]),[a,c]=m(null),u=e[0]||"";function p(){S("GET","/admin/keys").then(g=>o(g.keys||[])).catch(D)}j(()=>{p(),S("GET","/api/_User?limit=200").then(g=>l(g.results||[])).catch(()=>l([])),S("GET","/admin/permissions/functions").then(g=>c((g.functions||[]).map(k=>k.name))).catch(()=>c([]))},[]);let d={};s.forEach(g=>d[g.objectId]=g);function f(g){Ge("Revoke key",'Anything using "'+g.name+'" stops working immediately.',g.name,()=>{S("DELETE","/admin/keys/"+I(g.objectId)).then(()=>{O("Key revoked.","ok"),p()}).catch(D)},"Revoke")}let b=u&&u!=="new"&&r&&r.find(g=>g.objectId===u)||null;return t(ce,{title:"API keys",subtitle:"A key lets a script or another server call this backend. Tick what it may do; the secret is shown once.",children:[t(V,{children:[t(M,{kind:"primary",onClick:()=>Z("keys","new"),children:"New key"}),t(P,{onClick:p,children:"Refresh"})]}),t(G,{}),i?t(W,{children:[t(J,{kind:"warn",children:["This backend is ",t("b",{children:"dev-open"}),": while it is, every key may do everything, whatever its boxes say. The boxes take effect once Enforce my rules on this machine is on, at the top of the ",t("a",{href:"#/permissions",children:"Permissions page"}),". A deployed backend refuses to start with dev-open on."]}),t(G,{})]}):null,r?t(ye,{columns:["Name","May do","Acts as","Last used","Created","Status",""],rows:r,empty:t(oe,{icon:"\u{1F511}",action:{label:"New key",onClick:()=>Z("keys","new")},children:"No API keys yet. A key lets a server or script call this backend."}),renderRow:g=>t("tr",{class:g.revoked?"revoked":"",children:[t("td",{children:g.name}),t("td",{children:aa(g.scopes).map(k=>t(K,{kind:"accent",children:k},k))}),t("td",{children:g.actsAsUserId?d[g.actsAsUserId]?_r(d[g.actsAsUserId]):"a user that is gone":t("span",{class:"hint",children:"backend"})}),t("td",{title:he(g.lastUsedAt),children:je(g.lastUsedAt)}),t("td",{children:he(g.createdAt)}),t("td",{children:g.revoked?t(K,{kind:"bad",children:"revoked"}):t(K,{kind:"ok",children:"active"})}),t(Je,{children:g.revoked?null:t(W,{children:[t(M,{tiny:!0,onClick:()=>Z("keys",g.objectId),children:"Edit"})," ",t(M,{tiny:!0,kind:"danger",onClick:()=>f(g),children:"Revoke"})]})})]},g.objectId)}):null,u==="new"||b?t(yd,{existing:b,users:s,functions:a,onClose:()=>Z("keys"),onChanged:p},u):null]})}function yd({existing:e,users:n,functions:i,onClose:r,onChanged:o}){let[s,l]=m(e?e.name:""),[a,c]=m(e?Rr(e.scopes):oa),[u,p]=m(e&&e.actsAsUserId?n.find(v=>v.objectId===e.actsAsUserId)||{objectId:e.actsAsUserId}:null),[d,f]=m(!!(e&&e.actsAsUserId)),[b,g]=m(null),[k,y]=m(!1),[T,w]=m(null),x=sa(a),E=!k&&!Er(a)&&(e?!0:s.trim()!=="")&&(!d||!!u);function C(){if(Er(a)){g("Tick at least one thing the key may do.");return}if(d&&!u){g("Pick the user this key acts as, or switch it back to the backend.");return}g(null),y(!0);let v=d&&u?u.objectId:null;(e?S("PUT","/admin/keys/"+I(e.objectId),{scopes:x,actsAsUserId:v}):S("POST","/admin/keys",{name:s.trim(),scopes:x,actsAsUserId:v})).then(A=>{y(!1),o(),e?(O("Key updated.","ok"),r()):w(A.secret)}).catch(A=>{y(!1),g(A.message)})}let _=()=>{o(),r()};if(T!==null)return t(Se,{title:"Your new key: "+s.trim(),onClose:_,footer:t(P,{kind:"primary",onClick:_,children:"Done"}),children:t(kd,{secret:T})});let N=v=>{let R=v.trim().toLowerCase();return Promise.resolve(n.filter(A=>!R||(_r(A)+" "+String(A.email||"")).toLowerCase().indexOf(R)!==-1))};return t(Se,{title:e?'What "'+e.name+'" may do':"New key",subtitle:e?"The secret does not change. Anything using this key gets the new permissions on its next request.":void 0,onClose:r,footer:t(W,{children:[t(P,{onClick:r,children:"Cancel"}),t(M,{kind:"primary",disabled:!E,onClick:C,children:e?"Save":"Create"})]}),children:[t("div",{class:"form-grid",onInput:()=>g(null),children:[e?null:t($,{label:"Name",children:t("input",{type:"text",placeholder:"What it is for \u2014 e.g. nightly report",value:s,autoFocus:!0,onInput:v=>l(v.currentTarget.value)})}),t("div",{class:"field",children:[t("span",{class:"field-head",children:t("b",{children:"What it may do"})}),t(vd,{choice:a,onChange:c,functions:i})]}),t("div",{class:"field",children:[t("span",{class:"field-head",children:t("b",{children:"Acts as"})}),t("div",{class:"scope-group",children:[t("label",{class:"check",children:[t("input",{type:"radio",name:"acts-as",checked:!d,onChange:()=>f(!1)})," This key is the backend"]}),t("label",{class:"check",children:[t("input",{type:"radio",name:"acts-as",checked:d,onChange:()=>f(!0)})," This key acts as a user"]}),d?t("div",{class:"scope-indent",children:[t(Ce,{fetch:N,label:_r,detail:v=>v.email&&v.email!==v.username?String(v.email):"",keyOf:v=>v.objectId,value:u,onPick:p,placeholder:"Type a username\u2026",emptyText:n.length?"No user by that name.":"This backend has no users yet."}),t(H,{children:"Records the user cannot see, the key cannot see."})]}):t(H,{children:"It sees every record its permissions cover."})]})]})]}),b?t(J,{kind:"bad",style:"margin-top:12px",children:b}):null]})}function vd({choice:e,onChange:n,functions:i,disabled:r}){let o=i||[],s=e.functions.filter(u=>o.indexOf(u)===-1),l=o.concat(s),a=e.read&&e.write,c=(u,p)=>{let d=e.functions.filter(f=>f!==u);p&&d.push(u),n({...e,functions:d})};return t("div",{class:"scope-groups",children:[t("div",{class:"scope-group",children:[t("label",{class:"check scope-all",children:[t("input",{type:"checkbox","aria-label":"All data",checked:a,disabled:r,ref:u=>{u&&(u.indeterminate=!a&&(e.read||e.write))},onChange:u=>{let p=u.currentTarget.checked;n({...e,read:p,write:p})}}),t("b",{children:"Data"})]}),t("div",{class:"scope-list",children:[t(Fe,{checked:e.read,disabled:r,onChange:u=>n({...e,read:u}),children:"Read records"}),t(Fe,{checked:e.write,disabled:r,onChange:u=>n({...e,write:u}),children:"Write records"})]})]}),t("div",{class:"scope-group",children:[t("label",{class:"check scope-all",children:[t("input",{type:"checkbox","aria-label":"Call any function",checked:e.anyFunction,disabled:r,onChange:u=>n({...e,anyFunction:u.currentTarget.checked})}),t("b",{children:"Functions"})," ",t("span",{class:"hint",children:"call any function"})]}),t("div",{class:"scope-list"+(e.anyFunction?" greyed":""),children:i===null?t(H,{children:"Loading functions\u2026"}):l.length?l.map(u=>t(Fe,{checked:e.anyFunction||e.functions.indexOf(u)!==-1,disabled:r||e.anyFunction,onChange:p=>c(u,p),children:[t("span",{class:"mono",children:u}),s.indexOf(u)!==-1?t("span",{class:"hint",children:" (not deployed any more)"}):null]},u)):t(H,{children:"No functions yet. Deploy a cloud function and it appears here."})})]})]})}function kd({secret:e}){let[n,i]=m(!1),[r,o]=m("Collection");j(()=>{S("GET","/admin/schema").then(c=>{let u=(c.tables||[]).map(p=>p.name).filter(p=>p.charAt(0)!=="_")[0];u&&o(u)}).catch(()=>{})},[]);let s=typeof location<"u"?location.origin:"",l='curl -H "'+la+": "+e+'" '+s+"/api/"+r;function a(c){Ye(c).then(u=>{u?(i(!0),O("Copied to the clipboard.","ok"),setTimeout(()=>i(!1),2500)):O("The browser refused the clipboard. Select the secret and copy it by hand.","bad")})}return t("div",{class:"secret-card",children:[t(J,{kind:"warn",children:"This is the only time the secret is shown. It is stored hashed and cannot be recovered; if it is lost, revoke this key and make another."}),t(G,{h:10}),t("div",{class:"secret-box mono","data-secret":!0,children:e}),t(V,{style:"margin-top:8px",children:[t(P,{kind:"primary",onClick:()=>a(e),children:n?"Copied \u2713":"Copy secret"}),t(H,{children:["Send it in the ",la," header, or as a Bearer token."]})]}),t(G,{}),t("div",{class:"field",children:[t("span",{class:"field-head",children:t("b",{children:"Try it"})}),t("div",{class:"secret-box mono",children:l}),t(V,{style:"margin-top:6px",children:t(P,{tiny:!0,onClick:()=>a(l),children:"Copy the command"})})]})]})}var wd=/^[a-z0-9][a-z0-9-]{0,63}$/,da=[{id:"create",label:"created"},{id:"update",label:"changed"},{id:"delete",label:"deleted"}],si=[{bytes:64*1024,label:"64 KB"},{bytes:256*1024,label:"256 KB"},{bytes:1024*1024,label:"1 MB"},{bytes:5*1024*1024,label:"5 MB"},{bytes:10*1024*1024,label:"10 MB"}],Ar=300,Cd=30,Sd=1024*1024;function Td(e){let n=si.find(i=>i.bytes===e);return n?n.label:e>=1024*1024?Math.round(e/(1024*1024)*10)/10+" MB":e>=1024?Math.round(e/1024)+" KB":e+" bytes"}function xd(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,64)}function Rd(e){if(e.type==="schedule")return e.scheduleWords||e.schedule&&e.schedule.cron||"";if(e.type==="webhook")return"webhook \xB7 /"+(e.webhook&&e.webhook.slug||"");if(e.type==="db-change"){let n=e.dbChange||{},i=(n.actions||[]).map(r=>(da.find(o=>o.id===r)||{label:r}).label);return(n.collection||"")+": "+i.join(", ")}return""}function Ed(e,n,i){return e+"/hooks/"+encodeURIComponent(n)+"/"+i}function _d(e,n,i){return n==="token"?"curl -X POST -H 'Content-Type: application/json' -H 'X-Webhook-Token: "+i+\`' -d '{"hello":"world"}' \`+e:\`BODY='{"hello":"world"}'
65036
+ \`&&s++,i.push(r),n.push(i),i=[],r=""):r+=l}return(r.length||i.length)&&(i.push(r),n.push(i)),n.filter(s=>!(s.length===1&&s[0]===""))}var zo=[{id:"text",label:"Text"},{id:"number",label:"Number"},{id:"boolean",label:"Yes / no"},{id:"date",label:"Date"},{id:"json",label:"JSON"}];function Ht(e){return!e||typeof e!="object"||Array.isArray(e)?[]:Object.keys(e).map(n=>{let i=e[n];return typeof i=="boolean"?{key:n,type:"boolean",raw:i}:typeof i=="number"?{key:n,type:"number",raw:String(i)}:typeof i=="string"?/^\\d{4}-\\d{2}-\\d{2}T/.test(i)&&!isNaN(new Date(i).getTime())?{key:n,type:"date",raw:Bt(i)}:{key:n,type:"text",raw:i}:{key:n,type:"json",raw:i===void 0?"":JSON.stringify(i)}})}function Ct(e){let n={};return e.forEach((i,r)=>{let o=i.key.trim();if(!o){if(i.raw===""||i.raw===!1)return;throw new Error("Row "+(r+1)+" needs a name.")}if(o in n)throw new Error('"'+o+'" is listed twice.');switch(i.type){case"boolean":n[o]=i.raw===!0;break;case"number":{let s=String(i.raw).trim();if(s==="")throw new Error('"'+o+'" needs a number.');let l=Number(s);if(isNaN(l))throw new Error('"'+o+'" must be a number.');n[o]=l;break}case"date":{let s=String(i.raw).trim();if(!s)throw new Error('"'+o+'" needs a date.');let l=new Date(s);if(isNaN(l.getTime()))throw new Error('"'+o+'" is not a date.');n[o]=l.toISOString();break}case"json":{let s=String(i.raw).trim();if(!s)throw new Error('"'+o+'" needs a value.');try{n[o]=JSON.parse(s)}catch(l){throw new Error('"'+o+'" is not valid JSON: '+l.message)}break}default:n[o]=String(i.raw)}}),n}function St(e){return t(Xe,{id:e.id,rows:e.rows,onChange:e.onChange,blank:()=>({key:"",type:"text",raw:""}),addLabel:"+ Add a value",disabled:e.disabled,renderRow:(n,i)=>t(W,{children:[t("input",{type:"text",class:"kv-key",placeholder:e.keyPlaceholder||"name","aria-label":"Name",value:n.key,disabled:e.disabled,onInput:r=>i({...n,key:r.currentTarget.value})}),t("select",{class:"kv-type","aria-label":"Type",value:n.type,disabled:e.disabled,onChange:r=>{let o=r.currentTarget.value;i({...n,type:o,raw:o==="boolean"?n.raw===!0:n.type==="boolean"?"":n.raw})},children:zo.map(r=>t("option",{value:r.id,children:r.label},r.id))}),t(Fl,{row:n,update:i,disabled:e.disabled})]})})}function Fl({row:e,update:n,disabled:i}){let[r,o]=m(!1);switch(e.type){case"boolean":return t("label",{class:"check kv-value",children:[t("input",{type:"checkbox",checked:e.raw===!0,disabled:i,onChange:s=>n({...e,raw:s.currentTarget.checked})}),e.raw===!0?"Yes":"No"]});case"number":return t("input",{type:"number",step:"any",class:"kv-value","aria-label":"Value",value:String(e.raw),disabled:i,onInput:s=>n({...e,raw:s.currentTarget.value})});case"date":return t("input",{type:"datetime-local",class:"kv-value","aria-label":"Value",value:String(e.raw),disabled:i,onInput:s=>n({...e,raw:s.currentTarget.value})});case"json":return t("input",{type:"text",class:"kv-value mono","aria-label":"Value",placeholder:'["a", "b"] or { "k": 1 }',value:String(e.raw),disabled:i,onFocus:()=>o(!0),onBlur:()=>o(!1),onInput:s=>n({...e,raw:s.currentTarget.value})});default:return t("input",{type:"text",class:"kv-value","aria-label":"Value",value:String(e.raw),disabled:i,onInput:s=>n({...e,raw:s.currentTarget.value})})}}var Gi='a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';function Se(e){let n=ae(null),i=ae(null);return j(()=>{i.current=document.activeElement;let r=n.current;if(r){let s=r.querySelector(".drawer-body");(s&&s.querySelector(Gi)||r.querySelector(Gi)||r).focus()}let o=s=>{if(s.key==="Escape"){s.preventDefault(),e.onClose();return}if(s.key!=="Tab"||!r)return;let l=Array.from(r.querySelectorAll(Gi)).filter(u=>!u.hidden&&u.getAttribute("aria-hidden")!=="true");if(!l.length){s.preventDefault(),r.focus();return}let a=l[0],c=l[l.length-1];s.shiftKey&&document.activeElement===a?(s.preventDefault(),c.focus()):!s.shiftKey&&document.activeElement===c?(s.preventDefault(),a.focus()):r.contains(document.activeElement)||(s.preventDefault(),a.focus())};return window.addEventListener("keydown",o),()=>{window.removeEventListener("keydown",o);let s=i.current;s&&typeof s.focus=="function"&&s.focus()}},[]),t("div",{class:"drawer-scrim",onClick:r=>{r.target===r.currentTarget&&e.onClose()},children:t("div",{class:"drawer"+(e.wide?" wide":""),role:"dialog","aria-modal":"true","aria-label":e.title,tabIndex:-1,ref:n,children:[t("div",{class:"drawer-head",children:[t("div",{children:[t("h3",{children:e.title}),e.subtitle?t("div",{class:"sub",style:"margin:0",children:e.subtitle}):null]}),t("button",{type:"button",class:"btn tiny","aria-label":"Close",onClick:e.onClose,children:"\u2715"})]}),t("div",{class:"drawer-body",children:e.children}),e.footer?t("div",{class:"drawer-foot",children:e.footer}):null]})})}function $e({children:e,title:n="Danger zone"}){return t("div",{class:"danger-zone",children:[t("div",{class:"danger-zone-title",children:n}),e]})}function Ze(e){return t("div",{class:"danger-action",children:[t("div",{class:"danger-why",children:e.why}),t(B,{kind:"danger",onClick:()=>qe(e.title,e.warning,e.expected,e.onConfirm,e.verb||e.label),children:e.label})]})}function oe({children:e,icon:n,action:i,docs:r}){let o=i&&i.write!==!1?B:A;return t("div",{class:"empty-state",children:[n?t("div",{class:"empty-icon","aria-hidden":"true",children:n}):null,t("div",{class:"empty-text",children:e}),i?t(o,{kind:"primary",onClick:i.onClick,children:i.label}):null,r?t("a",{class:"empty-docs",href:r.href,target:"_blank",rel:"noreferrer",children:r.label}):null]})}var $i={text:[{id:"is",label:"is",arity:"one"},{id:"isNot",label:"is not",arity:"one"},{id:"contains",label:"contains",arity:"one"},{id:"startsWith",label:"starts with",arity:"one"},{id:"empty",label:"is empty",arity:"none"},{id:"notEmpty",label:"is not empty",arity:"none"},{id:"oneOf",label:"is one of",arity:"many"}],number:[{id:"eq",label:"=",arity:"one"},{id:"ne",label:"\u2260",arity:"one"},{id:"lt",label:"<",arity:"one"},{id:"lte",label:"\u2264",arity:"one"},{id:"gt",label:">",arity:"one"},{id:"gte",label:"\u2265",arity:"one"},{id:"between",label:"is between",arity:"two"},{id:"empty",label:"is empty",arity:"none"}],boolean:[{id:"yes",label:"is yes",arity:"none"},{id:"no",label:"is no",arity:"none"}],date:[{id:"on",label:"is",arity:"one"},{id:"before",label:"is before",arity:"one"},{id:"after",label:"is after",arity:"one"},{id:"between",label:"is between",arity:"two"},{id:"within",label:"is within",arity:"window"},{id:"empty",label:"is empty",arity:"none"}],link:[{id:"is",label:"is",arity:"one"},{id:"empty",label:"is empty",arity:"none"}],list:[{id:"contains",label:"contains",arity:"one"},{id:"empty",label:"is empty",arity:"none"}],location:[{id:"near",label:"is within",arity:"near"}],choice:[{id:"is",label:"is",arity:"one"}]};function zi(e){let n=$i[e.kind];if(!e.ops)return n;let i=e.ops.map(r=>n.find(o=>o.id===r)).filter(r=>!!r);return i.length?i:n}var Wn=[{id:"today",label:"today"},{id:"week",label:"this week"},{id:"month",label:"this month"},{id:"past7",label:"the past 7 days"},{id:"past30",label:"the past 30 days"}];function Jo(e){switch(e){case"String":return"text";case"Number":return"number";case"Boolean":return"boolean";case"Date":return"date";case"Pointer":return"link";case"Array":return"list";case"GeoPoint":return"location";default:return null}}function Un(e,n){return $i[e].find(i=>i.id===n)}function jt(e){let n=zi(e)[0],i={kind:"cond",field:e.name,op:n.id};return Xo(i,n)}function Yo(e,n,i){let r=Un(n,i)||$i[n][0];return Xo({kind:"cond",field:e.field,op:r.id,value:r.arity==="one"||r.arity==="two"?e.value:void 0},r)}function Xo(e,n){return n.arity==="one"&&e.value===void 0&&(e.value=""),n.arity==="two"&&(e.value===void 0&&(e.value=""),e.value2=""),n.arity==="many"&&(e.values=[]),n.arity==="window"&&(e.value="today"),n.arity==="near"&&(e.lat="",e.lng="",e.value="10"),e}function De(e){let n=/^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(e);if(!n)throw new Error('"'+e+'" is not a date.');return new Date(Number(n[1]),Number(n[2])-1,Number(n[3]))}function Re(e,n){return new Date(e.getFullYear(),e.getMonth(),e.getDate()+n)}function Tt(e){let n=i=>(i<10?"0":"")+i;return e.getFullYear()+"-"+n(e.getMonth()+1)+"-"+n(e.getDate())}function Hn(e){return e.getHours()===0&&e.getMinutes()===0&&e.getSeconds()===0&&e.getMilliseconds()===0}function Wt(e,n){let i=new Date(n.getFullYear(),n.getMonth(),n.getDate());switch(e){case"today":return[i,Re(i,1)];case"week":{let r=Re(i,-((i.getDay()+6)%7));return[r,Re(r,7)]}case"month":return[new Date(i.getFullYear(),i.getMonth(),1),new Date(i.getFullYear(),i.getMonth()+1,1)];case"past7":return[Re(i,-6),Re(i,1)];case"past30":return[Re(i,-29),Re(i,1)];default:throw new Error('"'+e+'" is not a date window.')}}var ft=e=>({__type:"Date",iso:e.toISOString()});function Ml(e){return e.replace(/[.*+?^\${}()|[\\]\\\\]/g,"\\\\$&")}function xt(e,n){let i=String(n===void 0?"":n).trim(),r=Number(i);if(i===""||isNaN(r))throw new Error(e+" needs a number.");return r}function Rt(e,n){if(n===void 0||n==="")throw new Error(e+" needs a value.");return n}function un(e,n){return{$or:[{[e]:n},{[e]:{$exists:!1}}]}}function Ji(e,n,i=new Date){let r=n.find(s=>s.name===e.field);if(!r)throw new Error('There is no field called "'+e.field+'".');let o=e.field;switch(r.kind){case"text":switch(e.op){case"is":return{[o]:{$eq:e.value||""}};case"isNot":return un(o,{$ne:e.value||""});case"contains":return{[o]:{contains:Rt(o,e.value)}};case"startsWith":return{[o]:{$regex:"^"+Ml(Rt(o,e.value))}};case"empty":return un(o,{$eq:""});case"notEmpty":return{$and:[{[o]:{$exists:!0}},{[o]:{$ne:""}}]};case"oneOf":if(!e.values||!e.values.length)throw new Error(o+" needs at least one value.");return{[o]:{$in:e.values.slice()}}}break;case"number":{let s={eq:"$eq",lt:"$lt",lte:"$lte",gt:"$gt",gte:"$gte"};if(e.op==="ne")return un(o,{$ne:xt(o,e.value)});if(s[e.op])return{[o]:{[s[e.op]]:xt(o,e.value)}};if(e.op==="between")return{[o]:{$gte:xt(o,e.value),$lte:xt(o,e.value2)}};if(e.op==="empty")return{[o]:{$exists:!1}};break}case"boolean":if(e.op==="yes")return{[o]:{$eq:!0}};if(e.op==="no")return un(o,{$eq:!1});break;case"date":{if(e.op==="empty")return{[o]:{$exists:!1}};if(e.op==="within"){let[l,a]=Wt(e.value||"today",i);return{[o]:{$gte:ft(l),$lt:ft(a)}}}let s=De(Rt(o,e.value));if(e.op==="on")return{[o]:{$gte:ft(s),$lt:ft(Re(s,1))}};if(e.op==="before")return{[o]:{$lt:ft(s)}};if(e.op==="after")return{[o]:{$gte:ft(Re(s,1))}};if(e.op==="between"){let l=De(Rt(o,e.value2)),[a,c]=s<=l?[s,l]:[l,s];return{[o]:{$gte:ft(a),$lt:ft(Re(c,1))}}}break}case"link":if(e.op==="empty")return{[o]:{$exists:!1}};if(e.op==="is")return{[o]:{$eq:{__type:"Pointer",className:r.targetClass,objectId:Rt(o,e.value)}}};break;case"list":if(e.op==="contains")return{[o]:{contains:JSON.stringify(Rt(o,e.value))}};if(e.op==="empty")return un(o,{$eq:[]});break;case"location":if(e.op==="near")return{[o]:{$nearSphere:{__type:"GeoPoint",latitude:xt(o+" latitude",e.lat),longitude:xt(o+" longitude",e.lng)},$maxDistanceInKilometers:xt(o+" distance",e.value)}};break;case"choice":if(e.op==="is")return{[o]:{$eq:Rt(o,e.value)}};break}throw new Error('"'+e.op+'" is not something '+o+" can be asked.")}function Yi(e,n,i=new Date){let r=e.items.map(o=>o.kind==="group"?Yi(o,n,i):Ji(o,n,i)).filter(o=>o!==null);return r.length?r.length===1?r[0]:{[e.conj==="or"?"$or":"$and"]:r}:null}var Ne=e=>!!e&&typeof e=="object"&&!Array.isArray(e);function jn(e){let n=Ne(e)&&e.__type==="Date"?e.iso:typeof e=="string"?e:null;if(typeof n!="string")return null;let i=new Date(n);return isNaN(i.getTime())?null:i}function Bl(e){if(!Ne(e))return null;let n=Object.keys(e);if(n.length!==1||n[0].charAt(0)==="$")return null;let i=e[n[0]];return Ne(i)&&Object.keys(i).length===1&&i.$exists===!1?n[0]:null}function Hl(e,n,i){if(e.length!==2||!Ne(e[0]))return null;let r=Bl(e[1]),o=Object.keys(e[0]);if(!r||o.length!==1||o[0]!==r)return null;let s=n.find(c=>c.name===r);if(!s)return null;let l=e[0][r];if(s.kind==="list")return Ne(l)&&Object.keys(l).length===1&&Array.isArray(l.$eq)&&l.$eq.length===0?{kind:"cond",field:r,op:"empty"}:null;let a=Zo(s,l,i);return a?s.kind==="text"&&a.op==="is"&&a.value===""?{kind:"cond",field:r,op:"empty"}:s.kind==="text"&&Ne(l)&&"$ne"in l||s.kind==="number"&&Ne(l)&&"$ne"in l||s.kind==="boolean"&&a.op==="no"?a:null:null}function jl(e,n){if(e.length!==2||!Ne(e[0])||!Ne(e[1]))return null;let i=Object.keys(e[0]),r=Object.keys(e[1]);if(i.length!==1||r.length!==1||i[0]!==r[0])return null;let o=n.find(a=>a.name===i[0]);if(!o||o.kind!=="text")return null;let s=e[0][i[0]],l=e[1][r[0]];return Ne(s)&&Object.keys(s).length===1&&s.$exists===!0&&Ne(l)&&Object.keys(l).length===1&&l.$ne===""?{kind:"cond",field:i[0],op:"notEmpty"}:null}function Wl(e){let n="";for(let i=0;i<e.length;i++){let r=e.charAt(i);if(r==="\\\\"){let o=e.charAt(i+1);if(!/[.*+?^\${}()|[\\]\\\\]/.test(o))return null;n+=o,i++}else{if(/[.*+?^\${}()|[\\]]/.test(r))return null;n+=r}}return n}function Ul(e,n,i){let r=Object.keys(n).sort().join(",");if(r==="$exists"&&n.$exists===!1)return{kind:"cond",field:e,op:"empty"};if(r==="$lt"){let o=jn(n.$lt);return o&&Hn(o)?{kind:"cond",field:e,op:"before",value:Tt(o)}:null}if(r==="$gte"){let o=jn(n.$gte);return o&&Hn(o)?{kind:"cond",field:e,op:"after",value:Tt(Re(o,-1))}:null}if(r==="$gte,$lt"){let o=jn(n.$gte),s=jn(n.$lt);if(!o||!s||!Hn(o)||!Hn(s)||s<=o)return null;for(let l of Wn){let[a,c]=Wt(l.id,i);if(a.getTime()===o.getTime()&&c.getTime()===s.getTime())return{kind:"cond",field:e,op:"within",value:l.id}}return Tt(Re(o,1))===Tt(s)?{kind:"cond",field:e,op:"on",value:Tt(o)}:{kind:"cond",field:e,op:"between",value:Tt(o),value2:Tt(Re(s,-1))}}return null}function Zo(e,n,i){let r=e.name,o=Ne(n)&&Object.keys(n).some(a=>a.charAt(0)==="$"||a==="contains")?n:{$eq:n},s=Object.keys(o).sort().join(","),l=(a,c={})=>({kind:"cond",field:r,op:a,...c});switch(e.kind){case"text":if(s==="$eq"&&typeof o.$eq=="string")return l("is",{value:o.$eq});if(s==="$ne"&&typeof o.$ne=="string")return l("isNot",{value:o.$ne});if(s==="contains"&&typeof o.contains=="string")return l("contains",{value:o.contains});if(s==="$regex"&&typeof o.$regex=="string"&&o.$regex.charAt(0)==="^"){let a=Wl(o.$regex.slice(1));return a===null?null:l("startsWith",{value:a})}return s==="$in"&&Array.isArray(o.$in)&&o.$in.every(a=>typeof a=="string")?l("oneOf",{values:o.$in.slice()}):s==="$exists"&&o.$exists===!1?l("empty"):null;case"number":{let a={$eq:"eq",$ne:"ne",$lt:"lt",$lte:"lte",$gt:"gt",$gte:"gte"};return a[s]&&typeof o[s]=="number"?l(a[s],{value:String(o[s])}):s==="$gte,$lte"&&typeof o.$gte=="number"&&typeof o.$lte=="number"?l("between",{value:String(o.$gte),value2:String(o.$lte)}):s==="$exists"&&o.$exists===!1?l("empty"):null}case"boolean":return s==="$eq"&&typeof o.$eq=="boolean"?l(o.$eq?"yes":"no"):null;case"date":return Ul(r,o,i);case"link":{if(s==="$exists"&&o.$exists===!1)return l("empty");if(s!=="$eq")return null;let a=o.$eq;return Ne(a)&&a.__type==="Pointer"&&typeof a.objectId=="string"?l("is",{value:a.objectId}):typeof a=="string"?l("is",{value:a}):null}case"list":if(s==="contains"&&typeof o.contains=="string")try{let a=JSON.parse(o.contains);return typeof a=="string"?l("contains",{value:a}):null}catch{return null}return null;case"choice":return s==="$eq"&&typeof o.$eq=="string"?l("is",{value:o.$eq}):null;case"location":{if(s!=="$maxDistanceInKilometers,$nearSphere")return null;let a=o.$nearSphere;return!Ne(a)||typeof a.latitude!="number"||typeof a.longitude!="number"||typeof o.$maxDistanceInKilometers!="number"?null:l("near",{lat:String(a.latitude),lng:String(a.longitude),value:String(o.$maxDistanceInKilometers)})}}return null}function Kl(e,n,i){let r=[];for(let o of e){let s=Kn(o,n,i);if(!s)return null;s.items.length===1&&s.items[0].kind==="cond"?r.push(s.items[0]):r.push(s)}return r}function Kn(e,n,i=new Date){if(e==null)return{kind:"group",conj:"and",items:[]};if(!Ne(e))return null;let r=Object.keys(e);if(!r.length)return{kind:"group",conj:"and",items:[]};if(r.length===1&&(r[0]==="$and"||r[0]==="$or")){let s=e[r[0]];if(!Array.isArray(s))return null;let l=r[0]==="$or"?Hl(s,n,i):jl(s,n);if(l)return{kind:"group",conj:"and",items:[l]};let a=Kl(s,n,i);return a?{kind:"group",conj:r[0]==="$or"?"or":"and",items:a}:null}let o=[];for(let s of r){if(s==="$and"||s==="$or"){let c=Kn({[s]:e[s]},n,i);if(!c)return null;c.items.length===1&&c.items[0].kind==="cond"?o.push(c.items[0]):o.push(c);continue}let l=n.find(c=>c.name===s);if(!l)return null;let a=Zo(l,e[s],i);if(!a)return null;o.push(a)}return{kind:"group",conj:"and",items:o}}function qi(e){if(!e)return"\u2026";try{return De(e).toLocaleDateString(void 0,{day:"numeric",month:"short",year:"numeric"})}catch{return e}}function Vl(e,n,i){let r=n.find(a=>a.name===e.field);if(!r)return e.field;let o=Un(r.kind,e.op),s=o?o.label:e.op,l=e.value===void 0||e.value===""?"\u2026":e.value;switch(o?o.arity:"none"){case"none":return e.field+" "+s;case"many":return e.field+" "+s+" "+((e.values||[]).join(", ")||"\u2026");case"window":return e.field+" "+s+" "+(Wn.find(a=>a.id===e.value)||{label:l}).label;case"near":return e.field+" is within "+l+" km of "+(e.lat||"\u2026")+", "+(e.lng||"\u2026");case"two":return e.field+" "+s+" "+(r.kind==="date"?qi(e.value):l)+" and "+(r.kind==="date"?qi(e.value2):e.value2||"\u2026");default:return r.kind==="date"?e.field+" "+s+" "+qi(e.value):r.kind==="link"&&i&&e.value?e.field+" "+s+" "+i(e.field,e.value):r.kind==="choice"&&e.value?e.field+" "+s+" "+((r.options||[]).find(a=>a.value===e.value)||{label:l}).label:e.field+" "+s+" "+l}}function Et(e,n,i){return e.items.map(o=>o.kind==="group"?"("+Et(o,n,i)+")":Vl(o,n,i)).join(e.conj==="or"?" or ":" and ")}async function Gl(e){let n=await S("GET","/admin/roles"),i=e.trim().toLowerCase();return(n.roles||[]).map(r=>r.name).filter(r=>!i||r.toLowerCase().includes(i))}async function ql(e){return(await S("GET","/admin/users?limit=20&q="+I(e.trim()))).users||[]}function Ut({state:e,onChange:n,disabled:i,collection:r}){let[o,s]=m({}),l=e.mode==="rows"?e.rows.filter(p=>p.key!=="*"&&!pt(p.key)).map(p=>p.key):[];j(()=>{l.filter(p=>!o[p]).forEach(p=>S("GET","/admin/users/"+I(p)).then(d=>s(f=>({...f,[p]:Bn(d)}))).catch(()=>s(d=>({...d,[p]:"a person who no longer exists"}))))},[l.join(",")]);let a=r?t("a",{class:"acl-more",href:"#/permissions/"+I(r),children:"This collection\u2019s permissions also apply \u2192"}):null;if(e.mode==="public")return t("div",{class:"acl-card",children:[t("div",{class:"acl-state",children:[t("b",{children:"Everyone"}),t("span",{class:"hint",children:"This record has no restrictions of its own: whoever the collection\u2019s permissions let in can see and change it."})]}),i?null:t("button",{type:"button",class:"btn tiny",onClick:()=>n(Mo()),children:"Restrict who can see this"}),a]});let c=p=>p.key==="*"?"Everyone":pt(p.key)?Fi(p.key):o[p.key]||"\u2026",u=p=>p.key==="*"?"":pt(p.key)?"role":"person";return t("div",{class:"acl-card",children:[Bi(e)?t("div",{class:"acl-state",children:[t("b",{children:"No one"}),t("span",{class:"hint",children:"Nothing is granted, so only the admin credential can see or change this record."})]}):null,t("table",{class:"acl-table",children:[t("thead",{children:t("tr",{children:[t("th",{children:"Who"}),t("th",{children:"Can see"}),t("th",{children:"Can change"}),t("th",{})]})}),t("tbody",{children:e.rows.map(p=>t("tr",{"data-acl-key":p.key==="*"?"everyone":u(p),children:[t("td",{children:[c(p)," ",u(p)?t("span",{class:"chip type",children:u(p)}):null]}),t("td",{children:t("input",{type:"checkbox","aria-label":c(p)+" can see",checked:p.read,disabled:i,onChange:d=>n(ji(e,p.key,{read:d.currentTarget.checked}))})}),t("td",{children:t("input",{type:"checkbox","aria-label":c(p)+" can change",checked:p.write,disabled:i,onChange:d=>n(ji(e,p.key,{write:d.currentTarget.checked}))})}),t("td",{children:p.key==="*"||i?null:t("button",{type:"button",class:"chip-x","aria-label":"Remove "+c(p),onClick:()=>n(Bo(e,p.key)),children:"\u2715"})})]},p.key))})]}),t("span",{class:"hint",children:"\u201CSigned-in users\u201D is not something a record can name: a record grants everyone, a role, or a person."}),i?null:t("div",{class:"acl-add",children:[t(Ce,{fetch:p=>Gl(p).then(d=>d.filter(f=>!e.rows.some(b=>b.key==="role:"+f))),label:p=>p,keyOf:p=>p,value:null,placeholder:"Add a role\u2026",emptyText:"No other role matches.",onPick:p=>p&&n(Hi(e,"role:"+p))}),t(Ce,{fetch:p=>ql(p).then(d=>d.filter(f=>!e.rows.some(b=>b.key===f.objectId))),label:p=>Bn(p),keyOf:p=>p.objectId,value:null,placeholder:"Add a person\u2026",emptyText:"No other person matches.",onPick:p=>{p&&(s(d=>({...d,[p.objectId]:Bn(p)})),n(Hi(e,p.objectId)))}}),t("button",{type:"button",class:"btn tiny",onClick:()=>n({mode:"public"}),children:"Remove the restrictions"})]}),a]})}var Vn=e=>!!e&&typeof e=="object"&&!Array.isArray(e);function $l(e){if(!e.length)return"text";let n=typeof e[0],i=n==="number"?"number":n==="boolean"?"boolean":n==="string"?"text":null;return i&&e.every(r=>typeof r==typeof e[0])?i:null}function We(e,n){let i=it(n);switch(e.type){case"Boolean":return i===!0;case"Number":return i==null?"":String(i);case"Date":return i?Bt(i):"";case"Pointer":return i==null?"":String(i);case"Array":{let r=Array.isArray(i)?i:[],o=$l(r);return o?{t:"list",itemType:o,items:r.map(s=>o==="boolean"?s===!0:String(s)),json:null}:{t:"list",itemType:"text",items:[],json:JSON.stringify(r,null,2)}}case"Object":return{t:"kv",rows:Vn(i)?Ht(i):[],json:null};case"GeoPoint":return Vn(i)&&typeof i.latitude=="number"?{t:"geo",lat:String(i.latitude),lng:String(i.longitude)}:{t:"geo",lat:"",lng:""};case"File":return{t:"file",file:Vn(i)&&typeof i.url=="string"?{__type:"File",name:String(i.name||""),url:i.url}:null};case"ACL":return{t:"acl",acl:Mi(n)};default:return i==null?"":typeof i=="object"?JSON.stringify(i):String(i)}}function Qo(e,n){let i=n.trim();if(i)try{return JSON.parse(i)}catch(r){throw new Error(e.name+" is not valid JSON: "+r.message)}}function ze(e,n){switch(e.type){case"Boolean":return n===!0;case"Number":{let i=String(n).trim();if(i==="")return;let r=Number(i);if(isNaN(r))throw new Error(e.name+" must be a number.");return r}case"Date":{let i=String(n);if(!i)return;let r=new Date(i);if(isNaN(r.getTime()))throw new Error(e.name+" is not a date.");return r.toISOString()}case"Pointer":return String(n)||void 0;case"Array":{let i=n;if(i.json!==null){let r=Qo(e,i.json);if(r!==void 0&&!Array.isArray(r))throw new Error(e.name+" must be a list, written [ \u2026 ].");return r}return i.items.length?i.items.map((r,o)=>{if(i.itemType==="boolean")return r===!0;let s=String(r);if(i.itemType==="number"){let l=Number(s.trim());if(s.trim()===""||isNaN(l))throw new Error(e.name+": item "+(o+1)+" must be a number.");return l}return s}):void 0}case"Object":{let i=n;if(i.json!==null){let r=Qo(e,i.json);if(r!==void 0&&!Vn(r))throw new Error(e.name+" must be an object, written { \u2026 }.");return r}if(!i.rows.length)return;try{return Ct(i.rows)}catch(r){throw new Error(e.name+": "+r.message)}}case"GeoPoint":{let i=n,r=i.lat.trim(),o=i.lng.trim();if(!r&&!o)return;let s=Number(r),l=Number(o);if(!r||!o||isNaN(s)||isNaN(l))throw new Error(e.name+" needs both a latitude and a longitude.");if(s<-90||s>90)throw new Error(e.name+": latitude is between -90 and 90.");if(l<-180||l>180)throw new Error(e.name+": longitude is between -180 and 180.");return{__type:"GeoPoint",latitude:s,longitude:l}}case"File":{let i=n;return i.file?{__type:"File",name:i.file.name,url:i.file.url}:void 0}case"ACL":return Fo(n.acl);default:return String(n)}}function ht(e){let n=[];for(let r of Object.keys(e)){if(Ue(r)||r==="ACL")continue;let o=e[r];typeof o=="string"&&o&&n.push(o)}let i=String(e.objectId||"");return{objectId:i,label:n[0]||nt(i),detail:n[1]||""}}async function Kt(e,n){let r=((await S("GET","/api/"+I(e)+"?limit=200&sort="+I('["-createdAt"]'))).results||[]).map(ht),o=n.trim().toLowerCase();return(o?r.filter(s=>(s.label+" "+s.detail).toLowerCase().includes(o)):r).slice(0,20)}function Vt({col:e,raw:n,onChange:i,autoFocus:r,onCommit:o,onCancel:s,disabled:l,id:a}){let c=d=>{!o&&!s||(d.key==="Enter"&&o&&!(d.target instanceof HTMLTextAreaElement)?(d.preventDefault(),o()):d.key==="Escape"&&s&&(d.preventDefault(),s()))},p={id:a,disabled:l,autoFocus:r,onKeyDown:c,onBlur:()=>{o&&o()},"aria-label":e.name};switch(e.type){case"Boolean":return t("label",{class:"switch",children:[t("input",{type:"checkbox",role:"switch",...p,onBlur:void 0,checked:n===!0,onChange:d=>i(d.currentTarget.checked)}),t("span",{class:"switch-track","aria-hidden":"true"}),n===!0?"Yes":"No"]});case"Number":return t("input",{type:"number",step:e.whole?1:"any",min:e.min,max:e.max,...p,value:String(n),title:"\u2325\u2191 / \u2325\u2193 nudges by 10",onKeyDown:d=>{if(d.altKey&&(d.key==="ArrowUp"||d.key==="ArrowDown")){d.preventDefault();let f=Number(String(n).trim()||"0");isNaN(f)||i(String(f+(d.key==="ArrowUp"?10:-10)));return}c(d)},onInput:d=>i(d.currentTarget.value)});case"Date":return t(Jl,{col:e,raw:String(n),onChange:i,common:p,inline:!!o});case"Pointer":return t(Yl,{col:e,raw:String(n),onChange:i,onCommit:o,disabled:l,autoFocus:r});case"Array":return t(Xl,{col:e,raw:n,onChange:i,disabled:l});case"Object":return t(Zl,{col:e,raw:n,onChange:i,disabled:l});case"GeoPoint":return t(Ql,{col:e,raw:n,onChange:i,disabled:l});case"File":return t(ec,{col:e,raw:n,onChange:i,disabled:l});case"ACL":return t(Ut,{state:n.acl,onChange:d=>i({t:"acl",acl:d}),disabled:l});default:{let d=String(n);if(e.allowed&&e.allowed.length){let f=e.allowed.map(String).indexOf(d)!==-1||d==="";return t("select",{...p,value:d,onChange:b=>i(b.currentTarget.value),children:[t("option",{value:"",children:"\u2014 none \u2014"}),e.allowed.map(b=>t("option",{value:String(b),children:String(b)},String(b))),f?null:t("option",{value:d,children:d+" (not one of the choices)"})]})}return d.indexOf(\`
65037
+ \`)!==-1&&!o?t("textarea",{id:a,disabled:l,autoFocus:r,rows:4,"aria-label":e.name,value:d,onInput:f=>i(f.currentTarget.value)}):t("input",{type:e.looksLike==="email"?"email":e.looksLike==="url"?"url":"text",maxLength:e.maxLength,...p,value:d,onInput:f=>i(f.currentTarget.value)})}}}function zl(){try{return Intl.DateTimeFormat().resolvedOptions().timeZone||""}catch{return""}}function Jl({col:e,raw:n,onChange:i,common:r,inline:o}){let[s,l]=n?n.split("T"):["",""],a=(c,u)=>i(c?c+"T"+(u||"00:00"):"");return o?t("input",{type:"datetime-local",...r,value:n,onInput:c=>i(c.currentTarget.value)}):t("div",{class:"field-line date-control",children:[t("input",{type:"date",id:r.id,disabled:r.disabled,autoFocus:r.autoFocus,"aria-label":e.name+" date",value:s||"",onInput:c=>a(c.currentTarget.value,l)}),t("input",{type:"time",disabled:r.disabled,"aria-label":e.name+" time",value:l||"",onInput:c=>a(s||Bt(new Date().toISOString()).split("T")[0],c.currentTarget.value)}),t("button",{type:"button",class:"btn tiny",disabled:r.disabled,onClick:()=>i(Bt(new Date().toISOString())),children:"Now"}),n?t("button",{type:"button",class:"btn tiny",disabled:r.disabled,"aria-label":"Clear "+e.name,onClick:()=>i(""),children:"\u2715"}):null,t("span",{class:"hint",children:zl()})]})}function Yl({col:e,raw:n,onChange:i,onCommit:r,disabled:o,autoFocus:s}){let[l,a]=m(n?{objectId:n,label:nt(n),detail:""}:null);return j(()=>{if(!n){a(null);return}l&&l.objectId===n&&l.label!==nt(n)||e.targetClass&&S("GET","/api/"+I(e.targetClass)+"/"+I(n)).then(c=>a(ht(c))).catch(()=>a({objectId:n,label:nt(n),detail:""}))},[n,e.targetClass]),t("div",{class:"field-line",children:[t(Ce,{fetch:c=>e.targetClass?Kt(e.targetClass,c):Promise.resolve([]),label:c=>c.label,detail:c=>c.detail,keyOf:c=>c.objectId,value:l,disabled:o,autoFocus:s,placeholder:"Search "+(e.targetClass||"records")+"\u2026",emptyText:"No "+(e.targetClass||"record")+" matches.",onPick:c=>{a(c),i(c?c.objectId:""),r&&setTimeout(r,0)}}),n&&e.targetClass&&!r?t("a",{class:"btn tiny",href:"#/collections/"+I(e.targetClass)+"/"+I(n),title:"Open this "+e.targetClass,children:"open \u2197"}):null]})}function Gn({on:e,onToggle:n,disabled:i}){return t("button",{type:"button",class:"btn tiny json-toggle",disabled:i,onClick:n,children:e?"Back to the form":"Edit as JSON"})}function Xl({col:e,raw:n,onChange:i,disabled:r}){let[o,s]=m(null);return n.json!==null?t("div",{class:"json-edit",children:[t("textarea",{rows:5,spellcheck:!1,disabled:r,"aria-label":e.name+" as JSON",value:n.json,onInput:l=>i({...n,json:l.currentTarget.value})}),t("div",{class:"field-line",children:[t(Gn,{on:!0,disabled:r,onToggle:()=>{try{let l=ze(e,n),a=We(e,l===void 0?[]:l);if(a.json!==null)return s("This list holds values a row cannot edit (objects, lists, or mixed types). It stays JSON.");s(null),i(a)}catch(l){s(l.message)}}}),o?t("span",{class:"chips-problem",children:o}):null]})]}):t("div",{class:"struct-edit",children:[t(Xe,{rows:n.items,onChange:l=>i({...n,items:l}),blank:()=>n.itemType==="boolean"?!1:"",addLabel:"+ Add an item",disabled:r,renderRow:(l,a,c)=>n.itemType==="boolean"?t("label",{class:"check",children:[t("input",{type:"checkbox","aria-label":e.name+" item "+(c+1),checked:l===!0,disabled:r,onChange:u=>a(u.currentTarget.checked)}),l===!0?"Yes":"No"]}):t("input",{type:n.itemType==="number"?"number":"text",step:"any",class:"kv-value","aria-label":e.name+" item "+(c+1),value:String(l),disabled:r,onInput:u=>a(u.currentTarget.value)})}),t("div",{class:"field-line",children:[t("select",{"aria-label":e.name+" item type",value:n.itemType,disabled:r||n.items.length>0,title:n.items.length?"Remove the items to change what the list holds.":void 0,onChange:l=>i({...n,itemType:l.currentTarget.value}),children:[t("option",{value:"text",children:"A list of text"}),t("option",{value:"number",children:"A list of numbers"}),t("option",{value:"boolean",children:"A list of yes / no"})]}),t(Gn,{on:!1,disabled:r,onToggle:()=>i({...n,json:JSON.stringify(es(()=>ze(e,n))||[],null,2)})})]})]})}function es(e){try{return e()}catch{return}}function Zl({col:e,raw:n,onChange:i,disabled:r}){let[o,s]=m(null);return n.json!==null?t("div",{class:"json-edit",children:[t("textarea",{rows:5,spellcheck:!1,disabled:r,"aria-label":e.name+" as JSON",value:n.json,onInput:l=>i({...n,json:l.currentTarget.value})}),t("div",{class:"field-line",children:[t(Gn,{on:!0,disabled:r,onToggle:()=>{try{let l=ze(e,n);s(null),i({t:"kv",rows:Ht(l||{}),json:null})}catch(l){s(l.message)}}}),o?t("span",{class:"chips-problem",children:o}):null]})]}):t("div",{class:"struct-edit",children:[t(St,{rows:n.rows,onChange:l=>i({...n,rows:l}),disabled:r}),t("div",{class:"field-line",children:t(Gn,{on:!1,disabled:r,onToggle:()=>i({...n,json:JSON.stringify(es(()=>ze(e,n))||{},null,2)})})})]})}function Ql({col:e,raw:n,onChange:i,disabled:r}){let[o,s]=m(null),l=()=>{if(!navigator.geolocation)return s("This browser cannot tell where you are.");navigator.geolocation.getCurrentPosition(a=>{s(null),i({t:"geo",lat:a.coords.latitude.toFixed(6),lng:a.coords.longitude.toFixed(6)})},a=>s(a.message||"Your location was not shared."))};return t("div",{class:"field-line geo-control",children:[t("input",{type:"number",step:"any",min:-90,max:90,placeholder:"Latitude","aria-label":e.name+" latitude",value:n.lat,disabled:r,onInput:a=>i({...n,lat:a.currentTarget.value})}),t("input",{type:"number",step:"any",min:-180,max:180,placeholder:"Longitude","aria-label":e.name+" longitude",value:n.lng,disabled:r,onInput:a=>i({...n,lng:a.currentTarget.value})}),t("button",{type:"button",class:"btn tiny",disabled:r,onClick:l,children:"Use my location"}),o?t("span",{class:"chips-problem",children:o}):null]})}function Xi(e){let n=/^[a-z]+:\\/\\/[^/]+(\\/.*)$/i.exec(e);return n?n[1]:e}function ec({col:e,raw:n,onChange:i,disabled:r}){let[o,s]=m(!1),[l,a]=m(null),[c,u]=m(null),[p,d]=m(!1),f=ae(null),b=async w=>{s(!0),a(null);try{let x={...ut(_e.get().credential),"content-type":w.type||"application/octet-stream"},R=await fetch("/files/"+I(w.name),{method:"POST",headers:x,body:w}),C=await R.json().catch(()=>null);if(R.status>=400||!C||!C.url)throw new Error(C&&(C.error||C.message)||"The upload was refused (HTTP "+R.status+").");u(w.size),i({t:"file",file:{__type:"File",name:C.name,url:C.url}})}catch(x){a(x.message)}finally{s(!1)}},g=w=>{w&&w[0]&&b(w[0])},[v,y]=m(null),T=n.file?n.file.name:"";if(j(()=>{y(null),T&&S("GET","/files/"+I(T)+"/sign").then(w=>y(Xi(w.url))).catch(()=>y(n.file?Xi(n.file.url):null))},[T]),n.file){let w=/\\.(png|jpe?g|gif|webp|svg|avif)$/i.test(n.file.name),x=v||Xi(n.file.url);return t("div",{class:"file-card",children:[w&&v?t("img",{class:"file-thumb",src:v,alt:""}):t("span",{class:"file-icon","aria-hidden":"true",children:"\u25A4"}),t("span",{class:"file-text",children:[t("b",{children:rt(n.file.name)}),c!==null?t("span",{class:"hint",children:mt(c)}):null]}),t("a",{class:"btn tiny",href:x,download:rt(n.file.name),target:"_blank",rel:"noopener",children:"Download"}),r?null:t("button",{type:"button",class:"btn tiny","aria-label":"Remove "+e.name,onClick:()=>i({t:"file",file:null}),children:"\u2715"})]})}return t("div",{class:"dropzone"+(p?" over":""),role:"button",tabIndex:0,"aria-label":"Upload "+e.name,onClick:()=>!r&&f.current&&f.current.click(),onKeyDown:w=>{(w.key==="Enter"||w.key===" ")&&f.current&&(w.preventDefault(),f.current.click())},onDragOver:w=>{w.preventDefault(),d(!0)},onDragLeave:()=>d(!1),onDrop:w=>{w.preventDefault(),d(!1),r||g(w.dataTransfer?w.dataTransfer.files:null)},children:[t("input",{ref:f,type:"file",hidden:!0,disabled:r,onChange:w=>g(w.currentTarget.files)}),o?"Uploading\u2026":"Drop a file here, or click to choose one",l?t("span",{class:"chips-problem",children:l}):null]})}function ts({collection:e,id:n,col:i,disabled:r}){let[o,s]=m(null),[l,a]=m(null),c=i.targetClass||"",u=()=>{if(!c)return;let d={$relatedTo:{object:{__type:"Pointer",className:e,objectId:n},key:i.name}};S("GET","/api/"+I(c)+"?limit=200&where="+I(JSON.stringify(d))).then(f=>s((f.results||[]).map(ht))).catch(f=>a(f.message))};j(u,[e,n,i.name]);let p=(d,f)=>S("PUT","/classes/"+I(e)+"/"+I(n),{[i.name]:{__op:d,objects:[{__type:"Pointer",className:c,objectId:f}]}}).then(()=>{a(null),u()}).catch(b=>a(b.message));return c?t("div",{class:"chips relation",children:[t("div",{class:"chips-set",children:[(o||[]).map(d=>t("span",{class:"chip accent",children:[t("a",{href:"#/collections/"+I(c)+"/"+I(d.objectId),children:d.label}),r?null:t("button",{type:"button",class:"chip-x","aria-label":"Unlink "+d.label,onClick:()=>p("RemoveRelation",d.objectId),children:"\u2715"})]},d.objectId)),o&&!o.length?t("span",{class:"hint",children:"Nothing linked yet."}):null,o?null:t("span",{class:"hint",children:"Loading\u2026"})]}),r?null:t(Ce,{fetch:d=>Kt(c,d).then(f=>f.filter(b=>!(o||[]).some(g=>g.objectId===b.objectId))),label:d=>d.label,detail:d=>d.detail,keyOf:d=>d.objectId,value:null,placeholder:"Link a "+c+"\u2026",emptyText:"No other "+c+" matches.",onPick:d=>{d&&p("AddRelation",d.objectId)}}),t("span",{class:"hint",children:"Linking and unlinking are saved as you go."}),l?t("span",{class:"chips-problem",children:l}):null]}):t("span",{class:"hint",children:"This relation has no target collection."})}function gt({group:e,onChange:n,fields:i,depth:r=0,flat:o=!1}){if(!i.length)return null;let s=(c,u)=>n({...e,items:e.items.map((p,d)=>d===c?u:p)}),l=c=>n({...e,items:e.items.filter((u,p)=>p!==c)}),a=c=>n({...e,items:[...e.items,c]});return t("div",{class:"filter-group"+(r?" nested":""),children:[e.items.map((c,u)=>t("div",{class:"filter-line",children:[t("span",{class:"filter-conj",children:u===0?r?"":"Where":u===1&&!o?t("select",{"aria-label":"And or or",value:e.conj,onChange:p=>n({...e,conj:p.currentTarget.value}),children:[t("option",{value:"and",children:"and"}),t("option",{value:"or",children:"or"})]}):e.conj}),c.kind==="cond"?t(tc,{cond:c,fields:i,onChange:p=>s(u,p)}):t(gt,{group:c,fields:i,depth:r+1,onChange:p=>p.items.length?s(u,p):l(u)}),t("button",{type:"button",class:"btn tiny list-x","aria-label":"Remove this condition",title:"Remove",onClick:()=>l(u),children:"\u2715"})]},u)),t("div",{class:"filter-adds",children:[t("button",{type:"button",class:"btn tiny",onClick:()=>a(jt(i[0])),children:"+ Add condition"}),r<1&&!o?t("button",{type:"button",class:"btn tiny",onClick:()=>a({kind:"group",conj:e.conj==="and"?"or":"and",items:[jt(i[0])]}),children:"+ Add group"}):null]})]})}function tc({cond:e,fields:n,onChange:i}){let r=n.find(c=>c.name===e.field)||n[0],o=zi(r),s=Un(r.kind,e.op)||o[0],l=(c,u,p="text",d)=>t("input",{type:p,step:p==="number"?"any":void 0,class:"filter-value","aria-label":u,placeholder:d,value:e[c]||"",onInput:f=>i({...e,[c]:f.currentTarget.value})}),a=r.kind==="number"?"number":r.kind==="date"?"date":"text";return t("span",{class:"filter-cond",children:[t("select",{"aria-label":"Field",value:r.name,onChange:c=>{let u=n.find(p=>p.name===c.currentTarget.value);i(u.kind===r.kind?{...e,field:u.name,value:u.kind==="link"?"":e.value}:jt(u))},children:n.map(c=>t("option",{value:c.name,children:c.name},c.name))}),t("select",{"aria-label":"Operator",value:s.id,onChange:c=>i(Yo(e,r.kind,c.currentTarget.value)),children:o.map(c=>t("option",{value:c.id,children:c.label},c.id))}),s.arity==="one"&&r.kind==="link"?t(nc,{field:r,id:e.value||"",onPick:c=>i({...e,value:c})}):null,s.arity==="one"&&r.kind==="choice"?t("select",{class:"filter-value","aria-label":r.name+" value",value:e.value||"",onChange:c=>i({...e,value:c.currentTarget.value}),children:[t("option",{value:"",children:"pick\u2026"}),(r.options||[]).map(c=>t("option",{value:c.value,children:c.label},c.value))]}):null,s.arity==="one"&&r.kind!=="link"&&r.kind!=="choice"?l("value",r.name+" value",a,r.kind==="list"?"an item":void 0):null,s.arity==="two"?t(W,{children:[l("value",r.name+" from",a),t("span",{class:"hint",children:"and"}),l("value2",r.name+" to",a)]}):null,s.arity==="many"?t(Ie,{items:e.values||[],onChange:c=>i({...e,values:c}),placeholder:"Type a value, Enter",addLabel:"Add"}):null,s.arity==="window"?t("select",{"aria-label":r.name+" window",value:e.value||"today",onChange:c=>i({...e,value:c.currentTarget.value}),children:Wn.map(c=>t("option",{value:c.id,children:c.label},c.id))}):null,s.arity==="near"?t(W,{children:[l("value",r.name+" distance in km","number"),t("span",{class:"hint",children:"km of"}),l("lat",r.name+" latitude","number","latitude"),l("lng",r.name+" longitude","number","longitude")]}):null]})}function nc({field:e,id:n,onPick:i}){let[r,o]=m(null);return j(()=>{if(!n||!e.targetClass)return o(null);r&&r.objectId===n||S("GET","/api/"+I(e.targetClass)+"/"+I(n)).then(s=>o(ht(s))).catch(()=>o({objectId:n,label:"a record that no longer exists",detail:""}))},[n,e.targetClass]),t(Ce,{fetch:s=>e.targetClass?Kt(e.targetClass,s):Promise.resolve([]),label:s=>s.label,detail:s=>s.detail,keyOf:s=>s.objectId,value:r,placeholder:"Pick a "+(e.targetClass||"record")+"\u2026",onPick:s=>{o(s),i(s?s.objectId:"")}})}var fn=[1,5,10,15,30],Zi=[{id:"minutes",label:"Every few minutes"},{id:"hourly",label:"Every hour"},{id:"daily",label:"Every day"},{id:"weekly",label:"Every week"},{id:"monthly",label:"Every month"},{id:"custom",label:"Custom"}],Qi=[{n:1,short:"Mon",long:"Monday"},{n:2,short:"Tue",long:"Tuesday"},{n:3,short:"Wed",long:"Wednesday"},{n:4,short:"Thu",long:"Thursday"},{n:5,short:"Fri",long:"Friday"},{n:6,short:"Sat",long:"Saturday"},{n:0,short:"Sun",long:"Sunday"}],is={mode:"daily",everyMinutes:15,minute:0,time:"09:00",days:[1,2,3,4,5],dayOfMonth:1,custom:""},ic={"@yearly":"0 0 1 1 *","@annually":"0 0 1 1 *","@monthly":"0 0 1 * *","@weekly":"0 0 * * 0","@daily":"0 0 * * *","@midnight":"0 0 * * *","@hourly":"0 * * * *","@minutely":"* * * * *"},mn=/^\\d+$/,rc=/^(\\d{1,2}):(\\d{2})$/;function ns(e){return(e<10?"0":"")+e}function Gt(e){let n=rc.exec(e.trim());if(!n)return null;let i=Number(n[1]),r=Number(n[2]);return i>23||r>59?null:[i,r]}function hn(e){let n=new Set;return e.map(i=>i===7?0:i).filter(i=>i>=0&&i<=6&&!n.has(i)&&!!n.add(i)).sort((i,r)=>(i===0?7:i)-(r===0?7:r))}function qn(e){switch(e.mode){case"minutes":return fn.indexOf(e.everyMinutes)===-1?"Pick how many minutes.":null;case"hourly":return Number.isInteger(e.minute)&&e.minute>=0&&e.minute<=59?null:"Pick a minute past the hour (0\u201359).";case"daily":return Gt(e.time)?null:"Pick a time of day.";case"weekly":return Gt(e.time)?e.days.length?null:"Pick at least one day.":"Pick a time of day.";case"monthly":return Gt(e.time)?Number.isInteger(e.dayOfMonth)&&e.dayOfMonth>=1&&e.dayOfMonth<=31?null:"Pick a day of the month (1\u201331).":"Pick a time of day.";default:return e.custom.trim()?null:"Type a cron expression."}}function er(e){if(qn(e))return"";switch(e.mode){case"minutes":return"*/"+e.everyMinutes+" * * * *";case"hourly":return e.minute+" * * * *";case"daily":{let[n,i]=Gt(e.time);return i+" "+n+" * * *"}case"weekly":{let[n,i]=Gt(e.time),r=hn(e.days).slice().sort((o,s)=>o-s);return i+" "+n+" * * "+r.join(",")}case"monthly":{let[n,i]=Gt(e.time);return i+" "+n+" "+e.dayOfMonth+" * *"}default:return e.custom.trim()}}function oc(e){let n=[];for(let i of e.split(",")){let r=i.trim(),o=/^(\\d+)-(\\d+)$/.exec(r);if(o){let s=Number(o[1]),l=Number(o[2]);if(s>l||l>7)return null;for(let a=s;a<=l;a++)n.push(a)}else if(mn.test(r)&&Number(r)<=7)n.push(Number(r));else return null}return n.length?hn(n):null}function qt(e){let n=(e||"").trim(),i={...is,custom:n};if(!n)return{...i,mode:"custom"};let r={...i,mode:"custom"},o=n.startsWith("@")?ic[n.toLowerCase()]:n;if(!o)return r;let s=o.split(/\\s+/);if(s.length!==5)return r;let[l,a,c,u,p]=s;if(u!=="*")return r;if(a==="*"&&c==="*"&&p==="*"){if(l==="*")return{...i,mode:"minutes",everyMinutes:1};let f=/^\\*\\/(\\d+)$/.exec(l);return f&&fn.indexOf(Number(f[1]))!==-1?{...i,mode:"minutes",everyMinutes:Number(f[1])}:mn.test(l)&&Number(l)<=59?{...i,mode:"hourly",minute:Number(l)}:r}if(!mn.test(l)||!mn.test(a)||Number(l)>59||Number(a)>23)return r;let d=ns(Number(a))+":"+ns(Number(l));if(c==="*"&&p==="*")return{...i,mode:"daily",time:d};if(c==="*"){let f=oc(p);return f?{...i,mode:"weekly",time:d,days:f}:r}return p==="*"&&mn.test(c)&&Number(c)>=1&&Number(c)<=31?{...i,mode:"monthly",time:d,dayOfMonth:Number(c)}:r}var sc=250;function _t(e){let[n,i]=m(()=>e.value?qt(e.value):qt("0 9 * * *")),[r,o]=m(null),[s,l]=m(!1),a=ae(null),c=ae(0),u=er(n),p=qn(n);j(()=>{a.current!==u&&(a.current=u,e.onChange(u))},[u]),j(()=>{e.value!==a.current&&(!e.value&&a.current!==null||i(e.value?qt(e.value):qt("0 9 * * *")))},[e.value]),j(()=>{if(!u){o(null);return}let g=++c.current;l(!0);let v=setTimeout(()=>{e.preview(u).then(y=>{g===c.current&&(o(y),l(!1))}).catch(y=>{g===c.current&&(o({valid:!1,error:y.message,words:null,next:[]}),l(!1))})},sc);return()=>clearTimeout(v)},[u]);let d=g=>i({...n,...g}),f=!!e.disabled,b="sched-mode-"+(e.id||"x");return t("div",{class:"sched",id:e.id,children:[t("div",{class:"sched-modes",role:"radiogroup","aria-label":"How often",children:Zi.map(g=>t("label",{class:"sched-mode"+(n.mode===g.id?" on":""),children:[t("input",{type:"radio",name:b,value:g.id,checked:n.mode===g.id,disabled:f,onChange:()=>d(g.id==="custom"&&u?{mode:g.id,custom:u}:{mode:g.id})}),g.label]},g.id))}),t("div",{class:"sched-controls",children:[n.mode==="minutes"?t("div",{class:"field-line",children:[t("span",{children:"Every"}),t("select",{"aria-label":"Minutes",value:String(n.everyMinutes),disabled:f,onChange:g=>d({everyMinutes:Number(g.currentTarget.value)}),children:fn.map(g=>t("option",{value:String(g),children:g===1?"minute":g+" minutes"},g))})]}):null,n.mode==="hourly"?t("div",{class:"field-line",children:[t("span",{children:"Every hour at"}),t("input",{type:"number",min:0,max:59,step:1,"aria-label":"Minute past the hour",class:"sched-minute",value:String(n.minute),disabled:f,onInput:g=>d({minute:Number(g.currentTarget.value)})}),t("span",{children:"minutes past"})]}):null,n.mode==="daily"?t("div",{class:"field-line",children:[t("span",{children:"Every day at"}),t("input",{type:"time","aria-label":"Time of day",value:n.time,disabled:f,onInput:g=>d({time:g.currentTarget.value})})]}):null,n.mode==="weekly"?t(W,{children:[t("div",{class:"sched-days",role:"group","aria-label":"Days of the week",children:Qi.map(g=>{let v=n.days.indexOf(g.n)!==-1;return t("label",{class:"sched-day"+(v?" on":""),title:g.long,children:[t("input",{type:"checkbox","aria-label":g.long,checked:v,disabled:f,onChange:y=>d({days:hn(y.currentTarget.checked?[...n.days,g.n]:n.days.filter(T=>T!==g.n))})}),g.short]},g.n)})}),t("div",{class:"field-line",children:[t("span",{children:"at"}),t("input",{type:"time","aria-label":"Time of day",value:n.time,disabled:f,onInput:g=>d({time:g.currentTarget.value})})]})]}):null,n.mode==="monthly"?t("div",{class:"field-line",children:[t("span",{children:"On day"}),t("input",{type:"number",min:1,max:31,step:1,"aria-label":"Day of the month",class:"sched-minute",value:String(n.dayOfMonth),disabled:f,onInput:g=>d({dayOfMonth:Number(g.currentTarget.value)})}),t("span",{children:"of every month at"}),t("input",{type:"time","aria-label":"Time of day",value:n.time,disabled:f,onInput:g=>d({time:g.currentTarget.value})}),n.dayOfMonth>28?t(M,{children:["Months without day ",n.dayOfMonth," are skipped."]}):null]}):null,n.mode==="custom"?t("div",{class:"field",children:[t("input",{type:"text",class:"mono","aria-label":"Cron expression",placeholder:"minute hour day-of-month month day-of-week",value:n.custom,disabled:f,onInput:g=>d({custom:g.currentTarget.value})}),t(M,{children:["Five fields: minute (0\u201359), hour (0\u201323), day of month (1\u201331), month (1\u201312 or jan\u2013dec), day of week (0\u20137 or sun\u2013sat, Sunday is 0). ",t("span",{class:"mono",children:"*"})," is any, ",t("span",{class:"mono",children:"*/5"})," every fifth, ",t("span",{class:"mono",children:"1-5"})," a range, ",t("span",{class:"mono",children:"1,3,5"})," a list. Presets: ",t("span",{class:"mono",children:"@hourly @daily @weekly @monthly"}),"."]})]}):null]}),t("div",{class:"sched-readout","aria-live":"polite",children:p?t(J,{kind:"warn",children:p}):r&&!r.valid?t(J,{kind:"bad",children:r.error||"That is not a schedule this backend can read."}):r?t(W,{children:[t("div",{class:"sched-words",children:r.words||t("span",{class:"hint",children:"No plain reading of this expression \u2014 the times below are what it does."})}),t("div",{class:"sched-next",children:[t("b",{children:"Next runs"}),t("ol",{children:r.next.map(g=>t("li",{children:new Date(g).toLocaleString()},g))})]}),t(M,{children:["Times are the backend\u2019s local time",r.timezone?", "+r.timezone:"",".",s?t(K,{children:" updating\u2026"}):null]})]}):t(M,{children:s?"Working out the next runs\u2026":""})})]})}var yt=[{id:"text",label:"Text",line:"words, names, descriptions",icon:"Aa",storage:"String"},{id:"number",label:"Number",line:"amounts, counts, prices",icon:"#",storage:"Number"},{id:"yesno",label:"Yes / No",line:"a switch",icon:"\u25D0",storage:"Boolean"},{id:"date",label:"Date & time",line:"when something happened",icon:"\u25F7",storage:"Date"},{id:"choice",label:"Choice",line:"one of a list you define",icon:"\u2630",storage:"String"},{id:"link",label:"Link",line:"points at one record in another collection",icon:"\u2192",storage:"Pointer"},{id:"links",label:"Links",line:"points at many records in another collection",icon:"\u21C9",storage:"Relation"},{id:"file",label:"Picture or file",line:"an upload",icon:"\u25A3",storage:"File"},{id:"location",label:"Location",line:"a point on the map",icon:"\u2316",storage:"GeoPoint"},{id:"list",label:"List",line:"several values",icon:"\u2261",storage:"Array"},{id:"anything",label:"Anything",line:"structured data for developers",icon:"{}",storage:"Object"}];function At(e){return yt.find(n=>n.id===e)||yt[0]}function rs(e){let n=e.trim().toLowerCase();return n?yt.filter(i=>(i.label+" "+i.line+" "+i.storage).toLowerCase().indexOf(n)!==-1):yt}function os(e,n){return(n||[]).filter(i=>i.declared&&i.rule.field===e).map(i=>i.rule)}function tr(e,n){return e.type==="String"&&os(e.name,n).some(i=>i.oneOf)?At("choice"):yt.find(i=>i.storage===e.type&&i.id!=="choice")||{id:"anything",label:e.type,line:"",icon:"?",storage:e.type}}function ss(e,n){return!n||!n.length?e:e.map(i=>{let r=os(i.name,n);if(!r.length)return i;let o={...i};return r.forEach(s=>{s.oneOf&&(o.allowed=s.oneOf),s.min!==void 0&&(o.min=s.min),s.max!==void 0&&(o.max=s.max),s.maxLength!==void 0&&(o.maxLength=s.maxLength),s.looksLike&&(o.looksLike=s.looksLike),s.whole&&(o.whole=!0)}),o})}function as(e="text"){return{kind:e,name:"",description:"",required:!1,unique:!1,dflt:"",boolDefaultSet:!1,boolDefault:!1,maxLength:"",looksLike:"",min:"",max:"",whole:!1,values:[],target:""}}function $t(e){return e==="text"||e==="number"||e==="yesno"||e==="choice"}function ls(e){return e==="text"||e==="number"||e==="date"||e==="choice"||e==="link"}var bt=e=>{let n=e.trim();if(!n)return null;let i=Number(n);return isNaN(i)?NaN:i};function cs(e,n){let i=e.name.trim(),r=Mt(i,n.taken,"The field");if(r)return r;let o=At(e.kind),s=e.required&&n.records>0?"The fill value":"The default";if((e.kind==="link"||e.kind==="links")&&!e.target)return"Choose the collection it points at.";if((e.kind==="link"||e.kind==="links")&&n.collections.indexOf(e.target)===-1)return"There is no collection called "+e.target+".";if(e.kind==="choice"){if(!e.values.length)return"A choice needs at least one value.";if(e.dflt&&e.values.indexOf(e.dflt)===-1)return s+" must be one of the choices."}if(e.kind==="number"){let l=bt(e.min),a=bt(e.max);if(l!==null&&isNaN(l))return"The minimum must be a number.";if(a!==null&&isNaN(a))return"The maximum must be a number.";if(l!==null&&a!==null&&l>a)return"The minimum is above the maximum.";let c=bt(e.dflt);if(c!==null&&isNaN(c))return s+" must be a number.";if(c!==null&&e.whole&&!Number.isInteger(c))return s+" must be a whole number.";if(c!==null&&l!==null&&c<l)return s+" is below the minimum.";if(c!==null&&a!==null&&c>a)return s+" is above the maximum.";if(e.whole&&l!==null&&!Number.isInteger(l))return"With whole numbers only, the minimum is a whole number too.";if(e.whole&&a!==null&&!Number.isInteger(a))return"With whole numbers only, the maximum is a whole number too."}if(e.kind==="text"){let l=bt(e.maxLength);if(l!==null&&(isNaN(l)||!Number.isInteger(l)||l<1))return"The max length is a whole number of at least 1.";if(l!==null&&e.dflt.length>l)return s+" is longer than the max length."}return e.required&&n.records>0&&!$t(o.id)?"A required "+o.label+" can only be added while the collection is empty: the "+Ke(n.records)+" already here would have nothing in it.":e.required&&n.records>0&&!ac(e)?"Say what the "+Ke(n.records)+" already in the collection get: a required field needs a value in each of them.":null}function Ke(e){return e===1?"1 record":e+" records"}function ds(e,n){return $t(e.kind)?e.required?n>0?{disabled:!1,label:"Fill the "+Ke(n)+" already here with",why:"Once, now. It is not a default: a new record without this field is refused.",fill:!0}:{disabled:!0,why:"A required field has no default: every record must say it.",label:"Default",fill:!1}:{disabled:!1,why:"",label:"Default",fill:!1}:{disabled:!0,why:"",label:"Default",fill:!1}}function ac(e){return e.kind==="yesno"?e.boolDefaultSet:$t(e.kind)?e.dflt.trim()!=="":!1}function us(e,n=0){let i=At(e.kind),r={name:e.name.trim(),type:i.storage};if((e.kind==="link"||e.kind==="links")&&(r.targetClass=e.target),e.required&&(r.required=!0),e.description.trim()&&(r.description=e.description.trim()),e.kind==="yesno")e.boolDefaultSet&&(r.defaultValue=e.boolDefault);else if(e.kind==="number"){let o=bt(e.dflt);o!==null&&!isNaN(o)&&(r.defaultValue=o)}else(e.kind==="text"||e.kind==="choice")&&e.dflt.trim()&&(r.defaultValue=e.dflt.trim());return e.required&&n>0&&r.defaultValue!==void 0&&(r.fillExisting=r.defaultValue,delete r.defaultValue),r}function ps(e){let n=e.name.trim(),i=[];if(e.kind==="choice"&&i.push({field:n,oneOf:e.values.slice()}),e.kind==="text"){let r=bt(e.maxLength);r!==null&&!isNaN(r)&&i.push({field:n,maxLength:r}),e.looksLike&&i.push({field:n,looksLike:e.looksLike})}if(e.kind==="number"){let r=bt(e.min),o=bt(e.max),s={field:n};r!==null&&!isNaN(r)&&(s.min=r),o!==null&&!isNaN(o)&&(s.max=o),(s.min!==void 0||s.max!==void 0)&&i.push(s),e.whole&&i.push({field:n,whole:!0})}return i}function ms(e,n,i){return(e||[]).filter(o=>o.declared&&o.rule.field!==n).map(o=>o.rule).concat(i)}function fs(e){return e.defaultValue===void 0||e.defaultValue===null?"":e.type==="Boolean"?e.defaultValue===!0?"Yes":"No":String(e.defaultValue)}var lc=[25,50,100,250],gn={kind:"group",conj:"and",items:[]},cc=["Object","Array","ACL","File","GeoPoint"],dc=["String","Number","Boolean","Date","Pointer"];function uc(e){try{return localStorage.getItem(e)}catch{return null}}function pc(e,n){try{n===null?localStorage.removeItem(e):localStorage.setItem(e,n)}catch{}}function mc(e){let n=[{name:"objectId",kind:"text"}];return(e&&e.columns||[]).forEach(i=>{if(Ue(i.name)||i.name==="ACL")return;let r=Jo(i.type);r&&n.push({name:i.name,kind:r,targetClass:i.targetClass})}),n.concat([{name:"createdAt",kind:"date"},{name:"updatedAt",kind:"date"}])}function gs(e,n){let i=0,r=[];return e.items.forEach(o=>{if(o.kind==="group"){let s=gs(o,n);i+=s.waiting,s.group.items.length&&r.push(s.group);return}try{Ji(o,n),r.push(o)}catch{i++}}),{group:{...e,items:r},waiting:i}}function bs(e){return e.items.reduce((n,i)=>n.concat(i.kind==="group"?bs(i):i.op==="is"?[i]:[]),[])}function fc(e,n,i){let r=e.findIndex(l=>l===n||l==="-"+n),o=r===-1?null:e[r],s=o===null?n:o===n?"-"+n:null;return i?r===-1?e.concat([n]):s?e.map((l,a)=>a===r?s:l):e.filter((l,a)=>a!==r):s?[s]:[]}function ys({params:e}){let{features:n,readonly:i,whoami:r}=te(),o=r?r.backend.id:"",[s,l]=m(null),[a,c]=m(""),[u,p]=m(""),[d,f]=m(gn),[b,g]=m(gn),[v,y]=m(null),[T,w]=m([]),[x,R]=m(50),[C,_]=m(!1),[N,k]=m(0),[E,P]=m({}),[U,X]=m(null),[H,q]=m(null),[h,L]=m([]),[Q,se]=m(""),[Me,Ae]=m(null),le=s?Object.keys(s):[],ie=e[0]&&le.indexOf(e[0])!==-1?e[0]:"",z=e[1]||"",de=s&&ie?s[ie]:void 0,ue=Ot(()=>mc(de),[de]),Be="nodegx.admin.columns."+o+"."+ie,Oe=ae(null),Qe=ae(null),st=ae(0);j(()=>(S("GET","/admin/schema").then(F=>{let Y={};(F.tables||[]).filter(ee=>ee.name.charAt(0)!=="_").forEach(ee=>Y[ee.name]={...ee,columns:ss(ee.columns||[],ee.checks)}),l(Y)}).catch(O),()=>on()),[]),j(()=>{if(!s)return;let F=Object.keys(s);F.length&&(!e[0]||F.indexOf(e[0])===-1)&&sn("collections",F[0])},[s,e[0]]),j(()=>{Oe.current&&clearTimeout(Oe.current),Oe.current=setTimeout(()=>p(a),250)},[a]),j(()=>{Qe.current&&clearTimeout(Qe.current),Qe.current=setTimeout(()=>g(d),250)},[d]),j(()=>{k(0),P({}),f(gn),g(gn),y(null),w([]),se(""),X(null);let F=uc(Be);Ae(F?JSON.parse(F):null),ie&&hi()},[ie]);let{group:Nt,waiting:mi}=Ot(()=>gs(b,ue),[b,ue]),[Br,Hr]=m({});j(()=>{bs(Nt).forEach(F=>{let Y=ue.find(ee=>ee.name===F.field);!Y||Y.kind!=="link"||!Y.targetClass||!F.value||Br[F.value]||S("GET","/api/"+I(Y.targetClass)+"/"+I(F.value)).then(ee=>Hr(Te=>({...Te,[F.value]:ht(ee).label}))).catch(()=>Hr(ee=>({...ee,[F.value]:"a record that no longer exists"})))})},[JSON.stringify(Nt)]);let Cn=Ot(()=>gc(de,U?U.rows:[]),[de,U]),[jr,Ya]=m({});j(()=>{!U||!de||(de.columns||[]).filter(F=>F.type==="Pointer"&&F.targetClass).forEach(F=>{let Y=Array.from(new Set(U.rows.map(ee=>it(ee[F.name])).filter(ee=>typeof ee=="string"&&!jr[ee])));Y.length&&S("GET","/api/"+I(F.targetClass)+"?limit="+Y.length+"&where="+I(JSON.stringify({objectId:{$in:Y}}))).then(ee=>{let Te={};(ee.results||[]).forEach(Le=>Te[Le.objectId]=ht(Le).label),Ya(Le=>({...Le,...Te}))}).catch(()=>{})})},[U,de]);let fi=Ot(()=>bc(Cn,Me),[Cn,Me]);function Xa(){let F=[],Y=u.trim();if(Y&&de){let Te=[{objectId:{contains:Y}}];(de.columns||[]).forEach(Le=>{Le.type==="String"&&Te.push({[Le.name]:{contains:Y}})}),F.push({$or:Te})}let ee=Yi(Nt,ue);return ee&&F.push(ee),v&&F.push(v),F.length?F.length===1?F[0]:{$and:F}:null}function Wr(F,Y){let ee=["limit="+F,"count=1","skip="+Y,"sort="+I(JSON.stringify(T.length?T:["-createdAt"]))],Te=Xa();return Te&&ee.push("where="+I(JSON.stringify(Te))),"/api/"+I(ie)+"?"+ee.join("&")}function at(){if(!ie)return;let F=++st.current;q(null),S("GET",Wr(x,N)).then(Y=>{if(F!==st.current)return;let ee=Y.results||[];X({rows:ee,total:Y.count!==void 0?Y.count:ee.length}),P(Te=>{let Le={};return ee.forEach(ct=>{Te[ct.objectId]&&(Le[ct.objectId]=!0)}),Le})}).catch(Y=>{F===st.current&&q(Y.message)})}function hi(){S("GET","/admin/views/"+I(ie)).then(F=>L(F.views||[])).catch(()=>L([]))}j(()=>{at()},[ie,u,JSON.stringify(Nt),JSON.stringify(v),JSON.stringify(T),N,x]);let Ur=ae(at);Ur.current=at,j(()=>{C&&ie?_o(ie,()=>Ur.current()):on()},[C,ie]);let Qt=U?U.total:0,lt=U?U.rows:[];if(j(()=>{let F=Y=>{if(Y.key!=="ArrowLeft"&&Y.key!=="ArrowRight"||Y.altKey||Y.metaKey||Y.ctrlKey||Y.shiftKey)return;let ee=Y.target;ee&&(ee.closest('input, textarea, select, [contenteditable], [role="combobox"]')||ee.isContentEditable)||document.querySelector('.drawer, [role="dialog"]')||(Y.key==="ArrowLeft"&&N>0&&k(Math.max(0,N-x)),Y.key==="ArrowRight"&&N+lt.length<Qt&&k(N+x))};return window.addEventListener("keydown",F),()=>window.removeEventListener("keydown",F)},[N,x,lt.length,Qt]),s&&!le.length)return t(hs,{children:t(oe,{icon:"\u25A6",action:{label:"Create one in Schema",onClick:()=>Z("schema"),write:!1},children:"This backend has no collections yet."})});let en=Object.keys(E),Za=!!(u.trim()||Nt.items.length||v);function Kr(F){f(F),k(0),se("")}function Vr(F){Ae(F),pc(Be,F?JSON.stringify(F):null)}function Qa(F){se(F);let Y=h.find(Te=>Te.name===F),ee=Y&&Y.filter?Y.filter:gn;f(ee),g(ee),y(null),w(Y?Y.sort:[]),Y&&Y.columns.length&&Vr(Y.columns),k(0)}function el(){ge(F=>t(kc,{initial:Q,taken:h.map(Y=>Y.name),close:F,save:Y=>S("PUT","/admin/views/"+I(ie)+"/"+I(Y),{filter:d.items.length?d:null,sort:T,columns:fi.map(ee=>ee.name)}).then(()=>{D("View \u201C"+Y+"\u201D saved.","ok"),se(Y),hi(),F()})}))}function tl(){let F=Q;we("Delete the view \u201C"+F+"\u201D","It is removed for everyone who administers this backend. The records are not touched.",()=>{S("DELETE","/admin/views/"+I(ie)+"/"+I(F)).then(()=>{D("View deleted.","ok"),se(""),hi()}).catch(O)})}function nl(){we("Delete "+en.length+" record(s)","They are removed from "+ie+" permanently.",()=>{en.reduce((F,Y)=>F.then(()=>S("DELETE","/api/"+I(ie)+"/"+I(Y))),Promise.resolve()).then(()=>D(en.length+" record(s) deleted.","ok")).catch(O).then(()=>{P({}),at()})})}function Gr(F,Y){if(!F.length)return D("Nothing to export.","bad");let ee=Cn,Te=[ee.map(yi=>dn(yi.name)).join(",")];F.forEach(yi=>Te.push(ee.map(ol=>dn(it(yi[ol.name]))).join(",")));let Le=URL.createObjectURL(new Blob([Te.join(\`
65038
+ \`)],{type:"text/csv"})),ct=document.createElement("a");ct.href=Le,ct.download=ie+Y+".csv",document.body.appendChild(ct),ct.click(),ct.remove(),setTimeout(()=>URL.revokeObjectURL(Le),1e3)}async function il(){let F=[];for(let ee=0;ee<2e5;ee+=500){let Le=(await S("GET",Wr(500,ee))).results||[];if(F.push(...Le),Le.length<500)break}Gr(F,"")}function rl(F){let Y;try{Y=F.trim()?JSON.parse(F):null}catch(Te){return q("The advanced filter is not valid JSON: "+Te.message)}if(q(null),Y===null)return y(null);let ee=Kn(Y,ue);if(ee&&ee.items.length){let Te=d.items.length?{kind:"group",conj:"and",items:[...d.items,...ee.conj==="and"?ee.items:[ee]]}:ee;f(Te),g(Te),y(null),D("The filter is now in rows above.","ok")}else y(Y);k(0)}let gi=Et(Nt,ue,(F,Y)=>Br[Y]||"\u2026"),bi=u.trim();return t(hs,{children:[t(V,{children:[t($,{label:"Collection",children:t("select",{value:ie,"aria-label":"Collection",onChange:F=>Z("collections",F.currentTarget.value),children:le.map(F=>t("option",{value:F,children:F},F))})}),t($,{label:"View",children:t("select",{"aria-label":"View",value:Q,onChange:F=>Qa(F.currentTarget.value),children:[t("option",{value:"",children:"All records"}),h.map(F=>t("option",{value:F.name,children:F.name},F.name))]})}),t($,{label:"Search",children:t("input",{type:"search",placeholder:"Search text fields\u2026",style:"min-width:200px",value:a,onInput:F=>{c(F.currentTarget.value),k(0)}})}),t(A,{onClick:at,children:"Refresh"}),n.realtime?t("label",{class:"check",children:[t("input",{type:"checkbox",checked:C,onChange:F=>_(F.currentTarget.checked)})," Live"]}):null,t(me,{}),en.length?t(B,{tiny:!0,kind:"danger",onClick:nl,children:["Delete ",en.length," selected"]}):null,t(B,{kind:"primary",onClick:()=>Z("collections",ie,"new"),children:"New record"})]}),t(G,{h:8}),t("div",{class:"filter-box",children:[d.items.length?t(gt,{group:d,fields:ue,onChange:Kr}):t("button",{type:"button",class:"btn tiny",onClick:()=>Kr({kind:"group",conj:"and",items:[jt(ue[1]||ue[0])]}),children:"+ Filter"}),mi?t(M,{children:mi===1?"One condition has no value yet; it is not applied.":mi+" conditions have no value yet; they are not applied."}):null,v?t(J,{kind:"accent",children:["An advanced filter is applied as well: ",t("code",{children:JSON.stringify(v)})," ",t(A,{tiny:!0,onClick:()=>y(null),children:"Clear it"})]}):null,t(V,{children:[t(A,{tiny:!0,onClick:()=>ge(F=>t(yc,{all:Cn,shown:fi.map(Y=>Y.name),close:F,onApply:Vr})),children:"Columns"}),t(B,{tiny:!0,onClick:el,children:"Save view\u2026"}),Q?t(B,{tiny:!0,kind:"danger",onClick:tl,children:"Delete view"}):null,t(me,{}),t(A,{tiny:!0,onClick:()=>Gr(lt,"-page"),children:"Export this page"}),t(A,{tiny:!0,onClick:()=>il().catch(O),children:"Export all records"}),t(B,{tiny:!0,onClick:()=>ge(F=>t(vc,{collection:ie,table:de,close:F,onDone:at})),children:"Import CSV"})]}),t(He,{label:"Advanced (JSON)",children:t(V,{children:[t(hc,{onApply:rl}),t(M,{children:"Enter applies. Anything the rows can say moves into the rows."})]})})]}),t(G,{h:8}),H?t(J,{kind:"bad",children:H}):null,U?t(W,{children:[t(V,{children:[t("span",{class:"result-sentence",children:[t("b",{children:[Qt," ",Qt===1?"record":"records"]}),gi?" where "+gi:"",bi?gi?", matching \u201C"+bi+"\u201D":" matching \u201C"+bi+"\u201D":"",v?" (and the advanced filter)":""]}),t(me,{}),lt.length?t(M,{children:["Showing ",N+1,"\u2013",N+lt.length]}):null,t("select",{"aria-label":"Records per page",value:String(x),onChange:F=>{R(Number(F.currentTarget.value)),k(0)},children:lc.map(F=>t("option",{value:String(F),children:[F," a page"]},F))}),t(A,{tiny:!0,disabled:!(N>0),onClick:()=>k(Math.max(0,N-x)),title:"Previous page (\u2190)",children:"Prev"}),t(A,{tiny:!0,disabled:N+lt.length>=Qt,onClick:()=>k(N+x),title:"Next page (\u2192)",children:"Next"})]}),t(G,{h:8}),lt.length?t(wc,{collection:ie,rows:lt,columns:fi,sort:T,onSort:(F,Y)=>{w(fc(T,F,Y)),k(0)},selected:E,setSelected:P,readonly:i,onOpen:F=>Z("collections",ie,F.objectId),onDeleted:at,onChanged:F=>X(Y=>Y&&{...Y,rows:Y.rows.map(ee=>ee.objectId===F.objectId?F:ee)}),labels:jr}):t("div",{class:"scroller",children:Za?t(oe,{children:"No records match."}):t(oe,{icon:"\uFF0B",action:{label:"Add the first one",onClick:()=>Z("collections",ie,"new")},children:"No records yet."})})]}):null,z&&s&&ie?t(Tc,{collection:ie,table:de,id:z==="new"?null:z,onClose:()=>Z("collections",ie),onSaved:()=>{Z("collections",ie),at()}},z):null]})}function hs({children:e}){return t("div",{children:[t("h1",{children:"Collections"}),t("p",{class:"sub",children:"Find records by saying it in rows, sort by a column, and open a record to change it. Live updates arrive over the backend\u2019s own SSE stream."}),e]})}function hc({onApply:e}){let[n,i]=m("");return t("input",{type:"text",placeholder:'{"status":"open"}',style:"min-width:280px","aria-label":"Advanced filter (JSON)",value:n,onInput:r=>i(r.currentTarget.value),onKeyDown:r=>{r.key==="Enter"&&(r.preventDefault(),e(n),i(""))}})}function gc(e,n){let i=[{name:"objectId",type:"String"}];(e&&e.columns||[]).forEach(o=>{!Ue(o.name)&&o.name!=="ACL"&&i.push(o)});let r=i.map(o=>o.name).concat(["createdAt","updatedAt","ACL"]);return n.forEach(o=>{Object.keys(o).forEach(s=>{r.indexOf(s)===-1&&(r.push(s),i.push({name:s,type:Ko(o[s])}))})}),i.concat([{name:"createdAt",type:"Date"},{name:"updatedAt",type:"Date"},{name:"ACL",type:"ACL"}])}function bc(e,n){if(!n)return e.filter(o=>o.name!=="objectId"&&o.name!=="updatedAt"&&o.name!=="ACL");let i={};e.forEach(o=>i[o.name]=o);let r=n.filter(o=>i[o]).map(o=>i[o]);return r.length?r:e.filter(o=>o.name!=="objectId")}function yc({all:e,shown:n,close:i,onApply:r}){let[o,s]=m(()=>n.concat(e.map(f=>f.name).filter(f=>n.indexOf(f)===-1))),[l,a]=m(()=>{let f={};return n.forEach(b=>f[b]=!0),f}),[c,u]=m(null),p=(f,b)=>{if(b<0||b>=o.length||f===b)return;let g=o.slice(),[v]=g.splice(f,1);g.splice(b,0,v),s(g)},d=f=>(e.find(b=>b.name===f)||{type:""}).type;return t(ke,{title:"Columns",actions:t(W,{children:[t(A,{onClick:()=>{r(null),i()},children:"Reset"}),t(me,{}),t(A,{onClick:i,children:"Cancel"}),t(A,{kind:"primary",onClick:()=>{let f=o.filter(b=>l[b]);if(!f.length)return D("Show at least one column.","bad");r(f),i()},children:"Apply"})]}),children:[t("p",{class:"sub",children:"Tick the columns to show; drag, or use \u2191 \u2193, to put them in order. Remembered for this collection in this browser."}),t("div",{class:"columns-list",children:o.map((f,b)=>t("div",{class:"columns-row"+(c===b?" dragging":""),draggable:!0,onDragStart:()=>u(b),onDragOver:g=>{g.preventDefault(),c!==null&&c!==b&&(p(c,b),u(b))},onDragEnd:()=>u(null),children:[t("span",{class:"drag-handle","aria-hidden":"true",children:"\u283F"}),t("label",{class:"check",children:[t("input",{type:"checkbox",checked:!!l[f],onChange:g=>a({...l,[f]:g.currentTarget.checked})}),f," ",t("span",{class:"th-type",children:d(f)})]}),t(me,{}),t("button",{type:"button",class:"btn tiny","aria-label":"Move "+f+" up",disabled:b===0,onClick:()=>p(b,b-1),children:"\u2191"}),t("button",{type:"button",class:"btn tiny","aria-label":"Move "+f+" down",disabled:b===o.length-1,onClick:()=>p(b,b+1),children:"\u2193"})]},f))})]})}function kc({initial:e,taken:n,close:i,save:r}){let[o,s]=m(e),[l,a]=m(null),c=()=>{let u=o.trim();if(!u)return a("A view needs a name.");r(u).catch(p=>a(p.message))};return t(ke,{title:"Save view",actions:t(W,{children:[t(A,{onClick:i,children:"Cancel"}),t(A,{kind:"primary",onClick:c,children:"Save"})]}),children:[t("p",{class:"sub",children:"The filter, the sort and the columns, kept by the backend so everyone who administers it can open them."}),t($,{label:"Name",children:t("input",{type:"text",value:o,onInput:u=>{s(u.currentTarget.value),a(null)},onKeyDown:u=>{u.key==="Enter"&&c()}})}),n.indexOf(o.trim())!==-1?t(M,{children:"There is a view with this name; saving replaces it."}):null,l?t(J,{kind:"bad",children:l}):null]})}function vc({collection:e,table:n,close:i,onDone:r}){let[o,s]=m(null),[l,a]=m(""),[c,u]=m([]),[p,d]=m(null),[f,b]=m(!1),[g,v]=m(null),y=ae(null),T=["objectId"].concat((n&&n.columns||[]).filter(k=>!Ue(k.name)&&k.name!=="ACL"&&k.type!=="Relation").map(k=>k.name)),w=k=>((n&&n.columns||[]).find(E=>E.name===k)||{type:k==="objectId"?"id":""}).type,x=()=>{if(!o)return"";let k=c.map((P,U)=>P?U:-1).filter(P=>P!==-1),E=[k.map(P=>dn(c[P])).join(",")];return o.slice(1).forEach(P=>E.push(k.map(U=>dn(P[U]===void 0?"":P[U])).join(","))),E.join(\`
65039
+ \`)},R=k=>S("POST","/admin/import/"+I(e),{format:"csv",content:x(),dryRun:k});j(()=>{if(!o||!c.some(Boolean))return d(null);let k=setTimeout(()=>{R(!0).then(d).catch(E=>v(E.message))},250);return()=>clearTimeout(k)},[o,c.join("|")]);let C=k=>{v(null),a(k.name);let E=new FileReader;E.onload=()=>{let P=$o(String(E.result||""));if(P.length<2)return v("That file has no rows under its header.");s(P),u(P[0].map(U=>T.find(X=>X.toLowerCase()===U.trim().toLowerCase())||""))},E.readAsText(k)},_=()=>{b(!0),R(!1).then(k=>{if(!k.applied)throw new Error(k.error||"Nothing was imported.");D(k.created+" added, "+k.updated+" updated"+(k.rejected.length?", "+k.rejected.length+" rejected":"")+".","ok"),r(),i()}).catch(k=>v(k.message)).then(()=>b(!1))},N=o?o[0]:[];return t(ke,{title:"Import CSV into "+e,wide:!0,actions:t(W,{children:[t(A,{onClick:i,children:"Cancel"}),t(A,{kind:"primary",disabled:f||!p||p.created+p.updated===0,onClick:_,children:p?"Import "+(p.created+p.updated)+" record(s)":"Import"})]}),children:[t(V,{children:[t(A,{onClick:()=>y.current&&y.current.click(),children:l?"Choose another file":"Choose a CSV file"}),t("input",{ref:y,type:"file",accept:".csv,text/csv",hidden:!0,onChange:k=>{let E=k.currentTarget.files;E&&E[0]&&C(E[0])}}),l?t(M,{children:[l," \xB7 ",o?o.length-1:0," rows"]}):t(M,{children:"The first row names the columns."})]}),o?t(W,{children:[t(G,{h:8}),t("div",{class:"scroller",children:t("table",{class:"grid import-preview",children:[t("thead",{children:t("tr",{children:N.map((k,E)=>t("th",{children:[t("div",{class:"hint",children:k}),t("select",{"aria-label":"Field for "+k,value:c[E]||"",onChange:P=>u(c.map((U,X)=>X===E?P.currentTarget.value:U)),children:[t("option",{value:"",children:"(skip)"}),T.map(P=>t("option",{value:P,children:[P," ",w(P)?"\xB7 "+w(P):""]},P))]})]},E))})}),t("tbody",{children:o.slice(1,21).map((k,E)=>t("tr",{children:N.map((P,U)=>t("td",{class:c[U]?"":"shy",children:k[U]},U))},E))})]})}),o.length>21?t(M,{children:["The first 20 of ",o.length-1," rows."]}):null,p?t(J,{kind:p.rejected.length?"warn":"",style:"margin-top:10px",children:[p.created," will be added",p.updated?", "+p.updated+" updated":"",p.rejected.length?", "+p.rejected.length+" rejected:":".",p.rejected.slice(0,5).map(k=>t("div",{class:"hint",children:["Row ",k.row+2,": ",k.errors.join("; ")]},k.row))]}):null]}):null,g?t(J,{kind:"bad",style:"margin-top:10px",children:g}):null]})}function wc({collection:e,rows:n,columns:i,sort:r,onSort:o,selected:s,setSelected:l,readonly:a,onOpen:c,onDeleted:u,onChanged:p,labels:d}){let f=n.every(y=>s[y.objectId]),b=ae(null),g=y=>{b.current&&clearTimeout(b.current),b.current=setTimeout(()=>c(y),230)},v=()=>{b.current&&clearTimeout(b.current),b.current=null};return t("div",{class:"scroller",children:t("table",{class:"grid",children:[t("thead",{children:t("tr",{children:[t("th",{class:"pick",children:t("input",{type:"checkbox",title:"Select all on this page",checked:f,onChange:y=>{let T=y.currentTarget.checked;l(w=>{let x={...w};return n.forEach(R=>{T?x[R.objectId]=!0:delete x[R.objectId]}),x})}})}),i.map(y=>{let T=r.findIndex(R=>R===y.name||R==="-"+y.name),w=T===-1?null:r[T].charAt(0)==="-"?"desc":"asc",x=dc.indexOf(y.type)!==-1||Ue(y.name);return t("th",{class:x?"sortable":"","aria-sort":w==="asc"?"ascending":w==="desc"?"descending":void 0,title:x?"Sort by "+y.name+" (\u21E7 to add to the sort)":void 0,onClick:x?R=>o(y.name,R.shiftKey):void 0,children:[y.name," ",t("span",{class:"th-type",children:y.type}),w?t("span",{class:"sort-mark",children:[w==="asc"?"\u25B2":"\u25BC",r.length>1?T+1:""]}):null]},y.name)}),t("th",{})]})}),t("tbody",{children:n.map(y=>t("tr",{class:"clickable",onClick:()=>g(y),children:[t("td",{class:"pick",onClick:T=>T.stopPropagation(),children:t("input",{type:"checkbox",checked:!!s[y.objectId],onChange:T=>{let w=T.currentTarget.checked;l(x=>{let R={...x};return w?R[y.objectId]=!0:delete R[y.objectId],R})}})}),i.map(T=>t(Cc,{collection:e,row:y,col:T,readonly:a,onChanged:p,cancelOpen:v,labels:d},T.name)),t("td",{class:"actions",onClick:T=>T.stopPropagation(),children:t(B,{tiny:!0,kind:"danger",onClick:()=>we("Delete record","This removes "+e+"/"+y.objectId+" permanently.",()=>{S("DELETE","/api/"+I(e)+"/"+I(y.objectId)).then(()=>{D("Record deleted.","ok"),u()}).catch(O)}),children:"Delete"})})]},y.objectId))})]})})}function Cc({collection:e,row:n,col:i,readonly:r,onChanged:o,cancelOpen:s,labels:l}){let[a,c]=m(!1),[u,p]=m(""),d=ae(!1),f=!r&&!Ue(i.name)&&i.type!=="Relation",b=i.type==="Pointer"?it(n[i.name]):null,g=i.name==="objectId"?nt(n.objectId):typeof b=="string"&&b?"\u2192 "+(l[b]||"\u2026"):Mn(n[i.name],i.type),v=i.name==="objectId"?String(n.objectId):g,y=i.type==="ACL"&&(n.ACL===null||n.ACL===void 0);function T(R){let C={},_=R===void 0?null:Ui(i,R);return C[i.name]=_,S("PUT","/api/"+I(e)+"/"+I(n.objectId),C).then(N=>{let k={...n,[i.name]:_};N&&N.updatedAt&&(k.updatedAt=N.updatedAt),o(k),D(i.name+" saved.","ok")})}function w(R){if(R.stopPropagation(),s(),!(a||!f)){if(i.type==="Boolean"){T(it(n[i.name])!==!0).catch(O);return}if(cc.indexOf(i.type)!==-1){ge(C=>t(Sc,{collection:e,col:i,value:n[i.name],close:C,save:T}));return}p(We(i,n[i.name])),d.current=!1,c(!0)}}function x(R,C){if(d.current||(d.current=!0,c(!1),!R))return;let _;try{_=ze(i,C===void 0?u:C)}catch(k){O(k);return}let N=it(n[i.name]);_===N||_===void 0&&N==null||T(_).catch(O)}return a?t("td",{class:"cell-edit",onClick:R=>R.stopPropagation(),children:t(Vt,{col:i,raw:u,onChange:R=>p(R),autoFocus:!0,onCommit:()=>x(!0),onCancel:()=>x(!1)})}):t("td",{class:(f?"editable":"locked")+(y?" shy":""),title:f?v+" \u2014 double-click to edit":v,onDblClick:w,children:g})}function Sc({collection:e,col:n,value:i,close:r,save:o}){let[s,l]=m(We(n,i));return t(ke,{title:n.type==="ACL"?"Who can see this record":"Edit "+n.name,actions:t(W,{children:[t(A,{onClick:r,children:"Cancel"}),t(A,{kind:"primary",onClick:()=>{let a;try{a=ze(n,s)}catch(c){return O(c)}o(a).then(r).catch(O)},children:"Save"})]}),children:n.type==="ACL"?t(Ut,{state:s.acl,onChange:a=>l({t:"acl",acl:a}),collection:e}):t(nr,{col:n,children:t(Vt,{col:n,raw:s,onChange:l,autoFocus:!0})})})}function nr({col:e,children:n}){let i=e.type==="Pointer"||e.type==="Relation";return t("div",{class:"field",children:[t("span",{class:"field-head",children:[t("b",{children:e.name}),e.required?t("span",{class:"req",children:"required"}):null,t("span",{class:"chip type",children:e.type+(i&&e.targetClass?" \u2192 "+e.targetClass:"")})]}),n]})}function Tc({collection:e,table:n,id:i,onClose:r,onSaved:o}){let s=i!==null,[l,a]=m(s?null:{}),[c,u]=m({}),[p,d]=m(null),[f,b]=m(!1),g=n&&n.columns||[],v=g.filter(C=>!Ue(C.name)&&C.name!=="ACL"&&C.type!=="Relation"),y=g.filter(C=>C.type==="Relation"),T={name:"ACL",type:"ACL"};j(()=>{if(!s){let C={};v.forEach(_=>C[_.name]=We(_,_.defaultValue)),C.ACL=We(T,null),u(C);return}S("GET","/api/"+I(e)+"/"+I(i)).then(C=>{a(C);let _={};v.forEach(N=>_[N.name]=We(N,C[N.name])),_.ACL=We(T,C.ACL),u(_)}).catch(C=>{b(!0),O(C)})},[e,i]);let w=C=>c[C.name]===void 0?We(C,void 0):c[C.name];function x(){let C={};try{v.forEach(E=>{let P=ze(E,w(E));if(P===void 0||P===""){if(E.required&&E.type!=="Boolean")throw new Error(E.name+" is required.");P===""&&E.type==="String"&&s?C[E.name]="":s&&l&&l[E.name]!==null&&l[E.name]!==void 0&&(C[E.name]=null);return}C[E.name]=Ui(E,P)});let N=ze(T,w(T)),k=s&&l?l.ACL===void 0?null:l.ACL:null;JSON.stringify(N)!==JSON.stringify(k)&&(C.ACL=N)}catch(N){d(N.message);return}let _="/api/"+I(e);(s?S("PUT",_+"/"+I(i),C):S("POST",_,C)).then(N=>{if(D(s?"Record saved.":"Record created.","ok"),!s&&y.length&&N&&N.objectId){Z("collections",e,N.objectId);return}o()}).catch(N=>d(N.message))}let R=s?"Edit "+e+" / "+nt(i):"New record in "+e;return t(Se,{title:R,wide:!0,subtitle:s?"objectId, createdAt and updatedAt are kept by the backend.":void 0,onClose:r,footer:t(W,{children:[t(A,{onClick:r,children:"Cancel"}),t(B,{kind:"primary",disabled:f||s&&!l,onClick:x,children:s?"Save":"Create"})]}),children:[f?t(J,{kind:"bad",children:["That record is not in ",e," any more."]}):s&&!l?t(M,{children:"Loading\u2026"}):t(W,{children:[!v.length&&!y.length?t(J,{children:[e," has no fields yet."," ",t(A,{tiny:!0,onClick:()=>Z("schema",e),children:"Add fields in Schema"})]}):null,t("div",{class:"form-grid",onInput:()=>d(null),children:[v.map((C,_)=>t(nr,{col:C,children:t(Vt,{col:C,raw:w(C),onChange:N=>u(k=>({...k,[C.name]:N})),autoFocus:_===0})},C.name)),y.map(C=>t(nr,{col:C,children:s?t(ts,{collection:e,id:i,col:C}):t(M,{children:"Links are made once the record exists \u2014 Create opens it."})},C.name))]}),t("div",{class:"drawer-section",children:"Who can see this record"}),t(Ut,{state:w(T).acl,onChange:C=>u(_=>({..._,ACL:{t:"acl",acl:C}})),collection:e})]}),p?t(J,{kind:"bad",style:"margin-top:12px",children:p}):null,s&&l?t(He,{label:"Raw record",children:t("textarea",{readOnly:!0,"aria-label":"Raw record",value:JSON.stringify(l,null,2),style:"min-height:200px"})}):null]})}async function $n(e,n){if(e==="_User"){let o=await S("GET","/admin/users?limit=1");return o&&o.total||0}let i="?count=1&limit=0"+(n?"&where="+I(JSON.stringify(n)):""),r=await S("GET","/api/"+I(e)+i);return r&&r.count||0}function vs({params:e}){let{readonly:n,whoami:i}=te(),r=i?i.accountColumns:void 0,[o,s]=m(null),l=o?o.map(f=>f.name):[],a=e[0]||"",c=e[1]==="new-field"?a:"",u=c&&o?o.find(f=>f.name===c):void 0;function p(){S("GET","/admin/schema").then(f=>s(f.tables||[])).catch(O)}j(p,[]),j(()=>{if(!o||!a)return;let f=document.getElementById("schema-"+a);f&&f.scrollIntoView({block:"start",behavior:"smooth"})},[o,a]);function d(){ge(f=>t(Fc,{names:l,close:f,onCreated:p}))}return t(ce,{title:"Schema",subtitle:"Your collections and their fields. Click a field name to rename it, or its kind to change it.",children:[t(V,{children:[t(B,{kind:"primary",onClick:d,children:"New collection"}),t(A,{onClick:p,children:"Refresh"})]}),t(G,{}),o&&!o.length?t(oe,{icon:"\u25A6",action:{label:"Create the first one",onClick:d},children:"No collections yet. A collection is a table of records \u2014 Products, Orders, Members."}):null,(o||[]).map(f=>t(Ec,{t:f,names:l,readonly:n,accountColumns:r,hit:f.name===a,reload:p},f.name)),u?t(Cs,{t:u,names:l,onClose:()=>Z("schema",c),onAdded:p}):null]})}function xc(e){return(e.indexes||[]).filter(n=>n.declared!==!1).length}function Rc({col:e,checks:n}){let i=tr(e,n),r=e.type==="Pointer"||e.type==="Relation";return t("span",{class:"kind",children:[t("span",{class:"kind-icon","aria-hidden":"true",children:i.icon}),i.label,r&&e.targetClass?" \u2192 "+Ve(e.targetClass):"",t("span",{class:"chip type",children:e.type})]})}function Ec({t:e,names:n,readonly:i,accountColumns:r,hit:o,reload:s}){let l=e.name.charAt(0)==="_",a=e.columns||[],c=a.filter(C=>!Ue(C.name)),p=[{name:"objectId",type:"String",note:"unique id, set by the backend"},{name:"createdAt",type:"Date",note:"set by the backend"},{name:"updatedAt",type:"Date",note:"set by the backend"}].concat(c),d=e.indexes||[],f=(e.checks||[]).filter(C=>C.declared),b=(e.checks||[]).filter(C=>!C.declared),[g,v]=m(null);j(()=>{l||$n(e.name).then(v).catch(()=>v(null))},[e.name,a.length]);function y(){Z("schema",e.name,"new-field")}function T(){ge(C=>t(Hc,{t:e,close:C,onApplied:s}))}function w(){S("POST","/admin/schema",{action:"deleteTable",table:e.name}).then(C=>{C&&C.deleted===!1?D('The backend reported that "'+e.name+'" was not deleted.',"bad"):D('Collection "'+e.name+'" deleted.',"ok"),s()}).catch(O)}function x(){_c(e.name).then(C=>{D(Ke(C)+" deleted from "+e.name+".","ok"),v(0)}).catch(O)}function R(C){let _=f.filter(N=>N.name!==C.name).map(N=>N.rule);S("POST","/admin/schema",{action:"setChecks",table:e.name,checks:_}).then(()=>D("Rule removed: "+C.description+".","ok")).catch(O).then(s)}return t("div",{class:"card"+(o?" hit":""),id:"schema-"+e.name,children:[t(V,{children:[t(vt,{children:Ve(e.name)}),l?t(K,{kind:"warn",children:e.name==="_User"?"accounts":"system"}):null,t(K,{children:[a.length," field(s)"]}),t(K,{children:[xc(e)," index(es)"]}),g!==null?t(K,{children:Ke(g)}):null,t(me,{}),t(A,{tiny:!0,onClick:()=>e.name==="_User"?Z("users"):Z("collections",e.name),children:e.name==="_User"?"Open people":"Open records"}),t(B,{tiny:!0,onClick:y,children:"Add field"}),t(B,{tiny:!0,onClick:T,children:"Indexes"})]}),e.description?t("div",{class:"shy collection-description",children:e.description}):null,t(G,{h:8}),t(ye,{columns:["Field","Kind","Required","Default",""],rows:p,renderRow:C=>{let _=!!C.note||Uo(e.name,C.name,r),N=_||i,k=f.some(E=>E.rule.field?E.rule.field===C.name:(E.rule.exactlyOne||E.rule.allOrNone||[]).indexOf(C.name)!==-1);return t("tr",{class:_?"sys":void 0,children:[t("td",{children:[N?C.name:t(Pc,{t:e,c:C,reload:s}),C.description?t("div",{class:"shy",children:C.description}):null]}),t("td",{children:N||C.type==="Relation"||k?t(Rc,{col:C,checks:e.checks}):t(Lc,{t:e,c:C,reload:s})}),t("td",{children:C.required?"\u2713":""}),t("td",{class:"shy",children:C.note||fs(C)}),t("td",{class:"actions",children:N?null:t(Ac,{t:e,c:C,reload:s})})]},C.name)}}),c.length?null:t(V,{style:"margin-top:8px",children:[t(M,{children:"No fields of your own yet."}),t(B,{tiny:!0,kind:"primary",onClick:y,children:"+ Add a field"})]}),f.length||b.length?t("div",{class:"rules",children:[t("div",{class:"field-head",children:[t("b",{children:"Rules"}),t(M,{children:"Every record must satisfy these; the backend refuses a write that breaks one."})]}),t("ul",{class:"rules-list",children:[f.map(C=>t("li",{class:"rule",children:[t("span",{children:C.description}),C.built?null:t(K,{kind:"bad",children:"not enforced"}),i?null:t("button",{type:"button",class:"chip-x","aria-label":"Remove the rule: "+C.description,title:"Remove this rule",onClick:()=>R(C),children:"\u2715"})]},C.name)),b.map(C=>t("li",{class:"rule",children:[t("span",{children:C.name}),t(K,{kind:"warn",children:"not declared"})]},C.name))]})]}):null,d.length?t(W,{children:[t(G,{h:8}),t(ye,{columns:["Index","Fields","Unique","Order","State"],rows:d,renderRow:(C,_)=>t("tr",{children:[t("td",{children:C.name}),t("td",{children:(C.fields||[]).join(", ")}),t("td",{children:C.unique?"yes":""}),t("td",{children:C.order==="desc"?"newest / largest first":"ascending"}),t("td",{children:C.declared===!1?t(K,{kind:"warn",children:"not declared"}):C.built?t(K,{kind:"ok",children:"built"}):t(K,{kind:"bad",children:"missing"})})]},C.name||_)})]}):null,l||i?null:t($e,{children:[t(Ze,{label:"Empty collection",why:"Delete every record in "+e.name+(g!==null?" ("+Ke(g)+")":"")+". The fields, rules and indexes stay.",title:"Empty collection "+e.name,warning:(g!==null?Ke(g)+" in":"Every record in")+' "'+e.name+'" '+(g===1?"is":"are")+" destroyed. The collection itself stays, with its fields.",expected:e.name,onConfirm:x,verb:"Empty"}),t(Ze,{label:"Delete collection",why:"Destroy "+e.name+" and every record in it. Permissions and triggers that name it are not removed and will start failing.",title:"Delete collection "+e.name,warning:'Every record in "'+e.name+'" is destroyed along with the collection. Permissions and triggers that reference it are NOT removed and will start failing.',expected:e.name,onConfirm:w})]})]})}async function _c(e){let n=0;for(let i=0;i<1e4;i++){let r=await S("GET","/api/"+I(e)+"?limit=200&keys=objectId"),o=(r&&r.results||[]).map(s=>s.objectId).filter(Boolean);if(!o.length)return n;await S("POST","/api/_batch",{operations:o.map(s=>({method:"delete",collection:e,objectId:s}))}),n+=o.length}return n}function Ac({t:e,c:n,reload:i}){let[r,o]=m(!1);function s(){o(!0),(e.name==="_User"?Promise.resolve(null):$n(e.name,{[n.name]:{$exists:!0}}).catch(()=>null)).then(a=>{o(!1);let c=a===null?"Records holding a value in it lose it.":a===0?"No record holds a value in it.":Ke(a)+" hold"+(a===1?"s":"")+" a value in it, and that value is lost.";qe("Drop the field "+n.name,c+' App logic that reads "'+n.name+'" will break. A rule or an index that reads it must be removed first.',n.name,()=>{S("POST","/admin/schema",{action:"dropColumn",table:e.name,column:n.name}).then(u=>D(u&&u.dropped===!1?'"'+n.name+'" was already gone.':"Dropped "+n.name+" from "+Ve(e.name)+".","ok")).catch(O).then(i)},"Drop")})}return t("button",{type:"button",class:"chip-x","aria-label":"Drop the field "+n.name,title:"Drop "+n.name,disabled:r,onClick:s,children:"\u2715"})}function Pc({t:e,c:n,reload:i}){let[r,o]=m(!1),[s,l]=m(n.name),a=ae(!1);function c(u){if(a.current)return;a.current=!0,o(!1);let p=s.trim();if(!u||p===n.name)return;let d=(e.columns||[]).map(b=>b.name).filter(b=>b!==n.name),f=Mt(p,d,"The field");if(f){D(f,"bad");return}S("POST","/admin/schema",{action:"renameColumn",table:e.name,oldName:n.name,newName:p}).then(()=>D("Renamed "+n.name+" to "+p+". Update any app logic that uses the old name.","ok")).catch(O).then(i)}return r?t("input",{type:"text",value:s,style:"width:160px","aria-label":"New name for "+n.name,autoFocus:!0,onInput:u=>l(u.currentTarget.value),onKeyDown:u=>{u.key==="Enter"?(u.preventDefault(),c(!0)):u.key==="Escape"&&(u.preventDefault(),c(!1))},onBlur:()=>c(!0)}):t("button",{type:"button",class:"inline-edit",title:"Rename "+n.name,onClick:()=>{l(n.name),a.current=!1,o(!0)},children:n.name})}var Ic=yt.filter(e=>e.id!=="choice"&&e.id!=="links");function Lc({t:e,c:n,reload:i}){let r=tr(n,e.checks);return t("select",{class:"inline-type",title:"Change the kind of "+n.name,"aria-label":"Kind of "+n.name,value:r.id,onChange:o=>{let s=o.currentTarget,l=At(s.value);s.value=r.id;let a=l.storage;a!==n.type&&$n(e.name,{[n.name]:{$exists:!0}}).catch(()=>null).then(c=>ge(u=>t(ke,{title:"Change "+n.name+" to "+l.label+"?",autoFocus:!1,actions:t(W,{children:[t(A,{onClick:u,children:"Cancel"}),t(A,{kind:"primary",onClick:()=>{u(),S("POST","/admin/schema",{action:"changeColumnType",table:e.name,column:n.name,type:a}).then(p=>{let d=p&&p.convertedValues||0;D(n.name+" is now "+l.label+(d?" \u2014 "+d+" stored value(s) converted.":"."),"ok")}).catch(O).then(i)},children:"Change kind"})]}),children:[t("p",{children:[c===null?"Values":c===0?"No record holds a value yet; nothing is converted. Values":Ke(c)+" hold a value in "+Ve(e.name)+"."+n.name+", and each one is converted. Values"," stored as"," ",r.label," (",n.type,") become ",l.label," (",a,")."]}),t(J,{kind:"warn",children:"A conversion can lose information: text that is not a number becomes 0, and a list or object turned into text stays text."})]})))},children:Ic.filter(o=>!(o.id==="link"&&n.type!=="Pointer")).map(o=>t("option",{value:o.id,children:o.label+" ("+o.storage+")"+(o.id==="link"&&n.targetClass?" \u2192 "+n.targetClass:"")},o.id))})}function ks(){return{name:"",type:"String",target:"",required:!1,dflt:""}}function Nc(e,n){let i=e.name.trim(),r=Mt(i,n,"Each field");if(r)throw new Error(r);let o={name:i,type:e.type};if(e.type==="Pointer"||e.type==="Relation"){if(!e.target)throw new Error(i+": choose the collection it points at.");o.targetClass=e.target}e.required&&(o.required=!0);let s=e.dflt.trim();if(s&&!e.required&&["String","Number","Boolean"].indexOf(e.type)!==-1)if(e.type==="Number"){if(isNaN(Number(s)))throw new Error(i+": the default must be a number.");o.defaultValue=Number(s)}else e.type==="Boolean"?o.defaultValue=s==="yes":o.defaultValue=s;return o}var Dc=yt.filter(e=>e.id!=="choice");function Oc({line:e,update:n,names:i,autoFocus:r}){let o=e.type==="Pointer"||e.type==="Relation",s=["String","Number","Boolean"].indexOf(e.type)!==-1,l="A required field has no default: every record must say it.";return t(W,{children:[t("input",{type:"text",placeholder:"fieldName","aria-label":"Field name",value:e.name,autoFocus:r,onInput:a=>n({...e,name:a.currentTarget.value})}),t("select",{"aria-label":"Kind",value:e.type,onChange:a=>n({...e,type:a.currentTarget.value}),children:Dc.map(a=>t("option",{value:a.storage,children:a.label+" ("+a.storage+")"},a.id))}),o?t("select",{"aria-label":"Points at",value:e.target,onChange:a=>n({...e,target:a.currentTarget.value}),children:[t("option",{value:"",children:"\u2192 choose a collection"}),i.map(a=>t("option",{value:a,children:["\u2192 ",Ve(a)]},a))]}):null,t("label",{class:"check",children:[t("input",{type:"checkbox",checked:e.required,onChange:a=>n({...e,required:a.currentTarget.checked})})," Required"]}),s?e.type==="Boolean"?t("select",{"aria-label":"Default",value:e.required?"":e.dflt,disabled:e.required,title:e.required?l:void 0,onChange:a=>n({...e,dflt:a.currentTarget.value}),children:[t("option",{value:"",children:e.required?"no default (required)":"no default"}),t("option",{value:"yes",children:"default: Yes"}),t("option",{value:"no",children:"default: No"})]}):t("input",{type:e.type==="Number"?"number":"text",step:"any",placeholder:e.required?"no default: every record must say it":"default (optional)","aria-label":"Default",title:e.required?l:void 0,disabled:e.required,value:e.required?"":e.dflt,onInput:a=>n({...e,dflt:a.currentTarget.value})}):null]})}function Fc({names:e,close:n,onCreated:i}){let[r,o]=m(""),[s,l]=m([ks()]),[a,c]=m(null),u=r?Wi(r.trim(),e):null;function p(){let d=r.trim(),f=[];try{let b=Wi(d,e);if(b)throw new Error(b);s.forEach(g=>{g.name.trim()&&f.push(Nc(g,f.map(v=>v.name)))})}catch(b){c(b.message);return}S("POST","/admin/schema",{action:"createTable",table:d,columns:f}).then(()=>{n(),D("Collection "+d+" created.","ok"),i()}).catch(b=>c(b.message))}return t(ke,{title:"New collection",wide:!0,actions:t(W,{children:[t(A,{onClick:n,children:"Cancel"}),t(A,{kind:"primary",onClick:p,children:"Create collection"})]}),children:t("div",{onInput:()=>c(null),children:[t("div",{class:"field",children:[t("b",{children:"Name"}),t("input",{type:"text",placeholder:"e.g. Products, Orders, Members",style:"width:100%","aria-label":"Collection name",value:r,onInput:d=>o(d.currentTarget.value)}),u?t("div",{class:"chips-problem",children:u}):null]}),t(G,{}),t("div",{class:"field-head",children:[t("b",{children:"Fields"}),t(M,{children:"More can be added later, and a Choice (one of a list) is added from the collection's card. objectId, createdAt and updatedAt come for free."})]}),t(G,{h:8}),t(Xe,{rows:s,onChange:l,blank:ks,addLabel:"+ Add field",removeTitle:"Remove this field",renderRow:(d,f,b)=>t(Oc,{line:d,update:f,names:e,autoFocus:b>0&&b===s.length-1})}),a?t(J,{kind:"bad",style:"margin-top:12px",children:a}):null]})})}function Ve(e){return e==="_User"?"Users":e}function ws({t:e,names:n,close:i,onAdded:r}){return t(Cs,{t:e,names:n,onClose:i,onAdded:r})}function Mc({onPick:e,autoFocus:n}){let[i,r]=m(""),o=rs(i);return t("div",{class:"kind-picker",children:[t("input",{type:"text",class:"kind-search",placeholder:"What does it hold? e.g. a number, a link, a picture","aria-label":"Search kinds",autoFocus:n,value:i,onInput:s=>r(s.currentTarget.value),onKeyDown:s=>{s.key==="Enter"&&o.length===1&&(s.preventDefault(),e(o[0].id))}}),t("div",{class:"tiles kinds",role:"radiogroup","aria-label":"Kind of field",children:[o.map(s=>t("label",{class:"tile",children:[t("input",{type:"radio",name:"kind",value:s.id,"aria-label":s.label,onChange:()=>e(s.id)}),t("span",{class:"kind-icon","aria-hidden":"true",children:s.icon}),t("b",{children:s.label}),t("span",{class:"sub",children:s.line}),t("span",{class:"chip type",children:s.storage})]},s.id)),o.length?null:t(M,{children:['Nothing matches "',i,'". Try text, number, date, link, picture.']})]})]})}function Bc({draft:e,update:n,names:i,taken:r,records:o}){let s=At(e.kind),l=e.name?Mt(e.name.trim(),r,"The field"):null,a=ds(e,o),c=p=>n({...e,...p}),u=(p,d,f={})=>t($,{label:p,children:t("input",{type:"text","aria-label":p,value:e[d],onInput:b=>c({[d]:b.currentTarget.value}),...f})});return t("div",{class:"field-options",children:[t("div",{class:"field",children:[t("b",{children:"Name"}),t("input",{type:"text",placeholder:"e.g. price, dueDate, ownerName","aria-label":"Field name",autoFocus:!0,value:e.name,onInput:p=>c({name:p.currentTarget.value})}),l?t("div",{class:"chips-problem",children:l}):t(M,{children:"Letters, digits and _ \u2014 this is the name app logic reads it by."})]}),u("Description (optional)","description",{placeholder:"One line about what goes here"}),t("div",{class:"when-body",id:"kind-options",children:[e.kind==="text"?t(W,{children:[t($,{label:a.label,children:[t("input",{type:"text","aria-label":a.label,value:e.dflt,disabled:a.disabled,placeholder:a.disabled?"no default":"optional",onInput:p=>c({dflt:p.currentTarget.value})}),a.why?t(M,{children:a.why}):null]}),t("div",{class:"field-line",children:[u("Max length","maxLength",{type:"number",min:1,step:1,placeholder:"any",class:"short"}),t($,{label:"Must look like",children:t("select",{"aria-label":"Must look like",value:e.looksLike,onChange:p=>c({looksLike:p.currentTarget.value}),children:[t("option",{value:"",children:"anything"}),t("option",{value:"email",children:"an email address"}),t("option",{value:"url",children:"a web address"})]})})]})]}):null,e.kind==="number"?t(W,{children:[t($,{label:a.label,children:[t("input",{type:"number",step:"any","aria-label":a.label,value:e.dflt,disabled:a.disabled,placeholder:a.disabled?"no default":"optional",onInput:p=>c({dflt:p.currentTarget.value})}),a.why?t(M,{children:a.why}):null]}),t("div",{class:"field-line",children:[u("At least","min",{type:"number",step:"any",placeholder:"no minimum",class:"short"}),u("At most","max",{type:"number",step:"any",placeholder:"no maximum",class:"short"})]}),t(re,{checked:e.whole,onChange:p=>c({whole:p}),label:"Whole numbers only",children:"Whole numbers only"})]}):null,e.kind==="yesno"?t(W,{children:[t(re,{checked:e.boolDefaultSet,disabled:a.disabled,onChange:p=>c({boolDefaultSet:p}),label:a.fill?a.label:"Has a default",children:a.fill?a.label+"\u2026":"New records start with a default"}),e.boolDefaultSet&&!a.disabled?t(re,{checked:e.boolDefault,onChange:p=>c({boolDefault:p}),label:a.fill?"Fill value":"Default value",children:[a.fill?"Fill with":"Default",": ",e.boolDefault?"Yes":"No"]}):null,a.why?t(M,{children:a.why}):null]}):null,e.kind==="date"?t(M,{children:"No options. The backend stamps createdAt and updatedAt on every record for you."}):null,e.kind==="choice"?t(W,{children:[t($,{label:"The choices",children:t(Ie,{id:"choice-values",items:e.values,onChange:p=>c({values:p,dflt:p.indexOf(e.dflt)===-1?"":e.dflt}),placeholder:"Type a value and press Enter",addLabel:"Add",validate:p=>p.length>200?"A choice is at most 200 characters.":null})}),t($,{label:a.label,children:[t("select",{"aria-label":a.label,value:e.dflt,disabled:a.disabled||!e.values.length,onChange:p=>c({dflt:p.currentTarget.value}),children:[t("option",{value:"",children:a.fill?"choose a value\u2026":"no default"}),e.values.map(p=>t("option",{value:p,children:p},p))]}),a.why?t(M,{children:a.why}):null]})]}):null,e.kind==="link"||e.kind==="links"?t($,{label:"Points at",children:[t("select",{"aria-label":"Points at",value:e.target,onChange:p=>c({target:p.currentTarget.value}),children:[t("option",{value:"",children:"choose a collection\u2026"}),i.map(p=>t("option",{value:p,children:Ve(p)},p))]}),e.target?t(M,{children:[e.kind==="link"?"Each record links to one "+Ve(e.target)+".":"Each record links to many "+Ve(e.target)+"."," When the ",Ve(e.target)," is deleted, the link stays and points at nothing."]}):null]}):null,e.kind==="file"?t(M,{children:"No options here. What can be uploaded, and how big, is set on the Files page for the whole backend."}):null,e.kind==="location"||e.kind==="list"||e.kind==="anything"?t(M,{children:"No options."}):null]}),t("div",{class:"drawer-section",children:"Rules"}),t("div",{class:"when-body",children:[t(re,{checked:e.required,onChange:p=>c({required:p}),label:"Required",children:"Required \u2014 every record must say it"}),e.required&&o>0&&$t(e.kind)?t(M,{children:[Ke(o)," already here: say what they get, once, in the box above. New records must say it themselves."]}):null,e.required&&o>0&&!$t(e.kind)?t(J,{kind:"warn",children:["A required ",s.label," can only be added while the collection is empty: the ",Ke(o)," already here would have nothing in it."]}):null,ls(e.kind)?t(re,{checked:e.unique,onChange:p=>c({unique:p}),label:"Must be unique",children:"Must be unique \u2014 no two records share a value"}):null]})]})}function Cs({t:e,names:n,onClose:i,onAdded:r}){let[o,s]=m(null),[l,a]=m(0),[c,u]=m(null),[p,d]=m(!1),f=(e.columns||[]).map(v=>v.name);j(()=>{$n(e.name).then(a).catch(()=>a(0))},[e.name]);function b(){if(!o)return;let v=cs(o,{taken:f,collections:n,records:l});if(v){u(v);return}let y=us(o,l),T=ps(o);d(!0),S("POST","/admin/schema",{action:"addColumn",table:e.name,column:y}).then(()=>T.length?S("POST","/admin/schema",{action:"setChecks",table:e.name,checks:ms(e.checks,y.name,T)}):null).then(()=>o.unique?S("POST","/admin/schema",{action:"setIndexes",table:e.name,indexes:(e.indexes||[]).filter(w=>w.declared!==!1).map(w=>({fields:w.fields,unique:w.unique,order:w.order})).concat([{fields:[y.name],unique:!0}])}):null).then(()=>{D(y.name+" added to "+Ve(e.name)+".","ok"),r(),i()}).catch(w=>{d(!1),u(w.message),r()})}let g=o?At(o.kind):null;return t(Se,{title:"Add a field to "+Ve(e.name),subtitle:o&&g?g.label+" \u2014 "+g.line:"What kind of thing does it hold?",onClose:i,wide:!0,footer:t(W,{children:[t(A,{onClick:i,children:"Cancel"}),o?t(W,{children:[t(A,{onClick:()=>s(null),children:"Change kind"}),t(B,{kind:"primary",disabled:p,onClick:b,children:"Add field"})]}):null]}),children:t("div",{onInput:()=>u(null),children:[o?t(W,{children:[t(V,{class:"kind-chosen",children:[t("span",{class:"kind-icon","aria-hidden":"true",children:g.icon}),t("b",{children:g.label}),t(fe,{style:"margin:0",children:g.line}),t("span",{class:"chip type",children:g.storage})]}),t(Bc,{draft:o,update:s,names:n,taken:f,records:l})]}):t(Mc,{autoFocus:!0,onPick:v=>s(as(v))}),c?t(J,{kind:"bad",style:"margin-top:12px",children:c}):null]})})}function Hc({t:e,close:n,onApplied:i}){let r=(e.columns||[]).map(p=>p.name).filter(p=>p!=="createdAt"&&p!=="updatedAt");r.indexOf("objectId")===-1&&r.unshift("objectId");let[o,s]=m((e.indexes||[]).filter(p=>p.declared!==!1).map(p=>({first:p.fields[0]||r[0],second:p.fields[1]||"",third:p.fields[2]||"",unique:!!p.unique,order:p.order==="desc"?"desc":"asc"}))),[l,a]=m(null);function c(){let p;try{p=o.map(d=>{let f=[d.first,d.second,d.third].filter(Boolean);if(f.length!==f.filter((g,v)=>f.indexOf(g)===v).length)throw new Error("An index lists each field once.");let b={fields:f};return d.unique&&(b.unique=!0),d.order==="desc"&&(b.order="desc"),b})}catch(d){a(d.message);return}S("POST","/admin/schema",{action:"setIndexes",table:e.name,indexes:p}).then(d=>{n();let f=d&&d.indexesCreated||[],b=d&&d.indexesDropped||[];D("Indexes applied: "+f.length+" created, "+b.length+" dropped.","ok"),i()}).catch(d=>a(d.message))}let u=({value:p,optional:d,onChange:f,label:b})=>t("select",{"aria-label":b,value:p,onChange:g=>f(g.currentTarget.value),children:[d?t("option",{value:"",children:d}):null,r.map(g=>t("option",{value:g,children:g},g))]});return t(ke,{title:"Indexes on "+Ve(e.name),wide:!0,autoFocus:!1,actions:t(W,{children:[t(A,{onClick:n,children:"Cancel"}),t(A,{kind:"primary",onClick:c,children:"Apply"})]}),children:[t(fe,{children:"An index makes lookups on its fields fast. Unique also refuses a second record with the same values. createdAt and updatedAt are always indexed. Removing a row drops that index."}),t(Xe,{rows:o,onChange:p=>{s(p),a(null)},blank:()=>({first:r[0],second:"",third:"",unique:!1,order:"asc"}),addLabel:"+ Add index",removeTitle:"Drop this index",renderRow:(p,d)=>t(W,{children:[t(u,{label:"First field",value:p.first,onChange:f=>d({...p,first:f})}),t(u,{label:"Then",value:p.second,optional:"then\u2026 (optional)",onChange:f=>d({...p,second:f})}),t(u,{label:"Then",value:p.third,optional:"then\u2026 (optional)",onChange:f=>d({...p,third:f})}),t("label",{class:"check",children:[t("input",{type:"checkbox",checked:p.unique,onChange:f=>d({...p,unique:f.currentTarget.checked})})," Unique"]}),t("select",{"aria-label":"Order",value:p.order,onChange:f=>d({...p,order:f.currentTarget.value}),children:[t("option",{value:"asc",children:"Ascending"}),t("option",{value:"desc",children:"Descending"})]})]})}),l?t(J,{kind:"bad",style:"margin-top:12px",children:l}):null]})}var jc=[{value:null,label:"No access to the manager",sub:"Signs in to your app only. Most people."},{value:"readonly",label:"Can look, not change",sub:"Opens this manager and sees everything \u2014 every write is refused. For a client, or support."},{value:"full",label:"Full admin",sub:"Everything, here and in your app: every rule lets them through."}];function Wc(e){return e==="full"?"admin":e==="readonly"?"read-only admin":null}var zt=50;function Pe(e){return typeof e.username=="string"&&e.username?e.username:typeof e.email=="string"&&e.email?e.email:"Someone with no name"}function Ss({params:e}){let{features:n}=te(),[i,r]=m(null),[o,s]=m([]),[l,a]=m([]),[c,u]=m(""),[p,d]=m(""),[f,b]=m(""),[g,v]=m(0),y=ae(null),T=e[0]||"";function w(){let k=new URLSearchParams({limit:String(zt),skip:String(g)});p.trim()&&k.set("q",p.trim()),f&&k.set("status",f),S("GET","/admin/users?"+k.toString()).then(r).catch(O)}function x(){n.roles&&S("GET","/admin/roles").then(k=>s((k.roles||[]).map(E=>E.name))).catch(()=>s([]))}j(()=>{x(),S("GET","/admin/schema").then(k=>a((k.tables||[]).map(E=>E.name))).catch(()=>a([]))},[]),j(w,[p,f,g]),j(()=>{y.current&&clearTimeout(y.current),y.current=setTimeout(()=>{v(0),d(c)},250)},[c]);let R=i?i.columns:[];function C(){ge(k=>t(ws,{t:{name:"_User",columns:(i?i.columns:[]).concat([{name:"username",type:"String"},{name:"email",type:"String"}])},names:l,close:k,onAdded:w}))}let _=!!(p.trim()||f),N=T==="new"||T==="invite";return t(ce,{title:"Users",subtitle:"The people who can sign in to this backend.",children:[t(V,{children:[t(B,{kind:"primary",onClick:()=>Z("users","new"),children:"Add user"}),n.email?t(B,{onClick:()=>Z("users","invite"),children:"Invite by email"}):null,t(B,{onClick:C,children:"Add a field"}),t(me,{}),t("input",{type:"search",class:"users-search","aria-label":"Search people",placeholder:"Search by name or email\u2026",value:c,onInput:k=>u(k.currentTarget.value)}),t("select",{"aria-label":"Show",value:f,onChange:k=>{v(0),b(k.currentTarget.value)},children:[t("option",{value:"",children:"Everyone"}),t("option",{value:"disabled",children:"Disabled"}),t("option",{value:"unverified",children:"Email not verified"}),t("option",{value:"admins",children:"Backend admins"})]})]}),t(G,{}),i?t(ye,{columns:["Person","Verified","Roles","Signed in","Joined"].concat(R.map(k=>k.name)),rows:i.users,empty:_?t(oe,{icon:"\u{1F50D}",children:"Nobody matches."}):t(oe,{icon:"\u{1F464}",action:{label:"Add the first person",onClick:()=>Z("users","new")},children:"No users yet. Add someone, or let people sign up from your app."}),renderRow:k=>t("tr",{class:"clickable"+(k.objectId===T?" hit":"")+(k.disabled?" revoked":""),onClick:()=>Z("users",k.objectId),children:[t("td",{children:t("div",{class:"person",children:[t("span",{class:"avatar","aria-hidden":"true",children:Pe(k).charAt(0).toUpperCase()}),t("span",{class:"person-text",children:[t("b",{children:Pe(k)}),k.disabled?t(W,{children:[" ",t(K,{kind:"bad",children:"disabled"})]}):null,k.adminAccess?t(W,{children:[" ",t(K,{kind:k.adminAccess==="full"?"accent":"warn",children:Wc(k.adminAccess)})]}):null,k.email&&k.email!==k.username?t("span",{class:"hint",children:k.email}):null]})]})}),t("td",{children:k.emailVerified?t(K,{kind:"ok",children:"verified"}):k.email?t(K,{children:"not yet"}):t("span",{class:"hint",children:"no email"})}),t("td",{children:k.roles.map(E=>t(K,{kind:"accent",children:E},E))}),t("td",{title:k.lastSessionAt?"Newest sign-in still active: "+he(k.lastSessionAt):void 0,children:k.sessions?je(k.lastSessionAt)+(k.sessions>1?" \xB7 "+k.sessions+" devices":""):t("span",{class:"hint",children:"not signed in"})}),t("td",{title:he(k.createdAt),children:je(k.createdAt)}),R.map(E=>t("td",{children:Mn(k[E.name],E.type)},E.name))]},k.objectId)}):null,i&&i.total>zt?t(V,{style:"margin-top:8px",children:[t(M,{children:[g+1,"\u2013",Math.min(g+zt,i.total)," of ",i.total]}),t(A,{tiny:!0,disabled:g===0,onClick:()=>v(Math.max(0,g-zt)),children:"Previous"}),t(A,{tiny:!0,disabled:g+zt>=i.total,onClick:()=>v(g+zt),children:"Next"})]}):null,N?t(Uc,{invite:T==="invite",columns:R,roleNames:o,onClose:()=>Z("users"),onCreated:k=>{w(),Z("users",k)}},T):T?t(Kc,{id:T,columns:R,roleNames:o,onChanged:w,onClose:()=>Z("users")},T):null]})}function zn(e=n=>Math.floor(Math.random()*n)){let n="abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ23456789",i=[];for(let r=0;r<4;r++){let o="";for(let s=0;s<4;s++)o+=n.charAt(e(n.length));i.push(o)}return i.join("-")}function Jn(e){let n=typeof crypto<"u"?crypto:null;if(n&&n.getRandomValues){let i=new Uint32Array(1);return n.getRandomValues(i),i[0]%e}return Math.floor(Math.random()*e)}function Ts({columns:e,raw:n,onChange:i}){return e.length?t(W,{children:e.map(r=>t("div",{class:"field",children:[t("span",{class:"field-head",children:[t("b",{children:r.name})," ",t("span",{class:"chip type",children:r.type})]}),t(Vt,{col:r,raw:n[r.name]!==void 0?n[r.name]:We(r,void 0),onChange:o=>i(r.name,o)})]},r.name))}):null}function xs(e,n,i){let r={};for(let o of e){if(n[o.name]===void 0)continue;let s=ze(o,n[o.name]);i&&JSON.stringify(We(o,i[o.name]))===JSON.stringify(n[o.name])||(r[o.name]=s===void 0?null:o.type==="Date"?{__type:"Date",iso:s}:s)}return r}function Uc({invite:e,columns:n,roleNames:i,onClose:r,onCreated:o}){let{features:s,whoami:l}=te(),[a,c]=m(""),[u,p]=m(""),[d,f]=m(""),[b,g]=m(!1),[v,y]=m([]),[T,w]=m({}),[x,R]=m(!1),[C,_]=m(null),[N,k]=m(null),[E,P]=m(!1),U=l&&l.accountColumns||{};j(()=>{e&&S("GET","/auth/providers").then(q=>_(!!(q.magicLink&&q.magicLink.enabled))).catch(()=>_(null))},[e]);let X=!E&&a.trim()!==""&&(e?u.trim()!==""&&C!==!1:d!=="");function H(){let q;try{q=xs(n,T,null)}catch(L){k(L.message);return}P(!0),k(null);let h={username:a.trim(),email:u.trim()||void 0,roles:v,properties:q};e?h.invite=!0:(h.password=d,h.sendVerification=x),S("POST","/admin/users",h).then(L=>{P(!1),e&&!L.invited?D(Pe(h)+" was added, but the invitation did not go: "+(L.inviteError||"the mail failed."),"bad"):e?D("Invitation sent to "+u.trim()+".","ok"):x&&L.verificationSent===!1?D("Added. The verification email did not go: "+(L.verificationError||"the mail failed."),"bad"):D(Pe(h)+" added.","ok"),o(L.objectId)}).catch(L=>{P(!1),k(L.message)})}return t(Se,{title:e?"Invite someone by email":"Add a user",subtitle:e?"They get a sign-in link and choose how to sign in from then on.":void 0,onClose:r,footer:t(W,{children:[t(A,{onClick:r,children:"Cancel"}),t(B,{kind:"primary",disabled:!X,onClick:H,children:e?"Send invitation":"Add user"})]}),children:[e&&C===!1?t(W,{children:[t(J,{kind:"warn",children:["An invitation is a magic sign-in link, and magic links are switched off on this backend."," ",t("a",{href:"#/signin",onClick:()=>Z("signin"),children:"Turn them on under Sign-in"}),", then come back."]}),t(G,{})]}):null,t("div",{class:"form-grid",onInput:()=>k(null),children:[t($,{label:"Username",children:t("input",{type:"text",autoFocus:!0,placeholder:"How they sign in \u2014 e.g. ann",value:a,onInput:q=>c(q.currentTarget.value)})}),t($,{label:e?"Email":"Email (optional)",children:t("input",{type:"email",placeholder:"ann@example.com",value:u,onInput:q=>p(q.currentTarget.value)})}),e?null:t("div",{class:"field",children:[t("span",{class:"field-head",title:U.password,children:t("b",{children:"Password"})}),t(V,{children:[t("input",{type:b?"text":"password","aria-label":"Password",autoComplete:"new-password",value:d,style:"flex:1",onInput:q=>f(q.currentTarget.value)}),t(A,{tiny:!0,onClick:()=>{f(zn(Jn)),g(!0)},children:"Generate"}),t(A,{tiny:!0,onClick:()=>g(!b),children:b?"Hide":"Show"})]}),t(M,{children:"Stored hashed. Give it to them yourself \u2014 it is not emailed."})]}),s.roles?t("div",{class:"field",children:[t("span",{class:"field-head",children:t("b",{children:"Roles"})}),t(Ie,{items:v,onChange:y,suggest:q=>Promise.resolve(i.filter(h=>h.toLowerCase().indexOf(q.trim().toLowerCase())!==-1)),placeholder:i.length?"Add a role\u2026":"No roles yet \u2014 make them on the Roles page"})]}):null,t(Ts,{columns:n,raw:T,onChange:(q,h)=>w({...T,[q]:h})}),!e&&s.email?t(Fe,{checked:x,disabled:!u.trim(),onChange:R,children:"Send them an email to verify their address"}):null]}),N?t(J,{kind:"bad",style:"margin-top:12px",children:N}):null]})}function Kc({id:e,columns:n,roleNames:i,onChanged:r,onClose:o}){let{features:s,whoami:l}=te(),a=l&&l.accountColumns||{},[c,u]=m(null),[p,d]=m(!1),[f,b]=m(null),[g,v]=m(""),[y,T]=m(""),[w,x]=m({}),[R,C]=m(""),[_,N]=m(!1),[k,E]=m(null),[P,U]=m(!1);function X(z){u(z),v(z.username||""),T(z.email||"");let de={};n.forEach(ue=>de[ue.name]=We(ue,z[ue.name])),x(de)}function H(){S("GET","/admin/users/"+I(e)).then(z=>X(z.user)).catch(z=>{z.status===404?d(!0):O(z)}),S("GET","/admin/users/"+I(e)+"/identities").then(z=>b(z.identities||[])).catch(()=>b([]))}j(H,[e,n.map(z=>z.name).join()]);function q(z,de){return U(!0),S("PUT","/admin/users/"+I(e),z).then(ue=>{U(!1),X(ue.user),r(),D(de+(ue.sessionsRevoked?" Signed out of "+ue.sessionsRevoked+" session"+(ue.sessionsRevoked===1?"":"s")+".":""),"ok")}).catch(ue=>{U(!1),E(ue.message)})}if(p)return t(Se,{title:"Nobody here",onClose:o,children:t(oe,{icon:"\u{1F464}",children:"This person is not on this backend any more."})});if(!c)return t(Se,{title:"Loading\u2026",onClose:o,children:t(M,{children:"Loading\u2026"})});let h=Pe(c);function L(){if(!c)return;let z;try{z=xs(n,w,c)}catch(ue){E(ue.message);return}let de={};if(g.trim()!==(c.username||"")&&(de.username=g.trim()),y.trim()!==(c.email||"")&&(de.email=y.trim()),Object.keys(z).length&&(de.properties=z),!Object.keys(de).length){D("Nothing has changed.","ok");return}q(de,"Saved.")}function Q(z){if(!c)return;let de=z.filter(Oe=>c.roles.indexOf(Oe)===-1),ue=c.roles.filter(Oe=>z.indexOf(Oe)===-1),Be=de.map(Oe=>S("POST","/admin/roles/"+I(Oe)+"/users",{userId:e})).concat(ue.map(Oe=>S("DELETE","/admin/roles/"+I(Oe)+"/users/"+I(e))));Promise.all(Be).then(()=>{D(de.length?h+" is now in "+de.join(", ")+".":h+" is no longer in "+ue.join(", ")+".","ok"),H(),r()}).catch(O)}function se(z){!c||z===c.adminAccess||(z==="full"?we("Make "+h+" a full admin",h+" can then open this manager and change anything on it, and every rule in your app lets them through. Only another full admin can take it back.",()=>q({adminAccess:"full"},h+" is now a full admin."),"Make full admin"):z==="readonly"?q({adminAccess:"readonly"},h+" can now open the manager, and change nothing."):q({adminAccess:null},h+" can no longer open the manager."))}function Me(z){z?we("Disable sign-in for "+h,h+" is signed out everywhere and cannot sign in again \u2014 by password, sign-in link or provider \u2014 until you switch this back. Their account, records and roles stay as they are.",()=>q({disabled:!0},h+" can no longer sign in."),"Disable sign-in"):q({disabled:!1},h+" can sign in again.")}function Ae(){we("Sign "+h+" out everywhere","Every device "+h+" is signed in on is signed out. They can sign straight back in.",()=>S("DELETE","/admin/users/"+I(e)+"/sessions").then(z=>{D("Signed out of "+z.sessionsRevoked+" session"+(z.sessionsRevoked===1?"":"s")+".","ok"),H(),r()}).catch(O),"Sign out everywhere")}function le(){!c||!c.email||S("POST","/requestPasswordReset",{email:c.email}).then(()=>D("A reset link was requested for "+c.email+". That endpoint answers the same whether or not the mail went; the Email page shows sends.","ok")).catch(O)}function ie(){S("DELETE","/admin/users/"+I(e)).then(()=>{D(h+" deleted.","ok"),r(),o()}).catch(O)}return t(Se,{title:h,subtitle:c.email&&c.email!==h?c.email:void 0,wide:!0,onClose:o,footer:t(W,{children:[t(A,{onClick:()=>Ge(c.objectId).then(z=>D(z?"Id copied.":"The browser refused the clipboard.",z?"ok":"bad")),children:"Copy id"}),t(me,{}),t(A,{onClick:o,children:"Close"})]}),children:t("div",{onInput:()=>E(null),children:[c.disabled?t(W,{children:[t(J,{kind:"bad",children:["Sign-in is disabled for ",h,"."]}),t(G,{})]}):null,t("h3",{class:"drawer-section",children:"Details"}),t("div",{class:"form-grid",children:[t($,{label:"Username",children:t("input",{type:"text",title:a.username,value:g,onInput:z=>v(z.currentTarget.value)})}),t($,{label:"Email",children:t("input",{type:"email",title:a.email,value:y,onInput:z=>T(z.currentTarget.value)})}),t(Ts,{columns:n,raw:w,onChange:(z,de)=>x({...w,[z]:de})})]}),t(V,{style:"margin-top:8px",children:[t(B,{kind:"primary",disabled:P,onClick:L,children:"Save details"}),t("span",{title:a.emailVerified,children:t(Fe,{checked:c.emailVerified,disabled:P||!c.email,onChange:z=>q({emailVerified:z},z?"Marked verified.":"Marked not verified."),children:"Email verified"})})]}),k?t(J,{kind:"bad",style:"margin-top:12px",children:k}):null,s.roles?t(W,{children:[t("h3",{class:"drawer-section",children:"Roles"}),t(Ie,{id:"person-roles",items:c.roles,onChange:Q,suggest:z=>Promise.resolve(i.filter(de=>de.toLowerCase().indexOf(z.trim().toLowerCase())!==-1)),placeholder:i.length?"Add a role\u2026":"No roles yet \u2014 make them on the Roles page"})]}):null,t("h3",{class:"drawer-section",children:"Backend access"}),l&&l.person&&l.person.id===e?t(J,{style:"margin-bottom:8px",children:"This is you. Another full admin can change your access; you cannot change your own."}):null,t("div",{class:"tiles access-tiles",role:"radiogroup","aria-label":"Backend access",title:a.adminAccess,children:jc.map(z=>{let de=(c.adminAccess||null)===z.value,ue=!!(l&&l.person&&l.person.id===e);return t("label",{class:"tile"+(de?" on":""),children:[t("input",{type:"radio",name:"admin-access",value:z.value||"",checked:de,disabled:P||ue,onChange:()=>se(z.value)}),t("b",{children:z.label}),t("span",{class:"sub",children:z.sub})]},String(z.value))})}),t("h3",{class:"drawer-section",children:"Sign-in"}),t("p",{class:"sub",children:[c.hasPassword?"Signs in with a password":"No password",f&&f.length?" and "+f.map(z=>z.displayName).join(", "):"","."]}),f&&f.length?t(ye,{columns:["Signs in with","Address","Last used"],rows:f,renderRow:z=>t("tr",{children:[t("td",{children:z.displayName}),t("td",{children:z.email||""}),t("td",{title:he(z.lastLoginAt),children:je(z.lastLoginAt)})]},z.objectId)}):null,t("div",{class:"field",style:"margin-top:8px",children:[t("span",{class:"field-head",title:a.password,children:t("b",{children:"Set a new password"})}),t(V,{children:[t("input",{type:_?"text":"password","aria-label":"New password",autoComplete:"new-password",value:R,style:"flex:1",onInput:z=>C(z.currentTarget.value)}),t(A,{tiny:!0,onClick:()=>{C(zn(Jn)),N(!0)},children:"Generate"}),t(B,{tiny:!0,kind:"primary",disabled:P||!R,onClick:()=>q({password:R},"Password changed.").then(()=>C("")),children:"Set password"})]}),t(M,{children:"Setting one signs them out everywhere. Give it to them yourself \u2014 it is not emailed."})]}),s.email&&c.email?t(V,{style:"margin-top:8px",children:t(B,{tiny:!0,onClick:le,children:"Email them a reset link"})}):null,t("h3",{class:"drawer-section",children:"Sessions"}),t(V,{children:[t("span",{children:c.sessions?"Signed in on "+c.sessions+" device"+(c.sessions===1?"":"s")+" \xB7 newest "+je(c.lastSessionAt):"Not signed in anywhere."}),t(me,{}),t(B,{tiny:!0,disabled:!c.sessions,onClick:Ae,children:"Sign out everywhere"})]}),t("h3",{class:"drawer-section",children:"Status"}),t("span",{title:a.disabled,children:t(Fe,{checked:c.disabled,disabled:P,onChange:Me,children:"Disable sign-in"})}),t(M,{children:"Keeps the account, its records and roles. Signs them out, and refuses every way back in until switched off."}),t(G,{h:16}),t($e,{children:t(Ze,{label:"Delete user",why:"Removes the account, signs them out everywhere and takes them out of every role. Records they made stay.",title:"Delete "+h,warning:"This removes "+h+" for good. To stop them signing in but keep the account, use Disable sign-in instead.",expected:c.username||h,onConfirm:ie})})]})})}var rr=[["find","list"],["get","open"],["create","create"],["update","change"],["delete","delete"]],Rs=[["upload","upload"],["read","open"],["delete","delete"]];function or(e,n){if(e===void 0)return!1;let i="role:"+n;return Array.isArray(e)?e.indexOf(i)!==-1:e===i}function ir(e,n,i){return e?n.map(([r])=>r).filter(r=>or(e[r],i)):[]}function Yn(e,n){if(!e)return[];let i=[],r=ir(e.defaults&&e.defaults.permissions,rr,n);r.length&&i.push({kind:"defaults",ops:r});let o=e.collections||{};for(let a of Object.keys(o).sort()){let c=o[a],u=ir(c&&c.permissions,rr,n);u.length&&i.push({kind:"collection",name:a,ops:u})}let s=e.functions||{};for(let a of Object.keys(s).sort()){let c=s[a];c&&or(c.call,n)&&i.push({kind:"function",name:a,ops:["call"]})}let l=ir(e.files,Rs,n);return l.length&&i.push({kind:"files",ops:l}),or(e.signup,n)&&i.push({kind:"signup",ops:["signup"]}),i}function Es(e){return e.reduce((n,i)=>n+i.ops.length,0)}function Vc(e,n){let r=(e==="files"?Rs:rr).find(([o])=>o===n);return r?r[1]:n==="call"?"call":n==="signup"?"sign up":n}function Gc(e){switch(e.kind){case"collection":return e.name||"";case"defaults":return"Every collection without a rule of its own";case"function":return"The "+e.name+" function";case"files":return"Files";case"signup":return"Signing up"}}function sr(e){return Gc(e)+": "+e.ops.map(n=>Vc(e.kind,n)).join(", ")}function _s(e){let n={defaults:!1,collections:0,functions:0,other:0};for(let i of e)i.kind==="defaults"?n.defaults=!0:i.kind==="collection"?n.collections++:i.kind==="function"?n.functions++:n.other++;return n}function As(e){return e.kind==="collection"&&e.name?"#/permissions/"+encodeURIComponent(e.name):e.kind==="function"?"#/permissions/functions":"#/permissions"}var qc=/^[a-zA-Z0-9_-]+$/,Is=280,Ps=/^[^\\s@,;]+@[^\\s@,;]+\\.[^\\s@,;]+$/;function $c(e,n){let i=e.trim();if(!i)return null;let r=Array.from(new Set(i.replace(/[a-zA-Z0-9_-]/g,"").split("")));if(r.length)return"A role name cannot contain "+r.map(l=>l===" "?"a space":"\u201C"+l+"\u201D").join(", ")+". Use letters, digits, - and _.";if(!qc.test(i))return"Use letters, digits, - and _.";let o=n.find(s=>s.toLowerCase()===i.toLowerCase());return o?"There is already a role called "+o+".":null}function zc(e,n){if(!e)return"No one yet";let i=n.slice(0,3),r=e-i.length;return i.length?r<=0?i.length===1?i[0]:i.slice(0,-1).join(", ")+" and "+i[i.length-1]:i.join(", ")+" and "+r+" more":e===1?"1 person":e+" people"}function Jc(e){if(!e.length)return"Nothing yet";let n=_s(e),i=[];return n.defaults&&i.push("every collection by default"),n.collections&&i.push(n.collections+(n.collections===1?" collection":" collections")),n.functions&&i.push(n.functions+(n.functions===1?" function":" functions")),n.other&&i.push(n.other+(n.other===1?" other rule":" other rules")),i.join(" \xB7 ")}function Ls({params:e}){let[n,i]=m(null),[r,o]=m(null),s=e[0]||"";function l(){S("GET","/admin/roles").then(u=>i(u.roles||[])).catch(O),S("GET","/admin/permissions").then(u=>o(u.config||{})).catch(()=>o({}))}j(l,[]);function a(){ge(u=>t(Yc,{taken:(n||[]).map(p=>p.name),close:u,onCreated:p=>{l(),Z("roles",p)}}))}let c=n&&s&&n.find(u=>u.name===s)||null;return t(ce,{title:"Roles",subtitle:"A role is a group of people. Permissions give a role something to do; everyone in it can then do that.",children:[t(V,{children:[t(B,{kind:"primary",onClick:a,children:"New role"}),t(A,{onClick:l,children:"Refresh"})]}),t(G,{}),n&&s&&!c?t(J,{kind:"warn",children:["There is no role called ",s,". It may have been deleted."]}):null,n?t(ye,{columns:["Role","Members","Used in","Created"],rows:n,empty:t(oe,{icon:"\u{1F465}",action:{label:"New role",onClick:a},children:"No roles yet. A role groups people so a permission can name all of them at once."}),renderRow:u=>{let p=(u.users||[]).length,d=u.names||[];return t("tr",{class:"clickable"+(u.name===s?" hit":""),onClick:()=>Z("roles",u.name),children:[t("td",{children:t("span",{class:"person-text",children:[t("b",{children:u.name}),u.description?t("span",{class:"hint",children:u.description}):null]})}),t("td",{children:t("div",{class:"person",children:[d.length?t("span",{class:"avatars","aria-hidden":"true",children:d.map(f=>t("span",{class:"avatar",children:f.charAt(0).toUpperCase()},f))}):null,t("span",{class:p?void 0:"hint",children:[p?t("b",{children:p}):null,p?" \xB7 ":null,zc(p,d)]})]})}),t("td",{class:r&&!Yn(r,u.name).length?"hint":void 0,children:r?Jc(Yn(r,u.name)):""}),t("td",{title:he(u.createdAt),children:u.createdAt?je(u.createdAt):""})]},u.name)}}):null,c?t(Xc,{role:c,config:r,onChanged:l,onClose:()=>Z("roles")},c.name):null]})}function Yc({taken:e,close:n,onCreated:i}){let[r,o]=m(""),[s,l]=m(""),[a,c]=m(!1),u=$c(r,e),p=!a&&r.trim()!==""&&!u;function d(){p&&(c(!0),S("POST","/admin/roles",{name:r.trim(),description:s.trim()||void 0}).then(()=>{n(),D("Role "+r.trim()+" created.","ok"),i(r.trim())}).catch(f=>{c(!1),O(f)}))}return t(ke,{title:"New role",actions:t(W,{children:[t(A,{onClick:n,children:"Cancel"}),t(A,{kind:"primary",disabled:!p,onClick:d,children:"Create role"})]}),children:[t($,{label:"Name",children:t("input",{type:"text","aria-label":"Role name",placeholder:"editors",value:r,"aria-invalid":u?"true":void 0,onInput:f=>o(f.currentTarget.value),onKeyDown:f=>{f.key==="Enter"&&d()}})}),u?t(J,{kind:"bad",children:u}):t(M,{children:"Letters, digits, - and _. Permissions will name it, so pick the word you would say: editors, billing, support."}),t($,{label:"What it is for (optional)",children:t("input",{type:"text","aria-label":"What it is for",placeholder:"People who can change the catalogue",maxLength:Is,value:s,onInput:f=>l(f.currentTarget.value)})})]})}function Xc({role:e,config:n,onChanged:i,onClose:r}){let{readonly:o,features:s}=te(),[l,a]=m(null),[c,u]=m(e.description||""),[p,d]=m(""),[f,b]=m(null),[g,v]=m(!1),[y,T]=m(null),w=Yn(n,e.name),x=Es(w),R=!!s.email&&y===!0;function C(){S("GET","/admin/users?limit=200&role="+I(e.name)).then(h=>a(h.users||[])).catch(O)}j(C,[e.name]),j(()=>{s.email&&S("GET","/auth/providers").then(h=>T(!!(h.magicLink&&h.magicLink.enabled))).catch(()=>T(null))},[]);function _(){C(),i()}let N=(l||[]).map(h=>h.objectId);async function k(h){return((await S("GET","/admin/users?limit=20&q="+I(h.trim()))).users||[]).filter(Q=>N.indexOf(Q.objectId)===-1)}function E(h){h&&S("POST","/admin/roles/"+I(e.name)+"/users",{userId:h.objectId}).then(()=>{D(Pe(h)+" added to "+e.name+".","ok"),_()}).catch(O)}function P(h){let L=Pe(h);we("Remove "+L+" from "+e.name+"?","Their account stays. They stop being able to do what "+e.name+" can do.",()=>{S("DELETE","/admin/roles/"+I(e.name)+"/users/"+I(h.objectId)).then(()=>{D(L+" removed from "+e.name+".","ok"),_()}).catch(O)},"Remove")}function U(){S("PUT","/admin/roles/"+I(e.name),{description:c.trim()}).then(()=>{D("Saved.","ok"),i()}).catch(O)}async function X(h){let L=h.split(/[\\s,;]+/).map(ue=>ue.trim()).filter(Boolean);if(!L.length)return;v(!0);let Q=[],se=[],Me=[],Ae=[],le=[],ie=new Set;for(let ue of L){let Be=ue.toLowerCase();if(!ie.has(Be)){if(ie.add(Be),!Ps.test(Be)){Ae.push(ue);continue}try{let Qe=((await S("GET","/admin/users?limit=20&q="+I(Be))).users||[]).find(st=>(st.email||"").toLowerCase()===Be);if(Qe)N.indexOf(Qe.objectId)!==-1?se.push(Pe(Qe)):(await S("POST","/admin/roles/"+I(e.name)+"/users",{userId:Qe.objectId}),Q.push(Pe(Qe)));else if(!R)le.push(ue+" (no one has that address, and invitations are off)");else{let st=await S("POST","/admin/users",{username:Be,email:Be,invite:!0,roles:[e.name]});st.invited?Me.push(Be):le.push(ue+" (added, but the invitation did not go: "+(st.inviteError||"the mail failed")+")")}}catch(Oe){le.push(ue+" ("+Oe.message+")")}}}v(!1);let z=[],de=[];de.push(Q.length+" added"),(R||Me.length)&&de.push(Me.length+" invited"),z.push(de.join(", ")+"."),se.length&&z.push("Already in "+e.name+": "+se.join(", ")+"."),Ae.length&&z.push("Not an email address: "+Ae.join(", ")+".");for(let ue of le)z.push("Not added: "+ue+".");b(z),!le.length&&!Ae.length&&d(""),D(z[0],le.length?"bad":"ok"),_()}let H=h=>h===1?"1 person":h+" people",q=(x?x+(x===1?" rule names ":" rules name ")+e.name+" ("+w.map(sr).join("; ")+") and will stop matching anyone.":"No rule names "+e.name+", so deleting it changes no permission.")+" "+(l&&l.length?H(l.length)+" will no longer be in it; their accounts stay.":"No one is in it.");return t(Se,{title:e.name,subtitle:e.description||"A role",onClose:r,wide:!0,children:[t("div",{class:"drawer-section",children:["Members",l?" ("+l.length+")":""]}),l?t(ye,{class:"members-table",columns:["Person","Email","Joined",""],rows:l,empty:t(oe,{icon:"\u{1F464}",children:["No one is in ",e.name," yet."]}),renderRow:h=>t("tr",{"data-member":Pe(h),children:[t("td",{children:t("div",{class:"person",children:[t("span",{class:"avatar","aria-hidden":"true",children:Pe(h).charAt(0).toUpperCase()}),t("b",{children:Pe(h)}),h.disabled?t(K,{kind:"bad",children:"disabled"}):null]})}),t("td",{children:h.email||t("span",{class:"hint",children:"no email"})}),t("td",{title:he(h.createdAt),children:je(h.createdAt)}),t("td",{children:o?null:t("button",{type:"button",class:"chip-x","aria-label":"Remove "+Pe(h)+" from "+e.name,onClick:()=>P(h),children:"\u2715"})})]},h.objectId)}):null,o?null:t(W,{children:[t($,{label:"Add people",children:t(Ce,{id:"role-add-person",fetch:k,label:Pe,detail:h=>h.email&&h.email!==h.username?h.email:"",keyOf:h=>h.objectId,value:null,openOnFocus:!1,onPick:E,placeholder:"Type a name or an email\u2026",emptyText:"No one else matches.",onCreate:R?X:void 0,createWhen:h=>Ps.test(h.toLowerCase()),createLabel:h=>"Invite "+h+" to "+e.name+" by email"})}),t($,{label:"Add several by email",children:t("textarea",{"aria-label":"Email addresses",rows:3,placeholder:\`ann@example.com, bob@example.com
65040
+ (one per line, or separated by commas)\`,value:p,onInput:h=>d(h.currentTarget.value)})}),t(V,{children:[t(B,{tiny:!0,disabled:g||!p.trim(),onClick:()=>X(p),children:g?"Adding\u2026":"Add them"}),t(M,{children:R?"Someone with an account is added. Anyone else is sent an invitation and joins "+e.name+" when the account is made.":s.email?"Someone with an account is added. Invitations are off: turn on sign-in links under Sign-in to invite people who have no account.":"Someone with an account is added. Email is not set up, so people without an account cannot be invited."})]}),f?t(J,{kind:f.length>1&&f.some(h=>h.indexOf("Not ")===0)?"warn":"",style:"margin-top:8px",children:t("span",{class:"bulk-report",children:f.map(h=>t("span",{children:h},h))})}):null]}),t("div",{class:"drawer-section",children:["What ",e.name," can do"]}),n?w.length?t("ul",{class:"role-uses",children:w.map(h=>t("li",{children:t("a",{href:As(h),children:sr(h)})},h.kind+":"+(h.name||"")))}):t("p",{class:"hint",children:["Nothing yet \u2014 ",t("a",{href:"#/permissions",children:["give ",e.name," something in Permissions \u2192"]})]}):null,t(M,{children:["A record can also be shared with ",e.name," on its own (Collections \u2192 a record \u2192 Who can see this); those are not counted here."]}),t("div",{class:"drawer-section",children:"About"}),t($,{label:"What it is for",children:t("input",{type:"text","aria-label":"What it is for",maxLength:Is,disabled:o,value:c,placeholder:"Say who belongs here",onInput:h=>u(h.currentTarget.value)})}),t(V,{children:[t(B,{tiny:!0,disabled:c.trim()===(e.description||""),onClick:U,children:"Save"}),t(me,{}),t(M,{children:["Created ",he(e.createdAt)]})]}),t($e,{children:t(Ze,{label:"Delete role",why:q,title:"Delete "+e.name,warning:q,expected:e.name,onConfirm:()=>{S("DELETE","/admin/roles/"+I(e.name)).then(()=>{D("Role "+e.name+" deleted.","ok"),i(),Z("roles")}).catch(O)}})})]})}var Xn=[{id:"google",label:"Google",line:"Sign in with a Google account",icon:"G"},{id:"github",label:"GitHub",line:"Sign in with a GitHub account",icon:"\u2325"},{id:"oidc",label:"Another OpenID Connect provider",line:"Okta, Auth0, Keycloak, Entra ID, GitLab, Authentik\u2026",icon:"\u26BF"}],Zc=[{id:"identity",scopes:["openid","email"],label:"Who they are and their email address (needed to sign in)",locked:!0},{id:"profile",scopes:["profile"],label:"Their name and profile picture",locked:!1},{id:"github-identity",scopes:["read:user","user:email"],label:"Their GitHub account and its email address (needed to sign in)",locked:!0}];function bn(e){let n=[],i=new Set;for(let r of Zc){let o=r.scopes.filter(s=>e.includes(s));o.length&&(n.push({id:r.id,scopes:o,label:r.label,locked:r.locked}),o.forEach(s=>i.add(s)))}for(let r of e)i.has(r)||(i.add(r),n.push({id:"raw:"+r,scopes:[r],label:r,locked:!1}));return n}function Qc(e,n,i){let r=new Set;for(let o of n)(o.locked||i.includes(o.id))&&o.scopes.forEach(s=>r.add(s));return e.filter(o=>r.has(o))}var ed=/^[a-z0-9][a-z0-9-]{0,31}$/,td=["exchange","magic-link","callback","start","providers"];function nd(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,32).replace(/-+$/g,"")}function ar(e){return e.idEdited?e.id.trim():e.tile!=="oidc"?e.tile:nd(e.displayName)}function lr(e,n){return e?ed.test(e)?td.includes(e)?"\u201C"+e+"\u201D is a reserved word on this backend. Pick another id.":n.includes(e)?"There is already a provider called "+e+".":null:"An id is lowercase letters, digits and dashes, up to 32 characters, starting with a letter or digit.":"The provider needs an id \u2014 it is the part of the callback URL that names it."}function cr(e,n){let i=bn(n&&n.scopes||[]);return{tile:e,displayName:e==="oidc"?"":n&&n.displayName||e,issuer:e==="oidc"?"":n&&n.issuer||"",id:"",idEdited:!1,clientId:"",clientSecret:"",ticked:i.filter(r=>!r.locked).map(r=>r.id),enabled:!0,allowSignup:!0}}function id(e,n){if(e.kind==="github")return"github";let i=n.google;return i&&e.issuer&&e.issuer===i.issuer?"google":"oidc"}function Ns(e,n){let i=id(e,n),r=e.scopes||[],o=bn(r);return{tile:i,displayName:e.displayName||"",issuer:e.issuer||"",id:e.id,idEdited:!0,clientId:e.clientId||"",clientSecret:"",ticked:o.filter(s=>!s.locked).map(s=>s.id),enabled:e.enabled!==!1,allowSignup:e.allowSignup!==!1}}function dr(e,n,i){return i&&i.scopes&&i.scopes.length?i.scopes:n&&n.scopes||[]}function Ds(e,n,i){let r=dr(e,n,i),o={displayName:e.displayName.trim(),issuer:e.tile==="github"?"":e.issuer.trim(),clientId:e.clientId.trim(),scopes:Qc(r,bn(r),e.ticked),enabled:e.enabled,allowSignup:e.allowSignup};return o.preset=e.tile,e.clientSecret&&(o.clientSecret=e.clientSecret),o}function Os(e,n,i){if(!e.displayName.trim())return"Give the sign-in button a label \u2014 what it says to your users.";if(e.tile==="oidc"){let r=e.issuer.trim();if(!r)return"The issuer URL is where this provider publishes its OpenID settings \u2014 it is on their developer page.";if(!/^https?:\\/\\/[^\\s/]+/.test(r))return"The issuer is an https:// URL, e.g. https://login.example.com."}if(!n){let r=lr(ar(e),i);if(r)return r}return e.clientId.trim()?!n&&!e.clientSecret?"Paste the client secret from step 2.":null:"Paste the client id from step 2."}function Fs(e){return e==="google"?["Open Google Cloud \u2192 APIs & Services \u2192 Credentials.","Create credentials \u2192 OAuth client ID \u2192 application type Web application.","Under Authorised redirect URIs, press Add URI and paste the callback URL above.","Press Create and keep the Client ID and Client secret it shows for step 3."]:e==="github"?["Open GitHub \u2192 Settings \u2192 Developer settings \u2192 OAuth Apps (an OAuth App, not a GitHub App).","Press New OAuth App and give it your app\u2019s name and home page.","Paste the callback URL above into Authorization callback URL, then Register.","Press Generate a new client secret and keep the Client ID and the secret for step 3."]:["In your provider\u2019s admin console make a new application of the OpenID Connect / OAuth 2 web kind.","Paste the callback URL above where it asks for the redirect URI (it may say sign-in redirect or callback).","Allow the authorization code flow; PKCE is used, so a client secret is still required by most providers.","Keep the Client ID and Client secret it shows for step 3."]}function Ms(e){return e.ready?null:e.kind!=="github"&&!(e.issuer||"").trim()?"Step 1: the issuer URL":(e.clientId||"").trim()?e.hasClientSecret?null:"Step 3: the client secret":"Step 3: the client id"}function Zn(e){let n=e.trim();if(!n)return"Type an origin.";if(!/^https?:\\/\\//i.test(n))return"An origin starts with https:// (or http://localhost while you develop) \u2014 e.g. https://"+n.replace(/^\\/+/,"").split("/")[0];let i;try{i=new URL(n)}catch{return"That is not a URL this browser can read."}return i.hostname?i.protocol==="http:"&&!rd(i.hostname)?"Plain http:// is only allowed for localhost \u2014 anywhere else the sign-in result would travel unencrypted. Use https://.":null:"An origin needs a host, e.g. https://app.example.com."}function rd(e){let n=e.toLowerCase();return n==="localhost"||n==="127.0.0.1"||n==="[::1]"||n.endsWith(".localhost")}function Qn(e){try{return new URL(e.trim()).origin}catch{return e.trim()}}function od(e,n){return e?e.replace("{id}",encodeURIComponent(n||"\u2026")):""}function Bs({params:e}){let{readonly:n}=te(),[i,r]=m(null),[o,s]=m(0),l=e[0]||null;function a(){S("GET","/admin/auth").then(g=>{r(g||{}),s(v=>v+1)}).catch(O)}j(()=>{a()},[]);function c(g){Ge(g).then(v=>D(v?"Callback URL copied.":"Could not copy \u2014 select the URL in the table instead.",v?"ok":"bad"))}function u(g){qe("Remove provider",'Users with no password and no other provider will not be able to sign in. Their linked identities are kept, so re-adding "'+g.id+'" restores access.',g.id,()=>{S("DELETE","/admin/auth/providers/"+I(g.id)).then(()=>{D("Provider removed.","ok"),a()}).catch(O)})}let d=(i&&i.config||{}).providers||[],f=l&&l!=="new"&&d.find(g=>g.id===l)||null,b=!!i&&(l==="new"||!!f);return t(ce,{title:"Sign-in",subtitle:"How the people using your app sign in: Google, GitHub or any OpenID Connect provider, and links by email.",children:i?t("div",{children:[i.baseUrl&&i.baseUrl.usedFallback?t(J,{kind:"warn",children:i.baseUrl.warning}):null,t(V,{children:[t(B,{kind:"primary",onClick:()=>Z("signin","new"),children:"Add provider"}),t(A,{onClick:a,children:"Refresh"})]}),t(G,{}),t(ye,{columns:["Provider","Kind","Status","Callback URL to register",""],rows:d,empty:t(oe,{icon:"\u26BF",action:{label:"Add provider",onClick:()=>Z("signin","new")},children:"No sign-in providers yet. Add Google, GitHub or any OpenID Connect provider \u2014 three steps, starting with the URL they will ask you for."}),renderRow:g=>{let v=Ms(g);return t("tr",{children:[t("td",{children:[t("b",{children:g.displayName||g.id}),t("div",{class:"sub",children:g.id})]}),t("td",{children:g.kind==="github"?"GitHub":"OpenID Connect"}),t("td",{children:[g.enabled?t(K,{kind:"ok",children:"enabled"}):t(K,{children:"disabled"}),g.ready?t(K,{kind:"ok",children:"ready"}):t(K,{kind:"warn",title:g.notReadyReason||void 0,children:"not ready"}),v?t("div",{class:"sub step-missing",children:"Missing \u2014 "+v}):null]}),t("td",{children:t("code",{style:"word-break:break-all",children:g.callbackUrl})}),t("td",{class:"actions",children:[t(A,{tiny:!0,onClick:()=>c(g.callbackUrl||""),children:"Copy URL"}),t(B,{tiny:!0,onClick:()=>Z("signin",g.id),children:"Edit"}),t(B,{tiny:!0,kind:"danger",onClick:()=>u(g),children:"Remove"})]})]},g.id)}}),t(sd,{data:i,readonly:n,onSaved:a},o),b?t(ld,{existing:f,presets:i.presets||{},callbackUrlTemplate:i.callbackUrlTemplate,baseUrlWarning:i.baseUrl&&i.baseUrl.usedFallback&&i.baseUrl.warning||null,taken:d.map(g=>g.id),onClose:()=>Z("signin"),onSaved:()=>{a(),Z("signin")}},l||"new"):null]}):null})}function sd({data:e,readonly:n,onSaved:i}){let r=e.config||{},o=r.magicLink||{},[s,l]=m(!!o.enabled),[a,c]=m(String(o.ttlMinutes===void 0?15:o.ttlMinutes)),[u,p]=m(o.allowSignup!==!1),[d,f]=m((r.linking||{}).autoLinkVerifiedEmail!==!1),[b,g]=m(r.redirectAllowList||[]),[v,y]=m(String((r.sessions||{}).idleDays??30));function T(){S("PUT","/admin/auth",{magicLink:{enabled:s,ttlMinutes:Number(a)||15,allowSignup:u},redirectAllowList:b,linking:{autoLinkVerifiedEmail:d},sessions:{idleDays:Number(v)}}).then(()=>{D("Sign-in policy saved.","ok"),i()}).catch(O)}return t("div",{children:[t("h2",{children:"Magic links"}),t(pe,{children:[!e.magicLinkReady&&e.magicLinkNotReadyReason?t(J,{kind:"warn",children:e.magicLinkNotReadyReason}):null,t(re,{checked:s,disabled:n,onChange:l,children:"People may sign in with a link sent to their email"}),t("div",{class:"grid2",style:"margin-top:10px",children:t($,{label:"A link works for (minutes)",children:t("input",{type:"number",min:1,max:1440,value:a,disabled:n,onInput:w=>c(w.currentTarget.value)})})}),t(V,{style:"margin-top:10px",children:t(re,{checked:u,disabled:n,onChange:p,children:"An address nobody has used yet may create an account"})})]}),t("h2",{children:"Where your app lives"}),t(pe,{id:"app-origins",children:[t(fe,{style:"margin:0 0 8px",children:"The web addresses your app is served from, e.g. https://app.example.com. After signing in, a person is only ever sent back to one of these or to this backend itself \u2014 that is what stops the sign-in callback being used to send people (and their fresh session) somewhere else. If your app is on another address than this backend and it is not listed here, every sign-in is refused before it starts."}),t(Ie,{items:b,onChange:g,disabled:n,validate:Zn,normalise:Qn,placeholder:"https://app.example.com",addLabel:"Add address"})]}),t("h2",{children:"Staying signed in"}),t(pe,{id:"session-length",children:[t("div",{class:"grid2",children:t($,{label:"A sign-in ends after this many days unused",children:t("input",{type:"number",min:1,max:3650,value:v,disabled:n,onInput:w=>y(w.currentTarget.value)})})}),t(fe,{style:"margin:8px 0 0",children:"A person stays signed in while they keep using your app. Each visit starts the count of days again. A sign-in left on a lost phone or a shared computer stops working once it has gone unused for this many days. A new number also applies to people who are already signed in."})]}),t("h2",{children:"Account linking"}),t(pe,{children:[t(re,{checked:d,disabled:n,onChange:f,children:"A provider sign-in joins an existing account with the same verified email address"}),t(fe,{style:"margin:8px 0 0",children:"Only a provider-verified address ever matches, and if the local account had never verified its own address, its password and sessions are revoked at the moment of linking \u2014 that is what stops someone registering your address before you do and keeping access afterwards. Every such event is in the audit trail as auth.link.credentials-revoked. Off means such a sign-in is refused rather than linked."})]}),t(V,{style:"margin-top:12px",children:t(B,{kind:"primary",onClick:T,children:"Save policy"})})]})}var ad=[{n:1,label:"Which"},{n:2,label:"Register with them"},{n:3,label:"Paste back"}];function ld({existing:e,presets:n,callbackUrlTemplate:i,baseUrlWarning:r,taken:o,onClose:s,onSaved:l}){let[a,c]=m(e?3:1),[u,p]=m(()=>e?Ns(e,n):cr("google",n.google)),[d,f]=m(!1),[b,g]=m(!1),v=n[u.tile],y=e?e.id:ar(u),T=e&&e.callbackUrl?e.callbackUrl:od(i,y),w=h=>p({...u,...h}),x=o.filter(h=>!e||h!==e.id);function R(h){if(e)return;let L=cr(h,n[h]);p({...L,enabled:u.enabled,allowSignup:u.allowSignup})}let C=(()=>{if(!u.displayName.trim())return"Give the sign-in button a label \u2014 what it says to your users.";if(u.tile==="oidc"){let h=u.issuer.trim();if(!h)return"The issuer URL is where this provider publishes its OpenID settings \u2014 it is on their developer page.";if(!/^https?:\\/\\/[^\\s/]+/.test(h))return"The issuer is an https:// URL, e.g. https://login.example.com."}return null})(),_=e?null:lr(y,x),N=Os(u,e,x);function k(){if(N){D(N,"bad");return}f(!0),S("PUT","/admin/auth/providers/"+I(y),Ds(u,v,e)).then(h=>{let L=h&&h.provider,Q=!!(L&&L.ready);D(Q?(L.displayName||y)+" is ready. Its callback URL is registered? Then people can sign in with it.":"Saved, but not usable yet: "+(L&&L.notReadyReason||"something is missing."),Q?"ok":"bad"),l()}).catch(h=>{f(!1),O(h)})}function E(){Ge(T).then(h=>{h?(g(!0),D("Callback URL copied.","ok"),setTimeout(()=>g(!1),2500)):D("The browser refused the clipboard. Select the URL and copy it by hand.","bad")})}let P=dr(u,v,e),U=bn(P),X=Xn.find(h=>h.id===u.tile)||Xn[0],H=h=>L=>w({[h]:L.currentTarget.value}),q=t(V,{children:[a>1?t(A,{onClick:()=>c(a-1),children:"Back"}):null,t(me,{}),a<3?t(A,{kind:"primary",onClick:()=>c(a+1),disabled:a===1?!!C:!!_,title:a===1?C||void 0:_||void 0,children:"Next"}):t(B,{kind:"primary",onClick:k,disabled:d||!!N,title:N||void 0,children:e?"Save":"Add "+(u.displayName.trim()||"provider")}),t(A,{onClick:s,children:"Cancel"})]});return t(Se,{title:e?"Edit "+(e.displayName||e.id):"Add a way to sign in",onClose:s,wide:!0,footer:q,children:[t("ol",{class:"wizard-steps","aria-label":"Steps",children:ad.map(h=>t("li",{class:h.n===a?"current":h.n<a||e?"done":"",children:t("button",{type:"button",disabled:h.n>a&&!e,onClick:()=>c(h.n),"aria-current":h.n===a?"step":void 0,children:[t("span",{class:"num","aria-hidden":"true",children:h.n}),t("span",{children:h.label})]})},h.n))}),a===1?t("div",{class:"wizard-step",id:"step-which",children:[t("div",{class:"tiles providers",role:"radiogroup","aria-label":"Which provider",children:Xn.map(h=>t("label",{class:"tile"+(u.tile===h.id?" on":""),children:[t("input",{type:"radio",name:"provider-tile",value:h.id,checked:u.tile===h.id,disabled:!!e,"aria-label":h.label,onChange:()=>R(h.id)}),t("b",{children:h.label}),t("span",{class:"sub",children:h.line})]},h.id))}),t("div",{class:"when-body",children:[t($,{label:"What the sign-in button says",children:t("input",{type:"text",id:"wiz-label",placeholder:u.tile==="oidc"?"e.g. Okta, Company SSO":"",value:u.displayName,onInput:H("displayName")})}),u.tile==="oidc"?t($,{label:"Issuer URL",children:t("input",{type:"text",id:"wiz-issuer",placeholder:"https://login.example.com",value:u.issuer,onInput:H("issuer")})}):null,u.tile==="oidc"?t(M,{children:v&&v.note||"Any OpenID Connect issuer works by configuration alone."}):null,C&&(u.displayName||u.issuer)?t(M,{children:C}):null]})]}):null,a===2?t("div",{class:"wizard-step",id:"step-register",children:[r?t(J,{kind:"warn",children:r}):null,t("div",{class:"field",children:[t("span",{class:"field-head",children:t("b",{children:X.label+" will ask for a redirect URL. It is this one:"})}),t("div",{class:"secret-box mono callback-url",id:"wiz-callback","data-callback":T,children:T}),t(V,{style:"margin-top:8px",children:[t(A,{kind:"primary",onClick:E,children:b?"Copied \u2713":"Copy"}),v&&v.consoleUrl?t("a",{class:"btn",href:v.consoleUrl,target:"_blank",rel:"noreferrer",children:"Open "+X.label+"\u2019s console \u2197"}):null]})]}),t("div",{class:"field",children:[t("span",{class:"field-head",children:t("b",{children:"Then, with them"})}),t("ol",{class:"wizard-list",id:"wiz-register-steps",children:Fs(u.tile).map((h,L)=>t("li",{children:h},L))})]}),e?null:t(He,{label:"Advanced: the id in the URL",children:[t($,{label:"Provider id",children:t("input",{type:"text",id:"wiz-id",value:y,onInput:h=>w({id:h.currentTarget.value,idEdited:!0})})}),t(M,{children:_||"It names this provider in the callback URL and in your app. Changing it later means registering a new URL."})]})]}):null,a===3?t("div",{class:"wizard-step",id:"step-paste",children:[t("div",{class:"grid2",children:[t($,{label:"Client id",children:t("input",{type:"text",id:"wiz-client-id",value:u.clientId,onInput:H("clientId")})}),t($,{label:"Client secret",children:t("input",{type:"password",id:"wiz-client-secret",placeholder:e&&e.hasClientSecret?"(unchanged)":"(not set)",value:u.clientSecret,onInput:H("clientSecret")})})]}),t("div",{class:"field",children:[t("span",{class:"field-head",children:[t("b",{children:"What we may read"}),t(M,{children:"The ones sign-in needs are on and cannot be turned off."})]}),t("div",{class:"scope-boxes",id:"wiz-scopes",children:[U.map(h=>t(Fe,{checked:h.locked||u.ticked.includes(h.id),disabled:h.locked,onChange:L=>w({ticked:L?[...u.ticked,h.id]:u.ticked.filter(Q=>Q!==h.id)}),children:t("span",{"data-scopes":h.scopes.join(" "),children:h.label})},h.id)),U.length?null:t(M,{children:"This provider asks for nothing beyond signing in."})]})]}),t("div",{class:"when-body",children:[t(re,{checked:u.allowSignup,onChange:h=>w({allowSignup:h}),children:"Someone new may create an account by signing in this way"}),t(re,{checked:u.enabled,onChange:h=>w({enabled:h}),children:"Shown on the sign-in page"})]}),N&&u.clientId?t(M,{children:N}):null]}):null]})}function Hs(e){if(typeof e=="string")return e;let n=Array.isArray(e.path?.hops)?e.path.hops.length:0;return\`Whoever reaches it (through \${n} link\${n===1?"":"s"})\`}var Pt=["find","get","create","update","delete"],ur="role:";function cd(e){return\`\${ur}\${e}\`}function pr(e){if(!e.startsWith(ur))return null;let n=e.slice(ur.length);return n.length>0?n:null}var mr={inherit:!1,anyone:!1,signedIn:!1,roles:[],unknown:[]},js={...mr,inherit:!0};function fr(e){return e==null?[]:Array.isArray(e)?e:[e]}function Jt(e){if(e==null)return{...js};let n={inherit:!1,anyone:!1,signedIn:!1,roles:[],unknown:[]};for(let i of fr(e)){if(typeof i!="string"){n.unknown.push(i);continue}if(i==="public")n.anyone=!0;else if(i==="authenticated")n.signedIn=!0;else{if(i==="nobody")continue;{let r=pr(i);r!==null?n.roles.includes(r)||n.roles.push(r):n.unknown.push(i)}}}return n}function ei(e){if(e.inherit)return;let n=[];e.anyone&&n.push("public"),e.signedIn&&n.push("authenticated");for(let i of e.roles)n.push(cd(i));for(let i of e.unknown)n.push(i);return n.length===0?"nobody":n.length===1?n[0]:n}function hr(e){return!e.inherit&&!e.anyone&&!e.signedIn&&e.roles.length===0&&e.unknown.length===0}function Ws(){return{...mr,anyone:!0}}function yn(){return{...mr}}function Us(){return{...js}}function Ks(e,n){return{...Gs(e),signedIn:n}}function Vs(e,n,i){let r=Gs(e),o=r.roles.filter(s=>s!==n);return i&&o.push(n),{...r,roles:o}}function Gs(e){return{...e,inherit:!1,anyone:!1}}var ot=[{op:"find",word:"List",verb:"list"},{op:"get",word:"Open one",verb:"open"},{op:"create",word:"Create",verb:"create"},{op:"update",word:"Change",verb:"change"},{op:"delete",word:"Delete",verb:"delete"}],gr=[{op:"upload",word:"Upload",verb:"upload"},{op:"read",word:"Open",verb:"open"},{op:"delete",word:"Delete",verb:"delete"}];function dd(e){let n=ot.find(i=>i.op===e)||gr.find(i=>i.op===e);return n?n.verb:e==="call"?"call":e}function ti(e,n){if(e.inherit)return n?"Default ("+ti(n)+")":"Default";if(e.anyone)return"Everyone";let i=[];return e.signedIn&&i.push("Signed in"),i.push(...e.roles,...e.unknown.map(Hs)),i.length?i.join(", "):"No one"}function kn(e,n){return ti(Jt(e),n===void 0?void 0:Jt(n))}function It(e,n){let i={};for(let r of n)i[r]=Jt(e?e[r]:void 0);return i}function Yt(e,n){let i={};for(let r of n){let o=ei(e[r]);o!==void 0&&(i[r]=o)}return i}function qs(e,n){return n.every(i=>e[i].inherit)}function Lt(e){let n=[];for(let i of e)for(let r of fr(i)){let o=typeof r=="string"?pr(r):null;o!==null&&n.indexOf(o)===-1&&n.push(o)}return n}function Xt(e,n){return n.map(i=>ei(e[i]))}function br(e,n){return n==="default"?{checked:e.inherit,implied:!1}:e.inherit?{checked:!1,implied:!1}:n==="everyone"?{checked:e.anyone,implied:!1}:n==="noOne"?{checked:hr(e),implied:e.anyone}:n==="signedIn"?{checked:e.signedIn||e.anyone,implied:e.anyone}:{checked:e.roles.indexOf(n.role)!==-1||e.anyone,implied:e.anyone}}function yr(e,n,i){return n==="default"?i?Us():yn():n==="noOne"?i?yn():e.inherit?yn():e:n==="everyone"?i?Ws():yn():n==="signedIn"?Ks(e,i):Vs(e,n.role,i)}function $s(e,n,i){return n.every(r=>e[r].roles.indexOf(i)===-1)}var zs=[{id:"public-read",label:"Public read, signed-in write",sentence:"Anyone can list and open records. Only signed-in people can create, change or delete them."},{id:"signed-in",label:"Signed-in only",sentence:"Only signed-in people can see or change anything."},{id:"owner",label:"Only the owner",sentence:"Signed-in people can create records, and each person sees and changes only the records they created."},{id:"role-only",label:"One role only",sentence:"Only people in the role you pick can see or change anything.",needsRole:!0},{id:"locked",label:"Locked",sentence:"No one can reach this collection except the admin credential."}];function Js(e,n){let i=r=>({find:r,get:r,create:r,update:r,delete:r});switch(e){case"public-read":return{permissions:{find:"public",get:"public",create:"authenticated",update:"authenticated",delete:"authenticated"},creatorOwns:!1};case"signed-in":return{permissions:i("authenticated"),creatorOwns:!1};case"owner":return{permissions:i("authenticated"),creatorOwns:!0};case"role-only":return{permissions:i("role:"+(n||"")),creatorOwns:!1};case"locked":return{permissions:i("nobody"),creatorOwns:!1}}}function kr(e){return e.filter(n=>!n.startsWith("_")).sort((n,i)=>n.localeCompare(i))}function Ys(e,n){if(!e||!e.permissions&&e.creatorOwns===void 0)return"The defaults";let i=e.permissions||{},r=ot.filter(s=>i[s.op]!==void 0).map(s=>s.verb+": "+kn(i[s.op]));e.creatorOwns!==void 0&&r.push(e.creatorOwns?"records belong to their creator":"records are shared");let o=ot.filter(s=>i[s.op]===void 0).length;return o&&o<ot.length&&r.push(o+" from the defaults"),r.join(" \xB7 ")||"The defaults"}function Xs(e){let n=Jt(e);return n.anyone?"public":hr(n)||n.inherit?"nobody":"other"}var Zs=[["off","Run every time"],["key","Replay on a repeated key"],["key-body","Replay on key + body"],["required","Require a key, then replay"],["required-body","Require a key + body"]];function ud(e){return!e||!e.enabled?"off":e.requireKey?e.hashBody?"required-body":"required":e.hashBody?"key-body":"key"}function pd(e){switch(e){case"off":return;case"key":return{enabled:!0};case"key-body":return{enabled:!0,hashBody:!0};case"required":return{enabled:!0,requireKey:!0};case"required-body":return{enabled:!0,requireKey:!0,hashBody:!0}}}function ni(e){return{call:Jt(e.configured===null?void 0:e.configured),runAs:e.runAs==="system"?"system":"",ownLimit:e.rateLimit!==null,perMinute:e.rateLimit?e.rateLimit.ratePerMinute:60,burst:e.rateLimit?e.rateLimit.burst:30,timeoutSeconds:e.timeoutMs===null?"":String(e.timeoutMs/1e3),idempotency:ud(e.idempotency)}}function Qs(e){if(e.ownLimit&&(!Number.isFinite(e.perMinute)||e.perMinute<0||!Number.isFinite(e.burst)||e.burst<0))return"A limit is a number of calls per minute and a burst, each 0 or more (0 and 0 means no limit at all).";if(e.timeoutSeconds.trim()!==""){let n=Number(e.timeoutSeconds);if(!Number.isFinite(n)||n<0)return"The time limit is a number of seconds, 0 or more (0 means no limit)."}return null}function ii(e){let n={},i=ei(e.call);i!==void 0&&(n.call=i),e.runAs==="system"&&(n.runAs="system"),e.ownLimit&&(n.rateLimit={ratePerMinute:e.perMinute,burst:e.burst}),e.timeoutSeconds.trim()!==""&&(n.timeoutMs=Math.round(Number(e.timeoutSeconds)*1e3));let r=pd(e.idempotency);return r&&(n.idempotency=r),Object.keys(n).length?n:void 0}function ea(e,n){let i=[];return e.deployed||i.push({kind:"warn",label:"not deployed",why:"A rule guards the name "+e.name+", but no deployed function answers to it. It grants nothing until one does."}),e.source==="graph"&&i.push({kind:"",label:"from the graph",why:"No rule of its own: the graph\u2019s Allow Unauthenticated port decides ("+(e.allowNoAuth?"ticked, so everyone":"unticked, so signed in")+")."}),e.graphRefusesAnonymous&&i.push({kind:"bad",label:"fails for signed-out callers",why:"This rule lets a signed-out caller through, but the function\u2019s Request node does not have Allow Unauthenticated ticked, so the call reaches the graph and fails there. Tick the port on the canvas, or set this to Signed in."}),e.rateLimitSource==="public-write-default"&&n&&i.push({kind:"accent",label:n.ratePerMinute+"/min default",why:"Anyone can call it and it writes records, and it has no limit of its own, so the public-write default applies: "+n.ratePerMinute+" a minute, burst "+n.burst+", per caller."}),e.rateLimitSource==="declared"&&e.effectiveRateLimit===null&&i.push({kind:"accent",label:"no limit, on purpose",why:"Its limit is 0 and 0, which means no budget of its own \u2014 the opt-out for a provider\u2019s webhook."}),i}function ta(e,n,i,r){let o=dd(n),s=r.allowed&&r.recordAllowed===!1,l=r.allowed&&!s?" may ":" may not ",a=n==="call"?"call "+i:o+" "+i;return e+l+a+(s?": the collection allows it, but that record\u2019s sharing does not.":".")}function na(e){return"The rule for it is "+kn(e.rule)+(e.source==="graph"?" (from the graph\u2019s Allow Unauthenticated port)":"")+"."}var ri=["upload","read","delete"];async function md(){let[e,n,i]=await Promise.all([S("GET","/admin/permissions"),S("GET","/admin/roles").catch(()=>({roles:[]})),S("GET","/admin/schema").catch(()=>({tables:[]}))]);return{config:e.config||{},etag:e.etag||"",enforced:e.enforced!==!1,loopback:e.loopback===!0,roles:(n.roles||[]).map(r=>r.name),tables:(i.tables||[]).map(r=>r.name)}}function vr(e,n){return S("PUT","/admin/permissions",e,n?{"If-Match":n}:void 0)}function Cr(){let[e,n]=m(null),i=()=>{md().then(n).catch(O)};return j(i,[]),[e,i,n]}function ra({params:e}){let n=e[0]||"";return n==="functions"?t(bd,{}):n?t(gd,{name:n},n):t(hd,{})}function Sr({enforced:e,switchBelow:n}){return e?t(J,{children:"Enforcement is on. Every rule below is applied to every request."}):n?t(J,{kind:"warn",children:"The rules below are not enforced yet. They take effect when you turn on Enforce my rules on this machine, under this notice."}):t(J,{kind:"warn",children:"Dev-open is on. While it is, this backend enforces nothing and everyone can do everything. The rules below say what will happen once Enforce my rules on this machine is on, at the top of the Permissions page. Dev-open only ever runs on a backend bound to this machine."})}var Tr=[{col:"everyone",word:"Everyone"},{col:"signedIn",word:"Signed in"},{col:"noOne",word:"No one"}];function wr(e){if(e==="default")return"Default";let n=Tr.find(i=>i.col===e);return n?n.word:e.role}function oi({rows:e,matrix:n,onChange:i,roles:r,onRoles:o,allRoles:s,defaults:l,disabled:a,id:c}){let u=e.map(b=>b.op),p=[...l?["default"]:[],...Tr.map(b=>b.col),...r.map(b=>({role:b}))],d=s.filter(b=>r.indexOf(b)===-1);function f(b,g){let v=n[b],y=br(v,g),T=e.find(x=>x.op===b).word,w=g==="default"||g==="everyone"||g==="signedIn"||g==="noOne"?g:"role-"+g.role;return t("td",{class:"perm-cell"+(y.implied?" implied":""),children:[t("input",{type:"checkbox","aria-label":T+": "+wr(g),"data-op":b,"data-col":w,checked:y.checked,disabled:a||y.implied,onChange:x=>i({...n,[b]:yr(v,g,x.target.checked)})}),g==="default"&&l?t("span",{class:"perm-default-words",children:ti(l[b])}):null]},w)}return t("div",{class:"scroller perm-scroller",children:t("table",{class:"perm-matrix",id:c,children:[t("thead",{children:t("tr",{children:[t("th",{class:"perm-op",children:"Who can\u2026"}),p.map(b=>{if(typeof b=="string")return t("th",{children:wr(b)},b);let g=$s(n,u,b.role);return t("th",{class:"perm-role",children:t("span",{class:"chip accent",children:[b.role,a?null:t("button",{type:"button",class:"chip-x","aria-label":"Remove the "+b.role+" column",title:g?"Remove this column":"Untick "+b.role+" in every row first",disabled:!g,onClick:()=>o(r.filter(v=>v!==b.role)),children:"\u2715"})]})},"role-"+b.role)}),t("th",{class:"perm-add",children:a?null:t(Ce,{id:c+"-add-role",fetch:b=>Promise.resolve(d.filter(g=>g.toLowerCase().includes(b.trim().toLowerCase()))),label:b=>b,keyOf:b=>b,value:null,onPick:b=>{b&&r.indexOf(b)===-1&&o(r.concat([b]))},placeholder:"+ role",emptyText:d.length?"No role matches.":s.length?"Every role is already a column.":"No roles yet \u2014 make one under Roles."})})]})}),t("tbody",{children:e.map(b=>t("tr",{"data-op":b.op,children:[t("td",{class:"perm-op",children:[t("b",{children:b.word})," ",t("span",{class:"hint mono",children:b.op})]}),p.map(g=>f(b.op,g)),t("td",{})]},b.op))})]})})}function fd({sel:e,onChange:n,roles:i,onRoles:r,allRoles:o,inheritWord:s,disabled:l,id:a}){let c=[...s?["default"]:[],...Tr.map(p=>p.col),...i.map(p=>({role:p}))],u=o.filter(p=>i.indexOf(p)===-1);return t("div",{class:"rule-boxes",id:a,children:[c.map(p=>{let d=br(e,p),f=p==="default"?s||"Default":wr(p),b=typeof p=="string"?p:"role-"+p.role;return t("label",{class:"check"+(d.implied?" implied":""),children:[t("input",{type:"checkbox","data-col":b,checked:d.checked,disabled:l||d.implied,onChange:g=>n(yr(e,p,g.target.checked))}),typeof p=="string"?f:t("span",{class:"chip accent",children:f}),typeof p!="string"&&!l&&e.roles.indexOf(p.role)===-1?t("button",{type:"button",class:"chip-x","aria-label":"Remove "+p.role,onClick:()=>r(i.filter(g=>g!==p.role)),children:"\u2715"}):null]},b)}),l?null:t("span",{class:"rule-add",children:t(Ce,{id:a+"-add-role",fetch:p=>Promise.resolve(u.filter(d=>d.toLowerCase().includes(p.trim().toLowerCase()))),label:p=>p,keyOf:p=>p,value:null,onPick:p=>{p&&i.indexOf(p)===-1&&r(i.concat([p]))},placeholder:"+ role",emptyText:u.length?"No role matches.":"No other roles."})})]})}function xr({kind:e,targets:n,initialTarget:i,enforced:r}){let[o,s]=m("user"),[l,a]=m(null),[c,u]=m("find"),[p,d]=m(i||n[0]||""),[f,b]=m(null),[g,v]=m(null),[y,T]=m(""),[w,x]=m(!1);j(()=>{!p&&n.length&&d(i||n[0])},[n.join(\`
65041
+ \`)]);let R=e==="collection"&&(c==="get"||c==="update"||c==="delete"),C=p!==""&&(o==="anonymous"||l!==null),_=o==="anonymous"?"Someone signed out":l?Pe(l):"someone";async function N(E){return(await S("GET","/admin/users?limit=20&q="+I(E.trim()))).users||[]}async function k(){if(C){x(!0);try{let P={principal:o==="anonymous"?{kind:"anonymous"}:{kind:"user",userId:l.objectId}};e==="function"?P.functionName=p:(P.collection=p,P.op=c,R&&f&&(P.record=await S("GET","/api/"+I(p)+"/"+I(f.objectId))));let U=await S("POST","/admin/permissions/check",P);v(U),T(ta(_,e==="function"?"call":c,p+(R&&f?" ("+f.label+")":""),U))}catch(E){O(E)}x(!1)}}return t(pe,{class:"tryas",id:"tryas",children:[t(V,{class:"tryas-line",children:[t(vt,{children:"Try it"}),t("span",{children:"as"}),t("select",{"aria-label":"Try as",value:o,onChange:E=>s(E.currentTarget.value),children:[t("option",{value:"user",children:"a person\u2026"}),t("option",{value:"anonymous",children:"someone signed out"})]}),o==="user"?t("span",{class:"tryas-person",children:t(Ce,{id:"tryas-person",fetch:N,label:Pe,detail:E=>E.email&&E.email!==E.username?E.email:"",keyOf:E=>E.objectId,value:l,onPick:a,placeholder:"Type a name or an email\u2026",emptyText:"No one matches."})}):null,t("span",{children:"doing"}),e==="function"?t("b",{children:"Call"}):t("select",{"aria-label":"Doing",value:c,onChange:E=>u(E.currentTarget.value),children:ot.map(E=>t("option",{value:E.op,children:E.word},E.op))}),t("span",{children:"on"}),t("select",{"aria-label":"On",value:p,onChange:E=>d(E.currentTarget.value),children:n.map(E=>t("option",{value:E,children:E},E))}),R?t("span",{class:"tryas-record",children:t(Ce,{id:"tryas-record",fetch:E=>p?Kt(p,E):Promise.resolve([]),label:E=>E.label,detail:E=>E.detail,keyOf:E=>E.objectId,value:f,onPick:b,placeholder:"any record, or pick one\u2026",emptyText:"No record matches."})}):null,t(A,{tiny:!0,kind:"primary",disabled:!C||w,onClick:k,children:w?"Asking\u2026":"Ask"})]}),g?t("div",{class:"verdict "+(g.allowed&&g.recordAllowed!==!1?"ok":"bad"),"aria-live":"polite",children:[t("b",{children:y}),t("span",{class:"hint",children:na(g)}),t("span",{class:"hint mono",children:g.reason}),r?null:t("span",{class:"hint",children:"Dev-open is on, so right now this backend would let it through anyway. This is what happens once enforcement is on."})]}):null]})}function ia(e,n){return{...e,defaults:{permissions:Yt(n.defaults,Pt),creatorOwns:n.owns},files:Yt(n.files,ri),signup:n.signup}}function hd(){let{readonly:e}=te(),[n,i,r]=Cr(),[o,s]=m(null),[l,a]=m([]),[c,u]=m([]),[p,d]=m(null),[f,b]=m(!1),[g,v]=m("");function y(P){let U=P.config,X=It(U.defaults&&U.defaults.permissions||{},Pt),H=It(U.files||{},ri),q={defaults:X,owns:!!(U.defaults&&U.defaults.creatorOwns),files:H,signup:U.signup===void 0?"nobody":U.signup};s(q),v(JSON.stringify(ia(U,q))),a(Lt(Xt(X,Pt))),u(Lt(Xt(H,ri))),d(null)}if(j(()=>{n&&y(n)},[n&&n.etag]),!n||!o)return t(ce,{title:"Permissions"});let T=n.config,w=kr(n.tables),x=n.tables.filter(P=>P.startsWith("_")).length,R=()=>ia(T,o),C=JSON.stringify(R())!==g,_=Pt.some(P=>o.defaults[P].inherit)||ri.some(P=>o.files[P].inherit);function N(P){b(!0),vr({...T,devOpen:!P},n.etag).then(()=>(D(P?"Your rules are now enforced on this machine.":"Dev-open is on: this backend enforces nothing until you turn it off.","ok"),i(),So().catch(()=>{}))).catch(U=>{U&&U.status===412?d(String(U.message)):O(U)}).then(()=>b(!1))}function k(){if(_){D("Every row of the defaults and of Files needs an answer.","bad");return}b(!0),vr(R(),n.etag).then(P=>{D("Permissions saved.","ok"),r({...n,config:P.config,etag:P.etag})}).catch(P=>{P&&P.status===412?d(String(P.message)):O(P)}).then(()=>b(!1))}let E=Xs(o.signup);return t(ce,{title:"Permissions",subtitle:"Who may list, open, create, change and delete what. Tick boxes; nothing here is typed.",children:[t(Sr,{enforced:n.enforced,switchBelow:n.loopback}),n.loopback?t(V,{style:"margin-top:10px",children:[t(re,{id:"enforce-rules",checked:n.enforced,disabled:e||f||C,onChange:N,children:"Enforce my rules on this machine"}),C?t(M,{children:"Save or undo your changes to the rules first."}):null]}):null,t(xr,{kind:"collection",targets:w,enforced:n.enforced}),t(G,{}),p?t(J,{kind:"bad",children:[p," ",t(A,{tiny:!0,onClick:i,children:"Reload"})]}):null,t("h2",{children:"Any collection without rules of its own"}),t(pe,{id:"defaults",children:[t(oi,{id:"defaults-matrix",rows:ot,matrix:o.defaults,onChange:P=>s({...o,defaults:P}),roles:l,onRoles:a,allRoles:n.roles,disabled:e}),t(G,{h:8}),t(re,{id:"defaults-owns",checked:o.owns,disabled:e,onChange:P=>s({...o,owns:P}),children:"Records belong to their creator"}),t("div",{class:"switch-why",children:"When a signed-in user creates a record, only they (and the roles allowed above) can see and change it."})]}),t("h2",{children:"Sign-up"}),t(pe,{id:"signup",children:[t($,{label:"Who can make an account",children:t("select",{"aria-label":"Who can make an account",disabled:e,value:E,onChange:P=>{let U=P.currentTarget.value;s({...o,signup:U==="public"?"public":U==="nobody"?"nobody":T.signup||"nobody"})},children:[t("option",{value:"public",children:"Anyone can sign up"}),t("option",{value:"nobody",children:"Nobody \u2014 accounts are made here, under Users"}),E==="other"?t("option",{value:"other",children:["As stored: ",kn(T.signup)]}):null]})}),t(M,{children:"Signing in with a link or a provider makes an account too, when Sign-in allows it."})]}),t("h2",{children:"Files"}),t(pe,{id:"files",children:[t(oi,{id:"files-matrix",rows:gr,matrix:o.files,onChange:P=>s({...o,files:P}),roles:c,onRoles:u,allRoles:n.roles,disabled:e}),t(M,{children:"Open means fetching a file by its address. A signed address always opens, whoever asks."})]}),t(V,{children:[t(B,{kind:"primary",disabled:!C||f,onClick:k,children:f?"Saving\u2026":"Save"}),t(A,{disabled:!C,onClick:()=>y(n),children:"Undo changes"}),t(me,{}),t(M,{children:C?"Unsaved changes.":"Saved."})]}),t("h2",{children:"Collections"}),t(ye,{columns:["Collection","Rules",""],rows:w,empty:t(oe,{action:{label:"Create a collection in Schema",onClick:()=>Z("schema"),write:!1},children:"No collections to set permissions on yet."}),renderRow:P=>{let U=(T.collections||{})[P],X=!!U&&(!!U.permissions||U.creatorOwns!==void 0);return t("tr",{class:"clickable","data-collection":P,onClick:()=>Z("permissions",P),children:[t("td",{children:t("b",{children:P})}),t("td",{class:X?void 0:"hint",children:Ys(U,T.defaults&&T.defaults.permissions||{})}),t("td",{children:X?t(K,{kind:"accent",children:"its own rules"}):t(K,{children:"defaults"})})]},P)}}),t(M,{children:[x?"The backend\u2019s own tables (accounts, roles, sessions, keys, audit) are locked to the admin credential and cannot be given rules. ":"",t("a",{href:"#/permissions/functions",children:"Who can call each function \u2192"})]})]})}function gd({name:e}){let{readonly:n}=te(),[i,,r]=Cr(),[o,s]=m(null),[l,a]=m([]),[c,u]=m(!0),[p,d]=m(!1),[f,b]=m(""),[g,v]=m(!1),y=Pt;function T(X){let H=(X.config.collections||{})[e],q=!!H&&(!!H.permissions||H.creatorOwns!==void 0),h=It(H&&H.permissions,y);s(h),a(Lt(Xt(h,y))),u(!q),d(H&&H.creatorOwns!==void 0?!!H.creatorOwns:!!(X.config.defaults&&X.config.defaults.creatorOwns))}if(j(()=>{i&&T(i)},[i&&i.etag]),!i||!o)return t(ce,{title:e});let w=i.config,x=It(w.defaults&&w.defaults.permissions||{},y),R=kr(i.tables),C=(w.collections||{})[e];if(e.startsWith("_"))return t(ce,{title:e,children:[t(J,{kind:"warn",children:[e," is one of the backend\u2019s own tables. It is locked to the admin credential and cannot be given rules."]}),t("a",{href:"#/permissions",children:"\u2190 Permissions"})]});let _=c?{}:{permissions:Yt(o,y),creatorOwns:p},N=C&&(C.permissions||C.creatorOwns!==void 0)?{permissions:Yt(It(C.permissions,y),y),creatorOwns:C.creatorOwns!==void 0?!!C.creatorOwns:!!(w.defaults&&w.defaults.creatorOwns)}:{},k=JSON.stringify(_)!==JSON.stringify(N),E=!c&&qs(o,y);function P(X){let H=Js(X,f),q=It(H.permissions,y);u(!1),s(q),a(Lt(Xt(q,y))),d(!!H.creatorOwns)}function U(){v(!0);let X=q=>S("GET","/admin/permissions").then(h=>{D(q,"ok"),r({...i,config:h.config||{},etag:h.etag||""})});(c?S("DELETE","/admin/permissions/collections/"+I(e)).then(()=>X(e+" now uses the defaults.")):S("PUT","/admin/permissions/collections/"+I(e),{permissions:Yt(o,y),creatorOwns:p}).then(()=>X("Permissions saved for "+e+"."))).catch(O).then(()=>v(!1))}return t(ce,{title:e,subtitle:"Who may list, open, create, change and delete "+e+" records.",children:[t("a",{href:"#/permissions",children:"\u2190 All collections"}),t(G,{}),t(Sr,{enforced:i.enforced}),t(xr,{kind:"collection",targets:R.indexOf(e)===-1?[e].concat(R):R,initialTarget:e,enforced:i.enforced}),t(G,{}),R.indexOf(e)===-1?t(J,{kind:"warn",children:["There is no collection called ",e," in the schema. Rules for it apply once one exists."]}):null,t(pe,{id:"collection-rules",children:[t(V,{children:[t(re,{id:"use-defaults",checked:c,disabled:n,onChange:u,children:"Use the defaults"}),t(me,{}),c?null:t("span",{class:"tpl-line",children:[t("span",{class:"hint",children:"Start from\u2026"}),zs.map(X=>t(A,{tiny:!0,title:X.sentence,disabled:n||X.needsRole&&!f,onClick:()=>P(X.id),children:X.label},X.id)),t("select",{"aria-label":"Role for the template",disabled:n,value:f,onChange:X=>b(X.currentTarget.value),children:[t("option",{value:"",children:"role\u2026"}),i.roles.map(X=>t("option",{value:X,children:X},X))]})]})]}),t(G,{h:8}),c?t(W,{children:[t(M,{children:["These are the backend\u2019s defaults. Turn the switch off to give ",e," rules of its own."]}),t(oi,{id:"collection-matrix",rows:ot,matrix:x,onChange:()=>{},roles:Lt(Xt(x,y)),onRoles:()=>{},allRoles:i.roles,disabled:!0}),t(G,{h:8}),t(re,{checked:!!(w.defaults&&w.defaults.creatorOwns),disabled:!0,onChange:()=>{},children:"Records belong to their creator"})]}):t(W,{children:[t(oi,{id:"collection-matrix",rows:ot,matrix:o,onChange:s,roles:l,onRoles:a,allRoles:i.roles,defaults:x,disabled:n}),t(G,{h:8}),t(re,{id:"collection-owns",checked:p,disabled:n,onChange:d,children:"Records belong to their creator"}),t("div",{class:"switch-why",children:"When a signed-in user creates a record, only they (and the roles allowed above) can see and change it."})]}),t(G,{h:8}),t(V,{children:[t(B,{kind:"primary",disabled:!k||g,onClick:U,children:g?"Saving\u2026":"Save"}),t(A,{disabled:!k,onClick:()=>T(i),children:"Undo changes"}),t(me,{}),t(M,{children:E?"Every row is on Default: saving stores no rule of its own, only the creator setting.":k?"Unsaved changes.":"Saved."})]})]})]})}function bd(){let{readonly:e}=te(),[n,i,r]=Cr(),[o,s]=m(null),[l,a]=m(null);function c(){S("GET","/admin/permissions/functions").then(s).catch(O)}if(j(c,[n&&n.etag]),!n||!o)return t(ce,{title:"Functions"});function u(d,f){let b={...n.config.functions||{}};return f?b[d]=f:delete b[d],vr({...n.config,functions:b},n.etag).then(g=>{D(d+" saved.","ok"),a(null),r({...n,config:g.config,etag:g.etag})}).catch(g=>{g&&g.status===412?a(String(g.message)):O(g)})}let p=o.functions.map(d=>d.name);return t(ce,{title:"Functions",subtitle:"Who can call each function, what it runs as, its budget, its time limit, and what a repeated delivery does.",children:[t("a",{href:"#/permissions",children:"\u2190 Permissions"}),t(G,{}),t(Sr,{enforced:n.enforced}),t(xr,{kind:"function",targets:p,enforced:n.enforced}),t(G,{}),l?t(J,{kind:"bad",children:[l," ",t(A,{tiny:!0,onClick:i,children:"Reload"})]}):null,t(M,{children:["Every function also shares one budget",o.classRateLimit?" ("+o.classRateLimit.ratePerMinute+" a minute, burst "+o.classRateLimit.burst+")":"","; a limit here can only tighten it. Without a time limit of its own a function gets ",o.defaultTimeoutMs/1e3," seconds.",o.idempotency.available?" A replayed answer is kept for "+o.idempotency.ttlHours+" hours.":" Duplicate deliveries cannot be replayed on this backend: its store is not available."]}),t(G,{}),o.functions.length?o.functions.map(d=>t(yd,{row:d,allRoles:n.roles,publicWriteDefault:o.publicWriteDefaultRateLimit,idempotencyAvailable:o.idempotency.available,readonly:e,onSave:f=>u(d.name,f)},d.name+":"+JSON.stringify(d))):t(oe,{icon:"\u0192",children:"No functions yet. Deploy a project with a Request node, or a rule appears here once one is written."})]})}function yd({row:e,allRoles:n,publicWriteDefault:i,idempotencyAvailable:r,readonly:o,onSave:s}){let[l,a]=m(()=>ni(e)),[c,u]=m(()=>Lt([e.configured===null?void 0:e.configured])),[p,d]=m(!1),f=ea(e,i),b=Qs(l),g=JSON.stringify(ii(l)||null)!==JSON.stringify(ii(ni(e))||null),v="From the graph ("+(e.allowNoAuth?"everyone":"signed in")+")";function y(){if(b){D(b,"bad");return}d(!0),s(ii(l)).then(()=>d(!1))}function T(){we("Forget the rules for "+e.name+"?","It goes back to what the graph declares"+(e.deployed?"":", and since nothing is deployed under that name, the row disappears")+".",()=>{d(!0),s(void 0).then(()=>d(!1))},"Forget")}return t(pe,{class:"fn-card",id:"fn-"+e.name,children:[t(V,{children:[t(vt,{children:e.name}),e.workflow?t("span",{class:"hint",children:e.workflow}):null,f.map(w=>t(K,{kind:w.kind,title:w.why,children:w.label},w.label)),t(me,{}),t("span",{class:"hint",children:["Who can call now: ",kn(e.call)]})]}),f.filter(w=>w.kind==="bad"||w.kind==="warn").map(w=>t(M,{children:w.why},w.label)),t(G,{h:8}),t($,{label:"Who can call",children:t(fd,{id:"fn-call-"+e.name,sel:l.call,onChange:w=>a({...l,call:w}),roles:c,onRoles:u,allRoles:n,inheritWord:v,disabled:o})}),t("div",{class:"fn-fields",children:[t($,{label:"Runs as",children:t("select",{"aria-label":"Runs as",disabled:o,value:l.runAs,onChange:w=>a({...l,runAs:w.currentTarget.value}),children:[t("option",{value:"",children:"Not said"}),t("option",{value:"system",children:"The system"})]})}),t($,{label:"Its own limit",children:t("span",{class:"field-line",children:[t(re,{label:"Its own limit",checked:l.ownLimit,disabled:o,onChange:w=>a({...l,ownLimit:w})}),l.ownLimit?t(W,{children:[t("input",{type:"number",min:0,"aria-label":"Calls per minute",disabled:o,value:l.perMinute,onInput:w=>a({...l,perMinute:Number(w.currentTarget.value)})}),t("span",{class:"hint",children:"a minute, burst"}),t("input",{type:"number",min:0,"aria-label":"Burst",disabled:o,value:l.burst,onInput:w=>a({...l,burst:Number(w.currentTarget.value)})})]}):t("span",{class:"hint",children:"the shared budget only"})]})}),t($,{label:"Time limit (seconds)",children:t("input",{type:"number",min:0,step:"any","aria-label":"Time limit in seconds",placeholder:"the default",disabled:o,value:l.timeoutSeconds,onInput:w=>a({...l,timeoutSeconds:w.currentTarget.value})})}),t($,{label:"Duplicate deliveries",children:t("select",{"aria-label":"Duplicate deliveries",disabled:o||!r,value:l.idempotency,onChange:w=>a({...l,idempotency:w.currentTarget.value}),children:Zs.map(([w,x])=>t("option",{value:w,children:x},w))})})]}),b?t(J,{kind:"bad",children:b}):null,t(V,{children:[t(B,{tiny:!0,kind:"primary",disabled:!g||p||!!b,onClick:y,children:p?"Saving\u2026":"Save"}),t(A,{tiny:!0,disabled:!g,onClick:()=>a(ni(e)),children:"Undo"}),e.source==="configured"||e.runAs||e.rateLimit||e.timeoutMs!==null||e.idempotency?t(B,{tiny:!0,disabled:p,onClick:T,children:"Forget its rules"}):null,t(me,{}),t(M,{children:g?"Unsaved changes.":"Saved."})]})]})}var oa={read:!1,write:!1,anyFunction:!1,functions:[]};function sa(e){let n=[];return e.read&&e.write?n.push("classes:*"):e.read?n.push("classes:read"):e.write&&n.push("classes:write"),e.anyFunction?n.push("functions:*"):e.functions.forEach(i=>n.push("functions:"+i)),n}function Rr(e){let n={read:!1,write:!1,anyFunction:!1,functions:[]};return(e||[]).forEach(i=>{if(i==="classes:*")n.read=n.write=!0;else if(i==="classes:read")n.read=!0;else if(i==="classes:write")n.write=!0;else if(i==="functions:*")n.anyFunction=!0;else if(i.indexOf("functions:")===0&&i.length>10){let r=i.slice(10);n.functions.indexOf(r)===-1&&n.functions.push(r)}}),n}function Er(e){return!e.read&&!e.write&&!e.anyFunction&&e.functions.length===0}function aa(e){let n=Rr(e),i=[];return n.read&&i.push("read data"),n.write&&i.push("write data"),n.anyFunction?i.push("any function"):n.functions.length===1?i.push("1 function"):n.functions.length>1&&i.push(n.functions.length+" functions"),i}var la="X-NodeGX-Api-Key";function _r(e){return String(e.username||e.email||e.objectId)}function ca({params:e}){let{whoami:n}=te(),i=!!(n&&n.security&&n.security.devOpen),[r,o]=m(null),[s,l]=m([]),[a,c]=m(null),u=e[0]||"";function p(){S("GET","/admin/keys").then(g=>o(g.keys||[])).catch(O)}j(()=>{p(),S("GET","/api/_User?limit=200").then(g=>l(g.results||[])).catch(()=>l([])),S("GET","/admin/permissions/functions").then(g=>c((g.functions||[]).map(v=>v.name))).catch(()=>c([]))},[]);let d={};s.forEach(g=>d[g.objectId]=g);function f(g){qe("Revoke key",'Anything using "'+g.name+'" stops working immediately.',g.name,()=>{S("DELETE","/admin/keys/"+I(g.objectId)).then(()=>{D("Key revoked.","ok"),p()}).catch(O)},"Revoke")}let b=u&&u!=="new"&&r&&r.find(g=>g.objectId===u)||null;return t(ce,{title:"API keys",subtitle:"A key lets a script or another server call this backend. Tick what it may do; the secret is shown once.",children:[t(V,{children:[t(B,{kind:"primary",onClick:()=>Z("keys","new"),children:"New key"}),t(A,{onClick:p,children:"Refresh"})]}),t(G,{}),i?t(W,{children:[t(J,{kind:"warn",children:["This backend is ",t("b",{children:"dev-open"}),": while it is, every key may do everything, whatever its boxes say. The boxes take effect once Enforce my rules on this machine is on, at the top of the ",t("a",{href:"#/permissions",children:"Permissions page"}),". A deployed backend refuses to start with dev-open on."]}),t(G,{})]}):null,r?t(ye,{columns:["Name","May do","Acts as","Last used","Created","Status",""],rows:r,empty:t(oe,{icon:"\u{1F511}",action:{label:"New key",onClick:()=>Z("keys","new")},children:"No API keys yet. A key lets a server or script call this backend."}),renderRow:g=>t("tr",{class:g.revoked?"revoked":"",children:[t("td",{children:g.name}),t("td",{children:aa(g.scopes).map(v=>t(K,{kind:"accent",children:v},v))}),t("td",{children:g.actsAsUserId?d[g.actsAsUserId]?_r(d[g.actsAsUserId]):"a user that is gone":t("span",{class:"hint",children:"backend"})}),t("td",{title:he(g.lastUsedAt),children:je(g.lastUsedAt)}),t("td",{children:he(g.createdAt)}),t("td",{children:g.revoked?t(K,{kind:"bad",children:"revoked"}):t(K,{kind:"ok",children:"active"})}),t(Ye,{children:g.revoked?null:t(W,{children:[t(B,{tiny:!0,onClick:()=>Z("keys",g.objectId),children:"Edit"})," ",t(B,{tiny:!0,kind:"danger",onClick:()=>f(g),children:"Revoke"})]})})]},g.objectId)}):null,u==="new"||b?t(kd,{existing:b,users:s,functions:a,onClose:()=>Z("keys"),onChanged:p},u):null]})}function kd({existing:e,users:n,functions:i,onClose:r,onChanged:o}){let[s,l]=m(e?e.name:""),[a,c]=m(e?Rr(e.scopes):oa),[u,p]=m(e&&e.actsAsUserId?n.find(k=>k.objectId===e.actsAsUserId)||{objectId:e.actsAsUserId}:null),[d,f]=m(!!(e&&e.actsAsUserId)),[b,g]=m(null),[v,y]=m(!1),[T,w]=m(null),x=sa(a),R=!v&&!Er(a)&&(e?!0:s.trim()!=="")&&(!d||!!u);function C(){if(Er(a)){g("Tick at least one thing the key may do.");return}if(d&&!u){g("Pick the user this key acts as, or switch it back to the backend.");return}g(null),y(!0);let k=d&&u?u.objectId:null;(e?S("PUT","/admin/keys/"+I(e.objectId),{scopes:x,actsAsUserId:k}):S("POST","/admin/keys",{name:s.trim(),scopes:x,actsAsUserId:k})).then(P=>{y(!1),o(),e?(D("Key updated.","ok"),r()):w(P.secret)}).catch(P=>{y(!1),g(P.message)})}let _=()=>{o(),r()};if(T!==null)return t(Se,{title:"Your new key: "+s.trim(),onClose:_,footer:t(A,{kind:"primary",onClick:_,children:"Done"}),children:t(wd,{secret:T})});let N=k=>{let E=k.trim().toLowerCase();return Promise.resolve(n.filter(P=>!E||(_r(P)+" "+String(P.email||"")).toLowerCase().indexOf(E)!==-1))};return t(Se,{title:e?'What "'+e.name+'" may do':"New key",subtitle:e?"The secret does not change. Anything using this key gets the new permissions on its next request.":void 0,onClose:r,footer:t(W,{children:[t(A,{onClick:r,children:"Cancel"}),t(B,{kind:"primary",disabled:!R,onClick:C,children:e?"Save":"Create"})]}),children:[t("div",{class:"form-grid",onInput:()=>g(null),children:[e?null:t($,{label:"Name",children:t("input",{type:"text",placeholder:"What it is for \u2014 e.g. nightly report",value:s,autoFocus:!0,onInput:k=>l(k.currentTarget.value)})}),t("div",{class:"field",children:[t("span",{class:"field-head",children:t("b",{children:"What it may do"})}),t(vd,{choice:a,onChange:c,functions:i})]}),t("div",{class:"field",children:[t("span",{class:"field-head",children:t("b",{children:"Acts as"})}),t("div",{class:"scope-group",children:[t("label",{class:"check",children:[t("input",{type:"radio",name:"acts-as",checked:!d,onChange:()=>f(!1)})," This key is the backend"]}),t("label",{class:"check",children:[t("input",{type:"radio",name:"acts-as",checked:d,onChange:()=>f(!0)})," This key acts as a user"]}),d?t("div",{class:"scope-indent",children:[t(Ce,{fetch:N,label:_r,detail:k=>k.email&&k.email!==k.username?String(k.email):"",keyOf:k=>k.objectId,value:u,onPick:p,placeholder:"Type a username\u2026",emptyText:n.length?"No user by that name.":"This backend has no users yet."}),t(M,{children:"Records the user cannot see, the key cannot see."})]}):t(M,{children:"It sees every record its permissions cover."})]})]})]}),b?t(J,{kind:"bad",style:"margin-top:12px",children:b}):null]})}function vd({choice:e,onChange:n,functions:i,disabled:r}){let o=i||[],s=e.functions.filter(u=>o.indexOf(u)===-1),l=o.concat(s),a=e.read&&e.write,c=(u,p)=>{let d=e.functions.filter(f=>f!==u);p&&d.push(u),n({...e,functions:d})};return t("div",{class:"scope-groups",children:[t("div",{class:"scope-group",children:[t("label",{class:"check scope-all",children:[t("input",{type:"checkbox","aria-label":"All data",checked:a,disabled:r,ref:u=>{u&&(u.indeterminate=!a&&(e.read||e.write))},onChange:u=>{let p=u.currentTarget.checked;n({...e,read:p,write:p})}}),t("b",{children:"Data"})]}),t("div",{class:"scope-list",children:[t(Fe,{checked:e.read,disabled:r,onChange:u=>n({...e,read:u}),children:"Read records"}),t(Fe,{checked:e.write,disabled:r,onChange:u=>n({...e,write:u}),children:"Write records"})]})]}),t("div",{class:"scope-group",children:[t("label",{class:"check scope-all",children:[t("input",{type:"checkbox","aria-label":"Call any function",checked:e.anyFunction,disabled:r,onChange:u=>n({...e,anyFunction:u.currentTarget.checked})}),t("b",{children:"Functions"})," ",t("span",{class:"hint",children:"call any function"})]}),t("div",{class:"scope-list"+(e.anyFunction?" greyed":""),children:i===null?t(M,{children:"Loading functions\u2026"}):l.length?l.map(u=>t(Fe,{checked:e.anyFunction||e.functions.indexOf(u)!==-1,disabled:r||e.anyFunction,onChange:p=>c(u,p),children:[t("span",{class:"mono",children:u}),s.indexOf(u)!==-1?t("span",{class:"hint",children:" (not deployed any more)"}):null]},u)):t(M,{children:"No functions yet. Deploy a cloud function and it appears here."})})]})]})}function wd({secret:e}){let[n,i]=m(!1),[r,o]=m("Collection");j(()=>{S("GET","/admin/schema").then(c=>{let u=(c.tables||[]).map(p=>p.name).filter(p=>p.charAt(0)!=="_")[0];u&&o(u)}).catch(()=>{})},[]);let s=typeof location<"u"?location.origin:"",l='curl -H "'+la+": "+e+'" '+s+"/api/"+r;function a(c){Ge(c).then(u=>{u?(i(!0),D("Copied to the clipboard.","ok"),setTimeout(()=>i(!1),2500)):D("The browser refused the clipboard. Select the secret and copy it by hand.","bad")})}return t("div",{class:"secret-card",children:[t(J,{kind:"warn",children:"This is the only time the secret is shown. It is stored hashed and cannot be recovered; if it is lost, revoke this key and make another."}),t(G,{h:10}),t("div",{class:"secret-box mono","data-secret":!0,children:e}),t(V,{style:"margin-top:8px",children:[t(A,{kind:"primary",onClick:()=>a(e),children:n?"Copied \u2713":"Copy secret"}),t(M,{children:["Send it in the ",la," header, or as a Bearer token."]})]}),t(G,{}),t("div",{class:"field",children:[t("span",{class:"field-head",children:t("b",{children:"Try it"})}),t("div",{class:"secret-box mono",children:l}),t(V,{style:"margin-top:6px",children:t(A,{tiny:!0,onClick:()=>a(l),children:"Copy the command"})})]})]})}var Cd=/^[a-z0-9][a-z0-9-]{0,63}$/,da=[{id:"create",label:"created"},{id:"update",label:"changed"},{id:"delete",label:"deleted"}],si=[{bytes:64*1024,label:"64 KB"},{bytes:256*1024,label:"256 KB"},{bytes:1024*1024,label:"1 MB"},{bytes:5*1024*1024,label:"5 MB"},{bytes:10*1024*1024,label:"10 MB"}],Ar=300,Sd=30,Td=1024*1024;function xd(e){let n=si.find(i=>i.bytes===e);return n?n.label:e>=1024*1024?Math.round(e/(1024*1024)*10)/10+" MB":e>=1024?Math.round(e/1024)+" KB":e+" bytes"}function Rd(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,64)}function Ed(e){if(e.type==="schedule")return e.scheduleWords||e.schedule&&e.schedule.cron||"";if(e.type==="webhook")return"webhook \xB7 /"+(e.webhook&&e.webhook.slug||"");if(e.type==="db-change"){let n=e.dbChange||{},i=(n.actions||[]).map(r=>(da.find(o=>o.id===r)||{label:r}).label);return(n.collection||"")+": "+i.join(", ")}return""}function _d(e,n,i){return e+"/hooks/"+encodeURIComponent(n)+"/"+i}function ua(e,n,i){return n==="token"?"curl -X POST -H 'Content-Type: application/json' -H 'X-Webhook-Token: "+i+\`' -d '{"hello":"world"}' \`+e:n==="hmac-sha512"?"KEY='"+i+\`'
65042
+ BODY='{"event":"charge.success","data":{"reference":"test"}}'
65043
+ SIG=$(printf '%s' "$BODY" | openssl dgst -sha512 -hmac "$KEY" | sed 's/^.* //')
65044
+ curl -X POST -H 'Content-Type: application/json' -H "x-paystack-signature: $SIG" -d "$BODY" \`+e:\`BODY='{"hello":"world"}'
65024
65045
  SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac '\`+i+\`' | sed 's/^.* //')
65025
- curl -X POST -H 'Content-Type: application/json' -H "X-Hub-Signature-256: sha256=$SIG" -d "$BODY" \`+e}function ua({params:e}){let[n,i]=m(null),[r,o]=m(null),[s,l]=m(null),[a,c]=m(null),[u,p]=m(null),d=e[0]||"";function f(){S("GET","/admin/triggers").then(y=>{i(y&&y.triggers||[]),y&&y.overlapDefault&&o(y.overlapDefault)}).catch(D)}j(()=>{f(),S("GET","/admin/permissions/functions").then(y=>l(y.functions||[])).catch(()=>l([])),S("GET","/admin/workflow-defs").then(y=>c(y&&(y.workflows||y.definitions)||[])).catch(()=>c([])),S("GET","/admin/schema").then(y=>p((y.tables||[]).map(T=>T.name).filter(T=>T.charAt(0)!=="_"))).catch(()=>p([]))},[]);function b(y,T){S("POST","/admin/triggers/"+I(y.id)+"/enabled",{enabled:T}).then(()=>{O((y.name||y.id)+(T?" is on.":" is off."),"ok"),f()}).catch(D)}let g=y=>{let T=(a||[]).find(w=>w.id===y);return T&&T.name?String(T.name):y},k=d&&d!=="new"&&n&&n.find(y=>y.id===d)||null;return t(ce,{title:"Triggers",subtitle:"Something that runs on its own: on a schedule, when called from outside, or when data changes.",children:[t(V,{children:[t(M,{kind:"primary",onClick:()=>Z("triggers","new"),children:"New trigger"}),t(P,{onClick:f,children:"Refresh"})]}),t(G,{}),n?t(ye,{columns:["Name","When","Runs","Next run","Last run","Overlap","Enabled"],rows:n,empty:t(oe,{icon:"\u23F1",action:{label:"Schedule a function",onClick:()=>Z("triggers","new")},children:"Nothing runs on its own yet."}),renderRow:y=>{let T=y.status||y;return t("tr",{id:"trigger-"+y.id,class:"clickable"+(y.id===d?" hit":""),onClick:()=>Z("triggers",y.id),children:[t("td",{children:y.name||y.id}),t("td",{children:Rd(y)}),t("td",{children:y.target&&y.target.kind==="workflow"?t(W,{children:[t("span",{class:"hint",children:"workflow "}),g(y.target.name||"")]}):t(W,{children:[t("span",{class:"hint",children:"function "}),t("span",{class:"mono",children:y.target&&y.target.name||""})]})}),t("td",{children:y.type==="schedule"?y.enabled?he(T.nextFireAt):t("span",{class:"hint",children:"off"}):t("span",{class:"hint",children:"\u2014"})}),t("td",{children:t(Ad,{status:T})}),t("td",{children:t(Id,{t:y})}),t("td",{onClick:w=>w.stopPropagation(),children:t(re,{checked:!!y.enabled,label:"Enabled: "+(y.name||y.id),onChange:w=>b(y,w)})})]},y.id)}}):null,(d==="new"||k)&&r?t(Fd,{existing:k,overlapDefault:r,functions:s,workflows:a,collections:u,onClose:()=>Z("triggers"),onChanged:f},d):null]})}function Ad({status:e}){let n=e.lastSkip,i=e.lastFiredAt?new Date(String(e.lastFiredAt)).getTime():0,r=n&&n.at?new Date(String(n.at)).getTime():0;return n&&r>=i?t("span",{title:"Skipped at "+he(n.at)+": the previous run was still going"+(n.yieldedTo?" ("+n.yieldedTo+")":"")+".",children:[t(K,{kind:"warn",children:"skipped"})," ",t("span",{class:"sub",children:[je(n.at)," \xB7 still running"]})]}):e.lastFiredAt?t("span",{title:he(e.lastFiredAt),children:[t(Pd,{result:e.lastResult})," ",t("span",{class:"sub",children:je(e.lastFiredAt)})]}):t("span",{class:"hint",children:"never"})}function Pd({result:e}){if(!e)return t(K,{children:"ran"});let n=e;return e==="ok"||n.ok===!0?t(K,{kind:"ok",children:"ok"}):t(K,{kind:"bad",children:typeof e=="string"?e:n.error||"failed"})}function Id({t:e}){if(e.type!=="schedule")return t("span",{class:"sub",children:"\u2014"});let n=e.effectiveOverlapPolicy||"skip",i=e.status||{},r=i.lastSkip,o=i.skipCount||1,s=r&&r.yieldedTo?" \u2192 "+r.yieldedTo:"";return t("div",{children:[t(K,{kind:n==="allow"?"":"ok",children:n}),r?t("div",{class:"sub",title:"The most recent fire that did not run, and the execution it yielded to.",children:"skipped "+o+"\xD7 \xB7 last "+he(r.at)+s}):null]})}function Ld(e,n){let i=e&&e.schedule||{},r=e&&e.webhook||{},o=e&&e.dbChange||{};return{name:e&&e.name||"",targetKind:e&&e.target&&e.target.kind||"function",targetName:e&&e.target&&e.target.name||"",type:e&&e.type||null,cron:i.cron||"",missed:i.missedFirePolicy||"skip",overlap:e&&e.effectiveOverlapPolicy||n,payload:Ht(i.payload),slug:r.slug||"",slugTouched:!!e,scheme:r.scheme||"hmac-sha256",maxBodyBytes:r.maxBodyBytes||Sd,collection:o.collection||"",actions:(o.actions||[]).slice(),sync:!!(e&&e.responseMode==="sync"),waitS:e&&e.responseTimeoutMs?Math.round(e.responseTimeoutMs/1e3):Cd,enabled:e?e.enabled!==!1:!0}}function Nd(e){if(!e.type)return"Pick when it runs.";if(!e.targetName.trim())return e.targetKind==="workflow"?"Pick the workflow it runs.":"Pick the function it runs.";if(e.type==="schedule"&&!e.cron)return"Finish the schedule.";if(e.type==="webhook"){if(!e.slug)return"Give the hook a path.";if(!wd.test(e.slug))return"A hook path is lowercase letters, digits and hyphens, starting with a letter or digit, up to 64 long."}if(e.type==="db-change"){if(!e.collection)return"Pick the collection to watch.";if(!e.actions.length)return"Tick at least one of created, changed, deleted."}return e.targetKind==="workflow"&&e.sync&&!(Number.isInteger(e.waitS)&&e.waitS>=1&&e.waitS<=Ar)?"Wait between 1 and "+Ar+" seconds.":null}function Dd(e){let n={type:e.type,name:e.name.trim()||void 0,enabled:e.enabled,target:{kind:e.targetKind,name:e.targetName.trim()}};if(e.targetKind==="workflow"&&e.sync&&(n.responseMode="sync",n.responseTimeoutMs=e.waitS*1e3),e.type==="schedule"){let i=Ct(e.payload);n.schedule={cron:e.cron,missedFirePolicy:e.missed,overlapPolicy:e.overlap,...Object.keys(i).length?{payload:i}:{}}}else e.type==="webhook"?n.webhook={slug:e.slug,scheme:e.scheme,maxBodyBytes:e.maxBodyBytes}:e.type==="db-change"&&(n.dbChange={collection:e.collection,actions:e.actions});return n}var Od=[{id:"schedule",title:"On a schedule",words:"Every so often, at a time you pick."},{id:"webhook",title:"When called from outside",words:"Another service posts to a URL \u2014 Stripe, GitHub, a form."},{id:"db-change",title:"When data changes",words:"A record in a collection is created, changed or deleted."}];function Fd({existing:e,overlapDefault:n,functions:i,workflows:r,collections:o,onClose:s,onChanged:l}){let{readonly:a,whoami:c}=te(),u=c&&c.backend&&c.backend.id||"",p=typeof location<"u"?location.origin:"",[d,f]=m(()=>Ld(e,n)),[b,g]=m(null),[k,y]=m(!1),[T,w]=m(null),x=h=>f(L=>({...L,...h})),E=Nd(d),C=!k&&!a&&!E,_=Ed(p,u,d.slug),N=h=>S("POST","/admin/triggers/preview",{cron:h,count:5});function v(){if(E){g(E);return}let h;try{h=Dd(d)}catch(Q){g(Q.message);return}g(null),y(!0),(e?S("PUT","/admin/triggers/"+I(e.id),h):S("POST","/admin/triggers",h)).then(Q=>{if(y(!1),l(),Q&&Q.secret){w({secret:Q.secret,note:Q.secretNote});return}O(e?"Saved.":"Trigger created.","ok"),s()}).catch(Q=>{y(!1),g(Q.message)})}function R(){e&&(y(!0),S("POST","/admin/triggers/"+I(e.id)+"/fire",{}).then(h=>{y(!1);let L=h&&h.response&&h.response.statusCode;O("Ran now"+(L?" ("+L+")":"")+" \u2014 the record is in Runs.",L&&L>=400?"bad":"ok"),l()}).catch(h=>{y(!1),D(h)}))}function A(){e&&we("Rotate the secret","A new secret is made and shown once. Every sender still using the current one is refused from that moment until it is given the new secret.",()=>{S("POST","/admin/triggers/"+I(e.id)+"/secret",{}).then(h=>{l(),w({secret:h.secret,note:h.secretNote})}).catch(D)},"Rotate")}function U(){e&&S("DELETE","/admin/triggers/"+I(e.id)).then(()=>{O("Trigger deleted.","ok"),l(),s()}).catch(D)}let X=()=>{l(),s()};if(T)return t(Se,{title:e?"New secret for "+(d.name||e.id):"Your new webhook: "+(d.name||d.slug),onClose:X,footer:t(P,{kind:"primary",onClick:X,children:"Done"}),children:t(jd,{secret:T.secret,note:T.note,url:_,scheme:d.scheme})});let B=e?d.name||e.id:"New trigger",q=e?"Last changed "+he(e.updatedAt)+".":void 0;return t(Se,{title:B,subtitle:q,onClose:s,wide:!0,footer:t(W,{children:[t(P,{onClick:s,children:"Cancel"}),t(re,{id:"trigger-enabled",checked:d.enabled,disabled:a,onChange:h=>x({enabled:h}),children:"Enabled"}),t(me,{}),e?t(P,{onClick:()=>location.hash=Fn("runs",[],{trigger:e.id}),title:"Every run this trigger started, in Runs.",children:"Runs of this trigger"}):null,e?t(M,{disabled:k,onClick:R,title:"Fire it once, now, whatever the schedule says. The run is recorded like any other.",children:"Run now"}):null,t(M,{kind:"primary",disabled:!C,onClick:v,children:e?"Save":"Create"})]}),children:[t("div",{class:"form-grid",onInput:()=>g(null),children:[t($,{label:"Name",children:t("input",{type:"text",placeholder:"What it does \u2014 e.g. nightly digest",value:d.name,disabled:a,onInput:h=>{let L=h.currentTarget.value;x(d.slugTouched?{name:L}:{name:L,slug:xd(L)})}})}),t("div",{class:"field",children:[t("span",{class:"field-head",children:t("b",{children:"What runs"})}),t("div",{class:"scope-group",children:[t("div",{class:"field-line",children:[t("label",{class:"check",children:[t("input",{type:"radio",name:"target-kind",checked:d.targetKind==="function",disabled:a,onChange:()=>x({targetKind:"function",targetName:"",sync:!1})})," A function"]}),t("label",{class:"check",children:[t("input",{type:"radio",name:"target-kind",checked:d.targetKind==="workflow",disabled:a,onChange:()=>x({targetKind:"workflow",targetName:""})})," A workflow"]})]}),t("div",{class:"scope-indent",id:"target-picker",children:d.targetKind==="function"?t(Md,{functions:i,value:d.targetName,onChange:h=>x({targetName:h}),disabled:a}):t(Bd,{workflows:r,value:d.targetName,onChange:h=>x({targetName:h}),disabled:a})})]})]}),t("div",{class:"field",children:[t("span",{class:"field-head",children:t("b",{children:"When"})}),e?t(H,{children:["This trigger runs ",d.type==="schedule"?"on a schedule":d.type==="webhook"?"when called from outside":"when data changes",". A trigger\u2019s kind cannot change \u2014 for a different kind, make a new one and delete this."]}):t("div",{class:"tiles",role:"radiogroup","aria-label":"When it runs",children:Od.map(h=>t("label",{class:"tile"+(d.type===h.id?" on":""),children:[t("input",{type:"radio",name:"trigger-type",value:h.id,checked:d.type===h.id,disabled:a,onChange:()=>x({type:h.id})}),t("b",{children:h.title}),t("span",{class:"sub",children:h.words})]},h.id))}),d.type==="schedule"?t("div",{class:"when-body",children:[t(_t,{id:"trigger-schedule",value:d.cron,onChange:h=>x({cron:h}),preview:N,disabled:a}),t(G,{h:10}),t("div",{class:"grid2",children:[t("div",{class:"scope-group",children:[t("div",{class:"field-head",children:t("b",{children:"If a run is missed while the backend is off"})}),t("label",{class:"check",children:[t("input",{type:"radio",name:"missed",checked:d.missed==="skip",disabled:a,onChange:()=>x({missed:"skip"})})," Skip it \u2014 carry on from the next time"]}),t("label",{class:"check",children:[t("input",{type:"radio",name:"missed",checked:d.missed==="run-once-on-start",disabled:a,onChange:()=>x({missed:"run-once-on-start"})})," Run once when the backend starts"]})]}),t("div",{class:"scope-group",children:[t("div",{class:"field-head",children:t("b",{children:"If the last run is still going"})}),t("label",{class:"check",children:[t("input",{type:"radio",name:"overlap",checked:d.overlap==="skip",disabled:a,onChange:()=>x({overlap:"skip"})})," Skip this run"]}),t("label",{class:"check",children:[t("input",{type:"radio",name:"overlap",checked:d.overlap==="queue-one",disabled:a,onChange:()=>x({overlap:"queue-one"})})," Queue one, run it when the other finishes"]}),t("label",{class:"check",children:[t("input",{type:"radio",name:"overlap",checked:d.overlap==="allow",disabled:a,onChange:()=>x({overlap:"allow"})})," Run anyway, alongside it"]})]})]})]}):null,d.type==="webhook"?t("div",{class:"when-body",children:[t($,{label:"Path",children:t("div",{class:"field-line",children:[t("span",{class:"mono hint",children:["/hooks/",u,"/"]}),t("input",{type:"text",class:"mono","aria-label":"Hook path",placeholder:"stripe-payments",value:d.slug,disabled:a,onInput:h=>x({slug:h.currentTarget.value.trim(),slugTouched:!0})})]})}),t("div",{class:"field",children:[t("span",{class:"field-head",children:t("b",{children:"The URL to paste into the sender"})}),t("div",{class:"secret-box mono","data-webhook-url":!0,children:_}),t(V,{style:"margin-top:6px",children:[t(P,{tiny:!0,onClick:()=>Ye(_).then(h=>O(h?"URL copied.":"The browser refused the clipboard.",h?"ok":"bad")),children:"Copy the URL"}),e?t(M,{tiny:!0,onClick:A,title:"Make a new secret and show it once.",children:"Rotate the secret"}):null]})]}),t("div",{class:"grid2",children:[t("div",{class:"scope-group",children:[t("div",{class:"field-head",children:t("b",{children:"How the sender proves it is them"})}),t("label",{class:"check",children:[t("input",{type:"radio",name:"scheme",checked:d.scheme==="hmac-sha256",disabled:a,onChange:()=>x({scheme:"hmac-sha256"})})," Signature (GitHub, Stripe)"]}),t(H,{children:"An HMAC-SHA256 of the body in X-Hub-Signature-256, X-Signature-256 or X-Webhook-Signature. The secret never crosses the wire."}),t("label",{class:"check",children:[t("input",{type:"radio",name:"scheme",checked:d.scheme==="token",disabled:a,onChange:()=>x({scheme:"token"})})," Shared token"]}),t(H,{children:"The secret itself, in X-Webhook-Token, as a Bearer token, or as ?token=. For senders that cannot sign."})]}),t("div",{class:"scope-group",children:[t("div",{class:"field-head",children:t("b",{children:"Largest body accepted"})}),t("select",{"aria-label":"Largest body accepted",value:String(d.maxBodyBytes),disabled:a,onChange:h=>x({maxBodyBytes:Number(h.currentTarget.value)}),children:(si.some(h=>h.bytes===d.maxBodyBytes)?si:[{bytes:d.maxBodyBytes,label:Td(d.maxBodyBytes)+" (as saved)"},...si]).map(h=>t("option",{value:String(h.bytes),children:h.label},h.bytes))}),t(H,{children:"A bigger call is refused with 413 and recorded in Runs."})]})]}),e?t(H,{children:"The secret was shown when the hook was made and is not kept in a readable form. If it is lost, rotate it."}):t(H,{children:"The secret is made when you press Create and shown once."})]}):null,d.type==="db-change"?t("div",{class:"when-body",children:[t($,{label:"Collection",children:t(Hd,{collections:o,value:d.collection,onChange:h=>x({collection:h}),disabled:a})}),t("div",{class:"field",children:[t("span",{class:"field-head",children:t("b",{children:"When a record is"})}),t("div",{class:"field-line",id:"change-actions",children:da.map(h=>t("label",{class:"check",children:[t("input",{type:"checkbox",value:h.id,checked:d.actions.indexOf(h.id)!==-1,disabled:a,onChange:L=>x({actions:L.currentTarget.checked?[...d.actions,h.id]:d.actions.filter(Q=>Q!==h.id)})}),h.label]},h.id))}),t(H,{children:"The changed record is handed to the target as its body. Writes made by the target itself do not fire it again."})]})]}):null]}),d.type==="schedule"?t("div",{class:"field",children:[t("span",{class:"field-head",children:[t("b",{children:"With"})," ",t("span",{class:"hint",children:"what each run receives"})]}),t(St,{id:"trigger-payload",rows:d.payload,onChange:h=>x({payload:h}),disabled:a})]}):null,d.targetKind==="workflow"?t("div",{class:"field",children:[t("span",{class:"field-head",children:[t("b",{children:"Answer"})," ",t("span",{class:"hint",children:"what a caller gets back"})]}),t("div",{class:"scope-group",children:[t("label",{class:"check",children:[t("input",{type:"radio",name:"answer",checked:!d.sync,disabled:a,onChange:()=>x({sync:!1})})," The run\u2019s id, straight away"]}),t("label",{class:"check",children:[t("input",{type:"radio",name:"answer",checked:d.sync,disabled:a,onChange:()=>x({sync:!0})})," The workflow\u2019s output \u2014 wait up to",t("input",{type:"number",min:1,max:Ar,step:1,"aria-label":"Seconds to wait",class:"sched-minute",value:String(d.waitS),disabled:a||!d.sync,onInput:h=>x({waitS:Number(h.currentTarget.value)})}),"seconds"]}),t(H,{children:"Only a caller can be answered: a webhook, or Run now. A schedule has nobody to answer."})]})]}):null]}),b?t(J,{kind:"bad",style:"margin-top:12px",children:b}):E&&d.type?t(H,{children:E}):null,e?t(W,{children:[t(G,{h:18}),t(qe,{children:t(Ze,{label:"Delete",why:"It stops running and its record here is gone. Past runs stay in Runs.",title:"Delete trigger",warning:'"'+(e.name||e.id)+'" will not run again.'+(e.type==="webhook"?" Its URL stops answering and its secret is discarded.":""),expected:e.name||e.id,onConfirm:U})})]}):null]})}function Md({functions:e,value:n,onChange:i,disabled:r}){let o=e||[],s=o.find(c=>c.name===n),l=n?s||{name:n,deployed:!1}:null;return t(W,{children:[t(Ce,{fetch:c=>{let u=c.trim().toLowerCase();return Promise.resolve(o.filter(p=>!u||p.name.toLowerCase().indexOf(u)!==-1))},label:c=>c.name,detail:c=>c.deployed===!1?"not deployed":"",keyOf:c=>c.name,value:l,onPick:c=>i(c?c.name:""),onCreate:c=>i(c),createLabel:c=>"Use the name \u201C"+c+"\u201D (not deployed yet)",placeholder:e===null?"Loading functions\u2026":"Type a function name\u2026",emptyText:o.length?"No function by that name.":"No functions are deployed yet. Type a name to use one that is coming.",disabled:r}),l&&l.deployed===!1?t(H,{children:[t(K,{kind:"warn",children:"not deployed"})," Nothing called ",l.name," is deployed on this backend. The trigger will fail until it is."]}):null]})}function Bd({workflows:e,value:n,onChange:i,disabled:r}){let o=e||[],s=c=>c.name?String(c.name):c.id,l=n?o.find(c=>c.id===n)||{id:n}:null;return t(Ce,{fetch:c=>{let u=c.trim().toLowerCase();return Promise.resolve(o.filter(p=>!u||s(p).toLowerCase().indexOf(u)!==-1))},label:s,keyOf:c=>c.id,value:l,onPick:c=>i(c?c.id:""),placeholder:e===null?"Loading workflows\u2026":"Type a workflow name\u2026",emptyText:o.length?"No workflow by that name.":"No workflows yet. Author one in the editor and it appears here.",disabled:r})}function Hd({collections:e,value:n,onChange:i,disabled:r}){let o=e||[];return t(Ce,{fetch:l=>{let a=l.trim().toLowerCase();return Promise.resolve(o.filter(c=>!a||c.toLowerCase().indexOf(a)!==-1))},label:l=>l,keyOf:l=>l,value:n||null,onPick:l=>i(l||""),placeholder:e===null?"Loading collections\u2026":"Type a collection name\u2026",emptyText:o.length?"No collection by that name.":"No collections yet.",disabled:r})}function jd({secret:e,note:n,url:i,scheme:r}){let[o,s]=m(!1),l=_d(i,r,e);function a(c,u){Ye(c).then(p=>{p?(s(!0),O(u+" copied.","ok"),setTimeout(()=>s(!1),2500)):O("The browser refused the clipboard. Select it and copy by hand.","bad")})}return t("div",{class:"secret-card",children:[t(J,{kind:"warn",children:[n||"Store this now \u2014 it is not recoverable."," Paste it into the sender beside the URL."]}),t(G,{h:10}),t("div",{class:"field",children:[t("span",{class:"field-head",children:t("b",{children:"URL"})}),t("div",{class:"secret-box mono","data-webhook-url":!0,children:i}),t(V,{style:"margin-top:6px",children:t(P,{tiny:!0,onClick:()=>a(i,"URL"),children:"Copy the URL"})})]}),t(G,{h:10}),t("div",{class:"field",children:[t("span",{class:"field-head",children:t("b",{children:"Secret"})}),t("div",{class:"secret-box mono","data-secret":!0,children:e}),t(V,{style:"margin-top:6px",children:[t(P,{kind:"primary",onClick:()=>a(e,"Secret"),children:o?"Copied \u2713":"Copy secret"}),t(H,{children:r==="token"?"The sender puts it in X-Webhook-Token.":"The sender signs each body with it (HMAC-SHA256) and sends the signature in X-Hub-Signature-256."})]})]}),t(G,{h:10}),t("div",{class:"field",children:[t("span",{class:"field-head",children:t("b",{children:"Try it"})}),t("pre",{class:"secret-box mono","data-example":!0,children:l}),t(V,{style:"margin-top:6px",children:t(P,{tiny:!0,onClick:()=>a(l,"Command"),children:"Copy the command"})})]})]})}function Wd(e){let n=[],i={},r=(o,s)=>{if(s>40||o===null||typeof o!="object")return;if(Array.isArray(o)){o.forEach(a=>r(a,s+1));return}let l=o;if(typeof l.$path=="string"){let a=/^body\\.([^.[\\]]+)/.exec(l.$path);a&&!i[a[1]]&&(i[a[1]]=!0,n.push(a[1]));return}if(!("$literal"in l))for(let a of Object.keys(l))r(l[a],s+1)};return(e&&e.steps||[]).forEach(o=>r(o,0)),n}function Ud(e){return e.map(n=>({key:n,type:"text",raw:""}))}function Kd(e){return e?e.engineStatus==="cancelled"?"cancelled":e.status:""}function pa(e){return typeof e.name=="string"&&e.name?e.name:e.id}function ma({params:e}){let[n,i]=m(null),[r,o]=m({}),s=e[0]||"";function l(){S("GET","/admin/workflow-defs").then(c=>{i(c&&(c.workflows||c.definitions)||[]),o(c&&c.lastRuns||{})}).catch(D)}j(()=>{l()},[]);let a=s&&n&&n.find(c=>c.id===s)||null;return t(ce,{title:"Workflows",subtitle:"Step-by-step jobs this backend runs. Run one here; author them in the editor.",children:[t(V,{children:t(P,{onClick:l,children:"Refresh"})}),t(G,{}),n?t(ye,{columns:["Name","Steps","Last run",""],rows:n,empty:t(oe,{icon:"\u21F6",action:{label:"Refresh",onClick:l,write:!1},children:"No workflows yet. Workflows are authored in the editor."}),renderRow:c=>{let u=r[c.id],p=Kd(u);return t("tr",{id:"workflow-"+c.id,class:c.id===s?"hit":"",children:[t("td",{children:pa(c)}),t("td",{children:String((c.steps||[]).length)}),t("td",{children:u?t("a",{href:ne("runs",u.id),title:"Open the record",children:[t(K,{kind:cn(p),children:p}),t("span",{class:"hint",children:" "+he(u.startedAt)})]}):t("span",{class:"hint",children:"never"})}),t("td",{class:"actions",children:[t(P,{tiny:!0,onClick:()=>location.hash=Fn("runs",[],{workflow:c.id}),title:"Every run of this workflow",children:"Runs"}),t(M,{tiny:!0,kind:"primary",onClick:()=>Z("workflows",c.id),children:"Run"})]})]},c.id)}}):null,s&&n&&!a?t(J,{kind:"warn",children:["There is no workflow called \u201C",s,"\u201D."]}):null,a?t(Vd,{workflow:a,onClose:()=>Z("workflows")},a.id):null]})}function Vd({workflow:e,onClose:n}){let i=Wd(e),[r,o]=m(()=>Ud(i)),[s,l]=m(null),[a,c]=m(!1);function u(){let p;try{p=Ct(r)}catch(d){l(d.message);return}l(null),c(!0),S("POST","/admin/workflow-defs/"+I(e.id)+"/run",{payload:p,wait:!1}).then(d=>{c(!1);let f=d&&d.executionId||d&&d.run&&d.run.executionId||"";if(f){Z("runs",f);return}O("Run finished: "+(d&&d.run&&d.run.status||"see Runs")+".","ok"),n()}).catch(d=>{c(!1),l(d.message)})}return t(Se,{title:"Run "+pa(e),subtitle:((e.steps||[]).length===1?"1 step":(e.steps||[]).length+" steps")+", starting at "+ke(e.entry),onClose:n,footer:t(W,{children:[t(P,{onClick:n,children:"Cancel"}),t(M,{kind:"primary",disabled:a,onClick:u,children:"Run"})]}),children:[t("div",{id:"run-inputs",children:[i.length?t(W,{children:[t(fe,{children:"What this workflow reads"}),t(H,{children:["Its steps ask for ",i.map(p=>"\u201C"+p+"\u201D").join(", ")," by name. Fill those in; add anything else the steps should also start with."]})]}):t(W,{children:[t(fe,{children:"What each step starts with"}),t(H,{children:"This workflow reads nothing by name. Anything you add here reaches every step as its starting data."})]}),t(G,{h:8}),t(St,{id:"run-payload",rows:r,onChange:o,keyPlaceholder:"name"})]}),s?t(J,{kind:"bad",style:"margin-top:12px",children:s}):null]})}var fa=[{id:"workflow",label:"workflow"},{id:"function",label:"function"},{id:"backup",label:"backup"},{id:"maintenance",label:"maintenance"}];var kn=50,ha=5e3;function Gd(e,n){return[{name:"status",kind:"choice",ops:["is"],options:Vo.filter(i=>i).map(i=>({value:i,label:i}))},{name:"kind",kind:"choice",ops:["is"],options:fa.map(i=>({value:i.id,label:i.label}))},{name:"name",kind:"text",ops:["contains"]},{name:"trigger",kind:"choice",ops:["is"],options:e.map(i=>({value:i.id,label:i.name||i.id}))},{name:"workflow",kind:"choice",ops:["is"],options:n.map(i=>({value:i.id,label:typeof i.name=="string"&&i.name?i.name:i.id}))},{name:"started",kind:"date",ops:["within","on","before","after","between"]},{name:"duration (s)",kind:"number",ops:["gte"]}]}function qd(e,n=new Date){if(e.conj==="or"&&e.items.length>1)throw new Error('Runs can be filtered with "and" only.');let i={},r=(o,s)=>{if(i[o]!==void 0)throw new Error(o+" is asked twice.");i[o]=s};for(let o of e.items){if(o.kind==="group")throw new Error('Runs can be filtered with "and" only.');let s=o,l=(s.value||"").trim();switch(s.field){case"status":l&&r("status",l);break;case"kind":l&&r("kind",l);break;case"name":l&&r("name",l);break;case"trigger":l&&r("trigger",l);break;case"workflow":l&&r("workflowId",l);break;case"started":{if(s.op==="within"){let[a,c]=Wt(l||"today",n);r("since",String(a.getTime())),r("until",String(c.getTime()-1))}else if(s.op==="on"&&l){let a=De(l);r("since",String(a.getTime())),r("until",String(Re(a,1).getTime()-1))}else if(s.op==="before"&&l)r("until",String(De(l).getTime()-1));else if(s.op==="after"&&l)r("since",String(Re(De(l),1).getTime()));else if(s.op==="between"&&l&&(s.value2||"").trim()){let a=De(l),c=De((s.value2||"").trim()),[u,p]=a<=c?[a,c]:[c,a];r("since",String(u.getTime())),r("until",String(Re(p,1).getTime()-1))}break}case"duration (s)":{if(!l)break;let a=Number(l);if(isNaN(a)||a<0)throw new Error("duration needs a number of seconds.");r("minDurationMs",String(Math.round(a*1e3)));break}default:throw new Error('There is no field called "'+s.field+'".')}}return i}function ga(e){let n=[],i=e||{};return i.trigger&&n.push({kind:"cond",field:"trigger",op:"is",value:i.trigger}),i.workflow&&n.push({kind:"cond",field:"workflow",op:"is",value:i.workflow}),i.status&&n.push({kind:"cond",field:"status",op:"is",value:i.status}),i.kind&&n.push({kind:"cond",field:"kind",op:"is",value:i.kind}),i.name&&n.push({kind:"cond",field:"name",op:"contains",value:i.name}),{kind:"group",conj:"and",items:n}}function $d(e,n,i=kn){let r=[];for(let o of Object.keys(e).sort())r.push(I(o)+"="+I(e[o]));return r.push("limit="+i),n&&r.push("offset="+n),r.join("&")}function ba(e){return e.status==="running"&&e.kind==="workflow"}function ya({params:e,query:n}){let[i,r]=m([]),[o,s]=m([]),[l,a]=m(()=>ga(n)),[c,u]=m(null),[p,d]=m(null),[f,b]=m(0),[g,k]=m(0),y=e[0]||"",T=ae(null),w=Gd(i,o),x=JSON.stringify(n||{});j(()=>{a(ga(n)),k(0)},[x]),j(()=>{S("GET","/admin/triggers").then(h=>r(h&&h.triggers||[])).catch(()=>r([])),S("GET","/admin/workflow-defs").then(h=>s(h&&h.workflows||[])).catch(()=>s([]))},[]);let E=null,C=null;try{E=qd(l)}catch(h){C=h.message}let _=E?$d(E,g):null;function N(){_&&Ii("GET","/executions?"+_).then(({body:h,headers:L})=>{d(Vi(h));let Q=parseInt(L.get("X-Total-Count")||"",10);b(isNaN(Q)?Vi(h).length:Q),u(null)}).catch(D)}j(()=>{u(C),N()},[_,C]);let v=!!p&&p.some(h=>h.status==="running");j(()=>{if(!v)return;let h=setInterval(N,ha);return()=>clearInterval(h)},[v,_]);function R(h){we("Cancel this run",'"'+(h.workflowName||h.workflowId)+'" stops where it is. Steps already done are not undone; the record says cancelled.',()=>{S("POST","/admin/workflow-runs/"+I(h.id)+"/cancel",{}).then(()=>{O("Cancelling \u2014 the record says so when it has stopped.","ok"),setTimeout(N,600)}).catch(D)},"Cancel the run")}function A(h){S("GET","/executions/"+I(h)).then(L=>{ge(Q=>t(zd,{id:h,data:L||{},close:Q,onCancel:ba(L||{})?()=>R(L):void 0}),()=>{T.current=null,Z("runs")})}).catch(L=>{D(L),T.current=null,Z("runs")})}j(()=>{!p||!y||T.current===y||(T.current=y,A(y))},[p,y]);let U=h=>(i.find(L=>L.id===h)||{name:""}).name||h,X=f?g+1:0,B=Math.min(g+kn,f),q=l.items.length>0;return t(ce,{title:"Runs",subtitle:"Every function, trigger, workflow and backup run this backend has recorded.",children:[t("div",{id:"runs-filter",children:t(gt,{group:l,fields:w,flat:!0,onChange:h=>{a(h),k(0)}})}),c?t(H,{children:c}):null,t(G,{}),t(V,{children:[v?t(K,{kind:"accent",title:"A run is still going: this list re-reads every "+ha/1e3+" seconds.",children:"live"}):null,t("span",{class:"hint",id:"runs-count",children:[f?"Showing "+X+"\u2013"+B+" of "+f:p?"No runs":"",q&&E&&Object.keys(E).length?" where "+Et(l,w):""]}),t(me,{}),t(P,{tiny:!0,disabled:g===0,onClick:()=>k(Math.max(0,g-kn)),children:"\u2039 Newer"}),t(P,{tiny:!0,disabled:g+kn>=f,onClick:()=>k(g+kn),children:"Older \u203A"}),t(P,{onClick:N,children:"Refresh"})]}),t(G,{}),p?t(ye,{columns:["Started","Kind","Name","Trigger","Status","Duration",""],rows:p,empty:q?t(oe,{children:"No runs match."}):t(oe,{icon:"\u25B6",action:{label:"Open Workflows",onClick:()=>Z("workflows"),write:!1},children:"No runs recorded yet. Fire a trigger or run a workflow and it appears here."}),renderRow:(h,L)=>{let Q=Ki(h),se=h.metadata&&typeof h.metadata.triggerId=="string"?h.metadata.triggerId:"";return t("tr",{class:h.id===y?"hit":"",children:[t("td",{children:he(h.startedAt)}),t("td",{children:t("span",{class:"hint",children:ke(h.kind)})}),t("td",{children:ke(h.workflowName||h.workflowId||h.functionName)}),t("td",{children:se?t("a",{href:ne("triggers",se),title:"Open this trigger",children:U(se)}):ke(h.triggerType)}),t("td",{children:t(K,{kind:cn(Q),children:Q})}),t("td",{children:h.durationMs!==void 0&&h.durationMs!==null?h.durationMs+" ms":"\u2014"}),t("td",{class:"actions",children:[ba(h)?t(M,{tiny:!0,kind:"danger",onClick:()=>R(h),children:"Cancel"}):null,t(P,{tiny:!0,onClick:()=>Z("runs",h.id),children:"Detail"})]})]},h.id||L)}}):null]})}function zd({id:e,data:n,close:i,onCancel:r}){let o=qo(n);return t(ve,{title:"Execution "+e,wide:!0,autoFocus:!1,actions:t(W,{children:[r?t(M,{kind:"danger",onClick:r,children:"Cancel the run"}):null,t(P,{onClick:i,children:"Close"})]}),children:[t(Jd,{summary:o,word:Ki(n)}),t(Yd,{summary:o}),t(Xd,{steps:n.steps||[]}),t(He,{label:"Raw JSON",children:t("textarea",{value:JSON.stringify(n,null,2),readOnly:!0,style:"min-height:320px"})})]})}function Jd({summary:e,word:n}){let i=(r,o)=>o===""||o===null||o===void 0?null:t("span",{class:"rec-fact",children:[r+" ",t("b",{children:String(o)})]});return t("div",{class:"rec-band",children:[t(K,{kind:cn(n),children:n||"unknown"}),i("ran",e.workflow),i("triggered by",e.triggerSource||e.trigger),i("started",e.startedAt?he(e.startedAt):""),i("took",e.durationMs===void 0||e.durationMs===null?"":e.durationMs+" ms"),i("steps",e.stepCount),e.capped?t(K,{kind:"warn",children:"record capped"}):null,e.costLine?t("div",{class:"rec-cost",children:e.costLine}):null]})}var Pr=5;function Yd({summary:e}){if(!e.failures.length)return null;let n=e.failures.length;return t("div",{class:"rec-danger",children:[t("div",{class:"rec-danger-head",children:n===1?"1 step failed":n+" steps failed"}),e.failures.slice(0,Pr).map(i=>t(W,{children:[t("div",{class:"rec-danger-step",children:i.step+(i.type?" \xB7 "+i.type:"")}),i.message?t("div",{class:"rec-danger-why",children:i.message}):null,i.detail?Object.keys(i.detail).map(r=>{let o=i.detail[r];return o===null||typeof o=="object"?null:t("div",{class:"rec-danger-why",children:r+": "+String(o)})}):null]})),n>Pr?t("div",{class:"rec-danger-why",children:"and "+(n-Pr)+" more \u2014 every step is in the table below."}):null]})}function Xd({steps:e}){return e.length?t("div",{class:"scroller",children:t("table",{children:[t("thead",{children:t("tr",{children:["","Step","Type","Duration","Status"].map((n,i)=>t("th",{children:n},i))})}),t("tbody",{children:e.map((n,i)=>t(Zd,{step:n,index:i},i))})]})}):t("div",{class:"empty",children:"This run recorded no steps."})}function Zd({step:e,index:n}){let[i,r]=m(!1);return t(W,{children:[t("tr",{class:"step-row"+(i?" open":""),onClick:()=>r(!i),children:[t("td",{children:String(n+1)}),t("td",{children:ke(e.nodeName||e.nodeId)}),t("td",{children:ke(e.nodeType)}),t("td",{children:e.durationMs===void 0||e.durationMs===null?"\u2014":e.durationMs+" ms"}),t("td",{children:t(K,{kind:Go(e.status),children:ke(e.status)})})]}),t("tr",{class:"step-pane"+(i?"":" hidden"),children:t("td",{colSpan:5,children:t(Qd,{step:e})})})]})}function Qd({step:e}){let n=(s,l)=>t("div",{class:"pane-part",children:[t("h4",{children:s}),t("div",{class:"jx",children:t(an,{value:l,name:null,depth:0,openTo:2})})]}),i=e.inputData!==void 0&&e.inputData!==null,r=e.outputData!==void 0&&e.outputData!==null,o=!e.errorMessage&&e.status!=="skipped"&&!i&&!r;return t("div",{children:[e.errorMessage?t("div",{class:"pane-part",children:[t("h4",{children:"Error"}),t("div",{class:"rec-danger-why",children:e.errorMessage})]}):null,e.status==="skipped"?t("div",{class:"pane-part",children:[t("h4",{children:"Skipped"}),t("div",{class:"jx-meta",children:"This step was not reached \u2014 the run took another path."})]}):null,i?n("Input",e.inputData):null,r?n("Output",e.outputData):null,o?t("div",{class:"jx-meta",children:"This step recorded no data."}):null]})}var wn=[{id:"images",label:"Images",line:"PNG, JPEG, GIF, WebP, BMP, icons and SVG",types:["image/png","image/jpeg","image/gif","image/webp","image/bmp","image/x-icon","image/svg+xml"]},{id:"documents",label:"Documents and data",line:"PDF, plain text, CSV and JSON",types:["application/pdf","text/plain","text/csv","application/json"]},{id:"audio",label:"Audio",line:"MP3",types:["audio/mpeg"]},{id:"archives",label:"Archives",line:"ZIP",types:["application/zip"]},{id:"web",label:"Web pages and scripts",line:"HTML, CSS and JavaScript \u2014 a page a browser would run",types:["text/html","text/css","text/javascript"]},{id:"unknown",label:"Executables and anything unrecognised",line:"Programs, installers and every other binary the backend cannot identify",types:["application/octet-stream"]}],eu=wn.reduce((e,n)=>e.concat(n.types),[]);function va(e){let n=[];for(let i of wn)if(e.categories.indexOf(i.id)!==-1)for(let r of i.types)n.indexOf(r)===-1&&n.push(r);for(let i of e.custom){let r=i.trim().toLowerCase();r&&n.indexOf(r)===-1&&n.push(r)}return n}function ka(e){let n=new Set(e.map(s=>s.trim().toLowerCase()).filter(Boolean)),i=[],r=new Set;for(let s of wn)if(s.types.every(l=>n.has(l))){i.push(s.id);for(let l of s.types)r.add(l)}let o=[];return n.forEach(s=>{r.has(s)||o.push(s)}),{categories:i,custom:o}}function wa(e){let n=e.trim().toLowerCase();return n?/^[a-z0-9!#$&^_.+-]+\\/[a-z0-9!#$&^_.+-]+$/.test(n)?null:"A content type looks like image/png or application/pdf.":"Type a content type."}function Ca(e){return eu.indexOf(e.trim().toLowerCase())!==-1}function li(e,n,i){return e+" "+(e===1?n:i)}function tu(e){let n=(e.tooNew||[]).length;return"Done: "+li(e.orphanBlobs.length,"file","files")+" no record knows about, "+li(e.orphanRows.length,"record","records")+" whose file is missing"+(e.deleted&&e.orphanBlobs.length?" \u2014 the unknown files were deleted":"")+(n?"; "+li(n,"file is","files are")+" too new to judge \u2014 under "+(e.graceMinutes||5)+" minutes old and perhaps still arriving \u2014 so "+(n===1?"it was":"they were")+" left alone":"")+"."}var ai=50;function Sa(e){try{let n=new URL(e,location.href);return n.pathname+n.search}catch{return e}}function Ta(e){return/^image\\//.test(e)}var nu=e=>S("POST","/admin/triggers/preview",{cron:e,count:5});function Ra(e){let[n,i]=m(null),[r,o]=m(0);function s(){S("GET","/admin/files/config").then(c=>{i(c),o(u=>u+1)}).catch(D)}j(()=>{s()},[]);let l=n&&n.config||{},a=l.thumbnails&&l.thumbnails.presets||{};return t(ce,{title:"Storage",subtitle:"The files your app has stored, which kinds are allowed, how they are thumbnailed, and the clean-up.",children:n?t("div",{children:[t(V,{children:[t(K,{kind:"accent",children:"stored "+(n.driverKind==="s3"?"in S3":"on this machine")}),n.transformsAvailable?t(K,{kind:"ok",children:"thumbnails available"}):t(K,{kind:"warn",title:ke(n.transformUnavailableReason),children:"thumbnails unavailable on this backend"})]}),t(G,{}),t(iu,{presets:a,transforms:!!n.transformsAvailable}),t("h2",{children:"Where files are stored"}),t(su,{config:l,credentialsConfigured:!!n.s3CredentialsConfigured,reload:s},"where"+r),n.driverKind==="s3"?t(lu,{},"move"+r):null,t("h2",{children:"Settings"}),t(cu,{config:l,reload:s},"limits"+r),t("h2",{children:"Thumbnail presets"}),t(mu,{presets:a,reload:s},"presets"+r),t("h2",{children:"Clean-up"}),t(fu,{config:l,reload:s},"sweep"+r)]}):null})}function iu({presets:e,transforms:n}){let{readonly:i}=te(),[r,o]=m(null),[s,l]=m(0),[a,c]=m(0),[u,p]=m(""),[d,f]=m({}),[b,g]=m({}),[k,y]=m(!1),[T,w]=m(!1),[x,E]=m(null),C=ae(null),_=e.sm?"sm":Object.keys(e)[0]||"";function N(){S("GET","/admin/files?limit="+ai+"&offset="+a+(u?"&q="+I(u):"")).then(B=>{o(B.files),l(B.count);let q=B.files.map(h=>h.name);q.length&&S("GET","/admin/files/uses?names="+I(q.join(","))).then(h=>f(L=>({...L,...h.uses}))).catch(D);for(let h of B.files)!Ta(h.contentType)||b[h.name]||S("GET","/files/"+I(h.name)+"/sign").then(L=>g(Q=>({...Q,[h.name]:Sa(L.url)}))).catch(()=>{})}).catch(D)}j(N,[a,u]);let v=async B=>{y(!0),E(null);try{let q={...ut(_e.get().credential),"content-type":B.type||"application/octet-stream"},h=await fetch("/files/"+I(B.name),{method:"POST",headers:q,body:B}),L=await h.json().catch(()=>null);if(!h.ok)throw new Error(L&&(L.error||L.message)||"HTTP "+h.status);O("Uploaded "+B.name+".","ok"),c(0),N()}catch(q){E(q.message)}finally{y(!1)}},R=B=>{if(B)for(let q=0;q<B.length;q++)v(B[q])};function A(B,q){S("DELETE","/admin/files/"+I(B.name)+(q?"?clear=1":"")).then(()=>{O("Deleted "+B.originalName+(q?" and cleared its fields.":"."),"ok"),N()}).catch(h=>{if(h instanceof Ft&&h.status===409){let L=d[B.name]||[];ge(Q=>t(ve,{title:'"'+B.originalName+'" is in use',autoFocus:!1,actions:t(W,{children:[t(P,{onClick:Q,children:"Keep it"}),t(M,{kind:"danger",onClick:()=>{Q(),A(B,!0)},children:"Delete anyway and clear "+L.length+" field"+(L.length===1?"":"s")})]}),children:[t("div",{class:"notice warn",children:h.message}),t("ul",{class:"uses-list",children:L.map(se=>t("li",{children:[t("a",{href:ne("collections",se.collection,se.objectId),children:se.collection+" \xB7 "+se.objectId}),t("span",{class:"hint",children:" in "+se.field})]},se.collection+se.objectId+se.field))})]}));return}D(h)})}let U=s?a+1:0,X=Math.min(a+ai,s);return t(pe,{children:[t("div",{id:"file-dropzone",class:"dropzone"+(T?" over":""),role:"button",tabIndex:0,"aria-label":"Upload files",onClick:()=>!i&&C.current&&C.current.click(),onKeyDown:B=>{(B.key==="Enter"||B.key===" ")&&C.current&&(B.preventDefault(),C.current.click())},onDragOver:B=>{B.preventDefault(),w(!0)},onDragLeave:()=>w(!1),onDrop:B=>{B.preventDefault(),w(!1),i||R(B.dataTransfer?B.dataTransfer.files:null)},children:[t("input",{ref:C,type:"file",multiple:!0,hidden:!0,disabled:i,onChange:B=>R(B.currentTarget.files)}),k?"Uploading\u2026":i?"Uploads need full backend access.":"Drop files here, or click to choose",x?t("span",{class:"chips-problem",children:x}):null]}),t(G,{}),t(V,{children:[t("input",{type:"search",id:"files-search",placeholder:"Find by name",value:u,onInput:B=>{p(B.currentTarget.value),c(0)},style:"flex:1 1 200px"}),t("span",{class:"hint",id:"files-count",children:s?"Showing "+U+"\u2013"+X+" of "+s:r?"No files":""}),t(P,{tiny:!0,disabled:a===0,onClick:()=>c(Math.max(0,a-ai)),children:"\u2039 Newer"}),t(P,{tiny:!0,disabled:X>=s,onClick:()=>c(a+ai),children:"Older \u203A"})]}),t(G,{}),r?t(ye,{class:"files",columns:["","File","Size","Kind","Uploaded","Used by",""],rows:r,renderRow:B=>{let q=d[B.name],h=b[B.name];return t("tr",{"data-file":B.name,children:[t("td",{class:"thumb-cell",children:Ta(B.contentType)&&h&&n&&_?t("img",{class:"file-thumb",src:h+"&thumb="+I(_),alt:"",onError:L=>L.currentTarget.style.display="none"}):t("span",{class:"file-icon","aria-hidden":"true",children:"\u25A4"})}),t("td",{children:[t("b",{children:B.originalName||rt(B.name)}),B.private?t(K,{kind:"warn",children:"private"}):null]}),t("td",{children:mt(B.size)}),t("td",{children:t("span",{class:"chip type",children:B.contentType})}),t("td",{children:he(B.createdAt)}),t("td",{class:"uses-cell",children:[q===void 0?t("span",{class:"hint",children:"\u2026"}):q.length===0?t("span",{class:"hint",children:"nothing"}):q.slice(0,3).map((L,Q)=>t("span",{children:[Q?", ":"",t("a",{href:ne("collections",L.collection,L.objectId),title:L.field,children:L.collection+" \xB7 "+L.objectId.slice(0,8)})]},Q)),q&&q.length>3?t("span",{class:"hint",children:" +"+(q.length-3)+" more"}):null]}),t(Je,{children:[t("a",{class:"btn tiny",href:h||"/files/"+I(B.name),download:B.originalName||rt(B.name),target:"_blank",rel:"noopener",onClick:L=>{h||(L.preventDefault(),S("GET","/files/"+I(B.name)+"/sign").then(Q=>{let se=document.createElement("a");se.href=Sa(Q.url),se.download=B.originalName||rt(B.name),se.rel="noopener",document.body.appendChild(se),se.click(),se.remove()}).catch(D))},children:"Download"}),t(M,{tiny:!0,kind:"danger",onClick:()=>we("Delete "+(B.originalName||rt(B.name))+"?",q&&q.length?"It is used by "+q.length+" record"+(q.length===1?"":"s")+"; the delete will ask before clearing them.":"No record points at it. The file is gone for good.",()=>A(B,!1)),children:"Delete"})]})]},B.name)},empty:u?t(oe,{children:"No file is called that."}):t(oe,{children:"Nothing stored yet. Drop a file above, or let your app upload one."})}):null]})}function ru(e){let n=e.driver||{};return{where:n.type==="s3"?"s3":"local",endpoint:n.endpoint||"",region:n.region||"us-east-1",bucket:n.bucket||"",pathStyle:n.forcePathStyle!==!1,accessKeyId:"",secretAccessKey:""}}function ou(e,n){if(e.where!=="s3")return null;if(!e.endpoint.trim())return"The bucket needs an endpoint (the address of the S3-compatible service).";if(!/^https?:\\/\\//.test(e.endpoint.trim()))return"The endpoint starts with https:// (or http:// for a service on this network).";if(!e.bucket.trim())return"The bucket needs a name.";let i=!!e.accessKeyId||!!e.secretAccessKey;return i&&(!e.accessKeyId||!e.secretAccessKey)?"Type both the access key id and the secret access key.":!i&&!n?"The bucket needs an access key id and a secret access key.":null}function xa(e){if(e.where!=="s3")return{driver:{type:"local"}};let n={driver:{type:"s3",endpoint:e.endpoint.trim(),region:e.region.trim()||"us-east-1",bucket:e.bucket.trim(),forcePathStyle:e.pathStyle}};return e.accessKeyId&&e.secretAccessKey&&(n.s3Credentials={accessKeyId:e.accessKeyId,secretAccessKey:e.secretAccessKey}),n}function su({config:e,credentialsConfigured:n,reload:i}){let{readonly:r}=te(),[o,s]=m(ru(e)),[l,a]=m(null),[c,u]=m(!1),p=ou(o,n),d=y=>{s(T=>({...T,...y})),a(null)},f=o.where==="local"||!!l&&l.ok;function b(){if(p){D(new Error(p));return}u(!0),S("POST","/admin/files/config/test",xa(o)).then(y=>a({ok:!!y.ok,words:y.words})).catch(y=>a({ok:!1,words:y.message})).then(()=>u(!1))}function g(){if(p){D(new Error(p));return}S("PUT","/admin/files/config",xa(o)).then(()=>{O(o.where==="s3"?'New uploads go to the bucket "'+o.bucket.trim()+'".':"New uploads are stored on this machine.","ok"),i()}).catch(D)}let k=(y,T,w)=>t("label",{class:"tile"+(o.where===y?" on":""),children:[t("input",{type:"radio",name:"where",value:y,checked:o.where===y,disabled:r,onChange:()=>d({where:y})}),t("b",{children:T}),t("span",{class:"sub",children:w})]});return t(pe,{id:"where-card",children:[t("div",{class:"tiles",role:"radiogroup","aria-label":"Where files are stored",children:[k("local","This machine","A folder beside the backend's data. Fine on a laptop; it fills a small server's disk."),k("s3","An S3-compatible bucket","AWS S3, MinIO, Backblaze, Cloudflare R2, Hetzner\u2026 Uploads and backup archives go there instead of the disk.")]}),o.where==="s3"?t("div",{id:"bucket-form",children:[t(G,{h:8}),t("div",{class:"grid2",children:[t($,{label:"Endpoint",children:t("input",{type:"url",id:"s3-endpoint",placeholder:"https://s3.example.com",value:o.endpoint,disabled:r,onInput:y=>d({endpoint:y.currentTarget.value})})}),t($,{label:"Region",children:t("input",{type:"text",id:"s3-region",placeholder:"us-east-1",value:o.region,disabled:r,onInput:y=>d({region:y.currentTarget.value})})}),t($,{label:"Bucket",children:t("input",{type:"text",id:"s3-bucket",placeholder:"my-app-files",value:o.bucket,disabled:r,onInput:y=>d({bucket:y.currentTarget.value})})})]}),t(re,{id:"s3-path-style",checked:o.pathStyle,onChange:y=>d({pathStyle:y}),disabled:r,children:"Path-style addressing"}),t(fe,{children:"On for MinIO and most self-hosted services (the bucket is in the path). Off for AWS S3's own style (the bucket is in the hostname)."}),t(G,{h:8}),t("div",{class:"field-head",children:[t("b",{children:"Key"}),n?t(K,{kind:"ok",children:"key: configured"}):t(K,{kind:"warn",children:"key: not configured"}),t("span",{class:"hint",children:n?"Leave both blank to keep the stored key. It is never shown.":"From your storage provider. Stored on this backend, never shown again."})]}),t("div",{class:"grid2",children:[t($,{label:"Access key id",children:t("input",{type:"text",id:"s3-access-key-id",autocomplete:"off",value:o.accessKeyId,disabled:r,onInput:y=>d({accessKeyId:y.currentTarget.value})})}),t($,{label:"Secret access key",children:t("input",{type:"password",id:"s3-secret",autocomplete:"new-password",value:o.secretAccessKey,disabled:r,onInput:y=>d({secretAccessKey:y.currentTarget.value})})})]}),l?t("div",{id:"s3-test-result",class:"notice "+(l.ok?"ok":"bad"),children:l.words}):null]}):null,t(fe,{children:"Files already stored on this machine stay there until you move them; the backend keeps serving them from here. Switching changes where NEW uploads and backups go."}),t(V,{style:"margin-top:12px",children:[o.where==="s3"?t(M,{tiny:!0,id:"s3-test",disabled:c||!!p,title:p||void 0,onClick:b,children:c?"Testing\u2026":"Test connection"}):null,t(M,{tiny:!0,kind:"primary",id:"save-where",disabled:!f||!!p,title:f?p||void 0:"Test the connection first.",onClick:g,children:"Save"})]})]})}function au(e){let n=e.progress,i=o=>o===1?"1 file":o+" files";if(n.state==="running")return"Moving "+(n.moved+n.failed.length+1>n.total?n.total:n.moved+n.failed.length+1)+" of "+n.total+"\u2026 "+mt(n.bytes)+" so far. The app keeps working while they move.";let r=n.state==="done"?i(n.moved)+" moved to the bucket"+(n.failed.length?", "+i(n.failed.length)+" left where "+(n.failed.length===1?"it was":"they were"):"")+". ":"";return e.onThisMachine?r+(e.onThisMachine===1?"1 file is":e.onThisMachine+" files are")+" still on this machine. They keep serving from here; new uploads go to the bucket.":r+"Every file is in the bucket."}function lu(){let[e,n]=m(null),i=ae(null);function r(){S("GET","/admin/files/move").then(c=>{n(c),c.progress.state==="running"?i.current=window.setTimeout(r,1e3):i.current!==null&&(i.current=null)}).catch(D)}j(()=>(r(),()=>{i.current!==null&&window.clearTimeout(i.current)}),[]);function o(){S("POST","/admin/files/move",{}).then(c=>{n(c),i.current=window.setTimeout(r,1e3)}).catch(D)}if(!e)return null;let s=e.progress,l=s.state==="running",a=e.onThisMachine;return t(pe,{id:"move-card",children:[t("div",{id:"move-words",children:au(e)}),l?t("progress",{id:"move-progress",style:"width:100%",max:s.total||1,value:s.moved+s.failed.length}):null,s.failed.length?t("ul",{id:"move-failed",class:"sub",children:s.failed.map(c=>t("li",{children:[c.name?t("b",{children:c.name}):null," ",c.error]},c.name))}):null,a>0&&!l?t(V,{style:"margin-top:12px",children:t(M,{tiny:!0,kind:"primary",id:"move-start",onClick:()=>we("Move "+(a===1?"1 file":a+" files")+" to the bucket?","Each file is copied into the bucket, checked, and only then removed from this machine \u2014 a file that cannot be moved stays where it is and keeps working.",o,"Move"),children:"Move them to the bucket"})}):null]})}function cu({config:e,reload:n}){let{readonly:i}=te(),[r,o]=m(String(Math.round((e.maxUploadBytes||0)/(1024*1024)))),[s,l]=m(String(e.signedUrlTtlSeconds||300)),[a,c]=m(ka(e.contentTypes&&e.contentTypes.denyList||[])),u=(d,f)=>c(b=>({...b,categories:f?b.categories.concat(b.categories.indexOf(d)===-1?[d]:[]):b.categories.filter(g=>g!==d)}));function p(){S("PUT","/admin/files/config",{maxUploadBytes:Math.max(1,Number(r)||1)*1024*1024,contentTypes:{denyList:va(a)},signedUrlTtlSeconds:Math.max(1,Number(s)||300)}).then(()=>{O("Storage settings saved.","ok"),n()}).catch(D)}return t(pe,{children:[t("div",{class:"grid2",children:[t($,{label:"Largest upload (MB)",children:t("input",{type:"number",min:"1",id:"max-mb",value:r,disabled:i,onInput:d=>o(d.currentTarget.value)})}),t($,{label:"A private file's link stays open for (seconds)",children:t("input",{type:"number",min:"1",id:"signed-ttl",value:s,disabled:i,onInput:d=>l(d.currentTarget.value)})})]}),t(G,{}),t("div",{class:"field-head",children:[t("b",{children:"Refuse these kinds"}),t("span",{class:"hint",children:"An upload is judged by its bytes, never by the name or the type the app claims."})]}),t("div",{class:"kind-boxes",id:"kind-boxes",children:wn.map(d=>t("label",{class:"check kind-box",children:[t("input",{type:"checkbox",value:d.id,checked:a.categories.indexOf(d.id)!==-1,disabled:i,onChange:f=>u(d.id,f.currentTarget.checked)}),t("span",{children:[t("b",{children:d.label}),t("span",{class:"sub",children:d.line})]})]},d.id))}),t(G,{h:8}),t("div",{class:"field-head",children:[t("b",{children:"Also refuse these types"}),t("span",{class:"hint",children:"One the backend can identify is refused; one it cannot is listed here and matches nothing."})]}),t("div",{id:"custom-types",children:t(Ie,{items:a.custom,onChange:d=>c(f=>({...f,custom:d})),validate:wa,normalise:d=>d.trim().toLowerCase(),label:d=>Ca(d)?d:d+" (never identified)",placeholder:"application/x-msdownload",addLabel:"Add type",disabled:i})}),t(V,{style:"margin-top:12px",children:t(M,{tiny:!0,kind:"primary",id:"save-limits",onClick:p,children:"Save settings"})})]})}function du(e){return Object.keys(e).map(n=>({name:n,width:String(e[n].width),height:String(e[n].height),fit:e[n].fit||"cover"}))}function uu(e){let n=new Set;for(let i of e){let r=i.name.trim();if(!r)return"Every preset needs a name.";if(!/^[a-zA-Z0-9_-]+$/.test(r))return'A preset name is letters, digits, "_" or "-" (it goes in a URL: ?thumb='+r+").";if(n.has(r))return'Two presets are called "'+r+'".';n.add(r);let o=Number(i.width),s=Number(i.height);if(!(o>0)||!(s>0)||!Number.isInteger(o)||!Number.isInteger(s))return'"'+r+'" needs a whole width and height in pixels.'}return null}function pu(e){let n={};for(let i of e)n[i.name.trim()]={width:Number(i.width),height:Number(i.height),fit:i.fit==="contain"?"contain":"cover"};return n}function mu({presets:e,reload:n}){let{readonly:i}=te(),[r,o]=m(du(e)),s=uu(r);function l(){if(s){D(new Error(s));return}S("PUT","/admin/files/config",{thumbnails:{presets:pu(r)}}).then(()=>{O("Thumbnail presets saved.","ok"),n()}).catch(D)}return t(pe,{children:[t(fe,{children:"A named preset is public: any app may ask for ?thumb=name. Sizes that are not a preset are admin-only."}),t(G,{h:8}),t(Xe,{id:"presets",rows:r,onChange:o,blank:()=>({name:"",width:"128",height:"128",fit:"cover"}),addLabel:"Add preset",disabled:i,head:t("div",{class:"list-row list-head",children:[t("span",{style:"flex:1 1 140px",children:"Name"}),t("span",{style:"width:80px",children:"Width"}),t("span",{style:"width:80px",children:"Height"}),t("span",{style:"width:110px",children:"Fit"})]}),renderRow:(a,c)=>t(W,{children:[t("input",{type:"text",value:a.name,placeholder:"name","aria-label":"Preset name",disabled:i,onInput:u=>c({...a,name:u.currentTarget.value})}),t("input",{type:"number",min:"1",style:"width:80px",value:a.width,"aria-label":"Width",disabled:i,onInput:u=>c({...a,width:u.currentTarget.value})}),t("input",{type:"number",min:"1",style:"width:80px",value:a.height,"aria-label":"Height",disabled:i,onInput:u=>c({...a,height:u.currentTarget.value})}),t("select",{style:"width:110px",value:a.fit,"aria-label":"Fit",disabled:i,onChange:u=>c({...a,fit:u.currentTarget.value}),children:[t("option",{value:"cover",children:"crop to fill"}),t("option",{value:"contain",children:"fit inside"})]})]})}),s?t(H,{children:s}):null,t(V,{style:"margin-top:12px",children:t(M,{tiny:!0,kind:"primary",id:"save-presets",disabled:!!s,onClick:l,children:"Save presets"})})]})}function fu({config:e,reload:n}){let{readonly:i}=te(),r=e.orphanSweep||{enabled:!1,cron:"0 3 * * *"},[o,s]=m(!!r.enabled),[l,a]=m(ke(r.cron)||"0 3 * * *"),u=(e.sweepStatus||{}).lastReport;function p(){S("PUT","/admin/files/config",{orphanSweep:{enabled:o,cron:l.trim()}}).then(()=>{O(o?"Clean-up scheduled.":"Clean-up schedule turned off.","ok"),n()}).catch(D)}function d(f){O("Looking for orphans\u2026"),S("POST","/admin/files/sweep",{deleteOrphans:f}).then(b=>{let g=b.report;O(tu(g),"ok"),n()}).catch(D)}return t(pe,{children:[t(V,{children:[u?t(K,{kind:u.error?"bad":u.orphanBlobs.length||u.orphanRows.length?"warn":"ok",children:"last checked "+he(u.at)+" \u2014 "+li(u.orphanBlobs.length,"unknown file","unknown files")+", "+u.orphanRows.length+" missing"+((u.tooNew||[]).length?", "+(u.tooNew||[]).length+" too new to judge":"")}):t(K,{children:"never run"}),t(me,{}),t(M,{tiny:!0,onClick:()=>d(!1),children:"Check now"}),t(M,{tiny:!0,kind:"danger",onClick:()=>we("Delete unknown files?","Files no record points at are deleted for good. Records whose file is missing are only reported.",()=>d(!0),"Check and delete"),children:"Check now and delete unknown files"})]}),t(G,{}),t(re,{id:"sweep-enabled",checked:o,onChange:s,disabled:i,children:"Check on a schedule"}),t(fe,{children:"A scheduled check only reports \u2014 it never deletes. Records whose file went missing are a data problem for a person, never cleaned up on their own."}),o?t("div",{class:"when-body",id:"sweep-schedule",children:t(_t,{id:"sweep-cron",value:l,onChange:a,preview:nu,disabled:i})}):null,t(V,{style:"margin-top:12px",children:t(M,{tiny:!0,kind:"primary",id:"save-sweep",onClick:p,children:"Save schedule"})})]})}var Zt=[{id:"gmail",label:"Gmail / Google Workspace",line:"A Google account sends the mail",host:"smtp.gmail.com",port:587,secure:!1,usernameHint:"Your full Gmail or Workspace address",passwordHint:"An App password, not your Google password \u2014 Google refuses the real one here",credentialUrl:"https://myaccount.google.com/apppasswords",credentialLabel:"Make an App password"},{id:"resend",label:"Resend",line:"Transactional email, an API key signs in",host:"smtp.resend.com",port:465,secure:!0,usernameHint:"The word resend",passwordHint:"An API key from Resend",credentialUrl:"https://resend.com/api-keys",credentialLabel:"Make an API key"},{id:"postmark",label:"Postmark",line:"The Server API token is both name and password",host:"smtp.postmarkapp.com",port:587,secure:!1,usernameHint:"Your Server API token",passwordHint:"The same Server API token again",credentialUrl:"https://account.postmarkapp.com/servers",credentialLabel:"Find the Server API token"},{id:"ses",label:"Amazon SES",line:"Pick the region your SES lives in",host:"email-smtp.{region}.amazonaws.com",port:587,secure:!1,usernameHint:"The SMTP user name from SES (not an IAM access key)",passwordHint:"The SMTP password SES made with it",credentialUrl:"https://console.aws.amazon.com/ses/home#/smtp",credentialLabel:"Make SMTP credentials"},{id:"mailgun",label:"Mailgun",line:"One sending domain, a postmaster login",host:"smtp.mailgun.org",port:587,secure:!1,usernameHint:"postmaster@ your sending domain",passwordHint:"The SMTP password for that domain",credentialUrl:"https://app.mailgun.com/mailgun-cp/sending/domains",credentialLabel:"Open your sending domains"},{id:"brevo",label:"Brevo",line:"Formerly Sendinblue",host:"smtp-relay.brevo.com",port:587,secure:!1,usernameHint:"The email address you log in to Brevo with",passwordHint:"An SMTP key, not your Brevo password",credentialUrl:"https://app.brevo.com/settings/keys/smtp",credentialLabel:"Make an SMTP key"},{id:"other",label:"Other",line:"Any SMTP server \u2014 type its host and port",host:"",port:587,secure:!1,usernameHint:"Whatever your provider gave you",passwordHint:"Whatever your provider gave you",credentialUrl:"",credentialLabel:""}],di=["us-east-1","us-east-2","us-west-2","eu-west-1","eu-west-2","eu-west-3","eu-central-1","eu-north-1","ap-south-1","ap-southeast-1","ap-southeast-2","ap-northeast-1","ap-northeast-2","ca-central-1","sa-east-1"];function ci(e){return Zt.find(n=>n.id===e)||Zt[Zt.length-1]}function hu(e,n){return e.host.replace("{region}",n||di[0])}var gu=/^email-smtp\\.([a-z0-9-]+)\\.amazonaws\\.com$/;function Ea(e){let n=(e||"").trim().toLowerCase(),i=gu.exec(n);return i?{preset:ci("ses"),region:i[1]}:{preset:Zt.find(o=>o.host&&o.host.toLowerCase()===n)||ci("other")}}function _a(e,n){return e.id==="other"?{host:"",port:"",secure:!1}:{host:hu(e,n),port:String(e.port),secure:e.secure}}var Ir={passwordReset:{title:"Password reset",line:"Sent when someone asks to reset their password."},verifyEmail:{title:"Verify your email",line:"Sent on sign-up when verification is on, and when someone asks again."},magicLink:{title:"Sign-in link",line:"Sent when someone signs in with a link instead of a password."},emailCode:{title:"Sign-in code",line:"Sent when someone signs in with a six-digit code instead of a password."}};function bu(e){return(Ir[e]||{title:e}).title}function Aa(e){return"{{"+e+"}}"}function yu(e,n,i,r){let o=Math.max(0,Math.min(n,e.length)),s=Math.max(o,Math.min(i,e.length));return{value:e.slice(0,o)+r+e.slice(s),caret:o+r.length}}var Pa="nodegx.admin.email.testTo";function Ia(){try{return localStorage.getItem(Pa)||""}catch{return""}}function La(e){try{localStorage.setItem(Pa,e)}catch{}}function Na({params:e}){let[n,i]=m(null),[r,o]=m(null),[s,l]=m(!1),[a,c]=m(0),u=e[0]||null;function p(){return l(!1),S("GET","/admin/email/templates").then(b=>o(b.templates||[])).catch(()=>l(!0))}function d(){S("GET","/admin/email/config").then(b=>{i(b),c(g=>g+1),p()}).catch(D)}j(()=>{d()},[]);let f=u&&r&&r.find(b=>b.id===u)||null;return t(ce,{title:"Email",subtitle:"Which provider sends your mail, what happens when someone signs up, and the three messages your app sends.",children:n?t("div",{children:[n.configured?t(J,{children:"Email is configured and enabled."}):t(J,{kind:"warn",children:"Email is NOT configured: "+(n.notConfiguredReason||"unknown reason")+". Sends fail loudly into execution records rather than queueing."}),t(vu,{data:n,reload:d},a),t("h2",{children:"Templates"}),t(fe,{style:"margin:0 0 8px",children:"Each message is plain text with names in double braces the backend fills in when it sends. Open one to change its words and see it rendered."}),t("div",{children:[s?t(J,{children:"Templates are unavailable on this backend."}):null,r&&!r.length?t(oe,{children:"No templates."}):null,(r||[]).map(b=>t(pe,{class:"template-card",children:[t(V,{children:[t(kt,{children:bu(b.id)}),b.isOverridden?t(K,{kind:"accent",children:"edited"}):t(K,{children:"default"}),t(me,{}),t(P,{tiny:!0,onClick:()=>Z("email",b.id),children:"Edit"})]}),t(fe,{style:"margin:6px 0 0",children:(Ir[b.id]||{line:""}).line}),t(fe,{style:"margin:4px 0 0",children:"Subject: "+ke((b.effective||{}).subject)})]},b.id))]}),f?t(ku,{template:f,onClose:()=>Z("email"),onChanged:()=>{p()}},f.id):null]}):null})}function vu({data:e,reload:n}){let{readonly:i}=te(),r=e.config||{},o=r.smtp||{},s=Ea(ke(o.host)),[l,a]=m(!!r.enabled),[c,u]=m(s.preset.id),[p,d]=m(s.region||di[0]),[f,b]=m(ke(o.host)),[g,k]=m(o.port===void 0?"":String(o.port)),[y,T]=m(!!o.secure),[w,x]=m(ke(o.username)),[E,C]=m(""),[_,N]=m(ke(r.fromAddress)),[v,R]=m(ke(r.fromName)),[A,U]=m(ke(r.baseUrl)),[X,B]=m(!!(r.verification&&r.verification.sendOnSignup)),[q,h]=m(!!(r.verification&&r.verification.requireForLogin)),L=ci(c);function Q(le,ie=p){if(u(le.id),le.id==="other")return;let z=_a(le,ie);b(z.host),k(z.port),T(z.secure)}function se(){let ie={config:{enabled:l,smtp:{host:f.trim(),port:g?Number(g):void 0,secure:y,username:w.trim()},fromAddress:_.trim(),fromName:v.trim(),baseUrl:A.trim(),verification:{sendOnSignup:X,requireForLogin:q}}};E&&(ie.smtpPassword=E),S("PUT","/admin/email/config",ie).then(()=>{O("Email settings saved.","ok"),n()}).catch(D)}function Me(){ge(le=>t(Cu,{close:le}))}let Ae=le=>ie=>le(ie.currentTarget.value);return t(pe,{children:[t(re,{checked:l,onChange:a,disabled:i,children:"Sending is on"}),t("div",{class:"drawer-section",children:"Who sends it"}),t("div",{class:"tiles providers",role:"radiogroup","aria-label":"Email provider",children:Zt.map(le=>t("label",{class:"tile"+(c===le.id?" on":""),children:[t("input",{type:"radio",name:"smtp-preset",value:le.id,checked:c===le.id,disabled:i,"aria-label":le.label,onChange:()=>Q(le)}),t("b",{children:le.label}),t("span",{class:"sub",children:le.line})]},le.id))}),L.id==="ses"?t("div",{class:"when-body",style:"margin-top:8px",children:t($,{label:"Region",children:t("select",{value:p,disabled:i,"aria-label":"SES region",onChange:le=>{let ie=le.currentTarget.value;d(ie),Q(L,ie)},children:di.map(le=>t("option",{value:le,children:le},le))})})}):null,L.id!=="other"?t(H,{children:t("span",{id:"preset-hint",children:["Username: "+L.usernameHint+". Password: "+L.passwordHint+". ",L.credentialUrl?t("a",{href:L.credentialUrl,target:"_blank",rel:"noreferrer",children:[L.credentialLabel," \u2197"]}):null]})}):null,t("div",{class:"grid2",style:"margin-top:10px",children:[t($,{label:"SMTP host",children:t("input",{type:"text",id:"smtp-host",value:f,disabled:i,onInput:Ae(b)})}),t($,{label:"SMTP port",children:t("input",{type:"number",id:"smtp-port",value:g,disabled:i,onInput:Ae(k)})}),t($,{label:"Username",children:t("input",{type:"text",id:"smtp-username",placeholder:L.id!=="other"?L.usernameHint:"",value:w,disabled:i,onInput:Ae(x)})}),t($,{label:"Password",children:t("input",{type:"password",id:"smtp-password",placeholder:e.hasSmtpPassword?"(unchanged)":L.id!=="other"?L.passwordHint:"(not set)",value:E,disabled:i,onInput:Ae(C)})}),t($,{label:"From address",children:t("input",{type:"text",id:"smtp-from",value:_,disabled:i,onInput:Ae(N)})}),t($,{label:"From name",children:t("input",{type:"text",value:v,disabled:i,onInput:Ae(R)})}),t($,{label:"Base URL (links in emails)",children:t("input",{type:"text",id:"smtp-baseurl",value:A,placeholder:"https://api.example.com",disabled:i,onInput:Ae(U)})})]}),t(V,{style:"margin-top:10px",children:t(Fe,{checked:y,onChange:T,disabled:i,children:"Implicit TLS (port 465)"})}),t("div",{class:"drawer-section",children:"When someone signs up"}),t("div",{class:"when-body",id:"signup-policy",children:[t(re,{checked:X,onChange:B,disabled:i,children:"Send them a verification email"}),t(re,{checked:q,onChange:h,disabled:i,children:"They cannot sign in until they have verified"})]}),t(V,{style:"margin-top:12px",children:[t(M,{tiny:!0,kind:"primary",onClick:se,children:"Save"}),t(M,{tiny:!0,onClick:Me,children:"Send test email"})]})]})}function ku({template:e,onClose:n,onChanged:i}){let{readonly:r}=te(),o={...e.default||{subject:"",text:"",html:""},...e.effective||{}},[s,l]=m(o.subject||""),[a,c]=m(o.text||""),[u,p]=m(o.html||""),[d,f]=m(null),[b,g]=m(null),[k,y]=m(!1),T=ae("text"),w=ae(null),x=ae(null),E=ae(null),C=ae(null),_=ae(0),N=e.variables||[],v=s!==(o.subject||"")||a!==(o.text||"")||u!==(o.html||"");j(()=>{let h=++_.current,L=setTimeout(()=>{let Q=new URLSearchParams({subject:s,text:a,html:u}).toString();S("GET","/admin/email/templates/"+I(e.id)+"/preview?"+Q).then(se=>{_.current===h&&(f(se.preview||null),g(null))}).catch(se=>{_.current===h&&g(se.message)})},300);return()=>clearTimeout(L)},[s,a,u,e.id]),j(()=>{let h=C.current;if(!h)return;C.current=null;let L=h.part==="subject"?w.current:h.part==="text"?x.current:E.current;if(L){L.focus();try{L.setSelectionRange(h.at,h.at)}catch{}}});function R(h){let L=T.current,Q=L==="subject"?w.current:L==="text"?x.current:E.current,se=L==="subject"?s:L==="text"?a:u,Me=Q&&Q.selectionStart!==null?Q.selectionStart:se.length,Ae=Q&&Q.selectionEnd!==null?Q.selectionEnd:Me,le=yu(se,Me,Ae,Aa(h));(L==="subject"?l:L==="text"?c:p)(le.value),C.current={part:L,at:le.caret}}function A(){y(!0),S("PUT","/admin/email/templates/"+I(e.id),{subject:s,text:a,html:u}).then(()=>{O("Template saved.","ok"),i()}).catch(D).then(()=>y(!1))}function U(){S("DELETE","/admin/email/templates/"+I(e.id)).then(()=>{O("Back to the default.","ok"),i()}).catch(D)}function X(){ge(h=>t(wu,{close:h,send:L=>S("POST","/admin/email/test",{to:L,template:e.id,subject:s,text:a,html:u}).then(()=>{O("Sent to "+L+".","ok")})}))}let B=Ir[e.id]||{title:e.id,line:""},q=h=>()=>{T.current=h};return t(Se,{title:B.title,subtitle:B.line,onClose:n,wide:!0,footer:t(V,{children:[t(M,{kind:"primary",onClick:A,disabled:k||!v,children:"Save"}),t(M,{onClick:X,children:"Send me this"}),t(me,{}),t(P,{onClick:n,children:"Close"})]}),children:[t($,{label:"Subject",children:t("input",{type:"text",id:"tpl-subject",ref:w,value:s,disabled:r,onFocus:q("subject"),onInput:h=>l(h.currentTarget.value)})}),t("div",{class:"field",children:[t("span",{class:"field-head",children:t("b",{children:"Names you can drop in"})}),t("div",{class:"placeholder-bar",role:"toolbar","aria-label":"Placeholders",children:[N.map(h=>t("button",{type:"button",class:"chip accent","data-placeholder":h.name,title:"Inserts "+Aa(h.name)+" \u2014 e.g. "+h.sample,disabled:r,onClick:()=>R(h.name),children:h.label},h.name)),N.length?null:t(H,{children:"This backend did not say which names this message may use."})]}),t(H,{children:"Click one to drop it where the cursor is. The backend fills it in when it sends."})]}),t($,{label:"Body",children:t("textarea",{id:"tpl-text",ref:x,rows:9,value:a,disabled:r,onFocus:q("text"),onInput:h=>c(h.currentTarget.value)})}),t("div",{class:"field",children:[t("span",{class:"field-head",children:[t("b",{children:"Preview"}),t(H,{children:"Rendered by the backend with sample values, exactly as it would send."})]}),t("div",{class:"preview-pane",id:"tpl-preview","aria-live":"polite",children:[b?t(J,{kind:"bad",children:b}):null,d?t(W,{children:[t("div",{class:"preview-subject",children:d.subject}),t("pre",{children:d.text})]}):t(H,{children:"Rendering\u2026"})]})]}),t(He,{label:"Edit HTML",children:[t(fe,{style:"margin:0 0 6px",children:"The HTML version, for mail apps that show it. Most people never need to touch this; the plain body above is always sent too."}),t("textarea",{id:"tpl-html",ref:E,rows:8,value:u,disabled:r,onFocus:q("html"),onInput:h=>p(h.currentTarget.value)}),d?t("div",{class:"field",style:"margin-top:8px",children:[t("span",{class:"field-head",children:t("b",{children:"Rendered HTML, as source"})}),t("pre",{class:"preview-pane mono",id:"tpl-html-preview",children:d.html})]}):null]}),e.isOverridden?t(qe,{children:t("div",{class:"danger-action",children:[t("div",{class:"danger-why",children:"Throws away your words for this message; the backend sends its own again."}),t(M,{kind:"danger",onClick:()=>we("Reset to default","Your edited subject, body and HTML for \u201C"+B.title+"\u201D are discarded. The shipped default is sent from now on.",U,"Reset to default"),children:"Reset to default"})]})}):null]})}function wu({close:e,send:n}){let[i,r]=m(Ia()),[o,s]=m(!1);function l(){let a=i.trim();a&&(s(!0),La(a),n(a).then(e).catch(c=>{s(!1),D(c)}))}return t(ve,{title:"Send me this",actions:t(W,{children:[t(P,{onClick:e,children:"Cancel"}),t(P,{kind:"primary",onClick:l,disabled:o,children:"Send"})]}),children:[t(fe,{children:"Sends this message as it is now \u2014 saved or not \u2014 with the sample values, through your SMTP, to one address."}),t("input",{type:"email",id:"send-me-to",placeholder:"you@example.com",style:"width:100%",value:i,onInput:a=>r(a.currentTarget.value),onKeyDown:a=>a.key==="Enter"&&l()})]})}function Cu({close:e}){let[n,i]=m(Ia());function r(){let o=n.trim();La(o),S("POST","/admin/email/test",{to:o}).then(()=>{e(),O("Test email sent.","ok")}).catch(D)}return t(ve,{title:"Send a test email",actions:t(W,{children:[t(P,{onClick:e,children:"Cancel"}),t(P,{kind:"primary",onClick:r,children:"Send"})]}),children:[t(fe,{children:"This sends a real message through the configured SMTP right now, and reports the failure reason if it cannot."}),t("input",{type:"email",placeholder:"you@example.com",style:"width:100%",value:n,onInput:o=>i(o.currentTarget.value)})]})}function Su(e,n){let i=e||{};return i.type==="s3"?'Archives live in the bucket "'+(n&&n.name||"?")+'" under '+(i.prefix||"backups/")+".":"Archives live in "+ke(i.path)+"."}var Tu="0 3 * * *",xu=e=>S("POST","/admin/triggers/preview",{cron:e,count:5});function Ru(e){let n=[],i=e.keepLast||0,r=e.keepDaily||0,o=e.keepWeekly||0;return i&&n.push("the last "+i),r&&n.push("one a day for "+r+" day"+(r===1?"":"s")),o&&n.push("one a week for "+o+" week"+(o===1?"":"s")),n.length?"Keeps "+n.join(", and ")+".":"Nothing is kept: every backup is deleted by the next one."}function Eu(e){return"The data is restored, but "+e.map(i=>i.file).join(", ")+(e.length===1?" was":" were")+" not taken up: "+e[0].reason+" The settings from before the restore stay in force."}function Da(e){let{readonly:n,whoami:i}=te(),[r,o]=m(null),[s,l]=m(0),[a,c]=m(null),u=i&&i.backend&&i.backend.name||"";function p(){S("GET","/admin/backups").then(E=>{o(E),l(C=>C+1)}).catch(D)}j(()=>{p()},[]);function d(){O("Backing up\u2026"),S("POST","/admin/backups",{}).then(E=>{O("Backup written"+(E.bytes?" ("+mt(E.bytes)+")":"")+".","ok"),p()}).catch(D)}function f(E){fetch("/admin/backups/archive?file="+I(E.file),{headers:ut(_e.get().credential)}).then(async C=>{if(!C.ok){let R=await C.json().catch(()=>null);throw new Error(R&&R.error||"HTTP "+C.status)}let _=await C.blob(),N=URL.createObjectURL(_),v=document.createElement("a");v.href=N,v.download=E.file,document.body.appendChild(v),v.click(),v.remove(),setTimeout(()=>URL.revokeObjectURL(N),1e4)}).catch(D)}function b(E,C){c(E.file),S("POST","/admin/backups/restore",{archive:E.file,safetySnapshot:C}).then(_=>{O("Restored "+E.file+(_.safetyArchive?" \u2014 the data from before is in "+_.safetyArchive.split("/").pop()+".":"."),"ok");let N=_.settings&&_.settings.refused||[];N.length&&O(Eu(N),"bad")}).catch(D).then(()=>{c(null),p()})}function g(E){ge(C=>t(Iu,{archive:E,backendName:u,close:C,onConfirm:_=>b(E,_)}))}let k=r&&r.config||{},y=k.status||{},T=r&&r.backups||[],w=r?r.bucket:void 0,x=!!a;return t(ce,{title:"Backups",subtitle:"When backups happen, how many are kept, and a restore when you must.",children:[a?t(J,{kind:"warn",children:[t("b",{children:["Restoring ",a,"\u2026"]})," The backend is swapping its database. Nothing on this page works until it answers."]}):null,r?t("div",{class:x?"blocked":"","aria-busy":x?"true":"false",children:[t(pe,{children:t(V,{children:[k.schedule&&k.schedule.enabled?t(K,{kind:"ok",children:"scheduled"}):t(K,{kind:"warn",children:"not scheduled"}),y.lastSuccessAt?t(K,{kind:"ok",children:"last backup "+he(y.lastSuccessAt)}):t(K,{kind:"warn",children:"never backed up"}),y.lastResult&&y.lastResult.ok===!1?t(K,{kind:"bad",title:ke(y.lastResult.error),children:"last attempt failed "+he(y.lastResult.at)}):null,y.nextRunAt?t(K,{children:"next "+he(y.nextRunAt)}):null,t(me,{}),t(M,{tiny:!0,kind:"primary",id:"backup-now",disabled:x,onClick:d,children:"Back up now"})]})}),t("h2",{children:"When"}),t(_u,{config:k,reload:p,blocked:x},"when"+s),t("h2",{children:"Keep"}),t(Au,{config:k,reload:p,blocked:x},"keep"+s),t("h2",{children:"Where archives go"}),t(Pu,{config:k,bucket:w,reload:p,blocked:x},"where"+s),t("h2",{children:"Archives"}),r&&r.listingError?t(J,{kind:"bad",children:[t("b",{children:"The archives could not be listed."})," ",r.listingError]}):null,t(ye,{columns:["Archive","Taken","Size",""],rows:T,renderRow:E=>t("tr",{"data-archive":E.file,children:[t("td",{children:[t("b",{children:E.file}),E.where==="s3"?t(K,{children:"in the bucket"}):null]}),t("td",{children:he(E.createdAt)}),t("td",{children:mt(E.bytes)}),t(Je,{children:[t(P,{tiny:!0,disabled:x,onClick:()=>f(E),children:"Download"}),t(M,{tiny:!0,kind:"danger",disabled:x||n,onClick:()=>g(E),children:"Restore\u2026"})]})]},E.file),empty:t(oe,{action:{label:"Back up now",onClick:d},children:"No backups yet."})}),t(fe,{style:"margin-top:10px",children:[Su(k.destination,w)+" A restore replaces the records, files, workflows and settings with the archive's; the run history is not in a backup and stays as it is. With ",t("i",{children:"Back up first"})," ticked, what is there now is archived before it goes."]})]}):null]})}function _u({config:e,reload:n,blocked:i}){let{readonly:r}=te(),o=e.schedule||null,[s,l]=m(!!(o&&o.enabled)),[a,c]=m(o&&o.cron||Tu),[u,p]=m(o&&o.missedFirePolicy==="run-once-on-start"?"run-once-on-start":"skip"),d=r||i;function f(){S("PUT","/admin/backups/config",{schedule:s?{enabled:!0,cron:a.trim(),missedFirePolicy:u}:null}).then(()=>{O(s?"Backup schedule saved.":"Backups are no longer scheduled.","ok"),n()}).catch(D)}return t(pe,{children:[t(re,{id:"backup-enabled",checked:s,onChange:l,disabled:d,children:"Back up on a schedule"}),s?t("div",{class:"when-body",id:"backup-schedule",children:[t(_t,{id:"backup-cron",value:a,onChange:c,preview:xu,disabled:d}),t(G,{h:10}),t("div",{class:"scope-group",children:[t("div",{class:"field-head",children:t("b",{children:"If a backup is missed while the backend is off"})}),t("label",{class:"check",children:[t("input",{type:"radio",name:"backup-missed",checked:u==="skip",disabled:d,onChange:()=>p("skip")})," Skip it \u2014 carry on from the next time"]}),t("label",{class:"check",children:[t("input",{type:"radio",name:"backup-missed",checked:u==="run-once-on-start",disabled:d,onChange:()=>p("run-once-on-start")})," Run it once, as soon as the backend is back"]})]})]}):null,t(V,{style:"margin-top:12px",children:t(M,{tiny:!0,kind:"primary",id:"save-schedule",disabled:i,onClick:f,children:"Save schedule"})})]})}function Au({config:e,reload:n,blocked:i}){let{readonly:r}=te(),o=e.retention||{},[s,l]=m(String(o.keepLast??7)),[a,c]=m(String(o.keepDaily??0)),[u,p]=m(String(o.keepWeekly??0)),[d,f]=m(!!e.includeSecrets),b=r||i,g=y=>Math.max(0,Math.floor(Number(y)||0));function k(){S("PUT","/admin/backups/config",{retention:{keepLast:g(s),keepDaily:g(a),keepWeekly:g(u)},includeSecrets:d}).then(()=>{O("Backup settings saved.","ok"),n()}).catch(D)}return t(pe,{children:[t("div",{class:"grid2",children:[t($,{label:"Keep the last",children:t("input",{type:"number",min:"0",id:"keep-last",value:s,disabled:b,onInput:y=>l(y.currentTarget.value)})}),t($,{label:"and one a day for (days)",children:t("input",{type:"number",min:"0",id:"keep-daily",value:a,disabled:b,onInput:y=>c(y.currentTarget.value)})}),t($,{label:"and one a week for (weeks)",children:t("input",{type:"number",min:"0",id:"keep-weekly",value:u,disabled:b,onInput:y=>p(y.currentTarget.value)})})]}),t("div",{class:"sub",id:"keep-words",children:Ru({keepLast:g(s),keepDaily:g(a),keepWeekly:g(u)})}),t(G,{}),t(re,{id:"include-secrets",checked:d,onChange:f,disabled:b,children:"Include this machine's secrets in each archive"}),t(fe,{children:"Off by default: an archive with secrets in it is a credential. Turn it on only where the archives are kept as safely as the backend."}),t(V,{style:"margin-top:12px",children:t(M,{tiny:!0,kind:"primary",id:"save-keep",disabled:i,onClick:k,children:"Save"})})]})}function Pu({config:e,bucket:n,reload:i,blocked:r}){let{readonly:o}=te(),s=e.destination||{},[l,a]=m(s.type==="s3"?"s3":"local"),[c,u]=m(ke(s.path)),p=o||r,d=!!(n&&n.connected);function f(){S("PUT","/admin/backups/config",{destination:l==="s3"?{type:"s3"}:{type:"local",path:c.trim()}}).then(()=>{O(l==="s3"?'Backups go to the bucket "'+(n&&n.name||"")+'".':"Backups are written to "+c.trim()+".","ok"),i()}).catch(D)}return t(pe,{id:"backup-where",children:[t("div",{class:"tiles",role:"radiogroup","aria-label":"Where archives go",children:[t("label",{class:"tile"+(l==="local"?" on":""),children:[t("input",{type:"radio",name:"backup-where",value:"local",checked:l==="local",disabled:p,onChange:()=>a("local")}),t("b",{children:"This machine"}),t("span",{class:"sub",children:"A folder beside the backend's data. An archive here is lost with the machine."})]}),t("label",{class:"tile"+(l==="s3"?" on":"")+(d?"":" disabled"),title:d?void 0:"Connect a bucket on the Storage page first.",children:[t("input",{type:"radio",name:"backup-where",value:"s3",checked:l==="s3",disabled:p||!d,onChange:()=>a("s3")}),t("b",{children:"The bucket from the Storage page"}),t("span",{class:"sub",children:d?'Archives go to "'+n.name+'" under '+(s.prefix||"backups/")+". Nothing to type: the bucket is set up once, on the Storage page.":"Connect a bucket on the Storage page first; backups use the same one."})]})]}),l==="local"?t($,{label:"Folder on the backend's machine",style:"margin-top:8px",children:t("input",{type:"text",id:"backup-dest",value:c,disabled:p,onInput:b=>u(b.currentTarget.value)})}):null,t(V,{style:"margin-top:12px",children:t(M,{tiny:!0,kind:"primary",id:"save-where",disabled:r,onClick:f,children:"Save"})})]})}function Iu({archive:e,backendName:n,close:i,onConfirm:r}){let[o,s]=m(""),[l,a]=m(!0),c=n||"restore",u=o===c;return t(ve,{title:"Restore "+e.file+"?",actions:t(W,{children:[t(P,{onClick:i,children:"Cancel"}),t(P,{kind:"danger",id:"restore-confirm",disabled:!u,onClick:()=>{u&&(i(),r(l))},children:"Restore"})]}),children:[t("div",{class:"notice bad",children:"Everything written since "+(e.createdAt?he(e.createdAt):"this archive was taken")+" \u2014 records, files, settings \u2014 is replaced by what the archive holds. Apps using this backend see the older data at once."}),t(G,{h:8}),t(Fe,{checked:l,onChange:a,children:"Back up first \u2014 archive what is there now, so this can be undone"}),t("p",{class:"sub",children:'Type "'+c+'" to confirm.'}),t("input",{type:"text",id:"restore-typed",placeholder:c,"aria-label":"Type "+c+" to confirm",style:"width:100%;margin-top:8px",value:o,onInput:p=>s(p.target.value)})]})}var Lu=/^[A-Za-z0-9_.-]{1,128}$/,Nu="NODEGX_SECRET_";function Oa(e){return Nu+e.replace(/[^A-Za-z0-9]+/g,"_").toUpperCase()}function Lr(e){return e.length===0||Lu.test(e)?null:'Use letters, digits, "_", "." or "-", 1-128 characters \u2014 the same names a Secret node can ask for.'}function Fa(e){return e.stillResolvesFromEnvironment?{severity:"bad",message:'"'+e.name+'" was removed from this backend\u2019s store, but '+e.envName+' is still set in the environment this backend is running in \u2014 so functions asking for "'+e.name+'" keep resolving it. Unset that variable and restart the backend to finish removing it.'}:e.existed?{severity:"ok",message:'"'+e.name+'" was removed. A Secret node asking for it now fires its failure output.'}:{severity:"ok",message:'"'+e.name+'" was not stored here, so nothing changed.'}}function Ma(e,n){let i=new Set(e.map(r=>r.envName));return n.filter(r=>!i.has(r))}function Ba(e){return e.busy||e.name.length===0||e.value.length===0?!1:Lr(e.name)===null}function ja(e){let{readonly:n}=te(),[i,r]=m(null);function o(){S("GET","/admin/secrets").then(r).catch(D)}j(()=>{o()},[]);function s(){ge(u=>t(Ha,{close:u,taken:i&&i.secrets.map(p=>p.name)||[],onSaved:o}))}function l(u){ge(p=>t(Ha,{close:p,fixedName:u.name,taken:[],onSaved:o}))}function a(u){Ge("Delete secret "+u.name+"?",'Every Secret node asking for "'+u.name+'" fires its failure output from the next run. This cannot be undone: the value is not kept anywhere else.',u.name,()=>{S("DELETE","/admin/secrets/"+I(u.name)).then(p=>{let d=Fa(p);O(d.message,d.severity),o()}).catch(D)})}let c=i?Ma(i.secrets,i.environment):[];return t(ce,{title:"Secrets",subtitle:"Keys and passwords your cloud functions read with a Secret node. A value is typed once and never shown again.",children:i?t("div",{children:[t(V,{children:[t(me,{}),t(M,{tiny:!0,kind:"primary",id:"add-secret",onClick:s,children:"Add secret"})]}),t(G,{}),t(ye,{columns:["Name","In a function","Also in the environment",""],rows:i.secrets,renderRow:u=>t("tr",{"data-secret":u.name,children:[t("td",{children:t("b",{children:u.name})}),t("td",{children:t("span",{class:"hint",children:"Secret node \u2192 "+u.name})}),t("td",{children:u.alsoInEnvironment?t(K,{kind:"warn",title:u.envName+" is set where this backend runs; the stored value wins while it exists.",children:"yes \u2014 "+u.envName}):t("span",{class:"hint",children:"no"})}),t(Je,{children:[t(M,{tiny:!0,disabled:n,onClick:()=>l(u),children:"Set a new value"}),t(M,{tiny:!0,kind:"danger",disabled:n,onClick:()=>a(u),children:"Delete"})]})]},u.name),empty:t(oe,{action:{label:"Add secret",onClick:s},children:"No secrets yet. A function that needs an API key or a password reads it from here, never from its code."})}),c.length?t("div",{style:"margin-top:12px",children:t(J,{children:["Set in the environment this backend runs in, not here: ",c.map((u,p)=>t("span",{children:[p?", ":"",t("code",{children:u})]},u)),". A function resolves those too; to change one, change the environment and restart."]})}):null,t(fe,{style:"margin-top:10px",children:"Values stay in this backend's secrets.json and are never sent back to a browser, an agent or a log \u2014 this page cannot show one, and neither can anything else."})]}):null})}function Ha({close:e,fixedName:n,taken:i,onSaved:r}){let[o,s]=m(n||""),[l,a]=m(""),[c,u]=m(!1),[p,d]=m(null),f=Lr(o)||(!n&&i.indexOf(o)!==-1?'There is already a secret called "'+o+'". Set a new value on it instead.':null),b=Ba({name:o,value:l,busy:c})&&!f;function g(){b&&(u(!0),S("PUT","/admin/secrets/"+I(o),{value:l}).then(k=>{O((k.created?"Stored ":"Replaced ")+o+". Functions read the new value from their next run.","ok"),e(),r()}).catch(k=>{u(!1),d(k.message)}))}return t(ve,{title:n?"Set a new value for "+n:"Add a secret",actions:t(W,{children:[t(P,{onClick:e,children:"Cancel"}),t(M,{kind:"primary",id:"secret-save",disabled:!b,onClick:g,children:c?"Saving\u2026":"Save"})]}),children:[n?null:t($,{label:"Name \u2014 what a Secret node asks for",children:t("input",{type:"text",id:"secret-name",value:o,placeholder:"STRIPE_KEY",autocomplete:"off",spellcheck:!1,onInput:k=>s(k.currentTarget.value)})}),f?t(H,{children:f}):o?t("div",{class:"hint",children:"Also read from the environment as "+Oa(o)}):null,t(G,{h:8}),t($,{label:"Value",children:t("input",{type:"password",id:"secret-value",value:l,autocomplete:"new-password",onInput:k=>a(k.currentTarget.value),onKeyDown:k=>k.key==="Enter"&&g()})}),t("div",{class:"hint",children:"Shown once, here, as you type it. After Save nothing can read it back \u2014 not this page, not an agent, not a log."}),p?t(J,{kind:"bad",children:p}):null]})}function Du(e){return e.columns.filter(n=>n.type==="String"&&["objectId","ACL"].indexOf(n.name)===-1).map(n=>n.name)}function Nr(e){if(!e)return"";let n=typeof e.rowsIndexed=="number"?e.rowsIndexed:null;return n===null?"index rebuilt":n+" record"+(n===1?"":"s")+" indexed"}function Wa(e){let[n,i]=m(null),[r,o]=m(null);function s(){S("GET","/admin/search").then(i).catch(D),S("GET","/admin/schema").then(l=>o((l.tables||[]).filter(a=>!a.name.startsWith("_")))).catch(D)}return j(()=>{s()},[]),t(ce,{title:"Search",subtitle:"Which collections answer a text search, and over which fields.",children:n&&r?t("div",{children:[n.fts5Available?null:t(J,{kind:"bad",children:"This backend's database was built without full-text search (SQLite FTS5). Search cannot be turned on here; the settings are kept for a backend that has it."}),t(G,{}),r.length?r.map(l=>t(Ou,{table:l,config:n.config.collections[l.name],reload:s,available:n.fts5Available},l.name)):t(oe,{children:"No collections yet. Make one on the Schema page and it appears here."}),t(fe,{style:"margin-top:10px",children:"A searchable collection answers a text search from an app and a node (the \`search\` field of a query). Only text fields can be searched; a field added later is not searched until it is ticked here."})]}):null})}function Ou({table:e,config:n,reload:i,available:r}){let{readonly:o}=te(),s=Du(e),l=n||{enabled:!1,fields:[]},[a,c]=m(!!l.enabled),[u,p]=m(l.fields.filter(x=>s.indexOf(x)!==-1)),[d,f]=m(null),[b,g]=m(void 0),k=a!==!!l.enabled||u.join(",")!==l.fields.join(","),y=a&&!u.length?"Pick at least one field to search.":null;function T(){if(y)return;f("save");let x=()=>{f(null),i()};if(!a){S("DELETE","/admin/search/collections/"+I(e.name)).then(()=>{O(e.name+" is no longer searchable.","ok"),g(void 0),x()}).catch(E=>{f(null),D(E)});return}S("PUT","/admin/search/collections/"+I(e.name),{enabled:!0,fields:u}).then(E=>{g(E.rebuild),O(e.name+" is searchable \u2014 "+Nr(E.rebuild)+".","ok"),x()}).catch(E=>{f(null),D(E)})}function w(){f("rebuild"),S("POST","/admin/search/collections/"+I(e.name)+"/rebuild",{}).then(x=>{g(x.rebuild),O(e.name+": "+Nr(x.rebuild)+".","ok"),f(null),i()}).catch(x=>{f(null),D(x)})}return t(pe,{style:"margin-bottom:10px",id:"search-card-"+e.name,children:[t(V,{children:[t(re,{id:"search-"+e.name,checked:a,onChange:c,disabled:o||!r||!!d,children:t("b",{children:e.name})}),l.enabled?t(K,{kind:"ok",children:"searchable"}):t(K,{children:"not searchable"}),b?t(K,{kind:"accent",children:Nr(b)}):null,t(me,{}),l.enabled?t(M,{tiny:!0,disabled:!!d||!r,onClick:w,children:d==="rebuild"?"Rebuilding\u2026":"Rebuild index"}):null,t(M,{tiny:!0,kind:"primary",disabled:!k||!!y||!!d||!r,onClick:T,children:d==="save"?"Saving\u2026":"Save"})]}),a?t("div",{style:"margin-top:8px",children:[t("div",{class:"field-head",children:[t("b",{children:"Fields to search"}),s.length?null:t("span",{class:"hint",children:"This collection has no text fields."})]}),t(Ie,{items:u,onChange:p,suggest:async x=>s.filter(E=>u.indexOf(E)===-1&&E.toLowerCase().indexOf(x.toLowerCase())!==-1),validate:x=>s.indexOf(x)===-1?'"'+x+'" is not a text field of '+e.name+".":null,placeholder:"Pick a field",addLabel:"Add field",disabled:o||!!d}),y?t("div",{class:"hint",children:y}):null]}):null]})}var Dr=[{id:"auth",label:"Signing in",line:"login, signup, password and magic-link requests"},{id:"data",label:"Records",line:"reading and writing collections"},{id:"files",label:"Files",line:"uploads and downloads"},{id:"functions",label:"Cloud functions",line:"calls to deployed functions"},{id:"hooks",label:"Webhooks",line:"triggers fired from outside"},{id:"admin",label:"This manager",line:"admin routes and agents"},{id:"public",label:"Everything else",line:"health, docs and public pages"}],Fu=[{id:"debug",label:"Everything (debug)"},{id:"info",label:"Normal (info)"},{id:"warn",label:"Warnings and errors"},{id:"error",label:"Errors only"},{id:"silent",label:"Nothing"}];function Mu(e){let n=e.trim();return n?n==="loopback"||n==="*"||/^[0-9a-fA-F:.]+$/.test(n)?null:'A proxy is an IP address, "loopback" (the same machine) or "*" (any).':"Type an address."}function Ua(e){let[n,i]=m(null),[r,o]=m(0);function s(){S("GET","/admin/ops").then(a=>{i(a.config),o(c=>c+1)}).catch(D)}j(()=>{s()},[]);let l=(a,c,u)=>S("PUT","/admin/ops",{[a]:c}).then(()=>{O(u,"ok"),s()}).catch(D);return t(ce,{title:"Server",subtitle:"Who may call from a browser, how many requests are allowed, what is logged and kept. Each card saves on its own and applies at once.",children:n?t("div",{children:[t("h2",{children:"Who may call from a browser"}),t(Bu,{cors:n.cors,save:a=>l("cors",a,"Browser access saved.")},"cors"+r),t("h2",{children:"Rate limits"}),t(Hu,{rl:n.rateLimit,save:a=>l("rateLimit",a,"Rate limits saved.")},"rate"+r),t("h2",{children:"Logging"}),t(ju,{logging:n.logging,save:a=>l("logging",a,"Logging saved.")},"log"+r),t("h2",{children:"Activity trail"}),t(Wu,{audit:n.audit,save:a=>l("audit",a,"Activity trail settings saved.")},"audit"+r),t("h2",{children:"Run history"}),t(Uu,{executions:n.executions,save:a=>l("executions",a,"Run history settings saved.")},"runs"+r),t("h2",{children:"How long a list can be"}),t(Vu,{queries:n.queries,save:a=>l("queries",a,"List lengths saved.")},"lists"+r),t("h2",{children:"Metrics"}),t(Gu,{metrics:n.metrics,save:a=>l("metrics",a,"Metrics settings saved.")},"metrics"+r)]}):null})}function Bu({cors:e,save:n}){let{readonly:i}=te(),r=e.origins.indexOf("*")!==-1,[o,s]=m(r),[l,a]=m(e.origins.filter(d=>d!=="*")),[c,u]=m(!!e.credentials),p=!o&&!l.length?"List at least one site, or allow any.":o&&c?"Cookies cannot be sent to any site \u2014 browsers refuse that pair. Name the sites, or turn cookies off.":null;return t(pe,{children:[t(re,{id:"cors-any",checked:o,onChange:s,disabled:i,children:"Any site may call this backend"}),t(fe,{children:"Fine while you build; a live backend should name its sites."}),o?null:t("div",{id:"cors-origins",style:"margin-top:8px",children:[t("div",{class:"field-head",children:[t("b",{children:"These sites"}),t("span",{class:"hint",children:"the address a browser shows for your app"})]}),t(Ie,{items:l,onChange:a,validate:Zn,normalise:Qn,placeholder:"https://app.example.com",addLabel:"Add site",disabled:i})]}),t(G,{h:8}),t(re,{id:"cors-credentials",checked:c,onChange:u,disabled:i,children:"Let those sites send cookies"}),p?t(H,{children:p}):null,t(V,{style:"margin-top:12px",children:t(M,{tiny:!0,kind:"primary",id:"save-cors",disabled:!!p,onClick:()=>n({origins:o?["*"]:l,credentials:c}),children:"Save"})})]})}function Hu({rl:e,save:n}){let{readonly:i}=te(),[r,o]=m(!!e.enabled),[s,l]=m(()=>{let k={};for(let y of Dr)k[y.id]={rate:String((e.policies[y.id]||{ratePerMinute:0}).ratePerMinute),burst:String((e.policies[y.id]||{burst:0}).burst)};return k}),[a,c]=m(e.trustedProxies||[]),[u,p]=m(String(e.realtimeMaxConnections)),[d,f]=m(String(e.functionRunQueries)),b=k=>Math.max(0,Math.floor(Number(k)||0));function g(){let k={...e.policies};for(let y of Dr)k[y.id]={ratePerMinute:b(s[y.id].rate),burst:b(s[y.id].burst)};n({enabled:r,trustedProxies:a,policies:k,realtimeMaxConnections:b(u),functionRunQueries:b(d)})}return t(pe,{children:[t(re,{id:"rate-enabled",checked:r,onChange:o,disabled:i,children:"Limit how fast one client may call"}),t(fe,{children:"Each client gets an allowance per minute for each kind of request, and a burst it may spend at once. 0 in the burst means no limit for that kind."}),r?t("div",{class:"rate-table",id:"rate-table",children:[t("div",{class:"list-row list-head",children:[t("span",{style:"flex:1 1 160px",children:"Kind of request"}),t("span",{style:"width:110px",children:"Per minute"}),t("span",{style:"width:90px",children:"Burst"})]}),Dr.map(k=>t("div",{class:"list-row","data-class":k.id,children:[t("span",{style:"flex:1 1 160px",children:[t("b",{children:k.label}),t("span",{class:"sub",children:k.line})]}),t("input",{type:"number",min:"0",style:"width:110px","aria-label":k.label+" per minute",value:s[k.id].rate,disabled:i,onInput:y=>l({...s,[k.id]:{...s[k.id],rate:y.currentTarget.value}})}),t("input",{type:"number",min:"0",style:"width:90px","aria-label":k.label+" burst",value:s[k.id].burst,disabled:i,onInput:y=>l({...s,[k.id]:{...s[k.id],burst:y.currentTarget.value}})})]},k.id))]}):null,t(G,{}),t("div",{class:"grid2",children:[t($,{label:"Live streams open at once (0 = no limit)",children:t("input",{type:"number",min:"0",id:"rate-streams",value:u,disabled:i,onInput:k=>p(k.currentTarget.value)})}),t($,{label:"Requests one function run may make (0 = no limit)",children:t("input",{type:"number",min:"0",id:"rate-per-run",value:d,disabled:i,onInput:k=>f(k.currentTarget.value)})})]}),t(G,{h:8}),t("div",{class:"field-head",children:[t("b",{children:"Proxies to believe about who is calling"}),t("span",{class:"hint",children:"a reverse proxy in front of this backend, so its clients are told apart"})]}),t(Ie,{items:a,onChange:c,validate:Mu,placeholder:"loopback",addLabel:"Add proxy",disabled:i,label:k=>k==="loopback"?"the same machine":k==="*"?"any (only behind a proxy that resets the header)":k}),t(V,{style:"margin-top:12px",children:t(M,{tiny:!0,kind:"primary",id:"save-rate",onClick:g,children:"Save"})})]})}function ju({logging:e,save:n}){let{readonly:i}=te(),[r,o]=m(e.level),[s,l]=m(e.format),[a,c]=m(!!e.requests);return t(pe,{children:[t("div",{class:"grid2",children:[t("div",{class:"scope-group",children:[t("div",{class:"field-head",children:t("b",{children:"How much to log"})}),Fu.map(u=>t("label",{class:"check",children:[t("input",{type:"radio",name:"log-level",value:u.id,checked:r===u.id,disabled:i,onChange:()=>o(u.id)})," ",u.label]},u.id))]}),t("div",{class:"scope-group",children:[t("div",{class:"field-head",children:t("b",{children:"How lines look"})}),t("label",{class:"check",children:[t("input",{type:"radio",name:"log-format",value:"auto",checked:s==="auto",disabled:i,onChange:()=>l("auto")})," Decide from where it runs (readable in a terminal, JSON under a supervisor)"]}),t("label",{class:"check",children:[t("input",{type:"radio",name:"log-format",value:"pretty",checked:s==="pretty",disabled:i,onChange:()=>l("pretty")})," Readable"]}),t("label",{class:"check",children:[t("input",{type:"radio",name:"log-format",value:"json",checked:s==="json",disabled:i,onChange:()=>l("json")})," JSON, one object per line"]})]})]}),t(G,{h:8}),t(re,{id:"log-requests",checked:a,onChange:c,disabled:i,children:"One line per request"}),t(V,{style:"margin-top:12px",children:t(M,{tiny:!0,kind:"primary",id:"save-logging",onClick:()=>n({level:r,format:s,requests:a}),children:"Save"})})]})}function Wu({audit:e,save:n}){let{readonly:i}=te(),[r,o]=m(!!e.enabled),[s,l]=m(String(e.retentionDays));return t(pe,{children:[t(re,{id:"audit-enabled",checked:r,onChange:o,disabled:i,children:"Record who changed what"}),t(fe,{children:"What the Activity page shows. Off, and nothing is written there."}),t(G,{h:8}),t($,{label:"Keep entries for (days; 0 keeps them forever)",children:t("input",{type:"number",min:"0",id:"audit-days",value:s,disabled:i,onInput:a=>l(a.currentTarget.value)})}),t(V,{style:"margin-top:12px",children:t(M,{tiny:!0,kind:"primary",id:"save-audit",onClick:()=>n({enabled:r,retentionDays:Math.max(0,Math.floor(Number(s)||0))}),children:"Save"})})]})}function Uu({executions:e,save:n}){let{readonly:i}=te(),[r,o]=m(String(e.retentionDays)),[s,l]=m(String(e.maxCount)),[a,c]=m(String(e.idempotencyTtlHours)),[u,p]=m(!1),d=b=>Math.max(0,Math.floor(Number(b)||0));function f(){p(!0),S("POST","/admin/executions/compact",{}).then(b=>{let g=b.beforeBytes||0,k=b.afterBytes||0;O("Run history compacted"+(g&&k?": "+Math.round(g/1024)+" KB \u2192 "+Math.round(k/1024)+" KB.":"."),"ok")}).catch(D).then(()=>p(!1))}return t(pe,{children:[t("div",{class:"grid2",children:[t($,{label:"Keep runs for (days; 0 = forever)",children:t("input",{type:"number",min:"0",id:"runs-days",value:r,disabled:i,onInput:b=>o(b.currentTarget.value)})}),t($,{label:"Keep at most (runs; 0 = no limit)",children:t("input",{type:"number",min:"0",id:"runs-most",value:s,disabled:i,onInput:b=>l(b.currentTarget.value)})}),t($,{label:"Remember an answered Idempotency-Key for (hours)",children:t("input",{type:"number",min:"0",id:"runs-ttl",value:a,disabled:i,onInput:b=>c(b.currentTarget.value)})})]}),t(fe,{children:"Whichever limit is reached first wins: a misfiring workflow can write a month of runs in an hour."}),t(V,{style:"margin-top:12px",children:[t(M,{tiny:!0,kind:"primary",id:"save-runs",onClick:()=>n({...e,retentionDays:d(r),maxCount:d(s),idempotencyTtlHours:d(a)}),children:"Save"}),t(M,{tiny:!0,id:"compact-now",disabled:u,onClick:()=>we("Compact the run history now?","The history file is rewritten without the space old runs left behind. Runs are kept; this only takes a moment.",f,"Compact"),children:u?"Compacting\u2026":"Compact now"})]})]})}function Ku(e,n){let i=r=>/^\\s*\\d+\\s*$/.test(r)&&Number(r)>=1;return i(e)?i(n)?Number(n)<Number(e)?"The longest list cannot be shorter than the one the app gets without asking.":null:"The longest list needs a whole number of at least 1.":"A list the app does not size needs a whole number of at least 1."}function Vu({queries:e,save:n}){let{readonly:i}=te(),[r,o]=m(String(e.defaultLimit)),[s,l]=m(String(e.maxLimit)),a=Ku(r,s);return t(pe,{children:[t("div",{class:"grid2",children:[t($,{label:"When the app does not say how many (records)",children:t("input",{type:"number",min:"1",step:"1",id:"lists-default",value:r,disabled:i,onInput:c=>o(c.currentTarget.value)})}),t($,{label:"The most the app may ask for (records)",children:t("input",{type:"number",min:"1",step:"1",id:"lists-max",value:s,disabled:i,onInput:c=>l(c.currentTarget.value)})})]}),t(fe,{children:"A query that forgot to say how many would otherwise read the whole collection. A list cut short tells the app so \u2014 nothing goes missing silently."}),a?t("div",{class:"chips-problem",id:"lists-problem",children:a}):null,t(V,{style:"margin-top:12px",children:t(M,{tiny:!0,kind:"primary",id:"save-lists",disabled:!!a,onClick:()=>n({defaultLimit:Number(r),maxLimit:Number(s)}),children:"Save"})})]})}function Gu({metrics:e,save:n}){let{readonly:i}=te(),[r,o]=m(!!e.enabled),[s,l]=m(!!e.allowLoopback),a=location.origin+"/metrics";return t(pe,{children:[t(re,{id:"metrics-enabled",checked:r,onChange:o,disabled:i,children:"Serve metrics for Prometheus"}),r?t(J,{children:["Scrape ",t("code",{id:"metrics-url",children:a})," with the admin credential"+(s?", or from this machine without one.":".")]}):null,t(G,{h:8}),t(re,{id:"metrics-loopback",checked:s,onChange:l,disabled:i||!r,children:"A scraper on this machine needs no credential"}),t(V,{style:"margin-top:12px",children:t(M,{tiny:!0,kind:"primary",id:"save-metrics",onClick:()=>n({enabled:r,allowLoopback:s}),children:"Save"})})]})}var qu=[{value:"admin",label:"an admin"},{value:"admin:readonly",label:"a read-only admin"},{value:"user",label:"a signed-in user"},{value:"apiKey",label:"an API key"},{value:"anonymous",label:"nobody signed in"},{value:"cli",label:"the command line"}];function Ka(e){return[{name:"action",kind:"choice",ops:["is"],options:e.map(n=>({value:n,label:ui(n)}))},{name:"who",kind:"choice",ops:["is"],options:qu},{name:"outcome",kind:"choice",ops:["is"],options:[{value:"success",label:"succeeded"},{value:"failure",label:"failed"}]},{name:"when",kind:"date",ops:["within","on","before","after","between"]}]}function Va(e,n=new Date){if(e.conj==="or"&&e.items.length>1)throw new Error('Activity can be filtered with "and" only.');let i={},r=(o,s)=>{if(i[o]!==void 0)throw new Error(o+" is asked twice.");i[o]=s};for(let o of e.items){if(o.kind==="group")throw new Error('Activity can be filtered with "and" only.');let s=o,l=(s.value||"").trim();switch(s.field){case"action":l&&r("action",l);break;case"who":l&&r("actorKind",l);break;case"outcome":l&&r("outcome",l);break;case"when":{if(s.op==="within"){let[a,c]=Wt(l||"today",n);r("since",String(a.getTime())),r("until",String(c.getTime()-1))}else if(s.op==="on"&&l){let a=De(l);r("since",String(a.getTime())),r("until",String(Re(a,1).getTime()-1))}else if(s.op==="before"&&l)r("until",String(De(l).getTime()-1));else if(s.op==="after"&&l)r("since",String(Re(De(l),1).getTime()));else if(s.op==="between"&&l&&(s.value2||"").trim()){let a=De(l),c=De((s.value2||"").trim()),[u,p]=a<=c?[a,c]:[c,a];r("since",String(u.getTime())),r("until",String(Re(p,1).getTime()-1))}break}default:throw new Error('There is no field called "'+s.field+'".')}}return i}function ui(e){return e.split(".").join(" \xB7 ")}function Or(e,n){let i=e.actorKind||"",r=e.actor||"";switch(i){case"admin":return r?n&&n(r)||"admin "+r:"the admin credential";case"admin:readonly":return r?(n&&n(r)||"admin "+r)+" (read-only)":"the read-only credential";case"apiKey":return"API key: "+(r||"?");case"user":return n&&n(r)||"user "+r;case"anonymous":return"nobody signed in";case"cli":return"the command line";default:return r?i+" "+r:i||"\u2014"}}function Fr(e){let n=e.route||"",i=e.target||{},r=s=>typeof i[s]=="string"?i[s]:"";if(/^admin\\/permissions\\/collections\\/:name/.test(n))return{text:r("name")+" \xB7 permissions",href:ne("permissions")};if(/^admin\\/permissions\\/functions\\/:name/.test(n))return{text:r("name")+" \xB7 function permissions",href:ne("permissions")};if(/^admin\\/permissions/.test(n))return{text:"permissions",href:ne("permissions")};if(/^admin\\/roles\\/:name\\/users\\/:userId/.test(n))return{text:r("name")+" \xB7 role member",href:ne("roles",r("name"))};if(/^admin\\/roles\\/:name/.test(n))return{text:r("name")+" \xB7 role",href:ne("roles",r("name"))};if(/^admin\\/roles$/.test(n))return{text:"a role",href:ne("roles")};if(/^admin\\/users\\/:id/.test(n))return{text:r("id")+" \xB7 user",href:ne("users",r("id"))};if(/^admin\\/users$/.test(n))return{text:"a user",href:ne("users")};if(/^_admin\\/setup$/.test(n))return{text:"the first admin account",href:ne("users")};if(/^admin\\/views\\/:collection\\/:name/.test(n))return{text:r("collection")+" \xB7 view "+r("name"),href:ne("collections",r("collection"))};if(/^admin\\/keys\\/:id/.test(n))return{text:r("id")+" \xB7 API key",href:ne("keys",r("id"))};if(/^admin\\/keys$/.test(n))return{text:"an API key",href:ne("keys")};if(/^admin\\/secrets\\/:name/.test(n))return{text:r("name")+" \xB7 secret",href:ne("secrets")};if(/^admin\\/schema\\/:table/.test(n))return{text:r("table")+" \xB7 schema",href:ne("schema",r("table"))};if(/^(admin\\/schema|api\\/_schema)/.test(n))return{text:"schema",href:ne("schema")};if(/^admin\\/backups\\/restore$/.test(n))return{text:"a restore",href:ne("backups")};if(/^admin\\/backups/.test(n))return{text:"backups",href:ne("backups")};if(/^admin\\/import\\/:collection/.test(n))return{text:r("collection")+" \xB7 import",href:ne("collections",r("collection"))};if(/^admin\\/triggers\\/:id/.test(n))return{text:r("id")+" \xB7 trigger",href:ne("triggers",r("id"))};if(/^admin\\/triggers$/.test(n))return{text:"a trigger",href:ne("triggers")};if(/^admin\\/workflow-defs\\/:id/.test(n))return{text:r("id")+" \xB7 workflow",href:ne("workflows",r("id"))};if(/^admin\\/workflow-defs$/.test(n))return{text:"a workflow",href:ne("workflows")};if(/^admin\\/workflow-runs\\/:executionId/.test(n))return{text:r("executionId")+" \xB7 run",href:ne("runs",r("executionId"))};if(/^admin\\/workflows\\/:name/.test(n))return{text:r("name")+" \xB7 function file",href:ne("workflows")};if(/^admin\\/workflows/.test(n))return{text:"function files",href:ne("workflows")};if(/^admin\\/email\\/templates\\/:id/.test(n))return{text:r("id")+" \xB7 email template",href:ne("email",r("id"))};if(/^admin\\/email/.test(n))return{text:"email",href:ne("email")};if(/^admin\\/files\\/:name/.test(n))return{text:r("name")+" \xB7 file",href:ne("files")};if(/^admin\\/files/.test(n))return{text:"storage",href:ne("files")};if(/^admin\\/search\\/collections\\/:name/.test(n))return{text:r("name")+" \xB7 search",href:ne("search")};if(/^admin\\/ops$/.test(n))return{text:"server settings",href:ne("server")};if(/^admin\\/executions\\/compact$/.test(n))return{text:"run history",href:ne("server")};if(/^admin\\/auth\\/providers\\/:id/.test(n))return{text:r("id")+" \xB7 sign-in provider",href:ne("signin",r("id"))};if(/^admin\\/auth/.test(n))return{text:"sign-in",href:ne("signin")};let o=Object.keys(i);return o.length?{text:o.map(s=>r(s)||String(i[s])).join(" \xB7 ")}:{text:"\u2014"}}var pi=100;function Ga(e){let[n,i]=m({kind:"group",conj:"and",items:[]}),[r,o]=m(null),[s,l]=m(null),[a,c]=m(0),[u,p]=m(null),[d,f]=m({}),b=Ka(r||[]),g={};try{g=Va(n),u&&p(null)}catch(_){u||p(_.message)}let k=Object.keys(g).map(_=>_+"="+encodeURIComponent(g[_])).join("&");function y(){S("GET","/admin/audit?limit="+pi+"&offset="+a+(k?"&"+k:"")).then(_=>{o(v=>v===null?_.actions||[]:v),l(_);let N=new Set;for(let v of _.entries||[])(v.actorKind==="admin"||v.actorKind==="admin:readonly"||v.actorKind==="user")&&v.actor&&!d[v.actor]&&N.add(v.actor);N.forEach(v=>{S("GET","/admin/users/"+encodeURIComponent(v)).then(R=>{let A=R.user||{},U=A.email||A.username;U&&f(X=>({...X,[v]:U}))}).catch(()=>{})})}).catch(D)}j(y,[k,a]);function T(_){let N=Fr(_);ge(v=>t(ve,{title:ui(_.action||""),autoFocus:!1,wide:!0,actions:t(P,{onClick:v,children:"Close"}),children:[t(V,{children:[t(K,{kind:_.outcome==="success"?"ok":"bad",children:_.outcome==="success"?"succeeded":"failed"}),t("span",{class:"hint",children:he(_.at)}),t("span",{class:"hint",children:"by "+Or(_,R=>d[R]||null)}),N.href?t("a",{href:N.href,onClick:v,children:N.text}):t("span",{children:N.text})]}),t(G,{h:8}),_.detail&&Object.keys(_.detail).length?t("div",{class:"jx",children:t(an,{value:_.detail,name:"what changed",openTo:2})}):t("div",{class:"hint",children:"Nothing more was recorded."}),t(G,{h:8}),t("div",{class:"hint",children:[_.ip?"from "+_.ip:"",_.requestId?"request "+_.requestId:"",_.status?"HTTP "+_.status:""].filter(Boolean).join(" \xB7 ")}),t(He,{label:"Raw entry",children:t("textarea",{readOnly:!0,value:JSON.stringify(_,null,2),style:"min-height:240px;width:100%"})})]}))}let w=n.items.length>0,x=s?s.count:0,E=x?a+1:0,C=Math.min(a+pi,x);return t(ce,{title:"Activity",subtitle:"Who changed what, when, and from where \u2014 permissions, roles, keys, schema, backups, settings and sign-ins.",children:[t("div",{id:"activity-filter",children:t(gt,{group:n,fields:b,flat:!0,onChange:_=>{i(_),c(0)}})}),u?t(H,{children:u}):null,t(G,{}),t(V,{children:[s?t(W,{children:[s.enabled?t(K,{kind:"ok",children:"recording"}):t(K,{kind:"warn",children:"turned off on the Server page"}),t(K,{children:s.retentionDays?"kept "+s.retentionDays+" days":"kept forever"}),t("span",{class:"hint",id:"activity-count",children:[x?"Showing "+E+"\u2013"+C+" of "+x:"No entries",w&&k?" where "+Et(n,b):""]})]}):null,t(P,{tiny:!0,disabled:a===0,onClick:()=>c(Math.max(0,a-pi)),children:"\u2039 Newer"}),t(P,{tiny:!0,disabled:C>=x,onClick:()=>c(a+pi),children:"Older \u203A"}),t(P,{tiny:!0,onClick:y,children:"Refresh"})]}),t(G,{}),s?t(W,{children:[t(ye,{columns:["When","What","Who","On","Outcome",""],rows:s.entries||[],renderRow:(_,N)=>{let v=Fr(_);return t("tr",{children:[t("td",{children:he(_.at)}),t("td",{children:ui(_.action||"")}),t("td",{children:Or(_,R=>d[R]||null)}),t("td",{children:v.href?t("a",{href:v.href,children:v.text}):v.text}),t("td",{children:t(K,{kind:_.outcome==="success"?"ok":"bad",children:_.outcome==="success"?"succeeded":"failed"})}),t(Je,{children:t(P,{tiny:!0,onClick:()=>T(_),children:"Detail"})})]},_.objectId||N)},empty:w?t(oe,{children:"Nothing matches."}):t(oe,{action:{label:"Refresh",onClick:y,write:!1},children:"Nothing recorded yet. Changes to permissions, roles, keys, schema, backups and settings land here."})}),t(fe,{style:"margin-top:10px",children:"The trail is a table in this backend\u2019s own database: whoever can reach the file can edit it. Ship the logs off the machine if you need a copy nobody here can rewrite \u2014 every entry has a matching log line with the same request id."})]}):null]})}var Mr=[{id:"collections",feature:"collections",component:ys},{id:"schema",feature:"schema",component:ks},{id:"users",feature:"users",component:Ss},{id:"roles",feature:"roles",component:Ls},{id:"signin",feature:"auth",component:Bs},{id:"permissions",feature:"permissions",component:ra},{id:"keys",feature:"apiKeys",component:ca},{id:"triggers",feature:"triggers",component:ua},{id:"workflows",feature:"workflows",component:ma},{id:"runs",feature:"executions",component:ya},{id:"files",feature:"files",component:Ra},{id:"email",feature:"email",component:Na},{id:"backups",feature:"backups",component:Da},{id:"secrets",feature:"secrets",component:ja},{id:"search",feature:"search",component:Wa},{id:"server",feature:"ops",component:Ua},{id:"audit",feature:"ops",component:Ga}];function qa(e){return Mr.find(n=>n.id===e)}function $a(){let e=te(),n=!!e.whoami&&!e.whoami.adminAccount&&!e.readonly;return t(W,{children:[e.whoami?n?t(zu,{}):t(Ju,{}):e.booting?null:t($u,{error:e.loginError}),t(Oi,{}),t(Di,{})]})}function $u({error:e}){let[n,i]=m(""),[r,o]=m(""),[s,l]=m(""),[a,c]=m(!0),[u,p]=m(!1);return t("div",{id:"login",children:t("form",{id:"login-form",onSubmit:d=>{if(d.preventDefault(),!u){if(s.trim()){p(!0),xo(s.trim(),a).finally(()=>p(!1));return}!n.trim()||!r||(p(!0),Ro(n.trim(),r,a).finally(()=>p(!1)))}},children:[t("h1",{children:[t("span",{class:"dot"})," NodeGX Backend"]}),t("p",{class:"sub",children:"Sign in to manage this backend."}),t("div",{class:"field",style:"margin-top: 14px",children:t("input",{id:"login-email",type:"text",autocomplete:"username",placeholder:"Email","aria-label":"Email",value:n,onInput:d=>i(d.currentTarget.value)})}),t("div",{class:"field-row",style:"margin-top: 8px",children:[t("input",{id:"login-password",type:"password",autocomplete:"current-password",placeholder:"Password","aria-label":"Password",value:r,onInput:d=>o(d.currentTarget.value)}),t("button",{class:"btn primary",type:"submit",disabled:u||!s.trim()&&(!n.trim()||!r),children:"Sign in"})]}),t("label",{class:"check",style:"margin-top: 10px",children:[t("input",{id:"login-remember",type:"checkbox",checked:a,onChange:d=>c(d.currentTarget.checked)})," ",s.trim()?"Keep the credential in this tab (it is never kept longer)":"Keep me signed in on this browser"]}),e?t("div",{id:"login-error",class:"notice bad",style:"margin-top: 14px",children:e}):null,t("div",{class:"login-alt",children:t(He,{label:"Use the admin credential instead",children:[t("div",{class:"field-row",style:"margin-top: 6px",children:[t("input",{id:"login-token",type:"password",autocomplete:"off",placeholder:"Admin credential","aria-label":"Admin credential",value:s,onInput:d=>l(d.currentTarget.value)}),t("button",{class:"btn",type:"submit",disabled:u||!s.trim(),children:"Sign in with it"})]}),t("p",{class:"sub",style:"margin: 10px 0 0; font-size: 11px",children:["The credential is the ",t("code",{children:"adminToken"})," in the backend\u2019s ",t("code",{children:"secrets.json"}),", or whatever was passed to ",t("code",{children:"--token"}),". A new backend asks you to create your admin account once you sign in with it. A read-only credential signs in here too."]})]})})]})})}function zu(){let e=te(),[n,i]=m(""),[r,o]=m(""),[s,l]=m(!1),[a,c]=m(!1),[u,p]=m(null),d=e.whoami.backend||{},f=n.trim().indexOf("@")!==-1&&r.length>0;return t("div",{id:"login",class:"setup",children:t("form",{id:"setup-form",onSubmit:b=>{b.preventDefault(),!(!f||a)&&(c(!0),p(null),Eo(n.trim(),r).catch(g=>p(g.message)).finally(()=>c(!1)))},children:[t("h1",{children:[t("span",{class:"dot"})," Create your admin account"]}),t("p",{class:"sub",children:[d.name?t("b",{children:d.name}):"This backend"," has no admin yet. Choose the email and password you will sign in with here \u2014 and in your app, where this account is in the ",t("b",{children:"admin"})," role."]}),t("div",{class:"field",style:"margin-top: 14px",children:t("input",{id:"setup-email",type:"email",autocomplete:"username",placeholder:"Email","aria-label":"Email",value:n,onInput:b=>i(b.currentTarget.value)})}),t("div",{class:"field-row",style:"margin-top: 8px",children:[t("input",{id:"setup-password",type:s?"text":"password",autocomplete:"new-password",placeholder:"Password","aria-label":"Password",value:r,onInput:b=>o(b.currentTarget.value)}),t("button",{type:"button",class:"btn",onClick:()=>{o(zn(Jn)),l(!0)},children:"Generate"}),t("button",{type:"button",class:"btn","aria-pressed":s,onClick:()=>l(!s),children:s?"Hide":"Show"})]}),t("div",{class:"field-row",style:"margin-top: 14px",children:t("button",{id:"setup-submit",class:"btn primary",type:"submit",disabled:a||!f,children:"Create and sign in"})}),u?t("div",{id:"setup-error",class:"notice bad",style:"margin-top: 14px",children:u}):null,t("p",{class:"sub",style:"margin: 16px 0 0; font-size: 11px",children:"The admin credential you signed in with stays as it is \u2014 scripts and the editor keep using it. From now on you sign in here as yourself, and give other people access from the Users page."}),t("p",{class:"sub",style:"margin: 8px 0 0; font-size: 11px",children:t("a",{href:"#",onClick:b=>{b.preventDefault(),Nn(null)},children:"Not now \u2014 sign out"})})]})})}function Ju(){let e=te(),n=Do(),i=Lo(e.features),r=qa(n.view),o=r&&e.features[r.feature]?r:void 0;j(()=>{if(o)return;let a=Mr.find(c=>e.features[c.feature]);a&&sn(a.id)},[n.view,o,e.features]),j(()=>{let a=No(location.hash).split("/")[0],c=On();a&&c.view&&a!==c.view&&sn(c.view,...c.params)},[n.view]);let s=o?o.component:null,l=e.whoami;return t("div",{id:"app",children:[t("div",{class:"brand",children:[t("span",{class:"dot"})," ",t("b",{children:"NodeGX"})," ",t("span",{class:"brand-role",children:"manager"})]}),t(Yu,{}),t("nav",{id:"nav","aria-label":"Sections",children:i.map(a=>t("div",{children:[t("div",{class:"group",children:a.label}),a.entries.map(c=>t("a",{href:ne(c.id),"aria-current":n.view===c.id?"true":"false",children:c.label},c.id))]},a.label))}),t("main",{id:"main",children:[l.security&&!l.security.enforced?t(Xu,{}):null,l.firstRun?t(Zu,{}):null,l.adminAccount?null:t(Qu,{}),s?t(s,{params:n.params,query:n.query},o.id):i.length?null:t(J,{kind:"bad",children:"This backend reports no available admin sections."})]})]})}function Yu(){let e=te(),n=ze(dt),i=Io(),r=e.whoami.backend||{},o=e.whoami.security;return t("div",{class:"topbar",children:[t("span",{id:"backend-name",class:"mono",children:[r.name," \xB7 ",r.id]}),t("span",{id:"tier-chip",children:e.readonly?t(K,{kind:"warn",children:"read-only"}):t(K,{kind:"accent",children:"admin"})}),t("span",{id:"person-chip",class:"mono",title:e.whoami.person?"Signed in as "+(e.whoami.person.email||e.whoami.person.username):"Signed in with the admin credential",children:e.whoami.person?e.whoami.person.email||e.whoami.person.username:"credential"}),t("span",{id:"enforce-chip",children:o&&o.enforced?t(K,{kind:"ok",children:"enforcing"}):t(K,{kind:"warn",children:"dev-open \u2014 nothing enforced"})}),t("span",{class:"spacer"}),t("span",{id:"live-chip",children:n.state==="off"?null:t(K,{kind:n.state==="live"?"ok":"warn",children:n.state})}),t("button",{type:"button",class:"btn tiny theme-toggle","aria-label":i.theme==="dark"?"Switch to the light theme":"Switch to the dark theme",title:i.theme==="dark"?"Light theme":"Dark theme",onClick:Po,children:i.theme==="dark"?"\u2600":"\u263E"}),t(P,{tiny:!0,id:"signout",onClick:()=>Nn(null),children:"Sign out"})]})}function Xu(){return t(J,{kind:"warn",children:[t("b",{children:"Dev-open is on. "}),"While it is, collection permissions and row ACLs do nothing for any caller of your app. Dev-open only runs on a backend bound to this machine. To test your real rules, turn on Enforce my rules on this machine at the top of the ",t("a",{href:"#/permissions",children:"Permissions page"}),"."]})}function Zu(){return t(J,{kind:"accent",children:[t("b",{children:"First run. "}),'This backend generated its own admin credential on this start. It is the "adminToken" in the backend\u2019s secrets.json (mode 0600) \u2014 the editor and scripts use it; you sign in here as yourself. To choose your own, restart the service with --token <your-secret>.']})}function Qu(){return t(J,{kind:"warn",children:[t("b",{children:"No admin account yet. "}),"Nobody has full access to this backend as a person. Sign in with the full admin credential and the manager asks you to create one."]})}Ao();var za=document.getElementById("root");za&&ao(t($a,{}),za);To();})();
65046
+ curl -X POST -H 'Content-Type: application/json' -H "X-Hub-Signature-256: sha256=$SIG" -d "$BODY" \`+e}function pa({params:e}){let[n,i]=m(null),[r,o]=m(null),[s,l]=m(null),[a,c]=m(null),[u,p]=m(null),d=e[0]||"";function f(){S("GET","/admin/triggers").then(y=>{i(y&&y.triggers||[]),y&&y.overlapDefault&&o(y.overlapDefault)}).catch(O)}j(()=>{f(),S("GET","/admin/permissions/functions").then(y=>l(y.functions||[])).catch(()=>l([])),S("GET","/admin/workflow-defs").then(y=>c(y&&(y.workflows||y.definitions)||[])).catch(()=>c([])),S("GET","/admin/schema").then(y=>p((y.tables||[]).map(T=>T.name).filter(T=>T.charAt(0)!=="_"))).catch(()=>p([]))},[]);function b(y,T){S("POST","/admin/triggers/"+I(y.id)+"/enabled",{enabled:T}).then(()=>{D((y.name||y.id)+(T?" is on.":" is off."),"ok"),f()}).catch(O)}let g=y=>{let T=(a||[]).find(w=>w.id===y);return T&&T.name?String(T.name):y},v=d&&d!=="new"&&n&&n.find(y=>y.id===d)||null;return t(ce,{title:"Triggers",subtitle:"Something that runs on its own: on a schedule, when called from outside, or when data changes.",children:[t(V,{children:[t(B,{kind:"primary",onClick:()=>Z("triggers","new"),children:"New trigger"}),t(A,{onClick:f,children:"Refresh"})]}),t(G,{}),n?t(ye,{columns:["Name","When","Runs","Next run","Last run","Overlap","Enabled"],rows:n,empty:t(oe,{icon:"\u23F1",action:{label:"Schedule a function",onClick:()=>Z("triggers","new")},children:"Nothing runs on its own yet."}),renderRow:y=>{let T=y.status||y;return t("tr",{id:"trigger-"+y.id,class:"clickable"+(y.id===d?" hit":""),onClick:()=>Z("triggers",y.id),children:[t("td",{children:y.name||y.id}),t("td",{children:Ed(y)}),t("td",{children:y.target&&y.target.kind==="workflow"?t(W,{children:[t("span",{class:"hint",children:"workflow "}),g(y.target.name||"")]}):t(W,{children:[t("span",{class:"hint",children:"function "}),t("span",{class:"mono",children:y.target&&y.target.name||""})]})}),t("td",{children:y.type==="schedule"?y.enabled?he(T.nextFireAt):t("span",{class:"hint",children:"off"}):t("span",{class:"hint",children:"\u2014"})}),t("td",{children:t(Ad,{status:T})}),t("td",{children:t(Id,{t:y})}),t("td",{onClick:w=>w.stopPropagation(),children:t(re,{checked:!!y.enabled,label:"Enabled: "+(y.name||y.id),onChange:w=>b(y,w)})})]},y.id)}}):null,(d==="new"||v)&&r?t(Fd,{existing:v,overlapDefault:r,functions:s,workflows:a,collections:u,onClose:()=>Z("triggers"),onChanged:f},d):null]})}function Ad({status:e}){let n=e.lastSkip,i=e.lastFiredAt?new Date(String(e.lastFiredAt)).getTime():0,r=n&&n.at?new Date(String(n.at)).getTime():0;return n&&r>=i?t("span",{title:"Skipped at "+he(n.at)+": the previous run was still going"+(n.yieldedTo?" ("+n.yieldedTo+")":"")+".",children:[t(K,{kind:"warn",children:"skipped"})," ",t("span",{class:"sub",children:[je(n.at)," \xB7 still running"]})]}):e.lastFiredAt?t("span",{title:he(e.lastFiredAt),children:[t(Pd,{result:e.lastResult})," ",t("span",{class:"sub",children:je(e.lastFiredAt)})]}):t("span",{class:"hint",children:"never"})}function Pd({result:e}){if(!e)return t(K,{children:"ran"});let n=e;return e==="ok"||n.ok===!0?t(K,{kind:"ok",children:"ok"}):t(K,{kind:"bad",children:typeof e=="string"?e:n.error||"failed"})}function Id({t:e}){if(e.type!=="schedule")return t("span",{class:"sub",children:"\u2014"});let n=e.effectiveOverlapPolicy||"skip",i=e.status||{},r=i.lastSkip,o=i.skipCount||1,s=r&&r.yieldedTo?" \u2192 "+r.yieldedTo:"";return t("div",{children:[t(K,{kind:n==="allow"?"":"ok",children:n}),r?t("div",{class:"sub",title:"The most recent fire that did not run, and the execution it yielded to.",children:"skipped "+o+"\xD7 \xB7 last "+he(r.at)+s}):null]})}function Ld(e,n){let i=e&&e.schedule||{},r=e&&e.webhook||{},o=e&&e.dbChange||{};return{name:e&&e.name||"",targetKind:e&&e.target&&e.target.kind||"function",targetName:e&&e.target&&e.target.name||"",type:e&&e.type||null,cron:i.cron||"",missed:i.missedFirePolicy||"skip",overlap:e&&e.effectiveOverlapPolicy||n,payload:Ht(i.payload),slug:r.slug||"",slugTouched:!!e,scheme:r.scheme||"hmac-sha256",senderKey:"",senderKeySaved:r.scheme==="hmac-sha512",maxBodyBytes:r.maxBodyBytes||Td,collection:o.collection||"",actions:(o.actions||[]).slice(),sync:!!(e&&e.responseMode==="sync"),waitS:e&&e.responseTimeoutMs?Math.round(e.responseTimeoutMs/1e3):Sd,enabled:e?e.enabled!==!1:!0}}function Nd(e){if(!e.type)return"Pick when it runs.";if(!e.targetName.trim())return e.targetKind==="workflow"?"Pick the workflow it runs.":"Pick the function it runs.";if(e.type==="schedule"&&!e.cron)return"Finish the schedule.";if(e.type==="webhook"){if(!e.slug)return"Give the hook a path.";if(!Cd.test(e.slug))return"A hook path is lowercase letters, digits and hyphens, starting with a letter or digit, up to 64 long.";if(e.scheme==="hmac-sha512"&&!e.senderKeySaved&&!e.senderKey.trim())return"Paste the Paystack secret key the hook checks with."}if(e.type==="db-change"){if(!e.collection)return"Pick the collection to watch.";if(!e.actions.length)return"Tick at least one of created, changed, deleted."}return e.targetKind==="workflow"&&e.sync&&!(Number.isInteger(e.waitS)&&e.waitS>=1&&e.waitS<=Ar)?"Wait between 1 and "+Ar+" seconds.":null}function Dd(e){let n={type:e.type,name:e.name.trim()||void 0,enabled:e.enabled,target:{kind:e.targetKind,name:e.targetName.trim()}};if(e.targetKind==="workflow"&&e.sync&&(n.responseMode="sync",n.responseTimeoutMs=e.waitS*1e3),e.type==="schedule"){let i=Ct(e.payload);n.schedule={cron:e.cron,missedFirePolicy:e.missed,overlapPolicy:e.overlap,...Object.keys(i).length?{payload:i}:{}}}else e.type==="webhook"?(n.webhook={slug:e.slug,scheme:e.scheme,maxBodyBytes:e.maxBodyBytes},e.scheme==="hmac-sha512"&&e.senderKey.trim()&&(n.secret=e.senderKey.trim())):e.type==="db-change"&&(n.dbChange={collection:e.collection,actions:e.actions});return n}var Od=[{id:"schedule",title:"On a schedule",words:"Every so often, at a time you pick."},{id:"webhook",title:"When called from outside",words:"Another service posts to a URL \u2014 Stripe, GitHub, a form."},{id:"db-change",title:"When data changes",words:"A record in a collection is created, changed or deleted."}];function Fd({existing:e,overlapDefault:n,functions:i,workflows:r,collections:o,onClose:s,onChanged:l}){let{readonly:a,whoami:c}=te(),u=c&&c.backend&&c.backend.id||"",p=typeof location<"u"?location.origin:"",[d,f]=m(()=>Ld(e,n)),[b,g]=m(null),[v,y]=m(!1),[T,w]=m(null),x=h=>f(L=>({...L,...h})),R=Nd(d),C=!v&&!a&&!R,_=_d(p,u,d.slug),N=h=>S("POST","/admin/triggers/preview",{cron:h,count:5});function k(){if(R){g(R);return}let h;try{h=Dd(d)}catch(Q){g(Q.message);return}g(null),y(!0),(e?S("PUT","/admin/triggers/"+I(e.id),h):S("POST","/admin/triggers",h)).then(Q=>{if(y(!1),l(),Q&&Q.secret){w({secret:Q.secret,note:Q.secretNote});return}D(e?"Saved.":"Trigger created.","ok"),s()}).catch(Q=>{y(!1),g(Q.message)})}function E(){e&&(y(!0),S("POST","/admin/triggers/"+I(e.id)+"/fire",{}).then(h=>{y(!1);let L=h&&h.response&&h.response.statusCode;D("Ran now"+(L?" ("+L+")":"")+" \u2014 the record is in Runs.",L&&L>=400?"bad":"ok"),l()}).catch(h=>{y(!1),O(h)}))}function P(){e&&we("Rotate the secret","A new secret is made and shown once. Every sender still using the current one is refused from that moment until it is given the new secret.",()=>{S("POST","/admin/triggers/"+I(e.id)+"/secret",{}).then(h=>{l(),w({secret:h.secret,note:h.secretNote})}).catch(O)},"Rotate")}function U(){e&&S("DELETE","/admin/triggers/"+I(e.id)).then(()=>{D("Trigger deleted.","ok"),l(),s()}).catch(O)}let X=()=>{l(),s()};if(T)return t(Se,{title:e?"New secret for "+(d.name||e.id):"Your new webhook: "+(d.name||d.slug),onClose:X,footer:t(A,{kind:"primary",onClick:X,children:"Done"}),children:t(Ud,{secret:T.secret,note:T.note,url:_,scheme:d.scheme})});let H=e?d.name||e.id:"New trigger",q=e?"Last changed "+he(e.updatedAt)+".":void 0;return t(Se,{title:H,subtitle:q,onClose:s,wide:!0,footer:t(W,{children:[t(A,{onClick:s,children:"Cancel"}),t(re,{id:"trigger-enabled",checked:d.enabled,disabled:a,onChange:h=>x({enabled:h}),children:"Enabled"}),t(me,{}),e?t(A,{onClick:()=>location.hash=Fn("runs",[],{trigger:e.id}),title:"Every run this trigger started, in Runs.",children:"Runs of this trigger"}):null,e?t(B,{disabled:v,onClick:E,title:"Fire it once, now, whatever the schedule says. The run is recorded like any other.",children:"Run now"}):null,t(B,{kind:"primary",disabled:!C,title:R||void 0,onClick:k,children:e?"Save":"Create"})]}),children:[t("div",{class:"form-grid",onInput:()=>g(null),children:[t($,{label:"Name",children:t("input",{type:"text",placeholder:"What it does \u2014 e.g. nightly digest",value:d.name,disabled:a,onInput:h=>{let L=h.currentTarget.value;x(d.slugTouched?{name:L}:{name:L,slug:Rd(L)})}})}),t("div",{class:"field",children:[t("span",{class:"field-head",children:t("b",{children:"What runs"})}),t("div",{class:"scope-group",children:[t("div",{class:"field-line",children:[t("label",{class:"check",children:[t("input",{type:"radio",name:"target-kind",checked:d.targetKind==="function",disabled:a,onChange:()=>x({targetKind:"function",targetName:"",sync:!1})})," A function"]}),t("label",{class:"check",children:[t("input",{type:"radio",name:"target-kind",checked:d.targetKind==="workflow",disabled:a,onChange:()=>x({targetKind:"workflow",targetName:""})})," A workflow"]})]}),t("div",{class:"scope-indent",id:"target-picker",children:d.targetKind==="function"?t(Md,{functions:i,value:d.targetName,onChange:h=>x({targetName:h}),disabled:a}):t(Bd,{workflows:r,value:d.targetName,onChange:h=>x({targetName:h}),disabled:a})})]})]}),t("div",{class:"field",children:[t("span",{class:"field-head",children:t("b",{children:"When"})}),e?t(M,{children:["This trigger runs ",d.type==="schedule"?"on a schedule":d.type==="webhook"?"when called from outside":"when data changes",". A trigger\u2019s kind cannot change \u2014 for a different kind, make a new one and delete this."]}):t("div",{class:"tiles",role:"radiogroup","aria-label":"When it runs",children:Od.map(h=>t("label",{class:"tile"+(d.type===h.id?" on":""),children:[t("input",{type:"radio",name:"trigger-type",value:h.id,checked:d.type===h.id,disabled:a,onChange:()=>x({type:h.id})}),t("b",{children:h.title}),t("span",{class:"sub",children:h.words})]},h.id))}),d.type==="schedule"?t("div",{class:"when-body",children:[t(_t,{id:"trigger-schedule",value:d.cron,onChange:h=>x({cron:h}),preview:N,disabled:a}),t(G,{h:10}),t("div",{class:"grid2",children:[t("div",{class:"scope-group",children:[t("div",{class:"field-head",children:t("b",{children:"If a run is missed while the backend is off"})}),t("label",{class:"check",children:[t("input",{type:"radio",name:"missed",checked:d.missed==="skip",disabled:a,onChange:()=>x({missed:"skip"})})," Skip it \u2014 carry on from the next time"]}),t("label",{class:"check",children:[t("input",{type:"radio",name:"missed",checked:d.missed==="run-once-on-start",disabled:a,onChange:()=>x({missed:"run-once-on-start"})})," Run once when the backend starts"]})]}),t("div",{class:"scope-group",children:[t("div",{class:"field-head",children:t("b",{children:"If the last run is still going"})}),t("label",{class:"check",children:[t("input",{type:"radio",name:"overlap",checked:d.overlap==="skip",disabled:a,onChange:()=>x({overlap:"skip"})})," Skip this run"]}),t("label",{class:"check",children:[t("input",{type:"radio",name:"overlap",checked:d.overlap==="queue-one",disabled:a,onChange:()=>x({overlap:"queue-one"})})," Queue one, run it when the other finishes"]}),t("label",{class:"check",children:[t("input",{type:"radio",name:"overlap",checked:d.overlap==="allow",disabled:a,onChange:()=>x({overlap:"allow"})})," Run anyway, alongside it"]})]})]})]}):null,d.type==="webhook"?t("div",{class:"when-body",children:[t($,{label:"Path",children:t("div",{class:"field-line",children:[t("span",{class:"mono hint",children:["/hooks/",u,"/"]}),t("input",{type:"text",class:"mono","aria-label":"Hook path",placeholder:"stripe-payments",value:d.slug,disabled:a,onInput:h=>x({slug:h.currentTarget.value.trim(),slugTouched:!0})})]})}),t("div",{class:"field",children:[t("span",{class:"field-head",children:t("b",{children:"The URL to paste into the sender"})}),t("div",{class:"secret-box mono","data-webhook-url":!0,children:_}),t(V,{style:"margin-top:6px",children:[t(A,{tiny:!0,onClick:()=>Ge(_).then(h=>D(h?"URL copied.":"The browser refused the clipboard.",h?"ok":"bad")),children:"Copy the URL"}),e&&e.webhook&&e.webhook.scheme!=="hmac-sha512"?t(B,{tiny:!0,onClick:P,title:"Make a new secret and show it once.",children:"Rotate the secret"}):null]})]}),t("div",{class:"grid2",children:[t("div",{class:"scope-group",children:[t("div",{class:"field-head",children:t("b",{children:"How the sender proves it is them"})}),t("label",{class:"check",children:[t("input",{type:"radio",name:"scheme",checked:d.scheme==="hmac-sha256",disabled:a,onChange:()=>x({scheme:"hmac-sha256"})})," Signature (GitHub, Stripe)"]}),t(M,{children:"An HMAC-SHA256 of the body in X-Hub-Signature-256, X-Signature-256 or X-Webhook-Signature. The secret never crosses the wire."}),t("label",{class:"check",children:[t("input",{type:"radio",name:"scheme",checked:d.scheme==="hmac-sha512",disabled:a,onChange:()=>x({scheme:"hmac-sha512"})})," Paystack signature"]}),t(M,{children:"An HMAC-SHA512 of the body in x-paystack-signature, made with the Paystack business's secret key. One hook per Paystack business."}),d.scheme==="hmac-sha512"?t("input",{type:"password",class:"mono","aria-label":"Paystack secret key",style:"margin:6px 0 10px",autocomplete:"off",placeholder:d.senderKeySaved?"Saved. Paste a new key only to change it.":"sk_test_\u2026 or sk_live_\u2026",value:d.senderKey,disabled:a,onInput:h=>x({senderKey:h.currentTarget.value})}):null,t("label",{class:"check",children:[t("input",{type:"radio",name:"scheme",checked:d.scheme==="token",disabled:a,onChange:()=>x({scheme:"token"})})," Shared token"]}),t(M,{children:"The secret itself, in X-Webhook-Token, as a Bearer token, or as ?token=. For senders that cannot sign."})]}),t("div",{class:"scope-group",children:[t("div",{class:"field-head",children:t("b",{children:"Largest body accepted"})}),t("select",{"aria-label":"Largest body accepted",value:String(d.maxBodyBytes),disabled:a,onChange:h=>x({maxBodyBytes:Number(h.currentTarget.value)}),children:(si.some(h=>h.bytes===d.maxBodyBytes)?si:[{bytes:d.maxBodyBytes,label:xd(d.maxBodyBytes)+" (as saved)"},...si]).map(h=>t("option",{value:String(h.bytes),children:h.label},h.bytes))}),t(M,{children:"A bigger call is refused with 413 and recorded in Runs."})]})]}),d.scheme==="hmac-sha512"?t(W,{children:[t(M,{children:"The key is kept with the backend's secrets and never shown again. Paste the hook's URL into the Paystack dashboard, under Settings, API keys and webhooks."}),t(Wd,{url:_})]}):e?t(M,{children:"The secret was shown when the hook was made and is not kept in a readable form. If it is lost, rotate it."}):t(M,{children:"The secret is made when you press Create and shown once."})]}):null,d.type==="db-change"?t("div",{class:"when-body",children:[t($,{label:"Collection",children:t(Hd,{collections:o,value:d.collection,onChange:h=>x({collection:h}),disabled:a})}),t("div",{class:"field",children:[t("span",{class:"field-head",children:t("b",{children:"When a record is"})}),t("div",{class:"field-line",id:"change-actions",children:da.map(h=>t("label",{class:"check",children:[t("input",{type:"checkbox",value:h.id,checked:d.actions.indexOf(h.id)!==-1,disabled:a,onChange:L=>x({actions:L.currentTarget.checked?[...d.actions,h.id]:d.actions.filter(Q=>Q!==h.id)})}),h.label]},h.id))}),t(M,{children:"The changed record is handed to the target as its body. Writes made by the target itself do not fire it again."})]})]}):null]}),d.type==="schedule"?t("div",{class:"field",children:[t("span",{class:"field-head",children:[t("b",{children:"With"})," ",t("span",{class:"hint",children:"what each run receives"})]}),t(St,{id:"trigger-payload",rows:d.payload,onChange:h=>x({payload:h}),disabled:a})]}):null,d.targetKind==="workflow"?t("div",{class:"field",children:[t("span",{class:"field-head",children:[t("b",{children:"Answer"})," ",t("span",{class:"hint",children:"what a caller gets back"})]}),t("div",{class:"scope-group",children:[t("label",{class:"check",children:[t("input",{type:"radio",name:"answer",checked:!d.sync,disabled:a,onChange:()=>x({sync:!1})})," The run\u2019s id, straight away"]}),t("label",{class:"check",children:[t("input",{type:"radio",name:"answer",checked:d.sync,disabled:a,onChange:()=>x({sync:!0})})," The workflow\u2019s output \u2014 wait up to",t("input",{type:"number",min:1,max:Ar,step:1,"aria-label":"Seconds to wait",class:"sched-minute",value:String(d.waitS),disabled:a||!d.sync,onInput:h=>x({waitS:Number(h.currentTarget.value)})}),"seconds"]}),t(M,{children:"Only a caller can be answered: a webhook, or Run now. A schedule has nobody to answer."})]})]}):null]}),b?t(J,{kind:"bad",style:"margin-top:12px",children:b}):R&&d.type?t(M,{children:R}):null,e?t(W,{children:[t(G,{h:18}),t($e,{children:t(Ze,{label:"Delete",why:"It stops running and its record here is gone. Past runs stay in Runs.",title:"Delete trigger",warning:'"'+(e.name||e.id)+'" will not run again.'+(e.type==="webhook"?" Its URL stops answering and its secret is discarded.":""),expected:e.name||e.id,onConfirm:U})})]}):null]})}function Md({functions:e,value:n,onChange:i,disabled:r}){let o=e||[],s=o.find(c=>c.name===n),l=n?s||{name:n,deployed:!1}:null;return t(W,{children:[t(Ce,{fetch:c=>{let u=c.trim().toLowerCase();return Promise.resolve(o.filter(p=>!u||p.name.toLowerCase().indexOf(u)!==-1))},label:c=>c.name,detail:c=>c.deployed===!1?"not deployed":"",keyOf:c=>c.name,value:l,onPick:c=>i(c?c.name:""),onCreate:c=>i(c),createLabel:c=>"Use the name \u201C"+c+"\u201D (not deployed yet)",placeholder:e===null?"Loading functions\u2026":"Type a function name\u2026",emptyText:o.length?"No function by that name.":"No functions are deployed yet. Type a name to use one that is coming.",disabled:r}),l&&l.deployed===!1?t(M,{children:[t(K,{kind:"warn",children:"not deployed"})," Nothing called ",l.name," is deployed on this backend. The trigger will fail until it is."]}):null]})}function Bd({workflows:e,value:n,onChange:i,disabled:r}){let o=e||[],s=c=>c.name?String(c.name):c.id,l=n?o.find(c=>c.id===n)||{id:n}:null;return t(Ce,{fetch:c=>{let u=c.trim().toLowerCase();return Promise.resolve(o.filter(p=>!u||s(p).toLowerCase().indexOf(u)!==-1))},label:s,keyOf:c=>c.id,value:l,onPick:c=>i(c?c.id:""),placeholder:e===null?"Loading workflows\u2026":"Type a workflow name\u2026",emptyText:o.length?"No workflow by that name.":"No workflows yet. Author one in the editor and it appears here.",disabled:r})}function Hd({collections:e,value:n,onChange:i,disabled:r}){let o=e||[];return t(Ce,{fetch:l=>{let a=l.trim().toLowerCase();return Promise.resolve(o.filter(c=>!a||c.toLowerCase().indexOf(a)!==-1))},label:l=>l,keyOf:l=>l,value:n||null,onPick:l=>i(l||""),placeholder:e===null?"Loading collections\u2026":"Type a collection name\u2026",emptyText:o.length?"No collection by that name.":"No collections yet.",disabled:r})}var jd="paste the secret key here";function Wd({url:e}){let n=ua(e,"hmac-sha512",jd);return t("div",{class:"field",children:[t("span",{class:"field-head",children:t("b",{children:"Try it"})}),t("pre",{class:"secret-box mono","data-example":!0,children:n}),t(V,{style:"margin-top:6px",children:t(A,{tiny:!0,onClick:()=>Ge(n).then(i=>D(i?"Command copied.":"The browser refused the clipboard. Select it and copy by hand.",i?"ok":"bad")),children:"Copy the command"})}),t(M,{children:"Put the key in the first line and run it in a terminal on this computer. The answer 200 means the hook accepted the signature. The test names no order, so nothing changes."})]})}function Ud({secret:e,note:n,url:i,scheme:r}){let[o,s]=m(!1),l=ua(i,r,e);function a(c,u){Ge(c).then(p=>{p?(s(!0),D(u+" copied.","ok"),setTimeout(()=>s(!1),2500)):D("The browser refused the clipboard. Select it and copy by hand.","bad")})}return t("div",{class:"secret-card",children:[t(J,{kind:"warn",children:[n||"Store this now \u2014 it is not recoverable."," Paste it into the sender beside the URL."]}),t(G,{h:10}),t("div",{class:"field",children:[t("span",{class:"field-head",children:t("b",{children:"URL"})}),t("div",{class:"secret-box mono","data-webhook-url":!0,children:i}),t(V,{style:"margin-top:6px",children:t(A,{tiny:!0,onClick:()=>a(i,"URL"),children:"Copy the URL"})})]}),t(G,{h:10}),t("div",{class:"field",children:[t("span",{class:"field-head",children:t("b",{children:"Secret"})}),t("div",{class:"secret-box mono","data-secret":!0,children:e}),t(V,{style:"margin-top:6px",children:[t(A,{kind:"primary",onClick:()=>a(e,"Secret"),children:o?"Copied \u2713":"Copy secret"}),t(M,{children:r==="token"?"The sender puts it in X-Webhook-Token.":"The sender signs each body with it (HMAC-SHA256) and sends the signature in X-Hub-Signature-256."})]})]}),t(G,{h:10}),t("div",{class:"field",children:[t("span",{class:"field-head",children:t("b",{children:"Try it"})}),t("pre",{class:"secret-box mono","data-example":!0,children:l}),t(V,{style:"margin-top:6px",children:t(A,{tiny:!0,onClick:()=>a(l,"Command"),children:"Copy the command"})})]})]})}function Kd(e){let n=[],i={},r=(o,s)=>{if(s>40||o===null||typeof o!="object")return;if(Array.isArray(o)){o.forEach(a=>r(a,s+1));return}let l=o;if(typeof l.$path=="string"){let a=/^body\\.([^.[\\]]+)/.exec(l.$path);a&&!i[a[1]]&&(i[a[1]]=!0,n.push(a[1]));return}if(!("$literal"in l))for(let a of Object.keys(l))r(l[a],s+1)};return(e&&e.steps||[]).forEach(o=>r(o,0)),n}function Vd(e){return e.map(n=>({key:n,type:"text",raw:""}))}function Gd(e){return e?e.engineStatus==="cancelled"?"cancelled":e.status:""}function ma(e){return typeof e.name=="string"&&e.name?e.name:e.id}function fa({params:e}){let[n,i]=m(null),[r,o]=m({}),s=e[0]||"";function l(){S("GET","/admin/workflow-defs").then(c=>{i(c&&(c.workflows||c.definitions)||[]),o(c&&c.lastRuns||{})}).catch(O)}j(()=>{l()},[]);let a=s&&n&&n.find(c=>c.id===s)||null;return t(ce,{title:"Workflows",subtitle:"Step-by-step jobs this backend runs. Run one here; author them in the editor.",children:[t(V,{children:t(A,{onClick:l,children:"Refresh"})}),t(G,{}),n?t(ye,{columns:["Name","Steps","Last run",""],rows:n,empty:t(oe,{icon:"\u21F6",action:{label:"Refresh",onClick:l,write:!1},children:"No workflows yet. Workflows are authored in the editor."}),renderRow:c=>{let u=r[c.id],p=Gd(u);return t("tr",{id:"workflow-"+c.id,class:c.id===s?"hit":"",children:[t("td",{children:ma(c)}),t("td",{children:String((c.steps||[]).length)}),t("td",{children:u?t("a",{href:ne("runs",u.id),title:"Open the record",children:[t(K,{kind:cn(p),children:p}),t("span",{class:"hint",children:" "+he(u.startedAt)})]}):t("span",{class:"hint",children:"never"})}),t("td",{class:"actions",children:[t(A,{tiny:!0,onClick:()=>location.hash=Fn("runs",[],{workflow:c.id}),title:"Every run of this workflow",children:"Runs"}),t(B,{tiny:!0,kind:"primary",onClick:()=>Z("workflows",c.id),children:"Run"})]})]},c.id)}}):null,s&&n&&!a?t(J,{kind:"warn",children:["There is no workflow called \u201C",s,"\u201D."]}):null,a?t(qd,{workflow:a,onClose:()=>Z("workflows")},a.id):null]})}function qd({workflow:e,onClose:n}){let i=Kd(e),[r,o]=m(()=>Vd(i)),[s,l]=m(null),[a,c]=m(!1);function u(){let p;try{p=Ct(r)}catch(d){l(d.message);return}l(null),c(!0),S("POST","/admin/workflow-defs/"+I(e.id)+"/run",{payload:p,wait:!1}).then(d=>{c(!1);let f=d&&d.executionId||d&&d.run&&d.run.executionId||"";if(f){Z("runs",f);return}D("Run finished: "+(d&&d.run&&d.run.status||"see Runs")+".","ok"),n()}).catch(d=>{c(!1),l(d.message)})}return t(Se,{title:"Run "+ma(e),subtitle:((e.steps||[]).length===1?"1 step":(e.steps||[]).length+" steps")+", starting at "+ve(e.entry),onClose:n,footer:t(W,{children:[t(A,{onClick:n,children:"Cancel"}),t(B,{kind:"primary",disabled:a,onClick:u,children:"Run"})]}),children:[t("div",{id:"run-inputs",children:[i.length?t(W,{children:[t(fe,{children:"What this workflow reads"}),t(M,{children:["Its steps ask for ",i.map(p=>"\u201C"+p+"\u201D").join(", ")," by name. Fill those in; add anything else the steps should also start with."]})]}):t(W,{children:[t(fe,{children:"What each step starts with"}),t(M,{children:"This workflow reads nothing by name. Anything you add here reaches every step as its starting data."})]}),t(G,{h:8}),t(St,{id:"run-payload",rows:r,onChange:o,keyPlaceholder:"name"})]}),s?t(J,{kind:"bad",style:"margin-top:12px",children:s}):null]})}var ha=[{id:"workflow",label:"workflow"},{id:"function",label:"function"},{id:"backup",label:"backup"},{id:"maintenance",label:"maintenance"}];var vn=50,ga=5e3;function $d(e,n){return[{name:"status",kind:"choice",ops:["is"],options:Vo.filter(i=>i).map(i=>({value:i,label:i}))},{name:"kind",kind:"choice",ops:["is"],options:ha.map(i=>({value:i.id,label:i.label}))},{name:"name",kind:"text",ops:["contains"]},{name:"trigger",kind:"choice",ops:["is"],options:e.map(i=>({value:i.id,label:i.name||i.id}))},{name:"workflow",kind:"choice",ops:["is"],options:n.map(i=>({value:i.id,label:typeof i.name=="string"&&i.name?i.name:i.id}))},{name:"started",kind:"date",ops:["within","on","before","after","between"]},{name:"duration (s)",kind:"number",ops:["gte"]}]}function zd(e,n=new Date){if(e.conj==="or"&&e.items.length>1)throw new Error('Runs can be filtered with "and" only.');let i={},r=(o,s)=>{if(i[o]!==void 0)throw new Error(o+" is asked twice.");i[o]=s};for(let o of e.items){if(o.kind==="group")throw new Error('Runs can be filtered with "and" only.');let s=o,l=(s.value||"").trim();switch(s.field){case"status":l&&r("status",l);break;case"kind":l&&r("kind",l);break;case"name":l&&r("name",l);break;case"trigger":l&&r("trigger",l);break;case"workflow":l&&r("workflowId",l);break;case"started":{if(s.op==="within"){let[a,c]=Wt(l||"today",n);r("since",String(a.getTime())),r("until",String(c.getTime()-1))}else if(s.op==="on"&&l){let a=De(l);r("since",String(a.getTime())),r("until",String(Re(a,1).getTime()-1))}else if(s.op==="before"&&l)r("until",String(De(l).getTime()-1));else if(s.op==="after"&&l)r("since",String(Re(De(l),1).getTime()));else if(s.op==="between"&&l&&(s.value2||"").trim()){let a=De(l),c=De((s.value2||"").trim()),[u,p]=a<=c?[a,c]:[c,a];r("since",String(u.getTime())),r("until",String(Re(p,1).getTime()-1))}break}case"duration (s)":{if(!l)break;let a=Number(l);if(isNaN(a)||a<0)throw new Error("duration needs a number of seconds.");r("minDurationMs",String(Math.round(a*1e3)));break}default:throw new Error('There is no field called "'+s.field+'".')}}return i}function ba(e){let n=[],i=e||{};return i.trigger&&n.push({kind:"cond",field:"trigger",op:"is",value:i.trigger}),i.workflow&&n.push({kind:"cond",field:"workflow",op:"is",value:i.workflow}),i.status&&n.push({kind:"cond",field:"status",op:"is",value:i.status}),i.kind&&n.push({kind:"cond",field:"kind",op:"is",value:i.kind}),i.name&&n.push({kind:"cond",field:"name",op:"contains",value:i.name}),{kind:"group",conj:"and",items:n}}function Jd(e,n,i=vn){let r=[];for(let o of Object.keys(e).sort())r.push(I(o)+"="+I(e[o]));return r.push("limit="+i),n&&r.push("offset="+n),r.join("&")}function ya(e){return e.status==="running"&&e.kind==="workflow"}function ka({params:e,query:n}){let[i,r]=m([]),[o,s]=m([]),[l,a]=m(()=>ba(n)),[c,u]=m(null),[p,d]=m(null),[f,b]=m(0),[g,v]=m(0),y=e[0]||"",T=ae(null),w=$d(i,o),x=JSON.stringify(n||{});j(()=>{a(ba(n)),v(0)},[x]),j(()=>{S("GET","/admin/triggers").then(h=>r(h&&h.triggers||[])).catch(()=>r([])),S("GET","/admin/workflow-defs").then(h=>s(h&&h.workflows||[])).catch(()=>s([]))},[]);let R=null,C=null;try{R=zd(l)}catch(h){C=h.message}let _=R?Jd(R,g):null;function N(){_&&Ii("GET","/executions?"+_).then(({body:h,headers:L})=>{d(Vi(h));let Q=parseInt(L.get("X-Total-Count")||"",10);b(isNaN(Q)?Vi(h).length:Q),u(null)}).catch(O)}j(()=>{u(C),N()},[_,C]);let k=!!p&&p.some(h=>h.status==="running");j(()=>{if(!k)return;let h=setInterval(N,ga);return()=>clearInterval(h)},[k,_]);function E(h){we("Cancel this run",'"'+(h.workflowName||h.workflowId)+'" stops where it is. Steps already done are not undone; the record says cancelled.',()=>{S("POST","/admin/workflow-runs/"+I(h.id)+"/cancel",{}).then(()=>{D("Cancelling \u2014 the record says so when it has stopped.","ok"),setTimeout(N,600)}).catch(O)},"Cancel the run")}function P(h){S("GET","/executions/"+I(h)).then(L=>{ge(Q=>t(Yd,{id:h,data:L||{},close:Q,onCancel:ya(L||{})?()=>E(L):void 0}),()=>{T.current=null,Z("runs")})}).catch(L=>{O(L),T.current=null,Z("runs")})}j(()=>{!p||!y||T.current===y||(T.current=y,P(y))},[p,y]);let U=h=>(i.find(L=>L.id===h)||{name:""}).name||h,X=f?g+1:0,H=Math.min(g+vn,f),q=l.items.length>0;return t(ce,{title:"Runs",subtitle:"Every function, trigger, workflow and backup run this backend has recorded.",children:[t("div",{id:"runs-filter",children:t(gt,{group:l,fields:w,flat:!0,onChange:h=>{a(h),v(0)}})}),c?t(M,{children:c}):null,t(G,{}),t(V,{children:[k?t(K,{kind:"accent",title:"A run is still going: this list re-reads every "+ga/1e3+" seconds.",children:"live"}):null,t("span",{class:"hint",id:"runs-count",children:[f?"Showing "+X+"\u2013"+H+" of "+f:p?"No runs":"",q&&R&&Object.keys(R).length?" where "+Et(l,w):""]}),t(me,{}),t(A,{tiny:!0,disabled:g===0,onClick:()=>v(Math.max(0,g-vn)),children:"\u2039 Newer"}),t(A,{tiny:!0,disabled:g+vn>=f,onClick:()=>v(g+vn),children:"Older \u203A"}),t(A,{onClick:N,children:"Refresh"})]}),t(G,{}),p?t(ye,{columns:["Started","Kind","Name","Trigger","Status","Duration",""],rows:p,empty:q?t(oe,{children:"No runs match."}):t(oe,{icon:"\u25B6",action:{label:"Open Workflows",onClick:()=>Z("workflows"),write:!1},children:"No runs recorded yet. Fire a trigger or run a workflow and it appears here."}),renderRow:(h,L)=>{let Q=Ki(h),se=h.metadata&&typeof h.metadata.triggerId=="string"?h.metadata.triggerId:"";return t("tr",{class:h.id===y?"hit":"",children:[t("td",{children:he(h.startedAt)}),t("td",{children:t("span",{class:"hint",children:ve(h.kind)})}),t("td",{children:ve(h.workflowName||h.workflowId||h.functionName)}),t("td",{children:se?t("a",{href:ne("triggers",se),title:"Open this trigger",children:U(se)}):ve(h.triggerType)}),t("td",{children:t(K,{kind:cn(Q),children:Q})}),t("td",{children:h.durationMs!==void 0&&h.durationMs!==null?h.durationMs+" ms":"\u2014"}),t("td",{class:"actions",children:[ya(h)?t(B,{tiny:!0,kind:"danger",onClick:()=>E(h),children:"Cancel"}):null,t(A,{tiny:!0,onClick:()=>Z("runs",h.id),children:"Detail"})]})]},h.id||L)}}):null]})}function Yd({id:e,data:n,close:i,onCancel:r}){let o=qo(n);return t(ke,{title:"Execution "+e,wide:!0,autoFocus:!1,actions:t(W,{children:[r?t(B,{kind:"danger",onClick:r,children:"Cancel the run"}):null,t(A,{onClick:i,children:"Close"})]}),children:[t(Xd,{summary:o,word:Ki(n)}),t(Zd,{summary:o}),t(Qd,{steps:n.steps||[]}),t(He,{label:"Raw JSON",children:t("textarea",{value:JSON.stringify(n,null,2),readOnly:!0,style:"min-height:320px"})})]})}function Xd({summary:e,word:n}){let i=(r,o)=>o===""||o===null||o===void 0?null:t("span",{class:"rec-fact",children:[r+" ",t("b",{children:String(o)})]});return t("div",{class:"rec-band",children:[t(K,{kind:cn(n),children:n||"unknown"}),i("ran",e.workflow),i("triggered by",e.triggerSource||e.trigger),i("started",e.startedAt?he(e.startedAt):""),i("took",e.durationMs===void 0||e.durationMs===null?"":e.durationMs+" ms"),i("steps",e.stepCount),e.capped?t(K,{kind:"warn",children:"record capped"}):null,e.costLine?t("div",{class:"rec-cost",children:e.costLine}):null]})}var Pr=5;function Zd({summary:e}){if(!e.failures.length)return e.errorMessage?t("div",{class:"rec-danger",children:[t("div",{class:"rec-danger-head",children:"Why it failed"}),t("div",{class:"rec-danger-why",children:e.errorMessage})]}):null;let n=e.failures.length;return t("div",{class:"rec-danger",children:[t("div",{class:"rec-danger-head",children:n===1?"1 step failed":n+" steps failed"}),e.failures.slice(0,Pr).map(i=>t(W,{children:[t("div",{class:"rec-danger-step",children:i.step+(i.type?" \xB7 "+i.type:"")}),i.message?t("div",{class:"rec-danger-why",children:i.message}):null,i.detail?Object.keys(i.detail).map(r=>{let o=i.detail[r];return o===null||typeof o=="object"?null:t("div",{class:"rec-danger-why",children:r+": "+String(o)})}):null]})),n>Pr?t("div",{class:"rec-danger-why",children:"and "+(n-Pr)+" more \u2014 every step is in the table below."}):null]})}function Qd({steps:e}){return e.length?t("div",{class:"scroller",children:t("table",{children:[t("thead",{children:t("tr",{children:["","Step","Type","Duration","Status"].map((n,i)=>t("th",{children:n},i))})}),t("tbody",{children:e.map((n,i)=>t(eu,{step:n,index:i},i))})]})}):t("div",{class:"empty",children:"This run recorded no steps."})}function eu({step:e,index:n}){let[i,r]=m(!1);return t(W,{children:[t("tr",{class:"step-row"+(i?" open":""),onClick:()=>r(!i),children:[t("td",{children:String(n+1)}),t("td",{children:ve(e.nodeName||e.nodeId)}),t("td",{children:ve(e.nodeType)}),t("td",{children:e.durationMs===void 0||e.durationMs===null?"\u2014":e.durationMs+" ms"}),t("td",{children:t(K,{kind:Go(e.status),children:ve(e.status)})})]}),t("tr",{class:"step-pane"+(i?"":" hidden"),children:t("td",{colSpan:5,children:t(tu,{step:e})})})]})}function tu({step:e}){let n=(s,l)=>t("div",{class:"pane-part",children:[t("h4",{children:s}),t("div",{class:"jx",children:t(an,{value:l,name:null,depth:0,openTo:2})})]}),i=e.inputData!==void 0&&e.inputData!==null,r=e.outputData!==void 0&&e.outputData!==null,o=!e.errorMessage&&e.status!=="skipped"&&!i&&!r;return t("div",{children:[e.errorMessage?t("div",{class:"pane-part",children:[t("h4",{children:"Error"}),t("div",{class:"rec-danger-why",children:e.errorMessage})]}):null,e.status==="skipped"?t("div",{class:"pane-part",children:[t("h4",{children:"Skipped"}),t("div",{class:"jx-meta",children:"This step was not reached \u2014 the run took another path."})]}):null,i?n("Input",e.inputData):null,r?n("Output",e.outputData):null,o?t("div",{class:"jx-meta",children:"This step recorded no data."}):null]})}var wn=[{id:"images",label:"Images",line:"PNG, JPEG, GIF, WebP, BMP, icons and SVG",types:["image/png","image/jpeg","image/gif","image/webp","image/bmp","image/x-icon","image/svg+xml"]},{id:"documents",label:"Documents and data",line:"PDF, plain text, CSV and JSON",types:["application/pdf","text/plain","text/csv","application/json"]},{id:"audio",label:"Audio",line:"MP3",types:["audio/mpeg"]},{id:"archives",label:"Archives",line:"ZIP",types:["application/zip"]},{id:"web",label:"Web pages and scripts",line:"HTML, CSS and JavaScript \u2014 a page a browser would run",types:["text/html","text/css","text/javascript"]},{id:"unknown",label:"Executables and anything unrecognised",line:"Programs, installers and every other binary the backend cannot identify",types:["application/octet-stream"]}],nu=wn.reduce((e,n)=>e.concat(n.types),[]);function va(e){let n=[];for(let i of wn)if(e.categories.indexOf(i.id)!==-1)for(let r of i.types)n.indexOf(r)===-1&&n.push(r);for(let i of e.custom){let r=i.trim().toLowerCase();r&&n.indexOf(r)===-1&&n.push(r)}return n}function wa(e){let n=new Set(e.map(s=>s.trim().toLowerCase()).filter(Boolean)),i=[],r=new Set;for(let s of wn)if(s.types.every(l=>n.has(l))){i.push(s.id);for(let l of s.types)r.add(l)}let o=[];return n.forEach(s=>{r.has(s)||o.push(s)}),{categories:i,custom:o}}function Ca(e){let n=e.trim().toLowerCase();return n?/^[a-z0-9!#$&^_.+-]+\\/[a-z0-9!#$&^_.+-]+$/.test(n)?null:"A content type looks like image/png or application/pdf.":"Type a content type."}function Sa(e){return nu.indexOf(e.trim().toLowerCase())!==-1}function li(e,n,i){return e+" "+(e===1?n:i)}function iu(e){let n=(e.tooNew||[]).length;return"Done: "+li(e.orphanBlobs.length,"file","files")+" no record knows about, "+li(e.orphanRows.length,"record","records")+" whose file is missing"+(e.deleted&&e.orphanBlobs.length?" \u2014 the unknown files were deleted":"")+(n?"; "+li(n,"file is","files are")+" too new to judge \u2014 under "+(e.graceMinutes||5)+" minutes old and perhaps still arriving \u2014 so "+(n===1?"it was":"they were")+" left alone":"")+"."}var ai=50;function Ta(e){try{let n=new URL(e,location.href);return n.pathname+n.search}catch{return e}}function xa(e){return/^image\\//.test(e)}var ru=e=>S("POST","/admin/triggers/preview",{cron:e,count:5});function Ea(e){let[n,i]=m(null),[r,o]=m(0);function s(){S("GET","/admin/files/config").then(c=>{i(c),o(u=>u+1)}).catch(O)}j(()=>{s()},[]);let l=n&&n.config||{},a=l.thumbnails&&l.thumbnails.presets||{};return t(ce,{title:"Storage",subtitle:"The files your app has stored, which kinds are allowed, how they are thumbnailed, and the clean-up.",children:n?t("div",{children:[t(V,{children:[t(K,{kind:"accent",children:"stored "+(n.driverKind==="s3"?"in S3":"on this machine")}),n.transformsAvailable?t(K,{kind:"ok",children:"thumbnails available"}):t(K,{kind:"warn",title:ve(n.transformUnavailableReason),children:"thumbnails unavailable on this backend"})]}),t(G,{}),t(ou,{presets:a,transforms:!!n.transformsAvailable}),t("h2",{children:"Where files are stored"}),t(lu,{config:l,credentialsConfigured:!!n.s3CredentialsConfigured,reload:s},"where"+r),n.driverKind==="s3"?t(du,{},"move"+r):null,t("h2",{children:"Settings"}),t(uu,{config:l,reload:s},"limits"+r),t("h2",{children:"Thumbnail presets"}),t(hu,{presets:a,reload:s},"presets"+r),t("h2",{children:"Clean-up"}),t(gu,{config:l,reload:s},"sweep"+r)]}):null})}function ou({presets:e,transforms:n}){let{readonly:i}=te(),[r,o]=m(null),[s,l]=m(0),[a,c]=m(0),[u,p]=m(""),[d,f]=m({}),[b,g]=m({}),[v,y]=m(!1),[T,w]=m(!1),[x,R]=m(null),C=ae(null),_=e.sm?"sm":Object.keys(e)[0]||"";function N(){S("GET","/admin/files?limit="+ai+"&offset="+a+(u?"&q="+I(u):"")).then(H=>{o(H.files),l(H.count);let q=H.files.map(h=>h.name);q.length&&S("GET","/admin/files/uses?names="+I(q.join(","))).then(h=>f(L=>({...L,...h.uses}))).catch(O);for(let h of H.files)!xa(h.contentType)||b[h.name]||S("GET","/files/"+I(h.name)+"/sign").then(L=>g(Q=>({...Q,[h.name]:Ta(L.url)}))).catch(()=>{})}).catch(O)}j(N,[a,u]);let k=async H=>{y(!0),R(null);try{let q={...ut(_e.get().credential),"content-type":H.type||"application/octet-stream"},h=await fetch("/files/"+I(H.name),{method:"POST",headers:q,body:H}),L=await h.json().catch(()=>null);if(!h.ok)throw new Error(L&&(L.error||L.message)||"HTTP "+h.status);D("Uploaded "+H.name+".","ok"),c(0),N()}catch(q){R(q.message)}finally{y(!1)}},E=H=>{if(H)for(let q=0;q<H.length;q++)k(H[q])};function P(H,q){S("DELETE","/admin/files/"+I(H.name)+(q?"?clear=1":"")).then(()=>{D("Deleted "+H.originalName+(q?" and cleared its fields.":"."),"ok"),N()}).catch(h=>{if(h instanceof Ft&&h.status===409){let L=d[H.name]||[];ge(Q=>t(ke,{title:'"'+H.originalName+'" is in use',autoFocus:!1,actions:t(W,{children:[t(A,{onClick:Q,children:"Keep it"}),t(B,{kind:"danger",onClick:()=>{Q(),P(H,!0)},children:"Delete anyway and clear "+L.length+" field"+(L.length===1?"":"s")})]}),children:[t("div",{class:"notice warn",children:h.message}),t("ul",{class:"uses-list",children:L.map(se=>t("li",{children:[t("a",{href:ne("collections",se.collection,se.objectId),children:se.collection+" \xB7 "+se.objectId}),t("span",{class:"hint",children:" in "+se.field})]},se.collection+se.objectId+se.field))})]}));return}O(h)})}let U=s?a+1:0,X=Math.min(a+ai,s);return t(pe,{children:[t("div",{id:"file-dropzone",class:"dropzone"+(T?" over":""),role:"button",tabIndex:0,"aria-label":"Upload files",onClick:()=>!i&&C.current&&C.current.click(),onKeyDown:H=>{(H.key==="Enter"||H.key===" ")&&C.current&&(H.preventDefault(),C.current.click())},onDragOver:H=>{H.preventDefault(),w(!0)},onDragLeave:()=>w(!1),onDrop:H=>{H.preventDefault(),w(!1),i||E(H.dataTransfer?H.dataTransfer.files:null)},children:[t("input",{ref:C,type:"file",multiple:!0,hidden:!0,disabled:i,onChange:H=>E(H.currentTarget.files)}),v?"Uploading\u2026":i?"Uploads need full backend access.":"Drop files here, or click to choose",x?t("span",{class:"chips-problem",children:x}):null]}),t(G,{}),t(V,{children:[t("input",{type:"search",id:"files-search",placeholder:"Find by name",value:u,onInput:H=>{p(H.currentTarget.value),c(0)},style:"flex:1 1 200px"}),t("span",{class:"hint",id:"files-count",children:s?"Showing "+U+"\u2013"+X+" of "+s:r?"No files":""}),t(A,{tiny:!0,disabled:a===0,onClick:()=>c(Math.max(0,a-ai)),children:"\u2039 Newer"}),t(A,{tiny:!0,disabled:X>=s,onClick:()=>c(a+ai),children:"Older \u203A"})]}),t(G,{}),r?t(ye,{class:"files",columns:["","File","Size","Kind","Uploaded","Used by",""],rows:r,renderRow:H=>{let q=d[H.name],h=b[H.name];return t("tr",{"data-file":H.name,children:[t("td",{class:"thumb-cell",children:xa(H.contentType)&&h&&n&&_?t("img",{class:"file-thumb",src:h+"&thumb="+I(_),alt:"",onError:L=>L.currentTarget.style.display="none"}):t("span",{class:"file-icon","aria-hidden":"true",children:"\u25A4"})}),t("td",{children:[t("b",{children:H.originalName||rt(H.name)}),H.private?t(K,{kind:"warn",children:"private"}):null]}),t("td",{children:mt(H.size)}),t("td",{children:t("span",{class:"chip type",children:H.contentType})}),t("td",{children:he(H.createdAt)}),t("td",{class:"uses-cell",children:[q===void 0?t("span",{class:"hint",children:"\u2026"}):q.length===0?t("span",{class:"hint",children:"nothing"}):q.slice(0,3).map((L,Q)=>t("span",{children:[Q?", ":"",t("a",{href:ne("collections",L.collection,L.objectId),title:L.field,children:L.collection+" \xB7 "+L.objectId.slice(0,8)})]},Q)),q&&q.length>3?t("span",{class:"hint",children:" +"+(q.length-3)+" more"}):null]}),t(Ye,{children:[t("a",{class:"btn tiny",href:h||"/files/"+I(H.name),download:H.originalName||rt(H.name),target:"_blank",rel:"noopener",onClick:L=>{h||(L.preventDefault(),S("GET","/files/"+I(H.name)+"/sign").then(Q=>{let se=document.createElement("a");se.href=Ta(Q.url),se.download=H.originalName||rt(H.name),se.rel="noopener",document.body.appendChild(se),se.click(),se.remove()}).catch(O))},children:"Download"}),t(B,{tiny:!0,kind:"danger",onClick:()=>we("Delete "+(H.originalName||rt(H.name))+"?",q&&q.length?"It is used by "+q.length+" record"+(q.length===1?"":"s")+"; the delete will ask before clearing them.":"No record points at it. The file is gone for good.",()=>P(H,!1)),children:"Delete"})]})]},H.name)},empty:u?t(oe,{children:"No file is called that."}):t(oe,{children:"Nothing stored yet. Drop a file above, or let your app upload one."})}):null]})}function su(e){let n=e.driver||{};return{where:n.type==="s3"?"s3":"local",endpoint:n.endpoint||"",region:n.region||"us-east-1",bucket:n.bucket||"",pathStyle:n.forcePathStyle!==!1,accessKeyId:"",secretAccessKey:""}}function au(e,n){if(e.where!=="s3")return null;if(!e.endpoint.trim())return"The bucket needs an endpoint (the address of the S3-compatible service).";if(!/^https?:\\/\\//.test(e.endpoint.trim()))return"The endpoint starts with https:// (or http:// for a service on this network).";if(!e.bucket.trim())return"The bucket needs a name.";let i=!!e.accessKeyId||!!e.secretAccessKey;return i&&(!e.accessKeyId||!e.secretAccessKey)?"Type both the access key id and the secret access key.":!i&&!n?"The bucket needs an access key id and a secret access key.":null}function Ra(e){if(e.where!=="s3")return{driver:{type:"local"}};let n={driver:{type:"s3",endpoint:e.endpoint.trim(),region:e.region.trim()||"us-east-1",bucket:e.bucket.trim(),forcePathStyle:e.pathStyle}};return e.accessKeyId&&e.secretAccessKey&&(n.s3Credentials={accessKeyId:e.accessKeyId,secretAccessKey:e.secretAccessKey}),n}function lu({config:e,credentialsConfigured:n,reload:i}){let{readonly:r}=te(),[o,s]=m(su(e)),[l,a]=m(null),[c,u]=m(!1),p=au(o,n),d=y=>{s(T=>({...T,...y})),a(null)},f=o.where==="local"||!!l&&l.ok;function b(){if(p){O(new Error(p));return}u(!0),S("POST","/admin/files/config/test",Ra(o)).then(y=>a({ok:!!y.ok,words:y.words})).catch(y=>a({ok:!1,words:y.message})).then(()=>u(!1))}function g(){if(p){O(new Error(p));return}S("PUT","/admin/files/config",Ra(o)).then(()=>{D(o.where==="s3"?'New uploads go to the bucket "'+o.bucket.trim()+'".':"New uploads are stored on this machine.","ok"),i()}).catch(O)}let v=(y,T,w)=>t("label",{class:"tile"+(o.where===y?" on":""),children:[t("input",{type:"radio",name:"where",value:y,checked:o.where===y,disabled:r,onChange:()=>d({where:y})}),t("b",{children:T}),t("span",{class:"sub",children:w})]});return t(pe,{id:"where-card",children:[t("div",{class:"tiles",role:"radiogroup","aria-label":"Where files are stored",children:[v("local","This machine","A folder beside the backend's data. Fine on a laptop; it fills a small server's disk."),v("s3","An S3-compatible bucket","AWS S3, MinIO, Backblaze, Cloudflare R2, Hetzner\u2026 Uploads and backup archives go there instead of the disk.")]}),o.where==="s3"?t("div",{id:"bucket-form",children:[t(G,{h:8}),t("div",{class:"grid2",children:[t($,{label:"Endpoint",children:t("input",{type:"url",id:"s3-endpoint",placeholder:"https://s3.example.com",value:o.endpoint,disabled:r,onInput:y=>d({endpoint:y.currentTarget.value})})}),t($,{label:"Region",children:t("input",{type:"text",id:"s3-region",placeholder:"us-east-1",value:o.region,disabled:r,onInput:y=>d({region:y.currentTarget.value})})}),t($,{label:"Bucket",children:t("input",{type:"text",id:"s3-bucket",placeholder:"my-app-files",value:o.bucket,disabled:r,onInput:y=>d({bucket:y.currentTarget.value})})})]}),t(re,{id:"s3-path-style",checked:o.pathStyle,onChange:y=>d({pathStyle:y}),disabled:r,children:"Path-style addressing"}),t(fe,{children:"On for MinIO and most self-hosted services (the bucket is in the path). Off for AWS S3's own style (the bucket is in the hostname)."}),t(G,{h:8}),t("div",{class:"field-head",children:[t("b",{children:"Key"}),n?t(K,{kind:"ok",children:"key: configured"}):t(K,{kind:"warn",children:"key: not configured"}),t("span",{class:"hint",children:n?"Leave both blank to keep the stored key. It is never shown.":"From your storage provider. Stored on this backend, never shown again."})]}),t("div",{class:"grid2",children:[t($,{label:"Access key id",children:t("input",{type:"text",id:"s3-access-key-id",autocomplete:"off",value:o.accessKeyId,disabled:r,onInput:y=>d({accessKeyId:y.currentTarget.value})})}),t($,{label:"Secret access key",children:t("input",{type:"password",id:"s3-secret",autocomplete:"new-password",value:o.secretAccessKey,disabled:r,onInput:y=>d({secretAccessKey:y.currentTarget.value})})})]}),l?t("div",{id:"s3-test-result",class:"notice "+(l.ok?"ok":"bad"),children:l.words}):null]}):null,t(fe,{children:"Files already stored on this machine stay there until you move them; the backend keeps serving them from here. Switching changes where NEW uploads and backups go."}),t(V,{style:"margin-top:12px",children:[o.where==="s3"?t(B,{tiny:!0,id:"s3-test",disabled:c||!!p,title:p||void 0,onClick:b,children:c?"Testing\u2026":"Test connection"}):null,t(B,{tiny:!0,kind:"primary",id:"save-where",disabled:!f||!!p,title:f?p||void 0:"Test the connection first.",onClick:g,children:"Save"})]})]})}function cu(e){let n=e.progress,i=o=>o===1?"1 file":o+" files";if(n.state==="running")return"Moving "+(n.moved+n.failed.length+1>n.total?n.total:n.moved+n.failed.length+1)+" of "+n.total+"\u2026 "+mt(n.bytes)+" so far. The app keeps working while they move.";let r=n.state==="done"?i(n.moved)+" moved to the bucket"+(n.failed.length?", "+i(n.failed.length)+" left where "+(n.failed.length===1?"it was":"they were"):"")+". ":"";return e.onThisMachine?r+(e.onThisMachine===1?"1 file is":e.onThisMachine+" files are")+" still on this machine. They keep serving from here; new uploads go to the bucket.":r+"Every file is in the bucket."}function du(){let[e,n]=m(null),i=ae(null);function r(){S("GET","/admin/files/move").then(c=>{n(c),c.progress.state==="running"?i.current=window.setTimeout(r,1e3):i.current!==null&&(i.current=null)}).catch(O)}j(()=>(r(),()=>{i.current!==null&&window.clearTimeout(i.current)}),[]);function o(){S("POST","/admin/files/move",{}).then(c=>{n(c),i.current=window.setTimeout(r,1e3)}).catch(O)}if(!e)return null;let s=e.progress,l=s.state==="running",a=e.onThisMachine;return t(pe,{id:"move-card",children:[t("div",{id:"move-words",children:cu(e)}),l?t("progress",{id:"move-progress",style:"width:100%",max:s.total||1,value:s.moved+s.failed.length}):null,s.failed.length?t("ul",{id:"move-failed",class:"sub",children:s.failed.map(c=>t("li",{children:[c.name?t("b",{children:c.name}):null," ",c.error]},c.name))}):null,a>0&&!l?t(V,{style:"margin-top:12px",children:t(B,{tiny:!0,kind:"primary",id:"move-start",onClick:()=>we("Move "+(a===1?"1 file":a+" files")+" to the bucket?","Each file is copied into the bucket, checked, and only then removed from this machine \u2014 a file that cannot be moved stays where it is and keeps working.",o,"Move"),children:"Move them to the bucket"})}):null]})}function uu({config:e,reload:n}){let{readonly:i}=te(),[r,o]=m(String(Math.round((e.maxUploadBytes||0)/(1024*1024)))),[s,l]=m(String(e.signedUrlTtlSeconds||300)),[a,c]=m(wa(e.contentTypes&&e.contentTypes.denyList||[])),u=(d,f)=>c(b=>({...b,categories:f?b.categories.concat(b.categories.indexOf(d)===-1?[d]:[]):b.categories.filter(g=>g!==d)}));function p(){S("PUT","/admin/files/config",{maxUploadBytes:Math.max(1,Number(r)||1)*1024*1024,contentTypes:{denyList:va(a)},signedUrlTtlSeconds:Math.max(1,Number(s)||300)}).then(()=>{D("Storage settings saved.","ok"),n()}).catch(O)}return t(pe,{children:[t("div",{class:"grid2",children:[t($,{label:"Largest upload (MB)",children:t("input",{type:"number",min:"1",id:"max-mb",value:r,disabled:i,onInput:d=>o(d.currentTarget.value)})}),t($,{label:"A private file's link stays open for (seconds)",children:t("input",{type:"number",min:"1",id:"signed-ttl",value:s,disabled:i,onInput:d=>l(d.currentTarget.value)})})]}),t(G,{}),t("div",{class:"field-head",children:[t("b",{children:"Refuse these kinds"}),t("span",{class:"hint",children:"An upload is judged by its bytes, never by the name or the type the app claims."})]}),t("div",{class:"kind-boxes",id:"kind-boxes",children:wn.map(d=>t("label",{class:"check kind-box",children:[t("input",{type:"checkbox",value:d.id,checked:a.categories.indexOf(d.id)!==-1,disabled:i,onChange:f=>u(d.id,f.currentTarget.checked)}),t("span",{children:[t("b",{children:d.label}),t("span",{class:"sub",children:d.line})]})]},d.id))}),t(G,{h:8}),t("div",{class:"field-head",children:[t("b",{children:"Also refuse these types"}),t("span",{class:"hint",children:"One the backend can identify is refused; one it cannot is listed here and matches nothing."})]}),t("div",{id:"custom-types",children:t(Ie,{items:a.custom,onChange:d=>c(f=>({...f,custom:d})),validate:Ca,normalise:d=>d.trim().toLowerCase(),label:d=>Sa(d)?d:d+" (never identified)",placeholder:"application/x-msdownload",addLabel:"Add type",disabled:i})}),t(V,{style:"margin-top:12px",children:t(B,{tiny:!0,kind:"primary",id:"save-limits",onClick:p,children:"Save settings"})})]})}function pu(e){return Object.keys(e).map(n=>({name:n,width:String(e[n].width),height:String(e[n].height),fit:e[n].fit||"cover"}))}function mu(e){let n=new Set;for(let i of e){let r=i.name.trim();if(!r)return"Every preset needs a name.";if(!/^[a-zA-Z0-9_-]+$/.test(r))return'A preset name is letters, digits, "_" or "-" (it goes in a URL: ?thumb='+r+").";if(n.has(r))return'Two presets are called "'+r+'".';n.add(r);let o=Number(i.width),s=Number(i.height);if(!(o>0)||!(s>0)||!Number.isInteger(o)||!Number.isInteger(s))return'"'+r+'" needs a whole width and height in pixels.'}return null}function fu(e){let n={};for(let i of e)n[i.name.trim()]={width:Number(i.width),height:Number(i.height),fit:i.fit==="contain"?"contain":"cover"};return n}function hu({presets:e,reload:n}){let{readonly:i}=te(),[r,o]=m(pu(e)),s=mu(r);function l(){if(s){O(new Error(s));return}S("PUT","/admin/files/config",{thumbnails:{presets:fu(r)}}).then(()=>{D("Thumbnail presets saved.","ok"),n()}).catch(O)}return t(pe,{children:[t(fe,{children:"A named preset is public: any app may ask for ?thumb=name. Sizes that are not a preset are admin-only."}),t(G,{h:8}),t(Xe,{id:"presets",rows:r,onChange:o,blank:()=>({name:"",width:"128",height:"128",fit:"cover"}),addLabel:"Add preset",disabled:i,head:t("div",{class:"list-row list-head",children:[t("span",{style:"flex:1 1 140px",children:"Name"}),t("span",{style:"width:80px",children:"Width"}),t("span",{style:"width:80px",children:"Height"}),t("span",{style:"width:110px",children:"Fit"})]}),renderRow:(a,c)=>t(W,{children:[t("input",{type:"text",value:a.name,placeholder:"name","aria-label":"Preset name",disabled:i,onInput:u=>c({...a,name:u.currentTarget.value})}),t("input",{type:"number",min:"1",style:"width:80px",value:a.width,"aria-label":"Width",disabled:i,onInput:u=>c({...a,width:u.currentTarget.value})}),t("input",{type:"number",min:"1",style:"width:80px",value:a.height,"aria-label":"Height",disabled:i,onInput:u=>c({...a,height:u.currentTarget.value})}),t("select",{style:"width:110px",value:a.fit,"aria-label":"Fit",disabled:i,onChange:u=>c({...a,fit:u.currentTarget.value}),children:[t("option",{value:"cover",children:"crop to fill"}),t("option",{value:"contain",children:"fit inside"})]})]})}),s?t(M,{children:s}):null,t(V,{style:"margin-top:12px",children:t(B,{tiny:!0,kind:"primary",id:"save-presets",disabled:!!s,onClick:l,children:"Save presets"})})]})}function gu({config:e,reload:n}){let{readonly:i}=te(),r=e.orphanSweep||{enabled:!1,cron:"0 3 * * *"},[o,s]=m(!!r.enabled),[l,a]=m(ve(r.cron)||"0 3 * * *"),u=(e.sweepStatus||{}).lastReport;function p(){S("PUT","/admin/files/config",{orphanSweep:{enabled:o,cron:l.trim()}}).then(()=>{D(o?"Clean-up scheduled.":"Clean-up schedule turned off.","ok"),n()}).catch(O)}function d(f){D("Looking for orphans\u2026"),S("POST","/admin/files/sweep",{deleteOrphans:f}).then(b=>{let g=b.report;D(iu(g),"ok"),n()}).catch(O)}return t(pe,{children:[t(V,{children:[u?t(K,{kind:u.error?"bad":u.orphanBlobs.length||u.orphanRows.length?"warn":"ok",children:"last checked "+he(u.at)+" \u2014 "+li(u.orphanBlobs.length,"unknown file","unknown files")+", "+u.orphanRows.length+" missing"+((u.tooNew||[]).length?", "+(u.tooNew||[]).length+" too new to judge":"")}):t(K,{children:"never run"}),t(me,{}),t(B,{tiny:!0,onClick:()=>d(!1),children:"Check now"}),t(B,{tiny:!0,kind:"danger",onClick:()=>we("Delete unknown files?","Files no record points at are deleted for good. Records whose file is missing are only reported.",()=>d(!0),"Check and delete"),children:"Check now and delete unknown files"})]}),t(G,{}),t(re,{id:"sweep-enabled",checked:o,onChange:s,disabled:i,children:"Check on a schedule"}),t(fe,{children:"A scheduled check only reports \u2014 it never deletes. Records whose file went missing are a data problem for a person, never cleaned up on their own."}),o?t("div",{class:"when-body",id:"sweep-schedule",children:t(_t,{id:"sweep-cron",value:l,onChange:a,preview:ru,disabled:i})}):null,t(V,{style:"margin-top:12px",children:t(B,{tiny:!0,kind:"primary",id:"save-sweep",onClick:p,children:"Save schedule"})})]})}var Zt=[{id:"gmail",label:"Gmail / Google Workspace",line:"A Google account sends the mail",host:"smtp.gmail.com",port:587,secure:!1,usernameHint:"Your full Gmail or Workspace address",passwordHint:"An App password, not your Google password \u2014 Google refuses the real one here",credentialUrl:"https://myaccount.google.com/apppasswords",credentialLabel:"Make an App password"},{id:"resend",label:"Resend",line:"Transactional email, an API key signs in",host:"smtp.resend.com",port:465,secure:!0,usernameHint:"The word resend",passwordHint:"An API key from Resend",credentialUrl:"https://resend.com/api-keys",credentialLabel:"Make an API key"},{id:"postmark",label:"Postmark",line:"The Server API token is both name and password",host:"smtp.postmarkapp.com",port:587,secure:!1,usernameHint:"Your Server API token",passwordHint:"The same Server API token again",credentialUrl:"https://account.postmarkapp.com/servers",credentialLabel:"Find the Server API token"},{id:"ses",label:"Amazon SES",line:"Pick the region your SES lives in",host:"email-smtp.{region}.amazonaws.com",port:587,secure:!1,usernameHint:"The SMTP user name from SES (not an IAM access key)",passwordHint:"The SMTP password SES made with it",credentialUrl:"https://console.aws.amazon.com/ses/home#/smtp",credentialLabel:"Make SMTP credentials"},{id:"mailgun",label:"Mailgun",line:"One sending domain, a postmaster login",host:"smtp.mailgun.org",port:587,secure:!1,usernameHint:"postmaster@ your sending domain",passwordHint:"The SMTP password for that domain",credentialUrl:"https://app.mailgun.com/mailgun-cp/sending/domains",credentialLabel:"Open your sending domains"},{id:"brevo",label:"Brevo",line:"Formerly Sendinblue",host:"smtp-relay.brevo.com",port:587,secure:!1,usernameHint:"The email address you log in to Brevo with",passwordHint:"An SMTP key, not your Brevo password",credentialUrl:"https://app.brevo.com/settings/keys/smtp",credentialLabel:"Make an SMTP key"},{id:"other",label:"Other",line:"Any SMTP server \u2014 type its host and port",host:"",port:587,secure:!1,usernameHint:"Whatever your provider gave you",passwordHint:"Whatever your provider gave you",credentialUrl:"",credentialLabel:""}],di=["us-east-1","us-east-2","us-west-2","eu-west-1","eu-west-2","eu-west-3","eu-central-1","eu-north-1","ap-south-1","ap-southeast-1","ap-southeast-2","ap-northeast-1","ap-northeast-2","ca-central-1","sa-east-1"];function ci(e){return Zt.find(n=>n.id===e)||Zt[Zt.length-1]}function bu(e,n){return e.host.replace("{region}",n||di[0])}var yu=/^email-smtp\\.([a-z0-9-]+)\\.amazonaws\\.com$/;function _a(e){let n=(e||"").trim().toLowerCase(),i=yu.exec(n);return i?{preset:ci("ses"),region:i[1]}:{preset:Zt.find(o=>o.host&&o.host.toLowerCase()===n)||ci("other")}}function Aa(e,n){return e.id==="other"?{host:"",port:"",secure:!1}:{host:bu(e,n),port:String(e.port),secure:e.secure}}var Ir={passwordReset:{title:"Password reset",line:"Sent when someone asks to reset their password."},verifyEmail:{title:"Verify your email",line:"Sent on sign-up when verification is on, and when someone asks again."},magicLink:{title:"Sign-in link",line:"Sent when someone signs in with a link instead of a password."},emailCode:{title:"Sign-in code",line:"Sent when someone signs in with a six-digit code instead of a password."}};function ku(e){return(Ir[e]||{title:e}).title}function Pa(e){return"{{"+e+"}}"}function vu(e,n,i,r){let o=Math.max(0,Math.min(n,e.length)),s=Math.max(o,Math.min(i,e.length));return{value:e.slice(0,o)+r+e.slice(s),caret:o+r.length}}var Ia="nodegx.admin.email.testTo";function La(){try{return localStorage.getItem(Ia)||""}catch{return""}}function Na(e){try{localStorage.setItem(Ia,e)}catch{}}function Da({params:e}){let[n,i]=m(null),[r,o]=m(null),[s,l]=m(!1),[a,c]=m(0),u=e[0]||null;function p(){return l(!1),S("GET","/admin/email/templates").then(b=>o(b.templates||[])).catch(()=>l(!0))}function d(){S("GET","/admin/email/config").then(b=>{i(b),c(g=>g+1),p()}).catch(O)}j(()=>{d()},[]);let f=u&&r&&r.find(b=>b.id===u)||null;return t(ce,{title:"Email",subtitle:"Which provider sends your mail, what happens when someone signs up, and the three messages your app sends.",children:n?t("div",{children:[n.configured?t(J,{children:"Email is configured and enabled."}):t(J,{kind:"warn",children:"Email is NOT configured: "+(n.notConfiguredReason||"unknown reason")+". Sends fail loudly into execution records rather than queueing."}),t(wu,{data:n,reload:d},a),t("h2",{children:"Templates"}),t(fe,{style:"margin:0 0 8px",children:"Each message is plain text with names in double braces the backend fills in when it sends. Open one to change its words and see it rendered."}),t("div",{children:[s?t(J,{children:"Templates are unavailable on this backend."}):null,r&&!r.length?t(oe,{children:"No templates."}):null,(r||[]).map(b=>t(pe,{class:"template-card",children:[t(V,{children:[t(vt,{children:ku(b.id)}),b.isOverridden?t(K,{kind:"accent",children:"edited"}):t(K,{children:"default"}),t(me,{}),t(A,{tiny:!0,onClick:()=>Z("email",b.id),children:"Edit"})]}),t(fe,{style:"margin:6px 0 0",children:(Ir[b.id]||{line:""}).line}),t(fe,{style:"margin:4px 0 0",children:"Subject: "+ve((b.effective||{}).subject)})]},b.id))]}),f?t(Cu,{template:f,onClose:()=>Z("email"),onChanged:()=>{p()}},f.id):null]}):null})}function wu({data:e,reload:n}){let{readonly:i}=te(),r=e.config||{},o=r.smtp||{},s=_a(ve(o.host)),[l,a]=m(!!r.enabled),[c,u]=m(s.preset.id),[p,d]=m(s.region||di[0]),[f,b]=m(ve(o.host)),[g,v]=m(o.port===void 0?"":String(o.port)),[y,T]=m(!!o.secure),[w,x]=m(ve(o.username)),[R,C]=m(""),[_,N]=m(ve(r.fromAddress)),[k,E]=m(ve(r.fromName)),[P,U]=m(ve(r.baseUrl)),[X,H]=m(!!(r.verification&&r.verification.sendOnSignup)),[q,h]=m(!!(r.verification&&r.verification.requireForLogin)),L=ci(c);function Q(le,ie=p){if(u(le.id),le.id==="other")return;let z=Aa(le,ie);b(z.host),v(z.port),T(z.secure)}function se(){let ie={config:{enabled:l,smtp:{host:f.trim(),port:g?Number(g):void 0,secure:y,username:w.trim()},fromAddress:_.trim(),fromName:k.trim(),baseUrl:P.trim(),verification:{sendOnSignup:X,requireForLogin:q}}};R&&(ie.smtpPassword=R),S("PUT","/admin/email/config",ie).then(()=>{D("Email settings saved.","ok"),n()}).catch(O)}function Me(){ge(le=>t(Tu,{close:le}))}let Ae=le=>ie=>le(ie.currentTarget.value);return t(pe,{children:[t(re,{checked:l,onChange:a,disabled:i,children:"Sending is on"}),t("div",{class:"drawer-section",children:"Who sends it"}),t("div",{class:"tiles providers",role:"radiogroup","aria-label":"Email provider",children:Zt.map(le=>t("label",{class:"tile"+(c===le.id?" on":""),children:[t("input",{type:"radio",name:"smtp-preset",value:le.id,checked:c===le.id,disabled:i,"aria-label":le.label,onChange:()=>Q(le)}),t("b",{children:le.label}),t("span",{class:"sub",children:le.line})]},le.id))}),L.id==="ses"?t("div",{class:"when-body",style:"margin-top:8px",children:t($,{label:"Region",children:t("select",{value:p,disabled:i,"aria-label":"SES region",onChange:le=>{let ie=le.currentTarget.value;d(ie),Q(L,ie)},children:di.map(le=>t("option",{value:le,children:le},le))})})}):null,L.id!=="other"?t(M,{children:t("span",{id:"preset-hint",children:["Username: "+L.usernameHint+". Password: "+L.passwordHint+". ",L.credentialUrl?t("a",{href:L.credentialUrl,target:"_blank",rel:"noreferrer",children:[L.credentialLabel," \u2197"]}):null]})}):null,t("div",{class:"grid2",style:"margin-top:10px",children:[t($,{label:"SMTP host",children:t("input",{type:"text",id:"smtp-host",value:f,disabled:i,onInput:Ae(b)})}),t($,{label:"SMTP port",children:t("input",{type:"number",id:"smtp-port",value:g,disabled:i,onInput:Ae(v)})}),t($,{label:"Username",children:t("input",{type:"text",id:"smtp-username",placeholder:L.id!=="other"?L.usernameHint:"",value:w,disabled:i,onInput:Ae(x)})}),t($,{label:"Password",children:t("input",{type:"password",id:"smtp-password",placeholder:e.hasSmtpPassword?"(unchanged)":L.id!=="other"?L.passwordHint:"(not set)",value:R,disabled:i,onInput:Ae(C)})}),t($,{label:"From address",children:t("input",{type:"text",id:"smtp-from",value:_,disabled:i,onInput:Ae(N)})}),t($,{label:"From name",children:t("input",{type:"text",value:k,disabled:i,onInput:Ae(E)})}),t($,{label:"Base URL (links in emails)",children:t("input",{type:"text",id:"smtp-baseurl",value:P,placeholder:"https://api.example.com",disabled:i,onInput:Ae(U)})})]}),t(V,{style:"margin-top:10px",children:t(Fe,{checked:y,onChange:T,disabled:i,children:"Implicit TLS (port 465)"})}),t("div",{class:"drawer-section",children:"When someone signs up"}),t("div",{class:"when-body",id:"signup-policy",children:[t(re,{checked:X,onChange:H,disabled:i,children:"Send them a verification email"}),t(re,{checked:q,onChange:h,disabled:i,children:"They cannot sign in until they have verified"})]}),t(V,{style:"margin-top:12px",children:[t(B,{tiny:!0,kind:"primary",onClick:se,children:"Save"}),t(B,{tiny:!0,onClick:Me,children:"Send test email"})]})]})}function Cu({template:e,onClose:n,onChanged:i}){let{readonly:r}=te(),o={...e.default||{subject:"",text:"",html:""},...e.effective||{}},[s,l]=m(o.subject||""),[a,c]=m(o.text||""),[u,p]=m(o.html||""),[d,f]=m(null),[b,g]=m(null),[v,y]=m(!1),T=ae("text"),w=ae(null),x=ae(null),R=ae(null),C=ae(null),_=ae(0),N=e.variables||[],k=s!==(o.subject||"")||a!==(o.text||"")||u!==(o.html||"");j(()=>{let h=++_.current,L=setTimeout(()=>{let Q=new URLSearchParams({subject:s,text:a,html:u}).toString();S("GET","/admin/email/templates/"+I(e.id)+"/preview?"+Q).then(se=>{_.current===h&&(f(se.preview||null),g(null))}).catch(se=>{_.current===h&&g(se.message)})},300);return()=>clearTimeout(L)},[s,a,u,e.id]),j(()=>{let h=C.current;if(!h)return;C.current=null;let L=h.part==="subject"?w.current:h.part==="text"?x.current:R.current;if(L){L.focus();try{L.setSelectionRange(h.at,h.at)}catch{}}});function E(h){let L=T.current,Q=L==="subject"?w.current:L==="text"?x.current:R.current,se=L==="subject"?s:L==="text"?a:u,Me=Q&&Q.selectionStart!==null?Q.selectionStart:se.length,Ae=Q&&Q.selectionEnd!==null?Q.selectionEnd:Me,le=vu(se,Me,Ae,Pa(h));(L==="subject"?l:L==="text"?c:p)(le.value),C.current={part:L,at:le.caret}}function P(){y(!0),S("PUT","/admin/email/templates/"+I(e.id),{subject:s,text:a,html:u}).then(()=>{D("Template saved.","ok"),i()}).catch(O).then(()=>y(!1))}function U(){S("DELETE","/admin/email/templates/"+I(e.id)).then(()=>{D("Back to the default.","ok"),i()}).catch(O)}function X(){ge(h=>t(Su,{close:h,send:L=>S("POST","/admin/email/test",{to:L,template:e.id,subject:s,text:a,html:u}).then(()=>{D("Sent to "+L+".","ok")})}))}let H=Ir[e.id]||{title:e.id,line:""},q=h=>()=>{T.current=h};return t(Se,{title:H.title,subtitle:H.line,onClose:n,wide:!0,footer:t(V,{children:[t(B,{kind:"primary",onClick:P,disabled:v||!k,children:"Save"}),t(B,{onClick:X,children:"Send me this"}),t(me,{}),t(A,{onClick:n,children:"Close"})]}),children:[t($,{label:"Subject",children:t("input",{type:"text",id:"tpl-subject",ref:w,value:s,disabled:r,onFocus:q("subject"),onInput:h=>l(h.currentTarget.value)})}),t("div",{class:"field",children:[t("span",{class:"field-head",children:t("b",{children:"Names you can drop in"})}),t("div",{class:"placeholder-bar",role:"toolbar","aria-label":"Placeholders",children:[N.map(h=>t("button",{type:"button",class:"chip accent","data-placeholder":h.name,title:"Inserts "+Pa(h.name)+" \u2014 e.g. "+h.sample,disabled:r,onClick:()=>E(h.name),children:h.label},h.name)),N.length?null:t(M,{children:"This backend did not say which names this message may use."})]}),t(M,{children:"Click one to drop it where the cursor is. The backend fills it in when it sends."})]}),t($,{label:"Body",children:t("textarea",{id:"tpl-text",ref:x,rows:9,value:a,disabled:r,onFocus:q("text"),onInput:h=>c(h.currentTarget.value)})}),t("div",{class:"field",children:[t("span",{class:"field-head",children:[t("b",{children:"Preview"}),t(M,{children:"Rendered by the backend with sample values, exactly as it would send."})]}),t("div",{class:"preview-pane",id:"tpl-preview","aria-live":"polite",children:[b?t(J,{kind:"bad",children:b}):null,d?t(W,{children:[t("div",{class:"preview-subject",children:d.subject}),t("pre",{children:d.text})]}):t(M,{children:"Rendering\u2026"})]})]}),t(He,{label:"Edit HTML",children:[t(fe,{style:"margin:0 0 6px",children:"The HTML version, for mail apps that show it. Most people never need to touch this; the plain body above is always sent too."}),t("textarea",{id:"tpl-html",ref:R,rows:8,value:u,disabled:r,onFocus:q("html"),onInput:h=>p(h.currentTarget.value)}),d?t("div",{class:"field",style:"margin-top:8px",children:[t("span",{class:"field-head",children:t("b",{children:"Rendered HTML, as source"})}),t("pre",{class:"preview-pane mono",id:"tpl-html-preview",children:d.html})]}):null]}),e.isOverridden?t($e,{children:t("div",{class:"danger-action",children:[t("div",{class:"danger-why",children:"Throws away your words for this message; the backend sends its own again."}),t(B,{kind:"danger",onClick:()=>we("Reset to default","Your edited subject, body and HTML for \u201C"+H.title+"\u201D are discarded. The shipped default is sent from now on.",U,"Reset to default"),children:"Reset to default"})]})}):null]})}function Su({close:e,send:n}){let[i,r]=m(La()),[o,s]=m(!1);function l(){let a=i.trim();a&&(s(!0),Na(a),n(a).then(e).catch(c=>{s(!1),O(c)}))}return t(ke,{title:"Send me this",actions:t(W,{children:[t(A,{onClick:e,children:"Cancel"}),t(A,{kind:"primary",onClick:l,disabled:o,children:"Send"})]}),children:[t(fe,{children:"Sends this message as it is now \u2014 saved or not \u2014 with the sample values, through your SMTP, to one address."}),t("input",{type:"email",id:"send-me-to",placeholder:"you@example.com",style:"width:100%",value:i,onInput:a=>r(a.currentTarget.value),onKeyDown:a=>a.key==="Enter"&&l()})]})}function Tu({close:e}){let[n,i]=m(La());function r(){let o=n.trim();Na(o),S("POST","/admin/email/test",{to:o}).then(()=>{e(),D("Test email sent.","ok")}).catch(O)}return t(ke,{title:"Send a test email",actions:t(W,{children:[t(A,{onClick:e,children:"Cancel"}),t(A,{kind:"primary",onClick:r,children:"Send"})]}),children:[t(fe,{children:"This sends a real message through the configured SMTP right now, and reports the failure reason if it cannot."}),t("input",{type:"email",placeholder:"you@example.com",style:"width:100%",value:n,onInput:o=>i(o.currentTarget.value)})]})}function xu(e,n){let i=e||{};return i.type==="s3"?'Archives live in the bucket "'+(n&&n.name||"?")+'" under '+(i.prefix||"backups/")+".":"Archives live in "+ve(i.path)+"."}var Ru="0 3 * * *",Eu=e=>S("POST","/admin/triggers/preview",{cron:e,count:5});function _u(e){let n=[],i=e.keepLast||0,r=e.keepDaily||0,o=e.keepWeekly||0;return i&&n.push("the last "+i),r&&n.push("one a day for "+r+" day"+(r===1?"":"s")),o&&n.push("one a week for "+o+" week"+(o===1?"":"s")),n.length?"Keeps "+n.join(", and ")+".":"Nothing is kept: every backup is deleted by the next one."}function Au(e){return"The data is restored, but "+e.map(i=>i.file).join(", ")+(e.length===1?" was":" were")+" not taken up: "+e[0].reason+" The settings from before the restore stay in force."}function Oa(e){let{readonly:n,whoami:i}=te(),[r,o]=m(null),[s,l]=m(0),[a,c]=m(null),u=i&&i.backend&&i.backend.name||"";function p(){S("GET","/admin/backups").then(R=>{o(R),l(C=>C+1)}).catch(O)}j(()=>{p()},[]);function d(){D("Backing up\u2026"),S("POST","/admin/backups",{}).then(R=>{D("Backup written"+(R.bytes?" ("+mt(R.bytes)+")":"")+".","ok"),p()}).catch(O)}function f(R){fetch("/admin/backups/archive?file="+I(R.file),{headers:ut(_e.get().credential)}).then(async C=>{if(!C.ok){let E=await C.json().catch(()=>null);throw new Error(E&&E.error||"HTTP "+C.status)}let _=await C.blob(),N=URL.createObjectURL(_),k=document.createElement("a");k.href=N,k.download=R.file,document.body.appendChild(k),k.click(),k.remove(),setTimeout(()=>URL.revokeObjectURL(N),1e4)}).catch(O)}function b(R,C){c(R.file),S("POST","/admin/backups/restore",{archive:R.file,safetySnapshot:C}).then(_=>{D("Restored "+R.file+(_.safetyArchive?" \u2014 the data from before is in "+_.safetyArchive.split("/").pop()+".":"."),"ok");let N=_.settings&&_.settings.refused||[];N.length&&D(Au(N),"bad")}).catch(O).then(()=>{c(null),p()})}function g(R){ge(C=>t(Nu,{archive:R,backendName:u,close:C,onConfirm:_=>b(R,_)}))}let v=r&&r.config||{},y=v.status||{},T=r&&r.backups||[],w=r?r.bucket:void 0,x=!!a;return t(ce,{title:"Backups",subtitle:"When backups happen, how many are kept, and a restore when you must.",children:[a?t(J,{kind:"warn",children:[t("b",{children:["Restoring ",a,"\u2026"]})," The backend is swapping its database. Nothing on this page works until it answers."]}):null,r?t("div",{class:x?"blocked":"","aria-busy":x?"true":"false",children:[t(pe,{children:t(V,{children:[v.schedule&&v.schedule.enabled?t(K,{kind:"ok",children:"scheduled"}):t(K,{kind:"warn",children:"not scheduled"}),y.lastSuccessAt?t(K,{kind:"ok",children:"last backup "+he(y.lastSuccessAt)}):t(K,{kind:"warn",children:"never backed up"}),y.lastResult&&y.lastResult.ok===!1?t(K,{kind:"bad",title:ve(y.lastResult.error),children:"last attempt failed "+he(y.lastResult.at)}):null,y.nextRunAt?t(K,{children:"next "+he(y.nextRunAt)}):null,t(me,{}),t(B,{tiny:!0,kind:"primary",id:"backup-now",disabled:x,onClick:d,children:"Back up now"})]})}),t("h2",{children:"When"}),t(Pu,{config:v,reload:p,blocked:x},"when"+s),t("h2",{children:"Keep"}),t(Iu,{config:v,reload:p,blocked:x},"keep"+s),t("h2",{children:"Where archives go"}),t(Lu,{config:v,bucket:w,reload:p,blocked:x},"where"+s),t("h2",{children:"Archives"}),r&&r.listingError?t(J,{kind:"bad",children:[t("b",{children:"The archives could not be listed."})," ",r.listingError]}):null,t(ye,{columns:["Archive","Taken","Size",""],rows:T,renderRow:R=>t("tr",{"data-archive":R.file,children:[t("td",{children:[t("b",{children:R.file}),R.where==="s3"?t(K,{children:"in the bucket"}):null]}),t("td",{children:he(R.createdAt)}),t("td",{children:mt(R.bytes)}),t(Ye,{children:[t(A,{tiny:!0,disabled:x,onClick:()=>f(R),children:"Download"}),t(B,{tiny:!0,kind:"danger",disabled:x||n,onClick:()=>g(R),children:"Restore\u2026"})]})]},R.file),empty:t(oe,{action:{label:"Back up now",onClick:d},children:"No backups yet."})}),t(fe,{style:"margin-top:10px",children:[xu(v.destination,w)+" A restore replaces the records, files, workflows and settings with the archive's; the run history is not in a backup and stays as it is. With ",t("i",{children:"Back up first"})," ticked, what is there now is archived before it goes."]})]}):null]})}function Pu({config:e,reload:n,blocked:i}){let{readonly:r}=te(),o=e.schedule||null,[s,l]=m(!!(o&&o.enabled)),[a,c]=m(o&&o.cron||Ru),[u,p]=m(o&&o.missedFirePolicy==="run-once-on-start"?"run-once-on-start":"skip"),d=r||i;function f(){S("PUT","/admin/backups/config",{schedule:s?{enabled:!0,cron:a.trim(),missedFirePolicy:u}:null}).then(()=>{D(s?"Backup schedule saved.":"Backups are no longer scheduled.","ok"),n()}).catch(O)}return t(pe,{children:[t(re,{id:"backup-enabled",checked:s,onChange:l,disabled:d,children:"Back up on a schedule"}),s?t("div",{class:"when-body",id:"backup-schedule",children:[t(_t,{id:"backup-cron",value:a,onChange:c,preview:Eu,disabled:d}),t(G,{h:10}),t("div",{class:"scope-group",children:[t("div",{class:"field-head",children:t("b",{children:"If a backup is missed while the backend is off"})}),t("label",{class:"check",children:[t("input",{type:"radio",name:"backup-missed",checked:u==="skip",disabled:d,onChange:()=>p("skip")})," Skip it \u2014 carry on from the next time"]}),t("label",{class:"check",children:[t("input",{type:"radio",name:"backup-missed",checked:u==="run-once-on-start",disabled:d,onChange:()=>p("run-once-on-start")})," Run it once, as soon as the backend is back"]})]})]}):null,t(V,{style:"margin-top:12px",children:t(B,{tiny:!0,kind:"primary",id:"save-schedule",disabled:i,onClick:f,children:"Save schedule"})})]})}function Iu({config:e,reload:n,blocked:i}){let{readonly:r}=te(),o=e.retention||{},[s,l]=m(String(o.keepLast??7)),[a,c]=m(String(o.keepDaily??0)),[u,p]=m(String(o.keepWeekly??0)),[d,f]=m(!!e.includeSecrets),b=r||i,g=y=>Math.max(0,Math.floor(Number(y)||0));function v(){S("PUT","/admin/backups/config",{retention:{keepLast:g(s),keepDaily:g(a),keepWeekly:g(u)},includeSecrets:d}).then(()=>{D("Backup settings saved.","ok"),n()}).catch(O)}return t(pe,{children:[t("div",{class:"grid2",children:[t($,{label:"Keep the last",children:t("input",{type:"number",min:"0",id:"keep-last",value:s,disabled:b,onInput:y=>l(y.currentTarget.value)})}),t($,{label:"and one a day for (days)",children:t("input",{type:"number",min:"0",id:"keep-daily",value:a,disabled:b,onInput:y=>c(y.currentTarget.value)})}),t($,{label:"and one a week for (weeks)",children:t("input",{type:"number",min:"0",id:"keep-weekly",value:u,disabled:b,onInput:y=>p(y.currentTarget.value)})})]}),t("div",{class:"sub",id:"keep-words",children:_u({keepLast:g(s),keepDaily:g(a),keepWeekly:g(u)})}),t(G,{}),t(re,{id:"include-secrets",checked:d,onChange:f,disabled:b,children:"Include this machine's secrets in each archive"}),t(fe,{children:"Off by default: an archive with secrets in it is a credential. Turn it on only where the archives are kept as safely as the backend."}),t(V,{style:"margin-top:12px",children:t(B,{tiny:!0,kind:"primary",id:"save-keep",disabled:i,onClick:v,children:"Save"})})]})}function Lu({config:e,bucket:n,reload:i,blocked:r}){let{readonly:o}=te(),s=e.destination||{},[l,a]=m(s.type==="s3"?"s3":"local"),[c,u]=m(ve(s.path)),p=o||r,d=!!(n&&n.connected);function f(){S("PUT","/admin/backups/config",{destination:l==="s3"?{type:"s3"}:{type:"local",path:c.trim()}}).then(()=>{D(l==="s3"?'Backups go to the bucket "'+(n&&n.name||"")+'".':"Backups are written to "+c.trim()+".","ok"),i()}).catch(O)}return t(pe,{id:"backup-where",children:[t("div",{class:"tiles",role:"radiogroup","aria-label":"Where archives go",children:[t("label",{class:"tile"+(l==="local"?" on":""),children:[t("input",{type:"radio",name:"backup-where",value:"local",checked:l==="local",disabled:p,onChange:()=>a("local")}),t("b",{children:"This machine"}),t("span",{class:"sub",children:"A folder beside the backend's data. An archive here is lost with the machine."})]}),t("label",{class:"tile"+(l==="s3"?" on":"")+(d?"":" disabled"),title:d?void 0:"Connect a bucket on the Storage page first.",children:[t("input",{type:"radio",name:"backup-where",value:"s3",checked:l==="s3",disabled:p||!d,onChange:()=>a("s3")}),t("b",{children:"The bucket from the Storage page"}),t("span",{class:"sub",children:d?'Archives go to "'+n.name+'" under '+(s.prefix||"backups/")+". Nothing to type: the bucket is set up once, on the Storage page.":"Connect a bucket on the Storage page first; backups use the same one."})]})]}),l==="local"?t($,{label:"Folder on the backend's machine",style:"margin-top:8px",children:t("input",{type:"text",id:"backup-dest",value:c,disabled:p,onInput:b=>u(b.currentTarget.value)})}):null,t(V,{style:"margin-top:12px",children:t(B,{tiny:!0,kind:"primary",id:"save-where",disabled:r,onClick:f,children:"Save"})})]})}function Nu({archive:e,backendName:n,close:i,onConfirm:r}){let[o,s]=m(""),[l,a]=m(!0),c=n||"restore",u=o===c;return t(ke,{title:"Restore "+e.file+"?",actions:t(W,{children:[t(A,{onClick:i,children:"Cancel"}),t(A,{kind:"danger",id:"restore-confirm",disabled:!u,onClick:()=>{u&&(i(),r(l))},children:"Restore"})]}),children:[t("div",{class:"notice bad",children:"Everything written since "+(e.createdAt?he(e.createdAt):"this archive was taken")+" \u2014 records, files, settings \u2014 is replaced by what the archive holds. Apps using this backend see the older data at once."}),t(G,{h:8}),t(Fe,{checked:l,onChange:a,children:"Back up first \u2014 archive what is there now, so this can be undone"}),t("p",{class:"sub",children:'Type "'+c+'" to confirm.'}),t("input",{type:"text",id:"restore-typed",placeholder:c,"aria-label":"Type "+c+" to confirm",style:"width:100%;margin-top:8px",value:o,onInput:p=>s(p.target.value)})]})}var Du=/^[A-Za-z0-9_.-]{1,128}$/,Ou="NODEGX_SECRET_";function Fa(e){return Ou+e.replace(/[^A-Za-z0-9]+/g,"_").toUpperCase()}function Lr(e){return e.length===0||Du.test(e)?null:'Use letters, digits, "_", "." or "-", 1-128 characters \u2014 the same names a Secret node can ask for.'}function Ma(e){return e.stillResolvesFromEnvironment?{severity:"bad",message:'"'+e.name+'" was removed from this backend\u2019s store, but '+e.envName+' is still set in the environment this backend is running in \u2014 so functions asking for "'+e.name+'" keep resolving it. Unset that variable and restart the backend to finish removing it.'}:e.existed?{severity:"ok",message:'"'+e.name+'" was removed. A Secret node asking for it now fires its failure output.'}:{severity:"ok",message:'"'+e.name+'" was not stored here, so nothing changed.'}}function Ba(e,n){let i=new Set(e.map(r=>r.envName));return n.filter(r=>!i.has(r))}function Ha(e){return e.busy||e.name.length===0||e.value.length===0?!1:Lr(e.name)===null}function Wa(e){let{readonly:n}=te(),[i,r]=m(null);function o(){S("GET","/admin/secrets").then(r).catch(O)}j(()=>{o()},[]);function s(){ge(u=>t(ja,{close:u,taken:i&&i.secrets.map(p=>p.name)||[],onSaved:o}))}function l(u){ge(p=>t(ja,{close:p,fixedName:u.name,taken:[],onSaved:o}))}function a(u){qe("Delete secret "+u.name+"?",'Every Secret node asking for "'+u.name+'" fires its failure output from the next run. This cannot be undone: the value is not kept anywhere else.',u.name,()=>{S("DELETE","/admin/secrets/"+I(u.name)).then(p=>{let d=Ma(p);D(d.message,d.severity),o()}).catch(O)})}let c=i?Ba(i.secrets,i.environment):[];return t(ce,{title:"Secrets",subtitle:"Keys and passwords your cloud functions read with a Secret node. A value is typed once and never shown again.",children:i?t("div",{children:[t(V,{children:[t(me,{}),t(B,{tiny:!0,kind:"primary",id:"add-secret",onClick:s,children:"Add secret"})]}),t(G,{}),t(ye,{columns:["Name","In a function","Also in the environment",""],rows:i.secrets,renderRow:u=>t("tr",{"data-secret":u.name,children:[t("td",{children:t("b",{children:u.name})}),t("td",{children:t("span",{class:"hint",children:"Secret node \u2192 "+u.name})}),t("td",{children:u.alsoInEnvironment?t(K,{kind:"warn",title:u.envName+" is set where this backend runs; the stored value wins while it exists.",children:"yes \u2014 "+u.envName}):t("span",{class:"hint",children:"no"})}),t(Ye,{children:[t(B,{tiny:!0,disabled:n,onClick:()=>l(u),children:"Set a new value"}),t(B,{tiny:!0,kind:"danger",disabled:n,onClick:()=>a(u),children:"Delete"})]})]},u.name),empty:t(oe,{action:{label:"Add secret",onClick:s},children:"No secrets yet. A function that needs an API key or a password reads it from here, never from its code."})}),c.length?t("div",{style:"margin-top:12px",children:t(J,{children:["Set in the environment this backend runs in, not here: ",c.map((u,p)=>t("span",{children:[p?", ":"",t("code",{children:u})]},u)),". A function resolves those too; to change one, change the environment and restart."]})}):null,t(fe,{style:"margin-top:10px",children:"Values stay in this backend's secrets.json and are never sent back to a browser, an agent or a log \u2014 this page cannot show one, and neither can anything else."})]}):null})}function ja({close:e,fixedName:n,taken:i,onSaved:r}){let[o,s]=m(n||""),[l,a]=m(""),[c,u]=m(!1),[p,d]=m(null),f=Lr(o)||(!n&&i.indexOf(o)!==-1?'There is already a secret called "'+o+'". Set a new value on it instead.':null),b=Ha({name:o,value:l,busy:c})&&!f;function g(){b&&(u(!0),S("PUT","/admin/secrets/"+I(o),{value:l}).then(v=>{D((v.created?"Stored ":"Replaced ")+o+". Functions read the new value from their next run.","ok"),e(),r()}).catch(v=>{u(!1),d(v.message)}))}return t(ke,{title:n?"Set a new value for "+n:"Add a secret",actions:t(W,{children:[t(A,{onClick:e,children:"Cancel"}),t(B,{kind:"primary",id:"secret-save",disabled:!b,onClick:g,children:c?"Saving\u2026":"Save"})]}),children:[n?null:t($,{label:"Name \u2014 what a Secret node asks for",children:t("input",{type:"text",id:"secret-name",value:o,placeholder:"STRIPE_KEY",autocomplete:"off",spellcheck:!1,onInput:v=>s(v.currentTarget.value)})}),f?t(M,{children:f}):o?t("div",{class:"hint",children:"Also read from the environment as "+Fa(o)}):null,t(G,{h:8}),t($,{label:"Value",children:t("input",{type:"password",id:"secret-value",value:l,autocomplete:"new-password",onInput:v=>a(v.currentTarget.value),onKeyDown:v=>v.key==="Enter"&&g()})}),t("div",{class:"hint",children:"Shown once, here, as you type it. After Save nothing can read it back \u2014 not this page, not an agent, not a log."}),p?t(J,{kind:"bad",children:p}):null]})}function Fu(e){return e.columns.filter(n=>n.type==="String"&&["objectId","ACL"].indexOf(n.name)===-1).map(n=>n.name)}function Nr(e){if(!e)return"";let n=typeof e.rowsIndexed=="number"?e.rowsIndexed:null;return n===null?"index rebuilt":n+" record"+(n===1?"":"s")+" indexed"}function Ua(e){let[n,i]=m(null),[r,o]=m(null);function s(){S("GET","/admin/search").then(i).catch(O),S("GET","/admin/schema").then(l=>o((l.tables||[]).filter(a=>!a.name.startsWith("_")))).catch(O)}return j(()=>{s()},[]),t(ce,{title:"Search",subtitle:"Which collections answer a text search, and over which fields.",children:n&&r?t("div",{children:[n.fts5Available?null:t(J,{kind:"bad",children:"This backend's database was built without full-text search (SQLite FTS5). Search cannot be turned on here; the settings are kept for a backend that has it."}),t(G,{}),r.length?r.map(l=>t(Mu,{table:l,config:n.config.collections[l.name],reload:s,available:n.fts5Available},l.name)):t(oe,{children:"No collections yet. Make one on the Schema page and it appears here."}),t(fe,{style:"margin-top:10px",children:"A searchable collection answers a text search from an app and a node (the \`search\` field of a query). Only text fields can be searched; a field added later is not searched until it is ticked here."})]}):null})}function Mu({table:e,config:n,reload:i,available:r}){let{readonly:o}=te(),s=Fu(e),l=n||{enabled:!1,fields:[]},[a,c]=m(!!l.enabled),[u,p]=m(l.fields.filter(x=>s.indexOf(x)!==-1)),[d,f]=m(null),[b,g]=m(void 0),v=a!==!!l.enabled||u.join(",")!==l.fields.join(","),y=a&&!u.length?"Pick at least one field to search.":null;function T(){if(y)return;f("save");let x=()=>{f(null),i()};if(!a){S("DELETE","/admin/search/collections/"+I(e.name)).then(()=>{D(e.name+" is no longer searchable.","ok"),g(void 0),x()}).catch(R=>{f(null),O(R)});return}S("PUT","/admin/search/collections/"+I(e.name),{enabled:!0,fields:u}).then(R=>{g(R.rebuild),D(e.name+" is searchable \u2014 "+Nr(R.rebuild)+".","ok"),x()}).catch(R=>{f(null),O(R)})}function w(){f("rebuild"),S("POST","/admin/search/collections/"+I(e.name)+"/rebuild",{}).then(x=>{g(x.rebuild),D(e.name+": "+Nr(x.rebuild)+".","ok"),f(null),i()}).catch(x=>{f(null),O(x)})}return t(pe,{style:"margin-bottom:10px",id:"search-card-"+e.name,children:[t(V,{children:[t(re,{id:"search-"+e.name,checked:a,onChange:c,disabled:o||!r||!!d,children:t("b",{children:e.name})}),l.enabled?t(K,{kind:"ok",children:"searchable"}):t(K,{children:"not searchable"}),b?t(K,{kind:"accent",children:Nr(b)}):null,t(me,{}),l.enabled?t(B,{tiny:!0,disabled:!!d||!r,onClick:w,children:d==="rebuild"?"Rebuilding\u2026":"Rebuild index"}):null,t(B,{tiny:!0,kind:"primary",disabled:!v||!!y||!!d||!r,onClick:T,children:d==="save"?"Saving\u2026":"Save"})]}),a?t("div",{style:"margin-top:8px",children:[t("div",{class:"field-head",children:[t("b",{children:"Fields to search"}),s.length?null:t("span",{class:"hint",children:"This collection has no text fields."})]}),t(Ie,{items:u,onChange:p,suggest:async x=>s.filter(R=>u.indexOf(R)===-1&&R.toLowerCase().indexOf(x.toLowerCase())!==-1),validate:x=>s.indexOf(x)===-1?'"'+x+'" is not a text field of '+e.name+".":null,placeholder:"Pick a field",addLabel:"Add field",disabled:o||!!d}),y?t("div",{class:"hint",children:y}):null]}):null]})}var Dr=[{id:"auth",label:"Signing in",line:"login, signup, password and magic-link requests"},{id:"data",label:"Records",line:"reading and writing collections"},{id:"files",label:"Files",line:"uploads and downloads"},{id:"functions",label:"Cloud functions",line:"calls to deployed functions"},{id:"hooks",label:"Webhooks",line:"triggers fired from outside"},{id:"admin",label:"This manager",line:"admin routes and agents"},{id:"public",label:"Everything else",line:"health, docs and public pages"}],Bu=[{id:"debug",label:"Everything (debug)"},{id:"info",label:"Normal (info)"},{id:"warn",label:"Warnings and errors"},{id:"error",label:"Errors only"},{id:"silent",label:"Nothing"}];function Hu(e){let n=e.trim();return n?n==="loopback"||n==="*"||/^[0-9a-fA-F:.]+$/.test(n)?null:'A proxy is an IP address, "loopback" (the same machine) or "*" (any).':"Type an address."}function Ka(e){let[n,i]=m(null),[r,o]=m(0);function s(){S("GET","/admin/ops").then(a=>{i(a.config),o(c=>c+1)}).catch(O)}j(()=>{s()},[]);let l=(a,c,u)=>S("PUT","/admin/ops",{[a]:c}).then(()=>{D(u,"ok"),s()}).catch(O);return t(ce,{title:"Server",subtitle:"Who may call from a browser, how many requests are allowed, what is logged and kept. Each card saves on its own and applies at once.",children:n?t("div",{children:[t("h2",{children:"Who may call from a browser"}),t(ju,{cors:n.cors,save:a=>l("cors",a,"Browser access saved.")},"cors"+r),t("h2",{children:"Rate limits"}),t(Wu,{rl:n.rateLimit,save:a=>l("rateLimit",a,"Rate limits saved.")},"rate"+r),t("h2",{children:"Logging"}),t(Uu,{logging:n.logging,save:a=>l("logging",a,"Logging saved.")},"log"+r),t("h2",{children:"Activity trail"}),t(Ku,{audit:n.audit,save:a=>l("audit",a,"Activity trail settings saved.")},"audit"+r),t("h2",{children:"Run history"}),t(Vu,{executions:n.executions,save:a=>l("executions",a,"Run history settings saved.")},"runs"+r),t("h2",{children:"How long a list can be"}),t(qu,{queries:n.queries,save:a=>l("queries",a,"List lengths saved.")},"lists"+r),t("h2",{children:"Metrics"}),t($u,{metrics:n.metrics,save:a=>l("metrics",a,"Metrics settings saved.")},"metrics"+r)]}):null})}function ju({cors:e,save:n}){let{readonly:i}=te(),r=e.origins.indexOf("*")!==-1,[o,s]=m(r),[l,a]=m(e.origins.filter(d=>d!=="*")),[c,u]=m(!!e.credentials),p=!o&&!l.length?"List at least one site, or allow any.":o&&c?"Cookies cannot be sent to any site \u2014 browsers refuse that pair. Name the sites, or turn cookies off.":null;return t(pe,{children:[t(re,{id:"cors-any",checked:o,onChange:s,disabled:i,children:"Any site may call this backend"}),t(fe,{children:"Fine while you build; a live backend should name its sites."}),o?null:t("div",{id:"cors-origins",style:"margin-top:8px",children:[t("div",{class:"field-head",children:[t("b",{children:"These sites"}),t("span",{class:"hint",children:"the address a browser shows for your app"})]}),t(Ie,{items:l,onChange:a,validate:Zn,normalise:Qn,placeholder:"https://app.example.com",addLabel:"Add site",disabled:i})]}),t(G,{h:8}),t(re,{id:"cors-credentials",checked:c,onChange:u,disabled:i,children:"Let those sites send cookies"}),p?t(M,{children:p}):null,t(V,{style:"margin-top:12px",children:t(B,{tiny:!0,kind:"primary",id:"save-cors",disabled:!!p,onClick:()=>n({origins:o?["*"]:l,credentials:c}),children:"Save"})})]})}function Wu({rl:e,save:n}){let{readonly:i}=te(),[r,o]=m(!!e.enabled),[s,l]=m(()=>{let v={};for(let y of Dr)v[y.id]={rate:String((e.policies[y.id]||{ratePerMinute:0}).ratePerMinute),burst:String((e.policies[y.id]||{burst:0}).burst)};return v}),[a,c]=m(e.trustedProxies||[]),[u,p]=m(String(e.realtimeMaxConnections)),[d,f]=m(String(e.functionRunQueries)),b=v=>Math.max(0,Math.floor(Number(v)||0));function g(){let v={...e.policies};for(let y of Dr)v[y.id]={ratePerMinute:b(s[y.id].rate),burst:b(s[y.id].burst)};n({enabled:r,trustedProxies:a,policies:v,realtimeMaxConnections:b(u),functionRunQueries:b(d)})}return t(pe,{children:[t(re,{id:"rate-enabled",checked:r,onChange:o,disabled:i,children:"Limit how fast one client may call"}),t(fe,{children:"Each client gets an allowance per minute for each kind of request, and a burst it may spend at once. 0 in the burst means no limit for that kind."}),r?t("div",{class:"rate-table",id:"rate-table",children:[t("div",{class:"list-row list-head",children:[t("span",{style:"flex:1 1 160px",children:"Kind of request"}),t("span",{style:"width:110px",children:"Per minute"}),t("span",{style:"width:90px",children:"Burst"})]}),Dr.map(v=>t("div",{class:"list-row","data-class":v.id,children:[t("span",{style:"flex:1 1 160px",children:[t("b",{children:v.label}),t("span",{class:"sub",children:v.line})]}),t("input",{type:"number",min:"0",style:"width:110px","aria-label":v.label+" per minute",value:s[v.id].rate,disabled:i,onInput:y=>l({...s,[v.id]:{...s[v.id],rate:y.currentTarget.value}})}),t("input",{type:"number",min:"0",style:"width:90px","aria-label":v.label+" burst",value:s[v.id].burst,disabled:i,onInput:y=>l({...s,[v.id]:{...s[v.id],burst:y.currentTarget.value}})})]},v.id))]}):null,t(G,{}),t("div",{class:"grid2",children:[t($,{label:"Live streams open at once (0 = no limit)",children:t("input",{type:"number",min:"0",id:"rate-streams",value:u,disabled:i,onInput:v=>p(v.currentTarget.value)})}),t($,{label:"Requests one function run may make (0 = no limit)",children:t("input",{type:"number",min:"0",id:"rate-per-run",value:d,disabled:i,onInput:v=>f(v.currentTarget.value)})})]}),t(G,{h:8}),t("div",{class:"field-head",children:[t("b",{children:"Proxies to believe about who is calling"}),t("span",{class:"hint",children:"a reverse proxy in front of this backend, so its clients are told apart"})]}),t(Ie,{items:a,onChange:c,validate:Hu,placeholder:"loopback",addLabel:"Add proxy",disabled:i,label:v=>v==="loopback"?"the same machine":v==="*"?"any (only behind a proxy that resets the header)":v}),t(V,{style:"margin-top:12px",children:t(B,{tiny:!0,kind:"primary",id:"save-rate",onClick:g,children:"Save"})})]})}function Uu({logging:e,save:n}){let{readonly:i}=te(),[r,o]=m(e.level),[s,l]=m(e.format),[a,c]=m(!!e.requests);return t(pe,{children:[t("div",{class:"grid2",children:[t("div",{class:"scope-group",children:[t("div",{class:"field-head",children:t("b",{children:"How much to log"})}),Bu.map(u=>t("label",{class:"check",children:[t("input",{type:"radio",name:"log-level",value:u.id,checked:r===u.id,disabled:i,onChange:()=>o(u.id)})," ",u.label]},u.id))]}),t("div",{class:"scope-group",children:[t("div",{class:"field-head",children:t("b",{children:"How lines look"})}),t("label",{class:"check",children:[t("input",{type:"radio",name:"log-format",value:"auto",checked:s==="auto",disabled:i,onChange:()=>l("auto")})," Decide from where it runs (readable in a terminal, JSON under a supervisor)"]}),t("label",{class:"check",children:[t("input",{type:"radio",name:"log-format",value:"pretty",checked:s==="pretty",disabled:i,onChange:()=>l("pretty")})," Readable"]}),t("label",{class:"check",children:[t("input",{type:"radio",name:"log-format",value:"json",checked:s==="json",disabled:i,onChange:()=>l("json")})," JSON, one object per line"]})]})]}),t(G,{h:8}),t(re,{id:"log-requests",checked:a,onChange:c,disabled:i,children:"One line per request"}),t(V,{style:"margin-top:12px",children:t(B,{tiny:!0,kind:"primary",id:"save-logging",onClick:()=>n({level:r,format:s,requests:a}),children:"Save"})})]})}function Ku({audit:e,save:n}){let{readonly:i}=te(),[r,o]=m(!!e.enabled),[s,l]=m(String(e.retentionDays));return t(pe,{children:[t(re,{id:"audit-enabled",checked:r,onChange:o,disabled:i,children:"Record who changed what"}),t(fe,{children:"What the Activity page shows. Off, and nothing is written there."}),t(G,{h:8}),t($,{label:"Keep entries for (days; 0 keeps them forever)",children:t("input",{type:"number",min:"0",id:"audit-days",value:s,disabled:i,onInput:a=>l(a.currentTarget.value)})}),t(V,{style:"margin-top:12px",children:t(B,{tiny:!0,kind:"primary",id:"save-audit",onClick:()=>n({enabled:r,retentionDays:Math.max(0,Math.floor(Number(s)||0))}),children:"Save"})})]})}function Vu({executions:e,save:n}){let{readonly:i}=te(),[r,o]=m(String(e.retentionDays)),[s,l]=m(String(e.maxCount)),[a,c]=m(String(e.idempotencyTtlHours)),[u,p]=m(!1),d=b=>Math.max(0,Math.floor(Number(b)||0));function f(){p(!0),S("POST","/admin/executions/compact",{}).then(b=>{let g=b.beforeBytes||0,v=b.afterBytes||0;D("Run history compacted"+(g&&v?": "+Math.round(g/1024)+" KB \u2192 "+Math.round(v/1024)+" KB.":"."),"ok")}).catch(O).then(()=>p(!1))}return t(pe,{children:[t("div",{class:"grid2",children:[t($,{label:"Keep runs for (days; 0 = forever)",children:t("input",{type:"number",min:"0",id:"runs-days",value:r,disabled:i,onInput:b=>o(b.currentTarget.value)})}),t($,{label:"Keep at most (runs; 0 = no limit)",children:t("input",{type:"number",min:"0",id:"runs-most",value:s,disabled:i,onInput:b=>l(b.currentTarget.value)})}),t($,{label:"Remember an answered Idempotency-Key for (hours)",children:t("input",{type:"number",min:"0",id:"runs-ttl",value:a,disabled:i,onInput:b=>c(b.currentTarget.value)})})]}),t(fe,{children:"Whichever limit is reached first wins: a misfiring workflow can write a month of runs in an hour."}),t(V,{style:"margin-top:12px",children:[t(B,{tiny:!0,kind:"primary",id:"save-runs",onClick:()=>n({...e,retentionDays:d(r),maxCount:d(s),idempotencyTtlHours:d(a)}),children:"Save"}),t(B,{tiny:!0,id:"compact-now",disabled:u,onClick:()=>we("Compact the run history now?","The history file is rewritten without the space old runs left behind. Runs are kept; this only takes a moment.",f,"Compact"),children:u?"Compacting\u2026":"Compact now"})]})]})}function Gu(e,n){let i=r=>/^\\s*\\d+\\s*$/.test(r)&&Number(r)>=1;return i(e)?i(n)?Number(n)<Number(e)?"The longest list cannot be shorter than the one the app gets without asking.":null:"The longest list needs a whole number of at least 1.":"A list the app does not size needs a whole number of at least 1."}function qu({queries:e,save:n}){let{readonly:i}=te(),[r,o]=m(String(e.defaultLimit)),[s,l]=m(String(e.maxLimit)),a=Gu(r,s);return t(pe,{children:[t("div",{class:"grid2",children:[t($,{label:"When the app does not say how many (records)",children:t("input",{type:"number",min:"1",step:"1",id:"lists-default",value:r,disabled:i,onInput:c=>o(c.currentTarget.value)})}),t($,{label:"The most the app may ask for (records)",children:t("input",{type:"number",min:"1",step:"1",id:"lists-max",value:s,disabled:i,onInput:c=>l(c.currentTarget.value)})})]}),t(fe,{children:"A query that forgot to say how many would otherwise read the whole collection. A list cut short tells the app so \u2014 nothing goes missing silently."}),a?t("div",{class:"chips-problem",id:"lists-problem",children:a}):null,t(V,{style:"margin-top:12px",children:t(B,{tiny:!0,kind:"primary",id:"save-lists",disabled:!!a,onClick:()=>n({defaultLimit:Number(r),maxLimit:Number(s)}),children:"Save"})})]})}function $u({metrics:e,save:n}){let{readonly:i}=te(),[r,o]=m(!!e.enabled),[s,l]=m(!!e.allowLoopback),a=location.origin+"/metrics";return t(pe,{children:[t(re,{id:"metrics-enabled",checked:r,onChange:o,disabled:i,children:"Serve metrics for Prometheus"}),r?t(J,{children:["Scrape ",t("code",{id:"metrics-url",children:a})," with the admin credential"+(s?", or from this machine without one.":".")]}):null,t(G,{h:8}),t(re,{id:"metrics-loopback",checked:s,onChange:l,disabled:i||!r,children:"A scraper on this machine needs no credential"}),t(V,{style:"margin-top:12px",children:t(B,{tiny:!0,kind:"primary",id:"save-metrics",onClick:()=>n({enabled:r,allowLoopback:s}),children:"Save"})})]})}var zu=[{value:"admin",label:"an admin"},{value:"admin:readonly",label:"a read-only admin"},{value:"user",label:"a signed-in user"},{value:"apiKey",label:"an API key"},{value:"anonymous",label:"nobody signed in"},{value:"cli",label:"the command line"}];function Va(e){return[{name:"action",kind:"choice",ops:["is"],options:e.map(n=>({value:n,label:ui(n)}))},{name:"who",kind:"choice",ops:["is"],options:zu},{name:"outcome",kind:"choice",ops:["is"],options:[{value:"success",label:"succeeded"},{value:"failure",label:"failed"}]},{name:"when",kind:"date",ops:["within","on","before","after","between"]}]}function Ga(e,n=new Date){if(e.conj==="or"&&e.items.length>1)throw new Error('Activity can be filtered with "and" only.');let i={},r=(o,s)=>{if(i[o]!==void 0)throw new Error(o+" is asked twice.");i[o]=s};for(let o of e.items){if(o.kind==="group")throw new Error('Activity can be filtered with "and" only.');let s=o,l=(s.value||"").trim();switch(s.field){case"action":l&&r("action",l);break;case"who":l&&r("actorKind",l);break;case"outcome":l&&r("outcome",l);break;case"when":{if(s.op==="within"){let[a,c]=Wt(l||"today",n);r("since",String(a.getTime())),r("until",String(c.getTime()-1))}else if(s.op==="on"&&l){let a=De(l);r("since",String(a.getTime())),r("until",String(Re(a,1).getTime()-1))}else if(s.op==="before"&&l)r("until",String(De(l).getTime()-1));else if(s.op==="after"&&l)r("since",String(Re(De(l),1).getTime()));else if(s.op==="between"&&l&&(s.value2||"").trim()){let a=De(l),c=De((s.value2||"").trim()),[u,p]=a<=c?[a,c]:[c,a];r("since",String(u.getTime())),r("until",String(Re(p,1).getTime()-1))}break}default:throw new Error('There is no field called "'+s.field+'".')}}return i}function ui(e){return e.split(".").join(" \xB7 ")}function Or(e,n){let i=e.actorKind||"",r=e.actor||"";switch(i){case"admin":return r?n&&n(r)||"admin "+r:"the admin credential";case"admin:readonly":return r?(n&&n(r)||"admin "+r)+" (read-only)":"the read-only credential";case"apiKey":return"API key: "+(r||"?");case"user":return n&&n(r)||"user "+r;case"anonymous":return"nobody signed in";case"cli":return"the command line";default:return r?i+" "+r:i||"\u2014"}}function Fr(e){let n=e.route||"",i=e.target||{},r=s=>typeof i[s]=="string"?i[s]:"";if(/^admin\\/permissions\\/collections\\/:name/.test(n))return{text:r("name")+" \xB7 permissions",href:ne("permissions")};if(/^admin\\/permissions\\/functions\\/:name/.test(n))return{text:r("name")+" \xB7 function permissions",href:ne("permissions")};if(/^admin\\/permissions/.test(n))return{text:"permissions",href:ne("permissions")};if(/^admin\\/roles\\/:name\\/users\\/:userId/.test(n))return{text:r("name")+" \xB7 role member",href:ne("roles",r("name"))};if(/^admin\\/roles\\/:name/.test(n))return{text:r("name")+" \xB7 role",href:ne("roles",r("name"))};if(/^admin\\/roles$/.test(n))return{text:"a role",href:ne("roles")};if(/^admin\\/users\\/:id/.test(n))return{text:r("id")+" \xB7 user",href:ne("users",r("id"))};if(/^admin\\/users$/.test(n))return{text:"a user",href:ne("users")};if(/^_admin\\/setup$/.test(n))return{text:"the first admin account",href:ne("users")};if(/^admin\\/views\\/:collection\\/:name/.test(n))return{text:r("collection")+" \xB7 view "+r("name"),href:ne("collections",r("collection"))};if(/^admin\\/keys\\/:id/.test(n))return{text:r("id")+" \xB7 API key",href:ne("keys",r("id"))};if(/^admin\\/keys$/.test(n))return{text:"an API key",href:ne("keys")};if(/^admin\\/secrets\\/:name/.test(n))return{text:r("name")+" \xB7 secret",href:ne("secrets")};if(/^admin\\/schema\\/:table/.test(n))return{text:r("table")+" \xB7 schema",href:ne("schema",r("table"))};if(/^(admin\\/schema|api\\/_schema)/.test(n))return{text:"schema",href:ne("schema")};if(/^admin\\/backups\\/restore$/.test(n))return{text:"a restore",href:ne("backups")};if(/^admin\\/backups/.test(n))return{text:"backups",href:ne("backups")};if(/^admin\\/import\\/:collection/.test(n))return{text:r("collection")+" \xB7 import",href:ne("collections",r("collection"))};if(/^admin\\/triggers\\/:id/.test(n))return{text:r("id")+" \xB7 trigger",href:ne("triggers",r("id"))};if(/^admin\\/triggers$/.test(n))return{text:"a trigger",href:ne("triggers")};if(/^admin\\/workflow-defs\\/:id/.test(n))return{text:r("id")+" \xB7 workflow",href:ne("workflows",r("id"))};if(/^admin\\/workflow-defs$/.test(n))return{text:"a workflow",href:ne("workflows")};if(/^admin\\/workflow-runs\\/:executionId/.test(n))return{text:r("executionId")+" \xB7 run",href:ne("runs",r("executionId"))};if(/^admin\\/workflows\\/:name/.test(n))return{text:r("name")+" \xB7 function file",href:ne("workflows")};if(/^admin\\/workflows/.test(n))return{text:"function files",href:ne("workflows")};if(/^admin\\/email\\/templates\\/:id/.test(n))return{text:r("id")+" \xB7 email template",href:ne("email",r("id"))};if(/^admin\\/email/.test(n))return{text:"email",href:ne("email")};if(/^admin\\/files\\/:name/.test(n))return{text:r("name")+" \xB7 file",href:ne("files")};if(/^admin\\/files/.test(n))return{text:"storage",href:ne("files")};if(/^admin\\/search\\/collections\\/:name/.test(n))return{text:r("name")+" \xB7 search",href:ne("search")};if(/^admin\\/ops$/.test(n))return{text:"server settings",href:ne("server")};if(/^admin\\/executions\\/compact$/.test(n))return{text:"run history",href:ne("server")};if(/^admin\\/auth\\/providers\\/:id/.test(n))return{text:r("id")+" \xB7 sign-in provider",href:ne("signin",r("id"))};if(/^admin\\/auth/.test(n))return{text:"sign-in",href:ne("signin")};let o=Object.keys(i);return o.length?{text:o.map(s=>r(s)||String(i[s])).join(" \xB7 ")}:{text:"\u2014"}}var pi=100;function qa(e){let[n,i]=m({kind:"group",conj:"and",items:[]}),[r,o]=m(null),[s,l]=m(null),[a,c]=m(0),[u,p]=m(null),[d,f]=m({}),b=Va(r||[]),g={};try{g=Ga(n),u&&p(null)}catch(_){u||p(_.message)}let v=Object.keys(g).map(_=>_+"="+encodeURIComponent(g[_])).join("&");function y(){S("GET","/admin/audit?limit="+pi+"&offset="+a+(v?"&"+v:"")).then(_=>{o(k=>k===null?_.actions||[]:k),l(_);let N=new Set;for(let k of _.entries||[])(k.actorKind==="admin"||k.actorKind==="admin:readonly"||k.actorKind==="user")&&k.actor&&!d[k.actor]&&N.add(k.actor);N.forEach(k=>{S("GET","/admin/users/"+encodeURIComponent(k)).then(E=>{let P=E.user||{},U=P.email||P.username;U&&f(X=>({...X,[k]:U}))}).catch(()=>{})})}).catch(O)}j(y,[v,a]);function T(_){let N=Fr(_);ge(k=>t(ke,{title:ui(_.action||""),autoFocus:!1,wide:!0,actions:t(A,{onClick:k,children:"Close"}),children:[t(V,{children:[t(K,{kind:_.outcome==="success"?"ok":"bad",children:_.outcome==="success"?"succeeded":"failed"}),t("span",{class:"hint",children:he(_.at)}),t("span",{class:"hint",children:"by "+Or(_,E=>d[E]||null)}),N.href?t("a",{href:N.href,onClick:k,children:N.text}):t("span",{children:N.text})]}),t(G,{h:8}),_.detail&&Object.keys(_.detail).length?t("div",{class:"jx",children:t(an,{value:_.detail,name:"what changed",openTo:2})}):t("div",{class:"hint",children:"Nothing more was recorded."}),t(G,{h:8}),t("div",{class:"hint",children:[_.ip?"from "+_.ip:"",_.requestId?"request "+_.requestId:"",_.status?"HTTP "+_.status:""].filter(Boolean).join(" \xB7 ")}),t(He,{label:"Raw entry",children:t("textarea",{readOnly:!0,value:JSON.stringify(_,null,2),style:"min-height:240px;width:100%"})})]}))}let w=n.items.length>0,x=s?s.count:0,R=x?a+1:0,C=Math.min(a+pi,x);return t(ce,{title:"Activity",subtitle:"Who changed what, when, and from where \u2014 permissions, roles, keys, schema, backups, settings and sign-ins.",children:[t("div",{id:"activity-filter",children:t(gt,{group:n,fields:b,flat:!0,onChange:_=>{i(_),c(0)}})}),u?t(M,{children:u}):null,t(G,{}),t(V,{children:[s?t(W,{children:[s.enabled?t(K,{kind:"ok",children:"recording"}):t(K,{kind:"warn",children:"turned off on the Server page"}),t(K,{children:s.retentionDays?"kept "+s.retentionDays+" days":"kept forever"}),t("span",{class:"hint",id:"activity-count",children:[x?"Showing "+R+"\u2013"+C+" of "+x:"No entries",w&&v?" where "+Et(n,b):""]})]}):null,t(A,{tiny:!0,disabled:a===0,onClick:()=>c(Math.max(0,a-pi)),children:"\u2039 Newer"}),t(A,{tiny:!0,disabled:C>=x,onClick:()=>c(a+pi),children:"Older \u203A"}),t(A,{tiny:!0,onClick:y,children:"Refresh"})]}),t(G,{}),s?t(W,{children:[t(ye,{columns:["When","What","Who","On","Outcome",""],rows:s.entries||[],renderRow:(_,N)=>{let k=Fr(_);return t("tr",{children:[t("td",{children:he(_.at)}),t("td",{children:ui(_.action||"")}),t("td",{children:Or(_,E=>d[E]||null)}),t("td",{children:k.href?t("a",{href:k.href,children:k.text}):k.text}),t("td",{children:t(K,{kind:_.outcome==="success"?"ok":"bad",children:_.outcome==="success"?"succeeded":"failed"})}),t(Ye,{children:t(A,{tiny:!0,onClick:()=>T(_),children:"Detail"})})]},_.objectId||N)},empty:w?t(oe,{children:"Nothing matches."}):t(oe,{action:{label:"Refresh",onClick:y,write:!1},children:"Nothing recorded yet. Changes to permissions, roles, keys, schema, backups and settings land here."})}),t(fe,{style:"margin-top:10px",children:"The trail is a table in this backend\u2019s own database: whoever can reach the file can edit it. Ship the logs off the machine if you need a copy nobody here can rewrite \u2014 every entry has a matching log line with the same request id."})]}):null]})}var Mr=[{id:"collections",feature:"collections",component:ys},{id:"schema",feature:"schema",component:vs},{id:"users",feature:"users",component:Ss},{id:"roles",feature:"roles",component:Ls},{id:"signin",feature:"auth",component:Bs},{id:"permissions",feature:"permissions",component:ra},{id:"keys",feature:"apiKeys",component:ca},{id:"triggers",feature:"triggers",component:pa},{id:"workflows",feature:"workflows",component:fa},{id:"runs",feature:"executions",component:ka},{id:"files",feature:"files",component:Ea},{id:"email",feature:"email",component:Da},{id:"backups",feature:"backups",component:Oa},{id:"secrets",feature:"secrets",component:Wa},{id:"search",feature:"search",component:Ua},{id:"server",feature:"ops",component:Ka},{id:"audit",feature:"ops",component:qa}];function $a(e){return Mr.find(n=>n.id===e)}function za(){let e=te(),n=!!e.whoami&&!e.whoami.adminAccount&&!e.readonly;return t(W,{children:[e.whoami?n?t(Yu,{}):t(Xu,{}):e.booting?null:t(Ju,{error:e.loginError}),t(Oi,{}),t(Di,{})]})}function Ju({error:e}){let[n,i]=m(""),[r,o]=m(""),[s,l]=m(""),[a,c]=m(!0),[u,p]=m(!1);return t("div",{id:"login",children:t("form",{id:"login-form",onSubmit:d=>{if(d.preventDefault(),!u){if(s.trim()){p(!0),xo(s.trim(),a).finally(()=>p(!1));return}!n.trim()||!r||(p(!0),Ro(n.trim(),r,a).finally(()=>p(!1)))}},children:[t("h1",{children:[t("span",{class:"dot"})," NodeGX Backend"]}),t("p",{class:"sub",children:"Sign in to manage this backend."}),t("div",{class:"field",style:"margin-top: 14px",children:t("input",{id:"login-email",type:"text",autocomplete:"username",placeholder:"Email","aria-label":"Email",value:n,onInput:d=>i(d.currentTarget.value)})}),t("div",{class:"field-row",style:"margin-top: 8px",children:[t("input",{id:"login-password",type:"password",autocomplete:"current-password",placeholder:"Password","aria-label":"Password",value:r,onInput:d=>o(d.currentTarget.value)}),t("button",{class:"btn primary",type:"submit",disabled:u||!s.trim()&&(!n.trim()||!r),children:"Sign in"})]}),t("label",{class:"check",style:"margin-top: 10px",children:[t("input",{id:"login-remember",type:"checkbox",checked:a,onChange:d=>c(d.currentTarget.checked)})," ",s.trim()?"Keep the credential in this tab (it is never kept longer)":"Keep me signed in on this browser"]}),e?t("div",{id:"login-error",class:"notice bad",style:"margin-top: 14px",children:e}):null,t("div",{class:"login-alt",children:t(He,{label:"Use the admin credential instead",children:[t("div",{class:"field-row",style:"margin-top: 6px",children:[t("input",{id:"login-token",type:"password",autocomplete:"off",placeholder:"Admin credential","aria-label":"Admin credential",value:s,onInput:d=>l(d.currentTarget.value)}),t("button",{class:"btn",type:"submit",disabled:u||!s.trim(),children:"Sign in with it"})]}),t("p",{class:"sub",style:"margin: 10px 0 0; font-size: 11px",children:["The credential is the ",t("code",{children:"adminToken"})," in the backend\u2019s ",t("code",{children:"secrets.json"}),", or whatever was passed to ",t("code",{children:"--token"}),". A new backend asks you to create your admin account once you sign in with it. A read-only credential signs in here too."]})]})})]})})}function Yu(){let e=te(),[n,i]=m(""),[r,o]=m(""),[s,l]=m(!1),[a,c]=m(!1),[u,p]=m(null),d=e.whoami.backend||{},f=n.trim().indexOf("@")!==-1&&r.length>0;return t("div",{id:"login",class:"setup",children:t("form",{id:"setup-form",onSubmit:b=>{b.preventDefault(),!(!f||a)&&(c(!0),p(null),Eo(n.trim(),r).catch(g=>p(g.message)).finally(()=>c(!1)))},children:[t("h1",{children:[t("span",{class:"dot"})," Create your admin account"]}),t("p",{class:"sub",children:[d.name?t("b",{children:d.name}):"This backend"," has no admin yet. Choose the email and password you will sign in with here \u2014 and in your app, where this account is in the ",t("b",{children:"admin"})," role."]}),t("div",{class:"field",style:"margin-top: 14px",children:t("input",{id:"setup-email",type:"email",autocomplete:"username",placeholder:"Email","aria-label":"Email",value:n,onInput:b=>i(b.currentTarget.value)})}),t("div",{class:"field-row",style:"margin-top: 8px",children:[t("input",{id:"setup-password",type:s?"text":"password",autocomplete:"new-password",placeholder:"Password","aria-label":"Password",value:r,onInput:b=>o(b.currentTarget.value)}),t("button",{type:"button",class:"btn",onClick:()=>{o(zn(Jn)),l(!0)},children:"Generate"}),t("button",{type:"button",class:"btn","aria-pressed":s,onClick:()=>l(!s),children:s?"Hide":"Show"})]}),t("div",{class:"field-row",style:"margin-top: 14px",children:t("button",{id:"setup-submit",class:"btn primary",type:"submit",disabled:a||!f,children:"Create and sign in"})}),u?t("div",{id:"setup-error",class:"notice bad",style:"margin-top: 14px",children:u}):null,t("p",{class:"sub",style:"margin: 16px 0 0; font-size: 11px",children:"The admin credential you signed in with stays as it is \u2014 scripts and the editor keep using it. From now on you sign in here as yourself, and give other people access from the Users page."}),t("p",{class:"sub",style:"margin: 8px 0 0; font-size: 11px",children:t("a",{href:"#",onClick:b=>{b.preventDefault(),Nn(null)},children:"Not now \u2014 sign out"})})]})})}function Xu(){let e=te(),n=Do(),i=Lo(e.features),r=$a(n.view),o=r&&e.features[r.feature]?r:void 0;j(()=>{if(o)return;let a=Mr.find(c=>e.features[c.feature]);a&&sn(a.id)},[n.view,o,e.features]),j(()=>{let a=No(location.hash).split("/")[0],c=On();a&&c.view&&a!==c.view&&sn(c.view,...c.params)},[n.view]);let s=o?o.component:null,l=e.whoami;return t("div",{id:"app",children:[t("div",{class:"brand",children:[t("span",{class:"dot"})," ",t("b",{children:"NodeGX"})," ",t("span",{class:"brand-role",children:"manager"})]}),t(Zu,{}),t("nav",{id:"nav","aria-label":"Sections",children:i.map(a=>t("div",{children:[t("div",{class:"group",children:a.label}),a.entries.map(c=>t("a",{href:ne(c.id),"aria-current":n.view===c.id?"true":"false",children:c.label},c.id))]},a.label))}),t("main",{id:"main",children:[l.security&&!l.security.enforced?t(Qu,{}):null,l.firstRun?t(ep,{}):null,l.adminAccount?null:t(tp,{}),s?t(s,{params:n.params,query:n.query},o.id):i.length?null:t(J,{kind:"bad",children:"This backend reports no available admin sections."})]})]})}function Zu(){let e=te(),n=Je(dt),i=Io(),r=e.whoami.backend||{},o=e.whoami.security;return t("div",{class:"topbar",children:[t("span",{id:"backend-name",class:"mono",children:[r.name," \xB7 ",r.id]}),t("span",{id:"tier-chip",children:e.readonly?t(K,{kind:"warn",children:"read-only"}):t(K,{kind:"accent",children:"admin"})}),t("span",{id:"person-chip",class:"mono",title:e.whoami.person?"Signed in as "+(e.whoami.person.email||e.whoami.person.username):"Signed in with the admin credential",children:e.whoami.person?e.whoami.person.email||e.whoami.person.username:"credential"}),t("span",{id:"enforce-chip",children:o&&o.enforced?t(K,{kind:"ok",children:"enforcing"}):t(K,{kind:"warn",children:"dev-open \u2014 nothing enforced"})}),t("span",{class:"spacer"}),t("span",{id:"live-chip",children:n.state==="off"?null:t(K,{kind:n.state==="live"?"ok":"warn",children:n.state})}),t("button",{type:"button",class:"btn tiny theme-toggle","aria-label":i.theme==="dark"?"Switch to the light theme":"Switch to the dark theme",title:i.theme==="dark"?"Light theme":"Dark theme",onClick:Po,children:i.theme==="dark"?"\u2600":"\u263E"}),t(A,{tiny:!0,id:"signout",onClick:()=>Nn(null),children:"Sign out"})]})}function Qu(){return t(J,{kind:"warn",children:[t("b",{children:"Dev-open is on. "}),"While it is, collection permissions and row ACLs do nothing for any caller of your app. Dev-open only runs on a backend bound to this machine. To test your real rules, turn on Enforce my rules on this machine at the top of the ",t("a",{href:"#/permissions",children:"Permissions page"}),"."]})}function ep(){return t(J,{kind:"accent",children:[t("b",{children:"First run. "}),'This backend generated its own admin credential on this start. It is the "adminToken" in the backend\u2019s secrets.json (mode 0600) \u2014 the editor and scripts use it; you sign in here as yourself. To choose your own, restart the service with --token <your-secret>.']})}function tp(){return t(J,{kind:"warn",children:[t("b",{children:"No admin account yet. "}),"Nobody has full access to this backend as a person. Sign in with the full admin credential and the manager asks you to create one."]})}Ao();var Ja=document.getElementById("root");Ja&&ao(t(za,{}),Ja);To();})();
65026
65047
  `;
65027
65048
  }
65028
65049
  });
@@ -82000,6 +82021,7 @@ var WorkflowRunner = class {
82000
82021
  // ../nodegx-backend/src/triggers/webhook.ts
82001
82022
  var crypto6 = __toESM(require("crypto"));
82002
82023
  var SIGNATURE_HEADERS = ["x-hub-signature-256", "x-signature-256", "x-webhook-signature"];
82024
+ var SIGNATURE_512_HEADERS = ["x-paystack-signature", "x-webhook-signature"];
82003
82025
  var TOKEN_HEADERS = ["x-webhook-token"];
82004
82026
  function timingSafeStrEqual(a, b) {
82005
82027
  const ha = crypto6.createHash("sha256").update(a).digest();
@@ -82024,20 +82046,23 @@ function verifyWebhook(scheme, secret, rawBody, headers, query) {
82024
82046
  if (!provided2) return { ok: false, reason: "missing webhook token (X-Webhook-Token / Bearer / ?token=)" };
82025
82047
  return timingSafeStrEqual(provided2, secret) ? { ok: true } : { ok: false, reason: "webhook token does not match" };
82026
82048
  }
82049
+ const algorithm = scheme === "hmac-sha512" ? "sha512" : "sha256";
82050
+ const headerNames = algorithm === "sha512" ? SIGNATURE_512_HEADERS : SIGNATURE_HEADERS;
82027
82051
  let provided;
82028
- for (const h of SIGNATURE_HEADERS) {
82052
+ for (const h of headerNames) {
82029
82053
  provided = provided || headerValue(headers, h);
82030
82054
  }
82031
82055
  if (!provided) {
82032
- return { ok: false, reason: `missing signature header (one of ${SIGNATURE_HEADERS.join(", ")})` };
82056
+ return { ok: false, reason: `missing signature header (one of ${headerNames.join(", ")})` };
82033
82057
  }
82034
- const sig = provided.startsWith("sha256=") ? provided.slice("sha256=".length) : provided;
82035
- const expectedHex = crypto6.createHmac("sha256", secret).update(rawBody).digest("hex");
82036
- const expectedB64 = crypto6.createHmac("sha256", secret).update(rawBody).digest("base64");
82058
+ const prefix = algorithm + "=";
82059
+ const sig = provided.startsWith(prefix) ? provided.slice(prefix.length) : provided;
82060
+ const expectedHex = crypto6.createHmac(algorithm, secret).update(rawBody).digest("hex");
82061
+ const expectedB64 = crypto6.createHmac(algorithm, secret).update(rawBody).digest("base64");
82037
82062
  if (timingSafeStrEqual(sig.toLowerCase(), expectedHex.toLowerCase()) || timingSafeStrEqual(sig, expectedB64)) {
82038
82063
  return { ok: true };
82039
82064
  }
82040
- return { ok: false, reason: "HMAC-SHA256 signature mismatch" };
82065
+ return { ok: false, reason: `HMAC-${algorithm.toUpperCase()} signature mismatch` };
82041
82066
  }
82042
82067
 
82043
82068
  // ../nodegx-backend/src/email/tokens.ts
@@ -86068,6 +86093,7 @@ function effectiveOverlapPolicy(trigger) {
86068
86093
  const authored = trigger.schedule && trigger.schedule.overlapPolicy;
86069
86094
  return authored || DEFAULT_OVERLAP_POLICY;
86070
86095
  }
86096
+ var WEBHOOK_SCHEMES = ["hmac-sha256", "hmac-sha512", "token"];
86071
86097
  var DEFAULT_RESPONSE_TIMEOUT_MS = 3e4;
86072
86098
  var MAX_RESPONSE_TIMEOUT_MS = 3e5;
86073
86099
  var TRIGGERS_FILE = "triggers.json";
@@ -86191,7 +86217,7 @@ function validateTriggerDef(raw) {
86191
86217
  if (typeof w.slug !== "string" || !SLUG_RE.test(w.slug)) {
86192
86218
  errors.push("webhook.slug must match [a-z0-9][a-z0-9-]{0,63} (lowercase, digits, hyphens)");
86193
86219
  }
86194
- if (w.scheme !== "hmac-sha256" && w.scheme !== "token") errors.push('webhook.scheme must be "hmac-sha256" or "token"');
86220
+ if (!WEBHOOK_SCHEMES.includes(w.scheme)) errors.push('webhook.scheme must be "hmac-sha256", "hmac-sha512" or "token"');
86195
86221
  if (typeof w.maxBodyBytes !== "number" || w.maxBodyBytes <= 0 || w.maxBodyBytes > 50 * 1024 * 1024) {
86196
86222
  errors.push("webhook.maxBodyBytes must be a positive number <= 50MB");
86197
86223
  }
@@ -86402,7 +86428,15 @@ ${errors.map((e) => ` - ${e}`).join("\n")}`);
86402
86428
  );
86403
86429
  if (clash) throw new TriggerConfigError(`webhook slug "${def2.webhook.slug}" is already used by trigger "${clash.id}"`);
86404
86430
  }
86405
- if (def2.type === "webhook") {
86431
+ if (def2.type === "webhook" && def2.webhook && def2.webhook.scheme === "hmac-sha512") {
86432
+ if (input2.secret) {
86433
+ this.secrets.set(WEBHOOK_SECRETS_NAMESPACE, id, input2.secret);
86434
+ } else if (!this.secrets.get(WEBHOOK_SECRETS_NAMESPACE, id) || existing && existing.webhook && existing.webhook.scheme !== "hmac-sha512") {
86435
+ throw new TriggerConfigError(
86436
+ "an hmac-sha512 (Paystack) hook checks the signature with the sender\u2019s own key: send it as `secret` (the Paystack secret key of the business that sends it). A secret made up here would refuse every call."
86437
+ );
86438
+ }
86439
+ } else if (def2.type === "webhook") {
86406
86440
  if (input2.secret) {
86407
86441
  this.secrets.set(WEBHOOK_SECRETS_NAMESPACE, id, input2.secret);
86408
86442
  mintedSecret = input2.secret;
@@ -86432,12 +86466,14 @@ ${errors.map((e) => ` - ${e}`).join("\n")}`);
86432
86466
  * `null` when there is no such trigger, `'not-a-webhook'` when it is not a
86433
86467
  * webhook: two different answers, because the second is a category error and
86434
86468
  * saying "no such trigger" about a schedule would send the caller looking for
86435
- * the wrong thing.
86469
+ * the wrong thing. `'supplied-secret'` for an hmac-sha512 (Paystack) hook:
86470
+ * its secret is the sender's key, so a minted one would refuse every call.
86436
86471
  */
86437
86472
  rotateWebhookSecret(id) {
86438
86473
  const t = this.triggers.find((x) => x.id === id);
86439
86474
  if (!t) return null;
86440
86475
  if (t.type !== "webhook") return "not-a-webhook";
86476
+ if (t.webhook && t.webhook.scheme === "hmac-sha512") return "supplied-secret";
86441
86477
  const secret = "whsec_" + crypto12.randomBytes(24).toString("base64url");
86442
86478
  this.secrets.set(WEBHOOK_SECRETS_NAMESPACE, id, secret);
86443
86479
  t.updatedAt = nowIso();
@@ -86818,6 +86854,12 @@ var AdminTriggerRoutes = class {
86818
86854
  if (result === "not-a-webhook") {
86819
86855
  throw new HttpError(400, `Trigger "${ctx.params.id}" is not a webhook \u2014 only a webhook has a secret.`);
86820
86856
  }
86857
+ if (result === "supplied-secret") {
86858
+ throw new HttpError(
86859
+ 400,
86860
+ `Trigger "${ctx.params.id}" checks Paystack's signature, made with the business's own secret key \u2014 a new secret made here would refuse every call. Save the hook with the new key as \`secret\` instead.`
86861
+ );
86862
+ }
86821
86863
  sendJSON(ctx.res, 200, {
86822
86864
  trigger: decorate(result.trigger),
86823
86865
  secret: result.secret,
@@ -91096,29 +91138,39 @@ Run it again with acceptLosses to let it.`);
91096
91138
 
91097
91139
  // ../nodegx-backend/src/storage/MetadataStore.ts
91098
91140
  var FILES_COLLECTION = "_Files";
91141
+ var FILE_COLUMNS = [
91142
+ { name: "storedName", type: "String" },
91143
+ { name: "originalName", type: "String" },
91144
+ { name: "size", type: "Number" },
91145
+ { name: "contentType", type: "String" },
91146
+ { name: "hash", type: "String" },
91147
+ { name: "driver", type: "String" },
91148
+ { name: "key", type: "String" },
91149
+ { name: "owner", type: "Pointer", targetClass: "_User" },
91150
+ { name: "private", type: "Boolean" },
91151
+ // P121-TWO001-MEMBERSPHOTO: the roles a private file is shared with, the same keys its ACL grants read. Kept as a
91152
+ // column because every record read drops a row's ACL (`cloudstore.js`), and a cloud function must be able to ask
91153
+ // "is this photo the members'?" before it puts it in a post.
91154
+ { name: "readers", type: "Array" }
91155
+ ];
91099
91156
  function ensureFilesTable(schemaManager) {
91100
91157
  if (!schemaManager) return;
91101
- schemaManager.createTable({
91102
- name: FILES_COLLECTION,
91103
- columns: [
91104
- { name: "storedName", type: "String" },
91105
- { name: "originalName", type: "String" },
91106
- { name: "size", type: "Number" },
91107
- { name: "contentType", type: "String" },
91108
- { name: "hash", type: "String" },
91109
- { name: "driver", type: "String" },
91110
- { name: "key", type: "String" },
91111
- { name: "owner", type: "Pointer", targetClass: "_User" },
91112
- { name: "private", type: "Boolean" }
91113
- ]
91114
- });
91158
+ if (schemaManager.createTable({ name: FILES_COLLECTION, columns: FILE_COLUMNS })) return;
91159
+ const have = new Set((schemaManager.getTableSchema(FILES_COLLECTION)?.columns ?? []).map((c) => c.name));
91160
+ for (const column of FILE_COLUMNS) if (!have.has(column.name)) schemaManager.addColumn(FILES_COLLECTION, column);
91115
91161
  }
91116
91162
  var MetadataStore = class {
91117
91163
  constructor(facade) {
91118
91164
  this.facade = facade;
91119
91165
  }
91120
91166
  async create(input2) {
91121
- const acl = input2.private && input2.owner ? { [input2.owner]: { read: true, write: true } } : void 0;
91167
+ let acl;
91168
+ if (input2.private) {
91169
+ const entries = {};
91170
+ if (input2.owner) entries[input2.owner] = { read: true, write: true };
91171
+ for (const reader of input2.readers ?? []) entries[reader] = { read: true };
91172
+ if (Object.keys(entries).length) acl = entries;
91173
+ }
91122
91174
  const record = await this.facade.rawCreate(FILES_COLLECTION, {
91123
91175
  storedName: input2.storedName,
91124
91176
  originalName: input2.originalName,
@@ -91129,6 +91181,7 @@ var MetadataStore = class {
91129
91181
  key: input2.key,
91130
91182
  owner: input2.owner,
91131
91183
  private: input2.private,
91184
+ ...input2.private && input2.readers?.length ? { readers: input2.readers } : {},
91132
91185
  ACL: acl
91133
91186
  });
91134
91187
  return record;
@@ -92836,6 +92889,25 @@ async function renderThumbnail(source2, sourceContentType, spec) {
92836
92889
  }
92837
92890
 
92838
92891
  // ../nodegx-backend/src/server/files.ts
92892
+ var MAX_FILE_READERS = 8;
92893
+ var READER_ATOM = /^role:[A-Za-z0-9_-]{1,64}$/;
92894
+ function parseFileReaders(raw) {
92895
+ const text2 = Array.isArray(raw) ? raw.join(",") : raw ?? "";
92896
+ const atoms = text2.split(",").map((a) => a.trim()).filter(Boolean);
92897
+ const readers = [...new Set(atoms)];
92898
+ for (const atom of readers) {
92899
+ if (!READER_ATOM.test(atom)) {
92900
+ throw new HttpError(
92901
+ 400,
92902
+ `X-NodeGX-File-Readers takes roles written role:<name>, separated by commas; "${atom}" is not one.`
92903
+ );
92904
+ }
92905
+ }
92906
+ if (readers.length > MAX_FILE_READERS) {
92907
+ throw new HttpError(400, `X-NodeGX-File-Readers names ${readers.length} roles; at most ${MAX_FILE_READERS}.`);
92908
+ }
92909
+ return readers;
92910
+ }
92839
92911
  function sanitizeName(name2) {
92840
92912
  const base = path17.basename(name2);
92841
92913
  return base.replace(/[^a-zA-Z0-9._-]/g, "_");
@@ -92864,6 +92936,17 @@ var FileRoutes = class {
92864
92936
  if (declaredLength && declaredLength > config.maxUploadBytes) {
92865
92937
  throw new HttpError(413, `Upload exceeds this backend's ${config.maxUploadBytes}-byte limit.`);
92866
92938
  }
92939
+ const readers = parseFileReaders(ctx.req.headers["x-nodegx-file-readers"]);
92940
+ if (readers.length && ctx.principal.kind !== "admin") {
92941
+ const held = new Set(principalKeys(ctx.principal));
92942
+ if (!readers.some((r) => held.has(r))) {
92943
+ throw new HttpError(
92944
+ 403,
92945
+ `You can share a file only with a role you hold, and you hold none of ${readers.join(", ")}.`,
92946
+ 119
92947
+ );
92948
+ }
92949
+ }
92867
92950
  const body = await readRawBody(ctx.req, config.maxUploadBytes);
92868
92951
  if (body.length === 0) throw new HttpError(400, "Refusing an empty upload.");
92869
92952
  const originalName = sanitizeName(ctx.params.name);
@@ -92876,7 +92959,7 @@ var FileRoutes = class {
92876
92959
  }
92877
92960
  const hash = sha256hex(body);
92878
92961
  const storedName = `${crypto15.randomBytes(8).toString("hex")}_${originalName}`;
92879
- const isPrivate2 = String(ctx.req.headers["x-nodegx-file-private"] || "").toLowerCase() === "true";
92962
+ const isPrivate2 = readers.length > 0 || String(ctx.req.headers["x-nodegx-file-private"] || "").toLowerCase() === "true";
92880
92963
  const owner = ctx.principal.kind === "user" ? ctx.principal.userId : null;
92881
92964
  const driver = this.subsystem.getDriver();
92882
92965
  const key = await driver.put(hash, body);
@@ -92890,7 +92973,8 @@ var FileRoutes = class {
92890
92973
  driver: driver.kind,
92891
92974
  key,
92892
92975
  owner,
92893
- private: isPrivate2
92976
+ private: isPrivate2,
92977
+ readers
92894
92978
  });
92895
92979
  sendJSON(ctx.res, 201, {
92896
92980
  url: `${this.baseUrl}/files/${encodeURIComponent(storedName)}`,
@@ -93542,7 +93626,7 @@ var crypto16 = __toESM(require("crypto"));
93542
93626
 
93543
93627
  // ../nodegx-backend/src/ops/headers.ts
93544
93628
  var ALLOW_METHODS = "GET, POST, PUT, DELETE, OPTIONS";
93545
- var ALLOW_HEADERS = "Content-Type, Authorization, X-Request-Id, X-Parse-Application-Id, X-Parse-Session-Token, X-Parse-Installation-Id, X-Parse-Master-Key, X-Parse-REST-API-Key, X-NodeGX-Api-Key, Last-Event-ID, X-NodeGX-File-Private, X-NodeGX-Upsert, X-NodeGX-If";
93629
+ var ALLOW_HEADERS = "Content-Type, Authorization, X-Request-Id, X-Parse-Application-Id, X-Parse-Session-Token, X-Parse-Installation-Id, X-Parse-Master-Key, X-Parse-REST-API-Key, X-NodeGX-Api-Key, Last-Event-ID, X-NodeGX-File-Private, X-NodeGX-File-Readers, X-NodeGX-Upsert, X-NodeGX-If";
93546
93630
  function allowedOrigin(requestOrigin2, config) {
93547
93631
  if (config.origins.includes("*")) {
93548
93632
  return config.credentials && requestOrigin2 ? requestOrigin2 : "*";