feat: integrate sonner for toast notifications and enhance UI feedback
Added the sonner library for toast notifications across various components, improving user feedback for actions such as saving settings, syncing rules, and handling errors. Updated the layout to include a Toaster component for consistent notification display. Refactored alert messages in the backups, gre, and filters pages to utilize the new notification system, enhancing overall user experience.
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
import { and, desc, eq, gte, lte } from "drizzle-orm"
|
||||
import { db } from "../../../db/index.js"
|
||||
import { events } from "../../../db/schema.js"
|
||||
import type { EventItem, EventLevel, EventSourceModule } from "../../../../../packages/contracts/dist/events.js"
|
||||
|
||||
export type EventInsertInput = {
|
||||
id: string
|
||||
createdAt: string
|
||||
level: EventLevel
|
||||
eventType: string
|
||||
sourceModule: EventSourceModule
|
||||
title: string
|
||||
message: string
|
||||
entityType?: string
|
||||
entityId?: string
|
||||
payload?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export type ListEventsParams = {
|
||||
limit: number
|
||||
level?: EventLevel
|
||||
sourceModule?: EventSourceModule
|
||||
from?: string
|
||||
to?: string
|
||||
}
|
||||
|
||||
function parsePayload(payloadJson: string | null): Record<string, unknown> {
|
||||
if (!payloadJson) return {}
|
||||
try {
|
||||
const parsed = JSON.parse(payloadJson) as unknown
|
||||
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
||||
return parsed as Record<string, unknown>
|
||||
}
|
||||
} catch {
|
||||
// keep backwards compatibility with malformed legacy payloads
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
function mapEventRow(row: typeof events.$inferSelect): EventItem {
|
||||
return {
|
||||
id: row.id,
|
||||
createdAt: row.createdAt,
|
||||
level: row.level,
|
||||
eventType: row.eventType,
|
||||
sourceModule: row.sourceModule as EventSourceModule,
|
||||
title: row.title,
|
||||
message: row.message,
|
||||
entityType: row.entityType ?? null,
|
||||
entityId: row.entityId ?? null,
|
||||
payload: parsePayload(row.payloadJson ?? null),
|
||||
}
|
||||
}
|
||||
|
||||
export function insertEventsBatch(items: EventInsertInput[]) {
|
||||
if (items.length === 0) return
|
||||
db.insert(events)
|
||||
.values(
|
||||
items.map((item) => ({
|
||||
id: item.id,
|
||||
createdAt: item.createdAt,
|
||||
level: item.level,
|
||||
eventType: item.eventType,
|
||||
sourceModule: item.sourceModule,
|
||||
title: item.title,
|
||||
message: item.message,
|
||||
entityType: item.entityType ?? null,
|
||||
entityId: item.entityId ?? null,
|
||||
payloadJson: item.payload ? JSON.stringify(item.payload) : null,
|
||||
})),
|
||||
)
|
||||
.run()
|
||||
}
|
||||
|
||||
export function listEvents(params: ListEventsParams): EventItem[] {
|
||||
const where = and(
|
||||
params.level ? eq(events.level, params.level) : undefined,
|
||||
params.sourceModule ? eq(events.sourceModule, params.sourceModule) : undefined,
|
||||
params.from ? gte(events.createdAt, params.from) : undefined,
|
||||
params.to ? lte(events.createdAt, params.to) : undefined,
|
||||
)
|
||||
const rows = db
|
||||
.select()
|
||||
.from(events)
|
||||
.where(where)
|
||||
.orderBy(desc(events.createdAt))
|
||||
.limit(params.limit)
|
||||
.all()
|
||||
return rows.map(mapEventRow)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { randomUUID } from "node:crypto"
|
||||
import {
|
||||
appendEventSchema,
|
||||
listEventsQuerySchema,
|
||||
type AppendEventBody,
|
||||
type EventItem,
|
||||
type ListEventsQuery,
|
||||
} from "../../../../../packages/contracts/dist/events.js"
|
||||
import { insertEventsBatch, listEvents, type EventInsertInput } from "../repository/events-repository.js"
|
||||
|
||||
function normalizeEventInput(input: AppendEventBody): EventInsertInput {
|
||||
return {
|
||||
id: randomUUID(),
|
||||
createdAt: input.createdAt ?? new Date().toISOString(),
|
||||
level: input.level,
|
||||
eventType: input.eventType.trim(),
|
||||
sourceModule: input.sourceModule,
|
||||
title: input.title.trim(),
|
||||
message: input.message.trim(),
|
||||
entityType: input.entityType?.trim() || undefined,
|
||||
entityId: input.entityId?.trim() || undefined,
|
||||
payload: input.payload ?? {},
|
||||
}
|
||||
}
|
||||
|
||||
export function appendEvent(input: AppendEventBody) {
|
||||
const parsed = appendEventSchema.parse(input)
|
||||
insertEventsBatch([normalizeEventInput(parsed)])
|
||||
}
|
||||
|
||||
export function appendEvents(inputs: AppendEventBody[]) {
|
||||
const rows = inputs.map((entry) => normalizeEventInput(appendEventSchema.parse(entry)))
|
||||
insertEventsBatch(rows)
|
||||
}
|
||||
|
||||
export function readEvents(query: Partial<ListEventsQuery>): EventItem[] {
|
||||
const parsed = listEventsQuerySchema.parse(query)
|
||||
return listEvents(parsed)
|
||||
}
|
||||
Reference in New Issue
Block a user