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

Commit ea8b95f

Browse files
committed
feat: add 'Save for All' button to apply egg resources to existing servers
1 parent e7bb8c7 commit ea8b95f

3 files changed

Lines changed: 112 additions & 2 deletions

File tree

public/js/admin.js

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1303,10 +1303,12 @@ async function renderAdminEggSettings(nestId, eggId) {
13031303
<label for="egg-disk">Disk Limit (MB)</label>
13041304
<input type="number" id="egg-disk" min="0" placeholder="e.g. 3072" style="width:100%" />
13051305
</div>
1306-
<div style="display:flex;gap:8px;margin-top:20px">
1306+
<div style="display:flex;gap:8px;margin-top:20px;flex-wrap:wrap">
13071307
<button type="submit" class="btn btn-primary" id="btn-save-egg-resources" style="width:auto">Save</button>
1308+
<button type="button" class="btn btn-warning" id="btn-save-egg-resources-all" style="width:auto">Save for All</button>
13081309
<button type="button" class="btn btn-ghost" id="btn-clear-egg-resources" style="width:auto">Clear Overrides</button>
13091310
</div>
1311+
<div id="admin-egg-apply-all-msg" style="margin-top:12px;display:none"></div>
13101312
</form>
13111313
</div>
13121314
`;
@@ -1375,6 +1377,43 @@ async function renderAdminEggSettings(nestId, eggId) {
13751377
if (errEl) { errEl.textContent = err.message; errEl.classList.add('show'); }
13761378
}
13771379
});
1380+
1381+
$a('#btn-save-egg-resources-all')?.addEventListener('click', async () => {
1382+
if (!confirm('Apply these resources to ALL existing servers using this egg? This will update their limits on the panel.')) return;
1383+
const btn = $a('#btn-save-egg-resources-all');
1384+
const msgEl = $a('#admin-egg-apply-all-msg');
1385+
const errEl = $a('#admin-egg-resources-error');
1386+
if (errEl) errEl.classList.remove('show');
1387+
if (msgEl) { msgEl.style.display = 'none'; }
1388+
btn.disabled = true;
1389+
btn.innerHTML = '<span class="spinner"></span> Applying...';
1390+
1391+
const cpu = $a('#egg-cpu').value;
1392+
const memory = $a('#egg-memory').value;
1393+
const disk = $a('#egg-disk').value;
1394+
1395+
try {
1396+
const data = await adminApi(`/settings/eggs/${nestId}/${eggId}/apply-all`, {
1397+
method: 'POST',
1398+
body: JSON.stringify({
1399+
cpu_limit: cpu !== '' ? parseInt(cpu, 10) : null,
1400+
memory_limit: memory !== '' ? parseInt(memory, 10) : null,
1401+
disk_limit: disk !== '' ? parseInt(disk, 10) : null,
1402+
}),
1403+
});
1404+
if (msgEl) {
1405+
msgEl.textContent = `Updated ${data.updated} / ${data.total} server(s) successfully.`;
1406+
msgEl.style.display = 'block';
1407+
msgEl.style.color = 'var(--accent-green)';
1408+
}
1409+
btn.innerHTML = 'Done!';
1410+
setTimeout(() => { btn.disabled = false; btn.innerHTML = 'Save for All'; }, 2000);
1411+
} catch (err) {
1412+
if (errEl) { errEl.textContent = err.message; errEl.classList.add('show'); }
1413+
btn.disabled = false;
1414+
btn.innerHTML = 'Save for All';
1415+
}
1416+
});
13781417
}
13791418

13801419
// ─── Add Nests Modal ────────────────────────────────────

routes/admin.js

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import argon2 from 'argon2';
33
import jwt from 'jsonwebtoken';
44
import { authenticateToken } from '../middleware/auth.js';
55
import { query } from '../config/db.js';
6-
import { getAllServers, getServerById, getEgg, getPteroNests, getPteroNestEggs, suspendPteroServer, unsuspendPteroServer, deletePteroServer, deletePteroUser } from '../services/pyrodactyl.js';
6+
import { getAllServers, getServerById, getEgg, getPteroNests, getPteroNestEggs, suspendPteroServer, unsuspendPteroServer, deletePteroServer, deletePteroUser, updatePteroServerBuild, getPergoServerIdsByEgg } from '../services/pyrodactyl.js';
77
import { verifyCap } from '../config/cap.js';
88
import { logActivity } from '../services/activity.js';
99

@@ -603,4 +603,47 @@ router.put('/settings/eggs/:nestId/:eggId', authenticateToken, requireAdmin, asy
603603
}
604604
});
605605

606+
router.post('/settings/eggs/:nestId/:eggId/apply-all', authenticateToken, requireAdmin, async (req, res) => {
607+
try {
608+
const { nestId, eggId } = req.params;
609+
const { cpu_limit, memory_limit, disk_limit } = req.body;
610+
611+
// Save to egg_resources first
612+
const [existing] = await query('SELECT id FROM egg_resources WHERE ptero_nest_id = ? AND ptero_egg_id = ?', [nestId, eggId]);
613+
if (existing) {
614+
await query('UPDATE egg_resources SET cpu_limit = ?, memory_limit = ?, disk_limit = ? WHERE id = ?',
615+
[cpu_limit ?? null, memory_limit ?? null, disk_limit ?? null, existing.id]);
616+
} else {
617+
await query('INSERT INTO egg_resources (ptero_nest_id, ptero_egg_id, cpu_limit, memory_limit, disk_limit) VALUES (?, ?, ?, ?, ?)',
618+
[nestId, eggId, cpu_limit ?? null, memory_limit ?? null, disk_limit ?? null]);
619+
}
620+
621+
// Find all panel servers using this egg
622+
const pteroIds = await getPergoServerIdsByEgg(parseInt(nestId, 10), parseInt(eggId, 10));
623+
624+
if (pteroIds.length === 0) {
625+
return res.json({ success: true, updated: 0, total: 0 });
626+
}
627+
628+
const limits = {};
629+
if (cpu_limit != null) limits.cpu = cpu_limit;
630+
if (memory_limit != null) limits.memory = memory_limit;
631+
if (disk_limit != null) limits.disk = disk_limit;
632+
633+
let updated = 0;
634+
for (const id of pteroIds) {
635+
try {
636+
await updatePteroServerBuild(id, limits);
637+
updated++;
638+
} catch (err) {
639+
console.error(`Failed to update server #${id} build:`, err.message);
640+
}
641+
}
642+
643+
res.json({ success: true, updated, total: pteroIds.length });
644+
} catch (err) {
645+
res.status(500).json({ error: 'Failed to apply resources to all servers: ' + err.message });
646+
}
647+
});
648+
606649
export default router;

services/pyrodactyl.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,34 @@ export async function reinstallPteroServer(serverId) {
255255
});
256256
}
257257

258+
export async function updatePteroServerBuild(serverId, limits) {
259+
await pteroFetch(`/servers/${serverId}/build`, {
260+
method: 'PATCH',
261+
body: JSON.stringify({ limits }),
262+
});
263+
}
264+
265+
export async function getPergoServerIdsByEgg(nestId, eggId) {
266+
const ids = [];
267+
let page = 1;
268+
let hasMore = true;
269+
while (hasMore) {
270+
const data = await pteroFetch(`/servers?page=${page}&per_page=100`);
271+
const servers = data.data.map(s => s.attributes);
272+
for (const s of servers) {
273+
if (s.nest === nestId && s.egg === eggId) {
274+
ids.push(s.id);
275+
}
276+
}
277+
if (data.meta?.pagination?.total_pages > page) {
278+
page++;
279+
} else {
280+
hasMore = false;
281+
}
282+
}
283+
return ids;
284+
}
285+
258286
export async function renamePteroServer(serverId, name) {
259287
const server = await getServerById(serverId);
260288
await pteroFetch(`/servers/${serverId}/details`, {

0 commit comments

Comments
 (0)