Skip to content

Commit 540835f

Browse files
committed
Enh(viewer): Only load logs on request
1 parent 8db4a93 commit 540835f

4 files changed

Lines changed: 156 additions & 28 deletions

File tree

codeclash/viewer/app.py

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -277,12 +277,11 @@ class GameMetadata:
277277
"""Metadata about a game session"""
278278

279279
results: dict[str, Any]
280-
main_log: str
281280
main_log_path: str
282281
metadata_file_path: str
283282
rounds: list[dict[str, Any]]
284283
agent_info: list[AgentInfo] | None = None
285-
all_logs: dict[str, dict[str, str]] | None = None # {log_type: {"content": content, "path": path}}
284+
all_logs: dict[str, dict[str, str]] | None = None # {log_type: {"path": path}} - content loaded on demand
286285

287286

288287
def process_round_results(
@@ -385,12 +384,11 @@ def parse_game_metadata(self) -> GameMetadata:
385384
results = metadata.raw_data
386385
metadata_file_path = str(self.log_dir / "metadata.json")
387386

388-
# Parse tournament.log if it exists
387+
# Get path to main log but don't load content
389388
main_log_file = self.log_dir / "tournament.log"
390-
main_log = main_log_file.read_text() if main_log_file.exists() else "No tournament log found"
391389
main_log_path = str(main_log_file) if main_log_file.exists() else ""
392390

393-
# Parse all available logs
391+
# Parse all available logs (metadata only, no content)
394392
all_logs = self._parse_all_logs()
395393

396394
# Extract agent information once
@@ -411,7 +409,6 @@ def parse_game_metadata(self) -> GameMetadata:
411409

412410
return GameMetadata(
413411
results=results,
414-
main_log=main_log,
415412
main_log_path=main_log_path,
416413
metadata_file_path=metadata_file_path,
417414
rounds=rounds,
@@ -468,7 +465,9 @@ def parse_trajectory(self, player_name: str, round_num: int) -> TrajectoryInfo |
468465
trajectory_file_path=str(traj_file),
469466
diff_by_files=diff_by_files,
470467
incremental_diff_by_files=incremental_diff_by_files,
471-
valid_submission=self._get_metadata().round_stats[str(round_num)]['player_stats'][player_name]['valid_submit']
468+
valid_submission=self._get_metadata().round_stats[str(round_num)]["player_stats"][player_name][
469+
"valid_submit"
470+
],
472471
)
473472
except (json.JSONDecodeError, KeyError) as e:
474473
logger.error(f"Error parsing {traj_file}: {e}", exc_info=True)
@@ -670,7 +669,7 @@ def load_matrix_analysis(self) -> dict[str, Any] | None:
670669
return None
671670

672671
def _parse_all_logs(self) -> dict[str, dict[str, str]]:
673-
"""Parse all available log files in the tournament directory"""
672+
"""Parse all available log files in the tournament directory (metadata only, no content)"""
674673
all_logs = {}
675674

676675
# Define log files to look for
@@ -680,11 +679,7 @@ def _parse_all_logs(self) -> dict[str, dict[str, str]]:
680679
for log_file, display_name in log_files.items():
681680
log_path = self.log_dir / log_file
682681
if log_path.exists():
683-
try:
684-
content = log_path.read_text()
685-
all_logs[display_name] = {"content": content, "path": str(log_path)}
686-
except (OSError, UnicodeDecodeError) as e:
687-
all_logs[display_name] = {"content": f"Error reading log file: {e}", "path": str(log_path)}
682+
all_logs[display_name] = {"path": str(log_path)}
688683

689684
# Check for player logs
690685
players_dir = self.log_dir / "players"
@@ -697,13 +692,8 @@ def _parse_all_logs(self) -> dict[str, dict[str, str]]:
697692
player_log = player_dir / "player.log"
698693

699694
if player_log.exists():
700-
try:
701-
content = player_log.read_text()
702-
display_name = f"Player {player_name} Log"
703-
all_logs[display_name] = {"content": content, "path": str(player_log)}
704-
except (OSError, UnicodeDecodeError) as e:
705-
display_name = f"Player {player_name} Log"
706-
all_logs[display_name] = {"content": f"Error reading log file: {e}", "path": str(player_log)}
695+
display_name = f"Player {player_name} Log"
696+
all_logs[display_name] = {"path": str(player_log)}
707697

708698
return all_logs
709699

@@ -1220,4 +1210,38 @@ def download_file():
12201210
return jsonify({"success": False, "error": str(e)}), 500
12211211

12221212

1213+
@app.route("/load-log")
1214+
def load_log():
1215+
"""Load log file content on demand"""
1216+
file_path = request.args.get("path")
1217+
1218+
if not file_path:
1219+
return jsonify({"success": False, "error": "No file path provided"}), 400
1220+
1221+
try:
1222+
# Convert to Path object
1223+
file_path_obj = Path(file_path)
1224+
1225+
# Security check: ensure the file exists
1226+
if not file_path_obj.exists():
1227+
return jsonify({"success": False, "error": "File does not exist"}), 404
1228+
1229+
# Security check: ensure the file is not a directory
1230+
if not file_path_obj.is_file():
1231+
return jsonify({"success": False, "error": "Path is not a file"}), 400
1232+
1233+
# Security check: ensure the path is within our expected logs directory
1234+
try:
1235+
file_path_obj.relative_to(LOG_BASE_DIR)
1236+
except ValueError:
1237+
return jsonify({"success": False, "error": "Invalid file path"}), 403
1238+
1239+
# Read and return the file content
1240+
content = file_path_obj.read_text()
1241+
return jsonify({"success": True, "content": content})
1242+
1243+
except (OSError, UnicodeDecodeError) as e:
1244+
return jsonify({"success": False, "error": f"Error reading file: {str(e)}"}), 500
1245+
1246+
12231247
# Use run_viewer.py to launch the application

codeclash/viewer/static/css/style.css

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -748,11 +748,13 @@ details summary {
748748
color: var(--success-color);
749749
}
750750

751-
.status-yes, .valid-yes {
751+
.status-yes,
752+
.valid-yes {
752753
color: var(--success-color);
753754
}
754755

755-
.status-no, .valid-no {
756+
.status-no,
757+
.valid-no {
756758
color: var(--neon-red);
757759
}
758760

@@ -2108,3 +2110,38 @@ summary:focus {
21082110
font-size: 0.7rem;
21092111
}
21102112
}
2113+
2114+
/* Log loading UI */
2115+
.log-load-placeholder {
2116+
padding: 2rem;
2117+
text-align: center;
2118+
}
2119+
2120+
.load-log-btn {
2121+
font-size: 1rem;
2122+
padding: 0.75rem 1.5rem;
2123+
border-radius: 0.5rem;
2124+
transition: all 0.2s ease;
2125+
}
2126+
2127+
.load-log-btn:hover {
2128+
transform: translateY(-2px);
2129+
box-shadow: 0 4px 8px rgba(0, 255, 136, 0.3);
2130+
}
2131+
2132+
.log-loading-spinner {
2133+
padding: 2rem;
2134+
text-align: center;
2135+
display: flex;
2136+
align-items: center;
2137+
justify-content: center;
2138+
gap: 1rem;
2139+
color: var(--text-secondary);
2140+
font-size: 1rem;
2141+
}
2142+
2143+
.log-loading-spinner .spinner-border {
2144+
width: 2rem;
2145+
height: 2rem;
2146+
border-width: 0.25rem;
2147+
}

codeclash/viewer/static/js/app.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,40 @@ function closeTocMenu() {
342342
}
343343
}
344344

345+
// Log loading functionality
346+
function loadLogContent(logPath, container) {
347+
const placeholder = container.querySelector(".log-load-placeholder");
348+
const spinner = container.querySelector(".log-loading-spinner");
349+
const contentPre = container.querySelector(".log-content-scrollable");
350+
const contentCode = container.querySelector(".log-content");
351+
352+
// Hide placeholder, show spinner
353+
placeholder.style.display = "none";
354+
spinner.style.display = "block";
355+
356+
// Fetch log content from server
357+
fetch(`/load-log?path=${encodeURIComponent(logPath)}`)
358+
.then((response) => response.json())
359+
.then((data) => {
360+
spinner.style.display = "none";
361+
362+
if (data.success) {
363+
// Display the content
364+
contentCode.textContent = data.content;
365+
contentPre.style.display = "block";
366+
} else {
367+
// Show error message
368+
contentCode.textContent = `Error loading log: ${data.error}`;
369+
contentPre.style.display = "block";
370+
}
371+
})
372+
.catch((error) => {
373+
spinner.style.display = "none";
374+
contentCode.textContent = `Error loading log: ${error}`;
375+
contentPre.style.display = "block";
376+
});
377+
}
378+
345379
// Setup button event listeners
346380
function setupButtonEventListeners() {
347381
// Pick game button
@@ -400,6 +434,17 @@ function setupButtonEventListeners() {
400434
collapseTrajectoryMessages(this);
401435
});
402436
});
437+
438+
// Log loading buttons
439+
document.querySelectorAll(".load-log-btn").forEach((button) => {
440+
button.addEventListener("click", function () {
441+
const logPath = this.getAttribute("data-log-path");
442+
const container = this.closest(".log-content-container");
443+
if (logPath && container) {
444+
loadLogContent(logPath, container);
445+
}
446+
});
447+
});
403448
}
404449

