Error: '.$error);
return;
}
} catch (\Throwable $e) {
return handleError($e, $this);
+ } finally {
+ $this->dispatch('refreshServerShow');
+ $this->server->refresh();
}
}
-
- public function mount()
- {
- $this->parameters = get_route_parameters();
- }
}
diff --git a/app/Livewire/Settings/Index.php b/app/Livewire/Settings/Index.php
index 754f0929b..0a6c5bae6 100644
--- a/app/Livewire/Settings/Index.php
+++ b/app/Livewire/Settings/Index.php
@@ -5,62 +5,95 @@ namespace App\Livewire\Settings;
use App\Jobs\CheckForUpdatesJob;
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\Rule;
use Livewire\Component;
class Index extends Component
{
public InstanceSettings $settings;
- public bool $do_not_track;
-
- public bool $is_auto_update_enabled;
-
- public bool $is_registration_enabled;
-
- public bool $is_dns_validation_enabled;
-
- public bool $is_api_enabled;
-
- public string $auto_update_frequency;
-
- public string $update_check_frequency;
-
- protected string $dynamic_config_path = '/data/coolify/proxy/dynamic';
-
protected Server $server;
- protected $rules = [
- 'settings.fqdn' => 'nullable',
- 'settings.resale_license' => 'nullable',
- 'settings.public_port_min' => 'required',
- 'settings.public_port_max' => 'required',
- 'settings.custom_dns_servers' => 'nullable',
- 'settings.instance_name' => 'nullable',
- 'settings.allowed_ips' => 'nullable',
- 'settings.is_auto_update_enabled' => 'boolean',
- 'auto_update_frequency' => 'string',
- 'update_check_frequency' => 'string',
- 'settings.instance_timezone' => 'required|string|timezone',
- ];
-
- protected $validationAttributes = [
- 'settings.fqdn' => 'FQDN',
- 'settings.resale_license' => 'Resale License',
- 'settings.public_port_min' => 'Public port min',
- 'settings.public_port_max' => 'Public port max',
- 'settings.custom_dns_servers' => 'Custom DNS servers',
- 'settings.allowed_ips' => 'Allowed IPs',
- 'settings.is_auto_update_enabled' => 'Auto Update Enabled',
- 'auto_update_frequency' => 'Auto Update Frequency',
- 'update_check_frequency' => 'Update Check Frequency',
- ];
-
+ #[Locked]
public $timezones;
+ #[Rule('boolean')]
+ public bool $is_auto_update_enabled;
+
+ #[Rule('nullable|string|max:255')]
+ public ?string $fqdn = null;
+
+ #[Rule('nullable|string|max:255')]
+ public ?string $resale_license = null;
+
+ #[Rule('required|integer|min:1025|max:65535')]
+ public int $public_port_min;
+
+ #[Rule('required|integer|min:1025|max:65535')]
+ public int $public_port_max;
+
+ #[Rule('nullable|string')]
+ public ?string $custom_dns_servers = null;
+
+ #[Rule('nullable|string|max:255')]
+ public ?string $instance_name = null;
+
+ #[Rule('nullable|string')]
+ public ?string $allowed_ips = null;
+
+ #[Rule('nullable|string')]
+ public ?string $public_ipv4 = null;
+
+ #[Rule('nullable|string')]
+ public ?string $public_ipv6 = null;
+
+ #[Rule('string')]
+ public string $auto_update_frequency;
+
+ #[Rule('string')]
+ public string $update_check_frequency;
+
+ #[Rule('required|string|timezone')]
+ public string $instance_timezone;
+
+ #[Rule('boolean')]
+ public bool $do_not_track;
+
+ #[Rule('boolean')]
+ public bool $is_registration_enabled;
+
+ #[Rule('boolean')]
+ public bool $is_dns_validation_enabled;
+
+ #[Rule('boolean')]
+ public bool $is_api_enabled;
+
+ #[Rule('boolean')]
+ public bool $disable_two_step_confirmation;
+
+ public function render()
+ {
+ return view('livewire.settings.index');
+ }
+
public function mount()
{
- if (isInstanceAdmin()) {
+ if (! isInstanceAdmin()) {
+ return redirect()->route('dashboard');
+ } else {
$this->settings = instanceSettings();
+ $this->fqdn = $this->settings->fqdn;
+ $this->resale_license = $this->settings->resale_license;
+ $this->public_port_min = $this->settings->public_port_min;
+ $this->public_port_max = $this->settings->public_port_max;
+ $this->custom_dns_servers = $this->settings->custom_dns_servers;
+ $this->instance_name = $this->settings->instance_name;
+ $this->allowed_ips = $this->settings->allowed_ips;
+ $this->public_ipv4 = $this->settings->public_ipv4;
+ $this->public_ipv6 = $this->settings->public_ipv6;
$this->do_not_track = $this->settings->do_not_track;
$this->is_auto_update_enabled = $this->settings->is_auto_update_enabled;
$this->is_registration_enabled = $this->settings->is_registration_enabled;
@@ -69,13 +102,22 @@ class Index extends Component
$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();
- } else {
- return redirect()->route('dashboard');
+ $this->instance_timezone = $this->settings->instance_timezone;
+ $this->disable_two_step_confirmation = $this->settings->disable_two_step_confirmation;
}
}
- public function instantSave()
+ public function instantSave($isSave = true)
{
+ $this->settings->fqdn = $this->fqdn;
+ $this->settings->resale_license = $this->resale_license;
+ $this->settings->public_port_min = $this->public_port_min;
+ $this->settings->public_port_max = $this->public_port_max;
+ $this->settings->custom_dns_servers = $this->custom_dns_servers;
+ $this->settings->instance_name = $this->instance_name;
+ $this->settings->allowed_ips = $this->allowed_ips;
+ $this->settings->public_ipv4 = $this->public_ipv4;
+ $this->settings->public_ipv6 = $this->public_ipv6;
$this->settings->do_not_track = $this->do_not_track;
$this->settings->is_auto_update_enabled = $this->is_auto_update_enabled;
$this->settings->is_registration_enabled = $this->is_registration_enabled;
@@ -83,8 +125,12 @@ class Index extends Component
$this->settings->is_api_enabled = $this->is_api_enabled;
$this->settings->auto_update_frequency = $this->auto_update_frequency;
$this->settings->update_check_frequency = $this->update_check_frequency;
- $this->settings->save();
- $this->dispatch('success', 'Settings updated!');
+ $this->settings->disable_two_step_confirmation = $this->disable_two_step_confirmation;
+ $this->settings->instance_timezone = $this->instance_timezone;
+ if ($isSave) {
+ $this->settings->save();
+ $this->dispatch('success', 'Settings updated!');
+ }
}
public function submit()
@@ -141,13 +187,8 @@ class Index extends Component
$this->settings->allowed_ips = $this->settings->allowed_ips->unique();
$this->settings->allowed_ips = $this->settings->allowed_ips->implode(',');
- $this->settings->do_not_track = $this->do_not_track;
- $this->settings->is_auto_update_enabled = $this->is_auto_update_enabled;
- $this->settings->is_registration_enabled = $this->is_registration_enabled;
- $this->settings->is_dns_validation_enabled = $this->is_dns_validation_enabled;
- $this->settings->is_api_enabled = $this->is_api_enabled;
- $this->settings->auto_update_frequency = $this->auto_update_frequency;
- $this->settings->update_check_frequency = $this->update_check_frequency;
+ $this->instantSave(isSave: false);
+
$this->settings->save();
$this->server->setupDynamicProxyConfiguration();
if (! $error_show) {
@@ -170,15 +211,16 @@ class Index extends Component
}
}
- public function updatedSettingsInstanceTimezone($value)
+ public function toggleTwoStepConfirmation($password)
{
- $this->settings->instance_timezone = $value;
- $this->settings->save();
- $this->dispatch('success', 'Instance timezone updated.');
- }
+ if (! Hash::check($password, Auth::user()->password)) {
+ $this->addError('password', 'The provided password is incorrect.');
- public function render()
- {
- return view('livewire.settings.index');
+ return;
+ }
+
+ $this->settings->disable_two_step_confirmation = $this->disable_two_step_confirmation = true;
+ $this->settings->save();
+ $this->dispatch('success', 'Two step confirmation has been disabled.');
}
}
diff --git a/app/Livewire/Settings/License.php b/app/Livewire/Settings/License.php
index ca0c9c1ae..8cb7aad8f 100644
--- a/app/Livewire/Settings/License.php
+++ b/app/Livewire/Settings/License.php
@@ -28,6 +28,9 @@ class License extends Component
if (! isCloud()) {
abort(404);
}
+ if (! isInstanceAdmin()) {
+ return redirect()->route('home');
+ }
$this->instance_id = config('app.id');
$this->settings = instanceSettings();
}
diff --git a/app/Livewire/SettingsBackup.php b/app/Livewire/SettingsBackup.php
index 9240aa96d..47423a3f9 100644
--- a/app/Livewire/SettingsBackup.php
+++ b/app/Livewire/SettingsBackup.php
@@ -2,50 +2,59 @@
namespace App\Livewire;
-use App\Jobs\DatabaseBackupJob;
use App\Models\InstanceSettings;
use App\Models\S3Storage;
use App\Models\ScheduledDatabaseBackup;
use App\Models\Server;
use App\Models\StandalonePostgresql;
+use Livewire\Attributes\Locked;
+use Livewire\Attributes\Rule;
use Livewire\Component;
class SettingsBackup extends Component
{
public InstanceSettings $settings;
- public $s3s;
-
public ?StandalonePostgresql $database = null;
public ScheduledDatabaseBackup|null|array $backup = [];
+ #[Locked]
+ public $s3s;
+
+ #[Locked]
public $executions = [];
- protected $rules = [
- 'database.uuid' => 'required',
- 'database.name' => 'required',
- 'database.description' => 'nullable',
- 'database.postgres_user' => 'required',
- 'database.postgres_password' => 'required',
+ #[Rule(['required'])]
+ public string $uuid;
- ];
+ #[Rule(['required'])]
+ public string $name;
- protected $validationAttributes = [
- 'database.uuid' => 'uuid',
- 'database.name' => 'name',
- 'database.description' => 'description',
- 'database.postgres_user' => 'postgres user',
- 'database.postgres_password' => 'postgres password',
- ];
+ #[Rule(['nullable'])]
+ public ?string $description = null;
+
+ #[Rule(['required'])]
+ public string $postgres_user;
+
+ #[Rule(['required'])]
+ public string $postgres_password;
public function mount()
{
- if (isInstanceAdmin()) {
+ if (! isInstanceAdmin()) {
+ return redirect()->route('dashboard');
+ } else {
$settings = instanceSettings();
$this->database = StandalonePostgresql::whereName('coolify-db')->first();
$s3s = S3Storage::whereTeamId(0)->get() ?? [];
if ($this->database) {
+ $this->uuid = $this->database->uuid;
+ $this->name = $this->database->name;
+ $this->description = $this->database->description;
+ $this->postgres_user = $this->database->postgres_user;
+ $this->postgres_password = $this->database->postgres_password;
+
if ($this->database->status !== 'running') {
$this->database->status = 'running';
$this->database->save();
@@ -55,13 +64,10 @@ class SettingsBackup extends Component
}
$this->settings = $settings;
$this->s3s = $s3s;
-
- } else {
- return redirect()->route('dashboard');
}
}
- public function add_coolify_database()
+ public function addCoolifyDatabase()
{
try {
$server = Server::findOrFail(0);
@@ -98,16 +104,14 @@ class SettingsBackup extends Component
}
}
- public function backup_now()
- {
- dispatch(new DatabaseBackupJob(
- backup: $this->backup
- ));
- $this->dispatch('success', 'Backup queued. It will be available in a few minutes.');
- }
-
public function submit()
{
+ $this->database->update([
+ 'name' => $this->name,
+ 'description' => $this->description,
+ 'postgres_user' => $this->postgres_user,
+ 'postgres_password' => $this->postgres_password,
+ ]);
$this->dispatch('success', 'Backup updated.');
}
}
diff --git a/app/Livewire/SettingsEmail.php b/app/Livewire/SettingsEmail.php
index 4515df9a7..2b515bf68 100644
--- a/app/Livewire/SettingsEmail.php
+++ b/app/Livewire/SettingsEmail.php
@@ -3,7 +3,6 @@
namespace App\Livewire;
use App\Models\InstanceSettings;
-use App\Notifications\TransactionalEmails\Test;
use Livewire\Component;
class SettingsEmail extends Component
@@ -124,10 +123,4 @@ class SettingsEmail extends Component
return handleError($e, $this);
}
}
-
- public function sendTestNotification()
- {
- $this->settings?->notify(new Test($this->emails));
- $this->dispatch('success', 'Test email sent.');
- }
}
diff --git a/app/Livewire/SettingsOauth.php b/app/Livewire/SettingsOauth.php
index c3884589f..17b3b89a3 100644
--- a/app/Livewire/SettingsOauth.php
+++ b/app/Livewire/SettingsOauth.php
@@ -24,6 +24,9 @@ class SettingsOauth extends Component
public function mount()
{
+ if (! isInstanceAdmin()) {
+ return redirect()->route('home');
+ }
$this->oauth_settings_map = OauthSetting::all()->sortBy('provider')->reduce(function ($carry, $setting) {
$carry[$setting->provider] = $setting;
diff --git a/app/Livewire/Source/Github/Change.php b/app/Livewire/Source/Github/Change.php
index 193b650ff..7df6f968f 100644
--- a/app/Livewire/Source/Github/Change.php
+++ b/app/Livewire/Source/Github/Change.php
@@ -93,52 +93,55 @@ class Change extends Component
// }
public function mount()
{
- $github_app_uuid = request()->github_app_uuid;
- $this->github_app = GithubApp::where('uuid', $github_app_uuid)->first();
- if (! $this->github_app) {
- return redirect()->route('source.all');
- }
- $this->applications = $this->github_app->applications;
- $settings = instanceSettings();
- $this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret');
+ try {
+ $github_app_uuid = request()->github_app_uuid;
+ $this->github_app = GithubApp::ownedByCurrentTeam()->whereUuid($github_app_uuid)->firstOrFail();
- $this->name = str($this->github_app->name)->kebab();
- $this->fqdn = $settings->fqdn;
+ $this->applications = $this->github_app->applications;
+ $settings = instanceSettings();
+ $this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret');
- if ($settings->public_ipv4) {
- $this->ipv4 = 'http://'.$settings->public_ipv4.':'.config('app.port');
- }
- if ($settings->public_ipv6) {
- $this->ipv6 = 'http://'.$settings->public_ipv6.':'.config('app.port');
- }
- if ($this->github_app->installation_id && session('from')) {
- $source_id = data_get(session('from'), 'source_id');
- if (! $source_id || $this->github_app->id !== $source_id) {
- session()->forget('from');
- } else {
- $parameters = data_get(session('from'), 'parameters');
- $back = data_get(session('from'), 'back');
- $environment_name = data_get($parameters, 'environment_name');
- $project_uuid = data_get($parameters, 'project_uuid');
- $type = data_get($parameters, 'type');
- $destination = data_get($parameters, 'destination');
- session()->forget('from');
+ $this->name = str($this->github_app->name)->kebab();
+ $this->fqdn = $settings->fqdn;
- return redirect()->route($back, [
- 'environment_name' => $environment_name,
- 'project_uuid' => $project_uuid,
- 'type' => $type,
- 'destination' => $destination,
- ]);
+ if ($settings->public_ipv4) {
+ $this->ipv4 = 'http://'.$settings->public_ipv4.':'.config('app.port');
}
+ if ($settings->public_ipv6) {
+ $this->ipv6 = 'http://'.$settings->public_ipv6.':'.config('app.port');
+ }
+ if ($this->github_app->installation_id && session('from')) {
+ $source_id = data_get(session('from'), 'source_id');
+ if (! $source_id || $this->github_app->id !== $source_id) {
+ session()->forget('from');
+ } else {
+ $parameters = data_get(session('from'), 'parameters');
+ $back = data_get(session('from'), 'back');
+ $environment_name = data_get($parameters, 'environment_name');
+ $project_uuid = data_get($parameters, 'project_uuid');
+ $type = data_get($parameters, 'type');
+ $destination = data_get($parameters, 'destination');
+ session()->forget('from');
+
+ return redirect()->route($back, [
+ 'environment_name' => $environment_name,
+ 'project_uuid' => $project_uuid,
+ 'type' => $type,
+ 'destination' => $destination,
+ ]);
+ }
+ }
+ $this->parameters = get_route_parameters();
+ if (isCloud() && ! isDev()) {
+ $this->webhook_endpoint = config('app.url');
+ } else {
+ $this->webhook_endpoint = $this->ipv4;
+ $this->is_system_wide = $this->github_app->is_system_wide;
+ }
+ } catch (\Throwable $e) {
+ return handleError($e, $this);
}
- $this->parameters = get_route_parameters();
- if (isCloud() && ! isDev()) {
- $this->webhook_endpoint = config('app.url');
- } else {
- $this->webhook_endpoint = $this->ipv4;
- $this->is_system_wide = $this->github_app->is_system_wide;
- }
+
}
public function submit()
diff --git a/app/Livewire/Source/Github/Create.php b/app/Livewire/Source/Github/Create.php
index f85e8646e..103c5c9fb 100644
--- a/app/Livewire/Source/Github/Create.php
+++ b/app/Livewire/Source/Github/Create.php
@@ -23,7 +23,7 @@ class Create extends Component
public function mount()
{
- $this->name = generate_random_name();
+ $this->name = substr(generate_random_name(), 0, 34); // GitHub Apps names can only be 34 characters long
}
public function createGitHubApp()
diff --git a/app/Livewire/Tags/Deployments.php b/app/Livewire/Tags/Deployments.php
index 270aa176a..e4afa5b60 100644
--- a/app/Livewire/Tags/Deployments.php
+++ b/app/Livewire/Tags/Deployments.php
@@ -7,19 +7,19 @@ use Livewire\Component;
class Deployments extends Component
{
- public $deployments_per_tag_per_server = [];
+ public $deploymentsPerTagPerServer = [];
- public $resource_ids = [];
+ public $resourceIds = [];
public function render()
{
return view('livewire.tags.deployments');
}
- public function get_deployments()
+ public function getDeployments()
{
try {
- $this->deployments_per_tag_per_server = ApplicationDeploymentQueue::whereIn('status', ['in_progress', 'queued'])->whereIn('application_id', $this->resource_ids)->get([
+ $this->deploymentsPerTagPerServer = ApplicationDeploymentQueue::whereIn('status', ['in_progress', 'queued'])->whereIn('application_id', $this->resourceIds)->get([
'id',
'application_id',
'application_name',
@@ -29,7 +29,7 @@ class Deployments extends Component
'server_id',
'status',
])->sortBy('id')->groupBy('server_name')->toArray();
- $this->dispatch('deployments', $this->deployments_per_tag_per_server);
+ $this->dispatch('deployments', $this->deploymentsPerTagPerServer);
} catch (\Exception $e) {
return handleError($e, $this);
}
diff --git a/app/Livewire/Tags/Index.php b/app/Livewire/Tags/Index.php
index a01d00a70..642b2bded 100644
--- a/app/Livewire/Tags/Index.php
+++ b/app/Livewire/Tags/Index.php
@@ -5,9 +5,11 @@ namespace App\Livewire\Tags;
use App\Http\Controllers\Api\DeployController;
use App\Models\Tag;
use Illuminate\Support\Collection;
+use Livewire\Attributes\Title;
use Livewire\Attributes\Url;
use Livewire\Component;
+#[Title('Tags | Coolify')]
class Index extends Component
{
#[Url()]
@@ -21,33 +23,47 @@ class Index extends Component
public $webhook = null;
- public $deployments_per_tag_per_server = [];
+ public $deploymentsPerTagPerServer = [];
- protected $listeners = ['deployments' => 'update_deployments'];
+ protected $listeners = ['deployments' => 'updateDeployments'];
- public function update_deployments($deployments)
+ public function render()
{
- $this->deployments_per_tag_per_server = $deployments;
+ return view('livewire.tags.index');
}
- public function tag_updated()
+ public function mount()
+ {
+ $this->tags = Tag::ownedByCurrentTeam()->get()->unique('name')->sortBy('name');
+ if ($this->tag) {
+ $this->tagUpdated();
+ }
+ }
+
+ public function updateDeployments($deployments)
+ {
+ $this->deploymentsPerTagPerServer = $deployments;
+ }
+
+ public function tagUpdated()
{
if ($this->tag == '') {
return;
}
- $tag = $this->tags->where('name', $this->tag)->first();
+ $sanitizedTag = htmlspecialchars($this->tag, ENT_QUOTES, 'UTF-8');
+ $tag = $this->tags->where('name', $sanitizedTag)->first();
if (! $tag) {
- $this->dispatch('error', "Tag ({$this->tag}) not found.");
+ $this->dispatch('error', 'Tag ('.e($sanitizedTag).') not found.');
$this->tag = '';
return;
}
- $this->webhook = generatTagDeployWebhook($tag->name);
+ $this->webhook = generateTagDeployWebhook($tag->name);
$this->applications = $tag->applications()->get();
$this->services = $tag->services()->get();
}
- public function redeploy_all()
+ public function redeployAll()
{
try {
$this->applications->each(function ($resource) {
@@ -63,17 +79,4 @@ class Index extends Component
return handleError($e, $this);
}
}
-
- public function mount()
- {
- $this->tags = Tag::ownedByCurrentTeam()->get()->unique('name')->sortBy('name');
- if ($this->tag) {
- $this->tag_updated();
- }
- }
-
- public function render()
- {
- return view('livewire.tags.index');
- }
}
diff --git a/app/Livewire/Tags/Show.php b/app/Livewire/Tags/Show.php
index 668101edb..0dffcce57 100644
--- a/app/Livewire/Tags/Show.php
+++ b/app/Livewire/Tags/Show.php
@@ -5,8 +5,10 @@ namespace App\Livewire\Tags;
use App\Http\Controllers\Api\DeployController;
use App\Models\ApplicationDeploymentQueue;
use App\Models\Tag;
+use Livewire\Attributes\Title;
use Livewire\Component;
+#[Title('Tags | Coolify')]
class Show extends Component
{
public $tags;
@@ -28,7 +30,7 @@ class Show extends Component
if (! $tag) {
return redirect()->route('tags.index');
}
- $this->webhook = generatTagDeployWebhook($tag->name);
+ $this->webhook = generateTagDeployWebhook($tag->name);
$this->applications = $tag->applications()->get();
$this->services = $tag->services()->get();
$this->tag = $tag;
diff --git a/app/Livewire/Team/AdminView.php b/app/Livewire/Team/AdminView.php
index 3026cb297..700c30f0a 100644
--- a/app/Livewire/Team/AdminView.php
+++ b/app/Livewire/Team/AdminView.php
@@ -2,6 +2,7 @@
namespace App\Livewire\Team;
+use App\Models\InstanceSettings;
use App\Models\Team;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
@@ -77,10 +78,12 @@ class AdminView extends Component
public function delete($id, $password)
{
- if (! Hash::check($password, Auth::user()->password)) {
- $this->addError('password', 'The provided password is incorrect.');
+ if (! data_get(InstanceSettings::get(), 'disable_two_step_confirmation')) {
+ if (! Hash::check($password, Auth::user()->password)) {
+ $this->addError('password', 'The provided password is incorrect.');
- return;
+ return;
+ }
}
if (! auth()->user()->isInstanceAdmin()) {
return $this->dispatch('error', 'You are not authorized to delete users');
diff --git a/app/Livewire/Team/Invitations.php b/app/Livewire/Team/Invitations.php
index 6a32a1d16..043c4e2e8 100644
--- a/app/Livewire/Team/Invitations.php
+++ b/app/Livewire/Team/Invitations.php
@@ -13,17 +13,18 @@ class Invitations extends Component
public function deleteInvitation(int $invitation_id)
{
- $initiation_found = TeamInvitation::find($invitation_id);
- if (! $initiation_found) {
+ try {
+ $initiation_found = TeamInvitation::ownedByCurrentTeam()->findOrFail($invitation_id);
+ $initiation_found->delete();
+ $this->refreshInvitations();
+ $this->dispatch('success', 'Invitation revoked.');
+ } catch (\Exception $e) {
return $this->dispatch('error', 'Invitation not found.');
}
- $initiation_found->delete();
- $this->refreshInvitations();
- $this->dispatch('success', 'Invitation revoked.');
}
public function refreshInvitations()
{
- $this->invitations = TeamInvitation::whereTeamId(currentTeam()->id)->get();
+ $this->invitations = TeamInvitation::ownedByCurrentTeam()->get();
}
}
diff --git a/app/Livewire/Team/InviteLink.php b/app/Livewire/Team/InviteLink.php
index 6c9e405fc..25f8a1ff5 100644
--- a/app/Livewire/Team/InviteLink.php
+++ b/app/Livewire/Team/InviteLink.php
@@ -41,6 +41,9 @@ class InviteLink extends Component
{
try {
$this->validate();
+ if (auth()->user()->role() === 'admin' && $this->role === 'owner') {
+ throw new \Exception('Admins cannot invite owners.');
+ }
$member_emails = currentTeam()->members()->get()->pluck('email');
if ($member_emails->contains($this->email)) {
return handleError(livewire: $this, customErrorMessage: "$this->email is already a member of ".currentTeam()->name.'.');
diff --git a/app/Livewire/Team/Member.php b/app/Livewire/Team/Member.php
index 680cb901b..4d5cec805 100644
--- a/app/Livewire/Team/Member.php
+++ b/app/Livewire/Team/Member.php
@@ -12,29 +12,57 @@ class Member extends Component
public function makeAdmin()
{
- $this->member->teams()->updateExistingPivot(currentTeam()->id, ['role' => 'admin']);
- $this->dispatch('reloadWindow');
+ try {
+ if (! auth()->user()->isAdmin()) {
+ throw new \Exception('You are not authorized to perform this action.');
+ }
+ $this->member->teams()->updateExistingPivot(currentTeam()->id, ['role' => 'admin']);
+ $this->dispatch('reloadWindow');
+ } catch (\Exception $e) {
+ $this->dispatch('error', $e->getMessage());
+ }
}
public function makeOwner()
{
- $this->member->teams()->updateExistingPivot(currentTeam()->id, ['role' => 'owner']);
- $this->dispatch('reloadWindow');
+ try {
+ if (! auth()->user()->isOwner()) {
+ throw new \Exception('You are not authorized to perform this action.');
+ }
+ $this->member->teams()->updateExistingPivot(currentTeam()->id, ['role' => 'owner']);
+ $this->dispatch('reloadWindow');
+ } catch (\Exception $e) {
+ $this->dispatch('error', $e->getMessage());
+ }
}
public function makeReadonly()
{
- $this->member->teams()->updateExistingPivot(currentTeam()->id, ['role' => 'member']);
- $this->dispatch('reloadWindow');
+ try {
+ if (! auth()->user()->isAdmin()) {
+ throw new \Exception('You are not authorized to perform this action.');
+ }
+ $this->member->teams()->updateExistingPivot(currentTeam()->id, ['role' => 'member']);
+ $this->dispatch('reloadWindow');
+ } catch (\Exception $e) {
+ $this->dispatch('error', $e->getMessage());
+ }
}
public function remove()
{
- $this->member->teams()->detach(currentTeam());
- Cache::forget("team:{$this->member->id}");
- Cache::remember('team:'.$this->member->id, 3600, function () {
- return $this->member->teams()->first();
- });
- $this->dispatch('reloadWindow');
+ try {
+ if (! auth()->user()->isAdmin()) {
+ throw new \Exception('You are not authorized to perform this action.');
+ }
+ $this->member->teams()->detach(currentTeam());
+ Cache::forget("team:{$this->member->id}");
+ Cache::remember('team:'.$this->member->id, 3600, function () {
+ return $this->member->teams()->first();
+ });
+ $this->dispatch('reloadWindow');
+ } catch (\Exception $e) {
+ $this->dispatch('error', $e->getMessage());
+ }
}
}
diff --git a/app/Models/Application.php b/app/Models/Application.php
index b20f65201..3fae7153b 100644
--- a/app/Models/Application.php
+++ b/app/Models/Application.php
@@ -1400,13 +1400,13 @@ class Application extends BaseModel
return [];
}
- public function getMetrics(int $mins = 5)
+ public function getCpuMetrics(int $mins = 5)
{
$server = $this->destination->server;
$container_name = $this->uuid;
if ($server->isMetricsEnabled()) {
$from = now()->subMinutes($mins)->toIso8601ZuluString();
- $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/cpu/history?from=$from'"], $server, false);
if (str($metrics)->contains('error')) {
$error = json_decode($metrics, true);
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
@@ -1415,14 +1415,33 @@ class Application extends BaseModel
}
throw new \Exception($error);
}
- $metrics = str($metrics)->explode("\n")->skip(1)->all();
- $parsedCollection = collect($metrics)->flatMap(function ($item) {
- return collect(explode("\n", trim($item)))->map(function ($line) {
- [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line));
- $cpu_usage_percent = number_format($cpu_usage_percent, 2);
+ $metrics = json_decode($metrics, true);
+ $parsedCollection = collect($metrics)->map(function ($metric) {
+ return [(int) $metric['time'], (float) $metric['percent']];
+ });
- return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage];
- });
+ return $parsedCollection->toArray();
+ }
+ }
+
+ public function getMemoryMetrics(int $mins = 5)
+ {
+ $server = $this->destination->server;
+ $container_name = $this->uuid;
+ if ($server->isMetricsEnabled()) {
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/memory/history?from=$from'"], $server, false);
+ if (str($metrics)->contains('error')) {
+ $error = json_decode($metrics, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error == 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
+ }
+ throw new \Exception($error);
+ }
+ $metrics = json_decode($metrics, true);
+ $parsedCollection = collect($metrics)->map(function ($metric) {
+ return [(int) $metric['time'], (float) $metric['used']];
});
return $parsedCollection->toArray();
@@ -1459,7 +1478,7 @@ class Application extends BaseModel
return $config;
}
-
+
public function setConfig($config)
{
$validator = Validator::make(['config' => $config], [
diff --git a/app/Models/EnvironmentVariable.php b/app/Models/EnvironmentVariable.php
index 9f8e4b342..f77d73db8 100644
--- a/app/Models/EnvironmentVariable.php
+++ b/app/Models/EnvironmentVariable.php
@@ -44,7 +44,7 @@ class EnvironmentVariable extends Model
'version' => 'string',
];
- protected $appends = ['real_value', 'is_shared'];
+ protected $appends = ['real_value', 'is_shared', 'is_really_required'];
protected static function booted()
{
@@ -74,6 +74,9 @@ class EnvironmentVariable extends Model
'version' => config('version'),
]);
});
+ static::saving(function (EnvironmentVariable $environmentVariable) {
+ $environmentVariable->updateIsShared();
+ });
}
public function service()
@@ -130,6 +133,13 @@ class EnvironmentVariable extends Model
);
}
+ protected function isReallyRequired(): Attribute
+ {
+ return Attribute::make(
+ get: fn () => $this->is_required && str($this->real_value)->isEmpty(),
+ );
+ }
+
protected function isShared(): Attribute
{
return Attribute::make(
@@ -210,4 +220,11 @@ class EnvironmentVariable extends Model
set: fn (string $value) => str($value)->trim()->replace(' ', '_')->value,
);
}
+
+ protected function updateIsShared(): void
+ {
+ $type = str($this->value)->after('{{')->before('.')->value;
+ $isShared = str($this->value)->startsWith('{{'.$type) && str($this->value)->endsWith('}}');
+ $this->is_shared = $isShared;
+ }
}
diff --git a/app/Models/GithubApp.php b/app/Models/GithubApp.php
index 66ecdd967..b9564e02c 100644
--- a/app/Models/GithubApp.php
+++ b/app/Models/GithubApp.php
@@ -31,6 +31,11 @@ class GithubApp extends BaseModel
});
}
+ public static function ownedByCurrentTeam()
+ {
+ return GithubApp::whereTeamId(currentTeam()->id);
+ }
+
public static function public()
{
return GithubApp::whereTeamId(currentTeam()->id)->whereisPublic(true)->whereNotNull('app_id')->get();
diff --git a/app/Models/GitlabApp.php b/app/Models/GitlabApp.php
index a789a7e65..2112a4a66 100644
--- a/app/Models/GitlabApp.php
+++ b/app/Models/GitlabApp.php
@@ -9,6 +9,11 @@ class GitlabApp extends BaseModel
'app_secret',
];
+ public static function ownedByCurrentTeam()
+ {
+ return GitlabApp::whereTeamId(currentTeam()->id);
+ }
+
public function applications()
{
return $this->morphMany(Application::class, 'source');
diff --git a/app/Models/InstanceSettings.php b/app/Models/InstanceSettings.php
index bb3d1478b..339daed2a 100644
--- a/app/Models/InstanceSettings.php
+++ b/app/Models/InstanceSettings.php
@@ -2,6 +2,7 @@
namespace App\Models;
+use App\Jobs\PullHelperImageJob;
use App\Notifications\Channels\SendsEmail;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
@@ -21,8 +22,23 @@ class InstanceSettings extends Model implements SendsEmail
'is_auto_update_enabled' => 'boolean',
'auto_update_frequency' => 'string',
'update_check_frequency' => 'string',
+ 'sentinel_token' => 'encrypted',
];
+ protected static function booted(): void
+ {
+ static::updated(function ($settings) {
+ if ($settings->isDirty('helper_version')) {
+ Server::chunkById(100, function ($servers) {
+ foreach ($servers as $server) {
+ PullHelperImageJob::dispatch($server);
+ }
+ });
+ }
+ });
+
+ }
+
public function fqdn(): Attribute
{
return Attribute::make(
@@ -86,16 +102,16 @@ class InstanceSettings extends Model implements SendsEmail
return "[{$instanceName}]";
}
- public function helperVersion(): Attribute
- {
- return Attribute::make(
- get: function ($value) {
- if (isDev()) {
- return 'latest';
- }
+ // public function helperVersion(): Attribute
+ // {
+ // return Attribute::make(
+ // get: function ($value) {
+ // if (isDev()) {
+ // return 'latest';
+ // }
- return $value;
- }
- );
- }
+ // return $value;
+ // }
+ // );
+ // }
}
diff --git a/app/Models/ScheduledDatabaseBackup.php b/app/Models/ScheduledDatabaseBackup.php
index 3921e32e4..473fc7b4b 100644
--- a/app/Models/ScheduledDatabaseBackup.php
+++ b/app/Models/ScheduledDatabaseBackup.php
@@ -51,7 +51,6 @@ class ScheduledDatabaseBackup extends BaseModel
}
}
-
return null;
}
}
diff --git a/app/Models/Server.php b/app/Models/Server.php
index 8864deef1..2f023a248 100644
--- a/app/Models/Server.php
+++ b/app/Models/Server.php
@@ -3,10 +3,15 @@
namespace App\Models;
use App\Actions\Server\InstallDocker;
+use App\Actions\Server\StartSentinel;
use App\Enums\ProxyTypes;
-use App\Jobs\PullSentinelImageJob;
+use App\Jobs\CheckAndStartSentinelJob;
+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\SoftDeletes;
+use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Process;
@@ -43,7 +48,7 @@ use Symfony\Component\Yaml\Yaml;
class Server extends BaseModel
{
- use SchemalessAttributesTrait;
+ use SchemalessAttributesTrait, SoftDeletes;
public static $batch_counter = 0;
@@ -58,6 +63,7 @@ class Server extends BaseModel
$payload['ip'] = str($server->ip)->trim();
}
$server->forceFill($payload);
+
});
static::created(function ($server) {
ServerSetting::create([
@@ -95,7 +101,8 @@ class Server extends BaseModel
}
}
});
- static::deleting(function ($server) {
+
+ static::forceDeleting(function ($server) {
$server->destinations()->each(function ($destination) {
$destination->delete();
});
@@ -103,12 +110,15 @@ class Server extends BaseModel
});
}
- public $casts = [
+ protected $casts = [
'proxy' => SchemalessAttributes::class,
'logdrain_axiom_api_key' => 'encrypted',
'logdrain_newrelic_license_key' => 'encrypted',
'delete_unused_volumes' => 'boolean',
'delete_unused_networks' => 'boolean',
+ 'unreachable_notification_sent' => 'boolean',
+ 'is_build_server' => 'boolean',
+ 'force_disabled' => 'boolean',
];
protected $schemalessAttributes = [
@@ -127,6 +137,11 @@ class Server extends BaseModel
protected $guarded = [];
+ public function type()
+ {
+ return 'server';
+ }
+
public static function isReachable()
{
return Server::ownedByCurrentTeam()->whereRelation('settings', 'is_reachable', true);
@@ -209,10 +224,13 @@ respond 404
1 => 'https',
],
'service' => 'noop',
- 'rule' => 'HostRegexp(`{catchall:.*}`)',
+ 'rule' => 'HostRegexp(`.+`)',
+ 'tls' => [
+ 'certResolver' => 'letsencrypt',
+ ],
'priority' => 1,
'middlewares' => [
- 0 => 'redirect-regexp@file',
+ 0 => 'redirect-regexp',
],
],
],
@@ -507,24 +525,48 @@ $schema://$host {
public function forceEnableServer()
{
- $this->settings->update([
- 'force_disabled' => false,
- ]);
+ $this->settings->force_disabled = false;
+ $this->settings->save();
}
public function forceDisableServer()
{
- $this->settings->update([
- 'force_disabled' => true,
- ]);
+ $this->settings->force_disabled = true;
+ $this->settings->save();
$sshKeyFileLocation = "id.root@{$this->uuid}";
Storage::disk('ssh-keys')->delete($sshKeyFileLocation);
Storage::disk('ssh-mux')->delete($this->muxFilename());
}
+ public function sentinelHeartbeat(bool $isReset = false)
+ {
+ $this->sentinel_updated_at = $isReset ? now()->subMinutes(6000) : now();
+ $this->save();
+ }
+
+ /**
+ * Get the wait time for Sentinel to push before performing an SSH check.
+ *
+ * @return int The wait time in seconds.
+ */
+ public function waitBeforeDoingSshCheck(): int
+ {
+ $wait = $this->settings->sentinel_push_interval_seconds * 3;
+ if ($wait < 120) {
+ $wait = 120;
+ }
+
+ return $wait;
+ }
+
+ public function isSentinelLive()
+ {
+ return Carbon::parse($this->sentinel_updated_at)->isAfter(now()->subSeconds($this->waitBeforeDoingSshCheck()));
+ }
+
public function isSentinelEnabled()
{
- return $this->isMetricsEnabled() || $this->isServerApiEnabled();
+ return ($this->isMetricsEnabled() || $this->isServerApiEnabled()) && ! $this->isBuildServer();
}
public function isMetricsEnabled()
@@ -534,49 +576,19 @@ $schema://$host {
public function isServerApiEnabled()
{
- return $this->settings->is_server_api_enabled;
- }
-
- public function checkServerApi()
- {
- if ($this->isServerApiEnabled()) {
- $server_ip = $this->ip;
- if (isDev()) {
- if ($this->id === 0) {
- $server_ip = 'localhost';
- }
- }
- $command = "curl -s http://{$server_ip}:12172/api/health";
- $process = Process::timeout(5)->run($command);
- if ($process->failed()) {
- ray($process->exitCode(), $process->output(), $process->errorOutput());
- throw new \Exception("Server API is not reachable on http://{$server_ip}:12172");
- }
-
- }
+ return $this->settings->is_sentinel_enabled;
}
public function checkSentinel()
{
- // ray("Checking sentinel on server: {$this->name}");
- if ($this->isSentinelEnabled()) {
- $sentinel_found = instant_remote_process(['docker inspect coolify-sentinel'], $this, false);
- $sentinel_found = json_decode($sentinel_found, true);
- $status = data_get($sentinel_found, '0.State.Status', 'exited');
- if ($status !== 'running') {
- // ray('Sentinel is not running, starting it...');
- PullSentinelImageJob::dispatch($this);
- } else {
- // ray('Sentinel is running');
- }
- }
+ CheckAndStartSentinelJob::dispatch($this);
}
public function getCpuMetrics(int $mins = 5)
{
if ($this->isMetricsEnabled()) {
$from = now()->subMinutes($mins)->toIso8601ZuluString();
- $cpu = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$this->settings->metrics_token}\" http://localhost:8888/api/cpu/history?from=$from'"], $this, false);
+ $cpu = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$this->settings->sentinel_token}\" http://localhost:8888/api/cpu/history?from=$from'"], $this, false);
if (str($cpu)->contains('error')) {
$error = json_decode($cpu, true);
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
@@ -585,17 +597,12 @@ $schema://$host {
}
throw new \Exception($error);
}
- $cpu = str($cpu)->explode("\n")->skip(1)->all();
- $parsedCollection = collect($cpu)->flatMap(function ($item) {
- return collect(explode("\n", trim($item)))->map(function ($line) {
- [$time, $cpu_usage_percent] = explode(',', trim($line));
- $cpu_usage_percent = number_format($cpu_usage_percent, 0);
-
- return [(int) $time, (float) $cpu_usage_percent];
- });
+ $cpu = json_decode($cpu, true);
+ $parsedCollection = collect($cpu)->map(function ($metric) {
+ return [(int) $metric['time'], (float) $metric['percent']];
});
- return $parsedCollection->toArray();
+ return $parsedCollection;
}
}
@@ -603,7 +610,7 @@ $schema://$host {
{
if ($this->isMetricsEnabled()) {
$from = now()->subMinutes($mins)->toIso8601ZuluString();
- $memory = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$this->settings->metrics_token}\" http://localhost:8888/api/memory/history?from=$from'"], $this, false);
+ $memory = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$this->settings->sentinel_token}\" http://localhost:8888/api/memory/history?from=$from'"], $this, false);
if (str($memory)->contains('error')) {
$error = json_decode($memory, true);
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
@@ -612,89 +619,19 @@ $schema://$host {
}
throw new \Exception($error);
}
- $memory = str($memory)->explode("\n")->skip(1)->all();
- $parsedCollection = collect($memory)->flatMap(function ($item) {
- return collect(explode("\n", trim($item)))->map(function ($line) {
- [$time, $used, $free, $usedPercent] = explode(',', trim($line));
- $usedPercent = number_format($usedPercent, 0);
-
- return [(int) $time, (float) $usedPercent];
- });
+ $memory = json_decode($memory, true);
+ $parsedCollection = collect($memory)->map(function ($metric) {
+ return [(int) $metric['time'], (float) $metric['usedPercent']];
});
return $parsedCollection->toArray();
}
}
- public function isServerReady(int $tries = 3)
- {
- if ($this->skipServer()) {
- return false;
- }
- $serverUptimeCheckNumber = $this->unreachable_count;
- if ($this->unreachable_count < $tries) {
- $serverUptimeCheckNumber = $this->unreachable_count + 1;
- }
- if ($this->unreachable_count > $tries) {
- $serverUptimeCheckNumber = $tries;
- }
-
- $serverUptimeCheckNumberMax = $tries;
-
- // ray('server: ' . $this->name);
- // ray('serverUptimeCheckNumber: ' . $serverUptimeCheckNumber);
- // ray('serverUptimeCheckNumberMax: ' . $serverUptimeCheckNumberMax);
-
- ['uptime' => $uptime] = $this->validateConnection();
- if ($uptime) {
- if ($this->unreachable_notification_sent === true) {
- $this->update(['unreachable_notification_sent' => false]);
- }
-
- return true;
- } else {
- if ($serverUptimeCheckNumber >= $serverUptimeCheckNumberMax) {
- // Reached max number of retries
- if ($this->unreachable_notification_sent === false) {
- ray('Server unreachable, sending notification...');
- // $this->team?->notify(new Unreachable($this));
- $this->update(['unreachable_notification_sent' => true]);
- }
- if ($this->settings->is_reachable === true) {
- $this->settings()->update([
- 'is_reachable' => false,
- ]);
- }
-
- foreach ($this->applications() as $application) {
- $application->update(['status' => 'exited']);
- }
- foreach ($this->databases() as $database) {
- $database->update(['status' => 'exited']);
- }
- foreach ($this->services()->get() as $service) {
- $apps = $service->applications()->get();
- $dbs = $service->databases()->get();
- foreach ($apps as $app) {
- $app->update(['status' => 'exited']);
- }
- foreach ($dbs as $db) {
- $db->update(['status' => 'exited']);
- }
- }
- } else {
- $this->update([
- 'unreachable_count' => $this->unreachable_count + 1,
- ]);
- }
-
- return false;
- }
- }
-
public function getDiskUsage(): ?string
{
- return instant_remote_process(["df /| tail -1 | awk '{ print $5}' | sed 's/%//g'"], $this, false);
+ return instant_remote_process(['df / --output=pcent | tr -cd 0-9'], $this, false);
+ // return instant_remote_process(["df /| tail -1 | awk '{ print $5}' | sed 's/%//g'"], $this, false);
}
public function definedResources()
@@ -974,7 +911,8 @@ $schema://$host {
public function isProxyShouldRun()
{
- if ($this->proxyType() === ProxyTypes::NONE->value || $this->settings->is_build_server) {
+ // TODO: Do we need "|| $this->proxy->force_stop" here?
+ if ($this->proxyType() === ProxyTypes::NONE->value || $this->isBuildServer()) {
return false;
}
@@ -1038,39 +976,111 @@ $schema://$host {
return data_get($this, 'settings.is_swarm_worker');
}
+ public function serverStatus(): bool
+ {
+ if ($this->status() === false) {
+ return false;
+ }
+ if ($this->isFunctional() === false) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public function status(): bool
+ {
+ if ($this->skipServer()) {
+ return false;
+ }
+ ['uptime' => $uptime] = $this->validateConnection(false);
+ if ($uptime === false) {
+ foreach ($this->applications() as $application) {
+ $application->status = 'exited';
+ $application->save();
+ }
+ foreach ($this->databases() as $database) {
+ $database->status = 'exited';
+ $database->save();
+ }
+ foreach ($this->services() as $service) {
+ $apps = $service->applications()->get();
+ $dbs = $service->databases()->get();
+ foreach ($apps as $app) {
+ $app->status = 'exited';
+ $app->save();
+ }
+ foreach ($dbs as $db) {
+ $db->status = 'exited';
+ $db->save();
+ }
+ }
+
+ return false;
+ }
+
+ return true;
+ }
+
+ public function isReachableChanged()
+ {
+ $this->refresh();
+ $unreachableNotificationSent = (bool) $this->unreachable_notification_sent;
+ $isReachable = (bool) $this->settings->is_reachable;
+ loggy('Server setting is_reachable changed to '.$isReachable.' for server '.$this->id.'. Unreachable notification sent: '.$unreachableNotificationSent);
+ // If the server is reachable, send the reachable notification if it was sent before
+ if ($isReachable === true) {
+ if ($unreachableNotificationSent === true) {
+ $this->sendReachableNotification();
+ }
+ } else {
+ // If the server is unreachable, send the unreachable notification if it was not sent before
+ if ($unreachableNotificationSent === false) {
+ $this->sendUnreachableNotification();
+ }
+ }
+ }
+
+ public function sendReachableNotification()
+ {
+ $this->unreachable_notification_sent = false;
+ $this->save();
+ $this->refresh();
+ $this->team->notify(new Reachable($this));
+ }
+
+ public function sendUnreachableNotification()
+ {
+ $this->unreachable_notification_sent = true;
+ $this->save();
+ $this->refresh();
+ $this->team->notify(new Unreachable($this));
+ }
+
public function validateConnection($isManualCheck = true)
{
config()->set('constants.ssh.mux_enabled', ! $isManualCheck);
- // ray('Manual Check: ' . ($isManualCheck ? 'true' : 'false'));
- $server = Server::find($this->id);
- if (! $server) {
- return ['uptime' => false, 'error' => 'Server not found.'];
- }
- if ($server->skipServer()) {
+ if ($this->skipServer()) {
return ['uptime' => false, 'error' => 'Server skipped.'];
}
try {
// Make sure the private key is stored
- if ($server->privateKey) {
- $server->privateKey->storeInFileSystem();
+ if ($this->privateKey) {
+ $this->privateKey->storeInFileSystem();
}
- instant_remote_process(['ls /'], $server);
- $server->settings()->update([
- 'is_reachable' => true,
- ]);
- $server->update([
- 'unreachable_count' => 0,
- ]);
- if (data_get($server, 'unreachable_notification_sent') === true) {
- $server->update(['unreachable_notification_sent' => false]);
+ instant_remote_process(['ls /'], $this);
+ if ($this->settings->is_reachable === false) {
+ $this->settings->is_reachable = true;
+ $this->settings->save();
}
return ['uptime' => true, 'error' => null];
} catch (\Throwable $e) {
- $server->settings()->update([
- 'is_reachable' => false,
- ]);
+ if ($this->settings->is_reachable === true) {
+ $this->settings->is_reachable = false;
+ $this->settings->save();
+ }
return ['uptime' => false, 'error' => $e->getMessage()];
}
@@ -1225,4 +1235,22 @@ $schema://$host {
{
return str($this->ip)->contains(':');
}
+
+ public function restartSentinel(bool $async = true): void
+ {
+ try {
+ if ($async) {
+ StartSentinel::dispatch($this, true);
+ } else {
+ StartSentinel::run($this, true);
+ }
+ } catch (\Throwable $e) {
+ loggy('Error restarting Sentinel: '.$e->getMessage());
+ }
+ }
+
+ public function url()
+ {
+ return base_url().'/server/'.$this->uuid;
+ }
}
diff --git a/app/Models/ServerSetting.php b/app/Models/ServerSetting.php
index c44a393b4..7a8e7b8ed 100644
--- a/app/Models/ServerSetting.php
+++ b/app/Models/ServerSetting.php
@@ -24,7 +24,7 @@ use OpenApi\Attributes as OA;
'is_logdrain_newrelic_enabled' => ['type' => 'boolean'],
'is_metrics_enabled' => ['type' => 'boolean'],
'is_reachable' => ['type' => 'boolean'],
- 'is_server_api_enabled' => ['type' => 'boolean'],
+ 'is_sentinel_enabled' => ['type' => 'boolean'],
'is_swarm_manager' => ['type' => 'boolean'],
'is_swarm_worker' => ['type' => 'boolean'],
'is_usable' => ['type' => 'boolean'],
@@ -35,9 +35,9 @@ use OpenApi\Attributes as OA;
'logdrain_highlight_project_id' => ['type' => 'string'],
'logdrain_newrelic_base_uri' => ['type' => 'string'],
'logdrain_newrelic_license_key' => ['type' => 'string'],
- 'metrics_history_days' => ['type' => 'integer'],
- 'metrics_refresh_rate_seconds' => ['type' => 'integer'],
- 'metrics_token' => ['type' => 'string'],
+ 'sentinel_metrics_history_days' => ['type' => 'integer'],
+ 'sentinel_metrics_refresh_rate_seconds' => ['type' => 'integer'],
+ 'sentinel_token' => ['type' => 'string'],
'docker_cleanup_frequency' => ['type' => 'string'],
'docker_cleanup_threshold' => ['type' => 'integer'],
'server_id' => ['type' => 'integer'],
@@ -53,8 +53,78 @@ class ServerSetting extends Model
protected $casts = [
'force_docker_cleanup' => 'boolean',
'docker_cleanup_threshold' => 'integer',
+ 'sentinel_token' => 'encrypted',
+ 'is_reachable' => 'boolean',
+ 'is_usable' => 'boolean',
];
+ protected static function booted()
+ {
+ static::creating(function ($setting) {
+ try {
+ if (str($setting->sentinel_token)->isEmpty()) {
+ $setting->generateSentinelToken(save: false);
+ }
+ if (str($setting->sentinel_custom_url)->isEmpty()) {
+ $setting->generateSentinelUrl(save: false);
+ }
+ } catch (\Throwable $e) {
+ loggy('Error creating server setting: '.$e->getMessage());
+ }
+ });
+ static::updated(function ($settings) {
+ if (
+ $settings->isDirty('sentinel_token') ||
+ $settings->isDirty('sentinel_custom_url') ||
+ $settings->isDirty('sentinel_metrics_refresh_rate_seconds') ||
+ $settings->isDirty('sentinel_metrics_history_days') ||
+ $settings->isDirty('sentinel_push_interval_seconds')
+ ) {
+ $settings->server->restartSentinel();
+ }
+ if ($settings->isDirty('is_reachable')) {
+ $settings->server->isReachableChanged();
+ }
+ });
+ }
+
+ public function generateSentinelToken(bool $save = true)
+ {
+ $data = [
+ 'server_uuid' => $this->server->uuid,
+ ];
+ $token = json_encode($data);
+ $encrypted = encrypt($token);
+ $this->sentinel_token = $encrypted;
+ if ($save) {
+ $this->save();
+ }
+
+ return $token;
+ }
+
+ public function generateSentinelUrl(bool $save = true)
+ {
+ $domain = null;
+ $settings = InstanceSettings::get();
+ if ($this->server->isLocalhost()) {
+ $domain = 'http://host.docker.internal:8000';
+ } elseif ($settings->fqdn) {
+ $domain = $settings->fqdn;
+ } elseif ($settings->public_ipv4) {
+ $domain = 'http://'.$settings->public_ipv4.':8000';
+ } elseif ($settings->public_ipv6) {
+ $domain = 'http://'.$settings->public_ipv6.':8000';
+ }
+ $this->sentinel_custom_url = $domain;
+ loggy('Sentinel URL: '.$domain);
+ if ($save) {
+ $this->save();
+ }
+
+ return $domain;
+ }
+
public function server()
{
return $this->belongsTo(Server::class);
diff --git a/app/Models/Service.php b/app/Models/Service.php
index 5f0fea4aa..0af1adf22 100644
--- a/app/Models/Service.php
+++ b/app/Models/Service.php
@@ -297,7 +297,7 @@ class Service extends BaseModel
'key' => 'CP_DISABLE_HTTPS',
'value' => data_get($disable_https, 'value'),
'rules' => 'required',
- 'customHelper' => "If you want to use https, set this to 0. Variable name: CP_DISABLE_HTTPS",
+ 'customHelper' => 'If you want to use https, set this to 0. Variable name: CP_DISABLE_HTTPS',
],
]);
}
@@ -319,7 +319,7 @@ class Service extends BaseModel
if ($password) {
$data = $data->merge([
'Password' => [
- 'key' => 'LABEL_STUDIO_PASSWORD',
+ 'key' => data_get($password, 'key'),
'value' => data_get($password, 'value'),
'rules' => 'required',
'isPassword' => true,
@@ -359,7 +359,7 @@ class Service extends BaseModel
if ($email) {
$data = $data->merge([
'Admin Email' => [
- 'key' => 'LANGFUSE_INIT_USER_EMAIL',
+ 'key' => data_get($email, 'key'),
'value' => data_get($email, 'value'),
'rules' => 'required|email',
],
@@ -370,7 +370,7 @@ class Service extends BaseModel
if ($password) {
$data = $data->merge([
'Admin Password' => [
- 'key' => 'LANGFUSE_INIT_USER_PASSWORD',
+ 'key' => data_get($password, 'key'),
'value' => data_get($password, 'value'),
'rules' => 'required',
'isPassword' => true,
@@ -384,7 +384,7 @@ class Service extends BaseModel
$email = $this->environment_variables()->where('key', 'IN_USER_EMAIL')->first();
$data = $data->merge([
'Email' => [
- 'key' => 'IN_USER_EMAIL',
+ 'key' => data_get($email, 'key'),
'value' => data_get($email, 'value'),
'rules' => 'required|email',
],
@@ -392,7 +392,7 @@ class Service extends BaseModel
$password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_INVOICENINJAUSER')->first();
$data = $data->merge([
'Password' => [
- 'key' => 'IN_PASSWORD',
+ 'key' => data_get($password, 'key'),
'value' => data_get($password, 'value'),
'rules' => 'required',
'isPassword' => true,
@@ -487,7 +487,7 @@ class Service extends BaseModel
if ($admin_password) {
$data = $data->merge([
'Admin Password' => [
- 'key' => 'SERVICE_PASSWORD_TOLGEE',
+ 'key' => data_get($admin_password, 'key'),
'value' => data_get($admin_password, 'value'),
'rules' => 'required',
'isPassword' => true,
@@ -534,7 +534,7 @@ class Service extends BaseModel
if ($admin_password) {
$data = $data->merge([
'Admin Password' => [
- 'key' => 'SERVICE_PASSWORD_UNLEASH',
+ 'key' => data_get($admin_password, 'key'),
'value' => data_get($admin_password, 'value'),
'rules' => 'required',
'isPassword' => true,
@@ -557,7 +557,7 @@ class Service extends BaseModel
if ($admin_password) {
$data = $data->merge([
'Admin Password' => [
- 'key' => 'GF_SECURITY_ADMIN_PASSWORD',
+ 'key' => data_get($admin_password, 'key'),
'value' => data_get($admin_password, 'value'),
'rules' => 'required',
'isPassword' => true,
@@ -919,7 +919,7 @@ class Service extends BaseModel
if ($admin_user) {
$data = $data->merge([
'User' => [
- 'key' => 'SERVICE_USER_ADMIN',
+ 'key' => data_get($admin_user, 'key'),
'value' => data_get($admin_user, 'value', 'admin'),
'readonly' => true,
'rules' => 'required',
@@ -929,7 +929,7 @@ class Service extends BaseModel
if ($admin_password) {
$data = $data->merge([
'Password' => [
- 'key' => 'SERVICE_PASSWORD_ADMIN',
+ 'key' => data_get($admin_password, 'key'),
'value' => data_get($admin_password, 'value'),
'rules' => 'required',
'isPassword' => true,
@@ -939,7 +939,7 @@ class Service extends BaseModel
if ($admin_email) {
$data = $data->merge([
'Email' => [
- 'key' => 'ADMIN_EMAIL',
+ 'key' => data_get($admin_email, 'key'),
'value' => data_get($admin_email, 'value'),
'rules' => 'required|email',
],
@@ -997,8 +997,8 @@ class Service extends BaseModel
break;
case $image->contains('mysql'):
$userVariables = ['SERVICE_USER_MYSQL', 'SERVICE_USER_WORDPRESS', 'MYSQL_USER'];
- $passwordVariables = ['SERVICE_PASSWORD_MYSQL', 'SERVICE_PASSWORD_WORDPRESS', 'MYSQL_PASSWORD','SERVICE_PASSWORD_64_MYSQL'];
- $rootPasswordVariables = ['SERVICE_PASSWORD_MYSQLROOT', 'SERVICE_PASSWORD_ROOT','SERVICE_PASSWORD_64_MYSQLROOT'];
+ $passwordVariables = ['SERVICE_PASSWORD_MYSQL', 'SERVICE_PASSWORD_WORDPRESS', 'MYSQL_PASSWORD', 'SERVICE_PASSWORD_64_MYSQL'];
+ $rootPasswordVariables = ['SERVICE_PASSWORD_MYSQLROOT', 'SERVICE_PASSWORD_ROOT', 'SERVICE_PASSWORD_64_MYSQLROOT'];
$dbNameVariables = ['MYSQL_DATABASE'];
$mysql_user = $this->environment_variables()->whereIn('key', $userVariables)->first();
$mysql_password = $this->environment_variables()->whereIn('key', $passwordVariables)->first();
@@ -1232,7 +1232,6 @@ class Service extends BaseModel
public function environment_variables(): HasMany
{
-
return $this->hasMany(EnvironmentVariable::class)->orderByRaw("LOWER(key) LIKE LOWER('SERVICE%') DESC, LOWER(key) ASC");
}
@@ -1316,4 +1315,20 @@ class Service extends BaseModel
return $networks;
}
+
+ protected function isDeployable(): Attribute
+ {
+ return Attribute::make(
+ get: function () {
+ $envs = $this->environment_variables()->where('is_required', true)->get();
+ foreach ($envs as $env) {
+ if ($env->is_really_required) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ );
+ }
}
diff --git a/app/Models/StandaloneClickhouse.php b/app/Models/StandaloneClickhouse.php
index e4341b1b9..c9d3ea031 100644
--- a/app/Models/StandaloneClickhouse.php
+++ b/app/Models/StandaloneClickhouse.php
@@ -266,33 +266,48 @@ class StandaloneClickhouse extends BaseModel
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
}
- public function getMetrics(int $mins = 5)
+ public function getCpuMetrics(int $mins = 5)
{
$server = $this->destination->server;
$container_name = $this->uuid;
- if ($server->isMetricsEnabled()) {
- $from = now()->subMinutes($mins)->toIso8601ZuluString();
- $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
- if (str($metrics)->contains('error')) {
- $error = json_decode($metrics, true);
- $error = data_get($error, 'error', 'Something is not okay, are you okay?');
- if ($error == 'Unauthorized') {
- $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
- }
- throw new \Exception($error);
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/cpu/history?from=$from'"], $server, false);
+ if (str($metrics)->contains('error')) {
+ $error = json_decode($metrics, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error == 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
}
- $metrics = str($metrics)->explode("\n")->skip(1)->all();
- $parsedCollection = collect($metrics)->flatMap(function ($item) {
- return collect(explode("\n", trim($item)))->map(function ($line) {
- [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line));
- $cpu_usage_percent = number_format($cpu_usage_percent, 2);
-
- return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage];
- });
- });
-
- return $parsedCollection->toArray();
+ throw new \Exception($error);
}
+ $metrics = json_decode($metrics, true);
+ $parsedCollection = collect($metrics)->map(function ($metric) {
+ return [(int) $metric['time'], (float) $metric['percent']];
+ });
+
+ return $parsedCollection->toArray();
+ }
+
+ public function getMemoryMetrics(int $mins = 5)
+ {
+ $server = $this->destination->server;
+ $container_name = $this->uuid;
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/memory/history?from=$from'"], $server, false);
+ if (str($metrics)->contains('error')) {
+ $error = json_decode($metrics, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error == 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
+ }
+ throw new \Exception($error);
+ }
+ $metrics = json_decode($metrics, true);
+ $parsedCollection = collect($metrics)->map(function ($metric) {
+ return [(int) $metric['time'], (float) $metric['used']];
+ });
+
+ return $parsedCollection->toArray();
}
public function isBackupSolutionAvailable()
diff --git a/app/Models/StandaloneDragonfly.php b/app/Models/StandaloneDragonfly.php
index 94ab2d745..2bde51080 100644
--- a/app/Models/StandaloneDragonfly.php
+++ b/app/Models/StandaloneDragonfly.php
@@ -266,33 +266,48 @@ class StandaloneDragonfly extends BaseModel
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
}
- public function getMetrics(int $mins = 5)
+ public function getCpuMetrics(int $mins = 5)
{
$server = $this->destination->server;
$container_name = $this->uuid;
- if ($server->isMetricsEnabled()) {
- $from = now()->subMinutes($mins)->toIso8601ZuluString();
- $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
- if (str($metrics)->contains('error')) {
- $error = json_decode($metrics, true);
- $error = data_get($error, 'error', 'Something is not okay, are you okay?');
- if ($error == 'Unauthorized') {
- $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
- }
- throw new \Exception($error);
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/cpu/history?from=$from'"], $server, false);
+ if (str($metrics)->contains('error')) {
+ $error = json_decode($metrics, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error == 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
}
- $metrics = str($metrics)->explode("\n")->skip(1)->all();
- $parsedCollection = collect($metrics)->flatMap(function ($item) {
- return collect(explode("\n", trim($item)))->map(function ($line) {
- [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line));
- $cpu_usage_percent = number_format($cpu_usage_percent, 2);
-
- return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage];
- });
- });
-
- return $parsedCollection->toArray();
+ throw new \Exception($error);
}
+ $metrics = json_decode($metrics, true);
+ $parsedCollection = collect($metrics)->map(function ($metric) {
+ return [(int) $metric['time'], (float) $metric['percent']];
+ });
+
+ return $parsedCollection->toArray();
+ }
+
+ public function getMemoryMetrics(int $mins = 5)
+ {
+ $server = $this->destination->server;
+ $container_name = $this->uuid;
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/memory/history?from=$from'"], $server, false);
+ if (str($metrics)->contains('error')) {
+ $error = json_decode($metrics, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error == 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
+ }
+ throw new \Exception($error);
+ }
+ $metrics = json_decode($metrics, true);
+ $parsedCollection = collect($metrics)->map(function ($metric) {
+ return [(int) $metric['time'], (float) $metric['used']];
+ });
+
+ return $parsedCollection->toArray();
}
public function isBackupSolutionAvailable()
diff --git a/app/Models/StandaloneKeydb.php b/app/Models/StandaloneKeydb.php
index 335c8931c..fbee9789a 100644
--- a/app/Models/StandaloneKeydb.php
+++ b/app/Models/StandaloneKeydb.php
@@ -266,33 +266,48 @@ class StandaloneKeydb extends BaseModel
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
}
- public function getMetrics(int $mins = 5)
+ public function getCpuMetrics(int $mins = 5)
{
$server = $this->destination->server;
$container_name = $this->uuid;
- if ($server->isMetricsEnabled()) {
- $from = now()->subMinutes($mins)->toIso8601ZuluString();
- $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
- if (str($metrics)->contains('error')) {
- $error = json_decode($metrics, true);
- $error = data_get($error, 'error', 'Something is not okay, are you okay?');
- if ($error == 'Unauthorized') {
- $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
- }
- throw new \Exception($error);
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/cpu/history?from=$from'"], $server, false);
+ if (str($metrics)->contains('error')) {
+ $error = json_decode($metrics, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error == 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
}
- $metrics = str($metrics)->explode("\n")->skip(1)->all();
- $parsedCollection = collect($metrics)->flatMap(function ($item) {
- return collect(explode("\n", trim($item)))->map(function ($line) {
- [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line));
- $cpu_usage_percent = number_format($cpu_usage_percent, 2);
-
- return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage];
- });
- });
-
- return $parsedCollection->toArray();
+ throw new \Exception($error);
}
+ $metrics = json_decode($metrics, true);
+ $parsedCollection = collect($metrics)->map(function ($metric) {
+ return [(int) $metric['time'], (float) $metric['percent']];
+ });
+
+ return $parsedCollection->toArray();
+ }
+
+ public function getMemoryMetrics(int $mins = 5)
+ {
+ $server = $this->destination->server;
+ $container_name = $this->uuid;
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/memory/history?from=$from'"], $server, false);
+ if (str($metrics)->contains('error')) {
+ $error = json_decode($metrics, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error == 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
+ }
+ throw new \Exception($error);
+ }
+ $metrics = json_decode($metrics, true);
+ $parsedCollection = collect($metrics)->map(function ($metric) {
+ return [(int) $metric['time'], (float) $metric['used']];
+ });
+
+ return $parsedCollection->toArray();
}
public function isBackupSolutionAvailable()
diff --git a/app/Models/StandaloneMariadb.php b/app/Models/StandaloneMariadb.php
index c6c08dee5..00f635faf 100644
--- a/app/Models/StandaloneMariadb.php
+++ b/app/Models/StandaloneMariadb.php
@@ -266,33 +266,48 @@ class StandaloneMariadb extends BaseModel
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
}
- public function getMetrics(int $mins = 5)
+ public function getCpuMetrics(int $mins = 5)
{
$server = $this->destination->server;
$container_name = $this->uuid;
- if ($server->isMetricsEnabled()) {
- $from = now()->subMinutes($mins)->toIso8601ZuluString();
- $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
- if (str($metrics)->contains('error')) {
- $error = json_decode($metrics, true);
- $error = data_get($error, 'error', 'Something is not okay, are you okay?');
- if ($error == 'Unauthorized') {
- $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
- }
- throw new \Exception($error);
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/cpu/history?from=$from'"], $server, false);
+ if (str($metrics)->contains('error')) {
+ $error = json_decode($metrics, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error == 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
}
- $metrics = str($metrics)->explode("\n")->skip(1)->all();
- $parsedCollection = collect($metrics)->flatMap(function ($item) {
- return collect(explode("\n", trim($item)))->map(function ($line) {
- [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line));
- $cpu_usage_percent = number_format($cpu_usage_percent, 2);
-
- return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage];
- });
- });
-
- return $parsedCollection->toArray();
+ throw new \Exception($error);
}
+ $metrics = json_decode($metrics, true);
+ $parsedCollection = collect($metrics)->map(function ($metric) {
+ return [(int) $metric['time'], (float) $metric['percent']];
+ });
+
+ return $parsedCollection->toArray();
+ }
+
+ public function getMemoryMetrics(int $mins = 5)
+ {
+ $server = $this->destination->server;
+ $container_name = $this->uuid;
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/memory/history?from=$from'"], $server, false);
+ if (str($metrics)->contains('error')) {
+ $error = json_decode($metrics, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error == 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
+ }
+ throw new \Exception($error);
+ }
+ $metrics = json_decode($metrics, true);
+ $parsedCollection = collect($metrics)->map(function ($metric) {
+ return [(int) $metric['time'], (float) $metric['used']];
+ });
+
+ return $parsedCollection->toArray();
}
public function isBackupSolutionAvailable()
diff --git a/app/Models/StandaloneMongodb.php b/app/Models/StandaloneMongodb.php
index 99893b1d1..c225011c5 100644
--- a/app/Models/StandaloneMongodb.php
+++ b/app/Models/StandaloneMongodb.php
@@ -286,33 +286,48 @@ class StandaloneMongodb extends BaseModel
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
}
- public function getMetrics(int $mins = 5)
+ public function getCpuMetrics(int $mins = 5)
{
$server = $this->destination->server;
$container_name = $this->uuid;
- if ($server->isMetricsEnabled()) {
- $from = now()->subMinutes($mins)->toIso8601ZuluString();
- $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
- if (str($metrics)->contains('error')) {
- $error = json_decode($metrics, true);
- $error = data_get($error, 'error', 'Something is not okay, are you okay?');
- if ($error == 'Unauthorized') {
- $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
- }
- throw new \Exception($error);
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/cpu/history?from=$from'"], $server, false);
+ if (str($metrics)->contains('error')) {
+ $error = json_decode($metrics, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error == 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
}
- $metrics = str($metrics)->explode("\n")->skip(1)->all();
- $parsedCollection = collect($metrics)->flatMap(function ($item) {
- return collect(explode("\n", trim($item)))->map(function ($line) {
- [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line));
- $cpu_usage_percent = number_format($cpu_usage_percent, 2);
-
- return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage];
- });
- });
-
- return $parsedCollection->toArray();
+ throw new \Exception($error);
}
+ $metrics = json_decode($metrics, true);
+ $parsedCollection = collect($metrics)->map(function ($metric) {
+ return [(int) $metric['time'], (float) $metric['percent']];
+ });
+
+ return $parsedCollection->toArray();
+ }
+
+ public function getMemoryMetrics(int $mins = 5)
+ {
+ $server = $this->destination->server;
+ $container_name = $this->uuid;
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/memory/history?from=$from'"], $server, false);
+ if (str($metrics)->contains('error')) {
+ $error = json_decode($metrics, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error == 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
+ }
+ throw new \Exception($error);
+ }
+ $metrics = json_decode($metrics, true);
+ $parsedCollection = collect($metrics)->map(function ($metric) {
+ return [(int) $metric['time'], (float) $metric['used']];
+ });
+
+ return $parsedCollection->toArray();
}
public function isBackupSolutionAvailable()
diff --git a/app/Models/StandaloneMysql.php b/app/Models/StandaloneMysql.php
index f2a5b5c14..52725ffc7 100644
--- a/app/Models/StandaloneMysql.php
+++ b/app/Models/StandaloneMysql.php
@@ -267,33 +267,48 @@ class StandaloneMysql extends BaseModel
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
}
- public function getMetrics(int $mins = 5)
+ public function getCpuMetrics(int $mins = 5)
{
$server = $this->destination->server;
$container_name = $this->uuid;
- if ($server->isMetricsEnabled()) {
- $from = now()->subMinutes($mins)->toIso8601ZuluString();
- $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
- if (str($metrics)->contains('error')) {
- $error = json_decode($metrics, true);
- $error = data_get($error, 'error', 'Something is not okay, are you okay?');
- if ($error == 'Unauthorized') {
- $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
- }
- throw new \Exception($error);
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/cpu/history?from=$from'"], $server, false);
+ if (str($metrics)->contains('error')) {
+ $error = json_decode($metrics, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error == 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
}
- $metrics = str($metrics)->explode("\n")->skip(1)->all();
- $parsedCollection = collect($metrics)->flatMap(function ($item) {
- return collect(explode("\n", trim($item)))->map(function ($line) {
- [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line));
- $cpu_usage_percent = number_format($cpu_usage_percent, 2);
-
- return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage];
- });
- });
-
- return $parsedCollection->toArray();
+ throw new \Exception($error);
}
+ $metrics = json_decode($metrics, true);
+ $parsedCollection = collect($metrics)->map(function ($metric) {
+ return [(int) $metric['time'], (float) $metric['percent']];
+ });
+
+ return $parsedCollection->toArray();
+ }
+
+ public function getMemoryMetrics(int $mins = 5)
+ {
+ $server = $this->destination->server;
+ $container_name = $this->uuid;
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/memory/history?from=$from'"], $server, false);
+ if (str($metrics)->contains('error')) {
+ $error = json_decode($metrics, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error == 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
+ }
+ throw new \Exception($error);
+ }
+ $metrics = json_decode($metrics, true);
+ $parsedCollection = collect($metrics)->map(function ($metric) {
+ return [(int) $metric['time'], (float) $metric['used']];
+ });
+
+ return $parsedCollection->toArray();
}
public function isBackupSolutionAvailable()
diff --git a/app/Models/StandalonePostgresql.php b/app/Models/StandalonePostgresql.php
index 1b18a5ca7..ccab7d658 100644
--- a/app/Models/StandalonePostgresql.php
+++ b/app/Models/StandalonePostgresql.php
@@ -268,37 +268,52 @@ class StandalonePostgresql extends BaseModel
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
}
- public function getMetrics(int $mins = 5)
- {
- $server = $this->destination->server;
- $container_name = $this->uuid;
- if ($server->isMetricsEnabled()) {
- $from = now()->subMinutes($mins)->toIso8601ZuluString();
- $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
- if (str($metrics)->contains('error')) {
- $error = json_decode($metrics, true);
- $error = data_get($error, 'error', 'Something is not okay, are you okay?');
- if ($error == 'Unauthorized') {
- $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
- }
- throw new \Exception($error);
- }
- $metrics = str($metrics)->explode("\n")->skip(1)->all();
- $parsedCollection = collect($metrics)->flatMap(function ($item) {
- return collect(explode("\n", trim($item)))->map(function ($line) {
- [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line));
- $cpu_usage_percent = number_format($cpu_usage_percent, 2);
-
- return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage];
- });
- });
-
- return $parsedCollection->toArray();
- }
- }
-
public function isBackupSolutionAvailable()
{
return true;
}
+
+ public function getCpuMetrics(int $mins = 5)
+ {
+ $server = $this->destination->server;
+ $container_name = $this->uuid;
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/cpu/history?from=$from'"], $server, false);
+ if (str($metrics)->contains('error')) {
+ $error = json_decode($metrics, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error == 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
+ }
+ throw new \Exception($error);
+ }
+ $metrics = json_decode($metrics, true);
+ $parsedCollection = collect($metrics)->map(function ($metric) {
+ return [(int) $metric['time'], (float) $metric['percent']];
+ });
+
+ return $parsedCollection->toArray();
+ }
+
+ public function getMemoryMetrics(int $mins = 5)
+ {
+ $server = $this->destination->server;
+ $container_name = $this->uuid;
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/memory/history?from=$from'"], $server, false);
+ if (str($metrics)->contains('error')) {
+ $error = json_decode($metrics, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error == 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
+ }
+ throw new \Exception($error);
+ }
+ $metrics = json_decode($metrics, true);
+ $parsedCollection = collect($metrics)->map(function ($metric) {
+ return [(int) $metric['time'], (float) $metric['used']];
+ });
+
+ return $parsedCollection->toArray();
+ }
}
diff --git a/app/Models/StandaloneRedis.php b/app/Models/StandaloneRedis.php
index a5868e243..5b6993b9a 100644
--- a/app/Models/StandaloneRedis.php
+++ b/app/Models/StandaloneRedis.php
@@ -210,7 +210,12 @@ class StandaloneRedis extends BaseModel
protected function internalDbUrl(): Attribute
{
return new Attribute(
- get: fn () => "redis://:{$this->redis_password}@{$this->uuid}:6379/0",
+ get: function () {
+ $redis_version = $this->getRedisVersion();
+ $username_part = version_compare($redis_version, '6.0', '>=') ? "{$this->redis_username}:" : '';
+
+ return "redis://{$username_part}{$this->redis_password}@{$this->uuid}:6379/0";
+ }
);
}
@@ -219,7 +224,10 @@ class StandaloneRedis extends BaseModel
return new Attribute(
get: function () {
if ($this->is_public && $this->public_port) {
- return "redis://:{$this->redis_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
+ $redis_version = $this->getRedisVersion();
+ $username_part = version_compare($redis_version, '6.0', '>=') ? "{$this->redis_username}:" : '';
+
+ return "redis://{$username_part}{$this->redis_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
}
return null;
@@ -227,6 +235,13 @@ class StandaloneRedis extends BaseModel
);
}
+ public function getRedisVersion()
+ {
+ $image_parts = explode(':', $this->image);
+
+ return $image_parts[1] ?? '0.0';
+ }
+
public function environment()
{
return $this->belongsTo(Environment::class);
@@ -262,37 +277,81 @@ class StandaloneRedis extends BaseModel
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
}
- public function getMetrics(int $mins = 5)
+ public function getCpuMetrics(int $mins = 5)
{
$server = $this->destination->server;
$container_name = $this->uuid;
- if ($server->isMetricsEnabled()) {
- $from = now()->subMinutes($mins)->toIso8601ZuluString();
- $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
- if (str($metrics)->contains('error')) {
- $error = json_decode($metrics, true);
- $error = data_get($error, 'error', 'Something is not okay, are you okay?');
- if ($error == 'Unauthorized') {
- $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
- }
- throw new \Exception($error);
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/cpu/history?from=$from'"], $server, false);
+ if (str($metrics)->contains('error')) {
+ $error = json_decode($metrics, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error == 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
}
- $metrics = str($metrics)->explode("\n")->skip(1)->all();
- $parsedCollection = collect($metrics)->flatMap(function ($item) {
- return collect(explode("\n", trim($item)))->map(function ($line) {
- [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line));
- $cpu_usage_percent = number_format($cpu_usage_percent, 2);
-
- return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage];
- });
- });
-
- return $parsedCollection->toArray();
+ throw new \Exception($error);
}
+ $metrics = json_decode($metrics, true);
+ $parsedCollection = collect($metrics)->map(function ($metric) {
+ return [(int) $metric['time'], (float) $metric['percent']];
+ });
+
+ return $parsedCollection->toArray();
+ }
+
+ public function getMemoryMetrics(int $mins = 5)
+ {
+ $server = $this->destination->server;
+ $container_name = $this->uuid;
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/memory/history?from=$from'"], $server, false);
+ if (str($metrics)->contains('error')) {
+ $error = json_decode($metrics, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error == 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
+ }
+ throw new \Exception($error);
+ }
+ $metrics = json_decode($metrics, true);
+ $parsedCollection = collect($metrics)->map(function ($metric) {
+ return [(int) $metric['time'], (float) $metric['used']];
+ });
+
+ return $parsedCollection->toArray();
}
public function isBackupSolutionAvailable()
{
return false;
}
+
+ public function redisPassword(): Attribute
+ {
+ return new Attribute(
+ get: function () {
+ $password = $this->runtime_environment_variables()->where('key', 'REDIS_PASSWORD')->first();
+ if (! $password) {
+ return null;
+ }
+
+ return $password->value;
+ },
+
+ );
+ }
+
+ public function redisUsername(): Attribute
+ {
+ return new Attribute(
+ get: function () {
+ $username = $this->runtime_environment_variables()->where('key', 'REDIS_USERNAME')->first();
+ if (! $username) {
+ return null;
+ }
+
+ return $username->value;
+ }
+ );
+ }
}
diff --git a/app/Models/Team.php b/app/Models/Team.php
index 3f8e97bc5..c5e46f165 100644
--- a/app/Models/Team.php
+++ b/app/Models/Team.php
@@ -34,6 +34,7 @@ use OpenApi\Attributes as OA;
'smtp_notifications_status_changes' => ['type' => 'boolean', 'description' => 'Whether to send status change notifications via SMTP.'],
'smtp_notifications_scheduled_tasks' => ['type' => 'boolean', 'description' => 'Whether to send scheduled task notifications via SMTP.'],
'smtp_notifications_database_backups' => ['type' => 'boolean', 'description' => 'Whether to send database backup notifications via SMTP.'],
+ 'smtp_notifications_server_disk_usage' => ['type' => 'boolean', 'description' => 'Whether to send server disk usage notifications via SMTP.'],
'discord_enabled' => ['type' => 'boolean', 'description' => 'Whether Discord is enabled or not.'],
'discord_webhook_url' => ['type' => 'string', 'description' => 'The Discord webhook URL.'],
'discord_notifications_test' => ['type' => 'boolean', 'description' => 'Whether to send test notifications via Discord.'],
@@ -41,6 +42,7 @@ use OpenApi\Attributes as OA;
'discord_notifications_status_changes' => ['type' => 'boolean', 'description' => 'Whether to send status change notifications via Discord.'],
'discord_notifications_database_backups' => ['type' => 'boolean', 'description' => 'Whether to send database backup notifications via Discord.'],
'discord_notifications_scheduled_tasks' => ['type' => 'boolean', 'description' => 'Whether to send scheduled task notifications via Discord.'],
+ 'discord_notifications_server_disk_usage' => ['type' => 'boolean', 'description' => 'Whether to send server disk usage notifications via Discord.'],
'show_boarding' => ['type' => 'boolean', 'description' => 'Whether to show the boarding screen or not.'],
'resend_enabled' => ['type' => 'boolean', 'description' => 'Whether to enable resending or not.'],
'resend_api_key' => ['type' => 'string', 'description' => 'The resending API key.'],
@@ -56,6 +58,7 @@ use OpenApi\Attributes as OA;
'telegram_notifications_deployments_message_thread_id' => ['type' => 'string', 'description' => 'The Telegram deployment message thread ID.'],
'telegram_notifications_status_changes_message_thread_id' => ['type' => 'string', 'description' => 'The Telegram status change message thread ID.'],
'telegram_notifications_database_backups_message_thread_id' => ['type' => 'string', 'description' => 'The Telegram database backup message thread ID.'],
+
'custom_server_limit' => ['type' => 'string', 'description' => 'The custom server limit.'],
'telegram_notifications_scheduled_tasks' => ['type' => 'boolean', 'description' => 'Whether to send scheduled task notifications via Telegram.'],
'telegram_notifications_scheduled_tasks_thread_id' => ['type' => 'string', 'description' => 'The Telegram scheduled task message thread ID.'],
@@ -164,8 +167,12 @@ class Team extends Model implements SendsDiscord, SendsEmail
if (currentTeam()->id === 0 && isDev()) {
return 9999999;
}
+ $team = Team::find(currentTeam()->id);
+ if (! $team) {
+ return 0;
+ }
- return Team::find(currentTeam()->id)->limits['serverLimit'];
+ return data_get($team, 'limits.serverLimit', 0);
}
public function limits(): Attribute
diff --git a/app/Models/TeamInvitation.php b/app/Models/TeamInvitation.php
index c202710e2..0f298a829 100644
--- a/app/Models/TeamInvitation.php
+++ b/app/Models/TeamInvitation.php
@@ -20,6 +20,11 @@ class TeamInvitation extends Model
return $this->belongsTo(Team::class);
}
+ public static function ownedByCurrentTeam()
+ {
+ return TeamInvitation::whereTeamId(currentTeam()->id);
+ }
+
public function isValid()
{
$createdAt = $this->created_at;
diff --git a/app/Notifications/Application/DeploymentFailed.php b/app/Notifications/Application/DeploymentFailed.php
index 1809da368..242980e00 100644
--- a/app/Notifications/Application/DeploymentFailed.php
+++ b/app/Notifications/Application/DeploymentFailed.php
@@ -4,6 +4,7 @@ namespace App\Notifications\Application;
use App\Models\Application;
use App\Models\ApplicationPreview;
+use App\Notifications\Dto\DiscordMessage;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
@@ -72,14 +73,42 @@ class DeploymentFailed extends Notification implements ShouldQueue
return $mail;
}
- public function toDiscord(): string
+ public function toDiscord(): DiscordMessage
{
if ($this->preview) {
- $message = 'Coolify: Pull request #'.$this->preview->pull_request_id.' of '.$this->application_name.' ('.$this->preview->fqdn.') deployment failed: ';
- $message .= '[View Deployment Logs]('.$this->deployment_url.')';
+ $message = new DiscordMessage(
+ title: ':cross_mark: Deployment failed',
+ description: 'Pull request: '.$this->preview->pull_request_id,
+ color: DiscordMessage::errorColor(),
+ isCritical: true,
+ );
+
+ $message->addField('Project', data_get($this->application, 'environment.project.name'), true);
+ $message->addField('Environment', $this->environment_name, true);
+ $message->addField('Name', $this->application_name, true);
+
+ $message->addField('Deployment Logs', '[Link]('.$this->deployment_url.')');
+ if ($this->fqdn) {
+ $message->addField('Domain', $this->fqdn, true);
+ }
} else {
- $message = 'Coolify: Deployment failed of '.$this->application_name.' ('.$this->fqdn.'): ';
- $message .= '[View Deployment Logs]('.$this->deployment_url.')';
+ if ($this->fqdn) {
+ $description = '[Open application]('.$this->fqdn.')';
+ } else {
+ $description = '';
+ }
+ $message = new DiscordMessage(
+ title: ':cross_mark: Deployment failed',
+ description: $description,
+ color: DiscordMessage::errorColor(),
+ isCritical: true,
+ );
+
+ $message->addField('Project', data_get($this->application, 'environment.project.name'), true);
+ $message->addField('Environment', $this->environment_name, true);
+ $message->addField('Name', $this->application_name, true);
+
+ $message->addField('Deployment Logs', '[Link]('.$this->deployment_url.')');
}
return $message;
diff --git a/app/Notifications/Application/DeploymentSuccess.php b/app/Notifications/Application/DeploymentSuccess.php
index 5085065c2..a2674ad76 100644
--- a/app/Notifications/Application/DeploymentSuccess.php
+++ b/app/Notifications/Application/DeploymentSuccess.php
@@ -4,6 +4,7 @@ namespace App\Notifications\Application;
use App\Models\Application;
use App\Models\ApplicationPreview;
+use App\Notifications\Dto\DiscordMessage;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
@@ -78,24 +79,39 @@ class DeploymentSuccess extends Notification implements ShouldQueue
return $mail;
}
- public function toDiscord(): string
+ public function toDiscord(): DiscordMessage
{
if ($this->preview) {
- $message = 'Coolify: New PR'.$this->preview->pull_request_id.' version successfully deployed of '.$this->application_name.'
+ $message = new DiscordMessage(
+ title: ':white_check_mark: Preview deployment successful',
+ description: 'Pull request: '.$this->preview->pull_request_id,
+ color: DiscordMessage::successColor(),
+ );
-';
if ($this->preview->fqdn) {
- $message .= '[Open Application]('.$this->preview->fqdn.') | ';
+ $message->addField('Application', '[Link]('.$this->preview->fqdn.')');
}
- $message .= '[Deployment logs]('.$this->deployment_url.')';
- } else {
- $message = 'Coolify: New version successfully deployed of '.$this->application_name.'
-';
+ $message->addField('Project', data_get($this->application, 'environment.project.name'), true);
+ $message->addField('Environment', $this->environment_name, true);
+ $message->addField('Name', $this->application_name, true);
+ $message->addField('Deployment logs', '[Link]('.$this->deployment_url.')');
+ } else {
if ($this->fqdn) {
- $message .= '[Open Application]('.$this->fqdn.') | ';
+ $description = '[Open application]('.$this->fqdn.')';
+ } else {
+ $description = '';
}
- $message .= '[Deployment logs]('.$this->deployment_url.')';
+ $message = new DiscordMessage(
+ title: ':white_check_mark: New version successfully deployed',
+ description: $description,
+ color: DiscordMessage::successColor(),
+ );
+ $message->addField('Project', data_get($this->application, 'environment.project.name'), true);
+ $message->addField('Environment', $this->environment_name, true);
+ $message->addField('Name', $this->application_name, true);
+
+ $message->addField('Deployment logs', '[Link]('.$this->deployment_url.')');
}
return $message;
diff --git a/app/Notifications/Application/StatusChanged.php b/app/Notifications/Application/StatusChanged.php
index 53ed8a589..a080fcabe 100644
--- a/app/Notifications/Application/StatusChanged.php
+++ b/app/Notifications/Application/StatusChanged.php
@@ -3,6 +3,7 @@
namespace App\Notifications\Application;
use App\Models\Application;
+use App\Notifications\Dto\DiscordMessage;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
@@ -55,12 +56,14 @@ class StatusChanged extends Notification implements ShouldQueue
return $mail;
}
- public function toDiscord(): string
+ public function toDiscord(): DiscordMessage
{
- $message = 'Coolify: '.$this->resource_name.' has been stopped.
-
-';
- $message .= '[Open Application in Coolify]('.$this->resource_url.')';
+ $message = new DiscordMessage(
+ title: ':cross_mark: Application stopped',
+ description: '[Open Application in Coolify]('.$this->resource_url.')',
+ color: DiscordMessage::errorColor(),
+ isCritical: true,
+ );
return $message;
}
diff --git a/app/Notifications/Channels/DiscordChannel.php b/app/Notifications/Channels/DiscordChannel.php
index f1706f138..3a33d8902 100644
--- a/app/Notifications/Channels/DiscordChannel.php
+++ b/app/Notifications/Channels/DiscordChannel.php
@@ -12,7 +12,7 @@ class DiscordChannel
*/
public function send(SendsDiscord $notifiable, Notification $notification): void
{
- $message = $notification->toDiscord($notifiable);
+ $message = $notification->toDiscord();
$webhookUrl = $notifiable->routeNotificationForDiscord();
if (! $webhookUrl) {
return;
diff --git a/app/Notifications/Container/ContainerRestarted.php b/app/Notifications/Container/ContainerRestarted.php
index 23f6de264..182a1f5fc 100644
--- a/app/Notifications/Container/ContainerRestarted.php
+++ b/app/Notifications/Container/ContainerRestarted.php
@@ -3,6 +3,7 @@
namespace App\Notifications\Container;
use App\Models\Server;
+use App\Notifications\Dto\DiscordMessage;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
@@ -34,9 +35,17 @@ class ContainerRestarted extends Notification implements ShouldQueue
return $mail;
}
- public function toDiscord(): string
+ public function toDiscord(): DiscordMessage
{
- $message = "Coolify: A resource ({$this->name}) has been restarted automatically on {$this->server->name}";
+ $message = new DiscordMessage(
+ title: ':warning: Resource restarted',
+ description: "{$this->name} has been restarted automatically on {$this->server->name}.",
+ color: DiscordMessage::infoColor(),
+ );
+
+ if ($this->url) {
+ $message->addField('Resource', '[Link]('.$this->url.')');
+ }
return $message;
}
diff --git a/app/Notifications/Container/ContainerStopped.php b/app/Notifications/Container/ContainerStopped.php
index bcf5e67a5..33a55c65a 100644
--- a/app/Notifications/Container/ContainerStopped.php
+++ b/app/Notifications/Container/ContainerStopped.php
@@ -3,6 +3,7 @@
namespace App\Notifications\Container;
use App\Models\Server;
+use App\Notifications\Dto\DiscordMessage;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
@@ -34,9 +35,17 @@ class ContainerStopped extends Notification implements ShouldQueue
return $mail;
}
- public function toDiscord(): string
+ public function toDiscord(): DiscordMessage
{
- $message = "Coolify: A resource ($this->name) has been stopped unexpectedly on {$this->server->name}";
+ $message = new DiscordMessage(
+ title: ':cross_mark: Resource stopped',
+ description: "{$this->name} has been stopped unexpectedly on {$this->server->name}.",
+ color: DiscordMessage::errorColor(),
+ );
+
+ if ($this->url) {
+ $message->addField('Resource', '[Link]('.$this->url.')');
+ }
return $message;
}
diff --git a/app/Notifications/Database/BackupFailed.php b/app/Notifications/Database/BackupFailed.php
index 77024c05b..8e2733339 100644
--- a/app/Notifications/Database/BackupFailed.php
+++ b/app/Notifications/Database/BackupFailed.php
@@ -3,6 +3,7 @@
namespace App\Notifications\Database;
use App\Models\ScheduledDatabaseBackup;
+use App\Notifications\Dto\DiscordMessage;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
@@ -45,9 +46,19 @@ class BackupFailed extends Notification implements ShouldQueue
return $mail;
}
- public function toDiscord(): string
+ public function toDiscord(): DiscordMessage
{
- return "Coolify: Database backup for {$this->name} (db:{$this->database_name}) with frequency of {$this->frequency} was FAILED.\n\nReason:\n{$this->output}";
+ $message = new DiscordMessage(
+ title: ':cross_mark: Database backup failed',
+ description: "Database backup for {$this->name} (db:{$this->database_name}) has FAILED.",
+ color: DiscordMessage::errorColor(),
+ isCritical: true,
+ );
+
+ $message->addField('Frequency', $this->frequency, true);
+ $message->addField('Output', $this->output);
+
+ return $message;
}
public function toTelegram(): array
diff --git a/app/Notifications/Database/BackupSuccess.php b/app/Notifications/Database/BackupSuccess.php
index f8dc6eb56..4bd6b97b8 100644
--- a/app/Notifications/Database/BackupSuccess.php
+++ b/app/Notifications/Database/BackupSuccess.php
@@ -3,6 +3,7 @@
namespace App\Notifications\Database;
use App\Models\ScheduledDatabaseBackup;
+use App\Notifications\Dto\DiscordMessage;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
@@ -44,9 +45,17 @@ class BackupSuccess extends Notification implements ShouldQueue
return $mail;
}
- public function toDiscord(): string
+ public function toDiscord(): DiscordMessage
{
- return "Coolify: Database backup for {$this->name} (db:{$this->database_name}) with frequency of {$this->frequency} was successful.";
+ $message = new DiscordMessage(
+ title: ':white_check_mark: Database backup successful',
+ description: "Database backup for {$this->name} (db:{$this->database_name}) was successful.",
+ color: DiscordMessage::successColor(),
+ );
+
+ $message->addField('Frequency', $this->frequency, true);
+
+ return $message;
}
public function toTelegram(): array
diff --git a/app/Notifications/Database/DailyBackup.php b/app/Notifications/Database/DailyBackup.php
deleted file mode 100644
index a51ac6283..000000000
--- a/app/Notifications/Database/DailyBackup.php
+++ /dev/null
@@ -1,50 +0,0 @@
-subject('Coolify: Daily backup statuses');
- $mail->view('emails.daily-backup', [
- 'databases' => $this->databases,
- ]);
-
- return $mail;
- }
-
- public function toDiscord(): string
- {
- return 'Coolify: Daily backup statuses';
- }
-
- public function toTelegram(): array
- {
- $message = 'Coolify: Daily backup statuses';
-
- return [
- 'message' => $message,
- ];
- }
-}
diff --git a/app/Notifications/Dto/DiscordMessage.php b/app/Notifications/Dto/DiscordMessage.php
new file mode 100644
index 000000000..856753dca
--- /dev/null
+++ b/app/Notifications/Dto/DiscordMessage.php
@@ -0,0 +1,83 @@
+fields[] = [
+ 'name' => $name,
+ 'value' => $value,
+ 'inline' => $inline,
+ ];
+
+ return $this;
+ }
+
+ public function toPayload(): array
+ {
+ $footerText = 'Coolify v'.config('version');
+ if (isCloud()) {
+ $footerText = 'Coolify Cloud';
+ }
+ $payload = [
+ 'embeds' => [
+ [
+ 'title' => $this->title,
+ 'description' => $this->description,
+ 'color' => $this->color,
+ 'fields' => $this->addTimestampToFields($this->fields),
+ 'footer' => [
+ 'text' => $footerText,
+ ],
+ ],
+ ],
+ ];
+ if ($this->isCritical) {
+ $payload['content'] = '@here';
+ }
+
+ return $payload;
+ }
+
+ private function addTimestampToFields(array $fields): array
+ {
+ $fields[] = [
+ 'name' => 'Time',
+ 'value' => 'timestamp.':R>',
+ 'inline' => true,
+ ];
+
+ return $fields;
+ }
+}
diff --git a/app/Notifications/Internal/GeneralNotification.php b/app/Notifications/Internal/GeneralNotification.php
index 1d4d648c8..48e7d8340 100644
--- a/app/Notifications/Internal/GeneralNotification.php
+++ b/app/Notifications/Internal/GeneralNotification.php
@@ -4,6 +4,7 @@ namespace App\Notifications\Internal;
use App\Notifications\Channels\DiscordChannel;
use App\Notifications\Channels\TelegramChannel;
+use App\Notifications\Dto\DiscordMessage;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;
@@ -32,9 +33,13 @@ class GeneralNotification extends Notification implements ShouldQueue
return $channels;
}
- public function toDiscord(): string
+ public function toDiscord(): DiscordMessage
{
- return $this->message;
+ return new DiscordMessage(
+ title: 'Coolify: General Notification',
+ description: $this->message,
+ color: DiscordMessage::infoColor(),
+ );
}
public function toTelegram(): array
diff --git a/app/Notifications/ScheduledTask/TaskFailed.php b/app/Notifications/ScheduledTask/TaskFailed.php
index 479cc1aa1..2cc33f2ba 100644
--- a/app/Notifications/ScheduledTask/TaskFailed.php
+++ b/app/Notifications/ScheduledTask/TaskFailed.php
@@ -3,6 +3,7 @@
namespace App\Notifications\ScheduledTask;
use App\Models\ScheduledTask;
+use App\Notifications\Dto\DiscordMessage;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
@@ -46,9 +47,19 @@ class TaskFailed extends Notification implements ShouldQueue
return $mail;
}
- public function toDiscord(): string
+ public function toDiscord(): DiscordMessage
{
- return "Coolify: Scheduled task ({$this->task->name}, [link]({$this->url})) failed with output: {$this->output}";
+ $message = new DiscordMessage(
+ title: ':cross_mark: Scheduled task failed',
+ description: "Scheduled task ({$this->task->name}) failed.",
+ color: DiscordMessage::errorColor(),
+ );
+
+ if ($this->url) {
+ $message->addField('Scheduled task', '[Link]('.$this->url.')');
+ }
+
+ return $message;
}
public function toTelegram(): array
diff --git a/app/Notifications/Server/DockerCleanup.php b/app/Notifications/Server/DockerCleanup.php
index 682ed7a1a..7ea1b84c2 100644
--- a/app/Notifications/Server/DockerCleanup.php
+++ b/app/Notifications/Server/DockerCleanup.php
@@ -5,6 +5,7 @@ namespace App\Notifications\Server;
use App\Models\Server;
use App\Notifications\Channels\DiscordChannel;
use App\Notifications\Channels\TelegramChannel;
+use App\Notifications\Dto\DiscordMessage;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;
@@ -49,11 +50,13 @@ class DockerCleanup extends Notification implements ShouldQueue
// return $mail;
// }
- public function toDiscord(): string
+ public function toDiscord(): DiscordMessage
{
- $message = "Coolify: Server '{$this->server->name}' cleanup job done!\n\n{$this->message}";
-
- return $message;
+ return new DiscordMessage(
+ title: ':white_check_mark: Server cleanup job done',
+ description: $this->message,
+ color: DiscordMessage::successColor(),
+ );
}
public function toTelegram(): array
diff --git a/app/Notifications/Server/ForceDisabled.php b/app/Notifications/Server/ForceDisabled.php
index 6377f2f15..a26c803ee 100644
--- a/app/Notifications/Server/ForceDisabled.php
+++ b/app/Notifications/Server/ForceDisabled.php
@@ -6,6 +6,7 @@ use App\Models\Server;
use App\Notifications\Channels\DiscordChannel;
use App\Notifications\Channels\EmailChannel;
use App\Notifications\Channels\TelegramChannel;
+use App\Notifications\Dto\DiscordMessage;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
@@ -50,9 +51,15 @@ class ForceDisabled extends Notification implements ShouldQueue
return $mail;
}
- public function toDiscord(): string
+ public function toDiscord(): DiscordMessage
{
- $message = "Coolify: Server ({$this->server->name}) disabled because it is not paid!\n All automations and integrations are stopped.\nPlease update your subscription to enable the server again [here](https://app.coolify.io/subscriptions).";
+ $message = new DiscordMessage(
+ title: ':cross_mark: Server disabled',
+ description: "Server ({$this->server->name}) disabled because it is not paid!",
+ color: DiscordMessage::errorColor(),
+ );
+
+ $message->addField('Please update your subscription to enable the server again!', '[Link](https://app.coolify.io/subscriptions)');
return $message;
}
diff --git a/app/Notifications/Server/ForceEnabled.php b/app/Notifications/Server/ForceEnabled.php
index 83594d643..65b65a10c 100644
--- a/app/Notifications/Server/ForceEnabled.php
+++ b/app/Notifications/Server/ForceEnabled.php
@@ -6,6 +6,7 @@ use App\Models\Server;
use App\Notifications\Channels\DiscordChannel;
use App\Notifications\Channels\EmailChannel;
use App\Notifications\Channels\TelegramChannel;
+use App\Notifications\Dto\DiscordMessage;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
@@ -50,11 +51,13 @@ class ForceEnabled extends Notification implements ShouldQueue
return $mail;
}
- public function toDiscord(): string
+ public function toDiscord(): DiscordMessage
{
- $message = "Coolify: Server ({$this->server->name}) enabled again!";
-
- return $message;
+ return new DiscordMessage(
+ title: ':white_check_mark: Server enabled',
+ description: "Server '{$this->server->name}' enabled again!",
+ color: DiscordMessage::successColor(),
+ );
}
public function toTelegram(): array
diff --git a/app/Notifications/Server/HighDiskUsage.php b/app/Notifications/Server/HighDiskUsage.php
index 34cb22091..e373abc03 100644
--- a/app/Notifications/Server/HighDiskUsage.php
+++ b/app/Notifications/Server/HighDiskUsage.php
@@ -3,9 +3,7 @@
namespace App\Notifications\Server;
use App\Models\Server;
-use App\Notifications\Channels\DiscordChannel;
-use App\Notifications\Channels\EmailChannel;
-use App\Notifications\Channels\TelegramChannel;
+use App\Notifications\Dto\DiscordMessage;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
@@ -17,26 +15,11 @@ class HighDiskUsage extends Notification implements ShouldQueue
public $tries = 1;
- public function __construct(public Server $server, public int $disk_usage, public int $docker_cleanup_threshold) {}
+ public function __construct(public Server $server, public int $disk_usage, public int $server_disk_usage_notification_threshold) {}
public function via(object $notifiable): array
{
- $channels = [];
- $isEmailEnabled = isEmailEnabled($notifiable);
- $isDiscordEnabled = data_get($notifiable, 'discord_enabled');
- $isTelegramEnabled = data_get($notifiable, 'telegram_enabled');
-
- if ($isDiscordEnabled) {
- $channels[] = DiscordChannel::class;
- }
- if ($isEmailEnabled) {
- $channels[] = EmailChannel::class;
- }
- if ($isTelegramEnabled) {
- $channels[] = TelegramChannel::class;
- }
-
- return $channels;
+ return setNotificationChannels($notifiable, 'server_disk_usage');
}
public function toMail(): MailMessage
@@ -46,15 +29,25 @@ class HighDiskUsage extends Notification implements ShouldQueue
$mail->view('emails.high-disk-usage', [
'name' => $this->server->name,
'disk_usage' => $this->disk_usage,
- 'threshold' => $this->docker_cleanup_threshold,
+ 'threshold' => $this->server_disk_usage_notification_threshold,
]);
return $mail;
}
- public function toDiscord(): string
+ public function toDiscord(): DiscordMessage
{
- $message = "Coolify: Server '{$this->server->name}' high disk usage detected!\nDisk usage: {$this->disk_usage}%. Threshold: {$this->docker_cleanup_threshold}%.\nPlease cleanup your disk to prevent data-loss.\nHere are some tips: https://coolify.io/docs/knowledge-base/server/automated-cleanup.";
+ $message = new DiscordMessage(
+ title: ':cross_mark: High disk usage detected',
+ description: "Server '{$this->server->name}' high disk usage detected!",
+ color: DiscordMessage::errorColor(),
+ isCritical: true,
+ );
+
+ $message->addField('Disk usage', "{$this->disk_usage}%", true);
+ $message->addField('Threshold', "{$this->server_disk_usage_notification_threshold}%", true);
+ $message->addField('What to do?', '[Link](https://coolify.io/docs/knowledge-base/server/automated-cleanup)', true);
+ $message->addField('Change Settings', '[Threshold]('.base_url().'/server/'.$this->server->uuid.'#advanced) | [Notification]('.base_url().'/notifications/discord)');
return $message;
}
@@ -62,7 +55,7 @@ class HighDiskUsage extends Notification implements ShouldQueue
public function toTelegram(): array
{
return [
- 'message' => "Coolify: Server '{$this->server->name}' high disk usage detected!\nDisk usage: {$this->disk_usage}%. Threshold: {$this->docker_cleanup_threshold}%.\nPlease cleanup your disk to prevent data-loss.\nHere are some tips: https://coolify.io/docs/knowledge-base/server/automated-cleanup.",
+ 'message' => "Coolify: Server '{$this->server->name}' high disk usage detected!\nDisk usage: {$this->disk_usage}%. Threshold: {$this->server_disk_usage_notification_threshold}%.\nPlease cleanup your disk to prevent data-loss.\nHere are some tips: https://coolify.io/docs/knowledge-base/server/automated-cleanup.",
];
}
}
diff --git a/app/Notifications/Server/Revived.php b/app/Notifications/Server/Reachable.php
similarity index 64%
rename from app/Notifications/Server/Revived.php
rename to app/Notifications/Server/Reachable.php
index 3f2b3b696..9b54501d9 100644
--- a/app/Notifications/Server/Revived.php
+++ b/app/Notifications/Server/Reachable.php
@@ -2,35 +2,37 @@
namespace App\Notifications\Server;
-use App\Actions\Docker\GetContainersStatus;
-use App\Jobs\ContainerStatusJob;
use App\Models\Server;
use App\Notifications\Channels\DiscordChannel;
use App\Notifications\Channels\EmailChannel;
use App\Notifications\Channels\TelegramChannel;
+use App\Notifications\Dto\DiscordMessage;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
-use Illuminate\Support\Facades\RateLimiter;
-class Revived extends Notification implements ShouldQueue
+class Reachable extends Notification implements ShouldQueue
{
use Queueable;
public $tries = 1;
+ protected bool $isRateLimited = false;
+
public function __construct(public Server $server)
{
- if ($this->server->unreachable_notification_sent === false) {
- return;
- }
- GetContainersStatus::dispatch($server)->onQueue('high');
- // dispatch(new ContainerStatusJob($server));
+ $this->isRateLimited = isEmailRateLimited(
+ limiterKey: 'server-reachable:'.$this->server->id,
+ );
}
public function via(object $notifiable): array
{
+ if ($this->isRateLimited) {
+ return [];
+ }
+
$channels = [];
$isEmailEnabled = isEmailEnabled($notifiable);
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
@@ -45,20 +47,8 @@ class Revived extends Notification implements ShouldQueue
if ($isTelegramEnabled) {
$channels[] = TelegramChannel::class;
}
- $executed = RateLimiter::attempt(
- 'notification-server-revived-'.$this->server->uuid,
- 1,
- function () use ($channels) {
- return $channels;
- },
- 7200,
- );
- if (! $executed) {
- return [];
- }
-
- return $executed;
+ return $channels;
}
public function toMail(): MailMessage
@@ -72,11 +62,13 @@ class Revived extends Notification implements ShouldQueue
return $mail;
}
- public function toDiscord(): string
+ public function toDiscord(): DiscordMessage
{
- $message = "Coolify: Server '{$this->server->name}' revived. All automations & integrations are turned on again!";
-
- return $message;
+ return new DiscordMessage(
+ title: ":white_check_mark: Server '{$this->server->name}' revived",
+ description: 'All automations & integrations are turned on again!',
+ color: DiscordMessage::successColor(),
+ );
}
public function toTelegram(): array
diff --git a/app/Notifications/Server/Unreachable.php b/app/Notifications/Server/Unreachable.php
index 2fb83559a..5bc568e82 100644
--- a/app/Notifications/Server/Unreachable.php
+++ b/app/Notifications/Server/Unreachable.php
@@ -6,11 +6,11 @@ use App\Models\Server;
use App\Notifications\Channels\DiscordChannel;
use App\Notifications\Channels\EmailChannel;
use App\Notifications\Channels\TelegramChannel;
+use App\Notifications\Dto\DiscordMessage;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
-use Illuminate\Support\Facades\RateLimiter;
class Unreachable extends Notification implements ShouldQueue
{
@@ -18,10 +18,21 @@ class Unreachable extends Notification implements ShouldQueue
public $tries = 1;
- public function __construct(public Server $server) {}
+ protected bool $isRateLimited = false;
+
+ public function __construct(public Server $server)
+ {
+ $this->isRateLimited = isEmailRateLimited(
+ limiterKey: 'server-unreachable:'.$this->server->id,
+ );
+ }
public function via(object $notifiable): array
{
+ if ($this->isRateLimited) {
+ return [];
+ }
+
$channels = [];
$isEmailEnabled = isEmailEnabled($notifiable);
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
@@ -36,23 +47,11 @@ class Unreachable extends Notification implements ShouldQueue
if ($isTelegramEnabled) {
$channels[] = TelegramChannel::class;
}
- $executed = RateLimiter::attempt(
- 'notification-server-unreachable-'.$this->server->uuid,
- 1,
- function () use ($channels) {
- return $channels;
- },
- 7200,
- );
- if (! $executed) {
- return [];
- }
-
- return $executed;
+ return $channels;
}
- public function toMail(): MailMessage
+ public function toMail(): ?MailMessage
{
$mail = new MailMessage;
$mail->subject("Coolify: Your server ({$this->server->name}) is unreachable.");
@@ -63,14 +62,20 @@ class Unreachable extends Notification implements ShouldQueue
return $mail;
}
- public function toDiscord(): string
+ public function toDiscord(): ?DiscordMessage
{
- $message = "Coolify: Your server '{$this->server->name}' is unreachable. All automations & integrations are turned off! Please check your server! IMPORTANT: We automatically try to revive your server and turn on all automations & integrations.";
+ $message = new DiscordMessage(
+ title: ':cross_mark: Server unreachable',
+ description: "Your server '{$this->server->name}' is unreachable.",
+ color: DiscordMessage::errorColor(),
+ );
+
+ $message->addField('IMPORTANT', 'We automatically try to revive your server and turn on all automations & integrations.');
return $message;
}
- public function toTelegram(): array
+ public function toTelegram(): ?array
{
return [
'message' => "Coolify: Your server '{$this->server->name}' is unreachable. All automations & integrations are turned off! Please check your server! IMPORTANT: We automatically try to revive your server and turn on all automations & integrations.",
diff --git a/app/Notifications/Test.php b/app/Notifications/Test.php
index 3b46a9a24..4c069ffd6 100644
--- a/app/Notifications/Test.php
+++ b/app/Notifications/Test.php
@@ -2,10 +2,12 @@
namespace App\Notifications;
+use App\Notifications\Dto\DiscordMessage;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
+use Illuminate\Queue\Middleware\RateLimited;
class Test extends Notification implements ShouldQueue
{
@@ -20,6 +22,14 @@ class Test extends Notification implements ShouldQueue
return setNotificationChannels($notifiable, 'test');
}
+ public function middleware(object $notifiable, string $channel)
+ {
+ return match ($channel) {
+ 'App\Notifications\Channels\EmailChannel' => [new RateLimited('email')],
+ default => [],
+ };
+ }
+
public function toMail(): MailMessage
{
$mail = new MailMessage;
@@ -29,11 +39,15 @@ class Test extends Notification implements ShouldQueue
return $mail;
}
- public function toDiscord(): string
+ public function toDiscord(): DiscordMessage
{
- $message = 'Coolify: This is a test Discord notification from Coolify.';
- $message .= "\n\n";
- $message .= '[Go to your dashboard]('.base_url().')';
+ $message = new DiscordMessage(
+ title: ':white_check_mark: Test Success',
+ description: 'This is a test Discord notification from Coolify. :cross_mark: :warning: :information_source:',
+ color: DiscordMessage::successColor(),
+ );
+
+ $message->addField(name: 'Dashboard', value: '[Link]('.base_url().')', inline: true);
return $message;
}
diff --git a/bootstrap/helpers/databases.php b/bootstrap/helpers/databases.php
index 950eb67b6..e12910f82 100644
--- a/bootstrap/helpers/databases.php
+++ b/bootstrap/helpers/databases.php
@@ -1,5 +1,6 @@
name = generate_database_name('redis');
- $database->redis_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
+ $redis_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
$database->environment_id = $environment_id;
$database->destination_id = $destination->id;
$database->destination_type = $destination->getMorphClass();
@@ -57,6 +58,20 @@ function create_standalone_redis($environment_id, $destination_uuid, ?array $oth
}
$database->save();
+ EnvironmentVariable::create([
+ 'key' => 'REDIS_PASSWORD',
+ 'value' => $redis_password,
+ 'standalone_redis_id' => $database->id,
+ 'is_shared' => false,
+ ]);
+
+ EnvironmentVariable::create([
+ 'key' => 'REDIS_USERNAME',
+ 'value' => 'default',
+ 'standalone_redis_id' => $database->id,
+ 'is_shared' => false,
+ ]);
+
return $database;
}
diff --git a/bootstrap/helpers/proxy.php b/bootstrap/helpers/proxy.php
index 5d1ad5390..496017217 100644
--- a/bootstrap/helpers/proxy.php
+++ b/bootstrap/helpers/proxy.php
@@ -164,6 +164,7 @@ function generate_default_proxy_configuration(Server $server)
'ports' => [
'80:80',
'443:443',
+ '443:443/udp',
'8080:8080',
],
'healthcheck' => [
@@ -187,6 +188,7 @@ function generate_default_proxy_configuration(Server $server)
'--entryPoints.http.http2.maxConcurrentStreams=50',
'--entrypoints.https.http.encodequerysemicolons=true',
'--entryPoints.https.http2.maxConcurrentStreams=50',
+ '--entrypoints.https.http3',
'--providers.docker.exposedbydefault=false',
'--providers.file.directory=/traefik/dynamic/',
'--providers.file.watch=true',
@@ -239,9 +241,11 @@ function generate_default_proxy_configuration(Server $server)
'ports' => [
'80:80',
'443:443',
+ '443:443/udp',
],
'labels' => [
'coolify.managed=true',
+ 'coolify.proxy=true',
],
'volumes' => [
'/var/run/docker.sock:/var/run/docker.sock:ro',
diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php
index f8bd1ffbd..dbab6861d 100644
--- a/bootstrap/helpers/shared.php
+++ b/bootstrap/helpers/shared.php
@@ -39,6 +39,7 @@ use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Process;
+use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Facades\Request;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Validator;
@@ -126,7 +127,7 @@ function refreshSession(?Team $team = null): void
}
function handleError(?Throwable $error = null, ?Livewire\Component $livewire = null, ?string $customErrorMessage = null)
{
- ray($error);
+ loggy($error);
if ($error instanceof TooManyRequestsException) {
if (isset($livewire)) {
return $livewire->dispatch('error', "Too many requests. Please try again in {$error->secondsUntilAvailable} seconds.");
@@ -142,6 +143,10 @@ function handleError(?Throwable $error = null, ?Livewire\Component $livewire = n
return 'Duplicate entry found. Please use a different name.';
}
+ if ($error instanceof \Illuminate\Database\Eloquent\ModelNotFoundException) {
+ abort(404);
+ }
+
if ($error instanceof Throwable) {
$message = $error->getMessage();
} else {
@@ -164,14 +169,11 @@ function get_route_parameters(): array
function get_latest_sentinel_version(): string
{
try {
- $response = Http::get('https://cdn.coollabs.io/sentinel/versions.json');
+ $response = Http::get('https://cdn.coollabs.io/coolify/versions.json');
$versions = $response->json();
- return data_get($versions, 'sentinel.version');
+ return data_get($versions, 'coolify.sentinel.version');
} catch (\Throwable $e) {
- //throw $e;
- ray($e->getMessage());
-
return '0.0.0';
}
}
@@ -643,7 +645,7 @@ function queryResourcesByUuid(string $uuid)
return $resource;
}
-function generatTagDeployWebhook($tag_name)
+function generateTagDeployWebhook($tag_name)
{
$baseUrl = base_url();
$api = Url::fromString($baseUrl).'/api/v1';
@@ -1338,13 +1340,6 @@ function isAnyDeploymentInprogress()
exit(0);
}
-function generateSentinelToken()
-{
- $token = Str::random(64);
-
- return $token;
-}
-
function isBase64Encoded($strValue)
{
return base64_encode(base64_decode($strValue, true)) === $strValue;
@@ -3569,6 +3564,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
]);
} else {
if ($value->startsWith('$')) {
+ $isRequired = false;
if ($value->contains(':-')) {
$value = replaceVariables($value);
$key = $value->before(':');
@@ -3583,11 +3579,13 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
$key = $value->before(':');
$value = $value->after(':?');
+ $isRequired = true;
} elseif ($value->contains('?')) {
$value = replaceVariables($value);
$key = $value->before('?');
$value = $value->after('?');
+ $isRequired = true;
}
if ($originalValue->value() === $value->value()) {
// This means the variable does not have a default value, so it needs to be created in Coolify
@@ -3598,6 +3596,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
], [
'is_build_time' => false,
'is_preview' => false,
+ 'is_required' => $isRequired,
]);
// Add the variable to the environment so it will be shown in the deployable compose file
$environment[$parsedKeyValue->value()] = $resource->environment_variables()->where('key', $parsedKeyValue)->where($nameOfId, $resource->id)->first()->value;
@@ -3611,6 +3610,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
'value' => $value,
'is_build_time' => false,
'is_preview' => false,
+ 'is_required' => $isRequired,
]);
}
@@ -3787,7 +3787,6 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
service_name: $serviceName,
image: $image,
predefinedPort: $predefinedPort
-
));
}
}
@@ -3896,6 +3895,8 @@ function isAssociativeArray($array)
*/
function add_coolify_default_environment_variables(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse|Application|Service $resource, Collection &$where_to_add, ?Collection $where_to_check = null)
{
+ // Currently disabled
+ return;
if ($resource instanceof Service) {
$ip = $resource->server->ip;
} else {
@@ -3983,13 +3984,14 @@ function instanceSettings()
return InstanceSettings::get();
}
-function loadConfigFromGit(string $repository, string $branch, string $base_directory, int $server_id, int $team_id) {
+function loadConfigFromGit(string $repository, string $branch, string $base_directory, int $server_id, int $team_id)
+{
$server = Server::find($server_id)->where('team_id', $team_id)->first();
- if (!$server) {
+ if (! $server) {
return;
}
- $uuid = new Cuid2();
+ $uuid = new Cuid2;
$cloneCommand = "git clone --no-checkout -b $branch $repository .";
$workdir = rtrim($base_directory, '/');
$fileList = collect([".$workdir/coolify.json"]);
@@ -4007,6 +4009,60 @@ function loadConfigFromGit(string $repository, string $branch, string $base_dire
try {
return instant_remote_process($commands, $server);
} catch (\Exception $e) {
- // continue
+ // continue
}
}
+
+function loggy($message = null, array $context = [])
+{
+ if (! isDev()) {
+ return;
+ }
+ if (function_exists('ray') && config('app.debug')) {
+ ray($message, $context);
+ }
+ if (is_null($message)) {
+ return app('log');
+ }
+
+ return app('log')->debug($message, $context);
+}
+function sslipDomainWarning(string $domains)
+{
+ $domains = str($domains)->trim()->explode(',');
+ $showSslipHttpsWarning = false;
+ $domains->each(function ($domain) use (&$showSslipHttpsWarning) {
+ if (str($domain)->contains('https') && str($domain)->contains('sslip')) {
+ $showSslipHttpsWarning = true;
+ }
+ });
+
+ return $showSslipHttpsWarning;
+}
+
+function isEmailRateLimited(string $limiterKey, int $decaySeconds = 3600, ?callable $callbackOnSuccess = null): bool
+{
+ if (isDev()) {
+ $decaySeconds = 120;
+ }
+ $rateLimited = false;
+ $executed = RateLimiter::attempt(
+ $limiterKey,
+ $maxAttempts = 0,
+ function () use (&$rateLimited, &$limiterKey, $callbackOnSuccess) {
+ isDev() && loggy('Rate limit not reached for '.$limiterKey);
+ $rateLimited = false;
+
+ if ($callbackOnSuccess) {
+ $callbackOnSuccess();
+ }
+ },
+ $decaySeconds,
+ );
+ if (! $executed) {
+ isDev() && loggy('Rate limit reached for '.$limiterKey.'. Rate limiter will be disabled for '.$decaySeconds.' seconds.');
+ $rateLimited = true;
+ }
+
+ return $rateLimited;
+}
diff --git a/composer.json b/composer.json
index 03adf9823..b17c3bf4e 100644
--- a/composer.json
+++ b/composer.json
@@ -14,7 +14,8 @@
"guzzlehttp/guzzle": "^7.5.0",
"laravel/fortify": "^v1.16.0",
"laravel/framework": "^v11",
- "laravel/horizon": "^5.27.1",
+ "laravel/horizon": "^5.29.1",
+ "laravel/pail": "^1.1",
"laravel/prompts": "^0.1.6",
"laravel/sanctum": "^v4.0",
"laravel/socialite": "^v5.14.0",
diff --git a/composer.lock b/composer.lock
index 420d87ec0..981e723d4 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "42c28ab141b70fcabf75b51afa96c670",
+ "content-hash": "943975ec232403b96a40d215253492d8",
"packages": [
{
"name": "amphp/amp",
@@ -317,16 +317,16 @@
},
{
"name": "amphp/parallel",
- "version": "v2.2.9",
+ "version": "v2.3.0",
"source": {
"type": "git",
"url": "https://github.com/amphp/parallel.git",
- "reference": "73d293f1fc4df1bebc3c4fce1432e82dd7032238"
+ "reference": "9777db1460d1535bc2a843840684fb1205225b87"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/amphp/parallel/zipball/73d293f1fc4df1bebc3c4fce1432e82dd7032238",
- "reference": "73d293f1fc4df1bebc3c4fce1432e82dd7032238",
+ "url": "https://api.github.com/repos/amphp/parallel/zipball/9777db1460d1535bc2a843840684fb1205225b87",
+ "reference": "9777db1460d1535bc2a843840684fb1205225b87",
"shasum": ""
},
"require": {
@@ -389,7 +389,7 @@
],
"support": {
"issues": "https://github.com/amphp/parallel/issues",
- "source": "https://github.com/amphp/parallel/tree/v2.2.9"
+ "source": "https://github.com/amphp/parallel/tree/v2.3.0"
},
"funding": [
{
@@ -397,7 +397,7 @@
"type": "github"
}
],
- "time": "2024-03-24T18:27:44+00:00"
+ "time": "2024-09-14T19:16:14+00:00"
},
{
"name": "amphp/parser",
@@ -921,16 +921,16 @@
},
{
"name": "aws/aws-sdk-php",
- "version": "3.321.9",
+ "version": "3.324.0",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
- "reference": "5de5099cfe0e17cb3eb2fe51de0101c99bc9442a"
+ "reference": "b258712f0d986e00e1143d55246b6f9e344c7184"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/5de5099cfe0e17cb3eb2fe51de0101c99bc9442a",
- "reference": "5de5099cfe0e17cb3eb2fe51de0101c99bc9442a",
+ "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/b258712f0d986e00e1143d55246b6f9e344c7184",
+ "reference": "b258712f0d986e00e1143d55246b6f9e344c7184",
"shasum": ""
},
"require": {
@@ -1013,22 +1013,22 @@
"support": {
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
"issues": "https://github.com/aws/aws-sdk-php/issues",
- "source": "https://github.com/aws/aws-sdk-php/tree/3.321.9"
+ "source": "https://github.com/aws/aws-sdk-php/tree/3.324.0"
},
- "time": "2024-09-11T18:15:49+00:00"
+ "time": "2024-10-10T18:06:36+00:00"
},
{
"name": "bacon/bacon-qr-code",
- "version": "v3.0.0",
+ "version": "v3.0.1",
"source": {
"type": "git",
"url": "https://github.com/Bacon/BaconQrCode.git",
- "reference": "510de6eca6248d77d31b339d62437cc995e2fb41"
+ "reference": "f9cc1f52b5a463062251d666761178dbdb6b544f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/510de6eca6248d77d31b339d62437cc995e2fb41",
- "reference": "510de6eca6248d77d31b339d62437cc995e2fb41",
+ "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/f9cc1f52b5a463062251d666761178dbdb6b544f",
+ "reference": "f9cc1f52b5a463062251d666761178dbdb6b544f",
"shasum": ""
},
"require": {
@@ -1067,9 +1067,9 @@
"homepage": "https://github.com/Bacon/BaconQrCode",
"support": {
"issues": "https://github.com/Bacon/BaconQrCode/issues",
- "source": "https://github.com/Bacon/BaconQrCode/tree/v3.0.0"
+ "source": "https://github.com/Bacon/BaconQrCode/tree/v3.0.1"
},
- "time": "2024-04-18T11:16:25+00:00"
+ "time": "2024-10-01T13:55:55+00:00"
},
{
"name": "brick/math",
@@ -1518,16 +1518,16 @@
},
{
"name": "doctrine/dbal",
- "version": "3.9.1",
+ "version": "3.9.3",
"source": {
"type": "git",
"url": "https://github.com/doctrine/dbal.git",
- "reference": "d7dc08f98cba352b2bab5d32c5e58f7e745c11a7"
+ "reference": "61446f07fcb522414d6cfd8b1c3e5f9e18c579ba"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/doctrine/dbal/zipball/d7dc08f98cba352b2bab5d32c5e58f7e745c11a7",
- "reference": "d7dc08f98cba352b2bab5d32c5e58f7e745c11a7",
+ "url": "https://api.github.com/repos/doctrine/dbal/zipball/61446f07fcb522414d6cfd8b1c3e5f9e18c579ba",
+ "reference": "61446f07fcb522414d6cfd8b1c3e5f9e18c579ba",
"shasum": ""
},
"require": {
@@ -1543,7 +1543,7 @@
"doctrine/coding-standard": "12.0.0",
"fig/log-test": "^1",
"jetbrains/phpstorm-stubs": "2023.1",
- "phpstan/phpstan": "1.12.0",
+ "phpstan/phpstan": "1.12.6",
"phpstan/phpstan-strict-rules": "^1.6",
"phpunit/phpunit": "9.6.20",
"psalm/plugin-phpunit": "0.18.4",
@@ -1611,7 +1611,7 @@
],
"support": {
"issues": "https://github.com/doctrine/dbal/issues",
- "source": "https://github.com/doctrine/dbal/tree/3.9.1"
+ "source": "https://github.com/doctrine/dbal/tree/3.9.3"
},
"funding": [
{
@@ -1627,7 +1627,7 @@
"type": "tidelift"
}
],
- "time": "2024-09-01T13:49:23+00:00"
+ "time": "2024-10-10T17:56:43+00:00"
},
{
"name": "doctrine/deprecations",
@@ -1937,16 +1937,16 @@
},
{
"name": "dragonmantank/cron-expression",
- "version": "v3.3.3",
+ "version": "v3.4.0",
"source": {
"type": "git",
"url": "https://github.com/dragonmantank/cron-expression.git",
- "reference": "adfb1f505deb6384dc8b39804c5065dd3c8c8c0a"
+ "reference": "8c784d071debd117328803d86b2097615b457500"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/adfb1f505deb6384dc8b39804c5065dd3c8c8c0a",
- "reference": "adfb1f505deb6384dc8b39804c5065dd3c8c8c0a",
+ "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/8c784d071debd117328803d86b2097615b457500",
+ "reference": "8c784d071debd117328803d86b2097615b457500",
"shasum": ""
},
"require": {
@@ -1959,10 +1959,14 @@
"require-dev": {
"phpstan/extension-installer": "^1.0",
"phpstan/phpstan": "^1.0",
- "phpstan/phpstan-webmozart-assert": "^1.0",
"phpunit/phpunit": "^7.0|^8.0|^9.0"
},
"type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.x-dev"
+ }
+ },
"autoload": {
"psr-4": {
"Cron\\": "src/Cron/"
@@ -1986,7 +1990,7 @@
],
"support": {
"issues": "https://github.com/dragonmantank/cron-expression/issues",
- "source": "https://github.com/dragonmantank/cron-expression/tree/v3.3.3"
+ "source": "https://github.com/dragonmantank/cron-expression/tree/v3.4.0"
},
"funding": [
{
@@ -1994,7 +1998,7 @@
"type": "github"
}
],
- "time": "2023-08-10T19:36:49+00:00"
+ "time": "2024-10-09T13:47:03+00:00"
},
{
"name": "egulias/email-validator",
@@ -2789,16 +2793,16 @@
},
{
"name": "laravel/fortify",
- "version": "v1.24.1",
+ "version": "v1.24.2",
"source": {
"type": "git",
"url": "https://github.com/laravel/fortify.git",
- "reference": "8158ba0960bb5f4aae509d01d74a95e16e30de20"
+ "reference": "42695c45087e5abb3e173725b4f1ef4956a7b47d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/fortify/zipball/8158ba0960bb5f4aae509d01d74a95e16e30de20",
- "reference": "8158ba0960bb5f4aae509d01d74a95e16e30de20",
+ "url": "https://api.github.com/repos/laravel/fortify/zipball/42695c45087e5abb3e173725b4f1ef4956a7b47d",
+ "reference": "42695c45087e5abb3e173725b4f1ef4956a7b47d",
"shasum": ""
},
"require": {
@@ -2850,20 +2854,20 @@
"issues": "https://github.com/laravel/fortify/issues",
"source": "https://github.com/laravel/fortify"
},
- "time": "2024-09-03T10:02:14+00:00"
+ "time": "2024-09-16T19:20:52+00:00"
},
{
"name": "laravel/framework",
- "version": "v11.23.2",
+ "version": "v11.27.2",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
- "reference": "d38bf0fd3a8936e1cb9ca8eb8d7304a564f790f3"
+ "reference": "a51d1f2b771c542324a3d9b76a98b1bbc75c0ee9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/framework/zipball/d38bf0fd3a8936e1cb9ca8eb8d7304a564f790f3",
- "reference": "d38bf0fd3a8936e1cb9ca8eb8d7304a564f790f3",
+ "url": "https://api.github.com/repos/laravel/framework/zipball/a51d1f2b771c542324a3d9b76a98b1bbc75c0ee9",
+ "reference": "a51d1f2b771c542324a3d9b76a98b1bbc75c0ee9",
"shasum": ""
},
"require": {
@@ -2882,7 +2886,7 @@
"fruitcake/php-cors": "^1.3",
"guzzlehttp/guzzle": "^7.8",
"guzzlehttp/uri-template": "^1.0",
- "laravel/prompts": "^0.1.18",
+ "laravel/prompts": "^0.1.18|^0.2.0|^0.3.0",
"laravel/serializable-closure": "^1.3",
"league/commonmark": "^2.2.1",
"league/flysystem": "^3.8.0",
@@ -2968,7 +2972,7 @@
"league/flysystem-sftp-v3": "^3.0",
"mockery/mockery": "^1.6",
"nyholm/psr7": "^1.2",
- "orchestra/testbench-core": "^9.4.0",
+ "orchestra/testbench-core": "^9.5",
"pda/pheanstalk": "^5.0",
"phpstan/phpstan": "^1.11.5",
"phpunit/phpunit": "^10.5|^11.0",
@@ -3027,6 +3031,7 @@
"src/Illuminate/Filesystem/functions.php",
"src/Illuminate/Foundation/helpers.php",
"src/Illuminate/Log/functions.php",
+ "src/Illuminate/Support/functions.php",
"src/Illuminate/Support/helpers.php"
],
"psr-4": {
@@ -3058,20 +3063,20 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
- "time": "2024-09-11T21:59:23+00:00"
+ "time": "2024-10-09T04:17:35+00:00"
},
{
"name": "laravel/horizon",
- "version": "v5.28.1",
+ "version": "v5.29.1",
"source": {
"type": "git",
"url": "https://github.com/laravel/horizon.git",
- "reference": "9d2c4eaeb11408384401f8a7d1b0ea4c76554f3f"
+ "reference": "9f482f21c23ed01c2366d1157843165165579c23"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/horizon/zipball/9d2c4eaeb11408384401f8a7d1b0ea4c76554f3f",
- "reference": "9d2c4eaeb11408384401f8a7d1b0ea4c76554f3f",
+ "url": "https://api.github.com/repos/laravel/horizon/zipball/9f482f21c23ed01c2366d1157843165165579c23",
+ "reference": "9f482f21c23ed01c2366d1157843165165579c23",
"shasum": ""
},
"require": {
@@ -3135,9 +3140,86 @@
],
"support": {
"issues": "https://github.com/laravel/horizon/issues",
- "source": "https://github.com/laravel/horizon/tree/v5.28.1"
+ "source": "https://github.com/laravel/horizon/tree/v5.29.1"
},
- "time": "2024-09-04T14:06:50+00:00"
+ "time": "2024-10-08T18:23:02+00:00"
+ },
+ {
+ "name": "laravel/pail",
+ "version": "v1.1.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/laravel/pail.git",
+ "reference": "b33ad8321416fe86efed7bf398f3306c47b4871b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/laravel/pail/zipball/b33ad8321416fe86efed7bf398f3306c47b4871b",
+ "reference": "b33ad8321416fe86efed7bf398f3306c47b4871b",
+ "shasum": ""
+ },
+ "require": {
+ "ext-mbstring": "*",
+ "illuminate/console": "^10.24|^11.0",
+ "illuminate/contracts": "^10.24|^11.0",
+ "illuminate/log": "^10.24|^11.0",
+ "illuminate/process": "^10.24|^11.0",
+ "illuminate/support": "^10.24|^11.0",
+ "nunomaduro/termwind": "^1.15|^2.0",
+ "php": "^8.2",
+ "symfony/console": "^6.0|^7.0"
+ },
+ "require-dev": {
+ "laravel/pint": "^1.13",
+ "orchestra/testbench": "^8.12|^9.0",
+ "pestphp/pest": "^2.20",
+ "pestphp/pest-plugin-type-coverage": "^2.3",
+ "phpstan/phpstan": "^1.10",
+ "symfony/var-dumper": "^6.3|^7.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.x-dev"
+ },
+ "laravel": {
+ "providers": [
+ "Laravel\\Pail\\PailServiceProvider"
+ ]
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Laravel\\Pail\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Taylor Otwell",
+ "email": "taylor@laravel.com"
+ },
+ {
+ "name": "Nuno Maduro",
+ "email": "enunomaduro@gmail.com"
+ }
+ ],
+ "description": "Easily delve into your Laravel application's log files directly from the command line.",
+ "homepage": "https://github.com/laravel/pail",
+ "keywords": [
+ "laravel",
+ "logs",
+ "php",
+ "tail"
+ ],
+ "support": {
+ "issues": "https://github.com/laravel/pail/issues",
+ "source": "https://github.com/laravel/pail"
+ },
+ "time": "2024-10-15T20:06:24+00:00"
},
{
"name": "laravel/prompts",
@@ -3199,16 +3281,16 @@
},
{
"name": "laravel/sanctum",
- "version": "v4.0.2",
+ "version": "v4.0.3",
"source": {
"type": "git",
"url": "https://github.com/laravel/sanctum.git",
- "reference": "9cfc0ce80cabad5334efff73ec856339e8ec1ac1"
+ "reference": "54aea9d13743ae8a6cdd3c28dbef128a17adecab"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/sanctum/zipball/9cfc0ce80cabad5334efff73ec856339e8ec1ac1",
- "reference": "9cfc0ce80cabad5334efff73ec856339e8ec1ac1",
+ "url": "https://api.github.com/repos/laravel/sanctum/zipball/54aea9d13743ae8a6cdd3c28dbef128a17adecab",
+ "reference": "54aea9d13743ae8a6cdd3c28dbef128a17adecab",
"shasum": ""
},
"require": {
@@ -3259,20 +3341,20 @@
"issues": "https://github.com/laravel/sanctum/issues",
"source": "https://github.com/laravel/sanctum"
},
- "time": "2024-04-10T19:39:58+00:00"
+ "time": "2024-09-27T14:55:41+00:00"
},
{
"name": "laravel/serializable-closure",
- "version": "v1.3.4",
+ "version": "v1.3.5",
"source": {
"type": "git",
"url": "https://github.com/laravel/serializable-closure.git",
- "reference": "61b87392d986dc49ad5ef64e75b1ff5fee24ef81"
+ "reference": "1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/61b87392d986dc49ad5ef64e75b1ff5fee24ef81",
- "reference": "61b87392d986dc49ad5ef64e75b1ff5fee24ef81",
+ "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c",
+ "reference": "1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c",
"shasum": ""
},
"require": {
@@ -3320,7 +3402,7 @@
"issues": "https://github.com/laravel/serializable-closure/issues",
"source": "https://github.com/laravel/serializable-closure"
},
- "time": "2024-08-02T07:48:17+00:00"
+ "time": "2024-09-23T13:33:08+00:00"
},
{
"name": "laravel/socialite",
@@ -3465,16 +3547,16 @@
},
{
"name": "laravel/tinker",
- "version": "v2.9.0",
+ "version": "v2.10.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/tinker.git",
- "reference": "502e0fe3f0415d06d5db1f83a472f0f3b754bafe"
+ "reference": "ba4d51eb56de7711b3a37d63aa0643e99a339ae5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/tinker/zipball/502e0fe3f0415d06d5db1f83a472f0f3b754bafe",
- "reference": "502e0fe3f0415d06d5db1f83a472f0f3b754bafe",
+ "url": "https://api.github.com/repos/laravel/tinker/zipball/ba4d51eb56de7711b3a37d63aa0643e99a339ae5",
+ "reference": "ba4d51eb56de7711b3a37d63aa0643e99a339ae5",
"shasum": ""
},
"require": {
@@ -3525,9 +3607,9 @@
],
"support": {
"issues": "https://github.com/laravel/tinker/issues",
- "source": "https://github.com/laravel/tinker/tree/v2.9.0"
+ "source": "https://github.com/laravel/tinker/tree/v2.10.0"
},
- "time": "2024-01-04T16:10:04+00:00"
+ "time": "2024-09-23T13:32:56+00:00"
},
{
"name": "laravel/ui",
@@ -3594,38 +3676,38 @@
},
{
"name": "lcobucci/jwt",
- "version": "5.3.0",
+ "version": "5.4.0",
"source": {
"type": "git",
"url": "https://github.com/lcobucci/jwt.git",
- "reference": "08071d8d2c7f4b00222cc4b1fb6aa46990a80f83"
+ "reference": "aac4fd512681fd5cb4b77d2105ab7ec700c72051"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/lcobucci/jwt/zipball/08071d8d2c7f4b00222cc4b1fb6aa46990a80f83",
- "reference": "08071d8d2c7f4b00222cc4b1fb6aa46990a80f83",
+ "url": "https://api.github.com/repos/lcobucci/jwt/zipball/aac4fd512681fd5cb4b77d2105ab7ec700c72051",
+ "reference": "aac4fd512681fd5cb4b77d2105ab7ec700c72051",
"shasum": ""
},
"require": {
"ext-openssl": "*",
"ext-sodium": "*",
- "php": "~8.1.0 || ~8.2.0 || ~8.3.0",
+ "php": "~8.2.0 || ~8.3.0 || ~8.4.0",
"psr/clock": "^1.0"
},
"require-dev": {
- "infection/infection": "^0.27.0",
- "lcobucci/clock": "^3.0",
+ "infection/infection": "^0.29",
+ "lcobucci/clock": "^3.2",
"lcobucci/coding-standard": "^11.0",
- "phpbench/phpbench": "^1.2.9",
+ "phpbench/phpbench": "^1.2",
"phpstan/extension-installer": "^1.2",
"phpstan/phpstan": "^1.10.7",
"phpstan/phpstan-deprecation-rules": "^1.1.3",
"phpstan/phpstan-phpunit": "^1.3.10",
"phpstan/phpstan-strict-rules": "^1.5.0",
- "phpunit/phpunit": "^10.2.6"
+ "phpunit/phpunit": "^11.1"
},
"suggest": {
- "lcobucci/clock": ">= 3.0"
+ "lcobucci/clock": ">= 3.2"
},
"type": "library",
"autoload": {
@@ -3651,7 +3733,7 @@
],
"support": {
"issues": "https://github.com/lcobucci/jwt/issues",
- "source": "https://github.com/lcobucci/jwt/tree/5.3.0"
+ "source": "https://github.com/lcobucci/jwt/tree/5.4.0"
},
"funding": [
{
@@ -3663,7 +3745,7 @@
"type": "patreon"
}
],
- "time": "2024-04-11T23:07:54+00:00"
+ "time": "2024-10-08T22:06:45+00:00"
},
{
"name": "league/commonmark",
@@ -3855,16 +3937,16 @@
},
{
"name": "league/flysystem",
- "version": "3.28.0",
+ "version": "3.29.1",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/flysystem.git",
- "reference": "e611adab2b1ae2e3072fa72d62c62f52c2bf1f0c"
+ "reference": "edc1bb7c86fab0776c3287dbd19b5fa278347319"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/e611adab2b1ae2e3072fa72d62c62f52c2bf1f0c",
- "reference": "e611adab2b1ae2e3072fa72d62c62f52c2bf1f0c",
+ "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/edc1bb7c86fab0776c3287dbd19b5fa278347319",
+ "reference": "edc1bb7c86fab0776c3287dbd19b5fa278347319",
"shasum": ""
},
"require": {
@@ -3932,22 +4014,22 @@
],
"support": {
"issues": "https://github.com/thephpleague/flysystem/issues",
- "source": "https://github.com/thephpleague/flysystem/tree/3.28.0"
+ "source": "https://github.com/thephpleague/flysystem/tree/3.29.1"
},
- "time": "2024-05-22T10:09:12+00:00"
+ "time": "2024-10-08T08:58:34+00:00"
},
{
"name": "league/flysystem-aws-s3-v3",
- "version": "3.28.0",
+ "version": "3.29.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git",
- "reference": "22071ef1604bc776f5ff2468ac27a752514665c8"
+ "reference": "c6ff6d4606e48249b63f269eba7fabdb584e76a9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/22071ef1604bc776f5ff2468ac27a752514665c8",
- "reference": "22071ef1604bc776f5ff2468ac27a752514665c8",
+ "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/c6ff6d4606e48249b63f269eba7fabdb584e76a9",
+ "reference": "c6ff6d4606e48249b63f269eba7fabdb584e76a9",
"shasum": ""
},
"require": {
@@ -3987,22 +4069,22 @@
"storage"
],
"support": {
- "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.28.0"
+ "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.29.0"
},
- "time": "2024-05-06T20:05:52+00:00"
+ "time": "2024-08-17T13:10:48+00:00"
},
{
"name": "league/flysystem-local",
- "version": "3.28.0",
+ "version": "3.29.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/flysystem-local.git",
- "reference": "13f22ea8be526ea58c2ddff9e158ef7c296e4f40"
+ "reference": "e0e8d52ce4b2ed154148453d321e97c8e931bd27"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/13f22ea8be526ea58c2ddff9e158ef7c296e4f40",
- "reference": "13f22ea8be526ea58c2ddff9e158ef7c296e4f40",
+ "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/e0e8d52ce4b2ed154148453d321e97c8e931bd27",
+ "reference": "e0e8d52ce4b2ed154148453d321e97c8e931bd27",
"shasum": ""
},
"require": {
@@ -4036,22 +4118,22 @@
"local"
],
"support": {
- "source": "https://github.com/thephpleague/flysystem-local/tree/3.28.0"
+ "source": "https://github.com/thephpleague/flysystem-local/tree/3.29.0"
},
- "time": "2024-05-06T20:05:52+00:00"
+ "time": "2024-08-09T21:24:39+00:00"
},
{
"name": "league/flysystem-sftp-v3",
- "version": "3.28.0",
+ "version": "3.29.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/flysystem-sftp-v3.git",
- "reference": "abedadd3c64d4f0e276d6ecc796ec8194d136b41"
+ "reference": "ce9b209e2fbe33122c755ffc18eb4d5bd256f252"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/thephpleague/flysystem-sftp-v3/zipball/abedadd3c64d4f0e276d6ecc796ec8194d136b41",
- "reference": "abedadd3c64d4f0e276d6ecc796ec8194d136b41",
+ "url": "https://api.github.com/repos/thephpleague/flysystem-sftp-v3/zipball/ce9b209e2fbe33122c755ffc18eb4d5bd256f252",
+ "reference": "ce9b209e2fbe33122c755ffc18eb4d5bd256f252",
"shasum": ""
},
"require": {
@@ -4085,22 +4167,22 @@
"sftp"
],
"support": {
- "source": "https://github.com/thephpleague/flysystem-sftp-v3/tree/3.28.0"
+ "source": "https://github.com/thephpleague/flysystem-sftp-v3/tree/3.29.0"
},
- "time": "2024-05-06T20:05:52+00:00"
+ "time": "2024-08-14T19:35:54+00:00"
},
{
"name": "league/mime-type-detection",
- "version": "1.15.0",
+ "version": "1.16.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/mime-type-detection.git",
- "reference": "ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301"
+ "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301",
- "reference": "ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301",
+ "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/2d6702ff215bf922936ccc1ad31007edc76451b9",
+ "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9",
"shasum": ""
},
"require": {
@@ -4131,7 +4213,7 @@
"description": "Mime-type detection for Flysystem",
"support": {
"issues": "https://github.com/thephpleague/mime-type-detection/issues",
- "source": "https://github.com/thephpleague/mime-type-detection/tree/1.15.0"
+ "source": "https://github.com/thephpleague/mime-type-detection/tree/1.16.0"
},
"funding": [
{
@@ -4143,7 +4225,7 @@
"type": "tidelift"
}
],
- "time": "2024-01-28T23:22:08+00:00"
+ "time": "2024-09-21T08:32:55+00:00"
},
{
"name": "league/oauth1-client",
@@ -4955,24 +5037,24 @@
},
{
"name": "nette/schema",
- "version": "v1.3.0",
+ "version": "v1.3.2",
"source": {
"type": "git",
"url": "https://github.com/nette/schema.git",
- "reference": "a6d3a6d1f545f01ef38e60f375d1cf1f4de98188"
+ "reference": "da801d52f0354f70a638673c4a0f04e16529431d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nette/schema/zipball/a6d3a6d1f545f01ef38e60f375d1cf1f4de98188",
- "reference": "a6d3a6d1f545f01ef38e60f375d1cf1f4de98188",
+ "url": "https://api.github.com/repos/nette/schema/zipball/da801d52f0354f70a638673c4a0f04e16529431d",
+ "reference": "da801d52f0354f70a638673c4a0f04e16529431d",
"shasum": ""
},
"require": {
"nette/utils": "^4.0",
- "php": "8.1 - 8.3"
+ "php": "8.1 - 8.4"
},
"require-dev": {
- "nette/tester": "^2.4",
+ "nette/tester": "^2.5.2",
"phpstan/phpstan-nette": "^1.0",
"tracy/tracy": "^2.8"
},
@@ -5011,9 +5093,9 @@
],
"support": {
"issues": "https://github.com/nette/schema/issues",
- "source": "https://github.com/nette/schema/tree/v1.3.0"
+ "source": "https://github.com/nette/schema/tree/v1.3.2"
},
- "time": "2023-12-11T11:54:22+00:00"
+ "time": "2024-10-06T23:10:23+00:00"
},
{
"name": "nette/utils",
@@ -5103,16 +5185,16 @@
},
{
"name": "nikic/php-parser",
- "version": "v5.1.0",
+ "version": "v5.3.1",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
- "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1"
+ "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/683130c2ff8c2739f4822ff7ac5c873ec529abd1",
- "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/8eea230464783aa9671db8eea6f8c6ac5285794b",
+ "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b",
"shasum": ""
},
"require": {
@@ -5155,9 +5237,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
- "source": "https://github.com/nikic/PHP-Parser/tree/v5.1.0"
+ "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.1"
},
- "time": "2024-07-01T20:03:41+00:00"
+ "time": "2024-10-08T18:51:32+00:00"
},
{
"name": "nubs/random-name-generator",
@@ -5897,16 +5979,16 @@
},
{
"name": "phpseclib/phpseclib",
- "version": "3.0.41",
+ "version": "3.0.42",
"source": {
"type": "git",
"url": "https://github.com/phpseclib/phpseclib.git",
- "reference": "621c73f7dcb310b61de34d1da4c4204e8ace6ceb"
+ "reference": "db92f1b1987b12b13f248fe76c3a52cadb67bb98"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/621c73f7dcb310b61de34d1da4c4204e8ace6ceb",
- "reference": "621c73f7dcb310b61de34d1da4c4204e8ace6ceb",
+ "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/db92f1b1987b12b13f248fe76c3a52cadb67bb98",
+ "reference": "db92f1b1987b12b13f248fe76c3a52cadb67bb98",
"shasum": ""
},
"require": {
@@ -5987,7 +6069,7 @@
],
"support": {
"issues": "https://github.com/phpseclib/phpseclib/issues",
- "source": "https://github.com/phpseclib/phpseclib/tree/3.0.41"
+ "source": "https://github.com/phpseclib/phpseclib/tree/3.0.42"
},
"funding": [
{
@@ -6003,20 +6085,20 @@
"type": "tidelift"
}
],
- "time": "2024-08-12T00:13:54+00:00"
+ "time": "2024-09-16T03:06:04+00:00"
},
{
"name": "phpstan/phpdoc-parser",
- "version": "1.30.1",
+ "version": "1.32.0",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpdoc-parser.git",
- "reference": "51b95ec8670af41009e2b2b56873bad96682413e"
+ "reference": "6ca22b154efdd9e3c68c56f5d94670920a1c19a4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/51b95ec8670af41009e2b2b56873bad96682413e",
- "reference": "51b95ec8670af41009e2b2b56873bad96682413e",
+ "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/6ca22b154efdd9e3c68c56f5d94670920a1c19a4",
+ "reference": "6ca22b154efdd9e3c68c56f5d94670920a1c19a4",
"shasum": ""
},
"require": {
@@ -6048,22 +6130,22 @@
"description": "PHPDoc parser with support for nullable, intersection and generic types",
"support": {
"issues": "https://github.com/phpstan/phpdoc-parser/issues",
- "source": "https://github.com/phpstan/phpdoc-parser/tree/1.30.1"
+ "source": "https://github.com/phpstan/phpdoc-parser/tree/1.32.0"
},
- "time": "2024-09-07T20:13:05+00:00"
+ "time": "2024-09-26T07:23:32+00:00"
},
{
"name": "phpstan/phpstan",
- "version": "1.12.3",
+ "version": "1.12.6",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
- "reference": "0fcbf194ab63d8159bb70d9aa3e1350051632009"
+ "reference": "dc4d2f145a88ea7141ae698effd64d9df46527ae"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan/zipball/0fcbf194ab63d8159bb70d9aa3e1350051632009",
- "reference": "0fcbf194ab63d8159bb70d9aa3e1350051632009",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/dc4d2f145a88ea7141ae698effd64d9df46527ae",
+ "reference": "dc4d2f145a88ea7141ae698effd64d9df46527ae",
"shasum": ""
},
"require": {
@@ -6108,7 +6190,7 @@
"type": "github"
}
],
- "time": "2024-09-09T08:10:35+00:00"
+ "time": "2024-10-06T15:03:59+00:00"
},
{
"name": "pion/laravel-chunk-upload",
@@ -6814,16 +6896,16 @@
},
{
"name": "purplepixie/phpdns",
- "version": "2.1.1",
+ "version": "2.2.0",
"source": {
"type": "git",
"url": "https://github.com/purplepixie/phpdns.git",
- "reference": "18cd3a43fadcfd16e2789e3c78a264945f6cbfad"
+ "reference": "2b77de5bb218bc4e5d9c4a4a12bd18fe80a6ab4d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/purplepixie/phpdns/zipball/18cd3a43fadcfd16e2789e3c78a264945f6cbfad",
- "reference": "18cd3a43fadcfd16e2789e3c78a264945f6cbfad",
+ "url": "https://api.github.com/repos/purplepixie/phpdns/zipball/2b77de5bb218bc4e5d9c4a4a12bd18fe80a6ab4d",
+ "reference": "2b77de5bb218bc4e5d9c4a4a12bd18fe80a6ab4d",
"shasum": ""
},
"require": {
@@ -6856,9 +6938,9 @@
"description": "PHP DNS Direct Query Module",
"support": {
"issues": "https://github.com/purplepixie/phpdns/issues",
- "source": "https://github.com/purplepixie/phpdns/tree/2.1.1"
+ "source": "https://github.com/purplepixie/phpdns/tree/2.2.0"
},
- "time": "2024-05-27T13:27:50+00:00"
+ "time": "2024-09-26T14:39:58+00:00"
},
{
"name": "pusher/pusher-php-server",
@@ -7148,21 +7230,21 @@
},
{
"name": "rector/rector",
- "version": "1.2.5",
+ "version": "1.2.6",
"source": {
"type": "git",
"url": "https://github.com/rectorphp/rector.git",
- "reference": "e98aa793ca3fcd17e893cfaf9103ac049775d339"
+ "reference": "6ca85da28159dbd3bb36211c5104b7bc91278e99"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/rectorphp/rector/zipball/e98aa793ca3fcd17e893cfaf9103ac049775d339",
- "reference": "e98aa793ca3fcd17e893cfaf9103ac049775d339",
+ "url": "https://api.github.com/repos/rectorphp/rector/zipball/6ca85da28159dbd3bb36211c5104b7bc91278e99",
+ "reference": "6ca85da28159dbd3bb36211c5104b7bc91278e99",
"shasum": ""
},
"require": {
"php": "^7.2|^8.0",
- "phpstan/phpstan": "^1.12.2"
+ "phpstan/phpstan": "^1.12.5"
},
"conflict": {
"rector/rector-doctrine": "*",
@@ -7195,7 +7277,7 @@
],
"support": {
"issues": "https://github.com/rectorphp/rector/issues",
- "source": "https://github.com/rectorphp/rector/tree/1.2.5"
+ "source": "https://github.com/rectorphp/rector/tree/1.2.6"
},
"funding": [
{
@@ -7203,7 +7285,7 @@
"type": "github"
}
],
- "time": "2024-09-08T17:43:24+00:00"
+ "time": "2024-10-03T08:56:44+00:00"
},
{
"name": "resend/resend-laravel",
@@ -7494,16 +7576,16 @@
},
{
"name": "sentry/sentry-laravel",
- "version": "4.8.0",
+ "version": "4.9.0",
"source": {
"type": "git",
"url": "https://github.com/getsentry/sentry-laravel.git",
- "reference": "2bbcb7e81097993cf64d5b296eaa6d396cddd5a7"
+ "reference": "73078e1f26d57f7a10e3bee2a2f543a02f6493c3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/2bbcb7e81097993cf64d5b296eaa6d396cddd5a7",
- "reference": "2bbcb7e81097993cf64d5b296eaa6d396cddd5a7",
+ "url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/73078e1f26d57f7a10e3bee2a2f543a02f6493c3",
+ "reference": "73078e1f26d57f7a10e3bee2a2f543a02f6493c3",
"shasum": ""
},
"require": {
@@ -7567,7 +7649,7 @@
],
"support": {
"issues": "https://github.com/getsentry/sentry-laravel/issues",
- "source": "https://github.com/getsentry/sentry-laravel/tree/4.8.0"
+ "source": "https://github.com/getsentry/sentry-laravel/tree/4.9.0"
},
"funding": [
{
@@ -7579,7 +7661,7 @@
"type": "custom"
}
],
- "time": "2024-08-15T19:03:01+00:00"
+ "time": "2024-09-19T12:58:53+00:00"
},
{
"name": "socialiteproviders/manager",
@@ -8580,16 +8662,16 @@
},
{
"name": "symfony/console",
- "version": "v7.1.4",
+ "version": "v7.1.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "1eed7af6961d763e7832e874d7f9b21c3ea9c111"
+ "reference": "0fa539d12b3ccf068a722bbbffa07ca7079af9ee"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/1eed7af6961d763e7832e874d7f9b21c3ea9c111",
- "reference": "1eed7af6961d763e7832e874d7f9b21c3ea9c111",
+ "url": "https://api.github.com/repos/symfony/console/zipball/0fa539d12b3ccf068a722bbbffa07ca7079af9ee",
+ "reference": "0fa539d12b3ccf068a722bbbffa07ca7079af9ee",
"shasum": ""
},
"require": {
@@ -8653,7 +8735,7 @@
"terminal"
],
"support": {
- "source": "https://github.com/symfony/console/tree/v7.1.4"
+ "source": "https://github.com/symfony/console/tree/v7.1.5"
},
"funding": [
{
@@ -8669,7 +8751,7 @@
"type": "tidelift"
}
],
- "time": "2024-08-15T22:48:53+00:00"
+ "time": "2024-09-20T08:28:38+00:00"
},
{
"name": "symfony/css-selector",
@@ -9100,16 +9182,16 @@
},
{
"name": "symfony/http-foundation",
- "version": "v7.1.3",
+ "version": "v7.1.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-foundation.git",
- "reference": "f602d5c17d1fa02f8019ace2687d9d136b7f4a1a"
+ "reference": "e30ef73b1e44eea7eb37ba69600a354e553f694b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-foundation/zipball/f602d5c17d1fa02f8019ace2687d9d136b7f4a1a",
- "reference": "f602d5c17d1fa02f8019ace2687d9d136b7f4a1a",
+ "url": "https://api.github.com/repos/symfony/http-foundation/zipball/e30ef73b1e44eea7eb37ba69600a354e553f694b",
+ "reference": "e30ef73b1e44eea7eb37ba69600a354e553f694b",
"shasum": ""
},
"require": {
@@ -9157,7 +9239,7 @@
"description": "Defines an object-oriented layer for the HTTP specification",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/http-foundation/tree/v7.1.3"
+ "source": "https://github.com/symfony/http-foundation/tree/v7.1.5"
},
"funding": [
{
@@ -9173,20 +9255,20 @@
"type": "tidelift"
}
],
- "time": "2024-07-26T12:41:01+00:00"
+ "time": "2024-09-20T08:28:38+00:00"
},
{
"name": "symfony/http-kernel",
- "version": "v7.1.4",
+ "version": "v7.1.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-kernel.git",
- "reference": "6efcbd1b3f444f631c386504fc83eeca25963747"
+ "reference": "44204d96150a9df1fc57601ec933d23fefc2d65b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-kernel/zipball/6efcbd1b3f444f631c386504fc83eeca25963747",
- "reference": "6efcbd1b3f444f631c386504fc83eeca25963747",
+ "url": "https://api.github.com/repos/symfony/http-kernel/zipball/44204d96150a9df1fc57601ec933d23fefc2d65b",
+ "reference": "44204d96150a9df1fc57601ec933d23fefc2d65b",
"shasum": ""
},
"require": {
@@ -9271,7 +9353,7 @@
"description": "Provides a structured process for converting a Request into a Response",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/http-kernel/tree/v7.1.4"
+ "source": "https://github.com/symfony/http-kernel/tree/v7.1.5"
},
"funding": [
{
@@ -9287,20 +9369,20 @@
"type": "tidelift"
}
],
- "time": "2024-08-30T17:02:28+00:00"
+ "time": "2024-09-21T06:09:21+00:00"
},
{
"name": "symfony/mailer",
- "version": "v7.1.2",
+ "version": "v7.1.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/mailer.git",
- "reference": "8fcff0af9043c8f8a8e229437cea363e282f9aee"
+ "reference": "bbf21460c56f29810da3df3e206e38dfbb01e80b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/mailer/zipball/8fcff0af9043c8f8a8e229437cea363e282f9aee",
- "reference": "8fcff0af9043c8f8a8e229437cea363e282f9aee",
+ "url": "https://api.github.com/repos/symfony/mailer/zipball/bbf21460c56f29810da3df3e206e38dfbb01e80b",
+ "reference": "bbf21460c56f29810da3df3e206e38dfbb01e80b",
"shasum": ""
},
"require": {
@@ -9351,7 +9433,7 @@
"description": "Helps sending emails",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/mailer/tree/v7.1.2"
+ "source": "https://github.com/symfony/mailer/tree/v7.1.5"
},
"funding": [
{
@@ -9367,20 +9449,20 @@
"type": "tidelift"
}
],
- "time": "2024-06-28T08:00:31+00:00"
+ "time": "2024-09-08T12:32:26+00:00"
},
{
"name": "symfony/mime",
- "version": "v7.1.4",
+ "version": "v7.1.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/mime.git",
- "reference": "ccaa6c2503db867f472a587291e764d6a1e58758"
+ "reference": "711d2e167e8ce65b05aea6b258c449671cdd38ff"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/mime/zipball/ccaa6c2503db867f472a587291e764d6a1e58758",
- "reference": "ccaa6c2503db867f472a587291e764d6a1e58758",
+ "url": "https://api.github.com/repos/symfony/mime/zipball/711d2e167e8ce65b05aea6b258c449671cdd38ff",
+ "reference": "711d2e167e8ce65b05aea6b258c449671cdd38ff",
"shasum": ""
},
"require": {
@@ -9435,7 +9517,7 @@
"mime-type"
],
"support": {
- "source": "https://github.com/symfony/mime/tree/v7.1.4"
+ "source": "https://github.com/symfony/mime/tree/v7.1.5"
},
"funding": [
{
@@ -9451,7 +9533,7 @@
"type": "tidelift"
}
],
- "time": "2024-08-13T14:28:19+00:00"
+ "time": "2024-09-20T08:28:38+00:00"
},
{
"name": "symfony/options-resolver",
@@ -10238,16 +10320,16 @@
},
{
"name": "symfony/process",
- "version": "v7.1.3",
+ "version": "v7.1.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
- "reference": "7f2f542c668ad6c313dc4a5e9c3321f733197eca"
+ "reference": "5c03ee6369281177f07f7c68252a280beccba847"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/process/zipball/7f2f542c668ad6c313dc4a5e9c3321f733197eca",
- "reference": "7f2f542c668ad6c313dc4a5e9c3321f733197eca",
+ "url": "https://api.github.com/repos/symfony/process/zipball/5c03ee6369281177f07f7c68252a280beccba847",
+ "reference": "5c03ee6369281177f07f7c68252a280beccba847",
"shasum": ""
},
"require": {
@@ -10279,7 +10361,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/process/tree/v7.1.3"
+ "source": "https://github.com/symfony/process/tree/v7.1.5"
},
"funding": [
{
@@ -10295,7 +10377,7 @@
"type": "tidelift"
}
],
- "time": "2024-07-26T12:44:47+00:00"
+ "time": "2024-09-19T21:48:23+00:00"
},
{
"name": "symfony/psr-http-message-bridge",
@@ -10608,16 +10690,16 @@
},
{
"name": "symfony/string",
- "version": "v7.1.4",
+ "version": "v7.1.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
- "reference": "6cd670a6d968eaeb1c77c2e76091c45c56bc367b"
+ "reference": "d66f9c343fa894ec2037cc928381df90a7ad4306"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/string/zipball/6cd670a6d968eaeb1c77c2e76091c45c56bc367b",
- "reference": "6cd670a6d968eaeb1c77c2e76091c45c56bc367b",
+ "url": "https://api.github.com/repos/symfony/string/zipball/d66f9c343fa894ec2037cc928381df90a7ad4306",
+ "reference": "d66f9c343fa894ec2037cc928381df90a7ad4306",
"shasum": ""
},
"require": {
@@ -10675,7 +10757,7 @@
"utf8"
],
"support": {
- "source": "https://github.com/symfony/string/tree/v7.1.4"
+ "source": "https://github.com/symfony/string/tree/v7.1.5"
},
"funding": [
{
@@ -10691,20 +10773,20 @@
"type": "tidelift"
}
],
- "time": "2024-08-12T09:59:40+00:00"
+ "time": "2024-09-20T08:28:38+00:00"
},
{
"name": "symfony/translation",
- "version": "v7.1.3",
+ "version": "v7.1.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
- "reference": "8d5e50c813ba2859a6dfc99a0765c550507934a1"
+ "reference": "235535e3f84f3dfbdbde0208ede6ca75c3a489ea"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/translation/zipball/8d5e50c813ba2859a6dfc99a0765c550507934a1",
- "reference": "8d5e50c813ba2859a6dfc99a0765c550507934a1",
+ "url": "https://api.github.com/repos/symfony/translation/zipball/235535e3f84f3dfbdbde0208ede6ca75c3a489ea",
+ "reference": "235535e3f84f3dfbdbde0208ede6ca75c3a489ea",
"shasum": ""
},
"require": {
@@ -10769,7 +10851,7 @@
"description": "Provides tools to internationalize your application",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/translation/tree/v7.1.3"
+ "source": "https://github.com/symfony/translation/tree/v7.1.5"
},
"funding": [
{
@@ -10785,7 +10867,7 @@
"type": "tidelift"
}
],
- "time": "2024-07-26T12:41:01+00:00"
+ "time": "2024-09-16T06:30:38+00:00"
},
{
"name": "symfony/translation-contracts",
@@ -10867,16 +10949,16 @@
},
{
"name": "symfony/uid",
- "version": "v7.1.4",
+ "version": "v7.1.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/uid.git",
- "reference": "82177535395109075cdb45a70533aa3d7a521cdf"
+ "reference": "8c7bb8acb933964055215d89f9a9871df0239317"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/uid/zipball/82177535395109075cdb45a70533aa3d7a521cdf",
- "reference": "82177535395109075cdb45a70533aa3d7a521cdf",
+ "url": "https://api.github.com/repos/symfony/uid/zipball/8c7bb8acb933964055215d89f9a9871df0239317",
+ "reference": "8c7bb8acb933964055215d89f9a9871df0239317",
"shasum": ""
},
"require": {
@@ -10921,7 +11003,7 @@
"uuid"
],
"support": {
- "source": "https://github.com/symfony/uid/tree/v7.1.4"
+ "source": "https://github.com/symfony/uid/tree/v7.1.5"
},
"funding": [
{
@@ -10937,20 +11019,20 @@
"type": "tidelift"
}
],
- "time": "2024-08-12T09:59:40+00:00"
+ "time": "2024-09-17T09:16:35+00:00"
},
{
"name": "symfony/var-dumper",
- "version": "v7.1.4",
+ "version": "v7.1.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
- "reference": "a5fa7481b199090964d6fd5dab6294d5a870c7aa"
+ "reference": "e20e03889539fd4e4211e14d2179226c513c010d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/var-dumper/zipball/a5fa7481b199090964d6fd5dab6294d5a870c7aa",
- "reference": "a5fa7481b199090964d6fd5dab6294d5a870c7aa",
+ "url": "https://api.github.com/repos/symfony/var-dumper/zipball/e20e03889539fd4e4211e14d2179226c513c010d",
+ "reference": "e20e03889539fd4e4211e14d2179226c513c010d",
"shasum": ""
},
"require": {
@@ -11004,7 +11086,7 @@
"dump"
],
"support": {
- "source": "https://github.com/symfony/var-dumper/tree/v7.1.4"
+ "source": "https://github.com/symfony/var-dumper/tree/v7.1.5"
},
"funding": [
{
@@ -11020,20 +11102,20 @@
"type": "tidelift"
}
],
- "time": "2024-08-30T16:12:47+00:00"
+ "time": "2024-09-16T10:07:02+00:00"
},
{
"name": "symfony/yaml",
- "version": "v6.4.11",
+ "version": "v6.4.12",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
- "reference": "be37e7f13195e05ab84ca5269365591edd240335"
+ "reference": "762ee56b2649659380e0ef4d592d807bc17b7971"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/yaml/zipball/be37e7f13195e05ab84ca5269365591edd240335",
- "reference": "be37e7f13195e05ab84ca5269365591edd240335",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/762ee56b2649659380e0ef4d592d807bc17b7971",
+ "reference": "762ee56b2649659380e0ef4d592d807bc17b7971",
"shasum": ""
},
"require": {
@@ -11076,7 +11158,7 @@
"description": "Loads and dumps YAML files",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/yaml/tree/v6.4.11"
+ "source": "https://github.com/symfony/yaml/tree/v6.4.12"
},
"funding": [
{
@@ -11092,7 +11174,7 @@
"type": "tidelift"
}
],
- "time": "2024-08-12T09:55:28+00:00"
+ "time": "2024-09-17T12:47:12+00:00"
},
{
"name": "tijsverkoyen/css-to-inline-styles",
@@ -11742,16 +11824,16 @@
},
{
"name": "zircote/swagger-php",
- "version": "4.10.6",
+ "version": "4.11.0",
"source": {
"type": "git",
"url": "https://github.com/zircote/swagger-php.git",
- "reference": "e462ff5269ea0ec91070edd5d51dc7215bdea3b6"
+ "reference": "3b6f3800f4fd6544ada4dce180c6b69eaead7c7c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/zircote/swagger-php/zipball/e462ff5269ea0ec91070edd5d51dc7215bdea3b6",
- "reference": "e462ff5269ea0ec91070edd5d51dc7215bdea3b6",
+ "url": "https://api.github.com/repos/zircote/swagger-php/zipball/3b6f3800f4fd6544ada4dce180c6b69eaead7c7c",
+ "reference": "3b6f3800f4fd6544ada4dce180c6b69eaead7c7c",
"shasum": ""
},
"require": {
@@ -11765,7 +11847,7 @@
"require-dev": {
"composer/package-versions-deprecated": "^1.11",
"doctrine/annotations": "^1.7 || ^2.0",
- "friendsofphp/php-cs-fixer": "^2.17 || ^3.47.1",
+ "friendsofphp/php-cs-fixer": "^2.17 || 3.62.0",
"phpstan/phpstan": "^1.6",
"phpunit/phpunit": ">=8",
"vimeo/psalm": "^4.23"
@@ -11817,31 +11899,31 @@
],
"support": {
"issues": "https://github.com/zircote/swagger-php/issues",
- "source": "https://github.com/zircote/swagger-php/tree/4.10.6"
+ "source": "https://github.com/zircote/swagger-php/tree/4.11.0"
},
- "time": "2024-07-26T03:04:43+00:00"
+ "time": "2024-10-09T03:11:12+00:00"
}
],
"packages-dev": [
{
"name": "barryvdh/laravel-debugbar",
- "version": "v3.13.5",
+ "version": "v3.14.3",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/laravel-debugbar.git",
- "reference": "92d86be45ee54edff735e46856f64f14b6a8bb07"
+ "reference": "c0bee7c08ae2429e4a9ed2bc75679b012db6e3bd"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/92d86be45ee54edff735e46856f64f14b6a8bb07",
- "reference": "92d86be45ee54edff735e46856f64f14b6a8bb07",
+ "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/c0bee7c08ae2429e4a9ed2bc75679b012db6e3bd",
+ "reference": "c0bee7c08ae2429e4a9ed2bc75679b012db6e3bd",
"shasum": ""
},
"require": {
"illuminate/routing": "^9|^10|^11",
"illuminate/session": "^9|^10|^11",
"illuminate/support": "^9|^10|^11",
- "maximebf/debugbar": "~1.22.0",
+ "maximebf/debugbar": "~1.23.0",
"php": "^8.0",
"symfony/finder": "^6|^7"
},
@@ -11854,7 +11936,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.13-dev"
+ "dev-master": "3.14-dev"
},
"laravel": {
"providers": [
@@ -11893,7 +11975,7 @@
],
"support": {
"issues": "https://github.com/barryvdh/laravel-debugbar/issues",
- "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.13.5"
+ "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.14.3"
},
"funding": [
{
@@ -11905,7 +11987,7 @@
"type": "github"
}
],
- "time": "2024-04-12T11:20:37+00:00"
+ "time": "2024-10-02T09:17:49+00:00"
},
{
"name": "brianium/paratest",
@@ -12127,26 +12209,26 @@
},
{
"name": "filp/whoops",
- "version": "2.15.4",
+ "version": "2.16.0",
"source": {
"type": "git",
"url": "https://github.com/filp/whoops.git",
- "reference": "a139776fa3f5985a50b509f2a02ff0f709d2a546"
+ "reference": "befcdc0e5dce67252aa6322d82424be928214fa2"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/filp/whoops/zipball/a139776fa3f5985a50b509f2a02ff0f709d2a546",
- "reference": "a139776fa3f5985a50b509f2a02ff0f709d2a546",
+ "url": "https://api.github.com/repos/filp/whoops/zipball/befcdc0e5dce67252aa6322d82424be928214fa2",
+ "reference": "befcdc0e5dce67252aa6322d82424be928214fa2",
"shasum": ""
},
"require": {
- "php": "^5.5.9 || ^7.0 || ^8.0",
+ "php": "^7.1 || ^8.0",
"psr/log": "^1.0.1 || ^2.0 || ^3.0"
},
"require-dev": {
- "mockery/mockery": "^0.9 || ^1.0",
- "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3",
- "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0"
+ "mockery/mockery": "^1.0",
+ "phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.3.3",
+ "symfony/var-dumper": "^4.0 || ^5.0"
},
"suggest": {
"symfony/var-dumper": "Pretty print complex values better with var-dumper available",
@@ -12186,7 +12268,7 @@
],
"support": {
"issues": "https://github.com/filp/whoops/issues",
- "source": "https://github.com/filp/whoops/tree/2.15.4"
+ "source": "https://github.com/filp/whoops/tree/2.16.0"
},
"funding": [
{
@@ -12194,7 +12276,7 @@
"type": "github"
}
],
- "time": "2023-11-03T12:00:00+00:00"
+ "time": "2024-09-25T12:00:00+00:00"
},
{
"name": "hamcrest/hamcrest-php",
@@ -12249,16 +12331,16 @@
},
{
"name": "laravel/dusk",
- "version": "v8.2.5",
+ "version": "v8.2.8",
"source": {
"type": "git",
"url": "https://github.com/laravel/dusk.git",
- "reference": "e641800393ce4ad39f0a47133f51aae67ceb01ad"
+ "reference": "5bff1e8dd87ec653a2202475377152e5d14fde40"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/dusk/zipball/e641800393ce4ad39f0a47133f51aae67ceb01ad",
- "reference": "e641800393ce4ad39f0a47133f51aae67ceb01ad",
+ "url": "https://api.github.com/repos/laravel/dusk/zipball/5bff1e8dd87ec653a2202475377152e5d14fde40",
+ "reference": "5bff1e8dd87ec653a2202475377152e5d14fde40",
"shasum": ""
},
"require": {
@@ -12315,22 +12397,22 @@
],
"support": {
"issues": "https://github.com/laravel/dusk/issues",
- "source": "https://github.com/laravel/dusk/tree/v8.2.5"
+ "source": "https://github.com/laravel/dusk/tree/v8.2.8"
},
- "time": "2024-08-26T12:34:33+00:00"
+ "time": "2024-10-04T14:02:20+00:00"
},
{
"name": "laravel/pint",
- "version": "v1.17.3",
+ "version": "v1.18.1",
"source": {
"type": "git",
"url": "https://github.com/laravel/pint.git",
- "reference": "9d77be916e145864f10788bb94531d03e1f7b482"
+ "reference": "35c00c05ec43e6b46d295efc0f4386ceb30d50d9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/pint/zipball/9d77be916e145864f10788bb94531d03e1f7b482",
- "reference": "9d77be916e145864f10788bb94531d03e1f7b482",
+ "url": "https://api.github.com/repos/laravel/pint/zipball/35c00c05ec43e6b46d295efc0f4386ceb30d50d9",
+ "reference": "35c00c05ec43e6b46d295efc0f4386ceb30d50d9",
"shasum": ""
},
"require": {
@@ -12383,20 +12465,20 @@
"issues": "https://github.com/laravel/pint/issues",
"source": "https://github.com/laravel/pint"
},
- "time": "2024-09-03T15:00:28+00:00"
+ "time": "2024-09-24T17:22:50+00:00"
},
{
"name": "maximebf/debugbar",
- "version": "v1.22.5",
+ "version": "v1.23.2",
"source": {
"type": "git",
"url": "https://github.com/maximebf/php-debugbar.git",
- "reference": "1b5cabe0ce013134cf595bfa427bbf2f6abcd989"
+ "reference": "689720d724c771ac4add859056744b7b3f2406da"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/1b5cabe0ce013134cf595bfa427bbf2f6abcd989",
- "reference": "1b5cabe0ce013134cf595bfa427bbf2f6abcd989",
+ "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/689720d724c771ac4add859056744b7b3f2406da",
+ "reference": "689720d724c771ac4add859056744b7b3f2406da",
"shasum": ""
},
"require": {
@@ -12418,7 +12500,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.22-dev"
+ "dev-master": "1.23-dev"
}
},
"autoload": {
@@ -12449,9 +12531,9 @@
],
"support": {
"issues": "https://github.com/maximebf/php-debugbar/issues",
- "source": "https://github.com/maximebf/php-debugbar/tree/v1.22.5"
+ "source": "https://github.com/maximebf/php-debugbar/tree/v1.23.2"
},
- "time": "2024-09-09T08:05:55+00:00"
+ "time": "2024-09-16T11:23:09+00:00"
},
{
"name": "mockery/mockery",
@@ -14892,16 +14974,16 @@
},
{
"name": "symfony/http-client",
- "version": "v6.4.11",
+ "version": "v6.4.12",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-client.git",
- "reference": "4c92046bb788648ff1098cc66da69aa7eac8cb65"
+ "reference": "fbebfcce21084d3e91ea987ae5bdd8c71ff0fd56"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-client/zipball/4c92046bb788648ff1098cc66da69aa7eac8cb65",
- "reference": "4c92046bb788648ff1098cc66da69aa7eac8cb65",
+ "url": "https://api.github.com/repos/symfony/http-client/zipball/fbebfcce21084d3e91ea987ae5bdd8c71ff0fd56",
+ "reference": "fbebfcce21084d3e91ea987ae5bdd8c71ff0fd56",
"shasum": ""
},
"require": {
@@ -14965,7 +15047,7 @@
"http"
],
"support": {
- "source": "https://github.com/symfony/http-client/tree/v6.4.11"
+ "source": "https://github.com/symfony/http-client/tree/v6.4.12"
},
"funding": [
{
@@ -14981,7 +15063,7 @@
"type": "tidelift"
}
],
- "time": "2024-08-26T06:30:21+00:00"
+ "time": "2024-09-20T08:21:33+00:00"
},
{
"name": "symfony/http-client-contracts",
diff --git a/config/debugbar.php b/config/debugbar.php
index eae406ba7..daeea96b6 100644
--- a/config/debugbar.php
+++ b/config/debugbar.php
@@ -18,6 +18,7 @@ return [
'except' => [
'telescope*',
'horizon*',
+ 'api*',
],
/*
diff --git a/config/sentry.php b/config/sentry.php
index b7e230e71..e8b6ab098 100644
--- a/config/sentry.php
+++ b/config/sentry.php
@@ -7,7 +7,7 @@ return [
// The release version of your application
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
- 'release' => '4.0.0-beta.358',
+ 'release' => '4.0.0-beta.361',
// When left empty or `null` the Laravel environment will be used
'environment' => config('app.env'),
diff --git a/config/testing.php b/config/testing.php
new file mode 100644
index 000000000..41b8eadf0
--- /dev/null
+++ b/config/testing.php
@@ -0,0 +1,6 @@
+ env('DUSK_TEST_EMAIL', 'test@example.com'),
+ 'dusk_test_password' => env('DUSK_TEST_PASSWORD', 'password'),
+];
diff --git a/config/version.php b/config/version.php
index a89343eb9..0e83ff40e 100644
--- a/config/version.php
+++ b/config/version.php
@@ -1,4 +1,3 @@
boolean('is_metrics_enabled')->default(false);
$table->integer('metrics_refresh_rate_seconds')->default(5);
$table->integer('metrics_history_days')->default(30);
- $table->string('metrics_token')->default(generateSentinelToken());
+ $table->string('metrics_token')->nullable();
});
}
diff --git a/database/migrations/2024_06_25_184323_update_db.php b/database/migrations/2024_06_25_184323_update_db.php
index f1b175a9c..8f9405b86 100644
--- a/database/migrations/2024_06_25_184323_update_db.php
+++ b/database/migrations/2024_06_25_184323_update_db.php
@@ -4,6 +4,7 @@ use App\Models\EnvironmentVariable;
use App\Models\Server;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Visus\Cuid2\Cuid2;
@@ -14,44 +15,45 @@ return new class extends Migration
*/
public function up(): void
{
- Schema::table('applications', function (Blueprint $table) {
- $table->dropColumn('docker_compose_pr_location');
- $table->dropColumn('docker_compose_pr');
- $table->dropColumn('docker_compose_pr_raw');
- });
- Schema::table('subscriptions', function (Blueprint $table) {
- $table->dropColumn('lemon_subscription_id');
- $table->dropColumn('lemon_order_id');
- $table->dropColumn('lemon_product_id');
- $table->dropColumn('lemon_variant_id');
- $table->dropColumn('lemon_variant_name');
- $table->dropColumn('lemon_customer_id');
- $table->dropColumn('lemon_status');
- $table->dropColumn('lemon_renews_at');
- $table->dropColumn('lemon_update_payment_menthod_url');
- $table->dropColumn('lemon_trial_ends_at');
- $table->dropColumn('lemon_ends_at');
- });
- Schema::table('environment_variables', function (Blueprint $table) {
- $table->string('uuid')->nullable()->after('id');
- });
+ try {
+ Schema::table('applications', function (Blueprint $table) {
+ $table->dropColumn('docker_compose_pr_location');
+ $table->dropColumn('docker_compose_pr');
+ $table->dropColumn('docker_compose_pr_raw');
+ });
+ Schema::table('subscriptions', function (Blueprint $table) {
+ $table->dropColumn('lemon_subscription_id');
+ $table->dropColumn('lemon_order_id');
+ $table->dropColumn('lemon_product_id');
+ $table->dropColumn('lemon_variant_id');
+ $table->dropColumn('lemon_variant_name');
+ $table->dropColumn('lemon_customer_id');
+ $table->dropColumn('lemon_status');
+ $table->dropColumn('lemon_renews_at');
+ $table->dropColumn('lemon_update_payment_menthod_url');
+ $table->dropColumn('lemon_trial_ends_at');
+ $table->dropColumn('lemon_ends_at');
+ });
+ Schema::table('environment_variables', function (Blueprint $table) {
+ $table->string('uuid')->nullable()->after('id');
+ });
- EnvironmentVariable::all()->each(function (EnvironmentVariable $environmentVariable) {
- $environmentVariable->update([
- 'uuid' => (string) new Cuid2,
- ]);
- });
- Schema::table('environment_variables', function (Blueprint $table) {
- $table->string('uuid')->nullable(false)->change();
- });
- Schema::table('server_settings', function (Blueprint $table) {
- $table->integer('metrics_history_days')->default(7)->change();
- });
- Server::all()->each(function (Server $server) {
- $server->settings->update([
- 'metrics_history_days' => 7,
- ]);
- });
+ EnvironmentVariable::all()->each(function (EnvironmentVariable $environmentVariable) {
+ $environmentVariable->update([
+ 'uuid' => (string) new Cuid2,
+ ]);
+ });
+ Schema::table('environment_variables', function (Blueprint $table) {
+ $table->string('uuid')->nullable(false)->change();
+ });
+ Schema::table('server_settings', function (Blueprint $table) {
+ $table->integer('metrics_history_days')->default(7)->change();
+ });
+
+ DB::table('server_settings')->update(['metrics_history_days' => 7]);
+ } catch (\Exception $e) {
+ loggy($e);
+ }
}
/**
diff --git a/database/migrations/2024_07_18_123458_add_force_cleanup_server.php b/database/migrations/2024_07_18_123458_add_force_cleanup_server.php
index a33665bd0..ea3695b3f 100644
--- a/database/migrations/2024_07_18_123458_add_force_cleanup_server.php
+++ b/database/migrations/2024_07_18_123458_add_force_cleanup_server.php
@@ -12,7 +12,7 @@ return new class extends Migration
public function up(): void
{
Schema::table('server_settings', function (Blueprint $table) {
- $table->boolean('is_force_cleanup_enabled')->default(false)->after('is_sentinel_enabled');
+ $table->boolean('is_force_cleanup_enabled')->default(false);
});
}
diff --git a/database/migrations/2024_10_11_114331_add_required_env_variables.php b/database/migrations/2024_10_11_114331_add_required_env_variables.php
new file mode 100644
index 000000000..4fde0c2bb
--- /dev/null
+++ b/database/migrations/2024_10_11_114331_add_required_env_variables.php
@@ -0,0 +1,28 @@
+boolean('is_required')->default(false);
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('environment_variables', function (Blueprint $table) {
+ $table->dropColumn('is_required');
+ });
+ }
+};
diff --git a/database/migrations/2024_10_14_090416_update_metrics_token_in_server_settings.php b/database/migrations/2024_10_14_090416_update_metrics_token_in_server_settings.php
new file mode 100644
index 000000000..d5c38501f
--- /dev/null
+++ b/database/migrations/2024_10_14_090416_update_metrics_token_in_server_settings.php
@@ -0,0 +1,54 @@
+dropColumn('metrics_token');
+ $table->dropColumn('metrics_refresh_rate_seconds');
+ $table->dropColumn('metrics_history_days');
+ $table->dropColumn('is_server_api_enabled');
+
+ $table->boolean('is_sentinel_enabled')->default(false);
+ $table->text('sentinel_token')->nullable();
+ $table->integer('sentinel_metrics_refresh_rate_seconds')->default(10);
+ $table->integer('sentinel_metrics_history_days')->default(7);
+ $table->integer('sentinel_push_interval_seconds')->default(60);
+ $table->string('sentinel_custom_url')->nullable();
+ });
+ Schema::table('servers', function (Blueprint $table) {
+ $table->dateTime('sentinel_updated_at')->default(now());
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('server_settings', function (Blueprint $table) {
+ $table->string('metrics_token')->nullable();
+ $table->integer('metrics_refresh_rate_seconds')->default(5);
+ $table->integer('metrics_history_days')->default(30);
+ $table->boolean('is_server_api_enabled')->default(false);
+
+ $table->dropColumn('is_sentinel_enabled');
+ $table->dropColumn('sentinel_token');
+ $table->dropColumn('sentinel_metrics_refresh_rate_seconds');
+ $table->dropColumn('sentinel_metrics_history_days');
+ $table->dropColumn('sentinel_push_interval_seconds');
+ $table->dropColumn('sentinel_custom_url');
+ });
+ Schema::table('servers', function (Blueprint $table) {
+ $table->dropColumn('sentinel_updated_at');
+ });
+ }
+};
diff --git a/database/migrations/2024_10_15_172139_add_is_shared_to_environment_variables.php b/database/migrations/2024_10_15_172139_add_is_shared_to_environment_variables.php
new file mode 100644
index 000000000..eb878e2f6
--- /dev/null
+++ b/database/migrations/2024_10_15_172139_add_is_shared_to_environment_variables.php
@@ -0,0 +1,22 @@
+boolean('is_shared')->default(false);
+ });
+ }
+
+ public function down()
+ {
+ Schema::table('environment_variables', function (Blueprint $table) {
+ $table->dropColumn('is_shared');
+ });
+ }
+}
diff --git a/database/migrations/2024_10_16_120026_move_redis_password_to_envs.php b/database/migrations/2024_10_16_120026_move_redis_password_to_envs.php
new file mode 100644
index 000000000..fa01e8e85
--- /dev/null
+++ b/database/migrations/2024_10_16_120026_move_redis_password_to_envs.php
@@ -0,0 +1,41 @@
+where('id', $redis->id)->value('redis_password');
+ EnvironmentVariable::create([
+ 'standalone_redis_id' => $redis->id,
+ 'key' => 'REDIS_PASSWORD',
+ 'value' => $redis_password,
+ ]);
+ EnvironmentVariable::create([
+ 'standalone_redis_id' => $redis->id,
+ 'key' => 'REDIS_USERNAME',
+ 'value' => 'default',
+ ]);
+ }
+ });
+ Schema::table('standalone_redis', function (Blueprint $table) {
+ $table->dropColumn('redis_password');
+ });
+ } catch (\Exception $e) {
+ echo 'Moving Redis passwords to envs failed.';
+ echo $e->getMessage();
+ }
+ }
+}
diff --git a/database/migrations/2024_10_16_192133_add_confirmation_settings_to_instance_settings_table.php b/database/migrations/2024_10_16_192133_add_confirmation_settings_to_instance_settings_table.php
new file mode 100644
index 000000000..7040daf44
--- /dev/null
+++ b/database/migrations/2024_10_16_192133_add_confirmation_settings_to_instance_settings_table.php
@@ -0,0 +1,22 @@
+boolean('disable_two_step_confirmation')->default(false);
+ });
+ }
+
+ public function down()
+ {
+ Schema::table('instance_settings', function (Blueprint $table) {
+ $table->dropColumn('disable_two_step_confirmation');
+ });
+ }
+};
diff --git a/database/migrations/2024_10_17_093722_add_soft_delete_to_servers.php b/database/migrations/2024_10_17_093722_add_soft_delete_to_servers.php
new file mode 100644
index 000000000..7a7f28e24
--- /dev/null
+++ b/database/migrations/2024_10_17_093722_add_soft_delete_to_servers.php
@@ -0,0 +1,28 @@
+softDeletes();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('servers', function (Blueprint $table) {
+ $table->dropSoftDeletes();
+ });
+ }
+};
diff --git a/database/migrations/2024_10_22_105745_add_server_disk_usage_threshold.php b/database/migrations/2024_10_22_105745_add_server_disk_usage_threshold.php
new file mode 100644
index 000000000..76ccd1352
--- /dev/null
+++ b/database/migrations/2024_10_22_105745_add_server_disk_usage_threshold.php
@@ -0,0 +1,28 @@
+integer('server_disk_usage_notification_threshold')->default(80)->after('docker_cleanup_threshold');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('server_settings', function (Blueprint $table) {
+ $table->dropColumn('server_disk_usage_notification_threshold');
+ });
+ }
+};
diff --git a/database/migrations/2024_10_22_121223_add_server_disk_usage_notification.php b/database/migrations/2024_10_22_121223_add_server_disk_usage_notification.php
new file mode 100644
index 000000000..a2aa381b7
--- /dev/null
+++ b/database/migrations/2024_10_22_121223_add_server_disk_usage_notification.php
@@ -0,0 +1,32 @@
+boolean('discord_notifications_server_disk_usage')->default(true)->after('discord_enabled');
+ $table->boolean('smtp_notifications_server_disk_usage')->default(true)->after('smtp_enabled');
+ $table->boolean('telegram_notifications_server_disk_usage')->default(true)->after('telegram_enabled');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('teams', function (Blueprint $table) {
+ $table->dropColumn('discord_notifications_server_disk_usage');
+ $table->dropColumn('smtp_notifications_server_disk_usage');
+ $table->dropColumn('telegram_notifications_server_disk_usage');
+ });
+ }
+};
diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php
index be5083108..6e66c64f4 100644
--- a/database/seeders/DatabaseSeeder.php
+++ b/database/seeders/DatabaseSeeder.php
@@ -26,6 +26,8 @@ class DatabaseSeeder extends Seeder
S3StorageSeeder::class,
StandalonePostgresqlSeeder::class,
OauthSettingSeeder::class,
+ DisableTwoStepConfirmationSeeder::class,
+ SentinelSeeder::class,
]);
}
}
diff --git a/database/seeders/DisableTwoStepConfirmationSeeder.php b/database/seeders/DisableTwoStepConfirmationSeeder.php
new file mode 100644
index 000000000..c43bf1b01
--- /dev/null
+++ b/database/seeders/DisableTwoStepConfirmationSeeder.php
@@ -0,0 +1,20 @@
+updateOrInsert(
+ [],
+ ['disable_two_step_confirmation' => true]
+ );
+ }
+}
diff --git a/database/seeders/ProductionSeeder.php b/database/seeders/ProductionSeeder.php
index 206f04d6b..90b9d46ff 100644
--- a/database/seeders/ProductionSeeder.php
+++ b/database/seeders/ProductionSeeder.php
@@ -186,6 +186,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
$this->call(OauthSettingSeeder::class);
$this->call(PopulateSshKeysDirectorySeeder::class);
+ $this->call(SentinelSeeder::class);
}
}
diff --git a/database/seeders/SentinelSeeder.php b/database/seeders/SentinelSeeder.php
new file mode 100644
index 000000000..117ba6782
--- /dev/null
+++ b/database/seeders/SentinelSeeder.php
@@ -0,0 +1,31 @@
+settings->sentinel_token)->isEmpty()) {
+ $server->settings->generateSentinelToken();
+ }
+ if (str($server->settings->sentinel_custom_url)->isEmpty()) {
+ $url = $server->settings->generateSentinelUrl();
+ if (str($url)->isEmpty()) {
+ $server->settings->is_sentinel_enabled = false;
+ $server->settings->save();
+ }
+ }
+ } catch (\Throwable $e) {
+ loggy("Error: {$e->getMessage()}\n");
+ }
+ }
+ });
+ }
+}
diff --git a/docker/coolify-realtime/package.json b/docker/coolify-realtime/package.json
index 90d4f77db..146e6b90a 100644
--- a/docker/coolify-realtime/package.json
+++ b/docker/coolify-realtime/package.json
@@ -4,7 +4,7 @@
"dependencies": {
"@xterm/addon-fit": "^0.10.0",
"@xterm/xterm": "^5.5.0",
- "cookie": "^0.6.0",
+ "cookie": "^0.7.0",
"axios": "1.7.5",
"dotenv": "^16.4.5",
"node-pty": "^1.0.0",
diff --git a/docker/dev/Dockerfile b/docker/dev/Dockerfile
index 63832dc36..d2381f764 100644
--- a/docker/dev/Dockerfile
+++ b/docker/dev/Dockerfile
@@ -5,34 +5,38 @@ ARG TARGETPLATFORM
ARG CLOUDFLARED_VERSION=2024.4.1
ARG POSTGRES_VERSION=15
-RUN apt-get update
-# Postgres version requirements
-RUN apt install dirmngr ca-certificates software-properties-common gnupg gnupg2 apt-transport-https curl -y
-RUN curl -fSsL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /usr/share/keyrings/postgresql.gpg > /dev/null
-RUN echo deb [arch=amd64,arm64,ppc64el signed-by=/usr/share/keyrings/postgresql.gpg] http://apt.postgresql.org/pub/repos/apt/ jammy-pgdg main | tee -a /etc/apt/sources.list.d/postgresql.list
+# Use build arguments for caching
+ARG BUILDTIME_DEPS="dirmngr ca-certificates software-properties-common gnupg gnupg2 apt-transport-https curl"
+ARG RUNTIME_DEPS="postgresql-client-$POSTGRES_VERSION php8.2-pgsql openssh-client git git-lfs jq lsof"
-RUN apt-get update
-RUN apt-get install postgresql-client-$POSTGRES_VERSION -y
+# Install dependencies
+RUN --mount=type=cache,target=/var/cache/apt \
+ apt-get update && \
+ apt-get install -y $BUILDTIME_DEPS && \
+ curl -fSsL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /usr/share/keyrings/postgresql.gpg > /dev/null && \
+ echo deb [arch=amd64,arm64,ppc64el signed-by=/usr/share/keyrings/postgresql.gpg] http://apt.postgresql.org/pub/repos/apt/ jammy-pgdg main | tee -a /etc/apt/sources.list.d/postgresql.list && \
+ apt-get update && \
+ apt-get install -y $RUNTIME_DEPS && \
+ apt-get -y autoremove && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/*
-# Coolify requirements
-RUN apt-get install -y php8.2-pgsql openssh-client git git-lfs jq lsof
-RUN apt-get -y autoremove && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/*
COPY --chmod=755 docker/dev/etc/s6-overlay/ /etc/s6-overlay/
COPY docker/dev/nginx.conf /etc/nginx/conf.d/custom.conf
-RUN echo "alias ll='ls -al'" >>/etc/bash.bashrc
-RUN echo "alias a='php artisan'" >>/etc/bash.bashrc
+RUN echo "alias ll='ls -al'" >>/etc/bash.bashrc && \
+ echo "alias a='php artisan'" >>/etc/bash.bashrc
RUN mkdir -p /usr/local/bin
-RUN /bin/bash -c "if [[ ${TARGETPLATFORM} == 'linux/amd64' ]]; then \
+RUN --mount=type=cache,target=/root/.cache \
+ /bin/bash -c "if [[ ${TARGETPLATFORM} == 'linux/amd64' ]]; then \
echo 'amd64' && \
curl -sSL https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-amd64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared \
;fi"
-RUN /bin/bash -c "if [[ ${TARGETPLATFORM} == 'linux/arm64' ]]; then \
+RUN --mount=type=cache,target=/root/.cache \
+ /bin/bash -c "if [[ ${TARGETPLATFORM} == 'linux/arm64' ]]; then \
echo 'arm64' && \
curl -L https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-arm64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared \
;fi"
diff --git a/docker/prod/Dockerfile b/docker/prod/Dockerfile
index d0cebcbca..22a5b143a 100644
--- a/docker/prod/Dockerfile
+++ b/docker/prod/Dockerfile
@@ -1,4 +1,4 @@
-FROM serversideup/php:8.2-fpm-nginx-v2.2.1 as base
+FROM serversideup/php:8.2-fpm-nginx-v2.2.1 AS base
WORKDIR /var/www/html
COPY composer.json composer.lock ./
diff --git a/lang/ar.json b/lang/ar.json
index c5ec96c8d..4b9afbe99 100644
--- a/lang/ar.json
+++ b/lang/ar.json
@@ -26,5 +26,12 @@
"input.code": "الرمز لمرة واحدة",
"input.recovery_code": "رمز الاسترداد",
"button.save": "حفظ",
- "repository.url": "أمثلة للمستودعات العامة، استخدم https://.... للمستودعات الخاصة، استخدم git@....
سيتم تحديد الفرع main لـ https://github.com/coollabsio/coolify-examples سيتم تحديد الفرع nodejs-fastify لـ https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify سيتم تحديد الفرع main لـ https://gitea.com/sedlav/expressjs.git سيتم تحديد الفرع main لـ https://gitlab.com/andrasbacsai/nodejs-example.git."
+ "repository.url": "أمثلة للمستودعات العامة، استخدم https://.... للمستودعات الخاصة، استخدم git@....
سيتم تحديد الفرع main لـ https://github.com/coollabsio/coolify-examples سيتم تحديد الفرع nodejs-fastify لـ https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify سيتم تحديد الفرع main لـ https://gitea.com/sedlav/expressjs.git سيتم تحديد الفرع main لـ https://gitlab.com/andrasbacsai/nodejs-example.git.",
+ "service.stop": "سيتم إيقاف هذه الخدمة.",
+ "resource.docker_cleanup": "قم بتشغيل Docker Cleanup (قم بإزالة الصور غير المستخدمة وذاكرة التخزين المؤقت للمنشئ).",
+ "resource.non_persistent": "سيتم حذف جميع البيانات غير الدائمة.",
+ "resource.delete_volumes": "حذف جميع المجلدات والملفات المرتبطة بهذا المورد بشكل دائم.",
+ "resource.delete_connected_networks": "حذف جميع الشبكات غير المحددة مسبقًا والمرتبطة بهذا المورد بشكل دائم.",
+ "resource.delete_configurations": "حذف جميع ملفات التعريف من الخادم بشكل دائم.",
+ "database.delete_backups_locally": "حذف كافة النسخ الاحتياطية نهائيًا من التخزين المحلي."
}
diff --git a/lang/en.json b/lang/en.json
index fa69c7035..5ea474b02 100644
--- a/lang/en.json
+++ b/lang/en.json
@@ -33,5 +33,6 @@
"resource.delete_volumes": "Permanently delete all volumes associated with this resource.",
"resource.delete_connected_networks": "Permanently delete all non-predefined networks associated with this resource.",
"resource.delete_configurations": "Permanently delete all configuration files from the server.",
- "database.delete_backups_locally": "All backups will be permanently deleted from local storage."
+ "database.delete_backups_locally": "All backups will be permanently deleted from local storage.",
+ "warning.sslipdomain": "Your configuration is saved, but sslip domain with https is NOT recommended, because Let's Encrypt servers with this public domain are rate limited (SSL certificate validation will fail).
Use your own domain instead."
}
diff --git a/lang/fr.json b/lang/fr.json
index cb089812e..dbd5a1bf7 100644
--- a/lang/fr.json
+++ b/lang/fr.json
@@ -1,30 +1,37 @@
{
- "auth.login": "Connexion",
- "auth.login.azure": "Connexion avec Microsoft",
- "auth.login.bitbucket": "Connexion avec Bitbucket",
- "auth.login.github": "Connexion avec GitHub",
- "auth.login.gitlab": "Connexion avec Gitlab",
- "auth.login.google": "Connexion avec Google",
- "auth.already_registered": "Déjà enregistré ?",
- "auth.confirm_password": "Confirmer le mot de passe",
- "auth.forgot_password": "Mot de passe oublié",
- "auth.forgot_password_send_email": "Envoyer l'email de réinitialisation de mot de passe",
- "auth.register_now": "S'enregistrer",
- "auth.logout": "Déconnexion",
- "auth.register": "S'enregistrer",
- "auth.registration_disabled": "L'enregistrement est désactivé. Merci de contacter l'administateur.",
- "auth.reset_password": "Réinitialiser le mot de passe",
- "auth.failed": "Aucune correspondance n'a été trouvée pour les informations d'identification renseignées.",
- "auth.failed.callback": "Erreur lors du processus de retour de la plateforme de connexion.",
- "auth.failed.password": "Le mot de passe renseigné est incorrect.",
- "auth.failed.email": "Aucun utilisateur avec cette adresse email n'a été trouvé.",
- "auth.throttle": "Trop de tentatives de connexion. Merci de réessayer dans :seconds secondes.",
- "input.name": "Nom",
- "input.email": "Email",
- "input.password": "Mot de passe",
- "input.password.again": "Mot de passe identique",
- "input.code": "Code à usage unique",
- "input.recovery_code": "Code de récupération",
- "button.save": "Sauvegarder",
- "repository.url": "Exemples Pour les dépôts publiques, utilisez https://.... Pour les dépôts privés, utilisez git@....
https://github.com/coollabsio/coolify-examples main sera la branche selectionnée https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify sera la branche selectionnée. https://gitea.com/sedlav/expressjs.git main sera la branche selectionnée. https://gitlab.com/andrasbacsai/nodejs-example.git main sera la branche selectionnée."
+ "auth.login": "Connexion",
+ "auth.login.azure": "Connexion avec Microsoft",
+ "auth.login.bitbucket": "Connexion avec Bitbucket",
+ "auth.login.github": "Connexion avec GitHub",
+ "auth.login.gitlab": "Connexion avec Gitlab",
+ "auth.login.google": "Connexion avec Google",
+ "auth.already_registered": "Déjà enregistré ?",
+ "auth.confirm_password": "Confirmer le mot de passe",
+ "auth.forgot_password": "Mot de passe oublié",
+ "auth.forgot_password_send_email": "Envoyer l'email de réinitialisation de mot de passe",
+ "auth.register_now": "S'enregistrer",
+ "auth.logout": "Déconnexion",
+ "auth.register": "S'enregistrer",
+ "auth.registration_disabled": "L'enregistrement est désactivé. Merci de contacter l'administrateur.",
+ "auth.reset_password": "Réinitialiser le mot de passe",
+ "auth.failed": "Aucune correspondance n'a été trouvée pour les informations d'identification renseignées.",
+ "auth.failed.callback": "Erreur lors du processus de retour de la plateforme de connexion.",
+ "auth.failed.password": "Le mot de passe renseigné est incorrect.",
+ "auth.failed.email": "Aucun utilisateur avec cette adresse email n'a été trouvé.",
+ "auth.throttle": "Trop de tentatives de connexion. Merci de réessayer dans :seconds secondes.",
+ "input.name": "Nom",
+ "input.email": "Email",
+ "input.password": "Mot de passe",
+ "input.password.again": "Mot de passe identique",
+ "input.code": "Code à usage unique",
+ "input.recovery_code": "Code de récupération",
+ "button.save": "Sauvegarder",
+ "repository.url": "Exemples Pour les dépôts publiques, utilisez https://.... Pour les dépôts privés, utilisez git@....
https://github.com/coollabsio/coolify-examples main sera la branche selectionnée https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify sera la branche selectionnée. https://gitea.com/sedlav/expressjs.git main sera la branche selectionnée. https://gitlab.com/andrasbacsai/nodejs-example.git main sera la branche selectionnée.",
+ "service.stop": "Ce service sera arrêté.",
+ "resource.docker_cleanup": "Exécuter le nettoyage Docker (supprimer les images inutilisées et le cache du builder).",
+ "resource.non_persistent": "Toutes les données non persistantes seront supprimées.",
+ "resource.delete_volumes": "Supprimer définitivement tous les volumes associés à cette ressource.",
+ "resource.delete_connected_networks": "Supprimer définitivement tous les réseaux non-prédéfinis associés à cette ressource.",
+ "resource.delete_configurations": "Supprimer définitivement tous les fichiers de configuration du serveur.",
+ "database.delete_backups_locally": "Toutes les sauvegardes seront définitivement supprimées du stockage local."
}
diff --git a/lang/ro.json b/lang/ro.json
new file mode 100644
index 000000000..db1aa85db
--- /dev/null
+++ b/lang/ro.json
@@ -0,0 +1,37 @@
+{
+ "auth.login": "Autentificare",
+ "auth.login.azure": "Autentificare prin Microsoft",
+ "auth.login.bitbucket": "Autentificare prin Bitbucket",
+ "auth.login.github": "Autentificare prin GitHub",
+ "auth.login.gitlab": "Autentificare prin Gitlab",
+ "auth.login.google": "Autentificare prin Google",
+ "auth.already_registered": "Sunteți deja înregistrat?",
+ "auth.confirm_password": "Confirmați parola",
+ "auth.forgot_password": "Ați uitat parola",
+ "auth.forgot_password_send_email": "Trimiteți e-mail-ul pentru resetarea parolei",
+ "auth.register_now": "Înregistrare",
+ "auth.logout": "Deconectare",
+ "auth.register": "Înregistrare",
+ "auth.registration_disabled": "Înregistrarea este dezactivată. Vă rugăm să contactați administratorul site-ului.",
+ "auth.reset_password": "Resetare parolă",
+ "auth.failed": "Autentificare nereușită. Vă rugăm să verificați datele introduse.",
+ "auth.failed.callback": "A apărut o eroare în timpul autentificării cu furnizorul extern.",
+ "auth.failed.password": "Parola furnizată este incorectă.",
+ "auth.failed.email": "Nu putem găsi un utilizator cu această adresă de e-mail.",
+ "auth.throttle": "Prea multe încercări de autentificare. Vă rugăm să încercați din nou în :seconds secunde.",
+ "input.name": "Nume",
+ "input.email": "E-mail",
+ "input.password": "Parolă",
+ "input.password.again": "Repetați parola",
+ "input.code": "Cod de unică folosință",
+ "input.recovery_code": "Cod de recuperare",
+ "button.save": "Salvare",
+ "repository.url": "Exemple Pentru depozite publice, utilizați https://.... Pentru depozite private, utilizați git@....
https://github.com/coollabsio/coolify-examples va fi selectată ramura main https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify va fi selectată ramura nodejs-fastify. https://gitea.com/sedlav/expressjs.git va fi selectată ramura main. https://gitlab.com/andrasbacsai/nodejs-example.git va fi selectată ramura main.",
+ "service.stop": "Acest serviciu va fi oprit.",
+ "resource.docker_cleanup": "Executați curățarea Docker (eliminați imaginile neutilizate și memoria cache a constructorului).",
+ "resource.non_persistent": "Toate datele nepersistente vor fi șterse.",
+ "resource.delete_volumes": "Ștergeți definitiv toate volumele asociate cu această resursă.",
+ "resource.delete_connected_networks": "Ștergeți definitiv toate rețelele non-predefinite asociate cu această resursă.",
+ "resource.delete_configurations": "Ștergeți definitiv toate fișierele de configurare de pe server.",
+ "database.delete_backups_locally": "Toate copiile de rezervă vor fi șterse definitiv din stocarea locală."
+}
diff --git a/openapi.yaml b/openapi.yaml
index 91d5c1443..d2616e9c6 100644
--- a/openapi.yaml
+++ b/openapi.yaml
@@ -98,6 +98,10 @@ paths:
is_static:
type: boolean
description: 'The flag to indicate if the application is static.'
+ static_image:
+ type: string
+ enum: ['nginx:alpine']
+ description: 'The static image.'
install_command:
type: string
description: 'The install command.'
@@ -323,6 +327,10 @@ paths:
is_static:
type: boolean
description: 'The flag to indicate if the application is static.'
+ static_image:
+ type: string
+ enum: ['nginx:alpine']
+ description: 'The static image.'
install_command:
type: string
description: 'The install command.'
@@ -548,6 +556,10 @@ paths:
is_static:
type: boolean
description: 'The flag to indicate if the application is static.'
+ static_image:
+ type: string
+ enum: ['nginx:alpine']
+ description: 'The static image.'
install_command:
type: string
description: 'The install command.'
@@ -3093,7 +3105,7 @@ paths:
security:
-
bearerAuth: []
- /healthcheck:
+ /health:
get:
summary: Healthcheck
description: 'Healthcheck endpoint.'
@@ -4959,7 +4971,7 @@ components:
type: boolean
is_reachable:
type: boolean
- is_server_api_enabled:
+ is_sentinel_enabled:
type: boolean
is_swarm_manager:
type: boolean
@@ -4981,11 +4993,11 @@ components:
type: string
logdrain_newrelic_license_key:
type: string
- metrics_history_days:
+ sentinel_metrics_history_days:
type: integer
- metrics_refresh_rate_seconds:
+ sentinel_metrics_refresh_rate_seconds:
type: integer
- metrics_token:
+ sentinel_token:
type: string
docker_cleanup_frequency:
type: string
diff --git a/public/svgs/affine.svg b/public/svgs/affine.svg
new file mode 100644
index 000000000..d8063e920
--- /dev/null
+++ b/public/svgs/affine.svg
@@ -0,0 +1,88 @@
+
diff --git a/public/svgs/calcom.svg b/public/svgs/calcom.svg
new file mode 100644
index 000000000..446b16655
--- /dev/null
+++ b/public/svgs/calcom.svg
@@ -0,0 +1,9 @@
+
\ No newline at end of file
diff --git a/public/svgs/cloudbeaver.svg b/public/svgs/cloudbeaver.svg
new file mode 100644
index 000000000..4a7634766
--- /dev/null
+++ b/public/svgs/cloudbeaver.svg
@@ -0,0 +1,7 @@
+
diff --git a/public/svgs/coder.svg b/public/svgs/coder.svg
new file mode 100644
index 000000000..45b7f795c
--- /dev/null
+++ b/public/svgs/coder.svg
@@ -0,0 +1,8 @@
+
diff --git a/public/svgs/cryptgeon.png b/public/svgs/cryptgeon.png
new file mode 100644
index 000000000..be121cfd0
Binary files /dev/null and b/public/svgs/cryptgeon.png differ
diff --git a/public/svgs/dify.png b/public/svgs/dify.png
new file mode 100644
index 000000000..326acf789
Binary files /dev/null and b/public/svgs/dify.png differ
diff --git a/public/svgs/edgedb.svg b/public/svgs/edgedb.svg
new file mode 100644
index 000000000..a906f7f7e
--- /dev/null
+++ b/public/svgs/edgedb.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/svgs/flowise.png b/public/svgs/flowise.png
new file mode 100644
index 000000000..6b0be0d2a
Binary files /dev/null and b/public/svgs/flowise.png differ
diff --git a/public/svgs/foundryvtt.png b/public/svgs/foundryvtt.png
new file mode 100644
index 000000000..c6a04508f
Binary files /dev/null and b/public/svgs/foundryvtt.png differ
diff --git a/public/svgs/freshrss.png b/public/svgs/freshrss.png
new file mode 100644
index 000000000..d1a75118f
Binary files /dev/null and b/public/svgs/freshrss.png differ
diff --git a/public/svgs/heyform.svg b/public/svgs/heyform.svg
new file mode 100644
index 000000000..ff29ca654
--- /dev/null
+++ b/public/svgs/heyform.svg
@@ -0,0 +1,5 @@
+
diff --git a/public/svgs/immich.svg b/public/svgs/immich.svg
new file mode 100644
index 000000000..9d844a772
--- /dev/null
+++ b/public/svgs/immich.svg
@@ -0,0 +1,66 @@
+
+
+
diff --git a/public/svgs/kimai.svg b/public/svgs/kimai.svg
new file mode 100644
index 000000000..35b146972
--- /dev/null
+++ b/public/svgs/kimai.svg
@@ -0,0 +1,67 @@
+
\ No newline at end of file
diff --git a/public/svgs/libretranslate.svg b/public/svgs/libretranslate.svg
new file mode 100644
index 000000000..103d47d60
--- /dev/null
+++ b/public/svgs/libretranslate.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/svgs/litequeen.svg b/public/svgs/litequeen.svg
new file mode 100644
index 000000000..aa0b8e038
--- /dev/null
+++ b/public/svgs/litequeen.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/svgs/martin.png b/public/svgs/martin.png
new file mode 100644
index 000000000..d1a99e148
Binary files /dev/null and b/public/svgs/martin.png differ
diff --git a/public/svgs/mindsdb.svg b/public/svgs/mindsdb.svg
new file mode 100644
index 000000000..53799dd1c
--- /dev/null
+++ b/public/svgs/mindsdb.svg
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/svgs/mosquitto.png b/public/svgs/mosquitto.png
new file mode 100644
index 000000000..eb287a7cd
Binary files /dev/null and b/public/svgs/mosquitto.png differ
diff --git a/public/svgs/ntfy.svg b/public/svgs/ntfy.svg
new file mode 100644
index 000000000..9e5b5136f
--- /dev/null
+++ b/public/svgs/ntfy.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/svgs/osticket.png b/public/svgs/osticket.png
new file mode 100644
index 000000000..65885b71b
Binary files /dev/null and b/public/svgs/osticket.png differ
diff --git a/public/svgs/owncloud.svg b/public/svgs/owncloud.svg
new file mode 100644
index 000000000..83631e3f5
--- /dev/null
+++ b/public/svgs/owncloud.svg
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/svgs/peppermint.png b/public/svgs/peppermint.png
new file mode 100644
index 000000000..38db83de0
Binary files /dev/null and b/public/svgs/peppermint.png differ
diff --git a/public/svgs/qbittorrent.svg b/public/svgs/qbittorrent.svg
new file mode 100644
index 000000000..69d8cf62a
--- /dev/null
+++ b/public/svgs/qbittorrent.svg
@@ -0,0 +1,16 @@
+
+
+ qbittorrent-new-light
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/svgs/traccar.png b/public/svgs/traccar.png
new file mode 100644
index 000000000..c747aea05
Binary files /dev/null and b/public/svgs/traccar.png differ
diff --git a/public/svgs/transmission.svg b/public/svgs/transmission.svg
new file mode 100644
index 000000000..9a11f77f4
--- /dev/null
+++ b/public/svgs/transmission.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/svgs/unsend.svg b/public/svgs/unsend.svg
new file mode 100644
index 000000000..f5ff6fabc
--- /dev/null
+++ b/public/svgs/unsend.svg
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/svgs/vvveb.svg b/public/svgs/vvveb.svg
new file mode 100644
index 000000000..2b66b3087
--- /dev/null
+++ b/public/svgs/vvveb.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/svgs/wireguard.svg b/public/svgs/wireguard.svg
new file mode 100644
index 000000000..81823b3eb
--- /dev/null
+++ b/public/svgs/wireguard.svg
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/public/svgs/zep.png b/public/svgs/zep.png
new file mode 100644
index 000000000..7d51b32dc
Binary files /dev/null and b/public/svgs/zep.png differ
diff --git a/public/svgs/zipline.png b/public/svgs/zipline.png
new file mode 100644
index 000000000..2b8f6972d
Binary files /dev/null and b/public/svgs/zipline.png differ
diff --git a/resources/views/components/forms/checkbox.blade.php b/resources/views/components/forms/checkbox.blade.php
index 439fc4ad2..fed6ad77f 100644
--- a/resources/views/components/forms/checkbox.blade.php
+++ b/resources/views/components/forms/checkbox.blade.php
@@ -14,7 +14,10 @@
'w-full' => $fullWidth,
])>
@if (!$hideLabel)
-