Skip to content

Commit f4c71d0

Browse files
author
Lasim
committed
feat(frontend): add satellite selection step in installation wizard
1 parent 5c87421 commit f4c71d0

File tree

4 files changed

+485
-47
lines changed

4 files changed

+485
-47
lines changed

services/frontend/src/components/mcp-server/wizard/McpServerInstallWizard.vue

Lines changed: 174 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@ import { toast } from 'vue-sonner'
99
import { McpInstallationService } from '@/services/mcpInstallationService'
1010
import { TeamService } from '@/services/teamService'
1111
import { McpCatalogService } from '@/services/mcpCatalogService'
12+
import { SatelliteService, type TeamSatellite } from '@/services/satelliteService'
1213
import { useEventBus } from '@/composables/useEventBus'
1314
import McpServerSelectionStep from './McpServerSelectionStep.vue'
1415
import EnvironmentVariablesStep from './EnvironmentVariablesStep.vue'
1516
import OAuthAuthorizationStep from './OAuthAuthorizationStep.vue'
16-
import PlatformSelectionStep from './PlatformSelectionStep.vue'
17+
import SatelliteSelectionStep from './SatelliteSelectionStep.vue'
1718
1819
// Props
1920
interface Props {
@@ -53,6 +54,7 @@ interface InstallationFormData {
5354
}
5455
platform: {
5556
installation_type: string
57+
satellite_id: string
5658
installation_id?: string
5759
platform_config?: any
5860
}
@@ -69,6 +71,10 @@ const environmentValidation = ref({
6971
const environmentStepTouched = ref(false)
7072
const currentTeamId = ref<string | null>(null)
7173
74+
// Satellite selection state
75+
const satellites = ref<TeamSatellite[]>([])
76+
const isFetchingSatellites = ref(false)
77+
7278
// Form data with proper initialization
7379
const formData = ref<InstallationFormData>({
7480
server: {
@@ -83,7 +89,8 @@ const formData = ref<InstallationFormData>({
8389
user_url_query_params: {}
8490
},
8591
platform: {
86-
installation_type: 'global'
92+
installation_type: 'global',
93+
satellite_id: ''
8794
}
8895
})
8996
@@ -92,25 +99,35 @@ const requiresOAuth = computed(() => {
9299
return formData.value.server.server_data?.requires_oauth === true
93100
})
94101
102+
// Computed property to determine if satellite step should be shown
103+
const shouldShowSatelliteStep = computed(() => {
104+
return satellites.value.length > 1
105+
})
106+
95107
// Progress steps for DsProgressSteps component
96108
const progressSteps = computed<ProgressStep[]>(() => {
97109
// Skip the first step (server selection) for progress display
98-
const wizardSteps = [
99-
{
110+
const wizardSteps = []
111+
112+
// Step 1: Satellite Selection (only if multiple satellites)
113+
if (shouldShowSatelliteStep.value) {
114+
wizardSteps.push({
100115
id: 1,
101-
title: t('mcpInstallations.wizard.steps.selectPlatform'),
102-
description: t('mcpInstallations.wizard.platform.helpText')
103-
},
104-
{
105-
id: 2,
106-
title: requiresOAuth.value
107-
? 'OAuth Authorization'
108-
: t('mcpInstallations.wizard.steps.configureEnvironment'),
109-
description: requiresOAuth.value
110-
? 'Authorize access to your account'
111-
: t('mcpInstallations.wizard.environment.helpText')
112-
}
113-
]
116+
title: t('mcpInstallations.wizard.satellite.title'),
117+
description: t('mcpInstallations.wizard.satellite.description')
118+
})
119+
}
120+
121+
// Step 2: Environment or OAuth
122+
wizardSteps.push({
123+
id: shouldShowSatelliteStep.value ? 2 : 1,
124+
title: requiresOAuth.value
125+
? 'OAuth Authorization'
126+
: t('mcpInstallations.wizard.steps.configureEnvironment'),
127+
description: requiresOAuth.value
128+
? 'Authorize access to your account'
129+
: t('mcpInstallations.wizard.environment.helpText')
130+
})
114131
115132
return wizardSteps
116133
})
@@ -119,8 +136,15 @@ const progressSteps = computed<ProgressStep[]>(() => {
119136
const completedSteps = computed(() => {
120137
const completed: number[] = []
121138
122-
// Environment step (index 0 in progress, step 1 in wizard)
123-
if (currentStep.value > 1) {
139+
// If satellite step is shown and we've passed it
140+
if (shouldShowSatelliteStep.value && currentStep.value > 1) {
141+
completed.push(0) // Satellite step completed
142+
}
143+
144+
// If we're on the final step (environment/oauth)
145+
if (shouldShowSatelliteStep.value && currentStep.value > 2) {
146+
completed.push(1)
147+
} else if (!shouldShowSatelliteStep.value && currentStep.value > 1) {
124148
completed.push(0)
125149
}
126150
@@ -130,13 +154,23 @@ const completedSteps = computed(() => {
130154
// Current progress step (adjusted for skipped server selection)
131155
const currentProgressStep = computed(() => {
132156
if (currentStep.value === 0) return -1 // Server selection, no progress shown
133-
return currentStep.value - 1 // Adjust for 0-based progress index
157+
158+
if (shouldShowSatelliteStep.value) {
159+
// With satellite step: Step 1 = satellite (progress 0), Step 2 = environment (progress 1)
160+
return currentStep.value - 1
161+
} else {
162+
// Without satellite step: Step 1 = environment (progress 0)
163+
return currentStep.value - 1
164+
}
134165
})
135166
136167
// Additional computed properties
137-
const totalSteps = 3 // Server selection (0), Environment (1), Platform (2)
168+
const totalSteps = computed(() => {
169+
// Server selection (0) + Satellite (1, conditional) + Environment/OAuth (2 or 1)
170+
return shouldShowSatelliteStep.value ? 3 : 2
171+
})
138172
const isFirstStep = computed(() => currentStep.value === 0)
139-
const isLastStep = computed(() => currentStep.value === totalSteps - 1)
173+
const isLastStep = computed(() => currentStep.value === totalSteps.value - 1)
140174
const canGoNext = computed(() => !isLastStep.value)
141175
const canGoPrevious = computed(() => !isFirstStep.value)
142176
@@ -162,8 +196,11 @@ const canSubmit = computed(() => {
162196
})
163197
164198
const nextStep = () => {
199+
// Determine which step we're on based on whether satellite step is shown
200+
const environmentStepIndex = shouldShowSatelliteStep.value ? 2 : 1
201+
165202
// Mark environment step as touched when user tries to proceed from it
166-
if (currentStep.value === 1) {
203+
if (currentStep.value === environmentStepIndex) {
167204
environmentStepTouched.value = true
168205
169206
// Check validation before proceeding
@@ -195,10 +232,11 @@ const previousStep = () => {
195232
formData.value.environment.team_env = {}
196233
formData.value.environment.user_env = {}
197234
environmentStepTouched.value = false
198-
} else if (currentStep.value === 1) {
199-
// Going back to environment step - clear platform data
200-
formData.value.platform.installation_type = 'global'
201-
formData.value.platform.platform_config = undefined
235+
} else if (shouldShowSatelliteStep.value && currentStep.value === 1) {
236+
// Going back to satellite step - clear satellite selection
237+
formData.value.platform.satellite_id = ''
238+
} else if (!shouldShowSatelliteStep.value && currentStep.value === 1) {
239+
// No satellite step, going back to server selection (step 0) - already handled above
202240
}
203241
}
204242
}
@@ -207,6 +245,33 @@ const handleCancel = () => {
207245
emit('cancel')
208246
}
209247
248+
// Fetch available satellites for the team
249+
const fetchSatellites = async () => {
250+
if (!currentTeamId.value) {
251+
return
252+
}
253+
254+
try {
255+
isFetchingSatellites.value = true
256+
const response = await SatelliteService.getTeamSatellites(currentTeamId.value)
257+
satellites.value = response.data.satellites
258+
259+
// Auto-select satellite if only one is available
260+
if (satellites.value.length === 1) {
261+
formData.value.platform.satellite_id = satellites.value[0]!.id
262+
}
263+
} catch (error) {
264+
const errorMessage = error instanceof Error ? error.message : 'Failed to fetch satellites'
265+
toast.error(t('mcpInstallations.wizard.satellite.errorFetching'), {
266+
description: errorMessage
267+
})
268+
// Set empty array on error to allow wizard to continue
269+
satellites.value = []
270+
} finally {
271+
isFetchingSatellites.value = false
272+
}
273+
}
274+
210275
// Initialize team context from event bus storage
211276
const initializeTeamContext = async () => {
212277
try {
@@ -538,6 +603,9 @@ onMounted(async () => {
538603
// Initialize team context
539604
await initializeTeamContext()
540605
606+
// Fetch available satellites for the team
607+
await fetchSatellites()
608+
541609
// Handle query parameters for pre-selection
542610
await handleQueryParameters()
543611
@@ -550,7 +618,7 @@ onMounted(async () => {
550618
formData.value = {
551619
server: { server_id: '' },
552620
environment: { team_args: [], team_env: {}, team_headers: {}, team_url_query_params: {}, user_env: {}, user_url_query_params: {} },
553-
platform: { installation_type: 'global' }
621+
platform: { installation_type: 'global', satellite_id: '' }
554622
}
555623
})
556624
})
@@ -628,34 +696,93 @@ onUnmounted(() => {
628696
:completed-steps="completedSteps"
629697
max-width="max-w-3xl"
630698
>
631-
<!-- Step 1 Content: Platform Selection -->
699+
<!-- Step Content 0: Satellite Selection if shown, otherwise Environment/OAuth -->
632700
<template #step-content-0>
633-
<PlatformSelectionStep
634-
v-model="formData.platform.installation_type"
635-
/>
636-
637-
<!-- Navigation Buttons for Platform Step -->
638-
<div class="flex items-center justify-between mt-6">
639-
<Button variant="outline" @click="previousStep">
640-
{{ t('navigation.previous') }}
641-
</Button>
701+
<!-- Satellite Selection (only if multiple satellites) -->
702+
<div v-if="shouldShowSatelliteStep">
703+
<SatelliteSelectionStep
704+
v-model="formData.platform.satellite_id"
705+
:satellites="satellites"
706+
:is-loading="isFetchingSatellites"
707+
/>
642708

643-
<div class="flex items-center gap-2">
644-
<Button variant="ghost" @click="handleCancel">
645-
{{ t('navigation.cancel') }}
709+
<!-- Navigation Buttons for Satellite Step -->
710+
<div class="flex items-center justify-between mt-6">
711+
<Button variant="outline" @click="previousStep">
712+
{{ t('navigation.previous') }}
646713
</Button>
647714

648-
<Button
649-
@click="nextStep"
650-
:disabled="!formData.platform.installation_type"
651-
>
652-
{{ t('navigation.next') }}
715+
<div class="flex items-center gap-2">
716+
<Button variant="ghost" @click="handleCancel">
717+
{{ t('navigation.cancel') }}
718+
</Button>
719+
720+
<Button
721+
@click="nextStep"
722+
:disabled="!formData.platform.satellite_id"
723+
>
724+
{{ t('navigation.next') }}
725+
</Button>
726+
</div>
727+
</div>
728+
</div>
729+
730+
<!-- Environment/OAuth Step (when satellite step is hidden) -->
731+
<div v-else>
732+
<!-- OAuth Authorization Step (if OAuth required) -->
733+
<OAuthAuthorizationStep
734+
v-if="requiresOAuth"
735+
:server-data="formData.server.server_data"
736+
:is-authorizing="isSubmitting"
737+
@authorize="handleOAuthAuthorization"
738+
/>
739+
740+
<!-- Environment Variables Step (if OAuth NOT required) -->
741+
<EnvironmentVariablesStep
742+
v-else
743+
v-model="formData.environment"
744+
:server-data="formData.server.server_data"
745+
@validation-change="handleValidationChange"
746+
/>
747+
748+
<!-- Navigation Buttons -->
749+
<div class="flex items-center justify-between mt-6">
750+
<Button variant="outline" @click="previousStep">
751+
{{ t('navigation.previous') }}
653752
</Button>
753+
754+
<div class="flex items-center gap-2">
755+
<Button variant="ghost" @click="handleCancel">
756+
{{ t('navigation.cancel') }}
757+
</Button>
758+
759+
<!-- OAuth: "Authorize & Install" button -->
760+
<Button
761+
v-if="requiresOAuth"
762+
@click="handleOAuthAuthorization"
763+
:loading="isSubmitting"
764+
:loading-text="t('mcpInstallations.wizard.authorizing')"
765+
:disabled="!formData.platform.installation_type"
766+
>
767+
{{ t('mcpInstallations.wizard.authorizeAndInstall') }}
768+
</Button>
769+
770+
<!-- Non-OAuth: "Install" button -->
771+
<Button
772+
v-else
773+
@click="submitInstallation"
774+
:disabled="!canSubmit"
775+
:loading="isSubmitting"
776+
:loading-text="t('mcpInstallations.wizard.installing')"
777+
>
778+
{{ t('mcpInstallations.wizard.install') }}
779+
</Button>
780+
</div>
654781
</div>
655782
</div>
656783
</template>
657784

658-
<!-- Step 2 Content: Environment Variables OR OAuth Authorization -->
785+
<!-- Environment/OAuth Step when satellite step is shown -->
659786
<template #step-content-1>
660787
<!-- OAuth Authorization Step (if OAuth required) -->
661788
<OAuthAuthorizationStep

0 commit comments

Comments
 (0)