sidekick-docker 0.2.2 → 0.2.3

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 CHANGED
@@ -83,6 +83,8 @@ The dashboard has 5 panels, each mapped to a number key:
83
83
  | `x` | Open context menu (actions for selected item) |
84
84
  | `f` | Open log filter (when on Logs tab) |
85
85
  | `/` | Open filter |
86
+ | `m` | Pin/unpin item for log comparison (Containers/Services) |
87
+ | `J` / `K` | Scroll compare pane (when in detail focus) |
86
88
  | `z` | Toggle expanded layout |
87
89
  | `?` | Show help overlay |
88
90
  | `V` | Show version |
@@ -112,6 +114,7 @@ The dashboard has 5 panels, each mapped to a number key:
112
114
  ## Features
113
115
 
114
116
  - **Real-time log streaming** — follows container logs with token-level syntax highlighting (HTTP methods, status codes, URLs, IPs, timestamps, JSON keys)
117
+ - **Dual-log compare** — pin a second container or service with `m` to view both log streams side by side
115
118
  - **Log search & filter** — press `f` on the Logs tab to search within log output with exact or fuzzy matching and match highlighting
116
119
  - **Log analytics** — severity count badges, severity sparkline over time, and pattern clustering that groups similar logs into templates with `<*>` wildcards
117
120
  - **Live stats with sparklines** — CPU, memory, network I/O, block I/O, and log severity charted as inline sparklines (60-sample history)
