@windypro-rourou/dsh-logcat 0.2.3 → 0.2.4
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/README.md +7 -4
- package/lib/client.js +48 -0
- package/lib/index.js +56 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ DSH Web GUI 的安卓实机调试面板(类似 Android Studio 的 Logcat 视
|
|
|
7
7
|
- **自动连接**:探测本机 adb(`ANDROID_HOME` / `ANDROID_SDK_ROOT` / 默认 `%LOCALAPPDATA%\Android\Sdk` / PATH),
|
|
8
8
|
每 2 秒轮询 `adb devices -l`;检测到处于调试模式的设备**自动附加 logcat 流**(`-v threadtime`),无需打开面板。
|
|
9
9
|
- **实时日志**:WebSocket 推送,每设备保留最近 2000 行环形缓冲;断线自动重连(指数退避)。
|
|
10
|
-
- **Logcat 面板**(侧边栏「Logcat
|
|
10
|
+
- **Logcat 面板**(侧边栏「Logcat」入口,右侧抽屉,**宽度可拖拽调整并记忆**):
|
|
11
11
|
- 设备下拉(显示型号/序列号/状态,记住上次选择)
|
|
12
12
|
- 级别过滤(V/D/I/W/E/F 单选,颜色与 Android Studio 一致)
|
|
13
13
|
- 关键词过滤、暂停/继续(暂停时缓冲,恢复自动回放)、清空、复制、导出 .txt
|
|
@@ -17,9 +17,12 @@ DSH Web GUI 的安卓实机调试面板(类似 Android Studio 的 Logcat 视
|
|
|
17
17
|
- `logcat_devices`:列出已连接设备(serial / model / state),判断能否实机调试。
|
|
18
18
|
- `adb_exec`:在指定设备执行 `adb shell` 命令(安装 APK、启动 Activity、查进程、截图、dump UI 等),
|
|
19
19
|
破坏性操作(卸载 / 重启 / 清数据)需先确认。
|
|
20
|
-
- `
|
|
21
|
-
-
|
|
22
|
-
|
|
20
|
+
- `logcat_set_package`:设置 / 清除当前测试的 app 包名(安装 / 启动应用后调用)。
|
|
21
|
+
- `logcat_recent`:读取某设备最近 N 条日志,支持级别 / 关键词过滤;
|
|
22
|
+
设置了测试包名(或显式传 `package`)时自动按该 app 的 pid 过滤日志。
|
|
23
|
+
- **实机调试工作流**:构建安卓应用时,agent 的通告会动态列出当前已连接设备(serial + 型号)与当前测试包名,
|
|
24
|
+
可先向用户确认后用 `adb_exec` 安装 / 启动、`logcat_set_package` 锁定目标 app、`logcat_recent` 按包名查看崩溃日志,
|
|
25
|
+
闭环真机调试。
|
|
23
26
|
- **附加能力**:`POST /api/dsh-logcat/exec` 可对设备执行 `adb shell` 命令(UI 后续版本可扩展)。
|
|
24
27
|
|
|
25
28
|
## 安装
|
package/lib/client.js
CHANGED
|
@@ -35,6 +35,8 @@ window.__ModuleLoader__.load({
|
|
|
35
35
|
[data-dsh-logcat-entry] .lc-entry-label { font-size: 13px; line-height: 1.2; opacity: .92; }
|
|
36
36
|
.dsh-logcat-view { position: fixed; top: 0; right: 0; bottom: 0; width: min(620px, 94vw); display: flex; flex-direction: column; background: var(--lc-bg, #ffffff); border-left: 1px solid rgba(128,128,128,.3); box-shadow: -10px 0 28px rgba(0,0,0,.18); z-index: 9999; }
|
|
37
37
|
.dsh-logcat-view[hidden] { display: none !important; }
|
|
38
|
+
.dsh-logcat-resize { position: absolute; left: -5px; top: 0; bottom: 0; width: 10px; cursor: col-resize; z-index: 2; }
|
|
39
|
+
.dsh-logcat-resize:hover, .dsh-logcat-resize[data-drag] { background: rgba(128,128,128,.28); }
|
|
38
40
|
.lc-panel { display: flex; flex-direction: column; height: 100%; min-height: 0; font-family: -apple-system, "Segoe UI", "Microsoft YaHei", sans-serif; }
|
|
39
41
|
.lc-header { display: flex; align-items: center; gap: 8px; padding: 8px 12px; border-bottom: 1px solid rgba(128,128,128,.25); flex: none; }
|
|
40
42
|
.lc-back { border: 0; background: transparent; color: inherit; font: inherit; cursor: pointer; display: flex; align-items: center; gap: 4px; padding: 4px 8px; border-radius: 6px; font-size: 13px; }
|
|
@@ -528,16 +530,59 @@ window.__ModuleLoader__.load({
|
|
|
528
530
|
function mountPanel(controller) {
|
|
529
531
|
let root;
|
|
530
532
|
let container;
|
|
533
|
+
let handle;
|
|
534
|
+
let drag = null;
|
|
535
|
+
|
|
536
|
+
const MIN_WIDTH = 320;
|
|
537
|
+
const DEFAULT_WIDTH = 620;
|
|
538
|
+
const readWidth = () => {
|
|
539
|
+
try {
|
|
540
|
+
const w = Number(localStorage.getItem("dsh-logcat-width"));
|
|
541
|
+
if (Number.isFinite(w) && w >= MIN_WIDTH && w <= window.innerWidth - 40) return w;
|
|
542
|
+
} catch { /* private mode */ }
|
|
543
|
+
return Math.min(DEFAULT_WIDTH, Math.max(MIN_WIDTH, window.innerWidth - 80));
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
const onMove = (e) => {
|
|
547
|
+
if (drag === null || container === undefined) return;
|
|
548
|
+
const width = Math.max(MIN_WIDTH, Math.min(window.innerWidth - 40, drag.startWidth + (drag.startX - e.clientX)));
|
|
549
|
+
container.style.width = width + "px";
|
|
550
|
+
};
|
|
551
|
+
const onUp = () => {
|
|
552
|
+
if (drag === null) return;
|
|
553
|
+
handle?.removeAttribute("data-drag");
|
|
554
|
+
try {
|
|
555
|
+
const w = container.style.width.replace("px", "");
|
|
556
|
+
localStorage.setItem("dsh-logcat-width", w);
|
|
557
|
+
} catch { /* private mode */ }
|
|
558
|
+
window.removeEventListener("mousemove", onMove);
|
|
559
|
+
window.removeEventListener("mouseup", onUp);
|
|
560
|
+
drag = null;
|
|
561
|
+
};
|
|
562
|
+
const onDown = (e) => {
|
|
563
|
+
e.preventDefault();
|
|
564
|
+
drag = { startX: e.clientX, startWidth: container.getBoundingClientRect().width };
|
|
565
|
+
handle?.setAttribute("data-drag", "");
|
|
566
|
+
window.addEventListener("mousemove", onMove);
|
|
567
|
+
window.addEventListener("mouseup", onUp);
|
|
568
|
+
};
|
|
531
569
|
|
|
532
570
|
const ensure = () => {
|
|
533
571
|
if (container !== undefined && container.isConnected) return;
|
|
534
572
|
root?.unmount();
|
|
535
573
|
root = undefined;
|
|
536
574
|
container?.remove();
|
|
575
|
+
handle = undefined;
|
|
537
576
|
container = document.createElement("div");
|
|
538
577
|
container.dataset.dshLogcatView = "";
|
|
539
578
|
container.className = "dsh-logcat-view";
|
|
579
|
+
container.style.width = readWidth() + "px";
|
|
540
580
|
container.hidden = true; // side-drawer: hidden until the sidebar entry is clicked
|
|
581
|
+
handle = document.createElement("div");
|
|
582
|
+
handle.className = "dsh-logcat-resize";
|
|
583
|
+
handle.title = "拖拽调整宽度";
|
|
584
|
+
handle.addEventListener("mousedown", onDown);
|
|
585
|
+
container.appendChild(handle);
|
|
541
586
|
document.body.appendChild(container);
|
|
542
587
|
root = createRoot(container);
|
|
543
588
|
root.render(h(LogcatPanel, { controller }));
|
|
@@ -553,11 +598,14 @@ window.__ModuleLoader__.load({
|
|
|
553
598
|
applyOpen();
|
|
554
599
|
|
|
555
600
|
return () => {
|
|
601
|
+
window.removeEventListener("mousemove", onMove);
|
|
602
|
+
window.removeEventListener("mouseup", onUp);
|
|
556
603
|
unsubscribe();
|
|
557
604
|
root?.unmount();
|
|
558
605
|
root = undefined;
|
|
559
606
|
container?.remove();
|
|
560
607
|
container = undefined;
|
|
608
|
+
handle = undefined;
|
|
561
609
|
};
|
|
562
610
|
}
|
|
563
611
|
//#endregion
|
package/lib/index.js
CHANGED
|
@@ -46,7 +46,10 @@ function logcatGuidance(engine) {
|
|
|
46
46
|
const deviceLine = live.length > 0
|
|
47
47
|
? `当前已连接安卓设备:${live.map((d) => `${d.serial}${d.model !== '' ? `(${d.model})` : ''}`).join('、')}(已授权,可实机调试:安装 APK / 启动应用 / logcat / 截图等)。`
|
|
48
48
|
: '当前无已连接的安卓设备(插上设备并开启 USB 调试后会自动识别)。'
|
|
49
|
-
|
|
49
|
+
const pkgLine = engine.currentPackage !== ''
|
|
50
|
+
? `当前测试应用包名:${engine.currentPackage}(logcat_recent 默认按它过滤日志)。`
|
|
51
|
+
: ''
|
|
52
|
+
return `${LOGCAT_GUIDANCE}\n${deviceLine}${pkgLine !== '' ? `\n${pkgLine}` : ''}`
|
|
50
53
|
}
|
|
51
54
|
}
|
|
52
55
|
|
|
@@ -207,6 +210,7 @@ class AdbEngine {
|
|
|
207
210
|
this.timer = null
|
|
208
211
|
this.polling = false
|
|
209
212
|
this.BUFFER_CAP = 2000
|
|
213
|
+
this.currentPackage = '' // app package currently under test (agent-set)
|
|
210
214
|
}
|
|
211
215
|
|
|
212
216
|
/** Probe and warm the adb server. Returns true when usable. */
|
|
@@ -340,6 +344,14 @@ class AdbEngine {
|
|
|
340
344
|
return picked.slice(-lines)
|
|
341
345
|
}
|
|
342
346
|
|
|
347
|
+
/** Map a package name to its running pids on a device ([] when not running). */
|
|
348
|
+
async pidsOfPackage(serial, packageName) {
|
|
349
|
+
if (this.adb === null || packageName === '') return []
|
|
350
|
+
const output = await runAdb(this.adb, ['-s', serial, 'shell', 'pidof', packageName], 8000)
|
|
351
|
+
if (output === null) return []
|
|
352
|
+
return output.split(/\s+/).map((s) => Number.parseInt(s, 10)).filter((n) => Number.isInteger(n) && n > 0)
|
|
353
|
+
}
|
|
354
|
+
|
|
343
355
|
/** Broadcast one frame to every open panel socket. */
|
|
344
356
|
broadcast(frame) {
|
|
345
357
|
const payload = JSON.stringify(frame)
|
|
@@ -477,6 +489,7 @@ function logcatRecentTool(engine) {
|
|
|
477
489
|
lines: { type: 'integer', description: 'Max entries to return (default 200, max 2000).' },
|
|
478
490
|
level: { type: 'string', enum: ['V', 'D', 'I', 'W', 'E', 'F'], description: 'Minimum severity filter (V=verbose … F=fatal).' },
|
|
479
491
|
filter: { type: 'string', description: 'Substring to filter the raw line (case-insensitive).' },
|
|
492
|
+
package: { type: 'string', description: 'App package to filter by (pid of the running app); falls back to the package set via logcat_set_package.' },
|
|
480
493
|
},
|
|
481
494
|
output: {
|
|
482
495
|
schema: {
|
|
@@ -499,6 +512,7 @@ function logcatRecentTool(engine) {
|
|
|
499
512
|
},
|
|
500
513
|
},
|
|
501
514
|
},
|
|
515
|
+
note: { type: 'string' },
|
|
502
516
|
},
|
|
503
517
|
},
|
|
504
518
|
render: (_args, value) => {
|
|
@@ -521,7 +535,45 @@ function logcatRecentTool(engine) {
|
|
|
521
535
|
const lines = Math.min(Math.max(Number(args.lines ?? 200) || 200, 1), 2000)
|
|
522
536
|
const level = typeof args.level === 'string' ? args.level : ''
|
|
523
537
|
const filter = typeof args.filter === 'string' ? args.filter : ''
|
|
524
|
-
|
|
538
|
+
const pkg = (typeof args.package === 'string' ? args.package.trim() : '') || engine.currentPackage
|
|
539
|
+
const picked = engine.recent(serial, lines, level, filter)
|
|
540
|
+
if (pkg === '') return { entries: picked }
|
|
541
|
+
const pids = await engine.pidsOfPackage(serial, pkg)
|
|
542
|
+
if (pids.length === 0) {
|
|
543
|
+
return { entries: [], note: `package ${pkg} is not running on ${serial} — start it first (adb_exec: am start -n ...) or pass another package` }
|
|
544
|
+
}
|
|
545
|
+
const byPid = picked.filter((e) => e.pid > 0 && pids.includes(e.pid))
|
|
546
|
+
return byPid.length > 0
|
|
547
|
+
? { entries: byPid }
|
|
548
|
+
: { entries: [], note: `no buffered logcat lines for package ${pkg} (pids ${pids.join(', ')}); the app may not have logged yet` }
|
|
549
|
+
},
|
|
550
|
+
})
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/** The logcat_set_package agent tool: set/clear the app package currently under test. */
|
|
554
|
+
function logcatSetPackageTool(engine) {
|
|
555
|
+
return defineTool({
|
|
556
|
+
name: 'logcat_set_package',
|
|
557
|
+
description: 'Set (or clear) the Android app package currently under test, so logcat_recent filters by it. ' +
|
|
558
|
+
'Call after installing/starting the app you are debugging (e.g. via adb_exec). ' +
|
|
559
|
+
'Pass an empty string to clear the filter. Triggers: focus logs on one app, filter logcat by package.',
|
|
560
|
+
parameters: {
|
|
561
|
+
package: { type: 'string', description: 'The app package to track, e.g. "com.example.app"; an empty string clears it.' },
|
|
562
|
+
},
|
|
563
|
+
output: {
|
|
564
|
+
schema: {
|
|
565
|
+
type: 'object',
|
|
566
|
+
additionalProperties: false,
|
|
567
|
+
properties: {
|
|
568
|
+
package: { type: 'string', required: true },
|
|
569
|
+
},
|
|
570
|
+
},
|
|
571
|
+
render: (_args, value) => [{ type: 'text', text: value?.package ? `tracking package: ${value.package}` : 'package filter cleared' }],
|
|
572
|
+
},
|
|
573
|
+
async execute(args) {
|
|
574
|
+
const pkg = typeof args.package === 'string' ? args.package.trim() : ''
|
|
575
|
+
engine.currentPackage = pkg
|
|
576
|
+
return { package: pkg }
|
|
525
577
|
},
|
|
526
578
|
})
|
|
527
579
|
}
|
|
@@ -639,6 +691,7 @@ export function apply(ctx, config) {
|
|
|
639
691
|
devices: engine.deviceList(),
|
|
640
692
|
streaming: [...engine.streams.keys()],
|
|
641
693
|
bufferSizes: Object.fromEntries([...engine.buffers.entries()].map(([s, b]) => [s, b.length])),
|
|
694
|
+
currentPackage: engine.currentPackage,
|
|
642
695
|
}),
|
|
643
696
|
}
|
|
644
697
|
if (typeof ctx.provide === 'function') ctx.provide('logcat', logcatHandle)
|
|
@@ -683,6 +736,7 @@ export function apply(ctx, config) {
|
|
|
683
736
|
logcatRecentTool(engine),
|
|
684
737
|
logcatDevicesTool(engine),
|
|
685
738
|
adbExecTool(engine),
|
|
739
|
+
logcatSetPackageTool(engine),
|
|
686
740
|
].map((tool) => ctx.tools.register(tool))
|
|
687
741
|
return () => { for (const dispose of disposers) dispose() }
|
|
688
742
|
}, 'dsh-logcat: tools')
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@windypro-rourou/dsh-logcat",
|
|
3
3
|
"description": "Android Logcat viewer for the dsh web GUI: auto-connects to any adb device in debug mode, live logcat stream with level/keyword filters, pause/clear/export, plus agent tools (logcat_recent). Hot-pluggable — mounted via ~/.dsh/cordis.patch.yml + a profile node_modules copy, no dsh source changes.",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.4",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "pnpm@11.22.0",
|
|
7
7
|
"engines": {
|