<?
function TERM_LOG(){ if (!is_object($GLOBALS['TERM_LOG'])) $GLOBALS['TERM_LOG']=new i_term_log();return($GLOBALS['TERM_LOG']);}

class i_term_log {
    public $termd_time_offset = 5; // погрешность секунд для отображения terminald по конкретному МО
    public $log_type;
    public $label = []; // Массив с метками, хранятся временные значения типа начальная/конечная строки МО, id МО и т.д.
    public $log_content = []; // Массив для обработанного контента
    public $log_stats = []; // Массив для статистики по всему логу
    public $data_for_nav = []; // Массив для статистики и ссылок для навигации
    public $err_cat_descr = [
        'network' => 'Сетевые ошибки',
        'equipment' => 'Неисправности оборудования',
        'measure' => 'Некорректное измерение',
        'other' => 'Прочие ошибки',
        'cancel_timeout' => 'Истек таймаут медосмотра',
        'at_auth' => 'Ошибки при авторизации',
    ];

//    ======================================================================
//    ОПЕРАЦИИ С ФАЙЛАМИ
//    ======================================================================

    // получает список имен логов, имеющихся на терминале
    function get_term_log_names($term_ip) {
        $res = [];
        $term_response = IO()->doPut_v2(_MAIN_PROTOCOL.$term_ip.'/log'); //TODO: htpps replace $_MAIN_PROTOCOL
        if ($term_response['status'] == 200) {
            $res_json = json_decode($term_response['content'], true);
            foreach ($res_json as $name) {
                $res[] = $name;
            }
        } else {
            $res = ['terminal.log', 'terminal1.log', 'terminal2.log', 'terminal3.log', 'terminal4.log', 'terminald.log'];
        }

        return $res;
    }

    // получает исходный файл лога по переданному url
    function get_log_from_url($url, $filename='terminal') { // Получаем лог с терминала по переданному url
        $log_fname =_DIR_TO_ROOT.'/logs/'.date('d-m-Y_H:i:s', time()).'_'.$filename.'.log' ; // создаем директорию на диске под файл
        if (file_exists($log_fname)) unlink($log_fname) ; // Проверка на существования файла с таким же названием
        $res = IO()->doPut_v2($url,'','','','',['debug'=>1,'save_content_to_file'=>$log_fname]); // Дергаем лог с терминала, сохраняем в файл во временной директории

        if($res['status']=='200') {
            return $log_fname; // Если успешно, то возвращаем путь к файлу
        }
        return false;
    }

    // загрузка исходного лога на странице обработки логов
    function load_log($tmp_fname, $fname) {
        $log_fname = _DIR_TO_ROOT.'/logs/'.md5(rand(1,100000)).'_'.$fname; // создаем директорию на диске под файл
        if (move_uploaded_file($tmp_fname,$log_fname)) {
            echo basename($log_fname).'<br>';
            return $log_fname;
        }
        return false;
    }

    // сохраняет обработанные данные в формате json
    function save_as_json($content, $log_fname) {
        $path_to_save = _DIR_TO_ROOT.'/temp/'.$log_fname;
        $json_content = json_encode($content);
        if (file_exists($path_to_save)) unlink($path_to_save) ; // Проверка на существования файла с таким же названием
        file_put_contents($path_to_save, $json_content, LOCK_EX);

        return $path_to_save;
    }

//    ======================================================================
//    ОБРАБОТКА ДАННЫХ
//    ======================================================================

    // обрабатывает исходный лог, приводит к единому виду, преобразует в json, сохраняет в файл в директории temp
    function process_log($path_to_log, $options=[]) {
        $log_fname = basename($path_to_log);
        $this->get_log_type($path_to_log);

        if($this->log_type == 'terminald') {
            $this->terminald_log_handler($path_to_log, $options);
        } else {
            $this->terminal_log_handler($path_to_log, $options);
        }

        if($options['save_as_json']) {
            $log_file = [$this -> log_content, $this -> log_stats, $this->log_type];
            $json_log_fname = $this->save_as_json($log_file, $log_fname);
            return $json_log_fname;
        }
    }

    function process_json_file($path_to_file) { // передаем ссылку на файл преобразованного в json лога
        $json = file_get_contents($path_to_file); // сохраняем в переменную текстуху из файла
        $log_file = json_decode($json, true); // преобразуем json в ассоциативный массив, сохраняем в свойство класса
        $this->log_content = $log_file[0];
        $this->log_stats = $log_file[1];
        $this->log_type = $log_file[2];
//        damp_array($log_file);
    }

    // определяет тип лога на основе названия
    function get_log_type($log_fname) {
        if(mb_stripos($log_fname, 'terminald')) {
            $this -> log_type = 'terminald';
            return 'terminald';
        } else {
            $this -> log_type = 'terminal';
            return 'terminal';
        }
    }

    // обработка лога для типа terminal.log
    function terminal_log_handler ($log_fname) {
        $text = file_get_contents($log_fname); // Получаем содержимое файла
        $arr = explode("\n",$text); // Разбиваем на массив
//        $timestamp_mark = false; // Метка о том, что обработка находится в рамках выбранной даты

        foreach ($arr as $item) {
            $line_options = [];
            $char_number = mb_strpos($item,'{'); // Ищем открывающую фигурную скобку, чтоб отловить ответ терминала в формате JSON
            $timeline_mask = '\d{4}\-\d{2}\-\d{2}T\d{2}\:\d{2}\:\d{2}\.\d{3}Z';

            if ($char_number !== false) { // Если находим, то JSON в строке есть
                $string = mb_substr($item, $char_number, (mb_strlen($item) - $char_number)); // Берем подстроку с JSON
                $decode_str = json_decode($string, true); // Разбираем JSON на ассоциативный массив
                if (is_array($decode_str)) {
                    if($decode_str['args']['id']['medexam']) {
                        $line_options['mo_id'] = $decode_str['args']['id']['medexam'];
                    }
                    if($decode_str['args']['data']['debug'] || $decode_str['debug']) { // Если находим дебаг в декодироанной строке
                        $line_options['check_debug'] = 1; // Передаем метку о наличии дебага
                    }
                }
            }

            // В некоторых случаях в логе содержиться html разметка, которая ломает дальнейшую обработку в firefox
            $item = strip_tags($item); // Убираем возможные html теги из строки

            // Разбиваем строку на дату и текст лога
            $str_arr = explode(' - ', $item);

            if (_sizeof($str_arr) > 1) { // Если в массиве больше 1 элемента, значит строка разбилась правильно
                // Вырезаем символы от hard reset и добавляем метку
                if (preg_match('/^' . $timeline_mask . '\b/', $str_arr[0]) == 0) {
                    $arr = preg_split('/' . $timeline_mask . '/', $str_arr[0]);
                    $str = $str_arr[0];
                    foreach ($arr as $ar) {
                        $str = str_replace($ar, '', $str);
                    }
                    $str_arr[0] = $str;
                    $line_options['hard_reset'] = 1;
                }
                $val = $this->process_log_line($str_arr[0], $str_arr[1], $line_options);
                $this->log_content[] = $val;
                $this->get_stats_from_val($val);
            } else { // Если в массиве 1 элемент, значит там строка, относящаяся к предыдущей строке
                $this->log_content[_sizeof($this->log_content) - 1]['val'] .= '<br>' . $item; // Добавляем полученную строку к предыдущей
            }
        }
    }

