From 720cd285aab49b134304d4df552c96c3d5a56b74 Mon Sep 17 00:00:00 2001 From: Its4Nik Date: Wed, 1 Oct 2025 18:14:49 +0200 Subject: [PATCH 01/40] =?UTF-8?q?=E2=9C=A8=20Refactor:=20Improve=20JSON=20?= =?UTF-8?q?column=20handling=20and=20table=20creation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit improves the handling of JSON columns within the sqlite-wrapper. It refactors the table creation process to accept a `jsonConfig` option, enabling automatic JSON serialization/deserialization for specified columns. - Adds `jsonConfig` to the `createTable` options, allowing specification of JSON columns. - Updates `transformRowToDb` to automatically serialize JSON columns before insertion/update. - Improves logging for debugging purposes. - Updates the example in the README to reflect the changes. --- packages/sqlite-wrapper/README.md | 19 ++++++++++-- packages/sqlite-wrapper/index.ts | 7 +++-- packages/sqlite-wrapper/package.json | 2 +- packages/sqlite-wrapper/query-builder/base.ts | 31 +++++++++++-------- .../sqlite-wrapper/query-builder/insert.ts | 6 ++++ packages/sqlite-wrapper/types.ts | 11 +++---- 6 files changed, 49 insertions(+), 27 deletions(-) diff --git a/packages/sqlite-wrapper/README.md b/packages/sqlite-wrapper/README.md index bc0c39a..82e6d91 100644 --- a/packages/sqlite-wrapper/README.md +++ b/packages/sqlite-wrapper/README.md @@ -19,16 +19,29 @@ bun add @dockstat/sqlite-wrapper ```ts import { DB, column } from "@dockstat/sqlite-wrapper"; -const db = new DB("app.db", { pragmas: [["journal_mode","WAL"], ["foreign_keys","ON"]] }); +type User = { + id?: number, + name: string, + active: boolean, + email: string, +} -db.createTable("users", { +const db = new DB("app.db", { + pragmas: [ + ["journal_mode","WAL"], + ["foreign_keys","ON"] + ] +}); + +const userTable = db.createTable("users", { id: column.id(), name: column.text({ notNull: true }), + active: column.boolean(), email: column.text({ unique: true, notNull: true }), created_at: column.createdAt() }); -const users = db.table("users") +const users = userTable .select(["id","name","email"]) .where({ active: true }) .orderBy("created_at").desc() diff --git a/packages/sqlite-wrapper/index.ts b/packages/sqlite-wrapper/index.ts index f11987a..75d0a32 100644 --- a/packages/sqlite-wrapper/index.ts +++ b/packages/sqlite-wrapper/index.ts @@ -102,7 +102,7 @@ class DB { tableName: string, jsonConfig?: JsonColumnConfig ): QueryBuilder { - logger.debug(`Creating QueryBuilder for table: ${tableName}`) + logger.debug(`Creating QueryBuilder for table: ${tableName} - JSONConfig: ${JSON.stringify(jsonConfig)}`) return new QueryBuilder(this.db, tableName, jsonConfig) } @@ -209,7 +209,8 @@ class DB { */ createTable<_T extends Record>( tableName: string, - columns: string | Record | Partial, ColumnDefinition>> | TableSchema, options?: TableOptions<_T>, + columns: string | Record | Partial, ColumnDefinition>> | TableSchema, + options?: TableOptions<_T>, ): QueryBuilder<_T> { const temp = options?.temporary ? 'TEMPORARY ' : '' const ifNot = options?.ifNotExists ? 'IF NOT EXISTS ' : '' @@ -276,7 +277,7 @@ class DB { this.setTableComment(tableName, options.comment) } - return this.table<_T>(tableName) + return this.table<_T>(tableName, options?.jsonConfig) } /** diff --git a/packages/sqlite-wrapper/package.json b/packages/sqlite-wrapper/package.json index 1d96d3a..41e3305 100644 --- a/packages/sqlite-wrapper/package.json +++ b/packages/sqlite-wrapper/package.json @@ -1,6 +1,6 @@ { "name": "@dockstat/sqlite-wrapper", - "version": "1.2.5", + "version": "1.2.6", "description": "A TypeScript wrapper around bun:sqlite with type-safe query building", "type": "module", "main": "./index.ts", diff --git a/packages/sqlite-wrapper/query-builder/base.ts b/packages/sqlite-wrapper/query-builder/base.ts index ec8a66b..e3de4cb 100644 --- a/packages/sqlite-wrapper/query-builder/base.ts +++ b/packages/sqlite-wrapper/query-builder/base.ts @@ -34,24 +34,24 @@ export abstract class BaseQueryBuilder> { whereConditions: [], whereParams: [], regexConditions: [], - jsonColumns: jsonConfig?.jsonColumns, + jsonColumns: jsonConfig, } } /** * Reset query builder state */ - protected reset(): void { - this.state.whereConditions = [] - this.state.whereParams = [] - this.state.regexConditions = [] - // Reset any ordering, limit, offset, selected columns if present - if ('orderColumn' in this) (this as any).orderColumn = undefined - if ('orderDirection' in this) (this as any).orderDirection = 'ASC' - if ('limitValue' in this) (this as any).limitValue = undefined - if ('offsetValue' in this) (this as any).offsetValue = undefined - if ('selectedColumns' in this) (this as any).selectedColumns = ['*'] - } + protected reset(): void { + this.state.whereConditions = [] + this.state.whereParams = [] + this.state.regexConditions = [] + // Reset any ordering, limit, offset, selected columns if present + if ('orderColumn' in this) (this as any).orderColumn = undefined + if ('orderDirection' in this) (this as any).orderDirection = 'ASC' + if ('limitValue' in this) (this as any).limitValue = undefined + if ('offsetValue' in this) (this as any).offsetValue = undefined + if ('selectedColumns' in this) (this as any).selectedColumns = ['*'] + } /** * Get the database instance @@ -197,11 +197,16 @@ export abstract class BaseQueryBuilder> { * Transform row data before inserting/updating to database (serialize JSON columns). */ protected transformRowToDb(row: Partial): DatabaseRowData { - if (!this.state.jsonColumns || !row) return row as DatabaseRowData + this.logger.debug(`Transforming row (${JSON.stringify(row)}) to row Data`) + if (!this.state.jsonColumns || !row) { + return row as DatabaseRowData + } const transformed: DatabaseRowData = { ...row } as DatabaseRowData + for (const column of this.state.jsonColumns) { const columnKey = String(column) + this.logger.debug(`Checking: ${columnKey}`) if ( transformed[columnKey] !== undefined && transformed[columnKey] !== null diff --git a/packages/sqlite-wrapper/query-builder/insert.ts b/packages/sqlite-wrapper/query-builder/insert.ts index 3779c6a..f624d96 100644 --- a/packages/sqlite-wrapper/query-builder/insert.ts +++ b/packages/sqlite-wrapper/query-builder/insert.ts @@ -9,6 +9,7 @@ import { WhereQueryBuilder } from "./where"; export class InsertQueryBuilder< T extends Record, > extends WhereQueryBuilder { + /** * Insert a single row or multiple rows into the table. * @@ -20,11 +21,16 @@ export class InsertQueryBuilder< data: Partial | Partial[], options?: InsertOptions, ): InsertResult { + this.getLogger().debug(`Building Data Array: ${data}`) const rows = Array.isArray(data) ? data : [data]; + + // Transform rows to handle JSON serialization const transformedRows = rows.map((row) => this.transformRowToDb(row)); + this.getLogger().debug(`Transformed row: ${JSON.stringify(transformedRows)}`) + if (transformedRows.length === 0) { throw new Error("insert: data cannot be empty"); } diff --git a/packages/sqlite-wrapper/types.ts b/packages/sqlite-wrapper/types.ts index 5d443a3..25678c4 100644 --- a/packages/sqlite-wrapper/types.ts +++ b/packages/sqlite-wrapper/types.ts @@ -1,4 +1,3 @@ -// Enhanced types.ts with comprehensive SQLite support import type { Database, SQLQueryBindings } from "bun:sqlite"; /** @@ -262,8 +261,8 @@ export interface TableOptions { temporary?: boolean; /** Table comment */ comment?: string; - /** JSON config for table creation and returned query builder */ - jsonConfig?: Array; + + jsonConfig?: JsonColumnConfig } /** @@ -380,7 +379,7 @@ export const column = { constraints: ColumnConstraints & { validateJson?: boolean }, ): ColumnDefinition => ({ type: SQLiteTypes.JSON, - check: constraints?.validateJson ?? true + check: constraints?.validateJson ? "JSON_VALID({{COLUMN}})" : constraints?.check, ...constraints, @@ -606,9 +605,7 @@ export interface InsertOptions { /** * JSON column configuration */ -export interface JsonColumnConfig { - jsonColumns?: Array; -} +export type JsonColumnConfig = Array /** * Generic database row type From 97eb386903aa66ca5cc93c4a6a55f462bc242f02 Mon Sep 17 00:00:00 2001 From: Its4Nik Date: Wed, 1 Oct 2025 18:24:17 +0200 Subject: [PATCH 02/40] =?UTF-8?q?=F0=9F=93=9D=20Update=20README=20and=20pa?= =?UTF-8?q?ckage=20version?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updates the README with more detailed features, switches to MPL-2.0 license and bumps package version to 1.2.7. --- packages/sqlite-wrapper/README.md | 37 ++++++++++++++++++++++++++-- packages/sqlite-wrapper/package.json | 2 +- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/packages/sqlite-wrapper/README.md b/packages/sqlite-wrapper/README.md index 82e6d91..cf98cde 100644 --- a/packages/sqlite-wrapper/README.md +++ b/packages/sqlite-wrapper/README.md @@ -16,7 +16,7 @@ bun add @dockstat/sqlite-wrapper ## 10-second quickstart -```ts +```typescript import { DB, column } from "@dockstat/sqlite-wrapper"; type User = { @@ -57,10 +57,43 @@ const users = userTable * 🛡️ Safety-first defaults — prevents accidental full-table updates/deletes * 🚀 Designed for production workflows: WAL, pragmatic PRAGMAs, bulk ops, transactions +## Core Features + +### Type Safety + +* **Compile-time validation** of column names and data shapes +* **IntelliSense support** for all operations +* **Generic interfaces** that adapt to your data models +* **Type-safe column definitions** with comprehensive constraint support + +### Safety-First Design + +* **Mandatory WHERE conditions** for UPDATE and DELETE operations to prevent accidental data loss +* **Parameter binding** for all queries to prevent SQL injection +* **Prepared statements** used internally for optimal performance +* **Transaction support** with automatic rollback on errors + +### Production Ready + +* **WAL mode** support for concurrent read/write operations +* **Comprehensive PRAGMA management** for performance tuning +* **Connection pooling** considerations built-in +* **Bulk operation** support with transaction batching +* **Schema introspection** tools for migrations and debugging + +### Complete SQLite Support + +* **All SQLite data types** with proper TypeScript mappings +* **Generated columns** (both VIRTUAL and STORED) +* **Foreign key constraints** with cascade options +* **JSON columns** with validation and transformation +* **Full-text search** preparation +* **Custom functions** and extensions support + ## Docs & examples See full technical docs [here](https://outline.itsnik.de/s/9d88c471-373e-4ef2-a955-b1058eb7dc99/doc/dockstatsqlite-wrapper-Lxt4IphXI5). ## License -MIT — maintained by Dockstat. Contributions welcome. +MPL-2.0 — maintained by Dockstat. Contributions welcome. diff --git a/packages/sqlite-wrapper/package.json b/packages/sqlite-wrapper/package.json index 41e3305..c694d00 100644 --- a/packages/sqlite-wrapper/package.json +++ b/packages/sqlite-wrapper/package.json @@ -1,6 +1,6 @@ { "name": "@dockstat/sqlite-wrapper", - "version": "1.2.6", + "version": "1.2.7", "description": "A TypeScript wrapper around bun:sqlite with type-safe query building", "type": "module", "main": "./index.ts", From ac46ab9aa9330d74a870938018ce1cba4ebbfd82 Mon Sep 17 00:00:00 2001 From: Its4Nik Date: Thu, 9 Oct 2025 00:14:54 +0200 Subject: [PATCH 03/40] =?UTF-8?q?=E2=9C=A8=20Feature:=20Implement=20Plugin?= =?UTF-8?q?=20Support=20and=20Theme=20Enhancements?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit introduces plugin support to DockStat, allowing for extending functionality through external plugins. It also enhances theme capabilities and improves code organization. - Implemented PluginHandler for managing plugins, including registration, updates, and deletion. - Added database table for storing plugin metadata and configurations. - Enhanced ThemeHandler to provide access to the theme table. - Updated dependencies and scripts for building plugins. - Moved and renamed existing plugin handler to prevent naming collisions The `pluginHandler` now handles registration, updates, and deletions of plugins. It also creates a plugin table, for storing information about plugins, such as version number, the plugin component, and plugin settings. BREAKING CHANGE: The original plugin handler was renamed to prevent naming collisions when new packages are built. --- apps/dockstat/app/.server/index.ts | 44 +- .../app/.server/src/plugins/handler.ts | 41 -- .../app/.server/src/theme/themeHandler.ts | 47 +- apps/dockstat/app/.server/src/utils.ts | 12 +- .../app/components/layout/Background.tsx | 25 + apps/dockstat/app/components/ui/Nav.tsx | 24 +- apps/dockstat/app/components/ui/NavCards.tsx | 9 +- apps/dockstat/app/root.tsx | 38 +- .../app/routes/api.v1.conf.navlinks.tsx | 7 + apps/dockstat/app/routes/api.v1.conf.tsx | 5 + .../dockstat/app/routes/api.v1.plugin.$id.tsx | 0 apps/dockstat/app/routes/plugin.$id.tsx | 0 apps/dockstat/app/utils/NavItem.tsx | 8 +- apps/dockstat/package.json | 40 +- apps/dockstore/package.json | 2 +- bun.lock | 428 +++++++++--------- install-deps.sh | 34 ++ package.json | 5 +- packages/db/index.ts | 19 +- packages/db/package.json | 4 +- packages/docker-client/package.json | 14 +- packages/logger/package.json | 4 +- packages/outline-sync/package.json | 2 +- packages/plugin-handler/.gitignore | 34 ++ packages/plugin-handler/README.md | 1 + packages/plugin-handler/package.json | 18 + packages/plugin-handler/src/consts.ts | 8 + packages/plugin-handler/src/index.ts | 77 ++++ packages/plugin-handler/src/utils.ts | 65 +++ packages/plugin-handler/tsconfig.json | 29 ++ packages/sqlite-wrapper/package.json | 4 +- packages/typings/package.json | 4 +- packages/typings/src/database.ts | 1 + packages/typings/src/plugins.ts | 31 +- packages/typings/src/themes.ts | 10 +- 35 files changed, 716 insertions(+), 378 deletions(-) delete mode 100644 apps/dockstat/app/.server/src/plugins/handler.ts create mode 100644 apps/dockstat/app/components/layout/Background.tsx create mode 100644 apps/dockstat/app/routes/api.v1.conf.navlinks.tsx create mode 100644 apps/dockstat/app/routes/api.v1.conf.tsx create mode 100644 apps/dockstat/app/routes/api.v1.plugin.$id.tsx create mode 100644 apps/dockstat/app/routes/plugin.$id.tsx create mode 100644 install-deps.sh create mode 100644 packages/plugin-handler/.gitignore create mode 100644 packages/plugin-handler/README.md create mode 100644 packages/plugin-handler/package.json create mode 100644 packages/plugin-handler/src/consts.ts create mode 100644 packages/plugin-handler/src/index.ts create mode 100644 packages/plugin-handler/src/utils.ts create mode 100644 packages/plugin-handler/tsconfig.json diff --git a/apps/dockstat/app/.server/index.ts b/apps/dockstat/app/.server/index.ts index 51e1bba..00215a8 100644 --- a/apps/dockstat/app/.server/index.ts +++ b/apps/dockstat/app/.server/index.ts @@ -3,24 +3,29 @@ import type sqliteWrapper from '@dockstat/sqlite-wrapper'; import type { QueryBuilder } from '@dockstat/sqlite-wrapper'; import type { DATABASE } from '@dockstat/typings'; import { createLogger } from '@dockstat/logger' -import { startUp } from './src/utils'; -import AdapterHandler from './src/adapters/handler'; -import ThemeHandler from './src/theme/themeHandler'; +import PluginHandler from "@dockstat/plugin-handler" + +import { startUp } from '~/.server/src/utils'; +import AdapterHandler from '~/.server/src/adapters/handler'; +import ThemeHandler from '~/.server/src/theme/themeHandler'; + + class ServerInstance { private logger: { - error: (msg: string) => void; - warn: (msg: string) => void; - info: (msg: string) => void; - debug: (msg: string) => void; + error: (msg: string) => void; + warn: (msg: string) => void; + info: (msg: string) => void; + debug: (msg: string) => void; } private AdapterHandler!: AdapterHandler; private DB!: DBFactory; private DBWrapper!: sqliteWrapper; private config_table!: QueryBuilder; private themeHandler!: ThemeHandler; + private pluginHandler!: PluginHandler - constructor(name = 'DockStatAPI'){ + constructor(name = 'DockStatAPI') { this.logger = createLogger(`${name}`); this.logger.debug("Initialized Server Logger"); this.logger.info("Starting DockStat Server... Please stand by."); @@ -61,6 +66,13 @@ class ServerInstance { } ] }, + "initialize plugins": { + steps: [ + () => { + this.pluginHandler = new PluginHandler(this.DB.getDB()) + } + ] + }, "Setup Adapter Handler": { steps: [ () => { @@ -115,21 +127,22 @@ class ServerInstance { } } ] - } + }, }); } - getLogger(){ + getLogger() { return this.logger; } - getDB(){ + getDB() { this.logger.debug("Getting Database Object"); const dbObj = { DB: this.DB, DBWrapper: this.DBWrapper, tables: { config: this.config_table, + themes: this.themeHandler.getThemeTable(), adapter: this.AdapterHandler.getAdapterTable() } }; @@ -137,15 +150,20 @@ class ServerInstance { return dbObj; } - getThemeHandler(){ + getThemeHandler() { this.logger.debug("Getting ThemeHandler instance"); return this.themeHandler; } - public getAdapterHandler(){ + getAdapterHandler() { this.logger.debug("Getting AdapterHandler instance"); return this.AdapterHandler; } + + getPluginHandler() { + this.logger.debug("Getting PluginHandler") + return this.pluginHandler; + } } export default new ServerInstance(); diff --git a/apps/dockstat/app/.server/src/plugins/handler.ts b/apps/dockstat/app/.server/src/plugins/handler.ts deleted file mode 100644 index 3e76992..0000000 --- a/apps/dockstat/app/.server/src/plugins/handler.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type DB from "@dockstat/sqlite-wrapper"; -import type { PLUGIN } from "@dockstat/typings" -import { column, type QueryBuilder } from "@dockstat/sqlite-wrapper"; -import { createLogger } from "@dockstat/logger"; - -export class PluginHandler { - private DB: DB - private table: QueryBuilder - private logger = createLogger("PluginHandler") - - constructor(DB: DB) { - this.DB = DB - - this.table = this.DB.createTable("plugins", { - id: column.id(), - name: column.text({ notNull: true }), - version: column.text({ notNull: true, check: "GLOB '[0-9]*.[0-9]*.[0-9]*'" }), - type: column.enum(["component", "backend", "multi"]), - component: column.blob({ notNull: false }), - backend: column.blob({ notNull: false }) - }) - } - - getPluginBlobs(id: number) { - const plugin = this.table.select(["backend", "component"]).where({ id: id }).first() - const tmp = new Map - - if (!plugin) { - throw new Error(`Plugin with id ${id} not found`) - } - - plugin.backend && tmp.set("backend", plugin.backend) - plugin.component && tmp.set('component', plugin.component) - - return tmp - } - - getSavedPlugins() { - return this.table.select(["*"]).all() - } -} diff --git a/apps/dockstat/app/.server/src/theme/themeHandler.ts b/apps/dockstat/app/.server/src/theme/themeHandler.ts index 3f9eab4..f832136 100644 --- a/apps/dockstat/app/.server/src/theme/themeHandler.ts +++ b/apps/dockstat/app/.server/src/theme/themeHandler.ts @@ -2,37 +2,38 @@ import { createLogger } from "@dockstat/logger"; import type DB from "@dockstat/sqlite-wrapper"; import type { THEME } from "@dockstat/typings"; import { column, type QueryBuilder } from "@dockstat/sqlite-wrapper"; -import { injectVariables } from "../utils"; +import { injectVariables } from "~/.server/src/utils"; class ThemeHandler { private DB: DB; private logger = createLogger("ThemeHandler") private themes: QueryBuilder; - constructor(db: DB){ + constructor(db: DB) { this.logger.info("Setting up ThemeHandler") this.DB = db this.logger.debug("Creating 'themes' table in DB"); - this.DB.createTable("themes",{ - name: column.text({notNull: true, primaryKey: true, unique: true}), - creator: column.text({notNull: true}), - license: column.text({notNull: true}), - vars: column.json({notNull: true}), - config: column.json({notNull: true}) - },{ifNotExists: true}) - - this.themes = this.DB.table("themes", {jsonColumns: ["vars", "config"]}) + this.themes = this.DB.createTable("themes", { + name: column.text({ notNull: true, primaryKey: true, unique: true }), + creator: column.text({ notNull: true }), + license: column.text({ notNull: true }), + vars: column.json({ notNull: true }), + config: column.json({ notNull: true }) + }, { ifNotExists: true }) this.logger.debug("Theme table created and assigned"); - this.logger.info("ThemeHandler setup complete"); } - getAll(){ + getThemeTable() { + return this.themes + } + + getAll() { return this.themes.select(["*"]).all() } - registerTheme(theme: THEME.ThemeTable){ + registerTheme(theme: THEME.ThemeTable) { this.logger.info(`Registering theme: ${theme.name}`); this.logger.debug(`Theme details: ${JSON.stringify(theme)}`); try { @@ -45,10 +46,10 @@ class ThemeHandler { } } - unregisterTheme(themeName: string){ + unregisterTheme(themeName: string) { this.logger.info(`Unregistering theme: ${themeName}`); try { - const result = this.themes.where({name: themeName}).delete() + const result = this.themes.where({ name: themeName }).delete() this.logger.info(`Theme "${themeName}" unregistered`); return result; } catch (err) { @@ -57,21 +58,21 @@ class ThemeHandler { } } - getThemeByName(themeName: string){ + getThemeByName(themeName: string) { this.logger.debug(`Getting theme by name: ${themeName}`); - return this.themes.where({name: themeName}).first() + return this.themes.where({ name: themeName }).first() } - getThemeByCreator(themeCreator: string){ + getThemeByCreator(themeCreator: string) { this.logger.debug(`Getting themes by creator: ${themeCreator}`); - return this.themes.select(["*"]).where({creator: themeCreator}).all() + return this.themes.select(["*"]).where({ creator: themeCreator }).all() } - activateTheme(themeName: string, docRoot: HTMLElement){ + activateTheme(themeName: string, docRoot: HTMLElement) { this.logger.info(`Activating theme: ${themeName}`); const theme = this.getThemeByName(themeName) - if(!theme){ + if (!theme) { this.logger.error(`No theme found for name: ${themeName}`); return { success: false }; } @@ -80,7 +81,7 @@ class ThemeHandler { this.logger.debug(`Injecting theme variables for "${themeName}"`); injectVariables(theme.vars, docRoot) this.logger.info(`Theme "${themeName}" activated`); - } catch (error: unknown){ + } catch (error: unknown) { this.logger.error(`Error injecting theme variables: ${error as string}`); throw new Error(error as string) } diff --git a/apps/dockstat/app/.server/src/utils.ts b/apps/dockstat/app/.server/src/utils.ts index fd13d0a..08549c6 100644 --- a/apps/dockstat/app/.server/src/utils.ts +++ b/apps/dockstat/app/.server/src/utils.ts @@ -42,6 +42,7 @@ export async function startUp( `❌ Step ${i + 1} failed in ${taskName}: ${(err as Error).message}` ) startUpLogger.debug(`Error stack: ${(err as Error).stack}`); + process.exit(1) } } } @@ -60,6 +61,7 @@ export async function startUp( `❌ Async Step ${i + 1} failed in ${taskName}: ${(err as Error).message}` ) startUpLogger.debug(`Error stack: ${(err as Error).stack}`); + process.exit(1) } } } @@ -76,7 +78,7 @@ export function injectVariables(variables: Record, docRoot: HTML for (const [key, value] of Object.entries(variables)) { logger.debug(`Injecting variable: ${key} with value: ${value}`); try { - injectSingleCSSVar(key,value, docRoot) + injectSingleCSSVar(key, value, docRoot) logger.info(`Injected variable: ${key}`); } catch (err) { logger.error(`Failed to inject variable ${key}: ${err}`); @@ -98,7 +100,7 @@ function injectSingleCSSVar(variable: string, value: string, docRoot: HTMLElemen return value; } -export function getHostObjectFromForm(form: FormData, setId = false){ +export function getHostObjectFromForm(form: FormData, setId = false) { const host: DB_target_host = { id: setId ? Number.parseInt(String(form.get('id'))) : 0, host: String(form.get('host')), @@ -110,11 +112,11 @@ export function getHostObjectFromForm(form: FormData, setId = false){ return host } -export function getDockerClientFromAdapterID(DOA: [string, DockerClient][], form: FormData){ +export function getDockerClientFromAdapterID(DOA: [string, DockerClient][], form: FormData) { const aID = String(form.get('adapterID')) const AOBJ = DOA.find(([id]) => id === aID) - if(!AOBJ) {throw new Error(`Adapter ID ${aID} not found`)} + if (!AOBJ) { throw new Error(`Adapter ID ${aID} not found`) } const [id, client] = AOBJ - return {id: id, client: client} + return { id: id, client: client } } diff --git a/apps/dockstat/app/components/layout/Background.tsx b/apps/dockstat/app/components/layout/Background.tsx new file mode 100644 index 0000000..6d87d7d --- /dev/null +++ b/apps/dockstat/app/components/layout/Background.tsx @@ -0,0 +1,25 @@ +import type { THEME } from "@dockstat/typings"; + +export default function Background(theme: THEME.ThemeTable) { + if (theme.config.bg.useEffect) { + switch (theme.config.bg.effect) { + case "linear-gradient": { + return + } + } + } + return; +} + +type LinearBGProps = { + colors: string[] + direction: THEME.ThemeConfig["bg"]["linearGradientDirection"] +} + +function LinearBG({ direction, colors }: LinearBGProps) { + return ( + + + + ) +} diff --git a/apps/dockstat/app/components/ui/Nav.tsx b/apps/dockstat/app/components/ui/Nav.tsx index aa96457..77ec5c2 100644 --- a/apps/dockstat/app/components/ui/Nav.tsx +++ b/apps/dockstat/app/components/ui/Nav.tsx @@ -1,11 +1,10 @@ -import React, { useEffect, useRef, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import gsap from "gsap"; import { useGSAP } from "@gsap/react"; import { Button } from "./Button"; -import { Book, Bubbles, Github, Navigation, Table } from "lucide-react"; -import { Link } from "react-router"; +import { Book, Github, Navigation } from "lucide-react"; +import { Link, useLoaderData } from "react-router"; import Modal from "./Modal"; -import { Card, CardBody } from "./Card"; import NavCards from "./NavCards"; import { NavItems } from "~/utils/NavItem"; @@ -19,7 +18,7 @@ gsap.registerPlugin(useGSAP) * TailwindCSS required. */ -export default function Nav() { +export default function Nav({ links }: { links: { href: string, slug: string }[] }) { const navRef = useRef(null); const [scrolled, setScrolled] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false) @@ -102,9 +101,15 @@ export default function Nav() {
DockStat
- +
+ {links.map((link) => { + return ( + + {link.slug} + + ) + })} +
Docs +
) : ( diff --git a/apps/dockstat/app/components/ui/NavCards.tsx b/apps/dockstat/app/components/ui/NavCards.tsx index 417b929..8372648 100644 --- a/apps/dockstat/app/components/ui/NavCards.tsx +++ b/apps/dockstat/app/components/ui/NavCards.tsx @@ -28,8 +28,13 @@ export default function NavCards({ cards }: NavCardsProps) { {links.map(({ label, href, icon, ariaLabel }) => ( - - {icon &&
{icon}
} + + {icon &&
{icon}
} {label} ))} diff --git a/apps/dockstat/app/root.tsx b/apps/dockstat/app/root.tsx index d5c2b74..f926f12 100644 --- a/apps/dockstat/app/root.tsx +++ b/apps/dockstat/app/root.tsx @@ -12,10 +12,29 @@ import type { Route } from './+types/root' import { createLogger } from '@dockstat/logger'; import { Card, CardBody, CardFooter, CardHeader } from './components/ui/Card'; import Nav from './components/ui/Nav'; -export { loader, action } from "react-router-theme"; +import type { DATABASE, THEME } from '@dockstat/typings'; +import { useState } from 'react'; +import { useOutletContext } from 'react-router'; export const clientLogger = createLogger("DockStat-Client") +export { loader } from "~/routes/api.v1.conf" + +export function Body({ DB_Config, children }: { children: React.ReactNode, DB_Config: DATABASE.DB_config }) { + return ( + <> +
+
+ {children} + + + + ) +} export function Layout({ children }: { children: React.ReactNode }) { + const data = useLoaderData() + const context = useOutletContext() + return ( @@ -24,20 +43,21 @@ export function Layout({ children }: { children: React.ReactNode }) { - -