2019-06-23 03:35:23 -07:00
< template >
< span >
2021-07-06 14:25:25 -07:00
< el -dialog :visible ="dialogVisible" append -to -body width = "80%" : title = "`Workflow Executions ${combinedExecutions.length}/${finishedExecutionsCountEstimated === true ? '~' : ''}${combinedExecutionsCount}`" :before-close ="closeDialog" >
2019-06-23 03:35:23 -07:00
< div class = "filters" >
< el -row >
< el -col :span ="4" class = "filter-headline" >
Filters :
< / e l - c o l >
< el -col :span ="6" >
< el -select v-model ="filter.workflowId" placeholder="Select Workflow" size="small" filterable @change="handleFilterChanged" >
< el -option
v - for = "item in workflows"
: key = "item.id"
: label = "item.name"
: value = "item.id" >
< / e l - o p t i o n >
< / e l - s e l e c t >
< / e l - c o l >
2019-12-12 15:39:56 -08:00
< el -col :span ="2" > & nbsp ;
< / e l - c o l >
< el -col :span ="4" >
< el -select v-model ="filter.status" placeholder="Select Status" size="small" filterable @change="handleFilterChanged" >
< el -option
v - for = "item in statuses"
: key = "item.id"
: label = "item.name"
: value = "item.id" >
< / e l - o p t i o n >
< / e l - s e l e c t >
< / e l - c o l >
2021-02-09 14:32:40 -08:00
< el -col :span ="4" > & nbsp ;
< / e l - c o l >
< el -col :span ="4" class = "autorefresh" >
< el -checkbox v-model ="autoRefresh" @change="handleAutoRefreshToggle" > Auto refresh < / el -checkbox >
2019-06-23 03:35:23 -07:00
< / e l - c o l >
< / e l - r o w >
< / div >
< div class = "selection-options" >
< span v-if ="checkAll === true || isIndeterminate === true" >
2021-07-06 14:25:25 -07:00
Selected : { { numSelected } } / < span v-if ="finishedExecutionsCountEstimated === true" > ~ < / span > {{ finishedExecutionsCount }}
2019-06-23 03:35:23 -07:00
< el -button type = "danger" title = "Delete Selected" icon = "el-icon-delete" size = "mini" @click ="handleDeleteSelected" circle > < / e l - b u t t o n >
< / span >
< / div >
2019-07-24 05:25:30 -07:00
< el -table :data ="combinedExecutions" stripe v-loading ="isDataLoading" :row-class-name="getRowClass" >
2019-06-23 03:35:23 -07:00
< el -table -column label = "" width = "30" >
<!-- eslint - disable - next - line vue / no - unused - vars -- >
< template slot = "header" slot -scope = " scope " >
< el -checkbox :indeterminate ="isIndeterminate" v-model ="checkAll" @change="handleCheckAllChange" > Check all < / el -checkbox >
< / template >
< template slot -scope = " scope " >
2019-07-24 05:25:30 -07:00
< el -checkbox v -if = " scope.row.stoppedAt ! = = undefined & & scope.row.id " : value = "selectedItems[scope.row.id.toString()] || checkAll" @change ="handleCheckboxChanged(scope.row.id)" > Check all < / e l - c h e c k b o x >
2019-06-23 03:35:23 -07:00
< / template >
< / e l - t a b l e - c o l u m n >
< el -table -column property = "startedAt" label = "Started At / ID" width = "205" >
< template slot -scope = " scope " >
{ { convertToDisplayDate ( scope . row . startedAt ) } } < br / >
2019-07-24 05:25:30 -07:00
< small v-if ="scope.row.id" > ID : {{ scope.row.id }} < / small >
2019-06-23 03:35:23 -07:00
< / template >
< / e l - t a b l e - c o l u m n >
< el -table -column property = "workflowName" label = "Name" >
< template slot -scope = " scope " >
< span class = "workflow-name" >
2019-07-24 05:25:30 -07:00
{ { scope . row . workflowName || '[UNSAVED WORKFLOW]' } }
2019-06-23 03:35:23 -07:00
< / span >
< span v-if ="scope.row.stoppedAt === undefined" >
( running )
< / span >
< span v-if ="scope.row.retryOf !== undefined" >
< br / > < small > Retry of "{{scope.row.retryOf}}" < / small >
< / span >
< span v -else -if = " scope.row.retrySuccessId ! = = undefined " >
< br / > < small > Success retry "{{scope.row.retrySuccessId}}" < / small >
< / span >
< / template >
< / e l - t a b l e - c o l u m n >
2019-10-06 02:09:01 -07:00
< el -table -column label = "Status" width = "120" align = "center" >
< template slot -scope = " scope " align = "center" >
2019-06-23 03:35:23 -07:00
< el -tooltip placement = "top" effect = "light" >
< div slot = "content" v-html ="statusTooltipText(scope.row)" > < / div >
< span class = "status-badge running" v-if ="scope.row.stoppedAt === undefined" >
Running
< / span >
< span class = "status-badge success" v -else -if = " scope.row.finished " >
Success
< / span >
2021-03-02 23:31:55 -08:00
< span class = "status-badge error" v -else -if = " scope.row.stoppedAt ! = = null " >
2019-06-23 03:35:23 -07:00
Error
< / span >
2021-03-02 23:31:55 -08:00
< span class = "status-badge warning" v-else >
Unknown
< / span >
2019-06-23 03:35:23 -07:00
< / e l - t o o l t i p >
2019-12-12 16:12:38 -08:00
< el -dropdown trigger = "click" @command ="handleRetryClick" >
< span class = "el-dropdown-link" >
2021-03-02 23:31:55 -08:00
< el -button class = "retry-button" v -bind : class = "{ warning: scope.row.stoppedAt === null }" circle v-if ="scope.row.stoppedAt !== undefined && !scope.row.finished && scope.row.retryOf === undefined && scope.row.retrySuccessId === undefined" type="text" size="small" title="Retry execution" >
2019-12-12 16:12:38 -08:00
< font -awesome -icon icon = "redo" / >
< / e l - b u t t o n >
< / span >
< el -dropdown -menu slot = "dropdown" >
< el -dropdown -item : command = "{command: 'currentlySaved', row: scope.row}" > Retry with currently saved workflow < / e l - d r o p d o w n - i t e m >
< el -dropdown -item : command = "{command: 'original', row: scope.row}" > Retry with original workflow < / e l - d r o p d o w n - i t e m >
< / e l - d r o p d o w n - m e n u >
< / e l - d r o p d o w n >
2019-06-23 03:35:23 -07:00
< / template >
< / e l - t a b l e - c o l u m n >
< el -table -column property = "mode" label = "Mode" width = "100" align = "center" > < / e l - t a b l e - c o l u m n >
< el -table -column label = "Running Time" width = "150" align = "center" >
< template slot -scope = " scope " >
< span v-if ="scope.row.stoppedAt === undefined" >
< font -awesome -icon icon = "spinner" spin / >
2019-07-24 05:25:30 -07:00
< execution -time :start-time ="scope.row.startedAt" / >
2019-06-23 03:35:23 -07:00
< / span >
2021-02-08 23:59:32 -08:00
<!-- stoppedAt will be null if process crashed -- >
< span v -else -if = " scope.row.stoppedAt = = = null " >
--
< / span >
2019-06-23 03:35:23 -07:00
< span v-else >
2019-07-24 05:25:30 -07:00
{ { displayTimer ( new Date ( scope . row . stoppedAt ) . getTime ( ) - new Date ( scope . row . startedAt ) . getTime ( ) , true ) } }
2019-06-23 03:35:23 -07:00
< / span >
< / template >
< / e l - t a b l e - c o l u m n >
< el -table -column label = "" width = "100" align = "center" >
< template slot -scope = " scope " >
2021-02-13 11:40:27 -08:00
< span v-if ="scope.row.stoppedAt === undefined" >
< el -button circle title = "Stop Execution" @click.stop ="stopExecution(scope.row.id)" :loading ="stoppingExecutions.includes(scope.row.id)" size = "mini" >
2019-06-23 03:35:23 -07:00
< font -awesome -icon icon = "stop" / >
< / e l - b u t t o n >
< / span >
2019-07-24 05:25:30 -07:00
< span v -else -if = " scope.row.id " >
2019-06-23 03:35:23 -07:00
< el -button circle title = "Open Past Execution" @click.stop ="displayExecution(scope.row)" size = "mini" >
< font -awesome -icon icon = "folder-open" / >
< / e l - b u t t o n >
< / span >
< / template >
< / e l - t a b l e - c o l u m n >
< / e l - t a b l e >
2021-07-06 14:25:25 -07:00
< div class = "load-more" v-if ="finishedExecutionsCount > finishedExecutions.length || finishedExecutionsCountEstimated === true" >
2019-06-23 03:35:23 -07:00
< el -button title = "Load More" @click ="loadMore()" size = "small" :disabled ="isDataLoading" >
< font -awesome -icon icon = "sync" / > Load More
< / e l - b u t t o n >
< / div >
< / e l - d i a l o g >
< / span >
< / template >
< script lang = "ts" >
import Vue from 'vue' ;
2019-07-24 05:25:30 -07:00
import ExecutionTime from '@/components/ExecutionTime.vue' ;
2019-06-23 03:35:23 -07:00
import WorkflowActivator from '@/components/WorkflowActivator.vue' ;
2021-05-05 17:46:33 -07:00
import { externalHooks } from '@/components/mixins/externalHooks' ;
2019-06-23 03:35:23 -07:00
import { restApi } from '@/components/mixins/restApi' ;
import { genericHelpers } from '@/components/mixins/genericHelpers' ;
import { showMessage } from '@/components/mixins/showMessage' ;
import {
IExecutionsCurrentSummaryExtended ,
IExecutionDeleteFilter ,
IExecutionsListResponse ,
IExecutionShortResponse ,
IExecutionsSummary ,
IWorkflowShortResponse ,
} from '@/Interface' ;
import {
IDataObject ,
} from 'n8n-workflow' ;
2021-03-10 06:51:18 -08:00
import {
range as _range ,
} from 'lodash' ;
2019-06-23 03:35:23 -07:00
import mixins from 'vue-typed-mixins' ;
export default mixins (
2021-05-05 17:46:33 -07:00
externalHooks ,
2019-06-23 03:35:23 -07:00
genericHelpers ,
restApi ,
showMessage ,
) . extend ( {
name : 'ExecutionsList' ,
props : [
'dialogVisible' ,
] ,
components : {
2019-07-24 05:25:30 -07:00
ExecutionTime ,
2019-06-23 03:35:23 -07:00
WorkflowActivator ,
} ,
data ( ) {
return {
finishedExecutions : [ ] as IExecutionsSummary [ ] ,
finishedExecutionsCount : 0 ,
2021-07-06 14:25:25 -07:00
finishedExecutionsCountEstimated : false ,
2019-06-23 03:35:23 -07:00
checkAll : false ,
2021-02-09 14:32:40 -08:00
autoRefresh : true ,
autoRefreshInterval : undefined as undefined | NodeJS . Timer ,
2019-06-23 03:35:23 -07:00
filter : {
2019-12-12 15:39:56 -08:00
status : 'ALL' ,
2019-06-23 03:35:23 -07:00
workflowId : 'ALL' ,
} ,
isDataLoading : false ,
requestItemsPerRequest : 10 ,
selectedItems : { } as { [ key : string ] : boolean ; } ,
stoppingExecutions : [ ] as string [ ] ,
workflows : [ ] as IWorkflowShortResponse [ ] ,
2019-12-12 15:39:56 -08:00
statuses : [
{
id : 'ALL' ,
name : 'Any Status' ,
} ,
{
id : 'error' ,
name : 'Error' ,
} ,
{
id : 'running' ,
name : 'Running' ,
} ,
{
id : 'success' ,
name : 'Success' ,
} ,
] ,
2019-06-23 03:35:23 -07:00
} ;
} ,
computed : {
2019-07-24 05:25:30 -07:00
activeExecutions ( ) : IExecutionsCurrentSummaryExtended [ ] {
return this . $store . getters . getActiveExecutions ;
} ,
2019-06-23 03:35:23 -07:00
combinedExecutions ( ) : IExecutionsSummary [ ] {
const returnData : IExecutionsSummary [ ] = [ ] ;
2019-12-12 15:39:56 -08:00
if ( [ 'ALL' , 'running' ] . includes ( this . filter . status ) ) {
returnData . push . apply ( returnData , this . activeExecutions ) ;
}
if ( [ 'ALL' , 'error' , 'success' ] . includes ( this . filter . status ) ) {
returnData . push . apply ( returnData , this . finishedExecutions ) ;
}
2019-06-23 03:35:23 -07:00
return returnData ;
} ,
combinedExecutionsCount ( ) : number {
2021-07-06 14:25:25 -07:00
return 0 + this . activeExecutions . length + this . finishedExecutionsCount ;
2019-06-23 03:35:23 -07:00
} ,
numSelected ( ) : number {
if ( this . checkAll === true ) {
return this . finishedExecutionsCount ;
}
return Object . keys ( this . selectedItems ) . length ;
} ,
isIndeterminate ( ) : boolean {
if ( this . checkAll === true ) {
return false ;
}
if ( this . numSelected > 0 ) {
return true ;
}
return false ;
} ,
2019-12-12 15:39:56 -08:00
workflowFilterCurrent ( ) : IDataObject {
2019-06-23 03:35:23 -07:00
const filter : IDataObject = { } ;
if ( this . filter . workflowId !== 'ALL' ) {
filter . workflowId = this . filter . workflowId ;
}
return filter ;
} ,
2019-12-12 15:39:56 -08:00
workflowFilterPast ( ) : IDataObject {
const filter : IDataObject = { } ;
if ( this . filter . workflowId !== 'ALL' ) {
filter . workflowId = this . filter . workflowId ;
}
if ( [ 'error' , 'success' ] . includes ( this . filter . status ) ) {
filter . finished = this . filter . status === 'success' ;
}
return filter ;
} ,
2019-06-23 03:35:23 -07:00
} ,
watch : {
dialogVisible ( newValue , oldValue ) {
if ( newValue ) {
this . openDialog ( ) ;
}
} ,
} ,
methods : {
closeDialog ( ) {
// Handle the close externally as the visible parameter is an external prop
// and is so not allowed to be changed here.
this . $emit ( 'closeDialog' ) ;
2021-02-09 14:32:40 -08:00
if ( this . autoRefreshInterval ) {
clearInterval ( this . autoRefreshInterval ) ;
this . autoRefreshInterval = undefined ;
}
2019-06-23 03:35:23 -07:00
return false ;
} ,
displayExecution ( execution : IExecutionShortResponse ) {
this . $router . push ( {
name : 'ExecutionById' ,
params : { id : execution . id } ,
} ) ;
this . closeDialog ( ) ;
} ,
2021-02-09 14:32:40 -08:00
handleAutoRefreshToggle ( ) {
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 . loadAutoRefresh , 4 * 1000 ) ; // refresh data every 4 secs
}
} ,
2019-06-23 03:35:23 -07:00
handleCheckAllChange ( ) {
if ( this . checkAll === false ) {
Vue . set ( this , 'selectedItems' , { } ) ;
}
} ,
handleCheckboxChanged ( executionId : string ) {
if ( this . selectedItems [ executionId ] ) {
Vue . delete ( this . selectedItems , executionId ) ;
} else {
Vue . set ( this . selectedItems , executionId , true ) ;
}
} ,
async handleDeleteSelected ( ) {
const deleteExecutions = await this . confirmMessage ( ` Are you sure that you want to delete the ${ this . numSelected } selected executions? ` , 'Delete Executions?' , 'warning' , 'Yes, delete!' ) ;
if ( deleteExecutions === false ) {
return ;
}
this . isDataLoading = true ;
const sendData : IExecutionDeleteFilter = { } ;
if ( this . checkAll === true ) {
2019-07-22 11:29:06 -07:00
sendData . deleteBefore = this . finishedExecutions [ 0 ] . startedAt as Date ;
2019-06-23 03:35:23 -07:00
} else {
sendData . ids = Object . keys ( this . selectedItems ) ;
}
2019-12-12 15:39:56 -08:00
sendData . filters = this . workflowFilterPast ;
2019-06-23 03:35:23 -07:00
try {
await this . restApi ( ) . deleteExecutions ( sendData ) ;
} catch ( error ) {
this . isDataLoading = false ;
this . $showError ( error , 'Problem deleting executions' , 'There was a problem deleting the executions:' ) ;
return ;
}
this . isDataLoading = false ;
this . $showMessage ( {
title : 'Execution deleted' ,
message : 'The executions got deleted!' ,
type : 'success' ,
} ) ;
Vue . set ( this , 'selectedItems' , { } ) ;
this . checkAll = false ;
this . refreshData ( ) ;
} ,
handleFilterChanged ( ) {
this . refreshData ( ) ;
} ,
2019-12-12 16:12:38 -08:00
handleRetryClick ( commandData : { command : string , row : IExecutionShortResponse } ) {
let loadWorkflow = false ;
if ( commandData . command === 'currentlySaved' ) {
loadWorkflow = true ;
}
this . retryExecution ( commandData . row , loadWorkflow ) ;
} ,
2019-06-23 03:35:23 -07:00
getRowClass ( data : IDataObject ) : string {
2019-07-24 05:25:30 -07:00
const classes : string [ ] = [ ] ;
2019-06-23 03:35:23 -07:00
if ( ( data . row as IExecutionsSummary ) . stoppedAt === undefined ) {
classes . push ( 'currently-running' ) ;
}
return classes . join ( ' ' ) ;
} ,
2019-07-24 05:25:30 -07:00
getWorkflowName ( workflowId : string ) : string | undefined {
2019-06-23 03:35:23 -07:00
const workflow = this . workflows . find ( ( data ) => data . id === workflowId ) ;
if ( workflow === undefined ) {
2019-07-24 05:25:30 -07:00
return undefined ;
2019-06-23 03:35:23 -07:00
}
return workflow . name ;
} ,
async loadActiveExecutions ( ) : Promise < void > {
2019-12-12 15:39:56 -08:00
const activeExecutions = await this . restApi ( ) . getCurrentExecutions ( this . workflowFilterCurrent ) ;
2019-07-24 05:25:30 -07:00
for ( const activeExecution of activeExecutions ) {
if ( activeExecution . workflowId !== undefined && activeExecution . workflowName === undefined ) {
activeExecution . workflowName = this . getWorkflowName ( activeExecution . workflowId ) ;
}
}
this . $store . commit ( 'setActiveExecutions' , activeExecutions ) ;
2019-06-23 03:35:23 -07:00
} ,
2021-02-09 14:32:40 -08:00
async loadAutoRefresh ( ) : Promise < void > {
2021-02-17 15:09:39 -08:00
const filter = this . workflowFilterPast ;
2021-03-02 23:31:55 -08:00
// We cannot use firstId here as some executions finish out of order. Let's say
// You have execution ids 500 to 505 running.
// Suppose 504 finishes before 500, 501, 502 and 503.
// iF you use firstId, filtering id >= 504 you won't
// ever get ids 500, 501, 502 and 503 when they finish
const pastExecutionsPromise : Promise < IExecutionsListResponse > = this . restApi ( ) . getPastExecutions ( filter , 30 ) ;
2021-02-09 14:32:40 -08:00
const currentExecutionsPromise : Promise < IExecutionsCurrentSummaryExtended [ ] > = this . restApi ( ) . getCurrentExecutions ( { } ) ;
2021-02-17 15:09:39 -08:00
const results = await Promise . all ( [ pastExecutionsPromise , currentExecutionsPromise ] ) ;
2021-02-09 14:32:40 -08:00
for ( const activeExecution of results [ 1 ] ) {
if ( activeExecution . workflowId !== undefined && activeExecution . workflowName === undefined ) {
activeExecution . workflowName = this . getWorkflowName ( activeExecution . workflowId ) ;
}
}
this . $store . commit ( 'setActiveExecutions' , results [ 1 ] ) ;
2021-05-12 08:51:54 -07:00
// execution IDs are typed as string, int conversion is necessary so we can order.
const alreadyPresentExecutionIds = this . finishedExecutions . map ( exec => parseInt ( exec . id , 10 ) ) ;
2021-03-10 06:51:18 -08:00
let lastId = 0 ;
const gaps = [ ] as number [ ] ;
2021-03-02 23:31:55 -08:00
for ( let i = results [ 0 ] . results . length - 1 ; i >= 0 ; i -- ) {
const currentItem = results [ 0 ] . results [ i ] ;
2021-03-10 06:51:18 -08:00
const currentId = parseInt ( currentItem . id , 10 ) ;
if ( lastId !== 0 && isNaN ( currentId ) === false ) {
// We are doing this iteration to detect possible gaps.
// The gaps are used to remove executions that finished
// and were deleted from database but were displaying
// in this list while running.
if ( currentId - lastId > 1 ) {
// We have some gaps.
const range = _range ( lastId + 1 , currentId ) ;
gaps . push ( ... range ) ;
}
}
lastId = parseInt ( currentItem . id , 10 ) || 0 ;
2021-03-02 23:31:55 -08:00
// Check new results from end to start
// Add new items accordingly.
2021-05-12 08:51:54 -07:00
const executionIndex = alreadyPresentExecutionIds . indexOf ( currentId ) ;
2021-03-02 23:31:55 -08:00
if ( executionIndex !== - 1 ) {
// Execution that we received is already present.
if ( this . finishedExecutions [ executionIndex ] . finished === false && currentItem . finished === true ) {
// Concurrency stuff. This might happen if the execution finishes
// prior to saving all information to database. Somewhat rare but
// With auto refresh and several executions, it happens sometimes.
// So we replace the execution data so it displays correctly.
this . finishedExecutions [ executionIndex ] = currentItem ;
}
continue ;
}
// Find the correct position to place this newcomer
let j ;
for ( j = this . finishedExecutions . length - 1 ; j >= 0 ; j -- ) {
2021-05-12 08:51:54 -07:00
if ( currentId < parseInt ( this . finishedExecutions [ j ] . id , 10 ) ) {
2021-03-02 23:31:55 -08:00
this . finishedExecutions . splice ( j + 1 , 0 , currentItem ) ;
break ;
}
}
if ( j === - 1 ) {
this . finishedExecutions . unshift ( currentItem ) ;
}
}
2021-03-10 06:51:18 -08:00
this . finishedExecutions = this . finishedExecutions . filter ( execution => ! gaps . includes ( parseInt ( execution . id , 10 ) ) && lastId >= parseInt ( execution . id , 10 ) ) ;
2021-02-09 14:32:40 -08:00
this . finishedExecutionsCount = results [ 0 ] . count ;
2021-07-06 14:25:25 -07:00
this . finishedExecutionsCountEstimated = results [ 0 ] . estimated ;
2021-02-09 14:32:40 -08:00
} ,
2019-06-23 03:35:23 -07:00
async loadFinishedExecutions ( ) : Promise < void > {
2019-12-12 15:39:56 -08:00
if ( this . filter . status === 'running' ) {
this . finishedExecutions = [ ] ;
this . finishedExecutionsCount = 0 ;
2021-07-06 14:25:25 -07:00
this . finishedExecutionsCountEstimated = false ;
2019-12-12 15:39:56 -08:00
return ;
}
const data = await this . restApi ( ) . getPastExecutions ( this . workflowFilterPast , this . requestItemsPerRequest ) ;
2019-06-23 03:35:23 -07:00
this . finishedExecutions = data . results ;
this . finishedExecutionsCount = data . count ;
2021-07-06 14:25:25 -07:00
this . finishedExecutionsCountEstimated = data . estimated ;
2019-06-23 03:35:23 -07:00
} ,
async loadMore ( ) {
2019-12-12 15:39:56 -08:00
if ( this . filter . status === 'running' ) {
return ;
}
2019-06-23 03:35:23 -07:00
this . isDataLoading = true ;
2019-12-12 15:39:56 -08:00
const filter = this . workflowFilterPast ;
2019-12-12 11:57:11 -08:00
let lastId : string | number | undefined ;
2019-06-23 03:35:23 -07:00
if ( this . finishedExecutions . length !== 0 ) {
const lastItem = this . finishedExecutions . slice ( - 1 ) [ 0 ] ;
2019-12-12 11:57:11 -08:00
lastId = lastItem . id ;
2019-06-23 03:35:23 -07:00
}
let data : IExecutionsListResponse ;
try {
2019-12-12 11:57:11 -08:00
data = await this . restApi ( ) . getPastExecutions ( filter , this . requestItemsPerRequest , lastId ) ;
2019-06-23 03:35:23 -07:00
} catch ( error ) {
this . isDataLoading = false ;
this . $showError ( error , 'Problem loading workflows' , 'There was a problem loading the workflows:' ) ;
return ;
}
this . finishedExecutions . push . apply ( this . finishedExecutions , data . results ) ;
this . finishedExecutionsCount = data . count ;
2021-07-06 14:25:25 -07:00
this . finishedExecutionsCountEstimated = data . estimated ;
2019-06-23 03:35:23 -07:00
this . isDataLoading = false ;
} ,
async loadWorkflows ( ) {
try {
const workflows = await this . restApi ( ) . getWorkflows ( ) ;
workflows . sort ( ( a , b ) => {
if ( a . name . toLowerCase ( ) < b . name . toLowerCase ( ) ) {
return - 1 ;
}
if ( a . name . toLowerCase ( ) > b . name . toLowerCase ( ) ) {
return 1 ;
}
return 0 ;
} ) ;
// @ts-ignore
workflows . unshift ( {
id : 'ALL' ,
2019-12-12 15:39:56 -08:00
name : 'All Workflows' ,
2019-06-23 03:35:23 -07:00
} ) ;
Vue . set ( this , 'workflows' , workflows ) ;
} catch ( error ) {
this . $showError ( error , 'Problem loading workflows' , 'There was a problem loading the workflows:' ) ;
}
} ,
2019-07-24 05:25:30 -07:00
async openDialog ( ) {
2019-06-23 03:35:23 -07:00
Vue . set ( this , 'selectedItems' , { } ) ;
this . filter . workflowId = 'ALL' ;
this . checkAll = false ;
2019-07-24 05:25:30 -07:00
await this . loadWorkflows ( ) ;
await this . refreshData ( ) ;
2021-02-09 14:32:40 -08:00
this . handleAutoRefreshToggle ( ) ;
2021-05-05 17:46:33 -07:00
this . $externalHooks ( ) . run ( 'executionsList.openDialog' ) ;
2019-06-23 03:35:23 -07:00
} ,
2019-12-12 16:12:38 -08:00
async retryExecution ( execution : IExecutionShortResponse , loadWorkflow ? : boolean ) {
2019-06-23 03:35:23 -07:00
this . isDataLoading = true ;
try {
2019-12-12 16:12:38 -08:00
const retrySuccessful = await this . restApi ( ) . retryExecution ( execution . id , loadWorkflow ) ;
2019-06-23 03:35:23 -07:00
2019-08-08 22:37:10 -07:00
if ( retrySuccessful === true ) {
2019-06-23 03:35:23 -07:00
this . $showMessage ( {
title : 'Retry successful' ,
message : 'The retry was successful!' ,
type : 'success' ,
} ) ;
} else {
this . $showMessage ( {
title : 'Retry unsuccessful' ,
message : 'The retry was not successful!' ,
type : 'error' ,
} ) ;
}
this . isDataLoading = false ;
} catch ( error ) {
this . $showError ( error , 'Problem with retry' , 'There was a problem with the retry:' ) ;
this . isDataLoading = false ;
}
} ,
async refreshData ( ) {
this . isDataLoading = true ;
try {
const activeExecutionsPromise = this . loadActiveExecutions ( ) ;
const finishedExecutionsPromise = this . loadFinishedExecutions ( ) ;
await Promise . all ( [ activeExecutionsPromise , finishedExecutionsPromise ] ) ;
} catch ( error ) {
this . $showError ( error , 'Problem loading' , 'There was a problem loading the data:' ) ;
}
this . isDataLoading = false ;
} ,
statusTooltipText ( entry : IExecutionsSummary ) : string {
if ( entry . stoppedAt === undefined ) {
return 'The worklow is currently executing.' ;
2019-08-08 11:24:37 -07:00
} else if ( entry . finished === true && entry . retryOf !== undefined ) {
return ` The workflow execution was a retry of " ${ entry . retryOf } " and it was successful. ` ;
2019-06-23 03:35:23 -07:00
} else if ( entry . finished === true ) {
return 'The worklow execution was successful.' ;
} else if ( entry . retryOf !== undefined ) {
2020-05-04 13:06:13 -07:00
return ` The workflow execution was a retry of " ${ entry . retryOf } " and failed.<br />New retries have to be started from the original execution. ` ;
2019-06-23 03:35:23 -07:00
} else if ( entry . retrySuccessId !== undefined ) {
2020-05-04 13:06:13 -07:00
return ` The workflow execution failed but the retry " ${ entry . retrySuccessId } " was successful. ` ;
2021-03-02 23:31:55 -08:00
} else if ( entry . stoppedAt === null ) {
return 'The workflow execution is probably still running but it may have crashed and n8n cannot safely tell. ' ;
2019-06-23 03:35:23 -07:00
} else {
2020-05-04 13:06:13 -07:00
return 'The workflow execution failed.' ;
2019-06-23 03:35:23 -07:00
}
} ,
2019-07-24 05:25:30 -07:00
async stopExecution ( activeExecutionId : string ) {
2019-06-23 03:35:23 -07:00
try {
// Add it to the list of currently stopping executions that we
// can show the user in the UI that it is in progress
2019-07-24 05:25:30 -07:00
this . stoppingExecutions . push ( activeExecutionId ) ;
2019-06-23 03:35:23 -07:00
2021-07-23 08:50:47 -07:00
await this . restApi ( ) . stopCurrentExecution ( activeExecutionId ) ;
2019-06-23 03:35:23 -07:00
// Remove it from the list of currently stopping executions
2019-07-24 05:25:30 -07:00
const index = this . stoppingExecutions . indexOf ( activeExecutionId ) ;
2019-06-23 03:35:23 -07:00
this . stoppingExecutions . splice ( index , 1 ) ;
this . $showMessage ( {
title : 'Execution stopped' ,
2019-07-24 05:25:30 -07:00
message : ` The execution with the id " ${ activeExecutionId } " got stopped! ` ,
2019-06-23 03:35:23 -07:00
type : 'success' ,
} ) ;
this . refreshData ( ) ;
} catch ( error ) {
this . $showError ( error , 'Problem stopping execution' , 'There was a problem stopping the execuction:' ) ;
}
} ,
} ,
} ) ;
< / script >
< style scoped lang = "scss" >
2021-02-09 14:32:40 -08:00
. autorefresh {
padding - right : 0.5 em ;
text - align : right ;
}
2019-06-23 03:35:23 -07:00
. filters {
line - height : 2 em ;
. refresh - button {
position : absolute ;
right : 0 ;
}
}
. load - more {
margin : 2 em 0 0 0 ;
width : 100 % ;
text - align : center ;
}
. retry - button {
color : $ -- custom - error - text ;
background - color : $ -- custom - error - background ;
margin - left : 5 px ;
2021-03-02 23:31:55 -08:00
& . warning {
background - color : $ -- custom - warning - background ;
color : $ -- custom - warning - text ;
}
2019-06-23 03:35:23 -07:00
}
. selection - options {
height : 2 em ;
}
. status - badge {
position : relative ;
display : inline - block ;
padding : 0 10 px ;
height : 30 px ;
line - height : 30 px ;
border - radius : 15 px ;
text - align : center ;
font - weight : 400 ;
& . error {
background - color : $ -- custom - error - background ;
color : $ -- custom - error - text ;
}
& . running {
background - color : $ -- custom - running - background ;
color : $ -- custom - running - text ;
}
& . success {
background - color : $ -- custom - success - background ;
color : $ -- custom - success - text ;
}
2021-03-02 23:31:55 -08:00
& . warning {
background - color : $ -- custom - warning - background ;
color : $ -- custom - warning - text ;
}
2019-06-23 03:35:23 -07:00
}
. workflow - name {
font - weight : bold ;
}
< / style >
< style lang = "scss" >
. currently - running {
background - color : $ -- color - primary - light ! important ;
}
. el - table tr : hover . currently - running td {
2019-10-06 02:09:01 -07:00
background - color : darken ( $ -- color - primary - light , 3 % ) ! important ;
2019-06-23 03:35:23 -07:00
}
< / style >