Compare commits

..

No commits in common. "1.x" and "1.0.0" have entirely different histories.
1.x ... 1.0.0

548 changed files with 25962 additions and 15988 deletions

View File

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

View File

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

View File

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

View File

@ -1,11 +0,0 @@
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

View File

@ -0,0 +1,12 @@
---
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/

View File

@ -1,35 +0,0 @@
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

View File

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

2
.gitignore vendored
View File

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

View File

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

View File

@ -1,6 +1,6 @@
<p align="center"> <p align="center">
<img src="https://github.com/vitodeploy/vito/assets/61919774/8060fded-58e3-4d58-b58b-5b717b0718e9" alt="VitoDeploy> <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>
<p align="center"> <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> <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> </p>
@ -34,9 +34,10 @@ ## Useful Links
- [Documentation](https://vitodeploy.com) - [Documentation](https://vitodeploy.com)
- [Install on Server](https://vitodeploy.com/introduction/installation.html#install-on-vps-recommended) - [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) - [Install via Docker](https://vitodeploy.com/introduction/installation.html#install-via-docker)
- [Roadmap](https://github.com/orgs/vitodeploy/projects/5) - [Feedbacks](https://vitodeploy.featurebase.app)
- [Video Demo](https://youtu.be/AbmUOBDOc28) - [Roadmap](https://vitodeploy.featurebase.app/roadmap)
- [Discord](https://discord.gg/uZeeHZZnm5) - [Video Demo](https://youtu.be/rLRHIyEfON8)
- [Discord](https://discord.gg/dcUWA5DV)
- [Contribution](/CONTRIBUTING.md) - [Contribution](/CONTRIBUTING.md)
- [Security](/SECURITY.md) - [Security](/SECURITY.md)
@ -49,6 +50,7 @@ ## Credits
- Alpinejs - Alpinejs
- HTMX - HTMX
- Vite - Vite
- Toastr by CodeSeven
- Prettier - Prettier
- Postcss - Postcss
- Flowbite - Flowbite

View File

@ -2,7 +2,6 @@
namespace App\Actions\CronJob; namespace App\Actions\CronJob;
use App\Enums\CronjobStatus;
use App\Models\CronJob; use App\Models\CronJob;
use App\Models\Server; use App\Models\Server;
@ -11,9 +10,7 @@ class DeleteCronJob
public function delete(Server $server, CronJob $cronJob): void public function delete(Server $server, CronJob $cronJob): void
{ {
$user = $cronJob->user; $user = $cronJob->user;
$cronJob->status = CronjobStatus::DELETING;
$cronJob->save();
$server->cron()->update($cronJob->user, CronJob::crontab($server, $user));
$cronJob->delete(); $cronJob->delete();
$server->cron()->update($cronJob->user, CronJob::crontab($server, $user));
} }
} }

View File

@ -1,20 +0,0 @@
<?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();
}
}

View File

@ -1,20 +0,0 @@
<?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();
}
}

View File

@ -22,9 +22,7 @@ public function create(Server $server, array $input): Database
'server_id' => $server->id, 'server_id' => $server->id,
'name' => $input['name'], 'name' => $input['name'],
]); ]);
/** @var \App\SSH\Services\Database\Database */ $server->database()->handler()->create($database->name);
$databaseHandler = $server->database()->handler();
$databaseHandler->create($database->name);
$database->status = DatabaseStatus::READY; $database->status = DatabaseStatus::READY;
$database->save(); $database->save();

View File

@ -1,150 +0,0 @@
<?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();
}
}
}

View File

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

View File

@ -19,7 +19,6 @@ public function add(User $user, array $input): void
'user_id' => $user->id, 'user_id' => $user->id,
'provider' => $input['provider'], 'provider' => $input['provider'],
'label' => $input['label'], 'label' => $input['label'],
'project_id' => isset($input['global']) && $input['global'] ? null : $user->current_project_id,
]); ]);
$this->validateType($channel, $input); $this->validateType($channel, $input);
$channel->data = $channel->provider()->createData($input); $channel->data = $channel->provider()->createData($input);

View File

@ -1,34 +0,0 @@
<?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();
}
}

View File

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

View File

