Compare commits

...

22 Commits

Author SHA1 Message Date
debcd002f1 Update README.md (#110) 2024-02-23 16:48:32 +01:00
59e8c82e5c Making the whole app into one docker image for easier usage (#104) 2024-02-23 15:28:41 +01:00
682da0d6d5 load the actual env from the server 2024-02-22 20:30:21 +01:00
9db310a06b fix notifications (#109) 2024-02-16 21:54:51 +01:00
f70963d6bb fix notification chanels and more tests (#108)
* fix notification chanels and more tests

* fix code style
2024-02-16 21:10:17 +01:00
b75df8e1c5 temp - disable notifier 2024-02-05 00:33:06 +01:00
a22e9cb946 temp - disable notifications on installation 2024-02-05 00:23:44 +01:00
b2b9bea0b1 temp - disable ufw validation 2024-02-05 00:12:55 +01:00
3f4a2bce3a fix (#106) 2024-02-05 00:07:44 +01:00
8bffefabef Upgrade to Livewire 3 (#103)
* upgrade to livewire 3

* fix updater

* fix modal events

* fix modal events
2024-02-04 18:11:22 +01:00
3da1f4fe4c fix server not having default service (#100) 2024-01-30 20:45:17 +01:00
2214a76e09 Update README.md (#97) 2024-01-29 14:56:28 +01:00
55bf8b8ecf fix ipv6 (#96) 2024-01-27 21:34:36 +01:00
0420babdef Bump vite from 4.2.3 to 4.5.2 (#95)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 4.2.3 to 4.5.2.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v4.5.2/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v4.5.2/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-27 21:09:10 +01:00
1c3d78a5ed installation without domain 2024-01-21 21:53:26 +01:00
8665435bc4 add blank php site (#94)
* add blank php site

* fix frontend

* fix source control check
2024-01-18 19:45:58 +01:00
0ec6a9dea2 fix db transaction usage 2024-01-14 12:56:25 +01:00
bdfda05398 update Roadmap URLs (#91) 2024-01-14 08:57:00 +01:00
919cdc6892 Bump follow-redirects from 1.15.2 to 1.15.5 (#90)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.2 to 1.15.5.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.2...v1.15.5)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-13 19:36:05 +01:00
902548e463 Fix roadmap link in README (#89) 2024-01-10 20:41:18 +01:00
2462b31f3b typo fix 2024-01-08 16:25:12 +01:00
e997d0deea WIP notifications and other refactors (#88)
* WIP notifications and other refactors
- refactor notification channels
- send notifications on events related to the servers and sites
- delete server log files on server deletion
- add telegram notification channel
- add new icons
- cache configs and icons on installation and updates
- new navbar for dark mode and settings

* discord channel

* build assets

* pint
2024-01-07 09:54:08 +01:00
284 changed files with 3027 additions and 1237 deletions

38
.env.docker Executable file
View File

@ -0,0 +1,38 @@
APP_NAME=Vito
APP_ENV=production
APP_KEY=
APP_DEBUG=false
APP_URL=http://localhost
LOG_CHANNEL=stack
LOG_LEVEL=debug
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=vito
DB_USERNAME=root
DB_PASSWORD=password
BROADCAST_DRIVER=null
CACHE_DRIVER=file
FILESYSTEM_DRIVER=local
QUEUE_CONNECTION=default
SESSION_DRIVER=database
SESSION_LIFETIME=120
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=smtp
MAIL_HOST=
MAIL_PORT=
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=null
MAIL_FROM_NAME="${APP_NAME}"
SSH_PUBLIC_KEY_NAME=ssh-public.key
SSH_PRIVATE_KEY_NAME=ssh-private.pem

View File

@ -26,8 +26,8 @@ REDIS_PASSWORD=null
REDIS_PORT=6379 REDIS_PORT=6379
MAIL_MAILER=smtp MAIL_MAILER=smtp
MAIL_HOST=mailhog MAIL_HOST=
MAIL_PORT=1025 MAIL_PORT=
MAIL_USERNAME=null MAIL_USERNAME=null
MAIL_PASSWORD=null MAIL_PASSWORD=null
MAIL_ENCRYPTION=null MAIL_ENCRYPTION=null

View File

@ -26,8 +26,8 @@ REDIS_PASSWORD=null
REDIS_PORT=6379 REDIS_PORT=6379
MAIL_MAILER=smtp MAIL_MAILER=smtp
MAIL_HOST=mailhog MAIL_HOST=
MAIL_PORT=1025 MAIL_PORT=
MAIL_USERNAME=null MAIL_USERNAME=null
MAIL_PASSWORD=null MAIL_PASSWORD=null
MAIL_ENCRYPTION=null MAIL_ENCRYPTION=null

View File

@ -26,8 +26,8 @@ REDIS_PASSWORD=null
REDIS_PORT=6379 REDIS_PORT=6379
MAIL_MAILER=smtp MAIL_MAILER=smtp
MAIL_HOST=mailhog MAIL_HOST=
MAIL_PORT=1025 MAIL_PORT=
MAIL_USERNAME=null MAIL_USERNAME=null
MAIL_PASSWORD=null MAIL_PASSWORD=null
MAIL_ENCRYPTION=null MAIL_ENCRYPTION=null

65
Dockerfile Normal file
View File

@ -0,0 +1,65 @@
FROM ubuntu:22.04
WORKDIR /var/www/html
ENV DEBIAN_FRONTEND noninteractive
RUN echo "mysql-server mysql-server/root_password password password" | debconf-set-selections
RUN echo "mysql-server mysql-server/root_password_again password password" | debconf-set-selections
# upgrade
RUN apt clean && apt update && apt update && apt upgrade -y && apt autoremove -y
# requirements
RUN apt install -y software-properties-common curl zip unzip git gcc
# nginx
RUN apt install -y nginx
# php
RUN apt update \
&& apt install -y gnupg gosu curl ca-certificates zip unzip git supervisor libcap2-bin libpng-dev \
python2 dnsutils librsvg2-bin fswatch wget \
&& add-apt-repository ppa:ondrej/php -y \
&& apt update \
&& apt install -y php8.1 php8.1-fpm php8.1-mbstring php8.1-mysql php8.1-mcrypt php8.1-gd php8.1-xml \
php8.1-curl php8.1-gettext php8.1-zip php8.1-bcmath php8.1-soap php8.1-redis
COPY docker/standalone/php.ini /etc/php/8.1/cli/conf.d/99-vito.ini
# composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
# mysql
RUN wget -c https://dev.mysql.com/get/mysql-apt-config_0.8.22-1_all.deb \
&& mkdir -p /etc/apt/keyrings \
&& apt clean \
&& apt update \
&& dpkg -i mysql-apt-config_0.8.22-1_all.deb \
&& apt install mysql-server -y
RUN service mysql stop
# app
COPY . /var/www/html
RUN rm -rf /var/www/html/vendor
RUN rm -rf /var/www/html/.env
RUN composer install --no-dev --prefer-dist
RUN chown -R www-data:www-data /var/www/html \
&& chmod -R 755 /var/www/html/storage /var/www/html/bootstrap/cache
# webserver
RUN rm /etc/nginx/sites-available/default
RUN rm /etc/nginx/sites-enabled/default
COPY docker/standalone/nginx.conf /etc/nginx/sites-available/default
RUN ln -s /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default
# supervisord
COPY docker/standalone/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
# start
COPY docker/standalone/start.sh /start.sh
RUN chmod +x /start.sh
EXPOSE 80 3306
CMD ["/start.sh"]

View File

@ -1,36 +1,49 @@
# Vito
![](https://github.com/vitodeploy/vito/workflows/tests/badge.svg) <p align="center">
<img alt="srcshot 2024-02-23 at 16 26 21@2x" src="https://github.com/vitodeploy/vito/assets/61919774/c22842ff-b458-443c-90b7-a98148b0d49e" alt="VitoDeploy>
<p align="center">
<a href="https://github.com/vitodeploy/vito/actions"><img alt="GitHub Workflow Status" src="https://github.com/vitodeploy/vito/workflows/tests/badge.svg"></a>
</p>
</p>
![image](https://github.com/vitodeploy/vito/assets/61919774/687d50e5-8a61-41b5-b708-752567e30aed) ------
## About Vito
Better Readme will come soon... :) Vito is a self-hosted web application that helps you manage your servers and deploy your PHP applications into production servers without a hassle.
## Documentation ## Features
https://vitodeploy.com - Provisions and Manages the server
- Easy database management, Supports Mysql and MariaDB
- Deploy your PHP applications such as Laravel
- Manage your server's firewall
- Supports Custom and Letsencrypt SSL
- Uses supervisor to handle queues
- Manages server's services
- Deploy your SSH Keys to the server
- Create and Manage cron jobs on the server
## Feedbacks ## Useful Links
https://features.vitodeploy.com - [Documentation](https://vitodeploy.com)
- [Feedbacks](https://vitodeploy.featurebase.app)
## Roadmap - [Roadmap](https://vitodeploy.featurebase.app/roadmap)
- [Discord](https://discord.gg/dcUWA5DV)
https://https://features.vitodeploy.com/roadmap - [Contribution](/CONTRIBUTING.md)
- [Security](/SECURITY.md)
## Contribution
Please read the contribution guide [Here](/CONTRIBUTING.md)
## Security
Please read the security policy [Here](/SECURITY.md)
## Credits ## Credits
- Laravel - Laravel
- Tailwind - Tailwindcss
- Livewire - Livewire
- Alpinejs - Alpinejs
- Vite - Vite
- Laravel Enum by BenSampo - Laravel Enum by BenSampo
- Log Viewer by Arunas Skirius
- PHPSecLib
- Laravel Blade Icons
- Guzzlehttp
- Owenvoke for `owenvoke/blade-fontawesome`
- Axios
- Toastr by CodeSeven

View File

@ -21,16 +21,25 @@ public function add(User $user, array $input): void
'label' => $input['label'], 'label' => $input['label'],
]); ]);
$this->validateType($channel, $input); $this->validateType($channel, $input);
$channel->data = $channel->provider()->data($input); $channel->data = $channel->provider()->createData($input);
$channel->save(); $channel->save();
if (! $channel->provider()->connect()) { if (! $channel->provider()->connect()) {
$channel->delete(); $channel->delete();
if ($channel->provider === \App\Enums\NotificationChannel::EMAIL) {
throw ValidationException::withMessages([
'email' => __('Could not connect! Make sure you configured `.env` file correctly.'),
]);
}
throw ValidationException::withMessages([ throw ValidationException::withMessages([
'provider' => __('Could not connect'), 'provider' => __('Could not connect'),
]); ]);
} }
$channel->connected = true;
$channel->save();
} }
/** /**
@ -49,7 +58,7 @@ protected function validate(array $input): void
*/ */
protected function validateType(NotificationChannel $channel, array $input): void protected function validateType(NotificationChannel $channel, array $input): void
{ {
Validator::make($input, $channel->provider()->validationRules()) Validator::make($input, $channel->provider()->createRules($input))
->validate(); ->validate();
} }
} }

View File

@ -44,9 +44,8 @@ public function create(User $creator, array $input): Server
'progress_step' => 'Initializing', 'progress_step' => 'Initializing',
]); ]);
try {
DB::beginTransaction(); DB::beginTransaction();
try {
if ($server->provider != 'custom') { if ($server->provider != 'custom') {
$server->provider_id = $input['server_provider']; $server->provider_id = $input['server_provider'];
} }
@ -119,7 +118,6 @@ private function validateInputs(array $input): void
if ($input['provider'] == 'custom') { if ($input['provider'] == 'custom') {
$rules['ip'] = [ $rules['ip'] = [
'required', 'required',
'ip',
new RestrictedIPAddressesRule(), new RestrictedIPAddressesRule(),
]; ];
$rules['port'] = [ $rules['port'] = [

View File

@ -23,9 +23,8 @@ public function create(Server $server, array $input): Site
{ {
$this->validateInputs($server, $input); $this->validateInputs($server, $input);
try {
DB::beginTransaction(); DB::beginTransaction();
try {
$site = new Site([ $site = new Site([
'server_id' => $server->id, 'server_id' => $server->id,
'type' => $input['type'], 'type' => $input['type'],

0
app/Actions/Site/UpdateEnv.php Executable file → Normal file
View File

View File

@ -2,9 +2,17 @@
namespace App\Contracts; namespace App\Contracts;
use Illuminate\Notifications\Messages\MailMessage;
interface Notification interface Notification
{ {
public function subject(): string; public function rawText(): string;
public function message(bool $mail = false): mixed; public function toEmail(object $notifiable): MailMessage;
public function toSlack(object $notifiable): string;
public function toDiscord(object $notifiable): string;
public function toTelegram(object $notifiable): string;
} }

View File

@ -4,11 +4,13 @@
interface NotificationChannel interface NotificationChannel
{ {
public function validationRules(): array; public function createRules(array $input): array;
public function data(array $input): array; public function createData(array $input): array;
public function data(): array;
public function connect(): bool; public function connect(): bool;
public function sendMessage(string $subject, string $text): void; public function send(object $notifiable, Notification $notification): void;
} }

View File

@ -11,4 +11,6 @@ final class NotificationChannel extends Enum
const SLACK = 'slack'; const SLACK = 'slack';
const DISCORD = 'discord'; const DISCORD = 'discord';
const TELEGRAM = 'telegram';
} }

View File

@ -8,6 +8,8 @@ final class SiteType extends Enum
{ {
const PHP = 'php'; const PHP = 'php';
const PHP_BLANK = 'php-blank';
const LARAVEL = 'laravel'; const LARAVEL = 'laravel';
const WORDPRESS = 'wordpress'; const WORDPRESS = 'wordpress';

17
app/Facades/Notifier.php Normal file
View File

@ -0,0 +1,17 @@
<?php
namespace App\Facades;
use App\Contracts\Notification;
use Illuminate\Support\Facades\Facade;
/**
* @method static void send(object $notifiable, Notification $notification)
*/
class Notifier extends Facade
{
protected static function getFacadeAccessor(): string
{
return 'notifier';
}
}

19
app/Helpers/Notifier.php Normal file
View File

@ -0,0 +1,19 @@
<?php
namespace App\Helpers;
use App\Contracts\Notification;
use App\Models\NotificationChannel;
class Notifier
{
/**
* In the future we can send notifications based on the notifiable instance,
* For example, If it was a server then we will send the channels specified by that server
* For now, we will send all channels.
*/
public function send(object $notifiable, Notification $notification): void
{
NotificationChannel::notifyAll($notification);
}
}

View File

@ -64,11 +64,15 @@ public function setLog(string $logType, $siteId = null): void
*/ */
public function connect(bool $sftp = false): void public function connect(bool $sftp = false): void
{ {
$ip = $this->server->ip;
if (str($ip)->contains(':')) {
$ip = '['.$ip.']';
}
try { try {
if ($sftp) { if ($sftp) {
$this->connection = new SFTP($this->server->ip, $this->server->port); $this->connection = new SFTP($ip, $this->server->port);
} else { } else {
$this->connection = new SSH2($this->server->ip, $this->server->port); $this->connection = new SSH2($ip, $this->server->port);
} }
$login = $this->connection->login($this->user, $this->privateKey); $login = $this->connection->login($this->user, $this->privateKey);

View File

@ -32,9 +32,6 @@ public function info(string $message): void
private function toast(string $type, string $message): void private function toast(string $type, string $message): void
{ {
$this->component->dispatchBrowserEvent('toast', [ $this->component->dispatch('toast', type: $type, message: $message);
'type' => $type,
'message' => $message,
]);
} }
} }

View File

@ -3,8 +3,11 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Exceptions\SourceControlIsNotConnected; use App\Exceptions\SourceControlIsNotConnected;
use App\Facades\Notifier;
use App\Models\GitHook; use App\Models\GitHook;
use App\Notifications\SourceControlDisconnected;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Throwable; use Throwable;
class GitHookController extends Controller class GitHookController extends Controller
@ -25,7 +28,7 @@ public function __invoke(Request $request)
try { try {
$gitHook->site->deploy(); $gitHook->site->deploy();
} catch (SourceControlIsNotConnected) { } catch (SourceControlIsNotConnected) {
// TODO: send notification Notifier::send($gitHook->sourceControl, new SourceControlDisconnected($gitHook->sourceControl));
} catch (Throwable $e) { } catch (Throwable $e) {
Log::error('git-hook-exception', (array) $e); Log::error('git-hook-exception', (array) $e);
} }

View File

@ -23,9 +23,9 @@ public function deploy(): void
$this->toast()->success(__('Deployment started!')); $this->toast()->success(__('Deployment started!'));
$this->emitTo(DeploymentsList::class, '$refresh'); $this->dispatch('$refresh')->to(DeploymentsList::class);
$this->emitTo(DeploymentScript::class, '$refresh'); $this->dispatch('$refresh')->to(DeploymentScript::class);
} catch (SourceControlIsNotConnected $e) { } catch (SourceControlIsNotConnected $e) {
session()->flash('toast.type', 'error'); session()->flash('toast.type', 'error');
session()->flash('toast.message', $e->getMessage()); session()->flash('toast.message', $e->getMessage());

View File

@ -27,8 +27,8 @@ public function save(): void
session()->flash('status', 'script-updated'); session()->flash('status', 'script-updated');
$this->emitTo(Deploy::class, '$refresh'); $this->dispatch('$refresh')->to(Deploy::class);
$this->emitTo(AutoDeployment::class, '$refresh'); $this->dispatch('$refresh')->to(AutoDeployment::class);
} }
public function render(): View public function render(): View

View File

@ -22,7 +22,7 @@ public function showLog(int $id): void
$deployment = $this->site->deployments()->findOrFail($id); $deployment = $this->site->deployments()->findOrFail($id);
$this->logContent = $deployment->log->content; $this->logContent = $deployment->log->content;
$this->dispatchBrowserEvent('open-modal', 'show-log'); $this->dispatch('open-modal', 'show-log');
} }
public function render(): View public function render(): View

View File

@ -14,11 +14,11 @@ class Env extends Component
public Site $site; public Site $site;
public string $env; public string $env = 'Loading...';
public function mount(): void public function loadEnv(): void
{ {
$this->env = $this->site->env; $this->env = $this->site->getEnv();
} }
public function save(): void public function save(): void
@ -27,7 +27,7 @@ public function save(): void
session()->flash('status', 'updating-env'); session()->flash('status', 'updating-env');
$this->emit(Deploy::class, '$refresh'); $this->dispatch('$refresh')->to(Deploy::class);
} }
public function render(): View public function render(): View

View File

@ -0,0 +1,19 @@
<?php
namespace App\Http\Livewire\Application;
use App\Models\Site;
use App\Traits\RefreshComponentOnBroadcast;
use Livewire\Component;
class PhpBlankApp extends Component
{
use RefreshComponentOnBroadcast;
public Site $site;
public function render()
{
return view('livewire.application.php-blank-app');
}
}

View File

@ -13,7 +13,7 @@ public function render(): View
$event = Cache::get('broadcast'); $event = Cache::get('broadcast');
if ($event) { if ($event) {
Cache::forget('broadcast'); Cache::forget('broadcast');
$this->emit('broadcast', $event); $this->dispatch('broadcast', $event);
} }
return view('livewire.broadcast'); return view('livewire.broadcast');

View File

@ -22,9 +22,9 @@ public function create(): void
{ {
app(\App\Actions\CronJob\CreateCronJob::class)->create($this->server, $this->all()); app(\App\Actions\CronJob\CreateCronJob::class)->create($this->server, $this->all());
$this->emitTo(CronjobsList::class, '$refresh'); $this->dispatch('$refresh')->to(CronjobsList::class);
$this->dispatchBrowserEvent('created', true); $this->dispatch('created');
} }
public function render(): View public function render(): View

View File

@ -23,7 +23,7 @@ public function delete(): void
$this->refreshComponent([]); $this->refreshComponent([]);
$this->dispatchBrowserEvent('confirmed', true); $this->dispatch('confirmed');
} }
public function render(): View public function render(): View

View File

@ -45,7 +45,7 @@ public function restore(): void
$this->refreshComponent([]); $this->refreshComponent([]);
$this->dispatchBrowserEvent('restored', true); $this->dispatch('restored');
} }
public function delete(): void public function delete(): void
@ -55,7 +55,7 @@ public function delete(): void
$file->delete(); $file->delete();
$this->dispatchBrowserEvent('confirmed', true); $this->dispatch('confirmed');
} }
public function render(): View public function render(): View

View File

@ -40,7 +40,7 @@ public function create(): void
$this->refreshComponent([]); $this->refreshComponent([]);
$this->dispatchBrowserEvent('backup-created', true); $this->dispatch('backup-created');
} }
public function files(int $id): void public function files(int $id): void
@ -48,7 +48,7 @@ public function files(int $id): void
$backup = Backup::query()->findOrFail($id); $backup = Backup::query()->findOrFail($id);
$this->backup = $backup; $this->backup = $backup;
$this->files = $backup->files()->orderByDesc('id')->simplePaginate(1); $this->files = $backup->files()->orderByDesc('id')->simplePaginate(1);
$this->dispatchBrowserEvent('show-files', true); $this->dispatch('show-files');
} }
public function backup(): void public function backup(): void
@ -57,7 +57,7 @@ public function backup(): void
$this->files = $this->backup?->files()->orderByDesc('id')->simplePaginate(); $this->files = $this->backup?->files()->orderByDesc('id')->simplePaginate();
$this->dispatchBrowserEvent('backup-running', true); $this->dispatch('backup-running');
} }
public function delete(): void public function delete(): void
@ -67,7 +67,7 @@ public function delete(): void
$backup->delete(); $backup->delete();
$this->dispatchBrowserEvent('confirmed', true); $this->dispatch('confirmed');
} }
public function render(): View public function render(): View

View File

@ -40,7 +40,7 @@ public function create(): void
$this->refreshComponent([]); $this->refreshComponent([]);
$this->dispatchBrowserEvent('database-created', true); $this->dispatch('database-created');
} }
public function delete(): void public function delete(): void
@ -52,9 +52,9 @@ public function delete(): void
$this->refreshComponent([]); $this->refreshComponent([]);
$this->emitTo(DatabaseUserList::class, '$refresh'); $this->dispatch('$refresh')->to(DatabaseUserList::class);
$this->dispatchBrowserEvent('confirmed', true); $this->dispatch('confirmed');
} }
public function render(): View public function render(): View

