feat(web): add admin settings and vault detail

This commit is contained in:
mirivlad 2026-07-17 06:01:40 +08:00
parent 5ca030e5f9
commit 6ca174b2d9
5 changed files with 41 additions and 1 deletions

View File

@ -116,6 +116,10 @@ var _translations = map[string]map[string]string{
"admin.runCleanup": "Запустить безопасную очистку", "admin.runCleanup": "Запустить безопасную очистку",
"admin.downloadDiagnostics": "Скачать диагностику", "admin.downloadDiagnostics": "Скачать диагностику",
"admin.vaultPrivacy": "Содержимое файлов и операции не отображаются в диагностике.", "admin.vaultPrivacy": "Содержимое файлов и операции не отображаются в диагностике.",
"admin.general": "Общие настройки",
"admin.serverName": "Название сервера",
"admin.allowRegistration": "Разрешить публичную регистрацию",
"admin.smtpConfigured": "SMTP настраивается отдельно и пароль никогда не возвращается в форму.",
"user.account": "Моя учётная запись", "user.account": "Моя учётная запись",
"user.devices": "Подключённые устройства", "user.devices": "Подключённые устройства",
"user.noDevices": "Устройств пока нет", "user.noDevices": "Устройств пока нет",
@ -355,6 +359,10 @@ var _translations = map[string]map[string]string{
"admin.runCleanup": "Run safe cleanup", "admin.runCleanup": "Run safe cleanup",
"admin.downloadDiagnostics": "Download diagnostics", "admin.downloadDiagnostics": "Download diagnostics",
"admin.vaultPrivacy": "File contents and operations are not displayed in diagnostics.", "admin.vaultPrivacy": "File contents and operations are not displayed in diagnostics.",
"admin.general": "General settings",
"admin.serverName": "Server name",
"admin.allowRegistration": "Allow public registration",
"admin.smtpConfigured": "SMTP is configured separately and its password is never returned to a form.",
"user.account": "My account", "user.account": "My account",
"user.devices": "Connected devices", "user.devices": "Connected devices",
"user.noDevices": "No devices yet", "user.noDevices": "No devices yet",

View File

@ -39,6 +39,9 @@ func NewServer(dbPath, dataDir string, cfg *Config) (*Server, error) {
if cfg == nil { if cfg == nil {
cfg = DefaultConfig() cfg = DefaultConfig()
} }
if cfg.path == "" {
cfg.path = filepath.Join(dataDir, "config.yml")
}
if err := cfg.normalize(); err != nil { if err := cfg.normalize(); err != nil {
return nil, fmt.Errorf("config: %w", err) return nil, fmt.Errorf("config: %w", err)
} }

View File

@ -0,0 +1,2 @@
{{define "admin_settings"}}{{template "layout" .}}{{end}}
{{define "content"}}<section class="admin-shell"><div class="admin-content"><p class="eyebrow">{{t .Locale "admin.settings"}}</p><h1>{{t .Locale "admin.settings"}}</h1><section class="card panel"><h2>{{t .Locale "admin.general"}}</h2><form method="post" action="/admin/action" class="stack"><input type="hidden" name="csrf_token" value="{{.CSRF}}"><input type="hidden" name="action" value="web-settings"><label>{{t .Locale "admin.serverName"}}<input name="server_name" value="{{.ServerName}}"></label><label>{{t .Locale "locale.label"}}<select name="default_locale"><option value="en" {{if eq .DefaultLocale "en"}}selected{{end}}>English</option><option value="ru" {{if eq .DefaultLocale "ru"}}selected{{end}}>Русский</option></select></label><label><input name="allow_registration" type="checkbox" {{if .AllowRegistration}}checked{{end}}>{{t .Locale "admin.allowRegistration"}}</label><label>{{t .Locale "field.password"}}<input name="password" type="password" required></label><button class="button primary">{{t .Locale "admin.saveUser"}}</button></form></section><section class="card panel"><h2>{{t .Locale "admin.smtpTitle"}}</h2><p class="muted">{{t .Locale "admin.smtpConfigured"}}</p><a class="button secondary" href="/admin/dashboard">{{t .Locale "common.back"}}</a></section></div></section>{{end}}

View File

@ -141,6 +141,10 @@ func (s *Server) handleAdminWeb(w http.ResponseWriter, r *http.Request) {
s.renderWebError(w, r, http.StatusInternalServerError, "error.internal", "/admin/dashboard") s.renderWebError(w, r, http.StatusInternalServerError, "error.internal", "/admin/dashboard")
return return
} }
if page == "settings" {
s.renderPage(w, r, "admin_settings", data)
return
}
s.renderPage(w, r, "admin", data) s.renderPage(w, r, "admin", data)
} }
@ -330,6 +334,27 @@ func (s *Server) handleAdminWebAction(w http.ResponseWriter, r *http.Request) {
} }
action := r.FormValue("action") action := r.FormValue("action")
switch action { switch action {
case "web-settings":
if !s.adminReauth(r, session.SubjectID) {
s.renderWebError(w, r, http.StatusForbidden, "error.invalidCredentials", "/admin/settings")
return
}
locale := r.FormValue("default_locale")
if locale != "ru" && locale != "en" {
locale = "en"
}
s.cfg.mu.Lock()
s.cfg.Web.DefaultLocale = locale
s.cfg.Web.AllowRegistration = r.FormValue("allow_registration") == "on"
s.cfg.Web.ServerName = strings.TrimSpace(r.FormValue("server_name"))
err := s.cfg.saveLocked()
s.cfg.mu.Unlock()
if err != nil {
jsonInternalError(w, err)
return
}
s.auditLog("web_settings_updated", "", "", s.clientIP(r), "updated by administrator")
http.Redirect(w, r, "/admin/settings", http.StatusSeeOther)
case "toggle-user": case "toggle-user":
if !s.adminReauth(r, session.SubjectID) { if !s.adminReauth(r, session.SubjectID) {
s.renderWebError(w, r, http.StatusForbidden, "error.invalidCredentials", "/admin/users") s.renderWebError(w, r, http.StatusForbidden, "error.invalidCredentials", "/admin/users")

View File

@ -20,6 +20,7 @@ type webRenderer struct {
type webPage struct { type webPage struct {
Locale string Locale string
LocalePreference string LocalePreference string
DefaultLocale string
Title string Title string
ServerName string ServerName string
CurrentPath string CurrentPath string
@ -111,7 +112,7 @@ func newWebRenderer() (*webRenderer, error) {
return nil, err return nil, err
} }
renderer := &webRenderer{templates: make(map[string]*template.Template)} renderer := &webRenderer{templates: make(map[string]*template.Template)}
for _, page := range []string{"home", "login", "register", "forgot", "reset", "confirm", "message", "error", "admin_login", "dashboard", "admin", "admin_create_user", "vault_detail"} { for _, page := range []string{"home", "login", "register", "forgot", "reset", "confirm", "message", "error", "admin_login", "dashboard", "admin", "admin_create_user", "vault_detail", "admin_settings"} {
clone, err := layout.Clone() clone, err := layout.Clone()
if err != nil { if err != nil {
return nil, err return nil, err
@ -139,6 +140,7 @@ func (s *Server) renderPageStatus(w http.ResponseWriter, r *http.Request, page s
} }
data.Locale = s.webLocale(r) data.Locale = s.webLocale(r)
data.LocalePreference = s.webLocalePreference(r) data.LocalePreference = s.webLocalePreference(r)
data.DefaultLocale = s.cfg.Web.DefaultLocale
data.ServerName = s.cfg.Web.ServerName data.ServerName = s.cfg.Web.ServerName
data.CurrentPath = r.URL.Path data.CurrentPath = r.URL.Path
data.CurrentURL = r.URL.RequestURI() data.CurrentURL = r.URL.RequestURI()