2026-01-20 17:40:21 +05:00
|
|
|
|
package utils
|
|
|
|
|
|
|
2026-01-21 17:56:21 +05:00
|
|
|
|
import (
|
|
|
|
|
|
"bytes"
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
"github.com/sirupsen/logrus"
|
|
|
|
|
|
"html/template"
|
|
|
|
|
|
"io"
|
|
|
|
|
|
"net/http"
|
|
|
|
|
|
"os"
|
|
|
|
|
|
"os/exec"
|
|
|
|
|
|
"path/filepath"
|
|
|
|
|
|
"strings"
|
|
|
|
|
|
"time"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-01-20 17:40:21 +05:00
|
|
|
|
// CalculateSelectOptionsIndex рассчитывает индекс первой и последней опции для отображения в поле выбора
|
|
|
|
|
|
func CalculateSelectOptionsIndex(focus, visibleCount, optionsCount int) (int, int) {
|
|
|
|
|
|
//Если количество отображаемых больше, чем есть количество опций на самом деле,
|
|
|
|
|
|
//то меняем количество опций для отображения на количество опций.
|
|
|
|
|
|
if visibleCount > optionsCount {
|
|
|
|
|
|
visibleCount = optionsCount
|
|
|
|
|
|
}
|
|
|
|
|
|
//Количество объектов сверху и снизу
|
|
|
|
|
|
half := visibleCount / 2
|
|
|
|
|
|
//Индекс первой опции для показа в списке
|
|
|
|
|
|
start := focus - half
|
|
|
|
|
|
//Если индекс меньше 0, то фиксируем его на первой
|
|
|
|
|
|
if start < 0 {
|
|
|
|
|
|
start = 0
|
|
|
|
|
|
}
|
|
|
|
|
|
//Индекс последней опции для показа в списке
|
|
|
|
|
|
end := start + visibleCount
|
|
|
|
|
|
//Если индекс больше, чем количество опций для вывода,
|
|
|
|
|
|
//то фиксируем его на последней опции.
|
|
|
|
|
|
//Производим перерасчет индекса первой опции
|
|
|
|
|
|
if end > optionsCount {
|
|
|
|
|
|
end = optionsCount
|
|
|
|
|
|
start = end - visibleCount
|
|
|
|
|
|
if start < 0 {
|
|
|
|
|
|
start = 0
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return start, end
|
|
|
|
|
|
}
|
2026-01-21 17:56:21 +05:00
|
|
|
|
|
|
|
|
|
|
func RenderFileTemplate(source, target string, vars map[string]interface{}) error {
|
|
|
|
|
|
if source == "" || target == "" {
|
|
|
|
|
|
return fmt.Errorf("source или target пустые")
|
|
|
|
|
|
}
|
|
|
|
|
|
var data []byte
|
|
|
|
|
|
var err error
|
|
|
|
|
|
if strings.HasPrefix(source, "http") {
|
|
|
|
|
|
resp, err := http.Get(source)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("ошибка получения данных файла по адресу %s, err : %v", source, err)
|
|
|
|
|
|
}
|
|
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
data, err = io.ReadAll(resp.Body)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
data, err = os.ReadFile(source)
|
|
|
|
|
|
}
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("ошибка чтения данных файла %s, err: %v", source, err)
|
|
|
|
|
|
}
|
|
|
|
|
|
tmpl, err := template.New("").Parse(string(data))
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("ошибка формирования шаблона строки: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
var rendered bytes.Buffer
|
|
|
|
|
|
if err = tmpl.Execute(&rendered, vars); err != nil {
|
|
|
|
|
|
return fmt.Errorf("ошибка подстановки значений в шаблон")
|
|
|
|
|
|
}
|
|
|
|
|
|
if _, err = os.Stat(target); err == nil {
|
|
|
|
|
|
backup := target + ".bak" + time.Now().Format("20060102-150405")
|
|
|
|
|
|
if cpErr := CopyFile(target, backup); cpErr != nil {
|
|
|
|
|
|
logrus.Warnf("Не удалось создать бекап: %v", cpErr)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
logrus.Infof("Бекап %s создан", backup)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if err = os.MkdirAll(filepath.Dir(target), 0755); err != nil {
|
|
|
|
|
|
return fmt.Errorf("ошибка создания директории для файла %s, err: %v", target, err)
|
|
|
|
|
|
}
|
|
|
|
|
|
if err = os.WriteFile(target, rendered.Bytes(), 0644); err != nil {
|
|
|
|
|
|
return fmt.Errorf("ошибка записи в файл %s, err: %v", target, err)
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func RenderTemplate(tmpl string, data map[string]interface{}) (string, error) {
|
|
|
|
|
|
if tmpl == "" {
|
|
|
|
|
|
return "", nil
|
|
|
|
|
|
}
|
|
|
|
|
|
t, err := template.New("").Parse(tmpl)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return "", fmt.Errorf("ошибка формирования шаблона строки: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
var buf bytes.Buffer
|
|
|
|
|
|
if err = t.Execute(&buf, data); err != nil {
|
|
|
|
|
|
return "", fmt.Errorf("ошибка подстановки значений в шаблон: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
return buf.String(), nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func RunCommand(cmdStr, runAs string) error {
|
|
|
|
|
|
if cmdStr == "" {
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
var cmd *exec.Cmd
|
|
|
|
|
|
if runAs != "" && runAs != "root" {
|
|
|
|
|
|
cmd = exec.Command("su", "-c", cmdStr, runAs)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
cmd = exec.Command("bach", "-c", cmdStr)
|
|
|
|
|
|
}
|
|
|
|
|
|
output, err := cmd.CombinedOutput()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("ошибка команды: %v\nВывод: %s", err, string(output))
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// CopyFile копирование файла в новое место
|
|
|
|
|
|
func CopyFile(src, dst string) error {
|
|
|
|
|
|
in, err := os.Open(src)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
defer in.Close()
|
|
|
|
|
|
|
|
|
|
|
|
out, err := os.Create(dst)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
defer out.Close()
|
|
|
|
|
|
_, err = io.Copy(out, in)
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|