feat(ui): add protocol icons and environment tag badges to tab bar

Tabs now show protocol-specific icons (terminal for SSH, monitor for
RDP) instead of plain colored dots. Environment tags from connections
render as colored pills: PROD in red, DEV in green, STAGING in amber,
TEST in blue. Root user sessions get a subtle red top-border accent.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Vantz Stockwell 2026-03-17 07:27:54 -04:00
parent 95c2368ab5
commit e11f6bc6a2

View File

@ -6,21 +6,50 @@
v-for="session in sessionStore.sessions" v-for="session in sessionStore.sessions"
:key="session.id" :key="session.id"
class="group flex items-center gap-2 px-3 h-9 text-xs whitespace-nowrap border-r border-[var(--wraith-border)] transition-all duration-500 cursor-pointer shrink-0" class="group flex items-center gap-2 px-3 h-9 text-xs whitespace-nowrap border-r border-[var(--wraith-border)] transition-all duration-500 cursor-pointer shrink-0"
:class=" :class="[
session.id === sessionStore.activeSessionId session.id === sessionStore.activeSessionId
? 'bg-[var(--wraith-bg-primary)] text-[var(--wraith-text-primary)] border-b-2 border-b-[var(--wraith-accent-blue)]' ? 'bg-[var(--wraith-bg-primary)] text-[var(--wraith-text-primary)] border-b-2 border-b-[var(--wraith-accent-blue)]'
: 'text-[var(--wraith-text-muted)] hover:text-[var(--wraith-text-secondary)] hover:bg-[var(--wraith-bg-tertiary)]' : 'text-[var(--wraith-text-muted)] hover:text-[var(--wraith-text-secondary)] hover:bg-[var(--wraith-bg-tertiary)]',
" isRootUser(session) ? 'border-t-2 border-t-[#f8514966]' : '',
]"
@click="sessionStore.activateSession(session.id)" @click="sessionStore.activateSession(session.id)"
> >
<!-- Protocol dot --> <!-- Protocol icon -->
<span <span class="shrink-0">
class="w-2 h-2 rounded-full shrink-0" <!-- SSH terminal icon -->
:class="session.protocol === 'ssh' ? 'bg-[#3fb950]' : 'bg-[#1f6feb]'" <svg
/> v-if="session.protocol === 'ssh'"
class="w-3.5 h-3.5 text-[#3fb950]"
viewBox="0 0 16 16"
fill="currentColor"
>
<path d="M0 2.75C0 1.784.784 1 1.75 1h12.5c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 14.25 15H1.75A1.75 1.75 0 0 1 0 13.25Zm1.75-.25a.25.25 0 0 0-.25.25v10.5c0 .138.112.25.25.25h12.5a.25.25 0 0 0 .25-.25V2.75a.25.25 0 0 0-.25-.25ZM7.25 8a.749.749 0 0 1-.22.53l-2.25 2.25a.749.749 0 1 1-1.06-1.06L5.44 8 3.72 6.28a.749.749 0 1 1 1.06-1.06l2.25 2.25c.141.14.22.331.22.53Zm1.5 1.5h3a.75.75 0 0 1 0 1.5h-3a.75.75 0 0 1 0-1.5Z" />
</svg>
<!-- RDP monitor icon -->
<svg
v-else
class="w-3.5 h-3.5 text-[#1f6feb]"
viewBox="0 0 16 16"
fill="currentColor"
>
<path d="M1.75 2.5h12.5a.25.25 0 0 1 .25.25v7.5a.25.25 0 0 1-.25.25H1.75a.25.25 0 0 1-.25-.25v-7.5a.25.25 0 0 1 .25-.25ZM14.25 1H1.75A1.75 1.75 0 0 0 0 2.75v7.5C0 11.216.784 12 1.75 12h4.388l-.533 1.5H4a.75.75 0 0 0 0 1.5h8a.75.75 0 0 0 0-1.5h-1.605l-.533-1.5h4.388A1.75 1.75 0 0 0 16 10.25v-7.5A1.75 1.75 0 0 0 14.25 1ZM9.112 13.5H6.888l.533-1.5h1.158l.533 1.5Z" />
</svg>
</span>
<span>{{ session.name }}</span> <span>{{ session.name }}</span>
<!-- Environment tag badges -->
<template v-if="getSessionTags(session).length > 0">
<span
v-for="tag in getSessionTags(session)"
:key="tag"
class="px-1 py-0.5 text-[9px] font-semibold rounded leading-none"
:class="tagClass(tag)"
>
{{ tag }}
</span>
</template>
<!-- Close button --> <!-- Close button -->
<span <span
class="ml-1 opacity-0 group-hover:opacity-100 hover:text-[var(--wraith-accent-red)] transition-opacity" class="ml-1 opacity-0 group-hover:opacity-100 hover:text-[var(--wraith-accent-red)] transition-opacity"
@ -42,7 +71,43 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useSessionStore } from "@/stores/session.store"; import { useSessionStore, type Session } from "@/stores/session.store";
import { useConnectionStore } from "@/stores/connection.store";
const sessionStore = useSessionStore(); const sessionStore = useSessionStore();
const connectionStore = useConnectionStore();
/** Get tags for a session's underlying connection. */
function getSessionTags(session: Session): string[] {
const conn = connectionStore.connections.find((c) => c.id === session.connectionId);
return conn?.tags ?? [];
}
/** Check if the connection for this session uses the root user. */
function isRootUser(session: Session): boolean {
const conn = connectionStore.connections.find((c) => c.id === session.connectionId);
if (!conn) return false;
// TODO: Get actual username from the credential or session
// For now, check mock data root user detection will come from the session/credential store
return false;
}
/** Return Tailwind classes for environment tag badges. */
function tagClass(tag: string): string {
const t = tag.toUpperCase();
if (t === "PROD" || t === "PRODUCTION") {
return "bg-[#da3633]/20 text-[#f85149]";
}
if (t === "DEV" || t === "DEVELOPMENT") {
return "bg-[#238636]/20 text-[#3fb950]";
}
if (t === "STAGING" || t === "STG") {
return "bg-[#9e6a03]/20 text-[#d29922]";
}
if (t === "TEST" || t === "QA") {
return "bg-[#1f6feb]/20 text-[#58a6ff]";
}
// Default for other tags
return "bg-[var(--wraith-bg-tertiary)] text-[var(--wraith-text-muted)]";
}
</script> </script>