<!DOCTYPE html>
<html>
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
		<title>lazyjournal: Go Coverage Report</title>
		<style>
			body {
				background: black;
				color: rgb(80, 80, 80);
			}
			body, pre, #legend span {
				font-family: Menlo, monospace;
				font-weight: bold;
			}
			#topbar {
				background: black;
				position: fixed;
				top: 0; left: 0; right: 0;
				height: 42px;
				border-bottom: 1px solid rgb(80, 80, 80);
			}
			#content {
				margin-top: 50px;
			}
			#nav, #legend {
				float: left;
				margin-left: 10px;
			}
			#legend {
				margin-top: 12px;
			}
			#nav {
				margin-top: 10px;
			}
			#legend span {
				margin: 0 5px;
			}
			.cov0 { color: rgb(192, 0, 0) }
.cov1 { color: rgb(128, 128, 128) }
.cov2 { color: rgb(116, 140, 131) }
.cov3 { color: rgb(104, 152, 134) }
.cov4 { color: rgb(92, 164, 137) }
.cov5 { color: rgb(80, 176, 140) }
.cov6 { color: rgb(68, 188, 143) }
.cov7 { color: rgb(56, 200, 146) }
.cov8 { color: rgb(44, 212, 149) }
.cov9 { color: rgb(32, 224, 152) }
.cov10 { color: rgb(20, 236, 155) }

		</style>
	</head>
	<body>
		<div id="topbar">
			<div id="nav">
				<select id="files">
				
				<option value="file0">github.com/Lifailon/lazyjournal/main.go (74.9%)</option>
				
				</select>
			</div>
			<div id="legend">
				<span>not tracked</span>
			
				<span class="cov0">not covered</span>
				<span class="cov8">covered</span>
			
			</div>
		</div>
		<div id="content">
		
		<pre class="file" id="file0" style="display: none">package main

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

        "github.com/awesome-gocui/gocui"
        "golang.org/x/text/encoding/charmap"
        "golang.org/x/text/encoding/unicode"
)

var programVersion string = "0.7.6"

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

type Logfile struct {
        name string
        path string
}

type DockerContainers struct {
        name string
        id   string
}

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

        testMode     bool // исключаем вызовы к gocui при тестирование функций
        tailSpinMode bool // режим покраски через tailspin
        colorMode    bool // отключение/включение покраски ключевых слов

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

        selectUnits                  string // название журнала (UNIT/USER_UNIT)
        selectPath                   string // путь к логам (/var/log/)
        selectContainerizationSystem string // название системы контейнеризации (docker/podman/kubernetes)
        selectFilterMode             string // режим фильтрации (default/fuzzy/regex)
        logViewCount                 string // количество логов для просмотра (5000)

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

        logfiles        []Logfile
        maxVisibleFiles int
        startFiles      int
        selectedFile    int

        dockerContainers           []DockerContainers
        maxVisibleDockerContainers int
        startDockerContainers      int
        selectedDockerContainer    int

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

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

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

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

        autoScroll     bool   // используется для автоматического скроллинга вниз при обновлении (если это не ручной скроллинг)
        newUpdateIndex int    // фиксируем текущую длинну массива (индекс) для вставки строки обновления (если это ручной выбор из списка)
        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

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

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

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

        // Регулярные выражения для покраски строк
        trimHttpRegex        *regexp.Regexp
        trimHttpsRegex       *regexp.Regexp
        trimPrefixPathRegex  *regexp.Regexp
        trimPostfixPathRegex *regexp.Regexp
        hexByteRegex         *regexp.Regexp
        dateTimeRegex        *regexp.Regexp
        timeMacAddressRegex  *regexp.Regexp
        dateIpAddressRegex   *regexp.Regexp
        dateRegex            *regexp.Regexp
        ipAddressRegex       *regexp.Regexp
        procRegex            *regexp.Regexp
        syslogUnitRegex      *regexp.Regexp
}

func showHelp() <span class="cov8" title="1">{
        fmt.Println("lazyjournal - terminal user interface for reading logs from journalctl, file system, Docker and Podman containers, as well Kubernetes pods.")
        fmt.Println("Source code: https://github.com/Lifailon/lazyjournal")
        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("    lazyjournal                Run interface")
        fmt.Println("    lazyjournal --help, -h     Show help")
        fmt.Println("    lazyjournal --version, -v  Show version")
        fmt.Println("    lazyjournal --audit, -a    Show audit information")
}</span>

func (app *App) showVersion() <span class="cov8" title="1">{
        fmt.Println(programVersion)
}</span>

func (app *App) showAudit() <span class="cov8" title="1">{
        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 </span><span class="cov8" title="1">{
                auditText = append(auditText, "  os: "+app.getOS)
        }</span> else<span class="cov8" title="1"> {
                var name, version string
                for _, line := range strings.Split(string(data), "\n") </span><span class="cov8" title="1">{
                        if strings.HasPrefix(line, "NAME=") </span><span class="cov8" title="1">{
                                name = strings.Trim(line[5:], "\"")
                        }</span>
                        <span class="cov8" title="1">if strings.HasPrefix(line, "VERSION=") </span><span class="cov8" title="1">{
                                version = strings.Trim(line[8:], "\"")
                        }</span>
                }
                <span class="cov8" title="1">auditText = append(auditText, "  os: "+app.getOS+" "+name+" "+version)</span>
        }

        <span class="cov8" title="1">auditText = append(auditText, "  arch: "+app.getArch)

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

        if app.getOS != "windows" </span><span class="cov8" title="1">{
                auditText = append(auditText, "  privilege: "+(map[bool]string{true: "root", false: "user"})[os.Geteuid() == 0])
        }</span>

        <span class="cov8" title="1">execPath, err := os.Executable()
        if err == nil </span><span class="cov8" title="1">{
                if strings.Contains(execPath, "tmp/go-build") || strings.Contains(execPath, "Temp\\go-build") </span><span class="cov8" title="1">{
                        auditText = append(auditText, "  execType: source code")
                }</span> else<span class="cov0" title="0"> {
                        auditText = append(auditText, "  execType: binary file")
                }</span>
        }
        <span class="cov8" title="1">auditText = append(auditText, "  execPath: "+execPath)

        if app.getOS == "windows" </span><span class="cov8" title="1">{
                // Windows Event
                app.loadWinEvents()
                auditText = append(auditText,
                        "winEvent:",
                        "  logs: ",
                        "  - count: "+strconv.Itoa(len(app.journals)),
                )
                // Filesystem
                if app.userName != "runneradmin" </span><span class="cov0" title="0">{
                        app.systemDisk = os.Getenv("SystemDrive")
                        if len(app.systemDisk) &gt;= 1 </span><span class="cov0" title="0">{
                                app.systemDisk = string(app.systemDisk[0])
                        }</span> else<span class="cov0" title="0"> {
                                app.systemDisk = "C"
                        }</span>
                        <span class="cov0" title="0">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"},
                        }
                        // Создаем группу для ожидания выполнения всех горутин
                        var wg sync.WaitGroup
                        // Мьютекс для безопасного доступа к переменной auditText
                        var mu sync.Mutex
                        for _, path := range paths </span><span class="cov0" title="0">{
                                // Увеличиваем счетчик горутин
                                wg.Add(1)
                                go func(path struct{ fullPath, path string }) </span><span class="cov0" title="0">{
                                        // Отнимаем счетчик горутин при завершении выполнения горутины
                                        defer wg.Done()
                                        var fullPath string
                                        if strings.HasPrefix(path.fullPath, "Program") </span><span class="cov0" title="0">{
                                                fullPath = "\"" + app.systemDisk + ":/" + path.fullPath + "\""
                                        }</span> else<span class="cov0" title="0"> {
                                                fullPath = "\"" + app.systemDisk + ":/Users/" + app.userName + path.fullPath + "\""
                                        }</span>
                                        <span class="cov0" title="0">app.loadWinFiles(path.path)
                                        lenLogFiles := strconv.Itoa(len(app.logfiles))
                                        // Блокируем доступ на завись в переменную auditText
                                        mu.Lock()
                                        auditText = append(auditText,
                                                "  - path: "+fullPath,
                                                "    count: "+lenLogFiles,
                                        )
                                        // Разблокировать мьютекс
                                        mu.Unlock()</span>
                                }(path)
                        }
                        // Ожидаем завершения всех горутин
                        <span class="cov0" title="0">wg.Wait()</span>
                }
        } else<span class="cov8" title="1"> {
                // systemd/journald
                auditText = append(auditText,
                        "systemd:",
                        "  journald:",
                )
                csCheck := exec.Command("journalctl", "--version")
                _, err := csCheck.Output()
                if err == nil </span><span class="cov8" title="1">{
                        auditText = append(auditText,
                                "  - installed: true",
                                "    journals:",
                        )
                        journalList := []struct {
                                name        string
                                journalName string
                        }{
                                {"Unit list", "services"},
                                {"System journals", "UNIT"},
                                {"User journals", "USER_UNIT"},
                                {"Kernel boot", "kernel"},
                        }
                        for _, journal := range journalList </span><span class="cov8" title="1">{
                                app.loadServices(journal.journalName)
                                lenJournals := strconv.Itoa(len(app.journals))
                                auditText = append(auditText,
                                        "    - name: "+journal.name,
                                        "      count: "+lenJournals,
                                )
                        }</span>
                } else<span class="cov0" title="0"> {
                        auditText = append(auditText, "  - installed: false")
                }</span>
                // Filesystem
                <span class="cov8" title="1">auditText = append(auditText,
                        "fileSystem:",
                        "  files:",
                )
                paths := []struct {
                        name string
                        path string
                }{
                        {"System var logs", "/var/log/"},
                        {"Optional package logs", "/opt/"},
                        {"Users home logs", "/home/"},
                        {"Process descriptor logs", "descriptor"},
                }
                for _, path := range paths </span><span class="cov8" title="1">{
                        app.loadFiles(path.path)
                        lenLogFiles := strconv.Itoa(len(app.logfiles))
                        auditText = append(auditText,
                                "  - name: "+path.name,
                                "    path: "+path.path,
                                "    count: "+lenLogFiles,
                        )
                }</span>
        }
        <span class="cov8" title="1">auditText = append(auditText,
                "containerization: ",
                "  system: ",
        )
        containerizationSystems := []string{
                "docker",
                "podman",
                "kubernetes",
        }
        for _, cs := range containerizationSystems </span><span class="cov8" title="1">{
                auditText = append(auditText, "  - name: "+cs)
                if cs == "kubernetes" </span><span class="cov8" title="1">{
                        csCheck := exec.Command("kubectl", "version")
                        output, _ := csCheck.Output()
                        // По умолчанию у version код возврата всегда 1, по этому проверяем вывод
                        if strings.Contains(string(output), "Version:") </span><span class="cov8" title="1">{
                                auditText = append(auditText, "    installed: true")
                                // Преобразуем байты в строку и обрезаем пробелы
                                csVersion := strings.TrimSpace(string(output))
                                // Удаляем текст до номера версии
                                csVersion = strings.Split(csVersion, "Version: ")[1]
                                // Забираем первую строку
                                csVersion = strings.Split(csVersion, "\n")[0]
                                auditText = append(auditText, "    version: "+csVersion)
                                cmd := exec.Command(
                                        cs, "get", "pods", "-o",
                                        "jsonpath={range .items[*]}{.metadata.uid} {.metadata.name} {.status.phase}{'\\n'}{end}",
                                )
                                _, err := cmd.Output()
                                if err == nil </span><span class="cov0" title="0">{
                                        app.loadDockerContainer(cs)
                                        auditText = append(auditText, "    pods: "+strconv.Itoa(len(app.dockerContainers)))
                                }</span> else<span class="cov8" title="1"> {
                                        auditText = append(auditText, "    pods: 0")
                                }</span>
                        } else<span class="cov0" title="0"> {
                                auditText = append(auditText, "    installed: false")
                        }</span>
                } else<span class="cov8" title="1"> {
                        csCheck := exec.Command(cs, "--version")
                        output, err := csCheck.Output()
                        if err == nil </span><span class="cov8" title="1">{
                                auditText = append(auditText, "    installed: true")
                                csVersion := strings.TrimSpace(string(output))
                                csVersion = strings.Split(csVersion, "version ")[1]
                                auditText = append(auditText, "    version: "+csVersion)
                                cmd := exec.Command(
                                        cs, "ps", "-a",
                                        "--format", "{{.ID}} {{.Names}} {{.State}}",
                                )
                                _, err := cmd.Output()
                                if err == nil </span><span class="cov8" title="1">{
                                        app.loadDockerContainer(cs)
                                        auditText = append(auditText, "    containers: "+strconv.Itoa(len(app.dockerContainers)))
                                }</span> else<span class="cov0" title="0"> {
                                        auditText = append(auditText, "    containers: 0")
                                }</span>
                        } else<span class="cov8" title="1"> {
                                auditText = append(auditText, "    installed: false")
                        }</span>
                }
        }
        <span class="cov8" title="1">for _, line := range auditText </span><span class="cov8" title="1">{
                fmt.Println(line)
        }</span>
}

// Предварительная компиляция регулярных выражений для покраски вывода и их доступности в тестах
var (
        // Исключаем все до http:// (включительно) в начале строки
        trimHttpRegex = regexp.MustCompile(`^.*http://|([^a-zA-Z0-9:/._?&amp;=+-].*)$`)
        // И после любого символа, который не может содержать в себе url
        trimHttpsRegex = regexp.MustCompile(`^.*https://|([^a-zA-Z0-9:/._?&amp;=+-].*)$`)
        // Иключаем все до первого символа слэша (не включительно)
        trimPrefixPathRegex = regexp.MustCompile(`^[^/]+`)
        // Исключаем все после первого символа, который не должен (но может) содержаться в пути
        trimPostfixPathRegex = regexp.MustCompile(`[=:'"(){}\[\]]+.*$`)
        // Байты или числа в шестнадцатеричном формате: 0x2 || 0xc0000001
        hexByteRegex = regexp.MustCompile(`\b0x[0-9A-Fa-f]+\b`)
        // Date: YYYY-MM-DDTHH:MM:SS.MS+HH:MM
        dateTimeRegex = regexp.MustCompile(`\b(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?([\+\-]\d{2}:\d{2})?)\b`)
        // MAC address + Time: H:MM || HH:MM || HH:MM:SS || XX:XX:XX:XX || XX:XX:XX:XX:XX:XX || XX-XX-XX-XX-XX-XX || HH:MM:SS,XXX || HH:MM:SS.XXX || HH:MM:SS+03
        timeMacAddressRegex = regexp.MustCompile(`\b(?:\d{1,2}:\d{2}(:\d{2}([\.\,\+]\d{2,6})?)?|\b(?:[0-9A-Fa-f]{2}[\:\-]){5}[0-9A-Fa-f]{2}\b)\b`)
        // Date + IP address + version (1.0 || 1.0.7 || 1.0-build)
        dateIpAddressRegex = regexp.MustCompile(`\b(\d{1,2}[\-\.]\d{1,2}[\-\.]\d{4}|\d{4}[\-\.]\d{1,2}[\-\.]\d{1,2}|(?:\d{1,3}\.){3}\d{1,3}(?::\d+|\.\d+|/\d+)?|\d+\.\d+[\+\-\.\w]+|\d+\.\d+)\b`)
        // Date: DD-MM-YYYY || DD.MM.YYYY || YYYY-MM-DD || YYYY.MM.DD
        dateRegex = regexp.MustCompile(`\b(\d{1,2}[\-\.]\d{1,2}[\-\.]\d{4}|\d{4}[\-\.]\d{1,2}[\-.]\d{1,2})\b`)
        // IP: 255.255.255.255 || 255.255.255.255:443 || 255.255.255.255.443 || 255.255.255.255/24
        ipAddressRegex = regexp.MustCompile(`\b(?:\d{1,3}\.){3}\d{1,3}(?::\d+|\.\d+|/\d+)?\b`)
        // int%
        procRegex = regexp.MustCompile(`(\d+)%`)
        // Syslog UNIT
        syslogUnitRegex = regexp.MustCompile(`^[a-zA-Z-_.]+\[\d+\]:$`)
)

var g *gocui.Gui

func runGoCui(mock bool) <span class="cov8" title="1">{
        // Инициализация значений по умолчанию + компиляция регулярных выражений для покраски
        app := &amp;App{
                testMode:                     false,
                tailSpinMode:                 false,
                colorMode:                    true,
                startServices:                0, // начальная позиция списка юнитов
                selectedJournal:              0, // начальный индекс выбранного журнала
                startFiles:                   0,
                selectedFile:                 0,
                startDockerContainers:        0,
                selectedDockerContainer:      0,
                selectUnits:                  "services",  // "UNIT" || "USER_UNIT" || "kernel"
                selectPath:                   "/var/log/", // "/opt/", "/home/" или "/Users/" (для MacOS) + /root/
                selectContainerizationSystem: "docker",    // "podman" || kubernetes
                selectFilterMode:             "default",   // "fuzzy" || "regex"
                logViewCount:                 "200000",    // 5000-300000
                journalListFrameColor:        gocui.ColorDefault,
                fileSystemFrameColor:         gocui.ColorDefault,
                dockerFrameColor:             gocui.ColorDefault,
                autoScroll:                   true,
                trimHttpRegex:                trimHttpRegex,
                trimHttpsRegex:               trimHttpsRegex,
                trimPrefixPathRegex:          trimPrefixPathRegex,
                trimPostfixPathRegex:         trimPostfixPathRegex,
                hexByteRegex:                 hexByteRegex,
                dateTimeRegex:                dateTimeRegex,
                timeMacAddressRegex:          timeMacAddressRegex,
                dateIpAddressRegex:           dateIpAddressRegex,
                dateRegex:                    dateRegex,
                ipAddressRegex:               ipAddressRegex,
                procRegex:                    procRegex,
                syslogUnitRegex:              syslogUnitRegex,
                keybindingsEnabled:           true,
        }

        // Определяем используемую ОС (linux/darwin/*bsd/windows) и архитектуру
        app.getOS = runtime.GOOS
        app.getArch = runtime.GOARCH

        // Аргументы
        help := flag.Bool("help", false, "Show help")
        flag.BoolVar(help, "h", false, "Show help")
        version := flag.Bool("version", false, "Show version")
        flag.BoolVar(version, "v", false, "Show version")
        audit := flag.Bool("audit", false, "Show audit information")
        flag.BoolVar(audit, "a", false, "Show audit information")

        // Обработка аргументов
        flag.Parse()
        if *help </span><span class="cov0" title="0">{
                showHelp()
                os.Exit(0)
        }</span>
        <span class="cov8" title="1">if *version </span><span class="cov0" title="0">{
                app.showVersion()
                os.Exit(0)
        }</span>
        <span class="cov8" title="1">if *audit </span><span class="cov0" title="0">{
                app.showAudit()
                os.Exit(0)
        }</span>

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

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

        // Цветовая схема GUI
        g.FgColor = gocui.ColorDefault // поля всех окон и цвет текста
        g.BgColor = gocui.ColorDefault // фон

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

        // Выполняем layout для инициализации интерфейса
        <span class="cov8" title="1">if err := app.layout(g); err != nil </span><span class="cov0" title="0">{
                log.Panicln(err)
        }</span>

        // Определяем переменные и массивы для покраски вывода
        // Текущее имя хоста
        <span class="cov8" title="1">app.hostName, _ = os.Hostname()
        // Удаляем доменную часть, если она есть
        if strings.Contains(app.hostName, ".") </span><span class="cov0" title="0">{
                app.hostName = strings.Split(app.hostName, ".")[0]
        }</span>
        // Текущее имя пользователя
        <span class="cov8" title="1">currentUser, _ := user.Current()
        app.userName = currentUser.Username
        // Удаляем доменную часть, если она есть
        if strings.Contains(app.userName, "\\") </span><span class="cov8" title="1">{
                app.userName = strings.Split(app.userName, "\\")[1]
        }</span>
        // Определяем букву системного диска с установленной ОС Windows
        <span class="cov8" title="1">app.systemDisk = os.Getenv("SystemDrive")
        if len(app.systemDisk) &gt;= 1 </span><span class="cov8" title="1">{
                app.systemDisk = string(app.systemDisk[0])
        }</span> else<span class="cov8" title="1"> {
                app.systemDisk = "C"
        }</span>
        // Имена пользователей
        <span class="cov8" title="1">passwd, _ := os.Open("/etc/passwd")
        scanner := bufio.NewScanner(passwd)
        for scanner.Scan() </span><span class="cov8" title="1">{
                line := scanner.Text()
                userName := strings.Split(line, ":")
                if len(userName) &gt; 0 </span><span class="cov8" title="1">{
                        app.userNameArray = append(app.userNameArray, userName[0])
                }</span>
        }
        // Список корневых каталогов (ls -d /*/) с приставкой "/"
        <span class="cov8" title="1">files, _ := os.ReadDir("/")
        for _, file := range files </span><span class="cov8" title="1">{
                if file.IsDir() </span><span class="cov8" title="1">{
                        app.rootDirArray = append(app.rootDirArray, "/"+file.Name())
                }</span>
        }

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

        // Filesystem
        <span class="cov8" title="1">if v, err := g.View("varLogs"); err == nil </span><span class="cov8" title="1">{
                _, viewHeight := v.Size()
                app.maxVisibleFiles = viewHeight
        }</span>

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

        // Docker
        <span class="cov8" title="1">if v, err := g.View("docker"); err == nil </span><span class="cov8" title="1">{
                _, viewHeight := v.Size()
                app.maxVisibleDockerContainers = viewHeight
        }</span>
        <span class="cov8" title="1">app.loadDockerContainer(app.selectContainerizationSystem)

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

        // Горутина для автоматического обновления вывода журнала каждые 5 секунд
        <span class="cov8" title="1">go func() </span><span class="cov8" title="1">{
                app.updateLogOutput(5)
        }</span>()

        // Горутина для отслеживания изменений размера окна
        <span class="cov8" title="1">go func() </span><span class="cov8" title="1">{
                app.updateWindowSize(1)
        }</span>()

        // Запус GUI
        <span class="cov8" title="1">if err := g.MainLoop(); err != nil &amp;&amp; !errors.Is(err, gocui.ErrQuit) </span><span class="cov0" title="0">{
                log.Panicln(err)
        }</span>
}

