Files
keystone/app/Http/Controllers/OperationController.php
Harry Bayliss 5b977c1f41
Some checks failed
CI / Lint (push) Failing after 22s
CI / Tests (push) Failing after 33s
wowowowowo
2026-05-28 15:15:41 +01:00

172 lines
6.8 KiB
PHP

<?php
namespace App\Http\Controllers;
use App\Enums\OperationKind;
use App\Enums\OperationStatus;
use App\Models\Application;
use App\Models\Environment;
use App\Models\Operation;
use App\Models\Organisation;
use App\Models\Server;
use App\Models\Service;
use App\Models\ServiceReplica;
use App\Models\ServiceSlice;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Inertia\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;
class OperationController extends Controller
{
public function index(Request $request, Organisation $organisation): Response
{
$applicationIds = $organisation->applications()->pluck('id');
$environmentIds = Environment::query()
->whereIn('application_id', $applicationIds)
->pluck('id');
$serverIds = $organisation->servers()->pluck('id');
$serviceIds = $organisation->services()->pluck('id');
$replicaIds = ServiceReplica::query()
->whereIn('service_id', $serviceIds)
->pluck('id');
$sliceIds = ServiceSlice::query()
->whereIn('service_id', $serviceIds)
->pluck('id');
$operations = Operation::query()
->with(['target', 'parent', 'children.target'])
->withCount('steps', 'children')
->where(function (Builder $query) use ($applicationIds, $environmentIds, $serverIds, $serviceIds, $replicaIds, $sliceIds): void {
$query
->where(function (Builder $query) use ($applicationIds): void {
$query->where('target_type', (new Application)->getMorphClass())
->whereIn('target_id', $applicationIds);
})
->orWhere(function (Builder $query) use ($environmentIds): void {
$query->where('target_type', (new Environment)->getMorphClass())
->whereIn('target_id', $environmentIds);
})
->orWhere(function (Builder $query) use ($serverIds): void {
$query->where('target_type', (new Server)->getMorphClass())
->whereIn('target_id', $serverIds);
})
->orWhere(function (Builder $query) use ($serviceIds): void {
$query->where('target_type', (new Service)->getMorphClass())
->whereIn('target_id', $serviceIds);
})
->orWhere(function (Builder $query) use ($replicaIds): void {
$query->where('target_type', (new ServiceReplica)->getMorphClass())
->whereIn('target_id', $replicaIds);
})
->orWhere(function (Builder $query) use ($sliceIds): void {
$query->where('target_type', (new ServiceSlice)->getMorphClass())
->whereIn('target_id', $sliceIds);
});
})
->when($request->filled('kind'), fn (Builder $query) => $query->where('kind', $request->string('kind')->toString()))
->when($request->filled('status'), fn (Builder $query) => $query->where('status', $request->string('status')->toString()))
->latest()
->paginate(30)
->withQueryString();
return inertia('operations/Index', [
'operations' => $operations,
'filters' => $request->only(['kind', 'status']),
'operationKinds' => OperationKind::toArray(),
'operationStatuses' => OperationStatus::toArray(),
]);
}
public function show(Organisation $organisation, Operation $operation): Response
{
abort_unless($this->operationBelongsToOrganisation($operation, $organisation), 404);
$operation->load([
'target',
'parent.target',
'children.target',
'children.steps',
'steps',
]);
return inertia('operations/Show', [
'operation' => $operation,
]);
}
public function retry(Organisation $organisation, Operation $operation): RedirectResponse
{
abort_unless($this->operationBelongsToOrganisation($operation, $organisation), 404);
$operation->update([
'status' => OperationStatus::PENDING,
'started_at' => null,
'finished_at' => null,
]);
return redirect()
->route('operations.show', [
'organisation' => $organisation->id,
'operation' => $operation->id,
])
->with('success', 'Operation queued to run again.');
}
public function cancel(Organisation $organisation, Operation $operation): RedirectResponse
{
abort_unless($this->operationBelongsToOrganisation($operation, $organisation), 404);
$operation->update([
'status' => OperationStatus::CANCELLED,
'finished_at' => now(),
]);
return redirect()
->route('operations.show', [
'organisation' => $organisation->id,
'operation' => $operation->id,
])
->with('success', 'Operation cancelled.');
}
public function downloadLogs(Organisation $organisation, Operation $operation): StreamedResponse
{
abort_unless($this->operationBelongsToOrganisation($operation, $organisation), 404);
$operation->load('steps');
return response()->streamDownload(function () use ($operation): void {
foreach ($operation->steps as $step) {
echo "# {$step->name}\n\n";
if ($step->logs) {
echo "## Logs\n{$step->logs}\n\n";
}
if ($step->error_logs) {
echo "## Error Logs\n{$step->error_logs}\n\n";
}
}
}, "operation-{$operation->hash}.log", [
'Content-Type' => 'text/plain',
]);
}
private function operationBelongsToOrganisation(Operation $operation, Organisation $organisation): bool
{
$target = $operation->target;
return match (true) {
$target instanceof Application => $target->organisation_id === $organisation->id,
$target instanceof Environment => $target->application()->where('organisation_id', $organisation->id)->exists(),
$target instanceof Server => $target->organisation_id === $organisation->id,
$target instanceof Service => $target->organisation_id === $organisation->id,
$target instanceof ServiceReplica => $target->service()->where('organisation_id', $organisation->id)->exists(),
$target instanceof ServiceSlice => $target->service()->where('organisation_id', $organisation->id)->exists(),
default => false,
};
}
}