From 052e28d2e3c0b04f8b790e0e8feb7ac280aa401e Mon Sep 17 00:00:00 2001
From: Saeed Vaziry <61919774+saeedvaziry@users.noreply.github.com>
Date: Sat, 13 Apr 2024 11:47:56 +0200
Subject: [PATCH] Monitoring & Service Management (#163)
Monitoring & Service Management
---
app/Actions/Monitoring/GetMetrics.php | 150 ++++++++++++
app/Actions/PHP/ChangeDefaultCli.php | 5 +-
app/Actions/PHP/GetPHPIni.php | 6 +-
app/Actions/PHP/InstallPHPExtension.php | 5 +-
.../Service/{Create.php => Install.php} | 20 +-
app/Actions/Service/Uninstall.php | 28 +++
app/Http/Controllers/API/AgentController.php | 36 +++
app/Http/Controllers/MetricController.php | 27 +++
app/Http/Controllers/ServiceController.php | 14 +-
app/Models/Metric.php | 53 +++++
app/Models/Server.php | 23 ++
app/Models/Service.php | 12 +-
app/SSH/OS/OS.php | 8 +
app/SSH/OS/scripts/cleanup.sh | 19 ++
app/SSH/Services/AbstractService.php | 42 ++++
.../AddOnServices/AbstractAddOnService.php | 18 --
.../Services/Database/AbstractDatabase.php | 86 +++++--
.../Database/scripts/mariadb/install-10.3.sh | 2 +
.../Database/scripts/mariadb/install-10.4.sh | 2 +
.../Database/scripts/mariadb/uninstall.sh | 9 +
.../Database/scripts/mysql/install-5.7.sh | 2 +
.../Database/scripts/mysql/install-8.0.sh | 2 +
.../Database/scripts/mysql/uninstall.sh | 9 +
.../Database/scripts/postgresql/uninstall.sh | 11 +
.../Services/Firewall/AbstractFirewall.php | 11 +-
app/SSH/Services/Firewall/Ufw.php | 6 +
app/SSH/Services/PHP/PHP.php | 39 ++-
.../ProcessManager/AbstractProcessManager.php | 36 ++-
.../Services/ProcessManager/Supervisor.php | 12 +
.../supervisor/uninstall-supervisor.sh | 8 +
app/SSH/Services/Redis/Redis.php | 31 ++-
app/SSH/Services/Redis/scripts/uninstall.sh | 15 ++
app/SSH/Services/ServiceInterface.php | 10 +
app/SSH/Services/VitoAgent/VitoAgent.php | 85 +++++++
app/SSH/Services/VitoAgent/scripts/install.sh | 53 +++++
.../Services/VitoAgent/scripts/uninstall.sh | 13 +
.../Services/Webserver/AbstractWebserver.php | 8 +-
app/SSH/Services/Webserver/Nginx.php | 26 ++
.../scripts/nginx/uninstall-nginx.sh | 12 +
app/ServerTypes/AbstractType.php | 5 +-
app/ServerTypes/Regular.php | 2 +-
app/Support/helpers.php | 9 +
config/core.php | 216 ++++++-----------
database/factories/MetricFactory.php | 22 ++
...2024_04_08_212940_create_metrics_table.php | 37 +++
database/seeders/MetricsSeeder.php | 32 +++
package-lock.json | 225 ++++++++++++++++++
package.json | 4 +-
public/static/images/vito-agent.svg | 22 ++
resources/js/app.js | 4 +
resources/views/components/chart.blade.php | 103 ++++++++
.../components/heroicons/o-calendar.blade.php | 14 ++
.../heroicons/o-chart-bar.blade.php | 14 ++
resources/views/layouts/sidebar.blade.php | 40 ++--
resources/views/layouts/site.blade.php | 2 +-
resources/views/metrics/index.blade.php | 68 ++++++
.../views/metrics/partials/filter.blade.php | 76 ++++++
.../servers/partials/create-server.blade.php | 1 +
resources/views/services/index.blade.php | 4 +-
.../partials/actions/mariadb.blade.php | 7 +-
.../services/partials/actions/mysql.blade.php | 7 +-
.../services/partials/actions/nginx.blade.php | 7 +-
.../services/partials/actions/php.blade.php | 7 +-
.../partials/actions/postgresql.blade.php | 7 +-
.../services/partials/actions/redis.blade.php | 7 +-
.../partials/actions/supervisor.blade.php | 7 +-
.../services/partials/actions/ufw.blade.php | 6 +-
.../partials/actions/vito-agent.blade.php | 6 +
.../views/services/partials/add-ons.blade.php | 33 ---
.../partials/available-services.blade.php | 33 +++
.../partials/installers/mariadb.blade.php | 48 ++++
.../partials/installers/mysql.blade.php | 48 ++++
.../partials/installers/nginx.blade.php | 13 +
.../partials/installers/php.blade.php | 43 ++++
.../partials/installers/postgresql.blade.php | 50 ++++
.../partials/installers/redis.blade.php | 13 +
.../partials/installers/supervisor.blade.php | 13 +
.../partials/installers/ufw.blade.php | 13 +
.../partials/installers/vito-agent.blade.php | 39 +++
.../services/partials/services-list.blade.php | 4 +-
.../views/services/partials/status.blade.php | 2 +-
.../services/partials/unit-actions.blade.php | 42 ----
.../partials/unit-actions/disable.blade.php | 7 +
.../partials/unit-actions/enable.blade.php | 8 +
.../partials/unit-actions/restart.blade.php | 7 +
.../partials/unit-actions/start.blade.php | 8 +
.../partials/unit-actions/stop.blade.php | 8 +
.../partials/unit-actions/uninstall.blade.php | 33 +++
routes/api.php | 4 +
routes/server.php | 5 +
scripts/install.sh | 4 +-
tests/Feature/MetricsTest.php | 40 ++++
tests/Feature/ServicesTest.php | 89 +++++++
tests/Unit/Actions/Service/InstallTest.php | 156 ++++++++++++
tests/Unit/Actions/Service/UninstallTest.php | 86 +++++++
95 files changed, 2423 insertions(+), 341 deletions(-)
create mode 100644 app/Actions/Monitoring/GetMetrics.php
rename app/Actions/Service/{Create.php => Install.php} (71%)
create mode 100644 app/Actions/Service/Uninstall.php
create mode 100644 app/Http/Controllers/API/AgentController.php
create mode 100644 app/Http/Controllers/MetricController.php
create mode 100644 app/Models/Metric.php
create mode 100644 app/SSH/OS/scripts/cleanup.sh
create mode 100644 app/SSH/Services/AbstractService.php
delete mode 100644 app/SSH/Services/AddOnServices/AbstractAddOnService.php
create mode 100644 app/SSH/Services/Database/scripts/mariadb/uninstall.sh
create mode 100755 app/SSH/Services/Database/scripts/mysql/uninstall.sh
create mode 100644 app/SSH/Services/Database/scripts/postgresql/uninstall.sh
create mode 100755 app/SSH/Services/ProcessManager/scripts/supervisor/uninstall-supervisor.sh
create mode 100755 app/SSH/Services/Redis/scripts/uninstall.sh
create mode 100644 app/SSH/Services/VitoAgent/VitoAgent.php
create mode 100644 app/SSH/Services/VitoAgent/scripts/install.sh
create mode 100644 app/SSH/Services/VitoAgent/scripts/uninstall.sh
create mode 100755 app/SSH/Services/Webserver/scripts/nginx/uninstall-nginx.sh
create mode 100644 database/factories/MetricFactory.php
create mode 100644 database/migrations/2024_04_08_212940_create_metrics_table.php
create mode 100644 database/seeders/MetricsSeeder.php
create mode 100644 public/static/images/vito-agent.svg
create mode 100644 resources/views/components/chart.blade.php
create mode 100644 resources/views/components/heroicons/o-calendar.blade.php
create mode 100644 resources/views/components/heroicons/o-chart-bar.blade.php
create mode 100644 resources/views/metrics/index.blade.php
create mode 100644 resources/views/metrics/partials/filter.blade.php
create mode 100644 resources/views/services/partials/actions/vito-agent.blade.php
delete mode 100644 resources/views/services/partials/add-ons.blade.php
create mode 100644 resources/views/services/partials/available-services.blade.php
create mode 100644 resources/views/services/partials/installers/mariadb.blade.php
create mode 100644 resources/views/services/partials/installers/mysql.blade.php
create mode 100644 resources/views/services/partials/installers/nginx.blade.php
create mode 100644 resources/views/services/partials/installers/php.blade.php
create mode 100644 resources/views/services/partials/installers/postgresql.blade.php
create mode 100644 resources/views/services/partials/installers/redis.blade.php
create mode 100644 resources/views/services/partials/installers/supervisor.blade.php
create mode 100644 resources/views/services/partials/installers/ufw.blade.php
create mode 100644 resources/views/services/partials/installers/vito-agent.blade.php
delete mode 100644 resources/views/services/partials/unit-actions.blade.php
create mode 100644 resources/views/services/partials/unit-actions/disable.blade.php
create mode 100644 resources/views/services/partials/unit-actions/enable.blade.php
create mode 100644 resources/views/services/partials/unit-actions/restart.blade.php
create mode 100644 resources/views/services/partials/unit-actions/start.blade.php
create mode 100644 resources/views/services/partials/unit-actions/stop.blade.php
create mode 100644 resources/views/services/partials/unit-actions/uninstall.blade.php
create mode 100644 tests/Feature/MetricsTest.php
create mode 100644 tests/Unit/Actions/Service/InstallTest.php
create mode 100644 tests/Unit/Actions/Service/UninstallTest.php
diff --git a/app/Actions/Monitoring/GetMetrics.php b/app/Actions/Monitoring/GetMetrics.php
new file mode 100644
index 0000000..e393064
--- /dev/null
+++ b/app/Actions/Monitoring/GetMetrics.php
@@ -0,0 +1,150 @@
+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('AVG(load) as load'),
+ DB::raw('AVG(memory_total) as memory_total'),
+ DB::raw('AVG(memory_used) as memory_used'),
+ DB::raw('AVG(memory_free) as memory_free'),
+ DB::raw('AVG(disk_total) as disk_total'),
+ DB::raw('AVG(disk_used) as disk_used'),
+ DB::raw('AVG(disk_free) 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();
+ }
+ }
+}
diff --git a/app/Actions/PHP/ChangeDefaultCli.php b/app/Actions/PHP/ChangeDefaultCli.php
index ff60476..79a311f 100644
--- a/app/Actions/PHP/ChangeDefaultCli.php
+++ b/app/Actions/PHP/ChangeDefaultCli.php
@@ -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]);
diff --git a/app/Actions/PHP/GetPHPIni.php b/app/Actions/PHP/GetPHPIni.php
index ef717d6..d0dd1be 100644
--- a/app/Actions/PHP/GetPHPIni.php
+++ b/app/Actions/PHP/GetPHPIni.php
@@ -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()]
diff --git a/app/Actions/PHP/InstallPHPExtension.php b/app/Actions/PHP/InstallPHPExtension.php
index eba6a41..adf3a43 100755
--- a/app/Actions/PHP/InstallPHPExtension.php
+++ b/app/Actions/PHP/InstallPHPExtension.php
@@ -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;
diff --git a/app/Actions/Service/Create.php b/app/Actions/Service/Install.php
similarity index 71%
rename from app/Actions/Service/Create.php
rename to app/Actions/Service/Install.php
index bfb75cc..51d332b 100644
--- a/app/Actions/Service/Create.php
+++ b/app/Actions/Service/Install.php
@@ -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();
diff --git a/app/Actions/Service/Uninstall.php b/app/Actions/Service/Uninstall.php
new file mode 100644
index 0000000..6528647
--- /dev/null
+++ b/app/Actions/Service/Uninstall.php
@@ -0,0 +1,28 @@
+ $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');
+ }
+}
diff --git a/app/Http/Controllers/API/AgentController.php b/app/Http/Controllers/API/AgentController.php
new file mode 100644
index 0000000..2271d8c
--- /dev/null
+++ b/app/Http/Controllers/API/AgentController.php
@@ -0,0 +1,36 @@
+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();
+ }
+}
diff --git a/app/Http/Controllers/MetricController.php b/app/Http/Controllers/MetricController.php
new file mode 100644
index 0000000..0c7f0db
--- /dev/null
+++ b/app/Http/Controllers/MetricController.php
@@ -0,0 +1,27 @@
+service('monitoring')) {
+ Toast::error('You need to install monitoring service first');
+
+ return redirect()->route('servers.services', $server);
+ }
+
+ return view('metrics.index', [
+ 'server' => $server,
+ 'data' => app(GetMetrics::class)->filter($server, $request->input()),
+ ]);
+ }
+}
diff --git a/app/Http/Controllers/ServiceController.php b/app/Http/Controllers/ServiceController.php
index 6969126..6e696ba 100644
--- a/app/Http/Controllers/ServiceController.php
+++ b/app/Http/Controllers/ServiceController.php
@@ -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!');
diff --git a/app/Models/Metric.php b/app/Models/Metric.php
new file mode 100644
index 0000000..357507a
--- /dev/null
+++ b/app/Models/Metric.php
@@ -0,0 +1,53 @@
+ '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);
+ }
+}
diff --git a/app/Models/Server.php b/app/Models/Server.php
index 7e63f01..c53cc41 100755
--- a/app/Models/Server.php
+++ b/app/Models/Server.php
@@ -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 */
diff --git a/app/Models/Service.php b/app/Models/Service.php
index 4516ae2..39d0d8c 100755
--- a/app/Models/Service.php
+++ b/app/Models/Service.php
@@ -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);
diff --git a/app/SSH/OS/OS.php b/app/SSH/OS/OS.php
index 4f3d886..ae07ce9 100644
--- a/app/SSH/OS/OS.php
+++ b/app/SSH/OS/OS.php
@@ -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'
+ );
+ }
}
diff --git a/app/SSH/OS/scripts/cleanup.sh b/app/SSH/OS/scripts/cleanup.sh
new file mode 100644
index 0000000..1290b67
--- /dev/null
+++ b/app/SSH/OS/scripts/cleanup.sh
@@ -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."
diff --git a/app/SSH/Services/AbstractService.php b/app/SSH/Services/AbstractService.php
new file mode 100644
index 0000000..1b58ccc
--- /dev/null
+++ b/app/SSH/Services/AbstractService.php
@@ -0,0 +1,42 @@
+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,
diff --git a/app/SSH/Services/Database/scripts/mariadb/install-10.3.sh b/app/SSH/Services/Database/scripts/mariadb/install-10.3.sh
index 8a946d0..06ede44 100755
--- a/app/SSH/Services/Database/scripts/mariadb/install-10.3.sh
+++ b/app/SSH/Services/Database/scripts/mariadb/install-10.3.sh
@@ -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
diff --git a/app/SSH/Services/Database/scripts/mariadb/install-10.4.sh b/app/SSH/Services/Database/scripts/mariadb/install-10.4.sh
index ec0dc75..6e1a075 100755
--- a/app/SSH/Services/Database/scripts/mariadb/install-10.4.sh
+++ b/app/SSH/Services/Database/scripts/mariadb/install-10.4.sh
@@ -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
diff --git a/app/SSH/Services/Database/scripts/mariadb/uninstall.sh b/app/SSH/Services/Database/scripts/mariadb/uninstall.sh
new file mode 100644
index 0000000..e0e51b1
--- /dev/null
+++ b/app/SSH/Services/Database/scripts/mariadb/uninstall.sh
@@ -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
diff --git a/app/SSH/Services/Database/scripts/mysql/install-5.7.sh b/app/SSH/Services/Database/scripts/mysql/install-5.7.sh
index 7348d94..29828bd 100755
--- a/app/SSH/Services/Database/scripts/mysql/install-5.7.sh
+++ b/app/SSH/Services/Database/scripts/mysql/install-5.7.sh
@@ -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
diff --git a/app/SSH/Services/Database/scripts/mysql/install-8.0.sh b/app/SSH/Services/Database/scripts/mysql/install-8.0.sh
index 0df07a7..eb6e605 100755
--- a/app/SSH/Services/Database/scripts/mysql/install-8.0.sh
+++ b/app/SSH/Services/Database/scripts/mysql/install-8.0.sh
@@ -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
diff --git a/app/SSH/Services/Database/scripts/mysql/uninstall.sh b/app/SSH/Services/Database/scripts/mysql/uninstall.sh
new file mode 100755
index 0000000..8835b52
--- /dev/null
+++ b/app/SSH/Services/Database/scripts/mysql/uninstall.sh
@@ -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
diff --git a/app/SSH/Services/Database/scripts/postgresql/uninstall.sh b/app/SSH/Services/Database/scripts/postgresql/uninstall.sh
new file mode 100644
index 0000000..a35fe93
--- /dev/null
+++ b/app/SSH/Services/Database/scripts/postgresql/uninstall.sh
@@ -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
diff --git a/app/SSH/Services/Firewall/AbstractFirewall.php b/app/SSH/Services/Firewall/AbstractFirewall.php
index 19b4724..e0358e5 100755
--- a/app/SSH/Services/Firewall/AbstractFirewall.php
+++ b/app/SSH/Services/Firewall/AbstractFirewall.php
@@ -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;
- }
}
diff --git a/app/SSH/Services/Firewall/Ufw.php b/app/SSH/Services/Firewall/Ufw.php
index 471b58a..a5132fc 100755
--- a/app/SSH/Services/Firewall/Ufw.php
+++ b/app/SSH/Services/Firewall/Ufw.php
@@ -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
diff --git a/app/SSH/Services/PHP/PHP.php b/app/SSH/Services/PHP/PHP.php
index 21eb4ab..ac65702 100644
--- a/app/SSH/Services/PHP/PHP.php
+++ b/app/SSH/Services/PHP/PHP.php
@@ -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
diff --git a/app/SSH/Services/ProcessManager/AbstractProcessManager.php b/app/SSH/Services/ProcessManager/AbstractProcessManager.php
index 229ba69..c67b5b8 100644
--- a/app/SSH/Services/ProcessManager/AbstractProcessManager.php
+++ b/app/SSH/Services/ProcessManager/AbstractProcessManager.php
@@ -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.');
+ }
+ },
+ ],
+ ];
}
}
diff --git a/app/SSH/Services/ProcessManager/Supervisor.php b/app/SSH/Services/ProcessManager/Supervisor.php
index baf0ed4..abc5074 100644
--- a/app/SSH/Services/ProcessManager/Supervisor.php
+++ b/app/SSH/Services/ProcessManager/Supervisor.php
@@ -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();
}
/**
diff --git a/app/SSH/Services/ProcessManager/scripts/supervisor/uninstall-supervisor.sh b/app/SSH/Services/ProcessManager/scripts/supervisor/uninstall-supervisor.sh
new file mode 100755
index 0000000..e0c48ee
--- /dev/null
+++ b/app/SSH/Services/ProcessManager/scripts/supervisor/uninstall-supervisor.sh
@@ -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
diff --git a/app/SSH/Services/Redis/Redis.php b/app/SSH/Services/Redis/Redis.php
index 1a1296b..552f219 100644
--- a/app/SSH/Services/Redis/Redis.php
+++ b/app/SSH/Services/Redis/Redis.php
@@ -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();
}
}
diff --git a/app/SSH/Services/Redis/scripts/uninstall.sh b/app/SSH/Services/Redis/scripts/uninstall.sh
new file mode 100755
index 0000000..15aa35b
--- /dev/null
+++ b/app/SSH/Services/Redis/scripts/uninstall.sh
@@ -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
diff --git a/app/SSH/Services/ServiceInterface.php b/app/SSH/Services/ServiceInterface.php
index 23646d0..b7b7822 100644
--- a/app/SSH/Services/ServiceInterface.php
+++ b/app/SSH/Services/ServiceInterface.php
@@ -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;
}
diff --git a/app/SSH/Services/VitoAgent/VitoAgent.php b/app/SSH/Services/VitoAgent/VitoAgent.php
new file mode 100644
index 0000000..96a6b84
--- /dev/null
+++ b/app/SSH/Services/VitoAgent/VitoAgent.php
@@ -0,0 +1,85 @@
+ [
+ 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(),
+ ];
+ }
+
+ public function data(): array
+ {
+ return [
+ 'url' => $this->service->type_data['url'] ?? null,
+ 'secret' => $this->service->type_data['secret'] ?? null,
+ ];
+ }
+
+ 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();
+ }
+}
diff --git a/app/SSH/Services/VitoAgent/scripts/install.sh b/app/SSH/Services/VitoAgent/scripts/install.sh
new file mode 100644
index 0000000..6c7ae32
--- /dev/null
+++ b/app/SSH/Services/VitoAgent/scripts/install.sh
@@ -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"
diff --git a/app/SSH/Services/VitoAgent/scripts/uninstall.sh b/app/SSH/Services/VitoAgent/scripts/uninstall.sh
new file mode 100644
index 0000000..03905ad
--- /dev/null
+++ b/app/SSH/Services/VitoAgent/scripts/uninstall.sh
@@ -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"
diff --git a/app/SSH/Services/Webserver/AbstractWebserver.php b/app/SSH/Services/Webserver/AbstractWebserver.php
index 297b86f..4864f90 100755
--- a/app/SSH/Services/Webserver/AbstractWebserver.php
+++ b/app/SSH/Services/Webserver/AbstractWebserver.php
@@ -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)
- {
- }
}
diff --git a/app/SSH/Services/Webserver/Nginx.php b/app/SSH/Services/Webserver/Nginx.php
index 5b2bf82..8e39d3e 100755
--- a/app/SSH/Services/Webserver/Nginx.php
+++ b/app/SSH/Services/Webserver/Nginx.php
@@ -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
diff --git a/app/SSH/Services/Webserver/scripts/nginx/uninstall-nginx.sh b/app/SSH/Services/Webserver/scripts/nginx/uninstall-nginx.sh
new file mode 100755
index 0000000..ec811bf
--- /dev/null
+++ b/app/SSH/Services/Webserver/scripts/nginx/uninstall-nginx.sh
@@ -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
diff --git a/app/ServerTypes/AbstractType.php b/app/ServerTypes/AbstractType.php
index bf5792a..2143d76 100755
--- a/app/ServerTypes/AbstractType.php
+++ b/app/ServerTypes/AbstractType.php
@@ -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');
diff --git a/app/ServerTypes/Regular.php b/app/ServerTypes/Regular.php
index 9ba276a..fa5cddb 100755
--- a/app/ServerTypes/Regular.php
+++ b/app/ServerTypes/Regular.php
@@ -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',
diff --git a/app/Support/helpers.php b/app/Support/helpers.php
index fd46659..f55450d 100755
--- a/app/Support/helpers.php
+++ b/app/Support/helpers.php
@@ -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);
+}
diff --git a/config/core.php b/config/core.php
index 02843c8..5a6afec 100755
--- a/config/core.php
+++ b/config/core.php
@@ -1,35 +1,5 @@
[
- 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,22 +345,22 @@
\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' => [
diff --git a/database/factories/MetricFactory.php b/database/factories/MetricFactory.php
new file mode 100644
index 0000000..8f2bdb2
--- /dev/null
+++ b/database/factories/MetricFactory.php
@@ -0,0 +1,22 @@
+ 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),
+ ];
+ }
+}
diff --git a/database/migrations/2024_04_08_212940_create_metrics_table.php b/database/migrations/2024_04_08_212940_create_metrics_table.php
new file mode 100644
index 0000000..ab8ab06
--- /dev/null
+++ b/database/migrations/2024_04_08_212940_create_metrics_table.php
@@ -0,0 +1,37 @@
+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');
+ }
+};
diff --git a/database/seeders/MetricsSeeder.php b/database/seeders/MetricsSeeder.php
new file mode 100644
index 0000000..826f6bc
--- /dev/null
+++ b/database/seeders/MetricsSeeder.php
@@ -0,0 +1,32 @@
+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,
+ ]);
+ }
+ }
+}
diff --git a/package-lock.json b/package-lock.json
index 4434558..63f7ebd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -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",
@@ -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",
@@ -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",
diff --git a/package.json b/package.json
index 638b03d..8261e80 100644
--- a/package.json
+++ b/package.json
@@ -22,6 +22,8 @@
"tailwindcss": "^3.1.0",
"tippy.js": "^6.3.7",
"toastr": "^2.1.4",
- "vite": "^4.5.3"
+ "vite": "^4.5.3",
+ "apexcharts": "^3.44.2",
+ "flowbite-datepicker": "^1.2.6"
}
}
diff --git a/public/static/images/vito-agent.svg b/public/static/images/vito-agent.svg
new file mode 100644
index 0000000..ddfda2f
--- /dev/null
+++ b/public/static/images/vito-agent.svg
@@ -0,0 +1,22 @@
+
diff --git a/resources/js/app.js b/resources/js/app.js
index 200621f..85ad3ab 100644
--- a/resources/js/app.js
+++ b/resources/js/app.js
@@ -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', {
diff --git a/resources/views/components/chart.blade.php b/resources/views/components/chart.blade.php
new file mode 100644
index 0000000..fb40159
--- /dev/null
+++ b/resources/views/components/chart.blade.php
@@ -0,0 +1,103 @@
+@props([
+ "id",
+ "type",
+ "title",
+ "color",
+ "sets",
+ "categories",
+ "toolbar" => false,
+])
+