Skip to content
This repository was archived by the owner on Aug 2, 2025. It is now read-only.

Commit d3c645f

Browse files
committed
feat(database): Add theme support
This commit introduces a new table to the database, allowing users to store and manage custom themes for the application. It also provides the ability to save a default theme on first start. The following changes were made: - Added a table to the database schema. - Added functions to interact with the table. - Add a Theme handler to expose theme actions
1 parent a710987 commit d3c645f

9 files changed

Lines changed: 353 additions & 255 deletions

File tree

docker/docker-compose.dev.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ services:
55
image: lscr.io/linuxserver/socket-proxy:latest
66
volumes:
77
- /var/run/docker.sock:/var/run/docker.sock:ro
8-
restart: unless-stopped
8+
restart: never
99
read_only: true
1010
tmpfs:
1111
- /run
@@ -44,9 +44,10 @@ services:
4444
sqlite-web:
4545
container_name: sqlite-web
4646
image: ghcr.io/coleifer/sqlite-web:latest
47+
restart: never
4748
ports:
4849
- 8080:8080
4950
volumes:
50-
- ../data:/data:ro
51+
- /home/nik/Documents/Code-local/dockstat-project/DockStat/data:/data:ro
5152
environment:
5253
- SQLITE_DATABASE=dockstatapi.db

src/core/database/database.ts

Lines changed: 73 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -13,26 +13,26 @@ const uid = userInfo().uid;
1313
export let db: Database;
1414

1515
try {
16-
const databasePath = path.join(dataFolder, "dockstatapi.db");
17-
console.log("Database path:", databasePath);
18-
console.log(`Running as: ${username} (${uid}:${gid})`);
16+
const databasePath = path.join(dataFolder, "dockstatapi.db");
17+
console.log("Database path:", databasePath);
18+
console.log(`Running as: ${username} (${uid}:${gid})`);
1919

20-
if (!existsSync(dataFolder)) {
21-
await mkdir(dataFolder, { recursive: true, mode: 0o777 });
22-
console.log("Created data directory:", dataFolder);
23-
}
20+
if (!existsSync(dataFolder)) {
21+
await mkdir(dataFolder, { recursive: true, mode: 0o777 });
22+
console.log("Created data directory:", dataFolder);
23+
}
2424

25-
db = new Database(databasePath, { create: true });
26-
console.log("Database opened successfully");
25+
db = new Database(databasePath, { create: true });
26+
console.log("Database opened successfully");
2727

28-
db.exec("PRAGMA journal_mode = WAL;");
28+
db.exec("PRAGMA journal_mode = WAL;");
2929
} catch (error) {
30-
console.error(`Cannot start DockStatAPI: ${error}`);
31-
process.exit(500);
30+
console.error(`Cannot start DockStatAPI: ${error}`);
31+
process.exit(500);
3232
}
3333

