From 01337c0cd6110e20d4a7683d651fb00c525b4369 Mon Sep 17 00:00:00 2001 From: Aditya Tripathi Date: Fri, 11 Sep 2026 06:33:39 +0000 Subject: [PATCH 01/16] feat(ui): dashboard refinements + wire:navigate setting fixes - Sidebar: keep nav icons at the same x-position when collapsed (muscle memory) - Database URLs: add copy button to internal/public URL fields - Project cards + list: compact icon stats for env/resource counts, add Created column - Server card graph: add CPU/Memory color legend to the tooltip - Server list: add IP address, resource count, and status dot columns - Respect is_wire_navigate_enabled on scheduled-backups links (use wireNavigate helper) --- app/Livewire/Project/Index.php | 1 + app/View/Components/Forms/Input.php | 9 +++- resources/css/app.css | 22 +++++++--- resources/css/utilities.css | 18 ++++---- .../components/database-status-info.blade.php | 4 +- .../views/components/forms/input.blade.php | 25 +++++++++++ resources/views/components/navbar.blade.php | 7 ++- resources/views/livewire/dashboard.blade.php | 20 ++++++--- .../dashboard/server-metrics-chart.blade.php | 6 ++- .../database/scheduled-backups.blade.php | 4 +- .../views/livewire/project/index.blade.php | 43 +++++++++++++------ .../views/livewire/server/index.blade.php | 27 ++++++++++-- 12 files changed, 135 insertions(+), 51 deletions(-) diff --git a/app/Livewire/Project/Index.php b/app/Livewire/Project/Index.php index f43b7542e6..b1f00b147d 100644 --- a/app/Livewire/Project/Index.php +++ b/app/Livewire/Project/Index.php @@ -57,6 +57,7 @@ class Index extends Component 'href' => $project->navigateTo(), 'environmentCount' => $project->environments->count(), 'resourceCount' => $resourceCount, + 'createdAt' => $project->created_at?->format('M j, Y') ?? '-', 'settingsHref' => auth()->user()->can('update', $project) ? route('project.edit', ['project_uuid' => $project->uuid]) : null, diff --git a/app/View/Components/Forms/Input.php b/app/View/Components/Forms/Input.php index a1b01e2808..e94b181375 100644 --- a/app/View/Components/Forms/Input.php +++ b/app/View/Components/Forms/Input.php @@ -25,6 +25,7 @@ class Input extends Component public bool $readonly = false, public ?string $helper = null, public bool $allowToPeak = true, + public bool $copyable = false, public bool $isMultiline = false, public string $defaultClass = 'input', public string $autocomplete = 'off', @@ -72,9 +73,15 @@ class Input extends Component } // Durable class (not type-attr based): Alpine may toggle type to "text" when revealing, // and settings-workspace CSS otherwise overrides utility padding-right. - if ($this->type === 'password' && $this->allowToPeak) { + $hasPeek = $this->type === 'password' && $this->allowToPeak; + if ($hasPeek) { $this->defaultClass = $this->defaultClass.' input-with-password-toggle'; } + if ($this->copyable) { + // Reserve clearance for a single copy button, or for both the peek eye + // and the copy button when the field is a maskable password. + $this->defaultClass = $this->defaultClass.($hasPeek ? ' input-with-copy-and-peek' : ' input-with-copy-button'); + } // $this->label = Str::title($this->label); return view('components.forms.input'); diff --git a/resources/css/app.css b/resources/css/app.css index 7b192d71a3..fbb3a37f44 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -522,6 +522,11 @@ tr td:first-child { padding-right: 2.5rem; } +/* Room for both the peek eye and the copy button on maskable read-only fields. */ +.input.input-with-copy-and-peek { + padding-right: 4.25rem; +} + .lds-heart { animation: lds-heart 1.2s infinite cubic-bezier(0.215, 0.61, 0.355, 1); } @@ -1958,6 +1963,11 @@ html[data-theme="custom"] textarea:disabled { padding-right: 2.5rem; } +.application-settings-workspace .input.input-with-copy-and-peek, +.application-settings-form .input.input-with-copy-and-peek { + padding-right: 4.25rem; +} + .application-settings-workspace .input:focus-visible, .application-settings-workspace .select:focus-visible, .application-settings-form .input:focus-visible, @@ -4372,11 +4382,11 @@ html[data-theme="custom"] .runtime-log-columns { .projects-table-grid { display: grid; grid-template-columns: - minmax(220px, 1.7fr) - minmax(100px, 0.65fr) - minmax(90px, 0.6fr) - minmax(220px, 1.5fr) - 6rem; + minmax(200px, 1.7fr) + 8rem + 7rem + minmax(200px, 1.6fr) + 5rem; column-gap: 1rem; } @@ -4423,7 +4433,7 @@ html[data-theme="custom"] .runtime-log-columns { @media (max-width: 1050px) { .projects-table-grid { - grid-template-columns: minmax(220px, 1fr) 7rem 6rem 6rem; + grid-template-columns: minmax(200px, 1fr) 8rem 7rem 5rem; } .projects-table-grid .project-description { diff --git a/resources/css/utilities.css b/resources/css/utilities.css index 297a5acc42..5a1efba5fb 100644 --- a/resources/css/utilities.css +++ b/resources/css/utilities.css @@ -415,15 +415,15 @@ } @media (min-width: 1024px) { - .sidebar-collapsed .menu-item { - justify-content: center; - width: var(--button-h, 2rem); - height: var(--button-h, 2rem); - min-height: var(--button-h, 2rem); - padding-left: 0; - padding-right: 0; - gap: 0; - margin-inline: auto; + /* Collapsed rail keeps every nav icon at its expanded x-position (left-aligned, + same 10px inset) so muscle memory holds when the sidebar is toggled; only the + label is hidden. Unlayered rule outranks the inline lg:justify-center/lg:px-0 + utilities on each row. The footer collapse toggle keeps its own centered + square via the .sidebar-toggle exclusion. */ + .sidebar-collapsed .menu-item:not(.sidebar-toggle) { + justify-content: flex-start; + padding-left: 0.625rem; + padding-right: 0.625rem; } .sidebar-collapsed .sidebar-collapsed-label { diff --git a/resources/views/components/database-status-info.blade.php b/resources/views/components/database-status-info.blade.php index b9298e2689..1ff2473f78 100644 --- a/resources/views/components/database-status-info.blade.php +++ b/resources/views/components/database-status-info.blade.php @@ -23,10 +23,10 @@ @else - @if ($dbUrlPublic) - @elseif ($showPublicUrlPlaceholder) get('wire:model')) + : null; +@endphp +
$isMultiline, 'w-full' => !$isMultiline, @@ -48,9 +57,18 @@ @endif + @if ($copyable) + + @endif
@else + @if ($copyable) +
+ @endif merge(['class' => $defaultClass]) }} @required($required) @readonly($readonly) @if ($modelBinding !== 'null') wire:model={{ $modelBinding }} wire:dirty.class="[box-shadow:inset_4px_0_0_#6b16ed,inset_0_0_0_2px_#e5e5e5] dark:[box-shadow:inset_4px_0_0_#fcd452,inset_0_0_0_2px_#242424]" @endif @@ -61,6 +79,13 @@ @if ($htmlId !== 'null') id={{ $htmlId }} @endif name="{{ $name }}" placeholder="{{ $attributes->get('placeholder') }}" @if ($autofocus) x-ref="autofocusInput" autofocus @endif> + @if ($copyable) + +
+ @endif @endif @if (!$label && $helper) diff --git a/resources/views/components/navbar.blade.php b/resources/views/components/navbar.blade.php index 251628c496..e3d123106a 100644 --- a/resources/views/components/navbar.blade.php +++ b/resources/views/components/navbar.blade.php @@ -52,11 +52,10 @@ }"> {{-- Search is only useful when workspace resources are available --}} @if (isSubscribed() || ! isCloud()) -
+
-
-

