-
Notifications
You must be signed in to change notification settings - Fork 36
Expand file tree
/
Copy pathTopHeader.vue
More file actions
159 lines (143 loc) · 5.7 KB
/
Copy pathTopHeader.vue
File metadata and controls
159 lines (143 loc) · 5.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
<template>
<header class="bg-base-100 border-b border-base-300 sticky top-0 z-30 overflow-x-hidden">
<div class="flex items-center justify-between px-6 py-4 max-w-full overflow-x-hidden">
<!-- Left: Mobile menu toggle + Search + Add Server -->
<div class="flex items-center space-x-3 flex-1 min-w-0 overflow-x-hidden">
<!-- Mobile menu toggle -->
<label for="sidebar-drawer" class="btn btn-ghost btn-square lg:hidden">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
</label>
<!-- Search Box with Button -->
<div class="flex items-center space-x-2 flex-1 max-w-2xl min-w-0">
<div class="relative flex-1">
<input
type="text"
placeholder="Search tools, servers..."
class="input input-bordered w-full pr-3"
v-model="searchQuery"
@keydown.enter="handleSearch"
/>
</div>
<button
@click="handleSearch"
class="btn btn-primary"
:disabled="!searchQuery.trim()"
>
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
<span class="hidden sm:inline ml-2">Search</span>
</button>
</div>
<!-- Add Server Button -->
<button @click="showAddServerModal = true" class="btn btn-primary">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
</svg>
<span class="hidden sm:inline ml-2">{{ addServerLabel }}</span>
</button>
</div>
<!-- Right: Stats + Proxy Info -->
<div class="hidden md:flex items-center space-x-3 flex-shrink-0">
<!-- Servers -->
<div class="flex items-center space-x-2 px-3 py-2 bg-base-200 rounded-lg text-sm">
<div
:class="[
'w-2 h-2 rounded-full',
systemStore.isRunning ? 'bg-success animate-pulse' : 'bg-error'
]"
/>
<span class="font-bold">{{ serversStore.serverCount.connected }}</span>
<span class="opacity-60">/</span>
<span>{{ serversStore.serverCount.total }}</span>
<span class="text-xs opacity-60">Servers</span>
</div>
<!-- Tools -->
<div class="flex items-center space-x-2 px-3 py-2 bg-base-200 rounded-lg text-sm">
<span class="font-bold">{{ serversStore.totalTools }}</span>
<span class="text-xs opacity-60">Tools</span>
</div>
<!-- Routing Mode -->
<div class="flex items-center space-x-2 px-3 py-2 bg-base-200 rounded-lg text-sm">
<span class="text-xs opacity-60">Mode:</span>
<span class="font-medium">{{ routingModeLabel }}</span>
</div>
<!-- Proxy Address with Copy -->
<div v-if="systemStore.listenAddr" class="flex items-center space-x-2 px-3 py-2 bg-base-200 rounded-lg">
<span class="text-xs font-medium opacity-60">Proxy:</span>
<code class="text-xs font-mono">{{ systemStore.listenAddr }}</code>
<button
@click="copyAddress"
class="btn btn-ghost btn-xs p-1 tooltip"
:data-tip="copyTooltip"
>
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
</svg>
</button>
</div>
</div>
</div>
<!-- Add Server Modal -->
<AddServerModal
:show="showAddServerModal"
@close="showAddServerModal = false"
@added="handleServerAdded"
/>
</header>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useRouter } from 'vue-router'
import { useSystemStore } from '@/stores/system'
import { useServersStore } from '@/stores/servers'
import { useAuthStore } from '@/stores/auth'
import AddServerModal from './AddServerModal.vue'
const router = useRouter()
const systemStore = useSystemStore()
const serversStore = useServersStore()
const authStore = useAuthStore()
const addServerLabel = computed(() => authStore.isTeamsEdition ? 'Add Personal Server' : 'Add Server')
const routingModeLabel = computed(() => {
const mode = systemStore.routingMode
switch (mode) {
case 'direct':
return 'Direct'
case 'code_execution':
return 'Code Exec'
default:
return 'Retrieve'
}
})
const searchQuery = ref('')
const copyTooltip = ref('Copy MCP address')
const showAddServerModal = ref(false)
async function copyAddress() {
const address = systemStore.listenAddr
if (!address) return
try {
await navigator.clipboard.writeText(`http://${address}/mcp`)
copyTooltip.value = 'Copied!'
setTimeout(() => {
copyTooltip.value = 'Copy MCP address'
}, 2000)
} catch (err) {
console.error('Failed to copy to clipboard:', err)
copyTooltip.value = 'Failed to copy'
setTimeout(() => {
copyTooltip.value = 'Copy MCP address'
}, 2000)
}
}
function handleSearch() {
if (searchQuery.value.trim()) {
router.push({ path: '/search', query: { q: searchQuery.value } })
}
}
function handleServerAdded() {
// Refresh servers list after adding
serversStore.fetchServers()
}
</script>