@maiyunnet/kebab 9.9.0 → 9.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/doc/kebab-rag.md CHANGED
@@ -1360,7 +1360,7 @@ index/variables/VER.md
1360
1360
 
1361
1361
  # Variable: VER
1362
1362
 
1363
- > `const` **VER**: `"9.9.0"` = `'9.9.0'`
1363
+ > `const` **VER**: `"9.10.0"` = `'9.10.0'`
1364
1364
 
1365
1365
  Defined in: [index.ts:10](https://github.com/maiyunnet/kebab/blob/master/index.ts#L10)
1366
1366
 
package/index.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * --- 本文件用来定义每个目录实体地址的常量 ---
6
6
  */
7
7
  /** --- 当前系统版本号 --- */
8
- export declare const VER = "9.9.0";
8
+ export declare const VER = "9.10.0";
9
9
  /** --- 框架根目录,以 / 结尾 --- */
10
10
  export declare const ROOT_PATH: string;
11
11
  /** --- 框架的 LIB,以 / 结尾 --- */
package/index.js CHANGED
@@ -6,7 +6,7 @@
6
6
  * --- 本文件用来定义每个目录实体地址的常量 ---
7
7
  */
8
8
  /** --- 当前系统版本号 --- */
9
- export const VER = '9.9.0';
9
+ export const VER = '9.10.0';
10
10
  // --- 服务端用的路径 ---
11
11
  const imu = decodeURIComponent(import.meta.url).replace('file://', '').replace(/^\/(\w:)/, '$1');
12
12
  /** --- /xxx/xxx --- */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maiyunnet/kebab",
3
- "version": "9.9.0",
3
+ "version": "9.10.0",
4
4
  "description": "Simple, easy-to-use, and fully-featured Node.js framework that is ready-to-use out of the box.",
5
5
  "type": "module",
6
6
  "keywords": [
package/sys/master.js CHANGED
@@ -519,8 +519,88 @@ function createRpcListener() {
519
519
  }));
520
520
  return;
521
521
  }
522
- // --- wc -l 高效获取总行数 ---
522
+ let limit = msg.limit ?? 100;
523
+ const offset = msg.offset ?? 0;
523
524
  let total = 0;
525
+ if (msg.search) {
526
+ // === 搜索模式:total 为匹配行数,offset/limit 均基于匹配行 ===
527
+ // --- shell 单引号安全转义(防止特殊字符破坏命令)---
528
+ const escaped = msg.search.replace(/'/g, "'\\''");
529
+ // --- 获取匹配行总数(grep -c 无匹配时退出码 1,|| echo 0 兜底)---
530
+ const countCmd = format === 'csv'
531
+ ? `tail -n +2 "${path}" | grep -F -c '${escaped}' || echo 0`
532
+ : `grep -F -c '${escaped}' "${path}" || echo 0`;
533
+ const countRtn = await lCore.exec(countCmd);
534
+ if (countRtn !== false) {
535
+ total = parseInt(countRtn.trim()) || 0;
536
+ }
537
+ // --- 获取分页数据:第 offset+1 到 offset+limit 条匹配行(1-indexed)---
538
+ const from = offset + 1;
539
+ const to = offset + limit;
540
+ const dataCmd = format === 'csv'
541
+ ? `tail -n +2 "${path}" | grep -F '${escaped}' | sed -n '${from},${to}p'`
542
+ : `grep -F '${escaped}' "${path}" | sed -n '${from},${to}p'`;
543
+ const dataRtn = await lCore.exec(dataCmd);
544
+ if (dataRtn === false) {
545
+ res.end(lText.stringifyJson({
546
+ 'result': 1,
547
+ 'list': [],
548
+ 'total': total,
549
+ }));
550
+ return;
551
+ }
552
+ /**
553
+ * --- csv 格式:string[][] 每行是字段数组,顺序与表头一致 ---
554
+ * --- [['H:i:s', unix, url, cookie, session, userAgent, realIp, cfIp, xIp, osMem, procMem, message], ...] ---
555
+ * --- jsonl 格式:object[] 每行是解析后的 JSON 对象 ---
556
+ * --- [{ time, unix, url, cookie, session, userAgent, realIp, cfIp, xIp, osMem, procMem, message }, ...] ---
557
+ */
558
+ const sList = [];
559
+ for (const rawLine of dataRtn.split('\n')) {
560
+ if (!rawLine) {
561
+ continue;
562
+ }
563
+ if (format === 'jsonl') {
564
+ const obj = lText.parseJson(rawLine);
565
+ if (obj) {
566
+ sList.push(obj);
567
+ }
568
+ }
569
+ else {
570
+ const result = [];
571
+ let currentField = '';
572
+ let inQuotes = false;
573
+ for (let i = 0; i < rawLine.length; ++i) {
574
+ const char = rawLine[i];
575
+ if (char === '"') {
576
+ if (inQuotes && rawLine[i + 1] === '"') {
577
+ currentField += '"';
578
+ ++i;
579
+ }
580
+ else {
581
+ inQuotes = !inQuotes;
582
+ }
583
+ }
584
+ else if (char === ',' && !inQuotes) {
585
+ result.push(currentField);
586
+ currentField = '';
587
+ }
588
+ else {
589
+ currentField += char;
590
+ }
591
+ }
592
+ result.push(currentField);
593
+ sList.push(result);
594
+ }
595
+ }
596
+ res.end(lText.stringifyJson({
597
+ 'result': 1,
598
+ 'list': sList,
599
+ 'total': total,
600
+ }));
601
+ return;
602
+ }
603
+ // === 无搜索模式:wc -l 获取总行数,grep -b '^' 定位字节偏移直接跳至 offset ===
524
604
  const wclRtn = await lCore.exec(`wc -l "${path}"`);
525
605
  if (wclRtn !== false) {
526
606
  const wclMatch = /^\s*(\d+)/.exec(wclRtn);
@@ -532,10 +612,6 @@ function createRpcListener() {
532
612
  }
533
613
  }
534
614
  }