View File

@ -38,7 +38,7 @@ public function create(): void
$this->refreshComponent([]); $this->refreshComponent([]);
$this->dispatchBrowserEvent('database-user-created', true); $this->dispatch('database-user-created');
} }
public function delete(): void public function delete(): void
@ -50,9 +50,9 @@ public function delete(): void
$this->refreshComponent([]); $this->refreshComponent([]);
$this->emitTo(DatabaseList::class, '$refresh'); $this->dispatch('$refresh')->to(DatabaseList::class);
$this->dispatchBrowserEvent('confirmed', true); $this->dispatch('confirmed');
} }
public function viewPassword(int $id): void public function viewPassword(int $id): void
@ -62,7 +62,7 @@ public function viewPassword(int $id): void
$this->viewPassword = $databaseUser->password; $this->viewPassword = $databaseUser->password;
$this->dispatchBrowserEvent('open-modal', 'database-user-password'); $this->dispatch('open-modal', 'database-user-password');
} }
public function showLink(int $id): void public function showLink(int $id): void
@ -73,7 +73,7 @@ public function showLink(int $id): void
$this->linkId = $id; $this->linkId = $id;
$this->link = $databaseUser->databases ?? []; $this->link = $databaseUser->databases ?? [];
$this->dispatchBrowserEvent('open-modal', 'link-database-user'); $this->dispatch('open-modal', 'link-database-user');
} }
public function link(): void public function link(): void
@ -85,7 +85,7 @@ public function link(): void
$this->refreshComponent([]); $this->refreshComponent([]);
$this->dispatchBrowserEvent('linked', true); $this->dispatch('linked');
} }
public function render(): View public function render(): View

View File

@ -28,9 +28,9 @@ public function create(): void
{ {
app(CreateRule::class)->create($this->server, $this->all()); app(CreateRule::class)->create($this->server, $this->all());
$this->emitTo(FirewallRulesList::class, '$refresh'); $this->dispatch('$refresh')->to(FirewallRulesList::class);
$this->dispatchBrowserEvent('created', true); $this->dispatch('created');
} }
public function render(): View public function render(): View

View File

@ -25,7 +25,7 @@ public function delete(): void
$this->refreshComponent([]); $this->refreshComponent([]);
$this->dispatchBrowserEvent('confirmed', true); $this->dispatch('confirmed');
} }
public function render(): View public function render(): View

View File

@ -15,6 +15,10 @@ class AddChannel extends Component
public string $email; public string $email;
public string $bot_token;
public string $chat_id;
public function add(): void public function add(): void
{ {
app(\App\Actions\NotificationChannels\AddChannel::class)->add( app(\App\Actions\NotificationChannels\AddChannel::class)->add(
@ -22,9 +26,9 @@ public function add(): void
$this->all() $this->all()
); );
$this->emitTo(ChannelsList::class, '$refresh'); $this->dispatch('$refresh')->to(ChannelsList::class);
$this->dispatchBrowserEvent('added', true); $this->dispatch('added');
} }
public function render(): View public function render(): View

View File

@ -25,7 +25,7 @@ public function delete(): void
$this->refreshComponent([]); $this->refreshComponent([]);
$this->dispatchBrowserEvent('confirmed', true); $this->dispatch('confirmed');
} }
public function render(): View public function render(): View

View File

@ -56,7 +56,7 @@ public function uninstall(): void
$this->refreshComponent([]); $this->refreshComponent([]);
$this->dispatchBrowserEvent('confirmed', true); $this->dispatch('confirmed');
} }
public function loadIni(int $id): void public function loadIni(int $id): void

View File

@ -33,7 +33,7 @@ public function submit(): void
session()->flash('status', 'profile-updated'); session()->flash('status', 'profile-updated');
$this->emitTo(UserDropdown::class, '$refresh'); $this->dispatch('$refresh')->to(UserDropdown::class);
} }
public function sendVerificationEmail(): void public function sendVerificationEmail(): void

View File

