Initial commit — SETEC LABS Manager (Setec_CDM)
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>
This commit is contained in:
216
setec-web/templates/settings.html
Normal file
216
setec-web/templates/settings.html
Normal file
@@ -0,0 +1,216 @@
|
||||
{% 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 %}
|
||||
Reference in New Issue
Block a user