package main

import (
	"bufio"
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io"
	"log"
	"log/slog"
	"math"
	"os"
	"os/exec"
	"os/user"
	"path/filepath"
	"regexp"
	"runtime"
	"slices"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"
	"unicode"
	"unicode/utf8"

	"github.com/awesome-gocui/gocui"
	"golang.org/x/text/encoding/charmap"
	winUnicode "golang.org/x/text/encoding/unicode"
	"gopkg.in/yaml.v3"
)

var appVersion string = "0.8.6"

// Структура конфигурации
type Config struct {
	Settings  Settings  `yaml:"settings"`
	Hotkeys   Hotkeys   `yaml:"hotkeys"`
	Interface Interface `yaml:"interface"`
	Ssh       Ssh       `yaml:"ssh"`
}

// Структура доступных параметров для переопределения значений по умолчанию при запуске (#27)
type Settings struct {
	LoggingEnable       string `yaml:"loggingEnable"`
	LoggingPath         string `yaml:"loggingPath"`
	LoggingType         string `yaml:"loggingType"`
	TailModeDisable     string `yaml:"tailModeDisable"`
	TailModeLines       string `yaml:"tailModeLines"`
	UpdateInterval      string `yaml:"updateInterval"`
	MinSymbolFilter     string `yaml:"minSymbolFilter"`
	TimezoneFilter      string `yaml:"timezoneFilter"`
	MouseDisable        string `yaml:"mouseDisable"`
	WrapModeDisable     string `yaml:"wrapModeDisable"`
	DockerStreamOnly    string `yaml:"dockerStreamOnly"`
	DockerContext       string `yaml:"dockerContext"`
	PodmanContext       string `yaml:"podmanContext"`
	KubernetesContext   string `yaml:"kubernetesContext"`
	KubernetesNamespace string `yaml:"kubernetesNamespace"`
	UnitType            string `yaml:"unitType"`
	JournalField        string `yaml:"journalField"`
	JournalPriority     string `yaml:"journalPriority"`
	JournalBoot         string `yaml:"journalBoot"`
	CustomPath          string `yaml:"customPath"`
	ColorMode           string `yaml:"colorMode"`
	ColorActionsDisable string `yaml:"colorActionsDisable"`
	DisableFastMode     string `yaml:"disableFastMode"`
}

// Структура доступных параметров для настройки интерфейса (#37)
type Interface struct {
	SystemLogList           string `yaml:"systemLogList"`
	FileLogList             string `yaml:"fileLogList"`
	ContainerLogList        string `yaml:"containerLogList"`
	SinceDateFilterMode     string `yaml:"sinceDateFilterMode"`
	UntilDateFilterMode     string `yaml:"untilDateFilterMode"`
	ForegroundColor         string `yaml:"foregroundColor"`
	BackgroundColor         string `yaml:"backgroundColor"`
	SelectedForegroundColor string `yaml:"selectedForegroundColor"`
	SelectedBackgroundColor string `yaml:"selectedBackgroundColor"`
	FrameColor              string `yaml:"frameColor"`
	TitleColor              string `yaml:"titleColor"`
	SelectedFrameColor      string `yaml:"selectedFrameColor"`
	SelectedTitleColor      string `yaml:"selectedTitleColor"`
	ErrorColor              string `yaml:"errorColor"`
}

// Структура доступных сочетаний клавиш для переопределения (#23)
type Hotkeys struct {
	ShowHelp             string `yaml:"showHelp"`
	ShowManager          string `yaml:"showManager"`
	SwitchWindow         string `yaml:"switchWindow"`
	BackSwitchWindows    string `yaml:"backSwitchWindows"`
	Up                   string `yaml:"up"`
	QuickUp              string `yaml:"quickUp"`
	VeryQuickUp          string `yaml:"veryQuickUp"`
	SwitchFilterMode     string `yaml:"switchFilterMode"`
	Down                 string `yaml:"down"`
	QuickDown            string `yaml:"quickDown"`
	VeryQuickDown        string `yaml:"veryQuickDown"`
	BackSwitchFilterMode string `yaml:"backSwitchFilterMode"`
	Left                 string `yaml:"left"`
	Right                string `yaml:"right"`
	DisableFilterByDate  string `yaml:"disableFilterByDate"`
	LoadJournal          string `yaml:"loadJournal"`
	GoToFilter           string `yaml:"goToFilter"`
	GoToEnd              string `yaml:"goToEnd"`
	GoToTop              string `yaml:"goToTop"`
	TailModeMore         string `yaml:"tailModeMore"`
	TailModeLess         string `yaml:"tailModeLess"`
	UpdateIntervalMore   string `yaml:"updateIntervalMore"`
	UpdateIntervalLess   string `yaml:"updateIntervalLess"`
	AutoUpdateJournal    string `yaml:"autoUpdateJournal"`
	UpdateJournal        string `yaml:"updateJournal"`
	UpdateLists          string `yaml:"updateLists"`
	SwitchColorMode      string `yaml:"switchColorMode"`
	SwitchPriority       string `yaml:"switchPriority"`
	SwitchDockerMode     string `yaml:"switchDockerMode"`
	SwitchStreamMode     string `yaml:"switchStreamMode"`
	TimestampShow        string `yaml:"timestampShow"`
	Exit                 string `yaml:"exit"`
}

// Структура с содержимым массива ssh хостов с параметрами подключения
type Ssh struct {
	Hosts []string `yaml:"hosts"`
}

// Структура хранения информации о журналах
type Journal struct {
	name    string // название журнала (имя службы) или дата загрузки
	boot_id string // id загрузки системы
}

type Logfile struct {
	name string
	path string
}

type DockerContainers struct {
	name      string
	rawName   string
	id        string
	namespace string
}

// Структура для парсинга логов из docker cli
type dockerLogLines struct {
	isError   bool
	timestamp time.Time
	content   string
}

// Основная структура приложения (графический интерфейс и данные журналов)
type App struct {
	gui *gocui.Gui // графический интерфейс (gocui)

	foregroundColor         gocui.Attribute // цвет текста по умолчанию
	backgroundColor         gocui.Attribute // цвет фона интерфейса
	selectedForegroundColor gocui.Attribute // Цвет текста при выборе в списке
	selectedBackgroundColor gocui.Attribute // Цвет фона при выборе в списке
	frameColor              gocui.Attribute // цвет окон
	titleColor              gocui.Attribute // цвет заголовка окон
	selectedFrameColor      gocui.Attribute // цвет выбранного окна
	selectedTitleColor      gocui.Attribute // цвет заголовка выбранного окна
	errorColor              gocui.Attribute // цвет ошибок

	// Цвета окон по умолчанию (изменяется в зависимости от доступности журналов)
	journalListFrameColor gocui.Attribute
	fileSystemFrameColor  gocui.Attribute
	dockerFrameColor      gocui.Attribute

	sshMode                bool     // использовать вызов команд (exec.Command) через ssh
	sshStatus              string   // режим работы (false или имя хоста) для статуса
	sshOptions             []string // опции для ssh подключения
	fastMode               bool     // загрузка журналов в горутине (beta mode)
	testMode               bool     // исключаем вызовы к gocui при тестирование функций
	colorMode              string   // режим покраски (default/tailspin/bat/disable)
	colorActionsDisable    bool     // отключить покраску для действий
	mouseSupport           bool     // включение/отключение поддержки мыши
	wrapSupport            bool     // включение/отключение встроенного переноса строк в окне содержимого логов
	dockerStreamLogs       bool     // принудительное чтение журналов контейнеров Docker из потоков (по умолчанию, чтение происходит из файловой системы, если есть доступ)
	dockerStreamLogsStatus string   // отображаемый режим чтения журнала Docker в статусе (в зависимости от прав доступа и флага)
	dockerStreamMode       string   // переменная для хранения режима чтения потоков (stream, stdout или stderr)

	dockerContext             string
	podmanContext             string
	kubernetesContext         string
	kubernetesNamespace       string
	kubernetesNamespaceStatus string

	getOS         string   // название ОС
	getArch       string   // архитектура процессора
	hostName      string   // текущее имя хоста для покраски в логах
	userName      string   // текущее имя пользователя
	systemDisk    string   // порядковая буква системного диска для Windows
	userNameArray []string // список всех пользователей

	unitType        string // фильтрация списков системных и пользовательских юнитов по типу (#46)
	journalField    string // фильтрация списка системных журналов по полю
	journalPriority string // фильтрация вывода системных и пользовательских журналов по приоритету
	journalBoot     string // фильтрация вывода системных и пользовательских журналов по порядковому номеру загрузки системы
	customPath      string // пользовательский путь для поиска логов в файловой системе (#31)

	selectUnits                  string // название журнала (systemUnits/userUnits/systemJournals/kernelBoot/auditd)
	selectPath                   string // путь к логам (varlog/customPath/home/descriptor)
	selectContainerizationSystem string // название системы контейнеризации (docker/compose/podman/kubernetes)
	selectFilterMode             string // режим фильтрации (default/fuzzy/regex/timestamp)

	logViewCount     string   // количество логов для просмотра
	logUpdateSeconds int      // период фонового обновления журнала
	secondsChan      chan int // канал для изменения интервала обновления в горутине

	journals           []Journal // список (массив/срез) журналов для отображения
	maxVisibleServices int       // максимальное количество видимых элементов в окне списка служб
	startServices      int       // индекс первого видимого элемента
	selectedJournal    int       // индекс выбранного журнала

	logfiles        []Logfile
	maxVisibleFiles int
	startFiles      int
	selectedFile    int

	dockerContainers           []DockerContainers
	maxVisibleDockerContainers int
	startDockerContainers      int
	selectedDockerContainer    int

	// Фильтрация по дате
	timestampFilterView bool      // отображение окон
	sinceDateFilterMode bool      // использовать режим фильтрации для since
	untilDateFilterMode bool      // использовать режим фильтрации для until
	sinceFilterText     string    // начало отрезка времени
	sinceFilterDate     time.Time // начало отрезка времени в формате time для проверки
	untilFilterText     string    // конец отрезка времени
	untilFilterDate     time.Time // конец отрезка времени в формате time для проверки
	limitFilterDate     time.Time // предельное значение для проверки untilFilterDate
	filterByDateStatus  string    // текстовое значение режима работы фильтра для статуса
	timezoneFilter      string    // смещение UTC для фильтрации по дате

	// Текст для фильтрации список журналов
	filterListText string

	// Массивы для хранения списка журналов без фильтрации
	journalsNotFilter         []Journal
	logfilesNotFilter         []Logfile
	dockerContainersNotFilter []DockerContainers

	// Переменные для отслеживания изменений размера окна
	windowWidth  int
	windowHeight int

	minSymbolFilter  int      // минимальное кол-во символов дли фильтрации вывода
	filterText       string   // текст для фильтрации записей журнала
	currentLogLines  []string // набор строк (срез) для хранения журнала без фильтрации
	filteredLogLines []string // набор строк (срез) для хранения журнала после фильтра
	logScrollPos     int      // позиция прокрутки для отображаемых строк журнала
	lastFilterText   string   // фиксируем содержимое последнего ввода текста для фильтрации

	// Настройка логирования приложения
	logging     bool
	loggingFile *os.File
	loggingPath string
	loggingType string

	autoScroll        bool   // используется для автоматического скроллинга вниз при обновлении (если это не ручной скроллинг)
	disableAutoScroll bool   // отключение автоматического обновления вывода
	lastUpdateLine    string // фиксируем предпоследнюю строку для делимитра
	updateTime        string // фиксируем время загрузки журнала для делимитра

	lastDateUpdateFile time.Time // последняя дата изменения файла
	lastSizeFile       int64     // размер файла
	updateFile         bool      // проверка для обновления вывода в горутине (отключение только если нет изменений в файле и для Windows Event)

	lastWindow   string // фиксируем последний используемый источник для вывода логов
	lastSelected string // фиксируем название последнего выбранного журнала или контейнера

	// Переменные для хранения значений автообновления вывода при смене окна
	lastSelectUnits            string
	lastBootId                 string
	lastLogPath                string
	lastContainerizationSystem string
	lastContainerId            string

	// Фиксируем последнее время загрузки и покраски журнала
	debugStartTime time.Time
	debugLoadTime  string
	debugColorTime string

	// Отключение привязки горячих клавиш на время загрузки списка
	keybindingsEnabled bool

	// Отключение отображения встроенных временных меток (timestamp) для логов контейнеров Docker и Kubernetes
	timestampDocker bool
	// Отключение отображения типа потока (stdout/stderr) для логов Docker
	streamTypeDocker bool

	// Регулярные выражения для покраски строк
	trimHttpRegex      *regexp.Regexp
	trimHttpsRegex     *regexp.Regexp
	hexByteRegex       *regexp.Regexp
	dateTimeRegex      *regexp.Regexp
	integersInputRegex *regexp.Regexp
	syslogUnitRegex    *regexp.Regexp

	lastCurrentView   string // фиксируем последнее используемое окно для Esc после /
	backCurrentView   bool   // отключаем/ключаем возврат
	globalCurrentView string // хранение названия текущего окна после возврата из окна менеджера

	dockerCompose        string            // название используемого исполняемого файла docker-compose или как плагин "docker compose"
	uniquePrefixColorMap map[string]string // карта для хранения уникального цвета для каждого контейнера в стеках compose
}

// Описание флагов
var (
	helpDescription              = "Show help"
	versionDescription           = "Show version"
	configDescription            = "Show configuration of hotkeys and settings (check values)"
	auditDescription             = "Show audit information"
	loggingDescription           = "Enable logging of executed commands for debugging"
	tailModeDisableDescription   = "Disable streaming of new events (log is loaded once without update)"
	tailLinesDescription         = "Change the number of log lines to output (range: 200-200000, default: 10K)"
	updateIntervalDescription    = "Change the update interval of the log output (range: 2-10, default: 5)"
	minSymbolsFilterDescription  = "Minimum number of symbols for filtering output (range: 1-10, default: 3)"
	timezoneFilterDescription    = "UTC offset when filtering by date (default: +00:00)"
	mouseDisableDescription      = "Disable mouse control support"
	wrapDisableDescription       = "Disable wrap mode in log content"
	dockerStreamOnlyDescription  = "Force reading of Docker container logs in stream mode (by default from the file system)"
	dockerContextDescription     = "Use the specified Docker context (default: default)"
	podmanContextDescription     = "Use the specified Podman context (not used by default)"
	kubernetesContextDescription = "Use the specified Kubernetes context (default: default)"
	namespaceDescription         = "Use the specified Kubernetes namespace (default: all)"
	unitTypeDescription          = "Filter the list of user and system units by type, e.g. \"service,timer,scope,socket,mount\" (default: service)"
	journalFieldDescription      = "Filter the list of system journals by field, e.g. _UID/_PID/_COMM/_EXE/_CMDLINE (default: SYSLOG_IDENTIFIER)"
	journalPriorityDescription   = "Filter the log output by priority (available values: debug, info, notice, warning, err, crit, alert, emerg)"
	journalBootDescription       = "Filter the log output by system boot period, e.g. 0 current or -1 previous (default: all)"
	pathDescription              = "Custom the path in the file system to search for logs (\"/opt\" in Linux and \"$HOME/Documents\" in Windows by default)"
	colorModeDescription         = "Highlighting mode for logs (available values: default, tailspin, bat or disable)"
	commandColorDescription      = "ANSI coloring in command line mode"
	commandFuzzyDescription      = "Filtering using fuzzy search in command line mode"
	commandRegexDescription      = "Filtering using regular expression (regexp) in command line mode"
	sshDescription               = "Connect to a remote host using standard ssh options (e.g. lazyjournal --ssh \"root@192.168.3.100 -p 22\")"
)

// Help flag (-h/--help)
func showHelp() {
	fmt.Println("lazyjournal - A TUI for viewing logs from journald, auditd, file system, Docker and Podman containers,")
	fmt.Println("Compose stacks and Kubernetes pods with supports log highlighting and several filtering modes.")
	fmt.Println()
	fmt.Println("If you have problems with the application, please open issue: https://github.com/Lifailon/lazyjournal/issues")
	fmt.Println()
	fmt.Println("  Flags:")
	fmt.Println("    --help, -h                 " + helpDescription)
	fmt.Println("    --version, -v              " + versionDescription)
	fmt.Println("    --config, -g               " + configDescription)
	fmt.Println("    --audit, -a                " + auditDescription)
	fmt.Println("    --logging, -l              " + loggingDescription)
	fmt.Println("    --tail-mode-disable, -d    " + tailModeDisableDescription)
	fmt.Println("    --tail-lines, -t           " + tailLinesDescription)
	fmt.Println("    --update-interval, -u      " + updateIntervalDescription)
	fmt.Println("    --min-symbols-filter, -F   " + minSymbolsFilterDescription)
	fmt.Println("    --timezone-filter, -T      " + timezoneFilterDescription)
	fmt.Println("    --mouse-disable, -m        " + mouseDisableDescription)
	fmt.Println("    --wrap-disable, -w         " + wrapDisableDescription)
	fmt.Println("    --color-mode, -C           " + colorModeDescription)
	fmt.Println("    --unit-types, -U            " + unitTypeDescription)
	fmt.Println("    --journal-field, -j        " + journalFieldDescription)
	fmt.Println("    --journal-priority, -J     " + journalPriorityDescription)
	fmt.Println("    --journal-boot, -b         " + journalBootDescription)
	fmt.Println("    --custom-path, -p          " + pathDescription)
	fmt.Println("    --docker-stream-only, -o   " + dockerStreamOnlyDescription)
	fmt.Println("    --docker-context, -D       " + dockerContextDescription)
	fmt.Println("    --podman-context, -P       " + podmanContextDescription)
	fmt.Println("    --kubernetes-context, -K   " + kubernetesContextDescription)
	fmt.Println("    --kubernetes-namespace, -n " + namespaceDescription)
	fmt.Println("    --command-color, -c        " + commandColorDescription)
	fmt.Println("    --command-fuzzy, -f        " + commandFuzzyDescription)
	fmt.Println("    --command-regex, -r        " + commandRegexDescription)
	fmt.Println("    --ssh, -s                  " + sshDescription)
	fmt.Println()
}

// Config flag (-g/--config)
func showConfig() {
	// Читаем конфигурацию (извлекаем путь и ошибки)
	configPath, err := config.getConfig()

	fmt.Println("configPath:", configPath)
	fmt.Println("---")

	// Проверяем конфигурацию на ошибки
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	// Выводим содержимое конфигурации
	// fmt.Println(string(configData))
	// Выводим полученные значения из конфигурации (форматированный вывод) с проверкой на пустые значения
	fmt.Println("settings:")
	fmt.Printf("  loggingEnable:            %s\n", config.Settings.LoggingEnable)
	fmt.Printf("  loggingPath:              %s\n", config.Settings.LoggingPath)
	fmt.Printf("  loggingType:              %s\n", config.Settings.LoggingType)
	fmt.Printf("  tailModeDisable:          %s\n", config.Settings.TailModeDisable)
	fmt.Printf("  tailModeLines:            %s\n", config.Settings.TailModeLines)
	fmt.Printf("  updateInterval:           %s\n", config.Settings.UpdateInterval)
	fmt.Printf("  minSymbolFilter:          %s\n", config.Settings.MinSymbolFilter)
	fmt.Printf("  timezoneFilter:           %s\n", config.Settings.TimezoneFilter)
	fmt.Printf("  mouseDisable:             %s\n", config.Settings.MouseDisable)
	fmt.Printf("  wrapModeDisable:          %s\n", config.Settings.WrapModeDisable)
	fmt.Printf("  colorMode:                %s\n", config.Settings.ColorMode)
	fmt.Printf("  colorActionsDisable:      %s\n", config.Settings.ColorActionsDisable)
	fmt.Printf("  unitType:                 %s\n", config.Settings.UnitType)
	fmt.Printf("  journalField:             %s\n", config.Settings.JournalField)
	fmt.Printf("  journalPriority:          %s\n", config.Settings.JournalPriority)
	fmt.Printf("  journalBoot:              %s\n", config.Settings.JournalBoot)
	fmt.Printf("  customPath:               %s\n", config.Settings.CustomPath)
	fmt.Printf("  dockerStreamOnly:         %s\n", config.Settings.DockerStreamOnly)
	fmt.Printf("  dockerContext:            %s\n", config.Settings.DockerContext)
	fmt.Printf("  podmanContext:            %s\n", config.Settings.PodmanContext)
	fmt.Printf("  kubernetesContext:        %s\n", config.Settings.KubernetesContext)
	fmt.Printf("  kubernetesNamespace:      %s\n", config.Settings.KubernetesNamespace)
	fmt.Printf("  disableFastMode:          %s\n", config.Settings.DisableFastMode)

	fmt.Println("interface:")
	fmt.Printf("  SystemLogList:            %s\n", config.Interface.SystemLogList)
	fmt.Printf("  FileLogList:              %s\n", config.Interface.FileLogList)
	fmt.Printf("  ContainerLogList:         %s\n", config.Interface.ContainerLogList)
	fmt.Printf("  sinceDateFilterMode:      %s\n", config.Interface.SinceDateFilterMode)
	fmt.Printf("  untilDateFilterMode:      %s\n", config.Interface.UntilDateFilterMode)
	fmt.Printf("  foregroundColor:          %s\n", config.Interface.ForegroundColor)
	fmt.Printf("  backgroundColor:          %s\n", config.Interface.BackgroundColor)
	fmt.Printf("  selectedForegroundColor:  %s\n", config.Interface.SelectedForegroundColor)
	fmt.Printf("  selectedBackgroundColor:  %s\n", config.Interface.SelectedBackgroundColor)
	fmt.Printf("  frameColor:               %s\n", config.Interface.FrameColor)
	fmt.Printf("  titleColor:               %s\n", config.Interface.TitleColor)
	fmt.Printf("  selectedFrameColor:       %s\n", config.Interface.SelectedFrameColor)
	fmt.Printf("  selectedTitleColor:       %s\n", config.Interface.SelectedTitleColor)
	fmt.Printf("  errorColor:               %s\n", config.Interface.ErrorColor)

	fmt.Println("hotkeys:")
	fmt.Printf("  showHelp:                 %s\n", config.Hotkeys.ShowHelp)
	fmt.Printf("  showManager:              %s\n", config.Hotkeys.ShowManager)
	fmt.Printf("  switchWindow:             %s\n", config.Hotkeys.SwitchWindow)
	fmt.Printf("  backSwitchWindows:        %s\n", config.Hotkeys.BackSwitchWindows)
	fmt.Printf("  up:                       %s\n", config.Hotkeys.Up)
	fmt.Printf("  quickUp:                  %s\n", config.Hotkeys.QuickUp)
	fmt.Printf("  veryQuickUp:              %s\n", config.Hotkeys.VeryQuickUp)
	fmt.Printf("  switchFilterMode:         %s\n", config.Hotkeys.SwitchFilterMode)
	fmt.Printf("  down:                     %s\n", config.Hotkeys.Down)
	fmt.Printf("  quickDown:                %s\n", config.Hotkeys.QuickDown)
	fmt.Printf("  veryQuickDown:            %s\n", config.Hotkeys.VeryQuickDown)
	fmt.Printf("  backSwitchFilterMode:     %s\n", config.Hotkeys.BackSwitchFilterMode)
	fmt.Printf("  left:                     %s\n", config.Hotkeys.Left)
	fmt.Printf("  right:                    %s\n", config.Hotkeys.Right)
	fmt.Printf("  disableFilterByDate:      %s\n", config.Hotkeys.DisableFilterByDate)
	fmt.Printf("  loadJournal:              %s\n", config.Hotkeys.LoadJournal)
	fmt.Printf("  goToFilter:               %s\n", config.Hotkeys.GoToFilter)
	fmt.Printf("  goToEnd:                  %s\n", config.Hotkeys.GoToEnd)
	fmt.Printf("  goToTop:                  %s\n", config.Hotkeys.GoToTop)
	fmt.Printf("  tailModeMore:             %s\n", config.Hotkeys.TailModeMore)
	fmt.Printf("  tailModeLess:             %s\n", config.Hotkeys.TailModeLess)
	fmt.Printf("  updateIntervalMore:       %s\n", config.Hotkeys.UpdateIntervalMore)
	fmt.Printf("  updateIntervalLess:       %s\n", config.Hotkeys.UpdateIntervalLess)
	fmt.Printf("  autoUpdateJournal:        %s\n", config.Hotkeys.AutoUpdateJournal)
	fmt.Printf("  updateJournal:            %s\n", config.Hotkeys.UpdateJournal)
	fmt.Printf("  updateLists:              %s\n", config.Hotkeys.UpdateLists)
	fmt.Printf("  switchColorMode:          %s\n", config.Hotkeys.SwitchColorMode)
	fmt.Printf("  switchPriority:           %s\n", config.Hotkeys.SwitchPriority)
	fmt.Printf("  switchDockerMode:         %s\n", config.Hotkeys.SwitchDockerMode)
	fmt.Printf("  switchStreamMode:         %s\n", config.Hotkeys.SwitchStreamMode)
	fmt.Printf("  timestampShow:            %s\n", config.Hotkeys.TimestampShow)
	fmt.Printf("  exit:                     %s\n", config.Hotkeys.Exit)

	if len(config.Ssh.Hosts) >= 1 {
		fmt.Println("ssh:")
		fmt.Printf("  hosts:\n")
		for _, sshHost := range config.Ssh.Hosts {
			fmt.Printf("    - %s\n", sshHost)
		}
	}
}

func winHomeDocsDir() string {
	homeDir, err := os.UserHomeDir()
	if err != nil {
		return ""
	}
	docsDir := filepath.Join(homeDir, "Documents")
	docsDir = strings.ReplaceAll(docsDir, "\\", "/")
	return docsDir
}

// Audit (#18)
func (app *App) showAudit() {
	var auditText []string
	app.testMode = true
	app.getOS = runtime.GOOS

	auditText = append(auditText,
		"system:",
		"  date: "+time.Now().Format("02.01.2006 15:04:05"),
		"  go: "+strings.ReplaceAll(runtime.Version(), "go", ""),
	)

	data, err := os.ReadFile("/etc/os-release")
	// Если ошибка при чтении файла, то возвращаем только название ОС
	if err != nil {
		auditText = append(auditText, "  os: "+app.getOS)
	} else {
		var name, version string
		for _, line := range strings.Split(string(data), "\n") {
			if strings.HasPrefix(line, "NAME=") {
				name = strings.Trim(line[5:], "\"")
			}
			if strings.HasPrefix(line, "VERSION=") {
				version = strings.Trim(line[8:], "\"")
			}
		}
		auditText = append(auditText, "  os: "+app.getOS+" "+name+" "+version)
	}

	auditText = append(auditText, "  arch: "+app.getArch)

	currentUser, _ := user.Current()
	app.userName = currentUser.Username
	if strings.Contains(app.userName, "\\") {
		app.userName = strings.Split(app.userName, "\\")[1]
	}
	auditText = append(auditText, "  username: "+app.userName)

	if app.getOS != "windows" {
		auditText = append(auditText, "  privilege: "+(map[bool]string{true: "root", false: "user"})[os.Geteuid() == 0])
	}

	execPath, err := os.Executable()
	if err == nil {
		if strings.Contains(execPath, "go-build") {
			auditText = append(auditText, "  execType: source code")
		} else {
			auditText = append(auditText, "  execType: binary file")
		}
	}
	auditText = append(auditText, "  execPath: "+execPath)

	if app.getOS == "windows" {
		// Windows Event
		app.loadWinEvents()
		auditText = append(auditText,
			"winEvent:",
			"  logs: ",
			"  - count: "+strconv.Itoa(len(app.journals)),
		)
		// Filesystem
		if app.userName != "runneradmin" {
			app.systemDisk = os.Getenv("SystemDrive")
			if len(app.systemDisk) >= 1 {
				app.systemDisk = string(app.systemDisk[0])
			} else {
				app.systemDisk = "C"
			}
			auditText = append(auditText,
				"fileSystem:",
				"  systemDisk: "+app.systemDisk,
				"  files:",
			)
			paths := []struct {
				fullPath string
				path     string
			}{
				{"Program Files", "ProgramFiles"},
				{"Program Files (x86)", "ProgramFiles86"},
				{"ProgramData", "ProgramData"},
				{"/AppData/Local", "AppDataLocal"},
				{"/AppData/Roaming", "AppDataRoaming"},
				{"Custom Path", "WinCustomPath"},
			}
			app.customPath = winHomeDocsDir()
			// Создаем группу для ожидания выполнения всех горутин
			var wg sync.WaitGroup
			// Мьютекс для безопасного доступа к переменной auditText
			var mu sync.Mutex
			for _, path := range paths {
				// Увеличиваем счетчик горутин
				wg.Add(1)
				go func(path struct{ fullPath, path string }) {
					// Отнимаем счетчик горутин при завершении выполнения горутины
					defer wg.Done()
					var fullPath string
					switch {
					case path.fullPath == "Custom Path":
						fullPath = "\"" + app.customPath + "\""
					case strings.HasPrefix(path.fullPath, "Program"):
						fullPath = "\"" + app.systemDisk + ":/" + path.fullPath + "\""
					default:
						fullPath = "\"" + app.systemDisk + ":/Users/" + app.userName + path.fullPath + "\""
					}
					app.loadWinFiles(path.path)
					lenLogFiles := strconv.Itoa(len(app.logfiles))
					// Блокируем доступ на завись в переменную auditText
					mu.Lock()
					auditText = append(auditText,
						"  - path: "+fullPath,
						"    count: "+lenLogFiles,
					)
					// Разблокировать мьютекс
					mu.Unlock()
				}(path)
			}
			// Ожидаем завершения всех горутин
			wg.Wait()
		}
	} else {
		// systemd/journald
		auditText = append(auditText,
			"journald:",
		)
		// default values
		app.unitType = "service"
		app.journalField = "SYSLOG_IDENTIFIER"
		app.journalPriority = "debug"
		app.journalBoot = "all"
		csCheck := exec.Command("journalctl", "--version")
		_, err := csCheck.Output()
		if err == nil {
			auditText = append(auditText,
				"  - installed: true",
				"    journals:",
			)
			journalList := []struct {
				name        string
				journalName string
			}{
				{"System units", "systemUnits"},
				{"User units", "userUnits"},
				{"System journals", "systemJournals"},
				{"Kernel boot", "kernelBoot"},
			}
			for _, journal := range journalList {
				app.loadServices(journal.journalName)
				lenJournals := strconv.Itoa(len(app.journals))
				auditText = append(auditText,
					"    - name: "+journal.name,
					"      count: "+lenJournals,
				)
			}
		} else {
			auditText = append(auditText, "  - installed: false")
		}
		// auditd
		auditText = append(auditText,
			"auditd:",
		)
		csCheck = exec.Command("dpkg-query", "-W", "-f='${Version}'", "auditd")
		auditdVersion, err := csCheck.Output()
		if err == nil {
			auditdVersionString := strings.ReplaceAll(string(auditdVersion), "'", "")
			auditdVersionString = strings.Split(auditdVersionString, "-")[0]
			auditText = append(auditText,
				"  - installed: true",
				"    version: "+string(auditdVersionString),
			)
			if os.Geteuid() == 0 {
				app.loadServices("auditd")
				lenRules := strconv.Itoa(len(app.journals))
				auditText = append(auditText,
					"    rules: "+lenRules,
				)
			} else {
				auditText = append(auditText,
					"    rules: requires root access",
				)
			}
		} else {
			auditText = append(auditText, "  - installed: false")
		}
		// Filesystem
		auditText = append(auditText,
			"fileSystem:",
		)
		paths := []struct {
			name string
			path string
		}{
			{"System var logs", "varlog"},
			{"Custom path", "customPath"},
			{"Users home logs", "home"},
			{"Process descriptor logs", "descriptor"},
		}
		for _, path := range paths {
			app.customPath = "/opt/"
			app.loadFiles(path.path)
			lenLogFiles := strconv.Itoa(len(app.logfiles))
			switch path.path {
			case "varlog":
				path.path = "/var/log/"
			case "customPath":
				path.path = "/opt/"
			case "home":
				path.path = "/home/"
			}
			auditText = append(auditText,
				"  - name: "+path.name,
				"    path: "+path.path,
				"    count: "+lenLogFiles,
			)
		}
	}
	auditText = append(auditText,
		"containerizationSystem: ",
	)
	containerizationSystems := []string{
		"docker",
		"compose",
		"podman",
		"kubernetes",
	}
	for _, cs := range containerizationSystems {
		auditText = append(auditText, "  - name: "+cs)
		switch cs {
		case "compose":
			composeBin := "docker compose"
			csCheck := exec.Command("docker", "compose", "version")
			output, err := csCheck.Output()
			if err != nil {
				composeBin = "docker-compose"
				csCheck = exec.Command(composeBin, "version")
				output, err = csCheck.Output()
			}
			if err == nil {
				auditText = append(auditText, "    installed: true")
				csVersion := strings.TrimSpace(string(output))
				splitVersion := strings.Split(csVersion, "version v")
				if len(splitVersion) == 2 && len(splitVersion[1]) > 0 {
					csVersion = splitVersion[1]
				} else {
					splitVersion = strings.Split(csVersion, "version ")
					if len(splitVersion) == 2 && len(splitVersion[1]) > 0 {
						csVersion = splitVersion[1]
						buildIndex := strings.Index(csVersion, ",")
						if buildIndex != -1 {
							csVersion = csVersion[:buildIndex]
						}
					}
				}
				auditText = append(auditText, "    version: "+csVersion)
				var cmd *exec.Cmd
				if composeBin == "docker compose" {
					cmd = exec.Command("docker", "compose", "ls", "-a")
				} else {
					cmd = exec.Command(composeBin, "ls", "-a")
				}
				_, err := cmd.Output()
				if err == nil {
					app.loadDockerContainer(cs)
					auditText = append(auditText, "    stacks: "+strconv.Itoa(len(app.dockerContainers)))
				} else {
					auditText = append(auditText, "    stacks: 0")
				}
			} else {
				auditText = append(auditText, "    installed: false")
			}
		case "kubernetes":
			cs = "kubectl"
			csCheck := exec.Command(cs, "version")
			output, _ := csCheck.Output()
			// По умолчанию у version код возврата всегда 1, по этому проверяем вывод
			if strings.Contains(string(output), "Version:") {
				auditText = append(auditText, "    installed: true")
				// Преобразуем байты в строку и обрезаем пробелы
				csVersion := strings.TrimSpace(string(output))
				// Удаляем текст до номера версии
				csVersion = strings.Split(csVersion, "Version: v")[1]
				// Забираем первую строку
				csVersion = strings.Split(csVersion, "\n")[0]
				auditText = append(auditText, "    version: "+csVersion)
				// Определяем namespace
				currentNamespace := app.kubernetesNamespace
				app.kubernetesNamespaceStatus = app.kubernetesNamespace
				if app.kubernetesNamespace == "all" {
					app.kubernetesNamespace = "--all-namespaces"
				} else {
					app.kubernetesNamespace = "--namespace=" + app.kubernetesNamespace
				}
				// kubectl pods
				cmd := exec.Command(
					cs, "get", "pods", "--context", app.kubernetesContext, app.kubernetesNamespace,
					"-o", "jsonpath={range .items[*]}{.metadata.uid} {.metadata.name} {.status.phase}{'\\n'}{end}",
				)
				_, err = cmd.Output()
				if err == nil {
					app.loadDockerContainer(cs)
					auditText = append(auditText, "    pods: "+strconv.Itoa(len(app.dockerContainers)))
				} else {
					auditText = append(auditText, "    pods: 0")
				}
				// kubectl context
				auditText = append(auditText, "    context: ")
				auditText = append(auditText, "      current: "+app.kubernetesContext)
				cmd = exec.Command(
					cs, "config", "get-contexts", "-o", "name", "--context", app.kubernetesContext,
				)
				contexts, err := cmd.Output()
				if err == nil {
					auditText = append(auditText, "      count: "+strconv.Itoa(len(strings.Split(strings.TrimSpace(string(contexts)), "\n"))))
				} else {
					auditText = append(auditText, "      count: 0")
				}
				// kubectl namespace
				auditText = append(auditText, "    namespace: ")
				auditText = append(auditText, "      current: "+currentNamespace)
				cmd = exec.Command(
					cs, "get", "namespaces", "-o", "name", "--context", app.kubernetesContext,
				)
				namespaces, err := cmd.Output()
				if err == nil {
					auditText = append(auditText, "      count: "+strconv.Itoa(len(strings.Split(strings.TrimSpace(string(namespaces)), "\n"))))
				} else {
					auditText = append(auditText, "      count: 0")
				}
			} else {
				auditText = append(auditText, "    installed: false")
			}
		// docker/podman case
		default:
			csCheck := exec.Command(cs, "--version")
			output, err := csCheck.Output()
			if err == nil {
				auditText = append(auditText, "    installed: true")
				csVersion := strings.TrimSpace(string(output))
				csVersion = strings.Split(csVersion, "version ")[1]
				csVersion = strings.Split(csVersion, ", ")[0]
				auditText = append(auditText, "    version: "+csVersion)
				cmd := exec.Command(
					cs, "ps", "-a",
				)
				_, err = cmd.Output()
				if err == nil {
					app.loadDockerContainer(cs)
					auditText = append(auditText, "    containers: "+strconv.Itoa(len(app.dockerContainers)))
				} else {
					auditText = append(auditText, "    containers: 0")
				}
				// docker context
				if cs == "docker" {
					auditText = append(auditText, "    context: ")
					auditText = append(auditText, "      current: "+app.dockerContext)
					cmd = exec.Command(cs, "context", "ls", "-q")
					contexts, err := cmd.Output()
					if err == nil {
						auditText = append(auditText, "      count: "+strconv.Itoa(len(strings.Split(strings.TrimSpace(string(contexts)), "\n"))))
					} else {
						auditText = append(auditText, "      count: 0")
					}
				}
			} else {
				auditText = append(auditText, "    installed: false")
			}
		}
	}
	for _, line := range auditText {
		fmt.Println(line)
	}
}

// Объявляем конфигурацию
var config Config

// Читаем конфигурацию
func (config *Config) getConfig() (string, error) {
	// Читаем файл конфигурации из текущего каталога
	currentDir, err := os.Getwd()
	if err != nil {
		return "", err
	}
	configPath := filepath.Join(currentDir, "config.yml")
	configData, err := os.ReadFile(configPath)
	if err != nil {
		// Из каталога с исполняемым файлом
		execDir, err := os.Executable()
		if err != nil {
			return "", err
		}
		configPath = filepath.Join(execDir, "config.yml")
		configData, err = os.ReadFile(configPath)
		if err != nil {
			// Из каталога ~/.config/lazyjournal/
			homePath, _ := os.UserHomeDir()
			configPath = filepath.Join(homePath, ".config", "lazyjournal", "config.yml")
			configData, err = os.ReadFile(configPath)
			if err != nil {
				return configPath, ErrConfigNotFound
			}
		}
	}
	// Парсим yaml конфигурации
	err = yaml.Unmarshal(configData, &config)
	if err != nil {
		return configPath, fmt.Errorf("%w: %w", ErrYamlSyntax, err)
	}
	return configPath, err
}

func (app *App) setupLogging() {
	app.logging = true
	logFile, err := os.OpenFile(
		app.loggingPath,
		os.O_CREATE|os.O_WRONLY|os.O_APPEND,
		0666,
	)
	if err != nil {
		return
	}
	app.loggingFile = logFile
	var logger *slog.Logger
	loggingOpts := &slog.HandlerOptions{
		Level: slog.LevelDebug,
	}
	if app.loggingType == "json" {
		jsonHandler := slog.NewJSONHandler(logFile, loggingOpts)
		logger = slog.New(jsonHandler)
	} else {
		textHandler := slog.NewTextHandler(logFile, loggingOpts)
		logger = slog.New(textHandler)
	}
	slog.SetDefault(logger)
	slog.Info("Launching lazyjournal")
}

// Предварительная компиляция регулярных выражений для покраски вывода и их доступности в тестах
var (
	// Исключаем все до http:// (включительно) в начале строки
	trimHttpRegex = regexp.MustCompile(`^.*http://|([^a-zA-Z0-9:/._?&=+-].*)$`)
	// И после любого символа, который не может содержать в себе url
	trimHttpsRegex = regexp.MustCompile(`^.*https://|([^a-zA-Z0-9:/._?&=+-].*)$`)
	// Байты или числа в шестнадцатеричном формате: 0x2 || 0xc0000001
	hexByteRegex = regexp.MustCompile(`\b0x[0-9A-Fa-f]+\b`)
	// DateTime: YYYY-MM-DDTHH:MM:SS.MS+HH:MM || YYYY-MM-DDTHH:MM:SS.MSZ
	dateTimeRegex = regexp.MustCompile(`\b(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?([+-]\d{2}:\d{2}|Z))\b`)
	// Integers: Int only + Time + MAC address + Percentage (int%) + Date2 (20/03/2025)
	integersInputRegex = regexp.MustCompile(`^[^a-zA-Z]*\d+[^a-zA-Z]*$`)
	// Syslog UNIT
	syslogUnitRegex = regexp.MustCompile(`^[a-zA-Z-_.]+\[\d+\]:$`)
	// Находим символы покраски для их удаления
	ansiEscape = regexp.MustCompile(`\x1b\[[0-9;]*m`)
)

// Ошибки
var (
	ErrConfigNotFound = errors.New("configuration file not found")
	ErrYamlSyntax     = errors.New("error yaml syntax in config file")
	ErrInvalidStat    = errors.New("invalid stat output")
)

// Статический массив ANSI цветов для покраски названий стеков в docker compose
var uniquePrefixColorArr = []string{
	"\033[32m", // Зеленый
	"\033[33m", // Желтый
	"\033[34m", // Синий
	"\033[35m", // Пурпурный
	"\033[36m", // Голубой
}

// Статический массив для сопоставления цветов в конфигурации с атрибутами gocui
var mapColorFromConfig = map[string]gocui.Attribute{
	"default": gocui.ColorDefault,
	"green":   gocui.ColorGreen,
	"black":   gocui.ColorBlack,
	"yellow":  gocui.ColorYellow,
	"red":     gocui.ColorRed,
	"blue":    gocui.ColorBlue,
	"cyan":    gocui.ColorCyan,
	"magenta": gocui.ColorMagenta,
	"white":   gocui.ColorWhite,
}

// Карта для хранения сокращенных значений количества логов для статуса
var logViewCountMap = map[string]string{
	"200":    "200",
	"500":    "500",
	"1000":   "1K",
	"5000":   "5K",
	"10000":  "10K",
	"20000":  "20K",
	"30000":  "30K",
	"40000":  "40K",
	"50000":  "50K",
	"100000": "100K",
	"150000": "150K",
	"200000": "200K",
}

// Функция получения списка всех доступных полей в журналах или списка загрузкок системы для проверки значения флага
func (app *App) journalCheck(mode string) ([]string, error) {
	var flag string
	cli := "journalctl"
	switch mode {
	case "units":
		cli = "systemctl"
		flag = "--type=help"
	case "fields":
		flag = "--fields"
	case "boots":
		flag = "--list-boots"
	}
	var cmd *exec.Cmd
	if app.sshMode {
		cmd = exec.Command(
			"ssh", append(app.sshOptions,
				cli, "--no-pager", flag,
			)...)
	} else {
		cmd = exec.Command(
			cli, "--no-pager", flag)
	}
	output, err := cmd.Output()
	if err != nil {
		return nil, fmt.Errorf("%s", string(output))
	} else {
		return strings.Split(strings.TrimSpace(string(output)), "\n"), nil
	}
}

// Функция для опредиления название удаленной системы с timeout в 5 секунд
func remoteGetOS(sshOptions []string) (string, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	cmd := exec.CommandContext(
		ctx,
		"ssh", append(
			sshOptions, "uname", "-s",
		)...)
	cmd.WaitDelay = 5 * time.Second
	// Извлекаем комбинированный вывод (захватываем stdout и stderr) для вывода ошибки подключения
	output, err := cmd.CombinedOutput()
	if err != nil {
		return "", fmt.Errorf("%s", strings.TrimSpace(string(output)))
	} else {
		return strings.ToLower(string(output)), nil
	}
}

// ---------------------------------------- gocui ----------------------------------------

// Объявляем GUI
var g *gocui.Gui

func runGoCui(mock bool) {
	// Инициализация значений по умолчанию + компиляция регулярных выражений для покраски
	app := &App{
		sshMode:                 false,
		fastMode:                true,
		testMode:                false,
		mouseSupport:            true,
		wrapSupport:             true,
		dockerStreamLogs:        false,
		dockerStreamMode:        "stream",
		dockerContext:           "default",
		podmanContext:           "nil",
		kubernetesContext:       "default",
		kubernetesNamespace:     "all",
		startServices:           0, // начальная позиция списка юнитов
		selectedJournal:         0, // начальный индекс выбранного журнала
		startFiles:              0,
		selectedFile:            0,
		startDockerContainers:   0,
		selectedDockerContainer: 0,
		debugLoadTime:           "0s",
		debugColorTime:          "0s",
		selectFilterMode:        "default",
		timestampFilterView:     false,
		sinceDateFilterMode:     false,
		untilDateFilterMode:     false,
		sinceFilterDate:         time.Now(),
		untilFilterDate:         time.Now().AddDate(0, 0, 1),
		limitFilterDate:         time.Now().AddDate(0, 0, 1),
		autoScroll:              true,
		trimHttpRegex:           trimHttpRegex,
		trimHttpsRegex:          trimHttpsRegex,
		hexByteRegex:            hexByteRegex,
		dateTimeRegex:           dateTimeRegex,
		integersInputRegex:      integersInputRegex,
		syslogUnitRegex:         syslogUnitRegex,
		keybindingsEnabled:      true,
		timestampDocker:         true,
		streamTypeDocker:        true,
		lastCurrentView:         "services",
		backCurrentView:         false,
		dockerCompose:           "docker compose",
		uniquePrefixColorMap:    make(map[string]string),
	}

	// Значения по умолчанию
	app.globalCurrentView = "filterList"

	// Фиксируем дату для фильтрации
	app.sinceFilterText = app.sinceFilterDate.Format("2006-01-02")
	app.untilFilterText = app.untilFilterDate.Format("2006-01-02")

	// Определяем используемую ОС (linux/darwin/*bsd/windows) и архитектуру
	app.getOS = runtime.GOOS
	app.getArch = runtime.GOARCH
	var composeVer *exec.Cmd
	// Проверяем установку compose как плагин docker или docker-compose
	if app.sshMode {
		composeVer = exec.Command(
			"ssh", append(app.sshOptions,
				"docker", "compose", "version",
			)...)
	} else {
		composeVer = exec.Command(
			"docker", "compose", "version",
		)
	}
	_, composeErr := composeVer.Output()
	if composeErr != nil {
		app.dockerCompose = "docker-compose"
	}

	// Аргументы
	help := flag.Bool("help", false, helpDescription)
	flag.BoolVar(help, "h", false, helpDescription)
	version := flag.Bool("version", false, versionDescription)
	flag.BoolVar(version, "v", false, versionDescription)
	configFlag := flag.Bool("config", false, configDescription)
	flag.BoolVar(configFlag, "g", false, configDescription)
	audit := flag.Bool("audit", false, auditDescription)
	flag.BoolVar(audit, "a", false, auditDescription)
	loggingFlag := flag.Bool("logging", false, loggingDescription)
	flag.BoolVar(loggingFlag, "l", false, loggingDescription)
	// Общие настройки
	disableScroll := flag.Bool("tail-mode-disable", false, tailModeDisableDescription)
	flag.BoolVar(disableScroll, "d", false, tailModeDisableDescription)
	tailFlag := flag.String("tail-lines", "10000", tailLinesDescription)
	flag.StringVar(tailFlag, "t", "10000", tailLinesDescription)
	updateFlag := flag.Int("update-interval", 5, updateIntervalDescription)
	flag.IntVar(updateFlag, "u", 5, updateIntervalDescription)
	minSymbolFilterFlag := flag.Int("min-symbols-filter", 3, minSymbolsFilterDescription)
	flag.IntVar(minSymbolFilterFlag, "F", 3, minSymbolsFilterDescription)
	timezoneFilterFlag := flag.String("timezone-filter", "+00:00", timezoneFilterDescription)
	flag.StringVar(timezoneFilterFlag, "T", "+00:00", timezoneFilterDescription)
	mouseDisable := flag.Bool("mouse-disable", false, mouseDisableDescription)
	flag.BoolVar(mouseDisable, "m", false, mouseDisableDescription)
	wrapModeDisable := flag.Bool("wrap-disable", false, wrapDisableDescription)
	flag.BoolVar(wrapModeDisable, "w", false, wrapDisableDescription)
	colorModeFlag := flag.String("color-mode", "default", colorModeDescription)
	flag.StringVar(colorModeFlag, "C", "default", colorModeDescription)
	// Специфические настройки
	unitTypeFlag := flag.String("unit-types", "service", unitTypeDescription)
	flag.StringVar(unitTypeFlag, "U", "service", unitTypeDescription)
	journalFieldFlag := flag.String("journal-field", "SYSLOG_IDENTIFIER", journalFieldDescription)
	flag.StringVar(journalFieldFlag, "j", "SYSLOG_IDENTIFIER", journalFieldDescription)
	journalPriorityFlag := flag.String("journal-priority", "debug", journalPriorityDescription)
	flag.StringVar(journalPriorityFlag, "J", "debug", journalPriorityDescription)
	journalBootFlag := flag.String("journal-boot", "all", journalBootDescription)
	flag.StringVar(journalBootFlag, "b", "all", journalBootDescription)
	pathFlag := flag.String("custom-path", "", pathDescription)
	flag.StringVar(pathFlag, "p", "", pathDescription)
	dockerStreamFlag := flag.Bool("docker-stream-only", false, dockerStreamOnlyDescription)
	flag.BoolVar(dockerStreamFlag, "o", false, dockerStreamOnlyDescription)
	dockerContextFlag := flag.String("docker-context", "default", dockerContextDescription)
	flag.StringVar(dockerContextFlag, "D", "default", dockerContextDescription)
	podmanContextFlag := flag.String("podman-context", "", podmanContextDescription)
	flag.StringVar(podmanContextFlag, "P", "", podmanContextDescription)
	kubernetesContextFlag := flag.String("kubernetes-context", "default", kubernetesContextDescription)
	flag.StringVar(kubernetesContextFlag, "K", "default", kubernetesContextDescription)
	kubernetesNamespaceFlag := flag.String("kubernetes-namespace", "all", namespaceDescription)
	flag.StringVar(kubernetesNamespaceFlag, "n", "all", namespaceDescription)
	// cli
	commandColor := flag.Bool("command-color", false, commandColorDescription)
	flag.BoolVar(commandColor, "c", false, commandColorDescription)
	commandFuzzy := flag.String("command-fuzzy", "", commandFuzzyDescription)
	flag.StringVar(commandFuzzy, "f", "", commandFuzzyDescription)
	commandRegex := flag.String("command-regex", "", commandRegexDescription)
	flag.StringVar(commandRegex, "r", "", commandRegexDescription)
	// ssh
	sshModeFlag := flag.String("ssh", "", sshDescription)
	flag.StringVar(sshModeFlag, "s", "", sshDescription)

	// Обработка аргументов
	flag.Parse()

	if *help {
		showHelp()
		os.Exit(0)
	}

	if *version {
		fmt.Println(appVersion)
		os.Exit(0)
	}

	if *configFlag {
		showConfig()
		os.Exit(0)
	}

	if *audit {
		app.showAudit()
		os.Exit(0)
	}

	// Проверяем и извлекаем значения настроек для флагов из конфигурации (#27)
	_, errConfig := config.getConfig()
	if errConfig != nil {
		fmt.Println(errConfig)
	}

	// Берем значение из конфигурации, если значение в конфигурации не пустое и значение флага по умолчанию
	if config.Settings.LoggingEnable != "" && !*loggingFlag {
		// Проверяем значение в конфигурации
		if strings.EqualFold(config.Settings.LoggingEnable, "true") {
			// Включаем режим логирования
			*loggingFlag = true
		}
	}

	// Настройки логирования из конфигурации
	if config.Settings.LoggingPath != "" {
		app.loggingPath = config.Settings.LoggingPath
	} else {
		app.loggingPath = "lazyjournal.log"
	}

	if config.Settings.LoggingType == "text" || config.Settings.LoggingType == "json" {
		app.loggingType = config.Settings.LoggingType
	} else {
		app.loggingType = "text"
	}

	// Включаем логирование
	if *loggingFlag {
		app.setupLogging()
		defer app.loggingFile.Close()
	}

	// -d/tail-mode-disable
	if config.Settings.TailModeDisable != "" && !*disableScroll {
		if strings.EqualFold(config.Settings.TailModeDisable, "true") {
			*disableScroll = true
		}
	}

	if *disableScroll {
		app.disableAutoScroll = true
		app.autoScroll = false
	}

	// -t/--tail-lines
	if config.Settings.TailModeLines != "" && *tailFlag == "10000" {
		tailFlag = &config.Settings.TailModeLines
	}

	// Проверяем значение флага -t/--tail-lines на валидность
	switch *tailFlag {
	case "200", "500", "1000", "5000", "10000", "20000", "30000", "40000", "50000", "100000", "150000", "200000":
		app.logViewCount = *tailFlag
	default:
		if *tailFlag != config.Settings.TailModeLines {
			// Если ошибка в флаге, возвращяем ошибку
			fmt.Println("Available values for tail mode: 200, 500, 1000, 5000, 10000, 20000, 30000, 40000, 50000, 100000, 150000 or 200000 (default: 10K lines)")
			os.Exit(1)
		} else {
			// Если ошибка в конфигурации (или значение не задано), задаем значение по умолчанию
			app.logViewCount = "10000"
		}
	}

	// -u/--update-interval
	if config.Settings.UpdateInterval != "" && *updateFlag == 5 {
		updateIntervalInt, err := strconv.Atoi(config.Settings.UpdateInterval)
		if err == nil {
			updateFlag = &updateIntervalInt
		}
	}

	// Проверяем значение флага -u/--update-interval на валидность
	if *updateFlag >= 2 && *updateFlag <= 10 {
		app.logUpdateSeconds = *updateFlag
	} else {
		updateIntervalInt, err := strconv.Atoi(config.Settings.UpdateInterval)
		if err == nil && *updateFlag != updateIntervalInt {
			fmt.Println("Valid range: 2-10 (default: 5 seconds)")
			os.Exit(1)
		} else {
			app.logUpdateSeconds = 5
		}
	}

	// -F/--min-symbols-filter
	if config.Settings.MinSymbolFilter != "" && *minSymbolFilterFlag == 3 {
		minSymbolFilterInt, err := strconv.Atoi(config.Settings.MinSymbolFilter)
		if err == nil {
			minSymbolFilterFlag = &minSymbolFilterInt
		}
	}

	// Проверяем значение флага -F/--min-symbols-filter на валидность
	if *minSymbolFilterFlag >= 1 && *minSymbolFilterFlag <= 10 {
		app.minSymbolFilter = *minSymbolFilterFlag
	} else {
		minSymbolFilterInt, err := strconv.Atoi(config.Settings.MinSymbolFilter)
		if err == nil && *minSymbolFilterFlag != minSymbolFilterInt {
			fmt.Println("Valid range: 1-10 (default: 3 symbols)")
			os.Exit(1)
		} else {
			app.minSymbolFilter = 3
		}
	}

	//  -T/--timezone-filter
	if config.Settings.TimezoneFilter != "" && *timezoneFilterFlag == "+00:00" {
		timezoneFilterFlag = &config.Settings.TimezoneFilter
	}

	// Значение для временной зоны по умолчанию
	app.timezoneFilter = "+00:00"
	// Проверяем формат временной зоны для флага -T/--timezone-filter на валидность
	checkTimezone := regexp.MustCompile(`^[+-][0-9]{2}:[0-9]{2}$`)
	if checkTimezone.MatchString(*timezoneFilterFlag) {
		app.timezoneFilter = *timezoneFilterFlag
	}

	// -m/--mouse-disable
	if config.Settings.MouseDisable != "" && !*mouseDisable {
		if strings.EqualFold(config.Settings.MouseDisable, "true") {
			*mouseDisable = true
		}
	}

	if *mouseDisable {
		app.mouseSupport = false
	}

	// -w/--wrap-disable
	if config.Settings.WrapModeDisable != "" && !*wrapModeDisable {
		if strings.EqualFold(config.Settings.WrapModeDisable, "true") {
			*wrapModeDisable = true
		}
	}

	if *wrapModeDisable {
		app.wrapSupport = false
	}

	// -C/--color-mode
	if config.Settings.ColorMode != "" && *colorModeFlag == "default" {
		colorModeFlag = &config.Settings.ColorMode
	}

	switch *colorModeFlag {
	case "default", "tailspin", "bat", "disable":
		app.colorMode = *colorModeFlag
	default:
		if *colorModeFlag != config.Settings.ColorMode {
			fmt.Println("Available values for color mode: default, tailspin, bat or disable")
			os.Exit(1)
		} else {
			app.colorMode = "default"
		}
	}

	if config.Settings.ColorActionsDisable != "" {
		if strings.EqualFold(config.Settings.ColorActionsDisable, "true") {
			app.colorActionsDisable = true
		}
	}

	// -U/--unit-types
	if config.Settings.UnitType != "" && *unitTypeFlag == "service" {
		app.unitType = config.Settings.UnitType
	} else {
		app.unitType = *unitTypeFlag
	}

	// Проверяем значение флага -U/--unit-types на валидность и возвращяем список всех существующих типов юнитов
	if app.unitType != "service" {
		unitTypesList, unitTypesErr := app.journalCheck("units")
		if unitTypesErr == nil {
			unitFlagArr := strings.SplitSeq(app.unitType, ",")
			for unit := range unitFlagArr {
				if !slices.Contains(unitTypesList, unit) {
					unitTypesList = unitTypesList[1:]
					unitTypesString := strings.Join(unitTypesList, ", ")
					fmt.Println("Unit type " + unit + " not found")
					fmt.Println("Available unit types: " + unitTypesString)
					os.Exit(1)
				}
			}
		}
	}

	// -j/--journal-field
	if config.Settings.JournalField != "" && *journalFieldFlag == "SYSLOG_IDENTIFIER" {
		app.journalField = config.Settings.JournalField
	} else {
		app.journalField = *journalFieldFlag
	}

	// Проверяем значение флага -j/--journal-field на валидность и возвращяем список существующих полей
	if app.journalBoot != "SYSLOG_IDENTIFIER" {
		fieldList, fieldErr := app.journalCheck("fields")
		if fieldErr == nil {
			if !slices.Contains(fieldList, app.journalField) {
				journaldFieldString := strings.Join(fieldList, ", ")
				fmt.Println("Field " + app.journalField + " not found")
				fmt.Println("Available fields: " + journaldFieldString)
				os.Exit(1)
			}
		}
	}

	// -J/--journal-priority
	if config.Settings.JournalPriority != "" && *journalPriorityFlag == "debug" {
		*journalPriorityFlag = config.Settings.JournalPriority
	}

	// Проверяем значение флага -J/--journal-priority на валидность и возвращяем список доступных приоритетов
	switch *journalPriorityFlag {
	case "debug", "info", "notice", "warning", "err", "crit", "alert", "emerg":
		app.journalPriority = *journalPriorityFlag
	default:
		if *journalPriorityFlag != config.Settings.JournalPriority {
			fmt.Println("Priority " + app.journalPriority + " not found")
			fmt.Println("Available priorities: debug, info, notice, warning, err, crit, alert and emerg")
			os.Exit(1)
		} else {
			app.journalPriority = "debug"
		}
	}

	// -b/--journal-boot
	if config.Settings.JournalBoot != "" && *journalBootFlag == "all" {
		app.journalBoot = config.Settings.JournalBoot
	} else {
		app.journalBoot = *journalBootFlag
	}

	// Проверяем значение флага -b/--journal-boot на валидность и возвращяем список загрузок системы
	if app.journalBoot != "all" {
		bootsList, bootsErr := app.journalCheck("boots")
		if bootsErr == nil {
			// Переварачиваем массив
			slices.Reverse(bootsList)
			// Извлекаем номера загрузок в новый массив для проверки переданного значения
			bootNumbers := make([]string, 0, len(bootsList))
			for _, line := range bootsList {
				bootNumber := strings.Fields(line)[0]
				bootNumbers = append(bootNumbers, bootNumber)
			}
			if !slices.Contains(bootNumbers, app.journalBoot) {
				fmt.Println("Boot number " + app.journalBoot + " not found")
				fmt.Println("Available list boots:")
				for _, line := range bootsList {
					fmt.Println(line)
				}
				os.Exit(1)
			}
		}
	}

	// -p/--custom-path
	if config.Settings.CustomPath != "" && *pathFlag == "" {
		pathFlag = &config.Settings.CustomPath
	}

	if (app.getOS != "windows" && strings.HasPrefix(*pathFlag, "/")) ||
		(app.getOS == "windows" && (strings.Contains(*pathFlag, ":\\") || strings.Contains(*pathFlag, ":/"))) {
		app.customPath = *pathFlag
		app.customPath = strings.TrimSuffix(app.customPath, "/")
		app.customPath = strings.TrimSuffix(app.customPath, "\\")
		app.customPath = strings.ReplaceAll(app.customPath, "//", "/")
		app.customPath = strings.ReplaceAll(app.customPath, "\\\\", "\\")
	} else {
		if *pathFlag != config.Settings.CustomPath {
			fmt.Println("Invalid custom path for " + app.getOS + ": " + *pathFlag)
			os.Exit(1)
		} else {
			if app.getOS == "windows" {
				app.customPath = winHomeDocsDir()
			} else {
				app.customPath = "/opt"
			}
		}
	}

	// -o/--docker-stream-only
	if config.Settings.DockerStreamOnly != "" && !*dockerStreamFlag {
		if strings.EqualFold(config.Settings.DockerStreamOnly, "true") {
			*dockerStreamFlag = true
		}
	}

	if *dockerStreamFlag {
		app.dockerStreamLogs = true
		app.dockerStreamLogsStatus = app.dockerStreamMode
	} else {
		// Проверяем доступность директории на чтение
		dir := "/var/lib/docker/containers"
		f, err := os.Open(dir)
		if err != nil {
			app.dockerStreamLogsStatus = app.dockerStreamMode
		} else {
			// Пробуем прочитать имя первого элемента (проверить список файлов/директорий)
			_, err = f.Readdirnames(1)
			f.Close()
			if err != nil {
				app.dockerStreamLogsStatus = app.dockerStreamMode
			} else {
				app.dockerStreamLogsStatus = "json-file"
			}
		}
	}

	// -D/--docker-context
	if config.Settings.DockerContext != "" && *dockerContextFlag == "default" {
		dockerContextFlag = &config.Settings.DockerContext
	}

	app.dockerContext = *dockerContextFlag

	// -P/--podman-context
	// Если в конфигурации не задано значение и значение флага по умолчанию пустое, то присваиваем значение из конфигурации
	if config.Settings.PodmanContext != "" && *podmanContextFlag == "" {
		podmanContextFlag = &config.Settings.PodmanContext
	}

	app.podmanContext = *podmanContextFlag

	// -K/--kubernetes-context
	if config.Settings.KubernetesContext != "" && *kubernetesContextFlag == "default" {
		kubernetesContextFlag = &config.Settings.KubernetesContext
	}

	app.kubernetesContext = *kubernetesContextFlag

	// -n/--kubernetes-namespace
	if config.Settings.KubernetesNamespace != "" && *kubernetesNamespaceFlag == "all" {
		kubernetesNamespaceFlag = &config.Settings.KubernetesNamespace
	}

	app.kubernetesNamespaceStatus = *kubernetesNamespaceFlag

	if *kubernetesNamespaceFlag == "all" {
		app.kubernetesNamespace = "--all-namespaces"
	} else {
		app.kubernetesNamespace = "--namespace=" + *kubernetesNamespaceFlag
	}

	// Fast mode (demo)
	if config.Settings.DisableFastMode != "" {
		if strings.EqualFold(config.Settings.DisableFastMode, "true") {
			app.fastMode = false
		}
	}

	// Определяем режим фильтрации по времени для статуса
	switch {
	case app.sinceDateFilterMode && !app.untilDateFilterMode:
		app.filterByDateStatus = "since only"
	case !app.sinceDateFilterMode && app.untilDateFilterMode:
		app.filterByDateStatus = "until only"
	case app.sinceDateFilterMode && app.untilDateFilterMode:
		app.filterByDateStatus = "since and until"
	case !app.sinceDateFilterMode && !app.untilDateFilterMode:
		app.filterByDateStatus = "false"
	}

	// Определяем списки в панелях по умолчанию при запуске интерфейса (#37)

	switch config.Interface.SystemLogList {
	case "userUnits", "systemJournals", "kernelBoot", "auditd":
		app.selectUnits = config.Interface.SystemLogList
	default:
		app.selectUnits = "systemUnits"
	}

	switch config.Interface.FileLogList {
	case "customPath", "home", "descriptor":
		app.selectPath = config.Interface.FileLogList
	default:
		app.selectPath = "varlog"
	}

	switch config.Interface.ContainerLogList {
	case "compose", "podman", "kubernetes":
		app.selectContainerizationSystem = config.Interface.ContainerLogList
	default:
		app.selectContainerizationSystem = "docker"
	}

	// Включение фильтрации по дате при запуске интерфейса (за сегодняшний день по умолчанию)

	if config.Interface.SinceDateFilterMode != "" {
		if strings.EqualFold(config.Interface.SinceDateFilterMode, "true") {
			app.sinceDateFilterMode = true
		}
	}

	if config.Interface.UntilDateFilterMode != "" {
		if strings.EqualFold(config.Interface.UntilDateFilterMode, "true") {
			app.untilDateFilterMode = true
		}
	}

	// Извлекаем цвета из конфигурации для покраски интерфейса

	var ok bool

	lowerForegroundColor := strings.ToLower(config.Interface.ForegroundColor)
	app.foregroundColor, ok = mapColorFromConfig[lowerForegroundColor]
	// Если значение не найдено в массиве mapColorFromConfig, присваиваем значение по умолчанию
	if !ok {
		app.foregroundColor = gocui.ColorDefault
	}

	lowerBackgroundColor := strings.ToLower(config.Interface.BackgroundColor)
	app.backgroundColor, ok = mapColorFromConfig[lowerBackgroundColor]
	if !ok {
		app.backgroundColor = gocui.ColorDefault
	}

	lowerSelectedForegroundColor := strings.ToLower(config.Interface.SelectedForegroundColor)
	app.selectedForegroundColor, ok = mapColorFromConfig[lowerSelectedForegroundColor]
	if !ok {
		app.selectedForegroundColor = gocui.ColorBlack
	}

	lowerSelectedBackgroundColor := strings.ToLower(config.Interface.SelectedBackgroundColor)
	app.selectedBackgroundColor, ok = mapColorFromConfig[lowerSelectedBackgroundColor]
	if !ok {
		app.selectedBackgroundColor = gocui.ColorGreen
	}

	lowerFrameColor := strings.ToLower(config.Interface.FrameColor)
	app.frameColor, ok = mapColorFromConfig[lowerFrameColor]
	if !ok {
		app.frameColor = gocui.ColorDefault
	}

	lowerTitleColor := strings.ToLower(config.Interface.TitleColor)
	app.titleColor, ok = mapColorFromConfig[lowerTitleColor]
	if !ok {
		app.titleColor = gocui.ColorDefault
	}

	lowerSelectedFrameColor := strings.ToLower(config.Interface.SelectedFrameColor)
	app.selectedFrameColor, ok = mapColorFromConfig[lowerSelectedFrameColor]
	if !ok {
		app.selectedFrameColor = gocui.ColorGreen
	}

	lowerSelectedTitleColor := strings.ToLower(config.Interface.SelectedTitleColor)
	app.selectedTitleColor, ok = mapColorFromConfig[lowerSelectedTitleColor]
	if !ok {
		app.selectedTitleColor = gocui.ColorGreen
	}

	lowerErrorColor := strings.ToLower(config.Interface.ErrorColor)
	app.errorColor, ok = mapColorFromConfig[lowerErrorColor]
	if !ok {
		app.errorColor = gocui.ColorRed
	}

	app.journalListFrameColor = app.frameColor
	app.fileSystemFrameColor = app.frameColor
	app.dockerFrameColor = app.frameColor

	// Определяем переменные и массивы для покраски вывода

	// Текущее имя хоста
	app.hostName, _ = os.Hostname()
	// Удаляем доменную часть, если она есть
	if strings.Contains(app.hostName, ".") {
		app.hostName = strings.Split(app.hostName, ".")[0]
	}
	// Текущее имя пользователя
	currentUser, _ := user.Current()
	app.userName = currentUser.Username
	// Удаляем доменную часть, если она есть
	if strings.Contains(app.userName, "\\") {
		app.userName = strings.Split(app.userName, "\\")[1]
	}
	// Определяем букву системного диска с установленной ОС Windows
	app.systemDisk = os.Getenv("SystemDrive")
	if len(app.systemDisk) >= 1 {
		app.systemDisk = string(app.systemDisk[0])
	} else {
		app.systemDisk = "C"
	}
	// Имена пользователей
	passwd, _ := os.Open("/etc/passwd")
	scanner := bufio.NewScanner(passwd)
	for scanner.Scan() {
		line := scanner.Text()
		userName := strings.Split(line, ":")
		if len(userName) > 0 {
			app.userNameArray = append(app.userNameArray, userName[0])
		}
	}

	// Обработка фильтрации с неточным поиском в режиме командной строки
	if *commandFuzzy != "" {
		app.commandLineFuzzy(*commandFuzzy, *commandColor)
	}

	// Обработка фильтрации с поддержкой регулярных выражений в режиме командной строки
	if *commandRegex != "" {
		filter := strings.ToLower(*commandRegex)
		// Добавляем флаг для нечувствительности к регистру по умолчанию
		filter = "(?i)" + filter
		// Компилируем и проверяем регулярное выражение
		regex, err := regexp.Compile(filter)
		if err != nil {
			fmt.Println("Regular expression syntax error")
			os.Exit(1)
		}
		app.commandLineRegex(regex, *commandColor)
	}

	// Обработка покраски вывода в режиме командной строки
	if *commandColor {
		app.commandLineColor(false)
	}

	if *commandColor || *commandFuzzy != "" || *commandRegex != "" {
		os.Exit(0)
	}

	// Включаем режим ssh и заполняем параметры (включая sudo и другие стандартные опции ssh подключения, например, порт)
	if *sshModeFlag != "" {
		options := strings.Split(*sshModeFlag, " ")
		app.sshOptions = append(app.sshOptions, options...)
		remoteOS, err := remoteGetOS(app.sshOptions)
		if err != nil {
			app.sshMode = false
			app.sshStatus = "false"
			app.getOS = runtime.GOOS
		} else {
			app.sshMode = true
			// Забираем только название хоста в статус
			app.sshStatus = options[0]
			app.getOS = remoteOS
		}
	} else {
		app.sshStatus = "false"
	}

	// Создаем GUI
	var err error
	if mock {
		g, err = gocui.NewGui(gocui.OutputSimulator, true) // 1-й параметр для режима работы терминала (tcell) и 2-й параметр для форка
	} else {
		g, err = gocui.NewGui(gocui.OutputNormal, true)
	}
	if err != nil {
		log.Panicln(err)
	}
	// Закрываем GUI после завершения
	defer g.Close()

	app.gui = g
	// Функция, которая будет вызываться при обновлении интерфейса
	g.SetManagerFunc(app.layout)

	// Включить поддержку мыши
	g.Mouse = app.mouseSupport

	// Цветовая схема GUI
	g.FgColor = app.foregroundColor // foreground (цвет текста по умолчанию)
	g.BgColor = app.backgroundColor // background (фон всего интерфейса)

	// Привязка клавиш для работы с интерфейсом из функции setupKeybindings()
	if err := app.setupKeybindings(); err != nil {
		log.Panicln("Error key bindings", err)
	}

	// Выполняем layout для инициализации интерфейса
	if err := app.layout(g); err != nil {
		log.Panicln(err)
	}

	// Фиксируем текущее количество видимых строк в терминале (-1 заголовок)
	if v, err := g.View("services"); err == nil {
		_, viewHeight := v.Size()
		app.maxVisibleServices = viewHeight
	}
	// Загрузка списка служб или событий Windows
	if app.getOS == "windows" {
		v, err := g.View("services")
		if err != nil {
			log.Panicln(err)
		}
		v.Title = " < Windows Event Logs (0) > "
		// Загружаем список событий Windows в горутине
		go func() {
			app.loadWinEvents()
		}()
	} else {
		app.loadServices(app.selectUnits)
	}

	// Filesystem
	if v, err := g.View("varLogs"); err == nil {
		_, viewHeight := v.Size()
		app.maxVisibleFiles = viewHeight
	}

	// Определяем ОС и загружаем файловые журналы
	if app.getOS == "windows" {
		selectedVarLog, err := g.View("varLogs")
		if err != nil {
			log.Panicln(err)
		}
		g.Update(func(g *gocui.Gui) error {
			selectedVarLog.Clear()
			fmt.Fprintln(selectedVarLog, "Searching log files...")
			selectedVarLog.Highlight = false
			return nil
		})
		selectedVarLog.Title = " < Program Files (0) > "
		app.selectPath = "ProgramFiles"
		// Загружаем список файлов Windows в горутине
		go func() {
			app.loadWinFiles(app.selectPath)
		}()
	} else {
		app.loadFiles(app.selectPath)
	}

	// Docker
	if v, err := g.View("docker"); err == nil {
		_, viewHeight := v.Size()
		app.maxVisibleDockerContainers = viewHeight
	}
	app.loadDockerContainer(app.selectContainerizationSystem)

	// Устанавливаем фокус на окно с журналами по умолчанию
	if _, err := g.SetCurrentView("filterList"); err != nil {
		return
	}

	// Горутина для автоматического обновления вывода журнала каждые n (logUpdateSeconds) секунд
	app.secondsChan = make(chan int, app.logUpdateSeconds)
	go func() {
		app.updateLogBackground(app.secondsChan, false)
	}()

	// Горутина для отслеживания изменений размера окна и его перерисовки
	go func() {
		app.updateWindowSize(1)
	}()

	// Запус GUI
	if err := g.MainLoop(); err != nil && !errors.Is(err, gocui.ErrQuit) {
		log.Panicln(err)
	}
}

func main() {
	runGoCui(false)
}

// Структура интерфейса окон GUI
func (app *App) layout(g *gocui.Gui) error {
	maxX, maxY := g.Size()                // получаем текущий размер интерфейса терминала (ширина, высота)
	leftPanelWidth := maxX / 4            // ширина левой колонки
	inputHeight := 3                      // высота поля ввода для фильтрации список
	availableHeight := maxY - inputHeight // общая высота всех трех окон слева
	panelHeight := availableHeight / 3    // высота каждого окна

	// Поле ввода для фильтрации списков
	if v, err := g.SetView("filterList", 0, 0, leftPanelWidth-1, inputHeight-1, 0); err != nil {
		if !errors.Is(err, gocui.ErrUnknownView) {
			return err
		}
		v.Title = "Filtering lists"
		v.Editable = true
		v.Wrap = true
		// Первое выбранное окно при запуске, по этому выделяем зеленым цветом
		v.FrameColor = app.selectedFrameColor // Цвет границы окна
		v.TitleColor = app.selectedTitleColor // Цвет заголовка окна
		v.Editor = app.createFilterEditor("lists")
	}

	// Окно для отображения списка доступных журналов (UNIT)
	// Размеры окна: заголовок, отступ слева, отступ сверху, ширина, высота, 5-й параметр из форка для продолжение окна (2)
	if v, err := g.SetView("services", 0, inputHeight, leftPanelWidth-1, inputHeight+panelHeight-1, 0); err != nil {
		if !errors.Is(err, gocui.ErrUnknownView) {
			return err
		}
		// Определяем заголовок панели в зависимости от выбранного журнала в конфигурации по умолчанию
		switch app.selectUnits {
		case "systemUnits":
			v.Title = " < System units (0) > "
		case "userUnits":
			v.Title = " < User units (0) > "
		case "systemJournals":
			v.Title = " < System journals (0) > "
		case "kernelBoot":
			v.Title = " < Kernel boot (0) > "
		case "auditd":
			v.Title = " < Audit rules keys (0) > "
		}
		v.Highlight = true  // выделение активного элемента в списке
		v.Wrap = false      // отключаем перенос строк
		v.Autoscroll = true // включаем автопрокрутку
		// Цвет границ и заголовка окна из конфигурации по умолчанию при загрузке интерфейса
		v.FrameColor = app.frameColor
		v.TitleColor = app.titleColor
		// Цветовая схема из форка awesome-gocui/gocui
		v.SelFgColor = app.selectedForegroundColor // Цвет текста при выборе в списке
		v.SelBgColor = app.selectedBackgroundColor // Цвет фона при выборе в списке
		app.updateServicesList()                   // выводим список журналов в это окно
	}

	// Окно для списка логов из файловой системы
	if v, err := g.SetView("varLogs", 0, inputHeight+panelHeight, leftPanelWidth-1, inputHeight+2*panelHeight-1, 0); err != nil {
		if !errors.Is(err, gocui.ErrUnknownView) {
			return err
		}
		switch app.selectPath {
		case "varlog":
			v.Title = " < System var logs (0) > "
		case "customPath":
			v.Title = " < Custom path - " + app.customPath + " (0) > "
		case "home":
			v.Title = " < Users home logs (0) > "
		case "descriptor":
			v.Title = " < Process descriptor logs (0) > "
		}
		v.Highlight = true
		v.Wrap = false
		v.Autoscroll = true
		v.FrameColor = app.frameColor
		v.TitleColor = app.titleColor
		v.SelFgColor = app.selectedForegroundColor
		v.SelBgColor = app.selectedBackgroundColor
		app.updateLogsList()
	}

	// Окно для списка контейнеров Docker и Podman
	// В maxY -2 для статуса
	if v, err := g.SetView("docker", 0, inputHeight+2*panelHeight, leftPanelWidth-1, maxY-1-2, 0); err != nil {
		if !errors.Is(err, gocui.ErrUnknownView) {
			return err
		}
		switch app.selectContainerizationSystem {
		case "docker":
			v.Title = " < Docker containers (0) > "
		case "compose":
			v.Title = " < Compose stacks (0) > "
		case "podman":
			v.Title = " < Podman containers (0) > "
		case "kubernetes":
			v.Title = " < Kubernetes pods (0) > "
		}
		v.Highlight = true
		v.Wrap = false
		v.Autoscroll = true
		v.FrameColor = app.frameColor
		v.TitleColor = app.titleColor
		v.SelFgColor = app.selectedForegroundColor
		v.SelBgColor = app.selectedBackgroundColor
	}

	// Окно ввода текста для фильтрации
	if v, err := g.SetView("filter", leftPanelWidth+1, 0, maxX-1, 2, 0); err != nil {
		if !errors.Is(err, gocui.ErrUnknownView) {
			return err
		}
		v.Title = "Filter (Default)"
		v.Editable = true                         // включить окно редактируемым для ввода текста
		v.Editor = app.createFilterEditor("logs") // редактор для обработки ввода
		v.Wrap = true
		v.FrameColor = app.frameColor
		v.TitleColor = app.titleColor
	}

	// Интерфейс скролла в окне вывода лога (maxX-3 ширина окна - отступ слева)
	if v, err := g.SetView("scrollLogs", maxX-3, 3, maxX-1, maxY-1-2, 0); err != nil {
		if !errors.Is(err, gocui.ErrUnknownView) {
			return err
		}
		v.Wrap = true
		v.Autoscroll = false
		v.FrameColor = app.frameColor
		v.TitleColor = app.titleColor
		// Постоянный цвет стрелочек в окне скролла
		v.FgColor = app.selectedBackgroundColor
		// Заполняем окно стрелками
		_, viewHeight := v.Size()
		fmt.Fprintln(v, "▲")
		for i := 1; i < viewHeight-1; i++ {
			fmt.Fprintln(v, " ")
		}
		fmt.Fprintln(v, "▼")
	}

	// Окно для вывода записей выбранного журнала (maxX-2 для отступа скролла и 8 для продолжения углов)
	if v, err := g.SetView("logs", leftPanelWidth+1, 3, maxX-1-2, maxY-1-2, 8); err != nil {
		if !errors.Is(err, gocui.ErrUnknownView) {
			return err
		}
		v.Title = "Logs"
		v.Wrap = app.wrapSupport
		v.Autoscroll = false
		v.FrameColor = app.frameColor
		v.TitleColor = app.titleColor
		v.Subtitle = "[ ]"
	}

	// Окно статуса внизу интерфейса (вместо Subtitle)
	if v, err := g.SetView("status", -1, maxY-3, maxX, maxY, 8); err != nil {
		if !errors.Is(err, gocui.ErrUnknownView) {
			return err
		}
		v.Frame = false // Отключаем рамку для статуса
		v.FgColor = app.foregroundColor
		fmt.Fprintf(v,
			" Tail mode: \033[32m%t\033[0m (\033[32m%s\033[0m lines) | "+
				"Update interval: \033[32m%d\033[0m sec | "+
				"Color mode: \033[32m%s\033[0m | "+
				"Filter by date: \033[32m%s\033[0m | "+
				"Filter by priority/boot: \033[32m%s\033[0m/\033[32m%s\033[0m | "+
				"Show timestamp: \033[32m%t\033[0m \n "+
				"SSH mode: \033[32m%s\033[0m | "+
				"Docker mode/context: \033[32m%s\033[0m/\033[32m%s\033[0m | "+
				"Kubernetes context/namespace: \033[32m%s\033[0m/\033[32m%s\033[0m",
			app.autoScroll,
			logViewCountMap[app.logViewCount],
			app.logUpdateSeconds,
			app.colorMode,
			app.filterByDateStatus,
			app.journalPriority,
			app.journalBoot,
			app.timestampDocker,
			app.sshStatus,
			app.dockerStreamLogsStatus,
			app.dockerContext,
			app.kubernetesContext,
			app.kubernetesNamespaceStatus,
		)
	}

	// Включение курсора в режиме фильтра и отключение в остальных окнах
	currentView := g.CurrentView()
	if currentView != nil && (currentView.Name() == "filter" || currentView.Name() == "filterList" || currentView.Name() == "sinceFilter" || currentView.Name() == "untilFilter") {
		g.Cursor = true
	} else {
		g.Cursor = false
	}

	return nil
}

// ---------------------------------------- systemd/journald/auditd/wineventlog ----------------------------------------

// Функция для удаления ANSI-символов покраски
func removeANSI(input string) string {
	ansiEscapeRegex := regexp.MustCompile(`\033\[[0-9;]*m`)
	return ansiEscapeRegex.ReplaceAllString(input, "")
}

// Функция для извлечения даты из строки для списка загрузок ядра
func parseDateFromName(name string) time.Time {
	cleanName := removeANSI(name)
	dateFormat := "02.01.2006 15:04:05"
	// Извлекаем дату, начиная с 22-го символа (после дефиса)
	parsedDate, _ := time.Parse(dateFormat, cleanName[22:])
	return parsedDate
}

// Функция для загрузки списка журналов служб или загрузок системы из journald с помощью journalctl
func (app *App) loadServices(journalName string) {
	app.journals = nil
	// Проверка, что в системе установлена и поддерживается утилита journalctl
	var checkJournald *exec.Cmd
	if app.sshMode {
		checkJournald = exec.Command(
			"ssh", append(app.sshOptions,
				"journalctl", "--version",
			)...)
	} else {
		checkJournald = exec.Command(
			"journalctl", "--version",
		)
	}
	// Проверяем на ошибки (очищаем список служб, отключаем курсор и выводим ошибку)
	if app.logging {
		slog.Info(checkJournald.String(), "action", "Check the binary")
	}
	_, err := checkJournald.Output()
	if err != nil && !app.testMode {
		vError, _ := app.gui.View("services")
		vError.Clear()
		app.journalListFrameColor = app.errorColor
		vError.FrameColor = app.journalListFrameColor
		vError.Highlight = false
		fmt.Fprintln(vError, "\033[31msystemd-journald not supported\033[0m")
		return
	}
	if err != nil && app.testMode {
		log.Print("Error: systemd-journald not supported")
	}
	switch journalName {
	// Unit list from systemd
	case "systemUnits", "userUnits":
		app.journals = append(app.journals, Journal{
			name:    "_all",
			boot_id: "_all",
		})
		// (1) Получаем список всех юнитов со статусом работы через systemctl в формате JSON
		var unitsList *exec.Cmd
		var unitTypeFlag = "--type=" + app.unitType // "service,timer,scope,socket,mount" (default: service)
		if app.sshMode {
			if journalName == "systemUnits" {
				unitsList = exec.Command(
					"ssh", append(app.sshOptions,
						"systemctl", "list-units", "--all", unitTypeFlag, "--no-legend", "--no-pager", "--output=json",
					)...)
			} else {
				unitsList = exec.Command(
					"ssh", append(app.sshOptions,
						"systemctl", "--user", "list-units", "--all", unitTypeFlag, "--no-legend", "--no-pager", "--output=json",
					)...)
			}
		} else {
			if journalName == "systemUnits" {
				unitsList = exec.Command(
					"systemctl", "list-units", "--all", unitTypeFlag, "--no-legend", "--no-pager", "--output=json",
				)
			} else {
				unitsList = exec.Command(
					"systemctl", "--user", "list-units", "--all", unitTypeFlag, "--no-legend", "--no-pager", "--output=json",
				)
			}
		}
		if app.logging {
			var logSource string
			if journalName == "systemUnits" {
				logSource = "Loading the system units"
			} else {
				logSource = "Loading the user units"
			}
			slog.Info(unitsList.String(), "action", logSource)
		}
		output, err := unitsList.Output()
		if !app.testMode {
			if err != nil {
				vError, _ := app.gui.View("services")
				vError.Clear()
				app.journalListFrameColor = app.errorColor
				vError.FrameColor = app.journalListFrameColor
				vError.Highlight = false
				fmt.Fprintln(vError, "\033[31mAccess denied in systemd via systemctl\033[0m")
				return
			}
			v, _ := app.gui.View("services")
			app.journalListFrameColor = app.frameColor
			if v.FrameColor != app.frameColor {
				v.FrameColor = app.selectedFrameColor
			}
			v.Highlight = true
		}
		if err != nil && app.testMode {
			log.Print("Error: access denied in systemd via systemctl")
		}
		// Чтение данных в формате JSON
		var units []map[string]any
		err = json.Unmarshal(output, &units)
		// Если ошибка JSON, создаем массив вручную
		if err != nil {
			lines := strings.Split(string(output), "\n")
			for _, line := range lines {
				// Разбиваем строку на поля (эквивалентно: awk '{print $1,$2,$3,$4}')
				fields := strings.Fields(line)
				// Пропускаем строки с недостаточным количеством полей
				if len(fields) < 4 {
					continue
				}
				// Заполняем временный массив из строки
				unit := map[string]any{
					"unit":   fields[0],
					"load":   fields[1],
					"active": fields[2],
					"sub":    fields[3],
				}
				// Добавляем временный массив строки в основной массив
				units = append(units, unit)
			}
		}
		// (2) Получаем список всех юнит-файлов для извлечения статуса автозагрузки и отключенных сервисов
		var unitFilesList *exec.Cmd
		if app.sshMode {
			unitFilesList = exec.Command(
				"ssh", append(app.sshOptions,
					"systemctl", "list-unit-files", unitTypeFlag, "--all", "--no-legend", "--no-pager", "--output=json", "--state=enabled,disabled",
				)...)
		} else {
			unitFilesList = exec.Command(
				"systemctl", "list-unit-files", unitTypeFlag, "--all", "--no-legend", "--no-pager", "--output=json", "--state=enabled,disabled",
			)
		}
		if app.logging {
			var logSource string
			if journalName == "systemUnits" {
				logSource = "Loading the system units"
			} else {
				logSource = "Loading the user units"
			}
			slog.Info(unitFilesList.String(), "action", logSource)
		}
		output, _ = unitFilesList.Output()
		var unitFiles []map[string]any
		err = json.Unmarshal(output, &unitFiles)
		if err != nil {
			lines := strings.Split(string(output), "\n")
			for _, line := range lines {
				fields := strings.Fields(line)
				if len(fields) < 3 {
					continue
				}
				unit := map[string]any{
					"unit_file": fields[0],
					"state":     fields[1],
					"preset":    fields[2], // предустановленное состояние, при установке пакета
				}
				unitFiles = append(unitFiles, unit)
			}
		}
		serviceMap := make(map[string]bool)
		// Анализ статуса
		for _, unit := range units {
			unitName, _ := unit["unit"].(string)
			// Состояние выполнения
			// serviceStatus, _ := unit["active"].(string)
			// Детализированное состояние
			serviceSubStatus, _ := unit["sub"].(string)
			switch serviceSubStatus {
			case "running", "mounted", "listening", "waiting":
				serviceSubStatus = "\033[32m" + serviceSubStatus + "\033[0m"
			case "dead", "failed":
				serviceSubStatus = "\033[31m" + serviceSubStatus + "\033[0m"
			default:
				serviceSubStatus = "\033[33m" + serviceSubStatus + "\033[0m"
			}
			// Добавляем статус состояние чтение конфигурации, только если была ошибка загрузки юнита в память
			serviceLoadStatus, _ := unit["load"].(string)
			if serviceLoadStatus != "loaded" {
				serviceSubStatus = serviceSubStatus + "/" + "\033[33m" + serviceLoadStatus + "\033[0m"
			}
			// (3) Добавляем статус автозагрузки (symlink в директорию /usr/lib/systemd/system)
			for i, unitFile := range unitFiles {
				if unitFileName, ok := unitFile["unit_file"].(string); ok && unitFileName == unitName {
					unitFileState, ok := unitFile["state"].(string)
					if ok {
						if unitFileState == "disabled" {
							serviceSubStatus = serviceSubStatus + "/" + "\033[31m" + unitFileState + "\033[0m"
						} else {
							serviceSubStatus = serviceSubStatus + "/" + "\033[32m" + unitFileState + "\033[0m"
						}
						// Удаляем найденный сервис из массива юнит файлов (list-unit-files)
						unitFiles = append(unitFiles[:i], unitFiles[i+1:]...)
					}
				}
			}
			name := "[" + serviceSubStatus + "] " + unitName
			bootID := unitName
			// Уникальный ключ для проверки
			uniqueKey := name + ":" + bootID
			if !serviceMap[uniqueKey] {
				serviceMap[uniqueKey] = true
				// Добавление записи в массив
				app.journals = append(app.journals, Journal{
					name:    name,
					boot_id: bootID,
				})
			}
		}
		// (4) Добавляем выключенные сервисы, которые присутствуют в list-unit-files и отсутствуют в list-units
		for _, unitFile := range unitFiles {
			unitFileName, okName := unitFile["unit_file"].(string)
			unitFileState, okState := unitFile["state"].(string)
			if okName && okState {
				var unitName string
				if unitFileState == "enabled" {
					unitName = "[" + "\033[31m" + "dead" + "\033[0m" + "/" + "\033[32m" + unitFileState + "\033[0m" + "] " + unitFileName
				} else {
					unitName = "[" + "\033[31m" + "dead" + "\033[0m" + "/" + "\033[31m" + unitFileState + "\033[0m" + "] " + unitFileName
				}
				app.journals = append(app.journals, Journal{
					name:    unitName,
					boot_id: unitFileName,
				})
			}
		}
	// System journals from journald
	case "systemJournals":
		app.journals = append(app.journals, Journal{
			name:    "_all",
			boot_id: "_all",
		})
		// journalctl -n 1 -o json | jq keys
		var fieldFlag = "--field=" + app.journalField // SYSLOG_IDENTIFIER/_UID/_PID/_COMM/_EXE/_CMDLINE
		var cmd *exec.Cmd
		if app.sshMode {
			cmd = exec.Command(
				"ssh", append(app.sshOptions,
					"journalctl", "--no-pager", fieldFlag,
				)...)
		} else {
			cmd = exec.Command(
				"journalctl", "--no-pager", fieldFlag,
			)
		}
		if app.logging {
			slog.Info(cmd.String(), "action", "Loading the system journals")
		}
		output, err := cmd.Output()
		if !app.testMode {
			if err != nil {
				vError, _ := app.gui.View("services")
				vError.Clear()
				app.journalListFrameColor = app.errorColor
				vError.FrameColor = app.journalListFrameColor
				vError.Highlight = false
				fmt.Fprintln(vError, "\033[31mError getting journals by "+app.journalField+" field from journald\033[0m")
				return
			} else {
				vError, _ := app.gui.View("services")
				app.journalListFrameColor = app.frameColor
				if vError.FrameColor != app.frameColor {
					vError.FrameColor = app.selectedFrameColor
				}
				vError.Highlight = true
			}
		}
		if err != nil && app.testMode {
			log.Print("Error: getting journals by " + app.journalField + " field from journald")
		}
		// Создаем массив (хеш-таблица с доступом по ключу) для уникальных имен
		journalMap := make(map[string]bool)
		scanner := bufio.NewScanner(strings.NewReader(string(output)))
		for scanner.Scan() {
			journalName := strings.TrimSpace(scanner.Text())
			if journalName != "" && !journalMap[journalName] {
				journalMap[journalName] = true
				app.journals = append(app.journals, Journal{
					name:    journalName,
					boot_id: "",
				})
			}
		}
		// Сортируем список по алфавиту
		sort.Slice(app.journals, func(i, j int) bool {
			return app.journals[i].name < app.journals[j].name
		})
	// Kernel boot list from journald
	case "kernelBoot":
		// Получаем список загрузок системы
		var cmd *exec.Cmd
		if app.sshMode {
			cmd = exec.Command(
				"ssh", append(app.sshOptions,
					"journalctl", "--list-boots", "-o", "json",
				)...)
		} else {
			cmd = exec.Command(
				"journalctl", "--list-boots", "-o", "json",
			)
		}
		if app.logging {
			slog.Info(cmd.String(), "action", "Loading the kernel boot")
		}
		bootOutput, err := cmd.Output()
		if !app.testMode {
			if err != nil {
				vError, _ := app.gui.View("services")
				vError.Clear()
				app.journalListFrameColor = app.errorColor
				vError.FrameColor = app.journalListFrameColor
				vError.Highlight = false
				fmt.Fprintln(vError, "\033[31mError getting boot information from journald\033[0m")
				return
			} else {
				vError, _ := app.gui.View("services")
				app.journalListFrameColor = app.frameColor
				if vError.FrameColor != app.frameColor {
					vError.FrameColor = app.selectedFrameColor
				}
				vError.Highlight = true
			}
		}
		if err != nil && app.testMode {
			log.Print("Error: getting boot information from journald")
		}
		// Структура для парсинга JSON
		type BootInfo struct {
			BootID     string `json:"boot_id"`
			FirstEntry int64  `json:"first_entry"`
			LastEntry  int64  `json:"last_entry"`
		}
		var bootRecords []BootInfo
		err = json.Unmarshal(bootOutput, &bootRecords)
		// Если JSON невалидный или режим тестирования (Ubuntu 20.04 не поддерживает вывод в формате json)
		if err != nil || app.testMode {
			// Парсим вывод построчно
			lines := strings.Split(string(bootOutput), "\n")
			for _, line := range lines {
				// Разбиваем строку на массив
				wordsArray := strings.Fields(line)
				// 0 d914ebeb67c6428a87f9cfe3861c295d Mon 2024-11-25 12:15:07 MSK—Mon 2024-11-25 18:34:53 MSK
				if len(wordsArray) >= 8 {
					bootId := wordsArray[1]
					// Забираем дату, проверяем и изменяем формат
					var parseDate []string
					var bootDate string
					parseDate = strings.Split(wordsArray[3], "-")
					if len(parseDate) == 3 {
						bootDate = fmt.Sprintf("%s.%s.%s", parseDate[2], parseDate[1], parseDate[0])
					} else {
						continue
					}
					var stopDate string
					parseDate = strings.Split(wordsArray[6], "-")
					if len(parseDate) == 3 {
						stopDate = fmt.Sprintf("%s.%s.%s", parseDate[2], parseDate[1], parseDate[0])
					} else {
						continue
					}
					// Заполняем массив
					bootDateTime := bootDate + " " + wordsArray[4]
					stopDateTime := stopDate + " " + wordsArray[7]
					app.journals = append(app.journals, Journal{
						name:    fmt.Sprintf("\033[34m%s\033[0m - \033[34m%s\033[0m", bootDateTime, stopDateTime),
						boot_id: bootId,
					})
				}
			}
		}
		if err == nil {
			// Очищаем массив, если он был заполнен в режиме тестирования
			app.journals = []Journal{}
			// Добавляем информацию о загрузках в app.journals
			for _, bootRecord := range bootRecords {
				// Преобразуем наносекунды в секунды
				firstEntryTime := time.Unix(bootRecord.FirstEntry/1000000, bootRecord.FirstEntry%1000000)
				lastEntryTime := time.Unix(bootRecord.LastEntry/1000000, bootRecord.LastEntry%1000000)
				// Форматируем строку в формате "DD.MM.YYYY HH:MM:SS"
				const dateFormat = "02.01.2006 15:04:05"
				name := fmt.Sprintf("\033[34m%s\033[0m - \033[34m%s\033[0m", firstEntryTime.Format(dateFormat), lastEntryTime.Format(dateFormat))
				// Добавляем в массив
				app.journals = append(app.journals, Journal{
					name:    name,
					boot_id: bootRecord.BootID,
				})
			}
		}
		// Сортируем по второй дате
		sort.Slice(app.journals, func(i, j int) bool {
			date1 := parseDateFromName(app.journals[i].name)
			date2 := parseDateFromName(app.journals[j].name)
			// Сравниваем по второй дате в обратном порядке (After для сортировки по убыванию)
			return date1.After(date2)
		})
	// Audit rules keys from auditd
	case "auditd":
		// Получаем список правил
		var cmd *exec.Cmd
		if app.sshMode {
			cmd = exec.Command(
				"ssh", append(app.sshOptions,
					"auditctl", "-l",
				)...)
		} else {
			cmd = exec.Command(
				"auditctl", "-l",
			)
		}
		if app.logging {
			slog.Info(cmd.String(), "action", "Loading the audit rules keys")
		}
		output, err := cmd.Output()
		// Проверяем, что auditd установлен и на ошибку доступа
		if !app.testMode {
			if err != nil {
				var errorText string
				if err.Error() == "exit status 4" {
					errorText = "Access denied in auditd via auditctl (root only)"
				} else {
					errorText = "Auditd not installed"
				}
				vError, _ := app.gui.View("services")
				vError.Clear()
				app.journalListFrameColor = app.errorColor
				vError.FrameColor = app.journalListFrameColor
				vError.Highlight = false
				fmt.Fprintln(vError, "\033[31m"+errorText+"\033[0m")
				return
			}
			v, _ := app.gui.View("services")
			app.journalListFrameColor = app.frameColor
			if v.FrameColor != app.frameColor {
				v.FrameColor = app.selectedFrameColor
			}
			v.Highlight = true
		}
		if err != nil && app.testMode {
			if strings.Contains(err.Error(), "root to run") {
				log.Print("Access denied in auditd via auditctl (root only)")
			} else {
				log.Print("Auditd not installed")
			}
		}
		// Заполняем список всех уникальный ключей
		keysMap := make(map[string]bool)
		scanner := bufio.NewScanner(strings.NewReader(string(output)))
		for scanner.Scan() {
			rule := scanner.Text()
			if strings.Contains(rule, "-k ") {
				// Разбиваем строку правила на 2 части (split) до ключа
				rulePart := strings.Split(rule, "-k ")
				if len(rulePart) > 1 {
					// Разбиваем на слова (fields) из второй части правила после ключа и извлекаем первое слово
					keyPart := strings.Fields(rulePart[1])[0]
					if !keysMap[keyPart] {
						keysMap[keyPart] = true
						app.journals = append(app.journals, Journal{
							name:    keyPart,
							boot_id: keyPart,
						})
					}
				}
			}
		}
	}
	if !app.testMode {
		// Сохраняем неотфильтрованный список
		app.journalsNotFilter = app.journals
		// Применяем фильтр при загрузки и обновляем список служб в интерфейсе через updateServicesList() внутри функции
		app.applyFilterList()
	}
}

// Функция для загрузки списка всех журналов событий Windows через PowerShell
func (app *App) loadWinEvents() {
	app.debugStartTime = time.Now()
	app.journals = nil
	// Получаем список, игнорируем ошибки, фильтруем пустые журналы, забираем нужные параметры, сортируем и выводим в формате JSON
	cmd := exec.Command("powershell", "-Command",
		"Get-WinEvent -ListLog * -ErrorAction Ignore | "+
			"Where-Object RecordCount -ne 0 | "+
			"Where-Object RecordCount -ne $null | "+
			"Select-Object LogName,RecordCount | "+
			"Sort-Object -Descending RecordCount | "+
			"ConvertTo-Json")
	if app.logging {
		slog.Info(cmd.String(), "action", "Loading the windows event logs")
	}
	eventsJson, _ := cmd.Output()
	var events []map[string]any
	_ = json.Unmarshal(eventsJson, &events)
	for _, event := range events {
		// Извлечение названия журнала и количество записей
		LogName, _ := event["LogName"].(string)
		RecordCount, _ := event["RecordCount"].(float64)
		RecordCountInt := int(RecordCount)
		RecordCountString := strconv.Itoa(RecordCountInt)
		// Удаляем приставку
		LogView := strings.ReplaceAll(LogName, "Microsoft-Windows-", "")
		// Разбивает строку на 2 части для покраски
		LogViewSplit := strings.SplitN(LogView, "/", 2)
		if len(LogViewSplit) == 2 {
			LogView = "\033[33m" + LogViewSplit[0] + "\033[0m" + ": " + "\033[36m" + LogViewSplit[1] + "\033[0m"
		} else {
			LogView = "\033[36m" + LogView + "\033[0m"
		}
		LogView = LogView + " (" + RecordCountString + ")"
		app.journals = append(app.journals, Journal{
			name:    LogView,
			boot_id: LogName,
		})
	}
	if !app.testMode {
		app.journalsNotFilter = app.journals
		app.applyFilterList()
	}
}

// Функция для обновления окна со списком служб
func (app *App) updateServicesList() {
	// Выбираем окно для заполнения в зависимости от используемого журнала
	v, err := app.gui.View("services")
	if err != nil {
		return
	}
	// Очищаем окно
	v.Clear()
	// Вычисляем конечную позицию видимой области (стартовая позиция + максимальное количество видимых строк)
	visibleEnd := min(app.startServices+app.maxVisibleServices, len(app.journals))
	// Отображаем только элементы в пределах видимой области
	for i := app.startServices; i < visibleEnd; i++ {
		fmt.Fprintln(v, app.journals[i].name)
	}
}

// Функция для перемещения по списку журналов вниз
func (app *App) nextService(v *gocui.View, step int) error {
	// Обновляем текущее количество видимых строк в терминале (-1 заголовок)
	_, viewHeight := v.Size()
	app.maxVisibleServices = viewHeight
	// Если список журналов пустой, ничего не делаем
	if len(app.journals) == 0 {
		return nil
	}
	// Переходим к следующему, если текущий выбранный журнал не последний
	if app.selectedJournal < len(app.journals)-1 {
		// Увеличиваем индекс выбранного журнала
		app.selectedJournal += step
		// Проверяем, чтобы не выйти за пределы списка
		if app.selectedJournal >= len(app.journals) {
			app.selectedJournal = len(app.journals) - 1
		}
		// Проверяем, вышли ли за пределы видимой области (увеличиваем стартовую позицию видимости, только если дошли до 0 + maxVisibleServices)
		if app.selectedJournal >= app.startServices+app.maxVisibleServices {
			// Сдвигаем видимую область вниз
			app.startServices += step
			// Проверяем, чтобы не выйти за пределы списка
			if app.startServices > len(app.journals)-app.maxVisibleServices {
				app.startServices = len(app.journals) - app.maxVisibleServices
			}
			// Обновляем отображение списка служб
			app.updateServicesList()
		}
		// Если сдвинули видимую область, корректируем индекс для смещения курсора в интерфейсе
		if app.selectedJournal < app.startServices+app.maxVisibleServices {
			// Выбираем журнал по скорректированному индексу
			return app.selectServiceByIndex(app.selectedJournal - app.startServices)
		}
	}
	return nil
}

// Функция для перемещения по списку журналов вверх
func (app *App) prevService(v *gocui.View, step int) error {
	_, viewHeight := v.Size()
	app.maxVisibleServices = viewHeight
	if len(app.journals) == 0 {
		return nil
	}
	// Переходим к предыдущему, если текущий выбранный журнал не первый
	if app.selectedJournal > 0 {
		app.selectedJournal -= step
		// Если ушли в минус (за начало журнала), приводим к нулю
		if app.selectedJournal < 0 {
			app.selectedJournal = 0
		}
		// Проверяем, вышли ли за пределы видимой области
		if app.selectedJournal < app.startServices {
			app.startServices -= step
			if app.startServices < 0 {
				app.startServices = 0
			}
			app.updateServicesList()
		}
		if app.selectedJournal >= app.startServices {
			return app.selectServiceByIndex(app.selectedJournal - app.startServices)
		}
	}
	return nil
}

// Функция для визуального выбора журнала по индексу (смещение курсора выделения)
func (app *App) selectServiceByIndex(index int) error {
	// Получаем доступ к представлению списка служб
	v, err := app.gui.View("services")
	if err != nil {
		return err
	}
	// Обновляем счетчик в заголовке
	re := regexp.MustCompile(`\s\(.+\) >`)
	updateTitle := " (0) >"
	if len(app.journals) != 0 {
		updateTitle = " (" + strconv.Itoa(app.selectedJournal+1) + "/" + strconv.Itoa(len(app.journals)) + ") >"
	}
	v.Title = re.ReplaceAllString(v.Title, updateTitle)
	// Устанавливаем курсор на нужный индекс (строку)
	// Первый столбец (0), индекс строки
	if err := v.SetCursor(0, index); err != nil {
		return nil
	}
	return nil
}

// Функция для выбора журнала в списке сервисов по нажатию Enter
func (app *App) selectService(g *gocui.Gui, v *gocui.View) error {
	// Проверка, что есть доступ к представлению и список журналов не пустой
	if v == nil || len(app.journals) == 0 {
		return nil
	}
	// Получаем текущую позицию курсора
	_, cy := v.Cursor()
	// Читаем строку, на которой находится курсор
	line, err := v.Line(cy)
	if err != nil {
		return err
	}
	// Загружаем журналы выбранной службы, обрезая пробелы в названии
	if app.fastMode {
		go func() {
			app.loadJournalLogs(strings.TrimSpace(line), true)
		}()
	} else {
		app.loadJournalLogs(strings.TrimSpace(line), true)
	}
	// Включаем загрузку журнала (только при ручном выборе для Windows)
	app.updateFile = true
	// Фиксируем для ручного или автоматического обновления вывода журнала
	app.lastWindow = "services"
	app.lastSelected = strings.TrimSpace(line)
	return nil
}

// Функция для загрузки записей журнала выбранной службы через journalctl
// Второй параметр для обнолвения позиции делимитра нового вывода лога а также сброса автоскролл
func (app *App) loadJournalLogs(serviceName string, newUpdate bool) {
	// Сбрасываем последнюю используемую систему контейнеризации (ошибка при покраске после compose)
	app.lastContainerizationSystem = ""
	if serviceName == "" {
		return
	}
	app.debugStartTime = time.Now()
	var output []byte
	var err error
	selectUnits := app.selectUnits
	if newUpdate {
		app.lastSelectUnits = app.selectUnits
	} else {
		selectUnits = app.lastSelectUnits
	}
	// Обновляем статус с названием источника журнала (название юнита)
	if !app.testMode {
		v, err := app.gui.View("logs")
		serviceWithoutStatus := serviceName
		serviceNameSplit := strings.Split(serviceWithoutStatus, "] ")
		if len(serviceNameSplit) == 2 && len(serviceNameSplit[1]) > 0 {
			serviceWithoutStatus = serviceNameSplit[1]
		}
		if err == nil {
			v.Subtitle = "[ " + selectUnits + "/" + serviceWithoutStatus + " ]"
		}
	}
	switch {
	// Читаем журналы Windows
	case app.getOS == "windows":
		if !app.updateFile {
			return
		}
		// Отключаем чтение в горутине
		app.updateFile = false
		// Извлекаем полное имя события
		var eventName string
		for _, journal := range app.journals {
			journalBootName := removeANSI(journal.name)
			if journalBootName == serviceName {
				eventName = journal.boot_id
				break
			}
		}
		output = app.loadWinEventLog(eventName)
		if len(output) == 0 && !app.testMode {
			v, _ := app.gui.View("logs")
			v.Clear()
			return
		}
		if len(output) == 0 && app.testMode {
			app.currentLogLines = []string{}
			return
		}
	// Читаем лог выбранного по ключу журнала аудита
	case selectUnits == "auditd":
		if newUpdate {
			app.lastBootId = serviceName
		} else {
			serviceName = app.lastBootId
		}
		var cmd *exec.Cmd
		if app.sshMode {
			cmd = exec.Command(
				"ssh", append(app.sshOptions,
					"ausearch", "-k", serviceName, "--format", "interpret",
				)...)
		} else {
			cmd = exec.Command(
				"ausearch", "-k", serviceName, "--format", "interpret",
			)
		}
		if app.logging {
			slog.Info(cmd.String(), "action", "Reading logs from audit rules keys")
		}
		output, err = cmd.Output()
		if err != nil && !app.testMode {
			v, _ := app.gui.View("logs")
			v.Clear()
			fmt.Fprintln(v, "\033[31mError getting auditd logs:", err, "\033[0m")
			return
		}
		if err != nil && app.testMode {
			log.Print("Error: getting auditd logs. ", err)
		}
	// Читаем лог ядра загрузки системы
	case selectUnits == "kernelBoot":
		// Извлекаем id журнала из названия
		var boot_id string
		for _, journal := range app.journals {
			journalBootName := removeANSI(journal.name)
			if journalBootName == serviceName {
				boot_id = journal.boot_id
				break
			}
		}
		// Сохраняем название для обновления вывода журнала при фильтрации списков
		if newUpdate {
			app.lastBootId = boot_id
		} else {
			boot_id = app.lastBootId
		}
		var cmd *exec.Cmd
		if app.sshMode {
			cmd = exec.Command(
				"ssh", append(app.sshOptions,
					"journalctl", "-k", "-b", boot_id, "--no-pager", "-n", app.logViewCount,
				)...)
		} else {
			cmd = exec.Command(
				"journalctl", "-k", "-b", boot_id, "--no-pager", "-n", app.logViewCount,
			)
		}
		if app.logging {
			slog.Info(cmd.String(), "action", "Reading logs from kernel boot")
		}
		output, err = cmd.Output()
		if err != nil && !app.testMode {
			v, _ := app.gui.View("logs")
			v.Clear()
			fmt.Fprintln(v, "\033[31mError getting kernal logs:", err, "\033[0m")
			return
		}
		if err != nil && app.testMode {
			log.Print("Error: getting kernal logs. ", err)
		}
	// Загрузка журналов для юнитов systemd (--unit=UNIT) и системных журналов с фильтрацией (--field=FIELD)
	default:
		// Удаляем статусы сервисов из навзания
		serviceNameNew := strings.Split(serviceName, "] ")
		if len(serviceNameNew) >= 2 {
			serviceName = serviceNameNew[1]
		}
		var cmd *exec.Cmd
		// #34 Используем массив для формирования аргументов команды
		var args []string
		// #46 Добавляем аргумент для пользовательских журналов
		if app.selectUnits == "userUnits" {
			args = append(args, "--user")
		}
		// Фильтрация по юниту (unit)
		if serviceName != "_all" && selectUnits != "systemJournals" {
			args = []string{"--unit=" + serviceName}
		}
		// Фильтрация по полю (field)
		if serviceName != "_all" && selectUnits == "systemJournals" {
			args = []string{app.journalField + "=" + serviceName}
		}
		// Фильтрация по порядковому номеру загрузки системы (boot)
		args = append(args, "--boot="+app.journalBoot)
		// Фильтрация по приоритету
		args = append(args, "--priority="+app.journalPriority)
		// Добавляем базовые аргументы
		args = append(args, "--no-pager")
		args = append(args, "--lines="+app.logViewCount)
		// Добавляем аргументы для фильтрации по времени
		if app.sinceDateFilterMode {
			args = append(args, "--since", app.sinceFilterText)
		}
		if app.untilDateFilterMode {
			args = append(args, "--until", app.untilFilterText)
		}
		// Проверяем режим работы и формируем команду для выполнения
		if app.sshMode {
			// Создаем копию массива с заранее определенной емкостью
			sshArgs := make([]string, 0, len(app.sshOptions)+1+len(args))
			sshArgs = append(sshArgs, app.sshOptions...)
			sshArgs = append(sshArgs, "journalctl")
			sshArgs = append(sshArgs, args...)
			cmd = exec.Command("ssh", sshArgs...)
		} else {
			cmd = exec.Command("journalctl", args...)
		}
		if app.logging {
			var logSource string
			switch app.selectUnits {
			case "systemUnits":
				logSource = "Reading logs from system units"
			case "userUnits":
				logSource = "Reading logs from user units"
			default:
				logSource = "Reading logs from system journals"
			}
			slog.Info(cmd.String(), "action", logSource)
		}
		output, err = cmd.Output()
		if err != nil && !app.testMode {
			v, _ := app.gui.View("logs")
			v.Clear()
			fmt.Fprintln(v, "\033[31mError getting journald logs:", err, "\033[0m")
			return
		}
		if err != nil && app.testMode {
			log.Print("Error: getting journald logs.", err)
		}
	}
	// Сохраняем строки журнала в массив
	app.currentLogLines = strings.Split(string(output), "\n")
	if !app.testMode {
		app.updateDelimiter(newUpdate)
		// Очищаем поле ввода для фильтрации, что бы не применять фильтрацию к новому журналу
		// app.filterText = ""
		// Применяем текущий фильтр к записям для обновления вывода
		app.applyFilter(false)
	}
}

// Функция для чтения и парсинга содержимого события Windows через wevtutil
func (app *App) loadWinEventLog(eventName string) (output []byte) {
	app.lastContainerizationSystem = ""
	if eventName == "" {
		return []byte("")
	}
	cmd := exec.Command("powershell", "-Command",
		"wevtutil qe "+eventName+" /f:text -l:en /c:"+app.logViewCount+
			" /q:'*[System[TimeCreated[timediff(@SystemTime) <= 2592000000]]]'")
	if app.logging {
		slog.Info(cmd.String(), "action", "Reading logs from windows event")
	}
	eventData, _ := cmd.Output()
	// Декодирование вывода из Windows-1251 в UTF-8
	decoder := charmap.Windows1251.NewDecoder()
	decodeEventData, decodeErr := decoder.Bytes(eventData)
	if decodeErr == nil {
		eventData = decodeEventData
	}
	// Разбиваем вывод на массив
	eventStrings := strings.Split(string(eventData), "Event[")
	var eventMessage []string
	for _, eventString := range eventStrings {
		var dateTime, eventID, level, description string
		// Разбиваем элемент массива на строки
		lines := strings.Split(eventString, "\n")
		// Флаг для обработки последней строки Description с содержимым Message
		isDescription := false
		for _, line := range lines {
			// Удаляем проблемы во всех строках
			trimmedLine := strings.TrimSpace(line)
			switch {
			// Обновляем формат даты
			case strings.HasPrefix(trimmedLine, "Date:"):
				dateTime = strings.ReplaceAll(trimmedLine, "Date: ", "")
				dateTimeParse := strings.Split(dateTime, "T")
				dateParse := strings.Split(dateTimeParse[0], "-")
				timeParse := strings.Split(dateTimeParse[1], ".")
				dateTime = fmt.Sprintf("%s.%s.%s %s", dateParse[2], dateParse[1], dateParse[0], timeParse[0])
			case strings.HasPrefix(trimmedLine, "Event ID:"):
				eventID = strings.ReplaceAll(trimmedLine, "Event ID: ", "")
			case strings.HasPrefix(trimmedLine, "Level:"):
				level = strings.ReplaceAll(trimmedLine, "Level: ", "")
			case strings.HasPrefix(trimmedLine, "Description:"):
				// Фиксируем и пропускаем Description
				isDescription = true
			case isDescription:
				// Добавляем до конца текущего массива все не пустые строки
				if trimmedLine != "" {
					description += "\n" + trimmedLine
				}
			}
		}
		if dateTime != "" && eventID != "" && level != "" && description != "" {
			eventMessage = append(eventMessage, fmt.Sprintf("%s %s (%s): %s", dateTime, level, eventID, strings.TrimSpace(description)))
		}
	}
	fullMessage := strings.Join(eventMessage, "\n")
	return []byte(fullMessage)
}

// ---------------------------------------- Filesystem ----------------------------------------

// Базовая структура os.Stat
type fileInfo struct {
	name    string
	size    int64
	modTime time.Time
}

// Дочерние методы os.Stat
func (fi *fileInfo) Name() string       { return fi.name }
func (fi *fileInfo) Size() int64        { return fi.size }
func (fi *fileInfo) ModTime() time.Time { return fi.modTime }
func (fi *fileInfo) Mode() os.FileMode  { return 0o644 } // default rights
func (fi *fileInfo) IsDir() bool        { return false } // only file
func (fi *fileInfo) Sys() any           { return nil }

// Имитация метода os.Stat через exec.Command
func (app *App) statFile(path string) (os.FileInfo, error) {
	if app.sshMode {
		// Аргументы для команды stats. Ключи для перехода по символическим ссылкам
		// для получения информации о целевых файлах (для проверки доступа) и форматирования вывода
		statArgs := app.sshOptions
		statArgs = append(statArgs, "stat", "-L", "-c", "'%n|%s|%Y'", path)
		cmd := exec.Command("ssh", statArgs...)
		if app.logging {
			slog.Info(cmd.String(), "action", "Loading the log files")
		}
		output, err := cmd.Output()
		if err != nil {
			return nil, err
		}
		// Парсим вывод stat (пример вывода: /var/log/syslog|8744995|1756116219)
		line := strings.TrimSpace(string(output))
		parts := strings.Split(line, "|")
		if len(parts) != 3 {
			return nil, fmt.Errorf("%w: %s", ErrInvalidStat, line)
		}
		// Преобразуем размер и время в int
		size, err := strconv.ParseInt(parts[1], 10, 64)
		if err != nil {
			return nil, err
		}
		modTimeUnix, err := strconv.ParseInt(parts[2], 10, 64)
		if err != nil {
			return nil, err
		}
		modTime := time.Unix(modTimeUnix, 0)
		// Создаем кастомный FileInfo
		return &fileInfo{
			name:    parts[0],
			size:    size,
			modTime: modTime,
		}, nil
	} else {
		// В локальном режиме возвращяем стандартный os.Stat
		return os.Stat(path)
	}
}

// Получение массива статистики по всем файлам
func (app *App) statFiles(paths []string) (map[string]os.FileInfo, error) {
	if len(paths) == 0 {
		return make(map[string]os.FileInfo), nil
	}
	// Удаляем лишние символы из путей
	replPaths := strings.Join(paths, "\n")
	replPaths = strings.ReplaceAll(replPaths, "(", "")
	replPaths = strings.ReplaceAll(replPaths, ")", "")
	paths = strings.Split(replPaths, "\n")
	args := make([]string, len(app.sshOptions))
	copy(args, app.sshOptions)
	args = append(args, "stat", "-L", "-c", "'%n|%s|%Y'")
	args = append(args, paths...)
	cmd := exec.Command("ssh", args...)
	if app.logging {
		slog.Info(cmd.String(), "action", "Loading the log files")
	}
	output, _ := cmd.Output()
	results := make(map[string]os.FileInfo)
	lines := strings.Split(strings.TrimSpace(string(output)), "\n")
	for _, line := range lines {
		if strings.TrimSpace(line) == "" {
			continue
		}
		parts := strings.Split(line, "|")
		if len(parts) != 3 {
			return nil, fmt.Errorf("%w: %s", ErrInvalidStat, line)
		}
		size, err := strconv.ParseInt(parts[1], 10, 64)
		if err != nil {
			return nil, err
		}
		modTimeUnix, err := strconv.ParseInt(parts[2], 10, 64)
		if err != nil {
			return nil, err
		}
		modTime := time.Unix(modTimeUnix, 0)
		results[parts[0]] = &fileInfo{
			name:    parts[0],
			size:    size,
			modTime: modTime,
		}
	}
	return results, nil
}

func (app *App) loadFiles(logPath string) {
	app.logfiles = nil // сбрасываем (очищаем) массив перед загрузкой новых журналов
	var output []byte
	switch logPath {
	case "descriptor":
		var cmd *exec.Cmd
		if app.sshMode {
			cmd = exec.Command(
				"ssh", append(app.sshOptions,
					"lsof", "-Fn",
				)...)
		} else {
			cmd = exec.Command("lsof", "-Fn")
		}
		// Подавить вывод ошибок при отсутствиее прав доступа (opendir: Permission denied)
		cmd.Stderr = nil
		if app.logging {
			slog.Info(cmd.String(), "action", "Loading the log files for process descriptor")
		}
		output, _ = cmd.Output()
		// Разбиваем вывод на строки
		files := strings.Split(strings.TrimSpace(string(output)), "\n")
		// Если список файлов пустой, возвращаем ошибку Permission denied
		if !app.testMode {
			if len(files) == 0 || (len(files) == 1 && files[0] == "") {
				vError, _ := app.gui.View("varLogs")
				vError.Clear()
				// Меняем цвет окна на красный
				app.fileSystemFrameColor = app.errorColor
				vError.FrameColor = app.fileSystemFrameColor
				// Отключаем курсор и выводим сообщение об ошибке
				vError.Highlight = false
				fmt.Fprintln(vError, "\033[31mPermission denied (files not found)\033[0m")
				return
			} else {
				vError, _ := app.gui.View("varLogs")
				app.fileSystemFrameColor = app.frameColor
				if vError.FrameColor != app.frameColor {
					vError.FrameColor = app.selectedFrameColor
				}
				vError.Highlight = true
			}
		} else {
			if len(files) == 0 || (len(files) == 1 && files[0] == "") {
				log.Print("Error: permission denied (files not found from descriptor)")
			}
		}
		// Очищаем массив перед добавлением отфильтрованных файлов
		output = []byte{}
		// Фильтруем строки, которые заканчиваются на ".log" и удаляем префикс (имя файла)
		for _, file := range files {
			if strings.HasSuffix(file, ".log") {
				file = strings.TrimPrefix(file, "n")
				output = append(output, []byte(file+"\n")...)
			}
		}
	case "varlog":
		logPath = "/var/log/"
		var cmd *exec.Cmd
		// Загрузка системных журналов для macOS
		if app.getOS == "darwin" {
			args := []string{
				logPath, "/Library/Logs",
				"-type", "f",
				"-name", "*.asl", "-o",
				"-name", "*.log", "-o",
				"-name", "*log*", "-o",
				"-name", "*.[0-9]*", "-o",
				"-name", "*.[0-9].*", "-o",
				"-name", "*.pcap", "-o",
				"-name", "*.pcap.gz", "-o",
				"-name", "*.pcapng", "-o",
				"-name", "*.pcapng.gz",
			}
			if app.sshMode {
				sshArgs := make([]string, 0, len(app.sshOptions)+1+len(args))
				sshArgs = append(sshArgs, app.sshOptions...)
				sshArgs = append(sshArgs, "find")
				sshArgs = append(sshArgs, args...)
				cmd = exec.Command("ssh", sshArgs...)
			} else {
				cmd = exec.Command("find", args...)
			}
		} else {
			// Загрузка системных журналов для Linux: все файлы, которые содержат log в расширение или названии (архивы включительно), а также расширение с цифрой (архивные) и pcap/pcapng
			args := []string{
				logPath,
				"-type", "f",
				"-name", "*.log", "-o",
				"-name", "*log*", "-o",
				"-name", "*.[0-9]*", "-o",
				"-name", "*.[0-9].*", "-o",
				"-name", "*.pcap", "-o",
				"-name", "*.pcap", "-o",
				"-name", "*.pcap.gz", "-o",
				"-name", "*.pcapng", "-o",
				"-name", "*.pcapng.gz",
			}
			if app.sshMode {
				sshArgs := app.sshOptions
				sshArgs = append(sshArgs, "find")
				sshArgs = append(sshArgs, args...)
				cmd = exec.Command("ssh", sshArgs...)
			} else {
				cmd = exec.Command("find", args...)
			}
		}
		if app.logging {
			slog.Info(cmd.String(), "action", "Loading the log files from var logs")
		}
		output, _ = cmd.Output()
		// Преобразуем вывод команды в строку и делим на массив строк
		files := strings.Split(strings.TrimSpace(string(output)), "\n")
		// Если список файлов пустой, возвращаем ошибку Permission denied
		if !app.testMode {
			if len(files) == 0 || (len(files) == 1 && files[0] == "") {
				vError, _ := app.gui.View("varLogs")
				vError.Clear()
				// Меняем цвет окна на красный
				app.fileSystemFrameColor = app.errorColor
				vError.FrameColor = app.fileSystemFrameColor
				// Отключаем курсор и выводим сообщение об ошибке
				vError.Highlight = false
				fmt.Fprintln(vError, "\033[31mPermission denied (files not found)\033[0m")
				return
			} else {
				vError, _ := app.gui.View("varLogs")
				app.fileSystemFrameColor = app.frameColor
				if vError.FrameColor != app.frameColor {
					vError.FrameColor = app.selectedFrameColor
				}
				vError.Highlight = true
			}
		} else {
			if len(files) == 0 || (len(files) == 1 && files[0] == "") {
				log.Print("Error: files not found in /var/log")
			}
		}
		// Добавляем пути по умолчанию для /var/log
		logPaths := []string{
			// Ядро
			"/var/log/dmesg\n",
			// Информация о входах и выходах пользователей, перезагрузках и остановках системы
			"/var/log/wtmp\n",
			// Информация о неудачных попытках входа в систему (например, неправильные пароли)
			"/var/log/btmp\n",
			// Информация о текущих пользователях, их сеансах и входах в систему
			"/var/run/utmp\n",
			"/run/utmp\n",
			// macOS/BSD/RHEL
			"/var/log/secure\n",
			"/var/log/messages\n",
			"/var/log/daemon\n",
			"/var/log/lpd-errs\n",
			"/var/log/security.out\n",
			"/var/log/daily.out\n",
			// Службы
			"/var/log/cron\n",
			"/var/log/ftpd\n",
			"/var/log/ntpd\n",
			"/var/log/named\n",
			"/var/log/dhcpd\n",
		}
		for _, path := range logPaths {
			output = append([]byte(path), output...)
		}
	case "customPath":
		logPath = app.customPath
		var cmd *exec.Cmd
		if app.sshMode {
			cmd = exec.Command(
				"ssh", append(app.sshOptions,
					"find", logPath,
					"-type", "f",
					"-name", "*.log", "-o",
					"-name", "*.log.*", "-o",
					"-name", "*.asl", "-o",
					"-name", "*.pcap", "-o",
					"-name", "*.pcap.gz", "-o",
					"-name", "*.pcapng", "-o",
					"-name", "*.pcapng.gz",
				)...,
			)
		} else {
			cmd = exec.Command(
				"find", logPath,
				"-type", "f",
				"-name", "*.log", "-o",
				"-name", "*.log.*", "-o",
				"-name", "*.asl", "-o",
				"-name", "*.pcap", "-o",
				"-name", "*.pcap.gz", "-o",
				"-name", "*.pcapng", "-o",
				"-name", "*.pcapng.gz",
			)
		}
		if app.logging {
			slog.Info(cmd.String(), "action", "Loading the log files from custom path")
		}
		output, _ = cmd.Output()
		files := strings.Split(strings.TrimSpace(string(output)), "\n")
		if !app.testMode {
			if len(files) == 0 || (len(files) == 1 && files[0] == "") {
				vError, _ := app.gui.View("varLogs")
				vError.Clear()
				// Меняем цвет окна на красный
				app.fileSystemFrameColor = app.errorColor
				vError.FrameColor = app.fileSystemFrameColor
				// Отключаем курсор и выводим сообщение об ошибке
				vError.Highlight = false
				fmt.Fprintln(vError, "\033[31mFiles not found\033[0m")
				return
			} else {
				vError, _ := app.gui.View("varLogs")
				app.fileSystemFrameColor = app.frameColor
				if vError.FrameColor != app.frameColor {
					vError.FrameColor = app.selectedFrameColor
				}
				vError.Highlight = true
			}
		} else {
			if len(files) == 0 || (len(files) == 1 && files[0] == "") {
				log.Print("Error: files not found in ", logPath)
			}
		}
	default:
		// Определяем домашний каталог в Linux
		logPath = "/home/"
		// Домашний каталог в macOS
		if app.getOS == "darwin" {
			logPath = "/Users/"
		}
		// Ищем файлы с помощью системной утилиты find
		var cmd *exec.Cmd
		args := []string{
			logPath,
			"-type", "f",
			"-name", "*.log", "-o",
			"-name", "*.asl", "-o",
			"-name", "*.pcap", "-o",
			"-name", "*.pcap.gz", "-o",
			"-name", "*.pcapng", "-o",
			"-name", "*.pcapng.gz",
		}
		if app.sshMode {
			sshArgs := app.sshOptions
			sshArgs = append(sshArgs, "find")
			sshArgs = append(sshArgs, args...)
			cmd = exec.Command("ssh", sshArgs...)
		} else {
			cmd = exec.Command("find", args...)
		}
		if app.logging {
			slog.Info(cmd.String(), "action", "Loading the log files from users home")
		}
		output, _ = cmd.Output()
		files := strings.Split(strings.TrimSpace(string(output)), "\n")
		if !app.testMode {
			if len(files) == 0 || (len(files) == 1 && files[0] == "") {
				vError, _ := app.gui.View("varLogs")
				vError.Clear()
				vError.Highlight = false
				fmt.Fprintln(vError, "\033[32mFiles not found\033[0m")
				return
			} else {
				vError, _ := app.gui.View("varLogs")
				app.fileSystemFrameColor = app.frameColor
				if vError.FrameColor != app.frameColor {
					vError.FrameColor = app.selectedFrameColor
				}
				vError.Highlight = true
			}
		} else {
			if len(files) == 0 || (len(files) == 1 && files[0] == "") {
				log.Print("Error: files not found in home directories")
			}
		}
		// Получаем содержимое файлов из домашнего каталога пользователя root
		var cmdRootDir *exec.Cmd
		args = []string{
			"/root/",
			"-type", "f",
			"-name", "*.log", "-o",
			"-name", "*.pcap", "-o",
			"-name", "*.pcap.gz", "-o",
			"-name", "*.pcapng", "-o",
			"-name", "*.pcapng.gz",
		}
		if app.sshMode {
			sshArgs := app.sshOptions
			sshArgs = append(sshArgs, "find")
			sshArgs = append(sshArgs, args...)
			cmdRootDir = exec.Command("ssh", sshArgs...)
		} else {
			cmdRootDir = exec.Command("find", args...)
		}
		if app.logging {
			slog.Info(cmd.String(), "action", "Loading the log files from users home")
		}
		outputRootDir, err := cmdRootDir.Output()
		// Добавляем содержимое директории /root/ в общий массив, если есть доступ
		if err == nil {
			output = append(output, outputRootDir...)
		}
		if app.fileSystemFrameColor == app.errorColor && !app.testMode {
			vError, _ := app.gui.View("varLogs")
			app.fileSystemFrameColor = app.frameColor
			if vError.FrameColor != app.frameColor {
				vError.FrameColor = app.selectedFrameColor
			}
			vError.Highlight = true
		}
	}
	// Формируем массив путей
	logFullPaths := strings.Split(strings.TrimSpace(string(output)), "\n")
	// Получаем статистику по всем файлам одним вызовом в режиме ssh
	var statFiles map[string]os.FileInfo
	if app.sshMode {
		statFiles, _ = app.statFiles(logFullPaths)
	}
	// Карта уникальных путей
	serviceMap := make(map[string]bool)
	// Основной цикл
	for _, logFullPath := range logFullPaths {
		// Удаляем префикс пути и расширение файла в конце
		logName := logFullPath
		if logPath != "descriptor" {
			logName = strings.TrimPrefix(logFullPath, logPath)
		}
		logName = strings.TrimSuffix(logName, ".log")
		logName = strings.TrimSuffix(logName, ".asl")
		logName = strings.TrimSuffix(logName, ".gz")
		logName = strings.TrimSuffix(logName, ".xz")
		logName = strings.TrimSuffix(logName, ".bz2")
		logName = strings.ReplaceAll(logName, "/", " ")
		logName = strings.ReplaceAll(logName, ".log.", ".")
		logName = strings.TrimPrefix(logName, " ")
		if logPath == "/home/" || logPath == "/Users/" {
			// Разбиваем строку на слова
			words := strings.Fields(logName)
			// Берем первое и последнее слово
			firstWord := words[0]
			lastWord := words[len(words)-1]
			logName = "\x1b[0;33m" + firstWord + "\033[0m" + ": " + lastWord
		}
		// Получаем информацию о файле
		var fileInfo os.FileInfo
		var exists bool
		var err error
		if app.sshMode {
			// Извлекаем статистику из массива
			fileInfo, exists = statFiles[logFullPath]
			// Пропускаем файл, если он не найден в результатах
			if !exists {
				continue
			}
		} else {
			// Запрашиваем статистику для каждого файла в локальном режиме
			fileInfo, err = os.Stat(logFullPath)
			if err != nil {
				// Пропускаем файл, если к нему нет доступа (актуально для статических файлов из переменной logPath)
				continue
			}
		}
		// Проверяем, что файл не пустой
		if fileInfo.Size() == 0 {
			// Пропускаем пустой файл
			continue
		}
		// Получаем дату изменения
		modTime := fileInfo.ModTime()
		// Форматирование даты в формат DD.MM.YYYY HH:MM
		formattedDate := modTime.Format("02.01.2006 15:04")
		// Проверяем, что полного пути до файла еще нет в списке
		if logName != "" && !serviceMap[logFullPath] {
			// Добавляем путь в массив для проверки уникальных путей
			serviceMap[logFullPath] = true
			// Получаем имя процесса для файла дескриптора
			if logPath == "descriptor" {
				var cmd *exec.Cmd
				if app.sshMode {
					cmd = exec.Command(
						"ssh", append(app.sshOptions,
							"lsof", "-Fc", logFullPath,
						)...)
				} else {
					cmd = exec.Command(
						"lsof", "-Fc", logFullPath,
					)
				}
				cmd.Stderr = nil
				if app.logging {
					slog.Info(cmd.String(), "action", "Loading the log files for process descriptor")
				}
				outputLsof, _ := cmd.Output()
				processLines := strings.Split(strings.TrimSpace(string(outputLsof)), "\n")
				// Ищем строку, которая содержит имя процесса (только первый процесс)
				for _, line := range processLines {
					if strings.HasPrefix(line, "c") {
						// Удаляем префикс
						processName := line[1:]
						logName = "\x1b[0;33m" + processName + "\033[0m" + ": " + logName
						break
					}
				}
			}
			// Выделение цветом подов и контейнеров k3s из файловой системы
			if strings.HasPrefix(logName, "pods") {
				logName = strings.Replace(logName, "pods", "\033[33mpod\033[0m", 1)
			}
			if strings.HasPrefix(logName, "containers") {
				logName = strings.Replace(logName, "containers", "\033[32mcontainer\033[0m", 1)
			}
			// Добавляем в список
			app.logfiles = append(app.logfiles, Logfile{
				name: "[" + "\033[34m" + formattedDate + "\033[0m" + "] " + logName,
				path: logFullPath,
			})
		}
	}
	// Сортируем по дате
	sort.Slice(app.logfiles, func(i, j int) bool {
		// Извлечение дат из имени
		layout := "02.01.2006 15:04"
		dateI, _ := time.Parse(layout, extractDate(app.logfiles[i].name))
		dateJ, _ := time.Parse(layout, extractDate(app.logfiles[j].name))
		// return dateI.Before(dateJ)
		// Сортировка в обратном порядке
		return dateI.After(dateJ)
	})
	if !app.testMode {
		app.logfilesNotFilter = app.logfiles
		app.applyFilterList()
		v, err := app.gui.View("varLogs")
		if err == nil {
			curTime := time.Now().Format("02.01.2006 15:04:05")
			v.Subtitle = "[ " + curTime + " ]"
		}
	}
}

func (app *App) loadWinFiles(logPath string) {
	app.logfiles = nil
	// Определяем путь по параметру
	switch logPath {
	case "ProgramFiles":
		logPath = app.systemDisk + ":\\Program Files"
	case "ProgramFiles86":
		logPath = app.systemDisk + ":\\Program Files (x86)"
	case "ProgramData":
		logPath = app.systemDisk + ":\\ProgramData"
	case "AppDataLocal":
		logPath = app.systemDisk + ":\\Users\\" + app.userName + "\\AppData\\Local"
	case "AppDataRoaming":
		logPath = app.systemDisk + ":\\Users\\" + app.userName + "\\AppData\\Roaming"
	case "WinCustomPath":
		logPath = app.customPath
	}
	// Массив для хранений списка файлов
	var files []string
	// Получаем список корневых директорий
	rootDirs, _ := os.ReadDir(logPath)
	// Проверяем файлы внутри customPath
	if logPath == app.customPath {
		for _, rootFile := range rootDirs {
			if !rootFile.IsDir() {
				if strings.HasSuffix(strings.ToLower(rootFile.Name()), ".log") {
					files = append(files, rootFile.Name())
				}
			}
		}
	}
	// Доступ к срезу files из нескольких горутин
	var mu sync.Mutex
	// Группа ожидания для отслеживания завершения всех горутин
	var wg sync.WaitGroup
	// Ищем файлы с помощью WalkDir в Windows
	for _, rootDir := range rootDirs {
		// Проверяем, является ли текущий элемент директорией
		if rootDir.IsDir() {
			// Увеличиваем счетчик ожидаемых горутин
			wg.Add(1)
			go func(dir string) {
				// Уменьшаем счетчик горутин после завершения выполнения текущей функции
				defer wg.Done()
				// Рекурсивно обходим все файлы и подкаталоги в текущей директории
				err := filepath.WalkDir(filepath.Join(logPath, dir), func(path string, d os.DirEntry, err error) error {
					if err != nil {
						// Игнорируем ошибки, чтобы не прерывать поиск
						return nil
					}
					// Проверяем, что текущий элемент не является директорией и имеет расширение .log
					if !d.IsDir() && strings.HasSuffix(strings.ToLower(d.Name()), ".log") {
						// Получаем относительный путь (без корневого пути logPath)
						relPath, _ := filepath.Rel(logPath, path)
						// Используем мьютекс для добавления файла в срез
						mu.Lock()
						files = append(files, relPath)
						mu.Unlock()
					}
					return nil
				})
				if err != nil {
					return
				}
			}(
				// Передаем имя текущей директории в горутину
				rootDir.Name(),
			)
		}
	}
	// Ждем завершения всех запущенных горутин
	wg.Wait()
	// Объединяем все пути в одну строку, разделенную символом новой строки
	output := strings.Join(files, "\n")
	if !app.testMode {
		// Если список файлов пустой, возвращаем ошибку
		if len(files) == 0 || (len(files) == 1 && files[0] == "") {
			vError, _ := app.gui.View("varLogs")
			vError.Clear()
			app.fileSystemFrameColor = app.errorColor
			vError.FrameColor = app.fileSystemFrameColor
			vError.Highlight = false
			fmt.Fprintln(vError, "\033[31mPermission denied (files not found)\033[0m")
			return
		} else {
			vError, _ := app.gui.View("varLogs")
			app.fileSystemFrameColor = app.frameColor
			if vError.FrameColor != app.frameColor {
				vError.FrameColor = app.selectedFrameColor
			}
			vError.Highlight = true
		}
	} else {
		if len(files) == 0 || (len(files) == 1 && files[0] == "") {
			log.Print("Error: files not found in ", logPath)
		}
	}
	serviceMap := make(map[string]bool)
	scanner := bufio.NewScanner(strings.NewReader(string(output)))
	for scanner.Scan() {
		// Формируем полный путь к файлу
		logFullPath := logPath + "\\" + scanner.Text()
		// Формируем имя файла для списка
		logName := scanner.Text()
		logName = strings.TrimSuffix(logName, ".log")
		logName = strings.ReplaceAll(logName, "\\", " ")
		// Получаем информацию о файле
		fileInfo, err := os.Stat(logFullPath)
		// Пропускаем файлы, к которым нет доступа
		if err != nil {
			continue
		}
		// Пропускаем пустые файлы
		if fileInfo.Size() == 0 {
			continue
		}
		// Получаем дату изменения
		modTime := fileInfo.ModTime()
		// Форматирование даты в формат DD.MM.YYYY HH:MM
		formattedDate := modTime.Format("02.01.2006 15:04")
		// Проверяем, что полного пути до файла еще нет в списке
		if logName != "" && !serviceMap[logFullPath] {
			// Добавляем путь в массив для проверки уникальных путей
			serviceMap[logFullPath] = true
			// Добавляем в список
			app.logfiles = append(app.logfiles, Logfile{
				name: "[" + "\033[34m" + formattedDate + "\033[0m" + "] " + logName,
				path: logFullPath,
			})
		}
	}
	// Сортируем по дате
	sort.Slice(app.logfiles, func(i, j int) bool {
		layout := "02.01.2006 15:04"
		dateI, _ := time.Parse(layout, extractDate(app.logfiles[i].name))
		dateJ, _ := time.Parse(layout, extractDate(app.logfiles[j].name))
		return dateI.After(dateJ)
	})
	if !app.testMode {
		app.logfilesNotFilter = app.logfiles
		app.applyFilterList()
		v, err := app.gui.View("varLogs")
		if err == nil {
			curTime := time.Now().Format("02.01.2006 15:04:05")
			v.Subtitle = "[ " + curTime + " ]"
		}
	}
}

// Функция для извлечения первой втречающейся даты в формате DD.MM.YYYY HH:MM
func extractDate(name string) string {
	re := regexp.MustCompile(`\d{2}\.\d{2}\.\d{4}\s\d{2}:\d{2}`)
	return re.FindString(name)
}

func (app *App) updateLogsList() {
	v, err := app.gui.View("varLogs")
	if err != nil {
		return
	}
	v.Clear()
	visibleEnd := min(app.startFiles+app.maxVisibleFiles, len(app.logfiles))
	for i := app.startFiles; i < visibleEnd; i++ {
		fmt.Fprintln(v, app.logfiles[i].name)
	}
}

func (app *App) nextFileName(v *gocui.View, step int) error {
	_, viewHeight := v.Size()
	app.maxVisibleFiles = viewHeight
	if len(app.logfiles) == 0 {
		return nil
	}
	if app.selectedFile < len(app.logfiles)-1 {
		app.selectedFile += step
		if app.selectedFile >= len(app.logfiles) {
			app.selectedFile = len(app.logfiles) - 1
		}
		if app.selectedFile >= app.startFiles+app.maxVisibleFiles {
			app.startFiles += step
			if app.startFiles > len(app.logfiles)-app.maxVisibleFiles {
				app.startFiles = len(app.logfiles) - app.maxVisibleFiles
			}
			app.updateLogsList()
		}
		if app.selectedFile < app.startFiles+app.maxVisibleFiles {
			return app.selectFileByIndex(app.selectedFile - app.startFiles)
		}
	}
	return nil
}

func (app *App) prevFileName(v *gocui.View, step int) error {
	_, viewHeight := v.Size()
	app.maxVisibleFiles = viewHeight
	if len(app.logfiles) == 0 {
		return nil
	}
	if app.selectedFile > 0 {
		app.selectedFile -= step
		if app.selectedFile < 0 {
			app.selectedFile = 0
		}
		if app.selectedFile < app.startFiles {
			app.startFiles -= step
			if app.startFiles < 0 {
				app.startFiles = 0
			}
			app.updateLogsList()
		}
		if app.selectedFile >= app.startFiles {
			return app.selectFileByIndex(app.selectedFile - app.startFiles)
		}
	}
	return nil
}

func (app *App) selectFileByIndex(index int) error {
	v, err := app.gui.View("varLogs")
	if err != nil {
		return err
	}
	// Обновляем счетчик в заголовке
	re := regexp.MustCompile(`\s\(.+\) >`)
	updateTitle := " (0) >"
	if len(app.logfiles) != 0 {
		updateTitle = " (" + strconv.Itoa(app.selectedFile+1) + "/" + strconv.Itoa(len(app.logfiles)) + ") >"
	}
	v.Title = re.ReplaceAllString(v.Title, updateTitle)
	if err := v.SetCursor(0, index); err != nil {
		return nil
	}
	return nil
}

func (app *App) selectFile(g *gocui.Gui, v *gocui.View) error {
	if v == nil || len(app.logfiles) == 0 {
		return nil
	}
	_, cy := v.Cursor()
	line, err := v.Line(cy)
	if err != nil {
		return err
	}
	if app.fastMode {
		go func() {
			app.loadFileLogs(strings.TrimSpace(line), true)
		}()
	} else {
		app.loadFileLogs(strings.TrimSpace(line), true)
	}
	app.lastWindow = "varLogs"
	app.lastSelected = strings.TrimSpace(line)
	return nil
}

// Функция для чтения файла
func (app *App) loadFileLogs(logName string, newUpdate bool) {
	app.lastContainerizationSystem = ""
	if logName == "" {
		return
	}
	app.debugStartTime = time.Now()
	// В параметре logName имя файла при выборе возвращяется без символов покраски
	// Получаем путь из массива по имени
	var logFullPath string
	for _, logfile := range app.logfiles {
		// Удаляем покраску из имени файла в сохраненном массиве
		logFileName := ansiEscape.ReplaceAllString(logfile.name, "")
		// Ищем переданное в функцию имя файла и извлекаем путь
		if logFileName == logName {
			logFullPath = logfile.path
			break
		}
	}
	// Обновляем статус с названием источника журнала (полный путь к файлу)
	if !app.testMode {
		v, err := app.gui.View("logs")
		if err == nil {
			v.Subtitle = "[ " + logFullPath + " ]"
		}
	}
	if newUpdate {
		app.lastLogPath = logFullPath
		// Фиксируем новую дату изменения и размер для выбранного файла
		fileInfo, err := app.statFile(logFullPath)
		if err != nil {
			return
		}
		fileModTime := fileInfo.ModTime()
		fileSize := fileInfo.Size()
		app.lastDateUpdateFile = fileModTime
		app.lastSizeFile = fileSize
		app.updateFile = true
	} else {
		logFullPath = app.lastLogPath
		// Проверяем дату изменения
		fileInfo, err := app.statFile(logFullPath)
		if err != nil {
			return
		}
		fileModTime := fileInfo.ModTime()
		fileSize := fileInfo.Size()
		// Обновлять файл в горутине, только если есть изменения (проверяем дату модификации и размер)
		if fileModTime != app.lastDateUpdateFile || fileSize != app.lastSizeFile {
			app.lastDateUpdateFile = fileModTime
			app.lastSizeFile = fileSize
			app.updateFile = true
		} else {
			app.updateFile = false
		}
	}
	// Читаем файл, толькое если были изменения
	if app.updateFile {
		// Читаем логи в системе Windows
		if app.getOS == "windows" {
			decodedOutput, stringErrors := app.loadWinFileLog(logFullPath)
			if stringErrors != "nil" && !app.testMode {
				v, _ := app.gui.View("logs")
				v.Clear()
				fmt.Fprintln(v, "\033[31mError", stringErrors, "\033[0m")
				return
			}
			if stringErrors != "nil" && app.testMode {
				log.Print("Error: ", stringErrors)
			}
			app.currentLogLines = strings.Split(string(decodedOutput), "\n")
		} else {
			var cmd *exec.Cmd
			// Читаем логи в системах UNIX (Linux/Darwin/*BSD)
			switch {
			// Читаем файлы в формате ASL (Apple System Log)
			case strings.HasSuffix(logFullPath, "asl"):
				if app.sshMode {
					cmd = exec.Command(
						"ssh", append(app.sshOptions,
							"syslog", "-f", logFullPath,
						)...)
				} else {
					cmd = exec.Command(
						"syslog", "-f", logFullPath,
					)
				}
				if app.logging {
					slog.Info(cmd.String(), "action", "Reading logs in asl format")
				}
				output, err := cmd.Output()
				if err != nil && !app.testMode {
					v, _ := app.gui.View("logs")
					v.Clear()
					fmt.Fprintln(v, " \033[31mError reading log using syslog tool in ASL (Apple System Log) format.\n", err, "\033[0m")
					return
				}
				if err != nil && app.testMode {
					log.Print("Error: reading log using syslog tool in ASL (Apple System Log) format. ", err)
				}
				app.currentLogLines = strings.Split(string(output), "\n")
			// Читаем журналы Packet Capture в формате pcap/pcapng
			case strings.HasSuffix(logFullPath, "pcap") || strings.HasSuffix(logFullPath, "pcapng"):
				if app.sshMode {
					cmd = exec.Command(
						"ssh", append(app.sshOptions,
							"tcpdump", "-n", "-r", logFullPath,
						)...)
				} else {
					cmd = exec.Command(
						"tcpdump", "-n", "-r", logFullPath,
					)
				}
				if app.logging {
					slog.Info(cmd.String(), "action", "Reading logs in pcap format")
				}
				output, err := cmd.Output()
				if err != nil && !app.testMode {
					v, _ := app.gui.View("logs")
					v.Clear()
					fmt.Fprintln(v, " \033[31mError reading log using tcpdump tool.\n", err, "\033[0m")
					return
				}
				if err != nil && app.testMode {
					log.Print("Error: reading log using tcpdump tool. ", err)
				}
				app.currentLogLines = strings.Split(string(output), "\n")
			// Packet Filter (PF) Firewall (OpenBSD)
			case strings.HasSuffix(logFullPath, "pflog"):
				if app.sshMode {
					cmd = exec.Command(
						"ssh", append(app.sshOptions,
							"tcpdump", "-e", "-n", "-r", logFullPath,
						)...)
				} else {
					cmd = exec.Command(
						"tcpdump", "-e", "-n", "-r", logFullPath,
					)
				}
				if app.logging {
					slog.Info(cmd.String(), "action", "Reading logs in pflog format")
				}
				output, err := cmd.Output()
				if err != nil && !app.testMode {
					v, _ := app.gui.View("logs")
					v.Clear()
					fmt.Fprintln(v, " \033[31mError reading log using tcpdump tool.\n", err, "\033[0m")
					return
				}
				app.currentLogLines = strings.Split(string(output), "\n")
			// Читаем архивные логи в формате pcap/pcapng (macOS)
			case strings.HasSuffix(logFullPath, "pcap.gz") || strings.HasSuffix(logFullPath, "pcapng.gz"):
				var unpacker = "gzip"
				// Создаем временный файл
				tmpFile, err := os.CreateTemp("", "temp-*.pcap")
				if err != nil && !app.testMode {
					vError, _ := app.gui.View("logs")
					vError.Clear()
					fmt.Fprintln(vError, " \033[31mError create temp file.\n", err, "\033[0m")
					return
				}
				// Удаляем временный файл после обработки
				defer os.Remove(tmpFile.Name())
				var cmdUnzip *exec.Cmd
				if app.sshMode {
					cmdUnzip = exec.Command(
						"ssh", append(app.sshOptions,
							unpacker, "-dc", logFullPath,
						)...)
				} else {
					cmdUnzip = exec.Command(
						unpacker, "-dc", logFullPath,
					)
				}
				cmdUnzip.Stdout = tmpFile
				if err := cmdUnzip.Start(); err != nil && !app.testMode {
					vError, _ := app.gui.View("logs")
					vError.Clear()
					fmt.Fprintln(vError, " \033[31mError starting", unpacker, "tool.\n", err, "\033[0m")
					return
				}
				if err := cmdUnzip.Wait(); err != nil && !app.testMode {
					vError, _ := app.gui.View("logs")
					vError.Clear()
					fmt.Fprintln(vError, " \033[31mError decompressing file with", unpacker, "tool.\n", err, "\033[0m")
					return
				}
				// Закрываем временный файл, чтобы tcpdump мог его открыть
				if err := tmpFile.Close(); err != nil && !app.testMode {
					vError, _ := app.gui.View("logs")
					vError.Clear()
					fmt.Fprintln(vError, " \033[31mError closing temp file.\n", err, "\033[0m")
					return
				}
				// Создаем команду для tcpdump
				var cmdTcpdump *exec.Cmd
				if app.sshMode {
					cmdTcpdump = exec.Command(
						"ssh", append(app.sshOptions,
							"tcpdump", "-n", "-r", tmpFile.Name(),
						)...)
				} else {
					cmdTcpdump = exec.Command(
						"tcpdump", "-n", "-r", tmpFile.Name(),
					)
				}
				if app.logging {
					slog.Info(cmd.String(), "action", "Reading logs in pcap format")
				}
				tcpdumpOut, err := cmdTcpdump.StdoutPipe()
				if err != nil && !app.testMode {
					vError, _ := app.gui.View("logs")
					vError.Clear()
					fmt.Fprintln(vError, " \033[31mError creating stdout pipe for tcpdump.\n", err, "\033[0m")
					return
				}
				// Запускаем tcpdump
				if err := cmdTcpdump.Start(); err != nil && !app.testMode {
					vError, _ := app.gui.View("logs")
					vError.Clear()
					fmt.Fprintln(vError, " \033[31mError starting tcpdump.\n", err, "\033[0m")
					return
				}
				// Читаем вывод tcpdump построчно
				scanner := bufio.NewScanner(tcpdumpOut)
				var lines []string
				for scanner.Scan() {
					lines = append(lines, scanner.Text())
				}
				if err := scanner.Err(); err != nil && !app.testMode {
					vError, _ := app.gui.View("logs")
					vError.Clear()
					fmt.Fprintln(vError, " \033[31mError reading output from tcpdump.\n", err, "\033[0m")
					return
				}
				// Ожидаем завершения tcpdump
				if err := cmdTcpdump.Wait(); err != nil && !app.testMode {
					vError, _ := app.gui.View("logs")
					vError.Clear()
					fmt.Fprintln(vError, " \033[31mError finishing tcpdump.\n", err, "\033[0m")
					return
				}
				app.currentLogLines = lines
			// Читаем архивные логи (unpack + stdout) в формате: gz/xz/bz2
			case strings.HasSuffix(logFullPath, ".gz") || strings.HasSuffix(logFullPath, ".xz") || strings.HasSuffix(logFullPath, ".bz2"):
				var unpacker string
				switch {
				case strings.HasSuffix(logFullPath, ".gz"):
					unpacker = "gzip"
				case strings.HasSuffix(logFullPath, ".xz"):
					unpacker = "xz"
				case strings.HasSuffix(logFullPath, ".bz2"):
					unpacker = "bzip2"
				}
				var cmdUnzip *exec.Cmd
				var cmdTail *exec.Cmd
				if app.sshMode {
					cmdUnzip = exec.Command(
						"ssh", append(app.sshOptions,
							unpacker, "-dc", logFullPath,
						)...)
					cmdTail = exec.Command(
						"ssh", append(app.sshOptions,
							"tail", "-n", app.logViewCount,
						)...)
				} else {
					cmdUnzip = exec.Command(
						unpacker, "-dc", logFullPath,
					)
					cmdTail = exec.Command(
						"tail", "-n", app.logViewCount,
					)
				}
				if app.logging {
					slog.Info(cmdUnzip.String(), "action", "Reading the archive log")
				}
				pipe, err := cmdUnzip.StdoutPipe()
				if err != nil && !app.testMode {
					vError, _ := app.gui.View("logs")
					vError.Clear()
					fmt.Fprintln(vError, " \033[31mError creating pipe for", unpacker, "tool.\n", err, "\033[0m")
					return
				}
				// Стандартный вывод программы передаем в stdin tail
				if app.logging {
					slog.Info(cmdTail.String(), "action", "Reading the archive log")
				}
				cmdTail.Stdin = pipe
				out, err := cmdTail.StdoutPipe()
				if err != nil && !app.testMode {
					vError, _ := app.gui.View("logs")
					vError.Clear()
					fmt.Fprintln(vError, " \033[31mError creating stdout pipe for tail.\n", err, "\033[0m")
					return
				}
				// Запуск команд
				if err := cmdUnzip.Start(); err != nil && !app.testMode {
					vError, _ := app.gui.View("logs")
					vError.Clear()
					fmt.Fprintln(vError, " \033[31mError starting", unpacker, "tool.\n", err, "\033[0m")
					return
				}
				if err := cmdTail.Start(); err != nil && !app.testMode {
					vError, _ := app.gui.View("logs")
					vError.Clear()
					fmt.Fprintln(vError, " \033[31mError starting tail from", unpacker, "stdout.\n", err, "\033[0m")
					return
				}
				// Чтение вывода
				output, err := io.ReadAll(out)
				if err != nil && !app.testMode {
					vError, _ := app.gui.View("logs")
					vError.Clear()
					fmt.Fprintln(vError, " \033[31mError reading output from tail.\n", err, "\033[0m")
					return
				}
				// Ожидание завершения команд
				if err := cmdUnzip.Wait(); err != nil && !app.testMode {
					vError, _ := app.gui.View("logs")
					vError.Clear()
					fmt.Fprintln(vError, " \033[31mError reading archive log using", unpacker, "tool.\n", err, "\033[0m")
					return
				}
				if err := cmdTail.Wait(); err != nil && !app.testMode {
					vError, _ := app.gui.View("logs")
					vError.Clear()
					fmt.Fprintln(vError, " \033[31mError reading log using tail tool.\n", err, "\033[0m")
					return
				}
				// Выводим содержимое
				app.currentLogLines = strings.Split(string(output), "\n")
			// Читаем бинарные файлы с помощью last для wtmp, а также utmp (OpenBSD) и utx.log (FreeBSD)
			case strings.Contains(logFullPath, "wtmp") || strings.Contains(logFullPath, "utmp") || strings.Contains(logFullPath, "utx.log"):
				if app.sshMode {
					cmd = exec.Command(
						"ssh", append(app.sshOptions,
							"last", "-f", logFullPath,
						)...)
				} else {
					cmd = exec.Command(
						"last", "-f", logFullPath,
					)
				}
				if app.logging {
					slog.Info(cmd.String(), "action", "Reading logs in wtmp/utmp/utx format")
				}
				output, err := cmd.Output()
				if err != nil && !app.testMode {
					v, _ := app.gui.View("logs")
					v.Clear()
					fmt.Fprintln(v, " \033[31mError reading log using last tool.\n", err, "\033[0m")
					return
				}
				// Разбиваем вывод на строки
				lines := strings.Split(string(output), "\n")
				var filteredLines []string
				// Фильтруем строки, исключая последнюю строку и пустые строки
				for _, line := range lines {
					trimmedLine := strings.TrimSpace(line)
					if trimmedLine != "" && !strings.Contains(trimmedLine, "begins") {
						filteredLines = append(filteredLines, trimmedLine)
					}
				}
				// Переворачиваем порядок строк
				for i, j := 0, len(filteredLines)-1; i < j; i, j = i+1, j-1 {
					filteredLines[i], filteredLines[j] = filteredLines[j], filteredLines[i]
				}
				app.currentLogLines = filteredLines
			// lastb for btmp
			case strings.Contains(logFullPath, "btmp"):
				if app.sshMode {
					cmd = exec.Command(
						"ssh", append(app.sshOptions,
							"lastb", "-f", logFullPath,
						)...)
				} else {
					cmd = exec.Command(
						"lastb", "-f", logFullPath,
					)
				}
				if app.logging {
					slog.Info(cmd.String(), "action", "Reading logs in btmp format")
				}
				output, err := cmd.Output()
				if err != nil && !app.testMode {
					v, _ := app.gui.View("logs")
					v.Clear()
					fmt.Fprintln(v, " \033[31mError reading log using lastb tool.\n", err, "\033[0m")
					return
				}
				lines := strings.Split(string(output), "\n")
				var filteredLines []string
				for _, line := range lines {
					trimmedLine := strings.TrimSpace(line)
					if trimmedLine != "" && !strings.Contains(trimmedLine, "begins") {
						filteredLines = append(filteredLines, trimmedLine)
					}
				}
				for i, j := 0, len(filteredLines)-1; i < j; i, j = i+1, j-1 {
					filteredLines[i], filteredLines[j] = filteredLines[j], filteredLines[i]
				}
				app.currentLogLines = filteredLines
			// Выводим содержимое из команды lastlog
			case strings.HasSuffix(logFullPath, "lastlog"):
				if app.sshMode {
					cmd = exec.Command(
						"ssh", append(app.sshOptions,
							"lastlog",
						)...)
				} else {
					cmd = exec.Command(
						"lastlog",
					)
				}
				if app.logging {
					slog.Info(cmd.String(), "action", "Reading logs in lastlog format")
				}
				output, err := cmd.Output()
				if err != nil && !app.testMode {
					v, _ := app.gui.View("logs")
					v.Clear()
					fmt.Fprintln(v, " \033[31mError reading log using lastlog tool.\n", err, "\033[0m")
					return
				}
				app.currentLogLines = strings.Split(string(output), "\n")
			// lastlogin for FreeBSD
			case strings.HasSuffix(logFullPath, "lastlogin"):
				if app.sshMode {
					cmd = exec.Command(
						"ssh", append(app.sshOptions,
							"lastlogin",
						)...)
				} else {
					cmd = exec.Command(
						"lastlogin",
					)
				}
				if app.logging {
					slog.Info(cmd.String(), "action", "Reading logs in lastlogin format")
				}
				output, err := cmd.Output()
				if err != nil && !app.testMode {
					v, _ := app.gui.View("logs")
					v.Clear()
					fmt.Fprintln(v, " \033[31mError reading log using lastlogin tool.\n", err, "\033[0m")
					return
				}
				app.currentLogLines = strings.Split(string(output), "\n")
			default:
				if app.sshMode {
					cmd = exec.Command(
						"ssh", append(app.sshOptions,
							"tail", "-n", app.logViewCount, logFullPath,
						)...)
				} else {
					cmd = exec.Command(
						"tail", "-n", app.logViewCount, logFullPath,
					)
				}
				if app.logging {
					slog.Info(cmd.String(), "action", "Reading log file")
				}
				output, err := cmd.Output()
				if err != nil && !app.testMode {
					v, _ := app.gui.View("logs")
					v.Clear()
					fmt.Fprintln(v, " \033[31mError reading log using tail tool.\n", err, "\033[0m")
					return
				}
				app.currentLogLines = strings.Split(string(output), "\n")
			}
		}
		if !app.testMode {
			app.updateDelimiter(newUpdate)
			app.applyFilter(false)
		}
	}
}

// Функция для чтения файла с опредилением кодировки в Windows
func (app *App) loadWinFileLog(filePath string) (output []byte, stringErrors string) {
	app.lastContainerizationSystem = ""
	if filePath == "" {
		return nil, "file not selected"
	}
	app.debugStartTime = time.Now()
	// Открываем файл
	file, err := os.Open(filePath)
	if err != nil {
		return nil, fmt.Sprintf("open file: %v", err)
	}
	defer file.Close()
	// Получаем информацию о файле
	stat, err := file.Stat()
	if err != nil {
		return nil, fmt.Sprintf("get file stat: %v", err)
	}
	// Получаем размер файла
	fileSize := stat.Size()
	// Буфер для хранения последних строк
	var buffer []byte
	lineCount := 0
	// Размер буфера чтения (читаем по 1КБ за раз)
	readSize := int64(1024)
	// Преобразуем строку с максимальным количеством строк в int
	logViewCountInt, _ := strconv.Atoi(app.logViewCount)
	// Читаем файл с конца
	for fileSize > 0 && lineCount < logViewCountInt {
		if fileSize < readSize {
			readSize = fileSize
		}
		_, err := file.Seek(fileSize-readSize, 0)
		if err != nil {
			return nil, fmt.Sprintf("detect the end of a file via seek: %v", err)
		}
		tempBuffer := make([]byte, readSize)
		_, err = file.Read(tempBuffer)
		if err != nil {
			return nil, fmt.Sprintf("read file: %v", err)
		}
		buffer = append(tempBuffer, buffer...)
		lineCount = strings.Count(string(buffer), "\n")
		fileSize -= int64(readSize)
	}
	// Проверка на UTF-16 с BOM
	utf16withBOM := func(data []byte) bool {
		return len(data) >= 2 && ((data[0] == 0xFF && data[1] == 0xFE) || (data[0] == 0xFE && data[1] == 0xFF))
	}
	// Проверка на UTF-16 LE без BOM
	utf16withoutBOM := func(data []byte) bool {
		if len(data)%2 != 0 {
			return false
		}
		for i := 1; i < len(data); i += 2 {
			if data[i] != 0x00 {
				return false
			}
		}
		return true
	}
	var decodedOutput []byte
	switch {
	case utf16withBOM(buffer):
		// Декодируем UTF-16 с BOM
		decodedOutput, err = winUnicode.UTF16(winUnicode.LittleEndian, winUnicode.ExpectBOM).NewDecoder().Bytes(buffer)
		if err != nil {
			return nil, fmt.Sprintf("decoding from UTF-16 with BOM: %v", err)
		}
	case utf16withoutBOM(buffer):
		// Декодируем UTF-16 LE без BOM
		decodedOutput, err = winUnicode.UTF16(winUnicode.LittleEndian, winUnicode.IgnoreBOM).NewDecoder().Bytes(buffer)
		if err != nil {
			return nil, fmt.Sprintf("decoding from UTF-16 LE without BOM: %v", err)
		}
	case utf8.Valid(buffer):
		// Декодируем UTF-8
		decodedOutput = buffer
	default:
		// Декодируем Windows-1251
		decodedOutput, err = charmap.Windows1251.NewDecoder().Bytes(buffer)
		if err != nil {
			return nil, fmt.Sprintf("decoding from Windows-1251: %v", err)
		}
	}
	return decodedOutput, "nil"
}

// ---------------------------------------- Docker/Compose/Podman/Kubernetes ----------------------------------------

func (app *App) loadDockerContainer(containerizationSystem string) {
	if containerizationSystem == "kubernetes" {
		containerizationSystem = "kubectl"
	}
	app.dockerContainers = nil
	// Создаем контекст выполнения удаленных команд по ssh (timeout 5s)
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	// Получаем версию для проверки, что система контейнеризации установлена
	var cmd *exec.Cmd
	if app.sshMode {
		if containerizationSystem == "compose" {
			// Корректируем формат команды
			if app.dockerCompose == "docker compose" {
				cmd = exec.CommandContext(
					ctx,
					"ssh", append(app.sshOptions,
						"docker", "compose", "version",
					)...)
			} else {
				cmd = exec.CommandContext(
					ctx,
					"ssh", append(app.sshOptions,
						app.dockerCompose, "version",
					)...)
			}
		} else {
			cmd = exec.CommandContext(
				ctx,
				"ssh", append(app.sshOptions,
					containerizationSystem, "version",
				)...)
		}
	} else {
		if containerizationSystem == "compose" {
			if app.dockerCompose == "docker compose" {
				cmd = exec.Command(
					"docker", "compose", "version",
				)
			} else {
				cmd = exec.Command(
					app.dockerCompose, "version",
				)
			}
		} else {
			cmd = exec.Command(
				containerizationSystem, "version",
			)
		}
	}
	if app.logging {
		slog.Info(cmd.String(), "action", "Check the binary")
	}
	version, err := cmd.Output()
	if err != nil && !app.testMode {
		vError, _ := app.gui.View("docker")
		vError.Clear()
		app.dockerFrameColor = app.errorColor
		vError.FrameColor = app.dockerFrameColor
		vError.Highlight = false
		switch containerizationSystem {
		case "kubectl":
			if strings.Contains(string(version), "Version:") {
				// Проверяем вывод kubectl, может быть ошибка подключения к кластеру
				cmd = exec.Command(containerizationSystem, "get", "nodes")
				if app.logging {
					slog.Info(cmd.String(), "action", "Check the connect to kubernetes cluster")
				}
				output, err := cmd.CombinedOutput()
				if err != nil {
					fmt.Fprintln(vError, "\033[31mError connection to the Kubernetes cluster\033[0m")
					app.currentLogLines = []string{string(output)}
					app.applyFilter(false)
				}
			} else {
				fmt.Fprintln(vError, "\033[31m"+containerizationSystem+" not installed (environment not found)\033[0m")
			}
		case "compose":
			fmt.Fprintln(vError, "\033[31m"+app.dockerCompose+" not installed (environment not found)\033[0m")
		default:
			fmt.Fprintln(vError, "\033[31m"+containerizationSystem+" not installed (environment not found)\033[0m")
		}
		return
	}
	if err != nil && app.testMode {
		switch containerizationSystem {
		case "kubectl":
			log.Print("Error:", containerizationSystem+" not installed or no connection to the Kubernetes cluster")
		case "compose":
			log.Print("Error:", app.dockerCompose+" not installed (environment not found)")
		default:
			log.Print("Error:", containerizationSystem+" not installed (environment not found)")
		}
	}
	switch containerizationSystem {
	case "kubectl":
		// Получаем список подов из k8s
		if app.sshMode {
			cmd = exec.CommandContext(
				ctx,
				"ssh", append(app.sshOptions,
					containerizationSystem, "get", "pods", "--context", app.kubernetesContext, app.kubernetesNamespace,
					"-o", "'jsonpath={range .items[*]}{.metadata.uid} {.metadata.name} {.status.phase} {.metadata.namespace}{\"\\n\"}{end}'",
				)...)
		} else {
			cmd = exec.CommandContext(
				ctx,
				containerizationSystem, "get", "pods", "--context", app.kubernetesContext, app.kubernetesNamespace,
				"-o", "jsonpath={range .items[*]}{.metadata.uid} {.metadata.name} {.status.phase} {.metadata.namespace}{\"\\n\"}{end}",
			)
		}
	case "compose":
		if app.sshMode {
			// Корректируем положение флага context в команде compose (после docker и перед context)
			if app.dockerCompose == "docker-compose" {
				cmd = exec.CommandContext(
					ctx,
					"ssh", append(app.sshOptions,
						app.dockerCompose,
						"--context", app.dockerContext, "ls", "-a",
					)...)
			} else {
				cmd = exec.CommandContext(
					ctx,
					"ssh", append(app.sshOptions,
						"docker",
						"--context", app.dockerContext, "compose", "ls", "-a",
					)...)
			}
		} else {
			if app.dockerCompose == "docker-compose" {
				cmd = exec.CommandContext(
					ctx,
					app.dockerCompose,
					"--context", app.dockerContext, "ls", "-a",
				)
			} else {
				cmd = exec.CommandContext(
					ctx,
					"docker",
					"--context", app.dockerContext, "compose", "ls", "-a",
				)
			}
		}

	case "podman":
		// #38 Отключаем использование контекста в Podman, если он не задан
		if app.podmanContext == "" {
			if app.sshMode {
				cmd = exec.CommandContext(
					ctx,
					"ssh", append(app.sshOptions,
						containerizationSystem,
						"ps", "-a",
						"--format", "'{{.ID}} {{.Names}} {{.State}}'",
					)...)
			} else {
				cmd = exec.CommandContext(
					ctx,
					containerizationSystem,
					"ps", "-a",
					"--format", "{{.ID}} {{.Names}} {{.State}}",
				)
			}
		} else {
			if app.sshMode {
				cmd = exec.CommandContext(
					ctx,
					"ssh", append(app.sshOptions,
						containerizationSystem,
						"--context", app.podmanContext,
						"ps", "-a",
						"--format", "'{{.ID}} {{.Names}} {{.State}}'",
					)...)
			} else {
				cmd = exec.CommandContext(
					ctx,
					containerizationSystem,
					"--context", app.podmanContext,
					"ps", "-a",
					"--format", "{{.ID}} {{.Names}} {{.State}}",
				)
			}
		}
	// Docker case
	default:
		if app.sshMode {
			cmd = exec.CommandContext(
				ctx,
				"ssh", append(app.sshOptions,
					containerizationSystem,
					"--context", app.dockerContext,
					"ps", "-a",
					// Добавляем кавычки для передаваемых через пробел параметров в ssh
					"--format", "'{{.ID}} {{.Names}} {{.State}}'",
				)...)
		} else {
			cmd = exec.CommandContext(
				ctx,
				containerizationSystem,
				"--context", app.dockerContext,
				"ps", "-a",
				"--format", "{{.ID}} {{.Names}} {{.State}}",
			)
		}
	}
	// Ожидаем выполнение в течение 2-х секунд
	cmd.WaitDelay = 2 * time.Second
	if app.logging {
		slog.Info(cmd.String(), "action", "Loading the container list")
	}
	output, err := cmd.Output()
	if !app.testMode {
		if err != nil {
			vError, _ := app.gui.View("docker")
			vError.Clear()
			app.dockerFrameColor = app.errorColor
			vError.FrameColor = app.dockerFrameColor
			vError.Highlight = false
			if containerizationSystem == "kubectl" {
				fmt.Fprintln(vError, "\033[31mUnable to access the Kubernetes cluster\033[0m")
			} else {
				fmt.Fprintln(vError, "\033[31mAccess denied or "+containerizationSystem+" service stopped\033[0m")
			}
			return
		} else {
			vError, _ := app.gui.View("docker")
			vError.Highlight = true
			app.dockerFrameColor = app.frameColor
			if vError.FrameColor != app.frameColor {
				vError.FrameColor = app.selectedFrameColor
			}
		}
	}
	if err != nil && app.testMode {
		if containerizationSystem == "kubectl" {
			log.Print("Error: unable to access the Kubernetes cluster")
		} else {
			log.Print("Error: access denied or " + containerizationSystem + " service stopped")
		}
	}
	var containers []string
	var stringOutput string
	// Парсим вывод compose
	if containerizationSystem == "compose" {
		stacks := strings.Split(strings.TrimSpace(string(output)), "\n")
		// Удаляем первую строку (элемент массива)
		stacks = stacks[1:]
		if len(stacks) != 0 {
			// Удаляем путь к конфигурационному файлу compose для каждой строки (элемента)
			for i, e := range stacks {
				line := strings.Split(e, "/")
				stacks[i] = line[0]
			}
		}
		containers = stacks
		stringOutput = strings.Join(containers, "\n")
	} else {
		containers = strings.Split(strings.TrimSpace(string(output)), "\n")
		stringOutput = string(output)
	}
	// Проверяем, что список контейнеров не пустой
	if !app.testMode {
		if len(containers) == 0 || (len(containers) == 1 && containers[0] == "") {
			vError, _ := app.gui.View("docker")
			vError.Clear()
			vError.Highlight = false
			fmt.Fprintln(vError, "\033[32mNo running containers\033[0m")
			return
		} else {
			vError, _ := app.gui.View("docker")
			app.fileSystemFrameColor = app.frameColor
			if vError.FrameColor != app.frameColor {
				vError.FrameColor = app.selectedFrameColor
			}
			vError.Highlight = true
		}
	}
	// Заполняем структуру dockerContainers (название и статус)
	serviceMap := make(map[string]bool)
	scanner := bufio.NewScanner(strings.NewReader(stringOutput))
	for scanner.Scan() {
		idName := scanner.Text()
		parts := strings.Fields(idName)
		if idName != "" && !serviceMap[idName] {
			serviceMap[idName] = true
			var containerName string
			var containerStatus string
			if containerizationSystem == "compose" {
				// Извлекаем имя стеке из первого параметра (в compose отсутствует id)
				containerName = parts[0]
				// Собираем все статусы в одну строку
				containerStatus = strings.Join(parts[1:], " ")
			} else {
				containerName = parts[1]
				containerStatus = parts[2]
			}
			composeStatus := containerStatus
			// Проверяем статус для покраски
			switch {
			case strings.HasPrefix(strings.ToLower(containerStatus), "running") ||
				strings.HasPrefix(strings.ToLower(containerStatus), "succe"):
				containerStatus = "\033[32m"
			case strings.HasPrefix(strings.ToLower(containerStatus), "pending") ||
				strings.HasPrefix(strings.ToLower(containerStatus), "pause") ||
				strings.HasPrefix(strings.ToLower(containerStatus), "restart") ||
				strings.Contains(strings.ToLower(containerStatus), "exited") && strings.Contains(strings.ToLower(containerStatus), "running"):
				containerStatus = "\033[33m"
			default:
				containerStatus = "\033[31m"
			}
			rawContainerName := containerName
			if containerizationSystem == "compose" {
				// Извлекаем количество запущенных контейнеров из статуса
				var runContainersInt int
				runContainersArr := strings.Split(composeStatus, "running(")
				if len(runContainersArr) > 1 {
					runContainersArr = strings.Split(runContainersArr[1], ")")
					runContainersInt, err = strconv.Atoi(runContainersArr[0])
					if err != nil {
						runContainersInt = 0
					}
				} else {
					runContainersArr = []string{"0"}
					runContainersInt = 0
				}
				// Извлекаем количество остановленных и перезапускающихся контейнеров из статуса
				var exitContainersInt int
				var restartContainersInt int
				exitContainersArr := strings.Split(composeStatus, "exited(")
				if len(exitContainersArr) > 1 {
					exitContainersArr = strings.Split(exitContainersArr[1], ")")
					exitContainersInt, err = strconv.Atoi(exitContainersArr[0])
					if err != nil {
						exitContainersInt = 0
					}
				} else {
					exitContainersInt = 0
				}
				restartContainersArr := strings.Split(composeStatus, "restarting(")
				if len(restartContainersArr) > 1 {
					restartContainersArr = strings.Split(restartContainersArr[1], ")")
					restartContainersInt, err = strconv.Atoi(restartContainersArr[0])
					if err != nil {
						restartContainersInt = 0
					}
				} else {
					restartContainersInt = 0
				}
				allContainers := strconv.Itoa(runContainersInt + exitContainersInt + restartContainersInt)
				containerName = "[" + runContainersArr[0] + " of " + allContainers + "] " + containerStatus + containerName + "\033[0m"
			} else {
				containerName = containerStatus + containerName + "\033[0m"
			}
			// Фиксируем название namespace для k8s
			var namespace string
			if containerizationSystem != "kubectl" || parts[3] == "" {
				namespace = ""
			} else {
				namespace = parts[3]
			}
			app.dockerContainers = append(app.dockerContainers, DockerContainers{
				name:      containerName,
				rawName:   rawContainerName,
				id:        parts[0],
				namespace: namespace,
			})
		}
	}
	sort.Slice(app.dockerContainers, func(i, j int) bool {
		return app.dockerContainers[i].name < app.dockerContainers[j].name
	})
	if !app.testMode {
		app.dockerContainersNotFilter = app.dockerContainers
		app.applyFilterList()
	}
}

func (app *App) updateDockerContainerList() {
	v, err := app.gui.View("docker")
	if err != nil {
		return
	}
	v.Clear()
	visibleEnd := min(app.startDockerContainers+app.maxVisibleDockerContainers, len(app.dockerContainers))
	for i := app.startDockerContainers; i < visibleEnd; i++ {
		fmt.Fprintln(v, app.dockerContainers[i].name)
	}
}

func (app *App) nextDockerContainer(v *gocui.View, step int) error {
	_, viewHeight := v.Size()
	app.maxVisibleDockerContainers = viewHeight
	if len(app.dockerContainers) == 0 {
		return nil
	}
	if app.selectedDockerContainer < len(app.dockerContainers)-1 {
		app.selectedDockerContainer += step
		if app.selectedDockerContainer >= len(app.dockerContainers) {
			app.selectedDockerContainer = len(app.dockerContainers) - 1
		}
		if app.selectedDockerContainer >= app.startDockerContainers+app.maxVisibleDockerContainers {
			app.startDockerContainers += step
			if app.startDockerContainers > len(app.dockerContainers)-app.maxVisibleDockerContainers {
				app.startDockerContainers = len(app.dockerContainers) - app.maxVisibleDockerContainers
			}
			app.updateDockerContainerList()
		}
		if app.selectedDockerContainer < app.startDockerContainers+app.maxVisibleDockerContainers {
			return app.selectDockerByIndex(app.selectedDockerContainer - app.startDockerContainers)
		}
	}
	return nil
}

func (app *App) prevDockerContainer(v *gocui.View, step int) error {
	_, viewHeight := v.Size()
	app.maxVisibleDockerContainers = viewHeight
	if len(app.dockerContainers) == 0 {
		return nil
	}
	if app.selectedDockerContainer > 0 {
		app.selectedDockerContainer -= step
		if app.selectedDockerContainer < 0 {
			app.selectedDockerContainer = 0
		}
		if app.selectedDockerContainer < app.startDockerContainers {
			app.startDockerContainers -= step
			if app.startDockerContainers < 0 {
				app.startDockerContainers = 0
			}
			app.updateDockerContainerList()
		}
		if app.selectedDockerContainer >= app.startDockerContainers {
			return app.selectDockerByIndex(app.selectedDockerContainer - app.startDockerContainers)
		}
	}
	return nil
}

func (app *App) selectDockerByIndex(index int) error {
	v, err := app.gui.View("docker")
	if err != nil {
		return err
	}
	// Обновляем счетчик в заголовке
	re := regexp.MustCompile(`\s\(.+\) >`)
	updateTitle := " (0) >"
	if len(app.dockerContainers) != 0 {
		updateTitle = " (" + strconv.Itoa(app.selectedDockerContainer+1) + "/" + strconv.Itoa(len(app.dockerContainers)) + ") >"
	}
	v.Title = re.ReplaceAllString(v.Title, updateTitle)
	if err := v.SetCursor(0, index); err != nil {
		return nil
	}
	return nil
}

func (app *App) selectDocker(g *gocui.Gui, v *gocui.View) error {
	if v == nil || len(app.dockerContainers) == 0 {
		return nil
	}
	_, cy := v.Cursor()
	line, err := v.Line(cy)
	if err != nil {
		return err
	}
	if app.fastMode {
		go func() {
			app.loadDockerLogs(strings.TrimSpace(line), true)
		}()
	} else {
		app.loadDockerLogs(strings.TrimSpace(line), true)
	}
	app.lastWindow = "docker"
	app.lastSelected = strings.TrimSpace(line)
	return nil
}

func (app *App) loadDockerLogs(containerName string, newUpdate bool) {
	// Прерываем выполнение функции, если имя контейнера пустое (при выборе пустого поля с помощью мыши)
	if containerName == "" {
		return
	}
	app.debugStartTime = time.Now()
	containerizationSystem := app.selectContainerizationSystem
	// Сохраняем систему контейнеризации для автообновления при смене окна
	if newUpdate {
		app.lastContainerizationSystem = app.selectContainerizationSystem
	} else {
		containerizationSystem = app.lastContainerizationSystem
	}
	// Обновляем статус с названием источника журнала (имя контейнера)
	if !app.testMode {
		v, err := app.gui.View("logs")
		if err == nil {
			v.Subtitle = "[ " + containerizationSystem + "/" + containerName + " ]"
		}
	}
	if !app.testMode {
		v, err := app.gui.View("logs")
		containerNameWithoutStatus := containerName
		containerNameSplit := strings.Split(containerNameWithoutStatus, "] ")
		if len(containerNameSplit) == 2 && len(containerNameSplit[1]) > 0 {
			containerNameWithoutStatus = containerNameSplit[1]
		}
		if err == nil {
			v.Subtitle = "[ " + containerizationSystem + "/" + containerNameWithoutStatus + " ]"
		}
	}
	if containerizationSystem == "kubernetes" {
		containerizationSystem = "kubectl"
	}
	// Извлекаем id контейнера и namespace для подов k8s
	var containerId string
	var namespace string
	for _, dockerContainer := range app.dockerContainers {
		dockerContainerName := ansiEscape.ReplaceAllString(dockerContainer.name, "")
		if dockerContainerName == containerName {
			containerId = dockerContainer.id
			namespace = dockerContainer.namespace
		}
	}
	// Сохраняем id контейнера для автообновления при смене окна
	if newUpdate {
		app.lastContainerId = containerId
	} else {
		containerId = app.lastContainerId
	}
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	// Читаем журналы Docker из файловой системы в формате JSON (если не отключено флагом и docker context default)
	var readFileContainer bool
	if containerizationSystem == "docker" && !app.dockerStreamLogs && app.dockerContext == "default" {
		// Получаем путь к журналу контейнера в файловой системе по id с помощью метода docker cli
		var cmd *exec.Cmd
		if app.sshMode {
			cmd = exec.CommandContext(
				ctx,
				"ssh", append(app.sshOptions,
					"docker", "inspect", "--format", "{{.LogPath}}", containerId,
				)...)
		} else {
			cmd = exec.Command("docker", "inspect", "--format", "{{.LogPath}}", containerId)
		}
		if app.logging {
			slog.Info(cmd.String(), "action", "Reading "+containerName+" container logs from file system")
		}
		logFilePathBytes, err := cmd.Output()
		if err != nil && !app.testMode {
			v, _ := app.gui.View("logs")
			v.Clear()
			fmt.Fprintln(v, "\033[31mError get log path via docker inspect:", err, "\033[0m")
			return
		}
		if err != nil && app.testMode {
			log.Print("Error: get log path via docker inspect. ", err)
		}
		logFilePath := strings.TrimSpace(string(logFilePathBytes))
		// Читаем файл с конца с помощью tail
		if app.sshMode {
			cmd = exec.CommandContext(
				ctx,
				"ssh", append(app.sshOptions,
					"tail", "-n", app.logViewCount, logFilePath,
				)...)
		} else {
			cmd = exec.Command("tail", "-n", app.logViewCount, logFilePath)
		}
		if app.logging {
			slog.Info(cmd.String(), "action", "Reading "+containerName+" container logs from file system")
		}
		output, err := cmd.Output()
		// Если ошибка чтения, значит нет доступа и переходим к чтению из потока
		if err != nil && app.dockerStreamLogsStatus == "json-file" {
			readFileContainer = false
			app.dockerStreamLogsStatus = app.dockerStreamMode
			app.dockerStreamLogs = true
			if !app.testMode {
				go func() {
					text := "Access denied to json logs (use root)"
					app.showInterfaceInfo(g, true, text)
					time.Sleep(3 * time.Second)
					app.closeInfo(g)
				}()
			}
		} else {
			readFileContainer = true
			app.dockerStreamLogsStatus = "json-file"
		}
		if readFileContainer {
			// Проверяем, что есть изменения в файле при повторном считывание
			if newUpdate {
				// Фиксируем новую дату изменения и размер для выбранного файла
				fileInfo, err := app.statFile(logFilePath)
				if err != nil {
					return
				}
				fileModTime := fileInfo.ModTime()
				fileSize := fileInfo.Size()
				app.lastDateUpdateFile = fileModTime
				app.lastSizeFile = fileSize
				app.updateFile = true
			} else {
				// Проверяем дату изменения
				fileInfo, err := app.statFile(logFilePath)
				if err != nil {
					return
				}
				fileModTime := fileInfo.ModTime()
				fileSize := fileInfo.Size()
				// Обновлять файл, только если есть изменения (проверяем дату модификации и размер)
				if fileModTime != app.lastDateUpdateFile || fileSize != app.lastSizeFile {
					app.lastDateUpdateFile = fileModTime
					app.lastSizeFile = fileSize
					app.updateFile = true
				} else {
					app.updateFile = false
				}
			}
			// Читаем файл, толькое если были изменения
			if app.updateFile {
				// Разбиваем строки на массив
				lines := strings.Split(strings.TrimSpace(string(output)), "\n")
				var formattedLines []string
				// Обрабатываем вывод в формате JSON построчно
				for _, line := range lines {
					// JSON-структура для парсинга
					var jsonData map[string]any
					err := json.Unmarshal([]byte(line), &jsonData)
					if err != nil {
						continue
					}
					// Извлекаем JSON данные
					stream, _ := jsonData["stream"].(string)
					timeStr, _ := jsonData["time"].(string)
					logMessage, _ := jsonData["log"].(string)
					// Проверяем режим вывода потоков и пропускаем лишние строки
					// Если текущий режим соответствует стандартному выводу и текущая строка содержит поток ошибки (или наоборот), пропускаем интерацию
					if app.dockerStreamMode == "stdout" && stream == "stderr" {
						continue
					}
					if app.dockerStreamMode == "stderr" && stream == "stdout" {
						continue
					}
					// Удаляем встроенный экранированный символ переноса строки
					logMessage = strings.TrimSuffix(logMessage, "\n")
					// Парсим строку времени в объект time.Time
					parsedTime, err := time.Parse(time.RFC3339Nano, timeStr)
					if err == nil {
						// Форматируем дату в формате: YYYY-MM-DDTHH:MM:SS.MS(x9)Z
						timeStr = parsedTime.Format("2006-01-02T15:04:05.000000000Z")
					}
					var formattedLine string
					// Заполняем строку в формате
					switch {
					case app.timestampDocker && app.streamTypeDocker:
						// stream time log
						formattedLine = fmt.Sprintf("%s %s %s", stream, timeStr, logMessage)
					case app.timestampDocker && !app.streamTypeDocker:
						// time log
						formattedLine = fmt.Sprintf("%s %s", timeStr, logMessage)
					case !app.timestampDocker && app.streamTypeDocker:
						// stream log
						formattedLine = fmt.Sprintf("%s %s", stream, logMessage)
					case !app.timestampDocker && !app.streamTypeDocker:
						// log only
						formattedLine = logMessage
					}
					formattedLines = append(formattedLines, formattedLine)
					// Если это последняя строка в выводе, добавляем перенос строки
				}
				app.currentLogLines = formattedLines
			}
		}
	}
	// Читаем лог через docker cli (если файл не найден или к нему нет доступа) или для compose/podman/kubectl
	if !readFileContainer || containerizationSystem != "docker" {
		// Извлекаем имя без статуса в containerId для docker compose и Kubernetes
		if containerizationSystem == "compose" || containerizationSystem == "kubectl" {
			parts := strings.Split(containerName, "] ")
			if len(parts) > 1 {
				containerId = parts[1]
			} else {
				containerId = parts[0]
			}
		}
		var cmd *exec.Cmd
		switch containerizationSystem {
		case "kubectl":
			// Собираем timezone с учетом смещения UTC
			sinceTimestamp := app.sinceFilterText + "T00:00:00" + app.timezoneFilter
			// Формируем команду kubectl с нужными ключами и предварительно извлеченным namespace при выборе пода
			if app.sshMode {
				if app.sinceDateFilterMode {
					cmd = exec.CommandContext(
						ctx,
						"ssh", append(app.sshOptions,
							containerizationSystem, "logs",
							"--since-time", sinceTimestamp,
							"--context", app.kubernetesContext, "-n", namespace,
							"--ignore-errors=true", "--insecure-skip-tls-verify-backend=true",
							"--all-containers=true", "--prefix=true",
							"--timestamps=true", "--tail", app.logViewCount, containerId,
						)...)
				} else {
					cmd = exec.CommandContext(
						ctx,
						"ssh", append(app.sshOptions,
							containerizationSystem, "logs",
							"--context", app.kubernetesContext, "-n", namespace,
							"--ignore-errors=true", "--insecure-skip-tls-verify-backend=true",
							"--all-containers=true", "--prefix=true",
							"--timestamps=true", "--tail", app.logViewCount, containerId,
						)...)
				}
			} else {
				if app.sinceDateFilterMode {
					cmd = exec.CommandContext(
						ctx,
						containerizationSystem, "logs",
						"--since-time", sinceTimestamp,
						"--context", app.kubernetesContext, "-n", namespace,
						"--ignore-errors=true", "--insecure-skip-tls-verify-backend=true",
						"--all-containers=true", "--prefix=true",
						"--timestamps=true", "--tail", app.logViewCount, containerId,
					)
				} else {
					cmd = exec.CommandContext(
						ctx,
						containerizationSystem, "logs",
						"--context", app.kubernetesContext, "-n", namespace,
						"--ignore-errors=true", "--insecure-skip-tls-verify-backend=true",
						"--all-containers=true", "--prefix=true",
						"--timestamps=true", "--tail", app.logViewCount, containerId,
					)
				}
			}
		case "compose":
			// Сначала получаем список контейнеров в стеке Compose
			if newUpdate {
				containerNameArr := app.getContainersFromCompose(containerId)
				// Предварительно очищаем карту
				clear(app.uniquePrefixColorMap)
				// Заполняем карту уникальных цветов для уникальной покраски названия контейнеров в префиксах compose
				for _, containerName := range containerNameArr {
					if containerName != "" {
						newColor := uniquePrefixColorArr[len(app.uniquePrefixColorMap)%len(uniquePrefixColorArr)]
						app.uniquePrefixColorMap[containerName] = newColor
					}
				}
			}
			sinceTimestamp := app.sinceFilterText + "T00:00:00" + app.timezoneFilter
			untilTimestamp := app.untilFilterText + "T00:00:00" + app.timezoneFilter
			if app.sshMode {
				if app.dockerCompose == "docker compose" {
					switch {
					case app.sinceDateFilterMode && app.untilDateFilterMode:
						cmd = exec.CommandContext(
							ctx,
							"ssh", append(app.sshOptions,
								"docker", "--context", app.dockerContext, "compose", "--project-name", containerId, "logs", "--timestamps", "--no-color",
								"--since", sinceTimestamp,
								"--until", untilTimestamp,
								"--tail", app.logViewCount,
							)...,
						)
					case app.sinceDateFilterMode && !app.untilDateFilterMode:
						cmd = exec.CommandContext(
							ctx,
							"ssh", append(app.sshOptions,
								"docker", "--context", app.dockerContext, "compose", "--project-name", containerId, "logs", "--timestamps", "--no-color",
								"--since", sinceTimestamp,
								"--tail", app.logViewCount,
							)...,
						)
					case !app.sinceDateFilterMode && app.untilDateFilterMode:
						cmd = exec.CommandContext(
							ctx,
							"ssh", append(app.sshOptions,
								"docker", "--context", app.dockerContext, "compose", "--project-name", containerId, "logs", "--timestamps", "--no-color",
								"--until", untilTimestamp,
								"--tail", app.logViewCount,
							)...,
						)
					default:
						cmd = exec.CommandContext(
							ctx,
							"ssh", append(app.sshOptions,
								"docker", "--context", app.dockerContext, "compose", "--project-name", containerId, "logs", "--timestamps", "--no-color",
								"--tail", app.logViewCount,
							)...,
						)
					}
				} else {
					switch {
					case app.sinceDateFilterMode && app.untilDateFilterMode:
						cmd = exec.CommandContext(
							ctx,
							"ssh", append(app.sshOptions,
								app.dockerCompose, "--context", app.dockerContext, "--project-name", containerId, "logs", "--timestamps", "--no-color",
								"--since", sinceTimestamp,
								"--until", untilTimestamp,
								"--tail", app.logViewCount,
							)...,
						)
					case app.sinceDateFilterMode && !app.untilDateFilterMode:
						cmd = exec.CommandContext(
							ctx,
							"ssh", append(app.sshOptions,
								app.dockerCompose, "--context", app.dockerContext, "--project-name", containerId, "logs", "--timestamps", "--no-color",
								"--since", sinceTimestamp,
								"--tail", app.logViewCount,
							)...,
						)
					case !app.sinceDateFilterMode && app.untilDateFilterMode:
						cmd = exec.CommandContext(
							ctx,
							"ssh", append(app.sshOptions,
								app.dockerCompose, "--context", app.dockerContext, "--project-name", containerId, "logs", "--timestamps", "--no-color",
								"--until", untilTimestamp,
								"--tail", app.logViewCount,
							)...,
						)
					default:
						cmd = exec.CommandContext(
							ctx,
							"ssh", append(app.sshOptions,
								app.dockerCompose, "--context", app.dockerContext, "--project-name", containerId, "logs", "--timestamps", "--no-color",
								"--tail", app.logViewCount,
							)...,
						)
					}
				}
			} else {
				if app.dockerCompose == "docker compose" {
					switch {
					case app.sinceDateFilterMode && app.untilDateFilterMode:
						cmd = exec.CommandContext(
							ctx,
							"docker", "--context", app.dockerContext, "compose", "--project-name", containerId, "logs", "--timestamps", "--no-color",
							"--since", sinceTimestamp,
							"--until", untilTimestamp,
							"--tail", app.logViewCount,
						)
					case app.sinceDateFilterMode && !app.untilDateFilterMode:
						cmd = exec.CommandContext(
							ctx,
							"docker", "--context", app.dockerContext, "compose", "--project-name", containerId, "logs", "--timestamps", "--no-color",
							"--since", sinceTimestamp,
							"--tail", app.logViewCount,
						)
					case !app.sinceDateFilterMode && app.untilDateFilterMode:
						cmd = exec.CommandContext(
							ctx,
							"docker", "--context", app.dockerContext, "compose", "--project-name", containerId, "logs", "--timestamps", "--no-color",
							"--until", untilTimestamp,
							"--tail", app.logViewCount,
						)
					default:
						cmd = exec.CommandContext(
							ctx,
							"docker", "--context", app.dockerContext, "compose", "--project-name", containerId, "logs", "--timestamps", "--no-color",
							"--tail", app.logViewCount,
						)
					}
				} else {
					switch {
					case app.sinceDateFilterMode && app.untilDateFilterMode:
						cmd = exec.CommandContext(
							ctx,
							app.dockerCompose, "--context", app.dockerContext, "--project-name", containerId, "logs", "--timestamps", "--no-color",
							"--since", sinceTimestamp,
							"--until", untilTimestamp,
							"--tail", app.logViewCount,
						)
					case app.sinceDateFilterMode && !app.untilDateFilterMode:
						cmd = exec.CommandContext(
							ctx,
							app.dockerCompose, "--context", app.dockerContext, "--project-name", containerId, "logs", "--timestamps", "--no-color",
							"--since", sinceTimestamp,
							"--tail", app.logViewCount,
						)
					case !app.sinceDateFilterMode && app.untilDateFilterMode:
						cmd = exec.CommandContext(
							ctx,
							app.dockerCompose, "--context", app.dockerContext, "--project-name", containerId, "logs", "--timestamps", "--no-color",
							"--until", untilTimestamp,
							"--tail", app.logViewCount,
						)
					default:
						cmd = exec.CommandContext(
							ctx,
							app.dockerCompose, "--context", app.dockerContext, "--project-name", containerId, "logs", "--timestamps", "--no-color",
							"--tail", app.logViewCount,
						)
					}
				}
			}
		// Podman and Docker case
		default:
			// Формируем опции для выполнения команды
			cmdOptions := []string{}
			// #38 Добавляем название контекста с проверкой флага для Podman
			if containerizationSystem == "docker" {
				cmdOptions = append(cmdOptions, "--context", app.dockerContext)
			} else if containerizationSystem == "podman" && app.podmanContext != "" {
				cmdOptions = append(cmdOptions, "--context", app.podmanContext)
			}
			// Добавляем фильтрацию по времени
			sinceTimestamp := app.sinceFilterText + "T00:00:00" + app.timezoneFilter
			untilTimestamp := app.untilFilterText + "T00:00:00" + app.timezoneFilter
			switch {
			case app.sinceDateFilterMode && app.untilDateFilterMode:
				cmdOptions = append(
					cmdOptions, "logs", "--timestamps", "--tail", app.logViewCount,
					"--since", sinceTimestamp,
					"--until", untilTimestamp,
				)
			case app.sinceDateFilterMode && !app.untilDateFilterMode:
				cmdOptions = append(
					cmdOptions, "logs", "--timestamps", "--tail", app.logViewCount,
					"--since", sinceTimestamp,
				)
			case !app.sinceDateFilterMode && app.untilDateFilterMode:
				cmdOptions = append(
					cmdOptions, "logs", "--timestamps", "--tail", app.logViewCount,
					"--until", untilTimestamp,
				)
			default:
				cmdOptions = append(
					cmdOptions, "logs", "--timestamps", "--tail", app.logViewCount,
				)
			}
			// Добавляем ssh параметры
			if app.sshMode {
				cmdOptions = append([]string{containerizationSystem}, cmdOptions...)
				cmdOptions = append(app.sshOptions, cmdOptions...)
				// ssh sshOptions containerizationSystem cmdOptions containerId
				cmd = exec.CommandContext(
					ctx,
					"ssh",
					append(
						cmdOptions,
						containerId,
					)...,
				)
			} else {
				// containerizationSystem cmdOptions containerId
				cmd = exec.CommandContext(
					ctx,
					containerizationSystem,
					append(
						cmdOptions,
						containerId,
					)...,
				)
			}
		}
		if app.logging {
			slog.Info(cmd.String(), "action", "Reading "+containerName+" container logs")
		}
		// Ожидаем выполнение в течение 2-х секунд
		cmd.WaitDelay = 2 * time.Second
		// Храним байты вывода
		var stdoutBytes, stderrBytes []byte
		var stdoutErr, stderrErr error
		// Храним комбинированный вывод двух потоков
		var combined []dockerLogLines
		switch {
		// Читаем только один поток в режиме stdout для Docker или compose и kubectl
		case app.dockerStreamMode == "stdout" || containerizationSystem == "compose" || containerizationSystem == "kubectl":
			// Читаем стандартный вывод
			stdoutPipe, _ := cmd.StdoutPipe()
			cmd.Start()
			stdoutBytes, stdoutErr = io.ReadAll(stdoutPipe)
			stdoutLines := strings.Split(string(stdoutBytes), "\n")
			// Удаляем последнюю пустую строку
			if len(stdoutLines) > 0 && stdoutLines[len(stdoutLines)-1] == "" {
				stdoutLines = stdoutLines[0 : len(stdoutLines)-1]
			}
			// Проверяем stdout на ошибки, если в выводе одна строка (например, 502 Bad Gateway в kubectl)
			if len(stdoutLines) <= 1 {
				lastLineContext := ""
				// Проверяем на пустую строку
				if len(stdoutLines) > 0 && len(stdoutLines[0]) > 0 {
					lastLineContext = stdoutLines[0]
				}
				combined = append(combined, dockerLogLines{
					isError:   false,
					timestamp: time.Now(),
					content:   lastLineContext,
				})
			} else {
				// Формируем итоговый массив
				for _, line := range stdoutLines {
					// Пропускаем пустые строки
					if strings.TrimSpace(line) == "" {
						continue
					}
					var ts time.Time
					var err error
					// Извлекаем timestamp
					switch containerizationSystem {
					case "compose":
						// Сначала извлекаем имя сервиса
						parts1 := strings.SplitN(line, " | ", 2)
						// Затем извлекаем timestamp
						parts2 := strings.SplitN(parts1[1], " ", 2)
						tsStr := strings.TrimSpace(parts2[0])
						ts, err = time.Parse(time.RFC3339Nano, tsStr)
					case "kubectl":
						// Сначала извлекаем префикс (название пода и контейнера в формате [pod/<podName>/<containerName>])
						parts1 := strings.SplitN(line, "] ", 2)
						// Затем извлекаем timestamp
						parts2 := strings.SplitN(parts1[1], " ", 2)
						tsStr := strings.TrimSpace(parts2[0])
						ts, err = time.Parse(time.RFC3339Nano, tsStr)
					default:
						// Извлекаем время из префикса docker/podman
						ts, err = parseTimestamp(line)
					}
					if err != nil {
						continue
					}
					combined = append(combined, dockerLogLines{
						isError:   false,
						timestamp: ts,
						content:   line,
					})
				}
				// Сортируем вывод по timestamp для compose
				if containerizationSystem == "compose" {
					sort.Slice(
						combined,
						func(i, j int) bool {
							return combined[i].timestamp.Before(combined[j].timestamp)
						},
					)
				}
			}
		case app.dockerStreamMode == "stderr":
			// Читаем вывод ошибок
			stderrPipe, _ := cmd.StderrPipe()
			_ = cmd.Start()
			stderrBytes, stderrErr = io.ReadAll(stderrPipe)
			stderrLines := strings.Split(string(stderrBytes), "\n")
			// Формируем итоговый массив
			for _, line := range stderrLines {
				if strings.TrimSpace(line) == "" {
					continue
				}
				ts, err := parseTimestamp(line)
				if err != nil {
					continue
				}
				combined = append(combined, dockerLogLines{
					isError:   true,
					timestamp: ts,
					content:   line,
				})
			}
		default:
			// Читаем стандартный вывод
			stdoutPipe, _ := cmd.StdoutPipe()
			// Читаем вывод ошибок
			stderrPipe, _ := cmd.StderrPipe()
			// Запускаем команду
			_ = cmd.Start()
			// Читаем два потока параллельно, чтобы не блокировать
			var wg sync.WaitGroup
			wg.Add(2)
			go func() {
				defer wg.Done()
				stdoutBytes, stdoutErr = io.ReadAll(stdoutPipe)
			}()
			go func() {
				defer wg.Done()
				stderrBytes, stderrErr = io.ReadAll(stderrPipe)
			}()
			wg.Wait()
			_ = cmd.Wait()
			// Обработка ошибок чтения
			if stdoutErr != nil || stderrErr != nil {
				if !app.testMode {
					v, _ := app.gui.View("logs")
					v.Clear()
					fmt.Fprintln(v, "\033[31mError getting logs from", containerName, "(id:", containerId, ")", "container.\033[0m")
					return
				} else {
					log.Print("Error: getting logs from ", containerName, " (id:", containerId, ")", " container.")
				}
			}
			// Получаем 2 массива вывода
			stdoutLines := strings.Split(string(stdoutBytes), "\n")
			stderrLines := strings.Split(string(stderrBytes), "\n")
			// Объединяем два массив
			for _, line := range stdoutLines {
				if strings.TrimSpace(line) == "" {
					continue
				}
				ts, err := parseTimestamp(line)
				if err != nil {
					continue
				}
				combined = append(combined, dockerLogLines{
					isError:   false,
					timestamp: ts,
					content:   line,
				})
			}
			for _, line := range stderrLines {
				if strings.TrimSpace(line) == "" {
					continue
				}
				ts, err := parseTimestamp(line)
				if err != nil {
					continue
				}
				combined = append(combined, dockerLogLines{
					isError:   true,
					timestamp: ts,
					content:   line,
				})
			}
			// Cортируем итоговый массив по timestamp
			sort.Slice(
				combined,
				func(i, j int) bool {
					return combined[i].timestamp.Before(combined[j].timestamp)
				},
			)
		}
		// Обновляем префиксы
		var finalLines []string
		for _, entry := range combined {
			entryLine := entry.content
			switch containerizationSystem {
			case "compose":
				prefixIndex := strings.Index(entryLine, "|")
				if prefixIndex != -1 {
					// Удаляем табуляцию из названия контейнера в префиксе compose
					beforePrefixLine := entryLine[:prefixIndex]
					beforePrefixLine = strings.TrimSpace(beforePrefixLine)
					// Удаляем индекс разделителя "|"
					afterPrefixLine := entryLine[prefixIndex+1:]
					// Заключаем название контейнера в квадратные скобки
					entryLine = "[" + beforePrefixLine + "]" + afterPrefixLine
				}
			case "kubectl":
				// Удаляем префикс названия типа объекта "pod/"
				entryLine = strings.Replace(entryLine, "pod/", "", 1)
			}
			// Удаляем из строки timestamp
			if !app.timestampDocker {
				entryLine = removeTimestamp(entryLine, containerizationSystem)
			}
			// Не добавляем префексы названия потока в отключенном режиме для Docker (а также для compose и kubectl по умолчанию)
			if !app.streamTypeDocker || containerizationSystem == "compose" || containerizationSystem == "kubectl" {
				finalLines = append(finalLines, entryLine)
			} else {
				prefix := "stdout "
				if entry.isError {
					prefix = "stderr "
				}
				finalLine := prefix + entryLine
				finalLines = append(finalLines, finalLine)
			}
		}
		app.currentLogLines = finalLines
	}
	// Обновляем фильтр и делиметр всегда для потоков ИЛИ если есть изменения в файле при его чтение
	if !readFileContainer || (readFileContainer && app.updateFile) || containerizationSystem != "docker" {
		app.updateDelimiter(newUpdate)
		app.applyFilter(false)
	}
}

// Функция для получения массива из названия контейнеров в заданном проекте Compose
func (app *App) getContainersFromCompose(projectName string) []string {
	var cmd *exec.Cmd
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	if app.sshMode {
		if app.dockerCompose == "docker-compose" {
			cmd = exec.CommandContext(
				ctx,
				"ssh", append(app.sshOptions,
					app.dockerCompose,
					"--context", app.dockerContext, "--project-name", projectName, "ps", "-a", "--services",
				)...)
		} else {
			cmd = exec.CommandContext(
				ctx,
				"ssh", append(app.sshOptions,
					"docker",
					"--context", app.dockerContext, "compose", "--project-name", projectName, "ps", "-a", "--services",
				)...)
		}
	} else {
		if app.dockerCompose == "docker-compose" {
			cmd = exec.CommandContext(
				ctx,
				app.dockerCompose,
				"--context", app.dockerContext, "--project-name", projectName, "ps", "-a", "--services",
			)
		} else {
			cmd = exec.CommandContext(
				ctx,
				"docker",
				"--context", app.dockerContext, "compose", "--project-name", projectName, "ps", "-a", "--services",
			)
		}
	}
	cmd.WaitDelay = 2 * time.Second
	if app.logging {
		slog.Info(cmd.String(), "action", "Loading the compose stacks")
	}
	output, err := cmd.Output()
	if err == nil {
		containerNameArr := strings.Split(strings.TrimSpace(string(output)), "\n")
		return containerNameArr
	} else {
		return []string{}
	}
}

// Функция извлечения timestamp для сортировки
func parseTimestamp(line string) (time.Time, error) {
	// Делим строку на две части по первому пробелу
	parts := strings.SplitN(line, " ", 2)
	// Удаляем лишние пробелы
	tsStr := strings.TrimSpace(parts[0])
	// Парсим строку (извлекаем временную метку)
	return time.Parse(time.RFC3339Nano, tsStr)
}

// Функция для удаления timestamp из строки
func removeTimestamp(line string, containerizationSystem string) string {
	// Удаляем слово до первого пробела для Docker или Podman
	if containerizationSystem == "docker" || containerizationSystem == "podman" {
		// Находим индекс первого пробела
		spaceIndex := strings.Index(line, " ")
		// Если пробела нет, возвращаем строку как есть
		if spaceIndex == -1 {
			return line
		}
		// Возвращаем строку начиная с символа после первого пробела
		return line[spaceIndex+1:]
	} else {
		// Удаляем второй индекс для compose или kubectl
		lineArr := strings.Split(line, " ")
		if len(lineArr) < 3 {
			return line
		} else {
			// Собираем массив в строку без второго индекса
			linePrefix := lineArr[0]
			lineText := strings.Join(lineArr[2:], " ")
			return linePrefix + " " + lineText
		}
	}
}

// ---------------------------------------- Filter ----------------------------------------

// Редактор обработки ввода текста для фильтрации
func (app *App) createFilterEditor(window string) gocui.Editor {
	return gocui.EditorFunc(func(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) {
		switch {
		// Добавляем символ в поле ввода
		case ch != 0 && mod == 0:
			v.EditWrite(ch)
		// Добавляем пробел
		case key == gocui.KeySpace:
			v.EditWrite(' ')
		// Удаляем символ слева от курсора
		case key == gocui.KeyBackspace || key == gocui.KeyBackspace2:
			v.EditDelete(true)
		// Удаляем символ справа от курсора
		case key == gocui.KeyDelete:
			v.EditDelete(false)
		// Быстрое перещенеие курсора влево
		case key == gocui.KeyArrowLeft && (mod == gocui.ModAlt || mod == gocui.ModMouseCtrl):
			buffer := v.Buffer()
			cursor, _ := v.Cursor()
			if len(buffer) > 0 && cursor > 0 {
				position := strings.LastIndex(buffer[0:cursor], " ")
				if position == -1 {
					v.SetCursor(0, 0)
				} else {
					v.SetCursor(position, 0)
				}
			}
			return
		// Быстрое перещенеие курсора вправо
		case key == gocui.KeyArrowRight && (mod == gocui.ModAlt || mod == gocui.ModMouseCtrl):
			buffer := v.Buffer()
			cursor, _ := v.Cursor()
			if len(buffer) > 0 && cursor < len(buffer) {
				position := strings.Index(buffer[cursor:len(buffer)-1], " ")
				if position == -1 {
					v.SetCursor(len(buffer), 0)
				} else {
					v.SetCursor(cursor+position+1, 0)
				}
			}
			return
		// Перемещение курсора влево
		case key == gocui.KeyArrowLeft:
			v.MoveCursor(-1, 0)
			return
		// Перемещение курсора вправо
		case key == gocui.KeyArrowRight:
			v.MoveCursor(1, 0)
			return
		}
		switch window {
		case "logs":
			// Обновляем текст в буфере
			app.filterText = strings.TrimSpace(v.Buffer())
			// Применяем функцию фильтрации к выводу записей журнала
			app.applyFilter(true)
		case "lists":
			app.filterListText = strings.TrimSpace(v.Buffer())
			app.applyFilterList()
		}
	})
}

// Функция для фильтрации по дате
func (app *App) timestampFilterEditor(window string) gocui.Editor {
	return gocui.EditorFunc(func(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) {
		customLeft, _ := getHotkey(config.Hotkeys.Left, "h")
		customRight, _ := getHotkey(config.Hotkeys.Right, "l")
		disableFilterByDate, _ := getHotkey(config.Hotkeys.DisableFilterByDate, "delete")
		var filterDate time.Time
		var filterText string
		switch window {
		case "sinceFilter":
			switch {
			// Пропускаем только Right/l для увеличения даты
			case key == gocui.KeyArrowRight || ch == customRight:
				filterDate, filterText = app.switchDate(app.sinceFilterDate, true)
			// Пропускаем только Left/h для уменьшения даты
			case key == gocui.KeyArrowLeft || ch == customLeft:
				filterDate, filterText = app.switchDate(app.sinceFilterDate, false)
			// На Del или Backspace отключаем фильтрацию
			case key == disableFilterByDate || key == gocui.KeyBackspace || key == gocui.KeyBackspace2:
				app.sinceDateFilterMode = false
				v.FrameColor = app.errorColor
				v.Clear()
				fmt.Fprint(v, "⎯")
				app.updateFilterStatus()
				return
			// Игнорируем другие символы
			default:
				return
			}
			// Проверяем дату - значение ДО не может быть больше или равно текущей дате или значению ПОСЛЕ
			if filterDate.After(app.limitFilterDate.AddDate(0, 0, -1)) || filterDate.After(app.untilFilterDate.AddDate(0, 0, -1)) {
				return
			}
			// Изменяем значения в переменных и интерфейсе, включаем режим фильтрации и красим в зеленый
			app.sinceFilterDate = filterDate
			app.sinceFilterText = filterText
			v.Clear()
			fmt.Fprint(v, app.sinceFilterText)
			app.sinceDateFilterMode = true
			v.FrameColor = app.selectedFrameColor
			app.updateFilterStatus()
		case "untilFilter":
			switch {
			case key == gocui.KeyArrowRight || ch == customRight:
				filterDate, filterText = app.switchDate(app.untilFilterDate, true)
			case key == gocui.KeyArrowLeft || ch == customLeft:
				filterDate, filterText = app.switchDate(app.untilFilterDate, false)
			case key == disableFilterByDate || key == gocui.KeyBackspace || key == gocui.KeyBackspace2:
				app.untilDateFilterMode = false
				v.FrameColor = app.errorColor
				v.Clear()
				fmt.Fprint(v, "⎯")
				app.updateFilterStatus()
				return
			default:
				return
			}
			// Проверяем дату - значение ПОСЛЕ не может быть больше текущей даты ИЛИ меньше значения ДО
			if filterDate.After(app.limitFilterDate) || filterDate.Before(app.sinceFilterDate.AddDate(0, 0, 1)) {
				return
			}
			app.untilFilterDate = filterDate
			app.untilFilterText = filterText
			v.Clear()
			fmt.Fprint(v, app.untilFilterText)
			app.untilDateFilterMode = true
			v.FrameColor = app.selectedFrameColor
			app.updateFilterStatus()
		}
	})
}

// Функция для изменения даты
func (app *App) switchDate(inputDate time.Time, up bool) (time.Time, string) {
	var outDate time.Time
	if up {
		outDate = inputDate.AddDate(0, 0, 1)
	} else {
		outDate = inputDate.AddDate(0, 0, -1)
	}
	return outDate, outDate.Format("2006-01-02")
}

// Функция для обновления статуса работы фильтра по дате
func (app *App) updateFilterStatus() {
	switch {
	case app.sinceDateFilterMode && !app.untilDateFilterMode:
		app.filterByDateStatus = "since only"
	case !app.sinceDateFilterMode && app.untilDateFilterMode:
		app.filterByDateStatus = "until only"
	case app.sinceDateFilterMode && app.untilDateFilterMode:
		app.filterByDateStatus = "since and until"
	case !app.sinceDateFilterMode && !app.untilDateFilterMode:
		app.filterByDateStatus = "false"
	}
	app.updateStatus()
	app.updateLogsView(false)
}

// Функция для обновления всех параметров в статусе
func (app *App) updateStatus() {
	vStatus, err := app.gui.View("status")
	if err != nil {
		return
	}
	vStatus.Clear()
	fmt.Fprintf(vStatus,
		" Tail mode: \033[32m%t\033[0m (\033[32m%s\033[0m lines) | "+
			"Update interval: \033[32m%d\033[0m sec | "+
			"Color mode: \033[32m%s\033[0m | "+
			"Filter by date: \033[32m%s\033[0m | "+
			"Filter by priority/boot: \033[32m%s\033[0m/\033[32m%s\033[0m | "+
			"Show timestamp: \033[32m%t\033[0m \n "+
			"SSH mode: \033[32m%s\033[0m | "+
			"Docker mode/context: \033[32m%s\033[0m/\033[32m%s\033[0m | "+
			"Kubernetes context/namespace: \033[32m%s\033[0m/\033[32m%s\033[0m",
		app.autoScroll,
		logViewCountMap[app.logViewCount],
		app.logUpdateSeconds,
		app.colorMode,
		app.filterByDateStatus,
		app.journalPriority,
		app.journalBoot,
		app.timestampDocker,
		app.sshStatus,
		app.dockerStreamLogsStatus,
		app.dockerContext,
		app.kubernetesContext,
		app.kubernetesNamespaceStatus,
	)
}

// Функция для фильтрации всех списоков журналов
func (app *App) applyFilterList() {
	filter := strings.ToLower(app.filterListText)
	// Временные массивы для отфильтрованных журналов
	var filteredJournals []Journal
	var filteredLogFiles []Logfile
	var filteredDockerContainers []DockerContainers
	for _, j := range app.journalsNotFilter {
		if strings.Contains(strings.ToLower(j.name), filter) {
			filteredJournals = append(filteredJournals, j)
		}
	}
	for _, j := range app.logfilesNotFilter {
		if strings.Contains(strings.ToLower(j.name), filter) {
			filteredLogFiles = append(filteredLogFiles, j)
		}
	}
	for _, j := range app.dockerContainersNotFilter {
		if strings.Contains(strings.ToLower(j.name), filter) {
			filteredDockerContainers = append(filteredDockerContainers, j)
		}
	}
	// Сбрасываем индексы выбранного журнала для правильного позиционирования
	app.selectedJournal = 0
	app.selectedFile = 0
	app.selectedDockerContainer = 0
	app.startServices = 0
	app.startFiles = 0
	app.startDockerContainers = 0
	// Сохраняем отфильтрованные и отсортированные данные
	app.journals = filteredJournals
	app.logfiles = filteredLogFiles
	app.dockerContainers = filteredDockerContainers
	// Обновляем статус количества служб
	if !app.testMode {
		// Обновляем списки в интерфейсе
		app.updateServicesList()
		app.updateLogsList()
		app.updateDockerContainerList()
		v, _ := app.gui.View("services")
		// Обновляем счетчик в заголовке
		re := regexp.MustCompile(`\s\(.+\) >`)
		updateTitle := " (0) >"
		if len(app.journals) != 0 {
			updateTitle = " (" + strconv.Itoa(app.selectedJournal+1) + "/" + strconv.Itoa(len(app.journals)) + ") >"
		}
		v.Title = re.ReplaceAllString(v.Title, updateTitle)
		// Обновляем статус количества файлов
		v, _ = app.gui.View("varLogs")
		// Обновляем счетчик в заголовке
		re = regexp.MustCompile(`\s\(.+\) >`)
		updateTitle = " (0) >"
		if len(app.logfiles) != 0 {
			updateTitle = " (" + strconv.Itoa(app.selectedFile+1) + "/" + strconv.Itoa(len(app.logfiles)) + ") >"
		}
		v.Title = re.ReplaceAllString(v.Title, updateTitle)
		// Обновляем статус количества контейнеров
		v, _ = app.gui.View("docker")
		// Обновляем счетчик в заголовке
		re = regexp.MustCompile(`\s\(.+\) >`)
		updateTitle = " (0) >"
		if len(app.dockerContainers) != 0 {
			updateTitle = " (" + strconv.Itoa(app.selectedDockerContainer+1) + "/" + strconv.Itoa(len(app.dockerContainers)) + ") >"
		}
		v.Title = re.ReplaceAllString(v.Title, updateTitle)
	}
}

// Функция проверки исполняемого файла
func (app *App) checkBin(commands []string) (string, error) {
	var binName string
	for _, command := range commands {
		cmd := exec.Command(command, "--version")
		if app.logging {
			slog.Info(cmd.String(), "action", "Check the binary")
		}
		_, err := cmd.Output()
		if err == nil {
			binName = command
			break
		}
	}
	// Возвращяем название бинарного файла для использования или выводим интерфейс ошибки на 3 секунды
	if len(binName) > 0 {
		return binName, nil
	} else {
		if !app.testMode {
			go func() {
				errorText := strings.Join(commands, " and ") + " not found in environment"
				app.showInterfaceInfo(g, true, errorText)
				time.Sleep(3 * time.Second)
				app.closeInfo(g)
			}()
		}
		return "", errors.New("binary file not found in environment")
	}
}

// Функция для фильтрации записей текущего журнала + покраска
func (app *App) applyFilter(color bool) {
	filter := app.filterText
	var skip = false
	var size int
	var viewHeight int
	var err error
	if !app.testMode {
		v, err := app.gui.View("filter")
		if err != nil {
			return
		}
		if color {
			v.FrameColor = app.selectedFrameColor
		}
		// Если текст фильтра не менялся и позиция курсора не в самом конце журнала, то пропускаем фильтрацию и покраску при пролистывании
		vLogs, _ := app.gui.View("logs")
		_, viewHeight := vLogs.Size()
		size = app.logScrollPos + viewHeight + 1
		if app.lastFilterText == filter && size < len(app.filteredLogLines) {
			skip = true
		}
		// Фиксируем текущий текст из фильтра
		app.lastFilterText = filter
	}
	// Фильтруем и красим, только если это не скроллинг
	if !skip {
		// Debug end load time
		endLoadTime := time.Since(app.debugStartTime)
		// Фиксируем время окончания загрузки журнала
		app.debugLoadTime = endLoadTime.Truncate(time.Millisecond).String()
		// Debug start color time
		// Фиксируем время начала покраски журнала
		startTime := time.Now()
		// Если текст фильтра пустой или равен любому символу для regex, возвращяем вывод без фильтрации
		if filter == "" || (filter == "." && app.selectFilterMode == "regex") ||
			// Если длинна текста меньше флага минального кол-ва символов фильтра, пропускаем фильтрацию
			len(filter) < app.minSymbolFilter {
			app.filteredLogLines = app.currentLogLines
		} else {
			app.filteredLogLines = make([]string, 0)
			// Опускаем регистр ввода текста для фильтра
			filter = strings.ToLower(filter)
			// Проверка регулярного выражения
			var regex *regexp.Regexp
			if app.selectFilterMode == "regex" {
				// Добавляем флаг для нечувствительности к регистру по умолчанию
				filter = "(?i)" + filter
				// Компилируем регулярное выражение
				regex, err = regexp.Compile(filter)
				// В случае синтаксической ошибки регулярного выражения, красим окно красным цветом и завершаем цикл
				if err != nil && !app.testMode {
					v, _ := app.gui.View("filter")
					v.FrameColor = app.errorColor
					return
				}
				if err != nil && !app.testMode {
					log.Print("Error: regex syntax")
					return
				}
			}
			// Проходимся по каждой строке
			for _, line := range app.currentLogLines {
				switch app.selectFilterMode {
				// Fuzzy (неточный поиск без учета регистра)
				case "fuzzy":
					outputLine := app.fuzzyFilter(line, filter)
					if outputLine != "" {
						app.filteredLogLines = append(app.filteredLogLines, outputLine)
					}
				// Regex (с использованием регулярных выражений и без учета регистра по умолчанию)
				case "regex":
					outputLine := app.regexFilter(line, regex)
					if outputLine != "" {
						app.filteredLogLines = append(app.filteredLogLines, outputLine)
					}
				// Default (точный поиск с учетом регистра)
				default:
					filter = app.filterText
					if filter == "" || strings.Contains(line, filter) {
						lineColor := strings.ReplaceAll(line, filter, "\x1b[0;44m"+filter+"\033[0m")
						app.filteredLogLines = append(app.filteredLogLines, lineColor)
					}
				}
			}
		}
		// Если последняя строка не содержит пустую строку, то добавляем две пустые строки или одну по умолчанию
		if len(app.filteredLogLines) > 0 && app.filteredLogLines[len(app.filteredLogLines)-1] != "" {
			app.filteredLogLines = append(app.filteredLogLines, "", "")
		} else {
			app.filteredLogLines = append(app.filteredLogLines, "")
		}
		// Определяем режим покраски или пропускаем
		switch app.colorMode {
		case "default":
			app.filteredLogLines = app.mainColor(app.filteredLogLines)
		case "tailspin":
			// Проверяем, что tailspin или tspin установлен в системе
			binName, err := app.checkBin([]string{"tailspin", "tspin"})
			if err == nil {
				cmd := exec.Command(binName)
				logLines := strings.Join(app.filteredLogLines, "\n")
				// Создаем пайп для передачи данных
				cmd.Stdin = bytes.NewBufferString(logLines)
				var out bytes.Buffer
				cmd.Stdout = &out
				// Если ошибка, пропускаем покраску
				if err := cmd.Run(); err == nil {
					colorLogLines := strings.Split(out.String(), "\n")
					app.filteredLogLines = colorLogLines
				}
			}
		case "bat":
			binName, err := app.checkBin([]string{"bat", "batcat"})
			if err == nil {
				cmd := exec.Command(
					binName,
					"--language=log",
					"--paging=never",
					"--style=plain",
					"--color=always",
					"--decorations=always",
					"--theme=ansi",
				)
				logLines := strings.Join(app.filteredLogLines, "\n")
				// Создаем пайп для передачи данных
				cmd.Stdin = bytes.NewBufferString(logLines)
				var out bytes.Buffer
				cmd.Stdout = &out
				// Если ошибка, пропускаем покраску
				if err := cmd.Run(); err == nil {
					colorLogLines := strings.Split(out.String(), "\n")
					app.filteredLogLines = colorLogLines
				}
			}
		}
		// Debug end time
		endTime := time.Since(startTime)
		app.debugColorTime = endTime.Truncate(time.Millisecond).String()
	}
	// Debug: корректируем текущую позицию скролла, если размер массива стал меньше
	if size > len(app.filteredLogLines) {
		newScrollPos := len(app.filteredLogLines) - viewHeight
		app.logScrollPos = max(newScrollPos, 0)
	}
	// Обновляем автоскролл (всегда опускаем вывод в самый низ) для отображения отфильтрованных записей
	if !app.testMode {
		// Включаем автоскролл и сбрасываем позицию
		if !app.disableAutoScroll {
			app.autoScroll = true
		} else {
			app.autoScroll = false
		}
		app.updateStatus()
		app.logScrollPos = 0
		app.updateLogsView(true)
	}
}

// Fyzzy: Функция для неточного поиска (параметры: строка из цикла и текст фильтрации)
func (app *App) fuzzyFilter(inputLine, filter string) string {
	// Разбиваем текст фильтра на массив из строк
	filterWords := strings.Fields(filter)
	// Опускаем регистр текущей строки цикла
	lineLower := strings.ToLower(inputLine)
	var match = true
	// Проверяем, если строка не содержит хотя бы одно слово из фильтра, то пропускаем строку
	for _, word := range filterWords {
		if !strings.Contains(lineLower, word) {
			match = false
			break
		}
	}
	// Если строка подходит под фильтр, возвращаем ее с покраской
	if match {
		// Временные символы для обозначения начала и конца покраски найденных символов
		startColor := "►"
		endColor := "◄"
		originalLine := inputLine
		// Проходимся по всем словосочетаниям фильтра (массив через пробел) для позиционирования покраски
		for _, word := range filterWords {
			wordLower := strings.ToLower(word)
			start := 0
			// Ищем все вхождения слова в строке с учетом регистра
			for {
				// Находим индекс вхождения с учетом регистра
				idx := strings.Index(strings.ToLower(originalLine[start:]), wordLower)
				if idx == -1 {
					break // Если больше нет вхождений, выходим
				}
				start += idx // корректируем индекс с учетом текущей позиции
				// Вставляем временные символы для покраски
				originalLine = originalLine[:start] + startColor + originalLine[start:start+len(word)] + endColor + originalLine[start+len(word):]
				// Сдвигаем индекс для поиска в оставшейся части строки
				start += len(startColor) + len(word) + len(endColor)
			}
		}
		// Заменяем временные символы на ANSI escape-последовательности
		originalLine = strings.ReplaceAll(originalLine, startColor, "\x1b[0;44m")
		originalLine = strings.ReplaceAll(originalLine, endColor, "\033[0m")
		return originalLine
	} else {
		return ""
	}
}

// Regex: Функция для поска с использованием регулярных выражений (параметры: строка из цикла и скомпилированное регулярное выражение)
func (app *App) regexFilter(inputLine string, regex *regexp.Regexp) string {
	// Проверяем, что строка подходит под регулярное выражение
	if regex.MatchString(inputLine) {
		// Находим все найденные совпадени
		matches := regex.FindAllString(inputLine, -1)
		// Красим только первое найденное совпадение
		inputLine = strings.ReplaceAll(inputLine, matches[0], "\x1b[0;44m"+matches[0]+"\033[0m")
		return inputLine
	} else {
		return ""
	}
}

// -f/--command-fuzzy
func (app *App) commandLineFuzzy(filter string, color bool) {
	stat, err := os.Stdin.Stat()
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		return
	}
	if (stat.Mode() & os.ModeCharDevice) != 0 {
		fmt.Fprintln(os.Stderr, "No data. Use pipe to transfer data.")
		return
	}
	scanner := bufio.NewScanner(os.Stdin)
	var inputLines []string
	for scanner.Scan() {
		inputLines = append(inputLines, scanner.Text())
	}
	if err := scanner.Err(); err != nil {
		fmt.Fprintln(os.Stderr, err)
		return
	}
	if len(inputLines) == 0 {
		fmt.Fprintln(os.Stderr)
		return
	}
	for _, line := range inputLines {
		outputLine := app.fuzzyFilter(line, filter)
		if outputLine != "" {
			app.filteredLogLines = append(app.filteredLogLines, outputLine)
		}
	}
	// Если передан второй параметр (аргумент color), используем функцию покраски
	if color {
		app.commandLineColor(true)
	} else {
		for _, line := range app.filteredLogLines {
			fmt.Println(line)
		}
	}
}

// -r/--command-regex
func (app *App) commandLineRegex(regex *regexp.Regexp, color bool) {
	stat, err := os.Stdin.Stat()
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		return
	}
	if (stat.Mode() & os.ModeCharDevice) != 0 {
		fmt.Fprintln(os.Stderr, "No data. Use pipe to transfer data.")
		return
	}
	scanner := bufio.NewScanner(os.Stdin)
	var inputLines []string
	for scanner.Scan() {
		inputLines = append(inputLines, scanner.Text())
	}
	if err := scanner.Err(); err != nil {
		fmt.Fprintln(os.Stderr, err)
		return
	}
	if len(inputLines) == 0 {
		fmt.Fprintln(os.Stderr)
		return
	}
	for _, line := range inputLines {
		outputLine := app.regexFilter(line, regex)
		if outputLine != "" {
			app.filteredLogLines = append(app.filteredLogLines, outputLine)
		}
	}
	if color {
		app.commandLineColor(true)
	} else {
		for _, line := range app.filteredLogLines {
			fmt.Println(line)
		}
	}
}

// ---------------------------------------- Coloring/Highlighting ----------------------------------------

// Функция для покраски вывода в режиме командной строки
func (app *App) commandLineColor(fromFilter bool) {
	var inputColoring []string
	// Извлекаем текст после фильтрации
	if fromFilter {
		inputColoring = app.mainColor(app.filteredLogLines)
	} else {
		// Проверяем, подключен ли stdin через pipe или перенаправлен
		stat, err := os.Stdin.Stat()
		if err != nil {
			fmt.Fprintln(os.Stderr, err)
			return
		}
		// Проверяем, пуст ли stdin (например, если нет pipe или перенаправления)
		if (stat.Mode() & os.ModeCharDevice) != 0 {
			fmt.Fprintln(os.Stderr, "No data. Use pipe to transfer data.")
			return
		}
		scanner := bufio.NewScanner(os.Stdin)
		var inputLines []string
		for scanner.Scan() {
			inputLines = append(inputLines, scanner.Text())
		}
		if err := scanner.Err(); err != nil {
			fmt.Fprintln(os.Stderr, err)
			return
		}
		if len(inputLines) == 0 {
			fmt.Fprintln(os.Stderr)
			return
		}
		inputColoring = app.mainColor(inputLines)
	}
	// Выводим построчно
	for _, line := range inputColoring {
		fmt.Println(line)
	}
}

// (1) Основная функция покраски
func (app *App) mainColor(inputText []string) []string {
	// Максимальное количество потоков
	const maxWorkers = 10
	// Канал для передачи индексов всех строк
	tasks := make(chan int, len(inputText))
	// Срез для хранения обработанных строк
	colorLogLines := make([]string, len(inputText))
	// Объявляем группу ожидания для синхронизации всех горутин (воркеров)
	var wg sync.WaitGroup
	// Создаем maxWorkers горутин, где каждая будет обрабатывать задачи из канала tasks
	for range maxWorkers {
		go func() {
			// Горутина будет работать, пока в канале tasks есть задачи
			for index := range tasks {
				// Обрабатываем строку и сохраняем результат по соответствующему индексу
				colorLogLines[index] = app.lineColor(inputText[index])
				// Уменьшаем счетчик задач в группе ожидания.
				wg.Done()
			}
		}()
	}
	// Добавляем задачи в канал
	for i := range inputText {
		// Увеличиваем счетчик задач в группе ожидания
		wg.Add(1)
		// Передаем индекс строки в канал tasks
		tasks <- i
	}
	// Закрываем канал задач, чтобы воркеры завершили работу после обработки всех задач
	close(tasks)
	// Ждем завершения всех задач
	wg.Wait()
	return colorLogLines
}

// (2) Функция для покраски строк
func (app *App) lineColor(inputLine string) string {
	// Если строка пустая, пропускаем ее сразу
	if inputLine == "" {
		return ""
	}
	var colorLine string
	var filterColor = false
	// Извлекаем название контейнера в логах стека compose
	var containerName string
	if app.lastContainerizationSystem == "compose" {
		// Исключаем строку с делиметром
		if !strings.HasPrefix(inputLine, "⎯") {
			// Извлекаем название контейнера
			splitLine := strings.SplitN(inputLine, "] ", 2)
			if len(splitLine) >= 2 {
				containerName = splitLine[0]
				// Удаляем первый индекс из названия контейнера (квадратная открывающиеся скобка)
				containerName = containerName[1:]
				// Удаляем название контейнера из покраски
				inputLine = splitLine[1]
			}
		}
	}
	// Разбиваем строку по пробелам, сохраняя их
	words := strings.Split(inputLine, " ")
	var colorLineBuilder strings.Builder
	for i, word := range words {
		// Исключаем строки с покраской при поиске (Background)
		if strings.Contains(word, "\x1b[0;44m") {
			filterColor = true
		}
		// Красим слово в функции
		if !filterColor {
			word = app.wordColor(word)
		}
		// Возобновляем покраску
		if strings.Contains(word, "\033[0m") {
			filterColor = false
		}
		// Добавляем слово обратно с пробелами
		if i != len(words)-1 {
			colorLineBuilder.WriteString(word + " ")
		} else {
			colorLineBuilder.WriteString(word)
		}
	}
	colorLine += colorLineBuilder.String()
	// (2.1) Добавляем желтую покраску для JSON строк (двойные кавычки и фигурные скобок)
	colorLine = strings.ReplaceAll(colorLine, "\"", "\033[33m\"\033[0m")
	colorLine = strings.ReplaceAll(colorLine, "{", "\033[33m{\033[0m")
	colorLine = strings.ReplaceAll(colorLine, "}", "\033[33m}\033[0m")
	// Словосочетания-исключения для ошибок
	colorLine = strings.ReplaceAll(colorLine, "Not found", "\033[31mNot found\033[0m")
	colorLine = strings.ReplaceAll(colorLine, "not found", "\033[31mnot found\033[0m")
	colorLine = strings.ReplaceAll(colorLine, "Bad request", "\033[31mBad request\033[0m")
	colorLine = strings.ReplaceAll(colorLine, "bad request", "\033[31mbad request\033[0m")
	// Возвращяем название контейнера с уникальной покраской
	if app.lastContainerizationSystem == "compose" && containerName != "" {
		if app.uniquePrefixColorMap[strings.TrimSpace(containerName)] != "" {
			return "[" + app.uniquePrefixColorMap[strings.TrimSpace(containerName)] + containerName + "\033[0m" + "] " + colorLine
		} else {
			return "[" + containerName + "] " + colorLine
		}
	} else {
		return colorLine
	}
}

// Игнорируем регистр и проверяем, что слово окружено не буквами и цифрами
func (app *App) replaceWordLower(word, keyword, color string) string {
	re := regexp.MustCompile(`(?i)\b` + regexp.QuoteMeta(keyword) + `\b`)
	return re.ReplaceAllStringFunc(word, func(match string) string {
		// Если цвет содержит фон, то добавляем отступы
		if strings.Contains(color, "30m") {
			return color + " " + match + " " + "\033[0m"
		} else {
			return color + match + "\033[0m"
		}
	})
}

// Поиск пользователей
func (app *App) containsUser(searchWord string) bool {
	return slices.Contains(app.userNameArray, searchWord)
}

// Покраска url путей
func (app *App) urlPathColor(cleanedWord string) string {
	// Используем Builder для объединения строк
	var sb strings.Builder
	// Начинаем с желтого цвета
	sb.WriteString("\033[33m")
	for _, char := range cleanedWord {
		switch {
		// Пурпурный цвет для символов и возвращяем желтый
		case char == '/' || char == '?' || char == '&' || char == '=' || char == ':' || char == '.':
			sb.WriteString("\033[35m")
			sb.WriteRune(char)
			sb.WriteString("\033[33m")
		// Синий цвет для цифр
		// case unicode.IsDigit(char):
		case char >= '0' && char <= '9':
			sb.WriteString("\033[34m")
			sb.WriteRune(char)
			sb.WriteString("\033[33m")
		default:
			sb.WriteRune(char)
		}
	}
	// Сброс цвета
	sb.WriteString("\033[0m")
	return sb.String()
}

func (app *App) intColor(inputString string) string {
	var colored strings.Builder
	// Флаги, для фиксации нахождения внутри числа/символа или нет
	inNumber := false
	inSymbol := false
	for _, char := range inputString {
		switch {
		case char >= '0' && char <= '9':
			// Если это цифра и мы еще не в числе, открываем цвет
			if !inNumber {
				colored.WriteString("\033[34m")
				inNumber = true
			}
		case char == '/' || char == ':' || char == '.' || char == '-' || char == '+' || char == '%':
			// Красим символы
			colored.WriteString("\033[35m")
			inSymbol = true
			inNumber = false
		default:
			// Если это не цифра и до этого было число, закрываем цвет
			if inNumber {
				inNumber = false
			}
			// Для всех других символов
			colored.WriteString("\033[0m")
		}
		// Добавляем символ в результат
		colored.WriteRune(char)
		// Закрываем цвет для символа
		if inSymbol {
			colored.WriteString("\033[0m")
			inSymbol = false
		}
	}
	// Закрываем цвет, если строка закончилась на числе
	if inNumber {
		colored.WriteString("\033[0m")
	}
	return colored.String()
}

// Проверка string на int и конвертация в int
func parseStringToInt(s string) (int, bool) {
	if len(s) == 0 {
		return 0, false
	}
	for _, r := range s {
		if !unicode.IsDigit(r) {
			return 0, false
		}
	}
	num, err := strconv.Atoi(s)
	if err != nil {
		return 0, false
	}
	return num, true
}

// (3) Функция для покраски словосочетаний
func (app *App) wordColor(inputWord string) string {
	// Опускаем регистр слова
	inputWordLower := strings.ToLower(inputWord)
	// Значение по умолчанию
	var coloredWord = inputWord
	switch {
	// Проверяем длинну символов на минимальную длинну или пропускаем покраску
	case len(inputWord) <= 3:
		// Исключения для вхождений длинной в 3 символа
		switch {
		case strings.Contains(inputWord, "GET"):
			coloredWord = app.replaceWordLower(inputWord, "GET", "\033[42m\033[30m")
		case strings.Contains(inputWord, "PUT"):
			coloredWord = strings.ReplaceAll(inputWord, "PUT", "\033[45m\033[30m PUT \033[0m")
		case strings.Contains(inputWord, "INF"):
			coloredWord = strings.ReplaceAll(inputWord, "INF", "\033[46m\033[30m INF \033[0m")
		case strings.Contains(inputWord, "WRN"):
			coloredWord = strings.ReplaceAll(inputWord, "WRN", "\033[43m\033[30m WRN \033[0m")
		case strings.Contains(inputWord, "ERR"):
			coloredWord = strings.ReplaceAll(inputWord, "ERR", "\033[41m\033[30m ERR \033[0m")
		default:
			// Проверяем вхождение символов на цифры
			inputInt, isInt := parseStringToInt(inputWord)
			if isInt {
				// Проверяем HTTP статус кодов ответов по стандарту Mozilla
				switch {
				case inputInt >= 200 && inputInt <= 208:
					coloredWord = strings.ReplaceAll(inputWord, inputWord, "\033[42m\033[30m "+inputWord+" \033[0m")
				case inputInt >= 300 && inputInt <= 308:
					coloredWord = strings.ReplaceAll(inputWord, inputWord, "\033[43m\033[30m "+inputWord+" \033[0m")
				case inputInt >= 400 && inputInt <= 431 || inputInt >= 500 && inputInt <= 511:
					coloredWord = strings.ReplaceAll(inputWord, inputWord, "\033[41m\033[30m "+inputWord+" \033[0m")
				default:
					// Красим цифры стандартно
					coloredWord = strings.ReplaceAll(inputWord, inputWord, "\033[34m"+inputWord+"\033[0m")
				}
			} else if app.integersInputRegex.MatchString(inputWord) {
				// Красим цифры по функции
				coloredWord = app.intColor(inputWord)
				return coloredWord
			}
		}
	// URL
	case strings.Contains(inputWord, "http://"):
		cleanedWord := app.trimHttpRegex.ReplaceAllString(inputWord, "")
		coloredChars := app.urlPathColor(cleanedWord)
		// Красный для http
		coloredWord = strings.ReplaceAll(inputWord, "http://"+cleanedWord, "\033[31mhttp\033[35m://"+coloredChars)
	case strings.Contains(inputWord, "https://"):
		cleanedWord := app.trimHttpsRegex.ReplaceAllString(inputWord, "")
		coloredChars := app.urlPathColor(cleanedWord)
		// Зеленый для https
		coloredWord = strings.ReplaceAll(inputWord, "https://"+cleanedWord, "\033[32mhttps\033[35m://"+coloredChars)
	// UNIX file paths and HTTP endpoints
	case strings.HasPrefix(inputWord, "/") || strings.HasPrefix(inputWord, "\"/") || strings.HasPrefix(inputWord, "~/") || strings.HasPrefix(inputWord, "./"):
		// Красим символы разделителя путей в пурпурный и возвращяем цвет
		coloredWord = strings.ReplaceAll(inputWord, "/", "\033[35m"+"/"+"\033[33m")
		// Начало query параметров для HTTP путей
		coloredWord = strings.ReplaceAll(coloredWord, "?", "\033[35m"+"?"+"\033[33m")
		// Разделитель параметров
		coloredWord = strings.ReplaceAll(coloredWord, "&", "\033[35m"+"&"+"\033[33m")
		// Разделитель для ключей и их значений в параметрах
		coloredWord = strings.ReplaceAll(coloredWord, "=", "\033[35m"+"="+"\033[33m")
		// Закрываем цвет
		coloredWord += "\033[0m"
		// Красим префиксы
		switch {
		case strings.HasPrefix(inputWord, "~/"):
			coloredWord = strings.Replace(coloredWord, "~", "\033[33m~\033[0m", 1)
		case strings.HasPrefix(inputWord, "./"):
			coloredWord = strings.Replace(coloredWord, ".", "\033[33m.\033[0m", 1)
		}
	// UNIX processes
	case app.syslogUnitRegex.MatchString(inputWord):
		unitSplit := strings.Split(inputWord, "[")
		unitName := unitSplit[0]
		unitId := strings.ReplaceAll(unitSplit[1], "]:", "")
		coloredWord = strings.ReplaceAll(inputWord, inputWord, "\033[36m"+unitName+"\033[0m"+"\033[33m"+"["+"\033[0m"+"\033[34m"+unitId+"\033[0m"+"\033[33m"+"]"+"\033[0m"+":")
	case strings.HasPrefix(inputWordLower, "kernel:"):
		coloredWord = app.replaceWordLower(inputWord, "kernel", "\033[36m")
	case strings.HasPrefix(inputWordLower, "rsyslogd:"):
		coloredWord = app.replaceWordLower(inputWord, "rsyslogd", "\033[36m")
	case strings.HasPrefix(inputWordLower, "sudo:"):
		coloredWord = app.replaceWordLower(inputWord, "sudo", "\033[36m")
	// HTTP request methods
	case strings.Contains(inputWord, "GET"):
		coloredWord = app.replaceWordLower(inputWord, "GET", "\033[42m\033[30m")
	case strings.Contains(inputWord, "POST"):
		coloredWord = app.replaceWordLower(inputWord, "POST", "\033[43m\033[30m")
	case strings.Contains(inputWord, "PUT"):
		coloredWord = app.replaceWordLower(inputWord, "PUT", "\033[45m\033[30m")
	case strings.Contains(inputWord, "PATCH"):
		coloredWord = app.replaceWordLower(inputWord, "PATCH", "\033[45m\033[30m")
	case strings.Contains(inputWord, "TRACE"):
		coloredWord = app.replaceWordLower(inputWord, "TRACE", "\033[45m\033[30m")
	case strings.Contains(inputWord, "OPTIONS"):
		coloredWord = app.replaceWordLower(inputWord, "OPTIONS", "\033[45m\033[30m")
	case strings.Contains(inputWord, "CONNECT"):
		coloredWord = app.replaceWordLower(inputWord, "CONNECT", "\033[42m\033[30m")
	case strings.Contains(inputWord, "DELETE"):
		coloredWord = app.replaceWordLower(inputWord, "DELETE", "\033[41m\033[30m")
	// Статусы
	case strings.Contains(inputWord, "DONE"):
		coloredWord = app.replaceWordLower(inputWord, "DONE", "\033[42m\033[30m")
	case strings.Contains(inputWord, "WARNING"):
		coloredWord = app.replaceWordLower(inputWord, "WARNING", "\033[43m\033[30m")
	case strings.Contains(inputWord, "WARN"):
		coloredWord = app.replaceWordLower(inputWord, "WARN", "\033[43m\033[30m")
	case strings.Contains(inputWord, "WRN"):
		coloredWord = app.replaceWordLower(inputWord, "WRN", "\033[43m\033[30m")
	case strings.Contains(inputWord, "DEBUG"):
		coloredWord = app.replaceWordLower(inputWord, "DEBUG", "\033[46m\033[30m")
	case strings.Contains(inputWord, "INFO"):
		coloredWord = app.replaceWordLower(inputWord, "INFO", "\033[46m\033[30m")
	case strings.Contains(inputWord, "INF"):
		coloredWord = app.replaceWordLower(inputWord, "INF", "\033[46m\033[30m")
	case strings.Contains(inputWord, "NOTICE"):
		coloredWord = app.replaceWordLower(inputWord, "NOTICE", "\033[46m\033[30m")
	case strings.Contains(inputWord, "ERROR"):
		coloredWord = app.replaceWordLower(inputWord, "ERROR", "\033[41m\033[30m")
	case strings.Contains(inputWord, "ERR"):
		coloredWord = app.replaceWordLower(inputWord, "ERR", "\033[41m\033[30m")
	case strings.Contains(inputWord, "CRITICAL"):
		coloredWord = app.replaceWordLower(inputWord, "CRITICAL", "\033[41m\033[30m")
	case strings.Contains(inputWord, "CRIT"):
		coloredWord = app.replaceWordLower(inputWord, "CRIT", "\033[41m\033[30m")
	case strings.Contains(inputWord, "ALERT"):
		coloredWord = app.replaceWordLower(inputWord, "ALERT", "\033[41m\033[30m")
	case strings.Contains(inputWord, "EMERGENCY"):
		coloredWord = app.replaceWordLower(inputWord, "EMERGENCY", "\033[41m\033[30m")
	case strings.Contains(inputWord, "EMERG"):
		coloredWord = app.replaceWordLower(inputWord, "EMERG", "\033[41m\033[30m")
	case strings.Contains(inputWord, "FAILURE"):
		coloredWord = app.replaceWordLower(inputWord, "FAILURE", "\033[41m\033[30m")
	case strings.Contains(inputWord, "FAIL"):
		coloredWord = app.replaceWordLower(inputWord, "FAIL", "\033[41m\033[30m")
	case strings.Contains(inputWord, "FATAL"):
		coloredWord = app.replaceWordLower(inputWord, "FATAL", "\033[41m\033[30m")
	// Желтый [33m]
	// Известные имена: hostname и username
	case strings.Contains(inputWord, app.hostName):
		coloredWord = strings.ReplaceAll(inputWord, app.hostName, ""+app.hostName+"\033[0m")
	case strings.Contains(inputWord, app.userName):
		coloredWord = strings.ReplaceAll(inputWord, app.userName, "\033[33m"+app.userName+"\033[0m")
	// Список пользователей из passwd
	case app.containsUser(inputWord):
		coloredWord = app.replaceWordLower(inputWord, inputWord, "\033[33m")
	// Предупреждения
	case strings.Contains(inputWordLower, "warn"):
		words := []string{"warnings", "warning", "warn"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[33m")
				break
			}
		}
	// Update delimiter
	case strings.Contains(inputWord, "⎯"):
		coloredWord = strings.ReplaceAll(inputWord, inputWord, "\033[35m"+inputWord+"\033[0m")
	// Исключения для зеленого
	case strings.Contains(inputWordLower, "unblock"):
		words := []string{"unblocking", "unblocked", "unblock"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[32m")
				break
			}
		}
	// Красный (ошибки) [31m]
	case strings.Contains(inputWordLower, "err"):
		words := []string{"stderr", "errors", "error", "erro", "err"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[31m")
				break
			}
		}
	case strings.Contains(inputWordLower, "dis"):
		words := []string{"disconnected", "disconnection", "disconnects", "disconnect", "disabled", "disabling", "disable"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[31m")
				break
			}
		}
	case strings.Contains(inputWordLower, "crash"):
		words := []string{"crashed", "crashing", "crash"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[31m")
				break
			}
		}
	case strings.Contains(inputWordLower, "delet"):
		words := []string{"deletion", "deleted", "deleting", "deletes", "delete"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[31m")
				break
			}
		}
	case strings.Contains(inputWordLower, "remov"):
		words := []string{"removing", "removed", "removes", "remove"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[31m")
				break
			}
		}
	case strings.Contains(inputWordLower, "stop"):
		words := []string{"stopping", "stopped", "stoped", "stops", "stop"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[31m")
				break
			}
		}
	case strings.Contains(inputWordLower, "invalid"):
		words := []string{"invalidation", "invalidating", "invalidated", "invalidate", "invalid"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[31m")
				break
			}
		}
	case strings.Contains(inputWordLower, "abort"):
		words := []string{"aborted", "aborting", "abort"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[31m")
				break
			}
		}
	case strings.Contains(inputWordLower, "block"):
		words := []string{"blocked", "blocker", "blocking", "blocks", "block"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[31m")
				break
			}
		}
	case strings.Contains(inputWordLower, "activ"):
		words := []string{"inactive", "deactivated", "deactivating", "deactivate"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[31m")
				break
			}
		}
	case strings.Contains(inputWordLower, "exit"):
		words := []string{"exited", "exiting", "exits", "exit"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[31m")
				break
			}
		}
	case strings.Contains(inputWordLower, "crit"):
		words := []string{"critical", "critic", "crit"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[31m")
				break
			}
		}
	case strings.Contains(inputWordLower, "fail"):
		words := []string{"failed", "failure", "failing", "fails", "fail"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[31m")
				break
			}
		}
	case strings.Contains(inputWordLower, "fatal"):
		words := []string{"fatality", "fataling", "fatals", "fatal"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[31m")
				break
			}
		}
	case strings.Contains(inputWordLower, "clos"):
		words := []string{"closed", "closing", "close"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[31m")
				break
			}
		}
	case strings.Contains(inputWordLower, "drop"):
		words := []string{"dropped", "droping", "drops", "drop"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[31m")
				break
			}
		}
	case strings.Contains(inputWordLower, "panic"):
		words := []string{"panicked", "panics", "panic"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[31m")
				break
			}
		}
	case strings.Contains(inputWordLower, "emerg"):
		words := []string{"emergency", "emerg"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[31m")
				break
			}
		}
	case strings.Contains(inputWordLower, "reject"):
		words := []string{"rejecting", "rejection", "rejected", "reject"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[31m")
				break
			}
		}
	case strings.Contains(inputWordLower, "refus"):
		words := []string{"refusing", "refused", "refuses", "refuse"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[31m")
				break
			}
		}
	case strings.Contains(inputWordLower, "denied"):
		coloredWord = app.replaceWordLower(inputWord, "denied", "\033[31m")
	case strings.Contains(inputWordLower, "unavailable"):
		coloredWord = app.replaceWordLower(inputWord, "unavailable", "\033[31m")
	case strings.Contains(inputWordLower, "unknown"):
		coloredWord = app.replaceWordLower(inputWord, "unknown", "\033[31m")
	case strings.Contains(inputWordLower, "unsuccessful"):
		coloredWord = app.replaceWordLower(inputWord, "unsuccessful", "\033[31m")
	case strings.Contains(inputWordLower, "unauthorized"):
		coloredWord = app.replaceWordLower(inputWord, "unauthorized", "\033[31m")
	case strings.Contains(inputWordLower, "forbidden"):
		coloredWord = app.replaceWordLower(inputWord, "forbidden", "\033[31m")
	case strings.Contains(inputWordLower, "conflict"):
		coloredWord = app.replaceWordLower(inputWord, "conflict", "\033[31m")
	case strings.Contains(inputWordLower, "severe"):
		coloredWord = app.replaceWordLower(inputWord, "severe", "\033[31m")
	case strings.Contains(inputWordLower, "false"):
		coloredWord = app.replaceWordLower(inputWord, "false", "\033[31m")
	case strings.Contains(inputWordLower, "null"):
		coloredWord = app.replaceWordLower(inputWord, "null", "\033[31m")
	case strings.Contains(inputWordLower, "nil"):
		coloredWord = app.replaceWordLower(inputWord, "nil", "\033[31m")
	case strings.Contains(inputWordLower, "none"):
		coloredWord = app.replaceWordLower(inputWord, "none", "\033[31m")
	// Исключения для синего
	case strings.Contains(inputWordLower, "res") && !app.colorActionsDisable:
		words := []string{"resolved", "resolving", "resolve", "restarting", "restarted", "restart"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
				break
			}
		}
	// Зеленый (успех) [32m]
	case strings.Contains(inputWordLower, "succe"):
		words := []string{"successfully", "successful", "succeeded", "succeed", "success"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[32m")
				break
			}
		}
	case strings.Contains(inputWordLower, "complet"):
		words := []string{"completed", "completing", "completion", "completes", "complete"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[32m")
				break
			}
		}
	case strings.Contains(inputWordLower, "accept"):
		words := []string{"accepted", "accepting", "acception", "acceptance", "acceptable", "acceptably", "accepte", "accepts", "accept"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[32m")
				break
			}
		}
	case strings.Contains(inputWordLower, "connect"):
		words := []string{"connected", "connecting", "connection", "connects", "connect"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[32m")
				break
			}
		}
	case strings.Contains(inputWordLower, "finish"):
		words := []string{"finished", "finishing", "finish"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[32m")
				break
			}
		}
	case strings.Contains(inputWordLower, "start"):
		words := []string{"started", "starting", "startup", "start"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[32m")
				break
			}
		}
	case strings.Contains(inputWordLower, "enable"):
		words := []string{"enabled", "enables", "enable"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[32m")
				break
			}
		}
	case strings.Contains(inputWordLower, "allow"):
		words := []string{"allowed", "allowing", "allow"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[32m")
				break
			}
		}
	case strings.Contains(inputWordLower, "pass"):
		words := []string{"passed", "passing"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[32m")
				break
			}
		}
	case strings.Contains(inputWordLower, "ready"):
		coloredWord = app.replaceWordLower(inputWord, "ready", "\033[32m")
	case strings.Contains(inputWordLower, "available"):
		coloredWord = app.replaceWordLower(inputWord, "available", "\033[32m")
	case strings.Contains(inputWordLower, "running"):
		coloredWord = app.replaceWordLower(inputWord, "running", "\033[32m")
	case strings.Contains(inputWordLower, "installed"):
		coloredWord = app.replaceWordLower(inputWord, "installed", "\033[32m")
	case strings.Contains(inputWordLower, "true"):
		coloredWord = app.replaceWordLower(inputWord, "true", "\033[32m")
	case strings.Contains(inputWordLower, "done"):
		coloredWord = app.replaceWordLower(inputWord, "done", "\033[32m")
	// Голубой (цифры) + пурпурный [34m]
	// Byte (0x04)
	case app.hexByteRegex.MatchString(inputWord):
		coloredWord = app.hexByteRegex.ReplaceAllStringFunc(inputWord, func(match string) string {
			colored := ""
			var coloredSb5765 strings.Builder
			for _, char := range match {
				if char == 'x' {
					coloredSb5765.WriteString("\033[35m" + string(char) + "\033[0m")
				} else {
					coloredSb5765.WriteString("\033[34m" + string(char) + "\033[0m")
				}
			}
			colored += coloredSb5765.String()
			return colored
		})
	// DateTime
	case app.dateTimeRegex.MatchString(inputWord):
		coloredWord = app.dateTimeRegex.ReplaceAllStringFunc(inputWord, func(match string) string {
			colored := ""
			var coloredSb5778 strings.Builder
			for _, char := range match {
				if char == '-' || char == '.' || char == ':' || char == '+' || char == 'T' || char == 'Z' {
					// Пурпурный для символов
					coloredSb5778.WriteString("\033[35m" + string(char) + "\033[0m")
				} else {
					// Синий для цифр
					coloredSb5778.WriteString("\033[34m" + string(char) + "\033[0m")
				}
			}
			colored += coloredSb5778.String()
			return colored
		})
	// Integers
	case app.integersInputRegex.MatchString(inputWord):
		coloredWord = app.intColor(inputWord)
		return coloredWord
	// Отключаем покраску действий
	case app.colorActionsDisable:
		break
	// Действия [36m]
	case strings.Contains(inputWordLower, "session"):
		coloredWord = app.replaceWordLower(inputWord, "session", "\033[36m")
	case strings.Contains(inputWordLower, "log"):
		words := []string{"logged", "login"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
				break
			}
		}
	case strings.Contains(inputWordLower, "regi"):
		words := []string{"registered", "registration"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
				break
			}
		}
	case strings.Contains(inputWordLower, "auth"):
		words := []string{"authenticating", "authentication", "authenticate", "authorization"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
				break
			}
		}
	case strings.Contains(inputWordLower, "return"):
		words := []string{"returned", "returne", "return"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
				break
			}
		}
	case strings.Contains(inputWordLower, "listen"):
		words := []string{"listening", "listener", "listen"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
				break
			}
		}
	case strings.Contains(inputWordLower, "open"):
		words := []string{"opening", "opened", "open"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
				break
			}
		}
	case strings.Contains(inputWordLower, "creat"):
		words := []string{"created", "creating", "creates", "create"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
				break
			}
		}
	case strings.Contains(inputWordLower, "boot"):
		words := []string{"reboot", "booting", "boot"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
				break
			}
		}
	case strings.Contains(inputWordLower, "load"):
		words := []string{"overloading", "overloaded", "overload", "uploading", "uploaded", "uploads", "upload", "downloading", "downloaded", "downloads", "download", "loading", "loaded", "load"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
				break
			}
		}
	case strings.Contains(inputWordLower, "up"):
		words := []string{"updates", "updated", "updating", "update", "upgrades", "upgraded", "upgrading", "upgrade", "setup"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
				break
			}
		}
	case strings.Contains(inputWordLower, "sync"):
		words := []string{"synchronization", "synchronize", "sync"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
				break
			}
		}
	case strings.Contains(inputWordLower, "launch"):
		words := []string{"launched", "launching", "launch"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
				break
			}
		}
	case strings.Contains(inputWordLower, "chang"):
		words := []string{"changed", "changing", "change"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
				break
			}
		}
	case strings.Contains(inputWordLower, "clea"):
		words := []string{"cleaning", "cleaner", "clearing", "cleared", "clear"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
				break
			}
		}
	case strings.Contains(inputWordLower, "skip"):
		words := []string{"skipping", "skipped", "skip"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
				break
			}
		}
	case strings.Contains(inputWordLower, "miss"):
		words := []string{"missing", "missed"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
				break
			}
		}
	case strings.Contains(inputWordLower, "mount"):
		words := []string{"mountpoint", "mounted", "mounting", "mount"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
				break
			}
		}
	case strings.Contains(inputWordLower, "conf"):
		words := []string{"configurations", "configuration", "configuring", "configured", "configure", "config"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
				break
			}
		}
	case strings.Contains(inputWordLower, "read"):
		words := []string{"reading", "readed", "read"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
				break
			}
		}
	case strings.Contains(inputWordLower, "writ"):
		words := []string{"writing", "writed", "write"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
				break
			}
		}
	case strings.Contains(inputWordLower, "sav"):
		words := []string{"saved", "saving", "save"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
				break
			}
		}
	case strings.Contains(inputWordLower, "paus"):
		words := []string{"paused", "pausing", "pause"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
				break
			}
		}
	case strings.Contains(inputWordLower, "norm"):
		words := []string{"normal", "norm"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
				break
			}
		}
	case strings.Contains(inputWordLower, "alert"):
		words := []string{"alerting", "alert"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
				break
			}
		}
	case strings.Contains(inputWordLower, "noti"):
		words := []string{"notifications", "notification", "notify", "noting", "notice"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
				break
			}
		}
	case strings.Contains(inputWordLower, "in"):
		words := []string{"informations", "information", "informing", "informed", "info", "installation", "installing", "install", "initialization", "initial"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
				break
			}
		}
	case strings.Contains(inputWordLower, "out"):
		words := []string{"timeout", "stdout"}
		for _, word := range words {
			if strings.Contains(inputWordLower, word) {
				coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
				break
			}
		}
	case strings.Contains(inputWordLower, "debug"):
		coloredWord = app.replaceWordLower(inputWord, "debug", "\033[36m")
	case strings.Contains(inputWordLower, "verbose"):
		coloredWord = app.replaceWordLower(inputWord, "verbose", "\033[36m")
	case strings.Contains(inputWordLower, "level"):
		coloredWord = app.replaceWordLower(inputWord, "level", "\033[36m")
	case strings.Contains(inputWordLower, "status"):
		coloredWord = app.replaceWordLower(inputWord, "status", "\033[36m")
	case strings.Contains(inputWordLower, "shutdown"):
		coloredWord = app.replaceWordLower(inputWord, "shutdown", "\033[36m")
	}
	return coloredWord
}

// ---------------------------------------- Log output ----------------------------------------

// Функция для обновления вывода журнала (параметр для прокрутки в самый вниз)
func (app *App) updateLogsView(lowerDown bool) {
	// Получаем доступ к выводу журнала
	v, err := app.gui.View("logs")
	if err != nil {
		return
	}
	// Очищаем окно для отображения новых строк
	v.Clear()
	// Получаем ширину и высоту окна
	viewWidth, viewHeight := v.Size()
	// Опускаем в самый низ, только если это не ручной скролл (отключается параметром)
	if lowerDown {
		// Если количество строк больше высоты окна, опускаем в самый низ
		if len(app.filteredLogLines) > viewHeight-1 {
			app.logScrollPos = len(app.filteredLogLines) - viewHeight - 1
		} else {
			app.logScrollPos = 0
		}
	}
	// Позиция скролла не может быть меньше 0
	if app.logScrollPos < 0 {
		app.logScrollPos = 0
	}
	// Определяем количество строк для отображения, начиная с позиции logScrollPos
	startLine := app.logScrollPos
	endLine := min(startLine+viewHeight, len(app.filteredLogLines))
	// Учитываем auto wrap (только в конце лога) и проверяем, что журнал не пустой
	if app.logScrollPos == len(app.filteredLogLines)-viewHeight-1 && len(app.filteredLogLines) > 0 {
		var viewLines = 0                             // количество строк для вывода
		var viewCounter = 0                           // обратный счетчик видимых строк для остановки
		var viewIndex = len(app.filteredLogLines) - 1 // начальный индекс для строк с конца
		for viewIndex >= 0 && viewIndex < len(app.filteredLogLines) {
			// #45 Проверка, что индекс не вышел за пределы массива (исправлено: проверка ДО использования индекса)

			// Фиксируем текущую входную строку и счетчик
			viewLines += 1
			viewCounter += 1
			// Получаем длинну видимых символов в строке с конца
			lengthLine := len([]rune(ansiEscape.ReplaceAllString(app.filteredLogLines[viewIndex], "")))
			// Если длинна строки больше ширины окна, получаем количество дополнительных строк
			if lengthLine > viewWidth && viewWidth > 0 {
				// Увеличивая счетчик и пропускаем строки
				viewCounter += lengthLine / viewWidth
			}
			// Если счетчик привысил количество видимых строк, вычетаем последнюю строку из видимости
			if viewCounter > viewHeight {
				viewLines -= 1
			}
			if viewCounter >= viewHeight {
				break
			}
			// Уменьшаем индекс
			viewIndex -= 1
		}
		// Индекс начала печати не должен быть меньше 0
		printStart := max(len(app.filteredLogLines)-viewLines-1, 0)
		for i := printStart; i < endLine; i++ {
			fmt.Fprintln(v, app.filteredLogLines[i])
		}
	} else {
		// Проходим по отфильтрованным строкам и выводим их
		for i := startLine; i < endLine; i++ {
			fmt.Fprintln(v, app.filteredLogLines[i])
		}
	}
	// Вычисляем процент прокрутки и обновляем заголовок
	var percentage = 0
	if len(app.filteredLogLines) > 0 {
		// Стартовая позиция + размер текущего вывода логов и округляем в большую сторону (math)
		percentage = int(math.Ceil(float64((startLine+viewHeight)*100) / float64(len(app.filteredLogLines))))
		if percentage > 100 {
			v.Title = fmt.Sprintf(
				"Logs: 100%% (%d) ["+app.debugLoadTime+"/"+app.debugColorTime+"]",
				len(app.filteredLogLines),
			)
		} else {
			v.Title = fmt.Sprintf("Logs: %d%% (%d/%d) ["+app.debugLoadTime+"/"+app.debugColorTime+"]",
				percentage,
				startLine+1+viewHeight,
				len(app.filteredLogLines),
			)
		}
	} else {
		v.Title = "Logs: 0% (0) [" + app.debugLoadTime + "/" + app.debugColorTime + "]"
	}
	app.viewScrollLogs(percentage)
}

// Функция для обновления интерфейса скроллинга
func (app *App) viewScrollLogs(percentage int) {
	vScroll, _ := app.gui.View("scrollLogs")
	vScroll.Clear()
	// Определяем высоту окна
	_, viewHeight := vScroll.Size()
	// Заполняем скролл пробелами, если вывод пустой или не выходит за пределы окна
	if percentage == 0 || percentage > 100 {
		fmt.Fprintln(vScroll, "▲")
		for i := 1; i < viewHeight-1; i++ {
			fmt.Fprintln(vScroll, " ")
		}
		fmt.Fprintln(vScroll, "▼")
	} else {
		// Рассчитываем позицию курсора (корректируем процент на размер скролла и верхней стрелки)
		scrollPosition := (viewHeight*percentage)/100 - 3 - 1
		fmt.Fprintln(vScroll, "▲")
		// Выводим строки с пробелами и символом █
	for_scroll:
		for i := 1; i < viewHeight-3; i++ {
			// Проверяем текущую поизицию
			switch {
			case i == scrollPosition:
				// Выводим скролл
				fmt.Fprintln(vScroll, "███")
			case scrollPosition <= 0 || app.logScrollPos == 0:
				// Если вышли за пределы окна или текст находится в самом начале, устанавливаем курсор в начало
				fmt.Fprintln(vScroll, "███")
				// Остальное заполняем пробелами с учетом стрелки и курсора (-4) до последней стрелки (-1)
				for i := 4; i < viewHeight-1; i++ {
					fmt.Fprintln(vScroll, " ")
				}
				break for_scroll
			default:
				// Пробелы на остальных строках
				fmt.Fprintln(vScroll, " ")
			}
		}
		fmt.Fprintln(vScroll, "▼")
	}
}

// Функция для скроллинга вниз
func (app *App) scrollDownLogs(step int) error {
	v, err := app.gui.View("logs")
	if err != nil {
		return err
	}
	// Получаем высоту окна, что бы не опускать лог с пустыми строками
	_, viewHeight := v.Size()
	// Проверяем, что размер журнала больше размера окна
	if len(app.filteredLogLines) > viewHeight {
		// Увеличиваем позицию прокрутки
		app.logScrollPos += step
		// Если достигнут конец списка, останавливаем на максимальной длинне с учетом высоты окна
		if app.logScrollPos > len(app.filteredLogLines)-1-viewHeight {
			app.logScrollPos = len(app.filteredLogLines) - 1 - viewHeight
			// Включаем автоскролл (если он не отключен)
			if !app.disableAutoScroll {
				app.autoScroll = true
			} else {
				app.autoScroll = false
			}
			if !app.testMode {
				app.updateStatus()
			}
		}
		// Вызываем функцию для обновления отображения журнала
		app.updateLogsView(false)
	}
	return nil
}

// Функция для скроллинга вверх
func (app *App) scrollUpLogs(step int) error {
	app.logScrollPos -= step
	if app.logScrollPos < 0 {
		app.logScrollPos = 0
	}
	// Отключаем автоскролл
	app.autoScroll = false
	if !app.testMode {
		app.updateStatus()
	}
	app.updateLogsView(false)
	return nil
}

// Функция для переход к началу журнала
func (app *App) pageUpLogs() {
	app.logScrollPos = 0
	app.autoScroll = false
	if !app.testMode {
		app.updateStatus()
	}
	app.updateLogsView(false)
}

// Функция для очистки поля ввода фильтра вывода лога
func (app *App) clearFilterEditor(g *gocui.Gui) {
	v, _ := g.View("filter")
	// Очищаем содержимое View
	v.Clear()
	// Устанавливаем курсор на начальную позицию
	if err := v.SetCursor(0, 0); err != nil {
		return
	}
	// Очищаем буфер фильтра
	app.filterText = ""
	app.applyFilter(false)
}

// Функция для очистки поля ввода фильтра списков
func (app *App) clearFilterListEditor(g *gocui.Gui) {
	v, _ := g.View("filterList")
	v.Clear()
	if err := v.SetCursor(0, 0); err != nil {
		return
	}
	app.filterListText = ""
	app.applyFilterList()
}

// Функция для обновления последнего выбранного вывода лога (параметр для загрузки журнала)
func (app *App) updateLogOutput(newUpdate bool) {
	// Выполняем обновление интерфейса через метод Update для иницилизации перерисовки интерфейса
	app.gui.Update(func(g *gocui.Gui) error {
		// Сбрасываем автоскролл, что бы опустить журнал вниз, т.к. это всегда ручное обновление
		if !app.disableAutoScroll {
			app.autoScroll = true
		} else {
			app.autoScroll = false
		}
		if !app.testMode {
			app.updateStatus()
		}
		switch app.lastWindow {
		case "services":
			if app.fastMode {
				go func() {
					app.loadJournalLogs(app.lastSelected, newUpdate)
				}()
			} else {
				app.loadJournalLogs(app.lastSelected, newUpdate)
			}
		case "varLogs":
			if app.fastMode {
				go func() {
					app.loadFileLogs(app.lastSelected, newUpdate)
				}()
			} else {
				app.loadFileLogs(app.lastSelected, newUpdate)
			}
		case "docker":
			if app.fastMode {
				go func() {
					app.loadDockerLogs(app.lastSelected, newUpdate)
				}()
			} else {
				app.loadDockerLogs(app.lastSelected, newUpdate)
			}
		}
		return nil
	})
}

// Запускает фоновое обновление с изменяемым интервалом (параметры для обновления времени и загрузки журнала)
func (app *App) updateLogBackground(secondsChan chan int, newUpdate bool) {
	seconds := app.logUpdateSeconds
	// Проверяем, есть ли в канале новое значение интервала
	select {
	case s := <-secondsChan:
		seconds = s
	default:
	}
	// Таймер
	ticker := time.NewTicker(time.Duration(seconds) * time.Second)
	// Гарантируем остановку таймера при выходе из функции
	defer ticker.Stop()
	for {
		select {
		// Если в канал поступило новое значение, перезапускаем таймер с новым интервалом
		case newSeconds := <-secondsChan:
			ticker.Reset(time.Duration(newSeconds) * time.Second)
		// Когда срабатывает таймер, выполняем обновление логов
		case <-ticker.C:
			// Обновляем журнал только если включен автоскролл
			if app.autoScroll {
				app.gui.Update(func(g *gocui.Gui) error {
					switch app.lastWindow {
					case "services":
						if app.fastMode {
							go func() {
								app.loadJournalLogs(app.lastSelected, newUpdate)
							}()
						} else {
							app.loadJournalLogs(app.lastSelected, newUpdate)
						}
					case "varLogs":
						if app.fastMode {
							go func() {
								app.loadFileLogs(app.lastSelected, newUpdate)
							}()
						} else {
							app.loadFileLogs(app.lastSelected, newUpdate)
						}
					case "docker":
						if app.fastMode {
							go func() {
								app.loadDockerLogs(app.lastSelected, newUpdate)
							}()
						} else {
							app.loadDockerLogs(app.lastSelected, newUpdate)
						}
					}
					return nil
				})
			}
		}
	}
}

// Функция для обновления вывода при изменение размера окна
func (app *App) updateWindowSize(seconds int) {
	for {
		app.gui.Update(func(g *gocui.Gui) error {
			v, err := g.View("logs")
			if err != nil {
				log.Panicln(err)
			}
			windowWidth, windowHeight := v.Size()
			if windowWidth != app.windowWidth || windowHeight != app.windowHeight {
				app.windowWidth, app.windowHeight = windowWidth, windowHeight
				app.updateLogsView(true)
				if v, err := g.View("services"); err == nil {
					_, viewHeight := v.Size()
					app.maxVisibleServices = viewHeight
				}
				if v, err := g.View("varLogs"); err == nil {
					_, viewHeight := v.Size()
					app.maxVisibleFiles = viewHeight
				}
				if v, err := g.View("docker"); err == nil {
					_, viewHeight := v.Size()
					app.maxVisibleDockerContainers = viewHeight
				}
				app.applyFilterList()
			}
			// Обновляем ширину для фильтрации по дате
			maxX, _ := g.Size()
			leftPanelWidth := maxX / 4
			filterWidth := (maxX - leftPanelWidth - 1) / 2
			if _, err := g.View("sinceFilter"); err == nil {
				if _, err := g.SetView("sinceFilter", leftPanelWidth+1, 0, leftPanelWidth+1+filterWidth, 2, 0); err != nil {
					return nil
				}
			}
			if _, err := g.View("untilFilter"); err == nil {
				if _, err := g.SetView("untilFilter", leftPanelWidth+1+filterWidth+1, 0, maxX-1, 2, 0); err != nil {
					return nil
				}
			}
			return nil
		})
		time.Sleep(time.Duration(seconds) * time.Second)
	}
}

// Функция для фиксации места загрузки журнала с помощью делимитра (параметр для обновления места и времени загрузки)
func (app *App) updateDelimiter(newUpdate bool) {
	if newUpdate {
		// Фиксируем (сохраняем) предпоследнюю (-2, т.к. последняя строка всегда пустая) строку для вставки делимитра (если это ручной выбор из списка) или выходим
		if len(app.currentLogLines) > 2 {
			app.lastUpdateLine = app.currentLogLines[len(app.currentLogLines)-2]
		} else {
			return
		}
		// Сбрасываем автоскролл
		if !app.disableAutoScroll {
			app.autoScroll = true
		} else {
			app.autoScroll = false
		}
		if !app.testMode {
			app.updateStatus()
		}
		// Фиксируем новое время загрузки журнала
		app.updateTime = time.Now().Format("15:04:05")
	} else {
		// Ищем индекс строки в массиве с конца
		delimiterIndex := 0
		for i := len(app.currentLogLines) - 1; i >= 0; i-- {
			if app.currentLogLines[i] == app.lastUpdateLine {
				delimiterIndex = i
				break
			}
		}
		// Проверяем, что строка найдена и найденный индекс меньше длинны массива строк
		if delimiterIndex > 0 && delimiterIndex < len(app.currentLogLines)-2 {
			// Формируем длинну делимитра
			v, _ := app.gui.View("logs")
			width, _ := v.Size()
			lengthDelimiter := width/2 - 5
			delimiter1 := strings.Repeat("⎯", lengthDelimiter)
			delimiter2 := delimiter1
			if width > lengthDelimiter+lengthDelimiter+10 {
				delimiter2 = strings.Repeat("⎯", lengthDelimiter+1)
			}
			var delimiterString = delimiter1 + " " + app.updateTime + " " + delimiter2
			// Вставляем новую строку после указанного индекса + 1 пустая строка (сдвигая остальные строки массива)
			app.currentLogLines = append(app.currentLogLines[:delimiterIndex+1],
				append([]string{delimiterString}, app.currentLogLines[delimiterIndex+1:]...)...)
		}
	}
}

// ---------------------------------------- Key Binding ----------------------------------------

// Карта для сопостовления сочетаний клавиш со значениями из конфигурации (#23)
var keyMap = map[string]gocui.Key{
	"f1":        gocui.KeyF1,
	"f2":        gocui.KeyF2,
	"f3":        gocui.KeyF3,
	"f4":        gocui.KeyF4,
	"f5":        gocui.KeyF5,
	"f6":        gocui.KeyF6,
	"f7":        gocui.KeyF7,
	"f8":        gocui.KeyF8,
	"f9":        gocui.KeyF9,
	"f10":       gocui.KeyF10,
	"f11":       gocui.KeyF11,
	"f12":       gocui.KeyF12,
	"ctrl+a":    gocui.KeyCtrlA,
	"ctrl+b":    gocui.KeyCtrlB,
	"ctrl+c":    gocui.KeyCtrlC,
	"ctrl+d":    gocui.KeyCtrlD,
	"ctrl+e":    gocui.KeyCtrlE,
	"ctrl+f":    gocui.KeyCtrlF,
	"ctrl+g":    gocui.KeyCtrlG,
	"ctrl+h":    gocui.KeyCtrlH,
	"ctrl+i":    gocui.KeyCtrlI,
	"ctrl+j":    gocui.KeyCtrlJ,
	"ctrl+k":    gocui.KeyCtrlK,
	"ctrl+l":    gocui.KeyCtrlL,
	"ctrl+m":    gocui.KeyCtrlM,
	"ctrl+n":    gocui.KeyCtrlN,
	"ctrl+o":    gocui.KeyCtrlO,
	"ctrl+p":    gocui.KeyCtrlP,
	"ctrl+q":    gocui.KeyCtrlQ,
	"ctrl+r":    gocui.KeyCtrlR,
	"ctrl+s":    gocui.KeyCtrlS,
	"ctrl+t":    gocui.KeyCtrlT,
	"ctrl+u":    gocui.KeyCtrlU,
	"ctrl+v":    gocui.KeyCtrlV,
	"ctrl+w":    gocui.KeyCtrlW,
	"ctrl+x":    gocui.KeyCtrlX,
	"ctrl+y":    gocui.KeyCtrlY,
	"ctrl+z":    gocui.KeyCtrlZ,
	"tab":       gocui.KeyTab,
	"shift+tab": gocui.KeyBacktab,
	"enter":     gocui.KeyEnter,
	"space":     gocui.KeySpace,
	"backspace": gocui.KeyBackspace,
	"del":       gocui.KeyDelete,
	"delete":    gocui.KeyDelete,
	"esc":       gocui.KeyEsc,
	"escape":    gocui.KeyEsc,
}

// Функция для опредиления клавиш из конфигурации (#23)
func getHotkey(configKey, defaultKey string) (any, gocui.Modifier) {
	// Опускаем регистр для всех вхождений (букв и сочетаний)
	inputKey := strings.ToLower(configKey)
	switch {
	// Если это одна буква, конвертируем string в rune и извлекаем значение
	case len(inputKey) == 1:
		if r, _ := utf8.DecodeRuneInString(inputKey); r != utf8.RuneError {
			return r, gocui.ModNone
		}
	// Возвращяем alt mode
	case strings.HasPrefix(inputKey, "alt+"):
		keyAlt := strings.Replace(inputKey, "alt+", "", 1)
		key, exists := keyMap[keyAlt]
		if exists {
			return key, gocui.ModAlt
		}
	// Если сочетание клавиш содержит shift, извлекаем последнюю букву в верхнем регистре
	case strings.HasPrefix(inputKey, "shift+") && inputKey != "shift+tab":
		inputKey = strings.ToTitle(configKey)
		return []rune(inputKey)[len(inputKey)-1], gocui.ModNone
	default:
		// Ищем сочетание клавиш в карте
		key, exists := keyMap[inputKey]
		if exists {
			return key, gocui.ModNone
		}
	}
	// Возвращяем значение по умолчанию (которое передается во втором параметре)
	if len(defaultKey) == 1 {
		if r, _ := utf8.DecodeRuneInString(defaultKey); r != utf8.RuneError {
			return r, gocui.ModNone
		}
	}
	return keyMap[defaultKey], gocui.ModNone
}

// Функция для биндинга клавиш
func (app *App) setupKeybindings() error {
	mainViews := []string{
		"filterList",
		"services",
		"varLogs",
		"docker",
		"filter",
		"sinceFilter",
		"untilFilter",
		"logs",
	}

	// Открытие окна справки (F1)
	customHelp, altMode := getHotkey(config.Hotkeys.ShowHelp, "f1")
	helpHandler := func(g *gocui.Gui, v *gocui.View) error {
		app.showInterfaceHelp(g)
		// Удаляем глобальные биндинги
		g.DeleteKeybindings("")
		// Удаляем все биндинги назначенные для окон
		for _, viewName := range mainViews {
			g.DeleteKeybindings(viewName)
		}
		// Создаем временный биндинг на Esc для закрытия окна
		if err := app.gui.SetKeybinding("", gocui.KeyEsc, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
			app.closeHelp(g)
			// Возвращяем стандартные биндиги после закрытия окна справки
			if err := app.setupKeybindings(); err != nil {
				log.Panicln("Error key bindings", err)
			}
			return nil
		}); err != nil {
			return err
		}
		if err := app.gui.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
			return err
		}
		return nil
	}
	if err := app.gui.SetKeybinding("", customHelp, altMode, helpHandler); err != nil {
		return err
	}

	// Открытие окна для переключения ssh хостов и контекстов (F2)
	customManager, altMode := getHotkey(config.Hotkeys.ShowManager, "f2")
	managerHandler := func(g *gocui.Gui, v *gocui.View) error {
		app.showInterfaceManager(g)
		g.DeleteKeybindings("")
		if v, err := g.SetCurrentView("sshManager"); err == nil {
			v.FrameColor = app.selectedFrameColor
			v.TitleColor = app.selectedTitleColor
		}
		for _, viewName := range mainViews {
			g.DeleteKeybindings(viewName)
		}
		// Tab для переключения между окнами
		customTab, altMode := getHotkey(config.Hotkeys.SwitchWindow, "tab")
		views := []string{
			"sshManager",
			"dockerContextManager",
			"kubernetesContextManager",
			"kubernetesNamespaceManager",
		}
		app.gui.SetKeybinding("", customTab, altMode, func(g *gocui.Gui, v *gocui.View) error {
			return app.nextViewManager(g, v, views)
		})
		customBackTab, altMode := getHotkey(config.Hotkeys.BackSwitchWindows, "shift+tab")
		backViews := []string{
			"kubernetesNamespaceManager",
			"kubernetesContextManager",
			"dockerContextManager",
			"sshManager",
		}
		app.gui.SetKeybinding("", customBackTab, altMode, func(g *gocui.Gui, v *gocui.View) error {
			return app.nextViewManager(g, v, backViews)
		})
		customEnter, altModeEnter := getHotkey(config.Hotkeys.LoadJournal, "enter")
		// Управление в окне sshManager
		if err := g.SetKeybinding("sshManager", gocui.KeyArrowUp, gocui.ModNone, app.moveCursorUp); err != nil {
			log.Panicln(err)
		}
		if err := g.SetKeybinding("sshManager", gocui.KeyArrowDown, gocui.ModNone, app.moveCursorDown); err != nil {
			log.Panicln(err)
		}
		if err := g.SetKeybinding("sshManager", customEnter, altModeEnter, app.getSelectedLine); err != nil {
			log.Panicln(err)
		}
		// Управление в окне dockerContextManager
		if err := g.SetKeybinding("dockerContextManager", gocui.KeyArrowUp, gocui.ModNone, app.moveCursorUp); err != nil {
			log.Panicln(err)
		}
		if err := g.SetKeybinding("dockerContextManager", gocui.KeyArrowDown, gocui.ModNone, app.moveCursorDown); err != nil {
			log.Panicln(err)
		}
		if err := g.SetKeybinding("dockerContextManager", customEnter, altModeEnter, app.getSelectedLine); err != nil {
			log.Panicln(err)
		}
		// Управление в окне kubernetesContextManager
		if err := g.SetKeybinding("kubernetesContextManager", gocui.KeyArrowUp, gocui.ModNone, app.moveCursorUp); err != nil {
			log.Panicln(err)
		}
		if err := g.SetKeybinding("kubernetesContextManager", gocui.KeyArrowDown, gocui.ModNone, app.moveCursorDown); err != nil {
			log.Panicln(err)
		}
		if err := g.SetKeybinding("kubernetesContextManager", customEnter, altModeEnter, app.getSelectedLine); err != nil {
			log.Panicln(err)
		}
		// Управление в окне kubernetesNamespaceManager
		if err := g.SetKeybinding("kubernetesNamespaceManager", gocui.KeyArrowUp, gocui.ModNone, app.moveCursorUp); err != nil {
			log.Panicln(err)
		}
		if err := g.SetKeybinding("kubernetesNamespaceManager", gocui.KeyArrowDown, gocui.ModNone, app.moveCursorDown); err != nil {
			log.Panicln(err)
		}
		if err := g.SetKeybinding("kubernetesNamespaceManager", customEnter, altModeEnter, app.getSelectedLine); err != nil {
			log.Panicln(err)
		}
		// Закрытие окна
		if err := app.gui.SetKeybinding("", gocui.KeyEsc, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
			app.closeManager(g)
			if err := app.setupKeybindings(); err != nil {
				log.Panicln("Error key bindings", err)
			}
			return nil
		}); err != nil {
			return err
		}
		if err := app.gui.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
			return err
		}
		return nil
	}
	if err := app.gui.SetKeybinding("", customManager, altMode, managerHandler); err != nil {
		return err
	}

	// ↑↑↑
	// Пролистывание вверх
	// Up (1)
	if err := app.gui.SetKeybinding("services", gocui.KeyArrowUp, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		return app.prevService(v, 1)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("varLogs", gocui.KeyArrowUp, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		return app.prevFileName(v, 1)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("docker", gocui.KeyArrowUp, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		return app.prevDockerContainer(v, 1)
	}); err != nil {
		return err
	}
	// PgUp (1) #10
	if err := app.gui.SetKeybinding("services", gocui.KeyPgup, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		return app.prevService(v, 1)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("varLogs", gocui.KeyPgup, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		return app.prevFileName(v, 1)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("docker", gocui.KeyPgup, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		return app.prevDockerContainer(v, 1)
	}); err != nil {
		return err
	}
	// Custom up from config
	// Default: k (1)
	customUp, altMode := getHotkey(config.Hotkeys.Up, "k")
	if err := app.gui.SetKeybinding("services", customUp, altMode, func(g *gocui.Gui, v *gocui.View) error {
		return app.prevService(v, 1)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("varLogs", customUp, altMode, func(g *gocui.Gui, v *gocui.View) error {
		return app.prevFileName(v, 1)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("docker", customUp, altMode, func(g *gocui.Gui, v *gocui.View) error {
		return app.prevDockerContainer(v, 1)
	}); err != nil {
		return err
	}
	// Shift+Up (10)
	if err := app.gui.SetKeybinding("services", gocui.KeyArrowUp, gocui.ModShift, func(g *gocui.Gui, v *gocui.View) error {
		return app.prevService(v, 10)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("varLogs", gocui.KeyArrowUp, gocui.ModShift, func(g *gocui.Gui, v *gocui.View) error {
		return app.prevFileName(v, 10)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("docker", gocui.KeyArrowUp, gocui.ModShift, func(g *gocui.Gui, v *gocui.View) error {
		return app.prevDockerContainer(v, 10)
	}); err != nil {
		return err
	}
	// Shift+PgUp (10)
	if err := app.gui.SetKeybinding("services", gocui.KeyPgup, gocui.ModShift, func(g *gocui.Gui, v *gocui.View) error {
		return app.prevService(v, 10)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("varLogs", gocui.KeyPgup, gocui.ModShift, func(g *gocui.Gui, v *gocui.View) error {
		return app.prevFileName(v, 10)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("docker", gocui.KeyPgup, gocui.ModShift, func(g *gocui.Gui, v *gocui.View) error {
		return app.prevDockerContainer(v, 10)
	}); err != nil {
		return err
	}
	// Custom up from config
	// Default: shift+k (10)
	customQuickUp, altMode := getHotkey(config.Hotkeys.QuickUp, "K")
	if err := app.gui.SetKeybinding("services", customQuickUp, altMode, func(g *gocui.Gui, v *gocui.View) error {
		return app.prevService(v, 10)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("varLogs", customQuickUp, altMode, func(g *gocui.Gui, v *gocui.View) error {
		return app.prevFileName(v, 10)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("docker", customQuickUp, altMode, func(g *gocui.Gui, v *gocui.View) error {
		return app.prevDockerContainer(v, 10)
	}); err != nil {
		return err
	}
	// Alt+Up (100)
	if err := app.gui.SetKeybinding("services", gocui.KeyArrowUp, gocui.ModAlt, func(g *gocui.Gui, v *gocui.View) error {
		return app.prevService(v, 100)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("varLogs", gocui.KeyArrowUp, gocui.ModAlt, func(g *gocui.Gui, v *gocui.View) error {
		return app.prevFileName(v, 100)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("docker", gocui.KeyArrowUp, gocui.ModAlt, func(g *gocui.Gui, v *gocui.View) error {
		return app.prevDockerContainer(v, 100)
	}); err != nil {
		return err
	}
	// Alt+PgUp (100)
	if err := app.gui.SetKeybinding("services", gocui.KeyPgup, gocui.ModAlt, func(g *gocui.Gui, v *gocui.View) error {
		return app.prevService(v, 100)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("varLogs", gocui.KeyPgup, gocui.ModAlt, func(g *gocui.Gui, v *gocui.View) error {
		return app.prevFileName(v, 100)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("docker", gocui.KeyPgup, gocui.ModAlt, func(g *gocui.Gui, v *gocui.View) error {
		return app.prevDockerContainer(v, 100)
	}); err != nil {
		return err
	}
	// Custom up from config
	// Default: ctrl+k (100)
	customVeryQuickUp, altMode := getHotkey(config.Hotkeys.VeryQuickUp, "ctrl+k")
	if err := app.gui.SetKeybinding("services", customVeryQuickUp, altMode, func(g *gocui.Gui, v *gocui.View) error {
		return app.prevService(v, 100)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("varLogs", customVeryQuickUp, altMode, func(g *gocui.Gui, v *gocui.View) error {
		return app.prevFileName(v, 100)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("docker", customVeryQuickUp, altMode, func(g *gocui.Gui, v *gocui.View) error {
		return app.prevDockerContainer(v, 100)
	}); err != nil {
		return err
	}

	// ↓↓↓
	// Перемещение вниз к следующей службе (функция nextService), файлу (nextFileName) или контейнеру (nextDockerContainer)
	// Down (1)
	if err := app.gui.SetKeybinding("services", gocui.KeyArrowDown, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		return app.nextService(v, 1)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("varLogs", gocui.KeyArrowDown, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		return app.nextFileName(v, 1)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("docker", gocui.KeyArrowDown, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		return app.nextDockerContainer(v, 1)
	}); err != nil {
		return err
	}
	// PgDown (1) #10
	if err := app.gui.SetKeybinding("services", gocui.KeyPgdn, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		return app.nextService(v, 1)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("varLogs", gocui.KeyPgdn, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		return app.nextFileName(v, 1)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("docker", gocui.KeyPgdn, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		return app.nextDockerContainer(v, 1)
	}); err != nil {
		return err
	}
	// Custom down from config
	// Default: j (1)
	customDown, altMode := getHotkey(config.Hotkeys.Down, "j")
	if err := app.gui.SetKeybinding("services", customDown, altMode, func(g *gocui.Gui, v *gocui.View) error {
		return app.nextService(v, 1)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("varLogs", customDown, altMode, func(g *gocui.Gui, v *gocui.View) error {
		return app.nextFileName(v, 1)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("docker", customDown, altMode, func(g *gocui.Gui, v *gocui.View) error {
		return app.nextDockerContainer(v, 1)
	}); err != nil {
		return err
	}
	// Быстрое пролистывание вниз через 10 записей
	// Shift+Down (10)
	if err := app.gui.SetKeybinding("services", gocui.KeyArrowDown, gocui.ModShift, func(g *gocui.Gui, v *gocui.View) error {
		return app.nextService(v, 10)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("varLogs", gocui.KeyArrowDown, gocui.ModShift, func(g *gocui.Gui, v *gocui.View) error {
		return app.nextFileName(v, 10)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("docker", gocui.KeyArrowDown, gocui.ModShift, func(g *gocui.Gui, v *gocui.View) error {
		return app.nextDockerContainer(v, 10)
	}); err != nil {
		return err
	}
	// Shift+PgDown (10)
	if err := app.gui.SetKeybinding("services", gocui.KeyPgdn, gocui.ModShift, func(g *gocui.Gui, v *gocui.View) error {
		return app.nextService(v, 10)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("varLogs", gocui.KeyPgdn, gocui.ModShift, func(g *gocui.Gui, v *gocui.View) error {
		return app.nextFileName(v, 10)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("docker", gocui.KeyPgdn, gocui.ModShift, func(g *gocui.Gui, v *gocui.View) error {
		return app.nextDockerContainer(v, 10)
	}); err != nil {
		return err
	}
	// Custom down from config
	// Default: shift+j (10)
	customQuickDown, altMode := getHotkey(config.Hotkeys.QuickDown, "J")
	if err := app.gui.SetKeybinding("services", customQuickDown, altMode, func(g *gocui.Gui, v *gocui.View) error {
		return app.nextService(v, 10)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("varLogs", customQuickDown, altMode, func(g *gocui.Gui, v *gocui.View) error {
		return app.nextFileName(v, 10)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("docker", customQuickDown, altMode, func(g *gocui.Gui, v *gocui.View) error {
		return app.nextDockerContainer(v, 10)
	}); err != nil {
		return err
	}
	// Alt+Down (100)
	if err := app.gui.SetKeybinding("services", gocui.KeyArrowDown, gocui.ModAlt, func(g *gocui.Gui, v *gocui.View) error {
		return app.nextService(v, 100)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("varLogs", gocui.KeyArrowDown, gocui.ModAlt, func(g *gocui.Gui, v *gocui.View) error {
		return app.nextFileName(v, 100)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("docker", gocui.KeyArrowDown, gocui.ModAlt, func(g *gocui.Gui, v *gocui.View) error {
		return app.nextDockerContainer(v, 100)
	}); err != nil {
		return err
	}
	// Alt+PgDown (100)
	if err := app.gui.SetKeybinding("services", gocui.KeyPgdn, gocui.ModAlt, func(g *gocui.Gui, v *gocui.View) error {
		return app.nextService(v, 100)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("varLogs", gocui.KeyPgdn, gocui.ModAlt, func(g *gocui.Gui, v *gocui.View) error {
		return app.nextFileName(v, 100)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("docker", gocui.KeyPgdn, gocui.ModAlt, func(g *gocui.Gui, v *gocui.View) error {
		return app.nextDockerContainer(v, 100)
	}); err != nil {
		return err
	}
	// Custom down from config
	// Default: ctrl+j (100)
	customVeryQuickDown, altMode := getHotkey(config.Hotkeys.VeryQuickDown, "ctrl+j")
	if err := app.gui.SetKeybinding("services", customVeryQuickDown, altMode, func(g *gocui.Gui, v *gocui.View) error {
		return app.nextService(v, 100)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("varLogs", customVeryQuickDown, altMode, func(g *gocui.Gui, v *gocui.View) error {
		return app.nextFileName(v, 100)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("docker", customVeryQuickDown, altMode, func(g *gocui.Gui, v *gocui.View) error {
		return app.nextDockerContainer(v, 100)
	}); err != nil {
		return err
	}

	// Filtering mode (↑/↓)
	// Переключение между режимами фильтрации через Up/Down для выбранного окна
	if err := app.gui.SetKeybinding("filter", gocui.KeyArrowUp, gocui.ModNone, app.setFilterModeRight); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("filter", gocui.KeyArrowDown, gocui.ModNone, app.setFilterModeLeft); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("sinceFilter", gocui.KeyArrowUp, gocui.ModNone, app.setFilterModeRight); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("sinceFilter", gocui.KeyArrowDown, gocui.ModNone, app.setFilterModeLeft); err != nil {
		return err
	}
	// PgUp/PgDown
	if err := app.gui.SetKeybinding("filter", gocui.KeyPgup, gocui.ModNone, app.setFilterModeRight); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("filter", gocui.KeyPgdn, gocui.ModNone, app.setFilterModeLeft); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("sinceFilter", gocui.KeyPgup, gocui.ModNone, app.setFilterModeRight); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("sinceFilter", gocui.KeyPgdn, gocui.ModNone, app.setFilterModeLeft); err != nil {
		return err
	}
	// Custom up and down for switch filter mode from config (ctrl+k b ctrl+j)
	customUpFilterMode, altModeUp := getHotkey(config.Hotkeys.SwitchFilterMode, "ctrl+k")
	customDownFilterMode, altModeDown := getHotkey(config.Hotkeys.BackSwitchFilterMode, "ctrl+j")
	if err := app.gui.SetKeybinding("filter", customUpFilterMode, altModeUp, app.setFilterModeRight); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("filter", customDownFilterMode, altModeDown, app.setFilterModeLeft); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("sinceFilter", customUpFilterMode, altModeUp, app.setFilterModeRight); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("sinceFilter", customDownFilterMode, altModeDown, app.setFilterModeLeft); err != nil {
		return err
	}

	// ←/→
	// Custom left and right from config
	customLeft, altModeLeft := getHotkey(config.Hotkeys.Left, "h")
	customRight, altModeRight := getHotkey(config.Hotkeys.Right, "l")
	// Переключение выбора журналов для systemd/journald (отключено для Windows)
	if app.getOS != "windows" {
		// Left/Right
		if err := app.gui.SetKeybinding("services", gocui.KeyArrowLeft, gocui.ModNone, app.setUnitListLeft); err != nil {
			return err
		}
		if err := app.gui.SetKeybinding("services", gocui.KeyArrowRight, gocui.ModNone, app.setUnitListRight); err != nil {
			return err
		}
		// Custom by default: h/l (100)
		if err := app.gui.SetKeybinding("services", customLeft, altModeLeft, app.setUnitListLeft); err != nil {
			return err
		}
		if err := app.gui.SetKeybinding("services", customRight, altModeRight, app.setUnitListRight); err != nil {
			return err
		}
	}
	// Переключение выбора журналов для File System
	if app.keybindingsEnabled {
		// Установка привязок
		if err := app.gui.SetKeybinding("varLogs", gocui.KeyArrowLeft, gocui.ModNone, app.setLogFilesListLeft); err != nil {
			return err
		}
		if err := app.gui.SetKeybinding("varLogs", gocui.KeyArrowRight, gocui.ModNone, app.setLogFilesListRight); err != nil {
			return err
		}
		if err := app.gui.SetKeybinding("varLogs", customLeft, altModeLeft, app.setLogFilesListLeft); err != nil {
			return err
		}
		if err := app.gui.SetKeybinding("varLogs", customRight, altModeRight, app.setLogFilesListRight); err != nil {
			return err
		}
	} else {
		// Удаление привязок
		if err := app.gui.DeleteKeybinding("varLogs", gocui.KeyArrowLeft, gocui.ModNone); err != nil {
			return err
		}
		if err := app.gui.DeleteKeybinding("varLogs", gocui.KeyArrowRight, gocui.ModNone); err != nil {
			return err
		}
		if err := app.gui.DeleteKeybinding("varLogs", customLeft, altModeLeft); err != nil {
			return err
		}
		if err := app.gui.DeleteKeybinding("varLogs", customRight, altModeRight); err != nil {
			return err
		}
	}
	// Переключение выбора журналов для Containerization System
	if err := app.gui.SetKeybinding("docker", gocui.KeyArrowLeft, gocui.ModNone, app.setContainersListLeft); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("docker", gocui.KeyArrowRight, gocui.ModNone, app.setContainersListRight); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("docker", customLeft, altModeLeft, app.setContainersListLeft); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("docker", customRight, altModeRight, app.setContainersListRight); err != nil {
		return err
	}

	// Logs ↓↓↓
	// Пролистывание вывода журнала через 1/10/500 записей вниз
	// Down/PgDown/j (1)
	if err := app.gui.SetKeybinding("logs", gocui.KeyArrowDown, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		return app.scrollDownLogs(1)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("logs", gocui.KeyPgdn, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		return app.scrollDownLogs(1)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("logs", customDown, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		return app.scrollDownLogs(1)
	}); err != nil {
		return err
	}
	// Shift + Down/PgDown/j (10)
	if err := app.gui.SetKeybinding("logs", gocui.KeyArrowDown, gocui.ModShift, func(g *gocui.Gui, v *gocui.View) error {
		return app.scrollDownLogs(10)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("logs", gocui.KeyPgdn, gocui.ModShift, func(g *gocui.Gui, v *gocui.View) error {
		return app.scrollDownLogs(10)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("logs", customQuickDown, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		return app.scrollDownLogs(10)
	}); err != nil {
		return err
	}
	// Alt/Ctrl + Down/PgDown and Ctrl+j (500)
	if err := app.gui.SetKeybinding("logs", gocui.KeyArrowDown, gocui.ModAlt, func(g *gocui.Gui, v *gocui.View) error {
		return app.scrollDownLogs(500)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("logs", gocui.KeyPgdn, gocui.ModAlt, func(g *gocui.Gui, v *gocui.View) error {
		return app.scrollDownLogs(500)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("logs", gocui.KeyArrowDown, gocui.ModMouseCtrl, func(g *gocui.Gui, v *gocui.View) error {
		return app.scrollDownLogs(500)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("logs", gocui.KeyPgdn, gocui.ModMouseCtrl, func(g *gocui.Gui, v *gocui.View) error {
		return app.scrollDownLogs(500)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("logs", customVeryQuickDown, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		return app.scrollDownLogs(500)
	}); err != nil {
		return err
	}

	// Logs ↑↑↑
	// Пролистывание вывода журнала через 1/10/500 записей вверх
	// Up/PgUp/k (1)
	if err := app.gui.SetKeybinding("logs", gocui.KeyArrowUp, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		return app.scrollUpLogs(1)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("logs", gocui.KeyPgup, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		return app.scrollUpLogs(1)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("logs", customUp, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		return app.scrollUpLogs(1)
	}); err != nil {
		return err
	}
	// Shift + Up/PgUp/k (10)
	if err := app.gui.SetKeybinding("logs", gocui.KeyArrowUp, gocui.ModShift, func(g *gocui.Gui, v *gocui.View) error {
		return app.scrollUpLogs(10)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("logs", gocui.KeyPgup, gocui.ModShift, func(g *gocui.Gui, v *gocui.View) error {
		return app.scrollUpLogs(10)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("logs", customQuickUp, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		return app.scrollUpLogs(10)
	}); err != nil {
		return err
	}
	// Alt/Ctrl + Up/PgUp and Ctrl+k (500)
	if err := app.gui.SetKeybinding("logs", gocui.KeyArrowUp, gocui.ModAlt, func(g *gocui.Gui, v *gocui.View) error {
		return app.scrollUpLogs(500)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("logs", gocui.KeyPgup, gocui.ModAlt, func(g *gocui.Gui, v *gocui.View) error {
		return app.scrollUpLogs(500)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("logs", gocui.KeyArrowUp, gocui.ModMouseCtrl, func(g *gocui.Gui, v *gocui.View) error {
		return app.scrollUpLogs(500)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("logs", gocui.KeyPgup, gocui.ModMouseCtrl, func(g *gocui.Gui, v *gocui.View) error {
		return app.scrollUpLogs(500)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("logs", customVeryQuickUp, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		return app.scrollUpLogs(500)
	}); err != nil {
		return err
	}

	// Tab для переключения между окнами
	customTab, altMode := getHotkey(config.Hotkeys.SwitchWindow, "tab")
	if err := app.gui.SetKeybinding("", customTab, altMode, app.nextView); err != nil {
		return err
	}
	// Shift+Tab (Back Tab) для переключения между окнами в обратном порядке
	customBackTab, altMode := getHotkey(config.Hotkeys.BackSwitchWindows, "shift+tab")
	if err := app.gui.SetKeybinding("", customBackTab, altMode, app.backView); err != nil {
		return err
	}

	// Enter для выбора службы и загрузки журналов
	customEnter, altModeEnter := getHotkey(config.Hotkeys.LoadJournal, "enter")
	if err := app.gui.SetKeybinding("services", customEnter, altModeEnter, app.selectService); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("varLogs", customEnter, altModeEnter, app.selectFile); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("docker", customEnter, altModeEnter, app.selectDocker); err != nil {
		return err
	}
	// Enter для загрузки журнала из фильтра по дате
	if err := app.gui.SetKeybinding("sinceFilter", customEnter, altModeEnter, func(g *gocui.Gui, v *gocui.View) error {
		app.updateLogOutput(true)
		return nil
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("untilFilter", customEnter, altModeEnter, func(g *gocui.Gui, v *gocui.View) error {
		app.updateLogOutput(true)
		return nil
	}); err != nil {
		return err
	}

	// filter (/) slash
	// Переключение фокуса на окно фильтрации списков журналов
	customSlash, altMode := getHotkey(config.Hotkeys.GoToFilter, "/")
	if err := app.gui.SetKeybinding("services", customSlash, altMode, func(g *gocui.Gui, v *gocui.View) error {
		app.lastCurrentView = "services"
		app.backCurrentView = true
		return app.setSelectView(app.gui, "filterList")
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("varLogs", customSlash, altMode, func(g *gocui.Gui, v *gocui.View) error {
		app.lastCurrentView = "varLogs"
		app.backCurrentView = true
		return app.setSelectView(app.gui, "filterList")
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("docker", customSlash, altMode, func(g *gocui.Gui, v *gocui.View) error {
		app.lastCurrentView = "docker"
		app.backCurrentView = true
		return app.setSelectView(app.gui, "filterList")
	}); err != nil {
		return err
	}
	// В окне вывода журнала переключаемся на фильтр журнала
	if err := app.gui.SetKeybinding("logs", customSlash, altMode, func(g *gocui.Gui, v *gocui.View) error {
		app.lastCurrentView = "logs"
		app.backCurrentView = true
		return app.setSelectView(app.gui, "filter")
	}); err != nil {
		return err
	}
	// Enter for return to the window
	// Возврат к последнему окну до использования слэша с использование Enter из окна фильтрации
	if err := app.gui.SetKeybinding("filterList", customEnter, altModeEnter, func(g *gocui.Gui, v *gocui.View) error {
		if app.backCurrentView {
			app.backCurrentView = false
			return app.setSelectView(app.gui, app.lastCurrentView)
		} else {
			return nil
		}
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("filter", customEnter, altModeEnter, func(g *gocui.Gui, v *gocui.View) error {
		if app.backCurrentView {
			app.backCurrentView = false
			return app.setSelectView(app.gui, app.lastCurrentView)
		} else {
			return nil
		}
	}); err != nil {
		return err
	}

	// End/Ctrl+E
	// Перемещение к концу журнала
	if err := app.gui.SetKeybinding("", gocui.KeyEnd, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		// Сбрасываем автоскролл
		if !app.disableAutoScroll {
			app.autoScroll = true
		} else {
			app.autoScroll = false
		}
		app.updateStatus()
		app.updateLogsView(true)
		return nil
	}); err != nil {
		return err
	}
	customEnd, altMode := getHotkey(config.Hotkeys.GoToEnd, "ctrl+e")
	if err := app.gui.SetKeybinding("", customEnd, altMode, func(g *gocui.Gui, v *gocui.View) error {
		if !app.disableAutoScroll {
			app.autoScroll = true
		} else {
			app.autoScroll = false
		}
		app.updateStatus()
		app.updateLogsView(true)
		return nil
	}); err != nil {
		return err
	}

	// Home/Ctrl+A
	// Перемещение к началу журнала
	if err := app.gui.SetKeybinding("", gocui.KeyHome, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		app.pageUpLogs()
		return nil
	}); err != nil {
		return err
	}
	customHome, altMode := getHotkey(config.Hotkeys.GoToTop, "ctrl+a")
	if err := app.gui.SetKeybinding("", customHome, altMode, func(g *gocui.Gui, v *gocui.View) error {
		app.pageUpLogs()
		return nil
	}); err != nil {
		return err
	}

	// tail mode "[" and "]"
	// Переключение для количества строк вывода
	customTailMore, altMode := getHotkey(config.Hotkeys.TailModeMore, "]")
	if err := app.gui.SetKeybinding("", customTailMore, altMode, app.setCountLogViewUp); err != nil {
		return err
	}
	customTailLess, altMode := getHotkey(config.Hotkeys.TailModeLess, "[")
	if err := app.gui.SetKeybinding("", customTailLess, altMode, app.setCountLogViewDown); err != nil {
		return err
	}

	// update interval "{" and "}"
	// Увеличение фоновго интервала обновления журнала
	customUpdateIntervalMore, altMode := getHotkey(config.Hotkeys.UpdateIntervalMore, "}")
	if err := app.gui.SetKeybinding("", customUpdateIntervalMore, altMode, func(g *gocui.Gui, v *gocui.View) error {
		if app.logUpdateSeconds >= 2 && app.logUpdateSeconds <= 9 {
			app.logUpdateSeconds++
			app.updateStatus()
		}
		return nil
	}); err != nil {
		return err
	}
	customUpdateIntervalLess, altMode := getHotkey(config.Hotkeys.UpdateIntervalLess, "{")
	if err := app.gui.SetKeybinding("", customUpdateIntervalLess, altMode, func(g *gocui.Gui, v *gocui.View) error {
		if app.logUpdateSeconds >= 3 && app.logUpdateSeconds <= 10 {
			app.logUpdateSeconds--
			// Изменяем интервал в горутине
			app.secondsChan <- app.logUpdateSeconds
			app.updateStatus()
		}
		return nil
	}); err != nil {
		return err
	}

	// auto update (Ctrl+U)
	// Включение или отключение автоматического скроллинга
	customAutoUpdate, altMode := getHotkey(config.Hotkeys.AutoUpdateJournal, "ctrl+u")
	if err := app.gui.SetKeybinding("", customAutoUpdate, altMode, func(g *gocui.Gui, v *gocui.View) error {
		if app.disableAutoScroll {
			app.disableAutoScroll = false
			app.autoScroll = false
		} else {
			app.disableAutoScroll = true
			app.autoScroll = false
		}
		app.updateStatus()
		app.updateLogOutput(false)
		return nil
	}); err != nil {
		return err
	}

	// update journal (Ctrl+R)
	// Ручное обновление текущего вывода журнала
	// Актуально в режиме выключенного автоматического обновления
	customUpdateJournal, altMode := getHotkey(config.Hotkeys.UpdateJournal, "ctrl+r")
	if err := app.gui.SetKeybinding("", customUpdateJournal, altMode, func(g *gocui.Gui, v *gocui.View) error {
		app.updateLogOutput(false)
		return nil
	}); err != nil {
		return err
	}

	// update lists (Ctrl+Q)
	// Обновить все текущие списки журналов вручную
	customUpdateLists, altMode := getHotkey(config.Hotkeys.UpdateLists, "ctrl+q")
	if err := app.gui.SetKeybinding("", customUpdateLists, altMode, func(g *gocui.Gui, v *gocui.View) error {
		if app.getOS != "windows" {
			app.loadServices(app.selectUnits)
			app.loadFiles(app.selectPath)
		} else {
			app.loadWinFiles(app.selectPath)
		}
		app.loadDockerContainer(app.selectContainerizationSystem)
		return nil
	}); err != nil {
		return err
	}

	// switch color mode - default (custom built-in), tailspin/tspin, bat/batcat or disable (Ctrl+W)
	customColor, altMode := getHotkey(config.Hotkeys.SwitchColorMode, "ctrl+w")
	if err := app.gui.SetKeybinding("", customColor, altMode, func(g *gocui.Gui, v *gocui.View) error {
		switch app.colorMode {
		case "disable":
			app.colorMode = "default"
		case "default":
			app.colorMode = "tailspin"
		case "tailspin":
			app.colorMode = "bat"
		case "bat":
			app.colorMode = "disable"
		}
		if len(app.currentLogLines) != 0 {
			app.updateLogsView(true)
			app.applyFilter(false)
			app.updateLogOutput(false)
		}
		app.updateStatus()
		return nil
	}); err != nil {
		return err
	}

	// switch priority for journald (Ctrl+P)
	customPriority, altMode := getHotkey(config.Hotkeys.SwitchPriority, "ctrl+p")
	if err := app.gui.SetKeybinding("", customPriority, altMode, func(g *gocui.Gui, v *gocui.View) error {
		switch app.journalPriority {
		case "debug":
			app.journalPriority = "info"
		case "info":
			app.journalPriority = "notice"
		case "notice":
			app.journalPriority = "warning"
		case "warning":
			app.journalPriority = "err"
		case "err":
			app.journalPriority = "crit"
		case "crit":
			app.journalPriority = "alert"
		case "alert":
			app.journalPriority = "emerg"
		case "emerg":
			app.journalPriority = "debug"
		}
		if len(app.currentLogLines) != 0 {
			app.updateLogsView(true)
			app.applyFilter(false)
			app.updateLogOutput(false)
		}
		app.updateStatus()
		return nil
	}); err != nil {
		return err
	}

	// docker log load mode from stream or file system (Ctrl+D)
	// Переключение режима чтения журналов Docker из потоков или файловой системы
	customDockerMode, altMode := getHotkey(config.Hotkeys.SwitchDockerMode, "ctrl+d")
	if err := app.gui.SetKeybinding("", customDockerMode, altMode, func(g *gocui.Gui, v *gocui.View) error {
		if app.dockerStreamLogs {
			app.dockerStreamLogs = false
			app.dockerStreamLogsStatus = "json-file"
		} else {
			app.dockerStreamLogs = true
			app.dockerStreamLogsStatus = app.dockerStreamMode
		}
		app.updateLogOutput(false)
		return nil
	}); err != nil {
		return err
	}

	// docker stream (Ctrl+S)
	// Переключение режима вывода потоков журналов (фильтрация по потоку)
	customStreamMode, altMode := getHotkey(config.Hotkeys.SwitchStreamMode, "ctrl+s")
	if err := app.gui.SetKeybinding("", customStreamMode, altMode, func(g *gocui.Gui, v *gocui.View) error {
		switch app.dockerStreamMode {
		case "stream":
			app.dockerStreamMode = "stdout"
		case "stdout":
			app.dockerStreamMode = "stderr"
		case "stderr":
			app.dockerStreamMode = "stream"
		}
		app.dockerStreamLogsStatus = app.dockerStreamMode
		app.updateLogOutput(false)
		return nil
	}); err != nil {
		return err
	}

	// docker timestamp (Ctrl+T)
	// Переключение режима вывода timestamp и названия потока
	customTimestamp, altMode := getHotkey(config.Hotkeys.TimestampShow, "ctrl+t")
	if err := app.gui.SetKeybinding("", customTimestamp, altMode, func(g *gocui.Gui, v *gocui.View) error {
		if app.timestampDocker {
			app.timestampDocker = false
		} else {
			app.timestampDocker = true
		}
		app.updateLogOutput(false)
		return nil
	}); err != nil {
		return err
	}

	// Exit (ctrl+c)
	// Очистка поля ввода для фильтрации списков или выход
	customExit, altMode := getHotkey(config.Hotkeys.Exit, "ctrl+c")
	if err := app.gui.SetKeybinding("filterList", customExit, altMode, func(g *gocui.Gui, v *gocui.View) error {
		if app.filterListText == "" {
			return quit(g, v)
		} else {
			// Очищаем фильтр
			app.clearFilterListEditor(g)
			// Возвращяемся к последнему окну из фильтра
			if app.backCurrentView {
				app.backCurrentView = false
				return app.setSelectView(app.gui, app.lastCurrentView)
			} else {
				return nil
			}
		}
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("services", customExit, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		if app.filterListText == "" {
			return quit(g, v)
		} else {
			app.clearFilterListEditor(g)
			return nil
		}
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("varLogs", customExit, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		if app.filterListText == "" {
			return quit(g, v)
		} else {
			app.clearFilterListEditor(g)
			return nil
		}
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("docker", customExit, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		if app.filterListText == "" {
			return quit(g, v)
		} else {
			app.clearFilterListEditor(g)
			return nil
		}
	}); err != nil {
		return err
	}
	// Очистка поля ввода для фильтрации логов или выход
	if err := app.gui.SetKeybinding("filter", customExit, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		if app.filterText == "" {
			return quit(g, v)
		} else {
			app.clearFilterEditor(g)
			if app.backCurrentView {
				app.backCurrentView = false
				return app.setSelectView(app.gui, app.lastCurrentView)
			} else {
				return nil
			}
		}
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("logs", customExit, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		if app.filterText == "" {
			return quit(g, v)
		} else {
			app.clearFilterEditor(g)
			return nil
		}
	}); err != nil {
		return err
	}
	// Очистка поля ввода для фильтрации по дате
	if err := app.gui.SetKeybinding("sinceFilter", customExit, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		if v.Buffer() == "⎯" {
			return quit(g, v)
		} else {
			app.sinceDateFilterMode = false
			v.FrameColor = app.errorColor
			v.Clear()
			fmt.Fprint(v, "⎯")
			app.updateFilterStatus()
			return nil
		}
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("untilFilter", customExit, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		if v.Buffer() == "⎯" {
			return quit(g, v)
		} else {
			app.untilDateFilterMode = false
			v.FrameColor = app.errorColor
			v.Clear()
			fmt.Fprint(v, "⎯")
			app.updateFilterStatus()
			return nil
		}
	}); err != nil {
		return err
	}

	// Mouse control
	// Привязка клика мыши для выбора элемента в списке журналов и изменения фокуса на окно
	if err := app.gui.SetKeybinding("filterList", gocui.MouseLeft, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		return app.setSelectView(g, "filterList")
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("services", gocui.MouseLeft, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		err := app.selectService(g, v)
		if err != nil {
			return err
		}
		return app.setSelectView(g, "services")
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("varLogs", gocui.MouseLeft, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		err := app.selectFile(g, v)
		if err != nil {
			return err
		}
		return app.setSelectView(g, "varLogs")
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("docker", gocui.MouseLeft, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		err := app.selectDocker(g, v)
		if err != nil {
			return err
		}
		return app.setSelectView(g, "docker")
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("filter", gocui.MouseLeft, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		return app.setSelectView(g, "filter")
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("sinceFilter", gocui.MouseLeft, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		return app.setSelectView(g, "sinceFilter")
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("untilFilter", gocui.MouseLeft, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		return app.setSelectView(g, "untilFilter")
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("logs", gocui.MouseLeft, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		return app.setSelectView(g, "logs")
	}); err != nil {
		return err
	}

	// Скроллинг колесом мыши вверх/вниз на 1 элемент
	if err := app.gui.SetKeybinding("services", gocui.MouseWheelUp, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		return app.prevService(v, 1)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("services", gocui.MouseWheelDown, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		return app.nextService(v, 1)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("varLogs", gocui.MouseWheelUp, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		return app.prevFileName(v, 1)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("varLogs", gocui.MouseWheelDown, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		return app.nextFileName(v, 1)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("docker", gocui.MouseWheelUp, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		return app.prevDockerContainer(v, 1)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("docker", gocui.MouseWheelDown, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		return app.nextDockerContainer(v, 1)
	}); err != nil {
		return err
	}
	// Скроллинг по журналу через 1 или 100 (alt/ctrl) строк
	if err := app.gui.SetKeybinding("logs", gocui.MouseWheelUp, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		return app.scrollUpLogs(1)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("logs", gocui.MouseWheelUp, gocui.ModAlt, func(g *gocui.Gui, v *gocui.View) error {
		return app.scrollUpLogs(100)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("logs", gocui.MouseWheelUp, gocui.ModMouseCtrl, func(g *gocui.Gui, v *gocui.View) error {
		return app.scrollUpLogs(100)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("logs", gocui.MouseWheelDown, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		return app.scrollDownLogs(1)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("logs", gocui.MouseWheelDown, gocui.ModAlt, func(g *gocui.Gui, v *gocui.View) error {
		return app.scrollDownLogs(100)
	}); err != nil {
		return err
	}
	if err := app.gui.SetKeybinding("logs", gocui.MouseWheelDown, gocui.ModMouseCtrl, func(g *gocui.Gui, v *gocui.View) error {
		return app.scrollDownLogs(100)
	}); err != nil {
		return err
	}

	return nil
}

// Интерфейс справки (F1)
func (app *App) showInterfaceHelp(g *gocui.Gui) {
	// Получаем размеры терминала
	maxX, maxY := g.Size()
	// Размеры окна help
	width, height := 108, 43
	// Вычисляем координаты для центрального расположения
	x0 := (maxX - width) / 2
	y0 := (maxY - height) / 2
	x1 := x0 + width
	y1 := y0 + height
	helpView, err := g.SetView("help", x0, y0, x1, y1, 0)
	if err != nil && !errors.Is(err, gocui.ErrUnknownView) {
		return
	}
	helpView.Title = " Help "
	helpView.Autoscroll = true
	helpView.Wrap = true
	helpView.FrameColor = app.selectedFrameColor
	helpView.TitleColor = app.selectedTitleColor
	helpView.Clear()
	fmt.Fprintln(helpView, "\n                   \033[32m_                              \033[36m_                                    _ ")
	fmt.Fprintln(helpView, "                  \033[32m| |                            \033[36m| |                                  | |")
	fmt.Fprintln(helpView, "                  \033[32m| |      __ _  ____ _   _      \033[36m| |  ___   _   _  _ __  _ __    __ _ | |")
	fmt.Fprintln(helpView, "                  \033[32m| |     / _` ||_  /| | | | \033[36m_   | | / _ \\ | | | || '__|| '_ \\  / _` || |")
	fmt.Fprintln(helpView, "                  \033[32m| |____| (_| | / / | |_| |\033[36m| |__| || (_) || |_| || |   | | | || (_| || |")
	fmt.Fprintln(helpView, "                  \033[32m|______|\\__,_|/___| \\__, | \033[36m\\____/  \\___/  \\__,_||_|   |_| |_| \\__,_||_|")
	fmt.Fprintln(helpView, "                  \033[32m					 __/ |                                             ")
	fmt.Fprintln(helpView, "                  \033[32m                    |___/\033[0m")
	fmt.Fprintln(helpView, "\n    Version: "+app.wordColor(appVersion))
	fmt.Fprintln(helpView, "\n    Hotkeys description (default values):")
	fmt.Fprintln(helpView, "\n      \033[32mF2\033[0m - interface for ssh manager and contexts switching.")
	fmt.Fprintln(helpView, "      \033[32mTab\033[0m - switch to next window.")
	fmt.Fprintln(helpView, "      \033[32mShift\033[0m+\033[32mTab\033[0m - return to previous window.")
	fmt.Fprintln(helpView, "      \033[32mUp\033[0m/\033[32mPgUp\033[0m/\033[32mk\033[0m and \033[32mDown\033[0m/\033[32mPgDown\033[0m/\033[32mj\033[0m - move up and down through all journal lists and log output,")
	fmt.Fprintln(helpView, "      as well as changing the filtering mode in the filter window.")
	fmt.Fprintln(helpView, "      \033[32mShift\033[0m/\033[32mAlt\033[0m+\033[32mUp\033[0m/\033[32mDown\033[0m - quickly move up and down through all journal lists and log output")
	fmt.Fprintln(helpView, "      every 10 or 100 lines (500 for log output).")
	fmt.Fprintln(helpView, "      \033[32mShift\033[0m/\033[32mCtrl\033[0m+\033[32mk\033[0m/\033[32mj\033[0m - quickly move up and down (like Vim and alternative for macOS from config).")
	fmt.Fprintln(helpView, "      \033[32mLeft\033[0m/\033[32mh\033[0m and \033[32mRight\033[0m/\033[32ml\033[0m - switch between journal lists in the selected window and change the date")
	fmt.Fprintln(helpView, "      in the filter window.")
	fmt.Fprintln(helpView, "      \033[32mDel\033[0m/\033[32mBackspace\033[0m - disable filtering by date.")
	fmt.Fprintln(helpView, "      \033[32mEnter\033[0m - load a log from the list window or return to the previous window from the filter window.")
	fmt.Fprintln(helpView, "      \033[32m/\033[0m - go to the filter window from the current list window or logs window.")
	fmt.Fprintln(helpView, "      \033[32mEnd\033[0m/\033[32mCtrl\033[0m+\033[32mE\033[0m - go to the end of the log.")
	fmt.Fprintln(helpView, "      \033[32mHome\033[0m/\033[32mCtrl\033[0m+\033[32mA\033[0m - go to the top of the log.")
	fmt.Fprintln(helpView, "      \033[32m[\033[0m/\033[32m]\033[0m - change the number of log lines to output (range: 200-200000, default: 10K).")
	fmt.Fprintln(helpView, "      \033[32m{\033[0m/\033[32m}\033[0m - change the update interval of the log output (range: 2-10, default: 5).")
	fmt.Fprintln(helpView, "      \033[32mCtrl\033[0m+\033[32mU\033[0m - disable streaming of new events (log is loaded once without update).")
	fmt.Fprintln(helpView, "      \033[32mCtrl\033[0m+\033[32mR\033[0m - update the current log output manually (relevant in disable streaming mode).")
	fmt.Fprintln(helpView, "      \033[32mCtrl\033[0m+\033[32mQ\033[0m - update all log lists.")
	fmt.Fprintln(helpView, "      \033[32mCtrl\033[0m+\033[32mW\033[0m - switch color mode between default, tailspin, bat or disable.")
	fmt.Fprintln(helpView, "      \033[32mCtrl\033[0m+\033[32mD\033[0m - change read mode for docker logs (stream only or json from file system).")
	fmt.Fprintln(helpView, "      \033[32mCtrl\033[0m+\033[32mS\033[0m - change stream display mode for docker logs (all, stdout or stderr only).")
	fmt.Fprintln(helpView, "      \033[32mCtrl\033[0m+\033[32mT\033[0m - enable or disable built-in timestamp for Docker and Kubernetes logs.")
	fmt.Fprintln(helpView, "      \033[32mCtrl\033[0m+\033[32mC\033[0m - clear input text in the filter window or exit.")
	fmt.Fprintln(helpView, "\n    Source code: "+app.wordColor("https://github.com/Lifailon/lazyjournal"))
}

func (app *App) closeHelp(g *gocui.Gui) {
	if err := g.DeleteView("help"); err != nil {
		return
	}
}

// Интерфейс ошибки
func (app *App) showInterfaceInfo(g *gocui.Gui, errInfo bool, text string) {
	maxX, maxY := g.Size()
	width, height := 70, 4
	x0 := (maxX - width) - 5
	y0 := (maxY - height) - 3
	x1 := x0 + width
	y1 := y0 + height
	helpView, err := g.SetView("info", x0, y0, x1, y1, 0)
	if err != nil && !errors.Is(err, gocui.ErrUnknownView) {
		return
	}
	if errInfo {
		helpView.Title = " Error "
		helpView.FrameColor = app.errorColor
		helpView.TitleColor = app.errorColor
	} else {
		helpView.Title = " Info "
		helpView.FrameColor = app.selectedFrameColor
		helpView.TitleColor = app.selectedTitleColor
	}
	helpView.Wrap = true
	helpView.Clear()
	fmt.Fprintln(helpView, text)
}

// Закрытие интерфейс ошибки
func (app *App) closeInfo(g *gocui.Gui) {
	if err := g.DeleteView("info"); err != nil {
		return
	}
}

// Интерфейс менеджера (F2)
func (app *App) showInterfaceManager(g *gocui.Gui) {
	maxX, maxY := g.Size()
	width, height := 108, 42
	x0 := (maxX - width) / 2
	y0 := (maxY - height) / 2
	x1 := x0 + width
	y1 := y0 + height
	managerView, err := g.SetView("manager", x0, y0, x1, y1, 0)
	if err != nil && !errors.Is(err, gocui.ErrUnknownView) {
		return
	}
	managerView.FrameColor = app.selectedFrameColor
	managerView.TitleColor = app.selectedTitleColor
	managerView.Clear()

	midX := x0 + (width / 2)
	midY := y0 + (height / 2)

	if v, err := g.SetView("sshManager", x0+1, y0+1, midX-1, midY-1, 0); err != nil {
		if !errors.Is(err, gocui.ErrUnknownView) {
			return
		}
		v.Title = " SSH Hosts "
		v.Highlight = true
		v.Wrap = false
		v.Autoscroll = true
		v.FrameColor = app.frameColor
		v.TitleColor = app.titleColor
		v.SelFgColor = app.selectedForegroundColor
		v.SelBgColor = app.selectedBackgroundColor
		v.Clear()
		sshHosts := []string{"localhost"}
		sshHosts = append(sshHosts, config.Ssh.Hosts...)
		for _, sshHost := range sshHosts {
			fmt.Fprintln(v, sshHost)
		}
	}

	if v, err := g.SetView("dockerContextManager", midX, y0+1, x1-1, midY-1, 0); err != nil {
		if !errors.Is(err, gocui.ErrUnknownView) {
			return
		}
		v.Title = " Docker Contexts "
		v.Highlight = true
		v.Wrap = false
		v.Autoscroll = true
		v.FrameColor = app.frameColor
		v.TitleColor = app.titleColor
		v.SelFgColor = app.selectedForegroundColor
		v.SelBgColor = app.selectedBackgroundColor
		v.Clear()
		contexts := app.getDockerContext()
		for _, ctx := range contexts {
			fmt.Fprintln(v, ctx)
		}
	}

	if v, err := g.SetView("kubernetesContextManager", x0+1, midY, midX-1, y1-1, 0); err != nil {
		if !errors.Is(err, gocui.ErrUnknownView) {
			return
		}
		v.Title = " Kubernetes Contexts "
		v.Highlight = true
		v.Wrap = false
		v.Autoscroll = true
		v.FrameColor = app.frameColor
		v.TitleColor = app.titleColor
		v.SelFgColor = app.selectedForegroundColor
		v.SelBgColor = app.selectedBackgroundColor
		v.Clear()
		contexts := app.getKubernetesContext()
		for _, ctx := range contexts {
			fmt.Fprintln(v, ctx)
		}
	}

	if v, err := g.SetView("kubernetesNamespaceManager", midX, midY, x1-1, y1-1, 0); err != nil {
		if !errors.Is(err, gocui.ErrUnknownView) {
			return
		}
		v.Title = " Kubernetes Namespaces "
		v.Highlight = true
		v.Wrap = false
		v.Autoscroll = true
		v.FrameColor = app.frameColor
		v.TitleColor = app.titleColor
		v.SelFgColor = app.selectedForegroundColor
		v.SelBgColor = app.selectedBackgroundColor
		namespaces := app.getKubernetesNamespace()
		for _, ns := range namespaces {
			fmt.Fprintln(v, ns)
		}
	}
}

// Функция для удаления всех окон менеджера ssh/контектов
func (app *App) closeManager(g *gocui.Gui) {
	if err := g.DeleteView("manager"); err != nil {
		return
	}
	if err := g.DeleteView("sshManager"); err != nil {
		return
	}
	if err := g.DeleteView("dockerContextManager"); err != nil {
		return
	}
	if err := g.DeleteView("kubernetesContextManager"); err != nil {
		return
	}
	if err := g.DeleteView("kubernetesNamespaceManager"); err != nil {
		return
	}
}

// Функция для перемещения курсора вверх в менеджере ssh/контектов
func (app *App) moveCursorUp(g *gocui.Gui, v *gocui.View) error {
	if v == nil {
		return nil
	}
	ox, oy := v.Origin()
	cx, cy := v.Cursor()
	if err := v.SetCursor(cx, cy-1); err != nil && oy > 0 {
		if err := v.SetOrigin(ox, oy-1); err != nil {
			return nil
		}
	}
	return nil
}

// Функция для перемещения курсора вниз в менеджере ssh/контектов
func (app *App) moveCursorDown(g *gocui.Gui, v *gocui.View) error {
	if v == nil {
		return nil
	}
	cx, cy := v.Cursor()
	line, _ := v.Line(cy + 1)
	if line != "" {
		if err := v.SetCursor(cx, cy+1); err != nil {
			ox, oy := v.Origin()
			if err := v.SetOrigin(ox, oy+1); err != nil {
				return nil
			}
		}
	}
	return nil
}

// Функция для получения выбранной строки
func (app *App) getSelectedLine(g *gocui.Gui, v *gocui.View) error {
	_, cy := v.Cursor()
	line, err := v.Line(cy)
	if err != nil {
		return nil
	}
	// Обновляем значения
	switch g.CurrentView().Name() {
	case "sshManager":
		lastOS := app.getOS
		if line == "localhost" {
			app.sshMode = false
			app.sshStatus = "false"
			// Определяем локальную ОС
			app.getOS = runtime.GOOS
		} else {
			// Включаем ssh режим и определяем параметры
			app.sshMode = true
			app.sshOptions = strings.Split(line, " ")
			app.sshStatus = app.sshOptions[0]
			// Определяем удаленную ОС
			getOS, err := remoteGetOS(app.sshOptions)
			if err != nil {
				app.sshMode = false
				app.sshStatus = "false"
				app.getOS = runtime.GOOS
				if !app.testMode {
					go func() {
						errorText := err.Error()
						app.showInterfaceInfo(g, true, errorText)
						time.Sleep(5 * time.Second)
						app.closeInfo(g)
					}()
				}
			} else {
				app.getOS = getOS
			}
		}
		// Требуется перерисовка окон при смене ОС
		if lastOS == "windows" && app.getOS != "windows" || lastOS != "windows" && app.getOS == "windows" {
			// Удаляем старые окна
			if err := g.DeleteView("services"); err != nil {
				return nil
			}
			if err := g.DeleteView("varLogs"); err != nil {
				return nil
			}
			// Создаем окна заново
			if err := app.layout(g); err != nil {
				log.Panicln(err)
			}
			// Обновляем названия окон
			if app.getOS == "windows" {
				app.selectPath = "ProgramFiles"
			} else {
				app.selectUnits = "services"
				app.selectPath = "varlog"
			}
		}
		// Обновляем списки сервисов и файлов
		if app.getOS != "windows" {
			app.loadServices(app.selectUnits)
			app.loadFiles(app.selectPath)
		} else {
			v, err := g.View("services")
			if err != nil {
				log.Panicln(err)
			}
			v.Title = " < Windows Event Logs (0) > "
			v.Clear()
			app.loadWinEvents()
			app.loadWinFiles(app.selectPath)
		}
		// Обновляем все списки в менеджере (что бы загрузить удаленный список контекстов Docker и Kubernetes)
		app.closeManager(g)
		app.showInterfaceManager(g)
		// Сбрасываем контексты
		app.dockerContext = "default"
		app.kubernetesContext = "default"
		app.kubernetesNamespace = "--all-namespaces"
	case "dockerContextManager":
		app.dockerContext = line
	case "kubernetesContextManager":
		app.kubernetesContext = line
	case "kubernetesNamespaceManager":
		app.kubernetesNamespaceStatus = line
		if line == "all" {
			app.kubernetesNamespace = "--all-namespaces"
		} else {
			app.kubernetesNamespace = "--namespace=" + line
		}
	}
	// Обновляем список контейнеров
	app.loadDockerContainer(app.selectContainerizationSystem)
	// Обновляем статус
	app.updateStatus()
	return nil
}

// Функция для переключения окон в менеджере ssh/контектов (3-й параметр содержит массив окон и определяет направление - tab/shift+tab)
func (app *App) nextViewManager(g *gocui.Gui, v *gocui.View, views []string) error {
	// Сбрасываем цвета у всех окон
	for _, name := range views {
		if view, err := g.View(name); err == nil {
			view.FrameColor = app.frameColor
			view.TitleColor = app.titleColor
		}
	}
	// Ищем индекс текущего окна
	currentName := v.Name()
	nextIndex := 0
	for i, name := range views {
		if name == currentName {
			nextIndex = (i + 1) % len(views)
			break
		}
	}
	// Получаем имя следующего окна
	nextName := views[nextIndex]
	// Устанавливаем фокус на новое окно
	nextView, err := g.SetCurrentView(nextName)
	if err != nil {
		return err
	}
	nextView.FrameColor = app.selectedFrameColor
	nextView.TitleColor = app.selectedTitleColor
	return nil
}

// Функция для получения списка контекстов Docker
func (app *App) getDockerContext() []string {
	var cmd *exec.Cmd
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	if app.sshMode {
		cmd = exec.CommandContext(
			ctx,
			"ssh", append(app.sshOptions,
				"docker", "context", "ls", "-q",
			)...)
	} else {
		cmd = exec.Command(
			"docker", "context", "ls", "-q",
		)
	}
	if app.logging {
		slog.Info(cmd.String(), "action", "Loading the docker context list")
	}
	context, err := cmd.Output()
	if err == nil {
		strCtx := string(context)
		return strings.Split(strCtx, "\n")
	}
	return nil
}

// Функция для получения списка контекстов Kubernetes
func (app *App) getKubernetesContext() []string {
	var cmd *exec.Cmd
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	if app.sshMode {
		cmd = exec.CommandContext(
			ctx,
			"ssh", append(app.sshOptions,
				"kubectl", "config", "get-contexts", "-o", "name",
			)...)
	} else {
		cmd = exec.Command(
			"kubectl", "config", "get-contexts", "-o", "name",
		)
	}
	if app.logging {
		slog.Info(cmd.String(), "action", "Loading the kubernetes context list")
	}
	context, err := cmd.Output()
	if err == nil {
		strCtx := string(context)
		return strings.Split(strCtx, "\n")
	}
	return nil
}

// Функция для получения списка контекстов Kubernetes
func (app *App) getKubernetesNamespace() []string {
	var cmd *exec.Cmd
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	if app.sshMode {
		cmd = exec.CommandContext(
			ctx,
			"ssh", append(app.sshOptions,
				"kubectl", "get", "namespace", "-o", "name",
			)...)
	} else {
		cmd = exec.Command(
			"kubectl", "get", "namespace", "-o", "name",
		)
	}
	if app.logging {
		slog.Info(cmd.String(), "action", "Loading the kubernetes namespace list")
	}
	namespace, err := cmd.Output()
	if err == nil {
		strNs := string(namespace)
		strNs = strings.ReplaceAll(strNs, "namespace/", "")
		arrNsAll := strings.Split(strNs, "\n")
		arrNs := []string{"all"}
		arrNs = append(arrNs, arrNsAll...)
		return arrNs
	}
	return nil
}

// Функции для переключения количества строк для вывода логов

func (app *App) setCountLogViewUp(g *gocui.Gui, v *gocui.View) error {
	switch app.logViewCount {
	case "200":
		app.logViewCount = "500"
	case "500":
		app.logViewCount = "1000"
	case "1000":
		app.logViewCount = "5000"
	case "5000":
		app.logViewCount = "10000"
	case "10000":
		app.logViewCount = "20000"
	case "20000":
		app.logViewCount = "30000"
	case "30000":
		app.logViewCount = "40000"
	case "40000":
		app.logViewCount = "50000"
	case "50000":
		app.logViewCount = "100000"
	case "100000":
		app.logViewCount = "150000"
	case "150000":
		app.logViewCount = "200000"
	case "200000":
		app.logViewCount = "200000"
	}
	// Загружаем журнал заново
	app.updateLogOutput(true)
	// Обновляем статус
	app.updateStatus()
	return nil
}

func (app *App) setCountLogViewDown(g *gocui.Gui, v *gocui.View) error {
	switch app.logViewCount {
	case "200000":
		app.logViewCount = "150000"
	case "150000":
		app.logViewCount = "100000"
	case "100000":
		app.logViewCount = "50000"
	case "50000":
		app.logViewCount = "40000"
	case "40000":
		app.logViewCount = "30000"
	case "30000":
		app.logViewCount = "20000"
	case "20000":
		app.logViewCount = "10000"
	case "10000":
		app.logViewCount = "5000"
	case "5000":
		app.logViewCount = "1000"
	case "1000":
		app.logViewCount = "500"
	case "500":
		app.logViewCount = "200"
	case "200":
		app.logViewCount = "200"
	}
	app.updateLogOutput(true)
	app.updateStatus()
	return nil
}

// Функция для переключения режима фильтрации (вверх)
func (app *App) setFilterModeRight(g *gocui.Gui, v *gocui.View) error {
	selectedFilter, err := g.View("filter")
	if err != nil {
		log.Panicln(err)
	}
	switch selectedFilter.Title {
	case "Filter (Default)":
		selectedFilter.Title = "Filter (Fuzzy)"
		app.selectFilterMode = "fuzzy"
	case "Filter (Fuzzy)":
		selectedFilter.Title = "Filter (Regex)"
		app.selectFilterMode = "regex"
	case "Filter (Regex)":
		// Фиксируем название
		selectedFilter.Title = "Filter (Timestamp)"
		app.selectFilterMode = "timestamp"
		// Создаем два новых окна
		maxX, _ := g.Size()
		leftPanelWidth := maxX / 4
		filterWidth := (maxX - leftPanelWidth - 1) / 2
		if v, err := g.SetView("sinceFilter", leftPanelWidth+1, 0, leftPanelWidth+1+filterWidth, 2, 0); err != nil {
			v.Title = "Since date"
			v.Editable = true
			v.Wrap = true
			// Обработка времени и даты
			v.Editor = app.timestampFilterEditor("sinceFilter")
			// Изменить цвет окна
			v.FrameColor = app.selectedFrameColor
			v.TitleColor = app.selectedTitleColor
			// Выбираем новое окно
			if _, err := g.SetCurrentView("sinceFilter"); err != nil {
				return nil
			}
			// Возобновляет текст из переменной
			fmt.Fprint(v, app.sinceFilterText)
			// Корректируем позицию курсора
			if err = v.SetCursor(len(app.sinceFilterText), 0); err != nil {
				return nil
			}
		}
		if v2, err := g.SetView("untilFilter", leftPanelWidth+1+filterWidth+1, 0, maxX-1, 2, 0); err != nil {
			v2.Title = "Until date"
			v2.Editable = true
			v2.Wrap = true
			v2.Editor = app.timestampFilterEditor("untilFilter")
			fmt.Fprint(v2, app.untilFilterText)
			if err = v2.SetCursor(len(app.untilFilterText), 0); err != nil {
				return nil
			}
		}
	case "Filter (Timestamp)":
		// Удаляем временные два окна
		if err = g.DeleteView("sinceFilter"); err != nil {
			return nil
		}
		if err = g.DeleteView("untilFilter"); err != nil {
			return nil
		}
		// Возвращяем фокус и цвет назад
		if _, err := g.SetCurrentView("filter"); err != nil {
			return nil
		}
		v.FrameColor = app.selectedFrameColor
		v.TitleColor = app.selectedTitleColor
		selectedFilter.Title = "Filter (Default)"
		app.selectFilterMode = "default"
	}
	if app.selectFilterMode == "timestamp" {
	} else {
		app.applyFilter(false)
	}
	return nil
}

// Функция для переключения режима фильтрации (вниз)
func (app *App) setFilterModeLeft(g *gocui.Gui, v *gocui.View) error {
	selectedFilter, err := g.View("filter")
	if err != nil {
		log.Panicln(err)
	}
	switch selectedFilter.Title {
	case "Filter (Default)":
		selectedFilter.Title = "Filter (Timestamp)"
		app.selectFilterMode = "timestamp"
		maxX, _ := g.Size()
		leftPanelWidth := maxX / 4
		filterWidth := (maxX - leftPanelWidth - 1) / 2
		if v, err := g.SetView("sinceFilter", leftPanelWidth+1, 0, leftPanelWidth+1+filterWidth, 2, 0); err != nil {
			v.Title = "Since date"
			v.Editable = true
			v.Wrap = true
			v.Editor = app.timestampFilterEditor("sinceFilter")
			v.FrameColor = app.selectedFrameColor
			v.TitleColor = app.selectedTitleColor
			if _, err := g.SetCurrentView("sinceFilter"); err != nil {
				return nil
			}
			fmt.Fprint(v, app.sinceFilterText)
			if err = v.SetCursor(len(app.sinceFilterText), 0); err != nil {
				return nil
			}
		}
		if v2, err := g.SetView("untilFilter", leftPanelWidth+1+filterWidth+1, 0, maxX-1, 2, 0); err != nil {
			v2.Title = "Until date"
			v2.Editable = true
			v2.Wrap = true
			v2.Editor = app.timestampFilterEditor("untilFilter")
			fmt.Fprint(v2, app.untilFilterText)
			if err = v2.SetCursor(len(app.untilFilterText), 0); err != nil {
				return nil
			}
		}
	case "Filter (Timestamp)":
		if err = g.DeleteView("sinceFilter"); err != nil {
			return nil
		}
		if err = g.DeleteView("untilFilter"); err != nil {
			return nil
		}
		if _, err := g.SetCurrentView("filter"); err != nil {
			return nil
		}
		v.FrameColor = app.selectedFrameColor
		v.TitleColor = app.selectedTitleColor
		selectedFilter.Title = "Filter (Regex)"
		app.selectFilterMode = "regex"
	case "Filter (Regex)":
		selectedFilter.Title = "Filter (Fuzzy)"
		app.selectFilterMode = "fuzzy"
	case "Filter (Fuzzy)":
		selectedFilter.Title = "Filter (Default)"
		app.selectFilterMode = "default"
	}
	return nil
}

// Функции для переключения выбора журналов из journalctl

func (app *App) setUnitListRight(g *gocui.Gui, v *gocui.View) error {
	selectedServices, err := g.View("services")
	if err != nil {
		log.Panicln(err)
	}
	// Сбрасываем содержимое массива и положение курсора
	app.journals = app.journals[:0]
	app.startServices = 0
	app.selectedJournal = 0
	// Меняем журнал и обновляем список
	switch app.selectUnits {
	case "systemUnits":
		app.selectUnits = "userUnits"
		selectedServices.Title = " < User units (0) > "
		app.loadServices(app.selectUnits)
	case "userUnits":
		app.selectUnits = "systemJournals"
		selectedServices.Title = " < System journals (0) > "
		app.loadServices(app.selectUnits)
	case "systemJournals":
		app.selectUnits = "kernelBoot"
		selectedServices.Title = " < Kernel boot (0) > "
		app.loadServices(app.selectUnits)
	case "kernelBoot":
		app.selectUnits = "auditd"
		selectedServices.Title = " < Audit rules keys (0) > "
		app.loadServices(app.selectUnits)
	case "auditd":
		app.selectUnits = "systemUnits"
		selectedServices.Title = " < System units (0) > "
		app.loadServices(app.selectUnits)
	}
	return nil
}

func (app *App) setUnitListLeft(g *gocui.Gui, v *gocui.View) error {
	selectedServices, err := g.View("services")
	if err != nil {
		log.Panicln(err)
	}
	app.journals = app.journals[:0]
	app.startServices = 0
	app.selectedJournal = 0
	switch app.selectUnits {
	case "systemUnits":
		app.selectUnits = "auditd"
		selectedServices.Title = " < Audit rules keys (0) > "
		app.loadServices(app.selectUnits)
	case "auditd":
		app.selectUnits = "kernelBoot"
		selectedServices.Title = " < Kernel boot (0) > "
		app.loadServices(app.selectUnits)
	case "kernelBoot":
		app.selectUnits = "systemJournals"
		selectedServices.Title = " < System journals (0) > "
		app.loadServices(app.selectUnits)
	case "systemJournals":
		app.selectUnits = "userUnits"
		selectedServices.Title = " < User units (0) > "
		app.loadServices(app.selectUnits)
	case "userUnits":
		app.selectUnits = "systemUnits"
		selectedServices.Title = " < System units (0) > "
		app.loadServices(app.selectUnits)
	}
	return nil
}

// Функция для переключения выбора журналов файловой системы
func (app *App) setLogFilesListRight(g *gocui.Gui, v *gocui.View) error {
	selectedVarLog, err := g.View("varLogs")
	if err != nil {
		log.Panicln(err)
	}
	// Добавляем сообщение о загрузке журнала
	g.Update(func(g *gocui.Gui) error {
		selectedVarLog.Clear()
		fmt.Fprintln(selectedVarLog, "Searching log files...")
		selectedVarLog.Highlight = false
		return nil
	})
	// Отключаем переключение списков
	app.keybindingsEnabled = false
	if err := app.setupKeybindings(); err != nil {
		log.Panicln("Error key bindings", err)
	}
	// Полсекундная задержка, для корректного обновления интерфейса после выполнения функции
	time.Sleep(500 * time.Millisecond)
	app.logfiles = app.logfiles[:0]
	app.startFiles = 0
	app.selectedFile = 0
	// Запускаем функцию загрузки журнала в горутине
	if app.getOS == "windows" {
		go func() {
			switch app.selectPath {
			case "ProgramFiles":
				app.selectPath = "ProgramFiles86"
				selectedVarLog.Title = " < Program Files x86 (0) > "
				app.loadWinFiles(app.selectPath)
			case "ProgramFiles86":
				app.selectPath = "ProgramData"
				selectedVarLog.Title = " < ProgramData (0) > "
				app.loadWinFiles(app.selectPath)
			case "ProgramData":
				app.selectPath = "AppDataLocal"
				selectedVarLog.Title = " < AppData Local (0) > "
				app.loadWinFiles(app.selectPath)
			case "AppDataLocal":
				app.selectPath = "AppDataRoaming"
				selectedVarLog.Title = " < AppData Roaming (0) > "
				app.loadWinFiles(app.selectPath)
			case "AppDataRoaming":
				app.selectPath = "WinCustomPath"
				selectedVarLog.Title = " < Custom Path (0) > "
				app.loadWinFiles(app.selectPath)
			case "WinCustomPath":
				app.selectPath = "ProgramFiles"
				selectedVarLog.Title = " < Program Files (0) > "
				app.loadWinFiles(app.selectPath)
			}
			// Включаем переключение списков
			app.keybindingsEnabled = true
			if err := app.setupKeybindings(); err != nil {
				log.Panicln("Error key bindings", err)
			}
		}()
	} else {
		go func() {
			switch app.selectPath {
			case "varlog":
				app.selectPath = "customPath"
				selectedVarLog.Title = " < Custom path - " + app.customPath + " (0) > "
				app.loadFiles(app.selectPath)
			case "customPath":
				app.selectPath = "home"
				selectedVarLog.Title = " < Users home logs (0) > "
				app.loadFiles(app.selectPath)
			case "home":
				app.selectPath = "descriptor"
				selectedVarLog.Title = " < Process descriptor logs (0) > "
				app.loadFiles(app.selectPath)
			case "descriptor":
				app.selectPath = "varlog"
				selectedVarLog.Title = " < System var logs (0) > "
				app.loadFiles(app.selectPath)
			}
			// Включаем переключение списков
			app.keybindingsEnabled = true
			if err := app.setupKeybindings(); err != nil {
				log.Panicln("Error key bindings", err)
			}
		}()
	}
	return nil
}

func (app *App) setLogFilesListLeft(g *gocui.Gui, v *gocui.View) error {
	selectedVarLog, err := g.View("varLogs")
	if err != nil {
		log.Panicln(err)
	}
	g.Update(func(g *gocui.Gui) error {
		selectedVarLog.Clear()
		fmt.Fprintln(selectedVarLog, "Searching log files...")
		selectedVarLog.Highlight = false
		return nil
	})
	app.keybindingsEnabled = false
	if err := app.setupKeybindings(); err != nil {
		log.Panicln("Error key bindings", err)
	}
	time.Sleep(500 * time.Millisecond)
	app.logfiles = app.logfiles[:0]
	app.startFiles = 0
	app.selectedFile = 0
	if app.getOS == "windows" {
		go func() {
			switch app.selectPath {
			case "ProgramFiles":
				app.selectPath = "WinCustomPath"
				selectedVarLog.Title = " < Custom Path (0) > "
				app.loadWinFiles(app.selectPath)
			case "WinCustomPath":
				app.selectPath = "AppDataRoaming"
				selectedVarLog.Title = " < AppData Roaming (0) > "
				app.loadWinFiles(app.selectPath)
			case "AppDataRoaming":
				app.selectPath = "AppDataLocal"
				selectedVarLog.Title = " < AppData Local (0) > "
				app.loadWinFiles(app.selectPath)
			case "AppDataLocal":
				app.selectPath = "ProgramData"
				selectedVarLog.Title = " < ProgramData (0) > "
				app.loadWinFiles(app.selectPath)
			case "ProgramData":
				app.selectPath = "ProgramFiles86"
				selectedVarLog.Title = " < Program Files x86 (0) > "
				app.loadWinFiles(app.selectPath)
			case "ProgramFiles86":
				app.selectPath = "ProgramFiles"
				selectedVarLog.Title = " < Program Files (0) > "
				app.loadWinFiles(app.selectPath)
			}
			app.keybindingsEnabled = true
			if err := app.setupKeybindings(); err != nil {
				log.Panicln("Error key bindings", err)
			}
		}()
	} else {
		go func() {
			switch app.selectPath {
			case "varlog":
				app.selectPath = "descriptor"
				selectedVarLog.Title = " < Process descriptor logs (0) > "
				app.loadFiles(app.selectPath)
			case "descriptor":
				app.selectPath = "home"
				selectedVarLog.Title = " < Users home logs (0) > "
				app.loadFiles(app.selectPath)
			case "home":
				app.selectPath = "customPath"
				selectedVarLog.Title = " < Custom path - " + app.customPath + " (0) > "
				app.loadFiles(app.selectPath)
			case "customPath":
				app.selectPath = "varlog"
				selectedVarLog.Title = " < System var logs (0) > "
				app.loadFiles(app.selectPath)
			}
			app.keybindingsEnabled = true
			if err := app.setupKeybindings(); err != nil {
				log.Panicln("Error key bindings", err)
			}
		}()
	}
	return nil
}

// Функция для переключения списков системы контейнеризации
func (app *App) setContainersListRight(g *gocui.Gui, v *gocui.View) error {
	selectedDocker, err := g.View("docker")
	if err != nil {
		log.Panicln(err)
	}
	app.dockerContainers = app.dockerContainers[:0]
	app.startDockerContainers = 0
	app.selectedDockerContainer = 0
	switch app.selectContainerizationSystem {
	case "docker":
		app.selectContainerizationSystem = "compose"
		selectedDocker.Title = " < Compose stacks (0) > "
		app.loadDockerContainer(app.selectContainerizationSystem)
	case "compose":
		app.selectContainerizationSystem = "podman"
		selectedDocker.Title = " < Podman containers (0) > "
		app.loadDockerContainer(app.selectContainerizationSystem)
	case "podman":
		app.selectContainerizationSystem = "kubernetes"
		selectedDocker.Title = " < Kubernetes pods (0) > "
		app.loadDockerContainer(app.selectContainerizationSystem)
	case "kubernetes":
		app.selectContainerizationSystem = "docker"
		selectedDocker.Title = " < Docker containers (0) > "
		app.loadDockerContainer(app.selectContainerizationSystem)
	}
	return nil
}

func (app *App) setContainersListLeft(g *gocui.Gui, v *gocui.View) error {
	selectedDocker, err := g.View("docker")
	if err != nil {
		log.Panicln(err)
	}
	app.dockerContainers = app.dockerContainers[:0]
	app.startDockerContainers = 0
	app.selectedDockerContainer = 0
	switch app.selectContainerizationSystem {
	case "docker":
		app.selectContainerizationSystem = "kubernetes"
		selectedDocker.Title = " < Kubernetes pods (0) > "
		app.loadDockerContainer(app.selectContainerizationSystem)
	case "kubernetes":
		app.selectContainerizationSystem = "podman"
		selectedDocker.Title = " < Podman containers (0) > "
		app.loadDockerContainer(app.selectContainerizationSystem)
	case "podman":
		app.selectContainerizationSystem = "compose"
		selectedDocker.Title = " < Compose stacks (0) > "
		app.loadDockerContainer(app.selectContainerizationSystem)
	case "compose":
		app.selectContainerizationSystem = "docker"
		selectedDocker.Title = " < Docker containers (0) > "
		app.loadDockerContainer(app.selectContainerizationSystem)
	}
	return nil
}

// Функция для переключения окон через Tab
func (app *App) nextView(g *gocui.Gui, v *gocui.View) error {
	selectedFilterList, err := g.View("filterList")
	if err != nil {
		log.Panicln(err)
	}
	selectedServices, err := g.View("services")
	if err != nil {
		log.Panicln(err)
	}
	selectedVarLog, err := g.View("varLogs")
	if err != nil {
		log.Panicln(err)
	}
	selectedDocker, err := g.View("docker")
	if err != nil {
		log.Panicln(err)
	}
	selectedFilter, err := g.View("filter")
	if err != nil {
		log.Panicln(err)
	}
	sinceFilter, err := g.View("sinceFilter")
	if err != nil {
		app.timestampFilterView = false
	} else {
		app.timestampFilterView = true
	}
	untilFilter, err := g.View("untilFilter")
	if err != nil {
		app.timestampFilterView = false
	} else {
		app.timestampFilterView = true
	}
	selectedLogs, err := g.View("logs")
	if err != nil {
		log.Panicln(err)
	}
	selectedScrollLogs, err := g.View("scrollLogs")
	if err != nil {
		log.Panicln(err)
	}
	currentView := g.CurrentView()
	var nextView string
	// Выставляем начальное окно
	if currentView == nil {
		nextView = "services"
	} else {
		cView := currentView.Name()
		// Проверяем активное окно
		views := []string{
			"filterList",
			"services",
			"varLogs",
			"docker",
			"filter",
			"sinceFilter",
			"untilFilter",
			"logs",
			"scrollLogs",
		}
		ok := false
		for _, v := range views {
			if cView == v {
				ok = true
				break
			}
		}
		if !ok {
			cView = app.globalCurrentView
		}
		switch cView {
		case "filterList":
			nextView = "services"
			selectedFilterList.FrameColor = app.frameColor
			selectedFilterList.TitleColor = app.titleColor
			selectedServices.FrameColor = app.selectedFrameColor
			selectedServices.TitleColor = app.selectedTitleColor
			selectedVarLog.FrameColor = app.fileSystemFrameColor
			selectedVarLog.TitleColor = app.titleColor
			selectedDocker.FrameColor = app.dockerFrameColor
			selectedDocker.TitleColor = app.titleColor
			selectedFilter.FrameColor = app.frameColor
			selectedFilter.TitleColor = app.titleColor
			selectedLogs.FrameColor = app.frameColor
			selectedLogs.TitleColor = app.titleColor
			selectedScrollLogs.FrameColor = app.frameColor
		case "services":
			nextView = "varLogs"
			selectedFilterList.FrameColor = app.frameColor
			selectedFilterList.TitleColor = app.titleColor
			selectedServices.FrameColor = app.journalListFrameColor
			selectedServices.TitleColor = app.titleColor
			selectedVarLog.FrameColor = app.selectedFrameColor
			selectedVarLog.TitleColor = app.selectedTitleColor
			selectedDocker.FrameColor = app.dockerFrameColor
			selectedDocker.TitleColor = app.titleColor
			selectedFilter.FrameColor = app.frameColor
			selectedFilter.TitleColor = app.titleColor
			selectedLogs.FrameColor = app.frameColor
			selectedLogs.TitleColor = app.titleColor
			selectedScrollLogs.FrameColor = app.frameColor
		case "varLogs":
			nextView = "docker"
			selectedFilterList.FrameColor = app.frameColor
			selectedFilterList.TitleColor = app.titleColor
			selectedServices.FrameColor = app.journalListFrameColor
			selectedServices.TitleColor = app.titleColor
			selectedVarLog.FrameColor = app.fileSystemFrameColor
			selectedVarLog.TitleColor = app.titleColor
			selectedDocker.FrameColor = app.selectedFrameColor
			selectedDocker.TitleColor = app.selectedTitleColor
			selectedFilter.FrameColor = app.frameColor
			selectedFilter.TitleColor = app.titleColor
			selectedLogs.FrameColor = app.frameColor
			selectedLogs.TitleColor = app.titleColor
			selectedScrollLogs.FrameColor = app.frameColor
		case "docker":
			if app.timestampFilterView {
				nextView = "sinceFilter"
				selectedFilterList.FrameColor = app.frameColor
				selectedFilterList.TitleColor = app.titleColor
				selectedServices.FrameColor = app.journalListFrameColor
				selectedServices.TitleColor = app.titleColor
				selectedVarLog.FrameColor = app.fileSystemFrameColor
				selectedVarLog.TitleColor = app.titleColor
				selectedDocker.FrameColor = app.dockerFrameColor
				selectedDocker.TitleColor = app.titleColor
				sinceFilter.FrameColor = app.selectedFrameColor
				sinceFilter.TitleColor = app.selectedTitleColor
				selectedLogs.FrameColor = app.frameColor
				selectedLogs.TitleColor = app.titleColor
				selectedScrollLogs.FrameColor = app.frameColor
			} else {
				nextView = "filter"
				selectedFilterList.FrameColor = app.frameColor
				selectedFilterList.TitleColor = app.titleColor
				selectedServices.FrameColor = app.journalListFrameColor
				selectedServices.TitleColor = app.titleColor
				selectedVarLog.FrameColor = app.fileSystemFrameColor
				selectedVarLog.TitleColor = app.titleColor
				selectedDocker.FrameColor = app.dockerFrameColor
				selectedDocker.TitleColor = app.titleColor
				selectedFilter.FrameColor = app.selectedFrameColor
				selectedFilter.TitleColor = app.selectedTitleColor
				selectedLogs.FrameColor = app.frameColor
				selectedLogs.TitleColor = app.titleColor
				selectedScrollLogs.FrameColor = app.frameColor
			}
		case "sinceFilter":
			nextView = "untilFilter"
			selectedFilterList.FrameColor = app.frameColor
			selectedFilterList.TitleColor = app.titleColor
			selectedServices.FrameColor = app.journalListFrameColor
			selectedServices.TitleColor = app.titleColor
			selectedVarLog.FrameColor = app.fileSystemFrameColor
			selectedVarLog.TitleColor = app.titleColor
			selectedDocker.FrameColor = app.dockerFrameColor
			selectedDocker.TitleColor = app.titleColor
			sinceFilter.FrameColor = app.frameColor
			sinceFilter.TitleColor = app.titleColor
			untilFilter.FrameColor = app.selectedFrameColor
			untilFilter.TitleColor = app.selectedTitleColor
			selectedLogs.FrameColor = app.frameColor
			selectedLogs.TitleColor = app.titleColor
			selectedScrollLogs.FrameColor = app.frameColor
		case "filter", "untilFilter":
			if app.timestampFilterView {
				untilFilter.FrameColor = app.frameColor
				untilFilter.TitleColor = app.titleColor
			}
			nextView = "logs"
			selectedFilterList.FrameColor = app.frameColor
			selectedFilterList.TitleColor = app.titleColor
			selectedServices.FrameColor = app.journalListFrameColor
			selectedServices.TitleColor = app.titleColor
			selectedVarLog.FrameColor = app.fileSystemFrameColor
			selectedVarLog.TitleColor = app.titleColor
			selectedDocker.FrameColor = app.dockerFrameColor
			selectedDocker.TitleColor = app.titleColor
			selectedFilter.FrameColor = app.frameColor
			selectedFilter.TitleColor = app.titleColor
			selectedLogs.FrameColor = app.selectedFrameColor
			selectedLogs.TitleColor = app.selectedTitleColor
			selectedScrollLogs.FrameColor = app.selectedFrameColor
		case "logs":
			nextView = "filterList"
			selectedFilterList.FrameColor = app.selectedFrameColor
			selectedFilterList.TitleColor = app.selectedTitleColor
			selectedServices.FrameColor = app.journalListFrameColor
			selectedServices.TitleColor = app.titleColor
			selectedVarLog.FrameColor = app.fileSystemFrameColor
			selectedVarLog.TitleColor = app.titleColor
			selectedDocker.FrameColor = app.dockerFrameColor
			selectedDocker.TitleColor = app.titleColor
			selectedFilter.FrameColor = app.frameColor
			selectedFilter.TitleColor = app.titleColor
			selectedLogs.FrameColor = app.frameColor
			selectedLogs.TitleColor = app.titleColor
			selectedScrollLogs.FrameColor = app.frameColor
		}
	}
	// Фиксируем название окна
	app.globalCurrentView = nextView
	// Устанавливаем новое активное окно
	if _, err := g.SetCurrentView(nextView); err != nil {
		return err
	}
	return nil
}

// Функция для переключения окон в обратном порядке через Shift+Tab
func (app *App) backView(g *gocui.Gui, v *gocui.View) error {
	selectedFilterList, err := g.View("filterList")
	if err != nil {
		log.Panicln(err)
	}
	selectedServices, err := g.View("services")
	if err != nil {
		log.Panicln(err)
	}
	selectedVarLog, err := g.View("varLogs")
	if err != nil {
		log.Panicln(err)
	}
	selectedDocker, err := g.View("docker")
	if err != nil {
		log.Panicln(err)
	}
	selectedFilter, err := g.View("filter")
	if err != nil {
		log.Panicln(err)
	}
	sinceFilter, err := g.View("sinceFilter")
	if err != nil {
		app.timestampFilterView = false
	} else {
		app.timestampFilterView = true
	}
	untilFilter, err := g.View("untilFilter")
	if err != nil {
		app.timestampFilterView = false
	} else {
		app.timestampFilterView = true
	}
	selectedLogs, err := g.View("logs")
	if err != nil {
		log.Panicln(err)
	}
	selectedScrollLogs, err := g.View("scrollLogs")
	if err != nil {
		log.Panicln(err)
	}
	currentView := g.CurrentView()
	var nextView string
	if currentView == nil {
		nextView = "services"
	} else {
		cView := currentView.Name()
		views := []string{
			"filterList",
			"services",
			"varLogs",
			"docker",
			"filter",
			"sinceFilter",
			"untilFilter",
			"logs",
			"scrollLogs",
		}
		ok := false
		for _, v := range views {
			if cView == v {
				ok = true
				break
			}
		}
		if !ok {
			cView = app.globalCurrentView
		}
		switch cView {
		case "filterList":
			nextView = "logs"
			selectedFilterList.FrameColor = app.frameColor
			selectedFilterList.TitleColor = app.titleColor
			selectedServices.FrameColor = app.journalListFrameColor
			selectedServices.TitleColor = app.titleColor
			selectedVarLog.FrameColor = app.fileSystemFrameColor
			selectedVarLog.TitleColor = app.titleColor
			selectedDocker.FrameColor = app.dockerFrameColor
			selectedDocker.TitleColor = app.titleColor
			selectedFilter.FrameColor = app.frameColor
			selectedFilter.TitleColor = app.titleColor
			selectedLogs.FrameColor = app.selectedFrameColor
			selectedLogs.TitleColor = app.selectedTitleColor
			selectedScrollLogs.FrameColor = app.selectedFrameColor
		case "services":
			nextView = "filterList"
			selectedFilterList.FrameColor = app.selectedFrameColor
			selectedFilterList.TitleColor = app.selectedTitleColor
			selectedServices.FrameColor = app.journalListFrameColor
			selectedServices.TitleColor = app.titleColor
			selectedVarLog.FrameColor = app.fileSystemFrameColor
			selectedVarLog.TitleColor = app.titleColor
			selectedDocker.FrameColor = app.dockerFrameColor
			selectedDocker.TitleColor = app.titleColor
			selectedFilter.FrameColor = app.frameColor
			selectedFilter.TitleColor = app.titleColor
			selectedLogs.FrameColor = app.frameColor
			selectedLogs.TitleColor = app.titleColor
			selectedScrollLogs.FrameColor = app.frameColor
		case "logs":
			if app.timestampFilterView {
				nextView = "untilFilter"
				selectedFilterList.FrameColor = app.frameColor
				selectedFilterList.TitleColor = app.titleColor
				selectedServices.FrameColor = app.journalListFrameColor
				selectedServices.TitleColor = app.titleColor
				selectedVarLog.FrameColor = app.fileSystemFrameColor
				selectedVarLog.TitleColor = app.titleColor
				selectedDocker.FrameColor = app.dockerFrameColor
				selectedDocker.TitleColor = app.titleColor
				untilFilter.FrameColor = app.selectedFrameColor
				untilFilter.TitleColor = app.selectedTitleColor
				selectedLogs.FrameColor = app.frameColor
				selectedLogs.TitleColor = app.titleColor
				selectedScrollLogs.FrameColor = app.frameColor
			} else {
				nextView = "filter"
				selectedFilterList.FrameColor = app.frameColor
				selectedFilterList.TitleColor = app.titleColor
				selectedServices.FrameColor = app.journalListFrameColor
				selectedServices.TitleColor = app.titleColor
				selectedVarLog.FrameColor = app.fileSystemFrameColor
				selectedVarLog.TitleColor = app.titleColor
				selectedDocker.FrameColor = app.dockerFrameColor
				selectedDocker.TitleColor = app.titleColor
				selectedFilter.FrameColor = app.selectedFrameColor
				selectedFilter.TitleColor = app.selectedTitleColor
				selectedLogs.FrameColor = app.frameColor
				selectedLogs.TitleColor = app.titleColor
				selectedScrollLogs.FrameColor = app.frameColor
			}
		case "untilFilter":
			nextView = "sinceFilter"
			selectedFilterList.FrameColor = app.frameColor
			selectedFilterList.TitleColor = app.titleColor
			selectedServices.FrameColor = app.journalListFrameColor
			selectedServices.TitleColor = app.titleColor
			selectedVarLog.FrameColor = app.fileSystemFrameColor
			selectedVarLog.TitleColor = app.titleColor
			selectedDocker.FrameColor = app.dockerFrameColor
			selectedDocker.TitleColor = app.titleColor
			sinceFilter.FrameColor = app.selectedFrameColor
			sinceFilter.TitleColor = app.selectedTitleColor
			untilFilter.FrameColor = app.frameColor
			untilFilter.TitleColor = app.titleColor
			selectedLogs.FrameColor = app.frameColor
			selectedLogs.TitleColor = app.titleColor
			selectedScrollLogs.FrameColor = app.frameColor
		case "filter", "sinceFilter":
			if app.timestampFilterView {
				sinceFilter.FrameColor = app.frameColor
				sinceFilter.TitleColor = app.titleColor
			}
			nextView = "docker"
			selectedFilterList.FrameColor = app.frameColor
			selectedFilterList.TitleColor = app.titleColor
			selectedServices.FrameColor = app.journalListFrameColor
			selectedServices.TitleColor = app.titleColor
			selectedVarLog.FrameColor = app.fileSystemFrameColor
			selectedVarLog.TitleColor = app.titleColor
			selectedDocker.FrameColor = app.selectedFrameColor
			selectedDocker.TitleColor = app.selectedTitleColor
			selectedFilter.FrameColor = app.frameColor
			selectedFilter.TitleColor = app.titleColor
			selectedLogs.FrameColor = app.frameColor
			selectedLogs.TitleColor = app.titleColor
			selectedScrollLogs.FrameColor = app.frameColor
		case "docker":
			nextView = "varLogs"
			selectedFilterList.FrameColor = app.frameColor
			selectedFilterList.TitleColor = app.titleColor
			selectedServices.FrameColor = app.journalListFrameColor
			selectedServices.TitleColor = app.titleColor
			selectedVarLog.FrameColor = app.selectedFrameColor
			selectedVarLog.TitleColor = app.selectedTitleColor
			selectedDocker.FrameColor = app.dockerFrameColor
			selectedDocker.TitleColor = app.titleColor
			selectedFilter.FrameColor = app.frameColor
			selectedFilter.TitleColor = app.titleColor
			selectedLogs.FrameColor = app.frameColor
			selectedLogs.TitleColor = app.titleColor
			selectedScrollLogs.FrameColor = app.frameColor
		case "varLogs":
			nextView = "services"
			selectedFilterList.FrameColor = app.frameColor
			selectedFilterList.TitleColor = app.titleColor
			selectedServices.FrameColor = app.selectedFrameColor
			selectedServices.TitleColor = app.selectedTitleColor
			selectedVarLog.FrameColor = app.fileSystemFrameColor
			selectedVarLog.TitleColor = app.titleColor
			selectedDocker.FrameColor = app.dockerFrameColor
			selectedDocker.TitleColor = app.titleColor
			selectedFilter.FrameColor = app.frameColor
			selectedFilter.TitleColor = app.titleColor
			selectedLogs.FrameColor = app.frameColor
			selectedLogs.TitleColor = app.titleColor
			selectedScrollLogs.FrameColor = app.frameColor
		}
	}
	app.globalCurrentView = nextView
	if _, err := g.SetCurrentView(nextView); err != nil {
		return err
	}
	return nil
}

func (app *App) setSelectView(g *gocui.Gui, viewName string) error {
	// Сбрасываем цвет всех окон
	views := []string{
		"filterList",
		"services",
		"varLogs",
		"docker",
		"filter",
		"sinceFilter",
		"untilFilter",
		"logs",
	}
	for _, name := range views {
		if v, err := g.View(name); err == nil {
			v.FrameColor = app.frameColor
			// Исключение для tail
			if name != "logs" {
				v.TitleColor = app.titleColor
			}
		}
	}
	// Устанавливаем цвет для активного окна
	if v, err := g.View(viewName); err == nil {
		v.FrameColor = app.selectedFrameColor
		if viewName != "logs" {
			v.TitleColor = app.selectedTitleColor
		}
	}
	// Устанавливаем фокус на активное окно
	_, err := g.SetCurrentView(viewName)
	return err
}

// Функция для выхода
func quit(g *gocui.Gui, v *gocui.View) error {
	return gocui.ErrQuit
}