@@ -56511,7 +56511,7 @@ var require_LogFilter = __commonJS({
56511
56511
  Object.defineProperty(exports2, "__esModule", { value: true });
56512
56512
  exports2.exactMatch = exactMatch;
56513
56513
  exports2.fuzzyMatch = fuzzyMatch;
56514
- exports2.filterLine = filterLine3;
56514
+ exports2.filterLine = filterLine4;
56515
56515
  function exactMatch(line, query) {
56516
56516
  if (!query)
56517
56517
  return { matched: true, matches: [] };
@@ -56546,7 +56546,7 @@ var require_LogFilter = __commonJS({
56546
56546
  matches.sort((a, b) => a.start - b.start);
56547
56547
  return { matched: true, matches };
56548
56548
  }
56549
- function filterLine3(line, query, mode) {
56549
+ function filterLine4(line, query, mode) {
56550
56550
  return mode === "exact" ? exactMatch(line, query) : fuzzyMatch(line, query);
56551
56551
  }
56552
56552
  }
@@ -94290,6 +94290,8 @@ var DockerState = class {
94290
94290
  composeProjects = [];
94291
94291
  selectedLogs = [];
94292
94292
  selectedComposeLogs = [];
94293
+ secondaryLogs = [];
94294
+ secondaryComposeLogs = [];
94293
94295
  inspectedEnv = /* @__PURE__ */ new Map();
94294
94296
  containerChanges = /* @__PURE__ */ new Map();
94295
94297
  imageLayers = /* @__PURE__ */ new Map();
@@ -94400,6 +94402,18 @@ var DockerState = class {
94400
94402
  clearComposeLogs() {
94401
94403
  this.selectedComposeLogs = [];
94402
94404
  }
94405
+ setSecondaryLogs(logs) {
94406
+ this.secondaryLogs = logs;
94407
+ }
94408
+ clearSecondaryLogs() {
94409
+ this.secondaryLogs = [];
94410
+ }
94411
+ setSecondaryComposeLogs(logs) {
94412
+ this.secondaryComposeLogs = logs;
94413
+ }
94414
+ clearSecondaryComposeLogs() {
94415
+ this.secondaryComposeLogs = [];
94416
+ }
94403
94417
  getStatsCollector() {
94404
94418
  return this.statsCollector;
94405
94419
  }
@@ -94434,13 +94448,17 @@ var DockerState = class {
94434
94448
  imageLayers: this.imageLayers,
94435
94449
  selectedContainerLogs: [...this.selectedLogs],
94436
94450
  selectedComposeLogs: [...this.selectedComposeLogs],
94451
+ secondaryContainerLogs: [...this.secondaryLogs],
94452
+ secondaryComposeLogs: [...this.secondaryComposeLogs],
94437
94453
  lastRefresh: this.lastRefresh,
94438
94454
  daemonConnected: this.daemonConnected,
94439
94455
  logFilterString: "",
94440
94456
  logFilterMode: "exact",
94441
94457
  logSeverityCounts: null,
94442
94458
  logSeverityTimeSeries: [],
94443
- logTemplates: []
94459
+ logTemplates: [],
94460
+ secondaryLogSeverityCounts: null,
94461
+ secondaryLogSeverityTimeSeries: []
94444
94462
  };
94445
94463
  }
94446
94464
  };
@@ -94661,6 +94679,64 @@ function colorizeNetworkContainer(name, id) {
94661
94679
  function stripCursorEscapes(text) {
94662
94680
  return text.replace(/\x1b\[\d*[ABCD]/g, "").replace(/\x1b\[\d*(?:;\d*)?[Hf]/g, "").replace(/\x1b\[\d*[JK]/g, "").replace(/\x1b\[\?\d+[hl]/g, "").replace(/\x1b\[[su]/g, "").replace(/\x1b[78]/g, "").replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, "").replace(/\r/g, "");
94663
94681
  }
94682
+ function renderLogLines(logs, filterString, filterMode, severityCounts) {
94683
+ const lines = [];
94684
+ if (severityCounts && severityCounts.total > 0) {
94685
+ const parts = [];
94686
+ if (severityCounts.error > 0) parts.push(`\x1B[31mE:${severityCounts.error}\x1B[39m`);
94687
+ if (severityCounts.warn > 0) parts.push(`\x1B[33mW:${severityCounts.warn}\x1B[39m`);
94688
+ if (severityCounts.info > 0) parts.push(`\x1B[38;2;43;76;126mI:${severityCounts.info}\x1B[39m`);
94689
+ if (severityCounts.debug > 0) parts.push(`\x1B[90mD:${severityCounts.debug}\x1B[39m`);
94690
+ if (parts.length > 0) lines.push(parts.join(" "));
94691
+ }
94692
+ if (filterString) {
94693
+ let matchCount = 0;
94694
+ for (const l of logs) {
94695
+ const result = (0, import_sidekick_docker_shared2.filterLine)(l.message, filterString, filterMode);
94696
+ if (result.matched) {
94697
+ matchCount++;
94698
+ lines.push(colorizeLogEntry(l, result.matches));
94699
+ }
94700
+ }
94701
+ if (lines.length <= (severityCounts ? 1 : 0)) {
94702
+ lines.push(`\x1B[90mNo logs matching "${filterString}"\x1B[39m`);
94703
+ } else {
94704
+ const headerIdx = severityCounts ? 1 : 0;
94705
+ lines.splice(headerIdx, 0, `\x1B[90m${matchCount} matches (f to filter, Tab to toggle mode)\x1B[39m`);
94706
+ }
94707
+ } else {
94708
+ for (const l of logs) {
94709
+ lines.push(colorizeLogEntry(l));
94710
+ }
94711
+ }
94712
+ return lines;
94713
+ }
94714
+ function clipAnsi(str, maxWidth) {
94715
+ let visible = 0;
94716
+ let i = 0;
94717
+ let result = "";
94718
+ while (i < str.length && visible < maxWidth) {
94719
+ if (str[i] === "\x1B") {
94720
+ const start = i;
94721
+ i++;
94722
+ if (i < str.length && str[i] === "[") {
94723
+ i++;
94724
+ while (i < str.length && str[i] !== "m" && !/[A-Za-z]/.test(str[i])) i++;
94725
+ if (i < str.length) i++;
94726
+ }
94727
+ result += str.slice(start, i);
94728
+ } else {
94729
+ result += str[i];
94730
+ visible++;
94731
+ i++;
94732
+ }
94733
+ }
94734
+ if (visible < maxWidth) {
94735
+ result += " ".repeat(maxWidth - visible);
94736
+ }
94737
+ result += "\x1B[0m";
94738
+ return result;
94739
+ }
94664
94740
 
94665
94741
  // src/dashboard/panels/ContainersPanel.ts
94666
94742
  var ContainersPanel = class {
@@ -94687,41 +94763,10 @@ var ContainersPanel = class {
94687
94763
  detailTabs = [
94688
94764
  {
94689
94765
  label: "Logs",
94690
- render: (item, metrics) => {
94766
+ render: (_item, metrics) => {
94691
94767
  const logs = metrics.selectedContainerLogs;
94692
94768
  if (logs.length === 0) return "No logs available. Select a container to view logs.";
94693
- const lines = [];
94694
- if (metrics.logSeverityCounts && metrics.logSeverityCounts.total > 0) {
94695
- const c = metrics.logSeverityCounts;
94696
- const parts = [];
94697
- if (c.error > 0) parts.push(`\x1B[31mE:${c.error}\x1B[39m`);
94698
- if (c.warn > 0) parts.push(`\x1B[33mW:${c.warn}\x1B[39m`);
94699
- if (c.info > 0) parts.push(`\x1B[38;2;43;76;126mI:${c.info}\x1B[39m`);
94700
- if (c.debug > 0) parts.push(`\x1B[90mD:${c.debug}\x1B[39m`);
94701
- if (parts.length > 0) lines.push(parts.join(" "));
94702
- }
94703
- const query = metrics.logFilterString;
94704
- const mode = metrics.logFilterMode;
94705
- if (query) {
94706
- let matchCount = 0;
94707
- for (const l of logs) {
94708
- const result = (0, import_sidekick_docker_shared4.filterLine)(l.message, query, mode);
94709
- if (result.matched) {
94710
- matchCount++;
94711
- lines.push(colorizeLogEntry(l, result.matches));
94712
- }
94713
- }
94714
- if (lines.length <= 1) {
94715
- lines.push(`\x1B[90mNo logs matching "${query}"\x1B[39m`);
94716
- } else {
94717
- lines.splice(1, 0, `\x1B[90m${matchCount} matches (f to filter, Tab to toggle mode)\x1B[39m`);
94718
- }
94719
- } else {
94720
- for (const l of logs) {
94721
- lines.push(colorizeLogEntry(l));
94722
- }
94723
- }
94724
- return lines.join("\n");
94769
+ return renderLogLines(logs, metrics.logFilterString, metrics.logFilterMode, metrics.logSeverityCounts).join("\n");
94725
94770
  },
94726
94771
  autoScrollBottom: true
94727
94772
  },
@@ -95021,7 +95066,7 @@ var ServicesPanel = class {
95021
95066
  render: (_item, metrics) => {
95022
95067
  const logs = metrics.selectedComposeLogs;
95023
95068
  if (logs.length === 0) return "No compose logs. Logs will appear when a service produces output.";
95024
- return logs.map((e) => colorizeLogEntry(e)).join("\n");
95069
+ return renderLogLines(logs, metrics.logFilterString, metrics.logFilterMode).join("\n");
95025
95070
  },
95026
95071
  autoScrollBottom: true
95027
95072
  }
@@ -95764,7 +95809,7 @@ function handleFilterInput(input, key, opts) {
95764
95809
  }
95765
95810
  function useKeyboardHandler(ctx) {
95766
95811
  const { exit } = use_app_default();
95767
- const { state, dispatch, panels, panel, selectedItem, contextActions, clampedSelection, currentItems, detailLines, detailViewportHeight, detailTabs, tabIdx, panelActions, sideScroll, addToast, removeToast, rotatePhrase } = ctx;
95812
+ const { state, dispatch, panels, panel, selectedItem, contextActions, clampedSelection, currentItems, detailLines, detailViewportHeight, detailTabs, tabIdx, panelActions, sideScroll, addToast, removeToast, rotatePhrase, secondaryDetailLineCount } = ctx;
95768
95813
  use_input_default((input, key) => {
95769
95814
  if (state.overlay === "exec") return;
95770
95815
  rotatePhrase();
@@ -95935,6 +95980,18 @@ function useKeyboardHandler(ctx) {
95935
95980
  addToast(`Sort: ${state.sortReversed ? "ascending" : "descending"}`, "info");
95936
95981
  return;
95937
95982
  }
95983
+ if (input === "m" && selectedItem && (panel.id === "containers" || panel.id === "services")) {
95984
+ const currentCompare = state.compareItemIds[panel.id] ?? null;
95985
+ if (currentCompare === selectedItem.id) {
95986
+ dispatch({ type: "PIN_COMPARE", panelId: panel.id, itemId: selectedItem.id });
95987
+ addToast("Unpinned comparison", "info");
95988
+ } else {
95989
+ dispatch({ type: "PIN_COMPARE", panelId: panel.id, itemId: selectedItem.id });
95990
+ const label = selectedItem.label.replace(/^[^\w]*/, "").trim();
95991
+ addToast(`Pinned ${label} for comparison`, "info");
95992
+ }
95993
+ return;
95994
+ }
95938
95995
  if (input === "x") {
95939
95996
  if (selectedItem && panelActions.length > 0) {
95940
95997
  dispatch({ type: "SET_OVERLAY", overlay: "context-menu" });
@@ -95980,6 +96037,14 @@ function useKeyboardHandler(ctx) {
95980
96037
  }
95981
96038
  }
95982
96039
  if (state.focusTarget === "detail") {
96040
+ if (input === "J" && secondaryDetailLineCount > 0) {
96041
+ dispatch({ type: "SCROLL_SECONDARY_DETAIL_DELTA", delta: 1, totalLines: secondaryDetailLineCount, viewportHeight: detailViewportHeight });
96042
+ return;
96043
+ }
96044
+ if (input === "K" && secondaryDetailLineCount > 0) {
96045
+ dispatch({ type: "SCROLL_SECONDARY_DETAIL_DELTA", delta: -1, totalLines: secondaryDetailLineCount, viewportHeight: detailViewportHeight });
96046
+ return;
96047
+ }
95983
96048
  if (input === "j" || key.downArrow) {
95984
96049
  dispatch({ type: "SCROLL_DETAIL_DELTA", delta: 1, totalLines: detailLines.length, viewportHeight: detailViewportHeight });
95985
96050
  return;
@@ -96147,7 +96212,7 @@ var EMPTY_HINTS = {
96147
96212
  networks: ["Create a network to get started:", " docker network create my-net"],
96148
96213
  services: ["Start a compose project:", " docker compose up -d"]
96149
96214
  };
96150
- function SideList({ items, selectedIndex, scrollOffset, focused, width, viewportHeight, panelTitle, filterString, panelId, totalCount, runningCount }) {
96215
+ function SideList({ items, selectedIndex, scrollOffset, focused, width, viewportHeight, panelTitle, filterString, panelId, totalCount, runningCount, compareItemId }) {
96151
96216
  const hasScrollUp = scrollOffset > 0;
96152
96217
  const hasScrollDown = scrollOffset + viewportHeight < items.length;
96153
96218
  const aboveCount = scrollOffset;
@@ -96174,7 +96239,8 @@ function SideList({ items, selectedIndex, scrollOffset, focused, width, viewport
96174
96239
  const segments = [...trimmed];
96175
96240
  const icon = segments[0] || "";
96176
96241
  const rest = segments.slice(1).join("");
96177
- const rightLabel = item.rightLabel || "";
96242
+ const isPinned = compareItemId != null && item.id === compareItemId;
96243
+ const rightLabel = isPinned ? `\u{1F4CC}${item.rightLabel ? " " + item.rightLabel : ""}` : item.rightLabel || "";
96178
96244
  const rightLen = rightLabel.length;
96179
96245
  const leftWidth = innerWidth - rightLen - (rightLen ? 1 : 0);
96180
96246
  if (isSelected && focused) {
@@ -96253,11 +96319,67 @@ function DetailPane({ lines, scrollOffset, viewportHeight, focused }) {
96253
96319
  ] });
96254
96320
  }
96255
96321
 
96322
+ // src/dashboard/ink/CompareDetailPane.tsx
96323
+ await init_build2();
96324
+ var import_jsx_runtime5 = __toESM(require_jsx_runtime(), 1);
96325
+ function CompareDetailPane({
96326
+ primaryLines,
96327
+ secondaryLines,
96328
+ primaryScrollOffset,
96329
+ secondaryScrollOffset,
96330
+ viewportHeight,
96331
+ totalWidth,
96332
+ focused,
96333
+ primaryLabel,
96334
+ secondaryLabel
96335
+ }) {
96336
+ const colWidth = Math.max(10, Math.floor((totalWidth - 5) / 2));
96337
+ const headerLeft = clipAnsi(`\x1B[1m${primaryLabel}\x1B[22m`, colWidth);
96338
+ const headerRight = clipAnsi(`\x1B[1m${secondaryLabel}\x1B[22m`, colWidth);
96339
+ const leftVisible = primaryLines.slice(primaryScrollOffset, primaryScrollOffset + viewportHeight);
96340
+ const rightVisible = secondaryLines.slice(secondaryScrollOffset, secondaryScrollOffset + viewportHeight);
96341
+ const leftHasUp = primaryScrollOffset > 0;
96342
+ const leftHasDown = primaryScrollOffset + viewportHeight < primaryLines.length;
96343
+ const rightHasUp = secondaryScrollOffset > 0;
96344
+ const rightHasDown = secondaryScrollOffset + viewportHeight < secondaryLines.length;
96345
+ const maxRows = Math.max(leftVisible.length, rightVisible.length, viewportHeight);
96346
+ const rows = [];
96347
+ for (let i = 0; i < maxRows && i < viewportHeight; i++) {
96348
+ const left = leftVisible[i] ?? "";
96349
+ const right = rightVisible[i] ?? "";
96350
+ rows.push(
96351
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { children: [
96352
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { wrap: "truncate", children: clipAnsi(left, colWidth) }),
96353
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", dimColor: true, children: " \u2502 " }),
96354
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { wrap: "truncate", children: clipAnsi(right, colWidth) })
96355
+ ] }, i)
96356
+ );
96357
+ }
96358
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { flexDirection: "column", flexGrow: 1, borderStyle: focused ? "bold" : "single", borderColor: focused ? "#2B4C7E" : "gray", children: [
96359
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { children: [
96360
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { wrap: "truncate", children: headerLeft }),
96361
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", dimColor: true, children: " \u2502 " }),
96362
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { wrap: "truncate", children: headerRight })
96363
+ ] }),
96364
+ (leftHasUp || rightHasUp) && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { children: [
96365
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", children: leftHasUp ? clipAnsi(`\u25B2 (${primaryScrollOffset} more)`, colWidth) : " ".repeat(colWidth) }),
96366
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", dimColor: true, children: " \u2502 " }),
96367
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", children: rightHasUp ? clipAnsi(`\u25B2 (${secondaryScrollOffset} more)`, colWidth) : " ".repeat(colWidth) })
96368
+ ] }),
96369
+ rows,
96370
+ (leftHasDown || rightHasDown) && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { children: [
96371
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", children: leftHasDown ? clipAnsi(`\u25BC (${Math.max(0, primaryLines.length - primaryScrollOffset - viewportHeight)} more)`, colWidth) : " ".repeat(colWidth) }),
96372
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", dimColor: true, children: " \u2502 " }),
96373
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", children: rightHasDown ? clipAnsi(`\u25BC (${Math.max(0, secondaryLines.length - secondaryScrollOffset - viewportHeight)} more)`, colWidth) : " ".repeat(colWidth) })
96374
+ ] })
96375
+ ] });
96376
+ }
96377
+
96256
96378
  // src/dashboard/ink/StatusBar.tsx
96257
96379
  var import_react32 = __toESM(require_react(), 1);
96258
96380
  await init_build2();
96259
96381
  var import_sidekick_docker_shared9 = __toESM(require_dist(), 1);
96260
- var import_jsx_runtime5 = __toESM(require_jsx_runtime(), 1);
96382
+ var import_jsx_runtime6 = __toESM(require_jsx_runtime(), 1);
96261
96383
  function formatAgo(date) {
96262
96384
  const secs = Math.floor((Date.now() - date.getTime()) / 1e3);
96263
96385
  if (secs < 5) return { text: "just now", stale: false };
@@ -96273,33 +96395,33 @@ function StatusBar({ daemonConnected, focusTarget, panelActionHints, filterStrin
96273
96395
  return () => clearInterval(timer);
96274
96396
  }, []);
96275
96397
  const ago = lastRefresh ? formatAgo(lastRefresh) : null;
96276
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { children: [
96277
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { bold: true, color: "magenta", children: ` \u26A1 ${import_sidekick_docker_shared9.BRAND_INLINE}` }),
96278
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", dimColor: true, children: ` ${import_sidekick_docker_shared9.BRAND_TAGLINE} v${version2}` }),
96279
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", dimColor: true, children: ` ${SEP2} ` }),
96280
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: daemonConnected ? "green" : "red", children: daemonConnected ? `\u25CF ${runningCount ?? 0}/${containerCount ?? 0}` : "\u25CB disconnected" }),
96281
- ago && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: ago.stale ? "yellow" : "gray", dimColor: !ago.stale, children: ` \u21BB ${ago.text}` }),
96282
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", dimColor: true, children: ` ${SEP2} ` }),
96283
- panelActionHints.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
96284
- panelActionHints.map((hint, i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react32.default.Fragment, { children: [
96285
- i > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { children: " " }),
96286
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: hint.destructive ? "red" : "#2B4C7E", children: `${hint.key}:${hint.label}` })
96398
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Box_default, { children: [
96399
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { bold: true, color: "magenta", children: ` \u26A1 ${import_sidekick_docker_shared9.BRAND_INLINE}` }),
96400
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "gray", dimColor: true, children: ` ${import_sidekick_docker_shared9.BRAND_TAGLINE} v${version2}` }),
96401
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "gray", dimColor: true, children: ` ${SEP2} ` }),
96402
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: daemonConnected ? "green" : "red", children: daemonConnected ? `\u25CF ${runningCount ?? 0}/${containerCount ?? 0}` : "\u25CB disconnected" }),
96403
+ ago && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: ago.stale ? "yellow" : "gray", dimColor: !ago.stale, children: ` \u21BB ${ago.text}` }),
96404
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "gray", dimColor: true, children: ` ${SEP2} ` }),
96405
+ panelActionHints.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
96406
+ panelActionHints.map((hint, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react32.default.Fragment, { children: [
96407
+ i > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { children: " " }),
96408
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: hint.destructive ? "red" : "#2B4C7E", children: `${hint.key}:${hint.label}` })
96287
96409
  ] }, hint.key)),
