@kyro-cms/admin 0.1.2 → 0.1.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 (58) hide show
  1. package/package.json +17 -6
  2. package/src/components/Admin.tsx +50 -1
  3. package/src/components/LoginPage.tsx +223 -0
  4. package/src/components/layout/Sidebar.tsx +35 -0
  5. package/src/index.ts +35 -0
  6. package/src/middleware.ts +2 -0
  7. package/src/pages/api/auth/register.ts +133 -0
  8. package/src/styles/main.css +148 -0
  9. package/.astro/content.d.ts +0 -154
  10. package/.astro/settings.json +0 -5
  11. package/.astro/types.d.ts +0 -2
  12. package/astro.config.mjs +0 -28
  13. package/bun.lock +0 -1374
  14. package/dist/client/_astro/AdminLayout.DkDpng53.css +0 -1
  15. package/dist/client/_astro/AutoForm.3eJCmCJp.js +0 -1
  16. package/dist/client/_astro/client.DyczpTbx.js +0 -9
  17. package/dist/client/_astro/index.B02hbnpo.js +0 -1
  18. package/dist/client/fonts/Serotiva-Black.woff2 +0 -0
  19. package/dist/client/fonts/Serotiva-Bold.woff2 +0 -0
  20. package/dist/client/fonts/Serotiva-Medium.woff2 +0 -0
  21. package/dist/client/fonts/Serotiva-Regular.woff2 +0 -0
  22. package/dist/client/fonts/Serotiva-SemiBold.woff2 +0 -0
  23. package/dist/server/chunks/AdminLayout_D-_JeUqC.mjs +0 -26
  24. package/dist/server/chunks/_id__BzI_o0qT.mjs +0 -50
  25. package/dist/server/chunks/_id__Cd-jOuY3.mjs +0 -238
  26. package/dist/server/chunks/_id__DvbD--iR.mjs +0 -992
  27. package/dist/server/chunks/_id__vpVaEo16.mjs +0 -128
  28. package/dist/server/chunks/_virtual_astro_server-island-manifest_CQQ1F5PF.mjs +0 -7
  29. package/dist/server/chunks/_virtual_astro_session-driver_Bk3Q189E.mjs +0 -4
  30. package/dist/server/chunks/astro-component_Dbx3T2Nh.mjs +0 -37
  31. package/dist/server/chunks/audit-logs_DrnUMRvY.mjs +0 -74
  32. package/dist/server/chunks/config_CPXslElD.mjs +0 -4221
  33. package/dist/server/chunks/dataStore_Dl7cA2Qp.mjs +0 -89
  34. package/dist/server/chunks/index_CVqOkerS.mjs +0 -2960
  35. package/dist/server/chunks/index_CX8SQ4BF.mjs +0 -55
  36. package/dist/server/chunks/index_CYofDU51.mjs +0 -58
  37. package/dist/server/chunks/index_DdNRhuaM.mjs +0 -55
  38. package/dist/server/chunks/index_DupPvtIF.mjs +0 -42
  39. package/dist/server/chunks/index_YTS_M-B9.mjs +0 -263
  40. package/dist/server/chunks/index_YeCzuVps.mjs +0 -53
  41. package/dist/server/chunks/login_DLyqMRO8.mjs +0 -93
  42. package/dist/server/chunks/logout_CSbt5wea.mjs +0 -50
  43. package/dist/server/chunks/me_C04jlYhH.mjs +0 -41
  44. package/dist/server/chunks/new_BbQ9b55M.mjs +0 -92
  45. package/dist/server/chunks/node_9bvTewss.mjs +0 -1014
  46. package/dist/server/chunks/noop-entrypoint_BOlrdqWF.mjs +0 -3
  47. package/dist/server/chunks/sequence_9cl7AJy-.mjs +0 -2503
  48. package/dist/server/chunks/server_peBx9VXG.mjs +0 -8117
  49. package/dist/server/chunks/sharp_pmJ7nHES.mjs +0 -142
  50. package/dist/server/chunks/users_Dzddy_YR.mjs +0 -137
  51. package/dist/server/entry.mjs +0 -5
  52. package/dist/server/virtual_astro_middleware.mjs +0 -48
  53. package/public/fonts/Serotiva-Black.woff2 +0 -0
  54. package/public/fonts/Serotiva-Bold.woff2 +0 -0
  55. package/public/fonts/Serotiva-Medium.woff2 +0 -0
  56. package/public/fonts/Serotiva-Regular.woff2 +0 -0
  57. package/public/fonts/Serotiva-SemiBold.woff2 +0 -0
  58. package/tsconfig.json +0 -12
