crackerjack 0.31.10__py3-none-any.whl → 0.31.12__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 crackerjack might be problematic. Click here for more details.
- crackerjack/CLAUDE.md +288 -705
- crackerjack/__main__.py +22 -8
- crackerjack/agents/__init__.py +0 -3
- crackerjack/agents/architect_agent.py +0 -43
- crackerjack/agents/base.py +1 -9
- crackerjack/agents/coordinator.py +2 -148
- crackerjack/agents/documentation_agent.py +109 -81
- crackerjack/agents/dry_agent.py +122 -97
- crackerjack/agents/formatting_agent.py +3 -16
- crackerjack/agents/import_optimization_agent.py +1174 -130
- crackerjack/agents/performance_agent.py +956 -188
- crackerjack/agents/performance_helpers.py +229 -0
- crackerjack/agents/proactive_agent.py +1 -48
- crackerjack/agents/refactoring_agent.py +516 -246
- crackerjack/agents/refactoring_helpers.py +282 -0
- crackerjack/agents/security_agent.py +393 -90
- crackerjack/agents/test_creation_agent.py +1776 -120
- crackerjack/agents/test_specialist_agent.py +59 -15
- crackerjack/agents/tracker.py +0 -102
- crackerjack/api.py +145 -37
- crackerjack/cli/handlers.py +48 -30
- crackerjack/cli/interactive.py +11 -11
- crackerjack/cli/options.py +66 -4
- crackerjack/code_cleaner.py +808 -148
- crackerjack/config/global_lock_config.py +110 -0
- crackerjack/config/hooks.py +43 -64
- crackerjack/core/async_workflow_orchestrator.py +247 -97
- crackerjack/core/autofix_coordinator.py +192 -109
- crackerjack/core/enhanced_container.py +46 -63
- crackerjack/core/file_lifecycle.py +549 -0
- crackerjack/core/performance.py +9 -8
- crackerjack/core/performance_monitor.py +395 -0
- crackerjack/core/phase_coordinator.py +281 -94
- crackerjack/core/proactive_workflow.py +9 -58
- crackerjack/core/resource_manager.py +501 -0
- crackerjack/core/service_watchdog.py +490 -0
- crackerjack/core/session_coordinator.py +4 -8
- crackerjack/core/timeout_manager.py +504 -0
- crackerjack/core/websocket_lifecycle.py +475 -0
- crackerjack/core/workflow_orchestrator.py +343 -209
- crackerjack/dynamic_config.py +47 -6
- crackerjack/errors.py +3 -4
- crackerjack/executors/async_hook_executor.py +63 -13
- crackerjack/executors/cached_hook_executor.py +14 -14
- crackerjack/executors/hook_executor.py +100 -37
- crackerjack/executors/hook_lock_manager.py +856 -0
- crackerjack/executors/individual_hook_executor.py +120 -86
- crackerjack/intelligence/__init__.py +0 -7
- crackerjack/intelligence/adaptive_learning.py +13 -86
- crackerjack/intelligence/agent_orchestrator.py +15 -78
- crackerjack/intelligence/agent_registry.py +12 -59
- crackerjack/intelligence/agent_selector.py +31 -92
- crackerjack/intelligence/integration.py +1 -41
- crackerjack/interactive.py +9 -9
- crackerjack/managers/async_hook_manager.py +25 -8
- crackerjack/managers/hook_manager.py +9 -9
- crackerjack/managers/publish_manager.py +57 -59
- crackerjack/managers/test_command_builder.py +6 -36
- crackerjack/managers/test_executor.py +9 -61
- crackerjack/managers/test_manager.py +17 -63
- crackerjack/managers/test_manager_backup.py +77 -127
- crackerjack/managers/test_progress.py +4 -23
- crackerjack/mcp/cache.py +5 -12
- crackerjack/mcp/client_runner.py +10 -10
- crackerjack/mcp/context.py +64 -6
- crackerjack/mcp/dashboard.py +14 -11
- crackerjack/mcp/enhanced_progress_monitor.py +55 -55
- crackerjack/mcp/file_monitor.py +72 -42
- crackerjack/mcp/progress_components.py +103 -84
- crackerjack/mcp/progress_monitor.py +122 -49
- crackerjack/mcp/rate_limiter.py +12 -12
- crackerjack/mcp/server_core.py +16 -22
- crackerjack/mcp/service_watchdog.py +26 -26
- crackerjack/mcp/state.py +15 -0
- crackerjack/mcp/tools/core_tools.py +95 -39
- crackerjack/mcp/tools/error_analyzer.py +6 -32
- crackerjack/mcp/tools/execution_tools.py +1 -56
- crackerjack/mcp/tools/execution_tools_backup.py +35 -131
- crackerjack/mcp/tools/intelligence_tool_registry.py +0 -36
- crackerjack/mcp/tools/intelligence_tools.py +2 -55
- crackerjack/mcp/tools/monitoring_tools.py +308 -145
- crackerjack/mcp/tools/proactive_tools.py +12 -42
- crackerjack/mcp/tools/progress_tools.py +23 -15
- crackerjack/mcp/tools/utility_tools.py +3 -40
- crackerjack/mcp/tools/workflow_executor.py +40 -60
- crackerjack/mcp/websocket/app.py +0 -3
- crackerjack/mcp/websocket/endpoints.py +206 -268
- crackerjack/mcp/websocket/jobs.py +213 -66
- crackerjack/mcp/websocket/server.py +84 -6
- crackerjack/mcp/websocket/websocket_handler.py +137 -29
- crackerjack/models/config_adapter.py +3 -16
- crackerjack/models/protocols.py +162 -3
- crackerjack/models/resource_protocols.py +454 -0
- crackerjack/models/task.py +3 -3
- crackerjack/monitoring/__init__.py +0 -0
- crackerjack/monitoring/ai_agent_watchdog.py +25 -71
- crackerjack/monitoring/regression_prevention.py +28 -87
- crackerjack/orchestration/advanced_orchestrator.py +44 -78
- crackerjack/orchestration/coverage_improvement.py +10 -60
- crackerjack/orchestration/execution_strategies.py +16 -16
- crackerjack/orchestration/test_progress_streamer.py +61 -53
- crackerjack/plugins/base.py +1 -1
- crackerjack/plugins/managers.py +22 -20
- crackerjack/py313.py +65 -21
- crackerjack/services/backup_service.py +467 -0
- crackerjack/services/bounded_status_operations.py +627 -0
- crackerjack/services/cache.py +7 -9
- crackerjack/services/config.py +35 -52
- crackerjack/services/config_integrity.py +5 -16
- crackerjack/services/config_merge.py +542 -0
- crackerjack/services/contextual_ai_assistant.py +17 -19
- crackerjack/services/coverage_ratchet.py +44 -73
- crackerjack/services/debug.py +25 -39
- crackerjack/services/dependency_monitor.py +52 -50
- crackerjack/services/enhanced_filesystem.py +14 -11
- crackerjack/services/file_hasher.py +1 -1
- crackerjack/services/filesystem.py +1 -12
- crackerjack/services/git.py +71 -47
- crackerjack/services/health_metrics.py +31 -27
- crackerjack/services/initialization.py +276 -428
- crackerjack/services/input_validator.py +760 -0
- crackerjack/services/log_manager.py +16 -16
- crackerjack/services/logging.py +7 -6
- crackerjack/services/metrics.py +43 -43
- crackerjack/services/pattern_cache.py +2 -31
- crackerjack/services/pattern_detector.py +26 -63
- crackerjack/services/performance_benchmarks.py +20 -45
- crackerjack/services/regex_patterns.py +2887 -0
- crackerjack/services/regex_utils.py +537 -0
- crackerjack/services/secure_path_utils.py +683 -0
- crackerjack/services/secure_status_formatter.py +534 -0
- crackerjack/services/secure_subprocess.py +605 -0
- crackerjack/services/security.py +47 -10
- crackerjack/services/security_logger.py +492 -0
- crackerjack/services/server_manager.py +109 -50
- crackerjack/services/smart_scheduling.py +8 -25
- crackerjack/services/status_authentication.py +603 -0
- crackerjack/services/status_security_manager.py +442 -0
- crackerjack/services/thread_safe_status_collector.py +546 -0
- crackerjack/services/tool_version_service.py +1 -23
- crackerjack/services/unified_config.py +36 -58
- crackerjack/services/validation_rate_limiter.py +269 -0
- crackerjack/services/version_checker.py +9 -40
- crackerjack/services/websocket_resource_limiter.py +572 -0
- crackerjack/slash_commands/__init__.py +52 -2
- crackerjack/tools/__init__.py +0 -0
- crackerjack/tools/validate_input_validator_patterns.py +262 -0
- crackerjack/tools/validate_regex_patterns.py +198 -0
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.12.dist-info}/METADATA +197 -12
- crackerjack-0.31.12.dist-info/RECORD +178 -0
- crackerjack/cli/facade.py +0 -104
- crackerjack-0.31.10.dist-info/RECORD +0 -149
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.12.dist-info}/WHEEL +0 -0
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.12.dist-info}/entry_points.txt +0 -0
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.12.dist-info}/licenses/LICENSE +0 -0
|
@@ -4,11 +4,14 @@ from pathlib import Path
|
|
|
4
4
|
from fastapi import FastAPI
|
|
5
5
|
from fastapi.responses import HTMLResponse
|
|
6
6
|
|
|
7
|
+
from ...services.secure_status_formatter import (
|
|
8
|
+
format_secure_status,
|
|
9
|
+
get_secure_status_formatter,
|
|
10
|
+
)
|
|
7
11
|
from .jobs import JobManager
|
|
8
12
|
|
|
9
13
|
|
|
10
14
|
def _build_job_list(job_manager: JobManager, progress_dir: Path) -> list[dict]:
|
|
11
|
-
"""Build list of jobs from progress files."""
|
|
12
15
|
jobs = []
|
|
13
16
|
if not progress_dir.exists():
|
|
14
17
|
return jobs
|
|
@@ -32,13 +35,12 @@ def _build_job_list(job_manager: JobManager, progress_dir: Path) -> list[dict]:
|
|
|
32
35
|
|
|
33
36
|
|
|
34
37
|
def _build_status_response(job_manager: JobManager, jobs: list[dict]) -> dict:
|
|
35
|
-
"""Build the status response dictionary."""
|
|
36
38
|
return {
|
|
37
39
|
"status": "running",
|
|
38
40
|
"message": "Crackerjack WebSocket Server",
|
|
39
41
|
"active_connections": len(job_manager.active_connections),
|
|
40
42
|
"jobs": jobs[:10],
|
|
41
|
-
"websocket_url": "ws://
|
|
43
|
+
"websocket_url": "ws://[INTERNAL_URL]/ws/progress/{job_id}",
|
|
42
44
|
"endpoints": {
|
|
43
45
|
"status": "/",
|
|
44
46
|
"latest_job": "/latest",
|
|
@@ -49,8 +51,150 @@ def _build_status_response(job_manager: JobManager, jobs: list[dict]) -> dict:
|
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
|
|
54
|
+
def _get_monitor_html(job_id: str) -> str:
|
|
55
|
+
"""Generate secure HTML for job monitoring page."""
|
|
56
|
+
return f"""
|
|
57
|
+
<!DOCTYPE html>
|
|
58
|
+
<html>
|
|
59
|
+
<head>
|
|
60
|
+
<title>Job Monitor - {job_id}</title>
|
|
61
|
+
<meta charset="UTF-8">
|
|
62
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
63
|
+
<style>
|
|
64
|
+
body {{
|
|
65
|
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
66
|
+
margin: 0;
|
|
67
|
+
padding: 20px;
|
|
68
|
+
background-color: #f5f5f5;
|
|
69
|
+
}}
|
|
70
|
+
.container {{
|
|
71
|
+
max-width: 800px;
|
|
72
|
+
margin: 0 auto;
|
|
73
|
+
background: white;
|
|
74
|
+
padding: 30px;
|
|
75
|
+
border-radius: 10px;
|
|
76
|
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
77
|
+
}}
|
|
78
|
+
.header {{
|
|
79
|
+
text-align: center;
|
|
80
|
+
margin-bottom: 30px;
|
|
81
|
+
padding-bottom: 20px;
|
|
82
|
+
border-bottom: 2px solid #eee;
|
|
83
|
+
}}
|
|
84
|
+
.job-id {{
|
|
85
|
+
font-family: 'Courier New', monospace;
|
|
86
|
+
background: #f0f0f0;
|
|
87
|
+
padding: 5px 10px;
|
|
88
|
+
border-radius: 5px;
|
|
89
|
+
color: #666;
|
|
90
|
+
}}
|
|
91
|
+
.status {{
|
|
92
|
+
margin: 20px 0;
|
|
93
|
+
padding: 15px;
|
|
94
|
+
border-radius: 5px;
|
|
95
|
+
font-weight: bold;
|
|
96
|
+
}}
|
|
97
|
+
.status.running {{ background-color: #e3f2fd; color: #1976d2; }}
|
|
98
|
+
.status.completed {{ background-color: #e8f5e8; color: #388e3c; }}
|
|
99
|
+
.status.failed {{ background-color: #ffebee; color: #d32f2f; }}
|
|
100
|
+
.status.connecting {{ background-color: #fff3e0; color: #f57c00; }}
|
|
101
|
+
.log {{
|
|
102
|
+
margin: 20px 0;
|
|
103
|
+
padding: 15px;
|
|
104
|
+
background: #1e1e1e;
|
|
105
|
+
color: #fff;
|
|
106
|
+
font-family: 'Courier New', monospace;
|
|
107
|
+
border-radius: 5px;
|
|
108
|
+
max-height: 300px;
|
|
109
|
+
overflow-y: auto;
|
|
110
|
+
font-size: 12px;
|
|
111
|
+
line-height: 1.4;
|
|
112
|
+
}}
|
|
113
|
+
.connection-status {{
|
|
114
|
+
position: fixed;
|
|
115
|
+
top: 10px;
|
|
116
|
+
right: 10px;
|
|
117
|
+
padding: 5px 10px;
|
|
118
|
+
border-radius: 15px;
|
|
119
|
+
font-size: 12px;
|
|
120
|
+
font-weight: bold;
|
|
121
|
+
}}
|
|
122
|
+
.connected {{ background: #4caf50; color: white; }}
|
|
123
|
+
.disconnected {{ background: #f44336; color: white; }}
|
|
124
|
+
</style>
|
|
125
|
+
</head>
|
|
126
|
+
<body>
|
|
127
|
+
<div class="connection-status" id="connectionStatus">Connecting...</div>
|
|
128
|
+
<div class="container">
|
|
129
|
+
<div class="header">
|
|
130
|
+
<h1>🚀 Crackerjack Job Monitor</h1>
|
|
131
|
+
<p>Job ID: <span class="job-id">{job_id}</span></p>
|
|
132
|
+
</div>
|
|
133
|
+
<div class="status connecting" id="status">
|
|
134
|
+
Status: Connecting to job...
|
|
135
|
+
</div>
|
|
136
|
+
<div class="log" id="log">
|
|
137
|
+
<div>Connecting to WebSocket...</div>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
<script>
|
|
142
|
+
const jobId = '{job_id}';
|
|
143
|
+
const wsUrl = `ws://[INTERNAL_URL]/ws/progress/${{jobId}}`;
|
|
144
|
+
let ws = null;
|
|
145
|
+
|
|
146
|
+
function updateConnectionStatus(status) {{
|
|
147
|
+
const elem = document.getElementById('connectionStatus');
|
|
148
|
+
elem.className = 'connection-status ' + status;
|
|
149
|
+
elem.textContent = status === 'connected' ? '🟢 Connected' : '🔴 Disconnected';
|
|
150
|
+
}}
|
|
151
|
+
|
|
152
|
+
function addLogEntry(message) {{
|
|
153
|
+
const log = document.getElementById('log');
|
|
154
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
155
|
+
const entry = document.createElement('div');
|
|
156
|
+
entry.textContent = `[${{timestamp}}] ${{message}}`;
|
|
157
|
+
log.appendChild(entry);
|
|
158
|
+
log.scrollTop = log.scrollHeight;
|
|
159
|
+
}}
|
|
160
|
+
|
|
161
|
+
function connect() {{
|
|
162
|
+
ws = new WebSocket(wsUrl);
|
|
163
|
+
|
|
164
|
+
ws.onopen = function() {{
|
|
165
|
+
updateConnectionStatus('connected');
|
|
166
|
+
addLogEntry('Connected to WebSocket');
|
|
167
|
+
}};
|
|
168
|
+
|
|
169
|
+
ws.onmessage = function(event) {{
|
|
170
|
+
try {{
|
|
171
|
+
const data = JSON.parse(event.data);
|
|
172
|
+
addLogEntry(data.message || 'Progress update');
|
|
173
|
+
}} catch (e) {{
|
|
174
|
+
addLogEntry('Received: ' + event.data);
|
|
175
|
+
}}
|
|
176
|
+
}};
|
|
177
|
+
|
|
178
|
+
ws.onclose = function() {{
|
|
179
|
+
updateConnectionStatus('disconnected');
|
|
180
|
+
addLogEntry('WebSocket connection closed');
|
|
181
|
+
}};
|
|
182
|
+
|
|
183
|
+
ws.onerror = function() {{
|
|
184
|
+
updateConnectionStatus('disconnected');
|
|
185
|
+
addLogEntry('WebSocket error occurred');
|
|
186
|
+
}};
|
|
187
|
+
}}
|
|
188
|
+
|
|
189
|
+
// Start connection
|
|
190
|
+
connect();
|
|
191
|
+
</script>
|
|
192
|
+
</body>
|
|
193
|
+
</html>
|
|
194
|
+
"""
|
|
195
|
+
|
|
196
|
+
|
|
52
197
|
def _get_test_html() -> str:
|
|
53
|
-
"""Generate HTML content for test page."""
|
|
54
198
|
return """
|
|
55
199
|
<!DOCTYPE html>
|
|
56
200
|
<html>
|
|
@@ -78,7 +222,7 @@ def _get_test_html() -> str:
|
|
|
78
222
|
border-radius: 5px;
|
|
79
223
|
}
|
|
80
224
|
button {
|
|
81
|
-
background: #
|
|
225
|
+
background: #007cba;
|
|
82
226
|
color: white;
|
|
83
227
|
border: none;
|
|
84
228
|
padding: 10px 20px;
|
|
@@ -86,7 +230,9 @@ def _get_test_html() -> str:
|
|
|
86
230
|
cursor: pointer;
|
|
87
231
|
margin: 5px;
|
|
88
232
|
}
|
|
89
|
-
button:hover {
|
|
233
|
+
button:hover {
|
|
234
|
+
background: #005a8b;
|
|
235
|
+
}
|
|
90
236
|
input[type="text"] {
|
|
91
237
|
padding: 8px;
|
|
92
238
|
border: 1px solid #ddd;
|
|
@@ -100,12 +246,21 @@ def _get_test_html() -> str:
|
|
|
100
246
|
border-radius: 5px;
|
|
101
247
|
font-weight: bold;
|
|
102
248
|
}
|
|
103
|
-
.success {
|
|
104
|
-
|
|
105
|
-
|
|
249
|
+
.success {
|
|
250
|
+
background: #d4edda;
|
|
251
|
+
color: #155724;
|
|
252
|
+
}
|
|
253
|
+
.error {
|
|
254
|
+
background: #f8d7da;
|
|
255
|
+
color: #721c24;
|
|
256
|
+
}
|
|
257
|
+
.info {
|
|
258
|
+
background: #d1ecf1;
|
|
259
|
+
color: #0c5460;
|
|
260
|
+
}
|
|
106
261
|
#log {
|
|
107
262
|
background: #1e1e1e;
|
|
108
|
-
color: #
|
|
263
|
+
color: #fff;
|
|
109
264
|
padding: 15px;
|
|
110
265
|
border-radius: 5px;
|
|
111
266
|
font-family: 'Courier New', monospace;
|
|
@@ -260,246 +415,6 @@ def _get_test_html() -> str:
|
|
|
260
415
|
"""
|
|
261
416
|
|
|
262
417
|
|
|
263
|
-
def _get_monitor_html(job_id: str) -> str:
|
|
264
|
-
"""Generate HTML content for job monitor page."""
|
|
265
|
-
return f"""
|
|
266
|
-
<!DOCTYPE html>
|
|
267
|
-
<html>
|
|
268
|
-
<head>
|
|
269
|
-
<title>Job Monitor - {job_id}</title>
|
|
270
|
-
<meta charset="UTF-8">
|
|
271
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
272
|
-
<style>
|
|
273
|
-
body {{
|
|
274
|
-
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
275
|
-
margin: 0;
|
|
276
|
-
padding: 20px;
|
|
277
|
-
background-color: #f5f5f5;
|
|
278
|
-
}}
|
|
279
|
-
.container {{
|
|
280
|
-
max-width: 800px;
|
|
281
|
-
margin: 0 auto;
|
|
282
|
-
background: white;
|
|
283
|
-
padding: 30px;
|
|
284
|
-
border-radius: 10px;
|
|
285
|
-
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
286
|
-
}}
|
|
287
|
-
.header {{
|
|
288
|
-
text-align: center;
|
|
289
|
-
margin-bottom: 30px;
|
|
290
|
-
padding-bottom: 20px;
|
|
291
|
-
border-bottom: 2px solid #007acc;
|
|
292
|
-
}}
|
|
293
|
-
.job-id {{
|
|
294
|
-
font-family: 'Courier New', monospace;
|
|
295
|
-
background: #f0f8ff;
|
|
296
|
-
padding: 5px 10px;
|
|
297
|
-
border-radius: 5px;
|
|
298
|
-
color: #007acc;
|
|
299
|
-
}}
|
|
300
|
-
.status {{
|
|
301
|
-
margin: 20px 0;
|
|
302
|
-
padding: 15px;
|
|
303
|
-
border-radius: 5px;
|
|
304
|
-
font-weight: bold;
|
|
305
|
-
}}
|
|
306
|
-
.status.running {{ background-color: #fff3cd; color: #856404; }}
|
|
307
|
-
.status.completed {{ background-color: #d4edda; color: #155724; }}
|
|
308
|
-
.status.failed {{ background-color: #f8d7da; color: #721c24; }}
|
|
309
|
-
.status.connecting {{ background-color: #cce7ff; color: #004085; }}
|
|
310
|
-
.progress-bar {{
|
|
311
|
-
width: 100%;
|
|
312
|
-
height: 20px;
|
|
313
|
-
background-color: #e9ecef;
|
|
314
|
-
border-radius: 10px;
|
|
315
|
-
overflow: hidden;
|
|
316
|
-
margin: 10px 0;
|
|
317
|
-
}}
|
|
318
|
-
.progress-fill {{
|
|
319
|
-
height: 100%;
|
|
320
|
-
background-color: #007acc;
|
|
321
|
-
width: 0%;
|
|
322
|
-
transition: width 0.3s ease;
|
|
323
|
-
}}
|
|
324
|
-
.details {{
|
|
325
|
-
margin: 20px 0;
|
|
326
|
-
padding: 15px;
|
|
327
|
-
background: #f8f9fa;
|
|
328
|
-
border-radius: 5px;
|
|
329
|
-
border-left: 4px solid #007acc;
|
|
330
|
-
}}
|
|
331
|
-
.log {{
|
|
332
|
-
margin: 20px 0;
|
|
333
|
-
padding: 15px;
|
|
334
|
-
background: #1e1e1e;
|
|
335
|
-
color: #ffffff;
|
|
336
|
-
font-family: 'Courier New', monospace;
|
|
337
|
-
border-radius: 5px;
|
|
338
|
-
max-height: 300px;
|
|
339
|
-
overflow-y: auto;
|
|
340
|
-
font-size: 12px;
|
|
341
|
-
line-height: 1.4;
|
|
342
|
-
}}
|
|
343
|
-
.connection-status {{
|
|
344
|
-
position: fixed;
|
|
345
|
-
top: 10px;
|
|
346
|
-
right: 10px;
|
|
347
|
-
padding: 5px 10px;
|
|
348
|
-
border-radius: 15px;
|
|
349
|
-
font-size: 12px;
|
|
350
|
-
font-weight: bold;
|
|
351
|
-
}}
|
|
352
|
-
.connected {{ background: #d4edda; color: #155724; }}
|
|
353
|
-
.disconnected {{ background: #f8d7da; color: #721c24; }}
|
|
354
|
-
.reconnecting {{ background: #fff3cd; color: #856404; }}
|
|
355
|
-
</style>
|
|
356
|
-
</head>
|
|
357
|
-
<body>
|
|
358
|
-
<div class="connection-status" id="connectionStatus">Connecting...</div>
|
|
359
|
-
|
|
360
|
-
<div class="container">
|
|
361
|
-
<div class="header">
|
|
362
|
-
<h1>🚀 Crackerjack Job Monitor</h1>
|
|
363
|
-
<p>Job ID: <span class="job-id">{job_id}</span></p>
|
|
364
|
-
</div>
|
|
365
|
-
|
|
366
|
-
<div class="status connecting" id="status">
|
|
367
|
-
Status: Connecting to job...
|
|
368
|
-
</div>
|
|
369
|
-
|
|
370
|
-
<div class="progress-bar">
|
|
371
|
-
<div class="progress-fill" id="progressFill"></div>
|
|
372
|
-
</div>
|
|
373
|
-
<div id="progressText">Progress: 0%</div>
|
|
374
|
-
|
|
375
|
-
<div class="details" id="details">
|
|
376
|
-
<strong>Current Stage:</strong> <span id="currentStage">Initializing</span><br>
|
|
377
|
-
<strong>Iteration:</strong> <span id="iteration">0</span> / <span id="maxIterations">10</span><br>
|
|
378
|
-
<strong>Message:</strong> <span id="message">Connecting...</span>
|
|
379
|
-
</div>
|
|
380
|
-
|
|
381
|
-
<div class="log" id="log">
|
|
382
|
-
<div>Connecting to WebSocket...</div>
|
|
383
|
-
</div>
|
|
384
|
-
</div>
|
|
385
|
-
|
|
386
|
-
<script>
|
|
387
|
-
const jobId = '{job_id}';
|
|
388
|
-
const wsUrl = `ws://localhost:8675/ws/progress/${{jobId}}`;
|
|
389
|
-
let ws = null;
|
|
390
|
-
let reconnectAttempts = 0;
|
|
391
|
-
const maxReconnectAttempts = 10;
|
|
392
|
-
|
|
393
|
-
const elements = {{
|
|
394
|
-
status: document.getElementById('status'),
|
|
395
|
-
progressFill: document.getElementById('progressFill'),
|
|
396
|
-
progressText: document.getElementById('progressText'),
|
|
397
|
-
currentStage: document.getElementById('currentStage'),
|
|
398
|
-
iteration: document.getElementById('iteration'),
|
|
399
|
-
maxIterations: document.getElementById('maxIterations'),
|
|
400
|
-
message: document.getElementById('message'),
|
|
401
|
-
log: document.getElementById('log'),
|
|
402
|
-
connectionStatus: document.getElementById('connectionStatus')
|
|
403
|
-
}};
|
|
404
|
-
|
|
405
|
-
function updateConnectionStatus(status) {{
|
|
406
|
-
elements.connectionStatus.className = 'connection-status ' + status;
|
|
407
|
-
elements.connectionStatus.textContent = {{
|
|
408
|
-
'connected': '🟢 Connected',
|
|
409
|
-
'disconnected': '🔴 Disconnected',
|
|
410
|
-
'reconnecting': '🟡 Reconnecting...'
|
|
411
|
-
}}[status] || '⚪ Unknown';
|
|
412
|
-
}}
|
|
413
|
-
|
|
414
|
-
function addLogEntry(message, type = 'info') {{
|
|
415
|
-
const timestamp = new Date().toLocaleTimeString();
|
|
416
|
-
const logEntry = document.createElement('div');
|
|
417
|
-
logEntry.textContent = `[${{timestamp}}] ${{message}}`;
|
|
418
|
-
if (type === 'error') logEntry.style.color = '#ff6b6b';
|
|
419
|
-
if (type === 'success') logEntry.style.color = '#51cf66';
|
|
420
|
-
elements.log.appendChild(logEntry);
|
|
421
|
-
elements.log.scrollTop = elements.log.scrollHeight;
|
|
422
|
-
}}
|
|
423
|
-
|
|
424
|
-
function updateProgress(data) {{
|
|
425
|
-
const progress = data.overall_progress || 0;
|
|
426
|
-
const status = data.status || 'unknown';
|
|
427
|
-
|
|
428
|
-
elements.progressFill.style.width = progress + '%';
|
|
429
|
-
elements.progressText.textContent = `Progress: ${{progress}}%`;
|
|
430
|
-
|
|
431
|
-
elements.status.textContent = `Status: ${{status}}`;
|
|
432
|
-
elements.status.className = 'status ' + status.toLowerCase();
|
|
433
|
-
|
|
434
|
-
elements.currentStage.textContent = data.current_stage || 'Unknown';
|
|
435
|
-
elements.iteration.textContent = data.iteration || 0;
|
|
436
|
-
elements.maxIterations.textContent = data.max_iterations || 10;
|
|
437
|
-
elements.message.textContent = data.message || 'No message';
|
|
438
|
-
|
|
439
|
-
addLogEntry(`${{data.current_stage || 'Unknown stage'}}: ${{data.message || 'No message'}}`,
|
|
440
|
-
status === 'failed' ? 'error' : status === 'completed' ? 'success' : 'info');
|
|
441
|
-
}}
|
|
442
|
-
|
|
443
|
-
function connect() {{
|
|
444
|
-
if (ws && ws.readyState === WebSocket.OPEN) return;
|
|
445
|
-
|
|
446
|
-
updateConnectionStatus('reconnecting');
|
|
447
|
-
addLogEntry('Connecting to WebSocket...');
|
|
448
|
-
|
|
449
|
-
ws = new WebSocket(wsUrl);
|
|
450
|
-
|
|
451
|
-
ws.onopen = function() {{
|
|
452
|
-
updateConnectionStatus('connected');
|
|
453
|
-
addLogEntry('Connected to WebSocket', 'success');
|
|
454
|
-
reconnectAttempts = 0;
|
|
455
|
-
}};
|
|
456
|
-
|
|
457
|
-
ws.onmessage = function(event) {{
|
|
458
|
-
try {{
|
|
459
|
-
const data = JSON.parse(event.data);
|
|
460
|
-
updateProgress(data);
|
|
461
|
-
}} catch (e) {{
|
|
462
|
-
addLogEntry('Error parsing message: ' + e.message, 'error');
|
|
463
|
-
}}
|
|
464
|
-
}};
|
|
465
|
-
|
|
466
|
-
ws.onclose = function() {{
|
|
467
|
-
updateConnectionStatus('disconnected');
|
|
468
|
-
addLogEntry('WebSocket connection closed');
|
|
469
|
-
if (reconnectAttempts < maxReconnectAttempts) {{
|
|
470
|
-
setTimeout(() => {{
|
|
471
|
-
reconnectAttempts++;
|
|
472
|
-
addLogEntry(`Attempting to reconnect (${{reconnectAttempts}}/${{maxReconnectAttempts}})...`);
|
|
473
|
-
connect();
|
|
474
|
-
}}, 2000);
|
|
475
|
-
}} else {{
|
|
476
|
-
addLogEntry('Maximum reconnection attempts reached', 'error');
|
|
477
|
-
}}
|
|
478
|
-
}};
|
|
479
|
-
|
|
480
|
-
ws.onerror = function(error) {{
|
|
481
|
-
addLogEntry('WebSocket error occurred', 'error');
|
|
482
|
-
updateConnectionStatus('disconnected');
|
|
483
|
-
}};
|
|
484
|
-
}}
|
|
485
|
-
|
|
486
|
-
// Start connection
|
|
487
|
-
connect();
|
|
488
|
-
|
|
489
|
-
// Periodically check connection health
|
|
490
|
-
setInterval(() => {{
|
|
491
|
-
if (!ws || ws.readyState !== WebSocket.OPEN) {{
|
|
492
|
-
if (reconnectAttempts < maxReconnectAttempts) {{
|
|
493
|
-
connect();
|
|
494
|
-
}}
|
|
495
|
-
}}
|
|
496
|
-
}}, 5000);
|
|
497
|
-
</script>
|
|
498
|
-
</body>
|
|
499
|
-
</html>
|
|
500
|
-
"""
|
|
501
|
-
|
|
502
|
-
|
|
503
418
|
def register_endpoints(
|
|
504
419
|
app: FastAPI,
|
|
505
420
|
job_manager: JobManager,
|
|
@@ -508,10 +423,25 @@ def register_endpoints(
|
|
|
508
423
|
@app.get("/")
|
|
509
424
|
async def get_status():
|
|
510
425
|
try:
|
|
426
|
+
# Build raw status response
|
|
511
427
|
jobs = _build_job_list(job_manager, progress_dir)
|
|
512
|
-
|
|
428
|
+
raw_status = _build_status_response(job_manager, jobs)
|
|
429
|
+
|
|
430
|
+
# Apply secure formatting
|
|
431
|
+
secure_status = format_secure_status(
|
|
432
|
+
raw_status,
|
|
433
|
+
project_root=progress_dir.parent,
|
|
434
|
+
user_context="websocket_client",
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
return secure_status
|
|
513
438
|
except Exception as e:
|
|
514
|
-
|
|
439
|
+
# Use secure error formatting
|
|
440
|
+
formatter = get_secure_status_formatter()
|
|
441
|
+
error_response = formatter.format_error_response(
|
|
442
|
+
str(e),
|
|
443
|
+
)
|
|
444
|
+
return error_response
|
|
515
445
|
|
|
516
446
|
@app.get("/latest")
|
|
517
447
|
async def get_latest_job():
|
|
@@ -519,31 +449,39 @@ def register_endpoints(
|
|
|
519
449
|
latest_job_id = job_manager.get_latest_job_id()
|
|
520
450
|
|
|
521
451
|
if not latest_job_id:
|
|
522
|
-
|
|
452
|
+
raw_response = {
|
|
523
453
|
"status": "no_jobs",
|
|
524
454
|
"message": "No jobs found",
|
|
525
455
|
"job_id": None,
|
|
526
456
|
"progress": None,
|
|
527
457
|
}
|
|
458
|
+
else:
|
|
459
|
+
progress_data = job_manager.get_job_progress(latest_job_id)
|
|
460
|
+
raw_response = {
|
|
461
|
+
"status": "success",
|
|
462
|
+
"message": f"Latest job: {latest_job_id}",
|
|
463
|
+
"job_id": latest_job_id,
|
|
464
|
+
"progress": progress_data,
|
|
465
|
+
"websocket_url": f"ws://[INTERNAL_URL]/ws/progress/{latest_job_id}",
|
|
466
|
+
"monitor_url": f"http://[INTERNAL_URL]/monitor/{latest_job_id}",
|
|
467
|
+
}
|
|
528
468
|
|
|
529
|
-
|
|
469
|
+
# Apply secure formatting
|
|
470
|
+
secure_response = format_secure_status(
|
|
471
|
+
raw_response,
|
|
472
|
+
project_root=progress_dir.parent,
|
|
473
|
+
user_context="websocket_client",
|
|
474
|
+
)
|
|
530
475
|
|
|
531
|
-
return
|
|
532
|
-
"status": "success",
|
|
533
|
-
"message": f"Latest job: {latest_job_id}",
|
|
534
|
-
"job_id": latest_job_id,
|
|
535
|
-
"progress": progress_data,
|
|
536
|
-
"websocket_url": f"ws://localhost:8675/ws/progress/{latest_job_id}",
|
|
537
|
-
"monitor_url": f"http://localhost:8675/monitor/{latest_job_id}",
|
|
538
|
-
}
|
|
476
|
+
return secure_response
|
|
539
477
|
|
|
540
478
|
except Exception as e:
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
"
|
|
545
|
-
|
|
546
|
-
|
|
479
|
+
# Use secure error formatting
|
|
480
|
+
formatter = get_secure_status_formatter()
|
|
481
|
+
error_response = formatter.format_error_response(
|
|
482
|
+
f"Failed to get latest job: {e}",
|
|
483
|
+
)
|
|
484
|
+
return error_response
|
|
547
485
|
|
|
548
486
|
@app.get("/monitor/{job_id}")
|
|
549
487
|
async def get_job_monitor_page(job_id: str):
|