feat(ui): polish domains, storage, env vars and resource nav

Improve project resource UIs: sort domains by DNS failure, stop re-adding www pairs on refresh, lazy-load storage tabs with counts, tighten env-var tables, keep application tabs active across Livewire polls, unify database type labels, and update related CSS/JS and tests.
This commit is contained in:
Andras Bacsai committed 2026-08-05 13:50:11 +02:00
1 parent dbc6f5e08c
commit a7a06aa6c8
75 files changed
+3909 -1173

No files matched your search

+269 -44
View File
@@ -75,40 +75,13 @@
@apply min-h-10 rounded-md border border-white/10 bg-white/10 px-2 py-2 text-sm font-semibold text-white shadow-inner active:bg-white/25;
}
/* Accent rail on the active rounded pill (sits flush on the left edge) */
.menu-item-active::before {
content: "";
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 3px;
background: var(--color-accent);
border-radius: 0.375rem 0 0 0.375rem;
pointer-events: none;
}
.menu-subitem-active::before {
content: "";
position: absolute;
top: 0;
bottom: 0;
left: 0;
z-index: 1;
width: 3px;
border-radius: 0.375rem 0 0 0.375rem;
background: var(--color-accent);
pointer-events: none;
}
.sidebar-collapsed .menu-item-active::before {
display: none;
}
/* active icon picks up full-strength foreground */
/* Active state is a solid fill only (no accent rail / border). */
.menu-item-active .menu-item-icon,
.menu-subitem-active .menu-item-icon {
opacity: 1;
}
/* Kill any legacy accent wash; fill is solid via menu-item-active utility. */
/* Kill any legacy accent wash or rail; fill is solid via menu-item-active utility. */
.menu-item-active,
.menu-subitem-active,
.dark .menu-item-active,
@@ -116,6 +89,12 @@
background-image: none;
}
.menu-item-active::before,
.menu-subitem-active::before {
content: none;
display: none;
}
/* vertical connector line for a nested nav group */
.nav-children {
position: relative;
@@ -971,6 +950,40 @@ body.terminal-is-fullscreen .terminal-fullscreen-shell [data-terminal-mobile-too
scroll-margin-top: 7rem;
}
/* Brief accent ring when a settings nav sub-item scrolls a section into view.
Use a real border on ::after (not animated multi-layer box-shadow) so the
ring is the same weight on every side — box-shadow rings look thicker on
the header edge next to the elevated strip. */
@keyframes application-settings-section-highlight {
0% {
opacity: 0;
}
15%,
60% {
opacity: 1;
}
100% {
opacity: 0;
}
}
.application-settings-section.is-section-highlight {
position: relative;
}
.application-settings-section.is-section-highlight::after {
content: '';
position: absolute;
inset: 0;
z-index: 5;
border-radius: inherit;
border: 0.5px solid var(--color-accent);
pointer-events: none;
animation: application-settings-section-highlight 500ms ease-out forwards;
}
/* Modals reuse the layer-card shell but size to content on large screens */
@media (min-width: 1024px) {
.application-settings-section.application-settings-form {
@@ -1038,6 +1051,9 @@ body.terminal-is-fullscreen .terminal-fullscreen-shell [data-terminal-mobile-too
top: 6.5rem;
align-self: start;
max-height: calc(100dvh - 7.25rem);
/* Inset content so the default ring-2 + ring-offset-2 focus ring is not
clipped by overflow-x on the right edge of this narrow column. */
padding-right: 0.375rem;
overflow-x: hidden;
overflow-y: auto;
overscroll-behavior: contain;
@@ -1111,9 +1127,11 @@ body.terminal-is-fullscreen .terminal-fullscreen-shell [data-terminal-mobile-too
.application-settings-workspace .button,
.application-settings-form .button {
height: 2rem;
min-height: 2rem;
border-radius: 8px;
padding-left: 0.75rem;
padding-right: 0.75rem;
white-space: nowrap;
}
.application-settings-workspace .form-control,
@@ -1292,6 +1310,38 @@ body.terminal-is-fullscreen .terminal-fullscreen-shell [data-terminal-mobile-too
color: var(--color-fg);
}
/*
* Active primary tab styles.
* The base rules above set background/color/box-shadow with higher specificity
* than Tailwind utilities (bg-warning/15, text-warning, ring-*), so active
* tabs need an explicit override or they look identical to inactive ones.
*/
.application-heading-actions .app-tab[aria-current='page'],
.application-heading-actions .app-tab.app-tab-active {
background: color-mix(in srgb, var(--color-coollabs) 10%, transparent);
color: var(--color-coollabs);
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--color-coollabs) 25%, transparent);
}
.application-heading-actions .app-tab[aria-current='page']:hover,
.application-heading-actions .app-tab.app-tab-active:hover {
background: color-mix(in srgb, var(--color-coollabs) 15%, transparent);
color: var(--color-coollabs);
}
.dark .application-heading-actions .app-tab[aria-current='page'],
.dark .application-heading-actions .app-tab.app-tab-active {
background: color-mix(in srgb, var(--color-warning) 15%, transparent);
color: var(--color-warning);
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--color-warning) 25%, transparent);
}
.dark .application-heading-actions .app-tab[aria-current='page']:hover,
.dark .application-heading-actions .app-tab.app-tab-active:hover {
background: color-mix(in srgb, var(--color-warning) 20%, transparent);
color: var(--color-warning);
}
.application-heading-actions .relative > button[x-ref='trigger'] {
padding-right: 0.625rem;
}
@@ -1583,7 +1633,11 @@ input[type="search"]::-webkit-search-results-decoration {
}
.env-table-grid {
grid-template-columns: minmax(0, 1.6fr) 6rem minmax(0, 1fr) 4rem 4.5rem 4.8rem 4.2rem 3rem;
grid-template-columns: minmax(14rem, 2.5fr) 4.8rem 6rem 4rem 4.5rem 4.8rem 4.2rem 3rem;
}
.env-table-grid.env-table-grid-no-type {
grid-template-columns: minmax(14rem, 2.5fr) 4.8rem 4rem 4.5rem 4.8rem 4.2rem 3rem;
}
/* Shared variables only store value shape (multiline), not per-resource flags. */
@@ -1594,7 +1648,7 @@ input[type="search"]::-webkit-search-results-decoration {
/* Env vars: collapse flag columns on tablet, card layout on phone */
@media (max-width: 1100px) {
.env-table-grid {
grid-template-columns: minmax(0, 1.4fr) 6rem minmax(0, 1fr) 3rem;
grid-template-columns: minmax(0, 1.4fr) 4.8rem 6rem 3rem;
gap: 0.75rem;
}
@@ -1619,12 +1673,7 @@ input[type="search"]::-webkit-search-results-decoration {
@media (max-width: 900px) {
.env-table-grid {
grid-template-columns: minmax(0, 1fr) 6rem 3rem;
}
/* Also hide Comment (3) */
.env-table-grid > :nth-child(3) {
display: none;
grid-template-columns: minmax(0, 1fr) 4.8rem 6rem 3rem;
}
.env-table-grid-shared {
@@ -1670,14 +1719,14 @@ input[type="search"]::-webkit-search-results-decoration {
word-break: break-word;
}
/* Type desktop column → hide; type shows as mobile badge on name row */
/* Managed and Type desktop columns */
.data-table-row.env-table-grid > :nth-child(2),
.data-table-row.env-table-grid > :nth-child(3),
.data-table-row.env-table-grid-shared > :nth-child(2) {
display: none !important;
}
/* Comment / flags already hidden; keep meta area for optional second line */
.data-table-row.env-table-grid > :nth-child(3),
.data-table-row.env-table-grid > :nth-child(4),
.data-table-row.env-table-grid > :nth-child(5),
.data-table-row.env-table-grid > :nth-child(6),
@@ -1695,13 +1744,10 @@ input[type="search"]::-webkit-search-results-decoration {
justify-self: end;
}
.env-type-mobile {
display: inline-flex !important;
}
}
.env-type-mobile {
display: none;
.env-managed-desktop {
display: flex;
}
@media (max-width: 640px) {
@@ -1835,6 +1881,154 @@ input[type="search"]::-webkit-search-results-decoration {
grid-template-columns: minmax(10rem, 1.7fr) 6rem minmax(7rem, 0.8fr) 7.5rem minmax(8rem, 1fr) 5rem;
}
/* Persistent storage volumes: Name | Source | Destination | [PR suffix] | [Actions] */
.volumes-table-grid-readonly {
grid-template-columns: minmax(12rem, 1.5fr) minmax(8rem, 1fr) minmax(8rem, 1fr);
}
.volumes-table-grid {
grid-template-columns: minmax(10rem, 1.4fr) minmax(6rem, 1fr) minmax(6rem, 1fr) minmax(10.5rem, auto);
}
.volumes-table-grid-with-pr {
grid-template-columns: minmax(9rem, 1.2fr) minmax(5.5rem, 0.85fr) minmax(5.5rem, 0.85fr) 9.25rem minmax(10.5rem, auto);
}
.volumes-mobile-label {
display: none;
}
/*
* Same tokens as .application-settings-form label (13px / medium / subtle).
* Do not use text-sm (14px) — settings labels override Tailwind to 13px.
*/
.volumes-mobile-label.is-visible,
.volumes-field-label {
font-size: 13px;
font-weight: 500;
line-height: 1rem;
color: var(--coollabs-subtle);
}
/* Compact inputs inside volume table rows (desktop) */
.data-table-row.volumes-table-grid .input,
.data-table-row.volumes-table-grid-with-pr .input,
.data-table-row.volumes-table-grid .listbox-trigger,
.data-table-row.volumes-table-grid-with-pr .listbox-trigger {
min-height: 2rem;
height: 2rem;
font-size: 12px;
}
.data-table-row.volumes-table-grid .listbox-trigger,
.data-table-row.volumes-table-grid-with-pr .listbox-trigger {
padding-inline: 0.5rem;
}
@media (max-width: 1100px) {
.volumes-table-grid {
grid-template-columns: minmax(9rem, 1.2fr) minmax(6rem, 1fr) minmax(9rem, auto);
}
.volumes-table-grid > .volumes-col-source,
.data-table-header.volumes-table-grid > .volumes-col-source {
display: none;
}
.volumes-table-grid-with-pr {
grid-template-columns: minmax(9rem, 1.1fr) minmax(6rem, 1fr) 8.5rem minmax(9rem, auto);
}
.volumes-table-grid-with-pr > .volumes-col-source,
.data-table-header.volumes-table-grid-with-pr > .volumes-col-source {
display: none;
}
.volumes-table-grid-readonly {
grid-template-columns: minmax(10rem, 1.4fr) minmax(8rem, 1fr);
}
.volumes-table-grid-readonly > .volumes-col-source,
.data-table-header.volumes-table-grid-readonly > .volumes-col-source {
display: none;
}
}
/* Phone: stacked card rows with per-field labels (table headers hidden) */
@media (max-width: 768px) {
.data-table-header.volumes-table-grid,
.data-table-header.volumes-table-grid-with-pr,
.data-table-header.volumes-table-grid-readonly {
display: none;
}
.data-table-row.volumes-table-grid,
.data-table-row.volumes-table-grid-with-pr,
.data-table-row.volumes-table-grid-readonly {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 0.625rem;
padding: 0.875rem 1rem;
min-height: 0;
}
.data-table-row.volumes-table-grid > .volumes-col-source,
.data-table-row.volumes-table-grid-with-pr > .volumes-col-source,
.data-table-row.volumes-table-grid-with-pr > .volumes-col-pr,
.data-table-row.volumes-table-grid-readonly > .volumes-col-source {
display: flex;
flex-direction: column;
gap: 0.25rem;
min-width: 0;
width: 100%;
}
.data-table-row.volumes-table-grid > *,
.data-table-row.volumes-table-grid-with-pr > *,
.data-table-row.volumes-table-grid-readonly > * {
width: 100%;
min-width: 0;
}
/* Match .application-settings-form label (13px), not Tailwind text-sm (14px) */
.volumes-mobile-label {
display: block;
font-size: 13px;
font-weight: 500;
line-height: 1rem;
color: var(--coollabs-subtle);
}
.volumes-cell-name,
.volumes-cell-dest,
.volumes-cell-actions {
display: flex;
flex-direction: column;
gap: 0.25rem;
min-width: 0;
width: 100%;
}
.volumes-cell-actions {
flex-direction: row;
flex-wrap: wrap;
align-items: center;
justify-content: flex-start;
gap: 0.5rem;
padding-top: 0.25rem;
}
.data-table-row.volumes-table-grid .input,
.data-table-row.volumes-table-grid-with-pr .input,
.data-table-row.volumes-table-grid .listbox-trigger,
.data-table-row.volumes-table-grid-with-pr .listbox-trigger {
min-height: 2.25rem;
height: 2.25rem;
font-size: 13px;
}
}
.deployment-table-grid {
grid-template-columns: 7.5rem minmax(7rem, 0.8fr) minmax(12rem, 1.7fr) minmax(8rem, 0.9fr) 6.5rem minmax(7rem, 0.8fr);
}
@@ -2587,6 +2781,37 @@ input[type="search"]::-webkit-search-results-decoration {
color: #f87171;
}
.table-badge-warning {
background: rgba(245, 158, 11, 0.16);
color: #b45309;
}
.dark .table-badge-warning {
background: rgba(245, 158, 11, 0.14);
color: #fbbf24;
}
.table-badge-success {
background: rgba(16, 185, 129, 0.14);
color: #047857;
}
.dark .table-badge-success {
background: rgba(16, 185, 129, 0.16);
color: #34d399;
}
/* Suggested / not-yet-configured domain rows — distinct from real FQDNs */
.domains-row-suggested {
background: rgba(245, 158, 11, 0.05);
box-shadow: inset 3px 0 0 0 rgba(245, 158, 11, 0.55);
}
.dark .domains-row-suggested {
background: rgba(245, 158, 11, 0.07);
box-shadow: inset 3px 0 0 0 rgba(251, 191, 36, 0.5);
}
/* Chip/tag input (comma-free multi-value entry, e.g. Domains) */
.chip-input {
display: flex;