@burdenoff/microfe-admin 2026.624.1 → 2026.626.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 (76) hide show
  1. package/dist/admin/components/AccessDenied.js +2 -2
  2. package/dist/admin/components/AccessDenied.js.map +1 -1
  3. package/dist/admin/components/ActivityFeed.js +4 -4
  4. package/dist/admin/components/ActivityFeed.js.map +1 -1
  5. package/dist/admin/components/Charts.js.map +1 -1
  6. package/dist/admin/components/ConfirmDialog.js +1 -1
  7. package/dist/admin/components/ConfirmDialog.js.map +1 -1
  8. package/dist/admin/components/DataTable.js +5 -5
  9. package/dist/admin/components/DataTable.js.map +1 -1
  10. package/dist/admin/components/FormSheet.js +1 -1
  11. package/dist/admin/components/FormSheet.js.map +1 -1
  12. package/dist/admin/components/MetricCard.js +40 -37
  13. package/dist/admin/components/MetricCard.js.map +1 -1
  14. package/dist/admin/components/PageLayout.js +28 -22
  15. package/dist/admin/components/PageLayout.js.map +1 -1
  16. package/dist/admin/components/TenantSsoPanel.js +1 -1
  17. package/dist/admin/components/TenantSsoPanel.js.map +1 -1
  18. package/dist/admin/components/Toast.js +11 -11
  19. package/dist/admin/components/Toast.js.map +1 -1
  20. package/dist/admin/components/forms/SubscriptionForm.js +2 -2
  21. package/dist/admin/components/forms/SubscriptionForm.js.map +1 -1
  22. package/dist/admin/components/forms/TenantForm.js +1 -1
  23. package/dist/admin/components/forms/UserForm.js +1 -1
  24. package/dist/admin/components/ui.js +48 -41
  25. package/dist/admin/components/ui.js.map +1 -1
  26. package/dist/admin/constants/index.js +33 -24
  27. package/dist/admin/constants/index.js.map +1 -1
  28. package/dist/admin/pages/AuditLogsPage.js +94 -77
  29. package/dist/admin/pages/AuditLogsPage.js.map +1 -1
  30. package/dist/admin/pages/BillingPage.js +70 -18
  31. package/dist/admin/pages/BillingPage.js.map +1 -1
  32. package/dist/admin/pages/CommunicationsPage.js +98 -90
  33. package/dist/admin/pages/CommunicationsPage.js.map +1 -1
  34. package/dist/admin/pages/ContentPagesPage.js +276 -259
  35. package/dist/admin/pages/ContentPagesPage.js.map +1 -1
  36. package/dist/admin/pages/DashboardPage.js +114 -82
  37. package/dist/admin/pages/DashboardPage.js.map +1 -1
  38. package/dist/admin/pages/FeatureFlagsPage.js +94 -74
  39. package/dist/admin/pages/FeatureFlagsPage.js.map +1 -1
  40. package/dist/admin/pages/OrganizationsListPage.js +106 -86
  41. package/dist/admin/pages/OrganizationsListPage.js.map +1 -1
  42. package/dist/admin/pages/ProductsPage.js +139 -128
  43. package/dist/admin/pages/ProductsPage.js.map +1 -1
  44. package/dist/admin/pages/ReferralsPage.js +74 -52
  45. package/dist/admin/pages/ReferralsPage.js.map +1 -1
  46. package/dist/admin/pages/RolesPermissionsPage.js +137 -116
  47. package/dist/admin/pages/RolesPermissionsPage.js.map +1 -1
  48. package/dist/admin/pages/SREPage.js +38 -32
  49. package/dist/admin/pages/SREPage.js.map +1 -1
  50. package/dist/admin/pages/SandboxSettingsPage.js +361 -322
  51. package/dist/admin/pages/SandboxSettingsPage.js.map +1 -1
  52. package/dist/admin/pages/SettingsPage.js +145 -130
  53. package/dist/admin/pages/SettingsPage.js.map +1 -1
  54. package/dist/admin/pages/SyncFailuresPage.js +123 -118
  55. package/dist/admin/pages/SyncFailuresPage.js.map +1 -1
  56. package/dist/admin/pages/TenantDetailPage.js +261 -218
  57. package/dist/admin/pages/TenantDetailPage.js.map +1 -1
  58. package/dist/admin/pages/TenantFormRoutePage.js +65 -61
  59. package/dist/admin/pages/TenantFormRoutePage.js.map +1 -1
  60. package/dist/admin/pages/TenantsListPage.js +88 -71
  61. package/dist/admin/pages/TenantsListPage.js.map +1 -1
  62. package/dist/admin/pages/UsersListPage.js +129 -112
  63. package/dist/admin/pages/UsersListPage.js.map +1 -1
  64. package/dist/admin/pages/WaitlistPage.js +80 -63
  65. package/dist/admin/pages/WaitlistPage.js.map +1 -1
  66. package/dist/admin/pages/WorkflowsPage.js +108 -100
  67. package/dist/admin/pages/WorkflowsPage.js.map +1 -1
  68. package/dist/admin/pages/WorkspacesListPage.js +62 -49
  69. package/dist/admin/pages/WorkspacesListPage.js.map +1 -1
  70. package/dist/node_modules/@hookform/resolvers/dist/resolvers.js +2 -2
  71. package/dist/node_modules/@hookform/resolvers/dist/resolvers.js.map +1 -1
  72. package/dist/node_modules/@hookform/resolvers/zod/dist/zod.js +35 -26
  73. package/dist/node_modules/@hookform/resolvers/zod/dist/zod.js.map +1 -1
  74. package/dist/node_modules/react-hook-form/dist/index.esm.js +609 -516
  75. package/dist/node_modules/react-hook-form/dist/index.esm.js.map +1 -1
  76. package/package.json +2 -2
