Merge branch 'next' into fix-environement-route
This commit is contained in:
@@ -3,7 +3,6 @@
|
|||||||
/public/build
|
/public/build
|
||||||
/public/hot
|
/public/hot
|
||||||
/public/storage
|
/public/storage
|
||||||
/storage/*.key
|
|
||||||
/vendor
|
/vendor
|
||||||
.env
|
.env
|
||||||
.env.backup
|
.env.backup
|
||||||
@@ -25,3 +24,15 @@ yarn-error.log
|
|||||||
.ignition.json
|
.ignition.json
|
||||||
.env.dusk.local
|
.env.dusk.local
|
||||||
docker/coolify-realtime/node_modules
|
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
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: docker/prod/Dockerfile
|
file: docker/production/Dockerfile
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
@@ -82,7 +82,7 @@ jobs:
|
|||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: docker/prod/Dockerfile
|
file: docker/production/Dockerfile
|
||||||
platforms: linux/aarch64
|
platforms: linux/aarch64
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
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
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: docker/prod/Dockerfile
|
file: docker/production/Dockerfile
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
@@ -75,7 +75,7 @@ jobs:
|
|||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: docker/prod/Dockerfile
|
file: docker/production/Dockerfile
|
||||||
platforms: linux/aarch64
|
platforms: linux/aarch64
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
|
18
README.md
18
README.md
@@ -40,7 +40,8 @@ Special thanks to our biggest sponsors!
|
|||||||
|
|
||||||
### Special Sponsors
|
### Special Sponsors
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
* [CCCareers](https://cccareers.org/) - A career development platform connecting coding bootcamp graduates with job opportunities in the tech industry.
|
* [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.
|
* [Hetzner](http://htznr.li/CoolifyXHetzner) - A German web hosting company offering affordable dedicated servers, cloud services, and web hosting solutions.
|
||||||
@@ -50,8 +51,11 @@ Special thanks to our biggest sponsors!
|
|||||||
* [QuantCDN](https://www.quantcdn.io/?ref=coolify.io) - A content delivery network (CDN) optimizing website performance through global content distribution.
|
* [QuantCDN](https://www.quantcdn.io/?ref=coolify.io) - A content delivery network (CDN) optimizing website performance through global content distribution.
|
||||||
* [Arcjet](https://arcjet.com/?ref=coolify.io) - A cloud-based platform providing real-time protection against API abuse and bot attacks.
|
* [Arcjet](https://arcjet.com/?ref=coolify.io) - A cloud-based platform providing real-time protection against API abuse and bot attacks.
|
||||||
* [SupaGuide](https://supa.guide/?ref=coolify.io) - A comprehensive resource hub offering guides and tutorials for web development using Supabase.
|
* [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.
|
* [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.
|
* [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.
|
* [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.
|
* [Brand Dev](https://brand.dev/?ref=coolify.io) - A web development agency specializing in creating custom digital experiences and brand identities.
|
||||||
@@ -90,8 +94,10 @@ Special thanks to our biggest sponsors!
|
|||||||
<a href="https://github.com/urtho"><img src="https://github.com/urtho.png" width="60px" alt="Paweł Pierścionek" /></a>
|
<a href="https://github.com/urtho"><img src="https://github.com/urtho.png" width="60px" alt="Paweł Pierścionek" /></a>
|
||||||
<a href="https://github.com/monocursive"><img src="https://github.com/monocursive.png" width="60px" alt="Michael Mazurczak" /></a>
|
<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://formbricks.com/?utm_source=coolify.io"><img src="https://github.com/formbricks.png" width="60px" alt="Formbricks" /></a>
|
||||||
<a href="https://x.com/adithsuhas17?utm_source=coolify.io"><img src="https://github.com/adith-suhas-sv.png" width="60px" alt="Adith Suhas" /></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://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://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://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>
|
<a href="https://evercam.io/?utm_source=coolify.io"><img src="https://github.com/evercam.png" width="60px" alt="Evercam" /></a>
|
||||||
@@ -147,10 +153,10 @@ By subscribing to the cloud version, you get the Coolify server for the same pri
|
|||||||
|
|
||||||
# Core Maintainers
|
# Core Maintainers
|
||||||
|
|
||||||
| Andras Bacsai | Peak |
|
| Andras Bacsai | 🏔️ Peak |
|
||||||
|------------|------------|
|
|------------|------------|
|
||||||
| <img src="https://github.com/andrasbacsai.png" width="200px" alt="Andras Bacsai" /> | <img src="https://github.com/peaklabs-dev.png" width="200px" alt="Peak Labs" /> |
|
| <img src="https://github.com/andrasbacsai.png" width="200px" alt="Andras Bacsai" /> | <img src="https://github.com/peaklabs-dev.png" width="200px" alt="peaklabs-dev" /> |
|
||||||
| <a href="https://x.com/heyandras"><img src="https://raw.githubusercontent.com/gauravghongde/social-icons/master/SVG/Color/Twitter.svg" width="25px"></a> <a href="https://github.com/andrasbacsai"><img src="https://raw.githubusercontent.com/gauravghongde/social-icons/master/SVG/Color/Github.svg" width="25px"></a> | <a href="https://x.com/peaklabs_dev"><img src="https://raw.githubusercontent.com/gauravghongde/social-icons/master/SVG/Color/Twitter.svg" width="25px"></a> <a href="https://github.com/peaklabs-dev"><img src="https://raw.githubusercontent.com/gauravghongde/social-icons/master/SVG/Color/Github.svg" width="25px"></a> |
|
| <a href="https://github.com/andrasbacsai"><img src="https://api.iconify.design/devicon:github.svg" width="25px"></a> <a href="https://x.com/heyandras"><img src="https://api.iconify.design/devicon:twitter.svg" width="25px"></a> <a href="https://bsky.app/profile/heyandras.dev"><img src="https://api.iconify.design/simple-icons:bluesky.svg" width="25px"></a> | <a href="https://github.com/peaklabs-dev"><img src="https://api.iconify.design/devicon:github.svg" width="25px"></a> <a href="https://x.com/peaklabs_dev"><img src="https://api.iconify.design/devicon:twitter.svg" width="25px"></a> <a href="https://bsky.app/profile/peaklabs.dev"><img src="https://api.iconify.design/simple-icons:bluesky.svg" width="25px"></a> |
|
||||||
|
|
||||||
# Repo Activity
|
# Repo Activity
|
||||||
|
|
||||||
|
@@ -24,7 +24,7 @@ class StartClickhouse
|
|||||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo 'Starting {$database->name}.'",
|
"echo 'Starting database.'",
|
||||||
"mkdir -p $this->configuration_dir",
|
"mkdir -p $this->configuration_dir",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@@ -26,7 +26,7 @@ class StartDragonfly
|
|||||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo 'Starting {$database->name}.'",
|
"echo 'Starting database.'",
|
||||||
"mkdir -p $this->configuration_dir",
|
"mkdir -p $this->configuration_dir",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@@ -27,7 +27,7 @@ class StartKeydb
|
|||||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo 'Starting {$database->name}.'",
|
"echo 'Starting database.'",
|
||||||
"mkdir -p $this->configuration_dir",
|
"mkdir -p $this->configuration_dir",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@@ -24,7 +24,7 @@ class StartMariadb
|
|||||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo 'Starting {$database->name}.'",
|
"echo 'Starting database.'",
|
||||||
"mkdir -p $this->configuration_dir",
|
"mkdir -p $this->configuration_dir",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@@ -30,7 +30,7 @@ class StartMongodb
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo 'Starting {$database->name}.'",
|
"echo 'Starting database.'",
|
||||||
"mkdir -p $this->configuration_dir",
|
"mkdir -p $this->configuration_dir",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@@ -24,7 +24,7 @@ class StartMysql
|
|||||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo 'Starting {$database->name}.'",
|
"echo 'Starting database.'",
|
||||||
"mkdir -p $this->configuration_dir",
|
"mkdir -p $this->configuration_dir",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@@ -25,7 +25,7 @@ class StartPostgresql
|
|||||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo 'Starting {$database->name}.'",
|
"echo 'Starting database.'",
|
||||||
"mkdir -p $this->configuration_dir",
|
"mkdir -p $this->configuration_dir",
|
||||||
"mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d/",
|
"mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d/",
|
||||||
];
|
];
|
||||||
|
@@ -25,7 +25,7 @@ class StartRedis
|
|||||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo 'Starting {$database->name}.'",
|
"echo 'Starting database.'",
|
||||||
"mkdir -p $this->configuration_dir",
|
"mkdir -p $this->configuration_dir",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@@ -179,7 +179,7 @@ class GetContainersStatus
|
|||||||
})->first();
|
})->first();
|
||||||
if (! $foundTcpProxy) {
|
if (! $foundTcpProxy) {
|
||||||
StartDatabaseProxy::run($database);
|
StartDatabaseProxy::run($database);
|
||||||
// $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
|
// $this->server->team?->notify(new ContainerRestarted("TCP Proxy for database", $this->server));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Actions\Proxy;
|
namespace App\Actions\Proxy;
|
||||||
|
|
||||||
|
use App\Enums\ProxyTypes;
|
||||||
use App\Events\ProxyStarted;
|
use App\Events\ProxyStarted;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
@@ -37,11 +38,16 @@ class StartProxy
|
|||||||
"echo 'Successfully started coolify-proxy.'",
|
"echo 'Successfully started coolify-proxy.'",
|
||||||
]);
|
]);
|
||||||
} else {
|
} 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([
|
$commands = $commands->merge([
|
||||||
"mkdir -p $proxy_path/dynamic",
|
"mkdir -p $proxy_path/dynamic",
|
||||||
"cd $proxy_path",
|
"cd $proxy_path",
|
||||||
"echo '$caddfile' > $proxy_path/dynamic/Caddyfile",
|
"echo '$caddyfile' > $proxy_path/dynamic/Caddyfile",
|
||||||
"echo 'Creating required Docker Compose file.'",
|
"echo 'Creating required Docker Compose file.'",
|
||||||
"echo 'Pulling docker image.'",
|
"echo 'Pulling docker image.'",
|
||||||
'docker compose pull',
|
'docker compose pull',
|
||||||
|
@@ -51,7 +51,6 @@ class ServerCheck
|
|||||||
|
|
||||||
$containerReplicates = null;
|
$containerReplicates = null;
|
||||||
$this->isSentinel = true;
|
$this->isSentinel = true;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
['containers' => $this->containers, 'containerReplicates' => $containerReplicates] = $this->server->getContainers();
|
['containers' => $this->containers, 'containerReplicates' => $containerReplicates] = $this->server->getContainers();
|
||||||
// ServerStorageCheckJob::dispatch($this->server);
|
// ServerStorageCheckJob::dispatch($this->server);
|
||||||
@@ -148,7 +147,6 @@ class ServerCheck
|
|||||||
} else {
|
} else {
|
||||||
$labels = Arr::undot(data_get($container, 'Config.Labels'));
|
$labels = Arr::undot(data_get($container, 'Config.Labels'));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
$managed = data_get($labels, 'coolify.managed');
|
$managed = data_get($labels, 'coolify.managed');
|
||||||
if (! $managed) {
|
if (! $managed) {
|
||||||
@@ -259,7 +257,7 @@ class ServerCheck
|
|||||||
})->first();
|
})->first();
|
||||||
if (! $foundTcpProxy) {
|
if (! $foundTcpProxy) {
|
||||||
StartDatabaseProxy::run($database);
|
StartDatabaseProxy::run($database);
|
||||||
// $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
|
// $this->server->team?->notify(new ContainerRestarted("TCP Proxy for database", $this->server));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -31,7 +31,7 @@ class UpdateCoolify
|
|||||||
}
|
}
|
||||||
CleanupDocker::dispatch($this->server);
|
CleanupDocker::dispatch($this->server);
|
||||||
$this->latestVersion = get_latest_version_of_coolify();
|
$this->latestVersion = get_latest_version_of_coolify();
|
||||||
$this->currentVersion = config('version');
|
$this->currentVersion = config('constants.coolify.version');
|
||||||
if (! $manual_update) {
|
if (! $manual_update) {
|
||||||
if (! $settings->is_auto_update_enabled) {
|
if (! $settings->is_auto_update_enabled) {
|
||||||
return;
|
return;
|
||||||
|
@@ -18,7 +18,6 @@ class CleanupUnreachableServers extends Command
|
|||||||
if ($servers->count() > 0) {
|
if ($servers->count() > 0) {
|
||||||
foreach ($servers as $server) {
|
foreach ($servers as $server) {
|
||||||
echo "Cleanup unreachable server ($server->id) with name $server->name";
|
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([
|
$server->update([
|
||||||
'ip' => '1.2.3.4',
|
'ip' => '1.2.3.4',
|
||||||
]);
|
]);
|
||||||
|
@@ -36,7 +36,7 @@ class CloudCleanupSubscriptions extends Command
|
|||||||
}
|
}
|
||||||
// If the team has no subscription id and the invoice is paid, we need to reset the invoice paid status
|
// If the team has no subscription id and the invoice is paid, we need to reset the invoice paid status
|
||||||
if (! (data_get($team, 'subscription.stripe_subscription_id'))) {
|
if (! (data_get($team, 'subscription.stripe_subscription_id'))) {
|
||||||
$this->info("Resetting invoice paid status for team {$team->id} {$team->name}");
|
$this->info("Resetting invoice paid status for team {$team->id}");
|
||||||
|
|
||||||
$team->subscription->update([
|
$team->subscription->update([
|
||||||
'stripe_invoice_paid' => false,
|
'stripe_invoice_paid' => false,
|
||||||
@@ -61,9 +61,9 @@ class CloudCleanupSubscriptions extends Command
|
|||||||
$this->info('Subscription id: '.data_get($team, 'subscription.stripe_subscription_id'));
|
$this->info('Subscription id: '.data_get($team, 'subscription.stripe_subscription_id'));
|
||||||
$confirm = $this->confirm('Do you want to cancel the subscription?', true);
|
$confirm = $this->confirm('Do you want to cancel the subscription?', true);
|
||||||
if (! $confirm) {
|
if (! $confirm) {
|
||||||
$this->info("Skipping team {$team->id} {$team->name}");
|
$this->info("Skipping team {$team->id}");
|
||||||
} else {
|
} else {
|
||||||
$this->info("Cancelling subscription for team {$team->id} {$team->name}");
|
$this->info("Cancelling subscription for team {$team->id}");
|
||||||
$team->subscription->update([
|
$team->subscription->update([
|
||||||
'stripe_invoice_paid' => false,
|
'stripe_invoice_paid' => false,
|
||||||
'stripe_trial_already_ended' => false,
|
'stripe_trial_already_ended' => false,
|
||||||
|
@@ -76,7 +76,5 @@ class Dev extends Command
|
|||||||
} else {
|
} else {
|
||||||
echo "Instance already initialized.\n";
|
echo "Instance already initialized.\n";
|
||||||
}
|
}
|
||||||
// Set permissions
|
|
||||||
Process::run(['chmod', '-R', 'o+rwx', '.']);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,14 +2,12 @@
|
|||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
use App\Jobs\SendConfirmationForWaitlistJob;
|
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Models\ApplicationPreview;
|
use App\Models\ApplicationPreview;
|
||||||
use App\Models\ScheduledDatabaseBackup;
|
use App\Models\ScheduledDatabaseBackup;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
use App\Models\Team;
|
use App\Models\Team;
|
||||||
use App\Models\Waitlist;
|
|
||||||
use App\Notifications\Application\DeploymentFailed;
|
use App\Notifications\Application\DeploymentFailed;
|
||||||
use App\Notifications\Application\DeploymentSuccess;
|
use App\Notifications\Application\DeploymentSuccess;
|
||||||
use App\Notifications\Application\StatusChanged;
|
use App\Notifications\Application\StatusChanged;
|
||||||
@@ -64,8 +62,6 @@ class Emails extends Command
|
|||||||
'backup-success' => 'Database - Backup Success',
|
'backup-success' => 'Database - Backup Success',
|
||||||
'backup-failed' => 'Database - Backup Failed',
|
'backup-failed' => 'Database - Backup Failed',
|
||||||
// 'invitation-link' => 'Invitation Link',
|
// '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-before-trial' => 'REAL - Registered Users Before Trial without Subscription',
|
||||||
'realusers-server-lost-connection' => 'REAL - Server Lost Connection',
|
'realusers-server-lost-connection' => 'REAL - Server Lost Connection',
|
||||||
],
|
],
|
||||||
@@ -187,7 +183,7 @@ class Emails extends Command
|
|||||||
'team_id' => 0,
|
'team_id' => 0,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
$this->mail = (new BackupSuccess($backup, $db))->toMail();
|
//$this->mail = (new BackupSuccess($backup->frequency, $db->name))->toMail();
|
||||||
$this->sendEmail();
|
$this->sendEmail();
|
||||||
break;
|
break;
|
||||||
// case 'invitation-link':
|
// case 'invitation-link':
|
||||||
@@ -204,23 +200,6 @@ class Emails extends Command
|
|||||||
// $this->mail = (new InvitationLink($user))->toMail();
|
// $this->mail = (new InvitationLink($user))->toMail();
|
||||||
// $this->sendEmail();
|
// $this->sendEmail();
|
||||||
// break;
|
// 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':
|
case 'realusers-before-trial':
|
||||||
$this->mail = new MailMessage;
|
$this->mail = new MailMessage;
|
||||||
$this->mail->view('emails.before-trial-conversion');
|
$this->mail->view('emails.before-trial-conversion');
|
||||||
|
@@ -13,7 +13,7 @@ class Horizon extends Command
|
|||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
if (config('constants.horizon.is_horizon_enabled')) {
|
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');
|
$this->call('horizon');
|
||||||
exit(0);
|
exit(0);
|
||||||
} else {
|
} else {
|
||||||
|
@@ -55,10 +55,8 @@ class Init extends Command
|
|||||||
} else {
|
} else {
|
||||||
$this->cleanup_in_progress_application_deployments();
|
$this->cleanup_in_progress_application_deployments();
|
||||||
}
|
}
|
||||||
echo "[3]: Cleanup Redis keys.\n";
|
|
||||||
$this->call('cleanup:redis');
|
$this->call('cleanup:redis');
|
||||||
|
|
||||||
echo "[4]: Cleanup stucked resources.\n";
|
|
||||||
$this->call('cleanup:stucked-resources');
|
$this->call('cleanup:stucked-resources');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -114,7 +112,6 @@ class Init extends Command
|
|||||||
|
|
||||||
private function optimize()
|
private function optimize()
|
||||||
{
|
{
|
||||||
echo "[1]: Optimizing Laravel (caching config, routes, views).\n";
|
|
||||||
Artisan::call('optimize:clear');
|
Artisan::call('optimize:clear');
|
||||||
Artisan::call('optimize');
|
Artisan::call('optimize');
|
||||||
}
|
}
|
||||||
@@ -189,7 +186,6 @@ class Init extends Command
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($commands->isNotEmpty()) {
|
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);
|
remote_process(command: $commands, type: ActivityTypes::INLINE->value, server: $server, ignore_errors: false);
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
@@ -200,7 +196,7 @@ class Init extends Command
|
|||||||
|
|
||||||
private function restore_coolify_db_backup()
|
private function restore_coolify_db_backup()
|
||||||
{
|
{
|
||||||
if (version_compare('4.0.0-beta.179', config('version'), '<=')) {
|
if (version_compare('4.0.0-beta.179', config('constants.coolify.version'), '<=')) {
|
||||||
try {
|
try {
|
||||||
$database = StandalonePostgresql::withTrashed()->find(0);
|
$database = StandalonePostgresql::withTrashed()->find(0);
|
||||||
if ($database && $database->trashed()) {
|
if ($database && $database->trashed()) {
|
||||||
@@ -228,19 +224,18 @@ class Init extends Command
|
|||||||
private function send_alive_signal()
|
private function send_alive_signal()
|
||||||
{
|
{
|
||||||
$id = config('app.id');
|
$id = config('app.id');
|
||||||
$version = config('version');
|
$version = config('constants.coolify.version');
|
||||||
$settings = instanceSettings();
|
$settings = instanceSettings();
|
||||||
$do_not_track = data_get($settings, 'do_not_track');
|
$do_not_track = data_get($settings, 'do_not_track');
|
||||||
if ($do_not_track == true) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
Http::get("https://undead.coolify.io/v4/alive?appId=$id&version=$version");
|
Http::get("https://undead.coolify.io/v4/alive?appId=$id&version=$version");
|
||||||
echo "[2]: Sending live signal!\n";
|
|
||||||
} catch (\Throwable $e) {
|
} 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();
|
$queued_inprogress_deployments = ApplicationDeploymentQueue::whereIn('status', [ApplicationDeploymentStatus::IN_PROGRESS->value, ApplicationDeploymentStatus::QUEUED->value])->get();
|
||||||
foreach ($queued_inprogress_deployments as $deployment) {
|
foreach ($queued_inprogress_deployments as $deployment) {
|
||||||
echo "Cleaning up deployment: {$deployment->id}\n";
|
|
||||||
$deployment->status = ApplicationDeploymentStatus::FAILED->value;
|
$deployment->status = ApplicationDeploymentStatus::FAILED->value;
|
||||||
$deployment->save();
|
$deployment->save();
|
||||||
}
|
}
|
||||||
@@ -264,7 +258,7 @@ class Init extends Command
|
|||||||
|
|
||||||
private function replace_slash_in_environment_name()
|
private function replace_slash_in_environment_name()
|
||||||
{
|
{
|
||||||
if (version_compare('4.0.0-beta.298', config('version'), '<=')) {
|
if (version_compare('4.0.0-beta.298', config('constants.coolify.version'), '<=')) {
|
||||||
$environments = Environment::all();
|
$environments = Environment::all();
|
||||||
foreach ($environments as $environment) {
|
foreach ($environments as $environment) {
|
||||||
if (str_contains($environment->name, '/')) {
|
if (str_contains($environment->name, '/')) {
|
||||||
|
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>
|
<div class="text-yellow-500"> Channels: </div>
|
||||||
<ul class="text-coolify">
|
<ul class="text-coolify">
|
||||||
<li>email</li>
|
<li>email</li>
|
||||||
<li>slack</li>
|
|
||||||
<li>discord</li>
|
<li>discord</li>
|
||||||
<li>telegram</li>
|
<li>telegram</li>
|
||||||
|
<li>slack</li>
|
||||||
|
<li>pushover</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -72,6 +73,6 @@ class NotifyDemo extends Command
|
|||||||
<div class="mr-1">
|
<div class="mr-1">
|
||||||
In which manner you wish a <strong class="text-coolify">coolified</strong> notification?
|
In which manner you wish a <strong class="text-coolify">coolified</strong> notification?
|
||||||
</div>
|
</div>
|
||||||
HTML, ['email', 'slack', 'discord', 'telegram']);
|
HTML, ['email', 'discord', 'telegram', 'slack', 'pushover']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -13,7 +13,7 @@ class Scheduler extends Command
|
|||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
if (config('constants.horizon.is_scheduler_enabled')) {
|
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');
|
$this->call('schedule:work');
|
||||||
exit(0);
|
exit(0);
|
||||||
} else {
|
} 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -20,7 +20,10 @@ class ServicesGenerate extends Command
|
|||||||
|
|
||||||
public function handle(): int
|
public function handle(): int
|
||||||
{
|
{
|
||||||
$serviceTemplatesJson = collect(glob(base_path('templates/compose/*.yaml')))
|
$serviceTemplatesJson = collect(array_merge(
|
||||||
|
glob(base_path('templates/compose/*.yaml')),
|
||||||
|
glob(base_path('templates/compose/*.yml'))
|
||||||
|
))
|
||||||
->mapWithKeys(function ($file): array {
|
->mapWithKeys(function ($file): array {
|
||||||
$file = basename($file);
|
$file = basename($file);
|
||||||
$parsed = $this->processFile($file);
|
$parsed = $this->processFile($file);
|
||||||
@@ -68,7 +71,7 @@ class ServicesGenerate extends Command
|
|||||||
'slogan' => $data->get('slogan', str($file)->headline()),
|
'slogan' => $data->get('slogan', str($file)->headline()),
|
||||||
'compose' => $compose,
|
'compose' => $compose,
|
||||||
'tags' => $tags,
|
'tags' => $tags,
|
||||||
'logo' => $data->get('logo', 'svgs/coolify.png'),
|
'logo' => $data->get('logo', 'svgs/default.webp'),
|
||||||
'minversion' => $data->get('minversion', '0.0.0'),
|
'minversion' => $data->get('minversion', '0.0.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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -50,7 +50,7 @@ class Kernel extends ConsoleKernel
|
|||||||
$this->instanceTimezone = config('app.timezone');
|
$this->instanceTimezone = config('app.timezone');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->scheduleInstance->job(new CleanupStaleMultiplexedConnections)->hourly();
|
// $this->scheduleInstance->job(new CleanupStaleMultiplexedConnections)->hourly();
|
||||||
|
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
// Instance Jobs
|
// Instance Jobs
|
||||||
@@ -132,7 +132,7 @@ class Kernel extends ConsoleKernel
|
|||||||
}
|
}
|
||||||
|
|
||||||
foreach ($servers as $server) {
|
foreach ($servers as $server) {
|
||||||
$serverTimezone = $server->settings->server_timezone;
|
$serverTimezone = data_get($server->settings, 'server_timezone', $this->instanceTimezone);
|
||||||
|
|
||||||
// Sentinel check
|
// Sentinel check
|
||||||
$lastSentinelUpdate = $server->sentinel_updated_at;
|
$lastSentinelUpdate = $server->sentinel_updated_at;
|
||||||
@@ -141,8 +141,12 @@ class Kernel extends ConsoleKernel
|
|||||||
if (validate_timezone($serverTimezone) === false) {
|
if (validate_timezone($serverTimezone) === false) {
|
||||||
$serverTimezone = config('app.timezone');
|
$serverTimezone = config('app.timezone');
|
||||||
}
|
}
|
||||||
$this->scheduleInstance->job(new ServerCheckJob($server))->timezone($serverTimezone)->everyMinute()->onOneServer();
|
if (isCloud()) {
|
||||||
// $this->scheduleInstance->job(new \App\Jobs\ServerCheckNewJob($server))->everyMinute()->onOneServer();
|
$this->scheduleInstance->job(new ServerCheckJob($server))->timezone($serverTimezone)->everyFiveMinutes()->onOneServer();
|
||||||
|
} else {
|
||||||
|
$this->scheduleInstance->job(new ServerCheckJob($server))->timezone($serverTimezone)->everyMinute()->onOneServer();
|
||||||
|
}
|
||||||
|
// $this->scheduleInstance->job(new \App\Jobs\ServerCheckNewJob($server))->everyFiveMinutes()->onOneServer();
|
||||||
|
|
||||||
// Check storage usage every 10 minutes if Sentinel does not activated
|
// Check storage usage every 10 minutes if Sentinel does not activated
|
||||||
$this->scheduleInstance->job(new ServerStorageCheckJob($server))->everyTenMinutes()->onOneServer();
|
$this->scheduleInstance->job(new ServerStorageCheckJob($server))->everyTenMinutes()->onOneServer();
|
||||||
@@ -154,7 +158,7 @@ class Kernel extends ConsoleKernel
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup multiplexed connections every hour
|
// Cleanup multiplexed connections every hour
|
||||||
$this->scheduleInstance->job(new ServerCleanupMux($server))->hourly()->onOneServer();
|
// $this->scheduleInstance->job(new ServerCleanupMux($server))->hourly()->onOneServer();
|
||||||
|
|
||||||
// Temporary solution until we have better memory management for Sentinel
|
// Temporary solution until we have better memory management for Sentinel
|
||||||
if ($server->isSentinelEnabled()) {
|
if ($server->isSentinelEnabled()) {
|
||||||
@@ -214,7 +218,7 @@ class Kernel extends ConsoleKernel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($service) {
|
if ($service) {
|
||||||
if (str($service->status())->contains('running') === false) {
|
if (str($service->status)->contains('running') === false) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -18,7 +18,7 @@ class DatabaseProxyStopped implements ShouldBroadcast
|
|||||||
public function __construct($teamId = null)
|
public function __construct($teamId = null)
|
||||||
{
|
{
|
||||||
if (is_null($teamId)) {
|
if (is_null($teamId)) {
|
||||||
$teamId = Auth::user()->currentTeam()->id ?? null;
|
$teamId = Auth::user()?->currentTeam()?->id ?? null;
|
||||||
}
|
}
|
||||||
if (is_null($teamId)) {
|
if (is_null($teamId)) {
|
||||||
throw new \Exception('Team id is null');
|
throw new \Exception('Team id is null');
|
||||||
|
@@ -25,26 +25,27 @@ class ApplicationsController extends Controller
|
|||||||
{
|
{
|
||||||
private function removeSensitiveData($application)
|
private function removeSensitiveData($application)
|
||||||
{
|
{
|
||||||
$token = auth()->user()->currentAccessToken();
|
|
||||||
$application->makeHidden([
|
$application->makeHidden([
|
||||||
'id',
|
'id',
|
||||||
|
'resourceable',
|
||||||
|
'resourceable_id',
|
||||||
|
'resourceable_type',
|
||||||
]);
|
]);
|
||||||
if ($token->can('view:sensitive')) {
|
if (request()->attributes->get('can_read_sensitive', false) === false) {
|
||||||
return serializeApiResponse($application);
|
$application->makeHidden([
|
||||||
|
'custom_labels',
|
||||||
|
'dockerfile',
|
||||||
|
'docker_compose',
|
||||||
|
'docker_compose_raw',
|
||||||
|
'manual_webhook_secret_bitbucket',
|
||||||
|
'manual_webhook_secret_gitea',
|
||||||
|
'manual_webhook_secret_github',
|
||||||
|
'manual_webhook_secret_gitlab',
|
||||||
|
'private_key_id',
|
||||||
|
'value',
|
||||||
|
'real_value',
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
$application->makeHidden([
|
|
||||||
'custom_labels',
|
|
||||||
'dockerfile',
|
|
||||||
'docker_compose',
|
|
||||||
'docker_compose_raw',
|
|
||||||
'manual_webhook_secret_bitbucket',
|
|
||||||
'manual_webhook_secret_gitea',
|
|
||||||
'manual_webhook_secret_github',
|
|
||||||
'manual_webhook_secret_gitlab',
|
|
||||||
'private_key_id',
|
|
||||||
'value',
|
|
||||||
'real_value',
|
|
||||||
]);
|
|
||||||
|
|
||||||
return serializeApiResponse($application);
|
return serializeApiResponse($application);
|
||||||
}
|
}
|
||||||
@@ -70,7 +71,8 @@ class ApplicationsController extends Controller
|
|||||||
items: new OA\Items(ref: '#/components/schemas/Application')
|
items: new OA\Items(ref: '#/components/schemas/Application')
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
]),
|
]
|
||||||
|
),
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 401,
|
response: 401,
|
||||||
ref: '#/components/responses/401',
|
ref: '#/components/responses/401',
|
||||||
@@ -180,8 +182,10 @@ class ApplicationsController extends Controller
|
|||||||
'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
|
'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
|
||||||
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
|
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
|
||||||
],
|
],
|
||||||
)),
|
)
|
||||||
]),
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
responses: [
|
responses: [
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 200,
|
response: 200,
|
||||||
@@ -284,8 +288,10 @@ class ApplicationsController extends Controller
|
|||||||
'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
|
'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
|
||||||
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
|
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
|
||||||
],
|
],
|
||||||
)),
|
)
|
||||||
]),
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
responses: [
|
responses: [
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 200,
|
response: 200,
|
||||||
@@ -388,8 +394,10 @@ class ApplicationsController extends Controller
|
|||||||
'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
|
'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
|
||||||
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
|
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
|
||||||
],
|
],
|
||||||
)),
|
)
|
||||||
]),
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
responses: [
|
responses: [
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 200,
|
response: 200,
|
||||||
@@ -476,8 +484,10 @@ class ApplicationsController extends Controller
|
|||||||
'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'],
|
'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'],
|
||||||
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
|
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
|
||||||
],
|
],
|
||||||
)),
|
)
|
||||||
]),
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
responses: [
|
responses: [
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 200,
|
response: 200,
|
||||||
@@ -561,8 +571,10 @@ class ApplicationsController extends Controller
|
|||||||
'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'],
|
'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'],
|
||||||
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
|
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
|
||||||
],
|
],
|
||||||
)),
|
)
|
||||||
]),
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
responses: [
|
responses: [
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 200,
|
response: 200,
|
||||||
@@ -612,8 +624,10 @@ class ApplicationsController extends Controller
|
|||||||
'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'],
|
'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'],
|
||||||
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
|
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
|
||||||
],
|
],
|
||||||
)),
|
)
|
||||||
]),
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
responses: [
|
responses: [
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 200,
|
response: 200,
|
||||||
@@ -1268,7 +1282,8 @@ class ApplicationsController extends Controller
|
|||||||
ref: '#/components/schemas/Application'
|
ref: '#/components/schemas/Application'
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
]),
|
]
|
||||||
|
),
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 401,
|
response: 401,
|
||||||
ref: '#/components/responses/401',
|
ref: '#/components/responses/401',
|
||||||
@@ -1340,7 +1355,8 @@ class ApplicationsController extends Controller
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
]),
|
]
|
||||||
|
),
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 401,
|
response: 401,
|
||||||
ref: '#/components/responses/401',
|
ref: '#/components/responses/401',
|
||||||
@@ -1466,8 +1482,10 @@ class ApplicationsController extends Controller
|
|||||||
'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
|
'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
|
||||||
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
|
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
|
||||||
],
|
],
|
||||||
)),
|
)
|
||||||
]),
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
responses: [
|
responses: [
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 200,
|
response: 200,
|
||||||
@@ -1482,7 +1500,8 @@ class ApplicationsController extends Controller
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
]),
|
]
|
||||||
|
),
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 401,
|
response: 401,
|
||||||
ref: '#/components/responses/401',
|
ref: '#/components/responses/401',
|
||||||
@@ -1591,16 +1610,33 @@ class ApplicationsController extends Controller
|
|||||||
}
|
}
|
||||||
$domains = $request->domains;
|
$domains = $request->domains;
|
||||||
if ($request->has('domains') && $server->isProxyShouldRun()) {
|
if ($request->has('domains') && $server->isProxyShouldRun()) {
|
||||||
$errors = [];
|
$uuid = $request->uuid;
|
||||||
$fqdn = $request->domains;
|
$fqdn = $request->domains;
|
||||||
$fqdn = str($fqdn)->replaceEnd(',', '')->trim();
|
$fqdn = str($fqdn)->replaceEnd(',', '')->trim();
|
||||||
$fqdn = str($fqdn)->replaceStart(',', '')->trim();
|
$fqdn = str($fqdn)->replaceStart(',', '')->trim();
|
||||||
$application->fqdn = $fqdn;
|
$errors = [];
|
||||||
if (! $application->settings->is_container_label_readonly_enabled) {
|
$fqdn = str($fqdn)->trim()->explode(',')->map(function ($domain) use (&$errors) {
|
||||||
$customLabels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
$domain = trim($domain);
|
||||||
$application->custom_labels = base64_encode($customLabels);
|
if (filter_var($domain, FILTER_VALIDATE_URL) === false || ! preg_match('/^https?:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,}/', $domain)) {
|
||||||
|
$errors[] = 'Invalid domain: '.$domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $domain;
|
||||||
|
});
|
||||||
|
if (count($errors) > 0) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Validation failed.',
|
||||||
|
'errors' => $errors,
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
if (checkIfDomainIsAlreadyUsed($fqdn, $teamId, $uuid)) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Validation failed.',
|
||||||
|
'errors' => [
|
||||||
|
'domains' => 'One of the domain is already used.',
|
||||||
|
],
|
||||||
|
], 422);
|
||||||
}
|
}
|
||||||
$request->offsetUnset('domains');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$dockerComposeDomainsJson = collect();
|
$dockerComposeDomainsJson = collect();
|
||||||
@@ -1690,7 +1726,8 @@ class ApplicationsController extends Controller
|
|||||||
items: new OA\Items(ref: '#/components/schemas/EnvironmentVariable')
|
items: new OA\Items(ref: '#/components/schemas/EnvironmentVariable')
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
]),
|
]
|
||||||
|
),
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 401,
|
response: 401,
|
||||||
ref: '#/components/responses/401',
|
ref: '#/components/responses/401',
|
||||||
@@ -1796,7 +1833,8 @@ class ApplicationsController extends Controller
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
]),
|
]
|
||||||
|
),
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 401,
|
response: 401,
|
||||||
ref: '#/components/responses/401',
|
ref: '#/components/responses/401',
|
||||||
@@ -1858,8 +1896,9 @@ class ApplicationsController extends Controller
|
|||||||
$is_preview = $request->is_preview ?? false;
|
$is_preview = $request->is_preview ?? false;
|
||||||
$is_build_time = $request->is_build_time ?? false;
|
$is_build_time = $request->is_build_time ?? false;
|
||||||
$is_literal = $request->is_literal ?? false;
|
$is_literal = $request->is_literal ?? false;
|
||||||
|
$key = str($request->key)->trim()->replace(' ', '_')->value;
|
||||||
if ($is_preview) {
|
if ($is_preview) {
|
||||||
$env = $application->environment_variables_preview->where('key', $request->key)->first();
|
$env = $application->environment_variables_preview->where('key', $key)->first();
|
||||||
if ($env) {
|
if ($env) {
|
||||||
$env->value = $request->value;
|
$env->value = $request->value;
|
||||||
if ($env->is_build_time != $is_build_time) {
|
if ($env->is_build_time != $is_build_time) {
|
||||||
@@ -1886,7 +1925,7 @@ class ApplicationsController extends Controller
|
|||||||
], 404);
|
], 404);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$env = $application->environment_variables->where('key', $request->key)->first();
|
$env = $application->environment_variables->where('key', $key)->first();
|
||||||
if ($env) {
|
if ($env) {
|
||||||
$env->value = $request->value;
|
$env->value = $request->value;
|
||||||
if ($env->is_build_time != $is_build_time) {
|
if ($env->is_build_time != $is_build_time) {
|
||||||
@@ -1984,7 +2023,8 @@ class ApplicationsController extends Controller
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
]),
|
]
|
||||||
|
),
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 401,
|
response: 401,
|
||||||
ref: '#/components/responses/401',
|
ref: '#/components/responses/401',
|
||||||
@@ -2028,6 +2068,7 @@ class ApplicationsController extends Controller
|
|||||||
$bulk_data = collect($bulk_data)->map(function ($item) {
|
$bulk_data = collect($bulk_data)->map(function ($item) {
|
||||||
return collect($item)->only(['key', 'value', 'is_preview', 'is_build_time', 'is_literal']);
|
return collect($item)->only(['key', 'value', 'is_preview', 'is_build_time', 'is_literal']);
|
||||||
});
|
});
|
||||||
|
$returnedEnvs = collect();
|
||||||
foreach ($bulk_data as $item) {
|
foreach ($bulk_data as $item) {
|
||||||
$validator = customApiValidator($item, [
|
$validator = customApiValidator($item, [
|
||||||
'key' => 'string|required',
|
'key' => 'string|required',
|
||||||
@@ -2049,8 +2090,9 @@ class ApplicationsController extends Controller
|
|||||||
$is_literal = $item->get('is_literal') ?? false;
|
$is_literal = $item->get('is_literal') ?? false;
|
||||||
$is_multi_line = $item->get('is_multiline') ?? false;
|
$is_multi_line = $item->get('is_multiline') ?? false;
|
||||||
$is_shown_once = $item->get('is_shown_once') ?? false;
|
$is_shown_once = $item->get('is_shown_once') ?? false;
|
||||||
|
$key = str($item->get('key'))->trim()->replace(' ', '_')->value;
|
||||||
if ($is_preview) {
|
if ($is_preview) {
|
||||||
$env = $application->environment_variables_preview->where('key', $item->get('key'))->first();
|
$env = $application->environment_variables_preview->where('key', $key)->first();
|
||||||
if ($env) {
|
if ($env) {
|
||||||
$env->value = $item->get('value');
|
$env->value = $item->get('value');
|
||||||
if ($env->is_build_time != $is_build_time) {
|
if ($env->is_build_time != $is_build_time) {
|
||||||
@@ -2075,10 +2117,12 @@ class ApplicationsController extends Controller
|
|||||||
'is_literal' => $is_literal,
|
'is_literal' => $is_literal,
|
||||||
'is_multiline' => $is_multi_line,
|
'is_multiline' => $is_multi_line,
|
||||||
'is_shown_once' => $is_shown_once,
|
'is_shown_once' => $is_shown_once,
|
||||||
|
'resourceable_type' => get_class($application),
|
||||||
|
'resourceable_id' => $application->id,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$env = $application->environment_variables->where('key', $item->get('key'))->first();
|
$env = $application->environment_variables->where('key', $key)->first();
|
||||||
if ($env) {
|
if ($env) {
|
||||||
$env->value = $item->get('value');
|
$env->value = $item->get('value');
|
||||||
if ($env->is_build_time != $is_build_time) {
|
if ($env->is_build_time != $is_build_time) {
|
||||||
@@ -2103,12 +2147,15 @@ class ApplicationsController extends Controller
|
|||||||
'is_literal' => $is_literal,
|
'is_literal' => $is_literal,
|
||||||
'is_multiline' => $is_multi_line,
|
'is_multiline' => $is_multi_line,
|
||||||
'is_shown_once' => $is_shown_once,
|
'is_shown_once' => $is_shown_once,
|
||||||
|
'resourceable_type' => get_class($application),
|
||||||
|
'resourceable_id' => $application->id,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$returnedEnvs->push($this->removeSensitiveData($env));
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json($this->removeSensitiveData($env))->setStatusCode(201);
|
return response()->json($returnedEnvs)->setStatusCode(201);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[OA\Post(
|
#[OA\Post(
|
||||||
@@ -2165,7 +2212,8 @@ class ApplicationsController extends Controller
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
]),
|
]
|
||||||
|
),
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 401,
|
response: 401,
|
||||||
ref: '#/components/responses/401',
|
ref: '#/components/responses/401',
|
||||||
@@ -2220,8 +2268,10 @@ class ApplicationsController extends Controller
|
|||||||
], 422);
|
], 422);
|
||||||
}
|
}
|
||||||
$is_preview = $request->is_preview ?? false;
|
$is_preview = $request->is_preview ?? false;
|
||||||
|
$key = str($request->key)->trim()->replace(' ', '_')->value;
|
||||||
|
|
||||||
if ($is_preview) {
|
if ($is_preview) {
|
||||||
$env = $application->environment_variables_preview->where('key', $request->key)->first();
|
$env = $application->environment_variables_preview->where('key', $key)->first();
|
||||||
if ($env) {
|
if ($env) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => 'Environment variable already exists. Use PATCH request to update it.',
|
'message' => 'Environment variable already exists. Use PATCH request to update it.',
|
||||||
@@ -2235,6 +2285,8 @@ class ApplicationsController extends Controller
|
|||||||
'is_literal' => $request->is_literal ?? false,
|
'is_literal' => $request->is_literal ?? false,
|
||||||
'is_multiline' => $request->is_multiline ?? false,
|
'is_multiline' => $request->is_multiline ?? false,
|
||||||
'is_shown_once' => $request->is_shown_once ?? false,
|
'is_shown_once' => $request->is_shown_once ?? false,
|
||||||
|
'resourceable_type' => get_class($application),
|
||||||
|
'resourceable_id' => $application->id,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
@@ -2242,7 +2294,7 @@ class ApplicationsController extends Controller
|
|||||||
])->setStatusCode(201);
|
])->setStatusCode(201);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$env = $application->environment_variables->where('key', $request->key)->first();
|
$env = $application->environment_variables->where('key', $key)->first();
|
||||||
if ($env) {
|
if ($env) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => 'Environment variable already exists. Use PATCH request to update it.',
|
'message' => 'Environment variable already exists. Use PATCH request to update it.',
|
||||||
@@ -2256,6 +2308,8 @@ class ApplicationsController extends Controller
|
|||||||
'is_literal' => $request->is_literal ?? false,
|
'is_literal' => $request->is_literal ?? false,
|
||||||
'is_multiline' => $request->is_multiline ?? false,
|
'is_multiline' => $request->is_multiline ?? false,
|
||||||
'is_shown_once' => $request->is_shown_once ?? false,
|
'is_shown_once' => $request->is_shown_once ?? false,
|
||||||
|
'resourceable_type' => get_class($application),
|
||||||
|
'resourceable_id' => $application->id,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
@@ -2314,7 +2368,8 @@ class ApplicationsController extends Controller
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
]),
|
]
|
||||||
|
),
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 401,
|
response: 401,
|
||||||
ref: '#/components/responses/401',
|
ref: '#/components/responses/401',
|
||||||
@@ -2342,7 +2397,10 @@ class ApplicationsController extends Controller
|
|||||||
'message' => 'Application not found.',
|
'message' => 'Application not found.',
|
||||||
], 404);
|
], 404);
|
||||||
}
|
}
|
||||||
$found_env = EnvironmentVariable::where('uuid', $request->env_uuid)->where('application_id', $application->id)->first();
|
$found_env = EnvironmentVariable::where('uuid', $request->env_uuid)
|
||||||
|
->where('resourceable_type', Application::class)
|
||||||
|
->where('resourceable_id', $application->id)
|
||||||
|
->first();
|
||||||
if (! $found_env) {
|
if (! $found_env) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => 'Environment variable not found.',
|
'message' => 'Environment variable not found.',
|
||||||
@@ -2406,9 +2464,11 @@ class ApplicationsController extends Controller
|
|||||||
properties: [
|
properties: [
|
||||||
'message' => ['type' => 'string', 'example' => 'Deployment request queued.', 'description' => 'Message.'],
|
'message' => ['type' => 'string', 'example' => 'Deployment request queued.', 'description' => 'Message.'],
|
||||||
'deployment_uuid' => ['type' => 'string', 'example' => 'doogksw', 'description' => 'UUID of the deployment.'],
|
'deployment_uuid' => ['type' => 'string', 'example' => 'doogksw', 'description' => 'UUID of the deployment.'],
|
||||||
])
|
]
|
||||||
|
)
|
||||||
),
|
),
|
||||||
]),
|
]
|
||||||
|
),
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 401,
|
response: 401,
|
||||||
ref: '#/components/responses/401',
|
ref: '#/components/responses/401',
|
||||||
@@ -2494,7 +2554,8 @@ class ApplicationsController extends Controller
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
]),
|
]
|
||||||
|
),
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 401,
|
response: 401,
|
||||||
ref: '#/components/responses/401',
|
ref: '#/components/responses/401',
|
||||||
@@ -2568,7 +2629,8 @@ class ApplicationsController extends Controller
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
]),
|
]
|
||||||
|
),
|
||||||
|
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 401,
|
response: 401,
|
||||||
|
@@ -19,26 +19,23 @@ class DatabasesController extends Controller
|
|||||||
{
|
{
|
||||||
private function removeSensitiveData($database)
|
private function removeSensitiveData($database)
|
||||||
{
|
{
|
||||||
$token = auth()->user()->currentAccessToken();
|
|
||||||
$database->makeHidden([
|
$database->makeHidden([
|
||||||
'id',
|
'id',
|
||||||
'laravel_through_key',
|
'laravel_through_key',
|
||||||
]);
|
]);
|
||||||
if ($token->can('view:sensitive')) {
|
if (request()->attributes->get('can_read_sensitive', false) === false) {
|
||||||
return serializeApiResponse($database);
|
$database->makeHidden([
|
||||||
|
'internal_db_url',
|
||||||
|
'external_db_url',
|
||||||
|
'postgres_password',
|
||||||
|
'dragonfly_password',
|
||||||
|
'redis_password',
|
||||||
|
'mongo_initdb_root_password',
|
||||||
|
'keydb_password',
|
||||||
|
'clickhouse_admin_password',
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$database->makeHidden([
|
|
||||||
'internal_db_url',
|
|
||||||
'external_db_url',
|
|
||||||
'postgres_password',
|
|
||||||
'dragonfly_password',
|
|
||||||
'redis_password',
|
|
||||||
'mongo_initdb_root_password',
|
|
||||||
'keydb_password',
|
|
||||||
'clickhouse_admin_password',
|
|
||||||
]);
|
|
||||||
|
|
||||||
return serializeApiResponse($database);
|
return serializeApiResponse($database);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,8 +208,9 @@ class DatabasesController extends Controller
|
|||||||
'mongo_conf' => ['type' => 'string', 'description' => 'Mongo conf'],
|
'mongo_conf' => ['type' => 'string', 'description' => 'Mongo conf'],
|
||||||
'mongo_initdb_root_username' => ['type' => 'string', 'description' => 'Mongo initdb root username'],
|
'mongo_initdb_root_username' => ['type' => 'string', 'description' => 'Mongo initdb root username'],
|
||||||
'mongo_initdb_root_password' => ['type' => 'string', 'description' => 'Mongo initdb root password'],
|
'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_root_password' => ['type' => 'string', 'description' => 'MySQL root password'],
|
||||||
|
'mysql_password' => ['type' => 'string', 'description' => 'MySQL password'],
|
||||||
'mysql_user' => ['type' => 'string', 'description' => 'MySQL user'],
|
'mysql_user' => ['type' => 'string', 'description' => 'MySQL user'],
|
||||||
'mysql_database' => ['type' => 'string', 'description' => 'MySQL database'],
|
'mysql_database' => ['type' => 'string', 'description' => 'MySQL database'],
|
||||||
'mysql_conf' => ['type' => 'string', 'description' => 'MySQL conf'],
|
'mysql_conf' => ['type' => 'string', 'description' => 'MySQL conf'],
|
||||||
@@ -241,7 +239,7 @@ class DatabasesController extends Controller
|
|||||||
)]
|
)]
|
||||||
public function update_by_uuid(Request $request)
|
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();
|
$teamId = getTeamIdFromToken();
|
||||||
if (is_null($teamId)) {
|
if (is_null($teamId)) {
|
||||||
return invalidTokenResponse();
|
return invalidTokenResponse();
|
||||||
@@ -413,12 +411,12 @@ class DatabasesController extends Controller
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'standalone-mongodb':
|
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(), [
|
$validator = customApiValidator($request->all(), [
|
||||||
'mongo_conf' => 'string',
|
'mongo_conf' => 'string',
|
||||||
'mongo_initdb_root_username' => 'string',
|
'mongo_initdb_root_username' => 'string',
|
||||||
'mongo_initdb_root_password' => 'string',
|
'mongo_initdb_root_password' => 'string',
|
||||||
'mongo_initdb_init_database' => 'string',
|
'mongo_initdb_database' => 'string',
|
||||||
]);
|
]);
|
||||||
if ($request->has('mongo_conf')) {
|
if ($request->has('mongo_conf')) {
|
||||||
if (! isBase64Encoded($request->mongo_conf)) {
|
if (! isBase64Encoded($request->mongo_conf)) {
|
||||||
@@ -443,9 +441,10 @@ class DatabasesController extends Controller
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
case 'standalone-mysql':
|
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(), [
|
$validator = customApiValidator($request->all(), [
|
||||||
'mysql_root_password' => 'string',
|
'mysql_root_password' => 'string',
|
||||||
|
'mysql_password' => 'string',
|
||||||
'mysql_user' => 'string',
|
'mysql_user' => 'string',
|
||||||
'mysql_database' => 'string',
|
'mysql_database' => 'string',
|
||||||
'mysql_conf' => 'string',
|
'mysql_conf' => 'string',
|
||||||
@@ -909,6 +908,7 @@ class DatabasesController extends Controller
|
|||||||
'environment_name' => ['type' => 'string', 'description' => 'Name of the environment'],
|
'environment_name' => ['type' => 'string', 'description' => 'Name of the environment'],
|
||||||
'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'],
|
'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_root_password' => ['type' => 'string', 'description' => 'MySQL root password'],
|
||||||
|
'mysql_password' => ['type' => 'string', 'description' => 'MySQL password'],
|
||||||
'mysql_user' => ['type' => 'string', 'description' => 'MySQL user'],
|
'mysql_user' => ['type' => 'string', 'description' => 'MySQL user'],
|
||||||
'mysql_database' => ['type' => 'string', 'description' => 'MySQL database'],
|
'mysql_database' => ['type' => 'string', 'description' => 'MySQL database'],
|
||||||
'mysql_conf' => ['type' => 'string', 'description' => 'MySQL conf'],
|
'mysql_conf' => ['type' => 'string', 'description' => 'MySQL conf'],
|
||||||
@@ -1013,7 +1013,7 @@ class DatabasesController extends Controller
|
|||||||
|
|
||||||
public function create_database(Request $request, NewDatabaseTypes $type)
|
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();
|
$teamId = getTeamIdFromToken();
|
||||||
if (is_null($teamId)) {
|
if (is_null($teamId)) {
|
||||||
@@ -1220,9 +1220,10 @@ class DatabasesController extends Controller
|
|||||||
|
|
||||||
return response()->json(serializeApiResponse($payload))->setStatusCode(201);
|
return response()->json(serializeApiResponse($payload))->setStatusCode(201);
|
||||||
} elseif ($type === NewDatabaseTypes::MYSQL) {
|
} 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(), [
|
$validator = customApiValidator($request->all(), [
|
||||||
'mysql_root_password' => 'string',
|
'mysql_root_password' => 'string',
|
||||||
|
'mysql_password' => 'string',
|
||||||
'mysql_user' => 'string',
|
'mysql_user' => 'string',
|
||||||
'mysql_database' => 'string',
|
'mysql_database' => 'string',
|
||||||
'mysql_conf' => 'string',
|
'mysql_conf' => 'string',
|
||||||
@@ -1456,12 +1457,12 @@ class DatabasesController extends Controller
|
|||||||
|
|
||||||
return response()->json(serializeApiResponse($payload))->setStatusCode(201);
|
return response()->json(serializeApiResponse($payload))->setStatusCode(201);
|
||||||
} elseif ($type === NewDatabaseTypes::MONGODB) {
|
} 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(), [
|
$validator = customApiValidator($request->all(), [
|
||||||
'mongo_conf' => 'string',
|
'mongo_conf' => 'string',
|
||||||
'mongo_initdb_root_username' => 'string',
|
'mongo_initdb_root_username' => 'string',
|
||||||
'mongo_initdb_root_password' => 'string',
|
'mongo_initdb_root_password' => 'string',
|
||||||
'mongo_initdb_init_database' => 'string',
|
'mongo_initdb_database' => 'string',
|
||||||
]);
|
]);
|
||||||
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||||
if ($validator->fails() || ! empty($extraFields)) {
|
if ($validator->fails() || ! empty($extraFields)) {
|
||||||
@@ -1557,7 +1558,8 @@ class DatabasesController extends Controller
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
]),
|
]
|
||||||
|
),
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 401,
|
response: 401,
|
||||||
ref: '#/components/responses/401',
|
ref: '#/components/responses/401',
|
||||||
@@ -1632,9 +1634,11 @@ class DatabasesController extends Controller
|
|||||||
type: 'object',
|
type: 'object',
|
||||||
properties: [
|
properties: [
|
||||||
'message' => ['type' => 'string', 'example' => 'Database starting request queued.'],
|
'message' => ['type' => 'string', 'example' => 'Database starting request queued.'],
|
||||||
])
|
]
|
||||||
|
)
|
||||||
),
|
),
|
||||||
]),
|
]
|
||||||
|
),
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 401,
|
response: 401,
|
||||||
ref: '#/components/responses/401',
|
ref: '#/components/responses/401',
|
||||||
@@ -1708,9 +1712,11 @@ class DatabasesController extends Controller
|
|||||||
type: 'object',
|
type: 'object',
|
||||||
properties: [
|
properties: [
|
||||||
'message' => ['type' => 'string', 'example' => 'Database stopping request queued.'],
|
'message' => ['type' => 'string', 'example' => 'Database stopping request queued.'],
|
||||||
])
|
]
|
||||||
|
)
|
||||||
),
|
),
|
||||||
]),
|
]
|
||||||
|
),
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 401,
|
response: 401,
|
||||||
ref: '#/components/responses/401',
|
ref: '#/components/responses/401',
|
||||||
@@ -1784,9 +1790,11 @@ class DatabasesController extends Controller
|
|||||||
type: 'object',
|
type: 'object',
|
||||||
properties: [
|
properties: [
|
||||||
'message' => ['type' => 'string', 'example' => 'Database restaring request queued.'],
|
'message' => ['type' => 'string', 'example' => 'Database restaring request queued.'],
|
||||||
])
|
]
|
||||||
|
)
|
||||||
),
|
),
|
||||||
]),
|
]
|
||||||
|
),
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 401,
|
response: 401,
|
||||||
ref: '#/components/responses/401',
|
ref: '#/components/responses/401',
|
||||||
|
@@ -16,15 +16,12 @@ class DeployController extends Controller
|
|||||||
{
|
{
|
||||||
private function removeSensitiveData($deployment)
|
private function removeSensitiveData($deployment)
|
||||||
{
|
{
|
||||||
$token = auth()->user()->currentAccessToken();
|
if (request()->attributes->get('can_read_sensitive', false) === false) {
|
||||||
if ($token->can('view:sensitive')) {
|
$deployment->makeHidden([
|
||||||
return serializeApiResponse($deployment);
|
'logs',
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$deployment->makeHidden([
|
|
||||||
'logs',
|
|
||||||
]);
|
|
||||||
|
|
||||||
return serializeApiResponse($deployment);
|
return serializeApiResponse($deployment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -37,7 +37,7 @@ class OtherController extends Controller
|
|||||||
)]
|
)]
|
||||||
public function version(Request $request)
|
public function version(Request $request)
|
||||||
{
|
{
|
||||||
return response(config('version'));
|
return response(config('constants.coolify.version'));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[OA\Get(
|
#[OA\Get(
|
||||||
|
@@ -53,11 +53,7 @@ class ResourcesController extends Controller
|
|||||||
$resources = $resources->flatten();
|
$resources = $resources->flatten();
|
||||||
$resources = $resources->map(function ($resource) {
|
$resources = $resources->map(function ($resource) {
|
||||||
$payload = $resource->toArray();
|
$payload = $resource->toArray();
|
||||||
if ($resource->getMorphClass() === \App\Models\Service::class) {
|
$payload['status'] = $resource->status;
|
||||||
$payload['status'] = $resource->status();
|
|
||||||
} else {
|
|
||||||
$payload['status'] = $resource->status;
|
|
||||||
}
|
|
||||||
$payload['type'] = $resource->type();
|
$payload['type'] = $resource->type();
|
||||||
|
|
||||||
return $payload;
|
return $payload;
|
||||||
|
@@ -11,13 +11,11 @@ class SecurityController extends Controller
|
|||||||
{
|
{
|
||||||
private function removeSensitiveData($team)
|
private function removeSensitiveData($team)
|
||||||
{
|
{
|
||||||
$token = auth()->user()->currentAccessToken();
|
if (request()->attributes->get('can_read_sensitive', false) === false) {
|
||||||
if ($token->can('view:sensitive')) {
|
$team->makeHidden([
|
||||||
return serializeApiResponse($team);
|
'private_key',
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
$team->makeHidden([
|
|
||||||
'private_key',
|
|
||||||
]);
|
|
||||||
|
|
||||||
return serializeApiResponse($team);
|
return serializeApiResponse($team);
|
||||||
}
|
}
|
||||||
|
@@ -19,25 +19,22 @@ class ServersController extends Controller
|
|||||||
{
|
{
|
||||||
private function removeSensitiveDataFromSettings($settings)
|
private function removeSensitiveDataFromSettings($settings)
|
||||||
{
|
{
|
||||||
$token = auth()->user()->currentAccessToken();
|
if (request()->attributes->get('can_read_sensitive', false) === false) {
|
||||||
if ($token->can('view:sensitive')) {
|
$settings = $settings->makeHidden([
|
||||||
return serializeApiResponse($settings);
|
'sentinel_token',
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
$settings = $settings->makeHidden([
|
|
||||||
'sentinel_token',
|
|
||||||
]);
|
|
||||||
|
|
||||||
return serializeApiResponse($settings);
|
return serializeApiResponse($settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function removeSensitiveData($server)
|
private function removeSensitiveData($server)
|
||||||
{
|
{
|
||||||
$token = auth()->user()->currentAccessToken();
|
|
||||||
$server->makeHidden([
|
$server->makeHidden([
|
||||||
'id',
|
'id',
|
||||||
]);
|
]);
|
||||||
if ($token->can('view:sensitive')) {
|
if (request()->attributes->get('can_read_sensitive', false) === false) {
|
||||||
return serializeApiResponse($server);
|
// Do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
return serializeApiResponse($server);
|
return serializeApiResponse($server);
|
||||||
@@ -157,11 +154,7 @@ class ServersController extends Controller
|
|||||||
'created_at' => $resource->created_at,
|
'created_at' => $resource->created_at,
|
||||||
'updated_at' => $resource->updated_at,
|
'updated_at' => $resource->updated_at,
|
||||||
];
|
];
|
||||||
if ($resource->type() === 'service') {
|
$payload['status'] = $resource->status;
|
||||||
$payload['status'] = $resource->status();
|
|
||||||
} else {
|
|
||||||
$payload['status'] = $resource->status;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $payload;
|
return $payload;
|
||||||
});
|
});
|
||||||
@@ -240,11 +233,7 @@ class ServersController extends Controller
|
|||||||
'created_at' => $resource->created_at,
|
'created_at' => $resource->created_at,
|
||||||
'updated_at' => $resource->updated_at,
|
'updated_at' => $resource->updated_at,
|
||||||
];
|
];
|
||||||
if ($resource->type() === 'service') {
|
$payload['status'] = $resource->status;
|
||||||
$payload['status'] = $resource->status();
|
|
||||||
} else {
|
|
||||||
$payload['status'] = $resource->status;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $payload;
|
return $payload;
|
||||||
});
|
});
|
||||||
@@ -567,6 +556,9 @@ class ServersController extends Controller
|
|||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
tags: ['Servers'],
|
tags: ['Servers'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server UUID', schema: new OA\Schema(type: 'string')),
|
||||||
|
],
|
||||||
requestBody: new OA\RequestBody(
|
requestBody: new OA\RequestBody(
|
||||||
required: true,
|
required: true,
|
||||||
description: 'Server updated.',
|
description: 'Server updated.',
|
||||||
@@ -596,8 +588,7 @@ class ServersController extends Controller
|
|||||||
new OA\MediaType(
|
new OA\MediaType(
|
||||||
mediaType: 'application/json',
|
mediaType: 'application/json',
|
||||||
schema: new OA\Schema(
|
schema: new OA\Schema(
|
||||||
type: 'array',
|
ref: '#/components/schemas/Server'
|
||||||
items: new OA\Items(ref: '#/components/schemas/Server')
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
@@ -679,7 +670,7 @@ class ServersController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
|
'uuid' => $server->uuid,
|
||||||
])->setStatusCode(201);
|
])->setStatusCode(201);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -18,19 +18,21 @@ class ServicesController extends Controller
|
|||||||
{
|
{
|
||||||
private function removeSensitiveData($service)
|
private function removeSensitiveData($service)
|
||||||
{
|
{
|
||||||
$token = auth()->user()->currentAccessToken();
|
|
||||||
$service->makeHidden([
|
$service->makeHidden([
|
||||||
'id',
|
'id',
|
||||||
|
'resourceable',
|
||||||
|
'resourceable_id',
|
||||||
|
'resourceable_type',
|
||||||
]);
|
]);
|
||||||
if ($token->can('view:sensitive')) {
|
if (request()->attributes->get('can_read_sensitive', false) === false) {
|
||||||
return serializeApiResponse($service);
|
$service->makeHidden([
|
||||||
|
'docker_compose_raw',
|
||||||
|
'docker_compose',
|
||||||
|
'value',
|
||||||
|
'real_value',
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$service->makeHidden([
|
|
||||||
'docker_compose_raw',
|
|
||||||
'docker_compose',
|
|
||||||
]);
|
|
||||||
|
|
||||||
return serializeApiResponse($service);
|
return serializeApiResponse($service);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,7 +336,8 @@ class ServicesController extends Controller
|
|||||||
EnvironmentVariable::create([
|
EnvironmentVariable::create([
|
||||||
'key' => $key,
|
'key' => $key,
|
||||||
'value' => $generatedValue,
|
'value' => $generatedValue,
|
||||||
'service_id' => $service->id,
|
'resourceable_id' => $service->id,
|
||||||
|
'resourceable_type' => $service->getMorphClass(),
|
||||||
'is_build_time' => false,
|
'is_build_time' => false,
|
||||||
'is_preview' => false,
|
'is_preview' => false,
|
||||||
]);
|
]);
|
||||||
@@ -674,7 +677,8 @@ class ServicesController extends Controller
|
|||||||
], 422);
|
], 422);
|
||||||
}
|
}
|
||||||
|
|
||||||
$env = $service->environment_variables()->where('key', $request->key)->first();
|
$key = str($request->key)->trim()->replace(' ', '_')->value;
|
||||||
|
$env = $service->environment_variables()->where('key', $key)->first();
|
||||||
if (! $env) {
|
if (! $env) {
|
||||||
return response()->json(['message' => 'Environment variable not found.'], 404);
|
return response()->json(['message' => 'Environment variable not found.'], 404);
|
||||||
}
|
}
|
||||||
@@ -800,9 +804,9 @@ class ServicesController extends Controller
|
|||||||
'errors' => $validator->errors(),
|
'errors' => $validator->errors(),
|
||||||
], 422);
|
], 422);
|
||||||
}
|
}
|
||||||
|
$key = str($item['key'])->trim()->replace(' ', '_')->value;
|
||||||
$env = $service->environment_variables()->updateOrCreate(
|
$env = $service->environment_variables()->updateOrCreate(
|
||||||
['key' => $item['key']],
|
['key' => $key],
|
||||||
$item
|
$item
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -910,7 +914,8 @@ class ServicesController extends Controller
|
|||||||
], 422);
|
], 422);
|
||||||
}
|
}
|
||||||
|
|
||||||
$existingEnv = $service->environment_variables()->where('key', $request->key)->first();
|
$key = str($request->key)->trim()->replace(' ', '_')->value;
|
||||||
|
$existingEnv = $service->environment_variables()->where('key', $key)->first();
|
||||||
if ($existingEnv) {
|
if ($existingEnv) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => 'Environment variable already exists. Use PATCH request to update it.',
|
'message' => 'Environment variable already exists. Use PATCH request to update it.',
|
||||||
@@ -996,7 +1001,8 @@ class ServicesController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
$env = EnvironmentVariable::where('uuid', $request->env_uuid)
|
$env = EnvironmentVariable::where('uuid', $request->env_uuid)
|
||||||
->where('service_id', $service->id)
|
->where('resourceable_type', Service::class)
|
||||||
|
->where('resourceable_id', $service->id)
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
if (! $env) {
|
if (! $env) {
|
||||||
@@ -1073,7 +1079,7 @@ class ServicesController extends Controller
|
|||||||
if (! $service) {
|
if (! $service) {
|
||||||
return response()->json(['message' => 'Service not found.'], 404);
|
return response()->json(['message' => 'Service not found.'], 404);
|
||||||
}
|
}
|
||||||
if (str($service->status())->contains('running')) {
|
if (str($service->status)->contains('running')) {
|
||||||
return response()->json(['message' => 'Service is already running.'], 400);
|
return response()->json(['message' => 'Service is already running.'], 400);
|
||||||
}
|
}
|
||||||
StartService::dispatch($service);
|
StartService::dispatch($service);
|
||||||
@@ -1151,7 +1157,7 @@ class ServicesController extends Controller
|
|||||||
if (! $service) {
|
if (! $service) {
|
||||||
return response()->json(['message' => 'Service not found.'], 404);
|
return response()->json(['message' => 'Service not found.'], 404);
|
||||||
}
|
}
|
||||||
if (str($service->status())->contains('stopped') || str($service->status())->contains('exited')) {
|
if (str($service->status)->contains('stopped') || str($service->status)->contains('exited')) {
|
||||||
return response()->json(['message' => 'Service is already stopped.'], 400);
|
return response()->json(['message' => 'Service is already stopped.'], 400);
|
||||||
}
|
}
|
||||||
StopService::dispatch($service);
|
StopService::dispatch($service);
|
||||||
|
@@ -10,20 +10,18 @@ class TeamController extends Controller
|
|||||||
{
|
{
|
||||||
private function removeSensitiveData($team)
|
private function removeSensitiveData($team)
|
||||||
{
|
{
|
||||||
$token = auth()->user()->currentAccessToken();
|
|
||||||
$team->makeHidden([
|
$team->makeHidden([
|
||||||
'custom_server_limit',
|
'custom_server_limit',
|
||||||
'pivot',
|
'pivot',
|
||||||
]);
|
]);
|
||||||
if ($token->can('view:sensitive')) {
|
if (request()->attributes->get('can_read_sensitive', false) === false) {
|
||||||
return serializeApiResponse($team);
|
$team->makeHidden([
|
||||||
|
'smtp_username',
|
||||||
|
'smtp_password',
|
||||||
|
'resend_api_key',
|
||||||
|
'telegram_token',
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
$team->makeHidden([
|
|
||||||
'smtp_username',
|
|
||||||
'smtp_password',
|
|
||||||
'resend_api_key',
|
|
||||||
'telegram_token',
|
|
||||||
]);
|
|
||||||
|
|
||||||
return serializeApiResponse($team);
|
return serializeApiResponse($team);
|
||||||
}
|
}
|
||||||
|
@@ -42,15 +42,13 @@ class Controller extends BaseController
|
|||||||
public function email_verify(EmailVerificationRequest $request)
|
public function email_verify(EmailVerificationRequest $request)
|
||||||
{
|
{
|
||||||
$request->fulfill();
|
$request->fulfill();
|
||||||
$name = request()->user()?->name;
|
|
||||||
|
|
||||||
// send_internal_notification("User {$name} verified their email address.");
|
|
||||||
return redirect(RouteServiceProvider::HOME);
|
return redirect(RouteServiceProvider::HOME);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function forgot_password(Request $request)
|
public function forgot_password(Request $request)
|
||||||
{
|
{
|
||||||
if (is_transactional_emails_active()) {
|
if (is_transactional_emails_enabled()) {
|
||||||
$arrayOfRequest = $request->only(Fortify::email());
|
$arrayOfRequest = $request->only(Fortify::email());
|
||||||
$request->merge([
|
$request->merge([
|
||||||
'email' => Str::lower($arrayOfRequest['email']),
|
'email' => Str::lower($arrayOfRequest['email']),
|
||||||
|
@@ -463,7 +463,7 @@ class Github extends Controller
|
|||||||
$private_key = data_get($data, 'pem');
|
$private_key = data_get($data, 'pem');
|
||||||
$webhook_secret = data_get($data, 'webhook_secret');
|
$webhook_secret = data_get($data, 'webhook_secret');
|
||||||
$private_key = PrivateKey::create([
|
$private_key = PrivateKey::create([
|
||||||
'name' => $slug,
|
'name' => "github-app-{$slug}",
|
||||||
'private_key' => $private_key,
|
'private_key' => $private_key,
|
||||||
'team_id' => $github_app->team_id,
|
'team_id' => $github_app->team_id,
|
||||||
'is_git_related' => true,
|
'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,
|
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
||||||
'abilities' => \Laravel\Sanctum\Http\Middleware\CheckAbilities::class,
|
'abilities' => \Laravel\Sanctum\Http\Middleware\CheckAbilities::class,
|
||||||
'ability' => \Laravel\Sanctum\Http\Middleware\CheckForAnyAbility::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 ?string $buildTarget = null;
|
||||||
|
|
||||||
|
private bool $disableBuildCache = false;
|
||||||
|
|
||||||
private Collection $saved_outputs;
|
private Collection $saved_outputs;
|
||||||
|
|
||||||
private ?string $full_healthcheck_url = null;
|
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->pull_request_id = $this->application_deployment_queue->pull_request_id;
|
||||||
$this->commit = $this->application_deployment_queue->commit;
|
$this->commit = $this->application_deployment_queue->commit;
|
||||||
$this->rollback = $this->application_deployment_queue->rollback;
|
$this->rollback = $this->application_deployment_queue->rollback;
|
||||||
|
$this->disableBuildCache = $this->application->settings->disable_build_cache;
|
||||||
$this->force_rebuild = $this->application_deployment_queue->force_rebuild;
|
$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->application_deployment_queue->restart_only;
|
||||||
$this->restart_only = $this->restart_only && $this->application->build_pack !== 'dockerimage' && $this->application->build_pack !== 'dockerfile';
|
$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;
|
$this->only_this_server = $this->application_deployment_queue->only_this_server;
|
||||||
@@ -463,7 +469,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$composeFile = $this->application->parse(pull_request_id: $this->pull_request_id, preview_id: data_get($this->preview, 'id'));
|
$composeFile = $this->application->parse(pull_request_id: $this->pull_request_id, preview_id: data_get($this->preview, 'id'));
|
||||||
$this->save_environment_variables();
|
$this->save_environment_variables();
|
||||||
if (! is_null($this->env_filename)) {
|
if (! is_null($this->env_filename)) {
|
||||||
$services = collect($composeFile['services']);
|
$services = collect(data_get($composeFile, 'services', []));
|
||||||
$services = $services->map(function ($service, $name) {
|
$services = $services->map(function ($service, $name) {
|
||||||
$service['env_file'] = [$this->env_filename];
|
$service['env_file'] = [$this->env_filename];
|
||||||
|
|
||||||
@@ -1976,6 +1982,9 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$this->build_args = $this->build_args->implode(' ');
|
$this->build_args = $this->build_args->implode(' ');
|
||||||
|
|
||||||
$this->application_deployment_queue->addLogEntry('----------------------------------------');
|
$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') {
|
if ($this->application->build_pack === 'static') {
|
||||||
$this->application_deployment_queue->addLogEntry('Static deployment. Copying static assets to the image.');
|
$this->application_deployment_queue->addLogEntry('Static deployment. Copying static assets to the image.');
|
||||||
} else {
|
} else {
|
||||||
|
@@ -27,7 +27,7 @@ class CheckForUpdatesJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$versions = $response->json();
|
$versions = $response->json();
|
||||||
|
|
||||||
$latest_version = data_get($versions, 'coolify.v4.version');
|
$latest_version = data_get($versions, 'coolify.v4.version');
|
||||||
$current_version = config('version');
|
$current_version = config('constants.coolify.version');
|
||||||
|
|
||||||
if (version_compare($latest_version, $current_version, '>')) {
|
if (version_compare($latest_version, $current_version, '>')) {
|
||||||
// New version available
|
// New version available
|
||||||
|
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -32,8 +32,6 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
public Server $server;
|
public Server $server;
|
||||||
|
|
||||||
public ScheduledDatabaseBackup $backup;
|
|
||||||
|
|
||||||
public StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|ServiceDatabase $database;
|
public StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|ServiceDatabase $database;
|
||||||
|
|
||||||
public ?string $container_name = null;
|
public ?string $container_name = null;
|
||||||
@@ -58,15 +56,16 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
public ?S3Storage $s3 = null;
|
public ?S3Storage $s3 = null;
|
||||||
|
|
||||||
public function __construct($backup)
|
public function __construct(public ScheduledDatabaseBackup $backup)
|
||||||
{
|
{
|
||||||
$this->onQueue('high');
|
$this->onQueue('high');
|
||||||
$this->backup = $backup;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
$databasesToBackup = null;
|
||||||
|
|
||||||
$this->team = Team::find($this->backup->team_id);
|
$this->team = Team::find($this->backup->team_id);
|
||||||
if (! $this->team) {
|
if (! $this->team) {
|
||||||
$this->backup->delete();
|
$this->backup->delete();
|
||||||
@@ -198,8 +197,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$databaseType = $this->database->type();
|
$databaseType = $this->database->type();
|
||||||
$databasesToBackup = data_get($this->backup, 'databases_to_backup');
|
$databasesToBackup = data_get($this->backup, 'databases_to_backup');
|
||||||
}
|
}
|
||||||
|
if (blank($databasesToBackup)) {
|
||||||
if (filled($databasesToBackup)) {
|
|
||||||
if (str($databaseType)->contains('postgres')) {
|
if (str($databaseType)->contains('postgres')) {
|
||||||
$databasesToBackup = [$this->database->postgres_db];
|
$databasesToBackup = [$this->database->postgres_db];
|
||||||
} elseif (str($databaseType)->contains('mongodb')) {
|
} elseif (str($databaseType)->contains('mongodb')) {
|
||||||
@@ -305,7 +303,9 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
if ($this->backup->save_s3) {
|
if ($this->backup->save_s3) {
|
||||||
$this->upload_to_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([
|
$this->backup_log->update([
|
||||||
'status' => 'success',
|
'status' => 'success',
|
||||||
'message' => $this->backup_output,
|
'message' => $this->backup_output,
|
||||||
|
@@ -4,7 +4,8 @@ namespace App\Jobs;
|
|||||||
|
|
||||||
use App\Actions\Server\CleanupDocker;
|
use App\Actions\Server\CleanupDocker;
|
||||||
use App\Models\Server;
|
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\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
@@ -12,7 +13,6 @@ use Illuminate\Foundation\Bus\Dispatchable;
|
|||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
|
|
||||||
class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
|
class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
|
||||||
{
|
{
|
||||||
@@ -38,35 +38,36 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
return;
|
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();
|
$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);
|
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;
|
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) {
|
if ($this->usageBefore >= $this->server->settings->docker_cleanup_threshold) {
|
||||||
CleanupDocker::run(server: $this->server);
|
CleanupDocker::run(server: $this->server);
|
||||||
$usageAfter = $this->server->getDiskUsage();
|
$usageAfter = $this->server->getDiskUsage();
|
||||||
if ($usageAfter < $this->usageBefore) {
|
$diskSaved = $this->usageBefore - $usageAfter;
|
||||||
$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);
|
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 {
|
} 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 {
|
} 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) {
|
} catch (\Throwable $e) {
|
||||||
CleanupDocker::run(server: $this->server);
|
$this->server->team?->notify(new DockerCleanupFailed($this->server, 'Docker cleanup job failed with the following error: '.$e->getMessage()));
|
||||||
Log::error('DockerCleanupJob failed: '.$e->getMessage());
|
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -10,6 +10,7 @@ use App\Models\Server;
|
|||||||
use App\Models\Service;
|
use App\Models\Service;
|
||||||
use App\Models\Team;
|
use App\Models\Team;
|
||||||
use App\Notifications\ScheduledTask\TaskFailed;
|
use App\Notifications\ScheduledTask\TaskFailed;
|
||||||
|
use App\Notifications\ScheduledTask\TaskSuccess;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
@@ -111,6 +112,8 @@ class ScheduledTaskJob implements ShouldQueue
|
|||||||
'message' => $this->task_output,
|
'message' => $this->task_output,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$this->team?->notify(new TaskSuccess($this->task, $this->task_output));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -125,7 +128,6 @@ class ScheduledTaskJob implements ShouldQueue
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
$this->team?->notify(new TaskFailed($this->task, $e->getMessage()));
|
$this->team?->notify(new TaskFailed($this->task, $e->getMessage()));
|
||||||
// send_internal_notification('ScheduledTaskJob failed with: ' . $e->getMessage());
|
|
||||||
throw $e;
|
throw $e;
|
||||||
} finally {
|
} finally {
|
||||||
ScheduledTaskDone::dispatch($this->team->id);
|
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 array $buttons,
|
||||||
public string $token,
|
public string $token,
|
||||||
public string $chatId,
|
public string $chatId,
|
||||||
public ?string $topicId = null,
|
public ?string $threadId = null,
|
||||||
) {
|
) {
|
||||||
$this->onQueue('high');
|
$this->onQueue('high');
|
||||||
}
|
}
|
||||||
@@ -67,12 +67,12 @@ class SendMessageToTelegramJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
'chat_id' => $this->chatId,
|
'chat_id' => $this->chatId,
|
||||||
'text' => $this->text,
|
'text' => $this->text,
|
||||||
];
|
];
|
||||||
if ($this->topicId) {
|
if ($this->threadId) {
|
||||||
$payload['message_thread_id'] = $this->topicId;
|
$payload['message_thread_id'] = $this->threadId;
|
||||||
}
|
}
|
||||||
$response = Http::post($url, $payload);
|
$response = Http::post($url, $payload);
|
||||||
if ($response->failed()) {
|
if ($response->failed()) {
|
||||||
throw new \Exception('Telegram notification failed with '.$response->status().' status code.'.$response->body());
|
throw new \RuntimeException('Telegram notification failed with '.$response->status().' status code.'.$response->body());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -31,7 +31,12 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
return [(new WithoutOverlapping($this->server->uuid))->dontRelease()];
|
return [(new WithoutOverlapping($this->server->uuid))->dontRelease()];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __construct(public Server $server) {}
|
public function __construct(public Server $server)
|
||||||
|
{
|
||||||
|
if (isDev()) {
|
||||||
|
$this->handle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
|
@@ -25,218 +25,242 @@ class StripeProcessJob implements ShouldQueue
|
|||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
$excludedPlans = config('subscription.stripe_excluded_plans');
|
try {
|
||||||
|
$excludedPlans = config('subscription.stripe_excluded_plans');
|
||||||
|
|
||||||
$type = data_get($this->event, 'type');
|
$type = data_get($this->event, 'type');
|
||||||
$this->type = $type;
|
$this->type = $type;
|
||||||
$data = data_get($this->event, 'data.object');
|
$data = data_get($this->event, 'data.object');
|
||||||
switch ($type) {
|
switch ($type) {
|
||||||
case 'radar.early_fraud_warning.created':
|
case 'radar.early_fraud_warning.created':
|
||||||
$stripe = new \Stripe\StripeClient(config('subscription.stripe_api_key'));
|
$stripe = new \Stripe\StripeClient(config('subscription.stripe_api_key'));
|
||||||
$id = data_get($data, 'id');
|
$id = data_get($data, 'id');
|
||||||
$charge = data_get($data, 'charge');
|
$charge = data_get($data, 'charge');
|
||||||
if ($charge) {
|
if ($charge) {
|
||||||
$stripe->refunds->create(['charge' => $charge]);
|
$stripe->refunds->create(['charge' => $charge]);
|
||||||
}
|
}
|
||||||
$pi = data_get($data, 'payment_intent');
|
$pi = data_get($data, 'payment_intent');
|
||||||
$piData = $stripe->paymentIntents->retrieve($pi, []);
|
$piData = $stripe->paymentIntents->retrieve($pi, []);
|
||||||
$customerId = data_get($piData, 'customer');
|
$customerId = data_get($piData, 'customer');
|
||||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
|
||||||
if ($subscription) {
|
|
||||||
$subscriptionId = data_get($subscription, 'stripe_subscription_id');
|
|
||||||
$stripe->subscriptions->cancel($subscriptionId, []);
|
|
||||||
$subscription->update([
|
|
||||||
'stripe_invoice_paid' => false,
|
|
||||||
]);
|
|
||||||
send_internal_notification("Early fraud warning created Refunded, subscription canceled. Charge: {$charge}, id: {$id}, pi: {$pi}");
|
|
||||||
} else {
|
|
||||||
send_internal_notification("Early fraud warning: subscription not found. Charge: {$charge}, id: {$id}, pi: {$pi}");
|
|
||||||
throw new \Exception("Early fraud warning: subscription not found. Charge: {$charge}, id: {$id}, pi: {$pi}");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'checkout.session.completed':
|
|
||||||
$clientReferenceId = data_get($data, 'client_reference_id');
|
|
||||||
if (is_null($clientReferenceId)) {
|
|
||||||
send_internal_notification('Checkout session completed without client reference id.');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
$userId = Str::before($clientReferenceId, ':');
|
|
||||||
$teamId = Str::after($clientReferenceId, ':');
|
|
||||||
$subscriptionId = data_get($data, 'subscription');
|
|
||||||
$customerId = data_get($data, 'customer');
|
|
||||||
$team = Team::find($teamId);
|
|
||||||
$found = $team->members->where('id', $userId)->first();
|
|
||||||
if (! $found->isAdmin()) {
|
|
||||||
send_internal_notification("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}, subscriptionid: {$subscriptionId}.");
|
|
||||||
throw new \Exception("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}, subscriptionid: {$subscriptionId}.");
|
|
||||||
}
|
|
||||||
$subscription = Subscription::where('team_id', $teamId)->first();
|
|
||||||
if ($subscription) {
|
|
||||||
send_internal_notification('Old subscription activated for team: '.$teamId);
|
|
||||||
$subscription->update([
|
|
||||||
'stripe_subscription_id' => $subscriptionId,
|
|
||||||
'stripe_customer_id' => $customerId,
|
|
||||||
'stripe_invoice_paid' => true,
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
send_internal_notification('New subscription for team: '.$teamId);
|
|
||||||
Subscription::create([
|
|
||||||
'team_id' => $teamId,
|
|
||||||
'stripe_subscription_id' => $subscriptionId,
|
|
||||||
'stripe_customer_id' => $customerId,
|
|
||||||
'stripe_invoice_paid' => true,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'invoice.paid':
|
|
||||||
$customerId = data_get($data, 'customer');
|
|
||||||
$planId = data_get($data, 'lines.data.0.plan.id');
|
|
||||||
if (Str::contains($excludedPlans, $planId)) {
|
|
||||||
send_internal_notification('Subscription excluded.');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
|
||||||
if ($subscription) {
|
|
||||||
$subscription->update([
|
|
||||||
'stripe_invoice_paid' => true,
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
throw new \Exception("No subscription found for customer: {$customerId}");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'invoice.payment_failed':
|
|
||||||
$customerId = data_get($data, 'customer');
|
|
||||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
|
||||||
if (! $subscription) {
|
|
||||||
send_internal_notification('invoice.payment_failed failed but no subscription found in Coolify for customer: '.$customerId);
|
|
||||||
throw new \Exception("No subscription found for customer: {$customerId}");
|
|
||||||
}
|
|
||||||
$team = data_get($subscription, 'team');
|
|
||||||
if (! $team) {
|
|
||||||
send_internal_notification('invoice.payment_failed failed but no team found in Coolify for customer: '.$customerId);
|
|
||||||
throw new \Exception("No team found in Coolify for customer: {$customerId}");
|
|
||||||
}
|
|
||||||
if (! $subscription->stripe_invoice_paid) {
|
|
||||||
SubscriptionInvoiceFailedJob::dispatch($team);
|
|
||||||
send_internal_notification('Invoice payment failed: '.$customerId);
|
|
||||||
} else {
|
|
||||||
send_internal_notification('Invoice payment failed but already paid: '.$customerId);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'payment_intent.payment_failed':
|
|
||||||
$customerId = data_get($data, 'customer');
|
|
||||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
|
||||||
if (! $subscription) {
|
|
||||||
send_internal_notification('payment_intent.payment_failed, no subscription found in Coolify for customer: '.$customerId);
|
|
||||||
throw new \Exception("No subscription found in Coolify for customer: {$customerId}");
|
|
||||||
}
|
|
||||||
if ($subscription->stripe_invoice_paid) {
|
|
||||||
send_internal_notification('payment_intent.payment_failed but invoice is active for customer: '.$customerId);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
send_internal_notification('Subscription payment failed for customer: '.$customerId);
|
|
||||||
break;
|
|
||||||
case 'customer.subscription.created':
|
|
||||||
$customerId = data_get($data, 'customer');
|
|
||||||
$subscriptionId = data_get($data, 'id');
|
|
||||||
$teamId = data_get($data, 'metadata.team_id');
|
|
||||||
$userId = data_get($data, 'metadata.user_id');
|
|
||||||
if (! $teamId || ! $userId) {
|
|
||||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
||||||
if ($subscription) {
|
if ($subscription) {
|
||||||
throw new \Exception("Subscription already exists for customer: {$customerId}");
|
$subscriptionId = data_get($subscription, 'stripe_subscription_id');
|
||||||
|
$stripe->subscriptions->cancel($subscriptionId, []);
|
||||||
|
$subscription->update([
|
||||||
|
'stripe_invoice_paid' => false,
|
||||||
|
]);
|
||||||
|
send_internal_notification("Early fraud warning created Refunded, subscription canceled. Charge: {$charge}, id: {$id}, pi: {$pi}");
|
||||||
|
} else {
|
||||||
|
send_internal_notification("Early fraud warning: subscription not found. Charge: {$charge}, id: {$id}, pi: {$pi}");
|
||||||
|
throw new \RuntimeException("Early fraud warning: subscription not found. Charge: {$charge}, id: {$id}, pi: {$pi}");
|
||||||
}
|
}
|
||||||
throw new \Exception('No team id or user id found');
|
|
||||||
}
|
|
||||||
$team = Team::find($teamId);
|
|
||||||
$found = $team->members->where('id', $userId)->first();
|
|
||||||
if (! $found->isAdmin()) {
|
|
||||||
send_internal_notification("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}.");
|
|
||||||
throw new \Exception("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}.");
|
|
||||||
}
|
|
||||||
$subscription = Subscription::where('team_id', $teamId)->first();
|
|
||||||
if ($subscription) {
|
|
||||||
send_internal_notification("Subscription already exists for team: {$teamId}");
|
|
||||||
throw new \Exception("Subscription already exists for team: {$teamId}");
|
|
||||||
} else {
|
|
||||||
Subscription::create([
|
|
||||||
'team_id' => $teamId,
|
|
||||||
'stripe_subscription_id' => $subscriptionId,
|
|
||||||
'stripe_customer_id' => $customerId,
|
|
||||||
'stripe_invoice_paid' => false,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
case 'customer.subscription.updated':
|
|
||||||
$teamId = data_get($data, 'metadata.team_id');
|
|
||||||
$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');
|
|
||||||
if (Str::contains($excludedPlans, $planId)) {
|
|
||||||
send_internal_notification('Subscription excluded.');
|
|
||||||
break;
|
break;
|
||||||
}
|
case 'checkout.session.completed':
|
||||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
$clientReferenceId = data_get($data, 'client_reference_id');
|
||||||
if (! $subscription) {
|
if (is_null($clientReferenceId)) {
|
||||||
if ($status === 'incomplete_expired') {
|
send_internal_notification('Checkout session completed without client reference id.');
|
||||||
send_internal_notification('Subscription incomplete expired');
|
break;
|
||||||
throw new \Exception('Subscription incomplete expired');
|
|
||||||
}
|
}
|
||||||
if ($teamId) {
|
$userId = Str::before($clientReferenceId, ':');
|
||||||
$subscription = Subscription::create([
|
$teamId = Str::after($clientReferenceId, ':');
|
||||||
|
$subscriptionId = data_get($data, 'subscription');
|
||||||
|
$customerId = data_get($data, 'customer');
|
||||||
|
$team = Team::find($teamId);
|
||||||
|
$found = $team->members->where('id', $userId)->first();
|
||||||
|
if (! $found->isAdmin()) {
|
||||||
|
send_internal_notification("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}, subscriptionid: {$subscriptionId}.");
|
||||||
|
throw new \RuntimeException("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}, subscriptionid: {$subscriptionId}.");
|
||||||
|
}
|
||||||
|
$subscription = Subscription::where('team_id', $teamId)->first();
|
||||||
|
if ($subscription) {
|
||||||
|
send_internal_notification('Old subscription activated for team: '.$teamId);
|
||||||
|
$subscription->update([
|
||||||
|
'stripe_subscription_id' => $subscriptionId,
|
||||||
|
'stripe_customer_id' => $customerId,
|
||||||
|
'stripe_invoice_paid' => true,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
send_internal_notification('New subscription for team: '.$teamId);
|
||||||
|
Subscription::create([
|
||||||
|
'team_id' => $teamId,
|
||||||
|
'stripe_subscription_id' => $subscriptionId,
|
||||||
|
'stripe_customer_id' => $customerId,
|
||||||
|
'stripe_invoice_paid' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'invoice.paid':
|
||||||
|
$customerId = data_get($data, 'customer');
|
||||||
|
$planId = data_get($data, 'lines.data.0.plan.id');
|
||||||
|
if (Str::contains($excludedPlans, $planId)) {
|
||||||
|
send_internal_notification('Subscription excluded.');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
||||||
|
if ($subscription) {
|
||||||
|
$subscription->update([
|
||||||
|
'stripe_invoice_paid' => true,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
throw new \RuntimeException("No subscription found for customer: {$customerId}");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'invoice.payment_failed':
|
||||||
|
$customerId = data_get($data, 'customer');
|
||||||
|
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
||||||
|
if (! $subscription) {
|
||||||
|
send_internal_notification('invoice.payment_failed failed but no subscription found in Coolify for customer: '.$customerId);
|
||||||
|
throw new \RuntimeException("No subscription found for customer: {$customerId}");
|
||||||
|
}
|
||||||
|
$team = data_get($subscription, 'team');
|
||||||
|
if (! $team) {
|
||||||
|
send_internal_notification('invoice.payment_failed failed but no team found in Coolify for customer: '.$customerId);
|
||||||
|
throw new \RuntimeException("No team found in Coolify for customer: {$customerId}");
|
||||||
|
}
|
||||||
|
if (! $subscription->stripe_invoice_paid) {
|
||||||
|
SubscriptionInvoiceFailedJob::dispatch($team);
|
||||||
|
send_internal_notification('Invoice payment failed: '.$customerId);
|
||||||
|
} else {
|
||||||
|
send_internal_notification('Invoice payment failed but already paid: '.$customerId);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'payment_intent.payment_failed':
|
||||||
|
$customerId = data_get($data, 'customer');
|
||||||
|
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
||||||
|
if (! $subscription) {
|
||||||
|
send_internal_notification('payment_intent.payment_failed, no subscription found in Coolify for customer: '.$customerId);
|
||||||
|
throw new \RuntimeException("No subscription found in Coolify for customer: {$customerId}");
|
||||||
|
}
|
||||||
|
if ($subscription->stripe_invoice_paid) {
|
||||||
|
send_internal_notification('payment_intent.payment_failed but invoice is active for customer: '.$customerId);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
send_internal_notification('Subscription payment failed for customer: '.$customerId);
|
||||||
|
break;
|
||||||
|
case 'customer.subscription.created':
|
||||||
|
$customerId = data_get($data, 'customer');
|
||||||
|
$subscriptionId = data_get($data, 'id');
|
||||||
|
$teamId = data_get($data, 'metadata.team_id');
|
||||||
|
$userId = data_get($data, 'metadata.user_id');
|
||||||
|
if (! $teamId || ! $userId) {
|
||||||
|
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
||||||
|
if ($subscription) {
|
||||||
|
throw new \RuntimeException("Subscription already exists for customer: {$customerId}");
|
||||||
|
}
|
||||||
|
throw new \RuntimeException('No team id or user id found');
|
||||||
|
}
|
||||||
|
$team = Team::find($teamId);
|
||||||
|
$found = $team->members->where('id', $userId)->first();
|
||||||
|
if (! $found->isAdmin()) {
|
||||||
|
send_internal_notification("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}.");
|
||||||
|
throw new \RuntimeException("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}.");
|
||||||
|
}
|
||||||
|
$subscription = Subscription::where('team_id', $teamId)->first();
|
||||||
|
if ($subscription) {
|
||||||
|
send_internal_notification("Subscription already exists for team: {$teamId}");
|
||||||
|
throw new \RuntimeException("Subscription already exists for team: {$teamId}");
|
||||||
|
} else {
|
||||||
|
Subscription::create([
|
||||||
'team_id' => $teamId,
|
'team_id' => $teamId,
|
||||||
'stripe_subscription_id' => $subscriptionId,
|
'stripe_subscription_id' => $subscriptionId,
|
||||||
'stripe_customer_id' => $customerId,
|
'stripe_customer_id' => $customerId,
|
||||||
'stripe_invoice_paid' => false,
|
'stripe_invoice_paid' => false,
|
||||||
]);
|
]);
|
||||||
} else {
|
|
||||||
send_internal_notification('No subscription and team id found');
|
|
||||||
throw new \Exception('No subscription and team id found');
|
|
||||||
}
|
}
|
||||||
}
|
case 'customer.subscription.updated':
|
||||||
$cancelAtPeriodEnd = data_get($data, 'cancel_at_period_end');
|
$teamId = data_get($data, 'metadata.team_id');
|
||||||
$feedback = data_get($data, 'cancellation_details.feedback');
|
$userId = data_get($data, 'metadata.user_id');
|
||||||
$comment = data_get($data, 'cancellation_details.comment');
|
$customerId = data_get($data, 'customer');
|
||||||
$lookup_key = data_get($data, 'items.data.0.price.lookup_key');
|
$status = data_get($data, 'status');
|
||||||
if (str($lookup_key)->contains('dynamic')) {
|
$subscriptionId = data_get($data, 'items.data.0.subscription') ?? data_get($data, 'id');
|
||||||
$quantity = data_get($data, 'items.data.0.quantity', 2);
|
$planId = data_get($data, 'items.data.0.plan.id') ?? data_get($data, 'plan.id');
|
||||||
$team = data_get($subscription, 'team');
|
if (Str::contains($excludedPlans, $planId)) {
|
||||||
if ($team) {
|
send_internal_notification('Subscription excluded.');
|
||||||
$team->update([
|
break;
|
||||||
'custom_server_limit' => $quantity,
|
}
|
||||||
]);
|
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
||||||
|
if (! $subscription) {
|
||||||
|
if ($status === 'incomplete_expired') {
|
||||||
|
send_internal_notification('Subscription incomplete expired');
|
||||||
|
throw new \RuntimeException('Subscription incomplete expired');
|
||||||
|
}
|
||||||
|
if ($teamId) {
|
||||||
|
$subscription = Subscription::create([
|
||||||
|
'team_id' => $teamId,
|
||||||
|
'stripe_subscription_id' => $subscriptionId,
|
||||||
|
'stripe_customer_id' => $customerId,
|
||||||
|
'stripe_invoice_paid' => false,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
send_internal_notification('No subscription and team id found');
|
||||||
|
throw new \RuntimeException('No subscription and team id found');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$cancelAtPeriodEnd = data_get($data, 'cancel_at_period_end');
|
||||||
|
$feedback = data_get($data, 'cancellation_details.feedback');
|
||||||
|
$comment = data_get($data, 'cancellation_details.comment');
|
||||||
|
$lookup_key = data_get($data, 'items.data.0.price.lookup_key');
|
||||||
|
if (str($lookup_key)->contains('dynamic')) {
|
||||||
|
$quantity = data_get($data, 'items.data.0.quantity', 2);
|
||||||
|
$team = data_get($subscription, 'team');
|
||||||
|
if ($team) {
|
||||||
|
$team->update([
|
||||||
|
'custom_server_limit' => $quantity,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
ServerLimitCheckJob::dispatch($team);
|
||||||
}
|
}
|
||||||
ServerLimitCheckJob::dispatch($team);
|
|
||||||
}
|
|
||||||
$subscription->update([
|
|
||||||
'stripe_feedback' => $feedback,
|
|
||||||
'stripe_comment' => $comment,
|
|
||||||
'stripe_plan_id' => $planId,
|
|
||||||
'stripe_cancel_at_period_end' => $cancelAtPeriodEnd,
|
|
||||||
]);
|
|
||||||
if ($status === 'paused' || $status === 'incomplete_expired') {
|
|
||||||
$subscription->update([
|
$subscription->update([
|
||||||
'stripe_invoice_paid' => false,
|
'stripe_feedback' => $feedback,
|
||||||
|
'stripe_comment' => $comment,
|
||||||
|
'stripe_plan_id' => $planId,
|
||||||
|
'stripe_cancel_at_period_end' => $cancelAtPeriodEnd,
|
||||||
]);
|
]);
|
||||||
}
|
if ($status === 'paused' || $status === 'incomplete_expired') {
|
||||||
if ($feedback) {
|
if ($subscription->stripe_subscription_id === $subscriptionId) {
|
||||||
$reason = "Cancellation feedback for {$customerId}: '".$feedback."'";
|
$subscription->update([
|
||||||
if ($comment) {
|
'stripe_invoice_paid' => false,
|
||||||
$reason .= ' with comment: \''.$comment."'";
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
if ($status === 'active') {
|
||||||
break;
|
if ($subscription->stripe_subscription_id === $subscriptionId) {
|
||||||
case 'customer.subscription.deleted':
|
$subscription->update([
|
||||||
// End subscription
|
'stripe_invoice_paid' => true,
|
||||||
$customerId = data_get($data, 'customer');
|
]);
|
||||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
|
}
|
||||||
$team = data_get($subscription, 'team');
|
}
|
||||||
$team?->subscriptionEnded();
|
if ($feedback) {
|
||||||
break;
|
$reason = "Cancellation feedback for {$customerId}: '".$feedback."'";
|
||||||
default:
|
if ($comment) {
|
||||||
// Unhandled event type
|
$reason .= ' with comment: \''.$comment."'";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'customer.subscription.deleted':
|
||||||
|
$customerId = data_get($data, 'customer');
|
||||||
|
$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');
|
||||||
|
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}");
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
send_internal_notification('StripeProcessJob error: '.$e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -14,7 +14,7 @@ class ProxyStartedNotification
|
|||||||
public function handle(ProxyStarted $event): void
|
public function handle(ProxyStarted $event): void
|
||||||
{
|
{
|
||||||
$this->server = data_get($event, 'data');
|
$this->server = data_get($event, 'data');
|
||||||
$this->server->setupDefault404Redirect();
|
$this->server->setupDefaultRedirect();
|
||||||
$this->server->setupDynamicProxyConfiguration();
|
$this->server->setupDynamicProxyConfiguration();
|
||||||
$this->server->proxy->force_stop = false;
|
$this->server->proxy->force_stop = false;
|
||||||
$this->server->save();
|
$this->server->save();
|
||||||
|
@@ -21,16 +21,28 @@ class Index extends Component
|
|||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
if (! isCloud()) {
|
if (! isCloud() && ! isDev()) {
|
||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
}
|
}
|
||||||
|
if (Auth::id() !== 0 && ! session('impersonating')) {
|
||||||
if (Auth::id() !== 0) {
|
|
||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
}
|
}
|
||||||
$this->getSubscribers();
|
$this->getSubscribers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function back()
|
||||||
|
{
|
||||||
|
if (session('impersonating')) {
|
||||||
|
session()->forget('impersonating');
|
||||||
|
$user = User::find(0);
|
||||||
|
$team_to_switch_to = $user->teams->first();
|
||||||
|
Auth::login($user);
|
||||||
|
refreshSession($team_to_switch_to);
|
||||||
|
|
||||||
|
return redirect(request()->header('Referer'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function submitSearch()
|
public function submitSearch()
|
||||||
{
|
{
|
||||||
if ($this->search !== '') {
|
if ($this->search !== '') {
|
||||||
@@ -52,9 +64,10 @@ class Index extends Component
|
|||||||
if (Auth::id() !== 0) {
|
if (Auth::id() !== 0) {
|
||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
}
|
}
|
||||||
|
session(['impersonating' => true]);
|
||||||
$user = User::find($user_id);
|
$user = User::find($user_id);
|
||||||
$team_to_switch_to = $user->teams->first();
|
$team_to_switch_to = $user->teams->first();
|
||||||
Cache::forget("team:{$user->id}");
|
// Cache::forget("team:{$user->id}");
|
||||||
Auth::login($user);
|
Auth::login($user);
|
||||||
refreshSession($team_to_switch_to);
|
refreshSession($team_to_switch_to);
|
||||||
|
|
||||||
|
@@ -173,13 +173,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
|||||||
|
|
||||||
public function getProxyType()
|
public function getProxyType()
|
||||||
{
|
{
|
||||||
// Set Default Proxy Type
|
|
||||||
$this->selectProxy(ProxyTypes::TRAEFIK->value);
|
$this->selectProxy(ProxyTypes::TRAEFIK->value);
|
||||||
// $proxyTypeSet = $this->createdServer->proxy->type;
|
|
||||||
// if (!$proxyTypeSet) {
|
|
||||||
// $this->currentState = 'select-proxy';
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
$this->getProjects();
|
$this->getProjects();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,7 +184,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$this->createdPrivateKey = PrivateKey::find($this->selectedExistingPrivateKey);
|
$this->createdPrivateKey = PrivateKey::where('team_id', currentTeam()->id)->where('id', $this->selectedExistingPrivateKey)->first();
|
||||||
$this->privateKey = $this->createdPrivateKey->private_key;
|
$this->privateKey = $this->createdPrivateKey->private_key;
|
||||||
$this->currentState = 'create-server';
|
$this->currentState = 'create-server';
|
||||||
}
|
}
|
||||||
|
@@ -35,7 +35,7 @@ class Docker extends Component
|
|||||||
$this->network = new Cuid2;
|
$this->network = new Cuid2;
|
||||||
$this->servers = Server::isUsable()->get();
|
$this->servers = Server::isUsable()->get();
|
||||||
if ($server_id) {
|
if ($server_id) {
|
||||||
$this->selectedServer = $this->servers->find($server_id);
|
$this->selectedServer = $this->servers->find($server_id) ?: $this->servers->first();
|
||||||
$this->serverId = $this->selectedServer->id;
|
$this->serverId = $this->selectedServer->id;
|
||||||
} else {
|
} else {
|
||||||
$this->selectedServer = $this->servers->first();
|
$this->selectedServer = $this->servers->first();
|
||||||
|
@@ -3,7 +3,6 @@
|
|||||||
namespace App\Livewire;
|
namespace App\Livewire;
|
||||||
|
|
||||||
use App\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
use Illuminate\Container\Attributes\Auth as AttributesAuth;
|
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
@@ -32,7 +31,7 @@ class NavbarDeleteTeam extends Component
|
|||||||
$currentTeam->delete();
|
$currentTeam->delete();
|
||||||
|
|
||||||
$currentTeam->members->each(function ($user) use ($currentTeam) {
|
$currentTeam->members->each(function ($user) use ($currentTeam) {
|
||||||
if ($user->id === AttributesAuth::id()) {
|
if ($user->id === Auth::id()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$user->teams()->detach($currentTeam);
|
$user->teams()->detach($currentTeam);
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Notifications;
|
namespace App\Livewire\Notifications;
|
||||||
|
|
||||||
|
use App\Models\DiscordNotificationSettings;
|
||||||
use App\Models\Team;
|
use App\Models\Team;
|
||||||
use App\Notifications\Test;
|
use App\Notifications\Test;
|
||||||
use Livewire\Attributes\Validate;
|
use Livewire\Attributes\Validate;
|
||||||
@@ -11,6 +12,8 @@ class Discord extends Component
|
|||||||
{
|
{
|
||||||
public Team $team;
|
public Team $team;
|
||||||
|
|
||||||
|
public DiscordNotificationSettings $settings;
|
||||||
|
|
||||||
#[Validate(['boolean'])]
|
#[Validate(['boolean'])]
|
||||||
public bool $discordEnabled = false;
|
public bool $discordEnabled = false;
|
||||||
|
|
||||||
@@ -18,27 +21,46 @@ class Discord extends Component
|
|||||||
public ?string $discordWebhookUrl = null;
|
public ?string $discordWebhookUrl = null;
|
||||||
|
|
||||||
#[Validate(['boolean'])]
|
#[Validate(['boolean'])]
|
||||||
public bool $discordNotificationsTest = false;
|
public bool $deploymentSuccessDiscordNotifications = false;
|
||||||
|
|
||||||
#[Validate(['boolean'])]
|
#[Validate(['boolean'])]
|
||||||
public bool $discordNotificationsDeployments = false;
|
public bool $deploymentFailureDiscordNotifications = true;
|
||||||
|
|
||||||
#[Validate(['boolean'])]
|
#[Validate(['boolean'])]
|
||||||
public bool $discordNotificationsStatusChanges = false;
|
public bool $statusChangeDiscordNotifications = false;
|
||||||
|
|
||||||
#[Validate(['boolean'])]
|
#[Validate(['boolean'])]
|
||||||
public bool $discordNotificationsDatabaseBackups = false;
|
public bool $backupSuccessDiscordNotifications = false;
|
||||||
|
|
||||||
#[Validate(['boolean'])]
|
#[Validate(['boolean'])]
|
||||||
public bool $discordNotificationsScheduledTasks = false;
|
public bool $backupFailureDiscordNotifications = true;
|
||||||
|
|
||||||
#[Validate(['boolean'])]
|
#[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()
|
public function mount()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->team = auth()->user()->currentTeam();
|
$this->team = auth()->user()->currentTeam();
|
||||||
|
$this->settings = $this->team->discordNotificationSettings;
|
||||||
$this->syncData();
|
$this->syncData();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
@@ -49,25 +71,40 @@ class Discord extends Component
|
|||||||
{
|
{
|
||||||
if ($toModel) {
|
if ($toModel) {
|
||||||
$this->validate();
|
$this->validate();
|
||||||
$this->team->discord_enabled = $this->discordEnabled;
|
$this->settings->discord_enabled = $this->discordEnabled;
|
||||||
$this->team->discord_webhook_url = $this->discordWebhookUrl;
|
$this->settings->discord_webhook_url = $this->discordWebhookUrl;
|
||||||
$this->team->discord_notifications_test = $this->discordNotificationsTest;
|
|
||||||
$this->team->discord_notifications_deployments = $this->discordNotificationsDeployments;
|
$this->settings->deployment_success_discord_notifications = $this->deploymentSuccessDiscordNotifications;
|
||||||
$this->team->discord_notifications_status_changes = $this->discordNotificationsStatusChanges;
|
$this->settings->deployment_failure_discord_notifications = $this->deploymentFailureDiscordNotifications;
|
||||||
$this->team->discord_notifications_database_backups = $this->discordNotificationsDatabaseBackups;
|
$this->settings->status_change_discord_notifications = $this->statusChangeDiscordNotifications;
|
||||||
$this->team->discord_notifications_scheduled_tasks = $this->discordNotificationsScheduledTasks;
|
$this->settings->backup_success_discord_notifications = $this->backupSuccessDiscordNotifications;
|
||||||
$this->team->discord_notifications_server_disk_usage = $this->discordNotificationsServerDiskUsage;
|
$this->settings->backup_failure_discord_notifications = $this->backupFailureDiscordNotifications;
|
||||||
$this->team->save();
|
$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();
|
refreshSession();
|
||||||
} else {
|
} else {
|
||||||
$this->discordEnabled = $this->team->discord_enabled;
|
$this->discordEnabled = $this->settings->discord_enabled;
|
||||||
$this->discordWebhookUrl = $this->team->discord_webhook_url;
|
$this->discordWebhookUrl = $this->settings->discord_webhook_url;
|
||||||
$this->discordNotificationsTest = $this->team->discord_notifications_test;
|
|
||||||
$this->discordNotificationsDeployments = $this->team->discord_notifications_deployments;
|
$this->deploymentSuccessDiscordNotifications = $this->settings->deployment_success_discord_notifications;
|
||||||
$this->discordNotificationsStatusChanges = $this->team->discord_notifications_status_changes;
|
$this->deploymentFailureDiscordNotifications = $this->settings->deployment_failure_discord_notifications;
|
||||||
$this->discordNotificationsDatabaseBackups = $this->team->discord_notifications_database_backups;
|
$this->statusChangeDiscordNotifications = $this->settings->status_change_discord_notifications;
|
||||||
$this->discordNotificationsScheduledTasks = $this->team->discord_notifications_scheduled_tasks;
|
$this->backupSuccessDiscordNotifications = $this->settings->backup_success_discord_notifications;
|
||||||
$this->discordNotificationsServerDiskUsage = $this->team->discord_notifications_server_disk_usage;
|
$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()
|
public function sendTestNotification()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->team->notify(new Test);
|
$this->team->notify(new Test(channel: 'discord'));
|
||||||
$this->dispatch('success', 'Test notification sent.');
|
$this->dispatch('success', 'Test notification sent.');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Notifications;
|
namespace App\Livewire\Notifications;
|
||||||
|
|
||||||
|
use App\Models\EmailNotificationSettings;
|
||||||
use App\Models\Team;
|
use App\Models\Team;
|
||||||
use App\Notifications\Test;
|
use App\Notifications\Test;
|
||||||
use Illuminate\Support\Facades\RateLimiter;
|
use Illuminate\Support\Facades\RateLimiter;
|
||||||
@@ -11,17 +12,20 @@ use Livewire\Component;
|
|||||||
|
|
||||||
class Email extends Component
|
class Email extends Component
|
||||||
{
|
{
|
||||||
|
protected $listeners = ['refresh' => '$refresh'];
|
||||||
|
|
||||||
|
#[Locked]
|
||||||
public Team $team;
|
public Team $team;
|
||||||
|
|
||||||
|
#[Locked]
|
||||||
|
public EmailNotificationSettings $settings;
|
||||||
|
|
||||||
#[Locked]
|
#[Locked]
|
||||||
public string $emails;
|
public string $emails;
|
||||||
|
|
||||||
#[Validate(['boolean'])]
|
#[Validate(['boolean'])]
|
||||||
public bool $smtpEnabled = false;
|
public bool $smtpEnabled = false;
|
||||||
|
|
||||||
#[Validate(['boolean'])]
|
|
||||||
public bool $useInstanceEmailSettings = false;
|
|
||||||
|
|
||||||
#[Validate(['nullable', 'email'])]
|
#[Validate(['nullable', 'email'])]
|
||||||
public ?string $smtpFromAddress = null;
|
public ?string $smtpFromAddress = null;
|
||||||
|
|
||||||
@@ -34,11 +38,11 @@ class Email extends Component
|
|||||||
#[Validate(['nullable', 'string'])]
|
#[Validate(['nullable', 'string'])]
|
||||||
public ?string $smtpHost = null;
|
public ?string $smtpHost = null;
|
||||||
|
|
||||||
#[Validate(['nullable', 'numeric'])]
|
#[Validate(['nullable', 'numeric', 'min:1', 'max:65535'])]
|
||||||
public ?int $smtpPort = null;
|
public ?int $smtpPort = null;
|
||||||
|
|
||||||
#[Validate(['nullable', 'string'])]
|
#[Validate(['nullable', 'string', 'in:tls,ssl,none'])]
|
||||||
public ?string $smtpEncryption = null;
|
public ?string $smtpEncryption = 'tls';
|
||||||
|
|
||||||
#[Validate(['nullable', 'string'])]
|
#[Validate(['nullable', 'string'])]
|
||||||
public ?string $smtpUsername = null;
|
public ?string $smtpUsername = null;
|
||||||
@@ -50,38 +54,61 @@ class Email extends Component
|
|||||||
public ?int $smtpTimeout = null;
|
public ?int $smtpTimeout = null;
|
||||||
|
|
||||||
#[Validate(['boolean'])]
|
#[Validate(['boolean'])]
|
||||||
public bool $smtpNotificationsTest = false;
|
public bool $resendEnabled = 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;
|
|
||||||
|
|
||||||
#[Validate(['nullable', 'string'])]
|
#[Validate(['nullable', 'string'])]
|
||||||
public ?string $resendApiKey = null;
|
public ?string $resendApiKey = null;
|
||||||
|
|
||||||
#[Validate(['required', 'email'])]
|
#[Validate(['boolean'])]
|
||||||
public string $testEmailAddress = '';
|
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;
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->team = auth()->user()->currentTeam();
|
$this->team = auth()->user()->currentTeam();
|
||||||
$this->emails = auth()->user()->email;
|
$this->emails = auth()->user()->email;
|
||||||
|
$this->settings = $this->team->emailNotificationSettings;
|
||||||
$this->syncData();
|
$this->syncData();
|
||||||
|
$this->testEmailAddress = auth()->user()->email;
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
@@ -91,47 +118,191 @@ class Email extends Component
|
|||||||
{
|
{
|
||||||
if ($toModel) {
|
if ($toModel) {
|
||||||
$this->validate();
|
$this->validate();
|
||||||
$this->team->smtp_enabled = $this->smtpEnabled;
|
$this->settings->smtp_enabled = $this->smtpEnabled;
|
||||||
$this->team->smtp_from_address = $this->smtpFromAddress;
|
$this->settings->smtp_from_address = $this->smtpFromAddress;
|
||||||
$this->team->smtp_from_name = $this->smtpFromName;
|
$this->settings->smtp_from_name = $this->smtpFromName;
|
||||||
$this->team->smtp_host = $this->smtpHost;
|
$this->settings->smtp_recipients = $this->smtpRecipients;
|
||||||
$this->team->smtp_port = $this->smtpPort;
|
$this->settings->smtp_host = $this->smtpHost;
|
||||||
$this->team->smtp_encryption = $this->smtpEncryption;
|
$this->settings->smtp_port = $this->smtpPort;
|
||||||
$this->team->smtp_username = $this->smtpUsername;
|
$this->settings->smtp_encryption = $this->smtpEncryption;
|
||||||
$this->team->smtp_password = $this->smtpPassword;
|
$this->settings->smtp_username = $this->smtpUsername;
|
||||||
$this->team->smtp_timeout = $this->smtpTimeout;
|
$this->settings->smtp_password = $this->smtpPassword;
|
||||||
$this->team->smtp_recipients = $this->smtpRecipients;
|
$this->settings->smtp_timeout = $this->smtpTimeout;
|
||||||
$this->team->smtp_notifications_test = $this->smtpNotificationsTest;
|
|
||||||
$this->team->smtp_notifications_deployments = $this->smtpNotificationsDeployments;
|
$this->settings->resend_enabled = $this->resendEnabled;
|
||||||
$this->team->smtp_notifications_status_changes = $this->smtpNotificationsStatusChanges;
|
$this->settings->resend_api_key = $this->resendApiKey;
|
||||||
$this->team->smtp_notifications_database_backups = $this->smtpNotificationsDatabaseBackups;
|
|
||||||
$this->team->smtp_notifications_scheduled_tasks = $this->smtpNotificationsScheduledTasks;
|
$this->settings->use_instance_email_settings = $this->useInstanceEmailSettings;
|
||||||
$this->team->smtp_notifications_server_disk_usage = $this->smtpNotificationsServerDiskUsage;
|
|
||||||
$this->team->use_instance_email_settings = $this->useInstanceEmailSettings;
|
$this->settings->deployment_success_email_notifications = $this->deploymentSuccessEmailNotifications;
|
||||||
$this->team->resend_enabled = $this->resendEnabled;
|
$this->settings->deployment_failure_email_notifications = $this->deploymentFailureEmailNotifications;
|
||||||
$this->team->resend_api_key = $this->resendApiKey;
|
$this->settings->status_change_email_notifications = $this->statusChangeEmailNotifications;
|
||||||
$this->team->save();
|
$this->settings->backup_success_email_notifications = $this->backupSuccessEmailNotifications;
|
||||||
refreshSession();
|
$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 {
|
} else {
|
||||||
$this->smtpEnabled = $this->team->smtp_enabled;
|
$this->smtpEnabled = $this->settings->smtp_enabled;
|
||||||
$this->smtpFromAddress = $this->team->smtp_from_address;
|
$this->smtpFromAddress = $this->settings->smtp_from_address;
|
||||||
$this->smtpFromName = $this->team->smtp_from_name;
|
$this->smtpFromName = $this->settings->smtp_from_name;
|
||||||
$this->smtpHost = $this->team->smtp_host;
|
$this->smtpRecipients = $this->settings->smtp_recipients;
|
||||||
$this->smtpPort = $this->team->smtp_port;
|
$this->smtpHost = $this->settings->smtp_host;
|
||||||
$this->smtpEncryption = $this->team->smtp_encryption;
|
$this->smtpPort = $this->settings->smtp_port;
|
||||||
$this->smtpUsername = $this->team->smtp_username;
|
$this->smtpEncryption = $this->settings->smtp_encryption;
|
||||||
$this->smtpPassword = $this->team->smtp_password;
|
$this->smtpUsername = $this->settings->smtp_username;
|
||||||
$this->smtpTimeout = $this->team->smtp_timeout;
|
$this->smtpPassword = $this->settings->smtp_password;
|
||||||
$this->smtpRecipients = $this->team->smtp_recipients;
|
$this->smtpTimeout = $this->settings->smtp_timeout;
|
||||||
$this->smtpNotificationsTest = $this->team->smtp_notifications_test;
|
|
||||||
$this->smtpNotificationsDeployments = $this->team->smtp_notifications_deployments;
|
$this->resendEnabled = $this->settings->resend_enabled;
|
||||||
$this->smtpNotificationsStatusChanges = $this->team->smtp_notifications_status_changes;
|
$this->resendApiKey = $this->settings->resend_api_key;
|
||||||
$this->smtpNotificationsDatabaseBackups = $this->team->smtp_notifications_database_backups;
|
|
||||||
$this->smtpNotificationsScheduledTasks = $this->team->smtp_notifications_scheduled_tasks;
|
$this->useInstanceEmailSettings = $this->settings->use_instance_email_settings;
|
||||||
$this->smtpNotificationsServerDiskUsage = $this->team->smtp_notifications_server_disk_usage;
|
|
||||||
$this->useInstanceEmailSettings = $this->team->use_instance_email_settings;
|
$this->deploymentSuccessEmailNotifications = $this->settings->deployment_success_email_notifications;
|
||||||
$this->resendEnabled = $this->team->resend_enabled;
|
$this->deploymentFailureEmailNotifications = $this->settings->deployment_failure_email_notifications;
|
||||||
$this->resendApiKey = $this->team->resend_api_key;
|
$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,
|
'test-email:'.$this->team->id,
|
||||||
$perMinute = 0,
|
$perMinute = 0,
|
||||||
function () {
|
function () {
|
||||||
$this->team?->notify(new Test($this->testEmailAddress));
|
$this->team?->notify(new Test($this->testEmailAddress, 'email'));
|
||||||
$this->dispatch('success', 'Test Email sent.');
|
$this->dispatch('success', 'Test Email sent.');
|
||||||
},
|
},
|
||||||
$decaySeconds = 10,
|
$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()
|
public function copyFromInstanceSettings()
|
||||||
{
|
{
|
||||||
$settings = instanceSettings();
|
$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;
|
namespace App\Livewire\Notifications;
|
||||||
|
|
||||||
use App\Models\Team;
|
use App\Models\Team;
|
||||||
|
use App\Models\TelegramNotificationSettings;
|
||||||
use App\Notifications\Test;
|
use App\Notifications\Test;
|
||||||
|
use Livewire\Attributes\Locked;
|
||||||
use Livewire\Attributes\Validate;
|
use Livewire\Attributes\Validate;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Telegram extends Component
|
class Telegram extends Component
|
||||||
{
|
{
|
||||||
|
protected $listeners = ['refresh' => '$refresh'];
|
||||||
|
|
||||||
|
#[Locked]
|
||||||
public Team $team;
|
public Team $team;
|
||||||
|
|
||||||
|
#[Locked]
|
||||||
|
public TelegramNotificationSettings $settings;
|
||||||
|
|
||||||
#[Validate(['boolean'])]
|
#[Validate(['boolean'])]
|
||||||
public bool $telegramEnabled = false;
|
public bool $telegramEnabled = false;
|
||||||
|
|
||||||
@@ -21,42 +29,82 @@ class Telegram extends Component
|
|||||||
public ?string $telegramChatId = null;
|
public ?string $telegramChatId = null;
|
||||||
|
|
||||||
#[Validate(['boolean'])]
|
#[Validate(['boolean'])]
|
||||||
public bool $telegramNotificationsTest = false;
|
public bool $deploymentSuccessTelegramNotifications = false;
|
||||||
|
|
||||||
#[Validate(['boolean'])]
|
#[Validate(['boolean'])]
|
||||||
public bool $telegramNotificationsDeployments = false;
|
public bool $deploymentFailureTelegramNotifications = true;
|
||||||
|
|
||||||
#[Validate(['boolean'])]
|
#[Validate(['boolean'])]
|
||||||
public bool $telegramNotificationsStatusChanges = false;
|
public bool $statusChangeTelegramNotifications = false;
|
||||||
|
|
||||||
#[Validate(['boolean'])]
|
#[Validate(['boolean'])]
|
||||||
public bool $telegramNotificationsDatabaseBackups = false;
|
public bool $backupSuccessTelegramNotifications = false;
|
||||||
|
|
||||||
#[Validate(['boolean'])]
|
#[Validate(['boolean'])]
|
||||||
public bool $telegramNotificationsScheduledTasks = false;
|
public bool $backupFailureTelegramNotifications = true;
|
||||||
|
|
||||||
#[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;
|
|
||||||
|
|
||||||
#[Validate(['boolean'])]
|
#[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()
|
public function mount()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->team = auth()->user()->currentTeam();
|
$this->team = auth()->user()->currentTeam();
|
||||||
|
$this->settings = $this->team->telegramNotificationSettings;
|
||||||
$this->syncData();
|
$this->syncData();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
@@ -67,39 +115,68 @@ class Telegram extends Component
|
|||||||
{
|
{
|
||||||
if ($toModel) {
|
if ($toModel) {
|
||||||
$this->validate();
|
$this->validate();
|
||||||
$this->team->telegram_enabled = $this->telegramEnabled;
|
$this->settings->telegram_enabled = $this->telegramEnabled;
|
||||||
$this->team->telegram_token = $this->telegramToken;
|
$this->settings->telegram_token = $this->telegramToken;
|
||||||
$this->team->telegram_chat_id = $this->telegramChatId;
|
$this->settings->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->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()
|
public function instantSave()
|
||||||
@@ -108,6 +185,8 @@ class Telegram extends Component
|
|||||||
$this->syncData(true);
|
$this->syncData(true);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
|
} finally {
|
||||||
|
$this->dispatch('refresh');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,6 +216,8 @@ class Telegram extends Component
|
|||||||
$this->telegramEnabled = false;
|
$this->telegramEnabled = false;
|
||||||
|
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
|
} finally {
|
||||||
|
$this->dispatch('refresh');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,7 +231,7 @@ class Telegram extends Component
|
|||||||
public function sendTestNotification()
|
public function sendTestNotification()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->team->notify(new Test);
|
$this->team->notify(new Test(channel: 'telegram'));
|
||||||
$this->dispatch('success', 'Test notification sent.');
|
$this->dispatch('success', 'Test notification sent.');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
|
@@ -25,6 +25,9 @@ class Advanced extends Component
|
|||||||
#[Validate(['boolean'])]
|
#[Validate(['boolean'])]
|
||||||
public bool $isAutoDeployEnabled = true;
|
public bool $isAutoDeployEnabled = true;
|
||||||
|
|
||||||
|
#[Validate(['boolean'])]
|
||||||
|
public bool $disableBuildCache = false;
|
||||||
|
|
||||||
#[Validate(['boolean'])]
|
#[Validate(['boolean'])]
|
||||||
public bool $isLogDrainEnabled = false;
|
public bool $isLogDrainEnabled = false;
|
||||||
|
|
||||||
@@ -95,6 +98,7 @@ class Advanced extends Component
|
|||||||
$this->application->settings->is_stripprefix_enabled = $this->isStripprefixEnabled;
|
$this->application->settings->is_stripprefix_enabled = $this->isStripprefixEnabled;
|
||||||
$this->application->settings->is_raw_compose_deployment_enabled = $this->isRawComposeDeploymentEnabled;
|
$this->application->settings->is_raw_compose_deployment_enabled = $this->isRawComposeDeploymentEnabled;
|
||||||
$this->application->settings->connect_to_docker_network = $this->isConnectToDockerNetworkEnabled;
|
$this->application->settings->connect_to_docker_network = $this->isConnectToDockerNetworkEnabled;
|
||||||
|
$this->application->settings->disable_build_cache = $this->disableBuildCache;
|
||||||
$this->application->settings->save();
|
$this->application->settings->save();
|
||||||
} else {
|
} else {
|
||||||
$this->isForceHttpsEnabled = $this->application->isForceHttpsEnabled();
|
$this->isForceHttpsEnabled = $this->application->isForceHttpsEnabled();
|
||||||
@@ -116,6 +120,7 @@ class Advanced extends Component
|
|||||||
$this->customInternalName = $this->application->settings->custom_internal_name;
|
$this->customInternalName = $this->application->settings->custom_internal_name;
|
||||||
$this->isRawComposeDeploymentEnabled = $this->application->settings->is_raw_compose_deployment_enabled;
|
$this->isRawComposeDeploymentEnabled = $this->application->settings->is_raw_compose_deployment_enabled;
|
||||||
$this->isConnectToDockerNetworkEnabled = $this->application->settings->connect_to_docker_network;
|
$this->isConnectToDockerNetworkEnabled = $this->application->settings->connect_to_docker_network;
|
||||||
|
$this->disableBuildCache = $this->application->settings->disable_build_cache;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -3,37 +3,44 @@
|
|||||||
namespace App\Livewire\Project\Application;
|
namespace App\Livewire\Project\Application;
|
||||||
|
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Models\Server;
|
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Configuration extends Component
|
class Configuration extends Component
|
||||||
{
|
{
|
||||||
|
public $currentRoute;
|
||||||
|
|
||||||
public Application $application;
|
public Application $application;
|
||||||
|
|
||||||
|
public $project;
|
||||||
|
|
||||||
|
public $environment;
|
||||||
|
|
||||||
public $servers;
|
public $servers;
|
||||||
|
|
||||||
protected $listeners = ['buildPackUpdated' => '$refresh'];
|
protected $listeners = ['buildPackUpdated' => '$refresh'];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
|
||||||
if (! $project) {
|
$this->currentRoute = request()->route()->getName();
|
||||||
return redirect()->route('dashboard');
|
$project = currentTeam()
|
||||||
}
|
->projects()
|
||||||
$environment = $project->load(['environments'])->environments->where('uuid', request()->route('environment_uuid'))->first()->load(['applications']);
|
->select('id', 'uuid', 'team_id')
|
||||||
if (! $environment) {
|
->where('uuid', request()->route('project_uuid'))
|
||||||
return redirect()->route('dashboard');
|
->firstOrFail();
|
||||||
}
|
$environment = $project->environments()
|
||||||
$application = $environment->applications->where('uuid', request()->route('application_uuid'))->first();
|
->select('id', 'name', 'project_id')
|
||||||
if (! $application) {
|
->where('uuid', request()->route('environment_uuid'))
|
||||||
return redirect()->route('dashboard');
|
->firstOrFail();
|
||||||
}
|
$application = $environment->applications()
|
||||||
|
->with(['destination'])
|
||||||
|
->where('uuid', request()->route('application_uuid'))
|
||||||
|
->firstOrFail();
|
||||||
|
|
||||||
|
$this->project = $project;
|
||||||
|
$this->environment = $environment;
|
||||||
$this->application = $application;
|
$this->application = $application;
|
||||||
$mainServer = $this->application->destination->server;
|
|
||||||
$servers = Server::ownedByCurrentTeam()->get();
|
|
||||||
$this->servers = $servers->filter(function ($server) use ($mainServer) {
|
|
||||||
return $server->id != $mainServer->id;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
|
@@ -23,7 +23,7 @@ class DeploymentNavbar extends Component
|
|||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->application = Application::find($this->application_deployment_queue->application_id);
|
$this->application = Application::ownedByCurrentTeam()->find($this->application_deployment_queue->application_id);
|
||||||
$this->server = $this->application->destination->server;
|
$this->server = $this->application->destination->server;
|
||||||
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
||||||
}
|
}
|
||||||
|
@@ -327,7 +327,7 @@ class General extends Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function set_redirect()
|
public function setRedirect()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$has_www = collect($this->application->fqdns)->filter(fn ($fqdn) => str($fqdn)->contains('www.'))->count();
|
$has_www = collect($this->application->fqdns)->filter(fn ($fqdn) => str($fqdn)->contains('www.'))->count();
|
||||||
@@ -360,10 +360,10 @@ class General extends Component
|
|||||||
if ($warning) {
|
if ($warning) {
|
||||||
$this->dispatch('warning', __('warning.sslipdomain'));
|
$this->dispatch('warning', __('warning.sslipdomain'));
|
||||||
}
|
}
|
||||||
$this->resetDefaultLabels();
|
// $this->resetDefaultLabels();
|
||||||
|
|
||||||
if ($this->application->isDirty('redirect')) {
|
if ($this->application->isDirty('redirect')) {
|
||||||
$this->set_redirect();
|
$this->setRedirect();
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->checkFqdns();
|
$this->checkFqdns();
|
||||||
|
@@ -36,7 +36,11 @@ class Heading extends Component
|
|||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->parameters = get_route_parameters();
|
$this->parameters = [
|
||||||
|
'project_uuid' => $this->application->project()->uuid,
|
||||||
|
'environment_name' => $this->application->environment->name,
|
||||||
|
'application_uuid' => $this->application->uuid,
|
||||||
|
];
|
||||||
$lastDeployment = $this->application->get_last_successful_deployment();
|
$lastDeployment = $this->application->get_last_successful_deployment();
|
||||||
$this->lastDeploymentInfo = data_get_str($lastDeployment, 'commit')->limit(7).' '.data_get($lastDeployment, 'commit_message');
|
$this->lastDeploymentInfo = data_get_str($lastDeployment, 'commit')->limit(7).' '.data_get($lastDeployment, 'commit_message');
|
||||||
$this->lastDeploymentLink = $this->application->gitCommitLink(data_get($lastDeployment, 'commit'));
|
$this->lastDeploymentLink = $this->application->gitCommitLink(data_get($lastDeployment, 'commit'));
|
||||||
|
@@ -121,7 +121,7 @@ class CloneMe extends Component
|
|||||||
$environmentVaribles = $application->environment_variables()->get();
|
$environmentVaribles = $application->environment_variables()->get();
|
||||||
foreach ($environmentVaribles as $environmentVarible) {
|
foreach ($environmentVaribles as $environmentVarible) {
|
||||||
$newEnvironmentVariable = $environmentVarible->replicate()->fill([
|
$newEnvironmentVariable = $environmentVarible->replicate()->fill([
|
||||||
'application_id' => $newApplication->id,
|
'resourceable_id' => $newApplication->id,
|
||||||
]);
|
]);
|
||||||
$newEnvironmentVariable->save();
|
$newEnvironmentVariable->save();
|
||||||
}
|
}
|
||||||
@@ -147,17 +147,8 @@ class CloneMe extends Component
|
|||||||
$environmentVaribles = $database->environment_variables()->get();
|
$environmentVaribles = $database->environment_variables()->get();
|
||||||
foreach ($environmentVaribles as $environmentVarible) {
|
foreach ($environmentVaribles as $environmentVarible) {
|
||||||
$payload = [];
|
$payload = [];
|
||||||
if ($database->type() === 'standalone-postgresql') {
|
$payload['resourceable_id'] = $newDatabase->id;
|
||||||
$payload['standalone_postgresql_id'] = $newDatabase->id;
|
$payload['resourceable_type'] = $newDatabase->getMorphClass();
|
||||||
} elseif ($database->type() === 'standalone-redis') {
|
|
||||||
$payload['standalone_redis_id'] = $newDatabase->id;
|
|
||||||
} elseif ($database->type() === 'standalone-mongodb') {
|
|
||||||
$payload['standalone_mongodb_id'] = $newDatabase->id;
|
|
||||||
} elseif ($database->type() === 'standalone-mysql') {
|
|
||||||
$payload['standalone_mysql_id'] = $newDatabase->id;
|
|
||||||
} elseif ($database->type() === 'standalone-mariadb') {
|
|
||||||
$payload['standalone_mariadb_id'] = $newDatabase->id;
|
|
||||||
}
|
|
||||||
$newEnvironmentVariable = $environmentVarible->replicate()->fill($payload);
|
$newEnvironmentVariable = $environmentVarible->replicate()->fill($payload);
|
||||||
$newEnvironmentVariable->save();
|
$newEnvironmentVariable->save();
|
||||||
}
|
}
|
||||||
|
@@ -9,11 +9,9 @@ class BackupNow extends Component
|
|||||||
{
|
{
|
||||||
public $backup;
|
public $backup;
|
||||||
|
|
||||||
public function backup_now()
|
public function backupNow()
|
||||||
{
|
{
|
||||||
dispatch(new DatabaseBackupJob(
|
DatabaseBackupJob::dispatch($this->backup);
|
||||||
backup: $this->backup
|
|
||||||
));
|
|
||||||
$this->dispatch('success', 'Backup queued. It will be available in a few minutes.');
|
$this->dispatch('success', 'Backup queued. It will be available in a few minutes.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,23 +6,34 @@ use Livewire\Component;
|
|||||||
|
|
||||||
class Configuration extends Component
|
class Configuration extends Component
|
||||||
{
|
{
|
||||||
|
public $currentRoute;
|
||||||
|
|
||||||
public $database;
|
public $database;
|
||||||
|
|
||||||
|
public $project;
|
||||||
|
|
||||||
|
public $environment;
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
$this->currentRoute = request()->route()->getName();
|
||||||
if (! $project) {
|
|
||||||
return redirect()->route('dashboard');
|
$project = currentTeam()
|
||||||
}
|
->projects()
|
||||||
$environment = $project->load(['environments'])->environments->where('uuid', request()->route('environment_uuid'))->first()->load(['applications']);
|
->select('id', 'uuid', 'team_id')
|
||||||
if (! $environment) {
|
->where('uuid', request()->route('project_uuid'))
|
||||||
return redirect()->route('dashboard');
|
->firstOrFail();
|
||||||
}
|
$environment = $project->environments()
|
||||||
$database = $environment->databases()->where('uuid', request()->route('database_uuid'))->first();
|
->select('id', 'name', 'project_id')
|
||||||
if (! $database) {
|
->where('uuid', request()->route('environment_uuid'))
|
||||||
return redirect()->route('dashboard');
|
->firstOrFail();
|
||||||
}
|
$database = $environment->databases()
|
||||||
|
->where('uuid', request()->route('database_uuid'))
|
||||||
|
->firstOrFail();
|
||||||
|
|
||||||
$this->database = $database;
|
$this->database = $database;
|
||||||
|
$this->project = $project;
|
||||||
|
$this->environment = $environment;
|
||||||
if (str($this->database->status)->startsWith('running') && is_null($this->database->config_hash)) {
|
if (str($this->database->status)->startsWith('running') && is_null($this->database->config_hash)) {
|
||||||
$this->database->isConfigurationChanged(true);
|
$this->database->isConfigurationChanged(true);
|
||||||
$this->dispatch('configurationChanged');
|
$this->dispatch('configurationChanged');
|
||||||
|
@@ -87,7 +87,8 @@ class DockerCompose extends Component
|
|||||||
'value' => $variable,
|
'value' => $variable,
|
||||||
'is_build_time' => false,
|
'is_build_time' => false,
|
||||||
'is_preview' => false,
|
'is_preview' => false,
|
||||||
'service_id' => $service->id,
|
'resourceable_id' => $service->id,
|
||||||
|
'resourceable_type' => $service->getMorphClass(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
$service->name = "service-$service->uuid";
|
$service->name = "service-$service->uuid";
|
||||||
|
File diff suppressed because one or more lines are too long
@@ -95,7 +95,8 @@ class Create extends Component
|
|||||||
EnvironmentVariable::create([
|
EnvironmentVariable::create([
|
||||||
'key' => $key,
|
'key' => $key,
|
||||||
'value' => $value,
|
'value' => $value,
|
||||||
'service_id' => $service->id,
|
'resourceable_id' => $service->id,
|
||||||
|
'resourceable_type' => $service->getMorphClass(),
|
||||||
'is_build_time' => false,
|
'is_build_time' => false,
|
||||||
'is_preview' => false,
|
'is_preview' => false,
|
||||||
]);
|
]);
|
||||||
|
@@ -46,126 +46,83 @@ class Index extends Component
|
|||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
}
|
}
|
||||||
$this->project = $project;
|
$this->project = $project;
|
||||||
$this->environment = $environment;
|
$this->environment = $environment->loadCount([
|
||||||
$this->applications = $this->environment->applications->load(['tags']);
|
'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) {
|
$this->applications = $this->applications->map(function ($application) {
|
||||||
if (data_get($application, 'environment.project.uuid')) {
|
$application->hrefLink = route('project.application.configuration', [
|
||||||
$application->hrefLink = route('project.application.configuration', [
|
|
||||||
'project_uuid' => data_get($application, 'environment.project.uuid'),
|
'project_uuid' => data_get($application, 'environment.project.uuid'),
|
||||||
'environment_uuid' => data_get($application, 'environment.uuid'),
|
'environment_uuid' => data_get($application, 'environment.uuid'),
|
||||||
'application_uuid' => data_get($application, 'uuid'),
|
'application_uuid' => data_get($application, 'uuid'),
|
||||||
]);
|
]);
|
||||||
}
|
|
||||||
|
|
||||||
return $application;
|
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_uuid' => data_get($postgresql, 'environment.uuid'),
|
|
||||||
'database_uuid' => data_get($postgresql, 'uuid'),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $postgresql;
|
// Load all database resources in a single query per type
|
||||||
});
|
$databaseTypes = [
|
||||||
$this->redis = $this->environment->redis->load(['tags'])->sortBy('name');
|
'postgresqls' => 'postgresqls',
|
||||||
$this->redis = $this->redis->map(function ($redis) {
|
'redis' => 'redis',
|
||||||
if (data_get($redis, 'environment.project.uuid')) {
|
'mongodbs' => 'mongodbs',
|
||||||
$redis->hrefLink = route('project.database.configuration', [
|
'mysqls' => 'mysqls',
|
||||||
'project_uuid' => data_get($redis, 'environment.project.uuid'),
|
'mariadbs' => 'mariadbs',
|
||||||
'environment_uuid' => data_get($redis, 'environment.uuid'),
|
'keydbs' => 'keydbs',
|
||||||
'database_uuid' => data_get($redis, 'uuid'),
|
'dragonflies' => 'dragonflies',
|
||||||
]);
|
'clickhouses' => 'clickhouses',
|
||||||
}
|
];
|
||||||
|
|
||||||
return $redis;
|
// Load all server-related data first to prevent duplicate queries
|
||||||
});
|
$serverData = $this->environment->applications()
|
||||||
$this->mongodbs = $this->environment->mongodbs->load(['tags'])->sortBy('name');
|
->with(['destination.server.settings'])
|
||||||
$this->mongodbs = $this->mongodbs->map(function ($mongodb) {
|
->get()
|
||||||
if (data_get($mongodb, 'environment.project.uuid')) {
|
->pluck('destination.server')
|
||||||
$mongodb->hrefLink = route('project.database.configuration', [
|
->filter()
|
||||||
'project_uuid' => data_get($mongodb, 'environment.project.uuid'),
|
->unique('id');
|
||||||
'environment_uuid' => data_get($mongodb, 'environment.uuid'),
|
|
||||||
'database_uuid' => data_get($mongodb, 'uuid'),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $mongodb;
|
foreach ($databaseTypes as $property => $relation) {
|
||||||
});
|
$this->{$property} = $this->environment->{$relation}()->with([
|
||||||
$this->mysqls = $this->environment->mysqls->load(['tags'])->sortBy('name');
|
'tags',
|
||||||
$this->mysqls = $this->mysqls->map(function ($mysql) {
|
'destination.server.settings',
|
||||||
if (data_get($mysql, 'environment.project.uuid')) {
|
])->get()->sortBy('name');
|
||||||
$mysql->hrefLink = route('project.database.configuration', [
|
$this->{$property} = $this->{$property}->map(function ($db) {
|
||||||
'project_uuid' => data_get($mysql, 'environment.project.uuid'),
|
$db->hrefLink = route('project.database.configuration', [
|
||||||
'environment_uuid' => data_get($mysql, 'environment.uuid'),
|
'project_uuid' => $this->project->uuid,
|
||||||
'database_uuid' => data_get($mysql, 'uuid'),
|
'database_uuid' => $db->uuid,
|
||||||
|
'environment_uuid' => data_get($this->environment, 'uuid'),
|
||||||
]);
|
]);
|
||||||
}
|
return $db;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return $mysql;
|
// Load services with their tags and server
|
||||||
});
|
$this->services = $this->environment->services()->with([
|
||||||
$this->mariadbs = $this->environment->mariadbs->load(['tags'])->sortBy('name');
|
'tags',
|
||||||
$this->mariadbs = $this->mariadbs->map(function ($mariadb) {
|
'destination.server.settings',
|
||||||
if (data_get($mariadb, 'environment.project.uuid')) {
|
])->get()->sortBy('name');
|
||||||
$mariadb->hrefLink = route('project.database.configuration', [
|
|
||||||
'project_uuid' => data_get($mariadb, 'environment.project.uuid'),
|
|
||||||
'environment_uuid' => data_get($mariadb, 'environment.uuid'),
|
|
||||||
'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_uuid' => data_get($keydb, 'environment.uuid'),
|
|
||||||
'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_uuid' => data_get($dragonfly, 'environment.uuid'),
|
|
||||||
'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_uuid' => data_get($clickhouse, 'environment.uuid'),
|
|
||||||
'database_uuid' => data_get($clickhouse, 'uuid'),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $clickhouse;
|
|
||||||
});
|
|
||||||
$this->services = $this->environment->services->load(['tags'])->sortBy('name');
|
|
||||||
$this->services = $this->services->map(function ($service) {
|
$this->services = $this->services->map(function ($service) {
|
||||||
if (data_get($service, 'environment.project.uuid')) {
|
$service->hrefLink = route('project.service.configuration', [
|
||||||
$service->hrefLink = route('project.service.configuration', [
|
|
||||||
'project_uuid' => data_get($service, 'environment.project.uuid'),
|
'project_uuid' => data_get($service, 'environment.project.uuid'),
|
||||||
'environment_uuid' => data_get($service, 'environment.uuid'),
|
'environment_uuid' => data_get($service, 'environment.uuid'),
|
||||||
'service_uuid' => data_get($service, 'uuid'),
|
'service_uuid' => data_get($service, 'uuid'),
|
||||||
]);
|
]);
|
||||||
$service->status = $service->status();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $service;
|
return $service;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -9,16 +9,22 @@ use Livewire\Component;
|
|||||||
|
|
||||||
class Configuration extends Component
|
class Configuration extends Component
|
||||||
{
|
{
|
||||||
|
public $currentRoute;
|
||||||
|
|
||||||
|
public $project;
|
||||||
|
|
||||||
|
public $environment;
|
||||||
|
|
||||||
public ?Service $service = null;
|
public ?Service $service = null;
|
||||||
|
|
||||||
public $applications;
|
public $applications;
|
||||||
|
|
||||||
public $databases;
|
public $databases;
|
||||||
|
|
||||||
public array $parameters;
|
|
||||||
|
|
||||||
public array $query;
|
public array $query;
|
||||||
|
|
||||||
|
public array $parameters;
|
||||||
|
|
||||||
public function getListeners()
|
public function getListeners()
|
||||||
{
|
{
|
||||||
$userId = Auth::id();
|
$userId = Auth::id();
|
||||||
@@ -38,11 +44,21 @@ class Configuration extends Component
|
|||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->parameters = get_route_parameters();
|
$this->parameters = get_route_parameters();
|
||||||
|
$this->currentRoute = request()->route()->getName();
|
||||||
$this->query = request()->query();
|
$this->query = request()->query();
|
||||||
$this->service = Service::whereUuid($this->parameters['service_uuid'])->first();
|
$project = currentTeam()
|
||||||
if (! $this->service) {
|
->projects()
|
||||||
return redirect()->route('dashboard');
|
->select('id', 'uuid', 'team_id')
|
||||||
}
|
->where('uuid', request()->route('project_uuid'))
|
||||||
|
->firstOrFail();
|
||||||
|
$environment = $project->environments()
|
||||||
|
->select('id', 'name', 'project_id')
|
||||||
|
->where('name', request()->route('environment_name'))
|
||||||
|
->firstOrFail();
|
||||||
|
$this->service = $environment->services()->whereUuid(request()->route('service_uuid'))->firstOrFail();
|
||||||
|
|
||||||
|
$this->project = $project;
|
||||||
|
$this->environment = $environment;
|
||||||
$this->applications = $this->service->applications->sort();
|
$this->applications = $this->service->applications->sort();
|
||||||
$this->databases = $this->service->databases->sort();
|
$this->databases = $this->service->databases->sort();
|
||||||
}
|
}
|
||||||
|
@@ -27,7 +27,7 @@ class Navbar extends Component
|
|||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
if (str($this->service->status())->contains('running') && is_null($this->service->config_hash)) {
|
if (str($this->service->status)->contains('running') && is_null($this->service->config_hash)) {
|
||||||
$this->service->isConfigurationChanged(true);
|
$this->service->isConfigurationChanged(true);
|
||||||
$this->dispatch('configurationChanged');
|
$this->dispatch('configurationChanged');
|
||||||
}
|
}
|
||||||
|
@@ -8,6 +8,7 @@ use App\Events\ApplicationStatusChanged;
|
|||||||
use App\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\StandaloneDocker;
|
use App\Models\StandaloneDocker;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
@@ -17,7 +18,7 @@ class Destination extends Component
|
|||||||
{
|
{
|
||||||
public $resource;
|
public $resource;
|
||||||
|
|
||||||
public $networks = [];
|
public Collection $networks;
|
||||||
|
|
||||||
public function getListeners()
|
public function getListeners()
|
||||||
{
|
{
|
||||||
@@ -30,6 +31,7 @@ class Destination extends Component
|
|||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
|
$this->networks = collect([]);
|
||||||
$this->loadData();
|
$this->loadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,38 +57,46 @@ class Destination extends Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function stop(int $server_id)
|
public function stop($serverId)
|
||||||
{
|
{
|
||||||
$server = Server::find($server_id);
|
try {
|
||||||
StopApplicationOneServer::run($this->resource, $server);
|
$server = Server::ownedByCurrentTeam()->findOrFail($serverId);
|
||||||
$this->refreshServers();
|
StopApplicationOneServer::run($this->resource, $server);
|
||||||
|
$this->refreshServers();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function redeploy(int $network_id, int $server_id)
|
public function redeploy(int $network_id, int $server_id)
|
||||||
{
|
{
|
||||||
if ($this->resource->additional_servers->count() > 0 && str($this->resource->docker_registry_image_name)->isEmpty()) {
|
try {
|
||||||
$this->dispatch('error', 'Failed to deploy.', 'Before deploying to multiple servers, you must first set a Docker image in the General tab.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/multiple-servers">documentation</a>');
|
if ($this->resource->additional_servers->count() > 0 && str($this->resource->docker_registry_image_name)->isEmpty()) {
|
||||||
|
$this->dispatch('error', 'Failed to deploy.', 'Before deploying to multiple servers, you must first set a Docker image in the General tab.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/multiple-servers">documentation</a>');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
$deployment_uuid = new Cuid2;
|
||||||
|
$server = Server::ownedByCurrentTeam()->findOrFail($server_id);
|
||||||
|
$destination = $server->standaloneDockers->where('id', $network_id)->firstOrFail();
|
||||||
|
queue_application_deployment(
|
||||||
|
deployment_uuid: $deployment_uuid,
|
||||||
|
application: $this->resource,
|
||||||
|
server: $server,
|
||||||
|
destination: $destination,
|
||||||
|
only_this_server: true,
|
||||||
|
no_questions_asked: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
return redirect()->route('project.application.deployment.show', [
|
||||||
|
'project_uuid' => data_get($this->resource, 'environment.project.uuid'),
|
||||||
|
'application_uuid' => data_get($this->resource, 'uuid'),
|
||||||
|
'deployment_uuid' => $deployment_uuid,
|
||||||
|
'environment_uuid' => data_get($this->resource, 'environment.uuid'),
|
||||||
|
]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
$deployment_uuid = new Cuid2;
|
|
||||||
$server = Server::find($server_id);
|
|
||||||
$destination = StandaloneDocker::find($network_id);
|
|
||||||
queue_application_deployment(
|
|
||||||
deployment_uuid: $deployment_uuid,
|
|
||||||
application: $this->resource,
|
|
||||||
server: $server,
|
|
||||||
destination: $destination,
|
|
||||||
only_this_server: true,
|
|
||||||
no_questions_asked: true,
|
|
||||||
);
|
|
||||||
|
|
||||||
return redirect()->route('project.application.deployment.show', [
|
|
||||||
'project_uuid' => data_get($this->resource, 'environment.project.uuid'),
|
|
||||||
'application_uuid' => data_get($this->resource, 'uuid'),
|
|
||||||
'deployment_uuid' => $deployment_uuid,
|
|
||||||
'environment_uuid' => data_get($this->resource, 'environment.uuid'),
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function promote(int $network_id, int $server_id)
|
public function promote(int $network_id, int $server_id)
|
||||||
@@ -119,23 +129,27 @@ class Destination extends Component
|
|||||||
|
|
||||||
public function removeServer(int $network_id, int $server_id, $password)
|
public function removeServer(int $network_id, int $server_id, $password)
|
||||||
{
|
{
|
||||||
if (! data_get(InstanceSettings::get(), 'disable_two_step_confirmation')) {
|
try {
|
||||||
if (! Hash::check($password, Auth::user()->password)) {
|
if (! data_get(InstanceSettings::get(), 'disable_two_step_confirmation')) {
|
||||||
$this->addError('password', 'The provided password is incorrect.');
|
if (! Hash::check($password, Auth::user()->password)) {
|
||||||
|
$this->addError('password', 'The provided password is incorrect.');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->resource->destination->server->id == $server_id && $this->resource->destination->id == $network_id) {
|
||||||
|
$this->dispatch('error', 'You cannot remove this destination server.', 'You are trying to remove the main server.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
$server = Server::ownedByCurrentTeam()->findOrFail($server_id);
|
||||||
|
StopApplicationOneServer::run($this->resource, $server);
|
||||||
|
$this->resource->additional_networks()->detach($network_id, ['server_id' => $server_id]);
|
||||||
|
$this->loadData();
|
||||||
|
ApplicationStatusChanged::dispatch(data_get($this->resource, 'environment.project.team.id'));
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->resource->destination->server->id == $server_id && $this->resource->destination->id == $network_id) {
|
|
||||||
$this->dispatch('error', 'You cannot remove this destination server.', 'You are trying to remove the main server.');
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$server = Server::find($server_id);
|
|
||||||
StopApplicationOneServer::run($this->resource, $server);
|
|
||||||
$this->resource->additional_networks()->detach($network_id, ['server_id' => $server_id]);
|
|
||||||
$this->loadData();
|
|
||||||
ApplicationStatusChanged::dispatch(data_get($this->resource, 'environment.project.team.id'));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,7 +4,6 @@ namespace App\Livewire\Project\Shared\EnvironmentVariable;
|
|||||||
|
|
||||||
use App\Models\EnvironmentVariable;
|
use App\Models\EnvironmentVariable;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Visus\Cuid2\Cuid2;
|
|
||||||
|
|
||||||
class All extends Component
|
class All extends Component
|
||||||
{
|
{
|
||||||
@@ -14,38 +13,35 @@ class All extends Component
|
|||||||
|
|
||||||
public bool $showPreview = false;
|
public bool $showPreview = false;
|
||||||
|
|
||||||
public ?string $modalId = null;
|
|
||||||
|
|
||||||
public ?string $variables = null;
|
public ?string $variables = null;
|
||||||
|
|
||||||
public ?string $variablesPreview = null;
|
public ?string $variablesPreview = null;
|
||||||
|
|
||||||
public string $view = 'normal';
|
public string $view = 'normal';
|
||||||
|
|
||||||
|
public bool $is_env_sorting_enabled = false;
|
||||||
|
|
||||||
protected $listeners = [
|
protected $listeners = [
|
||||||
'saveKey' => 'submit',
|
'saveKey' => 'submit',
|
||||||
'refreshEnvs',
|
'refreshEnvs',
|
||||||
'environmentVariableDeleted' => 'refreshEnvs',
|
'environmentVariableDeleted' => 'refreshEnvs',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $rules = [
|
|
||||||
'resource.settings.is_env_sorting_enabled' => 'required|boolean',
|
|
||||||
];
|
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
|
$this->is_env_sorting_enabled = data_get($this->resource, 'settings.is_env_sorting_enabled', false);
|
||||||
$this->resourceClass = get_class($this->resource);
|
$this->resourceClass = get_class($this->resource);
|
||||||
$resourceWithPreviews = [\App\Models\Application::class];
|
$resourceWithPreviews = [\App\Models\Application::class];
|
||||||
$simpleDockerfile = ! is_null(data_get($this->resource, 'dockerfile'));
|
$simpleDockerfile = filled(data_get($this->resource, 'dockerfile'));
|
||||||
if (str($this->resourceClass)->contains($resourceWithPreviews) && ! $simpleDockerfile) {
|
if (str($this->resourceClass)->contains($resourceWithPreviews) && ! $simpleDockerfile) {
|
||||||
$this->showPreview = true;
|
$this->showPreview = true;
|
||||||
}
|
}
|
||||||
$this->modalId = new Cuid2;
|
|
||||||
$this->sortEnvironmentVariables();
|
$this->sortEnvironmentVariables();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
|
$this->resource->settings->is_env_sorting_enabled = $this->is_env_sorting_enabled;
|
||||||
$this->resource->settings->save();
|
$this->resource->settings->save();
|
||||||
$this->sortEnvironmentVariables();
|
$this->sortEnvironmentVariables();
|
||||||
$this->dispatch('success', 'Environment variable settings updated.');
|
$this->dispatch('success', 'Environment variable settings updated.');
|
||||||
@@ -53,7 +49,7 @@ class All extends Component
|
|||||||
|
|
||||||
public function sortEnvironmentVariables()
|
public function sortEnvironmentVariables()
|
||||||
{
|
{
|
||||||
if (! data_get($this->resource, 'settings.is_env_sorting_enabled')) {
|
if ($this->is_env_sorting_enabled === false) {
|
||||||
if ($this->resource->environment_variables) {
|
if ($this->resource->environment_variables) {
|
||||||
$this->resource->environment_variables = $this->resource->environment_variables->sortBy('order')->values();
|
$this->resource->environment_variables = $this->resource->environment_variables->sortBy('order')->values();
|
||||||
}
|
}
|
||||||
@@ -178,35 +174,12 @@ class All extends Component
|
|||||||
$environment->is_multiline = $data['is_multiline'] ?? false;
|
$environment->is_multiline = $data['is_multiline'] ?? false;
|
||||||
$environment->is_literal = $data['is_literal'] ?? false;
|
$environment->is_literal = $data['is_literal'] ?? false;
|
||||||
$environment->is_preview = $data['is_preview'] ?? false;
|
$environment->is_preview = $data['is_preview'] ?? false;
|
||||||
|
$environment->resourceable_id = $this->resource->id;
|
||||||
$resourceType = $this->resource->type();
|
$environment->resourceable_type = $this->resource->getMorphClass();
|
||||||
$resourceIdField = $this->getResourceIdField($resourceType);
|
|
||||||
|
|
||||||
if ($resourceIdField) {
|
|
||||||
$environment->$resourceIdField = $this->resource->id;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $environment;
|
return $environment;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getResourceIdField($resourceType)
|
|
||||||
{
|
|
||||||
$resourceTypes = [
|
|
||||||
'application' => 'application_id',
|
|
||||||
'standalone-postgresql' => 'standalone_postgresql_id',
|
|
||||||
'standalone-redis' => 'standalone_redis_id',
|
|
||||||
'standalone-mongodb' => 'standalone_mongodb_id',
|
|
||||||
'standalone-mysql' => 'standalone_mysql_id',
|
|
||||||
'standalone-mariadb' => 'standalone_mariadb_id',
|
|
||||||
'standalone-keydb' => 'standalone_keydb_id',
|
|
||||||
'standalone-dragonfly' => 'standalone_dragonfly_id',
|
|
||||||
'standalone-clickhouse' => 'standalone_clickhouse_id',
|
|
||||||
'service' => 'service_id',
|
|
||||||
];
|
|
||||||
|
|
||||||
return $resourceTypes[$resourceType] ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function deleteRemovedVariables($isPreview, $variables)
|
private function deleteRemovedVariables($isPreview, $variables)
|
||||||
{
|
{
|
||||||
$method = $isPreview ? 'environment_variables_preview' : 'environment_variables';
|
$method = $isPreview ? 'environment_variables_preview' : 'environment_variables';
|
||||||
@@ -231,34 +204,14 @@ class All extends Component
|
|||||||
$environment->is_build_time = false;
|
$environment->is_build_time = false;
|
||||||
$environment->is_multiline = false;
|
$environment->is_multiline = false;
|
||||||
$environment->is_preview = $isPreview;
|
$environment->is_preview = $isPreview;
|
||||||
|
$environment->resourceable_id = $this->resource->id;
|
||||||
|
$environment->resourceable_type = $this->resource->getMorphClass();
|
||||||
|
|
||||||
$this->setEnvironmentResourceId($environment);
|
|
||||||
$environment->save();
|
$environment->save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function setEnvironmentResourceId($environment)
|
|
||||||
{
|
|
||||||
$resourceTypes = [
|
|
||||||
'application' => 'application_id',
|
|
||||||
'standalone-postgresql' => 'standalone_postgresql_id',
|
|
||||||
'standalone-redis' => 'standalone_redis_id',
|
|
||||||
'standalone-mongodb' => 'standalone_mongodb_id',
|
|
||||||
'standalone-mysql' => 'standalone_mysql_id',
|
|
||||||
'standalone-mariadb' => 'standalone_mariadb_id',
|
|
||||||
'standalone-keydb' => 'standalone_keydb_id',
|
|
||||||
'standalone-dragonfly' => 'standalone_dragonfly_id',
|
|
||||||
'standalone-clickhouse' => 'standalone_clickhouse_id',
|
|
||||||
'service' => 'service_id',
|
|
||||||
];
|
|
||||||
|
|
||||||
$resourceType = $this->resource->type();
|
|
||||||
if (isset($resourceTypes[$resourceType])) {
|
|
||||||
$environment->{$resourceTypes[$resourceType]} = $this->resource->id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function refreshEnvs()
|
public function refreshEnvs()
|
||||||
{
|
{
|
||||||
$this->resource->refresh();
|
$this->resource->refresh();
|
||||||
|
@@ -5,7 +5,6 @@ namespace App\Livewire\Project\Shared\EnvironmentVariable;
|
|||||||
use App\Models\EnvironmentVariable as ModelsEnvironmentVariable;
|
use App\Models\EnvironmentVariable as ModelsEnvironmentVariable;
|
||||||
use App\Models\SharedEnvironmentVariable;
|
use App\Models\SharedEnvironmentVariable;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Visus\Cuid2\Cuid2;
|
|
||||||
|
|
||||||
class Show extends Component
|
class Show extends Component
|
||||||
{
|
{
|
||||||
@@ -13,8 +12,6 @@ class Show extends Component
|
|||||||
|
|
||||||
public ModelsEnvironmentVariable|SharedEnvironmentVariable $env;
|
public ModelsEnvironmentVariable|SharedEnvironmentVariable $env;
|
||||||
|
|
||||||
public ?string $modalId = null;
|
|
||||||
|
|
||||||
public bool $isDisabled = false;
|
public bool $isDisabled = false;
|
||||||
|
|
||||||
public bool $isLocked = false;
|
public bool $isLocked = false;
|
||||||
@@ -23,6 +20,26 @@ class Show extends Component
|
|||||||
|
|
||||||
public string $type;
|
public string $type;
|
||||||
|
|
||||||
|
public string $key;
|
||||||
|
|
||||||
|
public ?string $value = null;
|
||||||
|
|
||||||
|
public ?string $real_value = null;
|
||||||
|
|
||||||
|
public bool $is_shared = false;
|
||||||
|
|
||||||
|
public bool $is_build_time = false;
|
||||||
|
|
||||||
|
public bool $is_multiline = false;
|
||||||
|
|
||||||
|
public bool $is_literal = false;
|
||||||
|
|
||||||
|
public bool $is_shown_once = false;
|
||||||
|
|
||||||
|
public bool $is_required = false;
|
||||||
|
|
||||||
|
public bool $is_really_required = false;
|
||||||
|
|
||||||
protected $listeners = [
|
protected $listeners = [
|
||||||
'refreshEnvs' => 'refresh',
|
'refreshEnvs' => 'refresh',
|
||||||
'refresh',
|
'refresh',
|
||||||
@@ -30,40 +47,59 @@ class Show extends Component
|
|||||||
];
|
];
|
||||||
|
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'env.key' => 'required|string',
|
'key' => 'required|string',
|
||||||
'env.value' => 'nullable',
|
'value' => 'nullable',
|
||||||
'env.is_build_time' => 'required|boolean',
|
'is_build_time' => 'required|boolean',
|
||||||
'env.is_multiline' => 'required|boolean',
|
'is_multiline' => 'required|boolean',
|
||||||
'env.is_literal' => 'required|boolean',
|
'is_literal' => 'required|boolean',
|
||||||
'env.is_shown_once' => 'required|boolean',
|
'is_shown_once' => 'required|boolean',
|
||||||
'env.real_value' => 'nullable',
|
'real_value' => 'nullable',
|
||||||
'env.is_required' => 'required|boolean',
|
'is_required' => 'required|boolean',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $validationAttributes = [
|
|
||||||
'env.key' => 'Key',
|
|
||||||
'env.value' => 'Value',
|
|
||||||
'env.is_build_time' => 'Build Time',
|
|
||||||
'env.is_multiline' => 'Multiline',
|
|
||||||
'env.is_literal' => 'Literal',
|
|
||||||
'env.is_shown_once' => 'Shown Once',
|
|
||||||
'env.is_required' => 'Required',
|
|
||||||
];
|
|
||||||
|
|
||||||
public function refresh()
|
|
||||||
{
|
|
||||||
$this->env->refresh();
|
|
||||||
$this->checkEnvs();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
|
$this->syncData();
|
||||||
if ($this->env->getMorphClass() === \App\Models\SharedEnvironmentVariable::class) {
|
if ($this->env->getMorphClass() === \App\Models\SharedEnvironmentVariable::class) {
|
||||||
$this->isSharedVariable = true;
|
$this->isSharedVariable = true;
|
||||||
}
|
}
|
||||||
$this->modalId = new Cuid2;
|
|
||||||
$this->parameters = get_route_parameters();
|
$this->parameters = get_route_parameters();
|
||||||
$this->checkEnvs();
|
$this->checkEnvs();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function refresh()
|
||||||
|
{
|
||||||
|
$this->syncData();
|
||||||
|
$this->checkEnvs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function syncData(bool $toModel = false)
|
||||||
|
{
|
||||||
|
if ($toModel) {
|
||||||
|
$this->validate();
|
||||||
|
$this->env->key = $this->key;
|
||||||
|
$this->env->value = $this->value;
|
||||||
|
$this->env->is_build_time = $this->is_build_time;
|
||||||
|
$this->env->is_multiline = $this->is_multiline;
|
||||||
|
$this->env->is_literal = $this->is_literal;
|
||||||
|
$this->env->is_shown_once = $this->is_shown_once;
|
||||||
|
$this->env->is_required = $this->is_required;
|
||||||
|
$this->env->is_shared = $this->is_shared;
|
||||||
|
$this->env->save();
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$this->key = $this->env->key;
|
||||||
|
$this->value = $this->env->value;
|
||||||
|
$this->is_build_time = $this->env->is_build_time ?? false;
|
||||||
|
$this->is_multiline = $this->env->is_multiline;
|
||||||
|
$this->is_literal = $this->env->is_literal;
|
||||||
|
$this->is_shown_once = $this->env->is_shown_once;
|
||||||
|
$this->is_required = $this->env->is_required ?? false;
|
||||||
|
$this->is_really_required = $this->env->is_really_required ?? false;
|
||||||
|
$this->is_shared = $this->env->is_shared ?? false;
|
||||||
|
$this->real_value = $this->env->real_value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function checkEnvs()
|
public function checkEnvs()
|
||||||
@@ -88,6 +124,9 @@ class Show extends Component
|
|||||||
public function lock()
|
public function lock()
|
||||||
{
|
{
|
||||||
$this->env->is_shown_once = true;
|
$this->env->is_shown_once = true;
|
||||||
|
if ($this->isSharedVariable) {
|
||||||
|
unset($this->env->is_required);
|
||||||
|
}
|
||||||
$this->serialize();
|
$this->serialize();
|
||||||
$this->env->save();
|
$this->env->save();
|
||||||
$this->checkEnvs();
|
$this->checkEnvs();
|
||||||
@@ -104,17 +143,17 @@ class Show extends Component
|
|||||||
try {
|
try {
|
||||||
if ($this->isSharedVariable) {
|
if ($this->isSharedVariable) {
|
||||||
$this->validate([
|
$this->validate([
|
||||||
'env.key' => 'required|string',
|
'key' => 'required|string',
|
||||||
'env.value' => 'nullable',
|
'value' => 'nullable',
|
||||||
'env.is_shown_once' => 'required|boolean',
|
'is_shown_once' => 'required|boolean',
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
$this->validate();
|
$this->validate();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! $this->isSharedVariable && $this->env->is_required && str($this->env->real_value)->isEmpty()) {
|
if (! $this->isSharedVariable && $this->is_required && str($this->value)->isEmpty()) {
|
||||||
$oldValue = $this->env->getOriginal('value');
|
$oldValue = $this->env->getOriginal('value');
|
||||||
$this->env->value = $oldValue;
|
$this->value = $oldValue;
|
||||||
$this->dispatch('error', 'Required environment variable cannot be empty.');
|
$this->dispatch('error', 'Required environment variable cannot be empty.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@@ -123,10 +162,10 @@ class Show extends Component
|
|||||||
$this->serialize();
|
$this->serialize();
|
||||||
|
|
||||||
if ($this->isSharedVariable) {
|
if ($this->isSharedVariable) {
|
||||||
unset($this->env->is_required);
|
unset($this->is_required);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->env->save();
|
$this->syncData(true);
|
||||||
$this->dispatch('success', 'Environment variable updated.');
|
$this->dispatch('success', 'Environment variable updated.');
|
||||||
$this->dispatch('envsUpdated');
|
$this->dispatch('envsUpdated');
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
@@ -168,18 +168,42 @@ class ExecuteContainerCommand extends Component
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
// Validate container name format
|
||||||
|
if (! preg_match('/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/', $this->selected_container)) {
|
||||||
|
throw new \InvalidArgumentException('Invalid container name format');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify container exists in our allowed list
|
||||||
$container = collect($this->containers)->firstWhere('container.Names', $this->selected_container);
|
$container = collect($this->containers)->firstWhere('container.Names', $this->selected_container);
|
||||||
if (is_null($container)) {
|
if (is_null($container)) {
|
||||||
throw new \RuntimeException('Container not found.');
|
throw new \RuntimeException('Container not found.');
|
||||||
}
|
}
|
||||||
$server = data_get($this->container, 'server');
|
|
||||||
|
// Verify server ownership and status
|
||||||
|
$server = data_get($container, 'server');
|
||||||
|
if (! $server || ! $server instanceof Server) {
|
||||||
|
throw new \RuntimeException('Invalid server configuration.');
|
||||||
|
}
|
||||||
|
|
||||||
if ($server->isForceDisabled()) {
|
if ($server->isForceDisabled()) {
|
||||||
throw new \RuntimeException('Server is disabled.');
|
throw new \RuntimeException('Server is disabled.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Additional ownership verification based on resource type
|
||||||
|
$resourceServer = match ($this->type) {
|
||||||
|
'application' => $this->resource->destination->server,
|
||||||
|
'database' => $this->resource->destination->server,
|
||||||
|
'service' => $this->resource->server,
|
||||||
|
default => throw new \RuntimeException('Invalid resource type.')
|
||||||
|
};
|
||||||
|
|
||||||
|
if ($server->id !== $resourceServer->id && ! $this->resource->additional_servers->contains('id', $server->id)) {
|
||||||
|
throw new \RuntimeException('Server ownership verification failed.');
|
||||||
|
}
|
||||||
|
|
||||||
$this->dispatch(
|
$this->dispatch(
|
||||||
'send-terminal-command',
|
'send-terminal-command',
|
||||||
isset($container),
|
true,
|
||||||
data_get($container, 'container.Names'),
|
data_get($container, 'container.Names'),
|
||||||
data_get($container, 'server.uuid')
|
data_get($container, 'server.uuid')
|
||||||
);
|
);
|
||||||
|
@@ -42,9 +42,11 @@ class ResourceOperations extends Component
|
|||||||
$uuid = (string) new Cuid2;
|
$uuid = (string) new Cuid2;
|
||||||
$server = $new_destination->server;
|
$server = $new_destination->server;
|
||||||
if ($this->resource->getMorphClass() === \App\Models\Application::class) {
|
if ($this->resource->getMorphClass() === \App\Models\Application::class) {
|
||||||
|
$name = 'clone-of-'.str($this->resource->name)->limit(20).'-'.$uuid;
|
||||||
|
|
||||||
$new_resource = $this->resource->replicate()->fill([
|
$new_resource = $this->resource->replicate()->fill([
|
||||||
'uuid' => $uuid,
|
'uuid' => $uuid,
|
||||||
'name' => $this->resource->name.'-clone-'.$uuid,
|
'name' => $name,
|
||||||
'fqdn' => generateFqdn($server, $uuid),
|
'fqdn' => generateFqdn($server, $uuid),
|
||||||
'status' => 'exited',
|
'status' => 'exited',
|
||||||
'destination_id' => $new_destination->id,
|
'destination_id' => $new_destination->id,
|
||||||
@@ -58,14 +60,19 @@ class ResourceOperations extends Component
|
|||||||
$environmentVaribles = $this->resource->environment_variables()->get();
|
$environmentVaribles = $this->resource->environment_variables()->get();
|
||||||
foreach ($environmentVaribles as $environmentVarible) {
|
foreach ($environmentVaribles as $environmentVarible) {
|
||||||
$newEnvironmentVariable = $environmentVarible->replicate()->fill([
|
$newEnvironmentVariable = $environmentVarible->replicate()->fill([
|
||||||
'application_id' => $new_resource->id,
|
'resourceable_id' => $new_resource->id,
|
||||||
|
'resourceable_type' => $new_resource->getMorphClass(),
|
||||||
]);
|
]);
|
||||||
$newEnvironmentVariable->save();
|
$newEnvironmentVariable->save();
|
||||||
}
|
}
|
||||||
$persistentVolumes = $this->resource->persistentStorages()->get();
|
$persistentVolumes = $this->resource->persistentStorages()->get();
|
||||||
foreach ($persistentVolumes as $volume) {
|
foreach ($persistentVolumes as $volume) {
|
||||||
|
$volumeName = str($volume->name)->replace($this->resource->uuid, $new_resource->uuid)->value();
|
||||||
|
if ($volumeName === $volume->name) {
|
||||||
|
$volumeName = $new_resource->uuid.'-'.str($volume->name)->afterLast('-');
|
||||||
|
}
|
||||||
$newPersistentVolume = $volume->replicate()->fill([
|
$newPersistentVolume = $volume->replicate()->fill([
|
||||||
'name' => $new_resource->uuid.'-'.str($volume->name)->afterLast('-'),
|
'name' => $volumeName,
|
||||||
'resource_id' => $new_resource->id,
|
'resource_id' => $new_resource->id,
|
||||||
]);
|
]);
|
||||||
$newPersistentVolume->save();
|
$newPersistentVolume->save();
|
||||||
|
@@ -24,6 +24,14 @@ class Executions extends Component
|
|||||||
#[Locked]
|
#[Locked]
|
||||||
public ?string $serverTimezone = null;
|
public ?string $serverTimezone = null;
|
||||||
|
|
||||||
|
public $currentPage = 1;
|
||||||
|
|
||||||
|
public $logsPerPage = 100;
|
||||||
|
|
||||||
|
public $selectedExecution = null;
|
||||||
|
|
||||||
|
public $isPollingActive = false;
|
||||||
|
|
||||||
public function getListeners()
|
public function getListeners()
|
||||||
{
|
{
|
||||||
$teamId = Auth::user()->currentTeam()->id;
|
$teamId = Auth::user()->currentTeam()->id;
|
||||||
@@ -54,16 +62,84 @@ class Executions extends Component
|
|||||||
public function refreshExecutions(): void
|
public function refreshExecutions(): void
|
||||||
{
|
{
|
||||||
$this->executions = $this->task->executions()->take(20)->get();
|
$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
|
public function selectTask($key): void
|
||||||
{
|
{
|
||||||
if ($key == $this->selectedKey) {
|
if ($key == $this->selectedKey) {
|
||||||
$this->selectedKey = null;
|
$this->selectedKey = null;
|
||||||
|
$this->selectedExecution = null;
|
||||||
|
$this->currentPage = 1;
|
||||||
|
$this->isPollingActive = false;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$this->selectedKey = $key;
|
$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)
|
public function formatDateInServerTimezone($date)
|
||||||
|
@@ -29,11 +29,20 @@ class Terminal extends Component
|
|||||||
$server = Server::ownedByCurrentTeam()->whereUuid($serverUuid)->firstOrFail();
|
$server = Server::ownedByCurrentTeam()->whereUuid($serverUuid)->firstOrFail();
|
||||||
|
|
||||||
if ($isContainer) {
|
if ($isContainer) {
|
||||||
|
// Validate container identifier format (alphanumeric, dashes, and underscores only)
|
||||||
|
if (! preg_match('/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/', $identifier)) {
|
||||||
|
throw new \InvalidArgumentException('Invalid container identifier format');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify container exists and belongs to the user's team
|
||||||
$status = getContainerStatus($server, $identifier);
|
$status = getContainerStatus($server, $identifier);
|
||||||
if ($status !== 'running') {
|
if ($status !== 'running') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$command = SshMultiplexingHelper::generateSshCommand($server, "docker exec -it {$identifier} sh -c 'PATH=\$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin && if [ -f ~/.profile ]; then . ~/.profile; fi && if [ -n \"\$SHELL\" ]; then exec \$SHELL; else sh; fi'");
|
|
||||||
|
// Escape the identifier for shell usage
|
||||||
|
$escapedIdentifier = escapeshellarg($identifier);
|
||||||
|
$command = SshMultiplexingHelper::generateSshCommand($server, "docker exec -it {$escapedIdentifier} sh -c 'PATH=\$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin && if [ -f ~/.profile ]; then . ~/.profile; fi && if [ -n \"\$SHELL\" ]; then exec \$SHELL; else sh; fi'");
|
||||||
} else {
|
} else {
|
||||||
$command = SshMultiplexingHelper::generateSshCommand($server, 'PATH=$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin && if [ -f ~/.profile ]; then . ~/.profile; fi && if [ -n "$SHELL" ]; then exec $SHELL; else sh; fi');
|
$command = SshMultiplexingHelper::generateSshCommand($server, 'PATH=$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin && if [ -f ~/.profile ]; then . ~/.profile; fi && if [ -n "$SHELL" ]; then exec $SHELL; else sh; fi');
|
||||||
}
|
}
|
||||||
|
@@ -11,13 +11,7 @@ class ApiTokens extends Component
|
|||||||
|
|
||||||
public $tokens = [];
|
public $tokens = [];
|
||||||
|
|
||||||
public bool $viewSensitiveData = false;
|
public array $permissions = ['read'];
|
||||||
|
|
||||||
public bool $readOnly = true;
|
|
||||||
|
|
||||||
public bool $rootAccess = false;
|
|
||||||
|
|
||||||
public array $permissions = ['read-only'];
|
|
||||||
|
|
||||||
public $isApiEnabled;
|
public $isApiEnabled;
|
||||||
|
|
||||||
@@ -29,51 +23,28 @@ class ApiTokens extends Component
|
|||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->isApiEnabled = InstanceSettings::get()->is_api_enabled;
|
$this->isApiEnabled = InstanceSettings::get()->is_api_enabled;
|
||||||
|
$this->getTokens();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getTokens()
|
||||||
|
{
|
||||||
$this->tokens = auth()->user()->tokens->sortByDesc('created_at');
|
$this->tokens = auth()->user()->tokens->sortByDesc('created_at');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updatedViewSensitiveData()
|
public function updatedPermissions($permissionToUpdate)
|
||||||
{
|
{
|
||||||
if ($this->viewSensitiveData) {
|
if ($permissionToUpdate == 'root') {
|
||||||
$this->permissions[] = 'view:sensitive';
|
$this->permissions = ['root'];
|
||||||
$this->permissions = array_diff($this->permissions, ['*']);
|
} elseif ($permissionToUpdate == 'read:sensitive' && ! in_array('read', $this->permissions)) {
|
||||||
$this->rootAccess = false;
|
$this->permissions[] = 'read';
|
||||||
|
} elseif ($permissionToUpdate == 'deploy') {
|
||||||
|
$this->permissions = ['deploy'];
|
||||||
} else {
|
} else {
|
||||||
$this->permissions = array_diff($this->permissions, ['view:sensitive']);
|
if (count($this->permissions) == 0) {
|
||||||
}
|
$this->permissions = ['read'];
|
||||||
$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;
|
|
||||||
}
|
}
|
||||||
|
sort($this->permissions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addNewToken()
|
public function addNewToken()
|
||||||
@@ -82,8 +53,8 @@ class ApiTokens extends Component
|
|||||||
$this->validate([
|
$this->validate([
|
||||||
'description' => 'required|min:3|max:255',
|
'description' => 'required|min:3|max:255',
|
||||||
]);
|
]);
|
||||||
$token = auth()->user()->createToken($this->description, $this->permissions);
|
$token = auth()->user()->createToken($this->description, array_values($this->permissions));
|
||||||
$this->tokens = auth()->user()->tokens;
|
$this->getTokens();
|
||||||
session()->flash('token', $token->plainTextToken);
|
session()->flash('token', $token->plainTextToken);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
@@ -92,8 +63,12 @@ class ApiTokens extends Component
|
|||||||
|
|
||||||
public function revoke(int $id)
|
public function revoke(int $id)
|
||||||
{
|
{
|
||||||
$token = auth()->user()->tokens()->where('id', $id)->first();
|
try {
|
||||||
$token->delete();
|
$token = auth()->user()->tokens()->where('id', $id)->firstOrFail();
|
||||||
$this->tokens = auth()->user()->tokens;
|
$token->delete();
|
||||||
|
$this->getTokens();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -22,7 +22,7 @@ class Advanced extends Component
|
|||||||
#[Validate('boolean')]
|
#[Validate('boolean')]
|
||||||
public bool $forceDockerCleanup = false;
|
public bool $forceDockerCleanup = false;
|
||||||
|
|
||||||
#[Validate('string')]
|
#[Validate(['string', 'required'])]
|
||||||
public string $dockerCleanupFrequency = '*/10 * * * *';
|
public string $dockerCleanupFrequency = '*/10 * * * *';
|
||||||
|
|
||||||
#[Validate(['integer', 'min:1', 'max:99'])]
|
#[Validate(['integer', 'min:1', 'max:99'])]
|
||||||
@@ -78,7 +78,6 @@ class Advanced extends Component
|
|||||||
try {
|
try {
|
||||||
$this->syncData(true);
|
$this->syncData(true);
|
||||||
$this->dispatch('success', 'Server updated.');
|
$this->dispatch('success', 'Server updated.');
|
||||||
// $this->dispatch('refreshServerShow');
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
@@ -49,33 +49,73 @@ class LogDrains extends Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function syncData(bool $toModel = false)
|
public function syncDataNewRelic(bool $toModel = false)
|
||||||
|
{
|
||||||
|
if ($toModel) {
|
||||||
|
$this->server->settings->is_logdrain_newrelic_enabled = $this->isLogDrainNewRelicEnabled;
|
||||||
|
$this->server->settings->logdrain_newrelic_license_key = $this->logDrainNewRelicLicenseKey;
|
||||||
|
$this->server->settings->logdrain_newrelic_base_uri = $this->logDrainNewRelicBaseUri;
|
||||||
|
} else {
|
||||||
|
$this->isLogDrainNewRelicEnabled = $this->server->settings->is_logdrain_newrelic_enabled;
|
||||||
|
$this->logDrainNewRelicLicenseKey = $this->server->settings->logdrain_newrelic_license_key;
|
||||||
|
$this->logDrainNewRelicBaseUri = $this->server->settings->logdrain_newrelic_base_uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function syncDataAxiom(bool $toModel = false)
|
||||||
|
{
|
||||||
|
if ($toModel) {
|
||||||
|
$this->server->settings->is_logdrain_axiom_enabled = $this->isLogDrainAxiomEnabled;
|
||||||
|
$this->server->settings->logdrain_axiom_dataset_name = $this->logDrainAxiomDatasetName;
|
||||||
|
$this->server->settings->logdrain_axiom_api_key = $this->logDrainAxiomApiKey;
|
||||||
|
} else {
|
||||||
|
$this->isLogDrainAxiomEnabled = $this->server->settings->is_logdrain_axiom_enabled;
|
||||||
|
$this->logDrainAxiomDatasetName = $this->server->settings->logdrain_axiom_dataset_name;
|
||||||
|
$this->logDrainAxiomApiKey = $this->server->settings->logdrain_axiom_api_key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function syncDataCustom(bool $toModel = false)
|
||||||
|
{
|
||||||
|
if ($toModel) {
|
||||||
|
$this->server->settings->is_logdrain_custom_enabled = $this->isLogDrainCustomEnabled;
|
||||||
|
$this->server->settings->logdrain_custom_config = $this->logDrainCustomConfig;
|
||||||
|
$this->server->settings->logdrain_custom_config_parser = $this->logDrainCustomConfigParser;
|
||||||
|
} else {
|
||||||
|
$this->isLogDrainCustomEnabled = $this->server->settings->is_logdrain_custom_enabled;
|
||||||
|
$this->logDrainCustomConfig = $this->server->settings->logdrain_custom_config;
|
||||||
|
$this->logDrainCustomConfigParser = $this->server->settings->logdrain_custom_config_parser;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function syncData(bool $toModel = false, ?string $type = null)
|
||||||
{
|
{
|
||||||
if ($toModel) {
|
if ($toModel) {
|
||||||
$this->customValidation();
|
$this->customValidation();
|
||||||
$this->server->settings->is_logdrain_newrelic_enabled = $this->isLogDrainNewRelicEnabled;
|
if ($type === 'newrelic') {
|
||||||
$this->server->settings->is_logdrain_axiom_enabled = $this->isLogDrainAxiomEnabled;
|
$this->syncDataNewRelic($toModel);
|
||||||
$this->server->settings->is_logdrain_custom_enabled = $this->isLogDrainCustomEnabled;
|
} elseif ($type === 'axiom') {
|
||||||
|
$this->syncDataAxiom($toModel);
|
||||||
$this->server->settings->logdrain_newrelic_license_key = $this->logDrainNewRelicLicenseKey;
|
} elseif ($type === 'custom') {
|
||||||
$this->server->settings->logdrain_newrelic_base_uri = $this->logDrainNewRelicBaseUri;
|
$this->syncDataCustom($toModel);
|
||||||
$this->server->settings->logdrain_axiom_dataset_name = $this->logDrainAxiomDatasetName;
|
} else {
|
||||||
$this->server->settings->logdrain_axiom_api_key = $this->logDrainAxiomApiKey;
|
$this->syncDataNewRelic($toModel);
|
||||||
$this->server->settings->logdrain_custom_config = $this->logDrainCustomConfig;
|
$this->syncDataAxiom($toModel);
|
||||||
$this->server->settings->logdrain_custom_config_parser = $this->logDrainCustomConfigParser;
|
$this->syncDataCustom($toModel);
|
||||||
|
}
|
||||||
$this->server->settings->save();
|
$this->server->settings->save();
|
||||||
} else {
|
} else {
|
||||||
$this->isLogDrainNewRelicEnabled = $this->server->settings->is_logdrain_newrelic_enabled;
|
if ($type === 'newrelic') {
|
||||||
$this->isLogDrainAxiomEnabled = $this->server->settings->is_logdrain_axiom_enabled;
|
$this->syncDataNewRelic($toModel);
|
||||||
$this->isLogDrainCustomEnabled = $this->server->settings->is_logdrain_custom_enabled;
|
} elseif ($type === 'axiom') {
|
||||||
|
$this->syncDataAxiom($toModel);
|
||||||
$this->logDrainNewRelicLicenseKey = $this->server->settings->logdrain_newrelic_license_key;
|
} elseif ($type === 'custom') {
|
||||||
$this->logDrainNewRelicBaseUri = $this->server->settings->logdrain_newrelic_base_uri;
|
$this->syncDataCustom($toModel);
|
||||||
$this->logDrainAxiomDatasetName = $this->server->settings->logdrain_axiom_dataset_name;
|
} else {
|
||||||
$this->logDrainAxiomApiKey = $this->server->settings->logdrain_axiom_api_key;
|
$this->syncDataNewRelic($toModel);
|
||||||
$this->logDrainCustomConfig = $this->server->settings->logdrain_custom_config;
|
$this->syncDataAxiom($toModel);
|
||||||
$this->logDrainCustomConfigParser = $this->server->settings->logdrain_custom_config_parser;
|
$this->syncDataCustom($toModel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,7 +176,7 @@ class LogDrains extends Component
|
|||||||
public function submit(string $type)
|
public function submit(string $type)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->syncData(true);
|
$this->syncData(true, $type);
|
||||||
$this->dispatch('success', 'Settings saved.');
|
$this->dispatch('success', 'Settings saved.');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
|
@@ -15,6 +15,8 @@ class Proxy extends Component
|
|||||||
|
|
||||||
public $proxy_settings = null;
|
public $proxy_settings = null;
|
||||||
|
|
||||||
|
public bool $redirect_enabled = true;
|
||||||
|
|
||||||
public ?string $redirect_url = null;
|
public ?string $redirect_url = null;
|
||||||
|
|
||||||
protected $listeners = ['proxyStatusUpdated', 'saveConfiguration' => 'submit'];
|
protected $listeners = ['proxyStatusUpdated', 'saveConfiguration' => 'submit'];
|
||||||
@@ -26,6 +28,7 @@ class Proxy extends Component
|
|||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->selectedProxy = $this->server->proxyType();
|
$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');
|
$this->redirect_url = data_get($this->server, 'proxy.redirect_url');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,7 +41,7 @@ class Proxy extends Component
|
|||||||
{
|
{
|
||||||
$this->server->proxy = null;
|
$this->server->proxy = null;
|
||||||
$this->server->save();
|
$this->server->save();
|
||||||
$this->dispatch('proxyChanged');
|
$this->dispatch('reloadWindow');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function selectProxy($proxy_type)
|
public function selectProxy($proxy_type)
|
||||||
@@ -46,7 +49,7 @@ class Proxy extends Component
|
|||||||
try {
|
try {
|
||||||
$this->server->changeProxy($proxy_type, async: false);
|
$this->server->changeProxy($proxy_type, async: false);
|
||||||
$this->selectedProxy = $this->server->proxy->type;
|
$this->selectedProxy = $this->server->proxy->type;
|
||||||
$this->dispatch('proxyStatusUpdated');
|
$this->dispatch('reloadWindow');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
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()
|
public function submit()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
SaveConfiguration::run($this->server, $this->proxy_settings);
|
SaveConfiguration::run($this->server, $this->proxy_settings);
|
||||||
$this->server->proxy->redirect_url = $this->redirect_url;
|
$this->server->proxy->redirect_url = $this->redirect_url;
|
||||||
$this->server->save();
|
$this->server->save();
|
||||||
$this->server->setupDefault404Redirect();
|
$this->server->setupDefaultRedirect();
|
||||||
$this->dispatch('success', 'Proxy configuration saved.');
|
$this->dispatch('success', 'Proxy configuration saved.');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
|
@@ -65,7 +65,7 @@ class Deploy extends Component
|
|||||||
public function restart()
|
public function restart()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->stop(forceStop: false);
|
$this->stop();
|
||||||
$this->dispatch('checkProxy');
|
$this->dispatch('checkProxy');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
@@ -105,6 +105,7 @@ class Deploy extends Component
|
|||||||
|
|
||||||
$startTime = Carbon::now()->getTimestamp();
|
$startTime = Carbon::now()->getTimestamp();
|
||||||
while ($process->running()) {
|
while ($process->running()) {
|
||||||
|
ray('running');
|
||||||
if (Carbon::now()->getTimestamp() - $startTime >= $timeout) {
|
if (Carbon::now()->getTimestamp() - $startTime >= $timeout) {
|
||||||
$this->forceStopContainer($containerName);
|
$this->forceStopContainer($containerName);
|
||||||
break;
|
break;
|
||||||
|
@@ -5,7 +5,7 @@ namespace App\Livewire\Server;
|
|||||||
use App\Actions\Server\StartSentinel;
|
use App\Actions\Server\StartSentinel;
|
||||||
use App\Actions\Server\StopSentinel;
|
use App\Actions\Server\StopSentinel;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Livewire\Attributes\Locked;
|
use Livewire\Attributes\Computed;
|
||||||
use Livewire\Attributes\Validate;
|
use Livewire\Attributes\Validate;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
@@ -79,9 +79,6 @@ class Show extends Component
|
|||||||
#[Validate(['required'])]
|
#[Validate(['required'])]
|
||||||
public string $serverTimezone;
|
public string $serverTimezone;
|
||||||
|
|
||||||
#[Locked]
|
|
||||||
public array $timezones;
|
|
||||||
|
|
||||||
public function getListeners()
|
public function getListeners()
|
||||||
{
|
{
|
||||||
$teamId = auth()->user()->currentTeam()->id;
|
$teamId = auth()->user()->currentTeam()->id;
|
||||||
@@ -96,13 +93,21 @@ class Show extends Component
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->server = Server::ownedByCurrentTeam()->whereUuid($server_uuid)->firstOrFail();
|
$this->server = Server::ownedByCurrentTeam()->whereUuid($server_uuid)->firstOrFail();
|
||||||
$this->timezones = collect(timezone_identifiers_list())->sort()->values()->toArray();
|
|
||||||
$this->syncData();
|
$this->syncData();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Computed]
|
||||||
|
public function timezones(): array
|
||||||
|
{
|
||||||
|
return collect(timezone_identifiers_list())
|
||||||
|
->sort()
|
||||||
|
->values()
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
public function syncData(bool $toModel = false)
|
public function syncData(bool $toModel = false)
|
||||||
{
|
{
|
||||||
if ($toModel) {
|
if ($toModel) {
|
||||||
|
@@ -7,7 +7,7 @@ use App\Models\InstanceSettings;
|
|||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
use Livewire\Attributes\Locked;
|
use Livewire\Attributes\Computed;
|
||||||
use Livewire\Attributes\Validate;
|
use Livewire\Attributes\Validate;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
@@ -17,18 +17,12 @@ class Index extends Component
|
|||||||
|
|
||||||
protected Server $server;
|
protected Server $server;
|
||||||
|
|
||||||
#[Locked]
|
|
||||||
public $timezones;
|
|
||||||
|
|
||||||
#[Validate('boolean')]
|
#[Validate('boolean')]
|
||||||
public bool $is_auto_update_enabled;
|
public bool $is_auto_update_enabled;
|
||||||
|
|
||||||
#[Validate('nullable|string|max:255')]
|
#[Validate('nullable|string|max:255')]
|
||||||
public ?string $fqdn = null;
|
public ?string $fqdn = null;
|
||||||
|
|
||||||
#[Validate('nullable|string|max:255')]
|
|
||||||
public ?string $resale_license = null;
|
|
||||||
|
|
||||||
#[Validate('required|integer|min:1025|max:65535')]
|
#[Validate('required|integer|min:1025|max:65535')]
|
||||||
public int $public_port_min;
|
public int $public_port_min;
|
||||||
|
|
||||||
@@ -53,7 +47,7 @@ class Index extends Component
|
|||||||
#[Validate('string')]
|
#[Validate('string')]
|
||||||
public string $auto_update_frequency;
|
public string $auto_update_frequency;
|
||||||
|
|
||||||
#[Validate('string')]
|
#[Validate('string|required')]
|
||||||
public string $update_check_frequency;
|
public string $update_check_frequency;
|
||||||
|
|
||||||
#[Validate('required|string|timezone')]
|
#[Validate('required|string|timezone')]
|
||||||
@@ -86,7 +80,6 @@ class Index extends Component
|
|||||||
} else {
|
} else {
|
||||||
$this->settings = instanceSettings();
|
$this->settings = instanceSettings();
|
||||||
$this->fqdn = $this->settings->fqdn;
|
$this->fqdn = $this->settings->fqdn;
|
||||||
$this->resale_license = $this->settings->resale_license;
|
|
||||||
$this->public_port_min = $this->settings->public_port_min;
|
$this->public_port_min = $this->settings->public_port_min;
|
||||||
$this->public_port_max = $this->settings->public_port_max;
|
$this->public_port_max = $this->settings->public_port_max;
|
||||||
$this->custom_dns_servers = $this->settings->custom_dns_servers;
|
$this->custom_dns_servers = $this->settings->custom_dns_servers;
|
||||||
@@ -101,16 +94,30 @@ class Index extends Component
|
|||||||
$this->is_api_enabled = $this->settings->is_api_enabled;
|
$this->is_api_enabled = $this->settings->is_api_enabled;
|
||||||
$this->auto_update_frequency = $this->settings->auto_update_frequency;
|
$this->auto_update_frequency = $this->settings->auto_update_frequency;
|
||||||
$this->update_check_frequency = $this->settings->update_check_frequency;
|
$this->update_check_frequency = $this->settings->update_check_frequency;
|
||||||
$this->timezones = collect(timezone_identifiers_list())->sort()->values()->toArray();
|
|
||||||
$this->instance_timezone = $this->settings->instance_timezone;
|
$this->instance_timezone = $this->settings->instance_timezone;
|
||||||
$this->disable_two_step_confirmation = $this->settings->disable_two_step_confirmation;
|
$this->disable_two_step_confirmation = $this->settings->disable_two_step_confirmation;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Computed]
|
||||||
|
public function timezones(): array
|
||||||
|
{
|
||||||
|
return collect(timezone_identifiers_list())
|
||||||
|
->sort()
|
||||||
|
->values()
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
public function instantSave($isSave = true)
|
public function instantSave($isSave = true)
|
||||||
{
|
{
|
||||||
|
$this->validate();
|
||||||
|
if ($this->settings->is_auto_update_enabled === true) {
|
||||||
|
$this->validate([
|
||||||
|
'auto_update_frequency' => ['required', 'string'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
$this->settings->fqdn = $this->fqdn;
|
$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_min = $this->public_port_min;
|
||||||
$this->settings->public_port_max = $this->public_port_max;
|
$this->settings->public_port_max = $this->public_port_max;
|
||||||
$this->settings->custom_dns_servers = $this->custom_dns_servers;
|
$this->settings->custom_dns_servers = $this->custom_dns_servers;
|
||||||
|
@@ -3,6 +3,10 @@
|
|||||||
namespace App\Livewire;
|
namespace App\Livewire;
|
||||||
|
|
||||||
use App\Models\InstanceSettings;
|
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\Attributes\Validate;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
@@ -10,39 +14,48 @@ class SettingsEmail extends Component
|
|||||||
{
|
{
|
||||||
public InstanceSettings $settings;
|
public InstanceSettings $settings;
|
||||||
|
|
||||||
|
#[Locked]
|
||||||
|
public Team $team;
|
||||||
|
|
||||||
#[Validate(['boolean'])]
|
#[Validate(['boolean'])]
|
||||||
public bool $smtpEnabled = false;
|
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'])]
|
#[Validate(['nullable', 'email'])]
|
||||||
public ?string $smtpFromAddress = null;
|
public ?string $smtpFromAddress = null;
|
||||||
|
|
||||||
#[Validate(['nullable', 'string'])]
|
#[Validate(['nullable', 'string'])]
|
||||||
public ?string $smtpFromName = null;
|
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'])]
|
#[Validate(['boolean'])]
|
||||||
public bool $resendEnabled = false;
|
public bool $resendEnabled = false;
|
||||||
|
|
||||||
#[Validate(['nullable', 'string'])]
|
#[Validate(['nullable', 'string'])]
|
||||||
public ?string $resendApiKey = null;
|
public ?string $resendApiKey = null;
|
||||||
|
|
||||||
|
#[Validate(['nullable', 'email'])]
|
||||||
|
public ?string $testEmailAddress = null;
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
if (isInstanceAdmin() === false) {
|
if (isInstanceAdmin() === false) {
|
||||||
@@ -50,6 +63,8 @@ class SettingsEmail extends Component
|
|||||||
}
|
}
|
||||||
$this->settings = instanceSettings();
|
$this->settings = instanceSettings();
|
||||||
$this->syncData();
|
$this->syncData();
|
||||||
|
$this->team = auth()->user()->currentTeam();
|
||||||
|
$this->testEmailAddress = auth()->user()->email;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function syncData(bool $toModel = false)
|
public function syncData(bool $toModel = false)
|
||||||
@@ -90,7 +105,7 @@ class SettingsEmail extends Component
|
|||||||
try {
|
try {
|
||||||
$this->resetErrorBag();
|
$this->resetErrorBag();
|
||||||
$this->syncData(true);
|
$this->syncData(true);
|
||||||
$this->dispatch('success', 'Settings saved.');
|
$this->dispatch('success', 'Transactional email settings updated.');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
@@ -99,19 +114,129 @@ class SettingsEmail extends Component
|
|||||||
public function instantSave(string $type)
|
public function instantSave(string $type)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
$this->resetErrorBag();
|
||||||
|
|
||||||
if ($type === 'SMTP') {
|
if ($type === 'SMTP') {
|
||||||
$this->resendEnabled = false;
|
$this->submitSmtp();
|
||||||
} else {
|
} elseif ($type === 'Resend') {
|
||||||
$this->smtpEnabled = false;
|
$this->submitResend();
|
||||||
}
|
|
||||||
$this->syncData(true);
|
|
||||||
if ($this->smtpEnabled || $this->resendEnabled) {
|
|
||||||
$this->dispatch('success', "{$type} enabled.");
|
|
||||||
} else {
|
|
||||||
$this->dispatch('success', "{$type} disabled.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
|
if ($type === 'SMTP') {
|
||||||
|
$this->smtpEnabled = false;
|
||||||
|
} elseif ($type === 'Resend') {
|
||||||
|
$this->resendEnabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
return handleError($e, $this);
|
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.client_secret"] = 'nullable';
|
||||||
$carry["oauth_settings_map.$setting->provider.redirect_uri"] = 'nullable';
|
$carry["oauth_settings_map.$setting->provider.redirect_uri"] = 'nullable';
|
||||||
$carry["oauth_settings_map.$setting->provider.tenant"] = 'nullable';
|
$carry["oauth_settings_map.$setting->provider.tenant"] = 'nullable';
|
||||||
|
$carry["oauth_settings_map.$setting->provider.base_url"] = 'nullable';
|
||||||
|
|
||||||
return $carry;
|
return $carry;
|
||||||
}, []);
|
}, []);
|
||||||
@@ -34,16 +35,30 @@ class SettingsOauth extends Component
|
|||||||
}, []);
|
}, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function updateOauthSettings()
|
private function updateOauthSettings(?string $provider = null)
|
||||||
{
|
{
|
||||||
foreach (array_values($this->oauth_settings_map) as &$setting) {
|
if ($provider) {
|
||||||
$setting->save();
|
$oauth = $this->oauth_settings_map[$provider];
|
||||||
|
if (! $oauth->couldBeEnabled()) {
|
||||||
|
$oauth->update(['enabled' => false]);
|
||||||
|
throw new \Exception('OAuth settings are not complete for '.$oauth->provider.'.<br/>Please fill in all required fields.');
|
||||||
|
}
|
||||||
|
$oauth->save();
|
||||||
|
$this->dispatch('success', 'OAuth settings for '.$oauth->provider.' updated successfully!');
|
||||||
|
} else {
|
||||||
|
foreach (array_values($this->oauth_settings_map) as &$setting) {
|
||||||
|
$setting->save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function instantSave()
|
public function instantSave(string $provider)
|
||||||
{
|
{
|
||||||
$this->updateOauthSettings();
|
try {
|
||||||
|
$this->updateOauthSettings($provider);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function submit()
|
public function submit()
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user