+
+
+
+ Delete ({{ selectedRows.length }})
+
+
{{ t('generic.create') }}
-
+
-
-
-
-
-
-
-
-
-
-
handleSelectionChange(e, ns)"
- @navigate="handleNavigate"
- @page-change="(e: CustomEvent) => handlePageChange(e, ns)"
- />
+
+ searchQuery = e.detail.value"
+ >
-
+ goToPage(e.detail.page)"
+ @selection-change="handleSelectionChange"
+ />
+
+
-
diff --git a/dashboard/pkg/epinio/store/epinio-store/actions.ts b/dashboard/pkg/epinio/store/epinio-store/actions.ts
index 9934db5c..94e454b9 100644
--- a/dashboard/pkg/epinio/store/epinio-store/actions.ts
+++ b/dashboard/pkg/epinio/store/epinio-store/actions.ts
@@ -562,6 +562,13 @@ export default {
await dispatch('findAll', { type, opt: { force: true } });
},
+ setActiveNamespaces: async({ commit, dispatch }: any, { type, namespaces }: { type: string; namespaces: string[] | null }) => {
+ commit('setActiveNamespaces', namespaces);
+ commit('setPaginationPage', { type, page: 1 });
+ commit('clearAll', type); // clear before fetch so merge starts from empty
+ await dispatch('findAll', { type, opt: { force: true } });
+ },
+
me: async({ dispatch, commit }: any): Promise
=> {
// Always fetch fresh so permissions reflect the current user (no stale cache after login switch)
const me = await dispatch('request', { opt: { url: `/api/v1/me` } });
diff --git a/dashboard/pkg/epinio/store/epinio-store/getters.ts b/dashboard/pkg/epinio/store/epinio-store/getters.ts
index 47ea9fc7..0fc02e1f 100644
--- a/dashboard/pkg/epinio/store/epinio-store/getters.ts
+++ b/dashboard/pkg/epinio/store/epinio-store/getters.ts
@@ -51,6 +51,7 @@ export default {
'/api/v1/namespaces': EPINIO_TYPES.NAMESPACE,
'/api/v1/configurations': EPINIO_TYPES.CONFIGURATION,
'/api/v1/services': EPINIO_TYPES.SERVICE_INSTANCE,
+ '/api/v1/applications': EPINIO_TYPES.APP,
'/api/v1/catalogservices': EPINIO_TYPES.CATALOG_SERVICE,
};
@@ -76,6 +77,13 @@ export default {
params.delete('search');
}
+ const namespaces = state.activeNamespaces;
+ if (namespaces && namespaces.length) {
+ params.set('namespaces', namespaces.join(','));
+ } else {
+ params.delete('namespaces');
+ }
+
const qs = params.toString();
return qs ? `${ path }?${ qs }` : path;
@@ -159,6 +167,8 @@ export default {
return out;
},
+ activeNamespaces: (state: any) => () => state.activeNamespaces,
+
singleProductCNSI: (state: any) => () => state.singleProductCNSI,
info: (state: any) => () => state.info,
diff --git a/dashboard/pkg/epinio/store/epinio-store/index.ts b/dashboard/pkg/epinio/store/epinio-store/index.ts
index 99cf641e..209352c7 100644
--- a/dashboard/pkg/epinio/store/epinio-store/index.ts
+++ b/dashboard/pkg/epinio/store/epinio-store/index.ts
@@ -18,7 +18,9 @@ const epinioFactory = (): CoreStoreSpecifics => {
searchQuery: {} as Record,
// Server-side namespace search results (names) for the navbar filter.
// null means no active query -> the filter shows the full list.
- namespaceSearch: null as string[] | null
+ namespaceSearch: null as string[] | null,
+ // Active namespaces from the top-level filter. null means no active filter -> the list shows all namespaces.
+ activeNamespaces: null as string[] | null,
};
},
diff --git a/dashboard/pkg/epinio/store/epinio-store/mutations.ts b/dashboard/pkg/epinio/store/epinio-store/mutations.ts
index 69cdec1b..65cfc7b4 100644
--- a/dashboard/pkg/epinio/store/epinio-store/mutations.ts
+++ b/dashboard/pkg/epinio/store/epinio-store/mutations.ts
@@ -50,6 +50,10 @@ export default {
state.searchQuery[type] = query;
},
+ setActiveNamespaces(state: any, namespaces: string[] | null) {
+ state.activeNamespaces = namespaces;
+ },
+
setNamespaceSearch(state: any, names: string[] | null) {
state.namespaceSearch = names;
},
diff --git a/dashboard/pkg/epinio/utils/namespaces.ts b/dashboard/pkg/epinio/utils/namespaces.ts
index 80131c5f..5dba2f1c 100644
--- a/dashboard/pkg/epinio/utils/namespaces.ts
+++ b/dashboard/pkg/epinio/utils/namespaces.ts
@@ -21,10 +21,12 @@ export function useNamespaces(store: any, opts: UseNamespacesOptions = {}) {
const isLoading = ref(false);
const classify = (rawData: any[]) => Promise.all(
- rawData.map((item: any) => store.dispatch(
- 'epinio/create',
- { type: EPINIO_TYPES.NAMESPACE, ...item }
- ))
+ rawData.map((item: any) =>
+ store.dispatch('epinio/create', {
+ type: EPINIO_TYPES.NAMESPACE,
+ ...item
+ })
+ )
);
const load = async (url: string) => {
@@ -47,25 +49,25 @@ export function useNamespaces(store: any, opts: UseNamespacesOptions = {}) {
return rows;
}
- return rows.filter((ns: any) => !!active[ns.meta?.name ?? ns.metadata?.name]);
+ return rows.filter(
+ (ns: any) => !!active[ns.meta?.name ?? ns.metadata?.name]
+ );
};
// Cached, so reopening a dropdown in the same form doesn't refetch
async function fetchAll() {
if (cached.value.length > 0) {
namespaces.value = cached.value;
-
return;
}
isLoading.value = true;
try {
- const rows = await load('/api/v1/namespaces');
- const scoped = opts.scopeToActiveFilter ? inActiveFilter(rows) : rows;
+ const classifiedData = await load('/api/v1/namespaces');
- namespaces.value = scoped;
- cached.value = scoped;
+ namespaces.value = classifiedData;
+ cached.value = classifiedData;
} catch (e) {
if (opts.onError) {
opts.onError(e);
@@ -81,9 +83,9 @@ export function useNamespaces(store: any, opts: UseNamespacesOptions = {}) {
isLoading.value = true;
try {
- namespaces.value = await load(
- `/api/v1/namespaces?search=${ encodeURIComponent(query) }`
- );
+ const classifiedData = await load(`/api/v1/namespaces?search=${ encodeURIComponent(query) }`);
+
+ namespaces.value = classifiedData;
} catch {
namespaces.value = [];
} finally {
@@ -91,24 +93,51 @@ export function useNamespaces(store: any, opts: UseNamespacesOptions = {}) {
}
}
- // Read-only namespace fields (view/edit) only need the record's own namespace
- // present for the label to render. Leaves the cache empty so a later create
- // still loads the full list.
function seed(name: string) {
namespaces.value = name ? [{ meta: { name } }] : [];
cached.value = [];
}
- const sorted = computed(() => sortBy(namespaces.value, (ns: any) => ns.meta?.name) as any[]);
+ const filteredNamespaces = computed(() => {
+ if (!opts.scopeToActiveFilter) {
+ return namespaces.value;
+ }
- const options = computed(() => sorted.value.map((ns: any) => ({
- label: ns.meta?.name || '',
- value: ns.meta?.name || '',
- })));
+ // Access the reactive store state inside the computed.
+ const active = store.state.activeNamespaceCache;
+
+ if (!active || Object.keys(active).length === 0) {
+ return namespaces.value;
+ }
- const firstName = computed(() => sorted.value[0]?.meta?.name || '');
+ return inActiveFilter(namespaces.value);
+ });
+
+ const sorted = computed(() =>
+ sortBy(
+ filteredNamespaces.value,
+ (ns: any) => ns.meta?.name
+ ) as any[]
+ );
+
+ const options = computed(() =>
+ sorted.value.map((ns: any) => ({
+ label: ns.meta?.name || '',
+ value: ns.meta?.name || '',
+ }))
+ );
+
+ const firstName = computed(
+ () => sorted.value[0]?.meta?.name || ''
+ );
return {
- namespaces: sorted, options, isLoading, firstName, fetchAll, search, seed
+ namespaces: sorted,
+ options,
+ isLoading,
+ firstName,
+ fetchAll,
+ search,
+ seed
};
}
diff --git a/dashboard/pkg/epinio/utils/table-formatters.ts b/dashboard/pkg/epinio/utils/table-formatters.ts
index 0d1e5b8e..89b6dff1 100644
--- a/dashboard/pkg/epinio/utils/table-formatters.ts
+++ b/dashboard/pkg/epinio/utils/table-formatters.ts
@@ -22,6 +22,10 @@ export function makeRouterLinks(
): HTMLSpanElement {
const span = document.createElement('span');
+ span.style.whiteSpace = 'normal';
+ span.style.overflowWrap = 'anywhere';
+ span.style.wordBreak = 'break-word';
+
items.forEach((item, index) => {
const a = document.createElement('a');