From 0cddee6ead1f471cc8e58f4c00c28337213657d7 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Wed, 8 Oct 2025 09:31:19 +0200 Subject: [PATCH 01/10] fix(dashboard): add cursor pointer to modal input buttons for better UX --- resources/views/livewire/dashboard.blade.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/views/livewire/dashboard.blade.php b/resources/views/livewire/dashboard.blade.php index 2c7ed4076..02a8d8367 100644 --- a/resources/views/livewire/dashboard.blade.php +++ b/resources/views/livewire/dashboard.blade.php @@ -21,7 +21,7 @@ + +
+ + / or ⌘K to focus + + +
@@ -154,22 +197,326 @@ class="min-h-[200px] items-center justify-center p-8">
- @if ($isCreateMode && count($creatableItems) > 0 && !$autoOpenResource) + @if ($isSelectingResource) + +
+ + @if ($selectedServerId === null) +
+
+ +
+

+ Select Server +

+ @if ($this->selectedResourceName) +
+ for {{ $this->selectedResourceName }} +
+ @endif +
+
+ @if ($loadingServers) +
+ + + + + + Loading + servers... +
+ @elseif (count($availableServers) > 0) + @foreach ($availableServers as $index => $server) + + @endforeach + @else +
+

No servers + available

+
+ @endif +
+ @endif + + + @if ($selectedServerId !== null && $selectedDestinationUuid === null) +
+
+ +
+

+ Select Destination +

+ @if ($this->selectedResourceName) +
+ for {{ $this->selectedResourceName }} +
+ @endif +
+
+ @if ($loadingDestinations) +
+ + + + + + Loading + destinations... +
+ @elseif (count($availableDestinations) > 0) + @foreach ($availableDestinations as $index => $destination) + + @endforeach + @else +
+

No destinations + available

+
+ @endif +
+ @endif + + + @if ($selectedDestinationUuid !== null && $selectedProjectUuid === null) +
+
+ +
+

+ Select Project +

+ @if ($this->selectedResourceName) +
+ for {{ $this->selectedResourceName }} +
+ @endif +
+
+ @if ($loadingProjects) +
+ + + + + + Loading + projects... +
+ @elseif (count($availableProjects) > 0) + @foreach ($availableProjects as $index => $project) + + @endforeach + @else +
+

No projects + available

+
+ @endif +
+ @endif + + + @if ($selectedProjectUuid !== null && $selectedEnvironmentUuid === null) +
+
+ +
+

+ Select Environment +

+ @if ($this->selectedResourceName) +
+ for {{ $this->selectedResourceName }} +
+ @endif +
+
+ @if ($loadingEnvironments) +
+ + + + + + Loading + environments... +
+ @elseif (count($availableEnvironments) > 0) + @foreach ($availableEnvironments as $index => $environment) + + @endforeach + @else +
+

No environments + available

