Первичная настройка, часть 2: шаг 9 «Оповещения и резервные копии», шаг 10 — итоговая проверка готовности (живые проверки со ссылкой «Исправить»); проверка модуля неторговых поручений QUIK (объекты M2M, инструменты, справочник получателей, цикл, ошибки операций); восстановление из копии на /first-run по одноразовому коду; возврат на тот же шаг мастера после «Сохранить» (return_to с ?step); пароль SMTP не выводится в страницу; карточки почты, адресатов и копий — общие для настроек и мастера
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
This commit is contained in:
@@ -14,6 +14,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -188,6 +189,7 @@ type WizardData struct {
|
||||
Certs bool
|
||||
NSD bool
|
||||
TestRun bool
|
||||
Alerts bool // оповещения настроены и резервные копии включены
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,7 +278,7 @@ func (h *setupHandlers) renderWizard(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
cancel()
|
||||
}
|
||||
if step >= 3 && step <= 5 { // DEPO, организация, QUIK МНП — общие блоки из «Настройки»
|
||||
if (step >= 3 && step <= 5) || step == 9 { // DEPO, организация, QUIK МНП, оповещения и копии — общие блоки из «Настройки»
|
||||
fd := h.setupData(w, r, "")
|
||||
d.Fancy = &fd
|
||||
}
|
||||
@@ -287,6 +289,7 @@ func (h *setupHandlers) renderWizard(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
d.Done.Org = doneOrg(s)
|
||||
d.Done.Alerts = doneAlerts(s)
|
||||
d.Done.Quik = !d.DepoLicensed || doneQuik(s)
|
||||
if step == 3 && d.DepoLicensed && strings.TrimSpace(s.Fancy.Server) != "" {
|
||||
// Есть ли у сервера SQL Agent: у Express его нет, у PostgreSQL — тоже; тогда обработку
|
||||
@@ -329,8 +332,8 @@ func depoServerEdition(ctx context.Context, f FancyMSSQLSettings) (string, bool)
|
||||
}
|
||||
|
||||
// wizardSteps — шагов в мастере: 1 лицензия, 2 база bj, 3 учёт DEPO, 4 наша организация, 5 QUIK МНП,
|
||||
// 6 Валидата, 7 сертификаты, 8 шлюз НРД, 9 проверка.
|
||||
const wizardSteps = 9
|
||||
// 6 Валидата, 7 сертификаты, 8 шлюз НРД, 9 оповещения и резервные копии, 10 проверка готовности.
|
||||
const wizardSteps = 10
|
||||
|
||||
// wizardProgressFile — номер последнего пройденного шага мастера (переход «Далее» на шаг N
|
||||
// означает, что шаги до N пройдены).
|
||||
@@ -998,7 +1001,7 @@ func (h *setupHandlers) saveAlertsSMTP(w http.ResponseWriter, r *http.Request) {
|
||||
Port: atoiDefault(r.FormValue("port"), 587),
|
||||
Security: nonEmpty(r.FormValue("security"), "starttls"),
|
||||
Username: strings.TrimSpace(r.FormValue("username")),
|
||||
Password: r.FormValue("password"),
|
||||
Password: pickStr(r.FormValue("password"), a.SMTP.Password), // пароль в страницу не выводится: пусто — прежний
|
||||
From: strings.TrimSpace(r.FormValue("from")),
|
||||
To: strings.TrimSpace(r.FormValue("to")),
|
||||
LastMsg: a.SMTP.LastMsg,
|
||||
@@ -1686,7 +1689,11 @@ func setupFlash(w http.ResponseWriter, r *http.Request, msg string) {
|
||||
if s := r.FormValue("section"); s != "" && isAnchorName(s) {
|
||||
q.Set("scrollto", s)
|
||||
}
|
||||
http.Redirect(w, r, dest+"?"+q.Encode(), http.StatusSeeOther)
|
||||
sep := "?"
|
||||
if strings.Contains(dest, "?") {
|
||||
sep = "&"
|
||||
}
|
||||
http.Redirect(w, r, dest+sep+q.Encode(), http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// safeReturnPath пропускает только наши же admin-пути ("/admin/..."), без
|
||||
@@ -1701,13 +1708,27 @@ func safeReturnPath(p string) string {
|
||||
if strings.HasPrefix(p, "//") || strings.ContainsAny(p, ":\\\r\n") {
|
||||
return ""
|
||||
}
|
||||
// только путь, без query/fragment
|
||||
if i := strings.IndexAny(p, "?#"); i >= 0 {
|
||||
if i := strings.IndexByte(p, '#'); i >= 0 {
|
||||
p = p[:i]
|
||||
}
|
||||
return p
|
||||
// путь и безопасные параметры (например ?step=4&sub=done в мастере); flash/scrollto — свежие
|
||||
path, query, _ := strings.Cut(p, "?")
|
||||
var keep []string
|
||||
for _, kv := range strings.Split(query, "&") {
|
||||
k, v, ok := strings.Cut(kv, "=")
|
||||
if !ok || k == "flash" || k == "scrollto" || !safeQueryPart.MatchString(k) || !safeQueryPart.MatchString(v) {
|
||||
continue
|
||||
}
|
||||
keep = append(keep, k+"="+v)
|
||||
}
|
||||
if len(keep) > 0 {
|
||||
return path + "?" + strings.Join(keep, "&")
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
var safeQueryPart = regexp.MustCompile(`^[A-Za-z0-9_-]{1,40}$`)
|
||||
|
||||
// isAnchorName пропускает только безопасные id-якоря (буквы/цифры/-/_),
|
||||
// чтобы не дать заголовкам ответа инжектить произвольное.
|
||||
func isAnchorName(s string) bool {
|
||||
|
||||
Reference in New Issue
Block a user