dashboard/shell/components/form/ResourceTabs/index.vue

299 lines
7.5 KiB
Vue

<script>
/*
Tab component for resource CRU pages featuring:
Labels and Annotation tabs with content filtered by create-edit-view mixin
*/
import Tabbed from '@shell/components/Tabbed';
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 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_FIRST_SEEN, 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 {
name: 'ResourceTabs',
components: {
Tabbed,
Tab,
Conditions,
PaginatedResourceTable,
RelatedResources,
},
mixins: [CreateEditView],
props: {
// resource instance
value: {
type: Object,
default: () => {
return {};
}
},
// create-edit-view mode
mode: {
type: String,
default: _VIEW,
},
defaultTab: {
type: String,
default: null,
},
needConditions: {
type: Boolean,
default: true
},
needEvents: {
type: Boolean,
default: true
},
needRelated: {
type: Boolean,
default: true
},
extensionParams: {
type: Object,
default: null
},
useHash: {
type: Boolean,
default: true
}
},
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
const paginationHeaders = eventSchema ? [
STEVE_EVENT_LAST_SEEN,
STEVE_EVENT_TYPE,
REASON,
headerFromSchemaColString('Subobject', eventSchema, this.$store.getters, true),
headerFromSchemaColString('Source', eventSchema, this.$store.getters, true),
MESSAGE,
STEVE_EVENT_FIRST_SEEN,
headerFromSchemaColString('Count', eventSchema, this.$store.getters, true),
STEVE_NAME_COL,
] : [];
return {
eventSchema,
EVENT,
selectedTab: this.defaultTab,
inStore,
showConditions: false,
paginationHeaders
};
},
beforeUnmount() {
this.$store.dispatch('cluster/forgetType', EVENT);
},
fetch() {
// By this stage the `value` should be set. Taking a chance that this is true
// The alternative is have an expensive watch on the `value` and trigger there (as well)
this.setShowConditions();
},
computed: {
showEvents() {
return this.isView && this.needEvents && this.eventSchema;
},
showRelated() {
return this.isView && this.needRelated;
},
eventHeaders() {
return [
{
name: 'type',
label: this.t('tableHeaders.type'),
value: 'eventType',
sort: 'eventType',
},
{
name: 'reason',
label: this.t('tableHeaders.reason'),
value: 'reason',
sort: 'reason',
},
{
name: 'date',
label: this.t('tableHeaders.updated'),
value: 'date',
sort: 'date:desc',
formatter: 'LiveDate',
formatterOpts: { addSuffix: true },
width: 125
},
{
name: 'message',
label: this.t('tableHeaders.message'),
value: 'message',
sort: 'message',
},
];
},
conditionsHaveIssues() {
if (this.showConditions) {
return this.value.status?.conditions?.filter((cond) => !isConditionReadyAndWaiting(cond)).some((cond) => cond.error);
}
return false;
}
},
methods: {
// Ensures we only fetch events and show the table when the events tab has been activated
tabChange(neu) {
this.selectedTab = neu?.selectedName;
},
/**
* Conditions come from a resource's `status`. They are used by both core resources like workloads as well as those from CRDs
* - Workloads
* - Nodes
* - Fleet git repo
* - Cluster (provisioning)
*
* Check here if the resource type contains conditions via the schema resourceFields
*/
async setShowConditions() {
if (this.isView && this.needConditions && !!this.value?.type && !!this.schema?.fetchResourceFields) {
await this.schema.fetchResourceFields();
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`;
// 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;
}
}
};
</script>
<template>
<Tabbed
v-bind="$attrs"
:default-tab="defaultTab"
:resource="value"
:use-hash="useHash"
@changed="tabChange"
>
<slot />
<Tab
v-if="showConditions"
label-key="resourceTabs.conditions.tab"
name="conditions"
:weight="-1"
:display-alert-icon="conditionsHaveIssues"
>
<Conditions :value="value" />
</Tab>
<Tab
v-if="showEvents"
label-key="resourceTabs.events.tab"
name="events"
:weight="-2"
>
<!-- namespaced: false given we don't want the default handling of namespaced resource (apply header filter) -->
<PaginatedResourceTable
v-if="selectedTab === 'events'"
:schema="eventSchema"
:local-filter="filterEventsLocal"
:api-filter="filterEventsApi"
:use-query-params-for-simple-filtering="false"
:headers="eventHeaders"
:paginationHeaders="paginationHeaders"
:namespaced="false"
/>
</Tab>
<Tab
v-if="showRelated"
name="related"
label-key="resourceTabs.related.tab"
:weight="-3"
>
<h3 v-t="'resourceTabs.related.from'" />
<RelatedResources
:ignore-types="[value.type]"
:value="value"
direction="from"
/>
<h3
v-t="'resourceTabs.related.to'"
class="mt-20"
/>
<RelatedResources
:ignore-types="[value.type]"
:value="value"
direction="to"
/>
</Tab>
</Tabbed>
</template>