+
+ @endif +
+ @endif +
+ @elseif ($isCreateMode && count($this->filteredCreatableItems) > 0 && !$autoOpenResource) -
-
+ {{--

Create New Resources @@ -177,96 +524,166 @@ class="px-4 py-2 bg-yellow-50 dark:bg-yellow-900/20 border-b border-yellow-100 d

Click on any item below to create a new resource

-

- @foreach ($creatableItems as $item) - + @endforeach + @endforeach +
+ @elseif (strlen($searchQuery) >= 1 && count($searchResults) > 0) + - - @endforeach -
- @elseif (strlen($searchQuery) >= 2 && count($searchResults) > 0) - @elseif (strlen($searchQuery) >= 2 && count($searchResults) === 0 && !$autoOpenResource) @@ -280,14 +697,6 @@ class="shrink-0 h-5 w-5 text-neutral-300 dark:text-neutral-600 self-center"

- @elseif (strlen($searchQuery) > 0 && strlen($searchQuery) < 2) -
-
-

- Type at least 2 characters to search -

-
-
@endif From e8b2ef0e0cfca06384c5c86af7e8c632175d031a Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Wed, 8 Oct 2025 13:29:58 +0200 Subject: [PATCH 03/10] fix(modal-confirmation): refine escape key handling to ensure modal closes only when open - Updated the keydown event handler to check if the modal is open before executing the close and reset functions, improving the modal's behavior and user experience. --- resources/views/components/modal-confirmation.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/components/modal-confirmation.blade.php b/resources/views/components/modal-confirmation.blade.php index 1a3c88f80..103f18316 100644 --- a/resources/views/components/modal-confirmation.blade.php +++ b/resources/views/components/modal-confirmation.blade.php @@ -114,7 +114,7 @@ } } }" - @keydown.escape.window="modalOpen = false; resetModal()" :class="{ 'z-40': modalOpen }" + @keydown.escape.window="if (modalOpen) { modalOpen = false; resetModal(); }" :class="{ 'z-40': modalOpen }" class="relative w-auto h-auto"> @if ($customButton) @if ($buttonFullWidth) From afd10048bda450b6bb6bd56a18fea84c6f25fcf9 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Wed, 8 Oct 2025 13:38:38 +0200 Subject: [PATCH 04/10] update globalsearch --- app/Livewire/GlobalSearch.php | 134 ++++++++++-- .../views/livewire/global-search.blade.php | 206 ++++++++++++++---- 2 files changed, 274 insertions(+), 66 deletions(-) diff --git a/app/Livewire/GlobalSearch.php b/app/Livewire/GlobalSearch.php index 33366f418..98571b87f 100644 --- a/app/Livewire/GlobalSearch.php +++ b/app/Livewire/GlobalSearch.php @@ -22,6 +22,8 @@ class GlobalSearch extends Component { public $searchQuery = ''; + private $previousTrimmedQuery = ''; + public $isModalOpen = false; public $searchResults = []; @@ -86,6 +88,7 @@ public function closeSearchModal() { $this->isModalOpen = false; $this->searchQuery = ''; + $this->previousTrimmedQuery = ''; $this->searchResults = []; } @@ -101,25 +104,49 @@ public static function clearTeamCache($teamId) public function updatedSearchQuery() { - $query = strtolower(trim($this->searchQuery)); + $trimmedQuery = trim($this->searchQuery); + + // If only spaces were added/removed, don't trigger a search + if ($trimmedQuery === $this->previousTrimmedQuery) { + return; + } + + $this->previousTrimmedQuery = $trimmedQuery; + + // If search query is empty, just clear results without processing + if (empty($trimmedQuery)) { + $this->searchResults = []; + $this->isCreateMode = false; + $this->creatableItems = []; + $this->autoOpenResource = null; + $this->isSelectingResource = false; + $this->cancelResourceSelection(); + + return; + } + + $query = strtolower($trimmedQuery); // Reset keyboard navigation index $this->dispatch('reset-selected-index'); - if (str_starts_with($query, 'new')) { + // Only enter create mode if query is exactly "new" or starts with "new " (space after) + if ($query === 'new' || str_starts_with($query, 'new ')) { $this->isCreateMode = true; $this->loadCreatableItems(); - $this->searchResults = []; // Check for sub-commands like "new project", "new server", etc. - // Use original query (not trimmed) to ensure exact match without trailing spaces - $detectedType = $this->detectSpecificResource(strtolower($this->searchQuery)); + $detectedType = $this->detectSpecificResource($query); if ($detectedType) { $this->navigateToResource($detectedType); } else { // If no specific resource detected, reset selection state $this->cancelResourceSelection(); } + + // Also search for existing resources that match the query + // This allows users to find resources with "new" in their name + $this->search(); } else { $this->isCreateMode = false; $this->creatableItems = []; @@ -624,6 +651,8 @@ private function search() // Search for matching creatable resources to show as suggestions (if no priority item) if (! $priorityCreatableItem) { $this->loadCreatableItems(); + + // Search in regular creatable items (apps, databases, quick actions) $creatableSuggestions = collect($this->creatableItems) ->filter(function ($item) use ($query) { $searchText = strtolower($item['name'].' '.$item['description'].' '.($item['type'] ?? '')); @@ -648,7 +677,37 @@ private function search() $item['is_creatable_suggestion'] = true; return $item; + }); + + // Also search in services (loaded on-demand) + $serviceSuggestions = collect($this->services) + ->filter(function ($item) use ($query) { + $searchText = strtolower($item['name'].' '.$item['description'].' '.($item['type'] ?? '')); + + return preg_match('/\b'.preg_quote($query, '/').'/i', $searchText); }) + ->map(function ($item) use ($query) { + // Calculate match priority: name > type > description + $name = strtolower($item['name']); + $type = strtolower($item['type'] ?? ''); + $description = strtolower($item['description']); + + if (preg_match('/\b'.preg_quote($query, '/').'/i', $name)) { + $item['match_priority'] = 1; + } elseif (preg_match('/\b'.preg_quote($query, '/').'/i', $type)) { + $item['match_priority'] = 2; + } else { + $item['match_priority'] = 3; + } + + $item['is_creatable_suggestion'] = true; + + return $item; + }); + + // Merge and sort all suggestions + $creatableSuggestions = $creatableSuggestions + ->merge($serviceSuggestions) ->sortBy('match_priority') ->take(10) ->values() @@ -914,31 +973,18 @@ private function loadCreatableItems() ]); } - // === Services Category === - - if ($user->can('createAnyResource')) { - // Load all services - $allServices = get_service_templates(); - - foreach ($allServices as $serviceKey => $service) { - $items->push([ - 'name' => str($serviceKey)->headline()->toString(), - 'description' => data_get($service, 'slogan', 'Deploy '.str($serviceKey)->headline()), - 'type' => 'one-click-service-'.$serviceKey, - 'category' => 'Services', - 'resourceType' => 'service', - ]); - } - } - $this->creatableItems = $items->toArray(); } public function navigateToResource($type) { - // Find the item by type + // Find the item by type - check regular items first, then services $item = collect($this->creatableItems)->firstWhere('type', $type); + if (! $item) { + $item = collect($this->services)->firstWhere('type', $type); + } + if (! $item) { return; } @@ -1227,12 +1273,52 @@ public function getSelectedResourceNameProperty() $this->loadCreatableItems(); } - // Find the item by type + // Find the item by type - check regular items first, then services $item = collect($this->creatableItems)->firstWhere('type', $this->selectedResourceType); + if (! $item) { + $item = collect($this->services)->firstWhere('type', $this->selectedResourceType); + } + return $item ? $item['name'] : null; } + public function getServicesProperty() + { + // Cache services in a static property to avoid reloading on every access + static $cachedServices = null; + + if ($cachedServices !== null) { + return $cachedServices; + } + + $user = auth()->user(); + + if (! $user->can('createAnyResource')) { + $cachedServices = []; + + return $cachedServices; + } + + // Load all services + $allServices = get_service_templates(); + $items = collect(); + + foreach ($allServices as $serviceKey => $service) { + $items->push([ + 'name' => str($serviceKey)->headline()->toString(), + 'description' => data_get($service, 'slogan', 'Deploy '.str($serviceKey)->headline()), + 'type' => 'one-click-service-'.$serviceKey, + 'category' => 'Services', + 'resourceType' => 'service', + ]); + } + + $cachedServices = $items->toArray(); + + return $cachedServices; + } + public function render() { return view('livewire.global-search'); diff --git a/resources/views/livewire/global-search.blade.php b/resources/views/livewire/global-search.blade.php index 0bd833d8e..ad9a9f6ad 100644 --- a/resources/views/livewire/global-search.blade.php +++ b/resources/views/livewire/global-search.blade.php @@ -1,14 +1,20 @@
+ x-init="$watch('modalOpen', value => { if (value) setTimeout(() => $refs.searchInput.focus(), 100) })" :disabled="isLoadingInitialData" + class="w-full pl-12 pr-32 py-4 text-base bg-white dark:bg-coolgray-100 border-none rounded-lg shadow-xl ring-1 ring-neutral-200 dark:ring-coolgray-300 focus:ring-2 focus:ring-neutral-400 dark:focus:ring-coolgray-300 dark:text-white placeholder-neutral-400 dark:placeholder-neutral-500 disabled:opacity-50 disabled:cursor-not-allowed" />
/ or ⌘K to focus @@ -172,31 +212,53 @@ class="pointer-events-auto px-2 py-1 text-xs font-medium text-neutral-500 dark:t
+ +
+
+
+ + + + + +

+ Please wait while we fetch your data +

+
+
+
+ @if (strlen($searchQuery) >= 1)
- -
-
- - - - - -

- Searching... -

+ +
+ +
+ @for ($i = 0; $i < 3; $i++) +
+
+
+
+
+
+
+
+
+
+
+ @endfor
- -
+ +
@if ($isSelectingResource)
@@ -516,15 +578,71 @@ class="p-3 bg-red-50 dark:bg-red-900/20 rounded-lg border border-red-200 dark:bo @elseif ($isCreateMode && count($this->filteredCreatableItems) > 0 && !$autoOpenResource)
- {{--
-

- Create New Resources -

-

- Click on any item below to create a new resource -

-
--}} + {{-- Show existing resources first if any match --}} + @php + $existingResources = collect($searchResults)->filter(fn($r) => !isset($r['is_creatable_suggestion']))->count(); + @endphp + @if ($existingResources > 0) + +
+

+ Existing Resources +

+
+ @foreach ($searchResults as $result) + @if (!isset($result['is_creatable_suggestion'])) + +
+
+
+ + {{ $result['name'] }} + + + @if ($result['type'] === 'application') + Application + @elseif ($result['type'] === 'service') + Service + @elseif ($result['type'] === 'database') + {{ ucfirst($result['subtype'] ?? 'Database') }} + @elseif ($result['type'] === 'server') + Server + @elseif ($result['type'] === 'project') + Project + @elseif ($result['type'] === 'environment') + Environment + @endif + +
+ @if (!empty($result['project']) && !empty($result['environment'])) +
+ {{ $result['project'] }} / + {{ $result['environment'] }} +
+ @endif + @if (!empty($result['description'])) +
+ {{ Str::limit($result['description'], 80) }} +
+ @endif +
+ + + +
+
+ @endif + @endforeach + @endif @php $grouped = collect($this->filteredCreatableItems)->groupBy('category'); @@ -695,6 +813,9 @@ class="shrink-0 h-5 w-5 text-neutral-300 dark:text-neutral-600 self-center"

Try different keywords or check the spelling

+

+ 💡 Tip: Search for service names like "wordpress", "postgres", or "redis" +

@endif @@ -957,4 +1078,5 @@ class="absolute top-0 right-0 flex items-center justify-center w-8 h-8 mt-5 mr-5
+
From 2e6e07bcc310f84838103e14710e27a21e626a51 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Wed, 8 Oct 2025 16:53:02 +0200 Subject: [PATCH 05/10] make global search frontend heavy --- app/Livewire/GlobalSearch.php | 4 + .../views/livewire/global-search.blade.php | 1180 +++++++++-------- 2 files changed, 609 insertions(+), 575 deletions(-) diff --git a/app/Livewire/GlobalSearch.php b/app/Livewire/GlobalSearch.php index 98571b87f..e378bd289 100644 --- a/app/Livewire/GlobalSearch.php +++ b/app/Livewire/GlobalSearch.php @@ -81,6 +81,7 @@ public function openSearchModal() { $this->isModalOpen = true; $this->loadSearchableItems(); + $this->loadCreatableItems(); $this->dispatch('search-modal-opened'); } @@ -973,6 +974,9 @@ private function loadCreatableItems() ]); } + // Merge with services + $items = $items->merge(collect($this->services)); + $this->creatableItems = $items->toArray(); } diff --git a/resources/views/livewire/global-search.blade.php b/resources/views/livewire/global-search.blade.php index ad9a9f6ad..95d2640a6 100644 --- a/resources/views/livewire/global-search.blade.php +++ b/resources/views/livewire/global-search.blade.php @@ -3,18 +3,74 @@ selectedIndex: -1, isSearching: false, isLoadingInitialData: false, + allSearchableItems: [], + searchQuery: '', + creatableItems: [], + isCreateMode: false, + + // Client-side search function + get searchResults() { + if (!this.searchQuery || this.searchQuery.length < 1) { + return []; + } + + const query = this.searchQuery.toLowerCase().trim(); + + const results = this.allSearchableItems.filter(item => { + if (!item.search_text) return false; + return item.search_text.toLowerCase().includes(query); + }).slice(0, 20); + + return results; + }, + + get filteredCreatableItems() { + if (!this.searchQuery || this.searchQuery.length < 1) { + return []; + } + const query = this.searchQuery.toLowerCase().trim(); + + if (query === 'new') { + return this.creatableItems; + } + + return this.creatableItems.filter(item => { + const searchText = `${item.name} ${item.description} ${item.type} ${item.category}`.toLowerCase(); + return searchText.includes(query); + }); + }, + + get groupedCreatableItems() { + const grouped = {}; + this.filteredCreatableItems.forEach(item => { + const category = item.category || 'Other'; + if (!grouped[category]) { + grouped[category] = []; + } + grouped[category].push(item); + }); + return grouped; + }, + openModal() { this.modalOpen = true; this.selectedIndex = -1; this.isLoadingInitialData = true; - // Dispatch event to load initial data - $wire.dispatch('loadInitialData'); + this.searchQuery = ''; + $wire.openSearchModal().then(() => { + this.allSearchableItems = $wire.allSearchableItems || []; + this.creatableItems = $wire.creatableItems || []; + this.isLoadingInitialData = false; + setTimeout(() => this.$refs.searchInput?.focus(), 50); + }); }, closeModal() { this.modalOpen = false; this.selectedIndex = -1; this.isSearching = false; this.isLoadingInitialData = false; + this.searchQuery = ''; + this.allSearchableItems = []; // Ensure scroll is restored document.body.style.overflow = ''; @this.closeSearchModal(); @@ -42,43 +98,39 @@ this.selectedIndex = -1; }); - // Listen for loading state changes - $wire.on('loadInitialData', () => { - this.isLoadingInitialData = true; - $wire.openSearchModal().finally(() => { - this.isLoadingInitialData = false; - // Focus input after data is loaded - setTimeout(() => this.$refs.searchInput?.focus(), 50); - }); - }); + this.$watch('searchQuery', (value) => { + this.selectedIndex = -1; + const trimmed = value.trim().toLowerCase(); - // Use Livewire lifecycle hooks for accurate loading state - const componentId = $wire.__instance.id; - - Livewire.hook('message.sent', (message, component) => { - // Only handle messages for this component instance - if (component.id !== componentId) return; - - // Check if this is a searchQuery update - if (message.updateQueue && message.updateQueue.some(update => update.payload.name === 'searchQuery')) { - this.isSearching = true; + if (trimmed === '') { + if ($wire.isSelectingResource) { + $wire.cancelResourceSelection(); + } + return; } - }); - Livewire.hook('message.processed', (message, component) => { - // Only handle messages for this component instance - if (component.id !== componentId) return; + const exactMatchCommands = [ + 'new project', 'new server', 'new team', 'new storage', 'new s3', + 'new private key', 'new privatekey', 'new key', + 'new github app', 'new github', 'new source', + 'new public', 'new public git', 'new public repo', 'new public repository', + 'new private github', 'new private gh', 'new private deploy', 'new deploy key', + 'new dockerfile', 'new docker compose', 'new compose', 'new docker image', 'new image', + 'new postgresql', 'new postgres', 'new mysql', 'new mariadb', + 'new redis', 'new keydb', 'new dragonfly', 'new mongodb', 'new mongo', 'new clickhouse' + ]; - // Check if this was a searchQuery update - if (message.updateQueue && message.updateQueue.some(update => update.payload.name === 'searchQuery')) { - this.isSearching = false; - } - }); + if (exactMatchCommands.includes(trimmed)) { + const matchingItem = this.creatableItems.find(item => { + const itemSearchText = `new ${item.name}`.toLowerCase(); + const itemType = `new ${item.type}`.toLowerCase(); + return itemSearchText === trimmed || itemType === trimmed || + (item.type && trimmed.includes(item.type.replace(/-/g, ' '))); + }); - // Also clear loading state when search is emptied - this.$watch('$wire.searchQuery', (value) => { - if (!value || value.length === 0) { - this.isSearching = false; + if (matchingItem) { + $wire.navigateToResource(matchingItem.type); + } } }); @@ -111,7 +163,7 @@ const escapeKeyHandler = (e) => { if (e.key === 'Escape' && this.modalOpen) { // If search query is empty, close the modal - if (!$wire.searchQuery || $wire.searchQuery === '') { + if (!this.searchQuery || this.searchQuery === '') { // Check if we're in a selection state using Alpine-accessible Livewire state if ($wire.isSelectingResource) { $wire.cancelResourceSelection(); @@ -122,7 +174,7 @@ } } else { // If search query has text, just clear it - $wire.searchQuery = ''; + this.searchQuery = ''; setTimeout(() => this.$refs.searchInput?.focus(), 100); } } @@ -191,13 +243,21 @@ class="fixed top-0 left-0 z-99 flex items-start justify-center w-screen h-screen
- + + + + +
- @@ -212,547 +272,347 @@ class="pointer-events-auto px-2 py-1 text-xs font-medium text-neutral-500 dark:t
- -
-
-
- - - - - -

