Initial commit
This commit is contained in:
Generated
+2747
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "unraid-monitor-backend",
|
||||
"version": "1.0.0",
|
||||
"description": "Backend API for Unraid Monitor PWA",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"dev": "ts-node-dev --respawn --transpile-only src/index.ts",
|
||||
"build": "tsc",
|
||||
"start": "node dist/index.js",
|
||||
"lint": "eslint src --ext .ts",
|
||||
"hash": "ts-node-dev --transpile-only src/hash.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.3.1",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"dockerode": "^4.0.2",
|
||||
"systeminformation": "^5.12.20",
|
||||
"express-validator": "^7.0.1",
|
||||
"helmet": "^7.1.0",
|
||||
"express-rate-limit": "^7.1.5",
|
||||
"uuid": "^9.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/jsonwebtoken": "^9.0.5",
|
||||
"@types/bcryptjs": "^2.4.6",
|
||||
"@types/node": "^20.10.6",
|
||||
"typescript": "^5.3.3",
|
||||
"ts-node-dev": "^2.0.0",
|
||||
"@types/uuid": "^9.0.7",
|
||||
"@types/dockerode": "3.3.46"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
import bcrypt from 'bcryptjs';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { LoginRequest, LoginResponse, User } from "../types";
|
||||
import { Request, Response } from 'express';
|
||||
import { generateToken } from '../middleware/auth';
|
||||
|
||||
const users: User[] = [];
|
||||
|
||||
const initializeDefaultUser = () => {
|
||||
if (users.length === 0) {
|
||||
const hashedPassword = '$2a$10$dUjaDLdCnYdEJYRUNe5jful8f94cfYvqM0fgpiajcIEcBQllz2076';
|
||||
users.push({
|
||||
id: uuidv4(),
|
||||
username: 'admin',
|
||||
password: hashedPassword,
|
||||
createdAt: new Date()
|
||||
});
|
||||
console.log('Default user created: admin');
|
||||
}
|
||||
};
|
||||
|
||||
initializeDefaultUser();
|
||||
|
||||
export class AuthController {
|
||||
async login(req: Request, res: Response) {
|
||||
try {
|
||||
const { username, password }: LoginRequest = req.body;
|
||||
|
||||
if (!username || !password) {
|
||||
return res.status(400).json({ error: 'Username and password required' });
|
||||
}
|
||||
|
||||
const user = users.find(u => u.username === username);
|
||||
|
||||
if (!user) {
|
||||
return res.status(401).json({ error: 'Invalid credentials' });
|
||||
}
|
||||
|
||||
const isValidPassword = bcrypt.compareSync(password, user.password);
|
||||
|
||||
if (!isValidPassword) {
|
||||
return res.status(401).json({ error: 'Invalid credentials' });
|
||||
}
|
||||
|
||||
const token = generateToken({ id: user.id, username: user.username });
|
||||
|
||||
const response: LoginResponse = {
|
||||
token,
|
||||
user: {
|
||||
id: user.id,
|
||||
username: user.username
|
||||
}
|
||||
};
|
||||
|
||||
res.json(response);
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
}
|
||||
|
||||
async register(req: Request, res: Response) {
|
||||
try {
|
||||
const { username, password } = req.body;
|
||||
|
||||
if (!username || !password) {
|
||||
return res.status(400).json({ error: 'Username and password required' });
|
||||
}
|
||||
|
||||
if (password.length < 6) {
|
||||
return res.status(400).json({ error:'Password must be at least 6 characters' });
|
||||
}
|
||||
|
||||
const existingUser = users.find(u => u.username === username);
|
||||
|
||||
if (existingUser) {
|
||||
return res.status(409).json({ error: 'Username already exists' });
|
||||
}
|
||||
|
||||
const hashedPassword = bcrypt.hashSync(password, 10);
|
||||
|
||||
const newUser: User = {
|
||||
id: uuidv4(),
|
||||
username,
|
||||
password: hashedPassword,
|
||||
createdAt: new Date()
|
||||
};
|
||||
|
||||
users.push(newUser);
|
||||
|
||||
const token = generateToken({ id: newUser.id, username: newUser.username });
|
||||
|
||||
const response: LoginResponse = {
|
||||
token,
|
||||
user: {
|
||||
id: newUser.id,
|
||||
username: newUser.username
|
||||
}
|
||||
};
|
||||
|
||||
res.status(201).json(response);
|
||||
} catch (error) {
|
||||
console.error('Register error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' })
|
||||
}
|
||||
}
|
||||
|
||||
async me(req: Request, res: Response) {
|
||||
try {
|
||||
// @ts-ignore - user wird durch authMiddleware gesetzt
|
||||
const userId = req.user.id;
|
||||
|
||||
const user = users.find(u => u.id === userId);
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: 'User not found' });
|
||||
}
|
||||
|
||||
res.json({
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
createdAt: user.createdAt
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Me error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import { Response } from 'express';
|
||||
import { DockerService } from '../services/dockerService';
|
||||
import { AuthRequest } from '../types';
|
||||
import { toError } from '../utils/helper';
|
||||
|
||||
const dockerService = new DockerService();
|
||||
|
||||
export class DockerController {
|
||||
async getContainers(req: AuthRequest, res: Response) {
|
||||
try {
|
||||
const containers = await dockerService.getContainers();
|
||||
res.json(containers);
|
||||
} catch (error) {
|
||||
console.error('Error getting containers:', error);
|
||||
res.status(500).json(toError('Failed to retrieve containers'));
|
||||
}
|
||||
}
|
||||
|
||||
async getContainer(req: AuthRequest, res: Response) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const container = await dockerService.getContainer(id);
|
||||
|
||||
if (!container) {
|
||||
return res.status(404).json(toError('Container not found'));
|
||||
}
|
||||
|
||||
res.json(container);
|
||||
} catch (error) {
|
||||
console.error('Error getting container:', error);
|
||||
res.status(500).json(toError('Failed to retrieve container'));
|
||||
}
|
||||
}
|
||||
|
||||
async startContainer(req: AuthRequest, res: Response) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
await dockerService.startContainer(id);
|
||||
res.json({ message: 'Container started successfully' });
|
||||
} catch (error) {
|
||||
console.error('Error starting container:', error);
|
||||
res.status(500).json(toError('Failed to start container'));
|
||||
}
|
||||
}
|
||||
|
||||
async stopContainer(req: AuthRequest, res: Response) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
await dockerService.stopContainer(id);
|
||||
res.json({ message: 'Container stopped successfully' });
|
||||
} catch (error) {
|
||||
console.error('Error stopping container:', error);
|
||||
res.status(500).json(toError('Failed to stop container'));
|
||||
}
|
||||
}
|
||||
|
||||
async restartContainer(req: AuthRequest, res: Response) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
await dockerService.restartContainer(id);
|
||||
res.json({ message: 'Container restarted successfully' });
|
||||
} catch (error) {
|
||||
console.error('Error restarting container:', error);
|
||||
res.status(500).json(toError('Failed to restart container'));
|
||||
}
|
||||
}
|
||||
|
||||
async getContainerLogs(req: AuthRequest, res: Response) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const tail = parseInt(req.query.tail as string) || 100;
|
||||
const logs = await dockerService.getContainerLogs(id, tail);
|
||||
res.json({ logs });
|
||||
} catch (error) {
|
||||
console.error('Error getting container logs:', error);
|
||||
res.status(500).json(toError('Failed to retrieve container logs'));
|
||||
}
|
||||
}
|
||||
|
||||
async getContainerStats(req: AuthRequest, res: Response) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const stats = await dockerService.getContainerStats(id);
|
||||
|
||||
if (!stats) {
|
||||
return res.status(404).json(toError('Container stats not available'));
|
||||
}
|
||||
|
||||
res.json(stats);
|
||||
} catch (error) {
|
||||
console.error('Error getting container stats:', error);
|
||||
res.status(500).json(toError('Failed to retrieve container stats'));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { Response } from 'express';
|
||||
import { SystemService } from '../services/systemService';
|
||||
import { AuthRequest } from '../types';
|
||||
import { toError } from '../utils/helper';
|
||||
|
||||
const systemService = new SystemService();
|
||||
|
||||
export class SystemController {
|
||||
async getStats(req: AuthRequest, res: Response) {
|
||||
try {
|
||||
const stats = await systemService.getSystemStats();
|
||||
res.json(stats);
|
||||
} catch (error) {
|
||||
console.error('Error getting system stats: ', error);
|
||||
res.status(500).json(toError('Failed to retrieve system statistics'));
|
||||
}
|
||||
}
|
||||
|
||||
async getUptime(req: AuthRequest, res: Response) {
|
||||
try {
|
||||
const stats = await systemService.getSystemStats();
|
||||
res.json({
|
||||
uptime: stats.uptime,
|
||||
formatted: systemService.formatUptime(stats.uptime)
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error getting uptime:', error);
|
||||
res.status(500).json(toError('Failed to retrieve uptime'));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import bcrypt from 'bcryptjs';
|
||||
|
||||
const pw = process.argv[2];
|
||||
if (!pw) {
|
||||
console.error('Argument muss übergeben werden!');
|
||||
process.exit(-1);
|
||||
}
|
||||
const hash = bcrypt.hashSync(pw, 10);
|
||||
console.log(`Hash: ${hash}`);
|
||||
@@ -0,0 +1,86 @@
|
||||
import express from 'express';
|
||||
import cors from 'cors';
|
||||
import helmet from 'helmet';
|
||||
import dotenv from 'dotenv';
|
||||
import rateLimit from 'express-rate-limit';
|
||||
import { AuthController } from './controllers/authController';
|
||||
import { SystemController } from './controllers/systemController';
|
||||
import { authMiddleware } from './middleware/auth';
|
||||
import { toError } from './utils/helper';
|
||||
import { DockerController } from './controllers/dockerController';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 4000;
|
||||
|
||||
app.use(helmet());
|
||||
app.use(cors({
|
||||
origin: process.env.FRONTEND_URL || '*',
|
||||
credentials: true
|
||||
}));
|
||||
app.use(express.json());
|
||||
|
||||
const limiter = rateLimit({
|
||||
windowMs: 15 * 60 * 1000, // 15 Minuten
|
||||
max: 100 // Max 100 Requests pro IP
|
||||
});
|
||||
app.use('/api/', limiter);
|
||||
|
||||
const authController = new AuthController();
|
||||
const systemController = new SystemController();
|
||||
const dockerController = new DockerController();
|
||||
|
||||
// Health Check
|
||||
app.get('/health', (req, res) => {
|
||||
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
||||
});
|
||||
|
||||
// Auth Routes (ohne authMiddleware)
|
||||
app.post('/api/auth/login', authController.login.bind(authController));
|
||||
app.post('/api/auth/register', authController.register.bind(authController));
|
||||
|
||||
// Protected Routes
|
||||
app.get('/api/auth/me', authMiddleware, authController.me.bind(authController));
|
||||
|
||||
// System Routes
|
||||
app.get('/api/system/stats', authMiddleware, systemController.getStats.bind(systemController));
|
||||
app.get('/api/system/uptime', authMiddleware, systemController.getUptime.bind(systemController));
|
||||
|
||||
// Docker Routes
|
||||
app.get('/api/docker/containers', authMiddleware, dockerController.getContainers.bind(dockerController));
|
||||
app.get('/api/docker/containers/:id', authMiddleware, dockerController.getContainer.bind(dockerController));
|
||||
app.post('/api/docker/containers/:id/start', authMiddleware, dockerController.startContainer.bind(dockerController));
|
||||
app.post('/api/docker/containers/:id/stop:', authMiddleware, dockerController.stopContainer.bind(dockerController));
|
||||
app.post('/api/docker/containers/:id/restart', authMiddleware, dockerController.restartContainer.bind(dockerController));
|
||||
app.get('/api/docker/containers/:id/logs', authMiddleware, dockerController.getContainerLogs.bind(dockerController));
|
||||
app.get('/api/docker/containers/:id/stats', authMiddleware, dockerController.getContainerStats.bind(dockerController));
|
||||
|
||||
// Error Handler
|
||||
app.use((err: Error, req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
console.error('Error:', err);
|
||||
res.status(500).json(toError('Internal server error'));
|
||||
});
|
||||
|
||||
// 404 Handler
|
||||
app.use((req, res) => {
|
||||
res.status(404).json(toError('Route not found'));
|
||||
});
|
||||
|
||||
// Server starten
|
||||
app.listen(PORT, () => {
|
||||
console.log(`🚀 Server running on port ${PORT}`);
|
||||
console.log(`📊 Environment: ${process.env.NODE_ENV || 'development'}`);
|
||||
console.log(`🔒 JWT Secret configured: ${!!process.env.JWT_SECRET}`);
|
||||
});
|
||||
|
||||
// Graceful shutdown
|
||||
process.on('SIGTERM', () => {
|
||||
console.log('SIGTERM signal received: closing HTTP server');
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
console.log('SIGTERM signal received: closing HTTP server');
|
||||
process.exit(0);
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
import { NextFunction, Response } from "express";
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { AuthRequest } from "../types";
|
||||
|
||||
const JWT_SECRET = process.env.JWT_SECRET || "changeit";
|
||||
|
||||
export const authMiddleware = (
|
||||
req: AuthRequest,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) => {
|
||||
try {
|
||||
const authHeader = req.headers.authorization;
|
||||
|
||||
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
||||
return res.status(401).json({ error: 'No token provided' });
|
||||
}
|
||||
|
||||
const token = authHeader.substring(7);
|
||||
|
||||
const decoded = jwt.verify(token, JWT_SECRET) as {
|
||||
id: string;
|
||||
username: string;
|
||||
};
|
||||
|
||||
req.user = decoded;
|
||||
next();
|
||||
} catch (error) {
|
||||
return res.status(401).json({ error: 'Invalid token' });
|
||||
}
|
||||
};
|
||||
|
||||
export const generateToken = (user: { id: string; username: string }): string => {
|
||||
return jwt.sign(
|
||||
{ id: user.id, username: user.username },
|
||||
JWT_SECRET,
|
||||
{ expiresIn: '7d' }
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,128 @@
|
||||
import Docker from 'dockerode';
|
||||
import { DockerContainer, DockerStats } from '../types';
|
||||
|
||||
export class DockerService {
|
||||
private docker: Docker;
|
||||
|
||||
constructor() {
|
||||
this.docker = new Docker({ socketPath: '/var/run/docker.sock' });
|
||||
}
|
||||
|
||||
async getContainers(): Promise<DockerContainer[]> {
|
||||
try {
|
||||
const containers = await this.docker.listContainers({ all: true });
|
||||
return containers.map((container) => ({
|
||||
id: container.Id.substring(0, 12),
|
||||
name: container.Names[0].replace('/', ''),
|
||||
image: container.Image,
|
||||
state: container.State,
|
||||
status: container.Status,
|
||||
created: container.Created,
|
||||
ports: container.Ports.map(port => ({
|
||||
privatePort: port.PrivatePort,
|
||||
publicPort: port.PublicPort,
|
||||
type: port.Type
|
||||
}))
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('Error getting containers:', error);
|
||||
throw new Error('Failed to retrieve Docker containers');
|
||||
}
|
||||
}
|
||||
|
||||
async getContainer(id: string): Promise<DockerContainer | null> {
|
||||
try {
|
||||
const container = this.docker.getContainer(id);
|
||||
const info = await container.inspect();
|
||||
|
||||
return {
|
||||
id: info.Id.substring(0, 12),
|
||||
name: info.Name.replace('/', ''),
|
||||
image: info.Config.Image,
|
||||
state: info.State.Status,
|
||||
status: info.State.Running ? 'running' : 'stopped',
|
||||
created: new Date(info.Created).getTime() / 1000,
|
||||
ports: Object.entries(info.NetworkSettings.Ports || {}).flatMap(([port, bindings]) => {
|
||||
if (!bindings) return [];
|
||||
return bindings.map(binding => ({
|
||||
privatePort: parseInt(port.split('/')[0]),
|
||||
publicPort: binding.HostPort ? parseInt(binding.HostPort) : undefined,
|
||||
type: port.split('/')[1]
|
||||
}));
|
||||
})
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error getting container:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async startContainer(id: string): Promise<void> {
|
||||
try {
|
||||
const container = this.docker.getContainer(id);
|
||||
await container.start();
|
||||
} catch (error) {
|
||||
console.error('Error starting container:', error);
|
||||
throw new Error('Failed to start container');
|
||||
}
|
||||
}
|
||||
|
||||
async stopContainer(id: string): Promise<void> {
|
||||
try {
|
||||
const container = this.docker.getContainer(id);
|
||||
await container.stop();
|
||||
} catch (error) {
|
||||
console.error('Error stopping container:', error);
|
||||
throw new Error('Failed to stop container');
|
||||
}
|
||||
}
|
||||
|
||||
async restartContainer(id: string): Promise<void> {
|
||||
try {
|
||||
const container = this.docker.getContainer(id);
|
||||
await container.restart();
|
||||
} catch (error) {
|
||||
console.error('Error restarting container:', error);
|
||||
throw new Error('Failed to restart container');
|
||||
}
|
||||
}
|
||||
|
||||
async getContainerLogs(id: string, tail: number = 100): Promise<string> {
|
||||
try {
|
||||
const container = this.docker.getContainer(id);
|
||||
const logs = await container.logs({
|
||||
stdout: true,
|
||||
stderr: true,
|
||||
tail,
|
||||
timestamps: true
|
||||
});
|
||||
|
||||
return logs.toString('utf-8');
|
||||
} catch (error) {
|
||||
console.error('Error getting container logs:', error);
|
||||
throw new Error('Failed to retrieve container logs');
|
||||
}
|
||||
}
|
||||
|
||||
async getContainerStats(id: string): Promise<DockerStats | null> {
|
||||
try {
|
||||
const container = this.docker.getContainer(id);
|
||||
const stats = await container.stats({ stream: false });
|
||||
|
||||
const cpuDelta = stats.cpu_stats.cpu_usage.total_usage - stats.precpu_stats.cpu_usage.total_usage;
|
||||
const systemDelta = stats.cpu_stats.system_cpu_usage - stats.precpu_stats.system_cpu_usage;
|
||||
const cpuUsage = (cpuDelta / systemDelta) * stats.cpu_stats.online_cpus * 100;
|
||||
|
||||
return {
|
||||
cpuUsage: Math.round(cpuUsage * 10) / 10,
|
||||
memoryUsage: stats.memory_stats.usage,
|
||||
memoryLimit: stats.memory_stats.limit,
|
||||
networkRx: stats.networks?.eth0?.rx_bytes || 0,
|
||||
networkTx: stats.networks?.eth0?.tx_bytes || 0
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error getting container stats:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import si from 'systeminformation';
|
||||
import { SystemStats } from "../types";
|
||||
|
||||
export class SystemService {
|
||||
async getSystemStats(): Promise<SystemStats> {
|
||||
try {
|
||||
const [cpu, mem, fsSize, currentLoad, osInfo, cpuTemp] = await Promise.all([
|
||||
si.cpu(),
|
||||
si.mem(),
|
||||
si.fsSize(),
|
||||
si.currentLoad(),
|
||||
si.osInfo(),
|
||||
si.cpuTemperature()
|
||||
]);
|
||||
|
||||
const disks = fsSize
|
||||
.filter(disk =>
|
||||
disk.mount !== '/boot' &&
|
||||
disk.mount !== '/dev' &&
|
||||
!disk.mount.startsWith('/var/lib/docker') &&
|
||||
disk.size > 0
|
||||
)
|
||||
.map(disk => ({
|
||||
device: disk.fs,
|
||||
mountpoint: disk.mount,
|
||||
size: disk.size,
|
||||
used: disk.used,
|
||||
available: disk.available,
|
||||
percentage: disk.use
|
||||
}));
|
||||
|
||||
return {
|
||||
cpu: {
|
||||
usage: Math.round(currentLoad.currentLoad * 10) / 10,
|
||||
temp: cpuTemp.main || null,
|
||||
model: cpu.brand
|
||||
},
|
||||
memory: {
|
||||
total: mem.total,
|
||||
used: mem.used,
|
||||
free: mem.free,
|
||||
percentage: Math.round((mem.used / mem.total) * 100 * 10) / 10
|
||||
},
|
||||
disk: disks,
|
||||
// TODO: Wo bekommt man die Uptime her?
|
||||
uptime: 0,
|
||||
hostname: osInfo.hostname
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error getting system stats:', error);
|
||||
throw new Error('Failed to retrieve system statistics');
|
||||
}
|
||||
}
|
||||
|
||||
formatUptime(seconds: number): string {
|
||||
const days = Math.floor(seconds / 86400);
|
||||
const hours = Math.floor((seconds % 86400) / 3600);
|
||||
const minutes = Math.floor((seconds % 3600) / 60);
|
||||
|
||||
if (days > 0) {
|
||||
return `${days}d ${hours}h`;
|
||||
} else if (hours > 0) {
|
||||
return `${hours}h ${minutes}m`;
|
||||
} else {
|
||||
return `${minutes}m`;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import { Request } from 'express';
|
||||
|
||||
export interface User {
|
||||
id: string;
|
||||
username: string;
|
||||
password: string;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
export interface AuthRequest extends Request {
|
||||
user?: {
|
||||
id: string;
|
||||
username: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface SystemStats {
|
||||
cpu: {
|
||||
usage: number;
|
||||
temp: number | null;
|
||||
model: string;
|
||||
};
|
||||
memory: {
|
||||
total: number;
|
||||
used: number;
|
||||
free: number;
|
||||
percentage: number;
|
||||
};
|
||||
disk: Array<{
|
||||
device: string;
|
||||
mountpoint: string;
|
||||
size: number;
|
||||
used: number;
|
||||
available: number;
|
||||
percentage: number;
|
||||
}>;
|
||||
uptime: number;
|
||||
hostname: string;
|
||||
}
|
||||
|
||||
export interface DockerContainer {
|
||||
id: string;
|
||||
name: string;
|
||||
image: string;
|
||||
state: string;
|
||||
status: string;
|
||||
created: number;
|
||||
ports: Array<{
|
||||
privatePort: number;
|
||||
publicPort?: number;
|
||||
type: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface DockerStats {
|
||||
cpuUsage: number;
|
||||
memoryUsage: number;
|
||||
memoryLimit: number;
|
||||
networkRx: number;
|
||||
networkTx: number;
|
||||
}
|
||||
|
||||
export interface LoginRequest {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface LoginResponse {
|
||||
token: string;
|
||||
user: {
|
||||
id: string;
|
||||
username: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ErrorResponse {
|
||||
error: string;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { ErrorResponse } from "../types";
|
||||
|
||||
export const toError = (errorText: string): ErrorResponse => {
|
||||
return { error: errorText };
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2020",
|
||||
"module": "commonjs",
|
||||
"lib": [
|
||||
"ES2020"
|
||||
],
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"moduleResolution": "node",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user