fix(ui): restore log header on notched phones and widen mobile container names (#4972)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
f9dc18a0aa
commit
db80ba682e
@@ -45,6 +45,17 @@
|
||||
:options="pageSizes.map((i) => ({ label: i.toLocaleString(), value: i }))"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-1 md:hidden">
|
||||
{{ $t("label.sort-by") }}
|
||||
<DropdownMenu class="dropdown-left btn-xs" v-model="mobileSortField" :options="sortOptions" />
|
||||
<button
|
||||
class="btn btn-square btn-ghost btn-xs"
|
||||
@click="direction *= -1"
|
||||
:aria-label="$t('label.sort-direction')"
|
||||
>
|
||||
<mdi:arrow-up :class="direction > 0 ? '' : 'rotate-180'" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="join max-md:hidden">
|
||||
<button
|
||||
class="btn join-item btn-xs md:btn-sm"
|
||||
@@ -65,7 +76,7 @@
|
||||
</div>
|
||||
<div class="rounded-box border-base-content/10 overflow-x-auto border">
|
||||
<table class="table-md md:table-lg table-zebra table">
|
||||
<thead>
|
||||
<thead class="max-md:hidden">
|
||||
<tr :data-direction="direction > 0 ? 'asc' : 'desc'">
|
||||
<th
|
||||
v-for="(value, key) in fields"
|
||||
@@ -90,20 +101,45 @@
|
||||
v-memo="[container.id, container.state, container.health, statMode, isMobile]"
|
||||
class="hover:bg-base-100/80!"
|
||||
>
|
||||
<td v-if="isVisible('name')" class="max-w-80 max-md:max-w-32">
|
||||
<div class="flex items-center gap-2">
|
||||
<ContainerStatusIcon :state="container.state" :health="container.health" class="shrink-0" />
|
||||
<div class="min-w-0">
|
||||
<router-link
|
||||
class="block truncate"
|
||||
:to="{ name: '/container/[id]', params: { id: container.id } }"
|
||||
:title="container.name"
|
||||
>
|
||||
{{ container.name }}
|
||||
</router-link>
|
||||
<td v-if="isVisible('name')" class="max-w-80 max-md:max-w-none">
|
||||
<div class="flex items-center gap-2 max-md:items-start">
|
||||
<ContainerStatusIcon
|
||||
:state="container.state"
|
||||
:health="container.health"
|
||||
class="shrink-0 max-md:mt-0.5"
|
||||
/>
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex items-baseline gap-2">
|
||||
<router-link
|
||||
class="min-w-0 flex-1 truncate"
|
||||
:to="{ name: '/container/[id]', params: { id: container.id } }"
|
||||
:title="container.name"
|
||||
>
|
||||
{{ container.name }}
|
||||
</router-link>
|
||||
<RelativeTime
|
||||
v-if="isMobile"
|
||||
:date="container.created"
|
||||
class="text-base-content/50 shrink-0 text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="container.customGroup" class="text-base-content/50 truncate text-xs">
|
||||
{{ container.customGroup }}
|
||||
</div>
|
||||
<div v-if="isMobile && container.state === 'running'" class="mt-1.5 flex items-center gap-1.5">
|
||||
<ContainerStatCell
|
||||
:container="container"
|
||||
type="cpu"
|
||||
:host="hosts[container.host]"
|
||||
:mode="statMode"
|
||||
/>
|
||||
<ContainerStatCell
|
||||
:container="container"
|
||||
type="mem"
|
||||
:host="hosts[container.host]"
|
||||
:mode="statMode"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
@@ -147,6 +183,7 @@
|
||||
import { Container } from "@/models/Container";
|
||||
import { toRefs } from "@vueuse/core";
|
||||
|
||||
const { t } = useI18n();
|
||||
const { hosts } = useHosts();
|
||||
const selectedHost = ref(null);
|
||||
|
||||
@@ -190,7 +227,7 @@ const fields: Record<
|
||||
label: "label.avg-cpu",
|
||||
mobileLabel: "label.cpu",
|
||||
sortFunc: (a: Container, b: Container) => (a.movingAverage.cpu - b.movingAverage.cpu) * direction.value,
|
||||
mobileVisible: true,
|
||||
mobileVisible: false,
|
||||
customClass: "min-w-48 max-md:min-w-0",
|
||||
},
|
||||
mem: {
|
||||
@@ -198,7 +235,7 @@ const fields: Record<
|
||||
mobileLabel: "label.mem",
|
||||
sortFunc: (a: Container, b: Container) =>
|
||||
(a.movingAverage.memoryUsage - b.movingAverage.memoryUsage) * direction.value,
|
||||
mobileVisible: true,
|
||||
mobileVisible: false,
|
||||
customClass: "min-w-48 max-md:min-w-0",
|
||||
},
|
||||
};
|
||||
@@ -237,6 +274,19 @@ const paginated = computed(() => {
|
||||
return sortedContainers.value.slice(start, end);
|
||||
});
|
||||
|
||||
const sortOptions = computed(() =>
|
||||
Object.entries(fields)
|
||||
.filter(([key]) => key !== "host" || Object.keys(hosts.value).length > 1)
|
||||
.map(([key, value]) => ({ label: t(value.mobileLabel ?? value.label), value: key })),
|
||||
);
|
||||
|
||||
const mobileSortField = computed({
|
||||
get: () => sortField.value,
|
||||
set: (field: keys) => {
|
||||
if (field !== sortField.value) sort(field);
|
||||
},
|
||||
});
|
||||
|
||||
function sort(field: keys) {
|
||||
if (sortField.value === field) {
|
||||
direction.value *= -1;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<header
|
||||
v-if="$slots.header"
|
||||
data-testid="scrollable-header"
|
||||
class="border-base-content/10 bg-base-200 sticky top-[var(--mobile-nav-height)] z-20 border-b py-0.5 shadow-[1px_1px_2px_0_rgb(0,0,0,0.05)] md:top-0 md:py-2"
|
||||
class="border-base-content/10 bg-base-200 sticky top-[var(--mobile-nav-offset)] z-20 border-b py-0.5 shadow-[1px_1px_2px_0_rgb(0,0,0,0.05)] md:top-0 md:py-2"
|
||||
>
|
||||
<slot name="header"></slot>
|
||||
</header>
|
||||
|
||||
@@ -207,6 +207,10 @@ body {
|
||||
* they stay locked together. The safe-area inset is already handled by body. */
|
||||
:root {
|
||||
--mobile-nav-height: 57px;
|
||||
/* Viewport offset of the nav's bottom edge. The nav is fixed to the viewport
|
||||
* so it sits above the body's safe-area padding, which means anything sticking
|
||||
* to it must add the inset back in. */
|
||||
--mobile-nav-offset: calc(var(--mobile-nav-height) + env(safe-area-inset-top));
|
||||
}
|
||||
|
||||
[class*="shadow-"] {
|
||||
|
||||
@@ -26,5 +26,15 @@ test.describe("mobile stats", () => {
|
||||
// The stats header must sit flush against the bottom of the fixed nav bar:
|
||||
// any gap exposes empty space, any overlap clips the CPU/MEM chips underneath the nav.
|
||||
expect(Math.abs(header!.y - (nav!.y + nav!.height))).toBeLessThanOrEqual(1);
|
||||
|
||||
// ...and stay there once the logs scroll under it.
|
||||
await page.mouse.wheel(0, 600);
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const stuckNav = await page.getByTestId("navigation").boundingBox();
|
||||
const stuckHeader = await page.getByTestId("scrollable-header").boundingBox();
|
||||
return Math.abs(stuckHeader!.y - (stuckNav!.y + stuckNav!.height));
|
||||
})
|
||||
.toBeLessThanOrEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -65,6 +65,8 @@ label:
|
||||
mem: MEM
|
||||
pinned: Fastgjort
|
||||
per-page: Rækker per side
|
||||
sort-by: Sortér efter
|
||||
sort-direction: Skift sorteringsrækkefølge
|
||||
host-menu: Værter og Containere
|
||||
swarm-menu: Tjenester og Stacks
|
||||
k8s-menu: Kubernetes
|
||||
|
||||
@@ -65,6 +65,8 @@ label:
|
||||
mem: MEM
|
||||
pinned: Angepinnt
|
||||
per-page: Zeilen pro Seite
|
||||
sort-by: Sortieren nach
|
||||
sort-direction: Sortierreihenfolge umkehren
|
||||
host-menu: Hosts und Container
|
||||
swarm-menu: Services und Stacks
|
||||
k8s-menu: Kubernetes
|
||||
|
||||
@@ -65,6 +65,8 @@ label:
|
||||
mem: MEM
|
||||
pinned: Pinned
|
||||
per-page: Rows per page
|
||||
sort-by: Sort by
|
||||
sort-direction: Toggle sort direction
|
||||
host-menu: Hosts and Containers
|
||||
swarm-menu: Services and Stacks
|
||||
k8s-menu: Kubernetes
|
||||
|
||||
@@ -65,6 +65,8 @@ label:
|
||||
mem: MEM
|
||||
pinned: Fijado
|
||||
per-page: Filas por página
|
||||
sort-by: Ordenar por
|
||||
sort-direction: Cambiar el orden
|
||||
host-menu: Hosts y Contenedores
|
||||
swarm-menu: Servicios y Stacks
|
||||
k8s-menu: Kubernetes
|
||||
|
||||
@@ -65,6 +65,8 @@ label:
|
||||
mem: MEM
|
||||
pinned: Epinglé
|
||||
per-page: Colonnes par page
|
||||
sort-by: Trier par
|
||||
sort-direction: Inverser l'ordre de tri
|
||||
host-menu: Hôtes et Conteneurs
|
||||
swarm-menu: Services et Stacks
|
||||
k8s-menu: Kubernetes
|
||||
|
||||
@@ -66,6 +66,8 @@ label:
|
||||
mem: MEM
|
||||
pinned: Disematkan
|
||||
per-page: Baris per halaman
|
||||
sort-by: Urutkan menurut
|
||||
sort-direction: Ubah arah pengurutan
|
||||
host-menu: Host dan Kontainer
|
||||
swarm-menu: Layanan dan Stack
|
||||
k8s-menu: Kubernetes
|
||||
|
||||
@@ -65,6 +65,8 @@ label:
|
||||
mem: MEM
|
||||
pinned: Fissato
|
||||
per-page: Righe per pagina
|
||||
sort-by: Ordina per
|
||||
sort-direction: Inverti l'ordine
|
||||
host-menu: Host e Container
|
||||
swarm-menu: Servizi e Stack
|
||||
k8s-menu: Kubernetes
|
||||
|
||||
@@ -65,6 +65,8 @@ label:
|
||||
mem: 메모리
|
||||
pinned: 고정됨
|
||||
per-page: 페이지당 행 수
|
||||
sort-by: 정렬 기준
|
||||
sort-direction: 정렬 순서 전환
|
||||
host-menu: 호스트 및 컨테이너
|
||||
swarm-menu: 서비스 및 스택
|
||||
k8s-menu: 쿠버네티스
|
||||
|
||||
@@ -65,6 +65,8 @@ label:
|
||||
mem: MEM
|
||||
pinned: Vastgezet
|
||||
per-page: Rijen per pagina
|
||||
sort-by: Sorteren op
|
||||
sort-direction: Sorteervolgorde omkeren
|
||||
host-menu: Hosts en Containers
|
||||
swarm-menu: Services en Stacks
|
||||
k8s-menu: Kubernetes
|
||||
|
||||
@@ -65,6 +65,8 @@ label:
|
||||
mem: MEM
|
||||
pinned: Przypięte
|
||||
per-page: Wierszy na strone
|
||||
sort-by: Sortuj według
|
||||
sort-direction: Zmień kierunek sortowania
|
||||
host-menu: Hosty i Kontenery
|
||||
swarm-menu: Usługi i Stosy
|
||||
k8s-menu: Kubernetes
|
||||
|
||||
@@ -65,6 +65,8 @@ label:
|
||||
mem: MEM
|
||||
pinned: Fixado
|
||||
per-page: Linhas por página
|
||||
sort-by: Ordenar por
|
||||
sort-direction: Inverter a ordem
|
||||
host-menu: Hosts e Containers
|
||||
swarm-menu: Serviços e Stacks
|
||||
k8s-menu: Kubernetes
|
||||
|
||||
@@ -65,6 +65,8 @@ label:
|
||||
mem: MEM
|
||||
pinned: Закреплено
|
||||
per-page: Строк на странице
|
||||
sort-by: Сортировать по
|
||||
sort-direction: Изменить порядок сортировки
|
||||
host-menu: Хосты и Контейнеры
|
||||
swarm-menu: Сервисы и Стеки
|
||||
k8s-menu: Kubernetes
|
||||
|
||||
@@ -48,6 +48,8 @@ label:
|
||||
avg-cpu: CPE povprečje (%)
|
||||
pinned: Pripeto
|
||||
per-page: Vrstic na stran
|
||||
sort-by: Razvrsti po
|
||||
sort-direction: Zamenjaj vrstni red
|
||||
host-menu: Gostitelji in zabojniki
|
||||
swarm-menu: Storitve in skladi
|
||||
k8s-menu: Kubernetes
|
||||
|
||||
@@ -65,6 +65,8 @@ label:
|
||||
mem: MEM
|
||||
pinned: Sabitlenmiş
|
||||
per-page: Sayfa başına satır
|
||||
sort-by: Sırala
|
||||
sort-direction: Sıralama yönünü değiştir
|
||||
host-menu: Sunucular ve Konteynerler
|
||||
swarm-menu: Servisler ve Yığınlar
|
||||
k8s-menu: Kubernetes
|
||||
|
||||
@@ -76,6 +76,8 @@ label:
|
||||
mem: MEM
|
||||
pinned: 已釘選
|
||||
per-page: 每頁列數
|
||||
sort-by: 排序方式
|
||||
sort-direction: 切換排序方向
|
||||
host-menu: 主機和容器
|
||||
swarm-menu: 服務和堆疊
|
||||
k8s-menu: Kubernetes
|
||||
|
||||
@@ -76,6 +76,8 @@ label:
|
||||
mem: MEM
|
||||
pinned: 固定
|
||||
per-page: 每页行数
|
||||
sort-by: 排序方式
|
||||
sort-direction: 切换排序方向
|
||||
host-menu: 主机和容器
|
||||
swarm-menu: 服务和堆栈
|
||||
k8s-menu: Kubernetes
|
||||
|
||||
Reference in New Issue
Block a user