@arcblock/did-connect-service 4.1.7 → 4.1.9

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 (66) hide show
  1. package/dist/_generated/asset-bytes.js +1 -1
  2. package/dist/_generated/asset-manifest.js +2 -2
  3. package/dist/assets/admin-core.a13c2729.js +1461 -0
  4. package/dist/assets/admin-core.e83fd379.js +1397 -0
  5. package/dist/assets/admin-extra.841b2bbe.js +2530 -0
  6. package/dist/blocklet-service-rpc.js +11 -11
  7. package/dist/blocklet-service-rpc.js.map +1 -1
  8. package/dist/handlers/access-key-handler.js +1 -1
  9. package/dist/handlers/access-key-handler.js.map +1 -1
  10. package/dist/handlers/admin-instance-handler.d.ts.map +1 -1
  11. package/dist/handlers/admin-instance-handler.js +20 -19
  12. package/dist/handlers/admin-instance-handler.js.map +1 -1
  13. package/dist/handlers/avatar-handler.d.ts +1 -1
  14. package/dist/handlers/avatar-handler.d.ts.map +1 -1
  15. package/dist/handlers/avatar-handler.js +1 -1
  16. package/dist/handlers/avatar-handler.js.map +1 -1
  17. package/dist/handlers/branding-handler.js +7 -7
  18. package/dist/handlers/branding-handler.js.map +1 -1
  19. package/dist/handlers/did-connect-handler.d.ts.map +1 -1
  20. package/dist/handlers/did-connect-handler.js +5 -2
  21. package/dist/handlers/did-connect-handler.js.map +1 -1
  22. package/dist/handlers/email-login-handler.d.ts.map +1 -1
  23. package/dist/handlers/email-login-handler.js +4 -2
  24. package/dist/handlers/email-login-handler.js.map +1 -1
  25. package/dist/handlers/federation-admin-handler.d.ts.map +1 -1
  26. package/dist/handlers/federation-admin-handler.js +23 -23
  27. package/dist/handlers/federation-admin-handler.js.map +1 -1
  28. package/dist/handlers/membership-handler.js +2 -2
  29. package/dist/handlers/membership-handler.js.map +1 -1
  30. package/dist/handlers/oauth-handler.d.ts.map +1 -1
  31. package/dist/handlers/oauth-handler.js +12 -7
  32. package/dist/handlers/oauth-handler.js.map +1 -1
  33. package/dist/handlers/passkey-handler.d.ts.map +1 -1
  34. package/dist/handlers/passkey-handler.js +7 -4
  35. package/dist/handlers/passkey-handler.js.map +1 -1
  36. package/dist/handlers/team-handler.d.ts.map +1 -1
  37. package/dist/handlers/team-handler.js +22 -22
  38. package/dist/handlers/team-handler.js.map +1 -1
  39. package/dist/identity/auto-membership.js +2 -2
  40. package/dist/identity/auto-membership.js.map +1 -1
  41. package/dist/pages/admin/i18n.d.ts.map +1 -1
  42. package/dist/pages/admin/i18n.js +12 -2
  43. package/dist/pages/admin/i18n.js.map +1 -1
  44. package/dist/pages/admin/index.js +1 -1
  45. package/dist/pages/admin/scripts/api-client.d.ts +1 -1
  46. package/dist/pages/admin/scripts/api-client.d.ts.map +1 -1
  47. package/dist/pages/admin/scripts/api-client.js +7 -3
  48. package/dist/pages/admin/scripts/api-client.js.map +1 -1
  49. package/dist/pages/admin/tab-access-keys.d.ts +1 -1
  50. package/dist/pages/admin/tab-access-keys.d.ts.map +1 -1
  51. package/dist/pages/admin/tab-access-keys.js +71 -7
  52. package/dist/pages/admin/tab-access-keys.js.map +1 -1
  53. package/dist/pages/admin/tab-members.d.ts +1 -1
  54. package/dist/pages/admin/tab-members.d.ts.map +1 -1
  55. package/dist/pages/admin/tab-members.js +3 -1
  56. package/dist/pages/admin/tab-members.js.map +1 -1
  57. package/dist/pages/login-page.js +1 -1
  58. package/dist/store/d1-store.d.ts +24 -19
  59. package/dist/store/d1-store.d.ts.map +1 -1
  60. package/dist/store/d1-store.js +156 -83
  61. package/dist/store/d1-store.js.map +1 -1
  62. package/dist/types.d.ts +11 -0
  63. package/dist/types.d.ts.map +1 -1
  64. package/migrations/0008_add_timestamps.sql +10 -0
  65. package/migrations/0009_audit_by_columns.sql +33 -0
  66. package/package.json +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"api-client.js","sourceRoot":"","sources":["../../../../src/pages/admin/scripts/api-client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,CAAC,MAAM,iBAAiB,GAAG,QAAQ,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgCzC,CAAC"}
