Source: management-system/src/frontend/views/DeploymentMonitoring.vue

<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>