@@ -3,7 +3,6 @@
|
||||
/public/build
|
||||
/public/hot
|
||||
/public/storage
|
||||
/storage/*.key
|
||||
/vendor
|
||||
.env
|
||||
.env.backup
|
||||
@@ -25,3 +24,15 @@ yarn-error.log
|
||||
.ignition.json
|
||||
.env.dusk.local
|
||||
docker/coolify-realtime/node_modules
|
||||
|
||||
/storage/*.key
|
||||
/storage/app/backups
|
||||
/storage/app/ssh/keys
|
||||
/storage/app/ssh/mux
|
||||
/storage/app/tmp
|
||||
/storage/app/debugbar
|
||||
/storage/logs
|
||||
/storage/pail
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: docker/prod/Dockerfile
|
||||
file: docker/production/Dockerfile
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: |
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: docker/prod/Dockerfile
|
||||
file: docker/production/Dockerfile
|
||||
platforms: linux/aarch64
|
||||
push: true
|
||||
tags: |
|
||||
|
||||
4
.github/workflows/coolify-staging-build.yml
vendored
4
.github/workflows/coolify-staging-build.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: docker/prod/Dockerfile
|
||||
file: docker/production/Dockerfile
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: |
|
||||
@@ -75,7 +75,7 @@ jobs:
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: docker/prod/Dockerfile
|
||||
file: docker/production/Dockerfile
|
||||
platforms: linux/aarch64
|
||||
push: true
|
||||
tags: |
|
||||
|
||||
10
README.md
10
README.md
@@ -40,7 +40,8 @@ Special thanks to our biggest sponsors!
|
||||
|
||||
### Special Sponsors
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
* [CCCareers](https://cccareers.org/) - A career development platform connecting coding bootcamp graduates with job opportunities in the tech industry.
|
||||
* [Hetzner](http://htznr.li/CoolifyXHetzner) - A German web hosting company offering affordable dedicated servers, cloud services, and web hosting solutions.
|
||||
@@ -52,7 +53,9 @@ Special thanks to our biggest sponsors!
|
||||
* [SupaGuide](https://supa.guide/?ref=coolify.io) - A comprehensive resource hub offering guides and tutorials for web development using Supabase.
|
||||
* [GoldenVM](https://billing.goldenvm.com/?ref=coolify.io) - A cloud hosting provider offering scalable infrastructure solutions for businesses of all sizes.
|
||||
* [Tigris](https://tigrisdata.com/?ref=coolify.io) - A fully managed serverless object storage service compatible with Amazon S3 API. Offers high performance, scalability, and built-in search capabilities for efficient data management.
|
||||
* [Advin](https://coolify.ad.vin/?ref=coolify.io) - A digital advertising agency specializing in programmatic advertising and data-driven marketing strategies.
|
||||
* [Cloudify.ro](https://cloudify.ro/?ref=coolify.io) - A cloud hosting provider offering scalable infrastructure solutions for businesses of all sizes.
|
||||
* [Syntaxfm](https://syntax.fm/?ref=coolify.io) - Podcast for web developers.
|
||||
* [PFGlabs](https://pfglabs.com/?ref=coolify.io) - Build real project with Golang.
|
||||
* [Treive](https://trieve.ai/?ref=coolify.io) - An AI-powered search and discovery platform for enhancing information retrieval in large datasets.
|
||||
* [Blacksmith](https://blacksmith.sh/?ref=coolify.io) - A cloud-native platform for automating infrastructure provisioning and management across multiple cloud providers.
|
||||
* [Brand Dev](https://brand.dev/?ref=coolify.io) - A web development agency specializing in creating custom digital experiences and brand identities.
|
||||
@@ -92,6 +95,9 @@ Special thanks to our biggest sponsors!
|
||||
<a href="https://github.com/monocursive"><img src="https://github.com/monocursive.png" width="60px" alt="Michael Mazurczak" /></a>
|
||||
<a href="https://formbricks.com/?utm_source=coolify.io"><img src="https://github.com/formbricks.png" width="60px" alt="Formbricks" /></a>
|
||||
<a href="https://startupfa.me?utm_source=coolify.io"><img src="https://github.com/startupfame.png" width="60px" alt="StartupFame" /></a>
|
||||
<a href="https://bsky.app/profile/jyc.dev"><img src="https://github.com/jycouet.png" width="60px" alt="jyc.dev" /></a>
|
||||
<a href="https://bitlaunch.io/?utm_source=coolify.io"><img src="https://github.com/bitlaunchio.png" width="60px" alt="BitLaunch" /></a>
|
||||
<a href="https://internetgarden.co/?utm_source=coolify.io"><img src="./other/logos/internetgarden.ico" width="60px" alt="Internet Garden" /></a>
|
||||
<a href="https://jonasjaeger.com?utm_source=coolify.io"><img src="https://github.com/toxin20.png" width="60px" alt="Jonas Jaeger" /></a>
|
||||
<a href="https://github.com/therealjp?utm_source=coolify.io"><img src="https://github.com/therealjp.png" width="60px" alt="JP" /></a>
|
||||
<a href="https://evercam.io/?utm_source=coolify.io"><img src="https://github.com/evercam.png" width="60px" alt="Evercam" /></a>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Actions\Proxy;
|
||||
|
||||
use App\Enums\ProxyTypes;
|
||||
use App\Events\ProxyStarted;
|
||||
use App\Models\Server;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
@@ -37,11 +38,16 @@ class StartProxy
|
||||
"echo 'Successfully started coolify-proxy.'",
|
||||
]);
|
||||
} else {
|
||||
$caddfile = 'import /dynamic/*.caddy';
|
||||
if (isDev()) {
|
||||
if ($proxyType === ProxyTypes::CADDY->value) {
|
||||
$proxy_path = '/data/coolify/proxy/caddy';
|
||||
}
|
||||
}
|
||||
$caddyfile = 'import /dynamic/*.caddy';
|
||||
$commands = $commands->merge([
|
||||
"mkdir -p $proxy_path/dynamic",
|
||||
"cd $proxy_path",
|
||||
"echo '$caddfile' > $proxy_path/dynamic/Caddyfile",
|
||||
"echo '$caddyfile' > $proxy_path/dynamic/Caddyfile",
|
||||
"echo 'Creating required Docker Compose file.'",
|
||||
"echo 'Pulling docker image.'",
|
||||
'docker compose pull',
|
||||
|
||||
@@ -18,7 +18,6 @@ class CleanupUnreachableServers extends Command
|
||||
if ($servers->count() > 0) {
|
||||
foreach ($servers as $server) {
|
||||
echo "Cleanup unreachable server ($server->id) with name $server->name";
|
||||
// send_internal_notification("Server $server->name is unreachable for 7 days. Cleaning up...");
|
||||
$server->update([
|
||||
'ip' => '1.2.3.4',
|
||||
]);
|
||||
|
||||
@@ -76,7 +76,5 @@ class Dev extends Command
|
||||
} else {
|
||||
echo "Instance already initialized.\n";
|
||||
}
|
||||
// Set permissions
|
||||
Process::run(['chmod', '-R', 'o+rwx', '.']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,12 @@
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Jobs\SendConfirmationForWaitlistJob;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationPreview;
|
||||
use App\Models\ScheduledDatabaseBackup;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\Team;
|
||||
use App\Models\Waitlist;
|
||||
use App\Notifications\Application\DeploymentFailed;
|
||||
use App\Notifications\Application\DeploymentSuccess;
|
||||
use App\Notifications\Application\StatusChanged;
|
||||
@@ -64,8 +62,6 @@ class Emails extends Command
|
||||
'backup-success' => 'Database - Backup Success',
|
||||
'backup-failed' => 'Database - Backup Failed',
|
||||
// 'invitation-link' => 'Invitation Link',
|
||||
'waitlist-invitation-link' => 'Waitlist Invitation Link',
|
||||
'waitlist-confirmation' => 'Waitlist Confirmation',
|
||||
'realusers-before-trial' => 'REAL - Registered Users Before Trial without Subscription',
|
||||
'realusers-server-lost-connection' => 'REAL - Server Lost Connection',
|
||||
],
|
||||
@@ -187,7 +183,7 @@ class Emails extends Command
|
||||
'team_id' => 0,
|
||||
]);
|
||||
}
|
||||
// $this->mail = (new BackupSuccess($backup->frequency, $db->name))->toMail();
|
||||
//$this->mail = (new BackupSuccess($backup->frequency, $db->name))->toMail();
|
||||
$this->sendEmail();
|
||||
break;
|
||||
// case 'invitation-link':
|
||||
@@ -204,23 +200,6 @@ class Emails extends Command
|
||||
// $this->mail = (new InvitationLink($user))->toMail();
|
||||
// $this->sendEmail();
|
||||
// break;
|
||||
case 'waitlist-invitation-link':
|
||||
$this->mail = new MailMessage;
|
||||
$this->mail->view('emails.waitlist-invitation', [
|
||||
'loginLink' => 'https://coolify.io',
|
||||
]);
|
||||
$this->mail->subject('Congratulations! You are invited to join Coolify Cloud.');
|
||||
$this->sendEmail();
|
||||
break;
|
||||
case 'waitlist-confirmation':
|
||||
$found = Waitlist::where('email', $this->email)->first();
|
||||
if ($found) {
|
||||
SendConfirmationForWaitlistJob::dispatch($this->email, $found->uuid);
|
||||
} else {
|
||||
throw new Exception('Waitlist not found');
|
||||
}
|
||||
|
||||
break;
|
||||
case 'realusers-before-trial':
|
||||
$this->mail = new MailMessage;
|
||||
$this->mail->view('emails.before-trial-conversion');
|
||||
|
||||
@@ -13,7 +13,7 @@ class Horizon extends Command
|
||||
public function handle()
|
||||
{
|
||||
if (config('constants.horizon.is_horizon_enabled')) {
|
||||
$this->info('[x]: Horizon is enabled. Starting.');
|
||||
$this->info('Horizon is enabled on this server.');
|
||||
$this->call('horizon');
|
||||
exit(0);
|
||||
} else {
|
||||
|
||||
@@ -55,10 +55,8 @@ class Init extends Command
|
||||
} else {
|
||||
$this->cleanup_in_progress_application_deployments();
|
||||
}
|
||||
echo "[3]: Cleanup Redis keys.\n";
|
||||
$this->call('cleanup:redis');
|
||||
|
||||
echo "[4]: Cleanup stucked resources.\n";
|
||||
$this->call('cleanup:stucked-resources');
|
||||
|
||||
try {
|
||||
@@ -114,7 +112,6 @@ class Init extends Command
|
||||
|
||||
private function optimize()
|
||||
{
|
||||
echo "[1]: Optimizing Laravel (caching config, routes, views).\n";
|
||||
Artisan::call('optimize:clear');
|
||||
Artisan::call('optimize');
|
||||
}
|
||||
@@ -189,7 +186,6 @@ class Init extends Command
|
||||
}
|
||||
}
|
||||
if ($commands->isNotEmpty()) {
|
||||
echo "Cleaning up unused networks from coolify proxy\n";
|
||||
remote_process(command: $commands, type: ActivityTypes::INLINE->value, server: $server, ignore_errors: false);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
@@ -232,15 +228,14 @@ class Init extends Command
|
||||
$settings = instanceSettings();
|
||||
$do_not_track = data_get($settings, 'do_not_track');
|
||||
if ($do_not_track == true) {
|
||||
echo "[2]: Skipping sending live signal as do_not_track is enabled\n";
|
||||
echo "Do_not_track is enabled\n";
|
||||
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Http::get("https://undead.coolify.io/v4/alive?appId=$id&version=$version");
|
||||
echo "[2]: Sending live signal!\n";
|
||||
} catch (\Throwable $e) {
|
||||
echo "[2]: Error in sending live signal: {$e->getMessage()}\n";
|
||||
echo "Error in sending live signal: {$e->getMessage()}\n";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,7 +248,6 @@ class Init extends Command
|
||||
}
|
||||
$queued_inprogress_deployments = ApplicationDeploymentQueue::whereIn('status', [ApplicationDeploymentStatus::IN_PROGRESS->value, ApplicationDeploymentStatus::QUEUED->value])->get();
|
||||
foreach ($queued_inprogress_deployments as $deployment) {
|
||||
echo "Cleaning up deployment: {$deployment->id}\n";
|
||||
$deployment->status = ApplicationDeploymentStatus::FAILED->value;
|
||||
$deployment->save();
|
||||
}
|
||||
|
||||
24
app/Console/Commands/Migration.php
Normal file
24
app/Console/Commands/Migration.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class Migration extends Command
|
||||
{
|
||||
protected $signature = 'start:migration';
|
||||
|
||||
protected $description = 'Start Migration';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
if (config('constants.migration.is_migration_enabled')) {
|
||||
$this->info('Migration is enabled on this server.');
|
||||
$this->call('migrate', ['--force' => true, '--isolated' => true]);
|
||||
exit(0);
|
||||
} else {
|
||||
$this->info('Migration is disabled on this server.');
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,9 +59,10 @@ class NotifyDemo extends Command
|
||||
<div class="text-yellow-500"> Channels: </div>
|
||||
<ul class="text-coolify">
|
||||
<li>email</li>
|
||||
<li>slack</li>
|
||||
<li>discord</li>
|
||||
<li>telegram</li>
|
||||
<li>slack</li>
|
||||
<li>pushover</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -72,6 +73,6 @@ class NotifyDemo extends Command
|
||||
<div class="mr-1">
|
||||
In which manner you wish a <strong class="text-coolify">coolified</strong> notification?
|
||||
</div>
|
||||
HTML, ['email', 'slack', 'discord', 'telegram']);
|
||||
HTML, ['email', 'discord', 'telegram', 'slack', 'pushover']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ class Scheduler extends Command
|
||||
public function handle()
|
||||
{
|
||||
if (config('constants.horizon.is_scheduler_enabled')) {
|
||||
$this->info('[x]: Scheduler is enabled. Starting.');
|
||||
$this->info('Scheduler is enabled on this server.');
|
||||
$this->call('schedule:work');
|
||||
exit(0);
|
||||
} else {
|
||||
|
||||
24
app/Console/Commands/Seeder.php
Normal file
24
app/Console/Commands/Seeder.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class Seeder extends Command
|
||||
{
|
||||
protected $signature = 'start:seeder';
|
||||
|
||||
protected $description = 'Start Seeder';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
if (config('constants.seeder.is_seeder_enabled')) {
|
||||
$this->info('Seeder is enabled on this server.');
|
||||
$this->call('db:seed', ['--class' => 'ProductionSeeder', '--force' => true]);
|
||||
exit(0);
|
||||
} else {
|
||||
$this->info('Seeder is disabled on this server.');
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\Waitlist;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class WaitlistInvite extends Command
|
||||
{
|
||||
public Waitlist|User|null $next_patient = null;
|
||||
|
||||
public ?string $password = null;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'waitlist:invite {--people=1} {--only-email} {email?}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Send invitation to the next user (or by email) in the waitlist';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$people = $this->option('people');
|
||||
for ($i = 0; $i < $people; $i++) {
|
||||
$this->main();
|
||||
}
|
||||
}
|
||||
|
||||
private function main()
|
||||
{
|
||||
if ($this->argument('email')) {
|
||||
if ($this->option('only-email')) {
|
||||
$this->next_patient = User::whereEmail($this->argument('email'))->first();
|
||||
$this->password = Str::password();
|
||||
$this->next_patient->update([
|
||||
'password' => Hash::make($this->password),
|
||||
'force_password_reset' => true,
|
||||
]);
|
||||
} else {
|
||||
$this->next_patient = Waitlist::where('email', $this->argument('email'))->first();
|
||||
}
|
||||
if (! $this->next_patient) {
|
||||
$this->error("{$this->argument('email')} not found in the waitlist.");
|
||||
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
$this->next_patient = Waitlist::orderBy('created_at', 'asc')->where('verified', true)->first();
|
||||
}
|
||||
if ($this->next_patient) {
|
||||
if ($this->option('only-email')) {
|
||||
$this->send_email();
|
||||
|
||||
return;
|
||||
}
|
||||
$this->register_user();
|
||||
$this->remove_from_waitlist();
|
||||
$this->send_email();
|
||||
} else {
|
||||
$this->info('No verified user found in the waitlist. 👀');
|
||||
}
|
||||
}
|
||||
|
||||
private function register_user()
|
||||
{
|
||||
$already_registered = User::whereEmail($this->next_patient->email)->first();
|
||||
if (! $already_registered) {
|
||||
$this->password = Str::password();
|
||||
User::create([
|
||||
'name' => str($this->next_patient->email)->before('@'),
|
||||
'email' => $this->next_patient->email,
|
||||
'password' => Hash::make($this->password),
|
||||
'force_password_reset' => true,
|
||||
]);
|
||||
$this->info("User registered ({$this->next_patient->email}) successfully. 🎉");
|
||||
} else {
|
||||
throw new \Exception('User already registered');
|
||||
}
|
||||
}
|
||||
|
||||
private function remove_from_waitlist()
|
||||
{
|
||||
$this->next_patient->delete();
|
||||
$this->info('User removed from waitlist successfully.');
|
||||
}
|
||||
|
||||
private function send_email()
|
||||
{
|
||||
$token = Crypt::encryptString("{$this->next_patient->email}@@@$this->password");
|
||||
$loginLink = route('auth.link', ['token' => $token]);
|
||||
$mail = new MailMessage;
|
||||
$mail->view('emails.waitlist-invitation', [
|
||||
'loginLink' => $loginLink,
|
||||
]);
|
||||
$mail->subject('Congratulations! You are invited to join Coolify Cloud.');
|
||||
send_user_an_email($mail, $this->next_patient->email);
|
||||
$this->info('Email sent successfully. 📧');
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Actions\Server\ServerCheck;
|
||||
use App\Enums\ProxyStatus;
|
||||
use App\Enums\ProxyTypes;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Console\Command;
|
||||
use Str;
|
||||
|
||||
class Weird extends Command
|
||||
{
|
||||
protected $signature = 'weird {--number=1} {--run}';
|
||||
|
||||
protected $description = 'Weird stuff';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
try {
|
||||
if (! isDev()) {
|
||||
$this->error('This command can only be run in development mode');
|
||||
|
||||
return;
|
||||
}
|
||||
$run = $this->option('run');
|
||||
if ($run) {
|
||||
$servers = Server::all();
|
||||
foreach ($servers as $server) {
|
||||
ServerCheck::dispatch($server);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
$number = $this->option('number');
|
||||
for ($i = 0; $i < $number; $i++) {
|
||||
$uuid = Str::uuid();
|
||||
$server = Server::create([
|
||||
'name' => 'localhost-'.$uuid,
|
||||
'description' => 'This is a test docker container in development mode',
|
||||
'ip' => 'coolify-testing-host',
|
||||
'team_id' => 0,
|
||||
'private_key_id' => 1,
|
||||
'proxy' => [
|
||||
'type' => ProxyTypes::NONE->value,
|
||||
'status' => ProxyStatus::EXITED->value,
|
||||
],
|
||||
]);
|
||||
$server->settings->update([
|
||||
'is_usable' => true,
|
||||
'is_reachable' => true,
|
||||
]);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,13 +25,10 @@ class ApplicationsController extends Controller
|
||||
{
|
||||
private function removeSensitiveData($application)
|
||||
{
|
||||
$token = auth()->user()->currentAccessToken();
|
||||
$application->makeHidden([
|
||||
'id',
|
||||
]);
|
||||
if ($token->can('view:sensitive')) {
|
||||
return serializeApiResponse($application);
|
||||
}
|
||||
if (request()->attributes->get('can_read_sensitive', false) === false) {
|
||||
$application->makeHidden([
|
||||
'custom_labels',
|
||||
'dockerfile',
|
||||
@@ -45,6 +42,7 @@ class ApplicationsController extends Controller
|
||||
'value',
|
||||
'real_value',
|
||||
]);
|
||||
}
|
||||
|
||||
return serializeApiResponse($application);
|
||||
}
|
||||
|
||||
@@ -19,15 +19,11 @@ class DatabasesController extends Controller
|
||||
{
|
||||
private function removeSensitiveData($database)
|
||||
{
|
||||
$token = auth()->user()->currentAccessToken();
|
||||
$database->makeHidden([
|
||||
'id',
|
||||
'laravel_through_key',
|
||||
]);
|
||||
if ($token->can('view:sensitive')) {
|
||||
return serializeApiResponse($database);
|
||||
}
|
||||
|
||||
if (request()->attributes->get('can_read_sensitive', false) === false) {
|
||||
$database->makeHidden([
|
||||
'internal_db_url',
|
||||
'external_db_url',
|
||||
@@ -38,6 +34,7 @@ class DatabasesController extends Controller
|
||||
'keydb_password',
|
||||
'clickhouse_admin_password',
|
||||
]);
|
||||
}
|
||||
|
||||
return serializeApiResponse($database);
|
||||
}
|
||||
@@ -211,8 +208,9 @@ class DatabasesController extends Controller
|
||||
'mongo_conf' => ['type' => 'string', 'description' => 'Mongo conf'],
|
||||
'mongo_initdb_root_username' => ['type' => 'string', 'description' => 'Mongo initdb root username'],
|
||||
'mongo_initdb_root_password' => ['type' => 'string', 'description' => 'Mongo initdb root password'],
|
||||
'mongo_initdb_init_database' => ['type' => 'string', 'description' => 'Mongo initdb init database'],
|
||||
'mongo_initdb_database' => ['type' => 'string', 'description' => 'Mongo initdb init database'],
|
||||
'mysql_root_password' => ['type' => 'string', 'description' => 'MySQL root password'],
|
||||
'mysql_password' => ['type' => 'string', 'description' => 'MySQL password'],
|
||||
'mysql_user' => ['type' => 'string', 'description' => 'MySQL user'],
|
||||
'mysql_database' => ['type' => 'string', 'description' => 'MySQL database'],
|
||||
'mysql_conf' => ['type' => 'string', 'description' => 'MySQL conf'],
|
||||
@@ -241,7 +239,7 @@ class DatabasesController extends Controller
|
||||
)]
|
||||
public function update_by_uuid(Request $request)
|
||||
{
|
||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'postgres_user', 'postgres_password', 'postgres_db', 'postgres_initdb_args', 'postgres_host_auth_method', 'postgres_conf', 'clickhouse_admin_user', 'clickhouse_admin_password', 'dragonfly_password', 'redis_password', 'redis_conf', 'keydb_password', 'keydb_conf', 'mariadb_conf', 'mariadb_root_password', 'mariadb_user', 'mariadb_password', 'mariadb_database', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_init_database', 'mysql_root_password', 'mysql_user', 'mysql_database', 'mysql_conf'];
|
||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'postgres_user', 'postgres_password', 'postgres_db', 'postgres_initdb_args', 'postgres_host_auth_method', 'postgres_conf', 'clickhouse_admin_user', 'clickhouse_admin_password', 'dragonfly_password', 'redis_password', 'redis_conf', 'keydb_password', 'keydb_conf', 'mariadb_conf', 'mariadb_root_password', 'mariadb_user', 'mariadb_password', 'mariadb_database', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_database', 'mysql_root_password', 'mysql_password', 'mysql_user', 'mysql_database', 'mysql_conf'];
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
@@ -413,12 +411,12 @@ class DatabasesController extends Controller
|
||||
}
|
||||
break;
|
||||
case 'standalone-mongodb':
|
||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_init_database'];
|
||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_database'];
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'mongo_conf' => 'string',
|
||||
'mongo_initdb_root_username' => 'string',
|
||||
'mongo_initdb_root_password' => 'string',
|
||||
'mongo_initdb_init_database' => 'string',
|
||||
'mongo_initdb_database' => 'string',
|
||||
]);
|
||||
if ($request->has('mongo_conf')) {
|
||||
if (! isBase64Encoded($request->mongo_conf)) {
|
||||
@@ -443,9 +441,10 @@ class DatabasesController extends Controller
|
||||
|
||||
break;
|
||||
case 'standalone-mysql':
|
||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mysql_root_password', 'mysql_user', 'mysql_database', 'mysql_conf'];
|
||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mysql_root_password', 'mysql_password', 'mysql_user', 'mysql_database', 'mysql_conf'];
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'mysql_root_password' => 'string',
|
||||
'mysql_password' => 'string',
|
||||
'mysql_user' => 'string',
|
||||
'mysql_database' => 'string',
|
||||
'mysql_conf' => 'string',
|
||||
@@ -909,6 +908,7 @@ class DatabasesController extends Controller
|
||||
'environment_name' => ['type' => 'string', 'description' => 'Name of the environment'],
|
||||
'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'],
|
||||
'mysql_root_password' => ['type' => 'string', 'description' => 'MySQL root password'],
|
||||
'mysql_password' => ['type' => 'string', 'description' => 'MySQL password'],
|
||||
'mysql_user' => ['type' => 'string', 'description' => 'MySQL user'],
|
||||
'mysql_database' => ['type' => 'string', 'description' => 'MySQL database'],
|
||||
'mysql_conf' => ['type' => 'string', 'description' => 'MySQL conf'],
|
||||
@@ -1013,7 +1013,7 @@ class DatabasesController extends Controller
|
||||
|
||||
public function create_database(Request $request, NewDatabaseTypes $type)
|
||||
{
|
||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'postgres_user', 'postgres_password', 'postgres_db', 'postgres_initdb_args', 'postgres_host_auth_method', 'postgres_conf', 'clickhouse_admin_user', 'clickhouse_admin_password', 'dragonfly_password', 'redis_password', 'redis_conf', 'keydb_password', 'keydb_conf', 'mariadb_conf', 'mariadb_root_password', 'mariadb_user', 'mariadb_password', 'mariadb_database', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_init_database', 'mysql_root_password', 'mysql_user', 'mysql_database', 'mysql_conf'];
|
||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'postgres_user', 'postgres_password', 'postgres_db', 'postgres_initdb_args', 'postgres_host_auth_method', 'postgres_conf', 'clickhouse_admin_user', 'clickhouse_admin_password', 'dragonfly_password', 'redis_password', 'redis_conf', 'keydb_password', 'keydb_conf', 'mariadb_conf', 'mariadb_root_password', 'mariadb_user', 'mariadb_password', 'mariadb_database', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_database', 'mysql_root_password', 'mysql_password', 'mysql_user', 'mysql_database', 'mysql_conf'];
|
||||
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
@@ -1220,9 +1220,10 @@ class DatabasesController extends Controller
|
||||
|
||||
return response()->json(serializeApiResponse($payload))->setStatusCode(201);
|
||||
} elseif ($type === NewDatabaseTypes::MYSQL) {
|
||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mysql_user', 'mysql_database', 'mysql_conf'];
|
||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mysql_root_password', 'mysql_password', 'mysql_user', 'mysql_database', 'mysql_conf'];
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'mysql_root_password' => 'string',
|
||||
'mysql_password' => 'string',
|
||||
'mysql_user' => 'string',
|
||||
'mysql_database' => 'string',
|
||||
'mysql_conf' => 'string',
|
||||
@@ -1456,12 +1457,12 @@ class DatabasesController extends Controller
|
||||
|
||||
return response()->json(serializeApiResponse($payload))->setStatusCode(201);
|
||||
} elseif ($type === NewDatabaseTypes::MONGODB) {
|
||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_init_database'];
|
||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_database'];
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'mongo_conf' => 'string',
|
||||
'mongo_initdb_root_username' => 'string',
|
||||
'mongo_initdb_root_password' => 'string',
|
||||
'mongo_initdb_init_database' => 'string',
|
||||
'mongo_initdb_database' => 'string',
|
||||
]);
|
||||
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||
if ($validator->fails() || ! empty($extraFields)) {
|
||||
|
||||
@@ -16,14 +16,11 @@ class DeployController extends Controller
|
||||
{
|
||||
private function removeSensitiveData($deployment)
|
||||
{
|
||||
$token = auth()->user()->currentAccessToken();
|
||||
if ($token->can('view:sensitive')) {
|
||||
return serializeApiResponse($deployment);
|
||||
}
|
||||
|
||||
if (request()->attributes->get('can_read_sensitive', false) === false) {
|
||||
$deployment->makeHidden([
|
||||
'logs',
|
||||
]);
|
||||
}
|
||||
|
||||
return serializeApiResponse($deployment);
|
||||
}
|
||||
|
||||
@@ -11,13 +11,11 @@ class SecurityController extends Controller
|
||||
{
|
||||
private function removeSensitiveData($team)
|
||||
{
|
||||
$token = auth()->user()->currentAccessToken();
|
||||
if ($token->can('view:sensitive')) {
|
||||
return serializeApiResponse($team);
|
||||
}
|
||||
if (request()->attributes->get('can_read_sensitive', false) === false) {
|
||||
$team->makeHidden([
|
||||
'private_key',
|
||||
]);
|
||||
}
|
||||
|
||||
return serializeApiResponse($team);
|
||||
}
|
||||
|
||||
@@ -19,25 +19,22 @@ class ServersController extends Controller
|
||||
{
|
||||
private function removeSensitiveDataFromSettings($settings)
|
||||
{
|
||||
$token = auth()->user()->currentAccessToken();
|
||||
if ($token->can('view:sensitive')) {
|
||||
return serializeApiResponse($settings);
|
||||
}
|
||||
if (request()->attributes->get('can_read_sensitive', false) === false) {
|
||||
$settings = $settings->makeHidden([
|
||||
'sentinel_token',
|
||||
]);
|
||||
}
|
||||
|
||||
return serializeApiResponse($settings);
|
||||
}
|
||||
|
||||
private function removeSensitiveData($server)
|
||||
{
|
||||
$token = auth()->user()->currentAccessToken();
|
||||
$server->makeHidden([
|
||||
'id',
|
||||
]);
|
||||
if ($token->can('view:sensitive')) {
|
||||
return serializeApiResponse($server);
|
||||
if (request()->attributes->get('can_read_sensitive', false) === false) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
return serializeApiResponse($server);
|
||||
|
||||
@@ -18,18 +18,17 @@ class ServicesController extends Controller
|
||||
{
|
||||
private function removeSensitiveData($service)
|
||||
{
|
||||
$token = auth()->user()->currentAccessToken();
|
||||
$service->makeHidden([
|
||||
'id',
|
||||
]);
|
||||
if ($token->can('view:sensitive')) {
|
||||
return serializeApiResponse($service);
|
||||
}
|
||||
|
||||
if (request()->attributes->get('can_read_sensitive', false) === false) {
|
||||
$service->makeHidden([
|
||||
'docker_compose_raw',
|
||||
'docker_compose',
|
||||
'value',
|
||||
'real_value',
|
||||
]);
|
||||
}
|
||||
|
||||
return serializeApiResponse($service);
|
||||
}
|
||||
|
||||
@@ -10,20 +10,18 @@ class TeamController extends Controller
|
||||
{
|
||||
private function removeSensitiveData($team)
|
||||
{
|
||||
$token = auth()->user()->currentAccessToken();
|
||||
$team->makeHidden([
|
||||
'custom_server_limit',
|
||||
'pivot',
|
||||
]);
|
||||
if ($token->can('view:sensitive')) {
|
||||
return serializeApiResponse($team);
|
||||
}
|
||||
if (request()->attributes->get('can_read_sensitive', false) === false) {
|
||||
$team->makeHidden([
|
||||
'smtp_username',
|
||||
'smtp_password',
|
||||
'resend_api_key',
|
||||
'telegram_token',
|
||||
]);
|
||||
}
|
||||
|
||||
return serializeApiResponse($team);
|
||||
}
|
||||
|
||||
@@ -42,15 +42,13 @@ class Controller extends BaseController
|
||||
public function email_verify(EmailVerificationRequest $request)
|
||||
{
|
||||
$request->fulfill();
|
||||
$name = request()->user()?->name;
|
||||
|
||||
// send_internal_notification("User {$name} verified their email address.");
|
||||
return redirect(RouteServiceProvider::HOME);
|
||||
}
|
||||
|
||||
public function forgot_password(Request $request)
|
||||
{
|
||||
if (is_transactional_emails_active()) {
|
||||
if (is_transactional_emails_enabled()) {
|
||||
$arrayOfRequest = $request->only(Fortify::email());
|
||||
$request->merge([
|
||||
'email' => Str::lower($arrayOfRequest['email']),
|
||||
|
||||
@@ -463,7 +463,7 @@ class Github extends Controller
|
||||
$private_key = data_get($data, 'pem');
|
||||
$webhook_secret = data_get($data, 'webhook_secret');
|
||||
$private_key = PrivateKey::create([
|
||||
'name' => $slug,
|
||||
'name' => "github-app-{$slug}",
|
||||
'private_key' => $private_key,
|
||||
'team_id' => $github_app->team_id,
|
||||
'is_git_related' => true,
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Webhook;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Waitlist as ModelsWaitlist;
|
||||
use Exception;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class Waitlist extends Controller
|
||||
{
|
||||
public function confirm(Request $request)
|
||||
{
|
||||
$email = request()->get('email');
|
||||
$confirmation_code = request()->get('confirmation_code');
|
||||
try {
|
||||
$found = ModelsWaitlist::where('uuid', $confirmation_code)->where('email', $email)->first();
|
||||
if ($found) {
|
||||
if (! $found->verified) {
|
||||
if ($found->created_at > now()->subMinutes(config('constants.waitlist.expiration'))) {
|
||||
$found->verified = true;
|
||||
$found->save();
|
||||
send_internal_notification('Waitlist confirmed: '.$email);
|
||||
|
||||
return 'Thank you for confirming your email address. We will notify you when you are next in line.';
|
||||
} else {
|
||||
$found->delete();
|
||||
send_internal_notification('Waitlist expired: '.$email);
|
||||
|
||||
return 'Your confirmation code has expired. Please sign up again.';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->route('dashboard');
|
||||
} catch (Exception $e) {
|
||||
send_internal_notification('Waitlist confirmation failed: '.$e->getMessage());
|
||||
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
}
|
||||
|
||||
public function cancel(Request $request)
|
||||
{
|
||||
$email = request()->get('email');
|
||||
$confirmation_code = request()->get('confirmation_code');
|
||||
try {
|
||||
$found = ModelsWaitlist::where('uuid', $confirmation_code)->where('email', $email)->first();
|
||||
if ($found && ! $found->verified) {
|
||||
$found->delete();
|
||||
send_internal_notification('Waitlist cancelled: '.$email);
|
||||
|
||||
return 'Your email address has been removed from the waitlist.';
|
||||
}
|
||||
|
||||
return redirect()->route('dashboard');
|
||||
} catch (Exception $e) {
|
||||
send_internal_notification('Waitlist cancellation failed: '.$e->getMessage());
|
||||
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,5 +69,7 @@ class Kernel extends HttpKernel
|
||||
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
||||
'abilities' => \Laravel\Sanctum\Http\Middleware\CheckAbilities::class,
|
||||
'ability' => \Laravel\Sanctum\Http\Middleware\CheckForAnyAbility::class,
|
||||
'api.ability' => \App\Http\Middleware\ApiAbility::class,
|
||||
'api.sensitive' => \App\Http\Middleware\ApiSensitiveData::class,
|
||||
];
|
||||
}
|
||||
|
||||
27
app/Http/Middleware/ApiAbility.php
Normal file
27
app/Http/Middleware/ApiAbility.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Laravel\Sanctum\Http\Middleware\CheckForAnyAbility;
|
||||
|
||||
class ApiAbility extends CheckForAnyAbility
|
||||
{
|
||||
public function handle($request, $next, ...$abilities)
|
||||
{
|
||||
try {
|
||||
if ($request->user()->tokenCan('root')) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
return parent::handle($request, $next, ...$abilities);
|
||||
} catch (\Illuminate\Auth\AuthenticationException $e) {
|
||||
return response()->json([
|
||||
'message' => 'Unauthenticated.',
|
||||
], 401);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'message' => 'Missing required permissions: '.implode(', ', $abilities),
|
||||
], 403);
|
||||
}
|
||||
}
|
||||
}
|
||||
21
app/Http/Middleware/ApiSensitiveData.php
Normal file
21
app/Http/Middleware/ApiSensitiveData.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ApiSensitiveData
|
||||
{
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
$token = $request->user()->currentAccessToken();
|
||||
|
||||
// Allow access to sensitive data if token has root or read:sensitive permission
|
||||
$request->attributes->add([
|
||||
'can_read_sensitive' => $token->can('root') || $token->can('read:sensitive'),
|
||||
]);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class IgnoreReadOnlyApiToken
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$token = auth()->user()->currentAccessToken();
|
||||
if ($token->can('*')) {
|
||||
return $next($request);
|
||||
}
|
||||
if ($token->can('read-only')) {
|
||||
return response()->json(['message' => 'You are not allowed to perform this action.'], 403);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class OnlyRootApiToken
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$token = auth()->user()->currentAccessToken();
|
||||
if ($token->can('*')) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
return response()->json(['message' => 'You are not allowed to perform this action.'], 403);
|
||||
}
|
||||
}
|
||||
@@ -140,6 +140,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
|
||||
private ?string $buildTarget = null;
|
||||
|
||||
private bool $disableBuildCache = false;
|
||||
|
||||
private Collection $saved_outputs;
|
||||
|
||||
private ?string $full_healthcheck_url = null;
|
||||
@@ -178,7 +180,11 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
$this->pull_request_id = $this->application_deployment_queue->pull_request_id;
|
||||
$this->commit = $this->application_deployment_queue->commit;
|
||||
$this->rollback = $this->application_deployment_queue->rollback;
|
||||
$this->disableBuildCache = $this->application->settings->disable_build_cache;
|
||||
$this->force_rebuild = $this->application_deployment_queue->force_rebuild;
|
||||
if ($this->disableBuildCache) {
|
||||
$this->force_rebuild = true;
|
||||
}
|
||||
$this->restart_only = $this->application_deployment_queue->restart_only;
|
||||
$this->restart_only = $this->restart_only && $this->application->build_pack !== 'dockerimage' && $this->application->build_pack !== 'dockerfile';
|
||||
$this->only_this_server = $this->application_deployment_queue->only_this_server;
|
||||
@@ -1976,6 +1982,9 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
$this->build_args = $this->build_args->implode(' ');
|
||||
|
||||
$this->application_deployment_queue->addLogEntry('----------------------------------------');
|
||||
if ($this->disableBuildCache) {
|
||||
$this->application_deployment_queue->addLogEntry('Docker build cache is disabled. It will not be used during the build process.');
|
||||
}
|
||||
if ($this->application->build_pack === 'static') {
|
||||
$this->application_deployment_queue->addLogEntry('Static deployment. Copying static assets to the image.');
|
||||
} else {
|
||||
@@ -2400,7 +2409,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
if (! $this->only_this_server) {
|
||||
$this->deploy_to_additional_destinations();
|
||||
}
|
||||
//$this->application->environment->project->team?->notify(new DeploymentSuccess($this->application, $this->deployment_uuid, $this->preview));
|
||||
$this->application->environment->project->team?->notify(new DeploymentSuccess($this->application, $this->deployment_uuid, $this->preview));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Actions\License\CheckResaleLicense;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class CheckResaleLicenseJob implements ShouldBeEncrypted, ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public function __construct() {}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
CheckResaleLicense::run();
|
||||
} catch (\Throwable $e) {
|
||||
send_internal_notification('CheckResaleLicenseJob failed with: '.$e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -306,7 +306,9 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
if ($this->backup->save_s3) {
|
||||
$this->upload_to_s3();
|
||||
}
|
||||
//$this->team?->notify(new BackupSuccess($this->backup, $this->database, $database));
|
||||
|
||||
$this->team->notify(new BackupSuccess($this->backup, $this->database, $database));
|
||||
|
||||
$this->backup_log->update([
|
||||
'status' => 'success',
|
||||
'message' => $this->backup_output,
|
||||
|
||||
@@ -4,7 +4,8 @@ namespace App\Jobs;
|
||||
|
||||
use App\Actions\Server\CleanupDocker;
|
||||
use App\Models\Server;
|
||||
use App\Notifications\Server\DockerCleanup;
|
||||
use App\Notifications\Server\DockerCleanupFailed;
|
||||
use App\Notifications\Server\DockerCleanupSuccess;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
@@ -12,7 +13,6 @@ use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
{
|
||||
@@ -38,35 +38,36 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->manualCleanup || $this->server->settings->force_docker_cleanup) {
|
||||
Log::info('DockerCleanupJob '.($this->manualCleanup ? 'manual' : 'force').' cleanup on '.$this->server->name);
|
||||
CleanupDocker::run(server: $this->server);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->usageBefore = $this->server->getDiskUsage();
|
||||
if (str($this->usageBefore)->isEmpty() || $this->usageBefore === null || $this->usageBefore === 0) {
|
||||
Log::info('DockerCleanupJob force cleanup on '.$this->server->name);
|
||||
|
||||
if ($this->manualCleanup || $this->server->settings->force_docker_cleanup) {
|
||||
CleanupDocker::run(server: $this->server);
|
||||
$usageAfter = $this->server->getDiskUsage();
|
||||
$this->server->team?->notify(new DockerCleanupSuccess($this->server, ($this->manualCleanup ? 'Manual' : 'Forced').' Docker cleanup job executed successfully. Disk usage before: '.$this->usageBefore.'%, Disk usage after: '.$usageAfter.'%.'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (str($this->usageBefore)->isEmpty() || $this->usageBefore === null || $this->usageBefore === 0) {
|
||||
CleanupDocker::run(server: $this->server);
|
||||
$this->server->team?->notify(new DockerCleanupSuccess($this->server, 'Docker cleanup job executed successfully, but no disk usage could be determined.'));
|
||||
}
|
||||
|
||||
if ($this->usageBefore >= $this->server->settings->docker_cleanup_threshold) {
|
||||
CleanupDocker::run(server: $this->server);
|
||||
$usageAfter = $this->server->getDiskUsage();
|
||||
if ($usageAfter < $this->usageBefore) {
|
||||
$this->server->team?->notify(new DockerCleanup($this->server, 'Saved '.($this->usageBefore - $usageAfter).'% disk space.'));
|
||||
Log::info('DockerCleanupJob done: Saved '.($this->usageBefore - $usageAfter).'% disk space on '.$this->server->name);
|
||||
$diskSaved = $this->usageBefore - $usageAfter;
|
||||
|
||||
if ($diskSaved > 0) {
|
||||
$this->server->team?->notify(new DockerCleanupSuccess($this->server, 'Saved '.$diskSaved.'% disk space. Disk usage before: '.$this->usageBefore.'%, Disk usage after: '.$usageAfter.'%.'));
|
||||
} else {
|
||||
Log::info('DockerCleanupJob failed to save disk space on '.$this->server->name);
|
||||
$this->server->team?->notify(new DockerCleanupSuccess($this->server, 'Docker cleanup job executed successfully, but no disk space was saved. Disk usage before: '.$this->usageBefore.'%, Disk usage after: '.$usageAfter.'%.'));
|
||||
}
|
||||
} else {
|
||||
Log::info('No need to clean up '.$this->server->name);
|
||||
$this->server->team?->notify(new DockerCleanupSuccess($this->server, 'No cleanup needed for '.$this->server->name));
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
CleanupDocker::run(server: $this->server);
|
||||
Log::error('DockerCleanupJob failed: '.$e->getMessage());
|
||||
$this->server->team?->notify(new DockerCleanupFailed($this->server, 'Docker cleanup job failed with the following error: '.$e->getMessage()));
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
use App\Models\Team;
|
||||
use App\Notifications\ScheduledTask\TaskFailed;
|
||||
use App\Notifications\ScheduledTask\TaskSuccess;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
@@ -111,6 +112,8 @@ class ScheduledTaskJob implements ShouldQueue
|
||||
'message' => $this->task_output,
|
||||
]);
|
||||
|
||||
$this->team?->notify(new TaskSuccess($this->task, $this->task_output));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -125,7 +128,6 @@ class ScheduledTaskJob implements ShouldQueue
|
||||
]);
|
||||
}
|
||||
$this->team?->notify(new TaskFailed($this->task, $e->getMessage()));
|
||||
// send_internal_notification('ScheduledTaskJob failed with: ' . $e->getMessage());
|
||||
throw $e;
|
||||
} finally {
|
||||
ScheduledTaskDone::dispatch($this->team->id);
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class SendConfirmationForWaitlistJob implements ShouldBeEncrypted, ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public function __construct(public string $email, public string $uuid) {}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
try {
|
||||
$mail = new MailMessage;
|
||||
$confirmation_url = base_url().'/webhooks/waitlist/confirm?email='.$this->email.'&confirmation_code='.$this->uuid;
|
||||
$cancel_url = base_url().'/webhooks/waitlist/cancel?email='.$this->email.'&confirmation_code='.$this->uuid;
|
||||
$mail->view('emails.waitlist-confirmation',
|
||||
[
|
||||
'confirmation_url' => $confirmation_url,
|
||||
'cancel_url' => $cancel_url,
|
||||
]);
|
||||
$mail->subject('You are on the waitlist!');
|
||||
send_user_an_email($mail, $this->email);
|
||||
} catch (\Throwable $e) {
|
||||
send_internal_notification("SendConfirmationForWaitlistJob failed for {$this->email} with error: ".$e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
50
app/Jobs/SendMessageToPushoverJob.php
Normal file
50
app/Jobs/SendMessageToPushoverJob.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Notifications\Dto\PushoverMessage;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class SendMessageToPushoverJob implements ShouldBeEncrypted, ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* The number of times the job may be attempted.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $tries = 5;
|
||||
|
||||
public $backoff = 10;
|
||||
|
||||
/**
|
||||
* The maximum number of unhandled exceptions to allow before failing.
|
||||
*/
|
||||
public int $maxExceptions = 5;
|
||||
|
||||
public function __construct(
|
||||
public PushoverMessage $message,
|
||||
public string $token,
|
||||
public string $user,
|
||||
) {
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$response = Http::post('https://api.pushover.net/1/messages.json', $this->message->toPayload($this->token, $this->user));
|
||||
if ($response->failed()) {
|
||||
throw new \RuntimeException('Pushover notification failed with ' . $response->status() . ' status code.' . $response->body());
|
||||
}
|
||||
}
|
||||
}
|
||||
59
app/Jobs/SendMessageToSlackJob.php
Normal file
59
app/Jobs/SendMessageToSlackJob.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Notifications\Dto\SlackMessage;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class SendMessageToSlackJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public function __construct(
|
||||
private SlackMessage $message,
|
||||
private string $webhookUrl
|
||||
) {
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
Http::post($this->webhookUrl, [
|
||||
'blocks' => [
|
||||
[
|
||||
'type' => 'section',
|
||||
'text' => [
|
||||
'type' => 'plain_text',
|
||||
'text' => 'Coolify Notification',
|
||||
],
|
||||
],
|
||||
],
|
||||
'attachments' => [
|
||||
[
|
||||
'color' => $this->message->color,
|
||||
'blocks' => [
|
||||
[
|
||||
'type' => 'header',
|
||||
'text' => [
|
||||
'type' => 'plain_text',
|
||||
'text' => $this->message->title,
|
||||
],
|
||||
],
|
||||
[
|
||||
'type' => 'section',
|
||||
'text' => [
|
||||
'type' => 'mrkdwn',
|
||||
'text' => $this->message->description,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ class SendMessageToTelegramJob implements ShouldBeEncrypted, ShouldQueue
|
||||
public array $buttons,
|
||||
public string $token,
|
||||
public string $chatId,
|
||||
public ?string $topicId = null,
|
||||
public ?string $threadId = null,
|
||||
) {
|
||||
$this->onQueue('high');
|
||||
}
|
||||
@@ -67,8 +67,8 @@ class SendMessageToTelegramJob implements ShouldBeEncrypted, ShouldQueue
|
||||
'chat_id' => $this->chatId,
|
||||
'text' => $this->text,
|
||||
];
|
||||
if ($this->topicId) {
|
||||
$payload['message_thread_id'] = $this->topicId;
|
||||
if ($this->threadId) {
|
||||
$payload['message_thread_id'] = $this->threadId;
|
||||
}
|
||||
$response = Http::post($url, $payload);
|
||||
if ($response->failed()) {
|
||||
|
||||
@@ -173,8 +173,8 @@ class StripeProcessJob implements ShouldQueue
|
||||
$userId = data_get($data, 'metadata.user_id');
|
||||
$customerId = data_get($data, 'customer');
|
||||
$status = data_get($data, 'status');
|
||||
$subscriptionId = data_get($data, 'items.data.0.subscription');
|
||||
$planId = data_get($data, 'items.data.0.plan.id');
|
||||
$subscriptionId = data_get($data, 'items.data.0.subscription') ?? data_get($data, 'id');
|
||||
$planId = data_get($data, 'items.data.0.plan.id') ?? data_get($data, 'plan.id');
|
||||
if (Str::contains($excludedPlans, $planId)) {
|
||||
send_internal_notification('Subscription excluded.');
|
||||
break;
|
||||
@@ -218,23 +218,43 @@ class StripeProcessJob implements ShouldQueue
|
||||
'stripe_cancel_at_period_end' => $cancelAtPeriodEnd,
|
||||
]);
|
||||
if ($status === 'paused' || $status === 'incomplete_expired') {
|
||||
if ($subscription->stripe_subscription_id === $subscriptionId) {
|
||||
$subscription->update([
|
||||
'stripe_invoice_paid' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
if ($status === 'active') {
|
||||
if ($subscription->stripe_subscription_id === $subscriptionId) {
|
||||
$subscription->update([
|
||||
'stripe_invoice_paid' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
if ($feedback) {
|
||||
$reason = "Cancellation feedback for {$customerId}: '".$feedback."'";
|
||||
if ($comment) {
|
||||
$reason .= ' with comment: \''.$comment."'";
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case 'customer.subscription.deleted':
|
||||
// End subscription
|
||||
$customerId = data_get($data, 'customer');
|
||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
|
||||
$subscriptionId = data_get($data, 'id');
|
||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->where('stripe_subscription_id', $subscriptionId)->first();
|
||||
if ($subscription) {
|
||||
$team = data_get($subscription, 'team');
|
||||
$team?->subscriptionEnded();
|
||||
if ($team) {
|
||||
$team->subscriptionEnded();
|
||||
} else {
|
||||
send_internal_notification('Subscription deleted but no team found in Coolify for customer: '.$customerId);
|
||||
throw new \RuntimeException("No team found in Coolify for customer: {$customerId}");
|
||||
}
|
||||
} else {
|
||||
send_internal_notification('Subscription deleted but no subscription found in Coolify for customer: '.$customerId);
|
||||
throw new \RuntimeException("No subscription found in Coolify for customer: {$customerId}");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new \RuntimeException("Unhandled event type: {$type}");
|
||||
|
||||
@@ -14,7 +14,7 @@ class ProxyStartedNotification
|
||||
public function handle(ProxyStarted $event): void
|
||||
{
|
||||
$this->server = data_get($event, 'data');
|
||||
$this->server->setupDefault404Redirect();
|
||||
$this->server->setupDefaultRedirect();
|
||||
$this->server->setupDynamicProxyConfiguration();
|
||||
$this->server->proxy->force_stop = false;
|
||||
$this->server->save();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Livewire\Notifications;
|
||||
|
||||
use App\Models\DiscordNotificationSettings;
|
||||
use App\Models\Team;
|
||||
use App\Notifications\Test;
|
||||
use Livewire\Attributes\Validate;
|
||||
@@ -11,6 +12,8 @@ class Discord extends Component
|
||||
{
|
||||
public Team $team;
|
||||
|
||||
public DiscordNotificationSettings $settings;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $discordEnabled = false;
|
||||
|
||||
@@ -18,27 +21,46 @@ class Discord extends Component
|
||||
public ?string $discordWebhookUrl = null;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $discordNotificationsTest = false;
|
||||
public bool $deploymentSuccessDiscordNotifications = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $discordNotificationsDeployments = false;
|
||||
public bool $deploymentFailureDiscordNotifications = true;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $discordNotificationsStatusChanges = false;
|
||||
public bool $statusChangeDiscordNotifications = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $discordNotificationsDatabaseBackups = false;
|
||||
public bool $backupSuccessDiscordNotifications = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $discordNotificationsScheduledTasks = false;
|
||||
public bool $backupFailureDiscordNotifications = true;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $discordNotificationsServerDiskUsage = false;
|
||||
public bool $scheduledTaskSuccessDiscordNotifications = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $scheduledTaskFailureDiscordNotifications = true;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $dockerCleanupSuccessDiscordNotifications = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $dockerCleanupFailureDiscordNotifications = true;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $serverDiskUsageDiscordNotifications = true;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $serverReachableDiscordNotifications = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $serverUnreachableDiscordNotifications = true;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
try {
|
||||
$this->team = auth()->user()->currentTeam();
|
||||
$this->settings = $this->team->discordNotificationSettings;
|
||||
$this->syncData();
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
@@ -49,25 +71,40 @@ class Discord extends Component
|
||||
{
|
||||
if ($toModel) {
|
||||
$this->validate();
|
||||
$this->team->discord_enabled = $this->discordEnabled;
|
||||
$this->team->discord_webhook_url = $this->discordWebhookUrl;
|
||||
$this->team->discord_notifications_test = $this->discordNotificationsTest;
|
||||
$this->team->discord_notifications_deployments = $this->discordNotificationsDeployments;
|
||||
$this->team->discord_notifications_status_changes = $this->discordNotificationsStatusChanges;
|
||||
$this->team->discord_notifications_database_backups = $this->discordNotificationsDatabaseBackups;
|
||||
$this->team->discord_notifications_scheduled_tasks = $this->discordNotificationsScheduledTasks;
|
||||
$this->team->discord_notifications_server_disk_usage = $this->discordNotificationsServerDiskUsage;
|
||||
$this->team->save();
|
||||
$this->settings->discord_enabled = $this->discordEnabled;
|
||||
$this->settings->discord_webhook_url = $this->discordWebhookUrl;
|
||||
|
||||
$this->settings->deployment_success_discord_notifications = $this->deploymentSuccessDiscordNotifications;
|
||||
$this->settings->deployment_failure_discord_notifications = $this->deploymentFailureDiscordNotifications;
|
||||
$this->settings->status_change_discord_notifications = $this->statusChangeDiscordNotifications;
|
||||
$this->settings->backup_success_discord_notifications = $this->backupSuccessDiscordNotifications;
|
||||
$this->settings->backup_failure_discord_notifications = $this->backupFailureDiscordNotifications;
|
||||
$this->settings->scheduled_task_success_discord_notifications = $this->scheduledTaskSuccessDiscordNotifications;
|
||||
$this->settings->scheduled_task_failure_discord_notifications = $this->scheduledTaskFailureDiscordNotifications;
|
||||
$this->settings->docker_cleanup_success_discord_notifications = $this->dockerCleanupSuccessDiscordNotifications;
|
||||
$this->settings->docker_cleanup_failure_discord_notifications = $this->dockerCleanupFailureDiscordNotifications;
|
||||
$this->settings->server_disk_usage_discord_notifications = $this->serverDiskUsageDiscordNotifications;
|
||||
$this->settings->server_reachable_discord_notifications = $this->serverReachableDiscordNotifications;
|
||||
$this->settings->server_unreachable_discord_notifications = $this->serverUnreachableDiscordNotifications;
|
||||
|
||||
$this->settings->save();
|
||||
refreshSession();
|
||||
} else {
|
||||
$this->discordEnabled = $this->team->discord_enabled;
|
||||
$this->discordWebhookUrl = $this->team->discord_webhook_url;
|
||||
$this->discordNotificationsTest = $this->team->discord_notifications_test;
|
||||
$this->discordNotificationsDeployments = $this->team->discord_notifications_deployments;
|
||||
$this->discordNotificationsStatusChanges = $this->team->discord_notifications_status_changes;
|
||||
$this->discordNotificationsDatabaseBackups = $this->team->discord_notifications_database_backups;
|
||||
$this->discordNotificationsScheduledTasks = $this->team->discord_notifications_scheduled_tasks;
|
||||
$this->discordNotificationsServerDiskUsage = $this->team->discord_notifications_server_disk_usage;
|
||||
$this->discordEnabled = $this->settings->discord_enabled;
|
||||
$this->discordWebhookUrl = $this->settings->discord_webhook_url;
|
||||
|
||||
$this->deploymentSuccessDiscordNotifications = $this->settings->deployment_success_discord_notifications;
|
||||
$this->deploymentFailureDiscordNotifications = $this->settings->deployment_failure_discord_notifications;
|
||||
$this->statusChangeDiscordNotifications = $this->settings->status_change_discord_notifications;
|
||||
$this->backupSuccessDiscordNotifications = $this->settings->backup_success_discord_notifications;
|
||||
$this->backupFailureDiscordNotifications = $this->settings->backup_failure_discord_notifications;
|
||||
$this->scheduledTaskSuccessDiscordNotifications = $this->settings->scheduled_task_success_discord_notifications;
|
||||
$this->scheduledTaskFailureDiscordNotifications = $this->settings->scheduled_task_failure_discord_notifications;
|
||||
$this->dockerCleanupSuccessDiscordNotifications = $this->settings->docker_cleanup_success_discord_notifications;
|
||||
$this->dockerCleanupFailureDiscordNotifications = $this->settings->docker_cleanup_failure_discord_notifications;
|
||||
$this->serverDiskUsageDiscordNotifications = $this->settings->server_disk_usage_discord_notifications;
|
||||
$this->serverReachableDiscordNotifications = $this->settings->server_reachable_discord_notifications;
|
||||
$this->serverUnreachableDiscordNotifications = $this->settings->server_unreachable_discord_notifications;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,7 +154,7 @@ class Discord extends Component
|
||||
public function sendTestNotification()
|
||||
{
|
||||
try {
|
||||
$this->team->notify(new Test);
|
||||
$this->team->notify(new Test(channel: 'discord'));
|
||||
$this->dispatch('success', 'Test notification sent.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Livewire\Notifications;
|
||||
|
||||
use App\Models\EmailNotificationSettings;
|
||||
use App\Models\Team;
|
||||
use App\Notifications\Test;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
@@ -11,17 +12,20 @@ use Livewire\Component;
|
||||
|
||||
class Email extends Component
|
||||
{
|
||||
protected $listeners = ['refresh' => '$refresh'];
|
||||
|
||||
#[Locked]
|
||||
public Team $team;
|
||||
|
||||
#[Locked]
|
||||
public EmailNotificationSettings $settings;
|
||||
|
||||
#[Locked]
|
||||
public string $emails;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $smtpEnabled = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $useInstanceEmailSettings = false;
|
||||
|
||||
#[Validate(['nullable', 'email'])]
|
||||
public ?string $smtpFromAddress = null;
|
||||
|
||||
@@ -34,11 +38,11 @@ class Email extends Component
|
||||
#[Validate(['nullable', 'string'])]
|
||||
public ?string $smtpHost = null;
|
||||
|
||||
#[Validate(['nullable', 'numeric'])]
|
||||
#[Validate(['nullable', 'numeric', 'min:1', 'max:65535'])]
|
||||
public ?int $smtpPort = null;
|
||||
|
||||
#[Validate(['nullable', 'string'])]
|
||||
public ?string $smtpEncryption = null;
|
||||
#[Validate(['nullable', 'string', 'in:tls,ssl,none'])]
|
||||
public ?string $smtpEncryption = 'tls';
|
||||
|
||||
#[Validate(['nullable', 'string'])]
|
||||
public ?string $smtpUsername = null;
|
||||
@@ -50,29 +54,50 @@ class Email extends Component
|
||||
public ?int $smtpTimeout = null;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $smtpNotificationsTest = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $smtpNotificationsDeployments = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $smtpNotificationsStatusChanges = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $smtpNotificationsDatabaseBackups = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $smtpNotificationsScheduledTasks = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $smtpNotificationsServerDiskUsage = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $resendEnabled;
|
||||
public bool $resendEnabled = false;
|
||||
|
||||
#[Validate(['nullable', 'string'])]
|
||||
public ?string $resendApiKey = null;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $useInstanceEmailSettings = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $deploymentSuccessEmailNotifications = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $deploymentFailureEmailNotifications = true;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $statusChangeEmailNotifications = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $backupSuccessEmailNotifications = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $backupFailureEmailNotifications = true;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $scheduledTaskSuccessEmailNotifications = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $scheduledTaskFailureEmailNotifications = true;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $dockerCleanupSuccessEmailNotifications = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $dockerCleanupFailureEmailNotifications = true;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $serverDiskUsageEmailNotifications = true;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $serverReachableEmailNotifications = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $serverUnreachableEmailNotifications = true;
|
||||
|
||||
#[Validate(['nullable', 'email'])]
|
||||
public ?string $testEmailAddress = null;
|
||||
|
||||
@@ -81,7 +106,9 @@ class Email extends Component
|
||||
try {
|
||||
$this->team = auth()->user()->currentTeam();
|
||||
$this->emails = auth()->user()->email;
|
||||
$this->settings = $this->team->emailNotificationSettings;
|
||||
$this->syncData();
|
||||
$this->testEmailAddress = auth()->user()->email;
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
@@ -91,47 +118,191 @@ class Email extends Component
|
||||
{
|
||||
if ($toModel) {
|
||||
$this->validate();
|
||||
$this->team->smtp_enabled = $this->smtpEnabled;
|
||||
$this->team->smtp_from_address = $this->smtpFromAddress;
|
||||
$this->team->smtp_from_name = $this->smtpFromName;
|
||||
$this->team->smtp_host = $this->smtpHost;
|
||||
$this->team->smtp_port = $this->smtpPort;
|
||||
$this->team->smtp_encryption = $this->smtpEncryption;
|
||||
$this->team->smtp_username = $this->smtpUsername;
|
||||
$this->team->smtp_password = $this->smtpPassword;
|
||||
$this->team->smtp_timeout = $this->smtpTimeout;
|
||||
$this->team->smtp_recipients = $this->smtpRecipients;
|
||||
$this->team->smtp_notifications_test = $this->smtpNotificationsTest;
|
||||
$this->team->smtp_notifications_deployments = $this->smtpNotificationsDeployments;
|
||||
$this->team->smtp_notifications_status_changes = $this->smtpNotificationsStatusChanges;
|
||||
$this->team->smtp_notifications_database_backups = $this->smtpNotificationsDatabaseBackups;
|
||||
$this->team->smtp_notifications_scheduled_tasks = $this->smtpNotificationsScheduledTasks;
|
||||
$this->team->smtp_notifications_server_disk_usage = $this->smtpNotificationsServerDiskUsage;
|
||||
$this->team->use_instance_email_settings = $this->useInstanceEmailSettings;
|
||||
$this->team->resend_enabled = $this->resendEnabled;
|
||||
$this->team->resend_api_key = $this->resendApiKey;
|
||||
$this->team->save();
|
||||
refreshSession();
|
||||
$this->settings->smtp_enabled = $this->smtpEnabled;
|
||||
$this->settings->smtp_from_address = $this->smtpFromAddress;
|
||||
$this->settings->smtp_from_name = $this->smtpFromName;
|
||||
$this->settings->smtp_recipients = $this->smtpRecipients;
|
||||
$this->settings->smtp_host = $this->smtpHost;
|
||||
$this->settings->smtp_port = $this->smtpPort;
|
||||
$this->settings->smtp_encryption = $this->smtpEncryption;
|
||||
$this->settings->smtp_username = $this->smtpUsername;
|
||||
$this->settings->smtp_password = $this->smtpPassword;
|
||||
$this->settings->smtp_timeout = $this->smtpTimeout;
|
||||
|
||||
$this->settings->resend_enabled = $this->resendEnabled;
|
||||
$this->settings->resend_api_key = $this->resendApiKey;
|
||||
|
||||
$this->settings->use_instance_email_settings = $this->useInstanceEmailSettings;
|
||||
|
||||
$this->settings->deployment_success_email_notifications = $this->deploymentSuccessEmailNotifications;
|
||||
$this->settings->deployment_failure_email_notifications = $this->deploymentFailureEmailNotifications;
|
||||
$this->settings->status_change_email_notifications = $this->statusChangeEmailNotifications;
|
||||
$this->settings->backup_success_email_notifications = $this->backupSuccessEmailNotifications;
|
||||
$this->settings->backup_failure_email_notifications = $this->backupFailureEmailNotifications;
|
||||
$this->settings->scheduled_task_success_email_notifications = $this->scheduledTaskSuccessEmailNotifications;
|
||||
$this->settings->scheduled_task_failure_email_notifications = $this->scheduledTaskFailureEmailNotifications;
|
||||
$this->settings->docker_cleanup_success_email_notifications = $this->dockerCleanupSuccessEmailNotifications;
|
||||
$this->settings->docker_cleanup_failure_email_notifications = $this->dockerCleanupFailureEmailNotifications;
|
||||
$this->settings->server_disk_usage_email_notifications = $this->serverDiskUsageEmailNotifications;
|
||||
$this->settings->server_reachable_email_notifications = $this->serverReachableEmailNotifications;
|
||||
$this->settings->server_unreachable_email_notifications = $this->serverUnreachableEmailNotifications;
|
||||
$this->settings->save();
|
||||
|
||||
} else {
|
||||
$this->smtpEnabled = $this->team->smtp_enabled;
|
||||
$this->smtpFromAddress = $this->team->smtp_from_address;
|
||||
$this->smtpFromName = $this->team->smtp_from_name;
|
||||
$this->smtpHost = $this->team->smtp_host;
|
||||
$this->smtpPort = $this->team->smtp_port;
|
||||
$this->smtpEncryption = $this->team->smtp_encryption;
|
||||
$this->smtpUsername = $this->team->smtp_username;
|
||||
$this->smtpPassword = $this->team->smtp_password;
|
||||
$this->smtpTimeout = $this->team->smtp_timeout;
|
||||
$this->smtpRecipients = $this->team->smtp_recipients;
|
||||
$this->smtpNotificationsTest = $this->team->smtp_notifications_test;
|
||||
$this->smtpNotificationsDeployments = $this->team->smtp_notifications_deployments;
|
||||
$this->smtpNotificationsStatusChanges = $this->team->smtp_notifications_status_changes;
|
||||
$this->smtpNotificationsDatabaseBackups = $this->team->smtp_notifications_database_backups;
|
||||
$this->smtpNotificationsScheduledTasks = $this->team->smtp_notifications_scheduled_tasks;
|
||||
$this->smtpNotificationsServerDiskUsage = $this->team->smtp_notifications_server_disk_usage;
|
||||
$this->useInstanceEmailSettings = $this->team->use_instance_email_settings;
|
||||
$this->resendEnabled = $this->team->resend_enabled;
|
||||
$this->resendApiKey = $this->team->resend_api_key;
|
||||
$this->smtpEnabled = $this->settings->smtp_enabled;
|
||||
$this->smtpFromAddress = $this->settings->smtp_from_address;
|
||||
$this->smtpFromName = $this->settings->smtp_from_name;
|
||||
$this->smtpRecipients = $this->settings->smtp_recipients;
|
||||
$this->smtpHost = $this->settings->smtp_host;
|
||||
$this->smtpPort = $this->settings->smtp_port;
|
||||
$this->smtpEncryption = $this->settings->smtp_encryption;
|
||||
$this->smtpUsername = $this->settings->smtp_username;
|
||||
$this->smtpPassword = $this->settings->smtp_password;
|
||||
$this->smtpTimeout = $this->settings->smtp_timeout;
|
||||
|
||||
$this->resendEnabled = $this->settings->resend_enabled;
|
||||
$this->resendApiKey = $this->settings->resend_api_key;
|
||||
|
||||
$this->useInstanceEmailSettings = $this->settings->use_instance_email_settings;
|
||||
|
||||
$this->deploymentSuccessEmailNotifications = $this->settings->deployment_success_email_notifications;
|
||||
$this->deploymentFailureEmailNotifications = $this->settings->deployment_failure_email_notifications;
|
||||
$this->statusChangeEmailNotifications = $this->settings->status_change_email_notifications;
|
||||
$this->backupSuccessEmailNotifications = $this->settings->backup_success_email_notifications;
|
||||
$this->backupFailureEmailNotifications = $this->settings->backup_failure_email_notifications;
|
||||
$this->scheduledTaskSuccessEmailNotifications = $this->settings->scheduled_task_success_email_notifications;
|
||||
$this->scheduledTaskFailureEmailNotifications = $this->settings->scheduled_task_failure_email_notifications;
|
||||
$this->dockerCleanupSuccessEmailNotifications = $this->settings->docker_cleanup_success_email_notifications;
|
||||
$this->dockerCleanupFailureEmailNotifications = $this->settings->docker_cleanup_failure_email_notifications;
|
||||
$this->serverDiskUsageEmailNotifications = $this->settings->server_disk_usage_email_notifications;
|
||||
$this->serverReachableEmailNotifications = $this->settings->server_reachable_email_notifications;
|
||||
$this->serverUnreachableEmailNotifications = $this->settings->server_unreachable_email_notifications;
|
||||
}
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
$this->resetErrorBag();
|
||||
$this->saveModel();
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function saveModel()
|
||||
{
|
||||
$this->syncData(true);
|
||||
$this->dispatch('success', 'Email notifications settings updated.');
|
||||
}
|
||||
|
||||
public function instantSave(?string $type = null)
|
||||
{
|
||||
try {
|
||||
$this->resetErrorBag();
|
||||
|
||||
if ($type === 'SMTP') {
|
||||
$this->submitSmtp();
|
||||
} elseif ($type === 'Resend') {
|
||||
$this->submitResend();
|
||||
} else {
|
||||
$this->smtpEnabled = false;
|
||||
$this->resendEnabled = false;
|
||||
$this->saveModel();
|
||||
|
||||
return;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
if ($type === 'SMTP') {
|
||||
$this->smtpEnabled = false;
|
||||
} elseif ($type === 'Resend') {
|
||||
$this->resendEnabled = false;
|
||||
}
|
||||
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
$this->dispatch('refresh');
|
||||
}
|
||||
}
|
||||
|
||||
public function submitSmtp()
|
||||
{
|
||||
try {
|
||||
$this->resetErrorBag();
|
||||
$this->validate([
|
||||
'smtpEnabled' => 'boolean',
|
||||
'smtpFromAddress' => 'required|email',
|
||||
'smtpFromName' => 'required|string',
|
||||
'smtpHost' => 'required|string',
|
||||
'smtpPort' => 'required|numeric',
|
||||
'smtpEncryption' => 'required|string|in:tls,ssl,none',
|
||||
'smtpUsername' => 'nullable|string',
|
||||
'smtpPassword' => 'nullable|string',
|
||||
'smtpTimeout' => 'nullable|numeric',
|
||||
], [
|
||||
'smtpFromAddress.required' => 'From Address is required.',
|
||||
'smtpFromAddress.email' => 'Please enter a valid email address.',
|
||||
'smtpFromName.required' => 'From Name is required.',
|
||||
'smtpHost.required' => 'SMTP Host is required.',
|
||||
'smtpPort.required' => 'SMTP Port is required.',
|
||||
'smtpPort.numeric' => 'SMTP Port must be a number.',
|
||||
'smtpEncryption.required' => 'Encryption type is required.',
|
||||
]);
|
||||
|
||||
$this->settings->resend_enabled = false;
|
||||
$this->settings->use_instance_email_settings = false;
|
||||
$this->resendEnabled = false;
|
||||
$this->useInstanceEmailSettings = false;
|
||||
|
||||
$this->settings->smtp_enabled = $this->smtpEnabled;
|
||||
$this->settings->smtp_from_address = $this->smtpFromAddress;
|
||||
$this->settings->smtp_from_name = $this->smtpFromName;
|
||||
$this->settings->smtp_host = $this->smtpHost;
|
||||
$this->settings->smtp_port = $this->smtpPort;
|
||||
$this->settings->smtp_encryption = $this->smtpEncryption;
|
||||
$this->settings->smtp_username = $this->smtpUsername;
|
||||
$this->settings->smtp_password = $this->smtpPassword;
|
||||
$this->settings->smtp_timeout = $this->smtpTimeout;
|
||||
|
||||
$this->settings->save();
|
||||
$this->dispatch('success', 'SMTP settings updated.');
|
||||
} catch (\Throwable $e) {
|
||||
$this->smtpEnabled = false;
|
||||
|
||||
return handleError($e);
|
||||
}
|
||||
}
|
||||
|
||||
public function submitResend()
|
||||
{
|
||||
try {
|
||||
$this->resetErrorBag();
|
||||
$this->validate([
|
||||
'resendEnabled' => 'boolean',
|
||||
'resendApiKey' => 'required|string',
|
||||
'smtpFromAddress' => 'required|email',
|
||||
'smtpFromName' => 'required|string',
|
||||
], [
|
||||
'resendApiKey.required' => 'Resend API Key is required.',
|
||||
'smtpFromAddress.required' => 'From Address is required.',
|
||||
'smtpFromAddress.email' => 'Please enter a valid email address.',
|
||||
'smtpFromName.required' => 'From Name is required.',
|
||||
]);
|
||||
|
||||
$this->settings->smtp_enabled = false;
|
||||
$this->settings->use_instance_email_settings = false;
|
||||
$this->smtpEnabled = false;
|
||||
$this->useInstanceEmailSettings = false;
|
||||
|
||||
$this->settings->resend_enabled = $this->resendEnabled;
|
||||
$this->settings->resend_api_key = $this->resendApiKey;
|
||||
$this->settings->smtp_from_address = $this->smtpFromAddress;
|
||||
$this->settings->smtp_from_name = $this->smtpFromName;
|
||||
|
||||
$this->settings->save();
|
||||
$this->dispatch('success', 'Resend settings updated.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,7 +320,7 @@ class Email extends Component
|
||||
'test-email:'.$this->team->id,
|
||||
$perMinute = 0,
|
||||
function () {
|
||||
$this->team?->notify(new Test($this->testEmailAddress));
|
||||
$this->team?->notify(new Test($this->testEmailAddress, 'email'));
|
||||
$this->dispatch('success', 'Test Email sent.');
|
||||
},
|
||||
$decaySeconds = 10,
|
||||
@@ -163,70 +334,6 @@ class Email extends Component
|
||||
}
|
||||
}
|
||||
|
||||
public function instantSaveInstance()
|
||||
{
|
||||
try {
|
||||
$this->smtpEnabled = false;
|
||||
$this->resendEnabled = false;
|
||||
$this->saveModel();
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function instantSaveSmtpEnabled()
|
||||
{
|
||||
try {
|
||||
$this->validate([
|
||||
'smtpHost' => 'required',
|
||||
'smtpPort' => 'required|numeric',
|
||||
], [
|
||||
'smtpHost.required' => 'SMTP Host is required.',
|
||||
'smtpPort.required' => 'SMTP Port is required.',
|
||||
]);
|
||||
$this->resendEnabled = false;
|
||||
$this->saveModel();
|
||||
} catch (\Throwable $e) {
|
||||
$this->smtpEnabled = false;
|
||||
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function instantSaveResend()
|
||||
{
|
||||
try {
|
||||
$this->validate([
|
||||
'resendApiKey' => 'required',
|
||||
], [
|
||||
'resendApiKey.required' => 'Resend API Key is required.',
|
||||
]);
|
||||
$this->smtpEnabled = false;
|
||||
$this->saveModel();
|
||||
} catch (\Throwable $e) {
|
||||
$this->resendEnabled = false;
|
||||
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function saveModel()
|
||||
{
|
||||
$this->syncData(true);
|
||||
refreshSession();
|
||||
$this->dispatch('success', 'Settings saved.');
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
$this->resetErrorBag();
|
||||
$this->saveModel();
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function copyFromInstanceSettings()
|
||||
{
|
||||
$settings = instanceSettings();
|
||||
|
||||
184
app/Livewire/Notifications/Pushover.php
Normal file
184
app/Livewire/Notifications/Pushover.php
Normal file
@@ -0,0 +1,184 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Notifications;
|
||||
|
||||
use App\Models\PushoverNotificationSettings;
|
||||
use App\Models\Team;
|
||||
use App\Notifications\Test;
|
||||
use Livewire\Attributes\Locked;
|
||||
use Livewire\Attributes\Validate;
|
||||
use Livewire\Component;
|
||||
|
||||
class Pushover extends Component
|
||||
{
|
||||
protected $listeners = ['refresh' => '$refresh'];
|
||||
|
||||
#[Locked]
|
||||
public Team $team;
|
||||
|
||||
#[Locked]
|
||||
public PushoverNotificationSettings $settings;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $pushoverEnabled = false;
|
||||
|
||||
#[Validate(['nullable', 'string'])]
|
||||
public ?string $pushoverUserKey = null;
|
||||
|
||||
#[Validate(['nullable', 'string'])]
|
||||
public ?string $pushoverApiToken = null;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $deploymentSuccessPushoverNotifications = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $deploymentFailurePushoverNotifications = true;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $statusChangePushoverNotifications = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $backupSuccessPushoverNotifications = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $backupFailurePushoverNotifications = true;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $scheduledTaskSuccessPushoverNotifications = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $scheduledTaskFailurePushoverNotifications = true;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $dockerCleanupSuccessPushoverNotifications = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $dockerCleanupFailurePushoverNotifications = true;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $serverDiskUsagePushoverNotifications = true;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $serverReachablePushoverNotifications = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $serverUnreachablePushoverNotifications = true;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
try {
|
||||
$this->team = auth()->user()->currentTeam();
|
||||
$this->settings = $this->team->pushoverNotificationSettings;
|
||||
$this->syncData();
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function syncData(bool $toModel = false)
|
||||
{
|
||||
if ($toModel) {
|
||||
$this->validate();
|
||||
$this->settings->pushover_enabled = $this->pushoverEnabled;
|
||||
$this->settings->pushover_user_key = $this->pushoverUserKey;
|
||||
$this->settings->pushover_api_token = $this->pushoverApiToken;
|
||||
|
||||
$this->settings->deployment_success_pushover_notifications = $this->deploymentSuccessPushoverNotifications;
|
||||
$this->settings->deployment_failure_pushover_notifications = $this->deploymentFailurePushoverNotifications;
|
||||
$this->settings->status_change_pushover_notifications = $this->statusChangePushoverNotifications;
|
||||
$this->settings->backup_success_pushover_notifications = $this->backupSuccessPushoverNotifications;
|
||||
$this->settings->backup_failure_pushover_notifications = $this->backupFailurePushoverNotifications;
|
||||
$this->settings->scheduled_task_success_pushover_notifications = $this->scheduledTaskSuccessPushoverNotifications;
|
||||
$this->settings->scheduled_task_failure_pushover_notifications = $this->scheduledTaskFailurePushoverNotifications;
|
||||
$this->settings->docker_cleanup_success_pushover_notifications = $this->dockerCleanupSuccessPushoverNotifications;
|
||||
$this->settings->docker_cleanup_failure_pushover_notifications = $this->dockerCleanupFailurePushoverNotifications;
|
||||
$this->settings->server_disk_usage_pushover_notifications = $this->serverDiskUsagePushoverNotifications;
|
||||
$this->settings->server_reachable_pushover_notifications = $this->serverReachablePushoverNotifications;
|
||||
$this->settings->server_unreachable_pushover_notifications = $this->serverUnreachablePushoverNotifications;
|
||||
|
||||
$this->settings->save();
|
||||
refreshSession();
|
||||
} else {
|
||||
$this->pushoverEnabled = $this->settings->pushover_enabled;
|
||||
$this->pushoverUserKey = $this->settings->pushover_user_key;
|
||||
$this->pushoverApiToken = $this->settings->pushover_api_token;
|
||||
|
||||
$this->deploymentSuccessPushoverNotifications = $this->settings->deployment_success_pushover_notifications;
|
||||
$this->deploymentFailurePushoverNotifications = $this->settings->deployment_failure_pushover_notifications;
|
||||
$this->statusChangePushoverNotifications = $this->settings->status_change_pushover_notifications;
|
||||
$this->backupSuccessPushoverNotifications = $this->settings->backup_success_pushover_notifications;
|
||||
$this->backupFailurePushoverNotifications = $this->settings->backup_failure_pushover_notifications;
|
||||
$this->scheduledTaskSuccessPushoverNotifications = $this->settings->scheduled_task_success_pushover_notifications;
|
||||
$this->scheduledTaskFailurePushoverNotifications = $this->settings->scheduled_task_failure_pushover_notifications;
|
||||
$this->dockerCleanupSuccessPushoverNotifications = $this->settings->docker_cleanup_success_pushover_notifications;
|
||||
$this->dockerCleanupFailurePushoverNotifications = $this->settings->docker_cleanup_failure_pushover_notifications;
|
||||
$this->serverDiskUsagePushoverNotifications = $this->settings->server_disk_usage_pushover_notifications;
|
||||
$this->serverReachablePushoverNotifications = $this->settings->server_reachable_pushover_notifications;
|
||||
$this->serverUnreachablePushoverNotifications = $this->settings->server_unreachable_pushover_notifications;
|
||||
}
|
||||
}
|
||||
|
||||
public function instantSavePushoverEnabled()
|
||||
{
|
||||
try {
|
||||
$this->validate([
|
||||
'pushoverUserKey' => 'required',
|
||||
'pushoverApiToken' => 'required',
|
||||
], [
|
||||
'pushoverUserKey.required' => 'Pushover User Key is required.',
|
||||
'pushoverApiToken.required' => 'Pushover API Token is required.',
|
||||
]);
|
||||
$this->saveModel();
|
||||
} catch (\Throwable $e) {
|
||||
$this->pushoverEnabled = false;
|
||||
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
$this->dispatch('refresh');
|
||||
}
|
||||
}
|
||||
|
||||
public function instantSave()
|
||||
{
|
||||
try {
|
||||
$this->syncData(true);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
$this->dispatch('refresh');
|
||||
}
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
$this->resetErrorBag();
|
||||
$this->syncData(true);
|
||||
$this->saveModel();
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function saveModel()
|
||||
{
|
||||
$this->syncData(true);
|
||||
refreshSession();
|
||||
$this->dispatch('success', 'Settings saved.');
|
||||
}
|
||||
|
||||
public function sendTestNotification()
|
||||
{
|
||||
try {
|
||||
$this->team->notify(new Test(channel: 'pushover'));
|
||||
$this->dispatch('success', 'Test notification sent.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.notifications.pushover');
|
||||
}
|
||||
}
|
||||
177
app/Livewire/Notifications/Slack.php
Normal file
177
app/Livewire/Notifications/Slack.php
Normal file
@@ -0,0 +1,177 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Notifications;
|
||||
|
||||
use App\Models\SlackNotificationSettings;
|
||||
use App\Models\Team;
|
||||
use App\Notifications\Test;
|
||||
use Livewire\Attributes\Locked;
|
||||
use Livewire\Attributes\Validate;
|
||||
use Livewire\Component;
|
||||
|
||||
class Slack extends Component
|
||||
{
|
||||
protected $listeners = ['refresh' => '$refresh'];
|
||||
|
||||
#[Locked]
|
||||
public Team $team;
|
||||
|
||||
#[Locked]
|
||||
public SlackNotificationSettings $settings;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $slackEnabled = false;
|
||||
|
||||
#[Validate(['url', 'nullable'])]
|
||||
public ?string $slackWebhookUrl = null;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $deploymentSuccessSlackNotifications = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $deploymentFailureSlackNotifications = true;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $statusChangeSlackNotifications = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $backupSuccessSlackNotifications = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $backupFailureSlackNotifications = true;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $scheduledTaskSuccessSlackNotifications = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $scheduledTaskFailureSlackNotifications = true;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $dockerCleanupSuccessSlackNotifications = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $dockerCleanupFailureSlackNotifications = true;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $serverDiskUsageSlackNotifications = true;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $serverReachableSlackNotifications = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $serverUnreachableSlackNotifications = true;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
try {
|
||||
$this->team = auth()->user()->currentTeam();
|
||||
$this->settings = $this->team->slackNotificationSettings;
|
||||
$this->syncData();
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function syncData(bool $toModel = false)
|
||||
{
|
||||
if ($toModel) {
|
||||
$this->validate();
|
||||
$this->settings->slack_enabled = $this->slackEnabled;
|
||||
$this->settings->slack_webhook_url = $this->slackWebhookUrl;
|
||||
|
||||
$this->settings->deployment_success_slack_notifications = $this->deploymentSuccessSlackNotifications;
|
||||
$this->settings->deployment_failure_slack_notifications = $this->deploymentFailureSlackNotifications;
|
||||
$this->settings->status_change_slack_notifications = $this->statusChangeSlackNotifications;
|
||||
$this->settings->backup_success_slack_notifications = $this->backupSuccessSlackNotifications;
|
||||
$this->settings->backup_failure_slack_notifications = $this->backupFailureSlackNotifications;
|
||||
$this->settings->scheduled_task_success_slack_notifications = $this->scheduledTaskSuccessSlackNotifications;
|
||||
$this->settings->scheduled_task_failure_slack_notifications = $this->scheduledTaskFailureSlackNotifications;
|
||||
$this->settings->docker_cleanup_success_slack_notifications = $this->dockerCleanupSuccessSlackNotifications;
|
||||
$this->settings->docker_cleanup_failure_slack_notifications = $this->dockerCleanupFailureSlackNotifications;
|
||||
$this->settings->server_disk_usage_slack_notifications = $this->serverDiskUsageSlackNotifications;
|
||||
$this->settings->server_reachable_slack_notifications = $this->serverReachableSlackNotifications;
|
||||
$this->settings->server_unreachable_slack_notifications = $this->serverUnreachableSlackNotifications;
|
||||
|
||||
$this->settings->save();
|
||||
refreshSession();
|
||||
} else {
|
||||
$this->slackEnabled = $this->settings->slack_enabled;
|
||||
$this->slackWebhookUrl = $this->settings->slack_webhook_url;
|
||||
|
||||
$this->deploymentSuccessSlackNotifications = $this->settings->deployment_success_slack_notifications;
|
||||
$this->deploymentFailureSlackNotifications = $this->settings->deployment_failure_slack_notifications;
|
||||
$this->statusChangeSlackNotifications = $this->settings->status_change_slack_notifications;
|
||||
$this->backupSuccessSlackNotifications = $this->settings->backup_success_slack_notifications;
|
||||
$this->backupFailureSlackNotifications = $this->settings->backup_failure_slack_notifications;
|
||||
$this->scheduledTaskSuccessSlackNotifications = $this->settings->scheduled_task_success_slack_notifications;
|
||||
$this->scheduledTaskFailureSlackNotifications = $this->settings->scheduled_task_failure_slack_notifications;
|
||||
$this->dockerCleanupSuccessSlackNotifications = $this->settings->docker_cleanup_success_slack_notifications;
|
||||
$this->dockerCleanupFailureSlackNotifications = $this->settings->docker_cleanup_failure_slack_notifications;
|
||||
$this->serverDiskUsageSlackNotifications = $this->settings->server_disk_usage_slack_notifications;
|
||||
$this->serverReachableSlackNotifications = $this->settings->server_reachable_slack_notifications;
|
||||
$this->serverUnreachableSlackNotifications = $this->settings->server_unreachable_slack_notifications;
|
||||
}
|
||||
}
|
||||
|
||||
public function instantSaveSlackEnabled()
|
||||
{
|
||||
try {
|
||||
$this->validate([
|
||||
'slackWebhookUrl' => 'required',
|
||||
], [
|
||||
'slackWebhookUrl.required' => 'Slack Webhook URL is required.',
|
||||
]);
|
||||
$this->saveModel();
|
||||
} catch (\Throwable $e) {
|
||||
$this->slackEnabled = false;
|
||||
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
$this->dispatch('refresh');
|
||||
}
|
||||
}
|
||||
|
||||
public function instantSave()
|
||||
{
|
||||
try {
|
||||
$this->syncData(true);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
$this->dispatch('refresh');
|
||||
}
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
$this->resetErrorBag();
|
||||
$this->syncData(true);
|
||||
$this->saveModel();
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function saveModel()
|
||||
{
|
||||
$this->syncData(true);
|
||||
refreshSession();
|
||||
$this->dispatch('success', 'Settings saved.');
|
||||
}
|
||||
|
||||
public function sendTestNotification()
|
||||
{
|
||||
try {
|
||||
$this->team->notify(new Test(channel: 'slack'));
|
||||
$this->dispatch('success', 'Test notification sent.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.notifications.slack');
|
||||
}
|
||||
}
|
||||
@@ -3,14 +3,22 @@
|
||||
namespace App\Livewire\Notifications;
|
||||
|
||||
use App\Models\Team;
|
||||
use App\Models\TelegramNotificationSettings;
|
||||
use App\Notifications\Test;
|
||||
use Livewire\Attributes\Locked;
|
||||
use Livewire\Attributes\Validate;
|
||||
use Livewire\Component;
|
||||
|
||||
class Telegram extends Component
|
||||
{
|
||||
protected $listeners = ['refresh' => '$refresh'];
|
||||
|
||||
#[Locked]
|
||||
public Team $team;
|
||||
|
||||
#[Locked]
|
||||
public TelegramNotificationSettings $settings;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $telegramEnabled = false;
|
||||
|
||||
@@ -21,42 +29,82 @@ class Telegram extends Component
|
||||
public ?string $telegramChatId = null;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $telegramNotificationsTest = false;
|
||||
public bool $deploymentSuccessTelegramNotifications = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $telegramNotificationsDeployments = false;
|
||||
public bool $deploymentFailureTelegramNotifications = true;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $telegramNotificationsStatusChanges = false;
|
||||
public bool $statusChangeTelegramNotifications = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $telegramNotificationsDatabaseBackups = false;
|
||||
public bool $backupSuccessTelegramNotifications = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $telegramNotificationsScheduledTasks = false;
|
||||
|
||||
#[Validate(['nullable', 'string'])]
|
||||
public ?string $telegramNotificationsTestMessageThreadId = null;
|
||||
|
||||
#[Validate(['nullable', 'string'])]
|
||||
public ?string $telegramNotificationsDeploymentsMessageThreadId = null;
|
||||
|
||||
#[Validate(['nullable', 'string'])]
|
||||
public ?string $telegramNotificationsStatusChangesMessageThreadId = null;
|
||||
|
||||
#[Validate(['nullable', 'string'])]
|
||||
public ?string $telegramNotificationsDatabaseBackupsMessageThreadId = null;
|
||||
|
||||
#[Validate(['nullable', 'string'])]
|
||||
public ?string $telegramNotificationsScheduledTasksThreadId = null;
|
||||
public bool $backupFailureTelegramNotifications = true;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $telegramNotificationsServerDiskUsage = false;
|
||||
public bool $scheduledTaskSuccessTelegramNotifications = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $scheduledTaskFailureTelegramNotifications = true;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $dockerCleanupSuccessTelegramNotifications = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $dockerCleanupFailureTelegramNotifications = true;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $serverDiskUsageTelegramNotifications = true;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $serverReachableTelegramNotifications = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $serverUnreachableTelegramNotifications = true;
|
||||
|
||||
#[Validate(['nullable', 'string'])]
|
||||
public ?string $telegramNotificationsDeploymentSuccessThreadId = null;
|
||||
|
||||
#[Validate(['nullable', 'string'])]
|
||||
public ?string $telegramNotificationsDeploymentFailureThreadId = null;
|
||||
|
||||
#[Validate(['nullable', 'string'])]
|
||||
public ?string $telegramNotificationsStatusChangeThreadId = null;
|
||||
|
||||
#[Validate(['nullable', 'string'])]
|
||||
public ?string $telegramNotificationsBackupSuccessThreadId = null;
|
||||
|
||||
#[Validate(['nullable', 'string'])]
|
||||
public ?string $telegramNotificationsBackupFailureThreadId = null;
|
||||
|
||||
#[Validate(['nullable', 'string'])]
|
||||
public ?string $telegramNotificationsScheduledTaskSuccessThreadId = null;
|
||||
|
||||
#[Validate(['nullable', 'string'])]
|
||||
public ?string $telegramNotificationsScheduledTaskFailureThreadId = null;
|
||||
|
||||
#[Validate(['nullable', 'string'])]
|
||||
public ?string $telegramNotificationsDockerCleanupSuccessThreadId = null;
|
||||
|
||||
#[Validate(['nullable', 'string'])]
|
||||
public ?string $telegramNotificationsDockerCleanupFailureThreadId = null;
|
||||
|
||||
#[Validate(['nullable', 'string'])]
|
||||
public ?string $telegramNotificationsServerDiskUsageThreadId = null;
|
||||
|
||||
#[Validate(['nullable', 'string'])]
|
||||
public ?string $telegramNotificationsServerReachableThreadId = null;
|
||||
|
||||
#[Validate(['nullable', 'string'])]
|
||||
public ?string $telegramNotificationsServerUnreachableThreadId = null;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
try {
|
||||
$this->team = auth()->user()->currentTeam();
|
||||
$this->settings = $this->team->telegramNotificationSettings;
|
||||
$this->syncData();
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
@@ -67,39 +115,68 @@ class Telegram extends Component
|
||||
{
|
||||
if ($toModel) {
|
||||
$this->validate();
|
||||
$this->team->telegram_enabled = $this->telegramEnabled;
|
||||
$this->team->telegram_token = $this->telegramToken;
|
||||
$this->team->telegram_chat_id = $this->telegramChatId;
|
||||
$this->team->telegram_notifications_test = $this->telegramNotificationsTest;
|
||||
$this->team->telegram_notifications_deployments = $this->telegramNotificationsDeployments;
|
||||
$this->team->telegram_notifications_status_changes = $this->telegramNotificationsStatusChanges;
|
||||
$this->team->telegram_notifications_database_backups = $this->telegramNotificationsDatabaseBackups;
|
||||
$this->team->telegram_notifications_scheduled_tasks = $this->telegramNotificationsScheduledTasks;
|
||||
$this->team->telegram_notifications_test_message_thread_id = $this->telegramNotificationsTestMessageThreadId;
|
||||
$this->team->telegram_notifications_deployments_message_thread_id = $this->telegramNotificationsDeploymentsMessageThreadId;
|
||||
$this->team->telegram_notifications_status_changes_message_thread_id = $this->telegramNotificationsStatusChangesMessageThreadId;
|
||||
$this->team->telegram_notifications_database_backups_message_thread_id = $this->telegramNotificationsDatabaseBackupsMessageThreadId;
|
||||
$this->team->telegram_notifications_scheduled_tasks_thread_id = $this->telegramNotificationsScheduledTasksThreadId;
|
||||
$this->team->telegram_notifications_server_disk_usage = $this->telegramNotificationsServerDiskUsage;
|
||||
$this->team->save();
|
||||
refreshSession();
|
||||
} else {
|
||||
$this->telegramEnabled = $this->team->telegram_enabled;
|
||||
$this->telegramToken = $this->team->telegram_token;
|
||||
$this->telegramChatId = $this->team->telegram_chat_id;
|
||||
$this->telegramNotificationsTest = $this->team->telegram_notifications_test;
|
||||
$this->telegramNotificationsDeployments = $this->team->telegram_notifications_deployments;
|
||||
$this->telegramNotificationsStatusChanges = $this->team->telegram_notifications_status_changes;
|
||||
$this->telegramNotificationsDatabaseBackups = $this->team->telegram_notifications_database_backups;
|
||||
$this->telegramNotificationsScheduledTasks = $this->team->telegram_notifications_scheduled_tasks;
|
||||
$this->telegramNotificationsTestMessageThreadId = $this->team->telegram_notifications_test_message_thread_id;
|
||||
$this->telegramNotificationsDeploymentsMessageThreadId = $this->team->telegram_notifications_deployments_message_thread_id;
|
||||
$this->telegramNotificationsStatusChangesMessageThreadId = $this->team->telegram_notifications_status_changes_message_thread_id;
|
||||
$this->telegramNotificationsDatabaseBackupsMessageThreadId = $this->team->telegram_notifications_database_backups_message_thread_id;
|
||||
$this->telegramNotificationsScheduledTasksThreadId = $this->team->telegram_notifications_scheduled_tasks_thread_id;
|
||||
$this->telegramNotificationsServerDiskUsage = $this->team->telegram_notifications_server_disk_usage;
|
||||
}
|
||||
$this->settings->telegram_enabled = $this->telegramEnabled;
|
||||
$this->settings->telegram_token = $this->telegramToken;
|
||||
$this->settings->telegram_chat_id = $this->telegramChatId;
|
||||
|
||||
$this->settings->deployment_success_telegram_notifications = $this->deploymentSuccessTelegramNotifications;
|
||||
$this->settings->deployment_failure_telegram_notifications = $this->deploymentFailureTelegramNotifications;
|
||||
$this->settings->status_change_telegram_notifications = $this->statusChangeTelegramNotifications;
|
||||
$this->settings->backup_success_telegram_notifications = $this->backupSuccessTelegramNotifications;
|
||||
$this->settings->backup_failure_telegram_notifications = $this->backupFailureTelegramNotifications;
|
||||
$this->settings->scheduled_task_success_telegram_notifications = $this->scheduledTaskSuccessTelegramNotifications;
|
||||
$this->settings->scheduled_task_failure_telegram_notifications = $this->scheduledTaskFailureTelegramNotifications;
|
||||
$this->settings->docker_cleanup_success_telegram_notifications = $this->dockerCleanupSuccessTelegramNotifications;
|
||||
$this->settings->docker_cleanup_failure_telegram_notifications = $this->dockerCleanupFailureTelegramNotifications;
|
||||
$this->settings->server_disk_usage_telegram_notifications = $this->serverDiskUsageTelegramNotifications;
|
||||
$this->settings->server_reachable_telegram_notifications = $this->serverReachableTelegramNotifications;
|
||||
$this->settings->server_unreachable_telegram_notifications = $this->serverUnreachableTelegramNotifications;
|
||||
|
||||
$this->settings->telegram_notifications_deployment_success_thread_id = $this->telegramNotificationsDeploymentSuccessThreadId;
|
||||
$this->settings->telegram_notifications_deployment_failure_thread_id = $this->telegramNotificationsDeploymentFailureThreadId;
|
||||
$this->settings->telegram_notifications_status_change_thread_id = $this->telegramNotificationsStatusChangeThreadId;
|
||||
$this->settings->telegram_notifications_backup_success_thread_id = $this->telegramNotificationsBackupSuccessThreadId;
|
||||
$this->settings->telegram_notifications_backup_failure_thread_id = $this->telegramNotificationsBackupFailureThreadId;
|
||||
$this->settings->telegram_notifications_scheduled_task_success_thread_id = $this->telegramNotificationsScheduledTaskSuccessThreadId;
|
||||
$this->settings->telegram_notifications_scheduled_task_failure_thread_id = $this->telegramNotificationsScheduledTaskFailureThreadId;
|
||||
$this->settings->telegram_notifications_docker_cleanup_success_thread_id = $this->telegramNotificationsDockerCleanupSuccessThreadId;
|
||||
$this->settings->telegram_notifications_docker_cleanup_failure_thread_id = $this->telegramNotificationsDockerCleanupFailureThreadId;
|
||||
$this->settings->telegram_notifications_server_disk_usage_thread_id = $this->telegramNotificationsServerDiskUsageThreadId;
|
||||
$this->settings->telegram_notifications_server_reachable_thread_id = $this->telegramNotificationsServerReachableThreadId;
|
||||
$this->settings->telegram_notifications_server_unreachable_thread_id = $this->telegramNotificationsServerUnreachableThreadId;
|
||||
|
||||
$this->settings->save();
|
||||
} else {
|
||||
$this->telegramEnabled = $this->settings->telegram_enabled;
|
||||
$this->telegramToken = $this->settings->telegram_token;
|
||||
$this->telegramChatId = $this->settings->telegram_chat_id;
|
||||
|
||||
$this->deploymentSuccessTelegramNotifications = $this->settings->deployment_success_telegram_notifications;
|
||||
$this->deploymentFailureTelegramNotifications = $this->settings->deployment_failure_telegram_notifications;
|
||||
$this->statusChangeTelegramNotifications = $this->settings->status_change_telegram_notifications;
|
||||
$this->backupSuccessTelegramNotifications = $this->settings->backup_success_telegram_notifications;
|
||||
$this->backupFailureTelegramNotifications = $this->settings->backup_failure_telegram_notifications;
|
||||
$this->scheduledTaskSuccessTelegramNotifications = $this->settings->scheduled_task_success_telegram_notifications;
|
||||
$this->scheduledTaskFailureTelegramNotifications = $this->settings->scheduled_task_failure_telegram_notifications;
|
||||
$this->dockerCleanupSuccessTelegramNotifications = $this->settings->docker_cleanup_success_telegram_notifications;
|
||||
$this->dockerCleanupFailureTelegramNotifications = $this->settings->docker_cleanup_failure_telegram_notifications;
|
||||
$this->serverDiskUsageTelegramNotifications = $this->settings->server_disk_usage_telegram_notifications;
|
||||
$this->serverReachableTelegramNotifications = $this->settings->server_reachable_telegram_notifications;
|
||||
$this->serverUnreachableTelegramNotifications = $this->settings->server_unreachable_telegram_notifications;
|
||||
|
||||
$this->telegramNotificationsDeploymentSuccessThreadId = $this->settings->telegram_notifications_deployment_success_thread_id;
|
||||
$this->telegramNotificationsDeploymentFailureThreadId = $this->settings->telegram_notifications_deployment_failure_thread_id;
|
||||
$this->telegramNotificationsStatusChangeThreadId = $this->settings->telegram_notifications_status_change_thread_id;
|
||||
$this->telegramNotificationsBackupSuccessThreadId = $this->settings->telegram_notifications_backup_success_thread_id;
|
||||
$this->telegramNotificationsBackupFailureThreadId = $this->settings->telegram_notifications_backup_failure_thread_id;
|
||||
$this->telegramNotificationsScheduledTaskSuccessThreadId = $this->settings->telegram_notifications_scheduled_task_success_thread_id;
|
||||
$this->telegramNotificationsScheduledTaskFailureThreadId = $this->settings->telegram_notifications_scheduled_task_failure_thread_id;
|
||||
$this->telegramNotificationsDockerCleanupSuccessThreadId = $this->settings->telegram_notifications_docker_cleanup_success_thread_id;
|
||||
$this->telegramNotificationsDockerCleanupFailureThreadId = $this->settings->telegram_notifications_docker_cleanup_failure_thread_id;
|
||||
$this->telegramNotificationsServerDiskUsageThreadId = $this->settings->telegram_notifications_server_disk_usage_thread_id;
|
||||
$this->telegramNotificationsServerReachableThreadId = $this->settings->telegram_notifications_server_reachable_thread_id;
|
||||
$this->telegramNotificationsServerUnreachableThreadId = $this->settings->telegram_notifications_server_unreachable_thread_id;
|
||||
}
|
||||
}
|
||||
|
||||
public function instantSave()
|
||||
@@ -108,6 +185,8 @@ class Telegram extends Component
|
||||
$this->syncData(true);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
$this->dispatch('refresh');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,6 +216,8 @@ class Telegram extends Component
|
||||
$this->telegramEnabled = false;
|
||||
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
$this->dispatch('refresh');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,7 +231,7 @@ class Telegram extends Component
|
||||
public function sendTestNotification()
|
||||
{
|
||||
try {
|
||||
$this->team->notify(new Test);
|
||||
$this->team->notify(new Test(channel: 'telegram'));
|
||||
$this->dispatch('success', 'Test notification sent.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
|
||||
@@ -25,6 +25,9 @@ class Advanced extends Component
|
||||
#[Validate(['boolean'])]
|
||||
public bool $isAutoDeployEnabled = true;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $disableBuildCache = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $isLogDrainEnabled = false;
|
||||
|
||||
@@ -95,6 +98,7 @@ class Advanced extends Component
|
||||
$this->application->settings->is_stripprefix_enabled = $this->isStripprefixEnabled;
|
||||
$this->application->settings->is_raw_compose_deployment_enabled = $this->isRawComposeDeploymentEnabled;
|
||||
$this->application->settings->connect_to_docker_network = $this->isConnectToDockerNetworkEnabled;
|
||||
$this->application->settings->disable_build_cache = $this->disableBuildCache;
|
||||
$this->application->settings->save();
|
||||
} else {
|
||||
$this->isForceHttpsEnabled = $this->application->isForceHttpsEnabled();
|
||||
@@ -116,6 +120,7 @@ class Advanced extends Component
|
||||
$this->customInternalName = $this->application->settings->custom_internal_name;
|
||||
$this->isRawComposeDeploymentEnabled = $this->application->settings->is_raw_compose_deployment_enabled;
|
||||
$this->isConnectToDockerNetworkEnabled = $this->application->settings->connect_to_docker_network;
|
||||
$this->disableBuildCache = $this->application->settings->disable_build_cache;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,125 +46,84 @@ class Index extends Component
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
$this->project = $project;
|
||||
$this->environment = $environment;
|
||||
$this->applications = $this->environment->applications->load(['tags']);
|
||||
$this->applications = $this->applications->map(function ($application) {
|
||||
if (data_get($application, 'environment.project.uuid')) {
|
||||
$application->hrefLink = route('project.application.configuration', [
|
||||
'project_uuid' => data_get($application, 'environment.project.uuid'),
|
||||
'environment_name' => data_get($application, 'environment.name'),
|
||||
'application_uuid' => data_get($application, 'uuid'),
|
||||
$this->environment = $environment->loadCount([
|
||||
'applications',
|
||||
'redis',
|
||||
'postgresqls',
|
||||
'mysqls',
|
||||
'keydbs',
|
||||
'dragonflies',
|
||||
'clickhouses',
|
||||
'mariadbs',
|
||||
'mongodbs',
|
||||
'services',
|
||||
]);
|
||||
|
||||
// Eager load all relationships for applications including nested ones
|
||||
$this->applications = $this->environment->applications()->with([
|
||||
'tags',
|
||||
'additional_servers.settings',
|
||||
'additional_networks',
|
||||
'destination.server.settings',
|
||||
'settings',
|
||||
])->get()->sortBy('name');
|
||||
$this->applications = $this->applications->map(function ($application) {
|
||||
$application->hrefLink = route('project.application.configuration', [
|
||||
'project_uuid' => $this->project->uuid,
|
||||
'application_uuid' => $application->uuid,
|
||||
'environment_name' => $this->environment->name,
|
||||
]);
|
||||
}
|
||||
|
||||
return $application;
|
||||
});
|
||||
$this->postgresqls = $this->environment->postgresqls->load(['tags'])->sortBy('name');
|
||||
$this->postgresqls = $this->postgresqls->map(function ($postgresql) {
|
||||
if (data_get($postgresql, 'environment.project.uuid')) {
|
||||
$postgresql->hrefLink = route('project.database.configuration', [
|
||||
'project_uuid' => data_get($postgresql, 'environment.project.uuid'),
|
||||
'environment_name' => data_get($postgresql, 'environment.name'),
|
||||
'database_uuid' => data_get($postgresql, 'uuid'),
|
||||
|
||||
// Load all database resources in a single query per type
|
||||
$databaseTypes = [
|
||||
'postgresqls' => 'postgresqls',
|
||||
'redis' => 'redis',
|
||||
'mongodbs' => 'mongodbs',
|
||||
'mysqls' => 'mysqls',
|
||||
'mariadbs' => 'mariadbs',
|
||||
'keydbs' => 'keydbs',
|
||||
'dragonflies' => 'dragonflies',
|
||||
'clickhouses' => 'clickhouses',
|
||||
];
|
||||
|
||||
// Load all server-related data first to prevent duplicate queries
|
||||
$serverData = $this->environment->applications()
|
||||
->with(['destination.server.settings'])
|
||||
->get()
|
||||
->pluck('destination.server')
|
||||
->filter()
|
||||
->unique('id');
|
||||
|
||||
foreach ($databaseTypes as $property => $relation) {
|
||||
$this->{$property} = $this->environment->{$relation}()->with([
|
||||
'tags',
|
||||
'destination.server.settings',
|
||||
])->get()->sortBy('name');
|
||||
$this->{$property} = $this->{$property}->map(function ($db) {
|
||||
$db->hrefLink = route('project.database.configuration', [
|
||||
'project_uuid' => $this->project->uuid,
|
||||
'database_uuid' => $db->uuid,
|
||||
'environment_name' => $this->environment->name,
|
||||
]);
|
||||
|
||||
return $db;
|
||||
});
|
||||
}
|
||||
|
||||
return $postgresql;
|
||||
});
|
||||
$this->redis = $this->environment->redis->load(['tags'])->sortBy('name');
|
||||
$this->redis = $this->redis->map(function ($redis) {
|
||||
if (data_get($redis, 'environment.project.uuid')) {
|
||||
$redis->hrefLink = route('project.database.configuration', [
|
||||
'project_uuid' => data_get($redis, 'environment.project.uuid'),
|
||||
'environment_name' => data_get($redis, 'environment.name'),
|
||||
'database_uuid' => data_get($redis, 'uuid'),
|
||||
]);
|
||||
}
|
||||
|
||||
return $redis;
|
||||
});
|
||||
$this->mongodbs = $this->environment->mongodbs->load(['tags'])->sortBy('name');
|
||||
$this->mongodbs = $this->mongodbs->map(function ($mongodb) {
|
||||
if (data_get($mongodb, 'environment.project.uuid')) {
|
||||
$mongodb->hrefLink = route('project.database.configuration', [
|
||||
'project_uuid' => data_get($mongodb, 'environment.project.uuid'),
|
||||
'environment_name' => data_get($mongodb, 'environment.name'),
|
||||
'database_uuid' => data_get($mongodb, 'uuid'),
|
||||
]);
|
||||
}
|
||||
|
||||
return $mongodb;
|
||||
});
|
||||
$this->mysqls = $this->environment->mysqls->load(['tags'])->sortBy('name');
|
||||
$this->mysqls = $this->mysqls->map(function ($mysql) {
|
||||
if (data_get($mysql, 'environment.project.uuid')) {
|
||||
$mysql->hrefLink = route('project.database.configuration', [
|
||||
'project_uuid' => data_get($mysql, 'environment.project.uuid'),
|
||||
'environment_name' => data_get($mysql, 'environment.name'),
|
||||
'database_uuid' => data_get($mysql, 'uuid'),
|
||||
]);
|
||||
}
|
||||
|
||||
return $mysql;
|
||||
});
|
||||
$this->mariadbs = $this->environment->mariadbs->load(['tags'])->sortBy('name');
|
||||
$this->mariadbs = $this->mariadbs->map(function ($mariadb) {
|
||||
if (data_get($mariadb, 'environment.project.uuid')) {
|
||||
$mariadb->hrefLink = route('project.database.configuration', [
|
||||
'project_uuid' => data_get($mariadb, 'environment.project.uuid'),
|
||||
'environment_name' => data_get($mariadb, 'environment.name'),
|
||||
'database_uuid' => data_get($mariadb, 'uuid'),
|
||||
]);
|
||||
}
|
||||
|
||||
return $mariadb;
|
||||
});
|
||||
$this->keydbs = $this->environment->keydbs->load(['tags'])->sortBy('name');
|
||||
$this->keydbs = $this->keydbs->map(function ($keydb) {
|
||||
if (data_get($keydb, 'environment.project.uuid')) {
|
||||
$keydb->hrefLink = route('project.database.configuration', [
|
||||
'project_uuid' => data_get($keydb, 'environment.project.uuid'),
|
||||
'environment_name' => data_get($keydb, 'environment.name'),
|
||||
'database_uuid' => data_get($keydb, 'uuid'),
|
||||
]);
|
||||
}
|
||||
|
||||
return $keydb;
|
||||
});
|
||||
$this->dragonflies = $this->environment->dragonflies->load(['tags'])->sortBy('name');
|
||||
$this->dragonflies = $this->dragonflies->map(function ($dragonfly) {
|
||||
if (data_get($dragonfly, 'environment.project.uuid')) {
|
||||
$dragonfly->hrefLink = route('project.database.configuration', [
|
||||
'project_uuid' => data_get($dragonfly, 'environment.project.uuid'),
|
||||
'environment_name' => data_get($dragonfly, 'environment.name'),
|
||||
'database_uuid' => data_get($dragonfly, 'uuid'),
|
||||
]);
|
||||
}
|
||||
|
||||
return $dragonfly;
|
||||
});
|
||||
$this->clickhouses = $this->environment->clickhouses->load(['tags'])->sortBy('name');
|
||||
$this->clickhouses = $this->clickhouses->map(function ($clickhouse) {
|
||||
if (data_get($clickhouse, 'environment.project.uuid')) {
|
||||
$clickhouse->hrefLink = route('project.database.configuration', [
|
||||
'project_uuid' => data_get($clickhouse, 'environment.project.uuid'),
|
||||
'environment_name' => data_get($clickhouse, 'environment.name'),
|
||||
'database_uuid' => data_get($clickhouse, 'uuid'),
|
||||
]);
|
||||
}
|
||||
|
||||
return $clickhouse;
|
||||
});
|
||||
$this->services = $this->environment->services->load(['tags'])->sortBy('name');
|
||||
// Load services with their tags and server
|
||||
$this->services = $this->environment->services()->with([
|
||||
'tags',
|
||||
'destination.server.settings',
|
||||
])->get()->sortBy('name');
|
||||
$this->services = $this->services->map(function ($service) {
|
||||
if (data_get($service, 'environment.project.uuid')) {
|
||||
$service->hrefLink = route('project.service.configuration', [
|
||||
'project_uuid' => data_get($service, 'environment.project.uuid'),
|
||||
'environment_name' => data_get($service, 'environment.name'),
|
||||
'service_uuid' => data_get($service, 'uuid'),
|
||||
'project_uuid' => $this->project->uuid,
|
||||
'service_uuid' => $service->uuid,
|
||||
'environment_name' => $this->environment->name,
|
||||
]);
|
||||
$service->status = $service->status();
|
||||
}
|
||||
|
||||
return $service;
|
||||
});
|
||||
|
||||
@@ -24,6 +24,14 @@ class Executions extends Component
|
||||
#[Locked]
|
||||
public ?string $serverTimezone = null;
|
||||
|
||||
public $currentPage = 1;
|
||||
|
||||
public $logsPerPage = 100;
|
||||
|
||||
public $selectedExecution = null;
|
||||
|
||||
public $isPollingActive = false;
|
||||
|
||||
public function getListeners()
|
||||
{
|
||||
$teamId = Auth::user()->currentTeam()->id;
|
||||
@@ -54,16 +62,84 @@ class Executions extends Component
|
||||
public function refreshExecutions(): void
|
||||
{
|
||||
$this->executions = $this->task->executions()->take(20)->get();
|
||||
if ($this->selectedKey) {
|
||||
$this->selectedExecution = $this->task->executions()->find($this->selectedKey);
|
||||
if ($this->selectedExecution && $this->selectedExecution->status !== 'running') {
|
||||
$this->isPollingActive = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function selectTask($key): void
|
||||
{
|
||||
if ($key == $this->selectedKey) {
|
||||
$this->selectedKey = null;
|
||||
$this->selectedExecution = null;
|
||||
$this->currentPage = 1;
|
||||
$this->isPollingActive = false;
|
||||
|
||||
return;
|
||||
}
|
||||
$this->selectedKey = $key;
|
||||
$this->selectedExecution = $this->task->executions()->find($key);
|
||||
$this->currentPage = 1;
|
||||
|
||||
// Start polling if task is running
|
||||
if ($this->selectedExecution && $this->selectedExecution->status === 'running') {
|
||||
$this->isPollingActive = true;
|
||||
}
|
||||
}
|
||||
|
||||
public function polling()
|
||||
{
|
||||
if ($this->selectedExecution && $this->isPollingActive) {
|
||||
$this->selectedExecution->refresh();
|
||||
if ($this->selectedExecution->status !== 'running') {
|
||||
$this->isPollingActive = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function loadMoreLogs()
|
||||
{
|
||||
$this->currentPage++;
|
||||
}
|
||||
|
||||
public function getLogLinesProperty()
|
||||
{
|
||||
if (! $this->selectedExecution) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
if (! $this->selectedExecution->message) {
|
||||
return collect(['Waiting for task output...']);
|
||||
}
|
||||
|
||||
$lines = collect(explode("\n", $this->selectedExecution->message));
|
||||
|
||||
return $lines->take($this->currentPage * $this->logsPerPage);
|
||||
}
|
||||
|
||||
public function downloadLogs(int $executionId)
|
||||
{
|
||||
$execution = $this->executions->firstWhere('id', $executionId);
|
||||
if (! $execution) {
|
||||
return;
|
||||
}
|
||||
|
||||
return response()->streamDownload(function () use ($execution) {
|
||||
echo $execution->message;
|
||||
}, 'task-execution-'.$execution->id.'.log');
|
||||
}
|
||||
|
||||
public function hasMoreLogs()
|
||||
{
|
||||
if (! $this->selectedExecution || ! $this->selectedExecution->message) {
|
||||
return false;
|
||||
}
|
||||
$lines = collect(explode("\n", $this->selectedExecution->message));
|
||||
|
||||
return $lines->count() > ($this->currentPage * $this->logsPerPage);
|
||||
}
|
||||
|
||||
public function formatDateInServerTimezone($date)
|
||||
|
||||
@@ -11,13 +11,7 @@ class ApiTokens extends Component
|
||||
|
||||
public $tokens = [];
|
||||
|
||||
public bool $viewSensitiveData = false;
|
||||
|
||||
public bool $readOnly = true;
|
||||
|
||||
public bool $rootAccess = false;
|
||||
|
||||
public array $permissions = ['read-only'];
|
||||
public array $permissions = ['read'];
|
||||
|
||||
public $isApiEnabled;
|
||||
|
||||
@@ -29,52 +23,29 @@ class ApiTokens extends Component
|
||||
public function mount()
|
||||
{
|
||||
$this->isApiEnabled = InstanceSettings::get()->is_api_enabled;
|
||||
$this->getTokens();
|
||||
}
|
||||
|
||||
private function getTokens()
|
||||
{
|
||||
$this->tokens = auth()->user()->tokens->sortByDesc('created_at');
|
||||
}
|
||||
|
||||
public function updatedViewSensitiveData()
|
||||
public function updatedPermissions($permissionToUpdate)
|
||||
{
|
||||
if ($this->viewSensitiveData) {
|
||||
$this->permissions[] = 'view:sensitive';
|
||||
$this->permissions = array_diff($this->permissions, ['*']);
|
||||
$this->rootAccess = false;
|
||||
if ($permissionToUpdate == 'root') {
|
||||
$this->permissions = ['root'];
|
||||
} elseif ($permissionToUpdate == 'read:sensitive' && ! in_array('read', $this->permissions)) {
|
||||
$this->permissions[] = 'read';
|
||||
} elseif ($permissionToUpdate == 'deploy') {
|
||||
$this->permissions = ['deploy'];
|
||||
} else {
|
||||
$this->permissions = array_diff($this->permissions, ['view:sensitive']);
|
||||
}
|
||||
$this->makeSureOneIsSelected();
|
||||
}
|
||||
|
||||
public function updatedReadOnly()
|
||||
{
|
||||
if ($this->readOnly) {
|
||||
$this->permissions[] = 'read-only';
|
||||
$this->permissions = array_diff($this->permissions, ['*']);
|
||||
$this->rootAccess = false;
|
||||
} else {
|
||||
$this->permissions = array_diff($this->permissions, ['read-only']);
|
||||
}
|
||||
$this->makeSureOneIsSelected();
|
||||
}
|
||||
|
||||
public function updatedRootAccess()
|
||||
{
|
||||
if ($this->rootAccess) {
|
||||
$this->permissions = ['*'];
|
||||
$this->readOnly = false;
|
||||
$this->viewSensitiveData = false;
|
||||
} else {
|
||||
$this->readOnly = true;
|
||||
$this->permissions = ['read-only'];
|
||||
}
|
||||
}
|
||||
|
||||
public function makeSureOneIsSelected()
|
||||
{
|
||||
if (count($this->permissions) == 0) {
|
||||
$this->permissions = ['read-only'];
|
||||
$this->readOnly = true;
|
||||
$this->permissions = ['read'];
|
||||
}
|
||||
}
|
||||
sort($this->permissions);
|
||||
}
|
||||
|
||||
public function addNewToken()
|
||||
{
|
||||
@@ -82,8 +53,8 @@ class ApiTokens extends Component
|
||||
$this->validate([
|
||||
'description' => 'required|min:3|max:255',
|
||||
]);
|
||||
$token = auth()->user()->createToken($this->description, $this->permissions);
|
||||
$this->tokens = auth()->user()->tokens;
|
||||
$token = auth()->user()->createToken($this->description, array_values($this->permissions));
|
||||
$this->getTokens();
|
||||
session()->flash('token', $token->plainTextToken);
|
||||
} catch (\Exception $e) {
|
||||
return handleError($e, $this);
|
||||
@@ -92,8 +63,12 @@ class ApiTokens extends Component
|
||||
|
||||
public function revoke(int $id)
|
||||
{
|
||||
$token = auth()->user()->tokens()->where('id', $id)->first();
|
||||
try {
|
||||
$token = auth()->user()->tokens()->where('id', $id)->firstOrFail();
|
||||
$token->delete();
|
||||
$this->tokens = auth()->user()->tokens;
|
||||
$this->getTokens();
|
||||
} catch (\Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ class Proxy extends Component
|
||||
|
||||
public $proxy_settings = null;
|
||||
|
||||
public bool $redirect_enabled = true;
|
||||
|
||||
public ?string $redirect_url = null;
|
||||
|
||||
protected $listeners = ['proxyStatusUpdated', 'saveConfiguration' => 'submit'];
|
||||
@@ -26,6 +28,7 @@ class Proxy extends Component
|
||||
public function mount()
|
||||
{
|
||||
$this->selectedProxy = $this->server->proxyType();
|
||||
$this->redirect_enabled = data_get($this->server, 'proxy.redirect_enabled', true);
|
||||
$this->redirect_url = data_get($this->server, 'proxy.redirect_url');
|
||||
}
|
||||
|
||||
@@ -38,7 +41,7 @@ class Proxy extends Component
|
||||
{
|
||||
$this->server->proxy = null;
|
||||
$this->server->save();
|
||||
$this->dispatch('proxyChanged');
|
||||
$this->dispatch('reloadWindow');
|
||||
}
|
||||
|
||||
public function selectProxy($proxy_type)
|
||||
@@ -46,7 +49,7 @@ class Proxy extends Component
|
||||
try {
|
||||
$this->server->changeProxy($proxy_type, async: false);
|
||||
$this->selectedProxy = $this->server->proxy->type;
|
||||
$this->dispatch('proxyStatusUpdated');
|
||||
$this->dispatch('reloadWindow');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
@@ -63,13 +66,25 @@ class Proxy extends Component
|
||||
}
|
||||
}
|
||||
|
||||
public function instantSaveRedirect()
|
||||
{
|
||||
try {
|
||||
$this->server->proxy->redirect_enabled = $this->redirect_enabled;
|
||||
$this->server->save();
|
||||
$this->server->setupDefaultRedirect();
|
||||
$this->dispatch('success', 'Proxy configuration saved.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
SaveConfiguration::run($this->server, $this->proxy_settings);
|
||||
$this->server->proxy->redirect_url = $this->redirect_url;
|
||||
$this->server->save();
|
||||
$this->server->setupDefault404Redirect();
|
||||
$this->server->setupDefaultRedirect();
|
||||
$this->dispatch('success', 'Proxy configuration saved.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
|
||||
@@ -65,7 +65,7 @@ class Deploy extends Component
|
||||
public function restart()
|
||||
{
|
||||
try {
|
||||
$this->stop(forceStop: false);
|
||||
$this->stop();
|
||||
$this->dispatch('checkProxy');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
@@ -105,6 +105,7 @@ class Deploy extends Component
|
||||
|
||||
$startTime = Carbon::now()->getTimestamp();
|
||||
while ($process->running()) {
|
||||
ray('running');
|
||||
if (Carbon::now()->getTimestamp() - $startTime >= $timeout) {
|
||||
$this->forceStopContainer($containerName);
|
||||
break;
|
||||
|
||||
@@ -23,9 +23,6 @@ class Index extends Component
|
||||
#[Validate('nullable|string|max:255')]
|
||||
public ?string $fqdn = null;
|
||||
|
||||
#[Validate('nullable|string|max:255')]
|
||||
public ?string $resale_license = null;
|
||||
|
||||
#[Validate('required|integer|min:1025|max:65535')]
|
||||
public int $public_port_min;
|
||||
|
||||
@@ -83,7 +80,6 @@ class Index extends Component
|
||||
} else {
|
||||
$this->settings = instanceSettings();
|
||||
$this->fqdn = $this->settings->fqdn;
|
||||
$this->resale_license = $this->settings->resale_license;
|
||||
$this->public_port_min = $this->settings->public_port_min;
|
||||
$this->public_port_max = $this->settings->public_port_max;
|
||||
$this->custom_dns_servers = $this->settings->custom_dns_servers;
|
||||
@@ -122,7 +118,6 @@ class Index extends Component
|
||||
}
|
||||
|
||||
$this->settings->fqdn = $this->fqdn;
|
||||
$this->settings->resale_license = $this->resale_license;
|
||||
$this->settings->public_port_min = $this->public_port_min;
|
||||
$this->settings->public_port_max = $this->public_port_max;
|
||||
$this->settings->custom_dns_servers = $this->custom_dns_servers;
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Team;
|
||||
use App\Notifications\Test;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Livewire\Attributes\Locked;
|
||||
use Livewire\Attributes\Validate;
|
||||
use Livewire\Component;
|
||||
|
||||
@@ -10,39 +14,48 @@ class SettingsEmail extends Component
|
||||
{
|
||||
public InstanceSettings $settings;
|
||||
|
||||
#[Locked]
|
||||
public Team $team;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $smtpEnabled = false;
|
||||
|
||||
#[Validate(['nullable', 'string'])]
|
||||
public ?string $smtpHost = null;
|
||||
|
||||
#[Validate(['nullable', 'numeric', 'min:1', 'max:65535'])]
|
||||
public ?int $smtpPort = null;
|
||||
|
||||
#[Validate(['nullable', 'string'])]
|
||||
public ?string $smtpEncryption = null;
|
||||
|
||||
#[Validate(['nullable', 'string'])]
|
||||
public ?string $smtpUsername = null;
|
||||
|
||||
#[Validate(['nullable'])]
|
||||
public ?string $smtpPassword = null;
|
||||
|
||||
#[Validate(['nullable', 'numeric'])]
|
||||
public ?int $smtpTimeout = null;
|
||||
|
||||
#[Validate(['nullable', 'email'])]
|
||||
public ?string $smtpFromAddress = null;
|
||||
|
||||
#[Validate(['nullable', 'string'])]
|
||||
public ?string $smtpFromName = null;
|
||||
|
||||
#[Validate(['nullable', 'string'])]
|
||||
public ?string $smtpRecipients = null;
|
||||
|
||||
#[Validate(['nullable', 'string'])]
|
||||
public ?string $smtpHost = null;
|
||||
|
||||
#[Validate(['nullable', 'numeric', 'min:1', 'max:65535'])]
|
||||
public ?int $smtpPort = null;
|
||||
|
||||
#[Validate(['nullable', 'string', 'in:tls,ssl,none'])]
|
||||
public ?string $smtpEncryption = 'tls';
|
||||
|
||||
#[Validate(['nullable', 'string'])]
|
||||
public ?string $smtpUsername = null;
|
||||
|
||||
#[Validate(['nullable', 'string'])]
|
||||
public ?string $smtpPassword = null;
|
||||
|
||||
#[Validate(['nullable', 'numeric'])]
|
||||
public ?int $smtpTimeout = null;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $resendEnabled = false;
|
||||
|
||||
#[Validate(['nullable', 'string'])]
|
||||
public ?string $resendApiKey = null;
|
||||
|
||||
#[Validate(['nullable', 'email'])]
|
||||
public ?string $testEmailAddress = null;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
if (isInstanceAdmin() === false) {
|
||||
@@ -50,6 +63,8 @@ class SettingsEmail extends Component
|
||||
}
|
||||
$this->settings = instanceSettings();
|
||||
$this->syncData();
|
||||
$this->team = auth()->user()->currentTeam();
|
||||
$this->testEmailAddress = auth()->user()->email;
|
||||
}
|
||||
|
||||
public function syncData(bool $toModel = false)
|
||||
@@ -90,7 +105,7 @@ class SettingsEmail extends Component
|
||||
try {
|
||||
$this->resetErrorBag();
|
||||
$this->syncData(true);
|
||||
$this->dispatch('success', 'Settings saved.');
|
||||
$this->dispatch('success', 'Transactional email settings updated.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
@@ -99,19 +114,129 @@ class SettingsEmail extends Component
|
||||
public function instantSave(string $type)
|
||||
{
|
||||
try {
|
||||
$this->resetErrorBag();
|
||||
|
||||
if ($type === 'SMTP') {
|
||||
$this->resendEnabled = false;
|
||||
} else {
|
||||
$this->smtpEnabled = false;
|
||||
}
|
||||
$this->syncData(true);
|
||||
if ($this->smtpEnabled || $this->resendEnabled) {
|
||||
$this->dispatch('success', "{$type} enabled.");
|
||||
} else {
|
||||
$this->dispatch('success', "{$type} disabled.");
|
||||
$this->submitSmtp();
|
||||
} elseif ($type === 'Resend') {
|
||||
$this->submitResend();
|
||||
}
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
if ($type === 'SMTP') {
|
||||
$this->smtpEnabled = false;
|
||||
} elseif ($type === 'Resend') {
|
||||
$this->resendEnabled = false;
|
||||
}
|
||||
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function submitSmtp()
|
||||
{
|
||||
try {
|
||||
$this->validate([
|
||||
'smtpEnabled' => 'boolean',
|
||||
'smtpFromAddress' => 'required|email',
|
||||
'smtpFromName' => 'required|string',
|
||||
'smtpHost' => 'required|string',
|
||||
'smtpPort' => 'required|numeric',
|
||||
'smtpEncryption' => 'required|string|in:tls,ssl,none',
|
||||
'smtpUsername' => 'nullable|string',
|
||||
'smtpPassword' => 'nullable|string',
|
||||
'smtpTimeout' => 'nullable|numeric',
|
||||
], [
|
||||
'smtpFromAddress.required' => 'From Address is required.',
|
||||
'smtpFromAddress.email' => 'Please enter a valid email address.',
|
||||
'smtpFromName.required' => 'From Name is required.',
|
||||
'smtpHost.required' => 'SMTP Host is required.',
|
||||
'smtpPort.required' => 'SMTP Port is required.',
|
||||
'smtpPort.numeric' => 'SMTP Port must be a number.',
|
||||
'smtpEncryption.required' => 'Encryption type is required.',
|
||||
]);
|
||||
|
||||
$this->resendEnabled = false;
|
||||
$this->settings->resend_enabled = false;
|
||||
|
||||
$this->settings->smtp_enabled = $this->smtpEnabled;
|
||||
$this->settings->smtp_host = $this->smtpHost;
|
||||
$this->settings->smtp_port = $this->smtpPort;
|
||||
$this->settings->smtp_encryption = $this->smtpEncryption;
|
||||
$this->settings->smtp_username = $this->smtpUsername;
|
||||
$this->settings->smtp_password = $this->smtpPassword;
|
||||
$this->settings->smtp_timeout = $this->smtpTimeout;
|
||||
$this->settings->smtp_from_address = $this->smtpFromAddress;
|
||||
$this->settings->smtp_from_name = $this->smtpFromName;
|
||||
|
||||
$this->settings->save();
|
||||
|
||||
$this->dispatch('success', 'SMTP settings updated.');
|
||||
} catch (\Throwable $e) {
|
||||
$this->smtpEnabled = false;
|
||||
|
||||
return handleError($e);
|
||||
}
|
||||
}
|
||||
|
||||
public function submitResend()
|
||||
{
|
||||
try {
|
||||
$this->validate([
|
||||
'resendEnabled' => 'boolean',
|
||||
'resendApiKey' => 'required|string',
|
||||
'smtpFromAddress' => 'required|email',
|
||||
'smtpFromName' => 'required|string',
|
||||
], [
|
||||
'resendApiKey.required' => 'Resend API Key is required.',
|
||||
'smtpFromAddress.required' => 'From Address is required.',
|
||||
'smtpFromAddress.email' => 'Please enter a valid email address.',
|
||||
'smtpFromName.required' => 'From Name is required.',
|
||||
]);
|
||||
|
||||
$this->smtpEnabled = false;
|
||||
$this->settings->smtp_enabled = false;
|
||||
|
||||
$this->settings->resend_enabled = $this->resendEnabled;
|
||||
$this->settings->resend_api_key = $this->resendApiKey;
|
||||
$this->settings->smtp_from_address = $this->smtpFromAddress;
|
||||
$this->settings->smtp_from_name = $this->smtpFromName;
|
||||
|
||||
$this->settings->save();
|
||||
|
||||
$this->dispatch('success', 'Resend settings updated.');
|
||||
} catch (\Throwable $e) {
|
||||
$this->resendEnabled = false;
|
||||
|
||||
return handleError($e);
|
||||
}
|
||||
}
|
||||
|
||||
public function sendTestEmail()
|
||||
{
|
||||
try {
|
||||
$this->validate([
|
||||
'testEmailAddress' => 'required|email',
|
||||
], [
|
||||
'testEmailAddress.required' => 'Test email address is required.',
|
||||
'testEmailAddress.email' => 'Please enter a valid email address.',
|
||||
]);
|
||||
|
||||
$executed = RateLimiter::attempt(
|
||||
'test-email:'.$this->team->id,
|
||||
$perMinute = 0,
|
||||
function () {
|
||||
$this->team?->notify(new Test($this->testEmailAddress, 'email'));
|
||||
$this->dispatch('success', 'Test Email sent.');
|
||||
},
|
||||
$decaySeconds = 10,
|
||||
);
|
||||
|
||||
if (! $executed) {
|
||||
throw new \Exception('Too many messages sent!');
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ class SettingsOauth extends Component
|
||||
$carry["oauth_settings_map.$setting->provider.client_secret"] = 'nullable';
|
||||
$carry["oauth_settings_map.$setting->provider.redirect_uri"] = 'nullable';
|
||||
$carry["oauth_settings_map.$setting->provider.tenant"] = 'nullable';
|
||||
$carry["oauth_settings_map.$setting->provider.base_url"] = 'nullable';
|
||||
|
||||
return $carry;
|
||||
}, []);
|
||||
|
||||
@@ -4,6 +4,11 @@ namespace App\Livewire\Source\Github;
|
||||
|
||||
use App\Jobs\GithubAppPermissionJob;
|
||||
use App\Models\GithubApp;
|
||||
use App\Models\PrivateKey;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Lcobucci\JWT\Configuration;
|
||||
use Lcobucci\JWT\Signer\Key\InMemory;
|
||||
use Lcobucci\JWT\Signer\Rsa\Sha256;
|
||||
use Livewire\Component;
|
||||
|
||||
class Change extends Component
|
||||
@@ -51,12 +56,20 @@ class Change extends Component
|
||||
'github_app.administration' => 'nullable|string',
|
||||
];
|
||||
|
||||
public function boot()
|
||||
{
|
||||
if ($this->github_app) {
|
||||
$this->github_app->makeVisible(['client_secret', 'webhook_secret']);
|
||||
}
|
||||
}
|
||||
|
||||
public function checkPermissions()
|
||||
{
|
||||
GithubAppPermissionJob::dispatchSync($this->github_app);
|
||||
$this->github_app->refresh()->makeVisible('client_secret')->makeVisible('webhook_secret');
|
||||
$this->dispatch('success', 'Github App permissions updated.');
|
||||
}
|
||||
|
||||
// public function check()
|
||||
// {
|
||||
|
||||
@@ -90,15 +103,16 @@ class Change extends Component
|
||||
|
||||
// ray($runners_by_repository);
|
||||
// }
|
||||
|
||||
public function mount()
|
||||
{
|
||||
try {
|
||||
$github_app_uuid = request()->github_app_uuid;
|
||||
$this->github_app = GithubApp::ownedByCurrentTeam()->whereUuid($github_app_uuid)->firstOrFail();
|
||||
$this->github_app->makeVisible(['client_secret', 'webhook_secret']);
|
||||
|
||||
$this->applications = $this->github_app->applications;
|
||||
$settings = instanceSettings();
|
||||
$this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret');
|
||||
|
||||
$this->name = str($this->github_app->name)->kebab();
|
||||
$this->fqdn = $settings->fqdn;
|
||||
@@ -142,6 +156,77 @@ class Change extends Component
|
||||
}
|
||||
}
|
||||
|
||||
public function getGithubAppNameUpdatePath()
|
||||
{
|
||||
if (str($this->github_app->organization)->isNotEmpty()) {
|
||||
return "{$this->github_app->html_url}/organizations/{$this->github_app->organization}/settings/apps/{$this->github_app->name}";
|
||||
}
|
||||
|
||||
return "{$this->github_app->html_url}/settings/apps/{$this->github_app->name}";
|
||||
}
|
||||
|
||||
private function generateGithubJwt($private_key, $app_id): string
|
||||
{
|
||||
$configuration = Configuration::forAsymmetricSigner(
|
||||
new Sha256,
|
||||
InMemory::plainText($private_key),
|
||||
InMemory::plainText($private_key)
|
||||
);
|
||||
|
||||
$now = time();
|
||||
|
||||
return $configuration->builder()
|
||||
->issuedBy((string) $app_id)
|
||||
->permittedFor('https://api.github.com')
|
||||
->identifiedBy((string) $now)
|
||||
->issuedAt(new \DateTimeImmutable("@{$now}"))
|
||||
->expiresAt(new \DateTimeImmutable('@'.($now + 600)))
|
||||
->getToken($configuration->signer(), $configuration->signingKey())
|
||||
->toString();
|
||||
}
|
||||
|
||||
public function updateGithubAppName()
|
||||
{
|
||||
try {
|
||||
$privateKey = PrivateKey::ownedByCurrentTeam()->find($this->github_app->private_key_id);
|
||||
|
||||
if (! $privateKey) {
|
||||
$this->dispatch('error', 'No private key found for this GitHub App.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$jwt = $this->generateGithubJwt($privateKey->private_key, $this->github_app->app_id);
|
||||
|
||||
$response = Http::withHeaders([
|
||||
'Accept' => 'application/vnd.github+json',
|
||||
'X-GitHub-Api-Version' => '2022-11-28',
|
||||
'Authorization' => "Bearer {$jwt}",
|
||||
])->get("{$this->github_app->api_url}/app");
|
||||
|
||||
if ($response->successful()) {
|
||||
$app_data = $response->json();
|
||||
$app_slug = $app_data['slug'] ?? null;
|
||||
|
||||
if ($app_slug) {
|
||||
$this->github_app->name = $app_slug;
|
||||
$this->name = str($app_slug)->kebab();
|
||||
$privateKey->name = "github-app-{$app_slug}";
|
||||
$privateKey->save();
|
||||
$this->github_app->save();
|
||||
$this->dispatch('success', 'GitHub App name and SSH key name synchronized successfully.');
|
||||
} else {
|
||||
$this->dispatch('info', 'Could not find App Name (slug) in GitHub response.');
|
||||
}
|
||||
} else {
|
||||
$error_message = $response->json()['message'] ?? 'Unknown error';
|
||||
$this->dispatch('error', "Failed to fetch GitHub App information: {$error_message}");
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Waitlist;
|
||||
|
||||
use App\Jobs\SendConfirmationForWaitlistJob;
|
||||
use App\Models\User;
|
||||
use App\Models\Waitlist;
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\Component;
|
||||
|
||||
class Index extends Component
|
||||
{
|
||||
public string $email;
|
||||
|
||||
public int $users = 0;
|
||||
|
||||
public int $waitingInLine = 0;
|
||||
|
||||
protected $rules = [
|
||||
'email' => 'required|email',
|
||||
];
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.waitlist.index')->layout('layouts.simple');
|
||||
}
|
||||
|
||||
public function mount()
|
||||
{
|
||||
if (config('constants.waitlist.enabled') == false) {
|
||||
return redirect()->route('register');
|
||||
}
|
||||
$this->waitingInLine = Waitlist::whereVerified(true)->count();
|
||||
$this->users = User::count();
|
||||
if (isDev()) {
|
||||
$this->email = 'waitlist@example.com';
|
||||
}
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
$this->validate();
|
||||
try {
|
||||
$already_registered = User::whereEmail($this->email)->first();
|
||||
if ($already_registered) {
|
||||
throw new \Exception('You are already on the waitlist or registered. <br>Please check your email to verify your email address or contact support.');
|
||||
}
|
||||
$found = Waitlist::where('email', $this->email)->first();
|
||||
if ($found) {
|
||||
if (! $found->verified) {
|
||||
$this->dispatch('error', 'You are already on the waitlist. <br>Please check your email to verify your email address.');
|
||||
|
||||
return;
|
||||
}
|
||||
$this->dispatch('error', 'You are already on the waitlist. <br>You will be notified when your turn comes. <br>Thank you.');
|
||||
|
||||
return;
|
||||
}
|
||||
$waitlist = Waitlist::create([
|
||||
'email' => Str::lower($this->email),
|
||||
'type' => 'registration',
|
||||
]);
|
||||
|
||||
$this->dispatch('success', 'Check your email to verify your email address.');
|
||||
dispatch(new SendConfirmationForWaitlistJob($this->email, $waitlist->uuid));
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -115,6 +115,12 @@ class Application extends BaseModel
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
static::addGlobalScope('withRelations', function ($builder) {
|
||||
$builder->withCount([
|
||||
'additional_servers',
|
||||
'additional_networks',
|
||||
]);
|
||||
});
|
||||
static::saving(function ($application) {
|
||||
$payload = [];
|
||||
if ($application->isDirty('fqdn')) {
|
||||
@@ -327,7 +333,7 @@ class Application extends BaseModel
|
||||
return null;
|
||||
}
|
||||
|
||||
public function failedTaskLink($task_uuid)
|
||||
public function taskLink($task_uuid)
|
||||
{
|
||||
if (data_get($this, 'environment.project.uuid')) {
|
||||
$route = route('project.application.scheduled-tasks', [
|
||||
@@ -551,11 +557,13 @@ class Application extends BaseModel
|
||||
{
|
||||
return Attribute::make(
|
||||
get: function () {
|
||||
if ($this->additional_servers->count() === 0) {
|
||||
return $this->destination->server->isFunctional();
|
||||
} else {
|
||||
if (! $this->relationLoaded('additional_servers') || $this->additional_servers->count() === 0) {
|
||||
return $this->destination?->server?->isFunctional() ?? false;
|
||||
}
|
||||
|
||||
$additional_servers_status = $this->additional_servers->pluck('pivot.status');
|
||||
$main_server_status = $this->destination->server->isFunctional();
|
||||
$main_server_status = $this->destination?->server?->isFunctional() ?? false;
|
||||
|
||||
foreach ($additional_servers_status as $status) {
|
||||
$server_status = str($status)->before(':')->value();
|
||||
if ($server_status !== 'running') {
|
||||
@@ -565,7 +573,6 @@ class Application extends BaseModel
|
||||
|
||||
return $main_server_status;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1321,7 +1328,34 @@ class Application extends BaseModel
|
||||
if (! $gitRemoteStatus['is_accessible']) {
|
||||
throw new \RuntimeException("Failed to read Git source:\n\n{$gitRemoteStatus['error']}");
|
||||
}
|
||||
$getGitVersion = instant_remote_process(['git --version'], $this->destination->server, false);
|
||||
$gitVersion = str($getGitVersion)->explode(' ')->last();
|
||||
|
||||
if (version_compare($gitVersion, '2.35.1', '<')) {
|
||||
$fileList = $fileList->map(function ($file) {
|
||||
$parts = explode('/', trim($file, '.'));
|
||||
$paths = collect();
|
||||
$currentPath = '';
|
||||
foreach ($parts as $part) {
|
||||
$currentPath .= ($currentPath ? '/' : '').$part;
|
||||
if (str($currentPath)->isNotEmpty()) {
|
||||
$paths->push($currentPath);
|
||||
}
|
||||
}
|
||||
|
||||
return $paths;
|
||||
})->flatten()->unique()->values();
|
||||
$commands = collect([
|
||||
"rm -rf /tmp/{$uuid}",
|
||||
"mkdir -p /tmp/{$uuid}",
|
||||
"cd /tmp/{$uuid}",
|
||||
$cloneCommand,
|
||||
'git sparse-checkout init',
|
||||
"git sparse-checkout set {$fileList->implode(' ')}",
|
||||
'git read-tree -mu HEAD',
|
||||
"cat .$workdir$composeFile",
|
||||
]);
|
||||
} else {
|
||||
$commands = collect([
|
||||
"rm -rf /tmp/{$uuid}",
|
||||
"mkdir -p /tmp/{$uuid}",
|
||||
@@ -1332,6 +1366,7 @@ class Application extends BaseModel
|
||||
'git read-tree -mu HEAD',
|
||||
"cat .$workdir$composeFile",
|
||||
]);
|
||||
}
|
||||
try {
|
||||
$composeFileContent = instant_remote_process($commands, $this->destination->server);
|
||||
} catch (\Exception $e) {
|
||||
|
||||
@@ -20,7 +20,7 @@ abstract class BaseModel extends Model
|
||||
});
|
||||
}
|
||||
|
||||
public function name(): Attribute
|
||||
public function sanitizedName(): Attribute
|
||||
{
|
||||
return new Attribute(
|
||||
get: fn () => sanitize_string($this->getRawOriginal('name')),
|
||||
|
||||
59
app/Models/DiscordNotificationSettings.php
Normal file
59
app/Models/DiscordNotificationSettings.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
|
||||
class DiscordNotificationSettings extends Model
|
||||
{
|
||||
use Notifiable;
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
protected $fillable = [
|
||||
'team_id',
|
||||
|
||||
'discord_enabled',
|
||||
'discord_webhook_url',
|
||||
|
||||
'deployment_success_discord_notifications',
|
||||
'deployment_failure_discord_notifications',
|
||||
'status_change_discord_notifications',
|
||||
'backup_success_discord_notifications',
|
||||
'backup_failure_discord_notifications',
|
||||
'scheduled_task_success_discord_notifications',
|
||||
'scheduled_task_failure_discord_notifications',
|
||||
'docker_cleanup_discord_notifications',
|
||||
'server_disk_usage_discord_notifications',
|
||||
'server_reachable_discord_notifications',
|
||||
'server_unreachable_discord_notifications',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'discord_enabled' => 'boolean',
|
||||
'discord_webhook_url' => 'encrypted',
|
||||
|
||||
'deployment_success_discord_notifications' => 'boolean',
|
||||
'deployment_failure_discord_notifications' => 'boolean',
|
||||
'status_change_discord_notifications' => 'boolean',
|
||||
'backup_success_discord_notifications' => 'boolean',
|
||||
'backup_failure_discord_notifications' => 'boolean',
|
||||
'scheduled_task_success_discord_notifications' => 'boolean',
|
||||
'scheduled_task_failure_discord_notifications' => 'boolean',
|
||||
'docker_cleanup_discord_notifications' => 'boolean',
|
||||
'server_disk_usage_discord_notifications' => 'boolean',
|
||||
'server_reachable_discord_notifications' => 'boolean',
|
||||
'server_unreachable_discord_notifications' => 'boolean',
|
||||
];
|
||||
|
||||
public function team()
|
||||
{
|
||||
return $this->belongsTo(Team::class);
|
||||
}
|
||||
|
||||
public function isEnabled()
|
||||
{
|
||||
return $this->discord_enabled;
|
||||
}
|
||||
}
|
||||
79
app/Models/EmailNotificationSettings.php
Normal file
79
app/Models/EmailNotificationSettings.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class EmailNotificationSettings extends Model
|
||||
{
|
||||
public $timestamps = false;
|
||||
|
||||
protected $fillable = [
|
||||
'team_id',
|
||||
|
||||
'smtp_enabled',
|
||||
'smtp_from_address',
|
||||
'smtp_from_name',
|
||||
'smtp_recipients',
|
||||
'smtp_host',
|
||||
'smtp_port',
|
||||
'smtp_encryption',
|
||||
'smtp_username',
|
||||
'smtp_password',
|
||||
'smtp_timeout',
|
||||
|
||||
'resend_enabled',
|
||||
'resend_api_key',
|
||||
|
||||
'use_instance_email_settings',
|
||||
|
||||
'deployment_success_email_notifications',
|
||||
'deployment_failure_email_notifications',
|
||||
'status_change_email_notifications',
|
||||
'backup_success_email_notifications',
|
||||
'backup_failure_email_notifications',
|
||||
'scheduled_task_success_email_notifications',
|
||||
'scheduled_task_failure_email_notifications',
|
||||
'server_disk_usage_email_notifications',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'smtp_enabled' => 'boolean',
|
||||
'smtp_from_address' => 'encrypted',
|
||||
'smtp_from_name' => 'encrypted',
|
||||
'smtp_recipients' => 'encrypted',
|
||||
'smtp_host' => 'encrypted',
|
||||
'smtp_port' => 'integer',
|
||||
'smtp_username' => 'encrypted',
|
||||
'smtp_password' => 'encrypted',
|
||||
'smtp_timeout' => 'integer',
|
||||
|
||||
'resend_enabled' => 'boolean',
|
||||
'resend_api_key' => 'encrypted',
|
||||
|
||||
'use_instance_email_settings' => 'boolean',
|
||||
|
||||
'deployment_success_email_notifications' => 'boolean',
|
||||
'deployment_failure_email_notifications' => 'boolean',
|
||||
'status_change_email_notifications' => 'boolean',
|
||||
'backup_success_email_notifications' => 'boolean',
|
||||
'backup_failure_email_notifications' => 'boolean',
|
||||
'scheduled_task_success_email_notifications' => 'boolean',
|
||||
'scheduled_task_failure_email_notifications' => 'boolean',
|
||||
'server_disk_usage_email_notifications' => 'boolean',
|
||||
];
|
||||
|
||||
public function team()
|
||||
{
|
||||
return $this->belongsTo(Team::class);
|
||||
}
|
||||
|
||||
public function isEnabled()
|
||||
{
|
||||
if (isCloud()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->smtp_enabled || $this->resend_enabled || $this->use_instance_email_settings;
|
||||
}
|
||||
}
|
||||
@@ -16,8 +16,19 @@ class InstanceSettings extends Model implements SendsEmail
|
||||
protected $guarded = [];
|
||||
|
||||
protected $casts = [
|
||||
'resale_license' => 'encrypted',
|
||||
'smtp_enabled' => 'boolean',
|
||||
'smtp_from_address' => 'encrypted',
|
||||
'smtp_from_name' => 'encrypted',
|
||||
'smtp_recipients' => 'encrypted',
|
||||
'smtp_host' => 'encrypted',
|
||||
'smtp_port' => 'integer',
|
||||
'smtp_username' => 'encrypted',
|
||||
'smtp_password' => 'encrypted',
|
||||
'smtp_timeout' => 'integer',
|
||||
|
||||
'resend_enabled' => 'boolean',
|
||||
'resend_api_key' => 'encrypted',
|
||||
|
||||
'allowed_ip_ranges' => 'array',
|
||||
'is_auto_update_enabled' => 'boolean',
|
||||
'auto_update_frequency' => 'string',
|
||||
@@ -81,7 +92,7 @@ class InstanceSettings extends Model implements SendsEmail
|
||||
return InstanceSettings::findOrFail(0);
|
||||
}
|
||||
|
||||
public function getRecepients($notification)
|
||||
public function getRecipients($notification)
|
||||
{
|
||||
$recipients = data_get($notification, 'emails', null);
|
||||
if (is_null($recipients) || $recipients === '') {
|
||||
|
||||
61
app/Models/PushoverNotificationSettings.php
Normal file
61
app/Models/PushoverNotificationSettings.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
|
||||
class PushoverNotificationSettings extends Model
|
||||
{
|
||||
use Notifiable;
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
protected $fillable = [
|
||||
'team_id',
|
||||
|
||||
'pushover_enabled',
|
||||
'pushover_user_key',
|
||||
'pushover_api_token',
|
||||
|
||||
'deployment_success_pushover_notifications',
|
||||
'deployment_failure_pushover_notifications',
|
||||
'status_change_pushover_notifications',
|
||||
'backup_success_pushover_notifications',
|
||||
'backup_failure_pushover_notifications',
|
||||
'scheduled_task_success_pushover_notifications',
|
||||
'scheduled_task_failure_pushover_notifications',
|
||||
'docker_cleanup_pushover_notifications',
|
||||
'server_disk_usage_pushover_notifications',
|
||||
'server_reachable_pushover_notifications',
|
||||
'server_unreachable_pushover_notifications',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'pushover_enabled' => 'boolean',
|
||||
'pushover_user_key' => 'encrypted',
|
||||
'pushover_api_token' => 'encrypted',
|
||||
|
||||
'deployment_success_pushover_notifications' => 'boolean',
|
||||
'deployment_failure_pushover_notifications' => 'boolean',
|
||||
'status_change_pushover_notifications' => 'boolean',
|
||||
'backup_success_pushover_notifications' => 'boolean',
|
||||
'backup_failure_pushover_notifications' => 'boolean',
|
||||
'scheduled_task_success_pushover_notifications' => 'boolean',
|
||||
'scheduled_task_failure_pushover_notifications' => 'boolean',
|
||||
'docker_cleanup_pushover_notifications' => 'boolean',
|
||||
'server_disk_usage_pushover_notifications' => 'boolean',
|
||||
'server_reachable_pushover_notifications' => 'boolean',
|
||||
'server_unreachable_pushover_notifications' => 'boolean',
|
||||
];
|
||||
|
||||
public function team()
|
||||
{
|
||||
return $this->belongsTo(Team::class);
|
||||
}
|
||||
|
||||
public function isEnabled()
|
||||
{
|
||||
return $this->pushover_enabled;
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,7 @@ class S3Storage extends BaseModel
|
||||
$this->is_usable = true;
|
||||
} catch (\Throwable $e) {
|
||||
$this->is_usable = false;
|
||||
if ($this->unusable_email_sent === false && is_transactional_emails_active()) {
|
||||
if ($this->unusable_email_sent === false && is_transactional_emails_enabled()) {
|
||||
$mail = new MailMessage;
|
||||
$mail->subject('Coolify: S3 Storage Connection Error');
|
||||
$mail->view('emails.s3-connection-error', ['name' => $this->name, 'reason' => $e->getMessage(), 'url' => route('storage.show', ['storage_uuid' => $this->uuid])]);
|
||||
|
||||
@@ -105,6 +105,14 @@ class Server extends BaseModel
|
||||
]);
|
||||
}
|
||||
}
|
||||
if (! isset($server->proxy->redirect_enabled)) {
|
||||
$server->proxy->redirect_enabled = true;
|
||||
}
|
||||
});
|
||||
static::retrieved(function ($server) {
|
||||
if (! isset($server->proxy->redirect_enabled)) {
|
||||
$server->proxy->redirect_enabled = true;
|
||||
}
|
||||
});
|
||||
|
||||
static::forceDeleting(function ($server) {
|
||||
@@ -184,42 +192,46 @@ class Server extends BaseModel
|
||||
return $this->proxyType() && $this->proxyType() !== 'NONE' && $this->isFunctional() && ! $this->isSwarmWorker() && ! $this->settings->is_build_server;
|
||||
}
|
||||
|
||||
public function setupDefault404Redirect()
|
||||
public function setupDefaultRedirect()
|
||||
{
|
||||
$banner =
|
||||
"# This file is generated by Coolify, do not edit it manually.\n".
|
||||
"# Disable the default redirect to customize (only if you know what are you doing).\n\n";
|
||||
$dynamic_conf_path = $this->proxyPath().'/dynamic';
|
||||
$proxy_type = $this->proxyType();
|
||||
$redirect_enabled = $this->proxy->redirect_enabled ?? true;
|
||||
$redirect_url = $this->proxy->redirect_url;
|
||||
if ($proxy_type === ProxyTypes::TRAEFIK->value) {
|
||||
$default_redirect_file = "$dynamic_conf_path/default_redirect_404.yaml";
|
||||
} elseif ($proxy_type === ProxyTypes::CADDY->value) {
|
||||
$default_redirect_file = "$dynamic_conf_path/default_redirect_404.caddy";
|
||||
}
|
||||
if (empty($redirect_url)) {
|
||||
if (isDev()) {
|
||||
if ($proxy_type === ProxyTypes::CADDY->value) {
|
||||
$conf = ':80, :443 {
|
||||
respond 404
|
||||
}';
|
||||
$conf =
|
||||
"# This file is automatically generated by Coolify.\n".
|
||||
"# Do not edit it manually (only if you know what are you doing).\n\n".
|
||||
$conf;
|
||||
$base64 = base64_encode($conf);
|
||||
instant_remote_process([
|
||||
"mkdir -p $dynamic_conf_path",
|
||||
"echo '$base64' | base64 -d | tee $default_redirect_file > /dev/null",
|
||||
], $this);
|
||||
$this->reloadCaddy();
|
||||
|
||||
return;
|
||||
$dynamic_conf_path = '/data/coolify/proxy/caddy/dynamic';
|
||||
}
|
||||
instant_remote_process([
|
||||
"mkdir -p $dynamic_conf_path",
|
||||
"rm -f $default_redirect_file",
|
||||
], $this);
|
||||
|
||||
return;
|
||||
}
|
||||
if ($proxy_type === ProxyTypes::TRAEFIK->value) {
|
||||
$default_redirect_file = "$dynamic_conf_path/default_redirect_503.yaml";
|
||||
} elseif ($proxy_type === ProxyTypes::CADDY->value) {
|
||||
$default_redirect_file = "$dynamic_conf_path/default_redirect_503.caddy";
|
||||
}
|
||||
|
||||
instant_remote_process([
|
||||
"mkdir -p $dynamic_conf_path",
|
||||
"rm -f $dynamic_conf_path/default_redirect_404.yaml",
|
||||
"rm -f $dynamic_conf_path/default_redirect_404.caddy",
|
||||
], $this);
|
||||
|
||||
if ($redirect_enabled === false) {
|
||||
instant_remote_process(["rm -f $default_redirect_file"], $this);
|
||||
} else {
|
||||
if ($proxy_type === ProxyTypes::CADDY->value) {
|
||||
if (filled($redirect_url)) {
|
||||
$conf = ":80, :443 {
|
||||
redir $redirect_url
|
||||
}";
|
||||
} else {
|
||||
$conf = ':80, :443 {
|
||||
respond 503
|
||||
}';
|
||||
}
|
||||
} elseif ($proxy_type === ProxyTypes::TRAEFIK->value) {
|
||||
$dynamic_conf = [
|
||||
'http' => [
|
||||
'routers' => [
|
||||
@@ -229,28 +241,31 @@ respond 404
|
||||
1 => 'https',
|
||||
],
|
||||
'service' => 'noop',
|
||||
'rule' => 'HostRegexp(`.+`)',
|
||||
'rule' => 'PathPrefix(`/`)',
|
||||
'tls' => [
|
||||
'certResolver' => 'letsencrypt',
|
||||
],
|
||||
'priority' => 1,
|
||||
'middlewares' => [
|
||||
0 => 'redirect-regexp',
|
||||
],
|
||||
'priority' => -1000,
|
||||
],
|
||||
],
|
||||
'services' => [
|
||||
'noop' => [
|
||||
'loadBalancer' => [
|
||||
'servers' => [
|
||||
0 => [
|
||||
'servers' => [],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
if (filled($redirect_url)) {
|
||||
$dynamic_conf['http']['routers']['catchall']['middlewares'] = [
|
||||
0 => 'redirect-regexp',
|
||||
];
|
||||
|
||||
$dynamic_conf['http']['services']['noop']['loadBalancer']['servers'][0] = [
|
||||
'url' => '',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'middlewares' => [
|
||||
];
|
||||
$dynamic_conf['http']['middlewares'] = [
|
||||
'redirect-regexp' => [
|
||||
'redirectRegex' => [
|
||||
'regex' => '(.*)',
|
||||
@@ -258,31 +273,16 @@ respond 404
|
||||
'permanent' => false,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
$conf = Yaml::dump($dynamic_conf, 12, 2);
|
||||
$conf =
|
||||
"# This file is automatically generated by Coolify.\n".
|
||||
"# Do not edit it manually (only if you know what are you doing).\n\n".
|
||||
$conf;
|
||||
|
||||
$base64 = base64_encode($conf);
|
||||
} elseif ($proxy_type === ProxyTypes::CADDY->value) {
|
||||
$conf = ":80, :443 {
|
||||
redir $redirect_url
|
||||
}";
|
||||
$conf =
|
||||
"# This file is automatically generated by Coolify.\n".
|
||||
"# Do not edit it manually (only if you know what are you doing).\n\n".
|
||||
$conf;
|
||||
$base64 = base64_encode($conf);
|
||||
}
|
||||
|
||||
$conf = Yaml::dump($dynamic_conf, 12, 2);
|
||||
}
|
||||
$conf = $banner.$conf;
|
||||
$base64 = base64_encode($conf);
|
||||
instant_remote_process([
|
||||
"mkdir -p $dynamic_conf_path",
|
||||
"echo '$base64' | base64 -d | tee $default_redirect_file > /dev/null",
|
||||
], $this);
|
||||
}
|
||||
|
||||
if ($proxy_type === 'CADDY') {
|
||||
$this->reloadCaddy();
|
||||
@@ -611,7 +611,9 @@ $schema://$host {
|
||||
}
|
||||
$memory = json_decode($memory, true);
|
||||
$parsedCollection = collect($memory)->map(function ($metric) {
|
||||
return [(int) $metric['time'], (float) $metric['usedPercent']];
|
||||
$usedPercent = $metric['usedPercent'] ?? 0.0;
|
||||
|
||||
return [(int) $metric['time'], (float) $usedPercent];
|
||||
});
|
||||
|
||||
return $parsedCollection->toArray();
|
||||
@@ -1040,7 +1042,7 @@ $schema://$host {
|
||||
$this->unreachable_notification_sent = false;
|
||||
$this->save();
|
||||
$this->refresh();
|
||||
// $this->team->notify(new Reachable($this));
|
||||
$this->team->notify(new Reachable($this));
|
||||
}
|
||||
|
||||
public function sendUnreachableNotification()
|
||||
@@ -1048,7 +1050,7 @@ $schema://$host {
|
||||
$this->unreachable_notification_sent = true;
|
||||
$this->save();
|
||||
$this->refresh();
|
||||
// $this->team->notify(new Unreachable($this));
|
||||
$this->team->notify(new Unreachable($this));
|
||||
}
|
||||
|
||||
public function validateConnection(bool $justCheckingNewKey = false)
|
||||
|
||||
@@ -1140,7 +1140,7 @@ class Service extends BaseModel
|
||||
return null;
|
||||
}
|
||||
|
||||
public function failedTaskLink($task_uuid)
|
||||
public function taskLink($task_uuid)
|
||||
{
|
||||
if (data_get($this, 'environment.project.uuid')) {
|
||||
$route = route('project.service.scheduled-tasks', [
|
||||
|
||||
59
app/Models/SlackNotificationSettings.php
Normal file
59
app/Models/SlackNotificationSettings.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
|
||||
class SlackNotificationSettings extends Model
|
||||
{
|
||||
use Notifiable;
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
protected $fillable = [
|
||||
'team_id',
|
||||
|
||||
'slack_enabled',
|
||||
'slack_webhook_url',
|
||||
|
||||
'deployment_success_slack_notifications',
|
||||
'deployment_failure_slack_notifications',
|
||||
'status_change_slack_notifications',
|
||||
'backup_success_slack_notifications',
|
||||
'backup_failure_slack_notifications',
|
||||
'scheduled_task_success_slack_notifications',
|
||||
'scheduled_task_failure_slack_notifications',
|
||||
'docker_cleanup_slack_notifications',
|
||||
'server_disk_usage_slack_notifications',
|
||||
'server_reachable_slack_notifications',
|
||||
'server_unreachable_slack_notifications',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'slack_enabled' => 'boolean',
|
||||
'slack_webhook_url' => 'encrypted',
|
||||
|
||||
'deployment_success_slack_notifications' => 'boolean',
|
||||
'deployment_failure_slack_notifications' => 'boolean',
|
||||
'status_change_slack_notifications' => 'boolean',
|
||||
'backup_success_slack_notifications' => 'boolean',
|
||||
'backup_failure_slack_notifications' => 'boolean',
|
||||
'scheduled_task_success_slack_notifications' => 'boolean',
|
||||
'scheduled_task_failure_slack_notifications' => 'boolean',
|
||||
'docker_cleanup_slack_notifications' => 'boolean',
|
||||
'server_disk_usage_slack_notifications' => 'boolean',
|
||||
'server_reachable_slack_notifications' => 'boolean',
|
||||
'server_unreachable_slack_notifications' => 'boolean',
|
||||
];
|
||||
|
||||
public function team()
|
||||
{
|
||||
return $this->belongsTo(Team::class);
|
||||
}
|
||||
|
||||
public function isEnabled()
|
||||
{
|
||||
return $this->slack_enabled;
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,9 @@ namespace App\Models;
|
||||
|
||||
use App\Notifications\Channels\SendsDiscord;
|
||||
use App\Notifications\Channels\SendsEmail;
|
||||
use App\Notifications\Channels\SendsPushover;
|
||||
use App\Notifications\Channels\SendsSlack;
|
||||
use App\Traits\HasNotificationSettings;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
@@ -19,49 +22,8 @@ use OpenApi\Attributes as OA;
|
||||
'personal_team' => ['type' => 'boolean', 'description' => 'Whether the team is personal or not.'],
|
||||
'created_at' => ['type' => 'string', 'description' => 'The date and time the team was created.'],
|
||||
'updated_at' => ['type' => 'string', 'description' => 'The date and time the team was last updated.'],
|
||||
'smtp_enabled' => ['type' => 'boolean', 'description' => 'Whether SMTP is enabled or not.'],
|
||||
'smtp_from_address' => ['type' => 'string', 'description' => 'The email address to send emails from.'],
|
||||
'smtp_from_name' => ['type' => 'string', 'description' => 'The name to send emails from.'],
|
||||
'smtp_recipients' => ['type' => 'string', 'description' => 'The email addresses to send emails to.'],
|
||||
'smtp_host' => ['type' => 'string', 'description' => 'The SMTP host.'],
|
||||
'smtp_port' => ['type' => 'string', 'description' => 'The SMTP port.'],
|
||||
'smtp_encryption' => ['type' => 'string', 'description' => 'The SMTP encryption.'],
|
||||
'smtp_username' => ['type' => 'string', 'description' => 'The SMTP username.'],
|
||||
'smtp_password' => ['type' => 'string', 'description' => 'The SMTP password.'],
|
||||
'smtp_timeout' => ['type' => 'string', 'description' => 'The SMTP timeout.'],
|
||||
'smtp_notifications_test' => ['type' => 'boolean', 'description' => 'Whether to send test notifications via SMTP.'],
|
||||
'smtp_notifications_deployments' => ['type' => 'boolean', 'description' => 'Whether to send deployment notifications via SMTP.'],
|
||||
'smtp_notifications_status_changes' => ['type' => 'boolean', 'description' => 'Whether to send status change notifications via SMTP.'],
|
||||
'smtp_notifications_scheduled_tasks' => ['type' => 'boolean', 'description' => 'Whether to send scheduled task notifications via SMTP.'],
|
||||
'smtp_notifications_database_backups' => ['type' => 'boolean', 'description' => 'Whether to send database backup notifications via SMTP.'],
|
||||
'smtp_notifications_server_disk_usage' => ['type' => 'boolean', 'description' => 'Whether to send server disk usage notifications via SMTP.'],
|
||||
'discord_enabled' => ['type' => 'boolean', 'description' => 'Whether Discord is enabled or not.'],
|
||||
'discord_webhook_url' => ['type' => 'string', 'description' => 'The Discord webhook URL.'],
|
||||
'discord_notifications_test' => ['type' => 'boolean', 'description' => 'Whether to send test notifications via Discord.'],
|
||||
'discord_notifications_deployments' => ['type' => 'boolean', 'description' => 'Whether to send deployment notifications via Discord.'],
|
||||
'discord_notifications_status_changes' => ['type' => 'boolean', 'description' => 'Whether to send status change notifications via Discord.'],
|
||||
'discord_notifications_database_backups' => ['type' => 'boolean', 'description' => 'Whether to send database backup notifications via Discord.'],
|
||||
'discord_notifications_scheduled_tasks' => ['type' => 'boolean', 'description' => 'Whether to send scheduled task notifications via Discord.'],
|
||||
'discord_notifications_server_disk_usage' => ['type' => 'boolean', 'description' => 'Whether to send server disk usage notifications via Discord.'],
|
||||
'show_boarding' => ['type' => 'boolean', 'description' => 'Whether to show the boarding screen or not.'],
|
||||
'resend_enabled' => ['type' => 'boolean', 'description' => 'Whether to enable resending or not.'],
|
||||
'resend_api_key' => ['type' => 'string', 'description' => 'The resending API key.'],
|
||||
'use_instance_email_settings' => ['type' => 'boolean', 'description' => 'Whether to use instance email settings or not.'],
|
||||
'telegram_enabled' => ['type' => 'boolean', 'description' => 'Whether Telegram is enabled or not.'],
|
||||
'telegram_token' => ['type' => 'string', 'description' => 'The Telegram token.'],
|
||||
'telegram_chat_id' => ['type' => 'string', 'description' => 'The Telegram chat ID.'],
|
||||
'telegram_notifications_test' => ['type' => 'boolean', 'description' => 'Whether to send test notifications via Telegram.'],
|
||||
'telegram_notifications_deployments' => ['type' => 'boolean', 'description' => 'Whether to send deployment notifications via Telegram.'],
|
||||
'telegram_notifications_status_changes' => ['type' => 'boolean', 'description' => 'Whether to send status change notifications via Telegram.'],
|
||||
'telegram_notifications_database_backups' => ['type' => 'boolean', 'description' => 'Whether to send database backup notifications via Telegram.'],
|
||||
'telegram_notifications_test_message_thread_id' => ['type' => 'string', 'description' => 'The Telegram test message thread ID.'],
|
||||
'telegram_notifications_deployments_message_thread_id' => ['type' => 'string', 'description' => 'The Telegram deployment message thread ID.'],
|
||||
'telegram_notifications_status_changes_message_thread_id' => ['type' => 'string', 'description' => 'The Telegram status change message thread ID.'],
|
||||
'telegram_notifications_database_backups_message_thread_id' => ['type' => 'string', 'description' => 'The Telegram database backup message thread ID.'],
|
||||
|
||||
'custom_server_limit' => ['type' => 'string', 'description' => 'The custom server limit.'],
|
||||
'telegram_notifications_scheduled_tasks' => ['type' => 'boolean', 'description' => 'Whether to send scheduled task notifications via Telegram.'],
|
||||
'telegram_notifications_scheduled_tasks_thread_id' => ['type' => 'string', 'description' => 'The Telegram scheduled task message thread ID.'],
|
||||
'members' => new OA\Property(
|
||||
property: 'members',
|
||||
type: 'array',
|
||||
@@ -70,20 +32,27 @@ use OpenApi\Attributes as OA;
|
||||
),
|
||||
]
|
||||
)]
|
||||
class Team extends Model implements SendsDiscord, SendsEmail
|
||||
|
||||
class Team extends Model implements SendsDiscord, SendsEmail, SendsPushover, SendsSlack
|
||||
{
|
||||
use Notifiable;
|
||||
use HasNotificationSettings, Notifiable;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
protected $casts = [
|
||||
'personal_team' => 'boolean',
|
||||
'smtp_password' => 'encrypted',
|
||||
'resend_api_key' => 'encrypted',
|
||||
];
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
static::created(function ($team) {
|
||||
$team->emailNotificationSettings()->create();
|
||||
$team->discordNotificationSettings()->create();
|
||||
$team->slackNotificationSettings()->create();
|
||||
$team->telegramNotificationSettings()->create();
|
||||
$team->pushoverNotificationSettings()->create();
|
||||
});
|
||||
|
||||
static::saving(function ($team) {
|
||||
if (auth()->user()?->isMember()) {
|
||||
throw new \Exception('You are not allowed to update this team.');
|
||||
@@ -114,29 +83,6 @@ class Team extends Model implements SendsDiscord, SendsEmail
|
||||
});
|
||||
}
|
||||
|
||||
public function routeNotificationForDiscord()
|
||||
{
|
||||
return data_get($this, 'discord_webhook_url', null);
|
||||
}
|
||||
|
||||
public function routeNotificationForTelegram()
|
||||
{
|
||||
return [
|
||||
'token' => data_get($this, 'telegram_token', null),
|
||||
'chat_id' => data_get($this, 'telegram_chat_id', null),
|
||||
];
|
||||
}
|
||||
|
||||
public function getRecepients($notification)
|
||||
{
|
||||
$recipients = data_get($notification, 'emails', null);
|
||||
if (is_null($recipients)) {
|
||||
return $this->members()->pluck('email')->toArray();
|
||||
}
|
||||
|
||||
return explode(',', $recipients);
|
||||
}
|
||||
|
||||
public static function serverLimitReached()
|
||||
{
|
||||
$serverLimit = Team::serverLimit();
|
||||
@@ -190,10 +136,75 @@ class Team extends Model implements SendsDiscord, SendsEmail
|
||||
|
||||
return $serverLimit ?? 2;
|
||||
}
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
public function routeNotificationForDiscord()
|
||||
{
|
||||
return data_get($this, 'discord_webhook_url', null);
|
||||
}
|
||||
|
||||
public function routeNotificationForTelegram()
|
||||
{
|
||||
return [
|
||||
'token' => data_get($this, 'telegram_token', null),
|
||||
'chat_id' => data_get($this, 'telegram_chat_id', null),
|
||||
];
|
||||
}
|
||||
|
||||
public function routeNotificationForSlack()
|
||||
{
|
||||
return data_get($this, 'slack_webhook_url', null);
|
||||
}
|
||||
|
||||
public function routeNotificationForPushover()
|
||||
{
|
||||
return [
|
||||
'user' => data_get($this, 'pushover_user_key', null),
|
||||
'token' => data_get($this, 'pushover_api_token', null),
|
||||
];
|
||||
}
|
||||
|
||||
public function getRecipients($notification)
|
||||
{
|
||||
$recipients = data_get($notification, 'emails', null);
|
||||
if (is_null($recipients)) {
|
||||
return $this->members()->pluck('email')->toArray();
|
||||
}
|
||||
|
||||
return explode(',', $recipients);
|
||||
}
|
||||
|
||||
public function isAnyNotificationEnabled()
|
||||
{
|
||||
if (isCloud()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->getNotificationSettings('email')?->isEnabled() ||
|
||||
$this->getNotificationSettings('discord')?->isEnabled() ||
|
||||
$this->getNotificationSettings('slack')?->isEnabled() ||
|
||||
$this->getNotificationSettings('telegram')?->isEnabled() ||
|
||||
$this->getNotificationSettings('pushover')?->isEnabled();
|
||||
}
|
||||
|
||||
public function subscriptionEnded()
|
||||
{
|
||||
$this->subscription->update([
|
||||
'stripe_subscription_id' => null,
|
||||
'stripe_plan_id' => null,
|
||||
'stripe_cancel_at_period_end' => false,
|
||||
'stripe_invoice_paid' => false,
|
||||
'stripe_trial_already_ended' => false,
|
||||
]);
|
||||
foreach ($this->servers as $server) {
|
||||
$server->settings()->update([
|
||||
'is_usable' => false,
|
||||
'is_reachable' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function environment_variables()
|
||||
{
|
||||
return $this->hasMany(SharedEnvironmentVariable::class)->whereNull('project_id')->whereNull('environment_id');
|
||||
@@ -257,32 +268,28 @@ class Team extends Model implements SendsDiscord, SendsEmail
|
||||
return $this->hasMany(S3Storage::class)->where('is_usable', true);
|
||||
}
|
||||
|
||||
public function subscriptionEnded()
|
||||
public function emailNotificationSettings()
|
||||
{
|
||||
$this->subscription->update([
|
||||
'stripe_subscription_id' => null,
|
||||
'stripe_plan_id' => null,
|
||||
'stripe_cancel_at_period_end' => false,
|
||||
'stripe_invoice_paid' => false,
|
||||
'stripe_trial_already_ended' => false,
|
||||
]);
|
||||
foreach ($this->servers as $server) {
|
||||
$server->settings()->update([
|
||||
'is_usable' => false,
|
||||
'is_reachable' => false,
|
||||
]);
|
||||
}
|
||||
return $this->hasOne(EmailNotificationSettings::class);
|
||||
}
|
||||
|
||||
public function isAnyNotificationEnabled()
|
||||
public function discordNotificationSettings()
|
||||
{
|
||||
if (isCloud()) {
|
||||
return true;
|
||||
}
|
||||
if ($this->smtp_enabled || $this->resend_enabled || $this->discord_enabled || $this->telegram_enabled || $this->use_instance_email_settings) {
|
||||
return true;
|
||||
return $this->hasOne(DiscordNotificationSettings::class);
|
||||
}
|
||||
|
||||
return false;
|
||||
public function telegramNotificationSettings()
|
||||
{
|
||||
return $this->hasOne(TelegramNotificationSettings::class);
|
||||
}
|
||||
|
||||
public function slackNotificationSettings()
|
||||
{
|
||||
return $this->hasOne(SlackNotificationSettings::class);
|
||||
}
|
||||
|
||||
public function pushoverNotificationSettings()
|
||||
{
|
||||
return $this->hasOne(PushoverNotificationSettings::class);
|
||||
}
|
||||
}
|
||||
|
||||
85
app/Models/TelegramNotificationSettings.php
Normal file
85
app/Models/TelegramNotificationSettings.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
|
||||
class TelegramNotificationSettings extends Model
|
||||
{
|
||||
use Notifiable;
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
protected $fillable = [
|
||||
'team_id',
|
||||
|
||||
'telegram_enabled',
|
||||
'telegram_token',
|
||||
'telegram_chat_id',
|
||||
|
||||
'deployment_success_telegram_notifications',
|
||||
'deployment_failure_telegram_notifications',
|
||||
'status_change_telegram_notifications',
|
||||
'backup_success_telegram_notifications',
|
||||
'backup_failure_telegram_notifications',
|
||||
'scheduled_task_success_telegram_notifications',
|
||||
'scheduled_task_failure_telegram_notifications',
|
||||
'docker_cleanup_telegram_notifications',
|
||||
'server_disk_usage_telegram_notifications',
|
||||
'server_reachable_telegram_notifications',
|
||||
'server_unreachable_telegram_notifications',
|
||||
|
||||
'telegram_notifications_deployment_success_thread_id',
|
||||
'telegram_notifications_deployment_failure_thread_id',
|
||||
'telegram_notifications_status_change_thread_id',
|
||||
'telegram_notifications_backup_success_thread_id',
|
||||
'telegram_notifications_backup_failure_thread_id',
|
||||
'telegram_notifications_scheduled_task_success_thread_id',
|
||||
'telegram_notifications_scheduled_task_failure_thread_id',
|
||||
'telegram_notifications_docker_cleanup_thread_id',
|
||||
'telegram_notifications_server_disk_usage_thread_id',
|
||||
'telegram_notifications_server_reachable_thread_id',
|
||||
'telegram_notifications_server_unreachable_thread_id',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'telegram_enabled' => 'boolean',
|
||||
'telegram_token' => 'encrypted',
|
||||
'telegram_chat_id' => 'encrypted',
|
||||
|
||||
'deployment_success_telegram_notifications' => 'boolean',
|
||||
'deployment_failure_telegram_notifications' => 'boolean',
|
||||
'status_change_telegram_notifications' => 'boolean',
|
||||
'backup_success_telegram_notifications' => 'boolean',
|
||||
'backup_failure_telegram_notifications' => 'boolean',
|
||||
'scheduled_task_success_telegram_notifications' => 'boolean',
|
||||
'scheduled_task_failure_telegram_notifications' => 'boolean',
|
||||
'docker_cleanup_telegram_notifications' => 'boolean',
|
||||
'server_disk_usage_telegram_notifications' => 'boolean',
|
||||
'server_reachable_telegram_notifications' => 'boolean',
|
||||
'server_unreachable_telegram_notifications' => 'boolean',
|
||||
|
||||
'telegram_notifications_deployment_success_thread_id' => 'encrypted',
|
||||
'telegram_notifications_deployment_failure_thread_id' => 'encrypted',
|
||||
'telegram_notifications_status_change_thread_id' => 'encrypted',
|
||||
'telegram_notifications_backup_success_thread_id' => 'encrypted',
|
||||
'telegram_notifications_backup_failure_thread_id' => 'encrypted',
|
||||
'telegram_notifications_scheduled_task_success_thread_id' => 'encrypted',
|
||||
'telegram_notifications_scheduled_task_failure_thread_id' => 'encrypted',
|
||||
'telegram_notifications_docker_cleanup_thread_id' => 'encrypted',
|
||||
'telegram_notifications_server_disk_usage_thread_id' => 'encrypted',
|
||||
'telegram_notifications_server_reachable_thread_id' => 'encrypted',
|
||||
'telegram_notifications_server_unreachable_thread_id' => 'encrypted',
|
||||
];
|
||||
|
||||
public function team()
|
||||
{
|
||||
return $this->belongsTo(Team::class);
|
||||
}
|
||||
|
||||
public function isEnabled()
|
||||
{
|
||||
return $this->telegram_enabled;
|
||||
}
|
||||
}
|
||||
@@ -114,7 +114,7 @@ class User extends Authenticatable implements SendsEmail
|
||||
return $this->belongsToMany(Team::class)->withPivot('role');
|
||||
}
|
||||
|
||||
public function getRecepients($notification)
|
||||
public function getRecipients($notification)
|
||||
{
|
||||
return $this->email;
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
||||
class Waitlist extends BaseModel
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $guarded = [];
|
||||
}
|
||||
@@ -6,6 +6,8 @@ use App\Models\Application;
|
||||
use App\Models\ApplicationPreview;
|
||||
use App\Notifications\CustomEmailNotification;
|
||||
use App\Notifications\Dto\DiscordMessage;
|
||||
use App\Notifications\Dto\PushoverMessage;
|
||||
use App\Notifications\Dto\SlackMessage;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
|
||||
class DeploymentFailed extends CustomEmailNotification
|
||||
@@ -44,7 +46,7 @@ class DeploymentFailed extends CustomEmailNotification
|
||||
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
return setNotificationChannels($notifiable, 'deployments');
|
||||
return $notifiable->getEnabledChannels('deployment_failure');
|
||||
}
|
||||
|
||||
public function toMail(): MailMessage
|
||||
@@ -128,4 +130,56 @@ class DeploymentFailed extends CustomEmailNotification
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function toPushover(): PushoverMessage
|
||||
{
|
||||
if ($this->preview) {
|
||||
$title = "Pull request #{$this->preview->pull_request_id} deployment failed";
|
||||
$message = "Pull request deployment failed for {$this->application_name}";
|
||||
} else {
|
||||
$title = 'Deployment failed';
|
||||
$message = "Deployment failed for {$this->application_name}";
|
||||
}
|
||||
|
||||
$buttons[] = [
|
||||
'text' => 'Deployment logs',
|
||||
'url' => $this->deployment_url,
|
||||
];
|
||||
|
||||
return new PushoverMessage(
|
||||
title: $title,
|
||||
level: 'error',
|
||||
message: $message,
|
||||
buttons: [
|
||||
...$buttons,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
public function toSlack(): SlackMessage
|
||||
{
|
||||
if ($this->preview) {
|
||||
$title = "Pull request #{$this->preview->pull_request_id} deployment failed";
|
||||
$description = "Pull request deployment failed for {$this->application_name}";
|
||||
if ($this->preview->fqdn) {
|
||||
$description .= "\nPreview URL: {$this->preview->fqdn}";
|
||||
}
|
||||
} else {
|
||||
$title = 'Deployment failed';
|
||||
$description = "Deployment failed for {$this->application_name}";
|
||||
if ($this->fqdn) {
|
||||
$description .= "\nApplication URL: {$this->fqdn}";
|
||||
}
|
||||
}
|
||||
|
||||
$description .= "\n\n**Project:** ".data_get($this->application, 'environment.project.name');
|
||||
$description .= "\n**Environment:** {$this->environment_name}";
|
||||
$description .= "\n**Deployment Logs:** {$this->deployment_url}";
|
||||
|
||||
return new SlackMessage(
|
||||
title: $title,
|
||||
description: $description,
|
||||
color: SlackMessage::errorColor()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ use App\Models\Application;
|
||||
use App\Models\ApplicationPreview;
|
||||
use App\Notifications\CustomEmailNotification;
|
||||
use App\Notifications\Dto\DiscordMessage;
|
||||
use App\Notifications\Dto\PushoverMessage;
|
||||
use App\Notifications\Dto\SlackMessage;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
|
||||
class DeploymentSuccess extends CustomEmailNotification
|
||||
@@ -44,13 +46,7 @@ class DeploymentSuccess extends CustomEmailNotification
|
||||
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
$channels = setNotificationChannels($notifiable, 'deployments');
|
||||
if (isCloud()) {
|
||||
// TODO: Make batch notifications work with email
|
||||
$channels = array_diff($channels, [\App\Notifications\Channels\EmailChannel::class]);
|
||||
}
|
||||
|
||||
return $channels;
|
||||
return $notifiable->getEnabledChannels('deployment_success');
|
||||
}
|
||||
|
||||
public function toMail(): MailMessage
|
||||
@@ -143,4 +139,67 @@ class DeploymentSuccess extends CustomEmailNotification
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function toPushover(): PushoverMessage
|
||||
{
|
||||
if ($this->preview) {
|
||||
$title = "Pull request #{$this->preview->pull_request_id} successfully deployed";
|
||||
$message = 'New PR' . $this->preview->pull_request_id . ' version successfully deployed of ' . $this->application_name . '';
|
||||
if ($this->preview->fqdn) {
|
||||
$buttons[] = [
|
||||
'text' => 'Open Application',
|
||||
'url' => $this->preview->fqdn,
|
||||
];
|
||||
}
|
||||
} else {
|
||||
$title = 'New version successfully deployed';
|
||||
$message = 'New version successfully deployed of ' . $this->application_name . '';
|
||||
if ($this->fqdn) {
|
||||
$buttons[] = [
|
||||
'text' => 'Open Application',
|
||||
'url' => $this->fqdn,
|
||||
];
|
||||
}
|
||||
}
|
||||
$buttons[] = [
|
||||
'text' => 'Deployment logs',
|
||||
'url' => $this->deployment_url,
|
||||
];
|
||||
|
||||
return new PushoverMessage(
|
||||
title: $title,
|
||||
level: 'success',
|
||||
message: $message,
|
||||
buttons: [
|
||||
...$buttons,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
public function toSlack(): SlackMessage
|
||||
{
|
||||
if ($this->preview) {
|
||||
$title = "Pull request #{$this->preview->pull_request_id} successfully deployed";
|
||||
$description = "New version successfully deployed for {$this->application_name}";
|
||||
if ($this->preview->fqdn) {
|
||||
$description .= "\nPreview URL: {$this->preview->fqdn}";
|
||||
}
|
||||
} else {
|
||||
$title = 'New version successfully deployed';
|
||||
$description = "New version successfully deployed for {$this->application_name}";
|
||||
if ($this->fqdn) {
|
||||
$description .= "\nApplication URL: {$this->fqdn}";
|
||||
}
|
||||
}
|
||||
|
||||
$description .= "\n\n**Project:** ".data_get($this->application, 'environment.project.name');
|
||||
$description .= "\n**Environment:** {$this->environment_name}";
|
||||
$description .= "\n**Deployment Logs:** {$this->deployment_url}";
|
||||
|
||||
return new SlackMessage(
|
||||
title: $title,
|
||||
description: $description,
|
||||
color: SlackMessage::successColor()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ namespace App\Notifications\Application;
|
||||
use App\Models\Application;
|
||||
use App\Notifications\CustomEmailNotification;
|
||||
use App\Notifications\Dto\DiscordMessage;
|
||||
use App\Notifications\Dto\PushoverMessage;
|
||||
use App\Notifications\Dto\SlackMessage;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
|
||||
class StatusChanged extends CustomEmailNotification
|
||||
@@ -34,7 +36,7 @@ class StatusChanged extends CustomEmailNotification
|
||||
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
return setNotificationChannels($notifiable, 'status_changes');
|
||||
return $notifiable->getEnabledChannels('status_change');
|
||||
}
|
||||
|
||||
public function toMail(): MailMessage
|
||||
@@ -75,4 +77,37 @@ class StatusChanged extends CustomEmailNotification
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function toPushover(): PushoverMessage
|
||||
{
|
||||
$message = $this->resource_name . ' has been stopped.';
|
||||
|
||||
return new PushoverMessage(
|
||||
title: 'Application stopped',
|
||||
level: 'error',
|
||||
message: $message,
|
||||
buttons: [
|
||||
[
|
||||
'text' => 'Open Application in Coolify',
|
||||
'url' => $this->resource_url,
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
public function toSlack(): SlackMessage
|
||||
{
|
||||
$title = 'Application stopped';
|
||||
$description = "{$this->resource_name} has been stopped";
|
||||
|
||||
$description .= "\n\n**Project:** ".data_get($this->resource, 'environment.project.name');
|
||||
$description .= "\n**Environment:** {$this->environment_name}";
|
||||
$description .= "\n**Application URL:** {$this->resource_url}";
|
||||
|
||||
return new SlackMessage(
|
||||
title: $title,
|
||||
description: $description,
|
||||
color: SlackMessage::errorColor()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,10 +13,13 @@ class DiscordChannel
|
||||
public function send(SendsDiscord $notifiable, Notification $notification): void
|
||||
{
|
||||
$message = $notification->toDiscord();
|
||||
$webhookUrl = $notifiable->routeNotificationForDiscord();
|
||||
if (! $webhookUrl) {
|
||||
|
||||
$discordSettings = $notifiable->discordNotificationSettings;
|
||||
|
||||
if (! $discordSettings || ! $discordSettings->isEnabled() || ! $discordSettings->discord_webhook_url) {
|
||||
return;
|
||||
}
|
||||
SendMessageToDiscordJob::dispatch($message, $webhookUrl);
|
||||
|
||||
SendMessageToDiscordJob::dispatch($message, $discordSettings->discord_webhook_url);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ class EmailChannel
|
||||
{
|
||||
try {
|
||||
$this->bootConfigs($notifiable);
|
||||
$recipients = $notifiable->getRecepients($notification);
|
||||
$recipients = $notifiable->getRecipients($notification);
|
||||
if (count($recipients) === 0) {
|
||||
throw new Exception('No email recipients found');
|
||||
}
|
||||
@@ -46,7 +46,9 @@ class EmailChannel
|
||||
|
||||
private function bootConfigs($notifiable): void
|
||||
{
|
||||
if (data_get($notifiable, 'use_instance_email_settings')) {
|
||||
$emailSettings = $notifiable->emailNotificationSettings;
|
||||
|
||||
if ($emailSettings->use_instance_email_settings) {
|
||||
$type = set_transanctional_email_settings();
|
||||
if (! $type) {
|
||||
throw new Exception('No email settings found.');
|
||||
@@ -54,23 +56,27 @@ class EmailChannel
|
||||
|
||||
return;
|
||||
}
|
||||
config()->set('mail.from.address', data_get($notifiable, 'smtp_from_address', 'test@example.com'));
|
||||
config()->set('mail.from.name', data_get($notifiable, 'smtp_from_name', 'Test'));
|
||||
if (data_get($notifiable, 'resend_enabled')) {
|
||||
|
||||
config()->set('mail.from.address', $emailSettings->smtp_from_address ?? 'test@example.com');
|
||||
config()->set('mail.from.name', $emailSettings->smtp_from_name ?? 'Test');
|
||||
|
||||
if ($emailSettings->resend_enabled) {
|
||||
config()->set('mail.default', 'resend');
|
||||
config()->set('resend.api_key', data_get($notifiable, 'resend_api_key'));
|
||||
config()->set('resend.api_key', $emailSettings->resend_api_key);
|
||||
}
|
||||
if (data_get($notifiable, 'smtp_enabled')) {
|
||||
|
||||
if ($emailSettings->smtp_enabled) {
|
||||
config()->set('mail.default', 'smtp');
|
||||
config()->set('mail.mailers.smtp', [
|
||||
'transport' => 'smtp',
|
||||
'host' => data_get($notifiable, 'smtp_host'),
|
||||
'port' => data_get($notifiable, 'smtp_port'),
|
||||
'encryption' => data_get($notifiable, 'smtp_encryption'),
|
||||
'username' => data_get($notifiable, 'smtp_username'),
|
||||
'password' => data_get($notifiable, 'smtp_password'),
|
||||
'timeout' => data_get($notifiable, 'smtp_timeout'),
|
||||
'host' => $emailSettings->smtp_host,
|
||||
'port' => $emailSettings->smtp_port,
|
||||
'encryption' => $emailSettings->smtp_encryption === 'none' ? null : $emailSettings->smtp_encryption,
|
||||
'username' => $emailSettings->smtp_username,
|
||||
'password' => $emailSettings->smtp_password,
|
||||
'timeout' => $emailSettings->smtp_timeout,
|
||||
'local_domain' => null,
|
||||
'auto_tls' => $emailSettings->smtp_encryption === 'none' ? '0' : '',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
21
app/Notifications/Channels/PushoverChannel.php
Normal file
21
app/Notifications/Channels/PushoverChannel.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications\Channels;
|
||||
|
||||
use App\Jobs\SendMessageToPushoverJob;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class PushoverChannel
|
||||
{
|
||||
public function send(SendsPushover $notifiable, Notification $notification): void
|
||||
{
|
||||
$message = $notification->toPushover();
|
||||
$pushoverSettings = $notifiable->pushoverNotificationSettings;
|
||||
|
||||
if (! $pushoverSettings || ! $pushoverSettings->isEnabled() || ! $pushoverSettings->pushover_user_key || ! $pushoverSettings->pushover_api_token) {
|
||||
return;
|
||||
}
|
||||
|
||||
SendMessageToPushoverJob::dispatch($message, $pushoverSettings->pushover_api_token, $pushoverSettings->pushover_user_key);
|
||||
}
|
||||
}
|
||||
@@ -4,5 +4,5 @@ namespace App\Notifications\Channels;
|
||||
|
||||
interface SendsEmail
|
||||
{
|
||||
public function getRecepients($notification);
|
||||
public function getRecipients($notification);
|
||||
}
|
||||
|
||||
8
app/Notifications/Channels/SendsPushover.php
Normal file
8
app/Notifications/Channels/SendsPushover.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications\Channels;
|
||||
|
||||
interface SendsPushover
|
||||
{
|
||||
public function routeNotificationForPushover();
|
||||
}
|
||||
8
app/Notifications/Channels/SendsSlack.php
Normal file
8
app/Notifications/Channels/SendsSlack.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications\Channels;
|
||||
|
||||
interface SendsSlack
|
||||
{
|
||||
public function routeNotificationForSlack();
|
||||
}
|
||||
24
app/Notifications/Channels/SlackChannel.php
Normal file
24
app/Notifications/Channels/SlackChannel.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications\Channels;
|
||||
|
||||
use App\Jobs\SendMessageToSlackJob;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class SlackChannel
|
||||
{
|
||||
/**
|
||||
* Send the given notification.
|
||||
*/
|
||||
public function send(SendsSlack $notifiable, Notification $notification): void
|
||||
{
|
||||
$message = $notification->toSlack();
|
||||
$slackSettings = $notifiable->slackNotificationSettings;
|
||||
|
||||
if (! $slackSettings || ! $slackSettings->isEnabled() || ! $slackSettings->slack_webhook_url) {
|
||||
return;
|
||||
}
|
||||
|
||||
SendMessageToSlackJob::dispatch($message, $slackSettings->slack_webhook_url);
|
||||
}
|
||||
}
|
||||
@@ -9,38 +9,39 @@ class TelegramChannel
|
||||
public function send($notifiable, $notification): void
|
||||
{
|
||||
$data = $notification->toTelegram($notifiable);
|
||||
$telegramData = $notifiable->routeNotificationForTelegram();
|
||||
$settings = $notifiable->telegramNotificationSettings;
|
||||
|
||||
$message = data_get($data, 'message');
|
||||
$buttons = data_get($data, 'buttons', []);
|
||||
$telegramToken = data_get($telegramData, 'token');
|
||||
$chatId = data_get($telegramData, 'chat_id');
|
||||
$topicId = null;
|
||||
$topicsInstance = get_class($notification);
|
||||
$telegramToken = $settings->telegram_token;
|
||||
$chatId = $settings->telegram_chat_id;
|
||||
|
||||
$threadId = match (get_class($notification)) {
|
||||
\App\Notifications\Application\DeploymentSuccess::class => $settings->telegram_notifications_deployment_success_thread_id,
|
||||
\App\Notifications\Application\DeploymentFailed::class => $settings->telegram_notifications_deployment_failure_thread_id,
|
||||
\App\Notifications\Application\StatusChanged::class,
|
||||
\App\Notifications\Container\ContainerRestarted::class,
|
||||
\App\Notifications\Container\ContainerStopped::class => $settings->telegram_notifications_status_change_thread_id,
|
||||
|
||||
\App\Notifications\Database\BackupSuccess::class => $settings->telegram_notifications_backup_success_thread_id,
|
||||
\App\Notifications\Database\BackupFailed::class => $settings->telegram_notifications_backup_failure_thread_id,
|
||||
|
||||
\App\Notifications\ScheduledTask\TaskSuccess::class => $settings->telegram_notifications_scheduled_task_success_thread_id,
|
||||
\App\Notifications\ScheduledTask\TaskFailed::class => $settings->telegram_notifications_scheduled_task_failure_thread_id,
|
||||
|
||||
\App\Notifications\Server\DockerCleanupSuccess::class => $settings->telegram_notifications_docker_cleanup_success_thread_id,
|
||||
\App\Notifications\Server\DockerCleanupFailed::class => $settings->telegram_notifications_docker_cleanup_failure_thread_id,
|
||||
\App\Notifications\Server\HighDiskUsage::class => $settings->telegram_notifications_server_disk_usage_thread_id,
|
||||
\App\Notifications\Server\Unreachable::class => $settings->telegram_notifications_server_unreachable_thread_id,
|
||||
\App\Notifications\Server\Reachable::class => $settings->telegram_notifications_server_reachable_thread_id,
|
||||
|
||||
default => null,
|
||||
};
|
||||
|
||||
switch ($topicsInstance) {
|
||||
case \App\Notifications\Test::class:
|
||||
$topicId = data_get($notifiable, 'telegram_notifications_test_message_thread_id');
|
||||
break;
|
||||
case \App\Notifications\Application\StatusChanged::class:
|
||||
case \App\Notifications\Container\ContainerRestarted::class:
|
||||
case \App\Notifications\Container\ContainerStopped::class:
|
||||
$topicId = data_get($notifiable, 'telegram_notifications_status_changes_message_thread_id');
|
||||
break;
|
||||
case \App\Notifications\Application\DeploymentSuccess::class:
|
||||
case \App\Notifications\Application\DeploymentFailed::class:
|
||||
$topicId = data_get($notifiable, 'telegram_notifications_deployments_message_thread_id');
|
||||
break;
|
||||
case \App\Notifications\Database\BackupSuccess::class:
|
||||
case \App\Notifications\Database\BackupFailed::class:
|
||||
$topicId = data_get($notifiable, 'telegram_notifications_database_backups_message_thread_id');
|
||||
break;
|
||||
case \App\Notifications\ScheduledTask\TaskFailed::class:
|
||||
$topicId = data_get($notifiable, 'telegram_notifications_scheduled_tasks_thread_id');
|
||||
break;
|
||||
}
|
||||
if (! $telegramToken || ! $chatId || ! $message) {
|
||||
return;
|
||||
}
|
||||
SendMessageToTelegramJob::dispatch($message, $buttons, $telegramToken, $chatId, $topicId);
|
||||
|
||||
SendMessageToTelegramJob::dispatch($message, $buttons, $telegramToken, $chatId, $threadId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ use Exception;
|
||||
use Illuminate\Mail\Message;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Log;
|
||||
|
||||
class TransactionalEmailChannel
|
||||
{
|
||||
@@ -15,8 +14,6 @@ class TransactionalEmailChannel
|
||||
{
|
||||
$settings = instanceSettings();
|
||||
if (! data_get($settings, 'smtp_enabled') && ! data_get($settings, 'resend_enabled')) {
|
||||
Log::info('SMTP/Resend not enabled');
|
||||
|
||||
return;
|
||||
}
|
||||
$email = $notifiable->email;
|
||||
|
||||
@@ -5,6 +5,8 @@ namespace App\Notifications\Container;
|
||||
use App\Models\Server;
|
||||
use App\Notifications\CustomEmailNotification;
|
||||
use App\Notifications\Dto\DiscordMessage;
|
||||
use App\Notifications\Dto\PushoverMessage;
|
||||
use App\Notifications\Dto\SlackMessage;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
|
||||
class ContainerRestarted extends CustomEmailNotification
|
||||
@@ -16,7 +18,7 @@ class ContainerRestarted extends CustomEmailNotification
|
||||
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
return setNotificationChannels($notifiable, 'status_changes');
|
||||
return $notifiable->getEnabledChannels('status_change');
|
||||
}
|
||||
|
||||
public function toMail(): MailMessage
|
||||
@@ -66,4 +68,38 @@ class ContainerRestarted extends CustomEmailNotification
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
public function toPushover(): PushoverMessage
|
||||
{
|
||||
$buttons = [];
|
||||
if ($this->url) {
|
||||
$buttons[] = [
|
||||
'text' => 'Check Proxy in Coolify',
|
||||
'url' => $this->url,
|
||||
];
|
||||
}
|
||||
|
||||
return new PushoverMessage(
|
||||
title: 'Resource restarted',
|
||||
level: 'warning',
|
||||
message: "A resource ({$this->name}) has been restarted automatically on {$this->server->name}",
|
||||
buttons: $buttons,
|
||||
);
|
||||
}
|
||||
|
||||
public function toSlack(): SlackMessage
|
||||
{
|
||||
$title = 'Resource restarted';
|
||||
$description = "A resource ({$this->name}) has been restarted automatically on {$this->server->name}";
|
||||
|
||||
if ($this->url) {
|
||||
$description .= "\n**Resource URL:** {$this->url}";
|
||||
}
|
||||
|
||||
return new SlackMessage(
|
||||
title: $title,
|
||||
description: $description,
|
||||
color: SlackMessage::warningColor()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ namespace App\Notifications\Container;
|
||||
use App\Models\Server;
|
||||
use App\Notifications\CustomEmailNotification;
|
||||
use App\Notifications\Dto\DiscordMessage;
|
||||
use App\Notifications\Dto\PushoverMessage;
|
||||
use App\Notifications\Dto\SlackMessage;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
|
||||
class ContainerStopped extends CustomEmailNotification
|
||||
@@ -16,7 +18,7 @@ class ContainerStopped extends CustomEmailNotification
|
||||
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
return setNotificationChannels($notifiable, 'status_changes');
|
||||
return $notifiable->getEnabledChannels('status_change');
|
||||
}
|
||||
|
||||
public function toMail(): MailMessage
|
||||
@@ -66,4 +68,39 @@ class ContainerStopped extends CustomEmailNotification
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
public function toPushover(): PushoverMessage
|
||||
{
|
||||
$buttons = [];
|
||||
if ($this->url) {
|
||||
$buttons[] = [
|
||||
'text' => 'Open Application in Coolify',
|
||||
'url' => $this->url,
|
||||
];
|
||||
}
|
||||
|
||||
return new PushoverMessage(
|
||||
title: 'Resource stopped',
|
||||
level: 'error',
|
||||
message: "A resource ({$this->name}) has been stopped unexpectedly on {$this->server->name}",
|
||||
buttons: $buttons,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public function toSlack(): SlackMessage
|
||||
{
|
||||
$title = 'Resource stopped';
|
||||
$description = "A resource ({$this->name}) has been stopped unexpectedly on {$this->server->name}";
|
||||
|
||||
if ($this->url) {
|
||||
$description .= "\n**Resource URL:** {$this->url}";
|
||||
}
|
||||
|
||||
return new SlackMessage(
|
||||
title: $title,
|
||||
description: $description,
|
||||
color: SlackMessage::errorColor()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ namespace App\Notifications\Database;
|
||||
use App\Models\ScheduledDatabaseBackup;
|
||||
use App\Notifications\CustomEmailNotification;
|
||||
use App\Notifications\Dto\DiscordMessage;
|
||||
use App\Notifications\Dto\PushoverMessage;
|
||||
use App\Notifications\Dto\SlackMessage;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
|
||||
class BackupFailed extends CustomEmailNotification
|
||||
@@ -22,13 +24,13 @@ class BackupFailed extends CustomEmailNotification
|
||||
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
return setNotificationChannels($notifiable, 'database_backups');
|
||||
return $notifiable->getEnabledChannels('backup_failure');
|
||||
}
|
||||
|
||||
public function toMail(): MailMessage
|
||||
{
|
||||
$mail = new MailMessage;
|
||||
$mail->subject("Coolify: [ACTION REQUIRED] Backup FAILED for {$this->database->name}");
|
||||
$mail->subject("Coolify: [ACTION REQUIRED] Database Backup FAILED for {$this->database->name}");
|
||||
$mail->view('emails.backup-failed', [
|
||||
'name' => $this->name,
|
||||
'database_name' => $this->database_name,
|
||||
@@ -62,4 +64,28 @@ class BackupFailed extends CustomEmailNotification
|
||||
'message' => $message,
|
||||
];
|
||||
}
|
||||
|
||||
public function toPushover(): PushoverMessage
|
||||
{
|
||||
return new PushoverMessage(
|
||||
title: 'Database backup failed',
|
||||
level: 'error',
|
||||
message: "Database backup for {$this->name} (db:{$this->database_name}) was FAILED<br/><br/><b>Frequency:</b> {$this->frequency} .<br/><b>Reason:</b> {$this->output}",
|
||||
);
|
||||
}
|
||||
|
||||
public function toSlack(): SlackMessage
|
||||
{
|
||||
$title = 'Database backup failed';
|
||||
$description = "Database backup for {$this->name} (db:{$this->database_name}) has FAILED.";
|
||||
|
||||
$description .= "\n\n**Frequency:** {$this->frequency}";
|
||||
$description .= "\n\n**Error Output:**\n{$this->output}";
|
||||
|
||||
return new SlackMessage(
|
||||
title: $title,
|
||||
description: $description,
|
||||
color: SlackMessage::errorColor()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ namespace App\Notifications\Database;
|
||||
use App\Models\ScheduledDatabaseBackup;
|
||||
use App\Notifications\CustomEmailNotification;
|
||||
use App\Notifications\Dto\DiscordMessage;
|
||||
use App\Notifications\Dto\PushoverMessage;
|
||||
use App\Notifications\Dto\SlackMessage;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
|
||||
class BackupSuccess extends CustomEmailNotification
|
||||
@@ -23,7 +25,7 @@ class BackupSuccess extends CustomEmailNotification
|
||||
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
return setNotificationChannels($notifiable, 'database_backups');
|
||||
return $notifiable->getEnabledChannels('backup_success');
|
||||
}
|
||||
|
||||
public function toMail(): MailMessage
|
||||
@@ -60,4 +62,29 @@ class BackupSuccess extends CustomEmailNotification
|
||||
'message' => $message,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
public function toPushover(): PushoverMessage
|
||||
{
|
||||
return new PushoverMessage(
|
||||
title: 'Database backup successful',
|
||||
level: 'success',
|
||||
message: "Database backup for {$this->name} (db:{$this->database_name}) was successful.<br/><br/><b>Frequency:</b> {$this->frequency}.",
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public function toSlack(): SlackMessage
|
||||
{
|
||||
$title = 'Database backup successful';
|
||||
$description = "Database backup for {$this->name} (db:{$this->database_name}) was successful.";
|
||||
|
||||
$description .= "\n\n**Frequency:** {$this->frequency}";
|
||||
|
||||
return new SlackMessage(
|
||||
title: $title,
|
||||
description: $description,
|
||||
color: SlackMessage::successColor()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
50
app/Notifications/Dto/PushoverMessage.php
Normal file
50
app/Notifications/Dto/PushoverMessage.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications\Dto;
|
||||
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class PushoverMessage
|
||||
{
|
||||
public function __construct(
|
||||
public string $title,
|
||||
public string $message,
|
||||
public array $buttons = [],
|
||||
public string $level = 'info',
|
||||
) {}
|
||||
|
||||
public function getLevelIcon(): string
|
||||
{
|
||||
return match ($this->level) {
|
||||
'info' => 'ℹ️',
|
||||
'error' => '❌',
|
||||
'success' => '✅ ',
|
||||
'warning' => '⚠️',
|
||||
};
|
||||
}
|
||||
|
||||
public function toPayload(string $token, string $user): array
|
||||
{
|
||||
$levelIcon = $this->getLevelIcon();
|
||||
$payload = [
|
||||
'token' => $token,
|
||||
'user' => $user,
|
||||
'title' => "{$levelIcon} {$this->title}",
|
||||
'message' => $this->message,
|
||||
'html' => 1,
|
||||
];
|
||||
|
||||
foreach ($this->buttons as $button) {
|
||||
$buttonUrl = data_get($button, 'url');
|
||||
$text = data_get($button, 'text', 'Click here');
|
||||
if ($buttonUrl && str_contains($buttonUrl, 'http://localhost')) {
|
||||
$buttonUrl = str_replace('http://localhost', config('app.url'), $buttonUrl);
|
||||
}
|
||||
$payload['message'] .= " <a href='" . $buttonUrl . "'>" . $text . '</a>';
|
||||
}
|
||||
|
||||
Log::info('Pushover message', $payload);
|
||||
|
||||
return $payload;
|
||||
}
|
||||
}
|
||||
32
app/Notifications/Dto/SlackMessage.php
Normal file
32
app/Notifications/Dto/SlackMessage.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications\Dto;
|
||||
|
||||
class SlackMessage
|
||||
{
|
||||
public function __construct(
|
||||
public string $title,
|
||||
public string $description,
|
||||
public string $color = '#0099ff'
|
||||
) {}
|
||||
|
||||
public static function infoColor(): string
|
||||
{
|
||||
return '#0099ff';
|
||||
}
|
||||
|
||||
public static function errorColor(): string
|
||||
{
|
||||
return '#ff0000';
|
||||
}
|
||||
|
||||
public static function successColor(): string
|
||||
{
|
||||
return '#00ff00';
|
||||
}
|
||||
|
||||
public static function warningColor(): string
|
||||
{
|
||||
return '#ffa500';
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
namespace App\Notifications\Internal;
|
||||
|
||||
use App\Notifications\Channels\DiscordChannel;
|
||||
use App\Notifications\Channels\TelegramChannel;
|
||||
use App\Notifications\Dto\DiscordMessage;
|
||||
use App\Notifications\Dto\PushoverMessage;
|
||||
use App\Notifications\Dto\SlackMessage;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Notification;
|
||||
@@ -22,18 +22,7 @@ class GeneralNotification extends Notification implements ShouldQueue
|
||||
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
$channels = [];
|
||||
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
||||
$isTelegramEnabled = data_get($notifiable, 'telegram_enabled');
|
||||
|
||||
if ($isDiscordEnabled) {
|
||||
$channels[] = DiscordChannel::class;
|
||||
}
|
||||
if ($isTelegramEnabled) {
|
||||
$channels[] = TelegramChannel::class;
|
||||
}
|
||||
|
||||
return $channels;
|
||||
return $notifiable->getEnabledChannels('general');
|
||||
}
|
||||
|
||||
public function toDiscord(): DiscordMessage
|
||||
@@ -51,4 +40,22 @@ class GeneralNotification extends Notification implements ShouldQueue
|
||||
'message' => $this->message,
|
||||
];
|
||||
}
|
||||
|
||||
public function toPushover(): PushoverMessage
|
||||
{
|
||||
return new PushoverMessage(
|
||||
title: 'General Notification',
|
||||
level: 'info',
|
||||
message: $this->message,
|
||||
);
|
||||
}
|
||||
|
||||
public function toSlack(): SlackMessage
|
||||
{
|
||||
return new SlackMessage(
|
||||
title: 'Coolify: General Notification',
|
||||
description: $this->message,
|
||||
color: SlackMessage::infoColor(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ namespace App\Notifications\ScheduledTask;
|
||||
use App\Models\ScheduledTask;
|
||||
use App\Notifications\CustomEmailNotification;
|
||||
use App\Notifications\Dto\DiscordMessage;
|
||||
use App\Notifications\Dto\PushoverMessage;
|
||||
use App\Notifications\Dto\SlackMessage;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
|
||||
class TaskFailed extends CustomEmailNotification
|
||||
@@ -15,15 +17,15 @@ class TaskFailed extends CustomEmailNotification
|
||||
{
|
||||
$this->onQueue('high');
|
||||
if ($task->application) {
|
||||
$this->url = $task->application->failedTaskLink($task->uuid);
|
||||
$this->url = $task->application->taskLink($task->uuid);
|
||||
} elseif ($task->service) {
|
||||
$this->url = $task->service->failedTaskLink($task->uuid);
|
||||
$this->url = $task->service->taskLink($task->uuid);
|
||||
}
|
||||
}
|
||||
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
return setNotificationChannels($notifiable, 'scheduled_tasks');
|
||||
return $notifiable->getEnabledChannels('scheduled_task_failure');
|
||||
}
|
||||
|
||||
public function toMail(): MailMessage
|
||||
@@ -68,4 +70,48 @@ class TaskFailed extends CustomEmailNotification
|
||||
'message' => $message,
|
||||
];
|
||||
}
|
||||
|
||||
public function toPushover(): PushoverMessage
|
||||
{
|
||||
$message = "Scheduled task ({$this->task->name}) failed<br/>";
|
||||
|
||||
if ($this->output) {
|
||||
$message .= "<br/><b>Error Output:</b>{$this->output}";
|
||||
}
|
||||
|
||||
$buttons = [];
|
||||
if ($this->url) {
|
||||
$buttons[] = [
|
||||
'text' => 'Open task in Coolify',
|
||||
'url' => (string) $this->url,
|
||||
];
|
||||
}
|
||||
|
||||
return new PushoverMessage(
|
||||
title: 'Scheduled task failed',
|
||||
level: 'error',
|
||||
message: $message,
|
||||
buttons: $buttons,
|
||||
);
|
||||
}
|
||||
|
||||
public function toSlack(): SlackMessage
|
||||
{
|
||||
$title = 'Scheduled task failed';
|
||||
$description = "Scheduled task ({$this->task->name}) failed.";
|
||||
|
||||
if ($this->output) {
|
||||
$description .= "\n\n**Error Output:**\n{$this->output}";
|
||||
}
|
||||
|
||||
if ($this->url) {
|
||||
$description .= "\n\n**Task URL:** {$this->url}";
|
||||
}
|
||||
|
||||
return new SlackMessage(
|
||||
title: $title,
|
||||
description: $description,
|
||||
color: SlackMessage::errorColor()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
108
app/Notifications/ScheduledTask/TaskSuccess.php
Normal file
108
app/Notifications/ScheduledTask/TaskSuccess.php
Normal file
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications\ScheduledTask;
|
||||
|
||||
use App\Models\ScheduledTask;
|
||||
use App\Notifications\CustomEmailNotification;
|
||||
use App\Notifications\Dto\DiscordMessage;
|
||||
use App\Notifications\Dto\PushoverMessage;
|
||||
use App\Notifications\Dto\SlackMessage;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
|
||||
class TaskSuccess extends CustomEmailNotification
|
||||
{
|
||||
public ?string $url = null;
|
||||
|
||||
public function __construct(public ScheduledTask $task, public string $output)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
if ($task->application) {
|
||||
$this->url = $task->application->taskLink($task->uuid);
|
||||
} elseif ($task->service) {
|
||||
$this->url = $task->service->taskLink($task->uuid);
|
||||
}
|
||||
}
|
||||
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
return $notifiable->getEnabledChannels('scheduled_task_success');
|
||||
}
|
||||
|
||||
public function toMail(): MailMessage
|
||||
{
|
||||
$mail = new MailMessage;
|
||||
$mail->subject("Coolify: Scheduled task ({$this->task->name}) succeeded.");
|
||||
$mail->view('emails.scheduled-task-success', [
|
||||
'task' => $this->task,
|
||||
'url' => $this->url,
|
||||
'output' => $this->output,
|
||||
]);
|
||||
|
||||
return $mail;
|
||||
}
|
||||
|
||||
public function toDiscord(): DiscordMessage
|
||||
{
|
||||
$message = new DiscordMessage(
|
||||
title: ':white_check_mark: Scheduled task succeeded',
|
||||
description: "Scheduled task ({$this->task->name}) succeeded.",
|
||||
color: DiscordMessage::successColor(),
|
||||
);
|
||||
|
||||
if ($this->url) {
|
||||
$message->addField('Scheduled task', '[Link]('.$this->url.')');
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
public function toTelegram(): array
|
||||
{
|
||||
$message = "Coolify: Scheduled task ({$this->task->name}) succeeded.";
|
||||
if ($this->url) {
|
||||
$buttons[] = [
|
||||
'text' => 'Open task in Coolify',
|
||||
'url' => (string) $this->url,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'message' => $message,
|
||||
];
|
||||
}
|
||||
|
||||
public function toPushover(): PushoverMessage
|
||||
{
|
||||
$message = "Coolify: Scheduled task ({$this->task->name}) succeeded.";
|
||||
$buttons = [];
|
||||
if ($this->url) {
|
||||
$buttons[] = [
|
||||
'text' => 'Open task in Coolify',
|
||||
'url' => (string) $this->url,
|
||||
];
|
||||
}
|
||||
|
||||
return new PushoverMessage(
|
||||
title: 'Scheduled task succeeded',
|
||||
level: 'success',
|
||||
message: $message,
|
||||
buttons: $buttons,
|
||||
);
|
||||
}
|
||||
|
||||
public function toSlack(): SlackMessage
|
||||
{
|
||||
$title = 'Scheduled task succeeded';
|
||||
$description = "Scheduled task ({$this->task->name}) succeeded.";
|
||||
|
||||
if ($this->url) {
|
||||
$description .= "\n\n**Task URL:** {$this->url}";
|
||||
}
|
||||
|
||||
return new SlackMessage(
|
||||
title: $title,
|
||||
description: $description,
|
||||
color: SlackMessage::successColor()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications\Server;
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Notifications\Channels\DiscordChannel;
|
||||
use App\Notifications\Channels\TelegramChannel;
|
||||
use App\Notifications\CustomEmailNotification;
|
||||
use App\Notifications\Dto\DiscordMessage;
|
||||
|
||||
class DockerCleanup extends CustomEmailNotification
|
||||
{
|
||||
public function __construct(public Server $server, public string $message)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
$channels = [];
|
||||
// $isEmailEnabled = isEmailEnabled($notifiable);
|
||||
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
||||
$isTelegramEnabled = data_get($notifiable, 'telegram_enabled');
|
||||
|
||||
if ($isDiscordEnabled) {
|
||||
$channels[] = DiscordChannel::class;
|
||||
}
|
||||
// if ($isEmailEnabled) {
|
||||
// $channels[] = EmailChannel::class;
|
||||
// }
|
||||
if ($isTelegramEnabled) {
|
||||
$channels[] = TelegramChannel::class;
|
||||
}
|
||||
|
||||
return $channels;
|
||||
}
|
||||
|
||||
// public function toMail(): MailMessage
|
||||
// {
|
||||
// $mail = new MailMessage();
|
||||
// $mail->subject("Coolify: Server ({$this->server->name}) high disk usage detected!");
|
||||
// $mail->view('emails.high-disk-usage', [
|
||||
// 'name' => $this->server->name,
|
||||
// 'disk_usage' => $this->disk_usage,
|
||||
// 'threshold' => $this->docker_cleanup_threshold,
|
||||
// ]);
|
||||
// return $mail;
|
||||
// }
|
||||
|
||||
public function toDiscord(): DiscordMessage
|
||||
{
|
||||
return new DiscordMessage(
|
||||
title: ':white_check_mark: Server cleanup job done',
|
||||
description: $this->message,
|
||||
color: DiscordMessage::successColor(),
|
||||
);
|
||||
}
|
||||
|
||||
public function toTelegram(): array
|
||||
{
|
||||
return [
|
||||
'message' => "Coolify: Server '{$this->server->name}' cleanup job done!\n\n{$this->message}",
|
||||
];
|
||||
}
|
||||
}
|
||||
69
app/Notifications/Server/DockerCleanupFailed.php
Normal file
69
app/Notifications/Server/DockerCleanupFailed.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications\Server;
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Notifications\CustomEmailNotification;
|
||||
use App\Notifications\Dto\DiscordMessage;
|
||||
use App\Notifications\Dto\PushoverMessage;
|
||||
use App\Notifications\Dto\SlackMessage;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
|
||||
class DockerCleanupFailed extends CustomEmailNotification
|
||||
{
|
||||
public function __construct(public Server $server, public string $message)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
return $notifiable->getEnabledChannels('docker_cleanup_failure');
|
||||
}
|
||||
|
||||
public function toMail(): MailMessage
|
||||
{
|
||||
$mail = new MailMessage;
|
||||
$mail->subject("Coolify: [ACTION REQUIRED] Docker cleanup job failed on {$this->server->name}");
|
||||
$mail->view('emails.docker-cleanup-failed', [
|
||||
'name' => $this->server->name,
|
||||
'text' => $this->message,
|
||||
]);
|
||||
|
||||
return $mail;
|
||||
}
|
||||
|
||||
public function toDiscord(): DiscordMessage
|
||||
{
|
||||
return new DiscordMessage(
|
||||
title: ':cross_mark: Coolify: [ACTION REQUIRED] Docker cleanup job failed on '.$this->server->name,
|
||||
description: $this->message,
|
||||
color: DiscordMessage::errorColor(),
|
||||
);
|
||||
}
|
||||
|
||||
public function toTelegram(): array
|
||||
{
|
||||
return [
|
||||
'message' => "Coolify: [ACTION REQUIRED] Docker cleanup job failed on {$this->server->name}!\n\n{$this->message}",
|
||||
];
|
||||
}
|
||||
|
||||
public function toPushover(): PushoverMessage
|
||||
{
|
||||
return new PushoverMessage(
|
||||
title: 'Docker cleanup job failed',
|
||||
level: 'error',
|
||||
message: "[ACTION REQUIRED] Docker cleanup job failed on {$this->server->name}!\n\n{$this->message}",
|
||||
);
|
||||
}
|
||||
|
||||
public function toSlack(): SlackMessage
|
||||
{
|
||||
return new SlackMessage(
|
||||
title: 'Coolify: [ACTION REQUIRED] Docker cleanup job failed',
|
||||
description: "Docker cleanup job failed on '{$this->server->name}'!\n\n{$this->message}",
|
||||
color: SlackMessage::errorColor()
|
||||
);
|
||||
}
|
||||
}
|
||||
69
app/Notifications/Server/DockerCleanupSuccess.php
Normal file
69
app/Notifications/Server/DockerCleanupSuccess.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications\Server;
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Notifications\CustomEmailNotification;
|
||||
use App\Notifications\Dto\DiscordMessage;
|
||||
use App\Notifications\Dto\PushoverMessage;
|
||||
use App\Notifications\Dto\SlackMessage;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
|
||||
class DockerCleanupSuccess extends CustomEmailNotification
|
||||
{
|
||||
public function __construct(public Server $server, public string $message)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
return $notifiable->getEnabledChannels('docker_cleanup_success');
|
||||
}
|
||||
|
||||
public function toMail(): MailMessage
|
||||
{
|
||||
$mail = new MailMessage;
|
||||
$mail->subject("Coolify: Docker cleanup job succeeded on {$this->server->name}");
|
||||
$mail->view('emails.docker-cleanup-success', [
|
||||
'name' => $this->server->name,
|
||||
'text' => $this->message,
|
||||
]);
|
||||
|
||||
return $mail;
|
||||
}
|
||||
|
||||
public function toDiscord(): DiscordMessage
|
||||
{
|
||||
return new DiscordMessage(
|
||||
title: ':white_check_mark: Coolify: Docker cleanup job succeeded on '.$this->server->name,
|
||||
description: $this->message,
|
||||
color: DiscordMessage::successColor(),
|
||||
);
|
||||
}
|
||||
|
||||
public function toTelegram(): array
|
||||
{
|
||||
return [
|
||||
'message' => "Coolify: Docker cleanup job succeeded on {$this->server->name}!\n\n{$this->message}",
|
||||
];
|
||||
}
|
||||
|
||||
public function toPushover(): PushoverMessage
|
||||
{
|
||||
return new PushoverMessage(
|
||||
title: 'Docker cleanup job succeeded',
|
||||
level: 'success',
|
||||
message: "Docker cleanup job succeeded on {$this->server->name}!\n\n{$this->message}",
|
||||
);
|
||||
}
|
||||
|
||||
public function toSlack(): SlackMessage
|
||||
{
|
||||
return new SlackMessage(
|
||||
title: 'Coolify: Docker cleanup job succeeded',
|
||||
description: "Docker cleanup job succeeded on '{$this->server->name}'!\n\n{$this->message}",
|
||||
color: SlackMessage::successColor()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,10 @@
|
||||
namespace App\Notifications\Server;
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Notifications\Channels\DiscordChannel;
|
||||
use App\Notifications\Channels\EmailChannel;
|
||||
use App\Notifications\Channels\TelegramChannel;
|
||||
use App\Notifications\CustomEmailNotification;
|
||||
use App\Notifications\Dto\DiscordMessage;
|
||||
use App\Notifications\Dto\PushoverMessage;
|
||||
use App\Notifications\Dto\SlackMessage;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
|
||||
class ForceDisabled extends CustomEmailNotification
|
||||
@@ -19,22 +18,7 @@ class ForceDisabled extends CustomEmailNotification
|
||||
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
$channels = [];
|
||||
$isEmailEnabled = isEmailEnabled($notifiable);
|
||||
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
||||
$isTelegramEnabled = data_get($notifiable, 'telegram_enabled');
|
||||
|
||||
if ($isDiscordEnabled) {
|
||||
$channels[] = DiscordChannel::class;
|
||||
}
|
||||
if ($isEmailEnabled) {
|
||||
$channels[] = EmailChannel::class;
|
||||
}
|
||||
if ($isTelegramEnabled) {
|
||||
$channels[] = TelegramChannel::class;
|
||||
}
|
||||
|
||||
return $channels;
|
||||
return $notifiable->getEnabledChannels('server_force_disabled');
|
||||
}
|
||||
|
||||
public function toMail(): MailMessage
|
||||
@@ -67,4 +51,27 @@ class ForceDisabled extends CustomEmailNotification
|
||||
'message' => "Coolify: Server ({$this->server->name}) disabled because it is not paid!\n All automations and integrations are stopped.\nPlease update your subscription to enable the server again [here](https://app.coolify.io/subscriptions).",
|
||||
];
|
||||
}
|
||||
|
||||
public function toPushover(): PushoverMessage
|
||||
{
|
||||
return new PushoverMessage(
|
||||
title: 'Server disabled',
|
||||
level: 'error',
|
||||
message: "Server ({$this->server->name}) disabled because it is not paid!\n All automations and integrations are stopped.<br/>Please update your subscription to enable the server again [here](https://app.coolify.io/subscriptions).",
|
||||
);
|
||||
}
|
||||
|
||||
public function toSlack(): SlackMessage
|
||||
{
|
||||
$title = 'Server disabled';
|
||||
$description = "Server ({$this->server->name}) disabled because it is not paid!\n";
|
||||
$description .= "All automations and integrations are stopped.\n\n";
|
||||
$description .= 'Please update your subscription to enable the server again: https://app.coolify.io/subscriptions';
|
||||
|
||||
return new SlackMessage(
|
||||
title: $title,
|
||||
description: $description,
|
||||
color: SlackMessage::errorColor()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,10 @@
|
||||
namespace App\Notifications\Server;
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Notifications\Channels\DiscordChannel;
|
||||
use App\Notifications\Channels\EmailChannel;
|
||||
use App\Notifications\Channels\TelegramChannel;
|
||||
use App\Notifications\CustomEmailNotification;
|
||||
use App\Notifications\Dto\DiscordMessage;
|
||||
use App\Notifications\Dto\PushoverMessage;
|
||||
use App\Notifications\Dto\SlackMessage;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
|
||||
class ForceEnabled extends CustomEmailNotification
|
||||
@@ -19,22 +18,7 @@ class ForceEnabled extends CustomEmailNotification
|
||||
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
$channels = [];
|
||||
$isEmailEnabled = isEmailEnabled($notifiable);
|
||||
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
||||
$isTelegramEnabled = data_get($notifiable, 'telegram_enabled');
|
||||
|
||||
if ($isDiscordEnabled) {
|
||||
$channels[] = DiscordChannel::class;
|
||||
}
|
||||
if ($isEmailEnabled) {
|
||||
$channels[] = EmailChannel::class;
|
||||
}
|
||||
if ($isTelegramEnabled) {
|
||||
$channels[] = TelegramChannel::class;
|
||||
}
|
||||
|
||||
return $channels;
|
||||
return $notifiable->getEnabledChannels('server_force_enabled');
|
||||
}
|
||||
|
||||
public function toMail(): MailMessage
|
||||
@@ -63,4 +47,22 @@ class ForceEnabled extends CustomEmailNotification
|
||||
'message' => "Coolify: Server ({$this->server->name}) enabled again!",
|
||||
];
|
||||
}
|
||||
|
||||
public function toPushover(): PushoverMessage
|
||||
{
|
||||
return new PushoverMessage(
|
||||
title: 'Server enabled',
|
||||
level: 'success',
|
||||
message: "Server ({$this->server->name}) enabled again!",
|
||||
);
|
||||
}
|
||||
|
||||
public function toSlack(): SlackMessage
|
||||
{
|
||||
return new SlackMessage(
|
||||
title: 'Server enabled',
|
||||
description: "Server '{$this->server->name}' enabled again!",
|
||||
color: SlackMessage::successColor()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ namespace App\Notifications\Server;
|
||||
use App\Models\Server;
|
||||
use App\Notifications\CustomEmailNotification;
|
||||
use App\Notifications\Dto\DiscordMessage;
|
||||
use App\Notifications\Dto\PushoverMessage;
|
||||
use App\Notifications\Dto\SlackMessage;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
|
||||
class HighDiskUsage extends CustomEmailNotification
|
||||
@@ -16,7 +18,7 @@ class HighDiskUsage extends CustomEmailNotification
|
||||
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
return setNotificationChannels($notifiable, 'server_disk_usage');
|
||||
return $notifiable->getEnabledChannels('server_disk_usage');
|
||||
}
|
||||
|
||||
public function toMail(): MailMessage
|
||||
@@ -55,4 +57,35 @@ class HighDiskUsage extends CustomEmailNotification
|
||||
'message' => "Coolify: Server '{$this->server->name}' high disk usage detected!\nDisk usage: {$this->disk_usage}%. Threshold: {$this->server_disk_usage_notification_threshold}%.\nPlease cleanup your disk to prevent data-loss.\nHere are some tips: https://coolify.io/docs/knowledge-base/server/automated-cleanup.",
|
||||
];
|
||||
}
|
||||
|
||||
public function toPushover(): PushoverMessage
|
||||
{
|
||||
return new PushoverMessage(
|
||||
title: 'High disk usage detected',
|
||||
level: 'warning',
|
||||
message: "Server '{$this->server->name}' high disk usage detected!<br/><br/><b>Disk usage:</b> {$this->disk_usage}%.<br/><b>Threshold:</b> {$this->server_disk_usage_notification_threshold}%.<br/>Please cleanup your disk to prevent data-loss.",
|
||||
buttons: [
|
||||
'Change settings' => base_url().'/server/'.$this->server->uuid."#advanced",
|
||||
'Tips for cleanup' => "https://coolify.io/docs/knowledge-base/server/automated-cleanup",
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
public function toSlack(): SlackMessage
|
||||
{
|
||||
$description = "Server '{$this->server->name}' high disk usage detected!\n";
|
||||
$description .= "Disk usage: {$this->disk_usage}%\n";
|
||||
$description .= "Threshold: {$this->server_disk_usage_notification_threshold}%\n\n";
|
||||
$description .= "Please cleanup your disk to prevent data-loss.\n";
|
||||
$description .= "Tips for cleanup: https://coolify.io/docs/knowledge-base/server/automated-cleanup\n";
|
||||
$description .= "Change settings:\n";
|
||||
$description .= '- Threshold: '.base_url().'/server/'.$this->server->uuid."#advanced\n";
|
||||
$description .= '- Notifications: '.base_url().'/notifications/discord';
|
||||
|
||||
return new SlackMessage(
|
||||
title: 'High disk usage detected',
|
||||
description: $description,
|
||||
color: SlackMessage::errorColor()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,10 @@
|
||||
namespace App\Notifications\Server;
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Notifications\Channels\DiscordChannel;
|
||||
use App\Notifications\Channels\EmailChannel;
|
||||
use App\Notifications\Channels\TelegramChannel;
|
||||
use App\Notifications\CustomEmailNotification;
|
||||
use App\Notifications\Dto\DiscordMessage;
|
||||
use App\Notifications\Dto\PushoverMessage;
|
||||
use App\Notifications\Dto\SlackMessage;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
|
||||
class Reachable extends CustomEmailNotification
|
||||
@@ -28,22 +27,7 @@ class Reachable extends CustomEmailNotification
|
||||
return [];
|
||||
}
|
||||
|
||||
$channels = [];
|
||||
$isEmailEnabled = isEmailEnabled($notifiable);
|
||||
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
||||
$isTelegramEnabled = data_get($notifiable, 'telegram_enabled');
|
||||
|
||||
if ($isDiscordEnabled) {
|
||||
$channels[] = DiscordChannel::class;
|
||||
}
|
||||
if ($isEmailEnabled) {
|
||||
$channels[] = EmailChannel::class;
|
||||
}
|
||||
if ($isTelegramEnabled) {
|
||||
$channels[] = TelegramChannel::class;
|
||||
}
|
||||
|
||||
return $channels;
|
||||
return $notifiable->getEnabledChannels('server_reachable');
|
||||
}
|
||||
|
||||
public function toMail(): MailMessage
|
||||
@@ -66,10 +50,28 @@ class Reachable extends CustomEmailNotification
|
||||
);
|
||||
}
|
||||
|
||||
public function toPushover(): PushoverMessage
|
||||
{
|
||||
return new PushoverMessage(
|
||||
title: 'Server revived',
|
||||
message: "Server '{$this->server->name}' revived. All automations & integrations are turned on again!",
|
||||
level: 'success',
|
||||
);
|
||||
}
|
||||
|
||||
public function toTelegram(): array
|
||||
{
|
||||
return [
|
||||
'message' => "Coolify: Server '{$this->server->name}' revived. All automations & integrations are turned on again!",
|
||||
];
|
||||
}
|
||||
|
||||
public function toSlack(): SlackMessage
|
||||
{
|
||||
return new SlackMessage(
|
||||
title: 'Server revived',
|
||||
description: "Server '{$this->server->name}' revived.\nAll automations & integrations are turned on again!",
|
||||
color: SlackMessage::successColor()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user