Compare commits

...

58 Commits
1.0.0 ... 1.4.0

Author SHA1 Message Date
e2dd9177f7 fix number format 2024-04-17 22:08:19 +02:00
5a9e8d6799 fix monitoring numbers 2024-04-17 21:09:09 +02:00
868b70f530 add cron to docker 2024-04-17 17:14:12 +02:00
d07e9bcad2 remote monitor (#167) 2024-04-17 16:03:06 +02:00
0cd815cce6 ui fix and build 2024-04-14 18:17:44 +02:00
5ab6617b5d fix read file 2024-04-14 17:53:08 +02:00
72b37c56fd ui fix 2024-04-14 14:53:58 +02:00
8a4ef66946 update Feature/add remote server logs (#166) 2024-04-14 14:41:00 +02:00
4517ca7d2a Feature/add remote server logs (#159) 2024-04-14 14:34:47 +02:00
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
e9016737d4 build frontend 2024-04-05 19:49:35 +02:00
f34d5eb82b Bump vite from 4.5.2 to 4.5.3 (#152) 2024-04-05 19:48:37 +02:00
12c500e125 Bug fixes (#155) 2024-04-05 19:45:09 +02:00
2d566b853f use textarea for code editor (#151) 2024-04-03 22:38:28 +02:00
ca93b521ec Merge branch 'main' into 1.x 2024-04-01 21:19:14 +02:00
bce05d3171 Merge pull request #148 from vitodeploy/versioning
show current version
2024-04-01 20:50:03 +02:00
929dd1dbaa show version a bit trasparent on mobile 2024-04-01 00:06:29 +02:00
2bcd145bea docker 2024-03-31 23:58:45 +02:00
c0f903d4ca show current version 2024-03-31 23:29:22 +02:00
cca4ab7ae3 fix code editor 2024-03-29 18:40:20 +01:00
51e7325d3d fix trusted procies 2024-03-29 18:25:14 +01:00
ce085879c1 Merge pull request #144 from vitodeploy/fix-env-update
empty content on editing file
2024-03-29 12:29:21 +01:00
8a49003e9e fix focus issue 2024-03-29 12:21:33 +01:00
dcc4276f09 fix spacing in the editor 2024-03-29 10:07:14 +01:00
f089779045 empty content on editing file 2024-03-29 00:42:36 +01:00
f1efb9a6c8 make project dropdown full width #132 2024-03-28 18:59:37 +01:00
a7d472fb45 update discord link 2024-03-27 22:33:24 +01:00
d01d406d3d Merge pull request #137 from vitodeploy/phpmyadmin
add phpmyadmin
2024-03-27 13:34:40 +01:00
c66c50835a fix tests 2024-03-27 13:32:25 +01:00
b6179d6693 build 2024-03-27 11:49:48 +01:00
9244e69fd8 add phpmyadmin 2024-03-27 11:41:29 +01:00
a7ba095919 build 2024-03-25 23:02:06 +01:00
807ae01646 Merge pull request #135 from vitodeploy/console
headless console
2024-03-25 22:57:56 +01:00
cc896d82e9 Merge branch 'console' of github.com:vitodeploy/vito into console 2024-03-25 22:55:48 +01:00
d16d3c1385 test 2024-03-25 22:52:45 +01:00
3946cf6b34 Merge branch '1.x' into console 2024-03-25 22:20:39 +01:00
165212fed2 add stop button 2024-03-25 22:20:21 +01:00
f6b36dfefc Update CONTRIBUTING.md 2024-03-25 22:19:30 +01:00
33594f2dba headless console 2024-03-24 21:58:48 +01:00
f68d6c7ca2 Merge pull request #133 from vitodeploy/fix-php-ini-update
fix php ini update bug
2024-03-24 15:19:06 +01:00
d504588f95 fix php ini update bug 2024-03-24 15:16:49 +01:00
a0af4e3e9d Merge pull request #128 from vitodeploy/1.x
Merge
2024-03-24 10:07:20 +01:00
ca0e33be2f Merge branch 'main' into 1.x 2024-03-24 09:58:50 +01:00
4d051330d6 Merge (#127) 2024-03-24 09:56:34 +01:00
ab2d6f64f3 Merge branch 'main' into 1.x 2024-03-24 09:56:07 +01:00
d9a56f95dd minify ace.js 2024-03-24 09:01:18 +01:00
884f18db63 Update README.md 2024-03-23 18:13:16 +01:00
536df65fc6 AGPL-3.0 2024-03-23 10:34:51 +01:00
226 changed files with 5222 additions and 22138 deletions

View File

@ -12,5 +12,5 @@ MAIL_PORT=
MAIL_USERNAME=null MAIL_USERNAME=null
MAIL_PASSWORD=null MAIL_PASSWORD=null
MAIL_ENCRYPTION=null MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=null MAIL_FROM_ADDRESS="noreply@${APP_NAME}"
MAIL_FROM_NAME="${APP_NAME}" MAIL_FROM_NAME="${APP_NAME}"

View File

@ -12,5 +12,5 @@ MAIL_PORT=
MAIL_USERNAME=null MAIL_USERNAME=null
MAIL_PASSWORD=null MAIL_PASSWORD=null
MAIL_ENCRYPTION=null MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=null MAIL_FROM_ADDRESS="noreply@${APP_NAME}"
MAIL_FROM_NAME="${APP_NAME}" MAIL_FROM_NAME="${APP_NAME}"

View File

@ -13,5 +13,5 @@ MAIL_PORT=
MAIL_USERNAME=null MAIL_USERNAME=null
MAIL_PASSWORD=null MAIL_PASSWORD=null
MAIL_ENCRYPTION=null MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=null MAIL_FROM_ADDRESS="noreply@${APP_NAME}"
MAIL_FROM_NAME="${APP_NAME}" MAIL_FROM_NAME="${APP_NAME}"

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

2
.gitignore vendored
View File

@ -7,6 +7,8 @@
/storage/test-key /storage/test-key
/storage/test-key.pub /storage/test-key.pub
/vendor /vendor
/storage/database.sqlite
/storage/database-test.sqlite
.env .env
.env.backup .env.backup
.env.production .env.production

View File

@ -1,23 +1,5 @@
# Contributing # Contributing
Thank you for your interest in contributing! There are a couple of contribution guidelines that make it easier to apply the incoming suggestions.
If you want to contribute please start with the issues. Issues labeled with "Bug" are the higher priorities. Please read the contribution guide on the website
## Issues https://vitodeploy.com/introduction/contribution-guide.html
1. Issues are the best place to propose a new feature.
2. If you are adding a feature that there is no issue for yet, please first open an issue and label it as "feature" and lets discuss it before you implement it.
3. Search the issues before proposing a feature to see if it is already under discussion. Referencing existing issues is a good way to increase the priority of your own.
4. We don't have an issue template yet, but the more detailed your explanation, the more quickly we'll be able to evaluate it.
5. Search for the issue that you also have. Give it a reaction (and comment, if you have something to add). We note that!
## Pull Requests
1. Open PRs represent issues that we're actively thinking about merging (at a pace we can manage). If we think a proposal needs more discussion, or that the existing code would require a lot of back-and-forth to merge, we might close it and suggest you make an issue.
2. All PRs should be made against the `main` branch. This can be changed in the future.
3. If you are making changes to the front-end layer, Please build the assets via `npm run build` and push it with the other changes.
4. Write tests for your code. Tests can be Unit or Feature.
5. Code refactors will be closed. For the architectural refactors open an issue first.
6. Use `./vendor/bin/pint` to style your code before opening a PR otherwise the actions will fail.
7. Typo fixes in documentation are welcome, but if it's at all debatable we might just close it.
## Misc
1. If you think we closed something incorrectly, feel free to (politely) tell us why! We're human and make mistakes.

View File

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

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\Enums\ServiceStatus;
use App\Models\Server; use App\Models\Server;
use App\SSH\Services\PHP\PHP;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
class ChangeDefaultCli class ChangeDefaultCli
@ -12,7 +13,9 @@ public function change(Server $server, array $input): void
{ {
$this->validate($server, $input); $this->validate($server, $input);
$service = $server->php($input['version']); $service = $server->php($input['version']);
$service->handler()->setDefaultCli(); /** @var PHP $handler */
$handler = $service->handler();
$handler->setDefaultCli();
$server->defaultService('php')->update(['is_default' => 0]); $server->defaultService('php')->update(['is_default' => 0]);
$service->update(['is_default' => 1]); $service->update(['is_default' => 1]);
$service->update(['status' => ServiceStatus::READY]); $service->update(['status' => ServiceStatus::READY]);

View File

@ -3,6 +3,7 @@
namespace App\Actions\PHP; namespace App\Actions\PHP;
use App\Models\Server; use App\Models\Server;
use App\SSH\Services\PHP\PHP;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
class GetPHPIni class GetPHPIni
@ -14,7 +15,10 @@ public function getIni(Server $server, array $input): string
$php = $server->php($input['version']); $php = $server->php($input['version']);
try { try {
return $php->handler()->getPHPIni(); /** @var PHP $handler */
$handler = $php->handler();
return $handler->getPHPIni();
} catch (\Throwable $e) { } catch (\Throwable $e) {
throw ValidationException::withMessages( throw ValidationException::withMessages(
['ini' => $e->getMessage()] ['ini' => $e->getMessage()]

View File

@ -4,6 +4,7 @@
use App\Models\Server; use App\Models\Server;
use App\Models\Service; use App\Models\Service;
use App\SSH\Services\PHP\PHP;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
@ -23,7 +24,9 @@ public function install(Server $server, array $input): Service
$service->save(); $service->save();
dispatch(function () use ($service, $input) { 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) { })->catch(function () use ($service, $input) {
$service->refresh(); $service->refresh();
$typeData = $service->type_data; $typeData = $service->type_data;

View File

@ -34,8 +34,6 @@ public function create(Site $site, array $input): void
$ssl->status = SslStatus::CREATED; $ssl->status = SslStatus::CREATED;
$ssl->save(); $ssl->save();
$site->type()->edit(); $site->type()->edit();
})->catch(function () use ($ssl) {
$ssl->delete();
}); });
} }

View File

@ -0,0 +1,35 @@
<?php
namespace App\Actions\Server;
use App\Models\Server;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
class CreateServerLog
{
/**
* @throws ValidationException
*/
public function create(Server $server, array $input): void
{
$this->validate($input);
$server->logs()->create([
'is_remote' => true,
'name' => $input['path'],
'type' => 'remote',
'disk' => 'ssh',
]);
}
/**
* @throws ValidationException
*/
protected function validate(array $input): void
{
Validator::make($input, [
'path' => 'required',
])->validate();
}
}

View File

@ -0,0 +1,57 @@
<?php
namespace App\Actions\Service;
use App\Enums\ServiceStatus;
use App\Models\Server;
use App\Models\Service;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
class Install
{
public function install(Server $server, array $input): Service
{
$this->validate($server, $input);
$service = new Service([
'server_id' => $server->id,
'name' => $input['name'],
'type' => $input['type'],
'version' => $input['version'],
'status' => ServiceStatus::INSTALLING,
]);
Validator::make($input, $service->handler()->creationRules($input))->validate();
$service->type_data = $service->handler()->creationData($input);
$service->save();
dispatch(function () use ($service) {
$service->handler()->install();
$service->status = ServiceStatus::READY;
$service->save();
})->catch(function () use ($service) {
$service->status = ServiceStatus::INSTALLATION_FAILED;
$service->save();
})->onConnection('ssh');
return $service;
}
private function validate(Server $server, array $input): void
{
Validator::make($input, [
'type' => [
'required',
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

@ -3,6 +3,8 @@
namespace App\Actions\Site; namespace App\Actions\Site;
use App\Enums\SiteStatus; use App\Enums\SiteStatus;
use App\Exceptions\RepositoryNotFound;
use App\Exceptions\RepositoryPermissionDenied;
use App\Exceptions\SourceControlIsNotConnected; use App\Exceptions\SourceControlIsNotConnected;
use App\Facades\Notifier; use App\Facades\Notifier;
use App\Models\Server; use App\Models\Server;
@ -19,7 +21,6 @@
class CreateSite class CreateSite
{ {
/** /**
* @throws SourceControlIsNotConnected
* @throws ValidationException * @throws ValidationException
*/ */
public function create(Server $server, array $input): Site public function create(Server $server, array $input): Site
@ -47,7 +48,15 @@ public function create(Server $server, array $input): Site
} }
} catch (SourceControlIsNotConnected) { } catch (SourceControlIsNotConnected) {
throw ValidationException::withMessages([ throw ValidationException::withMessages([
'source_control' => __('Source control is not connected'), 'source_control' => 'Source control is not connected',
]);
} catch (RepositoryPermissionDenied) {
throw ValidationException::withMessages([
'repository' => 'You do not have permission to access this repository',
]);
} catch (RepositoryNotFound) {
throw ValidationException::withMessages([
'repository' => 'Repository not found',
]); ]);
} }

View File

@ -0,0 +1,49 @@
<?php
namespace App\Actions\Site;
use App\Exceptions\RepositoryNotFound;
use App\Exceptions\RepositoryPermissionDenied;
use App\Exceptions\SourceControlIsNotConnected;
use App\Models\Site;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
class UpdateSourceControl
{
public function update(Site $site, array $input): void
{
$this->validate($input);
$site->source_control_id = $input['source_control'];
try {
if ($site->sourceControl()) {
$site->sourceControl()->getRepo($site->repository);
}
} catch (SourceControlIsNotConnected) {
throw ValidationException::withMessages([
'source_control' => 'Source control is not connected',
]);
} catch (RepositoryPermissionDenied) {
throw ValidationException::withMessages([
'repository' => 'You do not have permission to access this repository',
]);
} catch (RepositoryNotFound) {
throw ValidationException::withMessages([
'repository' => 'Repository not found',
]);
}
$site->save();
}
private function validate(array $input): void
{
Validator::make($input, [
'source_control' => [
'required',
Rule::exists('source_controls', 'id'),
],
])->validate();
}
}

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

@ -0,0 +1,31 @@
<?php
namespace App\Console\Commands;
use App\Models\Server;
use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Builder;
class GetMetricsCommand extends Command
{
protected $signature = 'metrics:get';
protected $description = 'Get server metrics';
public function handle(): void
{
$checkedMetrics = 0;
Server::query()->whereHas('services', function (Builder $query) {
$query->where('type', 'monitoring')
->where('name', 'remote-monitor');
})->chunk(10, function ($servers) use (&$checkedMetrics) {
/** @var Server $server */
foreach ($servers as $server) {
$info = $server->os()->resourceInfo();
$server->metrics()->create(array_merge($info, ['server_id' => $server->id]));
$checkedMetrics++;
}
});
$this->info("Checked $checkedMetrics metrics");
}
}

View File

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

View File

@ -10,7 +10,9 @@ final class Database
const MYSQL80 = 'mysql80'; const MYSQL80 = 'mysql80';
const MARIADB = 'mariadb'; const MARIADB103 = 'mariadb103';
const MARIADB104 = 'mariadb104';
const POSTGRESQL12 = 'postgresql12'; const POSTGRESQL12 = 'postgresql12';

View File

@ -11,4 +11,6 @@ final class SiteType
const LARAVEL = 'laravel'; const LARAVEL = 'laravel';
const WORDPRESS = 'wordpress'; const WORDPRESS = 'wordpress';
const PHPMYADMIN = 'phpmyadmin';
} }

View File

@ -2,9 +2,7 @@
namespace App\Exceptions; namespace App\Exceptions;
use Exception; class SSHAuthenticationError extends SSHError
class SSHAuthenticationError extends Exception
{ {
// //
} }

View File

@ -2,9 +2,7 @@
namespace App\Exceptions; namespace App\Exceptions;
use Exception; class SSLCreationException extends SSHError
class SSLCreationException extends Exception
{ {
// //
} }

View File

@ -12,7 +12,7 @@
* @method static init(Server $server, string $asUser = null) * @method static init(Server $server, string $asUser = null)
* @method static setLog(string $logType, int $siteId = null) * @method static setLog(string $logType, int $siteId = null)
* @method static connect() * @method static connect()
* @method static string exec(string $command, string $log = '', int $siteId = null) * @method static string exec(string $command, string $log = '', int $siteId = null, ?bool $stream = false)
* @method static string assertExecuted(array|string $commands) * @method static string assertExecuted(array|string $commands)
* @method static string assertExecutedContains(string $command) * @method static string assertExecutedContains(string $command)
* @method static disconnect() * @method static disconnect()

View File

@ -96,7 +96,7 @@ public function connect(bool $sftp = false): void
* @throws SSHCommandError * @throws SSHCommandError
* @throws SSHConnectionError * @throws SSHConnectionError
*/ */
public function exec(string|array $commands, string $log = '', ?int $siteId = null): string public function exec(string $command, string $log = '', ?int $siteId = null, ?bool $stream = false): string
{ {
if ($log) { if ($log) {
$this->setLog($log, $siteId); $this->setLog($log, $siteId);
@ -112,18 +112,34 @@ public function exec(string|array $commands, string $log = '', ?int $siteId = nu
throw new SSHConnectionError($e->getMessage()); throw new SSHConnectionError($e->getMessage());
} }
if (! is_array($commands)) {
$commands = [$commands];
}
try { try {
$result = ''; if ($this->asUser) {
foreach ($commands as $command) { $command = 'sudo su - '.$this->asUser.' -c '.'"'.addslashes($command).'"';
$result .= $this->executeCommand($command);
} }
return $result; $this->connection->setTimeout(0);
if ($stream) {
$this->connection->exec($command, function ($output) {
$this->log?->write($output);
echo $output;
ob_flush();
flush();
});
return '';
} else {
$output = $this->connection->exec($command);
$this->log?->write($output);
if (Str::contains($output, 'VITO_SSH_ERROR')) {
throw new SSHCommandError('SSH command failed with an error');
}
return $output;
}
} catch (Throwable $e) { } catch (Throwable $e) {
throw $e;
throw new SSHCommandError($e->getMessage()); throw new SSHCommandError($e->getMessage());
} }
} }
@ -141,28 +157,6 @@ public function upload(string $local, string $remote): void
$this->connection->put($remote, $local, SFTP::SOURCE_LOCAL_FILE); $this->connection->put($remote, $local, SFTP::SOURCE_LOCAL_FILE);
} }
/**
* @throws Exception
*/
protected function executeCommand(string $command): string
{
if ($this->asUser) {
$command = 'sudo su - '.$this->asUser.' -c '.'"'.addslashes($command).'"';
}
$this->connection->setTimeout(0);
$output = $this->connection->exec($command);
$this->log?->write($output);
if (Str::contains($output, 'VITO_SSH_ERROR')) {
throw new Exception('SSH command failed with an error');
}
return $output;
}
/** /**
* @throws Exception * @throws Exception
*/ */

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

@ -1,10 +1,11 @@
<?php <?php
namespace App\Http\Controllers; namespace App\Http\Controllers\API;
use App\Actions\Site\Deploy; use App\Actions\Site\Deploy;
use App\Exceptions\SourceControlIsNotConnected; use App\Exceptions\SourceControlIsNotConnected;
use App\Facades\Notifier; use App\Facades\Notifier;
use App\Http\Controllers\Controller;
use App\Models\GitHook; use App\Models\GitHook;
use App\Models\ServerLog; use App\Models\ServerLog;
use App\Notifications\SourceControlDisconnected; use App\Notifications\SourceControlDisconnected;

View File

@ -0,0 +1,16 @@
<?php
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
class HealthController extends Controller
{
public function __invoke()
{
return response()->json([
'success' => true,
'version' => vito_version(),
]);
}
}

View File

@ -7,6 +7,8 @@
use App\Actions\Site\UpdateDeploymentScript; use App\Actions\Site\UpdateDeploymentScript;
use App\Actions\Site\UpdateEnv; use App\Actions\Site\UpdateEnv;
use App\Exceptions\DeploymentScriptIsEmptyException; use App\Exceptions\DeploymentScriptIsEmptyException;
use App\Exceptions\RepositoryNotFound;
use App\Exceptions\RepositoryPermissionDenied;
use App\Exceptions\SourceControlIsNotConnected; use App\Exceptions\SourceControlIsNotConnected;
use App\Facades\Toast; use App\Facades\Toast;
use App\Helpers\HtmxResponse; use App\Helpers\HtmxResponse;
@ -24,12 +26,14 @@ public function deploy(Server $server, Site $site): HtmxResponse
app(Deploy::class)->run($site); app(Deploy::class)->run($site);
Toast::success('Deployment started!'); Toast::success('Deployment started!');
} catch (SourceControlIsNotConnected $e) { } catch (SourceControlIsNotConnected) {
Toast::error($e->getMessage()); Toast::error('Source control is not connected. Check site\'s settings.');
return htmx()->redirect(route('source-controls'));
} catch (DeploymentScriptIsEmptyException) { } catch (DeploymentScriptIsEmptyException) {
Toast::error('Deployment script is empty!'); Toast::error('Deployment script is empty!');
} catch (RepositoryPermissionDenied) {
Toast::error('You do not have permission to access this repository!');
} catch (RepositoryNotFound) {
Toast::error('Repository not found!');
} }
return htmx()->back(); return htmx()->back();
@ -83,6 +87,12 @@ public function enableAutoDeployment(Server $server, Site $site): HtmxResponse
Toast::success('Auto deployment has been enabled.'); Toast::success('Auto deployment has been enabled.');
} catch (SourceControlIsNotConnected) { } catch (SourceControlIsNotConnected) {
Toast::error('Source control is not connected. Check site\'s settings.'); Toast::error('Source control is not connected. Check site\'s settings.');
} catch (DeploymentScriptIsEmptyException) {
Toast::error('Deployment script is empty!');
} catch (RepositoryPermissionDenied) {
Toast::error('You do not have permission to access this repository!');
} catch (RepositoryNotFound) {
Toast::error('Repository not found!');
} }
} }

View File

@ -0,0 +1,43 @@
<?php
namespace App\Http\Controllers;
use App\Models\Server;
use Illuminate\Contracts\View\View;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
class ConsoleController extends Controller
{
public function index(Server $server): View
{
return view('console.index', [
'server' => $server,
]);
}
public function run(Server $server, Request $request)
{
$this->validate($request, [
'user' => [
'required',
Rule::in(['root', $server->ssh_user]),
],
'command' => 'required|string',
]);
return response()->stream(
function () use ($server, $request) {
$ssh = $server->ssh($request->user);
$log = 'console-'.time();
$ssh->exec(command: $request->command, log: $log, stream: true);
},
200,
[
'Cache-Control' => 'no-cache',
'X-Accel-Buffering' => 'no',
'Content-Type' => 'text/event-stream',
]
);
}
}

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

@ -71,7 +71,9 @@ public function updateIni(Server $server, Request $request): RedirectResponse
Toast::success('PHP ini updated!'); Toast::success('PHP ini updated!');
return back(); return back()->with([
'ini' => $request->input('ini'),
]);
} }
public function uninstall(Server $server, Request $request): RedirectResponse public function uninstall(Server $server, Request $request): RedirectResponse

View File

@ -54,7 +54,6 @@ public function show(Server $server): View
{ {
return view('servers.show', [ return view('servers.show', [
'server' => $server, 'server' => $server,
'logs' => $server->logs()->latest()->limit(10)->get(),
]); ]);
} }

View File

@ -2,10 +2,13 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Actions\Server\CreateServerLog;
use App\Facades\Toast;
use App\Models\Server; use App\Models\Server;
use App\Models\ServerLog; use App\Models\ServerLog;
use Illuminate\Contracts\View\View; use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class ServerLogController extends Controller class ServerLogController extends Controller
{ {
@ -13,6 +16,7 @@ public function index(Server $server): View
{ {
return view('server-logs.index', [ return view('server-logs.index', [
'server' => $server, 'server' => $server,
'pageTitle' => __('Vito Logs'),
]); ]);
} }
@ -26,4 +30,31 @@ public function show(Server $server, ServerLog $serverLog): RedirectResponse
'content' => $serverLog->getContent(), 'content' => $serverLog->getContent(),
]); ]);
} }
public function remote(Server $server): View
{
return view('server-logs.remote-logs', [
'server' => $server,
'remote' => true,
'pageTitle' => __('Remote Logs'),
]);
}
public function store(Server $server, Request $request): \App\Helpers\HtmxResponse
{
app(CreateServerLog::class)->create($server, $request->input());
Toast::success('Log added successfully.');
return htmx()->redirect(route('servers.logs.remote', ['server' => $server]));
}
public function destroy(Server $server, ServerLog $serverLog): RedirectResponse
{
$serverLog->delete();
Toast::success('Remote log deleted successfully.');
return redirect()->route('servers.logs.remote', ['server' => $server]);
}
} }

