Files

517 lines
30 KiB
HTML
Raw Permalink Normal View History

{% extends "base.html" %}
{% block title %}Setup Wizard{% endblock %}
{% block content %}
<h1>[+] Setup Wizard</h1>
<div id="wiz-steps" style="margin-bottom:15px;font-size:11px;color:#555">
<span id="ws-1" class="status-ok">[1] Terms</span> &rarr;
<span id="ws-2">[2] SSH Keys</span> &rarr;
<span id="ws-3">[3] VPS</span> &rarr;
<span id="ws-4">[4] API</span> &rarr;
<span id="ws-5">[5] Paths</span> &rarr;
<span id="ws-6">[6] Test</span>
</div>
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- Step 1: Terms / License -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<div id="step-1" class="card">
<div class="card-title">License Agreement &amp; Terms of Use</div>
<div style="font-size:12px;line-height:1.6;max-height:400px;overflow-y:auto;padding:10px;background:#000;border:1px solid #333;margin-bottom:15px">
<p style="color:#ff4444;margin-bottom:10px"><strong>IMPORTANT - READ BEFORE CONTINUING</strong></p>
<p style="margin-bottom:10px"><strong style="color:#ff4444">1. RESTRICTED USE LICENSE</strong><br>
This software is licensed for use by <strong>private individuals, independent security
researchers, and non-governmental organizations ONLY</strong>. By using this software you
affirm that you are not acting on behalf of, employed by, contracted by, or otherwise
affiliated with any:<br>
&bull; <strong style="color:#ff4444">Law enforcement agency</strong> (local, state, federal, or international)<br>
&bull; <strong style="color:#ff4444">Government agency or department</strong> (civilian or military)<br>
&bull; <strong style="color:#ff4444">Intelligence service</strong> (domestic or foreign)<br>
&bull; <strong style="color:#ff4444">Government contractor</strong> performing work for any of the above<br>
<br>
Use of this software by any of the above entities or their agents is <strong>strictly
prohibited</strong> and constitutes a violation of this license. This restriction applies
regardless of the purpose, including but not limited to: investigations, surveillance,
offensive operations, defensive operations, or "research." No exceptions.</p>
<p style="margin-bottom:10px"><strong style="color:#88ff88">2. NO WARRANTY</strong><br>
This software is provided "AS IS" without warranty of any kind, express or implied.
SETEC LABS makes no guarantees regarding reliability, availability, or fitness for
any particular purpose. Use at your own risk.</p>
<p style="margin-bottom:10px"><strong style="color:#88ff88">3. NO GUARANTEE OF SUPPORT</strong><br>
While the community may assist, there is no obligation to provide support, updates,
or bug fixes. This is free, open-source software maintained by volunteers.</p>
<p style="margin-bottom:10px"><strong style="color:#88ff88">4. LIMITATION OF LIABILITY</strong><br>
Under no circumstances shall SETEC LABS, its contributors, or the darkHal group be
liable for any direct, indirect, incidental, or consequential damages arising from
the use of this software. This includes but is not limited to: data loss, system
downtime, security breaches, or misconfiguration of your server.</p>
<p style="margin-bottom:10px"><strong style="color:#ff4444">5. IF YOU PAID FOR THIS SOFTWARE, YOU WERE SCAMMED</strong><br>
SETEC LABS Manager is <strong>100% free and open source</strong>. If someone charged you
money for this application, you were ripped off and likely received a version bundled
with malware. Delete it immediately.</p>
<p style="margin-bottom:10px"><strong style="color:#88ff88">6. OFFICIAL DOWNLOAD SOURCES</strong><br>
Only download SETEC applications from these trusted sources:<br>
&bull; <span style="color:#00ff41">repo.seteclabs.io</span> (Official Gitea repository)<br>
&bull; <span style="color:#00ff41">github.com/DigiJEth</span> (Official GitHub mirror)<br>
Any other source is unauthorized and potentially dangerous.</p>
<p style="margin-bottom:10px"><strong style="color:#88ff88">7. ROOT ACCESS WARNING</strong><br>
This software executes commands on remote servers via SSH with the privileges of the
configured user. Misconfiguration can result in data loss or security vulnerabilities.
You are solely responsible for the actions performed through this tool.</p>
<p style="color:#888;font-size:10px;margin-top:15px;border-top:1px solid #333;padding-top:10px">
SETEC LABS Manager &bull; Free Software &bull; darkHal Group &bull; For the people, not the state.</p>
</div>
<div style="margin-bottom:10px">
<label style="display:inline;cursor:pointer">
<input type="checkbox" id="tos-accept" onchange="tosChanged()" style="margin-right:8px">
<span style="color:#ffaa00">I have read and accept these terms and I am not affiliated with any government or law enforcement entity</span>
</label>
</div>
<button class="btn" id="btn-tos-next" onclick="acceptTOS()" disabled style="opacity:0.3">Next &rarr;</button>
</div>
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- Step 2: SSH Key Selection/Generation -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<div id="step-2" class="card" style="display:none">
<div class="card-title">SSH Key Setup</div>
<p style="font-size:12px;color:#888;margin-bottom:15px">
SETEC Manager connects to your VPS via SSH using key-based authentication.
Do you already have SSH keys generated?
</p>
<div style="margin-bottom:15px">
<button class="btn" id="btn-has-keys" onclick="sshKeyChoice('yes')" style="padding:10px 20px">
Yes, I have SSH keys
</button>
<button class="btn" id="btn-no-keys" onclick="sshKeyChoice('no')" style="padding:10px 20px">
No, I need to create them
</button>
</div>
<!-- YES: User has keys -->
<div id="ssh-has-keys" style="display:none">
<label>SSH Private Key Path</label>
<input type="text" id="w-key" style="width:100%" placeholder="C:/keys/setec">
<div style="font-size:10px;color:#555;margin:2px 3px 5px">
Enter the full path to your <strong style="color:#888">private</strong> key file (not the .pub file).
</div>
<div style="font-size:10px;color:#555;margin:2px 3px 10px">
Common locations:<br>
<span style="color:#00ff41;line-height:1.8">
&bull; C:/Users/YourName/.ssh/id_ed25519<br>
&bull; C:/Users/YourName/.ssh/id_rsa<br>
&bull; C:/keys/setec<br>
&bull; ~/.ssh/id_ed25519 (Linux/Mac)
</span>
</div>
<div style="margin-top:10px">
<button class="btn" onclick="goStep(1)">&larr; Back</button>
<button class="btn" onclick="saveKeyAndContinue()">Save &amp; Continue &rarr;</button>
</div>
</div>
<!-- NO: User needs keys -->
<div id="ssh-no-keys" style="display:none">
<p style="font-size:12px;color:#ffaa00;margin-bottom:10px">
No problem! Select your VPS hosting provider below for a step-by-step guide.
</p>
<label>Select your VPS host</label>
<select id="w-ssh-host" style="width:100%" onchange="sshHostChanged()">
<option value="">-- Select Host --</option>
<option value="hostinger">Hostinger</option>
<option value="digitalocean">DigitalOcean</option>
<option value="vultr">Vultr</option>
<option value="linode">Linode (Akamai)</option>
<option value="hetzner">Hetzner</option>
<option value="ovh">OVH / OVHcloud</option>
<option value="aws">AWS (EC2)</option>
<option value="contabo">Contabo</option>
<option value="other">Other / Self-Hosted</option>
</select>
<div id="ssh-host-guide" style="display:none;margin-top:15px;padding:12px;background:#000;border:1px solid #333;font-size:11px;line-height:1.8"></div>
<div id="ssh-generic-guide" style="display:none;margin-top:15px;padding:12px;background:#000;border:1px solid #333">
<p style="color:#88ff88;font-size:12px;margin-bottom:8px"><strong>Generate SSH Keys (any platform)</strong></p>
<p style="font-size:11px;color:#888;margin-bottom:8px">Open a terminal and run:</p>
<div style="font-size:10px;margin-bottom:10px">
<p style="color:#888;margin-bottom:3px">1. Generate key pair:</p>
<code style="color:#00ff41">ssh-keygen -t ed25519 -f C:/keys/setec -N ""</code>
</div>
<div style="font-size:10px;margin-bottom:10px">
<p style="color:#888;margin-bottom:3px">2. Copy the public key to your server:</p>
<code style="color:#00ff41">ssh-copy-id -i C:/keys/setec.pub root@YOUR_SERVER_IP</code>
</div>
<div style="font-size:10px;margin-bottom:10px">
<p style="color:#888;margin-bottom:3px">3. Test the connection:</p>
<code style="color:#00ff41">ssh -i C:/keys/setec root@YOUR_SERVER_IP</code>
</div>
<p style="font-size:10px;color:#555;margin-top:8px">Your private key will be at <span style="color:#00ff41">C:/keys/setec</span></p>
</div>
<div style="margin-top:15px">
<label>Once your keys are ready, enter the private key path:</label>
<input type="text" id="w-key-new" style="width:100%" placeholder="C:/keys/setec" value="C:/keys/setec">
</div>
<div style="margin-top:10px">
<button class="btn" onclick="goStep(1)">&larr; Back</button>
<button class="btn" onclick="saveNewKeyAndContinue()">Save &amp; Continue &rarr;</button>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- Step 3: VPS Connection -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<div id="step-3" class="card" style="display:none">
<div class="card-title">VPS Connection Setup</div>
<label>Server IP Address</label>
<input type="text" id="w-host" style="width:100%" placeholder="e.g. 192.168.1.100">
<div style="font-size:10px;color:#555;margin:2px 3px 10px">
Find your VPS IP in your hosting provider's control panel.
</div>
<label>SSH Username</label>
<input type="text" id="w-user" style="width:100%" placeholder="root" value="root">
<div style="font-size:10px;color:#555;margin:2px 3px 10px">
<strong style="color:#888">Recommended:</strong> Use <span style="color:#00ff41">root</span> or create a sudo user:<br>
<code style="color:#00ff41;font-size:10px">adduser setecadmin && usermod -aG sudo setecadmin</code><br>
<span style="color:#ffaa00">Important:</span> Disable password login after setting up SSH keys:<br>
<code style="color:#00ff41;font-size:10px">sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config && systemctl restart sshd</code>
</div>
<label>SSH Port</label>
<input type="number" id="w-port" style="width:100%" placeholder="2222" value="2222">
<div style="font-size:10px;color:#555;margin:2px 3px 10px">
<strong style="color:#ffaa00">We strongly recommend port 2222</strong> instead of default 22 to reduce brute-force attacks.<br>
<code style="color:#00ff41;font-size:10px">sed -i 's/^#*Port .*/Port 2222/' /etc/ssh/sshd_config && ufw allow 2222/tcp && systemctl restart sshd</code><br>
<span style="color:#ff4444">Do NOT close your current session until you verify the new port works!</span>
</div>
<div style="margin-top:10px">
<button class="btn" onclick="goStep(2)">&larr; Back</button>
<button class="btn" onclick="saveVPS()">Save &amp; Continue &rarr;</button>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- Step 4: API Setup -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<div id="step-4" class="card" style="display:none">
<div class="card-title">DNS API Setup</div>
<label>Domain</label>
<input type="text" id="w-domain" style="width:100%" placeholder="example.com">
<div style="font-size:10px;color:#555;margin:2px 3px 5px">Your primary domain name managed by this panel.</div>
<label>DNS Provider</label>
<select id="w-provider" style="width:100%" onchange="wizProviderChanged()">
<option value="">-- Select Provider --</option>
</select>
<div id="w-provider-notes" style="font-size:11px;color:#ffaa00;margin:5px 3px;min-height:20px"></div>
<label id="w-lbl-apikey">API Key</label>
<input type="text" id="w-apikey" style="width:100%" placeholder="Enter API key">
<div id="w-provider-docs" style="font-size:11px;margin:5px 3px"></div>
<div id="w-provider-help" style="font-size:10px;color:#555;margin:2px 3px 10px;display:none">
<div id="w-help-content"></div>
</div>
<div style="margin-top:10px">
<button class="btn" onclick="goStep(3)">&larr; Back</button>
<button class="btn" onclick="saveAPI()">Save &amp; Continue &rarr;</button>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- Step 5: Paths -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<div id="step-5" class="card" style="display:none">
<div class="card-title">Web Root &amp; Compose Path</div>
<label>Web Root Directory</label>
<input type="text" id="w-webroot" style="width:100%" placeholder="/var/www" value="/var/www">
<div style="font-size:10px;color:#555;margin:2px 3px 10px">Default: <span style="color:#00ff41">/var/www</span></div>
<label>Docker Compose Path</label>
<input type="text" id="w-compose" style="width:100%" placeholder="/opt/seteclabs/docker-compose.yml">
<div style="font-size:10px;color:#00ff41;margin:0 3px 5px;line-height:1.8">
&bull; /opt/seteclabs/docker-compose.yml<br>
&bull; /root/docker-compose.yml<br>
&bull; /srv/docker-compose.yml
</div>
<div style="margin-top:10px">
<button class="btn" onclick="goStep(4)">&larr; Back</button>
<button class="btn" onclick="savePaths()">Save &amp; Finish Setup &rarr;</button>
</div>
</div>
<!-- Step 6: Test (modal trigger) -->
<div id="step-6" style="display:none"></div>
<!-- Modal overlay -->
<div id="wiz-modal" style="display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.85);z-index:999;display:none;align-items:center;justify-content:center">
<div style="background:#111;border:1px solid #00ff41;padding:25px;max-width:500px;width:90%;max-height:80vh;overflow-y:auto">
<div id="modal-title" style="font-size:14px;color:#88ff88;margin-bottom:15px;border-bottom:1px solid #333;padding-bottom:8px">Setup Complete</div>
<div id="modal-body" style="font-size:12px;line-height:1.6"></div>
<div id="modal-buttons" style="margin-top:15px"></div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
let wizProviders = [];
let currentStep = 1;
// ── Step navigation ─────────────────────────────────────────────
function goStep(n) {
for (let i = 1; i <= 6; i++) {
const el = document.getElementById('step-' + i);
if (el) el.style.display = (i === n) ? 'block' : 'none';
const ws = document.getElementById('ws-' + i);
if (ws) ws.className = (i <= n) ? 'status-ok' : '';
}
currentStep = n;
}
function tosChanged() {
const btn = document.getElementById('btn-tos-next');
if (document.getElementById('tos-accept').checked) {
btn.disabled = false; btn.style.opacity = '1';
} else {
btn.disabled = true; btn.style.opacity = '0.3';
}
}
// ── TOS acceptance ──────────────────────────────────────────────
async function acceptTOS() {
const res = await fetch('/api/wizard/accept-tos', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({})
}).then(r => r.json());
if (res.ok) {
goStep(2);
} else {
alert('Error saving TOS acceptance: ' + (res.error || 'Unknown'));
}
}
// ── Provider help ────────────────────────────────────────────────
const providerHelp = {
hostinger: 'Log in to <strong>hPanel</strong> &rarr; click your profile icon &rarr; <strong>API Keys</strong> &rarr; Create new key with DNS permissions.',
cloudflare: 'Go to <strong>dash.cloudflare.com</strong> &rarr; My Profile &rarr; <strong>API Tokens</strong> &rarr; Create Token &rarr; use "Edit zone DNS" template.',
digitalocean: 'Go to <strong>cloud.digitalocean.com</strong> &rarr; API &rarr; <strong>Tokens</strong> &rarr; Generate New Token with read+write scope.',
vultr: 'Go to <strong>my.vultr.com</strong> &rarr; Account &rarr; <strong>API</strong> &rarr; Enable API and copy the key.',
linode: 'Go to <strong>cloud.linode.com</strong> &rarr; My Profile &rarr; <strong>API Tokens</strong> &rarr; Create Personal Access Token.',
godaddy: 'Go to <strong>developer.godaddy.com</strong> &rarr; API Keys &rarr; Create New API Key. Format: <span style="color:#00ff41">key:secret</span>.',
namecheap: 'Go to <strong>namecheap.com</strong> &rarr; Profile &rarr; Tools &rarr; <strong>API Access</strong>. Whitelist your IP.',
hetzner: 'Go to <strong>dns.hetzner.com</strong> &rarr; API Tokens &rarr; Create new token.',
ovh: 'Go to <strong>api.ovh.com/createApp</strong>. You need Application Key, Application Secret, and Consumer Key.',
aws_route53: 'In <strong>AWS IAM Console</strong>, create access key with Route53 permissions. Format: <span style="color:#00ff41">ACCESS_KEY:SECRET</span>.'
};
async function loadWizProviders() {
const res = await apiGet('/api/hosting/providers');
if (!res.ok) return;
wizProviders = res.data;
const sel = document.getElementById('w-provider');
wizProviders.forEach(p => {
const opt = document.createElement('option');
opt.value = p.id;
opt.textContent = p.name;
sel.appendChild(opt);
});
}
function wizProviderChanged() {
const id = document.getElementById('w-provider').value;
const p = wizProviders.find(x => x.id === id);
if (p) {
document.getElementById('w-lbl-apikey').textContent = p.api_key_label || 'API Key';
document.getElementById('w-provider-notes').textContent = p.notes || '';
document.getElementById('w-provider-docs').innerHTML = p.docs ?
'Docs: <a href="' + escHtml(p.docs) + '" target="_blank" style="color:#00ff41">' + escHtml(p.docs) + '</a>' : '';
const helpDiv = document.getElementById('w-provider-help');
const helpContent = document.getElementById('w-help-content');
if (providerHelp[id]) {
helpContent.innerHTML = '<strong style="color:#88ff88">How to get your key:</strong><br>' + providerHelp[id];
helpDiv.style.display = 'block';
} else { helpDiv.style.display = 'none'; }
} else {
document.getElementById('w-lbl-apikey').textContent = 'API Key';
document.getElementById('w-provider-notes').textContent = '';
document.getElementById('w-provider-docs').innerHTML = '';
document.getElementById('w-provider-help').style.display = 'none';
}
}
// ── SSH host guides ─────────────────────────────────────────────
const sshHostGuides = {
hostinger: { name: 'Hostinger', url: 'https://support.hostinger.com/en/articles/1583522-how-to-generate-ssh-keys', steps: 'Log in to <strong>hPanel</strong> &rarr; VPS &rarr; Settings &rarr; <strong>SSH Keys</strong> &rarr; Add SSH Key.' },
digitalocean: { name: 'DigitalOcean', url: 'https://docs.digitalocean.com/products/droplets/how-to/add-ssh-keys/', steps: 'Go to <strong>Settings</strong> &rarr; <strong>Security</strong> &rarr; SSH Keys &rarr; Add SSH Key.' },
vultr: { name: 'Vultr', url: 'https://docs.vultr.com/how-do-i-generate-ssh-keys', steps: 'Go to <strong>Account</strong> &rarr; <strong>SSH Keys</strong> &rarr; Add SSH Key.' },
linode: { name: 'Linode (Akamai)', url: 'https://www.linode.com/docs/guides/use-public-key-authentication-with-ssh/', steps: 'Go to <strong>Profile</strong> &rarr; <strong>SSH Keys</strong> &rarr; Add SSH Key.' },
hetzner: { name: 'Hetzner', url: 'https://docs.hetzner.com/cloud/servers/getting-started/connecting-to-the-server/', steps: 'Go to <strong>Security</strong> &rarr; <strong>SSH Keys</strong> in Hetzner Cloud Console.' },
ovh: { name: 'OVH', url: 'https://help.ovhcloud.com/csm/en-dedicated-servers-creating-ssh-keys?id=kb_article_view&sysparm_article=KB0047697', steps: 'In OVHcloud Control Panel &rarr; <strong>Public Cloud</strong> &rarr; <strong>SSH Keys</strong>.' },
aws: { name: 'AWS (EC2)', url: 'https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-key-pairs.html', steps: 'AWS Console &rarr; EC2 &rarr; <strong>Key Pairs</strong> &rarr; Create or Import.' },
contabo: { name: 'Contabo', url: 'https://contabo.com/blog/establishing-connection-server-ssh/', steps: 'Generate keys locally, copy manually with <code style="color:#00ff41">ssh-copy-id</code>.' },
other: { name: 'Other', url: '', steps: '' }
};
function sshKeyChoice(choice) {
document.getElementById('ssh-has-keys').style.display = (choice === 'yes') ? 'block' : 'none';
document.getElementById('ssh-no-keys').style.display = (choice === 'no') ? 'block' : 'none';
document.getElementById('btn-has-keys').style.background = (choice === 'yes') ? '#00ff41' : '';
document.getElementById('btn-has-keys').style.color = (choice === 'yes') ? '#000' : '';
document.getElementById('btn-no-keys').style.background = (choice === 'no') ? '#00ff41' : '';
document.getElementById('btn-no-keys').style.color = (choice === 'no') ? '#000' : '';
}
function sshHostChanged() {
const id = document.getElementById('w-ssh-host').value;
const guideDiv = document.getElementById('ssh-host-guide');
const genericDiv = document.getElementById('ssh-generic-guide');
if (!id) { guideDiv.style.display = 'none'; genericDiv.style.display = 'none'; return; }
const host = sshHostGuides[id];
genericDiv.style.display = 'block';
if (host && (host.url || host.steps)) {
let html = '<p style="color:#88ff88;font-size:12px;margin-bottom:8px"><strong>' + escHtml(host.name) + ' SSH Key Guide</strong></p>';
if (host.steps) html += '<p style="font-size:11px;color:#888;margin-bottom:8px">' + host.steps + '</p>';
if (host.url) html += '<p style="margin-top:8px"><a href="' + escHtml(host.url) + '" target="_blank" style="color:#00ff41">' + escHtml(host.url) + '</a></p>';
guideDiv.innerHTML = html;
guideDiv.style.display = 'block';
} else { guideDiv.style.display = 'none'; }
}
function saveKeyAndContinue() {
if (!document.getElementById('w-key').value) { alert('Enter SSH private key path.'); return; }
goStep(3);
}
function saveNewKeyAndContinue() {
const k = document.getElementById('w-key-new').value;
if (!k) { alert('Enter SSH private key path.'); return; }
document.getElementById('w-key').value = k;
goStep(3);
}
function getKeyPath() {
return document.getElementById('w-key').value || document.getElementById('w-key-new').value || '';
}
async function saveVPS() {
const body = { vps_host: document.getElementById('w-host').value, vps_user: document.getElementById('w-user').value,
vps_port: parseInt(document.getElementById('w-port').value) || 22, ssh_key_path: getKeyPath() };
if (!body.vps_host) { alert('Enter server IP.'); return; }
if (!body.ssh_key_path) { alert('Go back and enter SSH key path.'); return; }
const res = await apiPost('/api/settings', body);
if (res.ok) goStep(4); else alert('Error: ' + (res.error || 'Unknown'));
}
async function saveAPI() {
const body = { domain: document.getElementById('w-domain').value, hosting_provider: document.getElementById('w-provider').value,
hostinger_api_key: document.getElementById('w-apikey').value };
if (!body.domain) { alert('Enter your domain.'); return; }
const res = await apiPost('/api/settings', body);
if (res.ok) goStep(5); else alert('Error: ' + (res.error || 'Unknown'));
}
async function savePaths() {
const body = { web_root: document.getElementById('w-webroot').value || '/var/www',
compose_path: document.getElementById('w-compose').value, setup_complete: true };
const res = await apiPost('/api/settings', body);
if (res.ok) showTestModal(); else alert('Error: ' + (res.error || 'Unknown'));
}
// ── Test modal (same as before) ─────────────────────────────────
function showTestModal() {
document.getElementById('wiz-modal').style.display = 'flex';
document.getElementById('modal-title').textContent = 'Setup Complete!';
document.getElementById('modal-body').innerHTML =
'<p style="margin-bottom:10px">Your settings have been saved.</p>' +
'<p>Would you like to test the connection?</p>';
document.getElementById('modal-buttons').innerHTML =
'<button class="btn" onclick="runTest()">Yes, Test Connection</button> ' +
'<button class="btn" onclick="closeModal()">Skip</button>';
}
async function runTest() {
document.getElementById('modal-title').textContent = 'Testing Connection...';
document.getElementById('modal-body').innerHTML = '<span style="color:#00ff41">Connecting via SSH...</span>';
document.getElementById('modal-buttons').innerHTML = '';
const sshRes = await apiGet('/api/wizard/test');
if (sshRes.ok && sshRes.data) {
const d = sshRes.data;
if (d.ssh_ok) {
let html = '<p style="color:#00ff41;margin-bottom:10px"><strong>SSH: SUCCESS</strong></p>';
html += '<div style="background:#000;padding:8px;border:1px solid #333;font-size:11px;margin-bottom:10px">' + escHtml(d.ssh_output || '') + '</div>';
if (d.api_ok) html += '<p style="color:#00ff41"><strong>DNS API: SUCCESS</strong></p>';
else if (d.api_error) html += '<p style="color:#ffaa00"><strong>DNS API: ' + escHtml(d.api_error) + '</strong></p>';
document.getElementById('modal-title').textContent = 'Connection Successful!';
document.getElementById('modal-body').innerHTML = html;
document.getElementById('modal-buttons').innerHTML =
'<button class="btn" onclick="window.location.href=\'/\'">Go to Dashboard</button>';
} else { showTestFailed(d.ssh_error || d.error || 'Connection failed'); }
} else { showTestFailed(sshRes.error || 'Connection failed'); }
}
function showTestFailed(error) {
document.getElementById('modal-title').textContent = 'Connection Failed';
document.getElementById('modal-body').innerHTML =
'<p style="color:#ff4444;margin-bottom:10px"><strong>Connection Failed</strong></p>' +
'<div style="background:#000;padding:8px;border:1px solid #ff4444;font-size:11px;margin-bottom:10px;color:#ff4444;white-space:pre-wrap">' + escHtml(error) + '</div>' +
'<p style="color:#ffaa00;margin-bottom:8px">Check:</p>' +
'<ul style="font-size:11px;color:#888;margin-left:15px;line-height:1.8">' +
'<li>Server IP is correct and VPS is running</li>' +
'<li>SSH port is open and correct</li>' +
'<li>SSH key exists at the specified path</li>' +
'<li>Public key is in ~/.ssh/authorized_keys on server</li></ul>';
document.getElementById('modal-buttons').innerHTML =
'<button class="btn" onclick="closeModal()">Close &amp; Fix Settings</button>';
}
function closeModal() { document.getElementById('wiz-modal').style.display = 'none'; }
// ── Prefill ─────────────────────────────────────────────────────
async function prefillWizard() {
const res = await apiGet('/api/settings');
if (!res.ok) return;
const d = res.data;
if (d.vps_host) document.getElementById('w-host').value = d.vps_host;
if (d.vps_user) document.getElementById('w-user').value = d.vps_user;
if (d.vps_port) document.getElementById('w-port').value = d.vps_port;
if (d.ssh_key_path) {
document.getElementById('w-key').value = d.ssh_key_path;
document.getElementById('w-key-new').value = d.ssh_key_path;
}
if (d.domain) document.getElementById('w-domain').value = d.domain;
if (d.hostinger_api_key) document.getElementById('w-apikey').value = d.hostinger_api_key;
if (d.web_root) document.getElementById('w-webroot').value = d.web_root;
if (d.compose_path) document.getElementById('w-compose').value = d.compose_path;
if (d.hosting_provider) {
document.getElementById('w-provider').value = d.hosting_provider;
wizProviderChanged();
}
}
loadWizProviders().then(() => prefillWizard());
</script>
{% endblock %}