fix(dashboards): stabilize grid layout and remove owners N+1 fallback
This commit is contained in:
@@ -69,6 +69,7 @@
|
||||
let sortColumn = "title";
|
||||
let sortDirection = "asc";
|
||||
let openFilterColumn = null;
|
||||
let filterDropdownPosition = { left: 0, top: 0 };
|
||||
let columnFilterSearch = {
|
||||
title: "",
|
||||
git_status: "",
|
||||
@@ -482,8 +483,23 @@
|
||||
* @PRE: column is valid filter key.
|
||||
* @POST: openFilterColumn updated.
|
||||
*/
|
||||
function toggleFilterDropdown(column) {
|
||||
openFilterColumn = openFilterColumn === column ? null : column;
|
||||
function toggleFilterDropdown(column, event, panelWidth = 256) {
|
||||
event?.stopPropagation();
|
||||
if (openFilterColumn === column) {
|
||||
openFilterColumn = null;
|
||||
return;
|
||||
}
|
||||
const trigger = event?.currentTarget;
|
||||
if (trigger?.getBoundingClientRect) {
|
||||
const rect = trigger.getBoundingClientRect();
|
||||
const viewportWidth = typeof window !== "undefined" ? window.innerWidth : 1920;
|
||||
const safeLeft = Math.max(8, Math.min(rect.left, viewportWidth - panelWidth - 8));
|
||||
filterDropdownPosition = {
|
||||
left: safeLeft,
|
||||
top: rect.bottom + 8,
|
||||
};
|
||||
}
|
||||
openFilterColumn = column;
|
||||
}
|
||||
|
||||
// [/DEF:DashboardHub.toggleFilterDropdown:Function]
|
||||
@@ -1212,8 +1228,8 @@
|
||||
{#if isLoading}
|
||||
<div class="bg-white border border-slate-200 rounded-xl shadow-sm overflow-hidden">
|
||||
<div
|
||||
class="grid min-w-[1520px] gap-4 px-6 py-3 bg-slate-50 border-b border-slate-200 font-semibold text-[11px] uppercase tracking-wide text-slate-600"
|
||||
style="grid-template-columns: 40px minmax(250px,2.3fr) minmax(118px,0.9fr) minmax(150px,1fr) minmax(150px,1fr) minmax(300px,2.1fr) minmax(280px,2fr);"
|
||||
class="grid gap-4 px-6 py-3 bg-slate-50 border-b border-slate-200 font-semibold text-[11px] uppercase tracking-wide text-slate-600"
|
||||
style="width: max(100%, 1520px); grid-template-columns: 40px minmax(250px,2.3fr) minmax(118px,0.9fr) minmax(150px,1fr) minmax(280px,2fr) minmax(150px,1fr) minmax(300px,2.1fr);"
|
||||
>
|
||||
<div class="h-4 rounded bg-gray-200 animate-pulse"></div>
|
||||
<div class="h-4 rounded bg-gray-200 animate-pulse"></div>
|
||||
@@ -1225,8 +1241,8 @@
|
||||
</div>
|
||||
{#each Array(5) as _}
|
||||
<div
|
||||
class="grid min-w-[1520px] gap-4 px-6 py-4 border-b border-slate-200 last:border-b-0 hover:bg-slate-50 transition-colors"
|
||||
style="grid-template-columns: 40px minmax(250px,2.3fr) minmax(118px,0.9fr) minmax(150px,1fr) minmax(150px,1fr) minmax(300px,2.1fr) minmax(280px,2fr);"
|
||||
class="grid gap-4 px-6 py-4 border-b border-slate-200 last:border-b-0 hover:bg-slate-50 transition-colors"
|
||||
style="width: max(100%, 1520px); grid-template-columns: 40px minmax(250px,2.3fr) minmax(118px,0.9fr) minmax(150px,1fr) minmax(280px,2fr) minmax(150px,1fr) minmax(300px,2.1fr);"
|
||||
>
|
||||
<div class="h-4 rounded bg-gray-200 animate-pulse"></div>
|
||||
<div class="h-4 rounded bg-gray-200 animate-pulse"></div>
|
||||
@@ -1297,11 +1313,11 @@
|
||||
|
||||
<!-- Dashboard Grid -->
|
||||
<div class="bg-white border border-slate-200 rounded-xl shadow-sm">
|
||||
<div class="overflow-x-auto">
|
||||
<div class="overflow-x-auto overflow-y-visible">
|
||||
<!-- Grid Header -->
|
||||
<div
|
||||
class="grid min-w-[1520px] gap-4 px-6 py-3 bg-slate-50 border-b border-slate-200 font-semibold text-[11px] uppercase tracking-wide text-slate-600"
|
||||
style="grid-template-columns: 40px minmax(250px,2.3fr) minmax(118px,0.9fr) minmax(150px,1fr) minmax(150px,1fr) minmax(300px,2.1fr) minmax(280px,2fr);"
|
||||
class="grid gap-4 px-6 py-3 bg-slate-50 border-b border-slate-200 font-semibold text-[11px] uppercase tracking-wide text-slate-600"
|
||||
style="width: max(100%, 1520px); grid-template-columns: 40px minmax(250px,2.3fr) minmax(118px,0.9fr) minmax(150px,1fr) minmax(280px,2fr) minmax(150px,1fr) minmax(300px,2.1fr);"
|
||||
>
|
||||
<div></div>
|
||||
<div class="flex items-center gap-2">
|
||||
@@ -1311,13 +1327,13 @@
|
||||
<div class="relative column-filter">
|
||||
<button
|
||||
class="rounded border px-1.5 py-0.5 text-xs transition-colors {hasColumnFilter('title') ? 'border-blue-400 text-blue-700 bg-blue-50' : 'border-gray-300 text-gray-500 hover:text-gray-700'}"
|
||||
on:click={() => toggleFilterDropdown("title")}
|
||||
on:click={(event) => toggleFilterDropdown("title", event, 256)}
|
||||
title={$t.dashboard?.column_filter}
|
||||
>
|
||||
▾
|
||||
</button>
|
||||
{#if openFilterColumn === "title"}
|
||||
<div class="absolute z-30 mt-2 w-64 rounded-lg border border-gray-200 bg-white shadow-lg p-3">
|
||||
<div class="fixed z-50 w-64 rounded-lg border border-gray-200 bg-white shadow-lg p-3" style="left: {filterDropdownPosition.left}px; top: {filterDropdownPosition.top}px;">
|
||||
<input
|
||||
type="text"
|
||||
class="w-full rounded border border-gray-300 px-2 py-1 text-xs"
|
||||
@@ -1352,13 +1368,13 @@
|
||||
<div class="relative column-filter">
|
||||
<button
|
||||
class="rounded border px-1.5 py-0.5 text-xs transition-colors {hasColumnFilter('git_status') ? 'border-blue-400 text-blue-700 bg-blue-50' : 'border-gray-300 text-gray-500 hover:text-gray-700'}"
|
||||
on:click={() => toggleFilterDropdown("git_status")}
|
||||
on:click={(event) => toggleFilterDropdown("git_status", event, 256)}
|
||||
title={$t.dashboard?.column_filter}
|
||||
>
|
||||
▾
|
||||
</button>
|
||||
{#if openFilterColumn === "git_status"}
|
||||
<div class="absolute z-30 mt-2 w-64 rounded-lg border border-gray-200 bg-white shadow-lg p-3">
|
||||
<div class="fixed z-50 w-64 rounded-lg border border-gray-200 bg-white shadow-lg p-3" style="left: {filterDropdownPosition.left}px; top: {filterDropdownPosition.top}px;">
|
||||
<input
|
||||
type="text"
|
||||
class="w-full rounded border border-gray-300 px-2 py-1 text-xs"
|
||||
@@ -1393,13 +1409,13 @@
|
||||
<div class="relative column-filter">
|
||||
<button
|
||||
class="rounded border px-1.5 py-0.5 text-xs transition-colors {hasColumnFilter('llm_status') ? 'border-blue-400 text-blue-700 bg-blue-50' : 'border-gray-300 text-gray-500 hover:text-gray-700'}"
|
||||
on:click={() => toggleFilterDropdown("llm_status")}
|
||||
on:click={(event) => toggleFilterDropdown("llm_status", event, 256)}
|
||||
title={$t.dashboard?.column_filter}
|
||||
>
|
||||
▾
|
||||
</button>
|
||||
{#if openFilterColumn === "llm_status"}
|
||||
<div class="absolute z-30 mt-2 w-64 rounded-lg border border-gray-200 bg-white shadow-lg p-3">
|
||||
<div class="fixed z-50 w-64 rounded-lg border border-gray-200 bg-white shadow-lg p-3" style="left: {filterDropdownPosition.left}px; top: {filterDropdownPosition.top}px;">
|
||||
<input
|
||||
type="text"
|
||||
class="w-full rounded border border-gray-300 px-2 py-1 text-xs"
|
||||
@@ -1427,6 +1443,9 @@
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
{$t.dashboard?.actions}
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button class="hover:text-gray-900 transition-colors" on:click={() => handleSort("changed_on")}>
|
||||
{$t.dashboard?.changed_on} {getSortIndicator("changed_on")}
|
||||
@@ -1434,13 +1453,13 @@
|
||||
<div class="relative column-filter">
|
||||
<button
|
||||
class="rounded border px-1.5 py-0.5 text-xs transition-colors {hasColumnFilter('changed_on') ? 'border-blue-400 text-blue-700 bg-blue-50' : 'border-gray-300 text-gray-500 hover:text-gray-700'}"
|
||||
on:click={() => toggleFilterDropdown("changed_on")}
|
||||
on:click={(event) => toggleFilterDropdown("changed_on", event, 256)}
|
||||
title={$t.dashboard?.column_filter}
|
||||
>
|
||||
▾
|
||||
</button>
|
||||
{#if openFilterColumn === "changed_on"}
|
||||
<div class="absolute z-30 mt-2 w-64 rounded-lg border border-gray-200 bg-white shadow-lg p-3">
|
||||
<div class="fixed z-50 w-64 rounded-lg border border-gray-200 bg-white shadow-lg p-3" style="left: {filterDropdownPosition.left}px; top: {filterDropdownPosition.top}px;">
|
||||
<input
|
||||
type="text"
|
||||
class="w-full rounded border border-gray-300 px-2 py-1 text-xs"
|
||||
@@ -1475,13 +1494,13 @@
|
||||
<div class="relative column-filter">
|
||||
<button
|
||||
class="rounded border px-1.5 py-0.5 text-xs transition-colors {hasColumnFilter('actor') ? 'border-blue-400 text-blue-700 bg-blue-50' : 'border-gray-300 text-gray-500 hover:text-gray-700'}"
|
||||
on:click={() => toggleFilterDropdown("actor")}
|
||||
on:click={(event) => toggleFilterDropdown("actor", event, 288)}
|
||||
title={$t.dashboard?.column_filter}
|
||||
>
|
||||
▾
|
||||
</button>
|
||||
{#if openFilterColumn === "actor"}
|
||||
<div class="absolute z-30 mt-2 w-72 rounded-lg border border-gray-200 bg-white shadow-lg p-3">
|
||||
<div class="fixed z-50 w-72 rounded-lg border border-gray-200 bg-white shadow-lg p-3" style="left: {filterDropdownPosition.left}px; top: {filterDropdownPosition.top}px;">
|
||||
<input
|
||||
type="text"
|
||||
class="w-full rounded border border-gray-300 px-2 py-1 text-xs"
|
||||
@@ -1509,16 +1528,13 @@
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
{$t.dashboard?.actions}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Grid Rows -->
|
||||
{#each dashboards as dashboard}
|
||||
<div
|
||||
class="grid min-w-[1520px] gap-4 px-6 py-4 border-b border-slate-200 last:border-b-0 hover:bg-slate-50 transition-colors text-[13px]"
|
||||
style="grid-template-columns: 40px minmax(250px,2.3fr) minmax(118px,0.9fr) minmax(150px,1fr) minmax(150px,1fr) minmax(300px,2.1fr) minmax(280px,2fr);"
|
||||
class="grid gap-4 px-6 py-4 border-b border-slate-200 last:border-b-0 hover:bg-slate-50 transition-colors text-[13px]"
|
||||
style="width: max(100%, 1520px); grid-template-columns: 40px minmax(250px,2.3fr) minmax(118px,0.9fr) minmax(150px,1fr) minmax(280px,2fr) minmax(150px,1fr) minmax(300px,2.1fr);"
|
||||
>
|
||||
<!-- Checkbox -->
|
||||
<div>
|
||||
@@ -1586,33 +1602,12 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Changed On -->
|
||||
<div class="text-xs text-slate-600">
|
||||
{dashboard.changedOnLabel}
|
||||
</div>
|
||||
|
||||
<!-- Owners -->
|
||||
<div class="text-xs">
|
||||
<div class="text-slate-500 text-[10px] uppercase tracking-wide">{$t.dashboard?.owners || "Owners"}</div>
|
||||
{#if dashboard.owners?.length > 0}
|
||||
<div class="mt-0.5 flex flex-wrap items-center gap-1">
|
||||
{#each dashboard.owners as owner (owner)}
|
||||
<span class="rounded bg-slate-100 px-2 py-0.5 text-xs text-slate-700 truncate max-w-[180px]" title={owner}>
|
||||
{owner}
|
||||
</span>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="mt-0.5 text-gray-400">-</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="flex items-center">
|
||||
<div class="flex items-center gap-1">
|
||||
<div class="flex items-center gap-1.5">
|
||||
{#if !dashboard.git?.hasRepo}
|
||||
<button
|
||||
class="p-2 rounded border border-emerald-200 bg-emerald-50 text-emerald-700 hover:bg-emerald-100 hover:border-emerald-300 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
class="inline-flex h-8 w-8 items-center justify-center rounded-md border border-slate-200 bg-white text-emerald-600 hover:bg-emerald-50 hover:border-emerald-300 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
on:click={() => handleGitInit(dashboard)}
|
||||
disabled={isGitBusy(dashboard.id)}
|
||||
title={$t.git?.init_repo || "Init Git repository"}
|
||||
@@ -1630,7 +1625,7 @@
|
||||
</button>
|
||||
{:else}
|
||||
<button
|
||||
class="p-2 rounded border border-sky-200 bg-sky-50 text-sky-700 hover:bg-sky-100 hover:border-sky-300 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
class="inline-flex h-8 w-8 items-center justify-center rounded-md border border-slate-200 bg-white text-sky-600 hover:bg-sky-50 hover:border-sky-300 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
on:click={() => handleGitSync(dashboard)}
|
||||
disabled={isGitBusy(dashboard.id)}
|
||||
title={$t.git?.sync || "Sync from Superset"}
|
||||
@@ -1648,7 +1643,7 @@
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
class="p-2 rounded border border-orange-200 bg-orange-50 text-orange-700 hover:bg-orange-100 hover:border-orange-300 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
class="inline-flex h-8 w-8 items-center justify-center rounded-md border border-slate-200 bg-white text-orange-600 hover:bg-orange-50 hover:border-orange-300 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
on:click={() => handleGitCommit(dashboard)}
|
||||
disabled={isGitBusy(dashboard.id) ||
|
||||
!dashboard.git?.hasChangesForCommit}
|
||||
@@ -1668,7 +1663,7 @@
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
class="p-2 rounded border border-cyan-200 bg-cyan-50 text-cyan-700 hover:bg-cyan-100 hover:border-cyan-300 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
class="inline-flex h-8 w-8 items-center justify-center rounded-md border border-slate-200 bg-white text-cyan-600 hover:bg-cyan-50 hover:border-cyan-300 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
on:click={() => handleGitPull(dashboard)}
|
||||
disabled={isGitBusy(dashboard.id)}
|
||||
title={$t.git?.pull || "Pull"}
|
||||
@@ -1687,7 +1682,7 @@
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
class="p-2 rounded border border-indigo-200 bg-indigo-50 text-indigo-700 hover:bg-indigo-100 hover:border-indigo-300 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
class="inline-flex h-8 w-8 items-center justify-center rounded-md border border-slate-200 bg-white text-indigo-600 hover:bg-indigo-50 hover:border-indigo-300 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
on:click={() => handleGitPush(dashboard)}
|
||||
disabled={isGitBusy(dashboard.id)}
|
||||
title={$t.git?.push || "Push"}
|
||||
@@ -1707,7 +1702,7 @@
|
||||
</button>
|
||||
{/if}
|
||||
<button
|
||||
class="p-2 rounded border border-blue-200 bg-blue-50 text-blue-700 hover:bg-blue-100 hover:border-blue-300 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
class="inline-flex h-8 w-8 items-center justify-center rounded-md border border-slate-200 bg-white text-blue-600 hover:bg-blue-50 hover:border-blue-300 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
on:click={() => handleAction(dashboard, "migrate")}
|
||||
title={$t.dashboard?.action_migrate}
|
||||
>
|
||||
@@ -1723,7 +1718,7 @@
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
class="p-2 rounded border border-emerald-200 bg-emerald-50 text-emerald-700 hover:bg-emerald-100 hover:border-emerald-300 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
class="inline-flex h-8 w-8 items-center justify-center rounded-md border border-slate-200 bg-white text-teal-600 hover:bg-teal-50 hover:border-teal-300 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
on:click={() => handleValidate(dashboard)}
|
||||
disabled={validatingIds.has(dashboard.id)}
|
||||
title={$t.dashboard?.action_validate}
|
||||
@@ -1761,7 +1756,7 @@
|
||||
{/if}
|
||||
</button>
|
||||
<button
|
||||
class="p-2 rounded border border-amber-200 bg-amber-50 text-amber-700 hover:bg-amber-100 hover:border-amber-300 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
class="inline-flex h-8 w-8 items-center justify-center rounded-md border border-slate-200 bg-white text-amber-600 hover:bg-amber-50 hover:border-amber-300 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
on:click={() => handleAction(dashboard, "backup")}
|
||||
title={$t.dashboard?.action_backup}
|
||||
>
|
||||
@@ -1780,6 +1775,27 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Changed On -->
|
||||
<div class="text-xs text-slate-600">
|
||||
{dashboard.changedOnLabel}
|
||||
</div>
|
||||
|
||||
<!-- Owners -->
|
||||
<div class="text-xs">
|
||||
<div class="text-slate-500 text-[10px] uppercase tracking-wide">{$t.dashboard?.owners || "Owners"}</div>
|
||||
{#if dashboard.owners?.length > 0}
|
||||
<div class="mt-0.5 flex flex-wrap items-center gap-1">
|
||||
{#each dashboard.owners as owner (owner)}
|
||||
<span class="rounded bg-slate-100 px-2 py-0.5 text-xs text-slate-700 truncate max-w-[180px]" title={owner}>
|
||||
{owner}
|
||||
</span>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="mt-0.5 text-gray-400">-</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user