    // обработка лога для типа terminald.log
    function terminald_log_handler ($log_fname, $options=[]) {
        $date_mask = '\d{4}\/\d{2}\/\d{2}'; // маска формата даты, нужна для обрезки нежелательных символов
        $text = file_get_contents($log_fname); // Получаем содержимое файла
        $arr = explode("\n",$text); // Разбиваем на массив

        //
        //
        foreach ($arr as $key=>$line) {
            if($line == '') continue;

            $line_arr = explode(' ' ,$line);
            $date = $line_arr[0];
            $time = $line_arr[1];
            unset($line_arr[0]); unset($line_arr[1]);

            if (preg_match('/^' . $date_mask . '\b/', $date) == 0) {
                $arr = preg_split('/' . $date_mask . '/', $date);
                $str = $date;
                foreach ($arr as $ar) {
                    $str = str_replace($ar, '', $str);
                }
                $date = $str;
                $line_options['hard_reset'] = 1;
            }

            $tmp_date = explode('/',$date);
            $date = $tmp_date[2] . '-' . $tmp_date[1] . '-' . $tmp_date[0];

            $ts = strtotime($date.' '.$time);


            $content = implode(' ', $line_arr);
            $content = strip_tags($content);
//
//            if (_sizeof($options['timestamp'])) {
//                $timestamp = strtotime($this->timeline_to_datetime($timeline));
//
//                if ($timestamp >= $options['timestamp']['start'] && $timestamp <= $options['timestamp']['end']) {
//                    $this->log_content[] = $this->get_line_values($timeline, $content);
//                }
//            } else {
            $val = $this->process_d_log_line($ts, $content);
            $this->log_content[] = $val;
            $this->get_stats_from_val($val);
//            }
        }

    }

    // разбирает строку лога, ставим нужные метки,
    function process_log_line ($timeline, $val, $options=[]) {
        $timeline = trim($timeline, 'Z');
        $result  = [
            'timeline' => [
                'time' => $this->timeline_to_time($timeline),
                'date' => $this->timeline_to_date($timeline),
                'ts' => strtotime($timeline),
            ],
            'val' => $val,
        ];

//        echo '=========================<br>';
//        echo $timeline.'<br>';
//        echo $result['timeline']['time'].'<BR>';
//        echo $result['timeline']['date'].'<BR>';
//        echo $result['timeline']['ts'].'<BR>';

        if (stripos($val, 'info: Got hid auth') !== false || stripos($val, 'info: Got number') !== false || stripos($val, 'info: Got qr-code') !== false || stripos($val, 'info: faceidAuth: face matched:') !== false) { // Отлавливаем строку, с которой начинается медосмотр
            $this->label['start_from_line']=_sizeof($this->log_content); // Пихаем в массив с метками индекс начальной строки
        } elseif(stripos($val, 'info: Initializing readcard controller') !== false) { // Отлавливаем строку, на которой заканчивается медосмотр
            unset($this->label['start_from_line']); unset($this->label['cur_mo_id']); // Убираем значение начальной строки и id медосмотра из массива с метками
            $this->label['end_on_line']=_sizeof($this->log_content); // Пихаем в массив с метками индекс строки, на которой закончился медосмотр
        }

        if (stripos($val, 'info: ----------------------------------') !== false) {
            $result['check_reboot']=1;
        }

        //проверяем префикс строки
        $prefix_str_arr = explode(': ', $val);
        if ($prefix_str_arr[0] == 'error') {
            $result['add_error_css']=1;

            $result['error'] = $this->error_handler($val); // возвращает тип ошибки и краткое описание
        }

        if ($options['check_debug']) {
            $result['check_debug']=1;
        }

        // Присваиваем id строкам, относящимся к конкретному медосмотру
        if (isset($this->label['start_from_line'])) {
            if ($options['mo_id']) {
                if(!$this->label['cur_mo_id']) {
                    for ($i = $this->label['start_from_line']; $i < _sizeof($this->log_content); $i++) {
                        $this->log_content[$i]['mo_id'] = $options['mo_id'];
                    }
                }
                $this->label['cur_mo_id'] = $options['mo_id'];
            }

            if($this->label['cur_mo_id']) {
                $result['mo_id'] = $this->label['cur_mo_id'];
            }
        }

//        $this->get_stats_from_val($result);

        return $result;
    }

    function process_d_log_line($ts, $val, $options = []) {
        $result  = [
            'timeline' => [
                'time' => date('H:i:s', $ts),
                'date' => date('d-m-Y', $ts),
                'ts' => $ts,
            ],
            'val' => $val,
        ];

        if(mb_stripos($val,'not found') !== false) {
            $result['error'] = ['category' => 'equipment', 'type' => $val];
            $result['add_error_css']=1;
        } elseif (mb_stripos($val,'server') !== false and mb_stripos($val,'error') !== false) {
            $result['error'] = ['category' => 'network', 'type' => $val];
            $result['add_error_css']=1;
        }

        return $result;
    }



//    ======================================================================
//    СХЛОПЫВАНИЕ ПОВТОРЯЮЩИЕХСЯ СТРОК
//    ======================================================================

    function format_json_from_str($str) {
        if (preg_match('/\"date\":\"\d{4}\-\d{2}\-\d{2} \d{2}\:\d{2}\:\d{2}\"/',$str)) {
            $str=preg_replace('/,\"date\":\"\d{4}\-\d{2}\-\d{2} \d{2}\:\d{2}\:\d{2}\",/',',',$str);
            $str=preg_replace('/,\"date\":\"\d{4}\-\d{2}\-\d{2} \d{2}\:\d{2}\:\d{2}\"/','',$str);
            $str=preg_replace('/\"date\":\"\d{4}\-\d{2}\-\d{2} \d{2}\:\d{2}\:\d{2}\",/','',$str);
        }
        return $str;
    }

