mirror of
https://github.com/vitodeploy/vito.git
synced 2025-04-17 17:01:37 +00:00
parent
87ec0af697
commit
052e28d2e3
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('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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
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();
|
||||
}
|
||||
}
|
27
app/Http/Controllers/MetricController.php
Normal file
27
app/Http/Controllers/MetricController.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Actions\Monitoring\GetMetrics;
|
||||
use App\Facades\Toast;
|
||||
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
|
||||
{
|
||||
if (! $server->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()),
|
||||
]);
|
||||
}
|
||||
}
|
@ -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!');
|
||||
|
||||
|
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;
|
||||
}
|
||||
|
85
app/SSH/Services/VitoAgent/VitoAgent.php
Normal file
85
app/SSH/Services/VitoAgent/VitoAgent.php
Normal file
@ -0,0 +1,85 @@
|
||||
<?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(),
|
||||
];
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
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
|
||||
|
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);
|
||||
}
|
||||
|
216
config/core.php
216
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,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' => [
|
||||
|
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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
225
package-lock.json
generated
225
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",
|
||||
@ -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",
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
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 |
@ -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', {
|
||||
|
103
resources/views/components/chart.blade.php
Normal file
103
resources/views/components/chart.blade.php
Normal file
@ -0,0 +1,103 @@
|
||||
@props([
|
||||
"id",
|
||||
"type",
|
||||
"title",
|
||||
"color",
|
||||
"sets",
|
||||
"categories",
|
||||
"toolbar" => false,
|
||||
])
|
||||
<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: {
|
||||
formatter: function (value) {
|
||||
return parseInt(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (document.getElementById('{{ $id }}') && typeof ApexCharts !== 'undefined') {
|
||||
const chart = new ApexCharts(document.getElementById('{{ $id }}'), options);
|
||||
chart.render();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</x-simple-card>
|
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 |
@ -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,19 +52,17 @@ 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])"
|
||||
:active="request()->routeIs('servers.php')"
|
||||
>
|
||||
<x-heroicon name="o-code-bracket" class="h-6 w-6" />
|
||||
<span class="ml-2">
|
||||
{{ __("PHP") }}
|
||||
</span>
|
||||
</x-sidebar-link>
|
||||
</li>
|
||||
@endif
|
||||
<li>
|
||||
<x-sidebar-link
|
||||
:href="route('servers.php', ['server' => $server])"
|
||||
:active="request()->routeIs('servers.php')"
|
||||
>
|
||||
<x-heroicon name="o-code-bracket" class="h-6 w-6" />
|
||||
<span class="ml-2">
|
||||
{{ __("PHP") }}
|
||||
</span>
|
||||
</x-sidebar-link>
|
||||
</li>
|
||||
|
||||
@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])"
|
||||
|
68
resources/views/metrics/index.blade.php
Normal file
68
resources/views/metrics/index.blade.php
Normal file
@ -0,0 +1,68 @@
|
||||
<x-server-layout :server="$server">
|
||||
<x-slot name="pageTitle">{{ $server->name }} - Metrics</x-slot>
|
||||
|
||||
@include("metrics.partials.filter")
|
||||
|
||||
@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"
|
||||
class="h-[200px] !p-0"
|
||||
/>
|
||||
|
||||
<x-chart
|
||||
id="disk-usage"
|
||||
type="area"
|
||||
title="Disk"
|
||||
:sets="[$diskSets]"
|
||||
:categories="$data['metrics']->pluck('date')->toArray()"
|
||||
color="#109618"
|
||||
class="h-[200px] !p-0"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mt-10">
|
||||
<x-chart
|
||||
id="resource-usage"
|
||||
type="line"
|
||||
title="Resource Usage"
|
||||
:sets="[$cpuSets, $memorySets, $diskSets]"
|
||||
:categories="$data['metrics']->pluck('date')->toArray()"
|
||||
color="#109618"
|
||||
:toolbar="true"
|
||||
class="h-[400px] !px-0 !pt-0"
|
||||
/>
|
||||
</div>
|
||||
</x-server-layout>
|
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 }}
|
||||
|
@ -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")
|
||||
|
@ -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,5 @@
|
||||
@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")
|
||||
|
@ -0,0 +1,6 @@
|
||||
@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,33 +0,0 @@
|
||||
<div>
|
||||
<x-card-header>
|
||||
<x-slot name="title">Supported Services</x-slot>
|
||||
<x-slot name="description">Here you can find the supported services to install</x-slot>
|
||||
<x-slot name="aside"></x-slot>
|
||||
</x-card-header>
|
||||
|
||||
<div class="grid grid-cols-1 gap-5 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||
@foreach (config("core.add_on_services") as $addOn)
|
||||
<div
|
||||
class="relative flex h-auto flex-col items-center justify-between space-y-3 rounded-b-md rounded-t-md border border-gray-200 bg-white text-center dark:border-gray-700 dark:bg-gray-800"
|
||||
>
|
||||
<div class="space-y-3 p-5">
|
||||
<div class="flex items-center justify-center">
|
||||
<img src="{{ asset("static/images/" . $addOn . ".svg") }}" class="h-20 w-20" alt="" />
|
||||
</div>
|
||||
<div class="flex flex-grow flex-col items-start justify-center">
|
||||
<div class="flex items-center">
|
||||
<div class="flex items-center text-lg">
|
||||
{{ $addOn }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex w-full items-center justify-between rounded-b-md border-t border-t-gray-200 bg-gray-50 p-2 dark:border-t-gray-600 dark:bg-gray-700"
|
||||
>
|
||||
@include("services.partials.add-on-installers." . $addOn)
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,33 @@
|
||||
<div>
|
||||
<x-card-header>
|
||||
<x-slot name="title">Supported Services</x-slot>
|
||||
<x-slot name="description">Here you can find the supported services to install</x-slot>
|
||||
<x-slot name="aside"></x-slot>
|
||||
</x-card-header>
|
||||
|
||||
<div class="grid grid-cols-1 gap-5 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||
@foreach (config("core.service_handlers") as $key => $addOn)
|
||||
@if (! $server->services()->where("name", $key)->exists())
|
||||
<div
|
||||
class="relative flex h-auto flex-col items-center justify-between space-y-3 rounded-b-md rounded-t-md border border-gray-200 bg-white text-center dark:border-gray-700 dark:bg-gray-800"
|
||||
>
|
||||
<div class="space-y-3 p-5">
|
||||
<div class="flex items-center justify-center">
|
||||
<img src="{{ asset("static/images/" . $key . ".svg") }}" class="h-20 w-20" alt="" />
|
||||
</div>
|
||||
<div class="flex flex-grow flex-col items-center justify-center">
|
||||
<div class="flex items-center justify-center text-center text-lg">
|
||||
{{ $key }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex w-full items-center justify-between rounded-b-md border-t border-t-gray-200 bg-gray-50 p-2 dark:border-t-gray-600 dark:bg-gray-700"
|
||||
>
|
||||
@include("services.partials.installers." . $key)
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,48 @@
|
||||
<x-secondary-button class="!w-full" x-on:click="$dispatch('open-modal', 'install-mariadb')">Install</x-secondary-button>
|
||||
@push("modals")
|
||||
<x-modal name="install-mariadb">
|
||||
<form
|
||||
id="install-mariadb-form"
|
||||
hx-post="{{ route("servers.services.install", ["server" => $server]) }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-select="#install-mariadb-form"
|
||||
class="p-6"
|
||||
>
|
||||
@csrf
|
||||
<input type="hidden" name="name" value="mariadb" />
|
||||
<input type="hidden" name="type" value="database" />
|
||||
|
||||
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||
{{ __("Install mariadb") }}
|
||||
</h2>
|
||||
|
||||
<div class="mt-6">
|
||||
<x-input-label for="version" value="Version" />
|
||||
<x-select-input id="version" name="version" class="mt-1 w-full">
|
||||
@foreach (collect(config("core.databases_name"))->filter(fn ($value) => $value == "mariadb") as $db => $value)
|
||||
<option value="{{ config("core.databases_version")[$db] }}">
|
||||
{{ config("core.databases_name")[$db] }} {{ config("core.databases_version")[$db] }}
|
||||
</option>
|
||||
@endforeach
|
||||
</x-select-input>
|
||||
@error("version")
|
||||
<x-input-error class="mt-2" :messages="$message" />
|
||||
@enderror
|
||||
|
||||
@error("type")
|
||||
<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-install-mariadb" hx-disable class="ml-3">
|
||||
{{ __("Install") }}
|
||||
</x-primary-button>
|
||||
</div>
|
||||
</form>
|
||||
</x-modal>
|
||||
@endpush
|
48
resources/views/services/partials/installers/mysql.blade.php
Normal file
48
resources/views/services/partials/installers/mysql.blade.php
Normal file
@ -0,0 +1,48 @@
|
||||
<x-secondary-button class="!w-full" x-on:click="$dispatch('open-modal', 'install-mysql')">Install</x-secondary-button>
|
||||
@push("modals")
|
||||
<x-modal name="install-mysql">
|
||||
<form
|
||||
id="install-mysql-form"
|
||||
hx-post="{{ route("servers.services.install", ["server" => $server]) }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-select="#install-mysql-form"
|
||||
class="p-6"
|
||||
>
|
||||
@csrf
|
||||
<input type="hidden" name="name" value="mysql" />
|
||||
<input type="hidden" name="type" value="database" />
|
||||
|
||||
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||
{{ __("Install Mysql") }}
|
||||
</h2>
|
||||
|
||||
<div class="mt-6">
|
||||
<x-input-label for="version" value="Version" />
|
||||
<x-select-input id="version" name="version" class="mt-1 w-full">
|
||||
@foreach (collect(config("core.databases_name"))->filter(fn ($value) => $value == "mysql") as $db => $value)
|
||||
<option value="{{ config("core.databases_version")[$db] }}">
|
||||
{{ config("core.databases_name")[$db] }} {{ config("core.databases_version")[$db] }}
|
||||
</option>
|
||||
@endforeach
|
||||
</x-select-input>
|
||||
@error("version")
|
||||
<x-input-error class="mt-2" :messages="$message" />
|
||||
@enderror
|
||||
|
||||
@error("type")
|
||||
<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-install-mysql" hx-disable class="ml-3">
|
||||
{{ __("Install") }}
|
||||
</x-primary-button>
|
||||
</div>
|
||||
</form>
|
||||
</x-modal>
|
||||
@endpush
|
13
resources/views/services/partials/installers/nginx.blade.php
Normal file
13
resources/views/services/partials/installers/nginx.blade.php
Normal file
@ -0,0 +1,13 @@
|
||||
<form
|
||||
id="install-nginx"
|
||||
class="w-full"
|
||||
hx-post="{{ route("servers.services.install", ["server" => $server]) }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-select="#install-nginx"
|
||||
>
|
||||
@csrf
|
||||
<input type="hidden" name="name" value="nginx" />
|
||||
<input type="hidden" name="type" value="webserver" />
|
||||
<input type="hidden" name="version" value="latest" />
|
||||
<x-secondary-button class="!w-full" hx-disable>Install</x-secondary-button>
|
||||
</form>
|
43
resources/views/services/partials/installers/php.blade.php
Normal file
43
resources/views/services/partials/installers/php.blade.php
Normal file
@ -0,0 +1,43 @@
|
||||
<x-secondary-button class="!w-full" x-on:click="$dispatch('open-modal', 'install-php')">Install</x-secondary-button>
|
||||
@push("modals")
|
||||
<x-modal name="install-php">
|
||||
<form
|
||||
id="install-php-form"
|
||||
hx-post="{{ route("servers.services.install", ["server" => $server]) }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-select="#install-php-form"
|
||||
class="p-6"
|
||||
>
|
||||
@csrf
|
||||
<input type="hidden" name="type" value="php" />
|
||||
|
||||
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||
{{ __("Install PHP") }}
|
||||
</h2>
|
||||
|
||||
<div class="mt-6">
|
||||
<x-input-label for="version" value="Version" />
|
||||
<x-select-input id="version" name="version" class="mt-1 w-full">
|
||||
@foreach (config("core.php_versions") as $p)
|
||||
<option value="{{ $p }}">
|
||||
{{ $p }}
|
||||
</option>
|
||||
@endforeach
|
||||
</x-select-input>
|
||||
@error("version")
|
||||
<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-install-php" hx-disable class="ml-3">
|
||||
{{ __("Install") }}
|
||||
</x-primary-button>
|
||||
</div>
|
||||
</form>
|
||||
</x-modal>
|
||||
@endpush
|
@ -0,0 +1,50 @@
|
||||
<x-secondary-button class="!w-full" x-on:click="$dispatch('open-modal', 'install-postgresql')">
|
||||
Install
|
||||
</x-secondary-button>
|
||||
@push("modals")
|
||||
<x-modal name="install-postgresql">
|
||||
<form
|
||||
id="install-postgresql-form"
|
||||
hx-post="{{ route("servers.services.install", ["server" => $server]) }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-select="#install-postgresql-form"
|
||||
class="p-6"
|
||||
>
|
||||
@csrf
|
||||
<input type="hidden" name="name" value="postgresql" />
|
||||
<input type="hidden" name="type" value="database" />
|
||||
|
||||
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||
{{ __("Install postgresql") }}
|
||||
</h2>
|
||||
|
||||
<div class="mt-6">
|
||||
<x-input-label for="version" value="Version" />
|
||||
<x-select-input id="version" name="version" class="mt-1 w-full">
|
||||
@foreach (collect(config("core.databases_name"))->filter(fn ($value) => $value == "postgresql") as $db => $value)
|
||||
<option value="{{ config("core.databases_version")[$db] }}">
|
||||
{{ config("core.databases_name")[$db] }} {{ config("core.databases_version")[$db] }}
|
||||
</option>
|
||||
@endforeach
|
||||
</x-select-input>
|
||||
@error("version")
|
||||
<x-input-error class="mt-2" :messages="$message" />
|
||||
@enderror
|
||||
|
||||
@error("type")
|
||||
<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-install-postgresql" hx-disable class="ml-3">
|
||||
{{ __("Install") }}
|
||||
</x-primary-button>
|
||||
</div>
|
||||
</form>
|
||||
</x-modal>
|
||||
@endpush
|
13
resources/views/services/partials/installers/redis.blade.php
Normal file
13
resources/views/services/partials/installers/redis.blade.php
Normal file
@ -0,0 +1,13 @@
|
||||
<form
|
||||
id="install-redis"
|
||||
class="w-full"
|
||||
hx-post="{{ route("servers.services.install", ["server" => $server]) }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-select="#install-redis"
|
||||
>
|
||||
@csrf
|
||||
<input type="hidden" name="name" value="redis" />
|
||||
<input type="hidden" name="type" value="memory_database" />
|
||||
<input type="hidden" name="version" value="latest" />
|
||||
<x-secondary-button class="!w-full" hx-disable>Install</x-secondary-button>
|
||||
</form>
|
@ -0,0 +1,13 @@
|
||||
<form
|
||||
id="install-supervisor"
|
||||
class="w-full"
|
||||
hx-post="{{ route("servers.services.install", ["server" => $server]) }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-select="#install-supervisor"
|
||||
>
|
||||
@csrf
|
||||
<input type="hidden" name="name" value="supervisor" />
|
||||
<input type="hidden" name="type" value="process_manager" />
|
||||
<input type="hidden" name="version" value="latest" />
|
||||
<x-secondary-button class="!w-full" hx-disable>Install</x-secondary-button>
|
||||
</form>
|
13
resources/views/services/partials/installers/ufw.blade.php
Normal file
13
resources/views/services/partials/installers/ufw.blade.php
Normal file
@ -0,0 +1,13 @@
|
||||
<form
|
||||
id="install-ufw"
|
||||
class="w-full"
|
||||
hx-post="{{ route("servers.services.install", ["server" => $server]) }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-select="#install-ufw"
|
||||
>
|
||||
@csrf
|
||||
<input type="hidden" name="name" value="ufw" />
|
||||
<input type="hidden" name="type" value="firewall" />
|
||||
<input type="hidden" name="version" value="latest" />
|
||||
<x-secondary-button class="!w-full" hx-disable>Install</x-secondary-button>
|
||||
</form>
|
@ -0,0 +1,39 @@
|
||||
<x-secondary-button class="!w-full" x-on:click="$dispatch('open-modal', 'install-vito-agent')">
|
||||
Install
|
||||
</x-secondary-button>
|
||||
@push("modals")
|
||||
<x-modal name="install-vito-agent">
|
||||
<form
|
||||
id="install-vito-agent-form"
|
||||
hx-post="{{ route("servers.services.install", ["server" => $server]) }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-select="#install-vito-agent-form"
|
||||
class="p-6"
|
||||
>
|
||||
@csrf
|
||||
<input type="hidden" name="name" value="vito-agent" />
|
||||
<input type="hidden" name="type" value="monitoring" />
|
||||
<input type="hidden" name="version" value="latest" />
|
||||
|
||||
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||
{{ __("Install Vito Agent") }}
|
||||
</h2>
|
||||
|
||||
<div class="mt-6">
|
||||
<x-alert-warning>
|
||||
Vito Agent is only works if you are running your Vito instance on a cloud not local!
|
||||
</x-alert-warning>
|
||||
</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-vito-agent" hx-disable class="ml-3">
|
||||
{{ __("Install") }}
|
||||
</x-primary-button>
|
||||
</div>
|
||||
</form>
|
||||
</x-modal>
|
||||
@endpush
|
@ -15,10 +15,10 @@ class="relative flex h-auto flex-col items-center justify-between space-y-3 roun
|
||||
@include("services.partials.status", ["status" => $service->status])
|
||||
</div>
|
||||
<div class="space-y-3 p-5">
|
||||
<div class="flex items-center justify-center">
|
||||
<div class="mt-5 flex items-center justify-center">
|
||||
<img
|
||||
src="{{ asset("static/images/" . $service->name . ".svg") }}"
|
||||
class="h-20 w-20"
|
||||
class="h-[70px] w-[70px]"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
|
@ -11,7 +11,7 @@
|
||||
@endif
|
||||
|
||||
@if ($status == \App\Enums\ServiceStatus::UNINSTALLING)
|
||||
<x-status status="danger">{{ $status }}</x-status>
|
||||
<x-status status="warning">{{ $status }}</x-status>
|
||||
@endif
|
||||
|
||||
@if ($status == \App\Enums\ServiceStatus::FAILED)
|
||||
|
@ -1,42 +0,0 @@
|
||||
<x-icon-button
|
||||
data-tooltip="Restart Service"
|
||||
class="cursor-pointer"
|
||||
href="{{ route('servers.services.restart', ['server' => $server, 'service' => $service]) }}"
|
||||
>
|
||||
<x-heroicon name="o-arrow-path" class="h-5 w-5" />
|
||||
</x-icon-button>
|
||||
|
||||
<x-icon-button
|
||||
:disabled="$service->status != \App\Enums\ServiceStatus::STOPPED"
|
||||
data-tooltip="Start Service"
|
||||
class="cursor-pointer"
|
||||
href="{{ route('servers.services.start', ['server' => $server, 'service' => $service]) }}"
|
||||
>
|
||||
<x-heroicon name="o-play" class="h-5 w-5 text-green-400" />
|
||||
</x-icon-button>
|
||||
|
||||
<x-icon-button
|
||||
data-tooltip="Stop Service"
|
||||
:disabled="$service->status != \App\Enums\ServiceStatus::READY"
|
||||
class="cursor-pointer"
|
||||
href="{{ route('servers.services.stop', ['server' => $server, 'service' => $service]) }}"
|
||||
>
|
||||
<x-heroicon name="o-stop" class="h-5 w-5 text-red-400" />
|
||||
</x-icon-button>
|
||||
|
||||
<x-icon-button
|
||||
:disabled="$service->status != \App\Enums\ServiceStatus::DISABLED"
|
||||
data-tooltip="Enable Service"
|
||||
class="cursor-pointer"
|
||||
href="{{ route('servers.services.enable', ['server' => $server, 'service' => $service]) }}"
|
||||
>
|
||||
<x-heroicon name="o-check" class="h-5 w-5" />
|
||||
</x-icon-button>
|
||||
|
||||
<x-icon-button
|
||||
data-tooltip="Disable Service"
|
||||
class="cursor-pointer"
|
||||
href="{{ route('servers.services.disable', ['server' => $server, 'service' => $service]) }}"
|
||||
>
|
||||
<x-heroicon name="o-no-symbol" class="h-5 w-5" />
|
||||
</x-icon-button>
|
@ -0,0 +1,7 @@
|
||||
<x-icon-button
|
||||
data-tooltip="Disable Service"
|
||||
class="cursor-pointer"
|
||||
href="{{ route('servers.services.disable', ['server' => $server, 'service' => $service]) }}"
|
||||
>
|
||||
<x-heroicon name="o-no-symbol" class="h-5 w-5" />
|
||||
</x-icon-button>
|
@ -0,0 +1,8 @@
|
||||
<x-icon-button
|
||||
:disabled="$service->status != \App\Enums\ServiceStatus::DISABLED"
|
||||
data-tooltip="Enable Service"
|
||||
class="cursor-pointer"
|
||||
href="{{ route('servers.services.enable', ['server' => $server, 'service' => $service]) }}"
|
||||
>
|
||||
<x-heroicon name="o-check" class="h-5 w-5" />
|
||||
</x-icon-button>
|
@ -0,0 +1,7 @@
|
||||
<x-icon-button
|
||||
data-tooltip="Restart Service"
|
||||
class="cursor-pointer"
|
||||
href="{{ route('servers.services.restart', ['server' => $server, 'service' => $service]) }}"
|
||||
>
|
||||
<x-heroicon name="o-arrow-path" class="h-5 w-5" />
|
||||
</x-icon-button>
|
@ -0,0 +1,8 @@
|
||||
<x-icon-button
|
||||
:disabled="$service->status != \App\Enums\ServiceStatus::STOPPED"
|
||||
data-tooltip="Start Service"
|
||||
class="cursor-pointer"
|
||||
href="{{ route('servers.services.start', ['server' => $server, 'service' => $service]) }}"
|
||||
>
|
||||
<x-heroicon name="o-play" class="h-5 w-5 text-green-400" />
|
||||
</x-icon-button>
|
@ -0,0 +1,8 @@
|
||||
<x-icon-button
|
||||
data-tooltip="Stop Service"
|
||||
:disabled="$service->status != \App\Enums\ServiceStatus::READY"
|
||||
class="cursor-pointer"
|
||||
href="{{ route('servers.services.stop', ['server' => $server, 'service' => $service]) }}"
|
||||
>
|
||||
<x-heroicon name="o-stop" class="h-5 w-5 text-red-400" />
|
||||
</x-icon-button>
|
@ -0,0 +1,33 @@
|
||||
<x-icon-button
|
||||
data-tooltip="Uninstall Service"
|
||||
class="cursor-pointer"
|
||||
x-on:click="$dispatch('open-modal', 'uninstall-{{ $service->id }}')"
|
||||
>
|
||||
<x-heroicon name="o-trash" class="h-5 w-5" />
|
||||
</x-icon-button>
|
||||
@push("modals")
|
||||
<x-modal name="uninstall-{{ $service->id }}">
|
||||
<form
|
||||
id="uninstall-{{ $service->id }}-form"
|
||||
hx-post="{{ route("servers.services.uninstall", ["server" => $server, "service" => $service]) }}"
|
||||
hx-target="#uninstall-{{ $service->id }}-form"
|
||||
hx-select="#uninstall-{{ $service->id }}-form"
|
||||
hx-swap="outerHTML"
|
||||
class="p-6"
|
||||
>
|
||||
@csrf
|
||||
@method("delete")
|
||||
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">Confirm</h2>
|
||||
<p>Are you sure that you want to uninstall this service?</p>
|
||||
|
||||
@error("service")
|
||||
<x-input-error class="mt-2" :messages="$message" />
|
||||
@enderror
|
||||
|
||||
<div class="mt-6 flex justify-end">
|
||||
<x-secondary-button type="button" x-on:click="$dispatch('close')">Cancel</x-secondary-button>
|
||||
<x-danger-button class="ml-3" hx-disable>Confirm</x-danger-button>
|
||||
</div>
|
||||
</form>
|
||||
</x-modal>
|
||||
@endpush
|
@ -1,9 +1,13 @@
|
||||
<?php
|
||||
|
||||
// git hook
|
||||
|
||||
use App\Http\Controllers\API\AgentController;
|
||||
use App\Http\Controllers\API\GitHookController;
|
||||
use App\Http\Controllers\API\HealthController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::get('health', HealthController::class)->name('api.health');
|
||||
Route::any('git-hooks', GitHookController::class)->name('api.git-hooks');
|
||||
|
||||
Route::post('servers/{server}/agent/{id}', AgentController::class)->name('api.servers.agent');
|
||||
|
@ -7,6 +7,7 @@
|
||||
use App\Http\Controllers\DatabaseController;
|
||||
use App\Http\Controllers\DatabaseUserController;
|
||||
use App\Http\Controllers\FirewallController;
|
||||
use App\Http\Controllers\MetricController;
|
||||
use App\Http\Controllers\PHPController;
|
||||
use App\Http\Controllers\QueueController;
|
||||
use App\Http\Controllers\ServerController;
|
||||
@ -127,6 +128,10 @@
|
||||
Route::get('/{server}/services/{service}/enable', [ServiceController::class, 'enable'])->name('servers.services.enable');
|
||||
Route::get('/{server}/services/{service}/disable', [ServiceController::class, 'disable'])->name('servers.services.disable');
|
||||
Route::post('/{server}/services/install', [ServiceController::class, 'install'])->name('servers.services.install');
|
||||
Route::delete('/{server}/services/{service}/uninstall', [ServiceController::class, 'uninstall'])->name('servers.services.uninstall');
|
||||
|
||||
// metrics
|
||||
Route::get('/{server}/metrics', [MetricController::class, 'index'])->name('servers.metrics');
|
||||
|
||||
// console
|
||||
Route::get('/{server}/console', [ConsoleController::class, 'index'])->name('servers.console');
|
||||
|
@ -192,7 +192,6 @@ touch /home/${V_USERNAME}/.logs/workers/worker.log
|
||||
echo "${V_WORKER_CONFIG}" | tee /etc/supervisor/conf.d/worker.conf
|
||||
supervisorctl reread
|
||||
supervisorctl update
|
||||
supervisorctl start worker:*
|
||||
|
||||
# setup cronjobs
|
||||
echo "* * * * * cd /home/${V_USERNAME}/vito && php artisan schedule:run >> /dev/null 2>&1" | sudo -u ${V_USERNAME} crontab -
|
||||
@ -203,6 +202,9 @@ chown -R ${V_USERNAME}:${V_USERNAME} /home/${V_USERNAME}
|
||||
# cache
|
||||
php artisan config:cache
|
||||
|
||||
# start worker
|
||||
supervisorctl start worker:*
|
||||
|
||||
# print info
|
||||
echo "🎉 Congratulations!"
|
||||
echo "✅ SSH User: ${V_USERNAME}"
|
||||
|
40
tests/Feature/MetricsTest.php
Normal file
40
tests/Feature/MetricsTest.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Enums\ServiceStatus;
|
||||
use App\Models\Service;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class MetricsTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_visit_metrics(): void
|
||||
{
|
||||
$this->actingAs($this->user);
|
||||
|
||||
Service::factory()->create([
|
||||
'server_id' => $this->server->id,
|
||||
'name' => 'vito-agent',
|
||||
'type' => 'monitoring',
|
||||
'version' => 'latest',
|
||||
'status' => ServiceStatus::READY,
|
||||
]);
|
||||
|
||||
$this->get(route('servers.metrics', ['server' => $this->server]))
|
||||
->assertSee('CPU Load')
|
||||
->assertSee('Memory Usage')
|
||||
->assertSee('Disk Usage')
|
||||
->assertSee('Resource Usage');
|
||||
}
|
||||
|
||||
public function test_cannot_visit_metrics(): void
|
||||
{
|
||||
$this->actingAs($this->user);
|
||||
|
||||
$this->get(route('servers.metrics', ['server' => $this->server]))
|
||||
->assertRedirect(route('servers.services', ['server' => $this->server]));
|
||||
}
|
||||
}
|
@ -4,7 +4,10 @@
|
||||
|
||||
use App\Enums\ServiceStatus;
|
||||
use App\Facades\SSH;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ServicesTest extends TestCase
|
||||
@ -22,6 +25,7 @@ public function test_see_services_list(): void
|
||||
->assertSee('php')
|
||||
->assertSee('supervisor')
|
||||
->assertSee('redis')
|
||||
->assertSee('vito-agent')
|
||||
->assertSee('ufw');
|
||||
}
|
||||
|
||||
@ -235,6 +239,45 @@ public function test_failed_to_disable_service(string $name): void
|
||||
$this->assertEquals(ServiceStatus::FAILED, $service->status);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider installData
|
||||
*/
|
||||
public function test_install_service(string $name, string $type, string $version): void
|
||||
{
|
||||
Http::fake([
|
||||
'https://api.github.com/repos/vito/vito-agent/releases/latest' => Http::response([
|
||||
'tag_name' => '0.1.0',
|
||||
]),
|
||||
]);
|
||||
SSH::fake('Active: active');
|
||||
|
||||
$this->actingAs($this->user);
|
||||
|
||||
$server = Server::factory()->create([
|
||||
'user_id' => $this->user->id,
|
||||
'project_id' => $this->user->current_project_id,
|
||||
]);
|
||||
|
||||
$keys = $server->sshKey();
|
||||
if (! File::exists($keys['public_key_path']) || ! File::exists($keys['private_key_path'])) {
|
||||
$server->provider()->generateKeyPair();
|
||||
}
|
||||
$this->post(route('servers.services.install', [
|
||||
'server' => $server,
|
||||
]), [
|
||||
'name' => $name,
|
||||
'type' => $type,
|
||||
'version' => $version,
|
||||
])->assertSessionDoesntHaveErrors();
|
||||
|
||||
$this->assertDatabaseHas('services', [
|
||||
'server_id' => $server->id,
|
||||
'name' => $name,
|
||||
'type' => $type,
|
||||
'status' => ServiceStatus::READY,
|
||||
]);
|
||||
}
|
||||
|
||||
public static function data(): array
|
||||
{
|
||||
return [
|
||||
@ -247,4 +290,50 @@ public static function data(): array
|
||||
['mysql'],
|
||||
];
|
||||
}
|
||||
|
||||
public static function installData(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'nginx',
|
||||
'webserver',
|
||||
'latest',
|
||||
],
|
||||
[
|
||||
'php',
|
||||
'php',
|
||||
'7.4',
|
||||
],
|
||||
[
|
||||
'supervisor',
|
||||
'process_manager',
|
||||
'latest',
|
||||
],
|
||||
[
|
||||
'redis',
|
||||
'memory_database',
|
||||
'latest',
|
||||
],
|
||||
[
|
||||
'mysql',
|
||||
'database',
|
||||
'8.0',
|
||||
],
|
||||
[
|
||||
'mariadb',
|
||||
'database',
|
||||
'10.4',
|
||||
],
|
||||
[
|
||||
'postgresql',
|
||||
'database',
|
||||
'16',
|
||||
],
|
||||
[
|
||||
'vito-agent',
|
||||
'monitoring',
|
||||
'latest',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
156
tests/Unit/Actions/Service/InstallTest.php
Normal file
156
tests/Unit/Actions/Service/InstallTest.php
Normal file
@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Actions\Service;
|
||||
|
||||
use App\Actions\Service\Install;
|
||||
use App\Enums\ServiceStatus;
|
||||
use App\Facades\SSH;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Tests\TestCase;
|
||||
|
||||
class InstallTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_install_vito_agent(): void
|
||||
{
|
||||
SSH::fake('Active: active');
|
||||
Http::fake([
|
||||
'https://api.github.com/repos/vitodeploy/agent/tags' => Http::response([['name' => '0.1.0']]),
|
||||
]);
|
||||
|
||||
$service = app(Install::class)->install($this->server, [
|
||||
'type' => 'monitoring',
|
||||
'name' => 'vito-agent',
|
||||
'version' => 'latest',
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('services', [
|
||||
'server_id' => $this->server->id,
|
||||
'name' => 'vito-agent',
|
||||
'type' => 'monitoring',
|
||||
'version' => '0.1.0',
|
||||
'status' => ServiceStatus::READY,
|
||||
]);
|
||||
|
||||
$this->assertNotNull($service->type_data);
|
||||
}
|
||||
|
||||
public function test_install_vito_agent_failed(): void
|
||||
{
|
||||
$this->expectExceptionMessage('Failed to fetch tags');
|
||||
SSH::fake('Active: inactive');
|
||||
Http::fake([
|
||||
'https://api.github.com/repos/vitodeploy/agent/tags' => Http::response([]),
|
||||
]);
|
||||
app(Install::class)->install($this->server, [
|
||||
'type' => 'monitoring',
|
||||
'name' => 'vito-agent',
|
||||
'version' => 'latest',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_install_nginx(): void
|
||||
{
|
||||
$this->server->webserver()->delete();
|
||||
|
||||
SSH::fake('Active: active');
|
||||
|
||||
$service = app(Install::class)->install($this->server, [
|
||||
'type' => 'webserver',
|
||||
'name' => 'nginx',
|
||||
'version' => 'latest',
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('services', [
|
||||
'server_id' => $this->server->id,
|
||||
'name' => 'nginx',
|
||||
'type' => 'webserver',
|
||||
'version' => 'latest',
|
||||
'status' => ServiceStatus::READY,
|
||||
]);
|
||||
|
||||
$this->assertNotNull($service->type_data);
|
||||
}
|
||||
|
||||
public function test_install_mysql(): void
|
||||
{
|
||||
$this->server->database()->delete();
|
||||
|
||||
SSH::fake('Active: active');
|
||||
|
||||
$service = app(Install::class)->install($this->server, [
|
||||
'type' => 'database',
|
||||
'name' => 'mysql',
|
||||
'version' => '8.0',
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('services', [
|
||||
'server_id' => $this->server->id,
|
||||
'name' => 'mysql',
|
||||
'type' => 'database',
|
||||
'version' => '8.0',
|
||||
'status' => ServiceStatus::READY,
|
||||
]);
|
||||
|
||||
$this->assertNotNull($service->type_data);
|
||||
}
|
||||
|
||||
public function test_install_mysql_failed(): void
|
||||
{
|
||||
$this->expectException(ValidationException::class);
|
||||
app(Install::class)->install($this->server, [
|
||||
'type' => 'database',
|
||||
'name' => 'mysql',
|
||||
'version' => '8.0',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_install_supervisor(): void
|
||||
{
|
||||
$this->server->processManager()->delete();
|
||||
|
||||
SSH::fake('Active: active');
|
||||
|
||||
$service = app(Install::class)->install($this->server, [
|
||||
'type' => 'process_manager',
|
||||
'name' => 'supervisor',
|
||||
'version' => 'latest',
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('services', [
|
||||
'server_id' => $this->server->id,
|
||||
'name' => 'supervisor',
|
||||
'type' => 'process_manager',
|
||||
'version' => 'latest',
|
||||
'status' => ServiceStatus::READY,
|
||||
]);
|
||||
|
||||
$this->assertNotNull($service->type_data);
|
||||
}
|
||||
|
||||
public function test_install_redis(): void
|
||||
{
|
||||
$this->server->memoryDatabase()->delete();
|
||||
|
||||
SSH::fake('Active: active');
|
||||
|
||||
$service = app(Install::class)->install($this->server, [
|
||||
'type' => 'memory_database',
|
||||
'name' => 'redis',
|
||||
'version' => 'latest',
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('services', [
|
||||
'server_id' => $this->server->id,
|
||||
'name' => 'redis',
|
||||
'type' => 'memory_database',
|
||||
'version' => 'latest',
|
||||
'status' => ServiceStatus::READY,
|
||||
]);
|
||||
|
||||
$this->assertNotNull($service->type_data);
|
||||
}
|
||||
}
|
86
tests/Unit/Actions/Service/UninstallTest.php
Normal file
86
tests/Unit/Actions/Service/UninstallTest.php
Normal file
@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Actions\Service;
|
||||
|
||||
use App\Actions\Service\Uninstall;
|
||||
use App\Enums\ServiceStatus;
|
||||
use App\Facades\SSH;
|
||||
use App\Models\Database;
|
||||
use App\Models\Queue;
|
||||
use App\Models\Service;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Tests\TestCase;
|
||||
|
||||
class UninstallTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_uninstall_vito_agent(): void
|
||||
{
|
||||
SSH::fake();
|
||||
|
||||
Service::factory()->create([
|
||||
'server_id' => $this->server->id,
|
||||
'name' => 'vito-agent',
|
||||
'type' => 'monitoring',
|
||||
'version' => 'latest',
|
||||
'status' => ServiceStatus::READY,
|
||||
]);
|
||||
|
||||
app(Uninstall::class)->uninstall($this->server->monitoring());
|
||||
|
||||
$this->assertDatabaseMissing('services', [
|
||||
'server_id' => $this->server->id,
|
||||
'name' => 'vito-agent',
|
||||
'type' => 'monitoring',
|
||||
'version' => 'latest',
|
||||
'status' => ServiceStatus::READY,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cannot uninstall nginx because some sites using it
|
||||
*/
|
||||
public function test_cannot_uninstall_nginx(): void
|
||||
{
|
||||
SSH::fake();
|
||||
|
||||
$this->expectException(ValidationException::class);
|
||||
|
||||
app(Uninstall::class)->uninstall($this->server->webserver());
|
||||
}
|
||||
|
||||
/**
|
||||
* Cannot uninstall mysql because some databases exist
|
||||
*/
|
||||
public function test_cannot_uninstall_mysql(): void
|
||||
{
|
||||
SSH::fake();
|
||||
|
||||
Database::factory()->create([
|
||||
'server_id' => $this->server->id,
|
||||
]);
|
||||
|
||||
$this->expectException(ValidationException::class);
|
||||
|
||||
app(Uninstall::class)->uninstall($this->server->database());
|
||||
}
|
||||
|
||||
/**
|
||||
* Cannot uninstall supervisor because some queues exist
|
||||
*/
|
||||
public function test_cannot_uninstall_supervisor(): void
|
||||
{
|
||||
SSH::fake();
|
||||
|
||||
Queue::factory()->create([
|
||||
'server_id' => $this->server->id,
|
||||
'site_id' => $this->site->id,
|
||||
]);
|
||||
|
||||
$this->expectException(ValidationException::class);
|
||||
|
||||
app(Uninstall::class)->uninstall($this->server->processManager());
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user