3434
export function init() {
35-
db.exec(`
35+
db.exec(`
3636
CREATE TABLE IF NOT EXISTS backend_log_entries (
3737
timestamp STRING NOT NULL,
3838
level TEXT NOT NULL,
@@ -96,38 +96,68 @@ export function init() {
9696
slug TEXT NOT NULL,
9797
base TEXT NOT NULL
9898
);
99+
100+
CREATE TABLE IF NOT EXISTS themes (
101+
name TEXT NOT NULL,
102+
creator TEXT NOT NULL,
103+
vars TEXT NOT NULL,
104+
tags TEXT NOT NULL
105+
)
99106
`);
100107

101-
const configRow = db
102-
.prepare("SELECT COUNT(*) AS count FROM config")
103-
.get() as { count: number };
104-
105-
if (configRow.count === 0) {
106-
db.prepare(
107-
"INSERT INTO config (keep_data_for, fetching_interval) VALUES (7, 5)",
108-
).run();
109-
}
110-
111-
const hostRow = db
112-
.prepare("SELECT COUNT(*) AS count FROM docker_hosts")
113-
.get() as { count: number };
114-
115-
if (hostRow.count === 0) {
116-
db.prepare(
117-
"INSERT INTO docker_hosts (name, hostAddress, secure) VALUES (?, ?, ?)",
118-
).run("Localhost", "localhost:2375", false);
119-
}
120-
121-
const storeRow = db
122-
.prepare("SELECT COUNT(*) AS count FROM store_repos")
123-
.get() as { count: number };
124-
125-
if (storeRow.count === 0) {
126-
db.prepare("INSERT INTO store_repos (slug, base) VALUES (?, ?)").run(
127-
"DockStacks",
128-
"https://raw.githubusercontent.com/Its4Nik/DockStacks/refs/heads/main/Index.json",
129-
);
130-
}
108+
const themeRows = db
109+
.prepare("SELECT COUNT(*) AS count FROM themes")
110+
.get() as { count: number };
111+
112+
const defaultCss = `
113+
.root,
114+
#root,
115+
#docs-root {
116+
--accent: #f9a8d4;
117+
--secondary-accent: #fbcfe8;
118+
--border: #e5a3be;
119+
--muted-bg: #fbeff3;
120+
--gradient-from: #f8dbe2;
121+
--gradient-to: #f6e3eb;
122+
}
123+
`;
124+
125+
if (themeRows.count === 0) {
126+
db.prepare(
127+
"INSERT INTO themes (name, creator, vars, tags) VALUES (?,?,?,?)",
128+
).run("default", "Its4Nik", defaultCss, "[default]");
129+
}
130+
131+
const configRow = db
132+
.prepare("SELECT COUNT(*) AS count FROM config")
133+
.get() as { count: number };
134+
135+
if (configRow.count === 0) {
136+
db.prepare(
137+
"INSERT INTO config (keep_data_for, fetching_interval) VALUES (7, 5)",
138+
).run();
139+
}
140+
141+
const hostRow = db
142+
.prepare("SELECT COUNT(*) AS count FROM docker_hosts")
143+
.get() as { count: number };
144+
145+
if (hostRow.count === 0) {
146+
db.prepare(
147+
"INSERT INTO docker_hosts (name, hostAddress, secure) VALUES (?, ?, ?)",
148+
).run("Localhost", "localhost:2375", false);
149+
}
150+
151+
const storeRow = db
152+
.prepare("SELECT COUNT(*) AS count FROM store_repos")
153+
.get() as { count: number };
154+
155+
if (storeRow.count === 0) {
156+
db.prepare("INSERT INTO store_repos (slug, base) VALUES (?, ?)").run(
157+
"DockStacks",
158+
"https://raw.githubusercontent.com/Its4Nik/DockStacks/refs/heads/main/Index.json",
159+
);
160+
}
131161
}
132162

133163
init();

src/core/database/helper.ts

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,27 @@ import { logger } from "~/core/utils/logger";
22
import { backupInProgress } from "./_dbState";
33

44
export function executeDbOperation<T>(
5-
label: string,
6-
operation: () => T,
7-
validate?: () => void,
8-
dontLog?: boolean,
5+
label: string,
6+
operation: () => T,
7+
validate?: () => void,
8+
dontLog?: boolean,
99
): T {
10-
if (backupInProgress && label !== "backup" && label !== "restore") {
11-
throw new Error(
12-
`backup in progress Database operation not allowed: ${label}`,
13-
);
14-
}
15-
const startTime = Date.now();
16-
if (dontLog !== true) {
17-
logger.debug(`__task__ __db__ ${label} ⏳`);
18-
}
19-
if (validate) {
20-
validate();
21-
}
22-
const result = operation();
23-
const duration = Date.now() - startTime;
24-
if (dontLog !== true) {
25-
logger.debug(`__task__ __db__ ${label} ✔️ (${duration}ms)`);
26-
}
27-
return result;
10+
if (backupInProgress && label !== "backup" && label !== "restore") {
11+
throw new Error(
12+
`backup in progress Database operation not allowed: ${label}`,
13+
);
14+
}
15+
const startTime = Date.now();
16+
if (dontLog !== true) {
17+
logger.debug(`__task__ __db__ ${label} ⏳`);
18+
}
19+
if (validate) {
20+
validate();
21+
}
22+
const result = operation();
23+
const duration = Date.now() - startTime;
24+
if (dontLog !== true) {
25+
logger.debug(`__task__ __db__ ${label} ✔️ (${duration}ms)`);
26+
}
27+
return result;
2828
}

src/core/database/index.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,18 @@ import * as hostStats from "~/core/database/hostStats";
1010
import * as logs from "~/core/database/logs";
1111
import * as stacks from "~/core/database/stacks";
1212
import * as stores from "~/core/database/stores";
13+
import * as themes from "~/core/database/themes";
1314

1415
export const dbFunctions = {
15-
...dockerHosts,
16-
...logs,
17-
...config,
18-
...containerStats,
19-
...hostStats,
20-
...stacks,
21-
...backup,
22-
...stores,
16+
...dockerHosts,
17+
...logs,
18+
...config,
19+
...containerStats,
20+
...hostStats,
21+
...stacks,
22+
...backup,
23+
...stores,
24+
...themes,
2325
};
2426

2527
export type dbFunctions = typeof dbFunctions;

src/core/database/themes.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import type { Theme } from "~/typings/database";
2+
import { db } from "./database";
3+
import { executeDbOperation } from "./helper";
4+
5+
const stmt = {
6+
insert: db.prepare(`
7+
INSERT INTO themes (name, creator, vars, tags) VALUES (?, ?, ?, ?)
8+
`),
9+
remove: db.prepare(`DELETE FROM themes WHERE name = ?`),
10+
read: db.prepare(`SELECT * FROM themes WHERE name = ?`),
11+
readAll: db.prepare(`SELECT * FROM themes`),
12+
};
13+
14+
export function getThemes() {
15+
return executeDbOperation("Get Themes", () => stmt.readAll.all()) as Theme[];
16+
}
17+
18+
export function addTheme({ name, creator, vars, tags }: Theme) {
19+
return executeDbOperation("Save Theme", () =>
20+
stmt.insert.run(name, creator, vars, tags.toString()),
21+
);
22+
}
23+
export function getSpecificTheme(name: string): Theme {
24+
return executeDbOperation(
25+
"Getting specific Theme",
26+
() => stmt.read.get(name) as Theme,
27+
);
28+
}
29+
30+
export function deleteTheme(name: string) {
31+
return executeDbOperation("Remove Theme", () => stmt.remove.run(name));
32+
}

0 commit comments

Comments
 (0)