Skip to content

Commit d2e05d3

Browse files
Add option to tail and download log files from web UI.
1 parent e3b5783 commit d2e05d3

File tree

4 files changed

+87
-6
lines changed

4 files changed

+87
-6
lines changed

webapp/src/Controller/API/AbstractRestController.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -283,8 +283,11 @@ protected function listActionHelper(Request $request): array
283283
/**
284284
* Send a binary file response, sending a 304 if it did not modify since last requested.
285285
*/
286-
public static function sendBinaryFileResponse(Request $request, string $fileName): BinaryFileResponse
287-
{
286+
public static function sendBinaryFileResponse(
287+
Request $request,
288+
string $fileName,
289+
bool $download = false
290+
): BinaryFileResponse {
288291
// Note: we set auto-etag to true to automatically send the ETag based on the file contents.
289292
// ETags can be used to determine whether the file changed and if it didn't change, the response will
290293
// be a 304 Not Modified.
@@ -296,6 +299,9 @@ public static function sendBinaryFileResponse(Request $request, string $fileName
296299
$contentType = 'image/svg+xml';
297300
}
298301
$response->headers->set('Content-Type', $contentType);
302+
if ($download) {
303+
$response->headers->set('Content-Disposition', 'attachment');
304+
}
299305

300306
// Check if we need to send a 304 Not Modified and if so, send it.
301307
// This is done both on the

webapp/src/Controller/Jury/ConfigController.php

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@
22

33
namespace App\Controller\Jury;
44

5+
use App\Controller\API\AbstractRestController;
56
use App\Entity\Configuration;
67
use App\Service\CheckConfigService;
78
use App\Service\ConfigurationService;
89
use App\Service\DOMJudgeService;
910
use App\Service\EventLogService;
11+
use App\Utils\Utils;
1012
use Doctrine\ORM\EntityManagerInterface;
1113
use Psr\Log\LoggerInterface;
1214
use Symfony\Component\DependencyInjection\Attribute\Autowire;
15+
use Symfony\Component\HttpFoundation\BinaryFileResponse;
1316
use Symfony\Component\Security\Http\Attribute\IsGranted;
1417
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
1518
use Symfony\Component\HttpFoundation\Request;
@@ -127,13 +130,45 @@ public function checkAction(
127130
): Response {
128131
$results = $this->checkConfigService->runAll();
129132
$stopwatch = $this->checkConfigService->getStopwatch();
133+
$logFiles = glob($logsDir . '/*.log');
134+
$logFilesWithSize = [];
135+
foreach ($logFiles as $logFile) {
136+
$logFilesWithSize[str_replace($logsDir . '/', '', $logFile)] = Utils::printsize(filesize($logFile));
137+
}
130138
return $this->render('jury/config_check.html.twig', [
131139
'results' => $results,
132140
'stopwatch' => $stopwatch,
133141
'dir' => [
134-
'project' => dirname($projectDir),
135-
'log' => $logsDir,
136-
],
142+
'project' => dirname($projectDir),
143+
'log' => $logsDir,
144+
],
145+
'logFilesWithSize' => $logFilesWithSize,
137146
]);
138147
}
148+
149+
#[Route(path: '/tail-log/{logFile<[a-z0-9-]+\.log>}', name: 'jury_tail_log')]
150+
public function tailLogAction(
151+
string $logFile,
152+
#[Autowire('%kernel.logs_dir%')]
153+
string $logsDir
154+
): Response {
155+
$fullFile = "$logsDir/$logFile";
156+
$command = sprintf('tail -n200 %s', escapeshellarg($fullFile));
157+
exec($command, $lines);
158+
return $this->render('jury/tail_log.html.twig', [
159+
'logFile' => $logFile,
160+
'contents' => implode("\n", $lines),
161+
]);
162+
}
163+
164+
#[Route(path: '/download-log/{logFile<[a-z0-9-]+\.log>}', name: 'jury_download_log')]
165+
public function downloadLogAction(
166+
Request $request,
167+
string $logFile,
168+
#[Autowire('%kernel.logs_dir%')]
169+
string $logsDir
170+
): BinaryFileResponse {
171+
$fullFile = "$logsDir/$logFile";
172+
return AbstractRestController::sendBinaryFileResponse($request, $fullFile, true);
173+
}
139174
}

webapp/templates/jury/config_check.html.twig

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
<div class="card m-3 bg-light">
2525
<div class="card-body">
2626
<h5 class="card-title">Installation locations</h5>
27-
<table>
27+
<table class="w-100">
2828
<tr><td>Base url (use in submit client):</td><td><kbd>{{ url('root') }}</kbd></td></tr>
2929
<tr><td>API url (use in <kbd>restapi.secret</kbd>):</td><td><kbd>{{ url('current_api_root') | trim('/', 'right') }}</kbd></td></tr>
3030
<tr><td colspan="2">&nbsp;</td></tr>
@@ -36,6 +36,37 @@
3636
<tr><td>DOMjudge console binary:</td><td><kbd>{{ dir.project }}/webapp/bin/console</kbd></td></tr>
3737
<tr><td>Log directory:</td><td><kbd>{{ dir.log }}</kbd></td></tr>
3838
</table>
39+
40+
{% if logFilesWithSize is not empty %}
41+
<h5 class="card-title mt-3">Log files</h5>
42+
<table class="w-100">
43+
<thead>
44+
<tr>
45+
<th>File</th>
46+
<th>Size</th>
47+
<th>Actions</th>
48+
</tr>
49+
</thead>
50+
<tbody>
51+
{% for logFile, size in logFilesWithSize %}
52+
<tr>
53+
<td><kbd>{{ logFile }}</kbd></td>
54+
<td>{{ size }}</td>
55+
<td>
56+
<a href="{{ path('jury_tail_log', {'logFile': logFile}) }}" class="btn btn-outline-secondary btn-sm">
57+
<i class="fas fa-terminal"></i>
58+
Get last 200 lines
59+
</a>
60+
<a href="{{ path('jury_download_log', {'logFile': logFile}) }}" class="btn btn-outline-secondary btn-sm">
61+
<i class="fas fa-download"></i>
62+
Download
63+
</a>
64+
</td>
65+
</tr>
66+
{% endfor %}
67+
</tbody>
68+
</table>
69+
{% endif %}
3970
</div>
4071
</div>
4172

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{% extends 'jury/base.html.twig' %}
2+
3+
{% block title %}Last 200 lines of {{ logFile }} - {{ parent() }}{% endblock %}
4+
5+
{% block content %}
6+
<h1>Last 200 lines of <kbd>{{ logFile }}</kbd></h1>
7+
<pre class="output_text">{{ contents }}</pre>
8+
{% endblock %}
9+

0 commit comments

Comments
 (0)