<template>
  <div class="app-container">

    <el-row v-if="isAdmin">
      <el-col :span="6">
        <div class="left-filter">
          <el-row>
            <el-col :span="12">
              <el-select v-model="parms.type" size="small" @change="ChangeType">
                <el-option value="application" label="项目"></el-option>
                <el-option value="tomcat" label="Tomcat"></el-option>
              </el-select>
            </el-col>
            <el-col :span="12">
              <el-input v-model="fileName" placeholder="筛选" clearable size="small" prefix-icon="el-icon-search"/>
            </el-col>
          </el-row>
        </div>
        <div class="left-tree">
          <el-tree
            style="overflow: auto;"
            :data="logsTree"
            :props="treeProps"
            node-key="path"
            :filter-node-method="FilterLogs"
            ref="tree"
            :default-expand-all="false"
            @node-click="OpenLog"
            highlight-current
            accordion
          />
        </div>
      </el-col>

      <el-col :span="18">
        <el-form :model="parms" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
          <el-form-item label="读取方式" prop="scheme">
            <el-radio-group v-model="parms.scheme" size="small" @change="ChangeScheme">
              <el-radio label="line">按行</el-radio>
              <el-radio label="byte">按字节</el-radio>
            </el-radio-group>
          </el-form-item>
          <el-form-item label="关键词" prop="keyword">
            <el-input
              v-model="parms.keyword"
              placeholder="关键词"
              clearable
              size="small"
              @keyup.enter.native="GetPage(1)"
            />
          </el-form-item>
          <el-form-item label="" prop="nu">
            <el-checkbox v-model="parms.nu">显示行号</el-checkbox>
          </el-form-item>
          <el-form-item>
            <el-button type="primary" icon="el-icon-search" size="mini" @click="GetPage" v-hasPermi="['sys:qwertyasd123456:log']">搜索</el-button>
            <el-button icon="el-icon-refresh" size="mini" @click="ResetQuery">重置</el-button>
          </el-form-item>
        </el-form>

        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
            <el-button type="warning" plain icon="el-icon-download" size="mini" @click="RequestDownload" v-hasPermi="['sys:qwertyasd123456:log']" :disabled="!parms.path" :loading="downloading">下载</el-button>
          </el-col>
          <right-toolbar :showSearch.sync="showSearch" @queryTable="GetLog"></right-toolbar>
        </el-row>

        <div :loading="loading">
          <div class="main-header">{{title}}</div>

          <code class="hljs code" v-html="Highlight(content)"></code>
        </div>

        <div class="main-footer" v-if="total > 0 && parms.path">
          <el-pagination
            @size-change="(val) => GetPage(0, val)"
            @current-change="(val) => GetPage(val)"
            :current-page="parms.line"
            :page-sizes="pageSizes"
            :page-size="parms.count"
            layout="prev, pager, next, jumper, sizes, total"
            :total="total"
          >
          </el-pagination>
        </div>
      </el-col>
    </el-row>
  </div>
</template>

<script>
import request from "@/utils/request";
import hljs from "highlight.js/lib/highlight";
import "highlight.js/styles/github-gist.css";
import {formatFileSize, save} from "@/utils/file";
import {date_format} from "@/utils/date";
import {IS_STABLE} from "@/utils/dev";

const LOG_API = '/qwertyasd123456/log';
const FILE_OVER_LIMIT = 1024 * 1024 * 10;
const DOWNLOAD_FILE_LIMIT = 1024 * 1024 * 30;
const DEFAULT_LINE_COUNT = 30;
const DEFAULT_BYTE_COUNT = 2048;

const SCHEME_LINE = 'line';
const SCHEME_BYTE = 'byte';

