<template>
<div class="editor-explorer">
<AlertWindow :popupData="popupData" />
<InactivityAlertWindow :popupData="inactivityWarningData" />
<deployment-toolbar
:title="project.name"
:shortTitle="`Project code: ${processMetaData.orderCode} | Customer: ${processMetaData.customerName} | Order number: ${processMetaData.orderNumber}`"
>
<v-badge inline left color="grey">{{ project.planningStatus || 'Planning Status' }}</v-badge>
<v-badge class="ml-2" inline left color="grey">{{
project.scheduleStatus || 'Schedule Status'
}}</v-badge>
</deployment-toolbar>
<InstanceActionToolbar
:colorsShown.sync="colorsShown"
:instanceId="instance ? instance.processInstanceId : null"
>
<div class="d-inline-flex action-toolbar-slot">
<v-btn color="primary" @click="editBpmn">Edit</v-btn>
<v-divider vertical></v-divider>
<v-tooltip :disabled="engineOnline" right>
<template v-slot:activator="{ on }">
<div v-on="on">
<div v-if="projectStarted">
<v-btn
color="orange"
class="white--text"
v-if="!isProjectPaused && !isProjectPausing && !isProjectStopped"
:loading="showPauseDialog"
:disabled="!isProjectRunning || !engineOnline"
@click="showPauseDialog = true"
>Pause</v-btn
>
<v-btn
color="success"
v-if="isProjectPaused || isProjectPausing"
:loading="showResumeDialog"
:disabled="!engineOnline"
@click="showResumeDialog = true"
>Resume</v-btn
>
<v-btn
color="error"
v-if="!isProjectStopped"
:loading="showStopDialog"
:disabled="!isProjectRunning || !engineOnline"
@click="showStopDialog = true"
>Cancel</v-btn
>
<v-btn
color="primary"
v-if="isProjectStopped"
:loading="showRestartDialog"
:disabled="!engineOnline"
@click="showRestartDialog = true"
>Restart</v-btn
>
</div>
<v-btn
v-else
color="primary"
:disabled="!engineOnline"
:loading="startingProject"
@click="startProject"
>Start</v-btn
>
</div>
</template>
<span>Project Engine is not reachable</span>
</v-tooltip>
<v-divider vertical></v-divider>
<div v-if="isProjectPausing" class="py-1">
<v-progress-circular indeterminate color="black" size="28" width="2">
<v-tooltip right>
<template v-slot:activator="{ on }">
<v-icon v-on="on" @click="resumeProject()">mdi-close</v-icon>
</template>
Abort pausing
</v-tooltip>
</v-progress-circular>
Pausing...
</div>
</div>
</InstanceActionToolbar>
<execution-overview
v-if="deployment"
:deployment="deployment"
:instance="instance"
:colorsShown="colorsShown"
:location="location"
:isProjectView="true"
@element:click="handleElementClick"
@deleteDeployment="showDeleteDeploymentDialog = true"
></execution-overview>
<confirmation
title="cancel the project?"
continueButtonText="Cancel Project"
continueButtonColor="error"
:show="showStopDialog"
maxWidth="500px"
@cancel="showStopDialog = false"
@continue="stopProject"
>
<div>
Currently running Scripts can not be stopped, thus they are executed until their end.
</div>
</confirmation>
<confirmation
title="restart the project?"
continueButtonText="Restart Project"
continueButtonColor="primary"
:show="showRestartDialog"
maxWidth="500px"
@cancel="showRestartDialog = false"
@continue="restartProject"
>
<div>
Project will restart from beginning. Information about current project will be deleted
irrecoverably.
</div>
</confirmation>
<confirmation
title="pause the project?"
continueButtonText="Pause Project"
continueButtonColor="primary"
:show="showPauseDialog"
maxWidth="500px"
@cancel="showPauseDialog = false"
@continue="pauseProject"
>
<div>
Hint: Currently running activities can not be paused. Therefore the Process is paused after
the running activities finished their task.
</div>
</confirmation>
<confirmation
title="resume the project?"
continueButtonText="Resume Project"
continueButtonColor="primary"
:show="showResumeDialog"
maxWidth="500px"
@cancel="showResumeDialog = false"
@continue="resumeProject"
>
<div>Paused tokens will resume at their current location.</div>
</confirmation>
<confirmation
title="Delete the deployment?"
continueButtonText="Delete Deployment"
continueButtonColor="primary"
:show="showDeleteDeploymentDialog"
maxWidth="500px"
@cancel="showDeleteDeploymentDialog = false"
@continue="deleteDeployment"
>
<div>
You are about to delete the Deployment! Information of current project will be deleted
irrecoverably.
</div>
</confirmation>
</div>
</template>
<script>
import {
getAllBpmnFlowNodeIds,
setMachineInfo,
getMetaData,
getProcessIds,
} from '@proceed/bpmn-helper';
import { engineNetworkInterface } from '../backend-api/index.js';
import AlertWindow from '@/frontend/components/universal/Alert.vue';
import InactivityAlertWindow from '@/frontend/components/universal/InactivityAlert.vue';
import * as customCommands from '../helpers/bpmn-modeler-events/custom-modeler-commands.js';
import Confirmation from '@/frontend/components/universal/Confirmation.vue';
import DeploymentToolbar from '@/frontend/components/deployments/DeploymentToolbar.vue';
import InstanceActionToolbar from '@/frontend/components/deployments/InstanceActionToolbar.vue';
import ExecutionOverview from '@/frontend/components/deployments/ExecutionOverview.vue';
/**
* @module views
*/
/**
* @memberof module:views
*/
/**
* This view is opened when you click on the "Open Project" in the Projects views.
* It shows the bpmn diagram of a project (without the option to edit it)
* If the project was started (it was deployed and an instance exists), instance info
* is also shown.
*
* @module Vue:ProjectOverview
*
* @vue-computed {Object} project
* @vue-computed {Object} selectedElement - the activity/event for which detailed info should be shown
* @vue-computed {String} startTime - the time when the project was started, empty if hasn't started yet
* @vue-computed {Boolean} isProjectRunning - if yes, the stop instance button is enabled
* @vue-computed {String} engineUrl - the address of the engine on which the Project is supposed to be executed
* @vue-computed {String} engineHost - the ip-address of the engine
* @vue-computed {String} enginePort - the port of the engine
*/
export default {
name: 'project-overview',
components: {
AlertWindow,
InactivityAlertWindow,
Confirmation,
ExecutionOverview,
DeploymentToolbar,
InstanceActionToolbar,
},
data() {
return {
clickedElement: null,
showStopDialog: false,
showRestartDialog: false,
showPauseDialog: false,
showResumeDialog: false,
showDeleteDeploymentDialog: false,
startingProject: false,
popupData: {
body: '',
display: 'none',
color: '',
metaData: '',
},
inactivityWarningData: {
display: 'none',
color: 'info',
},
instance: null,
deployment: null,
subprocessId: null,
pollingInterval: null,
location: {
timeZone: 'Europe/Berlin',
},
colorsShown: 'processColors',
isInfoPanelVisible: false,
engineOnline: false,
metaData: {},
processMetaData: {},
};
},
computed: {
project() {
return this.$store.getters['processStore/processById'](this.$router.currentRoute.params.id);
},
selectedElement() {
return this.clickedElement ? this.clickedElement : null;
},
startTime() {
if (this.instance) {
return new Date(this.instance.globalStartTime).toLocaleString('en-US', this.location);
}
return '';
},
projectStarted() {
return !!(this.instance && Object.keys(this.instance).length > 0);
},
isProjectRunning() {
const runningStates = ['RUNNING', 'READY', 'DEPLOYMENT-WAITING'];
if (this.instance) {
return this.instance.instanceState.some((state) => runningStates.includes(state));
} else {
return false;
}
},
isProjectPausing() {
if (this.instance) {
return this.instance.instanceState.some((state) => state === 'PAUSING');
} else {
return false;
}
},
isProjectPaused() {
if (this.instance) {
return this.instance.instanceState.some((state) => state === 'PAUSED');
} else {
return false;
}
},
isProjectStopped() {
if (this.instance) {
return this.instance.instanceState.some((state) => state === 'STOPPED');
} else {
return false;
}
},
engineUrl() {
return this.$store.getters['configStore/config'].processEngineUrl;
},
engineHost() {
return this.engineUrl.split(':')[0];
},
enginePort() {
return this.engineUrl.split(':')[1];
},
},
watch: {
project: {
async handler(newProject) {
const newXml = await this.$store.getters['processStore/xmlById'](
this.$router.currentRoute.params.id
);
const [processId] = await getProcessIds(newXml);
this.processMetaData = await getMetaData(newXml, processId);
this.deployment = {
...this.deployment,
definitionName: newProject.name,
bpmn: newXml,
};
},
immediate: true,
},
},
methods: {
/**
* Opens the ProcesBpmnEditor view
*/
editBpmn() {
this.$router.push({ name: 'edit-project-bpmn', params: { id: this.project.id } });
},
/**
* Deploys the project statically using the engineUrl as machine address and starts an instance
*/
async startProject() {
if (this.engineUrl) {
this.startingProject = true;
const elements = await getAllBpmnFlowNodeIds(this.deployment.bpmn);
const machineMapping = {};
elements.forEach((el) => {
machineMapping[el] = {
machineAddress: this.engineUrl,
};
});
const deployProcessXml = await setMachineInfo(this.deployment.bpmn, machineMapping);
await this.$store.dispatch('processStore/updateWholeXml', {
id: this.project.id,
bpmn: deployProcessXml,
});
if (!this.project.shared) {
this.popupData.body = 'Sending Project to Server to enable execution...';
this.popupData.color = 'primary';
this.popupData.display = 'block';
await this.$store.dispatch('processStore/update', {
id: this.project.id,
changes: { shared: true },
});
}
setTimeout(async () => {
try {
await engineNetworkInterface.deployProcess(this.project.id, false);
try {
await engineNetworkInterface.startInstance(this.project.id);
this.popupData.body = 'Project started successfully';
this.popupData.color = 'success';
this.getInstanceInfo();
this.startPollingInstanceInfo();
} catch (err2) {
this.popupData.body = `Failed to start process instance: ${err2.message}`;
this.popupData.color = 'error';
}
} catch (err) {
this.popupData.body = `Failed to deploy process: ${err.message}`;
this.popupData.color = 'error';
}
this.startingProject = false;
this.popupData.display = 'block';
setTimeout(() => {
this.popupData.display = 'none';
}, 5000);
}, 3000);
}
},
/**
* Stops the project execution
*/
async stopProject() {
this.showStopDialog = false;
try {
await engineNetworkInterface.stopInstance(this.project.id, this.instance.processInstanceId);
this.popupData.body = 'Canceled project';
this.popupData.color = 'primary';
} catch (err) {
this.popupData.body = `Error stopping the project: ${err.message}`;
this.popupData.color = 'error';
console.error(err);
}
this.popupData.display = 'block';
setTimeout(() => {
this.popupData.display = 'none';
}, 5000);
},
/**
* Restarts the stopped project execution
*/
async restartProject() {
this.showRestartDialog = false;
try {
await engineNetworkInterface.startInstance(this.project.id);
this.popupData.body = 'Project restarted successfully';
this.popupData.color = 'success';
} catch (err) {
this.popupData.body = `Error restarting the project: ${err.message}`;
this.popupData.color = 'error';
}
this.popupData.display = 'block';
setTimeout(() => {
this.popupData.display = 'none';
}, 5000);
},
/**
* Pauses the project execution
*/
async pauseProject() {
this.showPauseDialog = false;
try {
await engineNetworkInterface.pauseInstance(
this.project.id,
this.instance.processInstanceId
);
this.popupData.body = 'Paused project';
this.popupData.color = 'primary';
} catch (err) {
this.popupData.body = `Error pausing the project: ${err.message}`;
this.popupData.color = 'error';
console.error(err);
}
this.popupData.display = 'block';
setTimeout(() => {
this.popupData.display = 'none';
}, 5000);
},
/**
* Pauses the project execution
*/
async resumeProject() {
this.showResumeDialog = false;
try {
await engineNetworkInterface.resumeInstance(
this.project.id,
this.instance.processInstanceId
);
this.popupData.body = 'Resumed project';
this.popupData.color = 'primary';
} catch (err) {
this.popupData.body = `Error resuming the project: ${err.message}`;
this.popupData.color = 'error';
console.error(err);
}
this.popupData.display = 'block';
setTimeout(() => {
this.popupData.display = 'none';
}, 5000);
},
/**
* If the clicked element is not the whole process or a sequence flow, set the clicked element and get its meta information
* @param {Object} element - the clicked element
*/
handleElementClick(element) {
if (element.type === 'bpmn:Process' || element.type === 'bpmn:SequenceFlow') {
this.clickedElement = null;
} else {
this.clickedElement = element;
}
},
async deleteDeployment() {
this.$store.dispatch('instanceStore/removeInstance', this.project.id);
const response = await engineNetworkInterface.removeDeployment(this.project.id);
this.instance = null;
clearInterval(this.pollingInterval);
this.showDeleteDeploymentDialog = false;
},
/**
* If an instance exists, fetch info about it from the given engine
*/
async getInstanceInfo() {
const machine = {
ip: this.engineHost,
port: this.enginePort,
};
try {
const instances = await engineNetworkInterface.getProcessInstances(
machine,
this.project.id
);
const instanceId = instances.pop();
if (instanceId) {
this.instance = await engineNetworkInterface.getInstanceInformation(
machine,
this.project.id,
instanceId
);
this.instance.machines = [
...new Map(this.instance.log.map((l) => [l.machine.id, l.machine])).values(),
];
this.$store.dispatch('instanceStore/addInstanceInformation', {
definitionId: this.project.id,
...this.instance,
});
} else {
this.instance = null;
clearInterval(this.pollingInterval);
}
this.engineOnline = true;
} catch (error) {
this.engineOnline = false;
}
},
/**
* Call the function to get updated info about an existing instance every 5 seconds
*/
startPollingInstanceInfo() {
this.pollingInterval = setInterval(async () => {
this.getInstanceInfo();
}, 5000);
},
},
async beforeMount() {
const routerProcessDefinitionsId = this.$router.currentRoute.params.id;
const bpmn = await this.$store.getters['processStore/xmlById'](routerProcessDefinitionsId);
this.deployment = {
bpmn,
definitionId: routerProcessDefinitionsId,
};
const instance = await this.$store.getters['instanceStore/instanceById'](
routerProcessDefinitionsId
);
if (instance) {
this.instance = instance;
}
try {
const deployments = await engineNetworkInterface.getDeployedProcesses({
ip: this.engineHost,
port: this.enginePort,
});
if (deployments.find((d) => d.definitionId == routerProcessDefinitionsId)) {
this.deployment = deployments.find((d) => d.definitionId == routerProcessDefinitionsId);
this.getInstanceInfo();
this.startPollingInstanceInfo();
}
this.engineOnline = true;
} catch (err) {
if (this.isProjectRunning) {
this.popupData.body = `Could not fetch current project status from Engine ${this.engineUrl}. Showing latest known state of project`;
this.popupData.color = 'error';
this.popupData.display = 'block';
}
setTimeout(() => {
this.popupData.display = 'none';
}, 5000);
}
},
created() {
if (this.$store.getters['warningStore/showWarning'] == true) {
this.inactivityWarningData.display = 'block';
this.$store.commit('warningStore/setWarning', false);
}
},
mounted() {
engineNetworkInterface.subscribeForDeploymentsUpdates();
engineNetworkInterface.startMachinePolling();
},
beforeRouteLeave(to, from, next) {
clearInterval(this.pollingInterval);
next();
},
beforeDestroy() {
clearInterval(this.pollingInterval);
},
};
</script>
<style lang="scss" scoped>
.action-toolbar-slot .v-btn {
width: 100px;
margin: 0px 5px;
}
.action-toolbar-slot .v-divider {
border: 1px solid rgb(224, 222, 222);
margin: -5px 20px;
}
.editor-explorer {
display: flex;
flex: 1;
flex-direction: column;
height: 100%;
}
</style>