Skip to content

Commit

Permalink
Scripts (#233)
Browse files Browse the repository at this point in the history
  • Loading branch information
saeedvaziry authored Jun 8, 2024
1 parent 3b42f93 commit a862a60
Show file tree
Hide file tree
Showing 36 changed files with 1,127 additions and 23 deletions.
4 changes: 3 additions & 1 deletion app/Actions/Database/CreateDatabase.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ public function create(Server $server, array $input): Database
'server_id' => $server->id,
'name' => $input['name'],
]);
$server->database()->handler()->create($database->name);
/** @var \App\SSH\Services\Database\Database */
$databaseHandler = $server->database()->handler();
$databaseHandler->create($database->name);
$database->status = DatabaseStatus::READY;
$database->save();

Expand Down
32 changes: 32 additions & 0 deletions app/Actions/Script/CreateScript.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace App\Actions\Script;

use App\Models\Script;
use App\Models\User;
use Illuminate\Support\Facades\Validator;

class CreateScript
{
public function create(User $user, array $input): Script
{
$this->validate($input);

$script = new Script([
'user_id' => $user->id,
'name' => $input['name'],
'content' => $input['content'],
]);
$script->save();

return $script;
}

private function validate(array $input): void
{
Validator::make($input, [
'name' => ['required', 'string', 'max:255'],
'content' => ['required', 'string'],
])->validate();
}
}
28 changes: 28 additions & 0 deletions app/Actions/Script/EditScript.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace App\Actions\Script;

use App\Models\Script;
use Illuminate\Support\Facades\Validator;

class EditScript
{
public function edit(Script $script, array $input): Script
{
$this->validate($input);

$script->name = $input['name'];
$script->content = $input['content'];
$script->save();

return $script;
}

private function validate(array $input): void
{
Validator::make($input, [
'name' => ['required', 'string', 'max:255'],
'content' => ['required', 'string'],
])->validate();
}
}
62 changes: 62 additions & 0 deletions app/Actions/Script/ExecuteScript.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

namespace App\Actions\Script;

use App\Enums\ScriptExecutionStatus;
use App\Models\Script;
use App\Models\ScriptExecution;
use App\Models\Server;
use App\Models\ServerLog;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;

class ExecuteScript
{
public function execute(Script $script, Server $server, array $input): ScriptExecution
{
$this->validate($server, $input);

$execution = new ScriptExecution([
'script_id' => $script->id,
'user' => $input['user'],
'variables' => $input['variables'] ?? [],
'status' => ScriptExecutionStatus::EXECUTING,
]);
$execution->save();

dispatch(function () use ($execution, $server, $script) {
$content = $execution->getContent();
$log = ServerLog::make($server, 'script-'.$script->id.'-'.strtotime('now'));
$log->save();
$execution->server_log_id = $log->id;
$execution->save();
$server->os()->runScript('~/', $content, $log, $execution->user);
$execution->status = ScriptExecutionStatus::COMPLETED;
$execution->save();
})->catch(function () use ($execution) {
$execution->status = ScriptExecutionStatus::FAILED;
$execution->save();
})->onConnection('ssh');

return $execution;
}

private function validate(Server $server, array $input): void
{
Validator::make($input, [
'user' => [
'required',
Rule::in([
'root',
$server->ssh_user,
]),
],
'variables' => 'array',
'variables.*' => [
'required',
'string',
'max:255',
],
])->validate();
}
}
12 changes: 12 additions & 0 deletions app/Enums/ScriptExecutionStatus.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace App\Enums;

final class ScriptExecutionStatus
{
const EXECUTING = 'executing';

const COMPLETED = 'completed';

const FAILED = 'failed';
}
111 changes: 111 additions & 0 deletions app/Http/Controllers/ScriptController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?php

namespace App\Http\Controllers;

use App\Actions\Script\CreateScript;
use App\Actions\Script\EditScript;
use App\Actions\Script\ExecuteScript;
use App\Facades\Toast;
use App\Helpers\HtmxResponse;
use App\Models\Script;
use App\Models\ScriptExecution;
use App\Models\Server;
use App\Models\User;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