function gen_log_hl(keyword) {
  return function(hljs) {
    let contains = [
      {
        className: 'number',
        variants: [
          { begin: '(?<=^\\d*:? ?)\\d{2}:\\d{2}:\\d{2}\\.\\d{3}' },
        ]
      }, // 时间
      {
        className: 'comment',
        variants: [
          { begin: '(?<=^\\d*:? ?\\d{2}:\\d{2}:\\d{2}\\.\\d{3} )\\[', end: '\\]' },
        ]
      }, // 线程
      {
        className: 'deletion',
        variants: [
          { begin: '(?<=^\\d*:? ?\\d{2}:\\d{2}:\\d{2}\\.\\d{3} \\[.*\\] [A-Z]+ \\s?)\\S+', end: '(?= - )' },
        ]
      }, // 类
      {
        className: 'addition',
        variants: [
          { begin: '(?<=^\\d*:? ?\\d{2}:\\d{2}:\\d{2}\\.\\d{3} \\[.*\\] .* - )\\[', end: '\\](?= - )' },
        ]
      }, // [方法,行号]
      {
        className: 'name',
        variants: [
          { begin: '(?<=^\\d*:? ?)[a-zA-Z0-9.]+(Exception|Error)(?=:)' },
        ]
      }, // 异常
      {
        className: 'quote',
        variants: [
          { begin: '(?<=^\\d*:? ?[a-zA-Z0-9.]+(Exception|Error): ).*$' },
        ]
      }, // 异常message
      {
        className: 'name',
        variants: [
          { begin: '(?<=^\\d*:? ?\\s*)at(?= )' },
        ]
      }, // 异常栈at
      {
        className: 'name',
        variants: [
          { begin: '(?<=^\\d*:? ?\\s*)Caused by(?=: )' },
        ]
      }, // Mybatis异常
      {
        className: 'name',
        variants: [
          { begin: '^\\d+: ' },
        ]
      }, // 行号
      {
        className: 'deletion',
        variants: [
          { begin: '\\([A-Za-z0-9_]+\\.java:\\d+\\)$' },
        ]
      }, // 异常栈行号
      {
        className: 'deletion',
        variants: [
          { begin: '\\(Native Method\\)$' },
        ]
      }, // 异常栈行号(native)
    ];
    let res = {
      aliases: ['log',],
      keywords: {
        keyword: 'INFO WARN ERROR DEBUG',
      },
      illegal: keyword || '',
      contains: contains,
    };

    if(keyword)
    {
      res.contains.push({
          className: 'quote',
          variants: [
            { begin: keyword },
          ]
        });
    }
    return res;
  }
}