405450
// Initialize everything when DOM is loaded

codeclash/viewer/templates/includes/overview.html

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,28 +75,39 @@ <h2><i class="bi bi-bar-chart"></i> Overview</h2>
7575
</div>
7676

7777
<!-- Tournament Log -->
78-
<details class="foldout">
78+
{% if metadata.main_log_path %}
79+
<details class="foldout log-foldout" data-log-path="{{ metadata.main_log_path }}">
7980
<summary>
8081
<i class="bi bi-journal"></i> Tournament Log
81-
{% if metadata.main_log_path %}
8282
<button class="copy-path-btn-small" data-path="{{ metadata.main_log_path }}" title="Copy file path">
8383
<i class="bi bi-clipboard"></i> Copy path
8484
</button>
8585
<button class="download-btn-small" data-path="{{ metadata.main_log_path }}" title="Download file">
8686
<i class="bi bi-download"></i> Download
8787
</button>
88-
{% endif %}
8988
</summary>
9089
<div class="log-content-container">
91-
<pre class="log-content-scrollable"><code>{{ metadata.main_log }}</code></pre>
90+
<div class="log-load-placeholder">
91+
<button class="btn btn-primary load-log-btn" data-log-path="{{ metadata.main_log_path }}">
92+
<i class="bi bi-download"></i> Load Log Content
93+
</button>
94+
</div>
95+
<div class="log-loading-spinner" style="display: none;">
96+
<div class="spinner-border text-primary" role="status">
97+
<span class="visually-hidden">Loading...</span>
98+
</div>
99+
<span class="ms-2">Loading log content...</span>
100+
</div>
101+
<pre class="log-content-scrollable" style="display: none;"><code class="log-content"></code></pre>
92102
</div>
93103
</details>
104+
{% endif %}
94105

