Flask-based VPS management panel with SSH remote command execution. Includes E2E encrypted SSH tunnel (AES-256-GCM + Go agent), setup wizard, security hardening tools, DNS management, firewall configs, monitoring, backup, and .sec patch update system. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
217 lines
8.6 KiB
HTML
217 lines
8.6 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Settings{% endblock %}
|
|
{% block content %}
|
|
<h1>[%] Settings</h1>
|
|
|
|
<div class="grid grid-2">
|
|
<div class="card">
|
|
<div class="card-title">VPS Connection</div>
|
|
<label>Host</label>
|
|
<input type="text" id="s-host" style="width:100%">
|
|
<label>User</label>
|
|
<input type="text" id="s-user" style="width:100%">
|
|
<label>Port</label>
|
|
<input type="number" id="s-port" style="width:100%">
|
|
<label>SSH Key Path</label>
|
|
<input type="text" id="s-key" style="width:100%">
|
|
</div>
|
|
<div class="card">
|
|
<div class="card-title">Domain & Paths</div>
|
|
<label>Domain</label>
|
|
<input type="text" id="s-domain" style="width:100%">
|
|
<label>Web Root</label>
|
|
<input type="text" id="s-webroot" style="width:100%">
|
|
<label>Compose Path</label>
|
|
<input type="text" id="s-compose" style="width:100%">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-title">Hosting Provider API</div>
|
|
<p style="color:#888;font-size:11px;margin-bottom:10px">Select your hosting provider and enter API credentials for DNS management.</p>
|
|
<label>Provider</label>
|
|
<select id="s-provider" style="width:100%" onchange="providerChanged()">
|
|
<option value="">-- Select Provider --</option>
|
|
</select>
|
|
<div id="provider-notes" style="font-size:11px;color:#555;margin:5px 3px"></div>
|
|
<label id="lbl-apikey">API Key</label>
|
|
<input type="text" id="s-apikey" style="width:100%" placeholder="Enter API key">
|
|
<div id="provider-docs" style="font-size:11px;margin:5px 3px"></div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-title">E2E SSH Encryption</div>
|
|
<p style="color:#888;font-size:11px;margin-bottom:10px">
|
|
Encrypts all SSH commands with AES-256-GCM before transport. Requires the setec-agent on the VPS.
|
|
</p>
|
|
<div id="e2e-status" style="margin-bottom:10px;font-size:12px;color:#555">Loading...</div>
|
|
<div class="toolbar" style="margin-bottom:0">
|
|
<button class="btn" id="btn-e2e-toggle" onclick="toggleE2E()">Enable E2E</button>
|
|
<button class="btn" onclick="deployE2E()">Deploy Agent</button>
|
|
<button class="btn" onclick="testE2E()">Test Tunnel</button>
|
|
</div>
|
|
<div class="output" id="e2e-output" style="margin-top:10px;display:none"></div>
|
|
</div>
|
|
|
|
<div class="toolbar">
|
|
<button class="btn" onclick="saveSettings()">Save Settings</button>
|
|
<button class="btn" onclick="loadSettings()">Reload</button>
|
|
<button class="btn" onclick="testConnection()">Test SSH Connection</button>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-title">Output</div>
|
|
<div class="output" id="output"><span class="info">Ready.</span></div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script>
|
|
let providers = [];
|
|
|
|
async function loadProviders() {
|
|
const res = await apiGet('/api/hosting/providers');
|
|
if (!res.ok) return;
|
|
providers = res.data;
|
|
const sel = document.getElementById('s-provider');
|
|
providers.forEach(p => {
|
|
const opt = document.createElement('option');
|
|
opt.value = p.id;
|
|
opt.textContent = p.name;
|
|
sel.appendChild(opt);
|
|
});
|
|
}
|
|
|
|
function providerChanged() {
|
|
const id = document.getElementById('s-provider').value;
|
|
const p = providers.find(x => x.id === id);
|
|
if (p) {
|
|
document.getElementById('lbl-apikey').textContent = p.api_key_label || 'API Key';
|
|
document.getElementById('provider-notes').textContent = p.notes || '';
|
|
document.getElementById('provider-docs').innerHTML = p.docs ? '<a href="' + p.docs + '" target="_blank">' + p.docs + '</a>' : '';
|
|
} else {
|
|
document.getElementById('lbl-apikey').textContent = 'API Key';
|
|
document.getElementById('provider-notes').textContent = '';
|
|
document.getElementById('provider-docs').innerHTML = '';
|
|
}
|
|
}
|
|
|
|
async function loadSettings() {
|
|
const res = await apiGet('/api/settings');
|
|
if (!res.ok) { showResult(res); return; }
|
|
const d = res.data;
|
|
document.getElementById('s-host').value = d.vps_host || '';
|
|
document.getElementById('s-user').value = d.vps_user || '';
|
|
document.getElementById('s-port').value = d.vps_port || 22;
|
|
document.getElementById('s-key').value = d.ssh_key_path || '';
|
|
document.getElementById('s-domain').value = d.domain || '';
|
|
document.getElementById('s-apikey').value = d.hostinger_api_key || '';
|
|
document.getElementById('s-webroot').value = d.web_root || '';
|
|
document.getElementById('s-compose').value = d.compose_path || '';
|
|
if (d.hosting_provider) {
|
|
document.getElementById('s-provider').value = d.hosting_provider;
|
|
providerChanged();
|
|
}
|
|
document.getElementById('output').innerHTML = '<span class="info">Settings loaded.</span>';
|
|
}
|
|
|
|
async function saveSettings() {
|
|
const body = {
|
|
vps_host: document.getElementById('s-host').value,
|
|
vps_user: document.getElementById('s-user').value,
|
|
vps_port: parseInt(document.getElementById('s-port').value),
|
|
ssh_key_path: document.getElementById('s-key').value,
|
|
domain: document.getElementById('s-domain').value,
|
|
hostinger_api_key: document.getElementById('s-apikey').value,
|
|
hosting_provider: document.getElementById('s-provider').value,
|
|
web_root: document.getElementById('s-webroot').value,
|
|
compose_path: document.getElementById('s-compose').value,
|
|
};
|
|
const res = await apiPost('/api/settings', body);
|
|
showResult(res);
|
|
}
|
|
|
|
async function testConnection() {
|
|
document.getElementById('output').innerHTML = '<span class="info">Testing SSH...</span>';
|
|
const res = await apiGet('/api/ssh/test');
|
|
showResult(res);
|
|
}
|
|
|
|
// ── E2E Tunnel ──
|
|
async function loadE2EStatus() {
|
|
const res = await apiGet('/api/e2e/status');
|
|
const el = document.getElementById('e2e-status');
|
|
const btn = document.getElementById('btn-e2e-toggle');
|
|
if (!res.ok) { el.innerHTML = '<span class="error">Failed to load E2E status</span>'; return; }
|
|
const d = res.data;
|
|
const active = d.e2e_enabled && d.e2e_deployed;
|
|
let html = 'Status: ';
|
|
if (active) {
|
|
html += '<span class="status-ok">ACTIVE</span>';
|
|
btn.textContent = 'Disable E2E';
|
|
} else if (d.e2e_deployed) {
|
|
html += '<span class="status-warn">Deployed but disabled</span>';
|
|
btn.textContent = 'Enable E2E';
|
|
} else {
|
|
html += '<span class="status-err">Not deployed</span>';
|
|
btn.textContent = 'Enable E2E';
|
|
}
|
|
html += ' | Agent: <code>' + d.agent_path + '</code>';
|
|
el.innerHTML = html;
|
|
}
|
|
|
|
async function toggleE2E() {
|
|
const res = await apiGet('/api/e2e/status');
|
|
if (!res.ok) return;
|
|
const nowEnabled = res.data.e2e_enabled && res.data.e2e_deployed;
|
|
const out = document.getElementById('e2e-output');
|
|
out.style.display = 'block';
|
|
out.innerHTML = '<span class="info">' + (nowEnabled ? 'Disabling' : 'Enabling') + ' E2E...</span>';
|
|
const r = await apiPost('/api/e2e/toggle', {enabled: !nowEnabled});
|
|
if (r.ok) {
|
|
out.innerHTML = '<span class="status-ok">E2E ' + (!nowEnabled ? 'enabled' : 'disabled') + '</span>';
|
|
} else {
|
|
out.innerHTML = '<span class="error">' + (r.error || 'Failed') + '</span>';
|
|
}
|
|
loadE2EStatus();
|
|
}
|
|
|
|
async function deployE2E() {
|
|
const out = document.getElementById('e2e-output');
|
|
out.style.display = 'block';
|
|
out.innerHTML = '<span class="info">Deploying agent + tunnel key to VPS... this may take a moment.</span>';
|
|
const r = await apiPost('/api/e2e/deploy', {});
|
|
if (r.ok) {
|
|
let html = '<span class="status-ok">Deployment complete!</span><br>';
|
|
(r.data || []).forEach(s => {
|
|
html += (s.ok ? '<span class="status-ok">[OK]</span>' : '<span class="status-err">[FAIL]</span>') + ' ' + s.step + '<br>';
|
|
});
|
|
out.innerHTML = html;
|
|
} else {
|
|
let html = '<span class="error">' + (r.error || 'Deploy failed') + '</span><br>';
|
|
(r.data || []).forEach(s => {
|
|
html += (s.ok ? '<span class="status-ok">[OK]</span>' : '<span class="status-err">[FAIL]</span>') + ' ' + s.step + '<br>';
|
|
});
|
|
out.innerHTML = html;
|
|
}
|
|
loadE2EStatus();
|
|
}
|
|
|
|
async function testE2E() {
|
|
const out = document.getElementById('e2e-output');
|
|
out.style.display = 'block';
|
|
out.innerHTML = '<span class="info">Testing E2E tunnel...</span>';
|
|
const r = await apiPost('/api/e2e/test', {});
|
|
if (r.ok) {
|
|
out.innerHTML = '<span class="status-ok">E2E tunnel working!</span><br><pre>' + (r.data.output || '') + '</pre>';
|
|
} else {
|
|
out.innerHTML = '<span class="error">' + (r.error || 'Test failed') + '</span>';
|
|
}
|
|
}
|
|
|
|
loadProviders();
|
|
loadSettings();
|
|
loadE2EStatus();
|
|
</script>
|
|
{% endblock %}
|