betterstart-cli 0.0.13 → 0.0.15
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.
- package/dist/assets/adapters/next/plugins/blog/schemas/menus.json +51 -11
- package/dist/assets/adapters/next/templates/init/components/layouts/admin-sidebar.tsx +7 -1
- package/dist/assets/adapters/next/templates/init/components/layouts/sidebar-branding.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/components/shared/data-table/data-grid.tsx +107 -0
- package/dist/assets/adapters/next/templates/init/components/shared/data-table/data-table.tsx +58 -45
- package/dist/assets/adapters/next/templates/init/components/shared/dev-mode/lifecycle-hooks-tab.tsx +3 -3
- package/dist/assets/adapters/next/templates/init/components/shared/dev-mode/snippets-tab.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/components/shared/entity-filters-bar.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/components/shared/media/edit-media-dialog-content.tsx +2 -2
- package/dist/assets/adapters/next/templates/init/components/shared/media/media-url-importer.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/components/shared/sort-indicator.tsx +7 -3
- package/dist/assets/adapters/next/templates/init/data/navigation.ts +2 -0
- package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-table-button.ts +85 -0
- package/dist/assets/adapters/next/templates/init/lib/actions/audit/get-audit-logs.ts +41 -16
- package/dist/assets/adapters/next/templates/init/lib/actions/audit/index.ts +2 -0
- package/dist/assets/adapters/next/templates/init/lib/actions/audit/types.ts +5 -0
- package/dist/assets/adapters/next/templates/init/lib/lifecycle-hooks/runner.ts +2 -1
- package/dist/assets/adapters/next/templates/init/lib/lifecycle-hooks/types.ts +2 -1
- package/dist/assets/adapters/next/templates/init/pages/audit-log/audit-log-detail-drawer.tsx +51 -43
- package/dist/assets/adapters/next/templates/init/pages/audit-log/audit-log-detail-row.tsx +2 -4
- package/dist/assets/adapters/next/templates/init/pages/audit-log/audit-log-page-content.tsx +465 -179
- package/dist/assets/adapters/next/templates/init/pages/audit-log/audit-log-page-skeleton.tsx +3 -15
- package/dist/assets/adapters/next/templates/init/pages/dashboard-page.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/pages/forgot-password-page-skeleton.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/pages/login-page-skeleton.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/pages/reset-password-page-skeleton.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/pages/users/users-page-content.tsx +83 -2
- package/dist/assets/adapters/next/templates/init/pages/users/users-table.tsx +126 -78
- package/dist/assets/adapters/next/templates/init/utils/editor/tiptap.ts +7 -8
- package/dist/assets/adapters/next/templates/init/utils/form/list-field.ts +17 -0
- package/dist/assets/adapters/next/templates/init/utils/user/user.ts +70 -0
- package/dist/assets/shared-assets/react-admin/custom/content-editor/math-bubble-menu.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/custom/content-editor/selection-bubble-menu.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/custom/content-editor/source-mode.tsx +0 -2
- package/dist/assets/shared-assets/react-admin/custom/content-editor/table-button.tsx +17 -65
- package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/link-popover/link-control-popover.tsx +3 -8
- package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/link-popover/link-popover.tsx +1 -2
- package/dist/assets/shared-assets/react-admin/custom/date-range-picker.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/custom/dynamic-list-field.tsx +20 -8
- package/dist/assets/shared-assets/react-admin/custom/gallery-field.tsx +2 -2
- package/dist/assets/shared-assets/react-admin/custom/media-gallery-field.tsx +2 -2
- package/dist/assets/shared-assets/react-admin/custom/nested-object-list-field.tsx +211 -0
- package/dist/assets/shared-assets/react-admin/custom/placeholder-card.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/custom/placeholder.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/custom/sortable-gallery-item.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/schema.json +136 -0
- package/dist/assets/shared-assets/react-admin/ui/accordion.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/alert.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/card.tsx +2 -2
- package/dist/assets/shared-assets/react-admin/ui/chart.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/checkbox.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/combobox.tsx +2 -2
- package/dist/assets/shared-assets/react-admin/ui/command.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/context-menu.tsx +6 -6
- package/dist/assets/shared-assets/react-admin/ui/drawer.tsx +2 -2
- package/dist/assets/shared-assets/react-admin/ui/dropdown-menu.tsx +6 -6
- package/dist/assets/shared-assets/react-admin/ui/empty.tsx +2 -2
- package/dist/assets/shared-assets/react-admin/ui/field.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/form.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/hover-card.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/input-group.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/item.tsx +2 -2
- package/dist/assets/shared-assets/react-admin/ui/menubar.tsx +7 -7
- package/dist/assets/shared-assets/react-admin/ui/navigation-menu.tsx +4 -4
- package/dist/assets/shared-assets/react-admin/ui/popover.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/resizable.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/select.tsx +2 -2
- package/dist/assets/shared-assets/react-admin/ui/sidebar.tsx +3 -3
- package/dist/assets/shared-assets/react-admin/ui/skeleton.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/tabs.tsx +2 -2
- package/dist/assets/shared-assets/react-admin/ui/textarea.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/tooltip.tsx +1 -1
- package/dist/cli.js +2701 -1654
- package/dist/cli.js.map +1 -1
- package/package.json +10 -2
- package/dist/assets/adapters/next/templates/init/pages/audit-log/audit-log-filters.tsx +0 -152
|
@@ -3,19 +3,21 @@
|
|
|
3
3
|
import { requireRole, UserRole } from '@admin/auth/middleware'
|
|
4
4
|
import db from '@admin/db'
|
|
5
5
|
import { auditLog } from '@admin/db/schema'
|
|
6
|
-
import { and, desc, eq, gte, ilike, lt, or, sql } from 'drizzle-orm'
|
|
6
|
+
import { and, asc, desc, eq, gte, ilike, lt, or, sql } from 'drizzle-orm'
|
|
7
7
|
import type { AuditLogEntry, AuditLogPage, AuditLogQuery } from './types'
|
|
8
8
|
|
|
9
|
-
const DEFAULT_AUDIT_LOG_PAGE =
|
|
10
|
-
const DEFAULT_AUDIT_LOG_PAGE_SIZE =
|
|
9
|
+
const DEFAULT_AUDIT_LOG_PAGE = 0
|
|
10
|
+
const DEFAULT_AUDIT_LOG_PAGE_SIZE = 20
|
|
11
11
|
const MAX_AUDIT_LOG_PAGE_SIZE = 100
|
|
12
12
|
|
|
13
13
|
function clampPage(value: number | undefined): number {
|
|
14
14
|
if (typeof value !== 'number' || !Number.isFinite(value)) return DEFAULT_AUDIT_LOG_PAGE
|
|
15
|
-
return Math.max(
|
|
15
|
+
return Math.max(0, Math.floor(value))
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
function
|
|
18
|
+
function normalizePageSize(value: number | undefined): number | null {
|
|
19
|
+
if (value === -1) return null
|
|
20
|
+
|
|
19
21
|
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {
|
|
20
22
|
return DEFAULT_AUDIT_LOG_PAGE_SIZE
|
|
21
23
|
}
|
|
@@ -23,6 +25,28 @@ function clampPageSize(value: number | undefined): number {
|
|
|
23
25
|
return Math.min(MAX_AUDIT_LOG_PAGE_SIZE, Math.max(1, Math.floor(value)))
|
|
24
26
|
}
|
|
25
27
|
|
|
28
|
+
function getAuditLogOrderBy(query: AuditLogQuery) {
|
|
29
|
+
const sortFn = query.dir === 'asc' ? asc : desc
|
|
30
|
+
|
|
31
|
+
switch (query.sort) {
|
|
32
|
+
case 'event':
|
|
33
|
+
return [sortFn(auditLog.category), sortFn(auditLog.action), desc(auditLog.createdAt)]
|
|
34
|
+
case 'target':
|
|
35
|
+
return [
|
|
36
|
+
sortFn(auditLog.targetType),
|
|
37
|
+
sortFn(auditLog.targetLabel),
|
|
38
|
+
sortFn(auditLog.targetId),
|
|
39
|
+
desc(auditLog.createdAt)
|
|
40
|
+
]
|
|
41
|
+
case 'actor':
|
|
42
|
+
return [sortFn(auditLog.actorEmail), sortFn(auditLog.actorName), desc(auditLog.createdAt)]
|
|
43
|
+
case 'sourceType':
|
|
44
|
+
return [sortFn(auditLog.sourceType), desc(auditLog.createdAt)]
|
|
45
|
+
default:
|
|
46
|
+
return [sortFn(auditLog.createdAt)]
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
26
50
|
function getExclusiveDateUpperBound(value: string | undefined): string | null {
|
|
27
51
|
if (!value) return null
|
|
28
52
|
const date = new Date(value)
|
|
@@ -34,8 +58,8 @@ function getExclusiveDateUpperBound(value: string | undefined): string | null {
|
|
|
34
58
|
|
|
35
59
|
async function queryAuditLogs(query: AuditLogQuery = {}): Promise<AuditLogPage> {
|
|
36
60
|
const page = clampPage(query.page)
|
|
37
|
-
const pageSize =
|
|
38
|
-
const offset =
|
|
61
|
+
const pageSize = normalizePageSize(query.pageSize)
|
|
62
|
+
const offset = pageSize ? page * pageSize : 0
|
|
39
63
|
const conditions = []
|
|
40
64
|
|
|
41
65
|
if (query.category) conditions.push(eq(auditLog.category, query.category))
|
|
@@ -70,24 +94,25 @@ async function queryAuditLogs(query: AuditLogQuery = {}): Promise<AuditLogPage>
|
|
|
70
94
|
}
|
|
71
95
|
|
|
72
96
|
const where = conditions.length > 0 ? and(...conditions) : undefined
|
|
97
|
+
const orderBy = getAuditLogOrderBy(query)
|
|
98
|
+
const orderedQuery = db
|
|
99
|
+
.select()
|
|
100
|
+
.from(auditLog)
|
|
101
|
+
.where(where)
|
|
102
|
+
.orderBy(...orderBy)
|
|
73
103
|
const [rows, countResult] = await Promise.all([
|
|
74
|
-
|
|
75
|
-
.select()
|
|
76
|
-
.from(auditLog)
|
|
77
|
-
.where(where)
|
|
78
|
-
.orderBy(desc(auditLog.createdAt))
|
|
79
|
-
.limit(pageSize)
|
|
80
|
-
.offset(offset),
|
|
104
|
+
pageSize ? orderedQuery.limit(pageSize).offset(offset) : orderedQuery,
|
|
81
105
|
db.select({ count: sql<number>`count(*)` }).from(auditLog).where(where)
|
|
82
106
|
])
|
|
83
107
|
const total = Number(countResult[0]?.count ?? 0)
|
|
108
|
+
const resolvedPageSize = pageSize ?? total
|
|
84
109
|
|
|
85
110
|
return {
|
|
86
111
|
auditLogs: rows as AuditLogEntry[],
|
|
87
112
|
total,
|
|
88
113
|
page,
|
|
89
|
-
pageSize,
|
|
90
|
-
totalPages: Math.max(1, Math.ceil(total / pageSize))
|
|
114
|
+
pageSize: resolvedPageSize,
|
|
115
|
+
totalPages: pageSize ? Math.max(1, Math.ceil(total / pageSize)) : 1
|
|
91
116
|
}
|
|
92
117
|
}
|
|
93
118
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export const AUDIT_LOG_CATEGORIES = [
|
|
2
2
|
'entity',
|
|
3
|
+
'form',
|
|
3
4
|
'settings',
|
|
4
5
|
'user',
|
|
5
6
|
'auth',
|
|
@@ -19,6 +20,8 @@ export const AUDIT_LOG_SOURCE_TYPES = ['admin_ui', 'server_action', 'system_job'
|
|
|
19
20
|
export type AuditLogCategory = (typeof AUDIT_LOG_CATEGORIES)[number]
|
|
20
21
|
export type AuditLogAction = (typeof AUDIT_LOG_ACTIONS)[number]
|
|
21
22
|
export type AuditLogSourceType = (typeof AUDIT_LOG_SOURCE_TYPES)[number]
|
|
23
|
+
export type AuditLogSortField = 'createdAt' | 'event' | 'target' | 'actor' | 'sourceType'
|
|
24
|
+
export type AuditLogSortDirection = 'asc' | 'desc'
|
|
22
25
|
|
|
23
26
|
export interface AuditLogEntry {
|
|
24
27
|
id: string
|
|
@@ -49,6 +52,8 @@ export interface AuditLogQuery {
|
|
|
49
52
|
createdAtTo?: string
|
|
50
53
|
page?: number
|
|
51
54
|
pageSize?: number
|
|
55
|
+
sort?: AuditLogSortField
|
|
56
|
+
dir?: AuditLogSortDirection
|
|
52
57
|
}
|
|
53
58
|
|
|
54
59
|
export interface AuditLogPage {
|
|
@@ -76,7 +76,7 @@ function toAdminEventContext(
|
|
|
76
76
|
if (!action) return null
|
|
77
77
|
|
|
78
78
|
return {
|
|
79
|
-
category: 'entity',
|
|
79
|
+
category: context.schemaType === 'form' ? 'form' : 'entity',
|
|
80
80
|
action,
|
|
81
81
|
targetType: context.entityType,
|
|
82
82
|
targetId: context.entityId ?? null,
|
|
@@ -89,6 +89,7 @@ function toAdminEventContext(
|
|
|
89
89
|
changedFields: getChangedFieldsFromLifecycleContext(event, context),
|
|
90
90
|
metadata: {
|
|
91
91
|
entityType: context.entityType,
|
|
92
|
+
schemaType: context.schemaType ?? 'entity',
|
|
92
93
|
...(context.metadata ?? {})
|
|
93
94
|
}
|
|
94
95
|
}
|
|
@@ -21,8 +21,9 @@ export type BeforeLifecycleHookEvent = Extract<LifecycleHookEvent, `before${stri
|
|
|
21
21
|
export type AfterLifecycleHookEvent = Extract<LifecycleHookEvent, `after${string}`>
|
|
22
22
|
|
|
23
23
|
export interface LifecycleHookContext {
|
|
24
|
-
user: User
|
|
24
|
+
user: User | null
|
|
25
25
|
entityType: string
|
|
26
|
+
schemaType?: 'entity' | 'form' | undefined
|
|
26
27
|
timestamp: string
|
|
27
28
|
entityId?: string | undefined
|
|
28
29
|
input?: Record<string, unknown> | undefined
|
package/dist/assets/adapters/next/templates/init/pages/audit-log/audit-log-detail-drawer.tsx
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import type { AuditLogEntry } from '@admin/actions/audit'
|
|
4
4
|
import { Badge } from '@admin/components/ui/badge'
|
|
5
|
+
import { Card, CardContent, CardHeader, CardTitle } from '@admin/components/ui/card'
|
|
5
6
|
import {
|
|
6
7
|
Drawer,
|
|
7
8
|
DrawerContent,
|
|
@@ -43,50 +44,57 @@ export function AuditLogDetailDrawer({ auditLog, open, onOpenChange }: AuditLogD
|
|
|
43
44
|
{auditLog ? `${auditLog.category}.${auditLog.action}` : 'No event selected'}
|
|
44
45
|
</DrawerDescription>
|
|
45
46
|
</DrawerHeader>
|
|
46
|
-
<ScrollArea className="min-h-0 flex-1">
|
|
47
|
+
<ScrollArea className="min-h-0 flex-1 px-6">
|
|
47
48
|
{auditLog ? (
|
|
48
|
-
<div className="
|
|
49
|
-
<
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
value={auditLog.
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
label="
|
|
69
|
-
value={
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
</
|
|
89
|
-
</
|
|
49
|
+
<div className="flex flex-col w-full gap-4">
|
|
50
|
+
<Card>
|
|
51
|
+
<CardHeader>
|
|
52
|
+
<CardTitle>Event Details</CardTitle>
|
|
53
|
+
</CardHeader>
|
|
54
|
+
|
|
55
|
+
<CardContent className="p-0!">
|
|
56
|
+
<AuditLogDetailRow label="Time" value={formatDateTime(auditLog.createdAt)} />
|
|
57
|
+
<AuditLogDetailRow label="Category" value={auditLog.category} />
|
|
58
|
+
<AuditLogDetailRow label="Action" value={auditLog.action} />
|
|
59
|
+
<AuditLogDetailRow
|
|
60
|
+
label="Target"
|
|
61
|
+
value={auditLog.targetLabel ?? auditLog.targetId}
|
|
62
|
+
/>
|
|
63
|
+
<AuditLogDetailRow label="Target Type" value={auditLog.targetType} />
|
|
64
|
+
<AuditLogDetailRow label="Target ID" value={auditLog.targetId} />
|
|
65
|
+
<AuditLogDetailRow
|
|
66
|
+
label="Actor"
|
|
67
|
+
value={auditLog.actorEmail ?? auditLog.actorName ?? auditLog.actorUserId}
|
|
68
|
+
/>
|
|
69
|
+
<AuditLogDetailRow label="Actor Role" value={auditLog.actorRole} />
|
|
70
|
+
<AuditLogDetailRow label="Source" value={auditLog.sourceType} />
|
|
71
|
+
<AuditLogDetailRow label="IP" value={auditLog.ipAddress} />
|
|
72
|
+
<AuditLogDetailRow label="User Agent" value={auditLog.userAgent} />
|
|
73
|
+
<AuditLogDetailRow
|
|
74
|
+
label="Fields"
|
|
75
|
+
value={
|
|
76
|
+
auditLog.changedFields.length > 0 ? (
|
|
77
|
+
<div className="flex flex-wrap gap-1">
|
|
78
|
+
{auditLog.changedFields.map((field) => (
|
|
79
|
+
<Badge key={field} variant="secondary">
|
|
80
|
+
{field}
|
|
81
|
+
</Badge>
|
|
82
|
+
))}
|
|
83
|
+
</div>
|
|
84
|
+
) : (
|
|
85
|
+
'None'
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
/>
|
|
89
|
+
</CardContent>
|
|
90
|
+
</Card>
|
|
91
|
+
|
|
92
|
+
<Card>
|
|
93
|
+
<CardHeader>
|
|
94
|
+
<CardTitle>Event Metadata</CardTitle>
|
|
95
|
+
</CardHeader>
|
|
96
|
+
<CardContent>{formatMetadata(auditLog.metadata)}</CardContent>
|
|
97
|
+
</Card>
|
|
90
98
|
</div>
|
|
91
99
|
) : null}
|
|
92
100
|
</ScrollArea>
|
|
@@ -7,10 +7,8 @@ interface AuditLogDetailRowProps {
|
|
|
7
7
|
|
|
8
8
|
export function AuditLogDetailRow({ label, value }: AuditLogDetailRowProps) {
|
|
9
9
|
return (
|
|
10
|
-
<div className="grid grid-cols-[120px_1fr] gap-3 border-b border-border
|
|
11
|
-
<dt className="text-
|
|
12
|
-
{label}
|
|
13
|
-
</dt>
|
|
10
|
+
<div className="grid grid-cols-[120px_1fr] gap-3 border-b border-border py-2 px-4 last:border-b-0">
|
|
11
|
+
<dt className="text-sm font-medium text-muted-foreground">{label}</dt>
|
|
14
12
|
<dd className="min-w-0 text-sm text-foreground break-words">{value || 'Unknown'}</dd>
|
|
15
13
|
</div>
|
|
16
14
|
)
|