Skip to content

Commit 1d2e617

Browse files
author
Lasim
committed
feat(frontend): load supported clients and update client selection modal
1 parent f069cbe commit 1d2e617

File tree

3 files changed

+124
-16
lines changed

3 files changed

+124
-16
lines changed

services/frontend/src/components/gateway-config/ClientConfigurationModal.vue

Lines changed: 98 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,44 @@ const emit = defineEmits<{
3434
const { t } = useI18n()
3535
3636
// State
37-
const selectedClient = ref('claude-desktop')
37+
const selectedClient = ref('')
3838
const configContent = ref('')
3939
const isLoading = ref(false)
4040
const isCopying = ref(false)
41+
const supportedClients = ref<string[]>([])
42+
const isLoadingClients = ref(false)
43+
44+
// Load supported clients when modal opens
45+
watch(() => props.open, async (isOpen) => {
46+
if (isOpen && supportedClients.value.length === 0) {
47+
await loadSupportedClients()
48+
}
49+
})
4150
4251
// Load configuration when client changes
4352
watch(selectedClient, async (newClient) => {
4453
if (newClient) {
4554
await loadConfiguration(newClient)
4655
}
47-
}, { immediate: true })
56+
})
57+
58+
// Load supported clients from API
59+
async function loadSupportedClients() {
60+
isLoadingClients.value = true
61+
try {
62+
const clients = await GatewayConfigService.getSupportedClients()
63+
supportedClients.value = clients
64+
// Set first client as default if no client is selected
65+
if (!selectedClient.value && clients.length > 0) {
66+
selectedClient.value = clients[0]
67+
}
68+
} catch (error) {
69+
const errorMessage = error instanceof Error ? error.message : 'Failed to load supported clients'
70+
toast.error(errorMessage)
71+
} finally {
72+
isLoadingClients.value = false
73+
}
74+
}
4875
4976
// Load configuration from API
5077
async function loadConfiguration(client: string) {
@@ -61,6 +88,28 @@ async function loadConfiguration(client: string) {
6188
}
6289
}
6390
91+
// Get display name for client (with fallback)
92+
function getClientDisplayName(client: string): string {
93+
// Convert client name to camelCase for i18n key matching
94+
const clientKey = client.split('-').map((word, index) => {
95+
if (index === 0) {
96+
return word.toLowerCase()
97+
}
98+
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
99+
}).join('')
100+
101+
const translationKey = `gatewayConfig.clients.${clientKey}`
102+
const translated = t(translationKey)
103+
104+
// If translation returns the key itself, it means translation doesn't exist
105+
// Fall back to a capitalized version of the client name
106+
if (translated === translationKey) {
107+
return client.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ')
108+
}
109+
110+
return translated
111+
}
112+
64113
function handleClose() {
65114
emit('update:open', false)
66115
}
@@ -73,15 +122,15 @@ async function handleCopyAndClose() {
73122
}
74123
75124
isCopying.value = true
76-
125+
77126
try {
78127
await navigator.clipboard.writeText(configContent.value)
79128
handleClose()
80129
// Show success toast after modal closes
81130
setTimeout(() => {
82131
toast.success(t('gatewayConfig.messages.copySuccess'))
83132
}, 100)
84-
} catch (error) {
133+
} catch {
85134
toast.error('Failed to copy configuration to clipboard')
86135
} finally {
87136
isCopying.value = false
@@ -102,19 +151,53 @@ async function handleCopyAndClose() {
102151
</AlertDialogHeader>
103152

104153
<div class="space-y-6">
154+
<!-- Installation Step -->
155+
<div class="space-y-3">
156+
<label class="text-sm font-medium">First, install the DeployStack Gateway</label>
157+
<div class="rounded-lg bg-gray-900 shadow-lg border border-gray-700 overflow-hidden">
158+
<!-- Terminal Header -->
159+
<div class="flex items-center justify-between bg-gray-800 px-4 py-2 border-b border-gray-600">
160+
<div class="flex items-center space-x-2">
161+
<div class="w-3 h-3 rounded-full bg-red-500"></div>
162+
<div class="w-3 h-3 rounded-full bg-yellow-500"></div>
163+
<div class="w-3 h-3 rounded-full bg-green-500"></div>
164+
</div>
165+
<div class="text-gray-400 text-sm font-mono">
166+
~/setup
167+
</div>
168+
<div class="w-16"></div>
169+
</div>
170+
171+
<!-- Terminal Body -->
172+
<div class="p-4 bg-gray-900">
173+
<div class="font-mono text-sm">
174+
<div class="text-gray-300 mb-1">
175+
<span class="text-white">$ </span><span class="text-yellow-300">npm install -g @deploystack/gateway</span>
176+
</div>
177+
<div class="text-gray-300 mb-3">
178+
<span class="text-white">$ </span><span class="text-yellow-300">deploystack login</span>
179+
</div>
180+
</div>
181+
</div>
182+
</div>
183+
</div>
184+
105185
<!-- Client Selection Dropdown -->
106186
<div class="space-y-2">
107187
<label class="text-sm font-medium">{{ t('gatewayConfig.modal.clientLabel') }}</label>
108-
<Select v-model="selectedClient">
188+
<Select v-model="selectedClient" :disabled="isLoadingClients">
109189
<SelectTrigger class="w-full">
110-
<SelectValue :placeholder="t('gatewayConfig.modal.selectPlaceholder')" />
190+
<SelectValue
191+
:placeholder="isLoadingClients ? 'Loading clients...' : t('gatewayConfig.modal.selectPlaceholder')"
192+
/>
111193
</SelectTrigger>
112194
<SelectContent>
113-
<SelectItem value="claude-desktop">
114-
{{ t('gatewayConfig.clients.claudeDesktop') }}
115-
</SelectItem>
116-
<SelectItem value="cursor">
117-
{{ t('gatewayConfig.clients.cursor') }}
195+
<SelectItem
196+
v-for="client in supportedClients"
197+
:key="client"
198+
:value="client"
199+
>
200+
{{ getClientDisplayName(client) }}
118201
</SelectItem>
119202
</SelectContent>
120203
</Select>
@@ -127,7 +210,7 @@ async function handleCopyAndClose() {
127210
v-model="configContent"
128211
:placeholder="isLoading ? t('gatewayConfig.modal.loading') : t('gatewayConfig.modal.configPlaceholder')"
129212
:disabled="isLoading"
130-
rows="12"
213+
rows="8"
131214
class="font-mono text-sm"
132215
readonly
133216
/>
@@ -138,11 +221,11 @@ async function handleCopyAndClose() {
138221
<Button @click="handleClose" variant="outline">
139222
{{ t('actions.close') }}
140223
</Button>
141-
<Button
142-
@click="handleCopyAndClose"
224+
<Button
225+
@click="handleCopyAndClose"
143226
:loading="isCopying"
144227
loading-text="Copying..."
145-
:disabled="!configContent.trim() || isLoading"
228+
:disabled="!configContent.trim() || isLoading || isLoadingClients"
146229
>
147230
{{ t('gatewayConfig.button.copyAndClose') }}
148231
</Button>

services/frontend/src/i18n/locales/en/gatewayConfig.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ export default {
1111

1212
clients: {
1313
claudeDesktop: 'Claude Desktop',
14-
cursor: 'Cursor'
14+
cline: 'Cline',
15+
vscode: 'VS Code',
16+
cursor: 'Cursor',
17+
windsurf: 'Windsurf'
1518
},
1619

1720
button: {

services/frontend/src/services/gatewayConfigService.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
import { getEnv } from '@/utils/env'
22

3+
interface ClientsListResponse {
4+
clients: string[]
5+
}
6+
37
export class GatewayConfigService {
48
private static baseUrl = getEnv('VITE_DEPLOYSTACK_BACKEND_URL')
59

10+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
611
static async getClientConfig(client: string): Promise<any> {
712
const response = await fetch(`${this.baseUrl}/api/gateway/config/${client}`, {
813
method: 'GET',
@@ -18,4 +23,21 @@ export class GatewayConfigService {
1823

1924
return response.json()
2025
}
26+
27+
static async getSupportedClients(): Promise<string[]> {
28+
const response = await fetch(`${this.baseUrl}/api/gateway/config/clients`, {
29+
method: 'GET',
30+
headers: {
31+
'Content-Type': 'application/json',
32+
},
33+
credentials: 'include',
34+
})
35+
36+
if (!response.ok) {
37+
throw new Error('Failed to fetch supported clients list')
38+
}
39+
40+
const data: ClientsListResponse = await response.json()
41+
return data.clients
42+
}
2143
}

0 commit comments

Comments
 (0)