    function find_repeats() {
        $cur_pattern = []; // массив под текущий паттерн
        $collapses = []; // массив под данные о схлопываниях

        $log_start_from_key = array_keys($this -> log_content)[0];

        for ($g_key = $log_start_from_key; $g_key < _sizeof($this -> log_content); $g_key++) {
            $value=$this->log_content[$g_key];
            $char_num = strpos($value['val'], '{'); // ищем json
            $check_json = false;
            if(json_decode(substr($value['val'], $char_num))) {
                $check_json = true;
            }

            if($check_json) { // если есть json, то режем строку на текст и на json, если нет, то присваиваем в json пустую строку
                $g_str['text'] = substr($value['val'], 0, $char_num);
                $g_str['json'] = substr($this->format_json_from_str($value['val']), $char_num);
            } else {
                $g_str['text'] = $value['val'];
                $g_str['json'] = '';
            }

            // если текущий паттерн не пустой, то сравниваем строки со строками из текущего паттерна
            if(_sizeof($cur_pattern)) {
                if($this->compare_str_to_collapse($g_str, $cur_pattern['lines'][$cur_pattern['str_to_compare']])) {
//                    if(json_decode($cur_pattern['lines'][$cur_pattern['str_to_compare']]['json']) == json_decode($g_str['json'])) {
                    if ($cur_pattern['str_to_compare'] < $cur_pattern['str_cnt'] - 1) {
                        $cur_pattern['str_to_compare']++;
                    } else {
                        $this->compare_to_collapse = [];

                        $cur_pattern['str_to_compare'] = 0;

                        if (!_sizeof($collapses[$cur_pattern['start_str']])) {
                            $collapses[$cur_pattern['start_str']]['start_str'] = $cur_pattern['start_str'];
                            $collapses[$cur_pattern['start_str']]['start_time'] = $cur_pattern['start_time'];
                            $collapses[$cur_pattern['start_str']]['collapse_cnt'] = 1;
                            $collapses[$cur_pattern['start_str']]['str_cnt'] = $cur_pattern['str_cnt'];
                        }

                        $collapses[$cur_pattern['start_str']]['collapse_cnt']++;
                        $collapses[$cur_pattern['start_str']]['end_str'] = $g_key;
                        $collapses[$cur_pattern['start_str']]['end_time'] = $this->log_content[$g_key]['timeline']['date'] . ' ' . $this->log_content[$g_key]['timeline']['time'];

                        continue;
                    }
                } else {
                    $cur_pattern = [];

                    if ($this->compare_to_collapse) {
//                        $start_from_key=array_keys($this->compare_to_collapse)[0];
                        reset($this->compare_to_collapse);
                        $g_key = key($this->compare_to_collapse);
                        $this->compare_to_collapse=[];

//                        $g_key=$start_from_key-1;

//                        reset($$this->compare_to_collapse);
//                        $g_key=$start_from_key-1;

                        continue;
                    }
                }
            } else {
                if (_sizeof($this->compare_to_collapse)) {

                    foreach ($this->compare_to_collapse as $key => $str) {
                        if($this->compare_str_to_collapse($g_str, $str)) {
                            $cur_pattern['str_cnt'] = $g_key - $key;
                            $cur_pattern['start_str'] = $key;
                            $cur_pattern['start_time'] = $this->log_content[$key]['timeline']['date'] . ' ' . $this->log_content[$key]['timeline']['time'];
                            if($cur_pattern['str_cnt'] == 1) {
                                $cur_pattern['str_to_compare'] = 0;

                                if (!_sizeof($collapses[$cur_pattern['start_str']])) {
                                    $collapses[$cur_pattern['start_str']]['start_str'] = $cur_pattern['start_str'];
                                    $collapses[$cur_pattern['start_str']]['start_time'] = $cur_pattern['start_time'];
                                    $collapses[$cur_pattern['start_str']]['collapse_cnt'] = 1;
                                    $collapses[$cur_pattern['start_str']]['str_cnt'] = $cur_pattern['str_cnt'];
                                    $collapses[$cur_pattern['start_str']]['collapse_cnt']++;
                                    $collapses[$cur_pattern['start_str']]['end_str'] = $g_key;
                                    $collapses[$cur_pattern['start_str']]['end_time'] = $value['timeline'];
                                }
                            } else {
                                $cur_pattern['str_to_compare'] = 1;
                            }

                            for ($i = 0; $i < $cur_pattern['str_cnt']; $i++) {
                                $cur_pattern['lines'][] = [
                                    'text' => $this->compare_to_collapse[$key + $i]['text'],
                                    'json' => $this->compare_to_collapse[$key + $i]['json'],
                                ];
                            }

//                            damp_array($cur_pattern);
                            $this->compare_to_collapse = [];
                            break;
                        }
                    }
                }
            }

            $this->compare_to_collapse[$g_key] = [
                'text' => $g_str['text'],
                'json' => $g_str['json'],
            ];
        }

//        damp_array($collapses);

        return $collapses;
    }

    function collapse_repeats() {
        $collapses = $this->find_repeats();
        foreach ($collapses as $collapse) {
            $hide_from_line = $collapse['start_str'] + $collapse['str_cnt'];
            for($i = $hide_from_line; $i <= $collapse['end_str']; $i++) {
                unset($this->log_content[$i]);
            }
            for($i = $collapse['start_str']; $i < $collapse['start_str'] + $collapse['str_cnt']; $i++) {
//                $data[$i]['add_error_css'] = 1;
//                $data[$i]['check_collapsed_error'] = 1;

                $this->log_content[$i]['collapsed_group']['start_line'] = $collapse['start_str'];
                $this->log_content[$i]['collapsed_group']['end_line'] = $collapse['end_str'];
                $this->log_content[$i]['collapsed_group']['start_time'] = $collapse['start_time'];
                $this->log_content[$i]['collapsed_group']['end_time'] = $collapse['end_time'];
                $this->log_content[$i]['collapsed_group']['lines_cnt'] = $collapse['str_cnt'];
                $this->log_content[$i]['collapsed_group']['cnt'] = $collapse['collapse_cnt'];
            }
        }
    }

    function compare_str_to_collapse($g_str, $cur_str) {
        if($g_str['text'] == $cur_str['text'] and json_decode($g_str['json']) == json_decode($cur_str['json'])) {
            return true;
        } else {
            return false;
        }
    }