- Please wait while we fetch your data -

-
+ + {{--
+
+

+ ✓ Data loaded successfully! +

+

+ searchable items available +

+

+ Start typing to search... +

-
+
--}} - @if (strlen($searchQuery) >= 1) -
- -
- -
- @for ($i = 0; $i < 3; $i++) -
-
-
-
-
-
-
+
+ +
+ @if ($isSelectingResource) + +
+ + @if ($selectedServerId === null) +
+
+ +
+

+ Select Server +

+ @if ($this->selectedResourceName) +
+ for {{ $this->selectedResourceName }} +
+ @endif
-
-
- @endfor -
-
- - -
- @if ($isSelectingResource) - -
- - @if ($selectedServerId === null) -
-
+ @if ($loadingServers) +
+ + + + + + Loading + servers... +
+ @elseif (count($availableServers) > 0) + @foreach ($availableServers as $index => $server) -
-

- Select Server -

- @if ($this->selectedResourceName) -
- for {{ $this->selectedResourceName }} -
- @endif -
-
- @if ($loadingServers) -
- - - - - - Loading - servers... -
- @elseif (count($availableServers) > 0) - @foreach ($availableServers as $index => $server) - - @endforeach - @else -
-

No servers - available

-
- @endif -
- @endif - - - @if ($selectedServerId !== null && $selectedDestinationUuid === null) -
-
- -
-

