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() {