diff --git a/cypress/e2e/tests/pages/fleet/advanced/workspaces.spec.ts b/cypress/e2e/tests/pages/fleet/advanced/workspaces.spec.ts index d74342d9722..3d3ca200dc0 100644 --- a/cypress/e2e/tests/pages/fleet/advanced/workspaces.spec.ts +++ b/cypress/e2e/tests/pages/fleet/advanced/workspaces.spec.ts @@ -18,7 +18,7 @@ describe('Workspaces', { testIsolation: 'off', tags: ['@fleet', '@adminUser'] }, let initialCount: number; it('check table headers are available in list and details view', () => { - FleetWorkspaceListPagePo.navTo(); + fleetWorkspacesPage.goTo(); fleetWorkspacesPage.waitForPage(); fleetWorkspacesPage.sortableTable().noRowsShouldNotExist(); fleetWorkspacesPage.sortableTable().filter(defaultWorkspace); diff --git a/shell/components/PaginatedResourceTable.vue b/shell/components/PaginatedResourceTable.vue index db4dce7767b..237a3b62307 100644 --- a/shell/components/PaginatedResourceTable.vue +++ b/shell/components/PaginatedResourceTable.vue @@ -2,13 +2,6 @@ import { defineComponent } from 'vue'; import ResourceFetch from '@shell/mixins/resource-fetch'; import ResourceTable from '@shell/components/ResourceTable.vue'; -import { StorePaginationResult } from '@shell/types/store/pagination.types'; - -export type FetchSecondaryResourcesOpts = { canPaginate: boolean } -export type FetchSecondaryResources = (opts: FetchSecondaryResourcesOpts) => Promise - -export type FetchPageSecondaryResourcesOpts = { canPaginate: boolean, force: boolean, page: any[], pagResult: StorePaginationResult } -export type FetchPageSecondaryResources = (opts: FetchPageSecondaryResourcesOpts) => Promise /** * This is meant to enable ResourceList like capabilities outside of List pages / components @@ -57,6 +50,8 @@ export default defineComponent({ * Information may be required from resources other than the primary one shown per row * * This will fetch them ALL and will be run in a non-server-side pagination world + * + * of type PagTableFetchSecondaryResources */ fetchSecondaryResources: { type: Function, @@ -69,6 +64,8 @@ export default defineComponent({ * This will fetch only those relevant to the current page using server-side pagination based filters * * called from shell/mixins/resource-fetch-api-pagination.js + * + * of type PagTableFetchPageSecondaryResources */ fetchPageSecondaryResources: { type: Function, diff --git a/shell/components/Questions/Reference.vue b/shell/components/Questions/Reference.vue index aefd3dc3ff6..8fdf2cd4400 100644 --- a/shell/components/Questions/Reference.vue +++ b/shell/components/Questions/Reference.vue @@ -1,7 +1,8 @@ @@ -90,15 +117,17 @@ export default { class="row" >
-
diff --git a/shell/components/RelatedWorkloadsTable.vue b/shell/components/RelatedWorkloadsTable.vue deleted file mode 100644 index 846a958fcdd..00000000000 --- a/shell/components/RelatedWorkloadsTable.vue +++ /dev/null @@ -1,50 +0,0 @@ - - - diff --git a/shell/components/form/LabeledSelect.vue b/shell/components/form/LabeledSelect.vue index 7626782fc64..edfadf55a99 100644 --- a/shell/components/form/LabeledSelect.vue +++ b/shell/components/form/LabeledSelect.vue @@ -396,7 +396,7 @@ export default { diff --git a/shell/components/form/ResourceSelector.vue b/shell/components/form/ResourceSelector.vue index 78ab9ec6741..2a50136ec25 100644 --- a/shell/components/form/ResourceSelector.vue +++ b/shell/components/form/ResourceSelector.vue @@ -36,6 +36,7 @@ export default { }, async fetch() { + // Used in conjunction with `matches/match/label selectors`. Requires https://github.com/rancher/dashboard/issues/10417 to fix const hash = await allHash({ allResources: this.$store.dispatch('cluster/findAll', { type: this.type }) }); this.allResources = hash.allResources; diff --git a/shell/components/form/ResourceTabs/index.vue b/shell/components/form/ResourceTabs/index.vue index b8f469c6b92..e80d5344a02 100644 --- a/shell/components/form/ResourceTabs/index.vue +++ b/shell/components/form/ResourceTabs/index.vue @@ -8,10 +8,14 @@ import Tab from '@shell/components/Tabbed/Tab'; import CreateEditView from '@shell/mixins/create-edit-view'; import Conditions from '@shell/components/form/Conditions'; import { EVENT } from '@shell/config/types'; -import SortableTable from '@shell/components/SortableTable'; +import PaginatedResourceTable from '@shell/components/PaginatedResourceTable.vue'; import { _VIEW } from '@shell/config/query-params'; import RelatedResources from '@shell/components/RelatedResources'; import { isConditionReadyAndWaiting } from '@shell/plugins/dashboard-store/resource-class'; +import { PaginationParamFilter } from '@shell/types/store/pagination.types'; +import { MESSAGE, REASON } from '@shell/config/table-headers'; +import { STEVE_EVENT_LAST_SEEN, STEVE_EVENT_TYPE, STEVE_NAME_COL } from '@shell/config/pagination-table-headers'; +import { headerFromSchemaColString } from '@shell/store/type-map.utils'; export default { @@ -21,7 +25,7 @@ export default { Tabbed, Tab, Conditions, - SortableTable, + PaginatedResourceTable, RelatedResources, }, @@ -69,14 +73,25 @@ export default { data() { const inStore = this.$store.getters['currentStore'](EVENT); + const eventSchema = this.$store.getters[`${ inStore }/schemaFor`](EVENT); // @TODO be smarter about which resources actually ever have events return { - hasEvents: this.$store.getters[`${ inStore }/schemaFor`](EVENT), // @TODO be smarter about which resources actually ever have events - allEvents: [], - selectedTab: this.defaultTab, - didLoadEvents: false, + eventSchema, + EVENT, + selectedTab: this.defaultTab, inStore, - showConditions: false, + showConditions: false, + paginationHeaders: [ + STEVE_EVENT_LAST_SEEN, + STEVE_EVENT_TYPE, + REASON, + headerFromSchemaColString('Subobject', eventSchema, this.$store.getters, true), + headerFromSchemaColString('Source', eventSchema, this.$store.getters, true), + MESSAGE, + headerFromSchemaColString('First Seen', eventSchema, this.$store.getters, true), + headerFromSchemaColString('Count', eventSchema, this.$store.getters, true), + STEVE_NAME_COL, + ] }; }, @@ -92,7 +107,7 @@ export default { computed: { showEvents() { - return this.isView && this.needEvents && this.hasEvents; + return this.isView && this.needEvents && this.eventSchema; }, showRelated() { return this.isView && this.needRelated; @@ -128,18 +143,6 @@ export default { }, ]; }, - events() { - return this.allEvents.filter((event) => { - return event.involvedObject?.uid === this.value?.metadata?.uid; - }).map((event) => { - return { - reason: (`${ event.reason || this.t('generic.unknown') }${ event.count > 1 ? ` (${ event.count })` : '' }`).trim(), - message: event.message || this.t('generic.unknown'), - date: event.lastTimestamp || event.firstTimestamp || event.metadata.creationTimestamp, - eventType: event.eventType - }; - }); - }, conditionsHaveIssues() { if (this.showConditions) { return this.value.status?.conditions?.filter((cond) => !isConditionReadyAndWaiting(cond)).some((cond) => cond.error); @@ -153,15 +156,6 @@ export default { // Ensures we only fetch events and show the table when the events tab has been activated tabChange(neu) { this.selectedTab = neu?.selectedName; - - if (!this.didLoadEvents && this.selectedTab === 'events') { - const inStore = this.$store.getters['currentStore'](EVENT); - - this.$store.dispatch(`${ inStore }/findAll`, { type: EVENT }).then((events) => { - this.allEvents = events; - this.didLoadEvents = true; - }); - } }, /** @@ -180,6 +174,54 @@ export default { this.showConditions = this.$store.getters[`${ this.inStore }/pathExistsInSchema`](this.value.type, 'status.conditions'); } }, + + /** + * Filter out hidden repos from list of all repos + */ + filterEventsLocal(rows) { + return rows.filter((event) => event.involvedObject?.uid === this.value?.metadata?.uid); + }, + + /** + * Filter out hidden repos via api + * + * pagination: PaginationArgs + * returns: PaginationArgs + */ + filterEventsApi(pagination) { + if (!pagination.filters) { + pagination.filters = []; + } + + const field = `involvedObject.uid`; // Pending API Support - https://github.com/rancher/rancher/issues/48603 + + // of type PaginationParamFilter + let existing = null; + + for (let i = 0; i < pagination.filters.length; i++) { + const filter = pagination.filters[i]; + + if (!!filter.fields.find((f) => f.field === field)) { + existing = filter; + break; + } + } + + const required = PaginationParamFilter.createSingleField({ + field, + exact: true, + value: this.value.metadata.uid, + equals: true + }); + + if (!!existing) { + Object.assign(existing, required); + } else { + pagination.filters.push(required); + } + + return pagination; + } } }; @@ -208,15 +250,16 @@ export default { name="events" :weight="-2" > - + diff --git a/shell/components/form/SecretSelector.vue b/shell/components/form/SecretSelector.vue index 4b491db8720..7f164582f7d 100644 --- a/shell/components/form/SecretSelector.vue +++ b/shell/components/form/SecretSelector.vue @@ -70,7 +70,7 @@ export default { secrets: null, SECRET, allSecretsSettings: { - mapResult: (secrets) => { + updateResources: (secrets) => { const allSecretsInNamespace = secrets.filter((secret) => this.types.includes(secret._type) && secret.namespace === this.namespace); const mappedSecrets = this.mapSecrets(allSecretsInNamespace.sort((a, b) => a.name.localeCompare(b.name))); @@ -81,7 +81,7 @@ export default { }, paginateSecretsSetting: { requestSettings: this.paginatePageOptions, - mapResult: (secrets) => { + updateResources: (secrets) => { const mappedSecrets = this.mapSecrets(secrets); this.secrets = secrets; // We need the key from the selected secret. When paginating we won't touch the store, so just pass back here diff --git a/shell/config/pagination-table-headers.js b/shell/config/pagination-table-headers.js index 6ae3cce8bd6..5691e288ee1 100644 --- a/shell/config/pagination-table-headers.js +++ b/shell/config/pagination-table-headers.js @@ -1,5 +1,7 @@ import { - STATE, NAME as NAME_COL, NAMESPACE as NAMESPACE_COL, AGE, OBJECT + STATE, NAME as NAME_COL, NAMESPACE as NAMESPACE_COL, AGE, OBJECT, + EVENT_LAST_SEEN_TIME, + EVENT_TYPE } from '@shell/config/table-headers'; // This file contains table headers @@ -52,6 +54,18 @@ export const STEVE_EVENT_OBJECT = { search: 'involvedObject.kind', }; +export const STEVE_EVENT_LAST_SEEN = { + ...EVENT_LAST_SEEN_TIME, + value: 'metadata.fields.0', + sort: 'metadata.fields.0', +}; + +export const STEVE_EVENT_TYPE = { + ...EVENT_TYPE, + value: '_type', + sort: '_type', +}; + export const STEVE_LIST_GROUPS = [{ tooltipKey: 'resourceTable.groupBy.none', icon: 'icon-list-flat', diff --git a/shell/config/product/explorer.js b/shell/config/product/explorer.js index e92087b93b5..237adfc979c 100644 --- a/shell/config/product/explorer.js +++ b/shell/config/product/explorer.js @@ -20,12 +20,13 @@ import { STORAGE_CLASS_PROVISIONER, PERSISTENT_VOLUME_SOURCE, HPA_REFERENCE, MIN_REPLICA, MAX_REPLICA, CURRENT_REPLICA, ACCESS_KEY, DESCRIPTION, EXPIRES, EXPIRY_STATE, LAST_USED, SUB_TYPE, AGE_NORMAN, SCOPE_NORMAN, PERSISTENT_VOLUME_CLAIM, RECLAIM_POLICY, PV_REASON, WORKLOAD_HEALTH_SCALE, POD_RESTARTS, - DURATION, MESSAGE, REASON, LAST_SEEN_TIME, EVENT_TYPE, OBJECT, ROLE, ROLES, VERSION, INTERNAL_EXTERNAL_IP, KUBE_NODE_OS, CPU, RAM, SECRET_DATA + DURATION, MESSAGE, REASON, EVENT_TYPE, OBJECT, ROLE, ROLES, VERSION, INTERNAL_EXTERNAL_IP, KUBE_NODE_OS, CPU, RAM, SECRET_DATA, + EVENT_LAST_SEEN_TIME } from '@shell/config/table-headers'; import { DSL } from '@shell/store/type-map'; import { - STEVE_AGE_COL, STEVE_EVENT_OBJECT, STEVE_LIST_GROUPS, STEVE_NAMESPACE_COL, STEVE_NAME_COL, STEVE_STATE_COL + STEVE_AGE_COL, STEVE_EVENT_LAST_SEEN, STEVE_EVENT_OBJECT, STEVE_EVENT_TYPE, STEVE_LIST_GROUPS, STEVE_NAMESPACE_COL, STEVE_NAME_COL, STEVE_STATE_COL } from '@shell/config/pagination-table-headers'; import { COLUMN_BREAKPOINTS } from '@shell/types/store/type-map'; @@ -321,23 +322,12 @@ export function init(store) { ] ); - const eventLastSeenTime = { - ...LAST_SEEN_TIME, - defaultSort: true, - }; - headers(EVENT, - [STATE, eventLastSeenTime, EVENT_TYPE, REASON, OBJECT, 'Subobject', 'Source', MESSAGE, 'First Seen', 'Count', NAME_COL, NAMESPACE_COL], + [STATE, EVENT_LAST_SEEN_TIME, EVENT_TYPE, REASON, OBJECT, 'Subobject', 'Source', MESSAGE, 'First Seen', 'Count', NAME_COL, NAMESPACE_COL], [ - STEVE_STATE_COL, { - ...eventLastSeenTime, - value: 'metadata.fields.0', - sort: 'metadata.fields.0', - }, { - ...EVENT_TYPE, - value: '_type', - sort: '_type', - }, + STEVE_STATE_COL, + STEVE_EVENT_LAST_SEEN, + STEVE_EVENT_TYPE, REASON, STEVE_EVENT_OBJECT, 'Subobject', diff --git a/shell/config/table-headers.js b/shell/config/table-headers.js index e06b0b74740..f754d81f5c6 100644 --- a/shell/config/table-headers.js +++ b/shell/config/table-headers.js @@ -515,6 +515,12 @@ export const LAST_SEEN_TIME = { sort: 'lastTimestamp:desc', tooltip: 'tableHeaders.lastSeenTooltip' }; + +export const EVENT_LAST_SEEN_TIME = { + ...LAST_SEEN_TIME, + defaultSort: true, +}; + export const LAST_HEARTBEAT_TIME = { name: 'lastHeartbeatTime', labelKey: 'tableHeaders.lastSeen', diff --git a/shell/detail/__tests__/autoscaling.horizontalpodautoscaler.test.ts b/shell/detail/__tests__/autoscaling.horizontalpodautoscaler.test.ts index c274017b39f..92f5b0b6446 100644 --- a/shell/detail/__tests__/autoscaling.horizontalpodautoscaler.test.ts +++ b/shell/detail/__tests__/autoscaling.horizontalpodautoscaler.test.ts @@ -7,9 +7,18 @@ describe('view: autoscaling.horizontalpodautoscaler', () => { 'i18n/t': (text: string) => text, t: (text: string) => text, currentStore: () => 'current_store', - 'current_store/schemaFor': jest.fn(), - 'current_store/all': jest.fn(), - workspace: jest.fn(), + 'current_store/schemaFor': () => ({ + attributes: { + columns: [ + { name: 'Subobject', field: '' }, + { name: 'Source', field: '' }, + { name: 'First Seen', field: '' }, + { name: 'Count', field: '' }] + } + }), + 'current_store/all': jest.fn(), + workspace: jest.fn(), + 'i18n/exists': jest.fn(), }, }; diff --git a/shell/detail/catalog.cattle.io.app.vue b/shell/detail/catalog.cattle.io.app.vue index 4c90ae4e7f0..c6702f3f50e 100644 --- a/shell/detail/catalog.cattle.io.app.vue +++ b/shell/detail/catalog.cattle.io.app.vue @@ -34,7 +34,7 @@ export default { const promises = { catalog: this.$store.dispatch('catalog/load'), allOperations: this.$store.dispatch('cluster/findAll', { type: CATALOG.OPERATION }), - secrets: this.value.fetchValues(true), + secret: this.value.fetchValues(true), }; const res = await allHash(promises); diff --git a/shell/detail/namespace.vue b/shell/detail/namespace.vue index a4deb7d988c..c9f785d429a 100644 --- a/shell/detail/namespace.vue +++ b/shell/detail/namespace.vue @@ -10,9 +10,7 @@ import Tab from '@shell/components/Tabbed/Tab'; import ResourceTable from '@shell/components/ResourceTable'; import SortableTable from '@shell/components/SortableTable'; import Loading from '@shell/components/Loading'; -import { - flatten, compact, filter, findKey, values -} from 'lodash'; +import { flatten, compact, findKey, values } from 'lodash'; export default { emits: ['input'], @@ -89,6 +87,9 @@ export default { }, ]; + const params = this.$route.params; + const { id: namespaceId } = params; + return { allWorkloads: { default: () => ([]), @@ -97,7 +98,10 @@ export default { resourceTypes: [], summaryStates: ['success', 'info', 'warning', 'error', 'unknown'], headers, - workloadSchema: WORKLOAD_SCHEMA + workloadSchema: WORKLOAD_SCHEMA, + inStore: this.$store.getters['currentProduct'].inStore, + statesByType: getStatesByType(), + namespaceId, }; }, @@ -106,10 +110,6 @@ export default { }, computed: { - inStore() { - return this.$store.getters['currentProduct'].inStore; - }, - namespacedResourceCounts() { const allClusterResourceCounts = this.$store.getters[`${ this.inStore }/all`](COUNT)[0].counts; @@ -148,20 +148,11 @@ export default { }, totals); }, - statesByType() { - return getStatesByType(); - }, - /** * Workload table data for current namespace */ workloadRows() { - const params = this.$route.params; - const { id } = params; - const rows = flatten(compact(this.allWorkloads)).filter((row) => !row.ownedByWorkload); - const namespacedRows = filter(rows, ({ metadata: { namespace } }) => namespace === id); - - return namespacedRows; + return flatten(compact(this.allWorkloads)).filter((row) => !row.ownedByWorkload); } }, @@ -198,7 +189,10 @@ export default { return Promise.all(values(WORKLOAD_TYPES) // You may not have RBAC to see some of the types .filter((type) => Boolean(this.schemaFor(type))) - .map((type) => this.$store.dispatch('cluster/findAll', { type })) + // findAll on each workload type here, argh! however... + // - results are shown in a single table containing all workloads rather than an SSP compatible way (one table per type) + // - we're restricting by namespace. not great, but a big improvement + .map((type) => this.$store.dispatch('cluster/findAll', { type, opt: { namespaced: this.namespaceId } })) ); }, diff --git a/shell/detail/networking.k8s.io.ingress.vue b/shell/detail/networking.k8s.io.ingress.vue index 1ac52715e25..3d514b006b0 100644 --- a/shell/detail/networking.k8s.io.ingress.vue +++ b/shell/detail/networking.k8s.io.ingress.vue @@ -1,12 +1,10 @@