+
-
diff --git a/src/main/resources/static/common/js/LogMonitorAdaptive.js b/src/main/resources/static/common/js/LogMonitorAdaptive.js
new file mode 100644
index 0000000..b6f279a
--- /dev/null
+++ b/src/main/resources/static/common/js/LogMonitorAdaptive.js
@@ -0,0 +1,459 @@
+class LogMonitorAdaptive {
+ constructor(container, opts = {}) {
+ this.container = typeof container === 'string'
+ ? document.querySelector(container)
+ : container;
+ if (!this.container) throw new Error('容器未找到');
+
+ /* ========== 配置项及其含义 ========== */
+ this.cfg = {
+ /* --- 尺寸相关 --- */
+ width: null, // 日志容器宽度。null=100%,可填数字(px)或字符串('80%'/'20rem')
+ height: null, // 日志容器高度。null=100vh,可填数字(px)或字符串('400px'/'50vh')
+
+ /* --- 日志条数与滚动 --- */
+ maxLines: 5000, // 最大保留日志条数,超出后自动删除最旧日志
+ autoScroll: true, // 出现新日志时是否自动滚动到底部
+
+ /* --- 主题与外观 --- */
+ theme: 'dark', // 主题:'dark' | 'light',控制整体配色
+ fontSize: 14, // 日志文字大小,单位 px
+ wordWrap: false, // 日志内容是否自动换行(true=换行,false=横向滚动)
+
+ /* --- 日志内容格式 --- */
+ showTimestamp: true, // 是否显示时间戳
+ showLevel: true, // 是否显示日志级别标签
+ timeFormat: 'HH:mm:ss.SSS', // 时间戳格式,可自定义
+
+ /* --- 日志级别 & 过滤 --- */
+ levels: ['debug', 'info', 'warn', 'error', 'success', 'system'], // 可用日志级别
+ // 过滤功能基于 levels;如只想显示 info/warn,可在这里删减
+
+ /* --- 功能开关 --- */
+ enableFilter: true, // 顶部是否显示级别过滤复选框
+ enableSearch: true, // 是否启用搜索框与高亮功能
+ enableExport: true, // 是否允许导出日志文件
+ enableClear: true, // 是否提供“清空”按钮
+ enablePause: true, // 是否提供“暂停/继续”按钮
+ enableThemeToggle: true, // 是否提供“切换主题”按钮
+ enableFullscreen: true, // 是否提供“全屏”按钮
+ enableFontSize: true, // 是否提供“字体大小 +/-”按钮
+ enableWordWrap: true, // 是否提供“换行/不换行”切换按钮
+
+ /* --- 暂停/继续 的回调函数 --- */
+ onTogglePause: () => {},
+
+ /* --- 创建完成 的回调函数 --- */
+ onCreated: () => {},
+
+ ...opts
+ };
+
+ /* 强制确保 levels 是数组,防止 forEach 报错 */
+ if (!Array.isArray(this.cfg.levels)) {
+ this.cfg.levels = ['info', 'warn', 'error'];
+ }
+
+ this.isPaused = false;
+ this.isFullscreen = false;
+ this.logs = [];
+ this.filters = new Set();
+ this.searchTerm = '';
+ this.highlightIndex = -1;
+ this.highlightEls = [];
+
+ this.initDOM();
+ this.bindResize();
+ this.bindGlobalEvents();
+
+ //执行回调函数
+ if(this.cfg.onCreated && typeof this.cfg.onCreated === 'function'){
+ this.cfg.onCreated();
+ }
+ }
+
+ /* ------------------------ 初始化 ------------------------ */
+ initDOM() {
+ this.container.innerHTML = '';
+ this.setContainerSize(); // 宽高自适应
+ this.applyContainerStyle();
+
+ // 顶部导航栏
+ this.navbar = document.createElement('div');
+ Object.assign(this.navbar.style, {
+ display: 'flex',
+ alignItems: 'center',
+ padding: '8px 12px',
+ backgroundColor: this.cfg.theme === 'dark' ? '#2d2d30' : '#f3f3f3',
+ borderBottom: `1px solid ${this.cfg.theme === 'dark' ? '#555' : '#ccc'}`,
+ gap: '10px',
+ fontSize: '14px',
+ flexWrap: 'wrap'
+ });
+
+ // 按钮组
+ if (this.cfg.enablePause) {
+ this.pauseBtn = this.createBtn('⏸ 暂停', () => this.togglePause(), {w: '72px'});
+ this.navbar.appendChild(this.pauseBtn);
+ }
+ if (this.cfg.enableClear) {
+ this.navbar.appendChild(this.createBtn('🗑 清空', () => this.clear(), {w: '72px'}));
+ }
+ if (this.cfg.enableExport) {
+ this.navbar.appendChild(this.createBtn('💾 导出', () => this.export(), {w: '72px'}));
+ }
+
+ // 主题切换按钮
+ if (this.cfg.enableThemeToggle) {
+ this.themeBtn = this.createBtn(
+ this.cfg.theme === 'dark' ? '☀️ 明亮' : '🌙 暗黑',
+ () => this.toggleTheme(),
+ {w: '80px'}
+ );
+ this.navbar.appendChild(this.themeBtn);
+ }
+
+ // 字体大小
+ if (this.cfg.enableFontSize) {
+ this.navbar.appendChild(this.createBtn('A-', () => this.changeFontSize(-1), {w: '36px'}));
+ this.navbar.appendChild(this.createBtn('A+', () => this.changeFontSize(1), {w: '36px'}));
+ }
+
+ // 换行
+ if (this.cfg.enableWordWrap) {
+ this.wrapBtn = this.createBtn(
+ this.cfg.wordWrap ? '⏎ 换行' : '⏎ 不换行',
+ () => this.toggleWordWrap(),
+ {w: '90px'}
+ );
+ this.navbar.appendChild(this.wrapBtn);
+ }
+
+ // 全屏
+ if (this.cfg.enableFullscreen) {
+ this.navbar.appendChild(this.createBtn('⛶ 全屏', () => this.toggleFullscreen(), {w: '72px'}));
+ }
+
+ // 过滤复选框
+ if (this.cfg.enableFilter) {
+ this.cfg.levels.forEach(lvl => {
+ this.navbar.appendChild(this.createCheckbox(lvl));
+ });
+ }
+
+ // 搜索框
+ if (this.cfg.enableSearch) {
+ const searchWrap = document.createElement('span');
+ searchWrap.style.marginLeft = 'auto';
+ searchWrap.style.display = 'flex';
+ searchWrap.style.alignItems = 'center';
+ searchWrap.style.gap = '4px';
+
+ this.searchInput = document.createElement('input');
+ Object.assign(this.searchInput.style, {
+ padding: '6px 8px',
+ fontSize: '14px',
+ border: `1px solid ${this.cfg.theme === 'dark' ? '#555' : '#bbb'}`,
+ borderRadius: '4px',
+ backgroundColor: this.cfg.theme === 'dark' ? '#3c3c3c' : '#fff',
+ color: 'inherit',
+ width: '260px'
+ });
+ this.searchInput.placeholder = '搜索…';
+ this.searchInput.oninput = () => {
+ this.searchTerm = this.searchInput.value.trim();
+ this.renderVisible();
+ };
+ searchWrap.appendChild(this.searchInput);
+
+ ['↑', '↓'].forEach((arr, idx) => {
+ searchWrap.appendChild(
+ this.createBtn(arr, () => this.navigateHighlight(idx), {w: '30px'})
+ );
+ });
+ this.navbar.appendChild(searchWrap);
+ }
+
+ // 日志视窗
+ this.viewport = document.createElement('div');
+ this.applyViewportStyle();
+ this.container.appendChild(this.navbar);
+ this.container.appendChild(this.viewport);
+
+ this.renderVisible();
+ }
+
+ /* ------------------------ 样式 ------------------------ */
+ setContainerSize() {
+ // 如果用户未指定宽高,则自适应
+ if (this.cfg.width === null) {
+ this.container.style.width = '100%';
+ } else {
+ this.container.style.width = typeof this.cfg.width === 'number' ? `${this.cfg.width}px` : this.cfg.width;
+ }
+
+ if (this.cfg.height === null) {
+ //this.container.style.height = '100vh';
+ const bodyMargin = parseFloat(getComputedStyle(document.body).marginTop || 0) +
+ parseFloat(getComputedStyle(document.body).marginBottom || 0);
+ this.container.style.height = `calc(100vh - ${bodyMargin}px - 2px)`;
+ } else {
+ this.container.style.height = typeof this.cfg.height === 'number' ? `${this.cfg.height}px` : this.cfg.height;
+ }
+ }
+
+ applyContainerStyle() {
+ Object.assign(this.container.style, {
+ display: 'flex',
+ flexDirection: 'column',
+ border: `1px solid ${this.cfg.theme === 'dark' ? '#444' : '#ccc'}`,
+ borderRadius: '6px',
+ fontFamily: 'Consolas, "Courier New", monospace',
+ fontSize: `${this.cfg.fontSize}px`,
+ lineHeight: 1.45,
+ backgroundColor: this.cfg.theme === 'dark' ? '#1e1e1e' : '#fafafa',
+ color: this.cfg.theme === 'dark' ? '#d4d4d4' : '#222',
+ overflow: 'hidden'
+ });
+ }
+
+ applyViewportStyle() {
+ Object.assign(this.viewport.style, {
+ flex: 1,
+ overflowY: 'auto',
+ padding: '8px',
+ whiteSpace: this.cfg.wordWrap ? 'pre-wrap' : 'pre',
+ wordBreak: 'break-word'
+ });
+ }
+
+ /* ------------------------ 工具 ------------------------ */
+ createBtn(text, handler, {w} = {}) {
+ const btn = document.createElement('button');
+ btn.textContent = text;
+ Object.assign(btn.style, {
+ padding: '6px 10px',
+ fontSize: '14px',
+ backgroundColor: this.cfg.theme === 'dark' ? '#444' : '#e7e7e7',
+ color: 'inherit',
+ border: `1px solid ${this.cfg.theme === 'dark' ? '#666' : '#ccc'}`,
+ borderRadius: '4px',
+ cursor: 'pointer',
+ minWidth: w || 'auto'
+ });
+ btn.onmouseenter = () => btn.style.backgroundColor = this.cfg.theme === 'dark' ? '#555' : '#d0d0d0';
+ btn.onmouseleave = () => btn.style.backgroundColor = this.cfg.theme === 'dark' ? '#444' : '#e7e7e7';
+ btn.onclick = handler;
+ return btn;
+ }
+
+ createCheckbox(level) {
+ const lbl = document.createElement('label');
+ lbl.style.color = this.cfg.theme === 'dark' ? '#ccc' : '#333';
+ lbl.style.fontSize = '13px';
+ lbl.style.display = 'flex';
+ lbl.style.alignItems = 'center';
+
+ const chk = document.createElement('input');
+ chk.type = 'checkbox';
+ chk.checked = true;
+ chk.style.margin = '0 4px';
+ chk.onchange = () => {
+ if (chk.checked) this.filters.delete(level);
+ else this.filters.add(level);
+ this.renderVisible();
+ };
+ lbl.appendChild(chk);
+ lbl.append(level.toUpperCase());
+ return lbl;
+ }
+
+ /* ------------------------ 日志渲染 ------------------------ */
+ log(message, level = 'info', ts = this.formatTime(new Date())) {
+ if (!this.cfg.levels.includes(level)) level = 'info';
+ if (this.isPaused) return;
+
+ ts = this.cfg.showTimestamp ? ts : '';
+ this.logs.push({message, level, ts, id: Date.now() + Math.random()});
+
+ if (this.logs.length > this.cfg.maxLines) this.logs.shift();
+ this.renderVisible();
+ }
+
+ renderVisible() {
+ this.viewport.innerHTML = '';
+ this.highlightEls = [];
+ this.highlightIndex = -1;
+
+ const filtered = this.logs.filter(l => !this.filters.has(l.level));
+ const frag = document.createDocumentFragment();
+ const regex = this.searchTerm ? new RegExp(`(${this.searchTerm})`, 'gi') : null;
+
+ filtered.forEach(entry => {
+ const line = document.createElement('div');
+ line.style.display = 'flex';
+ line.style.margin = '1px 0';
+
+ if (this.cfg.showTimestamp && entry.ts) {
+ const tsSpan = document.createElement('span');
+ tsSpan.textContent = entry.ts + ' ';
+ tsSpan.style.color = this.cfg.theme === 'dark' ? '#6a9955' : '#008000';
+ tsSpan.style.minWidth = '90px';
+ line.appendChild(tsSpan);
+ }
+
+ if (this.cfg.showLevel) {
+ const lvlSpan = document.createElement('span');
+ lvlSpan.textContent = `[${entry.level.toUpperCase()}]`;
+ const colors = {
+ debug: '#9c27b0', info: '#2196f3', warn: '#ff9800',
+ error: '#f44336', success: '#4caf50', system: '#00bcd4'
+ };
+ lvlSpan.style.color = colors[entry.level] || colors.info;
+ lvlSpan.style.minWidth = '70px';
+ lvlSpan.style.fontWeight = 'bold';
+ line.appendChild(lvlSpan);
+ }
+
+ const msgSpan = document.createElement('span');
+ msgSpan.style.flex = 1;
+ let msg = entry.message;
+ if (regex) {
+ if (regex.test(entry.message)) {
+ line.style.backgroundColor = 'rgba(255,235,59,0.25)';
+ this.highlightEls.push(line);
+ }
+ msg = entry.message.replace(regex, '$1');
+ }
+ msgSpan.innerHTML = msg;
+ line.appendChild(msgSpan);
+ frag.appendChild(line);
+ });
+
+ this.viewport.appendChild(frag);
+ if (this.cfg.autoScroll && !this.isPaused) {
+ this.viewport.scrollTop = this.viewport.scrollHeight;
+ }
+ }
+
+ /* ------------------------ 交互 ------------------------ */
+ togglePause() {
+ this.isPaused = !this.isPaused;
+ this.pauseBtn.textContent = this.isPaused ? '▶ 继续' : '⏸ 暂停';
+ this.pauseBtn.style.backgroundColor = this.isPaused
+ ? (this.cfg.theme === 'dark' ? '#d32f2f' : '#ff5252')
+ : (this.cfg.theme === 'dark' ? '#444' : '#e7e7e7');
+ //执行回调函数
+ if(this.cfg.onTogglePause && typeof this.cfg.onTogglePause === 'function'){
+ this.cfg.onTogglePause(this.isPaused);
+ }
+ }
+
+ toggleTheme() {
+ this.cfg.theme = this.cfg.theme === 'dark' ? 'light' : 'dark';
+ this.themeBtn.textContent = this.cfg.theme === 'dark' ? '☀️ 明亮' : '🌙 暗黑';
+ this.applyContainerStyle();
+ this.navbar.style.backgroundColor = this.cfg.theme === 'dark' ? '#2d2d30' : '#f3f3f3';
+ this.renderVisible();
+ }
+
+ changeFontSize(delta) {
+ this.cfg.fontSize = Math.max(10, Math.min(24, this.cfg.fontSize + delta));
+ this.container.style.fontSize = `${this.cfg.fontSize}px`;
+ }
+
+ toggleWordWrap() {
+ this.cfg.wordWrap = !this.cfg.wordWrap;
+ this.wrapBtn.textContent = this.cfg.wordWrap ? '⏎ 不换行' : '⏎ 换行';
+ this.applyViewportStyle();
+ }
+
+ toggleFullscreen() {
+ if (!this.isFullscreen) {
+ if (this.container.requestFullscreen) this.container.requestFullscreen();
+ else if (this.container.webkitRequestFullscreen) this.container.webkitRequestFullscreen();
+ else if (this.container.msRequestFullscreen) this.container.msRequestFullscreen();
+ } else {
+ if (document.exitFullscreen) document.exitFullscreen();
+ else if (document.webkitExitFullscreen) document.webkitExitFullscreen();
+ else if (document.msExitFullscreen) document.msExitFullscreen();
+ }
+ this.isFullscreen = !this.isFullscreen;
+ }
+
+ navigateHighlight(dir) {
+ if (!this.highlightEls.length) return;
+ this.highlightIndex = (this.highlightIndex + (dir === 0 ? -1 : 1) + this.highlightEls.length) % this.highlightEls.length;
+ this.highlightEls.forEach((el, idx) => {
+ el.style.outline = idx === this.highlightIndex ? '2px solid #ffeb3b' : 'none';
+ });
+ this.highlightEls[this.highlightIndex].scrollIntoView({block: 'nearest', behavior: 'smooth'});
+ }
+
+ clear() {
+ this.logs = [];
+ this.renderVisible();
+ }
+
+ export() {
+ const lines = this.logs.map(l => `${l.ts} [${l.level.toUpperCase()}] ${l.message}`);
+ const blob = new Blob([lines.join('\n')], {type: 'text/plain;charset=utf-8'});
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = `log_${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.txt`;
+ a.click();
+ URL.revokeObjectURL(url);
+ }
+
+ formatTime(date) {
+ const t = date.toTimeString().slice(0, 8);
+ const ms = String(date.getMilliseconds()).padStart(3, '0');
+ return `${t}.${ms}`;
+ }
+
+ bindResize() {
+ window.addEventListener('resize', () => {
+ this.setContainerSize(); // 响应窗口变化
+ });
+ }
+
+ bindGlobalEvents() {
+ document.addEventListener('keydown', e => {
+ if (e.key === 'Escape' && this.searchInput) {
+ this.searchInput.value = '';
+ this.searchTerm = '';
+ this.renderVisible();
+ }
+ if (e.ctrlKey && e.key === 'k') {
+ e.preventDefault();
+ this.clear();
+ }
+ });
+ }
+
+ /* 快捷方法 */
+ info(msg, ts = this.formatTime(new Date())) {
+ this.log(msg, 'info', ts);
+ }
+
+ warn(msg, ts = this.formatTime(new Date())) {
+ this.log(msg, 'warn', ts);
+ }
+
+ error(msg, ts = this.formatTime(new Date())) {
+ this.log(msg, 'error', ts);
+ }
+
+ success(msg, ts = this.formatTime(new Date())) {
+ this.log(msg, 'success', ts);
+ }
+
+ debug(msg, ts = this.formatTime(new Date())) {
+ this.log(msg, 'debug', ts);
+ }
+
+ system(msg, ts = this.formatTime(new Date())) {
+ this.log(msg, 'system', ts);
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/static/common/js/fmt-socket.js b/src/main/resources/static/common/js/fmt-socket.js
deleted file mode 100644
index 98eb7ef..0000000
--- a/src/main/resources/static/common/js/fmt-socket.js
+++ /dev/null
@@ -1,251 +0,0 @@
-/**
- * WebSocket封装工具类
- *
- * 代码示例:
- * let FmtSocket = new FmtSocket(
- * {url:'websocket服务器地址',id:'唯一标识',timeout:心跳检测间隔时间(毫秒)}
- * );
- * //调用初始化方法
- * FmtSocket.init((data) => {
- * //初始化完成之后的回调函数 ata为监听到的数据
- * //可以在这里做接收到消息后的逻辑处理
- * console.log(data);
- * });
- * //调用发送消息方法 message 消息字符串或对象
- * FmtSocket.sendMsg(message, () => {
- * //发送消息成功之后的回调函数
- * });
- *
- */
-class FmtSocket {
- id;
- #options;
- #ws;
- #entity;
- #deepProxyObj;
- #proxyMsg;
- /**
- * 构造函数
- * @param {Object} options
- */
- constructor(options) {
- this.#options = this.#extend({
- url: 'http://127.0.0.1/socket/ws',//后台websocket服务地址
- id: this.#uuid(),//如果没传此参数,创建一个随机的UUID传给后台服务作为标识
- timeout: 30000 //心跳检测间隔时间 默认30秒
- }, options);
- this.id = this.#options.id;
- this.#ws = null;
- //send:发送类型 HeartBeat:心跳 Logging:消息
- this.#entity = {send: null, data: null};
- }
-
- /**
- * 初始化websocket
- * @param {Function} callback (type, data) 回调函数
- */
- init(callback){
- if(callback && typeof callback === 'function'){
- this.#proxyMsg = [];
- this.#deepProxyObj = this.#deepProxy(this.#proxyMsg,callback);
- }
- this.#createWebSocket();
- }
- /**
- * 对象、数组变化监听(增删改)
- * @param {Object} obj
- * @param {Function} callback
- * @return {Object}
- */
- #deepProxy(obj, callback){
- if (typeof obj === 'object') {
- for (let key in obj) {
- if (typeof obj[key] === 'object') {
- obj[key] = this.#deepProxy(obj[key], callback);
- }
- }
- }
- return new Proxy(obj, {
- /**
- * @param {Object, Array} target 设置值的对象
- * @param {String} key 属性
- * @param {any} value 值
- * @param {Object} receiver this
- */
- set: (target, key, value, receiver)=> {
- if (typeof value === 'object') {
- value = this.#deepProxy(value, callback);
- }
- let cbType = target[key] === undefined ? 'create' : 'modify';
- //排除数组修改length回调
- if (!(Array.isArray(target) && key === 'length')) {
- if (cbType === 'create') {
- callback(value);
- }
- }
- return Reflect.set(target, key, value, receiver);
- },
- deleteProperty(target, key) {
- return Reflect.deleteProperty(target, key);
- }
- });
- }
- #createWebSocket(){
- try {
- if ('WebSocket' in window) {
- this.#ws = new WebSocket((this.#options.url + "?id=" + this.#options.id)
- .replace("http", "ws")
- .replace("https", "wss"));
- } else {
- alert('浏览器不支持WebSocket通讯');
- return false;
- }
- this.#onopen();
- this.#onmessage();
- this.#onerror();
- this.#onclose();
- //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
- window.onbeforeunload = ()=> {
- this.#ws.close();
- };
- } catch (e) {
- console.log("WebSocket:链接失败", e);
- alert('WebSocket:链接失败');
- }
- }
- /**
- * 关闭websocket链接
- * @param {Function} callback
- */
- closeWebSocket(callback){
- if(this.#ws){
- this.#ws.close();
- }
- if(callback && typeof callback === 'function'){
- callback();
- }
- }
- /**
- * 发送消息
- * @param {String, Object} message
- * @param {Function} callback
- */
- sendMsg(message, callback){
- if(!message || typeof message !== 'string' || typeof message !== 'object'){
- alert("FmtSocket sendMsg message is not null or message is not a string or object");
- return false;
- }
- if(callback && typeof callback !== 'function'){
- alert("FmtSocket sendMsg callback is not a function");
- return false;
- }
- this.#emptyObjectPropertyValues(this.#entity);
- this.#entity.send = 'Message';
- this.#entity.data = message;
- if (this.#ws.readyState === this.#ws.CONNECTING) {
- // 正在开启状态,等待1s后重新调用
- let socket = this;
- setTimeout(function () {
- socket.#ws.send(JSON.stringify(socket.#entity));
- callback();
- }, 1000)
- }else{
- this.#ws.send(JSON.stringify(this.#entity));
- callback();
- }
- }
- #onopen(){
- this.#ws.onopen = (event) => {
- console.log("WebSocket:链接开启");
- }
- }
- #onmessage(){
- this.#ws.onmessage = (event) => {
- this.#emptyObjectPropertyValues(this.#entity);
- this.#entity = JSON.parse(event.data);
- //心跳检测
- if(this.#entity.send === 'HeartBeat'){
- this.#heartBeat();
- }
- if(this.#deepProxyObj){
- this.#deepProxyObj.splice(0);//赋值前先清空,保证消息数组里只有一条最新的数据
- let data = JSON.parse(JSON.stringify(this.#entity));
- this.#deepProxyObj.push(data);
- }
- }
- }
- #onerror(){
- this.#ws.onerror = (event) => {
- console.log("WebSocket:发生错误", event);
- }
- }
- #onclose(){
- this.#ws.onclose = (event) => {
- console.log("WebSocket:链接关闭");
- }
- }
- #heartBeat(){
- setTimeout(()=> {
- this.#emptyObjectPropertyValues(this.#entity);
- if (this.#ws.readyState === this.#ws.CLOSING
- || this.#ws.readyState === this.#ws.CLOSED){
- alert("WebSocket通讯服务链接已关闭");
- return false;
- }
- this.#entity.send = 'HeartBeat';
- this.#entity.data = '心跳检测';
- if (this.#ws.readyState === this.#ws.CONNECTING) {
- // 正在开启状态,等待1s后重新调用
- setTimeout(()=> {
- this.#ws.send(JSON.stringify(this.#entity));
- }, 1000)
- }else{
- this.#ws.send(JSON.stringify(this.#entity));
- }
- },this.#options.timeout);
- }
- /**
- * 扩展,source相同属性的值去覆盖target, target如果没有这个属性就新增
- * @param {Object} target
- * @param {Object} source
- * @returns {Object}
- */
- #extend(target, source){
- if(typeof target !== 'object') return null;
- if(typeof source !== 'object') return null;
- for (const key in source) {
- // 使用for in会遍历数组所有的可枚举属性,包括原型。
- if (Object.prototype.hasOwnProperty.call(source, key)) {
- target[key] = source[key];
- }
- }
- return target;
- }
- #uuid(len, radix){
- const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
- let uuid = [], i;
- radix = radix || chars.length;
- if (len) {
- // Compact form
- for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix];
- } else {
- // rfc4122, version 4 form
- let r;
- // rfc4122 requires these characters
- uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
- uuid[14] = '4';
- // Fill in random data. At i==19 set the high bits of clock sequence as
- // per rfc4122, sec. 4.1.5
- for (i = 0; i < 36; i++) {
- if (!uuid[i]) {
- r = 0 | Math.random() * 16;
- uuid[i] = chars[(i === 19) ? (r & 0x3) | 0x8 : r];
- }
- }
- }
- return uuid.join('');
- }
- #emptyObjectPropertyValues(person){
- Object.keys(person).forEach(key => (person[key] = null));
- }
-}
\ No newline at end of file
diff --git a/src/main/resources/templates/download.html b/src/main/resources/templates/download.html
index dbd5214..180062b 100644
--- a/src/main/resources/templates/download.html
+++ b/src/main/resources/templates/download.html
@@ -40,7 +40,7 @@
diff --git a/src/main/resources/templates/logging.html b/src/main/resources/templates/logging.html
index 0ea0d26..3a31b6c 100644
--- a/src/main/resources/templates/logging.html
+++ b/src/main/resources/templates/logging.html
@@ -1,66 +1,72 @@
-
+
-
-
-
-
-
-
-
-
-
+
在线日志
-
-
-
-
-
-
-
-
-
+
+
-
+
diff --git a/src/main/resources/templates/upload.html b/src/main/resources/templates/upload.html
index 893e4fe..0254c11 100644
--- a/src/main/resources/templates/upload.html
+++ b/src/main/resources/templates/upload.html
@@ -33,7 +33,7 @@
diff --git a/src/test/java/cn/somkit/fmt/FmtApplicationTests.java b/src/test/java/cn/somkit/fmt/FmtApplicationTests.java
deleted file mode 100644
index 02d4233..0000000
--- a/src/test/java/cn/somkit/fmt/FmtApplicationTests.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package cn.somkit.fmt;
-
-import org.junit.jupiter.api.Test;
-import org.springframework.boot.test.context.SpringBootTest;
-
-@SpringBootTest
-class FmtApplicationTests {
-
- @Test
- void contextLoads() {
- }
-
-}