@alepha/ui 0.19.0 → 0.19.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 (151) hide show
  1. package/dist/admin/{AdminApiKeys-Bt1PjO6o.js → AdminApiKeys-C2ze85eD.js} +2 -2
  2. package/dist/admin/{AdminApiKeys-Bt1PjO6o.js.map → AdminApiKeys-C2ze85eD.js.map} +1 -1
  3. package/dist/admin/{AdminAudits-C7c1CN4c.js → AdminAudits-BIj81e4k.js} +2 -2
  4. package/dist/admin/{AdminAudits-C7c1CN4c.js.map → AdminAudits-BIj81e4k.js.map} +1 -1
  5. package/dist/admin/{AdminDashboard-C3RXpTp6.js → AdminDashboard-PMVzrwSu.js} +2 -2
  6. package/dist/admin/{AdminDashboard-C3RXpTp6.js.map → AdminDashboard-PMVzrwSu.js.map} +1 -1
  7. package/dist/admin/AdminFiles-Bq03BLt-.js +189 -0
  8. package/dist/admin/AdminFiles-Bq03BLt-.js.map +1 -0
  9. package/dist/admin/{AdminJobExecutions-D-G8RIlr.js → AdminJobs-D1_QGCDy.js} +400 -356
  10. package/dist/admin/AdminJobs-D1_QGCDy.js.map +1 -0
  11. package/dist/admin/{AdminLayout-BmZ9mtXh.js → AdminLayout-BNiwiw2D.js} +2 -2
  12. package/dist/admin/{AdminLayout-BmZ9mtXh.js.map → AdminLayout-BNiwiw2D.js.map} +1 -1
  13. package/dist/admin/{AdminNotifications-DHdzksww.js → AdminNotifications-DSKQtUfn.js} +84 -122
  14. package/dist/admin/AdminNotifications-DSKQtUfn.js.map +1 -0
  15. package/dist/admin/{AdminParameters-CyZQSXnN.js → AdminParameters-CoB7EhyM.js} +2 -2
  16. package/dist/admin/{AdminParameters-CyZQSXnN.js.map → AdminParameters-CoB7EhyM.js.map} +1 -1
  17. package/dist/admin/{AdminSessions--xwELDSO.js → AdminSessions-DFbFcrJQ.js} +2 -2
  18. package/dist/admin/{AdminSessions--xwELDSO.js.map → AdminSessions-DFbFcrJQ.js.map} +1 -1
  19. package/dist/admin/{AdminUserLayout-DvBTG5gd.js → AdminUserLayout-fSfi3KMm.js} +3 -3
  20. package/dist/admin/{AdminUserLayout-DvBTG5gd.js.map → AdminUserLayout-fSfi3KMm.js.map} +1 -1
  21. package/dist/admin/{AdminUserProfile-CzsPBl6Z.js → AdminUserProfile-_C-h8vUK.js} +3 -3
  22. package/dist/admin/{AdminUserProfile-CzsPBl6Z.js.map → AdminUserProfile-_C-h8vUK.js.map} +1 -1
  23. package/dist/admin/{AdminUserSessions-C-aUnhVN.js → AdminUserSessions-KpJHIeQo.js} +2 -2
  24. package/dist/admin/{AdminUserSessions-C-aUnhVN.js.map → AdminUserSessions-KpJHIeQo.js.map} +1 -1
  25. package/dist/admin/{AdminUsers-BYwei5sj.js → AdminUsers-DcVrzdQP.js} +2 -2
  26. package/dist/admin/{AdminUsers-BYwei5sj.js.map → AdminUsers-DcVrzdQP.js.map} +1 -1
  27. package/dist/admin/{AuthLayout-CkPGLJku.js → AuthLayout-CazfLzcf.js} +2 -2
  28. package/dist/admin/{AuthLayout-CkPGLJku.js.map → AuthLayout-CazfLzcf.js.map} +1 -1
  29. package/dist/admin/{Login-DSBqNsZc.js → Login-CaMjUrDP.js} +2 -2
  30. package/dist/admin/{Login-DSBqNsZc.js.map → Login-CaMjUrDP.js.map} +1 -1
  31. package/dist/admin/{Profile-CDRjJo0P.js → Profile-Ca4fZX15.js} +2 -2
  32. package/dist/{auth/Profile-Cy93pNTw.js.map → admin/Profile-Ca4fZX15.js.map} +1 -1
  33. package/dist/admin/{Register-4QGFOnfh.js → Register-C5DyKWPO.js} +2 -2
  34. package/dist/{demo/Register-KKZwr_lL.js.map → admin/Register-C5DyKWPO.js.map} +1 -1
  35. package/dist/admin/{ResetPassword-Gxc9L_mY.js → ResetPassword-BA5sAgXo.js} +2 -2
  36. package/dist/{auth/ResetPassword-B61QPlQi.js.map → admin/ResetPassword-BA5sAgXo.js.map} +1 -1
  37. package/dist/admin/{VerifyEmail-D7G5NnaN.js → VerifyEmail-DKNXROj_.js} +2 -2
  38. package/dist/{auth/VerifyEmail-CqBJ11id.js.map → admin/VerifyEmail-DKNXROj_.js.map} +1 -1
  39. package/dist/admin/{adminUserAtom-DCi4wf-v.js → adminUserAtom-BLNc7XbT.js} +1 -1
  40. package/dist/admin/{adminUserAtom-DCi4wf-v.js.map → adminUserAtom-BLNc7XbT.js.map} +1 -1
  41. package/dist/admin/{core-D1AbU50V.js → core-CJCEx18C.js} +111 -4
  42. package/dist/admin/core-CJCEx18C.js.map +1 -0
  43. package/dist/admin/index.d.ts +21 -3
  44. package/dist/admin/index.d.ts.map +1 -1
  45. package/dist/admin/index.js +29 -58
  46. package/dist/admin/index.js.map +1 -1
  47. package/dist/auth/{AuthLayout-CfRKcTqP.js → AuthLayout-vXPcCVzp.js} +2 -2
  48. package/dist/auth/{AuthLayout-CfRKcTqP.js.map → AuthLayout-vXPcCVzp.js.map} +1 -1
  49. package/dist/auth/{Login-DJyweoPS.js → Login-Dg08QR20.js} +2 -2
  50. package/dist/{demo/Login-CqG1iJbn.js.map → auth/Login-Dg08QR20.js.map} +1 -1
  51. package/dist/{demo/Profile-C0ojJCaG.js → auth/Profile-Bb5O1yeh.js} +2 -2
  52. package/dist/{admin/Profile-CDRjJo0P.js.map → auth/Profile-Bb5O1yeh.js.map} +1 -1
  53. package/dist/auth/{Register-CSqzzitW.js → Register-B2AN71NC.js} +2 -2
  54. package/dist/auth/{Register-CSqzzitW.js.map → Register-B2AN71NC.js.map} +1 -1
  55. package/dist/{demo/ResetPassword-DMrLFEtr.js → auth/ResetPassword-BLxwzbDj.js} +2 -2
  56. package/dist/{admin/ResetPassword-Gxc9L_mY.js.map → auth/ResetPassword-BLxwzbDj.js.map} +1 -1
  57. package/dist/auth/{VerifyEmail-CqBJ11id.js → VerifyEmail-CSDOk3Zm.js} +2 -2
  58. package/dist/{demo/VerifyEmail-BFCAFz6T.js.map → auth/VerifyEmail-CSDOk3Zm.js.map} +1 -1
  59. package/dist/auth/{core-C6D3pazL.js → core-DuGkjPiU.js} +2 -1
  60. package/dist/auth/core-DuGkjPiU.js.map +1 -0
  61. package/dist/auth/index.d.ts +20 -0
  62. package/dist/auth/index.d.ts.map +1 -1
  63. package/dist/auth/index.js +11 -11
  64. package/dist/core/index.d.ts +69 -17
  65. package/dist/core/index.d.ts.map +1 -1
  66. package/dist/core/index.js +110 -8
  67. package/dist/core/index.js.map +1 -1
  68. package/dist/demo/{AuthLayout-Dq5tSLSc.js → AuthLayout-DPsOOG4u.js} +2 -2
  69. package/dist/demo/{AuthLayout-Dq5tSLSc.js.map → AuthLayout-DPsOOG4u.js.map} +1 -1
  70. package/dist/demo/{DemoButton-_Ws2w-J0.js → DemoButton-wzcqGk4u.js} +3 -3
  71. package/dist/demo/{DemoButton-_Ws2w-J0.js.map → DemoButton-wzcqGk4u.js.map} +1 -1
  72. package/dist/demo/{DemoControlSelect-ChP4ZOpQ.js → DemoControlSelect-CMWvQ6Gm.js} +3 -3
  73. package/dist/demo/{DemoControlSelect-ChP4ZOpQ.js.map → DemoControlSelect-CMWvQ6Gm.js.map} +1 -1
  74. package/dist/demo/{DemoDataTable-Hwf_UUni.js → DemoDataTable-CHsAP3e2.js} +3 -3
  75. package/dist/demo/{DemoDataTable-Hwf_UUni.js.map → DemoDataTable-CHsAP3e2.js.map} +1 -1
  76. package/dist/demo/{DemoDialog-B01OMVRd.js → DemoDialog-Co2IePxX.js} +2 -2
  77. package/dist/demo/{DemoDialog-B01OMVRd.js.map → DemoDialog-Co2IePxX.js.map} +1 -1
  78. package/dist/demo/{DemoFlex-870PEl0V.js → DemoFlex-OEwQt5do.js} +3 -3
  79. package/dist/demo/{DemoFlex-870PEl0V.js.map → DemoFlex-OEwQt5do.js.map} +1 -1
  80. package/dist/demo/DemoHeading-Db-XkQIK.js +69 -0
  81. package/dist/demo/DemoHeading-Db-XkQIK.js.map +1 -0
  82. package/dist/demo/{DemoHome-DRbL2eGf.js → DemoHome-Cyp29ygy.js} +2 -2
  83. package/dist/demo/{DemoHome-DRbL2eGf.js.map → DemoHome-Cyp29ygy.js.map} +1 -1
  84. package/dist/demo/{DemoJsonViewer-DoABiqBW.js → DemoJsonViewer-DXtCeMzH.js} +3 -3
  85. package/dist/demo/{DemoJsonViewer-DoABiqBW.js.map → DemoJsonViewer-DXtCeMzH.js.map} +1 -1
  86. package/dist/demo/{DemoLayout-CN_PDCX2.js → DemoLayout-hh9VmZQP.js} +2 -2
  87. package/dist/demo/{DemoLayout-CN_PDCX2.js.map → DemoLayout-hh9VmZQP.js.map} +1 -1
  88. package/dist/demo/{DemoLogin-B5x-ug3Q.js → DemoLogin-DX7mnmkh.js} +13 -8
  89. package/dist/demo/{DemoLogin-B5x-ug3Q.js.map → DemoLogin-DX7mnmkh.js.map} +1 -1
  90. package/dist/demo/{DemoRegister-Q6sg2xuV.js → DemoRegister-DVcZl04m.js} +13 -8
  91. package/dist/demo/{DemoRegister-Q6sg2xuV.js.map → DemoRegister-DVcZl04m.js.map} +1 -1
  92. package/dist/demo/{DemoResetPassword-DrqZfmEw.js → DemoResetPassword-CPENlZH5.js} +13 -8
  93. package/dist/demo/{DemoResetPassword-DrqZfmEw.js.map → DemoResetPassword-CPENlZH5.js.map} +1 -1
  94. package/dist/demo/{DemoSidebar-CfKS6w1o.js → DemoSidebar-CGu7DZeM.js} +3 -3
  95. package/dist/demo/{DemoSidebar-CfKS6w1o.js.map → DemoSidebar-CGu7DZeM.js.map} +1 -1
  96. package/dist/demo/{DemoText-pT6Gi5b5.js → DemoText-DYUJ7bY_.js} +3 -3
  97. package/dist/demo/{DemoText-pT6Gi5b5.js.map → DemoText-DYUJ7bY_.js.map} +1 -1
  98. package/dist/demo/{DemoToast-I13NBzQQ.js → DemoToast-CgdnZNvx.js} +2 -2
  99. package/dist/demo/{DemoToast-I13NBzQQ.js.map → DemoToast-CgdnZNvx.js.map} +1 -1
  100. package/dist/demo/{DemoTypeForm-BqzcrtvN.js → DemoTypeForm-Pims-cGa.js} +3 -3
  101. package/dist/demo/{DemoTypeForm-BqzcrtvN.js.map → DemoTypeForm-Pims-cGa.js.map} +1 -1
  102. package/dist/demo/{DemoVerifyEmail-HwD8xfQw.js → DemoVerifyEmail-C7B3xxch.js} +8 -8
  103. package/dist/demo/{DemoVerifyEmail-HwD8xfQw.js.map → DemoVerifyEmail-C7B3xxch.js.map} +1 -1
  104. package/dist/demo/{Login-CqG1iJbn.js → Login-pwMF4TUj.js} +2 -2
  105. package/dist/{auth/Login-DJyweoPS.js.map → demo/Login-pwMF4TUj.js.map} +1 -1
  106. package/dist/{auth/Profile-Cy93pNTw.js → demo/Profile-BliZapZS.js} +2 -2
  107. package/dist/demo/{Profile-C0ojJCaG.js.map → Profile-BliZapZS.js.map} +1 -1
  108. package/dist/demo/{Register-KKZwr_lL.js → Register-CiwAT7Hy.js} +2 -2
  109. package/dist/{admin/Register-4QGFOnfh.js.map → demo/Register-CiwAT7Hy.js.map} +1 -1
  110. package/dist/{auth/ResetPassword-B61QPlQi.js → demo/ResetPassword-l9Vg4JE-.js} +2 -2
  111. package/dist/demo/{ResetPassword-DMrLFEtr.js.map → ResetPassword-l9Vg4JE-.js.map} +1 -1
  112. package/dist/demo/{Showcase-D49Wud2v.js → Showcase-CX6bDgwe.js} +2 -2
  113. package/dist/demo/{Showcase-D49Wud2v.js.map → Showcase-CX6bDgwe.js.map} +1 -1
  114. package/dist/demo/{VerifyEmail-BFCAFz6T.js → VerifyEmail-CAB-OS7i.js} +2 -2
  115. package/dist/{admin/VerifyEmail-D7G5NnaN.js.map → demo/VerifyEmail-CAB-OS7i.js.map} +1 -1
  116. package/dist/demo/{auth-D9qTZzCa.js → auth-uegJAdKu.js} +8 -8
  117. package/dist/demo/{auth-D9qTZzCa.js.map → auth-uegJAdKu.js.map} +1 -1
  118. package/dist/demo/{core-DRtQklr3.js → core-B4LVHzPn.js} +111 -9
  119. package/dist/demo/core-B4LVHzPn.js.map +1 -0
  120. package/dist/demo/index.js +19 -19
  121. package/dist/demo/index.js.map +1 -1
  122. package/package.json +6 -9
  123. package/src/admin/AdminRouter.tsx +5 -37
  124. package/src/admin/components/files/AdminFiles.tsx +123 -1
  125. package/src/admin/components/jobs/{AdminJobExecutions.tsx → AdminJobs.tsx} +450 -317
  126. package/src/admin/components/notifications/AdminNotifications.tsx +11 -25
  127. package/src/core/components/Section.tsx +109 -0
  128. package/src/core/components/SectionHeader.tsx +106 -0
  129. package/src/core/index.ts +4 -1
  130. package/src/core/table/components/DataTable.tsx +5 -1
  131. package/src/demo/DemoRouter.ts +1 -1
  132. package/src/demo/components/auth/DemoLogin.tsx +5 -0
  133. package/src/demo/components/auth/DemoRegister.tsx +5 -0
  134. package/src/demo/components/auth/DemoResetPassword.tsx +5 -0
  135. package/src/demo/components/core/DemoHeading.tsx +56 -3
  136. package/dist/admin/AdminFiles-31ivR6Wq.js +0 -110
  137. package/dist/admin/AdminFiles-31ivR6Wq.js.map +0 -1
  138. package/dist/admin/AdminJobDashboard-BABLe7hL.js +0 -402
  139. package/dist/admin/AdminJobDashboard-BABLe7hL.js.map +0 -1
  140. package/dist/admin/AdminJobExecutions-D-G8RIlr.js.map +0 -1
  141. package/dist/admin/AdminJobRegistry-oIS3K9NX.js +0 -269
  142. package/dist/admin/AdminJobRegistry-oIS3K9NX.js.map +0 -1
  143. package/dist/admin/AdminNotifications-DHdzksww.js.map +0 -1
  144. package/dist/admin/core-D1AbU50V.js.map +0 -1
  145. package/dist/auth/core-C6D3pazL.js.map +0 -1
  146. package/dist/demo/DemoHeading-C1YR27fz.js +0 -17
  147. package/dist/demo/DemoHeading-C1YR27fz.js.map +0 -1
  148. package/dist/demo/core-DRtQklr3.js.map +0 -1
  149. package/src/admin/components/jobs/AdminJobDashboard.tsx +0 -380
  150. package/src/admin/components/jobs/AdminJobRegistry.tsx +0 -301
  151. package/src/core/components/Heading.tsx +0 -19
