Skip to content

Commit 0a8573d

Browse files
authored
Merge pull request #10 from ZeroHost-Code/beta
Beta
2 parents eaae64c + 09b3a48 commit 0a8573d

4 files changed

Lines changed: 170 additions & 13 deletions

File tree

.github/workflows/deploy-beta.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
cp .env /tmp/beta.env 2>/dev/null; cp port.txt /tmp/beta.port.txt 2>/dev/null
2121
git fetch origin --prune
2222
git checkout beta
23-
git merge --ff-only origin/beta
23+
git reset --hard 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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
cp .env /tmp/main.env 2>/dev/null; cp port.txt /tmp/main.port.txt 2>/dev/null
2121
git fetch origin --prune
2222
git checkout main
23-
git merge --ff-only origin/main
23+
git reset --hard 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

public/js/app.js

Lines changed: 144 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1514,21 +1514,25 @@ function renderAccountEdit() {
15141514
Add your Pyrodactyl Client API key to enable live resource monitoring and server power state detection directly in the dashboard.
15151515
Generate one at <a href="https://panel.zero-host.org/account/api" target="_blank">panel.zero-host.org/account/api</a>.
15161516
</p>
1517-
<form id="api-key-form" style="max-width:480px">
1518-
<div class="form-group">
1519-
<label for="ptero-api-key-input">API Key</label>
1520-
<input type="password" id="ptero-api-key-input" placeholder="ptla_..." autocomplete="off" />
1521-
</div>
1522-
<button type="submit" class="btn btn-primary btn-full" id="save-api-key-btn">Save</button>
1523-
</form>
1524-
<div id="api-key-status" style="margin-top:8px;font-size:0.82rem;color:var(--text-muted)"></div>
1517+
<div id="api-key-section-content">
1518+
<form id="api-key-form" style="max-width:480px">
1519+
<div class="form-group">
1520+
<label for="ptero-api-key-input">API Key</label>
1521+
<input type="password" id="ptero-api-key-input" placeholder="ptla_..." autocomplete="off" />
1522+
</div>
1523+
<button type="submit" class="btn btn-primary btn-full" id="save-api-key-btn">Save</button>
1524+
</form>
1525+
<div id="api-key-status" style="margin-top:8px;font-size:0.82rem;color:var(--text-muted)"></div>
1526+
</div>
15251527
</div>
15261528
</div>
15271529
`;
15281530

15291531
$('#change-email-form').addEventListener('submit', handleChangeEmail);
15301532
$('#change-password-form').addEventListener('submit', handleChangePassword);
15311533
$('#api-key-form').addEventListener('submit', handleSaveApiKey);
1534+
1535+
checkApiKeyStatus();
15321536
}
15331537

15341538
async function handleSaveApiKey(e) {
@@ -1553,10 +1557,9 @@ async function handleSaveApiKey(e) {
15531557
method: 'PUT',
15541558
body: JSON.stringify({ apiKey: key }),
15551559
});
1556-
status.textContent = 'API key saved successfully!';
1557-
status.style.color = 'var(--accent-green)';
1558-
input.value = '';
15591560
showToast('Pyrodactyl API key saved', 'success');
1561+
renderApiKeySaved();
1562+
return;
15601563
} catch (err) {
15611564
status.textContent = err.message;
15621565
status.style.color = 'var(--accent-red)';
@@ -1566,6 +1569,136 @@ async function handleSaveApiKey(e) {
15661569
}
15671570
}
15681571

1572+
async function checkApiKeyStatus() {
1573+
try {
1574+
const data = await api('/servers/client-api-key');
1575+
if (data.hasKey) {
1576+
renderApiKeySaved();
1577+
}
1578+
} catch (err) {
1579+
// Keep default form if check fails
1580+
}
1581+
}
1582+
1583+
function renderApiKeySaved() {
1584+
const section = $('#api-key-section-content');
1585+
section.innerHTML = html`
1586+
<div style="padding:8px 0">
1587+
<p style="color:var(--accent-green);font-size:0.9rem;margin-bottom:16px">
1588+
&#10003; Your Pyrodactyl API key is saved and active.
1589+
</p>
1590+
<div style="display:flex;gap:8px">
1591+
<button class="btn btn-primary" id="modify-api-key-btn">Modify</button>
1592+
<button class="btn btn-danger" id="delete-api-key-btn">Delete</button>
1593+
</div>
1594+
</div>
1595+
`;
1596+
$('#modify-api-key-btn').addEventListener('click', handleModifyApiKey);
1597+
$('#delete-api-key-btn').addEventListener('click', handleDeleteApiKey);
1598+
}
1599+
1600+
function renderApiKeyForm() {
1601+
const section = $('#api-key-section-content');
1602+
section.innerHTML = html`
1603+
<form id="api-key-form" style="max-width:480px">
1604+
<div class="form-group">
1605+
<label for="ptero-api-key-input">API Key</label>
1606+
<input type="password" id="ptero-api-key-input" placeholder="ptla_..." autocomplete="off" />
1607+
</div>
1608+
<button type="submit" class="btn btn-primary btn-full" id="save-api-key-btn">Save</button>
1609+
</form>
1610+
<div id="api-key-status" style="margin-top:8px;font-size:0.82rem;color:var(--text-muted)"></div>
1611+
`;
1612+
$('#api-key-form').addEventListener('submit', handleSaveApiKey);
1613+
}
1614+
1615+
function handleModifyApiKey() {
1616+
const overlay = $('#modal-overlay');
1617+
const content = $('#modal-content');
1618+
content.innerHTML = html`
1619+
<div class="modal-title">Modify API Key</div>
1620+
<p style="color:var(--text-secondary);line-height:1.6;margin-bottom:16px">
1621+
Enter your new Pyrodactyl API key. The old key will be replaced.
1622+
</p>
1623+
<div class="form-group">
1624+
<label for="modal-api-key-input">New API Key</label>
1625+
<input type="password" id="modal-api-key-input" placeholder="ptla_..." autocomplete="off" />
1626+
</div>
1627+
<div class="modal-actions">
1628+
<button class="btn btn-ghost btn-full modal-cancel-btn">Cancel</button>
1629+
<button class="btn btn-primary btn-full" id="confirm-modify-api-key-btn">Save</button>
1630+
</div>
1631+
`;
1632+
overlay.classList.add('open');
1633+
$('#confirm-modify-api-key-btn').addEventListener('click', handleConfirmModifyApiKey);
1634+
}
1635+
1636+
function handleDeleteApiKey() {
1637+
const overlay = $('#modal-overlay');
1638+
const content = $('#modal-content');
1639+
content.innerHTML = html`
1640+
<div class="modal-title">Delete API Key</div>
1641+
<p style="color:var(--text-secondary);line-height:1.6;margin-bottom:16px">
1642+
Are you sure you want to delete your Pyrodactyl API key? Live resource monitoring and power state detection will stop working.
1643+
</p>
1644+
<div class="modal-actions">
1645+
<button class="btn btn-ghost btn-full modal-cancel-btn">Cancel</button>
1646+
<button class="btn btn-danger btn-full" id="confirm-delete-api-key-btn">Delete</button>
1647+
</div>
1648+
`;
1649+
overlay.classList.add('open');
1650+
$('#confirm-delete-api-key-btn').addEventListener('click', handleConfirmDeleteApiKey);
1651+
}
1652+
1653+
async function handleConfirmModifyApiKey() {
1654+
const btn = $('#confirm-modify-api-key-btn');
1655+
const input = $('#modal-api-key-input');
1656+
const key = input.value.trim();
1657+
1658+
if (!key) {
1659+
showToast('Please enter an API key', 'error');
1660+
return;
1661+
}
1662+
1663+
btn.disabled = true;
1664+
btn.innerHTML = '<span class="spinner"></span>';
1665+
1666+
try {
1667+
await api('/servers/client-api-key', {
1668+
method: 'PUT',
1669+
body: JSON.stringify({ apiKey: key }),
1670+
});
1671+
$('#modal-overlay').classList.remove('open');
1672+
showToast('Pyrodactyl API key updated', 'success');
1673+
renderApiKeySaved();
1674+
} catch (err) {
1675+
showToast(err.message, 'error');
1676+
} finally {
1677+
btn.disabled = false;
1678+
btn.innerHTML = 'Save';
1679+
}
1680+
}
1681+
1682+
async function handleConfirmDeleteApiKey() {
1683+
const btn = $('#confirm-delete-api-key-btn');
1684+
btn.disabled = true;
1685+
btn.innerHTML = '<span class="spinner"></span>';
1686+
1687+
try {
1688+
await api('/servers/client-api-key', {
1689+
method: 'DELETE',
1690+
});
1691+
$('#modal-overlay').classList.remove('open');
1692+
showToast('Pyrodactyl API key deleted', 'success');
1693+
renderApiKeyForm();
1694+
} catch (err) {
1695+
showToast(err.message, 'error');
1696+
} finally {
1697+
btn.disabled = false;
1698+
btn.innerHTML = 'Delete';
1699+
}
1700+
}
1701+
15691702
function renderAccountLinks() {
15701703
const el = $('#page-account');
15711704
el.innerHTML = html`

routes/servers.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,18 @@ router.post('/power/:identifier', authenticateToken, async (req, res) => {
450450
}
451451
});
452452

