Skip to content
This repository was archived by the owner on Jul 28, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
b775056
feat: add notify-all feature to admin settings
ddrayko Jun 30, 2026
1ea298a
feat: add modal popup for truncated notification messages
ddrayko Jun 30, 2026
f4b491e
fix: allow clicking read notifications to view full message in modal
ddrayko Jun 30, 2026
54c1239
Replace logo URLs: status.zero-host.org/upload/logo1.png → img.zero-h…
ddrayko Jul 1, 2026
1b46c5d
Fix favicon: switch to SVG for auto-scaling
ddrayko Jul 1, 2026
036f7c6
Add graceful shutdown handler with SIGTERM/SIGINT support
ddrayko Jul 1, 2026
6ed7979
Add request ID middleware for request tracing
ddrayko Jul 1, 2026
a9c9aa1
Add dedicated rate limiter for activity endpoint
ddrayko Jul 1, 2026
57cac64
Add input size limits and type validation to auth routes
ddrayko Jul 1, 2026
0b3030e
Add path traversal protection for avatar file operations
ddrayko Jul 1, 2026
e61938d
Bump token version on password change to invalidate other sessions
ddrayko Jul 1, 2026
c5af265
Add database connection pool monitoring and keep-alive
ddrayko Jul 1, 2026
aab6086
Add rate limiting to admin login and notify-all routes
ddrayko Jul 1, 2026
5bdc4fe
Add input validation and sanitization to admin routes
ddrayko Jul 1, 2026
193eca5
Add XSS protection for user-submitted content in UI rendering
ddrayko Jul 1, 2026
ef31d88
Add environment variable validation at startup
ddrayko Jul 1, 2026
62b853a
Add rate limiting to sensitive auth routes
ddrayko Jul 1, 2026
87ee4b9
Add memory cache cleanup and pagination limits in pyrodactyl service
ddrayko Jul 1, 2026
260c536
Add input validation and rate limiting to servers routes
ddrayko Jul 1, 2026
70f2be5
Add rate limiting to server renew and reinstall endpoints
ddrayko Jul 1, 2026
09e645e
Add brute force protection with incremental login delay
ddrayko Jul 1, 2026
014ea42
Add auto-cleanup for old notifications and activity logs
ddrayko Jul 1, 2026
a52c844
Add CSP upgrade-insecure-requests, concurrent request limiting, and r…
ddrayko Jul 1, 2026
33419fc
Add rate limiting to notification routes
ddrayko Jul 1, 2026
5fbbc8c
Add JSON parse error handling, method restriction, and urlencoded bod…
ddrayko Jul 1, 2026
0639e75
Add circuit breaker for Pterodactyl API calls
ddrayko Jul 1, 2026
b0407b5
Add additional security headers (X-Frame-Options, X-Robots-Tag)
ddrayko Jul 1, 2026
8dae459
Add database query timeout and connection leak detection
ddrayko Jul 1, 2026
12019be
Add rate limiting and input validation for admin API routes
ddrayko Jul 1, 2026
205f5c8
Add content-type validation middleware for API endpoints
ddrayko Jul 1, 2026
e6a8a0a
Add per-user rate limiting and API rate limit by route group
ddrayko Jul 1, 2026
d748dc1
Add error message sanitization to prevent information leakage
ddrayko Jul 1, 2026
d85538d
Enhance healthcheck endpoint with memory and uptime metrics
ddrayko Jul 1, 2026
42b6d78
Add process warning handler and memory leak detection
ddrayko Jul 1, 2026
a3b4336
Add input validation for nest and egg settings admin routes
ddrayko Jul 1, 2026
a4a155d
Add database indexes for query performance optimization
ddrayko Jul 1, 2026
79442a4
Add additional security headers
ddrayko Jul 1, 2026
4302ece
Add input type validation for change-password and change-email routes
ddrayko Jul 1, 2026
093b293
Add secure defaults and configuration hardening
ddrayko Jul 1, 2026
9057eef
Add rate limiting to avatar upload and data export routes
ddrayko Jul 1, 2026
d5313c9
Add large result set warning for database queries
ddrayko Jul 1, 2026
a2ab81f
Add rate limiting to server power actions
ddrayko Jul 1, 2026
a5ef137
Fix duplicate notifyAllLimiter declaration
ddrayko Jul 1, 2026
b8f3a2e
Fix syntax error and runtime bugs
ddrayko Jul 1, 2026
b7a8639
Fix database connection failure at startup
ddrayko Jul 1, 2026
b3f80fe
Remove rate limiting from admin panel routes (keep auth only)
ddrayko Jul 1, 2026
2810196
Fix circuit breaker: auto-reset after 30s cooldown, increase threshol…
ddrayko Jul 1, 2026
2e0d813
Remove circuit breaker from pteroFetch, let retries handle failures
ddrayko Jul 1, 2026
6afea2b
Add Docker image selection UI to server creation flow
ddrayko Jul 2, 2026
f0c7e0b
Fix docker select: show short name and use custom select dropdown
ddrayko Jul 2, 2026
e94a3c9
Show short Docker image name in dropdown
ddrayko Jul 2, 2026
3bb0279
feat: rewrite /create page with step-by-step wizard and admin logo/de…
ddrayko Jul 2, 2026
be570cc
feat: add slide transitions and progress indicator to wizard
ddrayko Jul 2, 2026
6a747c1
refactor: inline renderCreateServer into navigation handlers
ddrayko Jul 2, 2026
fc66e06
style: force 5 columns for nest grid
ddrayko Jul 2, 2026
1bc8098
style: revert nest grid to auto-fill
ddrayko Jul 2, 2026
287b87f
style: 5 fixed columns for nest grid with max card width
ddrayko Jul 2, 2026
ccc7235
style: adaptive nest grid, fills row width, max 5 per line
ddrayko Jul 2, 2026
3f9f865
style: remove wizard-content max-width to let nest grid fill screen
ddrayko Jul 2, 2026
67af1bf
style: use auto-fit so nest cards fill full width
ddrayko Jul 2, 2026
3270b6f
fix: improve docker image label fallback in summary
ddrayko Jul 2, 2026
0d9ccc8
bump version to v1.0.3
ddrayko Jul 2, 2026
1c8eaf9
fix: docker_images format is displayName:imageUrl, not imageUrl:displ…
ddrayko Jul 2, 2026
428a374
Update README.md
ddrayko Jul 2, 2026
0608428
style: replace docker image cards with custom select dropdown
ddrayko Jul 3, 2026
00df51f
feat: update URL with tab name when switching server/user tabs
ddrayko Jul 3, 2026
c24517e
feat: make nest and egg logos monochrome on /create page
ddrayko Jul 3, 2026
ce2cef9
fix: init lucide icons before early return in empty server list
ddrayko Jul 3, 2026
6a6fe31
Merge pull request #14 from ZeroHost-Code/main
ddrayko Jul 3, 2026
83f4f5f
fix: use grayscale instead of white monochrome for nest/egg logos
ddrayko Jul 3, 2026
01a9c87
feat: align Next button to the right in wizard
ddrayko Jul 3, 2026
932d43a
fix: dropdown not showing because CSS expects .open on .custom-select…
ddrayko Jul 3, 2026
1dddfda
fix: dropdown clipped by overflow:hidden on wizard-content-wrapper
ddrayko Jul 3, 2026
af9f33e
fix: reduce dropdown max-height to avoid reaching bottom of screen
ddrayko Jul 3, 2026
b9f6e74
feat: show per-egg custom resources in create summary instead of hard…
ddrayko Jul 3, 2026
aa240fe
fix: cap widget max-width matches other summary cards
ddrayko Jul 3, 2026
dba6923
fix: constrain docker section and name step next button to match card…
ddrayko Jul 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Game server management dashboard for the ZeroHost platform. Provides user-facing server lifecycle management (creation, renewal, suspension, deletion) backed by a Pyrodactyl/Pterodactyl panel API.

