@burdenoff/microfe-admin 2026.625.1 → 2026.712.1

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 (77) hide show
  1. package/dist/admin/AdminRoot.js +2 -2
  2. package/dist/admin/AdminRoot.js.map +1 -1
  3. package/dist/admin/AdminRoutes.js +130 -78
  4. package/dist/admin/AdminRoutes.js.map +1 -1
  5. package/dist/admin/components/AccessDenied.js +2 -2
  6. package/dist/admin/components/AccessDenied.js.map +1 -1
  7. package/dist/admin/components/ActivityFeed.js +4 -4
  8. package/dist/admin/components/ActivityFeed.js.map +1 -1
  9. package/dist/admin/components/Charts.js.map +1 -1
  10. package/dist/admin/components/ConfirmDialog.js +1 -1
  11. package/dist/admin/components/ConfirmDialog.js.map +1 -1
  12. package/dist/admin/components/DataTable.js.map +1 -1
  13. package/dist/admin/components/FormSheet.js +1 -1
  14. package/dist/admin/components/FormSheet.js.map +1 -1
  15. package/dist/admin/components/TenantSsoPanel.js +1 -1
  16. package/dist/admin/components/TenantSsoPanel.js.map +1 -1
  17. package/dist/admin/components/Toast.js +11 -11
  18. package/dist/admin/components/Toast.js.map +1 -1
  19. package/dist/admin/components/forms/SubscriptionForm.js +2 -2
  20. package/dist/admin/components/forms/SubscriptionForm.js.map +1 -1
  21. package/dist/admin/components/forms/TenantForm.js +1 -1
  22. package/dist/admin/components/forms/UserForm.js +1 -1
  23. package/dist/admin/components/ui.js +3 -3
  24. package/dist/admin/components/ui.js.map +1 -1
  25. package/dist/admin/constants/featureFlags.js +12 -0
  26. package/dist/admin/constants/featureFlags.js.map +1 -0
  27. package/dist/admin/constants/index.js +33 -24
  28. package/dist/admin/constants/index.js.map +1 -1
  29. package/dist/admin/pages/AuditLogsPage.js +92 -77
  30. package/dist/admin/pages/AuditLogsPage.js.map +1 -1
  31. package/dist/admin/pages/BillingPage.js +95 -51
  32. package/dist/admin/pages/BillingPage.js.map +1 -1
  33. package/dist/admin/pages/CommunicationsPage.js +97 -92
  34. package/dist/admin/pages/CommunicationsPage.js.map +1 -1
  35. package/dist/admin/pages/ContentPagesPage.js +274 -259
  36. package/dist/admin/pages/ContentPagesPage.js.map +1 -1
  37. package/dist/admin/pages/DashboardPage.js +113 -84
  38. package/dist/admin/pages/DashboardPage.js.map +1 -1
  39. package/dist/admin/pages/FeatureFlagsPage.js +93 -75
  40. package/dist/admin/pages/FeatureFlagsPage.js.map +1 -1
  41. package/dist/admin/pages/OrganizationsListPage.js +105 -87
  42. package/dist/admin/pages/OrganizationsListPage.js.map +1 -1
  43. package/dist/admin/pages/ProductsPage.js +138 -130
  44. package/dist/admin/pages/ProductsPage.js.map +1 -1
  45. package/dist/admin/pages/ReferralsPage.js +70 -52
  46. package/dist/admin/pages/ReferralsPage.js.map +1 -1
  47. package/dist/admin/pages/RolesPermissionsPage.js +136 -118
  48. package/dist/admin/pages/RolesPermissionsPage.js.map +1 -1
  49. package/dist/admin/pages/SREPage.js +34 -32
  50. package/dist/admin/pages/SREPage.js.map +1 -1
  51. package/dist/admin/pages/SandboxSettingsPage.js +360 -322
  52. package/dist/admin/pages/SandboxSettingsPage.js.map +1 -1
  53. package/dist/admin/pages/SettingsPage.js +64 -61
  54. package/dist/admin/pages/SettingsPage.js.map +1 -1
  55. package/dist/admin/pages/SyncFailuresPage.js +120 -118
  56. package/dist/admin/pages/SyncFailuresPage.js.map +1 -1
  57. package/dist/admin/pages/TenantDetailPage.js +286 -231
  58. package/dist/admin/pages/TenantDetailPage.js.map +1 -1
  59. package/dist/admin/pages/TenantFormRoutePage.js +78 -73
  60. package/dist/admin/pages/TenantFormRoutePage.js.map +1 -1
  61. package/dist/admin/pages/TenantsListPage.js +182 -155
  62. package/dist/admin/pages/TenantsListPage.js.map +1 -1
  63. package/dist/admin/pages/UsersListPage.js +128 -113
  64. package/dist/admin/pages/UsersListPage.js.map +1 -1
  65. package/dist/admin/pages/WaitlistPage.js +78 -63
  66. package/dist/admin/pages/WaitlistPage.js.map +1 -1
  67. package/dist/admin/pages/WorkflowsPage.js +107 -102
  68. package/dist/admin/pages/WorkflowsPage.js.map +1 -1
  69. package/dist/admin/pages/WorkspacesListPage.js +61 -49
  70. package/dist/admin/pages/WorkspacesListPage.js.map +1 -1
  71. package/dist/node_modules/@hookform/resolvers/dist/resolvers.js +2 -2
  72. package/dist/node_modules/@hookform/resolvers/dist/resolvers.js.map +1 -1
  73. package/dist/node_modules/@hookform/resolvers/zod/dist/zod.js +35 -26
  74. package/dist/node_modules/@hookform/resolvers/zod/dist/zod.js.map +1 -1
  75. package/dist/node_modules/react-hook-form/dist/index.esm.js +609 -516
  76. package/dist/node_modules/react-hook-form/dist/index.esm.js.map +1 -1
  77. package/package.json +2 -2
@@ -0,0 +1 @@
1
+ {"version":3,"file":"featureFlags.js","names":[],"sources":["../../../src/admin/constants/featureFlags.ts"],"sourcesContent":["export const TENANT_FEATURE_FLAGS = {\n management: 'tenant.management',\n list: 'tenant.list',\n detail: 'tenant.detail',\n create: 'tenant.create',\n edit: 'tenant.edit',\n} as const;\n\nexport type TenantFeatureFlag = (typeof TENANT_FEATURE_FLAGS)[keyof typeof TENANT_FEATURE_FLAGS];\n"],"mappings":";AAAA,IAAa,IAAuB;CAClC,YAAY;CACZ,MAAM;CACN,QAAQ;CACR,QAAQ;CACR,MAAM;CACP"}
@@ -1,90 +1,99 @@
1
1
  //#region src/admin/constants/index.ts
