crackerjack 0.30.3__py3-none-any.whl → 0.31.7__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 +1005 -0
- crackerjack/RULES.md +380 -0
- crackerjack/__init__.py +42 -13
- crackerjack/__main__.py +227 -299
- crackerjack/agents/__init__.py +41 -0
- crackerjack/agents/architect_agent.py +281 -0
- crackerjack/agents/base.py +170 -0
- crackerjack/agents/coordinator.py +512 -0
- crackerjack/agents/documentation_agent.py +498 -0
- crackerjack/agents/dry_agent.py +388 -0
- crackerjack/agents/formatting_agent.py +245 -0
- crackerjack/agents/import_optimization_agent.py +281 -0
- crackerjack/agents/performance_agent.py +669 -0
- crackerjack/agents/proactive_agent.py +104 -0
- crackerjack/agents/refactoring_agent.py +788 -0
- crackerjack/agents/security_agent.py +529 -0
- crackerjack/agents/test_creation_agent.py +657 -0
- crackerjack/agents/test_specialist_agent.py +486 -0
- crackerjack/agents/tracker.py +212 -0
- crackerjack/api.py +560 -0
- crackerjack/cli/__init__.py +24 -0
- crackerjack/cli/facade.py +104 -0
- crackerjack/cli/handlers.py +267 -0
- crackerjack/cli/interactive.py +471 -0
- crackerjack/cli/options.py +409 -0
- crackerjack/cli/utils.py +18 -0
- crackerjack/code_cleaner.py +618 -928
- crackerjack/config/__init__.py +19 -0
- crackerjack/config/hooks.py +218 -0
- crackerjack/core/__init__.py +0 -0
- crackerjack/core/async_workflow_orchestrator.py +406 -0
- crackerjack/core/autofix_coordinator.py +200 -0
- crackerjack/core/container.py +104 -0
- crackerjack/core/enhanced_container.py +542 -0
- crackerjack/core/performance.py +243 -0
- crackerjack/core/phase_coordinator.py +585 -0
- crackerjack/core/proactive_workflow.py +316 -0
- crackerjack/core/session_coordinator.py +289 -0
- crackerjack/core/workflow_orchestrator.py +826 -0
- crackerjack/dynamic_config.py +94 -103
- crackerjack/errors.py +263 -41
- crackerjack/executors/__init__.py +11 -0
- crackerjack/executors/async_hook_executor.py +431 -0
- crackerjack/executors/cached_hook_executor.py +242 -0
- crackerjack/executors/hook_executor.py +345 -0
- crackerjack/executors/individual_hook_executor.py +669 -0
- crackerjack/intelligence/__init__.py +44 -0
- crackerjack/intelligence/adaptive_learning.py +751 -0
- crackerjack/intelligence/agent_orchestrator.py +551 -0
- crackerjack/intelligence/agent_registry.py +414 -0
- crackerjack/intelligence/agent_selector.py +502 -0
- crackerjack/intelligence/integration.py +290 -0
- crackerjack/interactive.py +576 -315
- crackerjack/managers/__init__.py +11 -0
- crackerjack/managers/async_hook_manager.py +135 -0
- crackerjack/managers/hook_manager.py +137 -0
- crackerjack/managers/publish_manager.py +433 -0
- crackerjack/managers/test_command_builder.py +151 -0
- crackerjack/managers/test_executor.py +443 -0
- crackerjack/managers/test_manager.py +258 -0
- crackerjack/managers/test_manager_backup.py +1124 -0
- crackerjack/managers/test_progress.py +114 -0
- crackerjack/mcp/__init__.py +0 -0
- crackerjack/mcp/cache.py +336 -0
- crackerjack/mcp/client_runner.py +104 -0
- crackerjack/mcp/context.py +621 -0
- crackerjack/mcp/dashboard.py +636 -0
- crackerjack/mcp/enhanced_progress_monitor.py +479 -0
- crackerjack/mcp/file_monitor.py +336 -0
- crackerjack/mcp/progress_components.py +569 -0
- crackerjack/mcp/progress_monitor.py +949 -0
- crackerjack/mcp/rate_limiter.py +332 -0
- crackerjack/mcp/server.py +22 -0
- crackerjack/mcp/server_core.py +244 -0
- crackerjack/mcp/service_watchdog.py +501 -0
- crackerjack/mcp/state.py +395 -0
- crackerjack/mcp/task_manager.py +257 -0
- crackerjack/mcp/tools/__init__.py +17 -0
- crackerjack/mcp/tools/core_tools.py +249 -0
- crackerjack/mcp/tools/error_analyzer.py +308 -0
- crackerjack/mcp/tools/execution_tools.py +372 -0
- crackerjack/mcp/tools/execution_tools_backup.py +1097 -0
- crackerjack/mcp/tools/intelligence_tool_registry.py +80 -0
- crackerjack/mcp/tools/intelligence_tools.py +314 -0
- crackerjack/mcp/tools/monitoring_tools.py +502 -0
- crackerjack/mcp/tools/proactive_tools.py +384 -0
- crackerjack/mcp/tools/progress_tools.py +217 -0
- crackerjack/mcp/tools/utility_tools.py +341 -0
- crackerjack/mcp/tools/workflow_executor.py +565 -0
- crackerjack/mcp/websocket/__init__.py +14 -0
- crackerjack/mcp/websocket/app.py +39 -0
- crackerjack/mcp/websocket/endpoints.py +559 -0
- crackerjack/mcp/websocket/jobs.py +253 -0
- crackerjack/mcp/websocket/server.py +116 -0
- crackerjack/mcp/websocket/websocket_handler.py +78 -0
- crackerjack/mcp/websocket_server.py +10 -0
- crackerjack/models/__init__.py +31 -0
- crackerjack/models/config.py +93 -0
- crackerjack/models/config_adapter.py +230 -0
- crackerjack/models/protocols.py +118 -0
- crackerjack/models/task.py +154 -0
- crackerjack/monitoring/ai_agent_watchdog.py +450 -0
- crackerjack/monitoring/regression_prevention.py +638 -0
- crackerjack/orchestration/__init__.py +0 -0
- crackerjack/orchestration/advanced_orchestrator.py +970 -0
- crackerjack/orchestration/coverage_improvement.py +223 -0
- crackerjack/orchestration/execution_strategies.py +341 -0
- crackerjack/orchestration/test_progress_streamer.py +636 -0
- crackerjack/plugins/__init__.py +15 -0
- crackerjack/plugins/base.py +200 -0
- crackerjack/plugins/hooks.py +246 -0
- crackerjack/plugins/loader.py +335 -0
- crackerjack/plugins/managers.py +259 -0
- crackerjack/py313.py +8 -3
- crackerjack/services/__init__.py +22 -0
- crackerjack/services/cache.py +314 -0
- crackerjack/services/config.py +358 -0
- crackerjack/services/config_integrity.py +99 -0
- crackerjack/services/contextual_ai_assistant.py +516 -0
- crackerjack/services/coverage_ratchet.py +356 -0
- crackerjack/services/debug.py +736 -0
- crackerjack/services/dependency_monitor.py +617 -0
- crackerjack/services/enhanced_filesystem.py +439 -0
- crackerjack/services/file_hasher.py +151 -0
- crackerjack/services/filesystem.py +421 -0
- crackerjack/services/git.py +176 -0
- crackerjack/services/health_metrics.py +611 -0
- crackerjack/services/initialization.py +873 -0
- crackerjack/services/log_manager.py +286 -0
- crackerjack/services/logging.py +174 -0
- crackerjack/services/metrics.py +578 -0
- crackerjack/services/pattern_cache.py +362 -0
- crackerjack/services/pattern_detector.py +515 -0
- crackerjack/services/performance_benchmarks.py +653 -0
- crackerjack/services/security.py +163 -0
- crackerjack/services/server_manager.py +234 -0
- crackerjack/services/smart_scheduling.py +144 -0
- crackerjack/services/tool_version_service.py +61 -0
- crackerjack/services/unified_config.py +437 -0
- crackerjack/services/version_checker.py +248 -0
- crackerjack/slash_commands/__init__.py +14 -0
- crackerjack/slash_commands/init.md +122 -0
- crackerjack/slash_commands/run.md +163 -0
- crackerjack/slash_commands/status.md +127 -0
- crackerjack-0.31.7.dist-info/METADATA +742 -0
- crackerjack-0.31.7.dist-info/RECORD +149 -0
- crackerjack-0.31.7.dist-info/entry_points.txt +2 -0
- crackerjack/.gitignore +0 -34
- crackerjack/.libcst.codemod.yaml +0 -18
- crackerjack/.pdm.toml +0 -1
- crackerjack/crackerjack.py +0 -3805
- crackerjack/pyproject.toml +0 -286
- crackerjack-0.30.3.dist-info/METADATA +0 -1290
- crackerjack-0.30.3.dist-info/RECORD +0 -16
- {crackerjack-0.30.3.dist-info → crackerjack-0.31.7.dist-info}/WHEEL +0 -0
- {crackerjack-0.30.3.dist-info → crackerjack-0.31.7.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from fastapi import FastAPI
|
|
5
|
+
from fastapi.responses import HTMLResponse
|
|
6
|
+
|
|
7
|
+
from .jobs import JobManager
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _build_job_list(job_manager: JobManager, progress_dir: Path) -> list[dict]:
|
|
11
|
+
"""Build list of jobs from progress files."""
|
|
12
|
+
jobs = []
|
|
13
|
+
if not progress_dir.exists():
|
|
14
|
+
return jobs
|
|
15
|
+
for progress_file in progress_dir.glob("job-*.json"):
|
|
16
|
+
job_id = job_manager.extract_job_id_from_file(progress_file)
|
|
17
|
+
if job_id and job_manager.validate_job_id(job_id):
|
|
18
|
+
try:
|
|
19
|
+
progress_data = json.loads(progress_file.read_text())
|
|
20
|
+
jobs.append(
|
|
21
|
+
{
|
|
22
|
+
"job_id": job_id,
|
|
23
|
+
"status": progress_data.get("status", "unknown"),
|
|
24
|
+
"message": progress_data.get("message", ""),
|
|
25
|
+
"progress": progress_data.get("overall_progress", 0),
|
|
26
|
+
},
|
|
27
|
+
)
|
|
28
|
+
except (json.JSONDecodeError, OSError):
|
|
29
|
+
continue
|
|
30
|
+
jobs.sort(key=lambda j: j.get("job_id", ""), reverse=True)
|
|
31
|
+
return jobs
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _build_status_response(job_manager: JobManager, jobs: list[dict]) -> dict:
|
|
35
|
+
"""Build the status response dictionary."""
|
|
36
|
+
return {
|
|
37
|
+
"status": "running",
|
|
38
|
+
"message": "Crackerjack WebSocket Server",
|
|
39
|
+
"active_connections": len(job_manager.active_connections),
|
|
40
|
+
"jobs": jobs[:10],
|
|
41
|
+
"websocket_url": "ws://localhost:8675/ws/progress/{job_id}",
|
|
42
|
+
"endpoints": {
|
|
43
|
+
"status": "/",
|
|
44
|
+
"latest_job": "/latest",
|
|
45
|
+
"job_monitor": "/monitor/{job_id}",
|
|
46
|
+
"test": "/test",
|
|
47
|
+
"websocket": "/ws/progress/{job_id}",
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _get_test_html() -> str:
|
|
53
|
+
"""Generate HTML content for test page."""
|
|
54
|
+
return """
|
|
55
|
+
<!DOCTYPE html>
|
|
56
|
+
<html>
|
|
57
|
+
<head>
|
|
58
|
+
<title>WebSocket Test Page</title>
|
|
59
|
+
<meta charset="UTF-8">
|
|
60
|
+
<style>
|
|
61
|
+
body {
|
|
62
|
+
font-family: Arial, sans-serif;
|
|
63
|
+
max-width: 800px;
|
|
64
|
+
margin: 50px auto;
|
|
65
|
+
padding: 20px;
|
|
66
|
+
background-color: #f5f5f5;
|
|
67
|
+
}
|
|
68
|
+
.container {
|
|
69
|
+
background: white;
|
|
70
|
+
padding: 30px;
|
|
71
|
+
border-radius: 10px;
|
|
72
|
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
73
|
+
}
|
|
74
|
+
.test-section {
|
|
75
|
+
margin: 20px 0;
|
|
76
|
+
padding: 15px;
|
|
77
|
+
border: 1px solid #ddd;
|
|
78
|
+
border-radius: 5px;
|
|
79
|
+
}
|
|
80
|
+
button {
|
|
81
|
+
background: #007acc;
|
|
82
|
+
color: white;
|
|
83
|
+
border: none;
|
|
84
|
+
padding: 10px 20px;
|
|
85
|
+
border-radius: 5px;
|
|
86
|
+
cursor: pointer;
|
|
87
|
+
margin: 5px;
|
|
88
|
+
}
|
|
89
|
+
button:hover { background: #005999; }
|
|
90
|
+
input[type="text"] {
|
|
91
|
+
padding: 8px;
|
|
92
|
+
border: 1px solid #ddd;
|
|
93
|
+
border-radius: 3px;
|
|
94
|
+
margin: 5px;
|
|
95
|
+
width: 200px;
|
|
96
|
+
}
|
|
97
|
+
.status {
|
|
98
|
+
margin: 10px 0;
|
|
99
|
+
padding: 10px;
|
|
100
|
+
border-radius: 5px;
|
|
101
|
+
font-weight: bold;
|
|
102
|
+
}
|
|
103
|
+
.success { background: #d4edda; color: #155724; }
|
|
104
|
+
.error { background: #f8d7da; color: #721c24; }
|
|
105
|
+
.info { background: #d1ecf1; color: #0c5460; }
|
|
106
|
+
#log {
|
|
107
|
+
background: #1e1e1e;
|
|
108
|
+
color: #ffffff;
|
|
109
|
+
padding: 15px;
|
|
110
|
+
border-radius: 5px;
|
|
111
|
+
font-family: 'Courier New', monospace;
|
|
112
|
+
max-height: 300px;
|
|
113
|
+
overflow-y: auto;
|
|
114
|
+
margin-top: 20px;
|
|
115
|
+
}
|
|
116
|
+
</style>
|
|
117
|
+
</head>
|
|
118
|
+
<body>
|
|
119
|
+
<div class="container">
|
|
120
|
+
<h1>🧪 WebSocket Test Page</h1>
|
|
121
|
+
<p>Test WebSocket connectivity and server functionality</p>
|
|
122
|
+
|
|
123
|
+
<div class="test-section">
|
|
124
|
+
<h3>1. Server Status</h3>
|
|
125
|
+
<button onclick="checkServerStatus()">Check Status</button>
|
|
126
|
+
<div id="serverStatus"></div>
|
|
127
|
+
</div>
|
|
128
|
+
|
|
129
|
+
<div class="test-section">
|
|
130
|
+
<h3>2. Latest Job</h3>
|
|
131
|
+
<button onclick="checkLatestJob()">Get Latest Job</button>
|
|
132
|
+
<div id="latestJob"></div>
|
|
133
|
+
</div>
|
|
134
|
+
|
|
135
|
+
<div class="test-section">
|
|
136
|
+
<h3>3. WebSocket Test</h3>
|
|
137
|
+
<input type="text" id="testJobId" placeholder="Enter job ID (e.g., test-123)" value="test-123">
|
|
138
|
+
<button onclick="testWebSocket()">Test WebSocket</button>
|
|
139
|
+
<button onclick="disconnectWebSocket()">Disconnect</button>
|
|
140
|
+
<div id="wsStatus"></div>
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<div id="log">
|
|
144
|
+
<div>Ready to test...</div>
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
|
|
148
|
+
<script>
|
|
149
|
+
let testWs = null;
|
|
150
|
+
const log = document.getElementById('log');
|
|
151
|
+
|
|
152
|
+
function addLog(message, type = 'info') {
|
|
153
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
154
|
+
const div = document.createElement('div');
|
|
155
|
+
div.textContent = `[${timestamp}] ${message}`;
|
|
156
|
+
if (type === 'error') div.style.color = '#ff6b6b';
|
|
157
|
+
if (type === 'success') div.style.color = '#51cf66';
|
|
158
|
+
log.appendChild(div);
|
|
159
|
+
log.scrollTop = log.scrollHeight;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function showStatus(elementId, message, type = 'info') {
|
|
163
|
+
const element = document.getElementById(elementId);
|
|
164
|
+
element.innerHTML = `<div class="status ${type}">${message}</div>`;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function checkServerStatus() {
|
|
168
|
+
try {
|
|
169
|
+
addLog('Checking server status...');
|
|
170
|
+
const response = await fetch('/');
|
|
171
|
+
const data = await response.json();
|
|
172
|
+
|
|
173
|
+
showStatus('serverStatus',
|
|
174
|
+
`Status: ${data.status}<br>` +
|
|
175
|
+
`Active connections: ${data.active_connections}<br>` +
|
|
176
|
+
`Jobs: ${data.jobs?.length || 0}`, 'success');
|
|
177
|
+
addLog('Server status retrieved successfully', 'success');
|
|
178
|
+
} catch (error) {
|
|
179
|
+
showStatus('serverStatus', `Error: ${error.message}`, 'error');
|
|
180
|
+
addLog(`Server status error: ${error.message}`, 'error');
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function checkLatestJob() {
|
|
185
|
+
try {
|
|
186
|
+
addLog('Getting latest job...');
|
|
187
|
+
const response = await fetch('/latest');
|
|
188
|
+
const data = await response.json();
|
|
189
|
+
|
|
190
|
+
if (data.job_id) {
|
|
191
|
+
showStatus('latestJob',
|
|
192
|
+
`Job ID: ${data.job_id}<br>` +
|
|
193
|
+
`Status: ${data.progress?.status || 'unknown'}<br>` +
|
|
194
|
+
`Progress: ${data.progress?.overall_progress || 0}%`, 'success');
|
|
195
|
+
addLog(`Latest job: ${data.job_id}`, 'success');
|
|
196
|
+
} else {
|
|
197
|
+
showStatus('latestJob', data.message, 'info');
|
|
198
|
+
addLog(data.message);
|
|
199
|
+
}
|
|
200
|
+
} catch (error) {
|
|
201
|
+
showStatus('latestJob', `Error: ${error.message}`, 'error');
|
|
202
|
+
addLog(`Latest job error: ${error.message}`, 'error');
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function testWebSocket() {
|
|
207
|
+
const jobId = document.getElementById('testJobId').value;
|
|
208
|
+
if (!jobId) {
|
|
209
|
+
showStatus('wsStatus', 'Please enter a job ID', 'error');
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
disconnectWebSocket();
|
|
214
|
+
|
|
215
|
+
addLog(`Connecting to WebSocket for job: ${jobId}`);
|
|
216
|
+
const wsUrl = `ws://localhost:8675/ws/progress/${jobId}`;
|
|
217
|
+
testWs = new WebSocket(wsUrl);
|
|
218
|
+
|
|
219
|
+
testWs.onopen = function() {
|
|
220
|
+
showStatus('wsStatus', `Connected to ${jobId}`, 'success');
|
|
221
|
+
addLog('WebSocket connected successfully', 'success');
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
testWs.onmessage = function(event) {
|
|
225
|
+
try {
|
|
226
|
+
const data = JSON.parse(event.data);
|
|
227
|
+
addLog(`Received: ${data.message || 'Progress update'}`);
|
|
228
|
+
} catch (e) {
|
|
229
|
+
addLog(`Raw message: ${event.data}`);
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
testWs.onclose = function() {
|
|
234
|
+
showStatus('wsStatus', 'WebSocket closed', 'info');
|
|
235
|
+
addLog('WebSocket connection closed');
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
testWs.onerror = function(error) {
|
|
239
|
+
showStatus('wsStatus', `WebSocket error: ${error.message}`, 'error');
|
|
240
|
+
addLog(`WebSocket error: ${error.message}`, 'error');
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function disconnectWebSocket() {
|
|
245
|
+
if (testWs) {
|
|
246
|
+
testWs.close();
|
|
247
|
+
testWs = null;
|
|
248
|
+
showStatus('wsStatus', 'Disconnected', 'info');
|
|
249
|
+
addLog('WebSocket disconnected');
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Auto-check server status on load
|
|
254
|
+
window.onload = function() {
|
|
255
|
+
checkServerStatus();
|
|
256
|
+
};
|
|
257
|
+
</script>
|
|
258
|
+
</body>
|
|
259
|
+
</html>
|
|
260
|
+
"""
|
|
261
|
+
|
|
262
|
+
|
|
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
|
+
def register_endpoints(
|
|
504
|
+
app: FastAPI,
|
|
505
|
+
job_manager: JobManager,
|
|
506
|
+
progress_dir: Path,
|
|
507
|
+
) -> None:
|
|
508
|
+
@app.get("/")
|
|
509
|
+
async def get_status():
|
|
510
|
+
try:
|
|
511
|
+
jobs = _build_job_list(job_manager, progress_dir)
|
|
512
|
+
return _build_status_response(job_manager, jobs)
|
|
513
|
+
except Exception as e:
|
|
514
|
+
return {"status": "error", "message": str(e)}
|
|
515
|
+
|
|
516
|
+
@app.get("/latest")
|
|
517
|
+
async def get_latest_job():
|
|
518
|
+
try:
|
|
519
|
+
latest_job_id = job_manager.get_latest_job_id()
|
|
520
|
+
|
|
521
|
+
if not latest_job_id:
|
|
522
|
+
return {
|
|
523
|
+
"status": "no_jobs",
|
|
524
|
+
"message": "No jobs found",
|
|
525
|
+
"job_id": None,
|
|
526
|
+
"progress": None,
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
progress_data = job_manager.get_job_progress(latest_job_id)
|
|
530
|
+
|
|
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
|
+
}
|
|
539
|
+
|
|
540
|
+
except Exception as e:
|
|
541
|
+
return {
|
|
542
|
+
"status": "error",
|
|
543
|
+
"message": f"Failed to get latest job: {e}",
|
|
544
|
+
"job_id": None,
|
|
545
|
+
"progress": None,
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
@app.get("/monitor/{job_id}")
|
|
549
|
+
async def get_job_monitor_page(job_id: str):
|
|
550
|
+
if not job_manager.validate_job_id(job_id):
|
|
551
|
+
return HTMLResponse(
|
|
552
|
+
content="<h1>Error</h1><p>Invalid job ID</p>",
|
|
553
|
+
status_code=400,
|
|
554
|
+
)
|
|
555
|
+
return HTMLResponse(content=_get_monitor_html(job_id))
|
|
556
|
+
|
|
557
|
+
@app.get("/test")
|
|
558
|
+
async def get_test_page():
|
|
559
|
+
return HTMLResponse(content=_get_test_html())
|