@ -21,9 +21,9 @@ public function create(): void
app(\App\Actions\Projects\CreateProject::class) app(\App\Actions\Projects\CreateProject::class)
->create(auth()->user(), $this->inputs); ->create(auth()->user(), $this->inputs);
$this->emitTo(ProjectsList::class, '$refresh'); $this->dispatch('$refresh')->to(ProjectsList::class);
$this->dispatchBrowserEvent('created', true); $this->dispatch('created');
} }
public function render(): View public function render(): View

View File

@ -24,9 +24,9 @@ public function create(): void
{ {
app(\App\Actions\Queue\CreateQueue::class)->create($this->site, $this->all()); app(\App\Actions\Queue\CreateQueue::class)->create($this->site, $this->all());
$this->emitTo(QueuesList::class, '$refresh'); $this->dispatch('$refresh')->to(QueuesList::class);
$this->dispatchBrowserEvent('created', true); $this->dispatch('created');
} }
public function render(): View public function render(): View

View File

@ -24,7 +24,7 @@ public function delete(): void
$this->refreshComponent([]); $this->refreshComponent([]);
$this->dispatchBrowserEvent('confirmed', true); $this->dispatch('confirmed');
} }
public function start(Queue $queue): void public function start(Queue $queue): void

View File

@ -27,7 +27,7 @@ public function showLog(int $id): void
$log = $this->server->logs()->findOrFail($id); $log = $this->server->logs()->findOrFail($id);
$this->logContent = $log->content; $this->logContent = $log->content;
$this->dispatchBrowserEvent('open-modal', 'show-log'); $this->dispatch('open-modal', 'show-log');
} }
public function render(): View public function render(): View

View File

@ -22,9 +22,9 @@ public function connect(): void
{ {
app(CreateServerProvider::class)->create(auth()->user(), $this->all()); app(CreateServerProvider::class)->create(auth()->user(), $this->all());
$this->emitTo(ProvidersList::class, '$refresh'); $this->dispatch('$refresh')->to(ProvidersList::class);
$this->dispatchBrowserEvent('connected', true); $this->dispatch('connected');
} }
public function render(): View public function render(): View

View File

@ -25,7 +25,7 @@ public function delete(): void
$this->refreshComponent([]); $this->refreshComponent([]);
$this->dispatchBrowserEvent('confirmed', true); $this->dispatch('confirmed');
} }
public function render(): View public function render(): View

View File

@ -19,9 +19,9 @@ public function add(): void
$key->deployTo($this->server); $key->deployTo($this->server);
$this->emitTo(ServerKeysList::class, '$refresh'); $this->dispatch('$refresh')->to(ServerKeysList::class);
$this->dispatchBrowserEvent('added', true); $this->dispatch('added');
} }
public function render(): View public function render(): View

View File

@ -24,9 +24,9 @@ public function add(): void
$key->deployTo($this->server); $key->deployTo($this->server);
$this->emitTo(ServerKeysList::class, '$refresh'); $this->dispatch('$refresh')->to(ServerKeysList::class);
$this->dispatchBrowserEvent('added', true); $this->dispatch('added');
} }
public function render(): View public function render(): View

View File

@ -28,7 +28,7 @@ public function delete(): void
$this->refreshComponent([]); $this->refreshComponent([]);
$this->dispatchBrowserEvent('confirmed', true); $this->dispatch('confirmed');
} }
public function render(): View public function render(): View

View File

@ -21,7 +21,7 @@ public function refreshComponent(array $data): void
return; return;
} }
$this->emit('refreshComponent'); $this->dispatch('refreshComponent');
} }
public function render(): View public function render(): View

View File

@ -19,9 +19,9 @@ public function install(): void
{ {
app(InstallPHPMyAdminAction::class)->install($this->server, $this->all()); app(InstallPHPMyAdminAction::class)->install($this->server, $this->all());
$this->dispatchBrowserEvent('started', true); $this->dispatch('started');
$this->emitTo(ServicesList::class, '$refresh'); $this->dispatch('$refresh')->to(ServicesList::class);
} }
public function render(): View public function render(): View

View File

@ -26,7 +26,7 @@ public function refreshComponent(array $data): void
return; return;
} }
$this->emit('refreshComponent'); $this->dispatch('refreshComponent');
} }
public function render(): View public function render(): View

View File

@ -20,9 +20,9 @@ public function connect(): void
{ {
app(ConnectSourceControl::class)->connect($this->all()); app(ConnectSourceControl::class)->connect($this->all());
$this->emitTo(SourceControlsList::class, '$refresh'); $this->dispatch('$refresh')->to(SourceControlsList::class);
$this->dispatchBrowserEvent('connected', true); $this->dispatch('connected');
} }
public function render(): View public function render(): View

View File

@ -25,7 +25,7 @@ public function delete(): void
$this->refreshComponent([]); $this->refreshComponent([]);
$this->dispatchBrowserEvent('confirmed', true); $this->dispatch('confirmed');
} }
public function render(): View public function render(): View

View File

@ -19,9 +19,9 @@ public function add(): void
$this->all() $this->all()
); );
$this->emitTo(KeysList::class, '$refresh'); $this->dispatch('$refresh')->to(KeysList::class);
$this->dispatchBrowserEvent('added', true); $this->dispatch('added');
} }
public function render(): View public function render(): View

View File

@ -25,7 +25,7 @@ public function delete(): void
$this->refreshComponent([]); $this->refreshComponent([]);
$this->dispatchBrowserEvent('confirmed', true); $this->dispatch('confirmed');
} }
public function render(): View public function render(): View

View File

@ -23,9 +23,9 @@ public function create(): void
{ {
app(\App\Actions\SSL\CreateSSL::class)->create($this->site, $this->all()); app(\App\Actions\SSL\CreateSSL::class)->create($this->site, $this->all());
$this->emitTo(SslsList::class, '$refresh'); $this->dispatch('$refresh')->to(SslsList::class);
$this->dispatchBrowserEvent('created', true); $this->dispatch('created');
} }
public function render(): View public function render(): View

View File

@ -25,7 +25,7 @@ public function delete(): void
$this->refreshComponent([]); $this->refreshComponent([]);
$this->dispatchBrowserEvent('confirmed', true); $this->dispatch('confirmed');
} }
public function refreshComponent(array $data): void public function refreshComponent(array $data): void
@ -34,7 +34,7 @@ public function refreshComponent(array $data): void
$this->toast()->error(__('SSL creation failed!')); $this->toast()->error(__('SSL creation failed!'));
} }
$this->emit('refreshComponent'); $this->dispatch('refreshComponent');
} }
public function render(): View public function render(): View

View File

@ -32,9 +32,9 @@ public function connect(): void
{ {
app(CreateStorageProvider::class)->create(auth()->user(), $this->all()); app(CreateStorageProvider::class)->create(auth()->user(), $this->all());
$this->emitTo(ProvidersList::class, '$refresh'); $this->dispatch('$refresh')->to(ProvidersList::class);
$this->dispatchBrowserEvent('connected', true); $this->dispatch('connected');
} }
public function render(): View public function render(): View

View File

@ -25,7 +25,7 @@ public function delete(): void
$this->refreshComponent([]); $this->refreshComponent([]);
$this->dispatchBrowserEvent('confirmed', true); $this->dispatch('confirmed');
} }
public function render(): View public function render(): View

View File