- Select Destination -

- @if ($this->selectedResourceName) -
- for {{ $this->selectedResourceName }} -
- @endif -
-
- @if ($loadingDestinations) -
- - - - - - Loading - destinations... -
- @elseif (count($availableDestinations) > 0) - @foreach ($availableDestinations as $index => $destination) - - @endforeach - @else -
-

No destinations - available

-
- @endif -
- @endif - - - @if ($selectedDestinationUuid !== null && $selectedProjectUuid === null) -
-
- -
-

- Select Project -

- @if ($this->selectedResourceName) -
- for {{ $this->selectedResourceName }} -
- @endif -
-
- @if ($loadingProjects) -
- - - - - - Loading - projects... -
- @elseif (count($availableProjects) > 0) - @foreach ($availableProjects as $index => $project) - - @endforeach - @else -
-

No projects - available

-
- @endif -
- @endif - - - @if ($selectedProjectUuid !== null && $selectedEnvironmentUuid === null) -
-
- -
-

- Select Environment -

- @if ($this->selectedResourceName) -
- for {{ $this->selectedResourceName }} -
- @endif -
-
- @if ($loadingEnvironments) -
- - - - - - Loading - environments... -
- @elseif (count($availableEnvironments) > 0) - @foreach ($availableEnvironments as $index => $environment) - - @endforeach - @else -
-

