Compare commits

..

11 Commits
1.2.1 ... 1.3.0

Author SHA1 Message Date
75aed62d75 fix search bar position (#165) 2024-04-14 09:52:06 +02:00
aaef73d89d fix custom vhost update (#164) 2024-04-13 23:47:52 +02:00
f03a029e36 fix metrics page 2024-04-13 22:44:11 +02:00
52d195710b add data retention to the metrics 2024-04-13 22:38:27 +02:00
ddacc32e64 docker release action 2024-04-13 13:19:20 +02:00
2ae9a14d02 docker 2024-04-13 12:44:12 +02:00
3019c3d213 fix docker 2024-04-13 12:31:53 +02:00
c43869d255 docker 2024-04-13 12:23:28 +02:00
18748f77ac build 2024-04-13 11:50:24 +02:00
052e28d2e3 Monitoring & Service Management (#163)
Monitoring & Service Management
2024-04-13 11:47:56 +02:00
87ec0af697 update demo link 2024-04-07 20:19:56 +02:00
112 changed files with 3517 additions and 420 deletions

35
.github/workflows/docker-1x.yml vendored Normal file
View File

@ -0,0 +1,35 @@
name: Build and push Docker image
on:
push:
branches:
- 1.x
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push Docker image
run: |
docker buildx build . \
-f docker/Dockerfile \
-t vitodeploy/vito:1.x \
--build-arg="RELEASE=0" \
--platform linux/amd64,linux/arm64 \
--no-cache \
--push

35
.github/workflows/docker-release.yml vendored Normal file
View File

@ -0,0 +1,35 @@
name: Build and push Docker image
on:
release:
types: [created]
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push Docker image
run: |
docker buildx build . \
-f docker/Dockerfile \
-t vitodeploy/vito:${{ github.event.release.tag_name }} \
-t vitodeploy/vito:latest \
--build-arg="RELEASE=0" \
--platform linux/amd64,linux/arm64 \
--no-cache \
--push

View File

@ -36,7 +36,7 @@ ## Useful Links
- [Install via Docker](https://vitodeploy.com/introduction/installation.html#install-via-docker)
- [Feedbacks](https://vitodeploy.featurebase.app)
- [Roadmap](https://vitodeploy.featurebase.app/roadmap)
- [Video Demo](https://youtu.be/rLRHIyEfON8)
- [Video Demo](https://youtu.be/AbmUOBDOc28)
- [Discord](https://discord.gg/uZeeHZZnm5)
- [Contribution](/CONTRIBUTING.md)
- [Security](/SECURITY.md)

View File

@ -0,0 +1,150 @@
<?php
namespace App\Actions\Monitoring;
use App\Models\Server;
use Carbon\Carbon;
use Illuminate\Contracts\Database\Query\Expression;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
class GetMetrics
{
public function filter(Server $server, array $input): array
{
if (isset($input['from']) && isset($input['to']) && $input['from'] === $input['to']) {
$input['from'] = Carbon::parse($input['from'])->format('Y-m-d').' 00:00:00';
$input['to'] = Carbon::parse($input['to'])->format('Y-m-d').' 23:59:59';
}
$defaultInput = [
'period' => '10m',
];
$input = array_merge($defaultInput, $input);
$this->validate($input);
return $this->metrics(
server: $server,
fromDate: $this->getFromDate($input),
toDate: $this->getToDate($input),
interval: $this->getInterval($input)
);
}
private function metrics(
Server $server,
Carbon $fromDate,
Carbon $toDate,
?Expression $interval = null
): array {
$metrics = DB::table('metrics')
->where('server_id', $server->id)
->whereBetween('created_at', [$fromDate->format('Y-m-d H:i:s'), $toDate->format('Y-m-d H:i:s')])
->select(
[
DB::raw('created_at as date'),
DB::raw('ROUND(AVG(load), 2) as load'),
DB::raw('ROUND(AVG(memory_total), 2) as memory_total'),
DB::raw('ROUND(AVG(memory_used), 2) as memory_used'),
DB::raw('ROUND(AVG(memory_free), 2) as memory_free'),
DB::raw('ROUND(AVG(disk_total), 2) as disk_total'),
DB::raw('ROUND(AVG(disk_used), 2) as disk_used'),
DB::raw('ROUND(AVG(disk_free), 2) as disk_free'),
$interval,
],
)
->groupByRaw('date_interval')
->orderBy('date_interval')
->get()
->map(function ($item) {
$item->date = Carbon::parse($item->date)->format('Y-m-d H:i');
return $item;
});
return [
'metrics' => $metrics,
];
}
private function getFromDate(array $input): Carbon
{
if ($input['period'] === 'custom') {
return new Carbon($input['from']);
}
return Carbon::parse('-'.convert_time_format($input['period']));
}
private function getToDate(array $input): Carbon
{
if ($input['period'] === 'custom') {
return new Carbon($input['to']);
}
return Carbon::now();
}
private function getInterval(array $input): Expression
{
if ($input['period'] === 'custom') {
$from = new Carbon($input['from']);
$to = new Carbon($input['to']);
$periodInHours = $from->diffInHours($to);
}
if (! isset($periodInHours)) {
$periodInHours = Carbon::parse(
convert_time_format($input['period'])
)->diffInHours();
}
if ($periodInHours <= 1) {
return DB::raw("strftime('%Y-%m-%d %H:%M:00', created_at) as date_interval");
}
if ($periodInHours <= 24) {
return DB::raw("strftime('%Y-%m-%d %H:00:00', created_at) as date_interval");
}
if ($periodInHours > 24) {
return DB::raw("strftime('%Y-%m-%d 00:00:00', created_at) as date_interval");
}
}
private function validate(array $input): void
{
Validator::make($input, [
'period' => [
'required',
Rule::in([
'10m',
'30m',
'1h',
'12h',
'1d',
'7d',
'custom',
]),
],
])->validate();
if ($input['period'] === 'custom') {
Validator::make($input, [
'from' => [
'required',
'date',
'before:to',
],
'to' => [
'required',
'date',
'after:from',
],
])->validate();
}
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\Actions\Monitoring;
use App\Models\Server;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
class UpdateMetricSettings
{
public function update(Server $server, array $input): void
{
$this->validate($input);
$service = $server->monitoring();
$data = $service->handler()->data();
$data['data_retention'] = $input['data_retention'];
$service->type_data = $data;
$service->save();
}
private function validate(array $input): void
{
Validator::make($input, [
'data_retention' => [
'required',
Rule::in(config('core.metrics_data_retention')),
],
])->validate();
}
}

View File

@ -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]);

View File

@ -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()]

View File

@ -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;

View File

@ -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();

View 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');
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Console\Commands;
use App\Models\Service;
use Illuminate\Console\Command;
class DeleteOlderMetricsCommand extends Command
{
protected $signature = 'metrics:delete-older-metrics';
protected $description = 'Delete older metrics from database';
public function handle()
{
Service::query()->where('type', 'monitoring')->chunk(100, function ($services) {
$services->each(function ($service) {
$this->info("Deleting older metrics for service {$service->server->name}");
$service
->server
->metrics()
->where('created_at', '<', now()->subDays($service->handler()->data()['data_retention']))
->delete();
$this->info('Metrics deleted');
});
});
}
}

View File

@ -16,6 +16,7 @@ protected function schedule(Schedule $schedule): void
$schedule->command('backups:run "0 0 * * *"')->daily();
$schedule->command('backups:run "0 0 * * 0"')->weekly();
$schedule->command('backups:run "0 0 1 * *"')->monthly();
$schedule->command('metrics:delete-older-metrics')->daily();
}
/**

View 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();
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace App\Http\Controllers;
use App\Actions\Monitoring\GetMetrics;
use App\Actions\Monitoring\UpdateMetricSettings;
use App\Facades\Toast;
use App\Helpers\HtmxResponse;
use App\Models\Server;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class MetricController extends Controller
{
public function index(Server $server, Request $request): View|RedirectResponse
{
$this->checkIfMonitoringServiceInstalled($server);
return view('metrics.index', [
'server' => $server,
'data' => app(GetMetrics::class)->filter($server, $request->input()),
'lastMetric' => $server->metrics()->latest()->first(),
]);
}
public function settings(Server $server, Request $request): HtmxResponse
{
$this->checkIfMonitoringServiceInstalled($server);
app(UpdateMetricSettings::class)->update($server, $request->input());
Toast::success('Metric settings updated successfully');
return htmx()->back();
}
private function checkIfMonitoringServiceInstalled(Server $server): void
{
if (! $server->monitoring()) {
abort(404, 'Monitoring service is not installed on this server');
}
}
}

View File

@ -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!');

View File

@ -7,6 +7,7 @@
use App\Helpers\HtmxResponse;
use App\Models\Server;
use App\Models\Site;
use App\SSH\Services\Webserver\Webserver;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
@ -25,7 +26,10 @@ public function index(Server $server, Site $site): View
public function getVhost(Server $server, Site $site): RedirectResponse
{
return back()->with('vhost', $server->webserver()->handler()->getVHost($site));
/** @var Webserver $handler */
$handler = $server->webserver()->handler();
return back()->with('vhost', $handler->getVHost($site));
}
public function updateVhost(Server $server, Site $site, Request $request): RedirectResponse
@ -35,7 +39,9 @@ public function updateVhost(Server $server, Site $site, Request $request): Redir
]);
try {
$server->webserver()->handler()->updateVHost($site, false, $request->input('vhost'));
/** @var Webserver $handler */
$handler = $server->webserver()->handler();
$handler->updateVHost($site, false, $request->input('vhost'));
Toast::success('VHost updated successfully!');
} catch (Throwable $e) {

53
app/Models/Metric.php Normal file
View 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);
}
}

View File

@ -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 */

View File

@ -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);

View File

@ -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'
);
}
}

