فهرست منبع

Check for replica disks when deleting a replica

When deleting a replica, the confirmation alert shows whether the
replica has replica disks.
If the replica has replica disks, an option to delete those disks is
presented.

The same flow is kept when deleting multiple replicas. In this case,
'delete replica disks' is executed just for the replicas which actually
have replica disks and not for all the selected replicas.
Sergiu Miclea 6 سال پیش
والد
کامیت
2f1a0bd133

+ 134 - 0
src/components/molecules/DeleteReplicaModal/DeleteReplicaModal.jsx

@@ -0,0 +1,134 @@
+/*
+Copyright (C) 2020  Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// @flow
+
+import React from 'react'
+import { observer } from 'mobx-react'
+import styled from 'styled-components'
+
+import Modal from '../Modal'
+import Button from '../../atoms/Button'
+import StatusImage from '../../atoms/StatusImage'
+
+import Palette from '../../styleUtils/Palette'
+
+const Wrapper = styled.div`
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 48px;
+`
+const Message = styled.div`
+  font-size: 18px;
+  text-align: center;
+  margin-top: 48px;
+`
+const ExtraMessage = styled.div`
+  color: ${Palette.grayscale[4]};
+  margin: 11px 0 48px 0;
+  text-align: center;
+  font-size: 12px;
+`
+const Buttons = styled.div`
+  display: flex;
+  justify-content: space-between;
+  width: 100%;
+  align-items: flex-end;
+`
+const ButtonsColumn = styled.div`
+  display: flex;
+  flex-direction: column;
+`
+
+type Props = {
+  hasDisks: boolean,
+  isMultiReplicaSelection?: boolean,
+  onDeleteReplica: () => void,
+  onDeleteDisks: () => void,
+  onRequestClose: () => void,
+}
+
+@observer
+class DeleteReplicaModal extends React.Component<Props> {
+  renderExtraMessage() {
+    if (this.props.hasDisks) {
+      if (this.props.isMultiReplicaSelection) {
+        return (
+          <ExtraMessage>
+            Some of the selected Replicas have been executed at least once and thus may have disks created on the destination platform.
+            If those Replicas are to be deleted now, the disks on the destination will persist.
+            If this is not desired, please use the &quot;Delete Replica Disks&quot; option to delete those disks before deleting the Replicas themselves.
+          </ExtraMessage>
+        )
+      }
+
+      return (
+        <ExtraMessage>
+          This Replica has been executed at least once and thus may have disks created on the destination platform.
+          If the Replica is to be deleted now, the disks on the destination will persist.
+          If this is not desired, please use the &quot;Delete Replica Disks&quot; option to delete the disks before deleting the Replica itself.
+        </ExtraMessage>
+      )
+    }
+
+    return (
+      <ExtraMessage>
+        Deleting a Coriolis Replica is permanent!
+      </ExtraMessage>
+    )
+  }
+
+  render() {
+    let title = this.props.isMultiReplicaSelection ? 'Delete Selected Replicas?' : 'Delete Replica?'
+    let message = this.props.isMultiReplicaSelection ? 'Are you sure you want to delete the selected replicas?' : 'Are you sure you want to delete this replica?'
+    return (
+      <Modal
+        isOpen
+        title={title}
+        onRequestClose={this.props.onRequestClose}
+      >
+        <Wrapper>
+          <StatusImage status="QUESTION" />
+          <Message>{message}</Message>
+          {this.renderExtraMessage()}
+          <Buttons>
+            <Button secondary onClick={this.props.onRequestClose}>Cancel</Button>
+            <ButtonsColumn>
+              {this.props.hasDisks ? (
+                <Button
+                  onClick={this.props.onDeleteDisks}
+                  hollow
+                  style={{ marginBottom: '16px' }}
+                  alert
+                >
+                  Delete Replica Disks
+                </Button>
+              ) : null}
+              <Button
+                onClick={this.props.onDeleteReplica}
+                alert
+              >
+                Delete Replica{this.props.isMultiReplicaSelection ? 's' : ''}
+              </Button>
+            </ButtonsColumn>
+          </Buttons>
+        </Wrapper>
+      </Modal>
+    )
+  }
+}
+
+export default DeleteReplicaModal

+ 6 - 0
src/components/molecules/DeleteReplicaModal/package.json

@@ -0,0 +1,6 @@
+{
+  "name": "DeleteReplicaModal",
+  "version": "0.0.0",
+  "private": true,
+  "main": "./DeleteReplicaModal.jsx"
+}

+ 10 - 9
src/components/pages/ReplicaDetailsPage/ReplicaDetailsPage.jsx

@@ -27,6 +27,7 @@ import ReplicaExecutionOptions from '../../organisms/ReplicaExecutionOptions'
 import AlertModal from '../../organisms/AlertModal'
 import EditReplica from '../../organisms/EditReplica'
 import ReplicaMigrationOptions from '../../organisms/ReplicaMigrationOptions'
+import DeleteReplicaModal from '../../molecules/DeleteReplicaModal'
 
 import type { MainItem } from '../../../types/MainItem'
 import type { InstanceScript } from '../../../types/Instance'
@@ -274,7 +275,7 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
   }
 
   handleDeleteReplicaDisksConfirmation() {
-    this.setState({ showDeleteReplicaDisksConfirmation: false })
+    this.setState({ showDeleteReplicaDisksConfirmation: false, showDeleteReplicaConfirmation: false })
     replicaStore.deleteDisks(replicaStore.replicaDetails ? replicaStore.replicaDetails.id : '')
     this.props.history.push(`/replica/executions/${replicaStore.replicaDetails ? replicaStore.replicaDetails.id : ''}`)
   }
@@ -555,14 +556,14 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
           onConfirmation={() => { this.handleDeleteExecutionConfirmation() }}
           onRequestClose={() => { this.handleCloseExecutionConfirmation() }}
         />
-        <AlertModal
-          isOpen={this.state.showDeleteReplicaConfirmation}
-          title="Delete Replica?"
-          message="Are you sure you want to delete this replica?"
-          extraMessage="Deleting a Coriolis Replica is permanent!"
-          onConfirmation={() => { this.handleDeleteReplicaConfirmation() }}
-          onRequestClose={() => { this.handleCloseDeleteReplicaConfirmation() }}
-        />
+        {this.state.showDeleteReplicaConfirmation ? (
+          <DeleteReplicaModal
+            hasDisks={replicaStore.hasReplicaDisks(replicaStore.replicaDetails)}
+            onRequestClose={() => this.handleCloseDeleteReplicaConfirmation()}
+            onDeleteReplica={() => { this.handleDeleteReplicaConfirmation() }}
+            onDeleteDisks={() => { this.handleDeleteReplicaDisksConfirmation() }}
+          />
+        ) : null}
         <AlertModal
           isOpen={this.state.showDeleteReplicaDisksConfirmation}
           title="Delete Replica Disks?"

+ 10 - 10
src/components/pages/ReplicasPage/ReplicasPage.jsx

@@ -27,6 +27,7 @@ import MainListItem from '../../molecules/MainListItem'
 import Modal from '../../molecules/Modal'
 import ReplicaExecutionOptions from '../../organisms/ReplicaExecutionOptions'
 import ReplicaMigrationOptions from '../../organisms/ReplicaMigrationOptions'
+import DeleteReplicaModal from '../../molecules/DeleteReplicaModal'
 
 import type { MainItem } from '../../../types/MainItem'
 import type { Action as DropdownAction } from '../../molecules/ActionDropdown'
@@ -159,11 +160,11 @@ class ReplicasPage extends React.Component<{ history: any }, State> {
     this.props.history.push('/migrations')
   }
 
-  deleteSelectedReplicasDisks() {
-    this.state.selectedReplicas.forEach(replica => {
+  deleteReplicasDisks(replicas: MainItem[]) {
+    replicas.forEach(replica => {
       replicaStore.deleteDisks(replica.id)
     })
-    this.setState({ showDeleteDisksModal: false })
+    this.setState({ showDeleteDisksModal: false, showDeleteReplicasModal: false })
     notificationStore.alert('Deleting selected replicas\' disks')
   }
 
@@ -368,13 +369,12 @@ class ReplicasPage extends React.Component<{ history: any }, State> {
           }
         />
         {this.state.showDeleteReplicasModal ? (
-          <AlertModal
-            isOpen
-            title="Delete Selected Replicas?"
-            message="Are you sure you want to delete the selected replicas?"
-            extraMessage="Deleting a Coriolis Replica is permanent!"
-            onConfirmation={() => { this.deleteSelectedReplicas() }}
+          <DeleteReplicaModal
+            hasDisks={replicaStore.getReplicasWithDisks(this.state.selectedReplicas).length > 0}
+            isMultiReplicaSelection
             onRequestClose={() => { this.setState({ showDeleteReplicasModal: false }) }}
+            onDeleteReplica={() => { this.deleteSelectedReplicas() }}
+            onDeleteDisks={() => { this.deleteReplicasDisks(replicaStore.getReplicasWithDisks(this.state.selectedReplicas)) }}
           />
         ) : null}
         {this.state.showCancelExecutionModal ? (
@@ -421,7 +421,7 @@ class ReplicasPage extends React.Component<{ history: any }, State> {
             title="Delete Selected Replicas Disks?"
             message="Are you sure you want to delete the selected replicas' disks?"
             extraMessage="Deleting Coriolis Replica Disks is permanent!"
-            onConfirmation={() => { this.deleteSelectedReplicasDisks() }}
+            onConfirmation={() => { this.deleteReplicasDisks(this.state.selectedReplicas) }}
             onRequestClose={() => { this.setState({ showDeleteDisksModal: false }) }}
           />
         ) : null}

+ 19 - 0
src/stores/ReplicaStore.js

@@ -192,6 +192,25 @@ class ReplicaStore {
   async update(replica: MainItem, destinationEndpoint: Endpoint, updateData: UpdateData, defaultStorage: ?string, storageConfigDefault: string) {
     await ReplicaSource.update(replica, destinationEndpoint, updateData, defaultStorage, storageConfigDefault)
   }
+
+  getReplicasWithDisks(replicas: MainItem[]): MainItem[] {
+    let result = replicas.filter(r => this.hasReplicaDisks(r))
+    return result
+  }
+
+  hasReplicaDisks(replica: ?MainItem): boolean {
+    if (!replica || !replica.executions || replica.executions.length === 0) {
+      return false
+    }
+    if (!replica.executions.find(e => e.type === 'replica_execution')) {
+      return false
+    }
+    let lastExecution = replica.executions[replica.executions.length - 1]
+    if (lastExecution.type === 'replica_disks_delete' && lastExecution.status === 'COMPLETED') {
+      return false
+    }
+    return true
+  }
 }
 
 export default new ReplicaStore()

+ 1 - 0
src/types/Execution.js

@@ -23,4 +23,5 @@ export type Execution = {
   created_at: Date,
   updated_at: Date,
   tasks: Task[],
+  type: 'replica_execution' | 'replica_disks_delete' | 'replica_deploy' | 'replica_update'
 }