No environments - available

-
- @endif -
- @endif -
- @elseif ($isCreateMode && count($this->filteredCreatableItems) > 0 && !$autoOpenResource) - -
- {{-- Show existing resources first if any match --}} - @php - $existingResources = collect($searchResults)->filter(fn($r) => !isset($r['is_creatable_suggestion']))->count(); - @endphp - @if ($existingResources > 0) - -
-

- Existing Resources -

-
- @foreach ($searchResults as $result) - @if (!isset($result['is_creatable_suggestion'])) - + wire:click="selectServer({{ $server['id'] }}, true)" + class="search-result-item w-full text-left block px-4 py-3 hover:bg-yellow-50 dark:hover:bg-yellow-900/20 transition-colors focus:outline-none focus:bg-yellow-100 dark:focus:bg-yellow-900/30"> - @elseif (strlen($searchQuery) >= 1 && count($searchResults) > 0) -
- @foreach ($searchResults as $index => $result) - @if (isset($result['is_creatable_suggestion']) && $result['is_creatable_suggestion']) - {{-- Creatable suggestion with yellow theme --}} - + + @endforeach @else - {{-- Regular search result --}} +
+

No servers + available

+
+ @endif +
+ @endif + + + @if ($selectedServerId !== null && $selectedDestinationUuid === null) +
+
+ +
+