<img width="1920" height="1080" alt="image" src="https://github.com/user-attachments/assets/3bf1a711-9923-4089-8a12-94bbc134c41b" />

---

## Table of Contents
Expand Down
8 changes: 0 additions & 8 deletions config/cap.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
const CAP_SECRET = process.env.CAP_SECRET;
const CAP_ENDPOINT = process.env.CAP_ENDPOINT;

if (!CAP_SECRET) {
console.error('Missing CAP_SECRET environment variable');
}

if (!CAP_ENDPOINT) {
console.error('Missing CAP_ENDPOINT environment variable');
}

async function fetchWithTimeout(url, options = {}, timeout = 10000) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeout);
Expand Down
34 changes: 33 additions & 1 deletion config/db.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,50 @@ const pool = mariadb.createPool({
acquireTimeout: 10000,
idleTimeout: 30000,
insertIdAsNumber: true,
pingTimeout: 5000,
});

export async function closePool() {
try {
await pool.end();
} catch (err) {
console.error('Error closing pool:', err.message);
}
}

export async function getPoolStatus() {
try {
const active = pool.activeConnections();
const total = pool.totalConnections();
const idle = pool.idleConnections();
return { active, total, idle };
} catch {
return { active: -1, total: -1, idle: -1 };
}
}

export async function query(sql, params = []) {
let lastErr;
for (let attempt = 1; attempt <= 3; attempt++) {
let conn;
let queryTimeout;
try {
conn = await pool.getConnection();
const rows = await conn.query(sql, params);
const timeoutPromise = new Promise((_, reject) => {
queryTimeout = setTimeout(() => reject(new Error('Query timeout after 30000ms')), 30000);
});
let rows = await Promise.race([
conn.query(sql, params),
timeoutPromise,
]);
clearTimeout(queryTimeout);
if (Array.isArray(rows) && rows.length > 10000) {
console.warn(`Large result set detected: ${rows.length} rows for query: ${sql.slice(0, 100)}`);
}
return rows;
} catch (err) {
lastErr = err;
clearTimeout(queryTimeout);
if (err.code === 'ECONNRESET' || err.code === 'PROTOCOL_CONNECTION_LOST' || err.message?.includes('timeout')) {
console.error(`DB query attempt ${attempt}/3 failed:`, err.message);
if (attempt < 3) await new Promise(r => setTimeout(r, 100 * attempt));
Expand Down
10 changes: 10 additions & 0 deletions config/migrate.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ const tables = {
{ name: 'id', def: 'INT AUTO_INCREMENT PRIMARY KEY' },
{ name: 'ptero_nest_id', def: 'INT NOT NULL UNIQUE' },
{ name: 'name', def: 'VARCHAR(255) NOT NULL' },
{ name: 'logo', def: 'VARCHAR(255) DEFAULT NULL' },
{ name: 'description', def: 'TEXT DEFAULT NULL' },
{ name: 'created_at', def: 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP' },
],
},
Expand All @@ -64,6 +66,7 @@ const tables = {
{ name: 'id', def: 'INT AUTO_INCREMENT PRIMARY KEY' },
{ name: 'ptero_nest_id', def: 'INT NOT NULL' },
{ name: 'ptero_egg_id', def: 'INT NOT NULL' },
{ name: 'logo', def: 'VARCHAR(255) DEFAULT NULL' },
{ name: 'cpu_limit', def: 'INT DEFAULT NULL' },
{ name: 'memory_limit', def: 'INT DEFAULT NULL' },
{ name: 'disk_limit', def: 'INT DEFAULT NULL' },
Expand Down Expand Up @@ -106,7 +109,14 @@ const constraints = [
{ table: 'egg_resources', sql: 'ALTER TABLE egg_resources ADD UNIQUE INDEX idx_egg_resources_nest_egg (ptero_nest_id, ptero_egg_id)', name: 'idx_egg_resources_nest_egg' },
{ table: 'notifications', sql: 'ALTER TABLE notifications ADD INDEX idx_notif_user (user_id)', name: 'idx_notif_user' },
{ table: 'notifications', sql: 'ALTER TABLE notifications ADD INDEX idx_notif_user_read (user_id, is_read)', name: 'idx_notif_user_read' },
{ table: 'notifications', sql: 'ALTER TABLE notifications ADD INDEX idx_notif_created (created_at)', name: 'idx_notif_created' },
{ table: 'notifications', sql: 'ALTER TABLE notifications ADD CONSTRAINT fk_notif_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE', name: 'fk_notif_user' },
{ table: 'users', sql: 'ALTER TABLE users ADD INDEX idx_email (email)', name: 'idx_user_email' },
{ table: 'users', sql: 'ALTER TABLE users ADD INDEX idx_username (username)', name: 'idx_user_username' },
{ table: 'activity_log', sql: 'ALTER TABLE activity_log ADD INDEX idx_action (action)', name: 'idx_activity_action' },
{ table: 'activity_log', sql: 'ALTER TABLE activity_log ADD CONSTRAINT fk_activity_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE', name: 'fk_activity_user' },
{ table: 'server_meta', sql: 'ALTER TABLE server_meta ADD INDEX idx_ptero_server (ptero_server_id)', name: 'idx_ptero_server' },
{ table: 'server_meta', sql: 'ALTER TABLE server_meta ADD CONSTRAINT fk_server_meta_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE', name: 'fk_server_meta_user' },
];

export async function migrate() {
Expand Down
2 changes: 1 addition & 1 deletion config/pyrodactyl.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export const PTERO_URL = process.env.PTERO_URL || 'https://panel.zero-host.org';
export const PTERO_API_KEY = process.env.PTERO_API_KEY || '';
export const PANEL_DB_NAME = process.env.PANEL_DB_NAME || 'panel';
export const PANEL_DB_NAME = (process.env.PANEL_DB_NAME || 'panel').replace(/[^a-zA-Z0-9_]/g, '');

export const SERVER_LIMITS = {
memory: 512,
Expand Down
6 changes: 0 additions & 6 deletions middleware/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,6 @@ import { query } from '../config/db.js';

const JWT_SECRET = process.env.JWT_SECRET;

if (!JWT_SECRET) {
console.error('Missing JWT_SECRET environment variable');
} else if (/[\$\(\)]/.test(JWT_SECRET)) {
console.error('JWT_SECRET contains unresolved shell expansion characters ($(), backticks). Generate a proper random key (e.g. openssl rand -hex 32) and hardcode it in .env');
}

export async function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "zerohost-dashboard",
"version": "1.0.2",
"version": "1.0.3",
"private": true,
"type": "module",
"scripts": {
Expand Down
Loading