-
Notifications
You must be signed in to change notification settings - Fork 222
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
3b42f93
commit a862a60
Showing
36 changed files
with
1,127 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
Oops, something went wrong.