/*
Copyright (C) 2017 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 .
*/
// @flow
import { observable, runInAction, computed, action } from 'mobx'
import type { Instance } from '../types/Instance'
import type { Endpoint } from '../types/Endpoint'
import InstanceSource from '../sources/InstanceSource'
import ApiCaller from '../utils/ApiCaller'
import configLoader from '../utils/Config'
class InstanceStore {
@observable instancesLoading = false
@observable instancesPerPage = 6
@observable currentPage = 1
@observable searchChunksLoading = false
@observable searchedInstances: Instance[] = []
@observable backgroundInstances: Instance[] = []
@observable backgroundChunksLoading = false
@observable searching = false
@observable searchNotFound = false
@observable reloading = false
@observable instancesDetails: Instance[] = []
@observable loadingInstancesDetails = true
@observable instancesDetailsCount = 0
@observable instancesDetailsRemaining = 0
@observable searchText = ''
@computed get instances(): Instance[] {
if (this.searchText && this.searchedInstances.length > 0) {
return this.searchedInstances
}
return this.backgroundInstances
}
@computed get chunksLoading(): boolean {
if (this.searchText) {
return this.searchChunksLoading
}
return this.backgroundChunksLoading
}
lastEndpointId: string
reqId: number
@action async loadInstancesInChunks(options: {
endpoint: Endpoint,
vmsPerPage?: number,
reload?: boolean,
env?: any,
useCache?: boolean,
}) {
let { endpoint, vmsPerPage, reload, env, useCache } = options
vmsPerPage = vmsPerPage || 6
ApiCaller.cancelRequests(`${endpoint.id}-chunk`)
this.backgroundInstances = []
if (reload) {
this.reloading = true
} else {
this.instancesLoading = true
}
this.backgroundChunksLoading = true
this.lastEndpointId = endpoint.id
let chunkSize = configLoader.config.instancesListBackgroundLoading
let chunkCount = Math.max(chunkSize[endpoint.type] || chunkSize.default, vmsPerPage)
let loadNextChunk = async (lastEndpointId?: string) => {
let currentEndpointId = endpoint.id
let instances = await InstanceSource.loadInstancesChunk(currentEndpointId, chunkCount, lastEndpointId, `${endpoint.id}-chunk`, undefined, env, useCache)
if (currentEndpointId !== this.lastEndpointId) {
return
}
let shouldContinue = this.loadInstancesInChunksSuccess(instances, chunkCount, reload)
if (shouldContinue) {
loadNextChunk(instances[instances.length - 1].id)
}
}
loadNextChunk()
}
@action loadInstancesInChunksSuccess(instances: Instance[], chunkCount: number, reload?: boolean): boolean {
this.backgroundInstances = [...this.backgroundInstances, ...instances]
if (reload) {
this.reloading = false
}
this.instancesLoading = false
if (instances.length < chunkCount) {
this.backgroundChunksLoading = false
return false
}
return true
}
@action async loadInstances(endpointId: string): Promise {
this.instancesLoading = true
this.lastEndpointId = endpointId
try {
let instances = await InstanceSource.loadInstances(endpointId, true)
if (endpointId !== this.lastEndpointId) {
return
}
this.loadInstancesSuccess(instances)
} catch (ex) {
if (endpointId !== this.lastEndpointId) {
return
}
runInAction(() => { this.instancesLoading = false })
throw ex
}
}
@action loadInstancesSuccess(instances: Instance[]) {
this.backgroundInstances = instances
this.instancesLoading = false
}
@action async searchInstances(endpoint: Endpoint, searchText: string) {
ApiCaller.cancelRequests(`${endpoint.id}-chunk-search`)
this.searchText = searchText
this.searchNotFound = false
if (!searchText) {
this.currentPage = 1
this.searchedInstances = []
return
}
if (!this.backgroundChunksLoading) {
this.searchedInstances = this.backgroundInstances
.filter(i => (i.instance_name || i.name).toLowerCase().indexOf(searchText.toLowerCase()) > -1)
this.searchNotFound = Boolean(this.searchedInstances.length === 0)
this.currentPage = 1
return
}
this.searching = true
this.searchChunksLoading = true
let chunkSize = configLoader.config.instancesListBackgroundLoading
let chunkCount = Math.max(chunkSize[endpoint.type] || chunkSize.default, this.instancesPerPage)
let loadNextChunk = async (lastEndpointId?: string) => {
let instances = await InstanceSource.loadInstancesChunk(
endpoint.id,
chunkCount,
lastEndpointId,
`${endpoint.id}-chunk-search`,
searchText
)
if (this.searching) {
runInAction(() => {
this.currentPage = 1
this.searchedInstances = []
})
}
let shouldContinue = this.searchInstancesSuccess(instances, chunkCount)
if (shouldContinue) {
loadNextChunk(instances[instances.length - 1].id)
}
}
loadNextChunk()
}
@action searchInstancesSuccess(instances: Instance[], chunkCount: number): boolean {
this.searchedInstances = [...this.searchedInstances, ...instances]
this.searching = false
this.searchNotFound = Boolean(this.searchedInstances.length === 0)
if (instances.length < chunkCount) {
this.searchChunksLoading = false
return false
}
return true
}
@action reloadInstances(endpoint: Endpoint, chunkSize?: number, env?: any) {
this.searchNotFound = false
this.searchText = ''
this.currentPage = 1
this.loadInstancesInChunks({ endpoint, vmsPerPage: chunkSize, reload: true, env })
}
@action cancelIntancesChunksLoading() {
ApiCaller.cancelRequests(`${this.lastEndpointId}-chunk`)
this.lastEndpointId = ''
this.searchNotFound = false
this.searchText = ''
this.currentPage = 1
}
@action setPage(page: number) {
this.currentPage = page
}
@action updateInstancesPerPage(instancesPerPage: number) {
this.currentPage = 1
this.instancesPerPage = instancesPerPage
}
@action async loadInstancesDetailsBulk(
instanceInfos: {
endpointId: string,
instanceNames: string[],
env?: ?any,
}[]
) {
this.reqId = !this.reqId ? 1 : this.reqId + 1
this.instancesDetails = []
this.loadingInstancesDetails = true
InstanceSource.cancelInstancesDetailsRequests(this.reqId - 1)
try {
await Promise.all(instanceInfos.map(async i => {
await Promise.all(i.instanceNames.map(async name => {
let instanceDetails = await InstanceSource.loadInstanceDetails({
endpointId: i.endpointId,
instanceName: name,
reqId: this.reqId,
quietError: false,
env: i.env,
cache: true,
})
runInAction(() => {
this.instancesDetails = this.instancesDetails.filter(i => (i.name || i.instance_name || '') !== name)
this.instancesDetails.push(instanceDetails.instance)
this.instancesDetails.sort(n => (n.name || n.instance_name || '')
.localeCompare(n.name || n.instance_name || ''))
})
}))
}))
} finally {
this.loadingInstancesDetails = false
}
}
@action async loadInstancesDetails(opts: {
endpointId: string,
instancesInfo: Instance[],
cache?: boolean,
quietError?: boolean,
env?: any,
targetProvider: string,
}): Promise {
let { endpointId, instancesInfo, cache, quietError, env, targetProvider } = opts
// Use reqId to be able to uniquely identify the request so all but the latest request can be igonred and canceled
this.reqId = !this.reqId ? 1 : this.reqId + 1
InstanceSource.cancelInstancesDetailsRequests(this.reqId - 1)
instancesInfo.sort((a, b) => (a.instance_name || a.name).localeCompare(b.instance_name || b.name))
let count = instancesInfo.length
this.loadingInstancesDetails = true
this.instancesDetails = []
this.loadingInstancesDetails = true
this.instancesDetailsCount = count
this.instancesDetailsRemaining = count
await new Promise(resolve => {
Promise.all(instancesInfo.map(async instanceInfo => {
try {
let resp: { instance: Instance, reqId: number } =
await InstanceSource.loadInstanceDetails({
endpointId,
instanceName: instanceInfo.instance_name || instanceInfo.name,
targetProvider,
reqId: this.reqId,
quietError,
env,
cache,
})
if (resp.reqId !== this.reqId) {
return
}
runInAction(() => {
this.instancesDetailsRemaining -= 1
this.loadingInstancesDetails = this.instancesDetailsRemaining > 0
if (this.instancesDetails.find(i => i.id === resp.instance.id)) {
this.instancesDetails = this.instancesDetails.filter(i => i.id !== resp.instance.id)
}
})
runInAction(() => {
this.instancesDetails = [
...this.instancesDetails,
resp.instance,
]
this.instancesDetails.sort((a, b) => (a.instance_name || a.name).localeCompare((b.instance_name || b.name)))
})
if (this.instancesDetailsRemaining === 0) {
resolve()
}
} catch (err) {
runInAction(() => {
this.instancesDetailsRemaining -= 1
this.loadingInstancesDetails = this.instancesDetailsRemaining > 0
})
if (!err || err.reqId !== this.reqId) {
return
}
if (count === 0) {
resolve()
}
}
}))
})
}
@action clearInstancesDetails() {
this.instancesDetails = []
this.loadingInstancesDetails = false
this.instancesDetailsCount = 0
this.instancesDetailsRemaining = 0
}
}
export default new InstanceStore()