stash
This commit is contained in:
parent
6fcdd1eefa
commit
c0f70004eb
21 changed files with 1855 additions and 260 deletions
|
|
@ -10,12 +10,11 @@
|
|||
<li class="breadcrumb-item">
|
||||
<router-link to="/workflows" class="text-decoration-none">Workflows</router-link>
|
||||
</li>
|
||||
<li class="breadcrumb-item active" aria-current="page">{{ workflowDefinition?.name || workflowInstance?.name || 'Loading...' }}</li>
|
||||
<li class="breadcrumb-item active" aria-current="page">{{ workflowDefinition?.title || workflowInstance?.title || 'Loading...' }}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
<h1 class="h3 mb-0">{{ workflowDefinition?.name || workflowInstance?.name || 'Workflow Detail' }}</h1>
|
||||
</div>
|
||||
<div class="btn-group" role="group">
|
||||
<div class="btn-group breadcrumb" role="group">
|
||||
<button class="btn btn-outline-secondary" @click="$router.go(-1)">
|
||||
<b-icon-arrow-left class="me-1"></b-icon-arrow-left>
|
||||
Back
|
||||
|
|
@ -43,8 +42,86 @@
|
|||
|
||||
<!-- Workflow Content -->
|
||||
<div v-else-if="workflowInstance" class="row">
|
||||
<!-- Main Content Area -->
|
||||
<div class="col-xl-9 col-lg-12 col-lg-9-ex">
|
||||
<!-- Current Step Content -->
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h5 class="card-title mb-0">
|
||||
{{ currentStepDefinition?.name || `Step ${currentStep}` }}
|
||||
</h5>
|
||||
<small class="text-muted">{{ currentStepDefinition?.description }}</small>
|
||||
</div>
|
||||
<div class="step-navigation">
|
||||
<button class="btn btn-sm btn-outline-secondary me-1"
|
||||
@click="handlePrevStep"
|
||||
:disabled="!canNavigatePrev">
|
||||
<b-icon-chevron-left></b-icon-chevron-left>
|
||||
</button>
|
||||
<span class="mx-2 small">{{ currentStepIndex + 1 }} / {{ totalSteps }}</span>
|
||||
<button class="btn btn-sm btn-outline-secondary"
|
||||
@click="handleNextStep"
|
||||
:disabled="!canNavigateNext">
|
||||
<b-icon-chevron-right></b-icon-chevron-right>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<!-- Step-specific content based on workflow type and current step -->
|
||||
<component
|
||||
v-if="workflowComponent"
|
||||
:is="workflowComponent"
|
||||
:workflow-instance="workflowInstance"
|
||||
:step="currentStep"
|
||||
:payload="workflowInstance.payload"
|
||||
@update="handleStepUpdate"
|
||||
@next="handleNextStep"
|
||||
@prev="handlePrevStep"
|
||||
@complete="completeWorkflow"
|
||||
/>
|
||||
|
||||
<!-- Default step content if no specific component -->
|
||||
<div v-else class="text-center py-5">
|
||||
<b-icon-gear class="text-muted mb-3" style="font-size: 3rem;"></b-icon-gear>
|
||||
<h5 class="text-muted">{{ currentStepDefinition?.name || 'Step Content' }}</h5>
|
||||
<p class="text-muted">{{ currentStepDefinition?.description || 'This step is in progress.' }}</p>
|
||||
|
||||
<!-- Step Navigation Buttons -->
|
||||
<div class="mt-4">
|
||||
<button v-if="canNavigatePrev"
|
||||
class="btn btn-outline-secondary me-2"
|
||||
@click="handlePrevStep">
|
||||
<b-icon-chevron-left class="me-1"></b-icon-chevron-left>
|
||||
Previous
|
||||
</button>
|
||||
<button v-if="canNavigateNext"
|
||||
class="btn btn-primary"
|
||||
@click="handleNextStep">
|
||||
Next
|
||||
<b-icon-chevron-right class="ms-1"></b-icon-chevron-right>
|
||||
</button>
|
||||
<button v-else-if="currentStepIndex === stepDefinitions.length - 1"
|
||||
class="btn btn-success"
|
||||
@click="completeWorkflow">
|
||||
<b-icon-check-circle class="me-1"></b-icon-check-circle>
|
||||
Complete Workflow
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Debug Info (only in development) -->
|
||||
<div v-if="true" class="mt-4 border-top pt-3">
|
||||
<details>
|
||||
<summary class="text-muted small">Debug Info</summary>
|
||||
<pre class="small mt-2">{{ JSON.stringify(workflowInstance, null, 2) }}</pre>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Workflow Progress Sidebar -->
|
||||
<div class="col-lg-3 mb-4">
|
||||
<div class="col-lg-3 mb-4 d-none d-xl-block d-lg-block-ex">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h6 class="card-title mb-0">Progress Overview</h6>
|
||||
|
|
@ -113,85 +190,6 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content Area -->
|
||||
<div class="col-lg-9">
|
||||
<!-- Current Step Content -->
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h5 class="card-title mb-0">
|
||||
{{ currentStepDefinition?.name || `Step ${currentStep}` }}
|
||||
</h5>
|
||||
<small class="text-muted">{{ currentStepDefinition?.description }}</small>
|
||||
</div>
|
||||
<div class="step-navigation">
|
||||
<button class="btn btn-sm btn-outline-secondary me-1"
|
||||
@click="handlePrevStep"
|
||||
:disabled="!canNavigatePrev">
|
||||
<b-icon-chevron-left></b-icon-chevron-left>
|
||||
</button>
|
||||
<span class="mx-2 small">{{ currentStepIndex + 1 }} / {{ totalSteps }}</span>
|
||||
<button class="btn btn-sm btn-outline-secondary"
|
||||
@click="handleNextStep"
|
||||
:disabled="!canNavigateNext">
|
||||
<b-icon-chevron-right></b-icon-chevron-right>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<!-- Step-specific content based on workflow type and current step -->
|
||||
<component
|
||||
v-if="workflowComponent"
|
||||
:is="workflowComponent"
|
||||
:workflow-instance="workflowInstance"
|
||||
:step="currentStep"
|
||||
:payload="workflowInstance.payload"
|
||||
@update="handleStepUpdate"
|
||||
@next="handleNextStep"
|
||||
@prev="handlePrevStep"
|
||||
@complete="completeWorkflow"
|
||||
/>
|
||||
|
||||
<!-- Default step content if no specific component -->
|
||||
<div v-else class="text-center py-5">
|
||||
<b-icon-gear class="text-muted mb-3" style="font-size: 3rem;"></b-icon-gear>
|
||||
<h5 class="text-muted">{{ currentStepDefinition?.name || 'Step Content' }}</h5>
|
||||
<p class="text-muted">{{ currentStepDefinition?.description || 'This step is in progress.' }}</p>
|
||||
|
||||
<!-- Step Navigation Buttons -->
|
||||
<div class="mt-4">
|
||||
<button v-if="canNavigatePrev"
|
||||
class="btn btn-outline-secondary me-2"
|
||||
@click="handlePrevStep">
|
||||
<b-icon-chevron-left class="me-1"></b-icon-chevron-left>
|
||||
Previous
|
||||
</button>
|
||||
<button v-if="canNavigateNext"
|
||||
class="btn btn-primary"
|
||||
@click="handleNextStep">
|
||||
Next
|
||||
<b-icon-chevron-right class="ms-1"></b-icon-chevron-right>
|
||||
</button>
|
||||
<button v-else-if="currentStepIndex === stepDefinitions.length - 1"
|
||||
class="btn btn-success"
|
||||
@click="completeWorkflow">
|
||||
<b-icon-check-circle class="me-1"></b-icon-check-circle>
|
||||
Complete Workflow
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Debug Info (only in development) -->
|
||||
<div v-if="$isDevelopment" class="mt-4 border-top pt-3">
|
||||
<details>
|
||||
<summary class="text-muted small">Debug Info</summary>
|
||||
<pre class="small mt-2">{{ JSON.stringify(workflowInstance, null, 2) }}</pre>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
|
@ -217,7 +215,7 @@ export default {
|
|||
},
|
||||
step: {
|
||||
type: String,
|
||||
default: "initial"
|
||||
default: "1"
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
|
@ -232,13 +230,17 @@ export default {
|
|||
...mapState(['active_workflows']),
|
||||
|
||||
currentStep() {
|
||||
return this.step || "initial";
|
||||
return this.step || "1";
|
||||
},
|
||||
|
||||
workflowDefinition() {
|
||||
// `name` doubles as the workflow type identifier (e.g. 'import-items').
|
||||
if (!this.workflowInstance?.name) return null;
|
||||
return getWorkflow(this.workflowInstance.name);
|
||||
if (!this.workflowInstance?.slug) return null;
|
||||
return getWorkflow(this.workflowInstance.slug);
|
||||
},
|
||||
|
||||
getWorkflowDisplayName() {
|
||||
return getWorkflow(this.workflowInstance.slug)?.title;
|
||||
},
|
||||
|
||||
stepDefinitions() {
|
||||
|
|
@ -261,7 +263,7 @@ export default {
|
|||
// Return the single component implementing the whole workflow, if any,
|
||||
// using the workflow component registry. The component itself decides
|
||||
// what to render based on the `step` prop it receives.
|
||||
const workflowType = this.workflowInstance?.name;
|
||||
const workflowType = this.workflowInstance?.slug;
|
||||
return workflowType ? getWorkflowComponent(workflowType) : null;
|
||||
},
|
||||
|
||||
|
|
@ -506,4 +508,8 @@ export default {
|
|||
.step-navigation {
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.btn-group.breadcrumb{
|
||||
padding: calc(0.5rem - 1px) 1rem;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -16,49 +16,51 @@
|
|||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Workflow</th>
|
||||
<th>Status</th>
|
||||
<th>Progress</th>
|
||||
<th>Started</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Workflow</th>
|
||||
<th>Status</th>
|
||||
<th>Progress</th>
|
||||
<th>Started</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="workflow in activeWorkflows" :key="workflow.id">
|
||||
<td>
|
||||
<strong>{{ getWorkflowDisplayName(workflow) }}</strong>
|
||||
<br>
|
||||
<small class="text-muted">{{ workflow.payload?.workflow_config?.category || 'System' }}</small>
|
||||
</td>
|
||||
<td>
|
||||
<tr v-for="workflow in activeWorkflows" :key="workflow.id">
|
||||
<td>
|
||||
<strong>{{ getWorkflowDisplayName(workflow) }}</strong>
|
||||
<br>
|
||||
<small class="text-muted">{{
|
||||
workflow.payload?.workflow_config?.category || 'System'
|
||||
}}</small>
|
||||
</td>
|
||||
<td>
|
||||
<span :class="getStatusBadgeClass(workflow.state)">
|
||||
{{ workflow.status_display || workflow.state }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="progress" style="height: 8px;">
|
||||
<div class="progress-bar"
|
||||
:style="{ width: workflow.progress_percentage + '%' }"
|
||||
:class="getProgressBarClass(workflow.state)">
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="progress" style="height: 8px;">
|
||||
<div class="progress-bar"
|
||||
:style="{ width: workflow.progress_percentage + '%' }"
|
||||
:class="getProgressBarClass(workflow.state)">
|
||||
</div>
|
||||
<small class="text-muted">{{ workflow.progress_percentage }}%</small>
|
||||
</td>
|
||||
<td>{{ formatDate(workflow.started_at) }}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-primary me-1"
|
||||
@click="viewWorkflowDetails(workflow)"
|
||||
:disabled="loading">
|
||||
<b-icon-eye></b-icon-eye>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-danger"
|
||||
@click="abortWorkflowInstance(workflow)"
|
||||
:disabled="loading">
|
||||
<b-icon-x-circle></b-icon-x-circle>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</div>
|
||||
<small class="text-muted">{{ workflow.progress_percentage }}%</small>
|
||||
</td>
|
||||
<td>{{ formatDate(workflow.started_at) }}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-primary me-1"
|
||||
@click="viewWorkflowDetails(workflow)"
|
||||
:disabled="loading">
|
||||
<b-icon-eye></b-icon-eye>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-danger"
|
||||
@click="abortWorkflowInstance(workflow)"
|
||||
:disabled="loading">
|
||||
<b-icon-x-circle></b-icon-x-circle>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
|
@ -77,21 +79,25 @@
|
|||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div v-for="workflow in availableWorkflows" :key="workflow.id" class="col-lg-4 col-md-6 mb-3">
|
||||
<div v-for="workflow in availableWorkflows" :key="workflow.id"
|
||||
class="col-lg-4 col-md-6 mb-3">
|
||||
<div class="card h-100 workflow-card">
|
||||
<div class="card-body d-flex flex-column">
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<template v-for="(icon, index) in workflow.icons" :key="icon">
|
||||
<div class="workflow-icon me-3">
|
||||
<component :is="icon" class="text-primary" style="font-size: 1.5rem;"></component>
|
||||
<component :is="icon" class="text-primary"
|
||||
style="font-size: 1.5rem;"></component>
|
||||
</div>
|
||||
<!-- Add arrow between icons, but not after the last one -->
|
||||
<div v-if="index < workflow.icons.length - 1" class="workflow-arrow me-3">
|
||||
<b-icon-arrow-right class="text-muted" style="font-size: 1rem;"></b-icon-arrow-right>
|
||||
<div v-if="index < workflow.icons.length - 1"
|
||||
class="workflow-arrow me-3">
|
||||
<b-icon-arrow-right class="text-muted"
|
||||
style="font-size: 1rem;"></b-icon-arrow-right>
|
||||
</div>
|
||||
</template>
|
||||
<div class="workflow-header">
|
||||
<h6 class="card-title mb-1">{{ workflow.name }}</h6>
|
||||
<h6 class="card-title mb-1">{{ workflow.title }}</h6>
|
||||
<small class="text-muted">{{ workflow.category }}</small>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -105,10 +111,9 @@
|
|||
<span class="badge bg-light text-dark">{{ workflow.steps }} steps</span>
|
||||
</div>
|
||||
<button class="btn btn-primary w-100"
|
||||
@click="startWorkflow(workflow)"
|
||||
:disabled="isWorkflowRunning(workflow.id)">
|
||||
@click="startWorkflow(workflow)">
|
||||
<b-icon-play-fill class="me-1"></b-icon-play-fill>
|
||||
{{ isWorkflowRunning(workflow.id) ? 'Running...' : 'Start Workflow' }}
|
||||
Start Workflow
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -127,8 +132,8 @@
|
|||
<script>
|
||||
import * as BIcons from "bootstrap-icons-vue";
|
||||
import BaseLayout from "@/components/BaseLayout.vue";
|
||||
import { mapState, mapActions } from 'vuex';
|
||||
import { getAllWorkflows, getWorkflow, buildWorkflowApiPayload } from '@/workflows.js';
|
||||
import {mapState, mapActions} from 'vuex';
|
||||
import {getAllWorkflows, getWorkflow, buildWorkflowApiPayload} from '@/workflows.js';
|
||||
|
||||
export default {
|
||||
name: 'Workflows',
|
||||
|
|
@ -213,15 +218,8 @@ export default {
|
|||
minute: '2-digit'
|
||||
}).format(date);
|
||||
},
|
||||
isWorkflowRunning(workflowId) {
|
||||
return this.activeWorkflows.some(active =>
|
||||
active.name === workflowId &&
|
||||
active.state === 'running'
|
||||
);
|
||||
},
|
||||
getWorkflowDisplayName(workflow) {
|
||||
// `name` doubles as the workflow type identifier (e.g. 'import-items').
|
||||
return getWorkflow(workflow.name)?.name || workflow.name;
|
||||
return getWorkflow(workflow.slug)?.title;
|
||||
},
|
||||
async startWorkflow(workflow) {
|
||||
try {
|
||||
|
|
@ -232,11 +230,10 @@ export default {
|
|||
const workflowData = buildWorkflowApiPayload(workflow);
|
||||
|
||||
const newWorkflow = await this.createWorkflow(workflowData);
|
||||
console.log('Workflow started successfully:', newWorkflow);
|
||||
|
||||
// Immediately navigate to the workflow detail view
|
||||
// Get the first step from the workflow definition
|
||||
const firstStep = workflow.stepDefinitions?.[0]?.step || "initial";
|
||||
const firstStep = workflow.stepDefinitions?.[0]?.step || 1;
|
||||
this.$router.push({
|
||||
name: 'workflow-detail',
|
||||
params: {
|
||||
|
|
@ -257,9 +254,9 @@ export default {
|
|||
// Navigate to the workflow detail view
|
||||
// Use the workflow's current step if available, otherwise use the first step
|
||||
const currentStep = workflow.current_step ||
|
||||
workflow.payload?.current_step ||
|
||||
getWorkflow(workflow.name)?.stepDefinitions?.[0]?.step ||
|
||||
"initial";
|
||||
workflow.payload?.current_step ||
|
||||
getWorkflow(workflow.name)?.stepDefinitions?.[0]?.step ||
|
||||
"initial";
|
||||
|
||||
this.$router.push({
|
||||
name: 'workflow-detail',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue