Kaynağa Gözat

- Adds timeline navigation to execution replica
- Other small fixes

George Vrancianu 9 yıl önce
ebeveyn
işleme
44fb71cb13

+ 16 - 1
src/actions/MigrationActions/MigrationActions.js

@@ -30,7 +30,7 @@ let MigrationActions = Reflux.createActions({
   'getReplicaExecutions': { children: ['completed', 'failed'] },
   'getReplicaExecutionDetail': { children: ['completed', 'failed'] },
   'createMigrationFromReplica': { children: ['completed', 'failed'] },
-  'reloadReplicaExecution': { children: ['completed', 'failed'] },
+  'deleteReplicaExecution': { children: ['completed', 'failed'] },
   'getMigration': {},
   'setMigration': {},
   'setMigrationProperty': {}
@@ -182,6 +182,21 @@ MigrationActions.getReplicaExecutionDetail.listen((replica, executionId, callbac
     .catch(MigrationActions.getReplicaExecutionDetail.failed);
 })
 
+MigrationActions.deleteReplicaExecution.listen((replica, executionId, callback = null) => {
+  let projectId = Reflux.GlobalState.userStore.currentUser.project.id
+  Api.sendAjaxRequest({
+    url: `${servicesUrl.coriolis}/${projectId}/replicas/${replica.id}/executions/${executionId}`,
+    method: "DELETE"
+  })
+    .then((response) => {
+      MigrationActions.deleteReplicaExecution.completed(replica, executionId, response)
+      if (callback) {
+        callback(replica, executionId, response)
+      }
+    }, MigrationActions.deleteReplicaExecution.failed)
+    .catch(MigrationActions.deleteReplicaExecution.failed);
+})
+
 MigrationActions.addMigration.listen((migration) => {
   let payload = {}
   let instances = []

+ 37 - 24
src/components/App/App.scss

@@ -462,38 +462,46 @@ button {
     }
   }
 }
-:global(.taskIcon) {
-  width: 16px;
-  height: 16px;
-  display: block;
-  float: left;
-  margin: 1px 8px 0 8px;
-  &:global(.COMPLETED) {
-    background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTZweCIgaGVpZ2h0PSIxNnB4IiB2aWV3Qm94PSIwIDAgMTYgMTYiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+DQogICAgPGcgaWQ9IlN5bWJvbHMiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPg0KICAgICAgICA8ZyBpZD0iSWNvbi1PayI+DQogICAgICAgICAgICA8Y2lyY2xlIGlkPSJPdmFsLTIiIGZpbGw9IiM0Q0Q5NjQiIGN4PSI4IiBjeT0iOCIgcj0iOCI+PC9jaXJjbGU+DQogICAgICAgICAgICA8cG9seWxpbmUgaWQ9IlN0cm9rZS0zIiBzdHJva2U9IiNGRkZGRkYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgcG9pbnRzPSIxMiA2IDcuMDgzMDc0MTkgMTEgNCA4LjAwMzk3NTQyIj48L3BvbHlsaW5lPg0KICAgICAgICA8L2c+DQogICAgPC9nPg0KPC9zdmc+');
-  }
-  &:global(.RUNNING) {
-    background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTZweCIgaGVpZ2h0PSIxNnB4IiB2aWV3Qm94PSIwIDAgMTYgMTYiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+PGcgaWQ9Ikdyb3VwLTciIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxjaXJjbGUgaWQ9Ik92YWwtMi1Db3B5IiBmaWxsPSIjQTRBQUI1IiBjeD0iOCIgY3k9IjgiIHI9IjgiPjwvY2lyY2xlPjxwYXRoIGQ9Ik0xNiw4IEMxNiwzLjU4MTcyMiAxMi40MTgyNzgsMCA4LDAgTDgsOCBMMTYsOCBaIiBpZD0iQ29tYmluZWQtU2hhcGUiIGZpbGw9IiMwMDU2QjgiPjwvcGF0aD48Y2lyY2xlIGlkPSJPdmFsLTItQ29weSIgZmlsbD0iI0ZGRkZGRiIgY3g9IjgiIGN5PSI4IiByPSI2Ij48L2NpcmNsZT48L2c+PC9zdmc+');
-    animation: rotate 2s infinite linear;
-  }
-  &:global(.WARNING) {
-    background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTZweCIgaGVpZ2h0PSIxNnB4IiB2aWV3Qm94PSIwIDAgMTYgMTYiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+PGNpcmNsZSBpZD0iT3ZhbC0yIiBzdHJva2U9Im5vbmUiIGZpbGw9IiNGREMwMkYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgY3g9IjgiIGN5PSI4IiByPSI4Ij48L2NpcmNsZT48cGF0aCBkPSJNOCw4IEw4LDQiIGlkPSJMaW5lLUNvcHkiIHN0cm9rZT0iI0ZGRkZGRiIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIGZpbGw9Im5vbmUiPjwvcGF0aD48cGF0aCBkPSJNOCwxMi4wMjU5NzQgTDgsMTEuMzMzMzMzMyIgaWQ9IkxpbmUtQ29weSIgc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgZmlsbD0ibm9uZSI+PC9wYXRoPjwvc3ZnPg==');
-  }
-  &:global(.CANCELED) {
-    background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTZweCIgaGVpZ2h0PSIxNnB4IiB2aWV3Qm94PSIwIDAgMTYgMTYiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+PGNpcmNsZSBpZD0iT3ZhbC0yIiBzdHJva2U9Im5vbmUiIGZpbGw9IiNGREMwMkYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgY3g9IjgiIGN5PSI4IiByPSI4Ij48L2NpcmNsZT48cGF0aCBkPSJNOCw4IEw4LDQiIGlkPSJMaW5lLUNvcHkiIHN0cm9rZT0iI0ZGRkZGRiIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIGZpbGw9Im5vbmUiPjwvcGF0aD48cGF0aCBkPSJNOCwxMi4wMjU5NzQgTDgsMTEuMzMzMzMzMyIgaWQ9IkxpbmUtQ29weSIgc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgZmlsbD0ibm9uZSI+PC9wYXRoPjwvc3ZnPg==');
-  }
-  &:global(.PENDING) {
-    background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTZweCIgaGVpZ2h0PSIxNnB4IiB2aWV3Qm94PSIwIDAgMTYgMTYiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+PGNpcmNsZSBpZD0iT3ZhbC0yLUNvcHkiIHN0cm9rZT0ibm9uZSIgZmlsbD0iI0M4Q0NENyIgZmlsbC1ydWxlPSJldmVub2RkIiBjeD0iOCIgY3k9IjgiIHI9IjgiPjwvY2lyY2xlPjxjaXJjbGUgaWQ9Ik92YWwtMi1Db3B5IiBzdHJva2U9Im5vbmUiIGZpbGw9Im5vbmUiIGN4PSI4IiBjeT0iOCIgcj0iNiI+PC9jaXJjbGU+PHBhdGggZD0iTTgsOCBMOCw0IiBpZD0iTGluZS1Db3B5LTIiIHN0cm9rZT0iI0ZGRkZGRiIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIGZpbGw9Im5vbmUiPjwvcGF0aD48cGF0aCBkPSJNOCw4IEwxMCwxMCIgaWQ9IkxpbmUtQ29weS0zIiBzdHJva2U9IiNGRkZGRkYiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBmaWxsPSJub25lIj48L3BhdGg+PC9zdmc+');
+:global {
+  .taskIcon {
+    width: 16px;
+    height: 16px;
+    display: block;
+    float: left;
+    margin: 1px 8px 0 8px;
+    &.COMPLETED {
+      background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTZweCIgaGVpZ2h0PSIxNnB4IiB2aWV3Qm94PSIwIDAgMTYgMTYiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+DQogICAgPGcgaWQ9IlN5bWJvbHMiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPg0KICAgICAgICA8ZyBpZD0iSWNvbi1PayI+DQogICAgICAgICAgICA8Y2lyY2xlIGlkPSJPdmFsLTIiIGZpbGw9IiM0Q0Q5NjQiIGN4PSI4IiBjeT0iOCIgcj0iOCI+PC9jaXJjbGU+DQogICAgICAgICAgICA8cG9seWxpbmUgaWQ9IlN0cm9rZS0zIiBzdHJva2U9IiNGRkZGRkYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgcG9pbnRzPSIxMiA2IDcuMDgzMDc0MTkgMTEgNCA4LjAwMzk3NTQyIj48L3BvbHlsaW5lPg0KICAgICAgICA8L2c+DQogICAgPC9nPg0KPC9zdmc+');
+    }
+    &.RUNNING {
+      background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTZweCIgaGVpZ2h0PSIxNnB4IiB2aWV3Qm94PSIwIDAgMTYgMTYiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+PGcgaWQ9Ikdyb3VwLTciIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxjaXJjbGUgaWQ9Ik92YWwtMi1Db3B5IiBmaWxsPSIjQTRBQUI1IiBjeD0iOCIgY3k9IjgiIHI9IjgiPjwvY2lyY2xlPjxwYXRoIGQ9Ik0xNiw4IEMxNiwzLjU4MTcyMiAxMi40MTgyNzgsMCA4LDAgTDgsOCBMMTYsOCBaIiBpZD0iQ29tYmluZWQtU2hhcGUiIGZpbGw9IiMwMDU2QjgiPjwvcGF0aD48Y2lyY2xlIGlkPSJPdmFsLTItQ29weSIgZmlsbD0iI0ZGRkZGRiIgY3g9IjgiIGN5PSI4IiByPSI2Ij48L2NpcmNsZT48L2c+PC9zdmc+');
+      animation: rotate 2s infinite linear;
+    }
+    &.WARNING {
+      background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTZweCIgaGVpZ2h0PSIxNnB4IiB2aWV3Qm94PSIwIDAgMTYgMTYiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+PGNpcmNsZSBpZD0iT3ZhbC0yIiBzdHJva2U9Im5vbmUiIGZpbGw9IiNGREMwMkYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgY3g9IjgiIGN5PSI4IiByPSI4Ij48L2NpcmNsZT48cGF0aCBkPSJNOCw4IEw4LDQiIGlkPSJMaW5lLUNvcHkiIHN0cm9rZT0iI0ZGRkZGRiIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIGZpbGw9Im5vbmUiPjwvcGF0aD48cGF0aCBkPSJNOCwxMi4wMjU5NzQgTDgsMTEuMzMzMzMzMyIgaWQ9IkxpbmUtQ29weSIgc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgZmlsbD0ibm9uZSI+PC9wYXRoPjwvc3ZnPg==');
+    }
+    &.CANCELED {
+      background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTZweCIgaGVpZ2h0PSIxNnB4IiB2aWV3Qm94PSIwIDAgMTYgMTYiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+PGNpcmNsZSBpZD0iT3ZhbC0yIiBzdHJva2U9Im5vbmUiIGZpbGw9IiNGREMwMkYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgY3g9IjgiIGN5PSI4IiByPSI4Ij48L2NpcmNsZT48cGF0aCBkPSJNOCw4IEw4LDQiIGlkPSJMaW5lLUNvcHkiIHN0cm9rZT0iI0ZGRkZGRiIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIGZpbGw9Im5vbmUiPjwvcGF0aD48cGF0aCBkPSJNOCwxMi4wMjU5NzQgTDgsMTEuMzMzMzMzMyIgaWQ9IkxpbmUtQ29weSIgc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgZmlsbD0ibm9uZSI+PC9wYXRoPjwvc3ZnPg==');
+    }
+    &.PENDING {
+      background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTZweCIgaGVpZ2h0PSIxNnB4IiB2aWV3Qm94PSIwIDAgMTYgMTYiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+PGNpcmNsZSBpZD0iT3ZhbC0yLUNvcHkiIHN0cm9rZT0ibm9uZSIgZmlsbD0iI0M4Q0NENyIgZmlsbC1ydWxlPSJldmVub2RkIiBjeD0iOCIgY3k9IjgiIHI9IjgiPjwvY2lyY2xlPjxjaXJjbGUgaWQ9Ik92YWwtMi1Db3B5IiBzdHJva2U9Im5vbmUiIGZpbGw9Im5vbmUiIGN4PSI4IiBjeT0iOCIgcj0iNiI+PC9jaXJjbGU+PHBhdGggZD0iTTgsOCBMOCw0IiBpZD0iTGluZS1Db3B5LTIiIHN0cm9rZT0iI0ZGRkZGRiIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIGZpbGw9Im5vbmUiPjwvcGF0aD48cGF0aCBkPSJNOCw4IEwxMCwxMCIgaWQ9IkxpbmUtQ29weS0zIiBzdHJva2U9IiNGRkZGRkYiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBmaWxsPSJub25lIj48L3BhdGg+PC9zdmc+');
+    }
+    &.ERROR {
+      background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTZweCIgaGVpZ2h0PSIxNnB4IiB2aWV3Qm94PSIwIDAgMTYgMTYiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+PGcgaWQ9IlN5bWJvbHMiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxnIGlkPSJJY29uLUVycm9yIj48Y2lyY2xlIGlkPSJPdmFsLTIiIGZpbGw9IiNFNjI1NjUiIGN4PSI4IiBjeT0iOCIgcj0iOCI+PC9jaXJjbGU+PHBhdGggZD0iTTExLjQyODU3MTQsNC41NzE0Mjg1NyBMNC41NzE0Mjg1NywxMS40Mjg1NzE0IiBpZD0iTGluZSIgc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCI+PC9wYXRoPjxwYXRoIGQ9Ik0xMS40Mjg1NzE0LDExLjQyODU3MTQgTDQuNTcxNDI4NTcsNC41NzE0Mjg1NyIgaWQ9IkxpbmUtQ29weSIgc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCI+PC9wYXRoPjwvZz48L2c+PC9zdmc+');
+    }
   }
 
-  &:global(.ERROR) {
-    background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTZweCIgaGVpZ2h0PSIxNnB4IiB2aWV3Qm94PSIwIDAgMTYgMTYiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+PGcgaWQ9IlN5bWJvbHMiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxnIGlkPSJJY29uLUVycm9yIj48Y2lyY2xlIGlkPSJPdmFsLTIiIGZpbGw9IiNFNjI1NjUiIGN4PSI4IiBjeT0iOCIgcj0iOCI+PC9jaXJjbGU+PHBhdGggZD0iTTExLjQyODU3MTQsNC41NzE0Mjg1NyBMNC41NzE0Mjg1NywxMS40Mjg1NzE0IiBpZD0iTGluZSIgc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCI+PC9wYXRoPjxwYXRoIGQ9Ik0xMS40Mjg1NzE0LDExLjQyODU3MTQgTDQuNTcxNDI4NTcsNC41NzE0Mjg1NyIgaWQ9IkxpbmUtQ29weSIgc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCI+PC9wYXRoPjwvZz48L2c+PC9zdmc+');
-  }
 }
+
 :global {
   .icon {
     display: inline-block;
     background-repeat: no-repeat;
     background-position: center center;
+    &.chevron {
+      background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iN3B4IiBoZWlnaHQ9IjEycHgiIHZpZXdCb3g9IjAgMCA3IDEyIiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPg0KICAgIDxnIGlkPSJDb3Jpb2xpcyIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj4NCiAgICA8ZyBpZD0iMjEwLS1SZXBsaWNhLVRhc2tzLUNvcHkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0zNzIuMDAwMDAwLCAtMTk0LjAwMDAwMCkiIHN0cm9rZT0iIzdGODc5NSI+DQogICAgPGcgaWQ9IkNoZXZyb24tR3JleSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMzc2LjAwMDAwMCwgMjAwLjAwMDAwMCkgcm90YXRlKC0yNzAuMDAwMDAwKSB0cmFuc2xhdGUoLTM3Ni4wMDAwMDAsIC0yMDAuMDAwMDAwKSB0cmFuc2xhdGUoMzY4LjAwMDAwMCwgMTkyLjAwMDAwMCkiPg0KICAgIDxwb2x5bGluZSBpZD0iUmVjdGFuZ2xlLUNvcHkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDguMDAwMDAwLCA1LjUwMDAwMCkgcm90YXRlKC0zMTUuMDAwMDAwKSB0cmFuc2xhdGUoLTguMDAwMDAwLCAtNS41MDAwMDApICIgcG9pbnRzPSIxMS44ODkwODczIDEuNjEwOTEyNyAxMS44ODkwODczIDkuMzg5MDg3MyA0LjExMDkxMjcgOS4zODkwODczIj48L3BvbHlsaW5lPg0KICAgIDwvZz4NCiAgICA8L2c+DQogICAgPC9nPg0KICAgIDwvc3ZnPg==');
+      width: 7px;
+      height: 12px;
+    }
     &.refresh {
       background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTZweCIgaGVpZ2h0PSIxNnB4IiB2aWV3Qm94PSIwIDAgMTYgMTYiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+DQogICAgPGcgaWQ9IlN5bWJvbHMiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCI+DQogICAgICAgIDxnIGlkPSJJY29uLVJlZnJlc2gtR3JleSIgc3Ryb2tlPSIjNjE2NzcwIj4NCiAgICAgICAgICAgIDxwYXRoIGQ9Ik0xMi41NzMwMTYyLDQuODE2NjM4ODUgQzExLjU2NjI0NTgsMy4zNzMwNzc0NiA5Ljg5MzQyMzQ4LDIuNDI4NTcxNDMgOCwyLjQyODU3MTQzIEM0LjkyMjk4NDk3LDIuNDI4NTcxNDMgMi40Mjg1NzE0Myw0LjkyMjk4NDk3IDIuNDI4NTcxNDMsOCBDMi40Mjg1NzE0MywxMS4wNzcwMTUgNC45MjI5ODQ5NywxMy41NzE0Mjg2IDgsMTMuNTcxNDI4NiBMOCwxMy41NzE0Mjg2IEMxMS4wNzcwMTUsMTMuNTcxNDI4NiAxMy41NzE0Mjg2LDExLjA3NzAxNSAxMy41NzE0Mjg2LDgiIGlkPSJPdmFsLTQiPjwvcGF0aD4NCiAgICAgICAgICAgIDxwb2x5bGluZSBpZD0iUmVjdGFuZ2xlLTEwIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBwb2ludHM9IjEzLjE0Mjg1NzEgMiAxMy4xNDI4NTcxIDUuNDI4NTcxNDMgOS43MTQyODU3MSA1LjQyODU3MTQzIj48L3BvbHlsaW5lPg0KICAgICAgICA8L2c+DQogICAgPC9nPg0KPC9zdmc+');
       width: 16px;
@@ -1431,3 +1439,8 @@ textarea {
     background-position: 0 0;
   }
 }
+
+@keyframes :global(rotate) {
+  from {transform: rotate(0deg);}
+  to {transform: rotate(360deg);}
+}

+ 51 - 12
src/components/ExecutionsTimeline/ExecutionsTimeline.js

@@ -27,12 +27,13 @@ class ExecutionsTimeline extends Component {
 
   static propTypes = {
     executions: PropTypes.array,
-    currentExecution: PropTypes.object
+    currentExecution: PropTypes.object,
+    handleChangeExecution: PropTypes.func,
   }
 
   static defaultProps = {
     executions: null,
-    currentExecution: null
+    currentExecution: null,
   }
 
   constructor(props) {
@@ -47,14 +48,11 @@ class ExecutionsTimeline extends Component {
   }
 
   componentWillReceiveProps(newProps) {
-    let executions = this.props.executions
-    executions.sort((a, b) => {
-      return a.number - b.number;
-    })
+    let executions = this._getExecutions()
     let number = newProps.currentExecution.number - 1
     let visibleExecutions = []
     let count = 0
-    for (let i = number - ((numberOfExecutions - 1) / 2); i<= number + ((numberOfExecutions - 1) / 2); i++) {
+    for (let i = number - ((numberOfExecutions - 1) / 2); i <= number + ((numberOfExecutions - 1) / 2); i++) {
       visibleExecutions[count] = executions[i]
       count++
     }
@@ -63,13 +61,54 @@ class ExecutionsTimeline extends Component {
     })
   }
 
+  _getExecutions() {
+    let executions = this.props.executions
+    executions.sort((a, b) => {
+      return a.number - b.number;
+    })
+    return executions
+  }
+
+  isNext() {
+    let executions = this._getExecutions()
+    let number = this.props.currentExecution.number - 1
+    return executions[number + 1]
+  }
+
+  isPrev() {
+    let executions = this._getExecutions()
+    let number = this.props.currentExecution.number - 1
+    return executions[number - 1]
+  }
+
+  handleNext() {
+    let executions = this._getExecutions()
+    let number = this.props.currentExecution.number - 1
+    if (executions[number + 1]) {
+      this.props.handleChangeExecution(executions[number + 1])
+    }
+  }
+
+  handlePrev() {
+    let executions = this._getExecutions()
+    let number = this.props.currentExecution.number - 1
+    if (executions[number - 1]) {
+      this.props.handleChangeExecution(executions[number - 1])
+    }
+  }
+
   render() {
     let dotDistance = 100 / (numberOfExecutions + 1)
     let executions = this.state.visibleExecutions.map((execution, index) => {
       if (execution) {
         let style = { left: dotDistance * (index + 1) + "%"}
         return (
-          <div className={s.executionDot + (index == (numberOfExecutions - 1) / 2 ? (" " + s.current) : "")} style={style}>
+          <div
+            className={s.executionDot + (index == (numberOfExecutions - 1) / 2 ? (" " + s.current) : "")}
+            style={style}
+            onClick={(e) => this.props.handleChangeExecution(execution)}
+            key={"execution_" + index}
+          >
             <div className={"taskIcon " + execution.status}></div>
             <Moment format="D MMM YYYY" date={execution.created_at} />
           </div>
@@ -77,17 +116,17 @@ class ExecutionsTimeline extends Component {
       } else {
         return null;
       }
-
     })
-    return <div className={s.root}>
+    return (
       <div className={s.root}>
         <div className={s.line}>
+          { this.isPrev() && <div className={s.caretLeft + " icon chevron"} onClick={(e) => this.handlePrev()}></div>}
           <div className={s.progress}></div>
-
+          { this.isNext() && <div className={s.caretRight + " icon chevron"} onClick={(e) => this.handleNext()}></div>}
         </div>
         {executions}
       </div>
-    </div>
+    )
   }
 
 }

+ 35 - 1
src/components/ExecutionsTimeline/ExecutionsTimeline.scss

@@ -19,7 +19,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 .root {
   position: relative;
-  padding-bottom: 16px;
+  padding: 8px 0 40px;
   .line {
     background-color: $execution-line-color;
     height: 2px;
@@ -37,12 +37,46 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
     margin-left: -44px;
     margin-top: -8px;
     text-align: center;
+    transition: all $animation-swift-out;
+    cursor: pointer;
     :global(.taskIcon) {
       float: none;
       margin: 0 auto;
     }
     &.current {
       font-weight: 500;
+      :global(.taskIcon) {
+        width: 20px;
+        height: 20px;
+        background-size: cover;
+      }
+    }
+  }
+  .caretLeft {
+    position: absolute;
+    cursor: pointer;
+    top: -12px;
+    left: -24px;
+    opacity: 0;
+    transition: opacity $animation-swift-out;
+    padding: 8px;
+  }
+  .caretRight {
+    position: absolute;
+    cursor: pointer;
+    top: -12px;
+    right: -24px;
+    opacity: 0;
+    transition: all $animation-swift-out;
+    padding: 8px;
+    transform: rotate(180deg);
+  }
+  &:hover {
+    .caretLeft {
+      opacity: 1;
+    }
+    .caretRight {
+      opacity: 1;
     }
   }
 }

+ 23 - 32
src/components/ReplicaExecutions/ReplicaExecutions.js

@@ -41,8 +41,9 @@ class ReplicaExecutions extends Component {
   constructor(props) {
     super(props)
 
+    this.changeExecution = this.changeExecution.bind(this)
+
     this.state = {
-      currentExecution: null,
       executionRef: null,
       tasks: null
     }
@@ -65,26 +66,13 @@ class ReplicaExecutions extends Component {
   componentWillReceiveProps(newProps, oldProps) {
     if (newProps.migration && newProps.migration.executions.length) {
       let execution = newProps.migration.executions[newProps.migration.executions.length - 1]
-      this.setState( {
-        currentExecution: {
-          label: `${execution.number} - ${moment(execution.created_at).format("MMM Do YYYY HH:mm")} - ${execution.status}`,
-          value: execution.id
-        },
+      this.setState({
         executionRef: execution,
         tasks: execution.tasks
       })
     }
   }
 
-  changeExecution(option) {
-    let execution = this.props.migration.executions.filter(execution => execution.id == option.value)[0]
-    this.setState({
-      currentExecution: option,
-      executionRef: execution,
-      tasks: execution.tasks
-    })
-  }
-
   executeNow() {
     MigrationActions.executeReplica(this.props.migration)
     clearInterval(this.timeout)
@@ -97,8 +85,14 @@ class ReplicaExecutions extends Component {
     })
   }
 
+  deleteExecution() {
+    MigrationActions.cancelMigration(this.props.migration, (replica, response) => {
+      this.refreshExecution()
+    })
+  }
+
   refreshExecution() {
-    MigrationActions.getReplicaExecutionDetail(this.props.migration, this.state.currentExecution.value,
+    MigrationActions.getReplicaExecutionDetail(this.props.migration, this.state.executionRef.id,
       (replica, executionId, response) => {
         let props = this.props
         props.migration.tasks = response.data.execution.tasks
@@ -111,7 +105,7 @@ class ReplicaExecutions extends Component {
   pollTasks() {
     if (this.props && this.props.migration) {
       if (this.props.migration.executions[this.props.migration.executions.length - 1].status == "RUNNING") {
-        MigrationActions.getReplicaExecutionDetail(this.props.migration, this.state.currentExecution.value,
+        MigrationActions.getReplicaExecutionDetail(this.props.migration, this.state.executionRef.id,
           (replica, executionId, response) => {
             this.setState({
               tasks: response.data.execution.tasks
@@ -121,9 +115,16 @@ class ReplicaExecutions extends Component {
     }
   }
 
+  changeExecution(execution) {
+    this.setState({
+      executionRef: execution,
+      tasks: execution.tasks
+    })
+  }
+
   render() {
     if (this.props.migration) {
-      let executionBtn = <button className="red wire" onClick={(e) => this.executeNow(e)}>Delete execution</button>
+      let executionBtn = null //<button className="red wire" onClick={(e) => this.deleteExecution(e)}>Delete execution</button>
       if (this.props.migration.executions &&
         this.props.migration.executions[this.props.migration.executions.length - 1].status == "RUNNING") {
         executionBtn = <button className="gray wire" onClick={(e) => this.cancelExecution(e)}>Cancel execution</button>
@@ -132,24 +133,14 @@ class ReplicaExecutions extends Component {
       let executionsSorted = this.props.migration.executions
       executionsSorted.sort((a, b) => a.number - b.number)
 
-      let executions = executionsSorted.map(execution => {
-        return {
-          label: `${execution.number} - ${moment(execution.created_at).format("MMM Do YYYY HH:mm")} - ${execution.status}`,
-          value: execution.id
-        }
-      })
-
       return (
         <div className={s.root}>
           <div className={s.container}>
-            <Dropdown
-              options={executions}
-              onChange={(e) => this.changeExecution(e)}
-              placeholder="Select execution"
-              value={this.state ? this.state.currentExecution : null}
-              className={s.changeExecutionBtn}
+            <ExecutionsTimeline
+              executions={this.props.migration.executions}
+              currentExecution={this.state.executionRef}
+              handleChangeExecution={this.changeExecution}
             />
-            <ExecutionsTimeline executions={this.props.migration.executions} currentExecution={this.state.executionRef}/>
             <div className={s.executionsWrapper}>
               <div className={s.leftSide}>
                 <h4>Execution #{this.state.executionRef && this.state.executionRef.number}</h4>

+ 0 - 5
src/components/Tasks/Tasks.scss

@@ -120,8 +120,3 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 }
 
-
-@keyframes rotate {
-  from {transform: rotate(0deg);}
-  to {transform: rotate(360deg);}
-}