95106
<!-- All Other Logs -->
96107
{% if metadata.all_logs %}
97108
{% for log_name, log_data in metadata.all_logs.items() %}
98109
{% if log_name != "Tournament Log" %}
99-
<details class="foldout">
110+
<details class="foldout log-foldout" data-log-path="{{ log_data.path }}">
100111
<summary>
101112
<i class="bi bi-journal"></i> {{ log_name }}
102113
{% if log_data.path %}
@@ -109,7 +120,18 @@ <h2><i class="bi bi-bar-chart"></i> Overview</h2>
109120
{% endif %}
110121
</summary>
111122
<div class="log-content-container">
112-
<pre class="log-content-scrollable"><code>{{ log_data.content }}</code></pre>
123+
<div class="log-load-placeholder">
124+
<button class="btn btn-primary load-log-btn" data-log-path="{{ log_data.path }}">
125+
<i class="bi bi-download"></i> Load Log Content
126+
</button>
127+
</div>
128+
<div class="log-loading-spinner" style="display: none;">
129+
<div class="spinner-border text-primary" role="status">
130+
<span class="visually-hidden">Loading...</span>
131+
</div>
132+
<span class="ms-2">Loading log content...</span>
133+
</div>
134+
<pre class="log-content-scrollable" style="display: none;"><code class="log-content"></code></pre>
113135
</div>
114136
</details>
115137
{% endif %}

0 commit comments

Comments
 (0)