Skip to content

Commit

Permalink
1.0.20 超大文本日志查看器
Browse files Browse the repository at this point in the history
  • Loading branch information
xiaobaidadada committed Dec 18, 2024
1 parent f07f3de commit 8941d34
Show file tree
Hide file tree
Showing 18 changed files with 605 additions and 210 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ url: http://116.198.245.137:5569/
5. [excalidraw](https://github.com/excalidraw/excalidraw)绘图编辑器,这是一个很好用白板工具。
5. 切换根目录,在设置中添加多个文件夹路径后,可以在右上角选择切换根目录,只对一个session生效。
6. 终端,默认是bash, windwos下是 powershell。
7. 超大文本日志查看器,对任意文本右键使用作为日志类型查看。
- ssh代理,ftp代理: 可以管理多个linux服务器,作用和winscp类似,让终端和文件管理更方便。
- 网站,是网址收藏夹,可用于保存服务器上其它的网站
- ddns
Expand Down
10 changes: 6 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "filecat",
"version": "1.0.19",
"version": "1.0.20",
"description": "filecat 文件管理器",
"author": "xiaobaidadada",
"scripts": {
Expand Down Expand Up @@ -45,6 +45,8 @@
"远程桌面",
"浏览器代理",
"简洁",
"日志",
"log",
"filebrowser",
"administrator",
"filemanager",
Expand Down Expand Up @@ -85,8 +87,9 @@
"@excalidraw/excalidraw": "^0.17.6",
"@koa/cors": "^5.0.0",
"@xiaobaidadada/prebuild": "^13.0.1-7",
"@xterm/addon-fit": "^0.10.0",
"@xterm/xterm": "^5.5.0",
"@xterm/addon-fit": "^0.11.0-beta.70",
"@xterm/addon-search": "^0.16.0-beta.70",
"@xterm/xterm": "^5.6.0-beta.70",
"ace-builds": "^1.32.9",
"archiver": "^7.0.1",
"axios": "^1.7.7",
Expand Down Expand Up @@ -122,7 +125,6 @@
"pkg": "^5.8.1",
"protobufjs-cli": "^1.1.3",
"react": "^18.3.1",
"react-ace": "^12.0.0",
"react-dom": "^18.3.1",
"react-i18next": "^15.0.0",
"react-router-dom": "^6.26.2",
Expand Down
18 changes: 18 additions & 0 deletions src/common/file.pojo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,22 @@ export enum base64UploadType {
all, // 全部上传
start, // 开始部分
part, // 部分
}


export class LogViewerPojo {
path:string;
token: string;
position:number = 0; // 文件读取的偏移位置
line:number = 1000; // 读取一千行
once_max_size:number = 1024 * 60; // 一次读取最大的字节数 60 KB

back:boolean; // 往回查找

// 返回用的
context:string;
context_list:string[] = []; // 多个数组
context_start_position_list:number[] = []; // 多个数组 对应的开始位置
context_position_list:number[] = []; // 多个数组 对应的结束位置
max_size:number; // 当前文件的最大字节数
}
1 change: 1 addition & 0 deletions src/common/frame/WsData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ export enum CmdType {
file_uncompress_progress,
file_compress,
file_compress_progress,
log_viewer,

// rtsp
rtsp_get,
Expand Down
43 changes: 31 additions & 12 deletions src/common/frame/ws.client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,26 @@ export class WsClient {
private _promise;

private _msgHandlerMap = new Map();
private _msgResolveMap = new Map();
private _msgResolveTimeoutMap = new Map();

handMsg(cmdType: CmdType,data : WsData<any>) {
const fun = this._msgHandlerMap.get(data.cmdType)
if (fun) {
fun(data);
}
const resolve = this._msgResolveMap.get(data.cmdType)
if (resolve) {
resolve(data);
this._msgResolveMap.delete(data.cmdType)
}
const timeout = this._msgResolveTimeoutMap.get(cmdType);
if(timeout) {
clearTimeout(timeout);
this._msgResolveTimeoutMap.delete(cmdType);
}
}

constructor(url:string,authHandle:(socket:WebSocket)=>void) {
this._url = url;
this._authHandle = authHandle;
Expand Down Expand Up @@ -47,10 +67,7 @@ export class WsClient {
decoder.on("decoded",(d)=>{
const data = new WsData(d.data[0],d.data[1]);
data.wss = this._socket;
const fun = this._msgHandlerMap.get(data.cmdType)
if (fun) {
fun(data);
}
this.handMsg(data.cmdType,data);
})
}
let dataList = [];
Expand All @@ -59,10 +76,7 @@ export class WsClient {
if (protocolIsProto2) {
const data = WsData.decode(rowData);
data.wss = this._socket;
const fun = this._msgHandlerMap.get(data.cmdType)
if (fun) {
fun(data);
}
this.handMsg(data.cmdType,data);
} else {
decoder.add(rowData);
}
Expand Down Expand Up @@ -115,7 +129,7 @@ export class WsClient {
// 监听连接错误事件
socket.addEventListener('error', function (event) {
console.error('WebSocket error:', event);
unConnect()
unConnect();
});
this._socket = socket;
} else {
Expand All @@ -133,7 +147,7 @@ export class WsClient {
};


public async send(wsData:WsData<any>) {
public async send(wsData:WsData<any>):Promise<WsData<any>> {
if (this._promise) {
await this._promise;
this._promise = null;
Expand All @@ -147,8 +161,13 @@ export class WsClient {
} else {
this._socket!.send(data);
}
new Promise((resolve)=>{
this._msgHandlerMap.set(wsData.cmdType,resolve);
return new Promise((resolve,reject)=>{
const timeout = setTimeout(()=>{
resolve(null);
console.log('ws超时',wsData.cmdType)
},1000 * 6);
this._msgResolveMap.set(wsData.cmdType,resolve);
this._msgResolveTimeoutMap.set(wsData.cmdType,timeout);
})
}
// 可以作为锁,控制两个路由之前的跳转处理函数的先后顺序
Expand Down
8 changes: 7 additions & 1 deletion src/main/domain/file/file.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
FileCompressPojo,
FileTypeEnum,
FileVideoFormatTransPojo,
GetFilePojo
GetFilePojo, LogViewerPojo
} from "../../../common/file.pojo";
import {FileServiceImpl} from "./file.service";
import {Result, Sucess} from "../../other/Result";
Expand Down Expand Up @@ -167,6 +167,12 @@ export class FileController {
return ""
}

@msg(CmdType.log_viewer)
async log_viewer(data: WsData<LogViewerPojo>) {
// 如果一行太长 现在会进行截断成多个分裂的行
return FileServiceImpl.log_viewer(data);
}

// 获取studio路径
@Post("/studio/get/item")
async studio_get_item(@Body() data: { path: string }, @Req() ctx) {
Expand Down
144 changes: 136 additions & 8 deletions src/main/domain/file/file.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
FileCompressType, FileTreeList,
FileTypeEnum,
FileVideoFormatTransPojo, FileInfoItemData,
GetFilePojo
GetFilePojo, LogViewerPojo
} from "../../../common/file.pojo";
// import {config} from "../../other/config";
import fs, {Stats} from "fs";
Expand Down Expand Up @@ -78,7 +78,7 @@ class FileService extends FileCompress {
type: type,
name: item,
mtime: formattedCreationTime,
size:stats.size,
size: stats.size,
isLink: stats.isSymbolicLink(),
path: path.join(param_path, item)
})
Expand Down Expand Up @@ -136,7 +136,7 @@ class FileService extends FileCompress {
// limits: { fileSize: 1024 * 1024 * 2 }, // 限制文件大小为 2MB 无限制
}).single('file');

public uploadFile(filePath, req: Request,res:Response, token) {
public uploadFile(filePath, req: Request, res: Response, token) {

const sysPath = path.join(settingService.getFileRootPath(token), filePath ? decodeURIComponent(filePath) : "");
// if (!file) {
Expand All @@ -149,12 +149,12 @@ class FileService extends FileCompress {
// }
req['fileDir'] = path.dirname(sysPath);
req['fileName'] = path.basename(sysPath);
this.upload(req, res, (err) => {
if (err) {
this.upload(req, res, (err) => {
if (err) {

}
// 成功上传
});
}
// 成功上传
});
// 写入文件
// fs.writeFileSync(sysPath, file.buffer);
//multer 默认使用 return new Multer({}) 默认memoryStorage 这种方式 buffer 不属于v8内存管理 所以内存释放的比较慢
Expand Down Expand Up @@ -522,6 +522,134 @@ class FileService extends FileCompress {
}
return pojo;
}

go_forward_log( pojo : LogViewerPojo ,file_path) {
// 开始查找
let linesRead = 0; // 行数
let haveReadSize = 0; // 已经读取的字节数
const fd = fs.openSync(file_path, "r");
while (haveReadSize < pojo.once_max_size) {
// 创建一个 100 字节的缓冲区
const buffer = Buffer.alloc(1024);
// 返回实际读取的字节数
const bytesRead = fs.readSync(fd, buffer,
0, // 相对于当前的偏移位置
buffer.length, // 读取的长度
pojo.position // 当前位置
);
// 遍历 buffer 中的每一个字节
let done = false;
let last_h = -1; // 上一个/n 未开始的也算 /n 都是不包括
for (let i = 0; i <= bytesRead; i++) {
// 如果字节是换行符 '\n'(ASCII值为 10)
if (buffer[i] === 10 || i === bytesRead ) { // 换行或者 最后一个字符
linesRead++;
// 总的来说 字符串要不包括\n 但是结束位置包括\n
const end_offset = i === bytesRead?i+1:i;
pojo.context_list.push(buffer.toString('utf8', last_h+1, end_offset)); // i 不包括 /n
pojo.context_start_position_list.push(pojo.position + last_h + 1); // 开始位置
pojo.context_position_list.push(pojo.position + end_offset); // 结束位置 是/n的位置
if (linesRead >= pojo.line) {
done = true;
break;
}
last_h = i;
}
}

if (done) {
break;
} else {
haveReadSize += bytesRead;
// 更新文件位置
pojo.position += bytesRead;
}
if (bytesRead === 0) {
break;
}
}
// 关闭文件
fs.closeSync(fd);
return pojo;
}

go_back_log( pojo : LogViewerPojo ,file_path ) {
// 开始查找
let linesRead = 0; // 行数
let haveReadSize = 0; // 已经读取的字节数
const fd = fs.openSync(file_path, "r");
let buffer_len = 1024;
while (haveReadSize < pojo.once_max_size) {
if (pojo.position < buffer_len) {
// buffer_len = Math.floor(pojo.position / 2);
buffer_len = pojo.position; // 全部读完
} else {
buffer_len = pojo.position - buffer_len;
}
let buffer = Buffer.alloc(buffer_len); // 缓冲区满足当前位置往前移动的距离
pojo.position = pojo.position - buffer.length; // 位置前移
// 返回实际读取的字节数
const bytesRead = fs.readSync(fd, buffer,
0, // 相对于当前的偏移位置
buffer.length, // 读取的长度
pojo.position // 当前位置 往前推进了一点
);

// 遍历 buffer 中的每一个字节
let done = false;
let last_h = bytesRead; // 上一个 \n
for (let i = bytesRead; i >= 0; i--) {
// 如果字节是换行符 '\n'(ASCII值为 10)
if (buffer[i] === 10 || i===0) {
linesRead++;
const start_offset = i===0?i:i+1;
pojo.context_list.push(buffer.toString('utf8', i===0?i:i+1, last_h));
pojo.context_start_position_list.push(pojo.position + start_offset);
pojo.context_position_list.push(pojo.position + last_h);
if (linesRead >= pojo.line) {
done = true;
break;
}
last_h = i;
}
}

if (done) {
break;
} else {
haveReadSize += bytesRead;
// 更新文件位置
pojo.position -= bytesRead;
}
if (bytesRead === 0 || pojo.position <= 0) {
break;
}
}
// 关闭文件
fs.closeSync(fd);
return pojo;
}

log_viewer(data: WsData<LogViewerPojo>) {
const pojo = data.context as LogViewerPojo;
pojo.context = "";
pojo.context_list = [];
pojo.context_position_list = [];
pojo.context_start_position_list = [];
const root_path = settingService.getFileRootPath(pojo.token);
const file_path = path.join(root_path, decodeURIComponent(pojo.path));
// 获取文件的元数据
const stats = fs.statSync(file_path);
// 文件当前的最大大小
const fileSize = stats.size;
pojo.max_size = fileSize;
if ((pojo.position <= 0 && pojo.back) || pojo.position >= fileSize) {
pojo.context = '';
return pojo;
}
if (pojo.back) return this.go_back_log(pojo,file_path);
return this.go_forward_log(pojo, file_path);
}
}

export const FileServiceImpl = new FileService();
8 changes: 4 additions & 4 deletions src/web/project/component/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const Studio = React.lazy(() => import("./file/component/studio/Studio"))
const Net = React.lazy(() => import("./net/Net"))
const Settings = React.lazy(() => import("./setting/Setting"))
const NavIndex = React.lazy(() => import("./navindex/NavIndex"))
// const LogShell = React.lazy(() => import("./shell/LogShell"))
const FileLog = React.lazy(() => import("./file/component/LogViewer"))
import {useRecoilState} from "recoil";
import {$stroe} from "../util/store";
import {routerConfig} from "../../../common/RouterConfig";
Expand Down Expand Up @@ -61,9 +61,9 @@ function Layout() {
return (
<div>
{/*全局显示*/}
{/*<Suspense fallback={<div></div>}>*/}
{/* <LogShell />*/}
{/*</Suspense>*/}
<Suspense fallback={<div></div>}>
<FileLog />
</Suspense>
<Suspense fallback={<div></div>}>
<Prompt></Prompt>
</Suspense>
Expand Down
Loading

0 comments on commit 8941d34

Please sign in to comment.