simvyn 2.5.4 → 2.6.1
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 +6 -3
- package/dist/dashboard/assets/{index-C-atBUQQ.css → index-B-km6Iwq.css} +22 -0
- package/dist/dashboard/assets/{index-rWeZ9VzX.js → index-OLUFfF_h.js} +356 -44
- package/dist/dashboard/assets/index-OLUFfF_h.js.map +1 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/index.js +15 -2
- package/package.json +1 -1
- package/dist/dashboard/assets/index-rWeZ9VzX.js.map +0 -1
package/README.md
CHANGED
|
@@ -50,9 +50,9 @@ npx simvyn
|
|
|
50
50
|
|
|
51
51
|
## Features
|
|
52
52
|
|
|
53
|
-
- **Device Management** — Discover, boot, shutdown, erase, create, and clone simulators, emulators, and physical devices
|
|
53
|
+
- **Device Management** — Discover, boot, shutdown, erase, create, and clone simulators, emulators, and physical devices with favourite pinning
|
|
54
54
|
- **App Management** — Install, launch, terminate, uninstall apps via drag-and-drop or CLI
|
|
55
|
-
- **Log Viewer** — Real-time streaming with level filtering, regex search, pagination, and export
|
|
55
|
+
- **Log Viewer** — Real-time streaming with level filtering, find-in-page search (Cmd+F), regex search, pagination, and export
|
|
56
56
|
- **Location Simulation** — Set GPS coordinates, play GPX/KML routes with speed control, save favorites
|
|
57
57
|
- **Device Settings** — Dark mode, locale, permissions, status bar overrides, accessibility presets
|
|
58
58
|
- **Screenshots and Recording** — Capture screenshots and record screen video with history
|
|
@@ -81,6 +81,7 @@ Discover all connected iOS Simulators, Android Emulators, and USB-connected phys
|
|
|
81
81
|
- Boot, shutdown, and erase devices with one click or CLI command
|
|
82
82
|
- Create new iOS simulators with device type and runtime selection
|
|
83
83
|
- Clone and rename existing simulators
|
|
84
|
+
- Pin favourite devices to the top of the device selector
|
|
84
85
|
- Real-time device state updates via WebSocket
|
|
85
86
|
|
|
86
87
|
### App Management
|
|
@@ -97,15 +98,17 @@ Install, launch, terminate, and uninstall apps on any connected device. Drag and
|
|
|
97
98
|
|
|
98
99
|
### Log Viewer
|
|
99
100
|
|
|
100
|
-
Stream device logs in real time with powerful filtering. Filter by log level, search with regex patterns, and filter by process name. Export filtered logs for sharing or archival. Paginated history lets you scroll back through thousands of entries without performance issues.
|
|
101
|
+
Stream device logs in real time with powerful filtering. Filter by log level, search with regex patterns, and filter by process name. Use find-in-page search (Cmd+F) to highlight specific entries without hiding surrounding context. Export filtered logs for sharing or archival. Paginated history lets you scroll back through thousands of entries without performance issues.
|
|
101
102
|
|
|
102
103
|
<p align="center"><img src="https://raw.githubusercontent.com/pranshuchittora/simvyn/main/docs/assets/log-viewer.png" alt="Log Viewer" width="700" /></p>
|
|
103
104
|
|
|
104
105
|
- Real-time log streaming via WebSocket
|
|
105
106
|
- Filter by level: debug, info, warning, error, fatal
|
|
107
|
+
- Find-in-page search (Cmd+F) with match highlighting, navigation, and counter
|
|
106
108
|
- Regex search and process name filtering
|
|
107
109
|
- Paginated history with virtual scrolling
|
|
108
110
|
- Export filtered logs to file
|
|
111
|
+
- Pause/resume log streaming while preserving state across clears
|
|
109
112
|
|
|
110
113
|
### Location
|
|
111
114
|
|
|
@@ -420,6 +420,10 @@
|
|
|
420
420
|
top: 50%;
|
|
421
421
|
}
|
|
422
422
|
|
|
423
|
+
.top-2 {
|
|
424
|
+
top: calc(var(--spacing) * 2);
|
|
425
|
+
}
|
|
426
|
+
|
|
423
427
|
.top-full {
|
|
424
428
|
top: 100%;
|
|
425
429
|
}
|
|
@@ -428,6 +432,10 @@
|
|
|
428
432
|
right: calc(var(--spacing) * 0);
|
|
429
433
|
}
|
|
430
434
|
|
|
435
|
+
.right-2 {
|
|
436
|
+
right: calc(var(--spacing) * 2);
|
|
437
|
+
}
|
|
438
|
+
|
|
431
439
|
.right-3 {
|
|
432
440
|
right: calc(var(--spacing) * 3);
|
|
433
441
|
}
|
|
@@ -853,6 +861,10 @@
|
|
|
853
861
|
min-width: calc(var(--spacing) * 0);
|
|
854
862
|
}
|
|
855
863
|
|
|
864
|
+
.min-w-\[60px\] {
|
|
865
|
+
min-width: 60px;
|
|
866
|
+
}
|
|
867
|
+
|
|
856
868
|
.min-w-\[140px\] {
|
|
857
869
|
min-width: 140px;
|
|
858
870
|
}
|
|
@@ -2132,6 +2144,16 @@
|
|
|
2132
2144
|
}
|
|
2133
2145
|
}
|
|
2134
2146
|
|
|
2147
|
+
.hover\:bg-white\/\[0\.06\]:hover {
|
|
2148
|
+
background-color: #ffffff0f;
|
|
2149
|
+
}
|
|
2150
|
+
|
|
2151
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
2152
|
+
.hover\:bg-white\/\[0\.06\]:hover {
|
|
2153
|
+
background-color: color-mix(in oklab, var(--color-white) 6%, transparent);
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2135
2157
|
.hover\:text-accent-blue:hover {
|
|
2136
2158
|
color: var(--color-accent-blue);
|
|
2137
2159
|
}
|
|
@@ -18544,27 +18544,27 @@ const createLucideIcon = (iconName, iconNode) => {
|
|
|
18544
18544
|
Component.displayName = toPascalCase(iconName);
|
|
18545
18545
|
return Component;
|
|
18546
18546
|
};
|
|
18547
|
-
const __iconNode$
|
|
18547
|
+
const __iconNode$W = [
|
|
18548
18548
|
["path", { d: "M12 5v14", key: "s699le" }],
|
|
18549
18549
|
["path", { d: "m19 12-7 7-7-7", key: "1idqje" }]
|
|
18550
18550
|
];
|
|
18551
|
-
const ArrowDown = createLucideIcon("arrow-down", __iconNode$
|
|
18552
|
-
const __iconNode$
|
|
18551
|
+
const ArrowDown = createLucideIcon("arrow-down", __iconNode$W);
|
|
18552
|
+
const __iconNode$V = [
|
|
18553
18553
|
["path", { d: "m12 19-7-7 7-7", key: "1l729n" }],
|
|
18554
18554
|
["path", { d: "M19 12H5", key: "x3x0zl" }]
|
|
18555
18555
|
];
|
|
18556
|
-
const ArrowLeft = createLucideIcon("arrow-left", __iconNode$
|
|
18557
|
-
const __iconNode$
|
|
18556
|
+
const ArrowLeft = createLucideIcon("arrow-left", __iconNode$V);
|
|
18557
|
+
const __iconNode$U = [
|
|
18558
18558
|
["path", { d: "m5 12 7-7 7 7", key: "hav0vg" }],
|
|
18559
18559
|
["path", { d: "M12 19V5", key: "x0mq9r" }]
|
|
18560
18560
|
];
|
|
18561
|
-
const ArrowUp = createLucideIcon("arrow-up", __iconNode$
|
|
18562
|
-
const __iconNode$
|
|
18561
|
+
const ArrowUp = createLucideIcon("arrow-up", __iconNode$U);
|
|
18562
|
+
const __iconNode$T = [
|
|
18563
18563
|
["path", { d: "M 22 14 L 22 10", key: "nqc4tb" }],
|
|
18564
18564
|
["rect", { x: "2", y: "6", width: "16", height: "12", rx: "2", key: "13zb55" }]
|
|
18565
18565
|
];
|
|
18566
|
-
const Battery = createLucideIcon("battery", __iconNode$
|
|
18567
|
-
const __iconNode$
|
|
18566
|
+
const Battery = createLucideIcon("battery", __iconNode$T);
|
|
18567
|
+
const __iconNode$S = [
|
|
18568
18568
|
["path", { d: "M12 20v-9", key: "1qisl0" }],
|
|
18569
18569
|
["path", { d: "M14 7a4 4 0 0 1 4 4v3a6 6 0 0 1-12 0v-3a4 4 0 0 1 4-4z", key: "uouzyp" }],
|
|
18570
18570
|
["path", { d: "M14.12 3.88 16 2", key: "qol33r" }],
|
|
@@ -18577,8 +18577,8 @@ const __iconNode$R = [
|
|
|
18577
18577
|
["path", { d: "m8 2 1.88 1.88", key: "fmnt4t" }],
|
|
18578
18578
|
["path", { d: "M9 7.13V6a3 3 0 1 1 6 0v1.13", key: "1vgav8" }]
|
|
18579
18579
|
];
|
|
18580
|
-
const Bug = createLucideIcon("bug", __iconNode$
|
|
18581
|
-
const __iconNode$
|
|
18580
|
+
const Bug = createLucideIcon("bug", __iconNode$S);
|
|
18581
|
+
const __iconNode$R = [
|
|
18582
18582
|
[
|
|
18583
18583
|
"path",
|
|
18584
18584
|
{
|
|
@@ -18588,46 +18588,52 @@ const __iconNode$Q = [
|
|
|
18588
18588
|
],
|
|
18589
18589
|
["circle", { cx: "12", cy: "13", r: "3", key: "1vg3eu" }]
|
|
18590
18590
|
];
|
|
18591
|
-
const Camera = createLucideIcon("camera", __iconNode$
|
|
18592
|
-
const __iconNode$
|
|
18593
|
-
const Check = createLucideIcon("check", __iconNode$
|
|
18594
|
-
const __iconNode$
|
|
18595
|
-
const ChevronDown = createLucideIcon("chevron-down", __iconNode$
|
|
18596
|
-
const __iconNode$
|
|
18597
|
-
const ChevronLeft = createLucideIcon("chevron-left", __iconNode$
|
|
18598
|
-
const __iconNode$
|
|
18599
|
-
const ChevronRight = createLucideIcon("chevron-right", __iconNode$
|
|
18600
|
-
const __iconNode$
|
|
18601
|
-
const ChevronUp = createLucideIcon("chevron-up", __iconNode$
|
|
18602
|
-
const __iconNode$
|
|
18591
|
+
const Camera = createLucideIcon("camera", __iconNode$R);
|
|
18592
|
+
const __iconNode$Q = [["path", { d: "M20 6 9 17l-5-5", key: "1gmf2c" }]];
|
|
18593
|
+
const Check = createLucideIcon("check", __iconNode$Q);
|
|
18594
|
+
const __iconNode$P = [["path", { d: "m6 9 6 6 6-6", key: "qrunsl" }]];
|
|
18595
|
+
const ChevronDown = createLucideIcon("chevron-down", __iconNode$P);
|
|
18596
|
+
const __iconNode$O = [["path", { d: "m15 18-6-6 6-6", key: "1wnfg3" }]];
|
|
18597
|
+
const ChevronLeft = createLucideIcon("chevron-left", __iconNode$O);
|
|
18598
|
+
const __iconNode$N = [["path", { d: "m9 18 6-6-6-6", key: "mthhwq" }]];
|
|
18599
|
+
const ChevronRight = createLucideIcon("chevron-right", __iconNode$N);
|
|
18600
|
+
const __iconNode$M = [["path", { d: "m18 15-6-6-6 6", key: "153udz" }]];
|
|
18601
|
+
const ChevronUp = createLucideIcon("chevron-up", __iconNode$M);
|
|
18602
|
+
const __iconNode$L = [
|
|
18603
18603
|
["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
|
|
18604
18604
|
["path", { d: "M12 6v6l4 2", key: "mmk7yg" }]
|
|
18605
18605
|
];
|
|
18606
|
-
const Clock = createLucideIcon("clock", __iconNode$
|
|
18607
|
-
const __iconNode$
|
|
18606
|
+
const Clock = createLucideIcon("clock", __iconNode$L);
|
|
18607
|
+
const __iconNode$K = [
|
|
18608
18608
|
["line", { x1: "15", x2: "15", y1: "12", y2: "18", key: "1p7wdc" }],
|
|
18609
18609
|
["line", { x1: "12", x2: "18", y1: "15", y2: "15", key: "1nscbv" }],
|
|
18610
18610
|
["rect", { width: "14", height: "14", x: "8", y: "8", rx: "2", ry: "2", key: "17jyea" }],
|
|
18611
18611
|
["path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2", key: "zix9uf" }]
|
|
18612
18612
|
];
|
|
18613
|
-
const CopyPlus = createLucideIcon("copy-plus", __iconNode$
|
|
18614
|
-
const __iconNode$
|
|
18613
|
+
const CopyPlus = createLucideIcon("copy-plus", __iconNode$K);
|
|
18614
|
+
const __iconNode$J = [
|
|
18615
18615
|
["rect", { width: "14", height: "14", x: "8", y: "8", rx: "2", ry: "2", key: "17jyea" }],
|
|
18616
18616
|
["path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2", key: "zix9uf" }]
|
|
18617
18617
|
];
|
|
18618
|
-
const Copy = createLucideIcon("copy", __iconNode$
|
|
18619
|
-
const __iconNode$
|
|
18618
|
+
const Copy = createLucideIcon("copy", __iconNode$J);
|
|
18619
|
+
const __iconNode$I = [
|
|
18620
18620
|
["ellipse", { cx: "12", cy: "5", rx: "9", ry: "3", key: "msslwz" }],
|
|
18621
18621
|
["path", { d: "M3 5V19A9 3 0 0 0 21 19V5", key: "1wlel7" }],
|
|
18622
18622
|
["path", { d: "M3 12A9 3 0 0 0 21 12", key: "mv7ke4" }]
|
|
18623
18623
|
];
|
|
18624
|
-
const Database = createLucideIcon("database", __iconNode$
|
|
18625
|
-
const __iconNode$
|
|
18624
|
+
const Database = createLucideIcon("database", __iconNode$I);
|
|
18625
|
+
const __iconNode$H = [
|
|
18626
18626
|
["path", { d: "M12 15V3", key: "m9g1x1" }],
|
|
18627
18627
|
["path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4", key: "ih7n3h" }],
|
|
18628
18628
|
["path", { d: "m7 10 5 5 5-5", key: "brsn70" }]
|
|
18629
18629
|
];
|
|
18630
|
-
const Download = createLucideIcon("download", __iconNode$
|
|
18630
|
+
const Download = createLucideIcon("download", __iconNode$H);
|
|
18631
|
+
const __iconNode$G = [
|
|
18632
|
+
["circle", { cx: "12", cy: "12", r: "1", key: "41hilf" }],
|
|
18633
|
+
["circle", { cx: "19", cy: "12", r: "1", key: "1wjl8i" }],
|
|
18634
|
+
["circle", { cx: "5", cy: "12", r: "1", key: "1pcz8c" }]
|
|
18635
|
+
];
|
|
18636
|
+
const Ellipsis = createLucideIcon("ellipsis", __iconNode$G);
|
|
18631
18637
|
const __iconNode$F = [
|
|
18632
18638
|
[
|
|
18633
18639
|
"path",
|
|
@@ -47524,8 +47530,7 @@ const useLogStore = create((set) => ({
|
|
|
47524
47530
|
entries: [],
|
|
47525
47531
|
firstItemIndex: INITIAL_INDEX,
|
|
47526
47532
|
cursor: null,
|
|
47527
|
-
hasMore: true
|
|
47528
|
-
isPaused: false
|
|
47533
|
+
hasMore: true
|
|
47529
47534
|
}),
|
|
47530
47535
|
reset: () => set({ ...initialState }),
|
|
47531
47536
|
setStreaming: (deviceId) => set({ streamDeviceId: deviceId, isStreaming: deviceId !== null }),
|
|
@@ -47563,6 +47568,42 @@ function filterEntries(entries2, enabledLevels, processFilter, searchPattern) {
|
|
|
47563
47568
|
}
|
|
47564
47569
|
return filtered;
|
|
47565
47570
|
}
|
|
47571
|
+
const useSearchStore = create((set) => ({
|
|
47572
|
+
isOpen: false,
|
|
47573
|
+
query: "",
|
|
47574
|
+
isRegex: false,
|
|
47575
|
+
matchIndices: [],
|
|
47576
|
+
currentMatchIdx: -1,
|
|
47577
|
+
totalMatches: 0,
|
|
47578
|
+
open: () => set({ isOpen: true }),
|
|
47579
|
+
close: () => set({
|
|
47580
|
+
isOpen: false,
|
|
47581
|
+
query: "",
|
|
47582
|
+
matchIndices: [],
|
|
47583
|
+
currentMatchIdx: -1,
|
|
47584
|
+
totalMatches: 0
|
|
47585
|
+
}),
|
|
47586
|
+
setQuery: (q) => set({
|
|
47587
|
+
query: q,
|
|
47588
|
+
currentMatchIdx: q ? 0 : -1
|
|
47589
|
+
}),
|
|
47590
|
+
toggleRegex: () => set((s) => ({ isRegex: !s.isRegex })),
|
|
47591
|
+
setMatches: (indices) => set((s) => ({
|
|
47592
|
+
matchIndices: indices,
|
|
47593
|
+
totalMatches: indices.length,
|
|
47594
|
+
currentMatchIdx: indices.length === 0 ? -1 : s.currentMatchIdx >= indices.length ? 0 : s.currentMatchIdx < 0 ? 0 : s.currentMatchIdx
|
|
47595
|
+
})),
|
|
47596
|
+
nextMatch: () => set((s) => {
|
|
47597
|
+
if (s.totalMatches === 0) return s;
|
|
47598
|
+
return { currentMatchIdx: (s.currentMatchIdx + 1) % s.totalMatches };
|
|
47599
|
+
}),
|
|
47600
|
+
prevMatch: () => set((s) => {
|
|
47601
|
+
if (s.totalMatches === 0) return s;
|
|
47602
|
+
return {
|
|
47603
|
+
currentMatchIdx: (s.currentMatchIdx - 1 + s.totalMatches) % s.totalMatches
|
|
47604
|
+
};
|
|
47605
|
+
})
|
|
47606
|
+
}));
|
|
47566
47607
|
const levelColors = {
|
|
47567
47608
|
verbose: "text-gray-500",
|
|
47568
47609
|
debug: "text-cyan-400",
|
|
@@ -47583,6 +47624,64 @@ function formatTime(ts) {
|
|
|
47583
47624
|
return ts;
|
|
47584
47625
|
}
|
|
47585
47626
|
}
|
|
47627
|
+
function escapeRegex(s) {
|
|
47628
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
47629
|
+
}
|
|
47630
|
+
function highlightText(text, query, isRegex, isActiveRow) {
|
|
47631
|
+
if (!query) return text;
|
|
47632
|
+
let re2;
|
|
47633
|
+
try {
|
|
47634
|
+
re2 = isRegex ? new RegExp(query, "gi") : new RegExp(escapeRegex(query), "gi");
|
|
47635
|
+
} catch {
|
|
47636
|
+
return text;
|
|
47637
|
+
}
|
|
47638
|
+
const parts = [];
|
|
47639
|
+
let lastIndex = 0;
|
|
47640
|
+
let match;
|
|
47641
|
+
let i = 0;
|
|
47642
|
+
while ((match = re2.exec(text)) !== null) {
|
|
47643
|
+
if (match.index > lastIndex) {
|
|
47644
|
+
parts.push(text.slice(lastIndex, match.index));
|
|
47645
|
+
}
|
|
47646
|
+
parts.push(
|
|
47647
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
47648
|
+
"mark",
|
|
47649
|
+
{
|
|
47650
|
+
style: isActiveRow ? {
|
|
47651
|
+
background: "rgba(245,158,11,0.4)",
|
|
47652
|
+
color: "rgb(253,230,138)",
|
|
47653
|
+
borderRadius: 2,
|
|
47654
|
+
padding: "0 2px",
|
|
47655
|
+
boxShadow: "0 0 0 1px rgba(245,158,11,0.5)"
|
|
47656
|
+
} : {
|
|
47657
|
+
background: "rgba(245,158,11,0.2)",
|
|
47658
|
+
color: "rgb(253,211,155)",
|
|
47659
|
+
borderRadius: 2,
|
|
47660
|
+
padding: "0 2px"
|
|
47661
|
+
},
|
|
47662
|
+
children: match[0]
|
|
47663
|
+
},
|
|
47664
|
+
i++
|
|
47665
|
+
)
|
|
47666
|
+
);
|
|
47667
|
+
lastIndex = re2.lastIndex;
|
|
47668
|
+
if (match[0].length === 0) re2.lastIndex++;
|
|
47669
|
+
}
|
|
47670
|
+
if (lastIndex < text.length) {
|
|
47671
|
+
parts.push(text.slice(lastIndex));
|
|
47672
|
+
}
|
|
47673
|
+
return parts.length > 0 ? parts : text;
|
|
47674
|
+
}
|
|
47675
|
+
function testMatch(entry, query, isRegex) {
|
|
47676
|
+
if (!query) return false;
|
|
47677
|
+
try {
|
|
47678
|
+
const re2 = isRegex ? new RegExp(query, "i") : new RegExp(escapeRegex(query), "i");
|
|
47679
|
+
return re2.test(entry.message) || re2.test(entry.processName);
|
|
47680
|
+
} catch {
|
|
47681
|
+
const lq = query.toLowerCase();
|
|
47682
|
+
return entry.message.toLowerCase().includes(lq) || entry.processName.toLowerCase().includes(lq);
|
|
47683
|
+
}
|
|
47684
|
+
}
|
|
47586
47685
|
function LogList({ onLoadMore }) {
|
|
47587
47686
|
const virtuosoRef = reactExports.useRef(null);
|
|
47588
47687
|
const [locked, setLocked] = reactExports.useState(true);
|
|
@@ -47598,6 +47697,33 @@ function LogList({ onLoadMore }) {
|
|
|
47598
47697
|
const firstItemIndex = useLogStore((s) => s.firstItemIndex);
|
|
47599
47698
|
const hasMore = useLogStore((s) => s.hasMore);
|
|
47600
47699
|
const isLoadingHistory = useLogStore((s) => s.isLoadingHistory);
|
|
47700
|
+
const searchQuery = useSearchStore((s) => s.query);
|
|
47701
|
+
const searchIsRegex = useSearchStore((s) => s.isRegex);
|
|
47702
|
+
const currentMatchIdx = useSearchStore((s) => s.currentMatchIdx);
|
|
47703
|
+
const matchIndices = useSearchStore((s) => s.matchIndices);
|
|
47704
|
+
reactExports.useEffect(() => {
|
|
47705
|
+
if (!searchQuery) {
|
|
47706
|
+
useSearchStore.getState().setMatches([]);
|
|
47707
|
+
return;
|
|
47708
|
+
}
|
|
47709
|
+
const indices = [];
|
|
47710
|
+
for (let i = 0; i < entries2.length; i++) {
|
|
47711
|
+
if (testMatch(entries2[i], searchQuery, searchIsRegex)) {
|
|
47712
|
+
indices.push(i);
|
|
47713
|
+
}
|
|
47714
|
+
}
|
|
47715
|
+
useSearchStore.getState().setMatches(indices);
|
|
47716
|
+
}, [searchQuery, searchIsRegex, entries2]);
|
|
47717
|
+
reactExports.useEffect(() => {
|
|
47718
|
+
if (currentMatchIdx >= 0 && currentMatchIdx < matchIndices.length) {
|
|
47719
|
+
const entryIndex = matchIndices[currentMatchIdx];
|
|
47720
|
+
virtuosoRef.current?.scrollToIndex({
|
|
47721
|
+
index: entryIndex,
|
|
47722
|
+
align: "center",
|
|
47723
|
+
behavior: "auto"
|
|
47724
|
+
});
|
|
47725
|
+
}
|
|
47726
|
+
}, [currentMatchIdx, matchIndices]);
|
|
47601
47727
|
const jumpToBottom = reactExports.useCallback(() => {
|
|
47602
47728
|
setLocked(true);
|
|
47603
47729
|
virtuosoRef.current?.scrollToIndex({
|
|
@@ -47621,6 +47747,7 @@ function LogList({ onLoadMore }) {
|
|
|
47621
47747
|
) }) : null,
|
|
47622
47748
|
[hasMore, isLoadingHistory, onLoadMore]
|
|
47623
47749
|
);
|
|
47750
|
+
const activeMatchIndex = currentMatchIdx >= 0 ? matchIndices[currentMatchIdx] : -1;
|
|
47624
47751
|
if (entries2.length === 0) {
|
|
47625
47752
|
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "glass-empty-state h-full flex items-center justify-center", children: "No log entries" });
|
|
47626
47753
|
}
|
|
@@ -47639,12 +47766,23 @@ function LogList({ onLoadMore }) {
|
|
|
47639
47766
|
increaseViewportBy: 200,
|
|
47640
47767
|
computeItemKey: (index, entry) => `${entry.timestamp}-${entry.pid}-${index}`,
|
|
47641
47768
|
className: "glass-panel h-full font-mono text-xs leading-relaxed",
|
|
47642
|
-
itemContent: (
|
|
47643
|
-
|
|
47644
|
-
|
|
47645
|
-
/* @__PURE__ */ jsxRuntimeExports.
|
|
47646
|
-
|
|
47647
|
-
|
|
47769
|
+
itemContent: (index, entry) => {
|
|
47770
|
+
const isActiveRow = index === activeMatchIndex;
|
|
47771
|
+
const hasQuery = !!searchQuery;
|
|
47772
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
47773
|
+
"div",
|
|
47774
|
+
{
|
|
47775
|
+
className: "flex gap-2 px-3 py-0.5 hover:bg-white/[0.02]",
|
|
47776
|
+
style: isActiveRow ? { background: "rgba(245,158,11,0.06)" } : void 0,
|
|
47777
|
+
children: [
|
|
47778
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-text-muted shrink-0 w-[90px]", children: formatTime(entry.timestamp) }),
|
|
47779
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `shrink-0 w-[56px] uppercase ${levelColors[entry.level]}`, children: entry.level.slice(0, 5).padEnd(5) }),
|
|
47780
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-text-secondary shrink-0 w-[120px] truncate", children: hasQuery ? highlightText(entry.processName, searchQuery, searchIsRegex, isActiveRow) : entry.processName }),
|
|
47781
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-text-primary break-all", children: hasQuery ? highlightText(entry.message, searchQuery, searchIsRegex, isActiveRow) : entry.message })
|
|
47782
|
+
]
|
|
47783
|
+
}
|
|
47784
|
+
);
|
|
47785
|
+
}
|
|
47648
47786
|
}
|
|
47649
47787
|
),
|
|
47650
47788
|
!locked && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
@@ -47734,6 +47872,18 @@ function LogToolbar({ selectedDeviceId }) {
|
|
|
47734
47872
|
[entries2, enabledLevels, processFilter, searchPattern]
|
|
47735
47873
|
);
|
|
47736
47874
|
const searchTimer = reactExports.useRef(null);
|
|
47875
|
+
const [menuOpen, setMenuOpen] = reactExports.useState(false);
|
|
47876
|
+
const menuRef = reactExports.useRef(null);
|
|
47877
|
+
reactExports.useEffect(() => {
|
|
47878
|
+
if (!menuOpen) return;
|
|
47879
|
+
function handleClickOutside(e) {
|
|
47880
|
+
if (menuRef.current && !menuRef.current.contains(e.target)) {
|
|
47881
|
+
setMenuOpen(false);
|
|
47882
|
+
}
|
|
47883
|
+
}
|
|
47884
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
47885
|
+
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
47886
|
+
}, [menuOpen]);
|
|
47737
47887
|
const handleSearch = reactExports.useCallback(
|
|
47738
47888
|
(value) => {
|
|
47739
47889
|
if (searchTimer.current) clearTimeout(searchTimer.current);
|
|
@@ -47848,9 +47998,158 @@ function LogToolbar({ selectedDeviceId }) {
|
|
|
47848
47998
|
children: "Clear"
|
|
47849
47999
|
}
|
|
47850
48000
|
),
|
|
47851
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-xs ml-auto ${isPaused ? "text-yellow-400" : "text-text-muted"}`, children: isPaused ? "Paused" : filteredCount === totalCount ? `${totalCount} entries` : `${filteredCount} / ${totalCount} entries` })
|
|
48001
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-xs ml-auto ${isPaused ? "text-yellow-400" : "text-text-muted"}`, children: isPaused ? "Paused" : filteredCount === totalCount ? `${totalCount} entries` : `${filteredCount} / ${totalCount} entries` }),
|
|
48002
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative", ref: menuRef, children: [
|
|
48003
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
48004
|
+
"button",
|
|
48005
|
+
{
|
|
48006
|
+
type: "button",
|
|
48007
|
+
onClick: () => setMenuOpen((v) => !v),
|
|
48008
|
+
className: "glass-button px-1.5",
|
|
48009
|
+
title: "More actions",
|
|
48010
|
+
children: /* @__PURE__ */ jsxRuntimeExports.jsx(Ellipsis, { size: 14 })
|
|
48011
|
+
}
|
|
48012
|
+
),
|
|
48013
|
+
menuOpen && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
48014
|
+
"div",
|
|
48015
|
+
{
|
|
48016
|
+
className: "glass-panel absolute right-0 top-full mt-1 py-1 z-50 min-w-[140px]",
|
|
48017
|
+
style: { borderRadius: 8 },
|
|
48018
|
+
children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
48019
|
+
"button",
|
|
48020
|
+
{
|
|
48021
|
+
type: "button",
|
|
48022
|
+
className: "w-full flex items-center justify-between px-3 py-1.5 text-xs text-text-primary hover:bg-white/[0.06] transition-colors",
|
|
48023
|
+
onClick: () => {
|
|
48024
|
+
useSearchStore.getState().open();
|
|
48025
|
+
setMenuOpen(false);
|
|
48026
|
+
},
|
|
48027
|
+
children: [
|
|
48028
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Search" }),
|
|
48029
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-text-muted text-[10px]", children: navigator.platform.includes("Mac") ? "⌘F" : "Ctrl+F" })
|
|
48030
|
+
]
|
|
48031
|
+
}
|
|
48032
|
+
)
|
|
48033
|
+
}
|
|
48034
|
+
)
|
|
48035
|
+
] })
|
|
47852
48036
|
] });
|
|
47853
48037
|
}
|
|
48038
|
+
function SearchOverlay() {
|
|
48039
|
+
const isOpen = useSearchStore((s) => s.isOpen);
|
|
48040
|
+
const query = useSearchStore((s) => s.query);
|
|
48041
|
+
const isRegex = useSearchStore((s) => s.isRegex);
|
|
48042
|
+
const currentMatchIdx = useSearchStore((s) => s.currentMatchIdx);
|
|
48043
|
+
const totalMatches = useSearchStore((s) => s.totalMatches);
|
|
48044
|
+
const setQuery = useSearchStore((s) => s.setQuery);
|
|
48045
|
+
const toggleRegex = useSearchStore((s) => s.toggleRegex);
|
|
48046
|
+
const nextMatch = useSearchStore((s) => s.nextMatch);
|
|
48047
|
+
const prevMatch = useSearchStore((s) => s.prevMatch);
|
|
48048
|
+
const close = useSearchStore((s) => s.close);
|
|
48049
|
+
const inputRef = reactExports.useRef(null);
|
|
48050
|
+
const debounceRef = reactExports.useRef(null);
|
|
48051
|
+
reactExports.useEffect(() => {
|
|
48052
|
+
if (isOpen) {
|
|
48053
|
+
requestAnimationFrame(() => inputRef.current?.focus());
|
|
48054
|
+
}
|
|
48055
|
+
}, [isOpen]);
|
|
48056
|
+
const handleInputChange = reactExports.useCallback(
|
|
48057
|
+
(value) => {
|
|
48058
|
+
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
48059
|
+
debounceRef.current = setTimeout(() => setQuery(value), 100);
|
|
48060
|
+
},
|
|
48061
|
+
[setQuery]
|
|
48062
|
+
);
|
|
48063
|
+
const handleKeyDown = reactExports.useCallback(
|
|
48064
|
+
(e) => {
|
|
48065
|
+
if (e.key === "Enter" && e.shiftKey) {
|
|
48066
|
+
e.preventDefault();
|
|
48067
|
+
prevMatch();
|
|
48068
|
+
} else if (e.key === "Enter") {
|
|
48069
|
+
e.preventDefault();
|
|
48070
|
+
nextMatch();
|
|
48071
|
+
} else if (e.key === "Escape") {
|
|
48072
|
+
e.preventDefault();
|
|
48073
|
+
close();
|
|
48074
|
+
}
|
|
48075
|
+
},
|
|
48076
|
+
[nextMatch, prevMatch, close]
|
|
48077
|
+
);
|
|
48078
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx(AnimatePresence, { children: isOpen && /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
48079
|
+
motion.div,
|
|
48080
|
+
{
|
|
48081
|
+
initial: { y: -10, opacity: 0 },
|
|
48082
|
+
animate: { y: 0, opacity: 1 },
|
|
48083
|
+
exit: { y: -10, opacity: 0 },
|
|
48084
|
+
transition: { duration: 0.15, ease: "easeOut" },
|
|
48085
|
+
className: "absolute top-2 right-2 z-50 flex items-center gap-1.5 glass-panel px-2.5 py-1.5",
|
|
48086
|
+
style: { borderRadius: 10 },
|
|
48087
|
+
children: [
|
|
48088
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
48089
|
+
"input",
|
|
48090
|
+
{
|
|
48091
|
+
ref: inputRef,
|
|
48092
|
+
type: "text",
|
|
48093
|
+
placeholder: "Search logs...",
|
|
48094
|
+
defaultValue: query,
|
|
48095
|
+
onChange: (e) => handleInputChange(e.target.value),
|
|
48096
|
+
onKeyDown: handleKeyDown,
|
|
48097
|
+
className: "glass-input text-xs py-1 px-2 w-[180px]",
|
|
48098
|
+
style: { background: "rgba(0,0,0,0.15)", borderRadius: 6 }
|
|
48099
|
+
}
|
|
48100
|
+
),
|
|
48101
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
48102
|
+
"button",
|
|
48103
|
+
{
|
|
48104
|
+
type: "button",
|
|
48105
|
+
onClick: toggleRegex,
|
|
48106
|
+
className: "glass-button text-xs px-1.5 py-0.5",
|
|
48107
|
+
style: isRegex ? {
|
|
48108
|
+
borderColor: "rgba(0,180,255,0.5)",
|
|
48109
|
+
background: "rgba(0,180,255,0.15)",
|
|
48110
|
+
color: "rgba(100,200,255,1)"
|
|
48111
|
+
} : { fontSize: "0.7rem" },
|
|
48112
|
+
title: "Toggle regex",
|
|
48113
|
+
children: ".*"
|
|
48114
|
+
}
|
|
48115
|
+
),
|
|
48116
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
48117
|
+
"button",
|
|
48118
|
+
{
|
|
48119
|
+
type: "button",
|
|
48120
|
+
onClick: prevMatch,
|
|
48121
|
+
disabled: totalMatches === 0,
|
|
48122
|
+
className: "glass-button px-1 py-0.5",
|
|
48123
|
+
title: "Previous match (Shift+Enter)",
|
|
48124
|
+
children: /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronUp, { size: 14 })
|
|
48125
|
+
}
|
|
48126
|
+
),
|
|
48127
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
48128
|
+
"button",
|
|
48129
|
+
{
|
|
48130
|
+
type: "button",
|
|
48131
|
+
onClick: nextMatch,
|
|
48132
|
+
disabled: totalMatches === 0,
|
|
48133
|
+
className: "glass-button px-1 py-0.5",
|
|
48134
|
+
title: "Next match (Enter)",
|
|
48135
|
+
children: /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { size: 14 })
|
|
48136
|
+
}
|
|
48137
|
+
),
|
|
48138
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs text-text-muted whitespace-nowrap min-w-[60px] text-center", children: query ? totalMatches > 0 ? `${currentMatchIdx + 1} of ${totalMatches}` : "No results" : "" }),
|
|
48139
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
48140
|
+
"button",
|
|
48141
|
+
{
|
|
48142
|
+
type: "button",
|
|
48143
|
+
onClick: close,
|
|
48144
|
+
className: "glass-button px-1 py-0.5",
|
|
48145
|
+
title: "Close (Escape)",
|
|
48146
|
+
children: /* @__PURE__ */ jsxRuntimeExports.jsx(X$1, { size: 14 })
|
|
48147
|
+
}
|
|
48148
|
+
)
|
|
48149
|
+
]
|
|
48150
|
+
}
|
|
48151
|
+
) });
|
|
48152
|
+
}
|
|
47854
48153
|
function LogPanel() {
|
|
47855
48154
|
const { send } = useWs();
|
|
47856
48155
|
const selectedDeviceId = useDeviceStore((s) => s.selectedDeviceIds[0] ?? null);
|
|
@@ -47929,6 +48228,16 @@ function LogPanel() {
|
|
|
47929
48228
|
}
|
|
47930
48229
|
wasPausedRef.current = isPaused;
|
|
47931
48230
|
}, [isPaused, send, clear]);
|
|
48231
|
+
reactExports.useEffect(() => {
|
|
48232
|
+
function handleKeyDown(e) {
|
|
48233
|
+
if ((e.metaKey || e.ctrlKey) && e.key === "f") {
|
|
48234
|
+
e.preventDefault();
|
|
48235
|
+
useSearchStore.getState().open();
|
|
48236
|
+
}
|
|
48237
|
+
}
|
|
48238
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
48239
|
+
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
48240
|
+
}, []);
|
|
47932
48241
|
const loadMoreHistory = reactExports.useCallback(() => {
|
|
47933
48242
|
if (!selectedDeviceId || !hasMore || isLoadingHistory) return;
|
|
47934
48243
|
setLoadingHistory(true);
|
|
@@ -48000,7 +48309,10 @@ function LogPanel() {
|
|
|
48000
48309
|
!selectedDeviceId && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "glass-empty-state flex-1 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: "Select a booted device to stream logs" }) }),
|
|
48001
48310
|
selectedDeviceId && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
48002
48311
|
/* @__PURE__ */ jsxRuntimeExports.jsx(LogToolbar, { selectedDeviceId }),
|
|
48003
|
-
/* @__PURE__ */ jsxRuntimeExports.
|
|
48312
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-h-0 relative", children: [
|
|
48313
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(SearchOverlay, {}),
|
|
48314
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(LogList, { onLoadMore: loadMoreHistory })
|
|
48315
|
+
] })
|
|
48004
48316
|
] })
|
|
48005
48317
|
] });
|
|
48006
48318
|
}
|
|
@@ -52043,7 +52355,7 @@ function ToolSettingsPanel() {
|
|
|
52043
52355
|
const [copied, setCopied] = reactExports.useState(false);
|
|
52044
52356
|
const devices = useDeviceStore((s) => s.devices);
|
|
52045
52357
|
const modules = useModuleStore((s) => s.modules);
|
|
52046
|
-
const version = "2.
|
|
52358
|
+
const version = "2.6.1";
|
|
52047
52359
|
const fetchStorage = reactExports.useCallback(() => {
|
|
52048
52360
|
fetch("/api/tool-settings/storage").then((r) => r.json()).then((data) => setStorage(data)).catch(() => {
|
|
52049
52361
|
});
|
|
@@ -53392,4 +53704,4 @@ function App() {
|
|
|
53392
53704
|
clientExports.createRoot(document.getElementById("root")).render(
|
|
53393
53705
|
/* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.StrictMode, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(App, {}) })
|
|
53394
53706
|
);
|
|
53395
|
-
//# sourceMappingURL=index-
|
|
53707
|
+
//# sourceMappingURL=index-OLUFfF_h.js.map
|