@hed-hog/core 0.0.186 → 0.0.191

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 (56) hide show
  1. package/hedhog/data/menu.yaml +20 -20
  2. package/hedhog/frontend/app/account/2fa/page.tsx.ejs +5 -0
  3. package/hedhog/frontend/app/account/accounts/page.tsx.ejs +5 -0
  4. package/hedhog/frontend/app/account/components/active-sessions.tsx.ejs +356 -0
  5. package/hedhog/frontend/app/account/components/change-email-form.tsx.ejs +379 -0
  6. package/hedhog/frontend/app/account/components/change-password-form.tsx.ejs +184 -0
  7. package/hedhog/frontend/app/account/components/connected-accounts.tsx.ejs +144 -0
  8. package/hedhog/frontend/app/account/components/email-request-dialog.tsx.ejs +96 -0
  9. package/hedhog/frontend/app/account/components/mfa-add-buttons.tsx.ejs +43 -0
  10. package/hedhog/frontend/app/account/components/mfa-method-card.tsx.ejs +115 -0
  11. package/hedhog/frontend/app/account/components/mfa-setup-dialog.tsx.ejs +236 -0
  12. package/hedhog/frontend/app/account/components/profile-form.tsx.ejs +209 -0
  13. package/hedhog/frontend/app/account/components/recovery-codes-dialog.tsx.ejs +192 -0
  14. package/hedhog/frontend/app/account/components/regenerate-codes-dialog.tsx.ejs +372 -0
  15. package/hedhog/frontend/app/account/components/remove-mfa-dialog.tsx.ejs +337 -0
  16. package/hedhog/frontend/app/account/components/two-factor-auth.tsx.ejs +393 -0
  17. package/hedhog/frontend/app/account/components/verify-before-add-dialog.tsx.ejs +332 -0
  18. package/hedhog/frontend/app/account/email/page.tsx.ejs +5 -0
  19. package/hedhog/frontend/app/account/hooks/use-mfa-methods.ts.ejs +27 -0
  20. package/hedhog/frontend/app/account/hooks/use-mfa-setup.ts.ejs +461 -0
  21. package/hedhog/frontend/app/account/layout.tsx.ejs +105 -0
  22. package/hedhog/frontend/app/account/lib/mfa-utils.tsx.ejs +37 -0
  23. package/hedhog/frontend/app/account/page.tsx.ejs +5 -0
  24. package/hedhog/frontend/app/account/password/page.tsx.ejs +5 -0
  25. package/hedhog/frontend/app/account/profile/page.tsx.ejs +5 -0
  26. package/hedhog/frontend/app/account/sessions/page.tsx.ejs +5 -0
  27. package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +490 -0
  28. package/hedhog/frontend/app/configurations/[slug]/page.tsx.ejs +62 -0
  29. package/hedhog/frontend/app/configurations/layout.tsx.ejs +316 -0
  30. package/hedhog/frontend/app/configurations/page.tsx.ejs +35 -0
  31. package/hedhog/frontend/app/dashboard/[slug]/dashboard-content.tsx.ejs +351 -0
  32. package/hedhog/frontend/app/dashboard/[slug]/page.tsx.ejs +11 -0
  33. package/hedhog/frontend/app/dashboard/[slug]/types.ts.ejs +62 -0
  34. package/hedhog/frontend/app/dashboard/[slug]/widget-renderer.tsx.ejs +45 -0
  35. package/hedhog/frontend/app/dashboard/dashboard.css.ejs +196 -0
  36. package/hedhog/frontend/app/dashboard/management/page.tsx.ejs +63 -0
  37. package/hedhog/frontend/app/dashboard/management/tabs/component-roles-tab.tsx.ejs +516 -0
  38. package/hedhog/frontend/app/dashboard/management/tabs/components-tab.tsx.ejs +753 -0
  39. package/hedhog/frontend/app/dashboard/management/tabs/dashboard-roles-tab.tsx.ejs +516 -0
  40. package/hedhog/frontend/app/dashboard/management/tabs/dashboards-tab.tsx.ejs +489 -0
  41. package/hedhog/frontend/app/dashboard/management/tabs/items-tab.tsx.ejs +621 -0
  42. package/hedhog/frontend/app/dashboard/page.tsx.ejs +14 -0
  43. package/hedhog/frontend/app/mail/log/page.tsx.ejs +312 -0
  44. package/hedhog/frontend/app/mail/template/page.tsx.ejs +1177 -0
  45. package/hedhog/frontend/app/preferences/page.tsx.ejs +448 -0
  46. package/hedhog/frontend/app/roles/menus.tsx.ejs +504 -0
  47. package/hedhog/frontend/app/roles/page.tsx.ejs +814 -0
  48. package/hedhog/frontend/app/roles/routes.tsx.ejs +397 -0
  49. package/hedhog/frontend/app/roles/users.tsx.ejs +306 -0
  50. package/hedhog/frontend/app/users/active-session.tsx.ejs +159 -0
  51. package/hedhog/frontend/app/users/identifiers.tsx.ejs +279 -0
  52. package/hedhog/frontend/app/users/page.tsx.ejs +1257 -0
  53. package/hedhog/frontend/app/users/permissions.tsx.ejs +155 -0
  54. package/hedhog/frontend/messages/en.json +1080 -0
  55. package/hedhog/frontend/messages/pt.json +1135 -0
  56. package/package.json +3 -3
