sidekick-docker 0.2.0 → 0.2.2

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
@@ -97,6 +97,7 @@ The dashboard has 5 panels, each mapped to a number key:
97
97
  | `r` | Restart |
98
98
  | `R` | Remove (with confirmation) |
99
99
  | `e` | Exec into container |
100
+ | `c` | Copy logs to clipboard |
100
101
 
101
102
  ### Compose Actions (via context menu)
102
103
 
@@ -106,6 +107,7 @@ The dashboard has 5 panels, each mapped to a number key:
106
107
  | `d` | Down (stop project) |
107
108
  | `r` | Restart |
108
109
  | `S` | Stop |
110
+ | `c` | Copy logs to clipboard |
109
111
 
110
112
  ## Features
111
113
 
@@ -56086,7 +56086,7 @@ var require_ComposeClient = __commonJS({
56086
56086
  Object.defineProperty(exports2, "__esModule", { value: true });
56087
56087
  exports2.ComposeClient = void 0;
56088
56088
  var child_process_1 = __require("child_process");
56089
- var ComposeClient2 = class {
56089
+ var ComposeClient3 = class {
56090
56090
  async exec(args, cwd2) {
56091
56091
  return new Promise((resolve, reject) => {
56092
56092
  const proc = (0, child_process_1.spawn)("docker", ["compose", ...args], {
@@ -56224,7 +56224,7 @@ var require_ComposeClient = __commonJS({
56224
56224
  }
56225
56225
  }
56226
56226
  };
56227
- exports2.ComposeClient = ComposeClient2;
56227
+ exports2.ComposeClient = ComposeClient3;
56228
56228
  }
56229
56229
  });
56230
56230
 
@@ -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 = filterLine2;
56514
+ exports2.filterLine = filterLine3;
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 filterLine2(line, query, mode) {
56549
+ function filterLine3(line, query, mode) {
56550
56550
  return mode === "exact" ? exactMatch(line, query) : fuzzyMatch(line, query);
56551
56551
  }
56552
56552
  }
@@ -94249,7 +94249,7 @@ var {
94249
94249
 
94250
94250
  // src/commands/dashboard.ts
94251
94251
  var import_react37 = __toESM(require_react(), 1);
94252
- var import_sidekick_docker_shared13 = __toESM(require_dist(), 1);
94252
+ var import_sidekick_docker_shared14 = __toESM(require_dist(), 1);
94253
94253
  import { spawnSync } from "child_process";
94254
94254
 
94255
94255
  // src/utils/clipboard.ts
@@ -94732,9 +94732,7 @@ var ContainersPanel = class {
94732
94732
  if (c.state !== "running") return "Container is not running.";
94733
94733
  const latest = metrics.statsCollector.getLatest(c.id);
94734
94734
  if (!latest) {
94735
- const frames = "\u280B\u2819\u2839\u2838\u283C\u2834\u2826\u2827";
94736
- const idx = Math.floor(Date.now() / 200) % frames.length;
94737
- return `${frames[idx]} Loading stats...`;
94735
+ return "Loading stats...";
94738
94736
  }
94739
94737
  const cpuSeries = metrics.statsCollector.getCpuSeries(c.id);
94740
94738
  const memSeries = metrics.statsCollector.getMemorySeries(c.id);
@@ -94967,6 +94965,7 @@ var ContainersPanel = class {
94967
94965
  };
94968
94966
 
94969
94967
  // src/dashboard/panels/ServicesPanel.ts
94968
+ var import_sidekick_docker_shared5 = __toESM(require_dist(), 1);
94970
94969
  function getProjectName(d) {
94971
94970
  return d.type === "project" ? d.project.name : d.service.projectName;
94972
94971
  }
@@ -94978,12 +94977,17 @@ var ServicesPanel = class {
94978
94977
  onAction;
94979
94978
  onError;
94980
94979
  cwd;
94980
+ onCopyLogs;
94981
+ lastMetrics = null;
94981
94982
  constructor(composeClient, onAction, cwd2, onError) {
94982
94983
  this.composeClient = composeClient;
94983
94984
  this.onAction = onAction;
94984
94985
  this.onError = onError ?? defaultOnError;
94985
94986
  this.cwd = cwd2;
94986
94987
  }
94988
+ setOnCopyLogs(handler) {
94989
+ this.onCopyLogs = handler;
94990
+ }
94987
94991
  detailTabs = [
94988
94992
  {
94989
94993
  label: "Info",
@@ -95023,6 +95027,7 @@ var ServicesPanel = class {
95023
95027
  }
95024
95028
  ];
95025
95029
  getItems(metrics) {
95030
+ this.lastMetrics = metrics;
95026
95031
  const items = [];
95027
95032
  let sortKey = 0;
95028
95033
  for (const project of metrics.composeProjects) {
@@ -95117,6 +95122,23 @@ var ServicesPanel = class {
95117
95122
  });
95118
95123
  },
95119
95124
  condition: (item) => item.data !== null
95125
+ },
95126
+ {
95127
+ key: "c",
95128
+ label: "Copy Logs",
95129
+ handler: () => {
95130
+ if (!this.lastMetrics || !this.onCopyLogs) return;
95131
+ const logs = this.lastMetrics.selectedComposeLogs;
95132
+ const query = this.lastMetrics.logFilterString;
95133
+ const mode = this.lastMetrics.logFilterMode;
95134
+ let lines;
95135
+ if (query) {
95136
+ lines = logs.filter((l) => (0, import_sidekick_docker_shared5.filterLine)(l.message, query, mode).matched).map((l) => l.message);
95137
+ } else {
95138
+ lines = logs.map((l) => l.message);
95139
+ }
95140
+ this.onCopyLogs(lines.join("\n"));
95141
+ }
95120
95142
  }
95121
95143
  ];
95122
95144
  }
@@ -95402,15 +95424,15 @@ var NetworksPanel = class {
95402
95424
  };
95403
95425
 
95404
95426
  // src/dashboard/LogStreamManager.ts
95405
- var import_sidekick_docker_shared6 = __toESM(require_dist(), 1);
95427
+ var import_sidekick_docker_shared7 = __toESM(require_dist(), 1);
95406
95428
 
95407
95429
  // src/dashboard/BaseStreamManager.ts
95408
- var import_sidekick_docker_shared5 = __toESM(require_dist(), 1);
95430
+ var import_sidekick_docker_shared6 = __toESM(require_dist(), 1);
95409
95431
  var BaseStreamManager = class {
95410
95432
  currentId;
95411
95433
  aborted = false;
95412
95434
  streamPromise = null;
95413
- reconnect = new import_sidekick_docker_shared5.ReconnectScheduler();
95435
+ reconnect = new import_sidekick_docker_shared6.ReconnectScheduler();
95414
95436
  onChange;
95415
95437
  constructor(onChange) {
95416
95438
  this.onChange = onChange;
@@ -95437,7 +95459,7 @@ var BaseStreamManager = class {
95437
95459
  }
95438
95460
  this.reconnect.reset();
95439
95461
  } catch (err) {
95440
- console.debug(`${this.streamLabel} stream error:`, (0, import_sidekick_docker_shared5.errorMessage)(err));
95462
+ console.debug(`${this.streamLabel} stream error:`, (0, import_sidekick_docker_shared6.errorMessage)(err));
95441
95463
  }
95442
95464
  if (!this.aborted && this.isSameId(id, this.currentId)) {
95443
95465
  const scheduled = this.reconnect.schedule(() => {
@@ -95474,9 +95496,9 @@ var BaseStreamManager = class {
95474
95496
  var LogStreamManager = class extends BaseStreamManager {
95475
95497
  client;
95476
95498
  logs = [];
95477
- analytics = new import_sidekick_docker_shared6.LogAnalytics();
95478
- timeSeries = new import_sidekick_docker_shared6.LogSeverityTimeSeries();
95479
- templateEngine = new import_sidekick_docker_shared6.LogTemplateEngine();
95499
+ analytics = new import_sidekick_docker_shared7.LogAnalytics();
95500
+ timeSeries = new import_sidekick_docker_shared7.LogSeverityTimeSeries();
95501
+ templateEngine = new import_sidekick_docker_shared7.LogTemplateEngine();
95480
95502
  streamLabel = "log";
95481
95503
  constructor(client, onChange) {
95482
95504
  super(onChange);
@@ -95502,7 +95524,7 @@ var LogStreamManager = class extends BaseStreamManager {
95502
95524
  const severity = this.analytics.push(entry.message);
95503
95525
  this.timeSeries.push(severity);
95504
95526
  this.templateEngine.push(entry.message);
95505
- if (this.logs.length > import_sidekick_docker_shared6.MAX_LOG_LINES) {
95527
+ if (this.logs.length > import_sidekick_docker_shared7.MAX_LOG_LINES) {
95506
95528
  this.logs.shift();
95507
95529
  }
95508
95530
  }
@@ -95533,7 +95555,6 @@ var LogStreamManager = class extends BaseStreamManager {
95533
95555
  var StatsStreamManager = class extends BaseStreamManager {
95534
95556
  client;
95535
95557
  collector;
95536
- loadingInterval = null;
95537
95558
  streamLabel = "stats";
95538
95559
  constructor(client, collector, onChange) {
95539
95560
  super(onChange);
@@ -95557,21 +95578,11 @@ var StatsStreamManager = class extends BaseStreamManager {
95557
95578
  }
95558
95579
  processItem(id, stats) {
95559
95580
  this.collector.push(id, stats);
95560
- this.clearLoadingInterval();
95561
95581
  }
95562
95582
  onClear() {
95563
95583
  }
95564
95584
  onBeforeStream() {
95565
- this.loadingInterval = setInterval(() => this.onChange(), 200);
95566
- }
95567
- onStop() {
95568
- this.clearLoadingInterval();
95569
- }
95570
- clearLoadingInterval() {
95571
- if (this.loadingInterval) {
95572
- clearInterval(this.loadingInterval);
95573
- this.loadingInterval = null;
95574
- }
95585
+ this.onChange();
95575
95586
  }
95576
95587
  getCollector() {
95577
95588
  return this.collector;
@@ -95582,7 +95593,7 @@ var StatsStreamManager = class extends BaseStreamManager {
95582
95593
  };
95583
95594
 
95584
95595
  // src/dashboard/ComposeLogStreamManager.ts
95585
- var import_sidekick_docker_shared7 = __toESM(require_dist(), 1);
95596
+ var import_sidekick_docker_shared8 = __toESM(require_dist(), 1);
95586
95597
  var ComposeLogStreamManager = class extends BaseStreamManager {
95587
95598
  composeClient;
95588
95599
  logs = [];
@@ -95608,7 +95619,7 @@ var ComposeLogStreamManager = class extends BaseStreamManager {
95608
95619
  }
95609
95620
  processItem(_id, entry) {
95610
95621
  this.logs.push(entry);
95611
- if (this.logs.length > import_sidekick_docker_shared7.MAX_LOG_LINES) {
95622
+ if (this.logs.length > import_sidekick_docker_shared8.MAX_LOG_LINES) {
95612
95623
  this.logs.shift();
95613
95624
  }
95614
95625
  }
@@ -96229,8 +96240,7 @@ function DetailTabBar({ tabs, activeIndex }) {
96229
96240
  // src/dashboard/ink/DetailPane.tsx
96230
96241
  await init_build2();
96231
96242
  var import_jsx_runtime4 = __toESM(require_jsx_runtime(), 1);
96232
- function DetailPane({ content, scrollOffset, viewportHeight, focused }) {
96233
- const lines = content.split("\n");
96243
+ function DetailPane({ lines, scrollOffset, viewportHeight, focused }) {
96234
96244
  const visibleLines = lines.slice(scrollOffset, scrollOffset + viewportHeight);
96235
96245
  const hasScrollUp = scrollOffset > 0;
96236
96246
  const hasScrollDown = scrollOffset + viewportHeight < lines.length;
@@ -96246,7 +96256,7 @@ function DetailPane({ content, scrollOffset, viewportHeight, focused }) {
96246
96256
  // src/dashboard/ink/StatusBar.tsx
96247
96257
  var import_react32 = __toESM(require_react(), 1);
96248
96258
  await init_build2();
96249
- var import_sidekick_docker_shared8 = __toESM(require_dist(), 1);
96259
+ var import_sidekick_docker_shared9 = __toESM(require_dist(), 1);
96250
96260
  var import_jsx_runtime5 = __toESM(require_jsx_runtime(), 1);
96251
96261
  function formatAgo(date) {
96252
96262
  const secs = Math.floor((Date.now() - date.getTime()) / 1e3);
@@ -96259,13 +96269,13 @@ var SEP2 = "\u2502";
96259
96269
  function StatusBar({ daemonConnected, focusTarget, panelActionHints, filterString, containerCount, runningCount, version: version2, matchCount, totalCount, lastRefresh, contextHint }) {
96260
96270
  const [, setTick] = (0, import_react32.useState)(0);
96261
96271
  (0, import_react32.useEffect)(() => {
96262
- const timer = setInterval(() => setTick((t) => t + 1), 5e3);
96272
+ const timer = setInterval(() => setTick((t) => t + 1), 3e4);
96263
96273
  return () => clearInterval(timer);
96264
96274
  }, []);
96265
96275
  const ago = lastRefresh ? formatAgo(lastRefresh) : null;
96266
96276
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { children: [
96267
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { bold: true, color: "magenta", children: ` \u26A1 ${import_sidekick_docker_shared8.BRAND_INLINE}` }),
96268
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", dimColor: true, children: ` ${import_sidekick_docker_shared8.BRAND_TAGLINE} v${version2}` }),
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}` }),
96269
96279
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", dimColor: true, children: ` ${SEP2} ` }),
96270
96280
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: daemonConnected ? "green" : "red", children: daemonConnected ? `\u25CF ${runningCount ?? 0}/${containerCount ?? 0}` : "\u25CB disconnected" }),
96271
96281
  ago && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: ago.stale ? "yellow" : "gray", dimColor: !ago.stale, children: ` \u21BB ${ago.text}` }),
@@ -96288,7 +96298,7 @@ function StatusBar({ daemonConnected, focusTarget, panelActionHints, filterStrin
96288
96298
 
96289
96299
  // src/dashboard/ink/HelpOverlay.tsx
96290
96300
  await init_build2();
96291
- var import_sidekick_docker_shared9 = __toESM(require_dist(), 1);
96301
+ var import_sidekick_docker_shared10 = __toESM(require_dist(), 1);
96292
96302
  var import_jsx_runtime6 = __toESM(require_jsx_runtime(), 1);
96293
96303
  var GLOBAL_BINDINGS = [
96294
96304
  { key: "1-5", label: "Switch panel" },
@@ -96314,7 +96324,7 @@ function HelpOverlay({ panels, activePanelIndex, version: version2 }) {
96314
96324
  const actions = panel.getActions();
96315
96325
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Box_default, { flexDirection: "column", flexGrow: 1, padding: 1, children: [
96316
96326
  /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Box_default, { children: [
96317
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { bold: true, color: "magenta", children: `\u26A1 ${import_sidekick_docker_shared9.BRAND_INLINE} ${import_sidekick_docker_shared9.BRAND_TAGLINE}` }),
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}` }),
96318
96328
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "gray", dimColor: true, children: ` v${version2}` })
96319
96329
  ] }),
96320
96330
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { children: "" }),
@@ -96483,7 +96493,7 @@ function ToastNotification({ toast }) {
96483
96493
 
96484
96494
  // src/dashboard/ink/TooSmallOverlay.tsx
96485
96495
  await init_build2();
96486
- var import_sidekick_docker_shared10 = __toESM(require_dist(), 1);
96496
+ var import_sidekick_docker_shared11 = __toESM(require_dist(), 1);
96487
96497
  var import_jsx_runtime11 = __toESM(require_jsx_runtime(), 1);
96488
96498
  function TooSmallOverlay({ columns, rows }) {
96489
96499
  const needWidth = Math.max(0, 60 - columns);
@@ -96492,7 +96502,7 @@ function TooSmallOverlay({ columns, rows }) {
96492
96502
  if (needWidth > 0) hints.push(`${needWidth} col${needWidth > 1 ? "s" : ""} wider`);
96493
96503
  if (needHeight > 0) hints.push(`${needHeight} row${needHeight > 1 ? "s" : ""} taller`);
96494
96504
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { flexDirection: "column", justifyContent: "center", alignItems: "center", height: rows, width: columns, children: [
96495
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { bold: true, color: "magenta", children: `\u26A1 ${import_sidekick_docker_shared10.BRAND_INLINE}` }),
96505
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { bold: true, color: "magenta", children: `\u26A1 ${import_sidekick_docker_shared11.BRAND_INLINE}` }),
96496
96506
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { children: "" }),
96497
96507
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: "yellow", bold: true, children: "Terminal too small" }),
96498
96508
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: "gray", children: `${columns}\xD7${rows} \u2192 need ${hints.join(" and ")}` }),
@@ -96632,13 +96642,13 @@ function LogFilterOverlay({ filterString, filterMode }) {
96632
96642
  // src/dashboard/ink/VersionOverlay.tsx
96633
96643
  var import_react35 = __toESM(require_react(), 1);
96634
96644
  await init_build2();
96635
- var import_sidekick_docker_shared11 = __toESM(require_dist(), 1);
96645
+ var import_sidekick_docker_shared12 = __toESM(require_dist(), 1);
96636
96646
  var import_jsx_runtime15 = __toESM(require_jsx_runtime(), 1);
96637
96647
  function VersionOverlay({ version: version2 }) {
96638
- const phrase = import_react35.default.useMemo(() => (0, import_sidekick_docker_shared11.getRandomPhrase)(), []);
96648
+ const phrase = import_react35.default.useMemo(() => (0, import_sidekick_docker_shared12.getRandomPhrase)(), []);
96639
96649
  return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(Box_default, { flexDirection: "column", flexGrow: 1, padding: 1, children: [
96640
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { bold: true, color: "magenta", children: `\u26A1 ${import_sidekick_docker_shared11.BRAND_INLINE}` }),
96641
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: "#2B4C7E", bold: true, children: `${import_sidekick_docker_shared11.BRAND_TAGLINE} v${version2}` }),
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}` }),
96642
96652
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { children: "" }),
96643
96653
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: "gray", dimColor: true, children: "\u2500".repeat(40) }),
96644
96654
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { children: "" }),
@@ -96697,7 +96707,7 @@ function SortOverlay({ selectedIndex, currentField, reversed }) {
96697
96707
  }
96698
96708
 
96699
96709
  // src/dashboard/ink/Dashboard.tsx
96700
- var import_sidekick_docker_shared12 = __toESM(require_dist(), 1);
96710
+ var import_sidekick_docker_shared13 = __toESM(require_dist(), 1);
96701
96711
 
96702
96712
  // src/dashboard/ExecManager.ts
96703
96713
  var ExecManager = class {
@@ -96885,16 +96895,16 @@ var initialState = {
96885
96895
  sortReversed: false,
96886
96896
  sortMenuIndex: 0
96887
96897
  };
96888
- function Dashboard({ panels, metrics, onSelectionChange, execTriggerRef, onExecFallback }) {
96898
+ function Dashboard({ panels, metrics, onViewStateChange, execTriggerRef, onExecFallback }) {
96889
96899
  const [state, dispatch] = (0, import_react36.useReducer)(reducer, initialState);
96890
96900
  const { columns, rows } = useTerminalSize();
96891
96901
  const toastIdRef = (0, import_react36.useRef)(0);
96892
96902
  const execManagerRef = (0, import_react36.useRef)(null);
96893
- const [phrase, setPhrase] = import_react36.default.useState(() => (0, import_sidekick_docker_shared12.getRandomPhrase)());
96903
+ const [phrase, setPhrase] = import_react36.default.useState(() => (0, import_sidekick_docker_shared13.getRandomPhrase)());
96894
96904
  const phraseTimerRef = (0, import_react36.useRef)(null);
96895
96905
  const rotatePhraseRef = (0, import_react36.useRef)(void 0);
96896
96906
  rotatePhraseRef.current = () => {
96897
- setPhrase((0, import_sidekick_docker_shared12.getRandomPhrase)());
96907
+ setPhrase((0, import_sidekick_docker_shared13.getRandomPhrase)());
96898
96908
  if (phraseTimerRef.current) clearTimeout(phraseTimerRef.current);
96899
96909
  phraseTimerRef.current = setTimeout(() => rotatePhraseRef.current?.(), 7e3);
96900
96910
  };
@@ -97047,11 +97057,16 @@ function Dashboard({ panels, metrics, onSelectionChange, execTriggerRef, onExecF
97047
97057
  }
97048
97058
  }, [state.selectedItemIndex]);
97049
97059
  const selectedItem = currentItems[clampedSelection];
97050
- (0, import_react36.useEffect)(() => {
97051
- onSelectionChange?.(panel.id, selectedItem?.id ?? null);
97052
- }, [panel.id, selectedItem?.id]);
97053
97060
  const detailTabs = panel.detailTabs;
97054
97061
  const tabIdx = Math.min(state.detailTabIndex, detailTabs.length - 1);
97062
+ (0, import_react36.useEffect)(() => {
97063
+ onViewStateChange?.({
97064
+ panelId: panel.id,
97065
+ itemId: selectedItem?.id ?? null,
97066
+ detailTabIndex: tabIdx,
97067
+ sortField: state.sortField
97068
+ });
97069
+ }, [panel.id, selectedItem?.id, tabIdx, state.sortField]);
97055
97070
  const enrichedMetrics = {
97056
97071
  ...metrics,
97057
97072
  logFilterString: state.logFilterString,
@@ -97160,7 +97175,7 @@ function Dashboard({ panels, metrics, onSelectionChange, execTriggerRef, onExecF
97160
97175
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
97161
97176
  DetailPane,
97162
97177
  {
97163
- content: detailContent,
97178
+ lines: detailLines,
97164
97179
  scrollOffset: state.detailScrollOffset,
97165
97180
  viewportHeight: detailViewportHeight,
97166
97181
  focused: state.focusTarget === "detail"
@@ -97168,8 +97183,8 @@ function Dashboard({ panels, metrics, onSelectionChange, execTriggerRef, onExecF
97168
97183
  )
97169
97184
  ] })
97170
97185
  ] }),
97171
- state.overlay === "help" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(HelpOverlay, { panels, activePanelIndex: state.activePanelIndex, version: "0.2.0" }),
97172
- state.overlay === "version" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(VersionOverlay, { version: "0.2.0" }),
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" }),
97173
97188
  state.overlay === "exec" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
97174
97189
  ExecOverlay,
97175
97190
  {
@@ -97186,7 +97201,7 @@ function Dashboard({ panels, metrics, onSelectionChange, execTriggerRef, onExecF
97186
97201
  filterString: state.filterString,
97187
97202
  containerCount: metrics.containers.length,
97188
97203
  runningCount,
97189
- version: "0.2.0",
97204
+ version: "0.2.2",
97190
97205
  matchCount: state.filterString ? currentItems.length : void 0,
97191
97206
  totalCount: state.filterString ? totalItemCount : void 0,
97192
97207
  lastRefresh: metrics.lastRefresh,
@@ -97231,7 +97246,7 @@ function Dashboard({ panels, metrics, onSelectionChange, execTriggerRef, onExecF
97231
97246
  async function dashboardAction(_opts, cmd) {
97232
97247
  const globalOpts = cmd.parent?.opts() ?? cmd.opts();
97233
97248
  const socketPath = globalOpts.socket;
97234
- const client = new import_sidekick_docker_shared13.DockerClient(socketPath ? { socketPath } : void 0);
97249
+ const client = new import_sidekick_docker_shared14.DockerClient(socketPath ? { socketPath } : void 0);
97235
97250
  const ok = await client.ping();
97236
97251
  if (!ok) {
97237
97252
  console.error("Error: Cannot connect to Docker daemon. Is Docker running?");
@@ -97240,34 +97255,68 @@ async function dashboardAction(_opts, cmd) {
97240
97255
  const cwd2 = process.cwd();
97241
97256
  const state = new DockerState(client, cwd2);
97242
97257
  await state.refresh();
97243
- const composeClient = new import_sidekick_docker_shared13.ComposeClient();
97258
+ const composeClient = new import_sidekick_docker_shared14.ComposeClient();
97244
97259
  const onAction = () => {
97245
97260
  state.refresh().then(() => scheduleRender()).catch((e) => console.debug("refresh failed:", e));
97246
97261
  };
97247
97262
  const onError = (msg) => {
97248
97263
  console.debug("panel action failed:", msg);
97249
97264
  };
97265
+ let logFlushTimer = null;
97266
+ let composeLogFlushTimer = null;
97250
97267
  const logManager = new LogStreamManager(client, () => {
97268
+ if (logFlushTimer) return;
97269
+ logFlushTimer = setTimeout(() => {
97270
+ logFlushTimer = null;
97271
+ state.setSelectedLogs(logManager.getLogs());
97272
+ logSeverityCounts = logManager.getSeverityCounts();
97273
+ scheduleRender();
97274
+ }, 100);
97275
+ });
97276
+ const flushLogsNow = () => {
97277
+ if (logFlushTimer) {
97278
+ clearTimeout(logFlushTimer);
97279
+ logFlushTimer = null;
97280
+ }
97251
97281
  state.setSelectedLogs(logManager.getLogs());
97252
97282
  logSeverityCounts = logManager.getSeverityCounts();
97253
97283
  scheduleRender();
97254
- });
97284
+ };
97255
97285
  let logSeverityCounts = logManager.getSeverityCounts();
97256
97286
  const statsManager = new StatsStreamManager(client, state.getStatsCollector(), () => {
97257
97287
  scheduleRender();
97258
97288
  });
97259
97289
  const composeLogManager = new ComposeLogStreamManager(composeClient, () => {
97290
+ if (composeLogFlushTimer) return;
97291
+ composeLogFlushTimer = setTimeout(() => {
97292
+ composeLogFlushTimer = null;
97293
+ state.clearComposeLogs();
97294
+ for (const entry of composeLogManager.getLogs()) {
97295
+ state.appendComposeLog(entry);
97296
+ }
97297
+ scheduleRender();
97298
+ }, 100);
97299
+ });
97300
+ const flushComposeLogsNow = () => {
97301
+ if (composeLogFlushTimer) {
97302
+ clearTimeout(composeLogFlushTimer);
97303
+ composeLogFlushTimer = null;
97304
+ }
97260
97305
  state.clearComposeLogs();
97261
97306
  for (const entry of composeLogManager.getLogs()) {
97262
97307
  state.appendComposeLog(entry);
97263
97308
  }
97264
97309
  scheduleRender();
97265
- });
97266
- const onSelectionChange = (panelId, itemId) => {
97310
+ };
97311
+ const onViewStateChange = (viewState) => {
97312
+ const { panelId, itemId, detailTabIndex, sortField } = viewState;
97313
+ const wantsLiveStats = sortField === "cpu" || sortField === "mem" || sortField === "net" || sortField === "io" || sortField === "pids";
97267
97314
  if (panelId === "containers") {
97268
- logManager.select(itemId);
97269
- statsManager.select(itemId);
97270
- composeLogManager.selectCompose(null, null);
97315
+ void logManager.select(detailTabIndex === 0 ? itemId : null);
97316
+ void statsManager.select(itemId && (detailTabIndex === 1 || wantsLiveStats) ? itemId : null);
97317
+ void composeLogManager.selectCompose(null, null);
97318
+ flushLogsNow();
97319
+ flushComposeLogsNow();
97271
97320
  if (itemId && !state.getInspectedEnv(itemId)) {
97272
97321
  client.getContainerEnv(itemId).then((env3) => {
97273
97322
  state.setInspectedEnv(itemId, env3);
@@ -97281,18 +97330,26 @@ async function dashboardAction(_opts, cmd) {
97281
97330
  }).catch((e) => console.debug("getContainerChanges failed:", e));
97282
97331
  }
97283
97332
  } else if (panelId === "services" && itemId) {
97284
- logManager.select(null);
97285
- statsManager.select(null);
97286
- const parts = itemId.split(":");
97287
- if (parts[0] === "project") {
97288
- composeLogManager.selectCompose(parts.slice(1).join(":"), null);
97289
- } else if (parts[0] === "service") {
97290
- composeLogManager.selectCompose(parts[1], parts.slice(2).join(":"));
97333
+ void logManager.select(null);
97334
+ void statsManager.select(null);
97335
+ if (detailTabIndex === 1) {
97336
+ const parts = itemId.split(":");
97337
+ if (parts[0] === "project") {
97338
+ void composeLogManager.selectCompose(parts.slice(1).join(":"), null);
97339
+ } else if (parts[0] === "service") {
97340
+ void composeLogManager.selectCompose(parts[1], parts.slice(2).join(":"));
97341
+ }
97342
+ } else {
97343
+ void composeLogManager.selectCompose(null, null);
97291
97344
  }
97345
+ flushLogsNow();
97346
+ flushComposeLogsNow();
97292
97347
  } else if (panelId === "images") {
97293
- logManager.select(null);
97294
- statsManager.select(null);
97295
- composeLogManager.selectCompose(null, null);
97348
+ void logManager.select(null);
97349
+ void statsManager.select(null);
97350
+ void composeLogManager.selectCompose(null, null);
97351
+ flushLogsNow();
97352
+ flushComposeLogsNow();
97296
97353
  if (itemId && !state.getImageLayers(itemId)) {
97297
97354
  client.getImageHistory(itemId).then((layers) => {
97298
97355
  state.setImageLayers(itemId, layers);
@@ -97300,9 +97357,11 @@ async function dashboardAction(_opts, cmd) {
97300
97357
  }).catch((e) => console.debug("getImageHistory failed:", e));
97301
97358
  }
97302
97359
  } else {
97303
- logManager.select(null);
97304
- statsManager.select(null);
97305
- composeLogManager.selectCompose(null, null);
97360
+ void logManager.select(null);
97361
+ void statsManager.select(null);
97362
+ void composeLogManager.selectCompose(null, null);
97363
+ flushLogsNow();
97364
+ flushComposeLogsNow();
97306
97365
  }
97307
97366
  };
97308
97367
  const panels = [
@@ -97312,7 +97371,7 @@ async function dashboardAction(_opts, cmd) {
97312
97371
  new VolumesPanel(client, onAction, onError),
97313
97372
  new NetworksPanel(client, onAction, onError)
97314
97373
  ];
97315
- const watcher = new import_sidekick_docker_shared13.EventWatcher(client, {
97374
+ const watcher = new import_sidekick_docker_shared14.EventWatcher(client, {
97316
97375
  onEvent: (event) => {
97317
97376
  state.processEvent(event);
97318
97377
  scheduleRender();
@@ -97337,7 +97396,7 @@ async function dashboardAction(_opts, cmd) {
97337
97396
  import_react37.default.createElement(Dashboard, {
97338
97397
  panels,
97339
97398
  metrics: getEnrichedMetrics(),
97340
- onSelectionChange,
97399
+ onViewStateChange,
97341
97400
  execTriggerRef,
97342
97401
  onExecFallback
97343
97402
  })
@@ -97349,7 +97408,7 @@ async function dashboardAction(_opts, cmd) {
97349
97408
  import_react37.default.createElement(Dashboard, {
97350
97409
  panels,
97351
97410
  metrics: getEnrichedMetrics(),
97352
- onSelectionChange,
97411
+ onViewStateChange,
97353
97412
  execTriggerRef,
97354
97413
  onExecFallback
97355
97414
  })
@@ -97357,7 +97416,7 @@ async function dashboardAction(_opts, cmd) {
97357
97416
  const containersPanel = panels[0];
97358
97417
  containersPanel.setOnExec((containerId) => {
97359
97418
  const container = state.getMetrics().containers.find((c) => c.id === containerId);
97360
- const name = container?.name ?? (0, import_sidekick_docker_shared13.shortId)(containerId);
97419
+ const name = container?.name ?? (0, import_sidekick_docker_shared14.shortId)(containerId);
97361
97420
  if (execTriggerRef.current) {
97362
97421
  execTriggerRef.current(containerId, name);
97363
97422
  } else {
@@ -97367,6 +97426,10 @@ async function dashboardAction(_opts, cmd) {
97367
97426
  containersPanel.setOnCopyLogs((text) => {
97368
97427
  copyToClipboard(text);
97369
97428
  });
97429
+ const servicesPanel = panels[1];
97430
+ servicesPanel.setOnCopyLogs((text) => {
97431
+ copyToClipboard(text);
97432
+ });
97370
97433
  let renderTimer = null;
97371
97434
  function getEnrichedMetrics() {
97372
97435
  const m = state.getMetrics();
@@ -97383,7 +97446,7 @@ async function dashboardAction(_opts, cmd) {
97383
97446
  import_react37.default.createElement(Dashboard, {
97384
97447
  panels,
97385
97448
  metrics: getEnrichedMetrics(),
97386
- onSelectionChange,
97449
+ onViewStateChange,
97387
97450
  execTriggerRef,
97388
97451
  onExecFallback
97389
97452
  })
@@ -97406,6 +97469,14 @@ async function dashboardAction(_opts, cmd) {
97406
97469
  composeLogManager.dispose();
97407
97470
  } catch {
97408
97471
  }
97472
+ try {
97473
+ if (logFlushTimer) clearTimeout(logFlushTimer);
97474
+ } catch {
97475
+ }
97476
+ try {
97477
+ if (composeLogFlushTimer) clearTimeout(composeLogFlushTimer);
97478
+ } catch {
97479
+ }
97409
97480
  try {
97410
97481
  clearInterval(refreshInterval);
97411
97482
  } catch {
@@ -97430,9 +97501,9 @@ async function dashboardAction(_opts, cmd) {
97430
97501
  }
97431
97502
 
97432
97503
  // src/commands/ps.ts
97433
- var import_sidekick_docker_shared14 = __toESM(require_dist(), 1);
97504
+ var import_sidekick_docker_shared15 = __toESM(require_dist(), 1);
97434
97505
  async function psAction(opts) {
97435
- const client = new import_sidekick_docker_shared14.DockerClient();
97506
+ const client = new import_sidekick_docker_shared15.DockerClient();
97436
97507
  const ok = await client.ping();
97437
97508
  if (!ok) {
97438
97509
  console.error("Error: Cannot connect to Docker daemon. Is Docker running?");
@@ -97448,7 +97519,7 @@ async function psAction(opts) {
97448
97519
  console.log("-".repeat(100));
97449
97520
  for (const c of containers) {
97450
97521
  const row = [
97451
- (0, import_sidekick_docker_shared14.shortId)(c.id).padEnd(20),
97522
+ (0, import_sidekick_docker_shared15.shortId)(c.id).padEnd(20),
97452
97523
  `${(0, import_sidekick_docker_shared3.stateIcon)(c.state)} ${c.name}`.padEnd(20),
97453
97524
  c.image.padEnd(20),
97454
97525
  formatUptime(c.status).padEnd(20),
@@ -97459,9 +97530,9 @@ async function psAction(opts) {
97459
97530
  }
97460
97531
 
97461
97532
  // src/commands/logs.ts
97462
- var import_sidekick_docker_shared15 = __toESM(require_dist(), 1);
97533
+ var import_sidekick_docker_shared16 = __toESM(require_dist(), 1);
97463
97534
  async function logsAction(container, opts) {
97464
- const client = new import_sidekick_docker_shared15.DockerClient();
97535
+ const client = new import_sidekick_docker_shared16.DockerClient();
97465
97536
  const ok = await client.ping();
97466
97537
  if (!ok) {
97467
97538
  console.error("Error: Cannot connect to Docker daemon. Is Docker running?");
@@ -97472,13 +97543,13 @@ async function logsAction(container, opts) {
97472
97543
  follow: opts.follow ?? true,
97473
97544
  tail: parseInt(opts.tail || "100", 10)
97474
97545
  })) {
97475
- const ts = entry.timestamp ? (0, import_sidekick_docker_shared15.formatTimestampTime)(entry.timestamp) : "";
97546
+ const ts = entry.timestamp ? (0, import_sidekick_docker_shared16.formatTimestampTime)(entry.timestamp) : "";
97476
97547
  const prefix = entry.stream === "stderr" ? "\x1B[31m" : "";
97477
97548
  const reset = entry.stream === "stderr" ? "\x1B[0m" : "";
97478
97549
  console.log(`${prefix}${ts} ${entry.message}${reset}`);
97479
97550
  }
97480
97551
  } catch (err) {
97481
- const msg = (0, import_sidekick_docker_shared15.errorMessage)(err);
97552
+ const msg = (0, import_sidekick_docker_shared16.errorMessage)(err);
97482
97553
  if (msg.includes("no such container") || msg.includes("No such container")) {
97483
97554
  console.error(`Error: Container "${container}" not found.`);
97484
97555
  } else {
@@ -97490,7 +97561,7 @@ async function logsAction(container, opts) {
97490
97561
 
97491
97562
  // src/cli.ts
97492
97563
  var program2 = new Command();
97493
- program2.name("sidekick-docker").description("Docker management TUI dashboard").version("0.2.0").option("--socket <path>", "Docker socket path").action(async (_opts, cmd) => {
97564
+ program2.name("sidekick-docker").description("Docker management TUI dashboard").version("0.2.2").option("--socket <path>", "Docker socket path").action(async (_opts, cmd) => {
97494
97565
  await dashboardAction(_opts, cmd);
97495
97566
  });
97496
97567
  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.0",
3
+ "version": "0.2.2",
4
4
  "description": "Docker management TUI dashboard",
5
5
  "author": "Cesar Andres Lopez <cesarandreslopez@gmail.com>",
6
6
  "repository": {