View File

@ -2,11 +2,15 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Actions\Service\Install;
use App\Actions\Service\Uninstall;
use App\Facades\Toast; use App\Facades\Toast;
use App\Helpers\HtmxResponse;
use App\Models\Server; use App\Models\Server;
use App\Models\Service; use App\Models\Service;
use Illuminate\Contracts\View\View; use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class ServiceController extends Controller class ServiceController extends Controller
{ {
@ -62,4 +66,22 @@ public function disable(Server $server, Service $service): RedirectResponse
return back(); return back();
} }
public function install(Server $server, Request $request): HtmxResponse
{
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!');
return htmx()->back();
}
} }

View File

@ -4,6 +4,7 @@
use App\Actions\Site\CreateSite; use App\Actions\Site\CreateSite;
use App\Actions\Site\DeleteSite; use App\Actions\Site\DeleteSite;
use App\Enums\SiteStatus;
use App\Enums\SiteType; use App\Enums\SiteType;
use App\Facades\Toast; use App\Facades\Toast;
use App\Helpers\HtmxResponse; use App\Helpers\HtmxResponse;
@ -42,14 +43,38 @@ public function create(Server $server): View
]); ]);
} }
public function show(Server $server, Site $site): View public function show(Server $server, Site $site, Request $request): View|RedirectResponse|HtmxResponse
{ {
if (in_array($site->status, [SiteStatus::INSTALLING, SiteStatus::INSTALLATION_FAILED])) {
if ($request->hasHeader('HX-Request')) {
return htmx()->redirect(route('servers.sites.installing', [$server, $site]));
}
return redirect()->route('servers.sites.installing', [$server, $site]);
}
return view('sites.show', [ return view('sites.show', [
'server' => $server, 'server' => $server,
'site' => $site, 'site' => $site,
]); ]);
} }
public function installing(Server $server, Site $site, Request $request): View|RedirectResponse|HtmxResponse
{
if (! in_array($site->status, [SiteStatus::INSTALLING, SiteStatus::INSTALLATION_FAILED])) {
if ($request->hasHeader('HX-Request')) {
return htmx()->redirect(route('servers.sites.show', [$server, $site]));
}
return redirect()->route('servers.sites.show', [$server, $site]);
}
return view('sites.installing', [
'server' => $server,
'site' => $site,
]);
}
public function destroy(Server $server, Site $site): RedirectResponse public function destroy(Server $server, Site $site): RedirectResponse
{ {
app(DeleteSite::class)->delete($site); app(DeleteSite::class)->delete($site);

View File

@ -13,6 +13,7 @@ public function index(Server $server, Site $site): View
return view('site-logs.index', [ return view('site-logs.index', [
'server' => $server, 'server' => $server,
'site' => $site, 'site' => $site,
'pageTitle' => __('Vito Logs'),
]); ]);
} }
} }