View 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."

View 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
{
//
}
}

View File

@ -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;
}

View File

@ -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,

View File

@ -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

View File

@ -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

View 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

View File

@ -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

View File

@ -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

View 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

View 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

View File

@ -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;
}
}

View File

@ -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

View File

@ -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

View File

@ -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.');
}
},
],
];
}
}

View File

@ -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();
}
/**

View File

@ -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

View File

@ -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();
}
}

View 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

View File

@ -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;
}

View File

@ -0,0 +1,87 @@
<?php
namespace App\SSH\Services\VitoAgent;
use App\Models\Metric;
use App\SSH\HasScripts;
use App\SSH\Services\AbstractService;
use Illuminate\Support\Facades\Http;
use Illuminate\Validation\Rule;
use Ramsey\Uuid\Uuid;
class VitoAgent extends AbstractService
{
use HasScripts;
const TAGS_URL = 'https://api.github.com/repos/vitodeploy/agent/tags';
const DOWNLOAD_URL = 'https://github.com/vitodeploy/agent/releases/download/%s';
public function creationRules(array $input): array
{
return [
'type' => [
Rule::unique('services', 'type')->where('server_id', $this->service->server_id),
],
'version' => [
'required',
Rule::in(['latest']),
],
];
}
public function creationData(array $input): array
{
return [
'url' => '',
'secret' => Uuid::uuid4()->toString(),
'data_retention' => 10,
];
}
public function data(): array
{
return [
'url' => $this->service->type_data['url'] ?? null,
'secret' => $this->service->type_data['secret'] ?? null,
'data_retention' => $this->service->type_data['data_retention'] ?? 10,
];
}
public function install(): void
{
$tags = Http::get(self::TAGS_URL)->json();
if (empty($tags)) {
throw new \Exception('Failed to fetch tags');
}
$this->service->version = $tags[0]['name'];
$this->service->save();
$downloadUrl = sprintf(self::DOWNLOAD_URL, $this->service->version);
$data = $this->data();
$data['url'] = route('api.servers.agent', [$this->service->server, $this->service->id]);
$this->service->type_data = $data;
$this->service->save();
$this->service->refresh();
$this->service->server->ssh()->exec(
$this->getScript('install.sh', [
'download_url' => $downloadUrl,
'config_url' => $this->data()['url'],
'config_secret' => $this->data()['secret'],
]),
'install-vito-agent'
);
$status = $this->service->server->systemd()->status($this->service->unit);
$this->service->validateInstall($status);
}
public function uninstall(): void
{
$this->service->server->ssh()->exec(
$this->getScript('uninstall.sh'),
'uninstall-vito-agent'
);
Metric::where('server_id', $this->service->server_id)->delete();
}
}

View 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"

View 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"

View File

@ -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)
{
}
}

View File

@ -6,6 +6,7 @@
use App\Models\Site;
use App\Models\Ssl;
use App\SSH\HasScripts;
use Closure;
use Illuminate\Support\Str;
use Throwable;
@ -23,6 +24,31 @@ public function install(): void
]),
'install-nginx'
);
$this->service->server->os()->cleanup();
}
public function deletionRules(): array
{
return [
'service' => [
function (string $attribute, mixed $value, Closure $fail) {
$hasSite = $this->service->server->sites()
->exists();
if ($hasSite) {
$fail('Cannot uninstall webserver while you have websites using it.');
}
},
],
];
}
public function uninstall(): void
{
$this->service->server->ssh()->exec(
$this->getScript('nginx/uninstall-nginx.sh'),
'uninstall-nginx'
);
$this->service->server->os()->cleanup();
}
public function createVHost(Site $site): void
@ -44,7 +70,7 @@ public function updateVHost(Site $site, bool $noSSL = false, ?string $vhost = nu
$this->getScript('nginx/update-vhost.sh', [
'domain' => $site->domain,
'path' => $site->path,
'vhost' => $this->generateVhost($site, $noSSL),
'vhost' => $vhost ?? $this->generateVhost($site, $noSSL),
]),
'update-vhost',
$site->id

View 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

View File

@ -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');

View File

@ -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',

View File

@ -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);
}

View File

@ -1,35 +1,5 @@
<?php
use App\Enums\OperatingSystem;
use App\Enums\StorageProvider;
use App\NotificationChannels\Discord;
use App\NotificationChannels\Email;
use App\NotificationChannels\Slack;
use App\NotificationChannels\Telegram;
use App\ServerProviders\AWS;
use App\ServerProviders\DigitalOcean;
use App\ServerProviders\Hetzner;
use App\ServerProviders\Linode;
use App\ServerProviders\Vultr;
use App\SiteTypes\Laravel;
use App\SiteTypes\PHPBlank;
use App\SiteTypes\PHPMyAdmin;
use App\SiteTypes\PHPSite;
use App\SiteTypes\Wordpress;
use App\SourceControlProviders\Bitbucket;
use App\SourceControlProviders\Github;
use App\SourceControlProviders\Gitlab;
use App\SSH\Services\Database\Mariadb;
use App\SSH\Services\Database\Mysql;
use App\SSH\Services\Database\Postgresql;
use App\SSH\Services\Firewall\Ufw;
use App\SSH\Services\PHP\PHP;
use App\SSH\Services\ProcessManager\Supervisor;
use App\SSH\Services\Redis\Redis;
use App\SSH\Services\Webserver\Nginx;
use App\StorageProviders\Dropbox;
use App\StorageProviders\FTP;
return [
/*
* SSH
@ -44,12 +14,12 @@
* General
*/
'operating_systems' => [
OperatingSystem::UBUNTU20,
OperatingSystem::UBUNTU22,
\App\Enums\OperatingSystem::UBUNTU20,
\App\Enums\OperatingSystem::UBUNTU22,
],
'webservers' => ['none', 'nginx'],
'php_versions' => [
'none',
// 'none',
// '5.6',
'7.0',
'7.1',
@ -126,117 +96,102 @@
],
'server_providers_class' => [
\App\Enums\ServerProvider::CUSTOM => \App\ServerProviders\Custom::class,
\App\Enums\ServerProvider::AWS => AWS::class,
\App\Enums\ServerProvider::LINODE => Linode::class,
\App\Enums\ServerProvider::DIGITALOCEAN => DigitalOcean::class,
\App\Enums\ServerProvider::VULTR => Vultr::class,
\App\Enums\ServerProvider::HETZNER => Hetzner::class,
\App\Enums\ServerProvider::AWS => \App\ServerProviders\AWS::class,
\App\Enums\ServerProvider::LINODE => \App\ServerProviders\Linode::class,
\App\Enums\ServerProvider::DIGITALOCEAN => \App\ServerProviders\DigitalOcean::class,
\App\Enums\ServerProvider::VULTR => \App\ServerProviders\Vultr::class,
\App\Enums\ServerProvider::HETZNER => \App\ServerProviders\Hetzner::class,
],
'server_providers_default_user' => [
'custom' => [
'ubuntu_18' => 'root',
'ubuntu_20' => 'root',
'ubuntu_22' => 'root',
\App\Enums\OperatingSystem::UBUNTU20 => 'root',
\App\Enums\OperatingSystem::UBUNTU22 => 'root',
],
'aws' => [
'ubuntu_18' => 'ubuntu',
'ubuntu_20' => 'ubuntu',
'ubuntu_22' => 'ubuntu',
\App\Enums\OperatingSystem::UBUNTU20 => 'ubuntu',
\App\Enums\OperatingSystem::UBUNTU22 => 'ubuntu',
],
'linode' => [
'ubuntu_18' => 'root',
'ubuntu_20' => 'root',
'ubuntu_22' => 'root',
\App\Enums\OperatingSystem::UBUNTU20 => 'root',
\App\Enums\OperatingSystem::UBUNTU22 => 'root',
],
'digitalocean' => [
'ubuntu_18' => 'root',
'ubuntu_20' => 'root',
'ubuntu_22' => 'root',
\App\Enums\OperatingSystem::UBUNTU20 => 'root',
\App\Enums\OperatingSystem::UBUNTU22 => 'root',
],
'vultr' => [
'ubuntu_18' => 'root',
'ubuntu_20' => 'root',
'ubuntu_22' => 'root',
\App\Enums\OperatingSystem::UBUNTU20 => 'root',
\App\Enums\OperatingSystem::UBUNTU22 => 'root',
],
'hetzner' => [
'ubuntu_18' => 'root',
'ubuntu_20' => 'root',
'ubuntu_22' => 'root',
\App\Enums\OperatingSystem::UBUNTU20 => 'root',
\App\Enums\OperatingSystem::UBUNTU22 => 'root',
],
],
/*
* Service
*/
'service_handlers' => [
'nginx' => Nginx::class,
'mysql' => Mysql::class,
'mariadb' => Mariadb::class,
'postgresql' => Postgresql::class,
'redis' => Redis::class,
'php' => PHP::class,
'ufw' => Ufw::class,
'supervisor' => Supervisor::class,
'service_types' => [
'nginx' => 'webserver',
'mysql' => 'database',
'mariadb' => 'database',
'postgresql' => 'database',
'redis' => 'memory_database',
'php' => 'php',
'ufw' => 'firewall',
'supervisor' => 'process_manager',
'vito-agent' => 'monitoring',
],
'add_on_services' => [
// add-on services
'service_handlers' => [
'nginx' => \App\SSH\Services\Webserver\Nginx::class,
'mysql' => \App\SSH\Services\Database\Mysql::class,
'mariadb' => \App\SSH\Services\Database\Mariadb::class,
'postgresql' => \App\SSH\Services\Database\Postgresql::class,
'redis' => \App\SSH\Services\Redis\Redis::class,
'php' => \App\SSH\Services\PHP\PHP::class,
'ufw' => \App\SSH\Services\Firewall\Ufw::class,
'supervisor' => \App\SSH\Services\ProcessManager\Supervisor::class,
'vito-agent' => \App\SSH\Services\VitoAgent\VitoAgent::class,
],
'service_units' => [
'nginx' => [
'ubuntu_18' => [
\App\Enums\OperatingSystem::UBUNTU20 => [
'latest' => 'nginx',
],
'ubuntu_20' => [
'latest' => 'nginx',
],
'ubuntu_22' => [
\App\Enums\OperatingSystem::UBUNTU22 => [
'latest' => 'nginx',
],
],
'mysql' => [
'ubuntu_18' => [
\App\Enums\OperatingSystem::UBUNTU20 => [
'5.7' => 'mysql',
'8.0' => 'mysql',
],
'ubuntu_20' => [
'5.7' => 'mysql',
'8.0' => 'mysql',
],
'ubuntu_22' => [
\App\Enums\OperatingSystem::UBUNTU22 => [
'5.7' => 'mysql',
'8.0' => 'mysql',
],
],
'mariadb' => [
'ubuntu_18' => [
\App\Enums\OperatingSystem::UBUNTU20 => [
'10.3' => 'mariadb',
'10.4' => 'mariadb',
],
'ubuntu_20' => [
'10.3' => 'mariadb',
'10.4' => 'mariadb',
],
'ubuntu_22' => [
\App\Enums\OperatingSystem::UBUNTU22 => [
'10.3' => 'mariadb',
'10.4' => 'mariadb',
],
],
'postgresql' => [
'ubuntu_18' => [
\App\Enums\OperatingSystem::UBUNTU20 => [
'12' => 'postgresql',
'13' => 'postgresql',
'14' => 'postgresql',
'15' => 'postgresql',
'16' => 'postgresql',
],
'ubuntu_20' => [
'12' => 'postgresql',
'13' => 'postgresql',
'14' => 'postgresql',
'15' => 'postgresql',
'16' => 'postgresql',
],
'ubuntu_22' => [
\App\Enums\OperatingSystem::UBUNTU22 => [
'12' => 'postgresql',
'13' => 'postgresql',
'14' => 'postgresql',
@ -245,19 +200,7 @@
],
],
'php' => [
'ubuntu_18' => [
'5.6' => 'php5.6-fpm',
'7.0' => 'php7.0-fpm',
'7.1' => 'php7.1-fpm',
'7.2' => 'php7.2-fpm',
'7.3' => 'php7.3-fpm',
'7.4' => 'php7.4-fpm',
'8.0' => 'php8.0-fpm',
'8.1' => 'php8.1-fpm',
'8.2' => 'php8.2-fpm',
'8.3' => 'php8.3-fpm',
],
'ubuntu_20' => [
\App\Enums\OperatingSystem::UBUNTU20 => [
'5.6' => 'php5.6-fpm',
'7.0' => 'php7.0-fpm',
'7.1' => 'php7.1-fpm',
@ -268,7 +211,7 @@
'8.1' => 'php8.1-fpm',
'8.3' => 'php8.3-fpm',
],
'ubuntu_22' => [
\App\Enums\OperatingSystem::UBUNTU22 => [
'5.6' => 'php5.6-fpm',
'7.0' => 'php7.0-fpm',
'7.1' => 'php7.1-fpm',
@ -282,36 +225,35 @@
],
],
'redis' => [
'ubuntu_18' => [
\App\Enums\OperatingSystem::UBUNTU20 => [
'latest' => 'redis',
],
'ubuntu_20' => [
'latest' => 'redis',
],
'ubuntu_22' => [
\App\Enums\OperatingSystem::UBUNTU22 => [
'latest' => 'redis',
],
],
'supervisor' => [
'ubuntu_18' => [
\App\Enums\OperatingSystem::UBUNTU20 => [
'latest' => 'supervisor',
],
'ubuntu_20' => [
'latest' => 'supervisor',
],
'ubuntu_22' => [
\App\Enums\OperatingSystem::UBUNTU22 => [
'latest' => 'supervisor',
],
],
'ufw' => [
'ubuntu_18' => [
\App\Enums\OperatingSystem::UBUNTU20 => [
'latest' => 'ufw',
],
'ubuntu_20' => [
\App\Enums\OperatingSystem::UBUNTU22 => [
'latest' => 'ufw',
],
'ubuntu_22' => [
'latest' => 'ufw',
],
'vito-agent' => [
\App\Enums\OperatingSystem::UBUNTU20 => [
'latest' => 'vito-agent',
],
\App\Enums\OperatingSystem::UBUNTU22 => [
'latest' => 'vito-agent',
],
],
],
@ -327,11 +269,11 @@
\App\Enums\SiteType::PHPMYADMIN,
],
'site_types_class' => [
\App\Enums\SiteType::PHP => PHPSite::class,
\App\Enums\SiteType::PHP_BLANK => PHPBlank::class,
\App\Enums\SiteType::LARAVEL => Laravel::class,
\App\Enums\SiteType::WORDPRESS => Wordpress::class,
\App\Enums\SiteType::PHPMYADMIN => PHPMyAdmin::class,
\App\Enums\SiteType::PHP => \App\SiteTypes\PHPSite::class,
\App\Enums\SiteType::PHP_BLANK => \App\SiteTypes\PHPBlank::class,
\App\Enums\SiteType::LARAVEL => \App\SiteTypes\Laravel::class,
\App\Enums\SiteType::WORDPRESS => \App\SiteTypes\Wordpress::class,
\App\Enums\SiteType::PHPMYADMIN => \App\SiteTypes\PHPMyAdmin::class,
],
/*
@ -343,9 +285,9 @@
'bitbucket',
],
'source_control_providers_class' => [
'github' => Github::class,
'gitlab' => Gitlab::class,
'bitbucket' => Bitbucket::class,
'github' => \App\SourceControlProviders\Github::class,
'gitlab' => \App\SourceControlProviders\Gitlab::class,
'bitbucket' => \App\SourceControlProviders\Bitbucket::class,
],
/*
@ -403,26 +345,33 @@
\App\Enums\NotificationChannel::TELEGRAM,
],
'notification_channels_providers_class' => [
\App\Enums\NotificationChannel::SLACK => Slack::class,
\App\Enums\NotificationChannel::DISCORD => Discord::class,
\App\Enums\NotificationChannel::EMAIL => Email::class,
\App\Enums\NotificationChannel::TELEGRAM => Telegram::class,
\App\Enums\NotificationChannel::SLACK => \App\NotificationChannels\Slack::class,
\App\Enums\NotificationChannel::DISCORD => \App\NotificationChannels\Discord::class,
\App\Enums\NotificationChannel::EMAIL => \App\NotificationChannels\Email::class,
\App\Enums\NotificationChannel::TELEGRAM => \App\NotificationChannels\Telegram::class,
],
/*
* storage providers
*/
'storage_providers' => [
StorageProvider::DROPBOX,
StorageProvider::FTP,
\App\Enums\StorageProvider::DROPBOX,
\App\Enums\StorageProvider::FTP,
],
'storage_providers_class' => [
'dropbox' => Dropbox::class,
'ftp' => FTP::class,
'dropbox' => \App\StorageProviders\Dropbox::class,
'ftp' => \App\StorageProviders\Ftp::class,
],
'ssl_types' => [
\App\Enums\SslType::LETSENCRYPT,
\App\Enums\SslType::CUSTOM,
],
'metrics_data_retention' => [
7,
14,
30,
90,
],
];

View 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),
];
}
}

View File

@ -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');
}
};

View 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,
]);
}
}
}

View File

@ -1,25 +1,29 @@
ARG RELEASE=1
FROM ubuntu:22.04
ENV RELEASE_ARG=$RELEASE
WORKDIR /var/www/html
ENV DEBIAN_FRONTEND noninteractive
# upgrade
RUN apt clean && apt update && apt update && apt upgrade -y && apt autoremove -y
RUN apt-get clean && apt-get update && apt-get update && apt-get upgrade -y && apt-get autoremove -y
# requirements
RUN apt install -y software-properties-common curl zip unzip git gcc
RUN apt-get install -y software-properties-common curl zip unzip git gcc
# nginx
RUN apt install -y nginx
RUN apt-get install -y nginx
# php
RUN apt update \
&& apt install -y gnupg gosu curl ca-certificates zip unzip git supervisor libcap2-bin libpng-dev \
RUN apt-get update \
&& apt-get install -y gnupg gosu curl ca-certificates zip unzip git supervisor libcap2-bin libpng-dev \
python2 dnsutils librsvg2-bin fswatch wget \
&& add-apt-repository ppa:ondrej/php -y \
&& apt update \
&& apt install -y php8.2 php8.2-fpm php8.2-mbstring php8.2-mcrypt php8.2-gd php8.2-xml \
&& apt-get update \
&& apt-get install -y php8.2 php8.2-fpm php8.2-mbstring php8.2-mcrypt php8.2-gd php8.2-xml \
php8.2-curl php8.2-gettext php8.2-zip php8.2-bcmath php8.2-soap php8.2-redis php8.2-sqlite3
COPY docker/php.ini /etc/php/8.2/cli/conf.d/99-vito.ini
@ -29,7 +33,7 @@ RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local
# app
RUN rm -rf /var/www/html
RUN git clone -b 1.x https://github.com/vitodeploy/vito.git /var/www/html
RUN git checkout $(git tag -l --merged 1.x --sort=-v:refname | head -n 1)
RUN [ "$RELEASE_ARG" = "1" ] && git checkout $(git tag -l --merged 1.x --sort=-v:refname | head -n 1) || true
RUN composer install --no-dev --prefer-dist
RUN chown -R www-data:www-data /var/www/html \
&& chmod -R 755 /var/www/html/storage /var/www/html/bootstrap/cache

225
package-lock.json generated
View File

@ -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",

View File

@ -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"
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
{
"resources/css/app.css": {
"file": "assets/app-e3775b0a.css",
"file": "assets/app-53e4d707.css",
"isEntry": true,
"src": "resources/css/app.css"
},
@ -12,7 +12,7 @@
"css": [
"assets/app-a1ae07b3.css"
],
"file": "assets/app-4c0bf3b2.js",
"file": "assets/app-66009dff.js",
"isEntry": true,
"src": "resources/js/app.js"
}

View 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

View File

@ -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', {

View File

@ -0,0 +1,104 @@
@props([
"id",
"type",
"title",
"color",
"sets",
"categories",
"toolbar" => false,
"formatter" => null,
])
<x-simple-card {{ $attributes }}>
<div class="relative">
<div class="absolute left-4 top-4">{{ $title }}</div>
</div>
<div id="{{ $id }}" class="pt-4"></div>
<script>
window.addEventListener('load', function () {
let options = {
series: [
@foreach ($sets as $set)
{
name: '{{ $set["name"] }}',
data: @json($set["data"]),
color: '{{ $set["color"] }}'
},
@endforeach
],
chart: {
height: '100%',
maxWidth: '100%',
type: '{{ $type }}',
fontFamily: 'Inter, sans-serif',
dropShadow: {
enabled: false
},
toolbar: {
show: @js($toolbar)
}
},
tooltip: {
enabled: true,
x: {
show: true
}
},
legend: {
show: true
},
@if ($type == 'area')
fill: {
type: 'gradient',
gradient: {
opacityFrom: 0.55,
opacityTo: 0,
shade: '{{ $color }}',
gradientToColors: ['{{ $color }}']
}
},
@endif
dataLabels: {
enabled: false
},
stroke: {
width: 2,
curve: 'smooth'
},
grid: {
show: false,
strokeDashArray: 4,
padding: {
left: 2,
right: 2,
top: 0
}
},
xaxis: {
categories: @json($categories),
labels: {
show: false
},
axisBorder: {
show: false
},
axisTicks: {
show: false
}
},
yaxis: {
show: false,
labels: {
@if ($formatter)
formatter: {{ $formatter }},
@endif
}
}
};
if (document.getElementById('{{ $id }}') && typeof ApexCharts !== 'undefined') {
const chart = new ApexCharts(document.getElementById('{{ $id }}'), options);
chart.render();
}
});
</script>
</x-simple-card>

View 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

View 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

View File

@ -132,7 +132,10 @@
}"
@open-search.window="openSearch"
>
<div x-show="open" class="absolute bottom-0 left-0 right-0 top-0 flex max-w-full items-start justify-center">
<div
x-show="open"
class="fixed bottom-0 left-0 right-0 top-0 z-[2000] flex max-w-full items-start justify-center"
>
<div
x-on:click="close"
class="fixed inset-0 bottom-0 left-0 right-0 top-0 z-[1000] items-center bg-gray-500 opacity-75 dark:bg-gray-900"

View File

@ -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])"

View File

@ -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])"

View File

@ -0,0 +1,106 @@
<x-server-layout :server="$server">
<x-slot name="pageTitle">{{ $server->name }} - Metrics</x-slot>
<div class="space-y-4">
<div class="flex items-center justify-between">
@include("metrics.partials.filter")
@include("metrics.partials.data-retention")
</div>
@php
$cpuSets = [
"name" => "CPU Load",
"data" => $data["metrics"]->pluck("load")->toArray(),
"color" => "#ff9900",
];
$memorySets = [
"name" => "Memory Usage",
"data" => $data["metrics"]->pluck("memory_used")->toArray(),
"color" => "#3366cc",
];
$diskSets = [
"name" => "Disk Usage",
"data" => $data["metrics"]->pluck("disk_used")->toArray(),
"color" => "#109618",
];
@endphp
<div class="grid grid-cols-1 gap-4 lg:grid-cols-3">
<x-chart
id="cpu-load"
type="area"
title="CPU Load"
:sets="[$cpuSets]"
:categories="$data['metrics']->pluck('date')->toArray()"
color="#ff9900"
class="h-[200px] !p-0"
/>
<x-chart
id="memory-usage"
type="area"
title="Memory"
:sets="[$memorySets]"
:categories="$data['metrics']->pluck('date')->toArray()"
color="#3366cc"
formatter="function (value) { return (value / 1000).toFixed(2) + ` MB`; }"
class="h-[200px] !p-0"
/>
<x-chart
id="disk-usage"
type="area"
title="Disk"
:sets="[$diskSets]"
:categories="$data['metrics']->pluck('date')->toArray()"
formatter="function (value) { return value + ` MB`; }"
color="#109618"
class="h-[200px] !p-0"
/>
</div>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
<div class="grid grid-cols-1 gap-4">
<x-simple-card class="grid grid-cols-1 gap-4 lg:grid-cols-2">
<span class="text-center lg:text-left">Total Memory</span>
<div class="text-center text-xl font-bold text-gray-600 dark:text-gray-400 lg:text-right">
{{ $lastMetric ? $lastMetric->memory_total : "-" }} MB
</div>
</x-simple-card>
<x-simple-card class="grid grid-cols-1 gap-4 lg:grid-cols-2">
<span class="text-center lg:text-left">Used Memory</span>
<div class="text-center text-xl font-bold text-gray-600 dark:text-gray-400 lg:text-right">
{{ $lastMetric ? $lastMetric->memory_used : "-" }} MB
</div>
</x-simple-card>
<x-simple-card class="grid grid-cols-1 gap-4 lg:grid-cols-2">
<span class="text-center lg:text-left">Free Memory</span>
<div class="text-center text-xl font-bold text-gray-600 dark:text-gray-400 lg:text-right">
{{ $lastMetric ? $lastMetric->memory_free : "-" }} MB
</div>
</x-simple-card>
</div>
<div class="grid grid-cols-1 gap-4">
<x-simple-card class="grid grid-cols-1 gap-4 lg:grid-cols-2">
<span class="text-center lg:text-left">Total Space</span>
<div class="text-center text-xl font-bold text-gray-600 dark:text-gray-400 lg:text-right">
{{ $lastMetric ? $lastMetric->disk_total : "-" }} MB
</div>
</x-simple-card>
<x-simple-card class="grid grid-cols-1 gap-4 lg:grid-cols-2">
<span class="text-center lg:text-left">Used Space</span>
<div class="text-center text-xl font-bold text-gray-600 dark:text-gray-400 lg:text-right">
{{ $lastMetric ? $lastMetric->disk_used : "-" }} MB
</div>
</x-simple-card>
<x-simple-card class="grid grid-cols-1 gap-4 lg:grid-cols-2">
<span class="text-center lg:text-left">Free Space</span>
<div class="text-center text-xl font-bold text-gray-600 dark:text-gray-400 lg:text-right">
{{ $lastMetric ? $lastMetric->disk_free : "-" }} MB
</div>
</x-simple-card>
</div>
</div>
</div>
@stack("modals")
</x-server-layout>

View File

@ -0,0 +1,44 @@
<x-secondary-button
class="ml-2 hidden h-[42px] items-center lg:flex"
x-on:click="$dispatch('open-modal', 'metric-settings')"
>
<x-heroicon name="o-trash" class="mr-1 h-5 w-5" />
Data Retention
</x-secondary-button>
@push("modals")
<x-modal name="metric-settings">
<form
id="metric-settings-form"
hx-post="{{ route("servers.metrics.settings", ["server" => $server]) }}"
hx-swap="outerHTML"
hx-select="#metric-settings-form"
class="p-6"
>
@csrf
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">Data Retention</h2>
<div class="mt-6">
<x-input-label for="data_retention" value="Delete metrics older than" />
<x-select-input id="data_retention" name="data_retention" class="mt-1 w-full">
@foreach (config("core.metrics_data_retention") as $item)
<option value="{{ $item }}">{{ $item }} Days</option>
@endforeach
</x-select-input>
@error("data_retention")
<x-input-error class="mt-2" :messages="$message" />
@enderror
</div>
<div class="mt-6 flex justify-end">
<x-secondary-button type="button" x-on:click="$dispatch('close')">
{{ __("Cancel") }}
</x-secondary-button>
<x-primary-button id="btn-metric-settings" hx-disable class="ml-3">
{{ __("Save") }}
</x-primary-button>
</div>
</form>
</x-modal>
@endpush

View 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>

View File

@ -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 }}