2
2
  var e = {
3
+ success: "bg-status-success-bg text-status-success-text border-status-success-border",
4
+ warning: "bg-status-warning-bg text-status-warning-text border-status-warning-border",
5
+ error: "bg-status-error-bg text-status-error-text border-status-error-border",
6
+ info: "bg-status-info-bg text-status-info-text border-status-info-border",
7
+ neutral: "bg-status-neutral-bg text-status-neutral-text border-status-neutral-border"
8
+ }, t = {
3
9
  active: {
4
10
  label: "Active",
5
- color: "bg-success/10 text-success border-success/20"
11
+ color: e.success
6
12
  },
7
13
  suspended: {
8
14
  label: "Suspended",
9
- color: "bg-status-error-bg/10 text-status-error-text border-status-error-border/20"
15
+ color: e.error
10
16
  },
11
17
  trial: {
12
18
  label: "Trial",
13
- color: "bg-warning/10 text-warning border-warning/20"
19
+ color: e.warning
14
20
  },
15
21
  deleted: {
16
22
  label: "Deleted",
17
- color: "bg-bg-sunken text-text-secondary border-muted"
23
+ color: e.neutral
18
24
  },
19
25
  pending: {
20
26
  label: "Pending",
21
- color: "bg-info/10 text-info border-info/20"
27
+ color: e.info
22
28
  },
23
29
  provisioning: {
24
30
  label: "Provisioning",
25
- color: "bg-amber-100 text-amber-700 border-amber-200"
31
+ color: e.warning
26
32
  }
27
- }, t = {
33
+ }, n = {
28
34
  active: {
29
35
  label: "Active",
30
- color: "bg-success/10 text-success border-success/20"
36
+ color: e.success
31
37
  },
32
38
  inactive: {
33
39
  label: "Inactive",
34
- color: "bg-bg-sunken text-text-secondary border-muted"
40
+ color: e.neutral
35
41
  },
36
42
  blocked: {
37
43
  label: "Blocked",
38
- color: "bg-status-error-bg/10 text-status-error-text border-status-error-border/20"
44
+ color: e.error
39
45
  },
40
46
  pending: {
41
47
  label: "Pending",
42
- color: "bg-warning/10 text-warning border-warning/20"
48
+ color: e.warning
43
49
  }
44
- }, n = {
50
+ };
51
+ e.success, e.neutral, e.error, e.info, e.warning, e.info, e.warning, e.success, e.neutral, e.neutral, e.info, e.warning, e.error;
52
+ var r = {
45
53
  healthy: {
46
54
  label: "Healthy",
47
- color: "bg-success/10 text-success border-success/20"
55
+ color: e.success
48
56
  },
49
57
  degraded: {
50
58
  label: "Degraded",
51
- color: "bg-warning/10 text-warning border-warning/20"
59
+ color: e.warning
52
60
  },
53
61
  down: {
54
62
  label: "Down",
55
- color: "bg-status-error-bg/10 text-status-error-text border-status-error-border/20"
63
+ color: e.error
56
64
  },
57
65
  unknown: {
58
66
  label: "Unknown",
59
- color: "bg-bg-sunken text-text-secondary border-muted"
67
+ color: e.neutral
60
68
  }
61
- }, r = {
69
+ }, i = {
62
70
  pending: {
63
71
  label: "Pending",
64
- color: "bg-bg-sunken text-text-secondary border-muted"
72
+ color: e.neutral
65
73
  },
66
74
  running: {
67
75
  label: "Running",
68
- color: "bg-info/10 text-info border-info/20"
76
+ color: e.info
69
77
  },
70
78
  in_progress: {
71
79
  label: "In Progress",
72
- color: "bg-info/10 text-info border-info/20"
80
+ color: e.info
73
81
  },
74
82
  success: {
75
83
  label: "Success",
76
- color: "bg-success/10 text-success border-success/20"
84
+ color: e.success
77
85
  },
78
86
  failed: {
79
87
  label: "Failed",
80
- color: "bg-status-error-bg/10 text-status-error-text border-status-error-border/20"
88
+ color: e.error
81
89
  },
82
90
  rolled_back: {
83
91
  label: "Rolled Back",
84
- color: "bg-warning/10 text-warning border-warning/20"
92
+ color: e.warning
85
93
  }
86
94
  };
95
+ e.warning, e.info, e.error, e.info;
87
96
  //#endregion
88
- export { r as DEPLOYMENT_STATUS_CONFIG, n as HEALTH_STATUS_CONFIG, e as TENANT_STATUS_CONFIG, t as USER_STATUS_CONFIG };
97
+ export { i as DEPLOYMENT_STATUS_CONFIG, r as HEALTH_STATUS_CONFIG, t as TENANT_STATUS_CONFIG, n as USER_STATUS_CONFIG };
89
98
 