@ -2,11 +2,7 @@
namespace App\Actions\PHP; namespace App\Actions\PHP;
use App\Enums\PHPIniType;
use App\Models\Server; use App\Models\Server;
use App\SSH\Services\PHP\PHP;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
class GetPHPIni class GetPHPIni
@ -18,10 +14,7 @@ public function getIni(Server $server, array $input): string
$php = $server->php($input['version']); $php = $server->php($input['version']);
try { try {
/** @var PHP $handler */ return $php->handler()->getPHPIni();
$handler = $php->handler();
return $handler->getPHPIni($input['type']);
} catch (\Throwable $e) { } catch (\Throwable $e) {
throw ValidationException::withMessages( throw ValidationException::withMessages(
['ini' => $e->getMessage()] ['ini' => $e->getMessage()]
@ -31,13 +24,6 @@ public function getIni(Server $server, array $input): string
public function validate(Server $server, array $input): void 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())) { if (! isset($input['version']) || ! in_array($input['version'], $server->installedPHPVersions())) {
throw ValidationException::withMessages( throw ValidationException::withMessages(
['version' => __('This version is not installed')] ['version' => __('This version is not installed')]

View File

@ -4,7 +4,6 @@
use App\Models\Server; use App\Models\Server;
use App\Models\Service; use App\Models\Service;
use App\SSH\Services\PHP\PHP;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
@ -24,9 +23,7 @@ public function install(Server $server, array $input): Service
$service->save(); $service->save();
dispatch(function () use ($service, $input) { dispatch(function () use ($service, $input) {
/** @var PHP $handler */ $service->handler()->installExtension($input['extension']);
$handler = $service->handler();
$handler->installExtension($input['extension']);
})->catch(function () use ($service, $input) { })->catch(function () use ($service, $input) {
$service->refresh(); $service->refresh();
$typeData = $service->type_data; $typeData = $service->type_data;

View File

@ -2,13 +2,10 @@
namespace App\Actions\PHP; namespace App\Actions\PHP;
use App\Enums\PHPIniType;
use App\Models\Server; use App\Models\Server;
use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
use Throwable; use Throwable;
@ -25,19 +22,19 @@ public function update(Server $server, array $input): void
$tmpName = Str::random(10).strtotime('now'); $tmpName = Str::random(10).strtotime('now');
try { try {
/** @var FilesystemAdapter $storageDisk */ /** @var \Illuminate\Filesystem\FilesystemAdapter $storageDisk */
$storageDisk = Storage::disk('local'); $storageDisk = Storage::disk('local');
$storageDisk->put($tmpName, $input['ini']); $storageDisk->put($tmpName, $input['ini']);
$service->server->ssh('root')->upload( $service->server->ssh('root')->upload(
$storageDisk->path($tmpName), $storageDisk->path($tmpName),
sprintf('/etc/php/%s/%s/php.ini', $service->version, $input['type']) "/etc/php/$service->version/cli/php.ini"
); );
$this->deleteTempFile($tmpName); $this->deleteTempFile($tmpName);
} catch (Throwable) { } catch (Throwable) {
$this->deleteTempFile($tmpName); $this->deleteTempFile($tmpName);
throw ValidationException::withMessages([ throw ValidationException::withMessages([
'ini' => __("Couldn't update php.ini (:type) file!", ['type' => $input['type']]), 'ini' => __("Couldn't update php.ini file!"),
]); ]);
} }
@ -59,10 +56,6 @@ public function validate(Server $server, array $input): void
'string', 'string',
], ],
'version' => 'required|string', 'version' => 'required|string',
'type' => [
'required',
Rule::in([PHPIniType::CLI, PHPIniType::FPM]),
],
])->validate(); ])->validate();
if (! in_array($input['version'], $server->installedPHPVersions())) { if (! in_array($input['version'], $server->installedPHPVersions())) {

View File

@ -10,31 +10,26 @@ class CreateProject
{ {
public function create(User $user, array $input): Project public function create(User $user, array $input): Project
{ {
if (isset($input['name'])) { $this->validate($user, $input);
$input['name'] = strtolower($input['name']);
}
$this->validate($input);
$project = new Project([ $project = new Project([
'user_id' => $user->id,
'name' => $input['name'], 'name' => $input['name'],
]); ]);
$project->save(); $project->save();
$project->users()->attach($user);
return $project; return $project;
} }
private function validate(array $input): void private function validate(User $user, array $input): void
{ {
Validator::make($input, [ Validator::make($input, [
'name' => [ 'name' => [
'required', 'required',
'string', 'string',
'max:255', 'max:255',
'unique:projects,name', 'unique:projects,name,NULL,id,user_id,'.$user->id,
], ],
])->validate(); ])->validate();
} }

View File

@ -17,15 +17,11 @@ public function delete(User $user, Project $project): void
} }
if ($user->current_project_id == $project->id) { if ($user->current_project_id == $project->id) {
throw ValidationException::withMessages([
'project' => __('Cannot delete your current project.'),
]);
}
/** @var Project $randomProject */ /** @var Project $randomProject */
$randomProject = $user->projects()->where('project_id', '!=', $project->id)->first(); $randomProject = $user->projects()->where('id', '!=', $project->id)->first();
$user->current_project_id = $randomProject->id; $user->current_project_id = $randomProject->id;
$user->save(); $user->save();
}
$project->delete(); $project->delete();
} }

View File

@ -10,10 +10,6 @@ class UpdateProject
{ {
public function update(Project $project, array $input): Project public function update(Project $project, array $input): Project
{ {
if (isset($input['name'])) {
$input['name'] = strtolower($input['name']);
}
$this->validate($project, $input); $this->validate($project, $input);
$project->name = $input['name']; $project->name = $input['name'];

View File

@ -6,7 +6,6 @@
use App\Enums\SslType; use App\Enums\SslType;
use App\Models\Site; use App\Models\Site;
use App\Models\Ssl; use App\Models\Ssl;
use App\SSH\Services\Webserver\Webserver;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
@ -28,23 +27,16 @@ public function create(Site $site, array $input): void
'expires_at' => $input['type'] === SslType::LETSENCRYPT ? now()->addMonths(3) : $input['expires_at'], 'expires_at' => $input['type'] === SslType::LETSENCRYPT ? now()->addMonths(3) : $input['expires_at'],
'status' => SslStatus::CREATING, 'status' => SslStatus::CREATING,
]); ]);
$ssl->domains = [$site->domain];
if (isset($input['aliases']) && $input['aliases']) {
$ssl->domains = array_merge($ssl->domains, $site->aliases);
}
$ssl->save(); $ssl->save();
dispatch(function () use ($site, $ssl) { dispatch(function () use ($site, $ssl) {
/** @var Webserver $webserver */ $site->server->webserver()->handler()->setupSSL($ssl);
$webserver = $site->server->webserver()->handler();
$webserver->setupSSL($ssl);
$ssl->status = SslStatus::CREATED; $ssl->status = SslStatus::CREATED;
$ssl->save(); $ssl->save();
$site->type()->edit(); $site->type()->edit();
})->catch(function () use ($ssl) { })->catch(function () use ($ssl) {
$ssl->status = SslStatus::FAILED; $ssl->delete();
$ssl->save(); });
})->onConnection('ssh');
} }
/** /**

View File

@ -1,32 +0,0 @@
<?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();
}
}

View File

@ -1,28 +0,0 @@
<?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();
}
}

View File

@ -1,62 +0,0 @@
<?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();
}
}

View File

@ -2,7 +2,6 @@
namespace App\Actions\Server; namespace App\Actions\Server;
use App\Enums\ServerStatus;
use App\Facades\Notifier; use App\Facades\Notifier;
use App\Models\Server; use App\Models\Server;
use App\Notifications\ServerDisconnected; use App\Notifications\ServerDisconnected;
@ -16,12 +15,12 @@ public function check(Server $server): Server
try { try {
$server->ssh()->connect(); $server->ssh()->connect();
$server->refresh(); $server->refresh();
if (in_array($status, [ServerStatus::DISCONNECTED, ServerStatus::UPDATING])) { if ($status == 'disconnected') {
$server->status = ServerStatus::READY; $server->status = 'ready';
$server->save(); $server->save();
} }
} catch (Throwable) { } catch (Throwable) {
$server->status = ServerStatus::DISCONNECTED; $server->status = 'disconnected';
$server->save(); $server->save();
Notifier::send($server, new ServerDisconnected($server)); Notifier::send($server, new ServerDisconnected($server));
} }

View File

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

View File

@ -1,25 +0,0 @@
<?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');
}
}

View File

@ -38,7 +38,6 @@ public function create(User $user, array $input): ServerProvider
$serverProvider->profile = $input['name']; $serverProvider->profile = $input['name'];
$serverProvider->provider = $input['provider']; $serverProvider->provider = $input['provider'];
$serverProvider->credentials = $provider->credentialData($input); $serverProvider->credentials = $provider->credentialData($input);
$serverProvider->project_id = isset($input['global']) && $input['global'] ? null : $user->current_project_id;
$serverProvider->save(); $serverProvider->save();
return $serverProvider; return $serverProvider;

View File

@ -1,34 +0,0 @@
<?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();
}
}

View File

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

View File

@ -1,28 +0,0 @@
<?php
namespace App\Actions\Service;
use App\Enums\ServiceStatus;
use App\Models\Service;
use Illuminate\Support\Facades\Validator;
class Uninstall
{
public function uninstall(Service $service): void
{
Validator::make([
'service' => $service->id,
], $service->handler()->deletionRules())->validate();
$service->status = ServiceStatus::UNINSTALLING;
$service->save();
dispatch(function () use ($service) {
$service->handler()->uninstall();
$service->delete();
})->catch(function () use ($service) {
$service->status = ServiceStatus::FAILED;
$service->save();
})->onConnection('ssh');
}
}

View File

@ -3,8 +3,6 @@
namespace App\Actions\Site; namespace App\Actions\Site;
use App\Enums\SiteStatus; use App\Enums\SiteStatus;
use App\Exceptions\RepositoryNotFound;
use App\Exceptions\RepositoryPermissionDenied;
use App\Exceptions\SourceControlIsNotConnected; use App\Exceptions\SourceControlIsNotConnected;
use App\Facades\Notifier; use App\Facades\Notifier;
use App\Models\Server; use App\Models\Server;
@ -22,6 +20,7 @@ class CreateSite
{ {
/** /**
* @throws SourceControlIsNotConnected * @throws SourceControlIsNotConnected
* @throws ValidationException
*/ */
public function create(Server $server, array $input): Site public function create(Server $server, array $input): Site
{ {
@ -33,7 +32,7 @@ public function create(Server $server, array $input): Site
'server_id' => $server->id, 'server_id' => $server->id,
'type' => $input['type'], 'type' => $input['type'],
'domain' => $input['domain'], 'domain' => $input['domain'],
'aliases' => $input['aliases'] ?? [], 'aliases' => isset($input['alias']) ? [$input['alias']] : [],
'path' => '/home/'.$server->getSshUser().'/'.$input['domain'], 'path' => '/home/'.$server->getSshUser().'/'.$input['domain'],
'status' => SiteStatus::INSTALLING, 'status' => SiteStatus::INSTALLING,
]); ]);
@ -48,15 +47,7 @@ public function create(Server $server, array $input): Site
} }
} catch (SourceControlIsNotConnected) { } catch (SourceControlIsNotConnected) {
throw ValidationException::withMessages([ throw ValidationException::withMessages([
'source_control' => 'Source control is not connected', 'source_control' => __('Source control is not connected'),
]);
} catch (RepositoryPermissionDenied) {
throw ValidationException::withMessages([
'repository' => 'You do not have permission to access this repository',
]);
} catch (RepositoryNotFound) {
throw ValidationException::withMessages([
'repository' => 'Repository not found',
]); ]);
} }
@ -115,7 +106,7 @@ private function validateInputs(Server $server, array $input): void
return $query->where('server_id', $server->id); return $query->where('server_id', $server->id);
}), }),
], ],
'aliases.*' => [ 'alias' => [
new DomainRule(), new DomainRule(),
], ],
]; ];

View File

