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

Commit ef74b6a

Browse files
committed
Feat: Database stats routes and more
1 parent ffab878 commit ef74b6a

9 files changed

Lines changed: 1167 additions & 972 deletions

File tree

Lines changed: 42 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,52 @@
1+
import type { containerStatistics } from "~/typings/database";
12
import { db } from "./database";
23
import { executeDbOperation } from "./helper";
34

4-
const stmt = db.prepare(`
5+
const insert = db.prepare(`
56
INSERT INTO container_stats (id, hostId, name, image, status, state, cpu_usage, memory_usage)
67
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
78
`);
89

10+
const get = db.prepare("SELECT * FROM container_stats");
11+
912
export function addContainerStats(
10-
id: string,
11-
hostId: string,
12-
name: string,
13-
image: string,
14-
status: string,
15-
state: string,
16-
cpu_usage: number,
17-
memory_usage: number,
13+
id: string,
14+
hostId: string,
15+
name: string,
16+
image: string,
17+
status: string,
18+
state: string,
19+
cpu_usage: number,
20+
memory_usage: number
1821
) {
19-
return executeDbOperation(
20-
"Add Container Stats",
21-
() =>
22-
stmt.run(id, hostId, name, image, status, state, cpu_usage, memory_usage),
23-
() => {
24-
if (
25-
typeof id !== "string" ||
26-
typeof hostId !== "string" ||
27-
typeof cpu_usage !== "number" ||
28-
typeof memory_usage !== "number"
29-
) {
30-
throw new TypeError("Invalid container stats parameters");
31-
}
32-
},
33-
);
22+
return executeDbOperation(
23+
"Add Container Stats",
24+
() =>
25+
insert.run(
26+
id,
27+
hostId,
28+
name,
29+
image,
30+
status,
31+
state,
32+
cpu_usage,
33+
memory_usage
34+
),
35+
() => {
36+
if (
37+
typeof id !== "string" ||
38+
typeof hostId !== "string" ||
39+
typeof cpu_usage !== "number" ||
40+
typeof memory_usage !== "number"
41+
) {
42+
throw new TypeError("Invalid container stats parameters");
43+
}
44+
}
45+
);
46+
}
47+
48+
export function getContainerStats(): containerStatistics[] {
49+
return executeDbOperation("Get Container Stats", () =>
50+
get.all()
51+
) as containerStatistics[];
3452
}

src/core/database/database.ts

Lines changed: 35 additions & 34 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,
@@ -59,7 +59,7 @@ export function init() {
5959
);
6060
6161
CREATE TABLE IF NOT EXISTS host_stats (
62-
hostId INTEGER PRIMARY KEY NOT NULL,
62+
hostId INTEGER NOT NULL,
6363
hostName TEXT NOT NULL,
6464
dockerVersion TEXT NOT NULL,
6565
apiVersion TEXT NOT NULL,
@@ -72,7 +72,8 @@ export function init() {
7272
containersRunning INTEGER NOT NULL,
7373
containersStopped INTEGER NOT NULL,
7474
containersPaused INTEGER NOT NULL,
75-
images INTEGER NOT NULL
75+
images INTEGER NOT NULL,
76+
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
7677
);
7778
7879
CREATE TABLE IF NOT EXISTS container_stats (
@@ -94,25 +95,25 @@ export function init() {
9495
);
9596
`);
9697

97-
const configRow = db
98-
.prepare("SELECT COUNT(*) AS count FROM config")
99-
.get() as { count: number };
100-
101-
if (configRow.count === 0) {
102-
db.prepare(
103-
'INSERT INTO config (keep_data_for, fetching_interval, api_key) VALUES (7, 5, "changeme")',
104-
).run();
105-
}
106-
107-
const hostRow = db
108-
.prepare("SELECT COUNT(*) AS count FROM docker_hosts")
109-
.get() as { count: number };
110-
111-
if (hostRow.count === 0) {
112-
db.prepare(
113-
"INSERT INTO docker_hosts (name, hostAddress, secure) VALUES (?, ?, ?)",
114-
).run("Localhost", "localhost:2375", false);
115-
}
98+
const configRow = db
99+
.prepare("SELECT COUNT(*) AS count FROM config")
100+
.get() as { count: number };
101+
102+
if (configRow.count === 0) {
103+
db.prepare(
104+
'INSERT INTO config (keep_data_for, fetching_interval, api_key) VALUES (7, 5, "changeme")'
105+
).run();
106+
}
107+
108+
const hostRow = db
109+
.prepare("SELECT COUNT(*) AS count FROM docker_hosts")
110+
.get() as { count: number };
111+
112+
if (hostRow.count === 0) {
113+
db.prepare(
114+
"INSERT INTO docker_hosts (name, hostAddress, secure) VALUES (?, ?, ?)"
115+
).run("Localhost", "localhost:2375", false);
116+
}
116117
}
117118