View File

@ -2,10 +2,12 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Actions\Site\UpdateSourceControl;
use App\Facades\Toast; use App\Facades\Toast;
use App\Helpers\HtmxResponse; use App\Helpers\HtmxResponse;
use App\Models\Server; use App\Models\Server;
use App\Models\Site; use App\Models\Site;
use App\SSH\Services\Webserver\Webserver;
use Illuminate\Contracts\View\View; use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -24,7 +26,10 @@ public function index(Server $server, Site $site): View
public function getVhost(Server $server, Site $site): RedirectResponse 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 public function updateVhost(Server $server, Site $site, Request $request): RedirectResponse
@ -34,7 +39,9 @@ public function updateVhost(Server $server, Site $site, Request $request): Redir
]); ]);
try { 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!'); Toast::success('VHost updated successfully!');
} catch (Throwable $e) { } catch (Throwable $e) {
@ -63,4 +70,13 @@ public function updatePHPVersion(Server $server, Site $site, Request $request):
return htmx()->back(); return htmx()->back();
} }
public function updateSourceControl(Server $server, Site $site, Request $request): HtmxResponse
{
$site = app(UpdateSourceControl::class)->update($site, $request->input());
Toast::success('Source control updated successfully!');
return htmx()->back();
}
} }

View File

@ -7,13 +7,14 @@
use App\Facades\Toast; use App\Facades\Toast;
use Closure; use Closure;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\Response;
class HandleSSHErrors class HandleSSHErrors
{ {
public function handle(Request $request, Closure $next) public function handle(Request $request, Closure $next)
{ {
$res = $next($request); $res = $next($request);
if ($res->exception) { if ($res instanceof Response && $res->exception) {
if ($res->exception instanceof SSHConnectionError || $res->exception instanceof SSHCommandError) { if ($res->exception instanceof SSHConnectionError || $res->exception instanceof SSHCommandError) {
Toast::error($res->exception->getMessage()); Toast::error($res->exception->getMessage());

View File

@ -12,7 +12,7 @@ class TrustProxies extends Middleware
* *
* @var array<int, string>|string|null * @var array<int, string>|string|null
*/ */
protected $proxies; protected $proxies = '*';
/** /**
* The headers that should be used to detect proxies. * The headers that should be used to detect proxies.

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'); return $this->queues()->whereNull('site_id');
} }
public function metrics(): HasMany
{
return $this->hasMany(Metric::class);
}
public function sshKeys(): BelongsToMany public function sshKeys(): BelongsToMany
{ {
return $this->belongsToMany(SshKey::class, 'server_ssh_keys') return $this->belongsToMany(SshKey::class, 'server_ssh_keys')
@ -325,6 +330,24 @@ public function php(?string $version = null): ?Service
return $this->service('php', $version); 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 public function sshKey(): array
{ {
/** @var FilesystemAdapter $storageDisk */ /** @var FilesystemAdapter $storageDisk */

View File

@ -16,6 +16,7 @@
* @property string $disk * @property string $disk
* @property Server $server * @property Server $server
* @property ?Site $site * @property ?Site $site
* @property bool $is_remote
*/ */
class ServerLog extends AbstractModel class ServerLog extends AbstractModel
{ {
@ -27,11 +28,13 @@ class ServerLog extends AbstractModel
'type', 'type',
'name', 'name',
'disk', 'disk',
'is_remote',
]; ];
protected $casts = [ protected $casts = [
'server_id' => 'integer', 'server_id' => 'integer',
'site_id' => 'integer', 'site_id' => 'integer',
'is_remote' => 'boolean',
]; ];
public static function boot(): void public static function boot(): void
@ -64,6 +67,17 @@ public function site(): BelongsTo
return $this->belongsTo(Site::class); return $this->belongsTo(Site::class);
} }
public static function getRemote($query, bool $active = true, ?Site $site = null)
{
$query->where('is_remote', $active);
if ($site) {
$query->where('name', 'like', $site->path.'%');
}
return $query;
}
public function write($buf): void public function write($buf): void
{ {
if (Str::contains($buf, 'VITO_SSH_ERROR')) { if (Str::contains($buf, 'VITO_SSH_ERROR')) {
@ -78,6 +92,10 @@ public function write($buf): void
public function getContent(): ?string public function getContent(): ?string
{ {
if ($this->is_remote) {
return $this->server->os()->tail($this->name, 150);
}
if (Storage::disk($this->disk)->exists($this->name)) { if (Storage::disk($this->disk)->exists($this->name)) {
return Storage::disk($this->disk)->get($this->name); return Storage::disk($this->disk)->get($this->name);
} }

View File

@ -4,12 +4,7 @@
use App\Actions\Service\Manage; use App\Actions\Service\Manage;
use App\Exceptions\ServiceInstallationFailed; use App\Exceptions\ServiceInstallationFailed;
use App\SSH\Services\Database\Database as DatabaseHandler; use App\SSH\Services\ServiceInterface;
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 Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Str; use Illuminate\Support\Str;
@ -53,7 +48,9 @@ public static function boot(): void
parent::boot(); parent::boot();
static::creating(function (Service $service) { static::creating(function (Service $service) {
if (array_key_exists($service->name, config('core.service_units'))) {
$service->unit = config('core.service_units')[$service->name][$service->server->os][$service->version]; $service->unit = config('core.service_units')[$service->name][$service->server->os][$service->version];
}
}); });
} }
@ -62,8 +59,8 @@ public function server(): BelongsTo
return $this->belongsTo(Server::class); return $this->belongsTo(Server::class);
} }
public function handler( public function handler(): ServiceInterface
): PHPHandler|WebserverHandler|DatabaseHandler|FirewallHandler|ProcessManagerHandler|RedisHandler { {
$handler = config('core.service_handlers')[$this->name]; $handler = config('core.service_handlers')[$this->name];
return new $handler($this); return new $handler($this);
@ -81,26 +78,26 @@ public function validateInstall($result): void
public function start(): void public function start(): void
{ {
app(Manage::class)->start($this); $this->unit && app(Manage::class)->start($this);
} }
public function stop(): void public function stop(): void
{ {
app(Manage::class)->stop($this); $this->unit && app(Manage::class)->stop($this);
} }
public function restart(): void public function restart(): void
{ {
app(Manage::class)->restart($this); $this->unit && app(Manage::class)->restart($this);
} }
public function enable(): void public function enable(): void
{ {
app(Manage::class)->enable($this); $this->unit && app(Manage::class)->enable($this);
} }
public function disable(): void public function disable(): void
{ {
app(Manage::class)->disable($this); $this->unit && app(Manage::class)->disable($this);
} }
} }

View File

@ -4,6 +4,7 @@
use App\Exceptions\SourceControlIsNotConnected; use App\Exceptions\SourceControlIsNotConnected;
use App\SiteTypes\SiteType; use App\SiteTypes\SiteType;
use App\SSH\Services\Webserver\Webserver;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
@ -185,7 +186,9 @@ public function php(): ?Service
public function changePHPVersion($version): void public function changePHPVersion($version): void
{ {
$this->server->webserver()->handler()->changePHPVersion($this, $version); /** @var Webserver $handler */
$handler = $this->server->webserver()->handler();
$handler->changePHPVersion($this, $version);
$this->php_version = $version; $this->php_version = $version;
$this->save(); $this->save();
} }

View File

@ -7,7 +7,6 @@
use App\Helpers\Toast; use App\Helpers\Toast;
use Illuminate\Contracts\Container\BindingResolutionException; use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Http\Resources\Json\ResourceCollection; use Illuminate\Http\Resources\Json\ResourceCollection;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider class AppServiceProvider extends ServiceProvider
@ -37,9 +36,5 @@ public function boot(): void
$this->app->bind('toast', function () { $this->app->bind('toast', function () {
return new Toast; return new Toast;
}); });
if (str(config('app.url'))->startsWith('https://')) {
URL::forceScheme('https');
}
} }
} }

View File

@ -98,12 +98,12 @@ public function reboot(): void
); );
} }
public function editFile(string $path, string $content): void public function editFile(string $path, ?string $content = null): void
{ {
$this->server->ssh()->exec( $this->server->ssh()->exec(
$this->getScript('edit-file.sh', [ $this->getScript('edit-file.sh', [
'path' => $path, 'path' => $path,
'content' => $content, 'content' => $content ?? '',
]), ]),
); );
} }
@ -117,6 +117,16 @@ public function readFile(string $path): string
); );
} }
public function tail(string $path, int $lines): string
{
return $this->server->ssh()->exec(
$this->getScript('tail.sh', [
'path' => $path,
'lines' => $lines,
])
);
}
public function runScript(string $path, string $script, ?int $siteId = null): ServerLog public function runScript(string $path, string $script, ?int $siteId = null): ServerLog
{ {
$ssh = $this->server->ssh(); $ssh = $this->server->ssh();
@ -131,4 +141,46 @@ public function runScript(string $path, string $script, ?int $siteId = null): Se
return $ssh->log; return $ssh->log;
} }
public function download(string $url, string $path): string
{
return $this->server->ssh()->exec(
$this->getScript('download.sh', [
'url' => $url,
'path' => $path,
])
);
}
public function unzip(string $path): string
{
return $this->server->ssh()->exec(
'unzip '.$path
);
}
public function cleanup(): void
{
$this->server->ssh()->exec(
$this->getScript('cleanup.sh'),
'cleanup'
);
}
public function resourceInfo(): array
{
$info = $this->server->ssh()->exec(
$this->getScript('resource-info.sh'),
);
return [
'load' => str($info)->after('load:')->before(PHP_EOL)->toString(),
'memory_total' => str($info)->after('memory_total:')->before(PHP_EOL)->toString(),
'memory_used' => str($info)->after('memory_used:')->before(PHP_EOL)->toString(),
'memory_free' => str($info)->after('memory_free:')->before(PHP_EOL)->toString(),
'disk_total' => str($info)->after('disk_total:')->before(PHP_EOL)->toString(),
'disk_used' => str($info)->after('disk_used:')->before(PHP_EOL)->toString(),
'disk_free' => str($info)->after('disk_free:')->before(PHP_EOL)->toString(),
];
}
} }

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,3 @@
if ! wget __url__ -O __path__; then
echo 'VITO_SSH_ERROR' && exit 1
fi