@ -6,7 +6,6 @@
use App\Exceptions\DeploymentScriptIsEmptyException; use App\Exceptions\DeploymentScriptIsEmptyException;
use App\Exceptions\SourceControlIsNotConnected; use App\Exceptions\SourceControlIsNotConnected;
use App\Models\Deployment; use App\Models\Deployment;
use App\Models\ServerLog;
use App\Models\Site; use App\Models\Site;
class Deploy class Deploy
@ -30,7 +29,7 @@ public function run(Site $site): Deployment
'deployment_script_id' => $site->deploymentScript->id, 'deployment_script_id' => $site->deploymentScript->id,
'status' => DeploymentStatus::DEPLOYING, 'status' => DeploymentStatus::DEPLOYING,
]); ]);
$lastCommit = $site->sourceControl()?->provider()?->getLastCommit($site->repository, $site->branch); $lastCommit = $site->sourceControl()->provider()->getLastCommit($site->repository, $site->branch);
if ($lastCommit) { if ($lastCommit) {
$deployment->commit_id = $lastCommit['commit_id']; $deployment->commit_id = $lastCommit['commit_id'];
$deployment->commit_data = $lastCommit['commit_data']; $deployment->commit_data = $lastCommit['commit_data'];
@ -38,19 +37,9 @@ public function run(Site $site): Deployment
$deployment->save(); $deployment->save();
dispatch(function () use ($site, $deployment) { dispatch(function () use ($site, $deployment) {
/** @var ServerLog $log */ $log = $site->server->os()->runScript($site->path, $site->deploymentScript->content, $site->id);
$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->status = DeploymentStatus::FINISHED;
$deployment->log_id = $log->id;
$deployment->save(); $deployment->save();
})->catch(function () use ($deployment) { })->catch(function () use ($deployment) {
$deployment->status = DeploymentStatus::FAILED; $deployment->status = DeploymentStatus::FAILED;

View File

@ -1,33 +0,0 @@
<?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();
}
}

View File

@ -2,14 +2,10 @@
namespace App\Actions\Site; namespace App\Actions\Site;
use App\Exceptions\SSHUploadFailed;
use App\Models\Site; use App\Models\Site;
class UpdateEnv class UpdateEnv
{ {
/**
* @throws SSHUploadFailed
*/
public function update(Site $site, array $input): void public function update(Site $site, array $input): void
{ {
$site->server->os()->editFile( $site->server->os()->editFile(

View File

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

View File

@ -3,7 +3,6 @@
namespace App\Actions\SourceControl; namespace App\Actions\SourceControl;
use App\Models\SourceControl; use App\Models\SourceControl;
use App\Models\User;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;
@ -11,7 +10,7 @@
class ConnectSourceControl class ConnectSourceControl
{ {
public function connect(User $user, array $input): void public function connect(array $input): void
{ {
$this->validate($input); $this->validate($input);
@ -19,7 +18,6 @@ public function connect(User $user, array $input): void
'provider' => $input['provider'], 'provider' => $input['provider'],
'profile' => $input['name'], 'profile' => $input['name'],
'url' => Arr::has($input, 'url') ? $input['url'] : null, 'url' => Arr::has($input, 'url') ? $input['url'] : null,
'project_id' => isset($input['global']) && $input['global'] ? null : $user->current_project_id,
]); ]);
$this->validateProvider($sourceControl, $input); $this->validateProvider($sourceControl, $input);

View File

@ -1,54 +0,0 @@
<?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();
}
}

View File

@ -21,7 +21,6 @@ public function create(User $user, array $input): void
'user_id' => $user->id, 'user_id' => $user->id,
'provider' => $input['provider'], 'provider' => $input['provider'],
'profile' => $input['name'], 'profile' => $input['name'],
'project_id' => isset($input['global']) && $input['global'] ? null : $user->current_project_id,
]); ]);
$this->validateProvider($input, $storageProvider->provider()->validationRules()); $this->validateProvider($input, $storageProvider->provider()->validationRules());

View File

@ -1,34 +0,0 @@
<?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();
}
}

View File

@ -1,58 +0,0 @@
<?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();
}
}

View File

@ -1,49 +0,0 @@
<?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();
}
}

View File

@ -1,15 +0,0 @@
<?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();
}
}

View File

@ -1,36 +0,0 @@
<?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();
}
}

View File

@ -1,38 +0,0 @@
<?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();
}
}

View File

@ -1,40 +0,0 @@
<?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();
}
}

View File

@ -1,48 +0,0 @@
<?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();
}
}

View File

@ -7,7 +7,7 @@
class CreateUserCommand extends Command class CreateUserCommand extends Command
{ {
protected $signature = 'user:create {name} {email} {password} {--role=admin}'; protected $signature = 'user:create {name} {email} {password}';
protected $description = 'Create a new user'; protected $description = 'Create a new user';
@ -25,7 +25,6 @@ public function handle(): void
'name' => $this->argument('name'), 'name' => $this->argument('name'),
'email' => $this->argument('email'), 'email' => $this->argument('email'),
'password' => bcrypt($this->argument('password')), 'password' => bcrypt($this->argument('password')),
'role' => $this->option('role'),
]); ]);
$this->info('User created!'); $this->info('User created!');

View File

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

View File

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

View File

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

View File

@ -9,10 +9,4 @@ final class CronjobStatus
const READY = 'ready'; const READY = 'ready';
const DELETING = 'deleting'; const DELETING = 'deleting';
const ENABLING = 'enabling';
const DISABLING = 'disabling';
const DISABLED = 'disabled';
} }

View File

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

View File

@ -9,6 +9,4 @@ final class OperatingSystem
const UBUNTU20 = 'ubuntu_20'; const UBUNTU20 = 'ubuntu_20';
const UBUNTU22 = 'ubuntu_22'; const UBUNTU22 = 'ubuntu_22';
const UBUNTU24 = 'ubuntu_24';
} }

View File

@ -1,10 +0,0 @@
<?php
namespace App\Enums;
final class PHPIniType
{
const CLI = 'cli';
const FPM = 'fpm';
}

View File

@ -1,12 +0,0 @@
<?php
namespace App\Enums;
final class ScriptExecutionStatus
{
const EXECUTING = 'executing';
const COMPLETED = 'completed';
const FAILED = 'failed';
}

View File

@ -11,6 +11,4 @@ final class ServerStatus
const INSTALLATION_FAILED = 'installation_failed'; const INSTALLATION_FAILED = 'installation_failed';
const DISCONNECTED = 'disconnected'; const DISCONNECTED = 'disconnected';
const UPDATING = 'updating';
} }

View File

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

View File

@ -7,10 +7,4 @@ final class StorageProvider
const DROPBOX = 'dropbox'; const DROPBOX = 'dropbox';
const FTP = 'ftp'; const FTP = 'ftp';
const LOCAL = 'local';
const S3 = 's3';
const WASABI = 'wasabi';
} }

View File

@ -1,10 +0,0 @@
<?php
namespace App\Enums;
final class UserRole
{
const USER = 'user';
const ADMIN = 'admin';
}

View File

@ -4,4 +4,6 @@
use Exception; use Exception;
class DeploymentScriptIsEmptyException extends Exception {} class DeploymentScriptIsEmptyException extends Exception
{
}

View File

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

View File

@ -1,8 +0,0 @@
<?php
namespace App\Exceptions;
class SSHUploadFailed extends SSHError
{
//
}

View File

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

View File

@ -4,4 +4,6 @@
use Exception; use Exception;
class SourceControlIsNotConnected extends Exception {} class SourceControlIsNotConnected extends Exception
{
}

View File

@ -1,30 +0,0 @@
<?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;
}
}

View File

@ -3,7 +3,6 @@
namespace App\Facades; namespace App\Facades;
use App\Models\Server; use App\Models\Server;
use App\Models\ServerLog;
use App\Support\Testing\SSHFake; use App\Support\Testing\SSHFake;
use Illuminate\Support\Facades\Facade as FacadeAlias; use Illuminate\Support\Facades\Facade as FacadeAlias;
@ -11,13 +10,11 @@
* Class SSH * Class SSH
* *
* @method static init(Server $server, string $asUser = null) * @method static init(Server $server, string $asUser = null)
* @method static setLog(?ServerLog $log) * @method static setLog(string $logType, int $siteId = null)
* @method static connect() * @method static connect()
* @method static string exec(string $command, string $log = '', int $siteId = null, ?bool $stream = false) * @method static string exec(string $command, string $log = '', int $siteId = null)
* @method static string assertExecuted(array|string $commands) * @method static string assertExecuted(array|string $commands)
* @method static string assertExecutedContains(string $command) * @method static string assertExecutedContains(string $command)
* @method static string assertFileUploaded(string $toPath, ?string $content = null)
* @method static string getUploadedLocalPath()
* @method static disconnect() * @method static disconnect()
*/ */
class SSH extends FacadeAlias class SSH extends FacadeAlias