@ -3,8 +3,10 @@
namespace App\Jobs\Server; namespace App\Jobs\Server;
use App\Events\Broadcast; use App\Events\Broadcast;
use App\Facades\Notifier;
use App\Jobs\Job; use App\Jobs\Job;
use App\Models\Server; use App\Models\Server;
use App\Notifications\ServerDisconnected;
use Throwable; use Throwable;
class CheckConnection extends Job class CheckConnection extends Job
@ -39,7 +41,7 @@ public function failed(): void
{ {
$this->server->status = 'disconnected'; $this->server->status = 'disconnected';
$this->server->save(); $this->server->save();
/** @todo notify */ Notifier::send($this->server, new ServerDisconnected($this->server));
event( event(
new Broadcast('server-status-failed', [ new Broadcast('server-status-failed', [
'server' => $this->server, 'server' => $this->server,

View File

@ -26,9 +26,7 @@ public function handle(): void
new EditFileCommand( new EditFileCommand(
$this->site->path.'/.env', $this->site->path.'/.env',
$this->site->env $this->site->env
), )
'update-env',
$this->site->id
); );
event( event(
new Broadcast('deploy-site-env-finished', [ new Broadcast('deploy-site-env-finished', [

View File

@ -1,47 +0,0 @@
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Queue\SerializesModels;
class NotificationChannelMessage extends Mailable implements ShouldQueue
{
use Queueable, SerializesModels;
/**
* @var mixed
*/
public $text;
/**
* Create a new message instance.
*
* @return void
*/
public function __construct($subject, $text)
{
$this->subject = $subject;
$this->text = $text;
}
/**
* Build the message.
*
* @return $this
*/
public function build()
{
if ($this->text instanceof MailMessage) {
return $this->markdown('vendor.notifications.email', $this->text->data());
}
return $this->markdown('emails.notification-channel-message', [
'subject' => $this->subject,
'text' => $this->text,
]);
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class NotificationMail extends Mailable
{
use Queueable, SerializesModels;
public string $text;
public function __construct(string $subject, string $text)
{
$this->subject = $subject;
$this->text = $text;
}
public function build(): self
{
return $this->html($this->text);
}
}

View File

@ -69,8 +69,8 @@ public function deployHook(): void
*/ */
public function destroyHook(): void public function destroyHook(): void
{ {
try {
DB::beginTransaction(); DB::beginTransaction();
try {
$this->sourceControl->provider()->destroyHook($this->site->repository, $this->hook_id); $this->sourceControl->provider()->destroyHook($this->site->repository, $this->hook_id);
$this->delete(); $this->delete();
DB::commit(); DB::commit();

View File

@ -2,19 +2,21 @@
namespace App\Models; namespace App\Models;
use App\Contracts\Notification;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Notifications\Notifiable;
/** /**
* @property string $provider * @property int $id
* @property string $label * @property string provider
* @property array $data * @property array data
* @property bool $connected * @property string label
* @property bool $is_default * @property bool connected
* @property User $user
*/ */
class NotificationChannel extends AbstractModel class NotificationChannel extends AbstractModel
{ {
use HasFactory; use HasFactory;
use Notifiable;
protected $fillable = [ protected $fillable = [
'provider', 'provider',
@ -25,15 +27,24 @@ class NotificationChannel extends AbstractModel
]; ];
protected $casts = [ protected $casts = [
'data' => 'json', 'project_id' => 'integer',
'data' => 'array',
'connected' => 'boolean', 'connected' => 'boolean',
'is_default' => 'boolean', 'is_default' => 'boolean',
]; ];
public function provider(): \App\Contracts\NotificationChannel public function provider(): \App\Contracts\NotificationChannel
{ {
$provider = config('core.notification_channels_providers_class')[$this->provider]; $class = config('core.notification_channels_providers_class')[$this->provider];
return new $provider($this); return new $class($this);
}
public static function notifyAll(Notification $notification): void
{
$channels = self::all();
foreach ($channels as $channel) {
$channel->notify($notification);
}
} }
} }

View File

@ -4,15 +4,19 @@
use App\Contracts\ServerType; use App\Contracts\ServerType;
use App\Enums\ServerStatus; use App\Enums\ServerStatus;
use App\Enums\ServiceStatus;
use App\Facades\Notifier;
use App\Facades\SSH; use App\Facades\SSH;
use App\Jobs\Installation\Upgrade; use App\Jobs\Installation\Upgrade;
use App\Jobs\Server\CheckConnection; use App\Jobs\Server\CheckConnection;
use App\Jobs\Server\RebootServer; use App\Jobs\Server\RebootServer;
use App\Notifications\ServerInstallationStarted;
use App\Support\Testing\SSHFake; use App\Support\Testing\SSHFake;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str; use Illuminate\Support\Str;
@ -110,7 +114,9 @@ public static function boot(): void
$site->delete(); $site->delete();
}); });
$server->provider()->delete(); $server->provider()->delete();
$server->logs()->delete(); $server->logs()->each(function (ServerLog $log) {
$log->delete();
});
$server->services()->delete(); $server->services()->delete();
$server->databases()->delete(); $server->databases()->delete();
$server->databaseUsers()->delete(); $server->databaseUsers()->delete();
@ -222,6 +228,18 @@ public function defaultService($type): ?Service
->where('is_default', 1) ->where('is_default', 1)
->first(); ->first();
// If no default service found, get the first service with status ready or stopped
if (! $service) {
$service = $this->services()
->where('type', $type)
->whereIn('status', [ServiceStatus::READY, ServiceStatus::STOPPED])
->first();
if ($service) {
$service->is_default = 1;
$service->save();
}
}
return $service; return $service;
} }
@ -239,7 +257,7 @@ public function getServiceByUnit($unit): ?Service
public function install(): void public function install(): void
{ {
$this->type()->install(); $this->type()->install();
// $this->team->notify(new ServerInstallationStarted($this)); Notifier::send($this, new ServerInstallationStarted($this));
} }
public function ssh(?string $user = null): \App\Helpers\SSH|SSHFake public function ssh(?string $user = null): \App\Helpers\SSH|SSHFake
@ -343,7 +361,7 @@ public function sshKey(): array
]; ];
} }
/** @var \Illuminate\Filesystem\FilesystemAdapter $storageDisk */ /** @var FilesystemAdapter $storageDisk */
$storageDisk = Storage::disk(config('core.key_pairs_disk')); $storageDisk = Storage::disk(config('core.key_pairs_disk'));
return [ return [

View File

@ -34,6 +34,17 @@ class ServerLog extends AbstractModel
'site_id' => 'integer', 'site_id' => 'integer',
]; ];
public static function boot(): void
{
parent::boot();
static::deleting(function (ServerLog $log) {
if (Storage::disk($log->disk)->exists($log->name)) {
Storage::disk($log->disk)->delete($log->name);
}
});
}
public function getRouteKey(): string public function getRouteKey(): string
{ {
return 'log'; return 'log';

View File

@ -8,10 +8,14 @@
use App\Enums\SslStatus; use App\Enums\SslStatus;
use App\Events\Broadcast; use App\Events\Broadcast;
use App\Exceptions\SourceControlIsNotConnected; use App\Exceptions\SourceControlIsNotConnected;
use App\Facades\Notifier;
use App\Jobs\Site\ChangePHPVersion; use App\Jobs\Site\ChangePHPVersion;
use App\Jobs\Site\Deploy; use App\Jobs\Site\Deploy;
use App\Jobs\Site\DeployEnv; use App\Jobs\Site\DeployEnv;
use App\Jobs\Site\UpdateBranch; use App\Jobs\Site\UpdateBranch;
use App\Notifications\SiteInstallationFailed;
use App\Notifications\SiteInstallationSucceed;
use App\SSHCommands\Website\GetEnvCommand;
use Exception; use Exception;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
@ -354,8 +358,8 @@ public function enableAutoDeployment(): void
throw new SourceControlIsNotConnected($this->source_control); throw new SourceControlIsNotConnected($this->source_control);
} }
try {
DB::beginTransaction(); DB::beginTransaction();
try {
$gitHook = new GitHook([ $gitHook = new GitHook([
'site_id' => $this->id, 'site_id' => $this->id,
'source_control_id' => $this->sourceControl()->id, 'source_control_id' => $this->sourceControl()->id,
@ -406,7 +410,7 @@ public function installationFinished(): void
'site' => $this, 'site' => $this,
]) ])
); );
/** @todo notify */ Notifier::send($this, new SiteInstallationSucceed($this));
} }
/** /**
@ -422,7 +426,7 @@ public function installationFailed(Throwable $e): void
'site' => $this, 'site' => $this,
]) ])
); );
/** @todo notify */ Notifier::send($this, new SiteInstallationFailed($this));
Log::error('install-site-error', [ Log::error('install-site-error', [
'error' => (string) $e, 'error' => (string) $e,
]); ]);
@ -439,4 +443,13 @@ public function isReady(): bool
{ {
return $this->status === SiteStatus::READY; return $this->status === SiteStatus::READY;
} }
public function getEnv(): string
{
return $this->server->ssh()->exec(
new GetEnvCommand(
$this->domain
)
);
}
} }

View File

@ -0,0 +1,13 @@
<?php
namespace App\NotificationChannels;
use App\Contracts\NotificationChannel as NotificationChannelInterface;
use App\Models\NotificationChannel;
abstract class AbstractNotificationChannel implements NotificationChannelInterface
{
public function __construct(protected NotificationChannel $notificationChannel)
{
}
}

View File

@ -1,16 +0,0 @@
<?php
namespace App\NotificationChannels;
use App\Contracts\NotificationChannel as NotificationChannelContract;
use App\Models\NotificationChannel;
abstract class AbstractProvider implements NotificationChannelContract
{
protected NotificationChannel $notificationChannel;
public function __construct(NotificationChannel $notificationChannel)
{
$this->notificationChannel = $notificationChannel;
}
}

View File

@ -2,21 +2,30 @@
namespace App\NotificationChannels; namespace App\NotificationChannels;
use App\Contracts\Notification;
use App\Models\NotificationChannel;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
class Discord extends AbstractProvider class Discord extends AbstractNotificationChannel
{ {
public function validationRules(): array public function createRules(array $input): array
{ {
return [ return [
'webhook_url' => 'required|url', 'webhook_url' => 'required|url',
]; ];
} }
public function data(array $input): array public function createData(array $input): array
{ {
return [ return [
'webhook_url' => $input['webhook_url'], 'webhook_url' => $input['webhook_url'] ?? '',
];
}
public function data(): array
{
return [
'webhook_url' => $this->notificationChannel->data['webhook_url'] ?? '',
]; ];
} }
@ -24,35 +33,39 @@ public function connect(): bool
{ {
$connect = $this->checkConnection( $connect = $this->checkConnection(
__('Congratulations! 🎉'), __('Congratulations! 🎉'),
__("You've connected your Discord to Vito")."\n". __("You've connected your Discord to :app", ['app' => config('app.name')])."\n".
__('Manage your notification channels')."\n". __('Manage your notification channels')."\n".
route('notification-channels') route('notification-channels')
); );
if (! $connect) { if (! $connect) {
$this->notificationChannel->delete();
return false; return false;
} }
$this->notificationChannel->connected = true;
$this->notificationChannel->save();
return true; return true;
} }
public function sendMessage(string $subject, string $text): void
{
dispatch(function () use ($subject, $text) {
$data = $this->notificationChannel->data;
Http::post($data['webhook_url'], [
'content' => '*'.$subject.'*'."\n".$text,
]);
});
}
private function checkConnection(string $subject, string $text): bool private function checkConnection(string $subject, string $text): bool
{ {
$data = $this->notificationChannel->data; $connect = Http::post($this->data()['webhook_url'], [
$connect = Http::post($data['webhook_url'], [
'content' => '*'.$subject.'*'."\n".$text, 'content' => '*'.$subject.'*'."\n".$text,
]); ]);
return $connect->ok(); return $connect->ok();
} }
public function send(object $notifiable, Notification $notification): void
{
/** @var NotificationChannel $notifiable */
$this->notificationChannel = $notifiable;
$data = $this->notificationChannel->data;
Http::post($data['webhook_url'], [
'content' => $notification->toSlack($notifiable),
]);
}
} }

View File

@ -2,36 +2,58 @@
namespace App\NotificationChannels; namespace App\NotificationChannels;
use App\Mail\NotificationChannelMessage; use App\Contracts\Notification;
use App\Mail\NotificationMail;
use App\Models\NotificationChannel;
use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Mail;
use Throwable;
class Email extends AbstractProvider class Email extends AbstractNotificationChannel
{ {
public function validationRules(): array public function createRules(array $input): array
{ {
return [ return [
'email' => 'required|email', 'email' => 'required|email',
]; ];
} }
public function data(array $input): array public function createData(array $input): array
{ {
return [ return [
'email' => $input['email'], 'email' => $input['email'],
]; ];
} }
public function data(): array
{
return [
'email' => $this->notificationChannel->data['email'] ?? '',
];
}
public function connect(): bool public function connect(): bool
{ {
$this->notificationChannel->connected = true; try {
$this->notificationChannel->save(); Mail::to($this->data()['email'])->send(
new NotificationMail(
'Connected to VitoDeploy',
'This email confirms that you have connected your email to VitoDeploy.'
)
);
} catch (Throwable) {
return false;
}
return true; return true;
} }
public function sendMessage(string $subject, mixed $text): void public function send(object $notifiable, Notification $notification): void
{ {
$data = $this->notificationChannel->data; /** @var NotificationChannel $notifiable */
Mail::to($data['email'])->send(new NotificationChannelMessage($subject, $text)); $this->notificationChannel = $notifiable;
$message = $notification->toEmail($notifiable);
Mail::to($this->data()['email'])->send(
new NotificationMail($message->subject, $message->render())
);
} }
} }

View File

@ -2,21 +2,30 @@
namespace App\NotificationChannels; namespace App\NotificationChannels;
use App\Contracts\Notification;
use App\Models\NotificationChannel;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
class Slack extends AbstractProvider class Slack extends AbstractNotificationChannel
{ {
public function validationRules(): array public function createRules(array $input): array
{ {
return [ return [
'webhook_url' => 'required|url', 'webhook_url' => 'required|url',
]; ];
} }
public function data(array $input): array public function createData(array $input): array
{ {
return [ return [
'webhook_url' => $input['webhook_url'], 'webhook_url' => $input['webhook_url'] ?? '',
];
}
public function data(): array
{
return [
'webhook_url' => $this->notificationChannel->data['webhook_url'] ?? '',
]; ];
} }
@ -24,35 +33,39 @@ public function connect(): bool
{ {
$connect = $this->checkConnection( $connect = $this->checkConnection(
__('Congratulations! 🎉'), __('Congratulations! 🎉'),
__("You've connected your Slack to Vito")."\n". __("You've connected your Slack to :app", ['app' => config('app.name')])."\n".
__('Manage your notification channels')."\n". __('Manage your notification channels')."\n".
route('notification-channels') route('notification-channels')
); );
if (! $connect) { if (! $connect) {
$this->notificationChannel->delete();
return false; return false;
} }
$this->notificationChannel->connected = true;
$this->notificationChannel->save();
return true; return true;
} }
public function sendMessage(string $subject, string $text): void
{
dispatch(function () use ($subject, $text) {
$data = $this->notificationChannel->data;
Http::post($data['webhook_url'], [
'text' => '*'.$subject.'*'."\n".$text,
]);
});
}
private function checkConnection(string $subject, string $text): bool private function checkConnection(string $subject, string $text): bool
{ {
$data = $this->notificationChannel->data; $connect = Http::post($this->data()['webhook_url'], [
$connect = Http::post($data['webhook_url'], [
'text' => '*'.$subject.'*'."\n".$text, 'text' => '*'.$subject.'*'."\n".$text,
]); ]);
return $connect->ok(); return $connect->ok();
} }
public function send(object $notifiable, Notification $notification): void
{
/** @var NotificationChannel $notifiable */
$this->notificationChannel = $notifiable;
$data = $this->notificationChannel->data;
Http::post($data['webhook_url'], [
'text' => $notification->toSlack($notifiable),
]);
}
} }

View File

@ -0,0 +1,65 @@
<?php
namespace App\NotificationChannels;
use App\Contracts\Notification;
use App\Models\NotificationChannel;
use Illuminate\Support\Facades\Http;
use Throwable;
class Telegram extends AbstractNotificationChannel
{
protected string $apiUrl = 'https://api.telegram.org/bot';
public function createRules(array $input): array
{
return [
'bot_token' => 'required|string',
'chat_id' => 'required',
];
}
public function createData(array $input): array
{
return [
'bot_token' => $input['bot_token'],
'chat_id' => $input['chat_id'],
];
}
public function data(): array
{
return [
'bot_token' => $this->notificationChannel->data['bot_token'] ?? '',
'chat_id' => $this->notificationChannel->data['chat_id'] ?? '',
];
}
public function connect(): bool
{
try {
$this->sendToTelegram(__('Connected!'));
} catch (Throwable) {
return false;
}
return true;
}
public function send(object $notifiable, Notification $notification): void
{
/** @var NotificationChannel $notifiable */
$this->notificationChannel = $notifiable;
$this->sendToTelegram($notification->toTelegram($notifiable));
}
private function sendToTelegram(string $text): void
{
Http::post($this->apiUrl.$this->data()['bot_token'].'/sendMessage', [
'chat_id' => $this->data()['chat_id'],
'text' => $text,
'parse_mode' => 'markdown',
'disable_web_page_preview' => true,
]);
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace App\Notifications;
use App\Contracts\Notification as NotificationInterface;
use App\Models\NotificationChannel;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Queue\SerializesModels;
abstract class AbstractNotification extends Notification implements NotificationInterface, ShouldQueue
{
use Queueable, SerializesModels;
public function via(object $notifiable): string
{
/** @var NotificationChannel $notifiable */
return get_class($notifiable->provider());
}
public function toEmail(object $notifiable): MailMessage
{
return (new MailMessage())
->subject('Notification')
->line($this->rawText());
}
public function toSlack(object $notifiable): string
{
return $this->rawText();
}
public function toDiscord(object $notifiable): string
{
return $this->rawText();
}
public function toTelegram(object $notifiable): string
{
return $this->rawText();
}
}

View File

@ -2,12 +2,11 @@
namespace App\Notifications; namespace App\Notifications;
use App\Contracts\Notification;
use App\Models\Server; use App\Models\Server;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\MailMessage;
class FailedToDeleteServerFromProvider implements Notification class FailedToDeleteServerFromProvider extends AbstractNotification
{ {
use Queueable; use Queueable;
@ -18,26 +17,18 @@ public function __construct(Server $server)
$this->server = $server; $this->server = $server;
} }
public function subject(): string public function rawText(): string
{ {
return __('Failed to delete the server from the provider!');
}
public function message(bool $mail = false): mixed
{
if ($mail) {
return $this->mail();
}
return __("We couldn't delete [:server] \nfrom :provider \nPlease check your provider and delete it manually", [ return __("We couldn't delete [:server] \nfrom :provider \nPlease check your provider and delete it manually", [
'server' => $this->server->name, 'server' => $this->server->name,
'provider' => $this->server->provider, 'provider' => $this->server->provider,
]); ]);
} }
public function mail(): MailMessage public function toEmail(object $notifiable): MailMessage
{ {
return (new MailMessage) return (new MailMessage)
->subject(__('Failed to delete the server from the provider!'))
->line("We couldn't delete [".$this->server->name.'] from '.$this->server->provider) ->line("We couldn't delete [".$this->server->name.'] from '.$this->server->provider)
->line('Please check your provider and delete it manually'); ->line('Please check your provider and delete it manually');
} }

View File

@ -1,26 +0,0 @@
<?php
namespace App\Notifications;
use App\Contracts\Notification;
use App\Models\Ssl;
class SSLExpirationAlert implements Notification
{
protected Ssl $ssl;
public function __construct(Ssl $ssl)
{
$this->ssl = $ssl;
}
public function subject(): string
{
return __('SSL expiring soon!');
}
public function message(bool $mail = false): string
{
return $this->ssl->site->domain."'s ".__('SSL is expiring on').' '.$this->ssl->expires_at->format('Y-m-d');
}
}

View File

@ -2,11 +2,10 @@
namespace App\Notifications; namespace App\Notifications;
use App\Contracts\Notification;
use App\Models\Server; use App\Models\Server;
use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\MailMessage;
class ServerDisconnected implements Notification class ServerDisconnected extends AbstractNotification
{ {
protected Server $server; protected Server $server;
@ -15,25 +14,17 @@ public function __construct(Server $server)
$this->server = $server; $this->server = $server;
} }
public function subject(): string public function rawText(): string
{ {
return __('Server disconnected!');
}
public function message(bool $mail = false): mixed
{
if ($mail) {
return $this->mail();
}
return __("We've disconnected from your server [:server]", [ return __("We've disconnected from your server [:server]", [
'server' => $this->server->name, 'server' => $this->server->name,
]); ]);
} }
public function mail(): MailMessage public function toEmail(object $notifiable): MailMessage
{ {
return (new MailMessage) return (new MailMessage)
->subject(__('Server disconnected!'))
->line("We've disconnected from your server [".$this->server->name.'].') ->line("We've disconnected from your server [".$this->server->name.'].')
->line('Please check your sever is online and make sure that has our public keys in it'); ->line('Please check your sever is online and make sure that has our public keys in it');
} }

View File

@ -2,11 +2,10 @@
namespace App\Notifications; namespace App\Notifications;
use App\Contracts\Notification;
use App\Models\Server; use App\Models\Server;
use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\MailMessage;
class ServerInstallationFailed implements Notification class ServerInstallationFailed extends AbstractNotification
{ {
protected Server $server; protected Server $server;
@ -15,26 +14,18 @@ public function __construct(Server $server)
$this->server = $server; $this->server = $server;
} }
public function subject(): string public function rawText(): string
{ {
return __('Server installation failed!');
}
public function message(bool $mail = false): mixed
{
if ($mail) {
return $this->mail();
}
return __("Installation failed for server [:server] \nCheck your server's logs \n:logs", [ return __("Installation failed for server [:server] \nCheck your server's logs \n:logs", [
'server' => $this->server->name, 'server' => $this->server->name,
'logs' => url('/servers/'.$this->server->id.'/logs'), 'logs' => url('/servers/'.$this->server->id.'/logs'),
]); ]);
} }
private function mail(): MailMessage public function toEmail(object $notifiable): MailMessage
{ {
return (new MailMessage) return (new MailMessage)
->subject(__('Server installation failed!'))
->line('Your server ['.$this->server->name.'] installation has been failed.') ->line('Your server ['.$this->server->name.'] installation has been failed.')
->line('Check your server logs') ->line('Check your server logs')
->action('View Logs', url('/servers/'.$this->server->id.'/logs')); ->action('View Logs', url('/servers/'.$this->server->id.'/logs'));

View File

@ -2,11 +2,10 @@
namespace App\Notifications; namespace App\Notifications;
use App\Contracts\Notification;
use App\Models\Server; use App\Models\Server;
use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\MailMessage;
class ServerInstallationStarted implements Notification class ServerInstallationStarted extends AbstractNotification
{ {
protected Server $server; protected Server $server;
@ -15,26 +14,18 @@ public function __construct(Server $server)
$this->server = $server; $this->server = $server;
} }
public function subject(): string public function rawText(): string
{ {
return __('Server installation started!');
}
public function message(bool $mail = false): mixed
{
if ($mail) {
return $this->mail();
}
return __("Installation started for server [:server]\nThis may take several minutes depending on many things like your server's internet speed.\nAs soon as it finishes, We will notify you through this channel.\nYou can check the progress live on your dashboard.\n:progress", [ return __("Installation started for server [:server]\nThis may take several minutes depending on many things like your server's internet speed.\nAs soon as it finishes, We will notify you through this channel.\nYou can check the progress live on your dashboard.\n:progress", [
'server' => $this->server->name, 'server' => $this->server->name,
'progress' => url('/servers/'.$this->server->id), 'progress' => url('/servers/'.$this->server->id),
]); ]);
} }
public function mail(): MailMessage public function toEmail(object $notifiable): MailMessage
{ {
return (new MailMessage) return (new MailMessage)
->subject(__('Server installation started!'))
->line('Your server\'s ['.$this->server->name.'] installation has been started.') ->line('Your server\'s ['.$this->server->name.'] installation has been started.')
->line("This may take several minutes depending on many things like your server's internet speed.") ->line("This may take several minutes depending on many things like your server's internet speed.")
->line('As soon as it finishes, We will notify you through this channel.') ->line('As soon as it finishes, We will notify you through this channel.')

View File

@ -2,11 +2,10 @@
namespace App\Notifications; namespace App\Notifications;
use App\Contracts\Notification;
use App\Models\Server; use App\Models\Server;
use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\MailMessage;
class ServerInstallationSucceed implements Notification class ServerInstallationSucceed extends AbstractNotification
{ {
protected Server $server; protected Server $server;
@ -20,14 +19,10 @@ public function subject(): string
return __('Server installation succeed!'); return __('Server installation succeed!');
} }
public function message(bool $mail = false): mixed public function rawText(): string
{ {
$this->server->refresh(); $this->server->refresh();
if ($mail) {
return $this->mail();
}
return __("Installation succeed for server [:server] \nServer IP: :ip \nUser: :user\nPassword: :password\n:link", [ return __("Installation succeed for server [:server] \nServer IP: :ip \nUser: :user\nPassword: :password\n:link", [
'server' => $this->server->name, 'server' => $this->server->name,
'ip' => $this->server->ip, 'ip' => $this->server->ip,
@ -37,11 +32,12 @@ public function message(bool $mail = false): mixed
]); ]);
} }
public function mail(): MailMessage public function toEmail(object $notifiable): MailMessage
{ {
$this->server->refresh(); $this->server->refresh();
return (new MailMessage) return (new MailMessage)
->subject(__('Server installation succeed!'))
->line('Your server ['.$this->server->name.'] has been installed successfully.') ->line('Your server ['.$this->server->name.'] has been installed successfully.')
->line('Server IP: '.$this->server->ip) ->line('Server IP: '.$this->server->ip)
->line('User: '.$this->server->authentication['user']) ->line('User: '.$this->server->authentication['user'])

View File

@ -0,0 +1,30 @@
<?php
namespace App\Notifications;
use App\Models\Site;
use Illuminate\Notifications\Messages\MailMessage;
class SiteInstallationFailed extends AbstractNotification
{
public function __construct(protected Site $site)
{
}
public function rawText(): string
{
return __("Installation failed for site [:site] \nCheck your server's logs \n:logs", [
'site' => $this->site->domain,
'logs' => url('/servers/'.$this->site->server_id.'/logs'),
]);
}
public function toEmail(object $notifiable): MailMessage
{
return (new MailMessage)
->subject(__('Site installation failed!'))
->line('Your site\'s ['.$this->site->domain.'] installation has been failed.')
->line('Check your server logs')
->action('View Logs', url('/servers/'.$this->site->server_id.'/logs'));
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace App\Notifications;
use App\Models\Site;
use Illuminate\Notifications\Messages\MailMessage;
class SiteInstallationSucceed extends AbstractNotification
{
public function __construct(protected Site $site)
{
}
public function rawText(): string
{
return __('Installation succeed for site [:site]', [
'site' => $this->site->domain,
]);
}
public function toEmail(object $notifiable): MailMessage
{
return (new MailMessage)
->subject(__('Site installation succeed!'))
->line('Your site\'s ['.$this->site->domain.'] installation has been installed.')
->line('Check your site')
->action('View Site', url('/servers/'.$this->site->server_id.'/sites/'.$this->site->id));
}
}

View File

@ -2,41 +2,26 @@
namespace App\Notifications; namespace App\Notifications;
use Illuminate\Bus\Queueable; use App\Models\SourceControl;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class SourceControlDisconnected extends Notification implements ShouldQueue class SourceControlDisconnected extends AbstractNotification
{ {
use Queueable; public function __construct(protected SourceControl $sourceControl)
protected string $sourceControl;
public function __construct(string $sourceControl)
{ {
$this->sourceControl = $sourceControl;
} }
public function via(): array public function rawText(): string
{ {
return ['mail']; return __('Source control [:sourceControl] has been disconnected from Vito', [
'sourceControl' => $this->sourceControl->profile,
]);
} }
public function toMail(): MailMessage public function toEmail(object $notifiable): MailMessage
{ {
return (new MailMessage) return (new MailMessage)
->subject('Lost connection to your '.$this->sourceControl) ->subject(__('Source control disconnected!'))
->line("We've lost connection to your $this->sourceControl account.") ->line($this->rawText());
->line("We'll not able to do any deployments until you reconnect.")
->line("To reconnect your $this->sourceControl account please click on the bellow button.")
->action('Reconnect', url('/source-controls'));
}
public function toArray(): array
{
return [
//
];
} }
} }

View File

@ -2,6 +2,7 @@
namespace App\Providers; namespace App\Providers;
use App\Helpers\Notifier;
use App\Helpers\SSH; use App\Helpers\SSH;
use App\Support\SocialiteProviders\DropboxProvider; use App\Support\SocialiteProviders\DropboxProvider;
use Illuminate\Contracts\Container\BindingResolutionException; use Illuminate\Contracts\Container\BindingResolutionException;
@ -29,6 +30,9 @@ public function boot(): void
$this->app->bind('ssh', function () { $this->app->bind('ssh', function () {
return new SSH; return new SSH;
}); });
$this->app->bind('notifier', function () {
return new Notifier;
});
$this->extendSocialite(); $this->extendSocialite();
} }

View File

@ -0,0 +1,26 @@
<?php
namespace App\SSHCommands\Website;
use App\SSHCommands\Command;
use Illuminate\Support\Facades\File;
class GetEnvCommand extends Command
{
public function __construct(
protected string $domain
) {
}
public function file(): string
{
return File::get(resource_path('commands/website/get-env.sh'));
}
public function content(): string
{
return str($this->file())
->replace('__domain__', $this->domain)
->toString();
}
}

View File

@ -4,10 +4,14 @@
use App\Enums\OperatingSystem; use App\Enums\OperatingSystem;
use App\Exceptions\CouldNotConnectToProvider; use App\Exceptions\CouldNotConnectToProvider;
use App\Facades\Notifier;
use App\Notifications\FailedToDeleteServerFromProvider;
use Aws\Ec2\Ec2Client; use Aws\Ec2\Ec2Client;
use Aws\EC2InstanceConnect\EC2InstanceConnectClient; use Aws\EC2InstanceConnect\EC2InstanceConnectClient;
use Exception; use Exception;
use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Throwable;
class AWS extends AbstractProvider class AWS extends AbstractProvider
{ {
@ -125,9 +129,8 @@ public function delete(): void
$this->ec2Client->terminateInstances([ $this->ec2Client->terminateInstances([
'InstanceIds' => [$this->server->provider_data['instance_id']], 'InstanceIds' => [$this->server->provider_data['instance_id']],
]); ]);
} catch (Exception) { } catch (Throwable) {
/** @todo notify */ Notifier::send($this->server, new FailedToDeleteServerFromProvider($this->server));
// $this->server->team->notify(new FailedToDeleteServerFromProvider($this->server));
} }
} }
} }
@ -164,7 +167,7 @@ private function createKeyPair(): void
$result = $this->ec2Client->createKeyPair([ $result = $this->ec2Client->createKeyPair([
'KeyName' => $keyName, 'KeyName' => $keyName,
]); ]);
/** @var \Illuminate\Filesystem\FilesystemAdapter $storageDisk */ /** @var FilesystemAdapter $storageDisk */
$storageDisk = Storage::disk(config('core.key_pairs_disk')); $storageDisk = Storage::disk(config('core.key_pairs_disk'));
$storageDisk->put((string) $this->server->id, $result['KeyMaterial']); $storageDisk->put((string) $this->server->id, $result['KeyMaterial']);
generate_public_key( generate_public_key(

View File

@ -14,7 +14,6 @@ public function createValidationRules(array $input): array
return [ return [
'ip' => [ 'ip' => [
'required', 'required',
'ip',
Rule::unique('servers', 'ip'), Rule::unique('servers', 'ip'),
new RestrictedIPAddressesRule(), new RestrictedIPAddressesRule(),
], ],

View File

@ -4,6 +4,8 @@
use App\Exceptions\CouldNotConnectToProvider; use App\Exceptions\CouldNotConnectToProvider;
use App\Exceptions\ServerProviderError; use App\Exceptions\ServerProviderError;
use App\Facades\Notifier;
use App\Notifications\FailedToDeleteServerFromProvider;
use Exception; use Exception;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
@ -148,10 +150,9 @@ public function delete(): void
$delete = Http::withToken($this->server->serverProvider->credentials['token']) $delete = Http::withToken($this->server->serverProvider->credentials['token'])
->delete($this->apiUrl.'/droplets/'.$this->server->provider_data['droplet_id']); ->delete($this->apiUrl.'/droplets/'.$this->server->provider_data['droplet_id']);
/** @todo notify */ if (! $delete->ok()) {
// if (! $delete->ok()) { Notifier::send($this->server, new FailedToDeleteServerFromProvider($this->server));
// $this->server->team->notify(new FailedToDeleteServerFromProvider($this->server)); }
// }
} }
} }
} }

View File

@ -4,6 +4,8 @@
use App\Exceptions\CouldNotConnectToProvider; use App\Exceptions\CouldNotConnectToProvider;
use App\Exceptions\ServerProviderError; use App\Exceptions\ServerProviderError;
use App\Facades\Notifier;
use App\Notifications\FailedToDeleteServerFromProvider;
use Illuminate\Http\Client\Response; use Illuminate\Http\Client\Response;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
@ -122,10 +124,9 @@ public function delete(): void
$delete = Http::withToken($this->server->serverProvider->credentials['token']) $delete = Http::withToken($this->server->serverProvider->credentials['token'])
->delete($this->apiUrl.'/servers/'.$this->server->provider_data['hetzner_id']); ->delete($this->apiUrl.'/servers/'.$this->server->provider_data['hetzner_id']);
/** @todo notify */ if (! $delete->ok()) {
// if (! $delete->ok()) { Notifier::send($this->server, new FailedToDeleteServerFromProvider($this->server));
// $this->server->team->notify(new FailedToDeleteServerFromProvider($this->server)); }
// }
} }
// delete key // delete key

View File

@ -4,6 +4,8 @@
use App\Exceptions\CouldNotConnectToProvider; use App\Exceptions\CouldNotConnectToProvider;
use App\Exceptions\ServerProviderError; use App\Exceptions\ServerProviderError;
use App\Facades\Notifier;
use App\Notifications\FailedToDeleteServerFromProvider;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
@ -131,10 +133,9 @@ public function delete(): void
$delete = Http::withToken($this->server->serverProvider->credentials['token']) $delete = Http::withToken($this->server->serverProvider->credentials['token'])
->delete($this->apiUrl.'/linode/instances/'.$this->server->provider_data['linode_id']); ->delete($this->apiUrl.'/linode/instances/'.$this->server->provider_data['linode_id']);
/** @todo notify */ if (! $delete->ok()) {
// if (! $delete->ok()) { Notifier::send($this->server, new FailedToDeleteServerFromProvider($this->server));
// $this->server->team->notify(new FailedToDeleteServerFromProvider($this->server)); }
// }
} }
} }
} }

View File

@ -4,6 +4,8 @@
use App\Exceptions\CouldNotConnectToProvider; use App\Exceptions\CouldNotConnectToProvider;
use App\Exceptions\ServerProviderError; use App\Exceptions\ServerProviderError;
use App\Facades\Notifier;
use App\Notifications\FailedToDeleteServerFromProvider;
use Exception; use Exception;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
@ -144,10 +146,9 @@ public function delete(): void
$delete = Http::withToken($this->server->serverProvider->credentials['token']) $delete = Http::withToken($this->server->serverProvider->credentials['token'])
->delete($this->apiUrl.'/instances/'.$this->server->provider_data['instance_id']); ->delete($this->apiUrl.'/instances/'.$this->server->provider_data['instance_id']);
/** @todo notify */ if (! $delete->ok()) {
// if (! $delete->ok()) { Notifier::send($this->server, new FailedToDeleteServerFromProvider($this->server));
// $this->server->team->notify(new FailedToDeleteServerFromProvider($this->server)); }
// }
} }
} }
} }