453+
router.get('/client-api-key', authenticateToken, async (req, res) => {
454+
try {
455+
const userId = req.user.userId;
456+
const rows = await query('SELECT ptero_client_api_key FROM users WHERE id = ?', [userId]);
457+
const hasKey = rows.length > 0 && rows[0].ptero_client_api_key !== null;
458+
res.json({ hasKey });
459+
} catch (err) {
460+
console.error('API key check error:', err.message);
461+
res.status(500).json({ error: 'Failed to check API key status' });
462+
}
463+
});
464+
453465
router.put('/client-api-key', authenticateToken, async (req, res) => {
454466
try {
455467
const { apiKey } = req.body;
@@ -469,4 +481,16 @@ router.put('/client-api-key', authenticateToken, async (req, res) => {
469481
}
470482
});
471483

484+
router.delete('/client-api-key', authenticateToken, async (req, res) => {
485+
try {
486+
const userId = req.user.userId;
487+
await query('UPDATE users SET ptero_client_api_key = NULL WHERE id = ?', [userId]);
488+
await logActivity(req.user.userId, 'api_key_deleted', 'Deleted Pyrodactyl API key');
489+
res.json({ success: true });
490+
} catch (err) {
491+
console.error('API key delete error:', err.message);
492+
res.status(500).json({ error: 'Failed to delete API key' });
493+
}
494+
});
495+
472496
export default router;

0 commit comments

Comments
 (0)