118119
init();

src/core/database/dockerHosts.ts

Lines changed: 45 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,62 @@
1+
import type { DockerHost } from "~/typings/docker";
12
import { db } from "./database";
23
import { executeDbOperation } from "./helper";
34

45
const stmt = {
5-
insert: db.prepare(
6-
"INSERT INTO docker_hosts (name, hostAddress, secure) VALUES (?, ?, ?)",
7-
),
8-
selectAll: db.prepare(
9-
"SELECT id, name, hostAddress, secure FROM docker_hosts ORDER BY id DESC",
10-
),
11-
update: db.prepare(
12-
"UPDATE docker_hosts SET hostAddress = ?, secure = ?, name = ? WHERE id = ?",
13-
),
14-
delete: db.prepare("DELETE FROM docker_hosts WHERE id = ?"),
6+
insert: db.prepare(
7+
"INSERT INTO docker_hosts (name, hostAddress, secure) VALUES (?, ?, ?)"
8+
),
9+
selectAll: db.prepare(
10+
"SELECT id, name, hostAddress, secure FROM docker_hosts ORDER BY id DESC"
11+
),
12+
update: db.prepare(
13+
"UPDATE docker_hosts SET hostAddress = ?, secure = ?, name = ? WHERE id = ?"
14+
),
15+
delete: db.prepare("DELETE FROM docker_hosts WHERE id = ?"),
1516
};
1617

1718
export function addDockerHost(host: DockerHost) {
18-
return executeDbOperation(
19-
"Add Docker Host",
20-
() => stmt.insert.run(host.name, host.hostAddress, host.secure),
21-
() => {
22-
if (!host.name || !host.hostAddress)
23-
throw new Error("Missing required fields");
24-
if (typeof host.secure !== "boolean")
25-
throw new TypeError("Invalid secure type");
26-
},
27-
);
19+
return executeDbOperation(
20+
"Add Docker Host",
21+
() => stmt.insert.run(host.name, host.hostAddress, host.secure),
22+
() => {
23+
if (!host.name || !host.hostAddress)
24+
throw new Error("Missing required fields");
25+
if (typeof host.secure !== "boolean")
26+
throw new TypeError("Invalid secure type");
27+
}
28+
);
2829
}
2930

3031
export function getDockerHosts(): DockerHost[] {
31-
return executeDbOperation("Get Docker Hosts", () => {
32-
const rows = stmt.selectAll.all() as Array<
33-
Omit<DockerHost, "secure"> & { secure: number }
34-
>;
35-
return rows.map((row) => ({
36-
...row,
37-
secure: row.secure === 1,
38-
}));
39-
});
32+
return executeDbOperation("Get Docker Hosts", () => {
33+
const rows = stmt.selectAll.all() as Array<
34+
Omit<DockerHost, "secure"> & { secure: number }
35+
>;
36+
return rows.map((row) => ({
37+
...row,
38+
secure: row.secure === 1,
39+
}));
40+
});
4041
}
4142
1;
4243
export function updateDockerHost(host: DockerHost) {
43-
return executeDbOperation(
44-
"Update Docker Host",
45-
() => stmt.update.run(host.hostAddress, host.secure, host.name, host.id),
46-
() => {
47-
if (!host.id || typeof host.id !== "number")
48-
throw new Error("Invalid host ID");
49-
},
50-
);
44+
return executeDbOperation(
45+
"Update Docker Host",
46+
() => stmt.update.run(host.hostAddress, host.secure, host.name, host.id),
47+
() => {
48+
if (!host.id || typeof host.id !== "number")
49+
throw new Error("Invalid host ID");
50+
}
51+
);
5152
}
5253

