2022-10-26 01:02:56 -07:00
|
|
|
<template>
|
|
|
|
<div :class="['executions-sidebar', $style.container]">
|
|
|
|
<div :class="$style.heading">
|
2022-12-14 01:04:10 -08:00
|
|
|
<n8n-heading tag="h2" size="medium" color="text-dark">
|
2022-10-26 01:02:56 -07:00
|
|
|
{{ $locale.baseText('generic.executions') }}
|
|
|
|
</n8n-heading>
|
|
|
|
</div>
|
|
|
|
<div :class="$style.controls">
|
2022-12-14 01:04:10 -08:00
|
|
|
<el-checkbox v-model="autoRefresh" @change="onAutoRefreshToggle">{{
|
|
|
|
$locale.baseText('executionsList.autoRefresh')
|
|
|
|
}}</el-checkbox>
|
|
|
|
<n8n-popover trigger="click">
|
2022-11-18 05:59:31 -08:00
|
|
|
<template #reference>
|
2022-10-26 01:02:56 -07:00
|
|
|
<div :class="$style.filterButton">
|
|
|
|
<n8n-button icon="filter" type="tertiary" size="medium" :active="statusFilterApplied">
|
|
|
|
<n8n-badge v-if="statusFilterApplied" theme="primary" class="mr-4xs">1</n8n-badge>
|
|
|
|
{{ $locale.baseText('executionsList.filters') }}
|
|
|
|
</n8n-button>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
<div :class="$style['filters-dropdown']">
|
|
|
|
<div class="mb-s">
|
|
|
|
<n8n-input-label
|
|
|
|
:label="$locale.baseText('executions.ExecutionStatus')"
|
|
|
|
:bold="false"
|
|
|
|
size="small"
|
|
|
|
color="text-base"
|
|
|
|
class="mb-3xs"
|
|
|
|
/>
|
|
|
|
<n8n-select
|
|
|
|
v-model="filter.status"
|
|
|
|
size="small"
|
|
|
|
ref="typeInput"
|
|
|
|
:class="$style['type-input']"
|
|
|
|
:placeholder="$locale.baseText('generic.any')"
|
|
|
|
@change="onFilterChange"
|
|
|
|
>
|
|
|
|
<n8n-option
|
|
|
|
v-for="item in executionStatuses"
|
|
|
|
:key="item.id"
|
|
|
|
:label="item.name"
|
2022-12-14 01:04:10 -08:00
|
|
|
:value="item.id"
|
|
|
|
>
|
2022-10-26 01:02:56 -07:00
|
|
|
</n8n-option>
|
|
|
|
</n8n-select>
|
|
|
|
</div>
|
|
|
|
<div :class="[$style.filterMessage, 'mt-s']" v-if="statusFilterApplied">
|
|
|
|
<n8n-link @click="resetFilters">
|
|
|
|
{{ $locale.baseText('generic.reset') }}
|
|
|
|
</n8n-link>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</n8n-popover>
|
|
|
|
</div>
|
|
|
|
<div v-show="statusFilterApplied" class="mb-xs">
|
|
|
|
<n8n-info-tip :bold="false">
|
|
|
|
{{ $locale.baseText('generic.filtersApplied') }}
|
|
|
|
<n8n-link @click="resetFilters" size="small">
|
|
|
|
{{ $locale.baseText('generic.resetAllFilters') }}
|
|
|
|
</n8n-link>
|
|
|
|
</n8n-info-tip>
|
|
|
|
</div>
|
|
|
|
<div :class="$style.executionList" ref="executionList" @scroll="loadMore">
|
|
|
|
<div v-if="loading" class="mr-m">
|
|
|
|
<n8n-loading :class="$style.loader" variant="p" :rows="1" />
|
|
|
|
<n8n-loading :class="$style.loader" variant="p" :rows="1" />
|
|
|
|
<n8n-loading :class="$style.loader" variant="p" :rows="1" />
|
|
|
|
</div>
|
|
|
|
<execution-card
|
|
|
|
v-else
|
|
|
|
v-for="execution in executions"
|
|
|
|
:key="execution.id"
|
|
|
|
:execution="execution"
|
2022-12-22 03:40:33 -08:00
|
|
|
:ref="`execution-${execution.id}`"
|
2022-10-26 01:02:56 -07:00
|
|
|
@refresh="onRefresh"
|
|
|
|
@retryExecution="onRetryExecution"
|
|
|
|
/>
|
|
|
|
<div v-if="loadingMore" class="mr-m">
|
|
|
|
<n8n-loading :class="$style.loader" variant="p" :rows="1" />
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div :class="$style.infoAccordion">
|
|
|
|
<executions-info-accordion :initiallyExpanded="false" />
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script lang="ts">
|
|
|
|
import ExecutionCard from '@/components/ExecutionsView/ExecutionCard.vue';
|
|
|
|
import ExecutionsInfoAccordion from '@/components/ExecutionsView/ExecutionsInfoAccordion.vue';
|
|
|
|
import { VIEWS } from '../../constants';
|
|
|
|
import { range as _range } from 'lodash';
|
2022-12-14 01:04:10 -08:00
|
|
|
import { IExecutionsSummary } from '@/Interface';
|
2022-10-26 01:02:56 -07:00
|
|
|
import { Route } from 'vue-router';
|
|
|
|
import Vue from 'vue';
|
|
|
|
import { PropType } from 'vue';
|
2022-11-04 06:04:31 -07:00
|
|
|
import { mapStores } from 'pinia';
|
|
|
|
import { useUIStore } from '@/stores/ui';
|
2022-12-22 03:40:33 -08:00
|
|
|
import { useWorkflowsStore } from '@/stores/workflows';
|
2022-10-26 01:02:56 -07:00
|
|
|
|
|
|
|
export default Vue.extend({
|
|
|
|
name: 'executions-sidebar',
|
|
|
|
components: {
|
|
|
|
ExecutionCard,
|
|
|
|
ExecutionsInfoAccordion,
|
|
|
|
},
|
|
|
|
props: {
|
|
|
|
executions: {
|
|
|
|
type: Array as PropType<IExecutionsSummary[]>,
|
|
|
|
required: true,
|
|
|
|
},
|
|
|
|
loading: {
|
|
|
|
type: Boolean,
|
|
|
|
default: true,
|
|
|
|
},
|
|
|
|
loadingMore: {
|
|
|
|
type: Boolean,
|
|
|
|
default: false,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
data() {
|
|
|
|
return {
|
|
|
|
VIEWS,
|
|
|
|
filter: {
|
|
|
|
status: '',
|
|
|
|
},
|
|
|
|
autoRefresh: false,
|
|
|
|
autoRefreshInterval: undefined as undefined | NodeJS.Timer,
|
|
|
|
};
|
|
|
|
},
|
|
|
|
computed: {
|
2022-12-22 03:40:33 -08:00
|
|
|
...mapStores(useUIStore, useWorkflowsStore),
|
2022-10-26 01:02:56 -07:00
|
|
|
statusFilterApplied(): boolean {
|
|
|
|
return this.filter.status !== '';
|
|
|
|
},
|
2022-12-14 01:04:10 -08:00
|
|
|
executionStatuses(): Array<{ id: string; name: string }> {
|
2022-10-26 01:02:56 -07:00
|
|
|
return [
|
|
|
|
{ id: 'error', name: this.$locale.baseText('executionsList.error') },
|
|
|
|
{ id: 'running', name: this.$locale.baseText('executionsList.running') },
|
|
|
|
{ id: 'success', name: this.$locale.baseText('executionsList.success') },
|
|
|
|
{ id: 'waiting', name: this.$locale.baseText('executionsList.waiting') },
|
|
|
|
];
|
|
|
|
},
|
|
|
|
},
|
|
|
|
watch: {
|
2022-12-14 01:04:10 -08:00
|
|
|
$route(to: Route, from: Route) {
|
2022-10-26 01:02:56 -07:00
|
|
|
if (from.name === VIEWS.EXECUTION_PREVIEW && to.name === VIEWS.EXECUTION_HOME) {
|
|
|
|
// Skip parent route when navigating through executions with back button
|
|
|
|
this.$router.go(-1);
|
|
|
|
}
|
2022-12-14 01:04:10 -08:00
|
|
|
},
|
2022-10-26 01:02:56 -07:00
|
|
|
},
|
|
|
|
mounted() {
|
2022-11-04 06:04:31 -07:00
|
|
|
this.autoRefresh = this.uiStore.executionSidebarAutoRefresh === true;
|
2022-10-26 01:02:56 -07:00
|
|
|
if (this.autoRefresh) {
|
|
|
|
this.autoRefreshInterval = setInterval(() => this.onRefresh(), 4000);
|
|
|
|
}
|
2022-12-22 03:40:33 -08:00
|
|
|
this.scrollToActiveCard();
|
2022-10-26 01:02:56 -07:00
|
|
|
},
|
|
|
|
beforeDestroy() {
|
|
|
|
if (this.autoRefreshInterval) {
|
|
|
|
clearInterval(this.autoRefreshInterval);
|
|
|
|
this.autoRefreshInterval = undefined;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
methods: {
|
|
|
|
loadMore(): void {
|
|
|
|
if (!this.loading) {
|
|
|
|
const executionsList = this.$refs.executionList as HTMLElement;
|
|
|
|
if (executionsList) {
|
2022-12-14 01:04:10 -08:00
|
|
|
const diff =
|
|
|
|
executionsList.offsetHeight - (executionsList.scrollHeight - executionsList.scrollTop);
|
|
|
|
if (diff > -10 && diff < 10) {
|
2022-10-26 01:02:56 -07:00
|
|
|
this.$emit('loadMore');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
onRetryExecution(payload: Object) {
|
|
|
|
this.$emit('retryExecution', payload);
|
|
|
|
},
|
|
|
|
onRefresh(): void {
|
|
|
|
this.$emit('refresh');
|
|
|
|
},
|
|
|
|
onFilterChange(): void {
|
|
|
|
this.$emit('filterUpdated', this.prepareFilter());
|
|
|
|
},
|
|
|
|
reloadExecutions(): void {
|
|
|
|
this.$emit('reloadExecutions');
|
|
|
|
},
|
|
|
|
onAutoRefreshToggle(): void {
|
2022-11-04 06:04:31 -07:00
|
|
|
this.uiStore.executionSidebarAutoRefresh = this.autoRefresh;
|
2022-10-26 01:02:56 -07:00
|
|
|
if (this.autoRefreshInterval) {
|
|
|
|
// Clear any previously existing intervals (if any - there shouldn't)
|
|
|
|
clearInterval(this.autoRefreshInterval);
|
|
|
|
this.autoRefreshInterval = undefined;
|
|
|
|
}
|
|
|
|
if (this.autoRefresh) {
|
|
|
|
this.autoRefreshInterval = setInterval(() => this.onRefresh(), 4 * 1000); // refresh data every 4 secs
|
|
|
|
}
|
|
|
|
},
|
|
|
|
async resetFilters(): Promise<void> {
|
|
|
|
this.filter.status = '';
|
|
|
|
this.$emit('filterUpdated', this.prepareFilter());
|
|
|
|
},
|
|
|
|
prepareFilter(): object {
|
|
|
|
return {
|
|
|
|
finished: this.filter.status !== 'running',
|
|
|
|
status: this.filter.status,
|
|
|
|
};
|
|
|
|
},
|
2022-12-22 03:40:33 -08:00
|
|
|
scrollToActiveCard(): void {
|
|
|
|
const executionsList = this.$refs.executionList as HTMLElement;
|
2022-12-23 04:37:32 -08:00
|
|
|
const currentExecutionCard = this.$refs[
|
|
|
|
`execution-${this.workflowsStore.activeWorkflowExecution?.id}`
|
|
|
|
] as Vue[];
|
2022-12-22 03:40:33 -08:00
|
|
|
|
|
|
|
if (executionsList && currentExecutionCard && this.workflowsStore.activeWorkflowExecution) {
|
|
|
|
const cardElement = currentExecutionCard[0].$el as HTMLElement;
|
|
|
|
const cardRect = cardElement.getBoundingClientRect();
|
|
|
|
if (cardRect.top > executionsList.offsetHeight) {
|
|
|
|
executionsList.scrollTo({ top: cardRect.top });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2022-10-26 01:02:56 -07:00
|
|
|
},
|
|
|
|
});
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<style module lang="scss">
|
|
|
|
.container {
|
|
|
|
flex: 310px 0 0;
|
|
|
|
background-color: var(--color-background-xlight);
|
|
|
|
border-right: var(--border-base);
|
|
|
|
padding: var(--spacing-l) 0 var(--spacing-l) var(--spacing-l);
|
|
|
|
z-index: 1;
|
|
|
|
overflow: hidden;
|
|
|
|
}
|
|
|
|
|
|
|
|
.heading {
|
|
|
|
display: flex;
|
|
|
|
justify-content: space-between;
|
|
|
|
align-items: baseline;
|
|
|
|
padding-right: var(--spacing-l);
|
|
|
|
}
|
|
|
|
|
|
|
|
.controls {
|
|
|
|
padding: var(--spacing-s) 0 var(--spacing-xs);
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: space-between;
|
|
|
|
padding-right: var(--spacing-l);
|
|
|
|
|
|
|
|
button {
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.executionList {
|
|
|
|
height: calc(100% - 10.5em);
|
|
|
|
overflow: auto;
|
|
|
|
margin-bottom: var(--spacing-m);
|
|
|
|
background-color: var(--color-background-xlight) !important;
|
|
|
|
|
|
|
|
// Scrolling fader
|
|
|
|
&::before {
|
|
|
|
position: absolute;
|
|
|
|
display: block;
|
|
|
|
width: 270px;
|
|
|
|
height: 6px;
|
|
|
|
background: linear-gradient(to bottom, rgba(251, 251, 251, 1) 0%, rgba(251, 251, 251, 0) 100%);
|
|
|
|
z-index: 999;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Lower first execution card so fader is not visible when not scrolled
|
|
|
|
& > div:first-child {
|
|
|
|
margin-top: 3px;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.infoAccordion {
|
|
|
|
position: absolute;
|
|
|
|
bottom: 0;
|
|
|
|
margin-left: calc(-1 * var(--spacing-l));
|
|
|
|
border-top: var(--border-base);
|
|
|
|
|
|
|
|
& > div {
|
|
|
|
width: 309px;
|
|
|
|
background-color: var(--color-background-light);
|
2022-12-14 01:04:10 -08:00
|
|
|
margin-top: 0 !important;
|
2022-10-26 01:02:56 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
</style>
|