Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions dashboard/pkg/epinio/config/epinio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ export const BLANK_CLUSTER = '_';
// function to watch epinio route so css overrides only apply on epinio pages
const watchEpinioRoute = () => {
const observer = new MutationObserver(() => {
const isEpinio = window.location.pathname.includes('epinio');
const isEpinio = window.location.pathname.startsWith('/epinio');
document.body.classList.toggle('epinio-active', isEpinio);
});

observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['class'] });

document.body.classList.toggle('epinio-active', window.location.pathname.includes('epinio'));
document.body.classList.toggle('epinio-active', window.location.pathname.startsWith('/epinio'));
}

export function init($plugin: any, store: any) {
Expand Down
39 changes: 26 additions & 13 deletions dashboard/pkg/epinio/pages/c/_cluster/applications/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -283,15 +283,21 @@ function handleDeleted(app: any) {
onMounted(async () => {
window.addEventListener('resize', onResize);

const namespaces = visibleNamespaceNames();

await Promise.all([
const [,,, grouped] = await Promise.all([
store.dispatch('epinio/me'),
store.dispatch('epinio/findAll', { type: EPINIO_TYPES.CONFIGURATION }),
store.dispatch('epinio/findAll', { type: EPINIO_TYPES.SERVICE_INSTANCE }),
...namespaces.map(ns => fetchNamespaceApps(ns, 1, '', false)),
store.dispatch('epinio/findGroupedApps'),
]);

// ?? {} so a failed grouped fetch degrades to an empty loop rather than throwing
for (const [ns, nsData] of Object.entries(grouped ?? {})) {
const { items, meta } = nsData as { items: any[]; meta: any };
// spread-replace instead of direct mutation so Vue tracks the change
namespaceRows.value = { ...namespaceRows.value, [ns]: items };
namespaceMeta.value = { ...namespaceMeta.value, [ns]: meta };
}

pending.value = false;

startPolling(['namespaces', 'configurations', 'services'], store);
Expand All @@ -300,10 +306,17 @@ onMounted(async () => {
appModal.value?.openCreate();
}

appsPollIntervalId = window.setInterval(() => {
visibleNamespaceNames().forEach(ns => {
fetchNamespaceApps(ns, namespaceCurrentPages.value[ns] ?? 1, searchQueries.value[ns] ?? '', true);
});
appsPollIntervalId = window.setInterval(async() => {
// Sequential await respects per-namespace page/search state and avoids
// firing all calls simultaneously through the k8s client rate limiter.
for (const ns of visibleNamespaceNames()) {
await fetchNamespaceApps(
ns,
namespaceCurrentPages.value[ns] ?? 1,
searchQueries.value[ns] ?? '',
true,
);
}
}, APPS_POLL_RATE_MS);
});

Expand Down Expand Up @@ -336,7 +349,7 @@ onUnmounted(() => {
</template>
</Masthead>

<div v-if="pending">
<div v-if="pending" class="namespace-group">
<div class="namespace-group-header">
<h3 class="namespace-header">
Loading applications...
Expand Down Expand Up @@ -402,16 +415,16 @@ onUnmounted(() => {
.namespace-group {
margin-bottom: 2rem;

&:last-child {
margin-bottom: 0;
}

trailhand-table {
--sortable-table-row-hover-bg: var(--sortable-table-hover-bg);
--sortable-table-header-hover-bg: var(--sortable-table-hover-bg);
--sortable-table-header-sorted-bg: var(--sortable-table-hover-bg);
overflow-wrap: anywhere;
}

&:last-child {
margin-bottom: 0;
}
}

.namespace-group-header {
Expand Down
48 changes: 48 additions & 0 deletions dashboard/pkg/epinio/store/epinio-store/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,54 @@ export default {
return info;
},


/**
* Fetch page 1 of apps for every namespace in a single server call.
* Returns a map of namespace → { items, meta } matching findAppsInNamespace's shape.
* Falls back to an empty map on error so the UI can degrade gracefully.
*/
findGroupedApps: async(
ctx: any,
{ page = 1, pageSize = 10, search = '' }: {
page?: number;
pageSize?: number;
search?: string
} = {}
) => {
const { dispatch } = ctx;
let url = `/api/v1/applications/grouped?page=${ page }&pageSize=${ pageSize }`;
if (search) {
url += `&search=${ encodeURIComponent(search) }`;
}

// The request action runs epiniofy on the response, which spreads the namespace
// map and injects id/type keys. The guard below skips those non-namespace entries.
const grouped: Record<string, any> =
await dispatch('request', { opt: { url, _skipPaginationMeta: true } }) ?? {};

const appSchema = ctx.getters.schemaFor(EPINIO_TYPES.APP);
const result: Record<string, { items: any[]; meta: any }> = {};
for (const [ns, nsData] of Object.entries(grouped)) {
if (!nsData || typeof nsData !== 'object' || !Array.isArray((nsData as any).items)) {
continue;
}
const items = (nsData.items ?? []).map((item: any) =>
classify(ctx, epiniofy(item, appSchema, EPINIO_TYPES.APP))
);
result[ns] = {
items,
meta: {
page: nsData.page,
pageSize: nsData.pageSize,
totalItems: nsData.totalItems,
totalPages: nsData.totalPages,
},
};
}

return result;
},

/**
* Fetch a single page of applications scoped to a specific namespace.
* Does NOT touch global paginationMeta so per-namespace tables stay independent.
Expand Down
2 changes: 1 addition & 1 deletion dashboard/pkg/epinio/utils/polling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { _MERGE } from '@shell/plugins/dashboard-store/actions';
* polling on specific resource types as needed by particular lists or pages.
*/

const pollingRate = 15000; //15 seconds
const pollingRate = 30000; //30 seconds
const polling: any = {};

export function startPolling(types: string[], store: any): any {
Expand Down
Loading