ReplicaStore.ts 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. /*
  2. Copyright (C) 2017 Cloudbase Solutions SRL
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU Affero General Public License as
  5. published by the Free Software Foundation, either version 3 of the
  6. License, or (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU Affero General Public License for more details.
  11. You should have received a copy of the GNU Affero General Public License
  12. along with this program. If not, see <http://www.gnu.org/licenses/>.
  13. */
  14. import { observable, action, runInAction } from 'mobx'
  15. import notificationStore from './NotificationStore'
  16. import ReplicaSource from '../sources/ReplicaSource'
  17. import type {
  18. UpdateData, ReplicaItem, ReplicaItemDetails,
  19. } from '../@types/MainItem'
  20. import type { Execution, ExecutionTasks } from '../@types/Execution'
  21. import type { Endpoint } from '../@types/Endpoint'
  22. import type { Field } from '../@types/Field'
  23. class ReplicaStoreUtils {
  24. static getNewReplica(
  25. replicaDetails: ReplicaItemDetails,
  26. execution: Execution,
  27. ): ReplicaItemDetails {
  28. if (replicaDetails.executions) {
  29. return {
  30. ...replicaDetails,
  31. executions: [...replicaDetails.executions.filter(e => e.id !== execution.id), execution],
  32. }
  33. }
  34. return {
  35. ...replicaDetails,
  36. executions: [execution],
  37. }
  38. }
  39. }
  40. class ReplicaStore {
  41. @observable replicas: ReplicaItem[] = []
  42. @observable loading: boolean = false
  43. @observable replicaDetails: ReplicaItemDetails | null = null
  44. @observable replicaDetailsLoading: boolean = false
  45. @observable executionsTasks: ExecutionTasks[] = []
  46. @observable executionsTasksLoading: boolean = false
  47. @observable backgroundLoading: boolean = false
  48. @observable startingExecution: boolean = false
  49. @observable replicasWithDisks: ReplicaItemDetails[] = []
  50. @observable replicasWithDisksLoading: boolean = false
  51. replicasLoaded: boolean = false
  52. addExecution: { replicaId: string, execution: Execution } | null = null
  53. @action async getReplicas(
  54. options?: { showLoading?: boolean, skipLog?: boolean, quietError?: boolean },
  55. ): Promise<void> {
  56. this.backgroundLoading = true
  57. if ((options && options.showLoading) || !this.replicasLoaded) {
  58. this.loading = true
  59. }
  60. try {
  61. const replicas = await ReplicaSource
  62. .getReplicas(options && options.skipLog, options && options.quietError)
  63. this.getReplicasSuccess(replicas)
  64. } finally {
  65. this.getReplicasDone()
  66. }
  67. }
  68. @action async getReplicaDetails(options: {
  69. replicaId: string, showLoading?: boolean, polling?: boolean,
  70. }) {
  71. const { replicaId, showLoading, polling } = options
  72. if (showLoading) {
  73. this.replicaDetailsLoading = true
  74. }
  75. try {
  76. const replica = await ReplicaSource.getReplicaDetails({ replicaId, polling })
  77. runInAction(() => {
  78. this.replicaDetails = replica
  79. })
  80. } finally {
  81. runInAction(() => {
  82. this.replicaDetailsLoading = false
  83. })
  84. }
  85. }
  86. @action clearDetails() {
  87. this.replicaDetails = null
  88. }
  89. @action getReplicasSuccess(replicas: ReplicaItem[]) {
  90. this.replicasLoaded = true
  91. this.replicas = replicas
  92. }
  93. @action getReplicasDone() {
  94. this.loading = false
  95. this.backgroundLoading = false
  96. }
  97. private currentlyLoadingExecution: string = ''
  98. @action async getExecutionTasks(
  99. options: {
  100. replicaId: string,
  101. executionId?: string,
  102. polling?: boolean,
  103. },
  104. ) {
  105. const {
  106. replicaId, executionId, polling,
  107. } = options
  108. if (!polling && this.currentlyLoadingExecution === executionId) {
  109. return
  110. }
  111. this.currentlyLoadingExecution = polling ? this.currentlyLoadingExecution : executionId || ''
  112. if (!this.currentlyLoadingExecution) {
  113. return
  114. }
  115. if (!this.executionsTasks.find(e => e.id === this.currentlyLoadingExecution)) {
  116. this.executionsTasksLoading = true
  117. }
  118. try {
  119. const executionTasks = await ReplicaSource.getExecutionTasks({
  120. replicaId,
  121. executionId: this.currentlyLoadingExecution,
  122. polling,
  123. })
  124. runInAction(() => {
  125. this.executionsTasks = [
  126. ...this.executionsTasks.filter(e => e.id !== this.currentlyLoadingExecution),
  127. executionTasks,
  128. ]
  129. })
  130. } catch (err) {
  131. console.error(err)
  132. } finally {
  133. runInAction(() => {
  134. this.executionsTasksLoading = false
  135. })
  136. }
  137. }
  138. @action async execute(replicaId: string, fields?: Field[]): Promise<void> {
  139. this.startingExecution = true
  140. const execution = await ReplicaSource.execute(replicaId, fields)
  141. this.executeSuccess(replicaId, execution)
  142. }
  143. @action executeSuccess(replicaId: string, execution: Execution) {
  144. if (this.replicaDetails?.id === replicaId) {
  145. const updatedReplica = ReplicaStoreUtils
  146. .getNewReplica(this.replicaDetails, execution)
  147. this.replicaDetails = updatedReplica
  148. }
  149. this.getExecutionTasks({ replicaId, executionId: execution.id })
  150. this.startingExecution = false
  151. }
  152. async cancelExecution(
  153. options: {replicaId: string, executionId?: string, force?: boolean},
  154. ): Promise<void> {
  155. await ReplicaSource.cancelExecution(options)
  156. if (options.force) {
  157. notificationStore.alert('Force cancelled', 'success')
  158. } else {
  159. notificationStore.alert('Cancelled', 'success')
  160. }
  161. }
  162. async deleteExecution(replicaId: string, executionId: string): Promise<void> {
  163. await ReplicaSource.deleteExecution(replicaId, executionId)
  164. this.deleteExecutionSuccess(replicaId, executionId)
  165. }
  166. @action deleteExecutionSuccess(replicaId: string, executionId: string) {
  167. let executions = []
  168. if (this.replicaDetails?.id === replicaId) {
  169. executions = [...this.replicaDetails.executions.filter(e => e.id !== executionId)]
  170. this.replicaDetails.executions = executions
  171. }
  172. if (executionId === this.currentlyLoadingExecution) {
  173. this.currentlyLoadingExecution = ''
  174. }
  175. }
  176. async delete(replicaId: string) {
  177. await ReplicaSource.delete(replicaId)
  178. runInAction(() => { this.replicas = this.replicas.filter(r => r.id !== replicaId) })
  179. }
  180. async deleteDisks(replicaId: string) {
  181. const execution = await ReplicaSource.deleteDisks(replicaId)
  182. this.deleteDisksSuccess(replicaId, execution)
  183. }
  184. @action deleteDisksSuccess(replicaId: string, execution: Execution) {
  185. if (this.replicaDetails?.id === replicaId) {
  186. const updatedReplica = ReplicaStoreUtils
  187. .getNewReplica(this.replicaDetails, execution)
  188. this.replicaDetails = updatedReplica
  189. }
  190. }
  191. async update(options: {
  192. replica: ReplicaItemDetails,
  193. destinationEndpoint: Endpoint,
  194. updateData: UpdateData,
  195. defaultStorage: { value: string | null, busType?: string | null },
  196. storageConfigDefault: string,
  197. }) {
  198. await ReplicaSource.update(options)
  199. }
  200. testReplicaHasDisks(replica: ReplicaItemDetails | null) {
  201. if (!replica || !replica.executions || replica.executions.length === 0) {
  202. return false
  203. }
  204. if (!replica.executions.find(e => e.type === 'replica_execution')) {
  205. return false
  206. }
  207. const lastExecution = replica.executions[replica.executions.length - 1]
  208. if (lastExecution.type === 'replica_disks_delete' && lastExecution.status === 'COMPLETED') {
  209. return false
  210. }
  211. return true
  212. }
  213. @action
  214. async loadHaveReplicasDisks(replicas: ReplicaItem[]) {
  215. this.replicasWithDisksLoading = true
  216. try {
  217. const replicaDetails = await Promise.all(replicas
  218. .map(replica => ReplicaSource.getReplicaDetails({ replicaId: replica.id })))
  219. runInAction(() => {
  220. this.replicasWithDisks = replicaDetails.filter(r => this.testReplicaHasDisks(r))
  221. })
  222. } finally {
  223. runInAction(() => {
  224. this.replicasWithDisksLoading = false
  225. })
  226. }
  227. }
  228. }
  229. export default new ReplicaStore()