90
99
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/admin/constants/index.ts"],"sourcesContent":["import type {\n TenantStatus,\n UserStatus,\n SubscriptionStatus,\n TicketStatus,\n TicketPriority,\n HealthStatus,\n DeploymentStatus,\n CloudProvider,\n} from '../types';\n\n/**\n * Tenant status labels and colors\n */\nexport const TENANT_STATUS_CONFIG: Record<TenantStatus, { label: string; color: string }> = {\n active: { label: 'Active', color: 'bg-success/10 text-success border-success/20' },\n suspended: {\n label: 'Suspended',\n color: 'bg-status-error-bg/10 text-status-error-text border-status-error-border/20',\n },\n trial: { label: 'Trial', color: 'bg-warning/10 text-warning border-warning/20' },\n deleted: { label: 'Deleted', color: 'bg-bg-sunken text-text-secondary border-muted' },\n pending: { label: 'Pending', color: 'bg-info/10 text-info border-info/20' },\n provisioning: { label: 'Provisioning', color: 'bg-amber-100 text-amber-700 border-amber-200' },\n};\n\n/**\n * User status labels and colors\n */\nexport const USER_STATUS_CONFIG: Record<UserStatus, { label: string; color: string }> = {\n active: { label: 'Active', color: 'bg-success/10 text-success border-success/20' },\n inactive: { label: 'Inactive', color: 'bg-bg-sunken text-text-secondary border-muted' },\n blocked: { label: 'Blocked', color: 'bg-status-error-bg/10 text-status-error-text border-status-error-border/20' },\n pending: { label: 'Pending', color: 'bg-warning/10 text-warning border-warning/20' },\n};\n\n/**\n * Subscription status labels and colors\n */\nexport const SUBSCRIPTION_STATUS_CONFIG: Record<\n SubscriptionStatus,\n { label: string; color: string }\n> = {\n active: { label: 'Active', color: 'bg-success/10 text-success border-success/20' },\n canceled: { label: 'Canceled', color: 'bg-bg-sunken text-text-secondary border-muted' },\n past_due: {\n label: 'Past Due',\n color: 'bg-status-error-bg/10 text-status-error-text border-status-error-border/20',\n },\n trialing: { label: 'Trialing', color: 'bg-info/10 text-info border-info/20' },\n paused: { label: 'Paused', color: 'bg-warning/10 text-warning border-warning/20' },\n};\n\n/**\n * Ticket status labels and colors\n */\nexport const TICKET_STATUS_CONFIG: Record<TicketStatus, { label: string; color: string }> = {\n open: { label: 'Open', color: 'bg-info/10 text-info border-info/20' },\n in_progress: { label: 'In Progress', color: 'bg-warning/10 text-warning border-warning/20' },\n resolved: { label: 'Resolved', color: 'bg-success/10 text-success border-success/20' },\n closed: { label: 'Closed', color: 'bg-bg-sunken text-text-secondary border-muted' },\n};\n\n/**\n * Ticket priority labels and colors\n */\nexport const TICKET_PRIORITY_CONFIG: Record<TicketPriority, { label: string; color: string }> = {\n low: { label: 'Low', color: 'bg-bg-sunken text-text-secondary border-muted' },\n medium: { label: 'Medium', color: 'bg-info/10 text-info border-info/20' },\n high: { label: 'High', color: 'bg-warning/10 text-warning border-warning/20' },\n critical: {\n label: 'Critical',\n color: 'bg-status-error-bg/10 text-status-error-text border-status-error-border/20',\n },\n};\n\n/**\n * Health status labels and colors\n */\nexport const HEALTH_STATUS_CONFIG: Record<HealthStatus, { label: string; color: string }> = {\n healthy: { label: 'Healthy', color: 'bg-success/10 text-success border-success/20' },\n degraded: { label: 'Degraded', color: 'bg-warning/10 text-warning border-warning/20' },\n down: { label: 'Down', color: 'bg-status-error-bg/10 text-status-error-text border-status-error-border/20' },\n unknown: { label: 'Unknown', color: 'bg-bg-sunken text-text-secondary border-muted' },\n};\n\n/**\n * Deployment status labels and colors\n */\nexport const DEPLOYMENT_STATUS_CONFIG: Record<DeploymentStatus, { label: string; color: string }> =\n {\n pending: { label: 'Pending', color: 'bg-bg-sunken text-text-secondary border-muted' },\n running: { label: 'Running', color: 'bg-info/10 text-info border-info/20' },\n in_progress: { label: 'In Progress', color: 'bg-info/10 text-info border-info/20' },\n success: { label: 'Success', color: 'bg-success/10 text-success border-success/20' },\n failed: { label: 'Failed', color: 'bg-status-error-bg/10 text-status-error-text border-status-error-border/20' },\n rolled_back: { label: 'Rolled Back', color: 'bg-warning/10 text-warning border-warning/20' },\n };\n\n/**\n * Cloud provider labels and colors\n */\nexport const CLOUD_PROVIDER_CONFIG: Record<\n CloudProvider,\n { label: string; color: string; icon: string }\n> = {\n aws: {\n label: 'AWS',\n color: 'bg-orange-500/10 text-orange-500 border-orange-500/20',\n icon: 'aws',\n },\n azure: {\n label: 'Azure',\n color: 'bg-blue-500/10 text-blue-500 border-blue-500/20',\n icon: 'azure',\n },\n gcp: { label: 'GCP', color: 'bg-red-500/10 text-red-500 border-red-500/20', icon: 'gcp' },\n digitalocean: {\n label: 'DigitalOcean',\n color: 'bg-cyan-500/10 text-cyan-500 border-cyan-500/20',\n icon: 'do',\n },\n};\n\n/**\n * Chart colors using semantic tokens\n */\nexport const CHART_COLORS = {\n primary: 'hsl(var(--chart-1))',\n secondary: 'hsl(var(--chart-2))',\n tertiary: 'hsl(var(--chart-3))',\n quaternary: 'hsl(var(--chart-4))',\n quinary: 'hsl(var(--chart-5))',\n};\n\n/**\n * Default pagination\n */\nexport const DEFAULT_PAGE_SIZE = 20;\nexport const PAGE_SIZE_OPTIONS = [10, 20, 50, 100];\n"],"mappings":";AAcA,IAAa,IAA+E;CAC1F,QAAQ;EAAE,OAAO;EAAU,OAAO;EAAgD;CAClF,WAAW;EACT,OAAO;EACP,OAAO;EACR;CACD,OAAO;EAAE,OAAO;EAAS,OAAO;EAAgD;CAChF,SAAS;EAAE,OAAO;EAAW,OAAO;EAAiD;CACrF,SAAS;EAAE,OAAO;EAAW,OAAO;EAAuC;CAC3E,cAAc;EAAE,OAAO;EAAgB,OAAO;EAAgD;CAC/F,EAKY,IAA2E;CACtF,QAAQ;EAAE,OAAO;EAAU,OAAO;EAAgD;CAClF,UAAU;EAAE,OAAO;EAAY,OAAO;EAAiD;CACvF,SAAS;EAAE,OAAO;EAAW,OAAO;EAA8E;CAClH,SAAS;EAAE,OAAO;EAAW,OAAO;EAAgD;CACrF,EA6CY,IAA+E;CAC1F,SAAS;EAAE,OAAO;EAAW,OAAO;EAAgD;CACpF,UAAU;EAAE,OAAO;EAAY,OAAO;EAAgD;CACtF,MAAM;EAAE,OAAO;EAAQ,OAAO;EAA8E;CAC5G,SAAS;EAAE,OAAO;EAAW,OAAO;EAAiD;CACtF,EAKY,IACX;CACE,SAAS;EAAE,OAAO;EAAW,OAAO;EAAiD;CACrF,SAAS;EAAE,OAAO;EAAW,OAAO;EAAuC;CAC3E,aAAa;EAAE,OAAO;EAAe,OAAO;EAAuC;CACnF,SAAS;EAAE,OAAO;EAAW,OAAO;EAAgD;CACpF,QAAQ;EAAE,OAAO;EAAU,OAAO;EAA8E;CAChH,aAAa;EAAE,OAAO;EAAe,OAAO;EAAgD;CAC7F"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/admin/constants/index.ts"],"sourcesContent":["import type {\n TenantStatus,\n UserStatus,\n SubscriptionStatus,\n TicketStatus,\n TicketPriority,\n HealthStatus,\n DeploymentStatus,\n CloudProvider,\n} from '../types';\n\n/**\n * Semantic status-badge token bundles. These map to the platform status tokens\n * (bg/text/border) which are defined for EVERY theme + dark mode, so badges and\n * pills stay legible with strong contrast everywhere. Using these instead of\n * raw `bg-success/10`/`text-info` (whose base color tokens are undefined and\n * render invisible in dark mode).\n */\nconst STATUS_TOKENS = {\n success: 'bg-status-success-bg text-status-success-text border-status-success-border',\n warning: 'bg-status-warning-bg text-status-warning-text border-status-warning-border',\n error: 'bg-status-error-bg text-status-error-text border-status-error-border',\n info: 'bg-status-info-bg text-status-info-text border-status-info-border',\n neutral: 'bg-status-neutral-bg text-status-neutral-text border-status-neutral-border',\n} as const;\n\n/**\n * Tenant status labels and colors\n */\nexport const TENANT_STATUS_CONFIG: Record<TenantStatus, { label: string; color: string }> = {\n active: { label: 'Active', color: STATUS_TOKENS.success },\n suspended: { label: 'Suspended', color: STATUS_TOKENS.error },\n trial: { label: 'Trial', color: STATUS_TOKENS.warning },\n deleted: { label: 'Deleted', color: STATUS_TOKENS.neutral },\n pending: { label: 'Pending', color: STATUS_TOKENS.info },\n provisioning: { label: 'Provisioning', color: STATUS_TOKENS.warning },\n};\n\n/**\n * User status labels and colors\n */\nexport const USER_STATUS_CONFIG: Record<UserStatus, { label: string; color: string }> = {\n active: { label: 'Active', color: STATUS_TOKENS.success },\n inactive: { label: 'Inactive', color: STATUS_TOKENS.neutral },\n blocked: { label: 'Blocked', color: STATUS_TOKENS.error },\n pending: { label: 'Pending', color: STATUS_TOKENS.warning },\n};\n\n/**\n * Subscription status labels and colors\n */\nexport const SUBSCRIPTION_STATUS_CONFIG: Record<\n SubscriptionStatus,\n { label: string; color: string }\n> = {\n active: { label: 'Active', color: STATUS_TOKENS.success },\n canceled: { label: 'Canceled', color: STATUS_TOKENS.neutral },\n past_due: { label: 'Past Due', color: STATUS_TOKENS.error },\n trialing: { label: 'Trialing', color: STATUS_TOKENS.info },\n paused: { label: 'Paused', color: STATUS_TOKENS.warning },\n};\n\n/**\n * Ticket status labels and colors\n */\nexport const TICKET_STATUS_CONFIG: Record<TicketStatus, { label: string; color: string }> = {\n open: { label: 'Open', color: STATUS_TOKENS.info },\n in_progress: { label: 'In Progress', color: STATUS_TOKENS.warning },\n resolved: { label: 'Resolved', color: STATUS_TOKENS.success },\n closed: { label: 'Closed', color: STATUS_TOKENS.neutral },\n};\n\n/**\n * Ticket priority labels and colors\n */\nexport const TICKET_PRIORITY_CONFIG: Record<TicketPriority, { label: string; color: string }> = {\n low: { label: 'Low', color: STATUS_TOKENS.neutral },\n medium: { label: 'Medium', color: STATUS_TOKENS.info },\n high: { label: 'High', color: STATUS_TOKENS.warning },\n critical: { label: 'Critical', color: STATUS_TOKENS.error },\n};\n\n/**\n * Health status labels and colors\n */\nexport const HEALTH_STATUS_CONFIG: Record<HealthStatus, { label: string; color: string }> = {\n healthy: { label: 'Healthy', color: STATUS_TOKENS.success },\n degraded: { label: 'Degraded', color: STATUS_TOKENS.warning },\n down: { label: 'Down', color: STATUS_TOKENS.error },\n unknown: { label: 'Unknown', color: STATUS_TOKENS.neutral },\n};\n\n/**\n * Deployment status labels and colors\n */\nexport const DEPLOYMENT_STATUS_CONFIG: Record<DeploymentStatus, { label: string; color: string }> =\n {\n pending: { label: 'Pending', color: STATUS_TOKENS.neutral },\n running: { label: 'Running', color: STATUS_TOKENS.info },\n in_progress: { label: 'In Progress', color: STATUS_TOKENS.info },\n success: { label: 'Success', color: STATUS_TOKENS.success },\n failed: { label: 'Failed', color: STATUS_TOKENS.error },\n rolled_back: { label: 'Rolled Back', color: STATUS_TOKENS.warning },\n };\n\n/**\n * Cloud provider labels and colors. Each provider maps to a semantic status\n * tint so the pills stay theme-aware + dark-mode legible (no raw brand hexes).\n */\nexport const CLOUD_PROVIDER_CONFIG: Record<\n CloudProvider,\n { label: string; color: string; icon: string }\n> = {\n aws: { label: 'AWS', color: STATUS_TOKENS.warning, icon: 'aws' },\n azure: { label: 'Azure', color: STATUS_TOKENS.info, icon: 'azure' },\n gcp: { label: 'GCP', color: STATUS_TOKENS.error, icon: 'gcp' },\n digitalocean: { label: 'DigitalOcean', color: STATUS_TOKENS.info, icon: 'do' },\n};\n\n/**\n * Chart colors using semantic chart tokens (theme-aware full colors — not\n * `hsl(var(...))`, which would be invalid now that the chart vars are colors).\n */\nexport const CHART_COLORS = {\n primary: 'var(--color-chart-1)',\n secondary: 'var(--color-chart-2)',\n tertiary: 'var(--color-chart-3)',\n quaternary: 'var(--color-chart-4)',\n quinary: 'var(--color-chart-5)',\n};\n\n/**\n * Default pagination\n */\nexport const DEFAULT_PAGE_SIZE = 20;\nexport const PAGE_SIZE_OPTIONS = [10, 20, 50, 100];\n"],"mappings":";AAkBA,IAAM,IAAgB;CACpB,SAAS;CACT,SAAS;CACT,OAAO;CACP,MAAM;CACN,SAAS;CACV,EAKY,IAA+E;CAC1F,QAAQ;EAAE,OAAO;EAAU,OAAO,EAAc;EAAS;CACzD,WAAW;EAAE,OAAO;EAAa,OAAO,EAAc;EAAO;CAC7D,OAAO;EAAE,OAAO;EAAS,OAAO,EAAc;EAAS;CACvD,SAAS;EAAE,OAAO;EAAW,OAAO,EAAc;EAAS;CAC3D,SAAS;EAAE,OAAO;EAAW,OAAO,EAAc;EAAM;CACxD,cAAc;EAAE,OAAO;EAAgB,OAAO,EAAc;EAAS;CACtE,EAKY,IAA2E;CACtF,QAAQ;EAAE,OAAO;EAAU,OAAO,EAAc;EAAS;CACzD,UAAU;EAAE,OAAO;EAAY,OAAO,EAAc;EAAS;CAC7D,SAAS;EAAE,OAAO;EAAW,OAAO,EAAc;EAAO;CACzD,SAAS;EAAE,OAAO;EAAW,OAAO,EAAc;EAAS;CAC5D;AASmC,EAAc,SACV,EAAc,SACd,EAAc,OACd,EAAc,MAClB,EAAc,SAOlB,EAAc,MACA,EAAc,SACpB,EAAc,SAClB,EAAc,SAOpB,EAAc,SACR,EAAc,MAClB,EAAc,SACN,EAAc;AAMtD,IAAa,IAA+E;CAC1F,SAAS;EAAE,OAAO;EAAW,OAAO,EAAc;EAAS;CAC3D,UAAU;EAAE,OAAO;EAAY,OAAO,EAAc;EAAS;CAC7D,MAAM;EAAE,OAAO;EAAQ,OAAO,EAAc;EAAO;CACnD,SAAS;EAAE,OAAO;EAAW,OAAO,EAAc;EAAS;CAC5D,EAKY,IACX;CACE,SAAS;EAAE,OAAO;EAAW,OAAO,EAAc;EAAS;CAC3D,SAAS;EAAE,OAAO;EAAW,OAAO,EAAc;EAAM;CACxD,aAAa;EAAE,OAAO;EAAe,OAAO,EAAc;EAAM;CAChE,SAAS;EAAE,OAAO;EAAW,OAAO,EAAc;EAAS;CAC3D,QAAQ;EAAE,OAAO;EAAU,OAAO,EAAc;EAAO;CACvD,aAAa;EAAE,OAAO;EAAe,OAAO,EAAc;EAAS;CACpE;AAU2B,EAAc,SACV,EAAc,MAClB,EAAc,OACI,EAAc"}
@@ -1,42 +1,43 @@
1
1
  import { useAdminPermissions as e } from "../hooks/useAdminPermissions.js";