96288
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", dimColor: true, children: ` ${SEP2} ` })
96410
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "gray", dimColor: true, children: ` ${SEP2} ` })
96289
96411
  ] }),
96290
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: focusTarget === "side" ? "#2B4C7E" : "gray", bold: focusTarget === "side", children: "\u25C0" }),
96291
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", dimColor: true, children: "/" }),
96292
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: focusTarget === "detail" ? "#2B4C7E" : "gray", bold: focusTarget === "detail", children: "\u25B6" }),
96293
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", dimColor: true, children: " j/k Tab / ?" }),
96294
- contextHint ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "cyan", dimColor: true, children: ` ${contextHint}` }) : null,
96295
- filterString ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "yellow", bold: true, children: ` \u25C9 "${filterString}"${matchCount !== void 0 && totalCount !== void 0 ? ` ${matchCount}/${totalCount}` : ""}` }) : null
96412
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: focusTarget === "side" ? "#2B4C7E" : "gray", bold: focusTarget === "side", children: "\u25C0" }),
96413
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "gray", dimColor: true, children: "/" }),
96414
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: focusTarget === "detail" ? "#2B4C7E" : "gray", bold: focusTarget === "detail", children: "\u25B6" }),
96415
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "gray", dimColor: true, children: " j/k Tab / ?" }),
96416
+ contextHint ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "cyan", dimColor: true, children: ` ${contextHint}` }) : null,
96417
+ filterString ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "yellow", bold: true, children: ` \u25C9 "${filterString}"${matchCount !== void 0 && totalCount !== void 0 ? ` ${matchCount}/${totalCount}` : ""}` }) : null
96296
96418
  ] });
96297
96419
  }
96298
96420
 
96299
96421
  // src/dashboard/ink/HelpOverlay.tsx
96300
96422
  await init_build2();
96301
96423
  var import_sidekick_docker_shared10 = __toESM(require_dist(), 1);
96302
- var import_jsx_runtime6 = __toESM(require_jsx_runtime(), 1);
96424
+ var import_jsx_runtime7 = __toESM(require_jsx_runtime(), 1);
96303
96425
  var GLOBAL_BINDINGS = [
96304
96426
  { key: "1-5", label: "Switch panel" },
96305
96427
  { key: "j/k", label: "Navigate / scroll" },
@@ -96312,66 +96434,69 @@ var GLOBAL_BINDINGS = [
96312
96434
  { key: "a", label: "Toggle all/running (Containers)" },
96313
96435
  { key: "o", label: "Sort menu (Containers)" },
96314
96436
  { key: "R", label: "Reverse sort (Containers)" },
96437
+ { key: "f", label: "Log filter (Logs tab)" },
96438
+ { key: "m", label: "Pin/unpin log comparison" },
96439
+ { key: "J/K", label: "Scroll compare pane" },
96315
96440
  { key: "V", label: "Version info" },
96316
96441
  { key: "?", label: "This help" },
96317
96442
  { key: "q", label: "Quit" }
96318
96443
  ];
96319
96444
  function KeyBadge({ k }) {
96320
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "white", backgroundColor: "#2B4C7E", bold: true, children: ` ${k} ` });
96445
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: "white", backgroundColor: "#2B4C7E", bold: true, children: ` ${k} ` });
96321
96446
  }
96322
96447
  function HelpOverlay({ panels, activePanelIndex, version: version2 }) {
96323
96448
  const panel = panels[activePanelIndex];
96324
96449
  const actions = panel.getActions();
96325
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Box_default, { flexDirection: "column", flexGrow: 1, padding: 1, children: [
96326
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Box_default, { children: [
96327
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { bold: true, color: "magenta", children: `\u26A1 ${import_sidekick_docker_shared10.BRAND_INLINE} ${import_sidekick_docker_shared10.BRAND_TAGLINE}` }),
96328
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "gray", dimColor: true, children: ` v${version2}` })
96450
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Box_default, { flexDirection: "column", flexGrow: 1, padding: 1, children: [
96451
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Box_default, { children: [
96452
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { bold: true, color: "magenta", children: `\u26A1 ${import_sidekick_docker_shared10.BRAND_INLINE} ${import_sidekick_docker_shared10.BRAND_TAGLINE}` }),
96453
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: "gray", dimColor: true, children: ` v${version2}` })
96329
96454
  ] }),
96330
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { children: "" }),
96331
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Box_default, { children: [
96332
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { bold: true, color: "yellow", children: "\u2500\u2500 Navigation " }),
96333
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "gray", dimColor: true, children: "\u2500".repeat(30) })
96455
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { children: "" }),
96456
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Box_default, { children: [
96457
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { bold: true, color: "yellow", children: "\u2500\u2500 Navigation " }),
96458
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: "gray", dimColor: true, children: "\u2500".repeat(30) })
96334
96459
  ] }),
96335
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { children: "" }),
96336
- GLOBAL_BINDINGS.map((b) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Box_default, { children: [
96337
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Box_default, { width: 10, justifyContent: "flex-end", marginRight: 1, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(KeyBadge, { k: b.key }) }),
96338
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "gray", children: b.label })
96460
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { children: "" }),
96461
+ GLOBAL_BINDINGS.map((b) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Box_default, { children: [
96462
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Box_default, { width: 10, justifyContent: "flex-end", marginRight: 1, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(KeyBadge, { k: b.key }) }),
96463
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: "gray", children: b.label })
96339
96464
  ] }, b.key)),
