From a4ab69df56fb24be1bd4ec1960a43e9e56d7aa30 Mon Sep 17 00:00:00 2001
From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com>
Date: Sat, 18 Jul 2026 15:55:47 +0200
Subject: [PATCH] feat(v5): authorize creates, deep-link selection, list apps
in v4
Restrict V5 application and resource-connection creation to team admins.
Resolve project, environment, and application from query params and keep
session selection in sync. Surface V5 apps on the v4 resource index and
count them for project/environment emptiness. Create the flux data dir
on install and upgrade.
---
.../Controllers/V5/ApplicationController.php | 1 +
.../V5/Concerns/ResolvesProjectSelection.php | 11 +++-
.../Controllers/V5/DashboardController.php | 8 ++-
.../V5/ResourceConnectionController.php | 1 +
app/Livewire/Project/Resource/Index.php | 28 ++++++++-
app/Models/Environment.php | 11 +++-
app/Models/Project.php | 6 +-
app/Policies/V5/ApplicationPolicy.php | 7 +++
app/Policies/V5/ResourceConnectionPolicy.php | 7 +++
other/nightly/install.sh | 3 +-
other/nightly/upgrade.sh | 3 +
package-lock.json | 58 ++++++++++++-------
resources/js/v5/Pages/Dashboard.tsx | 16 ++++-
resources/js/v5/types.ts | 1 +
.../livewire/project/resource/index.blade.php | 20 +++++--
scripts/install.sh | 3 +-
scripts/upgrade.sh | 3 +
tests/Feature/V5/DashboardControllerTest.php | 31 ++++++++++
.../Feature/V5/V5CreateAuthorizationTest.php | 33 +++++++++++
tests/Feature/V5/V5ParentLifecycleTest.php | 47 +++++++++++++++
tests/Unit/UpgradePostgresScriptTest.php | 13 +++++
tests/Unit/V5/Policies/V5PolicyTest.php | 12 ++++
.../V5/V4ResourceIndexV5ApplicationTest.php | 14 +++++
23 files changed, 299 insertions(+), 38 deletions(-)
create mode 100644 tests/Feature/V5/V5CreateAuthorizationTest.php
create mode 100644 tests/Feature/V5/V5ParentLifecycleTest.php
create mode 100644 tests/Unit/V5/V4ResourceIndexV5ApplicationTest.php
diff --git a/app/Http/Controllers/V5/ApplicationController.php b/app/Http/Controllers/V5/ApplicationController.php
index 8d11cd6b6..ca1a4c636 100644
--- a/app/Http/Controllers/V5/ApplicationController.php
+++ b/app/Http/Controllers/V5/ApplicationController.php
@@ -51,6 +51,7 @@ public function __construct(private readonly ConnectionFirewallSync $firewallSyn
public function store(Request $request): JsonResponse
{
$currentTeam = $this->currentTeamOrFail($request);
+ $this->authorize('create', [V5Application::class, $currentTeam]);
$projects = $this->projects($currentTeam);
[$selectedProject, $selectedEnvironment] = $this->selectedProjectAndEnvironment($request, $projects);
diff --git a/app/Http/Controllers/V5/Concerns/ResolvesProjectSelection.php b/app/Http/Controllers/V5/Concerns/ResolvesProjectSelection.php
index 1d48eff33..a741d7f8f 100644
--- a/app/Http/Controllers/V5/Concerns/ResolvesProjectSelection.php
+++ b/app/Http/Controllers/V5/Concerns/ResolvesProjectSelection.php
@@ -34,8 +34,8 @@ protected function serializeCurrentTeam(mixed $currentTeam): ?array
*/
protected function selectedProjectAndEnvironment(Request $request, array $projects): array
{
- $selectedProjectUuid = $request->session()->get(self::SELECTED_PROJECT_SESSION_KEY);
- $selectedEnvironmentUuid = $request->session()->get(self::SELECTED_ENVIRONMENT_SESSION_KEY);
+ $selectedProjectUuid = $request->query('project', $request->session()->get(self::SELECTED_PROJECT_SESSION_KEY));
+ $selectedEnvironmentUuid = $request->query('environment', $request->session()->get(self::SELECTED_ENVIRONMENT_SESSION_KEY));
$selectedProject = null;
foreach ($projects as $project) {
@@ -59,6 +59,13 @@ protected function selectedProjectAndEnvironment(Request $request, array $projec
$selectedEnvironment ??= $selectedProject['environments'][0] ?? null;
+ if ($request->query->has('project') || $request->query->has('environment')) {
+ $request->session()->put([
+ self::SELECTED_PROJECT_SESSION_KEY => $selectedProject['uuid'] ?? null,
+ self::SELECTED_ENVIRONMENT_SESSION_KEY => $selectedEnvironment['uuid'] ?? null,
+ ]);
+ }
+
return [$selectedProject, $selectedEnvironment];
}
diff --git a/app/Http/Controllers/V5/DashboardController.php b/app/Http/Controllers/V5/DashboardController.php
index 9f5e26736..de7176875 100644
--- a/app/Http/Controllers/V5/DashboardController.php
+++ b/app/Http/Controllers/V5/DashboardController.php
@@ -33,17 +33,23 @@ public function __invoke(Request $request, FluxHealth $fluxHealth): Response
$currentTeam = $request->attributes->get('v5.currentTeam');
$projects = $this->projects($currentTeam);
[$selectedProject, $selectedEnvironment] = $this->selectedProjectAndEnvironment($request, $projects);
+ $applications = $this->applications($currentTeam, $selectedProject, $selectedEnvironment);
+ $requestedApplicationUuid = $request->query('application');
+ $selectedApplicationUuid = collect($applications)->contains(
+ fn (array $application): bool => $application['id'] === $requestedApplicationUuid
+ ) ? $requestedApplicationUuid : null;
return Inertia::render('Dashboard', [
'currentTeam' => $this->serializeCurrentTeam($currentTeam),
'flux' => $fluxHealth->check(),
- 'applications' => $this->applications($currentTeam, $selectedProject, $selectedEnvironment),
+ 'applications' => $applications,
'caddyIngresses' => $this->caddyIngresses($currentTeam),
'resourceConnections' => $this->resourceConnections($currentTeam, $selectedProject, $selectedEnvironment),
'nginxServers' => $this->nginxServers($currentTeam),
'projects' => $projects,
'selectedProjectUuid' => $selectedProject['uuid'] ?? null,
'selectedEnvironmentUuid' => $selectedEnvironment['uuid'] ?? null,
+ 'selectedApplicationUuid' => $selectedApplicationUuid,
]);
}
diff --git a/app/Http/Controllers/V5/ResourceConnectionController.php b/app/Http/Controllers/V5/ResourceConnectionController.php
index d901940d6..299432e83 100644
--- a/app/Http/Controllers/V5/ResourceConnectionController.php
+++ b/app/Http/Controllers/V5/ResourceConnectionController.php
@@ -35,6 +35,7 @@ public function __construct(
public function store(Request $request): JsonResponse
{
$currentTeam = $this->currentTeamOrFail($request);
+ $this->authorize('create', [ResourceConnection::class, $currentTeam]);
$projects = $this->projects($currentTeam);
[$selectedProject, $selectedEnvironment] = $this->selectedProjectAndEnvironment($request, $projects);
diff --git a/app/Livewire/Project/Resource/Index.php b/app/Livewire/Project/Resource/Index.php
index 094b61b28..acfa1c03f 100644
--- a/app/Livewire/Project/Resource/Index.php
+++ b/app/Livewire/Project/Resource/Index.php
@@ -4,6 +4,7 @@
use App\Models\Environment;
use App\Models\Project;
+use App\Models\V5\Application as V5Application;
use Illuminate\Support\Collection;
use Livewire\Component;
@@ -61,6 +62,7 @@ public function mount(): void
->select('id', 'uuid', 'name', 'project_id')
->with([
'applications:id,uuid,name,environment_id',
+ 'v5Applications:id,uuid,name,environment_id,status',
'services:id,uuid,name,environment_id',
'postgresqls:id,uuid,name,environment_id',
'redis:id,uuid,name,environment_id',
@@ -103,6 +105,23 @@ public function mount(): void
return $application;
});
+ $this->applications = $this->applications
+ ->merge(V5Application::query()
+ ->where('team_id', currentTeam()->id)
+ ->where('project_id', $this->project->id)
+ ->where('environment_id', $this->environment->id)
+ ->with('server:id,name')
+ ->get()
+ ->map(function (V5Application $application) use ($projectUuid, $environmentUuid) {
+ $application->hrefLink = route('v5.dashboard', [
+ 'project' => $projectUuid,
+ 'environment' => $environmentUuid,
+ 'application' => $application->uuid,
+ ]);
+
+ return $application;
+ }))
+ ->sortBy('name');
// Load all database resources in a single query per type
$databaseTypes = [
@@ -180,16 +199,19 @@ private function toSearchableArray(Collection $items): array
'uuid' => $item->uuid,
'name' => $item->name,
'fqdn' => $item->fqdn ?? null,
- 'description' => $item->description ?? null,
+ 'description' => $item instanceof V5Application ? 'Managed by Coolify V5' : ($item->description ?? null),
'status' => $item->status ?? '',
+ 'version' => $item instanceof V5Application ? 'v5' : 'v4',
'server_status' => $item->server_status ?? null,
'hrefLink' => $item->hrefLink ?? '',
'destination' => [
'server' => [
- 'name' => $item->destination?->server?->name ?? 'Unknown',
+ 'name' => $item instanceof V5Application
+ ? ($item->server?->name ?? 'Unknown')
+ : ($item->destination?->server?->name ?? 'Unknown'),
],
],
- 'tags' => $item->tags->map(fn ($tag) => [
+ 'tags' => ($item instanceof V5Application ? collect() : $item->tags)->map(fn ($tag) => [
'id' => $tag->id,
'name' => $tag->name,
])->values()->toArray(),
diff --git a/app/Models/Environment.php b/app/Models/Environment.php
index 1364d874a..0e5e3c673 100644
--- a/app/Models/Environment.php
+++ b/app/Models/Environment.php
@@ -2,6 +2,8 @@
namespace App\Models;
+use App\Models\V5\Application as V5Application;
+use App\Models\V5\ResourceConnection as V5ResourceConnection;
use App\Traits\ClearsGlobalSearchCache;
use App\Traits\HasSafeStringAttribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
@@ -54,7 +56,9 @@ public static function ownedByCurrentTeamAPI(int $teamId)
public function isEmpty()
{
- return $this->applications()->count() == 0 &&
+ return ! V5Application::query()->where('environment_id', $this->id)->exists() &&
+ ! V5ResourceConnection::query()->where('environment_id', $this->id)->exists() &&
+ $this->applications()->count() == 0 &&
$this->redis()->count() == 0 &&
$this->postgresqls()->count() == 0 &&
$this->mysqls()->count() == 0 &&
@@ -76,6 +80,11 @@ public function applications()
return $this->hasMany(Application::class);
}
+ public function v5Applications()
+ {
+ return $this->hasMany(V5Application::class);
+ }
+
public function postgresqls()
{
return $this->hasMany(StandalonePostgresql::class);
diff --git a/app/Models/Project.php b/app/Models/Project.php
index 5c821b017..fe64e320d 100644
--- a/app/Models/Project.php
+++ b/app/Models/Project.php
@@ -2,6 +2,8 @@
namespace App\Models;
+use App\Models\V5\Application as V5Application;
+use App\Models\V5\ResourceConnection as V5ResourceConnection;
use App\Traits\ClearsGlobalSearchCache;
use App\Traits\HasSafeStringAttribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
@@ -144,7 +146,9 @@ public function mariadbs()
public function isEmpty()
{
- return $this->applications()->count() == 0 &&
+ return ! V5Application::query()->where('project_id', $this->id)->exists() &&
+ ! V5ResourceConnection::query()->where('project_id', $this->id)->exists() &&
+ $this->applications()->count() == 0 &&
$this->redis()->count() == 0 &&
$this->postgresqls()->count() == 0 &&
$this->mysqls()->count() == 0 &&
diff --git a/app/Policies/V5/ApplicationPolicy.php b/app/Policies/V5/ApplicationPolicy.php
index 5b1877a00..aca24aa95 100644
--- a/app/Policies/V5/ApplicationPolicy.php
+++ b/app/Policies/V5/ApplicationPolicy.php
@@ -9,6 +9,13 @@
class ApplicationPolicy
{
+ public function create(User $user, Team $team): Response
+ {
+ return $user->isAdminOfTeam($team->id)
+ ? Response::allow()
+ : Response::deny('You do not have permission to manage applications in this team.');
+ }
+
/**
* Determine whether the user can view the application within the current team.
*
diff --git a/app/Policies/V5/ResourceConnectionPolicy.php b/app/Policies/V5/ResourceConnectionPolicy.php
index 6b3dcde79..04e472e8e 100644
--- a/app/Policies/V5/ResourceConnectionPolicy.php
+++ b/app/Policies/V5/ResourceConnectionPolicy.php
@@ -9,6 +9,13 @@
class ResourceConnectionPolicy
{
+ public function create(User $user, Team $team): Response
+ {
+ return $user->isAdminOfTeam($team->id)
+ ? Response::allow()
+ : Response::deny('You do not have permission to manage resource connections in this team.');
+ }
+
/**
* Determine whether the user can update the connection within the current team.
*/
diff --git a/other/nightly/install.sh b/other/nightly/install.sh
index f4458d440..33437aad5 100755
--- a/other/nightly/install.sh
+++ b/other/nightly/install.sh
@@ -228,11 +228,12 @@ if [ "$WARNING_SPACE" = true ]; then
sleep 5
fi
-mkdir -p /data/coolify/{source,ssh,applications,databases,backups,services,proxy,sentinel}
+mkdir -p /data/coolify/{source,ssh,applications,databases,backups,services,proxy,sentinel,flux}
mkdir -p /data/coolify/ssh/{keys,mux}
mkdir -p /data/coolify/proxy/dynamic
chown -R 9999:root /data/coolify
+chown -R 9999:root /data/coolify/flux
chmod -R 700 /data/coolify
INSTALLATION_LOG_WITH_DATE="/data/coolify/source/installation-${DATE}.log"
diff --git a/other/nightly/upgrade.sh b/other/nightly/upgrade.sh
index 635ddd77c..a15ec39a8 100644
--- a/other/nightly/upgrade.sh
+++ b/other/nightly/upgrade.sh
@@ -171,6 +171,9 @@ else
log "Network 'coolify' already exists"
fi
+mkdir -p /data/coolify/flux
+chown -R 9999:root /data/coolify/flux
+
# Check if Docker config file exists
DOCKER_CONFIG_MOUNT=""
if [ -f /root/.docker/config.json ]; then
diff --git a/package-lock.json b/package-lock.json
index 6422dd492..9b050a8a7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -135,7 +135,6 @@
"integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/code-frame": "^7.29.7",
"@babel/generator": "^7.29.7",
@@ -740,7 +739,6 @@
}
],
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=20.19.0"
},
@@ -789,7 +787,6 @@
}
],
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=20.19.0"
}
@@ -997,12 +994,36 @@
"@noble/ciphers": "^1.0.0"
}
},
+ "node_modules/@emnapi/core": {
+ "version": "1.11.2",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.2.tgz",
+ "integrity": "sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==",
+ "license": "MIT",
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "@emnapi/wasi-threads": "1.2.2",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/runtime": {
+ "version": "1.11.2",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.2.tgz",
+ "integrity": "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==",
+ "license": "MIT",
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
"node_modules/@emnapi/wasi-threads": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz",
"integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==",
"license": "MIT",
"optional": true,
+ "peer": true,
"dependencies": {
"tslib": "^2.4.0"
}
@@ -1244,7 +1265,6 @@
"integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==",
"dev": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": "^14.21.3 || >=16"
},
@@ -2082,7 +2102,8 @@
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/@types/babel__core": {
"version": "7.20.5",
@@ -2167,7 +2188,6 @@
"integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==",
"devOptional": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"csstype": "^3.2.2"
}
@@ -2178,7 +2198,6 @@
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"peerDependencies": {
"@types/react": "^19.2.0"
}
@@ -2337,8 +2356,7 @@
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz",
"integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/accepts": {
"version": "2.0.0",
@@ -2425,6 +2443,7 @@
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=10"
},
@@ -2445,6 +2464,7 @@
"integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
"dev": true,
"license": "Apache-2.0",
+ "peer": true,
"dependencies": {
"dequal": "^2.0.3"
}
@@ -2600,7 +2620,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.10.12",
"caniuse-lite": "^1.0.30001782",
@@ -3172,6 +3191,7 @@
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=6"
}
@@ -3200,7 +3220,8 @@
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/dot-prop": {
"version": "6.0.1",
@@ -3523,7 +3544,6 @@
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"accepts": "^2.0.0",
"body-parser": "^2.2.1",
@@ -3973,7 +3993,6 @@
"integrity": "sha512-2NFaIyNVgJmBs/ecmtGzlmluTFs5cHEWGTdu0t1HBwYzoGXOL5nUQBRMXsXWla5i4KkG//QMzVP88m1+I3fdAQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=16.9.0"
}
@@ -4871,6 +4890,7 @@
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"bin": {
"lz-string": "bin/bin.js"
}
@@ -5622,6 +5642,7 @@
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"ansi-regex": "^5.0.1",
"ansi-styles": "^5.0.0",
@@ -5777,7 +5798,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz",
"integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -5787,7 +5807,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz",
"integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
@@ -5800,7 +5819,8 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/react-refresh": {
"version": "0.18.0",
@@ -6501,8 +6521,7 @@
"version": "4.1.18",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
"integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/tapable": {
"version": "2.3.0",
@@ -6718,7 +6737,6 @@
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
"dev": true,
"license": "Apache-2.0",
- "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -6841,7 +6859,6 @@
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz",
"integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"lightningcss": "^1.32.0",
"picomatch": "^4.0.4",
@@ -7465,7 +7482,6 @@
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
diff --git a/resources/js/v5/Pages/Dashboard.tsx b/resources/js/v5/Pages/Dashboard.tsx
index 3ee7522ed..c1f7db12c 100644
--- a/resources/js/v5/Pages/Dashboard.tsx
+++ b/resources/js/v5/Pages/Dashboard.tsx
@@ -90,6 +90,7 @@ export default function Dashboard({
projects = [],
selectedProjectUuid = null,
selectedEnvironmentUuid = null,
+ selectedApplicationUuid = null,
}: V5DashboardProps) {
const [applications, setApplications] = useState