diff --git a/.gitignore b/.gitignore index a3645e5..d4a3757 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,7 @@ yarn-error.log* .DS_Store *.pem cache +logs # Orama orama_bump_*.json diff --git a/examples/create-server-example/index.mjs b/examples/create-server-example/index.mjs index 8aa1ff3..1534979 100644 --- a/examples/create-server-example/index.mjs +++ b/examples/create-server-example/index.mjs @@ -1,4 +1,5 @@ import path from "node:path" +import fastifyCors from "@fastify/cors" import Fastify from "fastify" import { createServer } from "pinorama-server" @@ -18,6 +19,8 @@ const pinoramaServer = createServer( } ) +pinoramaServer.register(fastifyCors) + pinoramaServer.listen({ port: 6200 }, (err, address) => { if (err) throw err console.log(`Pinorama server listening at ${address}`) @@ -40,7 +43,7 @@ const genericServer = Fastify({ } }) -genericServer.post("/logs", async function handler(req) { +genericServer.post("/docs", async function handler(req) { req.log.info(req.body.message) return req.body.message }) diff --git a/examples/create-server-example/package.json b/examples/create-server-example/package.json index 1c6d694..24a2dcf 100644 --- a/examples/create-server-example/package.json +++ b/examples/create-server-example/package.json @@ -4,10 +4,11 @@ "type": "module", "private": true, "scripts": { - "start": "node index.mjs", + "start": "node --inspect index.mjs", "clean": "rimraf node_modules" }, "dependencies": { + "@fastify/cors": "^9.0.1", "@fastify/one-line-logger": "^1.3.0", "fastify": "^4.26.2", "pinorama-server": "workspace:*", diff --git a/examples/fastify-example/index.mjs b/examples/fastify-example/index.mjs index a1755e1..5203743 100644 --- a/examples/fastify-example/index.mjs +++ b/examples/fastify-example/index.mjs @@ -1,4 +1,5 @@ import path from "node:path" +import fastifyCors from "@fastify/cors" import Fastify from "fastify" import { fastifyPinoramaServer } from "pinorama-server" @@ -18,13 +19,15 @@ const fastify = Fastify({ } }) +fastify.register(fastifyCors) + fastify.register(fastifyPinoramaServer, { prefix: "/my_pinorama_server", dbPath: path.resolve("./db.msp"), logLevel: "silent" // need to avoid loop }) -fastify.post("/logs", async function handler(req) { +fastify.post("/docs", async function handler(req) { req.log.info(req.body.message) return req.body.message }) diff --git a/examples/fastify-example/package.json b/examples/fastify-example/package.json index 5b5f770..0489923 100644 --- a/examples/fastify-example/package.json +++ b/examples/fastify-example/package.json @@ -4,10 +4,11 @@ "type": "module", "private": true, "scripts": { - "start": "node index.mjs", + "start": "node --inspect index.mjs", "clean": "rimraf node_modules" }, "dependencies": { + "@fastify/cors": "^9.0.1", "@fastify/one-line-logger": "^1.3.0", "fastify": "^4.26.2", "pinorama-server": "workspace:*", diff --git a/packages/pinorama-client/src/index.ts b/packages/pinorama-client/src/index.ts index 75663b8..14e09a8 100644 --- a/packages/pinorama-client/src/index.ts +++ b/packages/pinorama-client/src/index.ts @@ -111,7 +111,45 @@ export class PinoramaClient { return json } catch (error) { - console.error("error searching logs:", error) + console.error("error searching docs:", error) + throw error + } + } + + public async styles(): Promise { + try { + const response = await fetch(`${this.url}/styles.css`, { + method: "GET", + headers: { ...this.defaultHeaders, contentType: "text/css" } + }) + + if (response.status !== 200) { + throw new Error("[TODO ERROR]: PinoramaClient.styles failed") + } + + const css = await response.text() + return css + } catch (error) { + console.error("error fetching styles:", error) + throw error + } + } + + public async introspection(): Promise { + try { + const response = await fetch(`${this.url}/introspection`, { + method: "GET", + headers: this.defaultHeaders + }) + + const json = await response.json() + if (response.status !== 200) { + throw new Error(json.error) + } + + return json + } catch (error) { + console.error("error fetching introspection:", error) throw error } } diff --git a/packages/pinorama-server/package.json b/packages/pinorama-server/package.json index 6eec584..08cdfb6 100644 --- a/packages/pinorama-server/package.json +++ b/packages/pinorama-server/package.json @@ -23,6 +23,7 @@ "dependencies": { "@orama/orama": "2.0.19", "@orama/plugin-data-persistence": "2.0.19", + "change-case": "^5.4.4", "fastify": "^4.26.2", "fastify-plugin": "^4.5.1" }, diff --git a/packages/pinorama-server/src/index.mts b/packages/pinorama-server/src/index.mts index ff6824d..60588e8 100644 --- a/packages/pinorama-server/src/index.mts +++ b/packages/pinorama-server/src/index.mts @@ -33,19 +33,66 @@ type PinoramaServerOptions = { dbFormat?: PersistenceFormat prefix?: string logLevel?: LogLevel + ui: any } export const defaultOptions: PinoramaServerOptions = { adminSecret: process.env.PINORAMA_SERVER_ADMIN_SECRET, + dbFormat: "json", dbSchema: { - level: "number", + level: "enum", time: "number", msg: "string", - pid: "number", + pid: "enum", hostname: "string" }, - // dbPath: path.join(os.tmpdir(), "pinorama.msp"), - dbFormat: "json" + ui: {} + // ui: { + // props: { + // level: { + // label: "Level", + // type: "enum", + // } + // }, + // labels: { + // level: "Level", + // time: "Time", + // msg: "Message", + // pid: "PID", + // hostname: "Host" + // }, + // enumLabels: { + // level: { + // 10: "TRACE", + // 20: "DEBUG", + // 30: "INFO", + // 40: "WARN", + // 50: "ERROR", + // 60: "FATAL" + // } + // }, + // formatters: { + // time: "timestamp" + // }, + // styles: { + // time: { + // default: { opacity: "0.5" } + // }, + // level: { + // byValue: { + // 10: { color: "var(--color-gray-500)" }, + // 20: { color: "var(--color-purple-500)" }, + // 30: { color: "var(--color-lime-500)" }, + // 40: { color: "var(--color-yellow-500)" }, + // 50: { color: "var(--color-red-500)" }, + // 60: { color: "var(--color-red-500)" } + // } + // } + // }, + // defaultColumns: ["time", "level", "msg", "hostname", "pid"], + // defaultFacets: ["level", "hostname", "pid"] + // // facets: [["level", { search: false }], "hostname", "pid"], + // } } const fastifyPinoramaServer: FastifyPluginAsync = async ( @@ -72,8 +119,10 @@ const fastifyPinoramaServer: FastifyPluginAsync = async ( } fastify.register(routes.bulkRoute, registerOpts) + fastify.register(routes.introspectionRoute, registerOpts) fastify.register(routes.persistRoute, registerOpts) fastify.register(routes.searchRoute, registerOpts) + fastify.register(routes.stylesRoute, registerOpts) fastify.register(plugins.gracefulSaveHook) fastify.register(plugins.authHook) diff --git a/packages/pinorama-server/src/routes/index.mts b/packages/pinorama-server/src/routes/index.mts index 780f2e0..54203b1 100644 --- a/packages/pinorama-server/src/routes/index.mts +++ b/packages/pinorama-server/src/routes/index.mts @@ -1,3 +1,5 @@ export * from "./bulk.mjs" +export * from "./introspection.mjs" export * from "./persist.mjs" export * from "./search.mjs" +export * from "./styles.mjs" diff --git a/packages/pinorama-server/src/routes/introspection.mts b/packages/pinorama-server/src/routes/introspection.mts new file mode 100644 index 0000000..ab263d6 --- /dev/null +++ b/packages/pinorama-server/src/routes/introspection.mts @@ -0,0 +1,16 @@ +import type { FastifyInstance } from "fastify" + +export async function introspectionRoute(fastify: FastifyInstance) { + fastify.route({ + url: "/introspection", + method: "get", + handler: async () => { + const { dbSchema, ui } = fastify.pinoramaOpts + + return { + dbSchema, + ui + } + } + }) +} diff --git a/packages/pinorama-server/src/routes/styles.mts b/packages/pinorama-server/src/routes/styles.mts new file mode 100644 index 0000000..a841f52 --- /dev/null +++ b/packages/pinorama-server/src/routes/styles.mts @@ -0,0 +1,19 @@ +import type { FastifyInstance } from "fastify" +import { generateCSS } from "../utils/styles.mjs" + +export async function stylesRoute(fastify: FastifyInstance) { + fastify.route({ + url: "/styles.css", + method: "get", + handler: async (req, res) => { + const columns = fastify.pinoramaOpts?.ui?.columns + + if (!columns || columns?.length === 0) { + res.type("text/css").send("") + } + + const css = generateCSS(columns) + res.type("text/css").send(css) + } + }) +} diff --git a/packages/pinorama-server/src/utils/styles.mts b/packages/pinorama-server/src/utils/styles.mts new file mode 100644 index 0000000..5159182 --- /dev/null +++ b/packages/pinorama-server/src/utils/styles.mts @@ -0,0 +1,45 @@ +import { kebabCase } from "change-case" + +type StyleConfig = { + base?: Record + conditions?: Record> +} + +type ColumnnDefinition = + | [string, { style: StyleConfig; formatter?: string }] + | string + +export function generateCSS(columns: ColumnnDefinition[]) { + let css = "" + + const generateCSSProps = (styles: Record) => { + let props = "" + for (const [prop, value] of Object.entries(styles)) { + props += `${kebabCase(prop)}: ${value};` + } + return props + } + + for (const column of columns) { + if (typeof column === "string") continue + + const prefix = "pinorama-col" + const [className, config] = column + const baseStyles = config.style?.base ?? {} + const conditions = config.style?.conditions ?? {} + + // Generate base style + css += `.${prefix}-${kebabCase(className)} {` + css += generateCSSProps(baseStyles) + css += "}\n" + + // Generate conditional style + for (const [condition, conditionStyles] of Object.entries(conditions)) { + css += `.${prefix}-${kebabCase(className)}-${condition} {` + css += generateCSSProps(conditionStyles) + css += "}\n" + } + } + + return css +} diff --git a/packages/pinorama-studio/package.json b/packages/pinorama-studio/package.json index ba04ba0..e3a0915 100644 --- a/packages/pinorama-studio/package.json +++ b/packages/pinorama-studio/package.json @@ -17,12 +17,15 @@ "@fastify/cors": "^9.0.1", "@fastify/one-line-logger": "^1.3.0", "@fastify/static": "^7.0.4", + "@radix-ui/react-context-menu": "^2.2.1", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-menubar": "^1.0.4", "@radix-ui/react-slot": "^1.0.2", "@tanstack/react-query": "^5.40.1", + "@tanstack/react-table": "^8.19.2", "@tanstack/react-virtual": "^3.5.1", "chalk": "^5.3.0", + "change-case": "^5.4.4", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "date-fns": "^3.6.0", @@ -39,6 +42,7 @@ "tailwindcss-animate": "^1.0.7" }, "devDependencies": { + "@tailwind-plugin/expose-colors": "^1.1.8", "@types/node": "^20.14.2", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", diff --git a/packages/pinorama-studio/src/app.tsx b/packages/pinorama-studio/src/app.tsx index 5e72cee..fda2bbb 100644 --- a/packages/pinorama-studio/src/app.tsx +++ b/packages/pinorama-studio/src/app.tsx @@ -1,74 +1,18 @@ -import { useLogs } from "@/hooks" -import { useVirtualizer } from "@tanstack/react-virtual" -import { format } from "date-fns" -import { useRef } from "react" - -function LogLine({ log }: { log: any }) { - const levels = { - 60: { label: "fatal", className: "text-red-900" }, - 50: { label: "error", className: "text-red-600" }, - 40: { label: "warn", className: "text-yellow-500" }, - 30: { label: "info", className: "text-blue-500" }, - 20: { label: "debug", className: "text-green-500" }, - 10: { label: "trace", className: "text-gray-500" } - } - - const { label, className } = levels[log.level as keyof typeof levels] - - return ( - <> - {format(log.time, "Pp")} - {log.req ? ( - <> - - {log.req.method} {log.req.url} - - - ) : null} - {label} - {log.msg} - - ) -} +import { + usePinoramaDocs, + usePinoramaIntrospection + // usePinoramaStyles +} from "@/hooks" +import { Docs } from "./components/docs" function App() { - const parentRef = useRef(null) - - const rowVirtualizer = useVirtualizer({ - count: 200000, - getScrollElement: () => parentRef.current, - estimateSize: () => 20 - }) - - const { data } = useLogs() + // const { data: styles } = usePinoramaStyles() + const { data: introspection } = usePinoramaIntrospection() + const { data: docs } = usePinoramaDocs() - if (!data) return null + if (!docs || !introspection) return null - return ( - <> -
-
- {rowVirtualizer.getVirtualItems().map((virtualItem) => { - return ( -
- -
- ) - })} -
-
- - ) + return } export default App diff --git a/packages/pinorama-studio/src/components/docs.tsx b/packages/pinorama-studio/src/components/docs.tsx new file mode 100644 index 0000000..a2ba286 --- /dev/null +++ b/packages/pinorama-studio/src/components/docs.tsx @@ -0,0 +1,174 @@ +import { + ContextMenu, + ContextMenuCheckboxItem, + ContextMenuContent, + ContextMenuSeparator, + ContextMenuTrigger +} from "@/components/ui/context-menu" +import { + type Row, + createColumnHelper, + flexRender, + getCoreRowModel, + useReactTable +} from "@tanstack/react-table" +import { useVirtualizer } from "@tanstack/react-virtual" +import { useMemo, useRef } from "react" + +const columnHelper = createColumnHelper() + +export function Docs({ docs, introspection }: any) { + const columns = useMemo(() => { + return Object.keys(introspection.dbSchema).map((columnName: any) => { + return columnHelper.accessor(columnName, { + header: columnName, + cell: (info) => { + return ( +
+ {info.getValue() as string} +
+ ) + } + }) + }) + }, [introspection?.dbSchema]) + + const table = useReactTable({ + data: docs, + columns, + enableColumnResizing: true, + columnResizeMode: "onChange", + getCoreRowModel: getCoreRowModel() + }) + + const { rows } = table.getRowModel() + + const tableContainerRef = useRef(null) + + const virtualizer = useVirtualizer({ + count: rows.length, + getScrollElement: () => tableContainerRef.current, + estimateSize: () => 24, + overscan: 100 + }) + + return ( +
+ + + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + ))} + + ))} + + + + {table + .getAllColumns() + .filter( + (column) => + typeof column.accessorFn !== "undefined" && + column.getCanHide() + ) + .map((column) => { + return ( + + column.toggleVisibility(!!value) + } + > + {column.id} + + ) + })} + + { + table.resetColumnVisibility() + }} + > + Reset Columns + + + + + {virtualizer.getVirtualItems().map((virtualItem) => { + const doc = rows[virtualItem.index] as Row + return ( + + {doc.getVisibleCells().map((cell) => { + return ( + + ) + })} + + ) + })} + +
+ {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} + {header.column.getCanResize() && ( +
+ )} +
+ {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} +
+
+ ) +} diff --git a/packages/pinorama-studio/src/components/ui/context-menu.tsx b/packages/pinorama-studio/src/components/ui/context-menu.tsx new file mode 100644 index 0000000..7fa796a --- /dev/null +++ b/packages/pinorama-studio/src/components/ui/context-menu.tsx @@ -0,0 +1,198 @@ +import * as ContextMenuPrimitive from "@radix-ui/react-context-menu" +import { Check, ChevronRight, Circle } from "lucide-react" +import * as React from "react" + +import { cn } from "@/lib/utils" + +const ContextMenu = ContextMenuPrimitive.Root + +const ContextMenuTrigger = ContextMenuPrimitive.Trigger + +const ContextMenuGroup = ContextMenuPrimitive.Group + +const ContextMenuPortal = ContextMenuPrimitive.Portal + +const ContextMenuSub = ContextMenuPrimitive.Sub + +const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup + +const ContextMenuSubTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, children, ...props }, ref) => ( + + {children} + + +)) +ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName + +const ContextMenuSubContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName + +const ContextMenuContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName + +const ContextMenuItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, ...props }, ref) => ( + +)) +ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName + +const ContextMenuCheckboxItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, checked, ...props }, ref) => ( + + + + + + + {children} + +)) +ContextMenuCheckboxItem.displayName = + ContextMenuPrimitive.CheckboxItem.displayName + +const ContextMenuRadioItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)) +ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName + +const ContextMenuLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, ...props }, ref) => ( + +)) +ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName + +const ContextMenuSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName + +const ContextMenuShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ) +} +ContextMenuShortcut.displayName = "ContextMenuShortcut" + +export { + ContextMenu, + ContextMenuTrigger, + ContextMenuContent, + ContextMenuItem, + ContextMenuCheckboxItem, + ContextMenuRadioItem, + ContextMenuLabel, + ContextMenuSeparator, + ContextMenuShortcut, + ContextMenuGroup, + ContextMenuPortal, + ContextMenuSub, + ContextMenuSubContent, + ContextMenuSubTrigger, + ContextMenuRadioGroup +} diff --git a/packages/pinorama-studio/src/hooks/index.ts b/packages/pinorama-studio/src/hooks/index.ts index 08beb69..b49741b 100644 --- a/packages/pinorama-studio/src/hooks/index.ts +++ b/packages/pinorama-studio/src/hooks/index.ts @@ -1,11 +1,12 @@ import { usePinoramaClient } from "@/components/pinorama-client-provider" import { useQuery } from "@tanstack/react-query" +import { useEffect } from "react" -export const useLogs = () => { +export const usePinoramaDocs = () => { const client = usePinoramaClient() const query = useQuery({ - queryKey: ["logs"], + queryKey: ["docs"], queryFn: async () => { const response: any = await client?.search({ limit: 200000 @@ -16,3 +17,51 @@ export const useLogs = () => { return query } + +export const usePinoramaStyles = () => { + const client = usePinoramaClient() + + const query = useQuery({ + queryKey: ["styles"], + queryFn: async () => { + const response: any = await client?.styles() + return response + }, + enabled: !!client, + staleTime: Number.POSITIVE_INFINITY + }) + + useEffect(() => { + if (!query.data) return + + let styleElement = document.getElementById( + "pinorama-styles" + ) as HTMLStyleElement + + if (!styleElement) { + styleElement = document.createElement("style") + styleElement.id = "pinorama-styles" + document.head.appendChild(styleElement) + } + + styleElement.textContent = query.data + }, [query.data]) + + return query +} + +export const usePinoramaIntrospection = () => { + const client = usePinoramaClient() + + const query = useQuery({ + queryKey: ["introspection"], + queryFn: async () => { + const response: any = await client?.introspection() + return response + }, + enabled: !!client, + staleTime: Number.POSITIVE_INFINITY + }) + + return query +} diff --git a/packages/pinorama-studio/src/lib/formatters.ts b/packages/pinorama-studio/src/lib/formatters.ts new file mode 100644 index 0000000..e6f5b20 --- /dev/null +++ b/packages/pinorama-studio/src/lib/formatters.ts @@ -0,0 +1,5 @@ +import { format } from "date-fns" + +export const timestamp = (value: string) => { + return format(new Date(value), "MMM dd HH:mm:ss") +} diff --git a/packages/pinorama-studio/src/main.tsx b/packages/pinorama-studio/src/main.tsx index dd5d7ad..a624b2b 100644 --- a/packages/pinorama-studio/src/main.tsx +++ b/packages/pinorama-studio/src/main.tsx @@ -9,9 +9,7 @@ import "./globals.css" ReactDOM.createRoot(document.getElementById("app") as HTMLElement).render( - + diff --git a/packages/pinorama-studio/tailwind.config.js b/packages/pinorama-studio/tailwind.config.js index 3a70d4f..19cef08 100644 --- a/packages/pinorama-studio/tailwind.config.js +++ b/packages/pinorama-studio/tailwind.config.js @@ -73,5 +73,8 @@ module.exports = { } } }, - plugins: [require("tailwindcss-animate")] + plugins: [ + require("tailwindcss-animate"), + require("@tailwind-plugin/expose-colors")({ prefix: "--color" }) + ] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c0aa557..70d2072 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,9 @@ importers: examples/create-server-example: dependencies: + '@fastify/cors': + specifier: ^9.0.1 + version: 9.0.1 '@fastify/one-line-logger': specifier: ^1.3.0 version: 1.4.0 @@ -51,6 +54,9 @@ importers: examples/fastify-example: dependencies: + '@fastify/cors': + specifier: ^9.0.1 + version: 9.0.1 '@fastify/one-line-logger': specifier: ^1.3.0 version: 1.4.0 @@ -125,6 +131,9 @@ importers: '@orama/plugin-data-persistence': specifier: 2.0.19 version: 2.0.19 + change-case: + specifier: ^5.4.4 + version: 5.4.4 fastify: specifier: ^4.26.2 version: 4.28.0 @@ -153,6 +162,9 @@ importers: '@fastify/static': specifier: ^7.0.4 version: 7.0.4 + '@radix-ui/react-context-menu': + specifier: ^2.2.1 + version: 2.2.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-dropdown-menu': specifier: ^2.0.6 version: 2.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -165,12 +177,18 @@ importers: '@tanstack/react-query': specifier: ^5.40.1 version: 5.48.0(react@18.3.1) + '@tanstack/react-table': + specifier: ^8.19.2 + version: 8.19.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@tanstack/react-virtual': specifier: ^3.5.1 version: 3.7.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) chalk: specifier: ^5.3.0 version: 5.3.0 + change-case: + specifier: ^5.4.4 + version: 5.4.4 class-variance-authority: specifier: ^0.7.0 version: 0.7.0 @@ -214,6 +232,9 @@ importers: specifier: ^1.0.7 version: 1.0.7(tailwindcss@3.4.4) devDependencies: + '@tailwind-plugin/expose-colors': + specifier: ^1.1.8 + version: 1.1.8(tailwindcss@3.4.4) '@types/node': specifier: ^20.14.2 version: 20.14.9 @@ -956,6 +977,19 @@ packages: '@types/react': optional: true + '@radix-ui/react-context-menu@2.2.1': + resolution: {integrity: sha512-wvMKKIeb3eOrkJ96s722vcidZ+2ZNfcYZWBPRHIB1VWrF+fiF851Io6LX0kmK5wTDQFKdulCCKJk2c3SBaQHvA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-context@1.1.0': resolution: {integrity: sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==} peerDependencies: @@ -1335,6 +1369,12 @@ packages: '@sinclair/typebox@0.27.8': resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + '@tailwind-plugin/expose-colors@1.1.8': + resolution: {integrity: sha512-QHgZAvSWzfUW40gIpqVXovKRIx1zsgipcm++oZ0GIZ/Cq7qMqc199zP87lMkftKY0uZQJcoYWn3ExUlI5r7kww==} + engines: {node: '>=0.10'} + peerDependencies: + tailwindcss: '>=3.0.0 || >=3.0.0-alpha.1' + '@tanstack/query-core@5.48.0': resolution: {integrity: sha512-lZAfPPeVIqXCswE9SSbG33B6/91XOWt/Iq41bFeWb/mnHwQSIfFRbkS4bfs+WhIk9abRArF9Id2fp0Mgo+hq6Q==} @@ -1343,12 +1383,23 @@ packages: peerDependencies: react: ^18.0.0 + '@tanstack/react-table@8.19.2': + resolution: {integrity: sha512-itoSIAkA/Vsg+bjY23FSemcTyPhc5/1YjYyaMsr9QSH/cdbZnQxHVWrpWn0Sp2BWN71qkzR7e5ye8WuMmwyOjg==} + engines: {node: '>=12'} + peerDependencies: + react: '>=16.8' + react-dom: '>=16.8' + '@tanstack/react-virtual@3.7.0': resolution: {integrity: sha512-3RtOwEU1HKS4iFBoTcCrV3Szqt4KoERMhZr8v57dvnh5o70sR9GAdF+0aE/qhiOmePrKujGwAayFNJSr/8Dbqw==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + '@tanstack/table-core@8.19.2': + resolution: {integrity: sha512-KpRjhgehIhbfH78ARm/GJDXGnpdw4bCg3qas6yjWSi7czJhI/J6pWln7NHtmBkGE9ZbohiiNtLqwGzKmBfixig==} + engines: {node: '>=12'} + '@tanstack/virtual-core@3.7.0': resolution: {integrity: sha512-p0CWuqn+n8iZmsL7/l0Xg7kbyIKnHNqkEJkMDOkg4x3Ni3LohszmnJY8FPhTgG7Ad9ZFGcdKmn1R1mKUGEh9Xg==} @@ -1714,6 +1765,9 @@ packages: resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + change-case@5.4.4: + resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} + chardet@0.7.0: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} @@ -4236,6 +4290,20 @@ snapshots: optionalDependencies: '@types/react': 18.3.3 + '@radix-ui/react-context-menu@2.2.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-context': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-menu': 2.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.3)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 + '@radix-ui/react-context@1.1.0(@types/react@18.3.3)(react@18.3.1)': dependencies: react: 18.3.1 @@ -4566,6 +4634,10 @@ snapshots: '@sinclair/typebox@0.27.8': {} + '@tailwind-plugin/expose-colors@1.1.8(tailwindcss@3.4.4)': + dependencies: + tailwindcss: 3.4.4 + '@tanstack/query-core@5.48.0': {} '@tanstack/react-query@5.48.0(react@18.3.1)': @@ -4573,12 +4645,20 @@ snapshots: '@tanstack/query-core': 5.48.0 react: 18.3.1 + '@tanstack/react-table@8.19.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@tanstack/table-core': 8.19.2 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + '@tanstack/react-virtual@3.7.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@tanstack/virtual-core': 3.7.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + '@tanstack/table-core@8.19.2': {} + '@tanstack/virtual-core@3.7.0': {} '@types/babel__core@7.20.5': @@ -4989,6 +5069,8 @@ snapshots: chalk@5.3.0: {} + change-case@5.4.4: {} + chardet@0.7.0: {} check-error@1.0.3: