machineconfig 6.83__py3-none-any.whl → 6.85__py3-none-any.whl
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.
Potentially problematic release.
This version of machineconfig might be problematic. Click here for more details.
- machineconfig/cluster/sessions_managers/wt_local.py +16 -221
- machineconfig/cluster/sessions_managers/wt_local_manager.py +33 -174
- machineconfig/cluster/sessions_managers/wt_remote_manager.py +39 -197
- machineconfig/cluster/sessions_managers/wt_utils/manager_persistence.py +52 -0
- machineconfig/cluster/sessions_managers/wt_utils/monitoring_helpers.py +50 -0
- machineconfig/cluster/sessions_managers/wt_utils/status_reporting.py +76 -0
- machineconfig/cluster/sessions_managers/wt_utils/wt_helpers.py +199 -0
- machineconfig/scripts/linux/mcfgs +1 -1
- machineconfig/scripts/linux/term +39 -0
- machineconfig/scripts/python/ai/vscode_tasks.py +7 -2
- machineconfig/scripts/python/env_manager/path_manager_tui.py +1 -1
- machineconfig/scripts/python/fire_jobs.py +30 -65
- machineconfig/scripts/python/helpers_devops/cli_config.py +1 -1
- machineconfig/scripts/python/helpers_devops/cli_nw.py +50 -0
- machineconfig/scripts/python/helpers_devops/cli_self.py +3 -3
- machineconfig/scripts/python/helpers_devops/cli_utils.py +1 -1
- machineconfig/scripts/python/helpers_fire/helpers4.py +15 -0
- machineconfig/scripts/python/helpers_repos/cloud_repo_sync.py +1 -1
- machineconfig/scripts/python/nw/mount_nfs +1 -1
- machineconfig/scripts/python/nw/wifi_conn.py +1 -53
- machineconfig/scripts/python/terminal.py +110 -0
- machineconfig/scripts/python/utils.py +2 -0
- machineconfig/scripts/windows/mounts/mount_ssh.ps1 +1 -1
- machineconfig/scripts/windows/term.ps1 +48 -0
- machineconfig/settings/shells/bash/init.sh +2 -0
- machineconfig/settings/shells/pwsh/init.ps1 +1 -0
- machineconfig/setup_linux/web_shortcuts/interactive.sh +2 -1
- machineconfig/setup_windows/web_shortcuts/interactive.ps1 +2 -1
- machineconfig/utils/code.py +21 -14
- machineconfig/utils/installer.py +0 -1
- machineconfig/utils/options.py +12 -2
- machineconfig/utils/path_helper.py +1 -1
- machineconfig/utils/scheduling.py +0 -2
- machineconfig/utils/ssh.py +2 -2
- {machineconfig-6.83.dist-info → machineconfig-6.85.dist-info}/METADATA +1 -1
- {machineconfig-6.83.dist-info → machineconfig-6.85.dist-info}/RECORD +39 -35
- {machineconfig-6.83.dist-info → machineconfig-6.85.dist-info}/entry_points.txt +1 -0
- machineconfig/scripts/linux/other/share_smb +0 -1
- machineconfig/scripts/linux/warp-cli.sh +0 -122
- machineconfig/scripts/linux/z_ls +0 -104
- {machineconfig-6.83.dist-info → machineconfig-6.85.dist-info}/WHEEL +0 -0
- {machineconfig-6.83.dist-info → machineconfig-6.85.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
|
-
import json
|
|
3
|
-
import uuid
|
|
4
2
|
import logging
|
|
5
3
|
from pathlib import Path
|
|
6
4
|
from typing import Optional, Any
|
|
@@ -8,8 +6,17 @@ from rich.console import Console
|
|
|
8
6
|
from machineconfig.utils.scheduler import Scheduler
|
|
9
7
|
from machineconfig.cluster.sessions_managers.wt_local import run_command_in_wt_tab
|
|
10
8
|
from machineconfig.cluster.sessions_managers.wt_remote import WTRemoteLayoutGenerator
|
|
11
|
-
from machineconfig.cluster.sessions_managers.wt_utils.
|
|
9
|
+
from machineconfig.cluster.sessions_managers.wt_utils.wt_helpers import generate_random_suffix
|
|
12
10
|
from machineconfig.utils.schemas.layouts.layout_types import TabConfig, LayoutConfig
|
|
11
|
+
from machineconfig.cluster.sessions_managers.wt_utils.manager_persistence import (
|
|
12
|
+
generate_session_id, save_json_file, load_json_file, list_saved_sessions_in_dir, delete_session_dir, ensure_session_dir_exists
|
|
13
|
+
)
|
|
14
|
+
from machineconfig.cluster.sessions_managers.wt_utils.status_reporting import (
|
|
15
|
+
print_global_summary, print_session_health_status, print_commands_status, calculate_session_summary, calculate_global_summary_from_status
|
|
16
|
+
)
|
|
17
|
+
from machineconfig.cluster.sessions_managers.wt_utils.monitoring_helpers import (
|
|
18
|
+
collect_status_data_from_managers, flatten_status_data, check_if_all_stopped, print_status_table, collect_session_statuses, print_session_statuses
|
|
19
|
+
)
|
|
13
20
|
|
|
14
21
|
|
|
15
22
|
# Module-level logger to be used throughout this module
|
|
@@ -26,11 +33,8 @@ class WTSessionManager:
|
|
|
26
33
|
for machine, tab_config in machine2wt_tabs.items():
|
|
27
34
|
# Convert legacy dict[str, tuple[str,str]] to LayoutConfig
|
|
28
35
|
tabs: list[TabConfig] = [{"tabName": name, "startDir": cwd, "command": cmd} for name, (cwd, cmd) in tab_config.items()]
|
|
29
|
-
layout_config: LayoutConfig = {
|
|
30
|
-
|
|
31
|
-
"layoutTabs": tabs
|
|
32
|
-
}
|
|
33
|
-
session_name = f"{session_name_prefix}_{WTLayoutGenerator.generate_random_suffix(8)}"
|
|
36
|
+
layout_config: LayoutConfig = {"layoutName": f"{session_name_prefix}_{machine}", "layoutTabs": tabs}
|
|
37
|
+
session_name = f"{session_name_prefix}_{generate_random_suffix(8)}"
|
|
34
38
|
an_m = WTRemoteLayoutGenerator(layout_config=layout_config, remote_name=machine, session_name=session_name)
|
|
35
39
|
an_m.create_layout_file()
|
|
36
40
|
self.managers.append(an_m)
|
|
@@ -56,103 +60,47 @@ class WTSessionManager:
|
|
|
56
60
|
def run_monitoring_routine(self, wait_ms: int = 60000) -> None:
|
|
57
61
|
def routine(scheduler: Scheduler):
|
|
58
62
|
if scheduler.cycle % 2 == 0:
|
|
59
|
-
statuses =
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
keys = []
|
|
65
|
-
for item in statuses:
|
|
66
|
-
keys.extend(item.keys())
|
|
67
|
-
values = []
|
|
68
|
-
for item in statuses:
|
|
69
|
-
values.extend(item.values())
|
|
70
|
-
# Create list of dictionaries instead of DataFrame
|
|
71
|
-
status_data = []
|
|
72
|
-
for i, key in enumerate(keys):
|
|
73
|
-
if i < len(values):
|
|
74
|
-
status_data.append({"tabName": key, "status": values[i]})
|
|
75
|
-
|
|
76
|
-
# Check if all stopped
|
|
77
|
-
running_count = sum(1 for item in status_data if item.get("status", {}).get("running", False))
|
|
78
|
-
if running_count == 0: # they all stopped
|
|
79
|
-
scheduler.max_cycles = scheduler.cycle # stop the scheduler from calling this routine again
|
|
80
|
-
|
|
81
|
-
# Print status
|
|
82
|
-
for item in status_data:
|
|
83
|
-
print(f"Tab: {item['tabName']}, Status: {item['status']}")
|
|
63
|
+
statuses = collect_status_data_from_managers(self.managers)
|
|
64
|
+
status_data = flatten_status_data(statuses)
|
|
65
|
+
if check_if_all_stopped(status_data):
|
|
66
|
+
scheduler.max_cycles = scheduler.cycle
|
|
67
|
+
print_status_table(status_data)
|
|
84
68
|
else:
|
|
85
|
-
statuses =
|
|
86
|
-
|
|
87
|
-
a_status = an_m.session_manager.check_wt_session_status()
|
|
88
|
-
statuses.append(a_status)
|
|
89
|
-
|
|
90
|
-
# Print statuses
|
|
91
|
-
for i, status in enumerate(statuses):
|
|
92
|
-
print(f"Manager {i}: {status}")
|
|
93
|
-
|
|
69
|
+
statuses = collect_session_statuses(self.managers)
|
|
70
|
+
print_session_statuses(statuses)
|
|
94
71
|
sched = Scheduler(routine=routine, wait_ms=wait_ms, logger=logger)
|
|
95
72
|
sched.run()
|
|
96
73
|
|
|
97
74
|
def save(self, session_id: Optional[str] = None) -> str:
|
|
98
75
|
if session_id is None:
|
|
99
|
-
session_id =
|
|
100
|
-
|
|
101
|
-
# Create session directory
|
|
76
|
+
session_id = generate_session_id()
|
|
102
77
|
session_dir = TMP_SERIALIZATION_DIR / session_id
|
|
103
|
-
session_dir
|
|
104
|
-
|
|
105
|
-
# Save the machine2wt_tabs configuration
|
|
106
|
-
config_file = session_dir / "machine2wt_tabs.json"
|
|
107
|
-
text = json.dumps(self.machine2wt_tabs, indent=2, ensure_ascii=False)
|
|
108
|
-
config_file.write_text(text, encoding="utf-8")
|
|
109
|
-
|
|
110
|
-
# Save session metadata
|
|
78
|
+
ensure_session_dir_exists(session_dir)
|
|
79
|
+
save_json_file(session_dir / "machine2wt_tabs.json", self.machine2wt_tabs, "machine2wt_tabs")
|
|
111
80
|
metadata = {"session_name_prefix": self.session_name_prefix, "created_at": str(datetime.now()), "num_managers": len(self.managers), "machines": list(self.machine2wt_tabs.keys()), "manager_type": "WTSessionManager"}
|
|
112
|
-
|
|
113
|
-
text = json.dumps(metadata, indent=2, ensure_ascii=False)
|
|
114
|
-
metadata_file.write_text(text, encoding="utf-8")
|
|
115
|
-
|
|
116
|
-
# Save each WTRemoteLayoutGenerator
|
|
81
|
+
save_json_file(session_dir / "metadata.json", metadata, "metadata")
|
|
117
82
|
managers_dir = session_dir / "managers"
|
|
118
83
|
managers_dir.mkdir(exist_ok=True)
|
|
119
|
-
|
|
120
84
|
for i, manager in enumerate(self.managers):
|
|
121
|
-
|
|
122
|
-
manager.to_json(str(manager_file))
|
|
123
|
-
|
|
85
|
+
manager.to_json(str(managers_dir / f"manager_{i}_{manager.remote_name}.json"))
|
|
124
86
|
logger.info(f"✅ Saved WTSessionManager session to: {session_dir}")
|
|
125
87
|
return session_id
|
|
126
88
|
|
|
127
89
|
@classmethod
|
|
128
90
|
def load(cls, session_id: str) -> "WTSessionManager":
|
|
129
91
|
session_dir = TMP_SERIALIZATION_DIR / session_id
|
|
130
|
-
|
|
131
92
|
if not session_dir.exists():
|
|
132
93
|
raise FileNotFoundError(f"Session directory not found: {session_dir}")
|
|
133
|
-
|
|
134
|
-
if
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
# Load metadata
|
|
140
|
-
metadata_file = session_dir / "metadata.json"
|
|
141
|
-
session_name_prefix = "WTJobMgr" # default fallback
|
|
142
|
-
if metadata_file.exists():
|
|
143
|
-
text = metadata_file.read_text(encoding="utf-8")
|
|
144
|
-
metadata = json.loads(text)
|
|
145
|
-
session_name_prefix = metadata.get("session_name_prefix", "WTJobMgr")
|
|
146
|
-
# Create new instance (this will create new managers)
|
|
94
|
+
loaded_data = load_json_file(session_dir / "machine2wt_tabs.json", "Configuration file")
|
|
95
|
+
machine2wt_tabs = loaded_data if isinstance(loaded_data, dict) else {} # type: ignore[arg-type]
|
|
96
|
+
metadata_data = load_json_file(session_dir / "metadata.json", "Metadata file") if (session_dir / "metadata.json").exists() else {}
|
|
97
|
+
metadata = metadata_data if isinstance(metadata_data, dict) else {} # type: ignore[arg-type]
|
|
98
|
+
session_name_prefix = metadata.get("session_name_prefix", "WTJobMgr") # type: ignore[union-attr]
|
|
147
99
|
instance = cls(machine2wt_tabs=machine2wt_tabs, session_name_prefix=session_name_prefix)
|
|
148
|
-
# Load saved managers to restore their states
|
|
149
100
|
managers_dir = session_dir / "managers"
|
|
150
101
|
if managers_dir.exists():
|
|
151
|
-
# Clear the auto-created managers and load the saved ones
|
|
152
102
|
instance.managers = []
|
|
153
|
-
|
|
154
|
-
manager_files = sorted(managers_dir.glob("manager_*.json"))
|
|
155
|
-
for manager_file in manager_files:
|
|
103
|
+
for manager_file in sorted(managers_dir.glob("manager_*.json")):
|
|
156
104
|
try:
|
|
157
105
|
loaded_manager = WTRemoteLayoutGenerator.from_json(str(manager_file))
|
|
158
106
|
instance.managers.append(loaded_manager)
|
|
@@ -163,33 +111,11 @@ class WTSessionManager:
|
|
|
163
111
|
|
|
164
112
|
@staticmethod
|
|
165
113
|
def list_saved_sessions() -> list[str]:
|
|
166
|
-
|
|
167
|
-
return []
|
|
168
|
-
|
|
169
|
-
sessions = []
|
|
170
|
-
for item in TMP_SERIALIZATION_DIR.iterdir():
|
|
171
|
-
if item.is_dir() and (item / "metadata.json").exists():
|
|
172
|
-
sessions.append(item.name)
|
|
173
|
-
|
|
174
|
-
return sorted(sessions)
|
|
114
|
+
return list_saved_sessions_in_dir(TMP_SERIALIZATION_DIR)
|
|
175
115
|
|
|
176
116
|
@staticmethod
|
|
177
117
|
def delete_session(session_id: str) -> bool:
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if not session_dir.exists():
|
|
181
|
-
logger.warning(f"Session directory not found: {session_dir}")
|
|
182
|
-
return False
|
|
183
|
-
|
|
184
|
-
try:
|
|
185
|
-
import shutil
|
|
186
|
-
|
|
187
|
-
shutil.rmtree(session_dir)
|
|
188
|
-
logger.info(f"✅ Deleted session: {session_id}")
|
|
189
|
-
return True
|
|
190
|
-
except Exception as e:
|
|
191
|
-
logger.error(f"Failed to delete session {session_id}: {e}")
|
|
192
|
-
return False
|
|
118
|
+
return delete_session_dir(TMP_SERIALIZATION_DIR / session_id, session_id)
|
|
193
119
|
|
|
194
120
|
def start_all_sessions(self) -> dict[str, Any]:
|
|
195
121
|
"""Start all Windows Terminal sessions on their respective remote machines."""
|
|
@@ -216,122 +142,38 @@ class WTSessionManager:
|
|
|
216
142
|
return results
|
|
217
143
|
|
|
218
144
|
def check_all_sessions_status(self) -> dict[str, dict[str, Any]]:
|
|
219
|
-
"""Check the status of all remote sessions and their commands."""
|
|
220
145
|
status_report = {}
|
|
221
|
-
|
|
222
146
|
for manager in self.managers:
|
|
223
147
|
session_key = f"{manager.remote_name}:{manager.session_name}"
|
|
224
|
-
|
|
225
148
|
try:
|
|
226
|
-
# Get Windows Terminal session status
|
|
227
149
|
wt_status = manager.session_manager.check_wt_session_status()
|
|
228
|
-
|
|
229
|
-
# Get commands status for this session
|
|
230
150
|
tabs = manager.layout_config["layoutTabs"]
|
|
231
151
|
commands_status = manager.process_monitor.check_all_commands_status(tabs)
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
running_count = sum(1 for status in commands_status.values() if status.get("running", False))
|
|
235
|
-
total_count = len(commands_status)
|
|
236
|
-
|
|
237
|
-
status_report[session_key] = {
|
|
238
|
-
"remote_name": manager.remote_name,
|
|
239
|
-
"session_name": manager.session_name,
|
|
240
|
-
"wt_status": wt_status,
|
|
241
|
-
"commands_status": commands_status,
|
|
242
|
-
"summary": {"total_commands": total_count, "running_commands": running_count, "stopped_commands": total_count - running_count, "session_healthy": wt_status.get("wt_running", False)},
|
|
243
|
-
}
|
|
244
|
-
|
|
152
|
+
summary = calculate_session_summary(commands_status, wt_status.get("wt_running", False))
|
|
153
|
+
status_report[session_key] = {"remote_name": manager.remote_name, "session_name": manager.session_name, "wt_status": wt_status, "commands_status": commands_status, "summary": summary}
|
|
245
154
|
except Exception as e:
|
|
246
155
|
status_report[session_key] = {"remote_name": manager.remote_name, "session_name": manager.session_name, "error": str(e), "summary": {"total_commands": 0, "running_commands": 0, "stopped_commands": 0, "session_healthy": False}}
|
|
247
156
|
logger.error(f"Error checking status for {session_key}: {e}")
|
|
248
|
-
|
|
249
157
|
return status_report
|
|
250
158
|
|
|
251
159
|
def get_global_summary(self) -> dict[str, Any]:
|
|
252
|
-
"""Get a global summary across all remote sessions."""
|
|
253
160
|
all_status = self.check_all_sessions_status()
|
|
254
|
-
|
|
255
|
-
total_sessions = len(all_status)
|
|
256
|
-
healthy_sessions = sum(1 for status in all_status.values() if status["summary"]["session_healthy"])
|
|
257
|
-
total_commands = sum(status["summary"]["total_commands"] for status in all_status.values())
|
|
258
|
-
total_running = sum(status["summary"]["running_commands"] for status in all_status.values())
|
|
259
|
-
|
|
260
|
-
return {
|
|
261
|
-
"total_sessions": total_sessions,
|
|
262
|
-
"healthy_sessions": healthy_sessions,
|
|
263
|
-
"unhealthy_sessions": total_sessions - healthy_sessions,
|
|
264
|
-
"total_commands": total_commands,
|
|
265
|
-
"running_commands": total_running,
|
|
266
|
-
"stopped_commands": total_commands - total_running,
|
|
267
|
-
"all_sessions_healthy": healthy_sessions == total_sessions,
|
|
268
|
-
"all_commands_running": total_running == total_commands,
|
|
269
|
-
"remote_machines": list(set(status["remote_name"] for status in all_status.values())),
|
|
270
|
-
}
|
|
161
|
+
return calculate_global_summary_from_status(all_status, include_remote_machines=True)
|
|
271
162
|
|
|
272
163
|
def print_status_report(self) -> None:
|
|
273
|
-
"""Print a comprehensive status report for all remote sessions."""
|
|
274
164
|
all_status = self.check_all_sessions_status()
|
|
275
165
|
global_summary = self.get_global_summary()
|
|
276
|
-
|
|
277
|
-
print("=" * 80)
|
|
278
|
-
print("🖥️ WINDOWS TERMINAL REMOTE MANAGER STATUS REPORT")
|
|
279
|
-
print("=" * 80)
|
|
280
|
-
|
|
281
|
-
# Global summary
|
|
282
|
-
print("🌐 GLOBAL SUMMARY:")
|
|
283
|
-
print(f" Total sessions: {global_summary['total_sessions']}")
|
|
284
|
-
print(f" Healthy sessions: {global_summary['healthy_sessions']}")
|
|
285
|
-
print(f" Total commands: {global_summary['total_commands']}")
|
|
286
|
-
print(f" Running commands: {global_summary['running_commands']}")
|
|
287
|
-
print(f" Remote machines: {len(global_summary['remote_machines'])}")
|
|
288
|
-
print(f" All healthy: {'✅' if global_summary['all_sessions_healthy'] else '❌'}")
|
|
289
|
-
print()
|
|
290
|
-
|
|
291
|
-
# Per-session details
|
|
166
|
+
print_global_summary(global_summary, "WINDOWS TERMINAL REMOTE MANAGER STATUS REPORT")
|
|
292
167
|
for _, status in all_status.items():
|
|
293
|
-
remote_name
|
|
294
|
-
session_name = status["session_name"]
|
|
295
|
-
|
|
296
|
-
print(f"🖥️ REMOTE: {remote_name} | SESSION: {session_name}")
|
|
168
|
+
print(f"🖥️ REMOTE: {status['remote_name']} | SESSION: {status['session_name']}")
|
|
297
169
|
print("-" * 60)
|
|
298
|
-
|
|
299
170
|
if "error" in status:
|
|
300
171
|
print(f"❌ Error: {status['error']}")
|
|
301
172
|
print()
|
|
302
173
|
continue
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
commands_status = status["commands_status"]
|
|
306
|
-
summary = status["summary"]
|
|
307
|
-
|
|
308
|
-
# Windows Terminal session health
|
|
309
|
-
if wt_status.get("wt_running", False):
|
|
310
|
-
if wt_status.get("session_exists", False):
|
|
311
|
-
session_windows = wt_status.get("session_windows", [])
|
|
312
|
-
all_windows = wt_status.get("all_windows", [])
|
|
313
|
-
print(f"✅ Windows Terminal is running on {remote_name}")
|
|
314
|
-
print(f" Session windows: {len(session_windows)}")
|
|
315
|
-
print(f" Total WT windows: {len(all_windows)}")
|
|
316
|
-
else:
|
|
317
|
-
print(f"⚠️ Windows Terminal is running but no session windows found on {remote_name}")
|
|
318
|
-
else:
|
|
319
|
-
print(f"❌ Windows Terminal issue on {remote_name}: {wt_status.get('error', 'Unknown error')}")
|
|
320
|
-
|
|
321
|
-
# Commands in this session
|
|
322
|
-
print(f" Commands ({summary['running_commands']}/{summary['total_commands']} running):")
|
|
323
|
-
for tab_name, cmd_status in commands_status.items():
|
|
324
|
-
status_icon = "✅" if cmd_status.get("running", False) else "❌"
|
|
325
|
-
cmd_text = cmd_status.get("command", "Unknown")[:50]
|
|
326
|
-
if len(cmd_status.get("command", "")) > 50:
|
|
327
|
-
cmd_text += "..."
|
|
328
|
-
console.print(f" {status_icon} {tab_name}: {cmd_text}")
|
|
329
|
-
|
|
330
|
-
if cmd_status.get("processes"):
|
|
331
|
-
for proc in cmd_status["processes"][:2]: # Show first 2 processes
|
|
332
|
-
console.print(f" [dim]└─[/dim] PID {proc.get('pid', 'Unknown')}: {proc.get('name', 'Unknown')}")
|
|
174
|
+
print_session_health_status(status["wt_status"], remote_name=status["remote_name"])
|
|
175
|
+
print_commands_status(status["commands_status"], status["summary"])
|
|
333
176
|
print()
|
|
334
|
-
|
|
335
177
|
print("=" * 80)
|
|
336
178
|
|
|
337
179
|
def get_remote_overview(self) -> dict[str, Any]:
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import uuid
|
|
3
|
+
import logging
|
|
4
|
+
import shutil
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def generate_session_id() -> str:
|
|
12
|
+
return str(uuid.uuid4())[:8]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def save_json_file(file_path: Path, data: dict[str, Any] | list[Any], description: str) -> None:
|
|
16
|
+
text = json.dumps(data, indent=2, ensure_ascii=False)
|
|
17
|
+
file_path.write_text(text, encoding="utf-8")
|
|
18
|
+
logger.debug(f"Saved {description} to {file_path}")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def load_json_file(file_path: Path, description: str) -> dict[str, Any] | list[Any]:
|
|
22
|
+
if not file_path.exists():
|
|
23
|
+
raise FileNotFoundError(f"{description} not found: {file_path}")
|
|
24
|
+
text = file_path.read_text(encoding="utf-8")
|
|
25
|
+
return json.loads(text)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def list_saved_sessions_in_dir(serialization_dir: Path) -> list[str]:
|
|
29
|
+
if not serialization_dir.exists():
|
|
30
|
+
return []
|
|
31
|
+
sessions = []
|
|
32
|
+
for item in serialization_dir.iterdir():
|
|
33
|
+
if item.is_dir() and (item / "metadata.json").exists():
|
|
34
|
+
sessions.append(item.name)
|
|
35
|
+
return sorted(sessions)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def delete_session_dir(session_dir: Path, session_id: str) -> bool:
|
|
39
|
+
if not session_dir.exists():
|
|
40
|
+
logger.warning(f"Session directory not found: {session_dir}")
|
|
41
|
+
return False
|
|
42
|
+
try:
|
|
43
|
+
shutil.rmtree(session_dir)
|
|
44
|
+
logger.info(f"✅ Deleted session: {session_id}")
|
|
45
|
+
return True
|
|
46
|
+
except Exception as e:
|
|
47
|
+
logger.error(f"Failed to delete session {session_id}: {e}")
|
|
48
|
+
return False
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def ensure_session_dir_exists(session_dir: Path) -> None:
|
|
52
|
+
session_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def collect_status_data_from_managers(managers: list[Any]) -> list[dict[str, Any]]:
|
|
5
|
+
statuses = []
|
|
6
|
+
for manager in managers:
|
|
7
|
+
tabs = manager.layout_config["layoutTabs"]
|
|
8
|
+
if hasattr(manager, "process_monitor"):
|
|
9
|
+
a_status = manager.process_monitor.check_all_commands_status(tabs)
|
|
10
|
+
else:
|
|
11
|
+
a_status = manager.check_all_commands_status()
|
|
12
|
+
statuses.append(a_status)
|
|
13
|
+
return statuses
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def flatten_status_data(statuses: list[dict[str, dict[str, Any]]]) -> list[dict[str, Any]]:
|
|
17
|
+
keys = []
|
|
18
|
+
values = []
|
|
19
|
+
for item in statuses:
|
|
20
|
+
keys.extend(item.keys())
|
|
21
|
+
values.extend(item.values())
|
|
22
|
+
|
|
23
|
+
status_data = []
|
|
24
|
+
for i, key in enumerate(keys):
|
|
25
|
+
if i < len(values):
|
|
26
|
+
status_data.append({"tabName": key, "status": values[i]})
|
|
27
|
+
return status_data
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def check_if_all_stopped(status_data: list[dict[str, Any]]) -> bool:
|
|
31
|
+
running_count = sum(1 for item in status_data if item.get("status", {}).get("running", False))
|
|
32
|
+
return running_count == 0
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def print_status_table(status_data: list[dict[str, Any]]) -> None:
|
|
36
|
+
for item in status_data:
|
|
37
|
+
print(f"Tab: {item['tabName']}, Status: {item['status']}")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def collect_session_statuses(managers: list[Any]) -> list[dict[str, Any]]:
|
|
41
|
+
statuses = []
|
|
42
|
+
for manager in managers:
|
|
43
|
+
a_status = manager.session_manager.check_wt_session_status()
|
|
44
|
+
statuses.append(a_status)
|
|
45
|
+
return statuses
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def print_session_statuses(statuses: list[dict[str, Any]]) -> None:
|
|
49
|
+
for i, status in enumerate(statuses):
|
|
50
|
+
print(f"Manager {i}: {status}")
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from typing import Any, Optional
|
|
2
|
+
from rich.console import Console
|
|
3
|
+
|
|
4
|
+
console = Console()
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def print_global_summary(global_summary: dict[str, Any], title: str) -> None:
|
|
8
|
+
print("=" * 80)
|
|
9
|
+
print(f"🖥️ {title}")
|
|
10
|
+
print("=" * 80)
|
|
11
|
+
print("🌐 GLOBAL SUMMARY:")
|
|
12
|
+
print(f" Total sessions: {global_summary['total_sessions']}")
|
|
13
|
+
print(f" Healthy sessions: {global_summary['healthy_sessions']}")
|
|
14
|
+
print(f" Total commands: {global_summary['total_commands']}")
|
|
15
|
+
print(f" Running commands: {global_summary['running_commands']}")
|
|
16
|
+
if "remote_machines" in global_summary:
|
|
17
|
+
print(f" Remote machines: {len(global_summary['remote_machines'])}")
|
|
18
|
+
print(f" All healthy: {'✅' if global_summary['all_sessions_healthy'] else '❌'}")
|
|
19
|
+
print()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def print_session_health_status(wt_status: dict[str, Any], remote_name: Optional[str] = None) -> None:
|
|
23
|
+
location_str = f" on {remote_name}" if remote_name else ""
|
|
24
|
+
if wt_status.get("wt_running", False):
|
|
25
|
+
if wt_status.get("session_exists", False):
|
|
26
|
+
session_windows = wt_status.get("session_windows", [])
|
|
27
|
+
all_windows = wt_status.get("all_windows", [])
|
|
28
|
+
print(f"✅ Windows Terminal is running{location_str}")
|
|
29
|
+
print(f" Session windows: {len(session_windows)}")
|
|
30
|
+
print(f" Total WT windows: {len(all_windows)}")
|
|
31
|
+
else:
|
|
32
|
+
print(f"⚠️ Windows Terminal is running but no session windows found{location_str}")
|
|
33
|
+
else:
|
|
34
|
+
print(f"❌ Windows Terminal issue{location_str}: {wt_status.get('error', 'Unknown error')}")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def print_commands_status(commands_status: dict[str, dict[str, Any]], summary: dict[str, int]) -> None:
|
|
38
|
+
print(f" Commands ({summary['running_commands']}/{summary['total_commands']} running):")
|
|
39
|
+
for tab_name, cmd_status in commands_status.items():
|
|
40
|
+
status_icon = "✅" if cmd_status.get("running", False) else "❌"
|
|
41
|
+
cmd_text = cmd_status.get("command", "Unknown")[:50]
|
|
42
|
+
if len(cmd_status.get("command", "")) > 50:
|
|
43
|
+
cmd_text += "..."
|
|
44
|
+
console.print(f" {status_icon} {tab_name}: {cmd_text}")
|
|
45
|
+
if cmd_status.get("processes"):
|
|
46
|
+
for proc in cmd_status["processes"][:2]:
|
|
47
|
+
console.print(f" [dim]└─[/dim] PID {proc.get('pid', 'Unknown')}: {proc.get('name', 'Unknown')}")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def calculate_session_summary(commands_status: dict[str, dict[str, Any]], session_healthy: bool) -> dict[str, Any]:
|
|
51
|
+
running_count = sum(1 for status in commands_status.values() if status.get("running", False))
|
|
52
|
+
total_count = len(commands_status)
|
|
53
|
+
return {"total_commands": total_count, "running_commands": running_count, "stopped_commands": total_count - running_count, "session_healthy": session_healthy}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def calculate_global_summary_from_status(all_status: dict[str, dict[str, Any]], include_remote_machines: bool = False) -> dict[str, Any]:
|
|
57
|
+
total_sessions = len(all_status)
|
|
58
|
+
healthy_sessions = sum(1 for status in all_status.values() if status.get("summary", {}).get("session_healthy", False))
|
|
59
|
+
total_commands = sum(status.get("summary", {}).get("total_commands", 0) for status in all_status.values())
|
|
60
|
+
total_running = sum(status.get("summary", {}).get("running_commands", 0) for status in all_status.values())
|
|
61
|
+
|
|
62
|
+
result: dict[str, Any] = {
|
|
63
|
+
"total_sessions": total_sessions,
|
|
64
|
+
"healthy_sessions": healthy_sessions,
|
|
65
|
+
"unhealthy_sessions": total_sessions - healthy_sessions,
|
|
66
|
+
"total_commands": total_commands,
|
|
67
|
+
"running_commands": total_running,
|
|
68
|
+
"stopped_commands": total_commands - total_running,
|
|
69
|
+
"all_sessions_healthy": healthy_sessions == total_sessions,
|
|
70
|
+
"all_commands_running": total_running == total_commands,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if include_remote_machines:
|
|
74
|
+
result["remote_machines"] = list(set(status.get("remote_name", "") for status in all_status.values() if "remote_name" in status))
|
|
75
|
+
|
|
76
|
+
return result
|