1
+ {"version":3,"file":"api-client.js","sourceRoot":"","sources":["../../../../src/pages/admin/scripts/api-client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,CAAC,MAAM,iBAAiB,GAAG,QAAQ,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoCzC,CAAC"}
@@ -3,6 +3,6 @@
3
3
  */
4
4
  type TFn = (key: string, params?: Record<string, string>) => string;
5
5
  export declare function accessKeysTabHtml(t: TFn): string;
6
- export declare const ACCESS_KEYS_TAB_SCRIPT = "\nvar akPage = 1;\nvar akPageSize = 20;\nvar akSearchTimer = null;\n\nfunction debounceAkSearch() {\n clearTimeout(akSearchTimer);\n akSearchTimer = setTimeout(function() { loadAccessKeys(1); }, 300);\n}\n\nasync function loadAccessKeys(page) {\n akPage = page || 1;\n var search = document.getElementById(\"ak-search\").value.trim();\n var qs = \"?page=\" + akPage + \"&pageSize=\" + akPageSize;\n if (search) qs += \"&search=\" + encodeURIComponent(search);\n document.getElementById(\"ak-table-wrap\").innerHTML = skeletonTable(7, 5);\n document.getElementById(\"ak-pagination\").innerHTML = \"\";\n var data = await apiRaw(\"GET\", \"/.well-known/service/api/access-keys\" + qs);\n if (!data || data.error) {\n document.getElementById(\"ak-table-wrap\").innerHTML = '<div class=\"empty-state\"><div class=\"empty-state-title\">' + __t(\"accessKeys.loadFailed\") + '</div></div>';\n return;\n }\n renderAccessKeys(data.keys, { total: data.total, page: data.page, pageSize: data.pageSize });\n}\n\nfunction renderAccessKeys(keys, paging) {\n var wrap = document.getElementById(\"ak-table-wrap\");\n if (!keys || keys.length === 0) {\n wrap.innerHTML = '<div class=\"empty-state\"><div class=\"empty-state-icon\"><svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4\"/></svg></div><div class=\"empty-state-title\">' + __t(\"accessKeys.noKeys\") + '</div><div class=\"empty-state-desc\">' + __t(\"accessKeys.noKeysHint\") + '</div></div>';\n document.getElementById(\"ak-pagination\").innerHTML = \"\";\n return;\n }\n\n var html = '<table class=\"table\"><thead><tr><th>' + __t(\"accessKeys.col.keyId\") + '</th><th>' + __t(\"accessKeys.col.role\") + '</th><th class=\"col-hide-sm\">' + __t(\"accessKeys.col.remark\") + '</th><th class=\"col-hide-md\">' + __t(\"accessKeys.col.createdBy\") + '</th><th>' + __t(\"accessKeys.col.expires\") + '</th><th class=\"col-hide-md\">' + __t(\"accessKeys.col.lastUsed\") + '</th><th>' + __t(\"common.actions\") + '</th></tr></thead><tbody>';\n keys.forEach(function(k) {\n var keyIdHtml = '<did-address did=\"' + escapeHtml(k.accessKeyId) + '\" compact></did-address>';\n var roleBadge = '<span class=\"badge badge-' + k.role + '\">' + __t(\"common.\" + k.role) + '</span>';\n var remark = k.remark ? escapeHtml(k.remark.length > 40 ? k.remark.slice(0, 40) + \"...\" : k.remark) : '<span style=\"color:var(--text-secondary)\">\u2014</span>';\n var creator = k.createdByName\n ? escapeHtml(k.createdByName)\n : '<did-address did=\"' + escapeHtml(k.createdBy) + '\" compact></did-address>';\n var expiry = k.expireAt ? (new Date(k.expireAt) < new Date() ? '<span style=\"color:var(--error)\">' + __t(\"common.expired\") + '</span>' : relativeTime(k.expireAt)) : __t(\"common.never\");\n var lastUsed = k.lastUsedAt ? relativeTime(k.lastUsedAt) : __t(\"common.never\");\n var deleteLabel = k.remark ? escapeHtml(k.remark) : truncateDid(k.accessKeyId);\n\n html += '<tr>';\n html += '<td><code style=\"font-size:12px\">' + keyIdHtml + '</code></td>';\n html += '<td>' + roleBadge + '</td>';\n html += '<td class=\"col-hide-sm\">' + remark + '</td>';\n html += '<td class=\"col-hide-md\">' + creator + '</td>';\n html += '<td>' + expiry + '</td>';\n html += '<td class=\"col-hide-md\">' + lastUsed + '</td>';\n html += '<td class=\"action-cell\"><button class=\"action-trigger\" onclick=\"event.stopPropagation();toggleActionMenu(this)\">\u22EF</button><div class=\"action-menu\"><button class=\"action-menu-item\" onclick=\"event.stopPropagation();openEditAkDialog(\\''+k.accessKeyId+'\\')\">' + __t(\"common.edit\") + '</button><div class=\"action-menu-sep\"></div><button class=\"action-menu-item danger\" onclick=\"event.stopPropagation();confirmDeleteAk(\\''+k.accessKeyId+'\\',\\''+escapeHtml(deleteLabel)+'\\')\">' + __t(\"common.delete\") + '</button></div></td>';\n html += '</tr>';\n });\n html += '</tbody></table>';\n wrap.innerHTML = html;\n\n renderPagination(\"ak-pagination\", paging, loadAccessKeys);\n}\n\nfunction openCreateAkDialog() {\n var callerRole = __caller.role;\n var roleOptions = '<option value=\"guest\">Guest</option>';\n if (callerRole === \"member\" || callerRole === \"admin\" || callerRole === \"owner\") roleOptions += '<option value=\"member\"' + (callerRole === \"member\" ? ' selected' : '') + '>Member</option>';\n if (callerRole === \"admin\" || callerRole === \"owner\") roleOptions += '<option value=\"admin\"' + (callerRole === \"admin\" ? ' selected' : '') + '>Admin</option>';\n if (callerRole === \"owner\") roleOptions += '<option value=\"owner\">Owner</option>';\n\n var expiryOptions = '<option value=\"\">' + __t(\"accessKeys.expirations.never\") + '</option><option value=\"7d\">' + __t(\"accessKeys.expirations.days7\") + '</option><option value=\"30d\">' + __t(\"accessKeys.expirations.days30\") + '</option><option value=\"90d\">' + __t(\"accessKeys.expirations.days90\") + '</option><option value=\"custom\">' + __t(\"accessKeys.expirations.custom\") + '</option>';\n\n var html = '<div class=\"form-group\"><label class=\"form-label\">' + __t(\"accessKeys.role\") + '</label><select class=\"select\" id=\"ak-create-role\">' + roleOptions + '</select></div>';\n html += '<div class=\"form-group\"><label class=\"form-label\">' + __t(\"accessKeys.remark\") + '</label><input type=\"text\" class=\"input\" id=\"ak-create-remark\" placeholder=\"' + __t(\"accessKeys.remarkPlaceholder\") + '\" maxlength=\"200\" /></div>';\n html += '<div class=\"form-group\"><label class=\"form-label\">' + __t(\"accessKeys.expiration\") + '</label><select class=\"select\" id=\"ak-create-expiry\" onchange=\"toggleAkCustomExpiry()\">' + expiryOptions + '</select></div>';\n html += '<div class=\"form-group hidden\" id=\"ak-custom-expiry-wrap\"><label class=\"form-label\">' + __t(\"accessKeys.customDate\") + '</label><input type=\"datetime-local\" class=\"input\" id=\"ak-create-custom-expiry\" /></div>';\n\n openDialog(__t(\"accessKeys.createTitle\"), html, __t(\"common.save\"), async function() {\n var role = document.getElementById(\"ak-create-role\").value;\n var remark = document.getElementById(\"ak-create-remark\").value;\n var expirySelect = document.getElementById(\"ak-create-expiry\").value;\n var expireAt = null;\n\n if (expirySelect === \"custom\") {\n var customDate = document.getElementById(\"ak-create-custom-expiry\").value;\n if (!customDate) { showToast(__t(\"accessKeys.selectExpDate\"), \"error\"); return; }\n expireAt = new Date(customDate).toISOString();\n } else if (expirySelect) {\n var days = parseInt(expirySelect);\n expireAt = new Date(Date.now() + days * 86400000).toISOString();\n }\n\n var result = await apiRaw(\"POST\", \"/.well-known/service/api/access-keys\", { role: role, remark: remark, expireAt: expireAt });\n if (!result || result.error) {\n showToast(result ? result.error : __t(\"accessKeys.createFailed\"), \"error\");\n return;\n }\n\n closeDialog();\n showSecretDialog(result.accessKeySecret);\n loadAccessKeys(1);\n });\n}\n\nfunction toggleAkCustomExpiry() {\n var sel = document.getElementById(\"ak-create-expiry\").value;\n var wrap = document.getElementById(\"ak-custom-expiry-wrap\");\n if (sel === \"custom\") wrap.classList.remove(\"hidden\");\n else wrap.classList.add(\"hidden\");\n}\n\nfunction showSecretDialog(secret) {\n var html = '<div style=\"margin-bottom:12px;color:var(--error);font-weight:500\">' + __t(\"accessKeys.secretWarning\") + '</div>';\n var copyIconSvg = '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\" ry=\"2\"/><path d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\"/></svg>';\n var copyBtnStyle = 'position:absolute;top:4px;right:4px;width:28px;height:28px;padding:0;display:inline-flex;align-items:center;justify-content:center;background:var(--bg-base);border:1px solid var(--border);border-radius:4px;cursor:pointer;color:var(--text-secondary);transition:color 0.15s,border-color 0.15s';\n html += '<div style=\"position:relative\"><code id=\"ak-secret-display\" style=\"display:block;padding:12px 40px 12px 12px;background:var(--bg-base);border:1px solid var(--border);border-radius:6px;font-size:13px;word-break:break-all;user-select:all;overflow-wrap:anywhere\">' + escapeHtml(secret) + '</code>';\n html += '<button id=\"ak-copy-btn\" style=\"' + copyBtnStyle + '\" onclick=\"copyAkSecret()\" title=\"' + __t(\"common.copy\") + '\">' + copyIconSvg + '</button></div>';\n var curlCmd = 'curl -H \"Authorization: Bearer ' + secret + '\" ' + location.origin + '/.well-known/service/api/did/session';\n html += '<div style=\"margin-top:12px;font-size:13px;color:var(--text-secondary)\"><strong>' + __t(\"accessKeys.usage\") + '</strong></div>';\n html += '<div style=\"position:relative;margin-top:6px\"><code id=\"ak-curl-display\" style=\"display:block;padding:12px 40px 12px 12px;background:var(--bg-base);border:1px solid var(--border);border-radius:6px;font-size:12px;word-break:break-all;overflow-wrap:anywhere;user-select:all\">' + escapeHtml(curlCmd) + '</code>';\n html += '<button id=\"ak-curl-copy-btn\" style=\"' + copyBtnStyle + '\" onclick=\"copyAkCurl()\" title=\"' + __t(\"common.copy\") + '\">' + copyIconSvg + '</button></div>';\n\n openDialog(__t(\"accessKeys.secretTitle\"), html, __t(\"accessKeys.done\"), function() {\n document.getElementById(\"ak-secret-display\").textContent = \"\";\n closeDialog();\n });\n}\n\nfunction copyAkCurl() {\n var text = document.getElementById(\"ak-curl-display\").textContent;\n var btn = document.getElementById(\"ak-curl-copy-btn\");\n var origHtml = btn.innerHTML;\n function onCopied() {\n btn.innerHTML = '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M5 13l4 4L19 7\"/></svg>';\n btn.style.color = \"var(--green)\";\n setTimeout(function() { btn.innerHTML = origHtml; btn.style.color = \"\"; }, 2000);\n }\n if (navigator.clipboard && navigator.clipboard.writeText) {\n navigator.clipboard.writeText(text).then(onCopied).catch(function() {\n fallbackCopy(text);\n onCopied();\n });\n } else {\n fallbackCopy(text);\n onCopied();\n }\n}\n\nfunction copyAkSecret() {\n var text = document.getElementById(\"ak-secret-display\").textContent;\n var btn = document.getElementById(\"ak-copy-btn\");\n var origHtml = btn.innerHTML;\n function onCopied() {\n btn.innerHTML = '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M5 13l4 4L19 7\"/></svg>';\n btn.style.color = \"var(--green)\";\n setTimeout(function() { btn.innerHTML = origHtml; btn.style.color = \"\"; }, 2000);\n }\n if (navigator.clipboard && navigator.clipboard.writeText) {\n navigator.clipboard.writeText(text).then(onCopied).catch(function() {\n fallbackCopy(text);\n onCopied();\n });\n } else {\n fallbackCopy(text);\n onCopied();\n }\n}\n\nasync function openEditAkDialog(accessKeyId) {\n var data = await apiRaw(\"GET\", \"/.well-known/service/api/access-keys/\" + encodeURIComponent(accessKeyId));\n if (!data || data.error) { showToast(__t(\"accessKeys.loadKeyFailed\"), \"error\"); return; }\n\n var html = '<div class=\"form-group\"><label class=\"form-label\">' + __t(\"accessKeys.col.keyId\") + '</label><code style=\"font-size:12px\">' + escapeHtml(data.accessKeyId) + '</code></div>';\n html += '<div class=\"form-group\"><label class=\"form-label\">' + __t(\"accessKeys.role\") + '</label><span class=\"badge badge-' + data.role + '\">' + __t(\"common.\" + data.role) + '</span></div>';\n html += '<div class=\"form-group\"><label class=\"form-label\">' + __t(\"accessKeys.remark\") + '</label><input type=\"text\" class=\"input\" id=\"ak-edit-remark\" value=\"' + escapeHtml(data.remark || \"\") + '\" maxlength=\"200\" /></div>';\n html += '<div class=\"form-group\"><label class=\"form-label\">' + __t(\"accessKeys.expiration\") + '</label><input type=\"datetime-local\" class=\"input\" id=\"ak-edit-expiry\" value=\"' + (data.expireAt ? data.expireAt.slice(0,16) : \"\") + '\" /> <label style=\"margin-top:4px;display:block;font-size:13px\"><input type=\"checkbox\" id=\"ak-edit-no-expiry\"' + (data.expireAt ? \"\" : \" checked\") + ' onchange=\"toggleAkEditExpiry()\" /> ' + __t(\"accessKeys.neverExpires\") + '</label></div>';\n\n openDialog(__t(\"accessKeys.editTitle\"), html, __t(\"common.save\"), async function() {\n var remark = document.getElementById(\"ak-edit-remark\").value;\n var noExpiry = document.getElementById(\"ak-edit-no-expiry\").checked;\n var expireAt = noExpiry ? null : (document.getElementById(\"ak-edit-expiry\").value ? new Date(document.getElementById(\"ak-edit-expiry\").value).toISOString() : undefined);\n\n var body = { remark: remark };\n if (expireAt !== undefined) body.expireAt = expireAt;\n\n var result = await apiRaw(\"PUT\", \"/.well-known/service/api/access-keys/\" + encodeURIComponent(accessKeyId), body);\n if (!result || result.error) {\n showToast(result ? result.error : __t(\"accessKeys.updateFailed\"), \"error\");\n return;\n }\n closeDialog();\n showToast(__t(\"accessKeys.keyUpdated\"), \"success\");\n loadAccessKeys(akPage);\n });\n}\n\nfunction toggleAkEditExpiry() {\n var noExpiry = document.getElementById(\"ak-edit-no-expiry\").checked;\n document.getElementById(\"ak-edit-expiry\").disabled = noExpiry;\n}\n\nfunction confirmDeleteAk(accessKeyId, label) {\n openDialog(__t(\"accessKeys.deleteTitle\"), '<p>' + __t(\"accessKeys.deleteMsg\", { label: escapeHtml(label) }) + '</p><p style=\"color:var(--error)\">' + __t(\"accessKeys.deleteWarning\") + '</p>', __t(\"common.delete\"), async function() {\n var result = await apiRaw(\"DELETE\", \"/.well-known/service/api/access-keys/\" + encodeURIComponent(accessKeyId));\n if (result && result.error) {\n showToast(result.error, \"error\");\n return;\n }\n closeDialog();\n showToast(__t(\"accessKeys.keyDeleted\"), \"success\");\n loadAccessKeys(akPage);\n });\n}\n\n// Direct API call (bypasses team handler's /team prefix)\nasync function apiRaw(method, path, body) {\n try {\n var opts = { method: method, headers: { \"Accept\": \"application/json\" } };\n if (body !== undefined) {\n opts.headers[\"Content-Type\"] = \"application/json\";\n opts.body = JSON.stringify(body);\n }\n var res = await fetch(path, opts);\n if (res.status === 204) return {};\n return await res.json();\n } catch(e) {\n return null;\n }\n}\n\nregisterTab(\"access-keys\", function() { loadAccessKeys(1); });\n";
6
+ export declare const ACCESS_KEYS_TAB_SCRIPT = "\nvar akPage = 1;\nvar akPageSize = 20;\nvar akSearchTimer = null;\n\nfunction debounceAkSearch() {\n clearTimeout(akSearchTimer);\n akSearchTimer = setTimeout(function() { loadAccessKeys(1); }, 300);\n}\n\nasync function loadAccessKeys(page) {\n akPage = page || 1;\n var search = document.getElementById(\"ak-search\").value.trim();\n var qs = \"?page=\" + akPage + \"&pageSize=\" + akPageSize;\n if (search) qs += \"&search=\" + encodeURIComponent(search);\n document.getElementById(\"ak-table-wrap\").innerHTML = skeletonTable(7, 5);\n document.getElementById(\"ak-pagination\").innerHTML = \"\";\n var data = await apiRaw(\"GET\", \"/.well-known/service/api/access-keys\" + qs);\n if (!data || data.error) {\n document.getElementById(\"ak-table-wrap\").innerHTML = '<div class=\"empty-state\"><div class=\"empty-state-title\">' + __t(\"accessKeys.loadFailed\") + '</div></div>';\n return;\n }\n renderAccessKeys(data.keys, { total: data.total, page: data.page, pageSize: data.pageSize });\n}\n\nfunction renderAccessKeys(keys, paging) {\n var wrap = document.getElementById(\"ak-table-wrap\");\n if (!keys || keys.length === 0) {\n wrap.innerHTML = '<div class=\"empty-state\"><div class=\"empty-state-icon\"><svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4\"/></svg></div><div class=\"empty-state-title\">' + __t(\"accessKeys.noKeys\") + '</div><div class=\"empty-state-desc\">' + __t(\"accessKeys.noKeysHint\") + '</div></div>';\n document.getElementById(\"ak-pagination\").innerHTML = \"\";\n return;\n }\n\n var html = '<table class=\"table\"><thead><tr><th>' + __t(\"accessKeys.col.keyId\") + '</th><th>' + __t(\"accessKeys.col.role\") + '</th><th class=\"col-hide-sm\">' + __t(\"accessKeys.col.remark\") + '</th><th class=\"col-hide-md\">' + __t(\"accessKeys.col.createdBy\") + '</th><th>' + __t(\"accessKeys.col.expires\") + '</th><th class=\"col-hide-md\">' + __t(\"accessKeys.col.lastUsed\") + '</th><th>' + __t(\"common.actions\") + '</th></tr></thead><tbody>';\n keys.forEach(function(k) {\n var keyIdHtml = '<did-address did=\"' + escapeHtml(k.accessKeyId) + '\" compact></did-address>';\n var roleBadge = '<span class=\"badge badge-' + k.role + '\">' + __t(\"common.\" + k.role) + '</span>';\n var remark = k.remark ? escapeHtml(k.remark.length > 40 ? k.remark.slice(0, 40) + \"...\" : k.remark) : '<span style=\"color:var(--text-secondary)\">\u2014</span>';\n var creator = k.createdByName\n ? escapeHtml(k.createdByName)\n : '<did-address did=\"' + escapeHtml(k.createdBy) + '\" compact></did-address>';\n var expiry = k.expireAt ? (new Date(k.expireAt) < new Date() ? '<span style=\"color:var(--error)\">' + __t(\"common.expired\") + '</span>' : relativeTime(k.expireAt)) : __t(\"common.never\");\n var lastUsed = k.lastUsedAt ? relativeTime(k.lastUsedAt) : __t(\"common.never\");\n var deleteLabel = k.remark ? escapeHtml(k.remark) : truncateDid(k.accessKeyId);\n\n html += '<tr>';\n html += '<td><code style=\"font-size:12px\">' + keyIdHtml + '</code></td>';\n html += '<td>' + roleBadge + '</td>';\n html += '<td class=\"col-hide-sm\">' + remark + '</td>';\n html += '<td class=\"col-hide-md\">' + creator + '</td>';\n html += '<td>' + expiry + '</td>';\n html += '<td class=\"col-hide-md\">' + lastUsed + '</td>';\n html += '<td class=\"action-cell\"><button class=\"action-trigger\" onclick=\"event.stopPropagation();toggleActionMenu(this)\">\u22EF</button><div class=\"action-menu\"><button class=\"action-menu-item\" onclick=\"event.stopPropagation();openEditAkDialog(\\''+k.accessKeyId+'\\')\">' + __t(\"common.edit\") + '</button><div class=\"action-menu-sep\"></div><button class=\"action-menu-item danger\" onclick=\"event.stopPropagation();confirmDeleteAk(\\''+k.accessKeyId+'\\',\\''+escapeHtml(deleteLabel)+'\\')\">' + __t(\"common.delete\") + '</button></div></td>';\n html += '</tr>';\n });\n html += '</tbody></table>';\n wrap.innerHTML = html;\n\n renderPagination(\"ak-pagination\", paging, loadAccessKeys);\n}\n\nfunction openCreateAkDialog() {\n var callerRole = __caller.role;\n var roleOptions = '<option value=\"guest\">Guest</option>';\n if (callerRole === \"member\" || callerRole === \"admin\" || callerRole === \"owner\") roleOptions += '<option value=\"member\"' + (callerRole === \"member\" ? ' selected' : '') + '>Member</option>';\n if (callerRole === \"admin\" || callerRole === \"owner\") roleOptions += '<option value=\"admin\"' + (callerRole === \"admin\" ? ' selected' : '') + '>Admin</option>';\n if (callerRole === \"owner\") roleOptions += '<option value=\"owner\">Owner</option>';\n\n var expiryOptions = '<option value=\"\">' + __t(\"accessKeys.expirations.never\") + '</option><option value=\"7d\">' + __t(\"accessKeys.expirations.days7\") + '</option><option value=\"30d\">' + __t(\"accessKeys.expirations.days30\") + '</option><option value=\"90d\">' + __t(\"accessKeys.expirations.days90\") + '</option><option value=\"custom\">' + __t(\"accessKeys.expirations.custom\") + '</option>';\n\n var html = '<div class=\"form-group\"><label class=\"form-label\">' + __t(\"accessKeys.role\") + '</label><select class=\"select\" id=\"ak-create-role\">' + roleOptions + '</select></div>';\n html += '<div class=\"form-group\"><label class=\"form-label\">' + __t(\"accessKeys.remark\") + '</label><input type=\"text\" class=\"input\" id=\"ak-create-remark\" placeholder=\"' + __t(\"accessKeys.remarkPlaceholder\") + '\" maxlength=\"200\" /></div>';\n html += '<div class=\"form-group\"><label class=\"form-label\">' + __t(\"accessKeys.expiration\") + '</label><select class=\"select\" id=\"ak-create-expiry\" onchange=\"toggleAkCustomExpiry()\">' + expiryOptions + '</select></div>';\n html += '<div class=\"form-group hidden\" id=\"ak-custom-expiry-wrap\"><label class=\"form-label\">' + __t(\"accessKeys.customDate\") + '</label><input type=\"datetime-local\" class=\"input\" id=\"ak-create-custom-expiry\" /></div>';\n\n openDialog(__t(\"accessKeys.createTitle\"), html, __t(\"common.save\"), async function() {\n var role = document.getElementById(\"ak-create-role\").value;\n var remark = document.getElementById(\"ak-create-remark\").value;\n var expirySelect = document.getElementById(\"ak-create-expiry\").value;\n var expireAt = null;\n\n if (expirySelect === \"custom\") {\n var customDate = document.getElementById(\"ak-create-custom-expiry\").value;\n if (!customDate) { showToast(__t(\"accessKeys.selectExpDate\"), \"error\"); return; }\n expireAt = new Date(customDate).toISOString();\n } else if (expirySelect) {\n var days = parseInt(expirySelect);\n expireAt = new Date(Date.now() + days * 86400000).toISOString();\n }\n\n var result = await apiRaw(\"POST\", \"/.well-known/service/api/access-keys\", { role: role, remark: remark, expireAt: expireAt });\n if (!result || result.error) {\n showToast(result ? result.error : __t(\"accessKeys.createFailed\"), \"error\");\n return;\n }\n\n closeDialog();\n showSecretDialog(result.accessKeySecret);\n loadAccessKeys(1);\n });\n}\n\nfunction toggleAkCustomExpiry() {\n var sel = document.getElementById(\"ak-create-expiry\").value;\n var wrap = document.getElementById(\"ak-custom-expiry-wrap\");\n if (sel === \"custom\") wrap.classList.remove(\"hidden\");\n else wrap.classList.add(\"hidden\");\n}\n\n// Slugify a display name into something safe to use as an MCP server name\n// (lowercase, alnum + hyphens). Falls back to a fixed default when empty.\nfunction akSlug(s) {\n var slug = String(s || \"\").toLowerCase().replace(/[^a-z0-9]+/g, \"-\").replace(/^-+|-+$/g, \"\");\n return slug || \"did-connect\";\n}\n\nvar __akSnippets = null;\n\nfunction showSecretDialog(secret) {\n var html = '<div style=\"margin-bottom:12px;color:var(--error);font-weight:500\">' + __t(\"accessKeys.secretWarning\") + '</div>';\n var copyIconSvg = '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\" ry=\"2\"/><path d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\"/></svg>';\n var copyBtnStyle = 'position:absolute;top:4px;right:4px;width:28px;height:28px;padding:0;display:inline-flex;align-items:center;justify-content:center;background:var(--bg-base);border:1px solid var(--border);border-radius:4px;cursor:pointer;color:var(--text-secondary);transition:color 0.15s,border-color 0.15s';\n html += '<div style=\"position:relative\"><code id=\"ak-secret-display\" style=\"display:block;padding:12px 40px 12px 12px;background:var(--bg-base);border:1px solid var(--border);border-radius:6px;font-size:13px;word-break:break-all;user-select:all;overflow-wrap:anywhere\">' + escapeHtml(secret) + '</code>';\n html += '<button id=\"ak-copy-btn\" style=\"' + copyBtnStyle + '\" onclick=\"copyAkSecret()\" title=\"' + __t(\"common.copy\") + '\">' + copyIconSvg + '</button></div>';\n\n var mcpUrl = location.origin + \"/mcp\";\n var serverName = akSlug(__pageData && __pageData.appName);\n\n __akSnippets = {\n curl: {\n label: __t(\"accessKeys.tabCurl\"),\n hint: \"\",\n code: 'curl -H \"Authorization: Bearer ' + secret + '\" ' + location.origin + '/.well-known/service/api/did/session',\n },\n claude: {\n label: __t(\"accessKeys.tabClaude\"),\n hint: \"\",\n code: 'claude mcp add --transport http ' + serverName + ' ' + mcpUrl + ' --header \"Authorization: Bearer ' + secret + '\"',\n },\n codex: {\n label: __t(\"accessKeys.tabCodex\"),\n hint: __t(\"accessKeys.addToConfig\", { file: \"~/.codex/config.toml\" }),\n code: '[mcp_servers.' + serverName + ']\\nurl = \"' + mcpUrl + '\"\\nhttp_headers = { \"Authorization\" = \"Bearer ' + secret + '\" }',\n },\n cursor: {\n label: __t(\"accessKeys.tabCursor\"),\n hint: __t(\"accessKeys.addToConfig\", { file: \"mcp.json\" }),\n code: '{\\n \"mcpServers\": {\\n \"' + serverName + '\": {\\n \"url\": \"' + mcpUrl + '\",\\n \"headers\": {\\n \"Authorization\": \"Bearer ' + secret + '\"\\n }\\n }\\n }\\n}',\n },\n };\n\n html += '<div style=\"margin-top:16px;font-size:13px;color:var(--text-secondary)\"><strong>' + __t(\"accessKeys.usage\") + '</strong></div>';\n html += '<div style=\"display:flex;gap:4px;margin-top:8px;border-bottom:1px solid var(--border)\">';\n [\"curl\", \"claude\", \"codex\", \"cursor\"].forEach(function(key) {\n var isActive = key === \"curl\";\n html += '<button type=\"button\" class=\"ak-tab-btn\" data-ak-tab=\"' + key + '\" onclick=\"switchAkTab(\\'' + key + '\\')\" style=\"padding:6px 12px;font-size:12px;font-weight:500;border:none;background:none;cursor:pointer;color:' + (isActive ? \"var(--text-white)\" : \"var(--text-tertiary)\") + ';border-bottom:2px solid ' + (isActive ? \"var(--text-white)\" : \"transparent\") + '\">' + __akSnippets[key].label + '</button>';\n });\n html += '</div>';\n html += '<div id=\"ak-snippet-hint\" style=\"margin-top:8px;font-size:12px;color:var(--text-secondary);display:none\"></div>';\n html += '<div style=\"position:relative;margin-top:6px\"><pre id=\"ak-snippet-display\" style=\"margin:0;display:block;padding:12px 40px 12px 12px;background:var(--bg-base);border:1px solid var(--border);border-radius:6px;font-size:12px;white-space:pre-wrap;word-break:break-all;overflow-wrap:anywhere;user-select:all;font-family:inherit\"></pre>';\n html += '<button id=\"ak-snippet-copy-btn\" style=\"' + copyBtnStyle + '\" onclick=\"copyAkSnippet()\" title=\"' + __t(\"common.copy\") + '\">' + copyIconSvg + '</button></div>';\n\n openDialog(__t(\"accessKeys.secretTitle\"), html, __t(\"accessKeys.done\"), function() {\n document.getElementById(\"ak-secret-display\").textContent = \"\";\n __akSnippets = null;\n closeDialog();\n });\n\n switchAkTab(\"curl\");\n}\n\nfunction switchAkTab(key) {\n var snippet = __akSnippets && __akSnippets[key];\n if (!snippet) return;\n\n document.querySelectorAll(\".ak-tab-btn\").forEach(function(btn) {\n var isActive = btn.getAttribute(\"data-ak-tab\") === key;\n btn.style.color = isActive ? \"var(--text-white)\" : \"var(--text-tertiary)\";\n btn.style.borderBottomColor = isActive ? \"var(--text-white)\" : \"transparent\";\n });\n\n var hintEl = document.getElementById(\"ak-snippet-hint\");\n if (hintEl) {\n hintEl.textContent = snippet.hint || \"\";\n hintEl.style.display = snippet.hint ? \"block\" : \"none\";\n }\n var displayEl = document.getElementById(\"ak-snippet-display\");\n if (displayEl) displayEl.textContent = snippet.code;\n}\n\nfunction copyAkSnippet() {\n var text = document.getElementById(\"ak-snippet-display\").textContent;\n var btn = document.getElementById(\"ak-snippet-copy-btn\");\n var origHtml = btn.innerHTML;\n function onCopied() {\n btn.innerHTML = '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M5 13l4 4L19 7\"/></svg>';\n btn.style.color = \"var(--green)\";\n setTimeout(function() { btn.innerHTML = origHtml; btn.style.color = \"\"; }, 2000);\n }\n if (navigator.clipboard && navigator.clipboard.writeText) {\n navigator.clipboard.writeText(text).then(onCopied).catch(function() {\n fallbackCopy(text);\n onCopied();\n });\n } else {\n fallbackCopy(text);\n onCopied();\n }\n}\n\nfunction copyAkSecret() {\n var text = document.getElementById(\"ak-secret-display\").textContent;\n var btn = document.getElementById(\"ak-copy-btn\");\n var origHtml = btn.innerHTML;\n function onCopied() {\n btn.innerHTML = '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M5 13l4 4L19 7\"/></svg>';\n btn.style.color = \"var(--green)\";\n setTimeout(function() { btn.innerHTML = origHtml; btn.style.color = \"\"; }, 2000);\n }\n if (navigator.clipboard && navigator.clipboard.writeText) {\n navigator.clipboard.writeText(text).then(onCopied).catch(function() {\n fallbackCopy(text);\n onCopied();\n });\n } else {\n fallbackCopy(text);\n onCopied();\n }\n}\n\nasync function openEditAkDialog(accessKeyId) {\n var data = await apiRaw(\"GET\", \"/.well-known/service/api/access-keys/\" + encodeURIComponent(accessKeyId));\n if (!data || data.error) { showToast(__t(\"accessKeys.loadKeyFailed\"), \"error\"); return; }\n\n var html = '<div class=\"form-group\"><label class=\"form-label\">' + __t(\"accessKeys.col.keyId\") + '</label><code style=\"font-size:12px\">' + escapeHtml(data.accessKeyId) + '</code></div>';\n html += '<div class=\"form-group\"><label class=\"form-label\">' + __t(\"accessKeys.role\") + '</label><span class=\"badge badge-' + data.role + '\">' + __t(\"common.\" + data.role) + '</span></div>';\n html += '<div class=\"form-group\"><label class=\"form-label\">' + __t(\"accessKeys.remark\") + '</label><input type=\"text\" class=\"input\" id=\"ak-edit-remark\" value=\"' + escapeHtml(data.remark || \"\") + '\" maxlength=\"200\" /></div>';\n html += '<div class=\"form-group\"><label class=\"form-label\">' + __t(\"accessKeys.expiration\") + '</label><input type=\"datetime-local\" class=\"input\" id=\"ak-edit-expiry\" value=\"' + (data.expireAt ? data.expireAt.slice(0,16) : \"\") + '\" /> <label style=\"margin-top:4px;display:block;font-size:13px\"><input type=\"checkbox\" id=\"ak-edit-no-expiry\"' + (data.expireAt ? \"\" : \" checked\") + ' onchange=\"toggleAkEditExpiry()\" /> ' + __t(\"accessKeys.neverExpires\") + '</label></div>';\n\n openDialog(__t(\"accessKeys.editTitle\"), html, __t(\"common.save\"), async function() {\n var remark = document.getElementById(\"ak-edit-remark\").value;\n var noExpiry = document.getElementById(\"ak-edit-no-expiry\").checked;\n var expireAt = noExpiry ? null : (document.getElementById(\"ak-edit-expiry\").value ? new Date(document.getElementById(\"ak-edit-expiry\").value).toISOString() : undefined);\n\n var body = { remark: remark };\n if (expireAt !== undefined) body.expireAt = expireAt;\n\n var result = await apiRaw(\"PUT\", \"/.well-known/service/api/access-keys/\" + encodeURIComponent(accessKeyId), body);\n if (!result || result.error) {\n showToast(result ? result.error : __t(\"accessKeys.updateFailed\"), \"error\");\n return;\n }\n closeDialog();\n showToast(__t(\"accessKeys.keyUpdated\"), \"success\");\n loadAccessKeys(akPage);\n });\n}\n\nfunction toggleAkEditExpiry() {\n var noExpiry = document.getElementById(\"ak-edit-no-expiry\").checked;\n document.getElementById(\"ak-edit-expiry\").disabled = noExpiry;\n}\n\nfunction confirmDeleteAk(accessKeyId, label) {\n openDialog(__t(\"accessKeys.deleteTitle\"), '<p>' + __t(\"accessKeys.deleteMsg\", { label: escapeHtml(label) }) + '</p><p style=\"color:var(--error)\">' + __t(\"accessKeys.deleteWarning\") + '</p>', __t(\"common.delete\"), async function() {\n var result = await apiRaw(\"DELETE\", \"/.well-known/service/api/access-keys/\" + encodeURIComponent(accessKeyId));\n if (result && result.error) {\n showToast(result.error, \"error\");\n return;\n }\n closeDialog();\n showToast(__t(\"accessKeys.keyDeleted\"), \"success\");\n loadAccessKeys(akPage);\n });\n}\n\n// Direct API call (bypasses team handler's /team prefix)\nasync function apiRaw(method, path, body) {\n try {\n var opts = { method: method, headers: { \"Accept\": \"application/json\" } };\n if (body !== undefined) {\n opts.headers[\"Content-Type\"] = \"application/json\";\n opts.body = JSON.stringify(body);\n }\n var res = await fetch(path, opts);\n if (res.status === 204) return {};\n return await res.json();\n } catch(e) {\n return null;\n }\n}\n\nregisterTab(\"access-keys\", function() { loadAccessKeys(1); });\n";
7
7
  export {};
8
8
  //# sourceMappingURL=tab-access-keys.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tab-access-keys.d.ts","sourceRoot":"","sources":["../../../src/pages/admin/tab-access-keys.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,KAAK,GAAG,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC;AAEpE,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,GAAG,GAAG,MAAM,CAoBhD;AAED,eAAO,MAAM,sBAAsB,k/dAqOlC,CAAC"}
1
+ {"version":3,"file":"tab-access-keys.d.ts","sourceRoot":"","sources":["../../../src/pages/admin/tab-access-keys.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,KAAK,GAAG,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC;AAEpE,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,GAAG,GAAG,MAAM,CAoBhD;AAED,eAAO,MAAM,sBAAsB,m/jBAqSlC,CAAC"}
@@ -131,26 +131,90 @@ function toggleAkCustomExpiry() {
131
131
  else wrap.classList.add("hidden");
132
132
  }
133
133
 
134
+ // Slugify a display name into something safe to use as an MCP server name
135
+ // (lowercase, alnum + hyphens). Falls back to a fixed default when empty.
136
+ function akSlug(s) {
137
+ var slug = String(s || "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
138
+ return slug || "did-connect";
139
+ }
140
+
141
+ var __akSnippets = null;
142
+
134
143
  function showSecretDialog(secret) {
135
144
  var html = '<div style="margin-bottom:12px;color:var(--error);font-weight:500">' + __t("accessKeys.secretWarning") + '</div>';
136
145
  var copyIconSvg = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>';
137
146
  var copyBtnStyle = 'position:absolute;top:4px;right:4px;width:28px;height:28px;padding:0;display:inline-flex;align-items:center;justify-content:center;background:var(--bg-base);border:1px solid var(--border);border-radius:4px;cursor:pointer;color:var(--text-secondary);transition:color 0.15s,border-color 0.15s';
138
147
  html += '<div style="position:relative"><code id="ak-secret-display" style="display:block;padding:12px 40px 12px 12px;background:var(--bg-base);border:1px solid var(--border);border-radius:6px;font-size:13px;word-break:break-all;user-select:all;overflow-wrap:anywhere">' + escapeHtml(secret) + '</code>';
139
148
  html += '<button id="ak-copy-btn" style="' + copyBtnStyle + '" onclick="copyAkSecret()" title="' + __t("common.copy") + '">' + copyIconSvg + '</button></div>';
140
- var curlCmd = 'curl -H "Authorization: Bearer ' + secret + '" ' + location.origin + '/.well-known/service/api/did/session';
141
- html += '<div style="margin-top:12px;font-size:13px;color:var(--text-secondary)"><strong>' + __t("accessKeys.usage") + '</strong></div>';
142
- html += '<div style="position:relative;margin-top:6px"><code id="ak-curl-display" style="display:block;padding:12px 40px 12px 12px;background:var(--bg-base);border:1px solid var(--border);border-radius:6px;font-size:12px;word-break:break-all;overflow-wrap:anywhere;user-select:all">' + escapeHtml(curlCmd) + '</code>';
143
- html += '<button id="ak-curl-copy-btn" style="' + copyBtnStyle + '" onclick="copyAkCurl()" title="' + __t("common.copy") + '">' + copyIconSvg + '</button></div>';
149
+
150
+ var mcpUrl = location.origin + "/mcp";
151
+ var serverName = akSlug(__pageData && __pageData.appName);
152
+
153
+ __akSnippets = {
154
+ curl: {
155
+ label: __t("accessKeys.tabCurl"),
156
+ hint: "",
157
+ code: 'curl -H "Authorization: Bearer ' + secret + '" ' + location.origin + '/.well-known/service/api/did/session',
158
+ },
159
+ claude: {
160
+ label: __t("accessKeys.tabClaude"),
161
+ hint: "",
162
+ code: 'claude mcp add --transport http ' + serverName + ' ' + mcpUrl + ' --header "Authorization: Bearer ' + secret + '"',
163
+ },
164
+ codex: {
165
+ label: __t("accessKeys.tabCodex"),
166
+ hint: __t("accessKeys.addToConfig", { file: "~/.codex/config.toml" }),
167
+ code: '[mcp_servers.' + serverName + ']\\nurl = "' + mcpUrl + '"\\nhttp_headers = { "Authorization" = "Bearer ' + secret + '" }',
168
+ },
169
+ cursor: {
170
+ label: __t("accessKeys.tabCursor"),
171
+ hint: __t("accessKeys.addToConfig", { file: "mcp.json" }),
172
+ code: '{\\n "mcpServers": {\\n "' + serverName + '": {\\n "url": "' + mcpUrl + '",\\n "headers": {\\n "Authorization": "Bearer ' + secret + '"\\n }\\n }\\n }\\n}',
173
+ },
174
+ };
175
+
176
+ html += '<div style="margin-top:16px;font-size:13px;color:var(--text-secondary)"><strong>' + __t("accessKeys.usage") + '</strong></div>';
177
+ html += '<div style="display:flex;gap:4px;margin-top:8px;border-bottom:1px solid var(--border)">';
178
+ ["curl", "claude", "codex", "cursor"].forEach(function(key) {
179
+ var isActive = key === "curl";
180
+ html += '<button type="button" class="ak-tab-btn" data-ak-tab="' + key + '" onclick="switchAkTab(\\'' + key + '\\')" style="padding:6px 12px;font-size:12px;font-weight:500;border:none;background:none;cursor:pointer;color:' + (isActive ? "var(--text-white)" : "var(--text-tertiary)") + ';border-bottom:2px solid ' + (isActive ? "var(--text-white)" : "transparent") + '">' + __akSnippets[key].label + '</button>';
181
+ });
182
+ html += '</div>';
183
+ html += '<div id="ak-snippet-hint" style="margin-top:8px;font-size:12px;color:var(--text-secondary);display:none"></div>';
184
+ html += '<div style="position:relative;margin-top:6px"><pre id="ak-snippet-display" style="margin:0;display:block;padding:12px 40px 12px 12px;background:var(--bg-base);border:1px solid var(--border);border-radius:6px;font-size:12px;white-space:pre-wrap;word-break:break-all;overflow-wrap:anywhere;user-select:all;font-family:inherit"></pre>';
185
+ html += '<button id="ak-snippet-copy-btn" style="' + copyBtnStyle + '" onclick="copyAkSnippet()" title="' + __t("common.copy") + '">' + copyIconSvg + '</button></div>';
144
186
 
145
187
  openDialog(__t("accessKeys.secretTitle"), html, __t("accessKeys.done"), function() {
146
188
  document.getElementById("ak-secret-display").textContent = "";
189
+ __akSnippets = null;
147
190
  closeDialog();
148
191
  });
192
+
193
+ switchAkTab("curl");
194
+ }
195
+
196
+ function switchAkTab(key) {
197
+ var snippet = __akSnippets && __akSnippets[key];
198
+ if (!snippet) return;
199
+
200
+ document.querySelectorAll(".ak-tab-btn").forEach(function(btn) {
201
+ var isActive = btn.getAttribute("data-ak-tab") === key;
202
+ btn.style.color = isActive ? "var(--text-white)" : "var(--text-tertiary)";
203
+ btn.style.borderBottomColor = isActive ? "var(--text-white)" : "transparent";
204
+ });
205
+
206
+ var hintEl = document.getElementById("ak-snippet-hint");
207
+ if (hintEl) {
208
+ hintEl.textContent = snippet.hint || "";
209
+ hintEl.style.display = snippet.hint ? "block" : "none";
210
+ }
211
+ var displayEl = document.getElementById("ak-snippet-display");
212
+ if (displayEl) displayEl.textContent = snippet.code;
149
213
  }
150
214
 
151
- function copyAkCurl() {
152
- var text = document.getElementById("ak-curl-display").textContent;
153
- var btn = document.getElementById("ak-curl-copy-btn");
215
+ function copyAkSnippet() {
216
+ var text = document.getElementById("ak-snippet-display").textContent;
217
+ var btn = document.getElementById("ak-snippet-copy-btn");
154
218
  var origHtml = btn.innerHTML;
155
219
  function onCopied() {
156
220
  btn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 13l4 4L19 7"/></svg>';
@@ -1 +1 @@
1
- {"version":3,"file":"tab-access-keys.js","sourceRoot":"","sources":["../../../src/pages/admin/tab-access-keys.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,MAAM,UAAU,iBAAiB,CAAC,CAAM;IACtC,OAAO,UAAU,CAAC;;8BAEU,CAAC,CAAC,kBAAkB,CAAC;4BACvB,CAAC,CAAC,iBAAiB,CAAC;;;;;0EAK0B,CAAC,CAAC,mBAAmB,CAAC;;4EAEpB,CAAC,CAAC,sBAAsB,CAAC;;;;;;;;CAQpG,CAAC;AACF,CAAC;AAED,MAAM,CAAC,MAAM,sBAAsB,GAAG,QAAQ,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqO9C,CAAC"}
1
+ {"version":3,"file":"tab-access-keys.js","sourceRoot":"","sources":["../../../src/pages/admin/tab-access-keys.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,MAAM,UAAU,iBAAiB,CAAC,CAAM;IACtC,OAAO,UAAU,CAAC;;8BAEU,CAAC,CAAC,kBAAkB,CAAC;4BACvB,CAAC,CAAC,iBAAiB,CAAC;;;;;0EAK0B,CAAC,CAAC,mBAAmB,CAAC;;4EAEpB,CAAC,CAAC,sBAAsB,CAAC;;;;;;;;CAQpG,CAAC;AACF,CAAC;AAED,MAAM,CAAC,MAAM,sBAAsB,GAAG,QAAQ,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqS9C,CAAC"}
@@ -3,6 +3,6 @@
3
3
  */
4
4
  type TFn = (key: string, params?: Record<string, string>) => string;
5
5
  export declare function membersTabHtml(t: TFn): string;
6
- export declare const MEMBERS_TAB_SCRIPT = "\nvar membersPage = 1;\nvar membersPageSize = 20;\nvar memberSearchTimeout = null;\nvar currentMembersList = [];\nvar selectedMember = null;\nvar __membersAggregated = false;\n// Sort state: empty string = default (server decides + self-first on client).\n// Three-state click cycle: (none) \u2192 desc \u2192 asc \u2192 (none).\n// Persisted in location.search as sortBy/sortOrder so it survives refresh + share.\nvar membersSortBy = \"\";\nvar membersSortOrder = \"\";\nvar MEMBERS_SORT_VALID = [\"name\", \"role\", \"status\", \"source\", \"joined\"];\n\n/** Populate module sort state from the current URL query string. Idempotent. */\nfunction readMembersSortFromURL() {\n membersSortBy = \"\";\n membersSortOrder = \"\";\n var params = new URLSearchParams(location.search);\n var by = params.get(\"sortBy\");\n var order = params.get(\"sortOrder\");\n if (by && MEMBERS_SORT_VALID.indexOf(by) >= 0) {\n membersSortBy = by;\n membersSortOrder = (order === \"asc\" || order === \"desc\") ? order : \"desc\";\n }\n}\n\n/** Write current sort state back to location.search without reloading. */\nfunction writeMembersSortToURL() {\n var params = new URLSearchParams(location.search);\n if (membersSortBy) {\n params.set(\"sortBy\", membersSortBy);\n params.set(\"sortOrder\", membersSortOrder || \"desc\");\n } else {\n params.delete(\"sortBy\");\n params.delete(\"sortOrder\");\n }\n var qs = params.toString();\n var newUrl = location.pathname + (qs ? \"?\" + qs : \"\") + location.hash;\n history.replaceState(null, \"\", newUrl);\n}\n\n/** Cycle sort state for a column key and reload. */\nfunction toggleSort(key) {\n if (membersSortBy !== key) {\n membersSortBy = key;\n membersSortOrder = \"desc\";\n } else if (membersSortOrder === \"desc\") {\n membersSortOrder = \"asc\";\n } else {\n // Third click \u2014 back to default.\n membersSortBy = \"\";\n membersSortOrder = \"\";\n }\n writeMembersSortToURL();\n loadMembers(1);\n}\n\n/** Render a <th> with click-to-sort behavior. */\nfunction sortableTh(key, label, extraClass) {\n var isActive = membersSortBy === key;\n var arrow;\n if (isActive) {\n arrow = membersSortOrder === \"asc\"\n ? '<span class=\"sort-arrow sort-arrow-active\">\\u2191</span>'\n : '<span class=\"sort-arrow sort-arrow-active\">\\u2193</span>';\n } else {\n arrow = '<span class=\"sort-arrow\">\\u2195</span>';\n }\n var cls = \"sortable\" + (isActive ? \" sort-active\" : \"\") + (extraClass ? \" \" + extraClass : \"\");\n return '<th class=\"' + cls + '\" onclick=\"toggleSort(\\'' + key + '\\')\">' + label + arrow + '</th>';\n}\n\nfunction providerLabel(sp) {\n if (!sp) return \"\\u2014\";\n var key = \"members.providers.\" + sp;\n var val = __t(key);\n return val !== key ? val : sp;\n}\n\nfunction debouncedMemberSearch() {\n clearTimeout(memberSearchTimeout);\n memberSearchTimeout = setTimeout(function() { loadMembers(1); }, 300);\n}\n\n// Load registered instances for the filter dropdown (system admin only)\nvar __instanceNames = {};\nvar __instanceFilterInitialized = false;\nasync function initInstanceFilter() {\n if (__instanceFilterInitialized) return;\n try {\n var data = await api(\"GET\", \"/../admin/instances\");\n if (data.ok && data.instances && data.instances.length > 0) {\n __instanceFilterInitialized = true;\n var sel = document.getElementById(\"members-instance-filter\");\n data.instances.forEach(function(inst) {\n __instanceNames[inst.instanceDid] = inst.appName || inst.instanceDid.substring(0, 16);\n var opt = document.createElement(\"option\");\n opt.value = inst.instanceDid;\n opt.textContent = __instanceNames[inst.instanceDid];\n sel.appendChild(opt);\n });\n sel.style.display = \"\";\n }\n } catch(e) { /* not admin or no instances */ }\n}\n\n// Also support specific instance filter via query param\n// When ?instance=z1xxx, team handler needs to use that instanceDid\n\nasync function loadMembers(page) {\n membersPage = page || 1;\n var instanceFilter = document.getElementById(\"members-instance-filter\").value;\n var qs = \"?page=\" + membersPage + \"&pageSize=\" + membersPageSize;\n // System view (\"\") \u2192 use defaultInstanceDid so we query membership, not global users\n if (instanceFilter === \"\" && __pageData.instanceDid) {\n qs += \"&instance=\" + encodeURIComponent(__pageData.instanceDid);\n } else if (instanceFilter) {\n qs += \"&instance=\" + encodeURIComponent(instanceFilter);\n }\n var search = document.getElementById(\"members-search\").value.trim();\n if (search) qs += \"&search=\" + encodeURIComponent(search);\n var role = document.getElementById(\"members-role-filter\").value;\n if (role) qs += \"&role=\" + encodeURIComponent(role);\n var status = document.getElementById(\"members-status-filter\").value;\n if (status !== \"\") qs += \"&approved=\" + status;\n var source = document.getElementById(\"members-source-filter\").value;\n if (source) qs += \"&sourceProvider=\" + encodeURIComponent(source);\n if (membersSortBy) {\n qs += \"&sortBy=\" + encodeURIComponent(membersSortBy);\n qs += \"&sortOrder=\" + encodeURIComponent(membersSortOrder || \"desc\");\n }\n\n document.getElementById(\"members-table-wrap\").innerHTML = skeletonTable(8, 6);\n document.getElementById(\"members-pagination\").innerHTML = \"\";\n var data = await api(\"GET\", \"/members\" + qs);\n if (!data.ok) {\n document.getElementById(\"members-table-wrap\").innerHTML = '<div class=\"empty-state\"><div class=\"empty-state-icon\"><svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 8v4m0 4h.01\"/></svg></div><div class=\"empty-state-title\">' + __t(\"members.loadFailed\") + '</div></div>';\n return;\n }\n __membersAggregated = !!data.aggregated;\n // Only pin the caller to the top when using the default (server) order.\n // Under explicit sort, honor the user's intent without re-shuffling.\n if (membersSortBy) {\n currentMembersList = data.users || [];\n } else {\n currentMembersList = (data.users || []).slice().sort(function(a, b) {\n if (a.did === __caller.did) return -1;\n if (b.did === __caller.did) return 1;\n return (a.createdAt || 0) - (b.createdAt || 0);\n });\n }\n renderMembersTable(currentMembersList, data.paging);\n updateTransferSection(data.users);\n}\n\nfunction renderMembersTable(users, paging) {\n var wrap = document.getElementById(\"members-table-wrap\");\n if (!users || users.length === 0) {\n wrap.innerHTML = '<div class=\"empty-state\"><div class=\"empty-state-icon\"><svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2\"/><circle cx=\"9\" cy=\"7\" r=\"4\"/></svg></div><div class=\"empty-state-title\">' + __t(\"members.noMembers\") + '</div><div class=\"empty-state-desc\">' + __t(\"members.noMembersHint\") + '</div></div>';\n document.getElementById(\"members-pagination\").innerHTML = \"\";\n return;\n }\n\n var instanceSel = document.getElementById(\"members-instance-filter\");\n var instanceVisible = instanceSel && instanceSel.style.display !== 'none';\n var showInstance = instanceVisible && instanceSel.value === \"all\";\n var html = '<table class=\"table\"><thead><tr>';\n html += sortableTh(\"name\", __t(\"members.col.member\"));\n html += sortableTh(\"role\", __t(\"members.col.role\"));\n if (showInstance) html += '<th>' + __t(\"members.col.instance\") + '</th>';\n html += sortableTh(\"source\", __t(\"members.col.source\"), \"col-hide-sm\");\n html += sortableTh(\"status\", __t(\"members.col.status\"), \"col-hide-sm\");\n html += sortableTh(\"joined\", __t(\"members.col.joined\"), \"col-hide-md\");\n // Registered/Last Login From columns removed \u2014 domain info not meaningful in Worker context\n html += '<th></th></tr></thead><tbody>';\n users.forEach(function(u) {\n var userDid = u.did || u.user_did || '';\n var av = renderAvatar(u.avatar, u.fullName, \"sm\");\n var name = u.fullName ? escapeHtml(u.fullName) : '<span class=\"text-muted\">(unnamed)</span>';\n var did = '<did-address did=\"' + escapeHtml(userDid) + '\" compact></did-address>';\n var roleBadge = '<span class=\"badge badge-' + (u.role || \"guest\") + '\">' + __t(\"common.\" + (u.role || \"guest\")) + '</span>';\n var statusBadge = u.approved !== undefined ? (u.approved ? '<span class=\"badge badge-active\">' + __t(\"common.active\") + '</span>' : '<span class=\"badge badge-blocked\">' + __t(\"common.blocked\") + '</span>') : '<span class=\"text-muted\">\u2014</span>';\n var joined = (u.createdAt || u.joined_at) ? '<span class=\"cell-date\">' + escapeHtml(relativeTime(u.createdAt || u.joined_at)) + '</span>' : '<span class=\"text-muted\">\u2014</span>';\n html += '<tr style=\"cursor:pointer\" onclick=\"openMemberDetail(\\'' + escapeHtml(userDid) + '\\')\">';\n html += '<td><div class=\"member-identity\">' + av + '<div><div class=\"member-name\">' + name + '</div><div class=\"member-email\">' + did + '</div></div></div></td>';\n html += '<td>' + roleBadge + '</td>';\n if (showInstance) {\n if (__membersAggregated && u.instance_count > 1) {\n html += '<td><a href=\"javascript:void(0)\" onclick=\"event.stopPropagation();openMemberDetail(\\'' + escapeHtml(userDid) + '\\')\" style=\"color:var(--blue);font-size:14px;text-decoration:none\">' + u.instance_count + ' ' + __t(\"members.instances\") + '</a></td>';\n } else {\n var instDid = (__membersAggregated && u.instances && u.instances.length === 1) ? u.instances[0].instance_did : (u.instance_did || '');\n var instName = __instanceNames[instDid] || '';\n if (!instName) instName = instDid ? __t(\"members.currentInstance\") : '\u2014';\n html += '<td><div>' + escapeHtml(instName);\n if (instDid) html += '<div style=\"font-size:11px;color:var(--text-secondary);margin-top:2px\" onclick=\"event.stopPropagation()\"><did-address did=\"' + escapeHtml(instDid) + '\" compact></did-address></div>';\n html += '</div></td>';\n }\n }\n var sourceBadge = '<span class=\"text-muted\">' + escapeHtml(providerLabel(u.sourceProvider)) + '</span>';\n html += '<td class=\"col-hide-sm\">' + sourceBadge + '</td>';\n html += '<td class=\"col-hide-sm\">' + statusBadge + '</td>';\n html += '<td class=\"col-hide-md\">' + joined + '</td>';\n // Actions: only for current instance members, not cross-instance\n var canOperate = !instanceVisible || (instanceVisible && instanceSel.value === '');\n // In aggregated mode, only allow actions on users whose instance matches current system\n if (__membersAggregated) canOperate = false;\n html += '<td class=\"action-cell\">' + (canOperate ? renderMemberActions(u) : '') + '</td>';\n html += '</tr>';\n });\n html += '</tbody></table>';\n wrap.innerHTML = html;\n\n renderPagination(\"members-pagination\", paging, loadMembers);\n}\n\nfunction renderMemberActions(u) {\n var isOwner = __caller.role === \"owner\";\n var isAdmin = __caller.role === \"admin\";\n var isSelf = u.did === __caller.did;\n var ROLE_LEVEL = { owner: 3, admin: 2, member: 1, guest: 0 };\n var callerLevel = ROLE_LEVEL[__caller.role] || 0;\n var targetLevel = ROLE_LEVEL[u.role] || 0;\n // Can only act on members with lower privilege\n var canManage = !isSelf && targetLevel < callerLevel;\n var items = [];\n\n // Change role: can manage lower-level members\n if (canManage) {\n items.push('<button class=\"action-menu-item\" onclick=\"event.stopPropagation();openChangeRoleForDid(\\'' + escapeHtml(u.did) + '\\')\">' + __t(\"members.changeRole\") + '</button>');\n }\n if (canManage && u.approved) {\n items.push('<button class=\"action-menu-item\" onclick=\"event.stopPropagation();blockMember(\\'' + escapeHtml(u.did) + '\\',\\'' + escapeHtml(u.fullName || u.did) + '\\')\">' + __t(\"members.block\") + '</button>');\n }\n if (canManage && !u.approved) {\n items.push('<button class=\"action-menu-item\" onclick=\"event.stopPropagation();unblockMember(\\'' + escapeHtml(u.did) + '\\',\\'' + escapeHtml(u.fullName || u.did) + '\\')\">' + __t(\"members.unblock\") + '</button>');\n }\n if (items.length > 0 && canManage) {\n items.push('<div class=\"action-menu-sep\"></div>');\n }\n if (canManage) {\n items.push('<button class=\"action-menu-item danger\" onclick=\"event.stopPropagation();removeMember(\\'' + escapeHtml(u.did) + '\\',\\'' + escapeHtml(u.fullName || u.did) + '\\')\">' + __t(\"members.remove\") + '</button>');\n }\n\n if (items.length === 0) return \"\";\n return '<button class=\"action-trigger\" onclick=\"event.stopPropagation();toggleActionMenu(this)\">\u22EF</button><div class=\"action-menu\">' + items.join(\"\") + '</div>';\n}\n\nfunction toggleActionMenu(trigger) {\n var menu = trigger.nextElementSibling;\n var wasOpen = menu.classList.contains(\"open\");\n // Close all menus first\n document.querySelectorAll(\".action-menu.open\").forEach(function(m) { m.classList.remove(\"open\"); });\n if (!wasOpen) menu.classList.toggle(\"open\");\n}\n\nasync function openMemberDetail(did) {\n var u = currentMembersList.find(function(m) { return m.did === did; });\n if (!u) return;\n selectedMember = u;\n\n // Fetch full member info (with connectedAccounts) from API.\n // Falls back to in-memory list data if the request fails.\n var detail = u;\n try {\n var resp = await api(\"GET\", \"/members/\" + encodeURIComponent(did));\n if (resp && resp.ok && resp.user) {\n // Merge: in-memory list has fields the API may omit (instances, passkeyCount in some paths).\n detail = Object.assign({}, u, resp.user);\n selectedMember = detail;\n }\n } catch (e) {}\n\n var body = document.getElementById(\"member-detail-body\");\n var html = '<div class=\"profile-header\">' + renderAvatar(detail.avatar, detail.fullName) + '<div>';\n html += '<div style=\"font-weight:500;font-size:16px;color:var(--text-white)\">' + escapeHtml(detail.fullName || \"(unnamed)\") + '</div>';\n html += '<did-address did=\"' + escapeHtml(detail.did) + '\" compact></did-address>';\n html += '</div></div>';\n\n html += '<div class=\"settings-card\" style=\"margin-top:16px\">';\n html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailRole\") + '</span><span class=\"settings-value\"><span class=\"badge badge-' + (detail.role || \"guest\") + '\">' + __t(\"common.\" + (detail.role || \"guest\")) + '</span></span></div>';\n html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailStatus\") + '</span><span class=\"settings-value\">' + (detail.approved ? '<span class=\"badge badge-active\">' + __t(\"common.active\") + '</span>' : '<span class=\"badge badge-blocked\">' + __t(\"common.blocked\") + '</span>') + '</span></div>';\n html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailSource\") + '</span><span class=\"settings-value\">' + escapeHtml(providerLabel(detail.sourceProvider)) + '</span></div>';\n if (detail.email) html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailEmail\") + '</span><span class=\"settings-value\">' + escapeHtml(detail.email) + '</span></div>';\n if (detail.inviterName) html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailInvitedBy\") + '</span><span class=\"settings-value\">' + escapeHtml(detail.inviterName) + '</span></div>';\n if (detail.createdAt) html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailJoined\") + '</span><span class=\"settings-value\">' + escapeHtml(absoluteTime(detail.createdAt)) + '</span></div>';\n if (detail.lastLoginAt) html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailLastLogin\") + '</span><span class=\"settings-value\">' + escapeHtml(absoluteTime(detail.lastLoginAt)) + '</span></div>';\n if (detail.sourceDomain) html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailRegisteredFrom\") + '</span><span class=\"settings-value\">' + escapeHtml(detail.sourceDomain) + '</span></div>';\n if (detail.lastLoginDomain) html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailLastLoginFrom\") + '</span><span class=\"settings-value\">' + escapeHtml(detail.lastLoginDomain) + '</span></div>';\n if (detail.passkeyCount !== undefined) html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailPasskeys\") + '</span><span class=\"settings-value\">' + detail.passkeyCount + '</span></div>';\n html += '</div>';\n\n // Connected Accounts (read-only)\n if (detail.connectedAccounts && detail.connectedAccounts.length > 0) {\n html += '<div class=\"settings-card\" style=\"margin-top:16px\">';\n html += '<div class=\"settings-card-header\"><span>' + __t(\"members.detailConnectedAccounts\") + ' (' + detail.connectedAccounts.length + ')</span></div>';\n html += '<div style=\"max-height:240px;overflow-y:auto\">';\n detail.connectedAccounts.forEach(function(a) {\n var label = providerLabel(a.provider);\n var info = '';\n if (a.userInfo && a.userInfo.name) info = a.userInfo.name;\n else if (a.userInfo && a.userInfo.email) info = a.userInfo.email;\n else if (a.id) info = a.id;\n html += '<div class=\"settings-row\" style=\"gap:12px;align-items:flex-start\">';\n html += '<div style=\"flex:1;min-width:0\">';\n html += '<div style=\"font-size:14px;font-weight:500;color:var(--text)\">' + escapeHtml(label);\n if (a.isMain) html += ' <span class=\"badge badge-active\" style=\"font-size:10px;padding:1px 6px;\">' + __t(\"profile.mainAccount\") + '</span>';\n html += '</div>';\n if (info) html += '<div style=\"font-size:12px;color:var(--text-secondary);margin-top:2px\">' + escapeHtml(info) + '</div>';\n if (a.did) html += '<div style=\"font-size:11px;color:var(--text-secondary);margin-top:2px\"><did-address did=\"' + escapeHtml(a.did) + '\" compact></did-address></div>';\n html += '</div>';\n if (a.lastLoginAt) html += '<div style=\"font-size:12px;color:var(--text-secondary);flex-shrink:0\">' + escapeHtml(relativeTime(a.lastLoginAt)) + '</div>';\n html += '</div>';\n });\n html += '</div></div>';\n }\n\n // Instance memberships (aggregated mode)\n if (detail.instances && detail.instances.length > 0) {\n html += '<div class=\"settings-card\" style=\"margin-top:16px\">';\n html += '<div class=\"settings-card-header\"><span>' + __t(\"members.instanceMemberships\") + ' (' + detail.instances.length + ')</span></div>';\n html += '<div style=\"max-height:240px;overflow-y:auto\">';\n detail.instances.forEach(function(inst) {\n var instName = __instanceNames[inst.instance_did] || '';\n var instDid = inst.instance_did || '';\n html += '<div class=\"settings-row\" style=\"gap:12px\">';\n html += '<div style=\"flex:1;min-width:0\"><div style=\"font-size:14px;font-weight:500;color:var(--text)\">' + escapeHtml(instName || __t(\"members.currentInstance\")) + '</div>';\n if (instDid) html += '<div style=\"font-size:11px;color:var(--text-secondary);margin-top:2px\"><did-address did=\"' + escapeHtml(instDid) + '\" compact></did-address></div>';\n html += '</div>';\n html += '<div style=\"display:flex;align-items:center;gap:8px;flex-shrink:0\"><span class=\"badge badge-' + inst.role + '\">' + __t(\"common.\" + inst.role) + '</span>';\n if (inst.joined_at) html += '<span style=\"font-size:12px;color:var(--text-secondary)\">' + escapeHtml(relativeTime(inst.joined_at)) + '</span>';\n html += '</div></div>';\n });\n html += '</div></div>';\n }\n\n body.innerHTML = html;\n\n // Change role: mirror list's canOperate + renderMemberActions logic exactly.\n // Use post-merge detail so role/did reflect the latest API response.\n var changeRoleBtn = document.getElementById(\"member-detail-change-role\");\n var instanceSel = document.getElementById(\"members-instance-filter\");\n var instanceVisible = instanceSel && instanceSel.style.display !== 'none';\n var canOperate = !instanceVisible || (instanceVisible && instanceSel.value === '');\n if (__membersAggregated) canOperate = false;\n var isSelf = detail.did === __caller.did;\n var ROLE_LEVEL = { owner: 3, admin: 2, member: 1, guest: 0 };\n var callerLevel = ROLE_LEVEL[__caller.role] || 0;\n var targetLevel = ROLE_LEVEL[detail.role] || 0;\n var canChangeRole = canOperate && !isSelf && targetLevel < callerLevel;\n if (canChangeRole) {\n changeRoleBtn.classList.remove(\"hidden\");\n } else {\n changeRoleBtn.classList.add(\"hidden\");\n }\n\n showDialog(\"member-detail-dialog\");\n}\n\nfunction openChangeRoleForDid(did) {\n var u = currentMembersList.find(function(m) { return m.did === did; });\n if (!u) return;\n selectedMember = u;\n openChangeRoleDialog();\n}\n\nfunction openChangeRoleDialog() {\n if (!selectedMember) return;\n document.getElementById(\"change-role-title\").textContent = __t(\"members.changeRole\");\n document.getElementById(\"change-role-subtitle\").textContent = __t(\"members.changeRoleFor\", { name: selectedMember.fullName || truncateDid(selectedMember.did) });\n // Show/hide role options based on caller's level (only owner can assign admin)\n var radios = document.querySelectorAll('input[name=\"new-role\"]');\n radios.forEach(function(r) {\n r.checked = r.value === selectedMember.role;\n var label = r.closest('label');\n if (label) {\n if (r.value === 'admin' && __caller.role !== 'owner') label.style.display = 'none';\n else label.style.display = '';\n }\n });\n closeDialog(\"member-detail-dialog\");\n showDialog(\"change-role-dialog\");\n}\n\nasync function saveRoleChange() {\n if (!selectedMember) return;\n var selected = document.querySelector('input[name=\"new-role\"]:checked');\n if (!selected) return;\n var newRole = selected.value;\n var data = await api(\"PUT\", \"/members/\" + encodeURIComponent(selectedMember.did) + \"/role\", { role: newRole });\n if (data.ok) {\n showToast(__t(\"members.roleUpdated\"), \"success\");\n closeDialog(\"change-role-dialog\");\n loadMembers(membersPage);\n }\n}\n\nasync function blockMember(did, name) {\n var ok = await confirmDialog({ title: __t(\"members.blockTitle\"), message: __t(\"members.blockMsg\", { name: name }), confirmLabel: __t(\"members.blockBtn\"), danger: true });\n if (!ok) return;\n var data = await api(\"PUT\", \"/members/\" + encodeURIComponent(did) + \"/approval\", { approved: false });\n if (data.ok) { showToast(__t(\"members.blockSuccess\"), \"success\"); loadMembers(membersPage); }\n}\n\nasync function unblockMember(did, name) {\n var ok = await confirmDialog({ title: __t(\"members.unblockTitle\"), message: __t(\"members.unblockMsg\", { name: name }), confirmLabel: __t(\"members.unblock\") });\n if (!ok) return;\n var data = await api(\"PUT\", \"/members/\" + encodeURIComponent(did) + \"/approval\", { approved: true });\n if (data.ok) { showToast(__t(\"members.unblockSuccess\"), \"success\"); loadMembers(membersPage); }\n}\n\nasync function removeMember(did, name) {\n var ok = await confirmDialog({ title: __t(\"members.removeTitle\"), message: __t(\"members.removeMsg\", { name: name }), confirmLabel: __t(\"members.remove\"), danger: true });\n if (!ok) return;\n var data = await api(\"DELETE\", \"/members/\" + encodeURIComponent(did));\n if (data.ok) { showToast(__t(\"members.removeSuccess\"), \"success\"); loadMembers(membersPage); }\n}\n\nfunction updateTransferSection(users) {\n var section = document.getElementById(\"ownership-transfer-section\");\n if (__caller.role !== \"owner\") {\n section.classList.add(\"hidden\");\n return;\n }\n section.classList.remove(\"hidden\");\n var select = document.getElementById(\"transfer-target\");\n var html = '<option value=\"\">' + __t(\"members.transferSelect\") + '</option>';\n users.forEach(function(u) {\n if (u.did !== __caller.did && u.approved && u.role !== \"owner\") {\n html += '<option value=\"' + escapeHtml(u.did) + '\">' + escapeHtml(u.fullName || truncateDid(u.did)) + ' (' + u.role + ')</option>';\n }\n });\n select.innerHTML = html;\n}\n\nasync function handleTransferOwnership() {\n var target = document.getElementById(\"transfer-target\").value;\n if (!target) { showToast(__t(\"members.selectMemberFirst\"), \"error\"); return; }\n var targetUser = currentMembersList.find(function(u) { return u.did === target; });\n var name = targetUser ? (targetUser.fullName || truncateDid(target)) : truncateDid(target);\n var ok = await confirmDialog({\n title: __t(\"members.transferTitle\"),\n message: __t(\"members.transferMsg\", { name: name }),\n confirmLabel: __t(\"members.transferConfirm\"),\n danger: true,\n });\n if (!ok) return;\n var data = await api(\"POST\", \"/transfer-ownership\", { targetDid: target });\n if (data.ok) {\n showToast(__t(\"members.transferSuccess\"), \"success\");\n setTimeout(function() { location.reload(); }, 1000);\n }\n}\n\n// Close action menus on outside click\ndocument.addEventListener(\"click\", function() {\n document.querySelectorAll(\".action-menu.open\").forEach(function(m) { m.classList.remove(\"open\"); });\n});\n\nregisterTab(\"members\", function() {\n initInstanceFilter().then(function() {\n // Auto-select instance from URL query (e.g., ?instance=z1xxx)\n var urlInstance = new URLSearchParams(window.location.search).get(\"instance\");\n if (urlInstance) {\n var sel = document.getElementById(\"members-instance-filter\");\n if (sel) sel.value = urlInstance;\n }\n // Restore sort state from URL (sortBy / sortOrder) on tab enter.\n readMembersSortFromURL();\n loadMembers(1);\n });\n});\n";
6
+ export declare const MEMBERS_TAB_SCRIPT = "\nvar membersPage = 1;\nvar membersPageSize = 20;\nvar memberSearchTimeout = null;\nvar currentMembersList = [];\nvar selectedMember = null;\nvar __membersAggregated = false;\n// Sort state: empty string = default (server decides + self-first on client).\n// Three-state click cycle: (none) \u2192 desc \u2192 asc \u2192 (none).\n// Persisted in location.search as sortBy/sortOrder so it survives refresh + share.\nvar membersSortBy = \"\";\nvar membersSortOrder = \"\";\nvar MEMBERS_SORT_VALID = [\"name\", \"role\", \"status\", \"source\", \"joined\"];\n\n/** Populate module sort state from the current URL query string. Idempotent. */\nfunction readMembersSortFromURL() {\n membersSortBy = \"\";\n membersSortOrder = \"\";\n var params = new URLSearchParams(location.search);\n var by = params.get(\"sortBy\");\n var order = params.get(\"sortOrder\");\n if (by && MEMBERS_SORT_VALID.indexOf(by) >= 0) {\n membersSortBy = by;\n membersSortOrder = (order === \"asc\" || order === \"desc\") ? order : \"desc\";\n }\n}\n\n/** Write current sort state back to location.search without reloading. */\nfunction writeMembersSortToURL() {\n var params = new URLSearchParams(location.search);\n if (membersSortBy) {\n params.set(\"sortBy\", membersSortBy);\n params.set(\"sortOrder\", membersSortOrder || \"desc\");\n } else {\n params.delete(\"sortBy\");\n params.delete(\"sortOrder\");\n }\n var qs = params.toString();\n var newUrl = location.pathname + (qs ? \"?\" + qs : \"\") + location.hash;\n history.replaceState(null, \"\", newUrl);\n}\n\n/** Cycle sort state for a column key and reload. */\nfunction toggleSort(key) {\n if (membersSortBy !== key) {\n membersSortBy = key;\n membersSortOrder = \"desc\";\n } else if (membersSortOrder === \"desc\") {\n membersSortOrder = \"asc\";\n } else {\n // Third click \u2014 back to default.\n membersSortBy = \"\";\n membersSortOrder = \"\";\n }\n writeMembersSortToURL();\n loadMembers(1);\n}\n\n/** Render a <th> with click-to-sort behavior. */\nfunction sortableTh(key, label, extraClass) {\n var isActive = membersSortBy === key;\n var arrow;\n if (isActive) {\n arrow = membersSortOrder === \"asc\"\n ? '<span class=\"sort-arrow sort-arrow-active\">\\u2191</span>'\n : '<span class=\"sort-arrow sort-arrow-active\">\\u2193</span>';\n } else {\n arrow = '<span class=\"sort-arrow\">\\u2195</span>';\n }\n var cls = \"sortable\" + (isActive ? \" sort-active\" : \"\") + (extraClass ? \" \" + extraClass : \"\");\n return '<th class=\"' + cls + '\" onclick=\"toggleSort(\\'' + key + '\\')\">' + label + arrow + '</th>';\n}\n\nfunction providerLabel(sp) {\n if (!sp) return \"\\u2014\";\n var key = \"members.providers.\" + sp;\n var val = __t(key);\n return val !== key ? val : sp;\n}\n\nfunction debouncedMemberSearch() {\n clearTimeout(memberSearchTimeout);\n memberSearchTimeout = setTimeout(function() { loadMembers(1); }, 300);\n}\n\n// Load registered instances for the filter dropdown (system admin only)\nvar __instanceNames = {};\nvar __instanceFilterInitialized = false;\nasync function initInstanceFilter() {\n if (__instanceFilterInitialized) return;\n try {\n // Best-effort: this endpoint is system-admin-only. Instance-scoped owners/admins\n // get 403 here, which is expected \u2014 fail silently (no error toast).\n var data = await api(\"GET\", \"/../admin/instances\", undefined, { silent: true });\n if (data.ok && data.instances && data.instances.length > 0) {\n __instanceFilterInitialized = true;\n var sel = document.getElementById(\"members-instance-filter\");\n data.instances.forEach(function(inst) {\n __instanceNames[inst.instanceDid] = inst.appName || inst.instanceDid.substring(0, 16);\n var opt = document.createElement(\"option\");\n opt.value = inst.instanceDid;\n opt.textContent = __instanceNames[inst.instanceDid];\n sel.appendChild(opt);\n });\n sel.style.display = \"\";\n }\n } catch(e) { /* not admin or no instances */ }\n}\n\n// Also support specific instance filter via query param\n// When ?instance=z1xxx, team handler needs to use that instanceDid\n\nasync function loadMembers(page) {\n membersPage = page || 1;\n var instanceFilter = document.getElementById(\"members-instance-filter\").value;\n var qs = \"?page=\" + membersPage + \"&pageSize=\" + membersPageSize;\n // System view (\"\") \u2192 use defaultInstanceDid so we query membership, not global users\n if (instanceFilter === \"\" && __pageData.instanceDid) {\n qs += \"&instance=\" + encodeURIComponent(__pageData.instanceDid);\n } else if (instanceFilter) {\n qs += \"&instance=\" + encodeURIComponent(instanceFilter);\n }\n var search = document.getElementById(\"members-search\").value.trim();\n if (search) qs += \"&search=\" + encodeURIComponent(search);\n var role = document.getElementById(\"members-role-filter\").value;\n if (role) qs += \"&role=\" + encodeURIComponent(role);\n var status = document.getElementById(\"members-status-filter\").value;\n if (status !== \"\") qs += \"&approved=\" + status;\n var source = document.getElementById(\"members-source-filter\").value;\n if (source) qs += \"&sourceProvider=\" + encodeURIComponent(source);\n if (membersSortBy) {\n qs += \"&sortBy=\" + encodeURIComponent(membersSortBy);\n qs += \"&sortOrder=\" + encodeURIComponent(membersSortOrder || \"desc\");\n }\n\n document.getElementById(\"members-table-wrap\").innerHTML = skeletonTable(8, 6);\n document.getElementById(\"members-pagination\").innerHTML = \"\";\n var data = await api(\"GET\", \"/members\" + qs);\n if (!data.ok) {\n document.getElementById(\"members-table-wrap\").innerHTML = '<div class=\"empty-state\"><div class=\"empty-state-icon\"><svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 8v4m0 4h.01\"/></svg></div><div class=\"empty-state-title\">' + __t(\"members.loadFailed\") + '</div></div>';\n return;\n }\n __membersAggregated = !!data.aggregated;\n // Only pin the caller to the top when using the default (server) order.\n // Under explicit sort, honor the user's intent without re-shuffling.\n if (membersSortBy) {\n currentMembersList = data.users || [];\n } else {\n currentMembersList = (data.users || []).slice().sort(function(a, b) {\n if (a.did === __caller.did) return -1;\n if (b.did === __caller.did) return 1;\n return (a.createdAt || 0) - (b.createdAt || 0);\n });\n }\n renderMembersTable(currentMembersList, data.paging);\n updateTransferSection(data.users);\n}\n\nfunction renderMembersTable(users, paging) {\n var wrap = document.getElementById(\"members-table-wrap\");\n if (!users || users.length === 0) {\n wrap.innerHTML = '<div class=\"empty-state\"><div class=\"empty-state-icon\"><svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2\"/><circle cx=\"9\" cy=\"7\" r=\"4\"/></svg></div><div class=\"empty-state-title\">' + __t(\"members.noMembers\") + '</div><div class=\"empty-state-desc\">' + __t(\"members.noMembersHint\") + '</div></div>';\n document.getElementById(\"members-pagination\").innerHTML = \"\";\n return;\n }\n\n var instanceSel = document.getElementById(\"members-instance-filter\");\n var instanceVisible = instanceSel && instanceSel.style.display !== 'none';\n var showInstance = instanceVisible && instanceSel.value === \"all\";\n var html = '<table class=\"table\"><thead><tr>';\n html += sortableTh(\"name\", __t(\"members.col.member\"));\n html += sortableTh(\"role\", __t(\"members.col.role\"));\n if (showInstance) html += '<th>' + __t(\"members.col.instance\") + '</th>';\n html += sortableTh(\"source\", __t(\"members.col.source\"), \"col-hide-sm\");\n html += sortableTh(\"status\", __t(\"members.col.status\"), \"col-hide-sm\");\n html += sortableTh(\"joined\", __t(\"members.col.joined\"), \"col-hide-md\");\n // Registered/Last Login From columns removed \u2014 domain info not meaningful in Worker context\n html += '<th></th></tr></thead><tbody>';\n users.forEach(function(u) {\n var userDid = u.did || u.user_did || '';\n var av = renderAvatar(u.avatar, u.fullName, \"sm\");\n var name = u.fullName ? escapeHtml(u.fullName) : '<span class=\"text-muted\">(unnamed)</span>';\n var did = '<did-address did=\"' + escapeHtml(userDid) + '\" compact></did-address>';\n var roleBadge = '<span class=\"badge badge-' + (u.role || \"guest\") + '\">' + __t(\"common.\" + (u.role || \"guest\")) + '</span>';\n var statusBadge = u.approved !== undefined ? (u.approved ? '<span class=\"badge badge-active\">' + __t(\"common.active\") + '</span>' : '<span class=\"badge badge-blocked\">' + __t(\"common.blocked\") + '</span>') : '<span class=\"text-muted\">\u2014</span>';\n var joined = (u.createdAt || u.joined_at) ? '<span class=\"cell-date\">' + escapeHtml(relativeTime(u.createdAt || u.joined_at)) + '</span>' : '<span class=\"text-muted\">\u2014</span>';\n html += '<tr style=\"cursor:pointer\" onclick=\"openMemberDetail(\\'' + escapeHtml(userDid) + '\\')\">';\n html += '<td><div class=\"member-identity\">' + av + '<div><div class=\"member-name\">' + name + '</div><div class=\"member-email\">' + did + '</div></div></div></td>';\n html += '<td>' + roleBadge + '</td>';\n if (showInstance) {\n if (__membersAggregated && u.instance_count > 1) {\n html += '<td><a href=\"javascript:void(0)\" onclick=\"event.stopPropagation();openMemberDetail(\\'' + escapeHtml(userDid) + '\\')\" style=\"color:var(--blue);font-size:14px;text-decoration:none\">' + u.instance_count + ' ' + __t(\"members.instances\") + '</a></td>';\n } else {\n var instDid = (__membersAggregated && u.instances && u.instances.length === 1) ? u.instances[0].instance_did : (u.instance_did || '');\n var instName = __instanceNames[instDid] || '';\n if (!instName) instName = instDid ? __t(\"members.currentInstance\") : '\u2014';\n html += '<td><div>' + escapeHtml(instName);\n if (instDid) html += '<div style=\"font-size:11px;color:var(--text-secondary);margin-top:2px\" onclick=\"event.stopPropagation()\"><did-address did=\"' + escapeHtml(instDid) + '\" compact></did-address></div>';\n html += '</div></td>';\n }\n }\n var sourceBadge = '<span class=\"text-muted\">' + escapeHtml(providerLabel(u.sourceProvider)) + '</span>';\n html += '<td class=\"col-hide-sm\">' + sourceBadge + '</td>';\n html += '<td class=\"col-hide-sm\">' + statusBadge + '</td>';\n html += '<td class=\"col-hide-md\">' + joined + '</td>';\n // Actions: only for current instance members, not cross-instance\n var canOperate = !instanceVisible || (instanceVisible && instanceSel.value === '');\n // In aggregated mode, only allow actions on users whose instance matches current system\n if (__membersAggregated) canOperate = false;\n html += '<td class=\"action-cell\">' + (canOperate ? renderMemberActions(u) : '') + '</td>';\n html += '</tr>';\n });\n html += '</tbody></table>';\n wrap.innerHTML = html;\n\n renderPagination(\"members-pagination\", paging, loadMembers);\n}\n\nfunction renderMemberActions(u) {\n var isOwner = __caller.role === \"owner\";\n var isAdmin = __caller.role === \"admin\";\n var isSelf = u.did === __caller.did;\n var ROLE_LEVEL = { owner: 3, admin: 2, member: 1, guest: 0 };\n var callerLevel = ROLE_LEVEL[__caller.role] || 0;\n var targetLevel = ROLE_LEVEL[u.role] || 0;\n // Can only act on members with lower privilege\n var canManage = !isSelf && targetLevel < callerLevel;\n var items = [];\n\n // Change role: can manage lower-level members\n if (canManage) {\n items.push('<button class=\"action-menu-item\" onclick=\"event.stopPropagation();openChangeRoleForDid(\\'' + escapeHtml(u.did) + '\\')\">' + __t(\"members.changeRole\") + '</button>');\n }\n if (canManage && u.approved) {\n items.push('<button class=\"action-menu-item\" onclick=\"event.stopPropagation();blockMember(\\'' + escapeHtml(u.did) + '\\',\\'' + escapeHtml(u.fullName || u.did) + '\\')\">' + __t(\"members.block\") + '</button>');\n }\n if (canManage && !u.approved) {\n items.push('<button class=\"action-menu-item\" onclick=\"event.stopPropagation();unblockMember(\\'' + escapeHtml(u.did) + '\\',\\'' + escapeHtml(u.fullName || u.did) + '\\')\">' + __t(\"members.unblock\") + '</button>');\n }\n if (items.length > 0 && canManage) {\n items.push('<div class=\"action-menu-sep\"></div>');\n }\n if (canManage) {\n items.push('<button class=\"action-menu-item danger\" onclick=\"event.stopPropagation();removeMember(\\'' + escapeHtml(u.did) + '\\',\\'' + escapeHtml(u.fullName || u.did) + '\\')\">' + __t(\"members.remove\") + '</button>');\n }\n\n if (items.length === 0) return \"\";\n return '<button class=\"action-trigger\" onclick=\"event.stopPropagation();toggleActionMenu(this)\">\u22EF</button><div class=\"action-menu\">' + items.join(\"\") + '</div>';\n}\n\nfunction toggleActionMenu(trigger) {\n var menu = trigger.nextElementSibling;\n var wasOpen = menu.classList.contains(\"open\");\n // Close all menus first\n document.querySelectorAll(\".action-menu.open\").forEach(function(m) { m.classList.remove(\"open\"); });\n if (!wasOpen) menu.classList.toggle(\"open\");\n}\n\nasync function openMemberDetail(did) {\n var u = currentMembersList.find(function(m) { return m.did === did; });\n if (!u) return;\n selectedMember = u;\n\n // Fetch full member info (with connectedAccounts) from API.\n // Falls back to in-memory list data if the request fails.\n var detail = u;\n try {\n var resp = await api(\"GET\", \"/members/\" + encodeURIComponent(did));\n if (resp && resp.ok && resp.user) {\n // Merge: in-memory list has fields the API may omit (instances, passkeyCount in some paths).\n detail = Object.assign({}, u, resp.user);\n selectedMember = detail;\n }\n } catch (e) {}\n\n var body = document.getElementById(\"member-detail-body\");\n var html = '<div class=\"profile-header\">' + renderAvatar(detail.avatar, detail.fullName) + '<div>';\n html += '<div style=\"font-weight:500;font-size:16px;color:var(--text-white)\">' + escapeHtml(detail.fullName || \"(unnamed)\") + '</div>';\n html += '<did-address did=\"' + escapeHtml(detail.did) + '\" compact></did-address>';\n html += '</div></div>';\n\n html += '<div class=\"settings-card\" style=\"margin-top:16px\">';\n html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailRole\") + '</span><span class=\"settings-value\"><span class=\"badge badge-' + (detail.role || \"guest\") + '\">' + __t(\"common.\" + (detail.role || \"guest\")) + '</span></span></div>';\n html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailStatus\") + '</span><span class=\"settings-value\">' + (detail.approved ? '<span class=\"badge badge-active\">' + __t(\"common.active\") + '</span>' : '<span class=\"badge badge-blocked\">' + __t(\"common.blocked\") + '</span>') + '</span></div>';\n html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailSource\") + '</span><span class=\"settings-value\">' + escapeHtml(providerLabel(detail.sourceProvider)) + '</span></div>';\n if (detail.email) html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailEmail\") + '</span><span class=\"settings-value\">' + escapeHtml(detail.email) + '</span></div>';\n if (detail.inviterName) html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailInvitedBy\") + '</span><span class=\"settings-value\">' + escapeHtml(detail.inviterName) + '</span></div>';\n if (detail.createdAt) html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailJoined\") + '</span><span class=\"settings-value\">' + escapeHtml(absoluteTime(detail.createdAt)) + '</span></div>';\n if (detail.lastLoginAt) html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailLastLogin\") + '</span><span class=\"settings-value\">' + escapeHtml(absoluteTime(detail.lastLoginAt)) + '</span></div>';\n if (detail.sourceDomain) html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailRegisteredFrom\") + '</span><span class=\"settings-value\">' + escapeHtml(detail.sourceDomain) + '</span></div>';\n if (detail.lastLoginDomain) html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailLastLoginFrom\") + '</span><span class=\"settings-value\">' + escapeHtml(detail.lastLoginDomain) + '</span></div>';\n if (detail.passkeyCount !== undefined) html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailPasskeys\") + '</span><span class=\"settings-value\">' + detail.passkeyCount + '</span></div>';\n html += '</div>';\n\n // Connected Accounts (read-only)\n if (detail.connectedAccounts && detail.connectedAccounts.length > 0) {\n html += '<div class=\"settings-card\" style=\"margin-top:16px\">';\n html += '<div class=\"settings-card-header\"><span>' + __t(\"members.detailConnectedAccounts\") + ' (' + detail.connectedAccounts.length + ')</span></div>';\n html += '<div style=\"max-height:240px;overflow-y:auto\">';\n detail.connectedAccounts.forEach(function(a) {\n var label = providerLabel(a.provider);\n var info = '';\n if (a.userInfo && a.userInfo.name) info = a.userInfo.name;\n else if (a.userInfo && a.userInfo.email) info = a.userInfo.email;\n else if (a.id) info = a.id;\n html += '<div class=\"settings-row\" style=\"gap:12px;align-items:flex-start\">';\n html += '<div style=\"flex:1;min-width:0\">';\n html += '<div style=\"font-size:14px;font-weight:500;color:var(--text)\">' + escapeHtml(label);\n if (a.isMain) html += ' <span class=\"badge badge-active\" style=\"font-size:10px;padding:1px 6px;\">' + __t(\"profile.mainAccount\") + '</span>';\n html += '</div>';\n if (info) html += '<div style=\"font-size:12px;color:var(--text-secondary);margin-top:2px\">' + escapeHtml(info) + '</div>';\n if (a.did) html += '<div style=\"font-size:11px;color:var(--text-secondary);margin-top:2px\"><did-address did=\"' + escapeHtml(a.did) + '\" compact></did-address></div>';\n html += '</div>';\n if (a.lastLoginAt) html += '<div style=\"font-size:12px;color:var(--text-secondary);flex-shrink:0\">' + escapeHtml(relativeTime(a.lastLoginAt)) + '</div>';\n html += '</div>';\n });\n html += '</div></div>';\n }\n\n // Instance memberships (aggregated mode)\n if (detail.instances && detail.instances.length > 0) {\n html += '<div class=\"settings-card\" style=\"margin-top:16px\">';\n html += '<div class=\"settings-card-header\"><span>' + __t(\"members.instanceMemberships\") + ' (' + detail.instances.length + ')</span></div>';\n html += '<div style=\"max-height:240px;overflow-y:auto\">';\n detail.instances.forEach(function(inst) {\n var instName = __instanceNames[inst.instance_did] || '';\n var instDid = inst.instance_did || '';\n html += '<div class=\"settings-row\" style=\"gap:12px\">';\n html += '<div style=\"flex:1;min-width:0\"><div style=\"font-size:14px;font-weight:500;color:var(--text)\">' + escapeHtml(instName || __t(\"members.currentInstance\")) + '</div>';\n if (instDid) html += '<div style=\"font-size:11px;color:var(--text-secondary);margin-top:2px\"><did-address did=\"' + escapeHtml(instDid) + '\" compact></did-address></div>';\n html += '</div>';\n html += '<div style=\"display:flex;align-items:center;gap:8px;flex-shrink:0\"><span class=\"badge badge-' + inst.role + '\">' + __t(\"common.\" + inst.role) + '</span>';\n if (inst.joined_at) html += '<span style=\"font-size:12px;color:var(--text-secondary)\">' + escapeHtml(relativeTime(inst.joined_at)) + '</span>';\n html += '</div></div>';\n });\n html += '</div></div>';\n }\n\n body.innerHTML = html;\n\n // Change role: mirror list's canOperate + renderMemberActions logic exactly.\n // Use post-merge detail so role/did reflect the latest API response.\n var changeRoleBtn = document.getElementById(\"member-detail-change-role\");\n var instanceSel = document.getElementById(\"members-instance-filter\");\n var instanceVisible = instanceSel && instanceSel.style.display !== 'none';\n var canOperate = !instanceVisible || (instanceVisible && instanceSel.value === '');\n if (__membersAggregated) canOperate = false;\n var isSelf = detail.did === __caller.did;\n var ROLE_LEVEL = { owner: 3, admin: 2, member: 1, guest: 0 };\n var callerLevel = ROLE_LEVEL[__caller.role] || 0;\n var targetLevel = ROLE_LEVEL[detail.role] || 0;\n var canChangeRole = canOperate && !isSelf && targetLevel < callerLevel;\n if (canChangeRole) {\n changeRoleBtn.classList.remove(\"hidden\");\n } else {\n changeRoleBtn.classList.add(\"hidden\");\n }\n\n showDialog(\"member-detail-dialog\");\n}\n\nfunction openChangeRoleForDid(did) {\n var u = currentMembersList.find(function(m) { return m.did === did; });\n if (!u) return;\n selectedMember = u;\n openChangeRoleDialog();\n}\n\nfunction openChangeRoleDialog() {\n if (!selectedMember) return;\n document.getElementById(\"change-role-title\").textContent = __t(\"members.changeRole\");\n document.getElementById(\"change-role-subtitle\").textContent = __t(\"members.changeRoleFor\", { name: selectedMember.fullName || truncateDid(selectedMember.did) });\n // Show/hide role options based on caller's level (only owner can assign admin)\n var radios = document.querySelectorAll('input[name=\"new-role\"]');\n radios.forEach(function(r) {\n r.checked = r.value === selectedMember.role;\n var label = r.closest('label');\n if (label) {\n if (r.value === 'admin' && __caller.role !== 'owner') label.style.display = 'none';\n else label.style.display = '';\n }\n });\n closeDialog(\"member-detail-dialog\");\n showDialog(\"change-role-dialog\");\n}\n\nasync function saveRoleChange() {\n if (!selectedMember) return;\n var selected = document.querySelector('input[name=\"new-role\"]:checked');\n if (!selected) return;\n var newRole = selected.value;\n var data = await api(\"PUT\", \"/members/\" + encodeURIComponent(selectedMember.did) + \"/role\", { role: newRole });\n if (data.ok) {\n showToast(__t(\"members.roleUpdated\"), \"success\");\n closeDialog(\"change-role-dialog\");\n loadMembers(membersPage);\n }\n}\n\nasync function blockMember(did, name) {\n var ok = await confirmDialog({ title: __t(\"members.blockTitle\"), message: __t(\"members.blockMsg\", { name: name }), confirmLabel: __t(\"members.blockBtn\"), danger: true });\n if (!ok) return;\n var data = await api(\"PUT\", \"/members/\" + encodeURIComponent(did) + \"/approval\", { approved: false });\n if (data.ok) { showToast(__t(\"members.blockSuccess\"), \"success\"); loadMembers(membersPage); }\n}\n\nasync function unblockMember(did, name) {\n var ok = await confirmDialog({ title: __t(\"members.unblockTitle\"), message: __t(\"members.unblockMsg\", { name: name }), confirmLabel: __t(\"members.unblock\") });\n if (!ok) return;\n var data = await api(\"PUT\", \"/members/\" + encodeURIComponent(did) + \"/approval\", { approved: true });\n if (data.ok) { showToast(__t(\"members.unblockSuccess\"), \"success\"); loadMembers(membersPage); }\n}\n\nasync function removeMember(did, name) {\n var ok = await confirmDialog({ title: __t(\"members.removeTitle\"), message: __t(\"members.removeMsg\", { name: name }), confirmLabel: __t(\"members.remove\"), danger: true });\n if (!ok) return;\n var data = await api(\"DELETE\", \"/members/\" + encodeURIComponent(did));\n if (data.ok) { showToast(__t(\"members.removeSuccess\"), \"success\"); loadMembers(membersPage); }\n}\n\nfunction updateTransferSection(users) {\n var section = document.getElementById(\"ownership-transfer-section\");\n if (__caller.role !== \"owner\") {\n section.classList.add(\"hidden\");\n return;\n }\n section.classList.remove(\"hidden\");\n var select = document.getElementById(\"transfer-target\");\n var html = '<option value=\"\">' + __t(\"members.transferSelect\") + '</option>';\n users.forEach(function(u) {\n if (u.did !== __caller.did && u.approved && u.role !== \"owner\") {\n html += '<option value=\"' + escapeHtml(u.did) + '\">' + escapeHtml(u.fullName || truncateDid(u.did)) + ' (' + u.role + ')</option>';\n }\n });\n select.innerHTML = html;\n}\n\nasync function handleTransferOwnership() {\n var target = document.getElementById(\"transfer-target\").value;\n if (!target) { showToast(__t(\"members.selectMemberFirst\"), \"error\"); return; }\n var targetUser = currentMembersList.find(function(u) { return u.did === target; });\n var name = targetUser ? (targetUser.fullName || truncateDid(target)) : truncateDid(target);\n var ok = await confirmDialog({\n title: __t(\"members.transferTitle\"),\n message: __t(\"members.transferMsg\", { name: name }),\n confirmLabel: __t(\"members.transferConfirm\"),\n danger: true,\n });\n if (!ok) return;\n var data = await api(\"POST\", \"/transfer-ownership\", { targetDid: target });\n if (data.ok) {\n showToast(__t(\"members.transferSuccess\"), \"success\");\n setTimeout(function() { location.reload(); }, 1000);\n }\n}\n\n// Close action menus on outside click\ndocument.addEventListener(\"click\", function() {\n document.querySelectorAll(\".action-menu.open\").forEach(function(m) { m.classList.remove(\"open\"); });\n});\n\nregisterTab(\"members\", function() {\n initInstanceFilter().then(function() {\n // Auto-select instance from URL query (e.g., ?instance=z1xxx)\n var urlInstance = new URLSearchParams(window.location.search).get(\"instance\");\n if (urlInstance) {\n var sel = document.getElementById(\"members-instance-filter\");\n if (sel) sel.value = urlInstance;\n }\n // Restore sort state from URL (sortBy / sortOrder) on tab enter.\n readMembersSortFromURL();\n loadMembers(1);\n });\n});\n";
7
7
  export {};
8
8
  //# sourceMappingURL=tab-members.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tab-members.d.ts","sourceRoot":"","sources":["../../../src/pages/admin/tab-members.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,KAAK,GAAG,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC;AAEpE,wBAAgB,cAAc,CAAC,CAAC,EAAE,GAAG,GAAG,MAAM,CAiG7C;AAED,eAAO,MAAM,kBAAkB,ymzBAwd9B,CAAC"}
1
+ {"version":3,"file":"tab-members.d.ts","sourceRoot":"","sources":["../../../src/pages/admin/tab-members.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,KAAK,GAAG,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC;AAEpE,wBAAgB,cAAc,CAAC,CAAC,EAAE,GAAG,GAAG,MAAM,CAiG7C;AAED,eAAO,MAAM,kBAAkB,4yzBA0d9B,CAAC"}
@@ -190,7 +190,9 @@ var __instanceFilterInitialized = false;
190
190
  async function initInstanceFilter() {
191
191
  if (__instanceFilterInitialized) return;
192
192
  try {
193
- var data = await api("GET", "/../admin/instances");
193
+ // Best-effort: this endpoint is system-admin-only. Instance-scoped owners/admins
194
+ // get 403 here, which is expected — fail silently (no error toast).
195
+ var data = await api("GET", "/../admin/instances", undefined, { silent: true });
194
196
  if (data.ok && data.instances && data.instances.length > 0) {
195
197
  __instanceFilterInitialized = true;
196
198
  var sel = document.getElementById("members-instance-filter");
@@ -1 +1 @@
1
- {"version":3,"file":"tab-members.js","sourceRoot":"","sources":["../../../src/pages/admin/tab-members.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,MAAM,UAAU,cAAc,CAAC,CAAM;IACnC,OAAO,UAAU,CAAC;;8BAEU,CAAC,CAAC,eAAe,CAAC;4BACpB,CAAC,CAAC,cAAc,CAAC;;;;;+EAKkC,CAAC,CAAC,gBAAgB,CAAC;;;4BAGtE,CAAC,CAAC,sBAAsB,CAAC;yBAC5B,CAAC,CAAC,yBAAyB,CAAC;;;yBAG5B,CAAC,CAAC,kBAAkB,CAAC;8BAChB,CAAC,CAAC,cAAc,CAAC;8BACjB,CAAC,CAAC,cAAc,CAAC;+BAChB,CAAC,CAAC,eAAe,CAAC;8BACnB,CAAC,CAAC,cAAc,CAAC;;;yBAGtB,CAAC,CAAC,mBAAmB,CAAC;0BACrB,CAAC,CAAC,eAAe,CAAC;0BAClB,CAAC,CAAC,gBAAgB,CAAC;;;yBAGpB,CAAC,CAAC,oBAAoB,CAAC;+BACjB,CAAC,CAAC,0BAA0B,CAAC;gCAC5B,CAAC,CAAC,2BAA2B,CAAC;8BAChC,CAAC,CAAC,yBAAyB,CAAC;+BAC3B,CAAC,CAAC,0BAA0B,CAAC;+BAC7B,CAAC,CAAC,0BAA0B,CAAC;8BAC9B,CAAC,CAAC,yBAAyB,CAAC;gCAC1B,CAAC,CAAC,2BAA2B,CAAC;iCAC7B,CAAC,CAAC,4BAA4B,CAAC;8BAClC,CAAC,CAAC,yBAAyB,CAAC;;;;;;;;;;;;8EAYoB,CAAC,CAAC,kBAAkB,CAAC;;;2EAGxB,CAAC,CAAC,sBAAsB,CAAC;;;sFAGd,CAAC,CAAC,qBAAqB,CAAC;;;;;;;;;;UAUpG,CAAC,CAAC,gBAAgB,CAAC;;;;sFAIyD,CAAC,CAAC,cAAc,CAAC;iGACN,CAAC,CAAC,oBAAoB,CAAC;;;;;;;iCAOvF,CAAC,CAAC,oBAAoB,CAAC;;;;qEAIa,CAAC,CAAC,mBAAmB,CAAC;;;sEAGrB,CAAC,CAAC,oBAAoB,CAAC;;;qEAGxB,CAAC,CAAC,mBAAmB,CAAC;;;;;oFAKP,CAAC,CAAC,eAAe,CAAC;qDACjD,CAAC,CAAC,oBAAoB,CAAC;;;CAG3E,CAAC;AACF,CAAC;AAED,MAAM,CAAC,MAAM,kBAAkB,GAAG,QAAQ,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwd1C,CAAC"}
1
+ {"version":3,"file":"tab-members.js","sourceRoot":"","sources":["../../../src/pages/admin/tab-members.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,MAAM,UAAU,cAAc,CAAC,CAAM;IACnC,OAAO,UAAU,CAAC;;8BAEU,CAAC,CAAC,eAAe,CAAC;4BACpB,CAAC,CAAC,cAAc,CAAC;;;;;+EAKkC,CAAC,CAAC,gBAAgB,CAAC;;;4BAGtE,CAAC,CAAC,sBAAsB,CAAC;yBAC5B,CAAC,CAAC,yBAAyB,CAAC;;;yBAG5B,CAAC,CAAC,kBAAkB,CAAC;8BAChB,CAAC,CAAC,cAAc,CAAC;8BACjB,CAAC,CAAC,cAAc,CAAC;+BAChB,CAAC,CAAC,eAAe,CAAC;8BACnB,CAAC,CAAC,cAAc,CAAC;;;yBAGtB,CAAC,CAAC,mBAAmB,CAAC;0BACrB,CAAC,CAAC,eAAe,CAAC;0BAClB,CAAC,CAAC,gBAAgB,CAAC;;;yBAGpB,CAAC,CAAC,oBAAoB,CAAC;+BACjB,CAAC,CAAC,0BAA0B,CAAC;gCAC5B,CAAC,CAAC,2BAA2B,CAAC;8BAChC,CAAC,CAAC,yBAAyB,CAAC;+BAC3B,CAAC,CAAC,0BAA0B,CAAC;+BAC7B,CAAC,CAAC,0BAA0B,CAAC;8BAC9B,CAAC,CAAC,yBAAyB,CAAC;gCAC1B,CAAC,CAAC,2BAA2B,CAAC;iCAC7B,CAAC,CAAC,4BAA4B,CAAC;8BAClC,CAAC,CAAC,yBAAyB,CAAC;;;;;;;;;;;;8EAYoB,CAAC,CAAC,kBAAkB,CAAC;;;2EAGxB,CAAC,CAAC,sBAAsB,CAAC;;;sFAGd,CAAC,CAAC,qBAAqB,CAAC;;;;;;;;;;UAUpG,CAAC,CAAC,gBAAgB,CAAC;;;;sFAIyD,CAAC,CAAC,cAAc,CAAC;iGACN,CAAC,CAAC,oBAAoB,CAAC;;;;;;;iCAOvF,CAAC,CAAC,oBAAoB,CAAC;;;;qEAIa,CAAC,CAAC,mBAAmB,CAAC;;;sEAGrB,CAAC,CAAC,oBAAoB,CAAC;;;qEAGxB,CAAC,CAAC,mBAAmB,CAAC;;;;;oFAKP,CAAC,CAAC,eAAe,CAAC;qDACjD,CAAC,CAAC,oBAAoB,CAAC;;;CAG3E,CAAC;AACF,CAAC;AAED,MAAM,CAAC,MAAM,kBAAkB,GAAG,QAAQ,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0d1C,CAAC"}
@@ -50,7 +50,7 @@ export function renderLoginPage(config) {
50
50
  <meta charset="utf-8" />
51
51
  <meta name="viewport" content="width=device-width, initial-scale=1" />
52
52
  <title>${title}</title>
53
- <link rel="icon" href="/.well-known/service/blocklet/logo-favicon" type="image/png" />
53
+ <link rel="icon" href="/.well-known/service/blocklet/logo-favicon" />
54
54
  <link rel="stylesheet" href="/.well-known/service/theme/styles.css" />
55
55
  <script src="/.well-known/service/theme/init.js"></script>
56
56
  <link rel="stylesheet" href="${assetUrl("login.css")}" />
@@ -40,9 +40,9 @@ export declare class D1Store {
40
40
  * forwarded to the primary (per Cloudflare D1 Sessions API).
41
41
  *
42
42
  * `assumeMigrated` declares the schema is owned by the deploy-time
43
- * migrations (0001…0007 applied via `wrangler d1 migrations apply`) —
43
+ * migrations (0001…0008 applied via `wrangler d1 migrations apply`) —
44
44
  * {@link migrate} then becomes a no-op instead of replaying the full DDL
45
- * (CREATE batches + 8 always-failing ALTERs + seed INSERTs ≈ several
45
+ * (CREATE batches + 14 always-failing ALTERs + seed INSERTs ≈ several
46
46
  * serial D1 round-trips) on every fresh isolate's first handler build.
47
47
  * ONLY safe on migrated databases: with it set, a missing schema fails
48
48
  * loudly at the first real query instead of being silently created.
@@ -82,6 +82,7 @@ export declare class D1Store {
82
82
  ip?: string;
83
83
  domain?: string;
84
84
  emailVerified?: number;
85
+ createdBy?: string;
85
86
  }): Promise<void>;
86
87
  getUserByDid(did: string): Promise<StoredUser | null>;
87
88
  getUserByEmail(email: string): Promise<StoredUser | null>;
@@ -102,22 +103,24 @@ export declare class D1Store {
102
103
  total: number;
103
104
  }>;
104
105
  getMemberInfo(did: string): Promise<MemberInfo | null>;
105
- updateUserRole(did: string, role: string): Promise<void>;
106
- updateUserApproval(did: string, approved: boolean): Promise<void>;
106
+ updateUserRole(did: string, role: string, actor: string): Promise<void>;
107
+ updateUserApproval(did: string, approved: boolean, actor: string): Promise<void>;
107
108
  updateUserProfile(did: string, fields: {
108
109
  fullName?: string;
109
110
  email?: string;
110
111
  avatar?: string;
111
112
  emailVerified?: number;
112
- }): Promise<void>;
113
+ }, actor: string): Promise<void>;
113
114
  removeUser(did: string): Promise<void>;
114
- setUserInviter(did: string, inviterDid: string): Promise<void>;
115
+ setUserInviter(did: string, inviterDid: string, actor: string): Promise<void>;
115
116
  /**
116
117
  * Atomically transfer ownership: set target to owner, caller to admin.
117
118
  * Uses D1 batch for atomicity.
118
119
  */
119
- transferOwnership(currentOwnerDid: string, newOwnerDid: string): Promise<void>;
120
- createInvitation(input: CreateInvitationInput): Promise<StoredInvitation>;
120
+ transferOwnership(currentOwnerDid: string, newOwnerDid: string, actor: string): Promise<void>;
121
+ createInvitation(input: CreateInvitationInput & {
122
+ actor?: string;
123
+ }): Promise<StoredInvitation>;
121
124
  getInvitation(id: string): Promise<StoredInvitation | null>;
122
125
  getInvitations(opts: {
123
126
  page: number;
@@ -131,7 +134,7 @@ export declare class D1Store {
131
134
  * Atomically increment useCount. Returns false if maxUses already reached.
132
135
  */
133
136
  incrementInvitationUseCount(id: string): Promise<boolean>;
134
- updateInvitationStatus(id: string, status: string): Promise<void>;
137
+ updateInvitationStatus(id: string, status: string, actor: string): Promise<void>;
135
138
  deleteInvitation(id: string): Promise<void>;
136
139
  purgeExpiredInvitations(): Promise<void>;
137
140
  createAuditLog(input: CreateAuditLogInput): Promise<void>;
@@ -157,6 +160,7 @@ export declare class D1Store {
157
160
  extra: string;
158
161
  userInfo: string;
159
162
  ip?: string;
163
+ actor?: string;
160
164
  }): Promise<void>;
161
165
  upsertConnectedAccount(account: {
162
166
  did: string;
@@ -165,6 +169,7 @@ export declare class D1Store {
165
169
  provider: string;
166
170
  id: string;
167
171
  userInfo?: string;
172
+ actor?: string;
168
173
  }): Promise<void>;
169
174
  getConnectedAccountById(credentialId: string): Promise<StoredConnectedAccount | null>;
170
175
  getConnectedAccountByDid(did: string): Promise<StoredConnectedAccount | null>;
@@ -174,7 +179,7 @@ export declare class D1Store {
174
179
  getConnectedAccountsByProviderAndUser(provider: string, userDid: string): Promise<StoredConnectedAccount[]>;
175
180
  getUserByDidOrConnectedAccount(did: string): Promise<StoredUser | null>;
176
181
  decrementPasskeyCount(did: string): Promise<void>;
177
- updateConnectedAccountName(did: string, name: string): Promise<void>;
182
+ updateConnectedAccountName(did: string, name: string, actor: string): Promise<void>;
178
183
  deleteConnectedAccount(did: string): Promise<void>;
179
184
  createAccessKey(input: CreateAccessKeyInput): Promise<StoredAccessKey>;
180
185
  getAccessKeyById(accessKeyId: string): Promise<AccessKeyInfo | null>;
@@ -188,23 +193,23 @@ export declare class D1Store {
188
193
  keys: AccessKeyInfo[];
189
194
  total: number;
190
195
  }>;
191
- updateAccessKey(accessKeyId: string, input: UpdateAccessKeyInput): Promise<AccessKeyInfo | null>;
196
+ updateAccessKey(accessKeyId: string, input: UpdateAccessKeyInput, actor: string): Promise<AccessKeyInfo | null>;
192
197
  deleteAccessKey(accessKeyId: string): Promise<void>;
193
198
  refreshAccessKeyLastUsed(accessKeyId: string): Promise<void>;
194
199
  ensureBuiltinPolicies(): Promise<void>;
195
200
  ensureDefaultRule(): Promise<void>;
196
201
  getAccessPolicies(instanceDid?: string): Promise<AccessPolicyInfo[]>;
197
202
  getAccessPolicy(id: string): Promise<AccessPolicyInfo | null>;
198
- createAccessPolicy(input: CreateAccessPolicyInput): Promise<AccessPolicyInfo>;
199
- updateAccessPolicy(id: string, input: UpdateAccessPolicyInput): Promise<AccessPolicyInfo>;
203
+ createAccessPolicy(input: CreateAccessPolicyInput, actor: string): Promise<AccessPolicyInfo>;
204
+ updateAccessPolicy(id: string, input: UpdateAccessPolicyInput, actor: string): Promise<AccessPolicyInfo>;
200
205
  deleteAccessPolicy(id: string): Promise<void>;
201
206
  getAccessPolicyRuleCount(id: string): Promise<number>;
202
207
  getSecurityRules(instanceDid?: string): Promise<SecurityRuleInfo[]>;
203
208
  getSecurityRule(id: string): Promise<SecurityRuleInfo | null>;
204
- createSecurityRule(input: CreateSecurityRuleInput): Promise<SecurityRuleInfo>;
205
- updateSecurityRule(id: string, input: UpdateSecurityRuleInput): Promise<SecurityRuleInfo>;
209
+ createSecurityRule(input: CreateSecurityRuleInput, actor: string): Promise<SecurityRuleInfo>;
210
+ updateSecurityRule(id: string, input: UpdateSecurityRuleInput, actor: string): Promise<SecurityRuleInfo>;
206
211
  deleteSecurityRule(id: string): Promise<void>;
207
- createMembership(userDid: string, instanceDid: string, role: string, invitedBy?: string): Promise<void>;
212
+ createMembership(userDid: string, instanceDid: string, role: string, invitedBy: string | undefined, actor: string): Promise<void>;
208
213
  getMembership(userDid: string, instanceDid: string): Promise<{
209
214
  user_did: string;
210
215
  instance_did: string;
@@ -258,11 +263,11 @@ export declare class D1Store {
258
263
  createdAt: string | null;
259
264
  joined_at: string | null;
260
265
  }>>;
261
- updateMembershipRole(userDid: string, instanceDid: string, role: string): Promise<void>;
266
+ updateMembershipRole(userDid: string, instanceDid: string, role: string, actor: string): Promise<void>;
262
267
  deleteMembership(userDid: string, instanceDid: string): Promise<void>;
263
268
  deleteMembershipsByInstance(instanceDid: string): Promise<void>;
264
269
  getSetting(instanceDid: string, key: string): Promise<string | null>;
265
- setSetting(instanceDid: string, key: string, value: string): Promise<void>;
270
+ setSetting(instanceDid: string, key: string, value: string, actor: string): Promise<void>;
266
271
  listSettings(instanceDid: string): Promise<Array<{
267
272
  key: string;
268
273
  value: string | null;
@@ -292,7 +297,7 @@ export declare class D1Store {
292
297
  /** Bind a host to an instance. Idempotent; returns a conflict result if the
293
298
  * host already belongs to a different instance. Uses a dedicated INSERT
294
299
  * (not setSetting) so the host PK enforces one-host-one-instance. */
295
- bindInstanceHost(instanceDid: string, host: string): Promise<BindHostResult>;
300
+ bindInstanceHost(instanceDid: string, host: string, actor: string): Promise<BindHostResult>;
296
301
  /** Remove a single host binding. */
297
302
  unbindInstanceHost(host: string): Promise<void>;
298
303
  /** List all hosts bound to an instance (normalized form). */