@kedem/okdb 1.0.1 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/bin/okdb.js +1 -1
  2. package/keys/public.pem +3 -0
  3. package/okdb.js +1 -1
  4. package/package.json +2 -2
  5. package/public/admin-app.js +1 -1
  6. package/public/components/admin-route-host.ok.js +1 -1
  7. package/public/components/env-picker.ok.js +1 -1
  8. package/public/components/field-path-input.ok.js +1 -1
  9. package/public/components/field.html +154 -2
  10. package/public/components/geo-map.ok.js +1 -1
  11. package/public/components/ok-cron-input.ok.js +1 -1
  12. package/public/components/ok-dropzone.ok.js +1 -1
  13. package/public/components/ok-js-input.ok.js +1 -1
  14. package/public/components/ok-json-input.ok.js +1 -1
  15. package/public/components/ok-text-monaco-input.ok.js +1 -1
  16. package/public/components/theme-toggle.ok.js +1 -1
  17. package/public/index.html +29 -2
  18. package/public/layouts/_default.ok.html +1 -1
  19. package/public/layouts/okdb-cube.ok.html +1 -1
  20. package/public/login-app.js +1 -1
  21. package/public/login.html +205 -1
  22. package/public/modules/ok.js +1 -1
  23. package/public/sections/api/index.ok.html +1 -1
  24. package/public/sections/api/parts/api-endpoint.ok.js +1 -1
  25. package/public/sections/auth/index.ok.html +1 -1
  26. package/public/sections/auth/modals/create-token-modal.ok.js +1 -1
  27. package/public/sections/auth/modals/edit-token-modal.ok.js +1 -1
  28. package/public/sections/auth/modals/perm-info-popover.ok.js +1 -1
  29. package/public/sections/auth/parts/token-detail.ok.js +1 -1
  30. package/public/sections/auth/parts/token-list.ok.js +1 -1
  31. package/public/sections/db/index.ok.html +1 -1
  32. package/public/sections/db/modals/add-item-modal.ok.js +1 -1
  33. package/public/sections/db/modals/create-env-modal.ok.js +1 -1
  34. package/public/sections/db/modals/create-fts-modal.ok.js +1 -1
  35. package/public/sections/db/modals/create-index-modal.ok.js +1 -1
  36. package/public/sections/db/modals/create-pipeline-modal.ok.js +1 -1
  37. package/public/sections/db/modals/demo-env-modal.ok.js +1 -1
  38. package/public/sections/db/modals/remove-env-modal.ok.js +1 -1
  39. package/public/sections/db/modals/schema-edit-modal.ok.js +1 -1
  40. package/public/sections/db/parts/db-overview.ok.js +1 -1
  41. package/public/sections/db/parts/db-pipelines-panel.ok.js +1 -1
  42. package/public/sections/db/parts/edit-row.ok.js +1 -1
  43. package/public/sections/db/parts/tm-timeline.ok.js +1 -1
  44. package/public/sections/db/parts/type-changelog.ok.js +1 -1
  45. package/public/sections/db/parts/type-list.ok.js +1 -1
  46. package/public/sections/db/parts/type-pipelines-tab.ok.js +1 -1
  47. package/public/sections/db/parts/type-schema-tab.ok.js +1 -1
  48. package/public/sections/db/parts/type-viewer.ok.js +1 -1
  49. package/public/sections/db/parts/type-violations-tab.ok.js +1 -1
  50. package/public/sections/docs/index.ok.html +1 -1
  51. package/public/sections/embeddings/index.ok.html +1 -1
  52. package/public/sections/embeddings/modals/register-model-modal.ok.js +1 -1
  53. package/public/sections/embeddings/parts/embed-create-panel.ok.js +1 -1
  54. package/public/sections/embeddings/parts/embed-embedder-panel.ok.js +1 -1
  55. package/public/sections/embeddings/parts/embed-engine-declaration-editor.ok.js +1 -1
  56. package/public/sections/embeddings/parts/embed-engine-list.ok.js +1 -1
  57. package/public/sections/embeddings/parts/embed-indexer-panel.ok.js +1 -1
  58. package/public/sections/embeddings/parts/embed-lifecycle-bar.ok.js +1 -1
  59. package/public/sections/embeddings/parts/embed-model-catalog.ok.js +1 -1
  60. package/public/sections/embeddings/parts/embed-overview.ok.js +1 -1
  61. package/public/sections/embeddings/parts/embed-search-panel.ok.js +1 -1
  62. package/public/sections/embeddings/parts/embed-worker-panel.ok.js +1 -1
  63. package/public/sections/embeddings/parts/embeddings-engine-panel.ok.js +1 -1
  64. package/public/sections/embeddings/parts/engines-overview.ok.js +1 -1
  65. package/public/sections/embeddings/parts/pipeline-create-panel.ok.js +1 -1
  66. package/public/sections/engines/engine-panel-shared-style.js +1 -1
  67. package/public/sections/engines/engine-ui-utils.js +1 -1
  68. package/public/sections/engines/index.ok.html +1 -1
  69. package/public/sections/engines/modals/engine-modal.ok.js +1 -1
  70. package/public/sections/engines/parts/engine-declaration-editor.ok.js +1 -1
  71. package/public/sections/engines/parts/engine-lifecycle-actions.ok.js +1 -1
  72. package/public/sections/engines/parts/engine-panel-shell.ok.js +1 -1
  73. package/public/sections/engines/parts/engine-resource-card.ok.js +1 -1
  74. package/public/sections/engines/parts/engine-row-item.ok.js +1 -1
  75. package/public/sections/engines/parts/generic-engine-panel.ok.js +1 -1
  76. package/public/sections/files/index.ok.html +1 -1
  77. package/public/sections/files/parts/files-blob-status.ok.js +1 -1
  78. package/public/sections/files/parts/files-collection-list.ok.js +1 -1
  79. package/public/sections/files/parts/files-collection-panel.ok.js +1 -1
  80. package/public/sections/files/parts/files-file-list.ok.js +1 -1
  81. package/public/sections/files/parts/files-overview.ok.js +1 -1
  82. package/public/sections/files/parts/files-path-browser.ok.js +1 -1
  83. package/public/sections/files/parts/files-upload-panel.ok.js +1 -1
  84. package/public/sections/functions/index.ok.html +1 -1
  85. package/public/sections/functions/modals/function-modal.ok.js +1 -1
  86. package/public/sections/functions/parts/function-detail.ok.js +1 -1
  87. package/public/sections/functions/parts/function-logs.ok.js +1 -1
  88. package/public/sections/functions/parts/function-runner.ok.js +1 -1
  89. package/public/sections/functions/parts/functions-list.ok.js +1 -1
  90. package/public/sections/license/index.ok.html +1 -1
  91. package/public/sections/license/parts/license-panel.ok.js +1 -1
  92. package/public/sections/processors/index.ok.html +1 -1
  93. package/public/sections/queue/index.ok.html +1 -1
  94. package/public/sections/queue/modals/create-bucket-modal.ok.js +1 -1
  95. package/public/sections/queue/modals/edit-bucket-modal.ok.js +1 -1
  96. package/public/sections/queue/modals/edit-job-modal.ok.js +1 -1
  97. package/public/sections/queue/modals/enqueue-job-modal.ok.js +1 -1
  98. package/public/sections/queue/modals/job-type-modal.ok.js +1 -1
  99. package/public/sections/queue/parts/ok-job-form.ok.js +1 -1
  100. package/public/sections/queue/parts/queue-buckets.ok.js +1 -1
  101. package/public/sections/queue/parts/queue-job-types.ok.js +1 -1
  102. package/public/sections/queue/parts/queue-jobs.ok.js +1 -1
  103. package/public/sections/queue/parts/queue-types.ok.js +1 -1
  104. package/public/sections/queue/parts/queue-workers.ok.js +1 -1
  105. package/public/sections/sync/index.ok.html +1 -1
  106. package/public/sections/sync/parts/sync-node-detail.ok.js +1 -1
  107. package/public/sections/sync/parts/sync-retention.ok.js +1 -1
  108. package/public/sections/sync/parts/sync-topology.ok.js +1 -1
  109. package/public/sections/system/index.ok.html +1 -1
  110. package/public/sections/system/modals/data-ops-modal.ok.js +1 -1
  111. package/public/sections/system/parts/system-api-panel.ok.js +1 -1
  112. package/public/sections/system/parts/system-auth-panel.ok.js +1 -1
  113. package/public/sections/system/parts/system-log-history.ok.js +1 -1
  114. package/public/sections/system/parts/system-log.ok.js +1 -1
  115. package/public/sections/system/parts/system-status.ok.js +1 -1
  116. package/public/sections/system/parts/system-sync-panel.ok.js +1 -1
  117. package/public/sections/views/index.ok.html +1 -1
  118. package/public/sections/views/modals/create-view-modal.ok.js +1 -1
  119. package/public/sections/views/parts/view-detail.ok.js +1 -1
  120. package/public/sections/views/parts/views-list.ok.js +1 -1
  121. package/public/sections/views/parts/views-overview.ok.js +1 -1
  122. package/public/setup-app.js +1 -1
  123. package/public/setup.html +189 -1
  124. package/public/stores/auth-store.js +1 -1
  125. package/public/stores/events-store.js +1 -1
  126. package/public/stores/functions-store.js +1 -1
  127. package/public/stores/okdb-store.js +1 -1
  128. package/public/stores/theme-store.js +1 -1
  129. package/public/test-cron-input.html +53 -3
  130. package/public/test-json-input.html +182 -3
  131. package/public/util/permissions.js +1 -1
  132. package/public/util/sift-schema.js +1 -1
  133. package/public/vendor/okjs/ok.esm.min.js +1 -2
  134. package/public/view-models/type-query-vm.js +1 -1
  135. package/okdb-data/default/data.mdb +0 -0
  136. package/okdb-data/default/lock.mdb +0 -0
  137. package/okdb-data/~log/data.mdb +0 -0
  138. package/okdb-data/~log/lock.mdb +0 -0
  139. package/okdb-data/~system/data.mdb +0 -0
  140. package/okdb-data/~system/lock.mdb +0 -0
  141. package/public/images/logo.png +0 -0
  142. package/public/images/logo.svg +0 -333