View File

@ -1,37 +0,0 @@
<?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);
}
}

View File

@ -50,9 +50,14 @@ public function init(Server $server, ?string $asUser = null): self
return $this; return $this;
} }
public function setLog(ServerLog $log): self public function setLog(string $logType, $siteId = null): self
{ {
$this->log = $log; $this->log = $this->server->logs()->create([
'site_id' => $siteId,
'name' => $this->server->id.'-'.strtotime('now').'-'.$logType.'.log',
'type' => $logType,
'disk' => config('core.logs_disk'),
]);
return $this; return $this;
} }
@ -91,14 +96,12 @@ public function connect(bool $sftp = false): void
* @throws SSHCommandError * @throws SSHCommandError
* @throws SSHConnectionError * @throws SSHConnectionError
*/ */
public function exec(string $command, string $log = '', ?int $siteId = null, ?bool $stream = false): string public function exec(string|array $commands, string $log = '', ?int $siteId = null): string
{ {
if (! $this->log && $log) { if ($log) {
$this->log = ServerLog::make($this->server, $log); $this->setLog($log, $siteId);
if ($siteId) { } else {
$this->log->forSite($siteId); $this->log = null;
}
$this->log->save();
} }
try { try {
@ -109,34 +112,18 @@ public function exec(string $command, string $log = '', ?int $siteId = null, ?bo
throw new SSHConnectionError($e->getMessage()); throw new SSHConnectionError($e->getMessage());
} }
if (! is_array($commands)) {
$commands = [$commands];
}
try { try {
if ($this->asUser) { $result = '';
$command = 'sudo su - '.$this->asUser.' -c '.'"'.addslashes($command).'"'; foreach ($commands as $command) {
$result .= $this->executeCommand($command);
} }
$this->connection->setTimeout(0); return $result;
if ($stream) {
$this->connection->exec($command, function ($output) {
$this->log?->write($output);
echo $output;
ob_flush();
flush();
});
return '';
} else {
$output = $this->connection->exec($command);
$this->log?->write($output);
if ($this->connection->getExitStatus() !== 0 || Str::contains($output, 'VITO_SSH_ERROR')) {
throw new SSHCommandError('SSH command failed with an error', $this->connection->getExitStatus());
}
return $output;
}
} catch (Throwable $e) { } catch (Throwable $e) {
throw $e;
throw new SSHCommandError($e->getMessage()); throw new SSHCommandError($e->getMessage());
} }
} }
@ -154,6 +141,28 @@ public function upload(string $local, string $remote): void
$this->connection->put($remote, $local, SFTP::SOURCE_LOCAL_FILE); $this->connection->put($remote, $local, SFTP::SOURCE_LOCAL_FILE);
} }
/**
* @throws Exception
*/
protected function executeCommand(string $command): string
{
if ($this->asUser) {
$command = 'sudo su - '.$this->asUser.' -c '.'"'.addslashes($command).'"';
}
$this->connection->setTimeout(0);
$output = $this->connection->exec($command);
$this->log?->write($output);
if (Str::contains($output, 'VITO_SSH_ERROR')) {
throw new Exception('SSH command failed with an error');
}
return $output;
}
/** /**
* @throws Exception * @throws Exception
*/ */

View File

@ -1,36 +0,0 @@
<?php
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Models\Server;
use App\Models\Service;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class AgentController extends Controller
{
public function __invoke(Request $request, Server $server, int $id): JsonResponse
{
$validated = $this->validate($request, [
'load' => 'required|numeric',
'memory_total' => 'required|numeric',
'memory_used' => 'required|numeric',
'memory_free' => 'required|numeric',
'disk_total' => 'required|numeric',
'disk_used' => 'required|numeric',
'disk_free' => 'required|numeric',
]);
/** @var Service $service */
$service = $server->services()->findOrFail($id);
if ($request->header('secret') !== $service->handler()->data()['secret']) {
return response()->json(['error' => 'Unauthorized'], 401);
}
$server->metrics()->create(array_merge($validated, ['server_id' => $server->id]));
return response()->json();
}
}

View File

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

View File

