Added Run via console in tasks, added console WebUI and basic system report.

This commit is contained in:
abdulmohsen
2024-05-07 16:09:07 +03:00
parent 55145cc8fb
commit 8503178590
9 changed files with 452 additions and 19 deletions

149
src/API/System/Command.php Normal file
View File

@@ -0,0 +1,149 @@
<?php
declare(strict_types=1);
namespace App\API\System;
use App\Libs\Attributes\Route\Get;
use App\Libs\DataUtil;
use App\Libs\HTTP_STATUS;
use App\Libs\StreamClosure;
use JsonException;
use Nyholm\Psr7\Response;
use Psr\Http\Message\ResponseInterface as iResponse;
use Psr\Http\Message\ServerRequestInterface as iRequest;
use Symfony\Component\Process\Exception\ProcessTimedOutException;
use Symfony\Component\Process\Process;
final class Command
{
public const string URL = '%{api.prefix}/system/command';
private int $counter = 1;
public function __construct()
{
set_time_limit(0);
}
#[Get(self::URL . '[/]', name: 'system.command')]
public function __invoke(iRequest $request): iResponse
{
if (null === ($json = ag($request->getQueryParams(), 'json'))) {
return api_error('No command was given.', HTTP_STATUS::HTTP_BAD_REQUEST);
}
try {
$json = json_decode(base64_decode(rawurldecode($json)), true, flags: JSON_THROW_ON_ERROR);
$data = DataUtil::fromArray($json);
if (null === ($command = $data->get('command'))) {
return api_error('No command was given.', HTTP_STATUS::HTTP_BAD_REQUEST);
}
} catch (JsonException $e) {
return api_error(
r('Unable to decode json data. {error}', ['error' => $e->getMessage()]),
HTTP_STATUS::HTTP_BAD_REQUEST
);
}
if (!is_string($command)) {
return api_error('Command is invalid.', HTTP_STATUS::HTTP_BAD_REQUEST);
}
$callable = function () use ($command, $data, $request) {
ignore_user_abort(true);
$path = realpath(__DIR__ . '/../../../');
try {
$process = Process::fromShellCommandline(
command: "{$path}/bin/console {$command}",
cwd: $path,
env: $_ENV,
timeout: $data->get('timeout', 3600),
);
$process->start(callback: function ($type, $data) use ($process) {
echo "id: " . hrtime(true) . "\n";
echo "event: data\n";
$data = (string)$data;
echo implode(
PHP_EOL,
array_map(
function ($data) {
if (!is_string($data)) {
return null;
}
return 'data: ' . $data;
},
(array)preg_split("/\R/", $data)
)
);
echo "\n\n";
flush();
$this->counter = 3;
if (ob_get_length() > 0) {
ob_end_flush();
}
if (connection_aborted()) {
$process->stop(1, 9);
}
});
while ($process->isRunning()) {
usleep(500000);
$this->counter--;
if ($this->counter > 1) {
continue;
}
$this->counter = 3;
echo "id: " . hrtime(true) . "\n";
echo "event: ping\n";
echo 'data: ' . makeDate() . "\n\n";
flush();
if (ob_get_length() > 0) {
ob_end_flush();
}
if (connection_aborted()) {
$process->stop(1, 9);
}
}
} catch (ProcessTimedOutException) {
}
if (!connection_aborted()) {
echo "id: " . hrtime(true) . "\n";
echo "event: close\n";
echo 'data: ' . makeDate() . "\n\n";
flush();
if (ob_get_length() > 0) {
ob_end_flush();
}
}
exit;
};
return new Response(
status: HTTP_STATUS::HTTP_OK->value,
headers: [
'Content-Type' => 'text/event-stream',
'Cache-Control' => 'no-cache',
'Connection' => 'keep-alive',
'X-Accel-Buffering' => 'no',
'Access-Control-Allow-Origin' => '*',
'Access-Control-Allow-Credentials' => 'true',
'Last-Event-Id' => time(),
],
body: StreamClosure::create($callable)
);
}
}

21
src/API/System/Report.php Normal file
View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace App\API\System;
use App\Libs\Attributes\Route\Get;
use App\Libs\HTTP_STATUS;
use Psr\Http\Message\ResponseInterface as iResponse;
use Psr\Http\Message\ServerRequestInterface as iRequest;
final class Report
{
public const string URL = '%{api.prefix}/system/report';
#[Get(self::URL . '[/]', name: 'system.report')]
public function __invoke(iRequest $request): iResponse
{
return api_response(HTTP_STATUS::HTTP_OK, runCommand('system:report', asArray: true));
}
}