@intranefr/superbackend 1.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. package/.commiat +4 -0
  2. package/.env.example +47 -0
  3. package/README.md +110 -0
  4. package/index.js +94 -0
  5. package/package.json +67 -0
  6. package/public/css/styles.css +139 -0
  7. package/public/js/animations.js +41 -0
  8. package/sdk/error-tracking/browser/package.json +16 -0
  9. package/sdk/error-tracking/browser/src/core.js +270 -0
  10. package/sdk/error-tracking/browser/src/embed.js +18 -0
  11. package/sdk/error-tracking/browser/src/index.js +1 -0
  12. package/server.js +5 -0
  13. package/src/admin/endpointRegistry.js +300 -0
  14. package/src/controllers/admin.controller.js +321 -0
  15. package/src/controllers/adminAssets.controller.js +530 -0
  16. package/src/controllers/adminAssetsStorage.controller.js +260 -0
  17. package/src/controllers/adminEjsVirtual.controller.js +354 -0
  18. package/src/controllers/adminFeatureFlags.controller.js +155 -0
  19. package/src/controllers/adminHeadless.controller.js +1071 -0
  20. package/src/controllers/adminI18n.controller.js +604 -0
  21. package/src/controllers/adminJsonConfigs.controller.js +97 -0
  22. package/src/controllers/adminLlm.controller.js +273 -0
  23. package/src/controllers/adminMigration.controller.js +257 -0
  24. package/src/controllers/adminSeoConfig.controller.js +515 -0
  25. package/src/controllers/adminStats.controller.js +121 -0
  26. package/src/controllers/adminUploadNamespaces.controller.js +208 -0
  27. package/src/controllers/assets.controller.js +248 -0
  28. package/src/controllers/auth.controller.js +93 -0
  29. package/src/controllers/billing.controller.js +223 -0
  30. package/src/controllers/featureFlags.controller.js +35 -0
  31. package/src/controllers/forms.controller.js +217 -0
  32. package/src/controllers/globalSettings.controller.js +252 -0
  33. package/src/controllers/headlessCrud.controller.js +126 -0
  34. package/src/controllers/i18n.controller.js +12 -0
  35. package/src/controllers/invite.controller.js +249 -0
  36. package/src/controllers/jsonConfigs.controller.js +19 -0
  37. package/src/controllers/metrics.controller.js +149 -0
  38. package/src/controllers/notificationAdmin.controller.js +264 -0
  39. package/src/controllers/notifications.controller.js +131 -0
  40. package/src/controllers/org.controller.js +357 -0
  41. package/src/controllers/orgAdmin.controller.js +491 -0
  42. package/src/controllers/stripeAdmin.controller.js +410 -0
  43. package/src/controllers/user.controller.js +361 -0
  44. package/src/controllers/userAdmin.controller.js +277 -0
  45. package/src/controllers/waitingList.controller.js +167 -0
  46. package/src/controllers/webhook.controller.js +200 -0
  47. package/src/middleware/auth.js +66 -0
  48. package/src/middleware/errorCapture.js +170 -0
  49. package/src/middleware/headlessApiTokenAuth.js +57 -0
  50. package/src/middleware/org.js +108 -0
  51. package/src/middleware.js +901 -0
  52. package/src/models/ActionEvent.js +31 -0
  53. package/src/models/ActivityLog.js +41 -0
  54. package/src/models/Asset.js +84 -0
  55. package/src/models/AuditEvent.js +93 -0
  56. package/src/models/EmailLog.js +28 -0
  57. package/src/models/ErrorAggregate.js +72 -0
  58. package/src/models/FormSubmission.js +41 -0
  59. package/src/models/GlobalSetting.js +38 -0
  60. package/src/models/HeadlessApiToken.js +24 -0
  61. package/src/models/HeadlessModelDefinition.js +41 -0
  62. package/src/models/I18nEntry.js +77 -0
  63. package/src/models/I18nLocale.js +33 -0
  64. package/src/models/Invite.js +70 -0
  65. package/src/models/JsonConfig.js +46 -0
  66. package/src/models/Notification.js +60 -0
  67. package/src/models/Organization.js +57 -0
  68. package/src/models/OrganizationMember.js +43 -0
  69. package/src/models/StripeCatalogItem.js +77 -0
  70. package/src/models/StripeWebhookEvent.js +57 -0
  71. package/src/models/User.js +89 -0
  72. package/src/models/VirtualEjsFile.js +60 -0
  73. package/src/models/VirtualEjsFileVersion.js +43 -0
  74. package/src/models/VirtualEjsGroupChange.js +32 -0
  75. package/src/models/WaitingList.js +41 -0
  76. package/src/models/Webhook.js +63 -0
  77. package/src/models/Workflow.js +29 -0
  78. package/src/models/WorkflowExecution.js +12 -0
  79. package/src/routes/admin.routes.js +26 -0
  80. package/src/routes/adminAssets.routes.js +28 -0
  81. package/src/routes/adminAssetsStorage.routes.js +13 -0
  82. package/src/routes/adminAudit.routes.js +196 -0
  83. package/src/routes/adminEjsVirtual.routes.js +17 -0
  84. package/src/routes/adminErrors.routes.js +164 -0
  85. package/src/routes/adminFeatureFlags.routes.js +12 -0
  86. package/src/routes/adminHeadless.routes.js +38 -0
  87. package/src/routes/adminI18n.routes.js +22 -0
  88. package/src/routes/adminJsonConfigs.routes.js +15 -0
  89. package/src/routes/adminLlm.routes.js +12 -0
  90. package/src/routes/adminMigration.routes.js +81 -0
  91. package/src/routes/adminSeoConfig.routes.js +20 -0
  92. package/src/routes/adminUploadNamespaces.routes.js +13 -0
  93. package/src/routes/assets.routes.js +21 -0
  94. package/src/routes/auth.routes.js +12 -0
  95. package/src/routes/billing.routes.js +11 -0
  96. package/src/routes/errorTracking.routes.js +31 -0
  97. package/src/routes/featureFlags.routes.js +9 -0
  98. package/src/routes/forms.routes.js +9 -0
  99. package/src/routes/formsAdmin.routes.js +13 -0
  100. package/src/routes/globalSettings.routes.js +18 -0
  101. package/src/routes/headless.routes.js +15 -0
  102. package/src/routes/i18n.routes.js +8 -0
  103. package/src/routes/invite.routes.js +9 -0
  104. package/src/routes/jsonConfigs.routes.js +8 -0
  105. package/src/routes/log.routes.js +111 -0
  106. package/src/routes/metrics.routes.js +9 -0
  107. package/src/routes/notificationAdmin.routes.js +15 -0
  108. package/src/routes/notifications.routes.js +12 -0
  109. package/src/routes/org.routes.js +31 -0
  110. package/src/routes/orgAdmin.routes.js +20 -0
  111. package/src/routes/publicAssets.routes.js +7 -0
  112. package/src/routes/stripeAdmin.routes.js +20 -0
  113. package/src/routes/user.routes.js +22 -0
  114. package/src/routes/userAdmin.routes.js +15 -0
  115. package/src/routes/waitingList.routes.js +13 -0
  116. package/src/routes/waitingListAdmin.routes.js +9 -0
  117. package/src/routes/webhook.routes.js +32 -0
  118. package/src/routes/workflowWebhook.routes.js +54 -0
  119. package/src/routes/workflows.routes.js +110 -0
  120. package/src/services/assets.service.js +110 -0
  121. package/src/services/audit.service.js +62 -0
  122. package/src/services/auditLogger.js +165 -0
  123. package/src/services/ejsVirtual.service.js +614 -0
  124. package/src/services/email.service.js +351 -0
  125. package/src/services/errorLogger.js +221 -0
  126. package/src/services/featureFlags.service.js +202 -0
  127. package/src/services/forms.service.js +214 -0
  128. package/src/services/globalSettings.service.js +49 -0
  129. package/src/services/headlessApiTokens.service.js +158 -0
  130. package/src/services/headlessCrypto.service.js +31 -0
  131. package/src/services/headlessModels.service.js +356 -0
  132. package/src/services/i18n.service.js +314 -0
  133. package/src/services/i18nInferredKeys.service.js +337 -0
  134. package/src/services/jsonConfigs.service.js +392 -0
  135. package/src/services/llm.service.js +749 -0
  136. package/src/services/migration.service.js +581 -0
  137. package/src/services/migrationAssets/fsLocal.js +58 -0
  138. package/src/services/migrationAssets/index.js +134 -0
  139. package/src/services/migrationAssets/s3.js +75 -0
  140. package/src/services/migrationAssets/sftp.js +92 -0
  141. package/src/services/notification.service.js +212 -0
  142. package/src/services/objectStorage.service.js +514 -0
  143. package/src/services/seoConfig.service.js +402 -0
  144. package/src/services/storage.js +150 -0
  145. package/src/services/stripe.service.js +185 -0
  146. package/src/services/stripeHelper.service.js +264 -0
  147. package/src/services/uploadNamespaces.service.js +326 -0
  148. package/src/services/webhook.service.js +157 -0
  149. package/src/services/workflow.service.js +271 -0
  150. package/src/utils/asyncHandler.js +5 -0
  151. package/src/utils/encryption.js +80 -0
  152. package/src/utils/jwt.js +40 -0
  153. package/src/utils/orgRoles.js +156 -0
  154. package/src/utils/validation.js +26 -0
  155. package/src/utils/webhookRetry.js +93 -0
  156. package/views/admin-assets.ejs +444 -0
  157. package/views/admin-audit.ejs +283 -0
  158. package/views/admin-coolify-deploy.ejs +207 -0
  159. package/views/admin-dashboard-home.ejs +291 -0
  160. package/views/admin-dashboard.ejs +397 -0
  161. package/views/admin-ejs-virtual.ejs +280 -0
  162. package/views/admin-errors.ejs +368 -0
  163. package/views/admin-feature-flags.ejs +390 -0
  164. package/views/admin-forms.ejs +526 -0
  165. package/views/admin-global-settings.ejs +436 -0
  166. package/views/admin-headless.ejs +2020 -0
  167. package/views/admin-i18n-locales.ejs +221 -0
  168. package/views/admin-i18n.ejs +728 -0
  169. package/views/admin-json-configs.ejs +410 -0
  170. package/views/admin-llm.ejs +884 -0
  171. package/views/admin-metrics.ejs +274 -0
  172. package/views/admin-migration.ejs +814 -0
  173. package/views/admin-notifications.ejs +430 -0
  174. package/views/admin-organizations.ejs +984 -0
  175. package/views/admin-seo-config.ejs +673 -0
  176. package/views/admin-stripe-pricing.ejs +558 -0
  177. package/views/admin-test.ejs +342 -0
  178. package/views/admin-users.ejs +452 -0
  179. package/views/admin-waiting-list.ejs +547 -0
  180. package/views/admin-webhooks.ejs +329 -0
  181. package/views/admin-workflows.ejs +310 -0
  182. package/views/partials/admin-assets-script.ejs +2022 -0
  183. package/views/partials/admin-test-sidebar.ejs +14 -0
  184. package/views/partials/dashboard/nav-items.ejs +66 -0
  185. package/views/partials/dashboard/palette.ejs +63 -0
  186. package/views/partials/dashboard/sidebar.ejs +21 -0
  187. package/views/partials/dashboard/tab-bar.ejs +26 -0
  188. package/views/partials/footer.ejs +3 -0