96340
- actions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
96341
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { children: "" }),
96342
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Box_default, { children: [
96343
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { bold: true, color: "yellow", children: `\u2500\u2500 ${panel.title} Actions ` }),
96344
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "gray", dimColor: true, children: "\u2500".repeat(24) })
96465
+ actions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
96466
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { children: "" }),
96467
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Box_default, { children: [
96468
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { bold: true, color: "yellow", children: `\u2500\u2500 ${panel.title} Actions ` }),
96469
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: "gray", dimColor: true, children: "\u2500".repeat(24) })
96345
96470
  ] }),
96346
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { children: "" }),
96347
- actions.map((a) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Box_default, { children: [
96348
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Box_default, { width: 10, justifyContent: "flex-end", marginRight: 1, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(KeyBadge, { k: a.key }) }),
96349
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: a.confirm ? "red" : "gray", children: a.label }),
96350
- a.confirm && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "red", dimColor: true, children: " \u26A0" })
96471
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { children: "" }),
96472
+ actions.map((a) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Box_default, { children: [
96473
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Box_default, { width: 10, justifyContent: "flex-end", marginRight: 1, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(KeyBadge, { k: a.key }) }),
96474
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: a.confirm ? "red" : "gray", children: a.label }),
96475
+ a.confirm && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: "red", dimColor: true, children: " \u26A0" })
96351
96476
  ] }, a.key))
96352
96477
  ] }),
96353
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { children: "" }),
96354
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "gray", dimColor: true, children: "Press ? or Esc to close" })
96478
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { children: "" }),
96479
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: "gray", dimColor: true, children: "Press ? or Esc to close" })
96355
96480
  ] });
96356
96481
  }
96357
96482
 
96358
96483
  // src/dashboard/ink/FilterOverlay.tsx
96359
96484
  await init_build2();
96360
- var import_jsx_runtime7 = __toESM(require_jsx_runtime(), 1);
96485
+ var import_jsx_runtime8 = __toESM(require_jsx_runtime(), 1);
96361
96486
  function FilterOverlay({ filterString, matchCount, totalCount, panelTitle }) {
96362
96487
  const countInfo = matchCount !== void 0 && totalCount !== void 0 && panelTitle ? ` ${matchCount} of ${totalCount} ${panelTitle.toLowerCase()}` : "";
96363
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Box_default, { position: "absolute", marginTop: 1, marginLeft: 1, children: [
96364
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { backgroundColor: "#2B4C7E", color: "white", bold: true, children: ` \u2315 ${filterString}\u2588 ` }),
96365
- countInfo && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: "gray", dimColor: true, children: countInfo }),
96366
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: "gray", dimColor: true, children: " Enter: apply Esc: clear" })
96488
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Box_default, { position: "absolute", marginTop: 1, marginLeft: 1, children: [
96489
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { backgroundColor: "#2B4C7E", color: "white", bold: true, children: ` \u2315 ${filterString}\u2588 ` }),
96490
+ countInfo && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: "gray", dimColor: true, children: countInfo }),
96491
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: "gray", dimColor: true, children: " Enter: apply Esc: clear" })
96367
96492
  ] });
96368
96493
  }
96369
96494
 
96370
96495
  // src/dashboard/ink/ContextMenuOverlay.tsx
96371
96496
  await init_build2();
96372
- var import_jsx_runtime8 = __toESM(require_jsx_runtime(), 1);
96497
+ var import_jsx_runtime9 = __toESM(require_jsx_runtime(), 1);
96373
96498
  function ContextMenuOverlay({ actions, selectedIndex }) {
96374
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
96499
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
96375
96500
  Box_default,
96376
96501
  {
96377
96502
  position: "absolute",
@@ -96382,13 +96507,13 @@ function ContextMenuOverlay({ actions, selectedIndex }) {
96382
96507
  borderColor: "#2B4C7E",
96383
96508
  paddingX: 1,
96384
96509
  children: [
96385
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { bold: true, color: "#2B4C7E", children: "\u2630 Actions" }),
96510
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { bold: true, color: "#2B4C7E", children: "\u2630 Actions" }),
96386
96511
  actions.map((action, i) => {
96387
96512
  const isSelected = i === selectedIndex;
96388
96513
  const isDanger = !!action.confirm;
96389
96514
  const color = isSelected ? isDanger ? "red" : "#2B4C7E" : isDanger ? "red" : "white";
96390
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Box_default, { children: [
96391
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
96515
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Box_default, { children: [
96516
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
96392
96517
  Text,
96393
96518
  {
96394
96519
  color,
@@ -96397,7 +96522,7 @@ function ContextMenuOverlay({ actions, selectedIndex }) {
96397
96522
  children: ` ${action.key} `
96398
96523
  }
96399
96524
  ),
96400
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
96525
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
96401
96526
  Text,
96402
96527
  {
96403
96528
  color,
@@ -96408,8 +96533,8 @@ function ContextMenuOverlay({ actions, selectedIndex }) {
96408
96533
  )
96409
96534
  ] }, action.key);
96410
96535
  }),
96411
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { children: "" }),
96412
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: "gray", dimColor: true, children: "j/k select Enter run Esc close" })
96536
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { children: "" }),
96537
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: "gray", dimColor: true, children: "j/k select Enter run Esc close" })
96413
96538
  ]
96414
96539
  }
96415
96540
  );
@@ -96417,7 +96542,7 @@ function ContextMenuOverlay({ actions, selectedIndex }) {
96417
96542
 
96418
96543
  // src/dashboard/ink/ConfirmOverlay.tsx
96419
96544
  await init_build2();
96420
- var import_jsx_runtime9 = __toESM(require_jsx_runtime(), 1);
96545
+ var import_jsx_runtime10 = __toESM(require_jsx_runtime(), 1);
96421
96546
  var SEVERITY_CONFIG = {
96422
96547
  low: { borderColor: "yellow", icon: "\u26A0", title: "Confirm", warning: "" },
96423
96548
  high: { borderColor: "red", icon: "\u2717", title: "Destructive Action", warning: " This cannot be undone." },
@@ -96426,7 +96551,7 @@ var SEVERITY_CONFIG = {
96426
96551
  function ConfirmOverlay({ message, severity }) {
96427
96552
  const config = SEVERITY_CONFIG[severity];
96428
96553
  const color = config.borderColor;
96429
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
96554
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
96430
96555
  Box_default,
96431
96556
  {
96432
96557
  position: "absolute",
@@ -96438,19 +96563,19 @@ function ConfirmOverlay({ message, severity }) {
96438
96563
  paddingX: 2,
96439
96564
  paddingY: 1,
96440
96565
  children: [
96441
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Box_default, { children: [
96442
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color, bold: true, children: `${config.icon} ` }),
96443
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { bold: true, color, children: config.title })
96566
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { children: [
96567
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color, bold: true, children: `${config.icon} ` }),
96568
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { bold: true, color, children: config.title })
96444
96569
  ] }),
96445
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { children: "" }),
96446
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { bold: true, children: ` ${message}` }),
96447
- config.warning ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: "gray", dimColor: true, children: config.warning }) : null,
96448
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { children: "" }),
96449
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Box_default, { children: [
96450
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { backgroundColor: severity === "low" ? "yellow" : "green", color: severity === "low" ? "black" : "white", bold: true, children: " y Yes " }),
96451
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { children: " " }),
96452
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { backgroundColor: "red", color: "white", bold: true, children: " n No " }),
96453
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: "gray", dimColor: true, children: " or Esc to cancel" })
96570
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { children: "" }),
96571
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { bold: true, children: ` ${message}` }),
96572
+ config.warning ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: "gray", dimColor: true, children: config.warning }) : null,
96573
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { children: "" }),
96574
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { children: [
96575
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { backgroundColor: severity === "low" ? "yellow" : "green", color: severity === "low" ? "black" : "white", bold: true, children: " y Yes " }),
96576
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { children: " " }),
96577
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { backgroundColor: "red", color: "white", bold: true, children: " n No " }),
96578
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: "gray", dimColor: true, children: " or Esc to cancel" })
96454
96579
  ] })
96455
96580
  ]
96456
96581
  }
@@ -96460,7 +96585,7 @@ function ConfirmOverlay({ message, severity }) {
96460
96585
  // src/dashboard/ink/ToastNotification.tsx
96461
96586
  var import_react33 = __toESM(require_react(), 1);
96462
96587
  await init_build2();
96463
- var import_jsx_runtime10 = __toESM(require_jsx_runtime(), 1);
96588
+ var import_jsx_runtime11 = __toESM(require_jsx_runtime(), 1);
96464
96589
  var SEVERITY_COLORS2 = {
96465
96590
  error: "red",
96466
96591
  warning: "yellow",
@@ -96488,43 +96613,43 @@ function ToastNotification({ toast }) {
96488
96613
  };
96489
96614
  const icon = SEVERITY_ICONS[toast.severity] || "";
96490
96615
  const textColor = toast.severity === "warning" ? "black" : "white";
96491
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { position: "absolute", marginTop: 0, justifyContent: "flex-end", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { backgroundColor: SEVERITY_COLORS2[toast.severity] || "white", color: textColor, bold: true, children: ` ${icon} ${toast.message} ` }) });
96616
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Box_default, { position: "absolute", marginTop: 0, justifyContent: "flex-end", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { backgroundColor: SEVERITY_COLORS2[toast.severity] || "white", color: textColor, bold: true, children: ` ${icon} ${toast.message} ` }) });
96492
96617
  }