func main() <span class="cov0" title="0">{
        runGoCui(false)
}</span>

// Структура интерфейса окон GUI
func (app *App) layout(g *gocui.Gui) error <span class="cov8" title="1">{
        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 </span><span class="cov8" title="1">{
                if !errors.Is(err, gocui.ErrUnknownView) </span><span class="cov0" title="0">{
                        return err
                }</span>
                <span class="cov8" title="1">v.Title = "Filtering lists"
                v.Editable = true
                v.Wrap = true
                v.FrameColor = gocui.ColorGreen // Цвет границ окна
                v.TitleColor = gocui.ColorGreen // Цвет заголовка
                v.Editor = app.createFilterEditor("lists")</span>
        }

        // Окно для отображения списка доступных журналов (UNIT)
        // Размеры окна: заголовок, отступ слева, отступ сверху, ширина, высота, 5-й параметр из форка для продолжение окна (2)
        <span class="cov8" title="1">if v, err := g.SetView("services", 0, inputHeight, leftPanelWidth-1, inputHeight+panelHeight-1, 0); err != nil </span><span class="cov8" title="1">{
                if !errors.Is(err, gocui.ErrUnknownView) </span><span class="cov0" title="0">{
                        return err
                }</span>
                <span class="cov8" title="1">v.Title = " &lt; Unit list (0) &gt; " // заголовок окна
                v.Highlight = true              // выделение активного элемента в списке
                v.Wrap = false                  // отключаем перенос строк
                v.Autoscroll = true             // включаем автопрокрутку
                // Цветовая схема из форка awesome-gocui/gocui
                v.SelBgColor = gocui.ColorGreen // Цвет фона при выборе в списке
                v.SelFgColor = gocui.ColorBlack // Цвет текста
                app.updateServicesList()</span>        // выводим список журналов в это окно
        }

        // Окно для списка логов из файловой системы
        <span class="cov8" title="1">if v, err := g.SetView("varLogs", 0, inputHeight+panelHeight, leftPanelWidth-1, inputHeight+2*panelHeight-1, 0); err != nil </span><span class="cov8" title="1">{
                if !errors.Is(err, gocui.ErrUnknownView) </span><span class="cov0" title="0">{
                        return err
                }</span>
                <span class="cov8" title="1">v.Title = " &lt; System var logs (0) &gt; "
                v.Highlight = true
                v.Wrap = false
                v.Autoscroll = true
                v.SelBgColor = gocui.ColorGreen
                v.SelFgColor = gocui.ColorBlack
                app.updateLogsList()</span>
        }

        // Окно для списка контейнеров Docker и Podman
        <span class="cov8" title="1">if v, err := g.SetView("docker", 0, inputHeight+2*panelHeight, leftPanelWidth-1, maxY-1, 0); err != nil </span><span class="cov8" title="1">{
                if !errors.Is(err, gocui.ErrUnknownView) </span><span class="cov0" title="0">{
                        return err
                }</span>
                <span class="cov8" title="1">v.Title = " &lt; Docker containers (0) &gt; "
                v.Highlight = true
                v.Wrap = false
                v.Autoscroll = true
                v.SelBgColor = gocui.ColorGreen
                v.SelFgColor = gocui.ColorBlack</span>
        }

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

        // Интерфейс скролла в окне вывода лога (maxX-3 ширина окна - отступ слева)
        <span class="cov8" title="1">if v, err := g.SetView("scrollLogs", maxX-3, 3, maxX-1, maxY-1, 0); err != nil </span><span class="cov8" title="1">{
                if !errors.Is(err, gocui.ErrUnknownView) </span><span class="cov0" title="0">{
                        return err
                }</span>
                <span class="cov8" title="1">v.Wrap = true
                v.Autoscroll = false
                // Цвет текста (зеленый)
                v.FgColor = gocui.ColorGreen
                // Заполняем окно стрелками
                _, viewHeight := v.Size()
                fmt.Fprintln(v, "▲")
                for i := 1; i &lt; viewHeight-1; i++ </span><span class="cov8" title="1">{
                        fmt.Fprintln(v, " ")
                }</span>
                <span class="cov8" title="1">fmt.Fprintln(v, "▼")</span>
        }

        // Окно для вывода записей выбранного журнала (maxX-2 для отступа скролла и 8 для продолжения углов)
        <span class="cov8" title="1">if v, err := g.SetView("logs", leftPanelWidth+1, 3, maxX-1-2, maxY-1, 8); err != nil </span><span class="cov8" title="1">{
                if !errors.Is(err, gocui.ErrUnknownView) </span><span class="cov0" title="0">{
                        return err
                }</span>
                <span class="cov8" title="1">v.Title = "Logs"
                v.Wrap = true
                v.Autoscroll = false</span>
        }

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

        <span class="cov8" title="1">return nil</span>
}

// ---------------------------------------- journalctl/Windows Event Logs ----------------------------------------

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

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

// Функция для загрузки списка журналов служб или загрузок системы из journalctl
func (app *App) loadServices(journalName string) <span class="cov8" title="1">{
        app.journals = nil
        // Проверка, что в системе установлен/поддерживается утилита journalctl
        checkJournald := exec.Command("journalctl", "--version")
        // Проверяем на ошибки (очищаем список служб, отключаем курсор и выводим ошибку)
        _, err := checkJournald.Output()
        if err != nil &amp;&amp; !app.testMode </span><span class="cov0" title="0">{
                vError, _ := app.gui.View("services")
                vError.Clear()
                app.journalListFrameColor = gocui.ColorRed
                vError.FrameColor = app.journalListFrameColor
                vError.Highlight = false
                fmt.Fprintln(vError, "\033[31msystemd-journald not supported\033[0m")
                return
        }</span>
        <span class="cov8" title="1">if err != nil &amp;&amp; app.testMode </span><span class="cov0" title="0">{
                log.Print("Error: systemd-journald not supported")
        }</span>
        <span class="cov8" title="1">switch </span>{
        case journalName == "services":<span class="cov8" title="1">
                // Получаем список всех юнитов в системе через systemctl в формате JSON
                unitsList := exec.Command("systemctl", "list-units", "--all", "--plain", "--no-legend", "--no-pager", "--output=json") // "--type=service"
                output, err := unitsList.Output()
                if !app.testMode </span><span class="cov8" title="1">{
                        if err != nil </span><span class="cov0" title="0">{
                                vError, _ := app.gui.View("services")
                                vError.Clear()
                                app.journalListFrameColor = gocui.ColorRed
                                vError.FrameColor = app.journalListFrameColor
                                vError.Highlight = false
                                fmt.Fprintln(vError, "\033[31mAccess denied in systemd via systemctl\033[0m")
                                return
                        }</span>
                        <span class="cov8" title="1">v, _ := app.gui.View("services")
                        app.journalListFrameColor = gocui.ColorDefault
                        if v.FrameColor != gocui.ColorDefault </span><span class="cov8" title="1">{
                                v.FrameColor = gocui.ColorGreen
                        }</span>
                        <span class="cov8" title="1">v.Highlight = true</span>
                }
                <span class="cov8" title="1">if err != nil &amp;&amp; app.testMode </span><span class="cov0" title="0">{
                        log.Print("Error: access denied in systemd via systemctl")
                }</span>
                // Чтение данных в формате JSON
                <span class="cov8" title="1">var units []map[string]interface{}
                err = json.Unmarshal(output, &amp;units)
                // Если ошибка JSON, создаем массив вручную
                if err != nil </span><span class="cov0" title="0">{
                        lines := strings.Split(string(output), "\n")
                        for _, line := range lines </span><span class="cov0" title="0">{
                                // Разбиваем строку на поля (эквивалентно: awk '{print $1,$3,$4}')
                                fields := strings.Fields(line)
                                // Пропускаем строки с недостаточным количеством полей
                                if len(fields) &lt; 3 </span><span class="cov0" title="0">{
                                        continue</span>
                                }
                                // Заполняем временный массив из строки
                                <span class="cov0" title="0">unit := map[string]interface{}{
                                        "unit":   fields[0],
                                        "active": fields[2],
                                        "sub":    fields[3],
                                }
                                // Добавляем временный массив строки в основной массив
                                units = append(units, unit)</span>
                        }
                }
                <span class="cov8" title="1">serviceMap := make(map[string]bool)
                // Обработка записей
                for _, unit := range units </span><span class="cov8" title="1">{
                        // Извлечение данных в формате JSON и проверка статуса для покраски
                        unitName, _ := unit["unit"].(string)
                        active, _ := unit["active"].(string)
                        if active == "active" </span><span class="cov8" title="1">{
                                active = "\033[32m" + active + "\033[0m"
                        }</span> else<span class="cov8" title="1"> {
                                active = "\033[31m" + active + "\033[0m"
                        }</span>
                        <span class="cov8" title="1">sub, _ := unit["sub"].(string)
                        if sub == "exited" || sub == "dead" </span><span class="cov8" title="1">{
                                sub = "\033[31m" + sub + "\033[0m"
                        }</span> else<span class="cov8" title="1"> {
                                sub = "\033[32m" + sub + "\033[0m"
                        }</span>
                        <span class="cov8" title="1">name := unitName + " (" + active + "/" + sub + ")"
                        bootID := unitName
                        // Уникальный ключ для проверки
                        uniqueKey := name + ":" + bootID
                        if !serviceMap[uniqueKey] </span><span class="cov8" title="1">{
                                serviceMap[uniqueKey] = true
                                // Добавление записи в массив
                                app.journals = append(app.journals, Journal{
                                        name:    name,
                                        boot_id: bootID,
                                })
                        }</span>
                }
        case journalName == "kernel":<span class="cov8" title="1">
                // Получаем список загрузок системы
                bootCmd := exec.Command("journalctl", "--list-boots", "-o", "json")
                bootOutput, err := bootCmd.Output()
                if !app.testMode </span><span class="cov8" title="1">{
                        if err != nil </span><span class="cov0" title="0">{
                                vError, _ := app.gui.View("services")
                                vError.Clear()
                                app.journalListFrameColor = gocui.ColorRed
                                vError.FrameColor = app.journalListFrameColor
                                vError.Highlight = false
                                fmt.Fprintln(vError, "\033[31mError getting boot information from journald\033[0m")
                                return
                        }</span> else<span class="cov8" title="1"> {
                                vError, _ := app.gui.View("services")
                                app.journalListFrameColor = gocui.ColorDefault
                                if vError.FrameColor != gocui.ColorDefault </span><span class="cov8" title="1">{
                                        vError.FrameColor = gocui.ColorGreen
                                }</span>
                                <span class="cov8" title="1">vError.Highlight = true</span>
                        }
                }
                <span class="cov8" title="1">if err != nil &amp;&amp; app.testMode </span><span class="cov0" title="0">{
                        log.Print("Error: getting boot information from journald")
                }</span>
                // Структура для парсинга JSON
                <span class="cov8" title="1">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, &amp;bootRecords)
                // Если JSON невалидный или режим тестирования (Ubuntu 20.04 не поддерживает вывод в формате json)
                if err != nil || app.testMode </span><span class="cov8" title="1">{
                        // Парсим вывод построчно
                        lines := strings.Split(string(bootOutput), "\n")
                        for _, line := range lines </span><span class="cov8" title="1">{
                                // Разбиваем строку на массив
                                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) &gt;= 8 </span><span class="cov0" title="0">{
                                        bootId := wordsArray[1]
                                        // Забираем дату, проверяем и изменяем формат
                                        var parseDate []string
                                        var bootDate string
                                        parseDate = strings.Split(wordsArray[3], "-")
                                        if len(parseDate) == 3 </span><span class="cov0" title="0">{
                                                bootDate = fmt.Sprintf("%s.%s.%s", parseDate[2], parseDate[1], parseDate[0])
                                        }</span> else<span class="cov0" title="0"> {
                                                continue</span>
                                        }
                                        <span class="cov0" title="0">var stopDate string
                                        parseDate = strings.Split(wordsArray[6], "-")
                                        if len(parseDate) == 3 </span><span class="cov0" title="0">{
                                                stopDate = fmt.Sprintf("%s.%s.%s", parseDate[2], parseDate[1], parseDate[0])
                                        }</span> else<span class="cov0" title="0"> {
                                                continue</span>
                                        }
                                        // Заполняем массив
                                        <span class="cov0" title="0">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,
                                        })</span>
                                }
                        }
                }
                <span class="cov8" title="1">if err == nil </span><span class="cov8" title="1">{
                        // Очищаем массив, если он был заполнен в режиме тестирования
                        app.journals = []Journal{}
                        // Добавляем информацию о загрузках в app.journals
                        for _, bootRecord := range bootRecords </span><span class="cov8" title="1">{
                                // Преобразуем наносекунды в секунды
                                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,
                                })
                        }</span>
                }
                // Сортируем по второй дате
                <span class="cov8" title="1">sort.Slice(app.journals, func(i, j int) bool </span><span class="cov8" title="1">{
                        date1 := parseDateFromName(app.journals[i].name)
                        date2 := parseDateFromName(app.journals[j].name)
                        // Сравниваем по второй дате в обратном порядке (After для сортировки по убыванию)
                        return date1.After(date2)
                }</span>)
        default:<span class="cov8" title="1">
                cmd := exec.Command("journalctl", "--no-pager", "-F", journalName)
                output, err := cmd.Output()
                if !app.testMode </span><span class="cov8" title="1">{
                        if err != nil </span><span class="cov0" title="0">{
                                vError, _ := app.gui.View("services")
                                vError.Clear()
                                app.journalListFrameColor = gocui.ColorRed
                                vError.FrameColor = app.journalListFrameColor
                                vError.Highlight = false
                                fmt.Fprintln(vError, "\033[31mError getting services from journald via journalctl\033[0m")
                                return
                        }</span> else<span class="cov8" title="1"> {
                                vError, _ := app.gui.View("services")
                                app.journalListFrameColor = gocui.ColorDefault
                                if vError.FrameColor != gocui.ColorDefault </span><span class="cov8" title="1">{
                                        vError.FrameColor = gocui.ColorGreen
                                }</span>
                                <span class="cov8" title="1">vError.Highlight = true</span>
                        }
                }
                <span class="cov8" title="1">if err != nil &amp;&amp; app.testMode </span><span class="cov0" title="0">{
                        log.Print("Error: getting services from journald via journalctl")
                }</span>
                // Создаем массив (хеш-таблица с доступом по ключу) для уникальных имен служб
                <span class="cov8" title="1">serviceMap := make(map[string]bool)
                scanner := bufio.NewScanner(strings.NewReader(string(output)))
                for scanner.Scan() </span><span class="cov8" title="1">{
                        serviceName := strings.TrimSpace(scanner.Text())
                        if serviceName != "" &amp;&amp; !serviceMap[serviceName] </span><span class="cov8" title="1">{
                                serviceMap[serviceName] = true
                                app.journals = append(app.journals, Journal{
                                        name:    serviceName,
                                        boot_id: "",
                                })
                        }</span>
                }
                // Сортируем список служб по алфавиту
                <span class="cov8" title="1">sort.Slice(app.journals, func(i, j int) bool </span><span class="cov8" title="1">{
                        return app.journals[i].name &lt; app.journals[j].name
                }</span>)
        }
        <span class="cov8" title="1">if !app.testMode </span><span class="cov8" title="1">{
                // Сохраняем неотфильтрованный список
                app.journalsNotFilter = app.journals
                // Применяем фильтр при загрузки и обновляем список служб в интерфейсе через updateServicesList() внутри функции
                app.applyFilterList()
        }</span>
}

// Функция для загрузки списка всех журналов событий Windows через PowerShell
func (app *App) loadWinEvents() <span class="cov8" title="1">{
        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")
        eventsJson, _ := cmd.Output()
        var events []map[string]interface{}
        _ = json.Unmarshal(eventsJson, &amp;events)
        for _, event := range events </span><span class="cov8" title="1">{
                // Извлечение названия журнала и количество записей
                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 </span><span class="cov8" title="1">{
                        LogView = "\033[33m" + LogViewSplit[0] + "\033[0m" + ": " + "\033[36m" + LogViewSplit[1] + "\033[0m"
                }</span> else<span class="cov8" title="1"> {
                        LogView = "\033[36m" + LogView + "\033[0m"
                }</span>
                <span class="cov8" title="1">LogView = LogView + " (" + RecordCountString + ")"
                app.journals = append(app.journals, Journal{
                        name:    LogView,
                        boot_id: LogName,
                })</span>
        }
        <span class="cov8" title="1">if !app.testMode </span><span class="cov8" title="1">{
                app.journalsNotFilter = app.journals
                app.applyFilterList()
        }</span>
}

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

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

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

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

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