2
2
  import { EmptyState as t, Input as n } from "../components/ui.js";
3
3
  import { PageLayout as r } from "../components/PageLayout.js";
4
- import { formatRelativeTime as i } from "../utils/formatters.js";
5
- import { DataTable as a, Pagination as o } from "../components/DataTable.js";
6
- import { PermissionGuard as s } from "../components/PermissionGuard.js";
4
+ import { formatNumber as i, formatRelativeTime as a } from "../utils/formatters.js";
5
+ import { DataTable as o, Pagination as s } from "../components/DataTable.js";
6
+ import { PermissionGuard as c } from "../components/PermissionGuard.js";
7
7
  import "../components/index.js";
8
- import { useListAuditLogsQuery as c } from "../../generated/global-operations.js";
9
- import { useCallback as l, useMemo as u, useState as d } from "react";
10
- import { Database as f, FileText as p, Key as m, Search as h, Settings as g, Shield as _, User as v } from "lucide-react";
11
- import { jsx as y, jsxs as b } from "react/jsx-runtime";
12
- import { SaveToFilesButton as x } from "@burdenoff/fe-libs/storage";
8
+ import { useListAuditLogsQuery as l } from "../../generated/global-operations.js";
9
+ import { useCallback as u, useMemo as d, useState as f } from "react";
10
+ import { Database as p, FileText as m, Key as h, Search as g, Settings as _, Shield as v, User as y } from "lucide-react";
11
+ import { jsx as b, jsxs as x } from "react/jsx-runtime";
12
+ import { EmphasisPanel as S, PagePurpose as C } from "@burdenoff/fe-libs/ui";
13
+ import { SaveToFilesButton as w } from "@burdenoff/fe-libs/storage";
13
14
  //#region src/admin/pages/AuditLogsPage.tsx