    // парсер ошибок лога
    function error_handler($str) {
        $str_as_arr = explode(': ', $str);
        trim($str_as_arr[1], ' \t\n\r\0\x0B');
        $prev_str = $this->log_content[_sizeof($this->log_content) - 1];
        $error_description = mb_substr($str, 7, 37);

        // ищем json в строке
        $char_number = mb_strpos($str,'{');
        if($char_number !== false) $decode_str = json_decode(mb_substr($str, $char_number, (mb_strlen($str) - $char_number)), true);

        $result=[
            'type' => $str_as_arr[1],
            'description' => $error_description
        ];

        if(mb_stripos($str_as_arr[1], 'measure') !== false) {
            if(mb_stripos($str, 'Request failed with status code 500') !== false) {
                $result['category'] = 'equipment';
                return $result;
            } else {
                if(mb_stripos($prev_str['val'], 'Request failed with status code 500') !== false) {
                    unset($this->log_content[_sizeof($this->log_content) - 1]['error']);
                }
            }
        }

        if(mb_stripos($str, 'EHOSTUNREACH') !== false or mb_stripos($str, 'ECONNREFUSED') !== false) {
            $result['category'] = 'network';
            return $result;
        }

        switch ($str_as_arr[1]) {
            case 'measureAlcotester' :
                if (is_array($decode_str)) {
                    if ($decode_str['error'] == []) {
                        $result['category'] = 'equipment';
                        if(mb_stripos($prev_str['val'], 'Request failed with status code 500')) unset($this->log_content[_sizeof($this->log_content) - 1]['error']);
                    } else {
                        $result['category'] = 'measure';
                    }
                }
                break;

            case 'measurePupilometer' :
                if(mb_stripos($str, 'measure error') !== false) $result['category'] = 'measure';
                if(mb_stripos($str, '%s') !== false) $result['category'] = 'equipment';
                break;

            case 'doAlcotesterMeasure' :
                if (mb_stripos($str, 'timeout')) {
                    $result['category'] = 'measure';
                } elseif (mb_stripos($str, 'error undefined')) {
                    $result['category'] = 'equipment';
                    if ($prev_str['error'] and mb_stripos($prev_str['val'], 'timeout')) unset($this->log_content[_sizeof($this->log_content) - 1]['error']);
                }
                break;

            case 'doThermometerMeasure' :
                if(mb_stripos($str, 'data error') or (mb_stripos($str, 'thermometer error') and mb_stripos($str, 'timeout'))) $result['category'] = 'equipment';
                break;

            case 'doTonometerMeasure' :
                if(mb_stripos($str, 'equipmentError') !== false) {
                    $result['category'] = 'equipment';
                } elseif (mb_stripos($str, 'equipmentError') !== false) {

                }
                break;

            case 'doExam' :
                if($str_as_arr[2] == 'exam failed with error' and mb_strpos($str,'cancelTimeout')) {
                    if(mb_stripos($prev_str['val'], 'cancelTimeout') !== false) {
                        $this->log_content[_sizeof($this->log_content) - 1]['error']['category'] = 'cancel_timeout';
                        return;
                    } else {
                        $result['category'] = 'cancel_timeout';
                    }
                }
                break;

            case 'Can\'t reinitialize alcotester. Rebooting system' :
                if(mb_stripos($prev_str['val'], 'alcotesterCheck') !== false) {
                    return;
                } else {
                    $result['category'] = 'equipment';
                }
                break;

            case 'Webcam' :
                if(mb_stripos($str, 'stream inactive')) $result['category'] = 'equipment';
                break;

            case 'Got unknown scan code 16' :
                $result = [];
                break;

            case 'powerOn' :
            case 'Socket.io' :
                $result['category'] = 'network';
                if($str_as_arr[1] == 'powerOn' and mb_stripos($prev_str['val'], 'error: Client.send') !== false) unset($this->log_content[_sizeof($this->log_content) - 1]['error']);
                break;

            case 'Device initialization error' :
            case 'Webcam.initialize' :
            case 'alcotesterCheck' :
            case 'tonometerCheck' :
            case 'pupilometerCheck' :
            case 'thermometerCheck' :
            case 'webcamCheck' :
                $result['category'] = 'equipment';
                if(mb_stripos($prev_str['val'], 'can\'t initialize devices')) unset($this->log_content[_sizeof($this->log_content) - 1]['error']);
                break;

            case 'Error' :
                if(mb_stripos($str, 'Network Error')) $result['category'] = 'network';
                break;

            default :
                $result['category'] = 'other';
                break;

        }

        if(_sizeof($result) and !$result['category']) $result['category'] = 'other';

        return $result;
    }

//    ======================================================================
//    ФИЛЬТР ПРЕОБРАЗОВАННЫХ ДАННЫХ ЛОГА
//    ======================================================================

    function filter_log_data($options=[]) {
        $data = $this->log_content;
        if($options['by_mo']) {
            foreach ($data as $i => $val) {
                if($val['mo_id'] != $options['by_mo']) unset($data[$i]);
            }
            $data = array_values($data);
        } elseif ($options['by_timestamp']) {

            foreach ($data as $i => $val) {
                $cur_ts = $val['timeline']['ts'];

                if($cur_ts < $options['by_timestamp']['start'] or $cur_ts > $options['by_timestamp']['end']) {
                    unset($data[$i]);
                } else {
                    continue;
                }
            }
            $data = array_values($data);
//            damp_array($data);
        }
        $this->log_content = $data;
    }

//    ======================================================================
//    СБОР СТАТИСТИКИ
//    ======================================================================

    function get_stats_from_val($val) {
//        damp_array($val);
//        echo'===== '.$val['timeline']['date'].'<br>';

        if(!$this->log_stats['reboots_cnt']) $this->log_stats['reboots_cnt'] = 0;


        // собираем статистику по загрузкам интерфейса со всего лога
        if ($val['check_reboot']) {
            $this->log_stats['reboots_cnt']++;
        }

        // собираем статистику по ошибкам со всего лога
        if ($val['error']) {
            if ($val['collapsed_group']) {
                $this->log_stats['errors']['cnt'] += $val['collapsed_group']['cnt'];
                $this->log_stats['errors']['categories'][$val['error']['category']] += $val['collapsed_group']['cnt'];
                //                    $stats['errors']++;
            } else {
                $this->log_stats['errors']['cnt']++;
                $this->log_stats['errors']['categories'][$val['error']['category']]++;
            }
        }

        // собираем статистику по медосмотрам со всего лога
        if($val['mo_id']) {
            $this->label['mo_ids'][$val['mo_id']]++;

            $this->log_stats['mo']['cnt'] = _sizeof($this->label['mo_ids']);
            $this->log_stats['mo']['ids'][$val['mo_id']]['lines_cnt']++;
            if($val['error']) {
                $this->log_stats['mo']['ids'][$val['mo_id']]['errors']['cnt']++;
                $this->log_stats['mo']['ids'][$val['mo_id']]['errors']['categories'][$val['error']['category']]++;
            }
            if($val['check_debug']) $this->log_stats['mo']['ids'][$val['mo_id']]['debug']++;
        }

        // создаем массивы под даты и часы
        if(!is_array($this->log_stats['dates'][$val['timeline']['date']])) {
            $this->log_stats['dates'][$val['timeline']['date']] = [
                'errors' => ['cnt' => 0],
                'mo' => ['cnt' => 0],
                'reboot_cnt' => 0,
            ];
        }

        // считаем ошибки для определенной даты
        if($val['error']) {
            $this->log_stats['dates'][$val['timeline']['date']]['errors']['cnt']++;
            $this->log_stats['dates'][$val['timeline']['date']]['errors']['categories'][$val['error']['category']]++;
        }

        // считаем медосмотры для определенной даты
        if($val['mo_id']) {
            $this->log_stats['dates'][$val['timeline']['date']]['mo']['ids'][$val['mo_id']]++;
            $this->log_stats['dates'][$val['timeline']['date']]['mo']['cnt'] = _sizeof($this->log_stats['dates'][$val['timeline']['date']]['mo']['ids']);
        }

        // считаем перезагрузки интерфейса
        if ($val['check_reboot']) {
            $this->log_stats['dates'][$val['timeline']['date']]['reboot_cnt']++;
        }

        // работаем с часами по дате
        for($i = 00; $i <= 23; $i++) {
            $ts_hour_start = strtotime($val['timeline']['date'].' '.$i.':00:00');
            $ts_hour_end = $ts_hour_start + (60 * 60 - 1);

            // если нет массива под конкретный час, то создаем
            if(!is_array($this->log_stats['dates'][$val['timeline']['date']]['hours'][$i])) {
                $this->log_stats['dates'][$val['timeline']['date']]['hours'][$i] = [
                    'errors' => [
                        'cnt' => 0,
                        'categories' => ['network' => 0, 'equipment' => 0, 'measure' => 0, 'other' => 0, 'cancel_timeout' => 0],
                    ],
                    'mo_cnt' => 0,
                    'reboot_cnt' => 0,

                ];
            // наполняем массив каждого часа
            }
            if($val['timeline']['ts'] >= $ts_hour_start and $val['timeline']['ts'] <= $ts_hour_end) {
                if($val['error']) {
                    $this->log_stats['dates'][$val['timeline']['date']]['hours'][$i]['errors']['categories'][$val['error']['category']]++;
                    $this->log_stats['dates'][$val['timeline']['date']]['hours'][$i]['errors']['cnt']++;
                }
                if($val['mo_id']) {
                    $this->label['dates'][$val['timeline']['date']]['hours'][$i]['mo_ids'][$val['mo_id']]++;
                    $this->log_stats['dates'][$val['timeline']['date']]['hours'][$i]['mo_cnt'] = _sizeof($this->label['dates'][$val['timeline']['date']]['hours'][$i]['mo_ids']);
                }
                if($val['check_reboot']) {
                    $this->log_stats['dates'][$val['timeline']['date']]['hours'][$i]['reboot_cnt']++;
                }
            }
        }
    }