@@ -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,23 +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
- description: `Security and compliance audit trail${N ? ` · ${N} total` : ""}`,
87
- actions: /* @__PURE__ */ y(x, {
87
+ gradientWord: "Logs",
88
+ description: `Security and compliance audit trail${I ? ` · ${I} total` : ""}`,
89
+ actions: /* @__PURE__ */ b(w, {
88
90
  label: "Export CSV",
89
- disabled: k || M.length === 0,
91
+ disabled: M || F.length === 0,
90
92
  testId: "audit-logs-export",
91
93
  defaultFolderPath: "/Admin/Audit Logs",
92
94
  source: {
@@ -94,49 +96,63 @@ function T() {
94
96
  filename: `audit-logs-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.csv`,
95
97
  mimeType: "text/csv",
96
98
  getBlob: async () => {
97
- let e = F();
99
+ let e = R();
98
100
  return e ? { blob: e } : null;
99
101
  }
100
102
  },
101
- onDownload: I
103
+ onDownload: z
102
104
  }),
103
- children: /* @__PURE__ */ b("div", {
105
+ children: /* @__PURE__ */ x("div", {
104
106
  className: "space-y-4",
105
107
  children: [
106
- /* @__PURE__ */ b("div", {
107
- 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",
108
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", {
109
125
  className: "relative max-w-sm flex-1",
110
- 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, {
111
127
  placeholder: "Search by action, actor, or resource...",
112
- value: v,
113
- onChange: (e) => C(e.target.value),
128
+ value: y,
129
+ onChange: (e) => E(e.target.value),
114
130
  className: "pl-9"
115
131
  })]
116
- }), /* @__PURE__ */ y(n, {
132
+ }), /* @__PURE__ */ b(n, {
117
133
  placeholder: "Filter by resource (e.g. tenant, user)...",
118
- value: T,
134
+ value: O,
119
135
  onChange: (e) => {
120
- _(1), E(e.target.value);
136
+ v(1), k(e.target.value);
121
137
  },
122
138
  className: "max-w-xs"
123
139
  })]
124
140
  }),
125
- A && /* @__PURE__ */ b("div", {
141
+ N && /* @__PURE__ */ x("div", {
126
142
  className: "rounded-md border border-status-error-border/50 bg-status-error-bg/10 p-4 text-sm text-status-error-text",
127
- children: ["Failed to load audit logs: ", A.message]
143
+ children: ["Failed to load audit logs: ", N.message]
128
144
  }),
129
- /* @__PURE__ */ y(a, {
145
+ /* @__PURE__ */ b(o, {
130
146
  columns: [
131
147
  {
132
148
  key: "action",
133
149
  header: "Action",
134
- render: (e) => /* @__PURE__ */ b("div", {
150
+ render: (e) => /* @__PURE__ */ x("div", {
135
151
  className: "flex items-center gap-2",
136
- children: [/* @__PURE__ */ y("span", {
152
+ children: [/* @__PURE__ */ b("span", {
137
153
  className: "text-text-secondary",
138
- children: w(e.action)
139
- }), /* @__PURE__ */ y("span", {
154
+ children: D(e.action)
155
+ }), /* @__PURE__ */ b("span", {
140
156
  className: "font-mono text-sm font-medium text-text-primary",
141
157
  children: e.action
142
158
  })]
@@ -146,12 +162,12 @@ function T() {
146
162
  key: "actorId",
147
163
  header: "Actor",
148
164
  width: "180px",
149
- render: (e) => /* @__PURE__ */ b("div", {
165
+ render: (e) => /* @__PURE__ */ x("div", {
150
166
  className: "min-w-0",
151
- children: [/* @__PURE__ */ y("div", {
167
+ children: [/* @__PURE__ */ b("div", {
152
168
  className: "truncate text-sm",
153
169
  children: e.actorId
154
- }), /* @__PURE__ */ y("div", {
170
+ }), /* @__PURE__ */ b("div", {
155
171
  className: "text-xs text-text-secondary capitalize",
156
172
  children: e.actorType
157
173
  })]
@@ -161,12 +177,12 @@ function T() {
161
177
  key: "resource",
162
178
  header: "Resource",
163
179
  width: "150px",
164
- render: (e) => /* @__PURE__ */ b("div", {
180
+ render: (e) => /* @__PURE__ */ x("div", {
165
181
  className: "min-w-0",
166
- children: [/* @__PURE__ */ y("div", {
182
+ children: [/* @__PURE__ */ b("div", {
167
183
  className: "truncate text-sm",
168
184
  children: e.resource
169
- }), e.resourceId && /* @__PURE__ */ b("div", {
185
+ }), e.resourceId && /* @__PURE__ */ x("div", {
170
186
  className: "truncate text-xs text-text-secondary",
171
187
  children: ["#", String(e.resourceId).slice(-8)]
172
188
  })]
@@ -176,7 +192,7 @@ function T() {
176
192
  key: "tenantId",
177
193
  header: "Tenant",
178
194
  width: "130px",
179
- render: (e) => /* @__PURE__ */ y("span", {
195
+ render: (e) => /* @__PURE__ */ b("span", {
180
196
  className: "text-xs text-text-secondary",
181
197
  children: e.tenantId ? `#${e.tenantId.slice(-8)}` : "—"
182
198
  })
@@ -185,7 +201,7 @@ function T() {
185
201
  key: "ipAddress",
186
202
  header: "IP",
187
203
  width: "120px",
188
- render: (e) => /* @__PURE__ */ y("span", {
204
+ render: (e) => /* @__PURE__ */ b("span", {
189
205
  className: "font-mono text-xs text-text-secondary",
190
206
  children: e.ipAddress ?? "—"
191
207
  })
@@ -194,24 +210,25 @@ function T() {
194
210
  key: "createdAt",
195
211
  header: "Time",
196
212
  width: "110px",
197
- render: (e) => i(e.createdAt)
213
+ render: (e) => a(e.createdAt)
198
214
  }
199
215
  ],
200
- data: M,
216
+ data: F,
201
217
  keyExtractor: (e) => e.id,
202
- loading: k,
203
- emptyState: /* @__PURE__ */ y(t, {
204
- icon: /* @__PURE__ */ y(p, { className: "h-8 w-8" }),
218
+ loading: M,
219
+ emptyState: /* @__PURE__ */ b(t, {
220
+ illustration: "empty-files",
221
+ icon: /* @__PURE__ */ b(m, { className: "h-8 w-8" }),
205
222
  title: "No audit logs found",
206
223
  description: "Audit events will appear here when actions are performed"
207
224
  })
208
225
  }),
209
- N > S && /* @__PURE__ */ y(o, {
210
- page: g,
211
- totalPages: P,
212
- totalItems: N,
213
- pageSize: S,
214
- onPageChange: _
226
+ I > T && /* @__PURE__ */ b(s, {
227
+ page: _,
228
+ totalPages: L,
229
+ totalItems: I,
230
+ pageSize: T,
231
+ onPageChange: v
215
232
  })
216
233
  ]
217
234
  })
@@ -219,6 +236,6 @@ function T() {
219
236
  });
220
237
  }
221
238
  //#endregion
222
- export { T as AuditLogsPage };
239
+ export { O as AuditLogsPage };
223
240
 
224
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 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 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,\n permission change, and data action. Use this page to investigate a security incident,\n answer a 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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"}