Files
xen-orchestra/app/node_modules/xo-api/index.js
2016-02-03 11:53:54 +01:00

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