فهرست منبع

Merge pull request #542 from smiclea/delete-replica

Check for replica disks when deleting a replica CORWEB-235
Nashwan Azhari 6 سال پیش
والد
کامیت
25d99976df

+ 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'
 }