8905ebc6fb
- Первая страница — только администратор (и восстановление из копии); ключ продукта — после создания администратора, на шаге «Лицензия». Консоль первой загрузки ключ больше не спрашивает (для образа с комплектом bj). - «Подготовка модулей» начинает с проверки обновлений: есть — ставит (bj перезапускается, страница ждёт и продолжает), нет — дальше; выпуск, который уже ставили, по кругу не ставится. Затем модули лицензии и переход на «Время и сеть» (шаг 12) — часовой пояс клиент выбирает сам, шаг больше не проскакивает. - Учёт DEPO одним шагом: bj сам находит базу Fancy DEPO на сервере (по таблицам OD_FACES/OD_RESTS), имя базы не спрашивается. Один скрипт создаёт логин bj, таблицы обмена и права; по выбору — задание обработки в SQL Agent или обработка на сервере bj. Выполнить сразу (логин администратора СУБД не сохраняется), скачать скрипт для администратора или подключиться готовым логином. - Восстановление из копии — только на первой странице, в мастере его больше нет. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
99 lines
3.5 KiB
Go
99 lines
3.5 KiB
Go
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
|
|
}
|