Skip to content
This repository was archived by the owner on Jul 28, 2026. It is now read-only.

Commit e190426

Browse files
committed
add admin notice banner + various fixes (CAPTCHA on admin login, deploy workflow improvements, account deletion order, panel DB name config)
1 parent 27a2e0b commit e190426

11 files changed

Lines changed: 83 additions & 37 deletions

File tree

.env.example

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ DB_NAME=zerohost_dashboard
1717
PTERO_URL=https://panel.your-domain.com
1818
PTERO_API_KEY=change_me
1919

20-
# Encryption key for sensitive data — Generate with: openssl rand -hex 32
21-
ENCRYPTION_KEY=change_me_to_a_random_hex_string
20+
# Panel database name (for egg variable lookup)
21+
PANEL_DB_NAME=panel
2222

2323
# Cap CAPTCHA (self-hosted alternative to Turnstile)
2424
CAP_ENDPOINT=https://cap.zero-host.org/f6c8171b08/

.github/workflows/deploy-beta.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ jobs:
1818
script: |
1919
cd ${{ secrets.BETA_PATH }}
2020
cp .env /tmp/beta.env 2>/dev/null; cp port.txt /tmp/beta.port.txt 2>/dev/null
21-
git fetch origin
22-
git reset --hard origin/beta
23-
git checkout -B beta
21+
git fetch origin --prune
22+
git checkout beta
23+
git merge --ff-only origin/beta
2424
cp /tmp/beta.env .env 2>/dev/null; cp /tmp/beta.port.txt port.txt 2>/dev/null
2525
grep 'discord.zero-host.org' public/js/app.js || echo "⚠ DISCORD LINK NOT FOUND IN FILE"
2626
head -20 port.txt 2>/dev/null || echo "⚠ port.txt missing or empty"

.github/workflows/deploy-main.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ jobs:
1818
script: |
1919
cd ${{ secrets.MAIN_PATH }}
2020
cp .env /tmp/main.env 2>/dev/null; cp port.txt /tmp/main.port.txt 2>/dev/null
21-
git fetch origin
22-
git reset --hard origin/main
23-
git checkout -B main
21+
git fetch origin --prune
22+
git checkout main
23+
git merge --ff-only origin/main
2424
cp /tmp/main.env .env 2>/dev/null; cp /tmp/main.port.txt port.txt 2>/dev/null
2525
npm install
2626
pm2 restart zerohost-dashboard

config/pyrodactyl.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export const PTERO_URL = process.env.PTERO_URL || 'https://panel.zero-host.org';
22
export const PTERO_API_KEY = process.env.PTERO_API_KEY || '';
3+
export const PANEL_DB_NAME = process.env.PANEL_DB_NAME || 'panel';
34