@@ -0,0 +1,444 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Assets - Admin</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <style>
9
+ .toast {
10
+ animation: slideIn 0.3s ease-out;
11
+ }
12
+ @keyframes slideIn {
13
+ from { transform: translateX(100%); opacity: 0; }
14
+ to { transform: translateX(0); opacity: 1; }
15
+ }
16
+ .fade-out {
17
+ animation: fadeOut 0.3s ease-out forwards;
18
+ }
19
+ @keyframes fadeOut {
20
+ from { opacity: 1; }
21
+ to { opacity: 0; }
22
+ }
23
+ </style>
24
+ </head>
25
+ <body class="bg-gray-100">
26
+ <div class="min-h-screen">
27
+ <div class="bg-white shadow">
28
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
29
+ <div class="flex justify-between items-center">
30
+ <div>
31
+ <h1 class="text-2xl font-bold text-gray-900">Assets</h1>
32
+ <p class="text-sm text-gray-600 mt-1">Manage uploaded files and assets</p>
33
+ </div>
34
+ <div class="flex items-center gap-4">
35
+ </div>
36
+ </div>
37
+ </div>
38
+ </div>
39
+
40
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
41
+ <div id="storage-info" class="mb-6 bg-blue-50 border-l-4 border-blue-500 p-4">
42
+ <div class="flex items-center justify-between">
43
+ <h3 class="font-semibold text-blue-900">📦 Storage Info</h3>
44
+ <button id="storage-info-toggle" type="button" class="px-3 py-2 bg-white border rounded hover:bg-blue-100 text-sm" onclick="toggleStorageInfo()">
45
+ Show
46
+ </button>
47
+ </div>
48
+ <div id="storage-info-body" class="hidden mt-2">
49
+ <div class="text-sm text-blue-800" id="storage-info-content">Loading...</div>
50
+ </div>
51
+ </div>
52
+
53
+ <div class="mb-6 bg-white rounded-lg shadow">
54
+ <div class="px-4 py-3 border-b">
55
+ <div class="flex justify-between items-center">
56
+ <h3 class="text-lg font-semibold text-gray-900">Developer helpers</h3>
57
+ <div class="flex items-center gap-2">
58
+ <button id="dev-helpers-toggle" type="button" class="px-3 py-2 bg-gray-200 text-gray-800 rounded hover:bg-gray-300" onclick="toggleDevHelpers()">Show</button>
59
+ <button class="px-3 py-2 bg-gray-200 text-gray-800 rounded hover:bg-gray-300" onclick="copyDevSnippet()">Copy</button>
60
+ </div>
61
+ </div>
62
+ <p class="text-sm text-gray-600 mt-1">Quick snippet to retrieve assets via internal services (and public URL when available).</p>
63
+ </div>
64
+ <div id="dev-helpers-body" class="hidden p-4">
65
+ <pre id="dev-snippet" class="bg-gray-50 rounded p-3 text-sm overflow-auto"></pre>
66
+ </div>
67
+ </div>
68
+
69
+ <div class="mb-6 flex flex-wrap justify-between items-center gap-4">
70
+ <div class="flex flex-wrap gap-2">
71
+ <select id="filter-namespace" class="border rounded px-3 py-2 text-sm">
72
+ <option value="">All namespaces</option>
73
+ </select>
74
+ <input id="filter-tag" class="border rounded px-3 py-2 text-sm" placeholder="Tag (e.g. invoice)">
75
+ <select id="filter-visibility" class="border rounded px-3 py-2 text-sm">
76
+ <option value="">All visibility</option>
77
+ <option value="public">Public</option>
78
+ <option value="private">Private</option>
79
+ </select>
80
+ <select id="filter-content-type" class="border rounded px-3 py-2 text-sm">
81
+ <option value="">All types</option>
82
+ <option value="image">Images</option>
83
+ <option value="video">Videos</option>
84
+ <option value="application/pdf">PDFs</option>
85
+ </select>
86
+ <button onclick="loadAssets()" class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 flex items-center text-sm">
87
+ <svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
88
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
89
+ </svg>
90
+ Refresh
91
+ </button>
92
+ <div class="flex items-center gap-2 ml-2">
93
+ <button id="view-cards" type="button" onclick="setViewMode('cards')" class="px-3 py-2 text-sm rounded border bg-white hover:bg-gray-50">Cards</button>
94
+ <button id="view-list" type="button" onclick="setViewMode('list')" class="px-3 py-2 text-sm rounded border bg-white hover:bg-gray-50">List</button>
95
+ </div>
96
+ </div>
97
+ <div class="flex gap-2">
98
+ <button onclick="showNamespacesModal()" class="bg-gray-800 text-white px-4 py-2 rounded hover:bg-gray-900 flex items-center text-sm">
99
+ Manage Namespaces
100
+ </button>
101
+ <button onclick="showUploadModal()" class="bg-green-500 text-white px-4 py-2 rounded hover:bg-green-600 flex items-center text-sm">
102
+ <svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
103
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"></path>
104
+ </svg>
105
+ Upload Asset
106
+ </button>
107
+ </div>
108
+ </div>
109
+
110
+ <div id="namespaces-summary" class="mt-8">
111
+ <h3 class="text-lg font-semibold text-gray-900 mb-3">Namespaces</h3>
112
+ <div id="namespaces-summary-content" class="text-sm text-gray-600">Loading namespaces...</div>
113
+
114
+ <div id="quick-upload" class="hidden mt-4 bg-white rounded-lg border p-4">
115
+ <div class="flex flex-wrap items-center justify-between gap-3">
116
+ <div>
117
+ <h4 class="text-sm font-semibold text-gray-900">Quick upload</h4>
118
+ <p class="text-xs text-gray-600 mt-1">
119
+ Upload directly into <span class="font-medium" id="quick-upload-namespace-label"></span> using the namespace default visibility.
120
+ </p>
121
+ </div>
122
+ <button id="quick-upload-clipboard-btn" type="button" class="px-3 py-2 bg-gray-800 text-white rounded hover:bg-gray-900 text-sm" onclick="uploadFromClipboard()">
123
+ Upload from clipboard
124
+ </button>
125
+ </div>
126
+
127
+ <div id="quick-upload-dropzone" class="mt-3 border-2 border-dashed rounded-md p-6 bg-gray-50 text-center text-sm text-gray-700">
128
+ Drop files here to upload
129
+ <div class="text-xs text-gray-500 mt-1">or click to pick files</div>
130
+ <input id="quick-upload-file-input" type="file" class="hidden" multiple />
131
+ </div>
132
+
133
+ <div id="quick-upload-status" class="mt-3 text-xs text-gray-600"></div>
134
+ </div>
135
+ </div>
136
+
137
+ <div id="assets-bulk-actions" class="mt-6"></div>
138
+
139
+ <div id="assets-container" class="mt-6 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
140
+ <div class="text-center py-12 col-span-full">
141
+ <p class="text-gray-600">Loading assets...</p>
142
+ </div>
143
+ </div>
144
+
145
+ <div id="pagination" class="mt-6 flex justify-center gap-2"></div>
146
+ </div>
147
+ </div>
148
+
149
+ <div id="bulk-move-namespace-modal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
150
+ <div class="relative top-20 mx-auto p-5 border w-full max-w-lg shadow-lg rounded-md bg-white">
151
+ <div class="flex justify-between items-center mb-4">
152
+ <div>
153
+ <h3 class="text-xl font-bold">Move assets</h3>
154
+ <p class="text-xs text-gray-600 mt-1" id="bulk-move-namespace-subtitle"></p>
155
+ </div>
156
+ <button onclick="closeBulkMoveNamespaceModal()" class="text-gray-400 hover:text-gray-600">
157
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
158
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
159
+ </svg>
160
+ </button>
161
+ </div>
162
+
163
+ <div class="mb-3">
164
+ <label class="block text-sm font-medium mb-1">Target namespace</label>
165
+ <select id="bulk-move-namespace-select" class="w-full border rounded px-3 py-2"></select>
166
+ <p class="text-xs text-gray-500 mt-1">This moves the stored object to a new key under the selected namespace.</p>
167
+ </div>
168
+
169
+ <div class="flex justify-end gap-2">
170
+ <button type="button" onclick="closeBulkMoveNamespaceModal()" class="px-4 py-2 bg-gray-300 text-gray-700 rounded hover:bg-gray-400">Cancel</button>
171
+ <button type="button" id="bulk-move-namespace-confirm" onclick="confirmBulkMoveNamespace()" class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700">Move</button>
172
+ </div>
173
+ </div>
174
+ </div>
175
+
176
+ <div id="bulk-set-tags-modal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
177
+ <div class="relative top-20 mx-auto p-5 border w-full max-w-lg shadow-lg rounded-md bg-white">
178
+ <div class="flex justify-between items-center mb-4">
179
+ <div>
180
+ <h3 class="text-xl font-bold">Set tags</h3>
181
+ <p class="text-xs text-gray-600 mt-1" id="bulk-set-tags-subtitle"></p>
182
+ </div>
183
+ <button onclick="closeBulkSetTagsModal()" class="text-gray-400 hover:text-gray-600">
184
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
185
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
186
+ </svg>
187
+ </button>
188
+ </div>
189
+
190
+ <div class="mb-3">
191
+ <label class="block text-sm font-medium mb-1">Tags (comma separated)</label>
192
+ <input id="bulk-tags-input" class="w-full border rounded px-3 py-2" placeholder="invoice, marketing, screenshot">
193
+ <p class="text-xs text-gray-500 mt-1">Tags are normalized to lowercase and deduplicated. This replaces existing tags for the selected assets.</p>
194
+ </div>
195
+
196
+ <div class="flex justify-end gap-2">
197
+ <button type="button" onclick="closeBulkSetTagsModal()" class="px-4 py-2 bg-gray-300 text-gray-700 rounded hover:bg-gray-400">Cancel</button>
198
+ <button type="button" id="bulk-set-tags-confirm" onclick="confirmBulkSetTags()" class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700">Set tags</button>
199
+ </div>
200
+ </div>
201
+ </div>
202
+
203
+ <div id="tags-modal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
204
+ <div class="relative top-20 mx-auto p-5 border w-full max-w-lg shadow-lg rounded-md bg-white">
205
+ <div class="flex justify-between items-center mb-4">
206
+ <h3 class="text-xl font-bold" id="tags-modal-title">Edit Tags</h3>
207
+ <button onclick="closeTagsModal()" class="text-gray-400 hover:text-gray-600">
208
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
209
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
210
+ </svg>
211
+ </button>
212
+ </div>
213
+
214
+ <div class="mb-3">
215
+ <label class="block text-sm font-medium mb-1">Tags (comma separated)</label>
216
+ <input id="tags-input" class="w-full border rounded px-3 py-2" placeholder="invoice, marketing, screenshot">
217
+ <p class="text-xs text-gray-500 mt-1">Tags are normalized to lowercase and deduplicated.</p>
218
+ </div>
219
+
220
+ <div class="flex justify-end gap-2">
221
+ <button type="button" onclick="closeTagsModal()" class="px-4 py-2 bg-gray-300 text-gray-700 rounded hover:bg-gray-400">Cancel</button>
222
+ <button type="button" id="tags-save-btn" onclick="saveTags()" class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700">Save</button>
223
+ </div>
224
+ </div>
225
+ </div>
226
+
227
+ <div id="toast-container" class="fixed top-4 right-4 space-y-2 z-50"></div>
228
+
229
+ <div id="namespaces-modal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
230
+ <div class="relative top-10 mx-auto p-5 border w-full max-w-4xl shadow-lg rounded-md bg-white">
231
+ <div class="flex justify-between items-center mb-4">
232
+ <h3 class="text-xl font-bold">Upload Namespaces</h3>
233
+ <button onclick="closeNamespacesModal()" class="text-gray-400 hover:text-gray-600">
234
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
235
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
236
+ </svg>
237
+ </button>
238
+ </div>
239
+
240
+ <div class="flex justify-between items-center mb-4">
241
+ <p class="text-sm text-gray-600">Namespaces control max file size, allowed content types, and visibility policy.</p>
242
+ <button onclick="openNamespaceEditor('create')" class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 text-sm">New Namespace</button>
243
+ </div>
244
+
245
+ <div class="overflow-x-auto">
246
+ <table class="min-w-full divide-y divide-gray-200">
247
+ <thead class="bg-gray-50">
248
+ <tr>
249
+ <th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">Key</th>
250
+ <th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">Enabled</th>
251
+ <th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">Max size</th>
252
+ <th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">Allowed types</th>
253
+ <th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">Visibility</th>
254
+ <th class="px-4 py-2"></th>
255
+ </tr>
256
+ </thead>
257
+ <tbody id="namespaces-table" class="bg-white divide-y divide-gray-200"></tbody>
258
+ </table>
259
+ </div>
260
+
261
+ <div class="mt-4 flex justify-end">
262
+ <button onclick="closeNamespacesModal()" class="px-4 py-2 bg-gray-300 text-gray-700 rounded hover:bg-gray-400">Close</button>
263
+ </div>
264
+ </div>
265
+ </div>
266
+
267
+ <div id="namespace-editor-modal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
268
+ <div class="relative top-20 mx-auto p-5 border w-full max-w-lg shadow-lg rounded-md bg-white">
269
+ <div class="flex justify-between items-center mb-4">
270
+ <h3 class="text-xl font-bold" id="namespace-editor-title">Namespace</h3>
271
+ <button onclick="closeNamespaceEditor()" class="text-gray-400 hover:text-gray-600">
272
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
273
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
274
+ </svg>
275
+ </button>
276
+ </div>
277
+
278
+ <form id="namespace-editor-form" onsubmit="saveNamespace(event)">
279
+ <div class="mb-3">
280
+ <label class="block text-sm font-medium mb-1">Key *</label>
281
+ <input id="ns-key" class="w-full border rounded px-3 py-2" placeholder="avatars" required>
282
+ <p class="text-xs text-gray-500 mt-1">Use a stable identifier. The key is used in uploads as `namespace`.</p>
283
+ </div>
284
+
285
+ <div class="mb-3">
286
+ <label class="block text-sm font-medium mb-1">Enabled</label>
287
+ <select id="ns-enabled" class="w-full border rounded px-3 py-2">
288
+ <option value="true">true</option>
289
+ <option value="false">false</option>
290
+ </select>
291
+ </div>
292
+
293
+ <div class="mb-3">
294
+ <label class="block text-sm font-medium mb-1">Max file size (bytes)</label>
295
+ <input id="ns-max" type="number" class="w-full border rounded px-3 py-2" placeholder="2097152">
296
+ <div class="mt-2 flex flex-wrap gap-2">
297
+ <button type="button" class="text-xs text-blue-700 hover:text-blue-900 underline" onclick="setNamespaceMaxSizePresetMb(5)">5mb</button>
298
+ <button type="button" class="text-xs text-blue-700 hover:text-blue-900 underline" onclick="setNamespaceMaxSizePresetMb(10)">10mb</button>
299
+ <button type="button" class="text-xs text-blue-700 hover:text-blue-900 underline" onclick="setNamespaceMaxSizePresetMb(30)">30mb</button>
300
+ <button type="button" class="text-xs text-blue-700 hover:text-blue-900 underline" onclick="setNamespaceMaxSizePresetMb(50)">50mb</button>
301
+ <button type="button" class="text-xs text-blue-700 hover:text-blue-900 underline" onclick="setNamespaceMaxSizePresetMb(100)">100mb</button>
302
+ <button type="button" class="text-xs text-gray-600 hover:text-gray-800 underline" onclick="clearNamespaceMaxSize()">clear</button>
303
+ </div>
304
+ </div>
305
+
306
+ <div class="mb-3">
307
+ <label class="block text-sm font-medium mb-1">Allowed content types (comma separated)</label>
308
+ <input id="ns-types" class="w-full border rounded px-3 py-2" placeholder="image/png,image/jpeg">
309
+ <div class="mt-2 flex flex-wrap gap-2">
310
+ <button type="button" class="text-xs text-blue-700 hover:text-blue-900 underline" onclick="setNamespaceAllowedTypesPreset('media')">media</button>
311
+ <button type="button" class="text-xs text-blue-700 hover:text-blue-900 underline" onclick="setNamespaceAllowedTypesPreset('documents')">documents</button>
312
+ <button type="button" class="text-xs text-gray-600 hover:text-gray-800 underline" onclick="setNamespaceAllowedTypesPreset('allDefault')">default</button>
313
+ <button type="button" class="text-xs text-gray-600 hover:text-gray-800 underline" onclick="clearNamespaceAllowedTypes()">clear</button>
314
+ </div>
315
+ </div>
316
+
317
+ <div class="mb-3">
318
+ <label class="block text-sm font-medium mb-1">Key prefix</label>
319
+ <input id="ns-prefix" class="w-full border rounded px-3 py-2" placeholder="(auto) assets/<namespaceKey>">
320
+ </div>
321
+
322
+ <div class="mb-3">
323
+ <label class="block text-sm font-medium mb-1">Default visibility</label>
324
+ <select id="ns-default-vis" class="w-full border rounded px-3 py-2">
325
+ <option value="private">private</option>
326
+ <option value="public">public</option>
327
+ </select>
328
+ </div>
329
+
330
+ <div class="mb-4">
331
+ <label class="block text-sm font-medium mb-1">Enforce visibility</label>
332
+ <select id="ns-enforce-vis" class="w-full border rounded px-3 py-2">
333
+ <option value="false">false</option>
334
+ <option value="true">true</option>
335
+ </select>
336
+ <p class="text-xs text-gray-500 mt-1">If true, uploaded assets in this namespace always use the default visibility.</p>
337
+ </div>
338
+
339
+ <div class="flex justify-end gap-2">
340
+ <button type="button" onclick="closeNamespaceEditor()" class="px-4 py-2 bg-gray-300 text-gray-700 rounded hover:bg-gray-400">Cancel</button>
341
+ <button type="submit" id="ns-save-btn" class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700">Save</button>
342
+ </div>
343
+ </form>
344
+ </div>
345
+ </div>
346
+
347
+ <div id="upload-modal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
348
+ <div class="relative top-20 mx-auto p-5 border w-full max-w-lg shadow-lg rounded-md bg-white">
349
+ <div class="flex justify-between items-center mb-4">
350
+ <h3 class="text-xl font-bold">Upload Asset</h3>
351
+ <button onclick="closeUploadModal()" class="text-gray-400 hover:text-gray-600">
352
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
353
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
354
+ </svg>
355
+ </button>
356
+ </div>
357
+
358
+ <form id="upload-form" onsubmit="handleUpload(event)">
359
+ <div class="mb-4">
360
+ <label class="block text-sm font-medium mb-2">File *</label>
361
+ <input type="file" id="upload-file" class="w-full border rounded px-3 py-2" required>
362
+ </div>
363
+
364
+ <div class="mb-4">
365
+ <label class="block text-sm font-medium mb-2">Namespace</label>
366
+ <select id="upload-namespace" class="w-full border rounded px-3 py-2"></select>
367
+ <p class="text-xs text-gray-500 mt-1" id="upload-namespace-hint"></p>
368
+ </div>
369
+
370
+ <div class="mb-4">
371
+ <label class="block text-sm font-medium mb-2">Visibility</label>
372
+ <select id="upload-visibility" class="w-full border rounded px-3 py-2">
373
+ <option value="private">Private</option>
374
+ <option value="public">Public</option>
375
+ </select>
376
+ <p class="text-xs text-gray-500 mt-1" id="upload-visibility-hint"></p>
377
+ </div>
378
+
379
+ <div class="flex justify-end space-x-2">
380
+ <button type="button" onclick="closeUploadModal()" class="px-4 py-2 bg-gray-300 text-gray-700 rounded hover:bg-gray-400">Cancel</button>
381
+ <button type="submit" id="upload-btn" class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">Upload</button>
382
+ </div>
383
+ </form>
384
+ </div>
385
+ </div>
386
+
387
+ <div id="preview-modal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
388
+ <div class="relative top-10 mx-auto p-5 border w-full max-w-4xl shadow-lg rounded-md bg-white">
389
+ <div class="flex justify-between items-center mb-4">
390
+ <h3 class="text-xl font-bold" id="preview-title">Preview</h3>
391
+ <button onclick="closePreviewModal()" class="text-gray-400 hover:text-gray-600">
392
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
393
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
394
+ </svg>
395
+ </button>
396
+ </div>
397
+ <div id="preview-content" class="max-h-[70vh] overflow-auto flex justify-center items-center bg-gray-100 rounded p-4"></div>
398
+ </div>
399
+ </div>
400
+
401
+ <div id="replace-modal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
402
+ <div class="relative top-20 mx-auto p-5 border w-full max-w-lg shadow-lg rounded-md bg-white">
403
+ <div class="flex justify-between items-center mb-4">
404
+ <div>
405
+ <h3 class="text-xl font-bold" id="replace-modal-title">Replace asset</h3>
406
+ <p class="text-xs text-gray-600 mt-1" id="replace-modal-subtitle"></p>
407
+ </div>
408
+ <button onclick="closeReplaceModal()" class="text-gray-400 hover:text-gray-600">
409
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
410
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
411
+ </svg>
412
+ </button>
413
+ </div>
414
+
415
+ <div class="flex flex-wrap gap-2 mb-3">
416
+ <button id="replace-clipboard-btn" type="button" class="px-3 py-2 bg-gray-800 text-white rounded hover:bg-gray-900 text-sm" onclick="replaceFromClipboard()">Upload from clipboard</button>
417
+ <button id="replace-pick-btn" type="button" class="px-3 py-2 bg-gray-200 text-gray-800 rounded hover:bg-gray-300 text-sm" onclick="document.getElementById('replace-file-input').click()">Pick file</button>
418
+ <input id="replace-file-input" type="file" class="hidden" />
419
+ </div>
420
+
421
+ <div id="replace-dropzone" class="border-2 border-dashed rounded-md p-6 bg-gray-50 text-center text-sm text-gray-700">
422
+ Drop a file here to replace
423
+ <div class="text-xs text-gray-500 mt-1">(preserves visibility + metadata)</div>
424
+ </div>
425
+
426
+ <div id="replace-status" class="mt-3 text-xs text-gray-600"></div>
427
+
428
+ <div class="mt-4 flex justify-end">
429
+ <button type="button" onclick="closeReplaceModal()" class="px-4 py-2 bg-gray-300 text-gray-700 rounded hover:bg-gray-400">Close</button>
430
+ </div>
431
+ </div>
432
+ </div>
433
+
434
+ <%- include('partials/admin-assets-script') %>
435
+ <script>
436
+ window.addEventListener("keydown", (e) => {
437
+ if ((e.ctrlKey || e.metaKey) && e.key === "k") {
438
+ e.preventDefault();
439
+ window.parent.postMessage({ type: "keydown", ctrlK: true }, "*");
440
+ }
441
+ });
442
+ </script>
443
+ </body>
444
+ </html>