fix: build notification and deployment log links from the instance url (#11623)
This commit is contained in:
commit
7ca584877a
11 changed files with 88 additions and 119 deletions
|
|
@ -2362,12 +2362,8 @@ private function deploy_to_additional_destinations()
|
|||
destination: $destination,
|
||||
no_questions_asked: true,
|
||||
);
|
||||
$this->application_deployment_queue->addLogEntry("Deployment to {$server->name}. Logs: ".route('project.application.deployment.show', [
|
||||
'project_uuid' => data_get($this->application, 'environment.project.uuid'),
|
||||
'application_uuid' => data_get($this->application, 'uuid'),
|
||||
'deployment_uuid' => $deployment_uuid,
|
||||
'environment_uuid' => data_get($this->application, 'environment.uuid'),
|
||||
]));
|
||||
$deployment_url = base_url().'/project/'.data_get($this->application, 'environment.project.uuid').'/environment/'.data_get($this->application, 'environment.uuid').'/application/'.data_get($this->application, 'uuid')."/deployment/{$deployment_uuid}";
|
||||
$this->application_deployment_queue->addLogEntry("Deployment to {$server->name}. Logs: {$deployment_url}");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -66,7 +66,10 @@ public function handle()
|
|||
caCert: $caCert->ssl_certificate,
|
||||
caKey: $caCert->ssl_private_key,
|
||||
);
|
||||
$regenerated->push($certificate);
|
||||
$resource = $certificate->database;
|
||||
if ($resource) {
|
||||
$regenerated->push($resource);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Failed to regenerate SSL certificate: '.$e->getMessage());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -621,32 +621,6 @@ public function stoppedAfterRestartLimit(): bool
|
|||
&& $this->restart_limit_reached === true;
|
||||
}
|
||||
|
||||
public function taskLink($task_uuid)
|
||||
{
|
||||
if (data_get($this, 'environment.project.uuid')) {
|
||||
$route = route('project.application.scheduled-tasks', [
|
||||
'project_uuid' => data_get($this, 'environment.project.uuid'),
|
||||
'environment_uuid' => data_get($this, 'environment.uuid'),
|
||||
'application_uuid' => data_get($this, 'uuid'),
|
||||
'task_uuid' => $task_uuid,
|
||||
]);
|
||||
$settings = instanceSettings();
|
||||
if (data_get($settings, 'fqdn')) {
|
||||
$url = Url::fromString($route);
|
||||
$url = $url->withPort(null);
|
||||
$fqdn = data_get($settings, 'fqdn');
|
||||
$fqdn = str_replace(['http://', 'https://'], '', $fqdn);
|
||||
$url = $url->withHost($fqdn);
|
||||
|
||||
return $url->__toString();
|
||||
}
|
||||
|
||||
return $route;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function settings()
|
||||
{
|
||||
return $this->hasOne(ApplicationSetting::class);
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@
|
|||
use Illuminate\Support\Facades\Storage;
|
||||
use OpenApi\Attributes as OA;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
use Spatie\Url\Url;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
#[OA\Schema(
|
||||
|
|
@ -1463,32 +1462,6 @@ public function link()
|
|||
return null;
|
||||
}
|
||||
|
||||
public function taskLink($task_uuid)
|
||||
{
|
||||
if (data_get($this, 'environment.project.uuid')) {
|
||||
$route = route('project.service.scheduled-tasks', [
|
||||
'project_uuid' => data_get($this, 'environment.project.uuid'),
|
||||
'environment_uuid' => data_get($this, 'environment.uuid'),
|
||||
'service_uuid' => data_get($this, 'uuid'),
|
||||
'task_uuid' => $task_uuid,
|
||||
]);
|
||||
$settings = InstanceSettings::get();
|
||||
if (data_get($settings, 'fqdn')) {
|
||||
$url = Url::fromString($route);
|
||||
$url = $url->withPort(null);
|
||||
$fqdn = data_get($settings, 'fqdn');
|
||||
$fqdn = str_replace(['http://', 'https://'], '', $fqdn);
|
||||
$url = $url->withHost($fqdn);
|
||||
|
||||
return $url->__toString();
|
||||
}
|
||||
|
||||
return $route;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function documentation()
|
||||
{
|
||||
$services = get_service_templates();
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ public function __construct(public PersonalAccessToken $token)
|
|||
$this->onQueue('high');
|
||||
$this->tokenName = $token->name;
|
||||
$this->expiresAt = $token->expires_at?->format('Y-m-d H:i:s') ?? '';
|
||||
$this->manageUrl = route('security.api-tokens');
|
||||
$this->manageUrl = base_url().'/security/api-tokens';
|
||||
}
|
||||
|
||||
public function via(object $notifiable): array
|
||||
|
|
|
|||
|
|
@ -2,8 +2,11 @@
|
|||
|
||||
namespace App\Notifications\Application;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationPreview;
|
||||
use App\Models\BaseModel;
|
||||
use App\Models\ServiceApplication;
|
||||
use App\Models\ServiceDatabase;
|
||||
use App\Notifications\CustomEmailNotification;
|
||||
use App\Notifications\Dto\DiscordMessage;
|
||||
use App\Notifications\Dto\PushoverMessage;
|
||||
|
|
@ -49,14 +52,19 @@ public function __construct(public BaseModel $resource)
|
|||
if (str($this->fqdn)->explode(',')->count() > 1) {
|
||||
$this->fqdn = str($this->fqdn)->explode(',')->first();
|
||||
}
|
||||
$service = data_get($resource, 'service');
|
||||
$this->resource_url = match (true) {
|
||||
method_exists($this->resource, 'link') => $this->resource->link(),
|
||||
$resource instanceof ApplicationPreview => $resource->application->link(),
|
||||
is_object($service) && method_exists($service, 'link') => $service->link(),
|
||||
default => null,
|
||||
$this->resource_url = $this->resolveResourceUrl($resource);
|
||||
}
|
||||
|
||||
private function resolveResourceUrl(BaseModel $resource): string
|
||||
{
|
||||
[$type, $uuid] = match (true) {
|
||||
$resource instanceof Application => ['application', $resource->uuid],
|
||||
$resource instanceof ApplicationPreview => ['application', $resource->application->uuid],
|
||||
$resource instanceof ServiceApplication, $resource instanceof ServiceDatabase => ['service', $resource->service->uuid],
|
||||
default => ['database', $resource->uuid],
|
||||
};
|
||||
$this->resource_url ??= base_url()."/project/{$this->project_uuid}/environment/{$this->environment_uuid}";
|
||||
|
||||
return base_url()."/project/{$this->project_uuid}/environment/{$this->environment_uuid}/{$type}/{$uuid}";
|
||||
}
|
||||
|
||||
public function via(object $notifiable): array
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace App\Notifications\ScheduledTask;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\ScheduledTask;
|
||||
use App\Notifications\CustomEmailNotification;
|
||||
use App\Notifications\Dto\DiscordMessage;
|
||||
|
|
@ -16,10 +17,10 @@ class TaskFailed extends CustomEmailNotification
|
|||
public function __construct(public ScheduledTask $task, public string $output)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
if ($task->application) {
|
||||
$this->url = $task->application->taskLink($task->uuid);
|
||||
} elseif ($task->service) {
|
||||
$this->url = $task->service->taskLink($task->uuid);
|
||||
$resource = $task->application ?? $task->service;
|
||||
if ($resource) {
|
||||
$type = $resource instanceof Application ? 'application' : 'service';
|
||||
$this->url = base_url().'/project/'.data_get($resource, 'environment.project.uuid').'/environment/'.data_get($resource, 'environment.uuid')."/{$type}/{$resource->uuid}/tasks/{$task->uuid}";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace App\Notifications\ScheduledTask;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\ScheduledTask;
|
||||
use App\Notifications\CustomEmailNotification;
|
||||
use App\Notifications\Dto\DiscordMessage;
|
||||
|
|
@ -16,10 +17,10 @@ class TaskSuccess extends CustomEmailNotification
|
|||
public function __construct(public ScheduledTask $task, public string $output)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
if ($task->application) {
|
||||
$this->url = $task->application->taskLink($task->uuid);
|
||||
} elseif ($task->service) {
|
||||
$this->url = $task->service->taskLink($task->uuid);
|
||||
$resource = $task->application ?? $task->service;
|
||||
if ($resource) {
|
||||
$type = $resource instanceof Application ? 'application' : 'service';
|
||||
$this->url = base_url().'/project/'.data_get($resource, 'environment.project.uuid').'/environment/'.data_get($resource, 'environment.uuid')."/{$type}/{$resource->uuid}/tasks/{$task->uuid}";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
use App\Notifications\Dto\SlackMessage;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Support\Collection;
|
||||
use Spatie\Url\Url;
|
||||
|
||||
class SslExpirationNotification extends CustomEmailNotification
|
||||
{
|
||||
|
|
@ -19,39 +18,9 @@ public function __construct(array|Collection $resources)
|
|||
{
|
||||
$this->onQueue('high');
|
||||
$this->resources = collect($resources);
|
||||
|
||||
// Collect URLs for each resource
|
||||
$this->resources->each(function ($resource) {
|
||||
if (data_get($resource, 'environment.project.uuid')) {
|
||||
$routeName = match ($resource->type()) {
|
||||
'application' => 'project.application.configuration',
|
||||
'database' => 'project.database.configuration',
|
||||
'service' => 'project.service.configuration',
|
||||
default => null
|
||||
};
|
||||
|
||||
if ($routeName) {
|
||||
$route = route($routeName, [
|
||||
'project_uuid' => data_get($resource, 'environment.project.uuid'),
|
||||
'environment_uuid' => data_get($resource, 'environment.uuid'),
|
||||
$resource->type().'_uuid' => data_get($resource, 'uuid'),
|
||||
]);
|
||||
|
||||
$settings = instanceSettings();
|
||||
if (data_get($settings, 'fqdn')) {
|
||||
$url = Url::fromString($route);
|
||||
$url = $url->withPort(null);
|
||||
$fqdn = data_get($settings, 'fqdn');
|
||||
$fqdn = str_replace(['http://', 'https://'], '', $fqdn);
|
||||
$url = $url->withHost($fqdn);
|
||||
|
||||
$this->urls[$resource->name] = $url->__toString();
|
||||
} else {
|
||||
$this->urls[$resource->name] = $route;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
$this->urls = $this->resources->mapWithKeys(fn ($resource) => [
|
||||
$resource->name => base_url().'/project/'.data_get($resource, 'environment.project.uuid').'/environment/'.data_get($resource, 'environment.uuid')."/database/{$resource->uuid}",
|
||||
])->all();
|
||||
}
|
||||
|
||||
public function via(object $notifiable): array
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
|
||||
use App\Jobs\ApiTokenExpirationWarningJob;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\PersonalAccessToken;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
|
|
@ -14,6 +15,7 @@
|
|||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
InstanceSettings::forceCreate(['id' => 0]);
|
||||
$this->team = Team::factory()->create();
|
||||
$this->user = User::factory()->create();
|
||||
$this->team->members()->attach($this->user->id, ['role' => 'owner']);
|
||||
|
|
@ -137,4 +139,15 @@ function createTokenExpiring(User $user, Team $team, ?Carbon $expiresAt, ?Carbon
|
|||
Notification::assertNothingSent();
|
||||
expect($token->fresh()->api_token_expiration_warning_sent_at)->toBeNull();
|
||||
});
|
||||
|
||||
test('manage url uses the instance fqdn when configured', function () {
|
||||
InstanceSettings::query()->update(['fqdn' => 'https://coolify.example.com']);
|
||||
$token = createTokenExpiring($this->user, $this->team, Carbon::now()->addHours(12));
|
||||
|
||||
$notification = new ApiTokenExpiringNotification($token);
|
||||
|
||||
expect($notification->toSlack()->description)
|
||||
->toContain('https://coolify.example.com/security/api-tokens')
|
||||
->not->toContain('localhost');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,10 +6,21 @@
|
|||
use App\Models\Application;
|
||||
use App\Models\ApplicationPreview;
|
||||
use App\Models\BaseModel;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
use App\Models\ServiceApplication;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Notifications\Application\RestartLimitReached;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Mockery\MockInterface;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
InstanceSettings::forceCreate(['id' => 0, 'fqdn' => 'https://coolify.test']);
|
||||
});
|
||||
|
||||
function applicationWithRestartState(array $attributes = []): Application
|
||||
{
|
||||
$application = new Application;
|
||||
|
|
@ -161,14 +172,8 @@ function applicationWithRestartState(array $attributes = []): Application
|
|||
->and($sentinelJob)->toContain('if ($application->stoppedAfterRestartLimit() && $containerStatuses->every(');
|
||||
});
|
||||
|
||||
it('uses the application link for restart limit notifications', function () {
|
||||
$application = new class extends Application
|
||||
{
|
||||
public function link()
|
||||
{
|
||||
return 'https://coolify.test/project/link-from-model';
|
||||
}
|
||||
};
|
||||
it('builds restart limit notification urls from the instance base url', function () {
|
||||
$application = new Application;
|
||||
$application->forceFill([
|
||||
'name' => 'crashy-app',
|
||||
'uuid' => 'application-uuid',
|
||||
|
|
@ -183,7 +188,33 @@ public function link()
|
|||
|
||||
$notification = new RestartLimitReached($application);
|
||||
|
||||
expect($notification->resource_url)->toBe('https://coolify.test/project/link-from-model');
|
||||
expect($notification->resource_url)->toBe('https://coolify.test/project/project-uuid/environment/environment-uuid/application/application-uuid');
|
||||
});
|
||||
|
||||
it('links preview, service resource and database restart limit notifications to their pages', function () {
|
||||
$environment = (object) ['uuid' => 'environment-uuid', 'name' => 'production', 'project' => (object) ['uuid' => 'project-uuid']];
|
||||
|
||||
$application = new Application;
|
||||
$application->forceFill(['name' => 'app', 'uuid' => 'application-uuid']);
|
||||
$application->setRelation('environment', $environment);
|
||||
$preview = new ApplicationPreview;
|
||||
$preview->forceFill(['uuid' => 'preview-uuid', 'pull_request_id' => 42, 'restart_count' => 2, 'max_restart_count' => 2]);
|
||||
$preview->setRelation('application', $application);
|
||||
|
||||
$service = new Service;
|
||||
$service->forceFill(['uuid' => 'service-uuid']);
|
||||
$service->setRelation('environment', $environment);
|
||||
$serviceApplication = new ServiceApplication;
|
||||
$serviceApplication->forceFill(['name' => 'database', 'uuid' => 'service-application-uuid', 'restart_count' => 2, 'max_restart_count' => 2]);
|
||||
$serviceApplication->setRelation('service', $service);
|
||||
|
||||
$database = new StandalonePostgresql;
|
||||
$database->forceFill(['name' => 'postgres', 'uuid' => 'database-uuid', 'restart_count' => 2, 'max_restart_count' => 2]);
|
||||
$database->setRelation('environment', $environment);
|
||||
|
||||
expect((new RestartLimitReached($preview))->resource_url)->toBe('https://coolify.test/project/project-uuid/environment/environment-uuid/application/application-uuid')
|
||||
->and((new RestartLimitReached($serviceApplication))->resource_url)->toBe('https://coolify.test/project/project-uuid/environment/environment-uuid/service/service-uuid')
|
||||
->and((new RestartLimitReached($database))->resource_url)->toBe('https://coolify.test/project/project-uuid/environment/environment-uuid/database/database-uuid');
|
||||
});
|
||||
|
||||
it('uses the resolved environment project name in Slack restart limit notifications', function () {
|
||||
|
|
|
|||
Loading…
Reference in a new issue