96493
96618
 
96494
96619
  // src/dashboard/ink/TooSmallOverlay.tsx
96495
96620
  await init_build2();
96496
96621
  var import_sidekick_docker_shared11 = __toESM(require_dist(), 1);
96497
- var import_jsx_runtime11 = __toESM(require_jsx_runtime(), 1);
96622
+ var import_jsx_runtime12 = __toESM(require_jsx_runtime(), 1);
96498
96623
  function TooSmallOverlay({ columns, rows }) {
96499
96624
  const needWidth = Math.max(0, 60 - columns);
96500
96625
  const needHeight = Math.max(0, 15 - rows);
96501
96626
  const hints = [];
96502
96627
  if (needWidth > 0) hints.push(`${needWidth} col${needWidth > 1 ? "s" : ""} wider`);
96503
96628
  if (needHeight > 0) hints.push(`${needHeight} row${needHeight > 1 ? "s" : ""} taller`);
96504
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { flexDirection: "column", justifyContent: "center", alignItems: "center", height: rows, width: columns, children: [
96505
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { bold: true, color: "magenta", children: `\u26A1 ${import_sidekick_docker_shared11.BRAND_INLINE}` }),
96506
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { children: "" }),
96507
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: "yellow", bold: true, children: "Terminal too small" }),
96508
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: "gray", children: `${columns}\xD7${rows} \u2192 need ${hints.join(" and ")}` }),
96509
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { children: "" }),
96510
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: "gray", dimColor: true, children: "Resize to at least 60\xD715 to continue." })
96629
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Box_default, { flexDirection: "column", justifyContent: "center", alignItems: "center", height: rows, width: columns, children: [
96630
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { bold: true, color: "magenta", children: `\u26A1 ${import_sidekick_docker_shared11.BRAND_INLINE}` }),
96631
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { children: "" }),
96632
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { color: "yellow", bold: true, children: "Terminal too small" }),
96633
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { color: "gray", children: `${columns}\xD7${rows} \u2192 need ${hints.join(" and ")}` }),
96634
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { children: "" }),
96635
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { color: "gray", dimColor: true, children: "Resize to at least 60\xD715 to continue." })
96511
96636
  ] });
96512
96637
  }
96513
96638
 
96514
96639
  // src/dashboard/ink/ExecOverlay.tsx
96515
96640
  await init_build2();
96516
- var import_jsx_runtime12 = __toESM(require_jsx_runtime(), 1);
96641
+ var import_jsx_runtime13 = __toESM(require_jsx_runtime(), 1);
96517
96642
  function ExecOverlay({ containerName, outputLines }) {
96518
96643
  const { rows } = useTerminalSize();
96519
96644
  const viewportHeight = Math.max(1, rows - 3);
96520
96645
  const scrollOffset = Math.max(0, outputLines.length - viewportHeight);
96521
96646
  const visibleLines = outputLines.slice(scrollOffset, scrollOffset + viewportHeight);
96522
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Box_default, { flexDirection: "column", flexGrow: 1, children: [
96523
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Box_default, { children: [
96524
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { bold: true, color: "#2B4C7E", children: ` Exec: ${containerName} ` }),
96525
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { color: "gray", children: " (Ctrl+] to detach)" })
96647
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Box_default, { flexDirection: "column", flexGrow: 1, children: [
96648
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Box_default, { children: [
96649
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { bold: true, color: "#2B4C7E", children: ` Exec: ${containerName} ` }),
96650
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { color: "gray", children: " (Ctrl+] to detach)" })
96526
96651
  ] }),
96527
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Box_default, { flexDirection: "column", flexGrow: 1, children: visibleLines.map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { children: line || " " }, scrollOffset + i)) })
96652
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Box_default, { flexDirection: "column", flexGrow: 1, children: visibleLines.map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { children: line || " " }, scrollOffset + i)) })
96528
96653
  ] });
96529
96654
  }
96530
96655
 
@@ -96598,7 +96723,7 @@ function parseMouseEvent(data) {
96598
96723
  // src/dashboard/ink/mouse/MouseProvider.tsx
96599
96724
  var import_react34 = __toESM(require_react(), 1);
96600
96725
  await init_build2();
96601
- var import_jsx_runtime13 = __toESM(require_jsx_runtime(), 1);
96726
+ var import_jsx_runtime14 = __toESM(require_jsx_runtime(), 1);
96602
96727
  function InputSink() {
96603
96728
  use_input_default(() => {
96604
96729
  });
@@ -96622,20 +96747,20 @@ function MouseProvider({ onMouse, children }) {
96622
96747
  disableMouse();
96623
96748
  };
96624
96749
  }, [onMouse]);
96625
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
96626
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(InputSink, {}),
96750
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
96751
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(InputSink, {}),
96627
96752
  children
96628
96753
  ] });
96629
96754
  }
96630
96755
 
96631
96756
  // src/dashboard/ink/LogFilterOverlay.tsx
96632
96757
  await init_build2();
96633
- var import_jsx_runtime14 = __toESM(require_jsx_runtime(), 1);
96758
+ var import_jsx_runtime15 = __toESM(require_jsx_runtime(), 1);
96634
96759
  function LogFilterOverlay({ filterString, filterMode }) {
96635
96760
  const modeLabel = filterMode === "exact" ? "exact" : "fuzzy";
96636
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(Box_default, { position: "absolute", marginTop: 1, marginLeft: 1, children: [
96637
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { backgroundColor: "#2B4C7E", color: "white", bold: true, children: ` Log filter (${modeLabel}): ${filterString}\u2588 ` }),
96638
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { color: "gray", children: " Tab: toggle mode Enter: apply Esc: clear" })
96761
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(Box_default, { position: "absolute", marginTop: 1, marginLeft: 1, children: [
96762
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { backgroundColor: "#2B4C7E", color: "white", bold: true, children: ` Log filter (${modeLabel}): ${filterString}\u2588 ` }),
96763
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: "gray", children: " Tab: toggle mode Enter: apply Esc: clear" })
96639
96764
  ] });
96640
96765
  }
96641
96766
 
@@ -96643,26 +96768,26 @@ function LogFilterOverlay({ filterString, filterMode }) {
96643
96768
  var import_react35 = __toESM(require_react(), 1);
96644
96769
  await init_build2();
96645
96770
  var import_sidekick_docker_shared12 = __toESM(require_dist(), 1);
96646
- var import_jsx_runtime15 = __toESM(require_jsx_runtime(), 1);
96771
+ var import_jsx_runtime16 = __toESM(require_jsx_runtime(), 1);
96647
96772
  function VersionOverlay({ version: version2 }) {
96648
96773
  const phrase = import_react35.default.useMemo(() => (0, import_sidekick_docker_shared12.getRandomPhrase)(), []);
96649
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(Box_default, { flexDirection: "column", flexGrow: 1, padding: 1, children: [
96650
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { bold: true, color: "magenta", children: `\u26A1 ${import_sidekick_docker_shared12.BRAND_INLINE}` }),
96651
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: "#2B4C7E", bold: true, children: `${import_sidekick_docker_shared12.BRAND_TAGLINE} v${version2}` }),
96652
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { children: "" }),
96653
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: "gray", dimColor: true, children: "\u2500".repeat(40) }),
96654
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { children: "" }),
96655
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: "gray", italic: true, children: ` "${phrase}"` }),
96656
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { children: "" }),
96657
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: "gray", dimColor: true, children: "\u2500".repeat(40) }),
96658
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { children: "" }),
96659
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: "gray", dimColor: true, children: "Press V or Esc to close" })
96774
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(Box_default, { flexDirection: "column", flexGrow: 1, padding: 1, children: [
96775
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Text, { bold: true, color: "magenta", children: `\u26A1 ${import_sidekick_docker_shared12.BRAND_INLINE}` }),
96776
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Text, { color: "#2B4C7E", bold: true, children: `${import_sidekick_docker_shared12.BRAND_TAGLINE} v${version2}` }),
96777
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Text, { children: "" }),
96778
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Text, { color: "gray", dimColor: true, children: "\u2500".repeat(40) }),
96779
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Text, { children: "" }),
96780
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Text, { color: "gray", italic: true, children: ` "${phrase}"` }),
96781
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Text, { children: "" }),
96782
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Text, { color: "gray", dimColor: true, children: "\u2500".repeat(40) }),
96783
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Text, { children: "" }),
96784
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Text, { color: "gray", dimColor: true, children: "Press V or Esc to close" })
96660
96785
  ] });
96661
96786
  }
96662
96787
 
96663
96788
  // src/dashboard/ink/SortOverlay.tsx
96664
96789
  await init_build2();