    // возвращает массив с временными метками начала/конца периода
    // без опций разбивает переданный массив $data на периоды по часам
    // $options[time_step][1d] разбивает на дни
    // $options[all_from_to_hours] разбивает день на
    function get_ts_for_periods($data, $options=[]) {
//        damp_array($data);
        $result=[];

        if ($options['all_from_to_hours'] and (!$options['time_step'] or $options['time_step']=='1h')) {
            $date_from_string=explode('T',$data[0]['timeline'])[0];

            end($data);
            $date_to_string=explode('T',$data[key($data)]['timeline'])[0];

            $date_from=strtotime($date_from_string);
            $date_to=strtotime($date_to_string)+24*60*60;

            for ($time_i=$date_from;$time_i<$date_to;$time_i+=60*60) {
                $result[date('d-m H:i',$time_i)]=[
                    'start' => $time_i,
                    'end' => $time_i + (60 * 60 - 1),
                ];
            }
        }

        foreach ($data as $val) {
            $tmp_time = explode('T', $val['timeline']);
            $time = explode(":", $tmp_time[1]);
            $date = explode("-", $tmp_time[0]);
            switch ($options['time_step']) {
                case '5s': break;
                case '1d':
                    $res_time = $date[2].'-'.$date[1].'-'.$date[0];
                    $time_stamp_start=strtotime($res_time.' 00:00:00');
                    $time_stamp_end=$time_stamp_start+24*60*60-1;
                    break;
                case '1h':
                default:
                    $res_time = $date[2].'-'.$date[1].' '.$time[0].':00';
                    $time_stamp_start = strtotime($date[2] . '-' . $date[1] . '-' . $date[0] . ' ' . $time[0] . ':00:00');
                    $time_stamp_end = $time_stamp_start + 60 * 60 - 1;
                    break;
            }

            if (!_sizeof($result[$res_time])) {
                $result[$res_time]['start']=$time_stamp_start;
                $result[$res_time]['end']=$time_stamp_end;
            }
//            if(!_sizeof($result[$res_time])) $result[$res_time] = [];

        }

        return $result;
    }

//    ======================================================================
//    ФОРМИРОВАНИЕ И ВЫВОД ГРАФИКА
//    ======================================================================

    function day_log_graf($stats) {
        ?>
        <script type="text/javascript">
            FusionCharts.ready(function(){
                var revenueChart2 = new FusionCharts({
                    type: 'mscombi2d',
                    renderAt: 'chart-container',
                    width: '700',
                    height: '400',
                    dataFormat: 'json',
                    dataSource: {
                        "chart": {
                            //"caption": "<?echo $caption?>",
                            //"subCaption": "от <? echo date('d.m.Y',$time_from).' до '.date('d.m.Y',$time_to)?>",
                            "xAxisname": "Время",
                            "pYAxisName": "Кол-во МО",
                            "sYAxisName": "Кол-во ошибок",
                            "numberPrefix": "",
                            "sNumberSuffix" : "",
                            //"sYAxisMaxValue" : "50",
                            // "sYAxisMaxValue" : "15",

                            //Cosmetics
                            "paletteColors" : "#99d9db,#FF0000,#354FFF,#D600FF,#f2c500,#29B914",
                            "baseFontColor" : "#333333",
                            "baseFont" : "Helvetica Neue,Arial",
                            "captionFontSize" : "14",
                            "subcaptionFontSize" : "14",
                            "subcaptionFontBold" : "0",
                            "showBorder" : "0",
                            "bgColor" : "#ffffff",
                            "showShadow" : "0",
                            "canvasBgColor" : "#ffffff",
                            "canvasBorderAlpha" : "0",
                            "divlineAlpha" : "100",
                            "divlineColor" : "#999999",
                            "divlineThickness" : "1",
                            "divLineIsDashed" : "1",
                            "divLineDashLen" : "1",
                            "divLineGapLen" : "1",
                            "usePlotGradientColor" : "0",
                            "showplotborder" : "0",
                            "showXAxisLine" : "1",
                            "xAxisLineThickness" : "1",
                            "xAxisLineColor" : "#999999",
                            "showAlternateHGridColor" : "0",
                            "showAlternateVGridColor" : "0",
                            "legendBgAlpha" : "0",
                            "legendBorderAlpha" : "0",
                            "legendShadow" : "0",
                            "legendItemFontSize" : "10",
                            "legendItemFontColor" : "#666666",
                            "flatScrollBars": "1",
                            "scrollShowButtons": "0",
                            "scrollColor": "#cccccc",
                        },
                        "categories": [{
                            "category": [
                                <?foreach($stats as $date => $val) {
                                echo '{ "label": "'.$date.'" },' ;
                            }?>
                            ]
                        }],
                        "dataset": [
                            {
                                "seriesName": "Количество МО",
                                "showValues": 1,
                                "data": [<?foreach($stats as $val) echo '{ "value" : "'.$val['mo_cnt'].'" },'?>]
                            },
                            {
                                "seriesName": "Сетевые ошибки",
                                "parentYAxis": "S",
                                "renderAs": "line",
                                "showValues": "0",
                                "divlineThickness" : "1",
                                "data": [<?foreach($stats as $val) echo '{ "value" : "'.$val['errors']['categories']['network'].'" },'?>]
                            },
                            {
                                "seriesName": "Ошибки оборудования",
                                "parentYAxis": "S",
                                "renderAs": "line",
                                "showValues": "0",
                                "divlineThickness" : "1",
                                "data": [<?foreach($stats as $val) echo '{ "value" : "'.$val['errors']['categories']['equipment'].'" },'?>]
                            },
                            {
                                "seriesName": "Ошибки измерения",
                                "parentYAxis": "S",
                                "renderAs": "line",
                                "showValues": "0",
                                "divlineThickness" : "1",
                                "data": [<?foreach($stats as $val) echo '{ "value" : "'.$val['errors']['categories']['measure'].'" },'?>]
                            },
                            {
                                "seriesName": "Прочие ошибки",
                                "parentYAxis": "S",
                                "renderAs": "line",
                                "showValues": "0",
                                "divlineThickness" : "1",
                                "data": [<?foreach($stats as $val) echo '{ "value" : "'.$val['errors']['categories']['other'].'" },'?>]
                            },
                            {
                                "seriesName": "Таймаут",
                                "parentYAxis": "S",
                                "renderAs": "line",
                                "showValues": "0",
                                "divlineThickness" : "1",
                                "data": [<?foreach($stats as $val) echo '{ "value" : "'.$val['errors']['categories']['cancel_timeout'].'" },'?>]
                            }
                        ]
                    }
                });

                revenueChart2.render();
            });
        </script>
    <?}

