Files

223 lines
10 KiB
HTML
Raw Permalink Normal View History

{% 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 %}