14
- var S = 20, C = {
15
- auth: /* @__PURE__ */ y(v, { className: "h-3 w-3" }),
16
- user: /* @__PURE__ */ y(v, { className: "h-3 w-3" }),
17
- settings: /* @__PURE__ */ y(g, { className: "h-3 w-3" }),
18
- rbac: /* @__PURE__ */ y(_, { className: "h-3 w-3" }),
19
- data: /* @__PURE__ */ y(f, { className: "h-3 w-3" }),
20
- api: /* @__PURE__ */ y(m, { className: "h-3 w-3" })
15
+ var T = 20, E = {
16
+ auth: /* @__PURE__ */ b(y, { className: "h-3 w-3" }),
17
+ user: /* @__PURE__ */ b(y, { className: "h-3 w-3" }),
18
+ settings: /* @__PURE__ */ b(_, { className: "h-3 w-3" }),
19
+ rbac: /* @__PURE__ */ b(v, { className: "h-3 w-3" }),
20
+ data: /* @__PURE__ */ b(p, { className: "h-3 w-3" }),
21
+ api: /* @__PURE__ */ b(h, { className: "h-3 w-3" })
21
22
  };
22
- function w(e) {
23
- return C[e.split(".")[0]] ?? /* @__PURE__ */ y(p, { className: "h-3 w-3" });
23
+ function D(e) {
24
+ return E[e.split(".")[0]] ?? /* @__PURE__ */ b(m, { className: "h-3 w-3" });
24
25
  }
25
- function T() {
26
- let { canViewAuditLogs: f, isLoading: m } = e(), [g, _] = d(1), [v, C] = d(""), [T, E] = d(""), D = m || !f, { data: O, loading: k, error: A } = c({
26
+ function O() {
27
+ let { canViewAuditLogs: p, isLoading: h } = e(), [_, v] = f(1), [y, E] = f(""), [O, k] = f(""), A = h || !p, { data: j, loading: M, error: N } = l({
27
28
  variables: {
28
- resource: T || void 0,
29
- limit: S,
30
- offset: (g - 1) * S
29
+ resource: O || void 0,
30
+ limit: T,
31
+ offset: (_ - 1) * T
31
32
  },
32
33
  fetchPolicy: "cache-and-network",
33
- skip: D
34
- }), j = u(() => (O?.auditLogs?.edges ?? []).map((e) => e.node), [O]), M = u(() => {
35
- if (!v) return j;
36
- let e = v.toLowerCase();
37
- return j.filter((t) => t.action.toLowerCase().includes(e) || t.actorId.toLowerCase().includes(e) || t.resource.toLowerCase().includes(e) || t.tenantId.toLowerCase().includes(e));
38
- }, [j, v]), N = O?.auditLogs?.totalCount ?? 0, P = Math.ceil(N / S), F = l(() => {
39
- let e = M;
34
+ skip: A
35
+ }), P = d(() => (j?.auditLogs?.edges ?? []).map((e) => e.node), [j]), F = d(() => {
36
+ if (!y) return P;
37
+ let e = y.toLowerCase();
38
+ return P.filter((t) => t.action.toLowerCase().includes(e) || t.actorId.toLowerCase().includes(e) || t.resource.toLowerCase().includes(e) || t.tenantId.toLowerCase().includes(e));
39
+ }, [P, y]), I = j?.auditLogs?.totalCount ?? 0, L = Math.ceil(I / T), R = u(() => {
40
+ let e = F;
40
41
  if (e.length === 0) return null;
41
42
  let t = [
42
43
  "id",
@@ -70,24 +71,24 @@ function T() {
70
71
  n(e.createdAt)
71
72
  ].join(","))];
72
73
  return new Blob([r.join("\n")], { type: "text/csv;charset=utf-8;" });
73
- }, [M]), I = l(() => {
74
- let e = F();
74
+ }, [F]), z = u(() => {
75
+ let e = R();
75
76
  if (!e) return;
76
77
  let t = URL.createObjectURL(e), n = document.createElement("a"), r = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
77
78
  n.href = t, n.setAttribute("download", `audit-logs-${r}.csv`), document.body.appendChild(n), n.click(), document.body.removeChild(n), URL.revokeObjectURL(t);
78
- }, [F]);
79
- return /* @__PURE__ */ y(s, {
80
- allowed: f,
81
- isLoading: m,
79
+ }, [R]);
80
+ return /* @__PURE__ */ b(c, {
81
+ allowed: p,
82
+ isLoading: h,
82
83
  pageTitle: "Audit Logs",
83
84
  denyMessage: "You don't have permission to view audit logs.",
84
- children: /* @__PURE__ */ y(r, {
85
+ children: /* @__PURE__ */ b(r, {
85
86
  title: "Audit Logs",
86
87
  gradientWord: "Logs",
87
- description: `Security and compliance audit trail${N ? ` · ${N} total` : ""}`,
88
- actions: /* @__PURE__ */ y(x, {
88
+ description: `Security and compliance audit trail${I ? ` · ${I} total` : ""}`,
89
+ actions: /* @__PURE__ */ b(w, {
89
90
  label: "Export CSV",
90
- disabled: k || M.length === 0,
91
+ disabled: M || F.length === 0,
91
92
  testId: "audit-logs-export",
92
93
  defaultFolderPath: "/Admin/Audit Logs",
93
94
  source: {
@@ -95,49 +96,63 @@ function T() {
95
96
  filename: `audit-logs-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.csv`,
96
97
  mimeType: "text/csv",
97
98
  getBlob: async () => {
98
- let e = F();
99
+ let e = R();
99
100
  return e ? { blob: e } : null;
100
101
  }
101
102
  },
102
- onDownload: I
103
+ onDownload: z
103
104
  }),
104
- children: /* @__PURE__ */ b("div", {
105
+ children: /* @__PURE__ */ x("div", {
105
106
  className: "space-y-4",
106
107
  children: [
107
- /* @__PURE__ */ b("div", {
108
- className: "flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between",
108
+ /* @__PURE__ */ b(C, { children: "A tamper-evident record of who did what across the platform — every sign-in, permission change, and data action. Use this page to investigate a security incident, answer a compliance question, or export the trail as CSV for an auditor." }),
109
+ /* @__PURE__ */ x(S, {
110
+ className: "flex items-center gap-4 p-5",
109
111
  children: [/* @__PURE__ */ b("div", {
112
+ className: "flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-action-primary-bg/15 text-action-primary-bg",
113
+ children: /* @__PURE__ */ b(m, { className: "h-6 w-6" })
114
+ }), /* @__PURE__ */ x("div", { children: [/* @__PURE__ */ b("div", {
115
+ className: "text-3xl font-bold tabular-nums text-text-primary",
116
+ children: i(I)
117
+ }), /* @__PURE__ */ b("p", {
118
+ className: "text-sm text-text-secondary",
119
+ children: "recorded audit events"
120
+ })] })]
121
+ }),
122
+ /* @__PURE__ */ x("div", {
123
+ className: "flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between",
124
+ children: [/* @__PURE__ */ x("div", {
110
125
  className: "relative max-w-sm flex-1",
111
- children: [/* @__PURE__ */ y(h, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary" }), /* @__PURE__ */ y(n, {
126
+ children: [/* @__PURE__ */ b(g, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary" }), /* @__PURE__ */ b(n, {
112
127
  placeholder: "Search by action, actor, or resource...",
113
- value: v,
114
- onChange: (e) => C(e.target.value),
128
+ value: y,
129
+ onChange: (e) => E(e.target.value),
115
130
  className: "pl-9"
116
131
  })]
117
- }), /* @__PURE__ */ y(n, {
132
+ }), /* @__PURE__ */ b(n, {
118
133
  placeholder: "Filter by resource (e.g. tenant, user)...",
119
- value: T,
134
+ value: O,
120
135
  onChange: (e) => {
121
- _(1), E(e.target.value);
136
+ v(1), k(e.target.value);
122
137
  },
123
138
  className: "max-w-xs"
124
139
  })]
125
140
  }),
126
- A && /* @__PURE__ */ b("div", {
141
+ N && /* @__PURE__ */ x("div", {
127
142
  className: "rounded-md border border-status-error-border/50 bg-status-error-bg/10 p-4 text-sm text-status-error-text",
128
- children: ["Failed to load audit logs: ", A.message]
143
+ children: ["Failed to load audit logs: ", N.message]
129
144
  }),
130
- /* @__PURE__ */ y(a, {
145
+ /* @__PURE__ */ b(o, {
131
146
  columns: [
132
147
  {
133
148
  key: "action",
134
149
  header: "Action",
135
- render: (e) => /* @__PURE__ */ b("div", {
150
+ render: (e) => /* @__PURE__ */ x("div", {
136
151
  className: "flex items-center gap-2",
137
- children: [/* @__PURE__ */ y("span", {
152
+ children: [/* @__PURE__ */ b("span", {
138
153
  className: "text-text-secondary",
139
- children: w(e.action)
140
- }), /* @__PURE__ */ y("span", {
154
+ children: D(e.action)
155
+ }), /* @__PURE__ */ b("span", {
141
156
  className: "font-mono text-sm font-medium text-text-primary",
142
157
  children: e.action
143
158
  })]
@@ -147,12 +162,12 @@ function T() {
147
162
  key: "actorId",
148
163
  header: "Actor",
149
164
  width: "180px",
150
- render: (e) => /* @__PURE__ */ b("div", {
165
+ render: (e) => /* @__PURE__ */ x("div", {
151
166
  className: "min-w-0",
152
- children: [/* @__PURE__ */ y("div", {
167
+ children: [/* @__PURE__ */ b("div", {
153
168
  className: "truncate text-sm",
154
169
  children: e.actorId
155
- }), /* @__PURE__ */ y("div", {
170
+ }), /* @__PURE__ */ b("div", {
156
171
  className: "text-xs text-text-secondary capitalize",
157
172
  children: e.actorType
158
173
  })]
@@ -162,12 +177,12 @@ function T() {
162
177
  key: "resource",
163
178
  header: "Resource",
164
179
  width: "150px",
165
- render: (e) => /* @__PURE__ */ b("div", {
180
+ render: (e) => /* @__PURE__ */ x("div", {
166
181
  className: "min-w-0",
167
- children: [/* @__PURE__ */ y("div", {
182
+ children: [/* @__PURE__ */ b("div", {
168
183
  className: "truncate text-sm",
169
184
  children: e.resource
170
- }), e.resourceId && /* @__PURE__ */ b("div", {
185
+ }), e.resourceId && /* @__PURE__ */ x("div", {
171
186
  className: "truncate text-xs text-text-secondary",
172
187
  children: ["#", String(e.resourceId).slice(-8)]
173
188
  })]
@@ -177,7 +192,7 @@ function T() {
177
192
  key: "tenantId",
178
193
  header: "Tenant",
179
194
  width: "130px",
180
- render: (e) => /* @__PURE__ */ y("span", {
195
+ render: (e) => /* @__PURE__ */ b("span", {
181
196
  className: "text-xs text-text-secondary",
182
197
  children: e.tenantId ? `#${e.tenantId.slice(-8)}` : "—"
183
198
  })
@@ -186,7 +201,7 @@ function T() {
186
201
  key: "ipAddress",
187
202
  header: "IP",
188
203
  width: "120px",
189
- render: (e) => /* @__PURE__ */ y("span", {
204
+ render: (e) => /* @__PURE__ */ b("span", {
190
205
  className: "font-mono text-xs text-text-secondary",
191
206
  children: e.ipAddress ?? "—"
192
207
  })
@@ -195,25 +210,25 @@ function T() {
195
210
  key: "createdAt",
196
211
  header: "Time",
197
212
  width: "110px",
198
- render: (e) => i(e.createdAt)
213
+ render: (e) => a(e.createdAt)
199
214
  }
200
215
  ],
201
- data: M,
216
+ data: F,
202
217
  keyExtractor: (e) => e.id,
203
- loading: k,
204
- emptyState: /* @__PURE__ */ y(t, {
218
+ loading: M,
219
+ emptyState: /* @__PURE__ */ b(t, {
205
220
  illustration: "empty-files",
206
- icon: /* @__PURE__ */ y(p, { className: "h-8 w-8" }),
221
+ icon: /* @__PURE__ */ b(m, { className: "h-8 w-8" }),
207
222
  title: "No audit logs found",
208
223
  description: "Audit events will appear here when actions are performed"
209
224
  })
210
225
  }),
211
- N > S && /* @__PURE__ */ y(o, {
212
- page: g,
213
- totalPages: P,
214
- totalItems: N,
215
- pageSize: S,
216
- onPageChange: _
226
+ I > T && /* @__PURE__ */ b(s, {
227
+ page: _,
228
+ totalPages: L,
229
+ totalItems: I,
230
+ pageSize: T,
231
+ onPageChange: v
217
232
  })
218
233
  ]
219
234
  })
@@ -221,6 +236,6 @@ function T() {
221
236
  });
222
237
  }
223
238
  //#endregion
224
- export { T as AuditLogsPage };
239
+ export { O as AuditLogsPage };
225
240
 
226
241
  //# sourceMappingURL=AuditLogsPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AuditLogsPage.js","names":[],"sources":["../../../src/admin/pages/AuditLogsPage.tsx"],"sourcesContent":["import { useState, useMemo, useCallback } from 'react';\nimport { FileText, Search, Download, User, Settings, Shield, Database, Key } from 'lucide-react';\nimport { SaveToFilesButton } from '@burdenoff/fe-libs/storage';\nimport {\n PageLayout,\n Input,\n DataTable,\n Pagination,\n EmptyState,\n Button,\n PermissionGuard,\n} from '../components';\nimport { useAdminPermissions } from '../hooks/useAdminPermissions';\nimport { formatRelativeTime } from '../utils/formatters';\nimport { useListAuditLogsQuery } from '../../generated/global-operations';\n\nconst PAGE_SIZE = 20;\n\nconst ACTION_ICONS: Record<string, React.ReactNode> = {\n auth: <User className=\"h-3 w-3\" />,\n user: <User className=\"h-3 w-3\" />,\n settings: <Settings className=\"h-3 w-3\" />,\n rbac: <Shield className=\"h-3 w-3\" />,\n data: <Database className=\"h-3 w-3\" />,\n api: <Key className=\"h-3 w-3\" />,\n};\n\nfunction getActionIcon(action: string) {\n const prefix = action.split('.')[0];\n return ACTION_ICONS[prefix] ?? <FileText className=\"h-3 w-3\" />;\n}\n\n/**\n * Audit logs page — connected to real backend\n */\nexport function AuditLogsPage() {\n const { canViewAuditLogs, isLoading: permissionsLoading } = useAdminPermissions();\n\n const [page, setPage] = useState(1);\n const [search, setSearch] = useState('');\n const [resourceFilter, setResourceFilter] = useState('');\n\n const skipQueries = permissionsLoading || !canViewAuditLogs;\n\n const { data, loading, error } = useListAuditLogsQuery({\n variables: {\n resource: resourceFilter || undefined,\n limit: PAGE_SIZE,\n offset: (page - 1) * PAGE_SIZE,\n },\n fetchPolicy: 'cache-and-network',\n skip: skipQueries,\n });\n\n const auditLogs = useMemo(() => {\n const edges = data?.auditLogs?.edges ?? [];\n return edges.map((e: (typeof edges)[number]) => e.node);\n }, [data]);\n\n type AuditLogNode = (typeof auditLogs)[number];\n\n const filteredLogs = useMemo(() => {\n if (!search) return auditLogs;\n const q = search.toLowerCase();\n return auditLogs.filter(\n (log: AuditLogNode) =>\n log.action.toLowerCase().includes(q) ||\n log.actorId.toLowerCase().includes(q) ||\n log.resource.toLowerCase().includes(q) ||\n log.tenantId.toLowerCase().includes(q)\n );\n }, [auditLogs, search]);\n\n const total = data?.auditLogs?.totalCount ?? 0;\n const totalPages = Math.ceil(total / PAGE_SIZE);\n\n const buildAuditCsvBlob = useCallback((): Blob | null => {\n const rows = filteredLogs;\n if (rows.length === 0) return null;\n\n const headers = [\n 'id',\n 'action',\n 'actorId',\n 'actorType',\n 'resource',\n 'resourceId',\n 'tenantId',\n 'ipAddress',\n 'userAgent',\n 'changes',\n 'metadata',\n 'createdAt',\n ];\n\n const escape = (value: unknown): string => {\n if (value === null || value === undefined) return '';\n const str = typeof value === 'object' ? JSON.stringify(value) : String(value);\n // RFC 4180: wrap in quotes if it contains quote, comma, or newline; double internal quotes\n if (/[\",\\n\\r]/.test(str)) {\n return `\"${str.replace(/\"/g, '\"\"')}\"`;\n }\n return str;\n };\n\n const csvLines = [\n headers.join(','),\n ...rows.map((log: AuditLogNode) =>\n [\n escape(log.id),\n escape(log.action),\n escape(log.actorId),\n escape(log.actorType),\n escape(log.resource),\n escape(log.resourceId),\n escape(log.tenantId),\n escape(log.ipAddress),\n escape((log as { userAgent?: unknown }).userAgent),\n escape((log as { changes?: unknown }).changes),\n escape((log as { metadata?: unknown }).metadata),\n escape(log.createdAt),\n ].join(',')\n ),\n ];\n\n return new Blob([csvLines.join('\\n')], { type: 'text/csv;charset=utf-8;' });\n }, [filteredLogs]);\n\n const exportToCsv = useCallback(() => {\n const blob = buildAuditCsvBlob();\n if (!blob) return;\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n const stamp = new Date().toISOString().replace(/[:.]/g, '-');\n link.href = url;\n link.setAttribute('download', `audit-logs-${stamp}.csv`);\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n URL.revokeObjectURL(url);\n }, [buildAuditCsvBlob]);\n\n const columns = [\n {\n key: 'action',\n header: 'Action',\n render: (log: (typeof auditLogs)[number]) => (\n <div className=\"flex items-center gap-2\">\n <span className=\"text-text-secondary\">{getActionIcon(log.action)}</span>\n <span className=\"font-mono text-sm font-medium text-text-primary\">{log.action}</span>\n </div>\n ),\n },\n {\n key: 'actorId',\n header: 'Actor',\n width: '180px',\n render: (log: (typeof auditLogs)[number]) => (\n <div className=\"min-w-0\">\n <div className=\"truncate text-sm\">{log.actorId}</div>\n <div className=\"text-xs text-text-secondary capitalize\">{log.actorType}</div>\n </div>\n ),\n },\n {\n key: 'resource',\n header: 'Resource',\n width: '150px',\n render: (log: (typeof auditLogs)[number]) => (\n <div className=\"min-w-0\">\n <div className=\"truncate text-sm\">{log.resource}</div>\n {log.resourceId && (\n <div className=\"truncate text-xs text-text-secondary\">\n #{String(log.resourceId).slice(-8)}\n </div>\n )}\n </div>\n ),\n },\n {\n key: 'tenantId',\n header: 'Tenant',\n width: '130px',\n render: (log: (typeof auditLogs)[number]) => (\n <span className=\"text-xs text-text-secondary\">\n {log.tenantId ? `#${log.tenantId.slice(-8)}` : '—'}\n </span>\n ),\n },\n {\n key: 'ipAddress',\n header: 'IP',\n width: '120px',\n render: (log: (typeof auditLogs)[number]) => (\n <span className=\"font-mono text-xs text-text-secondary\">{log.ipAddress ?? '—'}</span>\n ),\n },\n {\n key: 'createdAt',\n header: 'Time',\n width: '110px',\n render: (log: (typeof auditLogs)[number]) => formatRelativeTime(log.createdAt),\n },\n ];\n\n return (\n <PermissionGuard\n allowed={canViewAuditLogs}\n isLoading={permissionsLoading}\n pageTitle=\"Audit Logs\"\n denyMessage=\"You don't have permission to view audit logs.\"\n >\n <PageLayout\n title=\"Audit Logs\"\n gradientWord=\"Logs\"\n description={`Security and compliance audit trail${total ? ` · ${total} total` : ''}`}\n actions={\n <SaveToFilesButton\n label=\"Export CSV\"\n disabled={loading || filteredLogs.length === 0}\n testId=\"audit-logs-export\"\n defaultFolderPath=\"/Admin/Audit Logs\"\n source={{\n kind: 'blob',\n filename: `audit-logs-${new Date().toISOString().replace(/[:.]/g, '-')}.csv`,\n mimeType: 'text/csv',\n getBlob: async () => {\n const blob = buildAuditCsvBlob();\n return blob ? { blob } : null;\n },\n }}\n onDownload={exportToCsv}\n />\n }\n >\n <div className=\"space-y-4\">\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"relative max-w-sm flex-1\">\n <Search className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <Input\n placeholder=\"Search by action, actor, or resource...\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n className=\"pl-9\"\n />\n </div>\n <Input\n placeholder=\"Filter by resource (e.g. tenant, user)...\"\n value={resourceFilter}\n onChange={(e) => {\n setPage(1);\n setResourceFilter(e.target.value);\n }}\n className=\"max-w-xs\"\n />\n </div>\n\n {error && (\n <div className=\"rounded-md border border-status-error-border/50 bg-status-error-bg/10 p-4 text-sm text-status-error-text\">\n Failed to load audit logs: {error.message}\n </div>\n )}\n\n <DataTable\n columns={columns}\n data={filteredLogs}\n keyExtractor={(l) => l.id}\n loading={loading}\n emptyState={\n <EmptyState\n illustration=\"empty-files\"\n icon={<FileText className=\"h-8 w-8\" />}\n title=\"No audit logs found\"\n description=\"Audit events will appear here when actions are performed\"\n />\n }\n />\n\n {total > PAGE_SIZE && (\n <Pagination\n page={page}\n totalPages={totalPages}\n totalItems={total}\n pageSize={PAGE_SIZE}\n onPageChange={setPage}\n />\n )}\n </div>\n </PageLayout>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AuditLogsPage.js","names":[],"sources":["../../../src/admin/pages/AuditLogsPage.tsx"],"sourcesContent":["import { useState, useMemo, useCallback } from 'react';\nimport { FileText, Search, Download, User, Settings, Shield, Database, Key } from 'lucide-react';\nimport { SaveToFilesButton } from '@burdenoff/fe-libs/storage';\nimport {\n PageLayout,\n Input,\n DataTable,\n Pagination,\n EmptyState,\n Button,\n PermissionGuard,\n} from '../components';\nimport { useAdminPermissions } from '../hooks/useAdminPermissions';\nimport { formatRelativeTime, formatNumber } from '../utils/formatters';\nimport { useListAuditLogsQuery } from '../../generated/global-operations';\nimport { PagePurpose, EmphasisPanel } from '@burdenoff/fe-libs/ui';\n\nconst PAGE_SIZE = 20;\n\nconst ACTION_ICONS: Record<string, React.ReactNode> = {\n auth: <User className=\"h-3 w-3\" />,\n user: <User className=\"h-3 w-3\" />,\n settings: <Settings className=\"h-3 w-3\" />,\n rbac: <Shield className=\"h-3 w-3\" />,\n data: <Database className=\"h-3 w-3\" />,\n api: <Key className=\"h-3 w-3\" />,\n};\n\nfunction getActionIcon(action: string) {\n const prefix = action.split('.')[0];\n return ACTION_ICONS[prefix] ?? <FileText className=\"h-3 w-3\" />;\n}\n\n/**\n * Audit logs page — connected to real backend\n */\nexport function AuditLogsPage() {\n const { canViewAuditLogs, isLoading: permissionsLoading } = useAdminPermissions();\n\n const [page, setPage] = useState(1);\n const [search, setSearch] = useState('');\n const [resourceFilter, setResourceFilter] = useState('');\n\n const skipQueries = permissionsLoading || !canViewAuditLogs;\n\n const { data, loading, error } = useListAuditLogsQuery({\n variables: {\n resource: resourceFilter || undefined,\n limit: PAGE_SIZE,\n offset: (page - 1) * PAGE_SIZE,\n },\n fetchPolicy: 'cache-and-network',\n skip: skipQueries,\n });\n\n const auditLogs = useMemo(() => {\n const edges = data?.auditLogs?.edges ?? [];\n return edges.map((e: (typeof edges)[number]) => e.node);\n }, [data]);\n\n type AuditLogNode = (typeof auditLogs)[number];\n\n const filteredLogs = useMemo(() => {\n if (!search) return auditLogs;\n const q = search.toLowerCase();\n return auditLogs.filter(\n (log: AuditLogNode) =>\n log.action.toLowerCase().includes(q) ||\n log.actorId.toLowerCase().includes(q) ||\n log.resource.toLowerCase().includes(q) ||\n log.tenantId.toLowerCase().includes(q)\n );\n }, [auditLogs, search]);\n\n const total = data?.auditLogs?.totalCount ?? 0;\n const totalPages = Math.ceil(total / PAGE_SIZE);\n\n const buildAuditCsvBlob = useCallback((): Blob | null => {\n const rows = filteredLogs;\n if (rows.length === 0) return null;\n\n const headers = [\n 'id',\n 'action',\n 'actorId',\n 'actorType',\n 'resource',\n 'resourceId',\n 'tenantId',\n 'ipAddress',\n 'userAgent',\n 'changes',\n 'metadata',\n 'createdAt',\n ];\n\n const escape = (value: unknown): string => {\n if (value === null || value === undefined) return '';\n const str = typeof value === 'object' ? JSON.stringify(value) : String(value);\n // RFC 4180: wrap in quotes if it contains quote, comma, or newline; double internal quotes\n if (/[\",\\n\\r]/.test(str)) {\n return `\"${str.replace(/\"/g, '\"\"')}\"`;\n }\n return str;\n };\n\n const csvLines = [\n headers.join(','),\n ...rows.map((log: AuditLogNode) =>\n [\n escape(log.id),\n escape(log.action),\n escape(log.actorId),\n escape(log.actorType),\n escape(log.resource),\n escape(log.resourceId),\n escape(log.tenantId),\n escape(log.ipAddress),\n escape((log as { userAgent?: unknown }).userAgent),\n escape((log as { changes?: unknown }).changes),\n escape((log as { metadata?: unknown }).metadata),\n escape(log.createdAt),\n ].join(',')\n ),\n ];\n\n return new Blob([csvLines.join('\\n')], { type: 'text/csv;charset=utf-8;' });\n }, [filteredLogs]);\n\n const exportToCsv = useCallback(() => {\n const blob = buildAuditCsvBlob();\n if (!blob) return;\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n const stamp = new Date().toISOString().replace(/[:.]/g, '-');\n link.href = url;\n link.setAttribute('download', `audit-logs-${stamp}.csv`);\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n URL.revokeObjectURL(url);\n }, [buildAuditCsvBlob]);\n\n const columns = [\n {\n key: 'action',\n header: 'Action',\n render: (log: (typeof auditLogs)[number]) => (\n <div className=\"flex items-center gap-2\">\n <span className=\"text-text-secondary\">{getActionIcon(log.action)}</span>\n <span className=\"font-mono text-sm font-medium text-text-primary\">{log.action}</span>\n </div>\n ),\n },\n {\n key: 'actorId',\n header: 'Actor',\n width: '180px',\n render: (log: (typeof auditLogs)[number]) => (\n <div className=\"min-w-0\">\n <div className=\"truncate text-sm\">{log.actorId}</div>\n <div className=\"text-xs text-text-secondary capitalize\">{log.actorType}</div>\n </div>\n ),\n },\n {\n key: 'resource',\n header: 'Resource',\n width: '150px',\n render: (log: (typeof auditLogs)[number]) => (\n <div className=\"min-w-0\">\n <div className=\"truncate text-sm\">{log.resource}</div>\n {log.resourceId && (\n <div className=\"truncate text-xs text-text-secondary\">\n #{String(log.resourceId).slice(-8)}\n </div>\n )}\n </div>\n ),\n },\n {\n key: 'tenantId',\n header: 'Tenant',\n width: '130px',\n render: (log: (typeof auditLogs)[number]) => (\n <span className=\"text-xs text-text-secondary\">\n {log.tenantId ? `#${log.tenantId.slice(-8)}` : '—'}\n </span>\n ),\n },\n {\n key: 'ipAddress',\n header: 'IP',\n width: '120px',\n render: (log: (typeof auditLogs)[number]) => (\n <span className=\"font-mono text-xs text-text-secondary\">{log.ipAddress ?? '—'}</span>\n ),\n },\n {\n key: 'createdAt',\n header: 'Time',\n width: '110px',\n render: (log: (typeof auditLogs)[number]) => formatRelativeTime(log.createdAt),\n },\n ];\n\n return (\n <PermissionGuard\n allowed={canViewAuditLogs}\n isLoading={permissionsLoading}\n pageTitle=\"Audit Logs\"\n denyMessage=\"You don't have permission to view audit logs.\"\n >\n <PageLayout\n title=\"Audit Logs\"\n gradientWord=\"Logs\"\n description={`Security and compliance audit trail${total ? ` · ${total} total` : ''}`}\n actions={\n <SaveToFilesButton\n label=\"Export CSV\"\n disabled={loading || filteredLogs.length === 0}\n testId=\"audit-logs-export\"\n defaultFolderPath=\"/Admin/Audit Logs\"\n source={{\n kind: 'blob',\n filename: `audit-logs-${new Date().toISOString().replace(/[:.]/g, '-')}.csv`,\n mimeType: 'text/csv',\n getBlob: async () => {\n const blob = buildAuditCsvBlob();\n return blob ? { blob } : null;\n },\n }}\n onDownload={exportToCsv}\n />\n }\n >\n <div className=\"space-y-4\">\n <PagePurpose>\n A tamper-evident record of who did what across the platform — every sign-in, permission\n change, and data action. Use this page to investigate a security incident, answer a\n compliance question, or export the trail as CSV for an auditor.\n </PagePurpose>\n\n {/* Emphasis: total recorded audit events. */}\n <EmphasisPanel className=\"flex items-center gap-4 p-5\">\n <div className=\"flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-action-primary-bg/15 text-action-primary-bg\">\n <FileText className=\"h-6 w-6\" />\n </div>\n <div>\n <div className=\"text-3xl font-bold tabular-nums text-text-primary\">\n {formatNumber(total)}\n </div>\n <p className=\"text-sm text-text-secondary\">recorded audit events</p>\n </div>\n </EmphasisPanel>\n\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"relative max-w-sm flex-1\">\n <Search className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <Input\n placeholder=\"Search by action, actor, or resource...\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n className=\"pl-9\"\n />\n </div>\n <Input\n placeholder=\"Filter by resource (e.g. tenant, user)...\"\n value={resourceFilter}\n onChange={(e) => {\n setPage(1);\n setResourceFilter(e.target.value);\n }}\n className=\"max-w-xs\"\n />\n </div>\n\n {error && (\n <div className=\"rounded-md border border-status-error-border/50 bg-status-error-bg/10 p-4 text-sm text-status-error-text\">\n Failed to load audit logs: {error.message}\n </div>\n )}\n\n <DataTable\n columns={columns}\n data={filteredLogs}\n keyExtractor={(l) => l.id}\n loading={loading}\n emptyState={\n <EmptyState\n illustration=\"empty-files\"\n icon={<FileText className=\"h-8 w-8\" />}\n title=\"No audit logs found\"\n description=\"Audit events will appear here when actions are performed\"\n />\n }\n />\n\n {total > PAGE_SIZE && (\n <Pagination\n page={page}\n totalPages={totalPages}\n totalItems={total}\n pageSize={PAGE_SIZE}\n onPageChange={setPage}\n />\n )}\n </div>\n </PageLayout>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}