a3abcd5412
Процедуры FTV_M2M_PROCESS_* содержат EXECUTE AS USER=SYSDBA; SQL Server требует этого пользователя при CREATE (FTV_M2M_JOB без EXECUTE AS создаётся, а 4 PROCESS_* — нет). - «Проверить возможности» проверяет наличие пользователя EXECUTE AS (из текста процедур) и блокирует установку с понятным сообщением, если его нет. - Ошибка установки сохраняется в статус (не мелькает флешем) с подсказкой про SYSDBA. - Подсказка добавлена и в мастере при «Подготовить». Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn
499 lines
22 KiB
Go
499 lines
22 KiB
Go
package lkgateway
|
||
|
||
// Шаг 3 мастера настройки (учёт DEPO) — маленький мастер по экранам вместо простыни настроек:
|
||
// описание → подключение → трансферные таблицы → обработка (job) → готово. Каждый экран делает
|
||
// своё действие по «Далее» и сразу проверяет результат; при ошибке экран не меняется, ошибка
|
||
// показывается понятным текстом. Полный раздел «Обмен Fancy» остаётся в «Настройке».
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"net/http"
|
||
"net/url"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
)
|
||
|
||
// depoWizardSubs — экраны шага 3 по порядку.
|
||
// Шаг 3 — два экрана (06.10.2026): подготовка базы одним действием и проверка. Прежние адреса экранов
|
||
// (intro, conn, tables, job) ведут на подготовку.
|
||
var depoWizardSubs = []struct{ Key, Title string }{
|
||
{"setup", "Подключение и подготовка базы"},
|
||
{"done", "Проверка"},
|
||
}
|
||
|
||
func depoWizardSub(v string) string {
|
||
for _, s := range depoWizardSubs {
|
||
if s.Key == v {
|
||
return v
|
||
}
|
||
}
|
||
return "setup"
|
||
}
|
||
|
||
// wizDepoGo — перейти на экран шага 3 с сообщением (ok=false — сообщение об ошибке).
|
||
func wizDepoGo(w http.ResponseWriter, r *http.Request, sub, msg string, ok bool) {
|
||
http.Redirect(w, r, wizDepoURL(sub, msg, ok), http.StatusSeeOther)
|
||
}
|
||
|
||
// wizDepoURL — адрес экрана шага 3 с сообщением.
|
||
func wizDepoURL(sub, msg string, ok bool) string {
|
||
if sub == "intro" || sub == "conn" || sub == "tables" || sub == "job" {
|
||
sub = "setup"
|
||
}
|
||
u := "/admin/wizard?step=3&sub=" + sub
|
||
if msg != "" {
|
||
if ok {
|
||
msg = "✓ " + msg
|
||
} else {
|
||
msg = "✗ " + msg
|
||
}
|
||
u += "&flash=" + url.QueryEscape(msg)
|
||
}
|
||
return u
|
||
}
|
||
|
||
// wizardDepoConn — POST /admin/setup/wizard-depo/conn: сохранить подключение и сразу проверить его. Имя базы
|
||
// DEPO клиент задаёт сам — bj по серверу не ищет, только проверяет, что это база DEPO.
|
||
func (h *setupHandlers) wizardDepoConn(w http.ResponseWriter, r *http.Request) {
|
||
if r.Method != http.MethodPost || !requireDepo(w, r) {
|
||
return
|
||
}
|
||
s := h.fancyFromForm(r)
|
||
manual := strings.TrimSpace(r.FormValue("depo_database"))
|
||
if !validDBName(manual) {
|
||
wizDepoGo(w, r, "conn", "укажите имя базы DEPO — латинские буквы, цифры и _, начинается с буквы", false)
|
||
return
|
||
}
|
||
if s.Server == "" || s.User == "" {
|
||
wizDepoGo(w, r, "conn", "укажите адрес сервера и логин", false)
|
||
return
|
||
}
|
||
if err := h.rc.UpdateFancy(s); err != nil {
|
||
wizDepoGo(w, r, "conn", "не сохранилось: "+err.Error(), false)
|
||
return
|
||
}
|
||
ctx, cancel := context.WithTimeout(depoLoginForce(r.Context()), 45*time.Second)
|
||
defer cancel()
|
||
if _, err := fancyPing(ctx, s, ""); err != nil {
|
||
wizDepoGo(w, r, "conn", "к серверу подключиться не удалось — "+err.Error()+". Проверьте адрес, порт, логин и пароль.", false)
|
||
return
|
||
}
|
||
db := manual
|
||
if err := depoCheckIsDepo(ctx, s, db); err != nil {
|
||
wizDepoGo(w, r, "conn", err.Error(), false)
|
||
return
|
||
}
|
||
if db != s.DepoDatabase {
|
||
s.DepoTablesCheck, s.DepoTablesCheckOK = "", false
|
||
}
|
||
s.DepoDatabase = db
|
||
if err := h.rc.UpdateFancy(s); err != nil {
|
||
wizDepoGo(w, r, "conn", "не сохранилось: "+err.Error(), false)
|
||
return
|
||
}
|
||
wizDepoGo(w, r, "tables", "подключение к "+s.Server+" работает; база DEPO — "+db, true)
|
||
}
|
||
|
||
// wizardDepoTables — POST /admin/setup/wizard-depo/tables: создать таблицы (bj) или проверить созданные
|
||
// администратором по скрипту.
|
||
func (h *setupHandlers) wizardDepoTables(w http.ResponseWriter, r *http.Request) {
|
||
if r.Method != http.MethodPost || !requireDepo(w, r) {
|
||
return
|
||
}
|
||
s := h.rc.Snapshot().Fancy
|
||
mode := r.FormValue("depo_tables_mode")
|
||
if mode != "script" {
|
||
mode = "auto"
|
||
}
|
||
if s.DepoTablesMode != mode {
|
||
s.DepoTablesMode = mode
|
||
_ = h.rc.UpdateFancy(s)
|
||
}
|
||
ctx, cancel := context.WithTimeout(depoLoginForce(r.Context()), 2*time.Minute)
|
||
defer cancel()
|
||
if mode == "auto" && r.FormValue("action") != "check" {
|
||
if _, err := depoTablesCreate(ctx, s, r.FormValue("admin_user"), r.FormValue("admin_password")); err != nil {
|
||
wizDepoGo(w, r, "tables", "таблицы не созданы — "+err.Error()+". Если у логина bj нет прав, введите разово логин администратора SQL.", false)
|
||
return
|
||
}
|
||
}
|
||
msg, ok := depoTablesCheck(ctx, s)
|
||
h.saveDepoTablesCheck(msg, ok)
|
||
if !ok {
|
||
wizDepoGo(w, r, "tables", msg, false)
|
||
return
|
||
}
|
||
if !s.IsPostgres() {
|
||
_ = depoWriteHandbookCtx(depoLoginForce(ctx), h.rc)
|
||
}
|
||
wizDepoGo(w, r, "job", "таблицы обмена на месте: "+msg, true)
|
||
}
|
||
|
||
// wizardDepoJob — POST /admin/setup/wizard-depo/job: выбрать, кто запускает обработку, сделать нужное
|
||
// (для «джобу создаёт bj» — создать) и проверить служебным событием.
|
||
func (h *setupHandlers) wizardDepoJob(w http.ResponseWriter, r *http.Request) {
|
||
if r.Method != http.MethodPost || !requireDepo(w, r) {
|
||
return
|
||
}
|
||
s := h.rc.Snapshot().Fancy
|
||
mode := r.FormValue("depo_job_mode")
|
||
if mode != "bj" && mode != "sql-auto" && mode != "sql-manual" {
|
||
mode = "sql-auto"
|
||
}
|
||
if s.DepoJobMode != mode {
|
||
s.DepoJobCheck, s.DepoJobCheckOK = "", false
|
||
}
|
||
s.DepoJobMode, s.DepoJobRun = mode, mode == "bj"
|
||
if err := h.rc.UpdateFancy(s); err != nil {
|
||
wizDepoGo(w, r, "job", "не сохранилось: "+err.Error(), false)
|
||
return
|
||
}
|
||
ctx, cancel := context.WithTimeout(depoLoginForce(r.Context()), 90*time.Second)
|
||
defer cancel()
|
||
jobNote := ""
|
||
if mode == "sql-auto" && r.FormValue("action") != "check" {
|
||
if err := depoJobCreateAgent(ctx, h.rc, r.FormValue("admin_user"), r.FormValue("admin_password")); err != nil {
|
||
if !strings.Contains(err.Error(), "уже существует") {
|
||
wizDepoGo(w, r, "job", "джоба не создана — "+err.Error(), false)
|
||
return
|
||
}
|
||
// джобы хранятся в msdb: при восстановлении базы DEPO из копии джоба остаётся — не пересоздаём
|
||
jobNote = "джоба " + depoJobName(h.rc.Snapshot().Fancy) + " на сервере уже есть (bj её не пересоздавал). "
|
||
} else {
|
||
jobNote = "джоба " + depoJobName(h.rc.Snapshot().Fancy) + " создана. "
|
||
}
|
||
}
|
||
// проверяем запуск джобы (не процедуры): только что созданная джоба запускается в течение интервала — ждём
|
||
au, ap := r.FormValue("admin_user"), r.FormValue("admin_password")
|
||
msg, ok := depoJobRunnerCheck(ctx, h.rc, au, ap)
|
||
for i := 0; !ok && i < 6 && strings.Contains(msg, "ни разу не запускалась"); i++ {
|
||
time.Sleep(5 * time.Second)
|
||
msg, ok = depoJobRunnerCheck(ctx, h.rc, au, ap)
|
||
}
|
||
msg = jobNote + msg
|
||
s = h.rc.Snapshot().Fancy
|
||
s.DepoJobCheck, s.DepoJobCheckOK, s.DepoJobCheckAt = msg, ok, time.Now()
|
||
_ = h.rc.UpdateFancy(s)
|
||
if !ok {
|
||
wizDepoGo(w, r, "job", "обработка пока не работает — "+msg, false)
|
||
return
|
||
}
|
||
wizDepoGo(w, r, "done", "обработка работает: "+msg, true)
|
||
}
|
||
|
||
// wizardDepoRecheck — POST /admin/setup/wizard-depo/recheck: перепроверить всё на экране «Готово».
|
||
func (h *setupHandlers) wizardDepoRecheck(w http.ResponseWriter, r *http.Request) {
|
||
if r.Method != http.MethodPost || !requireDepo(w, r) {
|
||
return
|
||
}
|
||
s := h.rc.Snapshot().Fancy
|
||
ctx, cancel := context.WithTimeout(depoLoginForce(r.Context()), 90*time.Second)
|
||
defer cancel()
|
||
if _, err := fancyPing(ctx, s, s.DepoDB()); err != nil {
|
||
wizDepoGo(w, r, "setup", "подключение не работает — "+err.Error(), false)
|
||
return
|
||
}
|
||
msg, ok := depoTablesCheck(ctx, s)
|
||
h.saveDepoTablesCheck(msg, ok)
|
||
if !ok {
|
||
wizDepoGo(w, r, "setup", msg, false)
|
||
return
|
||
}
|
||
jmsg, jok := depoJobCheck(ctx, h.rc)
|
||
pmsg, pok := depoProcCheck(ctx, h.rc) // обработка событий — отдельно, шаг не держит
|
||
s = h.rc.Snapshot().Fancy
|
||
s.DepoJobCheck, s.DepoJobCheckOK, s.DepoJobCheckAt = jmsg, jok, time.Now()
|
||
s.DepoProcCheck, s.DepoProcCheckOK, s.DepoProcCheckAt = pmsg, pok, time.Now()
|
||
_ = h.rc.UpdateFancy(s)
|
||
if !jok {
|
||
wizDepoGo(w, r, "done", "обработка не запускается — "+jmsg, false)
|
||
return
|
||
}
|
||
wizDepoGo(w, r, "done", "всё перепроверено", true)
|
||
}
|
||
|
||
// depoSetupForm — сервер DEPO, имя базы (обязательно) и способ обработки из формы экрана «Подготовка базы»
|
||
// (логин bj не трогается; пустой адрес сервера не ошибка — скрипту он не нужен, подключению проверяют вызывающие).
|
||
func (h *setupHandlers) depoSetupForm(r *http.Request) (FancyMSSQLSettings, string, bool, string) {
|
||
s := h.rc.Snapshot().Fancy
|
||
dbType := "mssql"
|
||
if r.FormValue("db_type") == "postgres" {
|
||
dbType = "postgres"
|
||
}
|
||
port, _ := strconv.Atoi(strings.TrimSpace(r.FormValue("port")))
|
||
if port == 0 {
|
||
port = map[string]int{"mssql": 1433, "postgres": 5432}[dbType]
|
||
}
|
||
server := strings.TrimSpace(r.FormValue("server"))
|
||
if server != s.Server || dbType != s.DBType {
|
||
s.DepoTablesCheck, s.DepoTablesCheckOK, s.DepoJobCheck, s.DepoJobCheckOK = "", false, "", false
|
||
s.DepoDatabase = ""
|
||
}
|
||
s.DBType, s.Port, s.Server = dbType, port, server
|
||
if s.Encrypt == "" {
|
||
s.Encrypt = "disable"
|
||
}
|
||
agent := r.FormValue("depo_job") != "bj" && dbType != "postgres"
|
||
s.DepoJobMode, s.DepoJobRun = "bj", !agent
|
||
if agent {
|
||
s.DepoJobMode = "sql-auto"
|
||
}
|
||
db := strings.TrimSpace(r.FormValue("depo_database"))
|
||
if db == "" { // bj по серверу базу не ищет: с какой базой работать, клиент говорит сам
|
||
return s, "", agent, "укажите имя базы DEPO — как она называется на сервере"
|
||
}
|
||
if !validDBName(db) {
|
||
return s, "", agent, "имя базы DEPO — только латинские буквы, цифры и _, начинается с буквы"
|
||
}
|
||
if agent { // задание обработки — своё у каждой базы: по умолчанию bj2depo_<база>
|
||
jn := strings.TrimSpace(r.FormValue("depo_job_name"))
|
||
if jn == "" {
|
||
jn = depoJobNameFor(db)
|
||
}
|
||
if !depoJobNameRe.MatchString(jn) {
|
||
return s, db, agent, "имя задания — латинские буквы, цифры, точка, дефис и _"
|
||
}
|
||
s.DepoJobName = jn
|
||
}
|
||
return s, db, agent, ""
|
||
}
|
||
|
||
// depoEnsureLogin — логин bj и его пароль для скрипта (пароль придумывает bj и запоминает: после
|
||
// выполнения скрипта bj входит с ним; повторная выдача скрипта — с тем же паролем).
|
||
func depoEnsureLogin(s *FancyMSSQLSettings) {
|
||
login := depoSetupLogin(*s)
|
||
if s.User != login || s.Password == "" {
|
||
s.User, s.Password = login, depoGenPassword()
|
||
}
|
||
}
|
||
|
||
// depoBjCreds — логин и пароль bj с экрана подготовки. Видны в обоих способах — «логин администратора» и
|
||
// «скрипт»: на общем сервере SQL логин bj_user может быть уже занят другим bj (подготовка сменила бы ему
|
||
// пароль) — тогда вписывают другое имя. Пустой пароль — bj придумает сам. Возвращает текст ошибки.
|
||
func depoBjCreds(r *http.Request, s *FancyMSSQLSettings) string {
|
||
if u := strings.TrimSpace(r.FormValue("bj_user")); u != "" {
|
||
if !depoLoginRe.MatchString(u) {
|
||
return "логин bj — латинские буквы, цифры, точка, дефис и _"
|
||
}
|
||
if u != s.User {
|
||
s.User, s.Password = u, ""
|
||
}
|
||
}
|
||
if p := r.FormValue("bj_password"); p != "" {
|
||
if len(p) < 8 {
|
||
return "пароль bj — не короче 8 знаков (SQL Server не примет короче)"
|
||
}
|
||
s.Password = p
|
||
}
|
||
return ""
|
||
}
|
||
|
||
// wizardDepoScript — POST /admin/setup/wizard-depo/script.sql: один скрипт подготовки базы файлом
|
||
// (view=1 — текстом для показа на странице: экран пересобирает его при каждой правке ответов).
|
||
func (h *setupHandlers) wizardDepoScript(w http.ResponseWriter, r *http.Request) {
|
||
if r.Method != http.MethodPost || !requireDepo(w, r) {
|
||
return
|
||
}
|
||
view := r.FormValue("view") != ""
|
||
fail := func(msg string) {
|
||
if view {
|
||
http.Error(w, msg, http.StatusBadRequest)
|
||
return
|
||
}
|
||
wizDepoGo(w, r, "setup", msg, false)
|
||
}
|
||
s, db, agent, bad := h.depoSetupForm(r)
|
||
if bad == "" {
|
||
bad = depoBjCreds(r, &s)
|
||
}
|
||
if bad != "" {
|
||
fail(bad)
|
||
return
|
||
}
|
||
depoEnsureLogin(&s)
|
||
s.DepoTablesMode = "script"
|
||
// показ на странице (экран пересобирает скрипт на ходу) ничего не сохраняет; файл — уходит администратору:
|
||
// запомнить логин и пароль из него, но только черновиком — в работу они пойдут после проверки входа
|
||
if !view && s.Server != "" {
|
||
if err := h.rc.UpdateFancy(depoDraft(s, h.rc.Snapshot().Fancy)); err != nil {
|
||
fail("не сохранилось: " + err.Error())
|
||
return
|
||
}
|
||
}
|
||
body := depoSetupScript(s, db, s.User, s.Password, agent)
|
||
if !s.IsPostgres() { // процедуры обработки bj2depo — тем же файлом
|
||
body += "\nGO\n" + depoProcsScriptFor(s, db, s.User)
|
||
}
|
||
w.Header().Set("Cache-Control", "no-store")
|
||
if view {
|
||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||
_, _ = w.Write([]byte(body))
|
||
return
|
||
}
|
||
if !s.IsPostgres() {
|
||
body = "\uFEFF" + body // SSMS без BOM читает кириллицу в комментариях как ANSI
|
||
}
|
||
w.Header().Set("Content-Type", "application/sql; charset=utf-8")
|
||
w.Header().Set("Content-Disposition", `attachment; filename="`+depoSetupFileName(s)+`"`)
|
||
_, _ = w.Write([]byte(body))
|
||
}
|
||
|
||
// depoDraft — настройки экрана с логином и паролем bj черновиком: рабочими остаются прежние (saved), новые —
|
||
// в DepoNewUser/DepoNewPass до успешного входа.
|
||
func depoDraft(s, saved FancyMSSQLSettings) FancyMSSQLSettings {
|
||
d := s
|
||
d.User, d.Password = saved.User, saved.Password
|
||
d.DepoNewUser, d.DepoNewPass = s.User, s.Password
|
||
return d
|
||
}
|
||
|
||
// wizardDepoSetup — POST /admin/setup/wizard-depo/setup: подготовить базу (mode=auto — bj сам логином
|
||
// администратора), проверить после скрипта (mode=script) или с готовым логином (mode=existing).
|
||
// stream=1 (экран с JavaScript) — ход по шагам строками JSON, чтобы после «Подготовить» было видно, что
|
||
// происходит: подключение, проверка базы, скрипт, вход bj, проверки; последняя строка — итог и куда перейти.
|
||
func (h *setupHandlers) wizardDepoSetup(w http.ResponseWriter, r *http.Request) {
|
||
if r.Method != http.MethodPost || !requireDepo(w, r) {
|
||
return
|
||
}
|
||
ctx, cancel := context.WithTimeout(depoLoginForce(r.Context()), 3*time.Minute)
|
||
defer cancel()
|
||
if r.FormValue("stream") == "" {
|
||
sub, msg, ok := h.depoSetupDo(ctx, r, func(string, string, string) {})
|
||
wizDepoGo(w, r, sub, msg, ok)
|
||
return
|
||
}
|
||
w.Header().Set("Content-Type", "application/x-ndjson; charset=utf-8")
|
||
w.Header().Set("Cache-Control", "no-store")
|
||
w.Header().Set("X-Accel-Buffering", "no")
|
||
fl, _ := w.(http.Flusher)
|
||
enc := json.NewEncoder(w)
|
||
send := func(v any) {
|
||
_ = enc.Encode(v)
|
||
if fl != nil {
|
||
fl.Flush()
|
||
}
|
||
}
|
||
sub, msg, ok := h.depoSetupDo(ctx, r, func(key, state, text string) {
|
||
send(map[string]string{"step": key, "state": state, "text": text})
|
||
})
|
||
send(map[string]any{"done": true, "ok": ok, "msg": msg, "go": wizDepoURL(sub, msg, ok)})
|
||
}
|
||
|
||
// depoSetupDo — подготовка или проверка базы DEPO по форме экрана; итог — куда перейти (sub) и сообщение.
|
||
func (h *setupHandlers) depoSetupDo(ctx context.Context, r *http.Request, step depoStep) (string, string, bool) {
|
||
fail := func(msg string) (string, string, bool) { return "setup", msg, false }
|
||
saved := h.rc.Snapshot().Fancy
|
||
s, db, agent, bad := h.depoSetupForm(r)
|
||
if bad == "" && s.Server == "" {
|
||
bad = "укажите адрес сервера DEPO"
|
||
}
|
||
if bad != "" {
|
||
return fail(bad)
|
||
}
|
||
mode, note := r.FormValue("mode"), ""
|
||
switch mode {
|
||
case "auto":
|
||
au := strings.TrimSpace(r.FormValue("admin_user"))
|
||
var miss []string
|
||
if au == "" {
|
||
miss = append(miss, "логин администратора SQL")
|
||
}
|
||
if r.FormValue("admin_password") == "" {
|
||
miss = append(miss, "пароль администратора SQL")
|
||
}
|
||
if len(miss) > 0 {
|
||
return fail("не заполнено: " + strings.Join(miss, ", ") + " (они используются один раз и не сохраняются)")
|
||
}
|
||
if bad := depoBjCreds(r, &s); bad != "" {
|
||
return fail(bad)
|
||
}
|
||
depoEnsureLogin(&s)
|
||
if err := depoSetupRun(ctx, s, db, au, r.FormValue("admin_password"), s.User, s.Password, agent, step); err != nil {
|
||
return fail("база не подготовлена — " + err.Error())
|
||
}
|
||
if !s.IsPostgres() { // процедуры обработки bj2depo (CREATE OR ALTER, с учётом правки под клиента)
|
||
step("procs", "run", "Устанавливаю процедуры обработки bj2depo")
|
||
adm := s
|
||
adm.User, adm.Password = au, r.FormValue("admin_password")
|
||
if err := depoProcsInstallInto(ctx, adm, s, db, s.User); err != nil {
|
||
return fail("процедуры обработки не установлены — " + err.Error() + depoProcErrHint(err.Error()))
|
||
}
|
||
step("procs", "ok", "")
|
||
}
|
||
s.DepoTablesMode = "auto"
|
||
note = "база " + db + " подготовлена: логин " + s.User + ", таблицы обмена, права"
|
||
if agent {
|
||
note += ", задание " + depoJobName(s)
|
||
}
|
||
note += ". "
|
||
case "existing":
|
||
u := strings.TrimSpace(r.FormValue("user"))
|
||
if u == "" {
|
||
return fail("введите логин bj и пароль")
|
||
}
|
||
if u != s.User || r.FormValue("password") != "" {
|
||
s.User, s.Password = u, r.FormValue("password")
|
||
}
|
||
default: // script — администратор выполнил скрипт: логин и пароль bj — с экрана (они же в настройках)
|
||
if bad := depoBjCreds(r, &s); bad != "" {
|
||
return fail(bad)
|
||
}
|
||
s.DepoTablesMode = "script"
|
||
if s.User == "" || s.Password == "" {
|
||
return fail("сначала скачайте скрипт и передайте администратору SQL")
|
||
}
|
||
}
|
||
s.DepoDatabase = db
|
||
if err := h.rc.UpdateFancy(depoDraft(s, saved)); err != nil {
|
||
return fail("не сохранилось: " + err.Error())
|
||
}
|
||
step("login", "run", "Вхожу в DEPO логином bj «"+s.User+"»")
|
||
if _, err := fancyPing(ctx, s, ""); err != nil {
|
||
return fail(note + "войти логином " + s.User + " не удалось — " + quikErrorDetail(err) + ". Выполнен ли скрипт на сервере " + s.Server + "?")
|
||
}
|
||
s.DepoNewUser, s.DepoNewPass = "", "" // вход проверен — логин и пароль в работу
|
||
if err := h.rc.UpdateFancy(s); err != nil {
|
||
return fail("не сохранилось: " + err.Error())
|
||
}
|
||
step("login", "ok", "bj сохранил логин «"+s.User+"» и пароль — дальше работает под ними")
|
||
if mode != "auto" { // при подготовке база уже проверена логином администратора
|
||
step("isdepo", "run", "Проверяю, что «"+db+"» — база учёта DEPO")
|
||
if err := depoCheckIsDepo(ctx, s, db); err != nil { // имя базы задал клиент — та ли это база
|
||
return fail(note + err.Error())
|
||
}
|
||
step("isdepo", "ok", "")
|
||
}
|
||
step("tables", "run", "Проверяю таблицы обмена FTV_M2M и права логина bj")
|
||
msg, ok := depoTablesCheck(ctx, s)
|
||
h.saveDepoTablesCheck(msg, ok)
|
||
if !ok {
|
||
return fail(note + msg)
|
||
}
|
||
if !s.IsPostgres() {
|
||
_ = depoWriteHandbookCtx(depoLoginForce(ctx), h.rc)
|
||
}
|
||
step("tables", "ok", "")
|
||
// запуск обработки: только что созданное задание запускается в течение интервала — подождать
|
||
if agent {
|
||
step("job", "run", "Проверяю задание SQL Server Agent «"+depoJobName(s)+"» — до 30 секунд")
|
||
} else {
|
||
step("job", "run", "Проверяю, что bj может запускать обработку обмена")
|
||
}
|
||
jmsg, jok := depoJobRunnerCheck(ctx, h.rc, "", "")
|
||
for i := 0; !jok && i < 6 && strings.Contains(jmsg, "ни разу не запускалась"); i++ {
|
||
time.Sleep(5 * time.Second)
|
||
jmsg, jok = depoJobRunnerCheck(ctx, h.rc, "", "")
|
||
}
|
||
step("job", map[bool]string{true: "ok", false: "warn"}[jok], jmsg)
|
||
step("procs", "run", "Проверяю процедуры обработки обмена")
|
||
pmsg, pok := depoProcCheck(ctx, h.rc) // процедуры обработки ставятся отдельно — шаг не держат
|
||
step("procs", map[bool]string{true: "ok", false: "warn"}[pok], pmsg)
|
||
cur := h.rc.Snapshot().Fancy
|
||
cur.DepoJobCheck, cur.DepoJobCheckOK, cur.DepoJobCheckAt = jmsg, jok, time.Now()
|
||
cur.DepoProcCheck, cur.DepoProcCheckOK, cur.DepoProcCheckAt = pmsg, pok, time.Now()
|
||
_ = h.rc.UpdateFancy(cur)
|
||
return "done", note + "подключение и таблицы обмена проверены", true
|
||
}
|