View File

@ -1 +1 @@
[ -f __path__ ] && cat __path__ [ -f __path__ ] && sudo cat __path__

View File

@ -0,0 +1,7 @@
echo "load:$(uptime | awk -F'load average:' '{print $2}' | awk -F, '{print $1}' | tr -d ' ')"
echo "memory_total:$(free -k | awk 'NR==2{print $2}')"
echo "memory_used:$(free -k | awk 'NR==2{print $3}')"
echo "memory_free:$(free -k | awk 'NR==2{print $7}')"
echo "disk_total:$(df -BM / | awk 'NR==2{print $2}' | sed 's/M//')"
echo "disk_used:$(df -BM / | awk 'NR==2{print $3}' | sed 's/M//')"
echo "disk_free:$(df -BM / | awk 'NR==2{print $4}' | sed 's/M//')"

View File

@ -0,0 +1 @@
sudo tail -n __lines__ __path__

View File

@ -0,0 +1,23 @@
<?php
namespace App\SSH\PHPMyAdmin;
use App\Models\Site;
use App\SSH\HasScripts;
class PHPMyAdmin
{
use HasScripts;
public function install(Site $site): void
{
$site->server->ssh()->exec(
$this->getScript('install.sh', [
'version' => $site->type_data['version'],
'path' => $site->path,
]),
'install-phpmyadmin',
$site->id
);
}
}