@@ -1,10 +1,10 @@
1
- - url: /dashboard
1
+ - url: /
2
2
  order: -1
3
3
  icon: house
4
4
  name:
5
5
  en: Dashboard
6
6
  pt: Dashboard
7
- slug: /dashboard
7
+ slug: /
8
8
  relations:
9
9
  role:
10
10
  - where:
@@ -14,88 +14,88 @@
14
14
  name:
15
15
  en: Configuration
16
16
  pt: Configurações
17
- slug: /management
17
+ slug: /core/management
18
18
  relations:
19
19
  role:
20
20
  - where:
21
21
  slug: admin
22
22
  - menu_id:
23
23
  where:
24
- slug: /management
25
- url: /dashboard/management
24
+ slug: /core/management
25
+ url: /core/dashboard/management
26
26
  order: 0
27
27
  icon: settings
28
28
  name:
29
29
  en: Dashboard
30
30
  pt: Dashboard
31
- slug: /dashboard/management
31
+ slug: /core/dashboard/management
32
32
  relations:
33
33
  role:
34
34
  - where:
35
35
  slug: admin
36
36
  - menu_id:
37
37
  where:
38
- slug: /management
39
- url: /roles
38
+ slug: /core/management
39
+ url: /core/roles
40
40
  order: 1
41
41
  icon: shield-check
42
42
  name:
43
43
  en: Roles
44
44
  pt: Cargos
45
- slug: /roles
45
+ slug: /core/roles
46
46
  relations:
47
47
  role:
48
48
  - where:
49
49
  slug: admin
50
50
  - menu_id:
51
51
  where:
52
- slug: /management
53
- url: /configurations
52
+ slug: /core/management
53
+ url: /core/configurations
54
54
  order: 2
55
55
  icon: settings-2
56
56
  name:
57
57
  en: Configurations
58
58
  pt: Configurações
59
- slug: /configurations
59
+ slug: /core/configurations
60
60
  relations:
61
61
  role:
62
62
  - where:
63
63
  slug: admin
64
64
  - menu_id:
65
65
  where:
66
- slug: /management
67
- url: /mail/template
66
+ slug: /core/management
67
+ url: /core/mail/template
68
68
  order: 3
69
69
  icon: mail
70
70
  name:
71
71
  en: Mail Template
72
72
  pt: Templates de e-mail
73
- slug: /mail-template
73
+ slug: /core/mail-template
74
74
  relations:
75
75
  role:
76
76
  - where:
77
77
  slug: admin
78
78
  - menu_id:
79
79
  where:
80
- slug: /management
81
- url: /mail/log
80
+ slug: /core/management
81
+ url: /core/mail/log
82
82
  order: 4
83
83
  icon: file-text
84
84
  name:
85
85
  en: Mail Log
86
86
  pt: E-mail enviados
87
- slug: /mail-log
87
+ slug: /core/mail-log
88
88
  relations:
89
89
  role:
90
90
  - where:
91
91
  slug: admin
92
- - url: /users
92
+ - url: /core/users
93
93
  order: 0
94
94
  icon: users
95
95
  name:
96
96
  en: Users
97
97
  pt: Usuários
98
- slug: /users
98
+ slug: /core/users
99
99
  relations:
100
100
  role:
101
101
  - where:
