fix: global settings form inside admin panel sidebar

This commit is contained in:
Claus Lohmar 2026-07-08 15:20:41 +01:00
parent e1e8ad0f08
commit 857ce833cf

117
main.go
View file

@ -235,22 +235,39 @@ func proxyToUpstream(upstream string) http.HandlerFunc {
func adminHandler(w http.ResponseWriter, r *http.Request) {
apiBase := "http://127.0.0.1:8080"
apiToken := os.Getenv("AUTHELIA_SECRET")
configDir := os.Getenv("CONFIG_DIR")
if configDir == "" {
configDir = "/opt/nextworkspace/config/nextworkspace"
}
// Load settings for Global tab
settings, _ := loadSettings(configDir)
domain := os.Getenv("DOMAIN")
adminEmail := os.Getenv("TLS_EMAIL")
switch r.Method {
case http.MethodGet:
// List users from authelia-api
req, _ := http.NewRequest("GET", apiBase+"/api/users", nil)
req.Header.Set("Authorization", "Bearer "+apiToken)
resp, err := http.DefaultClient.Do(req)
if err != nil {
http.Error(w, fmt.Sprintf("API error: %v", err), http.StatusBadGateway)
// If settings JSON format requested
if r.URL.Query().Get("format") == "settings" {
w.Header().Set("Content-Type", "application/json")
jsonData := fmt.Sprintf(`{"company_name":"%s","language":"%s","timezone":"%s","smtp_host":"%s","smtp_port":"%d","smtp_user":"%s","smtp_sender":"%s"}`,
settings.Company.Name, settings.Company.Language, settings.Company.Timezone,
settings.SMTP.Host, settings.SMTP.Port, settings.SMTP.User, settings.SMTP.Sender)
w.Write([]byte(jsonData))
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
// If JSON format requested, return raw API response
if r.URL.Query().Get("format") == "json" {
req, _ := http.NewRequest("GET", apiBase+"/api/users", nil)
req.Header.Set("Authorization", "Bearer "+apiToken)
resp, err := http.DefaultClient.Do(req)
if err != nil {
http.Error(w, fmt.Sprintf("API error: %v", err), http.StatusBadGateway)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
w.Header().Set("Content-Type", "application/json")
w.Write(body)
return
@ -258,8 +275,18 @@ func adminHandler(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.New("admin").Parse(adminHTML))
w.Header().Set("Content-Type", "text/html; charset=utf-8")
autheliaUp := false
if resp, err := http.Get("http://127.0.0.1:9091/api/health"); err == nil {
autheliaUp = resp.StatusCode == 200
resp.Body.Close()
}
tmpl.Execute(w, map[string]interface{}{
"ApiError": resp.StatusCode != 200,
"Settings": settings,
"Domain": domain,
"AdminEmail": adminEmail,
"AutheliaUp": autheliaUp,
})
case http.MethodPost:
@ -744,7 +771,20 @@ const adminHTML = `<!DOCTYPE html>
.btn-sm { padding: 0.3rem 0.6rem; font-size: 0.8rem; }
input, select { padding: 0.45rem 0.7rem; border: 1px solid #e2e8f0; border-radius: 6px; font-size: 0.88rem; color: #4a5568; background: #fff; outline: none; transition: border .12s; }
input:focus, select:focus { border-color: #63b3ed; box-shadow: 0 0 0 2px rgba(99,179,237,0.15); }
.field { margin-bottom: 0.9rem; }
.field label { display: block; font-size: 0.82rem; font-weight: 500; color: #4a5568; margin-bottom: 0.25rem; }
.field input, .field select { width: 100%; }
.field-row { display: flex; gap: 1rem; }
.field-row .field { flex: 1; }
.form-row { display: flex; gap: 0.75rem; align-items: center; flex-wrap: wrap; margin-bottom: 1rem; }
.info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0.5rem; }
.info-item { padding: 0.5rem 0; border-bottom: 1px solid #f7fafc; font-size: 0.9rem; }
.info-item .label { font-size: 0.78rem; color: #a0aec0; }
.info-item .value { font-size: 0.9rem; color: #4a5568; }
.status-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 0.35rem; }
.status-up { background: #48bb78; }
.status-down { background: #fc8181; }
.actions { display: flex; gap: 0.75rem; align-items: center; margin-top: 0.5rem; }
.success { background: #c6f6d5; color: #276749; padding: 0.75rem 1rem; border-radius: 8px; margin-bottom: 1rem; font-size: 0.88rem; border: 1px solid #9ae6b4; }
.error { background: #fed7d7; color: #c53030; padding: 0.75rem 1rem; border-radius: 8px; margin-bottom: 1rem; font-size: 0.88rem; border: 1px solid #feb2b2; }
.password-box { background: #1a1a2e; color: #63b3ed; padding: 0.65rem 1rem; border-radius: 6px; font-family: 'SF Mono', 'Fira Code', monospace; font-size: 0.85rem; margin-top: 0.5rem; display: inline-block; }
@ -785,7 +825,37 @@ const adminHTML = `<!DOCTYPE html>
<!-- Global -->
<div id="page-global" class="page">
<div class="page-header"><h2>Global Settings</h2><p>General system configuration for your workspace.</p></div>
<div class="card"><h3>System Information</h3><p style="color:#718096;font-size:0.9rem;">NextWorkspace is a self-hosted productivity suite powered by Caddy + Authelia.</p></div>
<form id="global-form" onsubmit="return saveGlobal(event)">
<div class="card">
<h3>Company</h3>
<div class="field"><label>Company Name</label><input type="text" name="company_name" value="{{.Settings.Company.Name}}"></div>
<div class="field-row">
<div class="field"><label>Language</label><select name="language"><option value="en" {{if eq .Settings.Company.Language "en"}}selected{{end}}>English</option><option value="de" {{if eq .Settings.Company.Language "de"}}selected{{end}}>Deutsch</option></select></div>
<div class="field"><label>Timezone</label><select name="timezone">{{$tz := .Settings.Company.Timezone}}<option value="UTC" {{if eq $tz "UTC"}}selected{{end}}>UTC</option><option value="Europe/London" {{if eq $tz "Europe/London"}}selected{{end}}>Europe/London</option><option value="Europe/Berlin" {{if eq $tz "Europe/Berlin"}}selected{{end}}>Europe/Berlin</option><option value="America/New_York" {{if eq $tz "America/New_York"}}selected{{end}}>America/New_York</option><option value="Asia/Tokyo" {{if eq $tz "Asia/Tokyo"}}selected{{end}}>Asia/Tokyo</option></select></div>
</div>
</div>
<div class="card">
<h3>SMTP</h3>
<div class="field"><label>SMTP Host</label><input type="text" name="smtp_host" value="{{.Settings.SMTP.Host}}" placeholder="smtp.openxchange.eu"></div>
<div class="field-row">
<div class="field"><label>SMTP Port</label><input type="number" name="smtp_port" value="{{.Settings.SMTP.Port}}" placeholder="587"></div>
<div class="field"><label>SMTP User</label><input type="text" name="smtp_user" value="{{.Settings.SMTP.User}}" placeholder="post@nextwks.eu"></div>
</div>
<div class="field-row">
<div class="field"><label>SMTP Password</label><input type="password" name="smtp_password" placeholder="Leave blank to keep current"></div>
<div class="field"><label>Sender Email</label><input type="email" name="smtp_sender" value="{{.Settings.SMTP.Sender}}" placeholder="post@nextwks.eu"></div>
</div>
</div>
<div class="card">
<h3>System (read-only)</h3>
<div class="info-grid">
<div class="info-item"><div class="label">Domain</div><div class="value">{{.Domain}}</div></div>
<div class="info-item"><div class="label">Admin Email</div><div class="value">{{.AdminEmail}}</div></div>
<div class="info-item"><div class="label">Authelia</div><div class="value">{{if .AutheliaUp}}<span class="status-dot status-up"></span>Running{{else}}<span class="status-dot status-down"></span>Unreachable{{end}}</div></div>
</div>
</div>
<div class="actions"><button type="submit" class="btn btn-primary">Save Settings</button><span id="savemsg" style="display:none;color:#48bb78;font-size:0.9rem;">Saved</span></div>
</form>
</div>
<!-- Access (Users) -->
@ -859,6 +929,33 @@ const adminHTML = `<!DOCTYPE html>
const page = document.getElementById('page-' + name);
if (page) page.classList.remove('hidden');
if (name === 'access') loadUsers();
if (name === 'global') loadGlobal();
return false;
}
async function loadGlobal() {
const resp = await fetch('/config?format=settings');
if (!resp.ok) return;
const s = await resp.json();
document.querySelector('[name="company_name"]').value = s.company_name || '';
document.querySelector('[name="language"]').value = s.language || 'en';
document.querySelector('[name="timezone"]').value = s.timezone || 'UTC';
document.querySelector('[name="smtp_host"]').value = s.smtp_host || '';
document.querySelector('[name="smtp_port"]').value = s.smtp_port || '587';
document.querySelector('[name="smtp_user"]').value = s.smtp_user || '';
document.querySelector('[name="smtp_sender"]').value = s.smtp_sender || '';
}
async function saveGlobal(e) {
e.preventDefault();
const form = document.getElementById('global-form');
const data = new FormData(form);
const resp = await fetch('/config/global/save', {method:'POST', body:new URLSearchParams(data)});
const msg = document.getElementById('savemsg');
if (resp.ok) {
msg.style.display = 'inline';
setTimeout(() => msg.style.display = 'none', 3000);
}
return false;
}