View File

@ -0,0 +1,25 @@
sudo rm -rf phpmyadmin
sudo rm -rf __path__
if ! wget https://files.phpmyadmin.net/phpMyAdmin/__version__/phpMyAdmin-__version__-all-languages.zip; then
echo 'VITO_SSH_ERROR' && exit 1
fi
if ! unzip phpMyAdmin-__version__-all-languages.zip; then
echo 'VITO_SSH_ERROR' && exit 1
fi
if ! rm -rf phpMyAdmin-__version__-all-languages.zip; then
echo 'VITO_SSH_ERROR' && exit 1
fi
if ! mv phpMyAdmin-__version__-all-languages __path__; then
echo 'VITO_SSH_ERROR' && exit 1
fi
if ! mv __path__/config.sample.inc.php __path__/config.inc.php; then
echo 'VITO_SSH_ERROR' && exit 1
fi
echo "PHPMyAdmin installed!"

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

@ -2,40 +2,78 @@
namespace App\SSH\Services\Database; namespace App\SSH\Services\Database;
use App\Enums\BackupStatus;
use App\Models\BackupFile; use App\Models\BackupFile;
use App\Models\Server;
use App\Models\Service;
use App\SSH\HasScripts; 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; use HasScripts;
protected Service $service;
protected Server $server;
abstract protected function getScriptsDir(): string; abstract protected function getScriptsDir(): string;
public function __construct(Service $service) public function creationRules(array $input): array
{ {
$this->service = $service; return [
$this->server = $service->server; '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 public function install(): void
{ {
$version = $this->service->version; $version = $this->service->version;
$command = $this->getScript($this->service->name.'/install-'.$version.'.sh'); $command = $this->getScript($this->service->name.'/install-'.$version.'.sh');
$this->server->ssh()->exec($command, 'install-'.$this->service->name.'-'.$version); $this->service->server->ssh()->exec($command, 'install-'.$this->service->name.'-'.$version);
$status = $this->server->systemd()->status($this->service->unit); $status = $this->service->server->systemd()->status($this->service->unit);
$this->service->validateInstall($status); $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 public function create(string $name): void
{ {
$this->server->ssh()->exec( $this->service->server->ssh()->exec(
$this->getScript($this->getScriptsDir().'/create.sh', [ $this->getScript($this->getScriptsDir().'/create.sh', [
'name' => $name, 'name' => $name,
]), ]),
@ -45,7 +83,7 @@ public function create(string $name): void
public function delete(string $name): void public function delete(string $name): void
{ {
$this->server->ssh()->exec( $this->service->server->ssh()->exec(
$this->getScript($this->getScriptsDir().'/delete.sh', [ $this->getScript($this->getScriptsDir().'/delete.sh', [
'name' => $name, 'name' => $name,
]), ]),
@ -55,7 +93,7 @@ public function delete(string $name): void
public function createUser(string $username, string $password, string $host): 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', [ $this->getScript($this->getScriptsDir().'/create-user.sh', [
'username' => $username, 'username' => $username,
'password' => $password, 'password' => $password,
@ -67,7 +105,7 @@ public function createUser(string $username, string $password, string $host): vo
public function deleteUser(string $username, string $host): void public function deleteUser(string $username, string $host): void
{ {
$this->server->ssh()->exec( $this->service->server->ssh()->exec(
$this->getScript($this->getScriptsDir().'/delete-user.sh', [ $this->getScript($this->getScriptsDir().'/delete-user.sh', [
'username' => $username, 'username' => $username,
'host' => $host, 'host' => $host,
@ -78,7 +116,7 @@ public function deleteUser(string $username, string $host): void
public function link(string $username, string $host, array $databases): void public function link(string $username, string $host, array $databases): void
{ {
$ssh = $this->server->ssh(); $ssh = $this->service->server->ssh();
foreach ($databases as $database) { foreach ($databases as $database) {
$ssh->exec( $ssh->exec(
@ -94,7 +132,7 @@ public function link(string $username, string $host, array $databases): void
public function unlink(string $username, string $host): void public function unlink(string $username, string $host): void
{ {
$this->server->ssh()->exec( $this->service->server->ssh()->exec(
$this->getScript($this->getScriptsDir().'/unlink.sh', [ $this->getScript($this->getScriptsDir().'/unlink.sh', [
'username' => $username, 'username' => $username,
'host' => $host, 'host' => $host,
@ -106,7 +144,7 @@ public function unlink(string $username, string $host): void
public function runBackup(BackupFile $backupFile): void public function runBackup(BackupFile $backupFile): void
{ {
// backup // backup
$this->server->ssh()->exec( $this->service->server->ssh()->exec(
$this->getScript($this->getScriptsDir().'/backup.sh', [ $this->getScript($this->getScriptsDir().'/backup.sh', [
'file' => $backupFile->name, 'file' => $backupFile->name,
'database' => $backupFile->backup->database->name, 'database' => $backupFile->backup->database->name,
@ -115,13 +153,13 @@ public function runBackup(BackupFile $backupFile): void
); );
// upload to storage // 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->path(),
$backupFile->storagePath(), $backupFile->storagePath(),
); );
// cleanup // cleanup
$this->server->ssh()->exec('rm '.$backupFile->name.'.zip'); $this->service->server->ssh()->exec('rm '.$backupFile->name.'.zip');
$backupFile->size = $upload['size']; $backupFile->size = $upload['size'];
$backupFile->save(); $backupFile->save();
@ -130,12 +168,12 @@ public function runBackup(BackupFile $backupFile): void
public function restoreBackup(BackupFile $backupFile, string $database): void public function restoreBackup(BackupFile $backupFile, string $database): void
{ {
// download // download
$backupFile->backup->storage->provider()->ssh($this->server)->download( $backupFile->backup->storage->provider()->ssh($this->service->server)->download(
$backupFile->storagePath(), $backupFile->storagePath(),
$backupFile->name.'.zip', $backupFile->name.'.zip',
); );
$this->server->ssh()->exec( $this->service->server->ssh()->exec(
$this->getScript($this->getScriptsDir().'/restore.sh', [ $this->getScript($this->getScriptsDir().'/restore.sh', [
'database' => $database, 'database' => $database,
'file' => $backupFile->name, '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 DEBIAN_FRONTEND=noninteractive apt-get install mariadb-server mariadb-backup -y
sudo systemctl unmask mysql.service
sudo service mysql start 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 DEBIAN_FRONTEND=noninteractive apt-get install mariadb-server mariadb-backup -y
sudo systemctl unmask mysql.service
sudo service mysql start 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 DEBIAN_FRONTEND=noninteractive apt-get install mysql-server -y
sudo systemctl unmask mysql.service
sudo service mysql enable sudo service mysql enable
sudo service mysql start 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 DEBIAN_FRONTEND=noninteractive apt-get install mysql-server -y
sudo systemctl unmask mysql.service
sudo service mysql enable sudo service mysql enable
sudo service mysql start 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; namespace App\SSH\Services\Firewall;
use App\Models\Service; use App\SSH\Services\AbstractService;
use App\SSH\Services\ServiceInterface;
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'), $this->getScript('ufw/install-ufw.sh'),
'install-ufw' '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 public function addRule(string $type, string $protocol, int $port, string $source, ?string $mask): void

View File

@ -0,0 +1,53 @@
<?php
namespace App\SSH\Services\Monitoring\RemoteMonitor;
use App\Models\Metric;
use App\SSH\Services\AbstractService;
use Closure;
use Illuminate\Validation\Rule;
class RemoteMonitor extends AbstractService
{
public function creationRules(array $input): array
{
return [
'type' => [
function (string $attribute, mixed $value, Closure $fail) {
$monitoringExists = $this->service->server->monitoring();
if ($monitoringExists) {
$fail('You already have a monitoring service on the server.');
}
},
],
'version' => [
'required',
Rule::in(['latest']),
],
];
}
public function creationData(array $input): array
{
return [
'data_retention' => 10,
];
}
public function data(): array
{
return [
'data_retention' => $this->service->type_data['data_retention'] ?? 10,
];
}
public function install(): void
{
//
}
public function uninstall(): void
{
Metric::where('server_id', $this->service->server_id)->delete();
}
}

View File

@ -0,0 +1,87 @@
<?php
namespace App\SSH\Services\Monitoring\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

@ -3,20 +3,43 @@
namespace App\SSH\Services\PHP; namespace App\SSH\Services\PHP;
use App\Exceptions\SSHCommandError; use App\Exceptions\SSHCommandError;
use App\Models\Service;
use App\SSH\HasScripts; use App\SSH\HasScripts;
use App\SSH\Services\ServiceInterface; use App\SSH\Services\AbstractService;
use Closure;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
class PHP implements ServiceInterface class PHP extends AbstractService
{ {
use HasScripts; use HasScripts;
protected Service $service; public function creationRules(array $input): array
public function __construct(Service $service)
{ {
$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 public function install(): void
@ -29,6 +52,7 @@ public function install(): void
]), ]),
'install-php-'.$this->service->version 'install-php-'.$this->service->version
); );
$this->service->server->os()->cleanup();
} }
public function uninstall(): void public function uninstall(): void
@ -39,6 +63,7 @@ public function uninstall(): void
]), ]),
'uninstall-php-'.$this->service->version 'uninstall-php-'.$this->service->version
); );
$this->service->server->os()->cleanup();
} }
public function setDefaultCli(): void public function setDefaultCli(): void

View File

@ -2,15 +2,37 @@
namespace App\SSH\Services\ProcessManager; namespace App\SSH\Services\ProcessManager;
use App\Models\Service; use App\SSH\Services\AbstractService;
use App\SSH\Services\ServiceInterface; use Closure;
abstract class AbstractProcessManager implements ProcessManager, ServiceInterface abstract class AbstractProcessManager extends AbstractService implements ProcessManager
{ {
protected Service $service; public function creationRules(array $input): array
{
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 __construct(Service $service) public function deletionRules(): array
{ {
$this->service = $service; 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'), $this->getScript('supervisor/install-supervisor.sh'),
'install-supervisor' '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; namespace App\SSH\Services\Redis;
use App\Models\Service;
use App\SSH\HasScripts; 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; 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 public function install(): void
@ -20,5 +31,17 @@ public function install(): void
$this->getScript('install.sh'), $this->getScript('install.sh'),
'install-redis' '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 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 install(): void;
public function uninstall(): void;
} }

View File

@ -2,12 +2,8 @@
namespace App\SSH\Services\Webserver; namespace App\SSH\Services\Webserver;
use App\Models\Service; use App\SSH\Services\AbstractService;
use App\SSH\Services\ServiceInterface;
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\Site;
use App\Models\Ssl; use App\Models\Ssl;
use App\SSH\HasScripts; use App\SSH\HasScripts;
use Closure;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Throwable; use Throwable;
@ -23,6 +24,31 @@ public function install(): void
]), ]),
'install-nginx' '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 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', [ $this->getScript('nginx/update-vhost.sh', [
'domain' => $site->domain, 'domain' => $site->domain,
'path' => $site->path, 'path' => $site->path,
'vhost' => $this->generateVhost($site, $noSSL), 'vhost' => $vhost ?? $this->generateVhost($site, $noSSL),
]), ]),
'update-vhost', 'update-vhost',
$site->id $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\Enums\ServiceStatus;
use App\Models\Server; use App\Models\Server;
use App\SSH\Services\PHP\PHP;
abstract class AbstractType implements ServerType abstract class AbstractType implements ServerType
{ {
@ -31,7 +32,9 @@ public function install(): void
$service->update(['status' => ServiceStatus::READY]); $service->update(['status' => ServiceStatus::READY]);
if ($service->type == 'php') { if ($service->type == 'php') {
$this->progress($currentProgress, 'installing-composer'); $this->progress($currentProgress, 'installing-composer');
$service->handler()->installComposer(); /** @var PHP $handler */
$handler = $service->handler();
$handler->installComposer();
} }
} }
$this->progress(100, 'finishing'); $this->progress(100, 'finishing');

View File

@ -13,7 +13,7 @@ public function createRules(array $input): array
], ],
'php' => [ 'php' => [
'required', 'required',
'in:'.implode(',', config('core.php_versions')), 'in:none,'.implode(',', config('core.php_versions')),
], ],
'database' => [ 'database' => [
'required', 'required',

49
app/SiteTypes/PHPMyAdmin.php Executable file
View File

@ -0,0 +1,49 @@
<?php
namespace App\SiteTypes;
use Illuminate\Validation\Rule;
class PHPMyAdmin extends PHPSite
{
public function supportedFeatures(): array
{
return [
//
];
}
public function createRules(array $input): array
{
return [
'php_version' => [
'required',
Rule::in($this->site->server->installedPHPVersions()),
],
'version' => 'required|string',
];
}
public function createFields(array $input): array
{
return [
'web_directory' => '',
'php_version' => $input['php_version'] ?? '',
];
}
public function data(array $input): array
{
return [
'version' => $input['version'],
];
}
public function install(): void
{
$this->site->server->webserver()->handler()->createVHost($this->site);
$this->progress(30);
app(\App\SSH\PHPMyAdmin\PHPMyAdmin::class)->install($this->site);
$this->progress(65);
}
}

View File

@ -34,7 +34,7 @@ public function connect(bool $sftp = false): void
} }
} }
public function exec(string|array $commands, string $log = '', ?int $siteId = null): string public function exec(string $command, string $log = '', ?int $siteId = null, ?bool $stream = false): string
{ {
if ($log) { if ($log) {
$this->setLog($log, $siteId); $this->setLog($log, $siteId);
@ -42,21 +42,19 @@ public function exec(string|array $commands, string $log = '', ?int $siteId = nu
$this->log = null; $this->log = null;
} }
if (! is_array($commands)) {
$commands = [$commands];
}
foreach ($commands as $command) {
if (is_string($command)) {
$this->commands[] = $command; $this->commands[] = $command;
} else {
$this->commands[] = get_class($command);
}
}
$output = $this->output ?? 'fake output'; $output = $this->output ?? 'fake output';
$this->log?->write($output); $this->log?->write($output);
if ($stream) {
echo $output;
ob_flush();
flush();
return '';
}
return $output; return $output;
} }

View File

@ -29,3 +29,22 @@ function htmx(): HtmxResponse
{ {
return new HtmxResponse(); return new HtmxResponse();
} }
function vito_version(): string
{
$version = exec('git describe --tags');
if (str($version)->contains('-')) {
return str($version)->before('-').' (dev)';
}
return $version;
}
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,34 +1,5 @@
<?php <?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\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 [ return [
/* /*
* SSH * SSH
@ -43,12 +14,12 @@
* General * General
*/ */
'operating_systems' => [ 'operating_systems' => [
OperatingSystem::UBUNTU20, \App\Enums\OperatingSystem::UBUNTU20,
OperatingSystem::UBUNTU22, \App\Enums\OperatingSystem::UBUNTU22,
], ],
'webservers' => ['none', 'nginx'], 'webservers' => ['none', 'nginx'],
'php_versions' => [ 'php_versions' => [
'none', // 'none',
// '5.6', // '5.6',
'7.0', '7.0',
'7.1', '7.1',
@ -125,114 +96,104 @@
], ],
'server_providers_class' => [ 'server_providers_class' => [
\App\Enums\ServerProvider::CUSTOM => \App\ServerProviders\Custom::class, \App\Enums\ServerProvider::CUSTOM => \App\ServerProviders\Custom::class,
\App\Enums\ServerProvider::AWS => AWS::class, \App\Enums\ServerProvider::AWS => \App\ServerProviders\AWS::class,
\App\Enums\ServerProvider::LINODE => Linode::class, \App\Enums\ServerProvider::LINODE => \App\ServerProviders\Linode::class,
\App\Enums\ServerProvider::DIGITALOCEAN => DigitalOcean::class, \App\Enums\ServerProvider::DIGITALOCEAN => \App\ServerProviders\DigitalOcean::class,
\App\Enums\ServerProvider::VULTR => Vultr::class, \App\Enums\ServerProvider::VULTR => \App\ServerProviders\Vultr::class,
\App\Enums\ServerProvider::HETZNER => Hetzner::class, \App\Enums\ServerProvider::HETZNER => \App\ServerProviders\Hetzner::class,
], ],
'server_providers_default_user' => [ 'server_providers_default_user' => [
'custom' => [ 'custom' => [
'ubuntu_18' => 'root', \App\Enums\OperatingSystem::UBUNTU20 => 'root',
'ubuntu_20' => 'root', \App\Enums\OperatingSystem::UBUNTU22 => 'root',
'ubuntu_22' => 'root',
], ],
'aws' => [ 'aws' => [
'ubuntu_18' => 'ubuntu', \App\Enums\OperatingSystem::UBUNTU20 => 'ubuntu',
'ubuntu_20' => 'ubuntu', \App\Enums\OperatingSystem::UBUNTU22 => 'ubuntu',
'ubuntu_22' => 'ubuntu',
], ],
'linode' => [ 'linode' => [
'ubuntu_18' => 'root', \App\Enums\OperatingSystem::UBUNTU20 => 'root',
'ubuntu_20' => 'root', \App\Enums\OperatingSystem::UBUNTU22 => 'root',
'ubuntu_22' => 'root',
], ],
'digitalocean' => [ 'digitalocean' => [
'ubuntu_18' => 'root', \App\Enums\OperatingSystem::UBUNTU20 => 'root',
'ubuntu_20' => 'root', \App\Enums\OperatingSystem::UBUNTU22 => 'root',
'ubuntu_22' => 'root',
], ],
'vultr' => [ 'vultr' => [
'ubuntu_18' => 'root', \App\Enums\OperatingSystem::UBUNTU20 => 'root',
'ubuntu_20' => 'root', \App\Enums\OperatingSystem::UBUNTU22 => 'root',
'ubuntu_22' => 'root',
], ],
'hetzner' => [ 'hetzner' => [
'ubuntu_18' => 'root', \App\Enums\OperatingSystem::UBUNTU20 => 'root',
'ubuntu_20' => 'root', \App\Enums\OperatingSystem::UBUNTU22 => 'root',
'ubuntu_22' => 'root',
], ],
], ],
/* /*
* Service * Service
*/ */
'service_types' => [
'nginx' => 'webserver',
'mysql' => 'database',
'mariadb' => 'database',
'postgresql' => 'database',
'redis' => 'memory_database',
'php' => 'php',
'ufw' => 'firewall',
'supervisor' => 'process_manager',
'vito-agent' => 'monitoring',
'remote-monitor' => 'monitoring',
],
'service_handlers' => [ 'service_handlers' => [
'nginx' => Nginx::class, 'nginx' => \App\SSH\Services\Webserver\Nginx::class,
'mysql' => Mysql::class, 'mysql' => \App\SSH\Services\Database\Mysql::class,
'mariadb' => Mariadb::class, 'mariadb' => \App\SSH\Services\Database\Mariadb::class,
'postgresql' => Postgresql::class, 'postgresql' => \App\SSH\Services\Database\Postgresql::class,
'redis' => Redis::class, 'redis' => \App\SSH\Services\Redis\Redis::class,
'php' => PHP::class, 'php' => \App\SSH\Services\PHP\PHP::class,
'ufw' => Ufw::class, 'ufw' => \App\SSH\Services\Firewall\Ufw::class,
'supervisor' => Supervisor::class, 'supervisor' => \App\SSH\Services\ProcessManager\Supervisor::class,
'vito-agent' => \App\SSH\Services\Monitoring\VitoAgent\VitoAgent::class,
'remote-monitor' => \App\SSH\Services\Monitoring\RemoteMonitor\RemoteMonitor::class,
], ],
'service_units' => [ 'service_units' => [
'nginx' => [ 'nginx' => [
'ubuntu_18' => [ \App\Enums\OperatingSystem::UBUNTU20 => [
'latest' => 'nginx', 'latest' => 'nginx',
], ],
'ubuntu_20' => [ \App\Enums\OperatingSystem::UBUNTU22 => [
'latest' => 'nginx',
],
'ubuntu_22' => [
'latest' => 'nginx', 'latest' => 'nginx',
], ],
], ],
'mysql' => [ 'mysql' => [
'ubuntu_18' => [ \App\Enums\OperatingSystem::UBUNTU20 => [
'5.7' => 'mysql', '5.7' => 'mysql',
'8.0' => 'mysql', '8.0' => 'mysql',
], ],
'ubuntu_20' => [ \App\Enums\OperatingSystem::UBUNTU22 => [
'5.7' => 'mysql',
'8.0' => 'mysql',
],
'ubuntu_22' => [
'5.7' => 'mysql', '5.7' => 'mysql',
'8.0' => 'mysql', '8.0' => 'mysql',
], ],
], ],
'mariadb' => [ 'mariadb' => [
'ubuntu_18' => [ \App\Enums\OperatingSystem::UBUNTU20 => [
'10.3' => 'mariadb', '10.3' => 'mariadb',
'10.4' => 'mariadb', '10.4' => 'mariadb',
], ],
'ubuntu_20' => [ \App\Enums\OperatingSystem::UBUNTU22 => [
'10.3' => 'mariadb',
'10.4' => 'mariadb',
],
'ubuntu_22' => [
'10.3' => 'mariadb', '10.3' => 'mariadb',
'10.4' => 'mariadb', '10.4' => 'mariadb',
], ],
], ],
'postgresql' => [ 'postgresql' => [
'ubuntu_18' => [ \App\Enums\OperatingSystem::UBUNTU20 => [
'12' => 'postgresql', '12' => 'postgresql',
'13' => 'postgresql', '13' => 'postgresql',
'14' => 'postgresql', '14' => 'postgresql',
'15' => 'postgresql', '15' => 'postgresql',
'16' => 'postgresql', '16' => 'postgresql',
], ],
'ubuntu_20' => [ \App\Enums\OperatingSystem::UBUNTU22 => [
'12' => 'postgresql',
'13' => 'postgresql',
'14' => 'postgresql',
'15' => 'postgresql',
'16' => 'postgresql',
],
'ubuntu_22' => [
'12' => 'postgresql', '12' => 'postgresql',
'13' => 'postgresql', '13' => 'postgresql',
'14' => 'postgresql', '14' => 'postgresql',
@ -241,19 +202,7 @@
], ],
], ],
'php' => [ 'php' => [
'ubuntu_18' => [ \App\Enums\OperatingSystem::UBUNTU20 => [
'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' => [
'5.6' => 'php5.6-fpm', '5.6' => 'php5.6-fpm',
'7.0' => 'php7.0-fpm', '7.0' => 'php7.0-fpm',
'7.1' => 'php7.1-fpm', '7.1' => 'php7.1-fpm',
@ -264,7 +213,7 @@
'8.1' => 'php8.1-fpm', '8.1' => 'php8.1-fpm',
'8.3' => 'php8.3-fpm', '8.3' => 'php8.3-fpm',
], ],
'ubuntu_22' => [ \App\Enums\OperatingSystem::UBUNTU22 => [
'5.6' => 'php5.6-fpm', '5.6' => 'php5.6-fpm',
'7.0' => 'php7.0-fpm', '7.0' => 'php7.0-fpm',
'7.1' => 'php7.1-fpm', '7.1' => 'php7.1-fpm',
@ -278,36 +227,35 @@
], ],
], ],
'redis' => [ 'redis' => [
'ubuntu_18' => [ \App\Enums\OperatingSystem::UBUNTU20 => [
'latest' => 'redis', 'latest' => 'redis',
], ],
'ubuntu_20' => [ \App\Enums\OperatingSystem::UBUNTU22 => [
'latest' => 'redis',
],
'ubuntu_22' => [
'latest' => 'redis', 'latest' => 'redis',
], ],
], ],
'supervisor' => [ 'supervisor' => [
'ubuntu_18' => [ \App\Enums\OperatingSystem::UBUNTU20 => [
'latest' => 'supervisor', 'latest' => 'supervisor',
], ],
'ubuntu_20' => [ \App\Enums\OperatingSystem::UBUNTU22 => [
'latest' => 'supervisor',
],
'ubuntu_22' => [
'latest' => 'supervisor', 'latest' => 'supervisor',
], ],
], ],
'ufw' => [ 'ufw' => [
'ubuntu_18' => [ \App\Enums\OperatingSystem::UBUNTU20 => [
'latest' => 'ufw', 'latest' => 'ufw',
], ],
'ubuntu_20' => [ \App\Enums\OperatingSystem::UBUNTU22 => [
'latest' => 'ufw', 'latest' => 'ufw',
], ],
'ubuntu_22' => [ ],
'latest' => 'ufw', 'vito-agent' => [
\App\Enums\OperatingSystem::UBUNTU20 => [
'latest' => 'vito-agent',
],
\App\Enums\OperatingSystem::UBUNTU22 => [
'latest' => 'vito-agent',
], ],
], ],
], ],
@ -320,12 +268,14 @@
\App\Enums\SiteType::PHP_BLANK, \App\Enums\SiteType::PHP_BLANK,
\App\Enums\SiteType::LARAVEL, \App\Enums\SiteType::LARAVEL,
\App\Enums\SiteType::WORDPRESS, \App\Enums\SiteType::WORDPRESS,
\App\Enums\SiteType::PHPMYADMIN,
], ],
'site_types_class' => [ 'site_types_class' => [
\App\Enums\SiteType::PHP => PHPSite::class, \App\Enums\SiteType::PHP => \App\SiteTypes\PHPSite::class,
\App\Enums\SiteType::PHP_BLANK => PHPBlank::class, \App\Enums\SiteType::PHP_BLANK => \App\SiteTypes\PHPBlank::class,
\App\Enums\SiteType::LARAVEL => Laravel::class, \App\Enums\SiteType::LARAVEL => \App\SiteTypes\Laravel::class,
\App\Enums\SiteType::WORDPRESS => Wordpress::class, \App\Enums\SiteType::WORDPRESS => \App\SiteTypes\Wordpress::class,
\App\Enums\SiteType::PHPMYADMIN => \App\SiteTypes\PHPMyAdmin::class,
], ],
/* /*
@ -337,9 +287,9 @@
'bitbucket', 'bitbucket',
], ],
'source_control_providers_class' => [ 'source_control_providers_class' => [
'github' => Github::class, 'github' => \App\SourceControlProviders\Github::class,
'gitlab' => Gitlab::class, 'gitlab' => \App\SourceControlProviders\Gitlab::class,
'bitbucket' => Bitbucket::class, 'bitbucket' => \App\SourceControlProviders\Bitbucket::class,
], ],
/* /*
@ -397,26 +347,33 @@
\App\Enums\NotificationChannel::TELEGRAM, \App\Enums\NotificationChannel::TELEGRAM,
], ],
'notification_channels_providers_class' => [ 'notification_channels_providers_class' => [
\App\Enums\NotificationChannel::SLACK => Slack::class, \App\Enums\NotificationChannel::SLACK => \App\NotificationChannels\Slack::class,
\App\Enums\NotificationChannel::DISCORD => Discord::class, \App\Enums\NotificationChannel::DISCORD => \App\NotificationChannels\Discord::class,
\App\Enums\NotificationChannel::EMAIL => Email::class, \App\Enums\NotificationChannel::EMAIL => \App\NotificationChannels\Email::class,
\App\Enums\NotificationChannel::TELEGRAM => Telegram::class, \App\Enums\NotificationChannel::TELEGRAM => \App\NotificationChannels\Telegram::class,
], ],
/* /*
* storage providers * storage providers
*/ */
'storage_providers' => [ 'storage_providers' => [
StorageProvider::DROPBOX, \App\Enums\StorageProvider::DROPBOX,
StorageProvider::FTP, \App\Enums\StorageProvider::FTP,
], ],
'storage_providers_class' => [ 'storage_providers_class' => [
'dropbox' => Dropbox::class, 'dropbox' => \App\StorageProviders\Dropbox::class,
'ftp' => FTP::class, 'ftp' => \App\StorageProviders\Ftp::class,
], ],
'ssl_types' => [ 'ssl_types' => [
\App\Enums\SslType::LETSENCRYPT, \App\Enums\SslType::LETSENCRYPT,
\App\Enums\SslType::CUSTOM, \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,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('server_logs', function (Blueprint $table) {
$table->boolean('is_remote')->default(false);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('server_logs', function (Blueprint $table) {
$table->dropColumn('is_remote');
});
}
};

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 FROM ubuntu:22.04
ENV RELEASE_ARG=$RELEASE
WORKDIR /var/www/html WORKDIR /var/www/html
ENV DEBIAN_FRONTEND noninteractive ENV DEBIAN_FRONTEND noninteractive
# upgrade # 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 # 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 # nginx
RUN apt install -y nginx RUN apt-get install -y nginx
# php # php
RUN apt update \ RUN apt-get update \
&& apt install -y gnupg gosu curl ca-certificates zip unzip git supervisor libcap2-bin libpng-dev \ && apt-get install -y cron gnupg gosu curl ca-certificates zip unzip git supervisor libcap2-bin libpng-dev \
python2 dnsutils librsvg2-bin fswatch wget \ python2 dnsutils librsvg2-bin fswatch wget \
&& add-apt-repository ppa:ondrej/php -y \ && add-apt-repository ppa:ondrej/php -y \
&& apt update \ && apt-get update \
&& apt install -y php8.2 php8.2-fpm php8.2-mbstring php8.2-mcrypt php8.2-gd php8.2-xml \ && apt-get install -y php8.2 php8.2-fpm php8.2-mbstring php8.2-mcrypt php8.2-gd php8.2-xml \
php8.2-curl php8.2-gettext php8.2-zip php8.2-bcmath php8.2-soap php8.2-redis php8.2-sqlite3 php8.2-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 COPY docker/php.ini /etc/php/8.2/cli/conf.d/99-vito.ini
@ -27,9 +31,9 @@ COPY docker/php.ini /etc/php/8.2/cli/conf.d/99-vito.ini
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
# app # app
COPY . /var/www/html RUN rm -rf /var/www/html
RUN rm -rf /var/www/html/vendor RUN git clone -b 1.x https://github.com/vitodeploy/vito.git /var/www/html
RUN rm -rf /var/www/html/.env 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 composer install --no-dev --prefer-dist
RUN chown -R www-data:www-data /var/www/html \ RUN chown -R www-data:www-data /var/www/html \
&& chmod -R 755 /var/www/html/storage /var/www/html/bootstrap/cache && chmod -R 755 /var/www/html/storage /var/www/html/bootstrap/cache
@ -40,6 +44,8 @@ RUN rm /etc/nginx/sites-enabled/default
COPY docker/nginx.conf /etc/nginx/sites-available/default COPY docker/nginx.conf /etc/nginx/sites-available/default
RUN ln -s /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default RUN ln -s /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default
RUN echo "* * * * * cd /var/www/html && php artisan schedule:run >> /var/log/cron.log 2>&1" | crontab -
# supervisord # supervisord
COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf

View File

@ -37,6 +37,8 @@ php /var/www/html/artisan view:cache
php /var/www/html/artisan user:create "$NAME" "$EMAIL" "$PASSWORD" php /var/www/html/artisan user:create "$NAME" "$EMAIL" "$PASSWORD"
cron
echo "Vito is running! 🚀" echo "Vito is running! 🚀"
/usr/bin/supervisord /usr/bin/supervisord

239
package-lock.json generated
View File

@ -8,8 +8,10 @@
"@tailwindcss/forms": "^0.5.2", "@tailwindcss/forms": "^0.5.2",
"@tailwindcss/typography": "^0.5.9", "@tailwindcss/typography": "^0.5.9",
"alpinejs": "^3.4.2", "alpinejs": "^3.4.2",
"apexcharts": "^3.44.2",
"autoprefixer": "^10.4.2", "autoprefixer": "^10.4.2",
"flowbite": "^2.3.0", "flowbite": "^2.3.0",
"flowbite-datepicker": "^1.2.6",
"htmx.org": "^1.9.10", "htmx.org": "^1.9.10",
"laravel-echo": "^1.15.0", "laravel-echo": "^1.15.0",
"laravel-vite-plugin": "^0.7.2", "laravel-vite-plugin": "^0.7.2",
@ -20,7 +22,7 @@
"tailwindcss": "^3.1.0", "tailwindcss": "^3.1.0",
"tippy.js": "^6.3.7", "tippy.js": "^6.3.7",
"toastr": "^2.1.4", "toastr": "^2.1.4",
"vite": "^4.5.2" "vite": "^4.5.3"
} }
}, },
"node_modules/@esbuild/android-arm": { "node_modules/@esbuild/android-arm": {
@ -529,6 +531,12 @@
"integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==", "integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==",
"dev": true "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": { "node_modules/alpinejs": {
"version": "3.12.0", "version": "3.12.0",
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.12.0.tgz", "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.12.0.tgz",
@ -557,6 +565,21 @@
"node": ">= 8" "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": { "node_modules/arg": {
"version": "5.0.2", "version": "5.0.2",
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
@ -885,6 +908,15 @@
"mini-svg-data-uri": "^1.4.3" "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": { "node_modules/fraction.js": {
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz",
@ -1680,6 +1712,97 @@
"url": "https://github.com/sponsors/ljharb" "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": { "node_modules/tailwindcss": {
"version": "3.3.1", "version": "3.3.1",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.1.tgz", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.1.tgz",
@ -1812,9 +1935,9 @@
"dev": true "dev": true
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "4.5.2", "version": "4.5.3",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz",
"integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==", "integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"esbuild": "^0.18.10", "esbuild": "^0.18.10",
@ -2177,6 +2300,12 @@
"integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==", "integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==",
"dev": true "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": { "alpinejs": {
"version": "3.12.0", "version": "3.12.0",
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.12.0.tgz", "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.12.0.tgz",
@ -2202,6 +2331,21 @@
"picomatch": "^2.0.4" "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": { "arg": {
"version": "5.0.2", "version": "5.0.2",
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
@ -2434,6 +2578,15 @@
"mini-svg-data-uri": "^1.4.3" "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": { "fraction.js": {
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz",
@ -2911,6 +3064,78 @@
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"dev": true "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": { "tailwindcss": {
"version": "3.3.1", "version": "3.3.1",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.1.tgz", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.1.tgz",
@ -3011,9 +3236,9 @@
"dev": true "dev": true
}, },
"vite": { "vite": {
"version": "4.5.2", "version": "4.5.3",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz",
"integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==", "integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==",
"dev": true, "dev": true,
"requires": { "requires": {
"esbuild": "^0.18.10", "esbuild": "^0.18.10",

View File

@ -22,6 +22,8 @@
"tailwindcss": "^3.1.0", "tailwindcss": "^3.1.0",
"tippy.js": "^6.3.7", "tippy.js": "^6.3.7",
"toastr": "^2.1.4", "toastr": "^2.1.4",
"vite": "^4.5.2" "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

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