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:
222
setec-web/templates/configs.html
Normal file
222
setec-web/templates/configs.html
Normal file
@@ -0,0 +1,222 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Config Editor{% endblock %}
|
||||
{% block content %}
|
||||
<h1>[=] Config Editor</h1>
|
||||
|
||||
<div class="grid grid-2">
|
||||
<div>
|
||||
<div class="card">
|
||||
<div class="card-title">Quick Access</div>
|
||||
<h2>Nginx</h2>
|
||||
<div class="toolbar">
|
||||
<button class="btn" onclick="loadConfig('/etc/nginx/nginx.conf')">nginx.conf</button>
|
||||
<button class="btn" onclick="listDir('/etc/nginx/sites-available/')">sites-available/</button>
|
||||
<button class="btn" onclick="listDir('/etc/nginx/conf.d/')">conf.d/</button>
|
||||
<button class="btn" onclick="listDir('/etc/nginx/snippets/')">snippets/</button>
|
||||
</div>
|
||||
<h2>GitLab</h2>
|
||||
<div class="toolbar">
|
||||
<button class="btn" onclick="loadConfig('/etc/gitlab/gitlab.rb')">gitlab.rb</button>
|
||||
</div>
|
||||
<h2>Gitea</h2>
|
||||
<div class="toolbar">
|
||||
<button class="btn" onclick="loadConfig('/etc/gitea/app.ini')">app.ini (etc)</button>
|
||||
<button class="btn" onclick="loadConfig('/var/lib/gitea/custom/conf/app.ini')">app.ini (custom)</button>
|
||||
</div>
|
||||
<h2>SSH</h2>
|
||||
<div class="toolbar">
|
||||
<button class="btn" onclick="loadConfig('/etc/ssh/sshd_config')">sshd_config</button>
|
||||
<button class="btn" onclick="loadConfig('/etc/ssh/ssh_config')">ssh_config</button>
|
||||
</div>
|
||||
<h2>Mail</h2>
|
||||
<div class="toolbar">
|
||||
<button class="btn" onclick="loadConfig('/etc/postfix/main.cf')">postfix main.cf</button>
|
||||
<button class="btn" onclick="loadConfig('/etc/postfix/master.cf')">postfix master.cf</button>
|
||||
<button class="btn" onclick="loadConfig('/etc/opendkim.conf')">opendkim.conf</button>
|
||||
</div>
|
||||
<h2>Security</h2>
|
||||
<div class="toolbar">
|
||||
<button class="btn" onclick="loadConfig('/etc/fail2ban/jail.local')">fail2ban jail</button>
|
||||
<button class="btn" onclick="loadConfig('/etc/ufw/ufw.conf')">ufw.conf</button>
|
||||
</div>
|
||||
<h2>Docker</h2>
|
||||
<div class="toolbar">
|
||||
<button class="btn" onclick="loadConfig('/etc/docker/daemon.json')">daemon.json</button>
|
||||
<button class="btn" onclick="loadConfig('/opt/seteclabs/docker-compose.yml')">docker-compose.yml</button>
|
||||
</div>
|
||||
<h2>System</h2>
|
||||
<div class="toolbar">
|
||||
<button class="btn" onclick="loadConfig('/etc/hosts')">hosts</button>
|
||||
<button class="btn" onclick="loadConfig('/etc/hostname')">hostname</button>
|
||||
<button class="btn" onclick="loadConfig('/etc/resolv.conf')">resolv.conf</button>
|
||||
<button class="btn" onclick="loadConfig('/etc/fstab')">fstab</button>
|
||||
<button class="btn" onclick="loadConfig('/etc/crontab')">crontab</button>
|
||||
</div>
|
||||
<h2>Custom Path</h2>
|
||||
<div class="toolbar">
|
||||
<input type="text" id="custom-path" placeholder="/etc/some/config.conf" style="width:300px">
|
||||
<button class="btn" onclick="loadConfig(document.getElementById('custom-path').value)">Load</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">Directory Browser</div>
|
||||
<div class="output" id="dir-list" style="max-height:300px"><span class="info">Click a directory button above</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="card">
|
||||
<div class="card-title">
|
||||
Editor: <span id="current-file" style="color:#ffaa00">none</span>
|
||||
</div>
|
||||
<textarea id="config-editor" rows="30" style="width:100%;resize:vertical;tab-size:4" spellcheck="false"></textarea>
|
||||
<div class="toolbar" style="margin-top:5px">
|
||||
<button class="btn" onclick="saveConfig()">Save</button>
|
||||
<button class="btn" onclick="testConfig()">Test Nginx</button>
|
||||
<button class="btn" onclick="showDiff()">Diff vs Backup</button>
|
||||
<button class="btn" onclick="showBackups()">List Backups</button>
|
||||
<button class="btn btn-warn" onclick="reloadSvc()">Reload Service</button>
|
||||
</div>
|
||||
<div style="margin-top:5px">
|
||||
<label>Reload service after save:</label>
|
||||
<select id="reload-svc">
|
||||
<option value="">-- none --</option>
|
||||
<option value="nginx">nginx</option>
|
||||
<option value="sshd">sshd</option>
|
||||
<option value="postfix">postfix</option>
|
||||
<option value="opendkim">opendkim</option>
|
||||
<option value="fail2ban">fail2ban</option>
|
||||
<option value="docker">docker</option>
|
||||
<option value="redis">redis</option>
|
||||
<option value="postgresql">postgresql</option>
|
||||
<option value="mysql">mysql</option>
|
||||
<option value="dovecot">dovecot</option>
|
||||
<option value="grafana-server">grafana</option>
|
||||
<option value="prometheus">prometheus</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">Output</div>
|
||||
<div class="output" id="output"><span class="info">Ready. Click a config file to edit.</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
let currentFile = '';
|
||||
|
||||
// Check URL params for ?file=
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
if (params.get('file')) {
|
||||
loadConfig(params.get('file'));
|
||||
}
|
||||
|
||||
async function loadConfig(path) {
|
||||
if (!path) return;
|
||||
currentFile = path;
|
||||
document.getElementById('current-file').textContent = path;
|
||||
document.getElementById('config-editor').value = 'Loading...';
|
||||
const res = await apiGet('/api/configs/read?path=' + encodeURIComponent(path));
|
||||
if (res.ok) {
|
||||
document.getElementById('config-editor').value = res.data.stdout || '';
|
||||
document.getElementById('output').innerHTML = '<span class="info">Loaded ' + escHtml(path) + ' (' + (res.data.stdout||'').split('\n').length + ' lines)</span>';
|
||||
} else {
|
||||
document.getElementById('config-editor').value = '';
|
||||
document.getElementById('output').innerHTML = '<span class="err">' + escHtml(res.error || 'Failed to read file') + '</span>';
|
||||
}
|
||||
// Auto-detect reload service
|
||||
if (path.includes('nginx')) document.getElementById('reload-svc').value = 'nginx';
|
||||
else if (path.includes('postfix')) document.getElementById('reload-svc').value = 'postfix';
|
||||
else if (path.includes('sshd') || path.includes('/ssh/')) document.getElementById('reload-svc').value = 'sshd';
|
||||
else if (path.includes('opendkim')) document.getElementById('reload-svc').value = 'opendkim';
|
||||
else if (path.includes('fail2ban')) document.getElementById('reload-svc').value = 'fail2ban';
|
||||
else if (path.includes('docker')) document.getElementById('reload-svc').value = 'docker';
|
||||
}
|
||||
|
||||
async function saveConfig() {
|
||||
if (!currentFile) { alert('No file loaded'); return; }
|
||||
if (!confirm('Save changes to ' + currentFile + '?\nA backup will be created automatically.')) return;
|
||||
const content = document.getElementById('config-editor').value;
|
||||
const res = await apiPost('/api/configs/write', {path: currentFile, content: content});
|
||||
if (res.ok) {
|
||||
document.getElementById('output').innerHTML = '<span class="status-ok">Saved ' + escHtml(currentFile) + ' (backup created)</span>';
|
||||
// Auto-reload service if selected
|
||||
const svc = document.getElementById('reload-svc').value;
|
||||
if (svc) {
|
||||
const r2 = await apiPost('/api/configs/reload-service', {service: svc});
|
||||
showResult(r2);
|
||||
}
|
||||
} else {
|
||||
showResult(res);
|
||||
}
|
||||
}
|
||||
|
||||
async function testConfig() {
|
||||
const res = await apiPost('/api/configs/test-nginx');
|
||||
showResult(res);
|
||||
}
|
||||
|
||||
async function showDiff() {
|
||||
if (!currentFile) return;
|
||||
const res = await apiPost('/api/configs/diff', {path: currentFile});
|
||||
showResult(res);
|
||||
}
|
||||
|
||||
async function showBackups() {
|
||||
if (!currentFile) return;
|
||||
const res = await apiGet('/api/configs/backups?path=' + encodeURIComponent(currentFile));
|
||||
showResult(res);
|
||||
}
|
||||
|
||||
async function reloadSvc() {
|
||||
const svc = document.getElementById('reload-svc').value;
|
||||
if (!svc) { alert('Select a service to reload'); return; }
|
||||
const res = await apiPost('/api/configs/reload-service', {service: svc});
|
||||
showResult(res);
|
||||
}
|
||||
|
||||
async function listDir(path) {
|
||||
const res = await apiGet('/api/files/list?path=' + encodeURIComponent(path));
|
||||
const el = document.getElementById('dir-list');
|
||||
if (!res.ok) { el.innerHTML = '<span class="err">' + res.error + '</span>'; return; }
|
||||
|
||||
const lines = (res.data.stdout || '').trim().split('\n');
|
||||
let html = '';
|
||||
for (const line of lines) {
|
||||
const parts = line.trim().split(/\s+/);
|
||||
if (parts.length < 9) continue;
|
||||
const fname = parts.slice(8).join(' ');
|
||||
if (fname === '.' || fname === '..') continue;
|
||||
const isDir = line.startsWith('d');
|
||||
const fullPath = path.replace(/\/$/, '') + '/' + fname;
|
||||
if (isDir) {
|
||||
html += `<a href="#" onclick="listDir('${fullPath}/');return false" style="color:#ffaa00">${fname}/</a>\n`;
|
||||
} else {
|
||||
html += `<a href="#" onclick="loadConfig('${fullPath}');return false">${fname}</a>\n`;
|
||||
}
|
||||
}
|
||||
el.innerHTML = html || '<span class="info">(empty)</span>';
|
||||
}
|
||||
|
||||
// Handle tab key in editor
|
||||
document.getElementById('config-editor').addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Tab') {
|
||||
e.preventDefault();
|
||||
const start = this.selectionStart;
|
||||
const end = this.selectionEnd;
|
||||
this.value = this.value.substring(0, start) + ' ' + this.value.substring(end);
|
||||
this.selectionStart = this.selectionEnd = start + 4;
|
||||
}
|
||||
// Ctrl+S to save
|
||||
if (e.key === 's' && (e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
saveConfig();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user