98 lines
2.8 KiB
Vue
98 lines
2.8 KiB
Vue
<script setup lang="ts">
|
|
interface Host {
|
|
id: number
|
|
name: string
|
|
hostname: string
|
|
port: number
|
|
protocol: 'ssh' | 'rdp'
|
|
color: string | null
|
|
}
|
|
|
|
interface HostGroup {
|
|
id: number
|
|
name: string
|
|
parentId: number | null
|
|
children: HostGroup[]
|
|
hosts: Host[]
|
|
}
|
|
|
|
const props = defineProps<{
|
|
groups: HostGroup[]
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
(e: 'select-host', host: Host): void
|
|
(e: 'new-host', groupId?: number): void
|
|
}>()
|
|
|
|
const expanded = ref<Set<number>>(new Set())
|
|
|
|
function toggleGroup(id: number) {
|
|
if (expanded.value.has(id)) {
|
|
expanded.value.delete(id)
|
|
} else {
|
|
expanded.value.add(id)
|
|
}
|
|
}
|
|
|
|
function isExpanded(id: number) {
|
|
return expanded.value.has(id)
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="flex-1 overflow-y-auto text-sm">
|
|
<!-- Groups -->
|
|
<template v-for="group in groups" :key="group.id">
|
|
<div>
|
|
<!-- Group header -->
|
|
<div
|
|
class="flex items-center gap-1 px-3 py-1.5 cursor-pointer hover:bg-gray-800 text-gray-400 hover:text-gray-200 select-none"
|
|
@click="toggleGroup(group.id)"
|
|
>
|
|
<span class="text-xs w-3">{{ isExpanded(group.id) ? '▾' : '▸' }}</span>
|
|
<span class="font-medium truncate flex-1">{{ group.name }}</span>
|
|
<button
|
|
@click.stop="emit('new-host', group.id)"
|
|
class="text-xs text-gray-600 hover:text-wraith-400 px-1"
|
|
title="Add host to group"
|
|
>+</button>
|
|
</div>
|
|
|
|
<!-- Group children (hosts + sub-groups) -->
|
|
<div v-if="isExpanded(group.id)" class="pl-3">
|
|
<!-- Sub-groups recursively -->
|
|
<HostTree
|
|
v-if="group.children?.length"
|
|
:groups="group.children"
|
|
@select-host="(h) => emit('select-host', h)"
|
|
@new-host="(gid) => emit('new-host', gid)"
|
|
/>
|
|
|
|
<!-- Hosts in this group -->
|
|
<div
|
|
v-for="host in group.hosts"
|
|
:key="host.id"
|
|
class="flex items-center gap-2 px-3 py-1 cursor-pointer hover:bg-gray-800 text-gray-300 hover:text-white"
|
|
@click="emit('select-host', host)"
|
|
>
|
|
<span
|
|
class="w-2 h-2 rounded-full shrink-0"
|
|
:style="host.color ? { backgroundColor: host.color } : {}"
|
|
:class="!host.color ? 'bg-gray-600' : ''"
|
|
/>
|
|
<span class="truncate flex-1">{{ host.name }}</span>
|
|
<span
|
|
class="text-xs px-1 rounded"
|
|
:class="host.protocol === 'rdp' ? 'text-purple-400 bg-purple-900/30' : 'text-wraith-400 bg-wraith-900/30'"
|
|
>{{ host.protocol.toUpperCase() }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<!-- Ungrouped hosts (shown at root level when no groups) -->
|
|
<div v-if="!groups.length" class="px-3 py-2 text-gray-600 text-xs">No groups yet</div>
|
|
</div>
|
|
</template>
|