    function log_graf($stats) {
        ?>
        <script type="text/javascript">
            FusionCharts.ready(function(){
                var revenueChart2 = new FusionCharts({
                    type: 'mscombi2d',
                    renderAt: 'chart-container',
                    width: '700',
                    height: '400',
                    dataFormat: 'json',
                    dataSource: {
                        "chart": {
                            //"caption": "<?echo $caption?>",
                            //"subCaption": "от <? echo date('d.m.Y',$time_from).' до '.date('d.m.Y',$time_to)?>",
                            "xAxisname": "Время",
                            "pYAxisName": "Кол-во МО",
                            "sYAxisName": "Кол-во ошибок",
                            "numberPrefix": "",
                            "sNumberSuffix" : "",
                            //"sYAxisMaxValue" : "50",
                            // "sYAxisMaxValue" : "15",

                            //Cosmetics
                            "paletteColors" : "#99d9db,#FF0000,#f2c500",
                            "baseFontColor" : "#333333",
                            "baseFont" : "Helvetica Neue,Arial",
                            "captionFontSize" : "14",
                            "subcaptionFontSize" : "14",
                            "subcaptionFontBold" : "0",
                            "showBorder" : "0",
                            "bgColor" : "#ffffff",
                            "showShadow" : "0",
                            "canvasBgColor" : "#ffffff",
                            "canvasBorderAlpha" : "0",
                            "divlineAlpha" : "100",
                            "divlineColor" : "#999999",
                            "divlineThickness" : "1",
                            "divLineIsDashed" : "1",
                            "divLineDashLen" : "1",
                            "divLineGapLen" : "1",
                            "usePlotGradientColor" : "0",
                            "showplotborder" : "0",
                            "showXAxisLine" : "1",
                            "xAxisLineThickness" : "1",
                            "xAxisLineColor" : "#999999",
                            "showAlternateHGridColor" : "0",
                            "showAlternateVGridColor" : "0",
                            "legendBgAlpha" : "0",
                            "legendBorderAlpha" : "0",
                            "legendShadow" : "0",
                            "legendItemFontSize" : "10",
                            "legendItemFontColor" : "#666666",
                            "flatScrollBars": "1",
                            "scrollShowButtons": "0",
                            "scrollColor": "#cccccc",
                        },
                        "categories": [{
                            "category": [
                            <?foreach($stats as $date => $val) {
                                echo '{ "label": "'.$date.'" },' ;
                            }?>
                            ]
                        }],
                        "dataset": [
                            {
                                "seriesName": "Количество МО",
                                "showValues": 1,
                                "data": [<?foreach($stats as $val) echo '{ "value" : "'.$val['mo_cnt'].'" },'?>]
                            },
                            {
                                "seriesName": "Ошибки",
                                "parentYAxis": "S",
                                "renderAs": "line",
                                "showValues": "0",
                                "divlineThickness" : "1",
                                "data": [<?foreach($stats as $val) echo '{ "value" : "'.$val['errors'].'" },'?>]
                            },
                            {
                                "seriesName": "Загрузка интерфейса",
                                "parentYAxis": "S",
                                "renderAs": "line",
                                "showValues": "0",
                                "divlineThickness" : "1",
                                "data": [<?foreach($stats as $val) echo '{ "value" : "'.$val['reboot_cnt'].'" },'?>]
                            }
                        ]
                    }
                });

                revenueChart2.render();
            });
        </script>
    <?}

//    ======================================================================
//    ФОРМИРОВАНИЕ ИНТЕРФЕЙСА
//    ======================================================================


// СНАЧАЛА ПРЕОБРАЗУЕМ ДАТУ К НОРМАЛЬНОМУ ЧИТАЕМОМУ ВИДУ, ПОТОМ РАБОТАЕМ С НЕЙ
// ТАКИМ ОБРАЗОМ ИЗБЕГАЕМ ОШИБОК С ЧАСОВЫМИ ПОЯСАМИ И РАЗНЫМИ ФОРМАТАМИ ДАТ В РАЗНЫХ ТИПАХ ЛОГОВ




    function print_log_header($fname, $options=[]) {
//        echo '-- at  print_log_header -- $fname = '.$fname.'<br>';
//        $content = $this->log_content;
        $dates = $this -> log_stats['dates'];
        $stats_for_graf = [];
//        damp_array($dates);
        foreach ($this->log_stats['dates'] as $date => $val) {
            $stats_for_graf[$date] = [
                'errors' => $val['errors']['cnt'],
                'mo_cnt' => $val['mo']['cnt'],
                'reboot_cnt' => $val['reboot_cnt'],
            ];
        }
//        damp_array($dates,1,-1);
        ?>
        <section class="log-header">
            <nav class="log-nav">
                <div class="log-nav_first">
                    <ul class="log-nav_list __dates js_log-dates">
                        <?foreach ($dates as $date => $val) {
                            $ts_start = strtotime($date.' 00:00:00');
                            $ts_end = $ts_start + (60 * 60 * 24 - 1);
                            ?>
                            <li class="log-nav_item">
                                <a href="" class="v2 log-nav_link" id="date-<?echo $date;?>" cmd="log/get_content_for_date" <?if($options['mo_card_btn']) echo 'mo_card_btn="1"'?> start="<?echo $ts_start?>" end="<?echo $ts_end?>" file="<? echo $fname?>" onclick="clickOnDateLink(this)">
                                    <?echo $date;?>
                                    <? if($this->log_type !== 'terminald') { ?>
                                        <span title="Медосмотры"><i class="fa fa-address-card" style="color: rgb(1, 161, 165); font-size: 15px;"></i> <?echo $this->log_stats['dates'][$date]['mo']['cnt']?></span>
                                    <?}?>
                                    <span title="Ошибки"><i class="fa fa-exclamation-triangle" style="color: #FF0000; font-size: 13px;"></i> <?echo $this->log_stats['dates'][$date]['errors']['cnt']?></span>
                                </a>
                            </li>
                        <?}?>
                    </ul>
                </div>

                <div class="log-nav_second" id="log_second_nav">
                    <span class="log-nav_placeholder">Выберите дату</span>
                </div>
            </nav>

            <div class="log-graf">
                <div id="chart-container" style="margin:0 auto;width:auto;">Подождите, идет построение графика</div>
                <? $this->log_graf($stats_for_graf); ?>
            </div>
        </section>
        <?
    }

