fix: restore config diff, error text, delete handler, webhook id and danger zone fixes

- Configuration diff: report all settings when an app has no earlier
  snapshot, so older apps show "configuration changed" again (#11430).
- DigitalOcean/Vultr: do not repeat the "API error:" prefix.
- HasSecretManager: the deleting listener returns nothing, so later
  deleting listeners still run.
- GitLab webhook: use new_public_id() like the other webhooks.
- Team danger zone: show the link and open the page only for the team owner.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andras Bacsai
2026-09-28 09:35:32 +02:00
co-authored by Claude Opus 5.5
parent 178e6665f8
commit 7c60ccda0d
8 changed files with 40 additions and 14 deletions
+3 -4
View File
@@ -17,7 +17,6 @@ use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Visus\Cuid2\Cuid2;
class Gitlab extends Controller
{
@@ -185,7 +184,7 @@ class Gitlab extends Controller
continue;
}
$deployment_uuid = new Cuid2;
$deployment_uuid = new_public_id();
$result = queue_application_deployment(
application: $application,
deployment_uuid: $deployment_uuid,
@@ -203,7 +202,7 @@ class Gitlab extends Controller
'mode' => 'app',
'application_uuid' => $application->uuid,
'application_name' => $application->name,
'deployment_uuid' => $deployment_uuid->toString(),
'deployment_uuid' => $deployment_uuid,
'commit' => $commit,
]);
@@ -275,7 +274,7 @@ class Gitlab extends Controller
continue;
}
$deployment_uuid = new Cuid2;
$deployment_uuid = new_public_id();
$found = ApplicationPreview::where('application_id', $application->id)
->where('pull_request_id', $pull_request_id)
->first();
+3 -1
View File
@@ -296,7 +296,9 @@ class ByDigitalOcean extends Component
$details = data_get($e->response->json(), $jsonMessageKey) ?: $e->response->body() ?: $details;
}
return "{$providerName} API error: {$details}";
$prefix = "{$providerName} API error: ";
return str_starts_with($details, $prefix) ? $details : $prefix.$details;
}
public function getAvailableSizesProperty(): array
+3 -1
View File
@@ -375,7 +375,9 @@ class ByVultr extends Component
$details = data_get($e->response->json(), $jsonMessageKey) ?: $e->response->body() ?: $details;
}
return "{$providerName} API error: {$details}";
$prefix = "{$providerName} API error: ";
return str_starts_with($details, $prefix) ? $details : $prefix.$details;
}
private function createVultrServer(VultrService $vultrService): array
+1
View File
@@ -16,6 +16,7 @@ class DangerZone extends Component
public function mount(): void
{
$this->team = currentTeam();
$this->authorize('delete', $this->team);
}
public function delete(): mixed
@@ -39,7 +39,9 @@ class ConfigurationDiffer
$previous = $previousItems[$key] ?? null;
$current = $currentItems[$key] ?? null;
if ($previous === null && ! in_array(data_get($current, 'section'), self::DYNAMIC_SECTIONS, true)) {
// Static settings missing from an older snapshot were newly tracked, not changed.
// Without any previous snapshot (legacy fallback), every setting is reported.
if ($previous === null && $previousItems !== [] && ! in_array(data_get($current, 'section'), self::DYNAMIC_SECTIONS, true)) {
continue;
}
+4 -1
View File
@@ -15,7 +15,10 @@ trait HasSecretManager
public static function bootHasSecretManager(): void
{
static::deleting(fn ($resource) => $resource->secretManagerLink()->delete());
// Return nothing: a non-null result halts the "deleting" event and skips later listeners.
static::deleting(function ($resource): void {
$resource->secretManagerLink()->delete();
});
}
public function secretManagerLink(): MorphOne
@@ -24,13 +24,13 @@
'active' => request()->routeIs('team.admin-view'),
'icon' => 'admin',
] : null,
[
auth()->user()->can('delete', currentTeam()) ? [
'label' => 'Danger Zone',
'route' => 'team.danger-zone',
'active' => request()->routeIs('team.danger-zone'),
'icon' => 'shield-alert',
'sectionStart' => true,
],
] : null,
])->filter();
@endphp
@@ -171,17 +171,34 @@ test('team index mounts when is_mcp_server_enabled is null on the session team',
// --- Team Danger Zone Livewire: delete ---
test('member cannot delete team via danger zone', function () {
test('member cannot open the team danger zone', function () {
$this->actingAs($this->member);
session(['currentTeam' => $this->team]);
Livewire::test(DangerZone::class)
->call('delete')
->assertDispatched('error');
Livewire::test(DangerZone::class)->assertForbidden();
expect(Team::find($this->team->id))->not->toBeNull();
});
test('owner can open the team danger zone', function () {
$this->actingAs($this->owner);
session(['currentTeam' => $this->team]);
Livewire::test(DangerZone::class)->assertSuccessful();
});
test('team settings show the danger zone link only to the team owner', function () {
session(['currentTeam' => $this->team]);
$this->actingAs($this->owner);
Livewire::test(TeamIndex::class)->assertSee('Danger Zone');
foreach ([$this->admin, $this->member] as $user) {
$this->actingAs($user);
Livewire::test(TeamIndex::class)->assertDontSee('Danger Zone');
}
});
test('admin cannot delete team via policy', function () {
$this->actingAs($this->admin);
session(['currentTeam' => $this->team]);