<template>
<div class="wrapper">
<popup :popupData="popupData" />
<deployment-toolbar :title="deployment.name">
<div style="margin: 5px">
<v-select
:items="selectableInstances"
v-model="selectedInstance"
label="Instance"
no-data-text="No instances found"
hide-details
clearable
small
single-line
return-object
></v-select>
</div>
</deployment-toolbar>
<InstanceActionToolbar
:colorsShown.sync="colorsShown"
:instanceId="selectedInstance ? selectedInstance.processInstanceId : null"
>
<div v-if="selectedInstance">
<v-btn
color="error"
:loading="isStoppingInstance"
:disabled="!selectedIsRunning"
@click.stop="stopInstance"
>
Stop Instance
</v-btn>
<v-btn
color="success"
:loading="isResumingInstance"
v-if="selectedInstance.instanceState.some((state) => state === 'PAUSED')"
@click.stop="resumeInstance"
>
Resume Instance
</v-btn>
<v-btn
color="warning"
:loading="isPausingInstance"
v-else
:disabled="!selectedIsRunning"
@click.stop="pauseInstance"
>
Pause Instance
</v-btn>
<v-btn color="primary" :loading="isStartingInstance" @click.stop="startInstance">
Start New Instance
</v-btn>
</div>
<v-btn v-else color="primary" :loading="isStartingInstance" @click.stop="startInstance">
Start Instance
</v-btn>
</InstanceActionToolbar>
<div class="d-flex flex-grow-1" v-if="deployment">
<!-- Deployment/Instance Card -->
<div class="card-wrapper">
<InstanceCard v-if="!selectedInstance" :deployment="deployment" />
</div>
<div class="d-flex flex-grow-1">
<execution-overview
:deployment="deployment"
:instance="selectedInstance"
:colorsShown="colorsShown"
></execution-overview>
</div>
</div>
</div>
</template>
<script>
import { engineNetworkInterface } from '@/frontend/backend-api/index.js';
import AlertWindow from '@/frontend/components/universal/Alert.vue';
import InstanceCard from '@/frontend/components/deployments/InstanceCard.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
* @module Vue:DeploymentMonitoring
*/
export default {
components: {
ExecutionOverview,
popup: AlertWindow,
InstanceCard,
DeploymentToolbar,
InstanceActionToolbar,
},
data() {
return {
/** */
selectedInstance: null,
isStartingInstance: false,
/** */
isStoppingInstance: false,
/** */
isPausingInstance: false,
/** */
isResumingInstance: false,
/** */
activeStates: ['PAUSED', 'RUNNING', 'READY', 'DEPLOYMENT-WAITING', 'WAITING'],
popupData: {
body: '',
display: 'none',
color: '',
},
colorsShown: 'processColors',
};
},
computed: {
/**
* All process deployments on known machines
*/
deployedProcesses() {
return this.$store.getters['machineStore/deployments'];
},
/**
* The wanted deployment
*/
deployment() {
const processDefinitionsId = this.$router.currentRoute.params.id;
return this.deployedProcesses.find(
(deployment) => deployment.definitionId === processDefinitionsId
);
},
/**
* Sorted instances in the deployment augmented with a title for the selection
*/
selectableInstances() {
if (!this.deployment) return [];
return this.deployment.instances
.concat() // create a copy of the array because the sorting is taking changes in place => side effects in store
.sort((i1, i2) => new Date(i2.globalStartTime) - new Date(i1.globalStartTime))
.map((instance, index) => ({
...instance,
// TODO: actually compute which state is the one to display (e.g. if there are [FORWARDED, RUNNING] display RUNNING)
text: `${index + 1}. Instance | ${instance.instanceState[0]}`,
}));
},
/**
* If the selected instance is currently running
*/
selectedIsRunning() {
if (this.selectedInstance) {
return this.selectedInstance.instanceState.some((state) =>
this.activeStates.includes(state)
);
} else {
return false;
}
},
},
methods: {
/**
* Start a new instance of the deployed process
*/
async startInstance() {
this.isStartingInstance = true;
try {
await engineNetworkInterface.startInstance(this.deployment.definitionId);
this.popupData.body = 'Executing process instance';
this.popupData.color = 'success';
setTimeout(() => {
this.popupData.display = 'none';
}, 5000);
} catch (err) {
this.popupData.body = 'Error starting the process instance';
this.popupData.color = 'error';
console.error(err);
}
this.isStartingInstance = false;
this.openPopup();
},
/**
* Stops the selected instance
*/
async stopInstance() {
this.isStoppingInstance = true;
try {
await engineNetworkInterface.stopInstance(
this.deployment.definitionId,
this.selectedInstance.processInstanceId
);
this.popupData.body = 'Stopped process instance';
this.popupData.color = 'primary';
setTimeout(() => {
this.popupData.display = 'none';
}, 5000);
} catch (err) {
this.popupData.body = 'Error stopping the process instance';
this.popupData.color = 'error';
console.error(err);
}
this.isStoppingInstance = false;
this.openPopup();
},
/**
* Pauses the selected instance
*/
async pauseInstance() {
this.isPausingInstance = true;
try {
await engineNetworkInterface.pauseInstance(
this.deployment.definitionId,
this.selectedInstance.processInstanceId
);
this.popupData.body = 'Paused process instance';
this.popupData.color = 'primary';
setTimeout(() => {
this.popupData.display = 'none';
}, 5000);
} catch (err) {
this.popupData.body = 'Error pausing the process instance';
this.popupData.color = 'error';
console.error(err);
}
this.isPausingInstance = false;
this.openPopup();
},
/**
* Resumes the selected instance
*/
async resumeInstance() {
this.isResumingInstance = true;
try {
await engineNetworkInterface.resumeInstance(
this.deployment.definitionId,
this.selectedInstance.processInstanceId
);
this.popupData.body = 'Resuming process instance';
this.popupData.color = 'primary';
setTimeout(() => {
this.popupData.display = 'none';
}, 5000);
} catch (err) {
this.popupData.body = 'Error resuming the process instance';
this.popupData.color = 'error';
console.error(err);
}
this.isResumingInstance = false;
this.openPopup();
},
/** */
openPopup() {
this.popupData.display = 'block';
},
},
watch: {
/**
* Makes sure that the selected instance stays the same if the list of selectable instances changes
*/
async selectableInstances(newInstances, oldInstances) {
if (newInstances.length > oldInstances.length) {
this.selectedInstance = this.selectableInstances[0];
} else if (this.selectedInstance) {
const sameInstance = this.selectableInstances.find(
(instance) => instance.processInstanceId === this.selectedInstance.processInstanceId
);
if (sameInstance) {
this.selectedInstance = sameInstance;
}
}
},
async selectedInstance(newSelected, oldSelected) {
if (!this.selectedInstance) {
this.colorsShown = 'processColors';
}
},
},
/**
* Signals to the backend that updates for this deployment are wanted
*/
mounted() {
this.selectedInstance = this.selectableInstances[0];
engineNetworkInterface.subscribeForDeploymentsUpdates();
engineNetworkInterface.startMachinePolling();
},
async beforeRouteLeave(to, from, next) {
await engineNetworkInterface.unsubscribeFromDeploymentsUpdates();
await engineNetworkInterface.stopMachinePolling();
next();
},
};
</script>
<style lang="scss" scoped>
.wrapper {
display: flex;
flex: 1;
flex-direction: column;
height: 100%;
}
.card-wrapper {
display: flex;
justify-content: center;
margin-top: 10px;
position: absolute;
z-index: 1;
width: 100%; /* Need a specific value to work */
max-height: 30%;
.v-card {
overflow-y: auto;
// For 3 cards we end up with a content widh of 32% * 3 = 96% plus the margin-right for between the cards 96% + 2% = 98%.
// Now 2% left for the margin to the left and the right for all cards
margin-right: 1%;
width: 32%;
// height: 100%;
&:last-child {
margin-right: 0;
}
}
}
</style>