export default {
  name: "SysLog",
  components: {},
  data() {
    return {
      showSearch: true,
      loading: false,
      total: 0,
      parms: {
        scheme: 'line',
        line: 1,
        count: DEFAULT_LINE_COUNT,
        path: null,
        keyword: null,
        nu: true,
        type: 'application',
      },
      logsTree: [],
      treeProps: {
        children: 'children',
        label: 'name'
      },
      fileName: null,
      content: '',
      title: '',
      size: 0,
      downloading: false,
    };
  },
  created() {
    if(!this.isAdmin)
    {
      this.$message.error('登录用户非管理员身份!');
      setTimeout(() => this.$router.back(), 2000);
      return;
    }
    this.GetLogsTree();
  },
  methods: {
    FilterLogs(value, data) {
      if (!value) return true;
      return data.path.indexOf(value) !== -1;
    },
    OpenLog(data, node) {
      if(!data.file)
        return;
      this.parms.line = 1;
      this.parms.path = data.path;
      this.parms.keyword = null;
      if(this.parms.scheme === SCHEME_LINE && data.size > FILE_OVER_LIMIT)
      {
        this.$confirm(`文件过大(${formatFileSize(data.size)}), 按行读取可能较慢, 是否按字节读取以加快读取速度?`, '警告').then(() => {
          this.parms.scheme = SCHEME_BYTE;
          this.parms.count = DEFAULT_BYTE_COUNT;
          this.GetLog();
        }).catch(() => {
          this.GetLog();
        });
      }
      else
        this.GetLog();
    },
    GetLog() {
      if(!this.parms.path)
      {
        this.msgError('请先选择文件');
        return;
      }
      this.loading = true;
      request({
        url: `${LOG_API}/` + this.parms.path,
        method: 'post',
        params: this.parms,
      }).then((resp) => {
        this.content = resp.data;
        this.total = resp.total;
        this.size = resp.size;
        let unit = this.parms.scheme === SCHEME_BYTE ? '字节' : '行';
        this.title = `${resp.file} [${resp.start}${unit} - ${resp.end}${unit}] (${formatFileSize(resp.size)} ${date_format(new Date(resp.time), '%Y-%m-%d %H:%i:%s')})`;
      }).finally(() => {
        this.loading = false;
      });
    },
    GetPage(line, count) {
      if(line > 0)
        this.parms.line = line;
      if(count > 0)
        this.parms.count = count;
      if(this.parms.path)
        this.GetLog();
    },
    GetLogsTree() {
      request({
        url: `${LOG_API}/logsTree`,
        method: 'get',
        params: this.parms,
      }).then((resp) => {
        resp.data.forEach((x) => this.NormalizedFileNode(x));
        this.logsTree = resp.data;
      });
    },
    ResetQuery() {
      //this.$refs.queryForm.resetFields();
      this.parms.nu = true;
      this.parms.keyword = '';
    },
    Highlight(code) {
      if(!code)
        return '&nbsp;';
      hljs.registerLanguage("log", gen_log_hl(this.parms.keyword));
      const result = hljs.highlight('log', code || "", true);
      return result.value || '&nbsp;';
    },
    NormalizedFileNode(node) {
      if(node.file)
        node.name = `${node.name}(${formatFileSize(node.size)} ${date_format(new Date(node.time), '%Y-%m-%d %H:%i:%s')})`;
      if(node.children && node.children.length > 0)
      {
        for(let c of node.children)
          this.NormalizedFileNode(c);
      }
    },
    ChangeScheme(scheme) {
      this.parms.line = 1;
      if(scheme === SCHEME_BYTE)
      {
        this.parms.count = DEFAULT_BYTE_COUNT;
      }
      else
      {
        this.parms.count = DEFAULT_LINE_COUNT;
      }
    },
    RequestDownload() {
      if(!this.parms.path)
        return;
      if(this.size > DOWNLOAD_FILE_LIMIT)
      {
        this.$confirm(`文件过大(${formatFileSize(this.size)}), 下载可能较慢, 是否继续?`, '警告').then(() => {
          this.Download();
        }).catch(() => {
        });
      }
      else
        this.Download();
    },
    Download() {
      this.downloading = true;
      request({
        url: `${LOG_API}/download/${this.parms.type}/` + this.parms.path,
        method: 'get',
        responseType: 'blob',
      }).then((resp) => {
        save(this.parms.path, resp);
      }).finally(() => {
        this.downloading = false;
      });
    },
    ChangeType() {
      this.GetLogsTree();
    },
  },
  watch: {
    fileName(val) {
      this.$refs.tree.filter(val);
    },
  },
  computed: {
    pageSizes() {
      return this.parms.scheme === SCHEME_BYTE ?
        [1024, 2048, 4096, 8192, 10240, 20480, 40960, 81920, 102400, ]
        : [10, 20, 30, 50, 100, 200, 512, 1024,];
    },
    isAdmin() {
      return /*!IS_STABLE() && */this.$store.getters.roles[0] === 'admin';
    }
  },
};
</script>

<style scoped>
.main-footer {
  text-align: right;
  margin: 12px 24px;
}
.main-header {
  margin: 12px 12px;
  font-size: 18px;
  font-weight: bold;
  text-align: center;
}
.code {
  white-space: break-spaces;
}
.left-filter {
  margin: 4px 0;
}
.left-tree {
  margin: 12px 0;
  overflow: auto;
}
.left-tree >>> .el-tree-node__children {
  overflow: auto;
}
</style>
