wraith/frontend/components/connections/HostCard.vue
Vantz Stockwell b93fe016ed feat: frontend — auth flow, connection manager UI, host tree
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 17:11:02 -04:00

98 lines
2.9 KiB
Vue

<script setup lang="ts">
interface Host {
id: number
name: string
hostname: string
port: number
protocol: 'ssh' | 'rdp'
tags: string[]
notes: string | null
color: string | null
lastConnectedAt: string | null
group: { id: number; name: string } | null
}
const props = defineProps<{
host: Host
}>()
const emit = defineEmits<{
(e: 'edit'): void
(e: 'delete'): void
(e: 'connect'): void
}>()
function formatLastConnected(ts: string | null): string {
if (!ts) return 'Never'
const d = new Date(ts)
const now = new Date()
const diffMs = now.getTime() - d.getTime()
const diffDays = Math.floor(diffMs / 86400000)
if (diffDays === 0) return 'Today'
if (diffDays === 1) return 'Yesterday'
if (diffDays < 30) return `${diffDays}d ago`
return d.toLocaleDateString()
}
</script>
<template>
<div
class="relative bg-gray-900 border border-gray-800 rounded-lg p-4 hover:border-wraith-700 transition-colors group cursor-pointer"
@click="emit('connect')"
>
<!-- Color indicator strip -->
<div
v-if="host.color"
class="absolute top-0 left-0 w-1 h-full rounded-l-lg"
:style="{ backgroundColor: host.color }"
/>
<!-- Header row -->
<div class="flex items-start justify-between gap-2 pl-2">
<div class="min-w-0 flex-1">
<h3 class="font-semibold text-white truncate">{{ host.name }}</h3>
<p class="text-sm text-gray-500 truncate">{{ host.hostname }}:{{ host.port }}</p>
</div>
<!-- Protocol badge -->
<span
class="text-xs font-medium px-2 py-0.5 rounded shrink-0"
:class="host.protocol === 'rdp'
? 'bg-purple-900/50 text-purple-300 border border-purple-800'
: 'bg-wraith-900/50 text-wraith-300 border border-wraith-800'"
>{{ host.protocol.toUpperCase() }}</span>
</div>
<!-- Group + last connected -->
<div class="mt-2 pl-2 flex items-center justify-between text-xs text-gray-600">
<span v-if="host.group" class="truncate">{{ host.group.name }}</span>
<span v-else class="italic">Ungrouped</span>
<span>{{ formatLastConnected(host.lastConnectedAt) }}</span>
</div>
<!-- Tags -->
<div v-if="host.tags?.length" class="mt-2 pl-2 flex flex-wrap gap-1">
<span
v-for="tag in host.tags"
:key="tag"
class="text-xs bg-gray-800 text-gray-400 px-1.5 py-0.5 rounded"
>{{ tag }}</span>
</div>
<!-- Action buttons (show on hover) -->
<div
class="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity flex gap-1"
@click.stop
>
<button
@click="emit('edit')"
class="text-xs bg-gray-800 hover:bg-gray-700 text-gray-400 hover:text-white px-2 py-1 rounded"
>Edit</button>
<button
@click="emit('delete')"
class="text-xs bg-gray-800 hover:bg-red-900 text-gray-400 hover:text-red-300 px-2 py-1 rounded"
>Delete</button>
</div>
</div>
</template>