96665
- var import_jsx_runtime16 = __toESM(require_jsx_runtime(), 1);
96790
+ var import_jsx_runtime17 = __toESM(require_jsx_runtime(), 1);
96666
96791
  var SORT_OPTIONS = [
96667
96792
  { field: "state", label: "State (running first)" },
96668
96793
  { field: "name", label: "Name" },
@@ -96673,7 +96798,7 @@ var SORT_OPTIONS = [
96673
96798
  { field: "pids", label: "PIDs" }
96674
96799
  ];
96675
96800
  function SortOverlay({ selectedIndex, currentField, reversed }) {
96676
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
96801
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
96677
96802
  Box_default,
96678
96803
  {
96679
96804
  position: "absolute",
@@ -96684,12 +96809,12 @@ function SortOverlay({ selectedIndex, currentField, reversed }) {
96684
96809
  borderColor: "#2B4C7E",
96685
96810
  paddingX: 1,
96686
96811
  children: [
96687
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Text, { bold: true, color: "#2B4C7E", children: "\u2195 Sort by" }),
96812
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Text, { bold: true, color: "#2B4C7E", children: "\u2195 Sort by" }),
96688
96813
  SORT_OPTIONS.map((opt, i) => {
96689
96814
  const isSelected = i === selectedIndex;
96690
96815
  const isCurrent = opt.field === currentField;
96691
96816
  const indicator = isCurrent ? reversed ? " \u25B2" : " \u25BC" : "";
96692
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Box_default, { children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
96817
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Box_default, { children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
96693
96818
  Text,
96694
96819
  {
96695
96820
  color: isSelected ? "#2B4C7E" : isCurrent ? "yellow" : "white",
@@ -96699,8 +96824,8 @@ function SortOverlay({ selectedIndex, currentField, reversed }) {
96699
96824
  }
96700
96825
  ) }, opt.field);
96701
96826
  }),
96702
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Text, { children: "" }),
96703
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Text, { color: "gray", dimColor: true, children: "j/k select Enter apply R reverse Esc close" })
96827
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Text, { children: "" }),
96828
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Text, { color: "gray", dimColor: true, children: "j/k select Enter apply R reverse Esc close" })
96704
96829
  ]
96705
96830
  }
96706
96831
  );
@@ -96764,7 +96889,7 @@ var ExecManager = class {
96764
96889
  };
96765
96890
 
96766
96891
  // src/dashboard/ink/Dashboard.tsx
96767
- var import_jsx_runtime17 = __toESM(require_jsx_runtime(), 1);
96892
+ var import_jsx_runtime18 = __toESM(require_jsx_runtime(), 1);
96768
96893
  var SORT_FIELDS2 = ["state", "name", "cpu", "mem", "net", "io", "pids"];
96769
96894
  var SIDE_PANEL_WIDTH = 28;
96770
96895
  var SIDE_PANEL_WIDTH_WIDE = 42;
@@ -96787,7 +96912,7 @@ function reducer(state, action) {
96787
96912
  detailScrollPerTab: {}
96788
96913
  };
96789
96914
  case "SELECT_ITEM":
96790
- return { ...state, selectedItemIndex: action.index, detailTabIndex: 0, detailScrollOffset: 0, detailScrollPerTab: {} };
96915
+ return { ...state, selectedItemIndex: action.index, detailTabIndex: 0, detailScrollOffset: 0, detailScrollPerTab: {}, secondaryDetailScrollOffset: 0 };
96791
96916
  case "SET_DETAIL_TAB": {
96792
96917
  const saved = { ...state.detailScrollPerTab, [state.detailTabIndex]: state.detailScrollOffset };
96793
96918
  return { ...state, detailTabIndex: action.index, detailScrollOffset: saved[action.index] ?? 0, detailScrollPerTab: saved };
@@ -96866,6 +96991,22 @@ function reducer(state, action) {
96866
96991
  const next = (state.sortMenuIndex + action.delta + SORT_FIELDS2.length) % SORT_FIELDS2.length;
96867
96992
  return { ...state, sortMenuIndex: next };
96868
96993
  }
96994
+ case "PIN_COMPARE": {
96995
+ const current = state.compareItemIds[action.panelId] ?? null;
96996
+ const newId = current === action.itemId ? null : action.itemId;
96997
+ return {
96998
+ ...state,
96999
+ compareItemIds: { ...state.compareItemIds, [action.panelId]: newId },
97000
+ secondaryDetailScrollOffset: 0
97001
+ };
97002
+ }
97003
+ case "SCROLL_SECONDARY_DETAIL":
97004
+ return { ...state, secondaryDetailScrollOffset: action.offset };
97005
+ case "SCROLL_SECONDARY_DETAIL_DELTA": {
97006
+ const maxOffset = Math.max(0, action.totalLines - action.viewportHeight);
97007
+ const next = Math.max(0, Math.min(state.secondaryDetailScrollOffset + action.delta, maxOffset));
97008
+ return { ...state, secondaryDetailScrollOffset: next };
97009
+ }
96869
97010
  default:
96870
97011
  return state;
96871
97012
  }
@@ -96893,7 +97034,9 @@ var initialState = {
96893
97034
  showAllContainers: true,
96894
97035
  sortField: "state",
96895
97036
  sortReversed: false,
96896
- sortMenuIndex: 0
97037
+ sortMenuIndex: 0,
97038
+ compareItemIds: {},
97039
+ secondaryDetailScrollOffset: 0
96897
97040
  };
