feat: harden auth flows and server mobile navigation

Add normalized email identity rate limiting for registration and forgot-password requests, and refresh Sentinel status from restart broadcasts.

Rework server sidebars and navbar for mobile menus and active status visibility.
This commit is contained in:
Andras Bacsai committed 2026-07-08 09:42:52 +02:00
1 parent 94079c90f2
commit b0f0f7d8d0
42 files changed
+1064 -128

No files matched your search

@@ -3,7 +3,7 @@
{{ data_get_str($server, 'name')->limit(10) }} > Advanced | Coolify
</x-slot>
<livewire:server.navbar :server="$server" />
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'general' }" class="flex flex-col h-full gap-8 sm:flex-row">
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'general' }" class="flex flex-col h-full gap-2 md:gap-8 md:flex-row">
<x-server.sidebar :server="$server" activeMenu="advanced" />
<form wire:submit='submit' class="w-full">
<div>
@@ -37,7 +37,7 @@
helper="You can specify the number of simultaneous build processes/deployments that should run concurrently." />
<x-forms.input canGate="update" :canResource="$server" id="dynamicTimeout"
type="number" min="1"
label="Deployment timeout (seconds)" required
label="Deployment timeout (sec)" required
helper="You can define the maximum duration for a deployment to run before timing it out." />
<x-forms.input canGate="update" :canResource="$server" id="deploymentQueueLimit"
type="number" min="1"
@@ -3,7 +3,7 @@
{{ data_get_str($server, 'name')->limit(10) }} > CA Certificate | Coolify
</x-slot>
<livewire:server.navbar :server="$server" />
<div class="flex flex-col h-full gap-8 sm:flex-row">
<div class="flex flex-col h-full gap-2 md:gap-8 md:flex-row">
<x-server.sidebar :server="$server" activeMenu="ca-certificate" />
<div class="flex flex-col gap-4">
<div class="flex items-center gap-2">
@@ -3,7 +3,7 @@
{{ data_get_str($server, 'name')->limit(10) }} > Metrics | Coolify
</x-slot>
<livewire:server.navbar :server="$server" />
<div class="flex flex-col h-full gap-8 sm:flex-row">
<div class="flex flex-col h-full gap-2 md:gap-8 md:flex-row">
<x-server.sidebar :server="$server" activeMenu="metrics" />
<div class="w-full">
<div class="flex items-center gap-2">
@@ -3,7 +3,7 @@
{{ data_get_str($server, 'name')->limit(10) }} > Hetzner Token | Coolify
</x-slot>
<livewire:server.navbar :server="$server" />
<div class="flex flex-col h-full gap-8 sm:flex-row">
<div class="flex flex-col h-full gap-2 md:gap-8 md:flex-row">
<x-server.sidebar :server="$server" activeMenu="cloud-provider-token" />
<div class="w-full">
@if ($server->hetzner_server_id)
@@ -3,7 +3,7 @@
{{ data_get_str($server, 'name')->limit(10) }} > Cloudflare Tunnel | Coolify
</x-slot>
<livewire:server.navbar :server="$server" />
<div class="flex flex-col h-full gap-8 sm:flex-row">
<div class="flex flex-col h-full gap-2 md:gap-8 md:flex-row">
<x-server.sidebar :server="$server" activeMenu="cloudflare-tunnel" />
<div class="w-full">
<div class="flex flex-col">
@@ -3,7 +3,7 @@
{{ data_get_str($server, 'name')->limit(10) }} > Delete Server | Coolify
</x-slot>
<livewire:server.navbar :server="$server" />
<div class="flex flex-col h-full gap-8 sm:flex-row">
<div class="flex flex-col h-full gap-2 md:gap-8 md:flex-row">
<x-server.sidebar :server="$server" activeMenu="danger" />
<div class="w-full">
@if ($server->id !== 0)
@@ -3,7 +3,7 @@
{{ data_get_str($server, 'name')->limit(10) }} > Destinations | Coolify
</x-slot>
<livewire:server.navbar :server="$server" />
<div class="flex flex-col h-full gap-8 sm:flex-row">
<div class="flex flex-col h-full gap-2 md:gap-8 md:flex-row">
<x-server.sidebar :server="$server" activeMenu="destinations" />
<div class="w-full">
@if ($server->isFunctional())
@@ -3,7 +3,7 @@
{{ data_get_str($server, 'name')->limit(10) }} > Docker Cleanup | Coolify
</x-slot>
<livewire:server.navbar :server="$server" />
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'general' }" class="flex flex-col h-full gap-8 sm:flex-row">
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'general' }" class="flex flex-col h-full gap-2 md:gap-8 md:flex-row">
<x-server.sidebar :server="$server" activeMenu="docker-cleanup" />
<div class="w-full">
<form wire:submit='submit'>
@@ -3,7 +3,7 @@
{{ data_get_str($server, 'name')->limit(10) }} > Log Drains | Coolify
</x-slot>
<livewire:server.navbar :server="$server" />
<div class="flex flex-col h-full gap-8 sm:flex-row">
<div class="flex flex-col h-full gap-2 md:gap-8 md:flex-row">
<x-server.sidebar :server="$server" activeMenu="log-drains" />
<div class="w-full">
@if ($server->isFunctional())
@@ -1,4 +1,4 @@
<div class="pb-6">
<div class="pb-0 md:pb-6">
<x-slide-over @startproxy.window="slideOverOpen = true" fullScreen closeWithX>
<x-slot:title>Proxy Startup Logs</x-slot:title>
<x-slot:content>
@@ -12,14 +12,17 @@
<livewire:activity-monitor header="Logs" fullHeight />
</x-slot:content>
</x-slide-over>
<div class="flex flex-col gap-1.5">
<div class="flex flex-wrap items-center gap-2">
<h1>Server</h1>
<h1>Server</h1>
<div class="pt-2 pb-4 md:pb-10">
<div class="flex-col md:flex-row flex gap-2">
<div data-testid="server-subtitle" class="text-xs lg:text-sm min-w-0 truncate">
{{ data_get($server, 'name') }}
</div>
@php
$showSentinelStatus = $server->isFunctional() && $server->isSentinelEnabled();
@endphp
@if ($server->proxySet() || $showSentinelStatus)
<div data-testid="server-status-summary" class="flex flex-wrap items-center gap-2">
<div data-testid="server-status-summary" class="flex flex-wrap items-center gap-1">
@if ($server->proxySet())
<div class="flex items-center gap-1">
@if ($proxyStatus === 'running')
@@ -57,11 +60,10 @@
</div>
@endif
</div>
<div class="subtitle">{{ data_get($server, 'name') }}</div>
</div>
<div class="navbar-main">
<nav
class="flex items-center gap-6 overflow-x-scroll sm:overflow-x-hidden scrollbar min-h-10 whitespace-nowrap pt-2">
class="scrollbar hidden min-h-10 w-full flex-nowrap items-center gap-6 overflow-x-scroll overflow-y-hidden pb-1 whitespace-nowrap md:flex md:w-auto md:overflow-visible">
<a class="{{ request()->routeIs('server.show') ? 'dark:text-white' : '' }}" href="{{ route('server.show', [
'server_uuid' => data_get($server, 'uuid'),
]) }}" {{ wireNavigate() }}>
@@ -114,12 +116,70 @@
</a>
@endcan
</nav>
<div class="order-first sm:order-last">
<div class="order-first w-full md:order-last md:w-auto">
<div>
@if ($server->proxySet())
@can('manageProxy', $server)
<div id="server-mobile-actions" class="mt-2 mb-3 md:hidden">
<div class="mb-1 text-xs font-semibold uppercase tracking-wide text-neutral-500 dark:text-neutral-400">Actions</div>
@if ($proxyStatus === 'running')
<div class="flex gap-2">
<div class="flex flex-nowrap gap-2 overflow-x-auto">
<button type="button" class="button shrink-0"
@click="document.getElementById('server-mobile-restart-proxy-trigger')?.click()">
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2">
<path d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
<path d="M20 4v5h-5" />
</g>
</svg>
Restart Proxy
</button>
<button type="button" class="button shrink-0 text-error"
@click="document.getElementById('server-mobile-stop-proxy-trigger')?.click()">
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
</path>
<path
d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
</path>
</svg>
Stop Proxy
</button>
@if ($traefikDashboardAvailable)
<a class="button shrink-0" target="_blank" href="http://{{ $serverIp }}:8080">
Traefik Dashboard
<x-external-link />
</a>
@endif
</div>
</div>
<x-modal-confirmation title="Confirm Proxy Restart?" buttonTitle="Restart Proxy"
submitAction="restart" :actions="[
'This proxy will be stopped and started again.',
'All resources hosted on coolify will be unavailable during the restart.',
]" :confirmWithText="false" :confirmWithPassword="false" step2ButtonText="Restart Proxy"
:dispatchEvent="true" dispatchEventType="restartEvent">
<x-slot:trigger>
<button id="server-mobile-restart-proxy-trigger" type="button" class="hidden">Restart Proxy</button>
</x-slot:trigger>
</x-modal-confirmation>
<x-modal-confirmation title="Confirm Proxy Stopping?" buttonTitle="Stop Proxy"
submitAction="stop(true)" :actions="[
'The coolify proxy will be stopped.',
'All resources hosted on coolify will be unavailable.',
]" :confirmWithText="false"
:confirmWithPassword="false" step2ButtonText="Stop Proxy" :dispatchEvent="true"
dispatchEventType="stopEvent">
<x-slot:trigger>
<button id="server-mobile-stop-proxy-trigger" type="button" class="hidden">Stop Proxy</button>
</x-slot:trigger>
</x-modal-confirmation>
<div class="hidden gap-2 md:flex">
<div class="mt-1" wire:loading wire:target="loadProxyConfiguration">
<x-loading text="Checking Traefik dashboard" />
</div>
@@ -181,6 +241,7 @@
</svg>
Start Proxy
</button>
</div>
@endif
@endcan
@endif
@@ -3,7 +3,7 @@
{{ data_get_str($server, 'name')->limit(10) }} > Private Key | Coolify
</x-slot>
<livewire:server.navbar :server="$server" />
<div class="flex flex-col h-full gap-8 sm:flex-row">
<div class="flex flex-col h-full gap-2 md:gap-8 md:flex-row">
<x-server.sidebar :server="$server" activeMenu="private-key" />
<div class="w-full">
<div class="flex items-end gap-2">
@@ -3,7 +3,7 @@
Proxy Dynamic Configuration | Coolify
</x-slot>
<livewire:server.navbar :server="$server" />
<div class="flex flex-col h-full gap-8 sm:flex-row">
<div class="flex flex-col h-full gap-2 md:gap-8 md:flex-row">
<x-server.sidebar-proxy :server="$server" :parameters="$parameters" />
@if ($server->isFunctional())
<div class="w-full">
@@ -3,7 +3,7 @@
Proxy Logs | Coolify
</x-slot>
<livewire:server.navbar :server="$server" />
<div class="flex flex-col h-full gap-8 sm:flex-row">
<div class="flex flex-col h-full gap-2 md:gap-8 md:flex-row">
<x-server.sidebar-proxy :server="$server" :parameters="$parameters" />
<div class="w-full">
<h2 class="pb-4">Logs</h2>
@@ -4,7 +4,7 @@
</x-slot>
<livewire:server.navbar :server="$server" />
@if ($server->isFunctional())
<div class="flex flex-col h-full gap-8 sm:flex-row">
<div class="flex flex-col h-full gap-2 md:gap-8 md:flex-row">
<x-server.sidebar-proxy :server="$server" :parameters="$parameters" />
<div class="w-full">
<livewire:server.proxy :server="$server" />
@@ -3,7 +3,7 @@
{{ data_get_str($server, 'name')->limit(10) }} > Server Resources | Coolify
</x-slot>
<livewire:server.navbar :server="$server" />
<div x-data="{ activeTab: 'managed' }" class="flex flex-col h-full gap-8 md:flex-row">
<div x-data="{ activeTab: 'managed' }" class="flex flex-col h-full gap-2 md:gap-8 md:flex-row">
<div class="w-full">
<div class="flex flex-col">
<div class="flex gap-2">
@@ -10,7 +10,7 @@
</x-slot:content>
</x-slide-over>
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'general' }" class="flex flex-col h-full gap-8 sm:flex-row">
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'general' }" class="flex flex-col h-full gap-2 md:gap-8 md:flex-row">
<x-server.sidebar-security :server="$server" :parameters="$parameters" />
<form wire:submit='submit' class="w-full">
<div>
@@ -3,7 +3,7 @@
{{ data_get_str($server, 'name')->limit(10) }} > Terminal Access | Coolify
</x-slot>
<livewire:server.navbar :server="$server" />
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'general' }" class="flex flex-col h-full gap-8 sm:flex-row">
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'general' }" class="flex flex-col h-full gap-2 md:gap-8 md:flex-row">
<x-server.sidebar-security :server="$server" :parameters="$parameters" />
<div class="w-full">
<div>
@@ -52,4 +52,4 @@
</div>
</div>
</div>
</div>
</div>
@@ -3,7 +3,7 @@
Sentinel Logs | Coolify
</x-slot>
<livewire:server.navbar :server="$server" />
<div class="flex flex-col h-full gap-8 sm:flex-row">
<div class="flex flex-col h-full gap-2 md:gap-8 md:flex-row">
<x-server.sidebar-sentinel :server="$server" :parameters="$parameters" />
<div class="w-full">
<h2 class="pb-4">Logs</h2>
@@ -4,7 +4,7 @@
</x-slot>
<livewire:server.navbar :server="$server" />
@if ($server->isFunctional())
<div class="flex flex-col h-full gap-8 sm:flex-row">
<div class="flex flex-col h-full gap-2 md:gap-8 md:flex-row">
<x-server.sidebar-sentinel :server="$server" :parameters="$parameters" />
<div class="w-full">
<livewire:server.sentinel :server="$server" />
@@ -3,7 +3,7 @@
{{ data_get_str($server, 'name')->limit(10) }} > General | Coolify
</x-slot>
<livewire:server.navbar :server="$server" />
<div class="flex flex-col h-full gap-8 sm:flex-row">
<div class="flex flex-col h-full gap-2 md:gap-8 md:flex-row">
<x-server.sidebar :server="$server" activeMenu="general" />
<div class="w-full">
<form wire:submit.prevent='submit' class="flex flex-col">
@@ -3,7 +3,7 @@
{{ data_get_str($server, 'name')->limit(10) }} > Swarm | Coolify
</x-slot>
<livewire:server.navbar :server="$server" />
<div class="flex flex-col h-full gap-8 sm:flex-row">
<div class="flex flex-col h-full gap-2 md:gap-8 md:flex-row">
<x-server.sidebar :server="$server" activeMenu="swarm" />
<div class="w-full">
<div>