@@ -1,380 +0,0 @@
1
- import { ActionButton, Flex, StatCards, Text, useToast } from "@alepha/ui";
2
- import { AreaChart, BarChart, DonutChart } from "@mantine/charts";
3
- import { Paper, SegmentedControl, SimpleGrid, Table } from "@mantine/core";
4
- import {
5
- IconAlertTriangle,
6
- IconCircleCheck,
7
- IconPlayerPlay,
8
- IconRefresh,
9
- IconTerminal2,
10
- } from "@tabler/icons-react";
11
- import type {
12
- AdminJobController,
13
- JobActivityPoint,
14
- JobFailure,
15
- JobQueueDepth,
16
- JobStats,
17
- } from "alepha/api/jobs";
18
- import { useClient } from "alepha/react";
19
- import { useI18n } from "alepha/react/i18n";
20
- import { useCallback, useEffect, useState } from "react";
21
-
22
- // ─────────────────────────────────────────────────────────────────────────────
23
-
24
- const TIME_RANGES = [
25
- { label: "24h", value: "1" },
26
- { label: "7d", value: "7" },
27
- { label: "14d", value: "14" },
28
- { label: "30d", value: "30" },
29
- ];
30
-
31
- const formatDuration = (
32
- start: Date | string,
33
- end?: Date | string | null,
34
- ): string => {
35
- const startTime = new Date(start).getTime();
36
- const endTime = end ? new Date(end).getTime() : Date.now();
37
- const duration = endTime - startTime;
38
-
39
- if (duration < 1000) return `${duration}ms`;
40
- if (duration < 60000) return `${(duration / 1000).toFixed(1)}s`;
41
- if (duration < 3600000)
42
- return `${Math.floor(duration / 60000)}m ${Math.floor((duration % 60000) / 1000)}s`;
43
- return `${Math.floor(duration / 3600000)}h ${Math.floor((duration % 3600000) / 60000)}m`;
44
- };
45
-
46
- // ─────────────────────────────────────────────────────────────────────────────
47
-
48
- interface RecentExecution {
49
- id: string;
50
- jobName: string;
51
- status: string;
52
- startedAt?: string;
53
- completedAt?: string;
54
- }
55
-
56
- // ─────────────────────────────────────────────────────────────────────────────
57
-
58
- const AdminJobDashboard = () => {
59
- const client = useClient<AdminJobController>();
60
- const { l } = useI18n();
61
- const toast = useToast();
62
-
63
- const [days, setDays] = useState("7");
64
- const [stats, setStats] = useState<JobStats | null>(null);
65
- const [recent, setRecent] = useState<RecentExecution[]>([]);
66
- const [failures, setFailures] = useState<JobFailure[]>([]);
67
- const [activity, setActivity] = useState<JobActivityPoint[]>([]);
68
- const [queueDepth, setQueueDepth] = useState<JobQueueDepth[]>([]);
69
-
70
- const daysNum = Number(days);
71
-
72
- const loadData = useCallback(async () => {
73
- try {
74
- const [statsData, recentData, failureData, activityData, queueData] =
75
- await Promise.all([
76
- client.getJobStats({ query: { days: daysNum } }),
77
- client.findJobExecutions({ query: { sort: "-createdAt", size: 10 } }),
78
- client.getJobTopFailures({ query: { days: daysNum } }),
79
- client.getJobActivity({ query: { days: daysNum } }),
80
- client.getJobQueueDepth(),
81
- ]);
82
- setStats(statsData);
83
- setRecent(recentData.content as RecentExecution[]);
84
- setFailures(failureData);
85
- setActivity(activityData);
86
- setQueueDepth(queueData);
87
- } catch {
88
- toast.danger("Failed to load dashboard data");
89
- }
90
- }, [client, toast, daysNum]);
91
-
92
- useEffect(() => {
93
- loadData();
94
- }, [loadData]);
95
-
96
- // Prepare chart data
97
- const activityChartData = activity.map((point) => ({
98
- date: new Date(point.date).toLocaleDateString("en-US", {
99
- month: "short",
100
- day: "numeric",
101
- }),
102
- completed: point.completed,
103
- failed: point.failed,
104
- }));
105
-
106
- const queueChartData = queueDepth
107
- .filter(
108
- (q) => q.pending + q.running + q.scheduled + q.retrying + q.dead > 0,
109
- )
110
- .map((q) => ({
111
- job:
112
- q.jobName.length > 20
113
- ? `...${q.jobName.slice(q.jobName.length - 18)}`
114
- : q.jobName,
115
- pending: q.pending,
116
- running: q.running,
117
- scheduled: q.scheduled,
118
- retrying: q.retrying,
119
- dead: q.dead,
120
- }));
121
-
122
- const statusDonutData = stats
123
- ? [
124
- { name: "Running", value: stats.running, color: "blue" },
125
- { name: "Pending", value: stats.pending, color: "gray" },
126
- { name: "Scheduled", value: stats.scheduled, color: "violet" },
127
- { name: "Retrying", value: stats.retrying, color: "yellow" },
128
- { name: "Dead", value: stats.dead, color: "red" },
129
- ].filter((d) => d.value > 0)
130
- : [];
131
-
132
- const rangeLabel =
133
- TIME_RANGES.find((r) => r.value === days)?.label ?? `${days}d`;
134
-
135
- return (
136
- <Flex flex={1} direction="column" gap="md" p="md">
137
- <Flex justify="space-between" align="center">
138
- <Text size="lg" fw={600}>
139
- Jobs Dashboard
140
- </Text>
141
- <Flex
142
- gap="sm"
143
- align="center"
144
- py="xs"
145
- px="sm"
146
- style={{
147
- backgroundColor: "var(--mantine-color-body)",
148
- borderRadius: "var(--mantine-radius-md)",
149
- border: "1px solid var(--mantine-color-default-border)",
150
- }}
151
- >
152
- <SegmentedControl
153
- size="xs"
154
- data={TIME_RANGES}
155
- value={days}
156
- onChange={setDays}
157
- />
158
- <ActionButton
159
- tooltip="Refresh"
160
- variant="minimal"
161
- size="sm"
162
- icon={IconRefresh}
163
- onClick={loadData}
164
- />
165
- </Flex>
166
- </Flex>
167
-
168
- {/* Stats Cards */}
169
- {stats && (
170
- <StatCards
171
- items={[
172
- {
173
- label: "Registered",
174
- value: stats.registered,
175
- icon: IconTerminal2,
176
- },
177
- {
178
- label: "Running",
179
- value: stats.running,
180
- icon: IconPlayerPlay,
181
- },
182
- {
183
- label: `Completed (${rangeLabel})`,
184
- value: stats.completed,
185
- icon: IconCircleCheck,
186
- },
187
- {
188
- label: `Failed (${rangeLabel})`,
189
- value: stats.failed,
190
- icon: IconAlertTriangle,
191
- },
192
- ]}
193
- />
194
- )}
195
-
196
- {/* Charts Row */}
197
- <SimpleGrid cols={{ base: 1, md: 2 }} spacing="md">
198
- {/* Activity Timeline */}
199
- <Paper p="md" radius="md" withBorder>
200
- <Text size="sm" fw={600} mb="sm">
201
- Activity ({rangeLabel})
202
- </Text>
203
- {activityChartData.length > 0 ? (
204
- <AreaChart
205
- h={220}
206
- data={activityChartData}
207
- dataKey="date"
208
- series={[
209
- { name: "completed", label: "Completed", color: "teal.6" },
210
- { name: "failed", label: "Failed", color: "red.6" },
211
- ]}
212
- curveType="monotone"
213
- withGradient
214
- withTooltip
215
- withDots={false}
216
- />
217
- ) : (
218
- <Flex h={220} align="center" justify="center">
219
- <Text size="sm" c="dimmed">
220
- No activity data
221
- </Text>
222
- </Flex>
223
- )}
224
- </Paper>
225
-
226
- {/* Current Status Donut */}
227
- <Paper p="md" radius="md" withBorder>
228
- <Text size="sm" fw={600} mb="sm">
229
- Active Executions
230
- </Text>
231
- {statusDonutData.length > 0 ? (
232
- <DonutChart
233
- h={220}
234
- data={statusDonutData}
235
- withTooltip
236
- tooltipDataSource="segment"
237
- chartLabel={String(
238
- statusDonutData.reduce((sum, d) => sum + d.value, 0),
239
- )}
240
- />
241
- ) : (
242
- <Flex h={220} align="center" justify="center">
243
- <Text size="sm" c="dimmed">
244
- No active executions
245
- </Text>
246
- </Flex>
247
- )}
248
- </Paper>
249
- </SimpleGrid>
250
-
251
- {/* Queue Depth Chart */}
252
- {queueChartData.length > 0 && (
253
- <Paper p="md" radius="md" withBorder>
254
- <Text size="sm" fw={600} mb="sm">
255
- Queue Depth by Job
256
- </Text>
257
- <BarChart
258
- h={200}
259
- data={queueChartData}
260
- dataKey="job"
261
- type="stacked"
262
- series={[
263
- { name: "running", label: "Running", color: "blue.6" },
264
- { name: "pending", label: "Pending", color: "gray.5" },
265
- { name: "scheduled", label: "Scheduled", color: "violet.5" },
266
- { name: "retrying", label: "Retrying", color: "yellow.5" },
267
- { name: "dead", label: "Dead", color: "red.6" },
268
- ]}
269
- withTooltip
270
- withLegend
271
- />
272
- </Paper>
273
- )}
274
-
275
- {/* Recent Activity + Top Failures side by side */}
276
- <SimpleGrid cols={{ base: 1, md: 2 }} spacing="md">
277
- {/* Recent Activity */}
278
- <Paper p="md" radius="md" withBorder>
279
- <Text size="sm" fw={600} mb="sm">
280
- Recent Executions
281
- </Text>
282
- <Table highlightOnHover>
283
- <Table.Thead>
284
- <Table.Tr>
285
- <Table.Th>Job</Table.Th>
286
- <Table.Th>Status</Table.Th>
287
- <Table.Th>Duration</Table.Th>
288
- </Table.Tr>
289
- </Table.Thead>
290
- <Table.Tbody>
291
- {recent.map((exec) => (
292
- <Table.Tr key={exec.id}>
293
- <Table.Td>
294
- <Text size="xs" fw={500} ff="monospace" lineClamp={1}>
295
- {exec.jobName}
296
- </Text>
297
- </Table.Td>
298
- <Table.Td>
299
- <Text size="xs" ff="monospace">
300
- {exec.status}
301
- </Text>
302
- </Table.Td>
303
- <Table.Td>
304
- <Text size="xs" c="dimmed" ff="monospace">
305
- {exec.startedAt &&
306
- (exec.completedAt || exec.status === "running")
307
- ? formatDuration(exec.startedAt, exec.completedAt)
308
- : "\u2014"}
309
- </Text>
310
- </Table.Td>
311
- </Table.Tr>
312
- ))}
313
- {recent.length === 0 && (
314
- <Table.Tr>
315
- <Table.Td colSpan={3}>
316
- <Text size="sm" c="dimmed" ta="center">
317
- No recent executions
318
- </Text>
319
- </Table.Td>
320
- </Table.Tr>
321
- )}
322
- </Table.Tbody>
323
- </Table>
324
- </Paper>
325
-
326
- {/* Top Failures */}
327
- <Paper p="md" radius="md" withBorder>
328
- <Text size="sm" fw={600} mb="sm">
329
- Top Failures ({rangeLabel})
330
- </Text>
331
- {failures.length > 0 ? (
332
- <Table>
333
- <Table.Thead>
334
- <Table.Tr>
335
- <Table.Th>Job</Table.Th>
336
- <Table.Th>Count</Table.Th>
337
- <Table.Th>Last Error</Table.Th>
338
- </Table.Tr>
339
- </Table.Thead>
340
- <Table.Tbody>
341
- {failures.map((f) => (
342
- <Table.Tr key={f.jobName}>
343
- <Table.Td>
344
- <Text size="xs" fw={500} ff="monospace" lineClamp={1}>
345
- {f.jobName}
346
- </Text>
347
- </Table.Td>
348
- <Table.Td>
349
- <Text size="xs" fw={600} ff="monospace">
350
- {f.failures}
351
- </Text>
352
- </Table.Td>
353
- <Table.Td>
354
- <Text
355
- size="xs"
356
- c="dimmed"
357
- lineClamp={1}
358
- style={{ maxWidth: 200 }}
359
- >
360
- {f.lastError ?? "\u2014"}
361
- </Text>
362
- </Table.Td>
363
- </Table.Tr>
364
- ))}
365
- </Table.Tbody>
366
- </Table>
367
- ) : (
368
- <Flex h={100} align="center" justify="center">
369
- <Text size="sm" c="dimmed">
370
- No failures in the last {rangeLabel}
371
- </Text>
372
- </Flex>
373
- )}
374
- </Paper>
375
- </SimpleGrid>
376
- </Flex>
377
- );
378
- };
379
-
380
- export default AdminJobDashboard;
@@ -1,301 +0,0 @@
1
- import type { DetailListItem } from "@alepha/ui";
2
- import {
3
- DataTable,
4
- DetailList,
5
- Flex,
6
- Text,
7
- useDialog,
8
- useToast,
9
- } from "@alepha/ui";
10
- import { Badge } from "@mantine/core";
11
- import {
12
- IconCircleCheck,
13
- IconCircleX,
14
- IconPlayerPlay,
15
- } from "@tabler/icons-react";
16
- import { t } from "alepha";
17
- import type {
18
- AdminJobController,
19
- JobCronInfo,
20
- JobFailure,
21
- JobQueueDepth,
22
- JobRegistration,
23
- } from "alepha/api/jobs";
24
- import { useClient } from "alepha/react";
25
- import { useI18n } from "alepha/react/i18n";
26
- import { useCallback, useEffect, useState } from "react";
27
-
28
- // ─────────────────────────────────────────────────────────────────────────────
29
-
30
- const registryFilters = t.object({
31
- type: t.optional(t.enum(["cron", "push", "both"])),
32
- });
33
-
34
- // ─────────────────────────────────────────────────────────────────────────────
35
-
36
- const AdminJobRegistry = () => {
37
- const client = useClient<AdminJobController>();
38
- const { l } = useI18n();
39
- const toast = useToast();
40
- const dialog = useDialog();
41
- const [refreshKey, setRefreshKey] = useState(0);
42
-
43
- // Extra data for enriched panels
44
- const [cronMap, setCronMap] = useState<Map<string, JobCronInfo>>(new Map());
45
- const [queueMap, setQueueMap] = useState<Map<string, JobQueueDepth>>(
46
- new Map(),
47
- );
48
- const [failureMap, setFailureMap] = useState<Map<string, JobFailure>>(
49
- new Map(),
50
- );
51
-
52
- const loadExtraData = useCallback(async () => {
53
- try {
54
- const [cronData, queueData, failureData] = await Promise.all([
55
- client.getCronJobs(),
56
- client.getJobQueueDepth(),
57
- client.getJobTopFailures(),
58
- ]);
59
- setCronMap(new Map(cronData.map((c) => [c.name, c])));
60
- setQueueMap(new Map(queueData.map((q) => [q.jobName, q])));
61
- setFailureMap(new Map(failureData.map((f) => [f.jobName, f])));
62
- } catch {
63
- // non-critical
64
- }
65
- }, [client]);
66
-
67
- useEffect(() => {
68
- loadExtraData();
69
- }, [loadExtraData, refreshKey]);
70
-
71
- const handleTriggerJob = useCallback(
72
- async (name: string) => {
73
- const confirmed = await dialog.confirm({
74
- title: "Trigger Job",
75
- message: `Are you sure you want to trigger "${name}" manually?`,
76
- confirmLabel: "Trigger",
77
- confirmColor: "blue",
78
- });
79
-
80
- if (!confirmed) return;
81
-
82
- return client.triggerJob({ body: { name } }).then(() => {
83
- toast.success(`Job "${name}" triggered`);
84
- setRefreshKey((k) => k + 1);
85
- });
86
- },
87
- [client, dialog, toast],
88
- );
89
-
90
- return (
91
- <Flex p="md" flex={1} direction="column" gap="md">
92
- <DataTable<JobRegistration, typeof registryFilters>
93
- key={`registry-${refreshKey}`}
94
- submitOnInit
95
- typeFormProps={{
96
- skipSubmitButton: true,
97
- columns: 1,
98
- }}
99
- tableProps={{
100
- horizontalSpacing: "sm",
101
- verticalSpacing: "sm",
102
- }}
103
- onFilterChange={(_key, _value, form) => form.submit()}
104
- filters={registryFilters}
105
- items={async (filters) => {
106
- const items = await client.getJobRegistry();
107
- const filtered = filters.type
108
- ? items.filter((i) => i.type === filters.type)
109
- : items;
110
- return { content: filtered };
111
- }}
112
- columns={{
113
- name: {
114
- label: "Name",
115
- value: (item) => (
116
- <Text size="sm" fw={500} ff="monospace">
117
- {item.name}
118
- </Text>
119
- ),
120
- },
121
- type: {
122
- label: "Type",
123
- value: (item) => (
124
- <Badge size="sm" variant="default">
125
- {item.type}
126
- </Badge>
127
- ),
128
- },
129
- priority: {
130
- label: "Priority",
131
- value: (item) => (
132
- <Text size="sm" tt="capitalize">
133
- {item.priority}
134
- </Text>
135
- ),
136
- },
137
- concurrency: {
138
- label: "Concurrency",
139
- value: (item) => (
140
- <Text size="sm" ff="monospace">
141
- {item.concurrency}
142
- </Text>
143
- ),
144
- },
145
- queue: {
146
- label: "Queue",
147
- value: (item) => {
148
- const q = queueMap.get(item.name);
149
- if (
150
- !q ||
151
- q.pending + q.running + q.scheduled + q.retrying + q.dead === 0
152
- ) {
153
- return (
154
- <Text size="xs" c="dimmed">
155
-
156
- </Text>
157
- );
158
- }
159
- return (
160
- <Flex gap={4}>
161
- {q.running > 0 && (
162
- <Badge size="xs" variant="default">
163
- {q.running} run
164
- </Badge>
165
- )}
166
- {q.pending > 0 && (
167
- <Badge size="xs" variant="default">
168
- {q.pending} pen
169
- </Badge>
170
- )}
171
- {q.retrying > 0 && (
172
- <Badge size="xs" variant="default">
173
- {q.retrying} retry
174
- </Badge>
175
- )}
176
- {q.dead > 0 && (
177
- <Badge size="xs" variant="default">
178
- {q.dead} dead
179
- </Badge>
180
- )}
181
- </Flex>
182
- );
183
- },
184
- },
185
- }}
186
- rowActions={(item) => [
187
- {
188
- label: "Trigger",
189
- color: "blue",
190
- icon: IconPlayerPlay,
191
- onClick: () => handleTriggerJob(item.name),
192
- },
193
- ]}
194
- panel={(item) => {
195
- const cron = cronMap.get(item.name);
196
- const failure = failureMap.get(item.name);
197
-
198
- const detailItems: DetailListItem[] = [
199
- {
200
- label: "Cron",
201
- value: item.cron ? (
202
- <Text size="sm" ff="monospace">
203
- {item.cron}
204
- </Text>
205
- ) : undefined,
206
- hidden: !item.cron,
207
- },
208
- {
209
- label: "Timeout",
210
- value: item.timeout,
211
- hidden: !item.timeout,
212
- },
213
- {
214
- label: "Retry",
215
- value: item.retry
216
- ? `${item.retry.retries}x${item.retry.hasBackoff ? " (backoff)" : ""}`
217
- : undefined,
218
- hidden: !item.retry,
219
- },
220
- {
221
- label: "Batch",
222
- value: item.batch
223
- ? `${item.batch.size} / ${item.batch.window}`
224
- : undefined,
225
- hidden: !item.batch,
226
- },
227
- {
228
- label: "Schema",
229
- value: item.hasSchema ? "Yes" : "No",
230
- },
231
- ];
232
-
233
- return (
234
- <Flex direction="column" gap="sm" p="sm">
235
- <DetailList items={detailItems} columns={3} />
236
-
237
- {/* Last cron execution */}
238
- {cron?.lastExecution && (
239
- <Flex gap="lg" wrap="wrap" align="center">
240
- <Text size="xs" c="dimmed" tt="uppercase" fw={600}>
241
- Last Run
242
- </Text>
243
- <Flex align="center" gap={4}>
244
- {cron.lastExecution.status === "completed" ? (
245
- <IconCircleCheck
246
- size={14}
247
- color="var(--mantine-color-dimmed)"
248
- />
249
- ) : (
250
- <IconCircleX
251
- size={14}
252
- color="var(--mantine-color-dimmed)"
253
- />
254
- )}
255
- <Text size="xs" tt="capitalize">
256
- {cron.lastExecution.status}
257
- </Text>
258
- </Flex>
259
- {cron.lastExecution.startedAt && (
260
- <Text size="xs" c="dimmed">
261
- {l(cron.lastExecution.startedAt, { date: "fromNow" })}
262
- </Text>
263
- )}
264
- {cron.lastExecution.error && (
265
- <Text size="xs" c="dimmed" lineClamp={1}>
266
- {cron.lastExecution.error}
267
- </Text>
268
- )}
269
- </Flex>
270
- )}
271
-
272
- {/* Failures */}
273
- {failure && (
274
- <Flex gap="lg" wrap="wrap" align="center">
275
- <Text size="xs" c="dimmed" tt="uppercase" fw={600}>
276
- Failures (7d)
277
- </Text>
278
- <Text size="xs" fw={500}>
279
- {failure.failures}
280
- </Text>
281
- {failure.lastError && (
282
- <Text
283
- size="xs"
284
- c="dimmed"
285
- lineClamp={1}
286
- style={{ maxWidth: 400 }}
287
- >
288
- {failure.lastError}
289
- </Text>
290
- )}
291
- </Flex>
292
- )}
293
- </Flex>
294
- );
295
- }}
296
- />
297
- </Flex>
298
- );
299
- };
300
-
301
- export default AdminJobRegistry;
@@ -1,19 +0,0 @@
1
- import type { ReactNode } from "react";
2
- import type { AlephaIntent } from "../interfaces/AlephaIntent.ts";
3
-
4
- export type HeadingProps = {
5
- title: string | ReactNode;
6
- description?: string | ReactNode;
7
- icon?: ReactNode;
8
- intent?: AlephaIntent;
9
- loading?: boolean;
10
- ellipsis?: boolean;
11
- fill?: boolean;
12
- tag?: string | ReactNode;
13
- };
14
-
15
- const Heading = (props: HeadingProps) => {
16
- return <h1>Heading</h1>;
17
- };
18
-
19
- export default Heading;