mirror of https://github.com/rancher/dashboard.git
299 lines
7.5 KiB
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>
|