96898
97041
  function Dashboard({ panels, metrics, onViewStateChange, execTriggerRef, onExecFallback }) {
96899
97042
  const [state, dispatch] = (0, import_react36.useReducer)(reducer, initialState);
@@ -97059,14 +97202,21 @@ function Dashboard({ panels, metrics, onViewStateChange, execTriggerRef, onExecF
97059
97202
  const selectedItem = currentItems[clampedSelection];
97060
97203
  const detailTabs = panel.detailTabs;
97061
97204
  const tabIdx = Math.min(state.detailTabIndex, detailTabs.length - 1);
97205
+ const compareItemId = state.compareItemIds[panel.id] ?? null;
97206
+ (0, import_react36.useEffect)(() => {
97207
+ if (compareItemId && selectedItem?.id === compareItemId) {
97208
+ dispatch({ type: "PIN_COMPARE", panelId: panel.id, itemId: compareItemId });
97209
+ }
97210
+ }, [selectedItem?.id, compareItemId, panel.id]);
97062
97211
  (0, import_react36.useEffect)(() => {
97063
97212
  onViewStateChange?.({
97064
97213
  panelId: panel.id,
97065
97214
  itemId: selectedItem?.id ?? null,
97066
97215
  detailTabIndex: tabIdx,
97067
- sortField: state.sortField
97216
+ sortField: state.sortField,
97217
+ compareItemId
97068
97218
  });
97069
- }, [panel.id, selectedItem?.id, tabIdx, state.sortField]);
97219
+ }, [panel.id, selectedItem?.id, tabIdx, state.sortField, compareItemId]);
97070
97220
  const enrichedMetrics = {
97071
97221
  ...metrics,
97072
97222
  logFilterString: state.logFilterString,
@@ -97087,6 +97237,31 @@ function Dashboard({ panels, metrics, onViewStateChange, execTriggerRef, onExecF
97087
97237
  dispatch({ type: "SCROLL_DETAIL", offset: detailLines.length - detailViewportHeight });
97088
97238
  }
97089
97239
  }, [shouldAutoScroll, detailLines.length, detailViewportHeight]);
97240
+ const isCompareActive = compareItemId != null && shouldAutoScroll;
97241
+ const secondaryDetailLines = import_react36.default.useMemo(() => {
97242
+ if (!isCompareActive) return [];
97243
+ if (panel.id === "containers") {
97244
+ const logs = enrichedMetrics.secondaryContainerLogs;
97245
+ if (logs.length === 0) return ["Waiting for logs..."];
97246
+ return renderLogLines(logs, state.logFilterString, state.logFilterMode, enrichedMetrics.secondaryLogSeverityCounts);
97247
+ }
97248
+ if (panel.id === "services") {
97249
+ const logs = enrichedMetrics.secondaryComposeLogs;
97250
+ if (logs.length === 0) return ["Waiting for logs..."];
97251
+ return renderLogLines(logs, state.logFilterString, state.logFilterMode);
97252
+ }
97253
+ return [];
97254
+ }, [isCompareActive, panel.id, enrichedMetrics.secondaryContainerLogs, enrichedMetrics.secondaryComposeLogs, enrichedMetrics.secondaryLogSeverityCounts, state.logFilterString, state.logFilterMode]);
97255
+ (0, import_react36.useEffect)(() => {
97256
+ if (isCompareActive && secondaryDetailLines.length > detailViewportHeight) {
97257
+ dispatch({ type: "SCROLL_SECONDARY_DETAIL", offset: secondaryDetailLines.length - detailViewportHeight });
97258
+ }
97259
+ }, [isCompareActive, secondaryDetailLines.length, detailViewportHeight]);
97260
+ const compareItemLabel = import_react36.default.useMemo(() => {
97261
+ if (!compareItemId) return "";
97262
+ const item = currentItems.find((it) => it.id === compareItemId);
97263
+ return item ? item.label.replace(/^[^\w]*/, "").trim() : compareItemId.slice(0, 12);
97264
+ }, [compareItemId, currentItems]);
97090
97265
  const panelActions = panel.getActions();
97091
97266
  const applicableActions = selectedItem ? panelActions.filter((a) => !a.condition || a.condition(selectedItem)) : [];
97092
97267
  const contextActions = state.overlay === "context-menu" ? applicableActions : [];
@@ -97134,27 +97309,35 @@ function Dashboard({ panels, metrics, onViewStateChange, execTriggerRef, onExecF
97134
97309
  sideScroll,
97135
97310
  addToast,
97136
97311
  removeToast,
97137
- rotatePhrase
97312
+ rotatePhrase,
97313
+ secondaryDetailLineCount: secondaryDetailLines.length
97138
97314
  });
97139
97315
  if (tooSmall) {
97140
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(TooSmallOverlay, { columns, rows });
97316
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(TooSmallOverlay, { columns, rows });
97141
97317
  }
97142
97318
  const showNormalLayout = state.overlay !== "help" && state.overlay !== "exec" && state.overlay !== "version";
97143
97319
  const panelActionHints = applicableActions.length > 0 ? [{ key: "x", label: "Actions", destructive: false }] : [];
97144
97320
  const contextHint = (() => {
97145
97321
  if (panel.id === "containers") {
97146
97322
  const parts = [];
97147
- if (tabIdx === 0) parts.push("f:Filter", "c:Copy");
97323
+ if (tabIdx === 0) {
97324
+ parts.push("f:Filter", "c:Copy", "m:Compare");
97325
+ }
97148
97326
  parts.push(state.showAllContainers ? "a:All" : "a:Running");
97149
97327
  parts.push(`o:\u2195${state.sortField}`);
97150
97328
  return parts.join(" ");
97151
97329
  }
97330
+ if (panel.id === "services") {
97331
+ const parts = [];
97332
+ if (tabIdx === 1) parts.push("m:Compare");
97333
+ return parts.join(" ");
97334
+ }
97152
97335
  return "";
97153
97336
  })();
97154
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(MouseProvider, { onMouse: handleMouse, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(Box_default, { flexDirection: "column", height: rows, width: columns, children: [
97155
- showNormalLayout && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(TabBar, { panels, activeIndex: state.activePanelIndex, layoutMode: state.layoutMode, phrase, panelCounts }),
97156
- showNormalLayout && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(Box_default, { flexGrow: 1, flexDirection: "row", children: [
97157
- sideWidth > 0 && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
97337
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(MouseProvider, { onMouse: handleMouse, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(Box_default, { flexDirection: "column", height: rows, width: columns, children: [
97338
+ showNormalLayout && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(TabBar, { panels, activeIndex: state.activePanelIndex, layoutMode: state.layoutMode, phrase, panelCounts }),
97339
+ showNormalLayout && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(Box_default, { flexGrow: 1, flexDirection: "row", children: [
97340
+ sideWidth > 0 && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
97158
97341
  SideList,
97159
97342
  {
97160
97343
  items: currentItems,
@@ -97167,12 +97350,26 @@ function Dashboard({ panels, metrics, onViewStateChange, execTriggerRef, onExecF
97167
97350
  filterString: state.filterString || void 0,
97168
97351
  panelId: panel.id,
97169
97352
  totalCount: totalItemCount,
97170
- runningCount: panel.id === "containers" ? runningCount : void 0
97353
+ runningCount: panel.id === "containers" ? runningCount : void 0,
97354
+ compareItemId: compareItemId || void 0
97171
97355
  }
97172
97356
  ),
97173
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(Box_default, { flexDirection: "column", flexGrow: 1, children: [
97174
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DetailTabBar, { tabs: detailTabs, activeIndex: state.detailTabIndex }),
97175
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
97357
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(Box_default, { flexDirection: "column", flexGrow: 1, children: [
97358
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(DetailTabBar, { tabs: detailTabs, activeIndex: state.detailTabIndex }),
97359
+ isCompareActive ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
97360
+ CompareDetailPane,
97361
+ {
97362
+ primaryLines: detailLines,
97363
+ secondaryLines: secondaryDetailLines,
97364
+ primaryScrollOffset: state.detailScrollOffset,
97365
+ secondaryScrollOffset: state.secondaryDetailScrollOffset,
97366
+ viewportHeight: detailViewportHeight,
97367
+ totalWidth: columns - sideWidth,
97368
+ focused: state.focusTarget === "detail",
97369
+ primaryLabel: selectedItem?.label.replace(/^[^\w]*/, "").trim() ?? "",
97370
+ secondaryLabel: compareItemLabel
97371
+ }
97372
+ ) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
97176
97373
  DetailPane,
97177
97374
  {
97178
97375
  lines: detailLines,
@@ -97183,16 +97380,16 @@ function Dashboard({ panels, metrics, onViewStateChange, execTriggerRef, onExecF
97183
97380
  )
97184
97381
  ] })
97185
97382
  ] }),
97186
- state.overlay === "help" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(HelpOverlay, { panels, activePanelIndex: state.activePanelIndex, version: "0.2.2" }),
97187
- state.overlay === "version" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(VersionOverlay, { version: "0.2.2" }),
97188
- state.overlay === "exec" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
97383
+ state.overlay === "help" && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(HelpOverlay, { panels, activePanelIndex: state.activePanelIndex, version: "0.2.3" }),
97384
+ state.overlay === "version" && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(VersionOverlay, { version: "0.2.3" }),
97385
+ state.overlay === "exec" && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
97189
97386
  ExecOverlay,
97190
97387
  {
97191
97388
  containerName: state.execContainerName,
97192
97389
  outputLines: state.execOutputLines
97193
97390
  }
97194
97391
  ),
97195
- state.overlay !== "exec" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
97392
+ state.overlay !== "exec" && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
97196
97393
  StatusBar,
97197
97394
  {
97198
97395
  daemonConnected: metrics.daemonConnected,
@@ -97201,15 +97398,15 @@ function Dashboard({ panels, metrics, onViewStateChange, execTriggerRef, onExecF
97201
97398
  filterString: state.filterString,
97202
97399
  containerCount: metrics.containers.length,
97203
97400
  runningCount,
97204
- version: "0.2.2",
97401
+ version: "0.2.3",
97205
97402
  matchCount: state.filterString ? currentItems.length : void 0,
97206
97403
  totalCount: state.filterString ? totalItemCount : void 0,
97207
97404
  lastRefresh: metrics.lastRefresh,
97208
97405
  contextHint
97209
97406
  }
97210
97407
  ),
97211
- state.overlay === "context-menu" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(ContextMenuOverlay, { actions: contextActions, selectedIndex: state.contextMenuIndex }),
97212
- state.overlay === "filter" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
97408
+ state.overlay === "context-menu" && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ContextMenuOverlay, { actions: contextActions, selectedIndex: state.contextMenuIndex }),
97409
+ state.overlay === "filter" && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
97213
97410
  FilterOverlay,
97214
97411
  {
97215
97412
  filterString: state.filterString,
@@ -97218,15 +97415,15 @@ function Dashboard({ panels, metrics, onViewStateChange, execTriggerRef, onExecF
97218
97415
  panelTitle: panel.title
97219
97416
  }
97220
97417
  ),
97221
- state.overlay === "log-filter" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
97418
+ state.overlay === "log-filter" && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
97222
97419
  LogFilterOverlay,
97223
97420
  {
97224
97421
  filterString: state.logFilterString,
97225
97422
  filterMode: state.logFilterMode
97226
97423
  }
97227
97424
  ),
97228
- state.overlay === "sort" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(SortOverlay, { selectedIndex: state.sortMenuIndex, currentField: state.sortField, reversed: state.sortReversed }),
97229
- state.overlay === "confirm" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
97425
+ state.overlay === "sort" && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(SortOverlay, { selectedIndex: state.sortMenuIndex, currentField: state.sortField, reversed: state.sortReversed }),
97426
+ state.overlay === "confirm" && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
97230
97427
  ConfirmOverlay,
97231
97428
  {
97232
97429
  message: state.confirmMessage,
@@ -97238,7 +97435,7 @@ function Dashboard({ panels, metrics, onViewStateChange, execTriggerRef, onExecF
97238
97435
  onCancel: () => dispatch({ type: "SET_CONFIRM", action: null, message: "" })
97239
97436
  }
97240
97437
  ),
97241
- state.toasts.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(ToastNotification, { toast: state.toasts[state.toasts.length - 1] })
97438
+ state.toasts.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ToastNotification, { toast: state.toasts[state.toasts.length - 1] })
97242
97439
  ] }) });
97243
97440
  }
97244
97441
 
@@ -97264,6 +97461,8 @@ async function dashboardAction(_opts, cmd) {
97264
97461
  };
97265
97462
  let logFlushTimer = null;
97266
97463
  let composeLogFlushTimer = null;
97464
+ let secondaryLogFlushTimer = null;
97465
+ let secondaryComposeLogFlushTimer = null;
97267
97466
  const logManager = new LogStreamManager(client, () => {
97268
97467
  if (logFlushTimer) return;
97269
97468
  logFlushTimer = setTimeout(() => {
@@ -97283,6 +97482,25 @@ async function dashboardAction(_opts, cmd) {
97283
97482
  scheduleRender();
97284
97483
  };
97285
97484
  let logSeverityCounts = logManager.getSeverityCounts();
97485
+ const secondaryLogManager = new LogStreamManager(client, () => {
97486
+ if (secondaryLogFlushTimer) return;
97487
+ secondaryLogFlushTimer = setTimeout(() => {
97488
+ secondaryLogFlushTimer = null;
97489
+ state.setSecondaryLogs(secondaryLogManager.getLogs());
97490
+ secondaryLogSeverityCounts = secondaryLogManager.getSeverityCounts();
97491
+ scheduleRender();
97492
+ }, 100);
97493
+ });
97494
+ const flushSecondaryLogsNow = () => {
97495
+ if (secondaryLogFlushTimer) {
97496
+ clearTimeout(secondaryLogFlushTimer);
97497
+ secondaryLogFlushTimer = null;
97498
+ }
97499
+ state.setSecondaryLogs(secondaryLogManager.getLogs());
97500
+ secondaryLogSeverityCounts = secondaryLogManager.getSeverityCounts();
97501
+ scheduleRender();
97502
+ };
97503
+ let secondaryLogSeverityCounts = secondaryLogManager.getSeverityCounts();
97286
97504
  const statsManager = new StatsStreamManager(client, state.getStatsCollector(), () => {
97287
97505
  scheduleRender();
97288
97506
  });
@@ -97308,15 +97526,35 @@ async function dashboardAction(_opts, cmd) {
97308
97526
  }
97309
97527
  scheduleRender();
97310
97528
  };
97529
+ const secondaryComposeLogManager = new ComposeLogStreamManager(composeClient, () => {
97530
+ if (secondaryComposeLogFlushTimer) return;
97531
+ secondaryComposeLogFlushTimer = setTimeout(() => {
97532
+ secondaryComposeLogFlushTimer = null;
97533
+ state.setSecondaryComposeLogs(secondaryComposeLogManager.getLogs());
97534
+ scheduleRender();
97535
+ }, 100);
97536
+ });
97537
+ const flushSecondaryComposeLogsNow = () => {
97538
+ if (secondaryComposeLogFlushTimer) {
97539
+ clearTimeout(secondaryComposeLogFlushTimer);
97540
+ secondaryComposeLogFlushTimer = null;
97541
+ }
97542
+ state.setSecondaryComposeLogs(secondaryComposeLogManager.getLogs());
97543
+ scheduleRender();
97544
+ };
97311
97545
  const onViewStateChange = (viewState) => {
97312
- const { panelId, itemId, detailTabIndex, sortField } = viewState;
97546
+ const { panelId, itemId, detailTabIndex, sortField, compareItemId } = viewState;
97313
97547
  const wantsLiveStats = sortField === "cpu" || sortField === "mem" || sortField === "net" || sortField === "io" || sortField === "pids";
97314
97548
  if (panelId === "containers") {
97315
97549
  void logManager.select(detailTabIndex === 0 ? itemId : null);
97316
97550
  void statsManager.select(itemId && (detailTabIndex === 1 || wantsLiveStats) ? itemId : null);
97317
97551
  void composeLogManager.selectCompose(null, null);
97552
+ void secondaryLogManager.select(detailTabIndex === 0 && compareItemId ? compareItemId : null);
97553
+ void secondaryComposeLogManager.selectCompose(null, null);
97318
97554
  flushLogsNow();
97319
97555
  flushComposeLogsNow();
97556
+ flushSecondaryLogsNow();
97557
+ flushSecondaryComposeLogsNow();
97320
97558
  if (itemId && !state.getInspectedEnv(itemId)) {
97321
97559
  client.getContainerEnv(itemId).then((env3) => {
97322
97560
  state.setInspectedEnv(itemId, env3);
@@ -97332,6 +97570,7 @@ async function dashboardAction(_opts, cmd) {
97332
97570
  } else if (panelId === "services" && itemId) {
97333
97571
  void logManager.select(null);
97334
97572
  void statsManager.select(null);
97573
+ void secondaryLogManager.select(null);
97335
97574
  if (detailTabIndex === 1) {
97336
97575
  const parts = itemId.split(":");
97337
97576
  if (parts[0] === "project") {
@@ -97339,17 +97578,34 @@ async function dashboardAction(_opts, cmd) {
97339
97578
  } else if (parts[0] === "service") {
97340
97579
  void composeLogManager.selectCompose(parts[1], parts.slice(2).join(":"));
97341
97580
  }
97581
+ if (compareItemId) {
97582
+ const cParts = compareItemId.split(":");
97583
+ if (cParts[0] === "project") {
97584
+ void secondaryComposeLogManager.selectCompose(cParts.slice(1).join(":"), null);
97585
+ } else if (cParts[0] === "service") {
97586
+ void secondaryComposeLogManager.selectCompose(cParts[1], cParts.slice(2).join(":"));
97587
+ }
97588
+ } else {
97589
+ void secondaryComposeLogManager.selectCompose(null, null);
97590
+ }
97342
97591
  } else {
97343
97592
  void composeLogManager.selectCompose(null, null);
97593
+ void secondaryComposeLogManager.selectCompose(null, null);
97344
97594
  }
97345
97595
  flushLogsNow();
97346
97596
  flushComposeLogsNow();
97597
+ flushSecondaryLogsNow();
97598
+ flushSecondaryComposeLogsNow();
97347
97599
  } else if (panelId === "images") {
97348
97600
  void logManager.select(null);
97349
97601
  void statsManager.select(null);
97350
97602
  void composeLogManager.selectCompose(null, null);
97603
+ void secondaryLogManager.select(null);
97604
+ void secondaryComposeLogManager.selectCompose(null, null);
97351
97605
  flushLogsNow();
97352
97606
  flushComposeLogsNow();
97607
+ flushSecondaryLogsNow();
97608
+ flushSecondaryComposeLogsNow();
97353
97609
  if (itemId && !state.getImageLayers(itemId)) {
97354
97610
  client.getImageHistory(itemId).then((layers) => {
97355
97611
  state.setImageLayers(itemId, layers);
@@ -97360,8 +97616,12 @@ async function dashboardAction(_opts, cmd) {
97360
97616
  void logManager.select(null);
97361
97617
  void statsManager.select(null);
97362
97618
  void composeLogManager.selectCompose(null, null);
97619
+ void secondaryLogManager.select(null);
97620
+ void secondaryComposeLogManager.selectCompose(null, null);
97363
97621
  flushLogsNow();
97364
97622
  flushComposeLogsNow();
97623
+ flushSecondaryLogsNow();
97624
+ flushSecondaryComposeLogsNow();
97365
97625
  }
97366
97626
  };
97367
97627
  const panels = [
@@ -97436,6 +97696,8 @@ async function dashboardAction(_opts, cmd) {
97436
97696
  m.logSeverityCounts = logSeverityCounts;
97437
97697
  m.logSeverityTimeSeries = logManager.getSeverityTimeSeries();
97438
97698
  m.logTemplates = logManager.getTemplates();
97699
+ m.secondaryLogSeverityCounts = secondaryLogSeverityCounts;
97700
+ m.secondaryLogSeverityTimeSeries = secondaryLogManager.getSeverityTimeSeries();
97439
97701
  return m;
97440
97702
  }
97441
97703
  function scheduleRender() {
@@ -97461,6 +97723,10 @@ async function dashboardAction(_opts, cmd) {
97461
97723
  logManager.dispose();
97462
97724
  } catch {
97463
97725
  }
97726
+ try {
97727
+ secondaryLogManager.dispose();
97728
+ } catch {
97729
+ }
97464
97730
  try {
97465
97731
  statsManager.dispose();
97466
97732
  } catch {
@@ -97469,14 +97735,26 @@ async function dashboardAction(_opts, cmd) {
97469
97735
  composeLogManager.dispose();
97470
97736
  } catch {
97471
97737
  }
97738
+ try {
97739
+ secondaryComposeLogManager.dispose();
97740
+ } catch {
97741
+ }
97472
97742
  try {
97473
97743
  if (logFlushTimer) clearTimeout(logFlushTimer);
97474
97744
  } catch {
97475
97745
  }
97746
+ try {
97747
+ if (secondaryLogFlushTimer) clearTimeout(secondaryLogFlushTimer);
97748
+ } catch {
97749
+ }
97476
97750
  try {
97477
97751
  if (composeLogFlushTimer) clearTimeout(composeLogFlushTimer);
97478
97752
  } catch {
97479
97753
  }
97754
+ try {
97755
+ if (secondaryComposeLogFlushTimer) clearTimeout(secondaryComposeLogFlushTimer);
97756
+ } catch {
97757
+ }
97480
97758
  try {
97481
97759
  clearInterval(refreshInterval);
97482
97760
  } catch {
@@ -97561,7 +97839,7 @@ async function logsAction(container, opts) {
97561
97839
 
97562
97840
  // src/cli.ts
97563
97841
  var program2 = new Command();
97564
- program2.name("sidekick-docker").description("Docker management TUI dashboard").version("0.2.2").option("--socket <path>", "Docker socket path").action(async (_opts, cmd) => {
97842
+ program2.name("sidekick-docker").description("Docker management TUI dashboard").version("0.2.3").option("--socket <path>", "Docker socket path").action(async (_opts, cmd) => {
97565
97843
  await dashboardAction(_opts, cmd);
97566
97844
  });
97567
97845
  program2.command("ps").description("List containers").option("-a, --all", "Show all containers (default: running only)", false).action(async (opts) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sidekick-docker",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "Docker management TUI dashboard",
5
5
  "author": "Cesar Andres Lopez <cesarandreslopez@gmail.com>",
6
6
  "repository": {