45
export const SERVER_LIMITS = {
56
memory: 512,

public/css/style.css

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2044,6 +2044,26 @@ tbody tr:hover {
20442044
-webkit-mask-image: radial-gradient(ellipse 90% 80% at 50% 30%, black 30%, transparent 80%);
20452045
}
20462046

2047+
.admin-notice {
2048+
display: flex;
2049+
align-items: center;
2050+
gap: 10px;
2051+
padding: 12px 16px;
2052+
margin-bottom: 24px;
2053+
background: rgba(238, 129, 50, 0.08);
2054+
border: 1px solid rgba(238, 129, 50, 0.2);
2055+
border-radius: var(--radius-sm);
2056+
font-size: 0.85rem;
2057+
color: var(--text-secondary);
2058+
line-height: 1.5;
2059+
}
2060+
2061+
.admin-notice svg {
2062+
flex-shrink: 0;
2063+
color: var(--accent-1);
2064+
opacity: 0.8;
2065+
}
2066+
20472067
.admin-content > * {
20482068
position: relative;
20492069
z-index: 1;

public/js/admin.js

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ function renderAdminLogin() {
9494
<label for="admin-password">Password</label>
9595
<input type="password" id="admin-password" placeholder="••••••••" required autocomplete="current-password" />
9696
</div>
97+
<div style="width:100%;margin-bottom:16px">
98+
<cap-widget data-cap-api-endpoint="https://cap.zero-host.org/f6c8171b08/" theme="dark"></cap-widget>
99+
</div>
97100
<button type="submit" class="btn btn-primary btn-full" id="admin-login-btn">Sign In</button>
98101
</form>
99102
<div class="auth-footer">
@@ -119,13 +122,16 @@ async function handleAdminLogin(e) {
119122
btn.innerHTML = '<span class="spinner"></span> Signing in...';
120123

121124
try {
122-
const data = await adminApi('/login', {
123-
method: 'POST',
124-
body: JSON.stringify({
125-
email: $a('#admin-email').value,
126-
password: $a('#admin-password').value,
127-
}),
128-
});
125+
const capWidget = $a('#admin-login-form cap-widget');
126+
const capToken = capWidget?.token || '';
127+
const data = await adminApi('/login', {
128+
method: 'POST',
129+
body: JSON.stringify({
130+
email: $a('#admin-email').value,
131+
password: $a('#admin-password').value,
132+
capToken,
133+
}),
134+
});
129135
adminState.token = data.token;
130136
adminState.user = data.user;
131137
localStorage.setItem(ADMIN_STORAGE_KEY, data.token);
@@ -167,6 +173,10 @@ function renderAdminLayout() {
167173
</div>
168174
</nav>
169175
<main class="admin-content">
176+
<div class="admin-notice">
177+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>
178+
<span>Le panel admin a été ajouté très récemment — peu de fonctionnalités et quelques petits bugs pour le moment. Je suis focus sur l'installateur bash, la doc d'installation et le polish UI côté user, mais une fois tout ça fini, le panel admin aura toute mon attention.</span>
179+
</div>
170180
<div class="admin-page active" id="admin-page-servers"></div>
171181
<div class="admin-page" id="admin-page-server-detail"></div>
172182
</main>
@@ -180,9 +190,11 @@ function renderAdminLayout() {
180190
adminNavigateTo('login');
181191
});
182192

183-
$a('.admin-nav-link').addEventListener('click', (e) => {
184-
e.preventDefault();
185-
adminNavigateTo('servers');
193+
document.querySelectorAll('.admin-nav-link').forEach(link => {
194+
link.addEventListener('click', (e) => {
195+
e.preventDefault();
196+
adminNavigateTo('servers');
197+
});
186198
});
187199

188200
updateAdminNav();

public/js/app.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ function openPyrodactylPanel(serverIdentifier) {
5252
window.open(url, '_blank');
5353
}
5454

55-
async function sendPowerCommand(identifier, signal) {
55+
async function sendPowerCommand(identifier, signal, event) {
5656
const btn = event?.target;
5757
if (btn) {
5858
btn.disabled = true;
@@ -1889,9 +1889,9 @@ async function renderServerDetail(serverId) {
18891889
</div>
18901890
</div>
18911891
<div style="display:flex;gap:8px;flex-wrap:wrap">
1892-
<button class="btn btn-success btn-full" style="flex:1" onclick="sendPowerCommand('${s.identifier}','start')" ${s.currentState === 'running' ? 'disabled' : ''}>Start</button>
1893-
<button class="btn btn-warning btn-full" style="flex:1" onclick="sendPowerCommand('${s.identifier}','stop')" ${s.currentState !== 'running' ? 'disabled' : ''}>Stop</button>
1894-
<button class="btn btn-ghost btn-full" style="flex:1" onclick="sendPowerCommand('${s.identifier}','restart')" ${s.currentState !== 'running' ? 'disabled' : ''}>Restart</button>
1892+
<button class="btn btn-success btn-full" style="flex:1" onclick="sendPowerCommand('${s.identifier}','start',event)" ${s.currentState === 'running' ? 'disabled' : ''}>Start</button>
1893+
<button class="btn btn-warning btn-full" style="flex:1" onclick="sendPowerCommand('${s.identifier}','stop',event)" ${s.currentState !== 'running' ? 'disabled' : ''}>Stop</button>
1894+
<button class="btn btn-ghost btn-full" style="flex:1" onclick="sendPowerCommand('${s.identifier}','restart',event)" ${s.currentState !== 'running' ? 'disabled' : ''}>Restart</button>
18951895
</div>
18961896
</div>
18971897

routes/admin.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import jwt from 'jsonwebtoken';
44
import { authenticateToken } from '../middleware/auth.js';
55
import { query } from '../config/db.js';
66
import { getAllServers, getServerById, suspendPteroServer, unsuspendPteroServer, deletePteroServer } from '../services/pyrodactyl.js';
7+
import { verifyCap } from '../config/cap.js';
78
import { logActivity } from '../services/activity.js';
89

910
const router = Router();
@@ -19,11 +20,15 @@ function requireAdmin(req, res, next) {
1920

2021
router.post('/login', async (req, res) => {
2122
try {
22-
const { email, password } = req.body;
23+
const { email, password, capToken } = req.body;
2324
if (!email || !password) {
2425
return res.status(400).json({ error: 'Email and password are required' });
2526
}
2627

28+
if (!await verifyCap(capToken)) {
29+
return res.status(400).json({ error: 'Please complete the security check' });
30+
}
31+
2732
const users = await query('SELECT * FROM users WHERE email = ?', [email]);
2833
if (users.length === 0) {
2934
return res.status(401).json({ error: 'Invalid credentials' });

routes/auth.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ async function isVpnOrProxy(ip) {
3030
return false;
3131
}
3232
try {
33-
const res = await fetchWithTimeout(`http://ip-api.com/json/${ip}?fields=proxy,hosting,query`);
33+
const res = await fetchWithTimeout(`https://ip-api.com/json/${ip}?fields=proxy,hosting,query`);
3434
const data = await res.json();
3535
return data.proxy === true || data.hosting === true;
3636
} catch {
@@ -368,12 +368,7 @@ router.post('/delete-account', authenticateToken, async (req, res) => {
368368
return res.status(401).json({ error: 'Password is incorrect' });
369369
}
370370

371-
await logActivity(user.id, 'account_deleted', 'Deleted account');
372-
373-
// Delete from local DB first (cascades to user_ips)
374-
await query('DELETE FROM users WHERE id = ?', [user.id]);
375-
376-
// Then try to clean up Pyrodactyl (best effort)
371+
// Clean up Pyrodactyl first (before deleting local user)
377372
if (pteroId) {
378373
try {
379374
const servers = await getServersByUser(pteroId);
@@ -395,6 +390,11 @@ router.post('/delete-account', authenticateToken, async (req, res) => {
395390
}
396391
}
397392

393+
await logActivity(user.id, 'account_deleted', 'Deleted account');
394+
395+
// Delete from local DB (cascades to user_ips)
396+
await query('DELETE FROM users WHERE id = ?', [user.id]);
397+
398398
res.json({ message: 'Account deleted successfully' });
399399
} catch (err) {
400400
console.error('Delete account error:', err.message);

routes/servers.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
getEgg,
1212
getAllEggs,
1313
} from '../services/pyrodactyl.js';
14-
import { PTERO_URL } from '../config/pyrodactyl.js';
14+
import { PTERO_URL, PANEL_DB_NAME } from '../config/pyrodactyl.js';
1515
import { query } from '../config/db.js';
1616
import { verifyCap } from '../config/cap.js';
1717
import { logActivity } from '../services/activity.js';
@@ -81,7 +81,7 @@ router.get('/eggs', authenticateToken, async (req, res) => {
8181
for (const { nest, egg } of eggs) {
8282
let variables = [];
8383
try {
84-
const vars = await query('SELECT name, env_variable, default_value, rules, description, user_viewable, user_editable FROM panel.egg_variables WHERE egg_id = ?', [egg.id]);
84+
const vars = await query('SELECT name, env_variable, default_value, rules, description, user_viewable, user_editable FROM ${PANEL_DB_NAME}.egg_variables WHERE egg_id = ?', [egg.id]);
8585
variables = vars;
8686
} catch {}
8787
simplified.push({
@@ -136,7 +136,7 @@ router.post('/create', authenticateToken, async (req, res) => {
136136
const egg = await getEgg(nestId, eggId);
137137
const dockerImage = Object.values(egg.docker_images)[0] || Object.keys(egg.docker_images)[0];
138138

139-
const eggVars = await query('SELECT name, env_variable, default_value, rules FROM panel.egg_variables WHERE egg_id = ?', [eggId]);
139+
const eggVars = await query('SELECT name, env_variable, default_value, rules FROM ${PANEL_DB_NAME}.egg_variables WHERE egg_id = ?', [eggId]);
140140

141141
const mergedEnv = {};
142142
for (const v of eggVars) {

0 commit comments

Comments
 (0)