diff --git a/admin/ui/e2e/specs/dashboard.spec.js b/admin/ui/e2e/specs/dashboard.spec.js index 3b6bc0c..e33a489 100644 --- a/admin/ui/e2e/specs/dashboard.spec.js +++ b/admin/ui/e2e/specs/dashboard.spec.js @@ -93,8 +93,10 @@ test.describe('Fleet Dashboard', () => { const cardCount = await page.getByTestId('instance-card').count(); - // Click remove on last card - await page.getByTestId('instance-card').last().getByTestId('instance-delete').click(); + // Open the actions menu on the last card and click Remove + const lastCard = page.getByTestId('instance-card').last(); + await lastCard.getByTestId('instance-actions').click(); + await lastCard.getByTestId('instance-delete').click(); // Confirm dialog await expect(page.getByTestId('confirm-ok')).toBeVisible(); diff --git a/admin/ui/src/assets/cloudblue-logo-white.png b/admin/ui/src/assets/cloudblue-logo-white.png new file mode 100644 index 0000000..17008ff Binary files /dev/null and b/admin/ui/src/assets/cloudblue-logo-white.png differ diff --git a/admin/ui/src/components/FleetKpiPanel.vue b/admin/ui/src/components/FleetKpiPanel.vue index d0f38e7..0ad400a 100644 --- a/admin/ui/src/components/FleetKpiPanel.vue +++ b/admin/ui/src/components/FleetKpiPanel.vue @@ -67,10 +67,22 @@ const scopeNote = computed(() => { .grid { display: grid; - grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--space-3); } +@media (max-width: 900px) { + .grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media (max-width: 520px) { + .grid { + grid-template-columns: 1fr; + } +} + .scope { font-size: var(--font-size-xs); color: var(--color-text-tertiary); diff --git a/admin/ui/src/components/InstanceActionMenu.vue b/admin/ui/src/components/InstanceActionMenu.vue new file mode 100644 index 0000000..661ff37 --- /dev/null +++ b/admin/ui/src/components/InstanceActionMenu.vue @@ -0,0 +1,163 @@ + + + + + diff --git a/admin/ui/src/components/InstanceCard.vue b/admin/ui/src/components/InstanceCard.vue index 26ffcf2..6a76af1 100644 --- a/admin/ui/src/components/InstanceCard.vue +++ b/admin/ui/src/components/InstanceCard.vue @@ -11,7 +11,9 @@ >
-

{{ instance.name }}

+

+ {{ instance.name }} +

{{ instance.address }}
-
+
Version - {{ instance.version }} + {{ instance.version || '—' }}
-
+
Last seen {{ - formatTime(instance.last_seen_at) + formatTime(instance.last_seen_at) || '—' }}
@@ -40,14 +42,10 @@ > Edit - - Remove - +
@@ -55,6 +53,7 @@ @@ -168,17 +254,20 @@ onUnmounted(() => metricsStore.clearInstance()); .page { display: flex; flex-direction: column; - gap: var(--space-5); + gap: var(--space-4); } .breadcrumb { display: flex; align-items: center; - gap: var(--space-2); + gap: 6px; font-size: var(--font-size-sm); } .breadcrumbLink { + display: inline-flex; + align-items: center; + gap: 6px; color: var(--color-text-secondary); text-decoration: none; } @@ -187,19 +276,18 @@ onUnmounted(() => metricsStore.clearInstance()); color: var(--color-accent); } -.breadcrumbSep { - color: var(--color-text-tertiary); -} - -.breadcrumbCurrent { - color: var(--color-text-primary); - font-weight: var(--font-weight-medium); -} - .header { display: flex; align-items: flex-start; justify-content: space-between; + gap: var(--space-4); +} + +.headerContent { + display: flex; + flex-direction: column; + gap: var(--space-3); + min-width: 0; } .title { @@ -212,20 +300,40 @@ onUnmounted(() => metricsStore.clearInstance()); .meta { display: flex; - align-items: center; - gap: var(--space-3); - margin-top: var(--space-2); + flex-wrap: wrap; + align-items: flex-start; + gap: var(--space-8); +} + +.metaItem { + display: flex; + flex-direction: column; + gap: 2px; } -.address { +.metaLabel { font-size: var(--font-size-xs); + color: var(--color-text-tertiary); + text-transform: uppercase; + letter-spacing: 0.04em; +} + +.metaValue { + font-size: var(--font-size-sm); + color: var(--color-text-primary); +} + +.metaMono { font-family: var(--font-family-mono); + font-size: var(--font-size-xs); color: var(--color-text-secondary); } -.version { - font-size: var(--font-size-xs); - color: var(--color-text-tertiary); +.actions { + display: flex; + align-items: center; + gap: var(--space-2); + flex-shrink: 0; } .tabs { @@ -269,4 +377,14 @@ onUnmounted(() => metricsStore.clearInstance()); justify-content: center; padding: var(--space-8) 0; } + +@media (max-width: 768px) { + .header { + flex-direction: column; + } + + .actions { + align-self: flex-start; + } +} diff --git a/admin/ui/src/views/LoginView.vue b/admin/ui/src/views/LoginView.vue index e2509e7..db7d93d 100644 --- a/admin/ui/src/views/LoginView.vue +++ b/admin/ui/src/views/LoginView.vue @@ -40,6 +40,11 @@
+
+ Chaperone — a + CloudBlue + project +
@@ -49,6 +54,7 @@ import { useRouter } from 'vue-router'; import { useAuthStore } from '../stores/auth.js'; import BaseInput from '../components/BaseInput.vue'; import BaseButton from '../components/BaseButton.vue'; +import cloudBlueLogo from '../assets/cloudblue-logo-white.png'; const router = useRouter(); const auth = useAuthStore(); @@ -82,10 +88,13 @@ async function handleSubmit() {