2535 lines
69 KiB
JavaScript
2535 lines
69 KiB
JavaScript
import asap from 'asap'
|
|
import cookies from 'cookies-js'
|
|
import fpSortBy from 'lodash/fp/sortBy'
|
|
import React from 'react'
|
|
import URL from 'url-parse'
|
|
import Xo from 'xo-lib'
|
|
import { createBackoff } from 'jsonrpc-websocket-client'
|
|
import {
|
|
assign,
|
|
filter,
|
|
forEach,
|
|
get,
|
|
includes,
|
|
isEmpty,
|
|
isEqual,
|
|
map,
|
|
once,
|
|
size,
|
|
sortBy,
|
|
throttle,
|
|
} from 'lodash'
|
|
import { lastly, reflect, tap } from 'promise-toolbox'
|
|
import {
|
|
forbiddenOperation,
|
|
noHostsAvailable,
|
|
vmIsTemplate,
|
|
} from 'xo-common/api-errors'
|
|
|
|
import _ from '../intl'
|
|
import fetch, { post } from '../fetch'
|
|
import invoke from '../invoke'
|
|
import logError from '../log-error'
|
|
import renderXoItem, { renderXoItemFromId } from '../render-xo-item'
|
|
import store from 'store'
|
|
import { alert, chooseAction, confirm } from '../modal'
|
|
import { error, info, success } from '../notification'
|
|
import { getObject } from 'selectors'
|
|
import { noop, resolveId, resolveIds } from '../utils'
|
|
import {
|
|
connected,
|
|
disconnected,
|
|
signedIn,
|
|
signedOut,
|
|
updateObjects,
|
|
updatePermissions,
|
|
} from '../store/actions'
|
|
|
|
// ===================================================================
|
|
|
|
export const XEN_DEFAULT_CPU_WEIGHT = 256
|
|
export const XEN_DEFAULT_CPU_CAP = 0
|
|
|
|
// ===================================================================
|
|
|
|
export const XEN_VIDEORAM_VALUES = [1, 2, 4, 8, 16]
|
|
|
|
// ===================================================================
|
|
|
|
export const isSrWritable = sr => sr && sr.content_type !== 'iso' && sr.size > 0
|
|
export const isSrShared = sr => sr && sr.shared
|
|
export const isVmRunning = vm => vm && vm.power_state === 'Running'
|
|
|
|
// ===================================================================
|
|
|
|
export const signOut = () => {
|
|
cookies.expire('token')
|
|
window.location.reload(true)
|
|
}
|
|
|
|
export const connect = () => {
|
|
xo.open(createBackoff()).catch(error => {
|
|
logError(error, 'failed to connect to xo-server')
|
|
})
|
|
}
|
|
|
|
const xo = invoke(() => {
|
|
const token = cookies.get('token')
|
|
if (!token) {
|
|
signOut()
|
|
throw new Error('no valid token')
|
|
}
|
|
|
|
const xo = new Xo({
|
|
credentials: { token },
|
|
})
|
|
|
|
xo.on('authenticationFailure', signOut)
|
|
xo.on('scheduledAttempt', ({ delay }) => {
|
|
console.warn('next attempt in %s ms', delay)
|
|
})
|
|
|
|
xo.on('closed', connect)
|
|
|
|
return xo
|
|
})
|
|
connect()
|
|
|
|
const _signIn = new Promise(resolve => xo.once('authenticated', resolve))
|
|
|
|
const _call = (method, params) => {
|
|
let promise = _signIn.then(() => xo.call(method, params))
|
|
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
promise = promise::tap(null, error => {
|
|
console.error('XO error', {
|
|
method,
|
|
params,
|
|
code: error.code,
|
|
message: error.message,
|
|
data: error.data,
|
|
})
|
|
})
|
|
}
|
|
|
|
return promise
|
|
}
|
|
|
|
// ===================================================================
|
|
|
|
export const connectStore = store => {
|
|
let updates = {}
|
|
const sendUpdates = throttle(() => {
|
|
store.dispatch(updateObjects(updates))
|
|
updates = {}
|
|
}, 5e2)
|
|
|
|
xo.on('open', () => store.dispatch(connected()))
|
|
xo.on('closed', () => {
|
|
store.dispatch(signedOut())
|
|
store.dispatch(disconnected())
|
|
})
|
|
xo.on('authenticated', () => {
|
|
store.dispatch(signedIn(xo.user))
|
|
|
|
_call('xo.getAllObjects', { ndjson: true })
|
|
.then(({ $getFrom }) => fetch('.' + $getFrom))
|
|
.then(response => response.text())
|
|
.then(data => {
|
|
const objects = Object.create(null)
|
|
|
|
const { length } = data
|
|
let i = 0
|
|
while (i < length) {
|
|
let j = data.indexOf('\n', i)
|
|
|
|
// no final \n
|
|
if (j === -1) {
|
|
j = length
|
|
}
|
|
|
|
// non empty line
|
|
if (j !== i) {
|
|
const object = JSON.parse(data.slice(i, j))
|
|
objects[object.id] = object
|
|
}
|
|
|
|
i = j + 1
|
|
}
|
|
|
|
store.dispatch(updateObjects(objects))
|
|
})
|
|
})
|
|
xo.on('notification', notification => {
|
|
if (notification.method !== 'all') {
|
|
return
|
|
}
|
|
|
|
assign(updates, notification.params.items)
|
|
sendUpdates()
|
|
})
|
|
subscribePermissions(permissions =>
|
|
store.dispatch(updatePermissions(permissions))
|
|
)
|
|
|
|
// work around to keep the user in Redux store up to date
|
|
//
|
|
// FIXME: store subscriptions data directly in Redux
|
|
subscribeUsers(user => {
|
|
store.dispatch(signedIn(xo.user))
|
|
})
|
|
}
|
|
|
|
// -------------------------------------------------------------------
|
|
|
|
export const resolveUrl = invoke(
|
|
xo._url, // FIXME: accessing private prop
|
|
baseUrl => to => new URL(to, baseUrl).toString()
|
|
)
|
|
|
|
// -------------------------------------------------------------------
|
|
|
|
const createSubscription = cb => {
|
|
const delay = 5e3
|
|
|
|
const subscribers = Object.create(null)
|
|
let cache
|
|
let n = 0
|
|
let nextId = 0
|
|
let timeout
|
|
|
|
let running = false
|
|
|
|
const uninstall = () => {
|
|
clearTimeout(timeout)
|
|
cache = undefined
|
|
}
|
|
|
|
const loop = () => {
|
|
if (running) {
|
|
return
|
|
}
|
|
|
|
running = true
|
|
_signIn.then(() => cb()).then(
|
|
result => {
|
|
running = false
|
|
|
|
if (n === 0) {
|
|
return uninstall()
|
|
}
|
|
|
|
timeout = setTimeout(loop, delay)
|
|
|
|
if (!isEqual(result, cache)) {
|
|
cache = result
|
|
|
|
forEach(subscribers, subscriber => {
|
|
// A subscriber might have disappeared during iteration.
|
|
//
|
|
// E.g.: if a subscriber triggers the subscription of another.
|
|
if (subscriber) {
|
|
subscriber(result)
|
|
}
|
|
})
|
|
}
|
|
},
|
|
error => {
|
|
running = false
|
|
|
|
if (n === 0) {
|
|
return uninstall()
|
|
}
|
|
|
|
console.error(error)
|
|
}
|
|
)
|
|
}
|
|
|
|
const subscribe = cb => {
|
|
const id = nextId++
|
|
subscribers[id] = cb
|
|
|
|
if (n++ !== 0) {
|
|
if (cache !== undefined) {
|
|
asap(() => cb(cache))
|
|
}
|
|
} else {
|
|
loop()
|
|
}
|
|
|
|
return once(() => {
|
|
delete subscribers[id]
|
|
|
|
if (--n === 0) {
|
|
uninstall()
|
|
}
|
|
})
|
|
}
|
|
|
|
subscribe.forceRefresh = () => {
|
|
if (n) {
|
|
clearTimeout(timeout)
|
|
loop()
|
|
}
|
|
}
|
|
|
|
return subscribe
|
|
}
|
|
|
|
// Subscriptions -----------------------------------------------------
|
|
|
|
export const subscribeCurrentUser = createSubscription(() => xo.refreshUser())
|
|
|
|
export const subscribeAcls = createSubscription(() => _call('acl.get'))
|
|
|
|
export const subscribeJobs = createSubscription(() => _call('job.getAll'))
|
|
|
|
export const subscribeJobsLogs = createSubscription(() =>
|
|
_call('log.get', { namespace: 'jobs' })
|
|
)
|
|
|
|
export const subscribeApiLogs = createSubscription(() =>
|
|
_call('log.get', { namespace: 'api' })
|
|
)
|
|
|
|
export const subscribePermissions = createSubscription(() =>
|
|
_call('acl.getCurrentPermissions')
|
|
)
|
|
|
|
export const subscribePlugins = createSubscription(() => _call('plugin.get'))
|
|
|
|
export const subscribeRemotes = createSubscription(() => _call('remote.getAll'))
|
|
|
|
export const subscribeResourceSets = createSubscription(() =>
|
|
_call('resourceSet.getAll')
|
|
)
|
|
|
|
export const subscribeSchedules = createSubscription(() =>
|
|
_call('schedule.getAll')
|
|
)
|
|
|
|
export const subscribeServers = createSubscription(
|
|
invoke(fpSortBy('host'), sort => () => _call('server.getAll').then(sort))
|
|
)
|
|
|
|
export const subscribeUsers = createSubscription(() =>
|
|
_call('user.getAll').then(users => {
|
|
forEach(users, user => {
|
|
user.type = 'user'
|
|
})
|
|
|
|
return sortBy(users, 'email')
|
|
})
|
|
)
|
|
|
|
export const subscribeGroups = createSubscription(() =>
|
|
_call('group.getAll').then(groups => {
|
|
forEach(groups, group => {
|
|
group.type = 'group'
|
|
})
|
|
|
|
return sortBy(groups, 'name')
|
|
})
|
|
)
|
|
|
|
export const subscribeRoles = createSubscription(
|
|
invoke(sortBy('name'), sort => () => _call('role.getAll').then(sort))
|
|
)
|
|
|
|
export const subscribeIpPools = createSubscription(() => _call('ipPool.getAll'))
|
|
|
|
export const subscribeResourceCatalog = createSubscription(() =>
|
|
_call('cloud.getResourceCatalog')
|
|
)
|
|
|
|
const checkSrCurrentStateSubscriptions = {}
|
|
export const subscribeCheckSrCurrentState = (pool, cb) => {
|
|
const poolId = resolveId(pool)
|
|
|
|
if (!checkSrCurrentStateSubscriptions[poolId]) {
|
|
checkSrCurrentStateSubscriptions[poolId] = createSubscription(() =>
|
|
_call('xosan.checkSrCurrentState', { poolId })
|
|
)
|
|
}
|
|
|
|
return checkSrCurrentStateSubscriptions[poolId](cb)
|
|
}
|
|
subscribeCheckSrCurrentState.forceRefresh = pool => {
|
|
if (pool === undefined) {
|
|
forEach(checkSrCurrentStateSubscriptions, subscription =>
|
|
subscription.forceRefresh()
|
|
)
|
|
return
|
|
}
|
|
|
|
const subscription = checkSrCurrentStateSubscriptions[resolveId(pool)]
|
|
if (subscription !== undefined) {
|
|
subscription.forceRefresh()
|
|
}
|
|
}
|
|
|
|
const missingPatchesByHost = {}
|
|
export const subscribeHostMissingPatches = (host, cb) => {
|
|
const hostId = resolveId(host)
|
|
|
|
if (missingPatchesByHost[hostId] == null) {
|
|
missingPatchesByHost[hostId] = createSubscription(() =>
|
|
getHostMissingPatches(host)
|
|
)
|
|
}
|
|
|
|
return missingPatchesByHost[hostId](cb)
|
|
}
|
|
subscribeHostMissingPatches.forceRefresh = host => {
|
|
if (host === undefined) {
|
|
forEach(missingPatchesByHost, subscription => subscription.forceRefresh())
|
|
return
|
|
}
|
|
|
|
const subscription = missingPatchesByHost[resolveId(host)]
|
|
if (subscription !== undefined) {
|
|
subscription.forceRefresh()
|
|
}
|
|
}
|
|
|
|
const volumeInfoBySr = {}
|
|
export const subscribeVolumeInfo = ({ sr, infoType }, cb) => {
|
|
sr = resolveId(sr)
|
|
|
|
if (volumeInfoBySr[sr] == null) {
|
|
volumeInfoBySr[sr] = {}
|
|
}
|
|
|
|
if (volumeInfoBySr[sr][infoType] == null) {
|
|
volumeInfoBySr[sr][infoType] = createSubscription(() =>
|
|
_call('xosan.getVolumeInfo', { sr, infoType })
|
|
)
|
|
}
|
|
|
|
return volumeInfoBySr[sr][infoType](cb)
|
|
}
|
|
subscribeVolumeInfo.forceRefresh = (() => {
|
|
const refreshSrVolumeInfo = volumeInfo => {
|
|
forEach(volumeInfo, subscription => subscription.forceRefresh())
|
|
}
|
|
|
|
return sr => {
|
|
if (sr === undefined) {
|
|
forEach(volumeInfoBySr, refreshSrVolumeInfo)
|
|
} else {
|
|
refreshSrVolumeInfo(volumeInfoBySr[sr])
|
|
}
|
|
}
|
|
})()
|
|
|
|
const unhealthyVdiChainsLengthSubscriptionsBySr = {}
|
|
export const createSrUnhealthyVdiChainsLengthSubscription = sr => {
|
|
sr = resolveId(sr)
|
|
let subscription = unhealthyVdiChainsLengthSubscriptionsBySr[sr]
|
|
if (subscription === undefined) {
|
|
subscription = createSubscription(() =>
|
|
_call('sr.getUnhealthyVdiChainsLength', { sr })
|
|
)
|
|
unhealthyVdiChainsLengthSubscriptionsBySr[sr] = subscription
|
|
}
|
|
return subscription
|
|
}
|
|
|
|
// System ============================================================
|
|
|
|
export const apiMethods = _call('system.getMethodsInfo')
|
|
|
|
export const serverVersion = _call('system.getServerVersion')
|
|
|
|
export const getXoServerTimezone = _call('system.getServerTimezone')
|
|
|
|
// XO --------------------------------------------------------------------------
|
|
|
|
export const importConfig = config =>
|
|
_call('xo.importConfig').then(({ $sendTo }) =>
|
|
post($sendTo, config).then(response => {
|
|
if (response.status !== 200) {
|
|
throw new Error('config import failed')
|
|
}
|
|
})
|
|
)
|
|
|
|
export const exportConfig = () =>
|
|
_call('xo.exportConfig').then(({ $getFrom: url }) => {
|
|
window.location = `.${url}`
|
|
})
|
|
|
|
// Server ------------------------------------------------------------
|
|
|
|
export const addServer = (host, username, password, label, allowUnauthorized) =>
|
|
_call('server.add', {
|
|
allowUnauthorized,
|
|
host,
|
|
label,
|
|
password,
|
|
username,
|
|
})::tap(subscribeServers.forceRefresh, () =>
|
|
error(_('serverError'), _('serverAddFailed'))
|
|
)
|
|
|
|
export const editServer = (server, props) =>
|
|
_call('server.set', { ...props, id: resolveId(server) })::tap(
|
|
subscribeServers.forceRefresh
|
|
)
|
|
|
|
export const connectServer = server =>
|
|
_call('server.connect', { id: resolveId(server) })::lastly(
|
|
subscribeServers.forceRefresh
|
|
)
|
|
|
|
export const disconnectServer = server =>
|
|
_call('server.disconnect', { id: resolveId(server) })::tap(
|
|
subscribeServers.forceRefresh
|
|
)
|
|
|
|
export const removeServer = server =>
|
|
_call('server.remove', { id: resolveId(server) })::tap(
|
|
subscribeServers.forceRefresh
|
|
)
|
|
|
|
// Pool --------------------------------------------------------------
|
|
|
|
export const editPool = (pool, props) =>
|
|
_call('pool.set', { id: resolveId(pool), ...props })
|
|
|
|
import AddHostModalBody from './add-host-modal' // eslint-disable-line import/first
|
|
export const addHostToPool = (pool, host) => {
|
|
if (host) {
|
|
return confirm({
|
|
icon: 'add',
|
|
title: _('addHostModalTitle'),
|
|
body: _('addHostModalMessage', {
|
|
pool: pool.name_label,
|
|
host: host.name_label,
|
|
}),
|
|
}).then(() =>
|
|
_call('pool.mergeInto', {
|
|
source: host.$pool,
|
|
target: pool.id,
|
|
force: true,
|
|
})
|
|
)
|
|
}
|
|
|
|
return confirm({
|
|
icon: 'add',
|
|
title: _('addHostModalTitle'),
|
|
body: <AddHostModalBody pool={pool} />,
|
|
}).then(params => {
|
|
if (!params.host) {
|
|
error(_('addHostNoHost'), _('addHostNoHostMessage'))
|
|
return
|
|
}
|
|
return _call('pool.mergeInto', {
|
|
source: params.host.$pool,
|
|
target: pool.id,
|
|
force: true,
|
|
}).catch(error => {
|
|
if (error.code !== 'HOSTS_NOT_HOMOGENEOUS') {
|
|
throw error
|
|
}
|
|
|
|
error(_('addHostErrorTitle'), _('addHostNotHomogeneousErrorMessage'))
|
|
})
|
|
}, noop)
|
|
}
|
|
|
|
export const detachHost = host =>
|
|
confirm({
|
|
icon: 'host-eject',
|
|
title: _('detachHostModalTitle'),
|
|
body: _('detachHostModalMessage', {
|
|
host: <strong>{host.name_label}</strong>,
|
|
}),
|
|
}).then(() => _call('host.detach', { host: host.id }))
|
|
|
|
export const forgetHost = host =>
|
|
confirm({
|
|
icon: 'host-forget',
|
|
title: _('forgetHostModalTitle'),
|
|
body: _('forgetHostModalMessage', {
|
|
host: <strong>{host.name_label}</strong>,
|
|
}),
|
|
}).then(() => _call('host.forget', { host: resolveId(host) }))
|
|
|
|
export const setDefaultSr = sr =>
|
|
_call('pool.setDefaultSr', { sr: resolveId(sr) })
|
|
|
|
export const setPoolMaster = host =>
|
|
confirm({
|
|
title: _('setPoolMasterModalTitle'),
|
|
body: _('setPoolMasterModalMessage', {
|
|
host: <strong>{host.name_label}</strong>,
|
|
}),
|
|
}).then(() => _call('pool.setPoolMaster', { host: resolveId(host) }), noop)
|
|
|
|
// Host --------------------------------------------------------------
|
|
|
|
export const editHost = (host, props) =>
|
|
_call('host.set', { ...props, id: resolveId(host) })
|
|
|
|
export const fetchHostStats = (host, granularity) =>
|
|
_call('host.stats', { host: resolveId(host), granularity })
|
|
|
|
export const setRemoteSyslogHost = (host, syslogDestination) =>
|
|
_call('host.setRemoteSyslogHost', {
|
|
id: resolveId(host),
|
|
syslogDestination,
|
|
})
|
|
|
|
export const setRemoteSyslogHosts = (hosts, syslogDestination) =>
|
|
Promise.all(map(hosts, host => setRemoteSyslogHost(host, syslogDestination)))
|
|
|
|
export const restartHost = (host, force = false) =>
|
|
confirm({
|
|
title: _('restartHostModalTitle'),
|
|
body: _('restartHostModalMessage'),
|
|
}).then(
|
|
() =>
|
|
_call('host.restart', { id: resolveId(host), force }).catch(error => {
|
|
if (noHostsAvailable.is(error)) {
|
|
alert(
|
|
_('noHostsAvailableErrorTitle'),
|
|
_('noHostsAvailableErrorMessage')
|
|
)
|
|
}
|
|
}),
|
|
noop
|
|
)
|
|
|
|
export const restartHosts = (hosts, force = false) => {
|
|
const nHosts = size(hosts)
|
|
return confirm({
|
|
title: _('restartHostsModalTitle', { nHosts }),
|
|
body: _('restartHostsModalMessage', { nHosts }),
|
|
}).then(
|
|
() =>
|
|
Promise.all(
|
|
map(hosts, host =>
|
|
_call('host.restart', { id: resolveId(host), force })::reflect()
|
|
)
|
|
).then(results => {
|
|
const nbErrors = filter(results, result => !result.isFulfilled()).length
|
|
if (nbErrors) {
|
|
return alert(
|
|
_('failHostBulkRestartTitle'),
|
|
_('failHostBulkRestartMessage', {
|
|
failedHosts: nbErrors,
|
|
totalHosts: results.length,
|
|
})
|
|
)
|
|
}
|
|
}),
|
|
noop
|
|
)
|
|
}
|
|
|
|
export const restartHostAgent = host =>
|
|
_call('host.restart_agent', { id: resolveId(host) })
|
|
|
|
export const restartHostsAgents = hosts => {
|
|
const nHosts = size(hosts)
|
|
return confirm({
|
|
title: _('restartHostsAgentsModalTitle', { nHosts }),
|
|
body: _('restartHostsAgentsModalMessage', { nHosts }),
|
|
}).then(() => Promise.all(map(hosts, restartHostAgent)), noop)
|
|
}
|
|
|
|
export const startHost = host => _call('host.start', { id: resolveId(host) })
|
|
|
|
export const stopHost = host =>
|
|
confirm({
|
|
title: _('stopHostModalTitle'),
|
|
body: _('stopHostModalMessage'),
|
|
}).then(() => _call('host.stop', { id: resolveId(host) }), noop)
|
|
|
|
export const stopHosts = hosts => {
|
|
const nHosts = size(hosts)
|
|
return confirm({
|
|
title: _('stopHostsModalTitle', { nHosts }),
|
|
body: _('stopHostsModalMessage', { nHosts }),
|
|
}).then(
|
|
() => map(hosts, host => _call('host.stop', { id: resolveId(host) })),
|
|
noop
|
|
)
|
|
}
|
|
|
|
export const enableHost = host => _call('host.enable', { id: resolveId(host) })
|
|
|
|
export const disableHost = host =>
|
|
_call('host.disable', { id: resolveId(host) })
|
|
|
|
const missingUpdatePluginByHost = { __proto__: null }
|
|
export const getHostMissingPatches = async host => {
|
|
const hostId = resolveId(host)
|
|
if (host.productBrand !== 'XCP-ng') {
|
|
const patches = await _call('host.listMissingPatches', { host: hostId })
|
|
// Hide paid patches to XS-free users
|
|
return host.license_params.sku_type !== 'free'
|
|
? patches
|
|
: filter(patches, { paid: false })
|
|
}
|
|
if (missingUpdatePluginByHost[hostId]) {
|
|
return null
|
|
}
|
|
try {
|
|
return await _call('host.listMissingPatches', { host: hostId })
|
|
} catch (_) {
|
|
missingUpdatePluginByHost[hostId] = true
|
|
return null
|
|
}
|
|
}
|
|
|
|
export const emergencyShutdownHost = host =>
|
|
confirm({
|
|
title: _('emergencyShutdownHostModalTitle'),
|
|
body: _('emergencyShutdownHostModalMessage', {
|
|
host: <strong>{host.name_label}</strong>,
|
|
}),
|
|
}).then(() => _call('host.emergencyShutdownHost', { host: resolveId(host) }))
|
|
|
|
export const emergencyShutdownHosts = hosts => {
|
|
const nHosts = size(hosts)
|
|
return confirm({
|
|
title: _('emergencyShutdownHostsModalTitle', { nHosts }),
|
|
body: _('emergencyShutdownHostsModalMessage', { nHosts }),
|
|
}).then(() => map(hosts, host => emergencyShutdownHost(host)), noop)
|
|
}
|
|
|
|
export const installHostPatch = (host, { uuid }) =>
|
|
_call('host.installPatch', { host: resolveId(host), patch: uuid })::tap(() =>
|
|
subscribeHostMissingPatches.forceRefresh(host)
|
|
)
|
|
|
|
export const installAllHostPatches = host =>
|
|
_call('host.installAllPatches', { host: resolveId(host) })::tap(() =>
|
|
subscribeHostMissingPatches.forceRefresh(host)
|
|
)
|
|
|
|
export const installAllPatchesOnPool = pool =>
|
|
_call('pool.installAllPatches', { pool: resolveId(pool) })::tap(() =>
|
|
subscribeHostMissingPatches.forceRefresh()
|
|
)
|
|
|
|
export const installSupplementalPack = (host, file) => {
|
|
info(
|
|
_('supplementalPackInstallStartedTitle'),
|
|
_('supplementalPackInstallStartedMessage')
|
|
)
|
|
|
|
return _call('host.installSupplementalPack', { host: resolveId(host) }).then(
|
|
({ $sendTo }) =>
|
|
post($sendTo, file)
|
|
.then(res => {
|
|
if (res.status !== 200) {
|
|
throw new Error('installing supplemental pack failed')
|
|
}
|
|
|
|
success(
|
|
_('supplementalPackInstallSuccessTitle'),
|
|
_('supplementalPackInstallSuccessMessage')
|
|
)
|
|
})
|
|
.catch(err => {
|
|
error(
|
|
_('supplementalPackInstallErrorTitle'),
|
|
_('supplementalPackInstallErrorMessage')
|
|
)
|
|
throw err
|
|
})
|
|
)
|
|
}
|
|
|
|
export const installSupplementalPackOnAllHosts = (pool, file) => {
|
|
info(
|
|
_('supplementalPackInstallStartedTitle'),
|
|
_('supplementalPackInstallStartedMessage')
|
|
)
|
|
|
|
return _call('pool.installSupplementalPack', { pool: resolveId(pool) }).then(
|
|
({ $sendTo }) =>
|
|
post($sendTo, file)
|
|
.then(res => {
|
|
if (res.status !== 200) {
|
|
throw new Error('installing supplemental pack failed')
|
|
}
|
|
|
|
success(
|
|
_('supplementalPackInstallSuccessTitle'),
|
|
_('supplementalPackInstallSuccessMessage')
|
|
)
|
|
})
|
|
.catch(err => {
|
|
error(
|
|
_('supplementalPackInstallErrorTitle'),
|
|
_('supplementalPackInstallErrorMessage')
|
|
)
|
|
throw err
|
|
})
|
|
)
|
|
}
|
|
|
|
// Containers --------------------------------------------------------
|
|
|
|
export const pauseContainer = (vm, container) =>
|
|
_call('docker.pause', { vm: resolveId(vm), container })
|
|
|
|
export const restartContainer = (vm, container) =>
|
|
_call('docker.restart', { vm: resolveId(vm), container })
|
|
|
|
export const startContainer = (vm, container) =>
|
|
_call('docker.start', { vm: resolveId(vm), container })
|
|
|
|
export const stopContainer = (vm, container) =>
|
|
_call('docker.stop', { vm: resolveId(vm), container })
|
|
|
|
export const unpauseContainer = (vm, container) =>
|
|
_call('docker.unpause', { vm: resolveId(vm), container })
|
|
|
|
// VM ----------------------------------------------------------------
|
|
|
|
const chooseActionToUnblockForbiddenStartVm = props =>
|
|
chooseAction({
|
|
icon: 'alarm',
|
|
buttons: [
|
|
{ label: _('cloneAndStartVM'), value: 'clone', btnStyle: 'success' },
|
|
{ label: _('forceStartVm'), value: 'force', btnStyle: 'danger' },
|
|
],
|
|
...props,
|
|
})
|
|
|
|
const cloneAndStartVM = async vm => _call('vm.start', { id: await cloneVm(vm) })
|
|
|
|
export const startVm = vm =>
|
|
_call('vm.start', { id: resolveId(vm) }).catch(async reason => {
|
|
if (!forbiddenOperation.is(reason)) {
|
|
throw reason
|
|
}
|
|
|
|
const choice = await chooseActionToUnblockForbiddenStartVm({
|
|
body: _('blockedStartVmModalMessage'),
|
|
title: _('forceStartVmModalTitle'),
|
|
})
|
|
|
|
if (choice === 'clone') {
|
|
return cloneAndStartVM(vm)
|
|
}
|
|
|
|
return _call('vm.start', { id: resolveId(vm), force: true })
|
|
})
|
|
|
|
export const startVms = vms =>
|
|
confirm({
|
|
title: _('startVmsModalTitle', { vms: vms.length }),
|
|
body: _('startVmsModalMessage', { vms: vms.length }),
|
|
}).then(async () => {
|
|
const forbiddenStart = []
|
|
let nErrors = 0
|
|
|
|
await Promise.all(
|
|
map(vms, id =>
|
|
_call('vm.start', { id }).catch(reason => {
|
|
if (forbiddenOperation.is(reason)) {
|
|
forbiddenStart.push(id)
|
|
} else {
|
|
nErrors++
|
|
}
|
|
})
|
|
)
|
|
)
|
|
|
|
if (forbiddenStart.length === 0) {
|
|
if (nErrors === 0) {
|
|
return
|
|
}
|
|
|
|
return error(
|
|
_('failedVmsErrorTitle'),
|
|
_('failedVmsErrorMessage', { nVms: nErrors })
|
|
)
|
|
}
|
|
|
|
const choice = await chooseActionToUnblockForbiddenStartVm({
|
|
body: _('blockedStartVmsModalMessage', { nVms: forbiddenStart.length }),
|
|
title: _('forceStartVmModalTitle'),
|
|
}).catch(noop)
|
|
|
|
if (nErrors !== 0) {
|
|
error(
|
|
_('failedVmsErrorTitle'),
|
|
_('failedVmsErrorMessage', { nVms: nErrors })
|
|
)
|
|
}
|
|
|
|
if (choice === 'clone') {
|
|
return Promise.all(
|
|
map(forbiddenStart, async id =>
|
|
cloneAndStartVM(getObject(store.getState(), id))
|
|
)
|
|
)
|
|
}
|
|
|
|
if (choice === 'force') {
|
|
return Promise.all(
|
|
map(forbiddenStart, id => _call('vm.start', { id, force: true }))
|
|
)
|
|
}
|
|
}, noop)
|
|
|
|
export const stopVm = (vm, force = false) =>
|
|
confirm({
|
|
title: _('stopVmModalTitle'),
|
|
body: _('stopVmModalMessage', { name: vm.name_label }),
|
|
}).then(() => _call('vm.stop', { id: resolveId(vm), force }), noop)
|
|
|
|
export const stopVms = (vms, force = false) =>
|
|
confirm({
|
|
title: _('stopVmsModalTitle', { vms: vms.length }),
|
|
body: _('stopVmsModalMessage', { vms: vms.length }),
|
|
}).then(
|
|
() => map(vms, vm => _call('vm.stop', { id: resolveId(vm), force })),
|
|
noop
|
|
)
|
|
|
|
export const suspendVm = vm => _call('vm.suspend', { id: resolveId(vm) })
|
|
|
|
export const suspendVms = vms =>
|
|
confirm({
|
|
title: _('suspendVmsModalTitle', { nVms: vms.length }),
|
|
body: _('suspendVmsModalMessage', { nVms: vms.length }),
|
|
}).then(
|
|
() =>
|
|
Promise.all(map(vms, vm => _call('vm.suspend', { id: resolveId(vm) }))),
|
|
noop
|
|
)
|
|
|
|
export const resumeVm = vm => _call('vm.resume', { id: resolveId(vm) })
|
|
|
|
export const recoveryStartVm = vm =>
|
|
_call('vm.recoveryStart', { id: resolveId(vm) })
|
|
|
|
export const restartVm = (vm, force = false) =>
|
|
confirm({
|
|
title: _('restartVmModalTitle'),
|
|
body: _('restartVmModalMessage', { name: vm.name_label }),
|
|
}).then(() => _call('vm.restart', { id: resolveId(vm), force }), noop)
|
|
|
|
export const restartVms = (vms, force = false) =>
|
|
confirm({
|
|
title: _('restartVmsModalTitle', { vms: vms.length }),
|
|
body: _('restartVmsModalMessage', { vms: vms.length }),
|
|
}).then(
|
|
() =>
|
|
Promise.all(
|
|
map(vms, vmId => _call('vm.restart', { id: resolveId(vmId), force }))
|
|
),
|
|
noop
|
|
)
|
|
|
|
export const cloneVm = ({ id, name_label: nameLabel }, fullCopy = false) =>
|
|
_call('vm.clone', {
|
|
id,
|
|
name: `${nameLabel}_clone`,
|
|
full_copy: fullCopy,
|
|
})
|
|
|
|
import CopyVmModalBody from './copy-vm-modal' // eslint-disable-line import/first
|
|
export const copyVm = (vm, sr, name, compress) => {
|
|
const vmId = resolveId(vm)
|
|
return sr !== undefined
|
|
? confirm({
|
|
title: _('copyVm'),
|
|
body: _('copyVmConfirm', { SR: sr.name_label }),
|
|
}).then(() =>
|
|
_call('vm.copy', {
|
|
vm: vmId,
|
|
sr: sr.id,
|
|
name: name || vm.name_label + '_COPY',
|
|
compress,
|
|
})
|
|
)
|
|
: confirm({
|
|
title: _('copyVm'),
|
|
body: <CopyVmModalBody vm={vm} />,
|
|
}).then(params => {
|
|
if (!params.sr) {
|
|
error('copyVmsNoTargetSr', 'copyVmsNoTargetSrMessage')
|
|
return
|
|
}
|
|
return _call('vm.copy', { vm: vmId, ...params })
|
|
}, noop)
|
|
}
|
|
|
|
import CopyVmsModalBody from './copy-vms-modal' // eslint-disable-line import/first
|
|
export const copyVms = vms => {
|
|
const _vms = resolveIds(vms)
|
|
return confirm({
|
|
title: _('copyVm'),
|
|
body: <CopyVmsModalBody vms={_vms} />,
|
|
}).then(({ compress, names, sr }) => {
|
|
if (sr !== undefined) {
|
|
return Promise.all(
|
|
map(_vms, (vm, index) =>
|
|
_call('vm.copy', { vm, sr, compress, name: names[index] })
|
|
)
|
|
)
|
|
}
|
|
error(_('copyVmsNoTargetSr'), _('copyVmsNoTargetSrMessage'))
|
|
}, noop)
|
|
}
|
|
|
|
export const convertVmToTemplate = vm =>
|
|
confirm({
|
|
title: 'Convert to template',
|
|
body: (
|
|
<div>
|
|
<p>Are you sure you want to convert this VM into a template?</p>
|
|
<p>This operation is definitive.</p>
|
|
</div>
|
|
),
|
|
}).then(() => _call('vm.convert', { id: resolveId(vm) }), noop)
|
|
|
|
export const deleteTemplates = templates =>
|
|
confirm({
|
|
title: _('templateDeleteModalTitle', { templates: templates.length }),
|
|
body: _('templateDeleteModalBody', { templates: templates.length }),
|
|
}).then(async () => {
|
|
const defaultTemplates = []
|
|
let nErrors = 0
|
|
await Promise.all(
|
|
map(resolveIds(templates), id =>
|
|
_call('vm.delete', { id }).catch(reason => {
|
|
if (vmIsTemplate.is(reason)) {
|
|
defaultTemplates.push(id)
|
|
} else {
|
|
nErrors++
|
|
}
|
|
})
|
|
)
|
|
)
|
|
|
|
const nDefaultTemplates = defaultTemplates.length
|
|
if (nDefaultTemplates === 0 && nErrors === 0) {
|
|
return
|
|
}
|
|
|
|
const showError = () =>
|
|
error(
|
|
_('failedToDeleteTemplatesTitle', { nTemplates: nErrors }),
|
|
_('failedToDeleteTemplatesMessage', { nTemplates: nErrors })
|
|
)
|
|
|
|
return nDefaultTemplates === 0
|
|
? showError()
|
|
: confirm({
|
|
title: _('deleteDefaultTemplatesTitle', { nDefaultTemplates }),
|
|
body: _('deleteDefaultTemplatesMessage', { nDefaultTemplates }),
|
|
})
|
|
.then(
|
|
() =>
|
|
Promise.all(
|
|
map(defaultTemplates, id =>
|
|
_call('vm.delete', {
|
|
id,
|
|
forceDeleteDefaultTemplate: true,
|
|
}).catch(() => {
|
|
nErrors++
|
|
})
|
|
)
|
|
),
|
|
noop
|
|
)
|
|
.then(() => {
|
|
if (nErrors !== 0) {
|
|
showError()
|
|
}
|
|
}, noop)
|
|
}, noop)
|
|
|
|
export const snapshotVm = vm => _call('vm.snapshot', { id: resolveId(vm) })
|
|
|
|
export const snapshotVms = vms =>
|
|
confirm({
|
|
title: _('snapshotVmsModalTitle', { vms: vms.length }),
|
|
body: _('snapshotVmsModalMessage', { vms: vms.length }),
|
|
}).then(() => map(vms, vmId => snapshotVm({ id: vmId })), noop)
|
|
|
|
export const deleteSnapshot = vm =>
|
|
confirm({
|
|
title: _('deleteSnapshotModalTitle'),
|
|
body: _('deleteSnapshotModalMessage'),
|
|
}).then(() => _call('vm.delete', { id: resolveId(vm) }), noop)
|
|
|
|
export const deleteSnapshots = vms =>
|
|
confirm({
|
|
title: _('deleteSnapshotsModalTitle', { nVms: vms.length }),
|
|
body: _('deleteSnapshotsModalMessage', { nVms: vms.length }),
|
|
}).then(
|
|
() =>
|
|
Promise.all(map(vms, vm => _call('vm.delete', { id: resolveId(vm) }))),
|
|
noop
|
|
)
|
|
|
|
import MigrateVmModalBody from './migrate-vm-modal' // eslint-disable-line import/first
|
|
export const migrateVm = (vm, host) =>
|
|
confirm({
|
|
title: _('migrateVmModalTitle'),
|
|
body: <MigrateVmModalBody vm={vm} host={host} />,
|
|
}).then(params => {
|
|
if (!params.targetHost) {
|
|
return error(
|
|
_('migrateVmNoTargetHost'),
|
|
_('migrateVmNoTargetHostMessage')
|
|
)
|
|
}
|
|
return _call('vm.migrate', { vm: vm.id, ...params })
|
|
}, noop)
|
|
|
|
import MigrateVmsModalBody from './migrate-vms-modal' // eslint-disable-line import/first
|
|
export const migrateVms = vms =>
|
|
confirm({
|
|
title: _('migrateVmModalTitle'),
|
|
body: <MigrateVmsModalBody vms={resolveIds(vms)} />,
|
|
}).then(params => {
|
|
if (isEmpty(params.vms)) {
|
|
return
|
|
}
|
|
if (!params.targetHost) {
|
|
return error(
|
|
_('migrateVmNoTargetHost'),
|
|
_('migrateVmNoTargetHostMessage')
|
|
)
|
|
}
|
|
|
|
const {
|
|
mapVmsMapVdisSrs,
|
|
mapVmsMapVifsNetworks,
|
|
mapVmsMigrationNetwork,
|
|
targetHost,
|
|
vms,
|
|
} = params
|
|
Promise.all(
|
|
map(vms, ({ id }) =>
|
|
_call('vm.migrate', {
|
|
mapVdisSrs: mapVmsMapVdisSrs[id],
|
|
mapVifsNetworks: mapVmsMapVifsNetworks[id],
|
|
migrationNetwork: mapVmsMigrationNetwork[id],
|
|
targetHost,
|
|
vm: id,
|
|
})
|
|
)
|
|
)
|
|
}, noop)
|
|
|
|
export const createVm = args => _call('vm.create', args)
|
|
|
|
export const createVms = (args, nameLabels, cloudConfigs) =>
|
|
confirm({
|
|
title: _('newVmCreateVms'),
|
|
body: _('newVmCreateVmsConfirm', { nbVms: nameLabels.length }),
|
|
}).then(
|
|
() =>
|
|
Promise.all(
|
|
map(nameLabels, (
|
|
name_label, // eslint-disable-line camelcase
|
|
i
|
|
) =>
|
|
_call('vm.create', {
|
|
...args,
|
|
name_label,
|
|
cloudConfig: get(cloudConfigs, i),
|
|
})
|
|
)
|
|
),
|
|
noop
|
|
)
|
|
|
|
export const getCloudInitConfig = template =>
|
|
_call('vm.getCloudInitConfig', { template })
|
|
|
|
export const deleteVm = (vm, retryWithForce = true) =>
|
|
confirm({
|
|
title: _('deleteVmModalTitle'),
|
|
body: _('deleteVmModalMessage'),
|
|
})
|
|
.then(() => _call('vm.delete', { id: resolveId(vm) }), noop)
|
|
.catch(error => {
|
|
if (forbiddenOperation.is(error) || !retryWithForce) {
|
|
throw error
|
|
}
|
|
|
|
return confirm({
|
|
title: _('deleteVmBlockedModalTitle'),
|
|
body: _('deleteVmBlockedModalMessage'),
|
|
}).then(
|
|
() => _call('vm.delete', { id: resolveId(vm), force: true }),
|
|
noop
|
|
)
|
|
})
|
|
|
|
export const deleteVms = vms =>
|
|
confirm({
|
|
title: _('deleteVmsModalTitle', { vms: vms.length }),
|
|
body: _('deleteVmsModalMessage', { vms: vms.length }),
|
|
strongConfirm: vms.length > 1 && {
|
|
messageId: 'deleteVmsConfirmText',
|
|
values: { nVms: vms.length },
|
|
},
|
|
}).then(
|
|
() =>
|
|
Promise.all(
|
|
map(vms, vmId => _call('vm.delete', { id: resolveId(vmId) }))
|
|
),
|
|
noop
|
|
)
|
|
|
|
export const importBackup = ({ remote, file, sr }) =>
|
|
_call('vm.importBackup', resolveIds({ remote, file, sr }))
|
|
|
|
export const importDeltaBackup = ({ remote, file, sr, mapVdisSrs }) =>
|
|
_call(
|
|
'vm.importDeltaBackup',
|
|
resolveIds({
|
|
remote,
|
|
filePath: file,
|
|
sr,
|
|
mapVdisSrs: resolveIds(mapVdisSrs),
|
|
})
|
|
)
|
|
|
|
import RevertSnapshotModalBody from './revert-snapshot-modal' // eslint-disable-line import/first
|
|
export const revertSnapshot = snapshot =>
|
|
confirm({
|
|
title: _('revertVmModalTitle'),
|
|
body: <RevertSnapshotModalBody />,
|
|
}).then(
|
|
snapshotBefore =>
|
|
_call('vm.revert', { snapshot: resolveId(snapshot), snapshotBefore }),
|
|
noop
|
|
)
|
|
|
|
export const editVm = (vm, props) =>
|
|
_call('vm.set', { ...props, id: resolveId(vm) }).catch(err => {
|
|
error(
|
|
_('setVmFailed', { vm: renderXoItemFromId(resolveId(vm)) }),
|
|
err.message
|
|
)
|
|
})
|
|
|
|
export const fetchVmStats = (vm, granularity) =>
|
|
_call('vm.stats', { id: resolveId(vm), granularity })
|
|
|
|
export const getVmsHaValues = () => _call('vm.getHaValues')
|
|
|
|
export const importVm = (file, type = 'xva', data = undefined, sr) => {
|
|
const { name } = file
|
|
|
|
info(_('startVmImport'), name)
|
|
|
|
return _call('vm.import', { type, data, sr: resolveId(sr) }).then(
|
|
({ $sendTo }) =>
|
|
post($sendTo, file)
|
|
.then(res => {
|
|
if (res.status !== 200) {
|
|
throw res.status
|
|
}
|
|
success(_('vmImportSuccess'), name)
|
|
return res.json().then(body => body.result)
|
|
})
|
|
.catch(() => {
|
|
error(_('vmImportFailed'), name)
|
|
})
|
|
)
|
|
}
|
|
|
|
export const importVms = (vms, sr) =>
|
|
Promise.all(
|
|
map(vms, ({ file, type, data }) =>
|
|
importVm(file, type, data, sr).catch(noop)
|
|
)
|
|
)
|
|
|
|
export const exportVm = vm => {
|
|
info(_('startVmExport'), vm.id)
|
|
return _call('vm.export', { vm: resolveId(vm) }).then(({ $getFrom: url }) => {
|
|
window.location = `.${url}`
|
|
})
|
|
}
|
|
|
|
export const insertCd = (vm, cd, force = false) =>
|
|
_call('vm.insertCd', {
|
|
id: resolveId(vm),
|
|
cd_id: resolveId(cd),
|
|
force,
|
|
})
|
|
|
|
export const ejectCd = vm => _call('vm.ejectCd', { id: resolveId(vm) })
|
|
|
|
export const setVmBootOrder = (vm, order) =>
|
|
_call('vm.setBootOrder', {
|
|
vm: resolveId(vm),
|
|
order,
|
|
})
|
|
|
|
export const attachDiskToVm = (vdi, vm, { bootable, mode, position }) =>
|
|
_call('vm.attachDisk', {
|
|
bootable,
|
|
mode,
|
|
position: (position && String(position)) || undefined,
|
|
vdi: resolveId(vdi),
|
|
vm: resolveId(vm),
|
|
})
|
|
|
|
export const createVgpu = (vm, { gpuGroup, vgpuType }) =>
|
|
_call('vm.createVgpu', resolveIds({ vm, gpuGroup, vgpuType }))
|
|
|
|
export const deleteVgpu = vgpu => _call('vm.deleteVgpu', resolveIds({ vgpu }))
|
|
|
|
export const shareVm = async (vm, resourceSet) =>
|
|
confirm({
|
|
title: _('shareVmInResourceSetModalTitle'),
|
|
body: _('shareVmInResourceSetModalMessage', {
|
|
self: renderXoItem({
|
|
...(await getResourceSet(resourceSet)),
|
|
type: 'resourceSet',
|
|
}),
|
|
}),
|
|
}).then(() => editVm(vm, { share: true }), noop)
|
|
|
|
// DISK ---------------------------------------------------------------
|
|
|
|
export const createDisk = (name, size, sr, { vm, bootable, mode, position }) =>
|
|
_call('disk.create', {
|
|
bootable,
|
|
mode,
|
|
name,
|
|
position,
|
|
size,
|
|
sr: resolveId(sr),
|
|
vm: resolveId(vm),
|
|
})
|
|
|
|
// VDI ---------------------------------------------------------------
|
|
|
|
export const editVdi = (vdi, props) =>
|
|
_call('vdi.set', { ...props, id: resolveId(vdi) })
|
|
|
|
export const deleteVdi = vdi =>
|
|
confirm({
|
|
title: _('deleteVdiModalTitle'),
|
|
body: _('deleteVdiModalMessage'),
|
|
}).then(() => _call('vdi.delete', { id: resolveId(vdi) }), noop)
|
|
|
|
export const deleteVdis = vdis =>
|
|
confirm({
|
|
title: _('deleteVdisModalTitle', { nVdis: vdis.length }),
|
|
body: _('deleteVdisModalMessage', { nVdis: vdis.length }),
|
|
}).then(
|
|
() =>
|
|
Promise.all(
|
|
map(vdis, vdi => _call('vdi.delete', { id: resolveId(vdi) }))
|
|
),
|
|
noop
|
|
)
|
|
|
|
export const deleteOrphanedVdis = vdis =>
|
|
confirm({
|
|
title: _('removeAllOrphanedObject'),
|
|
body: (
|
|
<div>
|
|
<p>{_('removeAllOrphanedModalWarning')}</p>
|
|
<p>{_('definitiveMessageModal')}</p>
|
|
</div>
|
|
),
|
|
}).then(
|
|
() => Promise.all(map(resolveIds(vdis), id => _call('vdi.delete', { id }))),
|
|
noop
|
|
)
|
|
|
|
export const migrateVdi = (vdi, sr) =>
|
|
_call('vdi.migrate', { id: resolveId(vdi), sr_id: resolveId(sr) })
|
|
|
|
// VBD ---------------------------------------------------------------
|
|
|
|
export const connectVbd = vbd => _call('vbd.connect', { id: resolveId(vbd) })
|
|
|
|
export const disconnectVbd = vbd =>
|
|
_call('vbd.disconnect', { id: resolveId(vbd) })
|
|
|
|
export const disconnectVbds = vbds =>
|
|
confirm({
|
|
title: _('disconnectVbdsModalTitle', { nVbds: vbds.length }),
|
|
body: _('disconnectVbdsModalMessage', { nVbds: vbds.length }),
|
|
}).then(
|
|
() =>
|
|
Promise.all(
|
|
map(vbds, vbd => _call('vbd.disconnect', { id: resolveId(vbd) }))
|
|
),
|
|
noop
|
|
)
|
|
|
|
export const deleteVbd = vbd => _call('vbd.delete', { id: resolveId(vbd) })
|
|
|
|
export const deleteVbds = vbds =>
|
|
confirm({
|
|
title: _('deleteVbdsModalTitle', { nVbds: vbds.length }),
|
|
body: _('deleteVbdsModalMessage', { nVbds: vbds.length }),
|
|
}).then(
|
|
() =>
|
|
Promise.all(
|
|
map(vbds, vbd => _call('vbd.delete', { id: resolveId(vbd) }))
|
|
),
|
|
noop
|
|
)
|
|
|
|
export const editVbd = (vbd, props) =>
|
|
_call('vbd.set', { ...props, id: resolveId(vbd) })
|
|
|
|
export const setBootableVbd = (vbd, bootable) =>
|
|
_call('vbd.setBootable', { vbd: resolveId(vbd), bootable })
|
|
|
|
// VIF ---------------------------------------------------------------
|
|
|
|
export const createVmInterface = (vm, network, mac) =>
|
|
_call('vm.createInterface', resolveIds({ vm, network, mac }))
|
|
|
|
export const connectVif = vif => _call('vif.connect', { id: resolveId(vif) })
|
|
|
|
export const disconnectVif = vif =>
|
|
_call('vif.disconnect', { id: resolveId(vif) })
|
|
|
|
export const deleteVif = vif => _call('vif.delete', { id: resolveId(vif) })
|
|
|
|
export const deleteVifs = vifs =>
|
|
confirm({
|
|
title: _('deleteVifsModalTitle', { nVifs: vifs.length }),
|
|
body: _('deleteVifsModalMessage', { nVifs: vifs.length }),
|
|
}).then(
|
|
() => map(vifs, vif => _call('vif.delete', { id: resolveId(vif) })),
|
|
noop
|
|
)
|
|
|
|
export const setVif = (
|
|
vif,
|
|
{ network, mac, allowedIpv4Addresses, allowedIpv6Addresses }
|
|
) =>
|
|
_call('vif.set', {
|
|
id: resolveId(vif),
|
|
network: resolveId(network),
|
|
mac,
|
|
allowedIpv4Addresses,
|
|
allowedIpv6Addresses,
|
|
})
|
|
|
|
// Network -----------------------------------------------------------
|
|
|
|
export const editNetwork = (network, props) =>
|
|
_call('network.set', { ...props, id: resolveId(network) })
|
|
|
|
import CreateNetworkModalBody from './create-network-modal' // eslint-disable-line import/first
|
|
export const createNetwork = container =>
|
|
confirm({
|
|
icon: 'network',
|
|
title: _('newNetworkCreate'),
|
|
body: <CreateNetworkModalBody container={container} />,
|
|
}).then(params => {
|
|
if (!params.name) {
|
|
return error(
|
|
_('newNetworkNoNameErrorTitle'),
|
|
_('newNetworkNoNameErrorMessage')
|
|
)
|
|
}
|
|
return _call('network.create', params)
|
|
}, noop)
|
|
|
|
export const getBondModes = () => _call('network.getBondModes')
|
|
|
|
import CreateBondedNetworkModalBody from './create-bonded-network-modal' // eslint-disable-line import/first
|
|
export const createBondedNetwork = container =>
|
|
confirm({
|
|
icon: 'network',
|
|
title: _('newBondedNetworkCreate'),
|
|
body: <CreateBondedNetworkModalBody pool={container.$pool} />,
|
|
}).then(params => {
|
|
if (!params.name) {
|
|
return error(
|
|
_('newNetworkNoNameErrorTitle'),
|
|
_('newNetworkNoNameErrorMessage')
|
|
)
|
|
}
|
|
return _call('network.createBonded', params)
|
|
}, noop)
|
|
|
|
export const deleteNetwork = network =>
|
|
confirm({
|
|
title: _('deleteNetwork'),
|
|
body: _('deleteNetworkConfirm'),
|
|
}).then(() => _call('network.delete', { network: resolveId(network) }), noop)
|
|
|
|
// PIF ---------------------------------------------------------------
|
|
|
|
export const connectPif = pif =>
|
|
confirm({
|
|
title: _('connectPif'),
|
|
body: _('connectPifConfirm'),
|
|
}).then(() => _call('pif.connect', { pif: resolveId(pif) }), noop)
|
|
|
|
export const disconnectPif = pif =>
|
|
confirm({
|
|
title: _('disconnectPif'),
|
|
body: _('disconnectPifConfirm'),
|
|
}).then(() => _call('pif.disconnect', { pif: resolveId(pif) }), noop)
|
|
|
|
export const deletePif = pif =>
|
|
confirm({
|
|
title: _('deletePif'),
|
|
body: _('deletePifConfirm'),
|
|
}).then(() => _call('pif.delete', { pif: resolveId(pif) }), noop)
|
|
|
|
export const deletePifs = pifs =>
|
|
confirm({
|
|
title: _('deletePifs'),
|
|
body: _('deletePifsConfirm', { nPifs: pifs.length }),
|
|
}).then(
|
|
() =>
|
|
Promise.all(
|
|
map(pifs, pif => _call('pif.delete', { pif: resolveId(pif) }))
|
|
),
|
|
noop
|
|
)
|
|
|
|
export const reconfigurePifIp = (pif, { mode, ip, netmask, gateway, dns }) =>
|
|
_call('pif.reconfigureIp', {
|
|
pif: resolveId(pif),
|
|
mode,
|
|
ip,
|
|
netmask,
|
|
gateway,
|
|
dns,
|
|
})
|
|
|
|
export const getIpv4ConfigModes = () => _call('pif.getIpv4ConfigurationModes')
|
|
|
|
export const editPif = (pif, { vlan }) =>
|
|
_call('pif.editPif', { pif: resolveId(pif), vlan })
|
|
|
|
// SR ----------------------------------------------------------------
|
|
|
|
export const deleteSr = sr =>
|
|
confirm({
|
|
title: 'Delete SR',
|
|
body: (
|
|
<div>
|
|
<p>Are you sure you want to remove this SR?</p>
|
|
<p>This operation is definitive, and ALL DISKS WILL BE LOST FOREVER.</p>
|
|
</div>
|
|
),
|
|
}).then(() => _call('sr.destroy', { id: resolveId(sr) }), noop)
|
|
|
|
export const fetchSrStats = (sr, granularity) =>
|
|
_call('sr.stats', { id: resolveId(sr), granularity })
|
|
|
|
export const forgetSr = sr =>
|
|
confirm({
|
|
title: _('srForgetModalTitle'),
|
|
body: _('srForgetModalMessage'),
|
|
}).then(() => _call('sr.forget', { id: resolveId(sr) }), noop)
|
|
export const forgetSrs = srs =>
|
|
confirm({
|
|
title: _('srsForgetModalTitle'),
|
|
body: _('srsForgetModalMessage'),
|
|
}).then(
|
|
() => Promise.all(map(resolveIds(srs), id => _call('sr.forget', { id }))),
|
|
noop
|
|
)
|
|
|
|
export const reconnectAllHostsSr = sr =>
|
|
confirm({
|
|
title: _('srReconnectAllModalTitle'),
|
|
body: _('srReconnectAllModalMessage'),
|
|
}).then(() => _call('sr.connectAllPbds', { id: resolveId(sr) }), noop)
|
|
export const reconnectAllHostsSrs = srs =>
|
|
confirm({
|
|
title: _('srReconnectAllModalTitle'),
|
|
body: _('srReconnectAllModalMessage'),
|
|
}).then(
|
|
() =>
|
|
Promise.all(resolveIds(srs), id => _call('sr.connectAllPbds', { id })),
|
|
noop
|
|
)
|
|
|
|
export const disconnectAllHostsSr = sr =>
|
|
confirm({
|
|
title: _('srDisconnectAllModalTitle'),
|
|
body: _('srDisconnectAllModalMessage'),
|
|
}).then(() => _call('sr.disconnectAllPbds', { id: resolveId(sr) }), noop)
|
|
export const disconnectAllHostsSrs = srs =>
|
|
confirm({
|
|
title: _('srDisconnectAllModalTitle'),
|
|
body: _('srsDisconnectAllModalMessage'),
|
|
}).then(
|
|
() =>
|
|
Promise.all(resolveIds(srs), id => _call('sr.disconnectAllPbds', { id })),
|
|
noop
|
|
)
|
|
|
|
export const editSr = (sr, { nameDescription, nameLabel }) =>
|
|
_call('sr.set', {
|
|
id: resolveId(sr),
|
|
name_description: nameDescription,
|
|
name_label: nameLabel,
|
|
})
|
|
|
|
export const rescanSr = sr => _call('sr.scan', { id: resolveId(sr) })
|
|
export const rescanSrs = srs =>
|
|
Promise.all(map(resolveIds(srs), id => _call('sr.scan', { id })))
|
|
|
|
// PBDs --------------------------------------------------------------
|
|
|
|
export const connectPbd = pbd => _call('pbd.connect', { id: resolveId(pbd) })
|
|
|
|
export const disconnectPbd = pbd =>
|
|
_call('pbd.disconnect', { id: resolveId(pbd) })
|
|
|
|
export const deletePbd = pbd => _call('pbd.delete', { id: resolveId(pbd) })
|
|
|
|
// Messages ----------------------------------------------------------
|
|
|
|
export const deleteMessage = message =>
|
|
_call('message.delete', { id: resolveId(message) })
|
|
|
|
export const deleteMessages = logs =>
|
|
confirm({
|
|
title: _('logDeleteMultiple', { nLogs: logs.length }),
|
|
body: _('logDeleteMultipleMessage', { nLogs: logs.length }),
|
|
}).then(() => Promise.all(map(logs, deleteMessage)), noop)
|
|
|
|
// Tags --------------------------------------------------------------
|
|
|
|
export const addTag = (object, tag) =>
|
|
_call('tag.add', { id: resolveId(object), tag })
|
|
|
|
export const removeTag = (object, tag) =>
|
|
_call('tag.remove', { id: resolveId(object), tag })
|
|
|
|
// Tasks --------------------------------------------------------------
|
|
|
|
export const cancelTask = task => _call('task.cancel', { id: resolveId(task) })
|
|
|
|
export const cancelTasks = tasks =>
|
|
confirm({
|
|
title: _('cancelTasksModalTitle', { nTasks: tasks.length }),
|
|
body: _('cancelTasksModalMessage', { nTasks: tasks.length }),
|
|
}).then(
|
|
() =>
|
|
Promise.all(
|
|
map(tasks, task => _call('task.cancel', { id: resolveId(task) }))
|
|
),
|
|
noop
|
|
)
|
|
|
|
export const destroyTask = task =>
|
|
_call('task.destroy', { id: resolveId(task) })
|
|
|
|
export const destroyTasks = tasks =>
|
|
confirm({
|
|
title: _('destroyTasksModalTitle', { nTasks: tasks.length }),
|
|
body: _('destroyTasksModalMessage', { nTasks: tasks.length }),
|
|
}).then(
|
|
() =>
|
|
Promise.all(
|
|
map(tasks, task => _call('task.destroy', { id: resolveId(task) }))
|
|
),
|
|
noop
|
|
)
|
|
|
|
// Jobs -------------------------------------------------------------
|
|
|
|
export const createJob = job =>
|
|
_call('job.create', { job })::tap(subscribeJobs.forceRefresh)
|
|
|
|
export const deleteJob = job =>
|
|
_call('job.delete', { id: resolveId(job) })::tap(subscribeJobs.forceRefresh)
|
|
|
|
export const deleteJobs = jobs =>
|
|
confirm({
|
|
title: _('deleteJobsModalTitle', { nJobs: jobs.length }),
|
|
body: _('deleteJobsModalMessage', { nJobs: jobs.length }),
|
|
}).then(
|
|
() =>
|
|
Promise.all(
|
|
map(jobs, job => _call('job.delete', { id: resolveId(job) }))
|
|
)::tap(subscribeJobs.forceRefresh),
|
|
noop
|
|
)
|
|
|
|
export const editJob = job =>
|
|
_call('job.set', { job })::tap(subscribeJobs.forceRefresh)
|
|
|
|
export const getJob = id => _call('job.get', { id })
|
|
|
|
export const runJob = job => {
|
|
info(_('runJob'), _('runJobVerbose'))
|
|
return _call('job.runSequence', { idSequence: [resolveId(job)] })
|
|
}
|
|
|
|
export const cancelJob = ({ id, name, runId }) =>
|
|
confirm({
|
|
title: _('cancelJob'),
|
|
body: _('cancelJobConfirm', {
|
|
id: id.slice(0, 5),
|
|
name: <strong>{name}</strong>,
|
|
}),
|
|
}).then(() => _call('job.cancel', { runId }))
|
|
|
|
// Backup/Schedule ---------------------------------------------------------
|
|
|
|
export const createSchedule = (
|
|
jobId,
|
|
{ cron, enabled, name = undefined, timezone = undefined }
|
|
) =>
|
|
_call('schedule.create', { jobId, cron, enabled, name, timezone })::tap(
|
|
subscribeSchedules.forceRefresh
|
|
)
|
|
|
|
export const deleteBackupSchedule = async schedule => {
|
|
await confirm({
|
|
title: _('deleteBackupSchedule'),
|
|
body: _('deleteBackupScheduleQuestion'),
|
|
})
|
|
await _call('schedule.delete', { id: schedule.id })
|
|
await _call('job.delete', { id: schedule.jobId })
|
|
|
|
subscribeSchedules.forceRefresh()
|
|
subscribeJobs.forceRefresh()
|
|
}
|
|
|
|
export const migrateBackupSchedule = id =>
|
|
confirm({
|
|
title: _('migrateBackupSchedule'),
|
|
body: _('migrateBackupScheduleMessage'),
|
|
}).then(() => _call('backupNg.migrateLegacyJob', { id: resolveId(id) }))
|
|
|
|
export const deleteSchedule = schedule =>
|
|
_call('schedule.delete', { id: resolveId(schedule) })::tap(
|
|
subscribeSchedules.forceRefresh
|
|
)
|
|
|
|
export const deleteSchedules = schedules =>
|
|
confirm({
|
|
title: _('deleteSchedulesModalTitle', { nSchedules: schedules.length }),
|
|
body: _('deleteSchedulesModalMessage', { nSchedules: schedules.length }),
|
|
}).then(() =>
|
|
map(schedules, schedule =>
|
|
_call('schedule.delete', { id: resolveId(schedule) })::tap(
|
|
subscribeSchedules.forceRefresh
|
|
)
|
|
)
|
|
)
|
|
|
|
export const disableSchedule = id => editSchedule({ id, enabled: false })
|
|
|
|
export const editSchedule = ({ id, jobId, cron, enabled, name, timezone }) =>
|
|
_call('schedule.set', { id, jobId, cron, enabled, name, timezone })::tap(
|
|
subscribeSchedules.forceRefresh
|
|
)
|
|
|
|
export const enableSchedule = id => editSchedule({ id, enabled: true })
|
|
|
|
export const getSchedule = id => _call('schedule.get', { id })
|
|
|
|
// Backup NG ---------------------------------------------------------
|
|
|
|
export const subscribeBackupNgJobs = createSubscription(() =>
|
|
_call('backupNg.getAllJobs')
|
|
)
|
|
|
|
export const subscribeBackupNgLogs = createSubscription(() =>
|
|
_call('backupNg.getAllLogs')
|
|
)
|
|
|
|
export const createBackupNgJob = props =>
|
|
_call('backupNg.createJob', props)::tap(subscribeBackupNgJobs.forceRefresh)
|
|
|
|
export const deleteBackupNgJobs = async ids => {
|
|
const { length } = ids
|
|
if (length === 0) {
|
|
return
|
|
}
|
|
const vars = { nJobs: length }
|
|
try {
|
|
await confirm({
|
|
title: _('confirmDeleteBackupJobsTitle', vars),
|
|
body: <p>{_('confirmDeleteBackupJobsBody', vars)}</p>,
|
|
})
|
|
} catch (_) {
|
|
return
|
|
}
|
|
|
|
return Promise.all(
|
|
ids.map(id => _call('backupNg.deleteJob', { id: resolveId(id) }))
|
|
)::tap(subscribeBackupNgJobs.forceRefresh)
|
|
}
|
|
|
|
export const editBackupNgJob = props =>
|
|
_call('backupNg.editJob', props)::tap(subscribeBackupNgJobs.forceRefresh)
|
|
|
|
export const getBackupNgJob = id => _call('backupNg.getJob', { id })
|
|
|
|
export const runBackupNgJob = params => _call('backupNg.runJob', params)
|
|
|
|
export const listVmBackups = remotes =>
|
|
_call('backupNg.listVmBackups', { remotes: resolveIds(remotes) })
|
|
|
|
export const restoreBackup = (backup, sr, startOnRestore) => {
|
|
const promise = _call('backupNg.importVmBackup', {
|
|
id: resolveId(backup),
|
|
sr: resolveId(sr),
|
|
})
|
|
|
|
if (startOnRestore) {
|
|
return promise.then(startVm)
|
|
}
|
|
|
|
return promise
|
|
}
|
|
|
|
export const deleteBackup = backup =>
|
|
_call('backupNg.deleteVmBackup', { id: resolveId(backup) })
|
|
|
|
export const deleteBackups = async backups => {
|
|
// delete sequentially from newest to oldest
|
|
backups = backups.slice().sort((b1, b2) => b2.timestamp - b1.timestamp)
|
|
for (let i = 0, n = backups.length; i < n; ++i) {
|
|
await deleteBackup(backups[i])
|
|
}
|
|
}
|
|
|
|
// Plugins -----------------------------------------------------------
|
|
|
|
export const loadPlugin = async id =>
|
|
_call('plugin.load', { id })::tap(subscribePlugins.forceRefresh, err =>
|
|
error(_('pluginError'), (err && err.message) || _('unknownPluginError'))
|
|
)
|
|
|
|
export const unloadPlugin = id =>
|
|
_call('plugin.unload', { id })::tap(subscribePlugins.forceRefresh, err =>
|
|
error(_('pluginError'), (err && err.message) || _('unknownPluginError'))
|
|
)
|
|
|
|
export const enablePluginAutoload = id =>
|
|
_call('plugin.enableAutoload', { id })::tap(subscribePlugins.forceRefresh)
|
|
|
|
export const disablePluginAutoload = id =>
|
|
_call('plugin.disableAutoload', { id })::tap(subscribePlugins.forceRefresh)
|
|
|
|
export const configurePlugin = (id, configuration) =>
|
|
_call('plugin.configure', { id, configuration })::tap(
|
|
() => {
|
|
info(_('pluginConfigurationSuccess'), _('pluginConfigurationChanges'))
|
|
subscribePlugins.forceRefresh()
|
|
},
|
|
err =>
|
|
error(
|
|
_('pluginError'),
|
|
JSON.stringify(err.data) || _('unknownPluginError')
|
|
)
|
|
)
|
|
|
|
export const purgePluginConfiguration = async id => {
|
|
await confirm({
|
|
title: _('purgePluginConfiguration'),
|
|
body: _('purgePluginConfigurationQuestion'),
|
|
})
|
|
await _call('plugin.purgeConfiguration', { id })
|
|
|
|
subscribePlugins.forceRefresh()
|
|
}
|
|
|
|
export const testPlugin = (id, data) => _call('plugin.test', { id, data })
|
|
|
|
export const sendUsageReport = () => _call('plugin.usageReport.send')
|
|
|
|
// Resource set ------------------------------------------------------
|
|
|
|
export const createResourceSet = (name, { subjects, objects, limits } = {}) =>
|
|
_call('resourceSet.create', { name, subjects, objects, limits })::tap(
|
|
subscribeResourceSets.forceRefresh
|
|
)
|
|
|
|
export const editResourceSet = (
|
|
id,
|
|
{ name, subjects, objects, limits, ipPools } = {}
|
|
) =>
|
|
_call('resourceSet.set', {
|
|
id,
|
|
name,
|
|
subjects,
|
|
objects,
|
|
limits,
|
|
ipPools,
|
|
})::tap(subscribeResourceSets.forceRefresh)
|
|
|
|
export const deleteResourceSet = async id => {
|
|
await confirm({
|
|
title: _('deleteResourceSetWarning'),
|
|
body: _('deleteResourceSetQuestion'),
|
|
})
|
|
await _call('resourceSet.delete', { id: resolveId(id) })
|
|
|
|
subscribeResourceSets.forceRefresh()
|
|
}
|
|
|
|
export const recomputeResourceSetsLimits = () =>
|
|
_call('resourceSet.recomputeAllLimits')
|
|
|
|
export const getResourceSet = id =>
|
|
_call('resourceSet.get', { id: resolveId(id) })
|
|
|
|
// Remote ------------------------------------------------------------
|
|
|
|
export const getRemote = remote =>
|
|
_call('remote.get', resolveIds({ id: remote }))::tap(null, err =>
|
|
error(_('getRemote'), err.message || String(err))
|
|
)
|
|
|
|
export const createRemote = (name, url) =>
|
|
_call('remote.create', { name, url })::tap(subscribeRemotes.forceRefresh)
|
|
|
|
export const deleteRemote = remote =>
|
|
_call('remote.delete', { id: resolveId(remote) })::tap(
|
|
subscribeRemotes.forceRefresh
|
|
)
|
|
|
|
export const deleteRemotes = remotes =>
|
|
confirm({
|
|
title: _('deleteRemotesModalTitle', { nRemotes: remotes.length }),
|
|
body: _('deleteRemotesModalMessage', { nRemotes: remotes.length }),
|
|
}).then(
|
|
() =>
|
|
Promise.all(
|
|
map(remotes, remote =>
|
|
_call('remote.delete', { id: resolveId(remote) })
|
|
)
|
|
)::tap(subscribeRemotes.forceRefresh),
|
|
noop
|
|
)
|
|
|
|
export const enableRemote = remote =>
|
|
_call('remote.set', { id: resolveId(remote), enabled: true })::tap(
|
|
subscribeRemotes.forceRefresh
|
|
)
|
|
|
|
export const disableRemote = remote =>
|
|
_call('remote.set', { id: resolveId(remote), enabled: false })::tap(
|
|
subscribeRemotes.forceRefresh
|
|
)
|
|
|
|
export const editRemote = (remote, { name, url }) =>
|
|
_call('remote.set', resolveIds({ remote, name, url }))::tap(
|
|
subscribeRemotes.forceRefresh
|
|
)
|
|
|
|
export const listRemote = remote =>
|
|
_call('remote.list', resolveIds({ id: remote }))::tap(
|
|
subscribeRemotes.forceRefresh,
|
|
err => error(_('listRemote'), err.message || String(err))
|
|
)
|
|
|
|
export const listRemoteBackups = remote =>
|
|
_call('backup.list', resolveIds({ remote }))::tap(null, err =>
|
|
error(_('listRemote'), err.message || String(err))
|
|
)
|
|
|
|
export const testRemote = remote =>
|
|
_call('remote.test', resolveIds({ id: remote }))::tap(null, err =>
|
|
error(_('testRemote'), err.message || String(err))
|
|
)
|
|
|
|
// File restore ----------------------------------------------------
|
|
|
|
export const scanDisk = (remote, disk) =>
|
|
_call('backup.scanDisk', resolveIds({ remote, disk }))
|
|
|
|
export const scanFiles = (remote, disk, path, partition) =>
|
|
_call('backup.scanFiles', resolveIds({ remote, disk, path, partition }))
|
|
|
|
export const fetchFiles = (remote, disk, partition, paths, format) =>
|
|
_call(
|
|
'backup.fetchFiles',
|
|
resolveIds({ remote, disk, partition, paths, format })
|
|
).then(({ $getFrom: url }) => {
|
|
window.location = `.${url}`
|
|
})
|
|
|
|
// File restore NG ----------------------------------------------------
|
|
|
|
export const listPartitions = (remote, disk) =>
|
|
_call('backupNg.listPartitions', resolveIds({ remote, disk }))
|
|
|
|
export const listFiles = (remote, disk, path, partition) =>
|
|
_call('backupNg.listFiles', resolveIds({ remote, disk, path, partition }))
|
|
|
|
export const fetchFilesNg = (remote, disk, partition, paths, format) =>
|
|
_call(
|
|
'backupNg.fetchFiles',
|
|
resolveIds({ remote, disk, partition, paths, format })
|
|
).then(({ $getFrom: url }) => {
|
|
window.location = `.${url}`
|
|
})
|
|
|
|
// -------------------------------------------------------------------
|
|
|
|
export const probeSrNfs = (host, server) =>
|
|
_call('sr.probeNfs', { host, server })
|
|
|
|
export const probeSrNfsExists = (host, server, serverPath) =>
|
|
_call('sr.probeNfsExists', { host, server, serverPath })
|
|
|
|
export const probeSrIscsiIqns = (
|
|
host,
|
|
target,
|
|
port = undefined,
|
|
chapUser = undefined,
|
|
chapPassword
|
|
) => {
|
|
const params = { host, target }
|
|
port && (params.port = port)
|
|
chapUser && (params.chapUser = chapUser)
|
|
chapPassword && (params.chapPassword = chapPassword)
|
|
return _call('sr.probeIscsiIqns', params)
|
|
}
|
|
|
|
export const probeSrIscsiLuns = (
|
|
host,
|
|
target,
|
|
targetIqn,
|
|
chapUser = undefined,
|
|
chapPassword
|
|
) => {
|
|
const params = { host, target, targetIqn }
|
|
chapUser && (params.chapUser = chapUser)
|
|
chapPassword && (params.chapPassword = chapPassword)
|
|
return _call('sr.probeIscsiLuns', params)
|
|
}
|
|
|
|
export const probeSrIscsiExists = (
|
|
host,
|
|
target,
|
|
targetIqn,
|
|
scsiId,
|
|
port = undefined,
|
|
chapUser = undefined,
|
|
chapPassword
|
|
) => {
|
|
const params = { host, target, targetIqn, scsiId }
|
|
port && (params.port = port)
|
|
chapUser && (params.chapUser = chapUser)
|
|
chapPassword && (params.chapPassword = chapPassword)
|
|
return _call('sr.probeIscsiExists', params)
|
|
}
|
|
|
|
export const probeSrHba = host => _call('sr.probeHba', { host })
|
|
|
|
export const probeSrHbaExists = (host, scsiId) =>
|
|
_call('sr.probeHbaExists', { host, scsiId })
|
|
|
|
export const reattachSr = (host, uuid, nameLabel, nameDescription, type) =>
|
|
_call('sr.reattach', { host, uuid, nameLabel, nameDescription, type })
|
|
|
|
export const reattachSrIso = (host, uuid, nameLabel, nameDescription, type) =>
|
|
_call('sr.reattachIso', { host, uuid, nameLabel, nameDescription, type })
|
|
|
|
export const createSrNfs = (
|
|
host,
|
|
nameLabel,
|
|
nameDescription,
|
|
server,
|
|
serverPath,
|
|
nfsVersion = undefined,
|
|
nfsOptions
|
|
) => {
|
|
const params = { host, nameLabel, nameDescription, server, serverPath }
|
|
nfsVersion && (params.nfsVersion = nfsVersion)
|
|
nfsOptions && (params.nfsOptions = nfsOptions)
|
|
return _call('sr.createNfs', params)
|
|
}
|
|
|
|
export const createSrIscsi = (
|
|
host,
|
|
nameLabel,
|
|
nameDescription,
|
|
target,
|
|
targetIqn,
|
|
scsiId,
|
|
port = undefined,
|
|
chapUser = undefined,
|
|
chapPassword = undefined
|
|
) => {
|
|
const params = { host, nameLabel, nameDescription, target, targetIqn, scsiId }
|
|
port && (params.port = port)
|
|
chapUser && (params.chapUser = chapUser)
|
|
chapPassword && (params.chapPassword = chapPassword)
|
|
return _call('sr.createIscsi', params)
|
|
}
|
|
|
|
export const createSrHba = (host, nameLabel, nameDescription, scsiId) =>
|
|
_call('sr.createHba', { host, nameLabel, nameDescription, scsiId })
|
|
|
|
export const createSrIso = (
|
|
host,
|
|
nameLabel,
|
|
nameDescription,
|
|
path,
|
|
type,
|
|
user = undefined,
|
|
password = undefined
|
|
) => {
|
|
const params = { host, nameLabel, nameDescription, path, type }
|
|
user && (params.user = user)
|
|
password && (params.password = password)
|
|
return _call('sr.createIso', params)
|
|
}
|
|
|
|
export const createSrLvm = (host, nameLabel, nameDescription, device) =>
|
|
_call('sr.createLvm', { host, nameLabel, nameDescription, device })
|
|
|
|
// Job logs ----------------------------------------------------------
|
|
|
|
export const deleteJobsLogs = async ids => {
|
|
const { length } = ids
|
|
if (length === 0) {
|
|
return
|
|
}
|
|
if (length !== 1) {
|
|
const vars = { nLogs: length }
|
|
try {
|
|
await confirm({
|
|
title: _('logDeleteMultiple', vars),
|
|
body: <p>{_('logDeleteMultipleMessage', vars)}</p>,
|
|
})
|
|
} catch (_) {
|
|
return
|
|
}
|
|
}
|
|
|
|
return _call('log.delete', {
|
|
namespace: 'jobs',
|
|
id: ids.map(resolveId),
|
|
})::tap(subscribeJobsLogs.forceRefresh)
|
|
}
|
|
|
|
// Logs
|
|
|
|
export const deleteApiLog = id =>
|
|
_call('log.delete', { namespace: 'api', id })::tap(
|
|
subscribeApiLogs.forceRefresh
|
|
)
|
|
|
|
// Acls, users, groups ----------------------------------------------------------
|
|
|
|
export const addAcl = ({ subject, object, action }) =>
|
|
_call('acl.add', resolveIds({ subject, object, action }))::tap(
|
|
subscribeAcls.forceRefresh,
|
|
err => error('Add ACL', err.message || String(err))
|
|
)
|
|
|
|
export const removeAcl = ({ subject, object, action }) =>
|
|
_call('acl.remove', resolveIds({ subject, object, action }))::tap(
|
|
subscribeAcls.forceRefresh,
|
|
err => error('Remove ACL', err.message || String(err))
|
|
)
|
|
|
|
export const editAcl = (
|
|
{ subject, object, action },
|
|
{
|
|
subject: newSubject = subject,
|
|
object: newObject = object,
|
|
action: newAction = action,
|
|
}
|
|
) =>
|
|
_call('acl.remove', resolveIds({ subject, object, action }))
|
|
.then(() =>
|
|
_call(
|
|
'acl.add',
|
|
resolveIds({
|
|
subject: newSubject,
|
|
object: newObject,
|
|
action: newAction,
|
|
})
|
|
)
|
|
)
|
|
::tap(subscribeAcls.forceRefresh, err =>
|
|
error('Edit ACL', err.message || String(err))
|
|
)
|
|
|
|
export const createGroup = name =>
|
|
_call('group.create', { name })::tap(subscribeGroups.forceRefresh, err =>
|
|
error(_('createGroup'), err.message || String(err))
|
|
)
|
|
|
|
export const setGroupName = (group, name) =>
|
|
_call('group.set', resolveIds({ group, name }))::tap(
|
|
subscribeGroups.forceRefresh
|
|
)
|
|
|
|
export const deleteGroup = group =>
|
|
confirm({
|
|
title: _('deleteGroup'),
|
|
body: <p>{_('deleteGroupConfirm')}</p>,
|
|
}).then(
|
|
() =>
|
|
_call('group.delete', resolveIds({ id: group }))::tap(
|
|
subscribeGroups.forceRefresh,
|
|
err => error(_('deleteGroup'), err.message || String(err))
|
|
),
|
|
noop
|
|
)
|
|
|
|
export const removeUserFromGroup = (user, group) =>
|
|
_call('group.removeUser', resolveIds({ id: group, userId: user }))::tap(
|
|
subscribeGroups.forceRefresh,
|
|
err => error(_('removeUserFromGroup'), err.message || String(err))
|
|
)
|
|
|
|
export const addUserToGroup = (user, group) =>
|
|
_call('group.addUser', resolveIds({ id: group, userId: user }))::tap(
|
|
subscribeGroups.forceRefresh,
|
|
err => error('Add User', err.message || String(err))
|
|
)
|
|
|
|
export const createUser = (email, password, permission) =>
|
|
_call('user.create', { email, password, permission })::tap(
|
|
subscribeUsers.forceRefresh,
|
|
err => error('Create user', err.message || String(err))
|
|
)
|
|
|
|
export const deleteUser = user =>
|
|
confirm({
|
|
title: _('deleteUser'),
|
|
body: <p>{_('deleteUserConfirm')}</p>,
|
|
}).then(() =>
|
|
_call('user.delete', { id: resolveId(user) })::tap(
|
|
subscribeUsers.forceRefresh,
|
|
err => error(_('deleteUser'), err.message || String(err))
|
|
)
|
|
)
|
|
|
|
export const editUser = (user, { email, password, permission }) =>
|
|
_call('user.set', { id: resolveId(user), email, password, permission })::tap(
|
|
subscribeUsers.forceRefresh
|
|
)
|
|
|
|
export const changePassword = (oldPassword, newPassword) =>
|
|
_call('user.changePassword', {
|
|
oldPassword,
|
|
newPassword,
|
|
}).then(
|
|
() => success(_('pwdChangeSuccess'), _('pwdChangeSuccessBody')),
|
|
() => error(_('pwdChangeError'), _('pwdChangeErrorBody'))
|
|
)
|
|
|
|
const _setUserPreferences = preferences =>
|
|
_call('user.set', {
|
|
id: xo.user.id,
|
|
preferences,
|
|
})::tap(subscribeCurrentUser.forceRefresh)
|
|
|
|
import NewSshKeyModalBody from './new-ssh-key-modal' // eslint-disable-line import/first
|
|
export const addSshKey = key => {
|
|
const { preferences } = xo.user
|
|
const otherKeys = (preferences && preferences.sshKeys) || []
|
|
if (key) {
|
|
return _setUserPreferences({
|
|
sshKeys: [...otherKeys, key],
|
|
})
|
|
}
|
|
return confirm({
|
|
icon: 'ssh-key',
|
|
title: _('newSshKeyModalTitle'),
|
|
body: <NewSshKeyModalBody />,
|
|
}).then(newKey => {
|
|
if (!newKey.title || !newKey.key) {
|
|
error(_('sshKeyErrorTitle'), _('sshKeyErrorMessage'))
|
|
return
|
|
}
|
|
return _setUserPreferences({
|
|
sshKeys: [...otherKeys, newKey],
|
|
})
|
|
}, noop)
|
|
}
|
|
|
|
export const deleteSshKey = key =>
|
|
confirm({
|
|
title: _('deleteSshKeyConfirm'),
|
|
body: _('deleteSshKeyConfirmMessage', {
|
|
title: <strong>{key.title}</strong>,
|
|
}),
|
|
}).then(() => {
|
|
const { preferences } = xo.user
|
|
return _setUserPreferences({
|
|
sshKeys: filter(
|
|
preferences && preferences.sshKeys,
|
|
k => k.key !== resolveId(key)
|
|
),
|
|
})
|
|
}, noop)
|
|
|
|
export const deleteSshKeys = keys =>
|
|
confirm({
|
|
title: _('deleteSshKeysConfirm', { nKeys: keys.length }),
|
|
body: _('deleteSshKeysConfirmMessage', {
|
|
nKeys: keys.length,
|
|
}),
|
|
}).then(() => {
|
|
const { preferences } = xo.user
|
|
const keyIds = resolveIds(keys)
|
|
return _setUserPreferences({
|
|
sshKeys: filter(
|
|
preferences && preferences.sshKeys,
|
|
sshKey => !includes(keyIds, sshKey.key)
|
|
),
|
|
})
|
|
}, noop)
|
|
|
|
// User filters --------------------------------------------------
|
|
|
|
import AddUserFilterModalBody from './add-user-filter-modal' // eslint-disable-line import/first
|
|
export const addCustomFilter = (type, value) => {
|
|
const { user } = xo
|
|
return confirm({
|
|
title: _('saveNewFilterTitle'),
|
|
body: <AddUserFilterModalBody user={user} type={type} value={value} />,
|
|
}).then(name => {
|
|
if (name.length === 0) {
|
|
return error(
|
|
_('saveNewUserFilterErrorTitle'),
|
|
_('saveNewUserFilterErrorBody')
|
|
)
|
|
}
|
|
|
|
const { preferences } = user
|
|
const filters = (preferences && preferences.filters) || {}
|
|
|
|
return _setUserPreferences({
|
|
filters: {
|
|
...filters,
|
|
[type]: {
|
|
...filters[type],
|
|
[name]: value,
|
|
},
|
|
},
|
|
})
|
|
})
|
|
}
|
|
|
|
export const removeCustomFilter = (type, name) =>
|
|
confirm({
|
|
title: _('removeUserFilterTitle'),
|
|
body: <p>{_('removeUserFilterBody')}</p>,
|
|
}).then(() => {
|
|
const { user } = xo
|
|
const { filters } = user.preferences
|
|
|
|
return _setUserPreferences({
|
|
filters: {
|
|
...filters,
|
|
[type]: {
|
|
...filters[type],
|
|
[name]: undefined,
|
|
},
|
|
},
|
|
})
|
|
})
|
|
|
|
export const editCustomFilter = (type, name, { newName = name, newValue }) => {
|
|
const { filters } = xo.user.preferences
|
|
return _setUserPreferences({
|
|
filters: {
|
|
...filters,
|
|
[type]: {
|
|
...filters[type],
|
|
[name]: undefined,
|
|
[newName]: newValue || filters[type][name],
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
export const setDefaultHomeFilter = (type, name) => {
|
|
const { user } = xo
|
|
const { preferences } = user
|
|
const defaultFilters = (preferences && preferences.defaultHomeFilters) || {}
|
|
|
|
return _setUserPreferences({
|
|
defaultHomeFilters: {
|
|
...defaultFilters,
|
|
[type]: name,
|
|
},
|
|
})
|
|
}
|
|
|
|
// IP pools --------------------------------------------------------------------
|
|
|
|
export const createIpPool = ({ name, ips, networks }) => {
|
|
const addresses = {}
|
|
forEach(ips, ip => {
|
|
addresses[ip] = {}
|
|
})
|
|
return _call('ipPool.create', {
|
|
name,
|
|
addresses,
|
|
networks: resolveIds(networks),
|
|
})::tap(subscribeIpPools.forceRefresh)
|
|
}
|
|
|
|
export const deleteIpPool = ipPool =>
|
|
_call('ipPool.delete', { id: resolveId(ipPool) })::tap(
|
|
subscribeIpPools.forceRefresh
|
|
)
|
|
|
|
export const setIpPool = (ipPool, { name, addresses, networks }) =>
|
|
_call('ipPool.set', {
|
|
id: resolveId(ipPool),
|
|
name,
|
|
addresses,
|
|
networks: resolveIds(networks),
|
|
})::tap(subscribeIpPools.forceRefresh)
|
|
|
|
// Cloud configs --------------------------------------------------------------------
|
|
|
|
export const subscribeCloudConfigs = createSubscription(() =>
|
|
_call('cloudConfig.getAll')
|
|
)
|
|
|
|
export const createCloudConfig = props =>
|
|
_call('cloudConfig.create', props)::tap(subscribeCloudConfigs.forceRefresh)
|
|
|
|
export const deleteCloudConfigs = ids => {
|
|
const { length } = ids
|
|
if (length === 0) {
|
|
return
|
|
}
|
|
|
|
const vars = { nCloudConfigs: length }
|
|
return confirm({
|
|
title: _('confirmDeleteCloudConfigsTitle', vars),
|
|
body: <p>{_('confirmDeleteCloudConfigsBody', vars)}</p>,
|
|
}).then(
|
|
() =>
|
|
Promise.all(
|
|
ids.map(id => _call('cloudConfig.delete', { id: resolveId(id) }))
|
|
)::tap(subscribeCloudConfigs.forceRefresh),
|
|
noop
|
|
)
|
|
}
|
|
|
|
export const editCloudConfig = (cloudConfig, props) =>
|
|
_call('cloudConfig.update', { ...props, id: resolveId(cloudConfig) })::tap(
|
|
subscribeCloudConfigs.forceRefresh
|
|
)
|
|
|
|
// XO SAN ----------------------------------------------------------------------
|
|
|
|
export const getVolumeInfo = (xosanSr, infoType) =>
|
|
_call('xosan.getVolumeInfo', { sr: xosanSr, infoType })
|
|
|
|
export const createXosanSR = ({
|
|
template,
|
|
pif,
|
|
vlan,
|
|
srs,
|
|
glusterType,
|
|
redundancy,
|
|
brickSize,
|
|
memorySize,
|
|
ipRange,
|
|
}) => {
|
|
const promise = _call('xosan.createSR', {
|
|
template,
|
|
pif: pif.id,
|
|
vlan: String(vlan),
|
|
srs: resolveIds(srs),
|
|
glusterType,
|
|
redundancy: Number.parseInt(redundancy),
|
|
brickSize,
|
|
memorySize,
|
|
ipRange,
|
|
})
|
|
|
|
// Force refresh in parallel to get the creation progress sooner
|
|
subscribeCheckSrCurrentState.forceRefresh()
|
|
|
|
return promise
|
|
}
|
|
|
|
export const addXosanBricks = (xosansr, lvmsrs, brickSize) =>
|
|
_call('xosan.addBricks', { xosansr, lvmsrs, brickSize })
|
|
|
|
export const replaceXosanBrick = (
|
|
xosansr,
|
|
previousBrick,
|
|
newLvmSr,
|
|
brickSize,
|
|
onSameVM = false
|
|
) =>
|
|
_call(
|
|
'xosan.replaceBrick',
|
|
resolveIds({ xosansr, previousBrick, newLvmSr, brickSize, onSameVM })
|
|
)
|
|
|
|
export const removeXosanBricks = (xosansr, bricks) =>
|
|
_call('xosan.removeBricks', { xosansr, bricks })
|
|
|
|
export const computeXosanPossibleOptions = (lvmSrs, brickSize) =>
|
|
_call('xosan.computeXosanPossibleOptions', { lvmSrs, brickSize })
|
|
|
|
export const registerXosan = () =>
|
|
_call('cloud.registerResource', { namespace: 'xosan' })::tap(
|
|
subscribeResourceCatalog.forceRefresh
|
|
)
|
|
|
|
export const fixHostNotInXosanNetwork = (xosanSr, host) =>
|
|
_call('xosan.fixHostNotInNetwork', { xosanSr, host })
|
|
|
|
// XOSAN packs -----------------------------------------------------------------
|
|
|
|
export const getResourceCatalog = () => _call('cloud.getResourceCatalog')
|
|
|
|
const downloadAndInstallXosanPack = (pack, pool, { version }) =>
|
|
_call('xosan.downloadAndInstallXosanPack', {
|
|
id: resolveId(pack),
|
|
version,
|
|
pool: resolveId(pool),
|
|
})
|
|
|
|
import UpdateXosanPacksModal from './update-xosan-packs-modal' // eslint-disable-line import/first
|
|
export const updateXosanPacks = pool =>
|
|
confirm({
|
|
title: _('xosanUpdatePacks'),
|
|
icon: 'host-patch-update',
|
|
body: <UpdateXosanPacksModal pool={pool} />,
|
|
}).then(pack => {
|
|
if (pack === undefined) {
|
|
return
|
|
}
|
|
|
|
return downloadAndInstallXosanPack(pack, pool, { version: pack.version })
|
|
})
|
|
|
|
// Licenses --------------------------------------------------------------------
|
|
|
|
export const getLicenses = productId => _call('xoa.getLicenses', { productId })
|
|
|
|
export const getLicense = (productId, boundObjectId) =>
|
|
_call('xoa.getLicense', { productId, boundObjectId })
|
|
|
|
export const unlockXosan = (licenseId, srId) =>
|
|
_call('xosan.unlock', { licenseId, sr: srId })
|