// Функция для загрузки записей журнала выбранной службы через journalctl
// Второй параметр для обнолвения позиции делимитра нового вывода лога а также сброса автоскролл
func (app *App) loadJournalLogs(serviceName string, newUpdate bool) <span class="cov8" title="1">{
        var output []byte
        var err error
        selectUnits := app.selectUnits
        if newUpdate </span><span class="cov8" title="1">{
                app.lastSelectUnits = app.selectUnits
        }</span> else<span class="cov8" title="1"> {
                selectUnits = app.lastSelectUnits
        }</span>
        <span class="cov8" title="1">switch </span>{
        // Читаем журналы Windows
        case app.getOS == "windows":<span class="cov8" title="1">
                if !app.updateFile </span><span class="cov8" title="1">{
                        return
                }</span>
                // Отключаем чтение в горутине
                <span class="cov8" title="1">app.updateFile = false
                // Извлекаем полное имя события
                var eventName string
                for _, journal := range app.journals </span><span class="cov8" title="1">{
                        journalBootName := removeANSI(journal.name)
                        if journalBootName == serviceName </span><span class="cov8" title="1">{
                                eventName = journal.boot_id
                                break</span>
                        }
                }
                <span class="cov8" title="1">output = app.loadWinEventLog(eventName)
                if len(output) == 0 &amp;&amp; !app.testMode </span><span class="cov0" title="0">{
                        v, _ := app.gui.View("logs")
                        v.Clear()
                        return
                }</span>
                <span class="cov8" title="1">if len(output) == 0 &amp;&amp; app.testMode </span><span class="cov8" title="1">{
                        app.currentLogLines = []string{}
                        return
                }</span>
        // Читаем лог ядра загрузки системы
        case selectUnits == "kernel":<span class="cov8" title="1">
                var boot_id string
                for _, journal := range app.journals </span><span class="cov8" title="1">{
                        journalBootName := removeANSI(journal.name)
                        if journalBootName == serviceName </span><span class="cov8" title="1">{
                                boot_id = journal.boot_id
                                break</span>
                        }
                }
                // Сохраняем название для обновления вывода журнала при фильтрации списков
                <span class="cov8" title="1">if newUpdate </span><span class="cov8" title="1">{
                        app.lastBootId = boot_id
                }</span> else<span class="cov0" title="0"> {
                        boot_id = app.lastBootId
                }</span>
                <span class="cov8" title="1">cmd := exec.Command("journalctl", "-k", "-b", boot_id, "--no-pager", "-n", app.logViewCount)
                output, err = cmd.Output()
                if err != nil &amp;&amp; !app.testMode </span><span class="cov0" title="0">{
                        v, _ := app.gui.View("logs")
                        v.Clear()
                        fmt.Fprintln(v, "\033[31mError getting kernal logs:", err, "\033[0m")
                        return
                }</span>
                <span class="cov8" title="1">if err != nil &amp;&amp; app.testMode </span><span class="cov0" title="0">{
                        log.Print("Error: getting kernal logs. ", err)
                }</span>
                // Для юнитов systemd
        default:<span class="cov8" title="1">
                if selectUnits == "services" </span><span class="cov8" title="1">{
                        // Удаляем статусы с покраской из навзания
                        var ansiEscape = regexp.MustCompile(`\s\(.+\)`)
                        serviceName = ansiEscape.ReplaceAllString(serviceName, "")
                }</span>
                <span class="cov8" title="1">cmd := exec.Command("journalctl", "-u", serviceName, "--no-pager", "-n", app.logViewCount)
                output, err = cmd.Output()
                if err != nil &amp;&amp; !app.testMode </span><span class="cov0" title="0">{
                        v, _ := app.gui.View("logs")
                        v.Clear()
                        fmt.Fprintln(v, "\033[31mError getting journald logs:", err, "\033[0m")
                        return
                }</span>
                <span class="cov8" title="1">if err != nil &amp;&amp; app.testMode </span><span class="cov0" title="0">{
                        log.Print("Error: getting journald logs.  ", err)
                }</span>
        }
        // Сохраняем строки журнала в массив
        <span class="cov8" title="1">app.currentLogLines = strings.Split(string(output), "\n")
        if !app.testMode </span><span class="cov8" title="1">{
                app.updateDelimiter(newUpdate)
                // Очищаем поле ввода для фильтрации, что бы не применять фильтрацию к новому журналу
                // app.filterText = ""
                // Применяем текущий фильтр к записям для обновления вывода
                app.applyFilter(false)
        }</span>
}

// Функция для чтения и парсинга содержимого события Windows через PowerShell (возвращяет текст в формате байт и текст ошибки)
// func (app *App) loadWinEventLog(eventName string) (output []byte) {
//         // Запуск во внешнем процессе PowerShell 5
//         cmd := exec.Command("powershell", "-Command",
//                 "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8;"+
//                         "Get-WinEvent -LogName "+eventName+" -MaxEvents 5000 | "+
//                         "Select-Object TimeCreated,Id,LevelDisplayName,Message | "+
//                         "Sort-Object TimeCreated | "+
//                         "ConvertTo-Json")
//         eventsJson, _ := cmd.Output()
//         var eventMessage []string
//         var eventStrings []map[string]interface{}
//         _ = json.Unmarshal(eventsJson, &amp;eventStrings)
//         for _, eventString := range eventStrings {
//                 // Извлекаем метку времени из json
//                 TimeCreated, _ := eventString["TimeCreated"].(string)
//                 // Извлекаем метку времени из строки
//                 parts := strings.Split(TimeCreated, "(")
//                 timestampString := strings.Split(parts[1], ")")[0]
//                 // Преобразуем строку в целое число (timestamp)
//                 timestamp, _ := strconv.Atoi(timestampString)
//                 // Преобразуем в Unix-формат (секунды и наносекунды)
//                 dateTime := time.Unix(int64(timestamp/1000), int64((timestamp%1000)*1000000)) // Миллисекунды -&gt; наносекунды
//                 // Извлекаем остальные данные из json
//                 LogId, _ := eventString["Id"].(float64)
//                 LogIdInt := int(LogId)
//                 LogIdString := strconv.Itoa(LogIdInt)
//                 LevelDisplayName, _ := eventString["LevelDisplayName"].(string)
//                 Message, _ := eventString["Message"].(string)
//                 // Удаляем встроенные переносы строки
//                 messageReplace := strings.ReplaceAll(Message, "\r\n", "")
//                 // Формируем строку и заполняем временный массив
//                 mess := dateTime.Format("02.01.2006 15:04:05") + " " + LevelDisplayName + " (" + LogIdString + "): " + messageReplace
//                 eventMessage = append(eventMessage, mess)
//         }
//         // Собираем все строки в одну и возвращяем байты
//         fullMessage := strings.Join(eventMessage, "\n")
//         return []byte(fullMessage)
// }

// Функция для чтения и парсинга содержимого события Windows через wevtutil
func (app *App) loadWinEventLog(eventName string) (output []byte) <span class="cov8" title="1">{
        cmd := exec.Command("cmd", "/C",
                "chcp 65001 &amp;&amp;"+
                        "wevtutil qe "+eventName+" /f:text -l:en")
        eventData, _ := cmd.Output()
        // Декодирование вывода из Windows-1251 в UTF-8
        decoder := charmap.Windows1251.NewDecoder()
        decodeEventData, decodeErr := decoder.Bytes(eventData)
        if decodeErr == nil </span><span class="cov8" title="1">{
                eventData = decodeEventData
        }</span>
        // Разбиваем вывод на массив
        <span class="cov8" title="1">eventStrings := strings.Split(string(eventData), "Event[")
        var eventMessage []string
        for _, eventString := range eventStrings </span><span class="cov8" title="1">{
                var dateTime, eventID, level, description string
                // Разбиваем элемент массива на строки
                lines := strings.Split(eventString, "\n")
                // Флаг для обработки последней строки Description с содержимым Message
                isDescription := false
                for _, line := range lines </span><span class="cov8" title="1">{
                        // Удаляем проблемы во всех строках
                        trimmedLine := strings.TrimSpace(line)
                        switch </span>{
                        // Обновляем формат даты
                        case strings.HasPrefix(trimmedLine, "Date:"):<span class="cov8" title="1">
                                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])</span>
                        case strings.HasPrefix(trimmedLine, "Event ID:"):<span class="cov8" title="1">
                                eventID = strings.ReplaceAll(trimmedLine, "Event ID: ", "")</span>
                        case strings.HasPrefix(trimmedLine, "Level:"):<span class="cov8" title="1">
                                level = strings.ReplaceAll(trimmedLine, "Level: ", "")</span>
                        case strings.HasPrefix(trimmedLine, "Description:"):<span class="cov8" title="1">
                                // Фиксируем и пропускаем Description
                                isDescription = true</span>
                        case isDescription:<span class="cov8" title="1">
                                // Добавляем до конца текущего массива все не пустые строки
                                if trimmedLine != "" </span><span class="cov8" title="1">{
                                        description += "\n" + trimmedLine
                                }</span>
                        }
                }
                <span class="cov8" title="1">if dateTime != "" &amp;&amp; eventID != "" &amp;&amp; level != "" &amp;&amp; description != "" </span><span class="cov8" title="1">{
                        eventMessage = append(eventMessage, fmt.Sprintf("%s %s (%s): %s", dateTime, level, eventID, strings.TrimSpace(description)))
                }</span>
        }
        <span class="cov8" title="1">fullMessage := strings.Join(eventMessage, "\n")
        return []byte(fullMessage)</span>
}

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

func (app *App) loadFiles(logPath string) <span class="cov8" title="1">{
        app.logfiles = nil // сбрасываем (очищаем) массив перед загрузкой новых журналов
        var output []byte
        switch </span>{
        case logPath == "descriptor":<span class="cov8" title="1">
                // n - имя файла (путь)
                // c - имя команды (процесса)
                cmd := exec.Command("lsof", "-Fn")
                // Подавить вывод ошибок при отсутствиее прав доступа (opendir: Permission denied)
                cmd.Stderr = nil
                output, _ = cmd.Output()
                // Разбиваем вывод на строки
                files := strings.Split(strings.TrimSpace(string(output)), "\n")
                // Если список файлов пустой, возвращаем ошибку Permission denied
                if !app.testMode </span><span class="cov8" title="1">{
                        if len(files) == 0 || (len(files) == 1 &amp;&amp; files[0] == "") </span><span class="cov0" title="0">{
                                vError, _ := app.gui.View("varLogs")
                                vError.Clear()
                                // Меняем цвет окна на красный
                                app.fileSystemFrameColor = gocui.ColorRed
                                vError.FrameColor = app.fileSystemFrameColor
                                // Отключаем курсор и выводим сообщение об ошибке
                                vError.Highlight = false
                                fmt.Fprintln(vError, "\033[31mPermission denied (files not found)\033[0m")
                                return
                        }</span> else<span class="cov8" title="1"> {
                                vError, _ := app.gui.View("varLogs")
                                app.fileSystemFrameColor = gocui.ColorDefault
                                if vError.FrameColor != gocui.ColorDefault </span><span class="cov8" title="1">{
                                        vError.FrameColor = gocui.ColorGreen
                                }</span>
                                <span class="cov8" title="1">vError.Highlight = true</span>
                        }
                } else<span class="cov8" title="1"> {
                        if len(files) == 0 || (len(files) == 1 &amp;&amp; files[0] == "") </span><span class="cov0" title="0">{
                                log.Print("Error: permission denied (files not found from descriptor)")
                        }</span>
                }
                // Очищаем массив перед добавлением отфильтрованных файлов
                <span class="cov8" title="1">output = []byte{}
                // Фильтруем строки, которые заканчиваются на ".log" и удаляем префикс (имя файла)
                for _, file := range files </span><span class="cov8" title="1">{
                        if strings.HasSuffix(file, ".log") </span><span class="cov8" title="1">{
                                file = strings.TrimPrefix(file, "n")
                                output = append(output, []byte(file+"\n")...)
                        }</span>
                }
        case logPath == "/var/log/":<span class="cov8" title="1">
                var cmd *exec.Cmd
                // Загрузка системных журналов для MacOS
                if app.getOS == "darwin" </span><span class="cov0" title="0">{
                        cmd = exec.Command(
                                "find", 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",
                        )
                }</span> else<span class="cov8" title="1"> {
                        // Загрузка системных журналов для Linux: все файлы, которые содержат log в расширение или названии (архивы включительно), а также расширение с цифрой (архивные) и pcap/pcapng
                        cmd = exec.Command(
                                "find", 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",
                        )
                }</span>
                <span class="cov8" title="1">output, _ = cmd.Output()
                // Преобразуем вывод команды в строку и делим на массив строк
                files := strings.Split(strings.TrimSpace(string(output)), "\n")
                // Если список файлов пустой, возвращаем ошибку Permission denied
                if !app.testMode </span><span class="cov8" title="1">{
                        if len(files) == 0 || (len(files) == 1 &amp;&amp; files[0] == "") </span><span class="cov0" title="0">{
                                vError, _ := app.gui.View("varLogs")
                                vError.Clear()
                                // Меняем цвет окна на красный
                                app.fileSystemFrameColor = gocui.ColorRed
                                vError.FrameColor = app.fileSystemFrameColor
                                // Отключаем курсор и выводим сообщение об ошибке
                                vError.Highlight = false
                                fmt.Fprintln(vError, "\033[31mPermission denied (files not found)\033[0m")
                                return
                        }</span> else<span class="cov8" title="1"> {
                                vError, _ := app.gui.View("varLogs")
                                app.fileSystemFrameColor = gocui.ColorDefault
                                if vError.FrameColor != gocui.ColorDefault </span><span class="cov8" title="1">{
                                        vError.FrameColor = gocui.ColorGreen
                                }</span>
                                <span class="cov8" title="1">vError.Highlight = true</span>
                        }
                } else<span class="cov8" title="1"> {
                        if len(files) == 0 || (len(files) == 1 &amp;&amp; files[0] == "") </span><span class="cov0" title="0">{
                                log.Print("Error: files not found in /var/log")
                        }</span>
                }
                // Добавляем пути по умолчанию для /var/log
                <span class="cov8" title="1">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 </span><span class="cov8" title="1">{
                        output = append([]byte(path), output...)
                }</span>
        case logPath == "/opt/":<span class="cov8" title="1">
                var cmd *exec.Cmd
                cmd = exec.Command(
                        "find", logPath,
                        "-type", "f",
                        "-name", "*.log", "-o",
                        "-name", "*.log.*",
                )
                output, _ = cmd.Output()
                files := strings.Split(strings.TrimSpace(string(output)), "\n")
                if !app.testMode </span><span class="cov8" title="1">{
                        if len(files) == 0 || (len(files) == 1 &amp;&amp; files[0] == "") </span><span class="cov0" title="0">{
                                vError, _ := app.gui.View("varLogs")
                                vError.Clear()
                                // Меняем цвет окна на красный
                                app.fileSystemFrameColor = gocui.ColorRed
                                vError.FrameColor = app.fileSystemFrameColor
                                // Отключаем курсор и выводим сообщение об ошибке
                                vError.Highlight = false
                                fmt.Fprintln(vError, "\033[31mFiles not found\033[0m")
                                return
                        }</span> else<span class="cov8" title="1"> {
                                vError, _ := app.gui.View("varLogs")
                                app.fileSystemFrameColor = gocui.ColorDefault
                                if vError.FrameColor != gocui.ColorDefault </span><span class="cov8" title="1">{
                                        vError.FrameColor = gocui.ColorGreen
                                }</span>
                                <span class="cov8" title="1">vError.Highlight = true</span>
                        }
                } else<span class="cov8" title="1"> {
                        if len(files) == 0 || (len(files) == 1 &amp;&amp; files[0] == "") </span><span class="cov0" title="0">{
                                log.Print("Error: files not found in /opt/")
                        }</span>
                }
        default:<span class="cov8" title="1">
                // Домашние каталоги пользователей: /home/ для Linux и /Users/ для MacOS
                if app.getOS == "darwin" </span><span class="cov0" title="0">{
                        logPath = "/Users/"
                }</span>
                // Ищем файлы с помощью системной утилиты find
                <span class="cov8" title="1">cmd := exec.Command(
                        "find", logPath,
                        "-type", "d",
                        "(",
                        "-name", "Library", "-o",
                        "-name", "Pictures", "-o",
                        "-name", "Movies", "-o",
                        "-name", "Music", "-o",
                        "-name", ".Trash", "-o",
                        "-name", ".cache",
                        ")",
                        "-prune", "-o",
                        "-type", "f",
                        "(",
                        "-name", "*.log", "-o",
                        "-name", "*.asl", "-o",
                        "-name", "*.pcap", "-o",
                        "-name", "*.pcap.gz", "-o",
                        "-name", "*.pcapng", "-o",
                        "-name", "*.pcapng.gz",
                        ")",
                )
                output, _ = cmd.Output()
                files := strings.Split(strings.TrimSpace(string(output)), "\n")
                if !app.testMode </span><span class="cov8" title="1">{
                        if len(files) == 0 || (len(files) == 1 &amp;&amp; files[0] == "") </span><span class="cov0" title="0">{
                                vError, _ := app.gui.View("varLogs")
                                vError.Clear()
                                vError.Highlight = false
                                fmt.Fprintln(vError, "\033[32mFiles not found\033[0m")
                                return
                        }</span> else<span class="cov8" title="1"> {
                                vError, _ := app.gui.View("varLogs")
                                app.fileSystemFrameColor = gocui.ColorDefault
                                if vError.FrameColor != gocui.ColorDefault </span><span class="cov8" title="1">{
                                        vError.FrameColor = gocui.ColorGreen
                                }</span>
                                <span class="cov8" title="1">vError.Highlight = true</span>
                        }
                } else<span class="cov8" title="1"> {
                        if len(files) == 0 || (len(files) == 1 &amp;&amp; files[0] == "") </span><span class="cov0" title="0">{
                                log.Print("Error: files not found in home directories")
                        }</span>
                }
                // Получаем содержимое файлов из домашнего каталога пользователя root
                <span class="cov8" title="1">cmdRootDir := exec.Command(
                        "find", "/root/",
                        "-type", "f",
                        "-name", "*.log", "-o",
                        "-name", "*.pcap", "-o",
                        "-name", "*.pcap.gz", "-o",
                        "-name", "*.pcapng", "-o",
                        "-name", "*.pcapng.gz",
                )
                outputRootDir, err := cmdRootDir.Output()
                // Добавляем содержимое директории /root/ в общий массив, если есть доступ
                if err == nil </span><span class="cov8" title="1">{
                        output = append(output, outputRootDir...)
                }</span>
                <span class="cov8" title="1">if app.fileSystemFrameColor == gocui.ColorRed &amp;&amp; !app.testMode </span><span class="cov0" title="0">{
                        vError, _ := app.gui.View("varLogs")
                        app.fileSystemFrameColor = gocui.ColorDefault
                        if vError.FrameColor != gocui.ColorDefault </span><span class="cov0" title="0">{
                                vError.FrameColor = gocui.ColorGreen
                        }</span>
                        <span class="cov0" title="0">vError.Highlight = true</span>
                }
        }
        <span class="cov8" title="1">serviceMap := make(map[string]bool)
        scanner := bufio.NewScanner(strings.NewReader(string(output)))
        for scanner.Scan() </span><span class="cov8" title="1">{
                // Получаем строку полного пути
                logFullPath := scanner.Text()
                // Удаляем префикс пути и расширение файла в конце
                logName := logFullPath
                if logPath != "descriptor" </span><span class="cov8" title="1">{
                        logName = strings.TrimPrefix(logFullPath, logPath)
                }</span>
                <span class="cov8" title="1">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/" </span><span class="cov8" title="1">{
                        // Разбиваем строку на слова
                        words := strings.Fields(logName)
                        // Берем первое и последнее слово
                        firstWord := words[0]
                        lastWord := words[len(words)-1]
                        logName = "\x1b[0;33m" + firstWord + "\033[0m" + ": " + lastWord
                }</span>
                // Получаем информацию о файле
                // cmd := exec.Command("bash", "-c", "stat --format='%y' /var/log/apache2/access.log | awk '{print $1}' | awk -F- '{print $3\".\"$2\".\"$1}'")
                <span class="cov8" title="1">fileInfo, err := os.Stat(logFullPath)
                if err != nil </span><span class="cov8" title="1">{
                        // Пропускаем файл, если к нему нет доступа (актуально для статических файлов из logPath)
                        continue</span>
                }
                // Проверяем, что файл не пустой
                <span class="cov8" title="1">if fileInfo.Size() == 0 </span><span class="cov8" title="1">{
                        // Пропускаем пустой файл
                        continue</span>
                }
                // Получаем дату изменения
                <span class="cov8" title="1">modTime := fileInfo.ModTime()
                // Форматирование даты в формат DD.MM.YYYY
                formattedDate := modTime.Format("02.01.2006")
                // Проверяем, что полного пути до файла еще нет в списке
                if logName != "" &amp;&amp; !serviceMap[logFullPath] </span><span class="cov8" title="1">{
                        // Добавляем путь в массив для проверки уникальных путей
                        serviceMap[logFullPath] = true
                        // Получаем имя процесса для файла дескриптора
                        if logPath == "descriptor" </span><span class="cov8" title="1">{
                                cmd := exec.Command("lsof", "-Fc", logFullPath)
                                cmd.Stderr = nil
                                outputLsof, _ := cmd.Output()
                                processLines := strings.Split(strings.TrimSpace(string(outputLsof)), "\n")
                                // Ищем строку, которая содержит имя процесса (только первый процесс)
                                for _, line := range processLines </span><span class="cov8" title="1">{
                                        if strings.HasPrefix(line, "c") </span><span class="cov8" title="1">{
                                                // Удаляем префикс
                                                processName := line[1:]
                                                logName = "\x1b[0;33m" + processName + "\033[0m" + ": " + logName
                                                break</span>
                                        }
                                }
                        }
                        // Добавляем в список
                        <span class="cov8" title="1">app.logfiles = append(app.logfiles, Logfile{
                                name: "[" + "\033[34m" + formattedDate + "\033[0m" + "] " + logName,
                                path: logFullPath,
                        })</span>
                }
        }
        // Сортируем по дате
        <span class="cov8" title="1">sort.Slice(app.logfiles, func(i, j int) bool </span><span class="cov8" title="1">{
                // Извлечение дат из имени
                layout := "02.01.2006"
                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)
        }</span>)
        <span class="cov8" title="1">if !app.testMode </span><span class="cov8" title="1">{
                app.logfilesNotFilter = app.logfiles
                app.applyFilterList()
        }</span>
}

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

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