class ScriptController extends Controller
{
public function index(Request $request): View
{
$this->authorize('viewAny', Script::class);

/** @var User $user */
$user = auth()->user();

$data = [
'scripts' => $user->scripts,
];

if ($request->has('edit')) {
$data['editScript'] = $user->scripts()->findOrFail($request->input('edit'));
}

if ($request->has('execute')) {
$data['executeScript'] = $user->scripts()->findOrFail($request->input('execute'));
}

return view('scripts.index', $data);
}

public function show(Script $script): View
{
$this->authorize('view', $script);

return view('scripts.show', [
'script' => $script,
'executions' => $script->executions()->latest()->paginate(20),
]);
}

public function store(Request $request): HtmxResponse
{
$this->authorize('create', Script::class);

/** @var User $user */
$user = auth()->user();

app(CreateScript::class)->create($user, $request->input());

Toast::success('Script created.');

return htmx()->redirect(route('scripts.index'));
}

public function edit(Request $request, Script $script): HtmxResponse
{
$this->authorize('update', $script);

app(EditScript::class)->edit($script, $request->input());

Toast::success('Script updated.');

return htmx()->redirect(route('scripts.index'));
}

public function execute(Script $script, Request $request): HtmxResponse
{
$this->validate($request, [
'server' => 'required|exists:servers,id',
]);

$server = Server::findOrFail($request->input('server'));

$this->authorize('execute', [$script, $server]);

app(ExecuteScript::class)->execute($script, $server, $request->input());

Toast::success('Executing the script...');

return htmx()->redirect(route('scripts.show', $script));
}

public function delete(Script $script): RedirectResponse
{
$this->authorize('delete', $script);

$script->delete();

Toast::success('Script deleted.');

return redirect()->route('scripts.index');
}

public function log(Script $script, ScriptExecution $execution): RedirectResponse
{
$this->authorize('view', $script);

return back()->with('content', $execution->serverLog?->getContent());
}
}
66 changes: 66 additions & 0 deletions app/Models/Script.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

namespace App\Models;

use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Support\Collection;

/**
* @property int $id
* @property int $user_id
* @property string $name
* @property string $content
* @property Carbon $created_at
* @property Carbon $updated_at
* @property Collection<ScriptExecution> $executions
* @property ?ScriptExecution $lastExecution
*/
class Script extends AbstractModel
{
use HasFactory;

protected $fillable = [
'user_id',
'name',
'content',
];

public static function boot(): void
{
parent::boot();

static::deleting(function (Script $script) {
$script->executions()->delete();
});
}

public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}

public function getVariables(): array
{
$variables = [];
preg_match_all('/\${(.*?)}/', $this->content, $matches);
foreach ($matches[1] as $match) {
$variables[] = $match;
}

return array_unique($variables);
}

public function executions(): HasMany
{
return $this->hasMany(ScriptExecution::class);
}

public function lastExecution(): HasOne
{
return $this->hasOne(ScriptExecution::class)->latest();
}
}
61 changes: 61 additions & 0 deletions app/Models/ScriptExecution.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

namespace App\Models;

use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

/**
* @property int $id
* @property int $script_id
* @property int $server_log_id
* @property string $user
* @property array $variables
* @property string $status
* @property Carbon $created_at
* @property Carbon $updated_at
* @property Script $script
* @property ?ServerLog $serverLog
*/
class ScriptExecution extends Model
{
use HasFactory;

protected $fillable = [
'script_id',
'server_log_id',
'user',
'variables',
'status',
];

protected $casts = [
'script_id' => 'integer',
'server_log_id' => 'integer',
'variables' => 'array',
];

public function script(): BelongsTo
{
return $this->belongsTo(Script::class);
}

public function getContent(): string
{
$content = $this->script->content;
foreach ($this->variables as $variable => $value) {
if (is_string($value) && ! empty($value)) {
$content = str_replace('${'.$variable.'}', $value, $content);
}
}

return $content;
}

public function serverLog(): BelongsTo
{
return $this->belongsTo(ServerLog::class);
}
}
Loading

0 comments on commit a862a60

Please sign in to comment.