mirror of
https://github.com/tiennm99/coolify.git
synced 2026-10-13 06:15:13 +00:00
Merge branch 'next' into feat/disable-default-redirect
This commit is contained in:
190 files changed
+2581
-952
No files matched your search
@@ -5,7 +5,7 @@
|
||||
|
||||
# About the Project
|
||||
|
||||
Coolify is an open-source & self-hostable alternative to Heroku / Netlify / Vercel / etc.
|
||||
Coolify is an open-source & self-hostable alternative to Heroku / Netlify / Vercel / etc.
|
||||
|
||||
It helps you manage your servers, applications, and databases on your own hardware; you only need an SSH connection. You can manage VPS, Bare Metal, Raspberry PIs, and anything else.
|
||||
|
||||
@@ -40,21 +40,21 @@ Special thanks to our biggest sponsors!
|
||||
|
||||
### Special Sponsors
|
||||
|
||||

|
||||

|
||||
|
||||
* [CCCareers](https://cccareers.org/) - A career development platform connecting coding bootcamp graduates with job opportunities in the tech industry.
|
||||
* [Hetzner](http://htznr.li/CoolifyXHetzner) - A German web hosting company offering affordable dedicated servers, cloud services, and web hosting solutions.
|
||||
* [Logto](https://logto.io/?ref=coolify) - An open-source authentication and authorization solution for building secure login systems and managing user identities.
|
||||
* [Tolgee](https://tolgee.io/?ref=coolify) - Developer & translator friendly web-based localization platform.
|
||||
* [BC Direct](https://bc.direct/?ref=coolify.io) - A digital marketing agency specializing in e-commerce solutions and online business growth strategies.
|
||||
* [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.
|
||||
* [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.
|
||||
* [Fractal Networks](https://fractalnetworks.co/?ref=coolify.io) - A decentralized network infrastructure company focusing on secure and private communication solutions.
|
||||
* [Advin](https://coolify.ad.vin/?ref=coolify.io) - A digital advertising agency specializing in programmatic advertising and data-driven marketing strategies.
|
||||
* [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.
|
||||
* [Latitude](https://latitude.sh/?ref=coolify.io) - A cloud computing platform offering bare metal servers and cloud instances for developers and businesses.
|
||||
* [Brand Dev](https://brand.dev/?ref=coolify.io) - A web development agency specializing in creating custom digital experiences and brand identities.
|
||||
* [Jobscollider](https://jobscollider.com/remote-jobs?ref=coolify.io) - A job search platform connecting professionals with remote work opportunities across various industries.
|
||||
* [Hostinger](https://www.hostinger.com/vps/coolify-hosting?ref=coolify.io) - A web hosting provider offering affordable hosting solutions, domain registration, and website building tools.
|
||||
@@ -63,6 +63,7 @@ Special thanks to our biggest sponsors!
|
||||
* [Juxtdigital](https://juxtdigital.dev/?ref=coolify.io) - A digital agency offering web development, design, and digital marketing services for businesses.
|
||||
* [Saasykit](https://saasykit.com/?ref=coolify.io) - A Laravel-based boilerplate providing essential components and features for building SaaS applications quickly.
|
||||
* [Massivegrid](https://massivegrid.com/?ref=coolify.io) - A cloud hosting provider offering scalable infrastructure solutions for businesses of all sizes.
|
||||
* [LiquidWeb](https://liquidweb.com/?utm_source=coolify.io) - Fast web hosting provider.
|
||||
|
||||
|
||||
## Github Sponsors ($40+)
|
||||
@@ -90,7 +91,11 @@ 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/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://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://jonasjaeger.com?utm_source=coolify.io"><img src="https://github.com/toxin20.png" width="60px" alt="Jonas Jaeger" /></a>
|
||||
<a href="https://github.com/therealjp?utm_source=coolify.io"><img src="https://github.com/therealjp.png" width="60px" alt="JP" /></a>
|
||||
<a href="https://evercam.io/?utm_source=coolify.io"><img src="https://github.com/evercam.png" width="60px" alt="Evercam" /></a>
|
||||
<a href="https://web3.career/?utm_source=coolify.io"><img src="https://web3.career/favicon1.png" width="60px" alt="Web3 Career" /></a>
|
||||
|
||||
## Organizations
|
||||
<a href="https://opencollective.com/coollabsio/organization/0/website"><img src="https://opencollective.com/coollabsio/organization/0/avatar.svg"></a>
|
||||
@@ -142,10 +147,10 @@ By subscribing to the cloud version, you get the Coolify server for the same pri
|
||||
|
||||
# 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" /> |
|
||||
| <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> |
|
||||
| <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://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
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ class StopApplication
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public string $jobQueue = 'high';
|
||||
|
||||
public function handle(Application $application, bool $previewDeployments = false, bool $dockerCleanup = true)
|
||||
{
|
||||
try {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace App\Actions\CoolifyTask;
|
||||
|
||||
use App\Data\CoolifyTaskArgs;
|
||||
use App\Enums\ActivityTypes;
|
||||
use App\Jobs\CoolifyTask;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
|
||||
@@ -47,11 +46,7 @@ class PrepareCoolifyTask
|
||||
call_event_on_finish: $this->remoteProcessArgs->call_event_on_finish,
|
||||
call_event_data: $this->remoteProcessArgs->call_event_data,
|
||||
);
|
||||
if ($this->remoteProcessArgs->type === ActivityTypes::COMMAND->value) {
|
||||
dispatch($job)->onQueue('high');
|
||||
} else {
|
||||
dispatch($job);
|
||||
}
|
||||
dispatch($job);
|
||||
$this->activity->refresh();
|
||||
|
||||
return $this->activity;
|
||||
|
||||
@@ -24,7 +24,7 @@ class StartClickhouse
|
||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||
|
||||
$this->commands = [
|
||||
"echo 'Starting {$database->name}.'",
|
||||
"echo 'Starting database.'",
|
||||
"mkdir -p $this->configuration_dir",
|
||||
];
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@ class StartDatabase
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public string $jobQueue = 'high';
|
||||
|
||||
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $database)
|
||||
{
|
||||
$server = $database->destination->server;
|
||||
@@ -49,7 +51,7 @@ class StartDatabase
|
||||
break;
|
||||
}
|
||||
if ($database->is_public && $database->public_port) {
|
||||
StartDatabaseProxy::dispatch($database)->onQueue('high');
|
||||
StartDatabaseProxy::dispatch($database);
|
||||
}
|
||||
|
||||
return $activity;
|
||||
|
||||
@@ -18,6 +18,8 @@ class StartDatabaseProxy
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public string $jobQueue = 'high';
|
||||
|
||||
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse|ServiceDatabase $database)
|
||||
{
|
||||
$internalPort = null;
|
||||
|
||||
@@ -26,7 +26,7 @@ class StartDragonfly
|
||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||
|
||||
$this->commands = [
|
||||
"echo 'Starting {$database->name}.'",
|
||||
"echo 'Starting database.'",
|
||||
"mkdir -p $this->configuration_dir",
|
||||
];
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ class StartKeydb
|
||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||
|
||||
$this->commands = [
|
||||
"echo 'Starting {$database->name}.'",
|
||||
"echo 'Starting database.'",
|
||||
"mkdir -p $this->configuration_dir",
|
||||
];
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ class StartMariadb
|
||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||
|
||||
$this->commands = [
|
||||
"echo 'Starting {$database->name}.'",
|
||||
"echo 'Starting database.'",
|
||||
"mkdir -p $this->configuration_dir",
|
||||
];
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ class StartMongodb
|
||||
}
|
||||
|
||||
$this->commands = [
|
||||
"echo 'Starting {$database->name}.'",
|
||||
"echo 'Starting database.'",
|
||||
"mkdir -p $this->configuration_dir",
|
||||
];
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ class StartMysql
|
||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||
|
||||
$this->commands = [
|
||||
"echo 'Starting {$database->name}.'",
|
||||
"echo 'Starting database.'",
|
||||
"mkdir -p $this->configuration_dir",
|
||||
];
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ class StartPostgresql
|
||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||
|
||||
$this->commands = [
|
||||
"echo 'Starting {$database->name}.'",
|
||||
"echo 'Starting database.'",
|
||||
"mkdir -p $this->configuration_dir",
|
||||
"mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d/",
|
||||
];
|
||||
|
||||
@@ -25,7 +25,7 @@ class StartRedis
|
||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||
|
||||
$this->commands = [
|
||||
"echo 'Starting {$database->name}.'",
|
||||
"echo 'Starting database.'",
|
||||
"mkdir -p $this->configuration_dir",
|
||||
];
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ class StopDatabaseProxy
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public string $jobQueue = 'high';
|
||||
|
||||
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|ServiceDatabase|StandaloneDragonfly|StandaloneClickhouse $database)
|
||||
{
|
||||
$server = data_get($database, 'destination.server');
|
||||
|
||||
@@ -7,7 +7,6 @@ use App\Actions\Shared\ComplexStatusCheck;
|
||||
use App\Models\ApplicationPreview;
|
||||
use App\Models\Server;
|
||||
use App\Models\ServiceDatabase;
|
||||
use App\Notifications\Container\ContainerRestarted;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
@@ -16,6 +15,8 @@ class GetContainersStatus
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public string $jobQueue = 'high';
|
||||
|
||||
public $applications;
|
||||
|
||||
public ?Collection $containers;
|
||||
@@ -178,7 +179,7 @@ class GetContainersStatus
|
||||
})->first();
|
||||
if (! $foundTcpProxy) {
|
||||
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 {
|
||||
|
||||
@@ -30,7 +30,7 @@ class CheckProxy
|
||||
if (is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop) {
|
||||
return false;
|
||||
}
|
||||
['uptime' => $uptime, 'error' => $error] = $server->validateConnection(false);
|
||||
['uptime' => $uptime, 'error' => $error] = $server->validateConnection();
|
||||
if (! $uptime) {
|
||||
throw new \Exception($error);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ class CleanupDocker
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public string $jobQueue = 'high';
|
||||
|
||||
public function handle(Server $server)
|
||||
{
|
||||
$settings = instanceSettings();
|
||||
|
||||
@@ -51,7 +51,6 @@ class ServerCheck
|
||||
|
||||
$containerReplicates = null;
|
||||
$this->isSentinel = true;
|
||||
|
||||
} else {
|
||||
['containers' => $this->containers, 'containerReplicates' => $containerReplicates] = $this->server->getContainers();
|
||||
// ServerStorageCheckJob::dispatch($this->server);
|
||||
@@ -130,10 +129,10 @@ class ServerCheck
|
||||
if ($foundLogDrainContainer) {
|
||||
$status = data_get($foundLogDrainContainer, 'State.Status');
|
||||
if ($status !== 'running') {
|
||||
StartLogDrain::dispatch($this->server)->onQueue('high');
|
||||
StartLogDrain::dispatch($this->server);
|
||||
}
|
||||
} else {
|
||||
StartLogDrain::dispatch($this->server)->onQueue('high');
|
||||
StartLogDrain::dispatch($this->server);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,7 +147,6 @@ class ServerCheck
|
||||
} else {
|
||||
$labels = Arr::undot(data_get($container, 'Config.Labels'));
|
||||
}
|
||||
|
||||
}
|
||||
$managed = data_get($labels, 'coolify.managed');
|
||||
if (! $managed) {
|
||||
@@ -259,7 +257,7 @@ class ServerCheck
|
||||
})->first();
|
||||
if (! $foundTcpProxy) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ class StartLogDrain
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public string $jobQueue = 'high';
|
||||
|
||||
public function handle(Server $server)
|
||||
{
|
||||
if ($server->settings->is_logdrain_newrelic_enabled) {
|
||||
|
||||
@@ -29,9 +29,9 @@ class UpdateCoolify
|
||||
if (! $this->server) {
|
||||
return;
|
||||
}
|
||||
CleanupDocker::dispatch($this->server)->onQueue('high');
|
||||
CleanupDocker::dispatch($this->server);
|
||||
$this->latestVersion = get_latest_version_of_coolify();
|
||||
$this->currentVersion = config('version');
|
||||
$this->currentVersion = config('constants.coolify.version');
|
||||
if (! $manual_update) {
|
||||
if (! $settings->is_auto_update_enabled) {
|
||||
return;
|
||||
|
||||
@@ -9,6 +9,8 @@ class ValidateServer
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public string $jobQueue = 'high';
|
||||
|
||||
public ?string $uptime = null;
|
||||
|
||||
public ?string $error = null;
|
||||
|
||||
@@ -9,6 +9,8 @@ class RestartService
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public string $jobQueue = 'high';
|
||||
|
||||
public function handle(Service $service)
|
||||
{
|
||||
StopService::run($service);
|
||||
|
||||
@@ -10,6 +10,8 @@ class StartService
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public string $jobQueue = 'high';
|
||||
|
||||
public function handle(Service $service)
|
||||
{
|
||||
$service->saveComposeConfigs();
|
||||
|
||||
@@ -10,6 +10,8 @@ class StopService
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public string $jobQueue = 'high';
|
||||
|
||||
public function handle(Service $service, bool $isDeleteOperation = false, bool $dockerCleanup = true)
|
||||
{
|
||||
try {
|
||||
|
||||
@@ -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 (! (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([
|
||||
'stripe_invoice_paid' => false,
|
||||
@@ -61,9 +61,9 @@ class CloudCleanupSubscriptions extends Command
|
||||
$this->info('Subscription id: '.data_get($team, 'subscription.stripe_subscription_id'));
|
||||
$confirm = $this->confirm('Do you want to cancel the subscription?', true);
|
||||
if (! $confirm) {
|
||||
$this->info("Skipping team {$team->id} {$team->name}");
|
||||
$this->info("Skipping team {$team->id}");
|
||||
} else {
|
||||
$this->info("Cancelling subscription for team {$team->id} {$team->name}");
|
||||
$this->info("Cancelling subscription for team {$team->id}");
|
||||
$team->subscription->update([
|
||||
'stripe_invoice_paid' => false,
|
||||
'stripe_trial_already_ended' => false,
|
||||
|
||||
@@ -187,7 +187,7 @@ class Emails extends Command
|
||||
'team_id' => 0,
|
||||
]);
|
||||
}
|
||||
$this->mail = (new BackupSuccess($backup, $db))->toMail();
|
||||
// $this->mail = (new BackupSuccess($backup->frequency, $db->name))->toMail();
|
||||
$this->sendEmail();
|
||||
break;
|
||||
// case 'invitation-link':
|
||||
|
||||
@@ -200,7 +200,7 @@ class Init extends Command
|
||||
|
||||
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 {
|
||||
$database = StandalonePostgresql::withTrashed()->find(0);
|
||||
if ($database && $database->trashed()) {
|
||||
@@ -228,7 +228,7 @@ class Init extends Command
|
||||
private function send_alive_signal()
|
||||
{
|
||||
$id = config('app.id');
|
||||
$version = config('version');
|
||||
$version = config('constants.coolify.version');
|
||||
$settings = instanceSettings();
|
||||
$do_not_track = data_get($settings, 'do_not_track');
|
||||
if ($do_not_track == true) {
|
||||
@@ -264,7 +264,7 @@ class Init extends Command
|
||||
|
||||
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();
|
||||
foreach ($environments as $environment) {
|
||||
if (str_contains($environment->name, '/')) {
|
||||
|
||||
@@ -96,7 +96,7 @@ class ServicesDelete extends Command
|
||||
if (! $confirmed) {
|
||||
break;
|
||||
}
|
||||
DeleteResourceJob::dispatch($toDelete)->onQueue('high');
|
||||
DeleteResourceJob::dispatch($toDelete);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -122,7 +122,7 @@ class ServicesDelete extends Command
|
||||
if (! $confirmed) {
|
||||
return;
|
||||
}
|
||||
DeleteResourceJob::dispatch($toDelete)->onQueue('high');
|
||||
DeleteResourceJob::dispatch($toDelete);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -148,7 +148,7 @@ class ServicesDelete extends Command
|
||||
if (! $confirmed) {
|
||||
return;
|
||||
}
|
||||
DeleteResourceJob::dispatch($toDelete)->onQueue('high');
|
||||
DeleteResourceJob::dispatch($toDelete);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,10 @@ class ServicesGenerate extends Command
|
||||
|
||||
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 {
|
||||
$file = basename($file);
|
||||
$parsed = $this->processFile($file);
|
||||
@@ -68,7 +71,7 @@ class ServicesGenerate extends Command
|
||||
'slogan' => $data->get('slogan', str($file)->headline()),
|
||||
'compose' => $compose,
|
||||
'tags' => $tags,
|
||||
'logo' => $data->get('logo', 'svgs/coolify.png'),
|
||||
'logo' => $data->get('logo', 'svgs/default.webp'),
|
||||
'minversion' => $data->get('minversion', '0.0.0'),
|
||||
];
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ class Kernel extends ConsoleKernel
|
||||
$this->instanceTimezone = config('app.timezone');
|
||||
}
|
||||
|
||||
$this->scheduleInstance->job(new CleanupStaleMultiplexedConnections)->hourly();
|
||||
// $this->scheduleInstance->job(new CleanupStaleMultiplexedConnections)->hourly();
|
||||
|
||||
if (isDev()) {
|
||||
// Instance Jobs
|
||||
@@ -132,7 +132,7 @@ class Kernel extends ConsoleKernel
|
||||
}
|
||||
|
||||
foreach ($servers as $server) {
|
||||
$serverTimezone = $server->settings->server_timezone;
|
||||
$serverTimezone = data_get($server->settings, 'server_timezone', $this->instanceTimezone);
|
||||
|
||||
// Sentinel check
|
||||
$lastSentinelUpdate = $server->sentinel_updated_at;
|
||||
@@ -141,8 +141,12 @@ class Kernel extends ConsoleKernel
|
||||
if (validate_timezone($serverTimezone) === false) {
|
||||
$serverTimezone = config('app.timezone');
|
||||
}
|
||||
$this->scheduleInstance->job(new ServerCheckJob($server))->timezone($serverTimezone)->everyMinute()->onOneServer();
|
||||
// $this->scheduleInstance->job(new \App\Jobs\ServerCheckNewJob($server))->everyMinute()->onOneServer();
|
||||
if (isCloud()) {
|
||||
$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
|
||||
$this->scheduleInstance->job(new ServerStorageCheckJob($server))->everyTenMinutes()->onOneServer();
|
||||
@@ -154,7 +158,7 @@ class Kernel extends ConsoleKernel
|
||||
}
|
||||
|
||||
// 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
|
||||
if ($server->isSentinelEnabled()) {
|
||||
|
||||
@@ -18,7 +18,7 @@ class DatabaseProxyStopped implements ShouldBroadcast
|
||||
public function __construct($teamId = null)
|
||||
{
|
||||
if (is_null($teamId)) {
|
||||
$teamId = Auth::user()->currentTeam()->id ?? null;
|
||||
$teamId = Auth::user()?->currentTeam()?->id ?? null;
|
||||
}
|
||||
if (is_null($teamId)) {
|
||||
throw new \Exception('Team id is null');
|
||||
|
||||
@@ -21,17 +21,14 @@ class SshMultiplexingHelper
|
||||
];
|
||||
}
|
||||
|
||||
public static function ensureMultiplexedConnection(Server $server)
|
||||
public static function ensureMultiplexedConnection(Server $server): bool
|
||||
{
|
||||
if (! self::isMultiplexingEnabled()) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
$sshConfig = self::serverSshConfiguration($server);
|
||||
$muxSocket = $sshConfig['muxFilename'];
|
||||
$sshKeyLocation = $sshConfig['sshKeyLocation'];
|
||||
|
||||
self::validateSshKey($sshKeyLocation);
|
||||
|
||||
$checkCommand = "ssh -O check -o ControlPath=$muxSocket ";
|
||||
if (data_get($server, 'settings.is_cloudflare_tunnel')) {
|
||||
@@ -41,16 +38,17 @@ class SshMultiplexingHelper
|
||||
$process = Process::run($checkCommand);
|
||||
|
||||
if ($process->exitCode() !== 0) {
|
||||
self::establishNewMultiplexedConnection($server);
|
||||
return self::establishNewMultiplexedConnection($server);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function establishNewMultiplexedConnection(Server $server)
|
||||
public static function establishNewMultiplexedConnection(Server $server): bool
|
||||
{
|
||||
$sshConfig = self::serverSshConfiguration($server);
|
||||
$sshKeyLocation = $sshConfig['sshKeyLocation'];
|
||||
$muxSocket = $sshConfig['muxFilename'];
|
||||
|
||||
$connectionTimeout = config('constants.ssh.connection_timeout');
|
||||
$serverInterval = config('constants.ssh.server_interval');
|
||||
$muxPersistTime = config('constants.ssh.mux_persist_time');
|
||||
@@ -60,15 +58,14 @@ class SshMultiplexingHelper
|
||||
if (data_get($server, 'settings.is_cloudflare_tunnel')) {
|
||||
$establishCommand .= ' -o ProxyCommand="cloudflared access ssh --hostname %h" ';
|
||||
}
|
||||
|
||||
$establishCommand .= self::getCommonSshOptions($server, $sshKeyLocation, $connectionTimeout, $serverInterval);
|
||||
$establishCommand .= "{$server->user}@{$server->ip}";
|
||||
|
||||
$establishProcess = Process::run($establishCommand);
|
||||
|
||||
if ($establishProcess->exitCode() !== 0) {
|
||||
throw new \RuntimeException('Failed to establish multiplexed connection: '.$establishProcess->errorOutput());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function removeMuxFile(Server $server)
|
||||
@@ -97,9 +94,8 @@ class SshMultiplexingHelper
|
||||
if ($server->isIpv6()) {
|
||||
$scp_command .= '-6 ';
|
||||
}
|
||||
if (self::isMultiplexingEnabled()) {
|
||||
if (self::isMultiplexingEnabled() && self::ensureMultiplexedConnection($server)) {
|
||||
$scp_command .= "-o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} ";
|
||||
self::ensureMultiplexedConnection($server);
|
||||
}
|
||||
|
||||
if (data_get($server, 'settings.is_cloudflare_tunnel')) {
|
||||
@@ -120,6 +116,9 @@ class SshMultiplexingHelper
|
||||
|
||||
$sshConfig = self::serverSshConfiguration($server);
|
||||
$sshKeyLocation = $sshConfig['sshKeyLocation'];
|
||||
|
||||
self::validateSshKey($server->privateKey);
|
||||
|
||||
$muxSocket = $sshConfig['muxFilename'];
|
||||
|
||||
$timeout = config('constants.ssh.command_timeout');
|
||||
@@ -127,9 +126,8 @@ class SshMultiplexingHelper
|
||||
|
||||
$ssh_command = "timeout $timeout ssh ";
|
||||
|
||||
if (self::isMultiplexingEnabled()) {
|
||||
if (self::isMultiplexingEnabled() && self::ensureMultiplexedConnection($server)) {
|
||||
$ssh_command .= "-o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} ";
|
||||
self::ensureMultiplexedConnection($server);
|
||||
}
|
||||
|
||||
if (data_get($server, 'settings.is_cloudflare_tunnel')) {
|
||||
@@ -154,13 +152,14 @@ class SshMultiplexingHelper
|
||||
return config('constants.ssh.mux_enabled') && ! config('constants.coolify.is_windows_docker_desktop');
|
||||
}
|
||||
|
||||
private static function validateSshKey(string $sshKeyLocation): void
|
||||
private static function validateSshKey(PrivateKey $privateKey): void
|
||||
{
|
||||
$checkKeyCommand = "ls $sshKeyLocation 2>/dev/null";
|
||||
$keyLocation = $privateKey->getKeyLocation();
|
||||
$checkKeyCommand = "ls $keyLocation 2>/dev/null";
|
||||
$keyCheckProcess = Process::run($checkKeyCommand);
|
||||
|
||||
if ($keyCheckProcess->exitCode() !== 0) {
|
||||
throw new \RuntimeException("SSH key file not accessible: $sshKeyLocation");
|
||||
$privateKey->storeInFileSystem();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1224,7 +1224,7 @@ class ApplicationsController extends Controller
|
||||
$service->name = "service-$service->uuid";
|
||||
$service->parse(isNew: true);
|
||||
if ($instantDeploy) {
|
||||
StartService::dispatch($service)->onQueue('high');
|
||||
StartService::dispatch($service);
|
||||
}
|
||||
|
||||
return response()->json(serializeApiResponse([
|
||||
@@ -1379,7 +1379,7 @@ class ApplicationsController extends Controller
|
||||
deleteVolumes: $request->query->get('delete_volumes', true),
|
||||
dockerCleanup: $request->query->get('docker_cleanup', true),
|
||||
deleteConnectedNetworks: $request->query->get('delete_connected_networks', true)
|
||||
)->onQueue('high');
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Application deletion request queued.',
|
||||
@@ -1591,16 +1591,32 @@ class ApplicationsController extends Controller
|
||||
}
|
||||
$domains = $request->domains;
|
||||
if ($request->has('domains') && $server->isProxyShouldRun()) {
|
||||
$errors = [];
|
||||
$uuid = $request->uuid;
|
||||
$fqdn = $request->domains;
|
||||
$fqdn = str($fqdn)->replaceEnd(',', '')->trim();
|
||||
$fqdn = str($fqdn)->replaceStart(',', '')->trim();
|
||||
$application->fqdn = $fqdn;
|
||||
if (! $application->settings->is_container_label_readonly_enabled) {
|
||||
$customLabels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
||||
$application->custom_labels = base64_encode($customLabels);
|
||||
$errors = [];
|
||||
$fqdn = str($fqdn)->trim()->explode(',')->map(function ($domain) use (&$errors) {
|
||||
$domain = trim($domain);
|
||||
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();
|
||||
@@ -2523,7 +2539,7 @@ class ApplicationsController extends Controller
|
||||
if (! $application) {
|
||||
return response()->json(['message' => 'Application not found.'], 404);
|
||||
}
|
||||
StopApplication::dispatch($application)->onQueue('high');
|
||||
StopApplication::dispatch($application);
|
||||
|
||||
return response()->json(
|
||||
[
|
||||
@@ -2811,3 +2827,30 @@ class ApplicationsController extends Controller
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$fqdn = str($fqdn)->replaceStart(',', '')->trim();
|
||||
$errors = [];
|
||||
$fqdn = str($fqdn)->trim()->explode(',')->map(function ($domain) use (&$errors) {
|
||||
if (filter_var($domain, FILTER_VALIDATE_URL) === false) {
|
||||
$errors[] = 'Invalid domain: ' . $domain;
|
||||
}
|
||||
|
||||
return str($domain)->trim()->lower();
|
||||
});
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -211,8 +211,9 @@ class DatabasesController extends Controller
|
||||
'mongo_conf' => ['type' => 'string', 'description' => 'Mongo conf'],
|
||||
'mongo_initdb_root_username' => ['type' => 'string', 'description' => 'Mongo initdb root username'],
|
||||
'mongo_initdb_root_password' => ['type' => 'string', 'description' => 'Mongo initdb root password'],
|
||||
'mongo_initdb_init_database' => ['type' => 'string', 'description' => 'Mongo initdb init database'],
|
||||
'mongo_initdb_database' => ['type' => 'string', 'description' => 'Mongo initdb init database'],
|
||||
'mysql_root_password' => ['type' => 'string', 'description' => 'MySQL root password'],
|
||||
'mysql_password' => ['type' => 'string', 'description' => 'MySQL password'],
|
||||
'mysql_user' => ['type' => 'string', 'description' => 'MySQL user'],
|
||||
'mysql_database' => ['type' => 'string', 'description' => 'MySQL database'],
|
||||
'mysql_conf' => ['type' => 'string', 'description' => 'MySQL conf'],
|
||||
@@ -241,7 +242,7 @@ class DatabasesController extends Controller
|
||||
)]
|
||||
public function update_by_uuid(Request $request)
|
||||
{
|
||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'postgres_user', 'postgres_password', 'postgres_db', 'postgres_initdb_args', 'postgres_host_auth_method', 'postgres_conf', 'clickhouse_admin_user', 'clickhouse_admin_password', 'dragonfly_password', 'redis_password', 'redis_conf', 'keydb_password', 'keydb_conf', 'mariadb_conf', 'mariadb_root_password', 'mariadb_user', 'mariadb_password', 'mariadb_database', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_init_database', 'mysql_root_password', 'mysql_user', 'mysql_database', 'mysql_conf'];
|
||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'postgres_user', 'postgres_password', 'postgres_db', 'postgres_initdb_args', 'postgres_host_auth_method', 'postgres_conf', 'clickhouse_admin_user', 'clickhouse_admin_password', 'dragonfly_password', 'redis_password', 'redis_conf', 'keydb_password', 'keydb_conf', 'mariadb_conf', 'mariadb_root_password', 'mariadb_user', 'mariadb_password', 'mariadb_database', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_database', 'mysql_root_password', 'mysql_password', 'mysql_user', 'mysql_database', 'mysql_conf'];
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
@@ -413,12 +414,12 @@ class DatabasesController extends Controller
|
||||
}
|
||||
break;
|
||||
case 'standalone-mongodb':
|
||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_init_database'];
|
||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_database'];
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'mongo_conf' => 'string',
|
||||
'mongo_initdb_root_username' => 'string',
|
||||
'mongo_initdb_root_password' => 'string',
|
||||
'mongo_initdb_init_database' => 'string',
|
||||
'mongo_initdb_database' => 'string',
|
||||
]);
|
||||
if ($request->has('mongo_conf')) {
|
||||
if (! isBase64Encoded($request->mongo_conf)) {
|
||||
@@ -443,9 +444,10 @@ class DatabasesController extends Controller
|
||||
|
||||
break;
|
||||
case 'standalone-mysql':
|
||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mysql_root_password', 'mysql_user', 'mysql_database', 'mysql_conf'];
|
||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mysql_root_password', 'mysql_password', 'mysql_user', 'mysql_database', 'mysql_conf'];
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'mysql_root_password' => 'string',
|
||||
'mysql_password' => 'string',
|
||||
'mysql_user' => 'string',
|
||||
'mysql_database' => 'string',
|
||||
'mysql_conf' => 'string',
|
||||
@@ -497,9 +499,9 @@ class DatabasesController extends Controller
|
||||
$database->update($request->all());
|
||||
|
||||
if ($whatToDoWithDatabaseProxy === 'start') {
|
||||
StartDatabaseProxy::dispatch($database)->onQueue('high');
|
||||
StartDatabaseProxy::dispatch($database);
|
||||
} elseif ($whatToDoWithDatabaseProxy === 'stop') {
|
||||
StopDatabaseProxy::dispatch($database)->onQueue('high');
|
||||
StopDatabaseProxy::dispatch($database);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
@@ -909,6 +911,7 @@ class DatabasesController extends Controller
|
||||
'environment_name' => ['type' => 'string', 'description' => 'Name of the environment'],
|
||||
'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'],
|
||||
'mysql_root_password' => ['type' => 'string', 'description' => 'MySQL root password'],
|
||||
'mysql_password' => ['type' => 'string', 'description' => 'MySQL password'],
|
||||
'mysql_user' => ['type' => 'string', 'description' => 'MySQL user'],
|
||||
'mysql_database' => ['type' => 'string', 'description' => 'MySQL database'],
|
||||
'mysql_conf' => ['type' => 'string', 'description' => 'MySQL conf'],
|
||||
@@ -1013,7 +1016,7 @@ class DatabasesController extends Controller
|
||||
|
||||
public function create_database(Request $request, NewDatabaseTypes $type)
|
||||
{
|
||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'postgres_user', 'postgres_password', 'postgres_db', 'postgres_initdb_args', 'postgres_host_auth_method', 'postgres_conf', 'clickhouse_admin_user', 'clickhouse_admin_password', 'dragonfly_password', 'redis_password', 'redis_conf', 'keydb_password', 'keydb_conf', 'mariadb_conf', 'mariadb_root_password', 'mariadb_user', 'mariadb_password', 'mariadb_database', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_init_database', 'mysql_root_password', 'mysql_user', 'mysql_database', 'mysql_conf'];
|
||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'postgres_user', 'postgres_password', 'postgres_db', 'postgres_initdb_args', 'postgres_host_auth_method', 'postgres_conf', 'clickhouse_admin_user', 'clickhouse_admin_password', 'dragonfly_password', 'redis_password', 'redis_conf', 'keydb_password', 'keydb_conf', 'mariadb_conf', 'mariadb_root_password', 'mariadb_user', 'mariadb_password', 'mariadb_database', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_database', 'mysql_root_password', 'mysql_password', 'mysql_user', 'mysql_database', 'mysql_conf'];
|
||||
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
@@ -1151,7 +1154,7 @@ class DatabasesController extends Controller
|
||||
}
|
||||
$database = create_standalone_postgresql($environment->id, $destination->uuid, $request->all());
|
||||
if ($instantDeploy) {
|
||||
StartDatabase::dispatch($database)->onQueue('high');
|
||||
StartDatabase::dispatch($database);
|
||||
}
|
||||
$database->refresh();
|
||||
$payload = [
|
||||
@@ -1206,7 +1209,7 @@ class DatabasesController extends Controller
|
||||
}
|
||||
$database = create_standalone_mariadb($environment->id, $destination->uuid, $request->all());
|
||||
if ($instantDeploy) {
|
||||
StartDatabase::dispatch($database)->onQueue('high');
|
||||
StartDatabase::dispatch($database);
|
||||
}
|
||||
|
||||
$database->refresh();
|
||||
@@ -1220,9 +1223,10 @@ class DatabasesController extends Controller
|
||||
|
||||
return response()->json(serializeApiResponse($payload))->setStatusCode(201);
|
||||
} elseif ($type === NewDatabaseTypes::MYSQL) {
|
||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mysql_user', 'mysql_database', 'mysql_conf'];
|
||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mysql_root_password', 'mysql_password', 'mysql_user', 'mysql_database', 'mysql_conf'];
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'mysql_root_password' => 'string',
|
||||
'mysql_password' => 'string',
|
||||
'mysql_user' => 'string',
|
||||
'mysql_database' => 'string',
|
||||
'mysql_conf' => 'string',
|
||||
@@ -1264,7 +1268,7 @@ class DatabasesController extends Controller
|
||||
}
|
||||
$database = create_standalone_mysql($environment->id, $destination->uuid, $request->all());
|
||||
if ($instantDeploy) {
|
||||
StartDatabase::dispatch($database)->onQueue('high');
|
||||
StartDatabase::dispatch($database);
|
||||
}
|
||||
|
||||
$database->refresh();
|
||||
@@ -1320,7 +1324,7 @@ class DatabasesController extends Controller
|
||||
}
|
||||
$database = create_standalone_redis($environment->id, $destination->uuid, $request->all());
|
||||
if ($instantDeploy) {
|
||||
StartDatabase::dispatch($database)->onQueue('high');
|
||||
StartDatabase::dispatch($database);
|
||||
}
|
||||
|
||||
$database->refresh();
|
||||
@@ -1357,7 +1361,7 @@ class DatabasesController extends Controller
|
||||
removeUnnecessaryFieldsFromRequest($request);
|
||||
$database = create_standalone_dragonfly($environment->id, $destination->uuid, $request->all());
|
||||
if ($instantDeploy) {
|
||||
StartDatabase::dispatch($database)->onQueue('high');
|
||||
StartDatabase::dispatch($database);
|
||||
}
|
||||
|
||||
return response()->json(serializeApiResponse([
|
||||
@@ -1406,7 +1410,7 @@ class DatabasesController extends Controller
|
||||
}
|
||||
$database = create_standalone_keydb($environment->id, $destination->uuid, $request->all());
|
||||
if ($instantDeploy) {
|
||||
StartDatabase::dispatch($database)->onQueue('high');
|
||||
StartDatabase::dispatch($database);
|
||||
}
|
||||
|
||||
$database->refresh();
|
||||
@@ -1442,7 +1446,7 @@ class DatabasesController extends Controller
|
||||
removeUnnecessaryFieldsFromRequest($request);
|
||||
$database = create_standalone_clickhouse($environment->id, $destination->uuid, $request->all());
|
||||
if ($instantDeploy) {
|
||||
StartDatabase::dispatch($database)->onQueue('high');
|
||||
StartDatabase::dispatch($database);
|
||||
}
|
||||
|
||||
$database->refresh();
|
||||
@@ -1456,12 +1460,12 @@ class DatabasesController extends Controller
|
||||
|
||||
return response()->json(serializeApiResponse($payload))->setStatusCode(201);
|
||||
} elseif ($type === NewDatabaseTypes::MONGODB) {
|
||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_init_database'];
|
||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_database'];
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'mongo_conf' => 'string',
|
||||
'mongo_initdb_root_username' => 'string',
|
||||
'mongo_initdb_root_password' => 'string',
|
||||
'mongo_initdb_init_database' => 'string',
|
||||
'mongo_initdb_database' => 'string',
|
||||
]);
|
||||
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||
if ($validator->fails() || ! empty($extraFields)) {
|
||||
@@ -1500,7 +1504,7 @@ class DatabasesController extends Controller
|
||||
}
|
||||
$database = create_standalone_mongodb($environment->id, $destination->uuid, $request->all());
|
||||
if ($instantDeploy) {
|
||||
StartDatabase::dispatch($database)->onQueue('high');
|
||||
StartDatabase::dispatch($database);
|
||||
}
|
||||
|
||||
$database->refresh();
|
||||
@@ -1557,7 +1561,8 @@ class DatabasesController extends Controller
|
||||
]
|
||||
)
|
||||
),
|
||||
]),
|
||||
]
|
||||
),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
@@ -1593,7 +1598,7 @@ class DatabasesController extends Controller
|
||||
deleteVolumes: $request->query->get('delete_volumes', true),
|
||||
dockerCleanup: $request->query->get('docker_cleanup', true),
|
||||
deleteConnectedNetworks: $request->query->get('delete_connected_networks', true)
|
||||
)->onQueue('high');
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Database deletion request queued.',
|
||||
@@ -1632,9 +1637,11 @@ class DatabasesController extends Controller
|
||||
type: 'object',
|
||||
properties: [
|
||||
'message' => ['type' => 'string', 'example' => 'Database starting request queued.'],
|
||||
])
|
||||
]
|
||||
)
|
||||
),
|
||||
]),
|
||||
]
|
||||
),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
@@ -1666,7 +1673,7 @@ class DatabasesController extends Controller
|
||||
if (str($database->status)->contains('running')) {
|
||||
return response()->json(['message' => 'Database is already running.'], 400);
|
||||
}
|
||||
StartDatabase::dispatch($database)->onQueue('high');
|
||||
StartDatabase::dispatch($database);
|
||||
|
||||
return response()->json(
|
||||
[
|
||||
@@ -1708,9 +1715,11 @@ class DatabasesController extends Controller
|
||||
type: 'object',
|
||||
properties: [
|
||||
'message' => ['type' => 'string', 'example' => 'Database stopping request queued.'],
|
||||
])
|
||||
]
|
||||
)
|
||||
),
|
||||
]),
|
||||
]
|
||||
),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
@@ -1742,7 +1751,7 @@ class DatabasesController extends Controller
|
||||
if (str($database->status)->contains('stopped') || str($database->status)->contains('exited')) {
|
||||
return response()->json(['message' => 'Database is already stopped.'], 400);
|
||||
}
|
||||
StopDatabase::dispatch($database)->onQueue('high');
|
||||
StopDatabase::dispatch($database);
|
||||
|
||||
return response()->json(
|
||||
[
|
||||
@@ -1784,9 +1793,11 @@ class DatabasesController extends Controller
|
||||
type: 'object',
|
||||
properties: [
|
||||
'message' => ['type' => 'string', 'example' => 'Database restaring request queued.'],
|
||||
])
|
||||
]
|
||||
)
|
||||
),
|
||||
]),
|
||||
]
|
||||
),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
@@ -1815,7 +1826,7 @@ class DatabasesController extends Controller
|
||||
if (! $database) {
|
||||
return response()->json(['message' => 'Database not found.'], 404);
|
||||
}
|
||||
RestartDatabase::dispatch($database)->onQueue('high');
|
||||
RestartDatabase::dispatch($database);
|
||||
|
||||
return response()->json(
|
||||
[
|
||||
|
||||
@@ -307,7 +307,7 @@ class DeployController extends Controller
|
||||
break;
|
||||
default:
|
||||
// Database resource
|
||||
StartDatabase::dispatch($resource)->onQueue('high');
|
||||
StartDatabase::dispatch($resource);
|
||||
$resource->update([
|
||||
'started_at' => now(),
|
||||
]);
|
||||
|
||||
@@ -37,7 +37,7 @@ class OtherController extends Controller
|
||||
)]
|
||||
public function version(Request $request)
|
||||
{
|
||||
return response(config('version'));
|
||||
return response(config('constants.coolify.version'));
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
|
||||
@@ -550,7 +550,7 @@ class ServersController extends Controller
|
||||
'is_build_server' => $request->is_build_server,
|
||||
]);
|
||||
if ($request->instant_validate) {
|
||||
ValidateServer::dispatch($server)->onQueue('high');
|
||||
ValidateServer::dispatch($server);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
@@ -567,6 +567,9 @@ class ServersController extends Controller
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
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(
|
||||
required: true,
|
||||
description: 'Server updated.',
|
||||
@@ -596,8 +599,7 @@ class ServersController extends Controller
|
||||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'array',
|
||||
items: new OA\Items(ref: '#/components/schemas/Server')
|
||||
ref: '#/components/schemas/Server'
|
||||
)
|
||||
),
|
||||
]),
|
||||
@@ -675,11 +677,11 @@ class ServersController extends Controller
|
||||
]);
|
||||
}
|
||||
if ($request->instant_validate) {
|
||||
ValidateServer::dispatch($server)->onQueue('high');
|
||||
ValidateServer::dispatch($server);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
|
||||
'uuid' => $server->uuid,
|
||||
])->setStatusCode(201);
|
||||
}
|
||||
|
||||
@@ -813,7 +815,7 @@ class ServersController extends Controller
|
||||
if (! $server) {
|
||||
return response()->json(['message' => 'Server not found.'], 404);
|
||||
}
|
||||
ValidateServer::dispatch($server)->onQueue('high');
|
||||
ValidateServer::dispatch($server);
|
||||
|
||||
return response()->json(['message' => 'Validation started.']);
|
||||
}
|
||||
|
||||
@@ -342,7 +342,7 @@ class ServicesController extends Controller
|
||||
}
|
||||
$service->parse(isNew: true);
|
||||
if ($instantDeploy) {
|
||||
StartService::dispatch($service)->onQueue('high');
|
||||
StartService::dispatch($service);
|
||||
}
|
||||
$domains = $service->applications()->get()->pluck('fqdn')->sort();
|
||||
$domains = $domains->map(function ($domain) {
|
||||
@@ -487,7 +487,7 @@ class ServicesController extends Controller
|
||||
deleteVolumes: $request->query->get('delete_volumes', true),
|
||||
dockerCleanup: $request->query->get('docker_cleanup', true),
|
||||
deleteConnectedNetworks: $request->query->get('delete_connected_networks', true)
|
||||
)->onQueue('high');
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Service deletion request queued.',
|
||||
@@ -1076,7 +1076,7 @@ class ServicesController extends Controller
|
||||
if (str($service->status())->contains('running')) {
|
||||
return response()->json(['message' => 'Service is already running.'], 400);
|
||||
}
|
||||
StartService::dispatch($service)->onQueue('high');
|
||||
StartService::dispatch($service);
|
||||
|
||||
return response()->json(
|
||||
[
|
||||
@@ -1154,7 +1154,7 @@ class ServicesController extends Controller
|
||||
if (str($service->status())->contains('stopped') || str($service->status())->contains('exited')) {
|
||||
return response()->json(['message' => 'Service is already stopped.'], 400);
|
||||
}
|
||||
StopService::dispatch($service)->onQueue('high');
|
||||
StopService::dispatch($service);
|
||||
|
||||
return response()->json(
|
||||
[
|
||||
@@ -1229,7 +1229,7 @@ class ServicesController extends Controller
|
||||
if (! $service) {
|
||||
return response()->json(['message' => 'Service not found.'], 404);
|
||||
}
|
||||
RestartService::dispatch($service)->onQueue('high');
|
||||
RestartService::dispatch($service);
|
||||
|
||||
return response()->json(
|
||||
[
|
||||
|
||||
@@ -463,7 +463,7 @@ class Github extends Controller
|
||||
$private_key = data_get($data, 'pem');
|
||||
$webhook_secret = data_get($data, 'webhook_secret');
|
||||
$private_key = PrivateKey::create([
|
||||
'name' => $slug,
|
||||
'name' => "github-app-{$slug}",
|
||||
'private_key' => $private_key,
|
||||
'team_id' => $github_app->team_id,
|
||||
'is_git_related' => true,
|
||||
|
||||
@@ -3,21 +3,26 @@
|
||||
namespace App\Http\Controllers\Webhook;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Jobs\ServerLimitCheckJob;
|
||||
use App\Jobs\SubscriptionInvoiceFailedJob;
|
||||
use App\Models\Subscription;
|
||||
use App\Models\Team;
|
||||
use App\Jobs\StripeProcessJob;
|
||||
use App\Models\Webhook;
|
||||
use Exception;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class Stripe extends Controller
|
||||
{
|
||||
protected $webhook;
|
||||
|
||||
public function events(Request $request)
|
||||
{
|
||||
try {
|
||||
$webhookSecret = config('subscription.stripe_webhook_secret');
|
||||
$signature = $request->header('Stripe-Signature');
|
||||
$event = \Stripe\Webhook::constructEvent(
|
||||
$request->getContent(),
|
||||
$signature,
|
||||
$webhookSecret
|
||||
);
|
||||
if (app()->isDownForMaintenance()) {
|
||||
$epoch = now()->valueOf();
|
||||
$data = [
|
||||
@@ -33,241 +38,17 @@ class Stripe extends Controller
|
||||
$json = json_encode($data);
|
||||
Storage::disk('webhooks-during-maintenance')->put("{$epoch}_Stripe::events_stripe", $json);
|
||||
|
||||
return;
|
||||
return response('Webhook received. Cool cool cool cool cool.', 200);
|
||||
}
|
||||
$webhookSecret = config('subscription.stripe_webhook_secret');
|
||||
$signature = $request->header('Stripe-Signature');
|
||||
$excludedPlans = config('subscription.stripe_excluded_plans');
|
||||
$event = \Stripe\Webhook::constructEvent(
|
||||
$request->getContent(),
|
||||
$signature,
|
||||
$webhookSecret
|
||||
);
|
||||
$webhook = Webhook::create([
|
||||
$this->webhook = Webhook::create([
|
||||
'type' => 'stripe',
|
||||
'payload' => $request->getContent(),
|
||||
]);
|
||||
$type = data_get($event, 'type');
|
||||
$data = data_get($event, 'data.object');
|
||||
switch ($type) {
|
||||
case 'radar.early_fraud_warning.created':
|
||||
$stripe = new \Stripe\StripeClient(config('subscription.stripe_api_key'));
|
||||
$id = data_get($data, 'id');
|
||||
$charge = data_get($data, 'charge');
|
||||
if ($charge) {
|
||||
$stripe->refunds->create(['charge' => $charge]);
|
||||
}
|
||||
$pi = data_get($data, 'payment_intent');
|
||||
$piData = $stripe->paymentIntents->retrieve($pi, []);
|
||||
$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}");
|
||||
StripeProcessJob::dispatch($event);
|
||||
|
||||
return response("Early fraud warning: subscription not found. Charge: {$charge}, id: {$id}, pi: {$pi}", 400);
|
||||
}
|
||||
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}.");
|
||||
|
||||
return response("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}, subscriptionid: {$subscriptionId}.", 400);
|
||||
}
|
||||
$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 {
|
||||
return response("No subscription found for customer: {$customerId}", 400);
|
||||
}
|
||||
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);
|
||||
|
||||
return response('No subscription found in Coolify.');
|
||||
}
|
||||
$team = data_get($subscription, 'team');
|
||||
if (! $team) {
|
||||
// send_internal_notification('invoice.payment_failed failed but no team found in Coolify for customer: '.$customerId);
|
||||
|
||||
return response('No team found in Coolify.');
|
||||
}
|
||||
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);
|
||||
|
||||
return response('No subscription found in Coolify.');
|
||||
}
|
||||
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) {
|
||||
return response("Subscription already exists for customer: {$customerId}", 200);
|
||||
}
|
||||
|
||||
return response('No team id or user id found', 400);
|
||||
}
|
||||
$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}.");
|
||||
|
||||
return response("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}.", 400);
|
||||
}
|
||||
$subscription = Subscription::where('team_id', $teamId)->first();
|
||||
if ($subscription) {
|
||||
return response("Subscription already exists for team: {$teamId}", 200);
|
||||
} else {
|
||||
Subscription::create([
|
||||
'team_id' => $teamId,
|
||||
'stripe_subscription_id' => $subscriptionId,
|
||||
'stripe_customer_id' => $customerId,
|
||||
'stripe_invoice_paid' => false,
|
||||
]);
|
||||
|
||||
return response('Subscription created');
|
||||
}
|
||||
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;
|
||||
}
|
||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
||||
if (! $subscription) {
|
||||
if ($status === 'incomplete_expired') {
|
||||
return response('Subscription incomplete expired', 200);
|
||||
}
|
||||
if ($teamId) {
|
||||
$subscription = Subscription::create([
|
||||
'team_id' => $teamId,
|
||||
'stripe_subscription_id' => $subscriptionId,
|
||||
'stripe_customer_id' => $customerId,
|
||||
'stripe_invoice_paid' => false,
|
||||
]);
|
||||
} else {
|
||||
return response('No subscription and team id found', 400);
|
||||
}
|
||||
}
|
||||
$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);
|
||||
}
|
||||
$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([
|
||||
'stripe_invoice_paid' => false,
|
||||
]);
|
||||
}
|
||||
if ($feedback) {
|
||||
$reason = "Cancellation feedback for {$customerId}: '".$feedback."'";
|
||||
if ($comment) {
|
||||
$reason .= ' with comment: \''.$comment."'";
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'customer.subscription.deleted':
|
||||
// End subscription
|
||||
$customerId = data_get($data, 'customer');
|
||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
|
||||
$team = data_get($subscription, 'team');
|
||||
$team?->subscriptionEnded();
|
||||
break;
|
||||
default:
|
||||
// Unhandled event type
|
||||
}
|
||||
return response('Webhook received. Cool cool cool cool cool.', 200);
|
||||
} catch (Exception $e) {
|
||||
if ($type !== 'payment_intent.payment_failed') {
|
||||
send_internal_notification("Subscription webhook ($type) failed: ".$e->getMessage());
|
||||
}
|
||||
$webhook->update([
|
||||
$this->webhook->update([
|
||||
'status' => 'failed',
|
||||
'failure_reason' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
@@ -140,6 +140,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
|
||||
private ?string $buildTarget = null;
|
||||
|
||||
private bool $disableBuildCache = false;
|
||||
|
||||
private Collection $saved_outputs;
|
||||
|
||||
private ?string $full_healthcheck_url = null;
|
||||
@@ -166,6 +168,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
|
||||
public function __construct(int $application_deployment_queue_id)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
|
||||
$this->application_deployment_queue = ApplicationDeploymentQueue::find($application_deployment_queue_id);
|
||||
$this->application = Application::find($this->application_deployment_queue->application_id);
|
||||
$this->build_pack = data_get($this->application, 'build_pack');
|
||||
@@ -176,7 +180,11 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
$this->pull_request_id = $this->application_deployment_queue->pull_request_id;
|
||||
$this->commit = $this->application_deployment_queue->commit;
|
||||
$this->rollback = $this->application_deployment_queue->rollback;
|
||||
$this->disableBuildCache = $this->application->settings->disable_build_cache;
|
||||
$this->force_rebuild = $this->application_deployment_queue->force_rebuild;
|
||||
if ($this->disableBuildCache) {
|
||||
$this->force_rebuild = true;
|
||||
}
|
||||
$this->restart_only = $this->application_deployment_queue->restart_only;
|
||||
$this->restart_only = $this->restart_only && $this->application->build_pack !== 'dockerimage' && $this->application->build_pack !== 'dockerfile';
|
||||
$this->only_this_server = $this->application_deployment_queue->only_this_server;
|
||||
@@ -349,8 +357,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
private function post_deployment()
|
||||
{
|
||||
if ($this->server->isProxyShouldRun()) {
|
||||
GetContainersStatus::dispatch($this->server)->onQueue('high');
|
||||
// dispatch(new ContainerStatusJob($this->server));
|
||||
GetContainersStatus::dispatch($this->server);
|
||||
}
|
||||
$this->next(ApplicationDeploymentStatus::FINISHED->value);
|
||||
if ($this->pull_request_id !== 0) {
|
||||
@@ -462,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'));
|
||||
$this->save_environment_variables();
|
||||
if (! is_null($this->env_filename)) {
|
||||
$services = collect($composeFile['services']);
|
||||
$services = collect(data_get($composeFile, 'services', []));
|
||||
$services = $services->map(function ($service, $name) {
|
||||
$service['env_file'] = [$this->env_filename];
|
||||
|
||||
@@ -1975,6 +1982,9 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
$this->build_args = $this->build_args->implode(' ');
|
||||
|
||||
$this->application_deployment_queue->addLogEntry('----------------------------------------');
|
||||
if ($this->disableBuildCache) {
|
||||
$this->application_deployment_queue->addLogEntry('Docker build cache is disabled. It will not be used during the build process.');
|
||||
}
|
||||
if ($this->application->build_pack === 'static') {
|
||||
$this->application_deployment_queue->addLogEntry('Static deployment. Copying static assets to the image.');
|
||||
} else {
|
||||
@@ -2399,7 +2409,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
if (! $this->only_this_server) {
|
||||
$this->deploy_to_additional_destinations();
|
||||
}
|
||||
$this->application->environment->project->team?->notify(new DeploymentSuccess($this->application, $this->deployment_uuid, $this->preview));
|
||||
//$this->application->environment->project->team?->notify(new DeploymentSuccess($this->application, $this->deployment_uuid, $this->preview));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,9 @@ class ApplicationPullRequestUpdateJob implements ShouldBeEncrypted, ShouldQueue
|
||||
public ApplicationPreview $preview,
|
||||
public ProcessStatus $status,
|
||||
public ?string $deployment_uuid = null
|
||||
) {}
|
||||
) {
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
|
||||
@@ -27,7 +27,7 @@ class CheckForUpdatesJob implements ShouldBeEncrypted, ShouldQueue
|
||||
$versions = $response->json();
|
||||
|
||||
$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, '>')) {
|
||||
// New version available
|
||||
|
||||
@@ -23,7 +23,10 @@ class CoolifyTask implements ShouldBeEncrypted, ShouldQueue
|
||||
public bool $ignore_errors,
|
||||
public $call_event_on_finish,
|
||||
public $call_event_data,
|
||||
) {}
|
||||
) {
|
||||
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
|
||||
@@ -60,12 +60,15 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
|
||||
public function __construct($backup)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
$this->backup = $backup;
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
$databasesToBackup = null;
|
||||
|
||||
$this->team = Team::find($this->backup->team_id);
|
||||
if (! $this->team) {
|
||||
$this->backup->delete();
|
||||
@@ -197,8 +200,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
$databaseType = $this->database->type();
|
||||
$databasesToBackup = data_get($this->backup, 'databases_to_backup');
|
||||
}
|
||||
|
||||
if (is_null($databasesToBackup)) {
|
||||
if (blank($databasesToBackup)) {
|
||||
if (str($databaseType)->contains('postgres')) {
|
||||
$databasesToBackup = [$this->database->postgres_db];
|
||||
} elseif (str($databaseType)->contains('mongodb')) {
|
||||
@@ -304,7 +306,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
if ($this->backup->save_s3) {
|
||||
$this->upload_to_s3();
|
||||
}
|
||||
$this->team?->notify(new BackupSuccess($this->backup, $this->database, $database));
|
||||
//$this->team?->notify(new BackupSuccess($this->backup, $this->database, $database));
|
||||
$this->backup_log->update([
|
||||
'status' => 'success',
|
||||
'message' => $this->backup_output,
|
||||
@@ -319,12 +321,10 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
'filename' => null,
|
||||
]);
|
||||
}
|
||||
send_internal_notification('DatabaseBackupJob failed with: '.$e->getMessage());
|
||||
$this->team?->notify(new BackupFailed($this->backup, $this->database, $this->backup_output, $database));
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
send_internal_notification('DatabaseBackupJob failed with: '.$e->getMessage());
|
||||
throw $e;
|
||||
} finally {
|
||||
if ($this->team) {
|
||||
|
||||
@@ -35,7 +35,9 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
|
||||
public bool $deleteVolumes = true,
|
||||
public bool $dockerCleanup = true,
|
||||
public bool $deleteConnectedNetworks = true
|
||||
) {}
|
||||
) {
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
@@ -87,7 +89,6 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
|
||||
$this->resource?->delete_connected_networks($this->resource->uuid);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
send_internal_notification('ContainerStoppingJob failed with: '.$e->getMessage());
|
||||
throw $e;
|
||||
} finally {
|
||||
$this->resource->forceDelete();
|
||||
|
||||
@@ -16,7 +16,10 @@ class PullHelperImageJob implements ShouldBeEncrypted, ShouldQueue
|
||||
|
||||
public $timeout = 1000;
|
||||
|
||||
public function __construct(public Server $server) {}
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
|
||||
@@ -17,7 +17,10 @@ class PullTemplatesFromCDN implements ShouldBeEncrypted, ShouldQueue
|
||||
|
||||
public $timeout = 10;
|
||||
|
||||
public function __construct() {}
|
||||
public function __construct()
|
||||
{
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
|
||||
@@ -360,7 +360,7 @@ class PushServerUpdateJob implements ShouldBeEncrypted, ShouldQueue
|
||||
private function checkLogDrainContainer()
|
||||
{
|
||||
if ($this->server->isLogDrainEnabled() && $this->foundLogDrainContainer === false) {
|
||||
StartLogDrain::dispatch($this->server)->onQueue('high');
|
||||
StartLogDrain::dispatch($this->server);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,8 @@ class ScheduledTaskJob implements ShouldQueue
|
||||
|
||||
public function __construct($task)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
|
||||
$this->task = $task;
|
||||
if ($service = $task->service()->first()) {
|
||||
$this->resource = $service;
|
||||
|
||||
@@ -32,7 +32,9 @@ class SendMessageToDiscordJob implements ShouldBeEncrypted, ShouldQueue
|
||||
public function __construct(
|
||||
public DiscordMessage $message,
|
||||
public string $webhookUrl
|
||||
) {}
|
||||
) {
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
|
||||
@@ -33,7 +33,9 @@ class SendMessageToTelegramJob implements ShouldBeEncrypted, ShouldQueue
|
||||
public string $token,
|
||||
public string $chatId,
|
||||
public ?string $topicId = null,
|
||||
) {}
|
||||
) {
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
@@ -70,7 +72,7 @@ class SendMessageToTelegramJob implements ShouldBeEncrypted, ShouldQueue
|
||||
}
|
||||
$response = Http::post($url, $payload);
|
||||
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()];
|
||||
}
|
||||
|
||||
public function __construct(public Server $server) {}
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
if (isDev()) {
|
||||
$this->handle();
|
||||
}
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
@@ -94,10 +99,10 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
|
||||
if ($foundLogDrainContainer) {
|
||||
$status = data_get($foundLogDrainContainer, 'State.Status');
|
||||
if ($status !== 'running') {
|
||||
StartLogDrain::dispatch($this->server)->onQueue('high');
|
||||
StartLogDrain::dispatch($this->server);
|
||||
}
|
||||
} else {
|
||||
StartLogDrain::dispatch($this->server)->onQueue('high');
|
||||
StartLogDrain::dispatch($this->server);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,10 @@ class ServerFilesFromServerJob implements ShouldBeEncrypted, ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public function __construct(public ServiceApplication|ServiceDatabase|Application $resource) {}
|
||||
public function __construct(public ServiceApplication|ServiceDatabase|Application $resource)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
|
||||
@@ -14,7 +14,10 @@ class ServerStorageSaveJob implements ShouldBeEncrypted, ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public function __construct(public LocalFileVolume $localFileVolume) {}
|
||||
public function __construct(public LocalFileVolume $localFileVolume)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\Subscription;
|
||||
use App\Models\Team;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Queue\Queueable;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class StripeProcessJob implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public $type;
|
||||
|
||||
public $webhook;
|
||||
|
||||
public $tries = 3;
|
||||
|
||||
public function __construct(public $event)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
$excludedPlans = config('subscription.stripe_excluded_plans');
|
||||
|
||||
$type = data_get($this->event, 'type');
|
||||
$this->type = $type;
|
||||
$data = data_get($this->event, 'data.object');
|
||||
switch ($type) {
|
||||
case 'radar.early_fraud_warning.created':
|
||||
$stripe = new \Stripe\StripeClient(config('subscription.stripe_api_key'));
|
||||
$id = data_get($data, 'id');
|
||||
$charge = data_get($data, 'charge');
|
||||
if ($charge) {
|
||||
$stripe->refunds->create(['charge' => $charge]);
|
||||
}
|
||||
$pi = data_get($data, 'payment_intent');
|
||||
$piData = $stripe->paymentIntents->retrieve($pi, []);
|
||||
$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 \RuntimeException("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 \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,
|
||||
'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;
|
||||
}
|
||||
$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);
|
||||
}
|
||||
$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([
|
||||
'stripe_invoice_paid' => false,
|
||||
]);
|
||||
}
|
||||
if ($feedback) {
|
||||
$reason = "Cancellation feedback for {$customerId}: '".$feedback."'";
|
||||
if ($comment) {
|
||||
$reason .= ' with comment: \''.$comment."'";
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'customer.subscription.deleted':
|
||||
// End subscription
|
||||
$customerId = data_get($data, 'customer');
|
||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
|
||||
$team = data_get($subscription, 'team');
|
||||
$team?->subscriptionEnded();
|
||||
break;
|
||||
default:
|
||||
throw new \RuntimeException("Unhandled event type: {$type}");
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
send_internal_notification('StripeProcessJob error: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,10 @@ class SubscriptionInvoiceFailedJob implements ShouldBeEncrypted, ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public function __construct(protected Team $team) {}
|
||||
public function __construct(protected Team $team)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
|
||||
@@ -18,6 +18,11 @@ class UpdateCoolifyJob implements ShouldBeEncrypted, ShouldQueue
|
||||
|
||||
public $timeout = 600;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
|
||||
@@ -172,13 +172,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
|
||||
public function getProxyType()
|
||||
{
|
||||
// Set Default Proxy Type
|
||||
$this->selectProxy(ProxyTypes::TRAEFIK->value);
|
||||
// $proxyTypeSet = $this->createdServer->proxy->type;
|
||||
// if (!$proxyTypeSet) {
|
||||
// $this->currentState = 'select-proxy';
|
||||
// return;
|
||||
// }
|
||||
$this->getProjects();
|
||||
}
|
||||
|
||||
@@ -189,7 +183,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
|
||||
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->currentState = 'create-server';
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ class Docker extends Component
|
||||
$this->network = new Cuid2;
|
||||
$this->servers = Server::isUsable()->get();
|
||||
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;
|
||||
} else {
|
||||
$this->selectedServer = $this->servers->first();
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use Illuminate\Container\Attributes\Auth as AttributesAuth;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
@@ -32,7 +31,7 @@ class NavbarDeleteTeam extends Component
|
||||
$currentTeam->delete();
|
||||
|
||||
$currentTeam->members->each(function ($user) use ($currentTeam) {
|
||||
if ($user->id === AttributesAuth::id()) {
|
||||
if ($user->id === Auth::id()) {
|
||||
return;
|
||||
}
|
||||
$user->teams()->detach($currentTeam);
|
||||
|
||||
@@ -37,7 +37,7 @@ class Email extends Component
|
||||
#[Validate(['nullable', 'numeric'])]
|
||||
public ?int $smtpPort = null;
|
||||
|
||||
#[Validate(['nullable', 'string'])]
|
||||
#[Validate(['nullable', 'string', 'in:tls,ssl,none'])]
|
||||
public ?string $smtpEncryption = null;
|
||||
|
||||
#[Validate(['nullable', 'string'])]
|
||||
@@ -73,6 +73,9 @@ class Email extends Component
|
||||
#[Validate(['nullable', 'string'])]
|
||||
public ?string $resendApiKey = null;
|
||||
|
||||
#[Validate(['nullable', 'email'])]
|
||||
public ?string $testEmailAddress = null;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
try {
|
||||
@@ -132,14 +135,21 @@ class Email extends Component
|
||||
}
|
||||
}
|
||||
|
||||
public function sendTestNotification()
|
||||
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->emails));
|
||||
$this->team?->notify(new Test($this->testEmailAddress));
|
||||
$this->dispatch('success', 'Test Email sent.');
|
||||
},
|
||||
$decaySeconds = 10,
|
||||
|
||||
@@ -25,6 +25,9 @@ class Advanced extends Component
|
||||
#[Validate(['boolean'])]
|
||||
public bool $isAutoDeployEnabled = true;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $disableBuildCache = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $isLogDrainEnabled = false;
|
||||
|
||||
@@ -95,6 +98,7 @@ class Advanced extends Component
|
||||
$this->application->settings->is_stripprefix_enabled = $this->isStripprefixEnabled;
|
||||
$this->application->settings->is_raw_compose_deployment_enabled = $this->isRawComposeDeploymentEnabled;
|
||||
$this->application->settings->connect_to_docker_network = $this->isConnectToDockerNetworkEnabled;
|
||||
$this->application->settings->disable_build_cache = $this->disableBuildCache;
|
||||
$this->application->settings->save();
|
||||
} else {
|
||||
$this->isForceHttpsEnabled = $this->application->isForceHttpsEnabled();
|
||||
@@ -116,6 +120,7 @@ class Advanced extends Component
|
||||
$this->customInternalName = $this->application->settings->custom_internal_name;
|
||||
$this->isRawComposeDeploymentEnabled = $this->application->settings->is_raw_compose_deployment_enabled;
|
||||
$this->isConnectToDockerNetworkEnabled = $this->application->settings->connect_to_docker_network;
|
||||
$this->disableBuildCache = $this->application->settings->disable_build_cache;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,24 +16,30 @@ class Configuration extends Component
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
||||
if (! $project) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']);
|
||||
if (! $environment) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
$application = $environment->applications->where('uuid', request()->route('application_uuid'))->first();
|
||||
if (! $application) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
$project = currentTeam()
|
||||
->projects()
|
||||
->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();
|
||||
$application = $environment->applications()
|
||||
->with(['destination'])
|
||||
->where('uuid', request()->route('application_uuid'))
|
||||
->firstOrFail();
|
||||
|
||||
$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;
|
||||
});
|
||||
if ($application->destination && $application->destination->server) {
|
||||
$mainServer = $application->destination->server;
|
||||
$this->servers = Server::ownedByCurrentTeam()
|
||||
->select('id', 'name')
|
||||
->where('id', '!=', $mainServer->id)
|
||||
->get();
|
||||
} else {
|
||||
$this->servers = collect();
|
||||
}
|
||||
}
|
||||
|
||||
public function render()
|
||||
|
||||
@@ -36,7 +36,11 @@ class Heading extends Component
|
||||
|
||||
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();
|
||||
$this->lastDeploymentInfo = data_get_str($lastDeployment, 'commit')->limit(7).' '.data_get($lastDeployment, 'commit_message');
|
||||
$this->lastDeploymentLink = $this->application->gitCommitLink(data_get($lastDeployment, 'commit'));
|
||||
@@ -45,13 +49,11 @@ class Heading extends Component
|
||||
public function check_status($showNotification = false)
|
||||
{
|
||||
if ($this->application->destination->server->isFunctional()) {
|
||||
GetContainersStatus::dispatch($this->application->destination->server)->onQueue('high');
|
||||
GetContainersStatus::dispatch($this->application->destination->server);
|
||||
}
|
||||
if ($showNotification) {
|
||||
$this->dispatch('success', 'Success', 'Application status updated.');
|
||||
}
|
||||
// Removed because it caused flickering
|
||||
// $this->dispatch('configurationChanged');
|
||||
}
|
||||
|
||||
public function force_deploy_without_cache()
|
||||
|
||||
@@ -91,12 +91,16 @@ class Select extends Component
|
||||
{
|
||||
$services = get_service_templates(true);
|
||||
$services = collect($services)->map(function ($service, $key) {
|
||||
$logo = data_get($service, 'logo', 'svgs/coolify.png');
|
||||
$default_logo = 'images/default.webp';
|
||||
$logo = data_get($service, 'logo', $default_logo);
|
||||
$local_logo_path = public_path($logo);
|
||||
|
||||
return [
|
||||
'name' => str($key)->headline(),
|
||||
'logo' => asset($logo),
|
||||
'logo_github_url' => 'https://raw.githubusercontent.com/coollabsio/coolify/refs/heads/main/public/a'.$logo,
|
||||
'logo_github_url' => file_exists($local_logo_path)
|
||||
? 'https://raw.githubusercontent.com/coollabsio/coolify/refs/heads/main/public/'.$logo
|
||||
: asset($default_logo),
|
||||
] + (array) $service;
|
||||
})->all();
|
||||
$gitBasedApplications = [
|
||||
@@ -144,14 +148,14 @@ class Select extends Component
|
||||
'id' => 'postgresql',
|
||||
'name' => 'PostgreSQL',
|
||||
'description' => 'PostgreSQL is an object-relational database known for its robustness, advanced features, and strong standards compliance.',
|
||||
'logo' => '<svg class="w-[4.5rem] aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-white fill-black" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path fill="#currentColor" d="M85.988 76.075c.632-5.262.443-6.034 4.362-5.182l.995.088c3.014.137 6.957-.485 9.272-1.561 4.986-2.313 7.942-6.177 3.026-5.162-11.215 2.313-11.986-1.483-11.986-1.483C103.5 45.204 108.451 22.9 104.178 17.44 92.524 2.548 72.35 9.59 72.012 9.773l-.108.021c-2.216-.461-4.695-.735-7.481-.78-5.075-.083-8.926 1.331-11.847 3.546 0 0-35.989-14.827-34.315 18.646.356 7.121 10.207 53.882 21.956 39.758 4.294-5.164 8.444-9.531 8.444-9.531 2.061 1.369 4.528 2.067 7.116 1.816l.2-.17c-.062.641-.035 1.268.081 2.01-3.027 3.383-2.137 3.977-8.189 5.222-6.122 1.262-2.525 3.508-.178 4.095 2.848.713 9.433 1.722 13.884-4.509l-.177.711c1.188.95 1.107 6.827 1.275 11.026.168 4.199.45 8.117 1.306 10.429.856 2.31 1.866 8.261 9.819 6.557 6.646-1.426 11.727-3.476 12.19-22.545"/><path fill="#currentColor" d="M71.208 102.77c-3.518 0-5.808-1.36-7.2-2.674-2.1-1.981-2.933-4.534-3.43-6.059l-.215-.637c-1.002-2.705-1.341-6.599-1.542-11.613a199.25 199.25 0 01-.075-2.352c-.017-.601-.038-1.355-.068-2.146a15.157 15.157 0 01-3.997 1.264c-2.48.424-5.146.286-7.926-.409-1.961-.49-3.999-1.506-5.16-3.076-3.385 2.965-6.614 2.562-8.373 1.976-3.103-1.035-5.88-3.942-8.491-8.89-1.859-3.523-3.658-8.115-5.347-13.646-2.94-9.633-4.808-19.779-4.974-23.109-.522-10.427 2.284-17.883 8.34-22.16 9.555-6.749 24.03-2.781 29.307-.979 3.545-2.137 7.716-3.178 12.43-3.102 2.532.041 4.942.264 7.181.662 2.335-.734 6.949-1.788 12.23-1.723 9.73.116 17.793 3.908 23.316 10.966 3.941 5.036 1.993 15.61.48 21.466-2.127 8.235-5.856 16.996-10.436 24.622 1.244.009 3.045-.141 5.607-.669 5.054-1.044 6.531 1.666 6.932 2.879 1.607 4.867-5.378 8.544-7.557 9.555-2.792 1.297-7.343 2.086-11.071 1.915l-.163-.011-.979-.086-.097.816-.093.799c-.25 9.664-1.631 15.784-4.472 19.829-2.977 4.239-7.116 5.428-10.761 6.209a16.146 16.146 0 01-3.396.383zm-7.402-35.174c2.271 1.817 2.47 5.236 2.647 11.626.022.797.043 1.552.071 2.257.086 2.134.287 7.132 1.069 9.244.111.298.21.602.314.922.872 2.672 1.31 4.011 5.081 3.203 3.167-.678 4.794-1.287 6.068-3.101 1.852-2.638 2.888-7.941 3.078-15.767l3.852.094-3.826-.459.112-.955c.367-3.148.631-5.424 2.736-6.928 1.688-1.207 3.613-1.09 5.146-.814-1.684-1.271-2.15-2.765-2.274-3.377l-.321-1.582.902-1.34c5.2-7.716 9.489-17.199 11.767-26.018 2.34-9.062 1.626-13.875.913-14.785-9.446-12.071-25.829-7.088-27.539-6.521l-.29.156-1.45.271-.743-.154c-2.047-.425-4.321-.66-6.76-.7-3.831-.064-6.921.841-9.455 2.764l-1.758 1.333-2.041-.841c-4.358-1.782-17.162-5.365-23.918-.58-3.75 2.656-5.458 7.861-5.078 15.47.125 2.512 1.833 12.021 4.647 21.245 3.891 12.746 7.427 16.979 8.903 17.472.257.087.926-.433 1.591-1.231 4.326-5.203 8.44-9.54 8.613-9.723l2.231-2.347 2.697 1.792c1.087.723 2.286 1.132 3.518 1.209l6.433-5.486-.932 9.51c-.021.214-.031.504.053 1.044l.28 1.803-1.213 1.358-.14.157 3.534 1.632 1.482-1.853z"/><path fill="#336791" d="M103.646 64.258c-11.216 2.313-11.987-1.484-11.987-1.484 11.842-17.571 16.792-39.876 12.52-45.335C92.524 2.547 72.35 9.59 72.013 9.773l-.109.019c-2.216-.459-4.695-.733-7.482-.778-5.075-.083-8.925 1.33-11.846 3.545 0 0-35.99-14.826-34.316 18.647.356 7.121 10.207 53.882 21.956 39.758 4.294-5.164 8.443-9.531 8.443-9.531 2.061 1.369 4.528 2.067 7.115 1.816l.201-.17c-.062.641-.034 1.268.08 2.01-3.026 3.383-2.138 3.977-8.188 5.222-6.123 1.262-2.526 3.508-.177 4.095 2.847.713 9.433 1.722 13.883-4.509l-.178.711c1.186.95 2.019 6.179 1.879 10.919s-.233 7.994.702 10.536c.935 2.541 1.866 8.261 9.82 6.557 6.646-1.425 10.09-5.116 10.57-11.272.34-4.377 1.109-3.73 1.158-7.644l.618-1.853c.711-5.934.113-7.848 4.208-6.957l.995.087c3.014.138 6.958-.485 9.273-1.561 4.986-2.314 7.943-6.177 3.028-5.162z"/><path fill="#fff" d="M71.61 100.394c-6.631.001-8.731-5.25-9.591-7.397-1.257-3.146-1.529-15.358-1.249-25.373a1.286 1.286 0 012.57.072c-.323 11.551.136 22.018 1.066 24.346 1.453 3.632 3.656 6.809 9.887 5.475 5.915-1.269 8.13-3.512 9.116-9.23.758-4.389 2.254-16.874 2.438-19.338a1.285 1.285 0 012.563.191c-.192 2.564-1.682 15.026-2.469 19.584-1.165 6.755-4.176 9.819-11.11 11.306a15.462 15.462 0 01-3.221.364zM35.659 74.749a5.343 5.343 0 01-1.704-.281c-4.307-1.437-8.409-8.451-12.193-20.849-2.88-9.438-4.705-19.288-4.865-22.489-.475-9.49 1.97-16.205 7.265-19.957 10.476-7.423 28.1-.354 28.845-.05a1.285 1.285 0 01-.972 2.379v.001c-.17-.07-17.07-6.84-26.392-.229-4.528 3.211-6.607 9.175-6.18 17.729.135 2.696 1.84 12.311 4.757 21.867 3.378 11.067 7.223 18.052 10.548 19.16.521.175 2.109.704 4.381-2.026 4.272-5.14 8.197-9.242 8.236-9.283a1.286 1.286 0 011.856 1.778c-.039.04-3.904 4.081-8.116 9.148-1.995 2.398-3.908 3.102-5.466 3.102zm55.92-10.829a1.284 1.284 0 01-1.065-2.004c11.971-17.764 16.173-39.227 12.574-43.825-4.53-5.788-10.927-8.812-19.012-8.985-5.987-.13-10.746 1.399-11.523 1.666l-.195.079c-.782.246-1.382-.1
Line truncated
|
||||
'logo' => '<svg class="w-[4.5rem] aspect-square h-[4.5rem] p-2 transition-all duration-200 dark:opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-white fill-black bg-black/10 dark:bg-white/10" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path fill="#currentColor" d="M85.988 76.075c.632-5.262.443-6.034 4.362-5.182l.995.088c3.014.137 6.957-.485 9.272-1.561 4.986-2.313 7.942-6.177 3.026-5.162-11.215 2.313-11.986-1.483-11.986-1.483C103.5 45.204 108.451 22.9 104.178 17.44 92.524 2.548 72.35 9.59 72.012 9.773l-.108.021c-2.216-.461-4.695-.735-7.481-.78-5.075-.083-8.926 1.331-11.847 3.546 0 0-35.989-14.827-34.315 18.646.356 7.121 10.207 53.882 21.956 39.758 4.294-5.164 8.444-9.531 8.444-9.531 2.061 1.369 4.528 2.067 7.116 1.816l.2-.17c-.062.641-.035 1.268.081 2.01-3.027 3.383-2.137 3.977-8.189 5.222-6.122 1.262-2.525 3.508-.178 4.095 2.848.713 9.433 1.722 13.884-4.509l-.177.711c1.188.95 1.107 6.827 1.275 11.026.168 4.199.45 8.117 1.306 10.429.856 2.31 1.866 8.261 9.819 6.557 6.646-1.426 11.727-3.476 12.19-22.545"/><path fill="#currentColor" d="M71.208 102.77c-3.518 0-5.808-1.36-7.2-2.674-2.1-1.981-2.933-4.534-3.43-6.059l-.215-.637c-1.002-2.705-1.341-6.599-1.542-11.613a199.25 199.25 0 01-.075-2.352c-.017-.601-.038-1.355-.068-2.146a15.157 15.157 0 01-3.997 1.264c-2.48.424-5.146.286-7.926-.409-1.961-.49-3.999-1.506-5.16-3.076-3.385 2.965-6.614 2.562-8.373 1.976-3.103-1.035-5.88-3.942-8.491-8.89-1.859-3.523-3.658-8.115-5.347-13.646-2.94-9.633-4.808-19.779-4.974-23.109-.522-10.427 2.284-17.883 8.34-22.16 9.555-6.749 24.03-2.781 29.307-.979 3.545-2.137 7.716-3.178 12.43-3.102 2.532.041 4.942.264 7.181.662 2.335-.734 6.949-1.788 12.23-1.723 9.73.116 17.793 3.908 23.316 10.966 3.941 5.036 1.993 15.61.48 21.466-2.127 8.235-5.856 16.996-10.436 24.622 1.244.009 3.045-.141 5.607-.669 5.054-1.044 6.531 1.666 6.932 2.879 1.607 4.867-5.378 8.544-7.557 9.555-2.792 1.297-7.343 2.086-11.071 1.915l-.163-.011-.979-.086-.097.816-.093.799c-.25 9.664-1.631 15.784-4.472 19.829-2.977 4.239-7.116 5.428-10.761 6.209a16.146 16.146 0 01-3.396.383zm-7.402-35.174c2.271 1.817 2.47 5.236 2.647 11.626.022.797.043 1.552.071 2.257.086 2.134.287 7.132 1.069 9.244.111.298.21.602.314.922.872 2.672 1.31 4.011 5.081 3.203 3.167-.678 4.794-1.287 6.068-3.101 1.852-2.638 2.888-7.941 3.078-15.767l3.852.094-3.826-.459.112-.955c.367-3.148.631-5.424 2.736-6.928 1.688-1.207 3.613-1.09 5.146-.814-1.684-1.271-2.15-2.765-2.274-3.377l-.321-1.582.902-1.34c5.2-7.716 9.489-17.199 11.767-26.018 2.34-9.062 1.626-13.875.913-14.785-9.446-12.071-25.829-7.088-27.539-6.521l-.29.156-1.45.271-.743-.154c-2.047-.425-4.321-.66-6.76-.7-3.831-.064-6.921.841-9.455 2.764l-1.758 1.333-2.041-.841c-4.358-1.782-17.162-5.365-23.918-.58-3.75 2.656-5.458 7.861-5.078 15.47.125 2.512 1.833 12.021 4.647 21.245 3.891 12.746 7.427 16.979 8.903 17.472.257.087.926-.433 1.591-1.231 4.326-5.203 8.44-9.54 8.613-9.723l2.231-2.347 2.697 1.792c1.087.723 2.286 1.132 3.518 1.209l6.433-5.486-.932 9.51c-.021.214-.031.504.053 1.044l.28 1.803-1.213 1.358-.14.157 3.534 1.632 1.482-1.853z"/><path fill="#336791" d="M103.646 64.258c-11.216 2.313-11.987-1.484-11.987-1.484 11.842-17.571 16.792-39.876 12.52-45.335C92.524 2.547 72.35 9.59 72.013 9.773l-.109.019c-2.216-.459-4.695-.733-7.482-.778-5.075-.083-8.925 1.33-11.846 3.545 0 0-35.99-14.826-34.316 18.647.356 7.121 10.207 53.882 21.956 39.758 4.294-5.164 8.443-9.531 8.443-9.531 2.061 1.369 4.528 2.067 7.115 1.816l.201-.17c-.062.641-.034 1.268.08 2.01-3.026 3.383-2.138 3.977-8.188 5.222-6.123 1.262-2.526 3.508-.177 4.095 2.847.713 9.433 1.722 13.883-4.509l-.178.711c1.186.95 2.019 6.179 1.879 10.919s-.233 7.994.702 10.536c.935 2.541 1.866 8.261 9.82 6.557 6.646-1.425 10.09-5.116 10.57-11.272.34-4.377 1.109-3.73 1.158-7.644l.618-1.853c.711-5.934.113-7.848 4.208-6.957l.995.087c3.014.138 6.958-.485 9.273-1.561 4.986-2.314 7.943-6.177 3.028-5.162z"/><path fill="#fff" d="M71.61 100.394c-6.631.001-8.731-5.25-9.591-7.397-1.257-3.146-1.529-15.358-1.249-25.373a1.286 1.286 0 012.57.072c-.323 11.551.136 22.018 1.066 24.346 1.453 3.632 3.656 6.809 9.887 5.475 5.915-1.269 8.13-3.512 9.116-9.23.758-4.389 2.254-16.874 2.438-19.338a1.285 1.285 0 012.563.191c-.192 2.564-1.682 15.026-2.469 19.584-1.165 6.755-4.176 9.819-11.11 11.306a15.462 15.462 0 01-3.221.364zM35.659 74.749a5.343 5.343 0 01-1.704-.281c-4.307-1.437-8.409-8.451-12.193-20.849-2.88-9.438-4.705-19.288-4.865-22.489-.475-9.49 1.97-16.205 7.265-19.957 10.476-7.423 28.1-.354 28.845-.05a1.285 1.285 0 01-.972 2.379v.001c-.17-.07-17.07-6.84-26.392-.229-4.528 3.211-6.607 9.175-6.18 17.729.135 2.696 1.84 12.311 4.757 21.867 3.378 11.067 7.223 18.052 10.548 19.16.521.175 2.109.704 4.381-2.026 4.272-5.14 8.197-9.242 8.236-9.283a1.286 1.286 0 011.856 1.778c-.039.04-3.904 4.081-8.116 9.148-1.995 2.398-3.908 3.102-5.466 3.102zm55.92-10.829a1.284 1.284 0 01-1.065-2.004c11.971-17.764 16.173-39.227 12.574-43.825-4.53-5.788-10.927-8.812-19.012-8.985-5.987-.13-10.746 1.399-11.523
Line truncated
|
||||
',
|
||||
],
|
||||
[
|
||||
'id' => 'mysql',
|
||||
'name' => 'MySQL',
|
||||
'description' => 'MySQL is an open-source relational database management system. ',
|
||||
'logo' => '<svg class="w-[4.5rem] aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-white fill-black" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
|
||||
'logo' => '<svg class="w-[4.5rem] aspect-square h-[4.5rem] p-2 transition-all duration-200 dark:opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-white fill-black bg-black/10 dark:bg-white/10" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
|
||||
<path fill="currentColor" d="M0 91.313h4.242V74.566l6.566 14.598c.773 1.77 1.832 2.391 3.914 2.391s3.098-.621 3.871-2.391l6.566-14.598v16.746h4.242V74.594c0-1.633-.652-2.422-2-2.828-3.223-1.004-5.383-.137-6.363 2.039l-6.441 14.41-6.238-14.41c-.937-2.176-3.14-3.043-6.359-2.039-1.348.406-2 1.195-2 2.828zM32.93 77.68h4.238v9.227c-.039.5.16 1.676 2.484 1.715h9.223V77.633h4.25c.02 0-.008 14.984-.008 15.047.023 3.695-4.582 4.496-6.707 4.559H33.02v-2.852l13.414-.004c2.73-.285 2.406-1.645 2.406-2.098v-1.113h-9.012c-4.195-.039-6.863-1.871-6.898-3.977-.004-.191.09-9.422 0-9.516zm0 0" />
|
||||
<path fill="currentColor" d="M56.391 91.313h12.195c1.426 0 2.813-.301 3.914-.816 1.836-.84 2.73-1.984 2.73-3.48v-3.098c0-1.223-1.016-2.367-3.016-3.125-1.059-.41-2.367-.625-3.629-.625h-5.141c-1.711 0-2.527-.516-2.73-1.656-.039-.137-.039-.246-.039-.383V76.2c0-.109 0-.219.039-.355.203-.867.652-1.113 2.16-1.25l.41-.027h12.109v-2.824H63.488c-1.711 0-2.609.109-3.426.352-2.527.789-3.629 2.039-3.629 4.215v2.473c0 1.902 2.16 3.535 5.789 3.914l1.223.055h4.406c.164 0 .324 0 .449.027 1.344.109 1.914.355 2.324.844.211.195.332.473.324.758v2.477c0 .297-.203.68-.609 1.004-.367.328-.98.543-1.793.598l-.449.027H56.391zm45.297-4.922c0 2.91 2.164 4.539 6.523 4.867l1.227.055h11.051v-2.828h-11.133c-2.488 0-3.426-.625-3.426-2.121V71.738h-4.238V86.39zm-23.75.148V76.457c0-2.559 1.801-4.113 5.355-4.602a7.976 7.976 0 0 1 1.145-.082h8.047c.41 0 .777.027 1.188.082 3.555.488 5.352 2.043 5.352 4.602v10.082c0 2.078-.762 3.188-2.523 3.914l4.18 3.77h-4.926l-3.379-3.051-3.402.215H84.44a9.23 9.23 0 0 1-2.492-.352c-2.699-.734-4.008-2.152-4.008-4.496zm4.578-.246c0 .137.039.273.082.438.246 1.172 1.348 1.824 3.023 1.824h3.852l-3.539-3.195h4.926l3.086 2.789c.57-.305.941-.766 1.074-1.363.039-.137.039-.273.039-.41v-9.668c0-.109 0-.246-.039-.383-.246-1.09-1.348-1.715-2.984-1.715h-6.414c-1.879 0-3.105.816-3.105 2.098zm0 0" />
|
||||
<path fill="#00618A" d="M124.219 67.047c-2.605-.07-4.598.172-6.301.891-.484.203-1.258.207-1.336.813.266.281.309.699.52 1.039.406.66 1.094 1.539 1.707 2l2.074 1.484c1.273.777 2.699 1.223 3.93 2 .723.461 1.441 1.039 2.148 1.559.348.254.582.656 1.039.816v-.074c-.238-.305-.301-.723-.52-1.039l-.965-.965c-.941-1.25-2.137-2.348-3.41-3.262-1.016-.727-3.281-1.711-3.707-2.891l-.074-.074c.719-.078 1.563-.34 2.223-.516 1.117-.301 2.113-.223 3.262-.52l1.559-.449v-.293c-.582-.598-.996-1.387-1.633-1.93-1.656-1.41-3.469-2.824-5.336-4.004-1.035-.652-2.312-1.074-3.41-1.629-.367-.187-1.016-.281-1.262-.594-.574-.734-.887-1.664-1.332-2.52l-2.668-5.633c-.562-1.285-.93-2.555-1.633-3.707-3.363-5.535-6.988-8.875-12.602-12.156-1.191-.699-2.633-.973-4.148-1.332l-2.449-.148c-.496-.211-1.012-.82-1.48-1.113-1.859-1.176-6.629-3.73-8.008-.371-.867 2.121 1.301 4.191 2.078 5.266.543.754 1.242 1.598 1.629 2.445.258.555.301 1.113.52 1.703.539 1.453 1.008 3.031 1.707 4.375.352.68.738 1.395 1.184 2 .273.371.742.539.816 1.113-.457.641-.484 1.633-.742 2.445-1.16 3.652-.723 8.191.965 10.898.516.828 1.734 2.609 3.41 1.926 1.465-.598 1.137-2.445 1.555-4.078.098-.367.039-.641.223-.887v.074l1.336 2.668c.988 1.59 2.738 3.25 4.223 4.371.773.582 1.379 1.59 2.375 1.93V68.6h-.074c-.195-.297-.496-.422-.742-.664-.582-.57-1.227-1.277-1.703-1.93-1.352-1.832-2.547-3.84-3.633-5.93-.52-.996-.973-2.098-1.41-3.113-.168-.391-.164-.984-.516-1.184-.48.742-1.187 1.344-1.559 2.223-.594 1.402-.668 3.117-.891 4.891l-.148.074c-1.031-.25-1.395-1.312-1.777-2.223-.973-2.305-1.152-6.02-.297-8.672.219-.687 1.219-2.852.813-3.484-.191-.633-.828-1-1.184-1.484a11.7 11.7 0 0 1-1.187-2.074c-.793-1.801-1.164-3.816-2-5.633-.398-.871-1.074-1.75-1.629-2.523-.617-.855-1.305-1.484-1.781-2.52-.168-.367-.398-.957-.148-1.336.078-.254.195-.359.445-.441.43-.332 1.629.109 2.074.293 1.191.496 2.184.965 3.191 1.633.48.32.969.941 1.555 1.113h.668c1.043.238 2.211.07 3.188.367 1.723.523 3.27 1.34 4.668 2.227 4.273 2.695 7.766 6.535 10.156 11.117.387.738.551 1.441.891 2.223.684 1.578 1.543 3.203 2.223 4.746s1.34 3.094 2.297 4.375c.504.672 2.453 1.031 3.336 1.406.621.262 1.637.535 2.223.891 1.125.676 2.211 1.48 3.266 2.223.523.375 2.141 1.188 2.223 1.855zM91.082 38.805a5.26 5.26 0 0 0-1.332.148v.074h.074c.258.535.715.879 1.035 1.336l.742 1.555.074-.07c.461-.324.668-.844.668-1.633-.187-.195-.211-.437-.371-.668-.211-.309-.621-.48-.891-.742zm0 0" />
|
||||
@@ -162,37 +166,37 @@ class Select extends Component
|
||||
'id' => 'mariadb',
|
||||
'name' => 'MariaDB',
|
||||
'description' => 'MariaDB is a community-developed, commercially supported fork of the MySQL relational database management system, intended to remain free and open-source.',
|
||||
'logo' => '<svg class="w-[4.5rem] aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-white fill-black" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path fill="currentColor" d="M127.434 12.182a1.727 1.727 0 0 0-1.174-.392c-1.168 0-2.68.793-3.495 1.218l-.322.165a11.095 11.095 0 0 1-4.365 1.1c-1.554.049-2.892.14-4.635.322-10.327 1.06-14.933 8.975-19.37 16.63-2.416 4.164-4.91 8.489-8.33 11.793a22.472 22.472 0 0 1-2.252 1.913c-3.54 2.631-7.985 4.51-11.443 5.84-3.329 1.273-6.964 2.417-10.474 3.524-3.219 1.012-6.255 1.97-9.048 3.008a96.902 96.902 0 0 1-3.275 1.14c-2.545.825-4.378 1.458-7.06 3.304a45.386 45.386 0 0 0-2.804 2.066 29.585 29.585 0 0 0-5.597 5.894 34.802 34.802 0 0 1-4.701 5.642c-.566.554-1.57.826-3.074.826-1.76 0-3.895-.363-6.154-.747-2.33-.413-4.738-.805-6.803-.805-1.677 0-2.962.273-3.92.826 0 0-1.617.942-2.298 2.16l.67.302a13.718 13.718 0 0 1 2.859 2.065 14.342 14.342 0 0 0 2.973 2.115 2.553 2.553 0 0 1 .918.582c-.281.413-.694.946-1.129 1.516-2.384 3.119-3.774 5.09-2.977 6.163a2.507 2.507 0 0 0 1.239.28c5.196 0 7.989-1.35 11.52-3.06 1.024-.495 2.066-1.004 3.305-1.528 2.065-.896 4.288-2.325 6.647-3.838 3.084-2.01 6.31-4.076 9.442-5.072a25.734 25.734 0 0 1 7.943-1.115c3.305 0 6.783.441 10.138.872 2.499.322 5.089.652 7.63.805.986.057 1.9.086 2.787.086a32.307 32.307 0 0 0 3.557-.185l.284-.1c1.781-1.094 2.617-3.444 3.425-5.717.52-1.462.96-2.775 1.652-3.61a1.054 1.054 0 0 1 .132-.11.166.166 0 0 1 .202.032v.066c-.412 8.885-3.99 14.527-7.608 19.543l-2.416 2.59s3.383 0 5.307-.744c7.024-2.099 12.324-6.725 16.181-14.103a60.185 60.185 0 0 0 2.549-5.82c.065-.165.673-.47.616.384-.021.252-.038.533-.059.827 0 .173 0 .35-.033.528-.1 1.24-.392 3.859-.392 3.859l2.169-1.162c5.229-3.304 9.26-9.97 12.318-20.343 1.272-4.321 2.205-8.613 3.027-12.392.983-4.545 1.83-8.44 2.801-9.952 1.524-2.37 3.85-3.973 6.101-5.53.306-.211.616-.414.917-.637 2.83-1.986 5.643-4.279 6.263-8.555v-.095c.45-3.189.07-4.002-.364-4.373zm-7.283 103.727h-10.327V97.92h9.315c3.56 0 6.952.67 6.902 4.66 0 2.813-1.747 3.59-3.589 3.886 2.615.224 4.188 1.892 4.188 4.586.017 4.035-3.523 4.858-6.489 4.858zm-.772-10.14c3.565 0 4.362-1.372 4.362-3.115 0-2.619-1.595-3.214-4.362-3.214h-7.402v6.328zm.099 1.52h-7.501v7.1h7.823c2.194 0 4.511-.723 4.511-3.486 0-3.19-2.665-3.615-4.833-3.615zm-31.497-9.37h8.125c6.828 0 10.24 3.764 10.19 8.994.05 5.436-3.716 8.997-9.591 8.997H87.98zm2.242 1.596v14.825h6.197c5.432 0 7.501-3.665 7.501-7.477 0-4.309-2.59-7.348-7.5-7.348zm-10.838 5.357v-2.095h3.404v13.132h-3.392v-2.114c-.896 1.52-2.739 2.391-4.982 2.391-4.684 0-7.303-3.305-7.303-7.105 0-3.664 2.479-6.609 6.804-6.609 2.454.029 4.498.855 5.469 2.4zm-8.675 4.387c0 2.416 1.52 4.485 4.462 4.485 2.841 0 4.386-2.02 4.386-4.411 0-2.392-1.599-4.436-4.544-4.436-2.828 0-4.3 2.04-4.3 4.362zm-10.013-9.947a1.722 1.722 0 0 1 1.818-1.768 1.788 1.788 0 0 1 1.847 1.821 1.714 1.714 0 0 1-1.847 1.744 1.743 1.743 0 0 1-1.818-1.797zm.15 3.465h3.39v9.596c0 .595.125 1.02.62 1.02a3.657 3.657 0 0 0 .648-.073l.525 2.478a5.931 5.931 0 0 1-2.242.414c-1.421 0-2.942-.414-2.942-3.64zM52.15 115.91h-3.386v-13.132h3.386v2.942a5.197 5.197 0 0 1 4.735-3.218 6.13 6.13 0 0 1 2.119.347l-.723 2.479a7.435 7.435 0 0 0-1.793-.249c-2.445 0-4.338 1.843-4.338 4.545zm-11.037-11.037v-2.095h3.392v13.132h-3.392v-2.114c-.896 1.52-2.738 2.391-4.982 2.391-4.688 0-7.303-3.305-7.303-7.105 0-3.664 2.479-6.609 6.804-6.609 2.466.029 4.51.855 5.481 2.4zm-8.675 4.387c0 2.416 1.52 4.485 4.462 4.485 2.838 0 4.383-2.02 4.383-4.411 0-2.391-1.595-4.436-4.544-4.436-2.826 0-4.296 2.04-4.296 4.362zm-9.24-11.34 4.651 17.99h-3.51L21.24 102.95 15.4 115.91h-2.965l-5.808-12.883-3.19 12.883H0l4.61-17.99h3.04l6.28 13.93 6.253-13.93z"/></svg>',
|
||||
'logo' => '<svg class="w-[4.5rem] aspect-square h-[4.5rem] p-2 transition-all duration-200 dark:opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-white fill-black bg-black/10 dark:bg-white/10" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path fill="currentColor" d="M127.434 12.182a1.727 1.727 0 0 0-1.174-.392c-1.168 0-2.68.793-3.495 1.218l-.322.165a11.095 11.095 0 0 1-4.365 1.1c-1.554.049-2.892.14-4.635.322-10.327 1.06-14.933 8.975-19.37 16.63-2.416 4.164-4.91 8.489-8.33 11.793a22.472 22.472 0 0 1-2.252 1.913c-3.54 2.631-7.985 4.51-11.443 5.84-3.329 1.273-6.964 2.417-10.474 3.524-3.219 1.012-6.255 1.97-9.048 3.008a96.902 96.902 0 0 1-3.275 1.14c-2.545.825-4.378 1.458-7.06 3.304a45.386 45.386 0 0 0-2.804 2.066 29.585 29.585 0 0 0-5.597 5.894 34.802 34.802 0 0 1-4.701 5.642c-.566.554-1.57.826-3.074.826-1.76 0-3.895-.363-6.154-.747-2.33-.413-4.738-.805-6.803-.805-1.677 0-2.962.273-3.92.826 0 0-1.617.942-2.298 2.16l.67.302a13.718 13.718 0 0 1 2.859 2.065 14.342 14.342 0 0 0 2.973 2.115 2.553 2.553 0 0 1 .918.582c-.281.413-.694.946-1.129 1.516-2.384 3.119-3.774 5.09-2.977 6.163a2.507 2.507 0 0 0 1.239.28c5.196 0 7.989-1.35 11.52-3.06 1.024-.495 2.066-1.004 3.305-1.528 2.065-.896 4.288-2.325 6.647-3.838 3.084-2.01 6.31-4.076 9.442-5.072a25.734 25.734 0 0 1 7.943-1.115c3.305 0 6.783.441 10.138.872 2.499.322 5.089.652 7.63.805.986.057 1.9.086 2.787.086a32.307 32.307 0 0 0 3.557-.185l.284-.1c1.781-1.094 2.617-3.444 3.425-5.717.52-1.462.96-2.775 1.652-3.61a1.054 1.054 0 0 1 .132-.11.166.166 0 0 1 .202.032v.066c-.412 8.885-3.99 14.527-7.608 19.543l-2.416 2.59s3.383 0 5.307-.744c7.024-2.099 12.324-6.725 16.181-14.103a60.185 60.185 0 0 0 2.549-5.82c.065-.165.673-.47.616.384-.021.252-.038.533-.059.827 0 .173 0 .35-.033.528-.1 1.24-.392 3.859-.392 3.859l2.169-1.162c5.229-3.304 9.26-9.97 12.318-20.343 1.272-4.321 2.205-8.613 3.027-12.392.983-4.545 1.83-8.44 2.801-9.952 1.524-2.37 3.85-3.973 6.101-5.53.306-.211.616-.414.917-.637 2.83-1.986 5.643-4.279 6.263-8.555v-.095c.45-3.189.07-4.002-.364-4.373zm-7.283 103.727h-10.327V97.92h9.315c3.56 0 6.952.67 6.902 4.66 0 2.813-1.747 3.59-3.589 3.886 2.615.224 4.188 1.892 4.188 4.586.017 4.035-3.523 4.858-6.489 4.858zm-.772-10.14c3.565 0 4.362-1.372 4.362-3.115 0-2.619-1.595-3.214-4.362-3.214h-7.402v6.328zm.099 1.52h-7.501v7.1h7.823c2.194 0 4.511-.723 4.511-3.486 0-3.19-2.665-3.615-4.833-3.615zm-31.497-9.37h8.125c6.828 0 10.24 3.764 10.19 8.994.05 5.436-3.716 8.997-9.591 8.997H87.98zm2.242 1.596v14.825h6.197c5.432 0 7.501-3.665 7.501-7.477 0-4.309-2.59-7.348-7.5-7.348zm-10.838 5.357v-2.095h3.404v13.132h-3.392v-2.114c-.896 1.52-2.739 2.391-4.982 2.391-4.684 0-7.303-3.305-7.303-7.105 0-3.664 2.479-6.609 6.804-6.609 2.454.029 4.498.855 5.469 2.4zm-8.675 4.387c0 2.416 1.52 4.485 4.462 4.485 2.841 0 4.386-2.02 4.386-4.411 0-2.392-1.599-4.436-4.544-4.436-2.828 0-4.3 2.04-4.3 4.362zm-10.013-9.947a1.722 1.722 0 0 1 1.818-1.768 1.788 1.788 0 0 1 1.847 1.821 1.714 1.714 0 0 1-1.847 1.744 1.743 1.743 0 0 1-1.818-1.797zm.15 3.465h3.39v9.596c0 .595.125 1.02.62 1.02a3.657 3.657 0 0 0 .648-.073l.525 2.478a5.931 5.931 0 0 1-2.242.414c-1.421 0-2.942-.414-2.942-3.64zM52.15 115.91h-3.386v-13.132h3.386v2.942a5.197 5.197 0 0 1 4.735-3.218 6.13 6.13 0 0 1 2.119.347l-.723 2.479a7.435 7.435 0 0 0-1.793-.249c-2.445 0-4.338 1.843-4.338 4.545zm-11.037-11.037v-2.095h3.392v13.132h-3.392v-2.114c-.896 1.52-2.738 2.391-4.982 2.391-4.688 0-7.303-3.305-7.303-7.105 0-3.664 2.479-6.609 6.804-6.609 2.466.029 4.51.855 5.481 2.4zm-8.675 4.387c0 2.416 1.52 4.485 4.462 4.485 2.838 0 4.383-2.02 4.383-4.411 0-2.391-1.595-4.436-4.544-4.436-2.826 0-4.296 2.04-4.296 4.362zm-9.24-11.34 4.651 17.99h-3.51L21.24 102.95 15.4 115.91h-2.965l-5.808-12.883-3.19 12.883H0l4.61-17.99h3.04l6.28 13.93 6.253-13.93z"/></svg>',
|
||||
],
|
||||
[
|
||||
'id' => 'redis',
|
||||
'name' => 'Redis',
|
||||
'description' => 'Redis is a source-available, in-memory storage, used as a distributed, in-memory key–value database, cache and message broker, with optional durability.',
|
||||
'logo' => '<svg class="w-[4.5rem] aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-white fill-black" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path d="M21.4 97.6c0 1.8-1.5 3.5-3.5 3.5-1.5 0-2.8.4-4 1.3-1.3.8-2.2 1.9-3 3.2-1.6 2.1-2.4 4.6-2.7 5.5v12.5c0 1.9-1.6 3.5-3.6 3.5-1.9 0-3.5-1.6-3.5-3.5v-26c0-1.9 1.6-3.4 3.5-3.4 2 0 3.6 1.5 3.6 3.4v.5c.4-.5.9-1 1.4-1.3 2.2-1.4 5-2.6 8.3-2.6 2 0 3.5 1.5 3.5 3.4zm-1.9 13c.1-9 7-16.5 16.1-16.5 8.6 0 15.3 6.4 15.9 15.3v.3c0 .1 0 .5-.1.6-.2 1.6-1.6 2.6-3.4 2.6H27c.3 1.5 1.1 3.2 2.2 4.3 1.4 1.6 4 2.8 6.3 3 2.4.2 5.2-.4 6.8-1.6 1.4-1.4 4.1-1.3 4.9-.2.9.9 1.5 2.9 0 4.3-3.2 3-7.1 4.3-11.8 4.3-8.9 0-15.9-7.5-15.9-16.4zm7.1-3.2h18.6c-.7-2.6-4-6.5-9.7-7-5.6.2-8.3 4.3-8.9 7zm58.3 16.1c0 1.9-1.6 3.6-3.6 3.6-1.8 0-3.2-1.3-3.5-2.8-2.5 1.7-5.7 2.8-9 2.8-8.9 0-16-7.5-16-16.4 0-9 7.1-16.5 16-16.5 3.2 0 6.4 1.1 8.8 2.8V84.5c0-1.9 1.6-3.6 3.6-3.6s3.6 1.6 3.6 3.6v26.2l.1 12.8zm-16-22.2c-2.4 0-4.5 1-6.2 2.7-1.6 1.6-2.6 4-2.6 6.6 0 2.5 1 4.9 2.6 6.5 1.6 1.7 3.8 2.7 6.2 2.7 2.4 0 4.5-1 6.2-2.7 1.6-1.6 2.6-4 2.6-6.5 0-2.6-1-5-2.6-6.6-1.6-1.7-3.7-2.7-6.2-2.7zm28.6-15.4c0 2-1.5 3.6-3.6 3.6-2 0-3.6-1.6-3.6-3.6v-1.4c0-2 1.6-3.6 3.6-3.6s3.6 1.6 3.6 3.6v1.4zm0 11.9v25.7c0 2-1.5 3.6-3.6 3.6-2 0-3.6-1.6-3.6-3.6V97.8c0-2.1 1.6-3.6 3.6-3.6 2.1 0 3.6 1.5 3.6 3.6zm4.5 19.8c1.2-1.6 3.5-1.8 4.9-.5 1.7 1.4 4.7 3 7.2 2.9 1.8 0 3.4-.6 4.5-1.3.9-.8 1.2-1.4 1.2-2 0-.3-.1-.5-.2-.7-.1-.2-.3-.5-.9-.8-.9-.7-2.9-1.4-5.3-1.8h-.1c-2-.4-4-.9-5.7-1.7-1.8-.9-3.4-2-4.5-3.8-.7-1.2-1.1-2.6-1.1-4.1 0-3 1.7-5.6 3.9-7.2 2.3-1.6 5.1-2.4 8.1-2.4 4.5 0 7.8 2.2 9.9 3.6 1.6 1.1 2 3.2 1.1 4.9-1.1 1.6-3.2 2-4.9.9-2.1-1.4-4-2.4-6.1-2.4-1.6 0-3.1.5-4 1.2-.9.6-1.1 1.2-1.1 1.5 0 .3 0 .3.1.5.1.1.3.4.7.7.9.6 2.6 1.2 4.8 1.6l.1.1h.1c2.2.4 4.2 1 6.1 1.9 1.8.8 3.6 2 4.7 3.9.8 1.3 1.3 2.8 1.3 4.3 0 3.2-1.8 5.9-4.1 7.6-2.4 1.6-5.3 2.6-8.6 2.6-5.1-.1-9.1-2.4-11.7-4.5-1.4-1.3-1.6-3.5-.4-5z" fill="#currentColor"/><path fill="#A41E11" d="M106.9 62.7c-5 2.6-30.7 13.2-36.2 16-5.5 2.9-8.5 2.8-12.8.8-4.4-2.1-31.7-13.1-36.7-15.5-2.5-1.2-3.8-2.2-3.8-3.1v-9.4s35.6-7.8 41.4-9.8c5.8-2.1 7.8-2.1 12.6-.3 4.9 1.8 34.2 7.1 39 8.8v9.3c.1.9-1 1.9-3.5 3.2z"/><path fill="#D82C20" d="M106.9 53.3c-5 2.6-30.7 13.2-36.2 16-5.5 2.9-8.5 2.8-12.8.8C53.5 68 26.2 57 21.2 54.6c-4.9-2.4-5-4-.2-5.9 4.8-1.9 32.1-12.6 37.8-14.6 5.8-2.1 7.8-2.1 12.6-.3 4.9 1.8 30.5 12 35.3 13.7 5 1.8 5.2 3.2.2 5.8z"/><path fill="#A41E11" d="M106.9 47.4c-5 2.6-30.7 13.2-36.2 16-5.5 2.9-8.5 2.8-12.8.8-4.4-2.1-31.7-13.2-36.7-15.5-2.5-1.2-3.8-2.2-3.8-3.1v-9.4s35.6-7.8 41.4-9.8c5.8-2.1 7.8-2.1 12.6-.3 4.9 1.8 34.2 7.1 39 8.8v9.3c.1.9-1 1.9-3.5 3.2z"/><path fill="#D82C20" d="M106.9 38c-5 2.6-30.7 13.2-36.2 16-5.5 2.9-8.5 2.8-12.8.8-4.3-2.1-31.7-13.1-36.6-15.5-4.9-2.4-5-4-.2-5.9 4.8-1.9 32.1-12.6 37.8-14.6 5.8-2.1 7.8-2.1 12.6-.3 4.9 1.8 30.5 12 35.3 13.7 4.9 1.7 5.1 3.2.1 5.8z"/><path fill="#A41E11" d="M106.9 31.5c-5 2.6-30.7 13.2-36.2 16-5.5 2.9-8.5 2.8-12.8.8-4.3-2.1-31.7-13.1-36.6-15.5-2.5-1.2-3.8-2.2-3.8-3.1v-9.4s35.6-7.8 41.4-9.8c5.8-2.1 7.8-2.1 12.6-.3 4.9 1.8 34.2 7.1 39 8.8v9.3c0 .8-1.1 1.9-3.6 3.2z"/><path fill="#D82C20" d="M106.9 22.1c-5 2.6-30.7 13.2-36.2 16-5.5 2.9-8.5 2.8-12.8.8-4.3-2.1-31.7-13.1-36.6-15.5s-5-4-.2-5.9c4.8-1.9 32.1-12.6 37.8-14.6C64.7.8 66.7.8 71.5 2.6c4.9 1.8 30.5 12 35.3 13.7 4.9 1.7 5.1 3.2.1 5.8z"/><path fill="#fff" d="M76.2 13l-8.1.8-1.8 4.4-2.9-4.9-9.3-.8L61 10l-2-3.8 6.5 2.5 6.1-2-1.7 4zM65.8 34.1l-15-6.3 21.6-3.3z"/><ellipse fill="#fff" cx="45" cy="19.9" rx="11.5" ry="4.5"/><path fill="#7A0C00" d="M85.7 14.2l12.8 5-12.8 5.1z"/><path fill="#AD2115" d="M71.6 19.8l14.1-5.6v10.1l-1.3.5z"/></svg>',
|
||||
'logo' => '<svg class="w-[4.5rem] aspect-square h-[4.5rem] p-2 transition-all duration-200 dark:opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-white fill-black bg-black/10 dark:bg-white/10" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path d="M21.4 97.6c0 1.8-1.5 3.5-3.5 3.5-1.5 0-2.8.4-4 1.3-1.3.8-2.2 1.9-3 3.2-1.6 2.1-2.4 4.6-2.7 5.5v12.5c0 1.9-1.6 3.5-3.6 3.5-1.9 0-3.5-1.6-3.5-3.5v-26c0-1.9 1.6-3.4 3.5-3.4 2 0 3.6 1.5 3.6 3.4v.5c.4-.5.9-1 1.4-1.3 2.2-1.4 5-2.6 8.3-2.6 2 0 3.5 1.5 3.5 3.4zm-1.9 13c.1-9 7-16.5 16.1-16.5 8.6 0 15.3 6.4 15.9 15.3v.3c0 .1 0 .5-.1.6-.2 1.6-1.6 2.6-3.4 2.6H27c.3 1.5 1.1 3.2 2.2 4.3 1.4 1.6 4 2.8 6.3 3 2.4.2 5.2-.4 6.8-1.6 1.4-1.4 4.1-1.3 4.9-.2.9.9 1.5 2.9 0 4.3-3.2 3-7.1 4.3-11.8 4.3-8.9 0-15.9-7.5-15.9-16.4zm7.1-3.2h18.6c-.7-2.6-4-6.5-9.7-7-5.6.2-8.3 4.3-8.9 7zm58.3 16.1c0 1.9-1.6 3.6-3.6 3.6-1.8 0-3.2-1.3-3.5-2.8-2.5 1.7-5.7 2.8-9 2.8-8.9 0-16-7.5-16-16.4 0-9 7.1-16.5 16-16.5 3.2 0 6.4 1.1 8.8 2.8V84.5c0-1.9 1.6-3.6 3.6-3.6s3.6 1.6 3.6 3.6v26.2l.1 12.8zm-16-22.2c-2.4 0-4.5 1-6.2 2.7-1.6 1.6-2.6 4-2.6 6.6 0 2.5 1 4.9 2.6 6.5 1.6 1.7 3.8 2.7 6.2 2.7 2.4 0 4.5-1 6.2-2.7 1.6-1.6 2.6-4 2.6-6.5 0-2.6-1-5-2.6-6.6-1.6-1.7-3.7-2.7-6.2-2.7zm28.6-15.4c0 2-1.5 3.6-3.6 3.6-2 0-3.6-1.6-3.6-3.6v-1.4c0-2 1.6-3.6 3.6-3.6s3.6 1.6 3.6 3.6v1.4zm0 11.9v25.7c0 2-1.5 3.6-3.6 3.6-2 0-3.6-1.6-3.6-3.6V97.8c0-2.1 1.6-3.6 3.6-3.6 2.1 0 3.6 1.5 3.6 3.6zm4.5 19.8c1.2-1.6 3.5-1.8 4.9-.5 1.7 1.4 4.7 3 7.2 2.9 1.8 0 3.4-.6 4.5-1.3.9-.8 1.2-1.4 1.2-2 0-.3-.1-.5-.2-.7-.1-.2-.3-.5-.9-.8-.9-.7-2.9-1.4-5.3-1.8h-.1c-2-.4-4-.9-5.7-1.7-1.8-.9-3.4-2-4.5-3.8-.7-1.2-1.1-2.6-1.1-4.1 0-3 1.7-5.6 3.9-7.2 2.3-1.6 5.1-2.4 8.1-2.4 4.5 0 7.8 2.2 9.9 3.6 1.6 1.1 2 3.2 1.1 4.9-1.1 1.6-3.2 2-4.9.9-2.1-1.4-4-2.4-6.1-2.4-1.6 0-3.1.5-4 1.2-.9.6-1.1 1.2-1.1 1.5 0 .3 0 .3.1.5.1.1.3.4.7.7.9.6 2.6 1.2 4.8 1.6l.1.1h.1c2.2.4 4.2 1 6.1 1.9 1.8.8 3.6 2 4.7 3.9.8 1.3 1.3 2.8 1.3 4.3 0 3.2-1.8 5.9-4.1 7.6-2.4 1.6-5.3 2.6-8.6 2.6-5.1-.1-9.1-2.4-11.7-4.5-1.4-1.3-1.6-3.5-.4-5z" fill="#currentColor"/><path fill="#A41E11" d="M106.9 62.7c-5 2.6-30.7 13.2-36.2 16-5.5 2.9-8.5 2.8-12.8.8-4.4-2.1-31.7-13.1-36.7-15.5-2.5-1.2-3.8-2.2-3.8-3.1v-9.4s35.6-7.8 41.4-9.8c5.8-2.1 7.8-2.1 12.6-.3 4.9 1.8 34.2 7.1 39 8.8v9.3c.1.9-1 1.9-3.5 3.2z"/><path fill="#D82C20" d="M106.9 53.3c-5 2.6-30.7 13.2-36.2 16-5.5 2.9-8.5 2.8-12.8.8C53.5 68 26.2 57 21.2 54.6c-4.9-2.4-5-4-.2-5.9 4.8-1.9 32.1-12.6 37.8-14.6 5.8-2.1 7.8-2.1 12.6-.3 4.9 1.8 30.5 12 35.3 13.7 5 1.8 5.2 3.2.2 5.8z"/><path fill="#A41E11" d="M106.9 47.4c-5 2.6-30.7 13.2-36.2 16-5.5 2.9-8.5 2.8-12.8.8-4.4-2.1-31.7-13.2-36.7-15.5-2.5-1.2-3.8-2.2-3.8-3.1v-9.4s35.6-7.8 41.4-9.8c5.8-2.1 7.8-2.1 12.6-.3 4.9 1.8 34.2 7.1 39 8.8v9.3c.1.9-1 1.9-3.5 3.2z"/><path fill="#D82C20" d="M106.9 38c-5 2.6-30.7 13.2-36.2 16-5.5 2.9-8.5 2.8-12.8.8-4.3-2.1-31.7-13.1-36.6-15.5-4.9-2.4-5-4-.2-5.9 4.8-1.9 32.1-12.6 37.8-14.6 5.8-2.1 7.8-2.1 12.6-.3 4.9 1.8 30.5 12 35.3 13.7 4.9 1.7 5.1 3.2.1 5.8z"/><path fill="#A41E11" d="M106.9 31.5c-5 2.6-30.7 13.2-36.2 16-5.5 2.9-8.5 2.8-12.8.8-4.3-2.1-31.7-13.1-36.6-15.5-2.5-1.2-3.8-2.2-3.8-3.1v-9.4s35.6-7.8 41.4-9.8c5.8-2.1 7.8-2.1 12.6-.3 4.9 1.8 34.2 7.1 39 8.8v9.3c0 .8-1.1 1.9-3.6 3.2z"/><path fill="#D82C20" d="M106.9 22.1c-5 2.6-30.7 13.2-36.2 16-5.5 2.9-8.5 2.8-12.8.8-4.3-2.1-31.7-13.1-36.6-15.5s-5-4-.2-5.9c4.8-1.9 32.1-12.6 37.8-14.6C64.7.8 66.7.8 71.5 2.6c4.9 1.8 30.5 12 35.3 13.7 4.9 1.7 5.1 3.2.1 5.8z"/><path fill="#fff" d="M76.2 13l-8.1.8-1.8 4.4-2.9-4.9-9.3-.8L61 10l-2-3.8 6.5 2.5 6.1-2-1.7 4zM65.8 34.1l-15-6.3 21.6-3.3z"/><ellipse fill="#fff" cx="45" cy="19.9" rx="11.5" ry="4.5"/><path fill="#7A0C00" d="M85.7 14.2l12.8 5-12.8 5.1z"/><path fill="#AD2115" d="M71.6 19.8l14.1-5.6v10.1l-1.3.5z"/></svg>',
|
||||
],
|
||||
[
|
||||
'id' => 'keydb',
|
||||
'name' => 'KeyDB',
|
||||
'description' => 'KeyDB is a database that offers high performance, low latency, and scalability for various data structures and workloads.',
|
||||
'logo' => ' <div class="w-[4.5rem] aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-white fill-black"><svg version="1.1" width="180" height="60" id="svg1326" viewBox="0 0 544 182" sodipodi:docname="keydb.svg" inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"><defs id="defs1330"/><sodipodi:namedview id="namedview1328" pagecolor="currentColor" bordercolor="#666666" borderopacity="1.0" inkscape:pageshadow="2" inkscape:pageopacity="0.00784314" inkscape:pagecheckerboard="true" showgrid="false" inkscape:zoom="1.5064209" inkscape:cx="374.06544" inkscape:cy="123.80338" inkscape:window-width="1536" inkscape:window-height="889" inkscape:window-x="-8" inkscape:window-y="-8" inkscape:window-maximized="1" inkscape:current-layer="svg1326" width="791px"/><path d="M 78.589334,169.85617 12.63903,131.77977 c -1.0281,-0.591 -1.6035,-1.6659 -1.6046,-2.7722 h -0.0134 V 52.833267 c 0,-1.2966 0.7688,-2.4135 1.8749,-2.9206 l 65.739904,-37.9545 c 1.0386,-0.5966 2.2717,-0.5456 3.2321,0.0264 l 65.951096,38.0761 c 1.0282,0.591 1.6042,1.6659 1.6047,2.7726 h 0.0134 v 76.174303 c 0,1.2962 -0.7685,2.4137 -1.8743,2.9202 l -65.740496,37.9554 c -1.0389,0.5964 -2.2717,0.5453 -3.233,-0.027 z M 17.44353,127.16987 v 0 l 62.785704,36.2488 62.785996,-36.2488 V 54.671267 l -62.785996,-36.2489 -62.785704,36.2489 z" style="fill:currentColor;fill-rule:evenodd" id="path1290"/><path d="M 80.229234,14.730167 14.23273,129.00757 h 131.9933 z" style="opacity:.88;fill:#ff0;fill-opacity:1;fill-rule:evenodd" id="path1292"/><path d="M 80.229234,21.136467 19.78603,125.79677 h 120.8861 z M 11.45983,127.40197 v 0 L 77.433934,13.163767 c 0.2713,-0.4856 0.6733,-0.9068 1.1895,-1.2056 1.531,-0.8864 3.4908,-0.3645 4.3778,1.1671 L 148.88863,127.21177 c 0.3464,0.5125 0.5485,1.1308 0.5485,1.7958 0,1.7733 -1.4378,3.2111 -3.2108,3.2111 H 14.23213 v -0.007 c -0.5459,5e-4 -1.099,-0.1386 -1.6052,-0.4318 -1.531,-0.8863 -2.0537,-2.8465 -1.1671,-4.3778 z" style="fill:currentColor;fill-rule:evenodd" id="path1294"/><path d="m 12.63903,55.605567 c -1.5309,-0.8799 -2.059,-2.8347 -1.1792,-4.3657 0.8802,-1.531 2.8347,-2.0591 4.3657,-1.1792 L 80.229234,87.229065 144.63323,50.060667 c 1.531,-0.8799 3.4855,-0.3518 4.3651,1.1792 0.8796,1.531 0.3517,3.4858 -1.1793,4.3657 L 81.867334,93.666065 c -0.9603,0.5723 -2.1931,0.6227 -3.2315,0.026 z" style="opacity:1;fill:currentColor;fill-rule:evenodd" id="path1296"/><path d="m 83.440034,167.11087 c 0,1.773 -1.4377,3.2111 -3.2108,3.2111 -1.7736,0 -3.2114,-1.4381 -3.2114,-3.2111 V 90.920065 c 0,-1.773 1.4378,-3.2111 3.2114,-3.2111 1.7731,0 3.2108,1.4381 3.2108,3.2111 z" style="fill:currentColor;fill-rule:evenodd" id="path1298"/><path d="m 146.22633,137.25697 c 4.5555,0 8.2491,-3.693 8.2491,-8.2494 0,-4.5564 -3.6936,-8.2491 -8.2491,-8.2491 -4.5564,0 -8.2503,3.6927 -8.2503,8.2491 0,4.5564 3.6939,8.2494 8.2503,8.2494 z" style="fill:currentColor;fill-rule:evenodd" id="path1300"/><path d="m 146.22633,61.082867 c 4.5555,0 8.2491,-3.6938 8.2491,-8.2496 0,-4.5562 -3.6936,-8.2494 -8.2491,-8.2494 -4.5564,0 -8.2503,3.6932 -8.2503,8.2494 0,4.5558 3.6939,8.2496 8.2503,8.2496 z" style="fill:currentColor;fill-rule:evenodd" id="path1302"/><path d="m 14.23213,61.082867 c 4.5561,0 8.25,-3.6938 8.25,-8.2496 0,-4.5562 -3.6939,-8.2494 -8.25,-8.2494 -4.5555003,0 -8.2494003,3.6932 -8.2494003,8.2494 0,4.5558 3.6939,8.2496 8.2494003,8.2496 z" style="fill:currentColor;fill-rule:evenodd" id="path1304"/><path d="m 14.23213,137.25697 c 4.5561,0 8.25,-3.693 8.25,-8.2494 0,-4.5564 -3.6939,-8.2491 -8.25,-8.2491 -4.5555003,0 -8.2494003,3.6927 -8.2494003,8.2491 0,4.5564 3.6939,8.2494 8.2494003,8.2494 z" style="fill:currentColor;fill-rule:evenodd" id="path1306"/><path d="m 80.229234,175.36027 c 4.5558,0 8.2497,-3.6933 8.2497,-8.2494 0,-4.5562 -3.6939,-8.2494 -8.2497,-8.2494 -4.5564,0 -8.2497,3.6932 -8.2497,8.2494 0,4.5561 3.6933,8.2494 8.2497,8.2494 z" style="fill:currentColor;fill-rule:evenodd" id="path1308"/><path d="m 80.229234,99.170065 c 4.5558,0 8.2497,-3.6938 8.2497,-8.25 0,-4.5561 -3.6939,-8.2494 -8.2497,-8.2494 -4.5564,0 -8.2497,3.6933 -8.2497,8.2494 0,4.5562 3.6933,8.25 8.2497,8.25 z" style="fill:currentColor;fill-rule:evenodd" id="path1310"/><path d="m 80.229234,22.979567 c 4.5558,0 8.2497,-3.6932 8.2497,-8.2494 0,-4.5561 -3.6939,-8.2492996 -8.2497,-8.2492996 -4.5564,0 -8.2497,3.6931996 -8.2497,8.2492996 0,4.5562 3.6933,8.2494 8.2497,8.2494 z" style="fill:currentColor;fill-rule:evenodd" id="path1312"/></svg></div>',
|
||||
'logo' => ' <div class="w-[4.5rem] aspect-square h-[4.5rem] p-2 transition-all duration-200 dark:opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-white fill-black bg-black/10 dark:bg-white/10"><svg version="1.1" width="180" height="60" id="svg1326" viewBox="0 0 544 182" sodipodi:docname="keydb.svg" inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"><defs id="defs1330"/><sodipodi:namedview id="namedview1328" pagecolor="currentColor" bordercolor="#666666" borderopacity="1.0" inkscape:pageshadow="2" inkscape:pageopacity="0.00784314" inkscape:pagecheckerboard="true" showgrid="false" inkscape:zoom="1.5064209" inkscape:cx="374.06544" inkscape:cy="123.80338" inkscape:window-width="1536" inkscape:window-height="889" inkscape:window-x="-8" inkscape:window-y="-8" inkscape:window-maximized="1" inkscape:current-layer="svg1326" width="791px"/><path d="M 78.589334,169.85617 12.63903,131.77977 c -1.0281,-0.591 -1.6035,-1.6659 -1.6046,-2.7722 h -0.0134 V 52.833267 c 0,-1.2966 0.7688,-2.4135 1.8749,-2.9206 l 65.739904,-37.9545 c 1.0386,-0.5966 2.2717,-0.5456 3.2321,0.0264 l 65.951096,38.0761 c 1.0282,0.591 1.6042,1.6659 1.6047,2.7726 h 0.0134 v 76.174303 c 0,1.2962 -0.7685,2.4137 -1.8743,2.9202 l -65.740496,37.9554 c -1.0389,0.5964 -2.2717,0.5453 -3.233,-0.027 z M 17.44353,127.16987 v 0 l 62.785704,36.2488 62.785996,-36.2488 V 54.671267 l -62.785996,-36.2489 -62.785704,36.2489 z" style="fill:currentColor;fill-rule:evenodd" id="path1290"/><path d="M 80.229234,14.730167 14.23273,129.00757 h 131.9933 z" style="opacity:.88;fill:#ff0;fill-opacity:1;fill-rule:evenodd" id="path1292"/><path d="M 80.229234,21.136467 19.78603,125.79677 h 120.8861 z M 11.45983,127.40197 v 0 L 77.433934,13.163767 c 0.2713,-0.4856 0.6733,-0.9068 1.1895,-1.2056 1.531,-0.8864 3.4908,-0.3645 4.3778,1.1671 L 148.88863,127.21177 c 0.3464,0.5125 0.5485,1.1308 0.5485,1.7958 0,1.7733 -1.4378,3.2111 -3.2108,3.2111 H 14.23213 v -0.007 c -0.5459,5e-4 -1.099,-0.1386 -1.6052,-0.4318 -1.531,-0.8863 -2.0537,-2.8465 -1.1671,-4.3778 z" style="fill:currentColor;fill-rule:evenodd" id="path1294"/><path d="m 12.63903,55.605567 c -1.5309,-0.8799 -2.059,-2.8347 -1.1792,-4.3657 0.8802,-1.531 2.8347,-2.0591 4.3657,-1.1792 L 80.229234,87.229065 144.63323,50.060667 c 1.531,-0.8799 3.4855,-0.3518 4.3651,1.1792 0.8796,1.531 0.3517,3.4858 -1.1793,4.3657 L 81.867334,93.666065 c -0.9603,0.5723 -2.1931,0.6227 -3.2315,0.026 z" style="opacity:1;fill:currentColor;fill-rule:evenodd" id="path1296"/><path d="m 83.440034,167.11087 c 0,1.773 -1.4377,3.2111 -3.2108,3.2111 -1.7736,0 -3.2114,-1.4381 -3.2114,-3.2111 V 90.920065 c 0,-1.773 1.4378,-3.2111 3.2114,-3.2111 1.7731,0 3.2108,1.4381 3.2108,3.2111 z" style="fill:currentColor;fill-rule:evenodd" id="path1298"/><path d="m 146.22633,137.25697 c 4.5555,0 8.2491,-3.693 8.2491,-8.2494 0,-4.5564 -3.6936,-8.2491 -8.2491,-8.2491 -4.5564,0 -8.2503,3.6927 -8.2503,8.2491 0,4.5564 3.6939,8.2494 8.2503,8.2494 z" style="fill:currentColor;fill-rule:evenodd" id="path1300"/><path d="m 146.22633,61.082867 c 4.5555,0 8.2491,-3.6938 8.2491,-8.2496 0,-4.5562 -3.6936,-8.2494 -8.2491,-8.2494 -4.5564,0 -8.2503,3.6932 -8.2503,8.2494 0,4.5558 3.6939,8.2496 8.2503,8.2496 z" style="fill:currentColor;fill-rule:evenodd" id="path1302"/><path d="m 14.23213,61.082867 c 4.5561,0 8.25,-3.6938 8.25,-8.2496 0,-4.5562 -3.6939,-8.2494 -8.25,-8.2494 -4.5555003,0 -8.2494003,3.6932 -8.2494003,8.2494 0,4.5558 3.6939,8.2496 8.2494003,8.2496 z" style="fill:currentColor;fill-rule:evenodd" id="path1304"/><path d="m 14.23213,137.25697 c 4.5561,0 8.25,-3.693 8.25,-8.2494 0,-4.5564 -3.6939,-8.2491 -8.25,-8.2491 -4.5555003,0 -8.2494003,3.6927 -8.2494003,8.2491 0,4.5564 3.6939,8.2494 8.2494003,8.2494 z" style="fill:currentColor;fill-rule:evenodd" id="path1306"/><path d="m 80.229234,175.36027 c 4.5558,0 8.2497,-3.6933 8.2497,-8.2494 0,-4.5562 -3.6939,-8.2494 -8.2497,-8.2494 -4.5564,0 -8.2497,3.6932 -8.2497,8.2494 0,4.5561 3.6933,8.2494 8.2497,8.2494 z" style="fill:currentColor;fill-rule:evenodd" id="path1308"/><path d="m 80.229234,99.170065 c 4.5558,0 8.2497,-3.6938 8.2497,-8.25 0,-4.5561 -3.6939,-8.2494 -8.2497,-8.2494 -4.5564,0 -8.2497,3.6933 -8.2497,8.2494 0,4.5562 3.6933,8.25 8.2497,8.25 z" style="fill:currentColor;fill-rule:evenodd" id="path1310"/><path d="m 80.229234,22.979567 c 4.5558,0 8.2497,-3.6932 8.2497,-8.2494 0,-4.5561 -3.6939,-8.2492996 -8.2497,-8.2492996 -4.5564,0 -8.2497,3.6931996 -8.2497,8.2492996 0,4.5562 3.6933,8.2494 8.2497,8.2494 z" style="fill:currentColor;fill-rule:evenodd" id="path1312"/></svg></div>',
|
||||
],
|
||||
[
|
||||
'id' => 'dragonfly',
|
||||
'name' => 'Dragonfly',
|
||||
'description' => 'Dragonfly DB is a drop-in Redis replacement that delivers 25x more throughput and 12x faster snapshotting than Redis.',
|
||||
'logo' => '<div class="w-[4.5rem] aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-white fill-black"><svg class="block dark:hidden" width="245" height="60" viewBox="0 0 210 50" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M22 0C3.883 0 0 3.883 0 22C0 40.117 3.883 44 22 44C40.117 44 44 40.117 44 22C44 3.883 40.117 0 22 0ZM19.5585 16.265C19.8224 16.406 20 16.7008 20 17C20 18.1046 20.8954 19 22 19C23.1046 19 24 18.1046 24 17C24 16.7008 24.1776 16.406 24.4414 16.265C25.0714 15.9283 25.5 15.2642 25.5 14.5C25.5 13.3954 24.6046 12.5 23.5 12.5C23.3071 12.5 23.1205 12.5273 22.944 12.5783C22.3661 12.7452 21.6339 12.7452 21.056 12.5783C20.8794 12.5273 20.6929 12.5 20.5 12.5C19.3954 12.5 18.5 13.3954 18.5 14.5C18.5 15.2642 18.9286 15.9283 19.5585 16.265ZM20.3087 18.8411C20.2282 18.8977 20.1644 18.978 20.1283 19.0744L19.6243 20.4185C19.5438 20.633 19.5395 20.8686 19.6119 21.0859L20.4253 23.526C20.4744 23.6733 20.5566 23.8087 20.687 23.893C20.9154 24.0408 21.3531 24.25 22 24.25C22.6468 24.25 23.0845 24.0408 23.3129 23.893C23.4433 23.8087 23.5255 23.6733 23.5746 23.526L24.388 21.0859C24.4604 20.8686 24.4561 20.633 24.3756 20.4185L23.8716 19.0744C23.8355 18.978 23.7717 18.8977 23.6912 18.8411C23.2461 19.2502 22.6522 19.5 22 19.5C21.3477 19.5 20.7538 19.2502 20.3087 18.8411ZM21 30L20.5105 24.3712C20.8174 24.5497 21.3156 24.75 22 24.75C22.6842 24.75 23.1825 24.5498 23.4895 24.3711L23 30C23 30 22.75 30.25 22 30.25C21.25 30.25 21 30 21 30ZM21.25 34L21.0055 30.5779L21.0331 30.5892C21.2547 30.6779 21.5686 30.75 22 30.75C22.4314 30.75 22.7453 30.6779 22.967 30.5892L22.9944 30.578L22.75 34C22.75 34 22.625 34.5 22 34.5C21.375 34.5 21.25 34 21.25 34ZM9.24995 20H19.2C18.8224 20.6294 18.9914 21.0678 19.1582 21.5005L19.1582 21.5005L19.1582 21.5006L19.1582 21.5006L19.1582 21.5006L19.1582 21.5006C19.1901 21.5835 19.222 21.6662 19.25 21.75L9.90851 24.4252C9.31202 24.5932 8.67362 24.3772 8.3018 23.8814L7.71391 23.0976C7.37302 22.643 7.31818 22.0349 7.57227 21.5267L7.92108 20.8291C8.17311 20.3251 8.68641 20.0048 9.24995 20ZM7.99998 19.2331L19.375 19.5C19.375 19.5 19.5546 19.0506 19.625 18.75C19.6491 18.647 19.6745 18.5441 19.6961 18.4586C19.7264 18.3386 19.6699 18.2154 19.5562 18.1662C18.2312 17.5935 10.5221 14.2625 9.23721 13.7331C8.73807 13.5197 8.16257 13.5926 7.73242 13.9238L6.58499 14.8072C6.20854 15.097 5.99872 15.5412 5.99998 16C6.00037 16.1456 6.02204 16.2928 6.06639 16.4369L6.62254 18.2331C6.80943 18.8405 7.36503 19.2067 7.99998 19.2331ZM34.75 20H24.8C25.1776 20.6294 25.0086 21.0679 24.8418 21.5005L24.8418 21.5005L24.8418 21.5006L24.8418 21.5006C24.8098 21.5835 24.7779 21.6662 24.75 21.75L34.0914 24.4252C34.6879 24.5932 35.3263 24.3772 35.6982 23.8814L36.286 23.0976C36.6269 22.643 36.6818 22.0349 36.4277 21.5267L36.0789 20.8291C35.8268 20.3251 35.3135 20.0048 34.75 20ZM36 19.2331L24.625 19.5C24.625 19.5 24.4453 19.0506 24.375 18.75C24.3509 18.647 24.3255 18.5441 24.3039 18.4586C24.2735 18.3386 24.3301 18.2154 24.4438 18.1662C25.7688 17.5935 33.4779 14.2625 34.7627 13.7331C35.2619 13.5197 35.8374 13.5926 36.2675 13.9238L37.415 14.8072C37.7914 15.097 38.0012 15.5412 38 16C37.9996 16.1456 37.9779 16.2928 37.9336 16.4369L37.3774 18.2331C37.1905 18.8405 36.6349 19.2067 36 19.2331Z" fill="black"/></svg><svg class="hidden dark:block" xmlns="http://www.w3.org/2000/svg" width="225" height="55" viewBox="0 0 378 88" fill="none"><path fill-rule="evenodd" clip-rule="evenodd" d="M44 0C7.766 0 0 7.766 0 44C0 80.234 7.766 88 44 88C80.234 88 88 80.234 88 44C88 7.766 80.234 0 44 0ZM39.1171 32.53C39.6448 32.8121 40 33.4016 40 34C40 36.2091 41.7909 38 44 38C46.2091 38 48 36.2091 48 34C48 33.4016 48.3552 32.8121 48.8829 32.53C50.1428 31.8566 51 30.5284 51 29C51 26.7909 49.2091 25 47 25C46.6142 25 46.2411 25.0546 45.8881 25.1566C44.7322 25.4904 43.2678 25.4904 42.1119 25.1566C41.7589 25.0546 41.3858 25 41 25C38.7909 25 37 26.7909 37 29C37 30.5284 37.8572 31.8566 39.1171 32.53ZM40.6174 37.6822C40.4565 37.7954 40.3289 37.9561 40.2566 38.1489L39.2486 40.837C39.0877 41.266 39.079 41.7371 39.2238 42.1717L40.8506 47.0521C40.9488 47.3466 41.1133 47.6174 41.374 47.786C41.8309 48.0815 42.7062 48.5 43.9999 48.5C45.2937 48.5 46.169 48.0815 46.6259 47.786C46.8866 47.6174 47.0511 47.3466 47.1492 47.0521L48.776 42.1717C48.9209 41.7371 48.9122 41.266 48.7513 40.837L47.7433 38.1489C47.671 37.9561 47.5434 37.7954 47.3825 37.6822C46.4922 38.5005 45.3044 39 43.9999 39C42.6955 39 41.5077 38.5005 40.6174 37.6822ZM42 60L41.0211 48.7423C41.6348 49.0994 42.6312 49.5 44 49.5C45.3684 49.5 46.365 49.0996 46.979 48.7423L46 60C46 60 45.5 60.5 44 60.5C42.5 60.5 42 60 42 60ZM42.5 68L42.0111 61.1559C42.0291 61.1635 42.0474 61.171 42.0662 61.1785C42.5095 61.3558 43.1373 61.5 44 61.5C44.8628 61.5 45.4906 61.3558 45.9339 61.1785C45.9526 61.171 45.9709 61.1635 45.9888 61.156L45.5 68C45.5 68 45.25 69 44 69C42.75 69 42.5 68 42.5 68ZM18.49
Line truncated
|
||||
'logo' => '<div class="w-[4.5rem] aspect-square h-[4.5rem] p-2 transition-all duration-200 dark: opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-white fill-black bg-black/10 dark:bg-white/10"><svg class="block dark:hidden" width="245" height="60" viewBox="0 0 210 50" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M22 0C3.883 0 0 3.883 0 22C0 40.117 3.883 44 22 44C40.117 44 44 40.117 44 22C44 3.883 40.117 0 22 0ZM19.5585 16.265C19.8224 16.406 20 16.7008 20 17C20 18.1046 20.8954 19 22 19C23.1046 19 24 18.1046 24 17C24 16.7008 24.1776 16.406 24.4414 16.265C25.0714 15.9283 25.5 15.2642 25.5 14.5C25.5 13.3954 24.6046 12.5 23.5 12.5C23.3071 12.5 23.1205 12.5273 22.944 12.5783C22.3661 12.7452 21.6339 12.7452 21.056 12.5783C20.8794 12.5273 20.6929 12.5 20.5 12.5C19.3954 12.5 18.5 13.3954 18.5 14.5C18.5 15.2642 18.9286 15.9283 19.5585 16.265ZM20.3087 18.8411C20.2282 18.8977 20.1644 18.978 20.1283 19.0744L19.6243 20.4185C19.5438 20.633 19.5395 20.8686 19.6119 21.0859L20.4253 23.526C20.4744 23.6733 20.5566 23.8087 20.687 23.893C20.9154 24.0408 21.3531 24.25 22 24.25C22.6468 24.25 23.0845 24.0408 23.3129 23.893C23.4433 23.8087 23.5255 23.6733 23.5746 23.526L24.388 21.0859C24.4604 20.8686 24.4561 20.633 24.3756 20.4185L23.8716 19.0744C23.8355 18.978 23.7717 18.8977 23.6912 18.8411C23.2461 19.2502 22.6522 19.5 22 19.5C21.3477 19.5 20.7538 19.2502 20.3087 18.8411ZM21 30L20.5105 24.3712C20.8174 24.5497 21.3156 24.75 22 24.75C22.6842 24.75 23.1825 24.5498 23.4895 24.3711L23 30C23 30 22.75 30.25 22 30.25C21.25 30.25 21 30 21 30ZM21.25 34L21.0055 30.5779L21.0331 30.5892C21.2547 30.6779 21.5686 30.75 22 30.75C22.4314 30.75 22.7453 30.6779 22.967 30.5892L22.9944 30.578L22.75 34C22.75 34 22.625 34.5 22 34.5C21.375 34.5 21.25 34 21.25 34ZM9.24995 20H19.2C18.8224 20.6294 18.9914 21.0678 19.1582 21.5005L19.1582 21.5005L19.1582 21.5006L19.1582 21.5006L19.1582 21.5006L19.1582 21.5006C19.1901 21.5835 19.222 21.6662 19.25 21.75L9.90851 24.4252C9.31202 24.5932 8.67362 24.3772 8.3018 23.8814L7.71391 23.0976C7.37302 22.643 7.31818 22.0349 7.57227 21.5267L7.92108 20.8291C8.17311 20.3251 8.68641 20.0048 9.24995 20ZM7.99998 19.2331L19.375 19.5C19.375 19.5 19.5546 19.0506 19.625 18.75C19.6491 18.647 19.6745 18.5441 19.6961 18.4586C19.7264 18.3386 19.6699 18.2154 19.5562 18.1662C18.2312 17.5935 10.5221 14.2625 9.23721 13.7331C8.73807 13.5197 8.16257 13.5926 7.73242 13.9238L6.58499 14.8072C6.20854 15.097 5.99872 15.5412 5.99998 16C6.00037 16.1456 6.02204 16.2928 6.06639 16.4369L6.62254 18.2331C6.80943 18.8405 7.36503 19.2067 7.99998 19.2331ZM34.75 20H24.8C25.1776 20.6294 25.0086 21.0679 24.8418 21.5005L24.8418 21.5005L24.8418 21.5006L24.8418 21.5006C24.8098 21.5835 24.7779 21.6662 24.75 21.75L34.0914 24.4252C34.6879 24.5932 35.3263 24.3772 35.6982 23.8814L36.286 23.0976C36.6269 22.643 36.6818 22.0349 36.4277 21.5267L36.0789 20.8291C35.8268 20.3251 35.3135 20.0048 34.75 20ZM36 19.2331L24.625 19.5C24.625 19.5 24.4453 19.0506 24.375 18.75C24.3509 18.647 24.3255 18.5441 24.3039 18.4586C24.2735 18.3386 24.3301 18.2154 24.4438 18.1662C25.7688 17.5935 33.4779 14.2625 34.7627 13.7331C35.2619 13.5197 35.8374 13.5926 36.2675 13.9238L37.415 14.8072C37.7914 15.097 38.0012 15.5412 38 16C37.9996 16.1456 37.9779 16.2928 37.9336 16.4369L37.3774 18.2331C37.1905 18.8405 36.6349 19.2067 36 19.2331Z" fill="black"/></svg><svg class="hidden dark:block" xmlns="http://www.w3.org/2000/svg" width="225" height="55" viewBox="0 0 378 88" fill="none"><path fill-rule="evenodd" clip-rule="evenodd" d="M44 0C7.766 0 0 7.766 0 44C0 80.234 7.766 88 44 88C80.234 88 88 80.234 88 44C88 7.766 80.234 0 44 0ZM39.1171 32.53C39.6448 32.8121 40 33.4016 40 34C40 36.2091 41.7909 38 44 38C46.2091 38 48 36.2091 48 34C48 33.4016 48.3552 32.8121 48.8829 32.53C50.1428 31.8566 51 30.5284 51 29C51 26.7909 49.2091 25 47 25C46.6142 25 46.2411 25.0546 45.8881 25.1566C44.7322 25.4904 43.2678 25.4904 42.1119 25.1566C41.7589 25.0546 41.3858 25 41 25C38.7909 25 37 26.7909 37 29C37 30.5284 37.8572 31.8566 39.1171 32.53ZM40.6174 37.6822C40.4565 37.7954 40.3289 37.9561 40.2566 38.1489L39.2486 40.837C39.0877 41.266 39.079 41.7371 39.2238 42.1717L40.8506 47.0521C40.9488 47.3466 41.1133 47.6174 41.374 47.786C41.8309 48.0815 42.7062 48.5 43.9999 48.5C45.2937 48.5 46.169 48.0815 46.6259 47.786C46.8866 47.6174 47.0511 47.3466 47.1492 47.0521L48.776 42.1717C48.9209 41.7371 48.9122 41.266 48.7513 40.837L47.7433 38.1489C47.671 37.9561 47.5434 37.7954 47.3825 37.6822C46.4922 38.5005 45.3044 39 43.9999 39C42.6955 39 41.5077 38.5005 40.6174 37.6822ZM42 60L41.0211 48.7423C41.6348 49.0994 42.6312 49.5 44 49.5C45.3684 49.5 46.365 49.0996 46.979 48.7423L46 60C46 60 45.5 60.5 44 60.5C42.5 60.5 42 60 42 60ZM42.5 68L42.0111 61.1559C42.0291 61.1635 42.0474 61.171 42.0662 61.1785C42.5095 61.3558 43.1373 61.5 44 61.5C44.8628 61.5 45.4906 61.3558 45.9339 61.1785C45.9526 61.171 45.9709 61.1635 45.9888 61.156L45.5 68C45.5 68 45.25 69 4
Line truncated
|
||||
],
|
||||
[
|
||||
'id' => 'mongodb',
|
||||
'name' => 'MongoDB',
|
||||
'description' => 'MongoDB is a source-available, cross-platform, document-oriented database program.',
|
||||
'logo' => '<svg class="w-[4.5rem] aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-white fill-black" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path fill-rule="evenodd" clip-rule="evenodd" fill="currentColor" d="M87.259 100.139c.169-.325.331-.612.469-.909.087-.19.221-.228.41-.223 1.133.032 2.266.067 3.4.078.963.01 1.928-.008 2.892-.019 1.086-.013 2.172-.07 3.257-.039 1.445.042 2.853.325 4.16.968 1.561.769 2.742 1.94 3.547 3.483a8.71 8.71 0 01.931 3.14c.172 1.608.059 3.179-.451 4.717-.632 1.906-1.832 3.365-3.499 4.458-1.283.841-2.69 1.338-4.198 1.622-1.596.301-3.197.204-4.798.209-1.756.007-3.511-.031-5.267-.051-.307-.003-.351-.061-.27-.354l.075-.27c.171-.538.263-.562.809-.652a2.83 2.83 0 001.087-.413c.184-.122.26-.44.332-.685.062-.214.065-.449.067-.675.025-3.425.051-6.849.065-10.272a44.077 44.077 0 00-.065-2.596c-.034-.605-.357-1.019-1.077-1.162-.56-.111-1.124-.197-1.687-.296l-.189-.059zm16.076 8.293c-.076-.682-.113-1.37-.235-2.042-.292-1.613-.998-3.018-2.238-4.119-2.005-1.779-4.419-2.053-6.949-1.841-.576.048-.7.245-.709.837-.014.84-.028 1.68-.029 2.52-.004 2.664-.004 5.328 0 7.992.001.758.009 1.516.031 2.272.024.774.305 1.429 1.063 1.729 1.195.473 2.452.529 3.706.336 2.003-.307 3.404-1.474 4.344-3.223.744-1.388.954-2.903 1.016-4.461zm4.869 9.071c-.024-.415.146-.758.356-1.073.057-.085.253-.081.388-.108l1.146-.227c.405-.086.618-.358.675-.755.038-.262.074-.527.077-.792a879.6 879.6 0 00.059-6.291c.01-2.1.002-4.2.002-6.3l-.009-.401c-.041-.675-.367-1.025-1.037-1.124l-1.453-.221c-.179-.024-.244-.11-.179-.269.112-.271.219-.552.377-.796.059-.09.258-.125.392-.122.694.01 1.388.062 2.082.061l6.041-.036c1.164-.001 2.288.202 3.332.759 1.149.612 1.792 1.559 1.976 2.849.192 1.355-.219 2.497-1.209 3.404-.407.374-.934.618-1.406.922l-.154.096c.438.161.855.3 1.261.466 1.188.487 2.133 1.248 2.633 2.463.395.959.395 1.959.161 2.953-.364 1.556-1.389 2.591-2.722 3.374-1.251.735-2.605 1.163-4.047 1.235-1.33.067-2.666.042-3.999.057l-.772.004a996.106 996.106 0 01-3.854-.096l-.117-.032zm5.537-6.089h.013c0 .658-.009 1.316.003 1.974.008.426-.007.864.085 1.274.138.613.418 1.166 1.106 1.342a6.671 6.671 0 002.818.124c1.177-.205 2.116-.795 2.631-1.916.382-.833.439-1.716.308-2.618-.174-1.188-.805-2.05-1.854-2.615-.688-.371-1.422-.598-2.204-.628-.876-.033-1.753-.035-2.629-.062-.246-.007-.28.118-.279.32.005.934.002 1.869.002 2.805zm1.865-4.475c.479-.024 1.021-.031 1.56-.085 1.032-.103 1.759-.622 2.138-1.609.193-.501.185-1.017.19-1.538.015-1.357-.777-2.469-2.066-2.929-.995-.355-2.021-.361-3.053-.333-.418.011-.605.194-.611.615l-.062 5.489c-.003.218.091.312.303.319l1.601.071z"/><path fill-rule="evenodd" clip-rule="evenodd" fill="currentColor" d="M10.543 116.448l-.073.944c-.68 0-1.307-.005-1.934.002-1.181.012-2.362.031-3.544.048l-.114.007c-.169-.02-.476-.02-.484-.07-.05-.281-.034-.576-.021-.867.001-.033.116-.075.183-.091l.919-.205c.573-.149.775-.396.802-.988.031-.667.062-1.335.065-2.002.009-1.642-.001-3.282.006-4.924.001-.384-.132-.67-.49-.826a43.787 43.787 0 00-1.285-.546c-.204-.082-.469-.127-.445-.401.024-.279.281-.352.523-.407 1.002-.229 2.005-.452 3.004-.696.322-.079.63-.212 1.015-.346.02.208.057.406.053.604l-.059.941c-.001.106.054.248.133.307.048.037.209-.03.289-.092.854-.65 1.758-1.211 2.789-1.538 1.597-.507 2.968-.037 3.928 1.34.338.485.339.485.808.146.805-.585 1.647-1.101 2.589-1.441 2.068-.747 4.055.201 4.774 2.284.262.756.362 1.537.36 2.335l-.019 5.298c-.001.437.144.686.56.822.467.153.951.258 1.477.396l-.122.911c-.598 0-1.148-.004-1.698.001-1.344.012-2.688.019-4.031.05-.234.006-.295-.052-.307-.271-.039-.701-.045-.7.615-.858l.222-.057c.645-.176.86-.374.865-1.028.015-1.878.054-3.761-.041-5.635-.099-1.944-1.642-2.979-3.526-2.481a5.194 5.194 0 00-1.69.814c-.175.125-.208.269-.194.488.053.828.086 1.657.093 2.486.012 1.451-.006 2.902 0 4.354.002.588.203.813.784.949l.863.199.16.036c.012.276.023.552.01.828-.008.173-.142.188-.292.185-.839-.021-1.679-.049-2.518-.047-1.021.002-2.041.031-3.061.049h-.24c0-.293-.014-.573.01-.852.005-.067.123-.161.204-.182l1.006-.213c.427-.105.631-.324.655-.758.114-2.01.196-4.021.007-6.03a3.695 3.695 0 00-.326-1.145c-.515-1.138-1.674-1.613-3.011-1.271-.635.162-1.208.453-1.75.82a.795.795 0 00-.378.695c0 2.005.007 4.01.013 6.014l.011.773c.012.539.241.823.776.939.344.078.692.131 1.082.203z"/><path fill-rule="evenodd" clip-rule="evenodd" fill="currentColor" d="M71.001 105.285c.155.754.152 1.432-.402 1.946-.55.511-1.246.339-1.925.225.063.358.133.662.167.97.247 2.289-.738 3.988-2.861 4.959-.802.366-1.653.502-2.522.572-.432.034-.81.364-.851.719-.042.36.184.73.636.838.533.127 1.089.163 1.636.226 1.174.134 2.361.195 3.521.405 1.754.316 2.733 1.847 2.424 3.609-.275 1.568-1.183 2.709-2.449 3.584-2.133 1.478-4.473 1.91-6.965 1.156-1.425-.432-2.43-1.369-2.777-2.885-.174-.759.011-1.446.582-1.961.679-.61 1.418-1.154 2.129-1.73l.23-.231-.264-.185c-.725-.344-1.305-.815-1.53-1.633-.077-.277.003-.459.238-.601.4-.241.798-.4
Line truncated
|
||||
'logo' => '<svg class="w-[4.5rem] aspect-square h-[4.5rem] p-2 transition-all duration-200 dark:opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-white fill-black bg-black/10 dark:bg-white/10" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path fill-rule="evenodd" clip-rule="evenodd" fill="currentColor" d="M87.259 100.139c.169-.325.331-.612.469-.909.087-.19.221-.228.41-.223 1.133.032 2.266.067 3.4.078.963.01 1.928-.008 2.892-.019 1.086-.013 2.172-.07 3.257-.039 1.445.042 2.853.325 4.16.968 1.561.769 2.742 1.94 3.547 3.483a8.71 8.71 0 01.931 3.14c.172 1.608.059 3.179-.451 4.717-.632 1.906-1.832 3.365-3.499 4.458-1.283.841-2.69 1.338-4.198 1.622-1.596.301-3.197.204-4.798.209-1.756.007-3.511-.031-5.267-.051-.307-.003-.351-.061-.27-.354l.075-.27c.171-.538.263-.562.809-.652a2.83 2.83 0 001.087-.413c.184-.122.26-.44.332-.685.062-.214.065-.449.067-.675.025-3.425.051-6.849.065-10.272a44.077 44.077 0 00-.065-2.596c-.034-.605-.357-1.019-1.077-1.162-.56-.111-1.124-.197-1.687-.296l-.189-.059zm16.076 8.293c-.076-.682-.113-1.37-.235-2.042-.292-1.613-.998-3.018-2.238-4.119-2.005-1.779-4.419-2.053-6.949-1.841-.576.048-.7.245-.709.837-.014.84-.028 1.68-.029 2.52-.004 2.664-.004 5.328 0 7.992.001.758.009 1.516.031 2.272.024.774.305 1.429 1.063 1.729 1.195.473 2.452.529 3.706.336 2.003-.307 3.404-1.474 4.344-3.223.744-1.388.954-2.903 1.016-4.461zm4.869 9.071c-.024-.415.146-.758.356-1.073.057-.085.253-.081.388-.108l1.146-.227c.405-.086.618-.358.675-.755.038-.262.074-.527.077-.792a879.6 879.6 0 00.059-6.291c.01-2.1.002-4.2.002-6.3l-.009-.401c-.041-.675-.367-1.025-1.037-1.124l-1.453-.221c-.179-.024-.244-.11-.179-.269.112-.271.219-.552.377-.796.059-.09.258-.125.392-.122.694.01 1.388.062 2.082.061l6.041-.036c1.164-.001 2.288.202 3.332.759 1.149.612 1.792 1.559 1.976 2.849.192 1.355-.219 2.497-1.209 3.404-.407.374-.934.618-1.406.922l-.154.096c.438.161.855.3 1.261.466 1.188.487 2.133 1.248 2.633 2.463.395.959.395 1.959.161 2.953-.364 1.556-1.389 2.591-2.722 3.374-1.251.735-2.605 1.163-4.047 1.235-1.33.067-2.666.042-3.999.057l-.772.004a996.106 996.106 0 01-3.854-.096l-.117-.032zm5.537-6.089h.013c0 .658-.009 1.316.003 1.974.008.426-.007.864.085 1.274.138.613.418 1.166 1.106 1.342a6.671 6.671 0 002.818.124c1.177-.205 2.116-.795 2.631-1.916.382-.833.439-1.716.308-2.618-.174-1.188-.805-2.05-1.854-2.615-.688-.371-1.422-.598-2.204-.628-.876-.033-1.753-.035-2.629-.062-.246-.007-.28.118-.279.32.005.934.002 1.869.002 2.805zm1.865-4.475c.479-.024 1.021-.031 1.56-.085 1.032-.103 1.759-.622 2.138-1.609.193-.501.185-1.017.19-1.538.015-1.357-.777-2.469-2.066-2.929-.995-.355-2.021-.361-3.053-.333-.418.011-.605.194-.611.615l-.062 5.489c-.003.218.091.312.303.319l1.601.071z"/><path fill-rule="evenodd" clip-rule="evenodd" fill="currentColor" d="M10.543 116.448l-.073.944c-.68 0-1.307-.005-1.934.002-1.181.012-2.362.031-3.544.048l-.114.007c-.169-.02-.476-.02-.484-.07-.05-.281-.034-.576-.021-.867.001-.033.116-.075.183-.091l.919-.205c.573-.149.775-.396.802-.988.031-.667.062-1.335.065-2.002.009-1.642-.001-3.282.006-4.924.001-.384-.132-.67-.49-.826a43.787 43.787 0 00-1.285-.546c-.204-.082-.469-.127-.445-.401.024-.279.281-.352.523-.407 1.002-.229 2.005-.452 3.004-.696.322-.079.63-.212 1.015-.346.02.208.057.406.053.604l-.059.941c-.001.106.054.248.133.307.048.037.209-.03.289-.092.854-.65 1.758-1.211 2.789-1.538 1.597-.507 2.968-.037 3.928 1.34.338.485.339.485.808.146.805-.585 1.647-1.101 2.589-1.441 2.068-.747 4.055.201 4.774 2.284.262.756.362 1.537.36 2.335l-.019 5.298c-.001.437.144.686.56.822.467.153.951.258 1.477.396l-.122.911c-.598 0-1.148-.004-1.698.001-1.344.012-2.688.019-4.031.05-.234.006-.295-.052-.307-.271-.039-.701-.045-.7.615-.858l.222-.057c.645-.176.86-.374.865-1.028.015-1.878.054-3.761-.041-5.635-.099-1.944-1.642-2.979-3.526-2.481a5.194 5.194 0 00-1.69.814c-.175.125-.208.269-.194.488.053.828.086 1.657.093 2.486.012 1.451-.006 2.902 0 4.354.002.588.203.813.784.949l.863.199.16.036c.012.276.023.552.01.828-.008.173-.142.188-.292.185-.839-.021-1.679-.049-2.518-.047-1.021.002-2.041.031-3.061.049h-.24c0-.293-.014-.573.01-.852.005-.067.123-.161.204-.182l1.006-.213c.427-.105.631-.324.655-.758.114-2.01.196-4.021.007-6.03a3.695 3.695 0 00-.326-1.145c-.515-1.138-1.674-1.613-3.011-1.271-.635.162-1.208.453-1.75.82a.795.795 0 00-.378.695c0 2.005.007 4.01.013 6.014l.011.773c.012.539.241.823.776.939.344.078.692.131 1.082.203z"/><path fill-rule="evenodd" clip-rule="evenodd" fill="currentColor" d="M71.001 105.285c.155.754.152 1.432-.402 1.946-.55.511-1.246.339-1.925.225.063.358.133.662.167.97.247 2.289-.738 3.988-2.861 4.959-.802.366-1.653.502-2.522.572-.432.034-.81.364-.851.719-.042.36.184.73.636.838.533.127 1.089.163 1.636.226 1.174.134 2.361.195 3.521.405 1.754.316 2.733 1.847 2.424 3.609-.275 1.568-1.183 2.709-2.449 3.584-2.133 1.478-4.473 1.91-6.965 1.156-1.425-.432-2.43-1.369-2.777-2.885-.174-.759.011-1.446.582-1.961.679-.61 1.418-1.154 2.129-1.73l.23-.231-.264-.185c-.725-.344-1.305-.815-1.53-1.633-.077-.
Line truncated
|
||||
],
|
||||
[
|
||||
'id' => 'clickhouse',
|
||||
'name' => 'ClickHouse',
|
||||
'description' => 'ClickHouse is a column-oriented database that supports real-time analytics, business intelligence, observability, ML and GenAI, and more.',
|
||||
'logo' => '<div class="w-[4.5rem] aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-white fill-black"><svg width="215" height="90" viewBox="0 0 100 43" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0_378_10860)"><rect x="2.70837" y="2.875" width="2.24992" height="20.2493" rx="0.236664" fill="currentColor"/><rect x="7.2085" y="2.875" width="2.24992" height="20.2493" rx="0.236664" fill="currentColor"/><rect x="11.7086" y="2.875" width="2.24992" height="20.2493" rx="0.236664" fill="currentColor"/><rect x="16.2076" y="2.875" width="2.24992" height="20.2493" rx="0.236664" fill="currentColor"/><rect x="20.7087" y="10.7502" width="2.24992" height="4.49985" rx="0.236664" fill="currentColor"/></g></svg></div>',
|
||||
'logo' => '<div class="w-[4.5rem] aspect-square h-[4.5rem] p-2 transition-all duration-200 dark:opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-white fill-black bg-black/10 dark:bg-white/10"><svg width="215" height="90" viewBox="0 0 100 43" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0_378_10860)"><rect x="2.70837" y="2.875" width="2.24992" height="20.2493" rx="0.236664" fill="currentColor"/><rect x="7.2085" y="2.875" width="2.24992" height="20.2493" rx="0.236664" fill="currentColor"/><rect x="11.7086" y="2.875" width="2.24992" height="20.2493" rx="0.236664" fill="currentColor"/><rect x="16.2076" y="2.875" width="2.24992" height="20.2493" rx="0.236664" fill="currentColor"/><rect x="20.7087" y="10.7502" width="2.24992" height="4.49985" rx="0.236664" fill="currentColor"/></g></svg></div>',
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -168,18 +168,42 @@ class ExecuteContainerCommand extends Component
|
||||
return;
|
||||
}
|
||||
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);
|
||||
if (is_null($container)) {
|
||||
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()) {
|
||||
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(
|
||||
'send-terminal-command',
|
||||
isset($container),
|
||||
true,
|
||||
data_get($container, 'container.Names'),
|
||||
data_get($container, 'server.uuid')
|
||||
);
|
||||
|
||||
@@ -29,11 +29,20 @@ class Terminal extends Component
|
||||
$server = Server::ownedByCurrentTeam()->whereUuid($serverUuid)->firstOrFail();
|
||||
|
||||
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);
|
||||
if ($status !== 'running') {
|
||||
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 {
|
||||
$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');
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ class Advanced extends Component
|
||||
#[Validate('boolean')]
|
||||
public bool $forceDockerCleanup = false;
|
||||
|
||||
#[Validate('string')]
|
||||
#[Validate(['string', 'required'])]
|
||||
public string $dockerCleanupFrequency = '*/10 * * * *';
|
||||
|
||||
#[Validate(['integer', 'min:1', 'max:99'])]
|
||||
@@ -78,7 +78,6 @@ class Advanced extends Component
|
||||
try {
|
||||
$this->syncData(true);
|
||||
$this->dispatch('success', 'Server updated.');
|
||||
// $this->dispatch('refreshServerShow');
|
||||
} catch (\Throwable $e) {
|
||||
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) {
|
||||
$this->customValidation();
|
||||
$this->server->settings->is_logdrain_newrelic_enabled = $this->isLogDrainNewRelicEnabled;
|
||||
$this->server->settings->is_logdrain_axiom_enabled = $this->isLogDrainAxiomEnabled;
|
||||
$this->server->settings->is_logdrain_custom_enabled = $this->isLogDrainCustomEnabled;
|
||||
|
||||
$this->server->settings->logdrain_newrelic_license_key = $this->logDrainNewRelicLicenseKey;
|
||||
$this->server->settings->logdrain_newrelic_base_uri = $this->logDrainNewRelicBaseUri;
|
||||
$this->server->settings->logdrain_axiom_dataset_name = $this->logDrainAxiomDatasetName;
|
||||
$this->server->settings->logdrain_axiom_api_key = $this->logDrainAxiomApiKey;
|
||||
$this->server->settings->logdrain_custom_config = $this->logDrainCustomConfig;
|
||||
$this->server->settings->logdrain_custom_config_parser = $this->logDrainCustomConfigParser;
|
||||
|
||||
if ($type === 'newrelic') {
|
||||
$this->syncDataNewRelic($toModel);
|
||||
} elseif ($type === 'axiom') {
|
||||
$this->syncDataAxiom($toModel);
|
||||
} elseif ($type === 'custom') {
|
||||
$this->syncDataCustom($toModel);
|
||||
} else {
|
||||
$this->syncDataNewRelic($toModel);
|
||||
$this->syncDataAxiom($toModel);
|
||||
$this->syncDataCustom($toModel);
|
||||
}
|
||||
$this->server->settings->save();
|
||||
} else {
|
||||
$this->isLogDrainNewRelicEnabled = $this->server->settings->is_logdrain_newrelic_enabled;
|
||||
$this->isLogDrainAxiomEnabled = $this->server->settings->is_logdrain_axiom_enabled;
|
||||
$this->isLogDrainCustomEnabled = $this->server->settings->is_logdrain_custom_enabled;
|
||||
|
||||
$this->logDrainNewRelicLicenseKey = $this->server->settings->logdrain_newrelic_license_key;
|
||||
$this->logDrainNewRelicBaseUri = $this->server->settings->logdrain_newrelic_base_uri;
|
||||
$this->logDrainAxiomDatasetName = $this->server->settings->logdrain_axiom_dataset_name;
|
||||
$this->logDrainAxiomApiKey = $this->server->settings->logdrain_axiom_api_key;
|
||||
$this->logDrainCustomConfig = $this->server->settings->logdrain_custom_config;
|
||||
$this->logDrainCustomConfigParser = $this->server->settings->logdrain_custom_config_parser;
|
||||
if ($type === 'newrelic') {
|
||||
$this->syncDataNewRelic($toModel);
|
||||
} elseif ($type === 'axiom') {
|
||||
$this->syncDataAxiom($toModel);
|
||||
} elseif ($type === 'custom') {
|
||||
$this->syncDataCustom($toModel);
|
||||
} else {
|
||||
$this->syncDataNewRelic($toModel);
|
||||
$this->syncDataAxiom($toModel);
|
||||
$this->syncDataCustom($toModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +176,7 @@ class LogDrains extends Component
|
||||
public function submit(string $type)
|
||||
{
|
||||
try {
|
||||
$this->syncData(true);
|
||||
$this->syncData(true, $type);
|
||||
$this->dispatch('success', 'Settings saved.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace App\Livewire\Server;
|
||||
use App\Actions\Server\StartSentinel;
|
||||
use App\Actions\Server\StopSentinel;
|
||||
use App\Models\Server;
|
||||
use Livewire\Attributes\Locked;
|
||||
use Livewire\Attributes\Computed;
|
||||
use Livewire\Attributes\Validate;
|
||||
use Livewire\Component;
|
||||
|
||||
@@ -79,9 +79,6 @@ class Show extends Component
|
||||
#[Validate(['required'])]
|
||||
public string $serverTimezone;
|
||||
|
||||
#[Locked]
|
||||
public array $timezones;
|
||||
|
||||
public function getListeners()
|
||||
{
|
||||
$teamId = auth()->user()->currentTeam()->id;
|
||||
@@ -96,13 +93,21 @@ class Show extends Component
|
||||
{
|
||||
try {
|
||||
$this->server = Server::ownedByCurrentTeam()->whereUuid($server_uuid)->firstOrFail();
|
||||
$this->timezones = collect(timezone_identifiers_list())->sort()->values()->toArray();
|
||||
$this->syncData();
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
#[Computed]
|
||||
public function timezones(): array
|
||||
{
|
||||
return collect(timezone_identifiers_list())
|
||||
->sort()
|
||||
->values()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public function syncData(bool $toModel = false)
|
||||
{
|
||||
if ($toModel) {
|
||||
|
||||
@@ -7,7 +7,7 @@ use App\Models\InstanceSettings;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Livewire\Attributes\Locked;
|
||||
use Livewire\Attributes\Computed;
|
||||
use Livewire\Attributes\Validate;
|
||||
use Livewire\Component;
|
||||
|
||||
@@ -17,9 +17,6 @@ class Index extends Component
|
||||
|
||||
protected Server $server;
|
||||
|
||||
#[Locked]
|
||||
public $timezones;
|
||||
|
||||
#[Validate('boolean')]
|
||||
public bool $is_auto_update_enabled;
|
||||
|
||||
@@ -53,7 +50,7 @@ class Index extends Component
|
||||
#[Validate('string')]
|
||||
public string $auto_update_frequency;
|
||||
|
||||
#[Validate('string')]
|
||||
#[Validate('string|required')]
|
||||
public string $update_check_frequency;
|
||||
|
||||
#[Validate('required|string|timezone')]
|
||||
@@ -101,14 +98,29 @@ class Index extends Component
|
||||
$this->is_api_enabled = $this->settings->is_api_enabled;
|
||||
$this->auto_update_frequency = $this->settings->auto_update_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->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)
|
||||
{
|
||||
$this->validate();
|
||||
if ($this->settings->is_auto_update_enabled === true) {
|
||||
$this->validate([
|
||||
'auto_update_frequency' => ['required', 'string'],
|
||||
]);
|
||||
}
|
||||
|
||||
$this->settings->fqdn = $this->fqdn;
|
||||
$this->settings->resale_license = $this->resale_license;
|
||||
$this->settings->public_port_min = $this->public_port_min;
|
||||
|
||||
@@ -19,7 +19,7 @@ class SettingsEmail extends Component
|
||||
#[Validate(['nullable', 'numeric', 'min:1', 'max:65535'])]
|
||||
public ?int $smtpPort = null;
|
||||
|
||||
#[Validate(['nullable', 'string'])]
|
||||
#[Validate(['nullable', 'string', 'in:tls,ssl,none'])]
|
||||
public ?string $smtpEncryption = null;
|
||||
|
||||
#[Validate(['nullable', 'string'])]
|
||||
|
||||
@@ -4,6 +4,11 @@ namespace App\Livewire\Source\Github;
|
||||
|
||||
use App\Jobs\GithubAppPermissionJob;
|
||||
use App\Models\GithubApp;
|
||||
use App\Models\PrivateKey;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Lcobucci\JWT\Configuration;
|
||||
use Lcobucci\JWT\Signer\Key\InMemory;
|
||||
use Lcobucci\JWT\Signer\Rsa\Sha256;
|
||||
use Livewire\Component;
|
||||
|
||||
class Change extends Component
|
||||
@@ -51,12 +56,20 @@ class Change extends Component
|
||||
'github_app.administration' => 'nullable|string',
|
||||
];
|
||||
|
||||
public function boot()
|
||||
{
|
||||
if ($this->github_app) {
|
||||
$this->github_app->makeVisible(['client_secret', 'webhook_secret']);
|
||||
}
|
||||
}
|
||||
|
||||
public function checkPermissions()
|
||||
{
|
||||
GithubAppPermissionJob::dispatchSync($this->github_app);
|
||||
$this->github_app->refresh()->makeVisible('client_secret')->makeVisible('webhook_secret');
|
||||
$this->dispatch('success', 'Github App permissions updated.');
|
||||
}
|
||||
|
||||
// public function check()
|
||||
// {
|
||||
|
||||
@@ -90,15 +103,16 @@ class Change extends Component
|
||||
|
||||
// ray($runners_by_repository);
|
||||
// }
|
||||
|
||||
public function mount()
|
||||
{
|
||||
try {
|
||||
$github_app_uuid = request()->github_app_uuid;
|
||||
$this->github_app = GithubApp::ownedByCurrentTeam()->whereUuid($github_app_uuid)->firstOrFail();
|
||||
$this->github_app->makeVisible(['client_secret', 'webhook_secret']);
|
||||
|
||||
$this->applications = $this->github_app->applications;
|
||||
$settings = instanceSettings();
|
||||
$this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret');
|
||||
|
||||
$this->name = str($this->github_app->name)->kebab();
|
||||
$this->fqdn = $settings->fqdn;
|
||||
@@ -142,6 +156,77 @@ class Change extends Component
|
||||
}
|
||||
}
|
||||
|
||||
public function getGithubAppNameUpdatePath()
|
||||
{
|
||||
if (str($this->github_app->organization)->isNotEmpty()) {
|
||||
return "{$this->github_app->html_url}/organizations/{$this->github_app->organization}/settings/apps/{$this->github_app->name}";
|
||||
}
|
||||
|
||||
return "{$this->github_app->html_url}/settings/apps/{$this->github_app->name}";
|
||||
}
|
||||
|
||||
private function generateGithubJwt($private_key, $app_id): string
|
||||
{
|
||||
$configuration = Configuration::forAsymmetricSigner(
|
||||
new Sha256,
|
||||
InMemory::plainText($private_key),
|
||||
InMemory::plainText($private_key)
|
||||
);
|
||||
|
||||
$now = time();
|
||||
|
||||
return $configuration->builder()
|
||||
->issuedBy((string) $app_id)
|
||||
->permittedFor('https://api.github.com')
|
||||
->identifiedBy((string) $now)
|
||||
->issuedAt(new \DateTimeImmutable("@{$now}"))
|
||||
->expiresAt(new \DateTimeImmutable('@'.($now + 600)))
|
||||
->getToken($configuration->signer(), $configuration->signingKey())
|
||||
->toString();
|
||||
}
|
||||
|
||||
public function updateGithubAppName()
|
||||
{
|
||||
try {
|
||||
$privateKey = PrivateKey::ownedByCurrentTeam()->find($this->github_app->private_key_id);
|
||||
|
||||
if (! $privateKey) {
|
||||
$this->dispatch('error', 'No private key found for this GitHub App.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$jwt = $this->generateGithubJwt($privateKey->private_key, $this->github_app->app_id);
|
||||
|
||||
$response = Http::withHeaders([
|
||||
'Accept' => 'application/vnd.github+json',
|
||||
'X-GitHub-Api-Version' => '2022-11-28',
|
||||
'Authorization' => "Bearer {$jwt}",
|
||||
])->get("{$this->github_app->api_url}/app");
|
||||
|
||||
if ($response->successful()) {
|
||||
$app_data = $response->json();
|
||||
$app_slug = $app_data['slug'] ?? null;
|
||||
|
||||
if ($app_slug) {
|
||||
$this->github_app->name = $app_slug;
|
||||
$this->name = str($app_slug)->kebab();
|
||||
$privateKey->name = "github-app-{$app_slug}";
|
||||
$privateKey->save();
|
||||
$this->github_app->save();
|
||||
$this->dispatch('success', 'GitHub App name and SSH key name synchronized successfully.');
|
||||
} else {
|
||||
$this->dispatch('info', 'Could not find App Name (slug) in GitHub response.');
|
||||
}
|
||||
} else {
|
||||
$error_message = $response->json()['message'] ?? 'Unknown error';
|
||||
$this->dispatch('error', "Failed to fetch GitHub App information: {$error_message}");
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Models;
|
||||
|
||||
use App\Enums\ApplicationDeploymentStatus;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Process\InvokedProcess;
|
||||
@@ -104,7 +105,7 @@ use Visus\Cuid2\Cuid2;
|
||||
|
||||
class Application extends BaseModel
|
||||
{
|
||||
use SoftDeletes;
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
private static $parserVersion = '4';
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
@@ -18,4 +19,18 @@ abstract class BaseModel extends Model
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function name(): Attribute
|
||||
{
|
||||
return new Attribute(
|
||||
get: fn () => sanitize_string($this->getRawOriginal('name')),
|
||||
);
|
||||
}
|
||||
|
||||
public function image(): Attribute
|
||||
{
|
||||
return new Attribute(
|
||||
get: fn () => sanitize_string($this->getRawOriginal('image')),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,7 @@ class EnvironmentVariable extends Model
|
||||
}
|
||||
}
|
||||
$environment_variable->update([
|
||||
'version' => config('version'),
|
||||
'version' => config('constants.coolify.version'),
|
||||
]);
|
||||
});
|
||||
static::saving(function (EnvironmentVariable $environmentVariable) {
|
||||
|
||||
+11
-14
@@ -11,6 +11,7 @@ use App\Notifications\Server\Reachable;
|
||||
use App\Notifications\Server\Unreachable;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
@@ -42,14 +43,13 @@ use Symfony\Component\Yaml\Yaml;
|
||||
'validation_logs' => ['type' => 'string', 'description' => 'The validation logs.'],
|
||||
'log_drain_notification_sent' => ['type' => 'boolean', 'description' => 'The flag to indicate if the log drain notification has been sent.'],
|
||||
'swarm_cluster' => ['type' => 'string', 'description' => 'The swarm cluster configuration.'],
|
||||
'delete_unused_volumes' => ['type' => 'boolean', 'description' => 'The flag to indicate if the unused volumes should be deleted.'],
|
||||
'delete_unused_networks' => ['type' => 'boolean', 'description' => 'The flag to indicate if the unused networks should be deleted.'],
|
||||
'settings' => ['$ref' => '#/components/schemas/ServerSetting'],
|
||||
]
|
||||
)]
|
||||
|
||||
class Server extends BaseModel
|
||||
{
|
||||
use SchemalessAttributesTrait, SoftDeletes;
|
||||
use HasFactory, SchemalessAttributesTrait, SoftDeletes;
|
||||
|
||||
public static $batch_counter = 0;
|
||||
|
||||
@@ -606,7 +606,8 @@ $schema://$host {
|
||||
}
|
||||
$memory = json_decode($memory, true);
|
||||
$parsedCollection = collect($memory)->map(function ($metric) {
|
||||
return [(int) $metric['time'], (float) $metric['usedPercent']];
|
||||
$usedPercent = $metric['usedPercent'] ?? 0.0;
|
||||
return [(int) $metric['time'], (float) $usedPercent];
|
||||
});
|
||||
|
||||
return $parsedCollection->toArray();
|
||||
@@ -809,7 +810,7 @@ $schema://$host {
|
||||
{
|
||||
return Attribute::make(
|
||||
get: function ($value) {
|
||||
return preg_replace('/[^0-9]/', '', $value);
|
||||
return (int) preg_replace('/[^0-9]/', '', $value);
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -983,7 +984,7 @@ $schema://$host {
|
||||
|
||||
public function status(): bool
|
||||
{
|
||||
['uptime' => $uptime] = $this->validateConnection(false);
|
||||
['uptime' => $uptime] = $this->validateConnection();
|
||||
if ($uptime === false) {
|
||||
foreach ($this->applications() as $application) {
|
||||
$application->status = 'exited';
|
||||
@@ -1035,7 +1036,7 @@ $schema://$host {
|
||||
$this->unreachable_notification_sent = false;
|
||||
$this->save();
|
||||
$this->refresh();
|
||||
$this->team->notify(new Reachable($this));
|
||||
// $this->team->notify(new Reachable($this));
|
||||
}
|
||||
|
||||
public function sendUnreachableNotification()
|
||||
@@ -1043,21 +1044,17 @@ $schema://$host {
|
||||
$this->unreachable_notification_sent = true;
|
||||
$this->save();
|
||||
$this->refresh();
|
||||
$this->team->notify(new Unreachable($this));
|
||||
// $this->team->notify(new Unreachable($this));
|
||||
}
|
||||
|
||||
public function validateConnection(bool $isManualCheck = true, bool $justCheckingNewKey = false)
|
||||
public function validateConnection(bool $justCheckingNewKey = false)
|
||||
{
|
||||
config()->set('constants.ssh.mux_enabled', ! $isManualCheck);
|
||||
config()->set('constants.ssh.mux_enabled', false);
|
||||
|
||||
if ($this->skipServer()) {
|
||||
return ['uptime' => false, 'error' => 'Server skipped.'];
|
||||
}
|
||||
try {
|
||||
// Make sure the private key is stored
|
||||
if ($this->privateKey) {
|
||||
$this->privateKey->storeInFileSystem();
|
||||
}
|
||||
instant_remote_process(['ls /'], $this);
|
||||
if ($this->settings->is_reachable === false) {
|
||||
$this->settings->is_reachable = true;
|
||||
|
||||
@@ -45,6 +45,8 @@ use OpenApi\Attributes as OA;
|
||||
'wildcard_domain' => ['type' => 'string'],
|
||||
'created_at' => ['type' => 'string'],
|
||||
'updated_at' => ['type' => 'string'],
|
||||
'delete_unused_volumes' => ['type' => 'boolean', 'description' => 'The flag to indicate if the unused volumes should be deleted.'],
|
||||
'delete_unused_networks' => ['type' => 'boolean', 'description' => 'The flag to indicate if the unused networks should be deleted.'],
|
||||
]
|
||||
)]
|
||||
class ServerSetting extends Model
|
||||
|
||||
@@ -4,18 +4,12 @@ namespace App\Notifications\Application;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationPreview;
|
||||
use App\Notifications\CustomEmailNotification;
|
||||
use App\Notifications\Dto\DiscordMessage;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class DeploymentFailed extends Notification implements ShouldQueue
|
||||
class DeploymentFailed extends CustomEmailNotification
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public $tries = 1;
|
||||
|
||||
public Application $application;
|
||||
|
||||
public ?ApplicationPreview $preview = null;
|
||||
@@ -34,6 +28,7 @@ class DeploymentFailed extends Notification implements ShouldQueue
|
||||
|
||||
public function __construct(Application $application, string $deployment_uuid, ?ApplicationPreview $preview = null)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
$this->application = $application;
|
||||
$this->deployment_uuid = $deployment_uuid;
|
||||
$this->preview = $preview;
|
||||
|
||||
@@ -4,18 +4,12 @@ namespace App\Notifications\Application;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationPreview;
|
||||
use App\Notifications\CustomEmailNotification;
|
||||
use App\Notifications\Dto\DiscordMessage;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class DeploymentSuccess extends Notification implements ShouldQueue
|
||||
class DeploymentSuccess extends CustomEmailNotification
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public $tries = 1;
|
||||
|
||||
public Application $application;
|
||||
|
||||
public ?ApplicationPreview $preview = null;
|
||||
@@ -34,6 +28,7 @@ class DeploymentSuccess extends Notification implements ShouldQueue
|
||||
|
||||
public function __construct(Application $application, string $deployment_uuid, ?ApplicationPreview $preview = null)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
$this->application = $application;
|
||||
$this->deployment_uuid = $deployment_uuid;
|
||||
$this->preview = $preview;
|
||||
|
||||
@@ -3,18 +3,12 @@
|
||||
namespace App\Notifications\Application;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Notifications\CustomEmailNotification;
|
||||
use App\Notifications\Dto\DiscordMessage;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class StatusChanged extends Notification implements ShouldQueue
|
||||
class StatusChanged extends CustomEmailNotification
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public $tries = 1;
|
||||
|
||||
public string $resource_name;
|
||||
|
||||
public string $project_uuid;
|
||||
@@ -27,6 +21,7 @@ class StatusChanged extends Notification implements ShouldQueue
|
||||
|
||||
public function __construct(public Application $resource)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
$this->resource_name = data_get($resource, 'name');
|
||||
$this->project_uuid = data_get($resource, 'environment.project.uuid');
|
||||
$this->environment_name = data_get($resource, 'environment.name');
|
||||
|
||||
@@ -17,6 +17,6 @@ class DiscordChannel
|
||||
if (! $webhookUrl) {
|
||||
return;
|
||||
}
|
||||
dispatch(new SendMessageToDiscordJob($message, $webhookUrl))->onQueue('high');
|
||||
SendMessageToDiscordJob::dispatch($message, $webhookUrl);
|
||||
}
|
||||
}
|
||||
@@ -66,11 +66,12 @@ class EmailChannel
|
||||
'transport' => 'smtp',
|
||||
'host' => data_get($notifiable, 'smtp_host'),
|
||||
'port' => data_get($notifiable, 'smtp_port'),
|
||||
'encryption' => data_get($notifiable, 'smtp_encryption'),
|
||||
'encryption' => data_get($notifiable, 'smtp_encryption') === 'none' ? null : data_get($notifiable, 'smtp_encryption'),
|
||||
'username' => data_get($notifiable, 'smtp_username'),
|
||||
'password' => data_get($notifiable, 'smtp_password'),
|
||||
'timeout' => data_get($notifiable, 'smtp_timeout'),
|
||||
'local_domain' => null,
|
||||
'auto_tls' => data_get($notifiable, 'smtp_encryption') === 'none' ? '0' : '',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,6 @@ class TelegramChannel
|
||||
if (! $telegramToken || ! $chatId || ! $message) {
|
||||
return;
|
||||
}
|
||||
dispatch(new SendMessageToTelegramJob($message, $buttons, $telegramToken, $chatId, $topicId))->onQueue('high');
|
||||
SendMessageToTelegramJob::dispatch($message, $buttons, $telegramToken, $chatId, $topicId);
|
||||
}
|
||||
}
|
||||
@@ -3,19 +3,16 @@
|
||||
namespace App\Notifications\Container;
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Notifications\CustomEmailNotification;
|
||||
use App\Notifications\Dto\DiscordMessage;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class ContainerRestarted extends Notification implements ShouldQueue
|
||||
class ContainerRestarted extends CustomEmailNotification
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public $tries = 1;
|
||||
|
||||
public function __construct(public string $name, public Server $server, public ?string $url = null) {}
|
||||
public function __construct(public string $name, public Server $server, public ?string $url = null)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
|
||||
@@ -3,19 +3,16 @@
|
||||
namespace App\Notifications\Container;
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Notifications\CustomEmailNotification;
|
||||
use App\Notifications\Dto\DiscordMessage;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class ContainerStopped extends Notification implements ShouldQueue
|
||||
class ContainerStopped extends CustomEmailNotification
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public $tries = 1;
|
||||
|
||||
public function __construct(public string $name, public Server $server, public ?string $url = null) {}
|
||||
public function __construct(public string $name, public Server $server, public ?string $url = null)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class CustomEmailNotification extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public $backoff = [10, 20, 30, 40, 50];
|
||||
|
||||
public $tries = 5;
|
||||
|
||||
public $maxExceptions = 5;
|
||||
}
|
||||
@@ -3,26 +3,19 @@
|
||||
namespace App\Notifications\Database;
|
||||
|
||||
use App\Models\ScheduledDatabaseBackup;
|
||||
use App\Notifications\CustomEmailNotification;
|
||||
use App\Notifications\Dto\DiscordMessage;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class BackupFailed extends Notification implements ShouldQueue
|
||||
class BackupFailed extends CustomEmailNotification
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public $backoff = 10;
|
||||
|
||||
public $tries = 2;
|
||||
|
||||
public string $name;
|
||||
|
||||
public string $frequency;
|
||||
|
||||
public function __construct(ScheduledDatabaseBackup $backup, public $database, public $output, public $database_name)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
$this->name = $database->name;
|
||||
$this->frequency = $backup->frequency;
|
||||
}
|
||||
|
||||
@@ -3,26 +3,20 @@
|
||||
namespace App\Notifications\Database;
|
||||
|
||||
use App\Models\ScheduledDatabaseBackup;
|
||||
use App\Notifications\CustomEmailNotification;
|
||||
use App\Notifications\Dto\DiscordMessage;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class BackupSuccess extends Notification implements ShouldQueue
|
||||
class BackupSuccess extends CustomEmailNotification
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public $backoff = 10;
|
||||
|
||||
public $tries = 3;
|
||||
|
||||
public string $name;
|
||||
|
||||
public string $frequency;
|
||||
|
||||
public function __construct(ScheduledDatabaseBackup $backup, public $database, public $database_name)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
|
||||
$this->name = $database->name;
|
||||
$this->frequency = $backup->frequency;
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ class DiscordMessage
|
||||
|
||||
public function toPayload(): array
|
||||
{
|
||||
$footerText = 'Coolify v'.config('version');
|
||||
$footerText = 'Coolify v'.config('constants.coolify.version');
|
||||
if (isCloud()) {
|
||||
$footerText = 'Coolify Cloud';
|
||||
}
|
||||
|
||||
@@ -15,7 +15,10 @@ class GeneralNotification extends Notification implements ShouldQueue
|
||||
|
||||
public $tries = 1;
|
||||
|
||||
public function __construct(public string $message) {}
|
||||
public function __construct(public string $message)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
|
||||
@@ -3,24 +3,17 @@
|
||||
namespace App\Notifications\ScheduledTask;
|
||||
|
||||
use App\Models\ScheduledTask;
|
||||
use App\Notifications\CustomEmailNotification;
|
||||
use App\Notifications\Dto\DiscordMessage;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class TaskFailed extends Notification implements ShouldQueue
|
||||
class TaskFailed extends CustomEmailNotification
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public $backoff = 10;
|
||||
|
||||
public $tries = 2;
|
||||
|
||||
public ?string $url = null;
|
||||
|
||||
public function __construct(public ScheduledTask $task, public string $output)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
if ($task->application) {
|
||||
$this->url = $task->application->failedTaskLink($task->uuid);
|
||||
} elseif ($task->service) {
|
||||
|
||||
@@ -5,18 +5,15 @@ namespace App\Notifications\Server;
|
||||
use App\Models\Server;
|
||||
use App\Notifications\Channels\DiscordChannel;
|
||||
use App\Notifications\Channels\TelegramChannel;
|
||||
use App\Notifications\CustomEmailNotification;
|
||||
use App\Notifications\Dto\DiscordMessage;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class DockerCleanup extends Notification implements ShouldQueue
|
||||
class DockerCleanup extends CustomEmailNotification
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public $tries = 1;
|
||||
|
||||
public function __construct(public Server $server, public string $message) {}
|
||||
public function __construct(public Server $server, public string $message)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
|
||||
@@ -6,19 +6,16 @@ use App\Models\Server;
|
||||
use App\Notifications\Channels\DiscordChannel;
|
||||
use App\Notifications\Channels\EmailChannel;
|
||||
use App\Notifications\Channels\TelegramChannel;
|
||||
use App\Notifications\CustomEmailNotification;
|
||||
use App\Notifications\Dto\DiscordMessage;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class ForceDisabled extends Notification implements ShouldQueue
|
||||
class ForceDisabled extends CustomEmailNotification
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public $tries = 1;
|
||||
|
||||
public function __construct(public Server $server) {}
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
|
||||
@@ -6,19 +6,16 @@ use App\Models\Server;
|
||||
use App\Notifications\Channels\DiscordChannel;
|
||||
use App\Notifications\Channels\EmailChannel;
|
||||
use App\Notifications\Channels\TelegramChannel;
|
||||
use App\Notifications\CustomEmailNotification;
|
||||
use App\Notifications\Dto\DiscordMessage;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class ForceEnabled extends Notification implements ShouldQueue
|
||||
class ForceEnabled extends CustomEmailNotification
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public $tries = 1;
|
||||
|
||||
public function __construct(public Server $server) {}
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
|
||||
@@ -3,19 +3,16 @@
|
||||
namespace App\Notifications\Server;
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Notifications\CustomEmailNotification;
|
||||
use App\Notifications\Dto\DiscordMessage;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class HighDiskUsage extends Notification implements ShouldQueue
|
||||
class HighDiskUsage extends CustomEmailNotification
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public $tries = 1;
|
||||
|
||||
public function __construct(public Server $server, public int $disk_usage, public int $server_disk_usage_notification_threshold) {}
|
||||
public function __construct(public Server $server, public int $disk_usage, public int $server_disk_usage_notification_threshold)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
|
||||
@@ -6,22 +6,17 @@ use App\Models\Server;
|
||||
use App\Notifications\Channels\DiscordChannel;
|
||||
use App\Notifications\Channels\EmailChannel;
|
||||
use App\Notifications\Channels\TelegramChannel;
|
||||
use App\Notifications\CustomEmailNotification;
|
||||
use App\Notifications\Dto\DiscordMessage;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class Reachable extends Notification implements ShouldQueue
|
||||
class Reachable extends CustomEmailNotification
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public $tries = 1;
|
||||
|
||||
protected bool $isRateLimited = false;
|
||||
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
$this->isRateLimited = isEmailRateLimited(
|
||||
limiterKey: 'server-reachable:'.$this->server->id,
|
||||
);
|
||||
|
||||
@@ -6,22 +6,17 @@ use App\Models\Server;
|
||||
use App\Notifications\Channels\DiscordChannel;
|
||||
use App\Notifications\Channels\EmailChannel;
|
||||
use App\Notifications\Channels\TelegramChannel;
|
||||
use App\Notifications\CustomEmailNotification;
|
||||
use App\Notifications\Dto\DiscordMessage;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class Unreachable extends Notification implements ShouldQueue
|
||||
class Unreachable extends CustomEmailNotification
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public $tries = 1;
|
||||
|
||||
protected bool $isRateLimited = false;
|
||||
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
$this->isRateLimited = isEmailRateLimited(
|
||||
limiterKey: 'server-unreachable:'.$this->server->id,
|
||||
);
|
||||
|
||||
@@ -15,7 +15,10 @@ class Test extends Notification implements ShouldQueue
|
||||
|
||||
public $tries = 5;
|
||||
|
||||
public function __construct(public ?string $emails = null) {}
|
||||
public function __construct(public ?string $emails = null)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
|
||||
Loaded 100 of 190 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user