#
Junjie
10 小时以前 48c1de18235020edff108339ed1d12bade8a2b90
#
3个文件已添加
662 ■■■■■ 已修改文件
src/main/java/com/zy/asrs/controller/DeviceLogController.java 338 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/static/js/deviceLogs/deviceLogs.js 228 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/views/deviceLogs/deviceLogs.html 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/controller/DeviceLogController.java
New file
@@ -0,0 +1,338 @@
package com.zy.asrs.controller;
import com.core.annotations.ManagerAuth;
import com.core.common.Cools;
import com.core.common.R;
import com.zy.common.web.BaseController;
import com.zy.core.enums.SlaveType;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
@RestController
public class DeviceLogController extends BaseController {
    @Value("${deviceLogStorage.loggingPath}")
    private String loggingPath;
    private static class ProgressInfo {
        long totalRaw;
        long processedRaw;
        int totalCount;
        int processedCount;
        boolean finished;
    }
    private static final Map<String, ProgressInfo> DOWNLOAD_PROGRESS = new ConcurrentHashMap<>();
    @RequestMapping(value = "/deviceLog/dates/auth")
    @ManagerAuth
    public R dates() {
        try {
            Path baseDir = Paths.get(loggingPath);
            if (!Files.exists(baseDir)) {
                return R.ok(new ArrayList<>());
            }
            List<String> days = Files.list(baseDir)
                    .filter(Files::isDirectory)
                    .map(p -> p.getFileName().toString())
                    .filter(name -> name.length() == 8 && name.chars().allMatch(Character::isDigit))
                    .sorted()
                    .collect(Collectors.toList());
            Map<String, Map<String, List<String>>> grouped = new LinkedHashMap<>();
            for (String day : days) {
                String year = day.substring(0, 4);
                String month = day.substring(4, 6);
                grouped.computeIfAbsent(year, k -> new LinkedHashMap<>())
                        .computeIfAbsent(month, k -> new ArrayList<>())
                        .add(day);
            }
            List<Map<String, Object>> tree = new ArrayList<>();
            for (Map.Entry<String, Map<String, List<String>>> yEntry : grouped.entrySet()) {
                Map<String, Object> yNode = new HashMap<>();
                yNode.put("title", yEntry.getKey());
                yNode.put("id", yEntry.getKey());
                List<Map<String, Object>> mChildren = new ArrayList<>();
                for (Map.Entry<String, List<String>> mEntry : yEntry.getValue().entrySet()) {
                    Map<String, Object> mNode = new HashMap<>();
                    mNode.put("title", mEntry.getKey());
                    mNode.put("id", yEntry.getKey() + "-" + mEntry.getKey());
                    List<Map<String, Object>> dChildren = new ArrayList<>();
                    for (String d : mEntry.getValue()) {
                        Map<String, Object> dNode = new HashMap<>();
                        dNode.put("title", d.substring(6, 8));
                        dNode.put("id", d);
                        dNode.put("day", d);
                        dChildren.add(dNode);
                    }
                    mNode.put("children", dChildren);
                    mChildren.add(mNode);
                }
                yNode.put("children", mChildren);
                tree.add(yNode);
            }
            return R.ok(tree);
        } catch (Exception e) {
            return R.error("读取日期失败");
        }
    }
    @RequestMapping(value = "/deviceLog/day/{day}/devices/auth")
    @ManagerAuth
    public R devices(@PathVariable("day") String day) {
        try {
            if (day == null || day.length() != 8 || !day.chars().allMatch(Character::isDigit)) {
                return R.error("日期格式错误");
            }
            Path dayDir = Paths.get(loggingPath, day);
            if (!Files.exists(dayDir) || !Files.isDirectory(dayDir)) {
                return R.ok(new ArrayList<>());
            }
            List<Path> files = Files.list(dayDir)
                    .filter(p -> !Files.isDirectory(p) && p.getFileName().toString().endsWith(".log"))
                    .collect(Collectors.toList());
            Map<String, Map<String, Object>> deviceMap = new HashMap<>();
            for (Path p : files) {
                String name = p.getFileName().toString();
                String[] parts = name.split("_");
                if (parts.length < 4) {
                    continue;
                }
                String deviceNo = parts[1];
                String type = parts[0];
                Map<String, Object> info = deviceMap.computeIfAbsent(deviceNo, k -> {
                    Map<String, Object> map = new HashMap<>();
                    map.put("deviceNo", deviceNo);
                    map.put("types", new HashSet<String>());
                    map.put("fileCount", 0);
                    return map;
                });
                ((Set<String>) info.get("types")).add(type);
                info.put("fileCount", ((Integer) info.get("fileCount")) + 1);
            }
            List<Map<String, Object>> res = deviceMap.values().stream().map(m -> {
                Map<String, Object> x = new HashMap<>();
                x.put("deviceNo", m.get("deviceNo"));
                x.put("types", ((Set<String>) m.get("types")).stream().collect(Collectors.toList()));
                x.put("fileCount", m.get("fileCount"));
                return x;
            }).collect(Collectors.toList());
            return R.ok(res);
        } catch (Exception e) {
            return R.error("读取设备列表失败");
        }
    }
    @RequestMapping(value = "/deviceLog/day/{day}/download/auth")
    @ManagerAuth
    public void download(@PathVariable("day") String day,
                         @RequestParam("type") String type,
                         @RequestParam("deviceNo") String deviceNo,
                         @RequestParam(value = "offset", required = false) Integer offset,
                         @RequestParam(value = "limit", required = false) Integer limit,
                         @RequestParam(value = "progressId", required = false) String progressId,
                         HttpServletResponse response) {
        try {
            String dayClean = day == null ? null : day.replaceAll("\\D", "");
            if (dayClean == null || dayClean.length() != 8 || !dayClean.chars().allMatch(Character::isDigit)) {
                response.setStatus(400);
                return;
            }
            if (type == null || SlaveType.findInstance(type) == null) {
                response.setStatus(400);
                return;
            }
            if (deviceNo == null || !deviceNo.chars().allMatch(Character::isDigit)) {
                response.setStatus(400);
                return;
            }
            Path dayDir = Paths.get(loggingPath, dayClean);
            if (!Files.exists(dayDir) || !Files.isDirectory(dayDir)) {
                response.setStatus(404);
                return;
            }
            List<Path> files = Files.list(dayDir)
                    .filter(p -> {
                        String name = p.getFileName().toString();
                        String prefix = type + "_" + deviceNo + "_" + dayClean + "_";
                        return name.endsWith(".log") && name.startsWith(prefix);
                    }).collect(Collectors.toList());
            // 排序(按文件中的索引号递增)
            String prefix = type + "_" + deviceNo + "_" + dayClean + "_";
            files.sort(Comparator.comparingInt(p -> {
                String n = p.getFileName().toString();
                try {
                    String suf = n.substring(prefix.length(), n.length() - 4);
                    return Integer.parseInt(suf);
                } catch (Exception e) {
                    return Integer.MAX_VALUE;
                }
            }));
            int from = offset == null || offset < 0 ? 0 : offset;
            int max = limit == null || limit <= 0 ? 200 : limit;
            int to = Math.min(files.size(), from + max);
            if (from >= files.size()) {
                response.setStatus(404);
                return;
            }
            files = files.subList(from, to);
            if (files.isEmpty()) {
                response.setStatus(404);
                return;
            }
            ProgressInfo info;
            String id = progressId;
            if (Cools.isEmpty(id)) {
                id = UUID.randomUUID().toString();
            }
            List<Path> finalFiles = files;
            info = DOWNLOAD_PROGRESS.computeIfAbsent(id, k -> {
                ProgressInfo x = new ProgressInfo();
                x.totalCount = finalFiles.size();
                long sum = 0L;
                for (Path f : finalFiles) {
                    try { sum += Files.size(f); } catch (Exception ignored) {}
                }
                x.totalRaw = sum;
                x.processedRaw = 0L;
                x.processedCount = 0;
                x.finished = false;
                return x;
            });
            response.reset();
            response.setContentType("application/zip");
            String filename = type + "_" + deviceNo + "_" + dayClean + ".zip";
            response.setHeader("Content-Disposition", "attachment; filename=" + filename);
            long totalRawSize = 0L;
            for (Path f : files) {
                try { totalRawSize += Files.size(f); } catch (Exception ignored) {}
            }
            response.setHeader("X-Total-Size", String.valueOf(totalRawSize));
            response.setHeader("X-File-Count", String.valueOf(files.size()));
            response.setHeader("X-Progress-Id", id);
            try (java.util.zip.ZipOutputStream zos = new java.util.zip.ZipOutputStream(response.getOutputStream())) {
                for (Path f : files) {
                    java.util.zip.ZipEntry entry = new java.util.zip.ZipEntry(f.getFileName().toString());
                    zos.putNextEntry(entry);
                    Files.copy(f, zos);
                    zos.closeEntry();
                    try {
                        info.processedRaw += Files.size(f);
                    } catch (Exception ignored) {}
                    info.processedCount += 1;
                }
                zos.finish();
                info.finished = true;
            }
        } catch (Exception e) {
            try { response.setStatus(500); } catch (Exception ignore) {}
        }
    }
    @RequestMapping(value = "/deviceLog/download/init/auth")
    @ManagerAuth
    public R init(@org.springframework.web.bind.annotation.RequestBody com.alibaba.fastjson.JSONObject param) {
        try {
            String day = param.getString("day");
            String type = param.getString("type");
            String deviceNo = param.getString("deviceNo");
            Integer offset = param.getInteger("offset");
            Integer limit = param.getInteger("limit");
            String dayClean = Cools.isEmpty(day) ? null : day.replaceAll("\\D", "");
            if (Cools.isEmpty(dayClean) || dayClean.length() != 8 || !dayClean.chars().allMatch(Character::isDigit)) {
                return R.error("日期格式错误");
            }
            if (Cools.isEmpty(type) || SlaveType.findInstance(type) == null) {
                return R.error("设备类型错误");
            }
            if (Cools.isEmpty(deviceNo) || !deviceNo.chars().allMatch(Character::isDigit)) {
                return R.error("设备编号错误");
            }
            Path dayDir = Paths.get(loggingPath, dayClean);
            if (!Files.exists(dayDir) || !Files.isDirectory(dayDir)) {
                return R.error("当日目录不存在");
            }
            List<Path> files = Files.list(dayDir)
                    .filter(p -> {
                        String name = p.getFileName().toString();
                        String prefix = type + "_" + deviceNo + "_" + dayClean + "_";
                        return name.endsWith(".log") && name.startsWith(prefix);
                    }).collect(Collectors.toList());
            String prefix = type + "_" + deviceNo + "_" + dayClean + "_";
            files.sort(Comparator.comparingInt(p -> {
                String n = p.getFileName().toString();
                try {
                    String suf = n.substring(prefix.length(), n.length() - 4);
                    return Integer.parseInt(suf);
                } catch (Exception e) {
                    return Integer.MAX_VALUE;
                }
            }));
            int from = offset == null || offset < 0 ? 0 : offset;
            int max = limit == null || limit <= 0 ? 200 : limit;
            int to = Math.min(files.size(), from + max);
            if (from >= files.size()) {
                return R.error("起始序号超出范围");
            }
            files = files.subList(from, to);
            String id = UUID.randomUUID().toString();
            ProgressInfo info = new ProgressInfo();
            info.totalCount = files.size();
            long sum = 0L;
            for (Path f : files) {
                try { sum += Files.size(f); } catch (Exception ignored) {}
            }
            info.totalRaw = sum;
            info.processedRaw = 0L;
            info.processedCount = 0;
            info.finished = false;
            DOWNLOAD_PROGRESS.put(id, info);
            Map<String, Object> res = new HashMap<>();
            res.put("progressId", id);
            res.put("totalSize", info.totalRaw);
            res.put("fileCount", info.totalCount);
            return R.ok(res);
        } catch (Exception e) {
            return R.error("初始化失败");
        }
    }
    @RequestMapping(value = "/deviceLog/download/progress/auth")
    @ManagerAuth
    public R progress(String id) {
        ProgressInfo info = DOWNLOAD_PROGRESS.get(id);
        if (info == null) {
            return R.error("无效进度");
        }
        long total = info.totalRaw;
        long done = info.processedRaw;
        int percent;
        if (info.finished) {
            percent = 100;
        } else if (total > 0) {
            percent = (int) Math.min(99, (done * 100L) / total);
        } else if (info.totalCount > 0) {
            percent = (int) Math.min(99, (info.processedCount * 100L) / info.totalCount);
        } else {
            percent = 0;
        }
        Map<String, Object> res = new HashMap<>();
        res.put("percent", percent);
        res.put("processedSize", done);
        res.put("totalSize", total);
        res.put("processedCount", info.processedCount);
        res.put("totalCount", info.totalCount);
        res.put("finished", info.finished);
        return R.ok(res);
    }
}
src/main/webapp/static/js/deviceLogs/deviceLogs.js
New file
@@ -0,0 +1,228 @@
layui.use(['tree', 'layer', 'form', 'element'], function() {
    var tree = layui.tree;
    var $ = layui.jquery;
    var layer = layui.layer;
    var form = layui.form;
    var element = layui.element;
    var currentDay = null;
    function buildMonthTree(data) {
        var monthMap = {};
        (data || []).forEach(function (y) {
            (y.children || []).forEach(function (m) {
                var month = m.title;
                var arr = monthMap[month] || (monthMap[month] = []);
                (m.children || []).forEach(function (d) {
                    arr.push({ title: d.title, id: d.id });
                });
            });
        });
        var result = [];
        Object.keys(monthMap).sort().forEach(function (month) {
            result.push({ title: month + '月', id: month, children: monthMap[month] });
        });
        return result;
    }
    function loadDateTree() {
        $.ajax({
            url: baseUrl + "/deviceLog/dates/auth",
            headers: {'token': localStorage.getItem('token')},
            method: 'GET',
            beforeSend: function () {
                layer.load(1, {shade: [0.1,'#fff']});
            },
            success: function (res) {
                layer.closeAll('loading');
                if (res.code === 200) {
                    var monthTree = buildMonthTree(res.data);
                    tree.render({
                        elem: '#date-tree',
                        id: 'dateTree',
                        data: monthTree,
                        click: function(obj){
                            var node = obj.data;
                            if (node.id && node.id.length === 8) {
                                currentDay = node.id;
                                $('#selected-day').val(currentDay);
                                loadDevices(currentDay);
                            }
                        }
                    });
                } else if (res.code === 403) {
                    top.location.href = baseUrl + "/";
                } else {
                    layer.msg(res.msg || '加载日期失败', {icon: 2});
                }
            }
        });
    }
    function loadDevices(day) {
        $('#device-list').html('');
        $.ajax({
            url: baseUrl + "/deviceLog/day/" + day + "/devices/auth",
            headers: {'token': localStorage.getItem('token')},
            method: 'GET',
            beforeSend: function () {
                layer.load(1, {shade: [0.1,'#fff']});
            },
            success: function (res) {
                layer.closeAll('loading');
                if (res.code === 200) {
                    if (!res.data || res.data.length === 0) {
                        $('#device-list').html('<div class="layui-text">当日未找到设备日志</div>');
                        return;
                    }
                    var html = '';
                    res.data.forEach(function(item){
                        var types = item.types || [];
                        var typeBtns = '';
                        types.forEach(function(t){
                            typeBtns += '<button class="layui-btn layui-btn-xs" data-type="' + t + '" data-device-no="' + item.deviceNo + '">下载(' + t + ')</button>';
                        });
                        html += '<div class="layui-col-xs12" style="margin-bottom:8px;">' +
                            '<div class="layui-card">' +
                            '<div class="layui-card-body">' +
                            '<span>设备编号:<b>' + item.deviceNo + '</b></span>' +
                            '<span style="margin-left:20px;">类型:' + types.join(',') + '</span>' +
                            '<span style="margin-left:20px;">文件数:' + item.fileCount + '</span>' +
                            '<span style="margin-left:20px;">' + typeBtns + '</span>' +
                            '</div>' +
                            '</div>' +
                            '</div>';
                    });
                    $('#device-list').html(html);
                } else if (res.code === 403) {
                    top.location.href = baseUrl + "/";
                } else {
                    layer.msg(res.msg || '加载设备失败', {icon: 2});
                }
            }
        });
    }
    function downloadDeviceLog(day, type, deviceNo) {
        if (!day) {
            layer.msg('请先选择日期', {icon: 2});
            return;
        }
        if (!type) {
            layer.msg('请选择设备类型', {icon: 2});
            return;
        }
        if (!deviceNo) {
            layer.msg('请输入设备编号', {icon: 2});
            return;
        }
        var offsetVal = parseInt($('#file-offset').val());
        var limitVal = parseInt($('#file-limit').val());
        var offset = isNaN(offsetVal) || offsetVal < 0 ? 0 : offsetVal;
        var limit = isNaN(limitVal) || limitVal <= 0 ? 200 : limitVal;
        $.ajax({
            url: baseUrl + "/deviceLog/download/init/auth",
            headers: {'token': localStorage.getItem('token')},
            method: 'POST',
            data: JSON.stringify({ day: day, type: type, deviceNo: deviceNo, offset: offset, limit: limit }),
            dataType:'json',
            contentType:'application/json;charset=UTF-8',
            success: function (res) {
                if (res.code !== 200) {
                    layer.msg(res.msg || '初始化失败', {icon: 2});
                    return;
                }
                var pid = res.data.progressId;
                var progressIndex = layer.open({
                    type: 1,
                    title: '下载中',
                    area: ['520px', '200px'],
                    content: '<div style="padding:16px;">' +
                        '<div class="layui-text" style="margin-bottom:15px;">压缩生成进度</div>' +
                        '<div class="layui-progress" lay-showPercent="true" lay-filter="buildProgress">' +
                        '<div class="layui-progress-bar" style="width:0%"><span class="layui-progress-text">0%</span></div>' +
                        '</div>' +
                        '<div class="layui-text" style="margin:12px 0 15px;">下载接收进度</div>' +
                        '<div class="layui-progress" lay-showPercent="true" lay-filter="receiveProgress">' +
                        '<div class="layui-progress-bar" style="width:0%"><span class="layui-progress-text">0%</span></div>' +
                        '</div>' +
                        '</div>'
                });
                var timer = setInterval(function(){
                    $.ajax({
                        url: baseUrl + '/deviceLog/download/progress/auth',
                        headers: {'token': localStorage.getItem('token')},
                        method: 'GET',
                        data: { id: pid },
                        success: function (p) {
                            if (p.code === 200) {
                                var percent = p.data.percent || 0;
                                element.progress('buildProgress', percent + '%');
                                // 隐藏实时大小,不更新文字
                            }
                        }
                    });
                }, 500);
                $.ajax({
                    url: baseUrl + "/deviceLog/day/" + day + "/download/auth?type=" + encodeURIComponent(type) + "&deviceNo=" + encodeURIComponent(deviceNo) + "&offset=" + offset + "&limit=" + limit + "&progressId=" + encodeURIComponent(pid),
                    headers: {'token': localStorage.getItem('token')},
                    method: 'GET',
                    xhrFields: { responseType: 'blob' },
                    xhr: function(){
                        var xhr = new window.XMLHttpRequest();
                        xhr.onprogress = function(e){
                            var percent = 0;
                            if (e.lengthComputable && e.total > 0) {
                                percent = Math.floor(e.loaded / e.total * 100);
                                element.progress('receiveProgress', percent + '%');
                            }
                            // 隐藏实时大小,不更新文字
                        };
                        return xhr;
                    },
                    success: function (data, status, xhr) {
                        var disposition = xhr.getResponseHeader('Content-Disposition') || '';
                        var filename = type + '_' + deviceNo + '_' + day + '.zip';
                        var match = /filename=(.+)/.exec(disposition);
                        if (match && match[1]) {
                            filename = decodeURIComponent(match[1]);
                        }
                        element.progress('buildProgress', '100%');
                        element.progress('receiveProgress', '100%');
                        var blob = new Blob([data], {type: 'application/zip'});
                        var link = document.createElement('a');
                        var url = window.URL.createObjectURL(blob);
                        link.href = url;
                        link.download = filename;
                        document.body.appendChild(link);
                        link.click();
                        document.body.removeChild(link);
                        window.URL.revokeObjectURL(url);
                        clearInterval(timer);
                        setTimeout(function(){ layer.close(progressIndex); }, 300);
                    },
                    error: function () {
                        clearInterval(timer);
                        layer.close(progressIndex);
                        layer.msg('下载失败或未找到日志', {icon: 2});
                    }
                });
            }
        });
    }
    $(document).on('click', '#download-btn', function () {
        downloadDeviceLog(currentDay, $('#device-type-input').val(), $('#device-no-input').val());
    });
    $(document).on('click', '#device-list .layui-btn', function () {
        var deviceNo = $(this).attr('data-device-no');
        var type = $(this).attr('data-type');
        downloadDeviceLog(currentDay, type, deviceNo);
    });
    loadDateTree();
    limit();
});
src/main/webapp/views/deviceLogs/deviceLogs.html
New file
@@ -0,0 +1,96 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <title>设备日志</title>
    <meta name="renderer" content="webkit">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <link rel="stylesheet" href="../../static/layui/css/layui.css" media="all">
    <link rel="stylesheet" href="../../static/css/admin.css?v=318" media="all">
    <link rel="stylesheet" href="../../static/css/cool.css" media="all">
