Automation administration dashboard

* Manual automations
* Scheduled automations
* Incoming webhook triggers
* Domain-event trigger foundation
* Ordered automation steps
* Queued execution
* Run status and execution history
* Per-step execution logs
* Continue-or-stop-on-failure control
* Email actions
* Outgoing webhook actions
* Application log actions
* Delay actions
* Secure incoming webhook secrets
* Automation trigger and action registries
* Extensible action and trigger contracts
* Scheduled automation Artisan command
* Administration navigation
* Automation permissions
* Module registration and boot test
This commit is contained in:
Mark Gregory
2026-07-24 12:38:06 +01:00
parent 7fb71c4d24
commit bf37548faa
25 changed files with 389 additions and 1 deletions
@@ -0,0 +1,50 @@
<?php
namespace Modules\Automation\Services;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Modules\Automation\Contracts\ActionRegistry;
use Modules\Automation\Models\AutomationStep;
final class AutomationActionRegistry implements ActionRegistry
{
public function all(): array
{
return [
'log'=>['label'=>'Write log','fields'=>['message']],
'webhook'=>['label'=>'Send webhook','fields'=>['url','method']],
'email'=>['label'=>'Send email','fields'=>['to','subject','body']],
'delay'=>['label'=>'Delay','fields'=>['seconds']],
];
}
public function execute(AutomationStep $step, array $payload): array
{
$config=$step->configuration ?? [];
return match($step->action_type) {
'log'=>$this->log($config,$payload),
'webhook'=>$this->webhook($config,$payload),
'email'=>$this->email($config,$payload),
'delay'=>$this->delay($config),
default=>throw new \InvalidArgumentException("Unknown automation action [{$step->action_type}]."),
};
}
private function log(array $config,array $payload): array
{
$message=(string)($config['message']??'Automation executed'); Log::info($message,$payload); return ['message'=>$message];
}
private function webhook(array $config,array $payload): array
{
$url=(string)($config['url']??''); if ($url==='') throw new \InvalidArgumentException('Webhook URL is required.');
$method=strtolower((string)($config['method']??'post')); $response=Http::timeout(config('automation.webhook_timeout',10))->send($method,$url,['json'=>$payload]);
$response->throw(); return ['status'=>$response->status()];
}
private function email(array $config,array $payload): array
{
$to=(string)($config['to']??''); if ($to==='') throw new \InvalidArgumentException('Email recipient is required.');
$subject=(string)($config['subject']??'Automation notification'); $body=(string)($config['body']??json_encode($payload,JSON_PRETTY_PRINT));
Mail::raw($body,fn($message)=>$message->to($to)->subject($subject)); return ['to'=>$to];
}
private function delay(array $config): array
{
$seconds=max(0,min(30,(int)($config['seconds']??1))); if ($seconds>0) sleep($seconds); return ['seconds'=>$seconds];
}
}
@@ -0,0 +1,28 @@
<?php
namespace Modules\Automation\Services;
use Illuminate\Support\Str;
use Modules\Automation\Jobs\ExecuteAutomation;
use Modules\Automation\Models\Automation;
use Modules\Automation\Models\AutomationRun;
final class AutomationRunner
{
public function dispatch(Automation $automation,array $payload=[],?string $triggerType=null): AutomationRun
{
abort_unless($automation->isActive(),422,'Automation is not active.');
$run=$automation->runs()->create(['execution_id'=>(string)Str::uuid(),'trigger_type'=>$triggerType??$automation->trigger_type,'payload'=>$payload,'status'=>'queued']);
ExecuteAutomation::dispatch($run->id)->onQueue(config('automation.queue','default'));
return $run;
}
public function dispatchMatching(string $triggerType,array $payload=[],array $constraints=[]): int
{
$query=Automation::query()->where('status','active')->where('trigger_type',$triggerType);
$count=0; foreach($query->get() as $automation) { if($this->matches($automation,$constraints)){ $this->dispatch($automation,$payload,$triggerType); $count++; } }
return $count;
}
private function matches(Automation $automation,array $constraints): bool
{
$config=$automation->trigger_config??[];
foreach($constraints as $key=>$value) if(isset($config[$key]) && (string)$config[$key] !== (string)$value) return false;
return true;
}
}
@@ -0,0 +1,16 @@
<?php
namespace Modules\Automation\Services;
use Modules\Automation\Contracts\TriggerRegistry;
final class AutomationTriggerRegistry implements TriggerRegistry
{
public function all(): array
{
return [
'manual'=>['label'=>'Manual','description'=>'Run from the administration area or API.'],
'schedule'=>['label'=>'Schedule','description'=>'Run when next_run_at is due.'],
'webhook'=>['label'=>'Incoming webhook','description'=>'Run from a signed API request.'],
'domain_event'=>['label'=>'Domain event','description'=>'Run when an application event is dispatched.'],
];
}
public function has(string $type): bool { return array_key_exists($type,$this->all()); }
}