@@ -0,0 +1,5 @@
1
+ import { TwoFactorAuth } from '../components/two-factor-auth';
2
+
3
+ export default function TwoFactorPage() {
4
+ return <TwoFactorAuth />;
5
+ }
@@ -0,0 +1,5 @@
1
+ import { ConnectedAccounts } from '../components/connected-accounts';
2
+
3
+ export default function AccountsPage() {
4
+ return <ConnectedAccounts />;
5
+ }
@@ -0,0 +1,356 @@
1
+ 'use client';
2
+
3
+ import { PaginationFooter } from '@/components/entity-list/pagination-footer';
4
+ import {
5
+ AlertDialog,
6
+ AlertDialogAction,
7
+ AlertDialogCancel,
8
+ AlertDialogContent,
9
+ AlertDialogDescription,
10
+ AlertDialogFooter,
11
+ AlertDialogHeader,
12
+ AlertDialogTitle,
13
+ } from '@/components/ui/alert-dialog';
14
+ import { Badge } from '@/components/ui/badge';
15
+ import { Button } from '@/components/ui/button';
16
+ import {
17
+ Card,
18
+ CardContent,
19
+ CardDescription,
20
+ CardHeader,
21
+ CardTitle,
22
+ } from '@/components/ui/card';
23
+ import {
24
+ Table,
25
+ TableBody,
26
+ TableCell,
27
+ TableHead,
28
+ TableHeader,
29
+ TableRow,
30
+ } from '@/components/ui/table';
31
+ import { usePagination } from '@/hooks/use-pagination';
32
+ import { formatDate, formatDateTime } from '@/lib/format-date';
33
+ import { parseBrowser, parseDeviceType, parseOS } from '@/lib/session';
34
+ import { UserSession } from '@hed-hog/api-types';
35
+ import { useApp } from '@hed-hog/next-app-provider';
36
+ import { Calendar, Clock, LogOut, MapPin } from 'lucide-react';
37
+ import { useTranslations } from 'next-intl';
38
+ import { useState } from 'react';
39
+ import { toast } from 'sonner';
40
+
41
+ export function ActiveSessions() {
42
+ const { request, logout, currentLocaleCode, getSettingValue } = useApp();
43
+ const t = useTranslations('core.ActiveSessions');
44
+ const [sessionToRevoke, setSessionToRevoke] = useState<number | null>(null);
45
+ const [revokeAllSessions, setRevokeAllSessions] = useState(false);
46
+ const [revokeAllOtherSessions, setRevokeAllOtherSessions] = useState(false);
47
+
48
+ const {
49
+ items: sessions,
50
+ refetch,
51
+ page,
52
+ setPage,
53
+ totalItems,
54
+ pageSize,
55
+ setPageSize,
56
+ } = usePagination({
57
+ url: `/sessions/user`,
58
+ });
59
+
60
+ const handleRevokeSession = async (sessionId: number) => {
61
+ try {
62
+ await request({ url: `/sessions/${sessionId}/revoke`, method: 'DELETE' });
63
+ await refetch();
64
+ setSessionToRevoke(null);
65
+ toast.success(t('revokeSuccess'));
66
+ } catch (error) {
67
+ toast.error(t('revokeFailure'));
68
+ }
69
+ };
70
+
71
+ const handleRevokeAllOtherSessions = async () => {
72
+ try {
73
+ await request({ url: '/sessions/revoke-all-other', method: 'DELETE' });
74
+ await refetch();
75
+ toast.success(t('revokeAllSuccess'));
76
+ } catch (error) {
77
+ toast.error(t('revokeAllFailure'));
78
+ }
79
+ };
80
+
81
+ const handleRevokeAllSessions = async () => {
82
+ try {
83
+ await request({ url: '/sessions/revoke-all', method: 'DELETE' });
84
+ await logout();
85
+ toast.success(t('revokeAllSuccess'));
86
+ } catch (error) {
87
+ toast.error(t('revokeAllFailure'));
88
+ }
89
+ };
90
+
91
+ return (
92
+ <>
93
+ <Card>
94
+ <CardHeader className="pb-4">
95
+ <div className="flex items-center justify-between">
96
+ <div>
97
+ <CardTitle>{t('title')}</CardTitle>
98
+ <CardDescription>{t('description')}</CardDescription>
99
+ </div>
100
+ {sessions.length > 1 && (
101
+ <div className="flex flex-row">
102
+ <Button
103
+ variant="outline"
104
+ size="sm"
105
+ onClick={() => setRevokeAllSessions(true)}
106
+ >
107
+ {t('revokeAllSessions')}
108
+ </Button>
109
+ <Button
110
+ variant="outline"
111
+ size="sm"
112
+ onClick={() => setRevokeAllOtherSessions(true)}
113
+ >
114
+ {t('revokeAllOtherSessions')}
115
+ </Button>
116
+ </div>
117
+ )}
118
+ </div>
119
+ </CardHeader>
120
+
121
+ <CardContent className="px-6 pb-6">
122
+ {sessions.length > 0 ? (
123
+ <>
124
+ <div className="rounded-md border">
125
+ <Table>
126
+ <TableHeader>
127
+ <TableRow>
128
+ <TableHead>{t('device')}</TableHead>
129
+ <TableHead>{t('browser')}</TableHead>
130
+ <TableHead>{t('location')}</TableHead>
131
+ <TableHead>{t('createdAt')}</TableHead>
132
+ <TableHead>{t('expiredAt')}</TableHead>
133
+ <TableHead className="text-right">
134
+ {t('action')}
135
+ </TableHead>
136
+ </TableRow>
137
+ </TableHeader>
138
+ <TableBody>
139
+ {sessions.map((session) => (
140
+ <SessionRow
141
+ key={session.id}
142
+ session={session}
143
+ onRequestRevoke={(id) => setSessionToRevoke(id)}
144
+ currentLocaleCode={currentLocaleCode}
145
+ getSettingValue={getSettingValue}
146
+ t={t}
147
+ />
148
+ ))}
149
+ </TableBody>
150
+ </Table>
151
+ </div>
152
+ <div className="mt-4">
153
+ <PaginationFooter
154
+ currentPage={page}
155
+ pageSize={pageSize}
156
+ totalItems={totalItems}
157
+ onPageChange={setPage}
158
+ onPageSizeChange={(newSize) => {
159
+ setPageSize(newSize);
160
+ setPage(1);
161
+ }}
162
+ pageSizeOptions={[10, 20, 30, 40, 50]}
163
+ />
164
+ </div>
165
+ </>
166
+ ) : (
167
+ <div className="text-center text-sm text-muted-foreground py-8">
168
+ {t('noActiveSessions')}
169
+ </div>
170
+ )}
171
+ </CardContent>
172
+ </Card>
173
+
174
+ <AlertDialog
175
+ open={sessionToRevoke !== null}
176
+ onOpenChange={(open) => !open && setSessionToRevoke(null)}
177
+ >
178
+ <AlertDialogContent>
179
+ <AlertDialogHeader>
180
+ <AlertDialogTitle>{t('revokeSessionTitle')}</AlertDialogTitle>
181
+ <AlertDialogDescription>
182
+ {t('revokeSessionDescription')}
183
+ </AlertDialogDescription>
184
+ </AlertDialogHeader>
185
+ <AlertDialogFooter>
186
+ <AlertDialogCancel>{t('cancel')}</AlertDialogCancel>
187
+ <AlertDialogAction
188
+ onClick={() =>
189
+ sessionToRevoke && handleRevokeSession(sessionToRevoke)
190
+ }
191
+ >
192
+ {t('revokeSession')}
193
+ </AlertDialogAction>
194
+ </AlertDialogFooter>
195
+ </AlertDialogContent>
196
+ </AlertDialog>
197
+
198
+ <AlertDialog open={revokeAllSessions} onOpenChange={setRevokeAllSessions}>
199
+ <AlertDialogContent>
200
+ <AlertDialogHeader>
201
+ <AlertDialogTitle>{t('revokeAllSessionsTitle')}</AlertDialogTitle>
202
+ <AlertDialogDescription>
203
+ {t('revokeAllSessionsDescription')}
204
+ </AlertDialogDescription>
205
+ </AlertDialogHeader>
206
+ <AlertDialogFooter>
207
+ <AlertDialogCancel>{t('cancel')}</AlertDialogCancel>
208
+ <AlertDialogAction onClick={handleRevokeAllSessions}>
209
+ {t('revokeSessions')}
210
+ </AlertDialogAction>
211
+ </AlertDialogFooter>
212
+ </AlertDialogContent>
213
+ </AlertDialog>
214
+
215
+ <AlertDialog
216
+ open={revokeAllOtherSessions}
217
+ onOpenChange={setRevokeAllOtherSessions}
218
+ >
219
+ <AlertDialogContent>
220
+ <AlertDialogHeader>
221
+ <AlertDialogTitle>
222
+ {t('revokeAllOtherSessionsTitle')}
223
+ </AlertDialogTitle>
224
+ <AlertDialogDescription>
225
+ {t('revokeAllOtherSessionsDescription')}
226
+ </AlertDialogDescription>
227
+ </AlertDialogHeader>
228
+ <AlertDialogFooter>
229
+ <AlertDialogCancel>{t('cancel')}</AlertDialogCancel>
230
+ <AlertDialogAction onClick={handleRevokeAllOtherSessions}>
231
+ {t('revokeSessions')}
232
+ </AlertDialogAction>
233
+ </AlertDialogFooter>
234
+ </AlertDialogContent>
235
+ </AlertDialog>
236
+ </>
237
+ );
238
+ }
239
+
240
+ function SessionRow({
241
+ session,
242
+ onRequestRevoke,
243
+ currentLocaleCode,
244
+ getSettingValue,
245
+ t,
246
+ }: {
247
+ session: UserSession;
248
+ onRequestRevoke: (id: number) => void;
249
+ currentLocaleCode: string;
250
+ getSettingValue: (key: string) => any;
251
+ t: any;
252
+ }) {
253
+ return (
254
+ <TableRow key={session.id}>
255
+ <TableCell>
256
+ <div className="flex items-center gap-3">
257
+ <div>
258
+ <p className="font-medium capitalize">
259
+ {parseDeviceType(session.user_agent)}
260
+ </p>
261
+ <p className="text-xs text-muted-foreground">
262
+ {parseOS(session.user_agent)}
263
+ </p>
264
+ </div>
265
+ </div>
266
+ </TableCell>
267
+ <TableCell>
268
+ <div>{parseBrowser(session.user_agent)}</div>
269
+
270
+ {session.revoked_at === null && (
271
+ <Badge variant="default" className="mt-1 text-xs">
272
+ {t('currentSession')}
273
+ </Badge>
274
+ )}
275
+ </TableCell>
276
+ <TableCell>
277
+ <div className="flex items-center gap-2">
278
+ <MapPin className="size-4 text-muted-foreground" />
279
+ <div>
280
+ <p className="text-md">
281
+ {`${session.location?.city || ''} `}
282
+ {session.location?.country}
283
+ </p>
284
+ <p className="text-xs text-muted-foreground">
285
+ {session.ip_address === '::1' ? '127.0.0.1' : session.ip_address}
286
+ </p>
287
+ </div>
288
+ </div>
289
+ </TableCell>
290
+ <TableCell>
291
+ <div className="flex items-center gap-2">
292
+ <Calendar className="size-4 text-muted-foreground" />
293
+ <div>
294
+ <p className="text-md">
295
+ {formatDate(
296
+ String(session.created_at),
297
+ getSettingValue,
298
+ currentLocaleCode
299
+ )}
300
+ </p>
301
+ <p className="text-xs text-muted-foreground">
302
+ {
303
+ formatDateTime(
304
+ String(session.created_at),
305
+ getSettingValue,
306
+ currentLocaleCode
307
+ ).split(' ')[1]
308
+ }
309
+ </p>
310
+ </div>
311
+ </div>
312
+ </TableCell>
313
+ <TableCell>
314
+ <div className="flex items-center gap-2">
315
+ <Clock className="size-4 text-muted-foreground" />
316
+ <div>
317
+ {session.revoked_at ? (
318
+ <>
319
+ <p className="text-md">
320
+ {formatDate(
321
+ String(session.revoked_at),
322
+ getSettingValue,
323
+ currentLocaleCode
324
+ )}
325
+ </p>
326
+ <p className="text-xs text-muted-foreground">
327
+ {
328
+ formatDateTime(
329
+ String(session.revoked_at),
330
+ getSettingValue,
331
+ currentLocaleCode
332
+ ).split(' ')[1]
333
+ }
334
+ </p>
335
+ </>
336
+ ) : (
337
+ <p className="text-xs text-muted-foreground">&nbsp;</p>
338
+ )}
339
+ </div>
340
+ </div>
341
+ </TableCell>
342
+ <TableCell className="text-right">
343
+ {session.revoked_at === null && (
344
+ <Button
345
+ variant="ghost"
346
+ size="sm"
347
+ onClick={() => onRequestRevoke(Number(session.id))}
348
+ >
349
+ <LogOut className="mr-2 size-4" />
350
+ {t('revoke')}
351
+ </Button>
352
+ )}
353
+ </TableCell>
354
+ </TableRow>
355
+ );
356
+ }