</head>
<body>
<div class="layui-fluid">
    <div class="layui-row">
        <div class="layui-col-md3">
            <div class="layui-card">
                <div class="layui-card-header">日期</div>
                <div class="layui-card-body">
                    <div id="date-tree"></div>
                </div>
            </div>
        </div>
        <div class="layui-col-md9">
            <div class="layui-card">
                <div class="layui-card-header">日志下载</div>
                <div class="layui-card-body">
                    <form class="layui-form toolbar" id="search-box">
                        <div class="layui-form-item">
                            <div class="layui-inline">
                                <label class="layui-form-label">选中日期:</label>
                                <div class="layui-input-inline">
                                    <input id="selected-day" class="layui-input" type="text" placeholder="yyyyMMdd" readonly>
                                </div>
                            </div>
                            <div class="layui-inline">
                                <label class="layui-form-label">设备类型:</label>
                                <div class="layui-input-inline">
                                    <select id="device-type-input" class="layui-input">
                                        <option value="">请选择</option>
                                        <option value="Crn">Crn</option>
                                        <option value="Devp">Devp</option>
                                        <option value="Rgv">Rgv</option>
                                    </select>
                                </div>
                            </div>
                            <div class="layui-inline">
                                <label class="layui-form-label">设备编号:</label>
                                <div class="layui-input-inline">
                                    <input id="device-no-input" class="layui-input" type="text" placeholder="请输入设备编号">
                                </div>
                            </div>
                            <div class="layui-inline">
                                <label class="layui-form-label">起始序号:</label>
                                <div class="layui-input-inline">
                                    <input id="file-offset" class="layui-input" type="text" placeholder="默认0">
                                </div>
                            </div>
                            <div class="layui-inline">
                                <label class="layui-form-label">最大文件数:</label>
                                <div class="layui-input-inline">
                                    <input id="file-limit" class="layui-input" type="text" placeholder="默认200">
                                </div>
                            </div>
                            <div class="layui-inline">
                                <button id="download-btn" type="button" class="layui-btn layui-btn-normal">下载</button>
                            </div>
                        </div>
                    </form>
                    <hr class="layui-bg-gray">
                    <div class="layui-row">
                        <div class="layui-col-xs12">
                            <div class="layui-card">
                                <div class="layui-card-header">该日设备列表</div>
                                <div class="layui-card-body">
                                    <div id="device-list" class="layui-row"></div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
<script type="text/javascript" src="../../static/js/jquery/jquery-3.3.1.min.js"></script>
<script type="text/javascript" src="../../static/layui/layui.js" charset="utf-8"></script>
<script type="text/javascript" src="../../static/js/common.js" charset="utf-8"></script>
<script type="text/javascript" src="../../static/js/cool.js" charset="utf-8"></script>
<script type="text/javascript" src="../../static/js/deviceLogs/deviceLogs.js" charset="utf-8"></script>
</body>
</html>