From 2a295d573b77f5efa32f43f713766fa9b169633e Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 23 Oct 2026 22:14:16 +0200 Subject: [PATCH] feat(ui): rework dashboard columns and polish the deploy canvas - Dashboard: projects and servers fill the left, recent deployments sit in a fixed-width right column; cards stretch to fill rows; the server table shows live CPU and memory usage when metrics are on. - Deploy canvas: purple accent in dark mode, the Steps panel floats over the canvas, and both canvases use the flask beta icon. Co-Authored-By: Claude Opus 5.5 --- resources/js/canvas/BetaBadge.svelte | 9 +++ .../js/canvas/deploy/DeployCanvas.svelte | 3 +- resources/js/canvas/deploy/state.svelte.js | 17 ++++- resources/js/canvas/deploy/styles.js | 11 +++- .../js/canvas/environment/Toolbar.svelte | 5 +- resources/views/livewire/dashboard.blade.php | 63 ++++++++++++++++--- .../dashboard/active-deployments.blade.php | 8 +-- 7 files changed, 92 insertions(+), 24 deletions(-) create mode 100644 resources/js/canvas/BetaBadge.svelte diff --git a/resources/js/canvas/BetaBadge.svelte b/resources/js/canvas/BetaBadge.svelte new file mode 100644 index 0000000000..45d949307a --- /dev/null +++ b/resources/js/canvas/BetaBadge.svelte @@ -0,0 +1,9 @@ + + + + + diff --git a/resources/js/canvas/deploy/DeployCanvas.svelte b/resources/js/canvas/deploy/DeployCanvas.svelte index 89dab86f61..14871dcfcd 100644 --- a/resources/js/canvas/deploy/DeployCanvas.svelte +++ b/resources/js/canvas/deploy/DeployCanvas.svelte @@ -4,6 +4,7 @@ import Dropdown from './Dropdown.svelte'; import Edges from './Edges.svelte'; import Icon from './Icon.svelte'; + import BetaBadge from '../BetaBadge.svelte'; import Node from './Node.svelte'; import PaletteItem from './PaletteItem.svelte'; import RunPanel from './RunPanel.svelte'; @@ -82,7 +83,7 @@ {/if} - Beta + {/snippet} diff --git a/resources/js/canvas/deploy/state.svelte.js b/resources/js/canvas/deploy/state.svelte.js index 2cfb6c3c24..ffb74cd4ff 100644 --- a/resources/js/canvas/deploy/state.svelte.js +++ b/resources/js/canvas/deploy/state.svelte.js @@ -121,6 +121,15 @@ const height = n => els.nodeEls.get(n.id)?.offsetHeight || 160; // A card's box in world units (its rendered size, or a usual card size). const boxOf = n => ({ x: n.x, y: n.y, w: width(n), h: height(n) }); +// The part of the viewport that the floating Steps panel does not cover, +// relative to the viewport (on small screens the panel is above it). +function openArea() { + const r = els.viewport.getBoundingClientRect(); + const p = els.root.querySelector('.cv-palette').getBoundingClientRect(); + const left = p.bottom > r.top ? Math.max(0, p.right - r.left) : 0; + return { left, width: r.width - left, height: r.height }; +} + export function fit() { if (!els.nodeEls.size) return; let minX = Infinity; let minY = Infinity; let maxX = -Infinity; let maxY = -Infinity; @@ -136,11 +145,11 @@ export function fit() { maxX = Math.max(maxX, pill.offsetLeft + pill.offsetWidth); maxY = Math.max(maxY, pill.offsetTop + pill.offsetHeight); } - const r = els.viewport.getBoundingClientRect(); + const r = openArea(); const pad = 60; const view = cv.view; view.z = Math.min(1.2, Math.max(MIN_ZOOM, Math.min((r.width - pad * 2) / (maxX - minX), (r.height - pad * 2) / (maxY - minY)))); - view.x = (r.width - (maxX - minX) * view.z) / 2 - minX * view.z; + view.x = r.left + (r.width - (maxX - minX) * view.z) / 2 - minX * view.z; view.y = (r.height - (maxY - minY) * view.z) / 2 - minY * view.z; els.pz.apply(); } @@ -259,7 +268,8 @@ function appendToChain(type) { } function centre() { const r = els.viewport.getBoundingClientRect(); - const p = toWorld(r.left + r.width / 2, r.top + r.height / 2); + const a = openArea(); + const p = toWorld(r.left + a.left + a.width / 2, r.top + r.height / 2); return [p.x - 140, p.y - 60]; } @@ -299,6 +309,7 @@ export function dragFromPalette(type, event) { ghost.remove(); const r = els.viewport.getBoundingClientRect(); if (e.clientX < r.left || e.clientX > r.right || e.clientY < r.top || e.clientY > r.bottom) return; + if (e.target.closest?.('.cv-palette')) return; const p = toWorld(e.clientX, e.clientY); addNode(type, p.x - 30, p.y - 16); }; diff --git a/resources/js/canvas/deploy/styles.js b/resources/js/canvas/deploy/styles.js index 869e76d236..b05e1fa014 100644 --- a/resources/js/canvas/deploy/styles.js +++ b/resources/js/canvas/deploy/styles.js @@ -16,8 +16,8 @@ const CSS = ` --cv-line: var(--coollabs-hairline, light-dark(#e5e5e5, rgb(255 255 255 / 0.08))); --cv-input-bg: light-dark(#fafafa, rgb(255 255 255 / 0.04)); --cv-input-line: light-dark(#e5e5e5, rgb(255 255 255 / 0.08)); - --cv-brand: light-dark(var(--color-coollabs), var(--color-warning)); - --cv-brand-text: var(--cv-brand); + --cv-brand: light-dark(var(--color-coollabs), var(--color-brand)); + --cv-brand-text: light-dark(var(--color-coollabs), var(--color-brand-light)); position: absolute; inset: 0; z-index: 10; display: grid; grid-template-columns: 15.5rem minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr); gap: 8px; padding: 8px; @@ -100,6 +100,13 @@ const CSS = ` /* Canvas island */ .cv-stage { position: relative; overflow: hidden; border: 0; background: transparent; } + /* Wide screens: the canvas fills the whole area to the screen edges and the + Steps panel floats over it, so cards slide under the panel, not cut off. */ + @media (min-width: 768px) { + .cv-stage { grid-column: 1 / -1; grid-row: -2 / -1; margin: 0 -8px -8px; } + .cv-docked .cv-stage { margin-top: -8px; } + .cv-palette { grid-column: 1; grid-row: -2 / -1; position: relative; z-index: 15; box-shadow: 0 16px 40px -20px rgb(0 0 0 / 0.35); } + } .cv-viewport { position: absolute; inset: 0; cursor: grab; touch-action: none; /* The dots are on .cv-root, so the viewport has none of its own. */ diff --git a/resources/js/canvas/environment/Toolbar.svelte b/resources/js/canvas/environment/Toolbar.svelte index ee53dcb078..2ac3fd0766 100644 --- a/resources/js/canvas/environment/Toolbar.svelte +++ b/resources/js/canvas/environment/Toolbar.svelte @@ -1,4 +1,5 @@