View File

@ -3,9 +3,12 @@
namespace App\ServerTypes; namespace App\ServerTypes;
use App\Events\Broadcast; use App\Events\Broadcast;
use App\Facades\Notifier;
use App\Jobs\Installation\Initialize; use App\Jobs\Installation\Initialize;
use App\Jobs\Installation\InstallRequirements; use App\Jobs\Installation\InstallRequirements;
use App\Jobs\Installation\Upgrade; use App\Jobs\Installation\Upgrade;
use App\Notifications\ServerInstallationFailed;
use App\Notifications\ServerInstallationSucceed;
use Illuminate\Support\Facades\Bus; use Illuminate\Support\Facades\Bus;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Throwable; use Throwable;
@ -66,7 +69,7 @@ public function install(): void
'server' => $this->server, 'server' => $this->server,
]) ])
); );
/** @todo notify */ Notifier::send($this->server, new ServerInstallationSucceed($this->server));
}; };
Bus::chain($jobs) Bus::chain($jobs)
@ -79,7 +82,7 @@ public function install(): void
'server' => $this->server, 'server' => $this->server,
]) ])
); );
/** @todo notify */ Notifier::send($this->server, new ServerInstallationFailed($this->server));
Log::error('server-installation-error', [ Log::error('server-installation-error', [
'error' => (string) $e, 'error' => (string) $e,
]); ]);

