feat: add 7 new features — exec shell, SSE events, compose import, docker resources, resource limits, email notifications, auto-updates
- Container exec/shell via WebSocket + xterm.js (subtle >_ button on cards) - Live dashboard updates via SSE (resource alerts, health changes, update notices) - Docker Compose import with YAML parsing, preview, and dependency-ordered deploy - Volume & network management modal with disk usage overview - CPU/memory resource limits on deploy and live update - Email SMTP notifications (nodemailer) alongside Discord/Telegram/ntfy - Scheduled auto-update scheduler with maintenance windows (daily/weekly/monthly) New deps: ws, js-yaml, nodemailer Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -80,6 +80,52 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Email -->
|
||||
<div class="notification-provider provider-card">
|
||||
<div class="provider-header">
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" id="email-enabled" />
|
||||
<span class="fw-500">Email (SMTP)</span>
|
||||
</label>
|
||||
<button id="email-test" class="test-btn btn-xs">Test</button>
|
||||
</div>
|
||||
<div id="email-config" style="display: none;">
|
||||
<div style="display: grid; grid-template-columns: 2fr 1fr; gap: 8px; margin-bottom: 8px;">
|
||||
<div>
|
||||
<label class="field-label-sm">SMTP Host:</label>
|
||||
<input type="text" id="email-host" placeholder="smtp.gmail.com" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="field-label-sm">Port:</label>
|
||||
<input type="number" id="email-port" value="587" placeholder="587" />
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 8px;">
|
||||
<div>
|
||||
<label class="field-label-sm">Username:</label>
|
||||
<input type="text" id="email-user" placeholder="user@gmail.com" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="field-label-sm">Password:</label>
|
||||
<input type="password" id="email-pass" placeholder="app password" />
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px;">
|
||||
<div>
|
||||
<label class="field-label-sm">From:</label>
|
||||
<input type="text" id="email-from" placeholder="DashCaddy <noreply@example.com>" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="field-label-sm">To:</label>
|
||||
<input type="text" id="email-to" placeholder="admin@example.com" />
|
||||
</div>
|
||||
</div>
|
||||
<label style="display: flex; align-items: center; gap: 6px; margin-top: 8px; font-size: 0.8rem;">
|
||||
<input type="checkbox" id="email-secure" /> Use TLS (port 465)
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Health Check Settings -->
|
||||
<h4 class="section-heading">Health Monitoring</h4>
|
||||
<div style="padding: 12px; background: var(--card-base); border-radius: 8px; border: 1px solid var(--border);">
|
||||
@@ -143,7 +189,7 @@
|
||||
const cancelBtn = document.getElementById('notifications-cancel');
|
||||
|
||||
// Provider toggle handlers
|
||||
['discord', 'telegram', 'ntfy'].forEach(provider => {
|
||||
['discord', 'telegram', 'ntfy', 'email'].forEach(provider => {
|
||||
const checkbox = document.getElementById(`${provider}-enabled`);
|
||||
const config = document.getElementById(`${provider}-config`);
|
||||
|
||||
@@ -175,17 +221,23 @@
|
||||
document.getElementById('discord-enabled').checked = config.providers?.discord?.enabled || false;
|
||||
document.getElementById('telegram-enabled').checked = config.providers?.telegram?.enabled || false;
|
||||
document.getElementById('ntfy-enabled').checked = config.providers?.ntfy?.enabled || false;
|
||||
document.getElementById('email-enabled').checked = config.providers?.email?.enabled || false;
|
||||
|
||||
// Show/hide config sections
|
||||
document.getElementById('discord-config').style.display = config.providers?.discord?.enabled ? 'block' : 'none';
|
||||
document.getElementById('telegram-config').style.display = config.providers?.telegram?.enabled ? 'block' : 'none';
|
||||
document.getElementById('ntfy-config').style.display = config.providers?.ntfy?.enabled ? 'block' : 'none';
|
||||
document.getElementById('email-config').style.display = config.providers?.email?.enabled ? 'block' : 'none';
|
||||
|
||||
// ntfy server URL
|
||||
if (config.providers?.ntfy?.serverUrl) {
|
||||
document.getElementById('ntfy-server').value = config.providers.ntfy.serverUrl;
|
||||
}
|
||||
|
||||
// email fields
|
||||
if (config.providers?.email?.host) document.getElementById('email-host').value = config.providers.email.host;
|
||||
if (config.providers?.email?.from) document.getElementById('email-from').value = config.providers.email.from;
|
||||
|
||||
// Health check
|
||||
document.getElementById('health-check-enabled').checked = config.healthCheck?.enabled || false;
|
||||
if (config.healthCheck?.intervalMinutes) {
|
||||
@@ -260,6 +312,16 @@
|
||||
enabled: document.getElementById('ntfy-enabled').checked,
|
||||
serverUrl: document.getElementById('ntfy-server').value.trim() || 'https://ntfy.sh',
|
||||
topic: document.getElementById('ntfy-topic').value.trim()
|
||||
},
|
||||
email: {
|
||||
enabled: document.getElementById('email-enabled').checked,
|
||||
host: document.getElementById('email-host').value.trim(),
|
||||
port: parseInt(document.getElementById('email-port').value) || 587,
|
||||
secure: document.getElementById('email-secure').checked,
|
||||
user: document.getElementById('email-user').value.trim(),
|
||||
pass: document.getElementById('email-pass').value.trim(),
|
||||
from: document.getElementById('email-from').value.trim(),
|
||||
to: document.getElementById('email-to').value.trim()
|
||||
}
|
||||
},
|
||||
events: {
|
||||
@@ -315,6 +377,7 @@
|
||||
document.getElementById('discord-test')?.addEventListener('click', () => testProvider('discord'));
|
||||
document.getElementById('telegram-test')?.addEventListener('click', () => testProvider('telegram'));
|
||||
document.getElementById('ntfy-test')?.addEventListener('click', () => testProvider('ntfy'));
|
||||
document.getElementById('email-test')?.addEventListener('click', () => testProvider('email'));
|
||||
|
||||
// Health check now button
|
||||
document.getElementById('health-check-now')?.addEventListener('click', async () => {
|
||||
|
||||
Reference in New Issue
Block a user