423 lines
9.8 KiB
JavaScript
423 lines
9.8 KiB
JavaScript
import angular from 'angular'
|
|
import angularCookies from 'angular-cookies'
|
|
import checkPermissions from 'xo-acl-resolver'
|
|
import cloneDeep from 'lodash.clonedeep'
|
|
import forEach from 'lodash.foreach'
|
|
import indexOf from 'lodash.indexof'
|
|
import sum from 'lodash.sum'
|
|
import XoIndex from 'xo-collection/index'
|
|
import xoLib from 'xo-lib'
|
|
import XoUniqueIndex from 'xo-collection/unique-index'
|
|
import XoView from 'xo-collection/view'
|
|
|
|
const {defineProperty} = Object
|
|
const {isArray, isString} = angular
|
|
|
|
// ===================================================================
|
|
|
|
// Low level XO API for Angular.
|
|
export default angular.module('xo-api', [
|
|
angularCookies
|
|
])
|
|
.run(function ($rootScope) {
|
|
// Ensure correct integration with Angular.
|
|
xoLib.setScheduler(function (fn) {
|
|
$rootScope.$evalAsync(fn)
|
|
})
|
|
})
|
|
.service('xoApi', function (
|
|
$cookies,
|
|
$rootScope,
|
|
$timeout,
|
|
$window
|
|
) {
|
|
const xo = new xoLib.Xo()
|
|
|
|
// A lots of event listeners are added to the collection.
|
|
xo.objects.setMaxListeners(0)
|
|
|
|
// Notifies Angular about changes in the collection.
|
|
xo.objects.on('finish', () => {
|
|
$rootScope.$applyAsync()
|
|
})
|
|
|
|
xo.signIn({ token: $cookies.get('token') }).catch(() => {
|
|
$cookies.remove('token')
|
|
|
|
// Full page reload.
|
|
$window.location.reload(true)
|
|
})
|
|
|
|
// ---------------------------------------------------------------
|
|
|
|
const getObject = (function (objects) {
|
|
const {
|
|
all: byIds,
|
|
indexes: {
|
|
ref: byRefs
|
|
}
|
|
} = objects
|
|
|
|
return function getObject (id, type) {
|
|
const object = byIds[id] || byRefs[id]
|
|
|
|
if (
|
|
// The object has been found and …
|
|
object && (
|
|
// … no type specified.
|
|
!type ||
|
|
|
|
// … is of the expected type.
|
|
(type === object.type) ||
|
|
|
|
// … is of one of the allowed types.
|
|
isArray(type) && (indexOf(type, object.type) === -1)
|
|
)
|
|
) {
|
|
return object
|
|
}
|
|
}
|
|
})(xo.objects)
|
|
|
|
// ---------------------------------------------------------------
|
|
|
|
let currentPermissions
|
|
;(function updateCurrentPermissions () {
|
|
xo.call('acl.getCurrentPermissions').then(permissions => {
|
|
currentPermissions = permissions
|
|
|
|
$timeout(updateCurrentPermissions, 1e4)
|
|
})
|
|
})()
|
|
|
|
function checkPermission (objectId, permission) {
|
|
if (xo.user.permission === 'admin') {
|
|
return true
|
|
}
|
|
|
|
// Auto unbox.
|
|
if (objectId.id) {
|
|
objectId = objectId.id
|
|
}
|
|
|
|
if (!currentPermissions) {
|
|
return false
|
|
}
|
|
|
|
try {
|
|
return checkPermissions(
|
|
currentPermissions,
|
|
objectId => {
|
|
const object = getObject(objectId)
|
|
if (!object) {
|
|
throw new Error(`no such object ${objectId}`)
|
|
}
|
|
return object
|
|
},
|
|
[
|
|
[ objectId, permission ]
|
|
]
|
|
)
|
|
} catch (_) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
function canAccess (objectId) {
|
|
return checkPermission(objectId, 'view')
|
|
}
|
|
|
|
// ---------------------------------------------------------------
|
|
|
|
const getView = (function () {
|
|
const views = Object.create(null)
|
|
|
|
function getView (viewName) {
|
|
let view = views[viewName]
|
|
if (!view) {
|
|
// The view name can be plural (ex VMs) but the type is
|
|
// singular.
|
|
const type = viewName[viewName.length - 1] === 's'
|
|
? viewName.slice(0, -1)
|
|
: viewName
|
|
|
|
const predicate = (object) => object.type === type
|
|
view = views[viewName] = new XoView(xo.objects, predicate)
|
|
}
|
|
return view
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
|
|
function registerLazyView (name, predicate, collection) {
|
|
defineProperty(views, name, {
|
|
configurable: true,
|
|
enumerable: true,
|
|
get () {
|
|
if (!collection) {
|
|
collection = xo.objects
|
|
} else if (isString(collection)) {
|
|
collection = getView(collection)
|
|
}
|
|
|
|
const view = new XoView(collection, predicate)
|
|
delete views[name]
|
|
views[name] = view
|
|
|
|
return view
|
|
}
|
|
})
|
|
}
|
|
|
|
registerLazyView(
|
|
'runningHosts',
|
|
host => host.power_state === 'Running',
|
|
'hosts'
|
|
)
|
|
|
|
const RUNNING_VM_STATUSES = {
|
|
Running: true,
|
|
Paused: true
|
|
}
|
|
registerLazyView(
|
|
'runningVms',
|
|
(vm) => RUNNING_VM_STATUSES[vm.power_state],
|
|
'VMs'
|
|
)
|
|
|
|
const RUNNING_TASK_STATUSES = {
|
|
cancelling: true,
|
|
pending: true
|
|
}
|
|
registerLazyView(
|
|
'runningTasks',
|
|
(task) => RUNNING_TASK_STATUSES[task.status] && canAccess(task.$host),
|
|
'tasks'
|
|
)
|
|
|
|
// -------------------------------------------------------------
|
|
|
|
return getView
|
|
})()
|
|
|
|
// ---------------------------------------------------------------
|
|
|
|
const getIndex = (function (indexes) {
|
|
function registerLazyIndex (name, computeHash, collection, isUnique) {
|
|
Object.defineProperty(indexes, name, {
|
|
configurable: true,
|
|
get () {
|
|
if (!collection) {
|
|
collection = xo.objects
|
|
} else if (isString(collection)) {
|
|
collection = getView(collection)
|
|
}
|
|
|
|
const index = new (isUnique ? XoUniqueIndex : XoIndex)(computeHash)
|
|
index._attachCollection(collection)
|
|
|
|
const items = index.items
|
|
|
|
delete indexes[name]
|
|
indexes[name] = items
|
|
|
|
return items
|
|
}
|
|
})
|
|
}
|
|
|
|
registerLazyIndex(
|
|
'hostsByPool',
|
|
'$poolId',
|
|
'hosts'
|
|
)
|
|
|
|
registerLazyIndex(
|
|
'networksByPool',
|
|
'$poolId',
|
|
'networks'
|
|
)
|
|
|
|
registerLazyIndex(
|
|
'poolPatchesByPool',
|
|
'$poolId',
|
|
'pool_patch'
|
|
)
|
|
|
|
registerLazyIndex(
|
|
'runningHostsByPool',
|
|
'$poolId',
|
|
'runningHosts'
|
|
)
|
|
|
|
registerLazyIndex(
|
|
'runningTasksByHost',
|
|
'$host',
|
|
'runningTasks'
|
|
)
|
|
|
|
registerLazyIndex(
|
|
'runningVmsByPool',
|
|
'$poolId',
|
|
'runningVms'
|
|
)
|
|
|
|
registerLazyIndex(
|
|
'srsByContainer',
|
|
'$container',
|
|
'SRs'
|
|
)
|
|
|
|
registerLazyIndex(
|
|
'vmsByContainer',
|
|
'$container',
|
|
'VMs'
|
|
)
|
|
|
|
registerLazyIndex(
|
|
'vmsByPool',
|
|
'$poolId',
|
|
'VMs'
|
|
)
|
|
|
|
registerLazyIndex(
|
|
'vmControllersByContainer',
|
|
'$container',
|
|
'VM-controllers',
|
|
true
|
|
)
|
|
|
|
registerLazyIndex(
|
|
'vmTemplatesByContainer',
|
|
'$container',
|
|
'VM-templates'
|
|
)
|
|
|
|
return function getIndex (name) {
|
|
const index = indexes[name]
|
|
if (!index) {
|
|
throw new Error('no such index ' + name)
|
|
}
|
|
|
|
return index
|
|
}
|
|
})(Object.create(null))
|
|
|
|
// ---------------------------------------------------------------
|
|
|
|
const stats = {
|
|
$CPUs: 0,
|
|
$vCPUs: 0,
|
|
$memory: {
|
|
usage: 0,
|
|
size: 0
|
|
}
|
|
}
|
|
|
|
getView('hosts').on('finish', function () {
|
|
stats.$CPUs = sum(this.all, host => +host.CPUs.cpu_count)
|
|
})
|
|
|
|
getView('runningVms').on('finish', function () {
|
|
stats.$vCPUs = sum(this.all, vm => vm.CPUs.number)
|
|
})
|
|
|
|
// TODO: maybe merge with stats.$CPUs.
|
|
getView('runningHosts').on('finish', function () {
|
|
// TODO: merge into a single loop.
|
|
stats.$memory.usage = sum(this.all, host => host.memory.usage)
|
|
stats.$memory.size = sum(this.all, host => host.memory.size)
|
|
})
|
|
|
|
// ---------------------------------------------------------------
|
|
|
|
return {
|
|
// -----------------
|
|
// Session
|
|
// -----------------
|
|
|
|
logOut () {
|
|
$cookies.remove('token')
|
|
|
|
// Full page reload.
|
|
$window.location.reload(true)
|
|
},
|
|
get status () {
|
|
return xo.status
|
|
},
|
|
get user () {
|
|
return xo.user
|
|
},
|
|
|
|
// -----------------
|
|
// RPC
|
|
// -----------------
|
|
|
|
call (method, params) {
|
|
// The params need to be cloned to prevent them from being
|
|
// changed before the method has really been sent.
|
|
return xo.call(method, cloneDeep(params))
|
|
},
|
|
|
|
// -----------------
|
|
// Objects
|
|
// -----------------
|
|
|
|
// Registers a watcher for objects update.
|
|
//
|
|
// The returned function can be used to unregister the watcher.
|
|
onUpdate (fn) {
|
|
// Prevent access to `xo.objects` via `this`.
|
|
const listener = () => { fn() }
|
|
|
|
xo.objects.on('finish', listener)
|
|
|
|
return () => {
|
|
xo.objects.removeListener('finish', listener)
|
|
}
|
|
},
|
|
|
|
// Collection of all objects.
|
|
all: xo.objects.all,
|
|
|
|
// Returns an object (or multiple objects) from its id/ref.
|
|
//
|
|
// They can be filter by type(s).
|
|
get (id, types) {
|
|
if (isArray(id)) {
|
|
const objects = []
|
|
|
|
forEach(id, id => {
|
|
const object = getObject(id, types)
|
|
if (object) {
|
|
objects.push(object)
|
|
}
|
|
})
|
|
|
|
return objects
|
|
}
|
|
|
|
return getObject(id, types)
|
|
},
|
|
|
|
getIndex,
|
|
|
|
// Returns a view (read-only XoCollection).
|
|
getView,
|
|
|
|
// Checks whether the current user has access to an object
|
|
// (identified via its id or ref).
|
|
canAccess,
|
|
// Provides a check for user interaction with an object
|
|
canInteract: checkPermission,
|
|
|
|
// -----------------
|
|
// Various
|
|
// -----------------
|
|
|
|
// Global stats.
|
|
stats
|
|
}
|
|
})
|
|
.name
|