View File

@ -4,12 +4,15 @@
use App\Enums\ServerStatus; use App\Enums\ServerStatus;
use App\Events\Broadcast; use App\Events\Broadcast;
use App\Facades\Notifier;
use App\Jobs\Installation\Initialize; use App\Jobs\Installation\Initialize;
use App\Jobs\Installation\InstallCertbot; use App\Jobs\Installation\InstallCertbot;
use App\Jobs\Installation\InstallComposer; use App\Jobs\Installation\InstallComposer;
use App\Jobs\Installation\InstallNodejs; use App\Jobs\Installation\InstallNodejs;
use App\Jobs\Installation\InstallRequirements; use App\Jobs\Installation\InstallRequirements;
use App\Jobs\Installation\Upgrade; use App\Jobs\Installation\Upgrade;
use App\Notifications\ServerInstallationFailed;
use App\Notifications\ServerInstallationSucceed;
use Illuminate\Support\Facades\Bus; use Illuminate\Support\Facades\Bus;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Throwable; use Throwable;
@ -88,7 +91,7 @@ public function install(): void
'server' => $this->server, 'server' => $this->server,
]) ])
); );
/** @todo notify */ Notifier::send($this->server, new ServerInstallationSucceed($this->server));
}; };
Bus::chain($jobs) Bus::chain($jobs)
@ -101,7 +104,7 @@ public function install(): void
'server' => $this->server, 'server' => $this->server,
]) ])
); );
/** @todo notify */ Notifier::send($this->server, new ServerInstallationFailed($this->server));
Log::error('server-installation-error', [ Log::error('server-installation-error', [
'error' => (string) $e, 'error' => (string) $e,
]); ]);
@ -160,7 +163,7 @@ protected function addSupervisor(): void
} }
/** /**
* add supervisor * add redis
*/ */
protected function addRedis(): void protected function addRedis(): void
{ {
@ -172,7 +175,7 @@ protected function addRedis(): void
} }
/** /**
* add supervisor * add ufw
*/ */
protected function addUfw(): void protected function addUfw(): void
{ {

View File

@ -22,11 +22,6 @@ public function delete(): void
dispatch(new DeleteSite($this->site))->onConnection('ssh'); dispatch(new DeleteSite($this->site))->onConnection('ssh');
} }
public function install(): void
{
// TODO: Implement install() method.
}
protected function progress(int $percentage): Closure protected function progress(int $percentage): Closure
{ {
return function () use ($percentage) { return function () use ($percentage) {

67
app/SiteTypes/PHPBlank.php Executable file
View File

@ -0,0 +1,67 @@
<?php
namespace App\SiteTypes;
use App\Enums\SiteFeature;
use App\Jobs\Site\CreateVHost;
use Illuminate\Support\Facades\Bus;
use Illuminate\Validation\Rule;
use Throwable;
class PHPBlank extends PHPSite
{
public function supportedFeatures(): array
{
return [
SiteFeature::DEPLOYMENT,
SiteFeature::ENV,
SiteFeature::SSL,
SiteFeature::QUEUES,
];
}
public function createValidationRules(array $input): array
{
return [
'php_version' => [
'required',
Rule::in($this->site->server->installedPHPVersions()),
],
];
}
public function createFields(array $input): array
{
return [
'web_directory' => $input['web_directory'] ?? '',
'php_version' => $input['php_version'] ?? '',
];
}
public function data(array $input): array
{
return [];
}
public function install(): void
{
$chain = [
new CreateVHost($this->site),
$this->progress(65),
function () {
$this->site->php()?->restart();
},
];
$chain[] = function () {
$this->site->installationFinished();
};
Bus::chain($chain)
->catch(function (Throwable $e) {
$this->site->installationFailed($e);
})
->onConnection('ssh-long')
->dispatch();
}
}

View File

@ -1,5 +1,6 @@
<?php <?php
use Illuminate\Contracts\Database\Query\Expression;
use Illuminate\Support\Str; use Illuminate\Support\Str;
function random_color(): string function random_color(): string
@ -63,3 +64,10 @@ function date_with_timezone($date, $timezone): string
return $dt->format('Y-m-d H:i:s'); return $dt->format('Y-m-d H:i:s');
} }
function cast_to_json(array $json): Illuminate\Database\Query\Expression|Expression
{
$json = addslashes(json_encode($json));
return DB::raw("CAST('{$json}' AS JSON)");
}

View File

@ -15,6 +15,6 @@ public function getListeners(): array
public function refreshComponent(array $data): void public function refreshComponent(array $data): void
{ {
$this->emit('refreshComponent'); $this->dispatch('refreshComponent');
} }
} }

View File