@@ -1,55 +0,0 @@
1
- import { c as createComponent } from './astro-component_Dbx3T2Nh.mjs';
2
- import 'piccolore';
3
- import { Q as renderTemplate, B as maybeRenderHead, a3 as addAttribute } from './sequence_9cl7AJy-.mjs';
4
- import { r as renderComponent } from './server_peBx9VXG.mjs';
5
- import { $ as $$AdminLayout } from './AdminLayout_D-_JeUqC.mjs';
6
-
7
- const $$Index = createComponent(async ($$result, $$props, $$slots) => {
8
- const Astro2 = $$result.createAstro($$props, $$slots);
9
- Astro2.self = $$Index;
10
- let auditLogs = [];
11
- let totalLogs = 0;
12
- try {
13
- const response = await fetch(`${Astro2.url.origin}/api/audit_logs?page=1&limit=50`);
14
- if (response.ok) {
15
- const data = await response.json();
16
- auditLogs = data.docs || [];
17
- totalLogs = data.totalDocs || 0;
18
- }
19
- } catch (error) {
20
- console.error("Failed to fetch audit logs:", error);
21
- }
22
- const actionColors = {
23
- login: "bg-green-50 text-green-600",
24
- logout: "bg-gray-50 text-gray-600",
25
- login_failed: "bg-red-50 text-red-600",
26
- register: "bg-blue-50 text-blue-600",
27
- password_change: "bg-yellow-50 text-yellow-600",
28
- password_reset: "bg-purple-50 text-purple-600",
29
- user_create: "bg-green-50 text-green-600",
30
- user_update: "bg-blue-50 text-blue-600",
31
- user_delete: "bg-red-50 text-red-600",
32
- user_lockout: "bg-orange-50 text-orange-600"
33
- };
34
- return renderTemplate`${renderComponent($$result, "AdminLayout", $$AdminLayout, { "title": "Audit Logs" }, { "default": async ($$result2) => renderTemplate` ${maybeRenderHead()}<div class="flex-1 overflow-y-auto p-8 pr-12 space-y-8"> <!-- Header --> <div class="surface-tile p-6"> <h1 class="text-3xl font-black tracking-tighter text-[#0b1222]">Audit Logs</h1> <p class="text-sm text-[#64748b] mt-1 font-medium">
35
- Security audit trail
36
- <span class="ml-2 text-[#0b1222] font-bold">· ${totalLogs} entries</span> </p> </div> <!-- Logs Table --> <div class="surface-tile overflow-hidden"> ${auditLogs.length === 0 ? renderTemplate`<div class="px-8 py-16 text-center"> <div class="flex flex-col items-center gap-4"> <div class="w-16 h-16 rounded-2xl bg-gray-50 flex items-center justify-center"> <svg class="w-8 h-8 text-[#9ca3af]" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path> </svg> </div> <p class="font-bold text-[#0b1222] text-base">No audit logs yet</p> <p class="text-sm text-[#64748b]">Logs will appear here as users interact with the system.</p> </div> </div>` : renderTemplate`<table class="w-full text-left"> <thead> <tr class="text-[#64748b] font-bold text-[10px] uppercase tracking-[0.3em] border-b border-gray-100"> <th class="px-8 py-6">Action</th> <th class="px-6 py-6">User</th> <th class="px-6 py-6">Resource</th> <th class="px-6 py-6">IP Address</th> <th class="px-6 py-6">Status</th> <th class="px-6 py-6">Timestamp</th> </tr> </thead> <tbody class="divide-y divide-gray-50"> ${auditLogs.map((log) => renderTemplate`<tr class="group hover:bg-gray-50/50 transition-colors"> <td class="px-8 py-4"> <span${addAttribute(`inline-flex items-center px-3 py-1 rounded-lg text-xs font-bold ${actionColors[log.action] || "bg-gray-50 text-gray-600"}`, "class")}> ${log.action.replace(/_/g, " ")} </span> </td> <td class="px-6 py-4"> <div class="text-sm font-bold text-[#0b1222]">${log.userEmail || "—"}</div> ${log.role && renderTemplate`<div class="text-xs text-[#64748b]">${log.role}</div>`} </td> <td class="px-6 py-4 text-sm text-[#64748b]">${log.resource}</td> <td class="px-6 py-4 text-sm text-[#64748b] font-mono">${log.ipAddress || "—"}</td> <td class="px-6 py-4"> ${log.success ? renderTemplate`<span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-lg text-xs font-bold bg-green-50 text-green-600"> <svg class="w-3 h-3" fill="currentColor" viewBox="0 0 20 20"> <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"></path> </svg>
37
- Success
38
- </span>` : renderTemplate`<span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-lg text-xs font-bold bg-red-50 text-red-600"> <svg class="w-3 h-3" fill="currentColor" viewBox="0 0 20 20"> <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"></path> </svg>
39
- Failed
40
- </span>`} </td> <td class="px-6 py-4 text-sm text-[#64748b]"> ${log.timestamp ? new Date(log.timestamp).toLocaleString("en-US", { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" }) : "—"} </td> </tr>`)} </tbody> </table>`} </div> </div> ` })}`;
41
- }, "/Users/macbook/Dev/Web/Astro/kyro-cms/admin/src/pages/audit/index.astro", void 0);
42
-
43
- const $$file = "/Users/macbook/Dev/Web/Astro/kyro-cms/admin/src/pages/audit/index.astro";
44
- const $$url = "/audit";
45
-
46
- const _page = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
47
- __proto__: null,
48
- default: $$Index,
49
- file: $$file,
50
- url: $$url
51
- }, Symbol.toStringTag, { value: 'Module' }));
52
-
53
- const page = () => _page;
54
-
55
- export { page };
@@ -1,58 +0,0 @@
1
- import { c as createComponent } from './astro-component_Dbx3T2Nh.mjs';
2
- import 'piccolore';
3
- import { Q as renderTemplate, B as maybeRenderHead, a3 as addAttribute } from './sequence_9cl7AJy-.mjs';
4
- import { r as renderComponent } from './server_peBx9VXG.mjs';
5
- import { $ as $$AdminLayout } from './AdminLayout_D-_JeUqC.mjs';
6
- import { c as collections } from './config_CPXslElD.mjs';
7
-
8
- const $$Index = createComponent(async ($$result, $$props, $$slots) => {
9
- const Astro2 = $$result.createAstro($$props, $$slots);
10
- Astro2.self = $$Index;
11
- const { collection } = Astro2.params;
12
- if (!collection || !collections[collection]) {
13
- return Astro2.redirect("/");
14
- }
15
- const config = collections[collection];
16
- const visibleFields = config.fields.filter((f) => f.name && !f.admin?.hidden && f.name !== "id");
17
- const displayFields = visibleFields.slice(0, 4);
18
- let docs = [];
19
- let totalDocs = 0;
20
- const page = parseInt(Astro2.url.searchParams.get("page") || "1");
21
- const limit = parseInt(Astro2.url.searchParams.get("limit") || "10");
22
- try {
23
- const response = await fetch(`${Astro2.url.origin}/api/${collection}?page=${page}&limit=${limit}`);
24
- if (response.ok) {
25
- const data = await response.json();
26
- docs = data.docs || [];
27
- totalDocs = data.totalDocs || 0;
28
- }
29
- } catch (error) {
30
- console.error("Failed to fetch documents:", error);
31
- }
32
- const totalPages = Math.ceil(totalDocs / limit);
33
- const collectionDescription = config.admin?.description || `Manage your ${config.label || collection} collection`;
34
- return renderTemplate`${renderComponent($$result, "AdminLayout", $$AdminLayout, { "title": config.label || collection || "Collection" }, { "default": async ($$result2) => renderTemplate` ${maybeRenderHead()}<div class="flex-1 overflow-y-auto p-8 space-y-6"> <!-- Header --> <div class="surface-tile p-8 flex items-center justify-between"> <div> <h1 class="text-3xl font-black tracking-tighter text-[#0b1222]"> ${config.label || collection} </h1> <p class="text-sm text-[#64748b] mt-1 font-medium"> ${collectionDescription} ${totalDocs > 0 && renderTemplate`<span class="ml-2 text-[#0b1222] font-bold">· ${totalDocs} documents</span>`} </p> </div> <a${addAttribute(`/${collection}/new`, "href")} id="btn-create-new" class="flex items-center gap-2 px-6 py-3 bg-[#0b1222] text-white rounded-xl font-bold transition-all hover:bg-[#1a2332] active:scale-95"> <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M12 5v14M5 12h14"></path> </svg>
35
- Create ${config.singularLabel || config.label || collection} </a> </div> <!-- Data Table --> <div class="surface-tile overflow-hidden p-0"> <table class="w-full text-left"> <thead> <tr class="text-[#64748b] font-bold text-[10px] uppercase tracking-[0.3em] border-b border-gray-100"> <th class="px-8 py-6 w-8"> <div class="w-5 h-5 rounded-md border-2 border-gray-200"></div> </th> ${displayFields.map((field) => renderTemplate`<th class="px-6 py-6">${field.label || field.name}</th>`)} ${config.timestamps && renderTemplate`<th class="px-6 py-6">Created</th>`} <th class="px-6 py-6 text-right">Actions</th> </tr> </thead> <tbody class="divide-y divide-gray-50"> ${docs.length === 0 ? renderTemplate`<tr> <td colspan="100%" class="px-8 py-16 text-center"> <div class="flex flex-col items-center gap-4"> <div class="w-16 h-16 rounded-2xl bg-gray-50 flex items-center justify-center"> <svg class="w-8 h-8 text-[#9ca3af]" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"></path> </svg> </div> <div> <p class="font-bold text-[#0b1222] text-base">No documents yet</p> <p class="text-sm text-[#64748b] mt-1">
36
- Get started by creating your first ${(config.singularLabel || config.label || collection || "item").toLowerCase()}.
37
- </p> </div> <a${addAttribute(`/${collection}/new`, "href")} class="mt-2 inline-flex items-center gap-2 px-5 py-2.5 bg-[#0b1222] text-white rounded-lg font-bold text-sm"> <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M12 5v14M5 12h14"></path> </svg>
38
- Create ${config.singularLabel || config.label || collection} </a> </div> </td> </tr>` : docs.map((doc) => renderTemplate`<tr class="group hover:bg-gray-50/50 transition-colors cursor-pointer"${addAttribute(`window.location='/${collection}/${doc.id}'`, "onclick")}> <td class="px-8 py-5"> <div class="w-5 h-5 rounded-md border-2 border-gray-200 group-hover:border-[#0b1222] transition-colors"></div> </td> ${displayFields.map((field, i) => renderTemplate`<td${addAttribute(`px-6 py-5 ${i === 0 ? "font-bold text-[#0b1222]" : "text-[#64748b]"}`, "class")}> ${field.type === "select" && doc[field.name] ? field.options?.find((o) => o.value === doc[field.name])?.label || doc[field.name] : String(doc[field.name] || "—").slice(0, 60)} </td>`)} ${config.timestamps && renderTemplate`<td class="px-6 py-5 text-[#64748b] text-sm"> ${doc.createdAt ? new Date(doc.createdAt).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" }) : "—"} </td>`} <td class="px-6 py-5 text-right"> <div class="flex items-center justify-end gap-1 opacity-0 group-hover:opacity-100 transition-opacity"> <a${addAttribute(`/${collection}/${doc.id}`, "href")} class="inline-flex items-center justify-center w-8 h-8 rounded-md text-[#64748b] hover:bg-gray-100 hover:text-[#0b1222] transition-colors" onclick="event.stopPropagation()"> <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path> </svg> </a> <button${addAttribute(`event.stopPropagation(); if(confirm('Delete this document?')) { fetch('/api/${collection}/${doc.id}', { method: 'DELETE' }).then(() => location.reload()); }`, "onclick")} class="inline-flex items-center justify-center w-8 h-8 rounded-md text-[#64748b] hover:bg-gray-200 hover:text-[#0b1222] transition-colors"> <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path> </svg> </button> </div> </td> </tr>`)} </tbody> </table> </div> <!-- Pagination --> ${totalDocs > limit && renderTemplate`<div class="flex items-center justify-between px-2"> <span class="text-sm text-[#64748b] font-medium">
39
- Showing <span class="text-[#0b1222] font-bold">${(page - 1) * limit + 1}</span> to <span class="text-[#0b1222] font-bold">${Math.min(page * limit, totalDocs)}</span> of <span class="text-[#0b1222] font-bold">${totalDocs}</span> </span> <div class="flex gap-2"> ${page > 1 && renderTemplate`<a${addAttribute(`/${collection}?page=${page - 1}&limit=${limit}`, "href")} class="px-4 py-2 border border-gray-200 rounded-lg text-sm font-bold text-[#0b1222] hover:bg-gray-50 transition-colors">
40
- ← Previous
41
- </a>`} ${page < totalPages && renderTemplate`<a${addAttribute(`/${collection}?page=${page + 1}&limit=${limit}`, "href")} class="px-4 py-2 bg-[#0b1222] text-white rounded-lg text-sm font-bold hover:bg-[#1a2332] transition-colors">
42
- Next →
43
- </a>`} </div> </div>`} </div> ` })}`;
44
- }, "/Users/macbook/Dev/Web/Astro/kyro-cms/admin/src/pages/[collection]/index.astro", void 0);
45
-
46
- const $$file = "/Users/macbook/Dev/Web/Astro/kyro-cms/admin/src/pages/[collection]/index.astro";
47
- const $$url = "/[collection]";
48
-
49
- const _page = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
50
- __proto__: null,
51
- default: $$Index,
52
- file: $$file,
53
- url: $$url
54
- }, Symbol.toStringTag, { value: 'Module' }));
55
-
56
- const page = () => _page;
57
-
58
- export { page };
@@ -1,55 +0,0 @@
1
- import { c as createComponent } from './astro-component_Dbx3T2Nh.mjs';
2
- import 'piccolore';
3
- import { Q as renderTemplate, B as maybeRenderHead, a3 as addAttribute } from './sequence_9cl7AJy-.mjs';
4
- import { r as renderComponent } from './server_peBx9VXG.mjs';
5
- import { $ as $$AdminLayout } from './AdminLayout_D-_JeUqC.mjs';
6
-
7
- const $$Index = createComponent(async ($$result, $$props, $$slots) => {
8
- const Astro2 = $$result.createAstro($$props, $$slots);
9
- Astro2.self = $$Index;
10
- let users = [];
11
- let totalUsers = 0;
12
- try {
13
- const response = await fetch(`${Astro2.url.origin}/api/users?page=1&limit=100`);
14
- if (response.ok) {
15
- const data = await response.json();
16
- users = data.docs || [];
17
- totalUsers = data.totalDocs || 0;
18
- }
19
- } catch (error) {
20
- console.error("Failed to fetch users:", error);
21
- }
22
- const roleColors = {
23
- super_admin: "bg-red-50 text-red-600",
24
- admin: "bg-purple-50 text-purple-600",
25
- editor: "bg-blue-50 text-blue-600",
26
- author: "bg-green-50 text-green-600",
27
- customer: "bg-gray-50 text-gray-600",
28
- guest: "bg-yellow-50 text-yellow-600"
29
- };
30
- return renderTemplate`${renderComponent($$result, "AdminLayout", $$AdminLayout, { "title": "Users" }, { "default": async ($$result2) => renderTemplate` ${maybeRenderHead()}<div class="flex-1 overflow-y-auto p-8 pr-12 space-y-8"> <!-- Header --> <div class="surface-tile p-6 flex items-center justify-between"> <div> <h1 class="text-3xl font-black tracking-tighter text-[#0b1222]">Users</h1> <p class="text-sm text-[#64748b] mt-1 font-medium">
31
- Manage user accounts and permissions
32
- <span class="ml-2 text-[#0b1222] font-bold">· ${totalUsers} users</span> </p> </div> <a href="/users/new" class="flex items-center gap-2 px-6 py-3 bg-[#0b1222] text-white rounded-xl font-bold transition-all hover:bg-[#1a2332] active:scale-95"> <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M12 5v14M5 12h14"></path> </svg>
33
- Add User
34
- </a> </div> <!-- Users Table --> <div class="surface-tile overflow-hidden"> ${users.length === 0 ? renderTemplate`<div class="px-8 py-16 text-center"> <div class="flex flex-col items-center gap-4"> <div class="w-16 h-16 rounded-2xl bg-gray-50 flex items-center justify-center"> <svg class="w-8 h-8 text-[#9ca3af]" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"></path> </svg> </div> <p class="font-bold text-[#0b1222] text-base">No users yet</p> <p class="text-sm text-[#64748b]">Create your first user to get started.</p> <a href="/users/new" class="mt-2 inline-flex items-center gap-2 px-5 py-2.5 bg-[#0b1222] text-white rounded-lg font-bold text-sm"> <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M12 5v14M5 12h14"></path> </svg>
35
- Add User
36
- </a> </div> </div>` : renderTemplate`<table class="w-full text-left"> <thead> <tr class="text-[#64748b] font-bold text-[10px] uppercase tracking-[0.3em] border-b border-gray-100"> <th class="px-8 py-6">Email</th> <th class="px-6 py-6">Role</th> <th class="px-6 py-6">Status</th> <th class="px-6 py-6">Last Login</th> <th class="px-6 py-6">Created</th> <th class="px-6 py-6 text-right">Actions</th> </tr> </thead> <tbody class="divide-y divide-gray-50"> ${users.map((user) => renderTemplate`<tr class="group hover:bg-gray-50/50 transition-colors cursor-pointer"${addAttribute(`window.location='/users/${user.id}'`, "onclick")}> <td class="px-8 py-5"> <div class="flex items-center gap-4"> <div class="w-10 h-10 rounded-full bg-[#0b1222] text-white flex items-center justify-center font-bold text-sm"> ${user.email.charAt(0).toUpperCase()} </div> <div> <div class="font-bold text-[#0b1222]">${user.email}</div> ${user.tenantId && renderTemplate`<div class="text-xs text-[#64748b]">Tenant: ${user.tenantId}</div>`} </div> </div> </td> <td class="px-6 py-5"> <span${addAttribute(`inline-flex items-center px-3 py-1 rounded-lg text-xs font-bold ${roleColors[user.role] || "bg-gray-50 text-gray-600"}`, "class")}> ${user.role} </span> </td> <td class="px-6 py-5"> ${user.locked ? renderTemplate`<span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-lg text-xs font-bold bg-red-50 text-red-600"> <svg class="w-3 h-3" fill="currentColor" viewBox="0 0 20 20"> <path fill-rule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clip-rule="evenodd"></path> </svg>
37
- Locked
38
- </span>` : renderTemplate`<span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-lg text-xs font-bold bg-green-50 text-green-600"> <svg class="w-3 h-3" fill="currentColor" viewBox="0 0 20 20"> <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"></path> </svg>
39
- Active
40
- </span>`} </td> <td class="px-6 py-5 text-sm text-[#64748b]"> ${user.lastLogin ? new Date(user.lastLogin).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" }) : "Never"} </td> <td class="px-6 py-5 text-sm text-[#64748b]"> ${user.createdAt ? new Date(user.createdAt).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" }) : "—"} </td> <td class="px-6 py-5 text-right"> <div class="flex items-center justify-end gap-1 opacity-0 group-hover:opacity-100 transition-opacity"> <a${addAttribute(`/users/${user.id}`, "href")} class="inline-flex items-center justify-center w-8 h-8 rounded-md text-[#64748b] hover:bg-gray-100 hover:text-[#0b1222] transition-colors" onclick="event.stopPropagation()"> <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path> </svg> </a> <button${addAttribute(`event.stopPropagation(); if(confirm('Delete this user?')) { fetch('/api/users/${user.id}', { method: 'DELETE' }).then(() => location.reload()); }`, "onclick")} class="inline-flex items-center justify-center w-8 h-8 rounded-md text-[#64748b] hover:bg-red-50 hover:text-red-600 transition-colors"> <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path> </svg> </button> </div> </td> </tr>`)} </tbody> </table>`} </div> </div> ` })}`;
41
- }, "/Users/macbook/Dev/Web/Astro/kyro-cms/admin/src/pages/users/index.astro", void 0);
42
-
43
- const $$file = "/Users/macbook/Dev/Web/Astro/kyro-cms/admin/src/pages/users/index.astro";
44
- const $$url = "/users";
45
-
46
- const _page = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
47
- __proto__: null,
48
- default: $$Index,
49
- file: $$file,
50
- url: $$url
51
- }, Symbol.toStringTag, { value: 'Module' }));
52
-
53
- const page = () => _page;
54
-
55
- export { page };
@@ -1,42 +0,0 @@
1
- import { c as createComponent } from './astro-component_Dbx3T2Nh.mjs';
2
- import 'piccolore';
3
- import { Q as renderTemplate, B as maybeRenderHead, a3 as addAttribute } from './sequence_9cl7AJy-.mjs';
4
- import { r as renderComponent } from './server_peBx9VXG.mjs';
5
- import { $ as $$AdminLayout } from './AdminLayout_D-_JeUqC.mjs';
6
- import { c as collections } from './config_CPXslElD.mjs';
7
-
8
- const $$Index = createComponent(($$result, $$props, $$slots) => {
9
- const authCollections = ["users", "roles", "audit_logs"];
10
- const authItems = authCollections.map((slug) => ({
11
- slug,
12
- label: collections[slug]?.label || slug,
13
- icon: slug === "users" ? "users" : slug === "roles" ? "shield" : "file-text"
14
- }));
15
- return renderTemplate`${renderComponent($$result, "AdminLayout", $$AdminLayout, { "title": "Dashboard" }, { "default": ($$result2) => renderTemplate` ${maybeRenderHead()}<div class="flex-1 overflow-y-auto p-8 pr-12 space-y-8"> <!-- Header --> <div class="surface-tile p-6 flex items-center justify-between gap-8"> <div class="relative flex-1 max-w-2xl"> <div class="absolute inset-y-0 left-6 flex items-center pointer-events-none text-[#64748b]"> <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path> </svg> </div> <input type="text" placeholder="Search anything..." class="w-full bg-gray-50/50 border border-transparent rounded-2xl py-4 pl-16 pr-8 text-lg font-medium focus:outline-none focus:bg-white focus:border-gray-100 transition-all shadow-inner"> </div> <div class="flex p-1.5 bg-gray-50/50 rounded-2xl"> <button class="flex items-center gap-3 px-8 py-3 bg-[#0b1222] text-white rounded-xl font-bold shadow-lg transition-all active:scale-95"> <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z"></path> </svg> <span>Card</span> </button> <button class="flex items-center gap-3 px-8 py-3 text-[#64748b] rounded-xl font-bold hover:bg-white/50 transition-all"> <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M4 6h16M4 12h16M4 18h16"></path> </svg> <span>List</span> </button> </div> </div> <!-- Auth Management Section --> <div class="surface-tile overflow-hidden"> <div class="flex items-center justify-between p-12 border-b border-gray-50/50"> <div class="flex-1"> <h2 class="text-5xl font-black tracking-tighter text-[#0b1222]">
16
- Authentication
17
- </h2> <p class="text-[#64748b] font-bold mt-4 uppercase tracking-[0.2em] text-sm">
18
- Manage users, roles, and security
19
- </p> </div> <a href="/users/new" class="flex items-center gap-2 px-6 py-3 bg-[#0b1222] text-white rounded-xl font-bold transition-all hover:bg-[#1a2332] active:scale-95"> <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M12 5v14M5 12h14"></path> </svg>
20
- Add User
21
- </a> </div> <div class="grid grid-cols-1 md:grid-cols-3 gap-6 p-6"> ${authItems.map((item) => renderTemplate`<a${addAttribute(`/${item.slug}`, "href")} class="group p-6 bg-gray-50/30 rounded-2xl hover:bg-gray-50/50 transition-all border border-transparent hover:border-gray-100/50"> <div class="flex items-center gap-4 mb-4"> <div${addAttribute(`w-12 h-12 rounded-xl flex items-center justify-center ${item.slug === "users" ? "bg-blue-50 text-blue-500" : item.slug === "roles" ? "bg-purple-50 text-purple-500" : "bg-green-50 text-green-500"}`, "class")}> ${item.icon === "users" && renderTemplate`<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"></path> </svg>`} ${item.icon === "shield" && renderTemplate`<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"></path> </svg>`} ${item.icon === "file-text" && renderTemplate`<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path> </svg>`} </div> <div> <h3 class="text-xl font-black text-[#0b1222] tracking-tighter">${item.label}</h3> <p class="text-sm text-[#64748b] font-medium mt-1">Manage ${item.slug.replace("_", " ")}</p> </div> </div> <div class="flex items-center gap-2 text-[#64748b] font-bold text-sm group-hover:text-[#0b1222] transition-colors"> <span>View all</span> <svg class="w-4 h-4 transform group-hover:translate-x-1 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path> </svg> </div> </a>`)} </div> </div> <!-- Security Quick Actions --> <div class="surface-tile overflow-hidden"> <div class="p-8 border-b border-gray-50/50"> <h2 class="text-3xl font-black tracking-tighter text-[#0b1222]">
22
- Security & Monitoring
23
- </h2> <p class="text-[#64748b] font-bold mt-2 text-sm">
24
- Rate limiting, audit logs, and account lockout settings
25
- </p> </div> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 p-6"> <a href="/audit_logs" class="p-5 bg-gray-50/30 rounded-xl hover:bg-gray-50/50 transition-all border border-transparent hover:border-gray-100/50 group"> <div class="w-10 h-10 rounded-lg bg-orange-50 text-orange-500 flex items-center justify-center mb-3"> <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path> </svg> </div> <h4 class="font-bold text-[#0b1222] mb-1">Audit Logs</h4> <p class="text-xs text-[#64748b]">View last 30 days</p> </a> <a href="/users?locked=true" class="p-5 bg-gray-50/30 rounded-xl hover:bg-gray-50/50 transition-all border border-transparent hover:border-gray-100/50 group"> <div class="w-10 h-10 rounded-lg bg-red-50 text-red-500 flex items-center justify-center mb-3"> <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"></path> </svg> </div> <h4 class="font-bold text-[#0b1222] mb-1">Locked Accounts</h4> <p class="text-xs text-[#64748b]">Manage lockouts</p> </a> <a href="/roles" class="p-5 bg-gray-50/30 rounded-xl hover:bg-gray-50/50 transition-all border border-transparent hover:border-gray-100/50 group"> <div class="w-10 h-10 rounded-lg bg-indigo-50 text-indigo-500 flex items-center justify-center mb-3"> <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"></path> </svg> </div> <h4 class="font-bold text-[#0b1222] mb-1">Permissions</h4> <p class="text-xs text-[#64748b]">RBAC settings</p> </a> <a href="/api/health" target="_blank" class="p-5 bg-gray-50/30 rounded-xl hover:bg-gray-50/50 transition-all border border-transparent hover:border-gray-100/50 group"> <div class="w-10 h-10 rounded-lg bg-green-50 text-green-500 flex items-center justify-center mb-3"> <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path> </svg> </div> <h4 class="font-bold text-[#0b1222] mb-1">API Health</h4> <p class="text-xs text-[#64748b]">System status</p> </a> </div> </div> <!-- Environment Info --> <div class="surface-tile p-6"> <h3 class="text-xl font-black text-[#0b1222] tracking-tighter mb-4">Configuration</h3> <div class="grid grid-cols-2 md:grid-cols-4 gap-4"> <div class="bg-gray-50/50 rounded-xl p-4"> <div class="text-xs text-[#64748b] font-bold uppercase tracking-wider mb-1">Database</div> <div class="text-sm font-bold text-[#0b1222]">PostgreSQL</div> </div> <div class="bg-gray-50/50 rounded-xl p-4"> <div class="text-xs text-[#64748b] font-bold uppercase tracking-wider mb-1">Cache/Sessions</div> <div class="text-sm font-bold text-[#0b1222]">Redis</div> </div> <div class="bg-gray-50/50 rounded-xl p-4"> <div class="text-xs text-[#64748b] font-bold uppercase tracking-wider mb-1">Auth Method</div> <div class="text-sm font-bold text-[#0b1222]">JWT + Redis</div> </div> <div class="bg-gray-50/50 rounded-xl p-4"> <div class="text-xs text-[#64748b] font-bold uppercase tracking-wider mb-1">Email</div> <div class="text-sm font-bold text-[#0b1222]">Nodemailer</div> </div> </div> <p class="text-xs text-[#64748b] mt-4 font-medium">
26
- Configure via <code class="bg-gray-100 px-2 py-0.5 rounded text-[#0b1222]">.env</code> file
27
- </p> </div> <!-- Quick Links --> <div class="grid grid-cols-1 md:grid-cols-3 gap-6"> <a href="/api/health" target="_blank" class="surface-tile group p-6 hover:shadow-2xl transition-all hover:translate-y-[-4px]"> <div class="flex items-center gap-4"> <div class="w-14 h-14 rounded-2xl bg-green-50 flex items-center justify-center text-green-500 group-hover:scale-110 transition-transform"> <svg class="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path> </svg> </div> <div> <h4 class="text-lg font-bold text-[#0b1222]">API Health</h4> <p class="text-sm text-[#64748b]">Check system status</p> </div> </div> </a> <a href="/graphql" target="_blank" class="surface-tile group p-6 hover:shadow-2xl transition-all hover:translate-y-[-4px]"> <div class="flex items-center gap-4"> <div class="w-14 h-14 rounded-2xl bg-pink-50 flex items-center justify-center text-pink-500 group-hover:scale-110 transition-transform"> <svg class="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path> </svg> </div> <div> <h4 class="text-lg font-bold text-[#0b1222]">GraphQL</h4> <p class="text-sm text-[#64748b]">API Playground</p> </div> </div> </a> <a href="/api/collections" target="_blank" class="surface-tile group p-6 hover:shadow-2xl transition-all hover:translate-y-[-4px]"> <div class="flex items-center gap-4"> <div class="w-14 h-14 rounded-2xl bg-blue-50 flex items-center justify-center text-blue-500 group-hover:scale-110 transition-transform"> <svg class="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4"></path> </svg> </div> <div> <h4 class="text-lg font-bold text-[#0b1222]">REST API</h4> <p class="text-sm text-[#64748b]">Collections endpoint</p> </div> </div> </a> </div> </div> ` })}`;
28
- }, "/Users/macbook/Dev/Web/Astro/kyro-cms/admin/src/pages/index.astro", void 0);
29
-
30
- const $$file = "/Users/macbook/Dev/Web/Astro/kyro-cms/admin/src/pages/index.astro";
31
- const $$url = "";
32
-
33
- const _page = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
34
- __proto__: null,
35
- default: $$Index,
36
- file: $$file,
37
- url: $$url
38
- }, Symbol.toStringTag, { value: 'Module' }));
39
-
40
- const page = () => _page;
41
-
42
- export { page };
@@ -1,263 +0,0 @@
1
- import { d as dataStore } from './dataStore_Dl7cA2Qp.mjs';
2
- import { c as collections } from './config_CPXslElD.mjs';
3
-
4
- dataStore.initialize(collections);
5
- const AUTH_COLLECTIONS = ["users", "roles", "audit_logs"];
6
- async function getAuthApi() {
7
- const { RedisAuthAdapter } = await import('./index_CVqOkerS.mjs');
8
- return new RedisAuthAdapter({
9
- url: process.env.REDIS_URL || "redis://localhost:6379",
10
- tls: process.env.REDIS_TLS === "true"
11
- });
12
- }
13
- const GET = async ({ params, url }) => {
14
- const collection = params.collection;
15
- if (AUTH_COLLECTIONS.includes(collection)) {
16
- const page2 = parseInt(url.searchParams.get("page") || "1");
17
- const limit2 = parseInt(url.searchParams.get("limit") || "25");
18
- const search = url.searchParams.get("search") || "";
19
- try {
20
- const adapter = await getAuthApi();
21
- await adapter.connect();
22
- if (collection === "users") {
23
- const pattern = search ? `*${search.toLowerCase()}*` : "*";
24
- let cursor = "0";
25
- const users = [];
26
- const seenIds = /* @__PURE__ */ new Set();
27
- do {
28
- const [nextCursor, keys] = await adapter.redis.scan(
29
- cursor,
30
- "MATCH",
31
- "kyro:auth:users:email:*",
32
- "COUNT",
33
- 100
34
- );
35
- cursor = nextCursor;
36
- for (const key of keys) {
37
- const userId = await adapter.redis.get(key);
38
- if (userId && !seenIds.has(userId)) {
39
- seenIds.add(userId);
40
- const user = await adapter.findUserById(userId);
41
- if (user) {
42
- const { passwordHash, ...safeUser } = user;
43
- users.push(safeUser);
44
- }
45
- }
46
- }
47
- } while (cursor !== "0");
48
- const totalDocs = users.length;
49
- const startIndex = (page2 - 1) * limit2;
50
- const paginatedUsers = users.slice(startIndex, startIndex + limit2);
51
- await adapter.disconnect();
52
- return new Response(
53
- JSON.stringify({
54
- docs: paginatedUsers,
55
- totalDocs,
56
- page: page2,
57
- limit: limit2,
58
- totalPages: Math.ceil(totalDocs / limit2)
59
- }),
60
- { status: 200, headers: { "Content-Type": "application/json" } }
61
- );
62
- }
63
- if (collection === "roles") {
64
- const defaultRoles = [
65
- {
66
- id: "super_admin",
67
- name: "super_admin",
68
- level: 100,
69
- inherits: ["admin"],
70
- description: "Full system access across all tenants"
71
- },
72
- {
73
- id: "admin",
74
- name: "admin",
75
- level: 90,
76
- inherits: ["editor"],
77
- description: "Full tenant access with all content permissions"
78
- },
79
- {
80
- id: "editor",
81
- name: "editor",
82
- level: 70,
83
- inherits: ["author"],
84
- description: "Edit and publish all content"
85
- },
86
- {
87
- id: "author",
88
- name: "author",
89
- level: 50,
90
- inherits: ["customer"],
91
- description: "Create and edit own content"
92
- },
93
- {
94
- id: "customer",
95
- name: "customer",
96
- level: 30,
97
- inherits: [],
98
- description: "Access own data and make purchases"
99
- },
100
- {
101
- id: "guest",
102
- name: "guest",
103
- level: 10,
104
- inherits: [],
105
- description: "Public read-only access"
106
- }
107
- ];
108
- await adapter.disconnect();
109
- return new Response(
110
- JSON.stringify({
111
- docs: defaultRoles,
112
- totalDocs: defaultRoles.length,
113
- page: page2,
114
- limit: limit2,
115
- totalPages: 1
116
- }),
117
- { status: 200, headers: { "Content-Type": "application/json" } }
118
- );
119
- }
120
- if (collection === "audit_logs") {
121
- const logs = await adapter.redis.keys("kyro:auth:audit:*");
122
- const auditLogs = [];
123
- for (const key of logs.slice(0, 100)) {
124
- const logData = await adapter.redis.hgetall(key);
125
- if (logData) {
126
- auditLogs.push({
127
- id: logData.id,
128
- action: logData.action,
129
- userId: logData.userId,
130
- userEmail: logData.userEmail,
131
- role: logData.role,
132
- resource: logData.resource,
133
- ipAddress: logData.ipAddress,
134
- userAgent: logData.userAgent,
135
- success: logData.success === "true",
136
- error: logData.error,
137
- timestamp: logData.timestamp
138
- });
139
- }
140
- }
141
- const sortedLogs = auditLogs.sort(
142
- (a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
143
- );
144
- const totalDocs = sortedLogs.length;
145
- const startIndex = (page2 - 1) * limit2;
146
- const paginatedLogs = sortedLogs.slice(startIndex, startIndex + limit2);
147
- await adapter.disconnect();
148
- return new Response(
149
- JSON.stringify({
150
- docs: paginatedLogs,
151
- totalDocs,
152
- page: page2,
153
- limit: limit2,
154
- totalPages: Math.ceil(totalDocs / limit2) || 1
155
- }),
156
- { status: 200, headers: { "Content-Type": "application/json" } }
157
- );
158
- }
159
- await adapter.disconnect();
160
- } catch (error) {
161
- console.error(`Error fetching ${collection}:`, error);
162
- return new Response(
163
- JSON.stringify({
164
- error: `Failed to fetch ${collection}`,
165
- docs: [],
166
- totalDocs: 0
167
- }),
168
- { status: 200, headers: { "Content-Type": "application/json" } }
169
- );
170
- }
171
- }
172
- const page = parseInt(url.searchParams.get("page") || "1");
173
- const limit = parseInt(url.searchParams.get("limit") || "25");
174
- try {
175
- const result = dataStore.find(collection, { page, limit });
176
- return new Response(JSON.stringify(result), {
177
- status: 200,
178
- headers: { "Content-Type": "application/json" }
179
- });
180
- } catch (error) {
181
- return new Response(
182
- JSON.stringify({ error: "Failed to fetch documents" }),
183
- { status: 500, headers: { "Content-Type": "application/json" } }
184
- );
185
- }
186
- };
187
- const POST = async ({ params, request }) => {
188
- const collection = params.collection;
189
- if (AUTH_COLLECTIONS.includes(collection)) {
190
- try {
191
- const adapter = await getAuthApi();
192
- await adapter.connect();
193
- const body = await request.json();
194
- if (collection === "users") {
195
- const { email, password, role, tenantId } = body;
196
- if (!email || !password) {
197
- await adapter.disconnect();
198
- return new Response(
199
- JSON.stringify({ error: "Email and password are required" }),
200
- { status: 400, headers: { "Content-Type": "application/json" } }
201
- );
202
- }
203
- const existing = await adapter.findUserByEmail(email);
204
- if (existing) {
205
- await adapter.disconnect();
206
- return new Response(
207
- JSON.stringify({ error: "Email already exists" }),
208
- { status: 400, headers: { "Content-Type": "application/json" } }
209
- );
210
- }
211
- const passwordHash = await adapter.hashPassword(password);
212
- const user = await adapter.createUser({
213
- email,
214
- passwordHash,
215
- role: role || "customer",
216
- tenantId
217
- });
218
- await adapter.disconnect();
219
- const { passwordHash: _, ...safeUser } = user;
220
- return new Response(JSON.stringify({ data: safeUser }), {
221
- status: 201,
222
- headers: { "Content-Type": "application/json" }
223
- });
224
- }
225
- await adapter.disconnect();
226
- return new Response(
227
- JSON.stringify({
228
- error: `Collection ${collection} does not support POST`
229
- }),
230
- { status: 405, headers: { "Content-Type": "application/json" } }
231
- );
232
- } catch (error) {
233
- console.error(`Error creating ${collection}:`, error);
234
- return new Response(
235
- JSON.stringify({ error: `Failed to create ${collection}` }),
236
- { status: 500, headers: { "Content-Type": "application/json" } }
237
- );
238
- }
239
- }
240
- try {
241
- const body = await request.json();
242
- const doc = dataStore.create(collection, body);
243
- return new Response(JSON.stringify({ data: doc }), {
244
- status: 201,
245
- headers: { "Content-Type": "application/json" }
246
- });
247
- } catch (error) {
248
- return new Response(
249
- JSON.stringify({ error: "Failed to create document" }),
250
- { status: 500, headers: { "Content-Type": "application/json" } }
251
- );
252
- }
253
- };
254
-
255
- const _page = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
256
- __proto__: null,
257
- GET,
258
- POST
259
- }, Symbol.toStringTag, { value: 'Module' }));
260
-
261
- const page = () => _page;
262
-
263
- export { page };
@@ -1,53 +0,0 @@
1
- import { c as createComponent } from './astro-component_Dbx3T2Nh.mjs';
2
- import 'piccolore';
3
- import { Q as renderTemplate, B as maybeRenderHead, a3 as addAttribute } from './sequence_9cl7AJy-.mjs';
4
- import { r as renderComponent } from './server_peBx9VXG.mjs';
5
- import { $ as $$AdminLayout } from './AdminLayout_D-_JeUqC.mjs';
6
-
7
- const $$Index = createComponent(($$result, $$props, $$slots) => {
8
- const defaultRoles = [
9
- { name: "super_admin", level: 100, inherits: ["admin"], description: "Full system access across all tenants", color: "bg-red-50 text-red-600 border-red-200" },
10
- { name: "admin", level: 90, inherits: ["editor"], description: "Full tenant access with all content permissions", color: "bg-purple-50 text-purple-600 border-purple-200" },
11
- { name: "editor", level: 70, inherits: ["author"], description: "Edit and publish all content", color: "bg-blue-50 text-blue-600 border-blue-200" },
12
- { name: "author", level: 50, inherits: ["customer"], description: "Create and edit own content", color: "bg-green-50 text-green-600 border-green-200" },
13
- { name: "customer", level: 30, inherits: [], description: "Access own data and make purchases", color: "bg-gray-50 text-gray-600 border-gray-200" },
14
- { name: "guest", level: 10, inherits: [], description: "Public read-only access", color: "bg-yellow-50 text-yellow-600 border-yellow-200" }
15
- ];
16
- const permissions = {
17
- super_admin: ["*"],
18
- admin: ["users:*", "roles:*", "content:*", "settings:*", "audit:read"],
19
- editor: ["content:*", "media:*", "comments:*"],
20
- author: ["content:create", "content:read", "content:update:own", "media:*"],
21
- customer: ["content:read", "orders:*", "profile:*"],
22
- guest: ["content:read"]
23
- };
24
- return renderTemplate`${renderComponent($$result, "AdminLayout", $$AdminLayout, { "title": "Roles" }, { "default": ($$result2) => renderTemplate` ${maybeRenderHead()}<div class="flex-1 overflow-y-auto p-8 pr-12 space-y-8"> <!-- Header --> <div class="surface-tile p-6"> <h1 class="text-3xl font-black tracking-tighter text-[#0b1222]">Roles & Permissions</h1> <p class="text-sm text-[#64748b] mt-1 font-medium">
25
- Role-based access control with hierarchical permissions
26
- </p> </div> <!-- Role Hierarchy --> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> ${defaultRoles.map((role) => renderTemplate`<div${addAttribute(`surface-tile p-6 border-2 ${role.color.split(" ").slice(2).join(" ")} rounded-2xl`, "class")}> <div class="flex items-center justify-between mb-4"> <h3 class="text-xl font-black text-[#0b1222] tracking-tighter">${role.name}</h3> <span${addAttribute(`px-3 py-1 rounded-lg text-xs font-bold ${role.color.split(" ").slice(0, 2).join(" ")}`, "class")}>
27
- Level ${role.level} </span> </div> <p class="text-sm text-[#64748b] mb-4">${role.description}</p> ${role.inherits.length > 0 && renderTemplate`<div class="mb-4"> <span class="text-xs font-bold text-[#64748b] uppercase tracking-wider">Inherits:</span> <div class="flex gap-2 mt-2"> ${role.inherits.map((parent) => renderTemplate`<span class="px-2 py-1 bg-gray-100 rounded text-xs font-bold text-[#64748b]">${parent}</span>`)} </div> </div>`} <div> <span class="text-xs font-bold text-[#64748b] uppercase tracking-wider">Permissions:</span> <div class="flex flex-wrap gap-1.5 mt-2"> ${(permissions[role.name] || []).map((perm) => renderTemplate`<span class="px-2 py-0.5 bg-gray-50 rounded text-[10px] font-bold text-[#0b1222]">${perm}</span>`)} </div> </div> </div>`)} </div> <!-- Permission Matrix --> <div class="surface-tile overflow-hidden"> <div class="p-6 border-b border-gray-100"> <h2 class="text-xl font-black text-[#0b1222] tracking-tighter">Permission Matrix</h2> </div> <table class="w-full text-left"> <thead> <tr class="text-[#64748b] font-bold text-[10px] uppercase tracking-[0.3em] border-b border-gray-100"> <th class="px-6 py-4">Resource</th> ${defaultRoles.map((role) => renderTemplate`<th class="px-4 py-4 text-center">${role.name}</th>`)} </tr> </thead> <tbody class="divide-y divide-gray-50"> ${[
28
- { resource: "Users", actions: ["create", "read", "update", "delete"] },
29
- { resource: "Roles", actions: ["create", "read", "update", "delete"] },
30
- { resource: "Content", actions: ["create", "read", "update", "delete", "publish"] },
31
- { resource: "Media", actions: ["create", "read", "update", "delete"] },
32
- { resource: "Settings", actions: ["read", "update"] },
33
- { resource: "Audit Logs", actions: ["read"] }
34
- ].map((row) => renderTemplate`<tr class="hover:bg-gray-50/50"> <td class="px-6 py-4 font-bold text-[#0b1222]">${row.resource}</td> ${defaultRoles.map((role) => {
35
- const hasAll = permissions[role.name]?.includes("*");
36
- const hasResource = permissions[role.name]?.some((p) => p.startsWith(row.resource.toLowerCase() + ":") || p === "*");
37
- return renderTemplate`<td class="px-4 py-4 text-center"> ${hasAll || hasResource ? renderTemplate`<svg class="w-5 h-5 text-green-500 mx-auto" fill="currentColor" viewBox="0 0 20 20"> <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"></path> </svg>` : renderTemplate`<svg class="w-5 h-5 text-gray-200 mx-auto" fill="currentColor" viewBox="0 0 20 20"> <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"></path> </svg>`} </td>`;
38
- })} </tr>`)} </tbody> </table> </div> </div> ` })}`;
39
- }, "/Users/macbook/Dev/Web/Astro/kyro-cms/admin/src/pages/roles/index.astro", void 0);
40
-
41
- const $$file = "/Users/macbook/Dev/Web/Astro/kyro-cms/admin/src/pages/roles/index.astro";
42
- const $$url = "/roles";
43
-
44
- const _page = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
45
- __proto__: null,
46
- default: $$Index,
47
- file: $$file,
48
- url: $$url
49
- }, Symbol.toStringTag, { value: 'Module' }));
50
-
51
- const page = () => _page;
52
-
53
- export { page };