    function print_log($options=[]) {


        $params = [];
//        if($options['mo_card_btn']) $params['mo_card_btn'] = 1;
        if($options['mo_card_btn']) $params['mo_card_btn'] = 1;

        $this->formation_log_table($params);

        ?>
        <a href="#" class="log_up-btn"><i class="fa fa-angle-up"></i></a>
        <?

    }

    function log_second_nav() {
//        damp_array($this->data_for_nav);
        ?>
        <ul class="log-nav_list">
            <?foreach ($this->data_for_nav as $val) {?>
            <li class="log-nav_item">
                <a href="#<?echo $val['line_id']?>" class="log-nav_link __second-nav <?if($val['type'] == 'error') echo 'red'; elseif($val['type'] == 'mo') echo 'lightgreen';?>">
                     <?if($val['type'] == 'mo') {?>
                        <div class="log-nav_line-num"><? echo '#'.$val['stats']['start_line'].'-'.$val['stats']['end_line'] ?></div>
                        <div class="log-nav_inf"><? echo 'МО №'.$val['stats']['id'] ?></div>
                        <div class="log-nav_indicators">
                            <?if(_sizeof($val['stats']['errors'])) foreach ($val['stats']['errors']['categories'] as $category => $cnt) {
                                $i_color = ''; //#FF0000,#354FFF,#D600FF,#f2c500,#29B914
                                switch ($category) {
                                    case 'network': $i_color = '#FF0000'; break;
                                    case 'equipment': $i_color = '#354FFF'; break;
                                    case 'measure': $i_color = '#D600FF'; break;
                                    case 'other': $i_color = '#f2c500'; break;
                                    case 'cancel_timeout': $i_color = '#29B914'; break;
                                    default: $i_color = '#fff'; break;
                                }?>
                                <div class="log-nav_indicator" title="<? echo $this->err_cat_descr[$category] ?>"> <div class="log-nav_indicator-icon"><i class="fa fa-exclamation-triangle" style="font-size: 11px; color: <?echo $i_color?>"></i></div>: <? echo $cnt?> </div>
                            <?}?>
                        </div>
                     <? } elseif ($val['type'] == 'error') { ?>
                        <div class="log-nav_line-num"><? echo '#'.$val['stats']['line'] ?></div>
                        <div class="log-nav_inf"><? echo $this->err_cat_descr[$val['stats']['category']] ?></div>
                        <div class="log-nav_indicators">
                            <div class="log-nav_indicator" title="<? echo $val['stats']['text'] ?>"> <div class="log-nav_indicator-icon"><i class="fa fa-info" style="color: blue;"></i></div> </div>
                            <? if($val['stats']['collapsed']) { ?>
                            <div class="log-nav_indicator" title="Схлопнуто повторов">
                                <div class="log-nav_indicator-icon"><i class="fa fa-clone" style="font-size: 10px; color: blue;"></i></div>: <? echo $val['stats']['collapsed']?>
                            </div>
                            <?}?>
                        </div>
                     <?}?>
                </a>
            </li>
            <?}?>
        </ul>
        <?
    }

    function formation_log_table($options=[]) {
//        damp_array($this->log_content, 1, -1);
//        damp_array($stats)
        $cur_mo_id = 0;
        $stats = $this->log_stats;
        ?>
        <div class="view-log">
            <div class="view-log_table">
                <div class="view-log_table-header">
                    <div class="view-log_td">№</div>
                    <div class="view-log_td">Дата</div>
                    <div class="view-log_td">Содержимое</div>
                    <div class="view-log_td">Доп. инф.</div>
                </div>

                <?
                foreach ($this -> log_content as $key => $val) {
                    if ($cur_mo_id) {
                        if (!$val['mo_id']) {
                            $html = ob_get_clean();
                            $this->data_for_nav[_sizeof($this->data_for_nav) -1]['stats']['end_line'] = $key - 1;?>
                            <div class="view-log_tr">
                                <div class="view-log_group __cut-content" id="mo-<?echo $cur_mo_id ?>" data-mo-id="<?echo $cur_mo_id ?>">
                                    <div class="view-log_tr __group-header">
                                        <div class="view-log_td">
                                            <?echo $this->label['start_line'];?> <br>
                                            <?echo $this->label['end_line'];?>
                                        </div>
                                        <div class="view-log_td">
                                            От <?
                                            echo $this->label['start_time'] ?><br>
                                            До <?
                                            echo $this->label['end_time'] ?>
                                        </div>
                                        <div class="view-log_td">Медосмотр №<?
                                            echo $cur_mo_id ?></div>
                                        <div class="view-log_td __indicators">
                                            <?if($options['mo_card_btn']) {?><div class="view-log_indicator"><a href="/cab/mo/<?echo $cur_mo_id?>/" class="new_window" target="_blank"><i class="fa fa-id-card" title="Перейти в карточку МО" style="color: rgb(1, 161, 165); margin-right: 3px;"></i></a></div><?}?>
                                            <?if(_sizeof($this->log_stats['mo']['ids'][$cur_mo_id]['errors']['categories'])) {
                                                foreach ($this->log_stats['mo']['ids'][$cur_mo_id]['errors']['categories'] as $cat => $cnt) {
                                                    switch ($cat) {
                                                        case 'network': $i_color = '#FF0000';  break;
                                                        case 'equipment': $i_color = '#354FFF'; break;
                                                        case 'measure': $i_color = '#D600FF'; break;
                                                        case 'other': $i_color = '#f2c500'; break;
                                                        case 'cancel_timeout': $i_color = '#29B914'; break;
                                                        default: $i_color = '#fff'; break;
                                                    } ?>

                                                    <div class="view-log_indicator" title="<?echo $this->err_cat_descr[$cat];?>">
                                                        <i class="fa fa-exclamation-triangle" style="color: <?echo $i_color?>; font-size: 13px;"></i>
                                                        : <? echo $cnt; ?>
                                                    </div>

                                                <?}
                                            };?>
                                            <?if($this->label['debug_counter'] != 0) {?> <div class="view-log_indicator" title="Дебаг в ответе сервера"><i class="fa fa-code" style="color: orangered;"></i>: <?echo $this->label['debug_counter']; ?></div><?};?>
                                        </div>
                                    </div>
                                    <?
                                    echo $html ?>
                                </div>
                            </div>
                            <?
                            $cur_mo_id = 0;

                            $this->label['err_counter'] = 0;
                            $this->label['$debug_counter'] = 0;
                        }
                    } else {
                        if ($val['mo_id']) {
                            $cur_mo_id = $val['mo_id'];

                            $this->data_for_nav[] = [
                                'type' => 'mo',
                                'line_id' => 'mo-'.$cur_mo_id,
                                'stats' => [
                                    'id' => $cur_mo_id,
                                    'errors' => $this->log_stats['mo']['ids'][$cur_mo_id]['errors'],
                                    'debug' => $this->log_stats['mo']['ids'][$cur_mo_id]['debug'],
                                    'start_line' => $key,
                                ],
                            ];

                            ob_start();
//                                $this->formation_log_str($key, $val);
                            $this->label['err_counter'] = 0;
                            $this->label['debug_counter'] = 0;
                            $this->label['start_time'] = $val['timeline']['time'];
                            $this->label['start_line'] = $key;
                        }
                    }
                    $this->formation_log_str($key, $val);
                }

                if ($cur_mo_id) {
                    $html=ob_get_clean();
                    echo '<div class="view-log_group" data-log-id="'.$cur_mo_id.'">'.$html.'</div>';
                    $cur_mo_id=0;
                }
                ?>
            </div>
        </div>


        <?
    }