package/public/login.html CHANGED
@@ -1 +1,205 @@
1
- <!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" type="image/png" href="favicon.ico"><link rel="apple-touch-icon" sizes="180x180" href="apple-touch-icon.png"><link rel="icon" type="image/png" sizes="32x32" href="favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="favicon-16x16.png"><link rel="manifest" href="site.webmanifest"><title>OKDB Login</title><script>!function(){try{var t=localStorage.getItem("okdbTheme");"dark"!==t&&"light"!==t&&(t=window.matchMedia&&window.matchMedia("(prefers-color-scheme: light)").matches?"light":"dark"),"light"===t&&document.documentElement.setAttribute("data-theme","light")}catch(t){}}()</script><style>:root{color-scheme:dark;--bg0:#071018;--bg1:#0d1722;--bg2:#142235;--line:#27415f;--text:#e8f2ff;--muted:#9ab2cc;--accent:#4fc1ff;--accent2:#7ee787}:root[data-theme=light]{color-scheme:light;--bg0:#dce8f5;--bg1:#f0f6fc;--bg2:#ffffff;--line:#b0c8e0;--text:#0d1a28;--muted:#3a5870;--accent:#1a7ab8;--accent2:#16a34a}*{box-sizing:border-box}body{margin:0;min-height:100vh;font-family:Inter,Segoe UI,Arial,sans-serif;color:var(--text);background:radial-gradient(circle at top,#16304b 0,var(--bg0) 45%,#05090f 100%);display:flex;align-items:center;justify-content:center;padding:24px}[data-theme=light] body{background:radial-gradient(circle at top,#c8dff5 0,var(--bg0) 45%,#e8f2ff 100%)}.shell{width:min(920px,100%);display:grid;grid-template-columns:1.2fr 0.95fr;background:rgba(8,16,26,.86);border:1px solid rgba(79,193,255,.16);border-radius:22px;overflow:hidden;box-shadow:0 24px 70px rgba(0,0,0,.45);backdrop-filter:blur(10px)}.hero{padding:40px;background:linear-gradient(160deg,rgba(79,193,255,.12),rgba(126,231,135,.06));border-right:1px solid rgba(79,193,255,.1)}.card{padding:40px;display:flex;flex-direction:column;justify-content:center}.eyebrow{display:inline-flex;align-items:center;gap:8px;padding:6px 10px;border-radius:999px;background:rgba(79,193,255,.12);color:var(--accent);font-size:12px;letter-spacing:.08em;text-transform:uppercase;border:1px solid rgba(79,193,255,.18)}h1{font-size:40px;line-height:1.05;margin:18px 0 12px}p{color:var(--muted);line-height:1.65}ul{padding-left:18px;color:var(--muted);line-height:1.8}.login-box{background:linear-gradient(180deg,rgba(20,34,53,.92),rgba(12,21,32,.92));border:1px solid rgba(255,255,255,.06);border-radius:18px;padding:24px}.provider{font-size:13px;color:var(--muted);margin-bottom:12px}.stack{display:flex;flex-direction:column;gap:14px}.field{display:flex;flex-direction:column;gap:7px}.field label{font-size:12px;color:var(--muted)}.field input{width:100%;background:#0b1420;color:var(--text);border:1px solid rgba(255,255,255,.08);border-radius:12px;padding:13px 14px;outline:0;font-size:14px}.field input:focus{border-color:rgba(79,193,255,.45);box-shadow:0 0 0 3px rgba(79,193,255,.12)}.muted-btn{display:inline-flex;align-items:center;justify-content:center;gap:12px;width:100%;text-decoration:none;color:var(--text);font-weight:700;font-size:15px;border-radius:12px;padding:14px 16px;background:#162434;border:1px solid rgba(255,255,255,.08)}.divider{display:flex;align-items:center;gap:12px;color:var(--muted);font-size:12px;text-transform:uppercase;letter-spacing:.08em}.divider::after,.divider::before{content:'';height:1px;background:rgba(255,255,255,.08);flex:1}.button{display:inline-flex;align-items:center;justify-content:center;gap:12px;width:100%;text-decoration:none;color:#04111f;font-weight:700;font-size:15px;border-radius:12px;padding:14px 16px;background:linear-gradient(135deg,var(--accent),#8fdcff);box-shadow:0 10px 30px rgba(79,193,255,.22)}.button:hover{filter:brightness(1.04)}.note{margin-top:16px;font-size:12px;color:var(--muted)}.error{margin-top:14px;padding:10px 12px;border-radius:10px;background:rgba(248,113,113,.12);border:1px solid rgba(248,113,113,.25);color:#fecaca;font-size:13px}.picker-title{font-size:13px;color:var(--muted);margin-bottom:12px}.picker-list{display:flex;flex-direction:column;gap:8px}.picker-item{display:flex;flex-direction:column;gap:4px;padding:12px 14px;border-radius:12px;background:#0b1420;border:1px solid rgba(255,255,255,.08);cursor:pointer;transition:border-color .15s}.picker-item:hover{border-color:rgba(79,193,255,.45)}.picker-item-label{font-size:14px;color:var(--text);font-weight:600}.picker-item-meta{font-size:12px;color:var(--muted)}@media (max-width:820px){.shell{grid-template-columns:1fr}.hero{border-right:none;border-bottom:1px solid rgba(79,193,255,.1)}}[data-theme=light] .shell{background:rgba(255,255,255,.92);border-color:rgba(26,122,184,.2)}[data-theme=light] .login-box{background:var(--bg1)}[data-theme=light] .field input,[data-theme=light] .muted-btn,[data-theme=light] .picker-item{background:var(--bg2);border-color:var(--line)}[data-theme=light] .hero{background:linear-gradient(160deg,rgba(26,122,184,.08),rgba(22,163,74,.04))}</style></head><body><main class="shell"><section class="hero"><div class="eyebrow">OKDB Admin</div><h1>Secure access to your database control plane.</h1><p>Sign in to manage environments, inspect system state, and operate OKDB securely.</p><ul><li>Session-based browser access for the admin UI</li><li>Access-token auth for API and MCP clients</li><li>Centralized authorization based on your provider claims</li></ul></section><section class="card"><div class="login-box"><div class="stack" id="loginStack" hidden><form id="basicForm" hidden><div class="provider">Username and password</div><div class="stack"><div class="field"><label for="user">User</label> <input id="user" name="user" autocomplete="username" placeholder="admin"></div><div class="field"><label for="password">Password</label> <input id="password" name="password" type="password" autocomplete="current-password" placeholder="••••••••"></div><button class="button" type="submit">Login</button></div></form><div class="divider" id="divider" hidden>or</div><div id="oauthWrap" hidden><div class="provider" id="providerLabel">Use a provider</div><a class="muted-btn" id="loginButton" href="/admin/auth/start?redirect=%2Fadmin%2Findex.html"><span id="providerButtonLabel">Use a provider</span></a></div></div><div id="pickerSection" hidden><div class="picker-title">Multiple accounts found. Choose one to continue:</div><div class="picker-list" id="pickerList"></div></div><div class="note" id="noteBox">API and MCP clients should use OAuth access tokens directly.</div><div class="error" id="errorBox" hidden></div></div></section></main><script type="module" src="login-app.js"></script></body></html>
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <link rel="icon" type="image/png" href="favicon.ico">
7
+ <link rel="apple-touch-icon" sizes="180x180" href="apple-touch-icon.png">
8
+ <link rel="icon" type="image/png" sizes="32x32" href="favicon-32x32.png">
9
+ <link rel="icon" type="image/png" sizes="16x16" href="favicon-16x16.png">
10
+ <link rel="manifest" href="site.webmanifest">
11
+ <title>OKDB Login</title>
12
+ <script>
13
+ (function () {
14
+ try {
15
+ var saved = localStorage.getItem('okdbTheme');
16
+ if (saved !== 'dark' && saved !== 'light') {
17
+ saved = window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
18
+ }
19
+ if (saved === 'light') document.documentElement.setAttribute('data-theme', 'light');
20
+ } catch (e) { /* localStorage blocked — default to dark */ }
21
+ })();
22
+ </script>
23
+ <style>
24
+ :root {
25
+ color-scheme: dark;
26
+ --bg0:#071018;
27
+ --bg1:#0d1722;
28
+ --bg2:#142235;
29
+ --line:#27415f;
30
+ --text:#e8f2ff;
31
+ --muted:#9ab2cc;
32
+ --accent:#4fc1ff;
33
+ --accent2:#7ee787;
34
+ }
35
+ :root[data-theme='light'] {
36
+ color-scheme: light;
37
+ --bg0:#dce8f5;
38
+ --bg1:#f0f6fc;
39
+ --bg2:#ffffff;
40
+ --line:#b0c8e0;
41
+ --text:#0d1a28;
42
+ --muted:#3a5870;
43
+ --accent:#1a7ab8;
44
+ --accent2:#16a34a;
45
+ }
46
+ * { box-sizing:border-box; }
47
+ body {
48
+ margin:0;
49
+ min-height:100vh;
50
+ font-family: Inter, Segoe UI, Arial, sans-serif;
51
+ color:var(--text);
52
+ background: radial-gradient(circle at top, #16304b 0%, var(--bg0) 45%, #05090f 100%);
53
+ display:flex;
54
+ align-items:center;
55
+ justify-content:center;
56
+ padding:24px;
57
+ }
58
+ [data-theme='light'] body {
59
+ background: radial-gradient(circle at top, #c8dff5 0%, var(--bg0) 45%, #e8f2ff 100%);
60
+ }
61
+ .shell {
62
+ width:min(920px, 100%);
63
+ display:grid;
64
+ grid-template-columns: 1.2fr 0.95fr;
65
+ background:rgba(8,16,26,.86);
66
+ border:1px solid rgba(79,193,255,.16);
67
+ border-radius:22px;
68
+ overflow:hidden;
69
+ box-shadow:0 24px 70px rgba(0,0,0,.45);
70
+ backdrop-filter: blur(10px);
71
+ }
72
+ .hero {
73
+ padding:40px;
74
+ background:linear-gradient(160deg, rgba(79,193,255,.12), rgba(126,231,135,.06));
75
+ border-right:1px solid rgba(79,193,255,.1);
76
+ }
77
+ .card { padding:40px; display:flex; flex-direction:column; justify-content:center; }
78
+ .eyebrow {
79
+ display:inline-flex; align-items:center; gap:8px; padding:6px 10px; border-radius:999px;
80
+ background:rgba(79,193,255,.12); color:var(--accent); font-size:12px; letter-spacing:.08em; text-transform:uppercase;
81
+ border:1px solid rgba(79,193,255,.18);
82
+ }
83
+ h1 { font-size:40px; line-height:1.05; margin:18px 0 12px; }
84
+ p { color:var(--muted); line-height:1.65; }
85
+ ul { padding-left:18px; color:var(--muted); line-height:1.8; }
86
+ .login-box {
87
+ background:linear-gradient(180deg, rgba(20,34,53,.92), rgba(12,21,32,.92));
88
+ border:1px solid rgba(255,255,255,.06);
89
+ border-radius:18px;
90
+ padding:24px;
91
+ }
92
+ .provider { font-size:13px; color:var(--muted); margin-bottom:12px; }
93
+ .stack { display:flex; flex-direction:column; gap:14px; }
94
+ .field { display:flex; flex-direction:column; gap:7px; }
95
+ .field label { font-size:12px; color:var(--muted); }
96
+ .field input {
97
+ width:100%; background:#0b1420; color:var(--text); border:1px solid rgba(255,255,255,.08);
98
+ border-radius:12px; padding:13px 14px; outline:none; font-size:14px;
99
+ }
100
+ .field input:focus { border-color: rgba(79,193,255,.45); box-shadow:0 0 0 3px rgba(79,193,255,.12); }
101
+ .muted-btn {
102
+ display:inline-flex; align-items:center; justify-content:center; gap:12px;
103
+ width:100%; text-decoration:none; color:var(--text); font-weight:700; font-size:15px;
104
+ border-radius:12px; padding:14px 16px; background:#162434; border:1px solid rgba(255,255,255,.08);
105
+ }
106
+ .divider {
107
+ display:flex; align-items:center; gap:12px; color:var(--muted); font-size:12px; text-transform:uppercase; letter-spacing:.08em;
108
+ }
109
+ .divider::before, .divider::after {
110
+ content:''; height:1px; background:rgba(255,255,255,.08); flex:1;
111
+ }
112
+ .button {
113
+ display:inline-flex; align-items:center; justify-content:center; gap:12px;
114
+ width:100%; text-decoration:none; color:#04111f; font-weight:700; font-size:15px;
115
+ border-radius:12px; padding:14px 16px; background:linear-gradient(135deg, var(--accent), #8fdcff);
116
+ box-shadow:0 10px 30px rgba(79,193,255,.22);
117
+ }
118
+ .button:hover { filter:brightness(1.04); }
119
+ .note { margin-top:16px; font-size:12px; color:var(--muted); }
120
+ .error {
121
+ margin-top:14px; padding:10px 12px; border-radius:10px;
122
+ background:rgba(248,113,113,.12); border:1px solid rgba(248,113,113,.25); color:#fecaca;
123
+ font-size:13px;
124
+ }
125
+ .picker-title { font-size:13px; color:var(--muted); margin-bottom:12px; }
126
+ .picker-list { display:flex; flex-direction:column; gap:8px; }
127
+ .picker-item {
128
+ display:flex; flex-direction:column; gap:4px; padding:12px 14px; border-radius:12px;
129
+ background:#0b1420; border:1px solid rgba(255,255,255,.08); cursor:pointer;
130
+ transition:border-color .15s;
131
+ }
132
+ .picker-item:hover { border-color:rgba(79,193,255,.45); }
133
+ .picker-item-label { font-size:14px; color:var(--text); font-weight:600; }
134
+ .picker-item-meta { font-size:12px; color:var(--muted); }
135
+ @media (max-width: 820px) {
136
+ .shell { grid-template-columns: 1fr; }
137
+ .hero { border-right:none; border-bottom:1px solid rgba(79,193,255,.1); }
138
+ }
139
+ [data-theme='light'] .shell {
140
+ background: rgba(255,255,255,.92);
141
+ border-color: rgba(26,122,184,.2);
142
+ }
143
+ [data-theme='light'] .login-box { background: var(--bg1); }
144
+ [data-theme='light'] .field input,
145
+ [data-theme='light'] .muted-btn,
146
+ [data-theme='light'] .picker-item { background: var(--bg2); border-color: var(--line); }
147
+ [data-theme='light'] .hero { background: linear-gradient(160deg, rgba(26,122,184,.08), rgba(22,163,74,.04)); }
148
+ </style>
149
+ </head>
150
+ <body>
151
+ <main class="shell">
152
+ <section class="hero">
153
+ <div class="eyebrow">OKDB Admin</div>
154
+ <h1>Secure access to your database control plane.</h1>
155
+ <p>
156
+ Sign in to manage environments, inspect system state, and operate OKDB securely.
157
+ </p>
158
+ <ul>
159
+ <li>Session-based browser access for the admin UI</li>
160
+ <li>Access-token auth for API and MCP clients</li>
161
+ <li>Centralized authorization based on your provider claims</li>
162
+ </ul>
163
+ </section>
164
+ <section class="card">
165
+ <div class="login-box">
166
+ <div class="stack" id="loginStack" hidden>
167
+ <form id="basicForm" hidden>
168
+ <div class="provider">Username and password</div>
169
+ <div class="stack">
170
+ <div class="field">
171
+ <label for="user">User</label>
172
+ <input id="user" name="user" autocomplete="username" placeholder="admin">
173
+ </div>
174
+ <div class="field">
175
+ <label for="password">Password</label>
176
+ <input id="password" name="password" type="password" autocomplete="current-password" placeholder="••••••••">
177
+ </div>
178
+ <button class="button" type="submit">Login</button>
179
+ </div>
180
+ </form>
181
+
182
+ <div class="divider" id="divider" hidden>or</div>
183
+
184
+ <div id="oauthWrap" hidden>
185
+ <div class="provider" id="providerLabel">Use a provider</div>
186
+ <a class="muted-btn" id="loginButton" href="/admin/auth/start?redirect=%2Fadmin%2Findex.html">
187
+ <span id="providerButtonLabel">Use a provider</span>
188
+ </a>
189
+ </div>
190
+ </div>
191
+
192
+ <div id="pickerSection" hidden>
193
+ <div class="picker-title">Multiple accounts found. Choose one to continue:</div>
194
+ <div class="picker-list" id="pickerList"></div>
195
+ </div>
196
+
197
+ <div class="note" id="noteBox">API and MCP clients should use OAuth access tokens directly.</div>
198
+ <div class="error" id="errorBox" hidden></div>
199
+ </div>
200
+ </section>
201
+ </main>
202
+ <script type="module" src="login-app.js"></script>
203
+ </body>
204
+ </html>
205
+
@@ -1 +1 @@
1
- const _0x4fad61=_0x18c3;(function(_0x53c2c7,_0x1ed713){const _0x47820f=_0x18c3,_0x5a7b0c=_0x53c2c7();while(!![]){try{const _0xc1fd56=parseInt(_0x47820f(0xb3))/0x1*(parseInt(_0x47820f(0xaf))/0x2)+-parseInt(_0x47820f(0xad))/0x3+-parseInt(_0x47820f(0xb4))/0x4*(-parseInt(_0x47820f(0xb0))/0x5)+-parseInt(_0x47820f(0xb1))/0x6+-parseInt(_0x47820f(0xa9))/0x7+parseInt(_0x47820f(0xa8))/0x8+-parseInt(_0x47820f(0xac))/0x9*(-parseInt(_0x47820f(0xaa))/0xa);if(_0xc1fd56===_0x1ed713)break;else _0x5a7b0c['push'](_0x5a7b0c['shift']());}catch(_0x3f6784){_0x5a7b0c['push'](_0x5a7b0c['shift']());}}}(_0x5c54,0x7c9c2));function _0x18c3(_0x37bc43,_0x341fe6){_0x37bc43=_0x37bc43-0xa8;const _0x5c541c=_0x5c54();let _0x18c308=_0x5c541c[_0x37bc43];return _0x18c308;}import _0x281a2b from'@okjs';import _0xf04f8 from'@toolkit/navigation/hash-router.ok.js';import _0x170ad0 from'@toolkit/errors/ok-crash-info.ok.js';function _0x5c54(){const _0x32b4ef=['65915plhmIa','5956422BpNQdN','register','675UAeECX','308DoiFEP','2017192GHbUBk','3351992DSCzuP','430pGUpIl','dev','83601FqZLHc','872466hMrRVl','forEach','1796AsVXCc'];_0x5c54=function(){return _0x32b4ef;};return _0x5c54();}import _0xd727d1 from'@toolkit/elements/ok-icon.ok.js';import _0xa59d2a from'@toolkit/elements/ok-table.ok.js';import _0x2ad1e3 from'@toolkit/core/ok-overlay.ok.js';import _0x38adac from'@toolkit/modals/ok-modal-service.ok.js';import _0x260dd8 from'@toolkit/modals/ok-toast-service.ok.js';import _0x1c8fd8 from'../components/admin-route-host.ok.js';import _0x1f0217 from'../components/theme-toggle.ok.js';export const ok=_0x281a2b({'env':_0x4fad61(0xab)});[_0x170ad0,_0xf04f8,_0xd727d1,_0xa59d2a,_0x2ad1e3,_0x38adac,_0x260dd8,_0x1c8fd8,_0x1f0217][_0x4fad61(0xae)](_0x4fa25d=>ok[_0x4fad61(0xb2)](_0x4fa25d));
1
+ import o from"@okjs";import t from"@toolkit/navigation/hash-router.ok.js";import r from"@toolkit/errors/ok-crash-info.ok.js";import m from"@toolkit/elements/ok-icon.ok.js";import e from"@toolkit/elements/ok-table.ok.js";import i from"@toolkit/core/ok-overlay.ok.js";import k from"@toolkit/modals/ok-modal-service.ok.js";import s from"@toolkit/modals/ok-toast-service.ok.js";import l from"../components/admin-route-host.ok.js";import n from"../components/theme-toggle.ok.js";export const ok=o({env:"dev"});[r,t,m,e,i,k,s,l,n].forEach(o=>ok.register(o));
@@ -1 +1 @@
1
- <script type="module">import okIcon from"@toolkit/elements/ok-icon.ok.js";import apiEndpoint from"./parts/api-endpoint.ok.js";export default{tag:"api-index",unwrap:!0,dependencies:[apiEndpoint,okIcon],context:e=>({routes:[],loading:!1,error:null,selected:null,search:"",tagOrder:["system","types","data","changelog","indexes","fts","queue","pipelines","sync","embeddings","envs","docs"],async load(){this.loading=!0,this.error=null;try{const e=await fetch(__ENV__.API+"/docs"),t=await e.json();if(!e.ok)throw new Error(t?.error?.message||"Failed to load routes");this.routes=t.data??[]}catch(e){this.error=e?.message||String(e)}finally{this.loading=!1}},get filtered(){const e=this.search.trim().toLowerCase();return e?this.routes.filter(t=>t.path.toLowerCase().includes(e)||t.method.toLowerCase().includes(e)||(t.meta?.summary||"").toLowerCase().includes(e)||(t.meta?.tags||[]).some(t=>t.includes(e))):this.routes},get groups(){const e=new Map;for(const t of this.filtered){const o=t.meta?.tags?.[0]??"other";e.has(o)||e.set(o,[]),e.get(o).push(t)}return[...e.entries()].sort(([e],[t])=>{const o=this.tagOrder.indexOf(e),s=this.tagOrder.indexOf(t);return-1===o&&-1===s?e.localeCompare(t):-1===o?1:-1===s?-1:o-s}).map(([e,t])=>({tag:e,routes:t}))},methodColor:e=>({GET:"var(--okdb-accent)",POST:"var(--okdb-success)",PUT:"var(--okdb-warning)",PATCH:"var(--okdb-accent-2)",DELETE:"var(--okdb-danger)"}[e]||"var(--okdb-method-default)"),select(e){this.selected!==e&&(this.selected=null,setTimeout(()=>{this.selected=e},0))},shortPath:e=>e.replace(/^\/api/,"").replace(/\[[^\]]*\]/g,""),optionalHint(e){const t=e.match(/\[([^\]]*)\]/);return t?t[1].replace(/=\w+/g,""):null}}),init(){this.load()}}</script><template><component import="../../layouts/_default.ok.html"><template name="header"></template><template name="sidebar"><div style="position:relative;flex-shrink:0"><input type="text" placeholder="Filter routes…" !value="search" @input="search = $el.value" style="width:100%;box-sizing:border-box;background:var(--okdb-app-bg-deep);border:1px solid var(--okdb-app-border);border-radius:7px;padding:6px 10px;color:var(--okdb-app-fg);font-size:12px;outline:0"/></div><if :="loading"><div class="loader-center" style="flex:1"><ok-icon name="loader" class="spin" style="width:24px;height:24px"></ok-icon></div></if><if :="error"><div style="font-size:12px;color:var(--okdb-danger);padding:8px">{{ error }}</div></if><div style="flex:1;min-height:0;display:flex;flex-direction:column;gap:12px;overflow-y:auto;padding-bottom:10px"><each :of="groups" let:item="g"><div><div style="font-size:10px;text-transform:uppercase;letter-spacing:.07em;opacity:.38;margin-bottom:4px;padding:0 2px">{{ g.tag }}</div><div style="display:flex;flex-direction:column;gap:1px"><each :of="g.routes" let:item="r"><button :class="['api-route-btn', selected === r ? 'api-route-btn--active' : '']" @click="select(r)" :title="r.path"><span :style="'font-size:10px;font-weight:700;width:38px;flex-shrink:0;text-align:center;color:'+methodColor(r.method)+';'">{{ r.method }} </span><span class="api-route-path"><if :="optionalHint(r.path)"><span style="opacity:.3">{{ optionalHint(r.path) }}</span></if>{{ shortPath(r.path) }}</span></button></each></div></div></each><if :="!loading && routes.length === 0 && !error"><div style="font-size:12px;opacity:.4;padding:8px">No routes loaded.</div></if></div><if :="routes.length > 0"><div style="font-size:11px;opacity:.35;padding-top:8px;border-top:1px solid var(--okdb-app-surface-2);flex-shrink:0">{{ filtered.length }} / {{ routes.length }} routes</div></if></template><template name="default"><if :="!selected"><div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;opacity:.4;gap:12px;user-select:none"><div style="font-size:32px">⌁</div><div style="font-size:14px;font-weight:600">Select a route to explore</div><div style="font-size:12px;opacity:.7">{{ routes.length }} routes · routes with <span style="opacity:.5">[/env/:env]</span> accept an optional environment prefix</div></div></if><if :="selected"><api-endpoint :endpoint="selected" style="height:100%"></api-endpoint></if></template></component></template><style>.api-route-btn{display:flex;align-items:center;gap:6px;padding:4px 6px;background:0 0;border:1px solid transparent;border-radius:6px;cursor:pointer;width:100%;text-align:left;transition:background .1s;min-width:0}.api-route-btn:hover{background:var(--okdb-app-surface);border-color:var(--okdb-app-border)}.api-route-btn--active{background:var(--okdb-accent-soft-bg)!important;border-color:color-mix(in srgb,var(--okdb-accent) 20%,transparent)!important}.api-route-path{font-family:monospace;font-size:11px;color:var(--okdb-app-fg-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0}.api-route-btn--active .api-route-path{color:var(--okdb-app-fg)}</style>
1
+ <script type="module">import e from"@toolkit/elements/ok-icon.ok.js";import t from"./parts/api-endpoint.ok.js";export default{tag:"api-index",unwrap:!0,dependencies:[t,e],context:e=>({routes:[],loading:!1,error:null,selected:null,search:"",tagOrder:["system","types","data","changelog","indexes","fts","queue","pipelines","sync","embeddings","envs","docs"],async load(){this.loading=!0,this.error=null;try{const e=await fetch(__ENV__.API+"/docs"),t=await e.json();if(!e.ok)throw new Error(t?.error?.message||"Failed to load routes");this.routes=t.data??[]}catch(e){this.error=e?.message||String(e)}finally{this.loading=!1}},get filtered(){const e=this.search.trim().toLowerCase();return e?this.routes.filter(t=>t.path.toLowerCase().includes(e)||t.method.toLowerCase().includes(e)||(t.meta?.summary||"").toLowerCase().includes(e)||(t.meta?.tags||[]).some(t=>t.includes(e))):this.routes},get groups(){const e=new Map;for(const t of this.filtered){const s=t.meta?.tags?.[0]??"other";e.has(s)||e.set(s,[]),e.get(s).push(t)}return[...e.entries()].sort(([e],[t])=>{const s=this.tagOrder.indexOf(e),o=this.tagOrder.indexOf(t);return-1===s&&-1===o?e.localeCompare(t):-1===s?1:-1===o?-1:s-o}).map(([e,t])=>({tag:e,routes:t}))},methodColor:e=>({GET:"var(--okdb-accent)",POST:"var(--okdb-success)",PUT:"var(--okdb-warning)",PATCH:"var(--okdb-accent-2)",DELETE:"var(--okdb-danger)"}[e]||"var(--okdb-method-default)"),select(e){this.selected!==e&&(this.selected=null,setTimeout(()=>{this.selected=e},0))},shortPath:e=>e.replace(/^\/api/,"").replace(/\[[^\]]*\]/g,""),optionalHint(e){const t=e.match(/\[([^\]]*)\]/);return t?t[1].replace(/=\w+/g,""):null}}),init(){this.load()}};</script><template><component import="../../layouts/_default.ok.html"><template name="header"></template><template name="sidebar"><div style="position:relative;flex-shrink:0"><input type="text" placeholder="Filter routes…" !value="search" @input="search = $el.value" style="width:100%;box-sizing:border-box;background:var(--okdb-app-bg-deep);border:1px solid var(--okdb-app-border);border-radius:7px;padding:6px 10px;color:var(--okdb-app-fg);font-size:12px;outline:0"/></div><if :="loading"><div class="loader-center" style="flex:1"><ok-icon name="loader" class="spin" style="width:24px;height:24px"></ok-icon></div></if><if :="error"><div style="font-size:12px;color:var(--okdb-danger);padding:8px">{{ error }}</div></if><div style="flex:1;min-height:0;display:flex;flex-direction:column;gap:12px;overflow-y:auto;padding-bottom:10px"><each :of="groups" let:item="g"><div><div style="font-size:10px;text-transform:uppercase;letter-spacing:.07em;opacity:.38;margin-bottom:4px;padding:0 2px">{{ g.tag }}</div><div style="display:flex;flex-direction:column;gap:1px"><each :of="g.routes" let:item="r"><button :class="['api-route-btn', selected === r ? 'api-route-btn--active' : '']" @click="select(r)" :title="r.path"><span :style="'font-size:10px;font-weight:700;width:38px;flex-shrink:0;text-align:center;color:'+methodColor(r.method)+';'">{{ r.method }} </span><span class="api-route-path"><if :="optionalHint(r.path)"><span style="opacity:.3">{{ optionalHint(r.path) }}</span></if>{{ shortPath(r.path) }}</span></button></each></div></div></each><if :="!loading && routes.length === 0 && !error"><div style="font-size:12px;opacity:.4;padding:8px">No routes loaded.</div></if></div><if :="routes.length > 0"><div style="font-size:11px;opacity:.35;padding-top:8px;border-top:1px solid var(--okdb-app-surface-2);flex-shrink:0">{{ filtered.length }} / {{ routes.length }} routes</div></if></template><template name="default"><if :="!selected"><div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;opacity:.4;gap:12px;user-select:none"><div style="font-size:32px">⌁</div><div style="font-size:14px;font-weight:600">Select a route to explore</div><div style="font-size:12px;opacity:.7">{{ routes.length }} routes · routes with <span style="opacity:.5">[/env/:env]</span> accept an optional environment prefix</div></div></if><if :="selected"><api-endpoint :endpoint="selected" style="height:100%"></api-endpoint></if></template></component></template><style>.api-route-btn{display:flex;align-items:center;gap:6px;padding:4px 6px;background:0 0;border:1px solid transparent;border-radius:6px;cursor:pointer;width:100%;text-align:left;transition:background .1s;min-width:0}.api-route-btn:hover{background:var(--okdb-app-surface);border-color:var(--okdb-app-border)}.api-route-btn--active{background:var(--okdb-accent-soft-bg)!important;border-color:color-mix(in srgb,var(--okdb-accent) 20%,transparent)!important}.api-route-path{font-family:monospace;font-size:11px;color:var(--okdb-app-fg-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0}.api-route-btn--active .api-route-path{color:var(--okdb-app-fg)}</style>
@@ -1 +1 @@
1
- function _0x20b2(_0x51ac3a,_0xbe1e8e){_0x51ac3a=_0x51ac3a-0xb4;const _0x472291=_0x4722();let _0x20b258=_0x472291[_0x51ac3a];return _0x20b258;}function _0x4722(){const _0x3cd5da=['4ykgvfw','6BrOgmF','trim','63496FcwakP','257290FrbrEH','var(--okdb-accent-2)','path','buildUrl','time','includes','join','endpoint','PUT','queryValues','DELETE','now','text','entries','matchAll','object','\x0a\x20\x20\x20\x20\x20\x20api-endpoint\x20.ae-section-label\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20font-size:\x2010px;\x0a\x20\x20\x20\x20\x20\x20\x20\x20text-transform:\x20uppercase;\x0a\x20\x20\x20\x20\x20\x20\x20\x20letter-spacing:\x20.07em;\x0a\x20\x20\x20\x20\x20\x20\x20\x20opacity:\x20.45;\x0a\x20\x20\x20\x20\x20\x20\x20\x20margin-bottom:\x206px;\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20api-endpoint\x20.ae-input\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20background:\x20var(--okdb-app-bg-deep);\x0a\x20\x20\x20\x20\x20\x20\x20\x20border:\x201px\x20solid\x20var(--okdb-app-border);\x0a\x20\x20\x20\x20\x20\x20\x20\x20border-radius:\x206px;\x0a\x20\x20\x20\x20\x20\x20\x20\x20padding:\x205px\x2010px;\x0a\x20\x20\x20\x20\x20\x20\x20\x20color:\x20var(--okdb-app-fg);\x0a\x20\x20\x20\x20\x20\x20\x20\x20font-size:\x2013px;\x0a\x20\x20\x20\x20\x20\x20\x20\x20font-family:\x20monospace;\x0a\x20\x20\x20\x20\x20\x20\x20\x20outline:\x20none;\x0a\x20\x20\x20\x20\x20\x20\x20\x20transition:\x20border-color\x20.12s;\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20api-endpoint\x20.ae-input:focus\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20border-color:\x20var(--okdb-accent-border);\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20api-endpoint\x20.ae-textarea\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20background:\x20var(--okdb-app-bg-deep);\x0a\x20\x20\x20\x20\x20\x20\x20\x20border:\x201px\x20solid\x20var(--okdb-app-border);\x0a\x20\x20\x20\x20\x20\x20\x20\x20border-radius:\x208px;\x0a\x20\x20\x20\x20\x20\x20\x20\x20padding:\x2010px\x2012px;\x0a\x20\x20\x20\x20\x20\x20\x20\x20color:\x20var(--okdb-app-fg);\x0a\x20\x20\x20\x20\x20\x20\x20\x20font-size:\x2012px;\x0a\x20\x20\x20\x20\x20\x20\x20\x20font-family:\x20\x27Cascadia\x20Code\x27,\x20\x27Fira\x20Mono\x27,\x20monospace;\x0a\x20\x20\x20\x20\x20\x20\x20\x20resize:\x20vertical;\x0a\x20\x20\x20\x20\x20\x20\x20\x20outline:\x20none;\x0a\x20\x20\x20\x20\x20\x20\x20\x20line-height:\x201.6;\x0a\x20\x20\x20\x20\x20\x20\x20\x20transition:\x20border-color\x20.12s;\x0a\x20\x20\x20\x20\x20\x20\x20\x20width:\x20100%;\x0a\x20\x20\x20\x20\x20\x20\x20\x20box-sizing:\x20border-box;\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20api-endpoint\x20.ae-textarea:focus\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20border-color:\x20var(--okdb-accent-soft-border);\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20api-endpoint\x20.ae-response\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20background:\x20var(--okdb-app-bg-deep);\x0a\x20\x20\x20\x20\x20\x20\x20\x20border:\x201px\x20solid\x20var(--okdb-app-surface-2);\x0a\x20\x20\x20\x20\x20\x20\x20\x20border-radius:\x208px;\x0a\x20\x20\x20\x20\x20\x20\x20\x20padding:\x2012px\x2014px;\x0a\x20\x20\x20\x20\x20\x20\x20\x20font-size:\x2012px;\x0a\x20\x20\x20\x20\x20\x20\x20\x20font-family:\x20\x27Cascadia\x20Code\x27,\x20\x27Fira\x20Mono\x27,\x20monospace;\x0a\x20\x20\x20\x20\x20\x20\x20\x20color:\x20#88c8b0;\x0a\x20\x20\x20\x20\x20\x20\x20\x20overflow-x:\x20auto;\x0a\x20\x20\x20\x20\x20\x20\x20\x20white-space:\x20pre-wrap;\x0a\x20\x20\x20\x20\x20\x20\x20\x20word-break:\x20break-all;\x0a\x20\x20\x20\x20\x20\x20\x20\x20margin:\x200;\x0a\x20\x20\x20\x20\x20\x20\x20\x20max-height:\x20400px;\x0a\x20\x20\x20\x20\x20\x20\x20\x20overflow-y:\x20auto;\x0a\x20\x20\x20\x20\x20\x20\x20\x20line-height:\x201.55;\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20','map','meta','body','filter','1523630rqEQWk','running','method','964128FhlaEQ','333265ZQFGOW','match','status','API','message','stringify','var(--okdb-success)','$watch','paramValues','var(--okdb-method-default)','POST','response','json','1201711cQEhvI','\x0a<div\x20style=\x22display:flex;\x20flex-direction:column;\x20gap:0;\x20height:100%;\x22>\x0a\x0a\x20\x20<!--\x20Method\x20+\x20path\x20header\x20-->\x0a\x20\x20<div\x20style=\x22display:flex;\x20align-items:center;\x20gap:12px;\x20padding:18px\x2020px\x2014px;\x20flex-shrink:0;\x20border-bottom:1px\x20solid\x20var(--okdb-app-surface-2);\x22>\x0a\x20\x20\x20\x20<span\x20:style=\x22\x27font-size:11px;\x20font-weight:700;\x20padding:3px\x2010px;\x20border-radius:5px;\x20border:1px\x20solid;\x20color:\x27+methodColor+\x27;\x20border-color:color-mix(in\x20srgb,\x20\x27+methodColor+\x27\x2033%,\x20transparent);\x20background:color-mix(in\x20srgb,\x20\x27+methodColor+\x27\x207%,\x20transparent);\x20letter-spacing:.06em;\x27\x22>\x0a\x20\x20\x20\x20\x20\x20{{\x20ep?.method\x20}}\x0a\x20\x20\x20\x20</span>\x0a\x20\x20\x20\x20<code\x20style=\x22font-family:monospace;\x20font-size:14px;\x20color:var(--okdb-app-fg);\x20word-break:break-all;\x22>{{\x20pathParts.before\x20}}<if\x20:=\x22pathParts.optional\x22><span\x20style=\x22opacity:.35;\x22>{{\x20pathParts.optional\x20}}</span></if>{{\x20pathParts.after\x20}}</code>\x0a\x20\x20</div>\x0a\x0a\x20\x20<!--\x20Scrollable\x20form\x20area\x20-->\x0a\x20\x20<div\x20style=\x22flex:1;\x20min-height:0;\x20overflow-y:auto;\x20padding:18px\x2020px;\x20display:flex;\x20flex-direction:column;\x20gap:18px;\x22>\x0a\x0a\x20\x20\x20\x20<!--\x20Summary\x20+\x20description\x20-->\x0a\x20\x20\x20\x20<if\x20:=\x22ep?.meta?.summary\x20||\x20ep?.meta?.description\x22>\x0a\x20\x20\x20\x20\x20\x20<div\x20style=\x22display:flex;\x20flex-direction:column;\x20gap:4px;\x22>\x0a\x20\x20\x20\x20\x20\x20\x20\x20<if\x20:=\x22ep?.meta?.summary\x22>\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20<div\x20style=\x22font-weight:600;\x20font-size:14px;\x20color:var(--okdb-app-fg-strong);\x22>{{\x20ep.meta.summary\x20}}</div>\x0a\x20\x20\x20\x20\x20\x20\x20\x20</if>\x0a\x20\x20\x20\x20\x20\x20\x20\x20<if\x20:=\x22ep?.meta?.description\x22>\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20<div\x20style=\x22font-size:13px;\x20opacity:.65;\x20line-height:1.6;\x22>{{\x20ep.meta.description\x20}}</div>\x0a\x20\x20\x20\x20\x20\x20\x20\x20</if>\x0a\x20\x20\x20\x20\x20\x20</div>\x0a\x20\x20\x20\x20</if>\x0a\x0a\x20\x20\x20\x20<!--\x20Path\x20params\x20-->\x0a\x20\x20\x20\x20<if\x20:=\x22pathParams.length\x20>\x200\x22>\x0a\x20\x20\x20\x20\x20\x20<div>\x0a\x20\x20\x20\x20\x20\x20\x20\x20<div\x20class=\x22ae-section-label\x22>Path\x20Parameters</div>\x0a\x20\x20\x20\x20\x20\x20\x20\x20<div\x20style=\x22display:flex;\x20flex-direction:column;\x20gap:6px;\x22>\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20<each\x20:of=\x22pathParams\x22\x20let:item=\x22p\x22>\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20<div\x20style=\x22display:flex;\x20align-items:center;\x20gap:10px;\x22>\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20<code\x20style=\x22font-family:monospace;\x20font-size:12px;\x20color:var(--okdb-accent);\x20width:120px;\x20flex-shrink:0;\x22>:{{\x20p\x20}}</code>\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20<if\x20:=\x22ep?.meta?.params?.[p]\x22>\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20<span\x20style=\x22font-size:11px;\x20opacity:.5;\x20flex-shrink:0;\x22>{{\x20ep.meta.params[p]\x20}}</span>\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20</if>\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20<input\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20type=\x22text\x22\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20class=\x22ae-input\x22\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20:placeholder=\x22p\x22\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20:value=\x22paramValues[p]\x20||\x20\x27\x27\x22\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20@input=\x22setParam(p,\x20$el.value)\x22\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20style=\x22flex:1;\x22\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20/>\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20</div>\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20</each>\x0a\x20\x20\x20\x20\x20\x20\x20\x20</div>\x0a\x20\x20\x20\x20\x20\x20</div>\x0a\x20\x20\x20\x20</if>\x0a\x0a\x20\x20\x20\x20<!--\x20Query\x20params\x20-->\x0a\x20\x20\x20\x20<if\x20:=\x22Object.keys(queryValues).length\x20>\x200\x22>\x0a\x20\x20\x20\x20\x20\x20<div>\x0a\x20\x20\x20\x20\x20\x20\x20\x20<div\x20class=\x22ae-section-label\x22>Query\x20Parameters</div>\x0a\x20\x20\x20\x20\x20\x20\x20\x20<div\x20style=\x22display:flex;\x20flex-direction:column;\x20gap:6px;\x22>\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20<each\x20:of=\x22Object.keys(queryValues)\x22\x20let:item=\x22k\x22>\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20<div\x20style=\x22display:flex;\x20align-items:center;\x20gap:10px;\x22>\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20<code\x20style=\x22font-family:monospace;\x20font-size:12px;\x20color:var(--okdb-accent-2);\x20width:120px;\x20flex-shrink:0;\x22>{{\x20k\x20}}</code>\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20<input\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20type=\x22text\x22\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20class=\x22ae-input\x22\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20:value=\x22queryValues[k]\x22\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20@input=\x22setQuery(k,\x20$el.value)\x22\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20style=\x22flex:1;\x22\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20/>\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20</div>\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20</each>\x0a\x20\x20\x20\x20\x20\x20\x20\x20</div>\x0a\x20\x20\x20\x20\x20\x20</div>\x0a\x20\x20\x20\x20</if>\x0a\x0a\x20\x20\x20\x20<!--\x20Body\x20editor\x20-->\x0a\x20\x20\x20\x20<if\x20:=\x22hasBody\x22>\x0a\x20\x20\x20\x20\x20\x20<div\x20style=\x22display:flex;\x20flex-direction:column;\x20gap:6px;\x22>\x0a\x20\x20\x20\x20\x20\x20\x20\x20<div\x20class=\x22ae-section-label\x22>Request\x20Body\x20<span\x20style=\x22opacity:.4;\x20font-weight:400;\x22>(JSON)</span></div>\x0a\x20\x20\x20\x20\x20\x20\x20\x20<textarea\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20class=\x22ae-textarea\x22\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20rows=\x2210\x22\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20spellcheck=\x22false\x22\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20!value=\x22bodyText\x22\x0a\x20\x20\x20\x20\x20\x20\x20\x20></textarea>\x0a\x20\x20\x20\x20\x20\x20</div>\x0a\x20\x20\x20\x20</if>\x0a\x0a\x20\x20\x20\x20<!--\x20Run\x20button\x20-->\x0a\x20\x20\x20\x20<div\x20style=\x22display:flex;\x20align-items:center;\x20gap:12px;\x22>\x0a\x20\x20\x20\x20\x20\x20<button\x0a\x20\x20\x20\x20\x20\x20\x20\x20@click=\x22run()\x22\x0a\x20\x20\x20\x20\x20\x20\x20\x20:disabled=\x22running\x22\x0a\x20\x20\x20\x20\x20\x20\x20\x20style=\x22background:var(--okdb-accent-soft-bg);\x20border-color:var(--okdb-accent-soft-border);\x20color:var(--okdb-accent);\x20font-weight:600;\x20padding:8px\x2022px;\x20font-size:13px;\x22\x0a\x20\x20\x20\x20\x20\x20>\x0a\x20\x20\x20\x20\x20\x20\x20\x20<if\x20:=\x22running\x22>⏳\x20Running…</if>\x0a\x20\x20\x20\x20\x20\x20\x20\x20<if\x20:=\x22!running\x22>▶\x20Run</if>\x0a\x20\x20\x20\x20\x20\x20</button>\x0a\x20\x20\x20\x20\x20\x20<if\x20:=\x22response\x22>\x0a\x20\x20\x20\x20\x20\x20\x20\x20<span\x20:style=\x22\x27font-size:12px;\x20font-weight:700;\x20color:\x27+statusColor(response.status)+\x27;\x27\x22>\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20{{\x20response.status\x20}}\x0a\x20\x20\x20\x20\x20\x20\x20\x20</span>\x0a\x20\x20\x20\x20\x20\x20\x20\x20<if\x20:=\x22response.serverMs\x20!==\x20null\x22>\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20<span\x20style=\x22font-size:12px;\x20opacity:.65;\x22\x20title=\x22Server\x20processing\x20time\x20(from\x20meta.time)\x22>\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20{{\x20response.serverMs\x20}}ms\x20<span\x20style=\x22opacity:.5;\x20font-size:11px;\x22>server</span>\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20</span>\x0a\x20\x20\x20\x20\x20\x20\x20\x20</if>\x0a\x20\x20\x20\x20\x20\x20\x20\x20<span\x20style=\x22font-size:12px;\x20opacity:.35;\x22\x20title=\x22Client\x20roundtrip\x20time\x22>\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20{{\x20response.clientMs\x20}}ms\x20<span\x20style=\x22font-size:11px;\x22>roundtrip</span>\x0a\x20\x20\x20\x20\x20\x20\x20\x20</span>\x0a\x20\x20\x20\x20\x20\x20</if>\x0a\x20\x20\x20\x20\x20\x20<if\x20:=\x22runError\x22>\x0a\x20\x20\x20\x20\x20\x20\x20\x20<span\x20style=\x22font-size:12px;\x20color:var(--okdb-danger);\x22>{{\x20runError\x20}}</span>\x0a\x20\x20\x20\x20\x20\x20</if>\x0a\x20\x20\x20\x20</div>\x0a\x0a\x20\x20\x20\x20<!--\x20Response\x20viewer\x20-->\x0a\x20\x20\x20\x20<if\x20:=\x22response\x22>\x0a\x20\x20\x20\x20\x20\x20<div\x20style=\x22display:flex;\x20flex-direction:column;\x20gap:6px;\x22>\x0a\x20\x20\x20\x20\x20\x20\x20\x20<div\x20class=\x22ae-section-label\x22>Response</div>\x0a\x20\x20\x20\x20\x20\x20\x20\x20<pre\x20class=\x22ae-response\x22>{{\x20prettyJson(response.body)\x20}}</pre>\x0a\x20\x20\x20\x20\x20\x20</div>\x0a\x20\x20\x20\x20</if>\x0a\x0a\x20\x20</div><!--\x20/scroll\x20area\x20-->\x0a</div>\x0a','replace','defaults','$attr','bodyText','(?=\x5c/|$)','reset','var(--okdb-danger)','54ifnfrY','var(--okdb-warning)','headers','5000590vDChuX','application/json','runError'];_0x4722=function(){return _0x3cd5da;};return _0x4722();}const _0x4b69e6=_0x20b2;(function(_0x77c8a6,_0x18e3cc){const _0x4cdf57=_0x20b2,_0x568e84=_0x77c8a6();while(!![]){try{const _0x1cf049=parseInt(_0x4cdf57(0xeb))/0x1+-parseInt(_0x4cdf57(0xd2))/0x2+parseInt(_0x4cdf57(0xea))/0x3+parseInt(_0x4cdf57(0xce))/0x4*(parseInt(_0x4cdf57(0xe7))/0x5)+parseInt(_0x4cdf57(0xcf))/0x6*(-parseInt(_0x4cdf57(0xbf))/0x7)+parseInt(_0x4cdf57(0xd1))/0x8*(parseInt(_0x4cdf57(0xc8))/0x9)+-parseInt(_0x4cdf57(0xcb))/0xa;if(_0x1cf049===_0x18e3cc)break;else _0x568e84['push'](_0x568e84['shift']());}catch(_0x599ecc){_0x568e84['push'](_0x568e84['shift']());}}}(_0x4722,0x32714));export default{'tag':'api-endpoint','attr':{'endpoint':{}},'context':_0xa8b66f=>({'paramValues':{},'queryValues':{},'bodyText':'','running':!0x1,'response':null,'runError':null,get 'ep'(){const _0x1fcd2a=_0x20b2;return _0xa8b66f[_0x1fcd2a(0xc3)][_0x1fcd2a(0xd9)];},get 'pathParts'(){const _0x348a81=_0x20b2,_0x55f13f=this['ep']?.['path']||'',_0xa2280=_0x55f13f['match'](/^(.*?)\[([^\]]*)\](.*)$/);return _0xa2280?{'before':_0xa2280[0x1],'optional':_0xa2280[0x2][_0x348a81(0xc1)](/=\w+/g,''),'after':_0xa2280[0x3]}:{'before':_0x55f13f,'optional':null,'after':''};},get 'pathParams'(){const _0x55eb60=_0x20b2;return[...(this['ep']?.['path']||'')['replace'](/\[([^\]]*)\]/g,'$1')[_0x55eb60(0xe0)](/:(\w+)/g)][_0x55eb60(0xe3)](_0x449526=>_0x449526[0x1]);},get 'hasBody'(){const _0x2960a5=_0x20b2;return[_0x2960a5(0xbc),_0x2960a5(0xda),'PATCH',_0x2960a5(0xdc)][_0x2960a5(0xd7)](this['ep']?.[_0x2960a5(0xe9)]);},get 'methodColor'(){const _0x546cda=_0x20b2;return{'GET':'var(--okdb-accent)','POST':_0x546cda(0xb8),'PUT':_0x546cda(0xc9),'PATCH':_0x546cda(0xd3),'DELETE':_0x546cda(0xc7)}[this['ep']?.['method']]||_0x546cda(0xbb);},'reset'(){const _0xa6dc83=_0x20b2;this[_0xa6dc83(0xba)]={},this[_0xa6dc83(0xdb)]={},this[_0xa6dc83(0xbd)]=null,this['runError']=null;const _0x277eec=this['ep']?.[_0xa6dc83(0xc2)]??{};for(const [_0xc52329,_0x4eee77]of Object[_0xa6dc83(0xdf)](_0x277eec))this[_0xa6dc83(0xba)][_0xc52329]=_0x4eee77;const _0x677582=this['ep']?.[_0xa6dc83(0xe4)]?.['query']||{};for(const [_0x5dbef3,_0x5a654b]of Object[_0xa6dc83(0xdf)](_0x677582))this[_0xa6dc83(0xdb)][_0x5dbef3]=null!=_0x5a654b?String(_0x5a654b):'';this[_0xa6dc83(0xc4)]=null!=this['ep']?.['meta']?.[_0xa6dc83(0xe5)]?JSON['stringify'](this['ep']['meta']['body'],null,0x2):'';},'buildUrl'(){const _0x5058be=_0x20b2;let _0x3490f6=this['ep'][_0x5058be(0xd4)][_0x5058be(0xc1)](/^\/api/,'');_0x3490f6=_0x3490f6['replace'](/\[([^\]]*)\]/g,(_0x1435b5,_0x1b1109)=>{const _0x106887=_0x5058be,_0x2c530b=_0x1b1109[_0x106887(0xec)](/:(\w+)(?:=(\w+))?/);if(!_0x2c530b)return _0x1b1109;const _0x190bde=_0x2c530b[0x1],_0x421d73=_0x2c530b[0x2]??'';return(this[_0x106887(0xba)][_0x190bde]??_0x421d73)===_0x421d73?'':_0x1b1109[_0x106887(0xc1)](/:(\w+)(?:=\w+)?/g,(_0x35e6c5,_0x57be25)=>'/'+encodeURIComponent(this['paramValues'][_0x57be25]??''))[_0x106887(0xc1)](/^\//,'/');});for(const [_0xec3de,_0x38fe5b]of Object[_0x5058be(0xdf)](this[_0x5058be(0xba)]))_0x3490f6=_0x3490f6['replace'](new RegExp(':'+_0xec3de+_0x5058be(0xc5),'g'),encodeURIComponent(_0x38fe5b||':'+_0xec3de));const _0x28edb5=Object['entries'](this[_0x5058be(0xdb)])[_0x5058be(0xe6)](([,_0x5dd72b])=>''!==_0x5dd72b&&null!=_0x5dd72b)['map'](([_0x55a678,_0x97d57a])=>encodeURIComponent(_0x55a678)+'='+encodeURIComponent(_0x97d57a))[_0x5058be(0xd8)]('&');return __ENV__[_0x5058be(0xb5)]+_0x3490f6+(_0x28edb5?'?'+_0x28edb5:'');},async 'run'(){const _0x1c8680=_0x20b2;this[_0x1c8680(0xe8)]=!0x0,this['response']=null,this['runError']=null;const _0x4731f7=Date[_0x1c8680(0xdd)]();try{const _0x1c5ff7=this[_0x1c8680(0xd5)](),_0x266533={'method':this['ep'][_0x1c8680(0xe9)]};this['hasBody']&&this[_0x1c8680(0xc4)][_0x1c8680(0xd0)]()&&(_0x266533[_0x1c8680(0xca)]={'Content-Type':_0x1c8680(0xcc)},_0x266533[_0x1c8680(0xe5)]=this[_0x1c8680(0xc4)]);const _0x379d1d=await fetch(_0x1c5ff7,_0x266533),_0x3758e4=Date[_0x1c8680(0xdd)]()-_0x4731f7;let _0x58963e;try{_0x58963e=await _0x379d1d[_0x1c8680(0xbe)]();}catch{_0x58963e=await _0x379d1d[_0x1c8680(0xde)]();}const _0x2099ca=_0x1c8680(0xe1)==typeof _0x58963e&&null!==_0x58963e?_0x58963e?.[_0x1c8680(0xe4)]?.[_0x1c8680(0xd6)]??null:null;this[_0x1c8680(0xbd)]={'status':_0x379d1d[_0x1c8680(0xb4)],'clientMs':_0x3758e4,'serverMs':_0x2099ca,'body':_0x58963e};}catch(_0x1bc0ba){this[_0x1c8680(0xcd)]=_0x1bc0ba?.[_0x1c8680(0xb6)]||String(_0x1bc0ba);}finally{this[_0x1c8680(0xe8)]=!0x1;}},'statusColor':_0x5ea341=>_0x5ea341>=0xc8&&_0x5ea341<0x12c?_0x4b69e6(0xb8):_0x5ea341>=0x190&&_0x5ea341<0x1f4?_0x4b69e6(0xc9):'var(--okdb-danger)','prettyJson'(_0x3ca9ba){const _0x2955ef=_0x4b69e6;try{return JSON[_0x2955ef(0xb7)](_0x3ca9ba,null,0x2);}catch{return String(_0x3ca9ba);}},'setParam'(_0x5e3c2b,_0x57d979){const _0xcaa72f=_0x4b69e6;this[_0xcaa72f(0xba)]={...this['paramValues'],[_0x5e3c2b]:_0x57d979};},'setQuery'(_0xcc927b,_0x28a3aa){const _0x59ffbc=_0x4b69e6;this[_0x59ffbc(0xdb)]={...this['queryValues'],[_0xcc927b]:_0x28a3aa};}}),'init'(_0x3bbe05){const _0x4c0296=_0x4b69e6;_0x3bbe05[_0x4c0296(0xb9)](()=>_0x3bbe05[_0x4c0296(0xc3)]['endpoint'],()=>{const _0x1f9f00=_0x4c0296;this[_0x1f9f00(0xc6)]();}),this[_0x4c0296(0xc6)]();},'template':_0x4b69e6(0xc0),'style':_0x4b69e6(0xe2)};
1
+ export default{tag:"api-endpoint",attr:{endpoint:{}},context:e=>({paramValues:{},queryValues:{},bodyText:"",running:!1,response:null,runError:null,get ep(){return e.$attr.endpoint},get pathParts(){const e=this.ep?.path||"",n=e.match(/^(.*?)\[([^\]]*)\](.*)$/);return n?{before:n[1],optional:n[2].replace(/=\w+/g,""),after:n[3]}:{before:e,optional:null,after:""}},get pathParams(){return[...(this.ep?.path||"").replace(/\[([^\]]*)\]/g,"$1").matchAll(/:(\w+)/g)].map(e=>e[1])},get hasBody(){return["POST","PUT","PATCH","DELETE"].includes(this.ep?.method)},get methodColor(){return{GET:"var(--okdb-accent)",POST:"var(--okdb-success)",PUT:"var(--okdb-warning)",PATCH:"var(--okdb-accent-2)",DELETE:"var(--okdb-danger)"}[this.ep?.method]||"var(--okdb-method-default)"},reset(){this.paramValues={},this.queryValues={},this.response=null,this.runError=null;const e=this.ep?.defaults??{};for(const[n,t]of Object.entries(e))this.paramValues[n]=t;const n=this.ep?.meta?.query||{};for(const[e,t]of Object.entries(n))this.queryValues[e]=null!=t?String(t):"";this.bodyText=null!=this.ep?.meta?.body?JSON.stringify(this.ep.meta.body,null,2):""},buildUrl(){let e=this.ep.path.replace(/^\/api/,"");e=e.replace(/\[([^\]]*)\]/g,(e,n)=>{const t=n.match(/:(\w+)(?:=(\w+))?/);if(!t)return n;const a=t[1],o=t[2]??"";return(this.paramValues[a]??o)===o?"":n.replace(/:(\w+)(?:=\w+)?/g,(e,n)=>"/"+encodeURIComponent(this.paramValues[n]??"")).replace(/^\//,"/")});for(const[n,t]of Object.entries(this.paramValues))e=e.replace(new RegExp(`:${n}(?=\\/|$)`,"g"),encodeURIComponent(t||`:${n}`));const n=Object.entries(this.queryValues).filter(([,e])=>""!==e&&null!=e).map(([e,n])=>`${encodeURIComponent(e)}=${encodeURIComponent(n)}`).join("&");return __ENV__.API+e+(n?"?"+n:"")},async run(){this.running=!0,this.response=null,this.runError=null;const e=Date.now();try{const n=this.buildUrl(),t={method:this.ep.method};this.hasBody&&this.bodyText.trim()&&(t.headers={"Content-Type":"application/json"},t.body=this.bodyText);const a=await fetch(n,t),o=Date.now()-e;let r;try{r=await a.json()}catch{r=await a.text()}const s="object"==typeof r&&null!==r?r?.meta?.time??null:null;this.response={status:a.status,clientMs:o,serverMs:s,body:r}}catch(e){this.runError=e?.message||String(e)}finally{this.running=!1}},statusColor:e=>e>=200&&e<300?"var(--okdb-success)":e>=400&&e<500?"var(--okdb-warning)":"var(--okdb-danger)",prettyJson(e){try{return JSON.stringify(e,null,2)}catch{return String(e)}},setParam(e,n){this.paramValues={...this.paramValues,[e]:n}},setQuery(e,n){this.queryValues={...this.queryValues,[e]:n}}}),init(e){e.$watch(()=>e.$attr.endpoint,()=>{this.reset()}),this.reset()},template:'\n<div style="display:flex; flex-direction:column; gap:0; height:100%;">\n\n \x3c!-- Method + path header --\x3e\n <div style="display:flex; align-items:center; gap:12px; padding:18px 20px 14px; flex-shrink:0; border-bottom:1px solid var(--okdb-app-surface-2);">\n <span :style="\'font-size:11px; font-weight:700; padding:3px 10px; border-radius:5px; border:1px solid; color:\'+methodColor+\'; border-color:color-mix(in srgb, \'+methodColor+\' 33%, transparent); background:color-mix(in srgb, \'+methodColor+\' 7%, transparent); letter-spacing:.06em;\'">\n {{ ep?.method }}\n </span>\n <code style="font-family:monospace; font-size:14px; color:var(--okdb-app-fg); word-break:break-all;">{{ pathParts.before }}<if :="pathParts.optional"><span style="opacity:.35;">{{ pathParts.optional }}</span></if>{{ pathParts.after }}</code>\n </div>\n\n \x3c!-- Scrollable form area --\x3e\n <div style="flex:1; min-height:0; overflow-y:auto; padding:18px 20px; display:flex; flex-direction:column; gap:18px;">\n\n \x3c!-- Summary + description --\x3e\n <if :="ep?.meta?.summary || ep?.meta?.description">\n <div style="display:flex; flex-direction:column; gap:4px;">\n <if :="ep?.meta?.summary">\n <div style="font-weight:600; font-size:14px; color:var(--okdb-app-fg-strong);">{{ ep.meta.summary }}</div>\n </if>\n <if :="ep?.meta?.description">\n <div style="font-size:13px; opacity:.65; line-height:1.6;">{{ ep.meta.description }}</div>\n </if>\n </div>\n </if>\n\n \x3c!-- Path params --\x3e\n <if :="pathParams.length > 0">\n <div>\n <div class="ae-section-label">Path Parameters</div>\n <div style="display:flex; flex-direction:column; gap:6px;">\n <each :of="pathParams" let:item="p">\n <div style="display:flex; align-items:center; gap:10px;">\n <code style="font-family:monospace; font-size:12px; color:var(--okdb-accent); width:120px; flex-shrink:0;">:{{ p }}</code>\n <if :="ep?.meta?.params?.[p]">\n <span style="font-size:11px; opacity:.5; flex-shrink:0;">{{ ep.meta.params[p] }}</span>\n </if>\n <input\n type="text"\n class="ae-input"\n :placeholder="p"\n :value="paramValues[p] || \'\'"\n @input="setParam(p, $el.value)"\n style="flex:1;"\n />\n </div>\n </each>\n </div>\n </div>\n </if>\n\n \x3c!-- Query params --\x3e\n <if :="Object.keys(queryValues).length > 0">\n <div>\n <div class="ae-section-label">Query Parameters</div>\n <div style="display:flex; flex-direction:column; gap:6px;">\n <each :of="Object.keys(queryValues)" let:item="k">\n <div style="display:flex; align-items:center; gap:10px;">\n <code style="font-family:monospace; font-size:12px; color:var(--okdb-accent-2); width:120px; flex-shrink:0;">{{ k }}</code>\n <input\n type="text"\n class="ae-input"\n :value="queryValues[k]"\n @input="setQuery(k, $el.value)"\n style="flex:1;"\n />\n </div>\n </each>\n </div>\n </div>\n </if>\n\n \x3c!-- Body editor --\x3e\n <if :="hasBody">\n <div style="display:flex; flex-direction:column; gap:6px;">\n <div class="ae-section-label">Request Body <span style="opacity:.4; font-weight:400;">(JSON)</span></div>\n <textarea\n class="ae-textarea"\n rows="10"\n spellcheck="false"\n !value="bodyText"\n ></textarea>\n </div>\n </if>\n\n \x3c!-- Run button --\x3e\n <div style="display:flex; align-items:center; gap:12px;">\n <button\n @click="run()"\n :disabled="running"\n style="background:var(--okdb-accent-soft-bg); border-color:var(--okdb-accent-soft-border); color:var(--okdb-accent); font-weight:600; padding:8px 22px; font-size:13px;"\n >\n <if :="running">⏳ Running…</if>\n <if :="!running">▶ Run</if>\n </button>\n <if :="response">\n <span :style="\'font-size:12px; font-weight:700; color:\'+statusColor(response.status)+\';\'">\n {{ response.status }}\n </span>\n <if :="response.serverMs !== null">\n <span style="font-size:12px; opacity:.65;" title="Server processing time (from meta.time)">\n {{ response.serverMs }}ms <span style="opacity:.5; font-size:11px;">server</span>\n </span>\n </if>\n <span style="font-size:12px; opacity:.35;" title="Client roundtrip time">\n {{ response.clientMs }}ms <span style="font-size:11px;">roundtrip</span>\n </span>\n </if>\n <if :="runError">\n <span style="font-size:12px; color:var(--okdb-danger);">{{ runError }}</span>\n </if>\n </div>\n\n \x3c!-- Response viewer --\x3e\n <if :="response">\n <div style="display:flex; flex-direction:column; gap:6px;">\n <div class="ae-section-label">Response</div>\n <pre class="ae-response">{{ prettyJson(response.body) }}</pre>\n </div>\n </if>\n\n </div>\x3c!-- /scroll area --\x3e\n</div>\n',style:"\n api-endpoint .ae-section-label {\n font-size: 10px;\n text-transform: uppercase;\n letter-spacing: .07em;\n opacity: .45;\n margin-bottom: 6px;\n }\n api-endpoint .ae-input {\n background: var(--okdb-app-bg-deep);\n border: 1px solid var(--okdb-app-border);\n border-radius: 6px;\n padding: 5px 10px;\n color: var(--okdb-app-fg);\n font-size: 13px;\n font-family: monospace;\n outline: none;\n transition: border-color .12s;\n }\n api-endpoint .ae-input:focus {\n border-color: var(--okdb-accent-border);\n }\n api-endpoint .ae-textarea {\n background: var(--okdb-app-bg-deep);\n border: 1px solid var(--okdb-app-border);\n border-radius: 8px;\n padding: 10px 12px;\n color: var(--okdb-app-fg);\n font-size: 12px;\n font-family: 'Cascadia Code', 'Fira Mono', monospace;\n resize: vertical;\n outline: none;\n line-height: 1.6;\n transition: border-color .12s;\n width: 100%;\n box-sizing: border-box;\n }\n api-endpoint .ae-textarea:focus {\n border-color: var(--okdb-accent-soft-border);\n }\n api-endpoint .ae-response {\n background: var(--okdb-app-bg-deep);\n border: 1px solid var(--okdb-app-surface-2);\n border-radius: 8px;\n padding: 12px 14px;\n font-size: 12px;\n font-family: 'Cascadia Code', 'Fira Mono', monospace;\n color: #88c8b0;\n overflow-x: auto;\n white-space: pre-wrap;\n word-break: break-all;\n margin: 0;\n max-height: 400px;\n overflow-y: auto;\n line-height: 1.55;\n }\n "};
@@ -1 +1 @@
1
- <script type="module">import tokenList from"./parts/token-list.ok.js";import tokenDetail from"./parts/token-detail.ok.js";import createTokenModal from"./modals/create-token-modal.ok.js";import editTokenModal from"./modals/edit-token-modal.ok.js";export default{tag:"auth-index",unwrap:!0,dependencies:[tokenList,tokenDetail,createTokenModal,editTokenModal],context:e=>({selectedToken:null,tokenListEl:null,selectedTokenId:e.$vars?.$router?._search?.token??null,selectToken(o){this.selectedToken=o??null,this.selectedTokenId=o?.id??null;const t=e.$vars?.$router;t&&(o?.id?t.search.token=o.id:delete t.search.token)},async openCreateModal(){await e.$ok.shared.modal.open({owner:e,title:"Create Token",content:'<create-token-modal :modal="modal" :on-done="onDone" />',context:e=>({modal:e,onDone:async e=>{if(e?.id){const o=this.tokenListEl?.$scope?.$innerContext;o&&(o._selectedId=e.id),this.selectToken(e)}await(this.tokenListEl?.$scope?.$innerContext?.load?.())}})})},async openEditModal(o){await e.$ok.shared.modal.open({owner:e,title:`Edit Token: ${o.label||"Unnamed"}`,content:'<edit-token-modal :modal="modal" :token="token" :on-done="onDone" />',context:e=>({modal:e,token:o,onDone:async()=>{await(this.tokenListEl?.$scope?.$innerContext?.load?.())}})})},async deleteToken(o){try{const t=await fetch(`${__ENV__.API}/auth/tokens/${encodeURIComponent(o)}`,{method:"DELETE"});if(!t.ok){const e=await t.json();throw new Error(e?.error?.message||"Failed to delete token")}e.$ok.shared?.toast?.success?.("Token deleted"),this.selectToken(null),await(this.tokenListEl?.$scope?.$innerContext?.load?.())}catch(o){e.$ok.shared?.toast?.error?.(o?.message||"Delete failed"),console.error("Delete token error",o)}}}),mount(e){e.$watch(()=>e.$vars?.$router?._search?.token,e=>{this.selectedTokenId=e??null,e?e!==this.selectedToken?.id&&this.tokenListEl?.$scope?.$innerContext?.load?.():this.selectedToken=null})}}</script><style>.auth-action-btn{padding:6px 10px;border:1px solid var(--okdb-app-border);border-radius:6px;background:0 0;color:var(--okdb-accent-muted);font-size:12px;font-weight:500;cursor:pointer;transition:background .12s,border-color .12s,color .12s;text-align:left}.auth-action-btn:hover{background:var(--okdb-app-surface-2);border-color:var(--okdb-app-border);color:var(--okdb-sky)}</style><template><component import="../../layouts/_default.ok.html"><template name="header"></template><template name="sidebar"><div style="display:flex;flex-direction:column;gap:6px;height:100%"><button @click="openCreateModal()" class="auth-action-btn">+ New Token</button><token-list :ref="tokenListEl = $el" :selected-id="selectedTokenId" @selected="selectToken($event.detail)"/></div></template><template name="default"><token-detail :token="selectedToken" @edit="openEditModal($event.detail)" @delete="deleteToken($event.detail)"/></template></component></template>
1
+ <script type="module">import e from"./parts/token-list.ok.js";import o from"./parts/token-detail.ok.js";import t from"./modals/create-token-modal.ok.js";import n from"./modals/edit-token-modal.ok.js";export default{tag:"auth-index",unwrap:!0,dependencies:[e,o,t,n],context:e=>({selectedToken:null,tokenListEl:null,selectedTokenId:e.$vars?.$router?._search?.token??null,selectToken(o){this.selectedToken=o??null,this.selectedTokenId=o?.id??null;const t=e.$vars?.$router;t&&(o?.id?t.search.token=o.id:delete t.search.token)},async openCreateModal(){await e.$ok.shared.modal.open({owner:e,title:"Create Token",content:'<create-token-modal :modal="modal" :on-done="onDone" />',context:e=>({modal:e,onDone:async e=>{if(e?.id){const o=this.tokenListEl?.$scope?.$innerContext;o&&(o._selectedId=e.id),this.selectToken(e)}await(this.tokenListEl?.$scope?.$innerContext?.load?.())}})})},async openEditModal(o){await e.$ok.shared.modal.open({owner:e,title:`Edit Token: ${o.label||"Unnamed"}`,content:'<edit-token-modal :modal="modal" :token="token" :on-done="onDone" />',context:e=>({modal:e,token:o,onDone:async()=>{await(this.tokenListEl?.$scope?.$innerContext?.load?.())}})})},async deleteToken(o){try{const t=await fetch(`${__ENV__.API}/auth/tokens/${encodeURIComponent(o)}`,{method:"DELETE"});if(!t.ok){const e=await t.json();throw new Error(e?.error?.message||"Failed to delete token")}e.$ok.shared?.toast?.success?.("Token deleted"),this.selectToken(null),await(this.tokenListEl?.$scope?.$innerContext?.load?.())}catch(o){e.$ok.shared?.toast?.error?.(o?.message||"Delete failed"),console.error("Delete token error",o)}}}),mount(e){e.$watch(()=>e.$vars?.$router?._search?.token,e=>{this.selectedTokenId=e??null,e?e!==this.selectedToken?.id&&this.tokenListEl?.$scope?.$innerContext?.load?.():this.selectedToken=null})}};</script><style>.auth-action-btn{padding:6px 10px;border:1px solid var(--okdb-app-border);border-radius:6px;background:0 0;color:var(--okdb-accent-muted);font-size:12px;font-weight:500;cursor:pointer;transition:background .12s,border-color .12s,color .12s;text-align:left}.auth-action-btn:hover{background:var(--okdb-app-surface-2);border-color:var(--okdb-app-border);color:var(--okdb-sky)}</style><template><component import="../../layouts/_default.ok.html"><template name="header"></template><template name="sidebar"><div style="display:flex;flex-direction:column;gap:6px;height:100%"><button @click="openCreateModal()" class="auth-action-btn">+ New Token</button><token-list :ref="tokenListEl = $el" :selected-id="selectedTokenId" @selected="selectToken($event.detail)"/></div></template><template name="default"><token-detail :token="selectedToken" @edit="openEditModal($event.detail)" @delete="deleteToken($event.detail)"/></template></component></template>
@@ -1 +1 @@
1
- const _0x4cef19=_0x52b1;function _0x4e57(){const _0x3b27ce=['10016QzZiIU','label','collapsedScopes','/auth/tokens','token','work','namespaces','test','length','611394SZALEO','indexOf','env','26776FkxTXc','application/json','catalogError','sort','success','clipboard','ADMIN_API','ttl','opOrder','query','1325286JaTkzi','run','var(--okdb-warning)','weak','Failed\x20to\x20create\x20token','isNsAllSelectedForGrant','165ewaeUb','operations','result','var(--okdb-danger)','close','validateForm','selectedPerms','write','godMode','shared','Gate','json','permissions','gate','grants','keys','map','showToken','System','<div\x20class=\x22tf-root\x22><if\x20:=\x22busy\x20&&\x20!showToken\x22><ok-overlay><div\x20class=\x22loader-center\x22><ok-icon\x20name=\x22loader\x22\x20class=\x22spin\x22\x20style=\x22width:28px;height:28px;\x22/></div></ok-overlay></if><if\x20:=\x22showToken\x20&&\x20createdToken\x22><div\x20style=\x22display:flex;\x20flex-direction:column;\x20gap:12px;\x22><div\x20style=\x22font-size:13px;\x20font-weight:600;\x20color:var(--okdb-success);\x22>✓\x20Token\x20Created</div><div\x20class=\x22tf-token-box\x22><div\x20class=\x22tf-token-warn\x22>⚠\x20Copy\x20this\x20token\x20now\x20—\x20it\x20will\x20not\x20be\x20shown\x20again.</div><div\x20class=\x22tf-token-value\x22>{{\x20createdToken.token\x20||\x20createdToken.value\x20||\x20createdToken\x20}}</div><div\x20style=\x22font-size:11px;\x20color:var(--okdb-app-fg-muted);\x22>{{\x20createdToken.label\x20||\x20\x27Unnamed\x27\x20}}</div></div><div\x20class=\x22tf-actions\x22><button\x20class=\x22tf-btn\x20tf-btn--primary\x22\x20@click=\x22copyToken()\x22>Copy\x20token</button>\x20<button\x20class=\x22tf-btn\x20tf-btn--secondary\x22\x20@click=\x22dismissToken()\x22>Done</button></div></div></if><if\x20:=\x22!showToken\x22><div\x20class=\x22tf-header-fields\x22><div\x20class=\x22tf-header-row\x22><div\x20class=\x22tf-field\x22\x20style=\x22flex:2;\x22><span\x20class=\x22tf-label\x22>Label</span>\x20<input\x20class=\x22tf-input\x22\x20type=\x22text\x22\x20placeholder=\x22e.g.,\x20API\x20Client\x22\x20:value=\x22label\x22\x20@input=\x22label\x20=\x20$el.value\x22\x20:disabled=\x22busy\x22></div><div\x20class=\x22tf-field\x22\x20style=\x22flex:1.5;\x22><span\x20class=\x22tf-label\x22>Expires\x20<span\x20style=\x22opacity:.4;\x22>(optional)</span></span>\x20<input\x20class=\x22tf-input\x22\x20type=\x22datetime-local\x22\x20:value=\x22expiresAt\x22\x20@input=\x22expiresAt\x20=\x20$el.value\x22\x20:disabled=\x22busy\x22></div></div><div\x20class=\x22tf-creds-section\x22><div><div\x20class=\x22tf-creds-label\x22>Login\x20credentials\x20<span\x20style=\x22opacity:.4;\x20text-transform:none;\x20font-weight:400;\x20letter-spacing:0;\x22>(optional)</span></div><div\x20class=\x22tf-creds-hint\x22>If\x20set,\x20this\x20token\x20can\x20also\x20be\x20used\x20via\x20username\x20+\x20password\x20authentication.</div></div><div\x20class=\x22tf-creds-row\x22><div\x20class=\x22tf-field\x22\x20style=\x22flex:1;\x22><span\x20class=\x22tf-label\x22>Username</span>\x20<input\x20class=\x22tf-input\x22\x20type=\x22text\x22\x20placeholder=\x22leave\x20blank\x20for\x20token-only\x22\x20:value=\x22name\x22\x20@input=\x22name\x20=\x20$el.value\x22\x20:disabled=\x22busy\x22\x20autocomplete=\x22off\x22></div><div\x20class=\x22tf-field\x22\x20style=\x22flex:1;\x22><span\x20class=\x22tf-label\x22>Password</span><div\x20class=\x22tf-pw-wrap\x22><input\x20class=\x22tf-input\x22\x20:type=\x22showPassword\x20?\x20\x27text\x27\x20:\x20\x27password\x27\x22\x20placeholder=\x22required\x20if\x20username\x20is\x20set\x22\x20:value=\x22password\x22\x20@input=\x22password\x20=\x20$el.value\x22\x20:disabled=\x22busy\x20||\x20!name\x22\x20autocomplete=\x22new-password\x22>\x20<button\x20class=\x22tf-pw-eye\x22\x20@click=\x22toggleShowPassword()\x22\x20type=\x22button\x22\x20tabindex=\x22-1\x22>{{\x20showPassword\x20?\x20\x27🙈\x27\x20:\x20\x27👁\x27\x20}}</button></div><if\x20:=\x22password\x22><div\x20class=\x22tf-strength-bar\x22><div\x20class=\x22tf-strength-fill\x22\x20:style=\x22\x27width:\x27\x20+\x20(passwordStrength(password)/5*100)\x20+\x20\x27%;\x20background:\x27\x20+\x20passwordStrengthColor(password)\x22/></div><div\x20class=\x22tf-strength-text\x22\x20:style=\x22\x27color:\x27\x20+\x20passwordStrengthColor(password)\x22>{{\x20passwordStrengthLabel(password)\x20}}</div></if></div></div></div></div><div\x20class=\x22tf-divider\x22/><if\x20:=\x22catalogLoading\x22><div\x20class=\x22tf-hint\x22\x20style=\x22padding-bottom:12px;\x22>Loading\x20permissions…</div></if><if\x20:=\x22catalogError\x20&&\x20!catalogLoading\x22><div\x20class=\x22tf-section\x22><span\x20class=\x22tf-label\x22>Permissions\x20<span\x20style=\x22color:var(--okdb-danger);\x22>(fallback)</span></span>\x20<input\x20class=\x22tf-input\x22\x20type=\x22text\x22\x20placeholder=\x22data:read,\x20data:write\x22\x20:value=\x22selectedPerms._fallback\x20||\x20\x27\x27\x22\x20@input=\x22selectedPerms\x20=\x20{\x20_fallback:\x20$el.value\x20}\x22\x20:disabled=\x22busy\x22>\x20<span\x20class=\x22tf-hint\x22>Comma-separated\x20permission\x20strings</span></div></if><if\x20:=\x22catalog\x20&&\x20!catalogError\x20&&\x20!catalogLoading\x22><div\x20class=\x22tf-section\x22\x20style=\x22gap:4px;\x22><div\x20:class=\x22[\x27tf-pill\x27,\x20\x27tf-pill--full\x27,\x20godMode\x20?\x20\x27tf-pill--full-on\x27\x20:\x20\x27\x27]\x22\x20@click=\x22toggleGodMode()\x22\x20style=\x22width:fit-content;\x22>★\x20&nbsp;Full\x20access\x20—\x20all\x20permissions</div></div><if\x20:=\x22!godMode\x22><each\x20:of=\x22getPermSections()\x22\x20let:item=\x22section\x22><div\x20class=\x22tf-section\x22><div\x20class=\x22tf-scope-header\x22\x20@click=\x22toggleScope(section.scope)\x22><span\x20class=\x22tf-scope-name\x22>{{\x20section.label\x20}}</span><if\x20:=\x22scopeSelectionCount(section)\x22><span\x20class=\x22tf-scope-count\x22>{{\x20scopeSelectionCount(section)\x20}}</span></if><span\x20class=\x22tf-scope-chevron\x22>{{\x20isCollapsed(section.scope)\x20?\x20\x27▸\x27\x20:\x20\x27▾\x27\x20}}</span></div><if\x20:=\x22!isCollapsed(section.scope)\x22><each\x20:of=\x22section.namespaces\x22\x20let:item=\x22nsEntry\x22><div\x20class=\x22tf-ns-block\x22><div\x20class=\x22tf-ns-row\x22><span\x20:class=\x22[\x27tf-ns-label\x27,\x20isNsAllSelected(nsEntry.ns)\x20?\x20\x27tf-ns-label--on\x27\x20:\x20\x27\x27]\x22\x20@click=\x22toggleNamespace(nsEntry.ns,\x20nsEntry.operations)\x22\x20title=\x22Toggle\x20all\x22>{{\x20nsEntry.nsLabel\x20||\x20nsEntry.ns\x20}}</span><if\x20:=\x22nsEntry.nsDescription\x22><span\x20class=\x22tf-ns-desc\x22>{{\x20nsEntry.nsDescription\x20}}</span></if></div><div\x20class=\x22tf-pills\x22><span\x20:class=\x22[\x27tf-pill\x27,\x20\x27tf-pill--wild\x27,\x20isNsAllSelected(nsEntry.ns)\x20?\x20\x27tf-pill--wild-on\x27\x20:\x20\x27\x27]\x22\x20@click=\x22toggleNamespace(nsEntry.ns,\x20nsEntry.operations)\x22\x20title=\x22ns:*\x20—\x20toggle\x20all\x22>*</span><each\x20:of=\x22getPermOps(nsEntry.ns,\x20nsEntry.operations,\x20nsEntry.operationMeta)\x22\x20let:item=\x22permOp\x22><span\x20class=\x22tf-pill-wrap\x22\x20@mouseenter=\x22setHover($event,\x20permOp.perm,\x20permOp)\x22\x20@mouseleave=\x22clearHover()\x22><span\x20:class=\x22[\x27tf-pill\x27,\x20permOp.checked\x20?\x20\x27tf-pill--on\x27\x20:\x20\x27\x27,\x20permOp.hasDestructive\x20?\x20\x27tf-pill--destructive\x27\x20:\x20\x27\x27]\x22\x20@click=\x22togglePerm(permOp.ns,\x20permOp.op,\x20nsEntry.operations)\x22>{{\x20permOp.op\x20}}<if\x20:=\x22permOp.hasDestructive\x22><span\x20class=\x22tf-pill-warn\x22>⚠</span></if></span></span></each></div></div></each></if></div></each><if\x20:=\x22catalog.envs\x20&&\x20catalog.envs.length\x20>\x200\x22><div\x20class=\x22tf-divider\x22/><div\x20class=\x22tf-section\x22\x20style=\x22gap:8px;\x22><div\x20class=\x22tf-scope-header\x22\x20style=\x22cursor:default;\x20pointer-events:none;\x22><span\x20class=\x22tf-scope-name\x22>Per-env\x20grants</span>\x20<span\x20class=\x22tf-hint\x22\x20style=\x22margin-left:6px;\x22>extra\x20permissions\x20for\x20a\x20specific\x20environment</span></div><if\x20:=\x22availableGrantEnvs().length\x20>\x200\x22><div\x20class=\x22tf-pills\x22><each\x20:of=\x22availableGrantEnvs()\x22\x20let:item=\x22envName\x22><span\x20class=\x22tf-pill\x20tf-pill--add\x22\x20@click=\x22addGrantEnv(envName)\x22>+\x20{{\x20envName\x20}}</span></each></div></if><each\x20:of=\x22getGrantEntries()\x22\x20let:item=\x22grantEntry\x22><div\x20class=\x22tf-env-row\x22><div\x20class=\x22tf-env-row-header\x22><div\x20style=\x22display:flex;\x20align-items:center;\x20gap:6px;\x22><span\x20class=\x22tf-env-name\x22>{{\x20grantEntry.env\x20}}</span>\x20<span\x20:class=\x22[\x27tf-pill\x27,\x20\x27tf-pill--wild\x27,\x20isEnvAllSelected(grantEntry.env)\x20?\x20\x27tf-pill--wild-on\x27\x20:\x20isEnvWildInherited(grantEntry.env)\x20?\x20\x27tf-pill--wild-inherited\x27\x20:\x20\x27\x27]\x22\x20@click=\x22toggleGrantAllPerms(grantEntry.env)\x22\x20title=\x22*\x20—\x20all\x20permissions\x20for\x20this\x20env\x22\x20style=\x22font-size:11px;\x20padding:1px\x206px;\x22>*</span><if\x20:=\x22grantEntry.globalCoverAll\x22><span\x20class=\x22tf-hint\x22\x20style=\x22color:rgba(79,193,255,.35);\x22\x20title=\x22All\x20permissions\x20already\x20covered\x20by\x20global\x20*\x20or\x20god\x20mode\x22>inherited</span></if></div><span\x20class=\x22tf-env-remove\x22\x20@click=\x22removeGrantEnv(grantEntry.env)\x22>✕\x20remove</span></div><each\x20:of=\x22grantEntry.nGroups\x22\x20let:item=\x22nsGroup\x22><div\x20class=\x22tf-ns-row\x22><span\x20:class=\x22[\x27tf-ns-label\x27,\x20isNsAllSelectedForGrant(grantEntry.env,\x20nsGroup.ns)\x20?\x20\x27tf-ns-label--on\x27\x20:\x20\x27\x27]\x22\x20@click=\x22toggleGrantNamespace(grantEntry.env,\x20nsGroup.ns,\x20nsGroup.ops)\x22\x20title=\x22Toggle\x20all\x22>{{\x20nsGroup.ns\x20}}</span><div\x20class=\x22tf-pills\x22><span\x20:class=\x22[\x27tf-pill\x27,\x20\x27tf-pill--wild\x27,\x20isNsAllSelectedForGrant(grantEntry.env,\x20nsGroup.ns)\x20?\x20\x27tf-pill--wild-on\x27\x20:\x20isNsWildInheritedForGrant(grantEntry.env,\x20nsGroup.ns)\x20?\x20\x27tf-pill--wild-inherited\x27\x20:\x20\x27\x27]\x22\x20@click=\x22toggleGrantNamespace(grantEntry.env,\x20nsGroup.ns,\x20nsGroup.ops)\x22\x20title=\x22ns:*\x20—\x20toggle\x20all\x22>*</span><each\x20:of=\x22nsGroup.ops\x22\x20let:item=\x22grantPerm\x22><span\x20class=\x22tf-pill-wrap\x22\x20@mouseenter=\x22setHover($event,\x20grantPerm.perm,\x20grantPerm)\x22\x20@mouseleave=\x22clearHover()\x22><span\x20:class=\x22[\x27tf-pill\x27,\x20grantPerm.checked\x20?\x20\x27tf-pill--on\x27\x20:\x20grantPerm.inherited\x20?\x20\x27tf-pill--inherited\x27\x20:\x20\x27\x27,\x20grantPerm.hasDestructive\x20?\x20\x27tf-pill--destructive\x27\x20:\x20\x27\x27]\x22\x20@click=\x22toggleGrantPerm(grantEntry.env,\x20nsGroup.ns,\x20grantPerm.op,\x20nsGroup.ops.map(o\x20=>\x20o.op))\x22>{{\x20grantPerm.op\x20}}<if\x20:=\x22grantPerm.hasDestructive\x22><span\x20class=\x22tf-pill-warn\x22>⚠</span></if></span></span></each></div></div></each></div></each></div></if></if></if><if\x20:=\x22error\x22><div\x20class=\x22tf-error\x22>{{\x20error\x20}}</div></if><div\x20class=\x22tf-actions\x22><button\x20class=\x22tf-btn\x20tf-btn--secondary\x22\x20@click=\x22cancel()\x22\x20:disabled=\x22busy\x22>Cancel</button>\x20<button\x20class=\x22tf-btn\x20tf-btn--primary\x22\x20@click=\x22save()\x22\x20:disabled=\x22busy\x20||\x20!label.trim()\x22>Create\x20Token</button></div></if><if\x20:=\x22hoveredPermOp\x20&&\x20tooltipPos\x22><div\x20class=\x22tf-pip\x22\x20:style=\x22\x27left:\x27\x20+\x20tooltipPos.left\x20+\x20\x27px;\x20top:\x27\x20+\x20(tooltipPos.bottom\x20+\x204)\x20+\x20\x27px;\x27\x22><div\x20class=\x22tf-pip-perm\x22>{{\x20hoveredPermOp.perm\x20||\x20hoveredPerm\x20}}</div><if\x20:=\x22hoveredPermOp.summary\x22><div\x20class=\x22tf-pip-summary\x22>{{\x20hoveredPermOp.summary\x20}}</div></if><if\x20:=\x22hoveredPermOp.routes\x20&&\x20hoveredPermOp.routes.length\x22><div\x20class=\x22tf-pip-routes-hd\x22>Routes\x20({{\x20hoveredPermOp.routes.length\x20}})</div><each\x20:of=\x22hoveredPermOp.routes\x22\x20let:item=\x22r\x22><div\x20class=\x22tf-pip-route\x22><span\x20class=\x22tf-pip-method\x22>{{\x20r.method\x20}}</span>\x20<span\x20class=\x22tf-pip-path\x22>{{\x20r.path\x20}}</span><if\x20:=\x22r.destructive\x22><span\x20class=\x22tf-pip-bd\x22>destructive</span></if><if\x20:=\x22r.longRunning\x22><span\x20class=\x22tf-pip-lr\x22>long</span></if></div><if\x20:=\x22r.summary\x22><div\x20class=\x22tf-pip-route-desc\x22>{{\x20r.summary\x20}}</div></if></each></if><if\x20:=\x22!(hoveredPermOp.routes\x20&&\x20hoveredPermOp.routes.length)\x22><div\x20class=\x22tf-pip-gate\x22>Gate\x20—\x20combine\x20with\x20underlying\x20namespace\x20permissions.</div></if><if\x20:=\x22getImpliedBy(hoveredPerm).length\x22><div\x20class=\x22tf-pip-implied\x22>Implied\x20by:\x20{{\x20getImpliedBy(hoveredPerm).join(\x27,\x20\x27)\x20}}</div></if></div></if></div>','fair','password','message','catalog','132668mEoKhF','error','stringify','split','tooltipPos','hoveredPermOp','$ok','destructive','busy','read','bottom','createdToken','routes','expiresAt','POST','data','959BLAPba','API','localeCompare','system','1790604jaiQgq','now','Token\x20label\x20is\x20required','filter','loadCatalog','showPassword','modal','getBoundingClientRect','then','transparent','trim','envs','_fallback','hoveredPerm','onDone','strong','scope','$attr','catch','Token\x20copied\x20to\x20clipboard','entries','includes','currentTarget','isEnvAllSelected','left','toast','some','332469nPNXmj','getTime','passwordStrength','catalogLoading','name','writeText'];_0x4e57=function(){return _0x3b27ce;};return _0x4e57();}(function(_0x2625e8,_0x2d0d87){const _0x82c656=_0x52b1,_0x19b077=_0x2625e8();while(!![]){try{const _0x7219b3=parseInt(_0x82c656(0xdb))/0x1+parseInt(_0x82c656(0xac))/0x2+-parseInt(_0x82c656(0x81))/0x3+parseInt(_0x82c656(0x84))/0x4*(-parseInt(_0x82c656(0x94))/0x5)+parseInt(_0x82c656(0x8e))/0x6+parseInt(_0x82c656(0xbc))/0x7*(parseInt(_0x82c656(0xe1))/0x8)+-parseInt(_0x82c656(0xc0))/0x9;if(_0x7219b3===_0x2d0d87)break;else _0x19b077['push'](_0x19b077['shift']());}catch(_0x377b92){_0x19b077['push'](_0x19b077['shift']());}}}(_0x4e57,0x28e80));import _0x1b9311 from'@toolkit/elements/ok-icon.ok.js';import _0x548e06 from'@toolkit/core/ok-overlay.ok.js';function _0x52b1(_0x21b160,_0x4ebf17){_0x21b160=_0x21b160-0x7a;const _0x4e5726=_0x4e57();let _0x52b1a2=_0x4e5726[_0x21b160];return _0x52b1a2;}export default{'tag':'create-token-modal','dependencies':[_0x548e06,_0x1b9311],'attr':{'modal':{},'onDone':{}},'context':_0x57fb72=>({'busy':!0x1,'showToken':!0x1,'createdToken':null,'error':null,'label':'','name':'','password':'','showPassword':!0x1,'expiresAt':'','catalog':null,'catalogError':!0x1,'catalogLoading':!0x1,'godMode':!0x1,'selectedPerms':{},'grants':{},'collapsedScopes':{},'hoveredPerm':null,'hoveredPermOp':null,'tooltipPos':null,'setHover'(_0x351e7a,_0x8bbe49,_0x1f3939){const _0x4e5b9a=_0x52b1,_0x57ce52=_0x351e7a[_0x4e5b9a(0xd6)][_0x4e5b9a(0xc7)]();this[_0x4e5b9a(0xcd)]=_0x8bbe49,this[_0x4e5b9a(0xb1)]=_0x1f3939;const _0x46ae2a=_0x57ce52;this[_0x4e5b9a(0xb0)]={'left':_0x46ae2a[_0x4e5b9a(0xd8)],'bottom':_0x46ae2a[_0x4e5b9a(0xb6)]};},'clearHover'(){const _0x2ba152=_0x52b1;this[_0x2ba152(0xcd)]=null,this[_0x2ba152(0xb1)]=null,this['tooltipPos']=null;},async 'loadCatalog'(){const _0x3ab83b=_0x52b1;this[_0x3ab83b(0xde)]=!0x0;try{const _0x5c6ee5=await fetch(__ENV__[_0x3ab83b(0x8a)]+'/auth/permissions');if(!_0x5c6ee5['ok'])throw new Error('Failed');const _0x450ce6=await _0x5c6ee5['json']();this[_0x3ab83b(0xab)]=_0x450ce6[_0x3ab83b(0xbb)]??_0x450ce6[_0x3ab83b(0x96)];}catch{this[_0x3ab83b(0x86)]=!0x0;}finally{this[_0x3ab83b(0xde)]=!0x1;}},'togglePerm'(_0xadf08d,_0x306730,_0x19ce8a){const _0x25d7f5=_0x52b1;if(this[_0x25d7f5(0x9c)])return;const _0x4490e9=_0xadf08d+':*';if(this[_0x25d7f5(0x9a)][_0x4490e9]){const _0x511352={...this['selectedPerms']};delete _0x511352[_0x4490e9];for(const _0x3bd3cc of _0x19ce8a)_0x3bd3cc!==_0x306730&&(_0x511352[_0xadf08d+':'+_0x3bd3cc]=!0x0);this[_0x25d7f5(0x9a)]=_0x511352;}else this[_0x25d7f5(0x9a)]={...this['selectedPerms'],[_0xadf08d+':'+_0x306730]:!this[_0x25d7f5(0x9a)][_0xadf08d+':'+_0x306730]};},'toggleGodMode'(){const _0x33b571=_0x52b1;this[_0x33b571(0x9c)]=!this[_0x33b571(0x9c)],this[_0x33b571(0x9c)]&&(this[_0x33b571(0x9a)]={});},'toggleNamespace'(_0x596379,_0x4ffb11){const _0x17292a=_0x52b1;if(this[_0x17292a(0x9c)])return;const _0x394ec3=_0x596379+':*',_0x60be4b={...this[_0x17292a(0x9a)]};if(_0x60be4b[_0x394ec3])delete _0x60be4b[_0x394ec3];else{for(const _0xd6091e of _0x4ffb11)delete _0x60be4b[_0x596379+':'+_0xd6091e];_0x60be4b[_0x394ec3]=!0x0;}this['selectedPerms']=_0x60be4b;},'toggleScope'(_0x57b5bd){const _0x522824=_0x52b1;this[_0x522824(0x7a)]={...this[_0x522824(0x7a)],[_0x57b5bd]:!this[_0x522824(0x7a)][_0x57b5bd]};},'isCollapsed'(_0x36e278){const _0x1359cb=_0x52b1;return!!this[_0x1359cb(0x7a)][_0x36e278];},'scopeSelectionCount'(_0x300d73){const _0x121ddd=_0x52b1;let _0x28a25f=0x0,_0x50ac38=0x0;for(const _0x2e66f8 of _0x300d73[_0x121ddd(0x7e)])for(const _0x58161e of _0x2e66f8[_0x121ddd(0x95)])_0x50ac38++,(this['selectedPerms'][_0x2e66f8['ns']+':'+_0x58161e]||this['selectedPerms'][_0x2e66f8['ns']+':*'])&&_0x28a25f++;return _0x28a25f>0x0?_0x28a25f+'/'+_0x50ac38:'';},'addGrantEnv'(_0x2d445f){const _0x470cd5=_0x52b1;_0x2d445f&&!this[_0x470cd5(0xa2)][_0x2d445f]&&(this[_0x470cd5(0xa2)]={...this[_0x470cd5(0xa2)],[_0x2d445f]:{}});},'removeGrantEnv'(_0x45fe16){const _0x4c7cd1=_0x52b1,_0x28be73={...this[_0x4c7cd1(0xa2)]};delete _0x28be73[_0x45fe16],this[_0x4c7cd1(0xa2)]=_0x28be73;},'availableGrantEnvs'(){const _0x3c17ac=_0x52b1;return this['catalog']?.['envs']?this[_0x3c17ac(0xab)][_0x3c17ac(0xcb)][_0x3c17ac(0xc3)](_0x4c6cf2=>!this[_0x3c17ac(0xa2)][_0x4c6cf2]):[];},'getPermSections'(){const _0xb00013=_0x52b1;if(!this['catalog']?.[_0xb00013(0x7e)])return[];const _0x58675b=this['catalog'][_0xb00013(0x8c)]||[_0xb00013(0xb5),'query','run','write',_0xb00013(0x7d),'peer'];return[{'scope':_0xb00013(0x83),'label':'Environment'},{'scope':_0xb00013(0xbf),'label':_0xb00013(0xa6)},{'scope':_0xb00013(0xa1),'label':_0xb00013(0x9e)}]['map'](_0x9a09d5=>({'scope':_0x9a09d5['scope'],'label':_0x9a09d5[_0xb00013(0xe2)],'namespaces':Object[_0xb00013(0xd4)](this[_0xb00013(0xab)]['namespaces'])['filter'](([,_0x550e05])=>_0x550e05[_0xb00013(0xd0)]===_0x9a09d5[_0xb00013(0xd0)])[_0xb00013(0xa4)](([_0x265be6,_0x600701])=>({'ns':_0x265be6,'nsLabel':_0x600701[_0xb00013(0xe2)],'nsDescription':_0x600701['description'],'operations':Object['keys'](_0x600701[_0xb00013(0x95)])[_0xb00013(0x87)]((_0x4db96e,_0x1e2c3c)=>{const _0xcdc768=_0xb00013,_0x1c8689=_0x58675b[_0xcdc768(0x82)](_0x4db96e),_0x2cdb7d=_0x58675b['indexOf'](_0x1e2c3c);return-0x1===_0x1c8689&&-0x1===_0x2cdb7d?_0x4db96e['localeCompare'](_0x1e2c3c):-0x1===_0x1c8689?0x1:-0x1===_0x2cdb7d?-0x1:_0x1c8689-_0x2cdb7d;}),'operationMeta':_0x600701[_0xb00013(0x95)]}))}))[_0xb00013(0xc3)](_0x3445f4=>_0x3445f4['namespaces']['length']>0x0);},'getPermOps'(_0x4ba369,_0x3ce660,_0x370813){const _0x5f0e9e=_0x52b1;return _0x3ce660[_0x5f0e9e(0xa4)](_0x44524e=>{const _0xccc341=_0x5f0e9e,_0x38b0cd=_0x370813?.[_0x44524e]||{},_0x53346b=_0x38b0cd[_0xccc341(0xb8)]||[];return{'ns':_0x4ba369,'op':_0x44524e,'perm':_0x4ba369+':'+_0x44524e,'summary':_0x38b0cd['summary']||'','routes':_0x53346b,'hasDestructive':_0x53346b[_0xccc341(0xda)](_0x4d05fb=>_0x4d05fb[_0xccc341(0xb3)]),'checked':!!this[_0xccc341(0x9a)][_0x4ba369+':'+_0x44524e]||!!this[_0xccc341(0x9a)][_0x4ba369+':*']};});},'getImpliedBy'(_0xe0bf42){const _0x8fcf37=_0x52b1,_0x8e0aa6=this[_0x8fcf37(0xab)]?.['implies']||{};return Object[_0x8fcf37(0xd4)](_0x8e0aa6)[_0x8fcf37(0xc3)](([,_0x13a81b])=>_0x13a81b[_0x8fcf37(0xd5)](_0xe0bf42))['map'](([_0x39b417])=>_0x39b417);},'getGrantEntries'(){const _0x2b8b75=_0x52b1;if(!this[_0x2b8b75(0xab)]?.[_0x2b8b75(0x7e)])return[];const _0x1d7e4d=this[_0x2b8b75(0xab)][_0x2b8b75(0x8c)]||[_0x2b8b75(0xb5),_0x2b8b75(0x8d),_0x2b8b75(0x8f),_0x2b8b75(0x9b),_0x2b8b75(0x7d),'peer'],_0x4fdab8=Object[_0x2b8b75(0xd4)](this['catalog'][_0x2b8b75(0x7e)])['filter'](([,_0x594e82])=>'env'===_0x594e82[_0x2b8b75(0xd0)])[_0x2b8b75(0xa4)](([_0x1d4568,_0x57ad3d])=>({'ns':_0x1d4568,'operations':Object[_0x2b8b75(0xa3)](_0x57ad3d[_0x2b8b75(0x95)])[_0x2b8b75(0x87)]((_0x4b7538,_0x2d670b)=>{const _0x5b372c=_0x2b8b75,_0x3a3c6d=_0x1d7e4d[_0x5b372c(0x82)](_0x4b7538),_0x257528=_0x1d7e4d['indexOf'](_0x2d670b);return-0x1===_0x3a3c6d&&-0x1===_0x257528?_0x4b7538[_0x5b372c(0xbe)](_0x2d670b):-0x1===_0x3a3c6d?0x1:-0x1===_0x257528?-0x1:_0x3a3c6d-_0x257528;})})),_0x4a6cfb=this['selectedPerms'],_0xc85fd1=this[_0x2b8b75(0x9c)];return Object[_0x2b8b75(0xd4)](this[_0x2b8b75(0xa2)])[_0x2b8b75(0xa4)](([_0x15304b,_0x59bfe7])=>({'env':_0x15304b,'globalCoverAll':_0xc85fd1||!!_0x4a6cfb['*'],'nGroups':_0x4fdab8[_0x2b8b75(0xa4)](({ns:_0x51e59e,operations:_0x37b24b})=>({'ns':_0x51e59e,'ops':_0x37b24b[_0x2b8b75(0xa4)](_0x37f66b=>{const _0x41fe13=_0x2b8b75,_0xeae53f=_0x51e59e+':'+_0x37f66b,_0x5ad7b5=!!_0x59bfe7[_0xeae53f]||!!_0x59bfe7[_0x51e59e+':*']||!!_0x59bfe7['*'],_0x4e3fce=!_0x5ad7b5&&(_0xc85fd1||!!_0x4a6cfb['*']||!!_0x4a6cfb[_0x51e59e+':*']||!!_0x4a6cfb[_0xeae53f]),_0x29fd97=this[_0x41fe13(0xab)]['namespaces'][_0x51e59e]?.['operations']?.[_0x37f66b]||{};return{'perm':_0xeae53f,'op':_0x37f66b,'summary':_0x29fd97['summary']||'','routes':_0x29fd97[_0x41fe13(0xb8)]||[],'hasDestructive':(_0x29fd97[_0x41fe13(0xb8)]||[])[_0x41fe13(0xda)](_0x10b2dd=>_0x10b2dd[_0x41fe13(0xb3)]),'checked':_0x5ad7b5,'inherited':_0x4e3fce};})}))}));},'isNsAllSelected'(_0x5f1bb){const _0x531d01=_0x52b1;return!!this[_0x531d01(0x9a)][_0x5f1bb+':*'];},'isEnvAllSelected'(_0x6fbe48){const _0x14a15f=_0x52b1;return!!this[_0x14a15f(0xa2)][_0x6fbe48]?.['*'];},'isEnvWildInherited'(_0x141741){const _0x3f2ce0=_0x52b1;return!this[_0x3f2ce0(0xd7)](_0x141741)&&(this[_0x3f2ce0(0x9c)]||!!this[_0x3f2ce0(0x9a)]['*']);},'isNsAllSelectedForGrant'(_0x46f8e8,_0x6f481c){const _0x21f3eb=_0x52b1;return!!this[_0x21f3eb(0xa2)][_0x46f8e8]?.['*']||!!this[_0x21f3eb(0xa2)][_0x46f8e8]?.[_0x6f481c+':*'];},'isNsWildInheritedForGrant'(_0x224544,_0x11eb50){const _0x608d=_0x52b1;return!this[_0x608d(0x93)](_0x224544,_0x11eb50)&&(this[_0x608d(0x9c)]||!!this[_0x608d(0x9a)]['*']||!!this[_0x608d(0x9a)][_0x11eb50+':*']);},'toggleGrantPerm'(_0x3d7b9b,_0x496064,_0x38ba5c,_0x506f1f){const _0x28f0de=_0x52b1,_0x2b2e6f=_0x496064+':'+_0x38ba5c,_0x2e1b8e=_0x496064+':*',_0x41ea94={...this['grants'][_0x3d7b9b]||{}};if(_0x41ea94['*'])delete _0x41ea94['*'];else{if(_0x41ea94[_0x2e1b8e]){delete _0x41ea94[_0x2e1b8e];for(const _0xbe6865 of _0x506f1f)_0xbe6865!==_0x38ba5c&&(_0x41ea94[_0x496064+':'+_0xbe6865]=!0x0);}else _0x41ea94[_0x2b2e6f]=!_0x41ea94[_0x2b2e6f];}this[_0x28f0de(0xa2)]={...this[_0x28f0de(0xa2)],[_0x3d7b9b]:_0x41ea94};},'toggleGrantNamespace'(_0x7a69fc,_0x216b3b,_0x140631){const _0x338080=_0x52b1,_0x3c0f09=_0x216b3b+':*',_0x5dcc8b={...this['grants'][_0x7a69fc]||{}};if(_0x5dcc8b['*'])delete _0x5dcc8b['*'];else{if(_0x5dcc8b[_0x3c0f09])delete _0x5dcc8b[_0x3c0f09];else{for(const _0x27bd01 of _0x140631)delete _0x5dcc8b[_0x27bd01['perm']];_0x5dcc8b[_0x3c0f09]=!0x0;}}this[_0x338080(0xa2)]={...this[_0x338080(0xa2)],[_0x7a69fc]:_0x5dcc8b};},'toggleGrantAllPerms'(_0x4d1bc8){const _0x57eaad=_0x52b1,_0x4dd9a7=this[_0x57eaad(0xa2)][_0x4d1bc8]||{};if(_0x4dd9a7['*']){const _0xa7ddd3={..._0x4dd9a7};delete _0xa7ddd3['*'],this[_0x57eaad(0xa2)]={...this[_0x57eaad(0xa2)],[_0x4d1bc8]:_0xa7ddd3};}else this['grants']={...this[_0x57eaad(0xa2)],[_0x4d1bc8]:{'*':!0x0}};},'validateForm'(){const _0x4d3108=_0x52b1;if(!this[_0x4d3108(0xe2)][_0x4d3108(0xca)]())throw new Error(_0x4d3108(0xc2));return!0x0;},async 'save'(){const _0x15b4da=_0x52b1;this[_0x15b4da(0xad)]=null;try{this[_0x15b4da(0x99)](),this['busy']=!0x0;const _0x54e74e={'label':this['label']['trim']()};if(this[_0x15b4da(0xb9)]){const _0x3a1006=new Date(this[_0x15b4da(0xb9)])[_0x15b4da(0xdc)]();_0x3a1006>Date[_0x15b4da(0xc1)]()&&(_0x54e74e[_0x15b4da(0x8b)]=_0x3a1006-Date['now']());}if(this[_0x15b4da(0x9c)])_0x54e74e[_0x15b4da(0xa0)]=['*'];else{if(this[_0x15b4da(0x86)]&&void 0x0!==this[_0x15b4da(0x9a)][_0x15b4da(0xcc)]){const _0x59c012=String(this[_0x15b4da(0x9a)]['_fallback']||'')[_0x15b4da(0xaf)](',')[_0x15b4da(0xa4)](_0x45ab76=>_0x45ab76[_0x15b4da(0xca)]())[_0x15b4da(0xc3)](_0x5599bd=>_0x5599bd);_0x59c012['length']&&(_0x54e74e['permissions']=_0x59c012);}else{const _0x290a57=Object[_0x15b4da(0xd4)](this[_0x15b4da(0x9a)])[_0x15b4da(0xc3)](([_0x2af830,_0x11d4e6])=>_0x11d4e6&&_0x15b4da(0xcc)!==_0x2af830)['map'](([_0x138553])=>_0x138553);_0x290a57['length']&&(_0x54e74e[_0x15b4da(0xa0)]=_0x290a57);}}const _0x4dab0f={};for(const [_0x39d7d5,_0x172ff8]of Object['entries'](this['grants'])){const _0x23af2d=Object[_0x15b4da(0xd4)](_0x172ff8)['filter'](([,_0x164a1d])=>_0x164a1d)[_0x15b4da(0xa4)](([_0x3c9e9e])=>_0x3c9e9e);_0x23af2d['length']&&(_0x4dab0f[_0x39d7d5]=_0x23af2d);}Object[_0x15b4da(0xa3)](_0x4dab0f)[_0x15b4da(0x80)]&&(_0x54e74e[_0x15b4da(0xa2)]=_0x4dab0f),this[_0x15b4da(0xdf)][_0x15b4da(0xca)]()&&(_0x54e74e[_0x15b4da(0xdf)]=this['name'][_0x15b4da(0xca)](),this[_0x15b4da(0xa9)]&&(_0x54e74e[_0x15b4da(0xa9)]=this['password']));const _0x1eaabe=await fetch(__ENV__[_0x15b4da(0xbd)]+_0x15b4da(0x7b),{'method':_0x15b4da(0xba),'headers':{'Content-Type':_0x15b4da(0x85)},'body':JSON[_0x15b4da(0xae)](_0x54e74e)});if(!_0x1eaabe['ok']){const _0x13dacf=await _0x1eaabe[_0x15b4da(0x9f)]();throw new Error(_0x13dacf?.[_0x15b4da(0xad)]?.['message']||_0x15b4da(0x92));}const _0x21c3a3=await _0x1eaabe['json']();this['createdToken']=_0x21c3a3[_0x15b4da(0xbb)]??_0x21c3a3,this[_0x15b4da(0xa5)]=!0x0;}catch(_0x3cc314){this[_0x15b4da(0xad)]=_0x3cc314?.[_0x15b4da(0xaa)]||String(_0x3cc314),_0x57fb72['$ok'][_0x15b4da(0x9d)]?.[_0x15b4da(0xd9)]?.[_0x15b4da(0xad)]?.(this[_0x15b4da(0xad)]);}finally{this[_0x15b4da(0xb4)]=!0x1;}},'copyToken'(){const _0x553d5e=_0x52b1;this['createdToken']?.['token']&&navigator[_0x553d5e(0x89)]?.[_0x553d5e(0xe0)](this[_0x553d5e(0xb7)][_0x553d5e(0x7c)])[_0x553d5e(0xc8)](()=>{const _0x3a8649=_0x553d5e;_0x57fb72[_0x3a8649(0xb2)][_0x3a8649(0x9d)]?.['toast']?.[_0x3a8649(0x88)]?.(_0x3a8649(0xd3));})[_0x553d5e(0xd2)](()=>{const _0x40dcdb=_0x553d5e;_0x57fb72[_0x40dcdb(0xb2)][_0x40dcdb(0x9d)]?.['toast']?.[_0x40dcdb(0xad)]?.('Failed\x20to\x20copy\x20token');});},'dismissToken'(){const _0x556826=_0x52b1;_0x57fb72[_0x556826(0xd1)][_0x556826(0xc6)][_0x556826(0x98)](),_0x57fb72[_0x556826(0xd1)][_0x556826(0xce)]?.(this[_0x556826(0xb7)]);},'cancel'(){const _0x10f4bd=_0x52b1;_0x57fb72['$attr'][_0x10f4bd(0xc6)]['close']();},'passwordStrength'(_0x442d65){const _0x4c3301=_0x52b1;if(!_0x442d65)return 0x0;let _0x29c7e9=0x0;return _0x442d65[_0x4c3301(0x80)]>=0x8&&_0x29c7e9++,_0x442d65[_0x4c3301(0x80)]>=0xe&&_0x29c7e9++,/[A-Z]/[_0x4c3301(0x7f)](_0x442d65)&&_0x29c7e9++,/[0-9]/[_0x4c3301(0x7f)](_0x442d65)&&_0x29c7e9++,/[^A-Za-z0-9]/['test'](_0x442d65)&&_0x29c7e9++,_0x29c7e9;},'passwordStrengthLabel'(_0x44b617){const _0x5e8eb3=_0x52b1,_0x1dbfd5=this[_0x5e8eb3(0xdd)](_0x44b617);return _0x44b617?_0x1dbfd5<=0x1?_0x5e8eb3(0x91):_0x1dbfd5<=0x3?_0x5e8eb3(0xa8):_0x5e8eb3(0xcf):'';},'passwordStrengthColor'(_0x100e8f){const _0x3d8167=_0x52b1,_0x35979a=this[_0x3d8167(0xdd)](_0x100e8f);return _0x100e8f?_0x35979a<=0x1?_0x3d8167(0x97):_0x35979a<=0x3?_0x3d8167(0x90):'var(--okdb-success)':_0x3d8167(0xc9);},'toggleShowPassword'(){const _0x26e414=_0x52b1;this[_0x26e414(0xc5)]=!this[_0x26e414(0xc5)];}}),'mount'(_0x33ce42){const _0x4970e4=_0x52b1;this[_0x4970e4(0xc4)]();},'style':'.tf-header-fields,.tf-root,create-token-modal{display:flex;flex-direction:column}.tf-root{gap:0;width:520px;max-width:88vw;position:relative}.tf-header-fields{gap:12px;padding-bottom:16px}.tf-field,.tf-header-row{display:flex;gap:10px}.tf-field{flex-direction:column;gap:4px;min-width:0}.tf-label{font-size:10px;color:var(--okdb-app-fg-muted);text-transform:uppercase;letter-spacing:.07em;font-weight:600}.tf-creds-section{display:flex;flex-direction:column;gap:8px;padding:10px\x2012px;border:1px\x20solid\x20var(--okdb-app-surface);border-radius:6px;background:rgba(0,0,0,.15)}.tf-creds-label{font-size:10px;color:var(--okdb-app-fg-muted);text-transform:uppercase;letter-spacing:.07em;font-weight:600}.tf-creds-hint{font-size:10px;color:var(--okdb-app-fg-subtle);line-height:1.4;margin-top:-4px}.tf-creds-row{display:flex;gap:10px}.tf-pw-wrap{position:relative;display:flex;align-items:center}.tf-pw-wrap\x20.tf-input{padding-right:30px}.tf-pw-eye{position:absolute;right:7px;background:0\x200;border:0;color:var(--okdb-app-fg-subtle);cursor:pointer;font-size:12px;padding:0;line-height:1;user-select:none}.tf-pw-eye:hover,.tf-scope-header:hover\x20.tf-scope-name{color:var(--okdb-accent-muted)}.tf-strength-bar{height:2px;border-radius:1px;background:var(--okdb-app-surface);margin-top:3px;overflow:hidden}.tf-strength-fill{height:100%;border-radius:1px;transition:width\x20.2s,background-color\x20.2s}.tf-strength-text{font-size:9px;margin-top:2px;text-transform:uppercase;letter-spacing:.05em}.tf-input{padding:6px\x208px;border:1px\x20solid\x20var(--okdb-app-surface-2);border-radius:4px;background:0\x200;color:var(--okdb-app-fg-strong);font-size:12px;font-family:monospace;width:100%;box-sizing:border-box}.tf-input:focus{outline:0;border-color:rgba(79,193,255,.4)}.tf-hint{font-size:10px;color:var(--okdb-app-fg-subtle);line-height:1.4}.tf-divider{height:1px;background:var(--okdb-app-surface);margin-bottom:12px}.tf-section{display:flex;flex-direction:column;gap:6px;margin-bottom:10px}.tf-scope-header{display:flex;align-items:center;gap:8px;cursor:pointer;user-select:none;padding:2px\x200}.tf-scope-name{font-size:10px;text-transform:uppercase;letter-spacing:.07em;color:var(--okdb-app-fg-muted);font-weight:600;transition:color\x20.1s}.tf-scope-count{font-size:10px;color:var(--okdb-accent);opacity:.8}.tf-scope-chevron{margin-left:auto;font-size:10px;color:var(--okdb-app-fg-subtle)}.tf-ns-block{display:flex;flex-direction:column;gap:4px;margin-bottom:8px}.tf-ns-row{display:flex;align-items:baseline;gap:8px}.tf-ns-label{font-size:11px;color:var(--okdb-app-fg-subtle);font-family:monospace;flex-shrink:0;cursor:pointer;user-select:none;transition:color\x20.1s;font-weight:600}.tf-ns-label:hover{color:var(--okdb-accent-muted)}.tf-ns-label--on{color:var(--okdb-accent)}.tf-ns-desc{font-size:10px;color:var(--okdb-app-fg-subtle);line-height:1.4}.tf-pills{display:flex;flex-wrap:wrap;gap:4px;padding-left:4px}.tf-pill-wrap{display:inline-flex}.tf-pill-warn{font-size:7px;color:rgba(215,150,60,.85);vertical-align:super;margin-left:1px;line-height:0}.tf-pill--destructive{border-color:rgba(200,140,60,.35)}.tf-pill--destructive:hover{border-color:rgba(200,140,60,.6)}.tf-pill{display:inline-flex;align-items:center;padding:2px\x208px;border-radius:4px;font-size:11px;font-family:monospace;border:1px\x20solid\x20var(--okdb-app-surface-2);color:var(--okdb-app-fg-subtle);cursor:pointer;user-select:none;transition:background\x20.1s,border-color\x20.1s,color\x20.1s;white-space:nowrap}.tf-pill:hover{border-color:var(--okdb-app-border);color:var(--okdb-accent-muted)}.tf-pill--on{background:rgba(79,193,255,.1);border-color:rgba(79,193,255,.35);color:var(--okdb-accent)}.tf-pill--on:hover{background:rgba(79,193,255,.15)}.tf-pill--full{border-color:rgba(251,191,36,.2);color:#5a4a20}.tf-pill--full-on,.tf-pill--full:hover{border-color:rgba(251,191,36,.4);color:var(--okdb-warning-text)}.tf-pill--full-on{background:rgba(251,191,36,.1)}.tf-pill--full-on:hover{background:rgba(251,191,36,.16)}.tf-pill--add{border-style:dashed;border-color:var(--okdb-app-surface-2);color:var(--okdb-app-fg-subtle)}.tf-pill--add:hover{border-color:var(--okdb-app-border);color:var(--okdb-accent-muted);border-style:solid}.tf-pill--wild{border-color:rgba(251,191,36,.2);color:#5a4a20;font-weight:700}.tf-pill--wild-on,.tf-pill--wild:hover{border-color:rgba(251,191,36,.4);color:var(--okdb-warning-text)}.tf-pill--wild-on{background:rgba(251,191,36,.1);font-weight:700}.tf-pill--wild-on:hover{background:rgba(251,191,36,.16)}.tf-pill--wild-inherited{border-style:dashed;border-color:rgba(251,191,36,.45);color:rgba(251,191,36,.6);background:rgba(251,191,36,.05);font-weight:700}.tf-pill--wild-inherited:hover{border-color:rgba(251,191,36,.65);color:rgba(251,191,36,.8);background:rgba(251,191,36,.09)}.tf-pill--inherited{border-style:dashed;border-color:rgba(79,193,255,.5);color:rgba(79,193,255,.65);background:rgba(79,193,255,.06)}.tf-pill--inherited:hover{border-color:rgba(79,193,255,.7);color:rgba(79,193,255,.85);background:rgba(79,193,255,.1)}.tf-env-row{display:flex;flex-direction:column;gap:6px;padding:8px\x200\x2010px;border-top:1px\x20solid\x20var(--okdb-app-bg)}.tf-env-row-header{display:flex;align-items:center;justify-content:space-between}.tf-env-name{font-size:11px;font-weight:600;color:var(--okdb-app-fg-muted);font-family:monospace}.tf-env-all,.tf-env-remove{font-size:10px;color:var(--okdb-app-fg-subtle);cursor:pointer;user-select:none;padding:1px\x204px;transition:color\x20.1s}.tf-env-remove:hover{color:var(--okdb-danger)}.tf-env-all:hover{color:var(--okdb-accent-muted)}.tf-env-all--on{color:var(--okdb-warning-text)}.tf-env-all--on:hover{color:var(--okdb-warning)}.tf-token-box{display:flex;flex-direction:column;gap:10px;padding:14px;border:1px\x20solid\x20rgba(79,193,255,.2);border-radius:6px;background:rgba(79,193,255,.03)}.tf-token-warn{font-size:11px;color:var(--okdb-warning-text);line-height:1.5}.tf-token-value{font-family:monospace;font-size:11px;color:var(--okdb-accent);word-break:break-all;padding:8px;background:rgba(0,0,0,.3);border-radius:4px;border:1px\x20solid\x20rgba(79,193,255,.15);cursor:text;user-select:all}.tf-error{font-size:11px;color:var(--okdb-danger);padding:6px\x200\x200}.tf-actions{display:flex;gap:8px;justify-content:flex-end;padding-top:14px;margin-top:4px;border-top:1px\x20solid\x20var(--okdb-app-surface)}.tf-btn{padding:5px\x2012px;border-radius:5px;font-size:12px;border:1px\x20solid;background:0\x200;cursor:pointer;transition:background\x20.12s,color\x20.12s}.tf-btn:disabled{opacity:.4;cursor:not-allowed}.tf-btn--primary{border-color:var(--okdb-app-border);color:var(--okdb-accent-muted)}.tf-btn--primary:not(:disabled):hover{background:var(--okdb-app-surface);border-color:var(--okdb-app-border);color:var(--okdb-sky)}.tf-btn--secondary{border-color:var(--okdb-app-surface-2);color:var(--okdb-app-fg-subtle)}.tf-btn--secondary:hover{background:var(--okdb-app-bg)}.tf-pip{position:fixed;z-index:9999;min-width:260px;max-width:460px;background:var(--okdb-app-bg-deep);border:1px\x20solid\x20var(--okdb-app-surface-2);border-radius:5px;padding:8px\x2010px;box-shadow:0\x204px\x2018px\x20rgba(0,0,0,.75);pointer-events:none;white-space:normal;font-size:11px}.tf-pip-perm{font-family:monospace;font-size:10px;color:var(--okdb-accent);margin-bottom:3px}.tf-pip-summary{color:var(--okdb-accent-muted);line-height:1.4;margin-bottom:5px}.tf-pip-routes-hd{font-size:9px;color:var(--okdb-app-fg-subtle);text-transform:uppercase;letter-spacing:.06em;margin-bottom:3px}.tf-pip-route{display:flex;align-items:baseline;gap:5px;padding:1px\x200}.tf-pip-method{font-family:monospace;font-size:9px;color:var(--okdb-accent);width:34px;flex-shrink:0}.tf-pip-path{font-family:monospace;font-size:10px;color:var(--okdb-accent-muted);word-break:break-all}.tf-pip-bd,.tf-pip-lr{font-size:8px;border-radius:2px;padding:0\x203px;flex-shrink:0}.tf-pip-bd{color:var(--okdb-danger);background:rgba(180,50,40,.15)}.tf-pip-lr{color:var(--okdb-app-fg-muted);background:rgba(100,100,100,.12)}.tf-pip-route-desc{font-size:9px;color:var(--okdb-app-fg-subtle);padding-left:39px;margin-bottom:1px}.tf-pip-gate,.tf-pip-implied{color:var(--okdb-app-fg-subtle);font-size:10px}.tf-pip-implied{margin-top:5px;font-size:9px;font-family:monospace}','template':_0x4cef19(0xa7)};
1
+ import e from"@toolkit/elements/ok-icon.ok.js";import t from"@toolkit/core/ok-overlay.ok.js";export default{tag:"create-token-modal",dependencies:[t,e],attr:{modal:{},onDone:{}},context:e=>({busy:!1,showToken:!1,createdToken:null,error:null,label:"",name:"",password:"",showPassword:!1,expiresAt:"",catalog:null,catalogError:!1,catalogLoading:!1,godMode:!1,selectedPerms:{},grants:{},collapsedScopes:{},hoveredPerm:null,hoveredPermOp:null,tooltipPos:null,setHover(e,t,s){const o=e.currentTarget.getBoundingClientRect();this.hoveredPerm=t,this.hoveredPermOp=s;const r=o;this.tooltipPos={left:r.left,bottom:r.bottom}},clearHover(){this.hoveredPerm=null,this.hoveredPermOp=null,this.tooltipPos=null},async loadCatalog(){this.catalogLoading=!0;try{const e=await fetch(`${__ENV__.ADMIN_API}/auth/permissions`);if(!e.ok)throw new Error("Failed");const t=await e.json();this.catalog=t.data??t.result}catch{this.catalogError=!0}finally{this.catalogLoading=!1}},togglePerm(e,t,s){if(this.godMode)return;const o=`${e}:*`;if(this.selectedPerms[o]){const r={...this.selectedPerms};delete r[o];for(const o of s)o!==t&&(r[`${e}:${o}`]=!0);this.selectedPerms=r}else this.selectedPerms={...this.selectedPerms,[`${e}:${t}`]:!this.selectedPerms[`${e}:${t}`]}},toggleGodMode(){this.godMode=!this.godMode,this.godMode&&(this.selectedPerms={})},toggleNamespace(e,t){if(this.godMode)return;const s=`${e}:*`,o={...this.selectedPerms};if(o[s])delete o[s];else{for(const s of t)delete o[`${e}:${s}`];o[s]=!0}this.selectedPerms=o},toggleScope(e){this.collapsedScopes={...this.collapsedScopes,[e]:!this.collapsedScopes[e]}},isCollapsed(e){return!!this.collapsedScopes[e]},scopeSelectionCount(e){let t=0,s=0;for(const o of e.namespaces)for(const e of o.operations)s++,(this.selectedPerms[`${o.ns}:${e}`]||this.selectedPerms[`${o.ns}:*`])&&t++;return t>0?`${t}/${s}`:""},addGrantEnv(e){e&&!this.grants[e]&&(this.grants={...this.grants,[e]:{}})},removeGrantEnv(e){const t={...this.grants};delete t[e],this.grants=t},availableGrantEnvs(){return this.catalog?.envs?this.catalog.envs.filter(e=>!this.grants[e]):[]},getPermSections(){if(!this.catalog?.namespaces)return[];const e=this.catalog.opOrder||["read","query","run","write","work","peer"];return[{scope:"env",label:"Environment"},{scope:"system",label:"System"},{scope:"gate",label:"Gate"}].map(t=>({scope:t.scope,label:t.label,namespaces:Object.entries(this.catalog.namespaces).filter(([,e])=>e.scope===t.scope).map(([t,s])=>({ns:t,nsLabel:s.label,nsDescription:s.description,operations:Object.keys(s.operations).sort((t,s)=>{const o=e.indexOf(t),r=e.indexOf(s);return-1===o&&-1===r?t.localeCompare(s):-1===o?1:-1===r?-1:o-r}),operationMeta:s.operations}))})).filter(e=>e.namespaces.length>0)},getPermOps(e,t,s){return t.map(t=>{const o=s?.[t]||{},r=o.routes||[];return{ns:e,op:t,perm:`${e}:${t}`,summary:o.summary||"",routes:r,hasDestructive:r.some(e=>e.destructive),checked:!!this.selectedPerms[`${e}:${t}`]||!!this.selectedPerms[`${e}:*`]}})},getImpliedBy(e){const t=this.catalog?.implies||{};return Object.entries(t).filter(([,t])=>t.includes(e)).map(([e])=>e)},getGrantEntries(){if(!this.catalog?.namespaces)return[];const e=this.catalog.opOrder||["read","query","run","write","work","peer"],t=Object.entries(this.catalog.namespaces).filter(([,e])=>"env"===e.scope).map(([t,s])=>({ns:t,operations:Object.keys(s.operations).sort((t,s)=>{const o=e.indexOf(t),r=e.indexOf(s);return-1===o&&-1===r?t.localeCompare(s):-1===o?1:-1===r?-1:o-r})})),s=this.selectedPerms,o=this.godMode;return Object.entries(this.grants).map(([e,r])=>({env:e,globalCoverAll:o||!!s["*"],nGroups:t.map(({ns:e,operations:t})=>({ns:e,ops:t.map(t=>{const a=`${e}:${t}`,n=!!r[a]||!!r[`${e}:*`]||!!r["*"],i=!n&&(o||!!s["*"]||!!s[`${e}:*`]||!!s[a]),l=this.catalog.namespaces[e]?.operations?.[t]||{};return{perm:a,op:t,summary:l.summary||"",routes:l.routes||[],hasDestructive:(l.routes||[]).some(e=>e.destructive),checked:n,inherited:i}})}))}))},isNsAllSelected(e){return!!this.selectedPerms[`${e}:*`]},isEnvAllSelected(e){return!!this.grants[e]?.["*"]},isEnvWildInherited(e){return!this.isEnvAllSelected(e)&&(this.godMode||!!this.selectedPerms["*"])},isNsAllSelectedForGrant(e,t){return!!this.grants[e]?.["*"]||!!this.grants[e]?.[`${t}:*`]},isNsWildInheritedForGrant(e,t){return!this.isNsAllSelectedForGrant(e,t)&&(this.godMode||!!this.selectedPerms["*"]||!!this.selectedPerms[`${t}:*`])},toggleGrantPerm(e,t,s,o){const r=`${t}:${s}`,a=`${t}:*`,n={...this.grants[e]||{}};if(n["*"])delete n["*"];else if(n[a]){delete n[a];for(const e of o)e!==s&&(n[`${t}:${e}`]=!0)}else n[r]=!n[r];this.grants={...this.grants,[e]:n}},toggleGrantNamespace(e,t,s){const o=`${t}:*`,r={...this.grants[e]||{}};if(r["*"])delete r["*"];else if(r[o])delete r[o];else{for(const e of s)delete r[e.perm];r[o]=!0}this.grants={...this.grants,[e]:r}},toggleGrantAllPerms(e){const t=this.grants[e]||{};if(t["*"]){const s={...t};delete s["*"],this.grants={...this.grants,[e]:s}}else this.grants={...this.grants,[e]:{"*":!0}}},validateForm(){if(!this.label.trim())throw new Error("Token label is required");return!0},async save(){this.error=null;try{this.validateForm(),this.busy=!0;const e={label:this.label.trim()};if(this.expiresAt){const t=new Date(this.expiresAt).getTime();t>Date.now()&&(e.ttl=t-Date.now())}if(this.godMode)e.permissions=["*"];else if(this.catalogError&&void 0!==this.selectedPerms._fallback){const t=String(this.selectedPerms._fallback||"").split(",").map(e=>e.trim()).filter(e=>e);t.length&&(e.permissions=t)}else{const t=Object.entries(this.selectedPerms).filter(([e,t])=>t&&"_fallback"!==e).map(([e])=>e);t.length&&(e.permissions=t)}const t={};for(const[e,s]of Object.entries(this.grants)){const o=Object.entries(s).filter(([,e])=>e).map(([e])=>e);o.length&&(t[e]=o)}Object.keys(t).length&&(e.grants=t),this.name.trim()&&(e.name=this.name.trim(),this.password&&(e.password=this.password));const s=await fetch(`${__ENV__.API}/auth/tokens`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(!s.ok){const e=await s.json();throw new Error(e?.error?.message||"Failed to create token")}const o=await s.json();this.createdToken=o.data??o,this.showToken=!0}catch(t){this.error=t?.message||String(t),e.$ok.shared?.toast?.error?.(this.error)}finally{this.busy=!1}},copyToken(){this.createdToken?.token&&navigator.clipboard?.writeText(this.createdToken.token).then(()=>{e.$ok.shared?.toast?.success?.("Token copied to clipboard")}).catch(()=>{e.$ok.shared?.toast?.error?.("Failed to copy token")})},dismissToken(){e.$attr.modal.close(),e.$attr.onDone?.(this.createdToken)},cancel(){e.$attr.modal.close()},passwordStrength(e){if(!e)return 0;let t=0;return e.length>=8&&t++,e.length>=14&&t++,/[A-Z]/.test(e)&&t++,/[0-9]/.test(e)&&t++,/[^A-Za-z0-9]/.test(e)&&t++,t},passwordStrengthLabel(e){const t=this.passwordStrength(e);return e?t<=1?"weak":t<=3?"fair":"strong":""},passwordStrengthColor(e){const t=this.passwordStrength(e);return e?t<=1?"var(--okdb-danger)":t<=3?"var(--okdb-warning)":"var(--okdb-success)":"transparent"},toggleShowPassword(){this.showPassword=!this.showPassword}}),mount(e){this.loadCatalog()},style:".tf-header-fields,.tf-root,create-token-modal{display:flex;flex-direction:column}.tf-root{gap:0;width:520px;max-width:88vw;position:relative}.tf-header-fields{gap:12px;padding-bottom:16px}.tf-field,.tf-header-row{display:flex;gap:10px}.tf-field{flex-direction:column;gap:4px;min-width:0}.tf-label{font-size:10px;color:var(--okdb-app-fg-muted);text-transform:uppercase;letter-spacing:.07em;font-weight:600}.tf-creds-section{display:flex;flex-direction:column;gap:8px;padding:10px 12px;border:1px solid var(--okdb-app-surface);border-radius:6px;background:rgba(0,0,0,.15)}.tf-creds-label{font-size:10px;color:var(--okdb-app-fg-muted);text-transform:uppercase;letter-spacing:.07em;font-weight:600}.tf-creds-hint{font-size:10px;color:var(--okdb-app-fg-subtle);line-height:1.4;margin-top:-4px}.tf-creds-row{display:flex;gap:10px}.tf-pw-wrap{position:relative;display:flex;align-items:center}.tf-pw-wrap .tf-input{padding-right:30px}.tf-pw-eye{position:absolute;right:7px;background:0 0;border:0;color:var(--okdb-app-fg-subtle);cursor:pointer;font-size:12px;padding:0;line-height:1;user-select:none}.tf-pw-eye:hover,.tf-scope-header:hover .tf-scope-name{color:var(--okdb-accent-muted)}.tf-strength-bar{height:2px;border-radius:1px;background:var(--okdb-app-surface);margin-top:3px;overflow:hidden}.tf-strength-fill{height:100%;border-radius:1px;transition:width .2s,background-color .2s}.tf-strength-text{font-size:9px;margin-top:2px;text-transform:uppercase;letter-spacing:.05em}.tf-input{padding:6px 8px;border:1px solid var(--okdb-app-surface-2);border-radius:4px;background:0 0;color:var(--okdb-app-fg-strong);font-size:12px;font-family:monospace;width:100%;box-sizing:border-box}.tf-input:focus{outline:0;border-color:rgba(79,193,255,.4)}.tf-hint{font-size:10px;color:var(--okdb-app-fg-subtle);line-height:1.4}.tf-divider{height:1px;background:var(--okdb-app-surface);margin-bottom:12px}.tf-section{display:flex;flex-direction:column;gap:6px;margin-bottom:10px}.tf-scope-header{display:flex;align-items:center;gap:8px;cursor:pointer;user-select:none;padding:2px 0}.tf-scope-name{font-size:10px;text-transform:uppercase;letter-spacing:.07em;color:var(--okdb-app-fg-muted);font-weight:600;transition:color .1s}.tf-scope-count{font-size:10px;color:var(--okdb-accent);opacity:.8}.tf-scope-chevron{margin-left:auto;font-size:10px;color:var(--okdb-app-fg-subtle)}.tf-ns-block{display:flex;flex-direction:column;gap:4px;margin-bottom:8px}.tf-ns-row{display:flex;align-items:baseline;gap:8px}.tf-ns-label{font-size:11px;color:var(--okdb-app-fg-subtle);font-family:monospace;flex-shrink:0;cursor:pointer;user-select:none;transition:color .1s;font-weight:600}.tf-ns-label:hover{color:var(--okdb-accent-muted)}.tf-ns-label--on{color:var(--okdb-accent)}.tf-ns-desc{font-size:10px;color:var(--okdb-app-fg-subtle);line-height:1.4}.tf-pills{display:flex;flex-wrap:wrap;gap:4px;padding-left:4px}.tf-pill-wrap{display:inline-flex}.tf-pill-warn{font-size:7px;color:rgba(215,150,60,.85);vertical-align:super;margin-left:1px;line-height:0}.tf-pill--destructive{border-color:rgba(200,140,60,.35)}.tf-pill--destructive:hover{border-color:rgba(200,140,60,.6)}.tf-pill{display:inline-flex;align-items:center;padding:2px 8px;border-radius:4px;font-size:11px;font-family:monospace;border:1px solid var(--okdb-app-surface-2);color:var(--okdb-app-fg-subtle);cursor:pointer;user-select:none;transition:background .1s,border-color .1s,color .1s;white-space:nowrap}.tf-pill:hover{border-color:var(--okdb-app-border);color:var(--okdb-accent-muted)}.tf-pill--on{background:rgba(79,193,255,.1);border-color:rgba(79,193,255,.35);color:var(--okdb-accent)}.tf-pill--on:hover{background:rgba(79,193,255,.15)}.tf-pill--full{border-color:rgba(251,191,36,.2);color:#5a4a20}.tf-pill--full-on,.tf-pill--full:hover{border-color:rgba(251,191,36,.4);color:var(--okdb-warning-text)}.tf-pill--full-on{background:rgba(251,191,36,.1)}.tf-pill--full-on:hover{background:rgba(251,191,36,.16)}.tf-pill--add{border-style:dashed;border-color:var(--okdb-app-surface-2);color:var(--okdb-app-fg-subtle)}.tf-pill--add:hover{border-color:var(--okdb-app-border);color:var(--okdb-accent-muted);border-style:solid}.tf-pill--wild{border-color:rgba(251,191,36,.2);color:#5a4a20;font-weight:700}.tf-pill--wild-on,.tf-pill--wild:hover{border-color:rgba(251,191,36,.4);color:var(--okdb-warning-text)}.tf-pill--wild-on{background:rgba(251,191,36,.1);font-weight:700}.tf-pill--wild-on:hover{background:rgba(251,191,36,.16)}.tf-pill--wild-inherited{border-style:dashed;border-color:rgba(251,191,36,.45);color:rgba(251,191,36,.6);background:rgba(251,191,36,.05);font-weight:700}.tf-pill--wild-inherited:hover{border-color:rgba(251,191,36,.65);color:rgba(251,191,36,.8);background:rgba(251,191,36,.09)}.tf-pill--inherited{border-style:dashed;border-color:rgba(79,193,255,.5);color:rgba(79,193,255,.65);background:rgba(79,193,255,.06)}.tf-pill--inherited:hover{border-color:rgba(79,193,255,.7);color:rgba(79,193,255,.85);background:rgba(79,193,255,.1)}.tf-env-row{display:flex;flex-direction:column;gap:6px;padding:8px 0 10px;border-top:1px solid var(--okdb-app-bg)}.tf-env-row-header{display:flex;align-items:center;justify-content:space-between}.tf-env-name{font-size:11px;font-weight:600;color:var(--okdb-app-fg-muted);font-family:monospace}.tf-env-all,.tf-env-remove{font-size:10px;color:var(--okdb-app-fg-subtle);cursor:pointer;user-select:none;padding:1px 4px;transition:color .1s}.tf-env-remove:hover{color:var(--okdb-danger)}.tf-env-all:hover{color:var(--okdb-accent-muted)}.tf-env-all--on{color:var(--okdb-warning-text)}.tf-env-all--on:hover{color:var(--okdb-warning)}.tf-token-box{display:flex;flex-direction:column;gap:10px;padding:14px;border:1px solid rgba(79,193,255,.2);border-radius:6px;background:rgba(79,193,255,.03)}.tf-token-warn{font-size:11px;color:var(--okdb-warning-text);line-height:1.5}.tf-token-value{font-family:monospace;font-size:11px;color:var(--okdb-accent);word-break:break-all;padding:8px;background:rgba(0,0,0,.3);border-radius:4px;border:1px solid rgba(79,193,255,.15);cursor:text;user-select:all}.tf-error{font-size:11px;color:var(--okdb-danger);padding:6px 0 0}.tf-actions{display:flex;gap:8px;justify-content:flex-end;padding-top:14px;margin-top:4px;border-top:1px solid var(--okdb-app-surface)}.tf-btn{padding:5px 12px;border-radius:5px;font-size:12px;border:1px solid;background:0 0;cursor:pointer;transition:background .12s,color .12s}.tf-btn:disabled{opacity:.4;cursor:not-allowed}.tf-btn--primary{border-color:var(--okdb-app-border);color:var(--okdb-accent-muted)}.tf-btn--primary:not(:disabled):hover{background:var(--okdb-app-surface);border-color:var(--okdb-app-border);color:var(--okdb-sky)}.tf-btn--secondary{border-color:var(--okdb-app-surface-2);color:var(--okdb-app-fg-subtle)}.tf-btn--secondary:hover{background:var(--okdb-app-bg)}.tf-pip{position:fixed;z-index:9999;min-width:260px;max-width:460px;background:var(--okdb-app-bg-deep);border:1px solid var(--okdb-app-surface-2);border-radius:5px;padding:8px 10px;box-shadow:0 4px 18px rgba(0,0,0,.75);pointer-events:none;white-space:normal;font-size:11px}.tf-pip-perm{font-family:monospace;font-size:10px;color:var(--okdb-accent);margin-bottom:3px}.tf-pip-summary{color:var(--okdb-accent-muted);line-height:1.4;margin-bottom:5px}.tf-pip-routes-hd{font-size:9px;color:var(--okdb-app-fg-subtle);text-transform:uppercase;letter-spacing:.06em;margin-bottom:3px}.tf-pip-route{display:flex;align-items:baseline;gap:5px;padding:1px 0}.tf-pip-method{font-family:monospace;font-size:9px;color:var(--okdb-accent);width:34px;flex-shrink:0}.tf-pip-path{font-family:monospace;font-size:10px;color:var(--okdb-accent-muted);word-break:break-all}.tf-pip-bd,.tf-pip-lr{font-size:8px;border-radius:2px;padding:0 3px;flex-shrink:0}.tf-pip-bd{color:var(--okdb-danger);background:rgba(180,50,40,.15)}.tf-pip-lr{color:var(--okdb-app-fg-muted);background:rgba(100,100,100,.12)}.tf-pip-route-desc{font-size:9px;color:var(--okdb-app-fg-subtle);padding-left:39px;margin-bottom:1px}.tf-pip-gate,.tf-pip-implied{color:var(--okdb-app-fg-subtle);font-size:10px}.tf-pip-implied{margin-top:5px;font-size:9px;font-family:monospace}",template:'<div class="tf-root"><if :="busy && !showToken"><ok-overlay><div class="loader-center"><ok-icon name="loader" class="spin" style="width:28px;height:28px;"/></div></ok-overlay></if><if :="showToken && createdToken"><div style="display:flex; flex-direction:column; gap:12px;"><div style="font-size:13px; font-weight:600; color:var(--okdb-success);">✓ Token Created</div><div class="tf-token-box"><div class="tf-token-warn">⚠ Copy this token now — it will not be shown again.</div><div class="tf-token-value">{{ createdToken.token || createdToken.value || createdToken }}</div><div style="font-size:11px; color:var(--okdb-app-fg-muted);">{{ createdToken.label || \'Unnamed\' }}</div></div><div class="tf-actions"><button class="tf-btn tf-btn--primary" @click="copyToken()">Copy token</button> <button class="tf-btn tf-btn--secondary" @click="dismissToken()">Done</button></div></div></if><if :="!showToken"><div class="tf-header-fields"><div class="tf-header-row"><div class="tf-field" style="flex:2;"><span class="tf-label">Label</span> <input class="tf-input" type="text" placeholder="e.g., API Client" :value="label" @input="label = $el.value" :disabled="busy"></div><div class="tf-field" style="flex:1.5;"><span class="tf-label">Expires <span style="opacity:.4;">(optional)</span></span> <input class="tf-input" type="datetime-local" :value="expiresAt" @input="expiresAt = $el.value" :disabled="busy"></div></div><div class="tf-creds-section"><div><div class="tf-creds-label">Login credentials <span style="opacity:.4; text-transform:none; font-weight:400; letter-spacing:0;">(optional)</span></div><div class="tf-creds-hint">If set, this token can also be used via username + password authentication.</div></div><div class="tf-creds-row"><div class="tf-field" style="flex:1;"><span class="tf-label">Username</span> <input class="tf-input" type="text" placeholder="leave blank for token-only" :value="name" @input="name = $el.value" :disabled="busy" autocomplete="off"></div><div class="tf-field" style="flex:1;"><span class="tf-label">Password</span><div class="tf-pw-wrap"><input class="tf-input" :type="showPassword ? \'text\' : \'password\'" placeholder="required if username is set" :value="password" @input="password = $el.value" :disabled="busy || !name" autocomplete="new-password"> <button class="tf-pw-eye" @click="toggleShowPassword()" type="button" tabindex="-1">{{ showPassword ? \'🙈\' : \'👁\' }}</button></div><if :="password"><div class="tf-strength-bar"><div class="tf-strength-fill" :style="\'width:\' + (passwordStrength(password)/5*100) + \'%; background:\' + passwordStrengthColor(password)"/></div><div class="tf-strength-text" :style="\'color:\' + passwordStrengthColor(password)">{{ passwordStrengthLabel(password) }}</div></if></div></div></div></div><div class="tf-divider"/><if :="catalogLoading"><div class="tf-hint" style="padding-bottom:12px;">Loading permissions…</div></if><if :="catalogError && !catalogLoading"><div class="tf-section"><span class="tf-label">Permissions <span style="color:var(--okdb-danger);">(fallback)</span></span> <input class="tf-input" type="text" placeholder="data:read, data:write" :value="selectedPerms._fallback || \'\'" @input="selectedPerms = { _fallback: $el.value }" :disabled="busy"> <span class="tf-hint">Comma-separated permission strings</span></div></if><if :="catalog && !catalogError && !catalogLoading"><div class="tf-section" style="gap:4px;"><div :class="[\'tf-pill\', \'tf-pill--full\', godMode ? \'tf-pill--full-on\' : \'\']" @click="toggleGodMode()" style="width:fit-content;">★ &nbsp;Full access — all permissions</div></div><if :="!godMode"><each :of="getPermSections()" let:item="section"><div class="tf-section"><div class="tf-scope-header" @click="toggleScope(section.scope)"><span class="tf-scope-name">{{ section.label }}</span><if :="scopeSelectionCount(section)"><span class="tf-scope-count">{{ scopeSelectionCount(section) }}</span></if><span class="tf-scope-chevron">{{ isCollapsed(section.scope) ? \'▸\' : \'▾\' }}</span></div><if :="!isCollapsed(section.scope)"><each :of="section.namespaces" let:item="nsEntry"><div class="tf-ns-block"><div class="tf-ns-row"><span :class="[\'tf-ns-label\', isNsAllSelected(nsEntry.ns) ? \'tf-ns-label--on\' : \'\']" @click="toggleNamespace(nsEntry.ns, nsEntry.operations)" title="Toggle all">{{ nsEntry.nsLabel || nsEntry.ns }}</span><if :="nsEntry.nsDescription"><span class="tf-ns-desc">{{ nsEntry.nsDescription }}</span></if></div><div class="tf-pills"><span :class="[\'tf-pill\', \'tf-pill--wild\', isNsAllSelected(nsEntry.ns) ? \'tf-pill--wild-on\' : \'\']" @click="toggleNamespace(nsEntry.ns, nsEntry.operations)" title="ns:* — toggle all">*</span><each :of="getPermOps(nsEntry.ns, nsEntry.operations, nsEntry.operationMeta)" let:item="permOp"><span class="tf-pill-wrap" @mouseenter="setHover($event, permOp.perm, permOp)" @mouseleave="clearHover()"><span :class="[\'tf-pill\', permOp.checked ? \'tf-pill--on\' : \'\', permOp.hasDestructive ? \'tf-pill--destructive\' : \'\']" @click="togglePerm(permOp.ns, permOp.op, nsEntry.operations)">{{ permOp.op }}<if :="permOp.hasDestructive"><span class="tf-pill-warn">⚠</span></if></span></span></each></div></div></each></if></div></each><if :="catalog.envs && catalog.envs.length > 0"><div class="tf-divider"/><div class="tf-section" style="gap:8px;"><div class="tf-scope-header" style="cursor:default; pointer-events:none;"><span class="tf-scope-name">Per-env grants</span> <span class="tf-hint" style="margin-left:6px;">extra permissions for a specific environment</span></div><if :="availableGrantEnvs().length > 0"><div class="tf-pills"><each :of="availableGrantEnvs()" let:item="envName"><span class="tf-pill tf-pill--add" @click="addGrantEnv(envName)">+ {{ envName }}</span></each></div></if><each :of="getGrantEntries()" let:item="grantEntry"><div class="tf-env-row"><div class="tf-env-row-header"><div style="display:flex; align-items:center; gap:6px;"><span class="tf-env-name">{{ grantEntry.env }}</span> <span :class="[\'tf-pill\', \'tf-pill--wild\', isEnvAllSelected(grantEntry.env) ? \'tf-pill--wild-on\' : isEnvWildInherited(grantEntry.env) ? \'tf-pill--wild-inherited\' : \'\']" @click="toggleGrantAllPerms(grantEntry.env)" title="* — all permissions for this env" style="font-size:11px; padding:1px 6px;">*</span><if :="grantEntry.globalCoverAll"><span class="tf-hint" style="color:rgba(79,193,255,.35);" title="All permissions already covered by global * or god mode">inherited</span></if></div><span class="tf-env-remove" @click="removeGrantEnv(grantEntry.env)">✕ remove</span></div><each :of="grantEntry.nGroups" let:item="nsGroup"><div class="tf-ns-row"><span :class="[\'tf-ns-label\', isNsAllSelectedForGrant(grantEntry.env, nsGroup.ns) ? \'tf-ns-label--on\' : \'\']" @click="toggleGrantNamespace(grantEntry.env, nsGroup.ns, nsGroup.ops)" title="Toggle all">{{ nsGroup.ns }}</span><div class="tf-pills"><span :class="[\'tf-pill\', \'tf-pill--wild\', isNsAllSelectedForGrant(grantEntry.env, nsGroup.ns) ? \'tf-pill--wild-on\' : isNsWildInheritedForGrant(grantEntry.env, nsGroup.ns) ? \'tf-pill--wild-inherited\' : \'\']" @click="toggleGrantNamespace(grantEntry.env, nsGroup.ns, nsGroup.ops)" title="ns:* — toggle all">*</span><each :of="nsGroup.ops" let:item="grantPerm"><span class="tf-pill-wrap" @mouseenter="setHover($event, grantPerm.perm, grantPerm)" @mouseleave="clearHover()"><span :class="[\'tf-pill\', grantPerm.checked ? \'tf-pill--on\' : grantPerm.inherited ? \'tf-pill--inherited\' : \'\', grantPerm.hasDestructive ? \'tf-pill--destructive\' : \'\']" @click="toggleGrantPerm(grantEntry.env, nsGroup.ns, grantPerm.op, nsGroup.ops.map(o => o.op))">{{ grantPerm.op }}<if :="grantPerm.hasDestructive"><span class="tf-pill-warn">⚠</span></if></span></span></each></div></div></each></div></each></div></if></if></if><if :="error"><div class="tf-error">{{ error }}</div></if><div class="tf-actions"><button class="tf-btn tf-btn--secondary" @click="cancel()" :disabled="busy">Cancel</button> <button class="tf-btn tf-btn--primary" @click="save()" :disabled="busy || !label.trim()">Create Token</button></div></if><if :="hoveredPermOp && tooltipPos"><div class="tf-pip" :style="\'left:\' + tooltipPos.left + \'px; top:\' + (tooltipPos.bottom + 4) + \'px;\'"><div class="tf-pip-perm">{{ hoveredPermOp.perm || hoveredPerm }}</div><if :="hoveredPermOp.summary"><div class="tf-pip-summary">{{ hoveredPermOp.summary }}</div></if><if :="hoveredPermOp.routes && hoveredPermOp.routes.length"><div class="tf-pip-routes-hd">Routes ({{ hoveredPermOp.routes.length }})</div><each :of="hoveredPermOp.routes" let:item="r"><div class="tf-pip-route"><span class="tf-pip-method">{{ r.method }}</span> <span class="tf-pip-path">{{ r.path }}</span><if :="r.destructive"><span class="tf-pip-bd">destructive</span></if><if :="r.longRunning"><span class="tf-pip-lr">long</span></if></div><if :="r.summary"><div class="tf-pip-route-desc">{{ r.summary }}</div></if></each></if><if :="!(hoveredPermOp.routes && hoveredPermOp.routes.length)"><div class="tf-pip-gate">Gate — combine with underlying namespace permissions.</div></if><if :="getImpliedBy(hoveredPerm).length"><div class="tf-pip-implied">Implied by: {{ getImpliedBy(hoveredPerm).join(\', \') }}</div></if></div></if></div>'};