Изменение вызова действий, проверка наличия действий секции, отображение счетчика выполнения действий в модальном окне, работа над подключением привязки горячих клавиш

This commit is contained in:
XeRo 2026-01-23 17:52:47 +05:00
parent 91b1eea1d4
commit c69a7ae84b
14 changed files with 886 additions and 706 deletions

View file

@ -1,17 +1,18 @@
package utils
import (
"bytes"
"bubble/types"
"errors"
"fmt"
"github.com/sirupsen/logrus"
"html/template"
"github.com/expr-lang/expr"
"io"
"io/fs"
"net/http"
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
)
// CalculateSelectOptionsIndex рассчитывает индекс первой и последней опции для отображения в поле выбора
@ -44,91 +45,6 @@ func CalculateSelectOptionsIndex(focus, visibleCount, optionsCount int) (int, in
return start, end
}
// RenderFileTemplate создает целевой файл на основе файла источника с подставленными значениями в нем
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
}
// RenderTemplate формирует строку с подставленными параметрами
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
}
// RunCommand запускает выполнение переданной команды от указанного лица
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("bash", "-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)
@ -145,3 +61,247 @@ func CopyFile(src, dst string) error {
_, err = io.Copy(out, in)
return err
}
// ParamsForCompute получение словаря переменных без приведения типов, для вычисления
func ParamsForCompute(app *types.PackageApplication) map[string]interface{} {
params := make(map[string]interface{})
for i := range app.Variables {
v := &app.Variables[i]
value := v.Value
if value == nil {
value = v.Default
}
params[v.Name] = value
}
// Добавляем значения из активных полей
for _, section := range app.Sections {
for _, field := range section.Fields {
if field.Variable == nil || !field.Active {
continue
}
value := field.Variable.Value
if value == nil {
value = field.Variable.Default
}
params[field.VariableName] = value
}
}
return params
}
// ParamsForCondition получение словаря переменных с приведением типов для проверки условий
func ParamsForCondition(app *types.PackageApplication) map[string]interface{} {
params := make(map[string]interface{})
for _, variable := range app.Variables {
value := variable.Value
if value == nil {
value = variable.Default
}
// Приводим к нужному типу, как в твоём старом коде
switch variable.Type {
case "bool":
if s, ok := value.(string); ok {
value = strings.ToLower(s) == "true"
}
case "number", "int", "float":
if s, ok := value.(string); ok {
if n, err := strconv.ParseFloat(s, 64); err == nil {
value = n
}
}
}
params[variable.Name] = value
}
// Также добавляем значения из полей секций (на случай, если переменная используется только в поле)
for _, section := range app.Sections {
for _, field := range section.Fields {
if field.Variable == nil || !field.Active {
continue
}
value := field.Variable.Value
if value == nil {
value = field.Variable.Default
}
switch field.Variable.Type {
case "bool":
if s, ok := value.(string); ok {
value = strings.ToLower(s) == "true"
}
case "number", "int", "float":
if s, ok := value.(string); ok {
if n, err := strconv.ParseFloat(s, 64); err == nil {
value = n
}
}
}
params[field.VariableName] = value
}
}
return params
}
// DefineExprCustomFunctions возвращает собственные функции expr
func DefineExprCustomFunctions() []expr.Option {
return []expr.Option{
//вычисляет значение по заданному регулярному выражению
expr.Function("regex",
func(args ...interface{}) (interface{}, error) {
if len(args) != 3 {
return "", fmt.Errorf("regex() ожидает 3 аргумента")
}
input := fmt.Sprintf("%v", args[0])
pattern := fmt.Sprintf("%v", args[1])
repl := fmt.Sprintf("%v", args[2])
re, err := regexp.Compile(pattern)
if err != nil {
return input, nil
}
return re.ReplaceAllString(input, repl), nil
}, new(func(string, string, string) string),
),
//проверяет совпадение переданного значения и указанного регулярного выражения
expr.Function("regex_match",
func(args ...interface{}) (interface{}, error) {
if len(args) != 2 {
return false, fmt.Errorf("regex_match() ожидает 2 аргумента")
}
input := fmt.Sprintf("%v", args[0])
pattern := fmt.Sprintf("%v", args[1])
re, err := regexp.Compile(pattern)
if err != nil {
return false, fmt.Errorf("некоректный regex %s, err:%v", pattern, err)
}
return re.MatchString(input), nil
}, new(func(string, string) bool),
),
//возвращает собственный текст ошибки
expr.Function("error",
func(args ...interface{}) (interface{}, error) {
if len(args) == 0 {
return nil, fmt.Errorf("error() требует сообщения")
}
return nil, fmt.Errorf("%v", args[0])
},
new(func(string) error),
),
//возвращает тело ответа по указанному адресу
expr.Function("http_get",
func(args ...interface{}) (interface{}, error) {
if len(args) == 0 {
return "", nil
}
url := fmt.Sprintf("%v", args[0])
resp, err := http.Get(url)
if err != nil {
return "", err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
return string(body), nil
},
new(func(string) string),
),
//возвращает результат выполнения команды из оболочки
expr.Function("shell",
func(args ...interface{}) (interface{}, error) {
if len(args) == 0 {
return "", nil
}
cmd := fmt.Sprintf("%v", args[0])
parts := strings.Fields(cmd)
if len(parts) == 0 {
return "", nil
}
out, err := exec.Command(parts[0], parts[1:]...).CombinedOutput()
if err != nil {
return "", err
}
return strings.TrimSpace(string(out)), nil
},
new(func(string) error),
),
//возвращает значение указанной переменной из env
expr.Function("env",
func(args ...interface{}) (interface{}, error) {
if len(args) == 0 {
return "", nil
}
return os.Getenv(fmt.Sprintf("%v", args[0])), nil
}, new(func(string) error),
),
//проверяет наличие файла в системе
expr.Function("file_exists",
func(args ...interface{}) (interface{}, error) {
if len(args) == 0 {
return false, nil
}
path := fmt.Sprintf("%v", args[0])
fileInfo, err := os.Stat(path)
if err == nil && !fileInfo.IsDir() {
return true, nil
} else if err == nil && fileInfo.IsDir() {
return false, fmt.Errorf("file is dir")
}
if errors.Is(err, fs.ErrNotExist) {
return false, fmt.Errorf("no such directory or file")
} else {
return false, err
}
}, new(func(string) bool),
),
//проверяет наличие директории в системе
expr.Function("dir_exists",
func(args ...interface{}) (interface{}, error) {
if len(args) == 0 {
return false, nil
}
path := fmt.Sprintf("%v", args[0])
fileInfo, err := os.Stat(path)
if err == nil && fileInfo.IsDir() {
return true, nil
} else if err == nil && !fileInfo.IsDir() {
return false, fmt.Errorf("file is not a dir")
}
if errors.Is(err, fs.ErrNotExist) {
return false, fmt.Errorf("no such directory or file")
} else {
return false, err
}
}, new(func(string) bool),
),
//проверяет доступность порта
expr.Function("port_free",
func(args ...interface{}) (interface{}, error) {
if len(args) == 0 {
return false, nil
}
port := fmt.Sprintf("%v", args[0])
cmd := exec.Command("ss", "-ltn", "sport =: "+port)
return cmd.Run() != nil, nil
}, new(func(string) error),
),
//проверяет наличие пользователя в системе
expr.Function("user_exists",
func(args ...interface{}) (interface{}, error) {
if len(args) == 0 {
return false, nil
}
name := fmt.Sprintf("%v", args[0])
cmd := exec.Command("id", "-u", name)
return cmd.Run() == nil, nil
}, new(func(string) bool),
),
}
}