func (app *App) updateLogsList() <span class="cov8" title="1">{
        v, err := app.gui.View("varLogs")
        if err != nil </span><span class="cov0" title="0">{
                return
        }</span>
        <span class="cov8" title="1">v.Clear()
        visibleEnd := app.startFiles + app.maxVisibleFiles
        if visibleEnd &gt; len(app.logfiles) </span><span class="cov8" title="1">{
                visibleEnd = len(app.logfiles)
        }</span>
        <span class="cov8" title="1">for i := app.startFiles; i &lt; visibleEnd; i++ </span><span class="cov8" title="1">{
                fmt.Fprintln(v, app.logfiles[i].name)
        }</span>
}

func (app *App) nextFileName(v *gocui.View, step int) error <span class="cov8" title="1">{
        _, viewHeight := v.Size()
        app.maxVisibleFiles = viewHeight
        if len(app.logfiles) == 0 </span><span class="cov8" title="1">{
                return nil
        }</span>
        <span class="cov8" title="1">if app.selectedFile &lt; len(app.logfiles)-1 </span><span class="cov8" title="1">{
                app.selectedFile += step
                if app.selectedFile &gt;= len(app.logfiles) </span><span class="cov8" title="1">{
                        app.selectedFile = len(app.logfiles) - 1
                }</span>
                <span class="cov8" title="1">if app.selectedFile &gt;= app.startFiles+app.maxVisibleFiles </span><span class="cov8" title="1">{
                        app.startFiles += step
                        if app.startFiles &gt; len(app.logfiles)-app.maxVisibleFiles </span><span class="cov8" title="1">{
                                app.startFiles = len(app.logfiles) - app.maxVisibleFiles
                        }</span>
                        <span class="cov8" title="1">app.updateLogsList()</span>
                }
                <span class="cov8" title="1">if app.selectedFile &lt; app.startFiles+app.maxVisibleFiles </span><span class="cov8" title="1">{
                        return app.selectFileByIndex(app.selectedFile - app.startFiles)
                }</span>
        }
        <span class="cov0" title="0">return nil</span>
}

func (app *App) prevFileName(v *gocui.View, step int) error <span class="cov8" title="1">{
        _, viewHeight := v.Size()
        app.maxVisibleFiles = viewHeight
        if len(app.logfiles) == 0 </span><span class="cov8" title="1">{
                return nil
        }</span>
        <span class="cov8" title="1">if app.selectedFile &gt; 0 </span><span class="cov8" title="1">{
                app.selectedFile -= step
                if app.selectedFile &lt; 0 </span><span class="cov8" title="1">{
                        app.selectedFile = 0
                }</span>
                <span class="cov8" title="1">if app.selectedFile &lt; app.startFiles </span><span class="cov8" title="1">{
                        app.startFiles -= step
                        if app.startFiles &lt; 0 </span><span class="cov8" title="1">{
                                app.startFiles = 0
                        }</span>
                        <span class="cov8" title="1">app.updateLogsList()</span>
                }
                <span class="cov8" title="1">if app.selectedFile &gt;= app.startFiles </span><span class="cov8" title="1">{
                        return app.selectFileByIndex(app.selectedFile - app.startFiles)
                }</span>
        }
        <span class="cov0" title="0">return nil</span>
}

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

func (app *App) selectFile(g *gocui.Gui, v *gocui.View) error <span class="cov8" title="1">{
        if v == nil || len(app.logfiles) == 0 </span><span class="cov8" title="1">{
                return nil
        }</span>
        <span class="cov8" title="1">_, cy := v.Cursor()
        line, err := v.Line(cy)
        if err != nil </span><span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">app.loadFileLogs(strings.TrimSpace(line), true)
        app.lastWindow = "varLogs"
        app.lastSelected = strings.TrimSpace(line)
        return nil</span>
}

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

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

// ---------------------------------------- Docker/Podman/k8s ----------------------------------------

func (app *App) loadDockerContainer(containerizationSystem string) <span class="cov8" title="1">{
        app.dockerContainers = nil
        // Получаем версию для проверки, что система контейнеризации установлена
        cmd := exec.Command(containerizationSystem, "version")
        _, err := cmd.Output()
        if err != nil &amp;&amp; !app.testMode </span><span class="cov8" title="1">{
                vError, _ := app.gui.View("docker")
                vError.Clear()
                app.dockerFrameColor = gocui.ColorRed
                vError.FrameColor = app.dockerFrameColor
                vError.Highlight = false
                fmt.Fprintln(vError, "\033[31m"+containerizationSystem+" not installed (environment not found)\033[0m")
                return
        }</span>
        <span class="cov8" title="1">if err != nil &amp;&amp; app.testMode </span><span class="cov0" title="0">{
                log.Print("Error:", containerizationSystem+" not installed (environment not found)")
        }</span>
        <span class="cov8" title="1">if containerizationSystem == "kubectl" </span><span class="cov0" title="0">{
                // Получаем список подов из k8s
                cmd = exec.Command(
                        containerizationSystem, "get", "pods", "-o",
                        "jsonpath={range .items[*]}{.metadata.uid} {.metadata.name} {.status.phase}{'\\n'}{end}",
                )
        }</span> else<span class="cov8" title="1"> {
                // Получаем список контейнеров из Docker или Podman
                cmd = exec.Command(
                        containerizationSystem, "ps", "-a",
                        "--format", "{{.ID}} {{.Names}} {{.State}}",
                )
        }</span>
        <span class="cov8" title="1">output, err := cmd.Output()
        if !app.testMode </span><span class="cov8" title="1">{
                if err != nil </span><span class="cov0" title="0">{
                        vError, _ := app.gui.View("docker")
                        vError.Clear()
                        app.dockerFrameColor = gocui.ColorRed
                        vError.FrameColor = app.dockerFrameColor
                        vError.Highlight = false
                        fmt.Fprintln(vError, "\033[31mAccess denied or "+containerizationSystem+" not running\033[0m")
                        return
                }</span> else<span class="cov8" title="1"> {
                        vError, _ := app.gui.View("docker")
                        app.dockerFrameColor = gocui.ColorDefault
                        vError.Highlight = true
                        if vError.FrameColor != gocui.ColorDefault </span><span class="cov8" title="1">{
                                vError.FrameColor = gocui.ColorGreen
                        }</span>
                }
        }
        <span class="cov8" title="1">if err != nil &amp;&amp; app.testMode </span><span class="cov0" title="0">{
                log.Print("Error: access denied or " + containerizationSystem + " not running")
        }</span>
        <span class="cov8" title="1">containers := strings.Split(strings.TrimSpace(string(output)), "\n")
        // Проверяем, что список контейнеров не пустой
        if !app.testMode </span><span class="cov8" title="1">{
                if len(containers) == 0 || (len(containers) == 1 &amp;&amp; containers[0] == "") </span><span class="cov8" title="1">{
                        vError, _ := app.gui.View("docker")
                        vError.Clear()
                        vError.Highlight = false
                        fmt.Fprintln(vError, "\033[32mNo running containers\033[0m")
                        return
                }</span> else<span class="cov8" title="1"> {
                        vError, _ := app.gui.View("docker")
                        app.fileSystemFrameColor = gocui.ColorDefault
                        if vError.FrameColor != gocui.ColorDefault </span><span class="cov8" title="1">{
                                vError.FrameColor = gocui.ColorGreen
                        }</span>
                        <span class="cov8" title="1">vError.Highlight = true</span>
                }
        }
        // Проверяем статус для покраски и заполняем структуру dockerContainers
        <span class="cov8" title="1">serviceMap := make(map[string]bool)
        scanner := bufio.NewScanner(strings.NewReader(string(output)))
        for scanner.Scan() </span><span class="cov8" title="1">{
                idName := scanner.Text()
                parts := strings.Fields(idName)
                if idName != "" &amp;&amp; !serviceMap[idName] </span><span class="cov8" title="1">{
                        serviceMap[idName] = true
                        containerStatus := parts[2]
                        if containerStatus == "running" || containerStatus == "Running" </span><span class="cov8" title="1">{
                                containerStatus = "\033[32m" + containerStatus + "\033[0m"
                        }</span> else<span class="cov0" title="0"> {
                                containerStatus = "\033[31m" + containerStatus + "\033[0m"
                        }</span>
                        <span class="cov8" title="1">containerName := parts[1] + " (" + containerStatus + ")"
                        app.dockerContainers = append(app.dockerContainers, DockerContainers{
                                name: containerName,
                                id:   parts[0],
                        })</span>
                }
        }
        <span class="cov8" title="1">sort.Slice(app.dockerContainers, func(i, j int) bool </span><span class="cov8" title="1">{
                return app.dockerContainers[i].name &lt; app.dockerContainers[j].name
        }</span>)
        <span class="cov8" title="1">if !app.testMode </span><span class="cov8" title="1">{
                app.dockerContainersNotFilter = app.dockerContainers
                app.applyFilterList()
        }</span>
}

func (app *App) updateDockerContainerList() <span class="cov8" title="1">{
        v, err := app.gui.View("docker")
        if err != nil </span><span class="cov0" title="0">{
                return
        }</span>
        <span class="cov8" title="1">v.Clear()
        visibleEnd := app.startDockerContainers + app.maxVisibleDockerContainers
        if visibleEnd &gt; len(app.dockerContainers) </span><span class="cov8" title="1">{
                visibleEnd = len(app.dockerContainers)
        }</span>
        <span class="cov8" title="1">for i := app.startDockerContainers; i &lt; visibleEnd; i++ </span><span class="cov8" title="1">{
                fmt.Fprintln(v, app.dockerContainers[i].name)
        }</span>
}

func (app *App) nextDockerContainer(v *gocui.View, step int) error <span class="cov8" title="1">{
        _, viewHeight := v.Size()
        app.maxVisibleDockerContainers = viewHeight
        if len(app.dockerContainers) == 0 </span><span class="cov8" title="1">{
                return nil
        }</span>
        <span class="cov8" title="1">if app.selectedDockerContainer &lt; len(app.dockerContainers)-1 </span><span class="cov8" title="1">{
                app.selectedDockerContainer += step
                if app.selectedDockerContainer &gt;= len(app.dockerContainers) </span><span class="cov8" title="1">{
                        app.selectedDockerContainer = len(app.dockerContainers) - 1
                }</span>
                <span class="cov8" title="1">if app.selectedDockerContainer &gt;= app.startDockerContainers+app.maxVisibleDockerContainers </span><span class="cov0" title="0">{
                        app.startDockerContainers += step
                        if app.startDockerContainers &gt; len(app.dockerContainers)-app.maxVisibleDockerContainers </span><span class="cov0" title="0">{
                                app.startDockerContainers = len(app.dockerContainers) - app.maxVisibleDockerContainers
                        }</span>
                        <span class="cov0" title="0">app.updateDockerContainerList()</span>
                }
                <span class="cov8" title="1">if app.selectedDockerContainer &lt; app.startDockerContainers+app.maxVisibleDockerContainers </span><span class="cov8" title="1">{
                        return app.selectDockerByIndex(app.selectedDockerContainer - app.startDockerContainers)
                }</span>
        }
        <span class="cov0" title="0">return nil</span>
}

func (app *App) prevDockerContainer(v *gocui.View, step int) error <span class="cov8" title="1">{
        _, viewHeight := v.Size()
        app.maxVisibleDockerContainers = viewHeight
        if len(app.dockerContainers) == 0 </span><span class="cov8" title="1">{
                return nil
        }</span>
        <span class="cov8" title="1">if app.selectedDockerContainer &gt; 0 </span><span class="cov8" title="1">{
                app.selectedDockerContainer -= step
                if app.selectedDockerContainer &lt; 0 </span><span class="cov8" title="1">{
                        app.selectedDockerContainer = 0
                }</span>
                <span class="cov8" title="1">if app.selectedDockerContainer &lt; app.startDockerContainers </span><span class="cov0" title="0">{
                        app.startDockerContainers -= step
                        if app.startDockerContainers &lt; 0 </span><span class="cov0" title="0">{
                                app.startDockerContainers = 0
                        }</span>
                        <span class="cov0" title="0">app.updateDockerContainerList()</span>
                }
                <span class="cov8" title="1">if app.selectedDockerContainer &gt;= app.startDockerContainers </span><span class="cov8" title="1">{
                        return app.selectDockerByIndex(app.selectedDockerContainer - app.startDockerContainers)
                }</span>
        }
        <span class="cov0" title="0">return nil</span>
}

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

func (app *App) selectDocker(g *gocui.Gui, v *gocui.View) error <span class="cov8" title="1">{
        if v == nil || len(app.dockerContainers) == 0 </span><span class="cov8" title="1">{
                return nil
        }</span>
        <span class="cov8" title="1">_, cy := v.Cursor()
        line, err := v.Line(cy)
        if err != nil </span><span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">app.loadDockerLogs(strings.TrimSpace(line), true)
        app.lastWindow = "docker"
        app.lastSelected = strings.TrimSpace(line)
        return nil</span>
}

func (app *App) loadDockerLogs(containerName string, newUpdate bool) <span class="cov8" title="1">{
        containerizationSystem := app.selectContainerizationSystem
        // Сохраняем систему контейнеризации для автообновления при смене окна
        if newUpdate </span><span class="cov8" title="1">{
                app.lastContainerizationSystem = app.selectContainerizationSystem
        }</span> else<span class="cov8" title="1"> {
                containerizationSystem = app.lastContainerizationSystem
        }</span>
        <span class="cov8" title="1">var ansiEscape = regexp.MustCompile(`\x1b\[[0-9;]*m`)
        var containerId string
        for _, dockerContainer := range app.dockerContainers </span><span class="cov8" title="1">{
                dockerContainerName := ansiEscape.ReplaceAllString(dockerContainer.name, "")
                if dockerContainerName == containerName </span><span class="cov8" title="1">{
                        containerId = dockerContainer.id
                }</span>
        }
        // Сохраняем id контейнера для автообновления при смене окна
        <span class="cov8" title="1">if newUpdate </span><span class="cov8" title="1">{
                app.lastContainerId = containerId
        }</span> else<span class="cov8" title="1"> {
                containerId = app.lastContainerId
        }</span>
        // Читаем локальный лог Docker в формате JSON
        <span class="cov8" title="1">var readFileContainer bool = false
        if containerizationSystem == "docker" </span><span class="cov8" title="1">{
                basePath := "/var/lib/docker/containers"
                var logFilePath string
                // Ищем файл лога в локальной системе по id
                _ = filepath.Walk(basePath, func(path string, info os.FileInfo, err error) error </span><span class="cov8" title="1">{
                        if err == nil &amp;&amp; strings.Contains(info.Name(), containerId) &amp;&amp; strings.HasSuffix(info.Name(), "-json.log") </span><span class="cov8" title="1">{
                                logFilePath = path
                                // Фиксируем, если найден файловый журнал
                                readFileContainer = true
                                // Останавливаем поиск
                                return filepath.SkipDir
                        }</span>
                        <span class="cov8" title="1">return nil</span>
                })
                // Читаем файл с конца с помощью tail
                <span class="cov8" title="1">if readFileContainer </span><span class="cov8" title="1">{
                        cmd := exec.Command("tail", "-n", app.logViewCount, logFilePath)
                        output, err := cmd.Output()
                        if err != nil &amp;&amp; !app.testMode </span><span class="cov0" title="0">{
                                v, _ := app.gui.View("logs")
                                v.Clear()
                                fmt.Fprintln(v, "\033[31mError reading log (tail):", err, "\033[0m")
                                return
                        }</span>
                        <span class="cov8" title="1">if err != nil &amp;&amp; app.testMode </span><span class="cov0" title="0">{
                                log.Print("Error: reading log via tail. ", err)
                        }</span>
                        // Разбиваем строки на массив
                        <span class="cov8" title="1">lines := strings.Split(strings.TrimSpace(string(output)), "\n")
                        var formattedLines []string
                        // Обрабатываем вывод в формате JSON построчно
                        for i, line := range lines </span><span class="cov8" title="1">{
                                // JSON-структура для парсинга
                                var jsonData map[string]interface{}
                                err := json.Unmarshal([]byte(line), &amp;jsonData)
                                if err != nil </span><span class="cov0" title="0">{
                                        continue</span>
                                }
                                // Извлекаем JSON данные
                                <span class="cov8" title="1">stream, _ := jsonData["stream"].(string)
                                timeStr, _ := jsonData["time"].(string)
                                logMessage, _ := jsonData["log"].(string)
                                // Удаляем встроенный экранированный символ переноса строки
                                logMessage = strings.TrimSuffix(logMessage, "\n")
                                // Парсим строку времени в объект time.Time
                                parsedTime, err := time.Parse(time.RFC3339Nano, timeStr)
                                if err == nil </span><span class="cov8" title="1">{
                                        // Форматируем дату в формат: DD:MM:YYYY HH:MM:SS
                                        timeStr = parsedTime.Format("02.01.2006 15:04:05")
                                }</span>
                                // Заполняем строку в формате: stream time: log
                                <span class="cov8" title="1">formattedLine := fmt.Sprintf("%s %s: %s", stream, timeStr, logMessage)
                                formattedLines = append(formattedLines, formattedLine)
                                // Если это последняя строка в выводе, добавляем перенос строки
                                if i == len(lines)-1 </span><span class="cov8" title="1">{
                                        formattedLines = append(formattedLines, "\n")
                                }</span>
                        }
                        <span class="cov8" title="1">app.currentLogLines = formattedLines</span>
                }
        }
        // Читаем лог через Docker cli (если файл не найден или к нему нет доступа) или Podman/k8s
        <span class="cov8" title="1">if !readFileContainer || containerizationSystem == "podman" || containerizationSystem == "kubectl" </span><span class="cov0" title="0">{
                // Извлекаем имя без статуса для k8s в containerId
                if containerizationSystem == "kubectl" </span><span class="cov0" title="0">{
                        parts := strings.Split(containerName, " (")
                        containerId = parts[0]
                }</span>
                <span class="cov0" title="0">cmd := exec.Command(containerizationSystem, "logs", "--tail", app.logViewCount, containerId)
                output, err := cmd.CombinedOutput() // читаем весь вывод, включая stderr
                if err != nil &amp;&amp; !app.testMode </span><span class="cov0" title="0">{
                        v, _ := app.gui.View("logs")
                        v.Clear()
                        fmt.Fprintln(v, "\033[31mError getting logs from", containerName, "(id:", containerId, ")", "container. Error:", err, "\033[0m")
                        return
                }</span>
                <span class="cov0" title="0">if err != nil &amp;&amp; app.testMode </span><span class="cov0" title="0">{
                        log.Print("Error: getting logs from ", containerName, " (id:", containerId, ")", " container. Error: ", err)
                }</span>
                <span class="cov0" title="0">app.currentLogLines = strings.Split(string(output), "\n")</span>
        }
        <span class="cov8" title="1">if !app.testMode </span><span class="cov8" title="1">{
                app.updateDelimiter(newUpdate)
                app.applyFilter(false)
        }</span>
}

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