+ Select Destination +

+ @if ($this->selectedResourceName) +
+ for {{ $this->selectedResourceName }} +
+ @endif +
+
+ @if ($loadingDestinations) +
+ + + + + + Loading + destinations... +
+ @elseif (count($availableDestinations) > 0) + @foreach ($availableDestinations as $index => $destination) + + @endforeach + @else +
+

No destinations + available

+
+ @endif +
+ @endif + + + @if ($selectedDestinationUuid !== null && $selectedProjectUuid === null) +
+
+ +
+

+ Select Project +

+ @if ($this->selectedResourceName) +
+ for {{ $this->selectedResourceName }} +
+ @endif +
+
+ @if ($loadingProjects) +
+ + + + + + Loading + projects... +
+ @elseif (count($availableProjects) > 0) + @foreach ($availableProjects as $index => $project) + + @endforeach + @else +
+

No projects + available

+
+ @endif +
+ @endif + + + @if ($selectedProjectUuid !== null && $selectedEnvironmentUuid === null) +
+
+ +
+

+ Select Environment +

+ @if ($this->selectedResourceName) +
+ for {{ $this->selectedResourceName }} +
+ @endif +
+
+ @if ($loadingEnvironments) +
+ + + + + + Loading + environments... +
+ @elseif (count($availableEnvironments) > 0) + @foreach ($availableEnvironments as $index => $environment) + + @endforeach + @else +
+

No environments + available

+
+ @endif +
+ @endif +
+ @elseif ($isCreateMode && count($this->filteredCreatableItems) > 0 && !$autoOpenResource) + +
+ {{-- Show existing resources first if any match --}} + @php + $existingResources = collect($searchResults) + ->filter(fn($r) => !isset($r['is_creatable_suggestion'])) + ->count(); + @endphp + @if ($existingResources > 0) + +
+

+ Existing Resources +

+
+ @foreach ($searchResults as $result) + @if (!isset($result['is_creatable_suggestion'])) - @elseif (strlen($searchQuery) >= 2 && count($searchResults) === 0 && !$autoOpenResource) -
-
-

- No results found -

-

- Try different keywords or check the spelling -

-

- 💡 Tip: Search for service names like "wordpress", "postgres", or "redis" -

+ @endif + + @php + $grouped = collect($this->filteredCreatableItems)->groupBy('category'); + @endphp + + @foreach ($grouped as $category => $items) + +
+

+ {{ $category }} +