    function formation_log_str($i, $o) {
//        damp_array($o);
        if($o['error']) {
            if(!$o['mo_id']) {
                $this->data_for_nav[] = [
                    'type' => 'error',
                    'line_id' => 'str-'.$i,
                    'stats' => ['line' => $i, 'category' => $o['error']['category'], 'text' => $o['val'],],
                ];
            }
            if($o['collapsed_group']) {
                if(!$o['mo_id']) $this->data_for_nav[_sizeof($this->data_for_nav) - 1]['stats']['collapsed'] = $o['collapsed_group']['cnt'];
                $this->label['err_counter'] += $o['collapsed_group']['cnt'];
            } else {
                $this->label['err_counter']++;
            }
        }
        if($o['check_debug']) $this->label['debug_counter']++;
        $this->label['end_time'] = $o['timeline']['time'];
        $this->label['end_line'] = $i;

        if($o['check_error'] == 1) {
            $type = 'error';
        } elseif ($o['check_debug'] == 1) {
            $type = 'debug';
        };

        ?>
        <div class="view-log_tr <?
            if($o['add_error_css']) echo ' __error';
            if($o['collapsed_group']) echo ' __collapsed';
        ?>" id="str-<?echo $i?>" <?if($type) {?>data-type="<?echo $type?>"<?}?>>
            <div class="view-log_td">
                <?
                if($o['collapsed_group']) {
                    if($i == $o['collapsed_group']['start_line']) {
                        echo $o['collapsed_group']['start_line'];
                    } elseif ($i == $o['collapsed_group']['start_line'] + $o['collapsed_group']['lines_cnt'] - 1) {
                        echo $o['collapsed_group']['end_line'];
                    } else {
                        echo '...';
                    }
                } else  {
                    echo $i;
                }
                ?>
            </div>
            <div class="view-log_td">
                <?
                if($o['collapsed_group']) {
                    if($i == $o['collapsed_group']['start_line']) {
                        echo $o['collapsed_group']['start_time'];
                    } elseif ($i == $o['collapsed_group']['start_line'] + $o['collapsed_group']['lines_cnt'] - 1) {
                        echo $o['collapsed_group']['end_time'];
                    } else {
                        echo '...';
                    }
                } else  {
                    echo $o['timeline']['date'] . ' ' . $o['timeline']['time'];
                }
                ?>
            </div>
            <div class="view-log_td">
                <p class="view-log_val-text"><? echo $this->log_text_handler($o['val']); ?></p>
            </div>
            <div class="view-log_td __indicators">
                <?
                if($o['error']) {
                    switch ($o['error']['category']) {
                        case 'network': $i_color = '#FF0000';  break;
                        case 'equipment': $i_color = '#354FFF'; break;
                        case 'measure': $i_color = '#D600FF'; break;
                        case 'other': $i_color = '#f2c500'; break;
                        case 'cancel_timeout': $i_color = '#29B914'; break;
                        default: $i_color = '#fff'; break;
                    }?>

                    <div class="view-log_indicator"><i class="fa fa-exclamation-triangle" style="color: <?echo $i_color;?>;" title="<?echo $this->err_cat_descr[$o['error']['category']];?>"></i></div>
                <?}

                if($o['collapsed_group']) {
                    if($i == $o['collapsed_group']['start_line']) { ?>
                        <div class="view-log_indicator" title="Свернуто повторений">
                            <i class="fa fa-clone" style="color: blue;font-size: .95em;"></i>: <?echo $o['collapsed_group']['cnt'];?></div>
                    <? }
                }

                if($o['check_reboot'] == 1) {?>
                    <div class="view-log_indicator" title="Загрузка интерфейса"><i class="fa fa-repeat" style="color: orangered;"></i></div>
                <?}
                if($o['check_debug']) {?>
                    <div class="view-log_indicator" title="Дебаг в ответе сервера"><i class="fa fa-code" style="color: orangered;"></i></div>
                <?}?>

            </div>
        </div>
        <?
    }

//    ======================================================================
//    ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ
//    ======================================================================

    // обрезает длинну строки
    function log_text_handler ($val) {
        $str_l = 150;

        if(mb_stripos( $val,'Client.send: sending') !== false) {
            $tmp_str = explode(": ", $val);
            $tmp_substr = explode(" ", $tmp_str[2]);
            $tmp_substr[1] = '<i class="highlight">'.$tmp_substr[1].'</i>';
            $tmp_str[2] = implode(' ', $tmp_substr);
            $val = implode(': ', $tmp_str);
        }

        if ( mb_strlen($val) > $str_l ) {
            $short_val = mb_substr($val, 0, $str_l);
            $cut_val = mb_substr($val, $str_l);
            return '<span>'.$short_val.'</span><span class="cut_val" style="display: none;">'.$cut_val.'</span> <a class="show_cut_val" href="#" data-val-cut="1">Показать</a>';
        } else {
            return $val;
        }
    }


    function timeline_to_datetime($val) {
        $datetime = explode('T', $val);
        $date = explode("-", $datetime[0]);
        $date = $date[2].'-'.$date[1].'-'.$date[0];
        return $date.' '.trim($datetime[1], 'Z');
    }

    function timeline_to_time($val, $options=[]) {
        $time = explode('T', $val)[1];

        if($options['round'] == 'h') {
            $time_tmp = explode(':', $datetime);
            $time = $time_tmp[0];
        };

        return $time;
    }

    function timeline_to_date($val) {
        $datetime = explode('T', $val);
        $date = explode("-", $datetime[0]);
        $date = $date[2].'-'.$date[1].'-'.$date[0];
        return $date;
    }

    function get_timeline_termd($time, $date) {
        $date = explode('/', $date);
        $date = implode('-', $date);
        $res = $date.'T'.$time.'Z';
        return $res;
    }

    function calc_elapsed_time($start_time, $end_time) {
        return $this->timeline_to_time($end_time) - $this->timeline_to_time($start_time);
    }

    function load_log_panel() {?>
        <form method="POST" enctype="multipart/form-data" class="sky-form no_box_shadow log-upload">
            <div class="log-upload_btn">
                <label for="file-upload-1">Выберите файл</label>
                <input type="file" name="source_log" id="file-upload-1">
            </div>
            <input class="button v1 ok" id="file-upload-submit" type="submit" cmd="load_log" value="Обработать лог" style="visibility: hidden;" >
        </form>
    <?}

    function get_zip_link($term_id) { ?>
        <button class="button v2 view-log_btn" cmd="log/get_zip_term_logs" term_id="<?echo $term_id?>" onclick="$j('#load_gif').removeAttr('hidden')">Получить все логи с терминала</button>
    <?}

}
?>
