From 35f70a1b4928b24ca2dd8854565e42c238e20ce6 Mon Sep 17 00:00:00 2001 From: zuevav Date: Tue, 6 Oct 2026 15:18:15 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A3=D1=87=D1=91=D1=82=20DEPO:=20=D0=B8=D0=BC?= =?UTF-8?q?=D1=8F=20=D0=B1=D0=B0=D0=B7=D1=8B=20=D0=BE=D0=B1=D1=8F=D0=B7?= =?UTF-8?q?=D0=B0=D1=82=D0=B5=D0=BB=D1=8C=D0=BD=D0=BE,=20bj=20=D0=B1=D0=B0?= =?UTF-8?q?=D0=B7=D1=83=20=D0=BF=D0=BE=20=D1=81=D0=B5=D1=80=D0=B2=D0=B5?= =?UTF-8?q?=D1=80=D1=83=20=D0=BD=D0=B5=20=D0=B8=D1=89=D0=B5=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Имя базы DEPO задаёт клиент — скрипт и подготовка логином администратора работают только с ней. Автопоиск по серверу убран (экран, скрипт, подготовка, проверка подключения, старый экран conn), выбор из нескольких найденных баз — тоже. Вместо поиска — защита от опечатки: скрипт (SQL Server и PostgreSQL), подготовка и проверка подключения сначала убеждаются, что в названной базе есть учётные таблицы OD_FACES и OD_RESTS, иначе останавливаются, ничего не меняя. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi --- internal/lkgateway/depo_isdepo.go | 40 ++++++++ internal/lkgateway/depo_setup.go | 65 ++++++------ internal/lkgateway/depodiscover.go | 98 ------------------- internal/lkgateway/letters.go | 2 +- internal/lkgateway/onboarding_test.go | 22 +++-- internal/lkgateway/setup.go | 2 - .../lkgateway/web/templates/admin_wizard.html | 22 ++--- internal/lkgateway/wizard_depo.go | 86 ++++------------ 8 files changed, 118 insertions(+), 219 deletions(-) create mode 100644 internal/lkgateway/depo_isdepo.go delete mode 100644 internal/lkgateway/depodiscover.go diff --git a/internal/lkgateway/depo_isdepo.go b/internal/lkgateway/depo_isdepo.go new file mode 100644 index 0000000..b6602a8 --- /dev/null +++ b/internal/lkgateway/depo_isdepo.go @@ -0,0 +1,40 @@ +package lkgateway + +// Проверка, что указанная база — учёт DEPO (06.10.2026). Имя базы клиент задаёт сам: bj по серверу базы не +// ищет. Чтобы опечатка в имени не привела bj в чужую базу, перед подготовкой и при проверке подключения +// bj смотрит, есть ли в ней учётные таблицы Fancy DEPO — лица OD_FACES и остатки OD_RESTS. + +import ( + "context" + "fmt" + "strings" + "time" +) + +// depoMarkerTables — по каким таблицам база опознаётся как DEPO (Fancy). +var depoMarkerTables = []string{"OD_FACES", "OD_RESTS"} + +// depoCheckIsDepo — в базе db есть учётные таблицы DEPO, видимые логину s.User. +func depoCheckIsDepo(ctx context.Context, s FancyMSSQLSettings, db string) error { + cctx, cancel := context.WithTimeout(ctx, 30*time.Second) + defer cancel() + conn, err := fancyOpen(s, db) + if err != nil { + return err + } + defer conn.Close() + if err := conn.PingContext(cctx); err != nil { + return fmt.Errorf("база %s недоступна логину %s: %s", db, s.User, quikErrorDetail(err)) + } + var miss []string + for _, t := range depoMarkerTables { + if !depoTableVisible(cctx, conn, s.IsPostgres(), t) { + miss = append(miss, t) + } + } + if len(miss) > 0 { + return fmt.Errorf("в базе %s нет учётных таблиц DEPO (%s) — проверьте имя базы: это не база учёта DEPO или у логина %s нет к ней доступа", + db, strings.Join(miss, ", "), s.User) + } + return nil +} diff --git a/internal/lkgateway/depo_setup.go b/internal/lkgateway/depo_setup.go index af61df3..7180a9c 100644 --- a/internal/lkgateway/depo_setup.go +++ b/internal/lkgateway/depo_setup.go @@ -2,8 +2,8 @@ package lkgateway // Подготовка базы учёта DEPO одним действием (06.10.2026). Раньше это были три экрана мастера — логин bj, // таблицы обмена, задание SQL Agent — и три скрипта. Теперь один скрипт делает всё: -// - находит базу DEPO на сервере сам (по учётным таблицам OD_FACES и OD_RESTS) — имя базы клиенту знать -// не нужно (SQL Server; для PostgreSQL базу назвать придётся: psql не умеет искать по чужим базам); +// - работает с базой, которую назвал клиент (сам по серверу не ищет), и сначала проверяет, что в ней есть +// учётные таблицы DEPO OD_FACES и OD_RESTS — опечатка в имени не уведёт в чужую базу; // - создаёт логин bj (пароль придумывает bj и запоминает) или обновляет его пароль; // - создаёт таблицы обмена FTV_M2M* и выдаёт логину bj права: чтение учёта, запись в таблицы обмена; // - по галочке «обработку запускает SQL Server Agent» — создаёт задание bj2depo, иначе обработку @@ -50,8 +50,8 @@ func depoGenPassword() string { // sqlStr — строковый литерал T-SQL / PostgreSQL (одинарная кавычка удваивается). func sqlStr(s string) string { return strings.ReplaceAll(s, "'", "''") } -// depoSetupScript — один скрипт подготовки базы DEPO. db — имя базы (пусто — найти на сервере; только SQL -// Server), agentJob — создать задание SQL Server Agent (иначе обработку вызывает bj). +// depoSetupScript — один скрипт подготовки базы DEPO. db — имя базы DEPO (задаёт клиент), agentJob — создать +// задание SQL Server Agent (иначе обработку вызывает bj). func depoSetupScript(f FancyMSSQLSettings, db, login, pass string, agentJob bool) string { if f.IsPostgres() { return depoSetupScriptPG(f, db, login, pass) @@ -65,13 +65,13 @@ func depoSetupScript(f FancyMSSQLSettings, db, login, pass string, agentJob bool SET NOCOUNT ON; -- ===== Проверьте перед запуском ========================================== --- База DEPO: пусто — скрипт найдёт её сам по таблицам OD_FACES и OD_RESTS. +-- База DEPO — точное имя базы учёта на этом сервере. DECLARE @db sysname = N'%[3]s'; -- Пароль логина bj (%[1]s). Меняете — впишите тот же пароль в bj, шаг «Учёт DEPO». DECLARE @pass nvarchar(128) = N'%[4]s'; -- ========================================================================= --- Что делает: находит базу DEPO, создаёт логин bj и таблицы обмена FTV_M2M*, выдаёт права +-- Что делает: проверяет, что @db — база DEPO, создаёт логин bj и таблицы обмена FTV_M2M*, выдаёт права -- (чтение учёта, запись только в таблицы обмена). Обработка обмена: %[2]s. -- Учётные данные DEPO не меняет. Запускать в SSMS или sqlcmd под логином с правами sysadmin. -- Можно запускать повторно. В скрипте пароль логина bj — после выполнения файл удалите. @@ -81,18 +81,19 @@ BEGIN RAISERROR(N'Впишите пароль логина bj в @pass в начале скрипта и запустите снова.', 16, 1); RETURN; END -DECLARE @found int; IF @db = N'' BEGIN - SELECT @found = COUNT(*) FROM sys.databases WHERE state = 0 AND database_id > 4 - AND OBJECT_ID(QUOTENAME(name) + N'.dbo.OD_FACES') IS NOT NULL AND OBJECT_ID(QUOTENAME(name) + N'.dbo.OD_RESTS') IS NOT NULL; - IF @found = 1 - SELECT @db = name FROM sys.databases WHERE state = 0 AND database_id > 4 - AND OBJECT_ID(QUOTENAME(name) + N'.dbo.OD_FACES') IS NOT NULL AND OBJECT_ID(QUOTENAME(name) + N'.dbo.OD_RESTS') IS NOT NULL; + RAISERROR(N'Впишите имя базы DEPO в @db в начале скрипта и запустите снова.', 16, 1); + RETURN; END -IF @db = N'' OR DB_ID(@db) IS NULL +IF DB_ID(@db) IS NULL BEGIN - RAISERROR(N'База DEPO не найдена или на сервере их несколько. Впишите имя базы DEPO в @db в начале скрипта и запустите снова.', 16, 1); + RAISERROR(N'Базы %%s на этом сервере нет. Проверьте имя в @db в начале скрипта.', 16, 1, @db); + RETURN; +END +IF OBJECT_ID(QUOTENAME(@db) + N'.dbo.OD_FACES', N'U') IS NULL OR OBJECT_ID(QUOTENAME(@db) + N'.dbo.OD_RESTS', N'U') IS NULL +BEGIN + RAISERROR(N'В базе %%s нет учётных таблиц DEPO (OD_FACES, OD_RESTS) — это не база DEPO. Проверьте имя в @db.', 16, 1, @db); RETURN; END PRINT N'База DEPO: ' + @db; @@ -162,6 +163,12 @@ END $$; GRANT CONNECT ON DATABASE "%[1]s" TO "%[2]s"; \c "%[1]s" +-- это база DEPO? (учётные таблицы OD_FACES и OD_RESTS) — иначе остановиться, ничего не создавая +DO $$ BEGIN + IF to_regclass('od_faces') IS NULL OR to_regclass('od_rests') IS NULL THEN + RAISE EXCEPTION 'В базе %% нет учётных таблиц DEPO (OD_FACES, OD_RESTS) — проверьте имя базы', current_database(); + END IF; +END $$; `, db, login, sqlStr(pass)) for _, s := range depoTablesDDL(true) { b.WriteString(s + ";\n") @@ -182,37 +189,31 @@ func depoSetupFileName(f FancyMSSQLSettings) string { return "bj-depo-setup-mssql.sql" } -// depoSetupRun — «bj сделает сам»: тот же скрипт выполняется логином администратора (не сохраняется). -// Возвращает найденную базу DEPO. -func depoSetupRun(ctx context.Context, f FancyMSSQLSettings, db, adminUser, adminPass, login, pass string, agentJob bool) (string, error) { +// depoSetupRun — «bj сделает сам»: тот же скрипт выполняется логином администратора (не сохраняется) в базе +// db, которую назвал клиент; сначала — проверка, что это база DEPO. +func depoSetupRun(ctx context.Context, f FancyMSSQLSettings, db, adminUser, adminPass, login, pass string, agentJob bool) error { adm := f adm.User, adm.Password = strings.TrimSpace(adminUser), adminPass - if db == "" { - found, err := depoDiscover(ctx, adm) - switch { - case err != nil: - return "", fmt.Errorf("список баз сервера логином %s: %s", adm.User, quikErrorDetail(err)) - case len(found) == 0: - return "", fmt.Errorf("на сервере нет базы учёта DEPO (с таблицами %s), доступной логину %s", strings.Join(depoMarkerTables, " и "), adm.User) - case len(found) > 1: - return "", fmt.Errorf("на сервере несколько баз DEPO (%s) — укажите нужную в поле «База DEPO»", strings.Join(found, ", ")) - } - db = found[0] + if !validDBName(db) { + return fmt.Errorf("укажите имя базы DEPO") + } + if err := depoCheckIsDepo(ctx, adm, db); err != nil { + return err } if f.IsPostgres() { - return db, depoSetupRunPG(ctx, adm, db, login, pass) + return depoSetupRunPG(ctx, adm, db, login, pass) } conn, err := fancyOpen(adm, "") if err != nil { - return db, err + return err } defer conn.Close() cctx, cancel := context.WithTimeout(ctx, 2*time.Minute) defer cancel() if _, err := conn.ExecContext(cctx, depoSetupScript(f, db, login, pass, agentJob)); err != nil { - return db, fmt.Errorf("подготовка базы %s логином %s: %s%s", db, adm.User, quikErrorDetail(err), depoTablesRightsHint(f, adm.User, err)) + return fmt.Errorf("подготовка базы %s логином %s: %s%s", db, adm.User, quikErrorDetail(err), depoTablesRightsHint(f, adm.User, err)) } - return db, nil + return nil } func depoSetupRunPG(ctx context.Context, adm FancyMSSQLSettings, db, login, pass string) error { diff --git a/internal/lkgateway/depodiscover.go b/internal/lkgateway/depodiscover.go deleted file mode 100644 index 3521e0a..0000000 --- a/internal/lkgateway/depodiscover.go +++ /dev/null @@ -1,98 +0,0 @@ -package lkgateway - -// Поиск базы DEPO на сервере (05.10.2026). Клиент не знает и не должен знать, как называется база учёта у -// него на сервере: на шаге «Подключение» он вводит только адрес, логин и пароль, а bj сам находит базы с -// учётными таблицами Fancy DEPO (лица OD_FACES и остатки OD_RESTS), к которым у этого логина есть доступ. -// Одна база — выбирается сразу, несколько — мастер предлагает выбрать, ни одной — понятная ошибка. - -import ( - "context" - "fmt" - "strings" - "time" -) - -// depoMarkerTables — по каким таблицам база опознаётся как DEPO (Fancy). -var depoMarkerTables = []string{"OD_FACES", "OD_RESTS"} - -// depoDiscoverMax — сколько баз сервера просматривать (на сервере могут быть десятки баз). -const depoDiscoverMax = 60 - -// depoDiscover — базы DEPO на сервере, доступные этому логину. -func depoDiscover(ctx context.Context, s FancyMSSQLSettings) ([]string, error) { - cctx, cancel := context.WithTimeout(ctx, 30*time.Second) - defer cancel() - if s.IsPostgres() { - return depoDiscoverPG(cctx, s) - } - conn, err := fancyOpen(s, "") - if err != nil { - return nil, err - } - defer conn.Close() - rows, err := conn.QueryContext(cctx, `SELECT name FROM sys.databases - WHERE state = 0 AND database_id > 4 AND HAS_DBACCESS(name) = 1 ORDER BY name`) - if err != nil { - return nil, fmt.Errorf("список баз сервера: %w", err) - } - var names []string - for rows.Next() { - var n string - if rows.Scan(&n) == nil && validDBName(n) && len(names) < depoDiscoverMax { - names = append(names, n) - } - } - rows.Close() - var found []string - for _, n := range names { - // имя проверено validDBName — подставлять в [ ] безопасно - q := "SELECT CASE WHEN " + strings.Join(func() []string { - var c []string - for _, t := range depoMarkerTables { - c = append(c, "OBJECT_ID(N'["+n+"].dbo."+t+"') IS NOT NULL") - } - return c - }(), " AND ") + " THEN 1 ELSE 0 END" - var ok int - if conn.QueryRowContext(cctx, q).Scan(&ok) == nil && ok == 1 { - found = append(found, n) - } - } - return found, nil -} - -func depoDiscoverPG(ctx context.Context, s FancyMSSQLSettings) ([]string, error) { - conn, err := fancyOpen(s, "") - if err != nil { - return nil, err - } - defer conn.Close() - rows, err := conn.QueryContext(ctx, `SELECT datname FROM pg_database - WHERE datallowconn AND NOT datistemplate AND has_database_privilege(datname, 'CONNECT') ORDER BY datname`) - if err != nil { - return nil, fmt.Errorf("список баз сервера: %w", err) - } - var names []string - for rows.Next() { - var n string - if rows.Scan(&n) == nil && validDBName(n) && n != "postgres" && len(names) < depoDiscoverMax { - names = append(names, n) - } - } - rows.Close() - var found []string - for _, n := range names { - db, err := fancyOpen(s, n) - if err != nil { - continue - } - var cnt int - err = db.QueryRowContext(ctx, `SELECT count(DISTINCT lower(table_name)) FROM information_schema.tables - WHERE lower(table_name) = ANY($1::text[])`, "{"+strings.ToLower(strings.Join(depoMarkerTables, ","))+"}").Scan(&cnt) - db.Close() - if err == nil && cnt == len(depoMarkerTables) { - found = append(found, n) - } - } - return found, nil -} diff --git a/internal/lkgateway/letters.go b/internal/lkgateway/letters.go index 3f3e544..7cfdc5a 100644 --- a/internal/lkgateway/letters.go +++ b/internal/lkgateway/letters.go @@ -83,7 +83,7 @@ func letterFor(s Settings, key string) (Letter, bool) { } return Letter{Key: key, Title: "Письмо администратору базы DEPO", To: "Администратор СУБД учёта DEPO", Subject: "Bridge&Join: подготовка базы DEPO на сервере " + orBracket(f.Server, "сервер DEPO"), - Body: fmt.Sprintf("Коллеги, добрый день!\n\nДля подключения системы Bridge&Join к учёту DEPO нужно на сервере %s выполнить один скрипт %s %s (файл прилагается — он скачивается в мастере настройки bj). Скрипт:\n— в начале — имя базы DEPO (пусто — скрипт найдёт её сам по учётным таблицам) и пароль логина bj: их можно поправить;\n— создаёт логин bj: права — чтение учётных таблиц DEPO и запись только в таблицы обмена FTV_M2M*;\n— создаёт таблицы обмена FTV_M2M*; учётные данные он не меняет;\n%s\nСкрипт можно запускать повторно. В нём пароль логина bj — после выполнения файл удалите.\n\nТакже нужно разрешить подключение к серверу базы с сервера Bridge&Join (адрес %s). Процедуры обработки обмена FTV_M2M_* поставляются отдельно.%s", + Body: fmt.Sprintf("Коллеги, добрый день!\n\nДля подключения системы Bridge&Join к учёту DEPO нужно на сервере %s выполнить один скрипт %s %s (файл прилагается — он скачивается в мастере настройки bj). Скрипт:\n— в начале — имя базы DEPO и пароль логина bj: проверьте их; скрипт сначала убедится, что это база DEPO (есть учётные таблицы OD_FACES и OD_RESTS), иначе остановится, ничего не меняя;\n— создаёт логин bj: права — чтение учётных таблиц DEPO и запись только в таблицы обмена FTV_M2M*;\n— создаёт таблицы обмена FTV_M2M*; учётные данные он не меняет;\n%s\nСкрипт можно запускать повторно. В нём пароль логина bj — после выполнения файл удалите.\n\nТакже нужно разрешить подключение к серверу базы с сервера Bridge&Join (адрес %s). Процедуры обработки обмена FTV_M2M_* поставляются отдельно.%s", orBracket(f.Server, "сервер DEPO"), depoSetupFileName(f), run, what, orBracket(ip, "IP-адрес сервера bj"), sign)}, true } return Letter{}, false diff --git a/internal/lkgateway/onboarding_test.go b/internal/lkgateway/onboarding_test.go index d88e89e..3e9ec26 100644 --- a/internal/lkgateway/onboarding_test.go +++ b/internal/lkgateway/onboarding_test.go @@ -347,17 +347,18 @@ func TestFromLocalBundle(t *testing.T) { } } -// Один скрипт подготовки базы DEPO: сам ищет базу, создаёт логин, таблицы, права и (по выбору) задание. +// Один скрипт подготовки базы DEPO: в названной базе (сам не ищет, но проверяет, что это DEPO) создаёт логин, +// таблицы, права и (по выбору) задание. func TestDepoSetupScript(t *testing.T) { f := FancyMSSQLSettings{} - s := depoSetupScript(f, "", "bj_user", "Pass1word", true) - for _, want := range []string{"DECLARE @db sysname = N'';", "DECLARE @pass nvarchar(128) = N'Pass1word';", "OD_FACES", "CREATE LOGIN [bj_user] WITH PASSWORD = N''' + REPLACE(@pass, N'''', N'''''')", "FTV_M2M_EVENTS", "db_datareader", "sp_add_job @job_name = N'bj2depo'", "@database_name = @db", "SQLAgentReaderRole"} { + s := depoSetupScript(f, "DEPO_DATA", "bj_user", "Pass1word", true) + for _, want := range []string{"DECLARE @db sysname = N'DEPO_DATA';", "RAISERROR(N'Базы %s на этом сервере нет.", "OBJECT_ID(QUOTENAME(@db) + N'.dbo.OD_FACES', N'U') IS NULL", "DECLARE @pass nvarchar(128) = N'Pass1word';", "OD_FACES", "CREATE LOGIN [bj_user] WITH PASSWORD = N''' + REPLACE(@pass, N'''', N'''''')", "FTV_M2M_EVENTS", "db_datareader", "sp_add_job @job_name = N'bj2depo'", "@database_name = @db", "SQLAgentReaderRole"} { if !strings.Contains(s, want) { t.Fatalf("в скрипте нет %q", want) } } - if strings.Contains(s, "BJ_DEPO_DATA") || strings.Contains(s, "\nGO") { - t.Fatal("в скрипте — имя нашей базы или GO (переменные @db между пакетами не живут)") + if strings.Contains(s, "BJ_DEPO_DATA") || strings.Contains(s, "\nGO") || strings.Contains(s, "sys.databases") || strings.Contains(s, "%!") { + t.Fatal("в скрипте — имя нашей базы, GO (переменные @db между пакетами не живут), поиск базы по серверу или сбой подстановки") } // каждая строка EXEC(@in + N'…') — закрытый литерал: чётное число кавычек for _, line := range strings.Split(s, "\n") { @@ -370,7 +371,7 @@ func TestDepoSetupScript(t *testing.T) { t.Fatal("обработка силами bj: без задания, с правом на процедуру и указанной базой") } pg := depoSetupScript(FancyMSSQLSettings{DBType: "postgres"}, "depo", "bj_user", "p'w", false) - if !strings.Contains(pg, `\c "depo"`) || !strings.Contains(pg, "PASSWORD 'p''w'") { + if !strings.Contains(pg, `\c "depo"`) || !strings.Contains(pg, "PASSWORD 'p''w'") || !strings.Contains(pg, "RAISE EXCEPTION 'В базе % нет учётных таблиц DEPO") { t.Fatal("PostgreSQL: подключение к базе и экранирование пароля") } if p := depoGenPassword(); len(p) != 24 || !strings.ContainsAny(p, "ABCDEFGHJKLMNPQRSTUVWXYZ") || !strings.ContainsAny(p, "23456789") { @@ -443,7 +444,7 @@ func TestDumpOnboardingPages(t *testing.T) { t.Fatal(err) } _ = os.WriteFile(filepath.Join(dir, "prepare.html"), []byte(b.String()), 0o644) - _ = os.WriteFile(filepath.Join(dir, "depo-script.sql"), []byte(depoSetupScript(FancyMSSQLSettings{}, "", "bj_user", "Xk7pQ2mN9vR4tL8wZ3hB6cJd", true)), 0o644) + _ = os.WriteFile(filepath.Join(dir, "depo-script.sql"), []byte(depoSetupScript(FancyMSSQLSettings{}, "DEPO_DATA", "bj_user", "Xk7pQ2mN9vR4tL8wZ3hB6cJd", true)), 0o644) } // Первая страница (администратор) — для снимка в TestDumpOnboardingPages. @@ -503,9 +504,12 @@ func TestDepoScriptView(t *testing.T) { if f := rc.Snapshot().Fancy; f.Server != "10.0.0.5" || f.Password != "Secret123x" { t.Fatalf("без адреса сервера настройки не должны меняться: %q", f.Server) } - // PostgreSQL — свой скрипт; без имени базы — понятная причина вместо скрипта - form.Set("db_type", "postgres") + // имя базы обязательно (bj по серверу не ищет) — без него вместо скрипта понятная причина form.Set("depo_database", "") + if w := post(form); w.Code != http.StatusBadRequest || !strings.Contains(w.Body.String(), "имя базы") { + t.Fatalf("SQL Server без базы: %d %s", w.Code, w.Body.String()) + } + form.Set("db_type", "postgres") if w := post(form); w.Code != http.StatusBadRequest || !strings.Contains(w.Body.String(), "имя базы") { t.Fatalf("PostgreSQL без базы: %d %s", w.Code, w.Body.String()) } diff --git a/internal/lkgateway/setup.go b/internal/lkgateway/setup.go index 622448f..92f52b5 100644 --- a/internal/lkgateway/setup.go +++ b/internal/lkgateway/setup.go @@ -174,7 +174,6 @@ type WizardData struct { DepoJobDef string // способ обработки по умолчанию для мастера Fancy *SetupData // шаг 3: общий блок «Обмен Fancy» (тот же, что в настройках) Sub string // экран шага 3 (учёт DEPO): intro|conn|tables|job|done - DepoCands []string // шаг 3: найдено несколько баз DEPO — выбор DepoUser string // шаг 3, «скрипт администратору»: логин bj, который создаст скрипт DepoPass string // и его пароль (сохранённый или придуманный сейчас — сохраняется со скриптом) DepoMode string // выбранный способ подготовки базы: auto|script|existing @@ -324,7 +323,6 @@ func (h *setupHandlers) renderWizard(w http.ResponseWriter, r *http.Request) { } if step == 3 { d.Sub = depoWizardSub(r.URL.Query().Get("sub")) - d.DepoCands = depoCands(r.URL.Query().Get("cands")) d.DepoUser, d.DepoPass = depoSetupLogin(s.Fancy), s.Fancy.Password if s.Fancy.User != d.DepoUser || d.DepoPass == "" { d.DepoPass = depoGenPassword() diff --git a/internal/lkgateway/web/templates/admin_wizard.html b/internal/lkgateway/web/templates/admin_wizard.html index ad49dfe..c81940e 100644 --- a/internal/lkgateway/web/templates/admin_wizard.html +++ b/internal/lkgateway/web/templates/admin_wizard.html @@ -78,7 +78,7 @@ .dq-sql { margin-top:14px; } .dq-sql-head { display:flex; align-items:center; gap:8px; margin:0 0 6px; justify-content:flex-start; } .dq-sql-st { flex:1; font-size:12.5px; color:var(--muted); } -.dq-sql-st.bad { color:var(--warn); } +.dq-sql pre:empty::before { content:attr(data-msg); color:var(--muted); font-family:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif; font-size:13px; } .dq-sql pre { max-height:380px; min-height:140px; font-size:12px; margin:0; transition:opacity .2s; } .dq-sql pre.dq-busy { opacity:.5; } .dq.is-pg [data-ms], .dq:not(.is-pg) [data-pg] { display:none !important; } @@ -179,10 +179,9 @@
- {{if .DepoCands}} - {{else}}{{end}} +
-

{{if .DepoCands}}Логину bj видны несколько баз DEPO — выберите нужную.{{else}}Не знаете имя базы — оставьте пустым: bj найдёт её сам по учётным таблицам OD_FACES и OD_RESTS.Для PostgreSQL имя базы нужно указать.{{end}}

+

Точное имя базы учёта DEPO — bj будет работать только с ней. Перед подготовкой bj проверит, что в ней есть учётные таблицы DEPO, и в чужую базу ничего не запишет.

@@ -221,9 +220,9 @@
Скрипт собирается из ответов выше и обновляется сам. - +
-

+          

         
         

Администратор выполняет скрипт в SSMS или sqlcmd под логином с правами sysadmin.Администратор выполняет скрипт в psql суперпользователем (postgres). Когда выполнит — нажмите «Проверить подключение».

{{template "letterCard" (letter "dba")}} @@ -240,13 +239,12 @@