@ -6,18 +6,22 @@
"license": "MIT", "license": "MIT",
"require": { "require": {
"php": "^8.1", "php": "^8.1",
"ext-ftp": "*",
"aws/aws-sdk-php": "^3.158", "aws/aws-sdk-php": "^3.158",
"bensampo/laravel-enum": "^6.3", "bensampo/laravel-enum": "^6.3",
"blade-ui-kit/blade-heroicons": "^2.2",
"davidhsianturi/blade-bootstrap-icons": "^1.4",
"guzzlehttp/guzzle": "^7.2", "guzzlehttp/guzzle": "^7.2",
"khatabwedaa/blade-css-icons": "^1.3",
"laravel/fortify": "^1.17", "laravel/fortify": "^1.17",
"laravel/framework": "^10.0", "laravel/framework": "^10.0",
"laravel/sanctum": "^3.2", "laravel/sanctum": "^3.2",
"laravel/socialite": "^5.2", "laravel/socialite": "^5.2",
"laravel/tinker": "^2.8", "laravel/tinker": "^2.8",
"livewire/livewire": "^2.12", "livewire/livewire": "^3.0",
"phpseclib/phpseclib": "~3.0", "opcodesio/log-viewer": "^3.0",
"opcodesio/log-viewer": "^2.5", "owenvoke/blade-fontawesome": "^2.5",
"ext-ftp": "*" "phpseclib/phpseclib": "~3.0"
}, },
"require-dev": { "require-dev": {
"fakerphp/faker": "^1.9.1", "fakerphp/faker": "^1.9.1",

588
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "bffd27708153bfce7c9dc34b975700de", "content-hash": "f2e6a21fc0abada9bc40b4e80df42b26",
"packages": [ "packages": [
{ {
"name": "aws/aws-crt-php", "name": "aws/aws-crt-php",
@ -300,6 +300,156 @@
], ],
"time": "2023-11-15T15:39:24+00:00" "time": "2023-11-15T15:39:24+00:00"
}, },
{
"name": "blade-ui-kit/blade-heroicons",
"version": "2.2.1",
"source": {
"type": "git",
"url": "https://github.com/blade-ui-kit/blade-heroicons.git",
"reference": "bcf4be8f6bbde0bb4c23f2e3fb189b88dec1580a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/blade-ui-kit/blade-heroicons/zipball/bcf4be8f6bbde0bb4c23f2e3fb189b88dec1580a",
"reference": "bcf4be8f6bbde0bb4c23f2e3fb189b88dec1580a",
"shasum": ""
},
"require": {
"blade-ui-kit/blade-icons": "^1.1",
"illuminate/support": "^9.0|^10.0",
"php": "^8.0"
},
"require-dev": {
"orchestra/testbench": "^7.0|^8.0",
"phpunit/phpunit": "^9.0"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"BladeUI\\Heroicons\\BladeHeroiconsServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"BladeUI\\Heroicons\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Dries Vints",
"homepage": "https://driesvints.com"
}
],
"description": "A package to easily make use of Heroicons in your Laravel Blade views.",
"homepage": "https://github.com/blade-ui-kit/blade-heroicons",
"keywords": [
"Heroicons",
"blade",
"laravel"
],
"support": {
"issues": "https://github.com/blade-ui-kit/blade-heroicons/issues",
"source": "https://github.com/blade-ui-kit/blade-heroicons/tree/2.2.1"
},
"funding": [
{
"url": "https://github.com/sponsors/driesvints",
"type": "github"
},
{
"url": "https://www.paypal.com/paypalme/driesvints",
"type": "paypal"
}
],
"time": "2023-12-18T20:44:03+00:00"
},
{
"name": "blade-ui-kit/blade-icons",
"version": "1.5.3",
"source": {
"type": "git",
"url": "https://github.com/blade-ui-kit/blade-icons.git",
"reference": "b5e6603218e2347ac81cb780bc6f71c8c3b31f5b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/blade-ui-kit/blade-icons/zipball/b5e6603218e2347ac81cb780bc6f71c8c3b31f5b",
"reference": "b5e6603218e2347ac81cb780bc6f71c8c3b31f5b",
"shasum": ""
},
"require": {
"illuminate/contracts": "^8.0|^9.0|^10.0",
"illuminate/filesystem": "^8.0|^9.0|^10.0",
"illuminate/support": "^8.0|^9.0|^10.0",
"illuminate/view": "^8.0|^9.0|^10.0",
"php": "^7.4|^8.0",
"symfony/console": "^5.3|^6.0",
"symfony/finder": "^5.3|^6.0"
},
"require-dev": {
"mockery/mockery": "^1.3",
"orchestra/testbench": "^6.0|^7.0|^8.0",
"phpunit/phpunit": "^9.0"
},
"bin": [
"bin/blade-icons-generate"
],
"type": "library",
"extra": {
"laravel": {
"providers": [
"BladeUI\\Icons\\BladeIconsServiceProvider"
]
}
},
"autoload": {
"files": [
"src/helpers.php"
],
"psr-4": {
"BladeUI\\Icons\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Dries Vints",
"homepage": "https://driesvints.com"
}
],
"description": "A package to easily make use of icons in your Laravel Blade views.",
"homepage": "https://github.com/blade-ui-kit/blade-icons",
"keywords": [
"blade",
"icons",
"laravel",
"svg"
],
"support": {
"issues": "https://github.com/blade-ui-kit/blade-icons/issues",
"source": "https://github.com/blade-ui-kit/blade-icons"
},
"funding": [
{
"url": "https://github.com/sponsors/driesvints",
"type": "github"
},
{
"url": "https://www.paypal.com/paypalme/driesvints",
"type": "paypal"
}
],
"time": "2023-10-18T10:50:13+00:00"
},
{ {
"name": "brick/math", "name": "brick/math",
"version": "0.11.0", "version": "0.11.0",
@ -618,6 +768,67 @@
}, },
"time": "2023-08-25T16:18:39+00:00" "time": "2023-08-25T16:18:39+00:00"
}, },
{
"name": "davidhsianturi/blade-bootstrap-icons",
"version": "v1.4.0",
"source": {
"type": "git",
"url": "https://github.com/davidhsianturi/blade-bootstrap-icons.git",
"reference": "255040a0058683dd5a0fd36dfa0857a91a95137f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/davidhsianturi/blade-bootstrap-icons/zipball/255040a0058683dd5a0fd36dfa0857a91a95137f",
"reference": "255040a0058683dd5a0fd36dfa0857a91a95137f",
"shasum": ""
},
"require": {
"blade-ui-kit/blade-icons": "^1.0",
"illuminate/support": "^8.0|^9.0|^10.0",
"php": "^7.4|^8.0"
},
"require-dev": {
"orchestra/testbench": "^6.0|^7.0|^8.0",
"phpunit/phpunit": "^9.0"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Davidhsianturi\\BladeBootstrapIcons\\BladeBootstrapIconsServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Davidhsianturi\\BladeBootstrapIcons\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "David H. Sianturi",
"email": "davidhsianturi@gmail.com",
"homepage": "https://davidhsianturi.com",
"role": "Developer"
}
],
"description": "A package to easily make use of Bootstrap Icons in your Laravel Blade views.",
"homepage": "https://github.com/davidhsianturi/blade-bootstrap-icons",
"keywords": [
"Bootstrap Icons",
"blade",
"laravel"
],
"support": {
"issues": "https://github.com/davidhsianturi/blade-bootstrap-icons/issues",
"source": "https://github.com/davidhsianturi/blade-bootstrap-icons/tree/v1.4.0"
},
"time": "2023-03-17T14:49:47+00:00"
},
{ {
"name": "dflydev/dot-access-data", "name": "dflydev/dot-access-data",
"version": "v3.0.2", "version": "v3.0.2",
@ -1533,6 +1744,67 @@
], ],
"time": "2023-12-03T19:50:20+00:00" "time": "2023-12-03T19:50:20+00:00"
}, },
{
"name": "khatabwedaa/blade-css-icons",
"version": "1.3.0",
"source": {
"type": "git",
"url": "https://github.com/khatabwedaa/blade-css-icons.git",
"reference": "a022e9a0057d9ce4f99728647fb139808c6134d8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/khatabwedaa/blade-css-icons/zipball/a022e9a0057d9ce4f99728647fb139808c6134d8",
"reference": "a022e9a0057d9ce4f99728647fb139808c6134d8",
"shasum": ""
},
"require": {
"blade-ui-kit/blade-icons": "^1.1",
"illuminate/support": "^9.0|^10",
"php": "^8.0"
},
"require-dev": {
"orchestra/testbench": "^7.0|^8.0",
"phpunit/phpunit": "^9.0"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Khatabwedaa\\BladeCssIcons\\BladeCssIconsServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Khatabwedaa\\BladeCssIcons\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Khatab Wedaa",
"email": "khatabwedaa@gmail.com",
"homepage": "https://twitter.com/khatabwedaa",
"role": "Developer"
}
],
"description": "A package to easily make use of Css.gg in your Laravel Blade views.",
"homepage": "https://github.com/khatabwedaa/blade-css-icons",
"keywords": [
"Css.gg",
"blade",
"laravel"
],
"support": {
"issues": "https://github.com/khatabwedaa/blade-css-icons/issues",
"source": "https://github.com/khatabwedaa/blade-css-icons/tree/1.3.0"
},
"time": "2023-02-04T14:04:11+00:00"
},
{ {
"name": "laminas/laminas-code", "name": "laminas/laminas-code",
"version": "4.13.0", "version": "4.13.0",
@ -2659,34 +2931,36 @@
}, },
{ {
"name": "livewire/livewire", "name": "livewire/livewire",
"version": "v2.12.6", "version": "v3.4.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/livewire/livewire.git", "url": "https://github.com/livewire/livewire.git",
"reference": "7d3a57b3193299cf1a0639a3935c696f4da2cf92" "reference": "c0489d4a76382f6dcf6e2702112f86aa089d0c8d"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/livewire/livewire/zipball/7d3a57b3193299cf1a0639a3935c696f4da2cf92", "url": "https://api.github.com/repos/livewire/livewire/zipball/c0489d4a76382f6dcf6e2702112f86aa089d0c8d",
"reference": "7d3a57b3193299cf1a0639a3935c696f4da2cf92", "reference": "c0489d4a76382f6dcf6e2702112f86aa089d0c8d",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"illuminate/database": "^7.0|^8.0|^9.0|^10.0", "illuminate/database": "^10.0|^11.0",
"illuminate/support": "^7.0|^8.0|^9.0|^10.0", "illuminate/routing": "^10.0|^11.0",
"illuminate/validation": "^7.0|^8.0|^9.0|^10.0", "illuminate/support": "^10.0|^11.0",
"illuminate/validation": "^10.0|^11.0",
"league/mime-type-detection": "^1.9", "league/mime-type-detection": "^1.9",
"php": "^7.2.5|^8.0", "php": "^8.1",
"symfony/http-kernel": "^5.0|^6.0" "symfony/http-kernel": "^6.2|^7.0"
}, },
"require-dev": { "require-dev": {
"calebporzio/sushi": "^2.1", "calebporzio/sushi": "^2.1",
"laravel/framework": "^7.0|^8.0|^9.0|^10.0", "laravel/framework": "^10.0|^11.0",
"laravel/prompts": "^0.1.6",
"mockery/mockery": "^1.3.1", "mockery/mockery": "^1.3.1",
"orchestra/testbench": "^5.0|^6.0|^7.0|^8.0", "orchestra/testbench": "8.20.0|^9.0",
"orchestra/testbench-dusk": "^5.2|^6.0|^7.0|^8.0", "orchestra/testbench-dusk": "8.20.0|^9.0",
"phpunit/phpunit": "^8.4|^9.0", "phpunit/phpunit": "^10.4",
"psy/psysh": "@stable" "psy/psysh": "^0.11.22|^0.12"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
@ -2720,7 +2994,7 @@
"description": "A front-end framework for Laravel.", "description": "A front-end framework for Laravel.",
"support": { "support": {
"issues": "https://github.com/livewire/livewire/issues", "issues": "https://github.com/livewire/livewire/issues",
"source": "https://github.com/livewire/livewire/tree/v2.12.6" "source": "https://github.com/livewire/livewire/tree/v3.4.4"
}, },
"funding": [ "funding": [
{ {
@ -2728,7 +3002,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2023-08-11T04:02:34+00:00" "time": "2024-01-28T19:07:11+00:00"
}, },
{ {
"name": "monolog/monolog", "name": "monolog/monolog",
@ -3296,20 +3570,21 @@
}, },
{ {
"name": "opcodesio/log-viewer", "name": "opcodesio/log-viewer",
"version": "v2.5.6", "version": "v3.4.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/opcodesio/log-viewer.git", "url": "https://github.com/opcodesio/log-viewer.git",
"reference": "34619b89ec0501222a661863e80dc2c92618d8f3" "reference": "f1d89dc2e54e186f6852533a165fc49a6a83fff8"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/opcodesio/log-viewer/zipball/34619b89ec0501222a661863e80dc2c92618d8f3", "url": "https://api.github.com/repos/opcodesio/log-viewer/zipball/f1d89dc2e54e186f6852533a165fc49a6a83fff8",
"reference": "34619b89ec0501222a661863e80dc2c92618d8f3", "reference": "f1d89dc2e54e186f6852533a165fc49a6a83fff8",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"illuminate/contracts": "^8.0|^9.0|^10.0", "illuminate/contracts": "^8.0|^9.0|^10.0|^11.0",
"opcodesio/mail-parser": "^0.1.6",
"php": "^8.0" "php": "^8.0"
}, },
"conflict": { "conflict": {
@ -3319,11 +3594,10 @@
"guzzlehttp/guzzle": "^7.2", "guzzlehttp/guzzle": "^7.2",
"itsgoingd/clockwork": "^5.1", "itsgoingd/clockwork": "^5.1",
"laravel/pint": "^1.0", "laravel/pint": "^1.0",
"nunomaduro/collision": "^6.0", "nunomaduro/collision": "^7.0|^8.0",
"orchestra/testbench": "^7.6|^8.0", "orchestra/testbench": "^7.6|^8.0|^9.0",
"pestphp/pest": "^1.21", "pestphp/pest": "^2.0",
"pestphp/pest-plugin-laravel": "^1.1", "pestphp/pest-plugin-laravel": "^2.0",
"phpunit/phpunit": "^9.5",
"spatie/test-time": "^1.3" "spatie/test-time": "^1.3"
}, },
"suggest": { "suggest": {
@ -3368,7 +3642,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/opcodesio/log-viewer/issues", "issues": "https://github.com/opcodesio/log-viewer/issues",
"source": "https://github.com/opcodesio/log-viewer/tree/v2.5.6" "source": "https://github.com/opcodesio/log-viewer/tree/v3.4.0"
}, },
"funding": [ "funding": [
{ {
@ -3380,7 +3654,122 @@
"type": "github" "type": "github"
} }
], ],
"time": "2023-09-03T08:22:57+00:00" "time": "2024-02-14T15:14:59+00:00"
},
{
"name": "opcodesio/mail-parser",
"version": "v0.1.6",
"source": {
"type": "git",
"url": "https://github.com/opcodesio/mail-parser.git",
"reference": "639ef31cbd146a63416283e75afce152e13233ea"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/opcodesio/mail-parser/zipball/639ef31cbd146a63416283e75afce152e13233ea",
"reference": "639ef31cbd146a63416283e75afce152e13233ea",
"shasum": ""
},
"require": {
"php": "^8.0"
},
"require-dev": {
"pestphp/pest": "^2.16",
"symfony/var-dumper": "^6.3"
},
"type": "library",
"autoload": {
"psr-4": {
"Opcodes\\MailParser\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Arunas Skirius",
"email": "arukomp@gmail.com",
"role": "Developer"
}
],
"description": "Parse emails without the mailparse extension",
"keywords": [
"arukompas",
"email",
"email parser",
"mail",
"opcodesio",
"php"
],
"support": {
"issues": "https://github.com/opcodesio/mail-parser/issues",
"source": "https://github.com/opcodesio/mail-parser/tree/v0.1.6"
},
"time": "2023-11-19T08:47:43+00:00"
},
{
"name": "owenvoke/blade-fontawesome",
"version": "v2.5.1",
"source": {
"type": "git",
"url": "https://github.com/owenvoke/blade-fontawesome.git",
"reference": "b3eac80b0f2f1b70083d4acea0da49350f88856e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/owenvoke/blade-fontawesome/zipball/b3eac80b0f2f1b70083d4acea0da49350f88856e",
"reference": "b3eac80b0f2f1b70083d4acea0da49350f88856e",
"shasum": ""
},
"require": {
"blade-ui-kit/blade-icons": "^1.5",
"illuminate/support": "^10.34",
"php": "^8.1",
"thecodingmachine/safe": "^2.5"
},
"require-dev": {
"laravel/pint": "^1.13",
"orchestra/testbench": "^8.12",
"pestphp/pest": "^2.26",
"phpstan/phpstan": "^1.10",
"symfony/var-dumper": "^6.3",
"thecodingmachine/phpstan-safe-rule": "^1.2"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"OwenVoke\\BladeFontAwesome\\BladeFontAwesomeServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"OwenVoke\\BladeFontAwesome\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "A package to easily make use of Font Awesome in your Laravel Blade views",
"support": {
"issues": "https://github.com/owenvoke/blade-fontawesome/issues",
"source": "https://github.com/owenvoke/blade-fontawesome/tree/v2.5.1"
},
"funding": [
{
"url": "https://ecologi.com/owenvoke?gift-trees",
"type": "custom"
},
{
"url": "https://github.com/owenvoke",
"type": "github"
}
],
"time": "2023-12-12T09:07:03+00:00"
}, },
{ {
"name": "paragonie/constant_time_encoding", "name": "paragonie/constant_time_encoding",
@ -6710,6 +7099,145 @@
], ],
"time": "2023-12-28T19:16:56+00:00" "time": "2023-12-28T19:16:56+00:00"
}, },
{
"name": "thecodingmachine/safe",
"version": "v2.5.0",
"source": {
"type": "git",
"url": "https://github.com/thecodingmachine/safe.git",
"reference": "3115ecd6b4391662b4931daac4eba6b07a2ac1f0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thecodingmachine/safe/zipball/3115ecd6b4391662b4931daac4eba6b07a2ac1f0",
"reference": "3115ecd6b4391662b4931daac4eba6b07a2ac1f0",
"shasum": ""
},
"require": {
"php": "^8.0"
},
"require-dev": {
"phpstan/phpstan": "^1.5",
"phpunit/phpunit": "^9.5",
"squizlabs/php_codesniffer": "^3.2",
"thecodingmachine/phpstan-strict-rules": "^1.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.2.x-dev"
}
},
"autoload": {
"files": [
"deprecated/apc.php",
"deprecated/array.php",
"deprecated/datetime.php",
"deprecated/libevent.php",
"deprecated/misc.php",
"deprecated/password.php",
"deprecated/mssql.php",
"deprecated/stats.php",
"deprecated/strings.php",
"lib/special_cases.php",
"deprecated/mysqli.php",
"generated/apache.php",
"generated/apcu.php",
"generated/array.php",
"generated/bzip2.php",
"generated/calendar.php",
"generated/classobj.php",
"generated/com.php",
"generated/cubrid.php",
"generated/curl.php",
"generated/datetime.php",
"generated/dir.php",
"generated/eio.php",
"generated/errorfunc.php",
"generated/exec.php",
"generated/fileinfo.php",
"generated/filesystem.php",
"generated/filter.php",
"generated/fpm.php",
"generated/ftp.php",
"generated/funchand.php",
"generated/gettext.php",
"generated/gmp.php",
"generated/gnupg.php",
"generated/hash.php",
"generated/ibase.php",
"generated/ibmDb2.php",
"generated/iconv.php",
"generated/image.php",
"generated/imap.php",
"generated/info.php",
"generated/inotify.php",
"generated/json.php",
"generated/ldap.php",
"generated/libxml.php",
"generated/lzf.php",
"generated/mailparse.php",
"generated/mbstring.php",
"generated/misc.php",
"generated/mysql.php",
"generated/network.php",
"generated/oci8.php",
"generated/opcache.php",
"generated/openssl.php",
"generated/outcontrol.php",
"generated/pcntl.php",
"generated/pcre.php",
"generated/pgsql.php",
"generated/posix.php",
"generated/ps.php",
"generated/pspell.php",
"generated/readline.php",
"generated/rpminfo.php",
"generated/rrd.php",
"generated/sem.php",
"generated/session.php",
"generated/shmop.php",
"generated/sockets.php",
"generated/sodium.php",
"generated/solr.php",
"generated/spl.php",
"generated/sqlsrv.php",
"generated/ssdeep.php",
"generated/ssh2.php",
"generated/stream.php",
"generated/strings.php",
"generated/swoole.php",
"generated/uodbc.php",
"generated/uopz.php",
"generated/url.php",
"generated/var.php",
"generated/xdiff.php",
"generated/xml.php",
"generated/xmlrpc.php",
"generated/yaml.php",
"generated/yaz.php",
"generated/zip.php",
"generated/zlib.php"
],
"classmap": [
"lib/DateTime.php",
"lib/DateTimeImmutable.php",
"lib/Exceptions/",
"deprecated/Exceptions/",
"generated/Exceptions/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "PHP core functions that throw exceptions instead of returning FALSE on error",
"support": {
"issues": "https://github.com/thecodingmachine/safe/issues",
"source": "https://github.com/thecodingmachine/safe/tree/v2.5.0"
},
"time": "2023-04-05T11:54:14+00:00"
},
{ {
"name": "tijsverkoyen/css-to-inline-styles", "name": "tijsverkoyen/css-to-inline-styles",
"version": "v2.2.7", "version": "v2.2.7",
@ -9428,5 +9956,5 @@
"ext-ftp": "*" "ext-ftp": "*"
}, },
"platform-dev": [], "platform-dev": [],
"plugin-api-version": "2.3.0" "plugin-api-version": "2.6.0"
} }

