mirror of
https://github.com/vitodeploy/vito.git
synced 2025-07-01 14:06:15 +00:00
Compare commits
17 Commits
Author | SHA1 | Date | |
---|---|---|---|
7cda14cb76 | |||
3bf3f7eebc | |||
e17fdbb1a0 | |||
e99146209e | |||
1223ea1499 | |||
2356e44f5b | |||
8c7c3d2192 | |||
e2b9d18a71 | |||
75e554ad74 | |||
4d59529767 | |||
ea31d0978f | |||
ee4e9e5452 | |||
48c12e26b2 | |||
72f68b5917 | |||
a889cf11a2 | |||
75a4fde8de | |||
fd67097884 |
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";
|
|
||||||
|
|
||||||
if (! Storage::disk('backups')->exists($localFilename)) {
|
|
||||||
$file->backup->server->ssh()->download(
|
$file->backup->server->ssh()->download(
|
||||||
Storage::disk('backups')->path($localFilename),
|
Storage::disk('tmp')->path(basename($file->path())),
|
||||||
$file->path()
|
$file->path()
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
return Storage::disk('backups')->download($localFilename, $file->name.'.zip');
|
return Storage::disk('tmp')->download(basename($file->path()));
|
||||||
}
|
}
|
||||||
|
|
||||||
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',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -197,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,
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
@ -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']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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';
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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,
|
||||||
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
@ -89,6 +90,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')
|
||||||
|
@ -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,
|
||||||
|
]),
|
||||||
|
'edit-file'
|
||||||
);
|
);
|
||||||
} catch (Throwable) {
|
|
||||||
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
|
||||||
{
|
{
|
||||||
|
if ($ssl->certificate_path) {
|
||||||
$this->service->server->ssh()->exec(
|
$this->service->server->ssh()->exec(
|
||||||
'sudo rm -rf '.dirname($ssl->certificate_path).'*',
|
'sudo rm -rf '.dirname($ssl->certificate_path),
|
||||||
'remove-ssl',
|
'remove-ssl',
|
||||||
$ssl->site_id
|
$ssl->site_id
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
$this->updateVHost($ssl->site);
|
$this->updateVHost($ssl->site);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
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')
|
||||||
|
@ -211,7 +211,7 @@
|
|||||||
// 'ExampleClass' => App\Example\ExampleClass::class,
|
// 'ExampleClass' => App\Example\ExampleClass::class,
|
||||||
])->toArray(),
|
])->toArray(),
|
||||||
|
|
||||||
'version' => '2.2.1',
|
'version' => '2.3.0',
|
||||||
|
|
||||||
'demo' => env('APP_DEMO', false),
|
'demo' => env('APP_DEMO', false),
|
||||||
];
|
];
|
||||||
|
@ -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(),
|
||||||
],
|
],
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
@ -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
|
||||||
|
21
docker/publish.sh
Normal file
21
docker/publish.sh
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
TAG=$1
|
||||||
|
|
||||||
|
if [ -z "$TAG" ]; then
|
||||||
|
echo "No tag provided"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -rf /tmp/vito
|
||||||
|
|
||||||
|
git clone git@github.com:vitodeploy/vito.git /tmp/vito
|
||||||
|
|
||||||
|
cd /tmp/vito || exit
|
||||||
|
|
||||||
|
docker buildx build . \
|
||||||
|
-f docker/Dockerfile \
|
||||||
|
-t vitodeploy/vito:"$TAG" \
|
||||||
|
--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 +1 @@
|
|||||||
rm -f {{ $path }}
|
rm -rf {{ $path }}
|
||||||
|
9
resources/views/ssh/os/edit-file.blade.php
Normal file
9
resources/views/ssh/os/edit-file.blade.php
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
@if($sudo) sudo @endif tee {!! $path !!} << 'VITO_SSH_EOF' > /dev/null
|
||||||
|
{!! $content !!}
|
||||||
|
VITO_SSH_EOF
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "Successfully wrote to {{ $path }}"
|
||||||
|
else
|
||||||
|
echo 'VITO_SSH_ERROR' && exit 1
|
||||||
|
fi
|
15
resources/views/ssh/os/extract.blade.php
Normal file
15
resources/views/ssh/os/extract.blade.php
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
@php
|
||||||
|
$extension = pathinfo($path, PATHINFO_EXTENSION);
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
@if($extension === 'zip')
|
||||||
|
unzip -o {{ $path }} -d {{ $destination }}
|
||||||
|
@elseif($extension === 'tar'))
|
||||||
|
tar -xf {{ $path }} -C {{ $destination }}
|
||||||
|
@elseif(in_array($extension, ['gz', 'tar.gz']))
|
||||||
|
tar -xzf {{ $path }} -C {{ $destination }}
|
||||||
|
@elseif(in_array($extension, ['bz2', 'tar.bz2']))
|
||||||
|
tar -xjf {{ $path }} -C {{ $destination }}
|
||||||
|
@else
|
||||||
|
echo "Unsupported archive format: {{ $extension }}"
|
||||||
|
@endif
|
@ -2,4 +2,4 @@
|
|||||||
echo 'VITO_SSH_ERROR' && exit 1
|
echo 'VITO_SSH_ERROR' && exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
{{ $script }}
|
{!! $script !!}
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
if ! sudo DEBIAN_FRONTEND=noninteractive mysqldump -u root {{ $database }} > {{ $file }}.sql; then
|
||||||
|
echo 'VITO_SSH_ERROR' && exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! DEBIAN_FRONTEND=noninteractive zip {{ $file }}.zip {{ $file }}.sql; then
|
||||||
|
echo 'VITO_SSH_ERROR' && exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! rm {{ $file }}.sql; then
|
||||||
|
echo 'VITO_SSH_ERROR' && exit 1
|
||||||
|
fi
|
@ -0,0 +1,9 @@
|
|||||||
|
if ! sudo mysql -e "CREATE USER IF NOT EXISTS '{{ $username }}'@'{{ $host }}' IDENTIFIED BY '{{ $password }}'"; then
|
||||||
|
echo 'VITO_SSH_ERROR' && exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! sudo mysql -e "FLUSH PRIVILEGES"; then
|
||||||
|
echo 'VITO_SSH_ERROR' && exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Command executed"
|
@ -0,0 +1,5 @@
|
|||||||
|
if ! sudo mysql -e "CREATE DATABASE IF NOT EXISTS {{ $name }} CHARACTER SET utf8 COLLATE utf8_general_ci"; then
|
||||||
|
echo 'VITO_SSH_ERROR' && exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Command executed"
|
@ -0,0 +1,9 @@
|
|||||||
|
if ! sudo mysql -e "DROP USER IF EXISTS '{{ $username }}'@'{{ $host }}'"; then
|
||||||
|
echo 'VITO_SSH_ERROR' && exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! sudo mysql -e "FLUSH PRIVILEGES"; then
|
||||||
|
echo 'VITO_SSH_ERROR' && exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Command executed"
|
@ -0,0 +1,5 @@
|
|||||||
|
if ! sudo mysql -e "DROP DATABASE IF EXISTS {{ $name }}"; then
|
||||||
|
echo 'VITO_SSH_ERROR' && exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Command executed"
|
@ -0,0 +1,9 @@
|
|||||||
|
if ! sudo mysql -e "GRANT ALL PRIVILEGES ON {{ $database }}.* TO '{{ $username }}'@'{{ $host }}'"; then
|
||||||
|
echo 'VITO_SSH_ERROR' && exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! sudo mysql -e "FLUSH PRIVILEGES"; then
|
||||||
|
echo 'VITO_SSH_ERROR' && exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Linking to {{ $database }} finished"
|
@ -0,0 +1,11 @@
|
|||||||
|
if ! DEBIAN_FRONTEND=noninteractive unzip {{ $file }}.zip; then
|
||||||
|
echo 'VITO_SSH_ERROR' && exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! sudo DEBIAN_FRONTEND=noninteractive mysql -u root {{ $database }} < {{ $file }}.sql; then
|
||||||
|
echo 'VITO_SSH_ERROR' && exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! rm {{ $file }}.sql {{ $file }}.zip; then
|
||||||
|
echo 'VITO_SSH_ERROR' && exit 1
|
||||||
|
fi
|
@ -0,0 +1,5 @@
|
|||||||
|
if ! sudo mysql -e "REVOKE ALL PRIVILEGES, GRANT OPTION FROM '{{ $username }}'@'{{ $host }}'"; then
|
||||||
|
echo 'VITO_SSH_ERROR' && exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Command executed"
|
@ -1,11 +0,0 @@
|
|||||||
if ! sudo ufw {{ $type }} from {{ $source }}{{ $mask }} to any proto {{ $protocol }} port {{ $port }}; then
|
|
||||||
echo 'VITO_SSH_ERROR' && exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! sudo ufw reload; then
|
|
||||||
echo 'VITO_SSH_ERROR' && exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! sudo service ufw restart; then
|
|
||||||
echo 'VITO_SSH_ERROR' && exit 1
|
|
||||||
fi
|
|
@ -0,0 +1,37 @@
|
|||||||
|
@include('ssh.services.firewall.ufw.backup-rules')
|
||||||
|
|
||||||
|
if ! sudo ufw --force reset; then
|
||||||
|
echo 'VITO_SSH_ERROR' && exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
if ! sudo ufw default deny incoming; then
|
||||||
|
echo 'VITO_SSH_ERROR' && exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! sudo ufw default allow outgoing; then
|
||||||
|
echo 'VITO_SSH_ERROR' && exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
@foreach($rules as $rule)
|
||||||
|
@php
|
||||||
|
$source = isset($rule->source) && $rule->source !== null
|
||||||
|
? $rule->source . (isset($rule->mask) && $rule->mask !== null ? '/' . $rule->mask : '')
|
||||||
|
: 'any';
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
if ! sudo ufw {{ $rule->type }} from {{ $source }} to any proto {{ $rule->protocol }} port {{ $rule->port }}; then
|
||||||
|
@include('ssh.services.firewall.ufw.restore-rules')
|
||||||
|
echo 'VITO_SSH_ERROR' && exit 1
|
||||||
|
fi
|
||||||
|
@endforeach
|
||||||
|
|
||||||
|
if ! sudo ufw --force enable; then
|
||||||
|
echo 'VITO_SSH_ERROR' && exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! sudo ufw reload; then
|
||||||
|
echo 'VITO_SSH_ERROR' && exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
@include('ssh.services.firewall.ufw.clear-backups')
|
@ -0,0 +1,6 @@
|
|||||||
|
sudo cp /etc/ufw/before.rules /tmp/ufw.before.backup
|
||||||
|
sudo cp /etc/ufw/after.rules /tmp/ufw.after.backup
|
||||||
|
sudo cp /etc/ufw/user.rules /tmp/ufw.user.backup
|
||||||
|
sudo cp /etc/ufw/before6.rules /tmp/ufw.before6.backup
|
||||||
|
sudo cp /etc/ufw/after6.rules /tmp/ufw.after6.backup
|
||||||
|
sudo cp /etc/ufw/user6.rules /tmp/ufw.user6.backup
|
@ -0,0 +1,6 @@
|
|||||||
|
sudo rm -f /tmp/ufw.before.backup
|
||||||
|
sudo rm -f /tmp/ufw.after.backup
|
||||||
|
sudo rm -f /tmp/ufw.user.backup
|
||||||
|
sudo rm -f /tmp/ufw.before6.backup
|
||||||
|
sudo rm -f /tmp/ufw.after6.backup
|
||||||
|
sudo rm -f /tmp/ufw.user6.backup
|
@ -1,11 +0,0 @@
|
|||||||
if ! sudo ufw delete {{ $type }} from {{ $source }}{{ $mask }} to any proto {{ $protocol }} port {{ $port }}; then
|
|
||||||
echo 'VITO_SSH_ERROR' && exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! sudo ufw reload; then
|
|
||||||
echo 'VITO_SSH_ERROR' && exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! sudo service ufw restart; then
|
|
||||||
echo 'VITO_SSH_ERROR' && exit 1
|
|
||||||
fi
|
|
@ -0,0 +1,10 @@
|
|||||||
|
sudo ufw --force disable
|
||||||
|
|
||||||
|
sudo cp /tmp/ufw.before.backup /etc/ufw/before.rules
|
||||||
|
sudo cp /tmp/ufw.after.backup /etc/ufw/after.rules
|
||||||
|
sudo cp /tmp/ufw.user.backup /etc/ufw/user.rules
|
||||||
|
sudo cp /tmp/ufw.before6.backup /etc/ufw/before6.rules
|
||||||
|
sudo cp /tmp/ufw.after6.backup /etc/ufw/after6.rules
|
||||||
|
sudo cp /tmp/ufw.user6.backup /etc/ufw/user6.rules
|
||||||
|
|
||||||
|
sudo ufw --force enable
|
@ -6,8 +6,12 @@
|
|||||||
}
|
}
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
@php
|
||||||
|
$backendName = preg_replace("/[^A-Za-z0-9 ]/", '', $site->domain).'_backend';
|
||||||
|
@endphp
|
||||||
|
|
||||||
@if ($site->type === \App\Enums\SiteType::LOAD_BALANCER)
|
@if ($site->type === \App\Enums\SiteType::LOAD_BALANCER)
|
||||||
upstream backend {
|
upstream {{ $backendName }} {
|
||||||
@switch($site->type_data['method'] ?? \App\Enums\LoadBalancerMethod::ROUND_ROBIN)
|
@switch($site->type_data['method'] ?? \App\Enums\LoadBalancerMethod::ROUND_ROBIN)
|
||||||
@case(\App\Enums\LoadBalancerMethod::LEAST_CONNECTIONS)
|
@case(\App\Enums\LoadBalancerMethod::LEAST_CONNECTIONS)
|
||||||
least_conn;
|
least_conn;
|
||||||
@ -49,7 +53,7 @@
|
|||||||
|
|
||||||
@if ($site->type()->language() === 'php')
|
@if ($site->type()->language() === 'php')
|
||||||
@php
|
@php
|
||||||
$phpSocket = 'unix:/var/run/php/php-fpm.sock';
|
$phpSocket = "unix:/var/run/php/php{$site->php_version}-fpm.sock";
|
||||||
if ($site->isIsolated()) {
|
if ($site->isIsolated()) {
|
||||||
$phpSocket = "unix:/run/php/php{$site->php_version}-fpm-{$site->user}.sock";
|
$phpSocket = "unix:/run/php/php{$site->php_version}-fpm-{$site->user}.sock";
|
||||||
}
|
}
|
||||||
@ -67,7 +71,7 @@
|
|||||||
|
|
||||||
@if ($site->type === \App\Enums\SiteType::LOAD_BALANCER)
|
@if ($site->type === \App\Enums\SiteType::LOAD_BALANCER)
|
||||||
location / {
|
location / {
|
||||||
proxy_pass http://backend$request_uri;
|
proxy_pass http://{{ $backendName }}$request_uri;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
@ -23,16 +23,47 @@ public function test_create_firewall_rule(): void
|
|||||||
'project' => $this->server->project,
|
'project' => $this->server->project,
|
||||||
'server' => $this->server,
|
'server' => $this->server,
|
||||||
]), [
|
]), [
|
||||||
|
'name' => 'Test',
|
||||||
'type' => 'allow',
|
'type' => 'allow',
|
||||||
'protocol' => 'tcp',
|
'protocol' => 'tcp',
|
||||||
'port' => '1234',
|
'port' => '1234',
|
||||||
'source' => '0.0.0.0',
|
'source' => '0.0.0.0',
|
||||||
'mask' => '0',
|
'mask' => '1',
|
||||||
])
|
])
|
||||||
->assertSuccessful()
|
->assertSuccessful()
|
||||||
->assertJsonFragment([
|
->assertJsonFragment([
|
||||||
'port' => 1234,
|
'port' => 1234,
|
||||||
'status' => FirewallRuleStatus::READY,
|
'status' => FirewallRuleStatus::CREATING,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_edit_firewall_rule(): void
|
||||||
|
{
|
||||||
|
SSH::fake();
|
||||||
|
|
||||||
|
Sanctum::actingAs($this->user, ['read', 'write']);
|
||||||
|
|
||||||
|
$rule = FirewallRule::factory()->create([
|
||||||
|
'server_id' => $this->server->id,
|
||||||
|
'port' => 1234,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->json('PUT', route('api.projects.servers.firewall-rules.edit', [
|
||||||
|
'project' => $this->server->project,
|
||||||
|
'server' => $this->server,
|
||||||
|
'firewallRule' => $rule,
|
||||||
|
]), [
|
||||||
|
'name' => 'Test',
|
||||||
|
'type' => 'allow',
|
||||||
|
'protocol' => 'tcp',
|
||||||
|
'port' => '55',
|
||||||
|
'source' => null,
|
||||||
|
'mask' => null,
|
||||||
|
])
|
||||||
|
->assertSuccessful()
|
||||||
|
->assertJsonFragment([
|
||||||
|
'port' => 55,
|
||||||
|
'status' => FirewallRuleStatus::UPDATING,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,22 +202,25 @@ public function test_update_env_file(): void
|
|||||||
->assertSuccessful()
|
->assertSuccessful()
|
||||||
->assertNotified('.env updated!');
|
->assertNotified('.env updated!');
|
||||||
|
|
||||||
SSH::assertFileUploaded('/home/vito/'.$this->site->domain.'/.env', 'APP_ENV="production"');
|
SSH::assertExecutedContains('tee /home/vito/vito.test/.env << \'VITO_SSH_EOF\'');
|
||||||
|
SSH::assertExecutedContains('APP_ENV="production"');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_git_hook_deployment(): void
|
/**
|
||||||
|
* @dataProvider hookData
|
||||||
|
*/
|
||||||
|
public function test_git_hook_deployment(string $provider, array $webhook, string $url, array $payload, bool $skip): void
|
||||||
{
|
{
|
||||||
SSH::fake();
|
SSH::fake();
|
||||||
Http::fake([
|
Http::fake([
|
||||||
'github.com/*' => Http::response([
|
$url => Http::response($payload),
|
||||||
'sha' => '123',
|
]);
|
||||||
'commit' => [
|
|
||||||
'message' => 'test commit message',
|
$this->site->update([
|
||||||
'name' => 'test commit name',
|
'branch' => 'main',
|
||||||
'email' => 'user@example.com',
|
]);
|
||||||
'url' => 'https://github.com',
|
$this->site->sourceControl->update([
|
||||||
],
|
'provider' => $provider,
|
||||||
], 200),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
GitHook::factory()->create([
|
GitHook::factory()->create([
|
||||||
@ -232,15 +235,29 @@ public function test_git_hook_deployment(): void
|
|||||||
'content' => 'git pull',
|
'content' => 'git pull',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->post(route('api.git-hooks'), [
|
$this->post(route('api.git-hooks', [
|
||||||
'secret' => 'secret',
|
'secret' => 'secret',
|
||||||
])->assertSessionDoesntHaveErrors();
|
]), $webhook)->assertSessionDoesntHaveErrors();
|
||||||
|
|
||||||
|
if ($skip) {
|
||||||
|
$this->assertDatabaseMissing('deployments', [
|
||||||
|
'site_id' => $this->site->id,
|
||||||
|
'deployment_script_id' => $this->site->deploymentScript->id,
|
||||||
|
'status' => DeploymentStatus::FINISHED,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$this->assertDatabaseHas('deployments', [
|
$this->assertDatabaseHas('deployments', [
|
||||||
'site_id' => $this->site->id,
|
'site_id' => $this->site->id,
|
||||||
'deployment_script_id' => $this->site->deploymentScript->id,
|
'deployment_script_id' => $this->site->deploymentScript->id,
|
||||||
'status' => DeploymentStatus::FINISHED,
|
'status' => DeploymentStatus::FINISHED,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$deployment = $this->site->deployments()->first();
|
||||||
|
$this->assertEquals('saeed', $deployment->commit_data['name']);
|
||||||
|
$this->assertEquals('saeed@vitodeploy.com', $deployment->commit_data['email']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_git_hook_deployment_invalid_secret(): void
|
public function test_git_hook_deployment_invalid_secret(): void
|
||||||
@ -270,4 +287,146 @@ public function test_git_hook_deployment_invalid_secret(): void
|
|||||||
'status' => DeploymentStatus::FINISHED,
|
'status' => DeploymentStatus::FINISHED,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function hookData(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
'github',
|
||||||
|
[
|
||||||
|
'ref' => 'refs/heads/main',
|
||||||
|
],
|
||||||
|
'github.com/*',
|
||||||
|
[
|
||||||
|
'sha' => '123',
|
||||||
|
'commit' => [
|
||||||
|
'committer' => [
|
||||||
|
'name' => 'saeed',
|
||||||
|
'email' => 'saeed@vitodeploy.com',
|
||||||
|
],
|
||||||
|
'message' => 'test commit message',
|
||||||
|
'url' => 'https://github.com',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
false,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'github',
|
||||||
|
[
|
||||||
|
'ref' => 'refs/heads/other-branch',
|
||||||
|
],
|
||||||
|
'github.com/*',
|
||||||
|
[
|
||||||
|
'sha' => '123',
|
||||||
|
'commit' => [
|
||||||
|
'committer' => [
|
||||||
|
'name' => 'saeed',
|
||||||
|
'email' => 'saeed@vitodeploy.com',
|
||||||
|
],
|
||||||
|
'message' => 'test commit message',
|
||||||
|
'url' => 'https://github.com',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
true,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'gitlab',
|
||||||
|
[
|
||||||
|
'ref' => 'main',
|
||||||
|
],
|
||||||
|
'gitlab.com/*',
|
||||||
|
[
|
||||||
|
[
|
||||||
|
'id' => '123',
|
||||||
|
'committer_name' => 'saeed',
|
||||||
|
'committer_email' => 'saeed@vitodeploy.com',
|
||||||
|
'title' => 'test',
|
||||||
|
'web_url' => 'https://gitlab.com',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
false,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'gitlab',
|
||||||
|
[
|
||||||
|
'ref' => 'other-branch',
|
||||||
|
],
|
||||||
|
'gitlab.com/*',
|
||||||
|
[
|
||||||
|
[
|
||||||
|
'id' => '123',
|
||||||
|
'committer_name' => 'saeed',
|
||||||
|
'committer_email' => 'saeed@vitodeploy.com',
|
||||||
|
'title' => 'test',
|
||||||
|
'web_url' => 'https://gitlab.com',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
true,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'bitbucket',
|
||||||
|
[
|
||||||
|
'push' => [
|
||||||
|
'changes' => [
|
||||||
|
[
|
||||||
|
'new' => [
|
||||||
|
'name' => 'main',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'bitbucket.org/*',
|
||||||
|
[
|
||||||
|
'values' => [
|
||||||
|
[
|
||||||
|
'hash' => '123',
|
||||||
|
'author' => [
|
||||||
|
'raw' => 'saeed <saeed@vitodeploy.com>',
|
||||||
|
],
|
||||||
|
'message' => 'test',
|
||||||
|
'links' => [
|
||||||
|
'html' => [
|
||||||
|
'href' => 'https://bitbucket.org',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
false,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'bitbucket',
|
||||||
|
[
|
||||||
|
'push' => [
|
||||||
|
'changes' => [
|
||||||
|
[
|
||||||
|
'new' => [
|
||||||
|
'name' => 'other-branch',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'bitbucket.org/*',
|
||||||
|
[
|
||||||
|
'values' => [
|
||||||
|
[
|
||||||
|
'hash' => '123',
|
||||||
|
'author' => [
|
||||||
|
'raw' => 'saeed <saeed@vitodeploy.com>',
|
||||||
|
],
|
||||||
|
'message' => 'test',
|
||||||
|
'links' => [
|
||||||
|
'html' => [
|
||||||
|
'href' => 'https://bitbucket.org',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
true,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,11 +22,16 @@ class DatabaseBackupTest extends TestCase
|
|||||||
{
|
{
|
||||||
use RefreshDatabase;
|
use RefreshDatabase;
|
||||||
|
|
||||||
public function test_create_backup(): void
|
/**
|
||||||
|
* @dataProvider data
|
||||||
|
*/
|
||||||
|
public function test_create_backup(string $db): void
|
||||||
{
|
{
|
||||||
SSH::fake();
|
SSH::fake();
|
||||||
Http::fake();
|
Http::fake();
|
||||||
|
|
||||||
|
$this->setupDatabase($db);
|
||||||
|
|
||||||
$this->actingAs($this->user);
|
$this->actingAs($this->user);
|
||||||
|
|
||||||
$database = Database::factory()->create([
|
$database = Database::factory()->create([
|
||||||
@ -152,8 +157,13 @@ public function test_update_backup(): void
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_delete_backup(): void
|
/**
|
||||||
|
* @dataProvider data
|
||||||
|
*/
|
||||||
|
public function test_delete_backup(string $db): void
|
||||||
{
|
{
|
||||||
|
$this->setupDatabase($db);
|
||||||
|
|
||||||
$this->actingAs($this->user);
|
$this->actingAs($this->user);
|
||||||
|
|
||||||
$database = Database::factory()->create([
|
$database = Database::factory()->create([
|
||||||
@ -182,11 +192,16 @@ public function test_delete_backup(): void
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_restore_backup(): void
|
/**
|
||||||
|
* @dataProvider data
|
||||||
|
*/
|
||||||
|
public function test_restore_backup(string $db): void
|
||||||
{
|
{
|
||||||
Http::fake();
|
Http::fake();
|
||||||
SSH::fake();
|
SSH::fake();
|
||||||
|
|
||||||
|
$this->setupDatabase($db);
|
||||||
|
|
||||||
$this->actingAs($this->user);
|
$this->actingAs($this->user);
|
||||||
|
|
||||||
$database = Database::factory()->create([
|
$database = Database::factory()->create([
|
||||||
@ -220,4 +235,24 @@ public function test_restore_backup(): void
|
|||||||
'status' => BackupFileStatus::RESTORED,
|
'status' => BackupFileStatus::RESTORED,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function setupDatabase(string $database): void
|
||||||
|
{
|
||||||
|
$this->server->services()->where('type', 'database')->delete();
|
||||||
|
|
||||||
|
$this->server->services()->create([
|
||||||
|
'type' => 'database',
|
||||||
|
'name' => config('core.databases_name.'.$database),
|
||||||
|
'version' => config('core.databases_version.'.$database),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function data(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
[\App\Enums\Database::MYSQL80],
|
||||||
|
[\App\Enums\Database::MARIADB104],
|
||||||
|
[\App\Enums\Database::POSTGRESQL16],
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
138
tests/Feature/FileManagerTest.php
Normal file
138
tests/Feature/FileManagerTest.php
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature;
|
||||||
|
|
||||||
|
use App\Facades\SSH;
|
||||||
|
use App\Models\File;
|
||||||
|
use App\Web\Pages\Servers\FileManager\Index;
|
||||||
|
use App\Web\Pages\Servers\FileManager\Widgets\FilesList;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Http\UploadedFile;
|
||||||
|
use Livewire\Livewire;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class FileManagerTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
public function test_see_files(): void
|
||||||
|
{
|
||||||
|
SSH::fake(<<<'EOF'
|
||||||
|
total 32
|
||||||
|
drwxr-xr-x 7 vito vito 4096 Feb 2 19:42 .
|
||||||
|
drwxr-xr-x 3 root root 4096 Feb 1 18:44 ..
|
||||||
|
drwx------ 3 vito vito 4096 Feb 1 18:45 .cache
|
||||||
|
drwxrwxr-x 3 vito vito 4096 Feb 1 18:45 .config
|
||||||
|
-rw-rw-r-- 1 vito vito 82 Feb 2 14:13 .gitconfig
|
||||||
|
drwxrwxr-x 3 vito vito 4096 Feb 1 18:45 .local
|
||||||
|
drwxr-xr-x 2 vito vito 4096 Feb 2 14:13 .ssh
|
||||||
|
drwxrwxr-x 3 vito vito 4096 Feb 2 21:25 test.vitodeploy.com
|
||||||
|
EOF
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->actingAs($this->user);
|
||||||
|
|
||||||
|
$this->get(
|
||||||
|
Index::getUrl([
|
||||||
|
'server' => $this->server,
|
||||||
|
])
|
||||||
|
)
|
||||||
|
->assertSuccessful()
|
||||||
|
->assertSee('.cache')
|
||||||
|
->assertSee('.config');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_upload_file(): void
|
||||||
|
{
|
||||||
|
SSH::fake();
|
||||||
|
|
||||||
|
$this->actingAs($this->user);
|
||||||
|
|
||||||
|
Livewire::test(FilesList::class, [
|
||||||
|
'server' => $this->server,
|
||||||
|
])
|
||||||
|
->callTableAction('upload', null, [
|
||||||
|
'file' => UploadedFile::fake()->create('test.txt'),
|
||||||
|
])
|
||||||
|
->assertSuccessful();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_create_file(): void
|
||||||
|
{
|
||||||
|
SSH::fake(<<<'EOF'
|
||||||
|
total 3
|
||||||
|
drwxr-xr-x 7 vito vito 4096 Feb 2 19:42 .
|
||||||
|
drwxr-xr-x 3 root root 4096 Feb 1 18:44 ..
|
||||||
|
-rw-rw-r-- 1 vito vito 82 Feb 2 14:13 test.txt
|
||||||
|
EOF
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->actingAs($this->user);
|
||||||
|
|
||||||
|
Livewire::test(FilesList::class, [
|
||||||
|
'server' => $this->server,
|
||||||
|
])
|
||||||
|
->callTableAction('new-file', null, [
|
||||||
|
'name' => 'test.txt',
|
||||||
|
'content' => 'Hello, world!',
|
||||||
|
])
|
||||||
|
->assertSuccessful();
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('files', [
|
||||||
|
'name' => 'test.txt',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_create_directory(): void
|
||||||
|
{
|
||||||
|
SSH::fake(<<<'EOF'
|
||||||
|
total 3
|
||||||
|
drwxr-xr-x 7 vito vito 4096 Feb 2 19:42 .
|
||||||
|
drwxr-xr-x 3 root root 4096 Feb 1 18:44 ..
|
||||||
|
drwxr-xr-x 2 vito vito 4096 Feb 2 14:13 test
|
||||||
|
EOF
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->actingAs($this->user);
|
||||||
|
|
||||||
|
Livewire::test(FilesList::class, [
|
||||||
|
'server' => $this->server,
|
||||||
|
])
|
||||||
|
->callTableAction('new-directory', null, [
|
||||||
|
'name' => 'test',
|
||||||
|
])
|
||||||
|
->assertSuccessful();
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('files', [
|
||||||
|
'name' => 'test',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_download_file(): void
|
||||||
|
{
|
||||||
|
SSH::fake(<<<'EOF'
|
||||||
|
total 3
|
||||||
|
drwxr-xr-x 7 vito vito 4096 Feb 2 19:42 .
|
||||||
|
drwxr-xr-x 3 root root 4096 Feb 1 18:44 ..
|
||||||
|
-rw-rw-r-- 1 vito vito 82 Feb 2 14:13 test.txt
|
||||||
|
EOF
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->actingAs($this->user);
|
||||||
|
|
||||||
|
$this->get(
|
||||||
|
Index::getUrl([
|
||||||
|
'server' => $this->server,
|
||||||
|
])
|
||||||
|
)->assertSuccessful();
|
||||||
|
|
||||||
|
$file = File::query()->where('name', 'test.txt')->firstOrFail();
|
||||||
|
|
||||||
|
Livewire::test(FilesList::class, [
|
||||||
|
'server' => $this->server,
|
||||||
|
])
|
||||||
|
->assertTableActionVisible('download', $file)
|
||||||
|
->callTableAction('download', $file)
|
||||||
|
->assertSuccessful();
|
||||||
|
}
|
||||||
|
}
|
@ -25,6 +25,7 @@ public function test_create_firewall_rule(): void
|
|||||||
'server' => $this->server,
|
'server' => $this->server,
|
||||||
])
|
])
|
||||||
->callAction('create', [
|
->callAction('create', [
|
||||||
|
'name' => 'Test',
|
||||||
'type' => 'allow',
|
'type' => 'allow',
|
||||||
'protocol' => 'tcp',
|
'protocol' => 'tcp',
|
||||||
'port' => '1234',
|
'port' => '1234',
|
||||||
|
Reference in New Issue
Block a user