// Редактор обработки ввода текста для фильтрации
func (app *App) createFilterEditor(window string) gocui.Editor <span class="cov8" title="1">{
        return gocui.EditorFunc(func(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) </span><span class="cov0" title="0">{
                switch </span>{
                // добавляем символ в поле ввода
                case ch != 0 &amp;&amp; mod == 0:<span class="cov0" title="0">
                        v.EditWrite(ch)</span>
                // добавляем пробел
                case key == gocui.KeySpace:<span class="cov0" title="0">
                        v.EditWrite(' ')</span>
                // удаляем символ слева от курсора
                case key == gocui.KeyBackspace || key == gocui.KeyBackspace2:<span class="cov0" title="0">
                        v.EditDelete(true)</span>
                // Удаляем символ справа от курсора
                case key == gocui.KeyDelete:<span class="cov0" title="0">
                        v.EditDelete(false)</span>
                // Перемещение курсора влево
                case key == gocui.KeyArrowLeft:<span class="cov0" title="0">
                        v.MoveCursor(-1, 0)</span> // удалить 3-й булевой параметр для форка
                // Перемещение курсора вправо
                case key == gocui.KeyArrowRight:<span class="cov0" title="0">
                        v.MoveCursor(1, 0)</span>
                }
                <span class="cov0" title="0">if window == "logs" </span><span class="cov0" title="0">{
                        // Обновляем текст в буфере
                        app.filterText = strings.TrimSpace(v.Buffer())
                        // Применяем функцию фильтрации к выводу записей журнала
                        app.applyFilter(true)
                }</span> else<span class="cov0" title="0"> if window == "lists" </span><span class="cov0" title="0">{
                        app.filterListText = strings.TrimSpace(v.Buffer())
                        app.applyFilterList()
                }</span>
        })
}

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

// Функция для фильтрации записей текущего журнала + покраска
func (app *App) applyFilter(color bool) <span class="cov8" title="1">{
        filter := app.filterText
        var skip bool = false
        var size int
        var viewHeight int
        var err error
        if !app.testMode </span><span class="cov8" title="1">{
                v, err := app.gui.View("filter")
                if err != nil </span><span class="cov0" title="0">{
                        return
                }</span>
                <span class="cov8" title="1">if color </span><span class="cov8" title="1">{
                        v.FrameColor = gocui.ColorGreen
                }</span>
                // Debug: если текст фильтра не менялся и позиция курсора не в самом конце журнала, то пропускаем фильтрацию и покраску при пролистывании
                <span class="cov8" title="1">vLogs, _ := app.gui.View("logs")
                _, viewHeight := vLogs.Size()
                size = app.logScrollPos + viewHeight + 1
                if app.lastFilterText == filter &amp;&amp; size &lt; len(app.filteredLogLines) </span><span class="cov0" title="0">{
                        skip = true
                }</span>
                // Фиксируем текущий текст из фильтра
                <span class="cov8" title="1">app.lastFilterText = filter</span>
        }
        // Фильтруем и красим, только если это не строллинг
        <span class="cov8" title="1">if !skip </span><span class="cov8" title="1">{
                // Debug start time
                startTime := time.Now()
                // Debug: если текст фильтра пустой или равен любому символу для regex, возвращяем вывод без фильтрации
                if filter == "" || (filter == "." &amp;&amp; app.selectFilterMode == "regex") </span><span class="cov8" title="1">{
                        app.filteredLogLines = app.currentLogLines
                }</span> else<span class="cov8" title="1"> {
                        app.filteredLogLines = make([]string, 0)
                        // Опускаем регистр ввода текста для фильтра
                        filter = strings.ToLower(filter)
                        // Проверка регулярного выражения
                        var regex *regexp.Regexp
                        if app.selectFilterMode == "regex" </span><span class="cov8" title="1">{
                                // Добавляем флаг для нечувствительности к регистру по умолчанию
                                filter = "(?i)" + filter
                                // Компилируем регулярное выражение
                                regex, err = regexp.Compile(filter)
                                // В случае синтаксической ошибки регулярного выражения, красим окно красным цветом и завершаем цикл
                                if err != nil &amp;&amp; !app.testMode </span><span class="cov0" title="0">{
                                        v, _ := app.gui.View("filter")
                                        v.FrameColor = gocui.ColorRed
                                        return
                                }</span>
                                <span class="cov8" title="1">if err != nil &amp;&amp; !app.testMode </span><span class="cov0" title="0">{
                                        log.Print("Error: regex syntax")
                                        return
                                }</span>
                        }
                        // Проходимся по каждой строке
                        <span class="cov8" title="1">for _, line := range app.currentLogLines </span><span class="cov8" title="1">{
                                switch </span>{
                                // Fuzzy (неточный поиск без учета регистра)
                                case app.selectFilterMode == "fuzzy":<span class="cov8" title="1">
                                        // Разбиваем текст фильтра на массив из строк
                                        filterWords := strings.Fields(filter)
                                        // Опускаем регистр текущей строки цикла
                                        lineLower := strings.ToLower(line)
                                        var match bool = true
                                        // Проверяем, если строка не содержит хотя бы одно слово из фильтра, то пропускаем строку
                                        for _, word := range filterWords </span><span class="cov8" title="1">{
                                                if !strings.Contains(lineLower, word) </span><span class="cov8" title="1">{
                                                        match = false
                                                        break</span>
                                                }
                                        }
                                        // Если строка подходит под фильтр, возвращаем ее с покраской
                                        <span class="cov8" title="1">if match </span><span class="cov8" title="1">{
                                                // Временные символы для обозначения начала и конца покраски найденных символов
                                                startColor := "►"
                                                endColor := "◄"
                                                originalLine := line
                                                // Проходимся по всем словосочетаниям фильтра (массив через пробел) для позиционирования покраски
                                                for _, word := range filterWords </span><span class="cov8" title="1">{
                                                        wordLower := strings.ToLower(word)
                                                        start := 0
                                                        // Ищем все вхождения слова в строке с учетом регистра
                                                        for </span><span class="cov8" title="1">{
                                                                // Находим индекс вхождения с учетом регистра
                                                                idx := strings.Index(strings.ToLower(originalLine[start:]), wordLower)
                                                                if idx == -1 </span><span class="cov8" title="1">{
                                                                        break</span> // Если больше нет вхождений, выходим
                                                                }
                                                                <span class="cov8" title="1">start += idx // корректируем индекс с учетом текущей позиции
                                                                // Вставляем временные символы для покраски
                                                                originalLine = originalLine[:start] + startColor + originalLine[start:start+len(word)] + endColor + originalLine[start+len(word):]
                                                                // Сдвигаем индекс для поиска в оставшейся части строки
                                                                start += len(startColor) + len(word) + len(endColor)</span>
                                                        }
                                                }
                                                // Заменяем временные символы на ANSI escape-последовательности
                                                <span class="cov8" title="1">originalLine = strings.ReplaceAll(originalLine, startColor, "\x1b[0;44m")
                                                originalLine = strings.ReplaceAll(originalLine, endColor, "\033[0m")
                                                app.filteredLogLines = append(app.filteredLogLines, originalLine)</span>
                                        }
                                // Regex (с использованием регулярных выражений и без учета регистра по умолчанию)
                                case app.selectFilterMode == "regex":<span class="cov8" title="1">
                                        // Проверяем, что строка подходит под регулярное выражение
                                        if regex.MatchString(line) </span><span class="cov8" title="1">{
                                                originalLine := line
                                                // Находим все найденные совпадени
                                                matches := regex.FindAllString(originalLine, -1)
                                                // Красим только первое найденное совпадение
                                                originalLine = strings.ReplaceAll(originalLine, matches[0], "\x1b[0;44m"+matches[0]+"\033[0m")
                                                app.filteredLogLines = append(app.filteredLogLines, originalLine)
                                        }</span>
                                // Default (точный поиск с учетом регистра)
                                default:<span class="cov8" title="1">
                                        filter = app.filterText
                                        if filter == "" || strings.Contains(line, filter) </span><span class="cov8" title="1">{
                                                lineColor := strings.ReplaceAll(line, filter, "\x1b[0;44m"+filter+"\033[0m")
                                                app.filteredLogLines = append(app.filteredLogLines, lineColor)
                                        }</span>
                                }
                        }
                }
                // Если последняя строка не содержит пустую строку, то добавляем ее
                <span class="cov8" title="1">if len(app.filteredLogLines) &gt; 0 &amp;&amp; app.filteredLogLines[len(app.filteredLogLines)-1] != "" </span><span class="cov8" title="1">{
                        app.filteredLogLines = append(app.filteredLogLines, "")
                }</span>
                // Отключаем покраску в режиме colorMode
                <span class="cov8" title="1">if app.colorMode </span><span class="cov8" title="1">{
                        // Режим покраски через tailspin
                        if app.tailSpinMode </span><span class="cov8" title="1">{
                                cmd := exec.Command("tailspin")
                                logLines := strings.Join(app.filteredLogLines, "\n")
                                // Создаем пайп для передачи данных
                                cmd.Stdin = bytes.NewBufferString(logLines)
                                var out bytes.Buffer
                                cmd.Stdout = &amp;out
                                if err := cmd.Run(); err != nil </span><span class="cov0" title="0">{
                                        fmt.Println(err)
                                }</span>
                                <span class="cov8" title="1">colorLogLines := strings.Split(out.String(), "\n")
                                app.filteredLogLines = colorLogLines</span>
                        } else<span class="cov8" title="1"> {
                                // Максимальное количество потоков
                                const maxWorkers = 10
                                // Канал для передачи индексов всех строк
                                tasks := make(chan int, len(app.filteredLogLines))
                                // Срез для хранения обработанных строк
                                colorLogLines := make([]string, len(app.filteredLogLines))
                                // Объявляем группу ожидания для синхронизации всех горутин (воркеров)
                                var wg sync.WaitGroup
                                // Создаем maxWorkers горутин, где каждая будет обрабатывать задачи из канала tasks
                                for i := 0; i &lt; maxWorkers; i++ </span><span class="cov8" title="1">{
                                        go func() </span><span class="cov8" title="1">{
                                                // Горутина будет работать, пока в канале tasks есть задачи
                                                for index := range tasks </span><span class="cov8" title="1">{
                                                        // Обрабатываем строку и сохраняем результат по соответствующему индексу
                                                        colorLogLines[index] = app.lineColor(app.filteredLogLines[index])
                                                        // Уменьшаем счетчик задач в группе ожидания.
                                                        wg.Done()
                                                }</span>
                                        }()
                                }
                                // Добавляем задачи в канал
                                <span class="cov8" title="1">for i := range app.filteredLogLines </span><span class="cov8" title="1">{
                                        // Увеличиваем счетчик задач в группе ожидания.
                                        wg.Add(1)
                                        // Передаем индекс строки в канал tasks
                                        tasks &lt;- i
                                }</span>
                                // Закрываем канал задач, чтобы воркеры завершили работу после обработки всех задач
                                <span class="cov8" title="1">close(tasks)
                                // Ждем завершения всех задач
                                wg.Wait()
                                app.filteredLogLines = colorLogLines</span>
                        }
                }
                // Debug end time
                <span class="cov8" title="1">endTime := time.Since(startTime)
                app.debugLoadTime = endTime.Truncate(time.Millisecond).String()</span>
        }
        // Debug: корректируем текущую позицию скролла, если размер массива стал меньше
        <span class="cov8" title="1">if size &gt; len(app.filteredLogLines) </span><span class="cov8" title="1">{
                newScrollPos := len(app.filteredLogLines) - viewHeight
                if newScrollPos &gt; 0 </span><span class="cov8" title="1">{
                        app.logScrollPos = newScrollPos
                }</span> else<span class="cov0" title="0"> {
                        app.logScrollPos = 0
                }</span>
        }
        // Обновляем окно для отображения отфильтрованных записей
        <span class="cov8" title="1">if !app.testMode </span><span class="cov8" title="1">{
                if app.autoScroll </span><span class="cov8" title="1">{
                        app.logScrollPos = 0
                        app.updateLogsView(true)
                }</span> else<span class="cov8" title="1"> {
                        app.updateLogsView(false)
                }</span>
        }
}

// ---------------------------------------- Coloring ----------------------------------------

// Функция для покраски строки
func (app *App) lineColor(inputLine string) string <span class="cov8" title="1">{
        // Разбиваем строку на слова
        words := strings.Fields(inputLine)
        var colorLine string
        var filterColor bool = false
        for _, word := range words </span><span class="cov8" title="1">{
                // Исключаем строки с покраской при поиске (Background)
                if strings.Contains(word, "\x1b[0;44m") </span><span class="cov8" title="1">{
                        filterColor = true
                }</span>
                // Красим слово в функции
                <span class="cov8" title="1">if !filterColor </span><span class="cov8" title="1">{
                        word = app.wordColor(word)
                }</span>
                // Возобновляем покраску
                <span class="cov8" title="1">if strings.Contains(word, "\033[0m") </span><span class="cov8" title="1">{
                        filterColor = false
                }</span>
                <span class="cov8" title="1">colorLine += word + " "</span>
        }
        <span class="cov8" title="1">return strings.TrimSpace(colorLine)</span>
}

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

// Поиск пользователей
func (app *App) containsUser(searchWord string) bool <span class="cov8" title="1">{
        for _, user := range app.userNameArray </span><span class="cov8" title="1">{
                if user == searchWord </span><span class="cov8" title="1">{
                        return true
                }</span>
        }
        <span class="cov8" title="1">return false</span>
}

// Поиск корневых директорий
func (app *App) containsPath(searchWord string) bool <span class="cov8" title="1">{
        for _, dir := range app.rootDirArray </span><span class="cov8" title="1">{
                if strings.Contains(searchWord, dir) </span><span class="cov8" title="1">{
                        return true
                }</span>
        }
        <span class="cov8" title="1">return false</span>
}

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

