feat: SMTP test button in admin modal — sends real test email, shows result
This commit is contained in:
parent
4afcc0e135
commit
c8cdb089a6
|
|
@ -332,6 +332,7 @@ func (s *Server) routes() *http.ServeMux {
|
|||
mux.HandleFunc("/admin/login", s.handleAdminLogin)
|
||||
mux.HandleFunc("/admin/dashboard", s.handleAdminDashboard)
|
||||
mux.HandleFunc("/admin/api/stats", s.handleAdminStats)
|
||||
mux.HandleFunc("/admin/api/smtp/test", s.handleAdminSMTPTest)
|
||||
mux.HandleFunc("/admin/", s.handleAdminAPI)
|
||||
mux.HandleFunc("/", s.handleNotFound)
|
||||
return mux
|
||||
|
|
@ -475,6 +476,46 @@ func (s *Server) smtpSend(to, subject, body string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
func (s *Server) smtpTest(host, port, user, pass, from, to string) error {
|
||||
if host == "" || port == "" || from == "" {
|
||||
return fmt.Errorf("SMTP not configured")
|
||||
}
|
||||
addr := net.JoinHostPort(host, port)
|
||||
msg := []byte("From: " + from + "\r\nTo: " + to + "\r\nSubject: Test from Verstak Sync\r\n\r\nThis is a test email from Verstak Sync Server.\r\n")
|
||||
if user != "" {
|
||||
auth := smtp.PlainAuth("", user, pass, host)
|
||||
if port == "465" {
|
||||
tlsCfg := &tls.Config{ServerName: host}
|
||||
conn, err := tls.Dial("tcp", addr, tlsCfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cl, err := smtp.NewClient(conn, host)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cl.Close()
|
||||
if err := cl.Auth(auth); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := cl.Mail(from); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := cl.Rcpt(to); err != nil {
|
||||
return err
|
||||
}
|
||||
w, err := cl.Data()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.Write(msg)
|
||||
return w.Close()
|
||||
}
|
||||
return smtp.SendMail(addr, auth, from, []string{to}, msg)
|
||||
}
|
||||
return smtp.SendMail(addr, nil, from, []string{to}, msg)
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// User helpers
|
||||
// ============================================================
|
||||
|
|
@ -1438,10 +1479,20 @@ function copyKey(key,btn){
|
|||
})
|
||||
}
|
||||
function delKey(id){if(confirm('Удалить ключ?'))fetch('/admin/api/keys/'+id,{method:'DELETE'}).then(()=>location.reload())}
|
||||
function openSMTP(){document.getElementById('smtp-modal').style.display='flex'}
|
||||
function openSMTP(){document.getElementById('smtp-modal').style.display='flex';document.getElementById('smtp-test-result').textContent=''}
|
||||
function closeSMTP(e){if(!e||e.target.id==='smtp-modal')document.getElementById('smtp-modal').style.display='none'}
|
||||
function openHealth(){var m=document.getElementById('health-modal');m.style.display='flex';document.getElementById('health-result').textContent='Загрузка...';fetch('/api/v1/health').then(function(r){return r.text()}).then(function(t){document.getElementById('health-result').textContent=t})}
|
||||
function closeHealth(e){if(!e||e.target.id==='health-modal')document.getElementById('health-modal').style.display='none'}
|
||||
function testSMTP(){
|
||||
var f=document.querySelector('#smtp-modal form')
|
||||
var fd=new FormData(f)
|
||||
var r=document.getElementById('smtp-test-result')
|
||||
r.textContent='⏳ Тестируем...';r.style.color='#888'
|
||||
fetch('/admin/api/smtp/test',{method:'POST',body:fd}).then(function(r2){return r2.json()}).then(function(d){
|
||||
r.textContent=d.ok?'✓ Тест пройден':'✗ '+d.error
|
||||
r.style.color=d.ok?'#4ade80':'#ff6b6b'
|
||||
}).catch(function(e){r.textContent='✗ '+e;r.style.color='#ff6b6b'})
|
||||
}
|
||||
</script>
|
||||
|
||||
<h3>Новый ключ</h3>
|
||||
|
|
@ -1461,7 +1512,11 @@ function closeHealth(e){if(!e||e.target.id==='health-modal')document.getElementB
|
|||
<div class="form-row"><label>Пароль</label><input type="password" name="smtp_pass" placeholder="••••••••"></div>
|
||||
<div class="form-row"><label>От кого</label><input name="smtp_from" value="` + smtpFrom + `" placeholder="noreply@example.com"></div>
|
||||
<div class="form-row"><label>URL сервера</label><input name="server_url" value="` + srvURL + `" placeholder="https://example.com:47732"></div>
|
||||
<div style="margin-top:12px"><button class="btn btn-primary">Сохранить SMTP</button></div>
|
||||
<div style="margin-top:12px;display:flex;gap:8px;align-items:center">
|
||||
<button class="btn btn-primary">Сохранить SMTP</button>
|
||||
<button class="btn" type="button" onclick="testSMTP()">Test</button>
|
||||
<span id="smtp-test-result" style="font-size:12px"></span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1487,6 +1542,34 @@ func (s *Server) handleAdminStats(w http.ResponseWriter, r *http.Request) {
|
|||
jsonOK(w, map[string]int{"ops": opsCount})
|
||||
}
|
||||
|
||||
func (s *Server) handleAdminSMTPTest(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.requireAdmin(w, r) {
|
||||
return
|
||||
}
|
||||
if err := r.ParseForm(); err != nil {
|
||||
jsonErr(w, 400, "bad form")
|
||||
return
|
||||
}
|
||||
host := r.FormValue("smtp_host")
|
||||
port := r.FormValue("smtp_port")
|
||||
user := r.FormValue("smtp_user")
|
||||
pass := r.FormValue("smtp_pass")
|
||||
from := r.FormValue("smtp_from")
|
||||
to := r.FormValue("test_to")
|
||||
if to == "" {
|
||||
to = from
|
||||
}
|
||||
if host == "" || port == "" || from == "" {
|
||||
jsonOK(w, map[string]interface{}{"ok": false, "error": "host, port and from required"})
|
||||
return
|
||||
}
|
||||
if err := s.smtpTest(host, port, user, pass, from, to); err != nil {
|
||||
jsonOK(w, map[string]interface{}{"ok": false, "error": err.Error()})
|
||||
return
|
||||
}
|
||||
jsonOK(w, map[string]interface{}{"ok": true})
|
||||
}
|
||||
|
||||
func (s *Server) handleAdminAPI(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.requireAdmin(w, r) {
|
||||
return
|
||||
|
|
|
|||
Loading…
Reference in New Issue