mirror of
https://github.com/vitodeploy/vito.git
synced 2025-07-01 05:56:16 +00:00
Compare commits
33 Commits
Author | SHA1 | Date | |
---|---|---|---|
9d4c7972ed | |||
abe27cde01 | |||
49137a757b | |||
97e20206e8 | |||
176ff3bbc4 | |||
b2440586d6 | |||
4b8e798e66 | |||
6143eb94b4 | |||
e52903c649 | |||
1a5cf4c57a | |||
d8ece27964 | |||
f54c754971 | |||
7cda14cb76 | |||
3bf3f7eebc | |||
e17fdbb1a0 | |||
e99146209e | |||
1223ea1499 | |||
a1cf09e35d | |||
2356e44f5b | |||
8c7c3d2192 | |||
e2b9d18a71 | |||
75e554ad74 | |||
4d59529767 | |||
ea31d0978f | |||
ee4e9e5452 | |||
48c12e26b2 | |||
72f68b5917 | |||
a889cf11a2 | |||
75a4fde8de | |||
fd67097884 | |||
705d029a63 | |||
7be63384d4 | |||
dd78c86a60 |
7
.github/workflows/docker-latest.yml
vendored
7
.github/workflows/docker-latest.yml
vendored
@ -2,9 +2,9 @@ name: Docker Latest
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
# push:
|
||||||
branches:
|
# branches:
|
||||||
- 2.x
|
# - 2.x
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-push:
|
build-and-push:
|
||||||
@ -31,5 +31,4 @@ jobs:
|
|||||||
-f docker/Dockerfile \
|
-f docker/Dockerfile \
|
||||||
-t vitodeploy/vito:latest \
|
-t vitodeploy/vito:latest \
|
||||||
--platform linux/amd64,linux/arm64 \
|
--platform linux/amd64,linux/arm64 \
|
||||||
--no-cache \
|
|
||||||
--push
|
--push
|
||||||
|
4
.github/workflows/docker-release.yml
vendored
4
.github/workflows/docker-release.yml
vendored
@ -2,8 +2,8 @@ name: Docker Release
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
release:
|
# release:
|
||||||
types: [ created ]
|
# types: [ created ]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-push:
|
build-and-push:
|
||||||
|
3
.github/workflows/tests.yml
vendored
3
.github/workflows/tests.yml
vendored
@ -40,5 +40,8 @@ jobs:
|
|||||||
- name: Create sqlite database
|
- name: Create sqlite database
|
||||||
run: touch storage/database-test.sqlite
|
run: touch storage/database-test.sqlite
|
||||||
|
|
||||||
|
- name: Set up the .env file
|
||||||
|
run: touch .env
|
||||||
|
|
||||||
- name: Run test suite
|
- name: Run test suite
|
||||||
run: php artisan test
|
run: php artisan test
|
||||||
|
@ -15,16 +15,12 @@ class ManageBackupFile
|
|||||||
*/
|
*/
|
||||||
public function download(BackupFile $file): StreamedResponse
|
public function download(BackupFile $file): StreamedResponse
|
||||||
{
|
{
|
||||||
$localFilename = "backup_{$file->id}_{$file->name}.zip";
|
$file->backup->server->ssh()->download(
|
||||||
|
Storage::disk('tmp')->path(basename($file->path())),
|
||||||
|
$file->path()
|
||||||
|
);
|
||||||
|
|
||||||
if (! Storage::disk('backups')->exists($localFilename)) {
|
return Storage::disk('tmp')->download(basename($file->path()));
|
||||||
$file->backup->server->ssh()->download(
|
|
||||||
Storage::disk('backups')->path($localFilename),
|
|
||||||
$file->path()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Storage::disk('backups')->download($localFilename, $file->name.'.zip');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete(BackupFile $file): void
|
public function delete(BackupFile $file): void
|
||||||
|
39
app/Actions/FileManager/FetchFiles.php
Normal file
39
app/Actions/FileManager/FetchFiles.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\FileManager;
|
||||||
|
|
||||||
|
use App\Exceptions\SSHError;
|
||||||
|
use App\Models\File;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
|
class FetchFiles
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @throws SSHError
|
||||||
|
*/
|
||||||
|
public function fetch(User $user, Server $server, array $input): void
|
||||||
|
{
|
||||||
|
File::parse(
|
||||||
|
$user,
|
||||||
|
$server,
|
||||||
|
$input['path'],
|
||||||
|
$input['user'],
|
||||||
|
$server->os()->ls($input['path'], $input['user'])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function rules(Server $server): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'path' => [
|
||||||
|
'required',
|
||||||
|
],
|
||||||
|
'user' => [
|
||||||
|
'required',
|
||||||
|
Rule::in($server->getSshUsers()),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -1,67 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Actions\FirewallRule;
|
|
||||||
|
|
||||||
use App\Enums\FirewallRuleStatus;
|
|
||||||
use App\Models\FirewallRule;
|
|
||||||
use App\Models\Server;
|
|
||||||
use App\SSH\Services\Firewall\Firewall;
|
|
||||||
use Illuminate\Validation\Rule;
|
|
||||||
|
|
||||||
class CreateRule
|
|
||||||
{
|
|
||||||
public function create(Server $server, array $input): FirewallRule
|
|
||||||
{
|
|
||||||
$rule = new FirewallRule([
|
|
||||||
'server_id' => $server->id,
|
|
||||||
'type' => $input['type'],
|
|
||||||
'protocol' => $input['protocol'],
|
|
||||||
'port' => $input['port'],
|
|
||||||
'source' => $input['source'],
|
|
||||||
'mask' => $input['mask'] ?? null,
|
|
||||||
]);
|
|
||||||
|
|
||||||
/** @var Firewall $firewallHandler */
|
|
||||||
$firewallHandler = $server->firewall()->handler();
|
|
||||||
$firewallHandler->addRule(
|
|
||||||
$rule->type,
|
|
||||||
$rule->getRealProtocol(),
|
|
||||||
$rule->port,
|
|
||||||
$rule->source,
|
|
||||||
$rule->mask
|
|
||||||
);
|
|
||||||
|
|
||||||
$rule->status = FirewallRuleStatus::READY;
|
|
||||||
$rule->save();
|
|
||||||
|
|
||||||
return $rule;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function rules(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'type' => [
|
|
||||||
'required',
|
|
||||||
'in:allow,deny',
|
|
||||||
],
|
|
||||||
'protocol' => [
|
|
||||||
'required',
|
|
||||||
Rule::in(array_keys(config('core.firewall_protocols_port'))),
|
|
||||||
],
|
|
||||||
'port' => [
|
|
||||||
'required',
|
|
||||||
'numeric',
|
|
||||||
'min:1',
|
|
||||||
'max:65535',
|
|
||||||
],
|
|
||||||
'source' => [
|
|
||||||
'required',
|
|
||||||
'ip',
|
|
||||||
],
|
|
||||||
'mask' => [
|
|
||||||
'nullable',
|
|
||||||
'numeric',
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Actions\FirewallRule;
|
|
||||||
|
|
||||||
use App\Enums\FirewallRuleStatus;
|
|
||||||
use App\Models\FirewallRule;
|
|
||||||
use App\Models\Server;
|
|
||||||
|
|
||||||
class DeleteRule
|
|
||||||
{
|
|
||||||
public function delete(Server $server, FirewallRule $rule): void
|
|
||||||
{
|
|
||||||
$rule->status = FirewallRuleStatus::DELETING;
|
|
||||||
$rule->save();
|
|
||||||
|
|
||||||
$server->firewall()
|
|
||||||
->handler()
|
|
||||||
->removeRule(
|
|
||||||
$rule->type,
|
|
||||||
$rule->getRealProtocol(),
|
|
||||||
$rule->port,
|
|
||||||
$rule->source,
|
|
||||||
$rule->mask
|
|
||||||
);
|
|
||||||
|
|
||||||
$rule->delete();
|
|
||||||
}
|
|
||||||
}
|
|
117
app/Actions/FirewallRule/ManageRule.php
Executable file
117
app/Actions/FirewallRule/ManageRule.php
Executable file
@ -0,0 +1,117 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\FirewallRule;
|
||||||
|
|
||||||
|
use App\Enums\FirewallRuleStatus;
|
||||||
|
use App\Models\FirewallRule;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\SSH\Services\Firewall\Firewall;
|
||||||
|
|
||||||
|
class ManageRule
|
||||||
|
{
|
||||||
|
public function create(Server $server, array $input): FirewallRule
|
||||||
|
{
|
||||||
|
$sourceAny = $input['source_any'] ?? empty($input['source'] ?? null);
|
||||||
|
$rule = new FirewallRule([
|
||||||
|
'name' => $input['name'],
|
||||||
|
'server_id' => $server->id,
|
||||||
|
'type' => $input['type'],
|
||||||
|
'protocol' => $input['protocol'],
|
||||||
|
'port' => $input['port'],
|
||||||
|
'source' => $sourceAny ? null : $input['source'],
|
||||||
|
'mask' => $sourceAny ? null : ($input['mask'] ?? null),
|
||||||
|
'status' => FirewallRuleStatus::CREATING,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$rule->save();
|
||||||
|
|
||||||
|
dispatch(fn () => $this->applyRule($rule));
|
||||||
|
|
||||||
|
return $rule;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(FirewallRule $rule, array $input): FirewallRule
|
||||||
|
{
|
||||||
|
$sourceAny = $input['source_any'] ?? empty($input['source'] ?? null);
|
||||||
|
$rule->update([
|
||||||
|
'name' => $input['name'],
|
||||||
|
'type' => $input['type'],
|
||||||
|
'protocol' => $input['protocol'],
|
||||||
|
'port' => $input['port'],
|
||||||
|
'source' => $sourceAny ? null : $input['source'],
|
||||||
|
'mask' => $sourceAny ? null : ($input['mask'] ?? null),
|
||||||
|
'status' => FirewallRuleStatus::UPDATING,
|
||||||
|
]);
|
||||||
|
|
||||||
|
dispatch(fn () => $this->applyRule($rule));
|
||||||
|
|
||||||
|
return $rule;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(FirewallRule $rule): void
|
||||||
|
{
|
||||||
|
$rule->status = FirewallRuleStatus::DELETING;
|
||||||
|
$rule->save();
|
||||||
|
|
||||||
|
dispatch(fn () => $this->applyRule($rule));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function applyRule($rule): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
/** @var Firewall $handler */
|
||||||
|
$handler = $rule->server->firewall()->handler();
|
||||||
|
$handler->applyRules();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$rule->server->firewallRules()
|
||||||
|
->where('status', '!=', FirewallRuleStatus::READY)
|
||||||
|
->update(['status' => FirewallRuleStatus::FAILED]);
|
||||||
|
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($rule->status === FirewallRuleStatus::DELETING) {
|
||||||
|
$rule->delete();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$rule->status = FirewallRuleStatus::READY;
|
||||||
|
$rule->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name' => [
|
||||||
|
'required',
|
||||||
|
'string',
|
||||||
|
'max:18',
|
||||||
|
],
|
||||||
|
'type' => [
|
||||||
|
'required',
|
||||||
|
'in:allow,deny',
|
||||||
|
],
|
||||||
|
'protocol' => [
|
||||||
|
'required',
|
||||||
|
'in:tcp,udp',
|
||||||
|
],
|
||||||
|
'port' => [
|
||||||
|
'required',
|
||||||
|
'numeric',
|
||||||
|
'min:1',
|
||||||
|
'max:65535',
|
||||||
|
],
|
||||||
|
'source' => [
|
||||||
|
'nullable',
|
||||||
|
'ip',
|
||||||
|
],
|
||||||
|
'mask' => [
|
||||||
|
'nullable',
|
||||||
|
'numeric',
|
||||||
|
'min:1',
|
||||||
|
'max:32',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -86,6 +86,9 @@ private function install(Server $server): void
|
|||||||
while ($maxWait > 0) {
|
while ($maxWait > 0) {
|
||||||
sleep(10);
|
sleep(10);
|
||||||
$maxWait -= 10;
|
$maxWait -= 10;
|
||||||
|
if (! $server->provider()->isRunning()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
$server->ssh()->connect();
|
$server->ssh()->connect();
|
||||||
break;
|
break;
|
||||||
@ -194,26 +197,29 @@ public function createFirewallRules(Server $server): void
|
|||||||
$server->firewallRules()->createMany([
|
$server->firewallRules()->createMany([
|
||||||
[
|
[
|
||||||
'type' => 'allow',
|
'type' => 'allow',
|
||||||
'protocol' => 'ssh',
|
'name' => 'SSH',
|
||||||
|
'protocol' => 'tcp',
|
||||||
'port' => 22,
|
'port' => 22,
|
||||||
'source' => '0.0.0.0',
|
'source' => null,
|
||||||
'mask' => 0,
|
'mask' => null,
|
||||||
'status' => FirewallRuleStatus::READY,
|
'status' => FirewallRuleStatus::READY,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'type' => 'allow',
|
'type' => 'allow',
|
||||||
'protocol' => 'http',
|
'name' => 'HTTP',
|
||||||
|
'protocol' => 'tcp',
|
||||||
'port' => 80,
|
'port' => 80,
|
||||||
'source' => '0.0.0.0',
|
'source' => null,
|
||||||
'mask' => 0,
|
'mask' => null,
|
||||||
'status' => FirewallRuleStatus::READY,
|
'status' => FirewallRuleStatus::READY,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'type' => 'allow',
|
'type' => 'allow',
|
||||||
'protocol' => 'https',
|
'name' => 'HTTPS',
|
||||||
|
'protocol' => 'tcp',
|
||||||
'port' => 443,
|
'port' => 443,
|
||||||
'source' => '0.0.0.0',
|
'source' => null,
|
||||||
'mask' => 0,
|
'mask' => null,
|
||||||
'status' => FirewallRuleStatus::READY,
|
'status' => FirewallRuleStatus::READY,
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
29
app/Actions/Site/CreateCommand.php
Normal file
29
app/Actions/Site/CreateCommand.php
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Site;
|
||||||
|
|
||||||
|
use App\Models\Command;
|
||||||
|
use App\Models\Site;
|
||||||
|
|
||||||
|
class CreateCommand
|
||||||
|
{
|
||||||
|
public function create(Site $site, array $input): Command
|
||||||
|
{
|
||||||
|
$script = new Command([
|
||||||
|
'site_id' => $site->id,
|
||||||
|
'name' => $input['name'],
|
||||||
|
'command' => $input['command'],
|
||||||
|
]);
|
||||||
|
$script->save();
|
||||||
|
|
||||||
|
return $script;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name' => ['required', 'string', 'max:255'],
|
||||||
|
'command' => ['required', 'string'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -68,6 +68,9 @@ public function create(Server $server, array $input): Site
|
|||||||
'content' => '',
|
'content' => '',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// create base commands if any
|
||||||
|
$site->commands()->createMany($site->type()->baseCommands());
|
||||||
|
|
||||||
// install site
|
// install site
|
||||||
dispatch(function () use ($site) {
|
dispatch(function () use ($site) {
|
||||||
$site->type()->install();
|
$site->type()->install();
|
||||||
|
25
app/Actions/Site/EditCommand.php
Normal file
25
app/Actions/Site/EditCommand.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Site;
|
||||||
|
|
||||||
|
use App\Models\Command;
|
||||||
|
|
||||||
|
class EditCommand
|
||||||
|
{
|
||||||
|
public function edit(Command $command, array $input): Command
|
||||||
|
{
|
||||||
|
$command->name = $input['name'];
|
||||||
|
$command->command = $input['command'];
|
||||||
|
$command->save();
|
||||||
|
|
||||||
|
return $command;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name' => ['required', 'string', 'max:255'],
|
||||||
|
'command' => ['required', 'string'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
58
app/Actions/Site/ExecuteCommand.php
Normal file
58
app/Actions/Site/ExecuteCommand.php
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Site;
|
||||||
|
|
||||||
|
use App\Enums\CommandExecutionStatus;
|
||||||
|
use App\Models\Command;
|
||||||
|
use App\Models\CommandExecution;
|
||||||
|
use App\Models\ServerLog;
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
class ExecuteCommand
|
||||||
|
{
|
||||||
|
public function execute(Command $command, User $user, array $input): CommandExecution
|
||||||
|
{
|
||||||
|
$execution = new CommandExecution([
|
||||||
|
'command_id' => $command->id,
|
||||||
|
'server_id' => $command->site->server_id,
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'variables' => $input['variables'] ?? [],
|
||||||
|
'status' => CommandExecutionStatus::EXECUTING,
|
||||||
|
]);
|
||||||
|
$execution->save();
|
||||||
|
|
||||||
|
dispatch(function () use ($execution, $command) {
|
||||||
|
$content = $execution->getContent();
|
||||||
|
$log = ServerLog::make($execution->server, 'command-'.$command->id.'-'.strtotime('now'));
|
||||||
|
$log->save();
|
||||||
|
$execution->server_log_id = $log->id;
|
||||||
|
$execution->save();
|
||||||
|
$execution->server->os()->runScript(
|
||||||
|
path: $command->site->path,
|
||||||
|
script: $content,
|
||||||
|
user: $command->site->user,
|
||||||
|
serverLog: $log,
|
||||||
|
variables: $execution->variables
|
||||||
|
);
|
||||||
|
$execution->status = CommandExecutionStatus::COMPLETED;
|
||||||
|
$execution->save();
|
||||||
|
})->catch(function () use ($execution) {
|
||||||
|
$execution->status = CommandExecutionStatus::FAILED;
|
||||||
|
$execution->save();
|
||||||
|
})->onConnection('ssh');
|
||||||
|
|
||||||
|
return $execution;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function rules(array $input): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'variables' => 'array',
|
||||||
|
'variables.*' => [
|
||||||
|
'required',
|
||||||
|
'string',
|
||||||
|
'max:255',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -16,6 +16,7 @@ class UpdateBranch
|
|||||||
public function update(Site $site, array $input): void
|
public function update(Site $site, array $input): void
|
||||||
{
|
{
|
||||||
$site->branch = $input['branch'];
|
$site->branch = $input['branch'];
|
||||||
|
app(Git::class)->fetchOrigin($site);
|
||||||
app(Git::class)->checkout($site);
|
app(Git::class)->checkout($site);
|
||||||
$site->save();
|
$site->save();
|
||||||
}
|
}
|
||||||
|
@ -2,19 +2,20 @@
|
|||||||
|
|
||||||
namespace App\Actions\Site;
|
namespace App\Actions\Site;
|
||||||
|
|
||||||
use App\Exceptions\SSHUploadFailed;
|
use App\Exceptions\SSHError;
|
||||||
use App\Models\Site;
|
use App\Models\Site;
|
||||||
|
|
||||||
class UpdateEnv
|
class UpdateEnv
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @throws SSHUploadFailed
|
* @throws SSHError
|
||||||
*/
|
*/
|
||||||
public function update(Site $site, array $input): void
|
public function update(Site $site, array $input): void
|
||||||
{
|
{
|
||||||
$site->server->os()->editFile(
|
$site->server->os()->editFileAs(
|
||||||
$site->path.'/.env',
|
$site->path.'/.env',
|
||||||
$input['env']
|
$site->user,
|
||||||
|
trim($input['env']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
32
app/Cli/Commands/AbstractCommand.php
Normal file
32
app/Cli/Commands/AbstractCommand.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Cli\Commands;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
use function Laravel\Prompts\error;
|
||||||
|
|
||||||
|
abstract class AbstractCommand extends Command
|
||||||
|
{
|
||||||
|
private User|null $user = null;
|
||||||
|
|
||||||
|
public function user()
|
||||||
|
{
|
||||||
|
if ($this->user) {
|
||||||
|
return $this->user->refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var User $user */
|
||||||
|
$user = User::query()->first();
|
||||||
|
|
||||||
|
if (!$user) {
|
||||||
|
error('The application is not setup');
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->user = $user;
|
||||||
|
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
}
|
18
app/Cli/Commands/InfoCommand.php
Normal file
18
app/Cli/Commands/InfoCommand.php
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Cli\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class InfoCommand extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'info';
|
||||||
|
|
||||||
|
protected $description = 'Show the application information';
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$this->info('Version: '.config('app.version'));
|
||||||
|
$this->info('Timezone: '.config('app.timezone'));
|
||||||
|
}
|
||||||
|
}
|
29
app/Cli/Commands/Projects/ProjectsListCommand.php
Normal file
29
app/Cli/Commands/Projects/ProjectsListCommand.php
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Cli\Commands\Projects;
|
||||||
|
|
||||||
|
use App\Cli\Commands\AbstractCommand;
|
||||||
|
use App\Models\Project;
|
||||||
|
|
||||||
|
use function Laravel\Prompts\table;
|
||||||
|
|
||||||
|
class ProjectsListCommand extends AbstractCommand
|
||||||
|
{
|
||||||
|
protected $signature = 'projects:list';
|
||||||
|
|
||||||
|
protected $description = 'Show projects list';
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$projects = Project::all();
|
||||||
|
|
||||||
|
table(
|
||||||
|
headers: ['ID', 'Name', 'Selected'],
|
||||||
|
rows: $projects->map(fn (Project $project) => [
|
||||||
|
$project->id,
|
||||||
|
$project->name,
|
||||||
|
$project->id === $this->user()->current_project_id ? 'Yes' : 'No',
|
||||||
|
])->toArray(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
35
app/Cli/Commands/Projects/ProjectsSelectCommand.php
Normal file
35
app/Cli/Commands/Projects/ProjectsSelectCommand.php
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Cli\Commands\Projects;
|
||||||
|
|
||||||
|
use App\Cli\Commands\AbstractCommand;
|
||||||
|
use App\Models\Project;
|
||||||
|
|
||||||
|
use function Laravel\Prompts\error;
|
||||||
|
use function Laravel\Prompts\info;
|
||||||
|
|
||||||
|
class ProjectsSelectCommand extends AbstractCommand
|
||||||
|
{
|
||||||
|
protected $signature = 'projects:select {project}';
|
||||||
|
|
||||||
|
protected $description = 'Select a project';
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$project = Project::query()->find($this->argument('project'));
|
||||||
|
|
||||||
|
if (! $project) {
|
||||||
|
error('The project does not exist');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->user()->update([
|
||||||
|
'current_project_id' => $project->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
info(__('The project [:project] has been selected' , [
|
||||||
|
'project' => $project->name,
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Cli\Commands\ServerProviders;
|
||||||
|
|
||||||
|
use App\Cli\Commands\AbstractCommand;
|
||||||
|
use function Laravel\Prompts\select;
|
||||||
|
use function Laravel\Prompts\text;
|
||||||
|
|
||||||
|
class ServerProvidersCreateCommand extends AbstractCommand
|
||||||
|
{
|
||||||
|
protected $signature = 'server-providers:create';
|
||||||
|
|
||||||
|
protected $description = 'Create a new server provider';
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$provider = select(
|
||||||
|
label: 'What is the server provider?',
|
||||||
|
options: collect(config('core.server_providers'))
|
||||||
|
->filter(fn($provider) => $provider != \App\Enums\ServerProvider::CUSTOM)
|
||||||
|
->mapWithKeys(fn($provider) => [$provider => $provider]),
|
||||||
|
);
|
||||||
|
$profile = text(
|
||||||
|
label: 'What should we call this provider profile?',
|
||||||
|
required: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Cli\Commands\ServerProviders;
|
||||||
|
|
||||||
|
use App\Cli\Commands\AbstractCommand;
|
||||||
|
use App\Models\Project;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\ServerProvider;
|
||||||
|
use function Laravel\Prompts\table;
|
||||||
|
|
||||||
|
class ServerProvidersListCommand extends AbstractCommand
|
||||||
|
{
|
||||||
|
protected $signature = 'server-providers:list';
|
||||||
|
|
||||||
|
protected $description = 'Show server providers list';
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$providers = $this->user()->serverProviders;
|
||||||
|
|
||||||
|
table(
|
||||||
|
headers: ['ID', 'Provider', 'Name', 'Created At'],
|
||||||
|
rows: $providers->map(fn (ServerProvider $provider) => [
|
||||||
|
$provider->id,
|
||||||
|
$provider->provider,
|
||||||
|
$provider->profile,
|
||||||
|
$provider->created_at_by_timezone,
|
||||||
|
])->toArray(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
27
app/Cli/Commands/Servers/ServersCreateCommand.php
Normal file
27
app/Cli/Commands/Servers/ServersCreateCommand.php
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Cli\Commands\Servers;
|
||||||
|
|
||||||
|
use App\Cli\Commands\AbstractCommand;
|
||||||
|
use function Laravel\Prompts\select;
|
||||||
|
use function Laravel\Prompts\text;
|
||||||
|
|
||||||
|
class ServersCreateCommand extends AbstractCommand
|
||||||
|
{
|
||||||
|
protected $signature = 'servers:create';
|
||||||
|
|
||||||
|
protected $description = 'Create a new server';
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$name = text(
|
||||||
|
label: 'What is the server name?',
|
||||||
|
required: true,
|
||||||
|
);
|
||||||
|
$os = select(
|
||||||
|
label: 'What is the server OS?',
|
||||||
|
options: collect(config('core.operating_systems'))
|
||||||
|
->mapWithKeys(fn($value) => [$value => $value]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
34
app/Cli/Commands/Servers/ServersListCommand.php
Normal file
34
app/Cli/Commands/Servers/ServersListCommand.php
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Cli\Commands\Servers;
|
||||||
|
|
||||||
|
use App\Cli\Commands\AbstractCommand;
|
||||||
|
use App\Models\Project;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use function Laravel\Prompts\table;
|
||||||
|
|
||||||
|
class ServersListCommand extends AbstractCommand
|
||||||
|
{
|
||||||
|
protected $signature = 'servers:list';
|
||||||
|
|
||||||
|
protected $description = 'Show servers list';
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$servers = $this->user()->currentProject->servers;
|
||||||
|
|
||||||
|
table(
|
||||||
|
headers: ['ID', 'Name', 'IP', 'Provider', 'OS', 'Status', 'Created At'],
|
||||||
|
rows: $servers->map(fn (Server $server) => [
|
||||||
|
$server->id,
|
||||||
|
$server->name,
|
||||||
|
$server->ip,
|
||||||
|
$server->provider,
|
||||||
|
$server->os,
|
||||||
|
$server->status,
|
||||||
|
$server->created_at_by_timezone,
|
||||||
|
])->toArray(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
81
app/Cli/Commands/SetupCommand.php
Normal file
81
app/Cli/Commands/SetupCommand.php
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Cli\Commands;
|
||||||
|
|
||||||
|
use App\Models\Project;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
|
use function Laravel\Prompts\text;
|
||||||
|
use function Laravel\Prompts\info;
|
||||||
|
|
||||||
|
class SetupCommand extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'setup';
|
||||||
|
|
||||||
|
protected $description = 'Setup the application';
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$this->prepareStorage();
|
||||||
|
|
||||||
|
$this->migrate();
|
||||||
|
|
||||||
|
$this->makeUser();
|
||||||
|
|
||||||
|
$this->makeProject();
|
||||||
|
|
||||||
|
info('The application has been setup');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function prepareStorage(): void
|
||||||
|
{
|
||||||
|
File::ensureDirectoryExists(storage_path());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function migrate(): void
|
||||||
|
{
|
||||||
|
$this->call('migrate', ['--force' => true]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function makeUser(): void
|
||||||
|
{
|
||||||
|
$user = User::query()->first();
|
||||||
|
if ($user) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$name = text(
|
||||||
|
label: 'What is your name?',
|
||||||
|
required: true,
|
||||||
|
);
|
||||||
|
$email = text(
|
||||||
|
label: 'What is your email?',
|
||||||
|
required: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
User::query()->create([
|
||||||
|
'name' => $name,
|
||||||
|
'email' => $email,
|
||||||
|
'password' => bcrypt(str()->random(16)),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function makeProject(): void
|
||||||
|
{
|
||||||
|
$project = Project::query()->first();
|
||||||
|
|
||||||
|
if ($project) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$project = Project::query()->create([
|
||||||
|
'name' => 'default',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user = User::query()->first();
|
||||||
|
$user->update([
|
||||||
|
'current_project_id' => $project->id,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
43
app/Cli/Kernel.php
Normal file
43
app/Cli/Kernel.php
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Cli;
|
||||||
|
|
||||||
|
use Illuminate\Console\Application as Artisan;
|
||||||
|
use Illuminate\Contracts\Events\Dispatcher;
|
||||||
|
use Illuminate\Database\Console\Migrations\InstallCommand;
|
||||||
|
use Illuminate\Database\Console\Migrations\MigrateCommand;
|
||||||
|
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||||
|
|
||||||
|
class Kernel extends ConsoleKernel
|
||||||
|
{
|
||||||
|
protected $commands = [
|
||||||
|
'command.migrate',
|
||||||
|
'command.migrate.install'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the commands for the application.
|
||||||
|
*/
|
||||||
|
protected function commands(): void
|
||||||
|
{
|
||||||
|
$this->load(__DIR__ . '/Commands');
|
||||||
|
|
||||||
|
$this->app->singleton('command.migrate', function ($app) {
|
||||||
|
return new MigrateCommand($app['migrator'], $app[Dispatcher::class]);
|
||||||
|
});
|
||||||
|
$this->app->singleton('command.migrate.install', function ($app) {
|
||||||
|
return new InstallCommand($app['migration.repository']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function shouldDiscoverCommands(): false
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getArtisan(): ?Artisan
|
||||||
|
{
|
||||||
|
return $this->artisan = (new Artisan($this->app, $this->events, $this->app->version()))
|
||||||
|
->resolveCommands($this->commands);
|
||||||
|
}
|
||||||
|
}
|
26
app/Console/Commands/ClearLogsCommand.php
Normal file
26
app/Console/Commands/ClearLogsCommand.php
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\ServerLog;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
class ClearLogsCommand extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'logs:clear';
|
||||||
|
|
||||||
|
protected $description = 'Clear all server logs';
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$this->info('Clearing logs...');
|
||||||
|
|
||||||
|
ServerLog::query()->delete();
|
||||||
|
|
||||||
|
File::cleanDirectory(Storage::disk('server-logs')->path(''));
|
||||||
|
|
||||||
|
$this->info('Logs cleared!');
|
||||||
|
}
|
||||||
|
}
|
12
app/Enums/CommandExecutionStatus.php
Normal file
12
app/Enums/CommandExecutionStatus.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
final class CommandExecutionStatus
|
||||||
|
{
|
||||||
|
const EXECUTING = 'executing';
|
||||||
|
|
||||||
|
const COMPLETED = 'completed';
|
||||||
|
|
||||||
|
const FAILED = 'failed';
|
||||||
|
}
|
@ -6,7 +6,11 @@ final class FirewallRuleStatus
|
|||||||
{
|
{
|
||||||
const CREATING = 'creating';
|
const CREATING = 'creating';
|
||||||
|
|
||||||
|
const UPDATING = 'updating';
|
||||||
|
|
||||||
const READY = 'ready';
|
const READY = 'ready';
|
||||||
|
|
||||||
const DELETING = 'deleting';
|
const DELETING = 'deleting';
|
||||||
|
|
||||||
|
const FAILED = 'failed';
|
||||||
}
|
}
|
||||||
|
@ -11,4 +11,6 @@ final class SiteFeature
|
|||||||
const SSL = 'ssl';
|
const SSL = 'ssl';
|
||||||
|
|
||||||
const QUEUES = 'queues';
|
const QUEUES = 'queues';
|
||||||
|
|
||||||
|
const COMMANDS = 'commands';
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,9 @@
|
|||||||
* @method static setLog(?ServerLog $log)
|
* @method static setLog(?ServerLog $log)
|
||||||
* @method static connect()
|
* @method static connect()
|
||||||
* @method static string exec(string $command, string $log = '', int $siteId = null, ?bool $stream = false, callable $streamCallback = null)
|
* @method static string exec(string $command, string $log = '', int $siteId = null, ?bool $stream = false, callable $streamCallback = null)
|
||||||
|
* @method static string upload(string $local, string $remote, ?string $owner = null)
|
||||||
|
* @method static string download(string $local, string $remote)
|
||||||
|
* @method static string write(string $path, string $content, string $owner = null)
|
||||||
* @method static string assertExecuted(array|string $commands)
|
* @method static string assertExecuted(array|string $commands)
|
||||||
* @method static string assertExecutedContains(string $command)
|
* @method static string assertExecutedContains(string $command)
|
||||||
* @method static string assertFileUploaded(string $toPath, ?string $content = null)
|
* @method static string assertFileUploaded(string $toPath, ?string $content = null)
|
||||||
|
@ -94,11 +94,7 @@ public function connect(bool $sftp = false): void
|
|||||||
*/
|
*/
|
||||||
public function exec(string $command, string $log = '', ?int $siteId = null, ?bool $stream = false, ?callable $streamCallback = null): string
|
public function exec(string $command, string $log = '', ?int $siteId = null, ?bool $stream = false, ?callable $streamCallback = null): string
|
||||||
{
|
{
|
||||||
if (! $log) {
|
if (! $this->log && $log) {
|
||||||
$log = 'run-command';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! $this->log) {
|
|
||||||
$this->log = ServerLog::make($this->server, $log);
|
$this->log = ServerLog::make($this->server, $log);
|
||||||
if ($siteId) {
|
if ($siteId) {
|
||||||
$this->log->forSite($siteId);
|
$this->log->forSite($siteId);
|
||||||
@ -116,13 +112,15 @@ public function exec(string $command, string $log = '', ?int $siteId = null, ?bo
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if ($this->asUser) {
|
if ($this->asUser) {
|
||||||
$command = 'sudo su - '.$this->asUser.' -c '.'"'.addslashes($command).'"';
|
$command = addslashes($command);
|
||||||
|
$command = str_replace('\\\'', '\'', $command);
|
||||||
|
$command = 'sudo su - '.$this->asUser.' -c '.'"'.trim($command).'"';
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->connection->setTimeout(0);
|
$this->connection->setTimeout(0);
|
||||||
if ($stream) {
|
if ($stream) {
|
||||||
$this->connection->exec($command, function ($output) use ($streamCallback) {
|
$this->connection->exec($command, function ($output) use ($streamCallback) {
|
||||||
$this->log->write($output);
|
$this->log?->write($output);
|
||||||
|
|
||||||
return $streamCallback($output);
|
return $streamCallback($output);
|
||||||
});
|
});
|
||||||
@ -131,7 +129,7 @@ public function exec(string $command, string $log = '', ?int $siteId = null, ?bo
|
|||||||
} else {
|
} else {
|
||||||
$output = '';
|
$output = '';
|
||||||
$this->connection->exec($command, function ($out) use (&$output) {
|
$this->connection->exec($command, function ($out) use (&$output) {
|
||||||
$this->log->write($out);
|
$this->log?->write($out);
|
||||||
$output .= $out;
|
$output .= $out;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -159,7 +157,7 @@ public function exec(string $command, string $log = '', ?int $siteId = null, ?bo
|
|||||||
/**
|
/**
|
||||||
* @throws Throwable
|
* @throws Throwable
|
||||||
*/
|
*/
|
||||||
public function upload(string $local, string $remote): void
|
public function upload(string $local, string $remote, ?string $owner = null): void
|
||||||
{
|
{
|
||||||
$this->log = null;
|
$this->log = null;
|
||||||
|
|
||||||
@ -167,7 +165,17 @@ public function upload(string $local, string $remote): void
|
|||||||
$this->connect(true);
|
$this->connect(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->connection->put($remote, $local, SFTP::SOURCE_LOCAL_FILE);
|
$tmpName = Str::random(10).strtotime('now');
|
||||||
|
$tempPath = home_path($this->user).'/'.$tmpName;
|
||||||
|
|
||||||
|
$this->connection->put($tempPath, $local, SFTP::SOURCE_LOCAL_FILE);
|
||||||
|
|
||||||
|
$this->exec(sprintf('sudo mv %s %s', $tempPath, $remote));
|
||||||
|
if (! $owner) {
|
||||||
|
$owner = $this->user;
|
||||||
|
}
|
||||||
|
$this->exec(sprintf('sudo chown %s:%s %s', $owner, $owner, $remote));
|
||||||
|
$this->exec(sprintf('sudo chmod 644 %s', $remote));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -187,22 +195,15 @@ public function download(string $local, string $remote): void
|
|||||||
/**
|
/**
|
||||||
* @throws SSHError
|
* @throws SSHError
|
||||||
*/
|
*/
|
||||||
public function write(string $remotePath, string $content, bool $sudo = false): void
|
public function write(string $remotePath, string $content, ?string $owner = null): void
|
||||||
{
|
{
|
||||||
$tmpName = Str::random(10).strtotime('now');
|
$tmpName = Str::random(10).strtotime('now');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
/** @var FilesystemAdapter $storageDisk */
|
/** @var FilesystemAdapter $storageDisk */
|
||||||
$storageDisk = Storage::disk('local');
|
$storageDisk = Storage::disk('local');
|
||||||
|
|
||||||
$storageDisk->put($tmpName, $content);
|
$storageDisk->put($tmpName, $content);
|
||||||
|
$this->upload($storageDisk->path($tmpName), $remotePath, $owner);
|
||||||
if ($sudo) {
|
|
||||||
$this->upload($storageDisk->path($tmpName), sprintf('/home/%s/%s', $this->server->ssh_user, $tmpName));
|
|
||||||
$this->exec(sprintf('sudo mv /home/%s/%s %s', $this->server->ssh_user, $tmpName, $remotePath));
|
|
||||||
} else {
|
|
||||||
$this->upload($storageDisk->path($tmpName), $remotePath);
|
|
||||||
}
|
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
throw new SSHCommandError(
|
throw new SSHCommandError(
|
||||||
message: $e->getMessage()
|
message: $e->getMessage()
|
||||||
|
@ -2,8 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\API;
|
namespace App\Http\Controllers\API;
|
||||||
|
|
||||||
use App\Actions\FirewallRule\CreateRule;
|
use App\Actions\FirewallRule\ManageRule;
|
||||||
use App\Actions\FirewallRule\DeleteRule;
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Resources\FirewallRuleResource;
|
use App\Http\Resources\FirewallRuleResource;
|
||||||
use App\Models\FirewallRule;
|
use App\Models\FirewallRule;
|
||||||
@ -21,6 +20,7 @@
|
|||||||
use Spatie\RouteAttributes\Attributes\Middleware;
|
use Spatie\RouteAttributes\Attributes\Middleware;
|
||||||
use Spatie\RouteAttributes\Attributes\Post;
|
use Spatie\RouteAttributes\Attributes\Post;
|
||||||
use Spatie\RouteAttributes\Attributes\Prefix;
|
use Spatie\RouteAttributes\Attributes\Prefix;
|
||||||
|
use Spatie\RouteAttributes\Attributes\Put;
|
||||||
|
|
||||||
#[Prefix('api/projects/{project}/servers/{server}/firewall-rules')]
|
#[Prefix('api/projects/{project}/servers/{server}/firewall-rules')]
|
||||||
#[Middleware(['auth:sanctum', 'can-see-project'])]
|
#[Middleware(['auth:sanctum', 'can-see-project'])]
|
||||||
@ -41,10 +41,11 @@ public function index(Project $project, Server $server): ResourceCollection
|
|||||||
|
|
||||||
#[Post('/', name: 'api.projects.servers.firewall-rules.create', middleware: 'ability:write')]
|
#[Post('/', name: 'api.projects.servers.firewall-rules.create', middleware: 'ability:write')]
|
||||||
#[Endpoint(title: 'create', description: 'Create a new firewall rule.')]
|
#[Endpoint(title: 'create', description: 'Create a new firewall rule.')]
|
||||||
|
#[BodyParam(name: 'name', required: true)]
|
||||||
#[BodyParam(name: 'type', required: true, enum: ['allow', 'deny'])]
|
#[BodyParam(name: 'type', required: true, enum: ['allow', 'deny'])]
|
||||||
#[BodyParam(name: 'protocol', required: true, enum: ['tcp', 'udp'])]
|
#[BodyParam(name: 'protocol', required: true, enum: ['tcp', 'udp'])]
|
||||||
#[BodyParam(name: 'port', required: true)]
|
#[BodyParam(name: 'port', required: true)]
|
||||||
#[BodyParam(name: 'source', required: true)]
|
#[BodyParam(name: 'source', required: false)]
|
||||||
#[BodyParam(name: 'mask', description: 'Mask for source IP.', example: '0')]
|
#[BodyParam(name: 'mask', description: 'Mask for source IP.', example: '0')]
|
||||||
#[ResponseFromApiResource(FirewallRuleResource::class, FirewallRule::class)]
|
#[ResponseFromApiResource(FirewallRuleResource::class, FirewallRule::class)]
|
||||||
public function create(Request $request, Project $project, Server $server): FirewallRuleResource
|
public function create(Request $request, Project $project, Server $server): FirewallRuleResource
|
||||||
@ -53,9 +54,31 @@ public function create(Request $request, Project $project, Server $server): Fire
|
|||||||
|
|
||||||
$this->validateRoute($project, $server);
|
$this->validateRoute($project, $server);
|
||||||
|
|
||||||
$this->validate($request, CreateRule::rules());
|
$this->validate($request, ManageRule::rules());
|
||||||
|
|
||||||
$firewallRule = app(CreateRule::class)->create($server, $request->all());
|
$firewallRule = app(ManageRule::class)->create($server, $request->all());
|
||||||
|
|
||||||
|
return new FirewallRuleResource($firewallRule);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Put('{firewallRule}', name: 'api.projects.servers.firewall-rules.edit', middleware: 'ability:write')]
|
||||||
|
#[Endpoint(title: 'edit', description: 'Update an existing firewall rule.')]
|
||||||
|
#[BodyParam(name: 'name', required: true)]
|
||||||
|
#[BodyParam(name: 'type', required: true, enum: ['allow', 'deny'])]
|
||||||
|
#[BodyParam(name: 'protocol', required: true, enum: ['tcp', 'udp'])]
|
||||||
|
#[BodyParam(name: 'port', required: true)]
|
||||||
|
#[BodyParam(name: 'source', required: false)]
|
||||||
|
#[BodyParam(name: 'mask', description: 'Mask for source IP.', example: '0')]
|
||||||
|
#[ResponseFromApiResource(FirewallRuleResource::class, FirewallRule::class)]
|
||||||
|
public function edit(Request $request, Project $project, Server $server, FirewallRule $firewallRule): FirewallRuleResource
|
||||||
|
{
|
||||||
|
$this->authorize('update', [FirewallRule::class, $firewallRule]);
|
||||||
|
|
||||||
|
$this->validateRoute($project, $server);
|
||||||
|
|
||||||
|
$this->validate($request, ManageRule::rules());
|
||||||
|
|
||||||
|
$firewallRule = app(ManageRule::class)->update($firewallRule, $request->all());
|
||||||
|
|
||||||
return new FirewallRuleResource($firewallRule);
|
return new FirewallRuleResource($firewallRule);
|
||||||
}
|
}
|
||||||
@ -81,7 +104,7 @@ public function delete(Project $project, Server $server, FirewallRule $firewallR
|
|||||||
|
|
||||||
$this->validateRoute($project, $server, $firewallRule);
|
$this->validateRoute($project, $server, $firewallRule);
|
||||||
|
|
||||||
app(DeleteRule::class)->delete($server, $firewallRule);
|
app(ManageRule::class)->delete($firewallRule);
|
||||||
|
|
||||||
return response()->noContent();
|
return response()->noContent();
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,8 @@ public function __invoke(Request $request)
|
|||||||
->firstOrFail();
|
->firstOrFail();
|
||||||
|
|
||||||
foreach ($gitHook->actions as $action) {
|
foreach ($gitHook->actions as $action) {
|
||||||
if ($action == 'deploy') {
|
$webhookBranch = $gitHook->site->sourceControl->provider()->getWebhookBranch($request->array());
|
||||||
|
if ($action == 'deploy' && $gitHook->site->branch === $webhookBranch) {
|
||||||
try {
|
try {
|
||||||
app(Deploy::class)->run($gitHook->site);
|
app(Deploy::class)->run($gitHook->site);
|
||||||
} catch (SourceControlIsNotConnected) {
|
} catch (SourceControlIsNotConnected) {
|
||||||
|
@ -37,7 +37,7 @@ public function run(Server $server, Request $request)
|
|||||||
|
|
||||||
return response()->stream(
|
return response()->stream(
|
||||||
function () use ($server, $request, $ssh, $log, $currentDir) {
|
function () use ($server, $request, $ssh, $log, $currentDir) {
|
||||||
$command = 'cd '.$currentDir.' && '.$request->command.' && echo "VITO_WORKING_DIR: $(pwd)"';
|
$command = 'cd '.$currentDir.' && '.$request->command.' && echo -n "VITO_WORKING_DIR: " && pwd';
|
||||||
$output = '';
|
$output = '';
|
||||||
$ssh->exec(command: $command, log: $log, stream: true, streamCallback: function ($out) use (&$output) {
|
$ssh->exec(command: $command, log: $log, stream: true, streamCallback: function ($out) use (&$output) {
|
||||||
echo preg_replace('/^VITO_WORKING_DIR:.*(\r?\n)?/m', '', $out);
|
echo preg_replace('/^VITO_WORKING_DIR:.*(\r?\n)?/m', '', $out);
|
||||||
@ -63,7 +63,7 @@ function () use ($server, $request, $ssh, $log, $currentDir) {
|
|||||||
public function workingDir(Server $server)
|
public function workingDir(Server $server)
|
||||||
{
|
{
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'dir' => Cache::get('console.'.$server->id.'.dir'),
|
'dir' => Cache::get('console.'.$server->id.'.dir', '~'),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ public function toArray(Request $request): array
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'id' => $this->id,
|
'id' => $this->id,
|
||||||
|
'name' => $this->name,
|
||||||
'server_id' => $this->server_id,
|
'server_id' => $this->server_id,
|
||||||
'type' => $this->type,
|
'type' => $this->type,
|
||||||
'protocol' => $this->protocol,
|
'protocol' => $this->protocol,
|
||||||
|
71
app/Models/Command.php
Normal file
71
app/Models/Command.php
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
<?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 $site_id
|
||||||
|
* @property string $name
|
||||||
|
* @property string $command
|
||||||
|
* @property Carbon $created_at
|
||||||
|
* @property Carbon $updated_at
|
||||||
|
* @property Collection<CommandExecution> $executions
|
||||||
|
* @property ?CommandExecution $lastExecution
|
||||||
|
* @property Site $site
|
||||||
|
*/
|
||||||
|
class Command extends AbstractModel
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'site_id',
|
||||||
|
'name',
|
||||||
|
'command',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'site_id' => 'integer',
|
||||||
|
];
|
||||||
|
|
||||||
|
public static function boot(): void
|
||||||
|
{
|
||||||
|
parent::boot();
|
||||||
|
|
||||||
|
static::deleting(function (Command $command) {
|
||||||
|
$command->executions()->delete();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function site(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Site::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getVariables(): array
|
||||||
|
{
|
||||||
|
$variables = [];
|
||||||
|
preg_match_all('/\${(.*?)}/', $this->command, $matches);
|
||||||
|
foreach ($matches[1] as $match) {
|
||||||
|
$variables[] = $match;
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_unique($variables);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function executions(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(CommandExecution::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function lastExecution(): HasOne
|
||||||
|
{
|
||||||
|
return $this->hasOne(CommandExecution::class)->latest();
|
||||||
|
}
|
||||||
|
}
|
83
app/Models/CommandExecution.php
Normal file
83
app/Models/CommandExecution.php
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Enums\CommandExecutionStatus;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property int $id
|
||||||
|
* @property int $command_id
|
||||||
|
* @property int $server_id
|
||||||
|
* @property int $user_id
|
||||||
|
* @property ?int $server_log_id
|
||||||
|
* @property array $variables
|
||||||
|
* @property string $status
|
||||||
|
* @property Carbon $created_at
|
||||||
|
* @property Carbon $updated_at
|
||||||
|
* @property Command $command
|
||||||
|
* @property ?ServerLog $serverLog
|
||||||
|
* @property Server $server
|
||||||
|
* @property ?User $user
|
||||||
|
*/
|
||||||
|
class CommandExecution extends AbstractModel
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'command_id',
|
||||||
|
'server_id',
|
||||||
|
'user_id',
|
||||||
|
'server_log_id',
|
||||||
|
'variables',
|
||||||
|
'status',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'command_id' => 'integer',
|
||||||
|
'server_id' => 'integer',
|
||||||
|
'user_id' => 'integer',
|
||||||
|
'server_log_id' => 'integer',
|
||||||
|
'variables' => 'array',
|
||||||
|
];
|
||||||
|
|
||||||
|
public static array $statusColors = [
|
||||||
|
CommandExecutionStatus::EXECUTING => 'warning',
|
||||||
|
CommandExecutionStatus::COMPLETED => 'success',
|
||||||
|
CommandExecutionStatus::FAILED => 'danger',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function command(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Command::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getContent(): string
|
||||||
|
{
|
||||||
|
$content = $this->command->command;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function server(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Server::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function user(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
}
|
147
app/Models/File.php
Normal file
147
app/Models/File.php
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property int $user_id
|
||||||
|
* @property int $server_id
|
||||||
|
* @property string $path
|
||||||
|
* @property string $type
|
||||||
|
* @property string $server_user
|
||||||
|
* @property string $name
|
||||||
|
* @property int $size
|
||||||
|
* @property int $links
|
||||||
|
* @property string $owner
|
||||||
|
* @property string $group
|
||||||
|
* @property string $date
|
||||||
|
* @property string $permissions
|
||||||
|
* @property User $user
|
||||||
|
* @property Server $server
|
||||||
|
*/
|
||||||
|
class File extends AbstractModel
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'user_id',
|
||||||
|
'server_id',
|
||||||
|
'server_user',
|
||||||
|
'path',
|
||||||
|
'type',
|
||||||
|
'name',
|
||||||
|
'size',
|
||||||
|
'links',
|
||||||
|
'owner',
|
||||||
|
'group',
|
||||||
|
'date',
|
||||||
|
'permissions',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'user_id' => 'integer',
|
||||||
|
'server_id' => 'integer',
|
||||||
|
'size' => 'integer',
|
||||||
|
'links' => 'integer',
|
||||||
|
'created_at' => 'datetime',
|
||||||
|
'updated_at' => 'datetime',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected static function boot(): void
|
||||||
|
{
|
||||||
|
parent::boot();
|
||||||
|
|
||||||
|
static::deleting(function (File $file) {
|
||||||
|
if ($file->name === '.' || $file->name === '..') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$file->server->os()->deleteFile($file->getFilePath(), $file->server_user);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function server(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Server::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function user(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function path(User $user, Server $server, string $serverUser): string
|
||||||
|
{
|
||||||
|
$file = self::query()
|
||||||
|
->where('user_id', $user->id)
|
||||||
|
->where('server_id', $server->id)
|
||||||
|
->where('server_user', $serverUser)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($file) {
|
||||||
|
return $file->path;
|
||||||
|
}
|
||||||
|
|
||||||
|
return home_path($serverUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function parse(User $user, Server $server, string $path, string $serverUser, string $listOutput): void
|
||||||
|
{
|
||||||
|
self::query()
|
||||||
|
->where('user_id', $user->id)
|
||||||
|
->where('server_id', $server->id)
|
||||||
|
->delete();
|
||||||
|
|
||||||
|
// Split output by line
|
||||||
|
$lines = explode("\n", trim($listOutput));
|
||||||
|
|
||||||
|
// Skip the first two lines (total count and . & .. directories)
|
||||||
|
array_shift($lines);
|
||||||
|
|
||||||
|
foreach ($lines as $line) {
|
||||||
|
if (preg_match('/^([drwx\-]+)\s+(\d+)\s+([\w\-]+)\s+([\w\-]+)\s+(\d+)\s+([\w\s:\-]+)\s+(.+)$/', $line, $matches)) {
|
||||||
|
$type = match ($matches[1][0]) {
|
||||||
|
'-' => 'file',
|
||||||
|
'd' => 'directory',
|
||||||
|
default => 'unknown',
|
||||||
|
};
|
||||||
|
if ($type === 'unknown') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($matches[7] === '.') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
self::create([
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'server_id' => $server->id,
|
||||||
|
'server_user' => $serverUser,
|
||||||
|
'path' => $path,
|
||||||
|
'type' => $type,
|
||||||
|
'name' => $matches[7],
|
||||||
|
'size' => (int) $matches[5],
|
||||||
|
'links' => (int) $matches[2],
|
||||||
|
'owner' => $matches[3],
|
||||||
|
'group' => $matches[4],
|
||||||
|
'date' => $matches[6],
|
||||||
|
'permissions' => $matches[1],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFilePath(): string
|
||||||
|
{
|
||||||
|
return $this->path.'/'.$this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isExtractable(): bool
|
||||||
|
{
|
||||||
|
$extension = pathinfo($this->name, PATHINFO_EXTENSION);
|
||||||
|
|
||||||
|
return in_array($extension, ['zip', 'tar', 'tar.gz', 'bz2', 'tar.bz2']);
|
||||||
|
}
|
||||||
|
}
|
@ -2,11 +2,13 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Enums\FirewallRuleStatus;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property int $server_id
|
* @property int $server_id
|
||||||
|
* @property string $name
|
||||||
* @property string $type
|
* @property string $type
|
||||||
* @property string $protocol
|
* @property string $protocol
|
||||||
* @property int $port
|
* @property int $port
|
||||||
@ -21,6 +23,7 @@ class FirewallRule extends AbstractModel
|
|||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
|
'name',
|
||||||
'server_id',
|
'server_id',
|
||||||
'type',
|
'type',
|
||||||
'protocol',
|
'protocol',
|
||||||
@ -36,13 +39,19 @@ class FirewallRule extends AbstractModel
|
|||||||
'port' => 'integer',
|
'port' => 'integer',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
public function getStatusColor(): string
|
||||||
|
{
|
||||||
|
return match ($this->status) {
|
||||||
|
FirewallRuleStatus::CREATING,
|
||||||
|
FirewallRuleStatus::UPDATING,
|
||||||
|
FirewallRuleStatus::DELETING => 'warning',
|
||||||
|
FirewallRuleStatus::READY => 'success',
|
||||||
|
FirewallRuleStatus::FAILED => 'danger',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public function server(): BelongsTo
|
public function server(): BelongsTo
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Server::class);
|
return $this->belongsTo(Server::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getRealProtocol(): string
|
|
||||||
{
|
|
||||||
return $this->protocol === 'udp' ? 'udp' : 'tcp';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
use App\Actions\Server\CheckConnection;
|
use App\Actions\Server\CheckConnection;
|
||||||
use App\Enums\ServerStatus;
|
use App\Enums\ServerStatus;
|
||||||
use App\Enums\ServiceStatus;
|
use App\Enums\ServiceStatus;
|
||||||
|
use App\Exceptions\SSHError;
|
||||||
use App\Facades\SSH;
|
use App\Facades\SSH;
|
||||||
use App\ServerTypes\ServerType;
|
use App\ServerTypes\ServerType;
|
||||||
use App\SSH\Cron\Cron;
|
use App\SSH\Cron\Cron;
|
||||||
@ -22,6 +23,7 @@
|
|||||||
use Illuminate\Support\Facades\File;
|
use Illuminate\Support\Facades\File;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property int $project_id
|
* @property int $project_id
|
||||||
@ -146,7 +148,7 @@ public static function boot(): void
|
|||||||
}
|
}
|
||||||
$server->provider()->delete();
|
$server->provider()->delete();
|
||||||
DB::commit();
|
DB::commit();
|
||||||
} catch (\Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
DB::rollBack();
|
DB::rollBack();
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
@ -465,6 +467,9 @@ public function cron(): Cron
|
|||||||
return new Cron($this);
|
return new Cron($this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws SSHError
|
||||||
|
*/
|
||||||
public function checkForUpdates(): void
|
public function checkForUpdates(): void
|
||||||
{
|
{
|
||||||
$this->updates = $this->os()->availableUpdates();
|
$this->updates = $this->os()->availableUpdates();
|
||||||
@ -480,4 +485,15 @@ public function getAvailableUpdatesAttribute(?int $value): int
|
|||||||
|
|
||||||
return $value;
|
return $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Throwable
|
||||||
|
*/
|
||||||
|
public function download(string $path, string $disk = 'tmp'): void
|
||||||
|
{
|
||||||
|
$this->ssh()->download(
|
||||||
|
Storage::disk($disk)->path(basename($path)),
|
||||||
|
$path
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,7 @@
|
|||||||
* @property Server $server
|
* @property Server $server
|
||||||
* @property ServerLog[] $logs
|
* @property ServerLog[] $logs
|
||||||
* @property Deployment[] $deployments
|
* @property Deployment[] $deployments
|
||||||
|
* @property Command[] $commands
|
||||||
* @property ?GitHook $gitHook
|
* @property ?GitHook $gitHook
|
||||||
* @property DeploymentScript $deploymentScript
|
* @property DeploymentScript $deploymentScript
|
||||||
* @property Queue[] $queues
|
* @property Queue[] $queues
|
||||||
@ -144,6 +145,11 @@ public function deployments(): HasMany
|
|||||||
return $this->hasMany(Deployment::class);
|
return $this->hasMany(Deployment::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function commands(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(Command::class);
|
||||||
|
}
|
||||||
|
|
||||||
public function gitHook(): HasOne
|
public function gitHook(): HasOne
|
||||||
{
|
{
|
||||||
return $this->hasOne(GitHook::class);
|
return $this->hasOne(GitHook::class);
|
||||||
|
61
app/Policies/CommandPolicy.php
Normal file
61
app/Policies/CommandPolicy.php
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Policies;
|
||||||
|
|
||||||
|
use App\Enums\SiteFeature;
|
||||||
|
use App\Models\Command;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\Site;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||||
|
|
||||||
|
class CommandPolicy
|
||||||
|
{
|
||||||
|
use HandlesAuthorization;
|
||||||
|
|
||||||
|
public function viewAny(User $user, Site $site, Server $server): bool
|
||||||
|
{
|
||||||
|
return ($user->isAdmin() || $server->project->users->contains($user)) &&
|
||||||
|
$server->isReady() &&
|
||||||
|
$site->hasFeature(SiteFeature::COMMANDS) &&
|
||||||
|
$site->isReady();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function view(User $user, Command $command, Site $site, Server $server): bool
|
||||||
|
{
|
||||||
|
return ($user->isAdmin() || $server->project->users->contains($user)) &&
|
||||||
|
$site->server_id === $server->id &&
|
||||||
|
$server->isReady() &&
|
||||||
|
$site->isReady() &&
|
||||||
|
$site->hasFeature(SiteFeature::COMMANDS) &&
|
||||||
|
$command->site_id === $site->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(User $user, Site $site, Server $server): bool
|
||||||
|
{
|
||||||
|
return ($user->isAdmin() || $server->project->users->contains($user)) &&
|
||||||
|
$server->isReady() &&
|
||||||
|
$site->hasFeature(SiteFeature::COMMANDS) &&
|
||||||
|
$site->isReady();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(User $user, Command $command, Site $site, Server $server): bool
|
||||||
|
{
|
||||||
|
return ($user->isAdmin() || $server->project->users->contains($user)) &&
|
||||||
|
$site->server_id === $server->id &&
|
||||||
|
$server->isReady() &&
|
||||||
|
$site->isReady() &&
|
||||||
|
$site->hasFeature(SiteFeature::COMMANDS) &&
|
||||||
|
$command->site_id === $site->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(User $user, Command $command, Site $site, Server $server): bool
|
||||||
|
{
|
||||||
|
return ($user->isAdmin() || $server->project->users->contains($user)) &&
|
||||||
|
$site->server_id === $server->id &&
|
||||||
|
$server->isReady() &&
|
||||||
|
$site->isReady() &&
|
||||||
|
$site->hasFeature(SiteFeature::COMMANDS) &&
|
||||||
|
$command->site_id === $site->id;
|
||||||
|
}
|
||||||
|
}
|
@ -18,6 +18,9 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
*/
|
*/
|
||||||
public function register(): void
|
public function register(): void
|
||||||
{
|
{
|
||||||
|
if ($this->app->runningInConsole()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
Fortify::ignoreRoutes();
|
Fortify::ignoreRoutes();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,6 +39,8 @@ public function boot(): void
|
|||||||
return new FTP;
|
return new FTP;
|
||||||
});
|
});
|
||||||
|
|
||||||
Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class);
|
if (! $this->app->runningInConsole()) {
|
||||||
|
Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,9 @@ class RouteServiceProvider extends ServiceProvider
|
|||||||
*/
|
*/
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
|
if ($this->app->runningInConsole()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
$this->configureRateLimiting();
|
$this->configureRateLimiting();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
use Filament\Panel;
|
use Filament\Panel;
|
||||||
use Filament\Support\Assets\Js;
|
use Filament\Support\Assets\Js;
|
||||||
use Filament\Support\Colors\Color;
|
use Filament\Support\Colors\Color;
|
||||||
|
use Filament\Support\Enums\MaxWidth;
|
||||||
use Filament\Support\Facades\FilamentAsset;
|
use Filament\Support\Facades\FilamentAsset;
|
||||||
use Filament\Support\Facades\FilamentColor;
|
use Filament\Support\Facades\FilamentColor;
|
||||||
use Filament\Support\Facades\FilamentView;
|
use Filament\Support\Facades\FilamentView;
|
||||||
@ -37,11 +38,18 @@ class WebServiceProvider extends ServiceProvider
|
|||||||
*/
|
*/
|
||||||
public function register(): void
|
public function register(): void
|
||||||
{
|
{
|
||||||
|
if ($this->app->runningInConsole()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
Filament::registerPanel($this->panel(Panel::make()));
|
Filament::registerPanel($this->panel(Panel::make()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
|
if ($this->app->runningInConsole()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
FilamentView::registerRenderHook(
|
FilamentView::registerRenderHook(
|
||||||
PanelsRenderHook::SIDEBAR_NAV_START,
|
PanelsRenderHook::SIDEBAR_NAV_START,
|
||||||
fn () => Livewire::mount(SelectProject::class)
|
fn () => Livewire::mount(SelectProject::class)
|
||||||
@ -89,6 +97,7 @@ public function panel(Panel $panel): Panel
|
|||||||
->colors([
|
->colors([
|
||||||
'primary' => Color::Indigo,
|
'primary' => Color::Indigo,
|
||||||
])
|
])
|
||||||
|
->maxContentWidth(MaxWidth::ScreenTwoExtraLarge)
|
||||||
->viteTheme('resources/css/filament/app/theme.css')
|
->viteTheme('resources/css/filament/app/theme.css')
|
||||||
->brandLogo(fn () => view('components.brand'))
|
->brandLogo(fn () => view('components.brand'))
|
||||||
->brandLogoHeight('30px')
|
->brandLogoHeight('30px')
|
||||||
|
@ -39,4 +39,18 @@ public function checkout(Site $site): void
|
|||||||
$site->id
|
$site->id
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws SSHError
|
||||||
|
*/
|
||||||
|
public function fetchOrigin(Site $site): void
|
||||||
|
{
|
||||||
|
$site->server->ssh($site->user)->exec(
|
||||||
|
view('ssh.git.fetch-origin', [
|
||||||
|
'path' => $site->path,
|
||||||
|
]),
|
||||||
|
'fetch-origin',
|
||||||
|
$site->id
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,14 +3,9 @@
|
|||||||
namespace App\SSH\OS;
|
namespace App\SSH\OS;
|
||||||
|
|
||||||
use App\Exceptions\SSHError;
|
use App\Exceptions\SSHError;
|
||||||
use App\Exceptions\SSHUploadFailed;
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\ServerLog;
|
use App\Models\ServerLog;
|
||||||
use App\Models\Site;
|
use App\Models\Site;
|
||||||
use Illuminate\Filesystem\FilesystemAdapter;
|
|
||||||
use Illuminate\Support\Facades\Storage;
|
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Throwable;
|
|
||||||
|
|
||||||
class OS
|
class OS
|
||||||
{
|
{
|
||||||
@ -178,24 +173,23 @@ public function reboot(): void
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws SSHUploadFailed
|
* @deprecated use write() instead
|
||||||
|
*
|
||||||
|
* @throws SSHError
|
||||||
*/
|
*/
|
||||||
public function editFile(string $path, ?string $content = null): void
|
public function editFileAs(string $path, string $user, ?string $content = null): void
|
||||||
{
|
{
|
||||||
$tmpName = Str::random(10).strtotime('now');
|
$sudo = $user === 'root';
|
||||||
try {
|
$actualUser = $sudo ? $this->server->getSshUser() : $user;
|
||||||
/** @var FilesystemAdapter $storageDisk */
|
|
||||||
$storageDisk = Storage::disk('local');
|
$this->server->ssh($actualUser)->exec(
|
||||||
$storageDisk->put($tmpName, $content);
|
view('ssh.os.edit-file', [
|
||||||
$this->server->ssh()->upload(
|
'path' => $path,
|
||||||
$storageDisk->path($tmpName),
|
'content' => $content,
|
||||||
$path
|
'sudo' => $sudo,
|
||||||
);
|
]),
|
||||||
} catch (Throwable) {
|
'edit-file'
|
||||||
throw new SSHUploadFailed;
|
);
|
||||||
} finally {
|
|
||||||
$this->deleteTempFile($tmpName);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -203,11 +197,11 @@ public function editFile(string $path, ?string $content = null): void
|
|||||||
*/
|
*/
|
||||||
public function readFile(string $path): string
|
public function readFile(string $path): string
|
||||||
{
|
{
|
||||||
return $this->server->ssh()->exec(
|
return trim($this->server->ssh()->exec(
|
||||||
view('ssh.os.read-file', [
|
view('ssh.os.read-file', [
|
||||||
'path' => $path,
|
'path' => $path,
|
||||||
])
|
])
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -263,10 +257,14 @@ public function download(string $url, string $path): string
|
|||||||
/**
|
/**
|
||||||
* @throws SSHError
|
* @throws SSHError
|
||||||
*/
|
*/
|
||||||
public function unzip(string $path): string
|
public function extract(string $path, ?string $destination = null, ?string $user = null): void
|
||||||
{
|
{
|
||||||
return $this->server->ssh()->exec(
|
$this->server->ssh($user)->exec(
|
||||||
'unzip '.$path
|
view('ssh.os.extract', [
|
||||||
|
'path' => $path,
|
||||||
|
'destination' => $destination,
|
||||||
|
]),
|
||||||
|
'extract'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -304,9 +302,9 @@ public function resourceInfo(): array
|
|||||||
/**
|
/**
|
||||||
* @throws SSHError
|
* @throws SSHError
|
||||||
*/
|
*/
|
||||||
public function deleteFile(string $path): void
|
public function deleteFile(string $path, ?string $user = null): void
|
||||||
{
|
{
|
||||||
$this->server->ssh()->exec(
|
$this->server->ssh($user)->exec(
|
||||||
view('ssh.os.delete-file', [
|
view('ssh.os.delete-file', [
|
||||||
'path' => $path,
|
'path' => $path,
|
||||||
]),
|
]),
|
||||||
@ -314,10 +312,31 @@ public function deleteFile(string $path): void
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function deleteTempFile(string $name): void
|
/**
|
||||||
|
* @throws SSHError
|
||||||
|
*/
|
||||||
|
public function ls(string $path, ?string $user = null): string
|
||||||
{
|
{
|
||||||
if (Storage::disk('local')->exists($name)) {
|
return $this->server->ssh($user)->exec('ls -la '.$path);
|
||||||
Storage::disk('local')->delete($name);
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
|
* @throws SSHError
|
||||||
|
*/
|
||||||
|
public function write(string $path, string $content, ?string $user = null): void
|
||||||
|
{
|
||||||
|
$this->server->ssh()->write(
|
||||||
|
$path,
|
||||||
|
$content,
|
||||||
|
$user
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws SSHError
|
||||||
|
*/
|
||||||
|
public function mkdir(string $path, ?string $user = null): string
|
||||||
|
{
|
||||||
|
return $this->server->ssh($user)->exec('mkdir -p '.$path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,5 @@
|
|||||||
|
|
||||||
interface Firewall
|
interface Firewall
|
||||||
{
|
{
|
||||||
public function addRule(string $type, string $protocol, int $port, string $source, ?string $mask): void;
|
public function applyRules(): void;
|
||||||
|
|
||||||
public function removeRule(string $type, string $protocol, int $port, string $source, ?string $mask): void;
|
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\SSH\Services\Firewall;
|
namespace App\SSH\Services\Firewall;
|
||||||
|
|
||||||
|
use App\Enums\FirewallRuleStatus;
|
||||||
use App\Exceptions\SSHError;
|
use App\Exceptions\SSHError;
|
||||||
|
|
||||||
class Ufw extends AbstractFirewall
|
class Ufw extends AbstractFirewall
|
||||||
@ -26,34 +27,16 @@ public function uninstall(): void
|
|||||||
/**
|
/**
|
||||||
* @throws SSHError
|
* @throws SSHError
|
||||||
*/
|
*/
|
||||||
public function addRule(string $type, string $protocol, int $port, string $source, ?string $mask): void
|
public function applyRules(): void
|
||||||
{
|
{
|
||||||
$this->service->server->ssh()->exec(
|
$rules = $this->service->server
|
||||||
view('ssh.services.firewall.ufw.add-rule', [
|
->firewallRules()
|
||||||
'type' => $type,
|
->where('status', '!=', FirewallRuleStatus::DELETING)
|
||||||
'protocol' => $protocol,
|
->get();
|
||||||
'port' => $port,
|
|
||||||
'source' => $source,
|
|
||||||
'mask' => $mask || $mask === 0 ? '/'.$mask : '',
|
|
||||||
]),
|
|
||||||
'add-firewall-rule'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws SSHError
|
|
||||||
*/
|
|
||||||
public function removeRule(string $type, string $protocol, int $port, string $source, ?string $mask): void
|
|
||||||
{
|
|
||||||
$this->service->server->ssh()->exec(
|
$this->service->server->ssh()->exec(
|
||||||
view('ssh.services.firewall.ufw.remove-rule', [
|
view('ssh.services.firewall.ufw.apply-rules', compact('rules')),
|
||||||
'type' => $type,
|
'apply-rules'
|
||||||
'protocol' => $protocol,
|
|
||||||
'port' => $port,
|
|
||||||
'source' => $source,
|
|
||||||
'mask' => $mask || $mask === 0 ? '/'.$mask : '',
|
|
||||||
]),
|
|
||||||
'remove-firewall-rule'
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -135,7 +135,7 @@ public function createFpmPool(string $user, string $version, $site_id): void
|
|||||||
'user' => $user,
|
'user' => $user,
|
||||||
'version' => $version,
|
'version' => $version,
|
||||||
]),
|
]),
|
||||||
true
|
'root'
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->service->server->systemd()->restart($this->service->unit);
|
$this->service->server->systemd()->restart($this->service->unit);
|
||||||
|
@ -55,7 +55,7 @@ public function create(
|
|||||||
'numprocs' => (string) $numprocs,
|
'numprocs' => (string) $numprocs,
|
||||||
'logFile' => $logFile,
|
'logFile' => $logFile,
|
||||||
]),
|
]),
|
||||||
true
|
'root'
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->service->server->ssh()->exec(
|
$this->service->server->ssh()->exec(
|
||||||
|
@ -26,7 +26,7 @@ public function install(): void
|
|||||||
view('ssh.services.webserver.nginx.nginx', [
|
view('ssh.services.webserver.nginx.nginx', [
|
||||||
'user' => $this->service->server->getSshUser(),
|
'user' => $this->service->server->getSshUser(),
|
||||||
]),
|
]),
|
||||||
true
|
'root'
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->service->server->systemd()->restart('nginx');
|
$this->service->server->systemd()->restart('nginx');
|
||||||
@ -83,7 +83,7 @@ public function createVHost(Site $site): void
|
|||||||
view('ssh.services.webserver.nginx.vhost', [
|
view('ssh.services.webserver.nginx.vhost', [
|
||||||
'site' => $site,
|
'site' => $site,
|
||||||
]),
|
]),
|
||||||
true
|
'root'
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->service->server->ssh()->exec(
|
$this->service->server->ssh()->exec(
|
||||||
@ -108,7 +108,7 @@ public function updateVHost(Site $site, ?string $vhost = null): void
|
|||||||
$vhost ?? view('ssh.services.webserver.nginx.vhost', [
|
$vhost ?? view('ssh.services.webserver.nginx.vhost', [
|
||||||
'site' => $site,
|
'site' => $site,
|
||||||
]),
|
]),
|
||||||
true
|
'root'
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->service->server->systemd()->restart('nginx');
|
$this->service->server->systemd()->restart('nginx');
|
||||||
@ -199,11 +199,13 @@ public function setupSSL(Ssl $ssl): void
|
|||||||
*/
|
*/
|
||||||
public function removeSSL(Ssl $ssl): void
|
public function removeSSL(Ssl $ssl): void
|
||||||
{
|
{
|
||||||
$this->service->server->ssh()->exec(
|
if ($ssl->certificate_path) {
|
||||||
'sudo rm -rf '.dirname($ssl->certificate_path).'*',
|
$this->service->server->ssh()->exec(
|
||||||
'remove-ssl',
|
'sudo rm -rf '.dirname($ssl->certificate_path),
|
||||||
$ssl->site_id
|
'remove-ssl',
|
||||||
);
|
$ssl->site_id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
$this->updateVHost($ssl->site);
|
$this->updateVHost($ssl->site);
|
||||||
}
|
}
|
||||||
|
@ -149,6 +149,10 @@ public function isRunning(): bool
|
|||||||
$this->server->save();
|
$this->server->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (! $this->server->ip) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (isset($result['Reservations'][0]['Instances'][0]['State']) && isset($result['Reservations'][0]['Instances'][0]['State']['Name'])) {
|
if (isset($result['Reservations'][0]['Instances'][0]['State']) && isset($result['Reservations'][0]['Instances'][0]['State']['Name'])) {
|
||||||
$status = $result['Reservations'][0]['Instances'][0]['State']['Name'];
|
$status = $result['Reservations'][0]['Instances'][0]['State']['Name'];
|
||||||
if ($status == 'running') {
|
if ($status == 'running') {
|
||||||
|
@ -176,6 +176,10 @@ public function isRunning(): bool
|
|||||||
$this->server->save();
|
$this->server->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (! $this->server->ip) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return $status->json()['droplet']['status'] == 'active';
|
return $status->json()['droplet']['status'] == 'active';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,6 +171,10 @@ public function isRunning(): bool
|
|||||||
$this->server->save();
|
$this->server->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (! $this->server->ip) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return $status->json()['instance']['status'] == 'active';
|
return $status->json()['instance']['status'] == 'active';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,6 +37,11 @@ public function editRules(array $input): array
|
|||||||
return $this->createRules($input);
|
return $this->createRules($input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function baseCommands(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
protected function progress(int $percentage): void
|
protected function progress(int $percentage): void
|
||||||
{
|
{
|
||||||
$this->site->progress = $percentage;
|
$this->site->progress = $percentage;
|
||||||
|
54
app/SiteTypes/Laravel.php
Executable file → Normal file
54
app/SiteTypes/Laravel.php
Executable file → Normal file
@ -2,4 +2,56 @@
|
|||||||
|
|
||||||
namespace App\SiteTypes;
|
namespace App\SiteTypes;
|
||||||
|
|
||||||
class Laravel extends PHPSite {}
|
class Laravel extends PHPSite
|
||||||
|
{
|
||||||
|
public function baseCommands(): array
|
||||||
|
{
|
||||||
|
return array_merge(parent::baseCommands(), [
|
||||||
|
// Initial Setup Commands
|
||||||
|
[
|
||||||
|
'name' => 'Generate Application Key',
|
||||||
|
'command' => 'php artisan key:generate',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'Create Storage Symbolic Link',
|
||||||
|
'command' => 'php artisan storage:link',
|
||||||
|
],
|
||||||
|
// Database Commands
|
||||||
|
[
|
||||||
|
'name' => 'Run Database Migrations',
|
||||||
|
'command' => 'php artisan migrate --force',
|
||||||
|
],
|
||||||
|
// Cache & Optimization Commands
|
||||||
|
[
|
||||||
|
'name' => 'Optimize Application',
|
||||||
|
'command' => 'php artisan optimize',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'Clear All Application Optimizations',
|
||||||
|
'command' => 'php artisan optimize:clear',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'Clear Application Cache Only',
|
||||||
|
'command' => 'php artisan cache:clear',
|
||||||
|
],
|
||||||
|
// Queue Commands
|
||||||
|
[
|
||||||
|
'name' => 'Restart Queue Workers',
|
||||||
|
'command' => 'php artisan queue:restart',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'Clear All Failed Queue Jobs',
|
||||||
|
'command' => 'php artisan queue:flush',
|
||||||
|
],
|
||||||
|
// Application State Commands
|
||||||
|
[
|
||||||
|
'name' => 'Put Application in Maintenance Mode',
|
||||||
|
'command' => 'php artisan down --retry=5 --refresh=6 --quiet',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'Bring Application Online',
|
||||||
|
'command' => 'php artisan up',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -13,6 +13,7 @@ public function supportedFeatures(): array
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
SiteFeature::DEPLOYMENT,
|
SiteFeature::DEPLOYMENT,
|
||||||
|
SiteFeature::COMMANDS,
|
||||||
SiteFeature::ENV,
|
SiteFeature::ENV,
|
||||||
SiteFeature::SSL,
|
SiteFeature::SSL,
|
||||||
SiteFeature::QUEUES,
|
SiteFeature::QUEUES,
|
||||||
@ -55,4 +56,9 @@ public function install(): void
|
|||||||
$this->progress(65);
|
$this->progress(65);
|
||||||
$this->site->php()?->restart();
|
$this->site->php()?->restart();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function baseCommands(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,12 +21,23 @@ public function supportedFeatures(): array
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
SiteFeature::DEPLOYMENT,
|
SiteFeature::DEPLOYMENT,
|
||||||
|
SiteFeature::COMMANDS,
|
||||||
SiteFeature::ENV,
|
SiteFeature::ENV,
|
||||||
SiteFeature::SSL,
|
SiteFeature::SSL,
|
||||||
SiteFeature::QUEUES,
|
SiteFeature::QUEUES,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function baseCommands(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
'name' => 'Install Composer Dependencies',
|
||||||
|
'command' => 'composer install --no-dev --no-interaction --no-progress',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
public function createRules(array $input): array
|
public function createRules(array $input): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
@ -19,4 +19,6 @@ public function install(): void;
|
|||||||
public function editRules(array $input): array;
|
public function editRules(array $input): array;
|
||||||
|
|
||||||
public function edit(): void;
|
public function edit(): void;
|
||||||
|
|
||||||
|
public function baseCommands(): array;
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ public function supportedFeatures(): array
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
SiteFeature::SSL,
|
SiteFeature::SSL,
|
||||||
|
SiteFeature::COMMANDS,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,4 +70,9 @@ protected function handleResponseErrors(Response $res, string $repo): void
|
|||||||
throw new RepositoryPermissionDenied($repo);
|
throw new RepositoryPermissionDenied($repo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getWebhookBranch(array $payload): string
|
||||||
|
{
|
||||||
|
return str($payload['ref'] ?? '')->after('refs/heads/')->toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -183,4 +183,9 @@ private function getAuthenticationHeaders(): array
|
|||||||
'Authorization' => 'Basic '.$basicAuth,
|
'Authorization' => 'Basic '.$basicAuth,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getWebhookBranch(array $payload): string
|
||||||
|
{
|
||||||
|
return data_get($payload, 'push.changes.0.new.name', 'default-branch');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -176,4 +176,9 @@ public function getApiUrl(): string
|
|||||||
|
|
||||||
return $host.$this->apiVersion;
|
return $host.$this->apiVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getWebhookBranch(array $payload): string
|
||||||
|
{
|
||||||
|
return $payload['ref'] ?? '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,4 +36,6 @@ public function getLastCommit(string $repo, string $branch): ?array;
|
|||||||
* @throws FailedToDeployGitKey
|
* @throws FailedToDeployGitKey
|
||||||
*/
|
*/
|
||||||
public function deployKey(string $title, string $repo, string $key): void;
|
public function deployKey(string $title, string $repo, string $key): void;
|
||||||
|
|
||||||
|
public function getWebhookBranch(array $payload): string;
|
||||||
}
|
}
|
||||||
|
@ -82,7 +82,7 @@ public function exec(string $command, string $log = '', ?int $siteId = null, ?bo
|
|||||||
return $output;
|
return $output;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function upload(string $local, string $remote): void
|
public function upload(string $local, string $remote, ?string $owner = null): void
|
||||||
{
|
{
|
||||||
$this->uploadedLocalPath = $local;
|
$this->uploadedLocalPath = $local;
|
||||||
$this->uploadedRemotePath = $remote;
|
$this->uploadedRemotePath = $remote;
|
||||||
|
@ -178,3 +178,31 @@ function get_from_route(string $modelName, string $routeKey): mixed
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function absolute_path(string $path): string
|
||||||
|
{
|
||||||
|
$parts = explode('/', $path);
|
||||||
|
$absoluteParts = [];
|
||||||
|
|
||||||
|
foreach ($parts as $part) {
|
||||||
|
if ($part === '' || $part === '.') {
|
||||||
|
continue; // Skip empty and current directory parts
|
||||||
|
}
|
||||||
|
if ($part === '..') {
|
||||||
|
array_pop($absoluteParts); // Move up one directory
|
||||||
|
} else {
|
||||||
|
$absoluteParts[] = $part; // Add valid directory parts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return '/'.implode('/', $absoluteParts);
|
||||||
|
}
|
||||||
|
|
||||||
|
function home_path(string $user): string
|
||||||
|
{
|
||||||
|
if ($user === 'root') {
|
||||||
|
return '/root';
|
||||||
|
}
|
||||||
|
|
||||||
|
return '/home/'.$user;
|
||||||
|
}
|
||||||
|
@ -64,7 +64,7 @@ protected function getHeaderActions(): array
|
|||||||
];
|
];
|
||||||
|
|
||||||
foreach ($this->script->getVariables() as $variable) {
|
foreach ($this->script->getVariables() as $variable) {
|
||||||
$form[] = TextInput::make($variable)
|
$form[] = TextInput::make('variables.'.$variable)
|
||||||
->label($variable)
|
->label($variable)
|
||||||
->rules(fn (Get $get) => ExecuteScript::rules($get())['variables.*']);
|
->rules(fn (Get $get) => ExecuteScript::rules($get())['variables.*']);
|
||||||
}
|
}
|
||||||
|
26
app/Web/Pages/Servers/FileManager/Index.php
Normal file
26
app/Web/Pages/Servers/FileManager/Index.php
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Web\Pages\Servers\FileManager;
|
||||||
|
|
||||||
|
use App\Web\Pages\Servers\Page;
|
||||||
|
|
||||||
|
class Index extends Page
|
||||||
|
{
|
||||||
|
protected static ?string $slug = 'servers/{server}/file-manager';
|
||||||
|
|
||||||
|
protected static ?string $title = 'File Manager';
|
||||||
|
|
||||||
|
protected $listeners = ['$refresh'];
|
||||||
|
|
||||||
|
public function mount(): void
|
||||||
|
{
|
||||||
|
$this->authorize('manage', $this->server);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getWidgets(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
[Widgets\FilesList::class, ['server' => $this->server]],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
372
app/Web/Pages/Servers/FileManager/Widgets/FilesList.php
Normal file
372
app/Web/Pages/Servers/FileManager/Widgets/FilesList.php
Normal file
@ -0,0 +1,372 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Web\Pages\Servers\FileManager\Widgets;
|
||||||
|
|
||||||
|
use App\Actions\FileManager\FetchFiles;
|
||||||
|
use App\Exceptions\SSHError;
|
||||||
|
use App\Models\File;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Web\Fields\CodeEditorField;
|
||||||
|
use App\Web\Pages\Servers\FileManager\Index;
|
||||||
|
use Filament\Forms\Components\FileUpload;
|
||||||
|
use Filament\Forms\Components\TextInput;
|
||||||
|
use Filament\Support\Enums\ActionSize;
|
||||||
|
use Filament\Support\Enums\IconPosition;
|
||||||
|
use Filament\Tables\Actions\Action;
|
||||||
|
use Filament\Tables\Actions\ActionGroup;
|
||||||
|
use Filament\Tables\Actions\DeleteBulkAction;
|
||||||
|
use Filament\Tables\Columns\IconColumn;
|
||||||
|
use Filament\Tables\Columns\TextColumn;
|
||||||
|
use Filament\Tables\Table;
|
||||||
|
use Filament\Widgets\TableWidget as Widget;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
class FilesList extends Widget
|
||||||
|
{
|
||||||
|
public Server $server;
|
||||||
|
|
||||||
|
public string $serverUser;
|
||||||
|
|
||||||
|
public string $path;
|
||||||
|
|
||||||
|
protected $listeners = ['$refresh'];
|
||||||
|
|
||||||
|
public function mount(): void
|
||||||
|
{
|
||||||
|
$this->serverUser = $this->server->ssh_user;
|
||||||
|
$this->path = home_path($this->serverUser);
|
||||||
|
if (request()->has('path') && request()->has('user')) {
|
||||||
|
$this->path = request('path');
|
||||||
|
$this->serverUser = request('user');
|
||||||
|
}
|
||||||
|
$this->refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getTableHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
$this->homeAction(),
|
||||||
|
$this->userAction(),
|
||||||
|
ActionGroup::make([
|
||||||
|
$this->refreshAction(),
|
||||||
|
$this->newFileAction(),
|
||||||
|
$this->newDirectoryAction(),
|
||||||
|
$this->uploadAction(),
|
||||||
|
])
|
||||||
|
->tooltip('Toolbar')
|
||||||
|
->icon('heroicon-o-ellipsis-vertical')
|
||||||
|
->color('gray')
|
||||||
|
->size(ActionSize::Large)
|
||||||
|
->iconPosition(IconPosition::After)
|
||||||
|
->dropdownPlacement('bottom-end'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getTableQuery(): Builder
|
||||||
|
{
|
||||||
|
return File::query()
|
||||||
|
->where('user_id', auth()->id())
|
||||||
|
->where('server_id', $this->server->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function table(Table $table): Table
|
||||||
|
{
|
||||||
|
return $table
|
||||||
|
->query($this->getTableQuery())
|
||||||
|
->headerActions($this->getTableHeaderActions())
|
||||||
|
->heading(str($this->path)->substr(-50)->start(str($this->path)->length() > 50 ? '...' : ''))
|
||||||
|
->columns([
|
||||||
|
IconColumn::make('type')
|
||||||
|
->sortable()
|
||||||
|
->icon(fn (File $file) => $this->getIcon($file)),
|
||||||
|
TextColumn::make('name')
|
||||||
|
->sortable(),
|
||||||
|
TextColumn::make('size')
|
||||||
|
->sortable(),
|
||||||
|
TextColumn::make('owner')
|
||||||
|
->sortable(),
|
||||||
|
TextColumn::make('group')
|
||||||
|
->sortable(),
|
||||||
|
TextColumn::make('date')
|
||||||
|
->sortable(),
|
||||||
|
TextColumn::make('permissions')
|
||||||
|
->sortable(),
|
||||||
|
])
|
||||||
|
->recordUrl(function (File $file) {
|
||||||
|
if ($file->type === 'directory') {
|
||||||
|
return Index::getUrl([
|
||||||
|
'server' => $this->server->id,
|
||||||
|
'user' => $file->server_user,
|
||||||
|
'path' => absolute_path($file->path.'/'.$file->name),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
})
|
||||||
|
->defaultSort('type')
|
||||||
|
->actions([
|
||||||
|
$this->extractAction(),
|
||||||
|
$this->downloadAction(),
|
||||||
|
$this->editAction(),
|
||||||
|
$this->deleteAction(),
|
||||||
|
])
|
||||||
|
->checkIfRecordIsSelectableUsing(
|
||||||
|
fn (File $file): bool => $file->name !== '..',
|
||||||
|
)
|
||||||
|
->bulkActions([
|
||||||
|
DeleteBulkAction::make()
|
||||||
|
->requiresConfirmation(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function changeUser(string $user): void
|
||||||
|
{
|
||||||
|
$this->redirect(
|
||||||
|
Index::getUrl([
|
||||||
|
'server' => $this->server->id,
|
||||||
|
'user' => $user,
|
||||||
|
'path' => home_path($user),
|
||||||
|
]),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function refresh(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
app(FetchFiles::class)->fetch(
|
||||||
|
auth()->user(),
|
||||||
|
$this->server,
|
||||||
|
[
|
||||||
|
'user' => $this->serverUser,
|
||||||
|
'path' => $this->path,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
} catch (SSHError) {
|
||||||
|
abort(404);
|
||||||
|
}
|
||||||
|
$this->dispatch('$refresh');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getIcon(File $file): string
|
||||||
|
{
|
||||||
|
if ($file->type === 'directory') {
|
||||||
|
return 'heroicon-o-folder';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (str($file->name)->endsWith('.blade.php')) {
|
||||||
|
return 'laravel';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (str($file->name)->endsWith('.php')) {
|
||||||
|
return 'php';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'heroicon-o-document-text';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function homeAction(): Action
|
||||||
|
{
|
||||||
|
return Action::make('home')
|
||||||
|
->label('Home')
|
||||||
|
->size(ActionSize::Small)
|
||||||
|
->icon('heroicon-o-home')
|
||||||
|
->action(function () {
|
||||||
|
$this->path = home_path($this->serverUser);
|
||||||
|
$this->refresh();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function userAction(): ActionGroup
|
||||||
|
{
|
||||||
|
$users = [];
|
||||||
|
foreach ($this->server->getSshUsers() as $user) {
|
||||||
|
$users[] = Action::make('user-'.$user)
|
||||||
|
->action(fn () => $this->changeUser($user))
|
||||||
|
->label($user);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ActionGroup::make($users)
|
||||||
|
->tooltip('Change user')
|
||||||
|
->label($this->serverUser)
|
||||||
|
->button()
|
||||||
|
->size(ActionSize::Small)
|
||||||
|
->color('gray')
|
||||||
|
->icon('heroicon-o-chevron-up-down')
|
||||||
|
->iconPosition(IconPosition::After)
|
||||||
|
->dropdownPlacement('bottom-end');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function refreshAction(): Action
|
||||||
|
{
|
||||||
|
return Action::make('refresh')
|
||||||
|
->label('Refresh')
|
||||||
|
->icon('heroicon-o-arrow-path')
|
||||||
|
->action(fn () => $this->refresh());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function newFileAction(): Action
|
||||||
|
{
|
||||||
|
return Action::make('new-file')
|
||||||
|
->label('New File')
|
||||||
|
->icon('heroicon-o-document-text')
|
||||||
|
->action(function (array $data) {
|
||||||
|
run_action($this, function () use ($data) {
|
||||||
|
$this->server->os()->write(
|
||||||
|
$this->path.'/'.$data['name'],
|
||||||
|
str_replace("\r\n", "\n", $data['content']),
|
||||||
|
$this->serverUser
|
||||||
|
);
|
||||||
|
$this->refresh();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
->form(function () {
|
||||||
|
return [
|
||||||
|
TextInput::make('name')
|
||||||
|
->placeholder('file-name.txt'),
|
||||||
|
CodeEditorField::make('content'),
|
||||||
|
];
|
||||||
|
})
|
||||||
|
->modalSubmitActionLabel('Create')
|
||||||
|
->modalHeading('New File')
|
||||||
|
->modalWidth('4xl');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function newDirectoryAction(): Action
|
||||||
|
{
|
||||||
|
return Action::make('new-directory')
|
||||||
|
->label('New Directory')
|
||||||
|
->icon('heroicon-o-folder')
|
||||||
|
->action(function (array $data) {
|
||||||
|
run_action($this, function () use ($data) {
|
||||||
|
$this->server->os()->mkdir(
|
||||||
|
$this->path.'/'.$data['name'],
|
||||||
|
$this->serverUser
|
||||||
|
);
|
||||||
|
$this->refresh();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
->form(function () {
|
||||||
|
return [
|
||||||
|
TextInput::make('name')
|
||||||
|
->placeholder('directory name'),
|
||||||
|
];
|
||||||
|
})
|
||||||
|
->modalSubmitActionLabel('Create')
|
||||||
|
->modalHeading('New Directory')
|
||||||
|
->modalWidth('lg');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function uploadAction(): Action
|
||||||
|
{
|
||||||
|
return Action::make('upload')
|
||||||
|
->label('Upload File')
|
||||||
|
->icon('heroicon-o-arrow-up-on-square')
|
||||||
|
->action(function (array $data) {
|
||||||
|
//
|
||||||
|
})
|
||||||
|
->after(function (array $data) {
|
||||||
|
run_action($this, function () use ($data) {
|
||||||
|
foreach ($data['file'] as $file) {
|
||||||
|
$this->server->ssh()->upload(
|
||||||
|
Storage::disk('tmp')->path($file),
|
||||||
|
$this->path.'/'.$file,
|
||||||
|
$this->serverUser
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$this->refresh();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
->form(function () {
|
||||||
|
return [
|
||||||
|
FileUpload::make('file')
|
||||||
|
->disk('tmp')
|
||||||
|
->multiple()
|
||||||
|
->preserveFilenames(),
|
||||||
|
];
|
||||||
|
})
|
||||||
|
->modalSubmitActionLabel('Upload to Server')
|
||||||
|
->modalHeading('Upload File')
|
||||||
|
->modalWidth('xl');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function extractAction(): Action
|
||||||
|
{
|
||||||
|
return Action::make('extract')
|
||||||
|
->tooltip('Extract')
|
||||||
|
->icon('heroicon-o-archive-box')
|
||||||
|
->hiddenLabel()
|
||||||
|
->visible(fn (File $file) => $file->isExtractable())
|
||||||
|
->action(function (File $file) {
|
||||||
|
$file->server->os()->extract($file->getFilePath(), $file->path, $file->server_user);
|
||||||
|
$this->refresh();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function downloadAction(): Action
|
||||||
|
{
|
||||||
|
return Action::make('download')
|
||||||
|
->tooltip('Download')
|
||||||
|
->icon('heroicon-o-arrow-down-tray')
|
||||||
|
->hiddenLabel()
|
||||||
|
->visible(fn (File $file) => $file->type === 'file')
|
||||||
|
->action(function (File $file) {
|
||||||
|
$file->server->ssh($file->server_user)->download(
|
||||||
|
Storage::disk('tmp')->path($file->name),
|
||||||
|
$file->getFilePath()
|
||||||
|
);
|
||||||
|
|
||||||
|
return Storage::disk('tmp')->download($file->name);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function editAction(): Action
|
||||||
|
{
|
||||||
|
return Action::make('edit')
|
||||||
|
->tooltip('Edit')
|
||||||
|
->icon('heroicon-o-pencil')
|
||||||
|
->hiddenLabel()
|
||||||
|
->visible(fn (File $file) => $file->type === 'file')
|
||||||
|
->action(function (File $file, array $data) {
|
||||||
|
$file->server->os()->write(
|
||||||
|
$file->getFilePath(),
|
||||||
|
str_replace("\r\n", "\n", $data['content']),
|
||||||
|
$file->server_user
|
||||||
|
);
|
||||||
|
$this->refresh();
|
||||||
|
})
|
||||||
|
->form(function (File $file) {
|
||||||
|
return [
|
||||||
|
CodeEditorField::make('content')
|
||||||
|
->formatStateUsing(function () use ($file) {
|
||||||
|
$file->server->ssh($file->server_user)->download(
|
||||||
|
Storage::disk('tmp')->path($file->name),
|
||||||
|
$file->getFilePath()
|
||||||
|
);
|
||||||
|
|
||||||
|
return Storage::disk('tmp')->get(basename($file->getFilePath()));
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
})
|
||||||
|
->modalSubmitActionLabel('Save')
|
||||||
|
->modalHeading('Edit')
|
||||||
|
->modalWidth('4xl');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function deleteAction(): Action
|
||||||
|
{
|
||||||
|
return Action::make('delete')
|
||||||
|
->tooltip('Delete')
|
||||||
|
->icon('heroicon-o-trash')
|
||||||
|
->color('danger')
|
||||||
|
->hiddenLabel()
|
||||||
|
->requiresConfirmation()
|
||||||
|
->visible(fn (File $file) => $file->name !== '..')
|
||||||
|
->action(function (File $file) {
|
||||||
|
run_action($this, function () use ($file) {
|
||||||
|
$file->delete();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -2,14 +2,17 @@
|
|||||||
|
|
||||||
namespace App\Web\Pages\Servers\Firewall;
|
namespace App\Web\Pages\Servers\Firewall;
|
||||||
|
|
||||||
use App\Actions\FirewallRule\CreateRule;
|
use App\Actions\FirewallRule\ManageRule;
|
||||||
use App\Models\FirewallRule;
|
use App\Models\FirewallRule;
|
||||||
use App\Web\Pages\Servers\Page;
|
use App\Web\Pages\Servers\Page;
|
||||||
use Filament\Actions\Action;
|
use Filament\Actions\Action;
|
||||||
|
use Filament\Forms\Components\Checkbox;
|
||||||
use Filament\Forms\Components\Select;
|
use Filament\Forms\Components\Select;
|
||||||
use Filament\Forms\Components\TextInput;
|
use Filament\Forms\Components\TextInput;
|
||||||
|
use Filament\Forms\Get;
|
||||||
use Filament\Notifications\Notification;
|
use Filament\Notifications\Notification;
|
||||||
use Filament\Support\Enums\MaxWidth;
|
use Filament\Support\Enums\MaxWidth;
|
||||||
|
use Illuminate\Support\Facades\Request;
|
||||||
|
|
||||||
class Index extends Page
|
class Index extends Page
|
||||||
{
|
{
|
||||||
@ -31,6 +34,64 @@ public function getWidgets(): array
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function getFirewallForm(?FirewallRule $record = null): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
TextInput::make('name')
|
||||||
|
->label('Purpose')
|
||||||
|
->default($record->name ?? null)
|
||||||
|
->rules(ManageRule::rules()['name']),
|
||||||
|
Select::make('type')
|
||||||
|
->label('Type')
|
||||||
|
->default($record->type ?? 'allow')
|
||||||
|
->options([
|
||||||
|
'allow' => 'Allow',
|
||||||
|
'deny' => 'Deny',
|
||||||
|
])
|
||||||
|
->rules(ManageRule::rules()['type']),
|
||||||
|
Select::make('protocol')
|
||||||
|
->label('Protocol')
|
||||||
|
->default($record->protocol ?? 'tcp')
|
||||||
|
->options([
|
||||||
|
'tcp' => 'TCP',
|
||||||
|
'udp' => 'UDP',
|
||||||
|
])
|
||||||
|
->rules(ManageRule::rules()['protocol']),
|
||||||
|
TextInput::make('port')
|
||||||
|
->label('Port')
|
||||||
|
->default($record->port ?? null)
|
||||||
|
->rules(['required', 'integer']),
|
||||||
|
Checkbox::make('source_any')
|
||||||
|
->label('Any Source')
|
||||||
|
->default(($record->source ?? null) == null)
|
||||||
|
->rules(['boolean'])
|
||||||
|
->helperText('Allow connections from any source, regardless of their IP address or subnet mask.')
|
||||||
|
->live(),
|
||||||
|
TextInput::make('source')
|
||||||
|
->hidden(fn (Get $get) => $get('source_any') == true)
|
||||||
|
->label('Source')
|
||||||
|
->helperText('The IP address of the source of the connection.')
|
||||||
|
->rules(ManageRule::rules()['source'])
|
||||||
|
->default($record->source ?? null)
|
||||||
|
->suffixAction(
|
||||||
|
\Filament\Forms\Components\Actions\Action::make('get_ip')
|
||||||
|
->icon('heroicon-o-globe-alt')
|
||||||
|
->color('primary')
|
||||||
|
->tooltip('Use My IP')
|
||||||
|
->action(function ($set) {
|
||||||
|
$ip = Request::ip();
|
||||||
|
$set('source', $ip);
|
||||||
|
})
|
||||||
|
),
|
||||||
|
TextInput::make('mask')
|
||||||
|
->hidden(fn (Get $get) => $get('source_any') == true)
|
||||||
|
->label('Mask')
|
||||||
|
->default($record->mask ?? null)
|
||||||
|
->helperText('The subnet mask of the source of the connection. Leave blank for a single IP address.')
|
||||||
|
->rules(ManageRule::rules()['mask']),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
protected function getHeaderActions(): array
|
protected function getHeaderActions(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
@ -45,37 +106,19 @@ protected function getHeaderActions(): array
|
|||||||
->label('Create a Rule')
|
->label('Create a Rule')
|
||||||
->icon('heroicon-o-plus')
|
->icon('heroicon-o-plus')
|
||||||
->modalWidth(MaxWidth::Large)
|
->modalWidth(MaxWidth::Large)
|
||||||
->form([
|
->modalHeading('Create Firewall Rule')
|
||||||
Select::make('type')
|
->modalDescription('Add a new rule to the firewall')
|
||||||
->native(false)
|
->modalSubmitActionLabel('Create')
|
||||||
->options([
|
->form(self::getFirewallForm())
|
||||||
'allow' => 'Allow',
|
|
||||||
'deny' => 'Deny',
|
|
||||||
])
|
|
||||||
->rules(CreateRule::rules()['type']),
|
|
||||||
Select::make('protocol')
|
|
||||||
->native(false)
|
|
||||||
->options([
|
|
||||||
'tcp' => 'TCP',
|
|
||||||
'udp' => 'UDP',
|
|
||||||
])
|
|
||||||
->rules(CreateRule::rules()['protocol']),
|
|
||||||
TextInput::make('port')
|
|
||||||
->rules(CreateRule::rules()['port']),
|
|
||||||
TextInput::make('source')
|
|
||||||
->rules(CreateRule::rules()['source']),
|
|
||||||
TextInput::make('mask')
|
|
||||||
->rules(CreateRule::rules()['mask']),
|
|
||||||
])
|
|
||||||
->action(function (array $data) {
|
->action(function (array $data) {
|
||||||
run_action($this, function () use ($data) {
|
run_action($this, function () use ($data) {
|
||||||
app(CreateRule::class)->create($this->server, $data);
|
app(ManageRule::class)->create($this->server, $data);
|
||||||
|
|
||||||
$this->dispatch('$refresh');
|
$this->dispatch('$refresh');
|
||||||
|
|
||||||
Notification::make()
|
Notification::make()
|
||||||
->success()
|
->success()
|
||||||
->title('Firewall rule created!')
|
->title('Applying Firewall Rule')
|
||||||
->send();
|
->send();
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
@ -2,15 +2,18 @@
|
|||||||
|
|
||||||
namespace App\Web\Pages\Servers\Firewall\Widgets;
|
namespace App\Web\Pages\Servers\Firewall\Widgets;
|
||||||
|
|
||||||
use App\Actions\FirewallRule\DeleteRule;
|
use App\Actions\FirewallRule\ManageRule;
|
||||||
use App\Models\FirewallRule;
|
use App\Models\FirewallRule;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use App\Web\Pages\Servers\Firewall\Index;
|
||||||
use Filament\Notifications\Notification;
|
use Filament\Notifications\Notification;
|
||||||
|
use Filament\Support\Enums\MaxWidth;
|
||||||
use Filament\Tables\Actions\Action;
|
use Filament\Tables\Actions\Action;
|
||||||
use Filament\Tables\Columns\TextColumn;
|
use Filament\Tables\Columns\TextColumn;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
use Filament\Widgets\TableWidget as Widget;
|
use Filament\Widgets\TableWidget as Widget;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class RulesList extends Widget
|
class RulesList extends Widget
|
||||||
{
|
{
|
||||||
@ -26,19 +29,40 @@ protected function getTableQuery(): Builder
|
|||||||
protected function getTableColumns(): array
|
protected function getTableColumns(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
TextColumn::make('name')
|
||||||
|
->searchable()
|
||||||
|
->sortable()
|
||||||
|
->label('Purpose'),
|
||||||
TextColumn::make('type')
|
TextColumn::make('type')
|
||||||
->sortable()
|
->sortable()
|
||||||
->extraAttributes(['class' => 'uppercase'])
|
->badge()
|
||||||
->color(fn (FirewallRule $record) => $record->type === 'allow' ? 'green' : 'red'),
|
->color(fn ($state) => $state === 'allow' ? 'success' : 'warning')
|
||||||
|
->label('Type')
|
||||||
|
->formatStateUsing(fn ($state) => Str::upper($state)),
|
||||||
|
TextColumn::make('id')
|
||||||
|
->sortable()
|
||||||
|
->label('Source')
|
||||||
|
->formatStateUsing(function (FirewallRule $record) {
|
||||||
|
$source = $record->source == null ? 'any' : $record->source;
|
||||||
|
if ($source !== 'any' && $record->mask !== null) {
|
||||||
|
$source .= '/'.$record->mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $source;
|
||||||
|
}),
|
||||||
TextColumn::make('protocol')
|
TextColumn::make('protocol')
|
||||||
->sortable()
|
->sortable()
|
||||||
->extraAttributes(['class' => 'uppercase']),
|
->badge()
|
||||||
|
->color('primary')
|
||||||
|
->label('Protocol')
|
||||||
|
->formatStateUsing(fn ($state) => Str::upper($state)),
|
||||||
TextColumn::make('port')
|
TextColumn::make('port')
|
||||||
->sortable(),
|
->sortable()
|
||||||
TextColumn::make('source')
|
->label('Port'),
|
||||||
->sortable(),
|
TextColumn::make('status')
|
||||||
TextColumn::make('mask')
|
->label('Status')
|
||||||
->sortable(),
|
->badge()
|
||||||
|
->color(fn (FirewallRule $record) => $record->getStatusColor()),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,6 +73,28 @@ public function table(Table $table): Table
|
|||||||
->query($this->getTableQuery())
|
->query($this->getTableQuery())
|
||||||
->columns($this->getTableColumns())
|
->columns($this->getTableColumns())
|
||||||
->actions([
|
->actions([
|
||||||
|
Action::make('edit')
|
||||||
|
->icon('heroicon-o-pencil')
|
||||||
|
->tooltip('Edit')
|
||||||
|
->hiddenLabel()
|
||||||
|
->modalWidth(MaxWidth::Large)
|
||||||
|
->modalHeading('Edit Firewall Rule')
|
||||||
|
->modalDescription('Edit the associated servers firewall rule.')
|
||||||
|
->modalSubmitActionLabel('Update')
|
||||||
|
->authorize(fn (FirewallRule $record) => auth()->user()->can('update', $record))
|
||||||
|
->form(fn ($record) => Index::getFirewallForm($record))
|
||||||
|
->action(function (FirewallRule $record, array $data) {
|
||||||
|
run_action($this, function () use ($record, $data) {
|
||||||
|
app(ManageRule::class)->update($record, $data);
|
||||||
|
|
||||||
|
$this->dispatch('$refresh');
|
||||||
|
|
||||||
|
Notification::make()
|
||||||
|
->success()
|
||||||
|
->title('Applying Firewall Rule')
|
||||||
|
->send();
|
||||||
|
});
|
||||||
|
}),
|
||||||
Action::make('delete')
|
Action::make('delete')
|
||||||
->icon('heroicon-o-trash')
|
->icon('heroicon-o-trash')
|
||||||
->tooltip('Delete')
|
->tooltip('Delete')
|
||||||
@ -58,7 +104,7 @@ public function table(Table $table): Table
|
|||||||
->authorize(fn (FirewallRule $record) => auth()->user()->can('delete', $record))
|
->authorize(fn (FirewallRule $record) => auth()->user()->can('delete', $record))
|
||||||
->action(function (FirewallRule $record) {
|
->action(function (FirewallRule $record) {
|
||||||
try {
|
try {
|
||||||
app(DeleteRule::class)->delete($this->server, $record);
|
app(ManageRule::class)->delete($record);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
Notification::make()
|
Notification::make()
|
||||||
->danger()
|
->danger()
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
use App\Web\Pages\Servers\Console\Index as ConsoleIndex;
|
use App\Web\Pages\Servers\Console\Index as ConsoleIndex;
|
||||||
use App\Web\Pages\Servers\CronJobs\Index as CronJobsIndex;
|
use App\Web\Pages\Servers\CronJobs\Index as CronJobsIndex;
|
||||||
use App\Web\Pages\Servers\Databases\Index as DatabasesIndex;
|
use App\Web\Pages\Servers\Databases\Index as DatabasesIndex;
|
||||||
|
use App\Web\Pages\Servers\FileManager\Index as FileManagerIndex;
|
||||||
use App\Web\Pages\Servers\Firewall\Index as FirewallIndex;
|
use App\Web\Pages\Servers\Firewall\Index as FirewallIndex;
|
||||||
use App\Web\Pages\Servers\Logs\Index as LogsIndex;
|
use App\Web\Pages\Servers\Logs\Index as LogsIndex;
|
||||||
use App\Web\Pages\Servers\Metrics\Index as MetricsIndex;
|
use App\Web\Pages\Servers\Metrics\Index as MetricsIndex;
|
||||||
@ -59,6 +60,13 @@ public function getSubNavigation(): array
|
|||||||
->url(DatabasesIndex::getUrl(parameters: ['server' => $this->server]));
|
->url(DatabasesIndex::getUrl(parameters: ['server' => $this->server]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (auth()->user()->can('manage', $this->server)) {
|
||||||
|
$items[] = NavigationItem::make(FileManagerIndex::getNavigationLabel())
|
||||||
|
->icon('heroicon-o-folder')
|
||||||
|
->isActiveWhen(fn () => request()->routeIs(FileManagerIndex::getRouteName().'*'))
|
||||||
|
->url(FileManagerIndex::getUrl(parameters: ['server' => $this->server]));
|
||||||
|
}
|
||||||
|
|
||||||
if (auth()->user()->can('viewAny', [Service::class, $this->server])) {
|
if (auth()->user()->can('viewAny', [Service::class, $this->server])) {
|
||||||
$items[] = NavigationItem::make(PHPIndex::getNavigationLabel())
|
$items[] = NavigationItem::make(PHPIndex::getNavigationLabel())
|
||||||
->icon('icon-php-alt')
|
->icon('icon-php-alt')
|
||||||
|
@ -56,6 +56,10 @@ public function getWidgets(): array
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($this->site->isReady()) {
|
if ($this->site->isReady()) {
|
||||||
|
if (in_array(SiteFeature::COMMANDS, $this->site->type()->supportedFeatures())) {
|
||||||
|
$widgets[] = [Widgets\Commands::class, ['site' => $this->site]];
|
||||||
|
}
|
||||||
|
|
||||||
if (in_array(SiteFeature::DEPLOYMENT, $this->site->type()->supportedFeatures())) {
|
if (in_array(SiteFeature::DEPLOYMENT, $this->site->type()->supportedFeatures())) {
|
||||||
$widgets[] = [Widgets\DeploymentsList::class, ['site' => $this->site]];
|
$widgets[] = [Widgets\DeploymentsList::class, ['site' => $this->site]];
|
||||||
}
|
}
|
||||||
|
176
app/Web/Pages/Servers/Sites/Widgets/Commands.php
Normal file
176
app/Web/Pages/Servers/Sites/Widgets/Commands.php
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Web\Pages\Servers\Sites\Widgets;
|
||||||
|
|
||||||
|
use App\Actions\Site\CreateCommand;
|
||||||
|
use App\Actions\Site\EditCommand;
|
||||||
|
use App\Actions\Site\ExecuteCommand;
|
||||||
|
use App\Models\Command;
|
||||||
|
use App\Models\CommandExecution;
|
||||||
|
use App\Models\Site;
|
||||||
|
use Filament\Forms\Components\TextInput;
|
||||||
|
use Filament\Forms\Get;
|
||||||
|
use Filament\Notifications\Notification;
|
||||||
|
use Filament\Support\Enums\MaxWidth;
|
||||||
|
use Filament\Tables\Actions\Action;
|
||||||
|
use Filament\Tables\Actions\DeleteAction;
|
||||||
|
use Filament\Tables\Actions\EditAction;
|
||||||
|
use Filament\Tables\Columns\TextColumn;
|
||||||
|
use Filament\Tables\Table;
|
||||||
|
use Filament\Widgets\TableWidget as Widget;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\View\ComponentAttributeBag;
|
||||||
|
|
||||||
|
class Commands extends Widget
|
||||||
|
{
|
||||||
|
public Site $site;
|
||||||
|
|
||||||
|
protected $listeners = ['$refresh'];
|
||||||
|
|
||||||
|
protected function getTableQuery(): Builder
|
||||||
|
{
|
||||||
|
return Command::query()->where('site_id', $this->site->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function applySortingToTableQuery(Builder $query): Builder
|
||||||
|
{
|
||||||
|
return $query->latest('created_at');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getTableColumns(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
TextColumn::make('name'),
|
||||||
|
TextColumn::make('lastExecution.status')
|
||||||
|
->label('Status')
|
||||||
|
->badge()
|
||||||
|
->color(fn (Command $record) => CommandExecution::$statusColors[$record->lastExecution?->status])
|
||||||
|
->sortable(),
|
||||||
|
TextColumn::make('created_at')
|
||||||
|
->label('Last Execution At')
|
||||||
|
->formatStateUsing(fn (Command $record) => $record->lastExecution?->created_at_by_timezone)
|
||||||
|
->searchable()
|
||||||
|
->sortable(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getTableHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Action::make('new-command')
|
||||||
|
->label('Create a Command')
|
||||||
|
->modalDescription('The command will be executed inside the site\'s directory')
|
||||||
|
->icon('heroicon-o-plus')
|
||||||
|
->authorize(fn () => auth()->user()->can('create', [Command::class, $this->site, $this->site->server]))
|
||||||
|
->action(function (array $data) {
|
||||||
|
run_action($this, function () use ($data) {
|
||||||
|
app(CreateCommand::class)->create($this->site, $data);
|
||||||
|
|
||||||
|
$this->dispatch('$refresh');
|
||||||
|
|
||||||
|
Notification::make()
|
||||||
|
->success()
|
||||||
|
->title('Command created!')
|
||||||
|
->send();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
->form([
|
||||||
|
TextInput::make('name')
|
||||||
|
->rules(CreateCommand::rules()['name']),
|
||||||
|
TextInput::make('command')
|
||||||
|
->placeholder('php artisan my:command')
|
||||||
|
->rules(CreateCommand::rules()['command'])
|
||||||
|
->helperText('You can use variables like ${VARIABLE_NAME} in the command. The variables will be asked when executing the command'),
|
||||||
|
])
|
||||||
|
->modalSubmitActionLabel('Create')
|
||||||
|
->modalHeading('New Command')
|
||||||
|
->modalWidth('md'),
|
||||||
|
];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function table(Table $table): Table
|
||||||
|
{
|
||||||
|
return $table
|
||||||
|
->query($this->getTableQuery())
|
||||||
|
->headerActions($this->getTableHeaderActions())
|
||||||
|
->columns($this->getTableColumns())
|
||||||
|
->heading('Commands')
|
||||||
|
->defaultPaginationPageOption(5)
|
||||||
|
->searchable(false)
|
||||||
|
->actions([
|
||||||
|
Action::make('execute')
|
||||||
|
->hiddenLabel()
|
||||||
|
->tooltip('Execute')
|
||||||
|
->icon('heroicon-o-play')
|
||||||
|
->modalWidth(MaxWidth::Medium)
|
||||||
|
->modalSubmitActionLabel('Execute')
|
||||||
|
->form(function (Command $record) {
|
||||||
|
$form = [
|
||||||
|
TextInput::make('command')->default($record->command)->disabled(),
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($record->getVariables() as $variable) {
|
||||||
|
$form[] = TextInput::make('variables.'.$variable)
|
||||||
|
->label($variable)
|
||||||
|
->rules(fn (Get $get) => ExecuteCommand::rules($get())['variables.*']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $form;
|
||||||
|
})
|
||||||
|
->authorize(fn (Command $record) => auth()->user()->can('update', [$record->site, $record->site->server]))
|
||||||
|
->action(function (array $data, Command $record) {
|
||||||
|
/** @var \App\Models\User $user */
|
||||||
|
$user = auth()->user();
|
||||||
|
app(ExecuteCommand::class)->execute($record, $user, $data);
|
||||||
|
$this->dispatch('$refresh');
|
||||||
|
}),
|
||||||
|
Action::make('logs')
|
||||||
|
->hiddenLabel()
|
||||||
|
->tooltip('Last Log')
|
||||||
|
->icon('heroicon-o-eye')
|
||||||
|
->modalHeading('View Last Execution Log')
|
||||||
|
->modalContent(function (Command $record) {
|
||||||
|
return view('components.console-view', [
|
||||||
|
'slot' => $record->lastExecution?->serverLog?->getContent() ?? 'Not executed yet',
|
||||||
|
'attributes' => new ComponentAttributeBag,
|
||||||
|
]);
|
||||||
|
})
|
||||||
|
->modalSubmitAction(false)
|
||||||
|
->modalCancelActionLabel('Close'),
|
||||||
|
EditAction::make('edit')
|
||||||
|
->hiddenLabel()
|
||||||
|
->tooltip('Edit')
|
||||||
|
->modalHeading('Edit Command')
|
||||||
|
->mutateRecordDataUsing(function (array $data, Command $record) {
|
||||||
|
return [
|
||||||
|
'name' => $record->name,
|
||||||
|
'command' => $record->command,
|
||||||
|
];
|
||||||
|
})
|
||||||
|
->form([
|
||||||
|
TextInput::make('name')
|
||||||
|
->rules(EditCommand::rules()['name']),
|
||||||
|
TextInput::make('command')
|
||||||
|
->rules(EditCommand::rules()['command'])
|
||||||
|
->helperText('You can use variables like ${VARIABLE_NAME} in the command. The variables will be asked when executing the command'),
|
||||||
|
|
||||||
|
])
|
||||||
|
->authorize(fn (Command $record) => auth()->user()->can('update', [$record, $this->site, $this->site->server]))
|
||||||
|
->using(function (array $data, Command $record) {
|
||||||
|
app(EditCommand::class)->edit($record, $data);
|
||||||
|
$this->dispatch('$refresh');
|
||||||
|
})
|
||||||
|
->modalWidth(MaxWidth::Medium),
|
||||||
|
DeleteAction::make('delete')
|
||||||
|
->icon('heroicon-o-trash')
|
||||||
|
->hiddenLabel()
|
||||||
|
->tooltip('Delete')
|
||||||
|
->modalHeading('Delete Command')
|
||||||
|
->authorize(fn (Command $record) => auth()->user()->can('delete', [$record, $this->site, $this->site->server]))
|
||||||
|
->using(function (array $data, Command $record) {
|
||||||
|
$record->delete();
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -180,6 +180,16 @@ public function infolist(Infolist $infolist): Infolist
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
|
TextEntry::make('repository')
|
||||||
|
->label('Repository')
|
||||||
|
->visible(fn (Site $record) => $record->repository)
|
||||||
|
->formatStateUsing(fn (Site $record) => $record->repository)
|
||||||
|
->inlineLabel(),
|
||||||
|
TextEntry::make('branch')
|
||||||
|
->label('Branch')
|
||||||
|
->visible(fn (Site $record) => $record->branch)
|
||||||
|
->formatStateUsing(fn (Site $record) => $record->branch)
|
||||||
|
->inlineLabel(),
|
||||||
]),
|
]),
|
||||||
])
|
])
|
||||||
->record($this->site);
|
->record($this->site);
|
||||||
|
2
bootstrap/cli-cache/.gitignore
vendored
Normal file
2
bootstrap/cli-cache/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
69
bootstrap/cli.php
Normal file
69
bootstrap/cli.php
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Application;
|
||||||
|
|
||||||
|
putenv('APP_SERVICES_CACHE='.__DIR__.'/cli-cache/services.php');
|
||||||
|
putenv('APP_PACKAGES_CACHE='.__DIR__.'/cli-cache/packages.php');
|
||||||
|
putenv('APP_CONFIG_CACHE='.__DIR__.'/cli-cache/config.php');
|
||||||
|
putenv('APP_ROUTES_CACHE='.__DIR__.'/cli-cache/routes.php');
|
||||||
|
putenv('APP_EVENTS_CACHE='.__DIR__.'/cli-cache/events.php');
|
||||||
|
putenv('LOG_CHANNEL=syslog');
|
||||||
|
putenv('QUEUE_CONNECTION=sync');
|
||||||
|
putenv('CACHE_DRIVER=null');
|
||||||
|
putenv('DB_DATABASE=database.sqlite');
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Create The Application
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The first thing we will do is create a new Laravel application instance
|
||||||
|
| which serves as the "glue" for all the components of Laravel, and is
|
||||||
|
| the IoC container for the system binding all of the various parts.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
$app = new Application(
|
||||||
|
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
|
||||||
|
);
|
||||||
|
|
||||||
|
$app->useStoragePath(getenv('HOME') . '/.vito/storage');
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Bind Important Interfaces
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Next, we need to bind some important interfaces into the container so
|
||||||
|
| we will be able to resolve them when needed. The kernels serve the
|
||||||
|
| incoming requests to this application from both the web and CLI.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// $app->singleton(
|
||||||
|
// Illuminate\Contracts\Http\Kernel::class,
|
||||||
|
// App\Http\Kernel::class
|
||||||
|
// );
|
||||||
|
|
||||||
|
$app->singleton(
|
||||||
|
Illuminate\Contracts\Console\Kernel::class,
|
||||||
|
App\Cli\Kernel::class
|
||||||
|
);
|
||||||
|
|
||||||
|
$app->singleton(
|
||||||
|
Illuminate\Contracts\Debug\ExceptionHandler::class,
|
||||||
|
App\Exceptions\Handler::class
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Return The Application
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This script returns the application instance. The instance is given to
|
||||||
|
| the calling script so we can separate the building of the instances
|
||||||
|
| from the actual running of the application and sending responses.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
return $app;
|
20
box.json
Normal file
20
box.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"directories": [
|
||||||
|
"app",
|
||||||
|
"bootstrap",
|
||||||
|
"config",
|
||||||
|
"database",
|
||||||
|
"public",
|
||||||
|
"resources",
|
||||||
|
"routes",
|
||||||
|
"storage",
|
||||||
|
"vendor"
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
"artisan"
|
||||||
|
],
|
||||||
|
"main": "cli",
|
||||||
|
"output": "vito-cli.phar",
|
||||||
|
"chmod": "0755",
|
||||||
|
"stub": true
|
||||||
|
}
|
53
cli
Normal file
53
cli
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
define('LARAVEL_START', microtime(true));
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Register The Auto Loader
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Composer provides a convenient, automatically generated class loader
|
||||||
|
| for our application. We just need to utilize it! We'll require it
|
||||||
|
| into the script here so that we do not have to worry about the
|
||||||
|
| loading of any our classes "manually". Feels great to relax.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
$autoloader = require file_exists(__DIR__.'/vendor/autoload.php') ? __DIR__.'/vendor/autoload.php' : __DIR__.'/../../autoload.php';
|
||||||
|
|
||||||
|
$app = require_once __DIR__.'/bootstrap/cli.php';
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Run The Artisan Application
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When we run the console application, the current CLI command will be
|
||||||
|
| executed in this console and the response sent back to a terminal
|
||||||
|
| or another output device for the developers. Here goes nothing!
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
|
||||||
|
|
||||||
|
$status = $kernel->handle(
|
||||||
|
$input = new Symfony\Component\Console\Input\ArgvInput,
|
||||||
|
new Symfony\Component\Console\Output\ConsoleOutput
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Shutdown The Application
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Once Artisan has finished running, we will fire off the shutdown events
|
||||||
|
| so that any final work may be done by the application before we shut
|
||||||
|
| down the process. This is the last thing to happen to the request.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
$kernel->terminate($input, $status);
|
||||||
|
|
||||||
|
exit($status);
|
@ -15,6 +15,7 @@
|
|||||||
"filament/filament": "^3.2",
|
"filament/filament": "^3.2",
|
||||||
"laravel/fortify": "^1.17",
|
"laravel/fortify": "^1.17",
|
||||||
"laravel/framework": "^11.0",
|
"laravel/framework": "^11.0",
|
||||||
|
"laravel/prompts": "^0.3.5",
|
||||||
"laravel/sanctum": "^4.0",
|
"laravel/sanctum": "^4.0",
|
||||||
"laravel/tinker": "^2.8",
|
"laravel/tinker": "^2.8",
|
||||||
"mobiledetect/mobiledetectlib": "^4.8",
|
"mobiledetect/mobiledetectlib": "^4.8",
|
||||||
|
16
composer.lock
generated
16
composer.lock
generated
@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "be3e63b7efd71f649cbffb0d469ba7c1",
|
"content-hash": "e211da7974e07c3b74ad59ce245a9446",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "anourvalar/eloquent-serialize",
|
"name": "anourvalar/eloquent-serialize",
|
||||||
@ -2559,16 +2559,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "laravel/prompts",
|
"name": "laravel/prompts",
|
||||||
"version": "v0.3.3",
|
"version": "v0.3.5",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/laravel/prompts.git",
|
"url": "https://github.com/laravel/prompts.git",
|
||||||
"reference": "749395fcd5f8f7530fe1f00dfa84eb22c83d94ea"
|
"reference": "57b8f7efe40333cdb925700891c7d7465325d3b1"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/laravel/prompts/zipball/749395fcd5f8f7530fe1f00dfa84eb22c83d94ea",
|
"url": "https://api.github.com/repos/laravel/prompts/zipball/57b8f7efe40333cdb925700891c7d7465325d3b1",
|
||||||
"reference": "749395fcd5f8f7530fe1f00dfa84eb22c83d94ea",
|
"reference": "57b8f7efe40333cdb925700891c7d7465325d3b1",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@ -2582,7 +2582,7 @@
|
|||||||
"laravel/framework": ">=10.17.0 <10.25.0"
|
"laravel/framework": ">=10.17.0 <10.25.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"illuminate/collections": "^10.0|^11.0",
|
"illuminate/collections": "^10.0|^11.0|^12.0",
|
||||||
"mockery/mockery": "^1.5",
|
"mockery/mockery": "^1.5",
|
||||||
"pestphp/pest": "^2.3|^3.4",
|
"pestphp/pest": "^2.3|^3.4",
|
||||||
"phpstan/phpstan": "^1.11",
|
"phpstan/phpstan": "^1.11",
|
||||||
@ -2612,9 +2612,9 @@
|
|||||||
"description": "Add beautiful and user-friendly forms to your command-line applications.",
|
"description": "Add beautiful and user-friendly forms to your command-line applications.",
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/laravel/prompts/issues",
|
"issues": "https://github.com/laravel/prompts/issues",
|
||||||
"source": "https://github.com/laravel/prompts/tree/v0.3.3"
|
"source": "https://github.com/laravel/prompts/tree/v0.3.5"
|
||||||
},
|
},
|
||||||
"time": "2024-12-30T15:53:31+00:00"
|
"time": "2025-02-11T13:34:40+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "laravel/sanctum",
|
"name": "laravel/sanctum",
|
||||||
|
@ -211,7 +211,7 @@
|
|||||||
// 'ExampleClass' => App\Example\ExampleClass::class,
|
// 'ExampleClass' => App\Example\ExampleClass::class,
|
||||||
])->toArray(),
|
])->toArray(),
|
||||||
|
|
||||||
'version' => '2.1.0',
|
'version' => '2.3.0',
|
||||||
|
|
||||||
'demo' => env('APP_DEMO', false),
|
'demo' => env('APP_DEMO', false),
|
||||||
];
|
];
|
||||||
|
22
config/cli.php
Normal file
22
config/cli.php
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'providers' => [
|
||||||
|
Illuminate\Bus\BusServiceProvider::class,
|
||||||
|
Illuminate\Cache\CacheServiceProvider::class,
|
||||||
|
Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
|
||||||
|
Illuminate\Database\DatabaseServiceProvider::class,
|
||||||
|
Illuminate\Encryption\EncryptionServiceProvider::class,
|
||||||
|
Illuminate\Filesystem\FilesystemServiceProvider::class,
|
||||||
|
Illuminate\Foundation\Providers\FoundationServiceProvider::class,
|
||||||
|
Illuminate\Hashing\HashServiceProvider::class,
|
||||||
|
Illuminate\Mail\MailServiceProvider::class,
|
||||||
|
Illuminate\Notifications\NotificationServiceProvider::class,
|
||||||
|
Illuminate\Pagination\PaginationServiceProvider::class,
|
||||||
|
Illuminate\Pipeline\PipelineServiceProvider::class,
|
||||||
|
Illuminate\Queue\QueueServiceProvider::class,
|
||||||
|
Illuminate\Redis\RedisServiceProvider::class,
|
||||||
|
Illuminate\Translation\TranslationServiceProvider::class,
|
||||||
|
App\Providers\AppServiceProvider::class,
|
||||||
|
],
|
||||||
|
];
|
@ -485,14 +485,6 @@
|
|||||||
'post_max_size' => 'M',
|
'post_max_size' => 'M',
|
||||||
],
|
],
|
||||||
|
|
||||||
/*
|
|
||||||
* firewall
|
|
||||||
*/
|
|
||||||
'firewall_protocols_port' => [
|
|
||||||
'tcp' => '',
|
|
||||||
'udp' => '',
|
|
||||||
],
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Disable these IPs for servers
|
* Disable these IPs for servers
|
||||||
*/
|
*/
|
||||||
|
@ -62,7 +62,7 @@
|
|||||||
'root' => storage_path('app/key-pairs'),
|
'root' => storage_path('app/key-pairs'),
|
||||||
],
|
],
|
||||||
|
|
||||||
'backups' => [
|
'tmp' => [
|
||||||
'driver' => 'local',
|
'driver' => 'local',
|
||||||
'root' => sys_get_temp_dir(),
|
'root' => sys_get_temp_dir(),
|
||||||
],
|
],
|
||||||
|
26
database/factories/CommandExecutionFactory.php
Normal file
26
database/factories/CommandExecutionFactory.php
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Factories;
|
||||||
|
|
||||||
|
use App\Enums\CommandExecutionStatus;
|
||||||
|
use App\Models\Command;
|
||||||
|
use App\Models\CommandExecution;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
|
||||||
|
class CommandExecutionFactory extends Factory
|
||||||
|
{
|
||||||
|
protected $model = CommandExecution::class;
|
||||||
|
|
||||||
|
public function definition(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'command_id' => Command::factory(),
|
||||||
|
'status' => $this->faker->randomElement([
|
||||||
|
CommandExecutionStatus::COMPLETED,
|
||||||
|
CommandExecutionStatus::FAILED,
|
||||||
|
CommandExecutionStatus::EXECUTING,
|
||||||
|
]),
|
||||||
|
'variables' => [],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
24
database/factories/CommandFactory.php
Normal file
24
database/factories/CommandFactory.php
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Factories;
|
||||||
|
|
||||||
|
use App\Models\Command;
|
||||||
|
use App\Models\Site;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
|
||||||
|
class CommandFactory extends Factory
|
||||||
|
{
|
||||||
|
protected $model = Command::class;
|
||||||
|
|
||||||
|
public function definition(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name' => $this->faker->words(3, true),
|
||||||
|
'command' => 'php artisan '.$this->faker->word,
|
||||||
|
'created_at' => Carbon::now(),
|
||||||
|
'updated_at' => Carbon::now(),
|
||||||
|
'site_id' => Site::factory(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
32
database/factories/FileFactory.php
Normal file
32
database/factories/FileFactory.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Factories;
|
||||||
|
|
||||||
|
use App\Models\File;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
|
||||||
|
class FileFactory extends Factory
|
||||||
|
{
|
||||||
|
protected $model = File::class;
|
||||||
|
|
||||||
|
public function definition(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'user_id' => $this->faker->randomNumber(), //
|
||||||
|
'server_id' => $this->faker->randomNumber(),
|
||||||
|
'server_user' => $this->faker->word(),
|
||||||
|
'path' => $this->faker->word(),
|
||||||
|
'type' => 'file',
|
||||||
|
'name' => $this->faker->name(),
|
||||||
|
'size' => $this->faker->randomNumber(),
|
||||||
|
'links' => $this->faker->randomNumber(),
|
||||||
|
'owner' => $this->faker->word(),
|
||||||
|
'group' => $this->faker->word(),
|
||||||
|
'date' => $this->faker->word(),
|
||||||
|
'permissions' => $this->faker->word(),
|
||||||
|
'created_at' => Carbon::now(),
|
||||||
|
'updated_at' => Carbon::now(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -12,6 +12,7 @@ class FirewallRuleFactory extends Factory
|
|||||||
public function definition(): array
|
public function definition(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
'name' => $this->faker->word,
|
||||||
'type' => 'allow',
|
'type' => 'allow',
|
||||||
'protocol' => 'tcp',
|
'protocol' => 'tcp',
|
||||||
'port' => $this->faker->numberBetween(1, 65535),
|
'port' => $this->faker->numberBetween(1, 65535),
|
||||||
|
33
database/migrations/2025_02_02_124012_create_files_table.php
Normal file
33
database/migrations/2025_02_02_124012_create_files_table.php
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('files', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->unsignedBigInteger('user_id');
|
||||||
|
$table->unsignedBigInteger('server_id');
|
||||||
|
$table->string('server_user');
|
||||||
|
$table->string('path');
|
||||||
|
$table->string('type');
|
||||||
|
$table->string('name');
|
||||||
|
$table->unsignedBigInteger('size');
|
||||||
|
$table->unsignedBigInteger('links');
|
||||||
|
$table->string('owner');
|
||||||
|
$table->string('group');
|
||||||
|
$table->string('date');
|
||||||
|
$table->string('permissions');
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('files');
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('firewall_rules', function (Blueprint $table) {
|
||||||
|
$table->string('name')->default('Undefined')->after('id');
|
||||||
|
$table->ipAddress('source')->default(null)->nullable()->change();
|
||||||
|
});
|
||||||
|
|
||||||
|
DB::statement("UPDATE firewall_rules SET name = UPPER(protocol) WHERE protocol IN ('ssh', 'http', 'https')");
|
||||||
|
DB::statement("UPDATE firewall_rules SET protocol = 'tcp' WHERE protocol IN ('ssh', 'http', 'https')");
|
||||||
|
DB::statement("UPDATE firewall_rules SET source = null WHERE source = '0.0.0.0'");
|
||||||
|
DB::statement("UPDATE firewall_rules SET mask = null WHERE mask = '0'");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
DB::statement("UPDATE firewall_rules SET protocol = LOWER(name) WHERE protocol = 'tcp' AND LOWER(name) IN ('ssh', 'http', 'https')");
|
||||||
|
DB::statement("UPDATE firewall_rules SET source = '0.0.0.0' WHERE source is null");
|
||||||
|
DB::statement("UPDATE firewall_rules SET mask = '0' WHERE mask is null");
|
||||||
|
|
||||||
|
Schema::table('firewall_rules', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('name');
|
||||||
|
$table->ipAddress('source')->default('0.0.0.0')->change();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('commands', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->unsignedInteger('site_id');
|
||||||
|
$table->string('name');
|
||||||
|
$table->text('command');
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('commands');
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('command_executions', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->unsignedBigInteger('command_id');
|
||||||
|
$table->unsignedInteger('server_id');
|
||||||
|
$table->unsignedBigInteger('user_id');
|
||||||
|
$table->unsignedBigInteger('server_log_id')->nullable();
|
||||||
|
$table->json('variables')->nullable();
|
||||||
|
$table->string('status');
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('command_executions');
|
||||||
|
}
|
||||||
|
};
|
@ -1,26 +1,22 @@
|
|||||||
FROM ubuntu:22.04
|
FROM ubuntu:24.04
|
||||||
|
|
||||||
WORKDIR /var/www/html
|
WORKDIR /var/www/html
|
||||||
|
|
||||||
ENV DEBIAN_FRONTEND noninteractive
|
ENV DEBIAN_FRONTEND noninteractive
|
||||||
|
|
||||||
# upgrade
|
# upgrade
|
||||||
RUN apt-get clean && apt-get update && apt-get update && apt-get upgrade -y && apt-get autoremove -y
|
RUN apt-get update && apt-get upgrade -y && apt-get autoremove -y
|
||||||
|
|
||||||
# requirements
|
# requirements
|
||||||
RUN apt-get install -y software-properties-common curl zip unzip gcc
|
RUN apt-get install -y software-properties-common curl zip unzip gcc nginx \
|
||||||
|
cron gnupg gosu curl ca-certificates zip unzip supervisor libcap2-bin libpng-dev \
|
||||||
# nginx
|
dnsutils librsvg2-bin fswatch wget openssh-client \
|
||||||
RUN apt-get install -y nginx
|
|
||||||
|
|
||||||
# php
|
|
||||||
RUN apt-get update \
|
|
||||||
&& apt-get install -y cron gnupg gosu curl ca-certificates zip unzip supervisor libcap2-bin libpng-dev \
|
|
||||||
python2 dnsutils librsvg2-bin fswatch wget openssh-client \
|
|
||||||
&& add-apt-repository ppa:ondrej/php -y \
|
&& add-apt-repository ppa:ondrej/php -y \
|
||||||
&& apt-get update \
|
&& apt-get update \
|
||||||
&& apt-get install -y php8.2 php8.2-fpm php8.2-mbstring php8.2-mcrypt php8.2-gd php8.2-xml \
|
&& apt-get install -y php8.2 php8.2-fpm php8.2-mbstring php8.2-mcrypt php8.2-gd php8.2-xml \
|
||||||
php8.2-curl php8.2-gettext php8.2-zip php8.2-bcmath php8.2-soap php8.2-redis php8.2-sqlite3 php8.2-intl
|
php8.2-curl php8.2-gettext php8.2-zip php8.2-bcmath php8.2-soap php8.2-redis php8.2-sqlite3 php8.2-intl
|
||||||
|
|
||||||
|
# php
|
||||||
COPY docker/php.ini /etc/php/8.2/cli/conf.d/99-vito.ini
|
COPY docker/php.ini /etc/php/8.2/cli/conf.d/99-vito.ini
|
||||||
|
|
||||||
# composer
|
# composer
|
||||||
|
54
docker/publish.sh
Normal file
54
docker/publish.sh
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
BRANCH=""
|
||||||
|
TAGS=()
|
||||||
|
|
||||||
|
# Parse arguments
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--branch)
|
||||||
|
BRANCH="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--tags)
|
||||||
|
IFS=',' read -r -a TAGS <<< "$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown option: $1"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Validate inputs
|
||||||
|
if [ -z "$BRANCH" ]; then
|
||||||
|
echo "No branch provided. Use --branch <git_branch>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ${#TAGS[@]} -eq 0 ]; then
|
||||||
|
echo "No tags provided. Use --tags tag1,tag2,tag3"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clone the specified branch of the repo
|
||||||
|
rm -rf /tmp/vito
|
||||||
|
git clone --branch "$BRANCH" --depth 1 git@github.com:vitodeploy/vito.git /tmp/vito
|
||||||
|
cd /tmp/vito || exit
|
||||||
|
|
||||||
|
# Prepare tag arguments for docker buildx
|
||||||
|
TAG_ARGS=()
|
||||||
|
for TAG in "${TAGS[@]}"; do
|
||||||
|
# Trim whitespace to avoid invalid tag formatting
|
||||||
|
TAG_CLEANED=$(echo -n "$TAG" | xargs)
|
||||||
|
TAG_ARGS+=("-t" "vitodeploy/vito:$TAG_CLEANED")
|
||||||
|
done
|
||||||
|
|
||||||
|
# Build and push the image
|
||||||
|
docker buildx build . \
|
||||||
|
-f docker/Dockerfile \
|
||||||
|
"${TAG_ARGS[@]}" \
|
||||||
|
--platform linux/amd64,linux/arm64 \
|
||||||
|
--no-cache \
|
||||||
|
--push
|
646
package-lock.json
generated
646
package-lock.json
generated
@ -11,7 +11,7 @@
|
|||||||
"apexcharts": "^3.44.2",
|
"apexcharts": "^3.44.2",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"brace": "^0.11.1",
|
"brace": "^0.11.1",
|
||||||
"laravel-vite-plugin": "^0.7.2",
|
"laravel-vite-plugin": "^1.2.0",
|
||||||
"postcss": "^8.4.45",
|
"postcss": "^8.4.45",
|
||||||
"postcss-nesting": "^13.0.0",
|
"postcss-nesting": "^13.0.0",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
@ -19,7 +19,7 @@
|
|||||||
"prettier-plugin-sh": "^0.14.0",
|
"prettier-plugin-sh": "^0.14.0",
|
||||||
"prettier-plugin-tailwindcss": "^0.5.11",
|
"prettier-plugin-tailwindcss": "^0.5.11",
|
||||||
"tailwindcss": "^3.4.10",
|
"tailwindcss": "^3.4.10",
|
||||||
"vite": "^4.5.5"
|
"vite": "^6.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@alloc/quick-lru": {
|
"node_modules/@alloc/quick-lru": {
|
||||||
@ -35,10 +35,27 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
|
"version": "0.25.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz",
|
||||||
|
"integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"aix"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@esbuild/android-arm": {
|
"node_modules/@esbuild/android-arm": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz",
|
||||||
"integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
|
"integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@ -49,13 +66,13 @@
|
|||||||
"android"
|
"android"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-arm64": {
|
"node_modules/@esbuild/android-arm64": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz",
|
||||||
"integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
|
"integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -66,13 +83,13 @@
|
|||||||
"android"
|
"android"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-x64": {
|
"node_modules/@esbuild/android-x64": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz",
|
||||||
"integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
|
"integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -83,13 +100,13 @@
|
|||||||
"android"
|
"android"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/darwin-arm64": {
|
"node_modules/@esbuild/darwin-arm64": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz",
|
||||||
"integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
|
"integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -100,13 +117,13 @@
|
|||||||
"darwin"
|
"darwin"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/darwin-x64": {
|
"node_modules/@esbuild/darwin-x64": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz",
|
||||||
"integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
|
"integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -117,13 +134,13 @@
|
|||||||
"darwin"
|
"darwin"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/freebsd-arm64": {
|
"node_modules/@esbuild/freebsd-arm64": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz",
|
||||||
"integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
|
"integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -134,13 +151,13 @@
|
|||||||
"freebsd"
|
"freebsd"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/freebsd-x64": {
|
"node_modules/@esbuild/freebsd-x64": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz",
|
||||||
"integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
|
"integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -151,13 +168,13 @@
|
|||||||
"freebsd"
|
"freebsd"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-arm": {
|
"node_modules/@esbuild/linux-arm": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz",
|
||||||
"integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
|
"integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@ -168,13 +185,13 @@
|
|||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-arm64": {
|
"node_modules/@esbuild/linux-arm64": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz",
|
||||||
"integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
|
"integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -185,13 +202,13 @@
|
|||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-ia32": {
|
"node_modules/@esbuild/linux-ia32": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz",
|
||||||
"integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
|
"integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
@ -202,13 +219,13 @@
|
|||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-loong64": {
|
"node_modules/@esbuild/linux-loong64": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz",
|
||||||
"integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
|
"integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
@ -219,13 +236,13 @@
|
|||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-mips64el": {
|
"node_modules/@esbuild/linux-mips64el": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz",
|
||||||
"integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
|
"integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"mips64el"
|
"mips64el"
|
||||||
],
|
],
|
||||||
@ -236,13 +253,13 @@
|
|||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-ppc64": {
|
"node_modules/@esbuild/linux-ppc64": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz",
|
||||||
"integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
|
"integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
@ -253,13 +270,13 @@
|
|||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-riscv64": {
|
"node_modules/@esbuild/linux-riscv64": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz",
|
||||||
"integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
|
"integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
@ -270,13 +287,13 @@
|
|||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-s390x": {
|
"node_modules/@esbuild/linux-s390x": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz",
|
||||||
"integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
|
"integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
@ -287,13 +304,13 @@
|
|||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-x64": {
|
"node_modules/@esbuild/linux-x64": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz",
|
||||||
"integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
|
"integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -304,13 +321,30 @@
|
|||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/netbsd-arm64": {
|
||||||
|
"version": "0.25.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz",
|
||||||
|
"integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"netbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/netbsd-x64": {
|
"node_modules/@esbuild/netbsd-x64": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz",
|
||||||
"integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
|
"integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -321,13 +355,30 @@
|
|||||||
"netbsd"
|
"netbsd"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/openbsd-arm64": {
|
||||||
|
"version": "0.25.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz",
|
||||||
|
"integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"openbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/openbsd-x64": {
|
"node_modules/@esbuild/openbsd-x64": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz",
|
||||||
"integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
|
"integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -338,13 +389,13 @@
|
|||||||
"openbsd"
|
"openbsd"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/sunos-x64": {
|
"node_modules/@esbuild/sunos-x64": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz",
|
||||||
"integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
|
"integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -355,13 +406,13 @@
|
|||||||
"sunos"
|
"sunos"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-arm64": {
|
"node_modules/@esbuild/win32-arm64": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz",
|
||||||
"integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
|
"integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -372,13 +423,13 @@
|
|||||||
"win32"
|
"win32"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-ia32": {
|
"node_modules/@esbuild/win32-ia32": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz",
|
||||||
"integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
|
"integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
@ -389,13 +440,13 @@
|
|||||||
"win32"
|
"win32"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-x64": {
|
"node_modules/@esbuild/win32-x64": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz",
|
||||||
"integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
|
"integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -406,7 +457,7 @@
|
|||||||
"win32"
|
"win32"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@isaacs/cliui": {
|
"node_modules/@isaacs/cliui": {
|
||||||
@ -529,6 +580,272 @@
|
|||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
|
"version": "4.34.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.8.tgz",
|
||||||
|
"integrity": "sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-android-arm64": {
|
||||||
|
"version": "4.34.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.8.tgz",
|
||||||
|
"integrity": "sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||||
|
"version": "4.34.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.8.tgz",
|
||||||
|
"integrity": "sha512-02rVdZ5tgdUNRxIUrFdcMBZQoaPMrxtwSb+/hOfBdqkatYHR3lZ2A2EGyHq2sGOd0Owk80oV3snlDASC24He3Q==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-darwin-x64": {
|
||||||
|
"version": "4.34.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.8.tgz",
|
||||||
|
"integrity": "sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||||
|
"version": "4.34.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.8.tgz",
|
||||||
|
"integrity": "sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||||
|
"version": "4.34.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.8.tgz",
|
||||||
|
"integrity": "sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||||
|
"version": "4.34.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.8.tgz",
|
||||||
|
"integrity": "sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||||
|
"version": "4.34.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.8.tgz",
|
||||||
|
"integrity": "sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||||
|
"version": "4.34.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.8.tgz",
|
||||||
|
"integrity": "sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||||
|
"version": "4.34.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.8.tgz",
|
||||||
|
"integrity": "sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
|
||||||
|
"version": "4.34.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.8.tgz",
|
||||||
|
"integrity": "sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ==",
|
||||||
|
"cpu": [
|
||||||
|
"loong64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
||||||
|
"version": "4.34.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.8.tgz",
|
||||||
|
"integrity": "sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||||
|
"version": "4.34.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.8.tgz",
|
||||||
|
"integrity": "sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw==",
|
||||||
|
"cpu": [
|
||||||
|
"riscv64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||||
|
"version": "4.34.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.8.tgz",
|
||||||
|
"integrity": "sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA==",
|
||||||
|
"cpu": [
|
||||||
|
"s390x"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||||
|
"version": "4.34.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.8.tgz",
|
||||||
|
"integrity": "sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||||
|
"version": "4.34.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.8.tgz",
|
||||||
|
"integrity": "sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||||
|
"version": "4.34.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.8.tgz",
|
||||||
|
"integrity": "sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||||
|
"version": "4.34.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.8.tgz",
|
||||||
|
"integrity": "sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||||
|
"version": "4.34.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.8.tgz",
|
||||||
|
"integrity": "sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
]
|
||||||
|
},
|
||||||
"node_modules/@tailwindcss/forms": {
|
"node_modules/@tailwindcss/forms": {
|
||||||
"version": "0.5.10",
|
"version": "0.5.10",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.10.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.10.tgz",
|
||||||
@ -558,6 +875,13 @@
|
|||||||
"tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1"
|
"tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/estree": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@vue/reactivity": {
|
"node_modules/@vue/reactivity": {
|
||||||
"version": "3.1.5",
|
"version": "3.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.1.5.tgz",
|
||||||
@ -946,9 +1270,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/esbuild": {
|
"node_modules/esbuild": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz",
|
||||||
"integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
|
"integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -956,31 +1280,34 @@
|
|||||||
"esbuild": "bin/esbuild"
|
"esbuild": "bin/esbuild"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@esbuild/android-arm": "0.18.20",
|
"@esbuild/aix-ppc64": "0.25.0",
|
||||||
"@esbuild/android-arm64": "0.18.20",
|
"@esbuild/android-arm": "0.25.0",
|
||||||
"@esbuild/android-x64": "0.18.20",
|
"@esbuild/android-arm64": "0.25.0",
|
||||||
"@esbuild/darwin-arm64": "0.18.20",
|
"@esbuild/android-x64": "0.25.0",
|
||||||
"@esbuild/darwin-x64": "0.18.20",
|
"@esbuild/darwin-arm64": "0.25.0",
|
||||||
"@esbuild/freebsd-arm64": "0.18.20",
|
"@esbuild/darwin-x64": "0.25.0",
|
||||||
"@esbuild/freebsd-x64": "0.18.20",
|
"@esbuild/freebsd-arm64": "0.25.0",
|
||||||
"@esbuild/linux-arm": "0.18.20",
|
"@esbuild/freebsd-x64": "0.25.0",
|
||||||
"@esbuild/linux-arm64": "0.18.20",
|
"@esbuild/linux-arm": "0.25.0",
|
||||||
"@esbuild/linux-ia32": "0.18.20",
|
"@esbuild/linux-arm64": "0.25.0",
|
||||||
"@esbuild/linux-loong64": "0.18.20",
|
"@esbuild/linux-ia32": "0.25.0",
|
||||||
"@esbuild/linux-mips64el": "0.18.20",
|
"@esbuild/linux-loong64": "0.25.0",
|
||||||
"@esbuild/linux-ppc64": "0.18.20",
|
"@esbuild/linux-mips64el": "0.25.0",
|
||||||
"@esbuild/linux-riscv64": "0.18.20",
|
"@esbuild/linux-ppc64": "0.25.0",
|
||||||
"@esbuild/linux-s390x": "0.18.20",
|
"@esbuild/linux-riscv64": "0.25.0",
|
||||||
"@esbuild/linux-x64": "0.18.20",
|
"@esbuild/linux-s390x": "0.25.0",
|
||||||
"@esbuild/netbsd-x64": "0.18.20",
|
"@esbuild/linux-x64": "0.25.0",
|
||||||
"@esbuild/openbsd-x64": "0.18.20",
|
"@esbuild/netbsd-arm64": "0.25.0",
|
||||||
"@esbuild/sunos-x64": "0.18.20",
|
"@esbuild/netbsd-x64": "0.25.0",
|
||||||
"@esbuild/win32-arm64": "0.18.20",
|
"@esbuild/openbsd-arm64": "0.25.0",
|
||||||
"@esbuild/win32-ia32": "0.18.20",
|
"@esbuild/openbsd-x64": "0.25.0",
|
||||||
"@esbuild/win32-x64": "0.18.20"
|
"@esbuild/sunos-x64": "0.25.0",
|
||||||
|
"@esbuild/win32-arm64": "0.25.0",
|
||||||
|
"@esbuild/win32-ia32": "0.25.0",
|
||||||
|
"@esbuild/win32-x64": "0.25.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/escalade": {
|
"node_modules/escalade": {
|
||||||
@ -1255,20 +1582,23 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/laravel-vite-plugin": {
|
"node_modules/laravel-vite-plugin": {
|
||||||
"version": "0.7.8",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-0.7.8.tgz",
|
"resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-1.2.0.tgz",
|
||||||
"integrity": "sha512-HWYqpQYHR3kEQ1LsHX7gHJoNNf0bz5z5mDaHBLzS+PGLCTmYqlU5/SZyeEgObV7z7bC/cnStYcY9H1DI1D5Udg==",
|
"integrity": "sha512-R0pJ+IcTVeqEMoKz/B2Ij57QVq3sFTABiFmb06gAwFdivbOgsUtuhX6N2MGLEArajrS3U5JbberzwOe7uXHMHQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"picocolors": "^1.0.0",
|
"picocolors": "^1.0.0",
|
||||||
"vite-plugin-full-reload": "^1.0.5"
|
"vite-plugin-full-reload": "^1.1.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"clean-orphaned-assets": "bin/clean.js"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14"
|
"node": "^18.0.0 || ^20.0.0 || >=22.0.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"vite": "^3.0.0 || ^4.0.0"
|
"vite": "^5.0.0 || ^6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lilconfig": {
|
"node_modules/lilconfig": {
|
||||||
@ -1546,9 +1876,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.5.1",
|
"version": "8.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
|
||||||
"integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==",
|
"integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -1998,19 +2328,41 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rollup": {
|
"node_modules/rollup": {
|
||||||
"version": "3.29.5",
|
"version": "4.34.8",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.8.tgz",
|
||||||
"integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==",
|
"integrity": "sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/estree": "1.0.6"
|
||||||
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"rollup": "dist/bin/rollup"
|
"rollup": "dist/bin/rollup"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.18.0",
|
"node": ">=18.0.0",
|
||||||
"npm": ">=8.0.0"
|
"npm": ">=8.0.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
|
"@rollup/rollup-android-arm-eabi": "4.34.8",
|
||||||
|
"@rollup/rollup-android-arm64": "4.34.8",
|
||||||
|
"@rollup/rollup-darwin-arm64": "4.34.8",
|
||||||
|
"@rollup/rollup-darwin-x64": "4.34.8",
|
||||||
|
"@rollup/rollup-freebsd-arm64": "4.34.8",
|
||||||
|
"@rollup/rollup-freebsd-x64": "4.34.8",
|
||||||
|
"@rollup/rollup-linux-arm-gnueabihf": "4.34.8",
|
||||||
|
"@rollup/rollup-linux-arm-musleabihf": "4.34.8",
|
||||||
|
"@rollup/rollup-linux-arm64-gnu": "4.34.8",
|
||||||
|
"@rollup/rollup-linux-arm64-musl": "4.34.8",
|
||||||
|
"@rollup/rollup-linux-loongarch64-gnu": "4.34.8",
|
||||||
|
"@rollup/rollup-linux-powerpc64le-gnu": "4.34.8",
|
||||||
|
"@rollup/rollup-linux-riscv64-gnu": "4.34.8",
|
||||||
|
"@rollup/rollup-linux-s390x-gnu": "4.34.8",
|
||||||
|
"@rollup/rollup-linux-x64-gnu": "4.34.8",
|
||||||
|
"@rollup/rollup-linux-x64-musl": "4.34.8",
|
||||||
|
"@rollup/rollup-win32-arm64-msvc": "4.34.8",
|
||||||
|
"@rollup/rollup-win32-ia32-msvc": "4.34.8",
|
||||||
|
"@rollup/rollup-win32-x64-msvc": "4.34.8",
|
||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2480,41 +2832,48 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "4.5.9",
|
"version": "6.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.9.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-6.2.0.tgz",
|
||||||
"integrity": "sha512-qK9W4xjgD3gXbC0NmdNFFnVFLMWSNiR3swj957yutwzzN16xF/E7nmtAyp1rT9hviDroQANjE4HK3H4WqWdFtw==",
|
"integrity": "sha512-7dPxoo+WsT/64rDcwoOjk76XHj+TqNTIvHKcuMQ1k4/SeHDaQt5GFAeLYzrimZrMpn/O6DtdI03WUjdxuPM0oQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.18.10",
|
"esbuild": "^0.25.0",
|
||||||
"postcss": "^8.4.27",
|
"postcss": "^8.5.3",
|
||||||
"rollup": "^3.27.1"
|
"rollup": "^4.30.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"vite": "bin/vite.js"
|
"vite": "bin/vite.js"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^14.18.0 || >=16.0.0"
|
"node": "^18.0.0 || ^20.0.0 || >=22.0.0"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/vitejs/vite?sponsor=1"
|
"url": "https://github.com/vitejs/vite?sponsor=1"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/node": ">= 14",
|
"@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
|
||||||
|
"jiti": ">=1.21.0",
|
||||||
"less": "*",
|
"less": "*",
|
||||||
"lightningcss": "^1.21.0",
|
"lightningcss": "^1.21.0",
|
||||||
"sass": "*",
|
"sass": "*",
|
||||||
|
"sass-embedded": "*",
|
||||||
"stylus": "*",
|
"stylus": "*",
|
||||||
"sugarss": "*",
|
"sugarss": "*",
|
||||||
"terser": "^5.4.0"
|
"terser": "^5.16.0",
|
||||||
|
"tsx": "^4.8.1",
|
||||||
|
"yaml": "^2.4.2"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
"@types/node": {
|
"@types/node": {
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
"jiti": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"less": {
|
"less": {
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
@ -2524,6 +2883,9 @@
|
|||||||
"sass": {
|
"sass": {
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
"sass-embedded": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"stylus": {
|
"stylus": {
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
@ -2532,6 +2894,12 @@
|
|||||||
},
|
},
|
||||||
"terser": {
|
"terser": {
|
||||||
"optional": true
|
"optional": true
|
||||||
|
},
|
||||||
|
"tsx": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"yaml": {
|
||||||
|
"optional": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
"apexcharts": "^3.44.2",
|
"apexcharts": "^3.44.2",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"brace": "^0.11.1",
|
"brace": "^0.11.1",
|
||||||
"laravel-vite-plugin": "^0.7.2",
|
"laravel-vite-plugin": "^1.2.0",
|
||||||
"postcss": "^8.4.45",
|
"postcss": "^8.4.45",
|
||||||
"postcss-nesting": "^13.0.0",
|
"postcss-nesting": "^13.0.0",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
@ -21,6 +21,6 @@
|
|||||||
"prettier-plugin-sh": "^0.14.0",
|
"prettier-plugin-sh": "^0.14.0",
|
||||||
"prettier-plugin-tailwindcss": "^0.5.11",
|
"prettier-plugin-tailwindcss": "^0.5.11",
|
||||||
"tailwindcss": "^3.4.10",
|
"tailwindcss": "^3.4.10",
|
||||||
"vite": "^4.5.5"
|
"vite": "^6.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
if ! echo '{{ $cron }}' | sudo -u {{ $user }} crontab -; then
|
if ! echo '{!! $cron !!}' | sudo -u {{ $user }} crontab -; then
|
||||||
echo 'VITO_SSH_ERROR' && exit 1
|
echo 'VITO_SSH_ERROR' && exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
7
resources/views/ssh/git/fetch-origin.blade.php
Normal file
7
resources/views/ssh/git/fetch-origin.blade.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
if ! cd {{ $path }}; then
|
||||||
|
echo 'VITO_SSH_ERROR' && exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! git fetch origin; then
|
||||||
|
echo 'VITO_SSH_ERROR' && exit 1
|
||||||
|
fi
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user