- {{ $project->environments->count() }} - {{ str('env')->plural($project->environments->count()) }} - · - {{ $resourceCount }} {{ str('resource')->plural($resourceCount) }} -

+
+
+ + + {{ $project->environments->count() }} + + + + {{ $resourceCount }} + +
@if ($firstEnvironment) diff --git a/resources/views/livewire/dashboard/server-metrics-chart.blade.php b/resources/views/livewire/dashboard/server-metrics-chart.blade.php index 7d6924d46c..afb1820e07 100644 --- a/resources/views/livewire/dashboard/server-metrics-chart.blade.php +++ b/resources/views/livewire/dashboard/server-metrics-chart.blade.php @@ -102,9 +102,11 @@ timeZoneName: 'short', }); + const dot = color => ``; + return `
-
CPU: ${formatPercent(cpu)}
-
Memory: ${formatPercent(memory)}
+
${dot('rgb(30, 144, 255)')}CPU: ${formatPercent(cpu)}
+
${dot('rgb(168, 85, 247)')}Memory: ${formatPercent(memory)}
Your time: ${formatLocalTimestamp(timestamp)}
UTC: ${formatUtcTimestamp(timestamp)}
`; diff --git a/resources/views/livewire/project/database/scheduled-backups.blade.php b/resources/views/livewire/project/database/scheduled-backups.blade.php index e52586eaaf..793cd2ff47 100644 --- a/resources/views/livewire/project/database/scheduled-backups.blade.php +++ b/resources/views/livewire/project/database/scheduled-backups.blade.php @@ -100,13 +100,13 @@ {{ $backup->save_s3 ? ($backup->s3?->name ?? 'Unavailable') : 'Local only' }}
@endforeach diff --git a/resources/views/livewire/project/index.blade.php b/resources/views/livewire/project/index.blade.php index d3554b8828..a60fd39b94 100644 --- a/resources/views/livewire/project/index.blade.php +++ b/resources/views/livewire/project/index.blade.php @@ -125,14 +125,19 @@
-
-

- - · - -

+ -
-
+
+ + + + + + + + +
+

diff --git a/resources/views/livewire/server/index.blade.php b/resources/views/livewire/server/index.blade.php index 14de8dffab..9e0481c0da 100644 --- a/resources/views/livewire/server/index.blade.php +++ b/resources/views/livewire/server/index.blade.php @@ -59,6 +59,8 @@ 'href' => route('server.show', ['server_uuid' => $server->uuid]), 'status' => $status, 'statusType' => $statusType, + 'ip' => $server->isLocalhost() ? 'localhost' : ($server->ip ?: '-'), + 'resourceCount' => $server->definedResources()->count(), ]; })->values(); @endphp @@ -172,13 +174,15 @@
+ class="grid min-w-[480px] grid-cols-[minmax(0,1fr)_9.5rem] border-b border-neutral-200 bg-neutral-50 px-4 py-2.5 text-[11px] font-medium text-neutral-500 md:min-w-[640px] md:grid-cols-[minmax(0,1fr)_11rem_6rem_9.5rem] dark:border-white/[0.08] dark:bg-white/[0.05] dark:text-fg-faint">
Server
+ +
Status