5354
export function deleteDockerHost(id: number) {
54-
return executeDbOperation(
55-
"Delete Docker Host",
56-
() => stmt.delete.run(id),
57-
() => {
58-
if (typeof id !== "number") throw new TypeError("Invalid ID type");
59-
},
60-
);
55+
return executeDbOperation(
56+
"Delete Docker Host",
57+
() => stmt.delete.run(id),
58+
() => {
59+
if (typeof id !== "number") throw new TypeError("Invalid ID type");
60+
}
61+
);
6162
}

src/core/database/hostStats.ts

Lines changed: 53 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,44 +2,64 @@ import type { HostStats } from "~/typings/docker";
22
import { db } from "./database";
33
import { executeDbOperation } from "./helper";
44

5-
const stmt = db.prepare(`
5+
const insert = db.prepare(`
66
INSERT INTO host_stats (
77
hostId, hostName, dockerVersion, apiVersion, os, architecture,
88
totalMemory, totalCPU, labels, containers, containersRunning,
99
containersStopped, containersPaused, images
1010
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
11-
ON CONFLICT(hostId) DO UPDATE SET
12-
dockerVersion = excluded.dockerVersion,
13-
apiVersion = excluded.apiVersion,
14-
os = excluded.os,
15-
architecture = excluded.architecture,
16-
totalMemory = excluded.totalMemory,
17-
totalCPU = excluded.totalCPU,
18-
labels = excluded.labels,
19-
containers = excluded.containers,
20-
containersRunning = excluded.containersRunning,
21-
containersStopped = excluded.containersStopped,
22-
containersPaused = excluded.containersPaused,
23-
images = excluded.images
2411
`);
2512

26-
export function updateHostStats(stats: HostStats) {
27-
return executeDbOperation("Update Host Stats", () =>
28-
stmt.run(
29-
stats.hostId,
30-
stats.hostName,
31-
stats.dockerVersion,
32-
stats.apiVersion,
33-
stats.os,
34-
stats.architecture,
35-
stats.totalMemory,
36-
stats.totalCPU,
37-
JSON.stringify(stats.labels),
38-
stats.containers,
39-
stats.containersRunning,
40-
stats.containersStopped,
41-
stats.containersPaused,
42-
stats.images,
43-
),
44-
);
13+
const selectStmt = db.prepare(`
14+
SELECT *
15+
FROM host_stats
16+
`);
17+
18+
export function addHostStats(stats: HostStats) {
19+
return executeDbOperation(
20+
"Update Host Stats",
21+
() =>
22+
insert.run(
23+
stats.hostId,
24+
stats.hostName,
25+
stats.dockerVersion,
26+
stats.apiVersion,
27+
stats.os,
28+
stats.architecture,
29+
stats.totalMemory,
30+
stats.totalCPU,
31+
JSON.stringify(stats.labels),
32+
stats.containers,
33+
stats.containersRunning,
34+
stats.containersStopped,
35+
stats.containersPaused,
36+
stats.images
37+
),
38+
() => {
39+
if (
40+
typeof stats.hostId !== "number" ||
41+
typeof stats.hostName !== "string" ||
42+
typeof stats.dockerVersion !== "string" ||
43+
typeof stats.apiVersion !== "string" ||
44+
typeof stats.os !== "string" ||
45+
typeof stats.architecture !== "string" ||
46+
typeof stats.totalMemory !== "number" ||
47+
typeof stats.totalCPU !== "number" ||
48+
typeof JSON.stringify(stats.labels) !== "string" ||
49+
typeof stats.containers !== "number" ||
50+
typeof stats.containersRunning !== "number" ||
51+
typeof stats.containersStopped !== "number" ||
52+
typeof stats.containersPaused !== "number" ||
53+
typeof stats.images !== "number"
54+
) {
55+
throw new TypeError(`Invalid Host Stats! - ${stats}`);
56+
}
57+
}
58+
);
59+
}
60+
61+
export function getHostStats(): HostStats[] {
62+
return executeDbOperation("Get Host Stats", () =>
63+
selectStmt.all()
64+
) as HostStats[];
4565
}

0 commit comments

Comments
 (0)