From 1d7b9c1dbc887ac32e9d57d1c8bfd1bcbca85cab Mon Sep 17 00:00:00 2001 From: panlatent Date: Wed, 26 Jun 2024 17:32:52 +0800 Subject: [PATCH] Update --- abstract/ArrayOutput.php | 23 +++ abstract/ContextInterface.php | 4 + abstract/OutputInterface.php | 2 +- composer.json | 9 +- src/Plugin.php | 2 +- src/actions/Command.php | 93 ------------ src/actions/Console.php | 143 +++++++++++++----- src/actions/CraftConsole.php | 63 ++++++++ src/actions/HttpRequest.php | 13 +- src/actions/StdOutput.php | 30 ++++ src/builder/Schedule.php | 6 +- src/controllers/ActionsController.php | 39 ++++- src/controllers/SchedulesController.php | 1 - src/di/ContainerAdapter.php | 7 +- src/di/NotFoundException.php | 11 ++ src/icons/action.svg | 4 + src/log/MemoryLog.php | 27 ++++ src/models/Context.php | 22 ++- src/services/Actions.php | 24 ++- src/services/Schedules.php | 18 +-- .../_components/actions/Console/output.twig | 15 ++ .../_components/actions/Console/settings.twig | 70 +++++++-- .../{Command => CraftConsole}/settings.twig | 18 +-- .../actions/HttpRequest/output.twig | 24 +++ .../_components/utilities/ActionRunner.twig | 52 ++++++- src/templates/_includes/forms/action.twig | 4 +- .../_includes/forms/actionSetting.twig | 4 + src/utilities/ActionRunner.php | 21 ++- src/web/assets/console/ConsoleAsset.php | 12 ++ 29 files changed, 569 insertions(+), 192 deletions(-) create mode 100644 abstract/ArrayOutput.php delete mode 100644 src/actions/Command.php create mode 100644 src/actions/CraftConsole.php create mode 100644 src/actions/StdOutput.php create mode 100644 src/di/NotFoundException.php create mode 100644 src/icons/action.svg create mode 100644 src/log/MemoryLog.php create mode 100644 src/templates/_components/actions/Console/output.twig rename src/templates/_components/actions/{Command => CraftConsole}/settings.twig (53%) create mode 100644 src/templates/_components/actions/HttpRequest/output.twig create mode 100644 src/templates/_includes/forms/actionSetting.twig create mode 100644 src/web/assets/console/ConsoleAsset.php diff --git a/abstract/ArrayOutput.php b/abstract/ArrayOutput.php new file mode 100644 index 0000000..59ab9cc --- /dev/null +++ b/abstract/ArrayOutput.php @@ -0,0 +1,23 @@ +getView()->renderTemplate($this->template, $this->arr); + } + + +} \ No newline at end of file diff --git a/abstract/ContextInterface.php b/abstract/ContextInterface.php index 1439d91..f96472f 100644 --- a/abstract/ContextInterface.php +++ b/abstract/ContextInterface.php @@ -7,6 +7,10 @@ interface ContextInterface { + public function addError(string $attribute, string $error): void; + + public function hasErrors(): bool; + public function getContainer(): ContainerInterface; public function getErrors(): array; diff --git a/abstract/OutputInterface.php b/abstract/OutputInterface.php index e878d2e..c08641d 100644 --- a/abstract/OutputInterface.php +++ b/abstract/OutputInterface.php @@ -6,5 +6,5 @@ interface OutputInterface { public function canStored(): bool; - public function getSettings(): array; + public function render(): string; } \ No newline at end of file diff --git a/composer.json b/composer.json index 681a500..ae66036 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ ], "require": { "php": ">=8.2", - "craftcms/cms": "^5.0", + "craftcms/cms": "^5.0.0", "dragonmantank/cron-expression": "^3.3", "guzzlehttp/guzzle": "^7.2", "nesbot/carbon": "^1.22 || ^2.10 || ^3.0", @@ -35,7 +35,8 @@ "psr/log": "^1.0 || ^2.0 || ^3.0", "react/event-loop": "^1.5", "symfony/process": "^6.0 || ^7.0", - "alexanderpas/http-enum": "^1.0" + "alexanderpas/http-enum": "^1.0", + "symfony/stopwatch": "^7.1" }, "replace": { "panlatent/craft-action-abstract": "self.version" @@ -56,7 +57,7 @@ "class": "panlatent\\schedule\\Plugin" }, "require-dev": { - "codeception/codeception": "^4.1", + "codeception/codeception": "^5.1", "craftcms/phpstan": "dev-main", "craftcms/rector": "dev-main" }, @@ -69,6 +70,6 @@ "craftcms/plugin-installer": true } }, - "minimum-stability": "dev", + "minimum-stability": "stable", "prefer-stable": true } diff --git a/src/Plugin.php b/src/Plugin.php index 6178a04..c42271f 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -227,7 +227,7 @@ private function _registerWebCron(): void private function _registerUtilities(): void { - Event::on(Utilities::class, Utilities::EVENT_REGISTER_UTILITIES, function(RegisterComponentTypesEvent $event) { + Event::on(Utilities::class, Utilities::EVENT_REGISTER_UTILITIES, static function(RegisterComponentTypesEvent $event) { $event->types[] = ActionRunner::class; }); } diff --git a/src/actions/Command.php b/src/actions/Command.php deleted file mode 100644 index 21ba383..0000000 --- a/src/actions/Command.php +++ /dev/null @@ -1,93 +0,0 @@ -command, - $this->arguments, - ]; - - if ($this->osUser) { - $command = array_merge(['sudo -u', $this->osUser], $command); - } - - return $command; - } - - public function execute(ContextInterface $context): bool - { - $workDir = $this->workDir ?: dirname(Craft::$app->request->getScriptFile()); - $process = new Process($this->buildCommand(), $workDir, $this->env, null, $this->timeout ?: null); - - $process->run(function ($type, $buffer) use ($context) { - $output = $buffer . "\n"; - $context->getLogger()->info($output); - }); - - return $process->isSuccessful(); - } - - public function getSettingsHtml(): ?string - { - $suggestions = []; - - $process = new Process([Plugin::getInstance()->getSettings()->getCliPath(), 'craft', 'help/list'], Craft::getAlias('@root')); - $process->run(); - - if ($process->isSuccessful()) { - $lines = explode("\n", mb_convert_encoding($process->getOutput(), mb_internal_encoding())); - - $data = []; - foreach ($lines as $line) { - if (($pos = strpos($line, '/')) === false) { - $data[$line] = []; - continue; - } - - $data[substr($line, 0, $pos)][] = [ - 'name' => $line, - 'hint' => $line, - ]; - } - - foreach ($data as $label => $commandSuggestions) { - $suggestions[] = [ - 'label' => $label, - 'data' => $commandSuggestions, - ]; - } - } - - return Craft::$app->getView()->renderTemplate('schedule/_components/actions/Command/settings', [ - 'schedule' => $this, - 'suggestions' => $suggestions, - ]); - } -} \ No newline at end of file diff --git a/src/actions/Console.php b/src/actions/Console.php index 25120b4..a62cbad 100644 --- a/src/actions/Console.php +++ b/src/actions/Console.php @@ -3,63 +3,138 @@ namespace panlatent\schedule\actions; use Craft; +use craft\helpers\App; use panlatent\craft\actions\abstract\Action; use panlatent\craft\actions\abstract\ContextInterface; use panlatent\schedule\Plugin; use Symfony\Component\Process\Process; -class Console extends Command +class Console extends Action { - public function execute(ContextInterface $context): bool - { -// $process = new Process($this->buildCommand(), dirname(Craft::$app->request->getScriptFile()), null, null, $this->timeout ?: null); -// -// $process->run(function ($type, $buffer) use ($logId) { -// $output = $buffer . "\n"; -// Craft::$app->getDb()->createCommand() -// ->update(Table::SCHEDULELOGS, [ -// 'status' => self::STATUS_PROCESSING, -// 'output' => new Expression("CONCAT([[output]],:output)", ['output' => $output]), -// ], [ -// 'id' => $logId, -// ]) -// ->execute(); -// }); + public ?string $command = null; + + public ?string $arguments = null; + + public ?string $workDir = null; + + public array $variables = []; + + public ?string $osUser = null; + + public ?int $timeout = null; + + public bool $disableOutput = false; + +// public bool $isPty = true; // -// return $process->isSuccessful(); - } +// public bool $isTty = false; - public function getSettingsHtml(): ?string + public function execute(ContextInterface $context): bool { - return Craft::$app->getView()->renderTemplate('schedule/_components/actions/Console/settings', [ - 'schedule' => $this, - ]); + $output = new StdOutput(); + $context->setOutput($output); + + $process = $this->createProcess(); + + $output = new StdOutput(); + $context->setOutput($output); + + $context->getLogger()->debug("Running command: \"{$process->getCommandLine()}\" in {$this->getWorkDir()}"); + $process->run(function ($type, $buffer) use ($output) { + $type = Process::ERR === $type ? 'error' : 'info'; + $output->writeln("<$type>$buffer"); + }); + + if (!$process->isSuccessful()) { + $context->addError('exitCode', $process->getExitCodeText()); + $context->addError('output', $process->getErrorOutput()); + } + + return !$context->hasErrors(); } - public function getCommandOptions(): array + public function getSettingsHtml(): ?string { - $options = []; + $suggestions = []; - $process = new Process([Plugin::getInstance()->getSettings()->getCliPath(), 'craft', 'help'], Craft::getAlias('@root')); + $process = new Process([Plugin::getInstance()->getSettings()->getCliPath(), 'craft', 'help/list'], Craft::getAlias('@root')); $process->run(); if ($process->isSuccessful()) { $lines = explode("\n", mb_convert_encoding($process->getOutput(), mb_internal_encoding())); + + $data = []; foreach ($lines as $line) { - if (str_starts_with($line, '-')) { - $options[] = ['optgroup' => substr($line, 2)]; + if (($pos = strpos($line, '/')) === false) { + $data[$line] = []; continue; } - if (preg_match('#^\s*(\w+/\w+)\s*(?:\(\w+\)|)\s+(.+)\s*$#', $line, $match)) { - $options[] = [ - 'label' => $match[1] . ' - ' . $match[2], //substr($line, 0, $pos), - 'value' => $match[1], - ]; - } + + $data[substr($line, 0, $pos)][] = [ + 'name' => $line, + 'hint' => $line, + ]; + } + + foreach ($data as $label => $commandSuggestions) { + $suggestions[] = [ + 'label' => $label, + 'data' => $commandSuggestions, + ]; } } - return $options; + return Craft::$app->getView()->renderTemplate('schedule/_components/actions/Console/settings', [ + 'action' => $this, + 'suggestions' => $suggestions, + ]); } + protected function createProcess(): Process + { + $command = $this->buildCommand(); + if ($this->osUser) { + $command = array_merge(['sudo -u', $this->osUser], $command); + } + + $process = new Process($command, $this->getWorkDir(), $this->getEnvironmentVariables(), null, $this->timeout ?: null); +// $process->setTty($this->isTty); +// $process->setPty($this->isPty); + + if ($this->disableOutput) { + $process->disableOutput(); + } + + return $process; + } + + protected function buildCommand(): array + { + $command = [$this->command]; + if ($this->arguments) { + $command[] = $this->arguments; + } + return $command; + } + + protected function defineRules(): array + { + return [ + [['command'], 'required'], + ]; + } + + protected function getWorkDir(): string + { + return $this->workDir ?: dirname(Craft::$app->request->getScriptFile()); + } + + private function getEnvironmentVariables(): array + { + $env = []; + foreach ($this->variables as $key => $value) { + $env[$key] = App::parseEnv($value); + } + return $env; + } } \ No newline at end of file diff --git a/src/actions/CraftConsole.php b/src/actions/CraftConsole.php new file mode 100644 index 0000000..ecd4945 --- /dev/null +++ b/src/actions/CraftConsole.php @@ -0,0 +1,63 @@ +getView()->renderTemplate('schedule/_components/actions/CraftConsole/settings', [ + 'action' => $this, + ]); + } + + public function getCommandOptions(): array + { + $options = []; + + $process = new Process([Plugin::getInstance()->getSettings()->getCliPath(), 'craft', 'help'], Craft::getAlias('@root')); + $process->run(); + + if ($process->isSuccessful()) { + $lines = explode("\n", mb_convert_encoding($process->getOutput(), mb_internal_encoding())); + foreach ($lines as $line) { + if (str_starts_with($line, '-')) { + $options[] = ['optgroup' => substr($line, 2)]; + continue; + } + if (preg_match('#^\s*(\w+/\w+)\s*(?:\(\w+\)|)\s+(.+)\s*$#', $line, $match)) { + $options[] = [ + 'label' => $match[1] . ' - ' . $match[2], + 'value' => $match[1], + ]; + } + } + } + + return $options; + } + + protected function buildCommand(): array + { + return array_merge([ + Plugin::getInstance()->getSettings()->getCliPath(), + self::CRAFT_BIN, + ], parent::buildCommand(), ['--color']); + } + + protected function getWorkDir(): string + { + return Craft::getAlias('@root'); + } +} \ No newline at end of file diff --git a/src/actions/HttpRequest.php b/src/actions/HttpRequest.php index 65d3f2c..f0e059a 100644 --- a/src/actions/HttpRequest.php +++ b/src/actions/HttpRequest.php @@ -9,6 +9,7 @@ use GuzzleHttp\ClientInterface; use panlatent\craft\actions\abstract\Action; use panlatent\craft\actions\abstract\ActionInterface; +use panlatent\craft\actions\abstract\ArrayOutput; use panlatent\craft\actions\abstract\ContextInterface; use panlatent\craft\actions\abstract\OutputInterface; use Psr\Container\NotFoundExceptionInterface; @@ -57,10 +58,13 @@ public function executeWithClient(ContextInterface $context, ClientInterface $cl { $response = $client->request($this->method, $this->url, $this->getOptions()); $statusCode = $response->getStatusCode(); - -// $context->setOutput(new class($response) implements OutputInterface { -// public function __construct(public ResponseInterface $response) {} -// }); + $context->setOutput(new ArrayOutput([ + 'method' => $this->method, + 'url' => $this->url, + 'statusCode' => $statusCode, + 'headers' => $response->getHeaders(), + 'body' => $response->getBody()->getContents(), + ], 'schedule/_components/actions/HttpRequest/output')); return $statusCode >= 200 && $statusCode < 400; } @@ -103,7 +107,6 @@ public function getSettingsHtml(): ?string ]); } - protected function getOptions(): array { $options = []; diff --git a/src/actions/StdOutput.php b/src/actions/StdOutput.php new file mode 100644 index 0000000..84a22c2 --- /dev/null +++ b/src/actions/StdOutput.php @@ -0,0 +1,30 @@ +getView()->renderTemplate('schedule/_components/actions/Console/output.twig', [ + 'message' => $this->message, + ]); + } + + public function writeln(string $message): void + { + $message = ConsoleHelper::ansiToHtml($message); + $this->message .= str_replace(PHP_EOL, '
', $message) . '
'; + } +} \ No newline at end of file diff --git a/src/builder/Schedule.php b/src/builder/Schedule.php index 8e650f1..5ec64da 100644 --- a/src/builder/Schedule.php +++ b/src/builder/Schedule.php @@ -4,8 +4,8 @@ use panlatent\craft\actions\abstract\ActionInterface; use panlatent\schedule\actions\Closure; -use panlatent\schedule\actions\Command; use panlatent\schedule\actions\Console; +use panlatent\schedule\actions\CraftConsole; use panlatent\schedule\actions\HttpRequest; use panlatent\schedule\models\Schedule as ScheduleModel; @@ -27,13 +27,13 @@ public static function closure(\Closure $closure) public static function exec(string $command, array $arguments = []) { - $action = new Command(); + $action = new Console(); return new Schedule($action); } public static function console(string $command, array $arguments = []) { - $action = new Console(); + $action = new CraftConsole(); return new Schedule($action); } diff --git a/src/controllers/ActionsController.php b/src/controllers/ActionsController.php index a5cb6af..6c70946 100644 --- a/src/controllers/ActionsController.php +++ b/src/controllers/ActionsController.php @@ -7,7 +7,12 @@ use craft\web\Controller; use panlatent\craft\actions\abstract\ActionInterface; use panlatent\schedule\actions\HttpRequest; +use panlatent\schedule\log\LogAdapter; +use panlatent\schedule\log\MemoryLog; +use panlatent\schedule\models\Context; use panlatent\schedule\Plugin; +use panlatent\schedule\utilities\ActionRunner; +use Symfony\Component\Stopwatch\Stopwatch; use yii\web\NotFoundHttpException; use yii\web\Response; @@ -62,7 +67,6 @@ public function actionEdit(?int $actionId = null, ?ActionInterface $action = nul ]); } - public function actionRenderSettings(): Response { $this->requirePostRequest(); @@ -72,7 +76,7 @@ public function actionRenderSettings(): Response $action = Plugin::getInstance()->actions->createAction($type); $view = Craft::$app->getView(); - $html = $action->getSettingsHtml(); + $html = $view->renderTemplate('schedule/_includes/forms/actionSetting.twig', ['action' => $action]); return $this->asJson([ 'settingsHtml' => $html, @@ -80,4 +84,35 @@ public function actionRenderSettings(): Response 'bodyHtml' => $view->getBodyHtml(), ]); } + + public function actionRun() + { + $this->requirePostRequest(); + $this->requireAcceptsJson(); + + $view = Craft::$app->getView(); + + $action = Plugin::getInstance()->actions->createActionFromRequest(); + $logger = new MemoryLog(); + $context = new Context($logger); + + $stopwatch = new Stopwatch(); + $stopwatch->start('run'); + $success = Plugin::getInstance()->actions->runActionWithContext($action, $context); + $stopwatch->stop('run'); + + $html = $context->getOutput()->render(); + $duration = $stopwatch->getEvent('run')->getDuration(); + $memory = $stopwatch->getEvent('run')->getMemory(); + + return $this->asJson([ + 'success' => $success, + 'outputHtml' => $html, + 'headHtml' => $view->getHeadHtml(), + 'bodyHtml' => $view->getBodyHtml(), + 'logs' => $logger->getMessages(), + 'duration' => $duration >= 1000 ? $duration/1000 . 's' : $duration . 'ms', + 'usageMemory' => $memory >= 1024*1024 ? $memory/(1024*1024) . 'MB' : $memory/1024 . 'KB', + ]); + } } \ No newline at end of file diff --git a/src/controllers/SchedulesController.php b/src/controllers/SchedulesController.php index b1de28c..b8f21b4 100644 --- a/src/controllers/SchedulesController.php +++ b/src/controllers/SchedulesController.php @@ -193,7 +193,6 @@ public function actionToggleSchedule(): Response $schedule->enabled = (bool)$request->getBodyParam('enabled'); if (!$schedules->saveSchedule($schedule)) { - var_dump($schedule->getErrors()); return $this->asJson(['success' => false]); } diff --git a/src/di/ContainerAdapter.php b/src/di/ContainerAdapter.php index bbba111..ffd6073 100644 --- a/src/di/ContainerAdapter.php +++ b/src/di/ContainerAdapter.php @@ -4,6 +4,7 @@ use Psr\Container\ContainerInterface; use yii\di\Container; +use yii\di\NotInstantiableException; readonly class ContainerAdapter implements ContainerInterface { @@ -14,7 +15,11 @@ public function __construct(protected Container $container) public function get(string $id) { - return $this->container->get($id); + try { + return $this->container->get($id); + } catch (NotInstantiableException $exception) { + throw new NotFoundException('', $exception->getMessage(), $exception->getCode(), $exception->getPrevious()); + } } public function has(string $id): bool diff --git a/src/di/NotFoundException.php b/src/di/NotFoundException.php new file mode 100644 index 0000000..f539ce9 --- /dev/null +++ b/src/di/NotFoundException.php @@ -0,0 +1,11 @@ + + + + diff --git a/src/log/MemoryLog.php b/src/log/MemoryLog.php new file mode 100644 index 0000000..c02895b --- /dev/null +++ b/src/log/MemoryLog.php @@ -0,0 +1,27 @@ +messages[] = [ + 'level' => $level, + 'message' => $message, + 'context' => $context + ]; + } + + public function getMessages(): array + { + return $this->messages; + } +} \ No newline at end of file diff --git a/src/models/Context.php b/src/models/Context.php index b8f826a..48aed02 100644 --- a/src/models/Context.php +++ b/src/models/Context.php @@ -12,11 +12,25 @@ class Context implements ContextInterface { + private ?OutputInterface $output = null; + + private array $errors = []; + public function __construct(protected readonly LoggerInterface $logger, protected readonly ?ContainerInterface $container = null) { } + public function addError(string $attribute, string $error): void + { + $this->errors[$attribute] = $error; + } + + public function hasErrors(): bool + { + return !empty($this->errors); + } + public function getContainer(): ContainerInterface { return $this->container ?? new ContainerAdapter(new Container()); @@ -24,7 +38,7 @@ public function getContainer(): ContainerInterface public function getErrors(): array { - return []; + return $this->errors; } public function getLogger(): LoggerInterface @@ -34,17 +48,17 @@ public function getLogger(): LoggerInterface public function getInput(): InputInterface { - // TODO: Implement getInput() method. + } public function getOutput(): OutputInterface { - // TODO: Implement getOutput() method. + return $this->output; } public function setOutput(OutputInterface $output): void { - // TODO: Implement setOutput() method. + $this->output = $output; } } \ No newline at end of file diff --git a/src/services/Actions.php b/src/services/Actions.php index 6392212..4c60ce0 100644 --- a/src/services/Actions.php +++ b/src/services/Actions.php @@ -10,9 +10,11 @@ use craft\helpers\Component as ComponentHelper; use craft\helpers\Db; use craft\helpers\StringHelper; +use craft\web\Request; use panlatent\craft\actions\abstract\ActionInterface; -use panlatent\schedule\actions\Command; +use panlatent\craft\actions\abstract\ContextInterface; use panlatent\schedule\actions\Console; +use panlatent\schedule\actions\CraftConsole; use panlatent\schedule\actions\ElementAction; use panlatent\schedule\actions\HttpRequest; use panlatent\schedule\actions\SendEmail; @@ -57,8 +59,8 @@ public function getAllActionTypes(): array { $event = new RegisterComponentTypesEvent([ 'types' => [ - Command::class, Console::class, + CraftConsole::class, ElementAction::class, HttpRequest::class, SendEmail::class, @@ -96,7 +98,14 @@ public function runAction(ActionInterface $action): bool { $context = new Context(new LogAdapter(Craft::$app->getLog()->getLogger(), 'action')); + return $this->runActionWithContext($action, $context); + } + public function runActionWithContext(ActionInterface $action, ContextInterface $context, bool $runValidation = true): bool + { +// if ($runValidation && !$action->validate()) { +// return false; +// } return $action->execute($context); } @@ -109,6 +118,17 @@ public function createAction(mixed $config): ActionInterface } } + public function createActionFromRequest(?Request $request = null): ActionInterface + { + if ($request === null) { + $request = Craft::$app->getRequest(); + } + + $type = $request->getBodyParam('actionType'); + $settings = $request->getBodyParam('actionTypes.' . $type) ?? []; + return $this->createAction(['type' => $type, 'settings' => $settings]); + } + public function saveAction(ActionInterface $action, bool $runValidation = true): bool { $isNew = $action->getIsNew(); diff --git a/src/services/Schedules.php b/src/services/Schedules.php index 1de377a..ee3c0c5 100644 --- a/src/services/Schedules.php +++ b/src/services/Schedules.php @@ -421,17 +421,13 @@ public function createScheduleFromRequest(Request $request = null): Schedule $request = Craft::$app->getRequest(); } -// try { - $actionType = $request->getRequiredBodyParam('actionType'); - $actionConfig = $request->getBodyParam('actionTypes.' . $actionType) ?? []; - $action = Plugin::getInstance()->actions->createAction(['type' => $actionType] + $actionConfig); - - $timerType = $request->getRequiredBodyParam('timerType'); - $timerConfig = $request->getBodyParam('timerTypes.' . $timerType) ?? []; - $timer = Plugin::getInstance()->timers->createTimer(['type' => $timerType] + $timerConfig); -// } catch () { -// -// } + $actionType = $request->getRequiredBodyParam('actionType'); + $actionConfig = $request->getBodyParam('actionTypes.' . $actionType) ?? []; + $action = Plugin::getInstance()->actions->createAction(['type' => $actionType] + $actionConfig); + + $timerType = $request->getRequiredBodyParam('timerType'); + $timerConfig = $request->getBodyParam('timerTypes.' . $timerType) ?? []; + $timer = Plugin::getInstance()->timers->createTimer(['type' => $timerType] + $timerConfig); return $this->createSchedule([ 'id' => $request->getBodyParam('scheduleId'), diff --git a/src/templates/_components/actions/Console/output.twig b/src/templates/_components/actions/Console/output.twig new file mode 100644 index 0000000..d6d55a7 --- /dev/null +++ b/src/templates/_components/actions/Console/output.twig @@ -0,0 +1,15 @@ +{% do view.registerAssetBundle('panlatent\\schedule\\web\\assets\\console\\ConsoleAsset') %} +

{{ "Console Output"|t('schedule') }}

+ +
+ {{- message|raw -}} +
+ +{% css %} +.console-tty { + font-family: Hack, monospace; + white-space: pre; + background-color: #000 !important; + color: #969696; +} +{% endcss %} diff --git a/src/templates/_components/actions/Console/settings.twig b/src/templates/_components/actions/Console/settings.twig index 943a914..345e9d0 100644 --- a/src/templates/_components/actions/Console/settings.twig +++ b/src/templates/_components/actions/Console/settings.twig @@ -1,30 +1,80 @@ {% import "_includes/forms" as forms %} -{% set scheduleType = className(schedule) %} +{% set actionType = className(action) %} -{{ forms.selectField({ +{{ forms.autosuggestField({ label: 'Command'|t('schedule'), required: true, id: 'command', name: 'command', - value: schedule.command, - options: schedule.commandOptions, - errors: schedule.getErrors('command'), + value: action.command, + errors: action.getErrors('command'), + suggestions: suggestions, }) }} {{ forms.textField({ label: 'Arguments'|t('schedule'), id: 'arguments', name: 'arguments', - value: schedule.arguments, - errors: schedule.getErrors('arguments'), + value: action.arguments, + errors: action.getErrors('arguments'), +}) }} + +{{ forms.autosuggestField({ + label: 'Work Dirctory'|t('schedule'), + id: 'workDir', + name: 'workDir', + value: action.workDir, + errors: action.getErrors('workDir'), + suggestEnvVars: true, + suggestAliases: true, +}) }} + +{{ forms.editableTableField({ + label: "Environment Variables"|t("schedule"), + id: "variables", + name: "variables", + addRowLabel: "Add a varibale"|t("schedule"), + cols: { + enabled: { + type: 'checkbox', + thin: true, + checked: true, + }, + name: { + type: 'singleline', + heading: "Name"|t("schedule"), + code: true, + }, + value: { + type: 'autosuggest', + heading: "Value"|t("schedule"), + suggestEnvVars: true, + } + }, + rows: action.variables, + defaultValues: { + enabled: true, + }, + allowAdd: true, + allowReorder: true, + allowDelete: true, + errors: action.getErrors("variables"), }) }} {{ forms.textField({ - label: 'Timeout'|t('schedule'), + label: 'Timeout'|t('action'), id: 'timeout', name: 'timeout', - value: schedule.timeout ?: '', + value: action.timeout ?: '', size: 5, - errors: schedule.getErrors('timeout'), + errors: action.getErrors('timeout'), }) }} + +{{ forms.lightswitchField({ + label: 'Disable Output'|t('action'), + id: 'disableOutput', + name: 'disableOutput', + on: action.disableOutput, + errors: action.getErrors('disableOutput'), +}) }} \ No newline at end of file diff --git a/src/templates/_components/actions/Command/settings.twig b/src/templates/_components/actions/CraftConsole/settings.twig similarity index 53% rename from src/templates/_components/actions/Command/settings.twig rename to src/templates/_components/actions/CraftConsole/settings.twig index 8f06f2d..59f5383 100644 --- a/src/templates/_components/actions/Command/settings.twig +++ b/src/templates/_components/actions/CraftConsole/settings.twig @@ -1,30 +1,30 @@ {% import "_includes/forms" as forms %} -{% set scheduleType = className(schedule) %} +{% set actionType = className(action) %} -{{ forms.autosuggestField({ +{{ forms.selectField({ label: 'Command'|t('schedule'), required: true, id: 'command', name: 'command', - value: schedule.command, - errors: schedule.getErrors('command'), - suggestions: suggestions, + value: action.command, + options: action.commandOptions, + errors: action.getErrors('command'), }) }} {{ forms.textField({ label: 'Arguments'|t('schedule'), id: 'arguments', name: 'arguments', - value: schedule.arguments, - errors: schedule.getErrors('arguments'), + value: action.arguments, + errors: action.getErrors('arguments'), }) }} {{ forms.textField({ label: 'Timeout'|t('schedule'), id: 'timeout', name: 'timeout', - value: schedule.timeout ?: '', + value: action.timeout ?: '', size: 5, - errors: schedule.getErrors('timeout'), + errors: action.getErrors('timeout'), }) }} diff --git a/src/templates/_components/actions/HttpRequest/output.twig b/src/templates/_components/actions/HttpRequest/output.twig new file mode 100644 index 0000000..a1f3cff --- /dev/null +++ b/src/templates/_components/actions/HttpRequest/output.twig @@ -0,0 +1,24 @@ +

{{ "HTTP Response"|t("schedule") }}

+ +
+
+
{{ 'URL'|t('schedule') }}
+
{{ method|upper }} {{ url }}
+
+
+ +

{{ "Headers"|t("schedule") }}

+
+ {% for header,values in headers %} +
+
{{ header }}
+
{{ values|join(',') }}
+
+ {% endfor %} +
+ +

{{ "Response Body"|t("schedule") }}

+ +
+ {{ body }} +
\ No newline at end of file diff --git a/src/templates/_components/utilities/ActionRunner.twig b/src/templates/_components/utilities/ActionRunner.twig index 67f8b2b..e8888ce 100644 --- a/src/templates/_components/utilities/ActionRunner.twig +++ b/src/templates/_components/utilities/ActionRunner.twig @@ -1,10 +1,50 @@ {% import "schedule/_includes/forms.twig" as forms %} -
+ + {{ forms.action({ + action: action, + }) }} -{{ forms.action({ - action: action, -}) }} +
+ + + + +
+
- - \ No newline at end of file +
+
+ +{% css %} +#action-logs { + +} +{% endcss %} + +{% js %} +$('#action-runner').on('submit', function(value) { + let form = $(this); + let _cancelToken = axios.CancelToken.source(); + Craft.sendActionRequest("POST", "schedule/actions/run", { + cancelToken: _cancelToken.token, + data: form.serialize() + }).then(function(response) { + $('#action-runner .status').html(''); + $('#action-runner .duration').html(response.data.duration); + $('#action-runner .usage-memory').html(response.data.usageMemory); + + + let $logs = $('#action-logs'); + for (let log of $(response.data.logs)) { + $logs.append($(`

${log.level} ${log.message}

`)); + } + + let $output = $(response.data.outputHtml || ''); + $('#action-output').html($output); + Craft.appendHeadHtml(response.data.headHtml); + Craft.appendBodyHtml(response.data.bodyHtml); + }) + return false; +}) +{% endjs %} \ No newline at end of file diff --git a/src/templates/_includes/forms/action.twig b/src/templates/_includes/forms/action.twig index 81338d5..a39803d 100644 --- a/src/templates/_includes/forms/action.twig +++ b/src/templates/_includes/forms/action.twig @@ -25,9 +25,7 @@ {% set isCurrent = (actionType == className(action)) %}