danilovl / web-command-bundle
A Symfony bundle for running console commands via web interface
Package info
github.com/danilovl/web-command-bundle
Type:symfony-bundle
pkg:composer/danilovl/web-command-bundle
Requires
- php: ^8.5
- doctrine/doctrine-bundle: ^3.2.2
- doctrine/orm: ^3.6.2
- easycorp/easyadmin-bundle: ^4.27.6
- symfony/asset: ^8.0
- symfony/console: ^8.0
- symfony/event-dispatcher: ^8.0
- symfony/framework-bundle: ^8.0
- symfony/messenger: ^8.0
- symfony/process: ^8.0
- symfony/twig-bundle: ^8.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.92.5
- phpstan/extension-installer: ^1.4.3
- phpstan/phpstan: ^2.1.33
- phpstan/phpstan-symfony: ^2.0.9
- phpunit/phpunit: ^12.0
README
WebCommandBundle
A Symfony bundle for running console commands via web interface (REST API). Supports both synchronous and asynchronous (via Messenger) execution.
Features
- REST API for listing and running commands.
- Support for asynchronous command execution using Symfony Messenger.
- Configurable API prefix, console path, memory and time limits.
- Entity-based management for commands, jobs and history.
- EasyAdmin integration for managing commands and viewing history.
- Flexible command configuration (voters, async, history, parameters, etc.).
- Automatic PHP binary detection.
Available dashboard
Installation
- Install the bundle via composer:
composer require danilovl/web-command-bundle
- Add the bundle to
config/bundles.php:
return [ // ... Danilovl\WebCommandBundle\WebCommandBundle::class => ['all' => true], ];
- Import routes in
config/routing.yaml(routing is not automatic):
web_command: resource: '@WebCommandBundle/src/Resources/config/routing.yaml'
- Doctrine entity mapping
Add entity mapping to doctrine.yaml in packages folder.
orm: mappings: Danilovl\WebCommandBundle: is_bundle: false type: attribute dir: "%kernel.project_dir%/vendor/danilovl/web-command-bundle/src/Entity/" prefix: 'Danilovl\WebCommandBundle\Entity'
- Update your database schema:
php bin/console doctrine:migrations:diff php bin/console doctrine:migrations:migrate
Configuration
Create config/packages/web_command.yaml:
danilovl_web_command: api_prefix: '/danilovl/web-command/api' console_path: null enable_async: false default_timeout: null default_time_limit: null default_memory_limit: null enabled_admin_dashboard: false enabled_dashboard_live_status: true
Note:
console_path: ifnull(default), it's%kernel.project_dir%/bin/console. If set, it's used as is (absolute path).default_timeout,default_time_limitanddefault_memory_limitarenullby default.default_timeout: default timeout (seconds) for synchronous command execution via API.default_time_limit: default timeout (seconds) for asynchronous command execution.default_memory_limit: applied via-d memory_limit=...during process execution.enabled_admin_dashboard: enable EasyAdmin dashboard integration.enabled_dashboard_live_status: enable/disable interval requests to check job status on the web dashboard.
Messenger Configuration
To enable asynchronous execution, configure Symfony Messenger:
framework: messenger: transports: async: '%env(MESSENGER_TRANSPORT_DSN)%' routing: 'Danilovl\WebCommandBundle\Message\RunCommandMessage': async
Usage
REST API
The bundle provides a REST API to interact with commands. By default, the API is available under the /danilovl/web-command/api prefix.
Admin Interface
The bundle includes a built-in EasyAdmin dashboard for managing commands and viewing execution history.
To use it, ensure you have EasyAdminBundle installed and configured, and enable it in the configuration:
danilovl_web_command: enabled_admin_dashboard: true
The dashboard is available at /admin/danilovl/web-command.
The admin interface allows you to:
- Create, update, and delete commands.
- View execution history and command outputs.
Web Dashboard
The bundle also provides a standalone web dashboard to run commands and view their history.
The dashboard is available at /danilovl/web-command/dashboard.
Route Name: danilovl_web_command_dashboard_index
Features:
- List of active commands.
- Input for additional parameters.
- Formatted output display.
- History of recent executions with pagination.
To use the web dashboard, you need to install assets:
php bin/console assets:install
Managing Commands
Each command is represented as an entity with several configuration options:
- Name: The display name of the command.
- Command: The actual Symfony console command (e.g.,
app:sync-users). - Parameters: Default arguments and options (e.g.,
["--force", "prod"]). - Allow Custom Parameters: If enabled, users can provide additional parameters when running the command.
- Async: Enable asynchronous execution via Symfony Messenger.
- Save History: Whether to record execution history in the database.
- Save Output: Save the command's stdout/stderr in the history record.
- Voter Class: Specify a Symfony Voter class or a security role (e.g.,
ROLE_ADMIN) to restrict command execution based on custom logic. - Active: Easily enable/disable command from being executed.
- Description: Add notes about the command's purpose.
When adding a command, specify its name and optional default parameters.
The Parameters field must be a JSON array of strings. These are arguments and options passed to the command.
Example for app:example --fix:
- Name:
app:example - Parameters:
["--fix"]
Example for app:example arg1 --option=value:
- Name:
app:example - Parameters:
["arg1", "--option=value"]
1. List Commands
Retrieve a list of all active commands that can be executed via the web interface.
Endpoint: GET /danilovl/web-command/api/commands/
Route Name: danilovl_web_commands
Response (200 OK):
[
{
"id": 1,
"name": "app:example-command",
"command": "app:example",
"allowCustomParameters": true,
"voterClass": "App\\Security\\Voter\\ExampleVoter",
"description": "This is an example command description",
"active": true,
"async": false,
"saveHistory": true,
"saveOutput": false,
"parameters": [],
"createdAt": "2024-03-28T10:00:00+00:00",
"updatedAt": null
}
]
2. Run Command
Execute a command.
Endpoint: POST /danilovl/web-command/api/commands/{id}/run
Route Name: danilovl_web_command_run
Request Body:
{
"input": ["--option=value", "argument"],
"timeout": 300
}
Response (200 OK - Synchronous execution):
{
"output": "Command output string...",
"exitCode": 0,
"duration": 1.5
}
Response (202 Accepted - Asynchronous execution):
Returned when the command has async: true and asynchronous execution is enabled.
{
"status": "queued",
"jobId": 1
}
Response (404 Not Found):
{
"error": "Command not found or disabled"
}
3. List Histories
Retrieve a paginated list of histories for a specific command.
Endpoint: GET /danilovl/web-command/api/commands/{command}/histories
Route Name: danilovl_web_command_histories
Path Parameters:
command: The ID of the command.
Query Parameters:
page(optional): Page number (default: 1)
Response (200 OK):
{
"histories": [
{
"id": 1,
"command": { "id": 1, "name": "app:example-command", ... },
"async": true,
"duration": 1.5,
"exitCode": 0,
"errorMessage": null,
"output": "Command output...",
"metaInfo": { "userId": 1, "userName": "admin" },
"createdAt": "2024-03-28 10:00:00"
}
],
"totalPages": 1,
"currentPage": 1
}
4. Get History Details
Retrieve details and output of a specific history record.
Endpoint: GET /danilovl/web-command/api/commands/histories/{id}
Route Name: danilovl_web_command_history_detail
Response (200 OK):
{
"id": 1,
"command": { "id": 1, "name": "app:example-command", ... },
"async": true,
"duration": 1.5,
"exitCode": 0,
"errorMessage": null,
"output": "Command output...",
"metaInfo": { "userId": 1, "userName": "admin" },
"createdAt": "2024-03-28 10:00:00"
}
Response (404 Not Found):
{
"error": "History not found"
}
5. Get Job Status
Retrieve the status of a specific job.
Endpoint: GET /danilovl/web-command/api/jobs/{id}/status
Route Name: danilovl_web_command_job_status
Response (200 OK):
{
"status": "queued"
}
Security
Voter
Each command can be protected by a Symfony Voter. Specify a Voter Class (or a role like ROLE_ADMIN) in the command configuration. The bundle will check permissions using AuthorizationCheckerInterface::isGranted().
URL Security
To restrict access to the dashboards and API to specific roles, configure Symfony's access_control in config/packages/security.yaml:
security: access_control: - { path: ^/api/web, roles: ROLE_ADMIN } - { path: ^/danilovl/web-command/dashboard, roles: ROLE_ADMIN }
Events
The bundle dispatches events during the command execution lifecycle, allowing you to intercept and modify the process.
CommandStartEvent
Dispatched before a command is executed.
Usage:
- Cancel command execution.
- Modify command input.
- Add or modify meta information.
use Danilovl\WebCommandBundle\Event\CommandStartEvent; use Symfony\Component\EventDispatcher\Attribute\AsEventListener; #[AsEventListener] class CommandStartListener { public function __invoke(CommandStartEvent $event): void { if ($someCondition) { $event->setShouldContinue(false); $event->setReason('Reason for cancellation'); } $metaInfo = $event->getMetaInfo() ?? []; $metaInfo['custom_key'] = 'custom_value'; $event->setMetaInfo($metaInfo); } }
CommandEndEvent
Dispatched after a command has finished execution (successfully or with an error).
use Danilovl\WebCommandBundle\Event\CommandEndEvent; use Symfony\Component\EventDispatcher\Attribute\AsEventListener; #[AsEventListener] class CommandEndListener { public function __invoke(CommandEndEvent $event): void { $exitCode = $event->getExitCode(); $output = $event->getOutput(); // Do something with the result } }
Meta Information
The History entity includes a metaInfo field (JSON). By default, if the Symfony Security component is available, it automatically stores:
userId: ID of the current user.userIdentifier: Identifier of the current user.
You can customize this data via CommandStartEvent::setMetaInfo(). If you set it to null, no meta information will be saved.
Database Schema Example
CREATE TABLE danilovl_web_command ( id INT AUTO_INCREMENT NOT NULL, name VARCHAR(255) NOT NULL, command VARCHAR(255) NOT NULL, parameters JSON NOT NULL, allow_custom_parameters TINYINT NOT NULL, voter_class VARCHAR(255) DEFAULT NULL, active TINYINT NOT NULL, async TINYINT NOT NULL, save_history TINYINT(1) NOT NULL DEFAULT 1, save_output TINYINT(1) NOT NULL DEFAULT 0, description LONGTEXT DEFAULT NULL, created_at DATETIME NOT NULL, updated_at DATETIME DEFAULT NULL, UNIQUE INDEX UNIQ_73DBE01B5E237E06 (name), PRIMARY KEY (id) ) DEFAULT CHARACTER SET utf8mb4; CREATE TABLE danilovl_web_command_job ( id INT AUTO_INCREMENT NOT NULL, command_id INT NOT NULL, input JSON NOT NULL, status VARCHAR(20) NOT NULL, created_at DATETIME NOT NULL, PRIMARY KEY (id), INDEX IDX_3389C56E33E1689A (command_id), CONSTRAINT FK_3389C56E33E1689A FOREIGN KEY (command_id) REFERENCES danilovl_web_command (id) ON DELETE CASCADE ) DEFAULT CHARACTER SET utf8mb4; CREATE TABLE danilovl_web_command_history ( id INT AUTO_INCREMENT NOT NULL, command_id INT NOT NULL, async TINYINT(1) NOT NULL, duration DOUBLE PRECISION NOT NULL, exit_code INT NOT NULL, error_message LONGTEXT DEFAULT NULL, output LONGTEXT DEFAULT NULL, meta_info JSON DEFAULT NULL, created_at DATETIME NOT NULL, PRIMARY KEY (id), INDEX IDX_HISTORY_COMMAND (command_id), CONSTRAINT FK_HISTORY_COMMAND FOREIGN KEY (command_id) REFERENCES danilovl_web_command (id) ON DELETE CASCADE ) DEFAULT CHARACTER SET utf8mb4;
License
The WebCommandBundle is open-sourced software licensed under the MIT license.
