Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
677f5047a9 | |||
ffd232ad61 | |||
05ec7868a9 | |||
f9c1b89608 | |||
1be8583ae6 | |||
fdb66f92f5 | |||
7c6ca4c81b | |||
9b9eadfe92 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -25,8 +25,10 @@ _cgo_export.*
|
|||||||
_testmain.go
|
_testmain.go
|
||||||
|
|
||||||
*.exe
|
*.exe
|
||||||
|
*.dll
|
||||||
*.test
|
*.test
|
||||||
*.prof
|
*.prof
|
||||||
|
*.zip
|
||||||
|
|
||||||
*coverage.out
|
*coverage.out
|
||||||
coverage.all
|
coverage.all
|
||||||
|
33
README.md
33
README.md
@ -11,27 +11,40 @@ Oracle Multi Querier (omq) - программа для выгрузки резу
|
|||||||
|
|
||||||
## Параметры подключения к серверам
|
## Параметры подключения к серверам
|
||||||
|
|
||||||
Список серверов должен быть указан в файле с расширением `.ini` со следующей структурой:
|
Список серверов должен быть указан в файле с расширением `.toml`, который лежит в папке `db`, со следующей структурой:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
[<HOST>/<SERVICE>]
|
[Servers]
|
||||||
Login = <LOGIN>
|
Name = "<NAME>"
|
||||||
Password = <PASSWORD>
|
Login = "<LOGIN>"
|
||||||
Name = <NAME>
|
Password = "<PASSWORD>"
|
||||||
|
Hosts = ["<HOST1>", "<HOST2>"]
|
||||||
|
Service = "<SERVICE>"
|
||||||
```
|
```
|
||||||
|
|
||||||
где:
|
где:
|
||||||
|
* `<NAME>` - наименование филиала
|
||||||
* `<HOST>` - адрес сервера
|
* `<HOST>` - адрес сервера
|
||||||
* `<SERVICE>` - наименование сервиса
|
* `<SERVICE>` - наименование сервиса
|
||||||
* `<LOGIN>` - логин
|
* `<LOGIN>` - логин
|
||||||
* `<PASSWORD>` - пароль
|
* `<PASSWORD>` - пароль
|
||||||
* `<NAME>` - наименование филиала
|
* `<HOST1>`, `<HOST2>` - список хостов БД, которые будут перебираться в порядке указания
|
||||||
|
* `<SERVICE>` - наименование сервиса
|
||||||
|
|
||||||
например:
|
например:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
[myserver.example.com/MYSERVICE]
|
[Servers]
|
||||||
Login = User1
|
Name = "Основной сервер"
|
||||||
Password = p@$$w0rd
|
Login = "User1"
|
||||||
Name = Основной сервер
|
Password = "p@$$w0rd1"
|
||||||
|
Hosts = ["db.server1.com", "db.server2.com"]
|
||||||
|
Service = "mydb"
|
||||||
|
|
||||||
|
[Servers]
|
||||||
|
Name = "Второй сервер сервер"
|
||||||
|
Login = "User2"
|
||||||
|
Password = "p@$$w0rd2"
|
||||||
|
Hosts = ["db.server3.com", "db.server4.com"]
|
||||||
|
Service = "mydb"
|
||||||
```
|
```
|
||||||
|
113
config.go
Normal file
113
config.go
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/BurntSushi/toml"
|
||||||
|
go_ora "github.com/sijms/go-ora/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
// Список серверов
|
||||||
|
Servers []*Server
|
||||||
|
|
||||||
|
// Кол-во prefetch строк
|
||||||
|
PrefetchRows int
|
||||||
|
|
||||||
|
// Таймаут ожидания ответа на запрос
|
||||||
|
Timeout int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
// Имя сервера
|
||||||
|
Name string
|
||||||
|
|
||||||
|
// Логин БД Oracle
|
||||||
|
Login string
|
||||||
|
|
||||||
|
// Пароль БД Oracle
|
||||||
|
Password string
|
||||||
|
|
||||||
|
// Список хостов БД Oracle
|
||||||
|
Hosts []string
|
||||||
|
|
||||||
|
// Наименование сервиса БД Oracle
|
||||||
|
Service string
|
||||||
|
|
||||||
|
// Статус работы с сервером
|
||||||
|
status string
|
||||||
|
|
||||||
|
// Ошибка работы с сервером
|
||||||
|
err error
|
||||||
|
|
||||||
|
config *Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadConfig(filePath string) (*Config, error) {
|
||||||
|
b, err := readFileIgnoreBOM(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
config := new(Config)
|
||||||
|
|
||||||
|
_, err = toml.NewDecoder(bytes.NewReader(b)).Decode(&config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.PrefetchRows <= 0 {
|
||||||
|
config.PrefetchRows = 1000
|
||||||
|
}
|
||||||
|
if config.Timeout < 0 {
|
||||||
|
config.Timeout = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range config.Servers {
|
||||||
|
config.Servers[i].config = config
|
||||||
|
}
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Url() (string, error) {
|
||||||
|
urlOptions := make(map[string]string)
|
||||||
|
|
||||||
|
if len(s.Hosts) == 0 {
|
||||||
|
return "", errors.New("hostname is not specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
host, portStr, err := net.SplitHostPort(s.Hosts[0])
|
||||||
|
if err != nil {
|
||||||
|
host = s.Hosts[0]
|
||||||
|
portStr = "1521"
|
||||||
|
}
|
||||||
|
port, err := strconv.Atoi(portStr)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
additionalHosts := make([]string, 0)
|
||||||
|
if len(s.Hosts) > 1 {
|
||||||
|
for _, v := range s.Hosts[1:] {
|
||||||
|
h, p, err := net.SplitHostPort(v)
|
||||||
|
if err != nil {
|
||||||
|
h = v
|
||||||
|
p = "1521"
|
||||||
|
}
|
||||||
|
additionalHosts = append(additionalHosts, fmt.Sprintf("%s:%s", h, p))
|
||||||
|
}
|
||||||
|
|
||||||
|
urlOptions["server"] = strings.Join(additionalHosts, ",") // TODO: должен добавляться порт
|
||||||
|
}
|
||||||
|
|
||||||
|
urlOptions["TIMEOUT"] = strconv.Itoa(s.config.Timeout)
|
||||||
|
urlOptions["PREFETCH_ROWS"] = strconv.Itoa(s.config.PrefetchRows)
|
||||||
|
|
||||||
|
return go_ora.BuildUrl(host, port, s.Service, s.Login, s.Password, urlOptions), nil
|
||||||
|
}
|
15
consts.go
15
consts.go
@ -1,5 +1,20 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// Путь к папке с SQL-скриптами
|
||||||
SQL_FILES_DIR = "sql"
|
SQL_FILES_DIR = "sql"
|
||||||
|
|
||||||
|
// Расширение файлов SQL-скриптов
|
||||||
|
SQL_FILE_EXT = "sql"
|
||||||
|
|
||||||
|
// Путь к папке с настройками БД
|
||||||
|
CONFIG_FILES_DIR = "db"
|
||||||
|
|
||||||
|
// Расширение файлов с настройками БД
|
||||||
|
CONFIG_FILE_EXT = "toml"
|
||||||
|
|
||||||
|
// Период обновления графического интерфейса
|
||||||
|
UI_UPDATE_PERIOD = time.Second / 2
|
||||||
)
|
)
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 6.4 KiB |
@ -30,6 +30,7 @@ type DummyTransformer struct{}
|
|||||||
func (e *DummyTransformer) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
func (e *DummyTransformer) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
||||||
copy(dst, src)
|
copy(dst, src)
|
||||||
|
|
||||||
return len(dst), len(src), nil
|
return len(src), len(src), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *DummyTransformer) Reset() {}
|
func (e *DummyTransformer) Reset() {}
|
||||||
|
10
export.go
10
export.go
@ -7,8 +7,10 @@ import (
|
|||||||
type ExportFormat string
|
type ExportFormat string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ExportFormatExcel ExportFormat = "XLSX"
|
ExportFormatExcel ExportFormat = "XLSX"
|
||||||
ExportFormatCsv ExportFormat = "CSV"
|
ExportFormatCsv ExportFormat = "CSV"
|
||||||
|
ExportFormatCsvZip ExportFormat = "CSV+ZIP"
|
||||||
|
ExportFormatCsvZst ExportFormat = "CSV+ZSTD"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Exporter - интерфейс экспорта
|
// Exporter - интерфейс экспорта
|
||||||
@ -22,6 +24,10 @@ func (e ExportFormat) GetExporter(encoding Encoding) (Exporter, error) {
|
|||||||
return new(XlsxExporter), nil
|
return new(XlsxExporter), nil
|
||||||
case ExportFormatCsv:
|
case ExportFormatCsv:
|
||||||
return &CsvExporter{Encoding: encoding}, nil
|
return &CsvExporter{Encoding: encoding}, nil
|
||||||
|
case ExportFormatCsvZip:
|
||||||
|
return &CsvZipExporter{Encoding: encoding}, nil
|
||||||
|
case ExportFormatCsvZst:
|
||||||
|
return &CsvZstExporter{Encoding: encoding}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("unknown format: %s", string(e))
|
return nil, fmt.Errorf("unknown format: %s", string(e))
|
||||||
|
@ -1,21 +1,20 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"archive/zip"
|
"bufio"
|
||||||
"encoding/csv"
|
"encoding/csv"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Экспорт в CSV, сжатый в ZIP-архив
|
// Экспорт в CSV
|
||||||
type CsvExporter struct {
|
type CsvExporter struct {
|
||||||
Encoding Encoding
|
Encoding Encoding
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CsvExporter) FileExt() string {
|
func (c *CsvExporter) FileExt() string {
|
||||||
return ".zip"
|
return ".csv"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CsvExporter) Convert(filePath string, rows chan []any) error {
|
func (c *CsvExporter) Convert(filePath string, rows chan []any) error {
|
||||||
@ -24,30 +23,19 @@ func (c *CsvExporter) Convert(filePath string, rows chan []any) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
z := zip.NewWriter(f)
|
buf := bufio.NewWriterSize(f, 4*1024*1024)
|
||||||
|
|
||||||
zw, err := z.Create(filepath.Base(filePath) + ".csv")
|
|
||||||
if err != nil {
|
|
||||||
f.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
/*var enc io.Writer
|
|
||||||
if c.Encoding == EncodingWndows1251 {
|
|
||||||
enc = charmap.Windows1251.NewEncoder().Writer(zw)
|
|
||||||
} else {
|
|
||||||
enc = zw
|
|
||||||
}*/
|
|
||||||
|
|
||||||
enc, err := c.Encoding.Encoder()
|
enc, err := c.Encoding.Encoder()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
w := csv.NewWriter(enc.Writer(zw))
|
w := csv.NewWriter(enc.Writer(buf))
|
||||||
w.Comma = ';'
|
w.Comma = ';'
|
||||||
|
|
||||||
|
rowNum := 0
|
||||||
for row := range rows {
|
for row := range rows {
|
||||||
|
rowNum++
|
||||||
rowsStr := make([]string, len(row))
|
rowsStr := make([]string, len(row))
|
||||||
for i := range row {
|
for i := range row {
|
||||||
rowsStr[i] = toCsvField(row[i])
|
rowsStr[i] = toCsvField(row[i])
|
||||||
@ -61,19 +49,23 @@ func (c *CsvExporter) Convert(filePath string, rows chan []any) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
w.Flush()
|
w.Flush()
|
||||||
|
if err = w.Error(); err != nil {
|
||||||
if w.Error() != nil {
|
|
||||||
f.Close()
|
f.Close()
|
||||||
return w.Error()
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = z.Close()
|
err = buf.Flush()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.Close()
|
f.Close()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return f.Close()
|
err = f.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func toCsvField(a any) string {
|
func toCsvField(a any) string {
|
||||||
|
83
export_csvzip.go
Normal file
83
export_csvzip.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"bufio"
|
||||||
|
"encoding/csv"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Экспорт в CSV, сжатый в ZIP-архив
|
||||||
|
type CsvZipExporter struct {
|
||||||
|
Encoding Encoding
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CsvZipExporter) FileExt() string {
|
||||||
|
return ".zip"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CsvZipExporter) Convert(filePath string, rows chan []any) error {
|
||||||
|
f, err := os.Create(filePath + c.FileExt())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := bufio.NewWriterSize(f, 4*1024*1024)
|
||||||
|
|
||||||
|
z := zip.NewWriter(buf)
|
||||||
|
|
||||||
|
zw, err := z.Create(filepath.Base(filePath) + ".csv")
|
||||||
|
if err != nil {
|
||||||
|
f.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
enc, err := c.Encoding.Encoder()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w := csv.NewWriter(enc.Writer(zw))
|
||||||
|
w.Comma = ';'
|
||||||
|
|
||||||
|
rowNum := 0
|
||||||
|
for row := range rows {
|
||||||
|
rowNum++
|
||||||
|
rowsStr := make([]string, len(row))
|
||||||
|
for i := range row {
|
||||||
|
rowsStr[i] = toCsvField(row[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
err = w.Write(rowsStr)
|
||||||
|
if err != nil {
|
||||||
|
f.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Flush()
|
||||||
|
if err = w.Error(); err != nil {
|
||||||
|
f.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = z.Close()
|
||||||
|
if err != nil {
|
||||||
|
f.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = buf.Flush()
|
||||||
|
if err != nil {
|
||||||
|
f.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = f.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
81
export_csvzst.go
Normal file
81
export_csvzst.go
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/csv"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/klauspost/compress/zstd"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Экспорт в CSV, сжатый в ZIP-архив
|
||||||
|
type CsvZstExporter struct {
|
||||||
|
Encoding Encoding
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CsvZstExporter) FileExt() string {
|
||||||
|
return ".csv.zst"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CsvZstExporter) Convert(filePath string, rows chan []any) error {
|
||||||
|
f, err := os.Create(filePath + c.FileExt())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := bufio.NewWriterSize(f, 4*1024*1024)
|
||||||
|
|
||||||
|
z, err := zstd.NewWriter(buf)
|
||||||
|
if err != nil {
|
||||||
|
f.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
enc, err := c.Encoding.Encoder()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w := csv.NewWriter(enc.Writer(z))
|
||||||
|
w.Comma = ';'
|
||||||
|
|
||||||
|
rowNum := 0
|
||||||
|
for row := range rows {
|
||||||
|
rowNum++
|
||||||
|
rowsStr := make([]string, len(row))
|
||||||
|
for i := range row {
|
||||||
|
rowsStr[i] = toCsvField(row[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
err = w.Write(rowsStr)
|
||||||
|
if err != nil {
|
||||||
|
f.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Flush()
|
||||||
|
if err = w.Error(); err != nil {
|
||||||
|
f.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = z.Close()
|
||||||
|
if err != nil {
|
||||||
|
f.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = buf.Flush()
|
||||||
|
if err != nil {
|
||||||
|
f.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = f.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
26
go.mod
26
go.mod
@ -1,29 +1,23 @@
|
|||||||
module omc
|
module omq
|
||||||
|
|
||||||
go 1.21.3
|
go 1.21
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/BurntSushi/toml v1.3.2
|
||||||
github.com/dimchansky/utfbom v1.1.1
|
github.com/dimchansky/utfbom v1.1.1
|
||||||
github.com/gdamore/tcell/v2 v2.6.0
|
github.com/sijms/go-ora/v2 v2.8.4
|
||||||
github.com/rivo/tview v0.0.0-20231115183240-7c9e464bac02
|
|
||||||
github.com/sijms/go-ora/v2 v2.7.22
|
|
||||||
github.com/xuri/excelize/v2 v2.8.0
|
github.com/xuri/excelize/v2 v2.8.0
|
||||||
|
github.com/ying32/govcl v2.2.3+incompatible
|
||||||
golang.org/x/text v0.14.0
|
golang.org/x/text v0.14.0
|
||||||
gopkg.in/ini.v1 v1.67.0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gdamore/encoding v1.0.0 // indirect
|
github.com/klauspost/compress v1.17.7 // indirect
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
|
||||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
|
||||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||||
github.com/richardlehane/mscfb v1.0.4 // indirect
|
github.com/richardlehane/mscfb v1.0.4 // indirect
|
||||||
github.com/richardlehane/msoleps v1.0.3 // indirect
|
github.com/richardlehane/msoleps v1.0.3 // indirect
|
||||||
github.com/rivo/uniseg v0.4.4 // indirect
|
github.com/xuri/efp v0.0.0-20230802181842-ad255f2331ca // indirect
|
||||||
github.com/xuri/efp v0.0.0-20231025114914-d1ff6096ae53 // indirect
|
github.com/xuri/nfp v0.0.0-20230819163627-dc951e3ffe1a // indirect
|
||||||
github.com/xuri/nfp v0.0.0-20230919160717-d98342af3f05 // indirect
|
golang.org/x/crypto v0.12.0 // indirect
|
||||||
golang.org/x/crypto v0.15.0 // indirect
|
golang.org/x/net v0.14.0 // indirect
|
||||||
golang.org/x/net v0.18.0 // indirect
|
|
||||||
golang.org/x/sys v0.14.0 // indirect
|
|
||||||
golang.org/x/term v0.14.0 // indirect
|
|
||||||
)
|
)
|
||||||
|
43
go.sum
43
go.sum
@ -1,17 +1,12 @@
|
|||||||
|
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
||||||
|
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
|
github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
|
||||||
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
|
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
|
||||||
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
|
||||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||||
github.com/gdamore/tcell/v2 v2.6.0 h1:OKbluoP9VYmJwZwq/iLb4BxwKcwGthaa1YNBJIyCySg=
|
|
||||||
github.com/gdamore/tcell/v2 v2.6.0/go.mod h1:be9omFATkdr0D9qewWW3d+MEvl5dha+Etb5y65J2H8Y=
|
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
|
||||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
|
||||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
|
||||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
|
||||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
||||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
@ -21,33 +16,26 @@ github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7
|
|||||||
github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
||||||
github.com/richardlehane/msoleps v1.0.3 h1:aznSZzrwYRl3rLKRT3gUk9am7T/mLNSnJINvN0AQoVM=
|
github.com/richardlehane/msoleps v1.0.3 h1:aznSZzrwYRl3rLKRT3gUk9am7T/mLNSnJINvN0AQoVM=
|
||||||
github.com/richardlehane/msoleps v1.0.3/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
github.com/richardlehane/msoleps v1.0.3/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
||||||
github.com/rivo/tview v0.0.0-20231115183240-7c9e464bac02 h1:UkSrnoeeuKdeNFe4ghSjZmp7tA5B1CQKnvV1By9FSYw=
|
github.com/sijms/go-ora/v2 v2.8.4 h1:6VmkqKg/8bELn+k3mr2wI9+inJEiNhNx1p2IOrnx7T4=
|
||||||
github.com/rivo/tview v0.0.0-20231115183240-7c9e464bac02/go.mod h1:nVwGv4MP47T0jvlk7KuTTjjuSmrGO4JF0iaiNt4bufE=
|
github.com/sijms/go-ora/v2 v2.8.4/go.mod h1:EHxlY6x7y9HAsdfumurRfTd+v8NrEOTR3Xl4FWlH6xk=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
|
||||||
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
|
||||||
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
|
|
||||||
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
|
||||||
github.com/sijms/go-ora/v2 v2.7.22 h1:B2mSLhDDWTgsjdM0d/O7iJn041cBy5z7fA8sR082vCg=
|
|
||||||
github.com/sijms/go-ora/v2 v2.7.22/go.mod h1:EHxlY6x7y9HAsdfumurRfTd+v8NrEOTR3Xl4FWlH6xk=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/xuri/efp v0.0.0-20230802181842-ad255f2331ca h1:uvPMDVyP7PXMMioYdyPH+0O+Ta/UO1WFfNYMO3Wz0eg=
|
||||||
github.com/xuri/efp v0.0.0-20230802181842-ad255f2331ca/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
|
github.com/xuri/efp v0.0.0-20230802181842-ad255f2331ca/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
|
||||||
github.com/xuri/efp v0.0.0-20231025114914-d1ff6096ae53 h1:Chd9DkqERQQuHpXjR/HSV1jLZA6uaoiwwH3vSuF3IW0=
|
|
||||||
github.com/xuri/efp v0.0.0-20231025114914-d1ff6096ae53/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
|
|
||||||
github.com/xuri/excelize/v2 v2.8.0 h1:Vd4Qy809fupgp1v7X+nCS/MioeQmYVVzi495UCTqB7U=
|
github.com/xuri/excelize/v2 v2.8.0 h1:Vd4Qy809fupgp1v7X+nCS/MioeQmYVVzi495UCTqB7U=
|
||||||
github.com/xuri/excelize/v2 v2.8.0/go.mod h1:6iA2edBTKxKbZAa7X5bDhcCg51xdOn1Ar5sfoXRGrQg=
|
github.com/xuri/excelize/v2 v2.8.0/go.mod h1:6iA2edBTKxKbZAa7X5bDhcCg51xdOn1Ar5sfoXRGrQg=
|
||||||
|
github.com/xuri/nfp v0.0.0-20230819163627-dc951e3ffe1a h1:Mw2VNrNNNjDtw68VsEj2+st+oCSn4Uz7vZw6TbhcV1o=
|
||||||
github.com/xuri/nfp v0.0.0-20230819163627-dc951e3ffe1a/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
|
github.com/xuri/nfp v0.0.0-20230819163627-dc951e3ffe1a/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
|
||||||
github.com/xuri/nfp v0.0.0-20230919160717-d98342af3f05 h1:qhbILQo1K3mphbwKh1vNm4oGezE1eF9fQWmNiIpSfI4=
|
github.com/ying32/govcl v2.2.3+incompatible h1:Iyfcl26yNE1USm+3uG+btQyhkoFIV18+VITrUdHu8Lw=
|
||||||
github.com/xuri/nfp v0.0.0-20230919160717-d98342af3f05/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
|
github.com/ying32/govcl v2.2.3+incompatible/go.mod h1:yZVtbJ9Md1nAVxtHKIriKZn4K6TQYqI1en3sN/m9FJ8=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
|
||||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||||
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
|
|
||||||
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
|
|
||||||
golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo=
|
golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo=
|
||||||
golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8=
|
golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
@ -57,9 +45,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
|||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
|
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
|
||||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||||
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
|
|
||||||
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@ -71,15 +58,11 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
|
||||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
||||||
golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8=
|
|
||||||
golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
@ -94,8 +77,6 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
|||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
|
||||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
22
icons.go
Normal file
22
icons.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
|
||||||
|
"github.com/ying32/govcl/vcl"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed img/*
|
||||||
|
var images embed.FS
|
||||||
|
|
||||||
|
func getImageBitmap(path string) *vcl.TBitmap {
|
||||||
|
b, err := images.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pic := vcl.NewPicture()
|
||||||
|
pic.LoadFromBytes(b)
|
||||||
|
|
||||||
|
return pic.Bitmap()
|
||||||
|
}
|
BIN
img/bullet_go.png
Normal file
BIN
img/bullet_go.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 405 B |
BIN
img/change_password.png
Normal file
BIN
img/change_password.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 667 B |
BIN
img/information.png
Normal file
BIN
img/information.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 784 B |
79
kernel.go
79
kernel.go
@ -5,7 +5,6 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -26,37 +25,36 @@ type Job struct {
|
|||||||
scriptFilePath string
|
scriptFilePath string
|
||||||
exportFileFormat ExportFormat
|
exportFileFormat ExportFormat
|
||||||
encoding Encoding
|
encoding Encoding
|
||||||
|
exportPath string
|
||||||
|
|
||||||
servers []Server
|
config Config
|
||||||
status string
|
|
||||||
|
|
||||||
isFinished bool
|
status string
|
||||||
|
isFinished bool
|
||||||
|
exportedRows int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *Job) init() error {
|
func (j *Job) init() error {
|
||||||
j.status = "Чтение списка серверов..."
|
j.status = "Чтение списка серверов..."
|
||||||
|
|
||||||
servers, err := loadConfig(j.configFilePath)
|
config, err := loadConfig(j.configFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
j.servers = servers
|
j.config = *config
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *Job) launch() error {
|
func (j *Job) launch() error {
|
||||||
j.status = "Чтение файла SQL-скрипта..."
|
j.status = "Чтение файла SQL-скрипта"
|
||||||
sqlBytes, err := readFileIgnoreBOM(j.scriptFilePath)
|
sqlBytes, err := readFileIgnoreBOM(j.scriptFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
branchFieldNum := getBranchFieldNumber(string(sqlBytes))
|
branchFieldNum := getBranchFieldNumber(string(sqlBytes))
|
||||||
if branchFieldNum <= 0 {
|
|
||||||
return fmt.Errorf("Некорректное значение номера поля филиала: %v", branchFieldNum)
|
|
||||||
}
|
|
||||||
|
|
||||||
rowsChan := j.iterateServers(string(sqlBytes), branchFieldNum)
|
rowsChan := j.iterateServers(string(sqlBytes), branchFieldNum)
|
||||||
|
|
||||||
@ -74,7 +72,7 @@ func (j *Job) export(inputRows chan Row) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fileName := filepath.Base(j.scriptFilePath)
|
fileName := filepath.Join(j.exportPath, filepath.Base(j.scriptFilePath))
|
||||||
fileName = strings.TrimSuffix(fileName, filepath.Ext(fileName))
|
fileName = strings.TrimSuffix(fileName, filepath.Ext(fileName))
|
||||||
|
|
||||||
outputRows := make(chan []any)
|
outputRows := make(chan []any)
|
||||||
@ -84,10 +82,9 @@ func (j *Job) export(inputRows chan Row) error {
|
|||||||
|
|
||||||
gotHeader := false
|
gotHeader := false
|
||||||
rowsCache := make([][]any, 0)
|
rowsCache := make([][]any, 0)
|
||||||
rowCount := -1
|
|
||||||
|
|
||||||
for row := range inputRows {
|
for row := range inputRows {
|
||||||
rowCount += 1
|
j.exportedRows++
|
||||||
|
|
||||||
if gotHeader {
|
if gotHeader {
|
||||||
outputRows <- row.data
|
outputRows <- row.data
|
||||||
@ -107,7 +104,7 @@ func (j *Job) export(inputRows chan Row) error {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
err = converter.Convert(fileName, outputRows)
|
err = converter.Convert(fileName, outputRows)
|
||||||
j.status = "ЗАВЕРШЕНО."
|
j.status = "Выгрузка завершена."
|
||||||
j.isFinished = true
|
j.isFinished = true
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -125,36 +122,42 @@ func (j *Job) iterateServers(sqlStr string, branchFieldNum int) chan Row {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
wg := new(sync.WaitGroup)
|
wg := new(sync.WaitGroup)
|
||||||
wg.Add(len(j.servers))
|
wg.Add(len(j.config.Servers))
|
||||||
|
|
||||||
for i, server := range j.servers {
|
for i, server := range j.config.Servers {
|
||||||
i := i
|
i := i
|
||||||
server := server
|
server := server
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
j.servers[i].Status = "Подключение к серверу"
|
serverUrl, err := server.Url()
|
||||||
db := sql.OpenDB(go_ora.NewConnector(server.Url))
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
err := db.Ping()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
j.servers[i].Error = err
|
j.config.Servers[i].err = err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
j.servers[i].Status = "Выполнение SQL-запроса"
|
server.status = "Подключение к серверу..."
|
||||||
|
db := sql.OpenDB(go_ora.NewConnector(serverUrl))
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
err = db.Ping()
|
||||||
|
if err != nil {
|
||||||
|
server.err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
server.status = "Выполнение SQL-запроса..."
|
||||||
rows, err := db.Query(sqlStr)
|
rows, err := db.Query(sqlStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
j.servers[i].Error = err
|
server.err = err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
cols, err := rows.Columns()
|
cols, err := rows.Columns()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
j.servers[i].Error = err
|
server.err = err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,8 +166,8 @@ func (j *Job) iterateServers(sqlStr string, branchFieldNum int) chan Row {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rowNum := 0
|
rowNum := 0
|
||||||
|
server.status = fmt.Sprintf("Выгружено %d строк...", rowNum)
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
j.servers[i].Status = fmt.Sprintf("Выгружено %d строк", rowNum)
|
|
||||||
|
|
||||||
pointers := make([]any, len(cols))
|
pointers := make([]any, len(cols))
|
||||||
container := make([]any, len(cols))
|
container := make([]any, len(cols))
|
||||||
@ -174,13 +177,22 @@ func (j *Job) iterateServers(sqlStr string, branchFieldNum int) chan Row {
|
|||||||
|
|
||||||
err = rows.Scan(pointers...)
|
err = rows.Scan(pointers...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
j.servers[i].Error = err
|
server.err = err
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
rowsChan <- Row{isHeader: false, data: append(append(container[:branchFieldNum-1], server.Name), container[branchFieldNum:]...)} // Добавление имени сервера
|
|
||||||
|
var resultRow []any
|
||||||
|
if branchFieldNum != 0 {
|
||||||
|
// Подстановка имени сервера
|
||||||
|
resultRow = append(append(container[:branchFieldNum-1], server.Name), container[branchFieldNum:]...)
|
||||||
|
} else {
|
||||||
|
resultRow = container
|
||||||
|
}
|
||||||
|
rowsChan <- Row{isHeader: false, data: resultRow}
|
||||||
rowNum += 1
|
rowNum += 1
|
||||||
|
server.status = fmt.Sprintf("Выгружено %d строк...", rowNum)
|
||||||
}
|
}
|
||||||
j.servers[i].Status = fmt.Sprintf("ЗАВЕРШЕНО: Выгружено %d строк.", rowNum)
|
server.status = fmt.Sprintf("Выгружено %d строк.", rowNum)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
j.status = "Ожидание завершения работы с серверами"
|
j.status = "Ожидание завершения работы с серверами"
|
||||||
@ -199,6 +211,7 @@ func sliceToAnySlice[T string | any](slice []T) []any {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// readFileIgnoreBOM возвращает содержимое файла без BOM
|
||||||
func readFileIgnoreBOM(filePath string) ([]byte, error) {
|
func readFileIgnoreBOM(filePath string) ([]byte, error) {
|
||||||
f, err := os.Open(filePath)
|
f, err := os.Open(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -220,21 +233,19 @@ func readFileIgnoreBOM(filePath string) ([]byte, error) {
|
|||||||
func getBranchFieldNumber(sqlStr string) int {
|
func getBranchFieldNumber(sqlStr string) int {
|
||||||
lines := strings.Split(sqlStr, "\n")
|
lines := strings.Split(sqlStr, "\n")
|
||||||
if len(lines) == 0 {
|
if len(lines) == 0 {
|
||||||
return 1
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
line := lines[0]
|
line := lines[0]
|
||||||
|
|
||||||
if !strings.HasPrefix(line, "-- ") {
|
if !strings.HasPrefix(line, "-- ") {
|
||||||
slog.Warn("Не указан номер колонки для вывода филиала, будет перезаписан первый столбец!")
|
return 0
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
line = strings.TrimPrefix(line, "-- ")
|
line = strings.TrimPrefix(line, "-- ")
|
||||||
fieldNum, err := strconv.Atoi(line)
|
fieldNum, err := strconv.Atoi(line)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Warn("Неверно указан номер колонки для вывода филиала, будет перезаписан первый столбец!")
|
return 0
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return fieldNum
|
return fieldNum
|
||||||
|
197
main.go
197
main.go
@ -1,201 +1,10 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log/slog"
|
_ "github.com/ying32/govcl/pkgs/winappres"
|
||||||
"os"
|
"github.com/ying32/govcl/vcl"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
|
||||||
"github.com/rivo/tview"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
|
||||||
logger := slog.New(&Handler{os.Stderr, slog.LevelInfo})
|
|
||||||
slog.SetDefault(logger)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
renderUI()
|
vcl.RunApp(&mainForm)
|
||||||
}
|
|
||||||
|
|
||||||
func renderUI() {
|
|
||||||
// SQL-files list
|
|
||||||
files, _ := filepath.Glob(filepath.Join(SQL_FILES_DIR, "*.sql"))
|
|
||||||
for i := range files {
|
|
||||||
files[i] = filepath.Base(files[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Servers file list
|
|
||||||
configs, _ := filepath.Glob("*.ini")
|
|
||||||
|
|
||||||
configFileDropDown := tview.NewDropDown().
|
|
||||||
SetOptions(configs, nil).
|
|
||||||
SetLabel("Файл настроек серверов").
|
|
||||||
SetCurrentOption(0)
|
|
||||||
sqlFileDropDown := tview.NewDropDown().
|
|
||||||
SetOptions(files, nil).
|
|
||||||
SetLabel("Файл SQL-скрипта").
|
|
||||||
SetCurrentOption(0)
|
|
||||||
encodingDropDown := tview.NewDropDown().
|
|
||||||
SetOptions([]string{string(EncodingWin1251), string(EncodingUtf8)}, nil).
|
|
||||||
SetLabel("Кодировка CSV-файла")
|
|
||||||
encodingDropDown.SetDisabled(true)
|
|
||||||
encodingDropDown.SetCurrentOption(0)
|
|
||||||
|
|
||||||
formatDropDown := tview.NewDropDown().
|
|
||||||
SetOptions([]string{string(ExportFormatExcel), string(ExportFormatCsv)}, func(text string, index int) {
|
|
||||||
if text == string(ExportFormatCsv) {
|
|
||||||
encodingDropDown.SetDisabled(false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
encodingDropDown.SetDisabled(true)
|
|
||||||
}).
|
|
||||||
SetLabel("Формат выгрузки").SetCurrentOption(0)
|
|
||||||
|
|
||||||
form := tview.NewForm().
|
|
||||||
AddFormItem(configFileDropDown).
|
|
||||||
AddFormItem(sqlFileDropDown).
|
|
||||||
AddFormItem(formatDropDown).
|
|
||||||
AddFormItem(encodingDropDown)
|
|
||||||
form.SetTitle("Параметры").SetBorder(true)
|
|
||||||
|
|
||||||
app := tview.NewApplication()
|
|
||||||
grid := tview.NewGrid().SetRows(-1, 1).
|
|
||||||
AddItem(form, 0, 0, 1, 1, 0, 0, true).
|
|
||||||
AddItem(tview.NewButton("Запуск").SetSelectedFunc(func() {
|
|
||||||
_, scriptFilePath := sqlFileDropDown.GetCurrentOption()
|
|
||||||
_, exportFileFormatStr := formatDropDown.GetCurrentOption()
|
|
||||||
_, configFilePath := configFileDropDown.GetCurrentOption()
|
|
||||||
_, encodingStr := encodingDropDown.GetCurrentOption()
|
|
||||||
|
|
||||||
job := &Job{
|
|
||||||
configFilePath: configFilePath,
|
|
||||||
scriptFilePath: filepath.Join(SQL_FILES_DIR, scriptFilePath),
|
|
||||||
exportFileFormat: ExportFormat(exportFileFormatStr),
|
|
||||||
encoding: Encoding(encodingStr)}
|
|
||||||
|
|
||||||
err := job.init()
|
|
||||||
if err != nil {
|
|
||||||
showError(app, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
renderProgressUI(app, job)
|
|
||||||
}), 1, 0, 1, 1, 0, 0, false)
|
|
||||||
grid.SetBorderPadding(0, 1, 1, 1)
|
|
||||||
|
|
||||||
form.SetTitle("Параметры выгрузки").SetTitleAlign(tview.AlignLeft)
|
|
||||||
if err := app.SetRoot(grid, true).EnableMouse(true).Run(); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func renderProgressUI(app *tview.Application, job *Job) {
|
|
||||||
statusTextViews := make([]*tview.TextView, 0)
|
|
||||||
maxServerNameLen := 0
|
|
||||||
for i := range job.servers {
|
|
||||||
if len(job.servers[i].Name) > maxServerNameLen {
|
|
||||||
maxServerNameLen = len([]rune(job.servers[i].Name)) + 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rows := make([]int, len(job.servers)+3)
|
|
||||||
for i := range rows {
|
|
||||||
rows[i] = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
grid := tview.NewGrid().SetRows(rows...).SetColumns(1, maxServerNameLen+2)
|
|
||||||
for i, server := range job.servers {
|
|
||||||
grid.AddItem(tview.NewTextView().SetTextAlign(tview.AlignLeft).SetText(server.Name), i+1, 1, 1, 1, 0, 0, false)
|
|
||||||
|
|
||||||
p := tview.NewTextView().SetTextAlign(tview.AlignLeft).SetText("...")
|
|
||||||
statusTextViews = append(statusTextViews, p)
|
|
||||||
grid.AddItem(p, i+1, 2, 1, 1, 0, 0, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
grid.AddItem(tview.NewTextView().SetTextAlign(tview.AlignLeft).SetText(strings.Repeat("-", maxServerNameLen)), len(job.servers)+1, 1, 1, 1, 0, 0, false)
|
|
||||||
grid.AddItem(tview.NewTextView().SetTextAlign(tview.AlignLeft), len(job.servers)+1, 2, 1, 1, 0, 0, false)
|
|
||||||
|
|
||||||
statusTextView := tview.NewTextView()
|
|
||||||
grid.AddItem(statusTextView, len(job.servers)+2, 2, 1, 1, 0, 0, false)
|
|
||||||
|
|
||||||
// Last empty line
|
|
||||||
grid.AddItem(tview.NewTextView(), len(job.servers)+3, 1, 1, 1, 0, 0, false)
|
|
||||||
grid.AddItem(tview.NewTextView(), len(job.servers)+3, 2, 1, 1, 0, 0, false)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
oneMoreUpdate := false
|
|
||||||
for !job.isFinished || oneMoreUpdate {
|
|
||||||
if !oneMoreUpdate {
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, server := range job.servers {
|
|
||||||
s := []string{server.Status}
|
|
||||||
if server.Error != nil {
|
|
||||||
s = append(s, server.Error.Error())
|
|
||||||
}
|
|
||||||
statusTextViews[i].SetText(strings.Join(s, ": "))
|
|
||||||
|
|
||||||
if server.Error != nil {
|
|
||||||
statusTextViews[i].SetTextColor(tcell.ColorRed)
|
|
||||||
} else if strings.HasPrefix(server.Status, "ЗАВЕРШЕНО: ") {
|
|
||||||
statusTextViews[i].SetTextColor(tcell.ColorGreen)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
statusTextView.SetText(job.status)
|
|
||||||
if strings.HasPrefix(job.status, "ЗАВЕРШЕНО") {
|
|
||||||
statusTextView.SetTextColor(tcell.ColorGreen)
|
|
||||||
}
|
|
||||||
|
|
||||||
app.ForceDraw()
|
|
||||||
if job.isFinished {
|
|
||||||
if !oneMoreUpdate {
|
|
||||||
oneMoreUpdate = true
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// TODO:
|
|
||||||
/*finishButton := tview.NewButton("OK").SetSelectedFunc(func() { app.Stop() }).SetBackgroundColor(tcell.ColorDarkGreen)
|
|
||||||
grid.AddItem(finishButton, len(job.servers)+2, 1, 1, 1, 0, 0, false)
|
|
||||||
app.ForceDraw().SetFocus(finishButton)*/
|
|
||||||
showMessage(app, "Выгрузка завершена.")
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
err := job.launch()
|
|
||||||
if err != nil {
|
|
||||||
showError(app, err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err := app.SetRoot(grid, true).SetFocus(grid).Run(); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func showError(app *tview.Application, err error) {
|
|
||||||
modal := tview.NewModal().
|
|
||||||
SetText(err.Error()).
|
|
||||||
AddButtons([]string{"OK"}).
|
|
||||||
SetBackgroundColor(tcell.ColorRed).
|
|
||||||
SetDoneFunc(func(buttonIndex int, buttonLabel string) {
|
|
||||||
os.Exit(1)
|
|
||||||
})
|
|
||||||
app.SetRoot(modal, true).SetFocus(modal)
|
|
||||||
}
|
|
||||||
|
|
||||||
func showMessage(app *tview.Application, text string) {
|
|
||||||
modal := tview.NewModal().
|
|
||||||
SetText(text).
|
|
||||||
AddButtons([]string{"OK"}).
|
|
||||||
SetDoneFunc(func(buttonIndex int, buttonLabel string) {
|
|
||||||
os.Exit(0)
|
|
||||||
})
|
|
||||||
app.SetRoot(modal, true).SetFocus(modal).ForceDraw()
|
|
||||||
}
|
}
|
||||||
|
2
make.bat
2
make.bat
@ -1 +1 @@
|
|||||||
go build -trimpath -buildmode=pie -ldflags "-s -w"
|
go build -trimpath -buildmode=pie -ldflags "-s -w -H=windowsgui"
|
||||||
|
68
servers.go
68
servers.go
@ -1,68 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
go_ora "github.com/sijms/go-ora/v2"
|
|
||||||
"gopkg.in/ini.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Server - экземпляр сервера
|
|
||||||
type Server struct {
|
|
||||||
// Полная ссылка на БД, вкючая логин/пароль
|
|
||||||
Url string
|
|
||||||
|
|
||||||
// Наименование филиала
|
|
||||||
Name string
|
|
||||||
|
|
||||||
// Статус работы с сервером
|
|
||||||
Status string
|
|
||||||
|
|
||||||
// Ошибка работы с сервером
|
|
||||||
Error error
|
|
||||||
}
|
|
||||||
|
|
||||||
// loadConfig считывает конфиг и возвращает список параметров серверов
|
|
||||||
func loadConfig(filePath string) ([]Server, error) {
|
|
||||||
servers := make([]Server, 0)
|
|
||||||
|
|
||||||
iniBytes, err := readFileIgnoreBOM(filePath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg, err := ini.Load(iniBytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg.DeleteSection("DEFAULT")
|
|
||||||
|
|
||||||
for _, server := range cfg.SectionStrings() {
|
|
||||||
loginKey, err := cfg.Section(server).GetKey("Login")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
passwordKey, err := cfg.Section(server).GetKey("Password")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
nameKey, err := cfg.Section(server).GetKey("Name")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
serv := strings.Split(server, "/")[0]
|
|
||||||
service := strings.Split(server, "/")[1]
|
|
||||||
dbUrl := go_ora.BuildUrl(serv, 1521, service, loginKey.String(), passwordKey.String(), map[string]string{"TIMEOUT": "0", "PREFETCH_ROWS": "1000"})
|
|
||||||
|
|
||||||
server := Server{
|
|
||||||
Url: dbUrl,
|
|
||||||
Name: nameKey.String()}
|
|
||||||
|
|
||||||
servers = append(servers, server)
|
|
||||||
}
|
|
||||||
|
|
||||||
return servers, nil
|
|
||||||
}
|
|
97
slog.go
97
slog.go
@ -1,97 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log/slog"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Мьютекс для предотвращения наложения строк из горутин
|
|
||||||
var mu = new(sync.Mutex)
|
|
||||||
|
|
||||||
type Handler struct {
|
|
||||||
w io.Writer
|
|
||||||
level slog.Level
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Handler) Enabled(c context.Context, l slog.Level) bool {
|
|
||||||
return l >= h.level
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Handler) Handle(c context.Context, r slog.Record) error {
|
|
||||||
mu.Lock()
|
|
||||||
defer mu.Unlock()
|
|
||||||
|
|
||||||
switch r.Level {
|
|
||||||
case slog.LevelError:
|
|
||||||
fmt.Fprintf(h.w, "• ОШИБКА: %v\n", r.Message)
|
|
||||||
|
|
||||||
r.Attrs(func(a slog.Attr) bool {
|
|
||||||
s := fmt.Sprintf(" %v=%v\n", a.Key, a.Value)
|
|
||||||
fmt.Fprint(h.w, s)
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
default:
|
|
||||||
fmt.Fprintf(h.w, "• %v ", r.Message)
|
|
||||||
|
|
||||||
r.Attrs(func(a slog.Attr) bool {
|
|
||||||
s := fmt.Sprintf("%v=%v ", a.Key, a.Value)
|
|
||||||
fmt.Fprint(h.w, s)
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
fmt.Fprint(h.w, "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/*func (h *Handler) HandleColor(c context.Context, r slog.Record) error {
|
|
||||||
var yellow = color.New(color.FgYellow).SprintFunc()
|
|
||||||
|
|
||||||
switch r.Level {
|
|
||||||
case slog.LevelError:
|
|
||||||
redHi := color.New(color.FgHiRed).SprintFunc()
|
|
||||||
fmt.Fprintf(h.w, redHi("• %v\n"), r.Message)
|
|
||||||
|
|
||||||
red := color.New(color.FgRed).SprintFunc()
|
|
||||||
r.Attrs(func(a slog.Attr) bool {
|
|
||||||
s := fmt.Sprintf(" %v=%v\n", red(a.Key), a.Value)
|
|
||||||
fmt.Fprint(h.w, s)
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
case slog.LevelWarn:
|
|
||||||
fmt.Fprintf(h.w, yellow("• %v\n"), r.Message)
|
|
||||||
|
|
||||||
red := color.New(color.FgRed).SprintFunc()
|
|
||||||
r.Attrs(func(a slog.Attr) bool {
|
|
||||||
s := fmt.Sprintf(" %v=%v\n", red(a.Key), yellow(a.Value))
|
|
||||||
fmt.Fprint(h.w, s)
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
default:
|
|
||||||
fmt.Fprintf(h.w, "• %v ", r.Message)
|
|
||||||
|
|
||||||
r.Attrs(func(a slog.Attr) bool {
|
|
||||||
s := fmt.Sprintf("%v=%v ", yellow(a.Key), a.Value)
|
|
||||||
fmt.Fprint(h.w, s)
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
fmt.Fprint(h.w, "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}*/
|
|
||||||
|
|
||||||
func (h *Handler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Handler) WithGroup(name string) slog.Handler {
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Handler) SetLevel(level slog.Level) {
|
|
||||||
h.level = level
|
|
||||||
}
|
|
80
ui_config_editior.go
Normal file
80
ui_config_editior.go
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ying32/govcl/vcl"
|
||||||
|
"github.com/ying32/govcl/vcl/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TConfigEditorForm struct {
|
||||||
|
*vcl.TForm
|
||||||
|
|
||||||
|
loginLabel *vcl.TLabel
|
||||||
|
passwordLabel *vcl.TLabel
|
||||||
|
|
||||||
|
loginEdit *vcl.TEdit
|
||||||
|
passwordEdit *vcl.TEdit
|
||||||
|
|
||||||
|
buttonPanel *vcl.TPanel
|
||||||
|
confirmButton *vcl.TButton
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TConfigEditorForm) OnFormCreate(sender vcl.IObject) {
|
||||||
|
f.SetWidth(640)
|
||||||
|
f.SetHeight(160)
|
||||||
|
f.Constraints().SetMinHeight(160) // to prevent wrong ordering of widgets for small windows sizes
|
||||||
|
f.SetPosition(types.PoOwnerFormCenter)
|
||||||
|
f.SetCaption("Настройки серверов")
|
||||||
|
f.SetDoubleBuffered(true)
|
||||||
|
f.SetBorderWidth(8)
|
||||||
|
|
||||||
|
f.loginLabel = vcl.NewLabel(f)
|
||||||
|
f.loginLabel.SetParent(f)
|
||||||
|
f.loginLabel.SetCaption("Логин:")
|
||||||
|
f.loginLabel.SetAlign(types.AlTop)
|
||||||
|
f.loginLabel.SetTop(0)
|
||||||
|
|
||||||
|
f.loginEdit = vcl.NewEdit(f)
|
||||||
|
f.loginEdit.SetParent(f)
|
||||||
|
f.loginEdit.SetAlign(types.AlTop)
|
||||||
|
f.loginEdit.SetTop(100)
|
||||||
|
|
||||||
|
f.passwordLabel = vcl.NewLabel(f)
|
||||||
|
f.passwordLabel.SetParent(f)
|
||||||
|
f.passwordLabel.SetCaption("Пароль:")
|
||||||
|
f.passwordLabel.SetAlign(types.AlTop)
|
||||||
|
f.passwordLabel.SetTop(200)
|
||||||
|
f.passwordLabel.BorderSpacing().SetTop(8)
|
||||||
|
|
||||||
|
f.passwordEdit = vcl.NewEdit(f)
|
||||||
|
f.passwordEdit.SetParent(f)
|
||||||
|
f.passwordEdit.SetAlign(types.AlTop)
|
||||||
|
f.passwordEdit.SetPasswordChar(uint16('*'))
|
||||||
|
f.passwordEdit.SetTop(300)
|
||||||
|
|
||||||
|
f.buttonPanel = vcl.NewPanel(f)
|
||||||
|
f.buttonPanel.SetParent(f)
|
||||||
|
f.buttonPanel.SetAlign(types.AlBottom)
|
||||||
|
f.buttonPanel.SetHeight(32)
|
||||||
|
f.buttonPanel.SetBevelOuter(types.BvNone)
|
||||||
|
|
||||||
|
f.confirmButton = vcl.NewButton(f)
|
||||||
|
f.confirmButton.SetParent(f.buttonPanel)
|
||||||
|
f.confirmButton.SetAlign(types.AlRight)
|
||||||
|
f.confirmButton.SetWidth(96)
|
||||||
|
f.confirmButton.SetCaption("Сохранить")
|
||||||
|
f.confirmButton.SetOnClick(f.OnConfirmButtonClick)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TConfigEditorForm) OnConfirmButtonClick(sender vcl.IObject) {
|
||||||
|
if f.loginEdit.Text() == "" {
|
||||||
|
vcl.MessageDlg("Логин не может быть пустым", types.MtError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.passwordEdit.Text() == "" {
|
||||||
|
vcl.MessageDlg("Пароль не может быть пустым", types.MtError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f.SetModalResult(types.IdOK)
|
||||||
|
}
|
393
ui_main_form.go
Normal file
393
ui_main_form.go
Normal file
@ -0,0 +1,393 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/BurntSushi/toml"
|
||||||
|
|
||||||
|
"github.com/ying32/govcl/vcl"
|
||||||
|
"github.com/ying32/govcl/vcl/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TMainForm struct {
|
||||||
|
*vcl.TForm
|
||||||
|
|
||||||
|
MainMenu *vcl.TMainMenu
|
||||||
|
ChangePasswordMenuItem *vcl.TMenuItem
|
||||||
|
AboutMenuItem *vcl.TMenuItem
|
||||||
|
|
||||||
|
PageControl *vcl.TPageControl
|
||||||
|
|
||||||
|
TabSheet1 *vcl.TTabSheet
|
||||||
|
GroupBox *vcl.TGroupBox
|
||||||
|
ConfigLabel *vcl.TLabel
|
||||||
|
ConfigComboBox *vcl.TComboBox
|
||||||
|
SqlFileLabel *vcl.TLabel
|
||||||
|
SqlFileComboBox *vcl.TComboBox
|
||||||
|
ExportFormatLabel *vcl.TLabel
|
||||||
|
ExportFormatComboBox *vcl.TComboBox
|
||||||
|
CharsetLabel *vcl.TLabel
|
||||||
|
CharsetComboBox *vcl.TComboBox
|
||||||
|
ExportPathLabel *vcl.TLabel
|
||||||
|
ExportPathPicker *vcl.TDirectoryEdit
|
||||||
|
BottomPanel *vcl.TPanel
|
||||||
|
LaunchButton *vcl.TBitBtn
|
||||||
|
|
||||||
|
TabSheet2 *vcl.TTabSheet
|
||||||
|
ServerListView *vcl.TListView
|
||||||
|
c1 *vcl.TListColumn
|
||||||
|
c2 *vcl.TListColumn
|
||||||
|
|
||||||
|
StatusBar *vcl.TStatusBar
|
||||||
|
p1 *vcl.TStatusPanel
|
||||||
|
p2 *vcl.TStatusPanel
|
||||||
|
}
|
||||||
|
|
||||||
|
var mainForm *TMainForm
|
||||||
|
|
||||||
|
func (f *TMainForm) OnFormCreate(sender vcl.IObject) {
|
||||||
|
f.SetWidth(800)
|
||||||
|
f.SetHeight(400)
|
||||||
|
f.Constraints().SetMinHeight(400) // to prevent wrong ordering of widgets for small windows sizes
|
||||||
|
f.SetPosition(types.PoDesktopCenter)
|
||||||
|
f.SetCaption("OMQ")
|
||||||
|
f.SetDoubleBuffered(true)
|
||||||
|
|
||||||
|
f.MainMenu = vcl.NewMainMenu(f)
|
||||||
|
f.ChangePasswordMenuItem = vcl.NewMenuItem(f)
|
||||||
|
f.MainMenu.Items().Add(f.ChangePasswordMenuItem)
|
||||||
|
f.ChangePasswordMenuItem.SetCaption("Изменить логин/пароль")
|
||||||
|
f.ChangePasswordMenuItem.SetBitmap(getImageBitmap("img/change_password.png"))
|
||||||
|
f.ChangePasswordMenuItem.SetOnClick(f.changePassword)
|
||||||
|
f.AboutMenuItem = vcl.NewMenuItem(f)
|
||||||
|
f.MainMenu.Items().Add(f.AboutMenuItem)
|
||||||
|
f.AboutMenuItem.SetCaption("О программе")
|
||||||
|
f.AboutMenuItem.SetBitmap(getImageBitmap("img/information.png"))
|
||||||
|
f.AboutMenuItem.SetOnClick(func(sender vcl.IObject) {
|
||||||
|
vcl.MessageDlg("Программа выгрузки результатов SQL-запросов с нескольких серверов баз данных Oracle.", types.MtInformation)
|
||||||
|
})
|
||||||
|
|
||||||
|
f.PageControl = vcl.NewPageControl(f)
|
||||||
|
f.PageControl.SetParent(f)
|
||||||
|
f.PageControl.SetAlign(types.AlClient)
|
||||||
|
|
||||||
|
f.TabSheet1 = vcl.NewTabSheet(f.PageControl)
|
||||||
|
f.TabSheet1.SetParent(f.PageControl)
|
||||||
|
f.TabSheet1.SetTabVisible(false)
|
||||||
|
|
||||||
|
f.GroupBox = vcl.NewGroupBox(f)
|
||||||
|
f.GroupBox.SetParent(f.TabSheet1)
|
||||||
|
f.GroupBox.SetAlign(types.AlClient)
|
||||||
|
f.GroupBox.SetCaption("Параметры запроса")
|
||||||
|
// TODO: добавить AutoSize
|
||||||
|
|
||||||
|
f.ConfigLabel = vcl.NewLabel(f)
|
||||||
|
f.ConfigLabel.SetParent(f.GroupBox)
|
||||||
|
f.ConfigLabel.SetCaption("Настройки серверов")
|
||||||
|
f.ConfigLabel.SetTop(0)
|
||||||
|
f.ConfigLabel.BorderSpacing().SetTop(8)
|
||||||
|
f.ConfigLabel.BorderSpacing().SetLeft(8)
|
||||||
|
f.ConfigLabel.BorderSpacing().SetRight(8)
|
||||||
|
f.ConfigLabel.SetAlign(types.AlTop)
|
||||||
|
|
||||||
|
f.ConfigComboBox = vcl.NewComboBox(f)
|
||||||
|
f.ConfigComboBox.SetParent(f.GroupBox)
|
||||||
|
f.ConfigComboBox.SetAlign(types.AlTop)
|
||||||
|
f.ConfigComboBox.BorderSpacing().SetTop(2)
|
||||||
|
f.ConfigComboBox.BorderSpacing().SetLeft(8)
|
||||||
|
f.ConfigComboBox.BorderSpacing().SetRight(8)
|
||||||
|
f.ConfigComboBox.SetTop(100)
|
||||||
|
f.ConfigComboBox.SetStyle(types.CsDropDownList)
|
||||||
|
|
||||||
|
f.SqlFileLabel = vcl.NewLabel(f)
|
||||||
|
f.SqlFileLabel.SetParent(f.GroupBox)
|
||||||
|
f.SqlFileLabel.SetCaption("SQL-скрипт")
|
||||||
|
f.SqlFileLabel.SetAlign(types.AlTop)
|
||||||
|
f.SqlFileLabel.BorderSpacing().SetTop(8)
|
||||||
|
f.SqlFileLabel.SetTop(200)
|
||||||
|
f.SqlFileLabel.BorderSpacing().SetLeft(8)
|
||||||
|
f.SqlFileLabel.BorderSpacing().SetRight(8)
|
||||||
|
|
||||||
|
f.SqlFileComboBox = vcl.NewComboBox(f)
|
||||||
|
f.SqlFileComboBox.SetParent(f.GroupBox)
|
||||||
|
f.SqlFileComboBox.SetAlign(types.AlTop)
|
||||||
|
f.SqlFileComboBox.BorderSpacing().SetTop(2)
|
||||||
|
f.SqlFileComboBox.BorderSpacing().SetLeft(8)
|
||||||
|
f.SqlFileComboBox.BorderSpacing().SetRight(8)
|
||||||
|
f.SqlFileComboBox.SetTop(300)
|
||||||
|
f.SqlFileComboBox.SetStyle(types.CsDropDownList)
|
||||||
|
|
||||||
|
f.ExportFormatLabel = vcl.NewLabel(f)
|
||||||
|
f.ExportFormatLabel.SetParent(f.GroupBox)
|
||||||
|
f.ExportFormatLabel.SetCaption("Формат выгрузки")
|
||||||
|
f.ExportFormatLabel.SetAlign(types.AlTop)
|
||||||
|
f.ExportFormatLabel.SetTop(400)
|
||||||
|
f.ExportFormatLabel.BorderSpacing().SetTop(8)
|
||||||
|
f.ExportFormatLabel.BorderSpacing().SetLeft(8)
|
||||||
|
f.ExportFormatLabel.BorderSpacing().SetRight(8)
|
||||||
|
|
||||||
|
f.ExportFormatComboBox = vcl.NewComboBox(f)
|
||||||
|
f.ExportFormatComboBox.SetParent(f.GroupBox)
|
||||||
|
f.ExportFormatComboBox.SetAlign(types.AlTop)
|
||||||
|
f.ExportFormatComboBox.BorderSpacing().SetTop(2)
|
||||||
|
f.ExportFormatComboBox.BorderSpacing().SetLeft(8)
|
||||||
|
f.ExportFormatComboBox.BorderSpacing().SetRight(8)
|
||||||
|
f.ExportFormatComboBox.SetTop(500)
|
||||||
|
f.ExportFormatComboBox.SetStyle(types.CsDropDownList)
|
||||||
|
f.ExportFormatComboBox.SetOnChange(f.OnExportFormatComboBoxChange)
|
||||||
|
|
||||||
|
f.CharsetLabel = vcl.NewLabel(f)
|
||||||
|
f.CharsetLabel.SetParent(f.GroupBox)
|
||||||
|
f.CharsetLabel.SetCaption("Кодировка CSV-файла")
|
||||||
|
f.CharsetLabel.SetAlign(types.AlTop)
|
||||||
|
f.CharsetLabel.SetTop(600)
|
||||||
|
f.CharsetLabel.BorderSpacing().SetTop(8)
|
||||||
|
f.CharsetLabel.BorderSpacing().SetLeft(8)
|
||||||
|
f.CharsetLabel.BorderSpacing().SetRight(8)
|
||||||
|
|
||||||
|
f.CharsetComboBox = vcl.NewComboBox(f)
|
||||||
|
f.CharsetComboBox.SetParent(f.GroupBox)
|
||||||
|
f.CharsetComboBox.SetAlign(types.AlTop)
|
||||||
|
f.CharsetComboBox.BorderSpacing().SetTop(2)
|
||||||
|
f.CharsetComboBox.BorderSpacing().SetLeft(8)
|
||||||
|
f.CharsetComboBox.BorderSpacing().SetRight(8)
|
||||||
|
f.CharsetComboBox.SetTop(700)
|
||||||
|
f.CharsetComboBox.SetStyle(types.CsDropDownList)
|
||||||
|
|
||||||
|
f.ExportPathLabel = vcl.NewLabel(f)
|
||||||
|
f.ExportPathLabel.SetParent(f.GroupBox)
|
||||||
|
f.ExportPathLabel.SetCaption("Путь сохранения результата")
|
||||||
|
f.ExportPathLabel.SetAlign(types.AlTop)
|
||||||
|
f.ExportPathLabel.SetTop(800)
|
||||||
|
f.ExportPathLabel.BorderSpacing().SetTop(8)
|
||||||
|
f.ExportPathLabel.BorderSpacing().SetLeft(8)
|
||||||
|
f.ExportPathLabel.BorderSpacing().SetRight(8)
|
||||||
|
|
||||||
|
f.ExportPathPicker = vcl.NewDirectoryEdit(f)
|
||||||
|
f.ExportPathPicker.SetParent(f.GroupBox)
|
||||||
|
f.ExportPathPicker.SetAlign(types.AlTop)
|
||||||
|
f.ExportPathPicker.BorderSpacing().SetTop(2)
|
||||||
|
f.ExportPathPicker.BorderSpacing().SetLeft(8)
|
||||||
|
f.ExportPathPicker.BorderSpacing().SetRight(8)
|
||||||
|
f.ExportPathPicker.SetTop(900)
|
||||||
|
f.ExportPathPicker.SetFlat(true)
|
||||||
|
|
||||||
|
f.BottomPanel = vcl.NewPanel(f)
|
||||||
|
f.BottomPanel.SetParent(f.TabSheet1)
|
||||||
|
f.BottomPanel.SetAlign(types.AlBottom)
|
||||||
|
f.BottomPanel.SetHeight(48)
|
||||||
|
f.BottomPanel.SetBevelOuter(types.BvNone)
|
||||||
|
|
||||||
|
f.LaunchButton = vcl.NewBitBtn(f)
|
||||||
|
f.LaunchButton.SetParent(f.BottomPanel)
|
||||||
|
f.LaunchButton.SetCaption("Запуск")
|
||||||
|
f.LaunchButton.SetAlign(types.AlRight)
|
||||||
|
f.LaunchButton.SetWidth(96)
|
||||||
|
f.LaunchButton.BorderSpacing().SetAround(8)
|
||||||
|
f.LaunchButton.SetOnClick(f.OnLaunchButtonClick)
|
||||||
|
f.LaunchButton.SetGlyph(getImageBitmap("img/bullet_go.png"))
|
||||||
|
|
||||||
|
f.TabSheet2 = vcl.NewTabSheet(f.PageControl)
|
||||||
|
f.TabSheet2.SetParent(f.PageControl)
|
||||||
|
f.TabSheet2.SetTabVisible(false)
|
||||||
|
|
||||||
|
f.ServerListView = vcl.NewListView(f)
|
||||||
|
f.ServerListView.SetParent(f.TabSheet2)
|
||||||
|
f.ServerListView.SetAlign(types.AlClient)
|
||||||
|
f.ServerListView.SetViewStyle(types.VsReport)
|
||||||
|
f.ServerListView.SetBorderStyle(types.BsNone)
|
||||||
|
f.ServerListView.SetReadOnly(true)
|
||||||
|
f.ServerListView.SetRowSelect(true)
|
||||||
|
|
||||||
|
f.c1 = f.ServerListView.Columns().Add()
|
||||||
|
f.c1.SetCaption("Сервер")
|
||||||
|
f.c1.SetAutoSize(true)
|
||||||
|
|
||||||
|
f.c2 = f.ServerListView.Columns().Add()
|
||||||
|
f.c2.SetCaption("Статус")
|
||||||
|
|
||||||
|
f.StatusBar = vcl.NewStatusBar(f)
|
||||||
|
f.StatusBar.SetParent(f)
|
||||||
|
f.StatusBar.SetSimplePanel(false)
|
||||||
|
f.p1 = f.StatusBar.Panels().Add()
|
||||||
|
f.p2 = f.StatusBar.Panels().Add()
|
||||||
|
|
||||||
|
f.SetOnResize(f.OnFormResize)
|
||||||
|
|
||||||
|
// Servers file list -------------------------------------------------------
|
||||||
|
configs, _ := filepath.Glob(filepath.Join(CONFIG_FILES_DIR, fmt.Sprintf("*.%s", CONFIG_FILE_EXT)))
|
||||||
|
for i := range configs {
|
||||||
|
configs[i] = strings.TrimSuffix(filepath.Base(configs[i]), "."+CONFIG_FILE_EXT)
|
||||||
|
}
|
||||||
|
for _, v := range configs {
|
||||||
|
f.ConfigComboBox.Items().Add(v)
|
||||||
|
}
|
||||||
|
if len(configs) > 0 {
|
||||||
|
f.ConfigComboBox.SetItemIndex(0)
|
||||||
|
}
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// SQL-files list ----------------------------------------------------------
|
||||||
|
files, _ := filepath.Glob(filepath.Join(SQL_FILES_DIR, fmt.Sprintf("*.%s", SQL_FILE_EXT)))
|
||||||
|
for i := range files {
|
||||||
|
files[i] = strings.TrimSuffix(filepath.Base(files[i]), "."+SQL_FILE_EXT)
|
||||||
|
}
|
||||||
|
for _, v := range files {
|
||||||
|
f.SqlFileComboBox.Items().Add(v)
|
||||||
|
}
|
||||||
|
if len(configs) > 0 {
|
||||||
|
f.SqlFileComboBox.SetItemIndex(0)
|
||||||
|
}
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// File formats ------------------------------------------------------------
|
||||||
|
for _, v := range []string{string(ExportFormatExcel), string(ExportFormatCsv), string(ExportFormatCsvZip), string(ExportFormatCsvZst)} {
|
||||||
|
f.ExportFormatComboBox.Items().Add(v)
|
||||||
|
}
|
||||||
|
f.ExportFormatComboBox.SetItemIndex(0)
|
||||||
|
|
||||||
|
// Charsets ----------------------------------------------------------------
|
||||||
|
for _, v := range []string{string(EncodingUtf8), string(EncodingWin1251)} {
|
||||||
|
f.CharsetComboBox.Items().Add(v)
|
||||||
|
}
|
||||||
|
f.CharsetComboBox.SetItemIndex(0)
|
||||||
|
f.OnExportFormatComboBoxChange(nil)
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TMainForm) OnFormResize(sender vcl.IObject) {
|
||||||
|
if f.Width() < 320 {
|
||||||
|
f.StatusBar.Panels().Items(0).SetWidth(f.Width() / 2)
|
||||||
|
} else {
|
||||||
|
f.StatusBar.Panels().Items(0).SetWidth(f.Width() - 160)
|
||||||
|
}
|
||||||
|
|
||||||
|
f.ServerListView.Columns().Items(1).SetWidth(
|
||||||
|
f.ServerListView.Width() - f.ServerListView.Columns().Items(0).Width())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TMainForm) OnLaunchButtonClick(sender vcl.IObject) {
|
||||||
|
f.MainMenu.Free()
|
||||||
|
|
||||||
|
job := &Job{
|
||||||
|
configFilePath: filepath.Join(CONFIG_FILES_DIR, f.ConfigComboBox.Text()) + "." + CONFIG_FILE_EXT,
|
||||||
|
scriptFilePath: filepath.Join(SQL_FILES_DIR, f.SqlFileComboBox.Text()) + "." + SQL_FILE_EXT,
|
||||||
|
exportFileFormat: ExportFormat(f.ExportFormatComboBox.Text()),
|
||||||
|
encoding: Encoding(f.CharsetComboBox.Text()),
|
||||||
|
exportPath: f.ExportPathPicker.Directory()}
|
||||||
|
|
||||||
|
err := job.init()
|
||||||
|
if err != nil {
|
||||||
|
vcl.MessageDlg(err.Error(), types.MtError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, server := range job.config.Servers {
|
||||||
|
item := f.ServerListView.Items().Add()
|
||||||
|
item.SetCaption(server.Name)
|
||||||
|
|
||||||
|
item.SubItems().Add("")
|
||||||
|
}
|
||||||
|
|
||||||
|
f.PageControl.SetPageIndex(1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for !job.isFinished {
|
||||||
|
f.UpdateStatus(job)
|
||||||
|
|
||||||
|
time.Sleep(UI_UPDATE_PERIOD)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
err = job.launch()
|
||||||
|
f.UpdateStatus(job)
|
||||||
|
if err != nil {
|
||||||
|
vcl.ThreadSync(func() {
|
||||||
|
vcl.MessageDlg(err.Error(), types.MtError)
|
||||||
|
f.Close()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
vcl.ThreadSync(func() {
|
||||||
|
vcl.MessageDlg("Завершено.", types.MtInformation)
|
||||||
|
f.Close()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TMainForm) OnExportFormatComboBoxChange(sender vcl.IObject) {
|
||||||
|
if slices.Contains([]string{string(ExportFormatCsv), string(ExportFormatCsvZip), string(ExportFormatCsvZst)}, f.ExportFormatComboBox.Text()) {
|
||||||
|
f.CharsetComboBox.SetEnabled(true)
|
||||||
|
} else {
|
||||||
|
f.CharsetComboBox.SetEnabled(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TMainForm) UpdateStatus(job *Job) {
|
||||||
|
vcl.ThreadSync(func() {
|
||||||
|
f.OnFormResize(nil)
|
||||||
|
|
||||||
|
f.StatusBar.Panels().Items(0).SetText(job.status)
|
||||||
|
f.StatusBar.Panels().Items(1).SetText(fmt.Sprintf("%d строк", job.exportedRows))
|
||||||
|
|
||||||
|
for i, v := range job.config.Servers {
|
||||||
|
s := []string{v.status}
|
||||||
|
if v.err != nil {
|
||||||
|
s = append(s, v.err.Error())
|
||||||
|
}
|
||||||
|
f.ServerListView.Items().Item(int32(i)).SubItems().SetText(strings.Join(s, ": "))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TMainForm) changePassword(sender vcl.IObject) {
|
||||||
|
changePasswordForm := new(TConfigEditorForm)
|
||||||
|
vcl.Application.CreateForm(&changePasswordForm)
|
||||||
|
if changePasswordForm.ShowModal() != types.IdOK {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newLogin := changePasswordForm.loginEdit.Text()
|
||||||
|
newPassword := changePasswordForm.passwordEdit.Text()
|
||||||
|
changePasswordForm.Free()
|
||||||
|
|
||||||
|
configFilePath := filepath.Join(CONFIG_FILES_DIR, f.ConfigComboBox.Text()) + "." + CONFIG_FILE_EXT
|
||||||
|
|
||||||
|
config, err := loadConfig(configFilePath)
|
||||||
|
if err != nil {
|
||||||
|
vcl.MessageDlg(err.Error(), types.MtError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, server := range config.Servers {
|
||||||
|
server.Login = newLogin
|
||||||
|
server.Password = newPassword
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Create(configFilePath)
|
||||||
|
if err != nil {
|
||||||
|
vcl.MessageDlg(err.Error(), types.MtError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = toml.NewEncoder(file).Encode(config)
|
||||||
|
if err != nil {
|
||||||
|
vcl.MessageDlg(err.Error(), types.MtError)
|
||||||
|
file.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = file.Close()
|
||||||
|
if err != nil {
|
||||||
|
vcl.MessageDlg(err.Error(), types.MtError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vcl.MessageDlg("Логин/пароль успешно изменены.", types.MtInformation)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user