Почта: поле «Проверочное письмо — кому» вернулось — по умолчанию администратор системы, можно указать свой адрес

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
This commit is contained in:
zuevav
2026-10-06 22:51:29 +03:00
parent 4593a72cec
commit c4fb0d9bfc
4 changed files with 55 additions and 12 deletions
+21
View File
@@ -1,6 +1,8 @@
package lkgateway package lkgateway
import ( import (
"net/http"
"net/http/httptest"
"path/filepath" "path/filepath"
"strings" "strings"
"testing" "testing"
@@ -136,3 +138,22 @@ func TestSMBLocation(t *testing.T) {
t.Error("smbShareOK/smbWin") t.Error("smbShareOK/smbWin")
} }
} }
// Поле «Проверочное письмо — кому»: адрес администратора по умолчанию не дублируется, свой — запоминается.
func TestSMTPTestTo(t *testing.T) {
s := SMTPSettings{To: "it@example.ru", TestTo: "old@example.ru"}
req := func(body string) *http.Request {
r := httptest.NewRequest(http.MethodPost, "/admin/setup/alerts-smtp-test", strings.NewReader(body))
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
return r
}
if got := smtpTestTo(req("test_to=me%40example.ru"), s); got != "me@example.ru" {
t.Errorf("свой адрес: %q", got)
}
if got := smtpTestTo(req("test_to=it%40example.ru"), s); got != "" {
t.Errorf("адрес администратора не хранится отдельно: %q", got)
}
if got := smtpTestTo(req("host=x"), s); got != "old@example.ru" {
t.Errorf("без поля — прежний: %q", got)
}
}
+12 -10
View File
@@ -557,16 +557,18 @@ type AlertSettings struct {
// SMTPSettings — параметры отправки уведомлений по e-mail. // SMTPSettings — параметры отправки уведомлений по e-mail.
type SMTPSettings struct { type SMTPSettings struct {
Enabled bool `json:"enabled"` Enabled bool `json:"enabled"`
Host string `json:"host"` Host string `json:"host"`
Port int `json:"port"` // 587 (STARTTLS), 465 (SSL), 25 Port int `json:"port"` // 587 (STARTTLS), 465 (SSL), 25
Security string `json:"security"` // "starttls" | "ssl" | "none" Security string `json:"security"` // "starttls" | "ssl" | "none"
Username string `json:"username"` Username string `json:"username"`
Password string `json:"password"` Password string `json:"password"`
From string `json:"from"` From string `json:"from"`
To string `json:"to"` // получатели через запятую To string `json:"to"` // администратор системы (через запятую) — задаётся в «Кому писать»
LastMsg string `json:"last_msg"` // TestTo — куда слать проверочное письмо (поле в блоке «Почта»); пусто — администратору системы.
LastAt time.Time `json:"last_at"` TestTo string `json:"test_to,omitempty"`
LastMsg string `json:"last_msg"`
LastAt time.Time `json:"last_at"`
} }
// ZabbixSettings — trapper-хук: bj шлёт значение элемента данных на сервер/прокси // ZabbixSettings — trapper-хук: bj шлёт значение элемента данных на сервер/прокси
+20 -2
View File
@@ -1042,6 +1042,7 @@ func (h *setupHandlers) saveAlertsSMTP(w http.ResponseWriter, r *http.Request) {
Password: pickStr(r.FormValue("password"), a.SMTP.Password), // пароль в страницу не выводится: пусто — прежний Password: pickStr(r.FormValue("password"), a.SMTP.Password), // пароль в страницу не выводится: пусто — прежний
From: strings.TrimSpace(r.FormValue("from")), From: strings.TrimSpace(r.FormValue("from")),
To: a.SMTP.To, // адрес администратора системы — в «Кому писать» (notifyGroupsCard) To: a.SMTP.To, // адрес администратора системы — в «Кому писать» (notifyGroupsCard)
TestTo: smtpTestTo(r, a.SMTP),
LastMsg: a.SMTP.LastMsg, LastMsg: a.SMTP.LastMsg,
LastAt: a.SMTP.LastAt, LastAt: a.SMTP.LastAt,
} }
@@ -1052,6 +1053,20 @@ func (h *setupHandlers) saveAlertsSMTP(w http.ResponseWriter, r *http.Request) {
setupFlash(w, r, "Настройки SMTP сохранены") setupFlash(w, r, "Настройки SMTP сохранены")
} }
// smtpTestTo — адрес проверочного письма из формы «Почта»; совпадает с адресом администратора — не храним отдельно
// (поле показывает его по умолчанию), поля в запросе нет — прежний.
func smtpTestTo(r *http.Request, s SMTPSettings) string {
_ = r.ParseForm()
if _, ok := r.Form["test_to"]; !ok {
return s.TestTo
}
v := strings.TrimSpace(r.FormValue("test_to"))
if v == strings.TrimSpace(s.To) {
return ""
}
return v
}
// testAlertsSMTP шлёт пробное письмо по текущим (сохранённым) настройкам. // testAlertsSMTP шлёт пробное письмо по текущим (сохранённым) настройкам.
func (h *setupHandlers) testAlertsSMTP(w http.ResponseWriter, r *http.Request) { func (h *setupHandlers) testAlertsSMTP(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost { if r.Method != http.MethodPost {
@@ -1060,8 +1075,11 @@ func (h *setupHandlers) testAlertsSMTP(w http.ResponseWriter, r *http.Request) {
} }
cfg := h.rc.Snapshot() cfg := h.rc.Snapshot()
a := cfg.Alerts a := cfg.Alerts
// кому: администратор системы, а пока его адреса нет — все адреса отделов из «Кому писать» // кому: адрес из поля «Проверочное письмо — кому», иначе администратор системы, а пока его адреса нет —
// все адреса отделов из «Кому писать»
a.SMTP.TestTo = smtpTestTo(r, a.SMTP)
test := a.SMTP test := a.SMTP
test.To = pickStr(a.SMTP.TestTo, a.SMTP.To)
if strings.TrimSpace(test.To) == "" { if strings.TrimSpace(test.To) == "" {
var all []string var all []string
for _, k := range notifyKinds { for _, k := range notifyKinds {
@@ -1070,7 +1088,7 @@ func (h *setupHandlers) testAlertsSMTP(w http.ResponseWriter, r *http.Request) {
test.To = mergeEmails(all...) test.To = mergeEmails(all...)
} }
if strings.TrimSpace(test.To) == "" { if strings.TrimSpace(test.To) == "" {
setupFlash(w, r, "Проверка SMTP: укажите хотя бы один адрес в блоке «Кому писать» и сохраните") setupFlash(w, r, "Проверка SMTP: укажите, куда отправить проверочное письмо")
return return
} }
err := sendSMTP(test, "[bj] Проверка уведомлений", "Это проверочное письмо от bj-server. Если вы его получили — SMTP-канал оповещений работает.") err := sendSMTP(test, "[bj] Проверка уведомлений", "Это проверочное письмо от bj-server. Если вы его получили — SMTP-канал оповещений работает.")
@@ -20,6 +20,8 @@
<div><label>Пароль {{if .Settings.Alerts.SMTP.Password}}<span class="muted">(задан; пусто — оставить)</span>{{end}}</label><input type="password" name="password" autocomplete="new-password" style="margin-top:3px"></div> <div><label>Пароль {{if .Settings.Alerts.SMTP.Password}}<span class="muted">(задан; пусто — оставить)</span>{{end}}</label><input type="password" name="password" autocomplete="new-password" style="margin-top:3px"></div>
<div><label>Отправитель (From)</label><input type="text" name="from" value="{{.Settings.Alerts.SMTP.From}}" placeholder="bj@example.ru" style="margin-top:3px"></div> <div><label>Отправитель (From)</label><input type="text" name="from" value="{{.Settings.Alerts.SMTP.From}}" placeholder="bj@example.ru" style="margin-top:3px"></div>
</div> </div>
<div style="margin-top:12px;max-width:420px"><label>Проверочное письмо — кому</label><input type="text" name="test_to" value="{{if .Settings.Alerts.SMTP.TestTo}}{{.Settings.Alerts.SMTP.TestTo}}{{else}}{{.Settings.Alerts.SMTP.To}}{{end}}" placeholder="it@example.ru" style="margin-top:3px;width:100%">
<span class="muted" style="font-size:12px;display:block;margin-top:4px;line-height:1.4">По умолчанию — администратор системы из блока «Кому писать».</span></div>
<div style="margin-top:16px;display:flex;gap:8px;flex-wrap:wrap;align-items:center"> <div style="margin-top:16px;display:flex;gap:8px;flex-wrap:wrap;align-items:center">
<button type="submit" class="btn">Сохранить</button> <button type="submit" class="btn">Сохранить</button>
<button type="submit" formaction="/admin/setup/alerts-smtp-test" class="btn btn-secondary">Отправить проверочное письмо</button> <button type="submit" formaction="/admin/setup/alerts-smtp-test" class="btn btn-secondary">Отправить проверочное письмо</button>