Skip to content

Commit b22b851

Browse files
authored
Merge pull request #62 from rewenila/feat/add-namesoace-dropdown
Add dropdown for namespace selection on App Wizard
2 parents ec4465f + 9817da7 commit b22b851

File tree

3 files changed

+69
-14
lines changed

3 files changed

+69
-14
lines changed

pkg/suse-ai-lifecycle-manager/pages/components/AppWizard.vue

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ import {
2323
getClusters,
2424
getInstalledHelmDetails,
2525
inferClusterRepoForChart,
26-
listClusterRepos
26+
listClusterRepos,
27+
listNamespaces
2728
} from '../../services/rancher-apps';
2829
import { getRepoAuthForClusterRepo } from '../../services/repo-auth';
2930
import { persistLoad, persistSave, persistClear } from '../../services/ui-persist';
@@ -67,6 +68,7 @@ const versionInfo = ref<any | null>(null);
6768
const questionsLoading = ref(false);
6869
const versionInfoKey = ref('');
6970
const defaultValuesSnapshot = ref<Record<string, any>>({});
71+
const namespaceOptions = ref<{label: string, value: string}[]>([]);
7072
7173
// Multi-cluster install progress state
7274
const showProgressModal = ref(false);
@@ -93,6 +95,52 @@ const versionOptions = computed(() =>
9395
(versions.value || []).map(v => ({ label: v, value: v }))
9496
);
9597
98+
// Fetch all namespaces from all clusters and filter out system namespaces
99+
async function fetchAllNamespaces() {
100+
if (!store) {
101+
return;
102+
}
103+
104+
try {
105+
const clusters = await getClusters(store);
106+
console.log('[SUSE-AI] All available clusters:', clusters);
107+
108+
const allNamespaces = new Set<string>();
109+
110+
await Promise.all(clusters.map(async (cluster) => {
111+
console.log('[SUSE-AI] Trying to get namespaces for cluster:', cluster);
112+
try {
113+
const namespaces = await listNamespaces(store, cluster.id);
114+
namespaces.forEach(ns => allNamespaces.add(ns));
115+
} catch (e) {
116+
console.warn(`[SUSE-AI] Failed to fetch namespaces for cluster ${cluster.id}:`, e);
117+
}
118+
}));
119+
120+
console.log('[SUSE-AI] Found all unique namespaces:', allNamespaces);
121+
122+
const systemPrefixes = ['c-', 'p-', 'kube-', 'cattle-', 'rancher', 'longhorn-', 'fleet-', 'cluster-fleet-', 'system-', 'istio-', 'neuvector', 'ingress-', 'cert-manager'];
123+
const userNamespaces = Array.from(allNamespaces).filter(name =>
124+
!systemPrefixes.some(prefix => name.startsWith(prefix))
125+
);
126+
127+
const desiredDefault = `${props.slug}-system`;
128+
if (!userNamespaces.includes(desiredDefault)) {
129+
userNamespaces.push(desiredDefault);
130+
}
131+
132+
const sortedNamespaces = userNamespaces.sort();
133+
namespaceOptions.value = sortedNamespaces.map(ns => ({ label: ns, value: ns }));
134+
135+
if (isInstallMode.value) {
136+
form.value.namespace = desiredDefault;
137+
}
138+
139+
} catch (e) {
140+
console.warn(`[SUSE-AI] Failed to fetch all namespaces:`, e);
141+
}
142+
}
143+
96144
// Wizard step configuration for Rancher Wizard component
97145
const wizardSteps = computed(() => [
98146
{
@@ -184,6 +232,7 @@ const basicInfoForm = computed({
184232
onMounted(async () => {
185233
try {
186234
await initializeWizard();
235+
await fetchAllNamespaces();
187236
} catch (e) {
188237
error.value = `Failed to initialize: ${e.message || 'Unknown error'}`;
189238
} finally {
@@ -969,6 +1018,7 @@ function previousStep() {
9691018
v-model:form="basicInfoForm"
9701019
:version-options="versionOptions"
9711020
:loading-versions="loadingVersions"
1021+
:namespace-options="namespaceOptions"
9721022
/>
9731023

9741024
<!-- Step: Target Cluster -->

pkg/suse-ai-lifecycle-manager/pages/components/wizard/BasicInfoStep.vue

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,15 @@
1010
/>
1111
</div>
1212
<div class="col span-6">
13-
<LabeledInput
13+
<LabeledSelect
1414
v-model:value="namespace"
1515
:label="t('suseai.wizard.form.namespace', 'Namespace')"
16-
:placeholder="t('suseai.wizard.form.namespacePlaceholder', 'Enter namespace')"
17-
required
16+
:options="namespaceOptions"
17+
:placeholder="t('suseai.wizard.form.namespacePlaceholder', 'Select or create a namespace')"
18+
:taggable="true"
19+
:searchable="true"
20+
:clearable="false"
21+
:required="true"
1822
/>
1923
</div>
2024
</div>
@@ -68,6 +72,7 @@ interface Props {
6872
form: BasicInfoForm;
6973
versionOptions: Array<{ label: string; value: string }>;
7074
loadingVersions: boolean;
75+
namespaceOptions: Array<{ label: string; value: string }>;
7176
}
7277
7378
interface Emits {
@@ -80,12 +85,6 @@ const emit = defineEmits<Emits>();
8085
// Simple fallback function for translations
8186
const t = (key: string, fallback: string) => fallback;
8287
83-
// Create individual computed properties for better reactivity
84-
const localForm = computed({
85-
get: () => props.form,
86-
set: (value: BasicInfoForm) => emit('update:form', value)
87-
});
88-
8988
// Individual field computeds for better reactivity
9089
const release = computed({
9190
get: () => props.form.release,
@@ -94,7 +93,10 @@ const release = computed({
9493
9594
const namespace = computed({
9695
get: () => props.form.namespace,
97-
set: (value: string) => emit('update:form', { ...props.form, namespace: value })
96+
set: (value: string | { label: string }) => {
97+
const namespaceName = typeof value === 'object' ? value.label : value;
98+
emit('update:form', { ...props.form, namespace: namespaceName });
99+
}
98100
});
99101
100102
const chartRepo = computed({

pkg/suse-ai-lifecycle-manager/services/rancher-apps.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -362,10 +362,13 @@ export async function listCatalogApps($store: RancherStore, clusterId: string):
362362
return res?.data?.items || res?.data || res?.items || [];
363363
}
364364

365-
async function listNamespaces($store: RancherStore, clusterId: string): Promise<string[]> {
366-
const url = `/k8s/clusters/${encodeURIComponent(clusterId)}/api/v1/namespaces?limit=5000`;
365+
export async function listNamespaces($store: RancherStore, clusterId: string): Promise<string[]> {
366+
const url = clusterId === 'local'
367+
? '/api/v1/namespaces?limit=5000'
368+
: `/k8s/clusters/${encodeURIComponent(clusterId)}/api/v1/namespaces?limit=5000`;
367369
const res = await $store.dispatch('rancher/request', { url, timeout: 20000 });
368-
const items = res?.data?.items || res?.data || [];
370+
const items = res?.data?.items || res?.data || res?.items || [];
371+
369372
return (items || []).map((n: NamespaceResource) => n?.metadata?.name).filter((n: string) => !!n);
370373
}
371374

0 commit comments

Comments
 (0)