Theme service: 7 built-in themes seeded from Rust, ThemePicker loads from backend. Workspace service: save/load snapshots, crash recovery detection. SettingsModal: full port with Tauri invoke. CommandPalette, HostKeyDialog, ConnectionEditDialog all ported. CodeMirror editor: inline above terminal, reads/writes via SFTP. Full keyboard shortcuts: Ctrl+K/W/Tab/Shift+Tab/1-9/B/F. Terminal search bar via Ctrl+F (SearchAddon). Tab badges: protocol dots, ROOT warning, environment pills. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
79 lines
2.9 KiB
Vue
79 lines
2.9 KiB
Vue
<template>
|
|
<div class="flex items-center bg-[var(--wraith-bg-secondary)] border-b border-[var(--wraith-border)] h-9 shrink-0">
|
|
<!-- Tabs -->
|
|
<div class="flex items-center overflow-x-auto min-w-0">
|
|
<button
|
|
v-for="session in sessionStore.sessions"
|
|
:key="session.id"
|
|
class="group flex items-center gap-1.5 px-3 h-9 text-xs whitespace-nowrap border-r border-[var(--wraith-border)] transition-all duration-500 cursor-pointer shrink-0"
|
|
:class="[
|
|
session.id === sessionStore.activeSessionId
|
|
? '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)]',
|
|
isRootUser(session) ? 'border-t-2 border-t-[#f8514966]' : '',
|
|
]"
|
|
@click="sessionStore.activateSession(session.id)"
|
|
>
|
|
<!-- Badge: protocol dot + root dot + env pills -->
|
|
<TabBadge
|
|
:protocol="session.protocol"
|
|
:username="session.username"
|
|
:tags="getSessionTags(session)"
|
|
/>
|
|
|
|
<span>{{ session.name }}</span>
|
|
|
|
<!-- Close button -->
|
|
<span
|
|
class="ml-1 opacity-0 group-hover:opacity-100 hover:text-[var(--wraith-accent-red)] transition-opacity"
|
|
@click.stop="sessionStore.closeSession(session.id)"
|
|
>
|
|
×
|
|
</span>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- New tab button -->
|
|
<button
|
|
class="flex items-center justify-center w-9 h-9 text-[var(--wraith-text-muted)] hover:text-[var(--wraith-text-primary)] hover:bg-[var(--wraith-bg-tertiary)] transition-colors cursor-pointer shrink-0"
|
|
title="New session"
|
|
>
|
|
+
|
|
</button>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { useSessionStore, type Session } from "@/stores/session.store";
|
|
import { useConnectionStore } from "@/stores/connection.store";
|
|
import TabBadge from "@/components/session/TabBadge.vue";
|
|
|
|
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 (drives the orange top border). */
|
|
function isRootUser(session: Session): boolean {
|
|
if (session.username === "root" || session.username === "Administrator") return true;
|
|
|
|
const conn = connectionStore.connections.find((c) => c.id === session.connectionId);
|
|
if (!conn) return false;
|
|
|
|
if (conn.options) {
|
|
try {
|
|
const opts = JSON.parse(conn.options);
|
|
if (opts?.username === "root" || opts?.username === "Administrator") return true;
|
|
} catch {
|
|
// ignore malformed options
|
|
}
|
|
}
|
|
|
|
return conn.tags?.some((t) => t === "root" || t === "Administrator") ?? false;
|
|
}
|
|
</script>
|