mirror of
https://github.com/vitodeploy/vito.git
synced 2025-07-01 14:06:15 +00:00
Compare commits
49 Commits
Author | SHA1 | Date | |
---|---|---|---|
75aed62d75 | |||
aaef73d89d | |||
f03a029e36 | |||
52d195710b | |||
ddacc32e64 | |||
2ae9a14d02 | |||
3019c3d213 | |||
c43869d255 | |||
18748f77ac | |||
052e28d2e3 | |||
87ec0af697 | |||
e9016737d4 | |||
f34d5eb82b | |||
12c500e125 | |||
2d566b853f | |||
ca93b521ec | |||
bce05d3171 | |||
929dd1dbaa | |||
2bcd145bea | |||
c0f903d4ca | |||
cca4ab7ae3 | |||
51e7325d3d | |||
ce085879c1 | |||
8a49003e9e | |||
dcc4276f09 | |||
f089779045 | |||
f1efb9a6c8 | |||
a7d472fb45 | |||
d01d406d3d | |||
c66c50835a | |||
b6179d6693 | |||
9244e69fd8 | |||
a7ba095919 | |||
807ae01646 | |||
cc896d82e9 | |||
d16d3c1385 | |||
3946cf6b34 | |||
165212fed2 | |||
f6b36dfefc | |||
33594f2dba | |||
f68d6c7ca2 | |||
d504588f95 | |||
a0af4e3e9d | |||
ca0e33be2f | |||
4d051330d6 | |||
ab2d6f64f3 | |||
d9a56f95dd | |||
884f18db63 | |||
536df65fc6 |
@ -12,5 +12,5 @@ MAIL_PORT=
|
||||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_ENCRYPTION=null
|
||||
MAIL_FROM_ADDRESS=null
|
||||
MAIL_FROM_ADDRESS="noreply@${APP_NAME}"
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
|
@ -12,5 +12,5 @@ MAIL_PORT=
|
||||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_ENCRYPTION=null
|
||||
MAIL_FROM_ADDRESS=null
|
||||
MAIL_FROM_ADDRESS="noreply@${APP_NAME}"
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
|
@ -13,5 +13,5 @@ MAIL_PORT=
|
||||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_ENCRYPTION=null
|
||||
MAIL_FROM_ADDRESS=null
|
||||
MAIL_FROM_ADDRESS="noreply@${APP_NAME}"
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
|
35
.github/workflows/docker-1x.yml
vendored
Normal file
35
.github/workflows/docker-1x.yml
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
name: Build and push Docker image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 1.x
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
run: |
|
||||
docker buildx build . \
|
||||
-f docker/Dockerfile \
|
||||
-t vitodeploy/vito:1.x \
|
||||
--build-arg="RELEASE=0" \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
--no-cache \
|
||||
--push
|
35
.github/workflows/docker-release.yml
vendored
Normal file
35
.github/workflows/docker-release.yml
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
name: Build and push Docker image
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
run: |
|
||||
docker buildx build . \
|
||||
-f docker/Dockerfile \
|
||||
-t vitodeploy/vito:${{ github.event.release.tag_name }} \
|
||||
-t vitodeploy/vito:latest \
|
||||
--build-arg="RELEASE=0" \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
--no-cache \
|
||||
--push
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -7,6 +7,8 @@
|
||||
/storage/test-key
|
||||
/storage/test-key.pub
|
||||
/vendor
|
||||
/storage/database.sqlite
|
||||
/storage/database-test.sqlite
|
||||
.env
|
||||
.env.backup
|
||||
.env.production
|
||||
|
@ -1,23 +1,5 @@
|
||||
# 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
|
||||
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.
|
||||
https://vitodeploy.com/introduction/contribution-guide.html
|
||||
|
@ -36,8 +36,8 @@ ## Useful Links
|
||||
- [Install via Docker](https://vitodeploy.com/introduction/installation.html#install-via-docker)
|
||||
- [Feedbacks](https://vitodeploy.featurebase.app)
|
||||
- [Roadmap](https://vitodeploy.featurebase.app/roadmap)
|
||||
- [Video Demo](https://youtu.be/rLRHIyEfON8)
|
||||
- [Discord](https://discord.gg/dcUWA5DV)
|
||||
- [Video Demo](https://youtu.be/AbmUOBDOc28)
|
||||
- [Discord](https://discord.gg/uZeeHZZnm5)
|
||||
- [Contribution](/CONTRIBUTING.md)
|
||||
- [Security](/SECURITY.md)
|
||||
|
||||
@ -54,3 +54,4 @@ ## Credits
|
||||
- Prettier
|
||||
- Postcss
|
||||
- Flowbite
|
||||
- svgrepo.com
|
||||
|
150
app/Actions/Monitoring/GetMetrics.php
Normal file
150
app/Actions/Monitoring/GetMetrics.php
Normal file
@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Monitoring;
|
||||
|
||||
use App\Models\Server;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Contracts\Database\Query\Expression;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class GetMetrics
|
||||
{
|
||||
public function filter(Server $server, array $input): array
|
||||
{
|
||||
if (isset($input['from']) && isset($input['to']) && $input['from'] === $input['to']) {
|
||||
$input['from'] = Carbon::parse($input['from'])->format('Y-m-d').' 00:00:00';
|
||||
$input['to'] = Carbon::parse($input['to'])->format('Y-m-d').' 23:59:59';
|
||||
}
|
||||
|
||||
$defaultInput = [
|
||||
'period' => '10m',
|
||||
];
|
||||
|
||||
$input = array_merge($defaultInput, $input);
|
||||
|
||||
$this->validate($input);
|
||||
|
||||
return $this->metrics(
|
||||
server: $server,
|
||||
fromDate: $this->getFromDate($input),
|
||||
toDate: $this->getToDate($input),
|
||||
interval: $this->getInterval($input)
|
||||
);
|
||||
}
|
||||
|
||||
private function metrics(
|
||||
Server $server,
|
||||
Carbon $fromDate,
|
||||
Carbon $toDate,
|
||||
?Expression $interval = null
|
||||
): array {
|
||||
$metrics = DB::table('metrics')
|
||||
->where('server_id', $server->id)
|
||||
->whereBetween('created_at', [$fromDate->format('Y-m-d H:i:s'), $toDate->format('Y-m-d H:i:s')])
|
||||
->select(
|
||||
[
|
||||
DB::raw('created_at as date'),
|
||||
DB::raw('ROUND(AVG(load), 2) as load'),
|
||||
DB::raw('ROUND(AVG(memory_total), 2) as memory_total'),
|
||||
DB::raw('ROUND(AVG(memory_used), 2) as memory_used'),
|
||||
DB::raw('ROUND(AVG(memory_free), 2) as memory_free'),
|
||||
DB::raw('ROUND(AVG(disk_total), 2) as disk_total'),
|
||||
DB::raw('ROUND(AVG(disk_used), 2) as disk_used'),
|
||||
DB::raw('ROUND(AVG(disk_free), 2) as disk_free'),
|
||||
$interval,
|
||||
],
|
||||
)
|
||||
->groupByRaw('date_interval')
|
||||
->orderBy('date_interval')
|
||||
->get()
|
||||
->map(function ($item) {
|
||||
$item->date = Carbon::parse($item->date)->format('Y-m-d H:i');
|
||||
|
||||
return $item;
|
||||
});
|
||||
|
||||
return [
|
||||
'metrics' => $metrics,
|
||||
];
|
||||
}
|
||||
|
||||
private function getFromDate(array $input): Carbon
|
||||
{
|
||||
if ($input['period'] === 'custom') {
|
||||
return new Carbon($input['from']);
|
||||
}
|
||||
|
||||
return Carbon::parse('-'.convert_time_format($input['period']));
|
||||
}
|
||||
|
||||
private function getToDate(array $input): Carbon
|
||||
{
|
||||
if ($input['period'] === 'custom') {
|
||||
return new Carbon($input['to']);
|
||||
}
|
||||
|
||||
return Carbon::now();
|
||||
}
|
||||
|
||||
private function getInterval(array $input): Expression
|
||||
{
|
||||
if ($input['period'] === 'custom') {
|
||||
$from = new Carbon($input['from']);
|
||||
$to = new Carbon($input['to']);
|
||||
$periodInHours = $from->diffInHours($to);
|
||||
}
|
||||
|
||||
if (! isset($periodInHours)) {
|
||||
$periodInHours = Carbon::parse(
|
||||
convert_time_format($input['period'])
|
||||
)->diffInHours();
|
||||
}
|
||||
|
||||
if ($periodInHours <= 1) {
|
||||
return DB::raw("strftime('%Y-%m-%d %H:%M:00', created_at) as date_interval");
|
||||
}
|
||||
|
||||
if ($periodInHours <= 24) {
|
||||
return DB::raw("strftime('%Y-%m-%d %H:00:00', created_at) as date_interval");
|
||||
}
|
||||
|
||||
if ($periodInHours > 24) {
|
||||
return DB::raw("strftime('%Y-%m-%d 00:00:00', created_at) as date_interval");
|
||||
}
|
||||
}
|
||||
|
||||
private function validate(array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'period' => [
|
||||
'required',
|
||||
Rule::in([
|
||||
'10m',
|
||||
'30m',
|
||||
'1h',
|
||||
'12h',
|
||||
'1d',
|
||||
'7d',
|
||||
'custom',
|
||||
]),
|
||||
],
|
||||
])->validate();
|
||||
|
||||
if ($input['period'] === 'custom') {
|
||||
Validator::make($input, [
|
||||
'from' => [
|
||||
'required',
|
||||
'date',
|
||||
'before:to',
|
||||
],
|
||||
'to' => [
|
||||
'required',
|
||||
'date',
|
||||
'after:from',
|
||||
],
|
||||
])->validate();
|
||||
}
|
||||
}
|
||||
}
|
32
app/Actions/Monitoring/UpdateMetricSettings.php
Normal file
32
app/Actions/Monitoring/UpdateMetricSettings.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Monitoring;
|
||||
|
||||
use App\Models\Server;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateMetricSettings
|
||||
{
|
||||
public function update(Server $server, array $input): void
|
||||
{
|
||||
$this->validate($input);
|
||||
|
||||
$service = $server->monitoring();
|
||||
|
||||
$data = $service->handler()->data();
|
||||
$data['data_retention'] = $input['data_retention'];
|
||||
$service->type_data = $data;
|
||||
$service->save();
|
||||
}
|
||||
|
||||
private function validate(array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'data_retention' => [
|
||||
'required',
|
||||
Rule::in(config('core.metrics_data_retention')),
|
||||
],
|
||||
])->validate();
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@
|
||||
|
||||
use App\Enums\ServiceStatus;
|
||||
use App\Models\Server;
|
||||
use App\SSH\Services\PHP\PHP;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class ChangeDefaultCli
|
||||
@ -12,7 +13,9 @@ public function change(Server $server, array $input): void
|
||||
{
|
||||
$this->validate($server, $input);
|
||||
$service = $server->php($input['version']);
|
||||
$service->handler()->setDefaultCli();
|
||||
/** @var PHP $handler */
|
||||
$handler = $service->handler();
|
||||
$handler->setDefaultCli();
|
||||
$server->defaultService('php')->update(['is_default' => 0]);
|
||||
$service->update(['is_default' => 1]);
|
||||
$service->update(['status' => ServiceStatus::READY]);
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace App\Actions\PHP;
|
||||
|
||||
use App\Models\Server;
|
||||
use App\SSH\Services\PHP\PHP;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class GetPHPIni
|
||||
@ -14,7 +15,10 @@ public function getIni(Server $server, array $input): string
|
||||
$php = $server->php($input['version']);
|
||||
|
||||
try {
|
||||
return $php->handler()->getPHPIni();
|
||||
/** @var PHP $handler */
|
||||
$handler = $php->handler();
|
||||
|
||||
return $handler->getPHPIni();
|
||||
} catch (\Throwable $e) {
|
||||
throw ValidationException::withMessages(
|
||||
['ini' => $e->getMessage()]
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
use App\SSH\Services\PHP\PHP;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
@ -23,7 +24,9 @@ public function install(Server $server, array $input): Service
|
||||
$service->save();
|
||||
|
||||
dispatch(function () use ($service, $input) {
|
||||
$service->handler()->installExtension($input['extension']);
|
||||
/** @var PHP $handler */
|
||||
$handler = $service->handler();
|
||||
$handler->installExtension($input['extension']);
|
||||
})->catch(function () use ($service, $input) {
|
||||
$service->refresh();
|
||||
$typeData = $service->type_data;
|
||||
|
@ -34,8 +34,6 @@ public function create(Site $site, array $input): void
|
||||
$ssl->status = SslStatus::CREATED;
|
||||
$ssl->save();
|
||||
$site->type()->edit();
|
||||
})->catch(function () use ($ssl) {
|
||||
$ssl->delete();
|
||||
});
|
||||
}
|
||||
|
||||
|
57
app/Actions/Service/Install.php
Normal file
57
app/Actions/Service/Install.php
Normal 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();
|
||||
}
|
||||
}
|
28
app/Actions/Service/Uninstall.php
Normal file
28
app/Actions/Service/Uninstall.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Service;
|
||||
|
||||
use App\Enums\ServiceStatus;
|
||||
use App\Models\Service;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class Uninstall
|
||||
{
|
||||
public function uninstall(Service $service): void
|
||||
{
|
||||
Validator::make([
|
||||
'service' => $service->id,
|
||||
], $service->handler()->deletionRules())->validate();
|
||||
|
||||
$service->status = ServiceStatus::UNINSTALLING;
|
||||
$service->save();
|
||||
|
||||
dispatch(function () use ($service) {
|
||||
$service->handler()->uninstall();
|
||||
$service->delete();
|
||||
})->catch(function () use ($service) {
|
||||
$service->status = ServiceStatus::FAILED;
|
||||
$service->save();
|
||||
})->onConnection('ssh');
|
||||
}
|
||||
}
|
@ -3,6 +3,8 @@
|
||||
namespace App\Actions\Site;
|
||||
|
||||
use App\Enums\SiteStatus;
|
||||
use App\Exceptions\RepositoryNotFound;
|
||||
use App\Exceptions\RepositoryPermissionDenied;
|
||||
use App\Exceptions\SourceControlIsNotConnected;
|
||||
use App\Facades\Notifier;
|
||||
use App\Models\Server;
|
||||
@ -19,7 +21,6 @@
|
||||
class CreateSite
|
||||
{
|
||||
/**
|
||||
* @throws SourceControlIsNotConnected
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function create(Server $server, array $input): Site
|
||||
@ -47,7 +48,15 @@ public function create(Server $server, array $input): Site
|
||||
}
|
||||
} catch (SourceControlIsNotConnected) {
|
||||
throw ValidationException::withMessages([
|
||||
'source_control' => __('Source control is not connected'),
|
||||
'source_control' => 'Source control is not connected',
|
||||
]);
|
||||
} catch (RepositoryPermissionDenied) {
|
||||
throw ValidationException::withMessages([
|
||||
'repository' => 'You do not have permission to access this repository',
|
||||
]);
|
||||
} catch (RepositoryNotFound) {
|
||||
throw ValidationException::withMessages([
|
||||
'repository' => 'Repository not found',
|
||||
]);
|
||||
}
|
||||
|
||||
|
49
app/Actions/Site/UpdateSourceControl.php
Normal file
49
app/Actions/Site/UpdateSourceControl.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Site;
|
||||
|
||||
use App\Exceptions\RepositoryNotFound;
|
||||
use App\Exceptions\RepositoryPermissionDenied;
|
||||
use App\Exceptions\SourceControlIsNotConnected;
|
||||
use App\Models\Site;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class UpdateSourceControl
|
||||
{
|
||||
public function update(Site $site, array $input): void
|
||||
{
|
||||
$this->validate($input);
|
||||
|
||||
$site->source_control_id = $input['source_control'];
|
||||
try {
|
||||
if ($site->sourceControl()) {
|
||||
$site->sourceControl()->getRepo($site->repository);
|
||||
}
|
||||
} catch (SourceControlIsNotConnected) {
|
||||
throw ValidationException::withMessages([
|
||||
'source_control' => 'Source control is not connected',
|
||||
]);
|
||||
} catch (RepositoryPermissionDenied) {
|
||||
throw ValidationException::withMessages([
|
||||
'repository' => 'You do not have permission to access this repository',
|
||||
]);
|
||||
} catch (RepositoryNotFound) {
|
||||
throw ValidationException::withMessages([
|
||||
'repository' => 'Repository not found',
|
||||
]);
|
||||
}
|
||||
$site->save();
|
||||
}
|
||||
|
||||
private function validate(array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'source_control' => [
|
||||
'required',
|
||||
Rule::exists('source_controls', 'id'),
|
||||
],
|
||||
])->validate();
|
||||
}
|
||||
}
|
28
app/Console/Commands/DeleteOlderMetricsCommand.php
Normal file
28
app/Console/Commands/DeleteOlderMetricsCommand.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Service;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class DeleteOlderMetricsCommand extends Command
|
||||
{
|
||||
protected $signature = 'metrics:delete-older-metrics';
|
||||
|
||||
protected $description = 'Delete older metrics from database';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
Service::query()->where('type', 'monitoring')->chunk(100, function ($services) {
|
||||
$services->each(function ($service) {
|
||||
$this->info("Deleting older metrics for service {$service->server->name}");
|
||||
$service
|
||||
->server
|
||||
->metrics()
|
||||
->where('created_at', '<', now()->subDays($service->handler()->data()['data_retention']))
|
||||
->delete();
|
||||
$this->info('Metrics deleted');
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
@ -16,6 +16,7 @@ protected function schedule(Schedule $schedule): void
|
||||
$schedule->command('backups:run "0 0 * * *"')->daily();
|
||||
$schedule->command('backups:run "0 0 * * 0"')->weekly();
|
||||
$schedule->command('backups:run "0 0 1 * *"')->monthly();
|
||||
$schedule->command('metrics:delete-older-metrics')->daily();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -10,7 +10,9 @@ final class Database
|
||||
|
||||
const MYSQL80 = 'mysql80';
|
||||
|
||||
const MARIADB = 'mariadb';
|
||||
const MARIADB103 = 'mariadb103';
|
||||
|
||||
const MARIADB104 = 'mariadb104';
|
||||
|
||||
const POSTGRESQL12 = 'postgresql12';
|
||||
|
||||
|
@ -11,4 +11,6 @@ final class SiteType
|
||||
const LARAVEL = 'laravel';
|
||||
|
||||
const WORDPRESS = 'wordpress';
|
||||
|
||||
const PHPMYADMIN = 'phpmyadmin';
|
||||
}
|
||||
|
@ -2,9 +2,7 @@
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class SSHAuthenticationError extends Exception
|
||||
class SSHAuthenticationError extends SSHError
|
||||
{
|
||||
//
|
||||
}
|
||||
|
@ -2,9 +2,7 @@
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class SSLCreationException extends Exception
|
||||
class SSLCreationException extends SSHError
|
||||
{
|
||||
//
|
||||
}
|
||||
|
@ -12,7 +12,7 @@
|
||||
* @method static init(Server $server, string $asUser = null)
|
||||
* @method static setLog(string $logType, int $siteId = null)
|
||||
* @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 assertExecutedContains(string $command)
|
||||
* @method static disconnect()
|
||||
|
@ -96,7 +96,7 @@ public function connect(bool $sftp = false): void
|
||||
* @throws SSHCommandError
|
||||
* @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) {
|
||||
$this->setLog($log, $siteId);
|
||||
@ -112,18 +112,34 @@ public function exec(string|array $commands, string $log = '', ?int $siteId = nu
|
||||
throw new SSHConnectionError($e->getMessage());
|
||||
}
|
||||
|
||||
if (! is_array($commands)) {
|
||||
$commands = [$commands];
|
||||
}
|
||||
|
||||
try {
|
||||
$result = '';
|
||||
foreach ($commands as $command) {
|
||||
$result .= $this->executeCommand($command);
|
||||
if ($this->asUser) {
|
||||
$command = 'sudo su - '.$this->asUser.' -c '.'"'.addslashes($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) {
|
||||
throw $e;
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
36
app/Http/Controllers/API/AgentController.php
Normal file
36
app/Http/Controllers/API/AgentController.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\API;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class AgentController extends Controller
|
||||
{
|
||||
public function __invoke(Request $request, Server $server, int $id): JsonResponse
|
||||
{
|
||||
$validated = $this->validate($request, [
|
||||
'load' => 'required|numeric',
|
||||
'memory_total' => 'required|numeric',
|
||||
'memory_used' => 'required|numeric',
|
||||
'memory_free' => 'required|numeric',
|
||||
'disk_total' => 'required|numeric',
|
||||
'disk_used' => 'required|numeric',
|
||||
'disk_free' => 'required|numeric',
|
||||
]);
|
||||
|
||||
/** @var Service $service */
|
||||
$service = $server->services()->findOrFail($id);
|
||||
|
||||
if ($request->header('secret') !== $service->handler()->data()['secret']) {
|
||||
return response()->json(['error' => 'Unauthorized'], 401);
|
||||
}
|
||||
|
||||
$server->metrics()->create(array_merge($validated, ['server_id' => $server->id]));
|
||||
|
||||
return response()->json();
|
||||
}
|
||||
}
|
@ -1,10 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
namespace App\Http\Controllers\API;
|
||||
|
||||
use App\Actions\Site\Deploy;
|
||||
use App\Exceptions\SourceControlIsNotConnected;
|
||||
use App\Facades\Notifier;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\GitHook;
|
||||
use App\Models\ServerLog;
|
||||
use App\Notifications\SourceControlDisconnected;
|
16
app/Http/Controllers/API/HealthController.php
Normal file
16
app/Http/Controllers/API/HealthController.php
Normal 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(),
|
||||
]);
|
||||
}
|
||||
}
|
@ -7,6 +7,8 @@
|
||||
use App\Actions\Site\UpdateDeploymentScript;
|
||||
use App\Actions\Site\UpdateEnv;
|
||||
use App\Exceptions\DeploymentScriptIsEmptyException;
|
||||
use App\Exceptions\RepositoryNotFound;
|
||||
use App\Exceptions\RepositoryPermissionDenied;
|
||||
use App\Exceptions\SourceControlIsNotConnected;
|
||||
use App\Facades\Toast;
|
||||
use App\Helpers\HtmxResponse;
|
||||
@ -24,12 +26,14 @@ public function deploy(Server $server, Site $site): HtmxResponse
|
||||
app(Deploy::class)->run($site);
|
||||
|
||||
Toast::success('Deployment started!');
|
||||
} catch (SourceControlIsNotConnected $e) {
|
||||
Toast::error($e->getMessage());
|
||||
|
||||
return htmx()->redirect(route('source-controls'));
|
||||
} catch (SourceControlIsNotConnected) {
|
||||
Toast::error('Source control is not connected. Check site\'s settings.');
|
||||
} catch (DeploymentScriptIsEmptyException) {
|
||||
Toast::error('Deployment script is empty!');
|
||||
} catch (RepositoryPermissionDenied) {
|
||||
Toast::error('You do not have permission to access this repository!');
|
||||
} catch (RepositoryNotFound) {
|
||||
Toast::error('Repository not found!');
|
||||
}
|
||||
|
||||
return htmx()->back();
|
||||
@ -83,6 +87,12 @@ public function enableAutoDeployment(Server $server, Site $site): HtmxResponse
|
||||
Toast::success('Auto deployment has been enabled.');
|
||||
} catch (SourceControlIsNotConnected) {
|
||||
Toast::error('Source control is not connected. Check site\'s settings.');
|
||||
} catch (DeploymentScriptIsEmptyException) {
|
||||
Toast::error('Deployment script is empty!');
|
||||
} catch (RepositoryPermissionDenied) {
|
||||
Toast::error('You do not have permission to access this repository!');
|
||||
} catch (RepositoryNotFound) {
|
||||
Toast::error('Repository not found!');
|
||||
}
|
||||
}
|
||||
|
||||
|
43
app/Http/Controllers/ConsoleController.php
Normal file
43
app/Http/Controllers/ConsoleController.php
Normal 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',
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
44
app/Http/Controllers/MetricController.php
Normal file
44
app/Http/Controllers/MetricController.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Actions\Monitoring\GetMetrics;
|
||||
use App\Actions\Monitoring\UpdateMetricSettings;
|
||||
use App\Facades\Toast;
|
||||
use App\Helpers\HtmxResponse;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class MetricController extends Controller
|
||||
{
|
||||
public function index(Server $server, Request $request): View|RedirectResponse
|
||||
{
|
||||
$this->checkIfMonitoringServiceInstalled($server);
|
||||
|
||||
return view('metrics.index', [
|
||||
'server' => $server,
|
||||
'data' => app(GetMetrics::class)->filter($server, $request->input()),
|
||||
'lastMetric' => $server->metrics()->latest()->first(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function settings(Server $server, Request $request): HtmxResponse
|
||||
{
|
||||
$this->checkIfMonitoringServiceInstalled($server);
|
||||
|
||||
app(UpdateMetricSettings::class)->update($server, $request->input());
|
||||
|
||||
Toast::success('Metric settings updated successfully');
|
||||
|
||||
return htmx()->back();
|
||||
}
|
||||
|
||||
private function checkIfMonitoringServiceInstalled(Server $server): void
|
||||
{
|
||||
if (! $server->monitoring()) {
|
||||
abort(404, 'Monitoring service is not installed on this server');
|
||||
}
|
||||
}
|
||||
}
|
@ -71,7 +71,9 @@ public function updateIni(Server $server, Request $request): RedirectResponse
|
||||
|
||||
Toast::success('PHP ini updated!');
|
||||
|
||||
return back();
|
||||
return back()->with([
|
||||
'ini' => $request->input('ini'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function uninstall(Server $server, Request $request): RedirectResponse
|
||||
|
@ -2,11 +2,15 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Actions\Service\Install;
|
||||
use App\Actions\Service\Uninstall;
|
||||
use App\Facades\Toast;
|
||||
use App\Helpers\HtmxResponse;
|
||||
use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ServiceController extends Controller
|
||||
{
|
||||
@ -62,4 +66,22 @@ public function disable(Server $server, Service $service): RedirectResponse
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
use App\Actions\Site\CreateSite;
|
||||
use App\Actions\Site\DeleteSite;
|
||||
use App\Enums\SiteStatus;
|
||||
use App\Enums\SiteType;
|
||||
use App\Facades\Toast;
|
||||
use App\Helpers\HtmxResponse;
|
||||
@ -42,14 +43,38 @@ public function create(Server $server): View
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(Server $server, Site $site): View
|
||||
public function show(Server $server, Site $site, Request $request): View|RedirectResponse|HtmxResponse
|
||||
{
|
||||
if (in_array($site->status, [SiteStatus::INSTALLING, SiteStatus::INSTALLATION_FAILED])) {
|
||||
if ($request->hasHeader('HX-Request')) {
|
||||
return htmx()->redirect(route('servers.sites.installing', [$server, $site]));
|
||||
}
|
||||
|
||||
return redirect()->route('servers.sites.installing', [$server, $site]);
|
||||
}
|
||||
|
||||
return view('sites.show', [
|
||||
'server' => $server,
|
||||
'site' => $site,
|
||||
]);
|
||||
}
|
||||
|
||||
public function installing(Server $server, Site $site, Request $request): View|RedirectResponse|HtmxResponse
|
||||
{
|
||||
if (! in_array($site->status, [SiteStatus::INSTALLING, SiteStatus::INSTALLATION_FAILED])) {
|
||||
if ($request->hasHeader('HX-Request')) {
|
||||
return htmx()->redirect(route('servers.sites.show', [$server, $site]));
|
||||
}
|
||||
|
||||
return redirect()->route('servers.sites.show', [$server, $site]);
|
||||
}
|
||||
|
||||
return view('sites.installing', [
|
||||
'server' => $server,
|
||||
'site' => $site,
|
||||
]);
|
||||
}
|
||||
|
||||
public function destroy(Server $server, Site $site): RedirectResponse
|
||||
{
|
||||
app(DeleteSite::class)->delete($site);
|
||||
|
@ -2,10 +2,12 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Actions\Site\UpdateSourceControl;
|
||||
use App\Facades\Toast;
|
||||
use App\Helpers\HtmxResponse;
|
||||
use App\Models\Server;
|
||||
use App\Models\Site;
|
||||
use App\SSH\Services\Webserver\Webserver;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
@ -24,7 +26,10 @@ public function index(Server $server, Site $site): View
|
||||
|
||||
public function getVhost(Server $server, Site $site): RedirectResponse
|
||||
{
|
||||
return back()->with('vhost', $server->webserver()->handler()->getVHost($site));
|
||||
/** @var Webserver $handler */
|
||||
$handler = $server->webserver()->handler();
|
||||
|
||||
return back()->with('vhost', $handler->getVHost($site));
|
||||
}
|
||||
|
||||
public function updateVhost(Server $server, Site $site, Request $request): RedirectResponse
|
||||
@ -34,7 +39,9 @@ public function updateVhost(Server $server, Site $site, Request $request): Redir
|
||||
]);
|
||||
|
||||
try {
|
||||
$server->webserver()->handler()->updateVHost($site, false, $request->input('vhost'));
|
||||
/** @var Webserver $handler */
|
||||
$handler = $server->webserver()->handler();
|
||||
$handler->updateVHost($site, false, $request->input('vhost'));
|
||||
|
||||
Toast::success('VHost updated successfully!');
|
||||
} catch (Throwable $e) {
|
||||
@ -63,4 +70,13 @@ public function updatePHPVersion(Server $server, Site $site, Request $request):
|
||||
|
||||
return htmx()->back();
|
||||
}
|
||||
|
||||
public function updateSourceControl(Server $server, Site $site, Request $request): HtmxResponse
|
||||
{
|
||||
$site = app(UpdateSourceControl::class)->update($site, $request->input());
|
||||
|
||||
Toast::success('Source control updated successfully!');
|
||||
|
||||
return htmx()->back();
|
||||
}
|
||||
}
|
||||
|
@ -7,13 +7,14 @@
|
||||
use App\Facades\Toast;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
class HandleSSHErrors
|
||||
{
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
$res = $next($request);
|
||||
if ($res->exception) {
|
||||
if ($res instanceof Response && $res->exception) {
|
||||
if ($res->exception instanceof SSHConnectionError || $res->exception instanceof SSHCommandError) {
|
||||
Toast::error($res->exception->getMessage());
|
||||
|
||||
|
@ -12,7 +12,7 @@ class TrustProxies extends Middleware
|
||||
*
|
||||
* @var array<int, string>|string|null
|
||||
*/
|
||||
protected $proxies;
|
||||
protected $proxies = '*';
|
||||
|
||||
/**
|
||||
* The headers that should be used to detect proxies.
|
||||
@ -20,7 +20,7 @@ class TrustProxies extends Middleware
|
||||
* @var int
|
||||
*/
|
||||
protected $headers =
|
||||
Request::HEADER_X_FORWARDED_FOR |
|
||||
Request::HEADER_X_FORWARDED_FOR |
|
||||
Request::HEADER_X_FORWARDED_HOST |
|
||||
Request::HEADER_X_FORWARDED_PORT |
|
||||
Request::HEADER_X_FORWARDED_PROTO |
|
||||
|
53
app/Models/Metric.php
Normal file
53
app/Models/Metric.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $server_id
|
||||
* @property float $load
|
||||
* @property float $memory_total
|
||||
* @property float $memory_used
|
||||
* @property float $memory_free
|
||||
* @property float $disk_total
|
||||
* @property float $disk_used
|
||||
* @property float $disk_free
|
||||
* @property Server $server
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
*/
|
||||
class Metric extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'server_id',
|
||||
'load',
|
||||
'memory_total',
|
||||
'memory_used',
|
||||
'memory_free',
|
||||
'disk_total',
|
||||
'disk_used',
|
||||
'disk_free',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'server_id' => 'integer',
|
||||
'load' => 'float',
|
||||
'memory_total' => 'float',
|
||||
'memory_used' => 'float',
|
||||
'memory_free' => 'float',
|
||||
'disk_total' => 'float',
|
||||
'disk_used' => 'float',
|
||||
'disk_free' => 'float',
|
||||
];
|
||||
|
||||
public function server(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Server::class);
|
||||
}
|
||||
}
|
@ -195,6 +195,11 @@ public function daemons(): HasMany
|
||||
return $this->queues()->whereNull('site_id');
|
||||
}
|
||||
|
||||
public function metrics(): HasMany
|
||||
{
|
||||
return $this->hasMany(Metric::class);
|
||||
}
|
||||
|
||||
public function sshKeys(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(SshKey::class, 'server_ssh_keys')
|
||||
@ -325,6 +330,24 @@ public function php(?string $version = null): ?Service
|
||||
return $this->service('php', $version);
|
||||
}
|
||||
|
||||
public function memoryDatabase(?string $version = null): ?Service
|
||||
{
|
||||
if (! $version) {
|
||||
return $this->defaultService('memory_database');
|
||||
}
|
||||
|
||||
return $this->service('memory_database', $version);
|
||||
}
|
||||
|
||||
public function monitoring(?string $version = null): ?Service
|
||||
{
|
||||
if (! $version) {
|
||||
return $this->defaultService('monitoring');
|
||||
}
|
||||
|
||||
return $this->service('monitoring', $version);
|
||||
}
|
||||
|
||||
public function sshKey(): array
|
||||
{
|
||||
/** @var FilesystemAdapter $storageDisk */
|
||||
|
@ -4,12 +4,7 @@
|
||||
|
||||
use App\Actions\Service\Manage;
|
||||
use App\Exceptions\ServiceInstallationFailed;
|
||||
use App\SSH\Services\Database\Database as DatabaseHandler;
|
||||
use App\SSH\Services\Firewall\Firewall as FirewallHandler;
|
||||
use App\SSH\Services\PHP\PHP as PHPHandler;
|
||||
use App\SSH\Services\ProcessManager\ProcessManager as ProcessManagerHandler;
|
||||
use App\SSH\Services\Redis\Redis as RedisHandler;
|
||||
use App\SSH\Services\Webserver\Webserver as WebserverHandler;
|
||||
use App\SSH\Services\ServiceInterface;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Support\Str;
|
||||
@ -53,7 +48,9 @@ public static function boot(): void
|
||||
parent::boot();
|
||||
|
||||
static::creating(function (Service $service) {
|
||||
$service->unit = config('core.service_units')[$service->name][$service->server->os][$service->version];
|
||||
if (array_key_exists($service->name, config('core.service_units'))) {
|
||||
$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);
|
||||
}
|
||||
|
||||
public function handler(
|
||||
): PHPHandler|WebserverHandler|DatabaseHandler|FirewallHandler|ProcessManagerHandler|RedisHandler {
|
||||
public function handler(): ServiceInterface
|
||||
{
|
||||
$handler = config('core.service_handlers')[$this->name];
|
||||
|
||||
return new $handler($this);
|
||||
@ -81,26 +78,26 @@ public function validateInstall($result): void
|
||||
|
||||
public function start(): void
|
||||
{
|
||||
app(Manage::class)->start($this);
|
||||
$this->unit && app(Manage::class)->start($this);
|
||||
}
|
||||
|
||||
public function stop(): void
|
||||
{
|
||||
app(Manage::class)->stop($this);
|
||||
$this->unit && app(Manage::class)->stop($this);
|
||||
}
|
||||
|
||||
public function restart(): void
|
||||
{
|
||||
app(Manage::class)->restart($this);
|
||||
$this->unit && app(Manage::class)->restart($this);
|
||||
}
|
||||
|
||||
public function enable(): void
|
||||
{
|
||||
app(Manage::class)->enable($this);
|
||||
$this->unit && app(Manage::class)->enable($this);
|
||||
}
|
||||
|
||||
public function disable(): void
|
||||
{
|
||||
app(Manage::class)->disable($this);
|
||||
$this->unit && app(Manage::class)->disable($this);
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,6 @@
|
||||
use App\Helpers\Toast;
|
||||
use Illuminate\Contracts\Container\BindingResolutionException;
|
||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||
use Illuminate\Support\Facades\URL;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
@ -37,9 +36,5 @@ public function boot(): void
|
||||
$this->app->bind('toast', function () {
|
||||
return new Toast;
|
||||
});
|
||||
|
||||
if (str(config('app.url'))->startsWith('https://')) {
|
||||
URL::forceScheme('https');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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->getScript('edit-file.sh', [
|
||||
'path' => $path,
|
||||
'content' => $content,
|
||||
'content' => $content ?? '',
|
||||
]),
|
||||
);
|
||||
}
|
||||
@ -131,4 +131,29 @@ public function runScript(string $path, string $script, ?int $siteId = null): Se
|
||||
|
||||
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'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
19
app/SSH/OS/scripts/cleanup.sh
Normal file
19
app/SSH/OS/scripts/cleanup.sh
Normal file
@ -0,0 +1,19 @@
|
||||
# Update package lists
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get update -y
|
||||
|
||||
# Remove unnecessary dependencies
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get autoremove --purge -y
|
||||
|
||||
# Clear package cache
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get clean -y
|
||||
|
||||
# Remove old configuration files
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get purge -y $(dpkg -l | grep '^rc' | awk '{print $2}')
|
||||
|
||||
# Clear temporary files
|
||||
sudo rm -rf /tmp/*
|
||||
|
||||
# Clear journal logs
|
||||
sudo DEBIAN_FRONTEND=noninteractive journalctl --vacuum-time=1d
|
||||
|
||||
echo "Cleanup completed."
|
3
app/SSH/OS/scripts/download.sh
Normal file
3
app/SSH/OS/scripts/download.sh
Normal file
@ -0,0 +1,3 @@
|
||||
if ! wget __url__ -O __path__; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
23
app/SSH/PHPMyAdmin/PHPMyAdmin.php
Normal file
23
app/SSH/PHPMyAdmin/PHPMyAdmin.php
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
25
app/SSH/PHPMyAdmin/scripts/install.sh
Executable file
25
app/SSH/PHPMyAdmin/scripts/install.sh
Executable 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!"
|
42
app/SSH/Services/AbstractService.php
Normal file
42
app/SSH/Services/AbstractService.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\SSH\Services;
|
||||
|
||||
use App\Models\Service;
|
||||
|
||||
abstract class AbstractService implements ServiceInterface
|
||||
{
|
||||
public function __construct(protected Service $service)
|
||||
{
|
||||
}
|
||||
|
||||
public function creationRules(array $input): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function creationData(array $input): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function deletionRules(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function data(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function install(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
public function uninstall(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
@ -2,40 +2,78 @@
|
||||
|
||||
namespace App\SSH\Services\Database;
|
||||
|
||||
use App\Enums\BackupStatus;
|
||||
use App\Models\BackupFile;
|
||||
use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
use App\SSH\HasScripts;
|
||||
use App\SSH\Services\ServiceInterface;
|
||||
use App\SSH\Services\AbstractService;
|
||||
use Closure;
|
||||
|
||||
abstract class AbstractDatabase implements Database, ServiceInterface
|
||||
abstract class AbstractDatabase extends AbstractService implements Database
|
||||
{
|
||||
use HasScripts;
|
||||
|
||||
protected Service $service;
|
||||
|
||||
protected Server $server;
|
||||
|
||||
abstract protected function getScriptsDir(): string;
|
||||
|
||||
public function __construct(Service $service)
|
||||
public function creationRules(array $input): array
|
||||
{
|
||||
$this->service = $service;
|
||||
$this->server = $service->server;
|
||||
return [
|
||||
'type' => [
|
||||
'required',
|
||||
function (string $attribute, mixed $value, Closure $fail) {
|
||||
$databaseExists = $this->service->server->database();
|
||||
if ($databaseExists) {
|
||||
$fail('You already have a database service on the server.');
|
||||
}
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function install(): void
|
||||
{
|
||||
$version = $this->service->version;
|
||||
$command = $this->getScript($this->service->name.'/install-'.$version.'.sh');
|
||||
$this->server->ssh()->exec($command, 'install-'.$this->service->name.'-'.$version);
|
||||
$status = $this->server->systemd()->status($this->service->unit);
|
||||
$this->service->server->ssh()->exec($command, 'install-'.$this->service->name.'-'.$version);
|
||||
$status = $this->service->server->systemd()->status($this->service->unit);
|
||||
$this->service->validateInstall($status);
|
||||
$this->service->server->os()->cleanup();
|
||||
}
|
||||
|
||||
public function deletionRules(): array
|
||||
{
|
||||
return [
|
||||
'service' => [
|
||||
function (string $attribute, mixed $value, Closure $fail) {
|
||||
$hasDatabase = $this->service->server->databases()->exists();
|
||||
if ($hasDatabase) {
|
||||
$fail('You have database(s) on the server.');
|
||||
}
|
||||
$hasDatabaseUser = $this->service->server->databaseUsers()->exists();
|
||||
if ($hasDatabaseUser) {
|
||||
$fail('You have database user(s) on the server.');
|
||||
}
|
||||
$hasRunningBackup = $this->service->server->backups()
|
||||
->where('status', BackupStatus::RUNNING)
|
||||
->exists();
|
||||
if ($hasRunningBackup) {
|
||||
$fail('You have database backup(s) on the server.');
|
||||
}
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function uninstall(): void
|
||||
{
|
||||
$version = $this->service->version;
|
||||
$command = $this->getScript($this->service->name.'/uninstall.sh');
|
||||
$this->service->server->ssh()->exec($command, 'uninstall-'.$this->service->name.'-'.$version);
|
||||
$this->service->server->os()->cleanup();
|
||||
}
|
||||
|
||||
public function create(string $name): void
|
||||
{
|
||||
$this->server->ssh()->exec(
|
||||
$this->service->server->ssh()->exec(
|
||||
$this->getScript($this->getScriptsDir().'/create.sh', [
|
||||
'name' => $name,
|
||||
]),
|
||||
@ -45,7 +83,7 @@ public function create(string $name): void
|
||||
|
||||
public function delete(string $name): void
|
||||
{
|
||||
$this->server->ssh()->exec(
|
||||
$this->service->server->ssh()->exec(
|
||||
$this->getScript($this->getScriptsDir().'/delete.sh', [
|
||||
'name' => $name,
|
||||
]),
|
||||
@ -55,7 +93,7 @@ public function delete(string $name): void
|
||||
|
||||
public function createUser(string $username, string $password, string $host): void
|
||||
{
|
||||
$this->server->ssh()->exec(
|
||||
$this->service->server->ssh()->exec(
|
||||
$this->getScript($this->getScriptsDir().'/create-user.sh', [
|
||||
'username' => $username,
|
||||
'password' => $password,
|
||||
@ -67,7 +105,7 @@ public function createUser(string $username, string $password, string $host): vo
|
||||
|
||||
public function deleteUser(string $username, string $host): void
|
||||
{
|
||||
$this->server->ssh()->exec(
|
||||
$this->service->server->ssh()->exec(
|
||||
$this->getScript($this->getScriptsDir().'/delete-user.sh', [
|
||||
'username' => $username,
|
||||
'host' => $host,
|
||||
@ -78,7 +116,7 @@ public function deleteUser(string $username, string $host): void
|
||||
|
||||
public function link(string $username, string $host, array $databases): void
|
||||
{
|
||||
$ssh = $this->server->ssh();
|
||||
$ssh = $this->service->server->ssh();
|
||||
|
||||
foreach ($databases as $database) {
|
||||
$ssh->exec(
|
||||
@ -94,7 +132,7 @@ public function link(string $username, string $host, array $databases): void
|
||||
|
||||
public function unlink(string $username, string $host): void
|
||||
{
|
||||
$this->server->ssh()->exec(
|
||||
$this->service->server->ssh()->exec(
|
||||
$this->getScript($this->getScriptsDir().'/unlink.sh', [
|
||||
'username' => $username,
|
||||
'host' => $host,
|
||||
@ -106,7 +144,7 @@ public function unlink(string $username, string $host): void
|
||||
public function runBackup(BackupFile $backupFile): void
|
||||
{
|
||||
// backup
|
||||
$this->server->ssh()->exec(
|
||||
$this->service->server->ssh()->exec(
|
||||
$this->getScript($this->getScriptsDir().'/backup.sh', [
|
||||
'file' => $backupFile->name,
|
||||
'database' => $backupFile->backup->database->name,
|
||||
@ -115,13 +153,13 @@ public function runBackup(BackupFile $backupFile): void
|
||||
);
|
||||
|
||||
// upload to storage
|
||||
$upload = $backupFile->backup->storage->provider()->ssh($this->server)->upload(
|
||||
$upload = $backupFile->backup->storage->provider()->ssh($this->service->server)->upload(
|
||||
$backupFile->path(),
|
||||
$backupFile->storagePath(),
|
||||
);
|
||||
|
||||
// cleanup
|
||||
$this->server->ssh()->exec('rm '.$backupFile->name.'.zip');
|
||||
$this->service->server->ssh()->exec('rm '.$backupFile->name.'.zip');
|
||||
|
||||
$backupFile->size = $upload['size'];
|
||||
$backupFile->save();
|
||||
@ -130,12 +168,12 @@ public function runBackup(BackupFile $backupFile): void
|
||||
public function restoreBackup(BackupFile $backupFile, string $database): void
|
||||
{
|
||||
// download
|
||||
$backupFile->backup->storage->provider()->ssh($this->server)->download(
|
||||
$backupFile->backup->storage->provider()->ssh($this->service->server)->download(
|
||||
$backupFile->storagePath(),
|
||||
$backupFile->name.'.zip',
|
||||
);
|
||||
|
||||
$this->server->ssh()->exec(
|
||||
$this->service->server->ssh()->exec(
|
||||
$this->getScript($this->getScriptsDir().'/restore.sh', [
|
||||
'database' => $database,
|
||||
'file' => $backupFile->name,
|
||||
|
@ -9,4 +9,6 @@ sudo DEBIAN_FRONTEND=noninteractive apt-get update
|
||||
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install mariadb-server mariadb-backup -y
|
||||
|
||||
sudo systemctl unmask mysql.service
|
||||
|
||||
sudo service mysql start
|
||||
|
@ -9,4 +9,6 @@ sudo DEBIAN_FRONTEND=noninteractive apt-get update
|
||||
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install mariadb-server mariadb-backup -y
|
||||
|
||||
sudo systemctl unmask mysql.service
|
||||
|
||||
sudo service mysql start
|
||||
|
9
app/SSH/Services/Database/scripts/mariadb/uninstall.sh
Normal file
9
app/SSH/Services/Database/scripts/mariadb/uninstall.sh
Normal file
@ -0,0 +1,9 @@
|
||||
sudo service mysql stop
|
||||
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get remove mariadb-server mariadb-backup -y
|
||||
|
||||
sudo rm -rf /etc/mysql
|
||||
sudo rm -rf /var/lib/mysql
|
||||
sudo rm -rf /var/log/mysql
|
||||
sudo rm -rf /var/run/mysqld
|
||||
sudo rm -rf /var/run/mysqld/mysqld.sock
|
@ -1,5 +1,7 @@
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install mysql-server -y
|
||||
|
||||
sudo systemctl unmask mysql.service
|
||||
|
||||
sudo service mysql enable
|
||||
|
||||
sudo service mysql start
|
||||
|
@ -6,6 +6,8 @@ sudo DEBIAN_FRONTEND=noninteractive apt-get update
|
||||
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install mysql-server -y
|
||||
|
||||
sudo systemctl unmask mysql.service
|
||||
|
||||
sudo service mysql enable
|
||||
|
||||
sudo service mysql start
|
||||
|
9
app/SSH/Services/Database/scripts/mysql/uninstall.sh
Executable file
9
app/SSH/Services/Database/scripts/mysql/uninstall.sh
Executable file
@ -0,0 +1,9 @@
|
||||
sudo service mysql stop
|
||||
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get remove mysql-server -y
|
||||
|
||||
sudo rm -rf /etc/mysql
|
||||
sudo rm -rf /var/lib/mysql
|
||||
sudo rm -rf /var/log/mysql
|
||||
sudo rm -rf /var/run/mysqld
|
||||
sudo rm -rf /var/run/mysqld/mysqld.sock
|
11
app/SSH/Services/Database/scripts/postgresql/uninstall.sh
Normal file
11
app/SSH/Services/Database/scripts/postgresql/uninstall.sh
Normal file
@ -0,0 +1,11 @@
|
||||
sudo service postgresql stop
|
||||
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get remove postgresql-* -y
|
||||
|
||||
sudo rm -rf /etc/postgresql
|
||||
sudo rm -rf /var/lib/postgresql
|
||||
sudo rm -rf /var/log/postgresql
|
||||
sudo rm -rf /var/run/postgresql
|
||||
sudo rm -rf /var/run/postgresql/postmaster.pid
|
||||
sudo rm -rf /var/run/postgresql/.s.PGSQL.5432
|
||||
sudo rm -rf /var/run/postgresql/.s.PGSQL.5432.lock
|
@ -2,15 +2,8 @@
|
||||
|
||||
namespace App\SSH\Services\Firewall;
|
||||
|
||||
use App\Models\Service;
|
||||
use App\SSH\Services\ServiceInterface;
|
||||
use App\SSH\Services\AbstractService;
|
||||
|
||||
abstract class AbstractFirewall implements Firewall, ServiceInterface
|
||||
abstract class AbstractFirewall extends AbstractService implements Firewall
|
||||
{
|
||||
protected Service $service;
|
||||
|
||||
public function __construct(Service $service)
|
||||
{
|
||||
$this->service = $service;
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,12 @@ public function install(): void
|
||||
$this->getScript('ufw/install-ufw.sh'),
|
||||
'install-ufw'
|
||||
);
|
||||
$this->service->server->os()->cleanup();
|
||||
}
|
||||
|
||||
public function uninstall(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
public function addRule(string $type, string $protocol, int $port, string $source, ?string $mask): void
|
||||
|
@ -3,20 +3,43 @@
|
||||
namespace App\SSH\Services\PHP;
|
||||
|
||||
use App\Exceptions\SSHCommandError;
|
||||
use App\Models\Service;
|
||||
use App\SSH\HasScripts;
|
||||
use App\SSH\Services\ServiceInterface;
|
||||
use App\SSH\Services\AbstractService;
|
||||
use Closure;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class PHP implements ServiceInterface
|
||||
class PHP extends AbstractService
|
||||
{
|
||||
use HasScripts;
|
||||
|
||||
protected Service $service;
|
||||
|
||||
public function __construct(Service $service)
|
||||
public function creationRules(array $input): array
|
||||
{
|
||||
$this->service = $service;
|
||||
return [
|
||||
'version' => [
|
||||
'required',
|
||||
Rule::in(config('core.php_versions')),
|
||||
Rule::unique('services', 'version')
|
||||
->where('type', 'php')
|
||||
->where('server_id', $this->service->server_id),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function deletionRules(): array
|
||||
{
|
||||
return [
|
||||
'service' => [
|
||||
function (string $attribute, mixed $value, Closure $fail) {
|
||||
$hasSite = $this->service->server->sites()
|
||||
->where('php_version', $this->service->version)
|
||||
->exists();
|
||||
if ($hasSite) {
|
||||
$fail('Some sites are using this PHP version.');
|
||||
}
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function install(): void
|
||||
@ -29,6 +52,7 @@ public function install(): void
|
||||
]),
|
||||
'install-php-'.$this->service->version
|
||||
);
|
||||
$this->service->server->os()->cleanup();
|
||||
}
|
||||
|
||||
public function uninstall(): void
|
||||
@ -39,6 +63,7 @@ public function uninstall(): void
|
||||
]),
|
||||
'uninstall-php-'.$this->service->version
|
||||
);
|
||||
$this->service->server->os()->cleanup();
|
||||
}
|
||||
|
||||
public function setDefaultCli(): void
|
||||
|
@ -2,15 +2,37 @@
|
||||
|
||||
namespace App\SSH\Services\ProcessManager;
|
||||
|
||||
use App\Models\Service;
|
||||
use App\SSH\Services\ServiceInterface;
|
||||
use App\SSH\Services\AbstractService;
|
||||
use Closure;
|
||||
|
||||
abstract class AbstractProcessManager implements ProcessManager, ServiceInterface
|
||||
abstract class AbstractProcessManager extends AbstractService implements ProcessManager
|
||||
{
|
||||
protected Service $service;
|
||||
|
||||
public function __construct(Service $service)
|
||||
public function creationRules(array $input): array
|
||||
{
|
||||
$this->service = $service;
|
||||
return [
|
||||
'type' => [
|
||||
'required',
|
||||
function (string $attribute, mixed $value, Closure $fail) {
|
||||
$processManagerExists = $this->service->server->processManager();
|
||||
if ($processManagerExists) {
|
||||
$fail('You already have a process manager service on the server.');
|
||||
}
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function deletionRules(): array
|
||||
{
|
||||
return [
|
||||
'service' => [
|
||||
function (string $attribute, mixed $value, Closure $fail) {
|
||||
$hasQueue = $this->service->server->queues()->exists();
|
||||
if ($hasQueue) {
|
||||
$fail('You have queue(s) on the server.');
|
||||
}
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,18 @@ public function install(): void
|
||||
$this->getScript('supervisor/install-supervisor.sh'),
|
||||
'install-supervisor'
|
||||
);
|
||||
$this->service->server->os()->cleanup();
|
||||
}
|
||||
|
||||
public function uninstall(): void
|
||||
{
|
||||
$this->service->server->ssh()->exec(
|
||||
$this->getScript('supervisor/uninstall-supervisor.sh'),
|
||||
'uninstall-supervisor'
|
||||
);
|
||||
$status = $this->service->server->systemd()->status($this->service->unit);
|
||||
$this->service->validateInstall($status);
|
||||
$this->service->server->os()->cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,8 @@
|
||||
sudo service supervisor stop
|
||||
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get remove supervisor -y
|
||||
|
||||
sudo rm -rf /etc/supervisor
|
||||
sudo rm -rf /var/log/supervisor
|
||||
sudo rm -rf /var/run/supervisor
|
||||
sudo rm -rf /var/run/supervisor/supervisor.sock
|
@ -2,16 +2,27 @@
|
||||
|
||||
namespace App\SSH\Services\Redis;
|
||||
|
||||
use App\Models\Service;
|
||||
use App\SSH\HasScripts;
|
||||
use App\SSH\Services\ServiceInterface;
|
||||
use App\SSH\Services\AbstractService;
|
||||
use Closure;
|
||||
|
||||
class Redis implements ServiceInterface
|
||||
class Redis extends AbstractService
|
||||
{
|
||||
use HasScripts;
|
||||
|
||||
public function __construct(protected Service $service)
|
||||
public function creationRules(array $input): array
|
||||
{
|
||||
return [
|
||||
'type' => [
|
||||
'required',
|
||||
function (string $attribute, mixed $value, Closure $fail) {
|
||||
$redisExists = $this->service->server->memoryDatabase();
|
||||
if ($redisExists) {
|
||||
$fail('You already have a Redis service on the server.');
|
||||
}
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function install(): void
|
||||
@ -20,5 +31,17 @@ public function install(): void
|
||||
$this->getScript('install.sh'),
|
||||
'install-redis'
|
||||
);
|
||||
$status = $this->service->server->systemd()->status($this->service->unit);
|
||||
$this->service->validateInstall($status);
|
||||
$this->service->server->os()->cleanup();
|
||||
}
|
||||
|
||||
public function uninstall(): void
|
||||
{
|
||||
$this->service->server->ssh()->exec(
|
||||
$this->getScript('uninstall.sh'),
|
||||
'uninstall-redis'
|
||||
);
|
||||
$this->service->server->os()->cleanup();
|
||||
}
|
||||
}
|
||||
|
15
app/SSH/Services/Redis/scripts/uninstall.sh
Executable file
15
app/SSH/Services/Redis/scripts/uninstall.sh
Executable file
@ -0,0 +1,15 @@
|
||||
sudo service redis stop
|
||||
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get remove redis-server -y
|
||||
|
||||
sudo rm -rf /etc/redis
|
||||
sudo rm -rf /var/lib/redis
|
||||
sudo rm -rf /var/log/redis
|
||||
sudo rm -rf /var/run/redis
|
||||
sudo rm -rf /var/run/redis/redis-server.pid
|
||||
sudo rm -rf /var/run/redis/redis-server.sock
|
||||
sudo rm -rf /var/run/redis/redis-server.sock
|
||||
|
||||
sudo DEBIAN_FRONTEND=noninteractive sudo apt-get autoremove -y
|
||||
|
||||
sudo DEBIAN_FRONTEND=noninteractive sudo apt-get autoclean -y
|
@ -4,5 +4,15 @@
|
||||
|
||||
interface ServiceInterface
|
||||
{
|
||||
public function creationRules(array $input): array;
|
||||
|
||||
public function creationData(array $input): array;
|
||||
|
||||
public function deletionRules(): array;
|
||||
|
||||
public function data(): array;
|
||||
|
||||
public function install(): void;
|
||||
|
||||
public function uninstall(): void;
|
||||
}
|
||||
|
87
app/SSH/Services/VitoAgent/VitoAgent.php
Normal file
87
app/SSH/Services/VitoAgent/VitoAgent.php
Normal file
@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace App\SSH\Services\VitoAgent;
|
||||
|
||||
use App\Models\Metric;
|
||||
use App\SSH\HasScripts;
|
||||
use App\SSH\Services\AbstractService;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
|
||||
class VitoAgent extends AbstractService
|
||||
{
|
||||
use HasScripts;
|
||||
|
||||
const TAGS_URL = 'https://api.github.com/repos/vitodeploy/agent/tags';
|
||||
|
||||
const DOWNLOAD_URL = 'https://github.com/vitodeploy/agent/releases/download/%s';
|
||||
|
||||
public function creationRules(array $input): array
|
||||
{
|
||||
return [
|
||||
'type' => [
|
||||
Rule::unique('services', 'type')->where('server_id', $this->service->server_id),
|
||||
],
|
||||
'version' => [
|
||||
'required',
|
||||
Rule::in(['latest']),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function creationData(array $input): array
|
||||
{
|
||||
return [
|
||||
'url' => '',
|
||||
'secret' => Uuid::uuid4()->toString(),
|
||||
'data_retention' => 10,
|
||||
];
|
||||
}
|
||||
|
||||
public function data(): array
|
||||
{
|
||||
return [
|
||||
'url' => $this->service->type_data['url'] ?? null,
|
||||
'secret' => $this->service->type_data['secret'] ?? null,
|
||||
'data_retention' => $this->service->type_data['data_retention'] ?? 10,
|
||||
];
|
||||
}
|
||||
|
||||
public function install(): void
|
||||
{
|
||||
$tags = Http::get(self::TAGS_URL)->json();
|
||||
if (empty($tags)) {
|
||||
throw new \Exception('Failed to fetch tags');
|
||||
}
|
||||
$this->service->version = $tags[0]['name'];
|
||||
$this->service->save();
|
||||
$downloadUrl = sprintf(self::DOWNLOAD_URL, $this->service->version);
|
||||
|
||||
$data = $this->data();
|
||||
$data['url'] = route('api.servers.agent', [$this->service->server, $this->service->id]);
|
||||
$this->service->type_data = $data;
|
||||
$this->service->save();
|
||||
$this->service->refresh();
|
||||
|
||||
$this->service->server->ssh()->exec(
|
||||
$this->getScript('install.sh', [
|
||||
'download_url' => $downloadUrl,
|
||||
'config_url' => $this->data()['url'],
|
||||
'config_secret' => $this->data()['secret'],
|
||||
]),
|
||||
'install-vito-agent'
|
||||
);
|
||||
$status = $this->service->server->systemd()->status($this->service->unit);
|
||||
$this->service->validateInstall($status);
|
||||
}
|
||||
|
||||
public function uninstall(): void
|
||||
{
|
||||
$this->service->server->ssh()->exec(
|
||||
$this->getScript('uninstall.sh'),
|
||||
'uninstall-vito-agent'
|
||||
);
|
||||
Metric::where('server_id', $this->service->server_id)->delete();
|
||||
}
|
||||
}
|
53
app/SSH/Services/VitoAgent/scripts/install.sh
Normal file
53
app/SSH/Services/VitoAgent/scripts/install.sh
Normal file
@ -0,0 +1,53 @@
|
||||
arch=$(uname -m)
|
||||
|
||||
if [ "$arch" == "x86_64" ]; then
|
||||
executable="vitoagent-linux-amd64"
|
||||
elif [ "$arch" == "i686" ]; then
|
||||
executable="vitoagent-linux-amd"
|
||||
elif [ "$arch" == "armv7l" ]; then
|
||||
executable="vitoagent-linux-arm"
|
||||
elif [ "$arch" == "aarch64" ]; then
|
||||
executable="vitoagent-linux-arm64"
|
||||
else
|
||||
executable="vitoagent-linux-amd64"
|
||||
fi
|
||||
|
||||
wget __download_url__/$executable
|
||||
|
||||
chmod +x ./$executable
|
||||
|
||||
sudo mv ./$executable /usr/local/bin/vito-agent
|
||||
|
||||
# create service
|
||||
export VITO_AGENT_SERVICE="
|
||||
[Unit]
|
||||
Description=Vito Agent
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
ExecStart=/usr/local/bin/vito-agent
|
||||
Restart=on-failure
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
"
|
||||
echo "${VITO_AGENT_SERVICE}" | sudo tee /etc/systemd/system/vito-agent.service
|
||||
|
||||
sudo mkdir -p /etc/vito-agent
|
||||
|
||||
export VITO_AGENT_CONFIG="
|
||||
{
|
||||
\"url\": \"__config_url__\",
|
||||
\"secret\": \"__config_secret__\"
|
||||
}
|
||||
"
|
||||
|
||||
echo "${VITO_AGENT_CONFIG}" | sudo tee /etc/vito-agent/config.json
|
||||
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable vito-agent
|
||||
sudo systemctl start vito-agent
|
||||
|
||||
echo "Vito Agent installed successfully"
|
13
app/SSH/Services/VitoAgent/scripts/uninstall.sh
Normal file
13
app/SSH/Services/VitoAgent/scripts/uninstall.sh
Normal file
@ -0,0 +1,13 @@
|
||||
sudo service vito-agent stop
|
||||
|
||||
sudo systemctl disable vito-agent
|
||||
|
||||
sudo rm -f /usr/local/bin/vito-agent
|
||||
|
||||
sudo rm -f /etc/systemd/system/vito-agent.service
|
||||
|
||||
sudo rm -rf /etc/vito-agent
|
||||
|
||||
sudo systemctl daemon-reload
|
||||
|
||||
echo "Vito Agent uninstalled successfully"
|
@ -2,12 +2,8 @@
|
||||
|
||||
namespace App\SSH\Services\Webserver;
|
||||
|
||||
use App\Models\Service;
|
||||
use App\SSH\Services\ServiceInterface;
|
||||
use App\SSH\Services\AbstractService;
|
||||
|
||||
abstract class AbstractWebserver implements ServiceInterface, Webserver
|
||||
abstract class AbstractWebserver extends AbstractService implements Webserver
|
||||
{
|
||||
public function __construct(protected Service $service)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
use App\Models\Site;
|
||||
use App\Models\Ssl;
|
||||
use App\SSH\HasScripts;
|
||||
use Closure;
|
||||
use Illuminate\Support\Str;
|
||||
use Throwable;
|
||||
|
||||
@ -23,6 +24,31 @@ public function install(): void
|
||||
]),
|
||||
'install-nginx'
|
||||
);
|
||||
$this->service->server->os()->cleanup();
|
||||
}
|
||||
|
||||
public function deletionRules(): array
|
||||
{
|
||||
return [
|
||||
'service' => [
|
||||
function (string $attribute, mixed $value, Closure $fail) {
|
||||
$hasSite = $this->service->server->sites()
|
||||
->exists();
|
||||
if ($hasSite) {
|
||||
$fail('Cannot uninstall webserver while you have websites using it.');
|
||||
}
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function uninstall(): void
|
||||
{
|
||||
$this->service->server->ssh()->exec(
|
||||
$this->getScript('nginx/uninstall-nginx.sh'),
|
||||
'uninstall-nginx'
|
||||
);
|
||||
$this->service->server->os()->cleanup();
|
||||
}
|
||||
|
||||
public function createVHost(Site $site): void
|
||||
@ -44,7 +70,7 @@ public function updateVHost(Site $site, bool $noSSL = false, ?string $vhost = nu
|
||||
$this->getScript('nginx/update-vhost.sh', [
|
||||
'domain' => $site->domain,
|
||||
'path' => $site->path,
|
||||
'vhost' => $this->generateVhost($site, $noSSL),
|
||||
'vhost' => $vhost ?? $this->generateVhost($site, $noSSL),
|
||||
]),
|
||||
'update-vhost',
|
||||
$site->id
|
||||
|
12
app/SSH/Services/Webserver/scripts/nginx/uninstall-nginx.sh
Executable file
12
app/SSH/Services/Webserver/scripts/nginx/uninstall-nginx.sh
Executable file
@ -0,0 +1,12 @@
|
||||
sudo service nginx stop
|
||||
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get purge nginx nginx-common nginx-full -y
|
||||
|
||||
sudo rm -rf /etc/nginx
|
||||
sudo rm -rf /var/log/nginx
|
||||
sudo rm -rf /var/lib/nginx
|
||||
sudo rm -rf /var/cache/nginx
|
||||
sudo rm -rf /usr/share/nginx
|
||||
sudo rm -rf /etc/systemd/system/nginx.service
|
||||
|
||||
sudo systemctl daemon-reload
|
@ -4,6 +4,7 @@
|
||||
|
||||
use App\Enums\ServiceStatus;
|
||||
use App\Models\Server;
|
||||
use App\SSH\Services\PHP\PHP;
|
||||
|
||||
abstract class AbstractType implements ServerType
|
||||
{
|
||||
@ -31,7 +32,9 @@ public function install(): void
|
||||
$service->update(['status' => ServiceStatus::READY]);
|
||||
if ($service->type == 'php') {
|
||||
$this->progress($currentProgress, 'installing-composer');
|
||||
$service->handler()->installComposer();
|
||||
/** @var PHP $handler */
|
||||
$handler = $service->handler();
|
||||
$handler->installComposer();
|
||||
}
|
||||
}
|
||||
$this->progress(100, 'finishing');
|
||||
|
@ -13,7 +13,7 @@ public function createRules(array $input): array
|
||||
],
|
||||
'php' => [
|
||||
'required',
|
||||
'in:'.implode(',', config('core.php_versions')),
|
||||
'in:none,'.implode(',', config('core.php_versions')),
|
||||
],
|
||||
'database' => [
|
||||
'required',
|
||||
|
49
app/SiteTypes/PHPMyAdmin.php
Executable file
49
app/SiteTypes/PHPMyAdmin.php
Executable 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);
|
||||
}
|
||||
}
|
@ -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) {
|
||||
$this->setLog($log, $siteId);
|
||||
@ -42,21 +42,19 @@ public function exec(string|array $commands, string $log = '', ?int $siteId = nu
|
||||
$this->log = null;
|
||||
}
|
||||
|
||||
if (! is_array($commands)) {
|
||||
$commands = [$commands];
|
||||
}
|
||||
|
||||
foreach ($commands as $command) {
|
||||
if (is_string($command)) {
|
||||
$this->commands[] = $command;
|
||||
} else {
|
||||
$this->commands[] = get_class($command);
|
||||
}
|
||||
}
|
||||
$this->commands[] = $command;
|
||||
|
||||
$output = $this->output ?? 'fake output';
|
||||
$this->log?->write($output);
|
||||
|
||||
if ($stream) {
|
||||
echo $output;
|
||||
ob_flush();
|
||||
flush();
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
|
@ -29,3 +29,17 @@ function htmx(): HtmxResponse
|
||||
{
|
||||
return new HtmxResponse();
|
||||
}
|
||||
|
||||
function vito_version(): string
|
||||
{
|
||||
return exec('git describe --tags');
|
||||
}
|
||||
|
||||
function convert_time_format($string): string
|
||||
{
|
||||
$string = preg_replace('/(\d+)m/', '$1 minutes', $string);
|
||||
$string = preg_replace('/(\d+)s/', '$1 seconds', $string);
|
||||
$string = preg_replace('/(\d+)d/', '$1 days', $string);
|
||||
|
||||
return preg_replace('/(\d+)h/', '$1 hours', $string);
|
||||
}
|
||||
|
219
config/core.php
219
config/core.php
@ -1,34 +1,5 @@
|
||||
<?php
|
||||
|
||||
use App\Enums\OperatingSystem;
|
||||
use App\Enums\StorageProvider;
|
||||
use App\NotificationChannels\Discord;
|
||||
use App\NotificationChannels\Email;
|
||||
use App\NotificationChannels\Slack;
|
||||
use App\NotificationChannels\Telegram;
|
||||
use App\ServerProviders\AWS;
|
||||
use App\ServerProviders\DigitalOcean;
|
||||
use App\ServerProviders\Hetzner;
|
||||
use App\ServerProviders\Linode;
|
||||
use App\ServerProviders\Vultr;
|
||||
use App\SiteTypes\Laravel;
|
||||
use App\SiteTypes\PHPBlank;
|
||||
use App\SiteTypes\PHPSite;
|
||||
use App\SiteTypes\Wordpress;
|
||||
use App\SourceControlProviders\Bitbucket;
|
||||
use App\SourceControlProviders\Github;
|
||||
use App\SourceControlProviders\Gitlab;
|
||||
use App\SSH\Services\Database\Mariadb;
|
||||
use App\SSH\Services\Database\Mysql;
|
||||
use App\SSH\Services\Database\Postgresql;
|
||||
use App\SSH\Services\Firewall\Ufw;
|
||||
use App\SSH\Services\PHP\PHP;
|
||||
use App\SSH\Services\ProcessManager\Supervisor;
|
||||
use App\SSH\Services\Redis\Redis;
|
||||
use App\SSH\Services\Webserver\Nginx;
|
||||
use App\StorageProviders\Dropbox;
|
||||
use App\StorageProviders\FTP;
|
||||
|
||||
return [
|
||||
/*
|
||||
* SSH
|
||||
@ -43,12 +14,12 @@
|
||||
* General
|
||||
*/
|
||||
'operating_systems' => [
|
||||
OperatingSystem::UBUNTU20,
|
||||
OperatingSystem::UBUNTU22,
|
||||
\App\Enums\OperatingSystem::UBUNTU20,
|
||||
\App\Enums\OperatingSystem::UBUNTU22,
|
||||
],
|
||||
'webservers' => ['none', 'nginx'],
|
||||
'php_versions' => [
|
||||
'none',
|
||||
// 'none',
|
||||
// '5.6',
|
||||
'7.0',
|
||||
'7.1',
|
||||
@ -125,114 +96,102 @@
|
||||
],
|
||||
'server_providers_class' => [
|
||||
\App\Enums\ServerProvider::CUSTOM => \App\ServerProviders\Custom::class,
|
||||
\App\Enums\ServerProvider::AWS => AWS::class,
|
||||
\App\Enums\ServerProvider::LINODE => Linode::class,
|
||||
\App\Enums\ServerProvider::DIGITALOCEAN => DigitalOcean::class,
|
||||
\App\Enums\ServerProvider::VULTR => Vultr::class,
|
||||
\App\Enums\ServerProvider::HETZNER => Hetzner::class,
|
||||
\App\Enums\ServerProvider::AWS => \App\ServerProviders\AWS::class,
|
||||
\App\Enums\ServerProvider::LINODE => \App\ServerProviders\Linode::class,
|
||||
\App\Enums\ServerProvider::DIGITALOCEAN => \App\ServerProviders\DigitalOcean::class,
|
||||
\App\Enums\ServerProvider::VULTR => \App\ServerProviders\Vultr::class,
|
||||
\App\Enums\ServerProvider::HETZNER => \App\ServerProviders\Hetzner::class,
|
||||
],
|
||||
'server_providers_default_user' => [
|
||||
'custom' => [
|
||||
'ubuntu_18' => 'root',
|
||||
'ubuntu_20' => 'root',
|
||||
'ubuntu_22' => 'root',
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => 'root',
|
||||
\App\Enums\OperatingSystem::UBUNTU22 => 'root',
|
||||
],
|
||||
'aws' => [
|
||||
'ubuntu_18' => 'ubuntu',
|
||||
'ubuntu_20' => 'ubuntu',
|
||||
'ubuntu_22' => 'ubuntu',
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => 'ubuntu',
|
||||
\App\Enums\OperatingSystem::UBUNTU22 => 'ubuntu',
|
||||
],
|
||||
'linode' => [
|
||||
'ubuntu_18' => 'root',
|
||||
'ubuntu_20' => 'root',
|
||||
'ubuntu_22' => 'root',
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => 'root',
|
||||
\App\Enums\OperatingSystem::UBUNTU22 => 'root',
|
||||
],
|
||||
'digitalocean' => [
|
||||
'ubuntu_18' => 'root',
|
||||
'ubuntu_20' => 'root',
|
||||
'ubuntu_22' => 'root',
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => 'root',
|
||||
\App\Enums\OperatingSystem::UBUNTU22 => 'root',
|
||||
],
|
||||
'vultr' => [
|
||||
'ubuntu_18' => 'root',
|
||||
'ubuntu_20' => 'root',
|
||||
'ubuntu_22' => 'root',
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => 'root',
|
||||
\App\Enums\OperatingSystem::UBUNTU22 => 'root',
|
||||
],
|
||||
'hetzner' => [
|
||||
'ubuntu_18' => 'root',
|
||||
'ubuntu_20' => 'root',
|
||||
'ubuntu_22' => 'root',
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => 'root',
|
||||
\App\Enums\OperatingSystem::UBUNTU22 => 'root',
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
* Service
|
||||
*/
|
||||
'service_types' => [
|
||||
'nginx' => 'webserver',
|
||||
'mysql' => 'database',
|
||||
'mariadb' => 'database',
|
||||
'postgresql' => 'database',
|
||||
'redis' => 'memory_database',
|
||||
'php' => 'php',
|
||||
'ufw' => 'firewall',
|
||||
'supervisor' => 'process_manager',
|
||||
'vito-agent' => 'monitoring',
|
||||
],
|
||||
'service_handlers' => [
|
||||
'nginx' => Nginx::class,
|
||||
'mysql' => Mysql::class,
|
||||
'mariadb' => Mariadb::class,
|
||||
'postgresql' => Postgresql::class,
|
||||
'redis' => Redis::class,
|
||||
'php' => PHP::class,
|
||||
'ufw' => Ufw::class,
|
||||
'supervisor' => Supervisor::class,
|
||||
'nginx' => \App\SSH\Services\Webserver\Nginx::class,
|
||||
'mysql' => \App\SSH\Services\Database\Mysql::class,
|
||||
'mariadb' => \App\SSH\Services\Database\Mariadb::class,
|
||||
'postgresql' => \App\SSH\Services\Database\Postgresql::class,
|
||||
'redis' => \App\SSH\Services\Redis\Redis::class,
|
||||
'php' => \App\SSH\Services\PHP\PHP::class,
|
||||
'ufw' => \App\SSH\Services\Firewall\Ufw::class,
|
||||
'supervisor' => \App\SSH\Services\ProcessManager\Supervisor::class,
|
||||
'vito-agent' => \App\SSH\Services\VitoAgent\VitoAgent::class,
|
||||
],
|
||||
'service_units' => [
|
||||
'nginx' => [
|
||||
'ubuntu_18' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => [
|
||||
'latest' => 'nginx',
|
||||
],
|
||||
'ubuntu_20' => [
|
||||
'latest' => 'nginx',
|
||||
],
|
||||
'ubuntu_22' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU22 => [
|
||||
'latest' => 'nginx',
|
||||
],
|
||||
],
|
||||
'mysql' => [
|
||||
'ubuntu_18' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => [
|
||||
'5.7' => 'mysql',
|
||||
'8.0' => 'mysql',
|
||||
],
|
||||
'ubuntu_20' => [
|
||||
'5.7' => 'mysql',
|
||||
'8.0' => 'mysql',
|
||||
],
|
||||
'ubuntu_22' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU22 => [
|
||||
'5.7' => 'mysql',
|
||||
'8.0' => 'mysql',
|
||||
],
|
||||
],
|
||||
'mariadb' => [
|
||||
'ubuntu_18' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => [
|
||||
'10.3' => 'mariadb',
|
||||
'10.4' => 'mariadb',
|
||||
],
|
||||
'ubuntu_20' => [
|
||||
'10.3' => 'mariadb',
|
||||
'10.4' => 'mariadb',
|
||||
],
|
||||
'ubuntu_22' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU22 => [
|
||||
'10.3' => 'mariadb',
|
||||
'10.4' => 'mariadb',
|
||||
],
|
||||
],
|
||||
'postgresql' => [
|
||||
'ubuntu_18' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => [
|
||||
'12' => 'postgresql',
|
||||
'13' => 'postgresql',
|
||||
'14' => 'postgresql',
|
||||
'15' => 'postgresql',
|
||||
'16' => 'postgresql',
|
||||
],
|
||||
'ubuntu_20' => [
|
||||
'12' => 'postgresql',
|
||||
'13' => 'postgresql',
|
||||
'14' => 'postgresql',
|
||||
'15' => 'postgresql',
|
||||
'16' => 'postgresql',
|
||||
],
|
||||
'ubuntu_22' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU22 => [
|
||||
'12' => 'postgresql',
|
||||
'13' => 'postgresql',
|
||||
'14' => 'postgresql',
|
||||
@ -241,19 +200,7 @@
|
||||
],
|
||||
],
|
||||
'php' => [
|
||||
'ubuntu_18' => [
|
||||
'5.6' => 'php5.6-fpm',
|
||||
'7.0' => 'php7.0-fpm',
|
||||
'7.1' => 'php7.1-fpm',
|
||||
'7.2' => 'php7.2-fpm',
|
||||
'7.3' => 'php7.3-fpm',
|
||||
'7.4' => 'php7.4-fpm',
|
||||
'8.0' => 'php8.0-fpm',
|
||||
'8.1' => 'php8.1-fpm',
|
||||
'8.2' => 'php8.2-fpm',
|
||||
'8.3' => 'php8.3-fpm',
|
||||
],
|
||||
'ubuntu_20' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => [
|
||||
'5.6' => 'php5.6-fpm',
|
||||
'7.0' => 'php7.0-fpm',
|
||||
'7.1' => 'php7.1-fpm',
|
||||
@ -264,7 +211,7 @@
|
||||
'8.1' => 'php8.1-fpm',
|
||||
'8.3' => 'php8.3-fpm',
|
||||
],
|
||||
'ubuntu_22' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU22 => [
|
||||
'5.6' => 'php5.6-fpm',
|
||||
'7.0' => 'php7.0-fpm',
|
||||
'7.1' => 'php7.1-fpm',
|
||||
@ -278,36 +225,35 @@
|
||||
],
|
||||
],
|
||||
'redis' => [
|
||||
'ubuntu_18' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => [
|
||||
'latest' => 'redis',
|
||||
],
|
||||
'ubuntu_20' => [
|
||||
'latest' => 'redis',
|
||||
],
|
||||
'ubuntu_22' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU22 => [
|
||||
'latest' => 'redis',
|
||||
],
|
||||
],
|
||||
'supervisor' => [
|
||||
'ubuntu_18' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => [
|
||||
'latest' => 'supervisor',
|
||||
],
|
||||
'ubuntu_20' => [
|
||||
'latest' => 'supervisor',
|
||||
],
|
||||
'ubuntu_22' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU22 => [
|
||||
'latest' => 'supervisor',
|
||||
],
|
||||
],
|
||||
'ufw' => [
|
||||
'ubuntu_18' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => [
|
||||
'latest' => 'ufw',
|
||||
],
|
||||
'ubuntu_20' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU22 => [
|
||||
'latest' => 'ufw',
|
||||
],
|
||||
'ubuntu_22' => [
|
||||
'latest' => 'ufw',
|
||||
],
|
||||
'vito-agent' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => [
|
||||
'latest' => 'vito-agent',
|
||||
],
|
||||
\App\Enums\OperatingSystem::UBUNTU22 => [
|
||||
'latest' => 'vito-agent',
|
||||
],
|
||||
],
|
||||
],
|
||||
@ -320,12 +266,14 @@
|
||||
\App\Enums\SiteType::PHP_BLANK,
|
||||
\App\Enums\SiteType::LARAVEL,
|
||||
\App\Enums\SiteType::WORDPRESS,
|
||||
\App\Enums\SiteType::PHPMYADMIN,
|
||||
],
|
||||
'site_types_class' => [
|
||||
\App\Enums\SiteType::PHP => PHPSite::class,
|
||||
\App\Enums\SiteType::PHP_BLANK => PHPBlank::class,
|
||||
\App\Enums\SiteType::LARAVEL => Laravel::class,
|
||||
\App\Enums\SiteType::WORDPRESS => Wordpress::class,
|
||||
\App\Enums\SiteType::PHP => \App\SiteTypes\PHPSite::class,
|
||||
\App\Enums\SiteType::PHP_BLANK => \App\SiteTypes\PHPBlank::class,
|
||||
\App\Enums\SiteType::LARAVEL => \App\SiteTypes\Laravel::class,
|
||||
\App\Enums\SiteType::WORDPRESS => \App\SiteTypes\Wordpress::class,
|
||||
\App\Enums\SiteType::PHPMYADMIN => \App\SiteTypes\PHPMyAdmin::class,
|
||||
],
|
||||
|
||||
/*
|
||||
@ -337,9 +285,9 @@
|
||||
'bitbucket',
|
||||
],
|
||||
'source_control_providers_class' => [
|
||||
'github' => Github::class,
|
||||
'gitlab' => Gitlab::class,
|
||||
'bitbucket' => Bitbucket::class,
|
||||
'github' => \App\SourceControlProviders\Github::class,
|
||||
'gitlab' => \App\SourceControlProviders\Gitlab::class,
|
||||
'bitbucket' => \App\SourceControlProviders\Bitbucket::class,
|
||||
],
|
||||
|
||||
/*
|
||||
@ -397,26 +345,33 @@
|
||||
\App\Enums\NotificationChannel::TELEGRAM,
|
||||
],
|
||||
'notification_channels_providers_class' => [
|
||||
\App\Enums\NotificationChannel::SLACK => Slack::class,
|
||||
\App\Enums\NotificationChannel::DISCORD => Discord::class,
|
||||
\App\Enums\NotificationChannel::EMAIL => Email::class,
|
||||
\App\Enums\NotificationChannel::TELEGRAM => Telegram::class,
|
||||
\App\Enums\NotificationChannel::SLACK => \App\NotificationChannels\Slack::class,
|
||||
\App\Enums\NotificationChannel::DISCORD => \App\NotificationChannels\Discord::class,
|
||||
\App\Enums\NotificationChannel::EMAIL => \App\NotificationChannels\Email::class,
|
||||
\App\Enums\NotificationChannel::TELEGRAM => \App\NotificationChannels\Telegram::class,
|
||||
],
|
||||
|
||||
/*
|
||||
* storage providers
|
||||
*/
|
||||
'storage_providers' => [
|
||||
StorageProvider::DROPBOX,
|
||||
StorageProvider::FTP,
|
||||
\App\Enums\StorageProvider::DROPBOX,
|
||||
\App\Enums\StorageProvider::FTP,
|
||||
],
|
||||
'storage_providers_class' => [
|
||||
'dropbox' => Dropbox::class,
|
||||
'ftp' => FTP::class,
|
||||
'dropbox' => \App\StorageProviders\Dropbox::class,
|
||||
'ftp' => \App\StorageProviders\Ftp::class,
|
||||
],
|
||||
|
||||
'ssl_types' => [
|
||||
\App\Enums\SslType::LETSENCRYPT,
|
||||
\App\Enums\SslType::CUSTOM,
|
||||
],
|
||||
|
||||
'metrics_data_retention' => [
|
||||
7,
|
||||
14,
|
||||
30,
|
||||
90,
|
||||
],
|
||||
];
|
||||
|
22
database/factories/MetricFactory.php
Normal file
22
database/factories/MetricFactory.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
class MetricFactory extends Factory
|
||||
{
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'server_id' => 1,
|
||||
'load' => $this->faker->randomFloat(2, 0, 100),
|
||||
'memory_total' => $this->faker->randomFloat(0, 0, 100),
|
||||
'memory_used' => $this->faker->randomFloat(0, 0, 100),
|
||||
'memory_free' => $this->faker->randomFloat(0, 0, 100),
|
||||
'disk_total' => $this->faker->randomFloat(0, 0, 100),
|
||||
'disk_used' => $this->faker->randomFloat(0, 0, 100),
|
||||
'disk_free' => $this->faker->randomFloat(0, 0, 100),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('metrics', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('server_id');
|
||||
$table->decimal('load', 5, 2);
|
||||
$table->decimal('memory_total', 15, 0);
|
||||
$table->decimal('memory_used', 15, 0);
|
||||
$table->decimal('memory_free', 15, 0);
|
||||
$table->decimal('disk_total', 15, 0);
|
||||
$table->decimal('disk_used', 15, 0);
|
||||
$table->decimal('disk_free', 15, 0);
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['server_id', 'created_at']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('metrics');
|
||||
}
|
||||
};
|
32
database/seeders/MetricsSeeder.php
Normal file
32
database/seeders/MetricsSeeder.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\Metric;
|
||||
use App\Models\Service;
|
||||
use Carbon\Carbon;
|
||||
use Carbon\CarbonPeriod;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class MetricsSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
Metric::query()->delete();
|
||||
|
||||
$monitoring = Service::query()
|
||||
->where('type', 'monitoring')
|
||||
->firstOrFail();
|
||||
|
||||
$range = CarbonPeriod::create(Carbon::now()->subDays(7), '1 minute', Carbon::now());
|
||||
foreach ($range as $date) {
|
||||
Metric::factory()->create([
|
||||
'server_id' => $monitoring->server_id,
|
||||
'created_at' => $date,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,25 +1,29 @@
|
||||
ARG RELEASE=1
|
||||
|
||||
FROM ubuntu:22.04
|
||||
|
||||
ENV RELEASE_ARG=$RELEASE
|
||||
|
||||
WORKDIR /var/www/html
|
||||
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
|
||||
# upgrade
|
||||
RUN apt clean && apt update && apt update && apt upgrade -y && apt autoremove -y
|
||||
RUN apt-get clean && apt-get update && apt-get update && apt-get upgrade -y && apt-get autoremove -y
|
||||
|
||||
# requirements
|
||||
RUN apt install -y software-properties-common curl zip unzip git gcc
|
||||
RUN apt-get install -y software-properties-common curl zip unzip git gcc
|
||||
|
||||
# nginx
|
||||
RUN apt install -y nginx
|
||||
RUN apt-get install -y nginx
|
||||
|
||||
# php
|
||||
RUN apt update \
|
||||
&& apt install -y gnupg gosu curl ca-certificates zip unzip git supervisor libcap2-bin libpng-dev \
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y gnupg gosu curl ca-certificates zip unzip git supervisor libcap2-bin libpng-dev \
|
||||
python2 dnsutils librsvg2-bin fswatch wget \
|
||||
&& add-apt-repository ppa:ondrej/php -y \
|
||||
&& apt update \
|
||||
&& apt install -y php8.2 php8.2-fpm php8.2-mbstring php8.2-mcrypt php8.2-gd php8.2-xml \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y php8.2 php8.2-fpm php8.2-mbstring php8.2-mcrypt php8.2-gd php8.2-xml \
|
||||
php8.2-curl php8.2-gettext php8.2-zip php8.2-bcmath php8.2-soap php8.2-redis php8.2-sqlite3
|
||||
COPY docker/php.ini /etc/php/8.2/cli/conf.d/99-vito.ini
|
||||
|
||||
@ -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
|
||||
|
||||
# app
|
||||
COPY . /var/www/html
|
||||
RUN rm -rf /var/www/html/vendor
|
||||
RUN rm -rf /var/www/html/.env
|
||||
RUN rm -rf /var/www/html
|
||||
RUN git clone -b 1.x https://github.com/vitodeploy/vito.git /var/www/html
|
||||
RUN [ "$RELEASE_ARG" = "1" ] && git checkout $(git tag -l --merged 1.x --sort=-v:refname | head -n 1) || true
|
||||
RUN composer install --no-dev --prefer-dist
|
||||
RUN chown -R www-data:www-data /var/www/html \
|
||||
&& chmod -R 755 /var/www/html/storage /var/www/html/bootstrap/cache
|
||||
|
239
package-lock.json
generated
239
package-lock.json
generated
@ -8,8 +8,10 @@
|
||||
"@tailwindcss/forms": "^0.5.2",
|
||||
"@tailwindcss/typography": "^0.5.9",
|
||||
"alpinejs": "^3.4.2",
|
||||
"apexcharts": "^3.44.2",
|
||||
"autoprefixer": "^10.4.2",
|
||||
"flowbite": "^2.3.0",
|
||||
"flowbite-datepicker": "^1.2.6",
|
||||
"htmx.org": "^1.9.10",
|
||||
"laravel-echo": "^1.15.0",
|
||||
"laravel-vite-plugin": "^0.7.2",
|
||||
@ -20,7 +22,7 @@
|
||||
"tailwindcss": "^3.1.0",
|
||||
"tippy.js": "^6.3.7",
|
||||
"toastr": "^2.1.4",
|
||||
"vite": "^4.5.2"
|
||||
"vite": "^4.5.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
@ -529,6 +531,12 @@
|
||||
"integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@yr/monotone-cubic-spline": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz",
|
||||
"integrity": "sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/alpinejs": {
|
||||
"version": "3.12.0",
|
||||
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.12.0.tgz",
|
||||
@ -557,6 +565,21 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/apexcharts": {
|
||||
"version": "3.48.0",
|
||||
"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.48.0.tgz",
|
||||
"integrity": "sha512-Lhpj1Ij6lKlrUke8gf+P+SE6uGUn+Pe1TnCJ+zqrY0YMvbqM3LMb1lY+eybbTczUyk0RmMZomlTa2NgX2EUs4Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@yr/monotone-cubic-spline": "^1.0.3",
|
||||
"svg.draggable.js": "^2.2.2",
|
||||
"svg.easing.js": "^2.0.0",
|
||||
"svg.filter.js": "^2.0.2",
|
||||
"svg.pathmorphing.js": "^0.1.3",
|
||||
"svg.resize.js": "^1.4.3",
|
||||
"svg.select.js": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/arg": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
|
||||
@ -885,6 +908,15 @@
|
||||
"mini-svg-data-uri": "^1.4.3"
|
||||
}
|
||||
},
|
||||
"node_modules/flowbite-datepicker": {
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/flowbite-datepicker/-/flowbite-datepicker-1.2.6.tgz",
|
||||
"integrity": "sha512-UbU/xXs9HFiwWfL4M1vpwIo8EpS0NUQSOvYnp0Z9u3N118nU7lPFGoUOq7su9d0aOJy9FssXzx1SZwN8MXhE1g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"flowbite": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fraction.js": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz",
|
||||
@ -1680,6 +1712,97 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/svg.draggable.js": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz",
|
||||
"integrity": "sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"svg.js": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svg.easing.js": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/svg.easing.js/-/svg.easing.js-2.0.0.tgz",
|
||||
"integrity": "sha512-//ctPdJMGy22YoYGV+3HEfHbm6/69LJUTAqI2/5qBvaNHZ9uUFVC82B0Pl299HzgH13rKrBgi4+XyXXyVWWthA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"svg.js": ">=2.3.x"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svg.filter.js": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/svg.filter.js/-/svg.filter.js-2.0.2.tgz",
|
||||
"integrity": "sha512-xkGBwU+dKBzqg5PtilaTb0EYPqPfJ9Q6saVldX+5vCRy31P6TlRCP3U9NxH3HEufkKkpNgdTLBJnmhDHeTqAkw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"svg.js": "^2.2.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svg.js": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz",
|
||||
"integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/svg.pathmorphing.js": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz",
|
||||
"integrity": "sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"svg.js": "^2.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svg.resize.js": {
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/svg.resize.js/-/svg.resize.js-1.4.3.tgz",
|
||||
"integrity": "sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"svg.js": "^2.6.5",
|
||||
"svg.select.js": "^2.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svg.resize.js/node_modules/svg.select.js": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz",
|
||||
"integrity": "sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"svg.js": "^2.2.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svg.select.js": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-3.0.1.tgz",
|
||||
"integrity": "sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"svg.js": "^2.6.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.1.tgz",
|
||||
@ -1812,9 +1935,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "4.5.2",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz",
|
||||
"integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==",
|
||||
"version": "4.5.3",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz",
|
||||
"integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.18.10",
|
||||
@ -2177,6 +2300,12 @@
|
||||
"integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==",
|
||||
"dev": true
|
||||
},
|
||||
"@yr/monotone-cubic-spline": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz",
|
||||
"integrity": "sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==",
|
||||
"dev": true
|
||||
},
|
||||
"alpinejs": {
|
||||
"version": "3.12.0",
|
||||
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.12.0.tgz",
|
||||
@ -2202,6 +2331,21 @@
|
||||
"picomatch": "^2.0.4"
|
||||
}
|
||||
},
|
||||
"apexcharts": {
|
||||
"version": "3.48.0",
|
||||
"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.48.0.tgz",
|
||||
"integrity": "sha512-Lhpj1Ij6lKlrUke8gf+P+SE6uGUn+Pe1TnCJ+zqrY0YMvbqM3LMb1lY+eybbTczUyk0RmMZomlTa2NgX2EUs4Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@yr/monotone-cubic-spline": "^1.0.3",
|
||||
"svg.draggable.js": "^2.2.2",
|
||||
"svg.easing.js": "^2.0.0",
|
||||
"svg.filter.js": "^2.0.2",
|
||||
"svg.pathmorphing.js": "^0.1.3",
|
||||
"svg.resize.js": "^1.4.3",
|
||||
"svg.select.js": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"arg": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
|
||||
@ -2434,6 +2578,15 @@
|
||||
"mini-svg-data-uri": "^1.4.3"
|
||||
}
|
||||
},
|
||||
"flowbite-datepicker": {
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/flowbite-datepicker/-/flowbite-datepicker-1.2.6.tgz",
|
||||
"integrity": "sha512-UbU/xXs9HFiwWfL4M1vpwIo8EpS0NUQSOvYnp0Z9u3N118nU7lPFGoUOq7su9d0aOJy9FssXzx1SZwN8MXhE1g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"flowbite": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"fraction.js": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz",
|
||||
@ -2911,6 +3064,78 @@
|
||||
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||
"dev": true
|
||||
},
|
||||
"svg.draggable.js": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz",
|
||||
"integrity": "sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"svg.js": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"svg.easing.js": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/svg.easing.js/-/svg.easing.js-2.0.0.tgz",
|
||||
"integrity": "sha512-//ctPdJMGy22YoYGV+3HEfHbm6/69LJUTAqI2/5qBvaNHZ9uUFVC82B0Pl299HzgH13rKrBgi4+XyXXyVWWthA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"svg.js": ">=2.3.x"
|
||||
}
|
||||
},
|
||||
"svg.filter.js": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/svg.filter.js/-/svg.filter.js-2.0.2.tgz",
|
||||
"integrity": "sha512-xkGBwU+dKBzqg5PtilaTb0EYPqPfJ9Q6saVldX+5vCRy31P6TlRCP3U9NxH3HEufkKkpNgdTLBJnmhDHeTqAkw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"svg.js": "^2.2.5"
|
||||
}
|
||||
},
|
||||
"svg.js": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz",
|
||||
"integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==",
|
||||
"dev": true
|
||||
},
|
||||
"svg.pathmorphing.js": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz",
|
||||
"integrity": "sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"svg.js": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"svg.resize.js": {
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/svg.resize.js/-/svg.resize.js-1.4.3.tgz",
|
||||
"integrity": "sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"svg.js": "^2.6.5",
|
||||
"svg.select.js": "^2.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"svg.select.js": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz",
|
||||
"integrity": "sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"svg.js": "^2.2.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"svg.select.js": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-3.0.1.tgz",
|
||||
"integrity": "sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"svg.js": "^2.6.5"
|
||||
}
|
||||
},
|
||||
"tailwindcss": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.1.tgz",
|
||||
@ -3011,9 +3236,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"vite": {
|
||||
"version": "4.5.2",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz",
|
||||
"integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==",
|
||||
"version": "4.5.3",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz",
|
||||
"integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"esbuild": "^0.18.10",
|
||||
|
@ -22,6 +22,8 @@
|
||||
"tailwindcss": "^3.1.0",
|
||||
"tippy.js": "^6.3.7",
|
||||
"toastr": "^2.1.4",
|
||||
"vite": "^4.5.2"
|
||||
"vite": "^4.5.3",
|
||||
"apexcharts": "^3.44.2",
|
||||
"flowbite-datepicker": "^1.2.6"
|
||||
}
|
||||
}
|
||||
|
1
public/build/assets/app-53e4d707.css
Normal file
1
public/build/assets/app-53e4d707.css
Normal file
File diff suppressed because one or more lines are too long
747
public/build/assets/app-66009dff.js
Normal file
747
public/build/assets/app-66009dff.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,6 +1,6 @@
|
||||
{
|
||||
"resources/css/app.css": {
|
||||
"file": "assets/app-986a0fb7.css",
|
||||
"file": "assets/app-53e4d707.css",
|
||||
"isEntry": true,
|
||||
"src": "resources/css/app.css"
|
||||
},
|
||||
@ -12,7 +12,7 @@
|
||||
"css": [
|
||||
"assets/app-a1ae07b3.css"
|
||||
],
|
||||
"file": "assets/app-e823d2ab.js",
|
||||
"file": "assets/app-66009dff.js",
|
||||
"isEntry": true,
|
||||
"src": "resources/js/app.js"
|
||||
}
|
||||
|
@ -1 +1,9 @@
|
||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="list" class="svg-inline--fa fa-list fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M80 368H16a16 16 0 0 0-16 16v64a16 16 0 0 0 16 16h64a16 16 0 0 0 16-16v-64a16 16 0 0 0-16-16zm0-320H16A16 16 0 0 0 0 64v64a16 16 0 0 0 16 16h64a16 16 0 0 0 16-16V64a16 16 0 0 0-16-16zm0 160H16a16 16 0 0 0-16 16v64a16 16 0 0 0 16 16h64a16 16 0 0 0 16-16v-64a16 16 0 0 0-16-16zm416 176H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16z"></path></svg>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="48" height="48" fill="white" fill-opacity="0.01" />
|
||||
<rect x="4" y="8" width="40" height="32" rx="2" fill="#2F88FF" stroke="#000000" stroke-width="4"
|
||||
stroke-linejoin="round" />
|
||||
<path d="M12 18L19 24L12 30" stroke="white" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path d="M23 32H36" stroke="white" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
Before Width: | Height: | Size: 786 B After Width: | Height: | Size: 654 B |
@ -1 +1,9 @@
|
||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="fire" class="svg-inline--fa fa-fire fa-w-12" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M216 23.86c0-23.8-30.65-32.77-44.15-13.04C48 191.85 224 200 224 288c0 35.63-29.11 64.46-64.85 63.99-35.17-.45-63.15-29.77-63.15-64.94v-85.51c0-21.7-26.47-32.23-41.43-16.5C27.8 213.16 0 261.33 0 320c0 105.87 86.13 192 192 192s192-86.13 192-192c0-170.29-168-193-168-296.14z"></path></svg>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 14 14" role="img" focusable="false" aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="olive"
|
||||
d="M12.123424 3.09708C9.1750899 1.36849 7.0055019 1 7.0055019 1s-.0023.00035-.0055.00096c-.0033-.00061-.0055-.00096-.0055-.00096s-2.169706.36849-5.117921 2.09708c0 0-.390942 8.72672 5.117921 9.90292.0019-.00035.0036-.00096.0055-.001.0019.00035.0036.00096.0055.001 5.5087451-1.1762 5.1179221-9.90292 5.1179221-9.90292z" />
|
||||
<path fill="#ff0"
|
||||
d="M7.0058749 1.74098c.482647.10437 2.177431.53826 4.3878041 1.77816-.0096 1.61164-.285353 7.71006-4.3878041 8.73494V1.74098z" />
|
||||
</svg>
|
Before Width: | Height: | Size: 499 B After Width: | Height: | Size: 781 B |
22
public/static/images/vito-agent.svg
Normal file
22
public/static/images/vito-agent.svg
Normal file
@ -0,0 +1,22 @@
|
||||
<svg width="800" height="800" viewBox="0 0 800 800" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="800" height="800" rx="50" fill="#5046E5" />
|
||||
<g filter="url(#filter0_d_1_35)">
|
||||
<path
|
||||
d="M400 601.2C392.4 601.2 386.2 599.4 381.4 595.8C376.6 592.2 372.8 586.8 370 579.6L205 202.2C202.2 195.4 201.4 189.8 202.6 185.4C203.8 180.6 206.4 176.8 210.4 174C214.8 171.2 219.8 169.8 225.4 169.8C233 169.8 238.6 171.6 242.2 175.2C246.2 178.4 249.4 183.2 251.8 189.6L410.2 557.4H391L548.8 189C551.6 183 555 178.4 559 175.2C563 171.6 568.6 169.8 575.8 169.8C581.4 169.8 586 171.2 589.6 174C593.6 176.8 596 180.6 596.8 185.4C598 190.2 597.2 195.8 594.4 202.2L429.4 579.6C426.6 586.8 422.8 592.2 418 595.8C413.6 599.4 407.6 601.2 400 601.2Z"
|
||||
fill="white" />
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d_1_35" x="196.8" y="169.8" width="405.8" height="439.4" filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha" />
|
||||
<feOffset dy="4" />
|
||||
<feGaussianBlur stdDeviation="2" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0" />
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1_35" />
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1_35" result="shape" />
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
File diff suppressed because it is too large
Load Diff
@ -1 +0,0 @@
|
||||
ace.define("ace/mode/sh", ["require", "exports", "module", "ace/lib/oop", "ace/mode/text", "ace/tokenizer", "ace/mode/sh_highlight_rules", "ace/range"], function (e, t, n) { var r = e("../lib/oop"), i = e("./text").Mode, s = e("../tokenizer").Tokenizer, o = e("./sh_highlight_rules").ShHighlightRules, u = e("../range").Range, a = function () { this.$tokenizer = new s((new o).getRules()) }; r.inherits(a, i), function () { this.toggleCommentLines = function (e, t, n, r) { var i = !0, s = /^(\s*)#/; for (var o = n; o <= r; o++)if (!s.test(t.getLine(o))) { i = !1; break } if (i) { var a = new u(0, 0, 0, 0); for (var o = n; o <= r; o++) { var f = t.getLine(o), l = f.match(s); a.start.row = o, a.end.row = o, a.end.column = l[0].length, t.replace(a, l[1]) } } else t.indentRows(n, r, "#") }, this.getNextLineIndent = function (e, t, n) { var r = this.$getIndent(t), i = this.$tokenizer.getLineTokens(t, e), s = i.tokens; if (s.length && s[s.length - 1].type == "comment") return r; if (e == "start") { var o = t.match(/^.*[\{\(\[\:]\s*$/); o && (r += n) } return r }; var e = { pass: 1, "return": 1, raise: 1, "break": 1, "continue": 1 }; this.checkOutdent = function (t, n, r) { if (r !== "\r\n" && r !== "\r" && r !== "\n") return !1; var i = this.$tokenizer.getLineTokens(n.trim(), t).tokens; if (!i) return !1; do var s = i.pop(); while (s && (s.type == "comment" || s.type == "text" && s.value.match(/^\s+$/))); return s ? s.type == "keyword" && e[s.value] : !1 }, this.autoOutdent = function (e, t, n) { n += 1; var r = this.$getIndent(t.getLine(n)), i = t.getTabString(); r.slice(-i.length) == i && t.remove(new u(n, r.length - i.length, n, r.length)) } }.call(a.prototype), t.Mode = a }), ace.define("ace/mode/sh_highlight_rules", ["require", "exports", "module", "ace/lib/oop", "ace/mode/text_highlight_rules"], function (e, t, n) { var r = e("../lib/oop"), i = e("./text_highlight_rules").TextHighlightRules, s = t.reservedKeywords = "!|{|}|case|do|done|elif|else|esac|fi|for|if|in|then|until|while|&|;|export|local|read|typeset|unset|elif|select|set", o = t.languageConstructs = "[|]|alias|bg|bind|break|builtin|cd|command|compgen|complete|continue|dirs|disown|echo|enable|eval|exec|exit|fc|fg|getopts|hash|help|history|jobs|kill|let|logout|popd|printf|pushd|pwd|return|set|shift|shopt|source|suspend|test|times|trap|type|ulimit|umask|unalias|wait", u = function () { var e = this.createKeywordMapper({ keyword: s, "support.function.builtin": o, "invalid.deprecated": "debugger" }, "identifier"), t = "(?:(?:[1-9]\\d*)|(?:0))", n = "(?:\\.\\d+)", r = "(?:\\d+)", i = "(?:(?:" + r + "?" + n + ")|(?:" + r + "\\.))", u = "(?:(?:" + i + "|" + r + ")" + ")", a = "(?:" + u + "|" + i + ")", f = "(?:&" + r + ")", l = "[a-zA-Z][a-zA-Z0-9_]*", c = "(?:(?:\\$" + l + ")|(?:" + l + "=))", h = "(?:\\$(?:SHLVL|\\$|\\!|\\?))", p = "(?:" + l + "\\s*\\(\\))"; this.$rules = { start: [{ token: "comment", regex: "#.*$" }, { token: "string", regex: '"(?:[^\\\\]|\\\\.)*?"' }, { token: "variable.language", regex: h }, { token: "variable", regex: c }, { token: "support.function", regex: p }, { token: "support.function", regex: f }, { token: "string", regex: "'(?:[^\\\\]|\\\\.)*?'" }, { token: "constant.numeric", regex: a }, { token: "constant.numeric", regex: t + "\\b" }, { token: e, regex: "[a-zA-Z_$][a-zA-Z0-9_$]*\\b" }, { token: "keyword.operator", regex: "\\+|\\-|\\*|\\*\\*|\\/|\\/\\/|~|<|>|<=|=>|=|!=" }, { token: "paren.lparen", regex: "[\\[\\(\\{]" }, { token: "paren.rparen", regex: "[\\]\\)\\}]" }, { token: "text", regex: "\\s+" }] } }; r.inherits(u, i), t.ShHighlightRules = u })
|
@ -1,5 +0,0 @@
|
||||
ace.define("ace/theme/github", ["require", "exports", "module", "ace/lib/dom"], function (e, t, n) {
|
||||
t.isDark = !1, t.cssClass = "ace-github", t.cssText = '/* CSS style content from github\'s default pygments highlighter template.Cursor and selection styles from textmate.css. */.ace-github .ace_gutter {background: #e8e8e8;color: #AAA;}.ace-github .ace_scroller {background: #fff;}.ace-github .ace_keyword {font-weight: bold;}.ace-github .ace_string {color: #D14;}.ace-github .ace_variable.ace_class {color: teal;}.ace-github .ace_constant.ace_numeric {color: #099;}.ace-github .ace_constant.ace_buildin {color: #0086B3;}.ace-github .ace_support.ace_function {color: #0086B3;}.ace-github .ace_comment {color: #998;font-style: italic;}.ace-github .ace_variable.ace_language {color: #0086B3;}.ace-github .ace_paren {font-weight: bold;}.ace-github .ace_boolean {font-weight: bold;}.ace-github .ace_string.ace_regexp {color: #009926;font-weight: normal;}.ace-github .ace_variable.ace_instance {color: teal;}.ace-github .ace_constant.ace_language {font-weight: bold;}.ace-github .ace_text-layer {}.ace-github .ace_cursor {border-left: 2px solid black;}.ace-github .ace_overwrite-cursors .ace_cursor {border-left: 0px;border-bottom: 1px solid black;}.ace-github .ace_marker-layer .ace_active-line {background: rgb(255, 255, 204);}.ace-github .ace_marker-layer .ace_selection {background: rgb(181, 213, 255);}.ace-github.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px white;border-radius: 2px;}/* bold keywords cause cursor issues for some fonts *//* this disables bold style for editor and keeps for static highlighter */.ace-github.ace_nobold .ace_line > span {font-weight: normal !important;}.ace-github .ace_marker-layer .ace_step {background: rgb(252, 255, 0);}.ace-github .ace_marker-layer .ace_stack {background: rgb(164, 229, 101);}.ace-github .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid rgb(192, 192, 192);}.ace-github .ace_gutter-active-line {background-color : rgba(0, 0, 0, 0.07);}.ace-github .ace_marker-layer .ace_selected-word {background: rgb(250, 250, 255);border: 1px solid rgb(200, 200, 250);}.ace-github .ace_print-margin {width: 1px;background: #e8e8e8;}.ace-github .ace_indent-guide {background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==") right repeat-y;}';
|
||||
var r = e("../lib/dom");
|
||||
r.importCssString(t.cssText, t.cssClass)
|
||||
})
|
@ -1,5 +0,0 @@
|
||||
ace.define("ace/theme/one-dark", ["require", "exports", "module", "ace/lib/dom"], function (e, t, n) {
|
||||
t.isDark = !1, t.cssClass = "ace-one-dark", t.cssText = '/* CSS style content from one-dark\'s default pygments highlighter template.Cursor and selection styles from textmate.css. */.ace-one-dark .ace_gutter{background:#282c34;color:#6a6f7a}.ace-one-dark .ace_print-margin{width:1px;background:#e8e8e8}.ace-one-dark{background-color:#282c34;color:#abb2bf}.ace-one-dark .ace_cursor{color:#528bff}.ace-one-dark .ace_marker-layer .ace_selection{background:#3d4350}.ace-one-dark.ace_multiselect .ace_selection.ace_start{box-shadow:0 0 3px 0 #282c34;border-radius:2px}.ace-one-dark .ace_marker-layer .ace_step{background:#c6dbae}.ace-one-dark .ace_marker-layer .ace_bracket{margin:-1px 0 0 -1px;border:1px solid #747369}.ace-one-dark .ace_marker-layer .ace_active-line{background:rgba(76,87,103,.19)}.ace-one-dark .ace_gutter-active-line{background-color:rgba(76,87,103,.19)}.ace-one-dark .ace_marker-layer .ace_selected-word{border:1px solid #3d4350}.ace-one-dark .ace_fold{background-color:#61afef;border-color:#abb2bf}.ace-one-dark .ace_keyword{color:#c678dd}.ace-one-dark .ace_keyword.ace_operator{color:#c678dd}.ace-one-dark .ace_keyword.ace_other.ace_unit{color:#d19a66}.ace-one-dark .ace_constant.ace_language{color:#d19a66}.ace-one-dark .ace_constant.ace_numeric{color:#d19a66}.ace-one-dark .ace_constant.ace_character{color:#56b6c2}.ace-one-dark .ace_constant.ace_other{color:#56b6c2}.ace-one-dark .ace_support.ace_function{color:#61afef}.ace-one-dark .ace_support.ace_constant{color:#d19a66}.ace-one-dark .ace_support.ace_class{color:#e5c07b}.ace-one-dark .ace_support.ace_type{color:#e5c07b}.ace-one-dark .ace_storage{color:#c678dd}.ace-one-dark .ace_storage.ace_type{color:#c678dd}.ace-one-dark .ace_invalid{color:#fff;background-color:#f2777a}.ace-one-dark .ace_invalid.ace_deprecated{color:#272b33;background-color:#d27b53}.ace-one-dark .ace_string{color:#98c379}.ace-one-dark .ace_string.ace_regexp{color:#e06c75}.ace-one-dark .ace_comment{font-style:italic;color:#5c6370}.ace-one-dark .ace_variable{color:#e06c75}.ace-one-dark .ace_variable.ace_parameter{color:#d19a66}.ace-one-dark .ace_meta.ace_tag{color:#e06c75}.ace-one-dark .ace_entity.ace_other.ace_attribute-name{color:#e06c75}.ace-one-dark .ace_entity.ace_name.ace_function{color:#61afef}.ace-one-dark .ace_entity.ace_name.ace_tag{color:#e06c75}.ace-one-dark .ace_markup.ace_heading{color:#98c379}.ace-one-dark .ace_indent-guide{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWPQ09NrYAgMjP4PAAtGAwchHMyAAAAAAElFTkSuQmCC) right repeat-y}';
|
||||
var r = e("../lib/dom");
|
||||
r.importCssString(t.cssText, t.cssClass)
|
||||
})
|
@ -1,9 +1,13 @@
|
||||
import 'flowbite';
|
||||
import 'flowbite/dist/datepicker.js';
|
||||
|
||||
import Alpine from 'alpinejs';
|
||||
window.Alpine = Alpine;
|
||||
Alpine.start();
|
||||
|
||||
import ApexCharts from 'apexcharts';
|
||||
window.ApexCharts = ApexCharts;
|
||||
|
||||
import htmx from "htmx.org";
|
||||
window.htmx = htmx;
|
||||
window.htmx.defineExtension('disable-element', {
|
||||
@ -23,8 +27,8 @@ window.htmx.defineExtension('disable-element', {
|
||||
});
|
||||
document.body.addEventListener('htmx:configRequest', (event) => {
|
||||
event.detail.headers['X-CSRF-TOKEN'] = document.head.querySelector('meta[name="csrf-token"]').content;
|
||||
if (window.getSelection) { window.getSelection().removeAllRanges(); }
|
||||
else if (document.selection) { document.selection.empty(); }
|
||||
// if (window.getSelection) { window.getSelection().removeAllRanges(); }
|
||||
// else if (document.selection) { document.selection.empty(); }
|
||||
});
|
||||
document.body.addEventListener('htmx:beforeRequest', (event) => {
|
||||
let targetElements = event.target.querySelectorAll('[hx-disable]');
|
||||
@ -38,6 +42,13 @@ document.body.addEventListener('htmx:afterRequest', (event) => {
|
||||
targetElements[i].disabled = false;
|
||||
}
|
||||
});
|
||||
document.body.addEventListener('htmx:afterSwap', (event) => {
|
||||
tippy('[data-tooltip]', {
|
||||
content(reference) {
|
||||
return reference.getAttribute('data-tooltip');
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
import toastr from 'toastr';
|
||||
window.toastr = toastr;
|
||||
@ -49,13 +60,6 @@ window.toastr.options = {
|
||||
|
||||
import tippy from 'tippy.js';
|
||||
import 'tippy.js/dist/tippy.css';
|
||||
document.body.addEventListener('htmx:afterSettle', (event) => {
|
||||
tippy('[data-tooltip]', {
|
||||
content(reference) {
|
||||
return reference.getAttribute('data-tooltip');
|
||||
},
|
||||
});
|
||||
});
|
||||
tippy('[data-tooltip]', {
|
||||
content(reference) {
|
||||
return reference.getAttribute('data-tooltip');
|
||||
|
@ -4,6 +4,7 @@
|
||||
<x-slot name="trigger">
|
||||
<x-secondary-button>
|
||||
{{ __("Auto Deployment") }}
|
||||
<x-heroicon name="o-chevron-down" class="ml-1 h-4 w-4" />
|
||||
</x-secondary-button>
|
||||
</x-slot>
|
||||
<x-slot name="content">
|
||||
|
@ -12,11 +12,13 @@ class="p-6"
|
||||
{{ __("Deployment Script") }}
|
||||
</h2>
|
||||
|
||||
<div class="mt-6">A bash script that will be executed when you run the deployment process.</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<x-input-label for="script" :value="__('Script')" />
|
||||
<x-code-editor id="script" name="script" lang="sh" class="mt-1 w-full">
|
||||
<x-textarea id="script" name="script" class="mt-1 min-h-[400px] w-full">
|
||||
{{ old("script", $site->deploymentScript?->content) }}
|
||||
</x-code-editor>
|
||||
</x-textarea>
|
||||
@error("script")
|
||||
<x-input-error class="mt-2" :messages="$message" />
|
||||
@enderror
|
||||
|
@ -21,9 +21,9 @@ class="mt-6"
|
||||
>
|
||||
<x-input-label for="env" :value="__('.env')" />
|
||||
<div id="env-content">
|
||||
<x-code-editor id="env" name="env" rows="10" class="mt-1 block w-full">
|
||||
<x-textarea id="env" name="env" rows="10" class="mt-1 block min-h-[400px] w-full">
|
||||
{{ old("env", session()->get("env") ?? "Loading...") }}
|
||||
</x-code-editor>
|
||||
</x-textarea>
|
||||
</div>
|
||||
@error("env")
|
||||
<x-input-error class="mt-2" :messages="$message" />
|
||||
|
@ -19,6 +19,7 @@
|
||||
<x-slot name="trigger">
|
||||
<x-secondary-button>
|
||||
{{ __("Manage") }}
|
||||
<x-heroicon name="o-chevron-down" class="ml-1 h-4 w-4" />
|
||||
</x-secondary-button>
|
||||
</x-slot>
|
||||
<x-slot name="content">
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user