+ + + @foreach ($items as $item) + + @endforeach + @endforeach +
+ @endif + + + + + +
- @endif +
From 6f086d26dbe053afb3d4700e32a9b6b29faa1572 Mon Sep 17 00:00:00 2001 From: ShadowArcanist <162910371+ShadowArcanist@users.noreply.github.com> Date: Wed, 8 Oct 2025 22:06:07 +0530 Subject: [PATCH 06/10] fix service ente-photos-with-s3 --- templates/compose/ente-photos-with-s3.yaml | 156 ++++++++++++--------- 1 file changed, 86 insertions(+), 70 deletions(-) diff --git a/templates/compose/ente-photos-with-s3.yaml b/templates/compose/ente-photos-with-s3.yaml index 96d74b1a8..844bc673b 100644 --- a/templates/compose/ente-photos-with-s3.yaml +++ b/templates/compose/ente-photos-with-s3.yaml @@ -7,107 +7,123 @@ services: museum: - image: ghcr.io/ente-io/server:latest + image: 'ghcr.io/ente-io/server:613c6a96390d7a624cf30b946955705d632423cc' # Released at 2025-09-14T22:16:37-07:00 environment: - SERVICE_URL_MUSEUM_8080 - - ENTE_HTTP_USE_TLS=${ENTE_HTTP_USE_TLS:-false} - - - ENTE_APPS_PUBLIC_ALBUMS=${SERVICE_URL_WEB_3002} - - ENTE_APPS_CAST=${SERVICE_URL_WEB_3004} - - ENTE_APPS_ACCOUNTS=${SERVICE_URL_WEB_3001} - - - ENTE_DB_HOST=${ENTE_DB_HOST:-postgres} - - ENTE_DB_PORT=${ENTE_DB_PORT:-5432} - - ENTE_DB_NAME=${ENTE_DB_NAME:-ente_db} - - ENTE_DB_USER=${SERVICE_USER_POSTGRES:-pguser} - - ENTE_DB_PASSWORD=${SERVICE_PASSWORD_POSTGRES} - - - ENTE_KEY_ENCRYPTION=${SERVICE_REALBASE64_ENCRYPTION} - - ENTE_KEY_HASH=${SERVICE_REALBASE64_64_HASH} - - - ENTE_JWT_SECRET=${SERVICE_REALBASE64_JWT} - - - ENTE_INTERNAL_ADMIN=${ENTE_INTERNAL_ADMIN:-1580559962386438} - - ENTE_INTERNAL_DISABLE_REGISTRATION=${ENTE_INTERNAL_DISABLE_REGISTRATION:-false} - - # S3/MinIO configuration - - S3_ARE_LOCAL_BUCKETS=true - - S3_USE_PATH_STYLE_URLS=true - - S3_B2_EU_CEN_KEY=${SERVICE_USER_MINIO} - - S3_B2_EU_CEN_SECRET=${SERVICE_PASSWORD_MINIO} - - S3_B2_EU_CEN_ENDPOINT=${SERVICE_URL_MINIO_3200} - - S3_B2_EU_CEN_REGION=eu-central-2 - - S3_B2_EU_CEN_BUCKET=b2-eu-cen + - ENTE_DB_HOST=postgres + - ENTE_DB_PORT=5432 + - 'ENTE_DB_NAME=${POSTGRES_DB:-ente_db}' + - 'ENTE_DB_USER=${SERVICE_USER_POSTGRES}' + - 'ENTE_DB_PASSWORD=${SERVICE_PASSWORD_POSTGRES}' + - 'ENTE_HTTP_USE_TLS=${ENTE_HTTP_USE_TLS:-false}' + - ENTE_S3_ARE_LOCAL_BUCKETS=false + - ENTE_S3_USE_PATH_STYLE_URLS=true + - 'ENTE_S3_B2_EU_CEN_KEY=${SERVICE_USER_MINIO}' + - 'ENTE_S3_B2_EU_CEN_SECRET=${SERVICE_PASSWORD_MINIO}' + - 'ENTE_S3_B2_EU_CEN_ENDPOINT=${SERVICE_FQDN_MINIO_9000}' + - ENTE_S3_B2_EU_CEN_REGION=eu-central-2 + - ENTE_S3_B2_EU_CEN_BUCKET=b2-eu-cen + - 'ENTE_KEY_ENCRYPTION=${SERVICE_REALBASE64_ENCRYPTION}' + - 'ENTE_KEY_HASH=${SERVICE_REALBASE64_64_HASH}' + - 'ENTE_JWT_SECRET=${SERVICE_REALBASE64_JWT}' + - 'ENTE_INTERNAL_ADMIN=${ENTE_INTERNAL_ADMIN:-1580559962386438}' + - 'ENTE_INTERNAL_DISABLE_REGISTRATION=${ENTE_INTERNAL_DISABLE_REGISTRATION:-false}' volumes: - - museum-data:/data - - museum-config:/config + - 'museum-data:/data' + - 'museum-config:/config' depends_on: postgres: condition: service_healthy minio: condition: service_started healthcheck: - test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8080/ping"] - interval: 5s - timeout: 5s - retries: 10 + test: + - CMD + - wget + - '--spider' + - 'http://127.0.0.1:8080/ping' + interval: 30s + timeout: 10s + retries: 3 + web: - image: ghcr.io/ente-io/web + image: 'ghcr.io/ente-io/web:ca03165f5e7f2a50105e6e40019c17ae6cdd934f' # Released at 2025-10-08T00:57:05-07:00 environment: - SERVICE_URL_WEB_3000 - - ENTE_API_ORIGIN=${SERVICE_URL_MUSEUM} - - ENTE_ALBUMS_ORIGIN=${SERVICE_URL_WEB_3002} - + - 'ENTE_API_ORIGIN=${SERVICE_URL_MUSEUM}' healthcheck: - test: ["CMD", "curl", "--fail", "http://127.0.0.1:3000"] - interval: 5s - timeout: 5s - retries: 10 + test: + - CMD + - curl + - '--fail' + - 'http://localhost:3000' + interval: 30s + timeout: 10s + retries: 3 + start_period: 10s + + postgres: - image: postgres:15-alpine + image: 'postgres:15-alpine' environment: - - POSTGRES_USER=${SERVICE_USER_POSTGRES} - - POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES} - - POSTGRES_DB=${POSTGRES_DB:-ente_db} + - 'POSTGRES_USER=${SERVICE_USER_POSTGRES}' + - 'POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES}' + - 'POSTGRES_DB=${POSTGRES_DB:-ente_db}' volumes: - - postgres-data:/var/lib/postgresql/data + - 'postgres-data:/var/lib/postgresql/data' healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] - interval: 5s + test: + - CMD-SHELL + - 'pg_isready -U ${SERVICE_USER_POSTGRES} -d ${POSTGRES_DB:-ente_db}' + interval: 10s timeout: 5s - retries: 10 + retries: 5 + minio: - image: quay.io/minio/minio:latest + image: 'quay.io/minio/minio:RELEASE.2025-09-07T16-13-09Z' # Released at 2025-09-07T16-13-09Z + command: 'server /data --console-address ":9001"' environment: - - SERVICE_URL_MINIO_9000 - - MINIO_ROOT_USER=${SERVICE_USER_MINIO} - - MINIO_ROOT_PASSWORD=${SERVICE_PASSWORD_MINIO} - command: server /data --address ":9000" --console-address ":9001" + - MINIO_SERVER_URL=$MINIO_SERVER_URL + - MINIO_BROWSER_REDIRECT_URL=$MINIO_BROWSER_REDIRECT_URL + - MINIO_ROOT_USER=$SERVICE_USER_MINIO + - MINIO_ROOT_PASSWORD=$SERVICE_PASSWORD_MINIO volumes: - - minio-data:/data + - 'minio-data:/data' healthcheck: - test: ["CMD", "mc", "ready", "local"] + test: + - CMD + - mc + - ready + - local interval: 5s timeout: 20s retries: 10 + minio-init: - image: minio/mc:latest - exclude_from_hc: true - restart: no + image: 'minio/mc:RELEASE.2025-08-13T08-35-41Z' # Released at 2025-08-13T08-35-41Z depends_on: minio: - condition: service_healthy + condition: service_started + restart: on-failure + exclude_from_hc: true environment: - - MINIO_ROOT_USER=${SERVICE_USER_MINIO} - - MINIO_ROOT_PASSWORD=${SERVICE_PASSWORD_MINIO} - entrypoint: > + - 'MINIO_ROOT_USER=${SERVICE_USER_MINIO}' + - 'MINIO_ROOT_PASSWORD=${SERVICE_PASSWORD_MINIO}' + - 'MINIO_CORS_URLS=$SERVICE_URL_MUSEUM,$SERVICE_URL_WEB' + entrypoint: |- /bin/sh -c " - mc alias set minio http://minio:9000 $${MINIO_ROOT_USER} $${MINIO_ROOT_PASSWORD}; - mc mb minio/b2-eu-cen --ignore-existing; - mc mb minio/wasabi-eu-central-2-v3 --ignore-existing; - mc mb minio/scw-eu-fr-v3 --ignore-existing; - echo 'MinIO buckets created successfully'; + echo \"MINIO_CORS_URLS: \$${MINIO_CORS_URLS}\"; + sleep 5; + until mc alias set minio http://minio:9000 \$${MINIO_ROOT_USER} \$${MINIO_ROOT_PASSWORD}; do + echo 'Waiting for MinIO...'; + sleep 2; + done; + mc admin config set minio api cors_allow_origin='$MINIO_CORS_URLS' || true; + mc mb minio/b2-eu-cen --ignore-existing; + mc mb minio/wasabi-eu-central-2-v3 --ignore-existing; + mc mb minio/scw-eu-fr-v3 --ignore-existing; + echo 'MinIO buckets and CORS configured'; " From 3323302021f6aa615d3629e8729cfd713268ec84 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Wed, 8 Oct 2025 19:50:06 +0200 Subject: [PATCH 07/10] refactor(navbar, app): improve layout and styling for better responsiveness - Adjusted CSS styles in the navbar for improved readability and responsiveness. - Updated the layout of the sidebar and navbar components to enhance user experience on different screen sizes. - Ensured consistent alignment and spacing for elements within the navbar and sidebar. --- resources/views/components/navbar.blade.php | 34 ++++++++++----------- resources/views/layouts/app.blade.php | 12 +++++--- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/resources/views/components/navbar.blade.php b/resources/views/components/navbar.blade.php index defa7bf6c..4b152dd11 100644 --- a/resources/views/components/navbar.blade.php +++ b/resources/views/components/navbar.blade.php @@ -59,25 +59,25 @@ if (this.zoom === '90') { const style = document.createElement('style'); style.textContent = ` - html { - font-size: 93.75%; - } - - :root { - --vh: 1vh; - } - - @media (min-width: 1024px) { - html { - font-size: 87.5%; - } - } - `; + html { + font-size: 93.75%; + } + + :root { + --vh: 1vh; + } + + @media (min-width: 1024px) { + html { + font-size: 87.5%; + } + } + `; document.head.appendChild(style); } } }"> -
+
Coolify
@@ -86,8 +86,8 @@
-
-