mirror of
https://github.com/vitodeploy/vito.git
synced 2025-07-01 14:06:15 +00:00
Compare commits
17 Commits
Author | SHA1 | Date | |
---|---|---|---|
75aed62d75 | |||
aaef73d89d | |||
f03a029e36 | |||
52d195710b | |||
ddacc32e64 | |||
2ae9a14d02 | |||
3019c3d213 | |||
c43869d255 | |||
18748f77ac | |||
052e28d2e3 | |||
87ec0af697 | |||
e9016737d4 | |||
f34d5eb82b | |||
12c500e125 | |||
2d566b853f | |||
ca93b521ec | |||
a0af4e3e9d |
35
.github/workflows/docker-1x.yml
vendored
Normal file
35
.github/workflows/docker-1x.yml
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
name: Build and push Docker image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 1.x
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
run: |
|
||||
docker buildx build . \
|
||||
-f docker/Dockerfile \
|
||||
-t vitodeploy/vito:1.x \
|
||||
--build-arg="RELEASE=0" \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
--no-cache \
|
||||
--push
|
35
.github/workflows/docker-release.yml
vendored
Normal file
35
.github/workflows/docker-release.yml
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
name: Build and push Docker image
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
run: |
|
||||
docker buildx build . \
|
||||
-f docker/Dockerfile \
|
||||
-t vitodeploy/vito:${{ github.event.release.tag_name }} \
|
||||
-t vitodeploy/vito:latest \
|
||||
--build-arg="RELEASE=0" \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
--no-cache \
|
||||
--push
|
@ -36,7 +36,7 @@ ## Useful Links
|
||||
- [Install via Docker](https://vitodeploy.com/introduction/installation.html#install-via-docker)
|
||||
- [Feedbacks](https://vitodeploy.featurebase.app)
|
||||
- [Roadmap](https://vitodeploy.featurebase.app/roadmap)
|
||||
- [Video Demo](https://youtu.be/rLRHIyEfON8)
|
||||
- [Video Demo](https://youtu.be/AbmUOBDOc28)
|
||||
- [Discord](https://discord.gg/uZeeHZZnm5)
|
||||
- [Contribution](/CONTRIBUTING.md)
|
||||
- [Security](/SECURITY.md)
|
||||
|
150
app/Actions/Monitoring/GetMetrics.php
Normal file
150
app/Actions/Monitoring/GetMetrics.php
Normal file
@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Monitoring;
|
||||
|
||||
use App\Models\Server;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Contracts\Database\Query\Expression;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class GetMetrics
|
||||
{
|
||||
public function filter(Server $server, array $input): array
|
||||
{
|
||||
if (isset($input['from']) && isset($input['to']) && $input['from'] === $input['to']) {
|
||||
$input['from'] = Carbon::parse($input['from'])->format('Y-m-d').' 00:00:00';
|
||||
$input['to'] = Carbon::parse($input['to'])->format('Y-m-d').' 23:59:59';
|
||||
}
|
||||
|
||||
$defaultInput = [
|
||||
'period' => '10m',
|
||||
];
|
||||
|
||||
$input = array_merge($defaultInput, $input);
|
||||
|
||||
$this->validate($input);
|
||||
|
||||
return $this->metrics(
|
||||
server: $server,
|
||||
fromDate: $this->getFromDate($input),
|
||||
toDate: $this->getToDate($input),
|
||||
interval: $this->getInterval($input)
|
||||
);
|
||||
}
|
||||
|
||||
private function metrics(
|
||||
Server $server,
|
||||
Carbon $fromDate,
|
||||
Carbon $toDate,
|
||||
?Expression $interval = null
|
||||
): array {
|
||||
$metrics = DB::table('metrics')
|
||||
->where('server_id', $server->id)
|
||||
->whereBetween('created_at', [$fromDate->format('Y-m-d H:i:s'), $toDate->format('Y-m-d H:i:s')])
|
||||
->select(
|
||||
[
|
||||
DB::raw('created_at as date'),
|
||||
DB::raw('ROUND(AVG(load), 2) as load'),
|
||||
DB::raw('ROUND(AVG(memory_total), 2) as memory_total'),
|
||||
DB::raw('ROUND(AVG(memory_used), 2) as memory_used'),
|
||||
DB::raw('ROUND(AVG(memory_free), 2) as memory_free'),
|
||||
DB::raw('ROUND(AVG(disk_total), 2) as disk_total'),
|
||||
DB::raw('ROUND(AVG(disk_used), 2) as disk_used'),
|
||||
DB::raw('ROUND(AVG(disk_free), 2) as disk_free'),
|
||||
$interval,
|
||||
],
|
||||
)
|
||||
->groupByRaw('date_interval')
|
||||
->orderBy('date_interval')
|
||||
->get()
|
||||
->map(function ($item) {
|
||||
$item->date = Carbon::parse($item->date)->format('Y-m-d H:i');
|
||||
|
||||
return $item;
|
||||
});
|
||||
|
||||
return [
|
||||
'metrics' => $metrics,
|
||||
];
|
||||
}
|
||||
|
||||
private function getFromDate(array $input): Carbon
|
||||
{
|
||||
if ($input['period'] === 'custom') {
|
||||
return new Carbon($input['from']);
|
||||
}
|
||||
|
||||
return Carbon::parse('-'.convert_time_format($input['period']));
|
||||
}
|
||||
|
||||
private function getToDate(array $input): Carbon
|
||||
{
|
||||
if ($input['period'] === 'custom') {
|
||||
return new Carbon($input['to']);
|
||||
}
|
||||
|
||||
return Carbon::now();
|
||||
}
|
||||
|
||||
private function getInterval(array $input): Expression
|
||||
{
|
||||
if ($input['period'] === 'custom') {
|
||||
$from = new Carbon($input['from']);
|
||||
$to = new Carbon($input['to']);
|
||||
$periodInHours = $from->diffInHours($to);
|
||||
}
|
||||
|
||||
if (! isset($periodInHours)) {
|
||||
$periodInHours = Carbon::parse(
|
||||
convert_time_format($input['period'])
|
||||
)->diffInHours();
|
||||
}
|
||||
|
||||
if ($periodInHours <= 1) {
|
||||
return DB::raw("strftime('%Y-%m-%d %H:%M:00', created_at) as date_interval");
|
||||
}
|
||||
|
||||
if ($periodInHours <= 24) {
|
||||
return DB::raw("strftime('%Y-%m-%d %H:00:00', created_at) as date_interval");
|
||||
}
|
||||
|
||||
if ($periodInHours > 24) {
|
||||
return DB::raw("strftime('%Y-%m-%d 00:00:00', created_at) as date_interval");
|
||||
}
|
||||
}
|
||||
|
||||
private function validate(array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'period' => [
|
||||
'required',
|
||||
Rule::in([
|
||||
'10m',
|
||||
'30m',
|
||||
'1h',
|
||||
'12h',
|
||||
'1d',
|
||||
'7d',
|
||||
'custom',
|
||||
]),
|
||||
],
|
||||
])->validate();
|
||||
|
||||
if ($input['period'] === 'custom') {
|
||||
Validator::make($input, [
|
||||
'from' => [
|
||||
'required',
|
||||
'date',
|
||||
'before:to',
|
||||
],
|
||||
'to' => [
|
||||
'required',
|
||||
'date',
|
||||
'after:from',
|
||||
],
|
||||
])->validate();
|
||||
}
|
||||
}
|
||||
}
|
32
app/Actions/Monitoring/UpdateMetricSettings.php
Normal file
32
app/Actions/Monitoring/UpdateMetricSettings.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Monitoring;
|
||||
|
||||
use App\Models\Server;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateMetricSettings
|
||||
{
|
||||
public function update(Server $server, array $input): void
|
||||
{
|
||||
$this->validate($input);
|
||||
|
||||
$service = $server->monitoring();
|
||||
|
||||
$data = $service->handler()->data();
|
||||
$data['data_retention'] = $input['data_retention'];
|
||||
$service->type_data = $data;
|
||||
$service->save();
|
||||
}
|
||||
|
||||
private function validate(array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'data_retention' => [
|
||||
'required',
|
||||
Rule::in(config('core.metrics_data_retention')),
|
||||
],
|
||||
])->validate();
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@
|
||||
|
||||
use App\Enums\ServiceStatus;
|
||||
use App\Models\Server;
|
||||
use App\SSH\Services\PHP\PHP;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class ChangeDefaultCli
|
||||
@ -12,7 +13,9 @@ public function change(Server $server, array $input): void
|
||||
{
|
||||
$this->validate($server, $input);
|
||||
$service = $server->php($input['version']);
|
||||
$service->handler()->setDefaultCli();
|
||||
/** @var PHP $handler */
|
||||
$handler = $service->handler();
|
||||
$handler->setDefaultCli();
|
||||
$server->defaultService('php')->update(['is_default' => 0]);
|
||||
$service->update(['is_default' => 1]);
|
||||
$service->update(['status' => ServiceStatus::READY]);
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace App\Actions\PHP;
|
||||
|
||||
use App\Models\Server;
|
||||
use App\SSH\Services\PHP\PHP;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class GetPHPIni
|
||||
@ -14,7 +15,10 @@ public function getIni(Server $server, array $input): string
|
||||
$php = $server->php($input['version']);
|
||||
|
||||
try {
|
||||
return $php->handler()->getPHPIni();
|
||||
/** @var PHP $handler */
|
||||
$handler = $php->handler();
|
||||
|
||||
return $handler->getPHPIni();
|
||||
} catch (\Throwable $e) {
|
||||
throw ValidationException::withMessages(
|
||||
['ini' => $e->getMessage()]
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
use App\SSH\Services\PHP\PHP;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
@ -23,7 +24,9 @@ public function install(Server $server, array $input): Service
|
||||
$service->save();
|
||||
|
||||
dispatch(function () use ($service, $input) {
|
||||
$service->handler()->installExtension($input['extension']);
|
||||
/** @var PHP $handler */
|
||||
$handler = $service->handler();
|
||||
$handler->installExtension($input['extension']);
|
||||
})->catch(function () use ($service, $input) {
|
||||
$service->refresh();
|
||||
$typeData = $service->type_data;
|
||||
|
@ -34,8 +34,6 @@ public function create(Site $site, array $input): void
|
||||
$ssl->status = SslStatus::CREATED;
|
||||
$ssl->save();
|
||||
$site->type()->edit();
|
||||
})->catch(function () use ($ssl) {
|
||||
$ssl->delete();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -8,14 +8,15 @@
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class Create
|
||||
class Install
|
||||
{
|
||||
public function create(Server $server, array $input): Service
|
||||
public function install(Server $server, array $input): Service
|
||||
{
|
||||
$this->validate($server, $input);
|
||||
|
||||
$service = new Service([
|
||||
'name' => $input['type'],
|
||||
'server_id' => $server->id,
|
||||
'name' => $input['name'],
|
||||
'type' => $input['type'],
|
||||
'version' => $input['version'],
|
||||
'status' => ServiceStatus::INSTALLING,
|
||||
@ -27,15 +28,13 @@ public function create(Server $server, array $input): Service
|
||||
|
||||
$service->save();
|
||||
|
||||
$service->handler()->create();
|
||||
|
||||
dispatch(function () use ($service) {
|
||||
$service->handler()->install();
|
||||
$service->status = ServiceStatus::READY;
|
||||
$service->save();
|
||||
})->catch(function () use ($service) {
|
||||
$service->handler()->delete();
|
||||
$service->delete();
|
||||
$service->status = ServiceStatus::INSTALLATION_FAILED;
|
||||
$service->save();
|
||||
})->onConnection('ssh');
|
||||
|
||||
return $service;
|
||||
@ -46,8 +45,11 @@ private function validate(Server $server, array $input): void
|
||||
Validator::make($input, [
|
||||
'type' => [
|
||||
'required',
|
||||
Rule::in(config('core.add_on_services')),
|
||||
Rule::unique('services', 'type')->where('server_id', $server->id),
|
||||
Rule::in(config('core.service_types')),
|
||||
],
|
||||
'name' => [
|
||||
'required',
|
||||
Rule::in(array_keys(config('core.service_types'))),
|
||||
],
|
||||
'version' => 'required',
|
||||
])->validate();
|
28
app/Actions/Service/Uninstall.php
Normal file
28
app/Actions/Service/Uninstall.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Service;
|
||||
|
||||
use App\Enums\ServiceStatus;
|
||||
use App\Models\Service;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class Uninstall
|
||||
{
|
||||
public function uninstall(Service $service): void
|
||||
{
|
||||
Validator::make([
|
||||
'service' => $service->id,
|
||||
], $service->handler()->deletionRules())->validate();
|
||||
|
||||
$service->status = ServiceStatus::UNINSTALLING;
|
||||
$service->save();
|
||||
|
||||
dispatch(function () use ($service) {
|
||||
$service->handler()->uninstall();
|
||||
$service->delete();
|
||||
})->catch(function () use ($service) {
|
||||
$service->status = ServiceStatus::FAILED;
|
||||
$service->save();
|
||||
})->onConnection('ssh');
|
||||
}
|
||||
}
|
@ -3,6 +3,8 @@
|
||||
namespace App\Actions\Site;
|
||||
|
||||
use App\Enums\SiteStatus;
|
||||
use App\Exceptions\RepositoryNotFound;
|
||||
use App\Exceptions\RepositoryPermissionDenied;
|
||||
use App\Exceptions\SourceControlIsNotConnected;
|
||||
use App\Facades\Notifier;
|
||||
use App\Models\Server;
|
||||
@ -19,7 +21,6 @@
|
||||
class CreateSite
|
||||
{
|
||||
/**
|
||||
* @throws SourceControlIsNotConnected
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function create(Server $server, array $input): Site
|
||||
@ -47,7 +48,15 @@ public function create(Server $server, array $input): Site
|
||||
}
|
||||
} catch (SourceControlIsNotConnected) {
|
||||
throw ValidationException::withMessages([
|
||||
'source_control' => __('Source control is not connected'),
|
||||
'source_control' => 'Source control is not connected',
|
||||
]);
|
||||
} catch (RepositoryPermissionDenied) {
|
||||
throw ValidationException::withMessages([
|
||||
'repository' => 'You do not have permission to access this repository',
|
||||
]);
|
||||
} catch (RepositoryNotFound) {
|
||||
throw ValidationException::withMessages([
|
||||
'repository' => 'Repository not found',
|
||||
]);
|
||||
}
|
||||
|
||||
|
49
app/Actions/Site/UpdateSourceControl.php
Normal file
49
app/Actions/Site/UpdateSourceControl.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Site;
|
||||
|
||||
use App\Exceptions\RepositoryNotFound;
|
||||
use App\Exceptions\RepositoryPermissionDenied;
|
||||
use App\Exceptions\SourceControlIsNotConnected;
|
||||
use App\Models\Site;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class UpdateSourceControl
|
||||
{
|
||||
public function update(Site $site, array $input): void
|
||||
{
|
||||
$this->validate($input);
|
||||
|
||||
$site->source_control_id = $input['source_control'];
|
||||
try {
|
||||
if ($site->sourceControl()) {
|
||||
$site->sourceControl()->getRepo($site->repository);
|
||||
}
|
||||
} catch (SourceControlIsNotConnected) {
|
||||
throw ValidationException::withMessages([
|
||||
'source_control' => 'Source control is not connected',
|
||||
]);
|
||||
} catch (RepositoryPermissionDenied) {
|
||||
throw ValidationException::withMessages([
|
||||
'repository' => 'You do not have permission to access this repository',
|
||||
]);
|
||||
} catch (RepositoryNotFound) {
|
||||
throw ValidationException::withMessages([
|
||||
'repository' => 'Repository not found',
|
||||
]);
|
||||
}
|
||||
$site->save();
|
||||
}
|
||||
|
||||
private function validate(array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'source_control' => [
|
||||
'required',
|
||||
Rule::exists('source_controls', 'id'),
|
||||
],
|
||||
])->validate();
|
||||
}
|
||||
}
|
28
app/Console/Commands/DeleteOlderMetricsCommand.php
Normal file
28
app/Console/Commands/DeleteOlderMetricsCommand.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Service;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class DeleteOlderMetricsCommand extends Command
|
||||
{
|
||||
protected $signature = 'metrics:delete-older-metrics';
|
||||
|
||||
protected $description = 'Delete older metrics from database';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
Service::query()->where('type', 'monitoring')->chunk(100, function ($services) {
|
||||
$services->each(function ($service) {
|
||||
$this->info("Deleting older metrics for service {$service->server->name}");
|
||||
$service
|
||||
->server
|
||||
->metrics()
|
||||
->where('created_at', '<', now()->subDays($service->handler()->data()['data_retention']))
|
||||
->delete();
|
||||
$this->info('Metrics deleted');
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
@ -16,6 +16,7 @@ protected function schedule(Schedule $schedule): void
|
||||
$schedule->command('backups:run "0 0 * * *"')->daily();
|
||||
$schedule->command('backups:run "0 0 * * 0"')->weekly();
|
||||
$schedule->command('backups:run "0 0 1 * *"')->monthly();
|
||||
$schedule->command('metrics:delete-older-metrics')->daily();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2,9 +2,7 @@
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class SSHAuthenticationError extends Exception
|
||||
class SSHAuthenticationError extends SSHError
|
||||
{
|
||||
//
|
||||
}
|
||||
|
@ -2,9 +2,7 @@
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class SSLCreationException extends Exception
|
||||
class SSLCreationException extends SSHError
|
||||
{
|
||||
//
|
||||
}
|
||||
|
@ -133,7 +133,7 @@ public function exec(string $command, string $log = '', ?int $siteId = null, ?bo
|
||||
$this->log?->write($output);
|
||||
|
||||
if (Str::contains($output, 'VITO_SSH_ERROR')) {
|
||||
throw new Exception('SSH command failed with an error');
|
||||
throw new SSHCommandError('SSH command failed with an error');
|
||||
}
|
||||
|
||||
return $output;
|
||||
|
36
app/Http/Controllers/API/AgentController.php
Normal file
36
app/Http/Controllers/API/AgentController.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\API;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class AgentController extends Controller
|
||||
{
|
||||
public function __invoke(Request $request, Server $server, int $id): JsonResponse
|
||||
{
|
||||
$validated = $this->validate($request, [
|
||||
'load' => 'required|numeric',
|
||||
'memory_total' => 'required|numeric',
|
||||
'memory_used' => 'required|numeric',
|
||||
'memory_free' => 'required|numeric',
|
||||
'disk_total' => 'required|numeric',
|
||||
'disk_used' => 'required|numeric',
|
||||
'disk_free' => 'required|numeric',
|
||||
]);
|
||||
|
||||
/** @var Service $service */
|
||||
$service = $server->services()->findOrFail($id);
|
||||
|
||||
if ($request->header('secret') !== $service->handler()->data()['secret']) {
|
||||
return response()->json(['error' => 'Unauthorized'], 401);
|
||||
}
|
||||
|
||||
$server->metrics()->create(array_merge($validated, ['server_id' => $server->id]));
|
||||
|
||||
return response()->json();
|
||||
}
|
||||
}
|
@ -7,6 +7,8 @@
|
||||
use App\Actions\Site\UpdateDeploymentScript;
|
||||
use App\Actions\Site\UpdateEnv;
|
||||
use App\Exceptions\DeploymentScriptIsEmptyException;
|
||||
use App\Exceptions\RepositoryNotFound;
|
||||
use App\Exceptions\RepositoryPermissionDenied;
|
||||
use App\Exceptions\SourceControlIsNotConnected;
|
||||
use App\Facades\Toast;
|
||||
use App\Helpers\HtmxResponse;
|
||||
@ -24,12 +26,14 @@ public function deploy(Server $server, Site $site): HtmxResponse
|
||||
app(Deploy::class)->run($site);
|
||||
|
||||
Toast::success('Deployment started!');
|
||||
} catch (SourceControlIsNotConnected $e) {
|
||||
Toast::error($e->getMessage());
|
||||
|
||||
return htmx()->redirect(route('source-controls'));
|
||||
} catch (SourceControlIsNotConnected) {
|
||||
Toast::error('Source control is not connected. Check site\'s settings.');
|
||||
} catch (DeploymentScriptIsEmptyException) {
|
||||
Toast::error('Deployment script is empty!');
|
||||
} catch (RepositoryPermissionDenied) {
|
||||
Toast::error('You do not have permission to access this repository!');
|
||||
} catch (RepositoryNotFound) {
|
||||
Toast::error('Repository not found!');
|
||||
}
|
||||
|
||||
return htmx()->back();
|
||||
@ -83,6 +87,12 @@ public function enableAutoDeployment(Server $server, Site $site): HtmxResponse
|
||||
Toast::success('Auto deployment has been enabled.');
|
||||
} catch (SourceControlIsNotConnected) {
|
||||
Toast::error('Source control is not connected. Check site\'s settings.');
|
||||
} catch (DeploymentScriptIsEmptyException) {
|
||||
Toast::error('Deployment script is empty!');
|
||||
} catch (RepositoryPermissionDenied) {
|
||||
Toast::error('You do not have permission to access this repository!');
|
||||
} catch (RepositoryNotFound) {
|
||||
Toast::error('Repository not found!');
|
||||
}
|
||||
}
|
||||
|
||||
|
44
app/Http/Controllers/MetricController.php
Normal file
44
app/Http/Controllers/MetricController.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Actions\Monitoring\GetMetrics;
|
||||
use App\Actions\Monitoring\UpdateMetricSettings;
|
||||
use App\Facades\Toast;
|
||||
use App\Helpers\HtmxResponse;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class MetricController extends Controller
|
||||
{
|
||||
public function index(Server $server, Request $request): View|RedirectResponse
|
||||
{
|
||||
$this->checkIfMonitoringServiceInstalled($server);
|
||||
|
||||
return view('metrics.index', [
|
||||
'server' => $server,
|
||||
'data' => app(GetMetrics::class)->filter($server, $request->input()),
|
||||
'lastMetric' => $server->metrics()->latest()->first(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function settings(Server $server, Request $request): HtmxResponse
|
||||
{
|
||||
$this->checkIfMonitoringServiceInstalled($server);
|
||||
|
||||
app(UpdateMetricSettings::class)->update($server, $request->input());
|
||||
|
||||
Toast::success('Metric settings updated successfully');
|
||||
|
||||
return htmx()->back();
|
||||
}
|
||||
|
||||
private function checkIfMonitoringServiceInstalled(Server $server): void
|
||||
{
|
||||
if (! $server->monitoring()) {
|
||||
abort(404, 'Monitoring service is not installed on this server');
|
||||
}
|
||||
}
|
||||
}
|
@ -2,7 +2,8 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Actions\Service\Create;
|
||||
use App\Actions\Service\Install;
|
||||
use App\Actions\Service\Uninstall;
|
||||
use App\Facades\Toast;
|
||||
use App\Helpers\HtmxResponse;
|
||||
use App\Models\Server;
|
||||
@ -68,7 +69,16 @@ public function disable(Server $server, Service $service): RedirectResponse
|
||||
|
||||
public function install(Server $server, Request $request): HtmxResponse
|
||||
{
|
||||
app(Create::class)->create($server, $request->input());
|
||||
app(Install::class)->install($server, $request->input());
|
||||
|
||||
Toast::success('Service is being uninstalled!');
|
||||
|
||||
return htmx()->back();
|
||||
}
|
||||
|
||||
public function uninstall(Server $server, Service $service): HtmxResponse
|
||||
{
|
||||
app(Uninstall::class)->uninstall($service);
|
||||
|
||||
Toast::success('Service is being uninstalled!');
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
use App\Actions\Site\CreateSite;
|
||||
use App\Actions\Site\DeleteSite;
|
||||
use App\Enums\SiteStatus;
|
||||
use App\Enums\SiteType;
|
||||
use App\Facades\Toast;
|
||||
use App\Helpers\HtmxResponse;
|
||||
@ -42,14 +43,38 @@ public function create(Server $server): View
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(Server $server, Site $site): View
|
||||
public function show(Server $server, Site $site, Request $request): View|RedirectResponse|HtmxResponse
|
||||
{
|
||||
if (in_array($site->status, [SiteStatus::INSTALLING, SiteStatus::INSTALLATION_FAILED])) {
|
||||
if ($request->hasHeader('HX-Request')) {
|
||||
return htmx()->redirect(route('servers.sites.installing', [$server, $site]));
|
||||
}
|
||||
|
||||
return redirect()->route('servers.sites.installing', [$server, $site]);
|
||||
}
|
||||
|
||||
return view('sites.show', [
|
||||
'server' => $server,
|
||||
'site' => $site,
|
||||
]);
|
||||
}
|
||||
|
||||
public function installing(Server $server, Site $site, Request $request): View|RedirectResponse|HtmxResponse
|
||||
{
|
||||
if (! in_array($site->status, [SiteStatus::INSTALLING, SiteStatus::INSTALLATION_FAILED])) {
|
||||
if ($request->hasHeader('HX-Request')) {
|
||||
return htmx()->redirect(route('servers.sites.show', [$server, $site]));
|
||||
}
|
||||
|
||||
return redirect()->route('servers.sites.show', [$server, $site]);
|
||||
}
|
||||
|
||||
return view('sites.installing', [
|
||||
'server' => $server,
|
||||
'site' => $site,
|
||||
]);
|
||||
}
|
||||
|
||||
public function destroy(Server $server, Site $site): RedirectResponse
|
||||
{
|
||||
app(DeleteSite::class)->delete($site);
|
||||
|
@ -2,10 +2,12 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Actions\Site\UpdateSourceControl;
|
||||
use App\Facades\Toast;
|
||||
use App\Helpers\HtmxResponse;
|
||||
use App\Models\Server;
|
||||
use App\Models\Site;
|
||||
use App\SSH\Services\Webserver\Webserver;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
@ -24,7 +26,10 @@ public function index(Server $server, Site $site): View
|
||||
|
||||
public function getVhost(Server $server, Site $site): RedirectResponse
|
||||
{
|
||||
return back()->with('vhost', $server->webserver()->handler()->getVHost($site));
|
||||
/** @var Webserver $handler */
|
||||
$handler = $server->webserver()->handler();
|
||||
|
||||
return back()->with('vhost', $handler->getVHost($site));
|
||||
}
|
||||
|
||||
public function updateVhost(Server $server, Site $site, Request $request): RedirectResponse
|
||||
@ -34,7 +39,9 @@ public function updateVhost(Server $server, Site $site, Request $request): Redir
|
||||
]);
|
||||
|
||||
try {
|
||||
$server->webserver()->handler()->updateVHost($site, false, $request->input('vhost'));
|
||||
/** @var Webserver $handler */
|
||||
$handler = $server->webserver()->handler();
|
||||
$handler->updateVHost($site, false, $request->input('vhost'));
|
||||
|
||||
Toast::success('VHost updated successfully!');
|
||||
} catch (Throwable $e) {
|
||||
@ -63,4 +70,13 @@ public function updatePHPVersion(Server $server, Site $site, Request $request):
|
||||
|
||||
return htmx()->back();
|
||||
}
|
||||
|
||||
public function updateSourceControl(Server $server, Site $site, Request $request): HtmxResponse
|
||||
{
|
||||
$site = app(UpdateSourceControl::class)->update($site, $request->input());
|
||||
|
||||
Toast::success('Source control updated successfully!');
|
||||
|
||||
return htmx()->back();
|
||||
}
|
||||
}
|
||||
|
53
app/Models/Metric.php
Normal file
53
app/Models/Metric.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $server_id
|
||||
* @property float $load
|
||||
* @property float $memory_total
|
||||
* @property float $memory_used
|
||||
* @property float $memory_free
|
||||
* @property float $disk_total
|
||||
* @property float $disk_used
|
||||
* @property float $disk_free
|
||||
* @property Server $server
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
*/
|
||||
class Metric extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'server_id',
|
||||
'load',
|
||||
'memory_total',
|
||||
'memory_used',
|
||||
'memory_free',
|
||||
'disk_total',
|
||||
'disk_used',
|
||||
'disk_free',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'server_id' => 'integer',
|
||||
'load' => 'float',
|
||||
'memory_total' => 'float',
|
||||
'memory_used' => 'float',
|
||||
'memory_free' => 'float',
|
||||
'disk_total' => 'float',
|
||||
'disk_used' => 'float',
|
||||
'disk_free' => 'float',
|
||||
];
|
||||
|
||||
public function server(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Server::class);
|
||||
}
|
||||
}
|
@ -195,6 +195,11 @@ public function daemons(): HasMany
|
||||
return $this->queues()->whereNull('site_id');
|
||||
}
|
||||
|
||||
public function metrics(): HasMany
|
||||
{
|
||||
return $this->hasMany(Metric::class);
|
||||
}
|
||||
|
||||
public function sshKeys(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(SshKey::class, 'server_ssh_keys')
|
||||
@ -325,6 +330,24 @@ public function php(?string $version = null): ?Service
|
||||
return $this->service('php', $version);
|
||||
}
|
||||
|
||||
public function memoryDatabase(?string $version = null): ?Service
|
||||
{
|
||||
if (! $version) {
|
||||
return $this->defaultService('memory_database');
|
||||
}
|
||||
|
||||
return $this->service('memory_database', $version);
|
||||
}
|
||||
|
||||
public function monitoring(?string $version = null): ?Service
|
||||
{
|
||||
if (! $version) {
|
||||
return $this->defaultService('monitoring');
|
||||
}
|
||||
|
||||
return $this->service('monitoring', $version);
|
||||
}
|
||||
|
||||
public function sshKey(): array
|
||||
{
|
||||
/** @var FilesystemAdapter $storageDisk */
|
||||
|
@ -4,13 +4,7 @@
|
||||
|
||||
use App\Actions\Service\Manage;
|
||||
use App\Exceptions\ServiceInstallationFailed;
|
||||
use App\SSH\Services\AddOnServices\AbstractAddOnService;
|
||||
use App\SSH\Services\Database\Database as DatabaseHandler;
|
||||
use App\SSH\Services\Firewall\Firewall as FirewallHandler;
|
||||
use App\SSH\Services\PHP\PHP as PHPHandler;
|
||||
use App\SSH\Services\ProcessManager\ProcessManager as ProcessManagerHandler;
|
||||
use App\SSH\Services\Redis\Redis as RedisHandler;
|
||||
use App\SSH\Services\Webserver\Webserver as WebserverHandler;
|
||||
use App\SSH\Services\ServiceInterface;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Support\Str;
|
||||
@ -65,8 +59,8 @@ public function server(): BelongsTo
|
||||
return $this->belongsTo(Server::class);
|
||||
}
|
||||
|
||||
public function handler(
|
||||
): PHPHandler|WebserverHandler|DatabaseHandler|FirewallHandler|ProcessManagerHandler|RedisHandler|AbstractAddOnService {
|
||||
public function handler(): ServiceInterface
|
||||
{
|
||||
$handler = config('core.service_handlers')[$this->name];
|
||||
|
||||
return new $handler($this);
|
||||
|
@ -148,4 +148,12 @@ public function unzip(string $path): string
|
||||
'unzip '.$path
|
||||
);
|
||||
}
|
||||
|
||||
public function cleanup(): void
|
||||
{
|
||||
$this->server->ssh()->exec(
|
||||
$this->getScript('cleanup.sh'),
|
||||
'cleanup'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
19
app/SSH/OS/scripts/cleanup.sh
Normal file
19
app/SSH/OS/scripts/cleanup.sh
Normal file
@ -0,0 +1,19 @@
|
||||
# Update package lists
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get update -y
|
||||
|
||||
# Remove unnecessary dependencies
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get autoremove --purge -y
|
||||
|
||||
# Clear package cache
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get clean -y
|
||||
|
||||
# Remove old configuration files
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get purge -y $(dpkg -l | grep '^rc' | awk '{print $2}')
|
||||
|
||||
# Clear temporary files
|
||||
sudo rm -rf /tmp/*
|
||||
|
||||
# Clear journal logs
|
||||
sudo DEBIAN_FRONTEND=noninteractive journalctl --vacuum-time=1d
|
||||
|
||||
echo "Cleanup completed."
|
42
app/SSH/Services/AbstractService.php
Normal file
42
app/SSH/Services/AbstractService.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\SSH\Services;
|
||||
|
||||
use App\Models\Service;
|
||||
|
||||
abstract class AbstractService implements ServiceInterface
|
||||
{
|
||||
public function __construct(protected Service $service)
|
||||
{
|
||||
}
|
||||
|
||||
public function creationRules(array $input): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function creationData(array $input): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function deletionRules(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function data(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function install(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
public function uninstall(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\SSH\Services\AddOnServices;
|
||||
|
||||
use App\SSH\Services\ServiceInterface;
|
||||
|
||||
abstract class AbstractAddOnService implements ServiceInterface
|
||||
{
|
||||
abstract public function creationRules(array $input): array;
|
||||
|
||||
abstract public function creationData(array $input): array;
|
||||
|
||||
abstract public function create(): void;
|
||||
|
||||
abstract public function delete(): void;
|
||||
|
||||
abstract public function data(): array;
|
||||
}
|
@ -2,40 +2,78 @@
|
||||
|
||||
namespace App\SSH\Services\Database;
|
||||
|
||||
use App\Enums\BackupStatus;
|
||||
use App\Models\BackupFile;
|
||||
use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
use App\SSH\HasScripts;
|
||||
use App\SSH\Services\ServiceInterface;
|
||||
use App\SSH\Services\AbstractService;
|
||||
use Closure;
|
||||
|
||||
abstract class AbstractDatabase implements Database, ServiceInterface
|
||||
abstract class AbstractDatabase extends AbstractService implements Database
|
||||
{
|
||||
use HasScripts;
|
||||
|
||||
protected Service $service;
|
||||
|
||||
protected Server $server;
|
||||
|
||||
abstract protected function getScriptsDir(): string;
|
||||
|
||||
public function __construct(Service $service)
|
||||
public function creationRules(array $input): array
|
||||
{
|
||||
$this->service = $service;
|
||||
$this->server = $service->server;
|
||||
return [
|
||||
'type' => [
|
||||
'required',
|
||||
function (string $attribute, mixed $value, Closure $fail) {
|
||||
$databaseExists = $this->service->server->database();
|
||||
if ($databaseExists) {
|
||||
$fail('You already have a database service on the server.');
|
||||
}
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function install(): void
|
||||
{
|
||||
$version = $this->service->version;
|
||||
$command = $this->getScript($this->service->name.'/install-'.$version.'.sh');
|
||||
$this->server->ssh()->exec($command, 'install-'.$this->service->name.'-'.$version);
|
||||
$status = $this->server->systemd()->status($this->service->unit);
|
||||
$this->service->server->ssh()->exec($command, 'install-'.$this->service->name.'-'.$version);
|
||||
$status = $this->service->server->systemd()->status($this->service->unit);
|
||||
$this->service->validateInstall($status);
|
||||
$this->service->server->os()->cleanup();
|
||||
}
|
||||
|
||||
public function deletionRules(): array
|
||||
{
|
||||
return [
|
||||
'service' => [
|
||||
function (string $attribute, mixed $value, Closure $fail) {
|
||||
$hasDatabase = $this->service->server->databases()->exists();
|
||||
if ($hasDatabase) {
|
||||
$fail('You have database(s) on the server.');
|
||||
}
|
||||
$hasDatabaseUser = $this->service->server->databaseUsers()->exists();
|
||||
if ($hasDatabaseUser) {
|
||||
$fail('You have database user(s) on the server.');
|
||||
}
|
||||
$hasRunningBackup = $this->service->server->backups()
|
||||
->where('status', BackupStatus::RUNNING)
|
||||
->exists();
|
||||
if ($hasRunningBackup) {
|
||||
$fail('You have database backup(s) on the server.');
|
||||
}
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function uninstall(): void
|
||||
{
|
||||
$version = $this->service->version;
|
||||
$command = $this->getScript($this->service->name.'/uninstall.sh');
|
||||
$this->service->server->ssh()->exec($command, 'uninstall-'.$this->service->name.'-'.$version);
|
||||
$this->service->server->os()->cleanup();
|
||||
}
|
||||
|
||||
public function create(string $name): void
|
||||
{
|
||||
$this->server->ssh()->exec(
|
||||
$this->service->server->ssh()->exec(
|
||||
$this->getScript($this->getScriptsDir().'/create.sh', [
|
||||
'name' => $name,
|
||||
]),
|
||||
@ -45,7 +83,7 @@ public function create(string $name): void
|
||||
|
||||
public function delete(string $name): void
|
||||
{
|
||||
$this->server->ssh()->exec(
|
||||
$this->service->server->ssh()->exec(
|
||||
$this->getScript($this->getScriptsDir().'/delete.sh', [
|
||||
'name' => $name,
|
||||
]),
|
||||
@ -55,7 +93,7 @@ public function delete(string $name): void
|
||||
|
||||
public function createUser(string $username, string $password, string $host): void
|
||||
{
|
||||
$this->server->ssh()->exec(
|
||||
$this->service->server->ssh()->exec(
|
||||
$this->getScript($this->getScriptsDir().'/create-user.sh', [
|
||||
'username' => $username,
|
||||
'password' => $password,
|
||||
@ -67,7 +105,7 @@ public function createUser(string $username, string $password, string $host): vo
|
||||
|
||||
public function deleteUser(string $username, string $host): void
|
||||
{
|
||||
$this->server->ssh()->exec(
|
||||
$this->service->server->ssh()->exec(
|
||||
$this->getScript($this->getScriptsDir().'/delete-user.sh', [
|
||||
'username' => $username,
|
||||
'host' => $host,
|
||||
@ -78,7 +116,7 @@ public function deleteUser(string $username, string $host): void
|
||||
|
||||
public function link(string $username, string $host, array $databases): void
|
||||
{
|
||||
$ssh = $this->server->ssh();
|
||||
$ssh = $this->service->server->ssh();
|
||||
|
||||
foreach ($databases as $database) {
|
||||
$ssh->exec(
|
||||
@ -94,7 +132,7 @@ public function link(string $username, string $host, array $databases): void
|
||||
|
||||
public function unlink(string $username, string $host): void
|
||||
{
|
||||
$this->server->ssh()->exec(
|
||||
$this->service->server->ssh()->exec(
|
||||
$this->getScript($this->getScriptsDir().'/unlink.sh', [
|
||||
'username' => $username,
|
||||
'host' => $host,
|
||||
@ -106,7 +144,7 @@ public function unlink(string $username, string $host): void
|
||||
public function runBackup(BackupFile $backupFile): void
|
||||
{
|
||||
// backup
|
||||
$this->server->ssh()->exec(
|
||||
$this->service->server->ssh()->exec(
|
||||
$this->getScript($this->getScriptsDir().'/backup.sh', [
|
||||
'file' => $backupFile->name,
|
||||
'database' => $backupFile->backup->database->name,
|
||||
@ -115,13 +153,13 @@ public function runBackup(BackupFile $backupFile): void
|
||||
);
|
||||
|
||||
// upload to storage
|
||||
$upload = $backupFile->backup->storage->provider()->ssh($this->server)->upload(
|
||||
$upload = $backupFile->backup->storage->provider()->ssh($this->service->server)->upload(
|
||||
$backupFile->path(),
|
||||
$backupFile->storagePath(),
|
||||
);
|
||||
|
||||
// cleanup
|
||||
$this->server->ssh()->exec('rm '.$backupFile->name.'.zip');
|
||||
$this->service->server->ssh()->exec('rm '.$backupFile->name.'.zip');
|
||||
|
||||
$backupFile->size = $upload['size'];
|
||||
$backupFile->save();
|
||||
@ -130,12 +168,12 @@ public function runBackup(BackupFile $backupFile): void
|
||||
public function restoreBackup(BackupFile $backupFile, string $database): void
|
||||
{
|
||||
// download
|
||||
$backupFile->backup->storage->provider()->ssh($this->server)->download(
|
||||
$backupFile->backup->storage->provider()->ssh($this->service->server)->download(
|
||||
$backupFile->storagePath(),
|
||||
$backupFile->name.'.zip',
|
||||
);
|
||||
|
||||
$this->server->ssh()->exec(
|
||||
$this->service->server->ssh()->exec(
|
||||
$this->getScript($this->getScriptsDir().'/restore.sh', [
|
||||
'database' => $database,
|
||||
'file' => $backupFile->name,
|
||||
|
@ -9,4 +9,6 @@ sudo DEBIAN_FRONTEND=noninteractive apt-get update
|
||||
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install mariadb-server mariadb-backup -y
|
||||
|
||||
sudo systemctl unmask mysql.service
|
||||
|
||||
sudo service mysql start
|
||||
|
@ -9,4 +9,6 @@ sudo DEBIAN_FRONTEND=noninteractive apt-get update
|
||||
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install mariadb-server mariadb-backup -y
|
||||
|
||||
sudo systemctl unmask mysql.service
|
||||
|
||||
sudo service mysql start
|
||||
|
9
app/SSH/Services/Database/scripts/mariadb/uninstall.sh
Normal file
9
app/SSH/Services/Database/scripts/mariadb/uninstall.sh
Normal file
@ -0,0 +1,9 @@
|
||||
sudo service mysql stop
|
||||
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get remove mariadb-server mariadb-backup -y
|
||||
|
||||
sudo rm -rf /etc/mysql
|
||||
sudo rm -rf /var/lib/mysql
|
||||
sudo rm -rf /var/log/mysql
|
||||
sudo rm -rf /var/run/mysqld
|
||||
sudo rm -rf /var/run/mysqld/mysqld.sock
|
@ -1,5 +1,7 @@
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install mysql-server -y
|
||||
|
||||
sudo systemctl unmask mysql.service
|
||||
|
||||
sudo service mysql enable
|
||||
|
||||
sudo service mysql start
|
||||
|
@ -6,6 +6,8 @@ sudo DEBIAN_FRONTEND=noninteractive apt-get update
|
||||
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install mysql-server -y
|
||||
|
||||
sudo systemctl unmask mysql.service
|
||||
|
||||
sudo service mysql enable
|
||||
|
||||
sudo service mysql start
|
||||
|
9
app/SSH/Services/Database/scripts/mysql/uninstall.sh
Executable file
9
app/SSH/Services/Database/scripts/mysql/uninstall.sh
Executable file
@ -0,0 +1,9 @@
|
||||
sudo service mysql stop
|
||||
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get remove mysql-server -y
|
||||
|
||||
sudo rm -rf /etc/mysql
|
||||
sudo rm -rf /var/lib/mysql
|
||||
sudo rm -rf /var/log/mysql
|
||||
sudo rm -rf /var/run/mysqld
|
||||
sudo rm -rf /var/run/mysqld/mysqld.sock
|
11
app/SSH/Services/Database/scripts/postgresql/uninstall.sh
Normal file
11
app/SSH/Services/Database/scripts/postgresql/uninstall.sh
Normal file
@ -0,0 +1,11 @@
|
||||
sudo service postgresql stop
|
||||
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get remove postgresql-* -y
|
||||
|
||||
sudo rm -rf /etc/postgresql
|
||||
sudo rm -rf /var/lib/postgresql
|
||||
sudo rm -rf /var/log/postgresql
|
||||
sudo rm -rf /var/run/postgresql
|
||||
sudo rm -rf /var/run/postgresql/postmaster.pid
|
||||
sudo rm -rf /var/run/postgresql/.s.PGSQL.5432
|
||||
sudo rm -rf /var/run/postgresql/.s.PGSQL.5432.lock
|
@ -2,15 +2,8 @@
|
||||
|
||||
namespace App\SSH\Services\Firewall;
|
||||
|
||||
use App\Models\Service;
|
||||
use App\SSH\Services\ServiceInterface;
|
||||
use App\SSH\Services\AbstractService;
|
||||
|
||||
abstract class AbstractFirewall implements Firewall, ServiceInterface
|
||||
abstract class AbstractFirewall extends AbstractService implements Firewall
|
||||
{
|
||||
protected Service $service;
|
||||
|
||||
public function __construct(Service $service)
|
||||
{
|
||||
$this->service = $service;
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,12 @@ public function install(): void
|
||||
$this->getScript('ufw/install-ufw.sh'),
|
||||
'install-ufw'
|
||||
);
|
||||
$this->service->server->os()->cleanup();
|
||||
}
|
||||
|
||||
public function uninstall(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
public function addRule(string $type, string $protocol, int $port, string $source, ?string $mask): void
|
||||
|
@ -3,20 +3,43 @@
|
||||
namespace App\SSH\Services\PHP;
|
||||
|
||||
use App\Exceptions\SSHCommandError;
|
||||
use App\Models\Service;
|
||||
use App\SSH\HasScripts;
|
||||
use App\SSH\Services\ServiceInterface;
|
||||
use App\SSH\Services\AbstractService;
|
||||
use Closure;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class PHP implements ServiceInterface
|
||||
class PHP extends AbstractService
|
||||
{
|
||||
use HasScripts;
|
||||
|
||||
protected Service $service;
|
||||
|
||||
public function __construct(Service $service)
|
||||
public function creationRules(array $input): array
|
||||
{
|
||||
$this->service = $service;
|
||||
return [
|
||||
'version' => [
|
||||
'required',
|
||||
Rule::in(config('core.php_versions')),
|
||||
Rule::unique('services', 'version')
|
||||
->where('type', 'php')
|
||||
->where('server_id', $this->service->server_id),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function deletionRules(): array
|
||||
{
|
||||
return [
|
||||
'service' => [
|
||||
function (string $attribute, mixed $value, Closure $fail) {
|
||||
$hasSite = $this->service->server->sites()
|
||||
->where('php_version', $this->service->version)
|
||||
->exists();
|
||||
if ($hasSite) {
|
||||
$fail('Some sites are using this PHP version.');
|
||||
}
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function install(): void
|
||||
@ -29,6 +52,7 @@ public function install(): void
|
||||
]),
|
||||
'install-php-'.$this->service->version
|
||||
);
|
||||
$this->service->server->os()->cleanup();
|
||||
}
|
||||
|
||||
public function uninstall(): void
|
||||
@ -39,6 +63,7 @@ public function uninstall(): void
|
||||
]),
|
||||
'uninstall-php-'.$this->service->version
|
||||
);
|
||||
$this->service->server->os()->cleanup();
|
||||
}
|
||||
|
||||
public function setDefaultCli(): void
|
||||
|
@ -2,15 +2,37 @@
|
||||
|
||||
namespace App\SSH\Services\ProcessManager;
|
||||
|
||||
use App\Models\Service;
|
||||
use App\SSH\Services\ServiceInterface;
|
||||
use App\SSH\Services\AbstractService;
|
||||
use Closure;
|
||||
|
||||
abstract class AbstractProcessManager implements ProcessManager, ServiceInterface
|
||||
abstract class AbstractProcessManager extends AbstractService implements ProcessManager
|
||||
{
|
||||
protected Service $service;
|
||||
|
||||
public function __construct(Service $service)
|
||||
public function creationRules(array $input): array
|
||||
{
|
||||
$this->service = $service;
|
||||
return [
|
||||
'type' => [
|
||||
'required',
|
||||
function (string $attribute, mixed $value, Closure $fail) {
|
||||
$processManagerExists = $this->service->server->processManager();
|
||||
if ($processManagerExists) {
|
||||
$fail('You already have a process manager service on the server.');
|
||||
}
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function deletionRules(): array
|
||||
{
|
||||
return [
|
||||
'service' => [
|
||||
function (string $attribute, mixed $value, Closure $fail) {
|
||||
$hasQueue = $this->service->server->queues()->exists();
|
||||
if ($hasQueue) {
|
||||
$fail('You have queue(s) on the server.');
|
||||
}
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,18 @@ public function install(): void
|
||||
$this->getScript('supervisor/install-supervisor.sh'),
|
||||
'install-supervisor'
|
||||
);
|
||||
$this->service->server->os()->cleanup();
|
||||
}
|
||||
|
||||
public function uninstall(): void
|
||||
{
|
||||
$this->service->server->ssh()->exec(
|
||||
$this->getScript('supervisor/uninstall-supervisor.sh'),
|
||||
'uninstall-supervisor'
|
||||
);
|
||||
$status = $this->service->server->systemd()->status($this->service->unit);
|
||||
$this->service->validateInstall($status);
|
||||
$this->service->server->os()->cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,8 @@
|
||||
sudo service supervisor stop
|
||||
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get remove supervisor -y
|
||||
|
||||
sudo rm -rf /etc/supervisor
|
||||
sudo rm -rf /var/log/supervisor
|
||||
sudo rm -rf /var/run/supervisor
|
||||
sudo rm -rf /var/run/supervisor/supervisor.sock
|
@ -2,16 +2,27 @@
|
||||
|
||||
namespace App\SSH\Services\Redis;
|
||||
|
||||
use App\Models\Service;
|
||||
use App\SSH\HasScripts;
|
||||
use App\SSH\Services\ServiceInterface;
|
||||
use App\SSH\Services\AbstractService;
|
||||
use Closure;
|
||||
|
||||
class Redis implements ServiceInterface
|
||||
class Redis extends AbstractService
|
||||
{
|
||||
use HasScripts;
|
||||
|
||||
public function __construct(protected Service $service)
|
||||
public function creationRules(array $input): array
|
||||
{
|
||||
return [
|
||||
'type' => [
|
||||
'required',
|
||||
function (string $attribute, mixed $value, Closure $fail) {
|
||||
$redisExists = $this->service->server->memoryDatabase();
|
||||
if ($redisExists) {
|
||||
$fail('You already have a Redis service on the server.');
|
||||
}
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function install(): void
|
||||
@ -20,5 +31,17 @@ public function install(): void
|
||||
$this->getScript('install.sh'),
|
||||
'install-redis'
|
||||
);
|
||||
$status = $this->service->server->systemd()->status($this->service->unit);
|
||||
$this->service->validateInstall($status);
|
||||
$this->service->server->os()->cleanup();
|
||||
}
|
||||
|
||||
public function uninstall(): void
|
||||
{
|
||||
$this->service->server->ssh()->exec(
|
||||
$this->getScript('uninstall.sh'),
|
||||
'uninstall-redis'
|
||||
);
|
||||
$this->service->server->os()->cleanup();
|
||||
}
|
||||
}
|
||||
|
15
app/SSH/Services/Redis/scripts/uninstall.sh
Executable file
15
app/SSH/Services/Redis/scripts/uninstall.sh
Executable file
@ -0,0 +1,15 @@
|
||||
sudo service redis stop
|
||||
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get remove redis-server -y
|
||||
|
||||
sudo rm -rf /etc/redis
|
||||
sudo rm -rf /var/lib/redis
|
||||
sudo rm -rf /var/log/redis
|
||||
sudo rm -rf /var/run/redis
|
||||
sudo rm -rf /var/run/redis/redis-server.pid
|
||||
sudo rm -rf /var/run/redis/redis-server.sock
|
||||
sudo rm -rf /var/run/redis/redis-server.sock
|
||||
|
||||
sudo DEBIAN_FRONTEND=noninteractive sudo apt-get autoremove -y
|
||||
|
||||
sudo DEBIAN_FRONTEND=noninteractive sudo apt-get autoclean -y
|
@ -4,5 +4,15 @@
|
||||
|
||||
interface ServiceInterface
|
||||
{
|
||||
public function creationRules(array $input): array;
|
||||
|
||||
public function creationData(array $input): array;
|
||||
|
||||
public function deletionRules(): array;
|
||||
|
||||
public function data(): array;
|
||||
|
||||
public function install(): void;
|
||||
|
||||
public function uninstall(): void;
|
||||
}
|
||||
|
87
app/SSH/Services/VitoAgent/VitoAgent.php
Normal file
87
app/SSH/Services/VitoAgent/VitoAgent.php
Normal file
@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace App\SSH\Services\VitoAgent;
|
||||
|
||||
use App\Models\Metric;
|
||||
use App\SSH\HasScripts;
|
||||
use App\SSH\Services\AbstractService;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
|
||||
class VitoAgent extends AbstractService
|
||||
{
|
||||
use HasScripts;
|
||||
|
||||
const TAGS_URL = 'https://api.github.com/repos/vitodeploy/agent/tags';
|
||||
|
||||
const DOWNLOAD_URL = 'https://github.com/vitodeploy/agent/releases/download/%s';
|
||||
|
||||
public function creationRules(array $input): array
|
||||
{
|
||||
return [
|
||||
'type' => [
|
||||
Rule::unique('services', 'type')->where('server_id', $this->service->server_id),
|
||||
],
|
||||
'version' => [
|
||||
'required',
|
||||
Rule::in(['latest']),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function creationData(array $input): array
|
||||
{
|
||||
return [
|
||||
'url' => '',
|
||||
'secret' => Uuid::uuid4()->toString(),
|
||||
'data_retention' => 10,
|
||||
];
|
||||
}
|
||||
|
||||
public function data(): array
|
||||
{
|
||||
return [
|
||||
'url' => $this->service->type_data['url'] ?? null,
|
||||
'secret' => $this->service->type_data['secret'] ?? null,
|
||||
'data_retention' => $this->service->type_data['data_retention'] ?? 10,
|
||||
];
|
||||
}
|
||||
|
||||
public function install(): void
|
||||
{
|
||||
$tags = Http::get(self::TAGS_URL)->json();
|
||||
if (empty($tags)) {
|
||||
throw new \Exception('Failed to fetch tags');
|
||||
}
|
||||
$this->service->version = $tags[0]['name'];
|
||||
$this->service->save();
|
||||
$downloadUrl = sprintf(self::DOWNLOAD_URL, $this->service->version);
|
||||
|
||||
$data = $this->data();
|
||||
$data['url'] = route('api.servers.agent', [$this->service->server, $this->service->id]);
|
||||
$this->service->type_data = $data;
|
||||
$this->service->save();
|
||||
$this->service->refresh();
|
||||
|
||||
$this->service->server->ssh()->exec(
|
||||
$this->getScript('install.sh', [
|
||||
'download_url' => $downloadUrl,
|
||||
'config_url' => $this->data()['url'],
|
||||
'config_secret' => $this->data()['secret'],
|
||||
]),
|
||||
'install-vito-agent'
|
||||
);
|
||||
$status = $this->service->server->systemd()->status($this->service->unit);
|
||||
$this->service->validateInstall($status);
|
||||
}
|
||||
|
||||
public function uninstall(): void
|
||||
{
|
||||
$this->service->server->ssh()->exec(
|
||||
$this->getScript('uninstall.sh'),
|
||||
'uninstall-vito-agent'
|
||||
);
|
||||
Metric::where('server_id', $this->service->server_id)->delete();
|
||||
}
|
||||
}
|
53
app/SSH/Services/VitoAgent/scripts/install.sh
Normal file
53
app/SSH/Services/VitoAgent/scripts/install.sh
Normal file
@ -0,0 +1,53 @@
|
||||
arch=$(uname -m)
|
||||
|
||||
if [ "$arch" == "x86_64" ]; then
|
||||
executable="vitoagent-linux-amd64"
|
||||
elif [ "$arch" == "i686" ]; then
|
||||
executable="vitoagent-linux-amd"
|
||||
elif [ "$arch" == "armv7l" ]; then
|
||||
executable="vitoagent-linux-arm"
|
||||
elif [ "$arch" == "aarch64" ]; then
|
||||
executable="vitoagent-linux-arm64"
|
||||
else
|
||||
executable="vitoagent-linux-amd64"
|
||||
fi
|
||||
|
||||
wget __download_url__/$executable
|
||||
|
||||
chmod +x ./$executable
|
||||
|
||||
sudo mv ./$executable /usr/local/bin/vito-agent
|
||||
|
||||
# create service
|
||||
export VITO_AGENT_SERVICE="
|
||||
[Unit]
|
||||
Description=Vito Agent
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
ExecStart=/usr/local/bin/vito-agent
|
||||
Restart=on-failure
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
"
|
||||
echo "${VITO_AGENT_SERVICE}" | sudo tee /etc/systemd/system/vito-agent.service
|
||||
|
||||
sudo mkdir -p /etc/vito-agent
|
||||
|
||||
export VITO_AGENT_CONFIG="
|
||||
{
|
||||
\"url\": \"__config_url__\",
|
||||
\"secret\": \"__config_secret__\"
|
||||
}
|
||||
"
|
||||
|
||||
echo "${VITO_AGENT_CONFIG}" | sudo tee /etc/vito-agent/config.json
|
||||
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable vito-agent
|
||||
sudo systemctl start vito-agent
|
||||
|
||||
echo "Vito Agent installed successfully"
|
13
app/SSH/Services/VitoAgent/scripts/uninstall.sh
Normal file
13
app/SSH/Services/VitoAgent/scripts/uninstall.sh
Normal file
@ -0,0 +1,13 @@
|
||||
sudo service vito-agent stop
|
||||
|
||||
sudo systemctl disable vito-agent
|
||||
|
||||
sudo rm -f /usr/local/bin/vito-agent
|
||||
|
||||
sudo rm -f /etc/systemd/system/vito-agent.service
|
||||
|
||||
sudo rm -rf /etc/vito-agent
|
||||
|
||||
sudo systemctl daemon-reload
|
||||
|
||||
echo "Vito Agent uninstalled successfully"
|
@ -2,12 +2,8 @@
|
||||
|
||||
namespace App\SSH\Services\Webserver;
|
||||
|
||||
use App\Models\Service;
|
||||
use App\SSH\Services\ServiceInterface;
|
||||
use App\SSH\Services\AbstractService;
|
||||
|
||||
abstract class AbstractWebserver implements ServiceInterface, Webserver
|
||||
abstract class AbstractWebserver extends AbstractService implements Webserver
|
||||
{
|
||||
public function __construct(protected Service $service)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
use App\Models\Site;
|
||||
use App\Models\Ssl;
|
||||
use App\SSH\HasScripts;
|
||||
use Closure;
|
||||
use Illuminate\Support\Str;
|
||||
use Throwable;
|
||||
|
||||
@ -23,6 +24,31 @@ public function install(): void
|
||||
]),
|
||||
'install-nginx'
|
||||
);
|
||||
$this->service->server->os()->cleanup();
|
||||
}
|
||||
|
||||
public function deletionRules(): array
|
||||
{
|
||||
return [
|
||||
'service' => [
|
||||
function (string $attribute, mixed $value, Closure $fail) {
|
||||
$hasSite = $this->service->server->sites()
|
||||
->exists();
|
||||
if ($hasSite) {
|
||||
$fail('Cannot uninstall webserver while you have websites using it.');
|
||||
}
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function uninstall(): void
|
||||
{
|
||||
$this->service->server->ssh()->exec(
|
||||
$this->getScript('nginx/uninstall-nginx.sh'),
|
||||
'uninstall-nginx'
|
||||
);
|
||||
$this->service->server->os()->cleanup();
|
||||
}
|
||||
|
||||
public function createVHost(Site $site): void
|
||||
@ -44,7 +70,7 @@ public function updateVHost(Site $site, bool $noSSL = false, ?string $vhost = nu
|
||||
$this->getScript('nginx/update-vhost.sh', [
|
||||
'domain' => $site->domain,
|
||||
'path' => $site->path,
|
||||
'vhost' => $this->generateVhost($site, $noSSL),
|
||||
'vhost' => $vhost ?? $this->generateVhost($site, $noSSL),
|
||||
]),
|
||||
'update-vhost',
|
||||
$site->id
|
||||
|
12
app/SSH/Services/Webserver/scripts/nginx/uninstall-nginx.sh
Executable file
12
app/SSH/Services/Webserver/scripts/nginx/uninstall-nginx.sh
Executable file
@ -0,0 +1,12 @@
|
||||
sudo service nginx stop
|
||||
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get purge nginx nginx-common nginx-full -y
|
||||
|
||||
sudo rm -rf /etc/nginx
|
||||
sudo rm -rf /var/log/nginx
|
||||
sudo rm -rf /var/lib/nginx
|
||||
sudo rm -rf /var/cache/nginx
|
||||
sudo rm -rf /usr/share/nginx
|
||||
sudo rm -rf /etc/systemd/system/nginx.service
|
||||
|
||||
sudo systemctl daemon-reload
|
@ -4,6 +4,7 @@
|
||||
|
||||
use App\Enums\ServiceStatus;
|
||||
use App\Models\Server;
|
||||
use App\SSH\Services\PHP\PHP;
|
||||
|
||||
abstract class AbstractType implements ServerType
|
||||
{
|
||||
@ -31,7 +32,9 @@ public function install(): void
|
||||
$service->update(['status' => ServiceStatus::READY]);
|
||||
if ($service->type == 'php') {
|
||||
$this->progress($currentProgress, 'installing-composer');
|
||||
$service->handler()->installComposer();
|
||||
/** @var PHP $handler */
|
||||
$handler = $service->handler();
|
||||
$handler->installComposer();
|
||||
}
|
||||
}
|
||||
$this->progress(100, 'finishing');
|
||||
|
@ -13,7 +13,7 @@ public function createRules(array $input): array
|
||||
],
|
||||
'php' => [
|
||||
'required',
|
||||
'in:'.implode(',', config('core.php_versions')),
|
||||
'in:none,'.implode(',', config('core.php_versions')),
|
||||
],
|
||||
'database' => [
|
||||
'required',
|
||||
|
@ -34,3 +34,12 @@ function vito_version(): string
|
||||
{
|
||||
return exec('git describe --tags');
|
||||
}
|
||||
|
||||
function convert_time_format($string): string
|
||||
{
|
||||
$string = preg_replace('/(\d+)m/', '$1 minutes', $string);
|
||||
$string = preg_replace('/(\d+)s/', '$1 seconds', $string);
|
||||
$string = preg_replace('/(\d+)d/', '$1 days', $string);
|
||||
|
||||
return preg_replace('/(\d+)h/', '$1 hours', $string);
|
||||
}
|
||||
|
223
config/core.php
223
config/core.php
@ -1,35 +1,5 @@
|
||||
<?php
|
||||
|
||||
use App\Enums\OperatingSystem;
|
||||
use App\Enums\StorageProvider;
|
||||
use App\NotificationChannels\Discord;
|
||||
use App\NotificationChannels\Email;
|
||||
use App\NotificationChannels\Slack;
|
||||
use App\NotificationChannels\Telegram;
|
||||
use App\ServerProviders\AWS;
|
||||
use App\ServerProviders\DigitalOcean;
|
||||
use App\ServerProviders\Hetzner;
|
||||
use App\ServerProviders\Linode;
|
||||
use App\ServerProviders\Vultr;
|
||||
use App\SiteTypes\Laravel;
|
||||
use App\SiteTypes\PHPBlank;
|
||||
use App\SiteTypes\PHPMyAdmin;
|
||||
use App\SiteTypes\PHPSite;
|
||||
use App\SiteTypes\Wordpress;
|
||||
use App\SourceControlProviders\Bitbucket;
|
||||
use App\SourceControlProviders\Github;
|
||||
use App\SourceControlProviders\Gitlab;
|
||||
use App\SSH\Services\Database\Mariadb;
|
||||
use App\SSH\Services\Database\Mysql;
|
||||
use App\SSH\Services\Database\Postgresql;
|
||||
use App\SSH\Services\Firewall\Ufw;
|
||||
use App\SSH\Services\PHP\PHP;
|
||||
use App\SSH\Services\ProcessManager\Supervisor;
|
||||
use App\SSH\Services\Redis\Redis;
|
||||
use App\SSH\Services\Webserver\Nginx;
|
||||
use App\StorageProviders\Dropbox;
|
||||
use App\StorageProviders\FTP;
|
||||
|
||||
return [
|
||||
/*
|
||||
* SSH
|
||||
@ -44,12 +14,12 @@
|
||||
* General
|
||||
*/
|
||||
'operating_systems' => [
|
||||
OperatingSystem::UBUNTU20,
|
||||
OperatingSystem::UBUNTU22,
|
||||
\App\Enums\OperatingSystem::UBUNTU20,
|
||||
\App\Enums\OperatingSystem::UBUNTU22,
|
||||
],
|
||||
'webservers' => ['none', 'nginx'],
|
||||
'php_versions' => [
|
||||
'none',
|
||||
// 'none',
|
||||
// '5.6',
|
||||
'7.0',
|
||||
'7.1',
|
||||
@ -126,117 +96,102 @@
|
||||
],
|
||||
'server_providers_class' => [
|
||||
\App\Enums\ServerProvider::CUSTOM => \App\ServerProviders\Custom::class,
|
||||
\App\Enums\ServerProvider::AWS => AWS::class,
|
||||
\App\Enums\ServerProvider::LINODE => Linode::class,
|
||||
\App\Enums\ServerProvider::DIGITALOCEAN => DigitalOcean::class,
|
||||
\App\Enums\ServerProvider::VULTR => Vultr::class,
|
||||
\App\Enums\ServerProvider::HETZNER => Hetzner::class,
|
||||
\App\Enums\ServerProvider::AWS => \App\ServerProviders\AWS::class,
|
||||
\App\Enums\ServerProvider::LINODE => \App\ServerProviders\Linode::class,
|
||||
\App\Enums\ServerProvider::DIGITALOCEAN => \App\ServerProviders\DigitalOcean::class,
|
||||
\App\Enums\ServerProvider::VULTR => \App\ServerProviders\Vultr::class,
|
||||
\App\Enums\ServerProvider::HETZNER => \App\ServerProviders\Hetzner::class,
|
||||
],
|
||||
'server_providers_default_user' => [
|
||||
'custom' => [
|
||||
'ubuntu_18' => 'root',
|
||||
'ubuntu_20' => 'root',
|
||||
'ubuntu_22' => 'root',
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => 'root',
|
||||
\App\Enums\OperatingSystem::UBUNTU22 => 'root',
|
||||
],
|
||||
'aws' => [
|
||||
'ubuntu_18' => 'ubuntu',
|
||||
'ubuntu_20' => 'ubuntu',
|
||||
'ubuntu_22' => 'ubuntu',
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => 'ubuntu',
|
||||
\App\Enums\OperatingSystem::UBUNTU22 => 'ubuntu',
|
||||
],
|
||||
'linode' => [
|
||||
'ubuntu_18' => 'root',
|
||||
'ubuntu_20' => 'root',
|
||||
'ubuntu_22' => 'root',
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => 'root',
|
||||
\App\Enums\OperatingSystem::UBUNTU22 => 'root',
|
||||
],
|
||||
'digitalocean' => [
|
||||
'ubuntu_18' => 'root',
|
||||
'ubuntu_20' => 'root',
|
||||
'ubuntu_22' => 'root',
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => 'root',
|
||||
\App\Enums\OperatingSystem::UBUNTU22 => 'root',
|
||||
],
|
||||
'vultr' => [
|
||||
'ubuntu_18' => 'root',
|
||||
'ubuntu_20' => 'root',
|
||||
'ubuntu_22' => 'root',
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => 'root',
|
||||
\App\Enums\OperatingSystem::UBUNTU22 => 'root',
|
||||
],
|
||||
'hetzner' => [
|
||||
'ubuntu_18' => 'root',
|
||||
'ubuntu_20' => 'root',
|
||||
'ubuntu_22' => 'root',
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => 'root',
|
||||
\App\Enums\OperatingSystem::UBUNTU22 => 'root',
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
* Service
|
||||
*/
|
||||
'service_handlers' => [
|
||||
'nginx' => Nginx::class,
|
||||
'mysql' => Mysql::class,
|
||||
'mariadb' => Mariadb::class,
|
||||
'postgresql' => Postgresql::class,
|
||||
'redis' => Redis::class,
|
||||
'php' => PHP::class,
|
||||
'ufw' => Ufw::class,
|
||||
'supervisor' => Supervisor::class,
|
||||
'service_types' => [
|
||||
'nginx' => 'webserver',
|
||||
'mysql' => 'database',
|
||||
'mariadb' => 'database',
|
||||
'postgresql' => 'database',
|
||||
'redis' => 'memory_database',
|
||||
'php' => 'php',
|
||||
'ufw' => 'firewall',
|
||||
'supervisor' => 'process_manager',
|
||||
'vito-agent' => 'monitoring',
|
||||
],
|
||||
'add_on_services' => [
|
||||
// add-on services
|
||||
'service_handlers' => [
|
||||
'nginx' => \App\SSH\Services\Webserver\Nginx::class,
|
||||
'mysql' => \App\SSH\Services\Database\Mysql::class,
|
||||
'mariadb' => \App\SSH\Services\Database\Mariadb::class,
|
||||
'postgresql' => \App\SSH\Services\Database\Postgresql::class,
|
||||
'redis' => \App\SSH\Services\Redis\Redis::class,
|
||||
'php' => \App\SSH\Services\PHP\PHP::class,
|
||||
'ufw' => \App\SSH\Services\Firewall\Ufw::class,
|
||||
'supervisor' => \App\SSH\Services\ProcessManager\Supervisor::class,
|
||||
'vito-agent' => \App\SSH\Services\VitoAgent\VitoAgent::class,
|
||||
],
|
||||
'service_units' => [
|
||||
'nginx' => [
|
||||
'ubuntu_18' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => [
|
||||
'latest' => 'nginx',
|
||||
],
|
||||
'ubuntu_20' => [
|
||||
'latest' => 'nginx',
|
||||
],
|
||||
'ubuntu_22' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU22 => [
|
||||
'latest' => 'nginx',
|
||||
],
|
||||
],
|
||||
'mysql' => [
|
||||
'ubuntu_18' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => [
|
||||
'5.7' => 'mysql',
|
||||
'8.0' => 'mysql',
|
||||
],
|
||||
'ubuntu_20' => [
|
||||
'5.7' => 'mysql',
|
||||
'8.0' => 'mysql',
|
||||
],
|
||||
'ubuntu_22' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU22 => [
|
||||
'5.7' => 'mysql',
|
||||
'8.0' => 'mysql',
|
||||
],
|
||||
],
|
||||
'mariadb' => [
|
||||
'ubuntu_18' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => [
|
||||
'10.3' => 'mariadb',
|
||||
'10.4' => 'mariadb',
|
||||
],
|
||||
'ubuntu_20' => [
|
||||
'10.3' => 'mariadb',
|
||||
'10.4' => 'mariadb',
|
||||
],
|
||||
'ubuntu_22' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU22 => [
|
||||
'10.3' => 'mariadb',
|
||||
'10.4' => 'mariadb',
|
||||
],
|
||||
],
|
||||
'postgresql' => [
|
||||
'ubuntu_18' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => [
|
||||
'12' => 'postgresql',
|
||||
'13' => 'postgresql',
|
||||
'14' => 'postgresql',
|
||||
'15' => 'postgresql',
|
||||
'16' => 'postgresql',
|
||||
],
|
||||
'ubuntu_20' => [
|
||||
'12' => 'postgresql',
|
||||
'13' => 'postgresql',
|
||||
'14' => 'postgresql',
|
||||
'15' => 'postgresql',
|
||||
'16' => 'postgresql',
|
||||
],
|
||||
'ubuntu_22' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU22 => [
|
||||
'12' => 'postgresql',
|
||||
'13' => 'postgresql',
|
||||
'14' => 'postgresql',
|
||||
@ -245,19 +200,7 @@
|
||||
],
|
||||
],
|
||||
'php' => [
|
||||
'ubuntu_18' => [
|
||||
'5.6' => 'php5.6-fpm',
|
||||
'7.0' => 'php7.0-fpm',
|
||||
'7.1' => 'php7.1-fpm',
|
||||
'7.2' => 'php7.2-fpm',
|
||||
'7.3' => 'php7.3-fpm',
|
||||
'7.4' => 'php7.4-fpm',
|
||||
'8.0' => 'php8.0-fpm',
|
||||
'8.1' => 'php8.1-fpm',
|
||||
'8.2' => 'php8.2-fpm',
|
||||
'8.3' => 'php8.3-fpm',
|
||||
],
|
||||
'ubuntu_20' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => [
|
||||
'5.6' => 'php5.6-fpm',
|
||||
'7.0' => 'php7.0-fpm',
|
||||
'7.1' => 'php7.1-fpm',
|
||||
@ -268,7 +211,7 @@
|
||||
'8.1' => 'php8.1-fpm',
|
||||
'8.3' => 'php8.3-fpm',
|
||||
],
|
||||
'ubuntu_22' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU22 => [
|
||||
'5.6' => 'php5.6-fpm',
|
||||
'7.0' => 'php7.0-fpm',
|
||||
'7.1' => 'php7.1-fpm',
|
||||
@ -282,36 +225,35 @@
|
||||
],
|
||||
],
|
||||
'redis' => [
|
||||
'ubuntu_18' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => [
|
||||
'latest' => 'redis',
|
||||
],
|
||||
'ubuntu_20' => [
|
||||
'latest' => 'redis',
|
||||
],
|
||||
'ubuntu_22' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU22 => [
|
||||
'latest' => 'redis',
|
||||
],
|
||||
],
|
||||
'supervisor' => [
|
||||
'ubuntu_18' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => [
|
||||
'latest' => 'supervisor',
|
||||
],
|
||||
'ubuntu_20' => [
|
||||
'latest' => 'supervisor',
|
||||
],
|
||||
'ubuntu_22' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU22 => [
|
||||
'latest' => 'supervisor',
|
||||
],
|
||||
],
|
||||
'ufw' => [
|
||||
'ubuntu_18' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => [
|
||||
'latest' => 'ufw',
|
||||
],
|
||||
'ubuntu_20' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU22 => [
|
||||
'latest' => 'ufw',
|
||||
],
|
||||
'ubuntu_22' => [
|
||||
'latest' => 'ufw',
|
||||
],
|
||||
'vito-agent' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => [
|
||||
'latest' => 'vito-agent',
|
||||
],
|
||||
\App\Enums\OperatingSystem::UBUNTU22 => [
|
||||
'latest' => 'vito-agent',
|
||||
],
|
||||
],
|
||||
],
|
||||
@ -327,11 +269,11 @@
|
||||
\App\Enums\SiteType::PHPMYADMIN,
|
||||
],
|
||||
'site_types_class' => [
|
||||
\App\Enums\SiteType::PHP => PHPSite::class,
|
||||
\App\Enums\SiteType::PHP_BLANK => PHPBlank::class,
|
||||
\App\Enums\SiteType::LARAVEL => Laravel::class,
|
||||
\App\Enums\SiteType::WORDPRESS => Wordpress::class,
|
||||
\App\Enums\SiteType::PHPMYADMIN => PHPMyAdmin::class,
|
||||
\App\Enums\SiteType::PHP => \App\SiteTypes\PHPSite::class,
|
||||
\App\Enums\SiteType::PHP_BLANK => \App\SiteTypes\PHPBlank::class,
|
||||
\App\Enums\SiteType::LARAVEL => \App\SiteTypes\Laravel::class,
|
||||
\App\Enums\SiteType::WORDPRESS => \App\SiteTypes\Wordpress::class,
|
||||
\App\Enums\SiteType::PHPMYADMIN => \App\SiteTypes\PHPMyAdmin::class,
|
||||
],
|
||||
|
||||
/*
|
||||
@ -343,9 +285,9 @@
|
||||
'bitbucket',
|
||||
],
|
||||
'source_control_providers_class' => [
|
||||
'github' => Github::class,
|
||||
'gitlab' => Gitlab::class,
|
||||
'bitbucket' => Bitbucket::class,
|
||||
'github' => \App\SourceControlProviders\Github::class,
|
||||
'gitlab' => \App\SourceControlProviders\Gitlab::class,
|
||||
'bitbucket' => \App\SourceControlProviders\Bitbucket::class,
|
||||
],
|
||||
|
||||
/*
|
||||
@ -403,26 +345,33 @@
|
||||
\App\Enums\NotificationChannel::TELEGRAM,
|
||||
],
|
||||
'notification_channels_providers_class' => [
|
||||
\App\Enums\NotificationChannel::SLACK => Slack::class,
|
||||
\App\Enums\NotificationChannel::DISCORD => Discord::class,
|
||||
\App\Enums\NotificationChannel::EMAIL => Email::class,
|
||||
\App\Enums\NotificationChannel::TELEGRAM => Telegram::class,
|
||||
\App\Enums\NotificationChannel::SLACK => \App\NotificationChannels\Slack::class,
|
||||
\App\Enums\NotificationChannel::DISCORD => \App\NotificationChannels\Discord::class,
|
||||
\App\Enums\NotificationChannel::EMAIL => \App\NotificationChannels\Email::class,
|
||||
\App\Enums\NotificationChannel::TELEGRAM => \App\NotificationChannels\Telegram::class,
|
||||
],
|
||||
|
||||
/*
|
||||
* storage providers
|
||||
*/
|
||||
'storage_providers' => [
|
||||
StorageProvider::DROPBOX,
|
||||
StorageProvider::FTP,
|
||||
\App\Enums\StorageProvider::DROPBOX,
|
||||
\App\Enums\StorageProvider::FTP,
|
||||
],
|
||||
'storage_providers_class' => [
|
||||
'dropbox' => Dropbox::class,
|
||||
'ftp' => FTP::class,
|
||||
'dropbox' => \App\StorageProviders\Dropbox::class,
|
||||
'ftp' => \App\StorageProviders\Ftp::class,
|
||||
],
|
||||
|
||||
'ssl_types' => [
|
||||
\App\Enums\SslType::LETSENCRYPT,
|
||||
\App\Enums\SslType::CUSTOM,
|
||||
],
|
||||
|
||||
'metrics_data_retention' => [
|
||||
7,
|
||||
14,
|
||||
30,
|
||||
90,
|
||||
],
|
||||
];
|
||||
|
22
database/factories/MetricFactory.php
Normal file
22
database/factories/MetricFactory.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
class MetricFactory extends Factory
|
||||
{
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'server_id' => 1,
|
||||
'load' => $this->faker->randomFloat(2, 0, 100),
|
||||
'memory_total' => $this->faker->randomFloat(0, 0, 100),
|
||||
'memory_used' => $this->faker->randomFloat(0, 0, 100),
|
||||
'memory_free' => $this->faker->randomFloat(0, 0, 100),
|
||||
'disk_total' => $this->faker->randomFloat(0, 0, 100),
|
||||
'disk_used' => $this->faker->randomFloat(0, 0, 100),
|
||||
'disk_free' => $this->faker->randomFloat(0, 0, 100),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('metrics', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('server_id');
|
||||
$table->decimal('load', 5, 2);
|
||||
$table->decimal('memory_total', 15, 0);
|
||||
$table->decimal('memory_used', 15, 0);
|
||||
$table->decimal('memory_free', 15, 0);
|
||||
$table->decimal('disk_total', 15, 0);
|
||||
$table->decimal('disk_used', 15, 0);
|
||||
$table->decimal('disk_free', 15, 0);
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['server_id', 'created_at']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('metrics');
|
||||
}
|
||||
};
|
32
database/seeders/MetricsSeeder.php
Normal file
32
database/seeders/MetricsSeeder.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\Metric;
|
||||
use App\Models\Service;
|
||||
use Carbon\Carbon;
|
||||
use Carbon\CarbonPeriod;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class MetricsSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
Metric::query()->delete();
|
||||
|
||||
$monitoring = Service::query()
|
||||
->where('type', 'monitoring')
|
||||
->firstOrFail();
|
||||
|
||||
$range = CarbonPeriod::create(Carbon::now()->subDays(7), '1 minute', Carbon::now());
|
||||
foreach ($range as $date) {
|
||||
Metric::factory()->create([
|
||||
'server_id' => $monitoring->server_id,
|
||||
'created_at' => $date,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,25 +1,29 @@
|
||||
ARG RELEASE=1
|
||||
|
||||
FROM ubuntu:22.04
|
||||
|
||||
ENV RELEASE_ARG=$RELEASE
|
||||
|
||||
WORKDIR /var/www/html
|
||||
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
|
||||
# upgrade
|
||||
RUN apt clean && apt update && apt update && apt upgrade -y && apt autoremove -y
|
||||
RUN apt-get clean && apt-get update && apt-get update && apt-get upgrade -y && apt-get autoremove -y
|
||||
|
||||
# requirements
|
||||
RUN apt install -y software-properties-common curl zip unzip git gcc
|
||||
RUN apt-get install -y software-properties-common curl zip unzip git gcc
|
||||
|
||||
# nginx
|
||||
RUN apt install -y nginx
|
||||
RUN apt-get install -y nginx
|
||||
|
||||
# php
|
||||
RUN apt update \
|
||||
&& apt install -y gnupg gosu curl ca-certificates zip unzip git supervisor libcap2-bin libpng-dev \
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y gnupg gosu curl ca-certificates zip unzip git supervisor libcap2-bin libpng-dev \
|
||||
python2 dnsutils librsvg2-bin fswatch wget \
|
||||
&& add-apt-repository ppa:ondrej/php -y \
|
||||
&& apt update \
|
||||
&& apt install -y php8.2 php8.2-fpm php8.2-mbstring php8.2-mcrypt php8.2-gd php8.2-xml \
|
||||
&& apt-get update \
|
||||
&& 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
|
||||
COPY docker/php.ini /etc/php/8.2/cli/conf.d/99-vito.ini
|
||||
|
||||
@ -29,7 +33,7 @@ RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local
|
||||
# app
|
||||
RUN rm -rf /var/www/html
|
||||
RUN git clone -b 1.x https://github.com/vitodeploy/vito.git /var/www/html
|
||||
RUN git checkout $(git tag -l --merged 1.x --sort=-v:refname | head -n 1)
|
||||
RUN [ "$RELEASE_ARG" = "1" ] && git checkout $(git tag -l --merged 1.x --sort=-v:refname | head -n 1) || true
|
||||
RUN composer install --no-dev --prefer-dist
|
||||
RUN chown -R www-data:www-data /var/www/html \
|
||||
&& chmod -R 755 /var/www/html/storage /var/www/html/bootstrap/cache
|
||||
|
239
package-lock.json
generated
239
package-lock.json
generated
@ -8,8 +8,10 @@
|
||||
"@tailwindcss/forms": "^0.5.2",
|
||||
"@tailwindcss/typography": "^0.5.9",
|
||||
"alpinejs": "^3.4.2",
|
||||
"apexcharts": "^3.44.2",
|
||||
"autoprefixer": "^10.4.2",
|
||||
"flowbite": "^2.3.0",
|
||||
"flowbite-datepicker": "^1.2.6",
|
||||
"htmx.org": "^1.9.10",
|
||||
"laravel-echo": "^1.15.0",
|
||||
"laravel-vite-plugin": "^0.7.2",
|
||||
@ -20,7 +22,7 @@
|
||||
"tailwindcss": "^3.1.0",
|
||||
"tippy.js": "^6.3.7",
|
||||
"toastr": "^2.1.4",
|
||||
"vite": "^4.5.2"
|
||||
"vite": "^4.5.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
@ -529,6 +531,12 @@
|
||||
"integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@yr/monotone-cubic-spline": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz",
|
||||
"integrity": "sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/alpinejs": {
|
||||
"version": "3.12.0",
|
||||
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.12.0.tgz",
|
||||
@ -557,6 +565,21 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/apexcharts": {
|
||||
"version": "3.48.0",
|
||||
"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.48.0.tgz",
|
||||
"integrity": "sha512-Lhpj1Ij6lKlrUke8gf+P+SE6uGUn+Pe1TnCJ+zqrY0YMvbqM3LMb1lY+eybbTczUyk0RmMZomlTa2NgX2EUs4Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@yr/monotone-cubic-spline": "^1.0.3",
|
||||
"svg.draggable.js": "^2.2.2",
|
||||
"svg.easing.js": "^2.0.0",
|
||||
"svg.filter.js": "^2.0.2",
|
||||
"svg.pathmorphing.js": "^0.1.3",
|
||||
"svg.resize.js": "^1.4.3",
|
||||
"svg.select.js": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/arg": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
|
||||
@ -885,6 +908,15 @@
|
||||
"mini-svg-data-uri": "^1.4.3"
|
||||
}
|
||||
},
|
||||
"node_modules/flowbite-datepicker": {
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/flowbite-datepicker/-/flowbite-datepicker-1.2.6.tgz",
|
||||
"integrity": "sha512-UbU/xXs9HFiwWfL4M1vpwIo8EpS0NUQSOvYnp0Z9u3N118nU7lPFGoUOq7su9d0aOJy9FssXzx1SZwN8MXhE1g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"flowbite": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fraction.js": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz",
|
||||
@ -1680,6 +1712,97 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/svg.draggable.js": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz",
|
||||
"integrity": "sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"svg.js": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svg.easing.js": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/svg.easing.js/-/svg.easing.js-2.0.0.tgz",
|
||||
"integrity": "sha512-//ctPdJMGy22YoYGV+3HEfHbm6/69LJUTAqI2/5qBvaNHZ9uUFVC82B0Pl299HzgH13rKrBgi4+XyXXyVWWthA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"svg.js": ">=2.3.x"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svg.filter.js": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/svg.filter.js/-/svg.filter.js-2.0.2.tgz",
|
||||
"integrity": "sha512-xkGBwU+dKBzqg5PtilaTb0EYPqPfJ9Q6saVldX+5vCRy31P6TlRCP3U9NxH3HEufkKkpNgdTLBJnmhDHeTqAkw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"svg.js": "^2.2.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svg.js": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz",
|
||||
"integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/svg.pathmorphing.js": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz",
|
||||
"integrity": "sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"svg.js": "^2.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svg.resize.js": {
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/svg.resize.js/-/svg.resize.js-1.4.3.tgz",
|
||||
"integrity": "sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"svg.js": "^2.6.5",
|
||||
"svg.select.js": "^2.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svg.resize.js/node_modules/svg.select.js": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz",
|
||||
"integrity": "sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"svg.js": "^2.2.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svg.select.js": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-3.0.1.tgz",
|
||||
"integrity": "sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"svg.js": "^2.6.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.1.tgz",
|
||||
@ -1812,9 +1935,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "4.5.2",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz",
|
||||
"integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==",
|
||||
"version": "4.5.3",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz",
|
||||
"integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.18.10",
|
||||
@ -2177,6 +2300,12 @@
|
||||
"integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==",
|
||||
"dev": true
|
||||
},
|
||||
"@yr/monotone-cubic-spline": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz",
|
||||
"integrity": "sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==",
|
||||
"dev": true
|
||||
},
|
||||
"alpinejs": {
|
||||
"version": "3.12.0",
|
||||
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.12.0.tgz",
|
||||
@ -2202,6 +2331,21 @@
|
||||
"picomatch": "^2.0.4"
|
||||
}
|
||||
},
|
||||
"apexcharts": {
|
||||
"version": "3.48.0",
|
||||
"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.48.0.tgz",
|
||||
"integrity": "sha512-Lhpj1Ij6lKlrUke8gf+P+SE6uGUn+Pe1TnCJ+zqrY0YMvbqM3LMb1lY+eybbTczUyk0RmMZomlTa2NgX2EUs4Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@yr/monotone-cubic-spline": "^1.0.3",
|
||||
"svg.draggable.js": "^2.2.2",
|
||||
"svg.easing.js": "^2.0.0",
|
||||
"svg.filter.js": "^2.0.2",
|
||||
"svg.pathmorphing.js": "^0.1.3",
|
||||
"svg.resize.js": "^1.4.3",
|
||||
"svg.select.js": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"arg": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
|
||||
@ -2434,6 +2578,15 @@
|
||||
"mini-svg-data-uri": "^1.4.3"
|
||||
}
|
||||
},
|
||||
"flowbite-datepicker": {
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/flowbite-datepicker/-/flowbite-datepicker-1.2.6.tgz",
|
||||
"integrity": "sha512-UbU/xXs9HFiwWfL4M1vpwIo8EpS0NUQSOvYnp0Z9u3N118nU7lPFGoUOq7su9d0aOJy9FssXzx1SZwN8MXhE1g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"flowbite": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"fraction.js": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz",
|
||||
@ -2911,6 +3064,78 @@
|
||||
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||
"dev": true
|
||||
},
|
||||
"svg.draggable.js": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz",
|
||||
"integrity": "sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"svg.js": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"svg.easing.js": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/svg.easing.js/-/svg.easing.js-2.0.0.tgz",
|
||||
"integrity": "sha512-//ctPdJMGy22YoYGV+3HEfHbm6/69LJUTAqI2/5qBvaNHZ9uUFVC82B0Pl299HzgH13rKrBgi4+XyXXyVWWthA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"svg.js": ">=2.3.x"
|
||||
}
|
||||
},
|
||||
"svg.filter.js": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/svg.filter.js/-/svg.filter.js-2.0.2.tgz",
|
||||
"integrity": "sha512-xkGBwU+dKBzqg5PtilaTb0EYPqPfJ9Q6saVldX+5vCRy31P6TlRCP3U9NxH3HEufkKkpNgdTLBJnmhDHeTqAkw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"svg.js": "^2.2.5"
|
||||
}
|
||||
},
|
||||
"svg.js": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz",
|
||||
"integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==",
|
||||
"dev": true
|
||||
},
|
||||
"svg.pathmorphing.js": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz",
|
||||
"integrity": "sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"svg.js": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"svg.resize.js": {
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/svg.resize.js/-/svg.resize.js-1.4.3.tgz",
|
||||
"integrity": "sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"svg.js": "^2.6.5",
|
||||
"svg.select.js": "^2.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"svg.select.js": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz",
|
||||
"integrity": "sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"svg.js": "^2.2.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"svg.select.js": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-3.0.1.tgz",
|
||||
"integrity": "sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"svg.js": "^2.6.5"
|
||||
}
|
||||
},
|
||||
"tailwindcss": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.1.tgz",
|
||||
@ -3011,9 +3236,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"vite": {
|
||||
"version": "4.5.2",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz",
|
||||
"integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==",
|
||||
"version": "4.5.3",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz",
|
||||
"integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"esbuild": "^0.18.10",
|
||||
|
@ -22,6 +22,8 @@
|
||||
"tailwindcss": "^3.1.0",
|
||||
"tippy.js": "^6.3.7",
|
||||
"toastr": "^2.1.4",
|
||||
"vite": "^4.5.2"
|
||||
"vite": "^4.5.3",
|
||||
"apexcharts": "^3.44.2",
|
||||
"flowbite-datepicker": "^1.2.6"
|
||||
}
|
||||
}
|
||||
|
1
public/build/assets/app-53e4d707.css
Normal file
1
public/build/assets/app-53e4d707.css
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
747
public/build/assets/app-66009dff.js
Normal file
747
public/build/assets/app-66009dff.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,6 +1,6 @@
|
||||
{
|
||||
"resources/css/app.css": {
|
||||
"file": "assets/app-e3775b0a.css",
|
||||
"file": "assets/app-53e4d707.css",
|
||||
"isEntry": true,
|
||||
"src": "resources/css/app.css"
|
||||
},
|
||||
@ -12,7 +12,7 @@
|
||||
"css": [
|
||||
"assets/app-a1ae07b3.css"
|
||||
],
|
||||
"file": "assets/app-5f99a92f.js",
|
||||
"file": "assets/app-66009dff.js",
|
||||
"isEntry": true,
|
||||
"src": "resources/js/app.js"
|
||||
}
|
||||
|
22
public/static/images/vito-agent.svg
Normal file
22
public/static/images/vito-agent.svg
Normal file
@ -0,0 +1,22 @@
|
||||
<svg width="800" height="800" viewBox="0 0 800 800" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="800" height="800" rx="50" fill="#5046E5" />
|
||||
<g filter="url(#filter0_d_1_35)">
|
||||
<path
|
||||
d="M400 601.2C392.4 601.2 386.2 599.4 381.4 595.8C376.6 592.2 372.8 586.8 370 579.6L205 202.2C202.2 195.4 201.4 189.8 202.6 185.4C203.8 180.6 206.4 176.8 210.4 174C214.8 171.2 219.8 169.8 225.4 169.8C233 169.8 238.6 171.6 242.2 175.2C246.2 178.4 249.4 183.2 251.8 189.6L410.2 557.4H391L548.8 189C551.6 183 555 178.4 559 175.2C563 171.6 568.6 169.8 575.8 169.8C581.4 169.8 586 171.2 589.6 174C593.6 176.8 596 180.6 596.8 185.4C598 190.2 597.2 195.8 594.4 202.2L429.4 579.6C426.6 586.8 422.8 592.2 418 595.8C413.6 599.4 407.6 601.2 400 601.2Z"
|
||||
fill="white" />
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d_1_35" x="196.8" y="169.8" width="405.8" height="439.4" filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha" />
|
||||
<feOffset dy="4" />
|
||||
<feGaussianBlur stdDeviation="2" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0" />
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1_35" />
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1_35" result="shape" />
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
||||
ace.define("ace/mode/sh", ["require", "exports", "module", "ace/lib/oop", "ace/mode/text", "ace/tokenizer", "ace/mode/sh_highlight_rules", "ace/range"], function (e, t, n) { var r = e("../lib/oop"), i = e("./text").Mode, s = e("../tokenizer").Tokenizer, o = e("./sh_highlight_rules").ShHighlightRules, u = e("../range").Range, a = function () { this.$tokenizer = new s((new o).getRules()) }; r.inherits(a, i), function () { this.toggleCommentLines = function (e, t, n, r) { var i = !0, s = /^(\s*)#/; for (var o = n; o <= r; o++)if (!s.test(t.getLine(o))) { i = !1; break } if (i) { var a = new u(0, 0, 0, 0); for (var o = n; o <= r; o++) { var f = t.getLine(o), l = f.match(s); a.start.row = o, a.end.row = o, a.end.column = l[0].length, t.replace(a, l[1]) } } else t.indentRows(n, r, "#") }, this.getNextLineIndent = function (e, t, n) { var r = this.$getIndent(t), i = this.$tokenizer.getLineTokens(t, e), s = i.tokens; if (s.length && s[s.length - 1].type == "comment") return r; if (e == "start") { var o = t.match(/^.*[\{\(\[\:]\s*$/); o && (r += n) } return r }; var e = { pass: 1, "return": 1, raise: 1, "break": 1, "continue": 1 }; this.checkOutdent = function (t, n, r) { if (r !== "\r\n" && r !== "\r" && r !== "\n") return !1; var i = this.$tokenizer.getLineTokens(n.trim(), t).tokens; if (!i) return !1; do var s = i.pop(); while (s && (s.type == "comment" || s.type == "text" && s.value.match(/^\s+$/))); return s ? s.type == "keyword" && e[s.value] : !1 }, this.autoOutdent = function (e, t, n) { n += 1; var r = this.$getIndent(t.getLine(n)), i = t.getTabString(); r.slice(-i.length) == i && t.remove(new u(n, r.length - i.length, n, r.length)) } }.call(a.prototype), t.Mode = a }), ace.define("ace/mode/sh_highlight_rules", ["require", "exports", "module", "ace/lib/oop", "ace/mode/text_highlight_rules"], function (e, t, n) { var r = e("../lib/oop"), i = e("./text_highlight_rules").TextHighlightRules, s = t.reservedKeywords = "!|{|}|case|do|done|elif|else|esac|fi|for|if|in|then|until|while|&|;|export|local|read|typeset|unset|elif|select|set", o = t.languageConstructs = "[|]|alias|bg|bind|break|builtin|cd|command|compgen|complete|continue|dirs|disown|echo|enable|eval|exec|exit|fc|fg|getopts|hash|help|history|jobs|kill|let|logout|popd|printf|pushd|pwd|return|set|shift|shopt|source|suspend|test|times|trap|type|ulimit|umask|unalias|wait", u = function () { var e = this.createKeywordMapper({ keyword: s, "support.function.builtin": o, "invalid.deprecated": "debugger" }, "identifier"), t = "(?:(?:[1-9]\\d*)|(?:0))", n = "(?:\\.\\d+)", r = "(?:\\d+)", i = "(?:(?:" + r + "?" + n + ")|(?:" + r + "\\.))", u = "(?:(?:" + i + "|" + r + ")" + ")", a = "(?:" + u + "|" + i + ")", f = "(?:&" + r + ")", l = "[a-zA-Z][a-zA-Z0-9_]*", c = "(?:(?:\\$" + l + ")|(?:" + l + "=))", h = "(?:\\$(?:SHLVL|\\$|\\!|\\?))", p = "(?:" + l + "\\s*\\(\\))"; this.$rules = { start: [{ token: "comment", regex: "#.*$" }, { token: "string", regex: '"(?:[^\\\\]|\\\\.)*?"' }, { token: "variable.language", regex: h }, { token: "variable", regex: c }, { token: "support.function", regex: p }, { token: "support.function", regex: f }, { token: "string", regex: "'(?:[^\\\\]|\\\\.)*?'" }, { token: "constant.numeric", regex: a }, { token: "constant.numeric", regex: t + "\\b" }, { token: e, regex: "[a-zA-Z_$][a-zA-Z0-9_$]*\\b" }, { token: "keyword.operator", regex: "\\+|\\-|\\*|\\*\\*|\\/|\\/\\/|~|<|>|<=|=>|=|!=" }, { token: "paren.lparen", regex: "[\\[\\(\\{]" }, { token: "paren.rparen", regex: "[\\]\\)\\}]" }, { token: "text", regex: "\\s+" }] } }; r.inherits(u, i), t.ShHighlightRules = u })
|
@ -1,5 +0,0 @@
|
||||
ace.define("ace/theme/github", ["require", "exports", "module", "ace/lib/dom"], function (e, t, n) {
|
||||
t.isDark = !1, t.cssClass = "ace-github", t.cssText = '/* CSS style content from github\'s default pygments highlighter template.Cursor and selection styles from textmate.css. */.ace-github .ace_gutter {background: #e8e8e8;color: #AAA;}.ace-github .ace_scroller {background: #fff;}.ace-github .ace_keyword {font-weight: bold;}.ace-github .ace_string {color: #D14;}.ace-github .ace_variable.ace_class {color: teal;}.ace-github .ace_constant.ace_numeric {color: #099;}.ace-github .ace_constant.ace_buildin {color: #0086B3;}.ace-github .ace_support.ace_function {color: #0086B3;}.ace-github .ace_comment {color: #998;font-style: italic;}.ace-github .ace_variable.ace_language {color: #0086B3;}.ace-github .ace_paren {font-weight: bold;}.ace-github .ace_boolean {font-weight: bold;}.ace-github .ace_string.ace_regexp {color: #009926;font-weight: normal;}.ace-github .ace_variable.ace_instance {color: teal;}.ace-github .ace_constant.ace_language {font-weight: bold;}.ace-github .ace_text-layer {}.ace-github .ace_cursor {border-left: 2px solid black;}.ace-github .ace_overwrite-cursors .ace_cursor {border-left: 0px;border-bottom: 1px solid black;}.ace-github .ace_marker-layer .ace_active-line {background: rgb(255, 255, 204);}.ace-github .ace_marker-layer .ace_selection {background: rgb(181, 213, 255);}.ace-github.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px white;border-radius: 2px;}/* bold keywords cause cursor issues for some fonts *//* this disables bold style for editor and keeps for static highlighter */.ace-github.ace_nobold .ace_line > span {font-weight: normal !important;}.ace-github .ace_marker-layer .ace_step {background: rgb(252, 255, 0);}.ace-github .ace_marker-layer .ace_stack {background: rgb(164, 229, 101);}.ace-github .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid rgb(192, 192, 192);}.ace-github .ace_gutter-active-line {background-color : rgba(0, 0, 0, 0.07);}.ace-github .ace_marker-layer .ace_selected-word {background: rgb(250, 250, 255);border: 1px solid rgb(200, 200, 250);}.ace-github .ace_print-margin {width: 1px;background: #e8e8e8;}.ace-github .ace_indent-guide {background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==") right repeat-y;}';
|
||||
var r = e("../lib/dom");
|
||||
r.importCssString(t.cssText, t.cssClass)
|
||||
})
|
@ -1,5 +0,0 @@
|
||||
ace.define("ace/theme/one-dark", ["require", "exports", "module", "ace/lib/dom"], function (e, t, n) {
|
||||
t.isDark = !1, t.cssClass = "ace-one-dark", t.cssText = '/* CSS style content from one-dark\'s default pygments highlighter template.Cursor and selection styles from textmate.css. */.ace-one-dark .ace_gutter{background:#282c34;color:#6a6f7a}.ace-one-dark .ace_print-margin{width:1px;background:#e8e8e8}.ace-one-dark{background-color:#282c34;color:#abb2bf}.ace-one-dark .ace_cursor{color:#528bff}.ace-one-dark .ace_marker-layer .ace_selection{background:#3d4350}.ace-one-dark.ace_multiselect .ace_selection.ace_start{box-shadow:0 0 3px 0 #282c34;border-radius:2px}.ace-one-dark .ace_marker-layer .ace_step{background:#c6dbae}.ace-one-dark .ace_marker-layer .ace_bracket{margin:-1px 0 0 -1px;border:1px solid #747369}.ace-one-dark .ace_marker-layer .ace_active-line{background:rgba(76,87,103,.19)}.ace-one-dark .ace_gutter-active-line{background-color:rgba(76,87,103,.19)}.ace-one-dark .ace_marker-layer .ace_selected-word{border:1px solid #3d4350}.ace-one-dark .ace_fold{background-color:#61afef;border-color:#abb2bf}.ace-one-dark .ace_keyword{color:#c678dd}.ace-one-dark .ace_keyword.ace_operator{color:#c678dd}.ace-one-dark .ace_keyword.ace_other.ace_unit{color:#d19a66}.ace-one-dark .ace_constant.ace_language{color:#d19a66}.ace-one-dark .ace_constant.ace_numeric{color:#d19a66}.ace-one-dark .ace_constant.ace_character{color:#56b6c2}.ace-one-dark .ace_constant.ace_other{color:#56b6c2}.ace-one-dark .ace_support.ace_function{color:#61afef}.ace-one-dark .ace_support.ace_constant{color:#d19a66}.ace-one-dark .ace_support.ace_class{color:#e5c07b}.ace-one-dark .ace_support.ace_type{color:#e5c07b}.ace-one-dark .ace_storage{color:#c678dd}.ace-one-dark .ace_storage.ace_type{color:#c678dd}.ace-one-dark .ace_invalid{color:#fff;background-color:#f2777a}.ace-one-dark .ace_invalid.ace_deprecated{color:#272b33;background-color:#d27b53}.ace-one-dark .ace_string{color:#98c379}.ace-one-dark .ace_string.ace_regexp{color:#e06c75}.ace-one-dark .ace_comment{font-style:italic;color:#5c6370}.ace-one-dark .ace_variable{color:#e06c75}.ace-one-dark .ace_variable.ace_parameter{color:#d19a66}.ace-one-dark .ace_meta.ace_tag{color:#e06c75}.ace-one-dark .ace_entity.ace_other.ace_attribute-name{color:#e06c75}.ace-one-dark .ace_entity.ace_name.ace_function{color:#61afef}.ace-one-dark .ace_entity.ace_name.ace_tag{color:#e06c75}.ace-one-dark .ace_markup.ace_heading{color:#98c379}.ace-one-dark .ace_indent-guide{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWPQ09NrYAgMjP4PAAtGAwchHMyAAAAAAElFTkSuQmCC) right repeat-y}';
|
||||
var r = e("../lib/dom");
|
||||
r.importCssString(t.cssText, t.cssClass)
|
||||
})
|
@ -1,9 +1,13 @@
|
||||
import 'flowbite';
|
||||
import 'flowbite/dist/datepicker.js';
|
||||
|
||||
import Alpine from 'alpinejs';
|
||||
window.Alpine = Alpine;
|
||||
Alpine.start();
|
||||
|
||||
import ApexCharts from 'apexcharts';
|
||||
window.ApexCharts = ApexCharts;
|
||||
|
||||
import htmx from "htmx.org";
|
||||
window.htmx = htmx;
|
||||
window.htmx.defineExtension('disable-element', {
|
||||
@ -23,12 +27,10 @@ window.htmx.defineExtension('disable-element', {
|
||||
});
|
||||
document.body.addEventListener('htmx:configRequest', (event) => {
|
||||
event.detail.headers['X-CSRF-TOKEN'] = document.head.querySelector('meta[name="csrf-token"]').content;
|
||||
if (window.getSelection) { window.getSelection().removeAllRanges(); }
|
||||
else if (document.selection) { document.selection.empty(); }
|
||||
// if (window.getSelection) { window.getSelection().removeAllRanges(); }
|
||||
// else if (document.selection) { document.selection.empty(); }
|
||||
});
|
||||
let activeElement = null;
|
||||
document.body.addEventListener('htmx:beforeRequest', (event) => {
|
||||
activeElement = document.activeElement;
|
||||
let targetElements = event.target.querySelectorAll('[hx-disable]');
|
||||
for (let i = 0; i < targetElements.length; i++) {
|
||||
targetElements[i].disabled = true;
|
||||
@ -46,11 +48,6 @@ document.body.addEventListener('htmx:afterSwap', (event) => {
|
||||
return reference.getAttribute('data-tooltip');
|
||||
},
|
||||
});
|
||||
if (activeElement) {
|
||||
activeElement.blur();
|
||||
activeElement.focus();
|
||||
activeElement = null;
|
||||
}
|
||||
});
|
||||
|
||||
import toastr from 'toastr';
|
||||
|
@ -4,6 +4,7 @@
|
||||
<x-slot name="trigger">
|
||||
<x-secondary-button>
|
||||
{{ __("Auto Deployment") }}
|
||||
<x-heroicon name="o-chevron-down" class="ml-1 h-4 w-4" />
|
||||
</x-secondary-button>
|
||||
</x-slot>
|
||||
<x-slot name="content">
|
||||
|
@ -12,11 +12,13 @@ class="p-6"
|
||||
{{ __("Deployment Script") }}
|
||||
</h2>
|
||||
|
||||
<div class="mt-6">A bash script that will be executed when you run the deployment process.</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<x-input-label for="script" :value="__('Script')" />
|
||||
<x-code-editor id="script" name="script" lang="sh" class="mt-1 w-full">
|
||||
<x-textarea id="script" name="script" class="mt-1 min-h-[400px] w-full">
|
||||
{{ old("script", $site->deploymentScript?->content) }}
|
||||
</x-code-editor>
|
||||
</x-textarea>
|
||||
@error("script")
|
||||
<x-input-error class="mt-2" :messages="$message" />
|
||||
@enderror
|
||||
|
@ -21,9 +21,9 @@ class="mt-6"
|
||||
>
|
||||
<x-input-label for="env" :value="__('.env')" />
|
||||
<div id="env-content">
|
||||
<x-code-editor id="env" name="env" rows="10" class="mt-1 block w-full">
|
||||
<x-textarea id="env" name="env" rows="10" class="mt-1 block min-h-[400px] w-full">
|
||||
{{ old("env", session()->get("env") ?? "Loading...") }}
|
||||
</x-code-editor>
|
||||
</x-textarea>
|
||||
</div>
|
||||
@error("env")
|
||||
<x-input-error class="mt-2" :messages="$message" />
|
||||
|
@ -19,6 +19,7 @@
|
||||
<x-slot name="trigger">
|
||||
<x-secondary-button>
|
||||
{{ __("Manage") }}
|
||||
<x-heroicon name="o-chevron-down" class="ml-1 h-4 w-4" />
|
||||
</x-secondary-button>
|
||||
</x-slot>
|
||||
<x-slot name="content">
|
||||
|
104
resources/views/components/chart.blade.php
Normal file
104
resources/views/components/chart.blade.php
Normal file
@ -0,0 +1,104 @@
|
||||
@props([
|
||||
"id",
|
||||
"type",
|
||||
"title",
|
||||
"color",
|
||||
"sets",
|
||||
"categories",
|
||||
"toolbar" => false,
|
||||
"formatter" => null,
|
||||
])
|
||||
<x-simple-card {{ $attributes }}>
|
||||
<div class="relative">
|
||||
<div class="absolute left-4 top-4">{{ $title }}</div>
|
||||
</div>
|
||||
<div id="{{ $id }}" class="pt-4"></div>
|
||||
<script>
|
||||
window.addEventListener('load', function () {
|
||||
let options = {
|
||||
series: [
|
||||
@foreach ($sets as $set)
|
||||
{
|
||||
name: '{{ $set["name"] }}',
|
||||
data: @json($set["data"]),
|
||||
color: '{{ $set["color"] }}'
|
||||
},
|
||||
@endforeach
|
||||
],
|
||||
chart: {
|
||||
height: '100%',
|
||||
maxWidth: '100%',
|
||||
type: '{{ $type }}',
|
||||
fontFamily: 'Inter, sans-serif',
|
||||
dropShadow: {
|
||||
enabled: false
|
||||
},
|
||||
toolbar: {
|
||||
show: @js($toolbar)
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
enabled: true,
|
||||
x: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
show: true
|
||||
},
|
||||
@if ($type == 'area')
|
||||
fill: {
|
||||
type: 'gradient',
|
||||
gradient: {
|
||||
opacityFrom: 0.55,
|
||||
opacityTo: 0,
|
||||
shade: '{{ $color }}',
|
||||
gradientToColors: ['{{ $color }}']
|
||||
}
|
||||
},
|
||||
@endif
|
||||
dataLabels: {
|
||||
enabled: false
|
||||
},
|
||||
stroke: {
|
||||
width: 2,
|
||||
curve: 'smooth'
|
||||
},
|
||||
grid: {
|
||||
show: false,
|
||||
strokeDashArray: 4,
|
||||
padding: {
|
||||
left: 2,
|
||||
right: 2,
|
||||
top: 0
|
||||
}
|
||||
},
|
||||
xaxis: {
|
||||
categories: @json($categories),
|
||||
labels: {
|
||||
show: false
|
||||
},
|
||||
axisBorder: {
|
||||
show: false
|
||||
},
|
||||
axisTicks: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
show: false,
|
||||
labels: {
|
||||
@if ($formatter)
|
||||
formatter: {{ $formatter }},
|
||||
@endif
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (document.getElementById('{{ $id }}') && typeof ApexCharts !== 'undefined') {
|
||||
const chart = new ApexCharts(document.getElementById('{{ $id }}'), options);
|
||||
chart.render();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</x-simple-card>
|
@ -1,45 +0,0 @@
|
||||
@props([
|
||||
"id",
|
||||
"name",
|
||||
"disabled" => false,
|
||||
"lang" => "text",
|
||||
])
|
||||
|
||||
<div
|
||||
x-data="{
|
||||
editorId: @js($id),
|
||||
disabled: @js($disabled),
|
||||
lang: @js($lang),
|
||||
init() {
|
||||
document.body.addEventListener('htmx:afterSettle', (event) => {
|
||||
let editor = null
|
||||
let theme =
|
||||
document.documentElement.className === 'dark'
|
||||
? 'one-dark'
|
||||
: 'github'
|
||||
editor = window.ace.edit(this.editorId, {})
|
||||
let contentElement = document.getElementById(
|
||||
`text-${this.editorId}`,
|
||||
)
|
||||
editor.setValue(contentElement.innerText, 1)
|
||||
if (this.disabled) {
|
||||
editor.setReadOnly(true)
|
||||
}
|
||||
editor.getSession().setMode(`ace/mode/${this.lang}`)
|
||||
editor.setTheme(`ace/theme/${theme}`)
|
||||
editor.setFontSize('15px')
|
||||
editor.setShowPrintMargin(false)
|
||||
editor.on('change', () => {
|
||||
contentElement.innerHTML = editor.getValue()
|
||||
})
|
||||
document.body.addEventListener('color-scheme-changed', (event) => {
|
||||
theme = event.detail.theme === 'dark' ? 'one-dark' : 'github'
|
||||
editor.setTheme(`ace/theme/${theme}`)
|
||||
})
|
||||
})
|
||||
},
|
||||
}"
|
||||
>
|
||||
<div id="{{ $id }}" class="min-h-[400px] w-full rounded-md border border-gray-200 dark:border-gray-700"></div>
|
||||
<textarea id="text-{{ $id }}" name="{{ $name }}" class="hidden">{{ $slot }}</textarea>
|
||||
</div>
|
14
resources/views/components/heroicons/o-calendar.blade.php
Normal file
14
resources/views/components/heroicons/o-calendar.blade.php
Normal file
@ -0,0 +1,14 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
{{ $attributes }}
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 0 1 2.25-2.25h13.5A2.25 2.25 0 0 1 21 7.5v11.25m-18 0A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75m-18 0v-7.5A2.25 2.25 0 0 1 5.25 9h13.5A2.25 2.25 0 0 1 21 11.25v7.5"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 482 B |
14
resources/views/components/heroicons/o-chart-bar.blade.php
Normal file
14
resources/views/components/heroicons/o-chart-bar.blade.php
Normal file
@ -0,0 +1,14 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
{{ $attributes }}
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 0 1 3 19.875v-6.75ZM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V8.625ZM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V4.125Z"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 705 B |
@ -10,7 +10,7 @@
|
||||
runUrl: '{{ route("servers.console.run", ["server" => $server]) }}',
|
||||
async run() {
|
||||
this.running = true
|
||||
this.output = 'Running...\n'
|
||||
this.output = this.command + '\n'
|
||||
const fetchOptions = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@ -23,6 +23,7 @@
|
||||
}),
|
||||
}
|
||||
|
||||
this.command = ''
|
||||
const response = await fetch(this.runUrl, fetchOptions)
|
||||
const reader = response.body.getReader()
|
||||
const decoder = new TextDecoder('utf-8')
|
||||
|
@ -21,8 +21,6 @@
|
||||
<link rel="preconnect" href="https://fonts.bunny.net" />
|
||||
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
|
||||
|
||||
<script src="{{ asset("static/libs/ace/ace.js") }}"></script>
|
||||
|
||||
@include("layouts.partials.favicon")
|
||||
|
||||
<!-- Scripts -->
|
||||
|
@ -132,7 +132,10 @@
|
||||
}"
|
||||
@open-search.window="openSearch"
|
||||
>
|
||||
<div x-show="open" class="absolute bottom-0 left-0 right-0 top-0 flex max-w-full items-start justify-center">
|
||||
<div
|
||||
x-show="open"
|
||||
class="fixed bottom-0 left-0 right-0 top-0 z-[2000] flex max-w-full items-start justify-center"
|
||||
>
|
||||
<div
|
||||
x-on:click="close"
|
||||
class="fixed inset-0 bottom-0 left-0 right-0 top-0 z-[1000] items-center bg-gray-500 opacity-75 dark:bg-gray-900"
|
||||
|
@ -37,7 +37,7 @@ class="fixed left-0 top-0 z-40 h-screen w-64 -translate-x-full border-r border-g
|
||||
</li>
|
||||
@endif
|
||||
|
||||
@if ($server->database())
|
||||
@if ($server->database()?->status == \App\Enums\ServiceStatus::READY)
|
||||
<li>
|
||||
<x-sidebar-link
|
||||
:href="route('servers.databases', ['server' => $server])"
|
||||
@ -52,7 +52,6 @@ class="fixed left-0 top-0 z-40 h-screen w-64 -translate-x-full border-r border-g
|
||||
</li>
|
||||
@endif
|
||||
|
||||
@if ($server->php())
|
||||
<li>
|
||||
<x-sidebar-link
|
||||
:href="route('servers.php', ['server' => $server])"
|
||||
@ -64,7 +63,6 @@ class="fixed left-0 top-0 z-40 h-screen w-64 -translate-x-full border-r border-g
|
||||
</span>
|
||||
</x-sidebar-link>
|
||||
</li>
|
||||
@endif
|
||||
|
||||
@if ($server->firewall())
|
||||
<li>
|
||||
@ -116,6 +114,20 @@ class="fixed left-0 top-0 z-40 h-screen w-64 -translate-x-full border-r border-g
|
||||
</x-sidebar-link>
|
||||
</li>
|
||||
|
||||
@if ($server->monitoring())
|
||||
<li>
|
||||
<x-sidebar-link
|
||||
:href="route('servers.metrics', ['server' => $server])"
|
||||
:active="request()->routeIs('servers.metrics')"
|
||||
>
|
||||
<x-heroicon name="o-chart-bar" class="h-6 w-6" />
|
||||
<span class="ml-2">
|
||||
{{ __("Metrics") }}
|
||||
</span>
|
||||
</x-sidebar-link>
|
||||
</li>
|
||||
@endif
|
||||
|
||||
<li>
|
||||
<x-sidebar-link
|
||||
:href="route('servers.console', ['server' => $server])"
|
||||
|
@ -28,7 +28,7 @@ class="mr-1"
|
||||
</x-tab-item>
|
||||
@endif
|
||||
|
||||
@if ($site->hasFeature(SiteFeature::QUEUES))
|
||||
@if ($site->hasFeature(SiteFeature::QUEUES) && $site->server->processManager()?->status == \App\Enums\ServiceStatus::READY)
|
||||
<x-tab-item
|
||||
class="mr-1"
|
||||
:href="route('servers.sites.queues', ['server' => $site->server, 'site' => $site])"
|
||||
|
106
resources/views/metrics/index.blade.php
Normal file
106
resources/views/metrics/index.blade.php
Normal file
@ -0,0 +1,106 @@
|
||||
<x-server-layout :server="$server">
|
||||
<x-slot name="pageTitle">{{ $server->name }} - Metrics</x-slot>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center justify-between">
|
||||
@include("metrics.partials.filter")
|
||||
@include("metrics.partials.data-retention")
|
||||
</div>
|
||||
@php
|
||||
$cpuSets = [
|
||||
"name" => "CPU Load",
|
||||
"data" => $data["metrics"]->pluck("load")->toArray(),
|
||||
"color" => "#ff9900",
|
||||
];
|
||||
$memorySets = [
|
||||
"name" => "Memory Usage",
|
||||
"data" => $data["metrics"]->pluck("memory_used")->toArray(),
|
||||
"color" => "#3366cc",
|
||||
];
|
||||
$diskSets = [
|
||||
"name" => "Disk Usage",
|
||||
"data" => $data["metrics"]->pluck("disk_used")->toArray(),
|
||||
"color" => "#109618",
|
||||
];
|
||||
@endphp
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 lg:grid-cols-3">
|
||||
<x-chart
|
||||
id="cpu-load"
|
||||
type="area"
|
||||
title="CPU Load"
|
||||
:sets="[$cpuSets]"
|
||||
:categories="$data['metrics']->pluck('date')->toArray()"
|
||||
color="#ff9900"
|
||||
class="h-[200px] !p-0"
|
||||
/>
|
||||
|
||||
<x-chart
|
||||
id="memory-usage"
|
||||
type="area"
|
||||
title="Memory"
|
||||
:sets="[$memorySets]"
|
||||
:categories="$data['metrics']->pluck('date')->toArray()"
|
||||
color="#3366cc"
|
||||
formatter="function (value) { return (value / 1000).toFixed(2) + ` MB`; }"
|
||||
class="h-[200px] !p-0"
|
||||
/>
|
||||
|
||||
<x-chart
|
||||
id="disk-usage"
|
||||
type="area"
|
||||
title="Disk"
|
||||
:sets="[$diskSets]"
|
||||
:categories="$data['metrics']->pluck('date')->toArray()"
|
||||
formatter="function (value) { return value + ` MB`; }"
|
||||
color="#109618"
|
||||
class="h-[200px] !p-0"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div class="grid grid-cols-1 gap-4">
|
||||
<x-simple-card class="grid grid-cols-1 gap-4 lg:grid-cols-2">
|
||||
<span class="text-center lg:text-left">Total Memory</span>
|
||||
<div class="text-center text-xl font-bold text-gray-600 dark:text-gray-400 lg:text-right">
|
||||
{{ $lastMetric ? $lastMetric->memory_total : "-" }} MB
|
||||
</div>
|
||||
</x-simple-card>
|
||||
<x-simple-card class="grid grid-cols-1 gap-4 lg:grid-cols-2">
|
||||
<span class="text-center lg:text-left">Used Memory</span>
|
||||
<div class="text-center text-xl font-bold text-gray-600 dark:text-gray-400 lg:text-right">
|
||||
{{ $lastMetric ? $lastMetric->memory_used : "-" }} MB
|
||||
</div>
|
||||
</x-simple-card>
|
||||
<x-simple-card class="grid grid-cols-1 gap-4 lg:grid-cols-2">
|
||||
<span class="text-center lg:text-left">Free Memory</span>
|
||||
<div class="text-center text-xl font-bold text-gray-600 dark:text-gray-400 lg:text-right">
|
||||
{{ $lastMetric ? $lastMetric->memory_free : "-" }} MB
|
||||
</div>
|
||||
</x-simple-card>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 gap-4">
|
||||
<x-simple-card class="grid grid-cols-1 gap-4 lg:grid-cols-2">
|
||||
<span class="text-center lg:text-left">Total Space</span>
|
||||
<div class="text-center text-xl font-bold text-gray-600 dark:text-gray-400 lg:text-right">
|
||||
{{ $lastMetric ? $lastMetric->disk_total : "-" }} MB
|
||||
</div>
|
||||
</x-simple-card>
|
||||
<x-simple-card class="grid grid-cols-1 gap-4 lg:grid-cols-2">
|
||||
<span class="text-center lg:text-left">Used Space</span>
|
||||
<div class="text-center text-xl font-bold text-gray-600 dark:text-gray-400 lg:text-right">
|
||||
{{ $lastMetric ? $lastMetric->disk_used : "-" }} MB
|
||||
</div>
|
||||
</x-simple-card>
|
||||
<x-simple-card class="grid grid-cols-1 gap-4 lg:grid-cols-2">
|
||||
<span class="text-center lg:text-left">Free Space</span>
|
||||
<div class="text-center text-xl font-bold text-gray-600 dark:text-gray-400 lg:text-right">
|
||||
{{ $lastMetric ? $lastMetric->disk_free : "-" }} MB
|
||||
</div>
|
||||
</x-simple-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@stack("modals")
|
||||
</x-server-layout>
|
44
resources/views/metrics/partials/data-retention.blade.php
Normal file
44
resources/views/metrics/partials/data-retention.blade.php
Normal file
@ -0,0 +1,44 @@
|
||||
<x-secondary-button
|
||||
class="ml-2 hidden h-[42px] items-center lg:flex"
|
||||
x-on:click="$dispatch('open-modal', 'metric-settings')"
|
||||
>
|
||||
<x-heroicon name="o-trash" class="mr-1 h-5 w-5" />
|
||||
Data Retention
|
||||
</x-secondary-button>
|
||||
@push("modals")
|
||||
<x-modal name="metric-settings">
|
||||
<form
|
||||
id="metric-settings-form"
|
||||
hx-post="{{ route("servers.metrics.settings", ["server" => $server]) }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-select="#metric-settings-form"
|
||||
class="p-6"
|
||||
>
|
||||
@csrf
|
||||
|
||||
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">Data Retention</h2>
|
||||
|
||||
<div class="mt-6">
|
||||
<x-input-label for="data_retention" value="Delete metrics older than" />
|
||||
<x-select-input id="data_retention" name="data_retention" class="mt-1 w-full">
|
||||
@foreach (config("core.metrics_data_retention") as $item)
|
||||
<option value="{{ $item }}">{{ $item }} Days</option>
|
||||
@endforeach
|
||||
</x-select-input>
|
||||
@error("data_retention")
|
||||
<x-input-error class="mt-2" :messages="$message" />
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex justify-end">
|
||||
<x-secondary-button type="button" x-on:click="$dispatch('close')">
|
||||
{{ __("Cancel") }}
|
||||
</x-secondary-button>
|
||||
|
||||
<x-primary-button id="btn-metric-settings" hx-disable class="ml-3">
|
||||
{{ __("Save") }}
|
||||
</x-primary-button>
|
||||
</div>
|
||||
</form>
|
||||
</x-modal>
|
||||
@endpush
|
76
resources/views/metrics/partials/filter.blade.php
Normal file
76
resources/views/metrics/partials/filter.blade.php
Normal file
@ -0,0 +1,76 @@
|
||||
<div class="flex items-center" x-data="{ period: '{{ request()->query("period") ?? "10m" }}' }">
|
||||
<x-dropdown align="left" class="ml-2">
|
||||
<x-slot name="trigger">
|
||||
<div data-tooltip="Change Period">
|
||||
<div
|
||||
class="flex w-full items-center rounded-md border border-gray-300 bg-white p-2.5 pr-10 text-sm capitalize text-gray-900 focus:border-primary-500 focus:ring-primary-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-primary-500 dark:focus:ring-primary-500"
|
||||
>
|
||||
<div x-text="period"></div>
|
||||
</div>
|
||||
<button type="button" class="absolute inset-y-0 right-0 flex items-center pr-2">
|
||||
<x-heroicon name="o-chevron-down" class="h-4 w-4 text-gray-400" />
|
||||
</button>
|
||||
</div>
|
||||
</x-slot>
|
||||
<x-slot name="content">
|
||||
<x-dropdown-link :href="route('servers.metrics', ['server' => $server, 'period' => '10m'])">
|
||||
10 Minutes
|
||||
</x-dropdown-link>
|
||||
<x-dropdown-link :href="route('servers.metrics', ['server' => $server, 'period' => '30m'])">
|
||||
30 Minutes
|
||||
</x-dropdown-link>
|
||||
<x-dropdown-link :href="route('servers.metrics', ['server' => $server, 'period' => '1h'])">
|
||||
1 Hour
|
||||
</x-dropdown-link>
|
||||
<x-dropdown-link :href="route('servers.metrics', ['server' => $server, 'period' => '12h'])">
|
||||
12 Hours
|
||||
</x-dropdown-link>
|
||||
<x-dropdown-link :href="route('servers.metrics', ['server' => $server, 'period' => '1d'])">
|
||||
1 Day
|
||||
</x-dropdown-link>
|
||||
<x-dropdown-link :href="route('servers.metrics', ['server' => $server, 'period' => '7d'])">
|
||||
7 Days
|
||||
</x-dropdown-link>
|
||||
<x-dropdown-link x-on:click="period = 'custom'" class="cursor-pointer">Custom</x-dropdown-link>
|
||||
</x-slot>
|
||||
</x-dropdown>
|
||||
|
||||
<form
|
||||
x-show="period === 'custom'"
|
||||
class="flex items-center"
|
||||
action="{{ route("servers.metrics", ["server" => $server, "period" => "custom"]) }}"
|
||||
>
|
||||
<input type="hidden" name="period" value="custom" />
|
||||
<div date-rangepicker datepicker-format="yyyy-mm-dd" class="ml-2 flex items-center">
|
||||
<div class="relative">
|
||||
<div class="pointer-events-none absolute inset-y-0 start-0 flex items-center ps-3">
|
||||
<x-heroicon name="o-calendar" class="h-4 w-4 text-gray-500 dark:text-gray-400" />
|
||||
</div>
|
||||
<input
|
||||
name="from"
|
||||
type="text"
|
||||
class="block w-full rounded-md border border-gray-300 bg-white p-2.5 ps-10 text-sm text-gray-900 focus:border-blue-500 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-blue-500 dark:focus:ring-blue-500"
|
||||
placeholder="{{ __("From Date") }}"
|
||||
value="{{ request()->query("from") }}"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
<span class="mx-2 text-gray-500">to</span>
|
||||
<div class="relative">
|
||||
<div class="pointer-events-none absolute inset-y-0 start-0 flex items-center ps-3">
|
||||
<x-heroicon name="o-calendar" class="h-4 w-4 text-gray-500 dark:text-gray-400" />
|
||||
</div>
|
||||
<input
|
||||
name="to"
|
||||
type="text"
|
||||
class="block w-full rounded-md border border-gray-300 bg-white p-2.5 ps-10 text-sm text-gray-900 focus:border-blue-500 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-blue-500 dark:focus:ring-blue-500"
|
||||
placeholder="{{ __("To Date") }}"
|
||||
value="{{ request()->query("to") }}"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<x-input-error class="absolute left-0 top-10 ml-1 mt-1" :messages="$errors->get('to')" />
|
||||
</div>
|
||||
</div>
|
||||
<x-primary-button class="ml-2 h-[42px]">{{ __("Filter") }}</x-primary-button>
|
||||
</form>
|
||||
</div>
|
@ -239,6 +239,7 @@ class="mt-1 block w-full"
|
||||
<div x-show="['{{ ServerType::REGULAR }}'].includes(type)">
|
||||
<x-input-label for="php" value="PHP" />
|
||||
<x-select-input id="php" name="php" class="mt-1 w-full">
|
||||
<option value="none" @if('none' == old('php', '8.2')) selected @endif>none</option>
|
||||
@foreach (config("core.php_versions") as $p)
|
||||
<option value="{{ $p }}" @if($p == old('php', '8.2')) selected @endif>
|
||||
{{ $p }}
|
||||
|
@ -13,20 +13,7 @@ class="@if($server->webserver() && $server->database()) grid-cols-3 @else grid-c
|
||||
@if ($server->webserver())
|
||||
<div class="border-r border-gray-200 p-5 dark:border-gray-900">
|
||||
<div class="flex items-center justify-center md:justify-start">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="h-8 w-8 text-primary-500"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M12 21a9.004 9.004 0 008.716-6.747M12 21a9.004 9.004 0 01-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 017.843 4.582M12 3a8.997 8.997 0 00-7.843 4.582m15.686 0A11.953 11.953 0 0112 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0121 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0112 16.5c-3.162 0-6.133-.815-8.716-2.247m0 0A9.015 9.015 0 013 12c0-1.605.42-3.113 1.157-4.418"
|
||||
/>
|
||||
</svg>
|
||||
<x-heroicon name="o-globe-alt" class="h-8 w-8 text-primary-500" />
|
||||
<div class="ml-2 hidden md:block">{{ __("Sites") }}</div>
|
||||
</div>
|
||||
<div class="mt-3 text-center text-3xl font-bold text-gray-600 dark:text-gray-400 md:text-left">
|
||||
@ -38,20 +25,7 @@ class="h-8 w-8 text-primary-500"
|
||||
@if ($server->database())
|
||||
<div class="border-r border-gray-200 p-5 dark:border-gray-900">
|
||||
<div class="flex items-center justify-center md:justify-start">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="h-8 w-8 text-primary-500"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M20.25 6.375c0 2.278-3.694 4.125-8.25 4.125S3.75 8.653 3.75 6.375m16.5 0c0-2.278-3.694-4.125-8.25-4.125S3.75 4.097 3.75 6.375m16.5 0v11.25c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125V6.375m16.5 0v3.75m-16.5-3.75v3.75m16.5 0v3.75C20.25 16.153 16.556 18 12 18s-8.25-1.847-8.25-4.125v-3.75m16.5 0c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125"
|
||||
/>
|
||||
</svg>
|
||||
<x-heroicon name="o-circle-stack" class="h-8 w-8 text-primary-500" />
|
||||
<div class="ml-2 hidden md:block">
|
||||
{{ __("Databases") }}
|
||||
</div>
|
||||
@ -64,20 +38,7 @@ class="h-8 w-8 text-primary-500"
|
||||
|
||||
<div class="p-5">
|
||||
<div class="flex items-center justify-center md:justify-start">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="h-8 w-8 text-primary-500"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
<x-heroicon name="o-clock" class="h-8 w-8 text-primary-500" />
|
||||
<div class="ml-2 hidden md:block">{{ __("Cron Jobs") }}</div>
|
||||
</div>
|
||||
<div class="mt-3 text-center text-3xl font-bold text-gray-600 dark:text-gray-400 md:text-left">
|
||||
|
@ -3,5 +3,7 @@
|
||||
|
||||
@include("services.partials.services-list")
|
||||
|
||||
{{-- @include("services.partials.add-ons") --}}
|
||||
@include("services.partials.available-services")
|
||||
|
||||
@stack("modals")
|
||||
</x-server-layout>
|
||||
|
@ -1 +1,6 @@
|
||||
@include("services.partials.unit-actions")
|
||||
@include("services.partials.unit-actions.restart")
|
||||
@include("services.partials.unit-actions.start")
|
||||
@include("services.partials.unit-actions.stop")
|
||||
@include("services.partials.unit-actions.enable")
|
||||
@include("services.partials.unit-actions.disable")
|
||||
@include("services.partials.unit-actions.uninstall")
|
||||
|
@ -1 +1,6 @@
|
||||
@include("services.partials.unit-actions")
|
||||
@include("services.partials.unit-actions.restart")
|
||||
@include("services.partials.unit-actions.start")
|
||||
@include("services.partials.unit-actions.stop")
|
||||
@include("services.partials.unit-actions.enable")
|
||||
@include("services.partials.unit-actions.disable")
|
||||
@include("services.partials.unit-actions.uninstall")
|
||||
|
@ -1 +1,6 @@
|
||||
@include("services.partials.unit-actions")
|
||||
@include("services.partials.unit-actions.restart")
|
||||
@include("services.partials.unit-actions.start")
|
||||
@include("services.partials.unit-actions.stop")
|
||||
@include("services.partials.unit-actions.enable")
|
||||
@include("services.partials.unit-actions.disable")
|
||||
@include("services.partials.unit-actions.uninstall")
|
||||
|
@ -1 +1,6 @@
|
||||
@include("services.partials.unit-actions")
|
||||
@include("services.partials.unit-actions.restart")
|
||||
@include("services.partials.unit-actions.start")
|
||||
@include("services.partials.unit-actions.stop")
|
||||
@include("services.partials.unit-actions.enable")
|
||||
@include("services.partials.unit-actions.disable")
|
||||
@include("services.partials.unit-actions.uninstall")
|
||||
|
@ -1 +1,6 @@
|
||||
@include("services.partials.unit-actions")
|
||||
@include("services.partials.unit-actions.restart")
|
||||
@include("services.partials.unit-actions.start")
|
||||
@include("services.partials.unit-actions.stop")
|
||||
@include("services.partials.unit-actions.enable")
|
||||
@include("services.partials.unit-actions.disable")
|
||||
@include("services.partials.unit-actions.uninstall")
|
||||
|
@ -1 +1,6 @@
|
||||
@include("services.partials.unit-actions")
|
||||
@include("services.partials.unit-actions.restart")
|
||||
@include("services.partials.unit-actions.start")
|
||||
@include("services.partials.unit-actions.stop")
|
||||
@include("services.partials.unit-actions.enable")
|
||||
@include("services.partials.unit-actions.disable")
|
||||
@include("services.partials.unit-actions.uninstall")
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user