View File

@ -13,20 +13,7 @@ class="@if($server->webserver() && $server->database()) grid-cols-3 @else grid-c
@if ($server->webserver())
<div class="border-r border-gray-200 p-5 dark:border-gray-900">
<div class="flex items-center justify-center md:justify-start">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="h-8 w-8 text-primary-500"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 21a9.004 9.004 0 008.716-6.747M12 21a9.004 9.004 0 01-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 017.843 4.582M12 3a8.997 8.997 0 00-7.843 4.582m15.686 0A11.953 11.953 0 0112 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0121 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0112 16.5c-3.162 0-6.133-.815-8.716-2.247m0 0A9.015 9.015 0 013 12c0-1.605.42-3.113 1.157-4.418"
/>
</svg>
<x-heroicon name="o-globe-alt" class="h-8 w-8 text-primary-500" />
<div class="ml-2 hidden md:block">{{ __("Sites") }}</div>
</div>
<div class="mt-3 text-center text-3xl font-bold text-gray-600 dark:text-gray-400 md:text-left">
@ -38,20 +25,7 @@ class="h-8 w-8 text-primary-500"
@if ($server->database())
<div class="border-r border-gray-200 p-5 dark:border-gray-900">
<div class="flex items-center justify-center md:justify-start">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="h-8 w-8 text-primary-500"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M20.25 6.375c0 2.278-3.694 4.125-8.25 4.125S3.75 8.653 3.75 6.375m16.5 0c0-2.278-3.694-4.125-8.25-4.125S3.75 4.097 3.75 6.375m16.5 0v11.25c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125V6.375m16.5 0v3.75m-16.5-3.75v3.75m16.5 0v3.75C20.25 16.153 16.556 18 12 18s-8.25-1.847-8.25-4.125v-3.75m16.5 0c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125"
/>
</svg>
<x-heroicon name="o-circle-stack" class="h-8 w-8 text-primary-500" />
<div class="ml-2 hidden md:block">
{{ __("Databases") }}
</div>
@ -64,20 +38,7 @@ class="h-8 w-8 text-primary-500"
<div class="p-5">
<div class="flex items-center justify-center md:justify-start">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="h-8 w-8 text-primary-500"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<x-heroicon name="o-clock" class="h-8 w-8 text-primary-500" />
<div class="ml-2 hidden md:block">{{ __("Cron Jobs") }}</div>
</div>
<div class="mt-3 text-center text-3xl font-bold text-gray-600 dark:text-gray-400 md:text-left">

View File

@ -3,5 +3,7 @@
@include("services.partials.services-list")
{{-- @include("services.partials.add-ons") --}}
@include("services.partials.available-services")
@stack("modals")
</x-server-layout>

View File

@ -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")

View File

@ -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")

View File

@ -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")

View File

@ -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")

View File

@ -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")

View File

@ -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")

View File

@ -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")

View File

@ -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")

View File

@ -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")

View File

@ -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>

View File

@ -0,0 +1,35 @@
<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>
<x-live id="live-available-services">
<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>
</x-live>
</div>

View File

@ -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

View 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

View 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>

View 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

View File

@ -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

View 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>

View File

@ -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>

View 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>

View File

@ -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

View File

@ -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>

View File

@ -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)

View File

@ -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>

View File

@ -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>

View File

@ -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>

Some files were not shown because too many files have changed in this diff Show More