View File

@ -13,6 +13,7 @@
use App\NotificationChannels\Discord; use App\NotificationChannels\Discord;
use App\NotificationChannels\Email; use App\NotificationChannels\Email;
use App\NotificationChannels\Slack; use App\NotificationChannels\Slack;
use App\NotificationChannels\Telegram;
use App\ServerProviders\AWS; use App\ServerProviders\AWS;
use App\ServerProviders\DigitalOcean; use App\ServerProviders\DigitalOcean;
use App\ServerProviders\Hetzner; use App\ServerProviders\Hetzner;
@ -24,6 +25,7 @@
use App\ServiceHandlers\ProcessManager\Supervisor; use App\ServiceHandlers\ProcessManager\Supervisor;
use App\ServiceHandlers\Webserver\Nginx; use App\ServiceHandlers\Webserver\Nginx;
use App\SiteTypes\Laravel; use App\SiteTypes\Laravel;
use App\SiteTypes\PHPBlank;
use App\SiteTypes\PHPSite; use App\SiteTypes\PHPSite;
use App\SiteTypes\Wordpress; use App\SiteTypes\Wordpress;
use App\SourceControlProviders\Bitbucket; use App\SourceControlProviders\Bitbucket;
@ -262,11 +264,13 @@
*/ */
'site_types' => [ 'site_types' => [
\App\Enums\SiteType::PHP, \App\Enums\SiteType::PHP,
\App\Enums\SiteType::PHP_BLANK,
\App\Enums\SiteType::LARAVEL, \App\Enums\SiteType::LARAVEL,
\App\Enums\SiteType::WORDPRESS, \App\Enums\SiteType::WORDPRESS,
], ],
'site_types_class' => [ 'site_types_class' => [
\App\Enums\SiteType::PHP => PHPSite::class, \App\Enums\SiteType::PHP => PHPSite::class,
\App\Enums\SiteType::PHP_BLANK => PHPBlank::class,
\App\Enums\SiteType::LARAVEL => Laravel::class, \App\Enums\SiteType::LARAVEL => Laravel::class,
\App\Enums\SiteType::WORDPRESS => Wordpress::class, \App\Enums\SiteType::WORDPRESS => Wordpress::class,
], ],
@ -343,11 +347,13 @@
\App\Enums\NotificationChannel::SLACK, \App\Enums\NotificationChannel::SLACK,
\App\Enums\NotificationChannel::DISCORD, \App\Enums\NotificationChannel::DISCORD,
\App\Enums\NotificationChannel::EMAIL, \App\Enums\NotificationChannel::EMAIL,
\App\Enums\NotificationChannel::TELEGRAM,
], ],
'notification_channels_providers_class' => [ 'notification_channels_providers_class' => [
\App\Enums\NotificationChannel::SLACK => Slack::class, \App\Enums\NotificationChannel::SLACK => Slack::class,
\App\Enums\NotificationChannel::DISCORD => Discord::class, \App\Enums\NotificationChannel::DISCORD => Discord::class,
\App\Enums\NotificationChannel::EMAIL => Email::class, \App\Enums\NotificationChannel::EMAIL => Email::class,
\App\Enums\NotificationChannel::TELEGRAM => Telegram::class,
], ],
/* /*

159
config/livewire.php Normal file
View File

@ -0,0 +1,159 @@
<?php
return [
/*
|---------------------------------------------------------------------------
| Class Namespace
|---------------------------------------------------------------------------
|
| This value sets the root class namespace for Livewire component classes in
| your application. This value will change where component auto-discovery
| finds components. It's also referenced by the file creation commands.
|
*/
'class_namespace' => 'App\\Http\\Livewire',
/*
|---------------------------------------------------------------------------
| View Path
|---------------------------------------------------------------------------
|
| This value is used to specify where Livewire component Blade templates are
| stored when running file creation commands like `artisan make:livewire`.
| It is also used if you choose to omit a component's render() method.
|
*/
'view_path' => resource_path('views/livewire'),
/*
|---------------------------------------------------------------------------
| Layout
|---------------------------------------------------------------------------
| The view that will be used as the layout when rendering a single component
| as an entire page via `Route::get('/post/create', CreatePost::class);`.
| In this case, the view returned by CreatePost will render into $slot.
|
*/
'layout' => 'layouts.app',
/*
|---------------------------------------------------------------------------
| Lazy Loading Placeholder
|---------------------------------------------------------------------------
| Livewire allows you to lazy load components that would otherwise slow down
| the initial page load. Every component can have a custom placeholder or
| you can define the default placeholder view for all components below.
|
*/
'lazy_placeholder' => null,
/*
|---------------------------------------------------------------------------
| Temporary File Uploads
|---------------------------------------------------------------------------
|
| Livewire handles file uploads by storing uploads in a temporary directory
| before the file is stored permanently. All file uploads are directed to
| a global endpoint for temporary storage. You may configure this below:
|
*/
'temporary_file_upload' => [
'disk' => null, // Example: 'local', 's3' | Default: 'default'
'rules' => null, // Example: ['file', 'mimes:png,jpg'] | Default: ['required', 'file', 'max:12288'] (12MB)
'directory' => null, // Example: 'tmp' | Default: 'livewire-tmp'
'middleware' => null, // Example: 'throttle:5,1' | Default: 'throttle:60,1'
'preview_mimes' => [ // Supported file types for temporary pre-signed file URLs...
'png', 'gif', 'bmp', 'svg', 'wav', 'mp4',
'mov', 'avi', 'wmv', 'mp3', 'm4a',
'jpg', 'jpeg', 'mpga', 'webp', 'wma',
],
'max_upload_time' => 5, // Max duration (in minutes) before an upload is invalidated...
],
/*
|---------------------------------------------------------------------------
| Render On Redirect
|---------------------------------------------------------------------------
|
| This value determines if Livewire will run a component's `render()` method
| after a redirect has been triggered using something like `redirect(...)`
| Setting this to true will render the view once more before redirecting
|
*/
'render_on_redirect' => false,
/*
|---------------------------------------------------------------------------
| Eloquent Model Binding
|---------------------------------------------------------------------------
|
| Previous versions of Livewire supported binding directly to eloquent model
| properties using wire:model by default. However, this behavior has been
| deemed too "magical" and has therefore been put under a feature flag.
|
*/
'legacy_model_binding' => false,
/*
|---------------------------------------------------------------------------
| Auto-inject Frontend Assets
|---------------------------------------------------------------------------
|
| By default, Livewire automatically injects its JavaScript and CSS into the
| <head> and <body> of pages containing Livewire components. By disabling
| this behavior, you need to use @livewireStyles and @livewireScripts.
|
*/
'inject_assets' => true,
/*
|---------------------------------------------------------------------------
| Navigate (SPA mode)
|---------------------------------------------------------------------------
|
| By adding `wire:navigate` to links in your Livewire application, Livewire
| will prevent the default link handling and instead request those pages
| via AJAX, creating an SPA-like effect. Configure this behavior here.
|
*/
'navigate' => [
'show_progress_bar' => true,
'progress_bar_color' => '#2299dd',
],
/*
|---------------------------------------------------------------------------
| HTML Morph Markers
|---------------------------------------------------------------------------
|
| Livewire intelligently "morphs" existing HTML into the newly rendered HTML
| after each update. To make this process more reliable, Livewire injects
| "markers" into the rendered Blade surrounding @if, @class & @foreach.
|
*/
'inject_morph_markers' => true,
/*
|---------------------------------------------------------------------------
| Pagination Theme
|---------------------------------------------------------------------------
|
| When enabling Livewire's pagination feature by using the `WithPagination`
| trait, Livewire will use Tailwind templates to render pagination views
| on the page. If you want Bootstrap CSS, you can specify: "bootstrap"
|
*/
'pagination_theme' => 'tailwind',
];

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