// Функция для покраски словосочетаний
func (app *App) wordColor(inputWord string) string <span class="cov8" title="1">{
        // Опускаем регистр слова
        inputWordLower := strings.ToLower(inputWord)
        // Значение по умолчанию
        var coloredWord string = inputWord
        switch </span>{
        // URL
        case strings.Contains(inputWord, "http://"):<span class="cov8" title="1">
                cleanedWord := app.trimHttpRegex.ReplaceAllString(inputWord, "")
                coloredChars := app.urlPathColor(cleanedWord)
                // Красный для http
                coloredWord = strings.ReplaceAll(inputWord, "http://"+cleanedWord, "\033[31mhttp\033[35m://"+coloredChars)</span>
        case strings.Contains(inputWord, "https://"):<span class="cov8" title="1">
                cleanedWord := app.trimHttpsRegex.ReplaceAllString(inputWord, "")
                coloredChars := app.urlPathColor(cleanedWord)
                // Зеленый для https
                coloredWord = strings.ReplaceAll(inputWord, "https://"+cleanedWord, "\033[32mhttps\033[35m://"+coloredChars)</span>
        // UNIX file paths
        case app.containsPath(inputWord):<span class="cov8" title="1">
                cleanedWord := app.trimPrefixPathRegex.ReplaceAllString(inputWord, "")
                cleanedWord = app.trimPostfixPathRegex.ReplaceAllString(cleanedWord, "")
                // Начинаем с желтого цвета
                coloredChars := "\033[33m"
                for _, char := range cleanedWord </span><span class="cov8" title="1">{
                        // Красим символы разделителя путей в пурпурный и возвращяем цвет
                        if char == '/' </span><span class="cov8" title="1">{
                                coloredChars += "\033[35m" + string(char) + "\033[33m"
                        }</span> else<span class="cov8" title="1"> {
                                coloredChars += string(char)
                        }</span>
                }
                <span class="cov8" title="1">coloredWord = strings.ReplaceAll(inputWord, cleanedWord, "\033[35m"+coloredChars+"\033[0m")</span>
        // Желтый (известные имена: hostname и username) [33m]
        case strings.Contains(inputWord, app.hostName):<span class="cov8" title="1">
                coloredWord = strings.ReplaceAll(inputWord, app.hostName, "\033[33m"+app.hostName+"\033[0m")</span>
        case strings.Contains(inputWord, app.userName):<span class="cov8" title="1">
                coloredWord = strings.ReplaceAll(inputWord, app.userName, "\033[33m"+app.userName+"\033[0m")</span>
        // Список пользователей из passwd
        case app.containsUser(inputWord):<span class="cov8" title="1">
                coloredWord = app.replaceWordLower(inputWord, inputWord, "\033[33m")</span>
        case strings.Contains(inputWordLower, "warn"):<span class="cov8" title="1">
                words := []string{"warnings", "warning", "warn"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[33m")
                                break</span>
                        }
                }
        // UNIX processes
        case app.syslogUnitRegex.MatchString(inputWord):<span class="cov8" title="1">
                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"+":")</span>
        case strings.HasPrefix(inputWordLower, "kernel:"):<span class="cov8" title="1">
                coloredWord = app.replaceWordLower(inputWord, "kernel", "\033[36m")</span>
        case strings.HasPrefix(inputWordLower, "rsyslogd:"):<span class="cov8" title="1">
                coloredWord = app.replaceWordLower(inputWord, "rsyslogd", "\033[36m")</span>
        case strings.HasPrefix(inputWordLower, "sudo:"):<span class="cov8" title="1">
                coloredWord = app.replaceWordLower(inputWord, "sudo", "\033[36m")</span>
        // Исключения
        case strings.Contains(inputWordLower, "unblock"):<span class="cov8" title="1">
                words := []string{"unblocking", "unblocked", "unblock"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[32m")
                                break</span>
                        }
                }
        // Красный (ошибки) [31m]
        case strings.Contains(inputWordLower, "err"):<span class="cov8" title="1">
                words := []string{"stderr", "errors", "error", "erro", "err"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[31m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "dis"):<span class="cov8" title="1">
                words := []string{"disconnected", "disconnection", "disconnects", "disconnect", "disabled", "disabling", "disable"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[31m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "crash"):<span class="cov8" title="1">
                words := []string{"crashed", "crashing", "crash"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[31m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "delet"):<span class="cov8" title="1">
                words := []string{"deletion", "deleted", "deleting", "deletes", "delete"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[31m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "remov"):<span class="cov8" title="1">
                words := []string{"removing", "removed", "removes", "remove"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[31m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "stop"):<span class="cov8" title="1">
                words := []string{"stopping", "stopped", "stoped", "stops", "stop"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[31m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "invalid"):<span class="cov8" title="1">
                words := []string{"invalidation", "invalidating", "invalidated", "invalidate", "invalid"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[31m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "abort"):<span class="cov8" title="1">
                words := []string{"aborted", "aborting", "abort"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[31m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "block"):<span class="cov8" title="1">
                words := []string{"blocked", "blocker", "blocking", "blocks", "block"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[31m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "activ"):<span class="cov8" title="1">
                words := []string{"inactive", "deactivated", "deactivating", "deactivate"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[31m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "exit"):<span class="cov8" title="1">
                words := []string{"exited", "exiting", "exits", "exit"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[31m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "crit"):<span class="cov8" title="1">
                words := []string{"critical", "critic", "crit"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[31m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "fail"):<span class="cov8" title="1">
                words := []string{"failed", "failure", "failing", "fails", "fail"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[31m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "reject"):<span class="cov8" title="1">
                words := []string{"rejecting", "rejection", "rejected", "reject"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[31m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "fatal"):<span class="cov8" title="1">
                words := []string{"fatality", "fataling", "fatals", "fatal"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[31m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "clos"):<span class="cov8" title="1">
                words := []string{"closed", "closing", "close"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[31m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "drop"):<span class="cov8" title="1">
                words := []string{"dropped", "droping", "drops", "drop"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[31m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "kill"):<span class="cov8" title="1">
                words := []string{"killer", "killing", "kills", "kill"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[31m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "cancel"):<span class="cov8" title="1">
                words := []string{"cancellation", "cancelation", "canceled", "cancelling", "canceling", "cancel"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[31m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "refus"):<span class="cov8" title="1">
                words := []string{"refusing", "refused", "refuses", "refuse"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[31m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "restrict"):<span class="cov8" title="1">
                words := []string{"restricting", "restricted", "restriction", "restrict"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[31m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "panic"):<span class="cov8" title="1">
                words := []string{"panicked", "panics", "panic"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[31m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "unknown"):<span class="cov8" title="1">
                coloredWord = app.replaceWordLower(inputWord, "unknown", "\033[31m")</span>
        case strings.Contains(inputWordLower, "unavailable"):<span class="cov8" title="1">
                coloredWord = app.replaceWordLower(inputWord, "unavailable", "\033[31m")</span>
        case strings.Contains(inputWordLower, "unsuccessful"):<span class="cov8" title="1">
                coloredWord = app.replaceWordLower(inputWord, "unsuccessful", "\033[31m")</span>
        case strings.Contains(inputWordLower, "found"):<span class="cov8" title="1">
                coloredWord = app.replaceWordLower(inputWord, "found", "\033[31m")</span>
        case strings.Contains(inputWordLower, "denied"):<span class="cov8" title="1">
                coloredWord = app.replaceWordLower(inputWord, "denied", "\033[31m")</span>
        case strings.Contains(inputWordLower, "conflict"):<span class="cov8" title="1">
                coloredWord = app.replaceWordLower(inputWord, "conflict", "\033[31m")</span>
        case strings.Contains(inputWordLower, "false"):<span class="cov8" title="1">
                coloredWord = app.replaceWordLower(inputWord, "false", "\033[31m")</span>
        case strings.Contains(inputWordLower, "none"):<span class="cov8" title="1">
                coloredWord = app.replaceWordLower(inputWord, "none", "\033[31m")</span>
        case strings.Contains(inputWordLower, "null"):<span class="cov8" title="1">
                coloredWord = app.replaceWordLower(inputWord, "null", "\033[31m")</span>
        // Исключения
        case strings.Contains(inputWordLower, "res"):<span class="cov8" title="1">
                words := []string{"resolved", "resolving", "resolve", "restarting", "restarted", "restart"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
                                break</span>
                        }
                }
        // Зеленый (успех) [32m]
        case strings.Contains(inputWordLower, "succe"):<span class="cov8" title="1">
                words := []string{"successfully", "successful", "succeeded", "succeed", "success"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[32m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "complet"):<span class="cov8" title="1">
                words := []string{"completed", "completing", "completion", "completes", "complete"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[32m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "accept"):<span class="cov8" title="1">
                words := []string{"accepted", "accepting", "acception", "acceptance", "acceptable", "acceptably", "accepte", "accepts", "accept"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[32m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "connect"):<span class="cov8" title="1">
                words := []string{"connected", "connecting", "connection", "connects", "connect"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[32m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "finish"):<span class="cov8" title="1">
                words := []string{"finished", "finishing", "finish"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[32m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "start"):<span class="cov8" title="1">
                words := []string{"started", "starting", "startup", "start"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[32m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "creat"):<span class="cov8" title="1">
                words := []string{"created", "creating", "creates", "create"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[32m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "enable"):<span class="cov8" title="1">
                words := []string{"enabled", "enables", "enable"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[32m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "allow"):<span class="cov8" title="1">
                words := []string{"allowed", "allowing", "allow"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[32m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "post"):<span class="cov8" title="1">
                words := []string{"posting", "posted", "postrouting", "post"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[32m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "rout"):<span class="cov8" title="1">
                words := []string{"prerouting", "routing", "routes", "route"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[32m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "forward"):<span class="cov8" title="1">
                words := []string{"forwarding", "forwards", "forward"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[32m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "pass"):<span class="cov8" title="1">
                words := []string{"passed", "passing", "password"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[32m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "run"):<span class="cov8" title="1">
                words := []string{"running", "runs", "run"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[32m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "add"):<span class="cov8" title="1">
                words := []string{"added", "add"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[32m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "open"):<span class="cov8" title="1">
                words := []string{"opening", "opened", "open"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[32m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "ok"):<span class="cov8" title="1">
                coloredWord = app.replaceWordLower(inputWord, "ok", "\033[32m")</span>
        case strings.Contains(inputWordLower, "available"):<span class="cov8" title="1">
                coloredWord = app.replaceWordLower(inputWord, "available", "\033[32m")</span>
        case strings.Contains(inputWordLower, "accessible"):<span class="cov8" title="1">
                coloredWord = app.replaceWordLower(inputWord, "accessible", "\033[32m")</span>
        case strings.Contains(inputWordLower, "done"):<span class="cov8" title="1">
                coloredWord = app.replaceWordLower(inputWord, "done", "\033[32m")</span>
        case strings.Contains(inputWordLower, "true"):<span class="cov8" title="1">
                coloredWord = app.replaceWordLower(inputWord, "true", "\033[32m")</span>
        // Синий (статусы) [36m]
        case strings.Contains(inputWordLower, "req"):<span class="cov8" title="1">
                words := []string{"requested", "requests", "request"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "reg"):<span class="cov8" title="1">
                words := []string{"registered", "registeration"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "boot"):<span class="cov8" title="1">
                words := []string{"reboot", "booting", "boot"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "out"):<span class="cov8" title="1">
                words := []string{"stdout", "timeout", "output"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "put"):<span class="cov8" title="1">
                words := []string{"input", "put"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "get"):<span class="cov8" title="1">
                words := []string{"getting", "get"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "set"):<span class="cov8" title="1">
                words := []string{"settings", "setting", "setup", "set"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "head"):<span class="cov8" title="1">
                words := []string{"headers", "header", "heades", "head"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "log"):<span class="cov8" title="1">
                words := []string{"logged", "login"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "load"):<span class="cov8" title="1">
                words := []string{"overloading", "overloaded", "overload", "uploading", "uploaded", "uploads", "upload", "downloading", "downloaded", "downloads", "download", "loading", "loaded", "load"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "read"):<span class="cov8" title="1">
                words := []string{"reading", "readed", "read"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "patch"):<span class="cov8" title="1">
                words := []string{"patching", "patched", "patch"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "up"):<span class="cov8" title="1">
                words := []string{"updates", "updated", "updating", "update", "upgrades", "upgraded", "upgrading", "upgrade", "backup", "up"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "listen"):<span class="cov8" title="1">
                words := []string{"listening", "listener", "listen"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "launch"):<span class="cov8" title="1">
                words := []string{"launched", "launching", "launch"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "chang"):<span class="cov8" title="1">
                words := []string{"changed", "changing", "change"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "clea"):<span class="cov8" title="1">
                words := []string{"cleaning", "cleaner", "clearing", "cleared", "clear"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "skip"):<span class="cov8" title="1">
                words := []string{"skipping", "skipped", "skip"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "miss"):<span class="cov8" title="1">
                words := []string{"missing", "missed"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "mount"):<span class="cov8" title="1">
                words := []string{"mountpoint", "mounted", "mounting", "mount"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "auth"):<span class="cov8" title="1">
                words := []string{"authenticating", "authentication", "authorization"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "conf"):<span class="cov8" title="1">
                words := []string{"configurations", "configuration", "configuring", "configured", "configure", "config", "conf"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "option"):<span class="cov8" title="1">
                words := []string{"options", "option"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "writ"):<span class="cov8" title="1">
                words := []string{"writing", "writed", "write"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "sav"):<span class="cov8" title="1">
                words := []string{"saved", "saving", "save"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "paus"):<span class="cov8" title="1">
                words := []string{"paused", "pausing", "pause"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "filt"):<span class="cov8" title="1">
                words := []string{"filtration", "filtr", "filtering", "filtered", "filter"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "norm"):<span class="cov8" title="1">
                words := []string{"normal", "norm"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "noti"):<span class="cov8" title="1">
                words := []string{"notifications", "notification", "notify", "noting", "notice"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "alert"):<span class="cov8" title="1">
                words := []string{"alerting", "alert"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "in"):<span class="cov8" title="1">
                words := []string{"informations", "information", "informing", "informed", "info", "installation", "installed", "installing", "install", "initialization", "initial", "using"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "down"):<span class="cov8" title="1">
                words := []string{"shutdown", "down"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "us"):<span class="cov8" title="1">
                words := []string{"status", "used", "use"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[36m")
                                break</span>
                        }
                }
        case strings.Contains(inputWordLower, "debug"):<span class="cov8" title="1">
                coloredWord = app.replaceWordLower(inputWord, "debug", "\033[36m")</span>
        case strings.Contains(inputWordLower, "verbose"):<span class="cov8" title="1">
                coloredWord = app.replaceWordLower(inputWord, "verbose", "\033[36m")</span>
        case strings.HasPrefix(inputWordLower, "trace"):<span class="cov8" title="1">
                coloredWord = app.replaceWordLower(inputWord, "trace", "\033[36m")</span>
        case strings.HasPrefix(inputWordLower, "protocol"):<span class="cov8" title="1">
                coloredWord = app.replaceWordLower(inputWord, "protocol", "\033[36m")</span>
        case strings.Contains(inputWordLower, "level"):<span class="cov8" title="1">
                coloredWord = app.replaceWordLower(inputWord, "level", "\033[36m")</span>
        // Голубой (цифры) [34m]
        // Byte (0x04)
        case app.hexByteRegex.MatchString(inputWord):<span class="cov8" title="1">
                coloredWord = app.hexByteRegex.ReplaceAllStringFunc(inputWord, func(match string) string </span><span class="cov8" title="1">{
                        colored := ""
                        for _, char := range match </span><span class="cov8" title="1">{
                                if char == 'x' </span><span class="cov8" title="1">{
                                        colored += "\033[35m" + string(char) + "\033[0m"
                                }</span> else<span class="cov8" title="1"> {
                                        colored += "\033[34m" + string(char) + "\033[0m"
                                }</span>
                        }
                        <span class="cov8" title="1">return colored</span>
                })
        // DateTime
        case app.dateTimeRegex.MatchString(inputWord):<span class="cov8" title="1">
                coloredWord = app.dateTimeRegex.ReplaceAllStringFunc(inputWord, func(match string) string </span><span class="cov8" title="1">{
                        colored := ""
                        for _, char := range match </span><span class="cov8" title="1">{
                                if char == '-' || char == '.' || char == ':' || char == '+' || char == 'T' </span><span class="cov8" title="1">{
                                        // Пурпурный для символов
                                        colored += "\033[35m" + string(char) + "\033[0m"
                                }</span> else<span class="cov8" title="1"> {
                                        // Синий для цифр
                                        colored += "\033[34m" + string(char) + "\033[0m"
                                }</span>
                        }
                        <span class="cov8" title="1">return colored</span>
                })
        // Time + MAC
        case app.timeMacAddressRegex.MatchString(inputWord):<span class="cov8" title="1">
                coloredWord = app.timeMacAddressRegex.ReplaceAllStringFunc(inputWord, func(match string) string </span><span class="cov8" title="1">{
                        colored := ""
                        for _, char := range match </span><span class="cov8" title="1">{
                                if char == '-' || char == ':' || char == '.' || char == ',' || char == '+' </span><span class="cov8" title="1">{
                                        colored += "\033[35m" + string(char) + "\033[0m"
                                }</span> else<span class="cov8" title="1"> {
                                        colored += "\033[34m" + string(char) + "\033[0m"
                                }</span>
                        }
                        <span class="cov8" title="1">return colored</span>
                })
        // Date + IP
        case app.dateIpAddressRegex.MatchString(inputWord):<span class="cov8" title="1">
                coloredWord = app.dateIpAddressRegex.ReplaceAllStringFunc(inputWord, func(match string) string </span><span class="cov8" title="1">{
                        colored := ""
                        for _, char := range match </span><span class="cov8" title="1">{
                                if char == '.' || char == ':' || char == '-' || char == '+' </span><span class="cov8" title="1">{
                                        colored += "\033[35m" + string(char) + "\033[0m"
                                }</span> else<span class="cov8" title="1"> {
                                        colored += "\033[34m" + string(char) + "\033[0m"
                                }</span>
                        }
                        <span class="cov8" title="1">return colored</span>
                })
        // Percentage (100%)
        case strings.Contains(inputWordLower, "%"):<span class="cov8" title="1">
                coloredWord = app.procRegex.ReplaceAllStringFunc(inputWord, func(match string) string </span><span class="cov8" title="1">{
                        colored := ""
                        for _, char := range match </span><span class="cov8" title="1">{
                                if char == '%' </span><span class="cov8" title="1">{
                                        colored += "\033[35m" + string(char) + "\033[0m"
                                }</span> else<span class="cov8" title="1"> {
                                        colored += "\033[34m" + string(char) + "\033[0m"
                                }</span>
                        }
                        <span class="cov8" title="1">return colored</span>
                })
        // tcpdump
        case strings.Contains(inputWordLower, "tcp"):<span class="cov8" title="1">
                coloredWord = app.replaceWordLower(inputWord, "tcp", "\033[33m")</span>
        case strings.Contains(inputWordLower, "udp"):<span class="cov8" title="1">
                coloredWord = app.replaceWordLower(inputWord, "udp", "\033[33m")</span>
        case strings.Contains(inputWordLower, "icmp"):<span class="cov8" title="1">
                coloredWord = app.replaceWordLower(inputWord, "icmp", "\033[33m")</span>
        case strings.Contains(inputWordLower, "ip"):<span class="cov8" title="1">
                words := []string{"ip4", "ipv4", "ip6", "ipv6", "ip"}
                for _, word := range words </span><span class="cov8" title="1">{
                        if strings.Contains(inputWordLower, word) </span><span class="cov8" title="1">{
                                coloredWord = app.replaceWordLower(inputWord, word, "\033[33m")
                                break</span>
                        }
                }
        // Update delimiter
        case strings.Contains(inputWord, "⎯"):<span class="cov8" title="1">
                coloredWord = app.replaceWordLower(inputWord, "⎯", "\033[32m")</span>
        // Исключения
        case strings.Contains(inputWordLower, "not"):<span class="cov8" title="1">
                coloredWord = app.replaceWordLower(inputWord, "not", "\033[31m")</span>
        }
        <span class="cov8" title="1">return coloredWord</span>
}

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

// Функция для обновления вывода журнала (параметр для прокрутки в самый вниз)
func (app *App) updateLogsView(lowerDown bool) <span class="cov8" title="1">{
        // Получаем доступ к выводу журнала
        v, err := app.gui.View("logs")
        if err != nil </span><span class="cov0" title="0">{
                return
        }</span>
        // Очищаем окно для отображения новых строк
        <span class="cov8" title="1">v.Clear()
        // Получаем ширину и высоту окна
        viewWidth, viewHeight := v.Size()
        // Опускаем в самый низ, только если это не ручной скролл (отключается параметром)
        if lowerDown </span><span class="cov8" title="1">{
                // Если количество строк больше высоты окна, опускаем в самый низ
                if len(app.filteredLogLines) &gt; viewHeight-1 </span><span class="cov0" title="0">{
                        app.logScrollPos = len(app.filteredLogLines) - viewHeight - 1
                }</span> else<span class="cov8" title="1"> {
                        app.logScrollPos = 0
                }</span>
        }
        // Определяем количество строк для отображения, начиная с позиции logScrollPos
        <span class="cov8" title="1">startLine := app.logScrollPos
        endLine := startLine + viewHeight
        if endLine &gt; len(app.filteredLogLines) </span><span class="cov8" title="1">{
                endLine = len(app.filteredLogLines)
        }</span>
        // Учитываем auto wrap (только в конце лога)
        <span class="cov8" title="1">if app.logScrollPos == len(app.filteredLogLines)-viewHeight-1 </span><span class="cov0" title="0">{
                var viewLines int = 0                             // количество строк для вывода
                var viewCounter int = 0                           // обратный счетчик видимых строк для остановки
                var viewIndex int = len(app.filteredLogLines) - 1 // начальный индекс для строк с конца
                for </span><span class="cov0" title="0">{
                        // Фиксируем текущую входную строку и счетчик
                        viewLines += 1
                        viewCounter += 1
                        // Получаем длинну видимых символов в строке с конца
                        var ansiEscape = regexp.MustCompile(`\x1b\[[0-9;]*m`)
                        lengthLine := len([]rune(ansiEscape.ReplaceAllString(app.filteredLogLines[viewIndex], "")))
                        // Если длинна строки больше ширины окна, получаем количество дополнительных строк
                        if lengthLine &gt; viewWidth </span><span class="cov0" title="0">{
                                // Увеличивая счетчик и пропускаем строки
                                viewCounter += lengthLine / viewWidth
                        }</span>
                        // Если счетчик привысил количество видимых строк, вычетаем последнюю строку из видимости
                        <span class="cov0" title="0">if viewCounter &gt; viewHeight </span><span class="cov0" title="0">{
                                viewLines -= 1
                        }</span>
                        <span class="cov0" title="0">if viewCounter &gt;= viewHeight </span><span class="cov0" title="0">{
                                break</span>
                        }
                        // Уменьшаем индекс
                        <span class="cov0" title="0">viewIndex -= 1</span>
                }
                <span class="cov0" title="0">for i := len(app.filteredLogLines) - viewLines - 1; i &lt; endLine; i++ </span><span class="cov0" title="0">{
                        fmt.Fprintln(v, app.filteredLogLines[i])
                }</span>
        } else<span class="cov8" title="1"> {
                // Проходим по отфильтрованным строкам и выводим их
                for i := startLine; i &lt; endLine; i++ </span><span class="cov8" title="1">{
                        fmt.Fprintln(v, app.filteredLogLines[i])
                }</span>
        }
        // Вычисляем процент прокрутки и обновляем заголовок
        <span class="cov8" title="1">var percentage int = 0
        if len(app.filteredLogLines) &gt; 0 </span><span class="cov8" title="1">{
                // Стартовая позиция + размер текущего вывода логов и округляем в большую сторону (math)
                percentage = int(math.Ceil(float64((startLine+viewHeight)*100) / float64(len(app.filteredLogLines))))
                if percentage &gt; 100 </span><span class="cov8" title="1">{
                        v.Title = fmt.Sprintf("Logs: 100%% (%d) ["+app.debugLoadTime+"]", len(app.filteredLogLines)) // "Logs: 100%% (%d) [Max lines: "+app.logViewCount+"/Load time: "+app.debugLoadTime+"]"
                }</span> else<span class="cov0" title="0"> {
                        v.Title = fmt.Sprintf("Logs: %d%% (%d/%d) ["+app.debugLoadTime+"]", percentage, startLine+1+viewHeight, len(app.filteredLogLines))
                }</span>
        } else<span class="cov8" title="1"> {
                v.Title = "Logs: 0% (0) [" + app.debugLoadTime + "]"
        }</span>
        <span class="cov8" title="1">app.viewScrollLogs(percentage)</span>
}

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

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

// Функция для скроллинга вверх
func (app *App) scrollUpLogs(step int) error <span class="cov8" title="1">{
        app.logScrollPos -= step
        if app.logScrollPos &lt; 0 </span><span class="cov8" title="1">{
                app.logScrollPos = 0
        }</span>
        // Отключаем автоскролл
        <span class="cov8" title="1">app.autoScroll = false
        app.updateLogsView(false)
        return nil</span>
}

// Функция для переход к началу журнала
func (app *App) pageUpLogs() <span class="cov8" title="1">{
        app.logScrollPos = 0
        app.autoScroll = false
        app.updateLogsView(false)
}</span>

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

// Функция для обновления последнего выбранного вывода лога
func (app *App) updateLogOutput(seconds int) <span class="cov8" title="1">{
        for </span><span class="cov8" title="1">{
                // Выполняем обновление интерфейса через метод Update для иницилизации перерисовки интерфейса
                app.gui.Update(func(g *gocui.Gui) error </span><span class="cov8" title="1">{
                        // Сбрасываем автоскролл, если это ручное обновление, что бы опустить журнал вниз
                        if seconds == 0 </span><span class="cov8" title="1">{
                                app.autoScroll = true
                        }</span>
                        <span class="cov8" title="1">switch app.lastWindow </span>{
                        case "services":<span class="cov8" title="1">
                                app.loadJournalLogs(app.lastSelected, false)</span>
                        case "varLogs":<span class="cov8" title="1">
                                app.loadFileLogs(app.lastSelected, false)</span>
                        case "docker":<span class="cov8" title="1">
                                app.loadDockerLogs(app.lastSelected, false)</span>
                        }
                        <span class="cov8" title="1">return nil</span>
                })
                <span class="cov8" title="1">if seconds == 0 </span><span class="cov8" title="1">{
                        break</span>
                }
                <span class="cov8" title="1">time.Sleep(time.Duration(seconds) * time.Second)</span>
        }
}

// Функция для обновления вывода при изменение размера окна
func (app *App) updateWindowSize(seconds int) <span class="cov8" title="1">{
        for </span><span class="cov8" title="1">{
                app.gui.Update(func(g *gocui.Gui) error </span><span class="cov8" title="1">{
                        v, err := g.View("logs")
                        if err != nil </span><span class="cov0" title="0">{
                                log.Panicln(err)
                        }</span>
                        <span class="cov8" title="1">windowWidth, windowHeight := v.Size()
                        if windowWidth != app.windowWidth || windowHeight != app.windowHeight </span><span class="cov8" title="1">{
                                app.windowWidth, app.windowHeight = windowWidth, windowHeight
                                app.updateLogsView(true)
                                if v, err := g.View("services"); err == nil </span><span class="cov8" title="1">{
                                        _, viewHeight := v.Size()
                                        app.maxVisibleServices = viewHeight
                                }</span>
                                <span class="cov8" title="1">if v, err := g.View("varLogs"); err == nil </span><span class="cov8" title="1">{
                                        _, viewHeight := v.Size()
                                        app.maxVisibleFiles = viewHeight
                                }</span>
                                <span class="cov8" title="1">if v, err := g.View("docker"); err == nil </span><span class="cov8" title="1">{
                                        _, viewHeight := v.Size()
                                        app.maxVisibleDockerContainers = viewHeight
                                }</span>
                                <span class="cov8" title="1">app.applyFilterList()</span>
                        }
                        <span class="cov8" title="1">return nil</span>
                })
                <span class="cov8" title="1">time.Sleep(time.Duration(seconds) * time.Second)</span>
        }
}

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

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

// Функция для биндинга клавиш
func (app *App) setupKeybindings() error <span class="cov8" title="1">{
        // Ctrl+C для выхода из приложения
        if err := app.gui.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil </span><span class="cov0" title="0">{
                return err
        }</span>
        // Tab для переключения между окнами
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("", gocui.KeyTab, gocui.ModNone, app.nextView); err != nil </span><span class="cov0" title="0">{
                return err
        }</span>
        // Shift+Tab для переключения между окнами в обратном порядке
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("", gocui.KeyBacktab, gocui.ModNone, app.backView); err != nil </span><span class="cov0" title="0">{
                return err
        }</span>
        // Enter для выбора службы и загрузки журналов
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("services", gocui.KeyEnter, gocui.ModNone, app.selectService); err != nil </span><span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("varLogs", gocui.KeyEnter, gocui.ModNone, app.selectFile); err != nil </span><span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("docker", gocui.KeyEnter, gocui.ModNone, app.selectDocker); err != nil </span><span class="cov0" title="0">{
                return err
        }</span>
        // Перемещение вниз к следующей службе (функция nextService), файлу (nextFileName) или контейнеру (nextDockerContainer)
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("services", gocui.KeyArrowDown, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.nextService(v, 1)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("varLogs", gocui.KeyArrowDown, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.nextFileName(v, 1)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("docker", gocui.KeyArrowDown, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.nextDockerContainer(v, 1)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        // Быстрое пролистывание вниз через 10 записей (Shift+Down)
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("services", gocui.KeyArrowDown, gocui.ModShift, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.nextService(v, 10)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("varLogs", gocui.KeyArrowDown, gocui.ModShift, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.nextFileName(v, 10)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("docker", gocui.KeyArrowDown, gocui.ModShift, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.nextDockerContainer(v, 10)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        // Alt+Down 100
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("services", gocui.KeyArrowDown, gocui.ModAlt, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.nextService(v, 100)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("varLogs", gocui.KeyArrowDown, gocui.ModAlt, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.nextFileName(v, 100)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("docker", gocui.KeyArrowDown, gocui.ModAlt, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.nextDockerContainer(v, 100)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        // Shift+D на 10 для macOS
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("services", 'D', gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.nextService(v, 10)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("varLogs", 'D', gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.nextFileName(v, 10)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("docker", 'D', gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.nextDockerContainer(v, 10)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        // Ctrl+D на 100 для macOS
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("services", gocui.KeyCtrlD, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.nextService(v, 100)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("varLogs", gocui.KeyCtrlD, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.nextFileName(v, 100)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("docker", gocui.KeyCtrlD, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.nextDockerContainer(v, 100)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        // Pgdn 1
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("services", gocui.KeyPgdn, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.nextService(v, 1)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("varLogs", gocui.KeyPgdn, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.nextFileName(v, 1)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("docker", gocui.KeyPgdn, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.nextDockerContainer(v, 1)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        // Shift+Pgdn 10
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("services", gocui.KeyPgdn, gocui.ModShift, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.nextService(v, 10)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("varLogs", gocui.KeyPgdn, gocui.ModShift, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.nextFileName(v, 10)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("docker", gocui.KeyPgdn, gocui.ModShift, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.nextDockerContainer(v, 10)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        // Alt+Pgdn 100
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("services", gocui.KeyPgdn, gocui.ModAlt, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.nextService(v, 100)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("varLogs", gocui.KeyPgdn, gocui.ModAlt, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.nextFileName(v, 100)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("docker", gocui.KeyPgdn, gocui.ModAlt, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.nextDockerContainer(v, 100)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        // ^^^
        // Пролистывание вверх
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("services", gocui.KeyArrowUp, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.prevService(v, 1)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("varLogs", gocui.KeyArrowUp, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.prevFileName(v, 1)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("docker", gocui.KeyArrowUp, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.prevDockerContainer(v, 1)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        // Shift+Up 10
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("services", gocui.KeyArrowUp, gocui.ModShift, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.prevService(v, 10)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("varLogs", gocui.KeyArrowUp, gocui.ModShift, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.prevFileName(v, 10)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("docker", gocui.KeyArrowUp, gocui.ModShift, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.prevDockerContainer(v, 10)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        // Alt+Up 100
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("services", gocui.KeyArrowUp, gocui.ModAlt, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.prevService(v, 100)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("varLogs", gocui.KeyArrowUp, gocui.ModAlt, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.prevFileName(v, 100)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("docker", gocui.KeyArrowUp, gocui.ModAlt, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.prevDockerContainer(v, 100)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        // Shift+U на 10 для macOS
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("services", 'U', gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.prevService(v, 10)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("varLogs", 'U', gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.prevFileName(v, 10)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("docker", 'U', gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.prevDockerContainer(v, 10)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        // Ctrl+U на 100 для macOS
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("services", gocui.KeyCtrlU, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.prevService(v, 100)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("varLogs", gocui.KeyCtrlU, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.prevFileName(v, 100)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("docker", gocui.KeyCtrlU, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.prevDockerContainer(v, 100)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        // Pgup 1
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("services", gocui.KeyPgup, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.prevService(v, 1)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("varLogs", gocui.KeyPgup, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.prevFileName(v, 1)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("docker", gocui.KeyPgup, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.prevDockerContainer(v, 1)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        // Shift+Pgup 10
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("services", gocui.KeyPgup, gocui.ModShift, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.prevService(v, 10)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("varLogs", gocui.KeyPgup, gocui.ModShift, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.prevFileName(v, 10)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("docker", gocui.KeyPgup, gocui.ModShift, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.prevDockerContainer(v, 10)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        // Alt+Pgup 100
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("services", gocui.KeyPgup, gocui.ModAlt, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.prevService(v, 100)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("varLogs", gocui.KeyPgup, gocui.ModAlt, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.prevFileName(v, 100)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("docker", gocui.KeyPgup, gocui.ModAlt, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.prevDockerContainer(v, 100)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        // Переключение выбора журналов для systemd/journald и отключаем для Windows
        <span class="cov8" title="1">if app.getOS != "windows" </span><span class="cov8" title="1">{
                if err := app.gui.SetKeybinding("services", gocui.KeyArrowRight, gocui.ModNone, app.setUnitListRight); err != nil </span><span class="cov0" title="0">{
                        return err
                }</span>
                <span class="cov8" title="1">if err := app.gui.SetKeybinding("services", gocui.KeyArrowLeft, gocui.ModNone, app.setUnitListLeft); err != nil </span><span class="cov0" title="0">{
                        return err
                }</span>
        }
        // Переключение выбора журналов для File System
        <span class="cov8" title="1">if app.keybindingsEnabled </span><span class="cov8" title="1">{
                // Установка привязок
                if err := app.gui.SetKeybinding("varLogs", gocui.KeyArrowRight, gocui.ModNone, app.setLogFilesListRight); err != nil </span><span class="cov0" title="0">{
                        return err
                }</span>
                <span class="cov8" title="1">if err := app.gui.SetKeybinding("varLogs", gocui.KeyArrowLeft, gocui.ModNone, app.setLogFilesListLeft); err != nil </span><span class="cov0" title="0">{
                        return err
                }</span>
        } else<span class="cov8" title="1"> {
                // Удаление привязок
                if err := app.gui.DeleteKeybinding("varLogs", gocui.KeyArrowRight, gocui.ModNone); err != nil </span><span class="cov0" title="0">{
                        return err
                }</span>
                <span class="cov8" title="1">if err := app.gui.DeleteKeybinding("varLogs", gocui.KeyArrowLeft, gocui.ModNone); err != nil </span><span class="cov0" title="0">{
                        return err
                }</span>
        }
        // Переключение выбора журналов для Containerization System
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("docker", gocui.KeyArrowRight, gocui.ModNone, app.setContainersListRight); err != nil </span><span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("docker", gocui.KeyArrowLeft, gocui.ModNone, app.setContainersListLeft); err != nil </span><span class="cov0" title="0">{
                return err
        }</span>
        // Переключение между режимами фильтрации через Up/Down для выбранного окна (filter)
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("filter", gocui.KeyArrowUp, gocui.ModNone, app.setFilterModeRight); err != nil </span><span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("filter", gocui.KeyArrowDown, gocui.ModNone, app.setFilterModeLeft); err != nil </span><span class="cov0" title="0">{
                return err
        }</span>
        // PgUp/PgDn Filter
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("filter", gocui.KeyPgup, gocui.ModNone, app.setFilterModeRight); err != nil </span><span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("filter", gocui.KeyPgdn, gocui.ModNone, app.setFilterModeLeft); err != nil </span><span class="cov0" title="0">{
                return err
        }</span>
        // Переключение для количества выводимых строк через Left/Right для выбранного окна (logs)
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("logs", gocui.KeyArrowLeft, gocui.ModNone, app.setCountLogViewDown); err != nil </span><span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("logs", gocui.KeyArrowRight, gocui.ModNone, app.setCountLogViewUp); err != nil </span><span class="cov0" title="0">{
                return err
        }</span>
        // &gt;&gt;&gt; Logs
        // Пролистывание вывода журнала через 1/10/500 записей вниз
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("logs", gocui.KeyArrowDown, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.scrollDownLogs(1)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("logs", gocui.KeyArrowDown, gocui.ModShift, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.scrollDownLogs(10)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("logs", gocui.KeyArrowDown, gocui.ModAlt, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.scrollDownLogs(500)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        // Shift+D 10
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("logs", 'D', gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.scrollDownLogs(10)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        // Ctrl+D 500
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("logs", gocui.KeyCtrlD, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.scrollDownLogs(500)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        // Up Logs 1/10/500
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("logs", gocui.KeyArrowUp, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.scrollUpLogs(1)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("logs", gocui.KeyArrowUp, gocui.ModShift, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.scrollUpLogs(10)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("logs", gocui.KeyArrowUp, gocui.ModAlt, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.scrollUpLogs(500)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        // Shift+U 500
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("logs", 'U', gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.scrollUpLogs(10)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        // Ctrl+U 10
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("logs", gocui.KeyCtrlU, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.scrollUpLogs(500)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        // KeyPgdn Logs 1/10/500
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("logs", gocui.KeyPgdn, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.scrollDownLogs(1)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("logs", gocui.KeyPgdn, gocui.ModShift, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.scrollDownLogs(10)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("logs", gocui.KeyPgdn, gocui.ModAlt, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.scrollDownLogs(500)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        // KeyPgup Logs 1/10/500
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("logs", gocui.KeyPgup, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.scrollUpLogs(1)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("logs", gocui.KeyPgup, gocui.ModShift, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.scrollUpLogs(10)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("logs", gocui.KeyPgup, gocui.ModAlt, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                return app.scrollUpLogs(500)
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        // Перемещение к концу журнала (Ctrl+E or End)
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("", gocui.KeyCtrlE, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                app.updateLogsView(true)
                return nil
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("", gocui.KeyEnd, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                app.updateLogsView(true)
                return nil
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        // Перемещение к началу журнала (Ctrl+A or Home)
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("", gocui.KeyCtrlA, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                app.pageUpLogs()
                return nil
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("", gocui.KeyHome, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                app.pageUpLogs()
                return nil
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        // Очистка поля ввода для фильтра (Ctrl+W)
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("", gocui.KeyCtrlW, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                app.clearFilterEditor(g)
                return nil
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        // Обновить все текущие списки журналов вручную (Ctrl+R)
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("", gocui.KeyCtrlR, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                if app.getOS != "windows" </span><span class="cov0" title="0">{
                        app.loadServices(app.selectUnits)
                        app.loadFiles(app.selectPath)
                }</span> else<span class="cov0" title="0"> {
                        app.loadWinFiles(app.selectPath)
                }</span>
                <span class="cov0" title="0">app.loadDockerContainer(app.selectContainerizationSystem)
                return nil</span>
        }); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        // Выключение/включение встроенной покраски ключевых слов (Ctrl+Q)
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("", gocui.KeyCtrlQ, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                if app.colorMode </span><span class="cov0" title="0">{
                        app.colorMode = false
                }</span> else<span class="cov0" title="0"> {
                        app.colorMode = true
                }</span>
                <span class="cov0" title="0">if len(app.currentLogLines) != 0 </span><span class="cov0" title="0">{
                        app.updateLogsView(true)
                        app.applyFilter(false)
                        app.updateLogOutput(0)
                }</span>
                <span class="cov0" title="0">return nil</span>
        }); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        // Включение/выключение режима покраски через tailspin (Ctrl+S)
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("", gocui.KeyCtrlS, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                if app.tailSpinMode </span><span class="cov0" title="0">{
                        app.tailSpinMode = false
                }</span> else<span class="cov0" title="0"> {
                        // Проверяем, что tailspin установлен в системе
                        tsCommands := []string{"tailspin", "tspin"}
                        for _, ts := range tsCommands </span><span class="cov0" title="0">{
                                cmd := exec.Command(ts, "--version")
                                _, err := cmd.Output()
                                if err == nil </span><span class="cov0" title="0">{
                                        app.tailSpinMode = true
                                }</span>
                        }
                }
                <span class="cov0" title="0">if len(app.currentLogLines) != 0 </span><span class="cov0" title="0">{
                        app.updateLogsView(true)
                        app.applyFilter(false)
                        app.updateLogOutput(0)
                }</span>
                <span class="cov0" title="0">return nil</span>
        }); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        // Отключить окно справки (F1)
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("", gocui.KeyF1, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                app.showInterfaceHelp(g)
                return nil
        }</span>); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        // Закрыть окно справки (Esc)
        <span class="cov8" title="1">if err := app.gui.SetKeybinding("", gocui.KeyEsc, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error </span><span class="cov0" title="0">{
                if err := app.closeHelp(g); err != nil </span><span class="cov0" title="0">{
                        return nil
                }</span>
                <span class="cov0" title="0">return nil</span>
        }); err != nil <span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">return nil</span>
}

func (app *App) showInterfaceHelp(g *gocui.Gui) <span class="cov8" title="1">{
        // Получаем размеры терминала
        maxX, maxY := g.Size()
        // Размеры окна help
        width, height := 104, 29
        // Вычисляем координаты для центрального расположения
        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 &amp;&amp; !errors.Is(err, gocui.ErrUnknownView) </span><span class="cov0" title="0">{
                return
        }</span>
        <span class="cov8" title="1">helpView.Title = " Help "
        helpView.Autoscroll = true
        helpView.Wrap = true
        helpView.FrameColor = gocui.ColorGreen
        helpView.TitleColor = gocui.ColorGreen
        helpView.Clear()
        fmt.Fprintln(helpView, "\n  \033[33mlazyjournal\033[0m - terminal user interface for reading logs from journalctl, file system, Docker and")
        fmt.Fprintln(helpView, "  Podman containers, as well Kubernetes pods.")
        fmt.Fprintln(helpView, "\n  Version: \033[36m"+programVersion+"\033[0m")
        fmt.Fprintln(helpView, "\n  Hotkeys:")
        fmt.Fprintln(helpView, "\n  \033[32mTab\033[0m - switch between windows.")
        fmt.Fprintln(helpView, "  \033[32mShift+Tab\033[0m - return to previous window.")
        fmt.Fprintln(helpView, "  \033[32mLeft/Right\033[0m - switch between journal lists in the selected window.")
        fmt.Fprintln(helpView, "  \033[32mEnter\033[0m - selection a journal from the list to display log output.")
        fmt.Fprintln(helpView, "  \033[32m&lt;Up/PgUp&gt;\033[0m and \033[32m&lt;Down/PgDown&gt;\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[32m&lt;Shift/Alt&gt;+&lt;Up/Down&gt;\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[32m&lt;Shift/Ctrl&gt;+&lt;U/D&gt;\033[0m - quickly move up and down (alternative for macOS).")
        fmt.Fprintln(helpView, "  \033[32mCtrl+A\033[0m or \033[32mHome\033[0m - go to top of log.")
        fmt.Fprintln(helpView, "  \033[32mCtrl+E\033[0m or \033[32mEnd\033[0m - go to the end of the log.")
        fmt.Fprintln(helpView, "  \033[32mCtrl+Q\033[0m - enable or disable built-in output coloring.")
        fmt.Fprintln(helpView, "  \033[32mCtrl+S\033[0m - enable or disable coloring via tailspin.")
        fmt.Fprintln(helpView, "  \033[32mCtrl+R\033[0m - update all log lists.")
        fmt.Fprintln(helpView, "  \033[32mCtrl+W\033[0m - clear text input field for filter to quickly update current log output without filtering.")
        fmt.Fprintln(helpView, "  \033[32mCtrl+C\033[0m - exit.")
        fmt.Fprintln(helpView, "  \033[32mEscape\033[0m - close help.")
        fmt.Fprintln(helpView, "\n  Source code: \033[35mhttps://github.com/Lifailon/lazyjournal\033[0m")</span>
}

func (app *App) closeHelp(g *gocui.Gui) error <span class="cov8" title="1">{
        if err := g.DeleteView("help"); err != nil </span><span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">return nil</span>
}

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

func (app *App) setCountLogViewUp(g *gocui.Gui, v *gocui.View) error <span class="cov8" title="1">{
        switch app.logViewCount </span>{
        case "5000":<span class="cov8" title="1">
                app.logViewCount = "10000"</span>
        case "10000":<span class="cov8" title="1">
                app.logViewCount = "50000"</span>
        case "50000":<span class="cov8" title="1">
                app.logViewCount = "100000"</span>
        case "100000":<span class="cov8" title="1">
                app.logViewCount = "200000"</span>
        case "200000":<span class="cov8" title="1">
                app.logViewCount = "300000"</span>
        case "300000":<span class="cov8" title="1">
                app.logViewCount = "300000"</span>
        }
        <span class="cov8" title="1">app.applyFilter(false)
        app.updateLogOutput(0)
        return nil</span>
}

func (app *App) setCountLogViewDown(g *gocui.Gui, v *gocui.View) error <span class="cov8" title="1">{
        switch app.logViewCount </span>{
        case "300000":<span class="cov8" title="1">
                app.logViewCount = "200000"</span>
        case "200000":<span class="cov8" title="1">
                app.logViewCount = "100000"</span>
        case "100000":<span class="cov8" title="1">
                app.logViewCount = "50000"</span>
        case "50000":<span class="cov8" title="1">
                app.logViewCount = "10000"</span>
        case "10000":<span class="cov8" title="1">
                app.logViewCount = "5000"</span>
        case "5000":<span class="cov8" title="1">
                app.logViewCount = "5000"</span>
        }
        <span class="cov8" title="1">app.applyFilter(false)
        app.updateLogOutput(0)
        return nil</span>
}

// Функции для переключения режима фильтрации

func (app *App) setFilterModeRight(g *gocui.Gui, v *gocui.View) error <span class="cov8" title="1">{
        selectedFilter, err := g.View("filter")
        if err != nil </span><span class="cov0" title="0">{
                log.Panicln(err)
        }</span>
        <span class="cov8" title="1">switch selectedFilter.Title </span>{
        case "Filter (Default)":<span class="cov8" title="1">
                selectedFilter.Title = "Filter (Fuzzy)"
                app.selectFilterMode = "fuzzy"</span>
        case "Filter (Fuzzy)":<span class="cov8" title="1">
                selectedFilter.Title = "Filter (Regex)"
                app.selectFilterMode = "regex"</span>
        case "Filter (Regex)":<span class="cov8" title="1">
                selectedFilter.Title = "Filter (Default)"
                app.selectFilterMode = "default"</span>
        }
        <span class="cov8" title="1">app.applyFilter(false)
        return nil</span>
}

func (app *App) setFilterModeLeft(g *gocui.Gui, v *gocui.View) error <span class="cov8" title="1">{
        selectedFilter, err := g.View("filter")
        if err != nil </span><span class="cov0" title="0">{
                log.Panicln(err)
        }</span>
        <span class="cov8" title="1">switch selectedFilter.Title </span>{
        case "Filter (Default)":<span class="cov8" title="1">
                selectedFilter.Title = "Filter (Regex)"
                app.selectFilterMode = "regex"</span>
        case "Filter (Regex)":<span class="cov8" title="1">
                selectedFilter.Title = "Filter (Fuzzy)"
                app.selectFilterMode = "fuzzy"</span>
        case "Filter (Fuzzy)":<span class="cov8" title="1">
                selectedFilter.Title = "Filter (Default)"
                app.selectFilterMode = "default"</span>
        }
        <span class="cov8" title="1">app.applyFilter(false)
        return nil</span>
}

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

func (app *App) setUnitListRight(g *gocui.Gui, v *gocui.View) error <span class="cov8" title="1">{
        selectedServices, err := g.View("services")
        if err != nil </span><span class="cov0" title="0">{
                log.Panicln(err)
        }</span>
        // Сбрасываем содержимое массива и положение курсора
        <span class="cov8" title="1">app.journals = app.journals[:0]
        app.startServices = 0
        app.selectedJournal = 0
        // Меняем журнал и обновляем список
        switch app.selectUnits </span>{
        case "services":<span class="cov8" title="1">
                app.selectUnits = "UNIT"
                selectedServices.Title = " &lt; System journals (0) &gt; "
                app.loadServices(app.selectUnits)</span>
        case "UNIT":<span class="cov8" title="1">
                app.selectUnits = "USER_UNIT"
                selectedServices.Title = " &lt; User journals (0) &gt; "
                app.loadServices(app.selectUnits)</span>
        case "USER_UNIT":<span class="cov8" title="1">
                app.selectUnits = "kernel"
                selectedServices.Title = " &lt; Kernel boot (0) &gt; "
                app.loadServices(app.selectUnits)</span>
        case "kernel":<span class="cov8" title="1">
                app.selectUnits = "services"
                selectedServices.Title = " &lt; Unit list (0) &gt; "
                app.loadServices(app.selectUnits)</span>
        }
        <span class="cov8" title="1">return nil</span>
}

func (app *App) setUnitListLeft(g *gocui.Gui, v *gocui.View) error <span class="cov8" title="1">{
        selectedServices, err := g.View("services")
        if err != nil </span><span class="cov0" title="0">{
                log.Panicln(err)
        }</span>
        <span class="cov8" title="1">app.journals = app.journals[:0]
        app.startServices = 0
        app.selectedJournal = 0
        switch app.selectUnits </span>{
        case "services":<span class="cov8" title="1">
                app.selectUnits = "kernel"
                selectedServices.Title = " &lt; Kernel boot (0) &gt; "
                app.loadServices(app.selectUnits)</span>
        case "kernel":<span class="cov8" title="1">
                app.selectUnits = "USER_UNIT"
                selectedServices.Title = " &lt; User journals (0) &gt; "
                app.loadServices(app.selectUnits)</span>
        case "USER_UNIT":<span class="cov8" title="1">
                app.selectUnits = "UNIT"
                selectedServices.Title = " &lt; System journals (0) &gt; "
                app.loadServices(app.selectUnits)</span>
        case "UNIT":<span class="cov8" title="1">
                app.selectUnits = "services"
                selectedServices.Title = " &lt; Unit list (0) &gt; "
                app.loadServices(app.selectUnits)</span>
        }
        <span class="cov8" title="1">return nil</span>
}

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

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

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

func (app *App) setContainersListLeft(g *gocui.Gui, v *gocui.View) error <span class="cov8" title="1">{
        selectedDocker, err := g.View("docker")
        if err != nil </span><span class="cov0" title="0">{
                log.Panicln(err)
        }</span>
        <span class="cov8" title="1">app.dockerContainers = app.dockerContainers[:0]
        app.startDockerContainers = 0
        app.selectedDockerContainer = 0
        switch app.selectContainerizationSystem </span>{
        case "docker":<span class="cov8" title="1">
                app.selectContainerizationSystem = "kubectl"
                selectedDocker.Title = " &lt; Kubernetes pods (0) &gt; "
                app.loadDockerContainer(app.selectContainerizationSystem)</span>
        case "kubectl":<span class="cov8" title="1">
                app.selectContainerizationSystem = "podman"
                selectedDocker.Title = " &lt; Podman containers (0) &gt; "
                app.loadDockerContainer(app.selectContainerizationSystem)</span>
        case "podman":<span class="cov8" title="1">
                app.selectContainerizationSystem = "docker"
                selectedDocker.Title = " &lt; Docker containers (0) &gt; "
                app.loadDockerContainer(app.selectContainerizationSystem)</span>
        }
        <span class="cov8" title="1">return nil</span>
}

// Функция для переключения окон через Tab
func (app *App) nextView(g *gocui.Gui, v *gocui.View) error <span class="cov8" title="1">{
        selectedFilterList, err := g.View("filterList")
        if err != nil </span><span class="cov0" title="0">{
                log.Panicln(err)
        }</span>
        <span class="cov8" title="1">selectedServices, err := g.View("services")
        if err != nil </span><span class="cov0" title="0">{
                log.Panicln(err)
        }</span>
        <span class="cov8" title="1">selectedVarLog, err := g.View("varLogs")
        if err != nil </span><span class="cov0" title="0">{
                log.Panicln(err)
        }</span>
        <span class="cov8" title="1">selectedDocker, err := g.View("docker")
        if err != nil </span><span class="cov0" title="0">{
                log.Panicln(err)
        }</span>
        <span class="cov8" title="1">selectedFilter, err := g.View("filter")
        if err != nil </span><span class="cov0" title="0">{
                log.Panicln(err)
        }</span>
        <span class="cov8" title="1">selectedLogs, err := g.View("logs")
        if err != nil </span><span class="cov0" title="0">{
                log.Panicln(err)
        }</span>
        <span class="cov8" title="1">selectedScrollLogs, err := g.View("scrollLogs")
        if err != nil </span><span class="cov0" title="0">{
                log.Panicln(err)
        }</span>
        <span class="cov8" title="1">currentView := g.CurrentView()
        var nextView string
        // Начальное окно
        if currentView == nil </span><span class="cov0" title="0">{
                nextView = "services"
        }</span> else<span class="cov8" title="1"> {
                switch currentView.Name() </span>{
                case "filterList":<span class="cov8" title="1">
                        nextView = "services"
                        selectedFilterList.FrameColor = gocui.ColorDefault
                        selectedFilterList.TitleColor = gocui.ColorDefault
                        selectedServices.FrameColor = gocui.ColorGreen
                        selectedServices.TitleColor = gocui.ColorGreen
                        selectedVarLog.FrameColor = app.fileSystemFrameColor
                        selectedVarLog.TitleColor = gocui.ColorDefault
                        selectedDocker.FrameColor = app.dockerFrameColor
                        selectedDocker.TitleColor = gocui.ColorDefault
                        selectedFilter.FrameColor = gocui.ColorDefault
                        selectedFilter.TitleColor = gocui.ColorDefault
                        selectedLogs.FrameColor = gocui.ColorDefault
                        selectedLogs.TitleColor = gocui.ColorDefault
                        selectedScrollLogs.FrameColor = gocui.ColorDefault</span>
                case "services":<span class="cov8" title="1">
                        nextView = "varLogs"
                        selectedFilterList.FrameColor = gocui.ColorDefault
                        selectedFilterList.TitleColor = gocui.ColorDefault
                        selectedServices.FrameColor = app.journalListFrameColor
                        selectedServices.TitleColor = gocui.ColorDefault
                        selectedVarLog.FrameColor = gocui.ColorGreen
                        selectedVarLog.TitleColor = gocui.ColorGreen
                        selectedDocker.FrameColor = app.dockerFrameColor
                        selectedDocker.TitleColor = gocui.ColorDefault
                        selectedFilter.FrameColor = gocui.ColorDefault
                        selectedFilter.TitleColor = gocui.ColorDefault
                        selectedLogs.FrameColor = gocui.ColorDefault
                        selectedLogs.TitleColor = gocui.ColorDefault
                        selectedScrollLogs.FrameColor = gocui.ColorDefault</span>
                case "varLogs":<span class="cov8" title="1">
                        nextView = "docker"
                        selectedFilterList.FrameColor = gocui.ColorDefault
                        selectedFilterList.TitleColor = gocui.ColorDefault
                        selectedServices.FrameColor = app.journalListFrameColor
                        selectedServices.TitleColor = gocui.ColorDefault
                        selectedVarLog.FrameColor = app.fileSystemFrameColor
                        selectedVarLog.TitleColor = gocui.ColorDefault
                        selectedDocker.FrameColor = gocui.ColorGreen
                        selectedDocker.TitleColor = gocui.ColorGreen
                        selectedFilter.FrameColor = gocui.ColorDefault
                        selectedFilter.TitleColor = gocui.ColorDefault
                        selectedLogs.FrameColor = gocui.ColorDefault
                        selectedLogs.TitleColor = gocui.ColorDefault
                        selectedScrollLogs.FrameColor = gocui.ColorDefault</span>
                case "docker":<span class="cov8" title="1">
                        nextView = "filter"
                        selectedFilterList.FrameColor = gocui.ColorDefault
                        selectedFilterList.TitleColor = gocui.ColorDefault
                        selectedServices.FrameColor = app.journalListFrameColor
                        selectedServices.TitleColor = gocui.ColorDefault
                        selectedVarLog.FrameColor = app.fileSystemFrameColor
                        selectedVarLog.TitleColor = gocui.ColorDefault
                        selectedDocker.FrameColor = app.dockerFrameColor
                        selectedDocker.TitleColor = gocui.ColorDefault
                        selectedFilter.FrameColor = gocui.ColorGreen
                        selectedFilter.TitleColor = gocui.ColorGreen
                        selectedLogs.FrameColor = gocui.ColorDefault
                        selectedLogs.TitleColor = gocui.ColorDefault
                        selectedScrollLogs.FrameColor = gocui.ColorDefault</span>
                case "filter":<span class="cov8" title="1">
                        nextView = "logs"
                        selectedFilterList.FrameColor = gocui.ColorDefault
                        selectedFilterList.TitleColor = gocui.ColorDefault
                        selectedServices.FrameColor = app.journalListFrameColor
                        selectedServices.TitleColor = gocui.ColorDefault
                        selectedVarLog.FrameColor = app.fileSystemFrameColor
                        selectedVarLog.TitleColor = gocui.ColorDefault
                        selectedDocker.FrameColor = app.dockerFrameColor
                        selectedDocker.TitleColor = gocui.ColorDefault
                        selectedFilter.FrameColor = gocui.ColorDefault
                        selectedFilter.TitleColor = gocui.ColorDefault
                        selectedLogs.FrameColor = gocui.ColorGreen
                        selectedLogs.TitleColor = gocui.ColorGreen
                        selectedScrollLogs.FrameColor = gocui.ColorGreen</span>
                case "logs":<span class="cov8" title="1">
                        nextView = "filterList"
                        selectedFilterList.FrameColor = gocui.ColorGreen
                        selectedFilterList.TitleColor = gocui.ColorGreen
                        selectedServices.FrameColor = app.journalListFrameColor
                        selectedServices.TitleColor = gocui.ColorDefault
                        selectedVarLog.FrameColor = app.fileSystemFrameColor
                        selectedVarLog.TitleColor = gocui.ColorDefault
                        selectedDocker.FrameColor = app.dockerFrameColor
                        selectedDocker.TitleColor = gocui.ColorDefault
                        selectedFilter.FrameColor = gocui.ColorDefault
                        selectedFilter.TitleColor = gocui.ColorDefault
                        selectedLogs.FrameColor = gocui.ColorDefault
                        selectedLogs.TitleColor = gocui.ColorDefault
                        selectedScrollLogs.FrameColor = gocui.ColorDefault</span>
                }
        }
        // Устанавливаем новое активное окно
        <span class="cov8" title="1">if _, err := g.SetCurrentView(nextView); err != nil </span><span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">return nil</span>
}

// Функция для переключения окон в обратном порядке через Shift+Tab
func (app *App) backView(g *gocui.Gui, v *gocui.View) error <span class="cov8" title="1">{
        selectedFilterList, err := g.View("filterList")
        if err != nil </span><span class="cov0" title="0">{
                log.Panicln(err)
        }</span>
        <span class="cov8" title="1">selectedServices, err := g.View("services")
        if err != nil </span><span class="cov0" title="0">{
                log.Panicln(err)
        }</span>
        <span class="cov8" title="1">selectedVarLog, err := g.View("varLogs")
        if err != nil </span><span class="cov0" title="0">{
                log.Panicln(err)
        }</span>
        <span class="cov8" title="1">selectedDocker, err := g.View("docker")
        if err != nil </span><span class="cov0" title="0">{
                log.Panicln(err)
        }</span>
        <span class="cov8" title="1">selectedFilter, err := g.View("filter")
        if err != nil </span><span class="cov0" title="0">{
                log.Panicln(err)
        }</span>
        <span class="cov8" title="1">selectedLogs, err := g.View("logs")
        if err != nil </span><span class="cov0" title="0">{
                log.Panicln(err)
        }</span>
        <span class="cov8" title="1">selectedScrollLogs, err := g.View("scrollLogs")
        if err != nil </span><span class="cov0" title="0">{
                log.Panicln(err)
        }</span>
        <span class="cov8" title="1">currentView := g.CurrentView()
        var nextView string
        if currentView == nil </span><span class="cov0" title="0">{
                nextView = "services"
        }</span> else<span class="cov8" title="1"> {
                switch currentView.Name() </span>{
                case "filterList":<span class="cov8" title="1">
                        nextView = "logs"
                        selectedFilterList.FrameColor = gocui.ColorDefault
                        selectedFilterList.TitleColor = gocui.ColorDefault
                        selectedServices.FrameColor = app.journalListFrameColor
                        selectedServices.TitleColor = gocui.ColorDefault
                        selectedVarLog.FrameColor = app.fileSystemFrameColor
                        selectedVarLog.TitleColor = gocui.ColorDefault
                        selectedDocker.FrameColor = app.dockerFrameColor
                        selectedDocker.TitleColor = gocui.ColorDefault
                        selectedFilter.FrameColor = gocui.ColorDefault
                        selectedFilter.TitleColor = gocui.ColorDefault
                        selectedLogs.FrameColor = gocui.ColorGreen
                        selectedLogs.TitleColor = gocui.ColorGreen
                        selectedScrollLogs.FrameColor = gocui.ColorGreen</span>
                case "services":<span class="cov8" title="1">
                        nextView = "filterList"
                        selectedFilterList.FrameColor = gocui.ColorGreen
                        selectedFilterList.TitleColor = gocui.ColorGreen
                        selectedServices.FrameColor = app.journalListFrameColor
                        selectedServices.TitleColor = gocui.ColorDefault
                        selectedVarLog.FrameColor = app.fileSystemFrameColor
                        selectedVarLog.TitleColor = gocui.ColorDefault
                        selectedDocker.FrameColor = app.dockerFrameColor
                        selectedDocker.TitleColor = gocui.ColorDefault
                        selectedFilter.FrameColor = gocui.ColorDefault
                        selectedFilter.TitleColor = gocui.ColorDefault
                        selectedLogs.FrameColor = gocui.ColorDefault
                        selectedLogs.TitleColor = gocui.ColorDefault
                        selectedScrollLogs.FrameColor = gocui.ColorDefault</span>
                case "logs":<span class="cov8" title="1">
                        nextView = "filter"
                        selectedFilterList.FrameColor = gocui.ColorDefault
                        selectedFilterList.TitleColor = gocui.ColorDefault
                        selectedServices.FrameColor = app.journalListFrameColor
                        selectedServices.TitleColor = gocui.ColorDefault
                        selectedVarLog.FrameColor = app.fileSystemFrameColor
                        selectedVarLog.TitleColor = gocui.ColorDefault
                        selectedDocker.FrameColor = app.dockerFrameColor
                        selectedDocker.TitleColor = gocui.ColorDefault
                        selectedFilter.FrameColor = gocui.ColorGreen
                        selectedFilter.TitleColor = gocui.ColorGreen
                        selectedLogs.FrameColor = gocui.ColorDefault
                        selectedLogs.TitleColor = gocui.ColorDefault
                        selectedScrollLogs.FrameColor = gocui.ColorDefault</span>
                case "filter":<span class="cov8" title="1">
                        nextView = "docker"
                        selectedFilterList.FrameColor = gocui.ColorDefault
                        selectedFilterList.TitleColor = gocui.ColorDefault
                        selectedServices.FrameColor = app.journalListFrameColor
                        selectedServices.TitleColor = gocui.ColorDefault
                        selectedVarLog.FrameColor = app.fileSystemFrameColor
                        selectedVarLog.TitleColor = gocui.ColorDefault
                        selectedDocker.FrameColor = gocui.ColorGreen
                        selectedDocker.TitleColor = gocui.ColorGreen
                        selectedFilter.FrameColor = gocui.ColorDefault
                        selectedFilter.TitleColor = gocui.ColorDefault
                        selectedLogs.FrameColor = gocui.ColorDefault
                        selectedLogs.TitleColor = gocui.ColorDefault
                        selectedScrollLogs.FrameColor = gocui.ColorDefault</span>
                case "docker":<span class="cov8" title="1">
                        nextView = "varLogs"
                        selectedFilterList.FrameColor = gocui.ColorDefault
                        selectedFilterList.TitleColor = gocui.ColorDefault
                        selectedServices.FrameColor = app.journalListFrameColor
                        selectedServices.TitleColor = gocui.ColorDefault
                        selectedVarLog.FrameColor = gocui.ColorGreen
                        selectedVarLog.TitleColor = gocui.ColorGreen
                        selectedDocker.FrameColor = app.dockerFrameColor
                        selectedDocker.TitleColor = gocui.ColorDefault
                        selectedFilter.FrameColor = gocui.ColorDefault
                        selectedFilter.TitleColor = gocui.ColorDefault
                        selectedLogs.FrameColor = gocui.ColorDefault
                        selectedLogs.TitleColor = gocui.ColorDefault
                        selectedScrollLogs.FrameColor = gocui.ColorDefault</span>
                case "varLogs":<span class="cov8" title="1">
                        nextView = "services"
                        selectedFilterList.FrameColor = gocui.ColorDefault
                        selectedFilterList.TitleColor = gocui.ColorDefault
                        selectedServices.FrameColor = gocui.ColorGreen
                        selectedServices.TitleColor = gocui.ColorGreen
                        selectedVarLog.FrameColor = app.fileSystemFrameColor
                        selectedVarLog.TitleColor = gocui.ColorDefault
                        selectedDocker.FrameColor = app.dockerFrameColor
                        selectedDocker.TitleColor = gocui.ColorDefault
                        selectedFilter.FrameColor = gocui.ColorDefault
                        selectedFilter.TitleColor = gocui.ColorDefault
                        selectedLogs.FrameColor = gocui.ColorDefault
                        selectedLogs.TitleColor = gocui.ColorDefault
                        selectedScrollLogs.FrameColor = gocui.ColorDefault</span>
                }
        }
        <span class="cov8" title="1">if _, err := g.SetCurrentView(nextView); err != nil </span><span class="cov0" title="0">{
                return err
        }</span>
        <span class="cov8" title="1">return nil</span>
}

// Функция для выхода
func quit(g *gocui.Gui, v *gocui.View) error <span class="cov8" title="1">{
        return gocui.ErrQuit
}</span>
</pre>
		
		</div>
	</body>
	<script>
	(function() {
		var files = document.getElementById('files');
		var visible;
		files.addEventListener('change', onChange, false);
		function select(part) {
			if (visible)
				visible.style.display = 'none';
			visible = document.getElementById(part);
			if (!visible)
				return;
			files.value = part;
			visible.style.display = 'block';
			location.hash = part;
		}
		function onChange() {
			select(files.value);
			window.scrollTo(0, 0);
		}
		if (location.hash != "") {
			select(location.hash.substr(1));
		}
		if (!visible) {
			select("file0");
		}
	})();
	</script>
</html>