mirror of
https://github.com/vitodeploy/vito.git
synced 2025-04-19 09:51:37 +00:00
Compare commits
88 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
fb651ab5ce | ||
|
c2625a7352 | ||
|
b72a2ddb1c | ||
|
0e8e6ef56f | ||
|
39fa25aee7 | ||
|
945c2e75c0 | ||
|
82933e29ff | ||
|
82c1f36ef6 | ||
|
e06d23b31a | ||
|
f0e7faa0e7 | ||
|
319fdb44e7 | ||
|
b62c40c97d | ||
|
e39e8c17a2 | ||
|
1391eb32d8 | ||
|
7f5e68e131 | ||
|
431da1b728 | ||
|
8c487a64fa | ||
|
a67e586a5d | ||
|
960db714b7 | ||
|
7da0221ccb | ||
|
9ac5f9ebb3 | ||
|
ed8965b92b | ||
|
9473d198e1 | ||
|
55269dbcde | ||
|
3d67153912 | ||
|
11e3b167cc | ||
|
ad027eb033 | ||
|
e031bafba5 | ||
|
b5c8d99ef8 | ||
|
109d644ad8 | ||
|
5ccbab74b1 | ||
|
7d367465ff | ||
|
eec83f577c | ||
|
fd77368cf3 | ||
|
a862a603f2 | ||
|
3b42f93654 | ||
|
661292df5e | ||
|
0cfb938320 | ||
|
dd4a3d30c0 | ||
|
2b849c888e | ||
|
d9a791755e | ||
|
e3ea8f975f | ||
|
de468ae1ba | ||
|
30ef8ad5eb | ||
|
88223a61f9 | ||
|
1067a5fd33 | ||
|
4361305206 | ||
|
fe331fd2b3 | ||
|
bbe3ca802d | ||
|
765ac21916 | ||
|
016886f307 | ||
|
179aefefac | ||
|
e704a13d6b | ||
|
9936958259 | ||
|
f81d928c66 | ||
|
3c4435701d | ||
|
ebbd81348a | ||
|
5debbd4f5d | ||
|
d846acaa8d | ||
|
35f896eab1 | ||
|
25977d2ead | ||
|
f0da1c6d8c | ||
|
e2dd9177f7 | ||
|
5a9e8d6799 | ||
|
868b70f530 | ||
|
d07e9bcad2 | ||
|
0cd815cce6 | ||
|
5ab6617b5d | ||
|
72b37c56fd | ||
|
8a4ef66946 | ||
|
4517ca7d2a | ||
|
75aed62d75 | ||
|
aaef73d89d | ||
|
f03a029e36 | ||
|
52d195710b | ||
|
ddacc32e64 | ||
|
2ae9a14d02 | ||
|
3019c3d213 | ||
|
c43869d255 | ||
|
18748f77ac | ||
|
052e28d2e3 | ||
|
87ec0af697 | ||
|
e9016737d4 | ||
|
f34d5eb82b | ||
|
12c500e125 | ||
|
2d566b853f | ||
|
ca93b521ec | ||
|
a0af4e3e9d |
11
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
11
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Feature request
|
||||
url: https://github.com/vitodeploy/vito/discussions/new?category=ideas
|
||||
about: Share ideas for new features
|
||||
- name: Support
|
||||
url: https://github.com/vitodeploy/vito/discussions/new?category=q-a
|
||||
about: Ask the community for help
|
||||
- name: Discord
|
||||
url: https://discord.gg/uZeeHZZnm5
|
||||
about: Join the community
|
12
.github/ISSUE_TEMPLATE/feature_request.md
vendored
12
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -1,12 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: feature
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
To request a feature or suggest an idea please add it to the feedback boards
|
||||
|
||||
https://features.vitodeploy.com/
|
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
|
@ -1,6 +1,6 @@
|
||||
|
||||
<p align="center">
|
||||
<img alt="srcshot 2024-02-23 at 16 26 21@2x" src="https://github.com/vitodeploy/vito/assets/61919774/9b3ae8fe-996a-4e10-b42e-74097f8e5512" alt="VitoDeploy>
|
||||
<img src="https://github.com/vitodeploy/vito/assets/61919774/8060fded-58e3-4d58-b58b-5b717b0718e9" alt="VitoDeploy>
|
||||
<p align="center">
|
||||
<a href="https://github.com/vitodeploy/vito/actions"><img alt="GitHub Workflow Status" src="https://github.com/vitodeploy/vito/workflows/tests/badge.svg"></a>
|
||||
</p>
|
||||
@ -34,9 +34,8 @@ ## Useful Links
|
||||
- [Documentation](https://vitodeploy.com)
|
||||
- [Install on Server](https://vitodeploy.com/introduction/installation.html#install-on-vps-recommended)
|
||||
- [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)
|
||||
- [Roadmap](https://github.com/orgs/vitodeploy/projects/5)
|
||||
- [Video Demo](https://youtu.be/AbmUOBDOc28)
|
||||
- [Discord](https://discord.gg/uZeeHZZnm5)
|
||||
- [Contribution](/CONTRIBUTING.md)
|
||||
- [Security](/SECURITY.md)
|
||||
@ -50,8 +49,6 @@ ## Credits
|
||||
- Alpinejs
|
||||
- HTMX
|
||||
- Vite
|
||||
- Toastr by CodeSeven
|
||||
- Prettier
|
||||
- Postcss
|
||||
- Flowbite
|
||||
- svgrepo.com
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Actions\CronJob;
|
||||
|
||||
use App\Enums\CronjobStatus;
|
||||
use App\Models\CronJob;
|
||||
use App\Models\Server;
|
||||
|
||||
@ -10,7 +11,9 @@ class DeleteCronJob
|
||||
public function delete(Server $server, CronJob $cronJob): void
|
||||
{
|
||||
$user = $cronJob->user;
|
||||
$cronJob->delete();
|
||||
$cronJob->status = CronjobStatus::DELETING;
|
||||
$cronJob->save();
|
||||
$server->cron()->update($cronJob->user, CronJob::crontab($server, $user));
|
||||
$cronJob->delete();
|
||||
}
|
||||
}
|
||||
|
20
app/Actions/CronJob/DisableCronJob.php
Executable file
20
app/Actions/CronJob/DisableCronJob.php
Executable file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\CronJob;
|
||||
|
||||
use App\Enums\CronjobStatus;
|
||||
use App\Models\CronJob;
|
||||
use App\Models\Server;
|
||||
|
||||
class DisableCronJob
|
||||
{
|
||||
public function disable(Server $server, CronJob $cronJob): void
|
||||
{
|
||||
$cronJob->status = CronjobStatus::DISABLING;
|
||||
$cronJob->save();
|
||||
|
||||
$server->cron()->update($cronJob->user, CronJob::crontab($server, $cronJob->user));
|
||||
$cronJob->status = CronjobStatus::DISABLED;
|
||||
$cronJob->save();
|
||||
}
|
||||
}
|
20
app/Actions/CronJob/EnableCronJob.php
Executable file
20
app/Actions/CronJob/EnableCronJob.php
Executable file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\CronJob;
|
||||
|
||||
use App\Enums\CronjobStatus;
|
||||
use App\Models\CronJob;
|
||||
use App\Models\Server;
|
||||
|
||||
class EnableCronJob
|
||||
{
|
||||
public function enable(Server $server, CronJob $cronJob): void
|
||||
{
|
||||
$cronJob->status = CronjobStatus::ENABLING;
|
||||
$cronJob->save();
|
||||
|
||||
$server->cron()->update($cronJob->user, CronJob::crontab($server, $cronJob->user));
|
||||
$cronJob->status = CronjobStatus::READY;
|
||||
$cronJob->save();
|
||||
}
|
||||
}
|
@ -22,7 +22,9 @@ public function create(Server $server, array $input): Database
|
||||
'server_id' => $server->id,
|
||||
'name' => $input['name'],
|
||||
]);
|
||||
$server->database()->handler()->create($database->name);
|
||||
/** @var \App\SSH\Services\Database\Database */
|
||||
$databaseHandler = $server->database()->handler();
|
||||
$databaseHandler->create($database->name);
|
||||
$database->status = DatabaseStatus::READY;
|
||||
$database->save();
|
||||
|
||||
|
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 (abs($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();
|
||||
}
|
||||
}
|
@ -19,6 +19,7 @@ public function add(User $user, array $input): void
|
||||
'user_id' => $user->id,
|
||||
'provider' => $input['provider'],
|
||||
'label' => $input['label'],
|
||||
'project_id' => isset($input['global']) && $input['global'] ? null : $user->current_project_id,
|
||||
]);
|
||||
$this->validateType($channel, $input);
|
||||
$channel->data = $channel->provider()->createData($input);
|
||||
|
34
app/Actions/NotificationChannels/EditChannel.php
Normal file
34
app/Actions/NotificationChannels/EditChannel.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\NotificationChannels;
|
||||
|
||||
use App\Models\NotificationChannel;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class EditChannel
|
||||
{
|
||||
public function edit(NotificationChannel $notificationChannel, User $user, array $input): void
|
||||
{
|
||||
$this->validate($input);
|
||||
|
||||
$notificationChannel->label = $input['label'];
|
||||
$notificationChannel->project_id = isset($input['global']) && $input['global'] ? null : $user->current_project_id;
|
||||
|
||||
$notificationChannel->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ValidationException
|
||||
*/
|
||||
private function validate(array $input): void
|
||||
{
|
||||
$rules = [
|
||||
'label' => [
|
||||
'required',
|
||||
],
|
||||
];
|
||||
Validator::make($input, $rules)->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]);
|
||||
|
@ -2,7 +2,11 @@
|
||||
|
||||
namespace App\Actions\PHP;
|
||||
|
||||
use App\Enums\PHPIniType;
|
||||
use App\Models\Server;
|
||||
use App\SSH\Services\PHP\PHP;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class GetPHPIni
|
||||
@ -14,7 +18,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($input['type']);
|
||||
} catch (\Throwable $e) {
|
||||
throw ValidationException::withMessages(
|
||||
['ini' => $e->getMessage()]
|
||||
@ -24,6 +31,13 @@ public function getIni(Server $server, array $input): string
|
||||
|
||||
public function validate(Server $server, array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'type' => [
|
||||
'required',
|
||||
Rule::in([PHPIniType::CLI, PHPIniType::FPM]),
|
||||
],
|
||||
])->validate();
|
||||
|
||||
if (! isset($input['version']) || ! in_array($input['version'], $server->installedPHPVersions())) {
|
||||
throw ValidationException::withMessages(
|
||||
['version' => __('This version is not installed')]
|
||||
|
@ -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;
|
||||
|
@ -2,10 +2,13 @@
|
||||
|
||||
namespace App\Actions\PHP;
|
||||
|
||||
use App\Enums\PHPIniType;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Filesystem\FilesystemAdapter;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Throwable;
|
||||
|
||||
@ -22,19 +25,19 @@ public function update(Server $server, array $input): void
|
||||
|
||||
$tmpName = Str::random(10).strtotime('now');
|
||||
try {
|
||||
/** @var \Illuminate\Filesystem\FilesystemAdapter $storageDisk */
|
||||
/** @var FilesystemAdapter $storageDisk */
|
||||
$storageDisk = Storage::disk('local');
|
||||
|
||||
$storageDisk->put($tmpName, $input['ini']);
|
||||
$service->server->ssh('root')->upload(
|
||||
$storageDisk->path($tmpName),
|
||||
"/etc/php/$service->version/cli/php.ini"
|
||||
sprintf('/etc/php/%s/%s/php.ini', $service->version, $input['type'])
|
||||
);
|
||||
$this->deleteTempFile($tmpName);
|
||||
} catch (Throwable) {
|
||||
$this->deleteTempFile($tmpName);
|
||||
throw ValidationException::withMessages([
|
||||
'ini' => __("Couldn't update php.ini file!"),
|
||||
'ini' => __("Couldn't update php.ini (:type) file!", ['type' => $input['type']]),
|
||||
]);
|
||||
}
|
||||
|
||||
@ -56,6 +59,10 @@ public function validate(Server $server, array $input): void
|
||||
'string',
|
||||
],
|
||||
'version' => 'required|string',
|
||||
'type' => [
|
||||
'required',
|
||||
Rule::in([PHPIniType::CLI, PHPIniType::FPM]),
|
||||
],
|
||||
])->validate();
|
||||
|
||||
if (! in_array($input['version'], $server->installedPHPVersions())) {
|
||||
|
@ -10,26 +10,31 @@ class CreateProject
|
||||
{
|
||||
public function create(User $user, array $input): Project
|
||||
{
|
||||
$this->validate($user, $input);
|
||||
if (isset($input['name'])) {
|
||||
$input['name'] = strtolower($input['name']);
|
||||
}
|
||||
|
||||
$this->validate($input);
|
||||
|
||||
$project = new Project([
|
||||
'user_id' => $user->id,
|
||||
'name' => $input['name'],
|
||||
]);
|
||||
|
||||
$project->save();
|
||||
|
||||
$project->users()->attach($user);
|
||||
|
||||
return $project;
|
||||
}
|
||||
|
||||
private function validate(User $user, array $input): void
|
||||
private function validate(array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'name' => [
|
||||
'required',
|
||||
'string',
|
||||
'max:255',
|
||||
'unique:projects,name,NULL,id,user_id,'.$user->id,
|
||||
'unique:projects,name',
|
||||
],
|
||||
])->validate();
|
||||
}
|
||||
|
@ -17,12 +17,16 @@ public function delete(User $user, Project $project): void
|
||||
}
|
||||
|
||||
if ($user->current_project_id == $project->id) {
|
||||
/** @var Project $randomProject */
|
||||
$randomProject = $user->projects()->where('id', '!=', $project->id)->first();
|
||||
$user->current_project_id = $randomProject->id;
|
||||
$user->save();
|
||||
throw ValidationException::withMessages([
|
||||
'project' => __('Cannot delete your current project.'),
|
||||
]);
|
||||
}
|
||||
|
||||
/** @var Project $randomProject */
|
||||
$randomProject = $user->projects()->where('project_id', '!=', $project->id)->first();
|
||||
$user->current_project_id = $randomProject->id;
|
||||
$user->save();
|
||||
|
||||
$project->delete();
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,10 @@ class UpdateProject
|
||||
{
|
||||
public function update(Project $project, array $input): Project
|
||||
{
|
||||
if (isset($input['name'])) {
|
||||
$input['name'] = strtolower($input['name']);
|
||||
}
|
||||
|
||||
$this->validate($project, $input);
|
||||
|
||||
$project->name = $input['name'];
|
||||
|
@ -6,6 +6,7 @@
|
||||
use App\Enums\SslType;
|
||||
use App\Models\Site;
|
||||
use App\Models\Ssl;
|
||||
use App\SSH\Services\Webserver\Webserver;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
@ -27,16 +28,23 @@ public function create(Site $site, array $input): void
|
||||
'expires_at' => $input['type'] === SslType::LETSENCRYPT ? now()->addMonths(3) : $input['expires_at'],
|
||||
'status' => SslStatus::CREATING,
|
||||
]);
|
||||
$ssl->domains = [$site->domain];
|
||||
if (isset($input['aliases']) && $input['aliases']) {
|
||||
$ssl->domains = array_merge($ssl->domains, $site->aliases);
|
||||
}
|
||||
$ssl->save();
|
||||
|
||||
dispatch(function () use ($site, $ssl) {
|
||||
$site->server->webserver()->handler()->setupSSL($ssl);
|
||||
/** @var Webserver $webserver */
|
||||
$webserver = $site->server->webserver()->handler();
|
||||
$webserver->setupSSL($ssl);
|
||||
$ssl->status = SslStatus::CREATED;
|
||||
$ssl->save();
|
||||
$site->type()->edit();
|
||||
})->catch(function () use ($ssl) {
|
||||
$ssl->delete();
|
||||
});
|
||||
$ssl->status = SslStatus::FAILED;
|
||||
$ssl->save();
|
||||
})->onConnection('ssh');
|
||||
}
|
||||
|
||||
/**
|
||||
|
32
app/Actions/Script/CreateScript.php
Normal file
32
app/Actions/Script/CreateScript.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Script;
|
||||
|
||||
use App\Models\Script;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class CreateScript
|
||||
{
|
||||
public function create(User $user, array $input): Script
|
||||
{
|
||||
$this->validate($input);
|
||||
|
||||
$script = new Script([
|
||||
'user_id' => $user->id,
|
||||
'name' => $input['name'],
|
||||
'content' => $input['content'],
|
||||
]);
|
||||
$script->save();
|
||||
|
||||
return $script;
|
||||
}
|
||||
|
||||
private function validate(array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'content' => ['required', 'string'],
|
||||
])->validate();
|
||||
}
|
||||
}
|
28
app/Actions/Script/EditScript.php
Normal file
28
app/Actions/Script/EditScript.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Script;
|
||||
|
||||
use App\Models\Script;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class EditScript
|
||||
{
|
||||
public function edit(Script $script, array $input): Script
|
||||
{
|
||||
$this->validate($input);
|
||||
|
||||
$script->name = $input['name'];
|
||||
$script->content = $input['content'];
|
||||
$script->save();
|
||||
|
||||
return $script;
|
||||
}
|
||||
|
||||
private function validate(array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'content' => ['required', 'string'],
|
||||
])->validate();
|
||||
}
|
||||
}
|
62
app/Actions/Script/ExecuteScript.php
Normal file
62
app/Actions/Script/ExecuteScript.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Script;
|
||||
|
||||
use App\Enums\ScriptExecutionStatus;
|
||||
use App\Models\Script;
|
||||
use App\Models\ScriptExecution;
|
||||
use App\Models\Server;
|
||||
use App\Models\ServerLog;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class ExecuteScript
|
||||
{
|
||||
public function execute(Script $script, Server $server, array $input): ScriptExecution
|
||||
{
|
||||
$this->validate($server, $input);
|
||||
|
||||
$execution = new ScriptExecution([
|
||||
'script_id' => $script->id,
|
||||
'user' => $input['user'],
|
||||
'variables' => $input['variables'] ?? [],
|
||||
'status' => ScriptExecutionStatus::EXECUTING,
|
||||
]);
|
||||
$execution->save();
|
||||
|
||||
dispatch(function () use ($execution, $server, $script) {
|
||||
$content = $execution->getContent();
|
||||
$log = ServerLog::make($server, 'script-'.$script->id.'-'.strtotime('now'));
|
||||
$log->save();
|
||||
$execution->server_log_id = $log->id;
|
||||
$execution->save();
|
||||
$server->os()->runScript('~/', $content, $log, $execution->user);
|
||||
$execution->status = ScriptExecutionStatus::COMPLETED;
|
||||
$execution->save();
|
||||
})->catch(function () use ($execution) {
|
||||
$execution->status = ScriptExecutionStatus::FAILED;
|
||||
$execution->save();
|
||||
})->onConnection('ssh');
|
||||
|
||||
return $execution;
|
||||
}
|
||||
|
||||
private function validate(Server $server, array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'user' => [
|
||||
'required',
|
||||
Rule::in([
|
||||
'root',
|
||||
$server->ssh_user,
|
||||
]),
|
||||
],
|
||||
'variables' => 'array',
|
||||
'variables.*' => [
|
||||
'required',
|
||||
'string',
|
||||
'max:255',
|
||||
],
|
||||
])->validate();
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Actions\Server;
|
||||
|
||||
use App\Enums\ServerStatus;
|
||||
use App\Facades\Notifier;
|
||||
use App\Models\Server;
|
||||
use App\Notifications\ServerDisconnected;
|
||||
@ -15,12 +16,12 @@ public function check(Server $server): Server
|
||||
try {
|
||||
$server->ssh()->connect();
|
||||
$server->refresh();
|
||||
if ($status == 'disconnected') {
|
||||
$server->status = 'ready';
|
||||
if (in_array($status, [ServerStatus::DISCONNECTED, ServerStatus::UPDATING])) {
|
||||
$server->status = ServerStatus::READY;
|
||||
$server->save();
|
||||
}
|
||||
} catch (Throwable) {
|
||||
$server->status = 'disconnected';
|
||||
$server->status = ServerStatus::DISCONNECTED;
|
||||
$server->save();
|
||||
Notifier::send($server, new ServerDisconnected($server));
|
||||
}
|
||||
|
35
app/Actions/Server/CreateServerLog.php
Executable file
35
app/Actions/Server/CreateServerLog.php
Executable file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Server;
|
||||
|
||||
use App\Models\Server;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class CreateServerLog
|
||||
{
|
||||
/**
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function create(Server $server, array $input): void
|
||||
{
|
||||
$this->validate($input);
|
||||
|
||||
$server->logs()->create([
|
||||
'is_remote' => true,
|
||||
'name' => $input['path'],
|
||||
'type' => 'remote',
|
||||
'disk' => 'ssh',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ValidationException
|
||||
*/
|
||||
protected function validate(array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'path' => 'required',
|
||||
])->validate();
|
||||
}
|
||||
}
|
25
app/Actions/Server/Update.php
Normal file
25
app/Actions/Server/Update.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Server;
|
||||
|
||||
use App\Enums\ServerStatus;
|
||||
use App\Facades\Notifier;
|
||||
use App\Models\Server;
|
||||
use App\Notifications\ServerUpdateFailed;
|
||||
|
||||
class Update
|
||||
{
|
||||
public function update(Server $server): void
|
||||
{
|
||||
$server->status = ServerStatus::UPDATING;
|
||||
$server->save();
|
||||
dispatch(function () use ($server) {
|
||||
$server->os()->upgrade();
|
||||
$server->checkConnection();
|
||||
$server->checkForUpdates();
|
||||
})->catch(function () use ($server) {
|
||||
Notifier::send($server, new ServerUpdateFailed($server));
|
||||
$server->checkConnection();
|
||||
})->onConnection('ssh');
|
||||
}
|
||||
}
|
@ -38,6 +38,7 @@ public function create(User $user, array $input): ServerProvider
|
||||
$serverProvider->profile = $input['name'];
|
||||
$serverProvider->provider = $input['provider'];
|
||||
$serverProvider->credentials = $provider->credentialData($input);
|
||||
$serverProvider->project_id = isset($input['global']) && $input['global'] ? null : $user->current_project_id;
|
||||
$serverProvider->save();
|
||||
|
||||
return $serverProvider;
|
||||
|
34
app/Actions/ServerProvider/EditServerProvider.php
Normal file
34
app/Actions/ServerProvider/EditServerProvider.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\ServerProvider;
|
||||
|
||||
use App\Models\ServerProvider;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class EditServerProvider
|
||||
{
|
||||
public function edit(ServerProvider $serverProvider, User $user, array $input): void
|
||||
{
|
||||
$this->validate($input);
|
||||
|
||||
$serverProvider->profile = $input['name'];
|
||||
$serverProvider->project_id = isset($input['global']) && $input['global'] ? null : $user->current_project_id;
|
||||
|
||||
$serverProvider->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ValidationException
|
||||
*/
|
||||
private function validate(array $input): void
|
||||
{
|
||||
$rules = [
|
||||
'name' => [
|
||||
'required',
|
||||
],
|
||||
];
|
||||
Validator::make($input, $rules)->validate();
|
||||
}
|
||||
}
|
@ -8,14 +8,15 @@
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class Create
|
||||
class Install
|
||||
{
|
||||
public function create(Server $server, array $input): Service
|
||||
public function install(Server $server, array $input): Service
|
||||
{
|
||||
$this->validate($server, $input);
|
||||
|
||||
$service = new Service([
|
||||
'name' => $input['type'],
|
||||
'server_id' => $server->id,
|
||||
'name' => $input['name'],
|
||||
'type' => $input['type'],
|
||||
'version' => $input['version'],
|
||||
'status' => ServiceStatus::INSTALLING,
|
||||
@ -27,15 +28,13 @@ public function create(Server $server, array $input): Service
|
||||
|
||||
$service->save();
|
||||
|
||||
$service->handler()->create();
|
||||
|
||||
dispatch(function () use ($service) {
|
||||
$service->handler()->install();
|
||||
$service->status = ServiceStatus::READY;
|
||||
$service->save();
|
||||
})->catch(function () use ($service) {
|
||||
$service->handler()->delete();
|
||||
$service->delete();
|
||||
$service->status = ServiceStatus::INSTALLATION_FAILED;
|
||||
$service->save();
|
||||
})->onConnection('ssh');
|
||||
|
||||
return $service;
|
||||
@ -46,8 +45,11 @@ private function validate(Server $server, array $input): void
|
||||
Validator::make($input, [
|
||||
'type' => [
|
||||
'required',
|
||||
Rule::in(config('core.add_on_services')),
|
||||
Rule::unique('services', 'type')->where('server_id', $server->id),
|
||||
Rule::in(config('core.service_types')),
|
||||
],
|
||||
'name' => [
|
||||
'required',
|
||||
Rule::in(array_keys(config('core.service_types'))),
|
||||
],
|
||||
'version' => 'required',
|
||||
])->validate();
|
28
app/Actions/Service/Uninstall.php
Normal file
28
app/Actions/Service/Uninstall.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Service;
|
||||
|
||||
use App\Enums\ServiceStatus;
|
||||
use App\Models\Service;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class Uninstall
|
||||
{
|
||||
public function uninstall(Service $service): void
|
||||
{
|
||||
Validator::make([
|
||||
'service' => $service->id,
|
||||
], $service->handler()->deletionRules())->validate();
|
||||
|
||||
$service->status = ServiceStatus::UNINSTALLING;
|
||||
$service->save();
|
||||
|
||||
dispatch(function () use ($service) {
|
||||
$service->handler()->uninstall();
|
||||
$service->delete();
|
||||
})->catch(function () use ($service) {
|
||||
$service->status = ServiceStatus::FAILED;
|
||||
$service->save();
|
||||
})->onConnection('ssh');
|
||||
}
|
||||
}
|
@ -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;
|
||||
@ -20,7 +22,6 @@ class CreateSite
|
||||
{
|
||||
/**
|
||||
* @throws SourceControlIsNotConnected
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function create(Server $server, array $input): Site
|
||||
{
|
||||
@ -32,7 +33,7 @@ public function create(Server $server, array $input): Site
|
||||
'server_id' => $server->id,
|
||||
'type' => $input['type'],
|
||||
'domain' => $input['domain'],
|
||||
'aliases' => isset($input['alias']) ? [$input['alias']] : [],
|
||||
'aliases' => $input['aliases'] ?? [],
|
||||
'path' => '/home/'.$server->getSshUser().'/'.$input['domain'],
|
||||
'status' => SiteStatus::INSTALLING,
|
||||
]);
|
||||
@ -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',
|
||||
]);
|
||||
}
|
||||
|
||||
@ -106,7 +115,7 @@ private function validateInputs(Server $server, array $input): void
|
||||
return $query->where('server_id', $server->id);
|
||||
}),
|
||||
],
|
||||
'alias' => [
|
||||
'aliases.*' => [
|
||||
new DomainRule(),
|
||||
],
|
||||
];
|
||||
|
@ -6,6 +6,7 @@
|
||||
use App\Exceptions\DeploymentScriptIsEmptyException;
|
||||
use App\Exceptions\SourceControlIsNotConnected;
|
||||
use App\Models\Deployment;
|
||||
use App\Models\ServerLog;
|
||||
use App\Models\Site;
|
||||
|
||||
class Deploy
|
||||
@ -29,7 +30,7 @@ public function run(Site $site): Deployment
|
||||
'deployment_script_id' => $site->deploymentScript->id,
|
||||
'status' => DeploymentStatus::DEPLOYING,
|
||||
]);
|
||||
$lastCommit = $site->sourceControl()->provider()->getLastCommit($site->repository, $site->branch);
|
||||
$lastCommit = $site->sourceControl()?->provider()?->getLastCommit($site->repository, $site->branch);
|
||||
if ($lastCommit) {
|
||||
$deployment->commit_id = $lastCommit['commit_id'];
|
||||
$deployment->commit_data = $lastCommit['commit_data'];
|
||||
@ -37,10 +38,20 @@ public function run(Site $site): Deployment
|
||||
$deployment->save();
|
||||
|
||||
dispatch(function () use ($site, $deployment) {
|
||||
$log = $site->server->os()->runScript($site->path, $site->deploymentScript->content, $site->id);
|
||||
$deployment->status = DeploymentStatus::FINISHED;
|
||||
/** @var ServerLog $log */
|
||||
$log = ServerLog::make($site->server, 'deploy-'.strtotime('now'))
|
||||
->forSite($site);
|
||||
$log->save();
|
||||
$deployment->log_id = $log->id;
|
||||
$deployment->save();
|
||||
$site->server->os()->runScript(
|
||||
path: $site->path,
|
||||
script: $site->deploymentScript->content,
|
||||
serverLog: $log,
|
||||
variables: $site->environmentVariables($deployment)
|
||||
);
|
||||
$deployment->status = DeploymentStatus::FINISHED;
|
||||
$deployment->save();
|
||||
})->catch(function () use ($deployment) {
|
||||
$deployment->status = DeploymentStatus::FAILED;
|
||||
$deployment->save();
|
||||
|
33
app/Actions/Site/UpdateAliases.php
Normal file
33
app/Actions/Site/UpdateAliases.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Site;
|
||||
|
||||
use App\Models\Site;
|
||||
use App\SSH\Services\Webserver\Webserver;
|
||||
use App\ValidationRules\DomainRule;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class UpdateAliases
|
||||
{
|
||||
public function update(Site $site, array $input): void
|
||||
{
|
||||
$this->validate($input);
|
||||
|
||||
$site->aliases = $input['aliases'] ?? [];
|
||||
|
||||
/** @var Webserver $webserver */
|
||||
$webserver = $site->server->webserver()->handler();
|
||||
$webserver->updateVHost($site, ! $site->hasSSL());
|
||||
|
||||
$site->save();
|
||||
}
|
||||
|
||||
private function validate(array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'aliases.*' => [
|
||||
new DomainRule(),
|
||||
],
|
||||
])->validate();
|
||||
}
|
||||
}
|
@ -2,10 +2,14 @@
|
||||
|
||||
namespace App\Actions\Site;
|
||||
|
||||
use App\Exceptions\SSHUploadFailed;
|
||||
use App\Models\Site;
|
||||
|
||||
class UpdateEnv
|
||||
{
|
||||
/**
|
||||
* @throws SSHUploadFailed
|
||||
*/
|
||||
public function update(Site $site, array $input): void
|
||||
{
|
||||
$site->server->os()->editFile(
|
||||
|
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();
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
namespace App\Actions\SourceControl;
|
||||
|
||||
use App\Models\SourceControl;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
@ -10,7 +11,7 @@
|
||||
|
||||
class ConnectSourceControl
|
||||
{
|
||||
public function connect(array $input): void
|
||||
public function connect(User $user, array $input): void
|
||||
{
|
||||
$this->validate($input);
|
||||
|
||||
@ -18,6 +19,7 @@ public function connect(array $input): void
|
||||
'provider' => $input['provider'],
|
||||
'profile' => $input['name'],
|
||||
'url' => Arr::has($input, 'url') ? $input['url'] : null,
|
||||
'project_id' => isset($input['global']) && $input['global'] ? null : $user->current_project_id,
|
||||
]);
|
||||
|
||||
$this->validateProvider($sourceControl, $input);
|
||||
|
54
app/Actions/SourceControl/EditSourceControl.php
Normal file
54
app/Actions/SourceControl/EditSourceControl.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\SourceControl;
|
||||
|
||||
use App\Models\SourceControl;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class EditSourceControl
|
||||
{
|
||||
public function edit(SourceControl $sourceControl, User $user, array $input): void
|
||||
{
|
||||
$this->validate($input);
|
||||
|
||||
$sourceControl->profile = $input['name'];
|
||||
$sourceControl->url = $input['url'] ?? null;
|
||||
$sourceControl->project_id = isset($input['global']) && $input['global'] ? null : $user->current_project_id;
|
||||
|
||||
$this->validateProvider($sourceControl, $input);
|
||||
|
||||
$sourceControl->provider_data = $sourceControl->provider()->createData($input);
|
||||
|
||||
if (! $sourceControl->provider()->connect()) {
|
||||
throw ValidationException::withMessages([
|
||||
'token' => __('Cannot connect to :provider or invalid token!', ['provider' => $sourceControl->provider]
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
$sourceControl->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ValidationException
|
||||
*/
|
||||
private function validate(array $input): void
|
||||
{
|
||||
$rules = [
|
||||
'name' => [
|
||||
'required',
|
||||
],
|
||||
];
|
||||
Validator::make($input, $rules)->validate();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ValidationException
|
||||
*/
|
||||
private function validateProvider(SourceControl $sourceControl, array $input): void
|
||||
{
|
||||
Validator::make($input, $sourceControl->provider()->createRules($input))->validate();
|
||||
}
|
||||
}
|
@ -21,6 +21,7 @@ public function create(User $user, array $input): void
|
||||
'user_id' => $user->id,
|
||||
'provider' => $input['provider'],
|
||||
'profile' => $input['name'],
|
||||
'project_id' => isset($input['global']) && $input['global'] ? null : $user->current_project_id,
|
||||
]);
|
||||
|
||||
$this->validateProvider($input, $storageProvider->provider()->validationRules());
|
||||
|
34
app/Actions/StorageProvider/EditStorageProvider.php
Normal file
34
app/Actions/StorageProvider/EditStorageProvider.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\StorageProvider;
|
||||
|
||||
use App\Models\StorageProvider;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class EditStorageProvider
|
||||
{
|
||||
public function edit(StorageProvider $storageProvider, User $user, array $input): void
|
||||
{
|
||||
$this->validate($input);
|
||||
|
||||
$storageProvider->profile = $input['name'];
|
||||
$storageProvider->project_id = isset($input['global']) && $input['global'] ? null : $user->current_project_id;
|
||||
|
||||
$storageProvider->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ValidationException
|
||||
*/
|
||||
private function validate(array $input): void
|
||||
{
|
||||
$rules = [
|
||||
'name' => [
|
||||
'required',
|
||||
],
|
||||
];
|
||||
Validator::make($input, $rules)->validate();
|
||||
}
|
||||
}
|
58
app/Actions/Tag/AttachTag.php
Normal file
58
app/Actions/Tag/AttachTag.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Tag;
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Models\Site;
|
||||
use App\Models\Tag;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class AttachTag
|
||||
{
|
||||
public function attach(User $user, array $input): Tag
|
||||
{
|
||||
$this->validate($input);
|
||||
|
||||
/** @var Server|Site $taggable */
|
||||
$taggable = $input['taggable_type']::findOrFail($input['taggable_id']);
|
||||
|
||||
$tag = Tag::query()->where('name', $input['name'])->first();
|
||||
if ($tag) {
|
||||
if (! $taggable->tags->contains($tag->id)) {
|
||||
$taggable->tags()->attach($tag->id);
|
||||
}
|
||||
|
||||
return $tag;
|
||||
}
|
||||
|
||||
$tag = new Tag([
|
||||
'project_id' => $user->currentProject->id,
|
||||
'name' => $input['name'],
|
||||
'color' => config('core.tag_colors')[array_rand(config('core.tag_colors'))],
|
||||
]);
|
||||
$tag->save();
|
||||
|
||||
$taggable->tags()->attach($tag->id);
|
||||
|
||||
return $tag;
|
||||
}
|
||||
|
||||
private function validate(array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'name' => [
|
||||
'required',
|
||||
],
|
||||
'taggable_id' => [
|
||||
'required',
|
||||
'integer',
|
||||
],
|
||||
'taggable_type' => [
|
||||
'required',
|
||||
Rule::in(config('core.taggable_types')),
|
||||
],
|
||||
])->validate();
|
||||
}
|
||||
}
|
49
app/Actions/Tag/CreateTag.php
Normal file
49
app/Actions/Tag/CreateTag.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Tag;
|
||||
|
||||
use App\Models\Tag;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class CreateTag
|
||||
{
|
||||
public function create(User $user, array $input): Tag
|
||||
{
|
||||
$this->validate($input);
|
||||
|
||||
$tag = Tag::query()
|
||||
->where('project_id', $user->current_project_id)
|
||||
->where('name', $input['name'])
|
||||
->first();
|
||||
if ($tag) {
|
||||
throw ValidationException::withMessages([
|
||||
'name' => ['Tag with this name already exists.'],
|
||||
]);
|
||||
}
|
||||
|
||||
$tag = new Tag([
|
||||
'project_id' => $user->currentProject->id,
|
||||
'name' => $input['name'],
|
||||
'color' => $input['color'],
|
||||
]);
|
||||
$tag->save();
|
||||
|
||||
return $tag;
|
||||
}
|
||||
|
||||
private function validate(array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'name' => [
|
||||
'required',
|
||||
],
|
||||
'color' => [
|
||||
'required',
|
||||
Rule::in(config('core.tag_colors')),
|
||||
],
|
||||
])->validate();
|
||||
}
|
||||
}
|
15
app/Actions/Tag/DeleteTag.php
Normal file
15
app/Actions/Tag/DeleteTag.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Tag;
|
||||
|
||||
use App\Models\Tag;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class DeleteTag
|
||||
{
|
||||
public function delete(Tag $tag): void
|
||||
{
|
||||
DB::table('taggables')->where('tag_id', $tag->id)->delete();
|
||||
$tag->delete();
|
||||
}
|
||||
}
|
36
app/Actions/Tag/DetachTag.php
Normal file
36
app/Actions/Tag/DetachTag.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Tag;
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Models\Site;
|
||||
use App\Models\Tag;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class DetachTag
|
||||
{
|
||||
public function detach(Tag $tag, array $input): void
|
||||
{
|
||||
$this->validate($input);
|
||||
|
||||
/** @var Server|Site $taggable */
|
||||
$taggable = $input['taggable_type']::findOrFail($input['taggable_id']);
|
||||
|
||||
$taggable->tags()->detach($tag->id);
|
||||
}
|
||||
|
||||
private function validate(array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'taggable_id' => [
|
||||
'required',
|
||||
'integer',
|
||||
],
|
||||
'taggable_type' => [
|
||||
'required',
|
||||
Rule::in(config('core.taggable_types')),
|
||||
],
|
||||
])->validate();
|
||||
}
|
||||
}
|
38
app/Actions/Tag/EditTag.php
Normal file
38
app/Actions/Tag/EditTag.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Tag;
|
||||
|
||||
use App\Models\Tag;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class EditTag
|
||||
{
|
||||
public function edit(Tag $tag, array $input): void
|
||||
{
|
||||
$this->validate($input);
|
||||
|
||||
$tag->name = $input['name'];
|
||||
$tag->color = $input['color'];
|
||||
|
||||
$tag->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ValidationException
|
||||
*/
|
||||
private function validate(array $input): void
|
||||
{
|
||||
$rules = [
|
||||
'name' => [
|
||||
'required',
|
||||
],
|
||||
'color' => [
|
||||
'required',
|
||||
Rule::in(config('core.tag_colors')),
|
||||
],
|
||||
];
|
||||
Validator::make($input, $rules)->validate();
|
||||
}
|
||||
}
|
40
app/Actions/User/CreateUser.php
Normal file
40
app/Actions/User/CreateUser.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\User;
|
||||
|
||||
use App\Enums\UserRole;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class CreateUser
|
||||
{
|
||||
public function create(array $input): User
|
||||
{
|
||||
$this->validate($input);
|
||||
|
||||
/** @var User $user */
|
||||
$user = User::query()->create([
|
||||
'name' => $input['name'],
|
||||
'email' => $input['email'],
|
||||
'role' => $input['role'],
|
||||
'password' => bcrypt($input['password']),
|
||||
'timezone' => 'UTC',
|
||||
]);
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
private function validate(array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email|unique:users,email',
|
||||
'password' => 'required|string|min:8',
|
||||
'role' => [
|
||||
'required',
|
||||
Rule::in([UserRole::ADMIN, UserRole::USER]),
|
||||
],
|
||||
])->validate();
|
||||
}
|
||||
}
|
48
app/Actions/User/UpdateUser.php
Normal file
48
app/Actions/User/UpdateUser.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\User;
|
||||
|
||||
use App\Enums\UserRole;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateUser
|
||||
{
|
||||
public function update(User $user, array $input): void
|
||||
{
|
||||
$this->validate($user, $input);
|
||||
|
||||
$user->name = $input['name'];
|
||||
$user->email = $input['email'];
|
||||
$user->timezone = $input['timezone'];
|
||||
$user->role = $input['role'];
|
||||
|
||||
if (isset($input['password']) && $input['password'] !== null) {
|
||||
$user->password = bcrypt($input['password']);
|
||||
}
|
||||
|
||||
$user->save();
|
||||
}
|
||||
|
||||
private function validate(User $user, array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'email' => ['required', 'email', 'max:255', Rule::unique('users')->ignore($user->id)],
|
||||
'timezone' => [
|
||||
'required',
|
||||
Rule::in(timezone_identifiers_list()),
|
||||
],
|
||||
'role' => [
|
||||
'required',
|
||||
Rule::in([UserRole::ADMIN, UserRole::USER]),
|
||||
function ($attribute, $value, $fail) use ($user) {
|
||||
if ($user->is(auth()->user()) && $value !== $user->role) {
|
||||
$fail('You cannot change your own role');
|
||||
}
|
||||
},
|
||||
],
|
||||
])->validate();
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@
|
||||
|
||||
class CreateUserCommand extends Command
|
||||
{
|
||||
protected $signature = 'user:create {name} {email} {password}';
|
||||
protected $signature = 'user:create {name} {email} {password} {--role=admin}';
|
||||
|
||||
protected $description = 'Create a new user';
|
||||
|
||||
@ -25,6 +25,7 @@ public function handle(): void
|
||||
'name' => $this->argument('name'),
|
||||
'email' => $this->argument('email'),
|
||||
'password' => bcrypt($this->argument('password')),
|
||||
'role' => $this->option('role'),
|
||||
]);
|
||||
|
||||
$this->info('User created!');
|
||||
|
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');
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
31
app/Console/Commands/GetMetricsCommand.php
Normal file
31
app/Console/Commands/GetMetricsCommand.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Server;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class GetMetricsCommand extends Command
|
||||
{
|
||||
protected $signature = 'metrics:get';
|
||||
|
||||
protected $description = 'Get server metrics';
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
$checkedMetrics = 0;
|
||||
Server::query()->whereHas('services', function (Builder $query) {
|
||||
$query->where('type', 'monitoring')
|
||||
->where('name', 'remote-monitor');
|
||||
})->chunk(10, function ($servers) use (&$checkedMetrics) {
|
||||
/** @var Server $server */
|
||||
foreach ($servers as $server) {
|
||||
$info = $server->os()->resourceInfo();
|
||||
$server->metrics()->create(array_merge($info, ['server_id' => $server->id]));
|
||||
$checkedMetrics++;
|
||||
}
|
||||
});
|
||||
$this->info("Checked $checkedMetrics metrics");
|
||||
}
|
||||
}
|
@ -16,6 +16,8 @@ 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();
|
||||
$schedule->command('metrics:get')->everyMinute();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -9,4 +9,10 @@ final class CronjobStatus
|
||||
const READY = 'ready';
|
||||
|
||||
const DELETING = 'deleting';
|
||||
|
||||
const ENABLING = 'enabling';
|
||||
|
||||
const DISABLING = 'disabling';
|
||||
|
||||
const DISABLED = 'disabled';
|
||||
}
|
||||
|
@ -9,4 +9,6 @@ final class OperatingSystem
|
||||
const UBUNTU20 = 'ubuntu_20';
|
||||
|
||||
const UBUNTU22 = 'ubuntu_22';
|
||||
|
||||
const UBUNTU24 = 'ubuntu_24';
|
||||
}
|
||||
|
10
app/Enums/PHPIniType.php
Normal file
10
app/Enums/PHPIniType.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
final class PHPIniType
|
||||
{
|
||||
const CLI = 'cli';
|
||||
|
||||
const FPM = 'fpm';
|
||||
}
|
12
app/Enums/ScriptExecutionStatus.php
Normal file
12
app/Enums/ScriptExecutionStatus.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
final class ScriptExecutionStatus
|
||||
{
|
||||
const EXECUTING = 'executing';
|
||||
|
||||
const COMPLETED = 'completed';
|
||||
|
||||
const FAILED = 'failed';
|
||||
}
|
@ -11,4 +11,6 @@ final class ServerStatus
|
||||
const INSTALLATION_FAILED = 'installation_failed';
|
||||
|
||||
const DISCONNECTED = 'disconnected';
|
||||
|
||||
const UPDATING = 'updating';
|
||||
}
|
||||
|
@ -7,4 +7,10 @@ final class StorageProvider
|
||||
const DROPBOX = 'dropbox';
|
||||
|
||||
const FTP = 'ftp';
|
||||
|
||||
const LOCAL = 'local';
|
||||
|
||||
const S3 = 's3';
|
||||
|
||||
const WASABI = 'wasabi';
|
||||
}
|
||||
|
10
app/Enums/UserRole.php
Normal file
10
app/Enums/UserRole.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
final class UserRole
|
||||
{
|
||||
const USER = 'user';
|
||||
|
||||
const ADMIN = 'admin';
|
||||
}
|
@ -4,6 +4,4 @@
|
||||
|
||||
use Exception;
|
||||
|
||||
class DeploymentScriptIsEmptyException extends Exception
|
||||
{
|
||||
}
|
||||
class DeploymentScriptIsEmptyException extends Exception {}
|
||||
|
@ -2,9 +2,7 @@
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class SSHAuthenticationError extends Exception
|
||||
class SSHAuthenticationError extends SSHError
|
||||
{
|
||||
//
|
||||
}
|
||||
|
8
app/Exceptions/SSHUploadFailed.php
Executable file
8
app/Exceptions/SSHUploadFailed.php
Executable file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
class SSHUploadFailed extends SSHError
|
||||
{
|
||||
//
|
||||
}
|
@ -2,9 +2,7 @@
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class SSLCreationException extends Exception
|
||||
class SSLCreationException extends SSHError
|
||||
{
|
||||
//
|
||||
}
|
||||
|
@ -4,6 +4,4 @@
|
||||
|
||||
use Exception;
|
||||
|
||||
class SourceControlIsNotConnected extends Exception
|
||||
{
|
||||
}
|
||||
class SourceControlIsNotConnected extends Exception {}
|
||||
|
30
app/Facades/FTP.php
Normal file
30
app/Facades/FTP.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Facades;
|
||||
|
||||
use App\Support\Testing\FTPFake;
|
||||
use FTP\Connection;
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
|
||||
/**
|
||||
* @method static bool|Connection connect(string $host, string $port, bool $ssl = false)
|
||||
* @method static bool login(string $username, string $password, bool|Connection $connection)
|
||||
* @method static void close(bool|Connection $connection)
|
||||
* @method static bool passive(bool|Connection $connection, bool $passive)
|
||||
* @method static bool delete(bool|Connection $connection, string $path)
|
||||
* @method static void assertConnected(string $host)
|
||||
*/
|
||||
class FTP extends Facade
|
||||
{
|
||||
protected static function getFacadeAccessor(): string
|
||||
{
|
||||
return 'ftp';
|
||||
}
|
||||
|
||||
public static function fake(): FTPFake
|
||||
{
|
||||
static::swap($fake = new FTPFake());
|
||||
|
||||
return $fake;
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
namespace App\Facades;
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Models\ServerLog;
|
||||
use App\Support\Testing\SSHFake;
|
||||
use Illuminate\Support\Facades\Facade as FacadeAlias;
|
||||
|
||||
@ -10,11 +11,13 @@
|
||||
* Class SSH
|
||||
*
|
||||
* @method static init(Server $server, string $asUser = null)
|
||||
* @method static setLog(string $logType, int $siteId = null)
|
||||
* @method static setLog(?ServerLog $log)
|
||||
* @method static connect()
|
||||
* @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 string assertFileUploaded(string $toPath, ?string $content = null)
|
||||
* @method static string getUploadedLocalPath()
|
||||
* @method static disconnect()
|
||||
*/
|
||||
class SSH extends FacadeAlias
|
||||
|
37
app/Helpers/FTP.php
Normal file
37
app/Helpers/FTP.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
use FTP\Connection;
|
||||
|
||||
class FTP
|
||||
{
|
||||
public function connect(string $host, string $port, bool $ssl = false): bool|Connection
|
||||
{
|
||||
if ($ssl) {
|
||||
return ftp_ssl_connect($host, $port, 5);
|
||||
}
|
||||
|
||||
return ftp_connect($host, $port, 5);
|
||||
}
|
||||
|
||||
public function login(string $username, string $password, bool|Connection $connection): bool
|
||||
{
|
||||
return ftp_login($connection, $username, $password);
|
||||
}
|
||||
|
||||
public function close(bool|Connection $connection): void
|
||||
{
|
||||
ftp_close($connection);
|
||||
}
|
||||
|
||||
public function passive(bool|Connection $connection, bool $passive): bool
|
||||
{
|
||||
return ftp_pasv($connection, $passive);
|
||||
}
|
||||
|
||||
public function delete(bool|Connection $connection, string $path): bool
|
||||
{
|
||||
return ftp_delete($connection, $path);
|
||||
}
|
||||
}
|
@ -50,14 +50,9 @@ public function init(Server $server, ?string $asUser = null): self
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setLog(string $logType, $siteId = null): self
|
||||
public function setLog(ServerLog $log): self
|
||||
{
|
||||
$this->log = $this->server->logs()->create([
|
||||
'site_id' => $siteId,
|
||||
'name' => $this->server->id.'-'.strtotime('now').'-'.$logType.'.log',
|
||||
'type' => $logType,
|
||||
'disk' => config('core.logs_disk'),
|
||||
]);
|
||||
$this->log = $log;
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -98,10 +93,12 @@ public function connect(bool $sftp = false): void
|
||||
*/
|
||||
public function exec(string $command, string $log = '', ?int $siteId = null, ?bool $stream = false): string
|
||||
{
|
||||
if ($log) {
|
||||
$this->setLog($log, $siteId);
|
||||
} else {
|
||||
$this->log = null;
|
||||
if (! $this->log && $log) {
|
||||
$this->log = ServerLog::make($this->server, $log);
|
||||
if ($siteId) {
|
||||
$this->log->forSite($siteId);
|
||||
}
|
||||
$this->log->save();
|
||||
}
|
||||
|
||||
try {
|
||||
@ -132,8 +129,8 @@ public function exec(string $command, string $log = '', ?int $siteId = null, ?bo
|
||||
|
||||
$this->log?->write($output);
|
||||
|
||||
if (Str::contains($output, 'VITO_SSH_ERROR')) {
|
||||
throw new Exception('SSH command failed with an error');
|
||||
if ($this->connection->getExitStatus() !== 0 || Str::contains($output, 'VITO_SSH_ERROR')) {
|
||||
throw new SSHCommandError('SSH command failed with an error', $this->connection->getExitStatus());
|
||||
}
|
||||
|
||||
return $output;
|
||||
|
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();
|
||||
}
|
||||
}
|
@ -7,7 +7,10 @@
|
||||
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\Exceptions\SSHUploadFailed;
|
||||
use App\Facades\Toast;
|
||||
use App\Helpers\HtmxResponse;
|
||||
use App\Models\Deployment;
|
||||
@ -20,16 +23,20 @@ class ApplicationController extends Controller
|
||||
{
|
||||
public function deploy(Server $server, Site $site): HtmxResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
try {
|
||||
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();
|
||||
@ -37,11 +44,15 @@ public function deploy(Server $server, Site $site): HtmxResponse
|
||||
|
||||
public function showDeploymentLog(Server $server, Site $site, Deployment $deployment): RedirectResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
return back()->with('content', $deployment->log?->getContent());
|
||||
}
|
||||
|
||||
public function updateDeploymentScript(Server $server, Site $site, Request $request): RedirectResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
app(UpdateDeploymentScript::class)->update($site, $request->input());
|
||||
|
||||
Toast::success('Deployment script updated!');
|
||||
@ -51,6 +62,8 @@ public function updateDeploymentScript(Server $server, Site $site, Request $requ
|
||||
|
||||
public function updateBranch(Server $server, Site $site, Request $request): RedirectResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
app(UpdateBranch::class)->update($site, $request->input());
|
||||
|
||||
Toast::success('Branch updated!');
|
||||
@ -60,20 +73,29 @@ public function updateBranch(Server $server, Site $site, Request $request): Redi
|
||||
|
||||
public function getEnv(Server $server, Site $site): RedirectResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
return back()->with('env', $site->getEnv());
|
||||
}
|
||||
|
||||
public function updateEnv(Server $server, Site $site, Request $request): RedirectResponse
|
||||
{
|
||||
app(UpdateEnv::class)->update($site, $request->input());
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
Toast::success('Env updated!');
|
||||
try {
|
||||
app(UpdateEnv::class)->update($site, $request->input());
|
||||
Toast::success('Env updated!');
|
||||
} catch (SSHUploadFailed) {
|
||||
Toast::error('Failed to update .env file!');
|
||||
}
|
||||
|
||||
return back();
|
||||
}
|
||||
|
||||
public function enableAutoDeployment(Server $server, Site $site): HtmxResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
if (! $site->isAutoDeployment()) {
|
||||
try {
|
||||
$site->enableAutoDeployment();
|
||||
@ -83,6 +105,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!');
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,6 +119,8 @@ public function enableAutoDeployment(Server $server, Site $site): HtmxResponse
|
||||
|
||||
public function disableAutoDeployment(Server $server, Site $site): HtmxResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
if ($site->isAutoDeployment()) {
|
||||
try {
|
||||
$site->disableAutoDeployment();
|
||||
|
@ -11,6 +11,8 @@ class ConsoleController extends Controller
|
||||
{
|
||||
public function index(Server $server): View
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
return view('console.index', [
|
||||
'server' => $server,
|
||||
]);
|
||||
@ -18,6 +20,8 @@ public function index(Server $server): View
|
||||
|
||||
public function run(Server $server, Request $request)
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
$this->validate($request, [
|
||||
'user' => [
|
||||
'required',
|
||||
|
@ -4,6 +4,8 @@
|
||||
|
||||
use App\Actions\CronJob\CreateCronJob;
|
||||
use App\Actions\CronJob\DeleteCronJob;
|
||||
use App\Actions\CronJob\DisableCronJob;
|
||||
use App\Actions\CronJob\EnableCronJob;
|
||||
use App\Facades\Toast;
|
||||
use App\Helpers\HtmxResponse;
|
||||
use App\Models\CronJob;
|
||||
@ -16,6 +18,8 @@ class CronjobController extends Controller
|
||||
{
|
||||
public function index(Server $server): View
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
return view('cronjobs.index', [
|
||||
'server' => $server,
|
||||
'cronjobs' => $server->cronJobs,
|
||||
@ -24,6 +28,8 @@ public function index(Server $server): View
|
||||
|
||||
public function store(Server $server, Request $request): HtmxResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
app(CreateCronJob::class)->create($server, $request->input());
|
||||
|
||||
Toast::success('Cronjob created successfully.');
|
||||
@ -33,10 +39,34 @@ public function store(Server $server, Request $request): HtmxResponse
|
||||
|
||||
public function destroy(Server $server, CronJob $cronJob): RedirectResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
app(DeleteCronJob::class)->delete($server, $cronJob);
|
||||
|
||||
Toast::success('Cronjob deleted successfully.');
|
||||
|
||||
return back();
|
||||
}
|
||||
|
||||
public function enable(Server $server, CronJob $cronJob): RedirectResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
app(EnableCronJob::class)->enable($server, $cronJob);
|
||||
|
||||
Toast::success('Cronjob enabled successfully.');
|
||||
|
||||
return back();
|
||||
}
|
||||
|
||||
public function disable(Server $server, CronJob $cronJob): RedirectResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
app(DisableCronJob::class)->disable($server, $cronJob);
|
||||
|
||||
Toast::success('Cronjob disabled successfully.');
|
||||
|
||||
return back();
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,8 @@ class DatabaseBackupController extends Controller
|
||||
{
|
||||
public function show(Server $server, Backup $backup): View
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
return view('databases.backups', [
|
||||
'server' => $server,
|
||||
'databases' => $server->databases,
|
||||
@ -28,6 +30,8 @@ public function show(Server $server, Backup $backup): View
|
||||
|
||||
public function run(Server $server, Backup $backup): RedirectResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
app(RunBackup::class)->run($backup);
|
||||
|
||||
Toast::success('Backup is running.');
|
||||
@ -37,6 +41,8 @@ public function run(Server $server, Backup $backup): RedirectResponse
|
||||
|
||||
public function store(Server $server, Request $request): HtmxResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
app(CreateBackup::class)->create('database', $server, $request->input());
|
||||
|
||||
Toast::success('Backup created successfully.');
|
||||
@ -46,6 +52,8 @@ public function store(Server $server, Request $request): HtmxResponse
|
||||
|
||||
public function destroy(Server $server, Backup $backup): RedirectResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
$backup->delete();
|
||||
|
||||
Toast::success('Backup deleted successfully.');
|
||||
@ -55,6 +63,8 @@ public function destroy(Server $server, Backup $backup): RedirectResponse
|
||||
|
||||
public function restore(Server $server, Backup $backup, BackupFile $backupFile, Request $request): HtmxResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
app(RestoreBackup::class)->restore($backupFile, $request->input());
|
||||
|
||||
Toast::success('Backup restored successfully.');
|
||||
@ -64,8 +74,17 @@ public function restore(Server $server, Backup $backup, BackupFile $backupFile,
|
||||
|
||||
public function destroyFile(Server $server, Backup $backup, BackupFile $backupFile): RedirectResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
$backupFile->delete();
|
||||
|
||||
$backupFile
|
||||
->backup
|
||||
->storage
|
||||
->provider()
|
||||
->ssh($server)
|
||||
->delete($backupFile->storagePath());
|
||||
|
||||
Toast::success('Backup file deleted successfully.');
|
||||
|
||||
return back();
|
||||
|
@ -17,6 +17,8 @@ class DatabaseController extends Controller
|
||||
{
|
||||
public function index(Server $server): View
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
return view('databases.index', [
|
||||
'server' => $server,
|
||||
'databases' => $server->databases,
|
||||
@ -27,6 +29,8 @@ public function index(Server $server): View
|
||||
|
||||
public function store(Server $server, Request $request): HtmxResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
$database = app(CreateDatabase::class)->create($server, $request->input());
|
||||
|
||||
if ($request->input('user')) {
|
||||
@ -40,6 +44,8 @@ public function store(Server $server, Request $request): HtmxResponse
|
||||
|
||||
public function destroy(Server $server, Database $database): RedirectResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
app(DeleteDatabase::class)->delete($server, $database);
|
||||
|
||||
Toast::success('Database deleted successfully.');
|
||||
|
@ -16,6 +16,8 @@ class DatabaseUserController extends Controller
|
||||
{
|
||||
public function store(Server $server, Request $request): HtmxResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
$database = app(CreateDatabaseUser::class)->create($server, $request->input());
|
||||
|
||||
if ($request->input('user')) {
|
||||
@ -29,6 +31,8 @@ public function store(Server $server, Request $request): HtmxResponse
|
||||
|
||||
public function destroy(Server $server, DatabaseUser $databaseUser): RedirectResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
app(DeleteDatabaseUser::class)->delete($server, $databaseUser);
|
||||
|
||||
Toast::success('User deleted successfully.');
|
||||
@ -38,6 +42,8 @@ public function destroy(Server $server, DatabaseUser $databaseUser): RedirectRes
|
||||
|
||||
public function password(Server $server, DatabaseUser $databaseUser): RedirectResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
return back()->with([
|
||||
'password' => $databaseUser->password,
|
||||
]);
|
||||
@ -45,6 +51,8 @@ public function password(Server $server, DatabaseUser $databaseUser): RedirectRe
|
||||
|
||||
public function link(Server $server, DatabaseUser $databaseUser, Request $request): HtmxResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
app(LinkUser::class)->link($databaseUser, $request->input());
|
||||
|
||||
Toast::success('Database linked successfully.');
|
||||
|
@ -16,6 +16,8 @@ class FirewallController extends Controller
|
||||
{
|
||||
public function index(Server $server): View
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
return view('firewall.index', [
|
||||
'server' => $server,
|
||||
'rules' => $server->firewallRules,
|
||||
@ -24,6 +26,8 @@ public function index(Server $server): View
|
||||
|
||||
public function store(Server $server, Request $request): HtmxResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
app(CreateRule::class)->create($server, $request->input());
|
||||
|
||||
Toast::success('Firewall rule created!');
|
||||
@ -33,6 +37,8 @@ public function store(Server $server, Request $request): HtmxResponse
|
||||
|
||||
public function destroy(Server $server, FirewallRule $firewallRule): RedirectResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
app(DeleteRule::class)->delete($server, $firewallRule);
|
||||
|
||||
Toast::success('Firewall rule deleted!');
|
||||
|
50
app/Http/Controllers/MetricController.php
Normal file
50
app/Http/Controllers/MetricController.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?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->authorize('manage', $server);
|
||||
|
||||
$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->authorize('manage', $server);
|
||||
|
||||
$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
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
if (! $server->monitoring()) {
|
||||
abort(404, 'Monitoring service is not installed on this server');
|
||||
}
|
||||
}
|
||||
}
|
@ -20,6 +20,8 @@ class PHPController extends Controller
|
||||
{
|
||||
public function index(Server $server): View
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
return view('php.index', [
|
||||
'server' => $server,
|
||||
'phps' => $server->services()->where('type', 'php')->get(),
|
||||
@ -29,6 +31,8 @@ public function index(Server $server): View
|
||||
|
||||
public function install(Server $server, Request $request): HtmxResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
try {
|
||||
app(InstallNewPHP::class)->install($server, $request->input());
|
||||
|
||||
@ -42,6 +46,8 @@ public function install(Server $server, Request $request): HtmxResponse
|
||||
|
||||
public function installExtension(Server $server, Request $request): HtmxResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
app(InstallPHPExtension::class)->install($server, $request->input());
|
||||
|
||||
Toast::success('PHP extension is being installed! Check the logs');
|
||||
@ -51,6 +57,8 @@ public function installExtension(Server $server, Request $request): HtmxResponse
|
||||
|
||||
public function defaultCli(Server $server, Request $request): HtmxResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
app(ChangeDefaultCli::class)->change($server, $request->input());
|
||||
|
||||
Toast::success('Default PHP CLI is being changed!');
|
||||
@ -60,6 +68,8 @@ public function defaultCli(Server $server, Request $request): HtmxResponse
|
||||
|
||||
public function getIni(Server $server, Request $request): RedirectResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
$ini = app(GetPHPIni::class)->getIni($server, $request->input());
|
||||
|
||||
return back()->with('ini', $ini);
|
||||
@ -67,9 +77,11 @@ public function getIni(Server $server, Request $request): RedirectResponse
|
||||
|
||||
public function updateIni(Server $server, Request $request): RedirectResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
app(UpdatePHPIni::class)->update($server, $request->input());
|
||||
|
||||
Toast::success('PHP ini updated!');
|
||||
Toast::success(__('PHP ini (:type) updated!', ['type' => $request->input('type')]));
|
||||
|
||||
return back()->with([
|
||||
'ini' => $request->input('ini'),
|
||||
@ -78,6 +90,8 @@ public function updateIni(Server $server, Request $request): RedirectResponse
|
||||
|
||||
public function uninstall(Server $server, Request $request): RedirectResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
app(UninstallPHP::class)->uninstall($server, $request->input());
|
||||
|
||||
Toast::success('PHP is being uninstalled!');
|
||||
|
@ -1,11 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Settings;
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Actions\User\UpdateUserPassword;
|
||||
use App\Actions\User\UpdateUserProfileInformation;
|
||||
use App\Facades\Toast;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
@ -14,7 +13,7 @@ class ProfileController extends Controller
|
||||
{
|
||||
public function index(): View
|
||||
{
|
||||
return view('settings.profile.index');
|
||||
return view('profile.index');
|
||||
}
|
||||
|
||||
public function info(Request $request): RedirectResponse
|
@ -19,6 +19,8 @@ class QueueController extends Controller
|
||||
{
|
||||
public function index(Server $server, Site $site): View
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
return view('queues.index', [
|
||||
'server' => $server,
|
||||
'site' => $site,
|
||||
@ -28,6 +30,8 @@ public function index(Server $server, Site $site): View
|
||||
|
||||
public function store(Server $server, Site $site, Request $request): HtmxResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
app(CreateQueue::class)->create($site, $request->input());
|
||||
|
||||
Toast::success('Queue is being created.');
|
||||
@ -37,6 +41,8 @@ public function store(Server $server, Site $site, Request $request): HtmxRespons
|
||||
|
||||
public function action(Server $server, Site $site, Queue $queue, string $action): HtmxResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
app(ManageQueue::class)->{$action}($queue);
|
||||
|
||||
Toast::success('Queue is about to '.$action);
|
||||
@ -46,6 +52,8 @@ public function action(Server $server, Site $site, Queue $queue, string $action)
|
||||
|
||||
public function destroy(Server $server, Site $site, Queue $queue): RedirectResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
app(DeleteQueue::class)->delete($queue);
|
||||
|
||||
Toast::success('Queue is being deleted.');
|
||||
@ -55,6 +63,8 @@ public function destroy(Server $server, Site $site, Queue $queue): RedirectRespo
|
||||
|
||||
public function logs(Server $server, Site $site, Queue $queue): RedirectResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
return back()->with('content', app(GetQueueLogs::class)->getLogs($queue));
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,8 @@ class SSHKeyController extends Controller
|
||||
{
|
||||
public function index(Server $server): View
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
return view('server-ssh-keys.index', [
|
||||
'server' => $server,
|
||||
'keys' => $server->sshKeys,
|
||||
@ -25,7 +27,9 @@ public function index(Server $server): View
|
||||
|
||||
public function store(Server $server, Request $request): HtmxResponse
|
||||
{
|
||||
/** @var \App\Models\SshKey $key */
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
/** @var SshKey $key */
|
||||
$key = app(CreateSshKey::class)->create(
|
||||
$request->user(),
|
||||
$request->input()
|
||||
@ -38,6 +42,8 @@ public function store(Server $server, Request $request): HtmxResponse
|
||||
|
||||
public function destroy(Server $server, SshKey $sshKey): RedirectResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
app(DeleteKeyFromServer::class)->delete($server, $sshKey);
|
||||
|
||||
Toast::success('SSH Key has been deleted.');
|
||||
@ -47,6 +53,8 @@ public function destroy(Server $server, SshKey $sshKey): RedirectResponse
|
||||
|
||||
public function deploy(Server $server, Request $request): HtmxResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
app(DeployKeyToServer::class)->deploy(
|
||||
$request->user(),
|
||||
$server,
|
||||
|
@ -17,6 +17,8 @@ class SSLController extends Controller
|
||||
{
|
||||
public function index(Server $server, Site $site): View
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
return view('ssls.index', [
|
||||
'server' => $server,
|
||||
'site' => $site,
|
||||
@ -26,6 +28,8 @@ public function index(Server $server, Site $site): View
|
||||
|
||||
public function store(Server $server, Site $site, Request $request): HtmxResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
app(CreateSSL::class)->create($site, $request->input());
|
||||
|
||||
Toast::success('SSL certificate is being created.');
|
||||
@ -35,6 +39,8 @@ public function store(Server $server, Site $site, Request $request): HtmxRespons
|
||||
|
||||
public function destroy(Server $server, Site $site, Ssl $ssl): RedirectResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
app(DeleteSSL::class)->delete($ssl);
|
||||
|
||||
Toast::success('SSL certificate has been deleted.');
|
||||
|
111
app/Http/Controllers/ScriptController.php
Normal file
111
app/Http/Controllers/ScriptController.php
Normal file
@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Actions\Script\CreateScript;
|
||||
use App\Actions\Script\EditScript;
|
||||
use App\Actions\Script\ExecuteScript;
|
||||
use App\Facades\Toast;
|
||||
use App\Helpers\HtmxResponse;
|
||||
use App\Models\Script;
|
||||
use App\Models\ScriptExecution;
|
||||
use App\Models\Server;
|
||||
use App\Models\User;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ScriptController extends Controller
|
||||
{
|
||||
public function index(Request $request): View
|
||||
{
|
||||
$this->authorize('viewAny', Script::class);
|
||||
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
$data = [
|
||||
'scripts' => $user->scripts,
|
||||
];
|
||||
|
||||
if ($request->has('edit')) {
|
||||
$data['editScript'] = $user->scripts()->findOrFail($request->input('edit'));
|
||||
}
|
||||
|
||||
if ($request->has('execute')) {
|
||||
$data['executeScript'] = $user->scripts()->findOrFail($request->input('execute'));
|
||||
}
|
||||
|
||||
return view('scripts.index', $data);
|
||||
}
|
||||
|
||||
public function show(Script $script): View
|
||||
{
|
||||
$this->authorize('view', $script);
|
||||
|
||||
return view('scripts.show', [
|
||||
'script' => $script,
|
||||
'executions' => $script->executions()->latest()->paginate(20),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request): HtmxResponse
|
||||
{
|
||||
$this->authorize('create', Script::class);
|
||||
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
app(CreateScript::class)->create($user, $request->input());
|
||||
|
||||
Toast::success('Script created.');
|
||||
|
||||
return htmx()->redirect(route('scripts.index'));
|
||||
}
|
||||
|
||||
public function edit(Request $request, Script $script): HtmxResponse
|
||||
{
|
||||
$this->authorize('update', $script);
|
||||
|
||||
app(EditScript::class)->edit($script, $request->input());
|
||||
|
||||
Toast::success('Script updated.');
|
||||
|
||||
return htmx()->redirect(route('scripts.index'));
|
||||
}
|
||||
|
||||
public function execute(Script $script, Request $request): HtmxResponse
|
||||
{
|
||||
$this->validate($request, [
|
||||
'server' => 'required|exists:servers,id',
|
||||
]);
|
||||
|
||||
$server = Server::findOrFail($request->input('server'));
|
||||
|
||||
$this->authorize('execute', [$script, $server]);
|
||||
|
||||
app(ExecuteScript::class)->execute($script, $server, $request->input());
|
||||
|
||||
Toast::success('Executing the script...');
|
||||
|
||||
return htmx()->redirect(route('scripts.show', $script));
|
||||
}
|
||||
|
||||
public function delete(Script $script): RedirectResponse
|
||||
{
|
||||
$this->authorize('delete', $script);
|
||||
|
||||
$script->delete();
|
||||
|
||||
Toast::success('Script deleted.');
|
||||
|
||||
return redirect()->route('scripts.index');
|
||||
}
|
||||
|
||||
public function log(Script $script, ScriptExecution $execution): RedirectResponse
|
||||
{
|
||||
$this->authorize('view', $script);
|
||||
|
||||
return back()->with('content', $execution->serverLog?->getContent());
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Models\Site;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
@ -23,10 +24,22 @@ public function search(Request $request): JsonResponse
|
||||
$query->where('name', 'like', '%'.$request->input('q').'%')
|
||||
->orWhere('ip', 'like', '%'.$request->input('q').'%');
|
||||
})
|
||||
->whereHas('project', function (Builder $projectQuery) {
|
||||
$projectQuery->whereHas('users', function (Builder $userQuery) {
|
||||
$userQuery->where('user_id', auth()->user()->id);
|
||||
});
|
||||
})
|
||||
->get();
|
||||
|
||||
$sites = Site::query()
|
||||
->where('domain', 'like', '%'.$request->input('q').'%')
|
||||
->whereHas('server', function (Builder $serverQuery) {
|
||||
$serverQuery->whereHas('project', function (Builder $projectQuery) {
|
||||
$projectQuery->whereHas('users', function (Builder $userQuery) {
|
||||
$userQuery->where('user_id', auth()->user()->id);
|
||||
});
|
||||
});
|
||||
})
|
||||
->get();
|
||||
|
||||
$result = [];
|
||||
|
@ -19,6 +19,9 @@ public function index(): View
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
$this->authorize('viewAny', [Server::class, $user->currentProject]);
|
||||
|
||||
$servers = $user->currentProject->servers()->orderByDesc('created_at')->get();
|
||||
|
||||
return view('servers.index', compact('servers'));
|
||||
@ -26,8 +29,15 @@ public function index(): View
|
||||
|
||||
public function create(Request $request): View
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
$this->authorize('create', [Server::class, $user->currentProject]);
|
||||
|
||||
$provider = $request->query('provider', old('provider', \App\Enums\ServerProvider::CUSTOM));
|
||||
$serverProviders = ServerProvider::query()->where('provider', $provider)->get();
|
||||
$serverProviders = ServerProvider::getByProjectId(auth()->user()->current_project_id)
|
||||
->where('provider', $provider)
|
||||
->get();
|
||||
|
||||
return view('servers.create', [
|
||||
'serverProviders' => $serverProviders,
|
||||
@ -40,8 +50,13 @@ public function create(Request $request): View
|
||||
*/
|
||||
public function store(Request $request): HtmxResponse
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
$this->authorize('create', [Server::class, $user->currentProject]);
|
||||
|
||||
$server = app(CreateServer::class)->create(
|
||||
$request->user(),
|
||||
$user,
|
||||
$request->input()
|
||||
);
|
||||
|
||||
@ -52,14 +67,17 @@ public function store(Request $request): HtmxResponse
|
||||
|
||||
public function show(Server $server): View
|
||||
{
|
||||
$this->authorize('view', $server);
|
||||
|
||||
return view('servers.show', [
|
||||
'server' => $server,
|
||||
'logs' => $server->logs()->latest()->limit(10)->get(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function delete(Server $server): RedirectResponse
|
||||
{
|
||||
$this->authorize('delete', $server);
|
||||
|
||||
$server->delete();
|
||||
|
||||
Toast::success('Server deleted successfully.');
|
||||
|
@ -2,22 +2,30 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Actions\Server\CreateServerLog;
|
||||
use App\Facades\Toast;
|
||||
use App\Models\Server;
|
||||
use App\Models\ServerLog;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ServerLogController extends Controller
|
||||
{
|
||||
public function index(Server $server): View
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
return view('server-logs.index', [
|
||||
'server' => $server,
|
||||
'pageTitle' => __('Vito Logs'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(Server $server, ServerLog $serverLog): RedirectResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
if ($server->id != $serverLog->server_id) {
|
||||
abort(404);
|
||||
}
|
||||
@ -26,4 +34,37 @@ public function show(Server $server, ServerLog $serverLog): RedirectResponse
|
||||
'content' => $serverLog->getContent(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function remote(Server $server): View
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
return view('server-logs.remote-logs', [
|
||||
'server' => $server,
|
||||
'remote' => true,
|
||||
'pageTitle' => __('Remote Logs'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Server $server, Request $request): \App\Helpers\HtmxResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
app(CreateServerLog::class)->create($server, $request->input());
|
||||
|
||||
Toast::success('Log added successfully.');
|
||||
|
||||
return htmx()->redirect(route('servers.logs.remote', ['server' => $server]));
|
||||
}
|
||||
|
||||
public function destroy(Server $server, ServerLog $serverLog): RedirectResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
$serverLog->delete();
|
||||
|
||||
Toast::success('Remote log deleted successfully.');
|
||||
|
||||
return redirect()->route('servers.logs.remote', ['server' => $server]);
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
use App\Actions\Server\EditServer;
|
||||
use App\Actions\Server\RebootServer;
|
||||
use App\Actions\Server\Update;
|
||||
use App\Facades\Toast;
|
||||
use App\Helpers\HtmxResponse;
|
||||
use App\Models\Server;
|
||||
@ -15,11 +16,15 @@ class ServerSettingController extends Controller
|
||||
{
|
||||
public function index(Server $server): View
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
return view('server-settings.index', compact('server'));
|
||||
}
|
||||
|
||||
public function checkConnection(Server $server): RedirectResponse|HtmxResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
$oldStatus = $server->status;
|
||||
|
||||
$server = $server->checkConnection();
|
||||
@ -41,6 +46,8 @@ public function checkConnection(Server $server): RedirectResponse|HtmxResponse
|
||||
|
||||
public function reboot(Server $server): HtmxResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
app(RebootServer::class)->reboot($server);
|
||||
|
||||
Toast::info('Server is rebooting.');
|
||||
@ -50,10 +57,32 @@ public function reboot(Server $server): HtmxResponse
|
||||
|
||||
public function edit(Request $request, Server $server): RedirectResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
app(EditServer::class)->edit($server, $request->input());
|
||||
|
||||
Toast::success('Server updated.');
|
||||
|
||||
return back();
|
||||
}
|
||||
|
||||
public function checkUpdates(Server $server): RedirectResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
$server->checkForUpdates();
|
||||
|
||||
return back();
|
||||
}
|
||||
|
||||
public function update(Server $server): HtmxResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
app(Update::class)->update($server);
|
||||
|
||||
Toast::info('Updating server. This may take a few minutes.');
|
||||
|
||||
return htmx()->back();
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,8 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Actions\Service\Create;
|
||||
use App\Actions\Service\Install;
|
||||
use App\Actions\Service\Uninstall;
|
||||
use App\Facades\Toast;
|
||||
use App\Helpers\HtmxResponse;
|
||||
use App\Models\Server;
|
||||
@ -15,6 +16,8 @@ class ServiceController extends Controller
|
||||
{
|
||||
public function index(Server $server): View
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
return view('services.index', [
|
||||
'server' => $server,
|
||||
'services' => $server->services,
|
||||
@ -23,6 +26,8 @@ public function index(Server $server): View
|
||||
|
||||
public function start(Server $server, Service $service): RedirectResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
$service->start();
|
||||
|
||||
Toast::success('Service is being started!');
|
||||
@ -32,6 +37,8 @@ public function start(Server $server, Service $service): RedirectResponse
|
||||
|
||||
public function stop(Server $server, Service $service): RedirectResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
$service->stop();
|
||||
|
||||
Toast::success('Service is being stopped!');
|
||||
@ -41,6 +48,8 @@ public function stop(Server $server, Service $service): RedirectResponse
|
||||
|
||||
public function restart(Server $server, Service $service): RedirectResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
$service->restart();
|
||||
|
||||
Toast::success('Service is being restarted!');
|
||||
@ -50,6 +59,8 @@ public function restart(Server $server, Service $service): RedirectResponse
|
||||
|
||||
public function enable(Server $server, Service $service): RedirectResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
$service->enable();
|
||||
|
||||
Toast::success('Service is being enabled!');
|
||||
@ -59,6 +70,8 @@ public function enable(Server $server, Service $service): RedirectResponse
|
||||
|
||||
public function disable(Server $server, Service $service): RedirectResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
$service->disable();
|
||||
|
||||
Toast::success('Service is being disabled!');
|
||||
@ -68,7 +81,20 @@ public function disable(Server $server, Service $service): RedirectResponse
|
||||
|
||||
public function install(Server $server, Request $request): HtmxResponse
|
||||
{
|
||||
app(Create::class)->create($server, $request->input());
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
app(Install::class)->install($server, $request->input());
|
||||
|
||||
Toast::success('Service is being installed!');
|
||||
|
||||
return htmx()->back();
|
||||
}
|
||||
|
||||
public function uninstall(Server $server, Service $service): HtmxResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
app(Uninstall::class)->uninstall($service);
|
||||
|
||||
Toast::success('Service is being uninstalled!');
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace App\Http\Controllers\Settings;
|
||||
|
||||
use App\Actions\NotificationChannels\AddChannel;
|
||||
use App\Actions\NotificationChannels\EditChannel;
|
||||
use App\Facades\Toast;
|
||||
use App\Helpers\HtmxResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
@ -13,11 +14,17 @@
|
||||
|
||||
class NotificationChannelController extends Controller
|
||||
{
|
||||
public function index(): View
|
||||
public function index(Request $request): View
|
||||
{
|
||||
return view('settings.notification-channels.index', [
|
||||
'channels' => NotificationChannel::query()->latest()->get(),
|
||||
]);
|
||||
$data = [
|
||||
'channels' => NotificationChannel::getByProjectId(auth()->user()->current_project_id)->get(),
|
||||
];
|
||||
|
||||
if ($request->has('edit')) {
|
||||
$data['editChannel'] = NotificationChannel::find($request->input('edit'));
|
||||
}
|
||||
|
||||
return view('settings.notification-channels.index', $data);
|
||||
}
|
||||
|
||||
public function add(Request $request): HtmxResponse
|
||||
@ -29,7 +36,20 @@ public function add(Request $request): HtmxResponse
|
||||
|
||||
Toast::success('Channel added successfully');
|
||||
|
||||
return htmx()->redirect(route('notification-channels'));
|
||||
return htmx()->redirect(route('settings.notification-channels'));
|
||||
}
|
||||
|
||||
public function update(NotificationChannel $notificationChannel, Request $request): HtmxResponse
|
||||
{
|
||||
app(EditChannel::class)->edit(
|
||||
$notificationChannel,
|
||||
$request->user(),
|
||||
$request->input(),
|
||||
);
|
||||
|
||||
Toast::success('Channel updated.');
|
||||
|
||||
return htmx()->redirect(route('settings.notification-channels'));
|
||||
}
|
||||
|
||||
public function delete(int $id): RedirectResponse
|
||||
@ -40,6 +60,6 @@ public function delete(int $id): RedirectResponse
|
||||
|
||||
Toast::success('Channel deleted successfully');
|
||||
|
||||
return redirect()->route('notification-channels');
|
||||
return redirect()->route('settings.notification-channels');
|
||||
}
|
||||
}
|
||||
|
@ -19,40 +19,42 @@ class ProjectController extends Controller
|
||||
{
|
||||
public function index(): View
|
||||
{
|
||||
$this->authorize('viewAny', Project::class);
|
||||
|
||||
return view('settings.projects.index', [
|
||||
'projects' => auth()->user()->projects,
|
||||
'projects' => Project::all(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function create(Request $request): HtmxResponse
|
||||
{
|
||||
$this->authorize('create', Project::class);
|
||||
|
||||
app(CreateProject::class)->create($request->user(), $request->input());
|
||||
|
||||
Toast::success('Project created.');
|
||||
|
||||
return htmx()->redirect(route('projects'));
|
||||
return htmx()->redirect(route('settings.projects'));
|
||||
}
|
||||
|
||||
public function update(Request $request, Project $project): HtmxResponse
|
||||
{
|
||||
/** @var Project $project */
|
||||
$project = $request->user()->projects()->findOrFail($project->id);
|
||||
$this->authorize('update', $project);
|
||||
|
||||
app(UpdateProject::class)->update($project, $request->input());
|
||||
|
||||
Toast::success('Project updated.');
|
||||
|
||||
return htmx()->redirect(route('projects'));
|
||||
return htmx()->redirect(route('settings.projects'));
|
||||
}
|
||||
|
||||
public function delete(Project $project): RedirectResponse
|
||||
{
|
||||
$this->authorize('delete', $project);
|
||||
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
/** @var Project $project */
|
||||
$project = $user->projects()->findOrFail($project->id);
|
||||
|
||||
try {
|
||||
app(DeleteProject::class)->delete($user, $project);
|
||||
} catch (ValidationException $e) {
|
||||
@ -66,7 +68,7 @@ public function delete(Project $project): RedirectResponse
|
||||
return back();
|
||||
}
|
||||
|
||||
public function switch($projectId): RedirectResponse
|
||||
public function switch(Request $request, $projectId): RedirectResponse
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
@ -74,9 +76,16 @@ public function switch($projectId): RedirectResponse
|
||||
/** @var Project $project */
|
||||
$project = $user->projects()->findOrFail($projectId);
|
||||
|
||||
$this->authorize('view', $project);
|
||||
|
||||
$user->current_project_id = $project->id;
|
||||
$user->save();
|
||||
|
||||
// check if the referer is settings/*
|
||||
if (str_contains($request->headers->get('referer'), 'settings')) {
|
||||
return redirect()->to($request->headers->get('referer'));
|
||||
}
|
||||
|
||||
return redirect()->route('servers');
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ public function add(Request $request): HtmxResponse
|
||||
|
||||
Toast::success('SSH Key added');
|
||||
|
||||
return htmx()->redirect(route('ssh-keys'));
|
||||
return htmx()->redirect(route('settings.ssh-keys'));
|
||||
}
|
||||
|
||||
public function delete(int $id): RedirectResponse
|
||||
@ -40,6 +40,6 @@ public function delete(int $id): RedirectResponse
|
||||
|
||||
Toast::success('SSH Key deleted');
|
||||
|
||||
return redirect()->route('ssh-keys');
|
||||
return redirect()->route('settings.ssh-keys');
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
use App\Actions\ServerProvider\CreateServerProvider;
|
||||
use App\Actions\ServerProvider\DeleteServerProvider;
|
||||
use App\Actions\ServerProvider\EditServerProvider;
|
||||
use App\Facades\Toast;
|
||||
use App\Helpers\HtmxResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
@ -14,11 +15,17 @@
|
||||
|
||||
class ServerProviderController extends Controller
|
||||
{
|
||||
public function index(): View
|
||||
public function index(Request $request): View
|
||||
{
|
||||
return view('settings.server-providers.index', [
|
||||
'providers' => auth()->user()->serverProviders,
|
||||
]);
|
||||
$data = [
|
||||
'providers' => ServerProvider::getByProjectId(auth()->user()->current_project_id)->get(),
|
||||
];
|
||||
|
||||
if ($request->has('edit')) {
|
||||
$data['editProvider'] = ServerProvider::find($request->input('edit'));
|
||||
}
|
||||
|
||||
return view('settings.server-providers.index', $data);
|
||||
}
|
||||
|
||||
public function connect(Request $request): HtmxResponse
|
||||
@ -30,7 +37,20 @@ public function connect(Request $request): HtmxResponse
|
||||
|
||||
Toast::success('Server provider connected.');
|
||||
|
||||
return htmx()->redirect(route('server-providers'));
|
||||
return htmx()->redirect(route('settings.server-providers'));
|
||||
}
|
||||
|
||||
public function update(ServerProvider $serverProvider, Request $request): HtmxResponse
|
||||
{
|
||||
app(EditServerProvider::class)->edit(
|
||||
$serverProvider,
|
||||
$request->user(),
|
||||
$request->input(),
|
||||
);
|
||||
|
||||
Toast::success('Provider updated.');
|
||||
|
||||
return htmx()->redirect(route('settings.server-providers'));
|
||||
}
|
||||
|
||||
public function delete(ServerProvider $serverProvider): RedirectResponse
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
use App\Actions\SourceControl\ConnectSourceControl;
|
||||
use App\Actions\SourceControl\DeleteSourceControl;
|
||||
use App\Actions\SourceControl\EditSourceControl;
|
||||
use App\Facades\Toast;
|
||||
use App\Helpers\HtmxResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
@ -14,22 +15,42 @@
|
||||
|
||||
class SourceControlController extends Controller
|
||||
{
|
||||
public function index(): View
|
||||
public function index(Request $request): View
|
||||
{
|
||||
return view('settings.source-controls.index', [
|
||||
'sourceControls' => SourceControl::query()->orderByDesc('id')->get(),
|
||||
]);
|
||||
$data = [
|
||||
'sourceControls' => SourceControl::getByProjectId(auth()->user()->current_project_id)->get(),
|
||||
];
|
||||
|
||||
if ($request->has('edit')) {
|
||||
$data['editSourceControl'] = SourceControl::find($request->input('edit'));
|
||||
}
|
||||
|
||||
return view('settings.source-controls.index', $data);
|
||||
}
|
||||
|
||||
public function connect(Request $request): HtmxResponse
|
||||
{
|
||||
app(ConnectSourceControl::class)->connect(
|
||||
$request->user(),
|
||||
$request->input(),
|
||||
);
|
||||
|
||||
Toast::success('Source control connected.');
|
||||
|
||||
return htmx()->redirect(route('source-controls'));
|
||||
return htmx()->redirect(route('settings.source-controls'));
|
||||
}
|
||||
|
||||
public function update(SourceControl $sourceControl, Request $request): HtmxResponse
|
||||
{
|
||||
app(EditSourceControl::class)->edit(
|
||||
$sourceControl,
|
||||
$request->user(),
|
||||
$request->input(),
|
||||
);
|
||||
|
||||
Toast::success('Source control updated.');
|
||||
|
||||
return htmx()->redirect(route('settings.source-controls'));
|
||||
}
|
||||
|
||||
public function delete(SourceControl $sourceControl): RedirectResponse
|
||||
@ -44,6 +65,6 @@ public function delete(SourceControl $sourceControl): RedirectResponse
|
||||
|
||||
Toast::success('Source control deleted.');
|
||||
|
||||
return redirect()->route('source-controls');
|
||||
return redirect()->route('settings.source-controls');
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
use App\Actions\StorageProvider\CreateStorageProvider;
|
||||
use App\Actions\StorageProvider\DeleteStorageProvider;
|
||||
use App\Actions\StorageProvider\EditStorageProvider;
|
||||
use App\Facades\Toast;
|
||||
use App\Helpers\HtmxResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
@ -14,11 +15,17 @@
|
||||
|
||||
class StorageProviderController extends Controller
|
||||
{
|
||||
public function index(): View
|
||||
public function index(Request $request): View
|
||||
{
|
||||
return view('settings.storage-providers.index', [
|
||||
'providers' => auth()->user()->storageProviders,
|
||||
]);
|
||||
$data = [
|
||||
'providers' => StorageProvider::getByProjectId(auth()->user()->current_project_id)->get(),
|
||||
];
|
||||
|
||||
if ($request->has('edit')) {
|
||||
$data['editProvider'] = StorageProvider::find($request->input('edit'));
|
||||
}
|
||||
|
||||
return view('settings.storage-providers.index', $data);
|
||||
}
|
||||
|
||||
public function connect(Request $request): HtmxResponse
|
||||
@ -30,7 +37,20 @@ public function connect(Request $request): HtmxResponse
|
||||
|
||||
Toast::success('Storage provider connected.');
|
||||
|
||||
return htmx()->redirect(route('storage-providers'));
|
||||
return htmx()->redirect(route('settings.storage-providers'));
|
||||
}
|
||||
|
||||
public function update(StorageProvider $storageProvider, Request $request): HtmxResponse
|
||||
{
|
||||
app(EditStorageProvider::class)->edit(
|
||||
$storageProvider,
|
||||
$request->user(),
|
||||
$request->input(),
|
||||
);
|
||||
|
||||
Toast::success('Provider updated.');
|
||||
|
||||
return htmx()->redirect(route('settings.storage-providers'));
|
||||
}
|
||||
|
||||
public function delete(StorageProvider $storageProvider): RedirectResponse
|
||||
|
90
app/Http/Controllers/Settings/TagController.php
Normal file
90
app/Http/Controllers/Settings/TagController.php
Normal file
@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Settings;
|
||||
|
||||
use App\Actions\Tag\AttachTag;
|
||||
use App\Actions\Tag\CreateTag;
|
||||
use App\Actions\Tag\DeleteTag;
|
||||
use App\Actions\Tag\DetachTag;
|
||||
use App\Actions\Tag\EditTag;
|
||||
use App\Facades\Toast;
|
||||
use App\Helpers\HtmxResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Tag;
|
||||
use App\Models\User;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TagController extends Controller
|
||||
{
|
||||
public function index(Request $request): View
|
||||
{
|
||||
$data = [
|
||||
'tags' => Tag::getByProjectId(auth()->user()->current_project_id)->get(),
|
||||
];
|
||||
|
||||
if ($request->has('edit')) {
|
||||
$data['editTag'] = Tag::find($request->input('edit'));
|
||||
}
|
||||
|
||||
return view('settings.tags.index', $data);
|
||||
}
|
||||
|
||||
public function create(Request $request): HtmxResponse
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = $request->user();
|
||||
|
||||
app(CreateTag::class)->create(
|
||||
$user,
|
||||
$request->input(),
|
||||
);
|
||||
|
||||
Toast::success('Tag created.');
|
||||
|
||||
return htmx()->redirect(route('settings.tags'));
|
||||
}
|
||||
|
||||
public function update(Tag $tag, Request $request): HtmxResponse
|
||||
{
|
||||
app(EditTag::class)->edit(
|
||||
$tag,
|
||||
$request->input(),
|
||||
);
|
||||
|
||||
Toast::success('Tag updated.');
|
||||
|
||||
return htmx()->redirect(route('settings.tags'));
|
||||
}
|
||||
|
||||
public function attach(Request $request): RedirectResponse
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = $request->user();
|
||||
|
||||
app(AttachTag::class)->attach($user, $request->input());
|
||||
|
||||
return back()->with([
|
||||
'status' => 'tag-created',
|
||||
]);
|
||||
}
|
||||
|
||||
public function detach(Request $request, Tag $tag): RedirectResponse
|
||||
{
|
||||
app(DetachTag::class)->detach($tag, $request->input());
|
||||
|
||||
return back()->with([
|
||||
'status' => 'tag-detached',
|
||||
]);
|
||||
}
|
||||
|
||||
public function delete(Tag $tag): RedirectResponse
|
||||
{
|
||||
app(DeleteTag::class)->delete($tag);
|
||||
|
||||
Toast::success('Tag deleted.');
|
||||
|
||||
return back();
|
||||
}
|
||||
}
|
91
app/Http/Controllers/Settings/UserController.php
Normal file
91
app/Http/Controllers/Settings/UserController.php
Normal file
@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Settings;
|
||||
|
||||
use App\Actions\User\CreateUser;
|
||||
use App\Actions\User\UpdateUser;
|
||||
use App\Facades\Toast;
|
||||
use App\Helpers\HtmxResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Project;
|
||||
use App\Models\User;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
public function index(): View
|
||||
{
|
||||
$users = User::query()->paginate(20);
|
||||
|
||||
return view('settings.users.index', compact('users'));
|
||||
}
|
||||
|
||||
public function store(Request $request): HtmxResponse
|
||||
{
|
||||
$user = app(CreateUser::class)->create($request->input());
|
||||
|
||||
return htmx()->redirect(route('settings.users.show', $user));
|
||||
}
|
||||
|
||||
public function show(User $user): View
|
||||
{
|
||||
return view('settings.users.show', [
|
||||
'user' => $user,
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(User $user, Request $request): RedirectResponse
|
||||
{
|
||||
app(UpdateUser::class)->update($user, $request->input());
|
||||
|
||||
Toast::success('User updated successfully');
|
||||
|
||||
return back();
|
||||
}
|
||||
|
||||
public function updateProjects(User $user, Request $request): HtmxResponse
|
||||
{
|
||||
$this->validate($request, [
|
||||
'projects.*' => [
|
||||
'required',
|
||||
Rule::exists('projects', 'id'),
|
||||
],
|
||||
]);
|
||||
|
||||
$user->projects()->sync($request->projects);
|
||||
|
||||
if ($user->currentProject && ! $user->projects->contains($user->currentProject)) {
|
||||
$user->current_project_id = null;
|
||||
$user->save();
|
||||
}
|
||||
|
||||
/** @var Project $firstProject */
|
||||
$firstProject = $user->projects->first();
|
||||
if (! $user->currentProject && $firstProject) {
|
||||
$user->current_project_id = $firstProject->id;
|
||||
$user->save();
|
||||
}
|
||||
|
||||
Toast::success('Projects updated successfully');
|
||||
|
||||
return htmx()->redirect(route('settings.users.show', $user));
|
||||
}
|
||||
|
||||
public function destroy(User $user): RedirectResponse
|
||||
{
|
||||
if ($user->is(request()->user())) {
|
||||
Toast::error('You cannot delete your own account');
|
||||
|
||||
return back();
|
||||
}
|
||||
|
||||
$user->delete();
|
||||
|
||||
Toast::success('User deleted successfully');
|
||||
|
||||
return redirect()->route('settings.users.index');
|
||||
}
|
||||
}
|
@ -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;
|
||||
@ -18,6 +19,8 @@ class SiteController extends Controller
|
||||
{
|
||||
public function index(Server $server): View
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
return view('sites.index', [
|
||||
'server' => $server,
|
||||
'sites' => $server->sites()->orderByDesc('id')->get(),
|
||||
@ -26,6 +29,8 @@ public function index(Server $server): View
|
||||
|
||||
public function store(Server $server, Request $request): HtmxResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
$site = app(CreateSite::class)->create($server, $request->input());
|
||||
|
||||
Toast::success('Site created');
|
||||
@ -35,6 +40,8 @@ public function store(Server $server, Request $request): HtmxResponse
|
||||
|
||||
public function create(Server $server): View
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
return view('sites.create', [
|
||||
'server' => $server,
|
||||
'type' => old('type', request()->query('type', SiteType::LARAVEL)),
|
||||
@ -42,16 +49,46 @@ 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
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
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
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
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
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
app(DeleteSite::class)->delete($site);
|
||||
|
||||
Toast::success('Site is being deleted');
|
||||
|
@ -10,9 +10,12 @@ class SiteLogController extends Controller
|
||||
{
|
||||
public function index(Server $server, Site $site): View
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
return view('site-logs.index', [
|
||||
'server' => $server,
|
||||
'site' => $site,
|
||||
'pageTitle' => __('Vito Logs'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,13 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Actions\Site\UpdateAliases;
|
||||
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;
|
||||
@ -16,6 +19,8 @@ class SiteSettingController extends Controller
|
||||
{
|
||||
public function index(Server $server, Site $site): View
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
return view('site-settings.index', [
|
||||
'server' => $server,
|
||||
'site' => $site,
|
||||
@ -24,17 +29,26 @@ 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));
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
/** @var Webserver $handler */
|
||||
$handler = $server->webserver()->handler();
|
||||
|
||||
return back()->with('vhost', $handler->getVHost($site));
|
||||
}
|
||||
|
||||
public function updateVhost(Server $server, Site $site, Request $request): RedirectResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
$this->validate($request, [
|
||||
'vhost' => 'required|string',
|
||||
]);
|
||||
|
||||
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) {
|
||||
@ -46,6 +60,8 @@ public function updateVhost(Server $server, Site $site, Request $request): Redir
|
||||
|
||||
public function updatePHPVersion(Server $server, Site $site, Request $request): HtmxResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
$this->validate($request, [
|
||||
'version' => [
|
||||
'required',
|
||||
@ -63,4 +79,26 @@ public function updatePHPVersion(Server $server, Site $site, Request $request):
|
||||
|
||||
return htmx()->back();
|
||||
}
|
||||
|
||||
public function updateSourceControl(Server $server, Site $site, Request $request): HtmxResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
app(UpdateSourceControl::class)->update($site, $request->input());
|
||||
|
||||
Toast::success('Source control updated successfully!');
|
||||
|
||||
return htmx()->back();
|
||||
}
|
||||
|
||||
public function updateAliases(Server $server, Site $site, Request $request): HtmxResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
app(UpdateAliases::class)->update($site, $request->input());
|
||||
|
||||
Toast::success('Aliases updated successfully!');
|
||||
|
||||
return htmx()->back();
|
||||
}
|
||||
}
|
||||
|
@ -68,5 +68,7 @@ class Kernel extends HttpKernel
|
||||
'server-is-ready' => ServerIsReadyMiddleware::class,
|
||||
'handle-ssh-errors' => HandleSSHErrors::class,
|
||||
'select-current-project' => SelectCurrentProject::class,
|
||||
'is-admin' => \App\Http\Middleware\IsAdmin::class,
|
||||
'must-have-current-project' => \App\Http\Middleware\MustHaveCurrentProject::class,
|
||||
];
|
||||
}
|
||||
|
@ -14,17 +14,17 @@ class HandleSSHErrors
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
$res = $next($request);
|
||||
if ($res instanceof Response && $res->exception) {
|
||||
if ($res->exception instanceof SSHConnectionError || $res->exception instanceof SSHCommandError) {
|
||||
Toast::error($res->exception->getMessage());
|
||||
// if ($res instanceof Response && $res->exception) {
|
||||
// if ($res->exception instanceof SSHConnectionError || $res->exception instanceof SSHCommandError) {
|
||||
// Toast::error($res->exception->getMessage());
|
||||
|
||||
if ($request->hasHeader('HX-Request')) {
|
||||
return htmx()->back();
|
||||
}
|
||||
// if ($request->hasHeader('HX-Request')) {
|
||||
// return htmx()->back();
|
||||
// }
|
||||
|
||||
return back();
|
||||
}
|
||||
}
|
||||
// return back();
|
||||
// }
|
||||
// }
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user