@ -7,10 +7,7 @@
use App\Actions\Site\UpdateDeploymentScript; use App\Actions\Site\UpdateDeploymentScript;
use App\Actions\Site\UpdateEnv; use App\Actions\Site\UpdateEnv;
use App\Exceptions\DeploymentScriptIsEmptyException; use App\Exceptions\DeploymentScriptIsEmptyException;
use App\Exceptions\RepositoryNotFound;
use App\Exceptions\RepositoryPermissionDenied;
use App\Exceptions\SourceControlIsNotConnected; use App\Exceptions\SourceControlIsNotConnected;
use App\Exceptions\SSHUploadFailed;
use App\Facades\Toast; use App\Facades\Toast;
use App\Helpers\HtmxResponse; use App\Helpers\HtmxResponse;
use App\Models\Deployment; use App\Models\Deployment;
@ -23,20 +20,16 @@ class ApplicationController extends Controller
{ {
public function deploy(Server $server, Site $site): HtmxResponse public function deploy(Server $server, Site $site): HtmxResponse
{ {
$this->authorize('manage', $server);
try { try {
app(Deploy::class)->run($site); app(Deploy::class)->run($site);
Toast::success('Deployment started!'); Toast::success('Deployment started!');
} catch (SourceControlIsNotConnected) { } catch (SourceControlIsNotConnected $e) {
Toast::error('Source control is not connected. Check site\'s settings.'); Toast::error($e->getMessage());
return htmx()->redirect(route('source-controls'));
} catch (DeploymentScriptIsEmptyException) { } catch (DeploymentScriptIsEmptyException) {
Toast::error('Deployment script is empty!'); Toast::error('Deployment script is empty!');
} catch (RepositoryPermissionDenied) {
Toast::error('You do not have permission to access this repository!');
} catch (RepositoryNotFound) {
Toast::error('Repository not found!');
} }
return htmx()->back(); return htmx()->back();
@ -44,15 +37,11 @@ public function deploy(Server $server, Site $site): HtmxResponse
public function showDeploymentLog(Server $server, Site $site, Deployment $deployment): RedirectResponse public function showDeploymentLog(Server $server, Site $site, Deployment $deployment): RedirectResponse
{ {
$this->authorize('manage', $server);
return back()->with('content', $deployment->log?->getContent()); return back()->with('content', $deployment->log?->getContent());
} }
public function updateDeploymentScript(Server $server, Site $site, Request $request): RedirectResponse public function updateDeploymentScript(Server $server, Site $site, Request $request): RedirectResponse
{ {
$this->authorize('manage', $server);
app(UpdateDeploymentScript::class)->update($site, $request->input()); app(UpdateDeploymentScript::class)->update($site, $request->input());
Toast::success('Deployment script updated!'); Toast::success('Deployment script updated!');
@ -62,8 +51,6 @@ public function updateDeploymentScript(Server $server, Site $site, Request $requ
public function updateBranch(Server $server, Site $site, Request $request): RedirectResponse public function updateBranch(Server $server, Site $site, Request $request): RedirectResponse
{ {
$this->authorize('manage', $server);
app(UpdateBranch::class)->update($site, $request->input()); app(UpdateBranch::class)->update($site, $request->input());
Toast::success('Branch updated!'); Toast::success('Branch updated!');
@ -73,29 +60,20 @@ public function updateBranch(Server $server, Site $site, Request $request): Redi
public function getEnv(Server $server, Site $site): RedirectResponse public function getEnv(Server $server, Site $site): RedirectResponse
{ {
$this->authorize('manage', $server);
return back()->with('env', $site->getEnv()); return back()->with('env', $site->getEnv());
} }
public function updateEnv(Server $server, Site $site, Request $request): RedirectResponse public function updateEnv(Server $server, Site $site, Request $request): RedirectResponse
{ {
$this->authorize('manage', $server);
try {
app(UpdateEnv::class)->update($site, $request->input()); app(UpdateEnv::class)->update($site, $request->input());
Toast::success('Env updated!'); Toast::success('Env updated!');
} catch (SSHUploadFailed) {
Toast::error('Failed to update .env file!');
}
return back(); return back();
} }
public function enableAutoDeployment(Server $server, Site $site): HtmxResponse public function enableAutoDeployment(Server $server, Site $site): HtmxResponse
{ {
$this->authorize('manage', $server);
if (! $site->isAutoDeployment()) { if (! $site->isAutoDeployment()) {
try { try {
$site->enableAutoDeployment(); $site->enableAutoDeployment();
@ -105,12 +83,6 @@ public function enableAutoDeployment(Server $server, Site $site): HtmxResponse
Toast::success('Auto deployment has been enabled.'); Toast::success('Auto deployment has been enabled.');
} catch (SourceControlIsNotConnected) { } catch (SourceControlIsNotConnected) {
Toast::error('Source control is not connected. Check site\'s settings.'); Toast::error('Source control is not connected. Check site\'s settings.');
} catch (DeploymentScriptIsEmptyException) {
Toast::error('Deployment script is empty!');
} catch (RepositoryPermissionDenied) {
Toast::error('You do not have permission to access this repository!');
} catch (RepositoryNotFound) {
Toast::error('Repository not found!');
} }
} }
@ -119,8 +91,6 @@ public function enableAutoDeployment(Server $server, Site $site): HtmxResponse
public function disableAutoDeployment(Server $server, Site $site): HtmxResponse public function disableAutoDeployment(Server $server, Site $site): HtmxResponse
{ {
$this->authorize('manage', $server);
if ($site->isAutoDeployment()) { if ($site->isAutoDeployment()) {
try { try {
$site->disableAutoDeployment(); $site->disableAutoDeployment();

View File

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

View File

@ -4,8 +4,6 @@
use App\Actions\CronJob\CreateCronJob; use App\Actions\CronJob\CreateCronJob;
use App\Actions\CronJob\DeleteCronJob; use App\Actions\CronJob\DeleteCronJob;
use App\Actions\CronJob\DisableCronJob;
use App\Actions\CronJob\EnableCronJob;
use App\Facades\Toast; use App\Facades\Toast;
use App\Helpers\HtmxResponse; use App\Helpers\HtmxResponse;
use App\Models\CronJob; use App\Models\CronJob;
@ -18,8 +16,6 @@ class CronjobController extends Controller
{ {
public function index(Server $server): View public function index(Server $server): View
{ {
$this->authorize('manage', $server);
return view('cronjobs.index', [ return view('cronjobs.index', [
'server' => $server, 'server' => $server,
'cronjobs' => $server->cronJobs, 'cronjobs' => $server->cronJobs,
@ -28,8 +24,6 @@ public function index(Server $server): View
public function store(Server $server, Request $request): HtmxResponse public function store(Server $server, Request $request): HtmxResponse
{ {
$this->authorize('manage', $server);
app(CreateCronJob::class)->create($server, $request->input()); app(CreateCronJob::class)->create($server, $request->input());
Toast::success('Cronjob created successfully.'); Toast::success('Cronjob created successfully.');
@ -39,34 +33,10 @@ public function store(Server $server, Request $request): HtmxResponse
public function destroy(Server $server, CronJob $cronJob): RedirectResponse public function destroy(Server $server, CronJob $cronJob): RedirectResponse
{ {
$this->authorize('manage', $server);
app(DeleteCronJob::class)->delete($server, $cronJob); app(DeleteCronJob::class)->delete($server, $cronJob);
Toast::success('Cronjob deleted successfully.'); Toast::success('Cronjob deleted successfully.');
return back(); 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();
}
} }

View File

@ -18,8 +18,6 @@ class DatabaseBackupController extends Controller
{ {
public function show(Server $server, Backup $backup): View public function show(Server $server, Backup $backup): View
{ {
$this->authorize('manage', $server);
return view('databases.backups', [ return view('databases.backups', [
'server' => $server, 'server' => $server,
'databases' => $server->databases, 'databases' => $server->databases,
@ -30,8 +28,6 @@ public function show(Server $server, Backup $backup): View
public function run(Server $server, Backup $backup): RedirectResponse public function run(Server $server, Backup $backup): RedirectResponse
{ {
$this->authorize('manage', $server);
app(RunBackup::class)->run($backup); app(RunBackup::class)->run($backup);
Toast::success('Backup is running.'); Toast::success('Backup is running.');
@ -41,8 +37,6 @@ public function run(Server $server, Backup $backup): RedirectResponse
public function store(Server $server, Request $request): HtmxResponse public function store(Server $server, Request $request): HtmxResponse
{ {
$this->authorize('manage', $server);
app(CreateBackup::class)->create('database', $server, $request->input()); app(CreateBackup::class)->create('database', $server, $request->input());
Toast::success('Backup created successfully.'); Toast::success('Backup created successfully.');
@ -52,8 +46,6 @@ public function store(Server $server, Request $request): HtmxResponse
public function destroy(Server $server, Backup $backup): RedirectResponse public function destroy(Server $server, Backup $backup): RedirectResponse
{ {
$this->authorize('manage', $server);
$backup->delete(); $backup->delete();
Toast::success('Backup deleted successfully.'); Toast::success('Backup deleted successfully.');
@ -63,8 +55,6 @@ public function destroy(Server $server, Backup $backup): RedirectResponse
public function restore(Server $server, Backup $backup, BackupFile $backupFile, Request $request): HtmxResponse public function restore(Server $server, Backup $backup, BackupFile $backupFile, Request $request): HtmxResponse
{ {
$this->authorize('manage', $server);
app(RestoreBackup::class)->restore($backupFile, $request->input()); app(RestoreBackup::class)->restore($backupFile, $request->input());
Toast::success('Backup restored successfully.'); Toast::success('Backup restored successfully.');
@ -74,17 +64,8 @@ public function restore(Server $server, Backup $backup, BackupFile $backupFile,
public function destroyFile(Server $server, Backup $backup, BackupFile $backupFile): RedirectResponse public function destroyFile(Server $server, Backup $backup, BackupFile $backupFile): RedirectResponse
{ {
$this->authorize('manage', $server);
$backupFile->delete(); $backupFile->delete();
$backupFile
->backup
->storage
->provider()
->ssh($server)
->delete($backupFile->storagePath());
Toast::success('Backup file deleted successfully.'); Toast::success('Backup file deleted successfully.');
return back(); return back();

View File

@ -17,8 +17,6 @@ class DatabaseController extends Controller
{ {
public function index(Server $server): View public function index(Server $server): View
{ {
$this->authorize('manage', $server);
return view('databases.index', [ return view('databases.index', [
'server' => $server, 'server' => $server,
'databases' => $server->databases, 'databases' => $server->databases,
@ -29,8 +27,6 @@ public function index(Server $server): View
public function store(Server $server, Request $request): HtmxResponse public function store(Server $server, Request $request): HtmxResponse
{ {
$this->authorize('manage', $server);
$database = app(CreateDatabase::class)->create($server, $request->input()); $database = app(CreateDatabase::class)->create($server, $request->input());
if ($request->input('user')) { if ($request->input('user')) {
@ -44,8 +40,6 @@ public function store(Server $server, Request $request): HtmxResponse
public function destroy(Server $server, Database $database): RedirectResponse public function destroy(Server $server, Database $database): RedirectResponse
{ {
$this->authorize('manage', $server);
app(DeleteDatabase::class)->delete($server, $database); app(DeleteDatabase::class)->delete($server, $database);
Toast::success('Database deleted successfully.'); Toast::success('Database deleted successfully.');

View File

@ -16,8 +16,6 @@ class DatabaseUserController extends Controller
{ {
public function store(Server $server, Request $request): HtmxResponse public function store(Server $server, Request $request): HtmxResponse
{ {
$this->authorize('manage', $server);
$database = app(CreateDatabaseUser::class)->create($server, $request->input()); $database = app(CreateDatabaseUser::class)->create($server, $request->input());
if ($request->input('user')) { if ($request->input('user')) {
@ -31,8 +29,6 @@ public function store(Server $server, Request $request): HtmxResponse
public function destroy(Server $server, DatabaseUser $databaseUser): RedirectResponse public function destroy(Server $server, DatabaseUser $databaseUser): RedirectResponse
{ {
$this->authorize('manage', $server);
app(DeleteDatabaseUser::class)->delete($server, $databaseUser); app(DeleteDatabaseUser::class)->delete($server, $databaseUser);
Toast::success('User deleted successfully.'); Toast::success('User deleted successfully.');
@ -42,8 +38,6 @@ public function destroy(Server $server, DatabaseUser $databaseUser): RedirectRes
public function password(Server $server, DatabaseUser $databaseUser): RedirectResponse public function password(Server $server, DatabaseUser $databaseUser): RedirectResponse
{ {
$this->authorize('manage', $server);
return back()->with([ return back()->with([
'password' => $databaseUser->password, 'password' => $databaseUser->password,
]); ]);
@ -51,8 +45,6 @@ public function password(Server $server, DatabaseUser $databaseUser): RedirectRe
public function link(Server $server, DatabaseUser $databaseUser, Request $request): HtmxResponse public function link(Server $server, DatabaseUser $databaseUser, Request $request): HtmxResponse
{ {
$this->authorize('manage', $server);
app(LinkUser::class)->link($databaseUser, $request->input()); app(LinkUser::class)->link($databaseUser, $request->input());
Toast::success('Database linked successfully.'); Toast::success('Database linked successfully.');

View File

@ -16,8 +16,6 @@ class FirewallController extends Controller
{ {
public function index(Server $server): View public function index(Server $server): View
{ {
$this->authorize('manage', $server);
return view('firewall.index', [ return view('firewall.index', [
'server' => $server, 'server' => $server,
'rules' => $server->firewallRules, 'rules' => $server->firewallRules,
@ -26,8 +24,6 @@ public function index(Server $server): View
public function store(Server $server, Request $request): HtmxResponse public function store(Server $server, Request $request): HtmxResponse
{ {
$this->authorize('manage', $server);
app(CreateRule::class)->create($server, $request->input()); app(CreateRule::class)->create($server, $request->input());
Toast::success('Firewall rule created!'); Toast::success('Firewall rule created!');
@ -37,8 +33,6 @@ public function store(Server $server, Request $request): HtmxResponse
public function destroy(Server $server, FirewallRule $firewallRule): RedirectResponse public function destroy(Server $server, FirewallRule $firewallRule): RedirectResponse
{ {
$this->authorize('manage', $server);
app(DeleteRule::class)->delete($server, $firewallRule); app(DeleteRule::class)->delete($server, $firewallRule);
Toast::success('Firewall rule deleted!'); Toast::success('Firewall rule deleted!');

View File

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

View File

@ -1,50 +0,0 @@
<?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');
}
}
}

View File

@ -20,8 +20,6 @@ class PHPController extends Controller
{ {
public function index(Server $server): View public function index(Server $server): View
{ {
$this->authorize('manage', $server);
return view('php.index', [ return view('php.index', [
'server' => $server, 'server' => $server,
'phps' => $server->services()->where('type', 'php')->get(), 'phps' => $server->services()->where('type', 'php')->get(),
@ -31,8 +29,6 @@ public function index(Server $server): View
public function install(Server $server, Request $request): HtmxResponse public function install(Server $server, Request $request): HtmxResponse
{ {
$this->authorize('manage', $server);
try { try {
app(InstallNewPHP::class)->install($server, $request->input()); app(InstallNewPHP::class)->install($server, $request->input());
@ -46,8 +42,6 @@ public function install(Server $server, Request $request): HtmxResponse
public function installExtension(Server $server, Request $request): HtmxResponse public function installExtension(Server $server, Request $request): HtmxResponse
{ {
$this->authorize('manage', $server);
app(InstallPHPExtension::class)->install($server, $request->input()); app(InstallPHPExtension::class)->install($server, $request->input());
Toast::success('PHP extension is being installed! Check the logs'); Toast::success('PHP extension is being installed! Check the logs');
@ -57,8 +51,6 @@ public function installExtension(Server $server, Request $request): HtmxResponse
public function defaultCli(Server $server, Request $request): HtmxResponse public function defaultCli(Server $server, Request $request): HtmxResponse
{ {
$this->authorize('manage', $server);
app(ChangeDefaultCli::class)->change($server, $request->input()); app(ChangeDefaultCli::class)->change($server, $request->input());
Toast::success('Default PHP CLI is being changed!'); Toast::success('Default PHP CLI is being changed!');
@ -68,8 +60,6 @@ public function defaultCli(Server $server, Request $request): HtmxResponse
public function getIni(Server $server, Request $request): RedirectResponse public function getIni(Server $server, Request $request): RedirectResponse
{ {
$this->authorize('manage', $server);
$ini = app(GetPHPIni::class)->getIni($server, $request->input()); $ini = app(GetPHPIni::class)->getIni($server, $request->input());
return back()->with('ini', $ini); return back()->with('ini', $ini);
@ -77,21 +67,15 @@ public function getIni(Server $server, Request $request): RedirectResponse
public function updateIni(Server $server, Request $request): RedirectResponse public function updateIni(Server $server, Request $request): RedirectResponse
{ {
$this->authorize('manage', $server);
app(UpdatePHPIni::class)->update($server, $request->input()); app(UpdatePHPIni::class)->update($server, $request->input());
Toast::success(__('PHP ini (:type) updated!', ['type' => $request->input('type')])); Toast::success('PHP ini updated!');
return back()->with([ return back();
'ini' => $request->input('ini'),
]);
} }
public function uninstall(Server $server, Request $request): RedirectResponse public function uninstall(Server $server, Request $request): RedirectResponse
{ {
$this->authorize('manage', $server);
app(UninstallPHP::class)->uninstall($server, $request->input()); app(UninstallPHP::class)->uninstall($server, $request->input());
Toast::success('PHP is being uninstalled!'); Toast::success('PHP is being uninstalled!');

View File

@ -19,8 +19,6 @@ class QueueController extends Controller
{ {
public function index(Server $server, Site $site): View public function index(Server $server, Site $site): View
{ {
$this->authorize('manage', $server);
return view('queues.index', [ return view('queues.index', [
'server' => $server, 'server' => $server,
'site' => $site, 'site' => $site,
@ -30,8 +28,6 @@ public function index(Server $server, Site $site): View
public function store(Server $server, Site $site, Request $request): HtmxResponse public function store(Server $server, Site $site, Request $request): HtmxResponse
{ {
$this->authorize('manage', $server);
app(CreateQueue::class)->create($site, $request->input()); app(CreateQueue::class)->create($site, $request->input());
Toast::success('Queue is being created.'); Toast::success('Queue is being created.');
@ -41,8 +37,6 @@ public function store(Server $server, Site $site, Request $request): HtmxRespons
public function action(Server $server, Site $site, Queue $queue, string $action): HtmxResponse public function action(Server $server, Site $site, Queue $queue, string $action): HtmxResponse
{ {
$this->authorize('manage', $server);
app(ManageQueue::class)->{$action}($queue); app(ManageQueue::class)->{$action}($queue);
Toast::success('Queue is about to '.$action); Toast::success('Queue is about to '.$action);
@ -52,8 +46,6 @@ public function action(Server $server, Site $site, Queue $queue, string $action)
public function destroy(Server $server, Site $site, Queue $queue): RedirectResponse public function destroy(Server $server, Site $site, Queue $queue): RedirectResponse
{ {
$this->authorize('manage', $server);
app(DeleteQueue::class)->delete($queue); app(DeleteQueue::class)->delete($queue);
Toast::success('Queue is being deleted.'); Toast::success('Queue is being deleted.');
@ -63,8 +55,6 @@ public function destroy(Server $server, Site $site, Queue $queue): RedirectRespo
public function logs(Server $server, Site $site, Queue $queue): RedirectResponse public function logs(Server $server, Site $site, Queue $queue): RedirectResponse
{ {
$this->authorize('manage', $server);
return back()->with('content', app(GetQueueLogs::class)->getLogs($queue)); return back()->with('content', app(GetQueueLogs::class)->getLogs($queue));
} }
} }

View File

@ -17,8 +17,6 @@ class SSHKeyController extends Controller
{ {
public function index(Server $server): View public function index(Server $server): View
{ {
$this->authorize('manage', $server);
return view('server-ssh-keys.index', [ return view('server-ssh-keys.index', [
'server' => $server, 'server' => $server,
'keys' => $server->sshKeys, 'keys' => $server->sshKeys,
@ -27,9 +25,7 @@ public function index(Server $server): View
public function store(Server $server, Request $request): HtmxResponse public function store(Server $server, Request $request): HtmxResponse
{ {
$this->authorize('manage', $server); /** @var \App\Models\SshKey $key */
/** @var SshKey $key */
$key = app(CreateSshKey::class)->create( $key = app(CreateSshKey::class)->create(
$request->user(), $request->user(),
$request->input() $request->input()
@ -42,8 +38,6 @@ public function store(Server $server, Request $request): HtmxResponse
public function destroy(Server $server, SshKey $sshKey): RedirectResponse public function destroy(Server $server, SshKey $sshKey): RedirectResponse
{ {
$this->authorize('manage', $server);
app(DeleteKeyFromServer::class)->delete($server, $sshKey); app(DeleteKeyFromServer::class)->delete($server, $sshKey);
Toast::success('SSH Key has been deleted.'); Toast::success('SSH Key has been deleted.');
@ -53,8 +47,6 @@ public function destroy(Server $server, SshKey $sshKey): RedirectResponse
public function deploy(Server $server, Request $request): HtmxResponse public function deploy(Server $server, Request $request): HtmxResponse
{ {
$this->authorize('manage', $server);
app(DeployKeyToServer::class)->deploy( app(DeployKeyToServer::class)->deploy(
$request->user(), $request->user(),
$server, $server,

View File

@ -17,8 +17,6 @@ class SSLController extends Controller
{ {
public function index(Server $server, Site $site): View public function index(Server $server, Site $site): View
{ {
$this->authorize('manage', $server);
return view('ssls.index', [ return view('ssls.index', [
'server' => $server, 'server' => $server,
'site' => $site, 'site' => $site,
@ -28,8 +26,6 @@ public function index(Server $server, Site $site): View
public function store(Server $server, Site $site, Request $request): HtmxResponse public function store(Server $server, Site $site, Request $request): HtmxResponse
{ {
$this->authorize('manage', $server);
app(CreateSSL::class)->create($site, $request->input()); app(CreateSSL::class)->create($site, $request->input());
Toast::success('SSL certificate is being created.'); Toast::success('SSL certificate is being created.');
@ -39,8 +35,6 @@ public function store(Server $server, Site $site, Request $request): HtmxRespons
public function destroy(Server $server, Site $site, Ssl $ssl): RedirectResponse public function destroy(Server $server, Site $site, Ssl $ssl): RedirectResponse
{ {
$this->authorize('manage', $server);
app(DeleteSSL::class)->delete($ssl); app(DeleteSSL::class)->delete($ssl);
Toast::success('SSL certificate has been deleted.'); Toast::success('SSL certificate has been deleted.');

View File

@ -1,111 +0,0 @@
<?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());
}
}

View File

@ -4,7 +4,6 @@
use App\Models\Server; use App\Models\Server;
use App\Models\Site; use App\Models\Site;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -24,22 +23,10 @@ public function search(Request $request): JsonResponse
$query->where('name', 'like', '%'.$request->input('q').'%') $query->where('name', 'like', '%'.$request->input('q').'%')
->orWhere('ip', '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(); ->get();
$sites = Site::query() $sites = Site::query()
->where('domain', 'like', '%'.$request->input('q').'%') ->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(); ->get();
$result = []; $result = [];

View File

@ -19,9 +19,6 @@ public function index(): View
{ {
/** @var User $user */ /** @var User $user */
$user = auth()->user(); $user = auth()->user();
$this->authorize('viewAny', [Server::class, $user->currentProject]);
$servers = $user->currentProject->servers()->orderByDesc('created_at')->get(); $servers = $user->currentProject->servers()->orderByDesc('created_at')->get();
return view('servers.index', compact('servers')); return view('servers.index', compact('servers'));
@ -29,15 +26,8 @@ public function index(): View
public function create(Request $request): 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)); $provider = $request->query('provider', old('provider', \App\Enums\ServerProvider::CUSTOM));
$serverProviders = ServerProvider::getByProjectId(auth()->user()->current_project_id) $serverProviders = ServerProvider::query()->where('provider', $provider)->get();
->where('provider', $provider)
->get();
return view('servers.create', [ return view('servers.create', [
'serverProviders' => $serverProviders, 'serverProviders' => $serverProviders,
@ -50,13 +40,8 @@ public function create(Request $request): View
*/ */
public function store(Request $request): HtmxResponse public function store(Request $request): HtmxResponse
{ {
/** @var User $user */
$user = auth()->user();
$this->authorize('create', [Server::class, $user->currentProject]);
$server = app(CreateServer::class)->create( $server = app(CreateServer::class)->create(
$user, $request->user(),
$request->input() $request->input()
); );
@ -67,17 +52,14 @@ public function store(Request $request): HtmxResponse
public function show(Server $server): View public function show(Server $server): View
{ {
$this->authorize('view', $server);
return view('servers.show', [ return view('servers.show', [
'server' => $server, 'server' => $server,
'logs' => $server->logs()->latest()->limit(10)->get(),
]); ]);
} }
public function delete(Server $server): RedirectResponse public function delete(Server $server): RedirectResponse
{ {
$this->authorize('delete', $server);
$server->delete(); $server->delete();
Toast::success('Server deleted successfully.'); Toast::success('Server deleted successfully.');

View File

@ -2,30 +2,22 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Actions\Server\CreateServerLog;
use App\Facades\Toast;
use App\Models\Server; use App\Models\Server;
use App\Models\ServerLog; use App\Models\ServerLog;
use Illuminate\Contracts\View\View; use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class ServerLogController extends Controller class ServerLogController extends Controller
{ {
public function index(Server $server): View public function index(Server $server): View
{ {
$this->authorize('manage', $server);
return view('server-logs.index', [ return view('server-logs.index', [
'server' => $server, 'server' => $server,
'pageTitle' => __('Vito Logs'),
]); ]);
} }
public function show(Server $server, ServerLog $serverLog): RedirectResponse public function show(Server $server, ServerLog $serverLog): RedirectResponse
{ {
$this->authorize('manage', $server);
if ($server->id != $serverLog->server_id) { if ($server->id != $serverLog->server_id) {
abort(404); abort(404);
} }
@ -34,37 +26,4 @@ public function show(Server $server, ServerLog $serverLog): RedirectResponse
'content' => $serverLog->getContent(), '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]);
}
} }

View File

@ -4,7 +4,6 @@
use App\Actions\Server\EditServer; use App\Actions\Server\EditServer;
use App\Actions\Server\RebootServer; use App\Actions\Server\RebootServer;
use App\Actions\Server\Update;
use App\Facades\Toast; use App\Facades\Toast;
use App\Helpers\HtmxResponse; use App\Helpers\HtmxResponse;
use App\Models\Server; use App\Models\Server;
@ -16,15 +15,11 @@ class ServerSettingController extends Controller
{ {
public function index(Server $server): View public function index(Server $server): View
{ {
$this->authorize('manage', $server);
return view('server-settings.index', compact('server')); return view('server-settings.index', compact('server'));
} }
public function checkConnection(Server $server): RedirectResponse|HtmxResponse public function checkConnection(Server $server): RedirectResponse|HtmxResponse
{ {
$this->authorize('manage', $server);
$oldStatus = $server->status; $oldStatus = $server->status;
$server = $server->checkConnection(); $server = $server->checkConnection();
@ -46,8 +41,6 @@ public function checkConnection(Server $server): RedirectResponse|HtmxResponse
public function reboot(Server $server): HtmxResponse public function reboot(Server $server): HtmxResponse
{ {
$this->authorize('manage', $server);
app(RebootServer::class)->reboot($server); app(RebootServer::class)->reboot($server);
Toast::info('Server is rebooting.'); Toast::info('Server is rebooting.');
@ -57,32 +50,10 @@ public function reboot(Server $server): HtmxResponse
public function edit(Request $request, Server $server): RedirectResponse public function edit(Request $request, Server $server): RedirectResponse
{ {
$this->authorize('manage', $server);
app(EditServer::class)->edit($server, $request->input()); app(EditServer::class)->edit($server, $request->input());
Toast::success('Server updated.'); Toast::success('Server updated.');
return back(); 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();
}
} }

View File

@ -2,22 +2,16 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Actions\Service\Install;
use App\Actions\Service\Uninstall;
use App\Facades\Toast; use App\Facades\Toast;
use App\Helpers\HtmxResponse;
use App\Models\Server; use App\Models\Server;
use App\Models\Service; use App\Models\Service;
use Illuminate\Contracts\View\View; use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class ServiceController extends Controller class ServiceController extends Controller
{ {
public function index(Server $server): View public function index(Server $server): View
{ {
$this->authorize('manage', $server);
return view('services.index', [ return view('services.index', [
'server' => $server, 'server' => $server,
'services' => $server->services, 'services' => $server->services,
@ -26,8 +20,6 @@ public function index(Server $server): View
public function start(Server $server, Service $service): RedirectResponse public function start(Server $server, Service $service): RedirectResponse
{ {
$this->authorize('manage', $server);
$service->start(); $service->start();
Toast::success('Service is being started!'); Toast::success('Service is being started!');
@ -37,8 +29,6 @@ public function start(Server $server, Service $service): RedirectResponse
public function stop(Server $server, Service $service): RedirectResponse public function stop(Server $server, Service $service): RedirectResponse
{ {
$this->authorize('manage', $server);
$service->stop(); $service->stop();
Toast::success('Service is being stopped!'); Toast::success('Service is being stopped!');
@ -48,8 +38,6 @@ public function stop(Server $server, Service $service): RedirectResponse
public function restart(Server $server, Service $service): RedirectResponse public function restart(Server $server, Service $service): RedirectResponse
{ {
$this->authorize('manage', $server);
$service->restart(); $service->restart();
Toast::success('Service is being restarted!'); Toast::success('Service is being restarted!');
@ -59,8 +47,6 @@ public function restart(Server $server, Service $service): RedirectResponse
public function enable(Server $server, Service $service): RedirectResponse public function enable(Server $server, Service $service): RedirectResponse
{ {
$this->authorize('manage', $server);
$service->enable(); $service->enable();
Toast::success('Service is being enabled!'); Toast::success('Service is being enabled!');
@ -70,34 +56,10 @@ public function enable(Server $server, Service $service): RedirectResponse
public function disable(Server $server, Service $service): RedirectResponse public function disable(Server $server, Service $service): RedirectResponse
{ {
$this->authorize('manage', $server);
$service->disable(); $service->disable();
Toast::success('Service is being disabled!'); Toast::success('Service is being disabled!');
return back(); return back();
} }
public function install(Server $server, Request $request): HtmxResponse
{
$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!');
return htmx()->back();
}
} }

View File

@ -3,7 +3,6 @@
namespace App\Http\Controllers\Settings; namespace App\Http\Controllers\Settings;
use App\Actions\NotificationChannels\AddChannel; use App\Actions\NotificationChannels\AddChannel;
use App\Actions\NotificationChannels\EditChannel;
use App\Facades\Toast; use App\Facades\Toast;
use App\Helpers\HtmxResponse; use App\Helpers\HtmxResponse;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
@ -14,17 +13,11 @@
class NotificationChannelController extends Controller class NotificationChannelController extends Controller
{ {
public function index(Request $request): View public function index(): View
{ {
$data = [ return view('settings.notification-channels.index', [
'channels' => NotificationChannel::getByProjectId(auth()->user()->current_project_id)->get(), 'channels' => NotificationChannel::query()->latest()->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 public function add(Request $request): HtmxResponse
@ -36,20 +29,7 @@ public function add(Request $request): HtmxResponse
Toast::success('Channel added successfully'); Toast::success('Channel added successfully');
return htmx()->redirect(route('settings.notification-channels')); return htmx()->redirect(route('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 public function delete(int $id): RedirectResponse
@ -60,6 +40,6 @@ public function delete(int $id): RedirectResponse
Toast::success('Channel deleted successfully'); Toast::success('Channel deleted successfully');
return redirect()->route('settings.notification-channels'); return redirect()->route('notification-channels');
} }
} }

View File

@ -1,10 +1,11 @@
<?php <?php
namespace App\Http\Controllers; namespace App\Http\Controllers\Settings;
use App\Actions\User\UpdateUserPassword; use App\Actions\User\UpdateUserPassword;
use App\Actions\User\UpdateUserProfileInformation; use App\Actions\User\UpdateUserProfileInformation;
use App\Facades\Toast; use App\Facades\Toast;
use App\Http\Controllers\Controller;
use Illuminate\Contracts\View\View; use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -13,7 +14,7 @@ class ProfileController extends Controller
{ {
public function index(): View public function index(): View
{ {
return view('profile.index'); return view('settings.profile.index');
} }
public function info(Request $request): RedirectResponse public function info(Request $request): RedirectResponse

View File

@ -19,42 +19,40 @@ class ProjectController extends Controller
{ {
public function index(): View public function index(): View
{ {
$this->authorize('viewAny', Project::class);
return view('settings.projects.index', [ return view('settings.projects.index', [
'projects' => Project::all(), 'projects' => auth()->user()->projects,
]); ]);
} }
public function create(Request $request): HtmxResponse public function create(Request $request): HtmxResponse
{ {
$this->authorize('create', Project::class);
app(CreateProject::class)->create($request->user(), $request->input()); app(CreateProject::class)->create($request->user(), $request->input());
Toast::success('Project created.'); Toast::success('Project created.');
return htmx()->redirect(route('settings.projects')); return htmx()->redirect(route('projects'));
} }
public function update(Request $request, Project $project): HtmxResponse public function update(Request $request, Project $project): HtmxResponse
{ {
$this->authorize('update', $project); /** @var Project $project */
$project = $request->user()->projects()->findOrFail($project->id);
app(UpdateProject::class)->update($project, $request->input()); app(UpdateProject::class)->update($project, $request->input());
Toast::success('Project updated.'); Toast::success('Project updated.');
return htmx()->redirect(route('settings.projects')); return htmx()->redirect(route('projects'));
} }
public function delete(Project $project): RedirectResponse public function delete(Project $project): RedirectResponse
{ {
$this->authorize('delete', $project);
/** @var User $user */ /** @var User $user */
$user = auth()->user(); $user = auth()->user();
/** @var Project $project */
$project = $user->projects()->findOrFail($project->id);
try { try {
app(DeleteProject::class)->delete($user, $project); app(DeleteProject::class)->delete($user, $project);
} catch (ValidationException $e) { } catch (ValidationException $e) {
@ -68,7 +66,7 @@ public function delete(Project $project): RedirectResponse
return back(); return back();
} }
public function switch(Request $request, $projectId): RedirectResponse public function switch($projectId): RedirectResponse
{ {
/** @var User $user */ /** @var User $user */
$user = auth()->user(); $user = auth()->user();
@ -76,16 +74,9 @@ public function switch(Request $request, $projectId): RedirectResponse
/** @var Project $project */ /** @var Project $project */
$project = $user->projects()->findOrFail($projectId); $project = $user->projects()->findOrFail($projectId);
$this->authorize('view', $project);
$user->current_project_id = $project->id; $user->current_project_id = $project->id;
$user->save(); $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'); return redirect()->route('servers');
} }
} }

View File

@ -29,7 +29,7 @@ public function add(Request $request): HtmxResponse
Toast::success('SSH Key added'); Toast::success('SSH Key added');
return htmx()->redirect(route('settings.ssh-keys')); return htmx()->redirect(route('ssh-keys'));
} }
public function delete(int $id): RedirectResponse public function delete(int $id): RedirectResponse
@ -40,6 +40,6 @@ public function delete(int $id): RedirectResponse
Toast::success('SSH Key deleted'); Toast::success('SSH Key deleted');
return redirect()->route('settings.ssh-keys'); return redirect()->route('ssh-keys');
} }
} }

View File

@ -4,7 +4,6 @@
use App\Actions\ServerProvider\CreateServerProvider; use App\Actions\ServerProvider\CreateServerProvider;
use App\Actions\ServerProvider\DeleteServerProvider; use App\Actions\ServerProvider\DeleteServerProvider;
use App\Actions\ServerProvider\EditServerProvider;
use App\Facades\Toast; use App\Facades\Toast;
use App\Helpers\HtmxResponse; use App\Helpers\HtmxResponse;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
@ -15,17 +14,11 @@
class ServerProviderController extends Controller class ServerProviderController extends Controller
{ {
public function index(Request $request): View public function index(): View
{ {
$data = [ return view('settings.server-providers.index', [
'providers' => ServerProvider::getByProjectId(auth()->user()->current_project_id)->get(), 'providers' => auth()->user()->serverProviders,
]; ]);
if ($request->has('edit')) {
$data['editProvider'] = ServerProvider::find($request->input('edit'));
}
return view('settings.server-providers.index', $data);
} }
public function connect(Request $request): HtmxResponse public function connect(Request $request): HtmxResponse
@ -37,20 +30,7 @@ public function connect(Request $request): HtmxResponse
Toast::success('Server provider connected.'); Toast::success('Server provider connected.');
return htmx()->redirect(route('settings.server-providers')); return htmx()->redirect(route('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 public function delete(ServerProvider $serverProvider): RedirectResponse

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