535
- /** --- 剩余 limit --- */
536
- let limit = msg.limit ?? 100;
537
- /** --- offset --- */
538
- const offset = msg.offset ?? 0;
539
615
  /**
540
616
  * --- 用 grep -b '^' 获取目标行的字节偏移,直接传给 createReadStream start 跳过 offset ---
541
617
  * --- jsonl 无表头:跳过 offset 行,从第 offset+1 行开始读 ---
@@ -560,7 +636,7 @@ function createRpcListener() {
560
636
  // --- offset 超出文件范围,返回空列表 ---
561
637
  res.end(lText.stringifyJson({
562
638
  'result': 1,
563
- 'data': [],
639
+ 'list': [],
564
640
  'total': total,
565
641
  }));
566
642
  return;
@@ -602,41 +678,39 @@ function createRpcListener() {
602
678
  // --- startByte > 0 时已跳过 offset(及 csv 表头),从第一行起均为数据行 ---
603
679
  // --- startByte === 0 且 csv 时仍需跳过表头(line > 1)---
604
680
  if (format === 'csv' && startByte === 0 ? line > 1 : line >= 1) {
605
- if (!msg.search || packet.includes(msg.search)) {
606
- if (format === 'jsonl') {
607
- const obj = lText.parseJson(packet);
608
- if (obj) {
609
- list.push(obj);
610
- --limit;
611
- }
681
+ if (format === 'jsonl') {
682
+ const obj = lText.parseJson(packet);
683
+ if (obj) {
684
+ list.push(obj);
685
+ --limit;
612
686
  }
613
- else {
614
- const result = [];
615
- let currentField = '';
616
- let inQuotes = false;
617
- for (let i = 0; i < packet.length; ++i) {
618
- const char = packet[i];
619
- if (char === '"') {
620
- if (inQuotes && packet[i + 1] === '"') {
621
- currentField += '"';
622
- ++i;
623
- }
624
- else {
625
- inQuotes = !inQuotes;
626
- }
627
- }
628
- else if (char === ',' && !inQuotes) {
629
- result.push(currentField);
630
- currentField = '';
687
+ }
688
+ else {
689
+ const result = [];
690
+ let currentField = '';
691
+ let inQuotes = false;
692
+ for (let i = 0; i < packet.length; ++i) {
693
+ const char = packet[i];
694
+ if (char === '"') {
695
+ if (inQuotes && packet[i + 1] === '"') {
696
+ currentField += '"';
697
+ ++i;
631
698
  }
632
699
  else {
633
- currentField += char;
700
+ inQuotes = !inQuotes;
634
701
  }
635
702
  }
636
- result.push(currentField);
637
- list.push(result);
638
- --limit;
703
+ else if (char === ',' && !inQuotes) {
704
+ result.push(currentField);
705
+ currentField = '';
706
+ }
707
+ else {
708
+ currentField += char;
709
+ }
639
710
  }
711
+ result.push(currentField);
712
+ list.push(result);
713
+ --limit;
640
714
  }
641
715
  }
642
716
  // --- 处理结束 ---