mirror of
https://github.com/openstf/stf
synced 2025-10-04 10:19:30 +02:00
Merge branch 'master' into feature/separate-device-logs
This commit is contained in:
commit
c3a51cf867
155 changed files with 17373 additions and 1235 deletions
1
LICENSE
1
LICENSE
|
@ -1,5 +1,6 @@
|
|||
Copyright © 2013 CyberAgent, Inc.
|
||||
Copyright © 2016 The OpenSTF Project
|
||||
Copyright © 2019 Orange SA
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
|
42
TESTING.md
42
TESTING.md
|
@ -5,20 +5,48 @@
|
|||
|
||||
## E2E Frontend
|
||||
|
||||
### On first run
|
||||
## On first run
|
||||
- `gulp webdriver-update`
|
||||
|
||||
### Chrome Local STF
|
||||
- Connect a device
|
||||
- Run stf
|
||||
- `gulp protractor`
|
||||
|
||||
### Multiple Browsers Local STF with a specific suite
|
||||
|
||||
## Protractor&Jasmine - Local STF tests
|
||||
|
||||
|
||||
---
|
||||
#### Preconditions
|
||||
Test configuration point to Google Chrome browser. Test works on Google Chrome v.77.0.3865.75 together with chromedriver with ver. 77.0.3865.40.
|
||||
|
||||
---
|
||||
|
||||
- Connect a device or start android emulator
|
||||
- Run RethinkDb
|
||||
```
|
||||
rethinkdb
|
||||
```
|
||||
- Run stf
|
||||
```
|
||||
./bin/stf local
|
||||
```
|
||||
Wait till STF will be fully functional and devices will be discovered
|
||||
- Run tests
|
||||
```
|
||||
gulp protractor
|
||||
```
|
||||
|
||||
---
|
||||
#### Info
|
||||
Test results can be found in:
|
||||
test-results/reports-protractor/dashboardReport-protractor/index.html
|
||||
|
||||
---
|
||||
|
||||
## Multiple Browsers Local STF with a specific suite
|
||||
- Connect a device
|
||||
- Run stf
|
||||
- `gulp protractor --multi --suite devices`
|
||||
|
||||
### Chrome Remote STF
|
||||
## Chrome Remote STF
|
||||
- `export STF_URL='http://stf-url/#!/'`
|
||||
- `export STF_USERNAME='user'`
|
||||
- `export STF_PASSWORD='pass'`
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/**
|
||||
* Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
module.exports.command = 'api'
|
||||
|
||||
module.exports.describe = 'Start an API unit.'
|
||||
|
@ -18,6 +22,18 @@ module.exports.builder = function(yargs) {
|
|||
, array: true
|
||||
, demand: true
|
||||
})
|
||||
.option('connect-push-dev', {
|
||||
alias: 'pd'
|
||||
, describe: 'Device-side ZeroMQ PULL endpoint to connect to.'
|
||||
, array: true
|
||||
, demand: true
|
||||
})
|
||||
.option('connect-sub-dev', {
|
||||
alias: 'sd'
|
||||
, describe: 'Device-side ZeroMQ PUB endpoint to connect to.'
|
||||
, array: true
|
||||
, demand: true
|
||||
})
|
||||
.option('port', {
|
||||
alias: 'p'
|
||||
, describe: 'The port to bind to.'
|
||||
|
@ -53,6 +69,8 @@ module.exports.handler = function(argv) {
|
|||
, endpoints: {
|
||||
push: argv.connectPush
|
||||
, sub: argv.connectSub
|
||||
, pushdev: argv.connectPushDev
|
||||
, subdev: argv.connectSubDev
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -30,16 +30,13 @@ module.exports.handler = function() {
|
|||
var proc = cp.spawn(command, args, options)
|
||||
var stdout = []
|
||||
|
||||
proc.stdout.on('readable', function() {
|
||||
var chunk
|
||||
while ((chunk = proc.stdout.read())) {
|
||||
stdout.push(chunk)
|
||||
}
|
||||
proc.stdout.on('data', function(data) {
|
||||
stdout.push(data)
|
||||
})
|
||||
|
||||
proc.on('error', reject)
|
||||
|
||||
proc.on('exit', function(code, signal) {
|
||||
proc.on('close', function(code, signal) {
|
||||
if (signal) {
|
||||
reject(new CheckError('Exited with signal %s', signal))
|
||||
}
|
||||
|
|
39
lib/cli/generate-fake-group/index.js
Normal file
39
lib/cli/generate-fake-group/index.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
module.exports.command = 'generate-fake-group'
|
||||
|
||||
module.exports.builder = function(yargs) {
|
||||
return yargs
|
||||
.strict()
|
||||
.option('n', {
|
||||
alias: 'number'
|
||||
, describe: 'How many groups to create.'
|
||||
, type: 'number'
|
||||
, default: 1
|
||||
})
|
||||
}
|
||||
|
||||
module.exports.handler = function(argv) {
|
||||
var logger = require('../../util/logger')
|
||||
var log = logger.createLogger('cli:generate-fake-group')
|
||||
var fake = require('../../util/fakegroup')
|
||||
var n = argv.number
|
||||
|
||||
function next() {
|
||||
return fake.generate().then(function(email) {
|
||||
log.info('Created fake group "%s"', email)
|
||||
return --n ? next() : null
|
||||
})
|
||||
}
|
||||
|
||||
return next()
|
||||
.then(function() {
|
||||
process.exit(0)
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.fatal('Fake group creation had an error:', err.stack)
|
||||
process.exit(1)
|
||||
})
|
||||
}
|
39
lib/cli/generate-fake-user/index.js
Normal file
39
lib/cli/generate-fake-user/index.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
module.exports.command = 'generate-fake-user'
|
||||
|
||||
module.exports.builder = function(yargs) {
|
||||
return yargs
|
||||
.strict()
|
||||
.option('n', {
|
||||
alias: 'number'
|
||||
, describe: 'How many users to create.'
|
||||
, type: 'number'
|
||||
, default: 1
|
||||
})
|
||||
}
|
||||
|
||||
module.exports.handler = function(argv) {
|
||||
var logger = require('../../util/logger')
|
||||
var log = logger.createLogger('cli:generate-fake-user')
|
||||
var fake = require('../../util/fakeuser')
|
||||
var n = argv.number
|
||||
|
||||
function next() {
|
||||
return fake.generate().then(function(email) {
|
||||
log.info('Created fake user "%s"', email)
|
||||
return --n ? next() : null
|
||||
})
|
||||
}
|
||||
|
||||
return next()
|
||||
.then(function() {
|
||||
process.exit(0)
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.fatal('Fake user creation had an error:', err.stack)
|
||||
process.exit(1)
|
||||
})
|
||||
}
|
51
lib/cli/groups-engine/index.js
Normal file
51
lib/cli/groups-engine/index.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
module.exports.command = 'groups-engine'
|
||||
|
||||
module.exports.describe = 'Start the groups engine unit.'
|
||||
|
||||
module.exports.builder = function(yargs) {
|
||||
return yargs
|
||||
.env('STF_GROUPS_ENGINE')
|
||||
.strict()
|
||||
.option('connect-push', {
|
||||
alias: 'c'
|
||||
, describe: 'App-side ZeroMQ PULL endpoint to connect to.'
|
||||
, array: true
|
||||
, demand: true
|
||||
})
|
||||
.option('connect-sub', {
|
||||
alias: 'u'
|
||||
, describe: 'App-side ZeroMQ PUB endpoint to connect to.'
|
||||
, array: true
|
||||
, demand: true
|
||||
})
|
||||
.option('connect-push-dev', {
|
||||
alias: 'pd'
|
||||
, describe: 'Device-side ZeroMQ PULL endpoint to connect to.'
|
||||
, array: true
|
||||
, demand: true
|
||||
})
|
||||
.option('connect-sub-dev', {
|
||||
alias: 'sd'
|
||||
, describe: 'Device-side ZeroMQ PUB endpoint to connect to.'
|
||||
, array: true
|
||||
, demand: true
|
||||
})
|
||||
.epilog('Each option can be be overwritten with an environment variable ' +
|
||||
'by converting the option to uppercase, replacing dashes with ' +
|
||||
'underscores and prefixing it with `STF_GROUPS_ENGINE_` .)')
|
||||
}
|
||||
|
||||
module.exports.handler = function(argv) {
|
||||
return require('../../units/groups-engine')({
|
||||
endpoints: {
|
||||
push: argv.connectPush
|
||||
, sub: argv.connectSub
|
||||
, pushdev: argv.connectPushDev
|
||||
, subdev: argv.connectSubDev
|
||||
}
|
||||
})
|
||||
}
|
|
@ -1,3 +1,7 @@
|
|||
/**
|
||||
* Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
var yargs = require('yargs')
|
||||
var Promise = require('bluebird')
|
||||
|
||||
|
@ -12,9 +16,12 @@ var _argv = yargs.usage('Usage: $0 <command> [options]')
|
|||
.command(require('./auth-oauth2'))
|
||||
.command(require('./auth-openid'))
|
||||
.command(require('./auth-saml2'))
|
||||
.command(require('./groups-engine'))
|
||||
.command(require('./device'))
|
||||
.command(require('./doctor'))
|
||||
.command(require('./generate-fake-device'))
|
||||
.command(require('./generate-fake-user'))
|
||||
.command(require('./generate-fake-group'))
|
||||
.command(require('./local'))
|
||||
.command(require('./log-rethinkdb'))
|
||||
.command(require('./migrate'))
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/**
|
||||
* Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
module.exports.command = 'local [serial..]'
|
||||
|
||||
module.exports.describe = 'Start a complete local development environment.'
|
||||
|
@ -337,6 +341,17 @@ module.exports.handler = function(argv) {
|
|||
, '--secret', argv.authSecret
|
||||
, '--connect-push', argv.bindAppPull
|
||||
, '--connect-sub', argv.bindAppPub
|
||||
, '--connect-push-dev', argv.bindDevPull
|
||||
, '--connect-sub-dev', argv.bindDevPub
|
||||
])
|
||||
|
||||
// groups engine
|
||||
, procutil.fork(path.resolve(__dirname, '..'), [
|
||||
'groups-engine'
|
||||
, '--connect-push', argv.bindAppPull
|
||||
, '--connect-sub', argv.bindAppPub
|
||||
, '--connect-push-dev', argv.bindDevPull
|
||||
, '--connect-sub-dev', argv.bindDevPub
|
||||
])
|
||||
|
||||
// websocket
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/**
|
||||
* Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
module.exports.command = 'migrate'
|
||||
|
||||
module.exports.describe = 'Migrates the database to the latest version.'
|
||||
|
@ -10,13 +14,44 @@ module.exports.handler = function() {
|
|||
var logger = require('../../util/logger')
|
||||
var log = logger.createLogger('cli:migrate')
|
||||
var db = require('../../db')
|
||||
var dbapi = require('../../db/api')
|
||||
const apiutil = require('../../util/apiutil')
|
||||
const Promise = require('bluebird')
|
||||
|
||||
return db.setup()
|
||||
.then(function() {
|
||||
process.exit(0)
|
||||
return new Promise(function(resolve, reject) {
|
||||
setTimeout(function() {
|
||||
return dbapi.getGroupByIndex(apiutil.ROOT, 'privilege').then(function(group) {
|
||||
if (!group) {
|
||||
const env = {
|
||||
STF_ROOT_GROUP_NAME: 'Common'
|
||||
, STF_ADMIN_NAME: 'administrator'
|
||||
, STF_ADMIN_EMAIL: 'administrator@fakedomain.com'
|
||||
}
|
||||
for (const i in env) {
|
||||
if (process.env[i]) {
|
||||
env[i] = process.env[i]
|
||||
}
|
||||
}
|
||||
return dbapi.createBootStrap(env)
|
||||
}
|
||||
return group
|
||||
})
|
||||
.then(function() {
|
||||
resolve(true)
|
||||
})
|
||||
.catch(function(err) {
|
||||
reject(err)
|
||||
})
|
||||
}, 1000)
|
||||
})
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.fatal('Migration had an error:', err.stack)
|
||||
process.exit(1)
|
||||
})
|
||||
.finally(function() {
|
||||
process.exit(0)
|
||||
})
|
||||
}
|
||||
|
|
1009
lib/db/api.js
1009
lib/db/api.js
File diff suppressed because it is too large
Load diff
|
@ -1,3 +1,7 @@
|
|||
/**
|
||||
* Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
var r = require('rethinkdb')
|
||||
|
||||
module.exports = {
|
||||
|
@ -50,9 +54,30 @@ module.exports = {
|
|||
return device('provider')('channel')
|
||||
}
|
||||
}
|
||||
, group: {
|
||||
indexFunction: function(device) {
|
||||
return device('group')('id')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
, logs: {
|
||||
primaryKey: 'id'
|
||||
}
|
||||
, groups: {
|
||||
primaryKey: 'id'
|
||||
, indexes: {
|
||||
privilege: null
|
||||
, owner: {
|
||||
indexFunction: function(group) {
|
||||
return group('owner')('email')
|
||||
}
|
||||
}
|
||||
, startTime: {
|
||||
indexFunction: function(group) {
|
||||
return group('dates').nth(0)('start')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,79 +1,527 @@
|
|||
/**
|
||||
* Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
var _ = require('lodash')
|
||||
var Promise = require('bluebird')
|
||||
|
||||
var dbapi = require('../../../db/api')
|
||||
var logger = require('../../../util/logger')
|
||||
var datautil = require('../../../util/datautil')
|
||||
|
||||
var log = logger.createLogger('api:controllers:devices')
|
||||
|
||||
module.exports = {
|
||||
getDevices: getDevices
|
||||
, getDeviceBySerial: getDeviceBySerial
|
||||
const apiutil = require('../../../util/apiutil')
|
||||
const lockutil = require('../../../util/lockutil')
|
||||
const util = require('util')
|
||||
const uuid = require('uuid')
|
||||
const wire = require('../../../wire')
|
||||
const wireutil = require('../../../wire/util')
|
||||
const wirerouter = require('../../../wire/router')
|
||||
|
||||
/* ------------------------------------ PRIVATE FUNCTIONS ------------------------------- */
|
||||
|
||||
function filterGenericDevices(req, res, devices) {
|
||||
apiutil.respond(res, 200, 'Devices Information', {
|
||||
devices: devices.map(function(device) {
|
||||
return apiutil.filterDevice(req, device)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function getDevices(req, res) {
|
||||
var fields = req.swagger.params.fields.value
|
||||
function getGenericDevices(req, res, loadDevices) {
|
||||
loadDevices(req.user.groups.subscribed).then(function(devices) {
|
||||
filterGenericDevices(req, res, devices)
|
||||
})
|
||||
.catch(function(err) {
|
||||
apiutil.internalError(res, 'Failed to load device list: ', err.stack)
|
||||
})
|
||||
}
|
||||
|
||||
dbapi.loadDevices()
|
||||
.then(function(cursor) {
|
||||
return Promise.promisify(cursor.toArray, cursor)()
|
||||
.then(function(list) {
|
||||
var deviceList = []
|
||||
|
||||
list.forEach(function(device) {
|
||||
datautil.normalize(device, req.user)
|
||||
var responseDevice = device
|
||||
|
||||
if (fields) {
|
||||
responseDevice = _.pick(device, fields.split(','))
|
||||
}
|
||||
deviceList.push(responseDevice)
|
||||
})
|
||||
|
||||
res.json({
|
||||
success: true
|
||||
, devices: deviceList
|
||||
})
|
||||
})
|
||||
function getDeviceFilteredGroups(serial, fields, bookingOnly) {
|
||||
return dbapi.getDeviceGroups(serial).then(function(groups) {
|
||||
return Promise.map(groups, function(group) {
|
||||
return !bookingOnly || !apiutil.isOriginGroup(group.class) ?
|
||||
group :
|
||||
'filtered'
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.error('Failed to load device list: ', err.stack)
|
||||
res.status(500).json({
|
||||
success: false
|
||||
.then(function(groups) {
|
||||
return _.without(groups, 'filtered').map(function(group) {
|
||||
if (fields) {
|
||||
return _.pick(apiutil.publishGroup(group), fields.split(','))
|
||||
}
|
||||
return apiutil.publishGroup(group)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function extractStandardizableDevices(devices) {
|
||||
return dbapi.getTransientGroups().then(function(groups) {
|
||||
return Promise.map(devices, function(device) {
|
||||
return Promise.map(groups, function(group) {
|
||||
if (group.devices.indexOf(device.serial) > -1) {
|
||||
return Promise.reject('booked')
|
||||
}
|
||||
return true
|
||||
})
|
||||
.then(function() {
|
||||
return device
|
||||
})
|
||||
.catch(function(err) {
|
||||
if (err !== 'booked') {
|
||||
throw err
|
||||
}
|
||||
return err
|
||||
})
|
||||
})
|
||||
.then(function(devices) {
|
||||
return _.without(devices, 'booked')
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function getStandardizableDevices(req, res) {
|
||||
dbapi.loadDevicesByOrigin(req.user.groups.subscribed).then(function(devices) {
|
||||
extractStandardizableDevices(devices).then(function(devices) {
|
||||
filterGenericDevices(req, res, devices)
|
||||
})
|
||||
})
|
||||
.catch(function(err) {
|
||||
apiutil.internalError(res, 'Failed to load device list: ', err.stack)
|
||||
})
|
||||
}
|
||||
|
||||
function removeDevice(serial, req, res) {
|
||||
const presentState = req.swagger.params.present.value
|
||||
const bookingState = req.swagger.params.booked.value
|
||||
const notesState = req.swagger.params.annotated.value
|
||||
const controllingState = req.swagger.params.controlled.value
|
||||
const anyPresentState = typeof presentState === 'undefined'
|
||||
const anyBookingState = typeof bookingState === 'undefined'
|
||||
const anyNotesState = typeof notesState === 'undefined'
|
||||
const anyControllingState = typeof controllingState === 'undefined'
|
||||
const lock = {}
|
||||
|
||||
function deleteGroupDevice(email, id) {
|
||||
const lock = {}
|
||||
|
||||
return dbapi.lockGroupByOwner(email, id).then(function(stats) {
|
||||
if (!stats.replaced) {
|
||||
return apiutil.lightComputeStats(res, stats)
|
||||
}
|
||||
const group = lock.group = stats.changes[0].new_val
|
||||
|
||||
if (group.devices.indexOf(serial) > -1) {
|
||||
return apiutil.isOriginGroup(group.class) ?
|
||||
dbapi.removeOriginGroupDevice(group, serial) :
|
||||
dbapi.removeGroupDevices(group, [serial])
|
||||
}
|
||||
return group
|
||||
})
|
||||
.finally(function() {
|
||||
lockutil.unlockGroup(lock)
|
||||
})
|
||||
}
|
||||
|
||||
function deleteDeviceInDatabase() {
|
||||
function wrappedDeleteDeviceInDatabase() {
|
||||
const result = {
|
||||
status: false
|
||||
, data: 'not deleted'
|
||||
}
|
||||
|
||||
return dbapi.loadDeviceBySerial(serial).then(function(device) {
|
||||
if (device && device.group.id === device.group.origin) {
|
||||
return deleteGroupDevice(device.group.owner.email, device.group.id)
|
||||
.then(function(group) {
|
||||
if (group !== 'not found') {
|
||||
return dbapi.deleteDevice(serial).then(function() {
|
||||
result.status = true
|
||||
result.data = 'deleted'
|
||||
})
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
return false
|
||||
})
|
||||
.then(function() {
|
||||
return result
|
||||
})
|
||||
}
|
||||
return apiutil.setIntervalWrapper(
|
||||
wrappedDeleteDeviceInDatabase
|
||||
, 10
|
||||
, Math.random() * 500 + 50)
|
||||
}
|
||||
|
||||
return dbapi.lockDeviceByOrigin(req.user.groups.subscribed, serial).then(function(stats) {
|
||||
if (!stats.replaced) {
|
||||
return apiutil.lightComputeStats(res, stats)
|
||||
}
|
||||
const device = lock.device = stats.changes[0].new_val
|
||||
|
||||
if (!anyPresentState && device.present !== presentState ||
|
||||
!anyControllingState && (device.owner === null) === controllingState ||
|
||||
!anyNotesState &&
|
||||
(typeof device.notes !== 'undefined' && device.notes !== '') !== notesState ||
|
||||
!anyBookingState && (device.group.id !== device.group.origin && !bookingState ||
|
||||
device.group.class === apiutil.STANDARD && bookingState)) {
|
||||
return 'unchanged'
|
||||
}
|
||||
if (device.group.class === apiutil.STANDARD) {
|
||||
return deleteDeviceInDatabase()
|
||||
}
|
||||
return dbapi.getDeviceTransientGroups(serial).then(function(groups) {
|
||||
if (groups.length && !anyBookingState && !bookingState) {
|
||||
return 'unchanged'
|
||||
}
|
||||
return Promise.each(groups, function(group) {
|
||||
return deleteGroupDevice(group.owner.email, group.id)
|
||||
})
|
||||
.then(function() {
|
||||
if (!groups.length && !anyBookingState && bookingState) {
|
||||
return 'unchanged'
|
||||
}
|
||||
return deleteDeviceInDatabase()
|
||||
})
|
||||
})
|
||||
})
|
||||
.finally(function() {
|
||||
lockutil.unlockDevice(lock)
|
||||
})
|
||||
}
|
||||
|
||||
/* ------------------------------------ PUBLIC FUNCTIONS ------------------------------- */
|
||||
|
||||
function getDevices(req, res) {
|
||||
const target = req.swagger.params.target.value
|
||||
|
||||
switch(target) {
|
||||
case apiutil.BOOKABLE:
|
||||
getGenericDevices(req, res, dbapi.loadBookableDevices)
|
||||
break
|
||||
case apiutil.ORIGIN:
|
||||
getGenericDevices(req, res, dbapi.loadDevicesByOrigin)
|
||||
break
|
||||
case apiutil.STANDARD:
|
||||
getGenericDevices(req, res, dbapi.loadStandardDevices)
|
||||
break
|
||||
case apiutil.STANDARDIZABLE:
|
||||
getStandardizableDevices(req, res)
|
||||
break
|
||||
default:
|
||||
getGenericDevices(req, res, dbapi.loadDevices)
|
||||
}
|
||||
}
|
||||
|
||||
function getDeviceBySerial(req, res) {
|
||||
var serial = req.swagger.params.serial.value
|
||||
var fields = req.swagger.params.fields.value
|
||||
|
||||
dbapi.loadDevice(serial)
|
||||
.then(function(device) {
|
||||
if (!device) {
|
||||
return res.status(404).json({
|
||||
success: false
|
||||
, description: 'Device not found'
|
||||
dbapi.loadDevice(req.user.groups.subscribed, serial)
|
||||
.then(function(cursor) {
|
||||
cursor.next(function(err, device) {
|
||||
if (err) {
|
||||
return res.status(404).json({
|
||||
success: false
|
||||
, description: 'Device not found'
|
||||
})
|
||||
}
|
||||
let responseDevice = apiutil.publishDevice(device, req.user)
|
||||
|
||||
if (fields) {
|
||||
responseDevice = _.pick(device, fields.split(','))
|
||||
}
|
||||
res.json({
|
||||
success: true
|
||||
, device: responseDevice
|
||||
})
|
||||
}
|
||||
|
||||
datautil.normalize(device, req.user)
|
||||
var responseDevice = device
|
||||
|
||||
if (fields) {
|
||||
responseDevice = _.pick(device, fields.split(','))
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true
|
||||
, device: responseDevice
|
||||
})
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.error('Failed to load device "%s": ', req.params.serial, err.stack)
|
||||
log.error('Failed to load device "%s": ', serial, err.stack)
|
||||
res.status(500).json({
|
||||
success: false
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function getDeviceGroups(req, res) {
|
||||
const serial = req.swagger.params.serial.value
|
||||
const fields = req.swagger.params.fields.value
|
||||
|
||||
dbapi.loadDevice(req.user.groups.subscribed, serial).then(function(cursor) {
|
||||
return cursor.toArray()
|
||||
})
|
||||
.then(function(devices) {
|
||||
if (!devices.length) {
|
||||
apiutil.respond(res, 404, 'Not Found (device)')
|
||||
}
|
||||
else {
|
||||
getDeviceFilteredGroups(serial, fields, false)
|
||||
.then(function(groups) {
|
||||
return apiutil.respond(res, 200, 'Groups Information', {groups: groups})
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(function(err) {
|
||||
apiutil.internalError(res, 'Failed to get device groups: ', err.stack)
|
||||
})
|
||||
}
|
||||
|
||||
function getDeviceBookings(req, res) {
|
||||
const serial = req.swagger.params.serial.value
|
||||
const fields = req.swagger.params.fields.value
|
||||
|
||||
dbapi.loadDevice(req.user.groups.subscribed, serial).then(function(cursor) {
|
||||
return cursor.toArray()
|
||||
})
|
||||
.then(function(devices) {
|
||||
if (!devices.length) {
|
||||
apiutil.respond(res, 404, 'Not Found (device)')
|
||||
}
|
||||
else {
|
||||
getDeviceFilteredGroups(serial, fields, true)
|
||||
.then(function(bookings) {
|
||||
apiutil.respond(res, 200, 'Bookings Information', {bookings: bookings})
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(function(err) {
|
||||
apiutil.internalError(res, 'Failed to get device bookings: ', err.stack)
|
||||
})
|
||||
}
|
||||
|
||||
function addOriginGroupDevices(req, res) {
|
||||
const serials = apiutil.getBodyParameter(req.body, 'serials')
|
||||
const fields = apiutil.getQueryParameter(req.swagger.params.fields)
|
||||
const target = apiutil.getQueryParameter(req.swagger.params.redirected) ? 'device' : 'devices'
|
||||
const lock = {}
|
||||
|
||||
function askUpdateDeviceOriginGroup(group, serial) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
const signature = util.format('%s', uuid.v4()).replace(/-/g, '')
|
||||
let messageListener
|
||||
const responseTimer = setTimeout(function() {
|
||||
req.options.channelRouter.removeListener(wireutil.global, messageListener)
|
||||
apiutil.respond(res, 504, 'Gateway Time-out')
|
||||
reject('timeout')
|
||||
}, 5000)
|
||||
|
||||
messageListener = wirerouter()
|
||||
.on(wire.DeviceOriginGroupMessage, function(channel, message) {
|
||||
if (message.signature === signature) {
|
||||
clearTimeout(responseTimer)
|
||||
req.options.channelRouter.removeListener(wireutil.global, messageListener)
|
||||
dbapi.loadDeviceBySerial(serial).then(function(device) {
|
||||
if (fields) {
|
||||
resolve(_.pick(apiutil.publishDevice(device, req.user), fields.split(',')))
|
||||
}
|
||||
else {
|
||||
resolve(apiutil.publishDevice(device, req.user))
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
.handler()
|
||||
|
||||
req.options.channelRouter.on(wireutil.global, messageListener)
|
||||
return dbapi.askUpdateDeviceOriginGroup(serial, group, signature)
|
||||
})
|
||||
}
|
||||
|
||||
function updateDeviceOriginGroup(group, serial) {
|
||||
const lock = {}
|
||||
|
||||
return dbapi.lockDeviceByOrigin(req.user.groups.subscribed, serial).then(function(stats) {
|
||||
if (!stats.replaced) {
|
||||
return apiutil.lightComputeStats(res, stats)
|
||||
}
|
||||
lock.device = stats.changes[0].new_val
|
||||
|
||||
return dbapi.isUpdateDeviceOriginGroupAllowed(serial, group)
|
||||
.then(function(updatingAllowed) {
|
||||
if (!updatingAllowed) {
|
||||
apiutil.respond(res, 403, 'Forbidden (device is currently booked)')
|
||||
return Promise.reject('booked')
|
||||
}
|
||||
return askUpdateDeviceOriginGroup(group, serial)
|
||||
})
|
||||
})
|
||||
.finally(function() {
|
||||
lockutil.unlockDevice(lock)
|
||||
})
|
||||
}
|
||||
|
||||
function updateDevicesOriginGroup(group, serials) {
|
||||
let results = []
|
||||
|
||||
return Promise.each(serials, function(serial) {
|
||||
return updateDeviceOriginGroup(group, serial).then(function(result) {
|
||||
results.push(result)
|
||||
})
|
||||
})
|
||||
.then(function() {
|
||||
const result = target === 'device' ? {device: {}} : {devices: []}
|
||||
|
||||
results = _.without(results, 'unchanged')
|
||||
if (!results.length) {
|
||||
return apiutil.respond(res, 200, `Unchanged (${target})`, result)
|
||||
}
|
||||
results = _.without(results, 'not found')
|
||||
if (!results.length) {
|
||||
return apiutil.respond(res, 404, `Not Found (${target})`)
|
||||
}
|
||||
if (target === 'device') {
|
||||
result.device = results[0]
|
||||
}
|
||||
else {
|
||||
result.devices = results
|
||||
}
|
||||
return apiutil.respond(res, 200, `Updated (${target})`, result)
|
||||
})
|
||||
.catch(function(err) {
|
||||
if (err !== 'booked' && err !== 'timeout' && err !== 'busy') {
|
||||
throw err
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return lockutil.lockGroup(req, res, lock).then(function(lockingSuccessed) {
|
||||
if (lockingSuccessed) {
|
||||
const group = lock.group
|
||||
|
||||
if (!apiutil.isOriginGroup(group.class)) {
|
||||
return apiutil.respond(res, 400, 'Bad Request (this group cannot act as an origin one)')
|
||||
}
|
||||
if (typeof serials !== 'undefined') {
|
||||
return updateDevicesOriginGroup(
|
||||
group
|
||||
, _.without(serials.split(','), '').filter(function(serial) {
|
||||
return group.devices.indexOf(serial) < 0
|
||||
})
|
||||
)
|
||||
}
|
||||
return dbapi.loadDevicesByOrigin(req.user.groups.subscribed).then(function(devices) {
|
||||
if (group.class === apiutil.BOOKABLE) {
|
||||
return devices
|
||||
}
|
||||
return extractStandardizableDevices(devices)
|
||||
})
|
||||
.then(function(devices) {
|
||||
const serials = []
|
||||
|
||||
devices.forEach(function(device) {
|
||||
if (group.devices.indexOf(device.serial) < 0) {
|
||||
serials.push(device.serial)
|
||||
}
|
||||
})
|
||||
return updateDevicesOriginGroup(group, serials)
|
||||
})
|
||||
}
|
||||
return false
|
||||
})
|
||||
.catch(function(err) {
|
||||
apiutil.internalError(res, `Failed to update ${target} origin group: `, err.stack)
|
||||
})
|
||||
.finally(function() {
|
||||
lockutil.unlockGroup(lock)
|
||||
})
|
||||
}
|
||||
|
||||
function addOriginGroupDevice(req, res) {
|
||||
apiutil.redirectApiWrapper('serial', addOriginGroupDevices, req, res)
|
||||
}
|
||||
|
||||
function removeOriginGroupDevices(req, res) {
|
||||
const lock = {}
|
||||
|
||||
return lockutil.lockGroup(req, res, lock).then(function(lockingSuccessed) {
|
||||
if (lockingSuccessed) {
|
||||
const group = lock.group
|
||||
|
||||
if (!apiutil.checkBodyParameter(req.body, 'serials')) {
|
||||
req.body = {serials: group.devices.join()}
|
||||
}
|
||||
return dbapi.getRootGroup().then(function(group) {
|
||||
req.swagger.params.id = {value: group.id}
|
||||
return addOriginGroupDevices(req, res)
|
||||
})
|
||||
}
|
||||
return false
|
||||
})
|
||||
.finally(function() {
|
||||
lockutil.unlockGroup(lock)
|
||||
})
|
||||
}
|
||||
|
||||
function removeOriginGroupDevice(req, res) {
|
||||
apiutil.redirectApiWrapper('serial', removeOriginGroupDevices, req, res)
|
||||
}
|
||||
|
||||
function deleteDevices(req, res) {
|
||||
const serials = apiutil.getBodyParameter(req.body, 'serials')
|
||||
const target = apiutil.getQueryParameter(req.swagger.params.redirected) ? 'device' : 'devices'
|
||||
|
||||
function removeDevices(serials) {
|
||||
let results = []
|
||||
|
||||
return Promise.each(serials, function(serial) {
|
||||
return removeDevice(serial, req, res).then(function(result) {
|
||||
if (result === 'not deleted') {
|
||||
apiutil.respond(res, 503, 'Server too busy [code: 2], please try again later')
|
||||
return Promise.reject('busy')
|
||||
}
|
||||
return results.push(result)
|
||||
})
|
||||
})
|
||||
.then(function() {
|
||||
results = _.without(results, 'unchanged')
|
||||
if (!results.length) {
|
||||
return apiutil.respond(res, 200, `Unchanged (${target})`)
|
||||
}
|
||||
if (!_.without(results, 'not found').length) {
|
||||
return apiutil.respond(res, 404, `Not Found (${target})`)
|
||||
}
|
||||
return apiutil.respond(res, 200, `Deleted (${target})`)
|
||||
})
|
||||
.catch(function(err) {
|
||||
if (err !== 'busy') {
|
||||
throw err
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
(function() {
|
||||
if (typeof serials === 'undefined') {
|
||||
return dbapi.loadDevicesByOrigin(req.user.groups.subscribed).then(function(devices) {
|
||||
return removeDevices(devices.map(function(device) {
|
||||
return device.serial
|
||||
}))
|
||||
})
|
||||
}
|
||||
else {
|
||||
return removeDevices(_.without(serials.split(','), ''))
|
||||
}
|
||||
})()
|
||||
.catch(function(err) {
|
||||
apiutil.internalError(res, `Failed to delete ${target}: `, err.stack)
|
||||
})
|
||||
}
|
||||
|
||||
function deleteDevice(req, res) {
|
||||
apiutil.redirectApiWrapper('serial', deleteDevices, req, res)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getDevices: getDevices
|
||||
, getDeviceBySerial: getDeviceBySerial
|
||||
, getDeviceGroups: getDeviceGroups
|
||||
, getDeviceBookings: getDeviceBookings
|
||||
, addOriginGroupDevice: addOriginGroupDevice
|
||||
, addOriginGroupDevices: addOriginGroupDevices
|
||||
, removeOriginGroupDevice: removeOriginGroupDevice
|
||||
, removeOriginGroupDevices: removeOriginGroupDevices
|
||||
, deleteDevice: deleteDevice
|
||||
, deleteDevices: deleteDevices
|
||||
}
|
||||
|
|
931
lib/units/api/controllers/groups.js
Normal file
931
lib/units/api/controllers/groups.js
Normal file
|
@ -0,0 +1,931 @@
|
|||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
const _ = require('lodash')
|
||||
const dbapi = require('../../../db/api')
|
||||
const apiutil = require('../../../util/apiutil')
|
||||
const lockutil = require('../../../util/lockutil')
|
||||
const util = require('util')
|
||||
const uuid = require('uuid')
|
||||
const Promise = require('bluebird')
|
||||
const usersapi = require('./users')
|
||||
|
||||
/* ---------------------------------- PRIVATE FUNCTIONS --------------------------------- */
|
||||
|
||||
function groupApiWrapper(email, fn, req, res) {
|
||||
dbapi.loadUser(email).then(function(user) {
|
||||
if (!user) {
|
||||
apiutil.respond(res, 404, 'Not Found (user)')
|
||||
}
|
||||
else {
|
||||
req.user = user
|
||||
fn(req, res)
|
||||
}
|
||||
})
|
||||
.catch(function(err) {
|
||||
apiutil.internalError(res, 'Failed to wrap "%s": ', fn.name, err.stack)
|
||||
})
|
||||
}
|
||||
|
||||
function getDevice(req, serial) {
|
||||
return dbapi.loadDeviceBySerial(serial).then(function(device) {
|
||||
if (!device) {
|
||||
throw new Error(`Device not found: ${serial}`)
|
||||
}
|
||||
return apiutil.filterDevice(req, device)
|
||||
})
|
||||
}
|
||||
|
||||
function checkConflicts(id, devices, dates) {
|
||||
function computeConflicts(conflicts, liteGroup, otherGroup) {
|
||||
if (otherGroup.id !== liteGroup.id) {
|
||||
const devices = _.intersection(liteGroup.devices, otherGroup.devices)
|
||||
|
||||
if (devices.length) {
|
||||
for (let liteGroupDate of liteGroup.dates) {
|
||||
for (let otherGroupDate of otherGroup.dates) {
|
||||
if (liteGroupDate.start < otherGroupDate.stop &&
|
||||
liteGroupDate.stop > otherGroupDate.start) {
|
||||
conflicts.push({
|
||||
devices: devices
|
||||
, date: {
|
||||
start: new Date(
|
||||
Math.max(liteGroupDate.start.getTime()
|
||||
, otherGroupDate.start.getTime()))
|
||||
, stop: new Date(
|
||||
Math.min(liteGroupDate.stop.getTime()
|
||||
, otherGroupDate.stop.getTime()))
|
||||
}
|
||||
, group: otherGroup.name
|
||||
, owner: otherGroup.owner
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dbapi.getTransientGroups().then(function(groups) {
|
||||
const conflicts = []
|
||||
|
||||
groups.forEach(function(otherGroup) {
|
||||
computeConflicts(
|
||||
conflicts
|
||||
, {id: id, devices: devices, dates: dates}
|
||||
, otherGroup)
|
||||
})
|
||||
return conflicts
|
||||
})
|
||||
}
|
||||
|
||||
function checkSchedule(res, oldGroup, _class, email, repetitions, privilege, start, stop) {
|
||||
if (oldGroup && oldGroup.devices.length &&
|
||||
(apiutil.isOriginGroup(_class) && !apiutil.isOriginGroup(oldGroup.class) ||
|
||||
apiutil.isOriginGroup(oldGroup.class) && !apiutil.isOriginGroup(_class))) {
|
||||
return Promise.resolve(apiutil.respond(res, 403,
|
||||
'Forbidden (unauthorized class while device list is not empty)'))
|
||||
}
|
||||
if (apiutil.isAdminGroup(_class) && privilege === apiutil.USER) {
|
||||
return Promise.resolve(apiutil.respond(res, 403, 'Forbidden (unauthorized class)'))
|
||||
}
|
||||
if (isNaN(start.getTime())) {
|
||||
return Promise.resolve(apiutil.respond(res, 400, 'Bad Request (Invalid startTime format)'))
|
||||
}
|
||||
if (isNaN(stop.getTime())) {
|
||||
return Promise.resolve(apiutil.respond(res, 400, 'Bad Request (Invalid stopTime format)'))
|
||||
}
|
||||
if (start >= stop) {
|
||||
return Promise.resolve(
|
||||
apiutil.respond(res, 400, 'Bad Request (Invalid life time: startTime >= stopTime)'))
|
||||
}
|
||||
if ((stop - start) > apiutil.CLASS_DURATION[_class]) {
|
||||
return Promise.resolve(apiutil.respond(res, 400,
|
||||
'Bad Request (Invalid Life time & class combination: life time > class duration)'
|
||||
))
|
||||
}
|
||||
switch(_class) {
|
||||
case apiutil.BOOKABLE:
|
||||
case apiutil.STANDARD:
|
||||
case apiutil.ONCE:
|
||||
if (repetitions !== 0) {
|
||||
return Promise.resolve(
|
||||
apiutil.respond(res, 400, 'Bad Request (Invalid class & repetitions combination)'))
|
||||
}
|
||||
break
|
||||
default:
|
||||
if (repetitions === 0) {
|
||||
return Promise.resolve(
|
||||
apiutil.respond(res, 400, 'Bad Request (Invalid class & repetitions combination)'))
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
return dbapi.loadUser(email).then(function(owner) {
|
||||
if (repetitions > owner.groups.quotas.repetitions) {
|
||||
return apiutil.respond(res, 400, 'Bad Request (Invalid repetitions value)')
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
/* ---------------------------------- PUBLIC FUNCTIONS ------------------------------------- */
|
||||
|
||||
function addGroupDevices(req, res) {
|
||||
const id = req.swagger.params.id.value
|
||||
const serials = apiutil.getBodyParameter(req.body, 'serials')
|
||||
const target = apiutil.getQueryParameter(req.swagger.params.redirected) ? 'device' : 'devices'
|
||||
const lock = {}
|
||||
let email = null
|
||||
|
||||
function addGroupDevice(group, serial) {
|
||||
const lock = {}
|
||||
|
||||
return dbapi.lockBookableDevice(req.user.groups.subscribed, serial).then(function(stats) {
|
||||
if (!stats.replaced) {
|
||||
return apiutil.lightComputeStats(res, stats)
|
||||
}
|
||||
lock.device = stats.changes[0].new_val
|
||||
|
||||
return dbapi.lockGroup(lock.device.group.origin).then(function(stats) {
|
||||
if (!stats.replaced) {
|
||||
return apiutil.lightComputeStats(res, stats)
|
||||
}
|
||||
lock.group = {id: lock.device.group.origin}
|
||||
|
||||
return checkConflicts(id, [serial], group.dates).then(function(conflicts) {
|
||||
return conflicts.length ?
|
||||
Promise.reject(conflicts) :
|
||||
dbapi.addGroupDevices(group, [serial])
|
||||
})
|
||||
})
|
||||
.finally(function() {
|
||||
lockutil.unlockGroup(lock)
|
||||
})
|
||||
})
|
||||
.finally(function() {
|
||||
lockutil.unlockDevice(lock)
|
||||
})
|
||||
}
|
||||
|
||||
function _addGroupDevices(lockedGroup, serials) {
|
||||
let results = []
|
||||
let group = lockedGroup
|
||||
|
||||
return Promise.each(serials, function(serial) {
|
||||
return addGroupDevice(group, serial).then(function(result) {
|
||||
results.push(result)
|
||||
if (result.hasOwnProperty('id')) {
|
||||
group = result
|
||||
}
|
||||
})
|
||||
})
|
||||
.then(function() {
|
||||
results = _.without(results, 'unchanged')
|
||||
if (!results.length) {
|
||||
apiutil.respond(res, 200, `Unchanged (group ${target})`, {group: {}})
|
||||
}
|
||||
else {
|
||||
results = _.without(results, 'not found')
|
||||
if (!results.length) {
|
||||
apiutil.respond(res, 404, `Not Found (group ${target})`)
|
||||
}
|
||||
else {
|
||||
apiutil.respond(res, 200, `Added (group ${target})`
|
||||
, {group: apiutil.publishGroup(results[results.length - 1])})
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(function(err) {
|
||||
if (err === 'quota is reached') {
|
||||
apiutil.respond(res, 403, 'Forbidden (groups duration quota is reached)')
|
||||
}
|
||||
else if (Array.isArray(err)) {
|
||||
apiutil.respond(res, 409, 'Conflicts Information', {conflicts: err})
|
||||
}
|
||||
else if (err !== 'busy') {
|
||||
throw err
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
lockutil.lockGroup(req, res, lock).then(function(lockingSuccessed) {
|
||||
if (lockingSuccessed) {
|
||||
let group = lock.group
|
||||
|
||||
if (req.user.privilege === apiutil.ADMIN && req.user.email !== group.owner.email) {
|
||||
email = group.owner.email
|
||||
return false
|
||||
}
|
||||
if (apiutil.isOriginGroup(group.class)) {
|
||||
return apiutil.respond(res, 400, 'Bad Request (use admin API for bookable/standard groups)')
|
||||
}
|
||||
|
||||
return (function() {
|
||||
if (typeof serials === 'undefined') {
|
||||
return dbapi.loadBookableDevices(req.user.groups.subscribed).then(function(devices) {
|
||||
const serials = []
|
||||
|
||||
devices.forEach(function(device) {
|
||||
if (group.devices.indexOf(device.serial) < 0) {
|
||||
serials.push(device.serial)
|
||||
}
|
||||
})
|
||||
return _addGroupDevices(group, serials)
|
||||
})
|
||||
}
|
||||
else {
|
||||
return _addGroupDevices(
|
||||
group
|
||||
, _.difference(
|
||||
_.without(serials.split(','), '')
|
||||
, group.devices)
|
||||
)
|
||||
}
|
||||
})()
|
||||
}
|
||||
return false
|
||||
})
|
||||
.catch(function(err) {
|
||||
apiutil.internalError(res, `Failed to add group ${target}: `, err.stack)
|
||||
})
|
||||
.finally(function() {
|
||||
lockutil.unlockGroup(lock)
|
||||
if (email) {
|
||||
groupApiWrapper(email, addGroupDevices, req, res)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function addGroupDevice(req, res) {
|
||||
apiutil.redirectApiWrapper('serial', addGroupDevices, req, res)
|
||||
}
|
||||
|
||||
function removeGroupDevices(req, res) {
|
||||
const serials = apiutil.getBodyParameter(req.body, 'serials')
|
||||
const target = apiutil.getQueryParameter(req.swagger.params.redirected) ? 'device' : 'devices'
|
||||
const lock = {}
|
||||
|
||||
lockutil.lockGroup(req, res, lock).then(function(lockingSuccessed) {
|
||||
if (lockingSuccessed) {
|
||||
const group = lock.group
|
||||
|
||||
if (apiutil.isOriginGroup(group.class)) {
|
||||
return apiutil.respond(res, 400, 'Bad Request (use admin API for bookable/standard groups)')
|
||||
}
|
||||
let serialsToRemove = group.devices
|
||||
|
||||
if (typeof serials !== 'undefined') {
|
||||
serialsToRemove = _.without(serials.split(','), '')
|
||||
}
|
||||
if (!serialsToRemove.length) {
|
||||
return apiutil.respond(res, 200, `Unchanged (group ${target})`, {group: {}})
|
||||
}
|
||||
serialsToRemove = _.intersection(serialsToRemove, group.devices)
|
||||
if (!serialsToRemove.length) {
|
||||
return apiutil.respond(res, 404, `Not Found (group ${target})`)
|
||||
}
|
||||
return dbapi.removeGroupDevices(group, serialsToRemove).then(function(group) {
|
||||
apiutil.respond(res, 200, `Removed (group ${target})`, {group: apiutil.publishGroup(group)})
|
||||
})
|
||||
}
|
||||
return false
|
||||
})
|
||||
.catch(function(err) {
|
||||
apiutil.internalError(res, `Failed to remove group ${target}: `, err.stack)
|
||||
})
|
||||
.finally(function() {
|
||||
lockutil.unlockGroup(lock)
|
||||
})
|
||||
}
|
||||
|
||||
function removeGroupDevice(req, res) {
|
||||
apiutil.redirectApiWrapper('serial', removeGroupDevices, req, res)
|
||||
}
|
||||
|
||||
function getGroupDevice(req, res) {
|
||||
const id = req.swagger.params.id.value
|
||||
const serial = req.swagger.params.serial.value
|
||||
|
||||
dbapi.getUserGroup(req.user.email, id).then(function(group) {
|
||||
if (!group) {
|
||||
apiutil.respond(res, 404, 'Not Found (group)')
|
||||
}
|
||||
else if (group.devices.indexOf(serial) < 0) {
|
||||
apiutil.respond(res, 404, 'Not Found (device)')
|
||||
}
|
||||
else {
|
||||
getDevice(req, serial).then(function(device) {
|
||||
apiutil.respond(res, 200, 'Device Information', {device: device})
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(function(err) {
|
||||
apiutil.internalError(res, 'Failed to get group device: ', err.stack)
|
||||
})
|
||||
}
|
||||
|
||||
function getGroupUser(req, res) {
|
||||
const id = req.swagger.params.id.value
|
||||
const email = req.swagger.params.email.value
|
||||
|
||||
dbapi.getUserGroup(req.user.email, id).then(function(group) {
|
||||
if (!group) {
|
||||
apiutil.respond(res, 404, 'Not Found (group)')
|
||||
}
|
||||
else if (group.users.indexOf(email) < 0) {
|
||||
apiutil.respond(res, 404, 'Not Found (user)')
|
||||
}
|
||||
else {
|
||||
usersapi.getUserByEmail(req, res)
|
||||
}
|
||||
})
|
||||
.catch(function(err) {
|
||||
apiutil.internalError(res, 'Failed to get group user: ', err.stack)
|
||||
})
|
||||
}
|
||||
|
||||
function getGroupUsers(req, res) {
|
||||
const id = req.swagger.params.id.value
|
||||
|
||||
dbapi.getUserGroup(req.user.email, id).then(function(group) {
|
||||
if (!group) {
|
||||
apiutil.respond(res, 404, 'Not Found (group)')
|
||||
}
|
||||
else {
|
||||
Promise.map(group.users, function(email) {
|
||||
return usersapi.getUserInfo(req, email).then(function(user) {
|
||||
return user || Promise.reject(`Group user not found: ${email}`)
|
||||
})
|
||||
})
|
||||
.then(function(users) {
|
||||
apiutil.respond(res, 200, 'Users Information', {users: users})
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(function(err) {
|
||||
apiutil.internalError(res, 'Failed to get group users: ', err.stack)
|
||||
})
|
||||
}
|
||||
|
||||
function removeGroupUsers(req, res) {
|
||||
const id = req.swagger.params.id.value
|
||||
const emails = apiutil.getBodyParameter(req.body, 'emails')
|
||||
const target = apiutil.getQueryParameter(req.swagger.params.redirected) ? 'user' : 'users'
|
||||
const lock = {}
|
||||
|
||||
function removeGroupUser(email, group, rootGroup) {
|
||||
if (group.users.indexOf(email) < 0) {
|
||||
return Promise.resolve('not found')
|
||||
}
|
||||
if (email === rootGroup.owner.email || email === group.owner.email) {
|
||||
return Promise.resolve('forbidden')
|
||||
}
|
||||
const lock = {}
|
||||
|
||||
return dbapi.lockUser(email).then(function(stats) {
|
||||
if (!stats.replaced) {
|
||||
return apiutil.lightComputeStats(res, stats)
|
||||
}
|
||||
lock.user = stats.changes[0].new_val
|
||||
|
||||
return dbapi.isRemoveGroupUserAllowed(email, group)
|
||||
.then(function(isAllowed) {
|
||||
return isAllowed ? dbapi.removeGroupUser(id, email) : 'forbidden'
|
||||
})
|
||||
})
|
||||
.finally(function() {
|
||||
lockutil.unlockUser(lock)
|
||||
})
|
||||
}
|
||||
|
||||
lockutil.lockGroup(req, res, lock).then(function(lockingSuccessed) {
|
||||
if (lockingSuccessed) {
|
||||
const group = lock.group
|
||||
|
||||
return dbapi.getRootGroup().then(function(rootGroup) {
|
||||
let emailsToRemove = group.users
|
||||
let results = []
|
||||
|
||||
if (typeof emails !== 'undefined') {
|
||||
emailsToRemove = _.without(emails.split(','), '')
|
||||
}
|
||||
return Promise.each(emailsToRemove, function(email) {
|
||||
return removeGroupUser(email, group, rootGroup).then(function(result) {
|
||||
results.push(result)
|
||||
})
|
||||
})
|
||||
.then(function() {
|
||||
if (!results.length) {
|
||||
return apiutil.respond(res, 200, `Unchanged (group ${target})`, {group: {}})
|
||||
}
|
||||
results = _.without(results, 'not found')
|
||||
if (!results.length) {
|
||||
return apiutil.respond(res, 404, `Not Found (group ${target})`)
|
||||
}
|
||||
if (!_.without(results, 'forbidden').length) {
|
||||
return apiutil.respond(res, 403, `Forbidden (group ${target})`)
|
||||
}
|
||||
return dbapi.getGroup(id).then(function(group) {
|
||||
apiutil.respond(res, 200, `Removed (group ${target})`, {
|
||||
group: apiutil.publishGroup(group)})
|
||||
})
|
||||
})
|
||||
})
|
||||
.catch(function(err) {
|
||||
if (err !== 'busy') {
|
||||
throw err
|
||||
}
|
||||
})
|
||||
}
|
||||
return false
|
||||
})
|
||||
.catch(function(err) {
|
||||
apiutil.internalError(res, `Failed to remove group ${target}: `, err.stack)
|
||||
})
|
||||
.finally(function() {
|
||||
lockutil.unlockGroup(lock)
|
||||
})
|
||||
}
|
||||
|
||||
function removeGroupUser(req, res) {
|
||||
apiutil.redirectApiWrapper('email', removeGroupUsers, req, res)
|
||||
}
|
||||
|
||||
function addGroupUsers(req, res) {
|
||||
const id = req.swagger.params.id.value
|
||||
const emails = apiutil.getBodyParameter(req.body, 'emails')
|
||||
const target = apiutil.getQueryParameter(req.swagger.params.redirected) ? 'user' : 'users'
|
||||
const lock = {}
|
||||
|
||||
function addGroupUser(email) {
|
||||
const lock = {}
|
||||
|
||||
return dbapi.lockUser(email).then(function(stats) {
|
||||
if (!stats.replaced) {
|
||||
return apiutil.lightComputeStats(res, stats)
|
||||
}
|
||||
lock.user = stats.changes[0].new_val
|
||||
|
||||
return dbapi.addGroupUser(id, email)
|
||||
})
|
||||
.finally(function() {
|
||||
lockutil.unlockUser(lock)
|
||||
})
|
||||
}
|
||||
|
||||
function _addGroupUsers(emails) {
|
||||
let results = []
|
||||
|
||||
return Promise.each(emails, function(email) {
|
||||
return addGroupUser(email).then(function(result) {
|
||||
results.push(result)
|
||||
})
|
||||
})
|
||||
.then(function() {
|
||||
results = _.without(results, 'unchanged')
|
||||
if (!results.length) {
|
||||
return apiutil.respond(res, 200, `Unchanged (group ${target})`, {group: {}})
|
||||
}
|
||||
if (!_.without(results, 'not found').length) {
|
||||
return apiutil.respond(res, 404, `Not Found (group ${target})`)
|
||||
}
|
||||
return dbapi.getGroup(id).then(function(group) {
|
||||
apiutil.respond(res, 200, `Added (group ${target})`, {group: apiutil.publishGroup(group)})
|
||||
})
|
||||
})
|
||||
.catch(function(err) {
|
||||
if (err !== 'busy') {
|
||||
throw err
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
lockutil.lockGroup(req, res, lock).then(function(lockingSuccessed) {
|
||||
if (!lockingSuccessed) {
|
||||
return false
|
||||
}
|
||||
const group = lock.group
|
||||
|
||||
return (function() {
|
||||
if (typeof emails === 'undefined') {
|
||||
return dbapi.getUsers().then(function(users) {
|
||||
const emails = []
|
||||
|
||||
users.forEach(function(user) {
|
||||
if (group.users.indexOf(user.email) < 0) {
|
||||
emails.push(user.email)
|
||||
}
|
||||
})
|
||||
return _addGroupUsers(emails)
|
||||
})
|
||||
}
|
||||
else {
|
||||
return _addGroupUsers(
|
||||
_.difference(
|
||||
_.without(emails.split(','), '')
|
||||
, group.users)
|
||||
)
|
||||
}
|
||||
})()
|
||||
})
|
||||
.catch(function(err) {
|
||||
apiutil.internalError(res, `Failed to add group ${target}: `, err.stack)
|
||||
})
|
||||
.finally(function() {
|
||||
lockutil.unlockGroup(lock)
|
||||
})
|
||||
}
|
||||
|
||||
function addGroupUser(req, res) {
|
||||
apiutil.redirectApiWrapper('email', addGroupUsers, req, res)
|
||||
}
|
||||
|
||||
function getGroup(req, res) {
|
||||
const id = req.swagger.params.id.value
|
||||
const fields = req.swagger.params.fields.value
|
||||
|
||||
dbapi.getUserGroup(req.user.email, id).then(function(group) {
|
||||
if (!group) {
|
||||
apiutil.respond(res, 404, 'Not Found (group)')
|
||||
return
|
||||
}
|
||||
let publishedGroup = apiutil.publishGroup(group)
|
||||
|
||||
if (fields) {
|
||||
publishedGroup = _.pick(publishedGroup, fields.split(','))
|
||||
}
|
||||
apiutil.respond(res, 200, 'Group Information', {group: publishedGroup})
|
||||
})
|
||||
.catch(function(err) {
|
||||
apiutil.internalError(res, 'Failed to get group: ', err.stack)
|
||||
})
|
||||
}
|
||||
|
||||
function getGroups(req, res) {
|
||||
const fields = req.swagger.params.fields.value
|
||||
const owner = req.swagger.params.owner.value
|
||||
let getGenericGroups
|
||||
|
||||
switch(owner) {
|
||||
case true:
|
||||
getGenericGroups = dbapi.getOwnerGroups
|
||||
break
|
||||
case false:
|
||||
getGenericGroups = dbapi.getOnlyUserGroups
|
||||
break
|
||||
default:
|
||||
getGenericGroups = dbapi.getUserGroups
|
||||
}
|
||||
getGenericGroups(req.user.email).then(function(groups) {
|
||||
return apiutil.respond(res, 200, 'Groups Information', {
|
||||
groups: groups.map(function(group) {
|
||||
if (fields) {
|
||||
return _.pick(apiutil.publishGroup(group), fields.split(','))
|
||||
}
|
||||
return apiutil.publishGroup(group)
|
||||
})
|
||||
})
|
||||
})
|
||||
.catch(function(err) {
|
||||
apiutil.internalError(res, 'Failed to get groups: ', err.stack)
|
||||
})
|
||||
}
|
||||
|
||||
function createGroup(req, res) {
|
||||
const _class = typeof req.body.class === 'undefined' ? apiutil.ONCE : req.body.class
|
||||
const repetitions =
|
||||
apiutil.isOriginGroup(_class) || typeof req.body.repetitions === 'undefined' ?
|
||||
0 :
|
||||
req.body.repetitions
|
||||
const now = Date.now()
|
||||
const start =
|
||||
apiutil.isOriginGroup(_class) ?
|
||||
new Date(now) :
|
||||
new Date(req.body.startTime || now)
|
||||
const stop =
|
||||
apiutil.isOriginGroup(_class) ?
|
||||
new Date(now + apiutil.ONE_YEAR) :
|
||||
new Date(req.body.stopTime || now + apiutil.ONE_HOUR)
|
||||
|
||||
checkSchedule(res, null, _class, req.user.email, repetitions, req.user.privilege,
|
||||
start, stop).then(function(checkingSuccessed) {
|
||||
if (!checkingSuccessed) {
|
||||
return
|
||||
}
|
||||
const name =
|
||||
typeof req.body.name === 'undefined' ?
|
||||
'New_' + util.format('%s', uuid.v4()).replace(/-/g, '') :
|
||||
req.body.name
|
||||
const state =
|
||||
apiutil.isOriginGroup(_class) || typeof req.body.state === 'undefined' ?
|
||||
apiutil.READY :
|
||||
req.body.state
|
||||
const isActive = state === apiutil.READY && apiutil.isOriginGroup(_class)
|
||||
const duration = 0
|
||||
const dates = apiutil.computeGroupDates({start: start, stop: stop}, _class, repetitions)
|
||||
|
||||
dbapi.createUserGroup({
|
||||
name: name
|
||||
, owner: {
|
||||
email: req.user.email
|
||||
, name: req.user.name
|
||||
}
|
||||
, privilege: req.user.privilege
|
||||
, class: _class
|
||||
, repetitions: repetitions
|
||||
, isActive: isActive
|
||||
, dates: dates
|
||||
, duration: duration
|
||||
, state: state
|
||||
})
|
||||
.then(function(group) {
|
||||
if (group) {
|
||||
apiutil.respond(res, 201, 'Created', {group: apiutil.publishGroup(group)})
|
||||
}
|
||||
else {
|
||||
apiutil.respond(res, 403, 'Forbidden (groups number quota is reached)')
|
||||
}
|
||||
})
|
||||
.catch(function(err) {
|
||||
apiutil.internalError(res, 'Failed to create group: ', err.stack)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function deleteGroups(req, res) {
|
||||
const ids = apiutil.getBodyParameter(req.body, 'ids')
|
||||
const target = apiutil.getQueryParameter(req.swagger.params.redirected) ? 'group' : 'groups'
|
||||
|
||||
function removeGroup(id) {
|
||||
const lock = {}
|
||||
|
||||
return dbapi.lockGroupByOwner(req.user.email, id).then(function(stats) {
|
||||
if (!stats.replaced) {
|
||||
return apiutil.lightComputeStats(res, stats)
|
||||
}
|
||||
const group = lock.group = stats.changes[0].new_val
|
||||
|
||||
if (group.privilege === apiutil.ROOT) {
|
||||
return 'forbidden'
|
||||
}
|
||||
if (group.class === apiutil.BOOKABLE) {
|
||||
return Promise.each(group.devices, function(serial) {
|
||||
return dbapi.isDeviceBooked(serial)
|
||||
.then(function(isBooked) {
|
||||
return isBooked ? Promise.reject('booked') : true
|
||||
})
|
||||
})
|
||||
.then(function() {
|
||||
return dbapi.deleteUserGroup(id)
|
||||
})
|
||||
.catch(function(err) {
|
||||
if (err !== 'booked') {
|
||||
throw err
|
||||
}
|
||||
return 'forbidden'
|
||||
})
|
||||
}
|
||||
else {
|
||||
return dbapi.deleteUserGroup(id)
|
||||
}
|
||||
})
|
||||
.finally(function() {
|
||||
lockutil.unlockGroup(lock)
|
||||
})
|
||||
}
|
||||
|
||||
function removeGroups(ids) {
|
||||
let results = []
|
||||
|
||||
return Promise.each(ids, function(id) {
|
||||
return removeGroup(id).then(function(result) {
|
||||
results.push(result)
|
||||
})
|
||||
})
|
||||
.then(function() {
|
||||
if (!results.length) {
|
||||
return apiutil.respond(res, 200, `Unchanged (${target})`)
|
||||
}
|
||||
results = _.without(results, 'not found')
|
||||
if (!results.length) {
|
||||
return apiutil.respond(res, 404, `Not Found (${target})`)
|
||||
}
|
||||
results = _.without(results, 'forbidden')
|
||||
if (!results.length) {
|
||||
return apiutil.respond(res, 403, `Forbidden (${target})`)
|
||||
}
|
||||
return apiutil.respond(res, 200, `Deleted (${target})`)
|
||||
})
|
||||
.catch(function(err) {
|
||||
if (err !== 'busy') {
|
||||
throw err
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
(function() {
|
||||
if (typeof ids === 'undefined') {
|
||||
return dbapi.getOwnerGroups(req.user.email).then(function(groups) {
|
||||
const ids = []
|
||||
|
||||
groups.forEach(function(group) {
|
||||
if (group.privilege !== apiutil.ROOT) {
|
||||
ids.push(group.id)
|
||||
}
|
||||
})
|
||||
return removeGroups(ids)
|
||||
})
|
||||
}
|
||||
else {
|
||||
return removeGroups(_.without(ids.split(','), ''))
|
||||
}
|
||||
})()
|
||||
.catch(function(err) {
|
||||
apiutil.internalError(res, `Failed to delete ${target}: `, err.stack)
|
||||
})
|
||||
}
|
||||
|
||||
function deleteGroup(req, res) {
|
||||
apiutil.redirectApiWrapper('id', deleteGroups, req, res)
|
||||
}
|
||||
|
||||
function updateGroup(req, res) {
|
||||
const id = req.swagger.params.id.value
|
||||
const lock = {}
|
||||
|
||||
function updateUserGroup(group, data) {
|
||||
return dbapi.updateUserGroup(group, data)
|
||||
.then(function(group) {
|
||||
if (group) {
|
||||
apiutil.respond(res, 200, 'Updated (group)', {group: apiutil.publishGroup(group)})
|
||||
}
|
||||
else {
|
||||
apiutil.respond(res, 403, 'Forbidden (groups duration quota is reached)')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
lockutil.lockGroup(req, res, lock).then(function(lockingSuccessed) {
|
||||
if (!lockingSuccessed) {
|
||||
return false
|
||||
}
|
||||
const group = lock.group
|
||||
const _class = typeof req.body.class === 'undefined' ? group.class : req.body.class
|
||||
const name = typeof req.body.name === 'undefined' ? group.name : req.body.name
|
||||
const repetitions =
|
||||
typeof req.body.repetitions === 'undefined' ?
|
||||
group.repetitions :
|
||||
req.body.repetitions
|
||||
const start = new Date(req.body.startTime || group.dates[0].start)
|
||||
const stop = new Date(req.body.stopTime || group.dates[0].stop)
|
||||
let state, isActive
|
||||
|
||||
if (apiutil.isOriginGroup(_class)) {
|
||||
state = apiutil.READY
|
||||
isActive = true
|
||||
}
|
||||
else {
|
||||
state = typeof req.body.state === 'undefined' ? apiutil.PENDING : req.body.state
|
||||
isActive = false
|
||||
}
|
||||
|
||||
if (group.state === apiutil.READY && state === apiutil.PENDING) {
|
||||
return apiutil.respond(res, 403, 'Forbidden (group is ready)')
|
||||
}
|
||||
|
||||
return checkSchedule(res, group, _class, group.owner.email, repetitions, group.privilege,
|
||||
start, stop).then(function(checkingSuccessed) {
|
||||
if (!checkingSuccessed) {
|
||||
return false
|
||||
}
|
||||
if (name === group.name &&
|
||||
start.toISOString() === group.dates[0].start.toISOString() &&
|
||||
stop.toISOString() === group.dates[0].stop.toISOString() &&
|
||||
state === group.state &&
|
||||
_class === group.class &&
|
||||
repetitions === group.repetitions) {
|
||||
return apiutil.respond(res, 200, 'Unchanged (group)', {group: {}})
|
||||
}
|
||||
const duration = group.devices.length * (stop - start) * (repetitions + 1)
|
||||
const dates = apiutil.computeGroupDates({start: start, stop: stop}, _class, repetitions)
|
||||
|
||||
if (start < group.dates[0].start ||
|
||||
stop > group.dates[0].stop ||
|
||||
repetitions > group.repetitions ||
|
||||
_class !== group.class) {
|
||||
return checkConflicts(id, group.devices, dates)
|
||||
.then(function(conflicts) {
|
||||
if (!conflicts.length) {
|
||||
return updateUserGroup(group, {
|
||||
name: name
|
||||
, state: state
|
||||
, class: _class
|
||||
, isActive: isActive
|
||||
, repetitions: repetitions
|
||||
, dates: dates
|
||||
, duration: duration
|
||||
})
|
||||
}
|
||||
return apiutil.respond(res, 409, 'Conflicts Information', {conflicts: conflicts})
|
||||
})
|
||||
}
|
||||
return updateUserGroup(group, {
|
||||
name: name
|
||||
, state: state
|
||||
, class: _class
|
||||
, isActive: isActive
|
||||
, repetitions: repetitions
|
||||
, dates: dates
|
||||
, duration: duration
|
||||
})
|
||||
})
|
||||
})
|
||||
.catch(function(err) {
|
||||
apiutil.internalError(res, 'Failed to update group: ', err.stack)
|
||||
})
|
||||
.finally(function() {
|
||||
lockutil.unlockGroup(lock)
|
||||
})
|
||||
}
|
||||
|
||||
function getGroupDevices(req, res) {
|
||||
const id = req.swagger.params.id.value
|
||||
const bookable = req.swagger.params.bookable.value
|
||||
|
||||
dbapi.getUserGroup(req.user.email, id).then(function(group) {
|
||||
if (!group) {
|
||||
apiutil.respond(res, 404, 'Not Found (group)')
|
||||
return
|
||||
}
|
||||
if (bookable) {
|
||||
if (apiutil.isOriginGroup(group.class)) {
|
||||
apiutil.respond(res, 400, 'Bad Request (group is not transient)')
|
||||
return
|
||||
}
|
||||
if (req.user.privilege === apiutil.ADMIN && req.user.email !== group.owner.email) {
|
||||
groupApiWrapper(group.owner.email, getGroupDevices, req, res)
|
||||
return
|
||||
}
|
||||
dbapi.loadBookableDevices(req.user.groups.subscribed).then(function(devices) {
|
||||
Promise.map(devices, function(device) {
|
||||
return device.serial
|
||||
})
|
||||
.then(function(serials) {
|
||||
return checkConflicts(group.id, serials, group.dates)
|
||||
.then(function(conflicts) {
|
||||
let bookableSerials = serials
|
||||
|
||||
conflicts.forEach(function(conflict) {
|
||||
bookableSerials = _.difference(bookableSerials, conflict.devices)
|
||||
})
|
||||
return bookableSerials
|
||||
})
|
||||
})
|
||||
.then(function(bookableSerials) {
|
||||
const deviceList = []
|
||||
|
||||
devices.forEach(function(device) {
|
||||
if (bookableSerials.indexOf(device.serial) > -1) {
|
||||
deviceList.push(apiutil.filterDevice(req, device))
|
||||
}
|
||||
})
|
||||
apiutil.respond(res, 200, 'Devices Information', {devices: deviceList})
|
||||
})
|
||||
})
|
||||
}
|
||||
else {
|
||||
Promise.map(group.devices, function(serial) {
|
||||
return getDevice(req, serial)
|
||||
})
|
||||
.then(function(devices) {
|
||||
apiutil.respond(res, 200, 'Devices Information', {devices: devices})
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(function(err) {
|
||||
apiutil.internalError(res, 'Failed to get group devices: ', err.stack)
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createGroup: createGroup
|
||||
, updateGroup: updateGroup
|
||||
, deleteGroup: deleteGroup
|
||||
, deleteGroups: deleteGroups
|
||||
, getGroup: getGroup
|
||||
, getGroups: getGroups
|
||||
, getGroupUser: getGroupUser
|
||||
, getGroupUsers: getGroupUsers
|
||||
, addGroupUser: addGroupUser
|
||||
, addGroupUsers: addGroupUsers
|
||||
, removeGroupUser: removeGroupUser
|
||||
, removeGroupUsers: removeGroupUsers
|
||||
, getGroupDevice: getGroupDevice
|
||||
, getGroupDevices: getGroupDevices
|
||||
, addGroupDevice: addGroupDevice
|
||||
, addGroupDevices: addGroupDevices
|
||||
, removeGroupDevice: removeGroupDevice
|
||||
, removeGroupDevices: removeGroupDevices
|
||||
}
|
|
@ -1,3 +1,7 @@
|
|||
/**
|
||||
* Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
var util = require('util')
|
||||
|
||||
var _ = require('lodash')
|
||||
|
@ -12,6 +16,9 @@ var wire = require('../../../wire')
|
|||
var wireutil = require('../../../wire/util')
|
||||
var wirerouter = require('../../../wire/router')
|
||||
|
||||
const apiutil = require('../../../util/apiutil')
|
||||
const jwtutil = require('../../../util/jwtutil')
|
||||
|
||||
var log = logger.createLogger('api:controllers:user')
|
||||
|
||||
module.exports = {
|
||||
|
@ -24,9 +31,16 @@ module.exports = {
|
|||
, remoteDisconnectUserDeviceBySerial: remoteDisconnectUserDeviceBySerial
|
||||
, getUserAccessTokens: getUserAccessTokens
|
||||
, addAdbPublicKey: addAdbPublicKey
|
||||
, addUserDeviceV2: addUserDevice
|
||||
, getAccessTokens: getAccessTokens
|
||||
, getAccessToken: getAccessToken
|
||||
, createAccessToken: createAccessToken
|
||||
, deleteAccessToken: deleteAccessToken
|
||||
, deleteAccessTokens: deleteAccessTokens
|
||||
}
|
||||
|
||||
function getUser(req, res) {
|
||||
// delete req.user.groups.lock
|
||||
res.json({
|
||||
success: true
|
||||
, user: req.user
|
||||
|
@ -53,6 +67,7 @@ function getUserDevices(req, res) {
|
|||
|
||||
res.json({
|
||||
success: true
|
||||
, description: 'Controlled devices information'
|
||||
, devices: deviceList
|
||||
})
|
||||
})
|
||||
|
@ -61,6 +76,7 @@ function getUserDevices(req, res) {
|
|||
log.error('Failed to load device list: ', err.stack)
|
||||
res.status(500).json({
|
||||
success: false
|
||||
, description: 'Internal Server Error'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -69,113 +85,121 @@ function getUserDeviceBySerial(req, res) {
|
|||
var serial = req.swagger.params.serial.value
|
||||
var fields = req.swagger.params.fields.value
|
||||
|
||||
dbapi.loadDevice(serial)
|
||||
.then(function(device) {
|
||||
if (!device) {
|
||||
return res.status(404).json({
|
||||
success: false
|
||||
, description: 'Device not found'
|
||||
dbapi.loadDevice(req.user.groups.subscribed, serial)
|
||||
.then(function(cursor) {
|
||||
cursor.next(function(err, device) {
|
||||
if (err) {
|
||||
return res.status(404).json({
|
||||
success: false
|
||||
, description: 'Device not found'
|
||||
})
|
||||
}
|
||||
|
||||
datautil.normalize(device, req.user)
|
||||
if (!deviceutil.isOwnedByUser(device, req.user)) {
|
||||
return res.status(403).json({
|
||||
success: false
|
||||
, description: 'Device is not owned by you'
|
||||
})
|
||||
}
|
||||
|
||||
var responseDevice = device
|
||||
if (fields) {
|
||||
responseDevice = _.pick(device, fields.split(','))
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true
|
||||
, description: 'Controlled device information'
|
||||
, device: responseDevice
|
||||
})
|
||||
}
|
||||
|
||||
datautil.normalize(device, req.user)
|
||||
if (!deviceutil.isOwnedByUser(device, req.user)) {
|
||||
return res.status(403).json({
|
||||
success: false
|
||||
, description: 'Device is not owned by you'
|
||||
})
|
||||
}
|
||||
|
||||
var responseDevice = device
|
||||
if (fields) {
|
||||
responseDevice = _.pick(device, fields.split(','))
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true
|
||||
, device: responseDevice
|
||||
})
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.error('Failed to load device "%s": ', req.params.serial, err.stack)
|
||||
res.status(500).json({
|
||||
success: false
|
||||
, description: 'Internal Server Error'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function addUserDevice(req, res) {
|
||||
var serial = req.body.serial
|
||||
var timeout = req.body.timeout || null
|
||||
var serial = req.hasOwnProperty('body') ? req.body.serial : req.swagger.params.serial.value
|
||||
var timeout = req.hasOwnProperty('body') ? req.body.timeout ||
|
||||
null : req.swagger.params.timeout.value || null
|
||||
|
||||
dbapi.loadDevice(serial)
|
||||
.then(function(device) {
|
||||
if (!device) {
|
||||
return res.status(404).json({
|
||||
success: false
|
||||
, description: 'Device not found'
|
||||
})
|
||||
}
|
||||
|
||||
datautil.normalize(device, req.user)
|
||||
if (!deviceutil.isAddable(device, req.user)) {
|
||||
return res.status(403).json({
|
||||
success: false
|
||||
, description: 'Device is being used or not available'
|
||||
})
|
||||
}
|
||||
|
||||
// Timer will be called if no JoinGroupMessage is received till 5 seconds
|
||||
var responseTimer = setTimeout(function() {
|
||||
req.options.channelRouter.removeListener(wireutil.global, messageListener)
|
||||
return res.status(504).json({
|
||||
dbapi.loadDevice(req.user.groups.subscribed, serial)
|
||||
.then(function(cursor) {
|
||||
cursor.next(function(err, device) {
|
||||
if (err) {
|
||||
return res.status(404).json({
|
||||
success: false
|
||||
, description: 'Device is not responding'
|
||||
})
|
||||
}, 5000)
|
||||
, description: 'Device not found'
|
||||
})
|
||||
}
|
||||
|
||||
var messageListener = wirerouter()
|
||||
.on(wire.JoinGroupMessage, function(channel, message) {
|
||||
if (message.serial === serial && message.owner.email === req.user.email) {
|
||||
clearTimeout(responseTimer)
|
||||
req.options.channelRouter.removeListener(wireutil.global, messageListener)
|
||||
datautil.normalize(device, req.user)
|
||||
if (!deviceutil.isAddable(device, req.user)) {
|
||||
return res.status(403).json({
|
||||
success: false
|
||||
, description: 'Device is being used or not available'
|
||||
})
|
||||
}
|
||||
|
||||
return res.json({
|
||||
success: true
|
||||
, description: 'Device successfully added'
|
||||
})
|
||||
}
|
||||
})
|
||||
.handler()
|
||||
// Timer will be called if no JoinGroupMessage is received till 5 seconds
|
||||
var responseTimer = setTimeout(function() {
|
||||
req.options.channelRouter.removeListener(wireutil.global, messageListener)
|
||||
return res.status(504).json({
|
||||
success: false
|
||||
, description: 'Device is not responding'
|
||||
})
|
||||
}, 5000)
|
||||
|
||||
req.options.channelRouter.on(wireutil.global, messageListener)
|
||||
var usage = 'automation'
|
||||
var messageListener = wirerouter()
|
||||
.on(wire.JoinGroupMessage, function(channel, message) {
|
||||
if (message.serial === serial && message.owner.email === req.user.email) {
|
||||
clearTimeout(responseTimer)
|
||||
req.options.channelRouter.removeListener(wireutil.global, messageListener)
|
||||
|
||||
req.options.push.send([
|
||||
device.channel
|
||||
, wireutil.envelope(
|
||||
new wire.GroupMessage(
|
||||
new wire.OwnerMessage(
|
||||
req.user.email
|
||||
, req.user.name
|
||||
, req.user.group
|
||||
)
|
||||
, timeout
|
||||
, wireutil.toDeviceRequirements({
|
||||
serial: {
|
||||
value: serial
|
||||
, match: 'exact'
|
||||
return res.json({
|
||||
success: true
|
||||
, description: 'Device successfully added'
|
||||
})
|
||||
}
|
||||
})
|
||||
, usage
|
||||
.handler()
|
||||
|
||||
req.options.channelRouter.on(wireutil.global, messageListener)
|
||||
var usage = 'automation'
|
||||
|
||||
req.options.push.send([
|
||||
device.channel
|
||||
, wireutil.envelope(
|
||||
new wire.GroupMessage(
|
||||
new wire.OwnerMessage(
|
||||
req.user.email
|
||||
, req.user.name
|
||||
, req.user.group
|
||||
)
|
||||
, timeout
|
||||
, wireutil.toDeviceRequirements({
|
||||
serial: {
|
||||
value: serial
|
||||
, match: 'exact'
|
||||
}
|
||||
})
|
||||
, usage
|
||||
)
|
||||
)
|
||||
)
|
||||
])
|
||||
])
|
||||
})
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.error('Failed to load device "%s": ', req.params.serial, err.stack)
|
||||
res.status(500).json({
|
||||
success: false
|
||||
, description: 'Internal Server Error'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -183,66 +207,70 @@ function addUserDevice(req, res) {
|
|||
function deleteUserDeviceBySerial(req, res) {
|
||||
var serial = req.swagger.params.serial.value
|
||||
|
||||
dbapi.loadDevice(serial)
|
||||
.then(function(device) {
|
||||
if (!device) {
|
||||
return res.status(404).json({
|
||||
success: false
|
||||
, description: 'Device not found'
|
||||
})
|
||||
}
|
||||
|
||||
datautil.normalize(device, req.user)
|
||||
if (!deviceutil.isOwnedByUser(device, req.user)) {
|
||||
return res.status(403).json({
|
||||
success: false
|
||||
, description: 'You cannot release this device. Not owned by you'
|
||||
})
|
||||
}
|
||||
|
||||
// Timer will be called if no JoinGroupMessage is received till 5 seconds
|
||||
var responseTimer = setTimeout(function() {
|
||||
req.options.channelRouter.removeListener(wireutil.global, messageListener)
|
||||
return res.status(504).json({
|
||||
dbapi.loadDevice(req.user.groups.subscribed, serial)
|
||||
.then(function(cursor) {
|
||||
cursor.next(function(err, device) {
|
||||
if (err) {
|
||||
return res.status(404).json({
|
||||
success: false
|
||||
, description: 'Device is not responding'
|
||||
})
|
||||
}, 5000)
|
||||
, description: 'Device not found'
|
||||
})
|
||||
}
|
||||
|
||||
var messageListener = wirerouter()
|
||||
.on(wire.LeaveGroupMessage, function(channel, message) {
|
||||
if (message.serial === serial && message.owner.email === req.user.email) {
|
||||
clearTimeout(responseTimer)
|
||||
req.options.channelRouter.removeListener(wireutil.global, messageListener)
|
||||
datautil.normalize(device, req.user)
|
||||
if (!deviceutil.isOwnedByUser(device, req.user)) {
|
||||
return res.status(403).json({
|
||||
success: false
|
||||
, description: 'You cannot release this device. Not owned by you'
|
||||
})
|
||||
}
|
||||
|
||||
return res.json({
|
||||
success: true
|
||||
, description: 'Device successfully removed'
|
||||
})
|
||||
}
|
||||
})
|
||||
.handler()
|
||||
// Timer will be called if no JoinGroupMessage is received till 5 seconds
|
||||
var responseTimer = setTimeout(function() {
|
||||
req.options.channelRouter.removeListener(wireutil.global, messageListener)
|
||||
return res.status(504).json({
|
||||
success: false
|
||||
, description: 'Device is not responding'
|
||||
})
|
||||
}, 5000)
|
||||
|
||||
req.options.channelRouter.on(wireutil.global, messageListener)
|
||||
var messageListener = wirerouter()
|
||||
.on(wire.LeaveGroupMessage, function(channel, message) {
|
||||
if (message.serial === serial &&
|
||||
(message.owner.email === req.user.email || req.user.privilege === 'admin')) {
|
||||
clearTimeout(responseTimer)
|
||||
req.options.channelRouter.removeListener(wireutil.global, messageListener)
|
||||
|
||||
req.options.push.send([
|
||||
device.channel
|
||||
, wireutil.envelope(
|
||||
new wire.UngroupMessage(
|
||||
wireutil.toDeviceRequirements({
|
||||
serial: {
|
||||
value: serial
|
||||
, match: 'exact'
|
||||
}
|
||||
})
|
||||
return res.json({
|
||||
success: true
|
||||
, description: 'Device successfully removed'
|
||||
})
|
||||
}
|
||||
})
|
||||
.handler()
|
||||
|
||||
req.options.channelRouter.on(wireutil.global, messageListener)
|
||||
|
||||
req.options.push.send([
|
||||
device.channel
|
||||
, wireutil.envelope(
|
||||
new wire.UngroupMessage(
|
||||
wireutil.toDeviceRequirements({
|
||||
serial: {
|
||||
value: serial
|
||||
, match: 'exact'
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
])
|
||||
])
|
||||
})
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.error('Failed to load device "%s": ', req.params.serial, err.stack)
|
||||
res.status(500).json({
|
||||
success: false
|
||||
, description: 'Internal Server Error'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -250,65 +278,68 @@ function deleteUserDeviceBySerial(req, res) {
|
|||
function remoteConnectUserDeviceBySerial(req, res) {
|
||||
var serial = req.swagger.params.serial.value
|
||||
|
||||
dbapi.loadDevice(serial)
|
||||
.then(function(device) {
|
||||
if (!device) {
|
||||
return res.status(404).json({
|
||||
success: false
|
||||
, description: 'Device not found'
|
||||
})
|
||||
}
|
||||
|
||||
datautil.normalize(device, req.user)
|
||||
if (!deviceutil.isOwnedByUser(device, req.user)) {
|
||||
return res.status(403).json({
|
||||
success: false
|
||||
, description: 'Device is not owned by you or is not available'
|
||||
})
|
||||
}
|
||||
|
||||
var responseChannel = 'txn_' + uuid.v4()
|
||||
req.options.sub.subscribe(responseChannel)
|
||||
|
||||
// Timer will be called if no JoinGroupMessage is received till 5 seconds
|
||||
var timer = setTimeout(function() {
|
||||
req.options.channelRouter.removeListener(responseChannel, messageListener)
|
||||
req.options.sub.unsubscribe(responseChannel)
|
||||
return res.status(504).json({
|
||||
dbapi.loadDevice(req.user.groups.subscribed, serial)
|
||||
.then(function(cursor) {
|
||||
cursor.next(function(err, device) {
|
||||
if (err) {
|
||||
return res.status(404).json({
|
||||
success: false
|
||||
, description: 'Device is not responding'
|
||||
})
|
||||
}, 5000)
|
||||
, description: 'Device not found'
|
||||
})
|
||||
}
|
||||
|
||||
var messageListener = wirerouter()
|
||||
.on(wire.ConnectStartedMessage, function(channel, message) {
|
||||
if (message.serial === serial) {
|
||||
clearTimeout(timer)
|
||||
req.options.sub.unsubscribe(responseChannel)
|
||||
req.options.channelRouter.removeListener(responseChannel, messageListener)
|
||||
datautil.normalize(device, req.user)
|
||||
if (!deviceutil.isOwnedByUser(device, req.user)) {
|
||||
return res.status(403).json({
|
||||
success: false
|
||||
, description: 'Device is not owned by you or is not available'
|
||||
})
|
||||
}
|
||||
|
||||
return res.json({
|
||||
success: true
|
||||
, remoteConnectUrl: message.url
|
||||
})
|
||||
}
|
||||
})
|
||||
.handler()
|
||||
var responseChannel = 'txn_' + uuid.v4()
|
||||
req.options.sub.subscribe(responseChannel)
|
||||
|
||||
req.options.channelRouter.on(responseChannel, messageListener)
|
||||
// Timer will be called if no JoinGroupMessage is received till 5 seconds
|
||||
var timer = setTimeout(function() {
|
||||
req.options.channelRouter.removeListener(responseChannel, messageListener)
|
||||
req.options.sub.unsubscribe(responseChannel)
|
||||
return res.status(504).json({
|
||||
success: false
|
||||
, description: 'Device is not responding'
|
||||
})
|
||||
}, 5000)
|
||||
|
||||
req.options.push.send([
|
||||
device.channel
|
||||
, wireutil.transaction(
|
||||
responseChannel
|
||||
, new wire.ConnectStartMessage()
|
||||
)
|
||||
])
|
||||
var messageListener = wirerouter()
|
||||
.on(wire.ConnectStartedMessage, function(channel, message) {
|
||||
if (message.serial === serial) {
|
||||
clearTimeout(timer)
|
||||
req.options.sub.unsubscribe(responseChannel)
|
||||
req.options.channelRouter.removeListener(responseChannel, messageListener)
|
||||
return res.json({
|
||||
success: true
|
||||
, description: 'Remote connection is enabled'
|
||||
, remoteConnectUrl: message.url
|
||||
})
|
||||
}
|
||||
})
|
||||
.handler()
|
||||
|
||||
req.options.channelRouter.on(responseChannel, messageListener)
|
||||
|
||||
req.options.push.send([
|
||||
device.channel
|
||||
, wireutil.transaction(
|
||||
responseChannel
|
||||
, new wire.ConnectStartMessage()
|
||||
)
|
||||
])
|
||||
})
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.error('Failed to load device "%s": ', req.params.serial, err.stack)
|
||||
res.status(500).json({
|
||||
success: false
|
||||
, description: 'Internal Server Error'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -316,65 +347,67 @@ function remoteConnectUserDeviceBySerial(req, res) {
|
|||
function remoteDisconnectUserDeviceBySerial(req, res) {
|
||||
var serial = req.swagger.params.serial.value
|
||||
|
||||
dbapi.loadDevice(serial)
|
||||
.then(function(device) {
|
||||
if (!device) {
|
||||
return res.status(404).json({
|
||||
success: false
|
||||
, description: 'Device not found'
|
||||
})
|
||||
}
|
||||
dbapi.loadDevice(req.user.groups.subscribed, serial)
|
||||
.then(function(cursor) {
|
||||
cursor.next(function(err, device) {
|
||||
if (err) {
|
||||
return res.status(404).json({
|
||||
success: false
|
||||
, description: 'Device not found'
|
||||
})
|
||||
}
|
||||
|
||||
datautil.normalize(device, req.user)
|
||||
if (!deviceutil.isOwnedByUser(device, req.user)) {
|
||||
return res.status(403).json({
|
||||
success: false
|
||||
, description: 'Device is not owned by you or is not available'
|
||||
})
|
||||
}
|
||||
datautil.normalize(device, req.user)
|
||||
if (!deviceutil.isOwnedByUser(device, req.user)) {
|
||||
return res.status(403).json({
|
||||
success: false
|
||||
, description: 'Device is not owned by you or is not available'
|
||||
})
|
||||
}
|
||||
|
||||
var responseChannel = 'txn_' + uuid.v4()
|
||||
req.options.sub.subscribe(responseChannel)
|
||||
var responseChannel = 'txn_' + uuid.v4()
|
||||
req.options.sub.subscribe(responseChannel)
|
||||
|
||||
// Timer will be called if no JoinGroupMessage is received till 5 seconds
|
||||
var timer = setTimeout(function() {
|
||||
req.options.channelRouter.removeListener(responseChannel, messageListener)
|
||||
req.options.sub.unsubscribe(responseChannel)
|
||||
return res.status(504).json({
|
||||
// Timer will be called if no JoinGroupMessage is received till 5 seconds
|
||||
var timer = setTimeout(function() {
|
||||
req.options.channelRouter.removeListener(responseChannel, messageListener)
|
||||
req.options.sub.unsubscribe(responseChannel)
|
||||
return res.status(504).json({
|
||||
success: false
|
||||
, description: 'Device is not responding'
|
||||
})
|
||||
}, 5000)
|
||||
})
|
||||
}, 5000)
|
||||
|
||||
var messageListener = wirerouter()
|
||||
.on(wire.ConnectStoppedMessage, function(channel, message) {
|
||||
if (message.serial === serial) {
|
||||
clearTimeout(timer)
|
||||
req.options.sub.unsubscribe(responseChannel)
|
||||
req.options.channelRouter.removeListener(responseChannel, messageListener)
|
||||
var messageListener = wirerouter()
|
||||
.on(wire.ConnectStoppedMessage, function(channel, message) {
|
||||
if (message.serial === serial) {
|
||||
clearTimeout(timer)
|
||||
req.options.sub.unsubscribe(responseChannel)
|
||||
req.options.channelRouter.removeListener(responseChannel, messageListener)
|
||||
return res.json({
|
||||
success: true
|
||||
, description: 'Device remote disconnected successfully'
|
||||
})
|
||||
}
|
||||
})
|
||||
.handler()
|
||||
|
||||
return res.json({
|
||||
success: true
|
||||
, description: 'Device remote disconnected successfully'
|
||||
})
|
||||
}
|
||||
})
|
||||
.handler()
|
||||
req.options.channelRouter.on(responseChannel, messageListener)
|
||||
|
||||
req.options.channelRouter.on(responseChannel, messageListener)
|
||||
|
||||
req.options.push.send([
|
||||
device.channel
|
||||
, wireutil.transaction(
|
||||
responseChannel
|
||||
, new wire.ConnectStopMessage()
|
||||
)
|
||||
])
|
||||
req.options.push.send([
|
||||
device.channel
|
||||
, wireutil.transaction(
|
||||
responseChannel
|
||||
, new wire.ConnectStopMessage()
|
||||
)
|
||||
])
|
||||
})
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.error('Failed to load device "%s": ', req.params.serial, err.stack)
|
||||
res.status(500).json({
|
||||
success: false
|
||||
, description: 'Internal Server Error'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -454,3 +487,111 @@ function addAdbPublicKey(req, res) {
|
|||
})
|
||||
})
|
||||
}
|
||||
|
||||
function getAccessToken(req, res) {
|
||||
const id = req.swagger.params.id.value
|
||||
|
||||
dbapi.loadAccessToken(id).then(function(token) {
|
||||
if (!token || token.email !== req.user.email) {
|
||||
apiutil.respond(res, 404, 'Not Found (access token)')
|
||||
}
|
||||
else {
|
||||
apiutil.respond(res, 200, 'Access Token Information', {
|
||||
token: apiutil.publishAccessToken(token)
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(function(err) {
|
||||
apiutil.internalError(res, 'Failed to delete access token "%s": ', id, err.stack)
|
||||
})
|
||||
}
|
||||
|
||||
function getAccessTokens(req, res) {
|
||||
dbapi.loadAccessTokens(req.user.email).then(function(cursor) {
|
||||
Promise.promisify(cursor.toArray, cursor)().then(function(tokens) {
|
||||
const tokenList = []
|
||||
|
||||
tokens.forEach(function(token) {
|
||||
tokenList.push(apiutil.publishAccessToken(token))
|
||||
})
|
||||
apiutil.respond(res, 200, 'Access Tokens Information', {tokens: tokenList})
|
||||
})
|
||||
})
|
||||
.catch(function(err) {
|
||||
apiutil.internalError(res, 'Failed to get access tokens: ', err.stack)
|
||||
})
|
||||
}
|
||||
|
||||
function createAccessToken(req, res) {
|
||||
const title = req.swagger.params.title.value
|
||||
const jwt = jwtutil.encode({
|
||||
payload: {
|
||||
email: req.user.email
|
||||
, name: req.user.name
|
||||
}
|
||||
, secret: req.options.secret
|
||||
})
|
||||
const id = util.format('%s-%s', uuid.v4(), uuid.v4()).replace(/-/g, '')
|
||||
|
||||
dbapi.saveUserAccessToken(req.user.email, {
|
||||
title: title
|
||||
, id: id
|
||||
, jwt: jwt
|
||||
})
|
||||
.then(function(stats) {
|
||||
req.options.pushdev.send([
|
||||
req.user.group
|
||||
, wireutil.envelope(new wire.UpdateAccessTokenMessage())
|
||||
])
|
||||
apiutil.respond(res, 201, 'Created (access token)',
|
||||
{token: apiutil.publishAccessToken(stats.changes[0].new_val)})
|
||||
})
|
||||
.catch(function(err) {
|
||||
apiutil.internalError(res, 'Failed to create access token "%s": ', title, err.stack)
|
||||
})
|
||||
}
|
||||
|
||||
function deleteAccessTokens(req, res) {
|
||||
dbapi.removeUserAccessTokens(req.user.email).then(function(stats) {
|
||||
if (!stats.deleted) {
|
||||
apiutil.respond(res, 200, 'Unchanged (access tokens)')
|
||||
}
|
||||
else {
|
||||
req.options.pushdev.send([
|
||||
req.user.group
|
||||
, wireutil.envelope(new wire.UpdateAccessTokenMessage())
|
||||
])
|
||||
apiutil.respond(res, 200, 'Deleted (access tokens)')
|
||||
}
|
||||
})
|
||||
.catch(function(err) {
|
||||
apiutil.internalError(res, 'Failed to delete access tokens: ', err.stack)
|
||||
})
|
||||
}
|
||||
|
||||
function deleteAccessToken(req, res) {
|
||||
const id = req.swagger.params.id.value
|
||||
|
||||
dbapi.loadAccessToken(id).then(function(token) {
|
||||
if (!token || token.email !== req.user.email) {
|
||||
apiutil.respond(res, 404, 'Not Found (access token)')
|
||||
}
|
||||
else {
|
||||
dbapi.removeAccessToken(id).then(function(stats) {
|
||||
if (!stats.deleted) {
|
||||
apiutil.respond(res, 404, 'Not Found (access token)')
|
||||
}
|
||||
else {
|
||||
req.options.pushdev.send([
|
||||
req.user.group
|
||||
, wireutil.envelope(new wire.UpdateAccessTokenMessage())
|
||||
])
|
||||
apiutil.respond(res, 200, 'Deleted (access token)')
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(function(err) {
|
||||
apiutil.internalError(res, 'Failed to delete access token "%s": ', id, err.stack)
|
||||
})
|
||||
}
|
||||
|
|
398
lib/units/api/controllers/users.js
Normal file
398
lib/units/api/controllers/users.js
Normal file
|
@ -0,0 +1,398 @@
|
|||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
const dbapi = require('../../../db/api')
|
||||
const _ = require('lodash')
|
||||
const apiutil = require('../../../util/apiutil')
|
||||
const lockutil = require('../../../util/lockutil')
|
||||
const Promise = require('bluebird')
|
||||
const wire = require('../../../wire')
|
||||
const wireutil = require('../../../wire/util')
|
||||
const userapi = require('./user')
|
||||
|
||||
/* --------------------------------- PRIVATE FUNCTIONS --------------------------------------- */
|
||||
|
||||
function userApiWrapper(fn, req, res) {
|
||||
const email = req.swagger.params.email.value
|
||||
|
||||
dbapi.loadUser(email).then(function(user) {
|
||||
if (!user) {
|
||||
apiutil.respond(res, 404, 'Not Found (user)')
|
||||
}
|
||||
else {
|
||||
req.user = user
|
||||
fn(req, res)
|
||||
}
|
||||
})
|
||||
.catch(function(err) {
|
||||
apiutil.internalError(res, 'Failed to wrap "%s": ', fn.name, err.stack)
|
||||
})
|
||||
}
|
||||
|
||||
function getPublishedUser(user, userEmail, adminEmail, fields) {
|
||||
let publishedUser = apiutil.publishUser(user)
|
||||
if (userEmail !== adminEmail) {
|
||||
publishedUser = _.pick(user, 'email', 'name', 'privilege')
|
||||
}
|
||||
if (fields) {
|
||||
publishedUser = _.pick(publishedUser, fields.split(','))
|
||||
}
|
||||
return publishedUser
|
||||
}
|
||||
|
||||
function removeUser(email, req, res) {
|
||||
const groupOwnerState = req.swagger.params.groupOwner.value
|
||||
const anyGroupOwnerState = typeof groupOwnerState === 'undefined'
|
||||
const lock = {}
|
||||
|
||||
function removeGroupUser(owner, id) {
|
||||
const lock = {}
|
||||
|
||||
return dbapi.lockGroupByOwner(owner, id).then(function(stats) {
|
||||
if (!stats.replaced) {
|
||||
return apiutil.lightComputeStats(res, stats)
|
||||
}
|
||||
lock.group = stats.changes[0].new_val
|
||||
|
||||
return owner === email ?
|
||||
dbapi.deleteUserGroup(id) :
|
||||
dbapi.removeGroupUser(id, email)
|
||||
})
|
||||
.finally(function() {
|
||||
lockutil.unlockGroup(lock)
|
||||
})
|
||||
}
|
||||
|
||||
function deleteUserInDatabase(channel) {
|
||||
return dbapi.removeUserAccessTokens(email).then(function() {
|
||||
return dbapi.deleteUser(email).then(function() {
|
||||
req.options.pushdev.send([
|
||||
channel
|
||||
, wireutil.envelope(new wire.DeleteUserMessage(
|
||||
email
|
||||
))
|
||||
])
|
||||
return 'deleted'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function computeUserGroupOwnership(groups) {
|
||||
if (anyGroupOwnerState) {
|
||||
return Promise.resolve(true)
|
||||
}
|
||||
return Promise.map(groups, function(group) {
|
||||
if (!groupOwnerState && group.owner.email === email) {
|
||||
return Promise.reject('filtered')
|
||||
}
|
||||
return !groupOwnerState || group.owner.email === email
|
||||
})
|
||||
.then(function(results) {
|
||||
return _.without(results, false).length > 0
|
||||
})
|
||||
.catch(function(err) {
|
||||
if (err === 'filtered') {
|
||||
return false
|
||||
}
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
if (req.user.email === email) {
|
||||
return Promise.resolve('forbidden')
|
||||
}
|
||||
return dbapi.lockUser(email).then(function(stats) {
|
||||
if (!stats.replaced) {
|
||||
return apiutil.lightComputeStats(res, stats)
|
||||
}
|
||||
const user = lock.user = stats.changes[0].new_val
|
||||
|
||||
return dbapi.getGroupsByUser(user.email).then(function(groups) {
|
||||
return computeUserGroupOwnership(groups).then(function(doContinue) {
|
||||
if (!doContinue) {
|
||||
return 'unchanged'
|
||||
}
|
||||
return Promise.each(groups, function(group) {
|
||||
return removeGroupUser(group.owner.email, group.id)
|
||||
})
|
||||
.then(function() {
|
||||
return deleteUserInDatabase(user.group)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
.finally(function() {
|
||||
lockutil.unlockUser(lock)
|
||||
})
|
||||
}
|
||||
|
||||
/* --------------------------------- PUBLIC FUNCTIONS --------------------------------------- */
|
||||
|
||||
function getUserInfo(req, email) {
|
||||
const fields = req.swagger.params.fields.value
|
||||
|
||||
return dbapi.loadUser(email).then(function(user) {
|
||||
if (user) {
|
||||
return dbapi.getRootGroup().then(function(group) {
|
||||
return getPublishedUser(user, req.user.email, group.owner.email, fields)
|
||||
})
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
function updateUserGroupsQuotas(req, res) {
|
||||
const email = req.swagger.params.email.value
|
||||
const duration =
|
||||
typeof req.swagger.params.duration.value !== 'undefined' ?
|
||||
req.swagger.params.duration.value :
|
||||
null
|
||||
const number =
|
||||
typeof req.swagger.params.number.value !== 'undefined' ?
|
||||
req.swagger.params.number.value :
|
||||
null
|
||||
const repetitions =
|
||||
typeof req.swagger.params.repetitions.value !== 'undefined' ?
|
||||
req.swagger.params.repetitions.value :
|
||||
null
|
||||
const lock = {}
|
||||
|
||||
lockutil.lockUser(email, res, lock).then(function(lockingSuccessed) {
|
||||
if (lockingSuccessed) {
|
||||
return dbapi.updateUserGroupsQuotas(email, duration, number, repetitions)
|
||||
.then(function(stats) {
|
||||
if (stats.replaced) {
|
||||
return apiutil.respond(res, 200, 'Updated (user quotas)', {
|
||||
user: apiutil.publishUser(stats.changes[0].new_val)
|
||||
})
|
||||
}
|
||||
if ((duration === null || duration === lock.user.groups.quotas.allocated.duration) &&
|
||||
(number === null || number === lock.user.groups.quotas.allocated.number) &&
|
||||
(repetitions === null || repetitions === lock.user.groups.quotas.repetitions)
|
||||
) {
|
||||
return apiutil.respond(res, 200, 'Unchanged (user quotas)', {user: {}})
|
||||
}
|
||||
return apiutil.respond(
|
||||
res
|
||||
, 400
|
||||
, 'Bad Request (quotas must be >= actual consumed resources)')
|
||||
})
|
||||
}
|
||||
return false
|
||||
})
|
||||
.catch(function(err) {
|
||||
apiutil.internalError(res, 'Failed to update user groups quotas: ', err.stack)
|
||||
})
|
||||
.finally(function() {
|
||||
lockutil.unlockUser(lock)
|
||||
})
|
||||
}
|
||||
|
||||
function updateDefaultUserGroupsQuotas(req, res) {
|
||||
const duration =
|
||||
typeof req.swagger.params.duration.value !== 'undefined' ?
|
||||
req.swagger.params.duration.value :
|
||||
null
|
||||
const number =
|
||||
typeof req.swagger.params.number.value !== 'undefined' ?
|
||||
req.swagger.params.number.value :
|
||||
null
|
||||
const repetitions =
|
||||
typeof req.swagger.params.repetitions.value !== 'undefined' ?
|
||||
req.swagger.params.repetitions.value :
|
||||
null
|
||||
const lock = {}
|
||||
|
||||
lockutil.lockUser(req.user.email, res, lock).then(function(lockingSuccessed) {
|
||||
if (lockingSuccessed) {
|
||||
return dbapi.updateDefaultUserGroupsQuotas(req.user.email, duration, number, repetitions)
|
||||
.then(function(stats) {
|
||||
if (stats.replaced) {
|
||||
return apiutil.respond(res, 200, 'Updated (user default quotas)', {
|
||||
user: apiutil.publishUser(stats.changes[0].new_val)
|
||||
})
|
||||
}
|
||||
return apiutil.respond(res, 200, 'Unchanged (user default quotas)', {user: {}})
|
||||
})
|
||||
}
|
||||
return false
|
||||
})
|
||||
.catch(function(err) {
|
||||
apiutil.internalError(res, 'Failed to update default user groups quotas: ', err.stack)
|
||||
})
|
||||
.finally(function() {
|
||||
lockutil.unlockUser(lock)
|
||||
})
|
||||
}
|
||||
|
||||
function getUserByEmail(req, res) {
|
||||
const email = req.swagger.params.email.value
|
||||
|
||||
getUserInfo(req, email).then(function(user) {
|
||||
if (user) {
|
||||
apiutil.respond(res, 200, 'User Information', {user: user})
|
||||
}
|
||||
else {
|
||||
apiutil.respond(res, 404, 'Not Found (user)')
|
||||
}
|
||||
})
|
||||
.catch(function(err) {
|
||||
apiutil.internalError(res, 'Failed to get user: ', err.stack)
|
||||
})
|
||||
}
|
||||
|
||||
function getUsers(req, res) {
|
||||
const fields = req.swagger.params.fields.value
|
||||
|
||||
dbapi.getUsers().then(function(users) {
|
||||
return dbapi.getRootGroup().then(function(group) {
|
||||
apiutil.respond(res, 200, 'Users Information', {
|
||||
users: users.map(function(user) {
|
||||
return getPublishedUser(user, req.user.email, group.owner.email, fields)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
.catch(function(err) {
|
||||
apiutil.internalError(res, 'Failed to get users: ', err.stack)
|
||||
})
|
||||
}
|
||||
|
||||
function createUser(req, res) {
|
||||
const email = req.swagger.params.email.value
|
||||
const name = req.swagger.params.name.value
|
||||
|
||||
dbapi.createUser(email, name, req.user.ip).then(function(stats) {
|
||||
if (!stats.inserted) {
|
||||
apiutil.respond(res, 403, 'Forbidden (user already exists)')
|
||||
}
|
||||
else {
|
||||
apiutil.respond(res, 201, 'Created (user)', {
|
||||
user: apiutil.publishUser(stats.changes[0].new_val)
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(function(err) {
|
||||
apiutil.internalError(res, 'Failed to create user: ', err.stack)
|
||||
})
|
||||
}
|
||||
|
||||
function deleteUsers(req, res) {
|
||||
const emails = apiutil.getBodyParameter(req.body, 'emails')
|
||||
const target = apiutil.getQueryParameter(req.swagger.params.redirected) ? 'user' : 'users'
|
||||
|
||||
function removeUsers(emails) {
|
||||
let results = []
|
||||
|
||||
return Promise.each(emails, function(email) {
|
||||
return removeUser(email, req, res).then(function(result) {
|
||||
results.push(result)
|
||||
})
|
||||
})
|
||||
.then(function() {
|
||||
results = _.without(results, 'unchanged')
|
||||
if (!results.length) {
|
||||
return apiutil.respond(res, 200, `Unchanged (${target})`)
|
||||
}
|
||||
results = _.without(results, 'not found')
|
||||
if (!results.length) {
|
||||
return apiutil.respond(res, 404, `Not Found (${target})`)
|
||||
}
|
||||
results = _.without(results, 'forbidden')
|
||||
if (!results.length) {
|
||||
apiutil.respond(res, 403, `Forbidden (${target})`)
|
||||
}
|
||||
return apiutil.respond(res, 200, `Deleted (${target})`)
|
||||
})
|
||||
.catch(function(err) {
|
||||
if (err !== 'busy') {
|
||||
throw err
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
(function() {
|
||||
if (typeof emails === 'undefined') {
|
||||
return dbapi.getEmails().then(function(emails) {
|
||||
return removeUsers(emails)
|
||||
})
|
||||
}
|
||||
else {
|
||||
return removeUsers(_.without(emails.split(','), ''))
|
||||
}
|
||||
})()
|
||||
.catch(function(err) {
|
||||
apiutil.internalError(res, 'Failed to delete ${target}: ', err.stack)
|
||||
})
|
||||
}
|
||||
|
||||
function deleteUser(req, res) {
|
||||
apiutil.redirectApiWrapper('email', deleteUsers, req, res)
|
||||
}
|
||||
|
||||
function createUserAccessToken(req, res) {
|
||||
userApiWrapper(userapi.createAccessToken, req, res)
|
||||
}
|
||||
|
||||
function deleteUserAccessToken(req, res) {
|
||||
userApiWrapper(userapi.deleteAccessToken, req, res)
|
||||
}
|
||||
|
||||
function deleteUserAccessTokens(req, res) {
|
||||
userApiWrapper(userapi.deleteAccessTokens, req, res)
|
||||
}
|
||||
|
||||
function getUserAccessToken(req, res) {
|
||||
userApiWrapper(userapi.getAccessToken, req, res)
|
||||
}
|
||||
|
||||
function getUserAccessTokens(req, res) {
|
||||
userApiWrapper(userapi.getAccessTokens, req, res)
|
||||
}
|
||||
|
||||
function getUserDevices(req, res) {
|
||||
userApiWrapper(userapi.getUserDevices, req, res)
|
||||
}
|
||||
|
||||
function getUserDevice(req, res) {
|
||||
userApiWrapper(userapi.getUserDeviceBySerial, req, res)
|
||||
}
|
||||
|
||||
function addUserDevice(req, res) {
|
||||
userApiWrapper(userapi.addUserDevice, req, res)
|
||||
}
|
||||
|
||||
function deleteUserDevice(req, res) {
|
||||
userApiWrapper(userapi.deleteUserDeviceBySerial, req, res)
|
||||
}
|
||||
|
||||
function remoteConnectUserDevice(req, res) {
|
||||
userApiWrapper(userapi.remoteConnectUserDeviceBySerial, req, res)
|
||||
}
|
||||
|
||||
function remoteDisconnectUserDevice(req, res) {
|
||||
userApiWrapper(userapi.remoteDisconnectUserDeviceBySerial, req, res)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
updateUserGroupsQuotas: updateUserGroupsQuotas
|
||||
, updateDefaultUserGroupsQuotas: updateDefaultUserGroupsQuotas
|
||||
, getUsers: getUsers
|
||||
, getUserByEmail: getUserByEmail
|
||||
, getUserInfo: getUserInfo
|
||||
, createUser: createUser
|
||||
, deleteUser: deleteUser
|
||||
, deleteUsers: deleteUsers
|
||||
, createUserAccessToken: createUserAccessToken
|
||||
, deleteUserAccessToken: deleteUserAccessToken
|
||||
, deleteUserAccessTokens: deleteUserAccessTokens
|
||||
, getUserAccessTokensV2: getUserAccessTokens
|
||||
, getUserAccessToken: getUserAccessToken
|
||||
, getUserDevicesV2: getUserDevices
|
||||
, getUserDevice: getUserDevice
|
||||
, addUserDeviceV3: addUserDevice
|
||||
, deleteUserDevice: deleteUserDevice
|
||||
, remoteConnectUserDevice: remoteConnectUserDevice
|
||||
, remoteDisconnectUserDevice: remoteDisconnectUserDevice
|
||||
}
|
|
@ -1,7 +1,12 @@
|
|||
/**
|
||||
* Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
var dbapi = require('../../../db/api')
|
||||
var jwtutil = require('../../../util/jwtutil')
|
||||
var urlutil = require('../../../util/urlutil')
|
||||
var logger = require('../../../util/logger')
|
||||
const apiutil = require('../../../util/apiutil')
|
||||
|
||||
var log = logger.createLogger('api:helpers:securityHandlers')
|
||||
|
||||
|
@ -47,17 +52,27 @@ function accessTokenAuth(req, res, next) {
|
|||
if (!data) {
|
||||
return res.status(500).json({
|
||||
success: false
|
||||
, description: 'Internal Server Error'
|
||||
})
|
||||
}
|
||||
|
||||
dbapi.loadUser(data.email)
|
||||
.then(function(user) {
|
||||
if (user) {
|
||||
if (user.privilege === apiutil.USER &&
|
||||
req.swagger.operation.definition.tags.indexOf('admin') > -1) {
|
||||
return res.status(403).json({
|
||||
success: false
|
||||
, description: 'Forbidden: privileged operation (admin)'
|
||||
})
|
||||
}
|
||||
req.user = user
|
||||
next()
|
||||
}
|
||||
else {
|
||||
return res.status(500).json({
|
||||
success: false
|
||||
, description: 'Internal Server Error'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
@ -86,6 +101,7 @@ function accessTokenAuth(req, res, next) {
|
|||
else {
|
||||
return res.status(500).json({
|
||||
success: false
|
||||
, description: 'Internal Server Error'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/**
|
||||
* Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
var http = require('http')
|
||||
var path = require('path')
|
||||
var events = require('events')
|
||||
|
@ -52,16 +56,51 @@ module.exports = function(options) {
|
|||
lifecycle.fatal()
|
||||
})
|
||||
|
||||
var pushdev = zmqutil.socket('push')
|
||||
Promise.map(options.endpoints.pushdev, function(endpoint) {
|
||||
return srv.resolve(endpoint).then(function(records) {
|
||||
return srv.attempt(records, function(record) {
|
||||
log.info('Sending output to "%s"', record.url)
|
||||
pushdev.connect(record.url)
|
||||
return Promise.resolve(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.fatal('Unable to connect to pushdev endpoint', err)
|
||||
lifecycle.fatal()
|
||||
})
|
||||
|
||||
var subdev = zmqutil.socket('sub')
|
||||
Promise.map(options.endpoints.subdev, function(endpoint) {
|
||||
return srv.resolve(endpoint).then(function(records) {
|
||||
return srv.attempt(records, function(record) {
|
||||
log.info('Receiving input from "%s"', record.url)
|
||||
subdev.connect(record.url)
|
||||
return Promise.resolve(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.fatal('Unable to connect to subdev endpoint', err)
|
||||
lifecycle.fatal()
|
||||
})
|
||||
|
||||
// Establish always-on channels
|
||||
;[wireutil.global].forEach(function(channel) {
|
||||
log.info('Subscribing to permanent channel "%s"', channel)
|
||||
sub.subscribe(channel)
|
||||
subdev.subscribe(channel)
|
||||
})
|
||||
|
||||
sub.on('message', function(channel, data) {
|
||||
channelRouter.emit(channel.toString(), channel, data)
|
||||
})
|
||||
|
||||
subdev.on('message', function(channel, data) {
|
||||
channelRouter.emit(channel.toString(), channel, data)
|
||||
})
|
||||
|
||||
// Swagger Express Config
|
||||
var config = {
|
||||
appRoot: __dirname
|
||||
|
@ -83,6 +122,8 @@ module.exports = function(options) {
|
|||
push: push
|
||||
, sub: sub
|
||||
, channelRouter: channelRouter
|
||||
, pushdev: pushdev
|
||||
, subdev: subdev
|
||||
})
|
||||
|
||||
req.options = reqOptions
|
||||
|
@ -96,7 +137,7 @@ module.exports = function(options) {
|
|||
}))
|
||||
|
||||
lifecycle.observe(function() {
|
||||
[push, sub].forEach(function(sock) {
|
||||
[push, sub, pushdev, subdev].forEach(function(sock) {
|
||||
try {
|
||||
sock.close()
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,3 +1,7 @@
|
|||
/**
|
||||
* Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
var jwtutil = require('../../../util/jwtutil')
|
||||
var urlutil = require('../../../util/urlutil')
|
||||
|
||||
|
@ -18,6 +22,7 @@ module.exports = function(options) {
|
|||
})
|
||||
.then(function() {
|
||||
req.session.jwt = data
|
||||
req.sessionOptions.httpOnly = false
|
||||
res.redirect(redir)
|
||||
})
|
||||
.catch(next)
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/**
|
||||
* Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
var http = require('http')
|
||||
|
||||
var express = require('express')
|
||||
|
@ -16,6 +20,8 @@ var pathutil = require('../../util/pathutil')
|
|||
var urlutil = require('../../util/urlutil')
|
||||
var lifecycle = require('../../util/lifecycle')
|
||||
|
||||
const dbapi = require('../../db/api')
|
||||
|
||||
module.exports = function(options) {
|
||||
var log = logger.createLogger('auth-ldap')
|
||||
var app = express()
|
||||
|
@ -54,6 +60,24 @@ module.exports = function(options) {
|
|||
res.redirect('/auth/ldap/')
|
||||
})
|
||||
|
||||
app.get('/auth/contact', function(req, res) {
|
||||
dbapi.getRootGroup().then(function(group) {
|
||||
res.status(200)
|
||||
.json({
|
||||
success: true
|
||||
, contact: group.owner
|
||||
})
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.error('Unexpected error', err.stack)
|
||||
res.status(500)
|
||||
.json({
|
||||
success: false
|
||||
, error: 'ServerError'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
app.get('/auth/ldap/', function(req, res) {
|
||||
res.render('index')
|
||||
})
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/**
|
||||
* Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
var http = require('http')
|
||||
|
||||
var express = require('express')
|
||||
|
@ -16,6 +20,8 @@ var pathutil = require('../../util/pathutil')
|
|||
var urlutil = require('../../util/urlutil')
|
||||
var lifecycle = require('../../util/lifecycle')
|
||||
|
||||
const dbapi = require('../../db/api')
|
||||
|
||||
module.exports = function(options) {
|
||||
var log = logger.createLogger('auth-mock')
|
||||
var app = express()
|
||||
|
@ -80,6 +86,24 @@ module.exports = function(options) {
|
|||
res.redirect('/auth/mock/')
|
||||
})
|
||||
|
||||
app.get('/auth/contact', function(req, res) {
|
||||
dbapi.getRootGroup().then(function(group) {
|
||||
res.status(200)
|
||||
.json({
|
||||
success: true
|
||||
, contact: group.owner
|
||||
})
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.error('Unexpected error', err.stack)
|
||||
res.status(500)
|
||||
.json({
|
||||
success: false
|
||||
, error: 'ServerError'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
app.get('/auth/mock/', function(req, res) {
|
||||
res.render('index')
|
||||
})
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/**
|
||||
* Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
var util = require('util')
|
||||
|
||||
var syrup = require('stf-syrup')
|
||||
|
@ -117,6 +121,7 @@ module.exports = syrup.serial()
|
|||
if (plugin.isRunning()) {
|
||||
activeServer.close()
|
||||
activeServer.end()
|
||||
activeServer = null
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -131,7 +136,7 @@ module.exports = syrup.serial()
|
|||
}
|
||||
|
||||
lifecycle.observe(plugin.stop)
|
||||
group.on('leave', plugin.end)
|
||||
group.on('leave', plugin.stop)
|
||||
|
||||
router
|
||||
.on(wire.ConnectStartMessage, function(channel) {
|
||||
|
@ -163,7 +168,7 @@ module.exports = syrup.serial()
|
|||
})
|
||||
.on(wire.ConnectStopMessage, function(channel) {
|
||||
var reply = wireutil.reply(options.serial)
|
||||
plugin.end()
|
||||
plugin.stop()
|
||||
.then(function() {
|
||||
push.send([
|
||||
channel
|
||||
|
@ -187,6 +192,5 @@ module.exports = syrup.serial()
|
|||
})
|
||||
})
|
||||
|
||||
return plugin.start()
|
||||
.return(plugin)
|
||||
return(plugin)
|
||||
})
|
||||
|
|
|
@ -33,8 +33,9 @@ module.exports = syrup.serial()
|
|||
.dependency(require('../support/adb'))
|
||||
.dependency(require('../support/router'))
|
||||
.dependency(require('../support/push'))
|
||||
.dependency(require('../support/sdk'))
|
||||
.dependency(require('../resources/service'))
|
||||
.define(function(options, adb, router, push, apk) {
|
||||
.define(function(options, adb, router, push, sdk, apk) {
|
||||
var log = logger.createLogger('device:plugins:service')
|
||||
var messageResolver = new MessageResolver()
|
||||
var plugin = new events.EventEmitter()
|
||||
|
@ -62,9 +63,11 @@ module.exports = syrup.serial()
|
|||
}
|
||||
|
||||
function callService(intent) {
|
||||
var startServiceCmd = (sdk.level < 26) ? 'startservice' : 'start-foreground-service'
|
||||
log.info('using \'%s\' command for API %s', startServiceCmd, sdk.level)
|
||||
return adb.shell(options.serial, util.format(
|
||||
'am startservice --user 0 %s'
|
||||
, intent
|
||||
'am %s --user 0 %s'
|
||||
, startServiceCmd, intent
|
||||
))
|
||||
.timeout(15000)
|
||||
.then(function(out) {
|
||||
|
@ -76,8 +79,8 @@ module.exports = syrup.serial()
|
|||
.then(function(line) {
|
||||
if (line.indexOf('--user') !== -1) {
|
||||
return adb.shell(options.serial, util.format(
|
||||
'am startservice %s'
|
||||
, intent
|
||||
'am %s %s'
|
||||
, startServiceCmd, intent
|
||||
))
|
||||
.timeout(15000)
|
||||
.then(function() {
|
||||
|
|
|
@ -44,6 +44,7 @@ module.exports = syrup.serial()
|
|||
, identity.product
|
||||
, identity.cpuPlatform
|
||||
, identity.openGLESVersion
|
||||
, identity.marketName
|
||||
))
|
||||
])
|
||||
})
|
||||
|
|
|
@ -62,7 +62,7 @@ module.exports = syrup.serial()
|
|||
}
|
||||
|
||||
function removeResource(res) {
|
||||
return adb.shell(options.serial, ['rm', res.dest])
|
||||
return adb.shell(options.serial, ['rm', '-f', res.dest])
|
||||
.timeout(10000)
|
||||
.then(function(out) {
|
||||
return streamutil.readAll(out)
|
||||
|
|
|
@ -36,7 +36,7 @@ module.exports = syrup.serial()
|
|||
}
|
||||
|
||||
function removeResource(res) {
|
||||
return adb.shell(options.serial, ['rm', res.dest])
|
||||
return adb.shell(options.serial, ['rm', '-f', res.dest])
|
||||
.timeout(10000)
|
||||
.then(function(out) {
|
||||
return streamutil.readAll(out)
|
||||
|
|
|
@ -35,7 +35,7 @@ module.exports = syrup.serial()
|
|||
}
|
||||
|
||||
function removeResource(res) {
|
||||
return adb.shell(options.serial, ['rm', res.dest])
|
||||
return adb.shell(options.serial, ['rm', '-f', res.dest])
|
||||
.timeout(10000)
|
||||
.then(function(out) {
|
||||
return streamutil.readAll(out)
|
||||
|
|
115
lib/units/groups-engine/index.js
Normal file
115
lib/units/groups-engine/index.js
Normal file
|
@ -0,0 +1,115 @@
|
|||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
const events = require('events')
|
||||
const Promise = require('bluebird')
|
||||
const logger = require('../../util/logger')
|
||||
const zmqutil = require('../../util/zmqutil')
|
||||
const srv = require('../../util/srv')
|
||||
const lifecycle = require('../../util/lifecycle')
|
||||
const wireutil = require('../../wire/util')
|
||||
|
||||
const groupsScheduler = require('./scheduler')
|
||||
const groupsWatcher = require('./watchers/groups')
|
||||
const devicesWatcher = require('./watchers/devices')
|
||||
const usersWatcher = require('./watchers/users')
|
||||
|
||||
module.exports = function(options) {
|
||||
const log = logger.createLogger('groups-engine')
|
||||
const channelRouter = new events.EventEmitter()
|
||||
|
||||
const push = zmqutil.socket('push')
|
||||
Promise.map(options.endpoints.push, function(endpoint) {
|
||||
return srv.resolve(endpoint).then(function(records) {
|
||||
return srv.attempt(records, function(record) {
|
||||
log.info('Sending output to "%s"', record.url)
|
||||
push.connect(record.url)
|
||||
return Promise.resolve(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.fatal('Unable to connect to push endpoint', err)
|
||||
lifecycle.fatal()
|
||||
})
|
||||
|
||||
// Input
|
||||
const sub = zmqutil.socket('sub')
|
||||
Promise.map(options.endpoints.sub, function(endpoint) {
|
||||
return srv.resolve(endpoint).then(function(records) {
|
||||
return srv.attempt(records, function(record) {
|
||||
log.info('Receiving input from "%s"', record.url)
|
||||
sub.connect(record.url)
|
||||
return Promise.resolve(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.fatal('Unable to connect to sub endpoint', err)
|
||||
lifecycle.fatal()
|
||||
})
|
||||
|
||||
const pushdev = zmqutil.socket('push')
|
||||
Promise.map(options.endpoints.pushdev, function(endpoint) {
|
||||
return srv.resolve(endpoint).then(function(records) {
|
||||
return srv.attempt(records, function(record) {
|
||||
log.info('Sending output to "%s"', record.url)
|
||||
pushdev.connect(record.url)
|
||||
return Promise.resolve(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.fatal('Unable to connect to pushdev endpoint', err)
|
||||
lifecycle.fatal()
|
||||
})
|
||||
|
||||
const subdev = zmqutil.socket('sub')
|
||||
Promise.map(options.endpoints.subdev, function(endpoint) {
|
||||
return srv.resolve(endpoint).then(function(records) {
|
||||
return srv.attempt(records, function(record) {
|
||||
log.info('Receiving input from "%s"', record.url)
|
||||
subdev.connect(record.url)
|
||||
return Promise.resolve(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.fatal('Unable to connect to subdev endpoint', err)
|
||||
lifecycle.fatal()
|
||||
})
|
||||
|
||||
// Establish always-on channels
|
||||
;[wireutil.global].forEach(function(channel) {
|
||||
log.info('Subscribing to permanent channel "%s"', channel)
|
||||
sub.subscribe(channel)
|
||||
subdev.subscribe(channel)
|
||||
})
|
||||
|
||||
sub.on('message', function(channel, data) {
|
||||
channelRouter.emit(channel.toString(), channel, data)
|
||||
})
|
||||
|
||||
subdev.on('message', function(channel, data) {
|
||||
channelRouter.emit(channel.toString(), channel, data)
|
||||
})
|
||||
|
||||
groupsScheduler()
|
||||
groupsWatcher(push, pushdev, channelRouter)
|
||||
devicesWatcher(push, pushdev, channelRouter)
|
||||
usersWatcher(pushdev)
|
||||
|
||||
lifecycle.observe(function() {
|
||||
[push, sub, pushdev, subdev].forEach(function(sock) {
|
||||
try {
|
||||
sock.close()
|
||||
}
|
||||
catch (err) {
|
||||
// No-op
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
log.info('Groups engine started')
|
||||
}
|
156
lib/units/groups-engine/scheduler/index.js
Normal file
156
lib/units/groups-engine/scheduler/index.js
Normal file
|
@ -0,0 +1,156 @@
|
|||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
const Promise = require('bluebird')
|
||||
const logger = require('../../../util/logger')
|
||||
const apiutil = require('../../../util/apiutil')
|
||||
const db = require('../../../db')
|
||||
const dbapi = require('../../../db/api')
|
||||
const r = require('rethinkdb')
|
||||
|
||||
module.exports = function() {
|
||||
const log = logger.createLogger('groups-scheduler')
|
||||
|
||||
function updateOriginGroupLifetime(group) {
|
||||
const lock = {}
|
||||
|
||||
return dbapi.adminLockGroup(group.id, lock).then(function(lockingSuccessed) {
|
||||
if (lockingSuccessed) {
|
||||
const now = Date.now()
|
||||
|
||||
return db.run(r.table('groups').get(group.id).update({
|
||||
dates: [{
|
||||
start: new Date(now)
|
||||
, stop: new Date(now + (group.dates[0].stop - group.dates[0].start))
|
||||
}]
|
||||
}))
|
||||
}
|
||||
return false
|
||||
})
|
||||
.finally(function() {
|
||||
return dbapi.adminUnlockGroup(lock)
|
||||
})
|
||||
}
|
||||
|
||||
function deleteUserGroup(group) {
|
||||
const lock = {}
|
||||
|
||||
return dbapi.adminLockGroup(group.id, lock).then(function(lockingSuccessed) {
|
||||
if (lockingSuccessed) {
|
||||
return dbapi.deleteUserGroup(group.id)
|
||||
}
|
||||
else {
|
||||
return db.run(r.table('groups').get(group.id).update({
|
||||
isActive: false
|
||||
, state: apiutil.WAITING
|
||||
}))
|
||||
}
|
||||
})
|
||||
.finally(function() {
|
||||
return dbapi.adminUnlockGroup(lock)
|
||||
})
|
||||
}
|
||||
|
||||
function updateGroupDates(group, incr, isActive) {
|
||||
const repetitions = group.repetitions - incr
|
||||
const dates = group.dates.slice(incr)
|
||||
const duration = group.devices.length * (dates[0].stop - dates[0].start) * (repetitions + 1)
|
||||
|
||||
return db.run(r.table('groups').get(group.id).update({
|
||||
dates: dates
|
||||
, repetitions: repetitions
|
||||
, duration: duration
|
||||
, isActive: isActive
|
||||
, state: apiutil.READY
|
||||
}))
|
||||
.then(function() {
|
||||
return dbapi.updateUserGroupDuration(group.owner.email, group.duration, duration)
|
||||
})
|
||||
}
|
||||
|
||||
function doBecomeUnactiveGroup(group) {
|
||||
const lock = {}
|
||||
|
||||
return dbapi.adminLockGroup(group.id, lock).then(function(lockingSuccessed) {
|
||||
if (lockingSuccessed) {
|
||||
return updateGroupDates(group, 1, false)
|
||||
}
|
||||
else {
|
||||
return db.run(r.table('groups').get(group.id).update({
|
||||
isActive: false
|
||||
, state: apiutil.WAITING
|
||||
}))
|
||||
}
|
||||
})
|
||||
.finally(function() {
|
||||
return dbapi.adminUnlockGroup(lock)
|
||||
})
|
||||
}
|
||||
|
||||
function doCleanElapsedGroupDates(group, incr) {
|
||||
const lock = {}
|
||||
|
||||
return dbapi.adminLockGroup(group.id, lock).then(function(lockingSuccessed) {
|
||||
return lockingSuccessed ? updateGroupDates(group, incr, false) : false
|
||||
})
|
||||
.finally(function() {
|
||||
return dbapi.adminUnlockGroup(lock)
|
||||
})
|
||||
}
|
||||
|
||||
function doBecomeActiveGroup(group, incr) {
|
||||
const lock = {}
|
||||
|
||||
return dbapi.adminLockGroup(group.id, lock).then(function(lockingSuccessed) {
|
||||
return lockingSuccessed ? updateGroupDates(group, incr, true) : false
|
||||
})
|
||||
.finally(function() {
|
||||
return dbapi.adminUnlockGroup(lock)
|
||||
})
|
||||
}
|
||||
|
||||
dbapi.unlockBookingObjects().then(function() {
|
||||
setInterval(function() {
|
||||
const now = Date.now()
|
||||
|
||||
dbapi.getReadyGroupsOrderByIndex('startTime').then(function(groups) {
|
||||
Promise.each(groups, (function(group) {
|
||||
if (apiutil.isOriginGroup(group.class)) {
|
||||
if (now >= group.dates[0].stop.getTime()) {
|
||||
return updateOriginGroupLifetime(group)
|
||||
}
|
||||
}
|
||||
else if ((group.isActive || group.state === apiutil.WAITING) &&
|
||||
now >= group.dates[0].stop.getTime()) {
|
||||
if (group.dates.length === 1) {
|
||||
return deleteUserGroup(group)
|
||||
}
|
||||
else {
|
||||
return doBecomeUnactiveGroup(group)
|
||||
}
|
||||
}
|
||||
else if (!group.isActive) {
|
||||
for(const i in group.dates) {
|
||||
if (now >= group.dates[i].stop.getTime()) {
|
||||
if (group.dates[i].stop === group.dates[group.dates.length - 1].stop) {
|
||||
return deleteUserGroup(group)
|
||||
}
|
||||
}
|
||||
else if (now < group.dates[i].start.getTime()) {
|
||||
return i > 0 ? doCleanElapsedGroupDates(group, i) : false
|
||||
}
|
||||
else {
|
||||
return doBecomeActiveGroup(group, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}))
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.error('An error occured during groups scheduling', err.stack)
|
||||
})
|
||||
}, 1000)
|
||||
})
|
||||
}
|
255
lib/units/groups-engine/watchers/devices.js
Normal file
255
lib/units/groups-engine/watchers/devices.js
Normal file
|
@ -0,0 +1,255 @@
|
|||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
const wirerouter = require('../../../wire/router')
|
||||
const _ = require('lodash')
|
||||
const r = require('rethinkdb')
|
||||
const util = require('util')
|
||||
const uuid = require('uuid')
|
||||
const logger = require('../../../util/logger')
|
||||
const timeutil = require('../../../util/timeutil')
|
||||
const wireutil = require('../../../wire/util')
|
||||
const wire = require('../../../wire')
|
||||
const dbapi = require('../../../db/api')
|
||||
const db = require('../../../db')
|
||||
|
||||
module.exports = function(push, pushdev, channelRouter) {
|
||||
const log = logger.createLogger('watcher-devices')
|
||||
|
||||
function sendReleaseDeviceControl(serial, channel) {
|
||||
push.send([
|
||||
channel
|
||||
, wireutil.envelope(
|
||||
new wire.UngroupMessage(
|
||||
wireutil.toDeviceRequirements({
|
||||
serial: {
|
||||
value: serial
|
||||
, match: 'exact'
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
])
|
||||
}
|
||||
|
||||
function sendDeviceGroupChange(id, group, serial, originName) {
|
||||
pushdev.send([
|
||||
wireutil.global
|
||||
, wireutil.envelope(
|
||||
new wire.DeviceGroupChangeMessage(
|
||||
id
|
||||
, new wire.DeviceGroupMessage(
|
||||
group.id
|
||||
, group.name
|
||||
, new wire.DeviceGroupOwnerMessage(
|
||||
group.owner.email
|
||||
, group.owner.name
|
||||
)
|
||||
, new wire.DeviceGroupLifetimeMessage(
|
||||
group.dates[0].start.getTime()
|
||||
, group.dates[0].stop.getTime()
|
||||
)
|
||||
, group.class
|
||||
, group.repetitions
|
||||
, originName
|
||||
)
|
||||
, serial
|
||||
)
|
||||
)
|
||||
])
|
||||
}
|
||||
|
||||
function sendDeviceChange(device1, device2, action) {
|
||||
function publishDevice() {
|
||||
const device = _.cloneDeep(device1)
|
||||
|
||||
delete device.channel
|
||||
delete device.owner
|
||||
delete device.group.id
|
||||
delete device.group.lifeTime
|
||||
return device
|
||||
}
|
||||
|
||||
pushdev.send([
|
||||
wireutil.global
|
||||
, wireutil.envelope(
|
||||
new wire.DeviceChangeMessage(
|
||||
publishDevice()
|
||||
, action
|
||||
, device2.group.origin
|
||||
, timeutil.now('nano')
|
||||
)
|
||||
)
|
||||
])
|
||||
}
|
||||
|
||||
function sendReleaseDeviceControlAndDeviceGroupChange(
|
||||
device
|
||||
, sendDeviceGroupChangeWrapper) {
|
||||
let messageListener
|
||||
const responseTimer = setTimeout(function() {
|
||||
channelRouter.removeListener(wireutil.global, messageListener)
|
||||
sendDeviceGroupChangeWrapper()
|
||||
}, 5000)
|
||||
|
||||
messageListener = wirerouter()
|
||||
.on(wire.LeaveGroupMessage, function(channel, message) {
|
||||
if (message.serial === device.serial &&
|
||||
message.owner.email === device.owner.email) {
|
||||
clearTimeout(responseTimer)
|
||||
channelRouter.removeListener(wireutil.global, messageListener)
|
||||
sendDeviceGroupChangeWrapper()
|
||||
}
|
||||
})
|
||||
.handler()
|
||||
|
||||
channelRouter.on(wireutil.global, messageListener)
|
||||
sendReleaseDeviceControl(device.serial, device.channel)
|
||||
}
|
||||
|
||||
db.run(r
|
||||
.table('devices')
|
||||
.pluck(
|
||||
'serial'
|
||||
, 'channel'
|
||||
, 'owner'
|
||||
, 'model'
|
||||
, 'operator'
|
||||
, 'manufacturer'
|
||||
, {group: ['id', 'origin', 'originName', 'lifeTime']}
|
||||
, {provider: ['name']}
|
||||
, {network: ['type', 'subtype']}
|
||||
, {display: ['height', 'width']}
|
||||
, 'version'
|
||||
, 'sdk'
|
||||
, 'abi'
|
||||
, 'cpuPlatform'
|
||||
, 'openGLESVersion'
|
||||
, {phone: ['imei']}
|
||||
, 'marketName'
|
||||
)
|
||||
.changes(), function(err, cursor) {
|
||||
if (err) {
|
||||
throw err
|
||||
}
|
||||
return cursor
|
||||
})
|
||||
.then(function(cursor) {
|
||||
cursor.each(function(err, data) {
|
||||
if (err) {
|
||||
throw err
|
||||
}
|
||||
if (data.old_val === null) {
|
||||
return sendDeviceChange(data.new_val, data.new_val, 'created')
|
||||
}
|
||||
else if (data.new_val === null) {
|
||||
sendDeviceChange(data.old_val, data.old_val, 'deleted')
|
||||
}
|
||||
else if (data.new_val.model !== data.old_val.model ||
|
||||
data.new_val.group.origin !== data.old_val.group.origin ||
|
||||
data.new_val.operator !== data.old_val.operator ||
|
||||
data.new_val.hasOwnProperty('network') &&
|
||||
(!data.old_val.hasOwnProperty('network') ||
|
||||
data.new_val.network.type !== data.old_val.network.type ||
|
||||
data.new_val.network.subtype !== data.old_val.network.subtype
|
||||
) ||
|
||||
data.new_val.provider.name !== data.old_val.provider.name) {
|
||||
sendDeviceChange(data.new_val, data.old_val, 'updated')
|
||||
}
|
||||
|
||||
const isDeleted = data.new_val === null
|
||||
const id = isDeleted ? data.old_val.group.id : data.new_val.group.id
|
||||
|
||||
return dbapi.getGroup(id).then(function(group) {
|
||||
function sendDeviceGroupChangeOnDeviceDeletion() {
|
||||
const fakeGroup = Object.assign({}, group)
|
||||
|
||||
fakeGroup.id = util.format('%s', uuid.v4()).replace(/-/g, '')
|
||||
fakeGroup.name = 'none'
|
||||
sendDeviceGroupChange(
|
||||
group.id
|
||||
, fakeGroup
|
||||
, data.old_val.serial
|
||||
, data.old_val.group.originName
|
||||
)
|
||||
}
|
||||
|
||||
function sendDeviceGroupChangeOnDeviceCurrentGroupUpdating() {
|
||||
sendDeviceGroupChange(
|
||||
data.old_val.group.id
|
||||
, group
|
||||
, data.new_val.serial
|
||||
, data.new_val.group.originName
|
||||
)
|
||||
}
|
||||
|
||||
if (group) {
|
||||
if (isDeleted) {
|
||||
if (data.old_val.owner) {
|
||||
sendReleaseDeviceControlAndDeviceGroupChange(
|
||||
data.old_val
|
||||
, sendDeviceGroupChangeOnDeviceDeletion
|
||||
)
|
||||
return
|
||||
}
|
||||
sendDeviceGroupChangeOnDeviceDeletion()
|
||||
return
|
||||
}
|
||||
|
||||
const isChangeCurrentGroup = data.new_val.group.id !== data.old_val.group.id
|
||||
const isChangeOriginGroup = data.new_val.group.origin !== data.old_val.group.origin
|
||||
const isChangeLifeTime =
|
||||
data.new_val.group.lifeTime.start.getTime() !==
|
||||
data.old_val.group.lifeTime.start.getTime()
|
||||
|
||||
if (isChangeLifeTime && !isChangeCurrentGroup && !isChangeOriginGroup) {
|
||||
sendDeviceGroupChange(
|
||||
data.old_val.group.id
|
||||
, group
|
||||
, data.new_val.serial
|
||||
, data.new_val.group.originName
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (isChangeCurrentGroup) {
|
||||
if (data.new_val.owner && group.users.indexOf(data.new_val.owner.email) < 0) {
|
||||
sendReleaseDeviceControlAndDeviceGroupChange(
|
||||
data.new_val
|
||||
, sendDeviceGroupChangeOnDeviceCurrentGroupUpdating
|
||||
)
|
||||
}
|
||||
else {
|
||||
sendDeviceGroupChangeOnDeviceCurrentGroupUpdating()
|
||||
}
|
||||
}
|
||||
|
||||
if (isChangeOriginGroup) {
|
||||
dbapi.getGroup(data.old_val.group.origin).then(function(originGroup) {
|
||||
if (originGroup) {
|
||||
dbapi.removeOriginGroupDevice(originGroup, data.new_val.serial)
|
||||
}
|
||||
})
|
||||
dbapi.getGroup(data.new_val.group.origin).then(function(originGroup) {
|
||||
if (originGroup) {
|
||||
dbapi.addOriginGroupDevice(originGroup, data.new_val.serial)
|
||||
}
|
||||
})
|
||||
if (!isChangeCurrentGroup) {
|
||||
sendDeviceGroupChange(
|
||||
data.new_val.group.id
|
||||
, group
|
||||
, data.new_val.serial
|
||||
, data.new_val.group.originName
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.error('An error occured during DEVICES table watching', err.stack)
|
||||
})
|
||||
}
|
346
lib/units/groups-engine/watchers/groups.js
Normal file
346
lib/units/groups-engine/watchers/groups.js
Normal file
|
@ -0,0 +1,346 @@
|
|||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
const wirerouter = require('../../../wire/router')
|
||||
const Promise = require('bluebird')
|
||||
const _ = require('lodash')
|
||||
const r = require('rethinkdb')
|
||||
const logger = require('../../../util/logger')
|
||||
const timeutil = require('../../../util/timeutil')
|
||||
const apiutil = require('../../../util/apiutil')
|
||||
const wireutil = require('../../../wire/util')
|
||||
const wire = require('../../../wire')
|
||||
const dbapi = require('../../../db/api')
|
||||
const db = require('../../../db')
|
||||
|
||||
module.exports = function(push, pushdev, channelRouter) {
|
||||
const log = logger.createLogger('watcher-groups')
|
||||
|
||||
function sendReleaseDeviceControl(serial, channel) {
|
||||
push.send([
|
||||
channel
|
||||
, wireutil.envelope(
|
||||
new wire.UngroupMessage(
|
||||
wireutil.toDeviceRequirements({
|
||||
serial: {
|
||||
value: serial
|
||||
, match: 'exact'
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
])
|
||||
}
|
||||
|
||||
function sendGroupChange(
|
||||
group
|
||||
, subscribers
|
||||
, isChangedDates
|
||||
, isChangedClass
|
||||
, isAddedUser
|
||||
, users
|
||||
, isAddedDevice
|
||||
, devices
|
||||
, action) {
|
||||
function dates2String(dates) {
|
||||
return dates.map(function(date) {
|
||||
return {
|
||||
start: date.start.toJSON()
|
||||
, stop: date.stop.toJSON()
|
||||
}
|
||||
})
|
||||
}
|
||||
pushdev.send([
|
||||
wireutil.global
|
||||
, wireutil.envelope(
|
||||
new wire.GroupChangeMessage(
|
||||
new wire.GroupField(
|
||||
group.id
|
||||
, group.name
|
||||
, group.class
|
||||
, group.privilege
|
||||
, group.owner
|
||||
, dates2String(group.dates)
|
||||
, group.duration
|
||||
, group.repetitions
|
||||
, group.devices
|
||||
, group.users
|
||||
, group.state
|
||||
, group.isActive
|
||||
)
|
||||
, action
|
||||
, subscribers
|
||||
, isChangedDates
|
||||
, isChangedClass
|
||||
, isAddedUser
|
||||
, users
|
||||
, isAddedDevice
|
||||
, devices
|
||||
, timeutil.now('nano')
|
||||
)
|
||||
)
|
||||
])
|
||||
}
|
||||
|
||||
function sendGroupUsersChange(group, users, devices, isAdded, action) {
|
||||
const isDeletedLater = action === 'GroupDeletedLater'
|
||||
|
||||
pushdev.send([
|
||||
wireutil.global
|
||||
, wireutil.envelope(
|
||||
new wire.GroupUserChangeMessage(users, isAdded, group.id, isDeletedLater, devices))
|
||||
])
|
||||
}
|
||||
|
||||
function doUpdateDeviceOriginGroup(group) {
|
||||
return dbapi.updateDeviceOriginGroup(group.ticket.serial, group).then(function() {
|
||||
push.send([
|
||||
wireutil.global
|
||||
, wireutil.envelope(
|
||||
new wire.DeviceOriginGroupMessage(group.ticket.signature)
|
||||
)
|
||||
])
|
||||
})
|
||||
}
|
||||
|
||||
function doUpdateDevicesCurrentGroup(group, devices) {
|
||||
return Promise.map(devices, function(serial) {
|
||||
return dbapi.updateDeviceCurrentGroup(serial, group)
|
||||
})
|
||||
}
|
||||
|
||||
function doUpdateDevicesCurrentGroupFromOrigin(devices) {
|
||||
return Promise.map(devices, function(serial) {
|
||||
return dbapi.updateDeviceCurrentGroupFromOrigin(serial)
|
||||
})
|
||||
}
|
||||
|
||||
function doUpdateDevicesCurrentGroupDates(group) {
|
||||
if (apiutil.isOriginGroup(group.class)) {
|
||||
return Promise.map(group.devices, function(serial) {
|
||||
return dbapi.loadDeviceBySerial(serial).then(function(device) {
|
||||
return device.group.id === group.id ?
|
||||
doUpdateDevicesCurrentGroup(group, [serial]) :
|
||||
false
|
||||
})
|
||||
})
|
||||
}
|
||||
else {
|
||||
return Promise.map(group.devices, function(serial) {
|
||||
return doUpdateDevicesCurrentGroup(group, [serial])
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function treatGroupUsersChange(group, users, isActive, isAddedUser) {
|
||||
if (isActive) {
|
||||
return Promise.map(users, function(email) {
|
||||
return Promise.map(group.devices, function(serial) {
|
||||
return dbapi.loadDeviceBySerial(serial).then(function(device) {
|
||||
if (device && device.group.id === group.id) {
|
||||
if (!isAddedUser && device.owner && device.owner.email === email) {
|
||||
return new Promise(function(resolve) {
|
||||
let messageListener
|
||||
const responseTimer = setTimeout(function() {
|
||||
channelRouter.removeListener(wireutil.global, messageListener)
|
||||
resolve(serial)
|
||||
}, 5000)
|
||||
|
||||
messageListener = wirerouter()
|
||||
.on(wire.LeaveGroupMessage, function(channel, message) {
|
||||
if (message.serial === serial &&
|
||||
message.owner.email === email) {
|
||||
clearTimeout(responseTimer)
|
||||
channelRouter.removeListener(wireutil.global, messageListener)
|
||||
resolve(serial)
|
||||
}
|
||||
})
|
||||
.handler()
|
||||
|
||||
channelRouter.on(wireutil.global, messageListener)
|
||||
sendReleaseDeviceControl(serial, device.channel)
|
||||
})
|
||||
}
|
||||
return serial
|
||||
}
|
||||
return false
|
||||
})
|
||||
})
|
||||
.then(function(devices) {
|
||||
sendGroupUsersChange(
|
||||
group, [email], _.without(devices, false), isAddedUser, 'GroupUser(s)Updated')
|
||||
})
|
||||
})
|
||||
}
|
||||
else {
|
||||
return sendGroupUsersChange(group, users, [], isAddedUser, 'GroupUser(s)Updated')
|
||||
}
|
||||
}
|
||||
|
||||
function treatGroupDevicesChange(oldGroup, group, devices, isAddedDevice) {
|
||||
if (isAddedDevice) {
|
||||
return doUpdateDevicesCurrentGroup(group, devices)
|
||||
}
|
||||
else {
|
||||
return doUpdateDevicesCurrentGroupFromOrigin(devices)
|
||||
.then(function() {
|
||||
if (group === null) {
|
||||
sendGroupUsersChange(oldGroup, oldGroup.users, [], false, 'GroupDeletedLater')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function treatGroupDeletion(group) {
|
||||
if (apiutil.isOriginGroup(group.class)) {
|
||||
return dbapi.getRootGroup().then(function(rootGroup) {
|
||||
return Promise.map(group.devices, function(serial) {
|
||||
return dbapi.updateDeviceOriginGroup(serial, rootGroup)
|
||||
})
|
||||
.then(function() {
|
||||
sendGroupUsersChange(group, group.users, [], false, 'GroupDeletedLater')
|
||||
})
|
||||
})
|
||||
}
|
||||
else {
|
||||
return sendGroupUsersChange(group, group.users, [], false, 'GroupDeleted')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
db.run(r
|
||||
.table('groups')
|
||||
.pluck(
|
||||
'id'
|
||||
, 'name'
|
||||
, 'class'
|
||||
, 'privilege'
|
||||
, 'owner'
|
||||
, 'dates'
|
||||
, 'duration'
|
||||
, 'repetitions'
|
||||
, 'devices'
|
||||
, 'users'
|
||||
, 'state'
|
||||
, 'isActive'
|
||||
, 'ticket'
|
||||
)
|
||||
.changes(), function(err, cursor) {
|
||||
if (err) {
|
||||
throw err
|
||||
}
|
||||
return cursor
|
||||
})
|
||||
.then(function(cursor) {
|
||||
cursor.each(function(err, data) {
|
||||
let users, devices, isBecomeActive, isBecomeUnactive, isActive
|
||||
, isAddedUser, isAddedDevice, isUpdatedDeviceOriginGroup, isChangedDates
|
||||
|
||||
if (err) {
|
||||
throw err
|
||||
}
|
||||
if (data.old_val === null) {
|
||||
sendGroupChange(
|
||||
data.new_val
|
||||
, data.new_val.users
|
||||
, false
|
||||
, false
|
||||
, false
|
||||
, []
|
||||
, false
|
||||
, []
|
||||
, 'created'
|
||||
)
|
||||
return sendGroupUsersChange(
|
||||
data.new_val
|
||||
, data.new_val.users
|
||||
, data.new_val.devices
|
||||
, true
|
||||
, 'GroupCreated'
|
||||
)
|
||||
}
|
||||
|
||||
if (data.new_val === null) {
|
||||
sendGroupChange(
|
||||
data.old_val
|
||||
, data.old_val.users
|
||||
, false
|
||||
, false
|
||||
, false
|
||||
, []
|
||||
, false
|
||||
, []
|
||||
, 'deleted'
|
||||
)
|
||||
|
||||
users = data.old_val.users
|
||||
devices = data.old_val.devices
|
||||
isChangedDates = false
|
||||
isActive = data.old_val.isActive
|
||||
isBecomeActive = isBecomeUnactive = false
|
||||
isAddedUser = isAddedDevice = false
|
||||
isUpdatedDeviceOriginGroup = false
|
||||
}
|
||||
else {
|
||||
users = _.xor(data.new_val.users, data.old_val.users)
|
||||
devices = _.xor(data.new_val.devices, data.old_val.devices)
|
||||
isChangedDates =
|
||||
data.old_val.dates.length !== data.new_val.dates.length ||
|
||||
data.old_val.dates[0].start.getTime() !==
|
||||
data.new_val.dates[0].start.getTime() ||
|
||||
data.old_val.dates[0].stop.getTime() !==
|
||||
data.new_val.dates[0].stop.getTime()
|
||||
isActive = data.new_val.isActive
|
||||
isBecomeActive = !data.old_val.isActive && data.new_val.isActive
|
||||
isBecomeUnactive = data.old_val.isActive && !data.new_val.isActive
|
||||
isAddedUser = data.new_val.users.length > data.old_val.users.length
|
||||
isAddedDevice = data.new_val.devices.length > data.old_val.devices.length
|
||||
isUpdatedDeviceOriginGroup =
|
||||
data.new_val.ticket !== null &&
|
||||
(data.old_val.ticket === null ||
|
||||
data.new_val.ticket.signature !== data.old_val.ticket.signature)
|
||||
|
||||
if (!isUpdatedDeviceOriginGroup) {
|
||||
sendGroupChange(
|
||||
data.new_val
|
||||
, _.union(data.old_val.users, data.new_val.users)
|
||||
, isChangedDates
|
||||
, data.old_val.class !== data.new_val.class
|
||||
, isAddedUser
|
||||
, users
|
||||
, isAddedDevice
|
||||
, devices
|
||||
, 'updated'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (isUpdatedDeviceOriginGroup) {
|
||||
return doUpdateDeviceOriginGroup(data.new_val)
|
||||
}
|
||||
else if (isBecomeActive && data.new_val.devices.length) {
|
||||
return doUpdateDevicesCurrentGroup(data.new_val, data.new_val.devices)
|
||||
}
|
||||
else if (isBecomeUnactive && data.new_val.devices.length) {
|
||||
return doUpdateDevicesCurrentGroupFromOrigin(data.new_val.devices)
|
||||
}
|
||||
else if (devices.length && isActive && !apiutil.isOriginGroup(data.old_val.class)) {
|
||||
return treatGroupDevicesChange(data.old_val, data.new_val, devices, isAddedDevice)
|
||||
}
|
||||
else if (data.new_val === null) {
|
||||
return treatGroupDeletion(data.old_val)
|
||||
}
|
||||
else if (isChangedDates && isActive) {
|
||||
return doUpdateDevicesCurrentGroupDates(data.new_val)
|
||||
}
|
||||
else if (users.length) {
|
||||
return treatGroupUsersChange(data.old_val, users, isActive, isAddedUser)
|
||||
}
|
||||
return true
|
||||
})
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.error('An error occured during GROUPS table watching', err.stack)
|
||||
})
|
||||
}
|
94
lib/units/groups-engine/watchers/users.js
Normal file
94
lib/units/groups-engine/watchers/users.js
Normal file
|
@ -0,0 +1,94 @@
|
|||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
const timeutil = require('../../../util/timeutil')
|
||||
const r = require('rethinkdb')
|
||||
const _ = require('lodash')
|
||||
const logger = require('../../../util/logger')
|
||||
const wireutil = require('../../../wire/util')
|
||||
const wire = require('../../../wire')
|
||||
const db = require('../../../db')
|
||||
|
||||
module.exports = function(pushdev) {
|
||||
const log = logger.createLogger('watcher-users')
|
||||
|
||||
function sendUserChange(user, isAddedGroup, groups, action, targets) {
|
||||
pushdev.send([
|
||||
wireutil.global
|
||||
, wireutil.envelope(
|
||||
new wire.UserChangeMessage(
|
||||
user
|
||||
, isAddedGroup
|
||||
, groups
|
||||
, action
|
||||
, targets
|
||||
, timeutil.now('nano')))
|
||||
])
|
||||
}
|
||||
|
||||
db.run(r
|
||||
.table('users')
|
||||
.pluck(
|
||||
'email'
|
||||
, 'name'
|
||||
, 'privilege'
|
||||
, {groups: ['quotas', 'subscribed']
|
||||
})
|
||||
.changes(), function(err, cursor) {
|
||||
if (err) {
|
||||
throw err
|
||||
}
|
||||
return cursor
|
||||
})
|
||||
.then(function(cursor) {
|
||||
cursor.each(function(err, data) {
|
||||
if (err) {
|
||||
throw err
|
||||
}
|
||||
if (data.old_val === null) {
|
||||
sendUserChange(data.new_val, false, [], 'created', ['settings'])
|
||||
}
|
||||
else if (data.new_val === null) {
|
||||
sendUserChange(data.old_val, false, [], 'deleted', ['settings'])
|
||||
}
|
||||
else {
|
||||
const targets = []
|
||||
|
||||
if (!_.isEqual(
|
||||
data.new_val.groups.quotas.allocated
|
||||
, data.old_val.groups.quotas.allocated)) {
|
||||
targets.push('settings')
|
||||
targets.push('view')
|
||||
}
|
||||
else if (!_.isEqual(
|
||||
data.new_val.groups.quotas.consumed
|
||||
, data.old_val.groups.quotas.consumed)) {
|
||||
targets.push('view')
|
||||
}
|
||||
else if (data.new_val.groups.quotas.defaultGroupsNumber !==
|
||||
data.old_val.groups.quotas.defaultGroupsNumber ||
|
||||
data.new_val.groups.quotas.defaultGroupsDuration !==
|
||||
data.old_val.groups.quotas.defaultGroupsDuration ||
|
||||
data.new_val.groups.quotas.defaultGroupsRepetitions !==
|
||||
data.old_val.groups.quotas.defaultGroupsRepetitions ||
|
||||
data.new_val.groups.quotas.repetitions !==
|
||||
data.old_val.groups.quotas.repetitions ||
|
||||
!_.isEqual(data.new_val.groups.subscribed, data.old_val.groups.subscribed)) {
|
||||
targets.push('settings')
|
||||
}
|
||||
if (targets.length) {
|
||||
sendUserChange(
|
||||
data.new_val
|
||||
, data.new_val.groups.subscribed.length > data.old_val.groups.subscribed.length
|
||||
, _.xor(data.new_val.groups.subscribed, data.old_val.groups.subscribed)
|
||||
, 'updated'
|
||||
, targets)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.error('An error occured during USERS table watching', err.stack)
|
||||
})
|
||||
}
|
|
@ -1,3 +1,7 @@
|
|||
/**
|
||||
* Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
var Promise = require('bluebird')
|
||||
|
||||
var logger = require('../../util/logger')
|
||||
|
@ -55,17 +59,70 @@ module.exports = db.ensureConnectivity(function(options) {
|
|||
})
|
||||
|
||||
devDealer.on('message', wirerouter()
|
||||
.on(wire.UpdateAccessTokenMessage, function(channel, message, data) {
|
||||
appDealer.send([channel, data])
|
||||
})
|
||||
.on(wire.DeleteUserMessage, function(channel, message, data) {
|
||||
appDealer.send([channel, data])
|
||||
})
|
||||
.on(wire.DeviceChangeMessage, function(channel, message, data) {
|
||||
appDealer.send([channel, data])
|
||||
})
|
||||
.on(wire.UserChangeMessage, function(channel, message, data) {
|
||||
appDealer.send([channel, data])
|
||||
})
|
||||
.on(wire.GroupChangeMessage, function(channel, message, data) {
|
||||
appDealer.send([channel, data])
|
||||
})
|
||||
.on(wire.DeviceGroupChangeMessage, function(channel, message, data) {
|
||||
appDealer.send([channel, data])
|
||||
})
|
||||
.on(wire.GroupUserChangeMessage, function(channel, message, data) {
|
||||
appDealer.send([channel, data])
|
||||
})
|
||||
// Initial device message
|
||||
.on(wire.DeviceIntroductionMessage, function(channel, message, data) {
|
||||
.on(wire.DeviceIntroductionMessage, function(channel, message) {
|
||||
dbapi.saveDeviceInitialState(message.serial, message)
|
||||
.then(function() {
|
||||
.then(function(device) {
|
||||
devDealer.send([
|
||||
message.provider.channel
|
||||
, wireutil.envelope(new wire.DeviceRegisteredMessage(
|
||||
message.serial
|
||||
))
|
||||
])
|
||||
appDealer.send([channel, data])
|
||||
appDealer.send([
|
||||
channel
|
||||
, wireutil.envelope(new wire.DeviceIntroductionMessage(
|
||||
message.serial
|
||||
, message.status
|
||||
, new wire.ProviderMessage(
|
||||
message.provider.channel
|
||||
, message.provider.name
|
||||
)
|
||||
, new wire.DeviceGroupMessage(
|
||||
device.group.id
|
||||
, device.group.name
|
||||
, new wire.DeviceGroupOwnerMessage(
|
||||
device.group.owner.email
|
||||
, device.group.owner.name
|
||||
)
|
||||
, new wire.DeviceGroupLifetimeMessage(
|
||||
device.group.lifeTime.start.getTime()
|
||||
, device.group.lifeTime.stop.getTime()
|
||||
)
|
||||
, device.group.class
|
||||
, device.group.repetitions
|
||||
, device.group.originName
|
||||
)
|
||||
))
|
||||
])
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.error(
|
||||
'Unable to save the initial state of Device "%s"'
|
||||
, message.serial
|
||||
, err.stack
|
||||
)
|
||||
})
|
||||
})
|
||||
// Workerless messages
|
||||
|
|
|
@ -37,7 +37,7 @@ module.exports = function(options) {
|
|||
})
|
||||
.catch(function(err) {
|
||||
log.error('Unable to read manifest of "%s"', req.params.id, err.stack)
|
||||
res.status(500)
|
||||
res.status(200)
|
||||
.json({
|
||||
success: false
|
||||
})
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/**
|
||||
* Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
var http = require('http')
|
||||
var events = require('events')
|
||||
var util = require('util')
|
||||
|
@ -23,6 +27,8 @@ var ip = require('./middleware/remote-ip')
|
|||
var auth = require('./middleware/auth')
|
||||
var jwtutil = require('../../util/jwtutil')
|
||||
|
||||
const apiutil = require('../../util/apiutil')
|
||||
|
||||
module.exports = function(options) {
|
||||
var log = logger.createLogger('websocket')
|
||||
var server = http.createServer()
|
||||
|
@ -118,23 +124,99 @@ module.exports = function(options) {
|
|||
}
|
||||
}
|
||||
|
||||
let disconnectSocket
|
||||
var messageListener = wirerouter()
|
||||
.on(wire.UpdateAccessTokenMessage, function() {
|
||||
socket.emit('user.keys.accessToken.updated')
|
||||
})
|
||||
.on(wire.DeleteUserMessage, function() {
|
||||
disconnectSocket(true)
|
||||
})
|
||||
.on(wire.DeviceChangeMessage, function(channel, message) {
|
||||
if (user.groups.subscribed.indexOf(message.device.group.origin) > -1 ||
|
||||
user.groups.subscribed.indexOf(message.oldOriginGroupId) > -1) {
|
||||
socket.emit('user.settings.devices.' + message.action, message)
|
||||
}
|
||||
})
|
||||
.on(wire.UserChangeMessage, function(channel, message) {
|
||||
Promise.map(message.targets, function(target) {
|
||||
socket.emit('user.' + target + '.users.' + message.action, message)
|
||||
})
|
||||
})
|
||||
.on(wire.GroupChangeMessage, function(channel, message) {
|
||||
if (user.privilege === 'admin' ||
|
||||
user.email === message.group.owner.email ||
|
||||
!apiutil.isOriginGroup(message.group.class) &&
|
||||
(message.action === 'deleted' ||
|
||||
message.action === 'updated' &&
|
||||
(message.isChangedDates || message.isChangedClass || message.devices.length))) {
|
||||
socket.emit('user.settings.groups.' + message.action, message)
|
||||
}
|
||||
if (message.subscribers.indexOf(user.email) > -1) {
|
||||
socket.emit('user.view.groups.' + message.action, message)
|
||||
}
|
||||
})
|
||||
.on(wire.DeviceGroupChangeMessage, function(channel, message) {
|
||||
if (user.groups.subscribed.indexOf(message.id) > -1) {
|
||||
if (user.groups.subscribed.indexOf(message.group.id) > -1) {
|
||||
socket.emit('device.updateGroupDevice', {
|
||||
important: true
|
||||
, data: {
|
||||
serial: message.serial
|
||||
, group: message.group
|
||||
}
|
||||
})
|
||||
}
|
||||
else {
|
||||
socket.emit('device.removeGroupDevices', {important: true, devices: [message.serial]})
|
||||
}
|
||||
}
|
||||
else if (user.groups.subscribed.indexOf(message.group.id) > -1) {
|
||||
socket.emit('device.addGroupDevices', {important: true, devices: [message.serial]})
|
||||
}
|
||||
})
|
||||
.on(wire.GroupUserChangeMessage, function(channel, message) {
|
||||
if (message.users.indexOf(user.email) > -1) {
|
||||
if (message.isAdded) {
|
||||
user.groups.subscribed = _.union(user.groups.subscribed, [message.id])
|
||||
if (message.devices.length) {
|
||||
socket.emit('device.addGroupDevices', {important: true, devices: message.devices})
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (message.devices.length) {
|
||||
socket.emit('device.removeGroupDevices', {important: true, devices: message.devices})
|
||||
}
|
||||
if (message.isDeletedLater) {
|
||||
setTimeout(function() {
|
||||
user.groups.subscribed = _.without(user.groups.subscribed, message.id)
|
||||
}, 5000)
|
||||
}
|
||||
else {
|
||||
user.groups.subscribed = _.without(user.groups.subscribed, message.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.on(wire.DeviceLogMessage, function(channel, message) {
|
||||
socket.emit('device.log', message)
|
||||
})
|
||||
.on(wire.DeviceIntroductionMessage, function(channel, message) {
|
||||
socket.emit('device.add', {
|
||||
important: true
|
||||
, data: {
|
||||
serial: message.serial
|
||||
, present: false
|
||||
, provider: message.provider
|
||||
, owner: null
|
||||
, status: message.status
|
||||
, ready: false
|
||||
, reverseForwards: []
|
||||
}
|
||||
})
|
||||
if (user.groups.subscribed.indexOf(message.group.id) > -1) {
|
||||
socket.emit('device.add', {
|
||||
important: true
|
||||
, data: {
|
||||
serial: message.serial
|
||||
, present: true
|
||||
, provider: message.provider
|
||||
, owner: null
|
||||
, status: message.status
|
||||
, ready: false
|
||||
, reverseForwards: []
|
||||
, group: message.group
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
.on(wire.DeviceReadyMessage, function(channel, message) {
|
||||
socket.emit('device.change', {
|
||||
|
@ -307,6 +389,7 @@ module.exports = function(options) {
|
|||
joinChannel(user.group)
|
||||
|
||||
new Promise(function(resolve) {
|
||||
disconnectSocket = resolve
|
||||
socket.on('disconnect', resolve)
|
||||
// Global messages for all clients using socket.io
|
||||
//
|
||||
|
@ -314,15 +397,19 @@ module.exports = function(options) {
|
|||
.on('device.note', function(data) {
|
||||
return dbapi.setDeviceNote(data.serial, data.note)
|
||||
.then(function() {
|
||||
return dbapi.loadDevice(data.serial)
|
||||
return dbapi.loadDevice(user.groups.subscribed, data.serial)
|
||||
})
|
||||
.then(function(device) {
|
||||
if (device) {
|
||||
io.emit('device.change', {
|
||||
important: true
|
||||
, data: {
|
||||
serial: device.serial
|
||||
, notes: device.notes
|
||||
.then(function(cursor) {
|
||||
if (cursor) {
|
||||
cursor.next(function(err, device) {
|
||||
if (!err) {
|
||||
io.emit('device.change', {
|
||||
important: true
|
||||
, data: {
|
||||
serial: device.serial
|
||||
, notes: device.notes
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -364,7 +451,7 @@ module.exports = function(options) {
|
|||
.on('user.keys.accessToken.remove', function(data) {
|
||||
return dbapi.removeUserAccessToken(user.email, data.title)
|
||||
.then(function() {
|
||||
socket.emit('user.keys.accessToken.removed', data.title)
|
||||
socket.emit('user.keys.accessToken.updated')
|
||||
})
|
||||
})
|
||||
.on('user.keys.adb.add', function(data) {
|
||||
|
@ -916,6 +1003,7 @@ module.exports = function(options) {
|
|||
channelRouter.removeListener(channel, messageListener)
|
||||
sub.unsubscribe(channel)
|
||||
})
|
||||
socket.disconnect(true)
|
||||
})
|
||||
.catch(function(err) {
|
||||
// Cannot guarantee integrity of client
|
||||
|
@ -923,8 +1011,7 @@ module.exports = function(options) {
|
|||
'Client had an error, disconnecting due to probable loss of integrity'
|
||||
, err.stack
|
||||
)
|
||||
|
||||
socket.disconnect(true)
|
||||
// move 'socket.disconnect(true)' statement to finally block instead!
|
||||
})
|
||||
})
|
||||
|
||||
|
|
257
lib/util/apiutil.js
Normal file
257
lib/util/apiutil.js
Normal file
|
@ -0,0 +1,257 @@
|
|||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
const Promise = require('bluebird')
|
||||
const _ = require('lodash')
|
||||
const logger = require('./logger')
|
||||
const datautil = require('./datautil')
|
||||
|
||||
const apiutil = Object.create(null)
|
||||
const log = logger.createLogger('api:controllers:apiutil')
|
||||
|
||||
apiutil.PENDING = 'pending'
|
||||
apiutil.READY = 'ready'
|
||||
apiutil.WAITING = 'waiting'
|
||||
|
||||
apiutil.BOOKABLE = 'bookable'
|
||||
apiutil.STANDARD = 'standard'
|
||||
apiutil.ONCE = 'once'
|
||||
apiutil.DEBUG = 'debug'
|
||||
apiutil.ORIGIN = 'origin'
|
||||
apiutil.STANDARDIZABLE = 'standardizable'
|
||||
|
||||
apiutil.ROOT = 'root'
|
||||
apiutil.ADMIN = 'admin'
|
||||
apiutil.USER = 'user'
|
||||
|
||||
apiutil.FIVE_MN = 300 * 1000
|
||||
apiutil.ONE_HOUR = 3600 * 1000
|
||||
apiutil.ONE_DAY = 24 * apiutil.ONE_HOUR
|
||||
apiutil.ONE_WEEK = 7 * apiutil.ONE_DAY
|
||||
apiutil.ONE_MONTH = 30 * apiutil.ONE_DAY
|
||||
apiutil.ONE_QUATER = 3 * apiutil.ONE_MONTH
|
||||
apiutil.ONE_HALF_YEAR = 6 * apiutil.ONE_MONTH
|
||||
apiutil.ONE_YEAR = 365 * apiutil.ONE_DAY
|
||||
|
||||
apiutil.MAX_USER_GROUPS_NUMBER = 5
|
||||
apiutil.MAX_USER_GROUPS_DURATION = 15 * apiutil.ONE_DAY
|
||||
apiutil.MAX_USER_GROUPS_REPETITIONS = 10
|
||||
|
||||
apiutil.CLASS_DURATION = {
|
||||
once: Infinity
|
||||
, bookable: Infinity
|
||||
, standard: Infinity
|
||||
, hourly: apiutil.ONE_HOUR
|
||||
, daily: apiutil.ONE_DAY
|
||||
, weekly: apiutil.ONE_WEEK
|
||||
, monthly: apiutil.ONE_MONTH
|
||||
, quaterly: apiutil.ONE_QUATER
|
||||
, halfyearly: apiutil.ONE_HALF_YEAR
|
||||
, yearly: apiutil.ONE_YEAR
|
||||
, debug: apiutil.FIVE_MN
|
||||
}
|
||||
|
||||
apiutil.isOriginGroup = function(_class) {
|
||||
return _class === apiutil.BOOKABLE || _class === apiutil.STANDARD
|
||||
}
|
||||
|
||||
apiutil.isAdminGroup = function(_class) {
|
||||
return apiutil.isOriginGroup(_class) || _class === apiutil.DEBUG
|
||||
}
|
||||
|
||||
apiutil.internalError = function(res, ...args) {
|
||||
log.error.apply(log, args)
|
||||
apiutil.respond(res, 500, 'Internal Server Error')
|
||||
}
|
||||
|
||||
apiutil.respond = function(res, code, message, data) {
|
||||
const status = code >= 200 && code < 300
|
||||
const response = {
|
||||
success: status
|
||||
, description: message
|
||||
}
|
||||
|
||||
if (data) {
|
||||
for (const key in data) {
|
||||
if (data.hasOwnProperty(key)) {
|
||||
response[key] = data[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
res.status(code).json(response)
|
||||
return status
|
||||
}
|
||||
|
||||
apiutil.publishGroup = function(group) {
|
||||
// delete group.lock
|
||||
delete group.createdAt
|
||||
delete group.ticket
|
||||
return group
|
||||
}
|
||||
|
||||
apiutil.publishDevice = function(device, user) {
|
||||
datautil.normalize(device, user)
|
||||
// delete device.group.lock
|
||||
return device
|
||||
}
|
||||
|
||||
apiutil.publishUser = function(user) {
|
||||
// delete user.groups.lock
|
||||
return user
|
||||
}
|
||||
|
||||
apiutil.publishAccessToken = function(token) {
|
||||
delete token.email
|
||||
delete token.jwt
|
||||
return token
|
||||
}
|
||||
|
||||
apiutil.filterDevice = function(req, device) {
|
||||
const fields = req.swagger.params.fields.value
|
||||
|
||||
if (fields) {
|
||||
return _.pick(apiutil.publishDevice(device, req.user), fields.split(','))
|
||||
}
|
||||
return apiutil.publishDevice(device, req.user)
|
||||
}
|
||||
|
||||
apiutil.computeDuration = function(group, deviceNumber) {
|
||||
return (group.devices.length + deviceNumber) *
|
||||
(group.dates[0].stop - group.dates[0].start) *
|
||||
(group.repetitions + 1)
|
||||
}
|
||||
|
||||
apiutil.lightComputeStats = function(res, stats) {
|
||||
if (stats.locked) {
|
||||
apiutil.respond(res, 503, 'Server too busy, please try again later')
|
||||
return Promise.reject('busy')
|
||||
}
|
||||
return 'not found'
|
||||
}
|
||||
|
||||
apiutil.computeStats = function(res, stats, objectName, ...lock) {
|
||||
if (!stats.replaced) {
|
||||
if (stats.skipped) {
|
||||
return apiutil.respond(res, 404, `Not Found (${objectName})`)
|
||||
}
|
||||
if (stats.locked) {
|
||||
return apiutil.respond(res, 503, 'Server too busy, please try again later')
|
||||
}
|
||||
return apiutil.respond(res, 403, `Forbidden (${objectName})`)
|
||||
}
|
||||
if (lock.length) {
|
||||
lock[0][objectName] = stats.changes[0].new_val
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
apiutil.lockResult = function(stats) {
|
||||
const result = {status: false, data: stats}
|
||||
|
||||
if (stats.replaced || stats.skipped) {
|
||||
result.status = true
|
||||
result.data.locked = false
|
||||
}
|
||||
else {
|
||||
result.data.locked = true
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
apiutil.lockDeviceResult = function(stats, fn, groups, serial) {
|
||||
const result = apiutil.lockResult(stats)
|
||||
if (!result.status) {
|
||||
return fn(groups, serial).then(function(devices) {
|
||||
if (!devices.length) {
|
||||
result.data.locked = false
|
||||
result.status = true
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
apiutil.setIntervalWrapper = function(fn, numTimes, delay) {
|
||||
return fn().then(function(result) {
|
||||
if (result.status) {
|
||||
return result.data
|
||||
}
|
||||
return new Promise(function(resolve, reject) {
|
||||
let counter = 0
|
||||
const interval = setInterval(function() {
|
||||
return fn().then(function(result) {
|
||||
if (result.status || ++counter === numTimes) {
|
||||
if (!result.status && counter === numTimes) {
|
||||
log.debug('%s() failed %s times in a loop!', fn.name, counter)
|
||||
}
|
||||
clearInterval(interval)
|
||||
resolve(result.data)
|
||||
}
|
||||
})
|
||||
.catch(function(err) {
|
||||
clearInterval(interval)
|
||||
reject(err)
|
||||
})
|
||||
}, delay)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
apiutil.redirectApiWrapper = function(field, fn, req, res) {
|
||||
if (typeof req.body === 'undefined') {
|
||||
req.body = {}
|
||||
}
|
||||
req.body[field + 's'] = req.swagger.params[field].value
|
||||
req.swagger.params.redirected = {value: true}
|
||||
fn(req, res)
|
||||
}
|
||||
|
||||
apiutil.computeGroupDates = function(lifeTime, _class, repetitions) {
|
||||
const dates = new Array(lifeTime)
|
||||
|
||||
for(let repetition = 1
|
||||
, currentLifeTime = {
|
||||
start: new Date(lifeTime.start.getTime())
|
||||
, stop: new Date(lifeTime.stop.getTime())
|
||||
}
|
||||
; repetition <= repetitions
|
||||
; repetition++) {
|
||||
currentLifeTime.start = new Date(
|
||||
currentLifeTime.start.getTime() +
|
||||
apiutil.CLASS_DURATION[_class]
|
||||
)
|
||||
currentLifeTime.stop = new Date(
|
||||
currentLifeTime.stop.getTime() +
|
||||
apiutil.CLASS_DURATION[_class]
|
||||
)
|
||||
dates.push({
|
||||
start: new Date(currentLifeTime.start.getTime())
|
||||
, stop: new Date(currentLifeTime.stop.getTime())
|
||||
})
|
||||
}
|
||||
return dates
|
||||
}
|
||||
|
||||
apiutil.checkBodyParameter = function(body, parameter) {
|
||||
return typeof body !== 'undefined' && typeof body[parameter] !== 'undefined'
|
||||
}
|
||||
|
||||
apiutil.getBodyParameter = function(body, parameter) {
|
||||
let undef
|
||||
|
||||
return apiutil.checkBodyParameter(body, parameter) ? body[parameter] : undef
|
||||
}
|
||||
|
||||
apiutil.checkQueryParameter = function(parameter) {
|
||||
return typeof parameter !== 'undefined' && typeof parameter.value !== 'undefined'
|
||||
}
|
||||
|
||||
apiutil.getQueryParameter = function(parameter) {
|
||||
let undef
|
||||
|
||||
return apiutil.checkQueryParameter(parameter) ? parameter.value : undef
|
||||
}
|
||||
|
||||
module.exports = apiutil
|
|
@ -1,3 +1,7 @@
|
|||
/**
|
||||
* Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
var deviceData = require('stf-device-db')
|
||||
var browserData = require('stf-browser-db')
|
||||
|
||||
|
@ -41,13 +45,14 @@ datautil.applyBrowsers = function(device) {
|
|||
}
|
||||
|
||||
datautil.applyOwner = function(device, user) {
|
||||
device.using = !!device.owner && device.owner.email === user.email
|
||||
device.using = !!device.owner &&
|
||||
(device.owner.email === user.email || user.privilege === 'admin')
|
||||
return device
|
||||
}
|
||||
|
||||
// Only owner can see this information
|
||||
datautil.applyOwnerOnlyInfo = function(device, user) {
|
||||
if (device.owner && device.owner.email === user.email) {
|
||||
if (device.owner && (device.owner.email === user.email || user.privilege === 'admin')) {
|
||||
// No-op
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/**
|
||||
* Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
var logger = require('./logger')
|
||||
|
||||
var log = logger.createLogger('util:deviceutil')
|
||||
|
@ -8,7 +12,7 @@ deviceutil.isOwnedByUser = function(device, user) {
|
|||
return device.present &&
|
||||
device.ready &&
|
||||
device.owner &&
|
||||
device.owner.email === user.email &&
|
||||
(device.owner.email === user.email || user.privilege === 'admin') &&
|
||||
device.using
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ var util = require('util')
|
|||
|
||||
var split = require('split')
|
||||
var Promise = require('bluebird')
|
||||
var androidDeviceList = require('android-device-list')
|
||||
|
||||
var devutil = module.exports = Object.create(null)
|
||||
|
||||
|
@ -135,6 +136,7 @@ devutil.makeIdentity = function(serial, properties) {
|
|||
var product = properties['ro.product.name']
|
||||
var cpuPlatform = properties['ro.board.platform']
|
||||
var openGLESVersion = properties['ro.opengles.version']
|
||||
var marketName = properties['ro.product.device']
|
||||
|
||||
openGLESVersion = parseInt(openGLESVersion, 10)
|
||||
if (isNaN(openGLESVersion)) {
|
||||
|
@ -157,6 +159,13 @@ devutil.makeIdentity = function(serial, properties) {
|
|||
model = model.substr(manufacturer.length)
|
||||
}
|
||||
|
||||
if (marketName) {
|
||||
var devices = androidDeviceList.getDevicesByDeviceId(marketName)
|
||||
if (devices.length > 0) {
|
||||
marketName = devices[0].name
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up remaining model name
|
||||
// model = model.replace(/[_ ]/g, '')
|
||||
return {
|
||||
|
@ -171,5 +180,6 @@ devutil.makeIdentity = function(serial, properties) {
|
|||
, product: product
|
||||
, cpuPlatform: cpuPlatform
|
||||
, openGLESVersion: openGLESVersion
|
||||
, marketName: marketName
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/**
|
||||
* Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
var util = require('util')
|
||||
|
||||
var uuid = require('uuid')
|
||||
|
@ -7,10 +11,8 @@ var dbapi = require('../db/api')
|
|||
var devices = require('stf-device-db/dist/devices-latest')
|
||||
|
||||
module.exports.generate = function(wantedModel) {
|
||||
var serial = util.format(
|
||||
'fake-%s'
|
||||
, uuid.v4(null, new Buffer(16)).toString('base64')
|
||||
)
|
||||
// no base64 because some characters as '=' or '/' are not compatible through API (delete devices)
|
||||
const serial = 'fake-' + util.format('%s', uuid.v4()).replace(/-/g, '')
|
||||
|
||||
return dbapi.saveDeviceInitialState(serial, {
|
||||
provider: {
|
||||
|
@ -28,7 +30,7 @@ module.exports.generate = function(wantedModel) {
|
|||
, model: model
|
||||
, version: '4.1.2'
|
||||
, abi: 'armeabi-v7a'
|
||||
, sdk: 8 + Math.floor(Math.random() * 12)
|
||||
, sdk: (8 + Math.floor(Math.random() * 12)).toString() // string required!
|
||||
, display: {
|
||||
density: 3
|
||||
, fps: 60
|
||||
|
@ -49,6 +51,9 @@ module.exports.generate = function(wantedModel) {
|
|||
, phoneNumber: '0000000000'
|
||||
}
|
||||
, product: model
|
||||
, cpuPlatform: 'msm8996'
|
||||
, openGLESVersion: '3.1'
|
||||
, marketName: 'Bar F9+'
|
||||
})
|
||||
})
|
||||
.then(function() {
|
||||
|
|
42
lib/util/fakegroup.js
Normal file
42
lib/util/fakegroup.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
const util = require('util')
|
||||
const uuid = require('uuid')
|
||||
const dbapi = require('../db/api')
|
||||
const apiutil = require('./apiutil')
|
||||
|
||||
module.exports.generate = function() {
|
||||
return dbapi.getRootGroup().then(function(rootGroup) {
|
||||
const now = Date.now()
|
||||
|
||||
return dbapi.createUserGroup({
|
||||
name: 'fakegroup-' + util.format('%s', uuid.v4()).replace(/-/g, '')
|
||||
, owner: {
|
||||
email: rootGroup.owner.email
|
||||
, name: rootGroup.owner.name
|
||||
}
|
||||
, privilege: apiutil.ADMIN
|
||||
, class: apiutil.BOOKABLE
|
||||
, repetitions: 0
|
||||
, isActive: true
|
||||
, dates: apiutil.computeGroupDates(
|
||||
{
|
||||
start: new Date(now)
|
||||
, stop: new Date(now + apiutil.ONE_YEAR)
|
||||
}
|
||||
, apiutil.BOOKABLE
|
||||
, 0
|
||||
)
|
||||
, duration: 0
|
||||
, state: apiutil.READY
|
||||
})
|
||||
.then(function(group) {
|
||||
if (group) {
|
||||
return group.id
|
||||
}
|
||||
throw new Error('Forbidden (groups number quota is reached)')
|
||||
})
|
||||
})
|
||||
}
|
14
lib/util/fakeuser.js
Normal file
14
lib/util/fakeuser.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
const util = require('util')
|
||||
const uuid = require('uuid')
|
||||
const dbapi = require('../db/api')
|
||||
|
||||
module.exports.generate = function() {
|
||||
const name = 'fakeuser-' + util.format('%s', uuid.v4()).replace(/-/g, '')
|
||||
const email = name + '@openstf.com'
|
||||
|
||||
return dbapi.createUser(email, name, '127.0.0.1').return(email)
|
||||
}
|
69
lib/util/lockutil.js
Normal file
69
lib/util/lockutil.js
Normal file
|
@ -0,0 +1,69 @@
|
|||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
const apiutil = require('./apiutil')
|
||||
const dbapi = require('../db/api')
|
||||
|
||||
const lockutil = Object.create(null)
|
||||
|
||||
lockutil.unlockDevice = function(lock) {
|
||||
if (lock.device) {
|
||||
dbapi.unlockDevice(lock.device.serial)
|
||||
}
|
||||
}
|
||||
|
||||
lockutil.lockUser = function(email, res, lock) {
|
||||
return dbapi.lockUser(email)
|
||||
.then(function(stats) {
|
||||
return apiutil.computeStats(res, stats, 'user', lock)
|
||||
})
|
||||
}
|
||||
|
||||
lockutil.unlockUser = function(lock) {
|
||||
if (lock.user) {
|
||||
dbapi.unlockUser(lock.user.email)
|
||||
}
|
||||
}
|
||||
|
||||
lockutil.lockGroupAndUser = function(req, res, lock) {
|
||||
return lockutil.lockGroup(req, res, lock).then(function(lockingSuccessed) {
|
||||
return lockingSuccessed ?
|
||||
lockutil.lockUser(req.user.email, res, lock) :
|
||||
false
|
||||
})
|
||||
}
|
||||
|
||||
lockutil.unlockGroupAndUser = function(lock) {
|
||||
lockutil.unlockGroup(lock)
|
||||
lockutil.unlockUser(lock)
|
||||
}
|
||||
|
||||
lockutil.lockGroup = function(req, res, lock) {
|
||||
const id = req.swagger.params.id.value
|
||||
const email = req.user.email
|
||||
|
||||
return dbapi.lockGroupByOwner(email, id).then(function(stats) {
|
||||
return apiutil.computeStats(res, stats, 'group', lock)
|
||||
})
|
||||
}
|
||||
|
||||
lockutil.unlockGroup = function(lock) {
|
||||
if (lock.group) {
|
||||
dbapi.unlockGroup(lock.group.id)
|
||||
}
|
||||
}
|
||||
|
||||
lockutil.unlockGroupAndDevice = function(lock) {
|
||||
lockutil.unlockGroup(lock)
|
||||
lockutil.unlockDevice(lock)
|
||||
}
|
||||
|
||||
lockutil.lockGenericDevice = function(req, res, lock, lockDevice) {
|
||||
return lockDevice(req.user.groups.subscribed, req.swagger.params.serial.value)
|
||||
.then(function(stats) {
|
||||
return apiutil.computeStats(res, stats, 'device', lock)
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = lockutil
|
|
@ -36,6 +36,8 @@ module.exports.readAll = function(stream) {
|
|||
stream.on('readable', readableListener)
|
||||
stream.on('end', endListener)
|
||||
|
||||
readableListener()
|
||||
|
||||
return resolver.promise.finally(function() {
|
||||
stream.removeListener('error', errorListener)
|
||||
stream.removeListener('readable', readableListener)
|
||||
|
|
22
lib/util/timeutil.js
Normal file
22
lib/util/timeutil.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
const timeutil = Object.create(null)
|
||||
|
||||
timeutil.now = function(unit) {
|
||||
const hrTime = process.hrtime()
|
||||
|
||||
switch (unit) {
|
||||
case 'milli':
|
||||
return hrTime[0] * 1000 + hrTime[1] / 1000000
|
||||
case 'micro':
|
||||
return hrTime[0] * 1000000 + hrTime[1] / 1000
|
||||
case 'nano':
|
||||
return hrTime[0] * 1000000000 + hrTime[1]
|
||||
default:
|
||||
return hrTime[0] * 1000000000 + hrTime[1]
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = timeutil
|
|
@ -1,3 +1,7 @@
|
|||
//
|
||||
// Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
//
|
||||
|
||||
// Message wrapper
|
||||
|
||||
enum MessageType {
|
||||
|
@ -78,6 +82,160 @@ enum MessageType {
|
|||
FileSystemGetMessage = 82;
|
||||
ConnectStartedMessage = 92;
|
||||
ConnectStoppedMessage = 93;
|
||||
GroupUserChangeMessage = 1200;
|
||||
DeviceGroupChangeMessage = 1201;
|
||||
DeviceOriginGroupMessage = 1202;
|
||||
DeleteUserMessage = 1203;
|
||||
UpdateAccessTokenMessage = 1204;
|
||||
GroupChangeMessage = 1205;
|
||||
UserChangeMessage = 1206;
|
||||
DeviceChangeMessage = 1207;
|
||||
}
|
||||
|
||||
message UpdateAccessTokenMessage {
|
||||
}
|
||||
|
||||
message DeleteUserMessage {
|
||||
required string email = 1;
|
||||
}
|
||||
|
||||
message DeviceOriginGroupMessage {
|
||||
required string signature = 1;
|
||||
}
|
||||
|
||||
message UserQuotasDetailField {
|
||||
required double duration = 1;
|
||||
required uint32 number = 2;
|
||||
}
|
||||
|
||||
message UserQuotasField {
|
||||
required UserQuotasDetailField allocated = 1;
|
||||
required UserQuotasDetailField consumed = 2;
|
||||
required uint32 defaultGroupsDuration = 3;
|
||||
required uint32 defaultGroupsNumber = 4;
|
||||
required uint32 defaultGroupsRepetitions = 5;
|
||||
required uint32 repetitions = 6;
|
||||
}
|
||||
|
||||
message UserGroupsField {
|
||||
required UserQuotasField quotas = 1;
|
||||
repeated string subscribed = 2;
|
||||
}
|
||||
|
||||
message UserField {
|
||||
required string email = 1;
|
||||
required string name = 2;
|
||||
required string privilege = 3;
|
||||
required UserGroupsField groups = 4;
|
||||
}
|
||||
|
||||
message UserChangeMessage {
|
||||
required UserField user = 1;
|
||||
required bool isAddedGroup = 2;
|
||||
repeated string groups = 3;
|
||||
required string action = 4;
|
||||
repeated string targets = 5;
|
||||
required double timeStamp = 6;
|
||||
}
|
||||
|
||||
message DeviceNetworkField {
|
||||
optional string type = 1;
|
||||
optional string subtype = 2;
|
||||
}
|
||||
|
||||
message DeviceDisplayField {
|
||||
optional uint32 height = 1;
|
||||
optional uint32 width = 2;
|
||||
}
|
||||
|
||||
message DevicePhoneField {
|
||||
optional string imei = 1;
|
||||
}
|
||||
|
||||
message DeviceProviderField {
|
||||
optional string name = 1;
|
||||
}
|
||||
|
||||
message DeviceGroupField {
|
||||
optional string origin = 1;
|
||||
optional string originName = 2;
|
||||
}
|
||||
|
||||
message DeviceField {
|
||||
required string serial = 1;
|
||||
optional string model = 2;
|
||||
optional string version = 3;
|
||||
optional string operator = 4;
|
||||
optional DeviceNetworkField network = 5;
|
||||
optional DeviceDisplayField display = 6;
|
||||
optional string manufacturer = 7;
|
||||
optional string sdk = 8;
|
||||
optional string abi = 9;
|
||||
optional string cpuPlatform = 10;
|
||||
optional string openGLESVersion = 11;
|
||||
optional DevicePhoneField phone = 12;
|
||||
optional DeviceProviderField provider = 13;
|
||||
optional DeviceGroupField group = 14;
|
||||
optional string marketName = 15;
|
||||
}
|
||||
|
||||
message DeviceChangeMessage {
|
||||
required DeviceField device = 1;
|
||||
required string action = 2;
|
||||
required string oldOriginGroupId = 3;
|
||||
required double timeStamp = 4;
|
||||
}
|
||||
|
||||
message GroupDateField {
|
||||
required string start = 1;
|
||||
required string stop = 2;
|
||||
}
|
||||
|
||||
message GroupOwnerField {
|
||||
required string email = 1;
|
||||
required string name = 2;
|
||||
}
|
||||
|
||||
message GroupField {
|
||||
required string id = 1;
|
||||
required string name = 2;
|
||||
required string class = 3;
|
||||
required string privilege = 4;
|
||||
required GroupOwnerField owner = 5;
|
||||
repeated GroupDateField dates = 6;
|
||||
required uint32 duration = 7;
|
||||
required uint32 repetitions = 8;
|
||||
repeated string devices = 9;
|
||||
repeated string users = 10;
|
||||
required string state = 11;
|
||||
required bool isActive = 12;
|
||||
}
|
||||
|
||||
message GroupChangeMessage {
|
||||
required GroupField group = 1;
|
||||
required string action = 2;
|
||||
repeated string subscribers = 3;
|
||||
required bool isChangedDates = 4;
|
||||
required bool isChangedClass = 5;
|
||||
required bool isAddedUser = 6;
|
||||
repeated string users = 7;
|
||||
required bool isAddedDevice = 8;
|
||||
repeated string devices = 9;
|
||||
required double timeStamp = 10;
|
||||
}
|
||||
|
||||
message DeviceGroupChangeMessage {
|
||||
required string id = 1;
|
||||
required DeviceGroupMessage group = 2;
|
||||
required string serial = 3;
|
||||
}
|
||||
|
||||
message GroupUserChangeMessage {
|
||||
repeated string users = 1;
|
||||
required bool isAdded = 2;
|
||||
required string id = 3;
|
||||
required bool isDeletedLater = 4;
|
||||
repeated string devices = 5;
|
||||
}
|
||||
|
||||
message ConnectStartedMessage {
|
||||
|
@ -132,6 +290,26 @@ message DeviceLogMessage {
|
|||
|
||||
// Introductions
|
||||
|
||||
message DeviceGroupOwnerMessage {
|
||||
required string email = 1;
|
||||
required string name = 2;
|
||||
}
|
||||
|
||||
message DeviceGroupLifetimeMessage {
|
||||
required double start = 1;
|
||||
required double stop = 2;
|
||||
}
|
||||
|
||||
message DeviceGroupMessage {
|
||||
required string id = 1;
|
||||
required string name = 2;
|
||||
required DeviceGroupOwnerMessage owner = 3;
|
||||
required DeviceGroupLifetimeMessage lifeTime = 4;
|
||||
required string class = 5;
|
||||
required uint32 repetitions = 6;
|
||||
required string originName = 7;
|
||||
}
|
||||
|
||||
message ProviderMessage {
|
||||
required string channel = 1;
|
||||
required string name = 2;
|
||||
|
@ -145,6 +323,7 @@ message DeviceIntroductionMessage {
|
|||
required string serial = 1;
|
||||
required DeviceStatus status = 2;
|
||||
required ProviderMessage provider = 3;
|
||||
optional DeviceGroupMessage group = 4;
|
||||
}
|
||||
|
||||
message DeviceRegisteredMessage {
|
||||
|
@ -230,6 +409,7 @@ message DeviceIdentityMessage {
|
|||
optional string product = 12;
|
||||
optional string cpuPlatform = 13;
|
||||
optional string openGLESVersion = 14;
|
||||
optional string marketName = 15;
|
||||
}
|
||||
|
||||
message DeviceProperty {
|
||||
|
|
21
package.json
21
package.json
|
@ -37,6 +37,7 @@
|
|||
"adbkit": "^2.11.1",
|
||||
"adbkit-apkreader": "^3.1.1",
|
||||
"adbkit-monkey": "^1.0.1",
|
||||
"android-device-list": "^1.2.1",
|
||||
"aws-sdk": "^2.4.13",
|
||||
"basic-auth": "^1.0.3",
|
||||
"bluebird": "^2.10.1",
|
||||
|
@ -62,9 +63,9 @@
|
|||
"lodash": "^4.14.2",
|
||||
"markdown-serve": "^0.3.2",
|
||||
"mime": "^1.3.4",
|
||||
"minicap-prebuilt": "^2.3.0",
|
||||
"minicap-prebuilt-beta": "^2.4.0",
|
||||
"minimatch": "^3.0.3",
|
||||
"minitouch-prebuilt": "^1.2.0",
|
||||
"minitouch-prebuilt-beta": "^1.3.0",
|
||||
"my-local-ip": "^1.0.0",
|
||||
"openid": "^2.0.1",
|
||||
"passport": "^0.3.2",
|
||||
|
@ -109,6 +110,7 @@
|
|||
"exports-loader": "^0.6.2",
|
||||
"extract-text-webpack-plugin": "^1.0.1",
|
||||
"file-loader": "^0.9.0",
|
||||
"fs-extra": "^8.1.0",
|
||||
"gulp": "^3.8.11",
|
||||
"gulp-angular-gettext": "^2.1.0",
|
||||
"gulp-eslint": "^3.0.1",
|
||||
|
@ -118,15 +120,16 @@
|
|||
"gulp-run": "^1.6.12",
|
||||
"gulp-util": "^3.0.7",
|
||||
"html-loader": "^0.4.0",
|
||||
"http-https": "^1.0.0",
|
||||
"imports-loader": "^0.6.5",
|
||||
"jasmine-core": "^2.4.1",
|
||||
"jasmine-reporters": "^2.1.1",
|
||||
"jasmine-reporters": "^2.3.2",
|
||||
"json-loader": "^0.5.4",
|
||||
"karma": "^1.1.2",
|
||||
"karma-chrome-launcher": "^1.0.1",
|
||||
"karma": "^1.7.1",
|
||||
"karma-chrome-launcher": "^2.2.0",
|
||||
"karma-firefox-launcher": "^1.0.0",
|
||||
"karma-ie-launcher": "^1.0.0",
|
||||
"karma-jasmine": "^1.0.2",
|
||||
"karma-jasmine": "^2.0.1",
|
||||
"karma-junit-reporter": "^1.1.0",
|
||||
"karma-opera-launcher": "^1.0.0",
|
||||
"karma-phantomjs-launcher": "^1.0.0",
|
||||
|
@ -138,8 +141,8 @@
|
|||
"node-libs-browser": "^1.0.0",
|
||||
"node-sass": "^3.4.2",
|
||||
"phantomjs-prebuilt": "^2.1.11",
|
||||
"protractor": "^4.0.3",
|
||||
"protractor-html-screenshot-reporter": "0.0.21",
|
||||
"protractor": "^5.4.1",
|
||||
"protractor-html-reporter-2": "1.0.4",
|
||||
"raw-loader": "^0.5.1",
|
||||
"sass-loader": "^4.0.0",
|
||||
"script-loader": "^0.7.0",
|
||||
|
@ -151,7 +154,7 @@
|
|||
"then-jade": "^2.4.1",
|
||||
"url-loader": "^0.5.7",
|
||||
"webpack": "^1.12.11",
|
||||
"webpack-dev-server": "^1.14.1"
|
||||
"webpack-dev-server": "^3.1.11"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6.9"
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/**
|
||||
* Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
require.ensure([], function(require) {
|
||||
require('angular')
|
||||
require('angular-route')
|
||||
|
@ -10,13 +14,15 @@ require.ensure([], function(require) {
|
|||
require('angular-hotkeys').name,
|
||||
require('./layout').name,
|
||||
require('./device-list').name,
|
||||
require('./group-list').name,
|
||||
require('./control-panes').name,
|
||||
require('./menu').name,
|
||||
require('./settings').name,
|
||||
require('./docs').name,
|
||||
require('./user').name,
|
||||
require('./../common/lang').name,
|
||||
require('stf/standalone').name
|
||||
require('stf/standalone').name,
|
||||
require('./group-list').name
|
||||
])
|
||||
.config(function($routeProvider, $locationProvider) {
|
||||
$locationProvider.hashPrefix('!')
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
module.exports = function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
buttonStyle: '@?',
|
||||
columnData: '=',
|
||||
resetData: '&'
|
||||
},
|
||||
template: require('./column-choice.pug'),
|
||||
}
|
||||
}
|
23
res/app/components/stf/column-choice/column-choice.css
Normal file
23
res/app/components/stf/column-choice/column-choice.css
Normal file
|
@ -0,0 +1,23 @@
|
|||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
.stf-column-choice .stf-column-customize {
|
||||
white-space: nowrap;
|
||||
padding: 10px;
|
||||
padding-bottom: 0;
|
||||
column-count: 2;
|
||||
-moz-column-count: 2;
|
||||
-webkit-column-count: 2;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.stf-column-choice .stf-column-customize .checkbox {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.stf-column-choice .stf-column-customize .checkbox-label {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
|
24
res/app/components/stf/column-choice/column-choice.pug
Normal file
24
res/app/components/stf/column-choice/column-choice.pug
Normal file
|
@ -0,0 +1,24 @@
|
|||
//
|
||||
Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
//
|
||||
|
||||
.stf-column-choice
|
||||
.btn-group(uib-dropdown auto-close='outsideClick')
|
||||
button.btn.btn-sm.btn-primary-outline(
|
||||
style='margin-top: 5px; {{buttonStyle}}'
|
||||
type='button'
|
||||
uib-dropdown-toggle)
|
||||
i.fa.fa-columns
|
||||
span(translate) Customize
|
||||
ul.dropdown-menu.pointer.stf-column-customize(
|
||||
uib-dropdown-menu role='menu'
|
||||
ng-click='$event.stopPropagation()')
|
||||
li(ng-repeat='column in columnData')
|
||||
label.checkbox.pointer
|
||||
input(type='checkbox' ng-model='column.selected')
|
||||
span.checkbox-label(ng-bind-template='{{::column.name | translate}}')
|
||||
li
|
||||
button.btn.btn-xs.btn-danger-outline.checkbox(ng-click='resetData()')
|
||||
i.fa.fa-trash-o
|
||||
span(ng-bind='"Reset"|translate')
|
||||
|
12
res/app/components/stf/column-choice/index.js
Normal file
12
res/app/components/stf/column-choice/index.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
require('./column-choice.css')
|
||||
|
||||
module.exports = angular.module('stf.column-choice', [
|
||||
require('stf/common-ui').name
|
||||
])
|
||||
.directive('stfColumnChoice', require('./column-choice-directive'))
|
||||
|
||||
|
|
@ -1,4 +1,9 @@
|
|||
/**
|
||||
* Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
module.exports = angular.module('stf/common-ui', [
|
||||
require('./pagination').name,
|
||||
require('./safe-apply').name,
|
||||
require('./clear-button').name,
|
||||
require('./filter-button').name,
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
module.exports =
|
||||
function GenericModalServiceFactory($uibModal) {
|
||||
const service = {}
|
||||
|
||||
const ModalInstanceCtrl = function($scope, $uibModalInstance, data) {
|
||||
$scope.data = data
|
||||
|
||||
$scope.ok = function() {
|
||||
$uibModalInstance.close(true)
|
||||
}
|
||||
|
||||
$scope.cancel = function() {
|
||||
$uibModalInstance.dismiss('cancel')
|
||||
}
|
||||
}
|
||||
|
||||
service.open = function(data) {
|
||||
var modalInstance = $uibModal.open({
|
||||
template: require('./generic-modal.pug'),
|
||||
controller: ModalInstanceCtrl,
|
||||
size: data.size,
|
||||
animation: true,
|
||||
resolve: {
|
||||
data: function() {
|
||||
return data
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return modalInstance.result
|
||||
}
|
||||
|
||||
return service
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
describe('GenericModalService', function() {
|
||||
|
||||
beforeEach(angular.mock.module(require('./').name))
|
||||
|
||||
it('should ...', inject(function() {
|
||||
|
||||
//expect(FatalMessageService.doSomething()).toEqual('something');
|
||||
|
||||
}))
|
||||
|
||||
})
|
|
@ -0,0 +1,36 @@
|
|||
//
|
||||
Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
//
|
||||
|
||||
.stf-generic-modal.stf-modal
|
||||
.modal-header
|
||||
h4.modal-title.text-warning(ng-if="data.type === 'Warning'")
|
||||
i.fa.fa-warning
|
||||
.button-spacer
|
||||
span(translate) {{data.type}}
|
||||
|
||||
h4.modal-title.text-info(ng-if="data.type === 'Information'")
|
||||
i.fa.fa-info-circle
|
||||
.button-spacer
|
||||
span(translate) {{data.type}}
|
||||
|
||||
h4.modal-title.text-danger(ng-if="data.type === 'Error'")
|
||||
i.fa.fa-times-circle
|
||||
.button-spacer
|
||||
span(translate) {{data.type}}
|
||||
|
||||
.modal-body
|
||||
label.control-label
|
||||
span(translate) {{data.message}}
|
||||
|
||||
.modal-footer
|
||||
button.btn.btn-primary(
|
||||
type='button'
|
||||
ng-click='ok()')
|
||||
span(translate) OK
|
||||
|
||||
button.btn.btn-warning(
|
||||
type='button'
|
||||
ng-if='data.cancel'
|
||||
ng-click='cancel()')
|
||||
span(translate) Cancel
|
|
@ -0,0 +1,8 @@
|
|||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
module.exports = angular.module('stf.generic-modal', [
|
||||
require('stf/common-ui/modals/common').name
|
||||
])
|
||||
.factory('GenericModalService', require('./generic-modal-service'))
|
|
@ -1,4 +1,9 @@
|
|||
/**
|
||||
* Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
module.exports = angular.module('stf.modals', [
|
||||
require('./generic-modal').name,
|
||||
require('./fatal-message').name,
|
||||
require('./socket-disconnected').name,
|
||||
require('./version-update').name,
|
||||
|
|
12
res/app/components/stf/common-ui/pagination/index.js
Normal file
12
res/app/components/stf/common-ui/pagination/index.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
require('./pagination.css')
|
||||
|
||||
module.exports = angular.module('stf.pagination', [
|
||||
])
|
||||
.filter('pagedObjectsFilter', require('./pagination-filter'))
|
||||
.directive('stfPager', require('./pagination-directive'))
|
||||
.factory('ItemsPerPageOptionsService', require('./pagination-service'))
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
module.exports = function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
tooltipLabel: '@',
|
||||
iconStyle: '@?',
|
||||
itemsSearchStyle: '@?',
|
||||
itemsSearch: '=',
|
||||
itemsPerPageOptions: '<',
|
||||
itemsPerPage: '=',
|
||||
totalItems: '<',
|
||||
totalItemsStyle: '@?',
|
||||
currentPage: '='
|
||||
},
|
||||
template: require('./pagination.pug'),
|
||||
link: function(scope, element, attrs) {
|
||||
scope.currentPage = 1
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
module.exports = function() {
|
||||
return function(objects, scope, currentPage, maxItems, searchItems) {
|
||||
scope[searchItems] = objects
|
||||
if (scope[maxItems].value === 0) {
|
||||
return objects
|
||||
}
|
||||
return objects.slice(
|
||||
(scope[currentPage] - 1) * scope[maxItems].value
|
||||
, scope[currentPage] * scope[maxItems].value
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
module.exports = function ItemsPerPageOptionsServiceFactory() {
|
||||
const service = [
|
||||
{name: '1', value: 1}
|
||||
, {name: '5', value: 5}
|
||||
, {name: '10', value: 10}
|
||||
, {name: '20', value: 20}
|
||||
, {name: '50', value: 50}
|
||||
, {name: '100', value: 100}
|
||||
, {name: '200', value: 200}
|
||||
, {name: '500', value: 500}
|
||||
, {name: '1000', value: 1000}
|
||||
, {name: '*', value: 0}
|
||||
]
|
||||
|
||||
return service
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
34
res/app/components/stf/common-ui/pagination/pagination.pug
Normal file
34
res/app/components/stf/common-ui/pagination/pagination.pug
Normal file
|
@ -0,0 +1,34 @@
|
|||
//
|
||||
Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
//
|
||||
|
||||
.input-group(style='margin-right: 5px; {{itemsSearchStyle}}' class='{{itemsSearchStyle}}')
|
||||
.input-group-addon.input-sm
|
||||
i.glyphicon.glyphicon-search(
|
||||
class='{{iconStyle}}'
|
||||
uib-tooltip='{{tooltipLabel}}'
|
||||
tooltip-placement='auto top-right'
|
||||
tooltip-popup-delay='500')
|
||||
input.form-control.input-sm(type='text' placeholder='Search' ng-model='itemsSearch')
|
||||
|
||||
select.custon-select.form-control.input-sm(
|
||||
ng-model='itemsPerPage'
|
||||
ng-options='option as option.name for option in itemsPerPageOptions track by option.value')
|
||||
|
||||
uib-pagination(
|
||||
style='vertical-align: middle; width: -moz-max-content'
|
||||
total-items='totalItems'
|
||||
items-per-page='itemsPerPage.value'
|
||||
class='pagination-sm'
|
||||
max-size='1'
|
||||
boundary-links='true'
|
||||
boundary-link-numbers='false'
|
||||
previous-text='<' next-text='>' first-text='First' last-text='Last'
|
||||
rotate='true'
|
||||
ng-model='currentPage')
|
||||
|
||||
button.btn.btn-sm.btn-info(
|
||||
type='button'
|
||||
class='{{totalItemsStyle}}'
|
||||
style='pointer-events: none')
|
||||
span {{totalItems}}
|
|
@ -1,6 +1,11 @@
|
|||
/**
|
||||
* Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
var oboe = require('oboe')
|
||||
var _ = require('lodash')
|
||||
var EventEmitter = require('eventemitter3')
|
||||
let Promise = require('bluebird')
|
||||
|
||||
module.exports = function DeviceServiceFactory($http, socket, EnhanceDeviceService) {
|
||||
var deviceService = {}
|
||||
|
@ -93,6 +98,12 @@ module.exports = function DeviceServiceFactory($http, socket, EnhanceDeviceServi
|
|||
if (index >= 0) {
|
||||
devices.splice(index, 1)
|
||||
delete devicesBySerial[data.serial]
|
||||
for (var serial in devicesBySerial) {
|
||||
if (devicesBySerial[serial] > index) {
|
||||
devicesBySerial[serial]--
|
||||
}
|
||||
}
|
||||
sync(data)
|
||||
this.emit('remove', data)
|
||||
}
|
||||
}.bind(this)
|
||||
|
@ -131,6 +142,8 @@ module.exports = function DeviceServiceFactory($http, socket, EnhanceDeviceServi
|
|||
}
|
||||
notify(event)
|
||||
}
|
||||
|
||||
/** code removed to avoid to show forbidden devices in user view!
|
||||
else {
|
||||
if (options.filter(event.data)) {
|
||||
insert(event.data)
|
||||
|
@ -139,6 +152,7 @@ module.exports = function DeviceServiceFactory($http, socket, EnhanceDeviceServi
|
|||
notify(event)
|
||||
}
|
||||
}
|
||||
**/
|
||||
}
|
||||
|
||||
scopedSocket.on('device.add', addListener)
|
||||
|
@ -153,6 +167,43 @@ module.exports = function DeviceServiceFactory($http, socket, EnhanceDeviceServi
|
|||
}
|
||||
|
||||
this.devices = devices
|
||||
|
||||
function addGroupDevicesListener(event) {
|
||||
return Promise.map(event.devices, function(serial) {
|
||||
return deviceService.load(serial).then(function(device) {
|
||||
return device
|
||||
})
|
||||
})
|
||||
.then(function(_devices) {
|
||||
_devices.forEach(function(device) {
|
||||
if (device && typeof devicesBySerial[device.serial] === 'undefined') {
|
||||
insert(device)
|
||||
notify(event)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function removeGroupDevicesListener(event) {
|
||||
event.devices.forEach(function(serial) {
|
||||
if (typeof devicesBySerial[serial] !== 'undefined') {
|
||||
remove(devices[devicesBySerial[serial]])
|
||||
notify(event)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function updateGroupDeviceListener(event) {
|
||||
let device = get(event.data)
|
||||
if (device) {
|
||||
modify(device, event.data)
|
||||
notify(event)
|
||||
}
|
||||
}
|
||||
|
||||
scopedSocket.on('device.addGroupDevices', addGroupDevicesListener)
|
||||
scopedSocket.on('device.removeGroupDevices', removeGroupDevicesListener)
|
||||
scopedSocket.on('device.updateGroupDevice', updateGroupDeviceListener)
|
||||
}
|
||||
|
||||
Tracker.prototype = new EventEmitter()
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/**
|
||||
* Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
module.exports = function EnhanceDeviceServiceFactory($filter, AppState) {
|
||||
var service = {}
|
||||
|
||||
|
@ -62,6 +66,8 @@ module.exports = function EnhanceDeviceServiceFactory($filter, AppState) {
|
|||
device.enhancedUserProfileUrl = enhanceUserProfileUrl(device.owner.email)
|
||||
device.enhancedUserName = device.owner.name || 'No name'
|
||||
}
|
||||
|
||||
device.enhancedGroupOwnerProfileUrl = enhanceUserProfileUrl(device.group.owner.email)
|
||||
}
|
||||
|
||||
function enhanceUserProfileUrl(email) {
|
||||
|
|
107
res/app/components/stf/devices/devices-service.js
Normal file
107
res/app/components/stf/devices/devices-service.js
Normal file
|
@ -0,0 +1,107 @@
|
|||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
const oboe = require('oboe')
|
||||
|
||||
module.exports = function DevicesServiceFactory(
|
||||
$rootScope
|
||||
, $http
|
||||
, socket
|
||||
, CommonService
|
||||
) {
|
||||
const DevicesService = {}
|
||||
|
||||
function buildQueryParameters(filters) {
|
||||
var query = ''
|
||||
|
||||
if (filters.present !== 'Any') {
|
||||
query += 'present=' + filters.present.toLowerCase()
|
||||
}
|
||||
if (filters.booked !== 'Any') {
|
||||
query += (query === '' ? '' : '&') + 'booked=' + filters.booked.toLowerCase()
|
||||
}
|
||||
if (filters.annotated !== 'Any') {
|
||||
query += (query === '' ? '' : '&') + 'annotated=' + filters.annotated.toLowerCase()
|
||||
}
|
||||
if (filters.controlled !== 'Any') {
|
||||
query += (query === '' ? '' : '&') + 'controlled=' + filters.controlled.toLowerCase()
|
||||
}
|
||||
return query === '' ? query : '?' + query
|
||||
}
|
||||
|
||||
DevicesService.getOboeDevices = function(target, fields, addDevice) {
|
||||
return oboe(CommonService.getBaseUrl()
|
||||
+ '/api/v1/devices?target=' + target + '&fields=' + fields)
|
||||
.node('devices[*]', function(device) {
|
||||
addDevice(device)
|
||||
})
|
||||
}
|
||||
|
||||
DevicesService.getDevices = function(target, fields) {
|
||||
return $http.get('/api/v1/devices?target=' + target + '&fields=' + fields)
|
||||
}
|
||||
|
||||
DevicesService.getDevice = function(serial, fields) {
|
||||
return $http.get('/api/v1/devices/' + serial + '?fields=' + fields)
|
||||
}
|
||||
|
||||
DevicesService.removeDevice = function(serial, filters) {
|
||||
return $http.delete('/api/v1/devices/' + serial + buildQueryParameters(filters))
|
||||
}
|
||||
|
||||
DevicesService.removeDevices = function(filters, serials) {
|
||||
return $http({
|
||||
method: 'DELETE',
|
||||
url: '/api/v1/devices' + buildQueryParameters(filters),
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=utf-8'
|
||||
},
|
||||
data: typeof serials === 'undefined' ? serials : JSON.stringify({serials: serials})
|
||||
})
|
||||
}
|
||||
|
||||
DevicesService.addOriginGroupDevice = function(id, serial) {
|
||||
return $http.put('/api/v1/devices/' + serial + '/groups/' + id)
|
||||
}
|
||||
|
||||
DevicesService.addOriginGroupDevices = function(id, serials) {
|
||||
return $http({
|
||||
method: 'PUT',
|
||||
url: '/api/v1/devices/groups/' + id + '?fields=""',
|
||||
data: typeof serials === 'undefined' ? serials : JSON.stringify({serials: serials})
|
||||
})
|
||||
}
|
||||
|
||||
DevicesService.removeOriginGroupDevice = function(id, serial) {
|
||||
return $http.delete('/api/v1/devices/' + serial + '/groups/' + id)
|
||||
}
|
||||
|
||||
DevicesService.removeOriginGroupDevices = function(id, serials) {
|
||||
return $http({
|
||||
method: 'DELETE',
|
||||
url: '/api/v1/devices/groups/' + id + '?fields=""',
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=utf-8'
|
||||
},
|
||||
data: typeof serials === 'undefined' ? serials : JSON.stringify({serials: serials})
|
||||
})
|
||||
}
|
||||
|
||||
socket.on('user.settings.devices.created', function(device) {
|
||||
$rootScope.$broadcast('user.settings.devices.created', device)
|
||||
$rootScope.$apply()
|
||||
})
|
||||
|
||||
socket.on('user.settings.devices.deleted', function(device) {
|
||||
$rootScope.$broadcast('user.settings.devices.deleted', device)
|
||||
$rootScope.$apply()
|
||||
})
|
||||
|
||||
socket.on('user.settings.devices.updated', function(device) {
|
||||
$rootScope.$broadcast('user.settings.devices.updated', device)
|
||||
$rootScope.$apply()
|
||||
})
|
||||
|
||||
return DevicesService
|
||||
}
|
9
res/app/components/stf/devices/index.js
Normal file
9
res/app/components/stf/devices/index.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
module.exports = angular.module('stf.devices', [
|
||||
require('stf/util/common').name,
|
||||
require('stf/socket').name
|
||||
])
|
||||
.factory('DevicesService', require('./devices-service'))
|
184
res/app/components/stf/groups/groups-service.js
Normal file
184
res/app/components/stf/groups/groups-service.js
Normal file
|
@ -0,0 +1,184 @@
|
|||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
const oboe = require('oboe')
|
||||
|
||||
module.exports = function GroupsServiceFactory(
|
||||
$rootScope
|
||||
, $http
|
||||
, socket
|
||||
, CommonService
|
||||
) {
|
||||
const GroupsService = {}
|
||||
|
||||
GroupsService.getGroupUsers = function(id, fields) {
|
||||
return $http.get('/api/v1/groups/' + id + '/users?fields=' + fields)
|
||||
}
|
||||
|
||||
GroupsService.getOboeGroupUsers = function(id, fields, addGroupUser) {
|
||||
return oboe(CommonService.getBaseUrl()
|
||||
+ '/api/v1/groups/' + id + '/users?fields=' + fields)
|
||||
.node('users[*]', function(user) {
|
||||
addGroupUser(user)
|
||||
})
|
||||
}
|
||||
|
||||
GroupsService.getGroupDevices = function(id, bookable, fields) {
|
||||
return $http.get('/api/v1/groups/' + id + '/devices?bookable=' + bookable + '&fields=' + fields)
|
||||
}
|
||||
|
||||
GroupsService.getOboeGroupDevices = function(id, bookable, fields, addGroupDevice) {
|
||||
return oboe(CommonService.getBaseUrl()
|
||||
+ '/api/v1/groups/' + id + '/devices?bookable=' + bookable + '&fields=' + fields)
|
||||
.node('devices[*]', function(device) {
|
||||
addGroupDevice(device)
|
||||
})
|
||||
}
|
||||
|
||||
GroupsService.getGroupDevice = function(id, serial, fields) {
|
||||
return $http.get('/api/v1/groups/' + id + '/devices/' + serial + '?fields=' + fields)
|
||||
}
|
||||
|
||||
GroupsService.addGroupDevice = function(id, serial) {
|
||||
return $http.put('/api/v1/groups/' + id + '/devices/' + serial)
|
||||
}
|
||||
|
||||
GroupsService.addGroupDevices = function(id, serials) {
|
||||
return $http({
|
||||
method: 'PUT',
|
||||
url: '/api/v1/groups/' + id + '/devices',
|
||||
data: typeof serials === 'undefined' ? serials : JSON.stringify({serials: serials})
|
||||
})
|
||||
}
|
||||
|
||||
GroupsService.removeGroupDevice = function(id, serial) {
|
||||
return $http.delete('/api/v1/groups/' + id + '/devices/' + serial)
|
||||
}
|
||||
|
||||
GroupsService.removeGroupDevices = function(id, serials) {
|
||||
return $http({
|
||||
method: 'DELETE',
|
||||
url: '/api/v1/groups/' + id + '/devices',
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=utf-8'
|
||||
},
|
||||
data: typeof serials === 'undefined' ? serials : JSON.stringify({serials: serials})
|
||||
})
|
||||
}
|
||||
|
||||
GroupsService.addGroupUser = function(id, email) {
|
||||
return $http.put('/api/v1/groups/' + id + '/users/' + email)
|
||||
}
|
||||
|
||||
GroupsService.addGroupUsers = function(id, emails) {
|
||||
return $http({
|
||||
method: 'PUT',
|
||||
url: '/api/v1/groups/' + id + '/users',
|
||||
data: typeof emails === 'undefined' ? emails : JSON.stringify({emails: emails})
|
||||
})
|
||||
}
|
||||
|
||||
GroupsService.removeGroupUser = function(id, email) {
|
||||
return $http.delete('/api/v1/groups/' + id + '/users/' + email)
|
||||
}
|
||||
|
||||
GroupsService.removeGroupUsers = function(id, emails) {
|
||||
return $http({
|
||||
method: 'DELETE',
|
||||
url: '/api/v1/groups/' + id + '/users',
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=utf-8'
|
||||
},
|
||||
data: typeof emails === 'undefined' ? emails : JSON.stringify({emails: emails})
|
||||
})
|
||||
}
|
||||
|
||||
GroupsService.getOboeGroups = function(addGroup) {
|
||||
return oboe(CommonService.getBaseUrl() + '/api/v1/groups')
|
||||
.node('groups[*]', function(group) {
|
||||
addGroup(group)
|
||||
})
|
||||
}
|
||||
|
||||
GroupsService.getGroups = function() {
|
||||
return $http.get('/api/v1/groups')
|
||||
}
|
||||
|
||||
GroupsService.getOboeMyGroups = function(addGroup) {
|
||||
return oboe(CommonService.getBaseUrl() + '/api/v1/groups?owner=true')
|
||||
.node('groups[*]', function(group) {
|
||||
addGroup(group)
|
||||
})
|
||||
}
|
||||
|
||||
GroupsService.getMyGroups = function() {
|
||||
return $http.get('/api/v1/groups?owner=true')
|
||||
}
|
||||
|
||||
GroupsService.getGroup = function(id) {
|
||||
return $http.get('/api/v1/groups/' + id)
|
||||
}
|
||||
|
||||
GroupsService.removeGroup = function(id) {
|
||||
return $http.delete('/api/v1/groups/' + id)
|
||||
}
|
||||
|
||||
GroupsService.removeGroups = function(ids) {
|
||||
return $http({
|
||||
method: 'DELETE',
|
||||
url: '/api/v1/groups?_=' + Date.now(),
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=utf-8'
|
||||
},
|
||||
data: typeof ids === 'undefined' ? ids : JSON.stringify({ids: ids})
|
||||
})
|
||||
}
|
||||
|
||||
GroupsService.createGroup = function() {
|
||||
return $http({
|
||||
method: 'POST',
|
||||
url: '/api/v1/groups',
|
||||
data: JSON.stringify({'state': 'pending'})
|
||||
})
|
||||
}
|
||||
|
||||
GroupsService.updateGroup = function(id, data) {
|
||||
return $http({
|
||||
method: 'PUT',
|
||||
url: '/api/v1/groups/' + id,
|
||||
data: JSON.stringify(data)
|
||||
})
|
||||
}
|
||||
socket.on('user.settings.groups.created', function(group) {
|
||||
$rootScope.$broadcast('user.settings.groups.created', group)
|
||||
$rootScope.$apply()
|
||||
})
|
||||
|
||||
socket.on('user.settings.groups.deleted', function(group) {
|
||||
$rootScope.$broadcast('user.settings.groups.deleted', group)
|
||||
$rootScope.$apply()
|
||||
})
|
||||
|
||||
socket.on('user.settings.groups.updated', function(group) {
|
||||
$rootScope.$broadcast('user.settings.groups.updated', group)
|
||||
$rootScope.$apply()
|
||||
})
|
||||
|
||||
socket.on('user.view.groups.created', function(group) {
|
||||
$rootScope.$broadcast('user.view.groups.created', group)
|
||||
$rootScope.$apply()
|
||||
})
|
||||
|
||||
socket.on('user.view.groups.deleted', function(group) {
|
||||
$rootScope.$broadcast('user.view.groups.deleted', group)
|
||||
$rootScope.$apply()
|
||||
})
|
||||
|
||||
socket.on('user.view.groups.updated', function(group) {
|
||||
$rootScope.$broadcast('user.view.groups.updated', group)
|
||||
$rootScope.$apply()
|
||||
})
|
||||
|
||||
return GroupsService
|
||||
}
|
8
res/app/components/stf/groups/index.js
Normal file
8
res/app/components/stf/groups/index.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
module.exports = angular.module('stf.groups', [
|
||||
require('stf/util/common').name
|
||||
])
|
||||
.factory('GroupsService', require('./groups-service'))
|
|
@ -1,3 +1,7 @@
|
|||
/**
|
||||
* Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
module.exports = function AccessTokenServiceFactory(
|
||||
$rootScope
|
||||
, $http
|
||||
|
@ -26,7 +30,7 @@ module.exports = function AccessTokenServiceFactory(
|
|||
$rootScope.$apply()
|
||||
})
|
||||
|
||||
socket.on('user.keys.accessToken.removed', function() {
|
||||
socket.on('user.keys.accessToken.updated', function() {
|
||||
$rootScope.$broadcast('user.keys.accessTokens.updated')
|
||||
$rootScope.$apply()
|
||||
})
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
module.exports = angular.module('stf/user', [
|
||||
require('stf/socket').name,
|
||||
require('stf/common-ui').name,
|
||||
require('stf/app-state').name
|
||||
])
|
||||
.factory('UserService', require('./user-service'))
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
/**
|
||||
* Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
module.exports = function UserServiceFactory(
|
||||
$rootScope
|
||||
, $http
|
||||
, socket
|
||||
, AppState
|
||||
, AddAdbKeyModalService
|
||||
|
@ -8,6 +13,10 @@ module.exports = function UserServiceFactory(
|
|||
|
||||
var user = UserService.currentUser = AppState.user
|
||||
|
||||
UserService.getUser = function() {
|
||||
return $http.get('/api/v1/user')
|
||||
}
|
||||
|
||||
UserService.getAdbKeys = function() {
|
||||
return (user.adbKeys || (user.adbKeys = []))
|
||||
}
|
||||
|
|
8
res/app/components/stf/users/index.js
Normal file
8
res/app/components/stf/users/index.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
module.exports = angular.module('stf.users', [
|
||||
require('stf/util/common').name
|
||||
])
|
||||
.factory('UsersService', require('./users-service'))
|
96
res/app/components/stf/users/users-service.js
Normal file
96
res/app/components/stf/users/users-service.js
Normal file
|
@ -0,0 +1,96 @@
|
|||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
const oboe = require('oboe')
|
||||
|
||||
module.exports = function UsersServiceFactory(
|
||||
$rootScope
|
||||
, $http
|
||||
, socket
|
||||
, CommonService
|
||||
) {
|
||||
const UsersService = {}
|
||||
|
||||
function buildQueryParameters(filters) {
|
||||
var query = ''
|
||||
|
||||
if (filters.groupOwner !== 'Any') {
|
||||
query += 'groupOwner=' + filters.groupOwner.toLowerCase()
|
||||
}
|
||||
return query === '' ? query : '?' + query
|
||||
}
|
||||
|
||||
UsersService.getOboeUsers = function(fields, addUser) {
|
||||
return oboe(CommonService.getBaseUrl() + '/api/v1/users?fields=' + fields)
|
||||
.node('users[*]', function(user) {
|
||||
addUser(user)
|
||||
})
|
||||
}
|
||||
|
||||
UsersService.getUsers = function(fields) {
|
||||
return $http.get('/api/v1/users?fields=' + fields)
|
||||
}
|
||||
|
||||
UsersService.getUser = function(email, fields) {
|
||||
return $http.get('/api/v1/users/' + email + '?fields=' + fields)
|
||||
}
|
||||
|
||||
UsersService.removeUser = function(email, filters) {
|
||||
return $http.delete('/api/v1/users/' + email + buildQueryParameters(filters))
|
||||
}
|
||||
|
||||
UsersService.removeUsers = function(filters, emails) {
|
||||
return $http({
|
||||
method: 'DELETE',
|
||||
url: '/api/v1/users' + buildQueryParameters(filters),
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=utf-8'
|
||||
},
|
||||
data: typeof emails === 'undefined' ? emails : JSON.stringify({emails: emails})
|
||||
})
|
||||
}
|
||||
|
||||
UsersService.updateUserGroupsQuotas = function(email, number, duration, repetitions) {
|
||||
return $http.put(
|
||||
'/api/v1/users/' + email +
|
||||
'/groupsQuotas?number=' + number +
|
||||
'&duration=' + duration +
|
||||
'&repetitions=' + repetitions
|
||||
)
|
||||
}
|
||||
|
||||
UsersService.updateDefaultUserGroupsQuotas = function(number, duration, repetitions) {
|
||||
return $http.put(
|
||||
'/api/v1/users/groupsQuotas?number=' + number +
|
||||
'&duration=' + duration +
|
||||
'&repetitions=' + repetitions
|
||||
)
|
||||
}
|
||||
|
||||
UsersService.createUser = function(name, email) {
|
||||
return $http.post('/api/v1/users/' + email + '?name=' + name)
|
||||
}
|
||||
|
||||
socket.on('user.settings.users.created', function(user) {
|
||||
$rootScope.$broadcast('user.settings.users.created', user)
|
||||
$rootScope.$apply()
|
||||
})
|
||||
|
||||
socket.on('user.settings.users.deleted', function(user) {
|
||||
$rootScope.$broadcast('user.settings.users.deleted', user)
|
||||
$rootScope.$apply()
|
||||
})
|
||||
|
||||
socket.on('user.view.users.updated', function(user) {
|
||||
$rootScope.$broadcast('user.view.users.updated', user)
|
||||
$rootScope.$apply()
|
||||
})
|
||||
|
||||
socket.on('user.settings.users.updated', function(user) {
|
||||
$rootScope.$broadcast('user.settings.users.updated', user)
|
||||
$rootScope.$apply()
|
||||
})
|
||||
|
||||
return UsersService
|
||||
}
|
223
res/app/components/stf/util/common/common-service.js
Normal file
223
res/app/components/stf/util/common/common-service.js
Normal file
|
@ -0,0 +1,223 @@
|
|||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
const _ = require('lodash')
|
||||
|
||||
module.exports = function CommonServiceFactory(
|
||||
$window,
|
||||
$location,
|
||||
GenericModalService
|
||||
) {
|
||||
const service = {}
|
||||
|
||||
const FIVE_MN = 300 * 1000
|
||||
const ONE_HOUR = 3600 * 1000
|
||||
const ONE_DAY = 24 * ONE_HOUR
|
||||
const ONE_WEEK = 7 * ONE_DAY
|
||||
const ONE_MONTH = 30 * ONE_DAY
|
||||
const ONE_QUATER = 3 * ONE_MONTH
|
||||
const ONE_HALF_YEAR = 6 * ONE_MONTH
|
||||
const ONE_YEAR = 365 * ONE_DAY
|
||||
|
||||
function getClassOptionsField(id, field) {
|
||||
for(var i in service.classOptions) {
|
||||
if (service.classOptions[i].id === id) {
|
||||
return service.classOptions[i][field]
|
||||
}
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
service.classOptions = [
|
||||
{name: 'Once', id: 'once', privilege: 'user', duration: Infinity},
|
||||
{name: 'Hourly', id: 'hourly', privilege: 'user', duration: ONE_HOUR},
|
||||
{name: 'Daily', id: 'daily', privilege: 'user', duration: ONE_DAY},
|
||||
{name: 'Weekly', id: 'weekly', privilege: 'user', duration: ONE_WEEK},
|
||||
{name: 'Monthly', id: 'monthly', privilege: 'user', duration: ONE_MONTH},
|
||||
{name: 'Quaterly', id: 'quaterly', privilege: 'user', duration: ONE_QUATER},
|
||||
{name: 'Halfyearly', id: 'halfyearly', privilege: 'user', duration: ONE_HALF_YEAR},
|
||||
{name: 'Yearly', id: 'yearly', privilege: 'user', duration: ONE_YEAR},
|
||||
{name: 'Debug', id: 'debug', privilege: 'admin', duration: FIVE_MN},
|
||||
{name: 'Bookable', id: 'bookable', privilege: 'admin', duration: Infinity},
|
||||
{name: 'Standard', id: 'standard', privilege: 'admin', duration: Infinity}
|
||||
]
|
||||
|
||||
service.getClassName = function(id) {
|
||||
return getClassOptionsField(id, 'name')
|
||||
}
|
||||
|
||||
service.getClassDuration = function(id) {
|
||||
return getClassOptionsField(id, 'duration')
|
||||
}
|
||||
|
||||
service.getDuration = function(ms) {
|
||||
if (ms < 1000) {
|
||||
return '0s'
|
||||
}
|
||||
var s = Math.floor(ms / 1000)
|
||||
var m = Math.floor(s / 60)
|
||||
|
||||
s %= 60
|
||||
var h = Math.floor(m / 60)
|
||||
|
||||
m %= 60
|
||||
var d = Math.floor(h / 24)
|
||||
|
||||
h %= 24
|
||||
return (d === 0 ? '' : d + 'd') +
|
||||
(h === 0 ? '' : (d === 0 ? '' : ' ') + h + 'h') +
|
||||
(m === 0 ? '' : (h === 0 ? '' : ' ') + m + 'm') +
|
||||
(s === 0 ? '' : (m === 0 ? '' : ' ') + s + 's')
|
||||
}
|
||||
|
||||
service.errorWrapper = function(fn, args) {
|
||||
return fn.apply(null, args).catch(function(error) {
|
||||
return GenericModalService.open({
|
||||
message: error.data ?
|
||||
error.data.description :
|
||||
error.status + ' ' + error.statusText
|
||||
, type: 'Error'
|
||||
, size: 'lg'
|
||||
, cancel: false
|
||||
})
|
||||
.then(function() {
|
||||
return error
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
service.getIndex = function(array, value, property) {
|
||||
for(var i in array) {
|
||||
if (array[i][property] === value) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
service.merge = function(oldObject, newObject) {
|
||||
var undefinedValue
|
||||
|
||||
return _.merge(oldObject, newObject, function(a, b) {
|
||||
return _.isArray(b) ? b : undefinedValue
|
||||
})
|
||||
}
|
||||
|
||||
service.isAddable = function(object, timeStamp) {
|
||||
return typeof object === 'undefined' ||
|
||||
timeStamp >= object.timeStamp && object.index === -1
|
||||
}
|
||||
|
||||
service.isExisting = function(object) {
|
||||
return typeof object !== 'undefined' &&
|
||||
object.index !== -1
|
||||
}
|
||||
|
||||
service.isRemovable = function(object, timeStamp) {
|
||||
return service.isExisting(object) &&
|
||||
timeStamp >= object.timeStamp
|
||||
}
|
||||
|
||||
service.add = function(array, objects, value, property, timeStamp) {
|
||||
if (service.isAddable(objects[value[property]], timeStamp)) {
|
||||
objects[value[property]] = {
|
||||
index: array.push(value) - 1
|
||||
, timeStamp: timeStamp
|
||||
}
|
||||
return array[objects[value[property]].index]
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
service.update = function(array, objects, value, property, timeStamp, noAdding) {
|
||||
if (service.isExisting(objects[value[property]])) {
|
||||
service.merge(array[objects[value[property]].index], value)
|
||||
objects[value[property]].timeStamp = timeStamp
|
||||
return array[objects[value[property]].index]
|
||||
}
|
||||
else if (!noAdding) {
|
||||
return service.add(array, objects, value, property, timeStamp)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
service.delete = function(array, objects, key, timeStamp) {
|
||||
if (service.isRemovable(objects[key], timeStamp)) {
|
||||
const index = objects[key].index
|
||||
const value = array.splice(index, 1)[0]
|
||||
|
||||
objects[key].index = -1
|
||||
objects[key].timeStamp = timeStamp
|
||||
for (var k in objects) {
|
||||
if (objects[k].index > index) {
|
||||
objects[k].index--
|
||||
}
|
||||
}
|
||||
return value
|
||||
}
|
||||
else if (typeof objects[key] === 'undefined') {
|
||||
objects[key] = {
|
||||
index: -1
|
||||
, timeStamp: timeStamp
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
service.sortBy = function(data, column) {
|
||||
const index = service.getIndex(data.columns, column.name, 'name')
|
||||
|
||||
if (index !== data.sort.index) {
|
||||
data.sort.reverse = false
|
||||
column.sort = 'sort-asc'
|
||||
data.columns[data.sort.index].sort = 'none'
|
||||
data.sort.index = index
|
||||
}
|
||||
else {
|
||||
data.sort.reverse = !data.sort.reverse
|
||||
column.sort = column.sort === 'sort-asc' ? 'sort-desc' : 'sort-asc'
|
||||
}
|
||||
return service
|
||||
}
|
||||
|
||||
service.isOriginGroup = function(_class) {
|
||||
return _class === 'bookable' || _class === 'standard'
|
||||
}
|
||||
|
||||
service.isNoRepetitionsGroup = function(_class) {
|
||||
return service.isOriginGroup(_class) || _class === 'once'
|
||||
}
|
||||
|
||||
service.url = function(url) {
|
||||
const a = $window.document.createElement('a')
|
||||
|
||||
$window.document.body.appendChild(a)
|
||||
a.href = url
|
||||
a.click()
|
||||
$window.document.body.removeChild(a)
|
||||
return service
|
||||
}
|
||||
|
||||
service.copyToClipboard = function(data) {
|
||||
const input = $window.document.createElement('input')
|
||||
|
||||
$window.document.body.appendChild(input)
|
||||
input.value = data
|
||||
input.select()
|
||||
$window.document.execCommand('copy')
|
||||
$window.document.body.removeChild(input)
|
||||
return service
|
||||
}
|
||||
|
||||
service.getBaseUrl = function() {
|
||||
return $location.protocol()
|
||||
+ '://'
|
||||
+ $location.host()
|
||||
+ ':'
|
||||
+ $location.port()
|
||||
}
|
||||
|
||||
return service
|
||||
}
|
||||
|
8
res/app/components/stf/util/common/index.js
Normal file
8
res/app/components/stf/util/common/index.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
module.exports = angular.module('stf.util.common', [
|
||||
require('stf/common-ui').name
|
||||
])
|
||||
.factory('CommonService', require('./common-service'))
|
|
@ -13,5 +13,4 @@ describe('StoreAccountCtrl', function() {
|
|||
expect(1).toEqual(1)
|
||||
|
||||
}))
|
||||
|
||||
})
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/**
|
||||
* Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
module.exports =
|
||||
function ControlPanesController($scope, $http, gettext, $routeParams,
|
||||
$timeout, $location, DeviceService, GroupService, ControlService,
|
||||
|
@ -85,7 +89,9 @@ module.exports =
|
|||
|
||||
$scope.$watch('device.state', function(newValue, oldValue) {
|
||||
if (newValue !== oldValue) {
|
||||
if (oldValue === 'using') {
|
||||
/*************** fix bug: it seems automation state was forgotten ? *************/
|
||||
if (oldValue === 'using' || oldValue === 'automation') {
|
||||
/******************************************************************************/
|
||||
FatalMessageService.open($scope.device, false)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/**
|
||||
* Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
var _ = require('lodash')
|
||||
|
||||
var filterOps = {
|
||||
|
@ -18,7 +22,7 @@ var filterOps = {
|
|||
}
|
||||
}
|
||||
|
||||
module.exports = function DeviceColumnService($filter, gettext) {
|
||||
module.exports = function DeviceColumnService($filter, gettext, SettingsService, AppState) {
|
||||
// Definitions for all possible values.
|
||||
return {
|
||||
state: DeviceStatusCell({
|
||||
|
@ -27,6 +31,52 @@ module.exports = function DeviceColumnService($filter, gettext) {
|
|||
return $filter('translate')(device.enhancedStateAction)
|
||||
}
|
||||
})
|
||||
, group: TextCell({
|
||||
title: gettext('Group Name')
|
||||
, value: function(device) {
|
||||
return $filter('translate')(device.group.name)
|
||||
}
|
||||
})
|
||||
, groupSchedule: TextCell({
|
||||
title: gettext('Group Class')
|
||||
, value: function(device) {
|
||||
return $filter('translate')(device.group.class)
|
||||
}
|
||||
})
|
||||
, groupOwner: LinkCell({
|
||||
title: gettext('Group Owner')
|
||||
, target: '_blank'
|
||||
, value: function(device) {
|
||||
return $filter('translate')(device.group.owner.name)
|
||||
}
|
||||
, link: function(device) {
|
||||
return device.enhancedGroupOwnerProfileUrl
|
||||
}
|
||||
})
|
||||
, groupEndTime: TextCell({
|
||||
title: gettext('Group Expiration Date')
|
||||
, value: function(device) {
|
||||
return $filter('date')(device.group.lifeTime.stop, SettingsService.get('dateFormat'))
|
||||
}
|
||||
})
|
||||
, groupStartTime: TextCell({
|
||||
title: gettext('Group Starting Date')
|
||||
, value: function(device) {
|
||||
return $filter('date')(device.group.lifeTime.start, SettingsService.get('dateFormat'))
|
||||
}
|
||||
})
|
||||
, groupRepetitions: TextCell({
|
||||
title: gettext('Group Repetitions')
|
||||
, value: function(device) {
|
||||
return device.group.repetitions
|
||||
}
|
||||
})
|
||||
, groupOrigin: TextCell({
|
||||
title: gettext('Group Origin')
|
||||
, value: function(device) {
|
||||
return $filter('translate')(device.group.originName)
|
||||
}
|
||||
})
|
||||
, model: DeviceModelCell({
|
||||
title: gettext('Model')
|
||||
, value: function(device) {
|
||||
|
@ -38,7 +88,7 @@ module.exports = function DeviceColumnService($filter, gettext) {
|
|||
, value: function(device) {
|
||||
return device.name || device.model || device.serial
|
||||
}
|
||||
})
|
||||
}, AppState.user.email)
|
||||
, operator: TextCell({
|
||||
title: gettext('Carrier')
|
||||
, value: function(device) {
|
||||
|
@ -179,6 +229,12 @@ module.exports = function DeviceColumnService($filter, gettext) {
|
|||
return device.manufacturer || ''
|
||||
}
|
||||
})
|
||||
, marketName: TextCell({
|
||||
title: gettext('Market name')
|
||||
, value: function(device) {
|
||||
return device.marketName || ''
|
||||
}
|
||||
})
|
||||
, sdk: NumberCell({
|
||||
title: gettext('SDK')
|
||||
, defaultOrder: 'desc'
|
||||
|
@ -305,8 +361,10 @@ function zeroPadTwoDigit(digit) {
|
|||
}
|
||||
|
||||
function compareIgnoreCase(a, b) {
|
||||
var la = (a || '').toLowerCase()
|
||||
var lb = (b || '').toLowerCase()
|
||||
/***** fix bug: cast to String for Safari compatibility ****/
|
||||
var la = (String(a) || '').toLowerCase()
|
||||
var lb = (String(b) || '').toLowerCase()
|
||||
/***********************************************************/
|
||||
if (la === lb) {
|
||||
return 0
|
||||
}
|
||||
|
@ -316,8 +374,10 @@ function compareIgnoreCase(a, b) {
|
|||
}
|
||||
|
||||
function filterIgnoreCase(a, filterValue) {
|
||||
var va = (a || '').toLowerCase()
|
||||
var vb = filterValue.toLowerCase()
|
||||
/***** fix bug: cast to String for Safari compatibility ****/
|
||||
var va = (String(a) || '').toLowerCase()
|
||||
var vb = String(filterValue).toLowerCase()
|
||||
/***********************************************************/
|
||||
return va.indexOf(vb) !== -1
|
||||
}
|
||||
|
||||
|
@ -551,7 +611,7 @@ function DeviceModelCell(options) {
|
|||
})
|
||||
}
|
||||
|
||||
function DeviceNameCell(options) {
|
||||
function DeviceNameCell(options, ownerEmail) {
|
||||
return _.defaults(options, {
|
||||
title: options.title
|
||||
, defaultOrder: 'asc'
|
||||
|
@ -566,11 +626,11 @@ function DeviceNameCell(options) {
|
|||
var a = td.firstChild
|
||||
var t = a.firstChild
|
||||
|
||||
if (device.using) {
|
||||
if (device.using && device.owner.email === ownerEmail) {
|
||||
a.className = 'device-product-name-using'
|
||||
a.href = '#!/control/' + device.serial
|
||||
}
|
||||
else if (device.usable) {
|
||||
else if (device.usable && !device.using) {
|
||||
a.className = 'device-product-name-usable'
|
||||
a.href = '#!/control/' + device.serial
|
||||
}
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
/**
|
||||
* Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
module.exports = angular.module('stf.device-list.column', [
|
||||
require('gettext').name
|
||||
require('gettext').name,
|
||||
require('stf/settings').name,
|
||||
require('stf/app-state').name
|
||||
])
|
||||
.service('DeviceColumnService', require('./device-column-service'))
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/**
|
||||
* Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
var QueryParser = require('./util/query-parser')
|
||||
|
||||
module.exports = function DeviceListCtrl(
|
||||
|
@ -55,6 +59,10 @@ module.exports = function DeviceListCtrl(
|
|||
name: 'manufacturer'
|
||||
, selected: false
|
||||
}
|
||||
, {
|
||||
name: 'marketName'
|
||||
, selected: false
|
||||
}
|
||||
, {
|
||||
name: 'sdk'
|
||||
, selected: false
|
||||
|
@ -123,6 +131,34 @@ module.exports = function DeviceListCtrl(
|
|||
name: 'owner'
|
||||
, selected: true
|
||||
}
|
||||
, {
|
||||
name: 'group'
|
||||
, selected: false
|
||||
}
|
||||
, {
|
||||
name: 'groupSchedule'
|
||||
, selected: false
|
||||
}
|
||||
, {
|
||||
name: 'groupStartTime'
|
||||
, selected: false
|
||||
}
|
||||
, {
|
||||
name: 'groupEndTime'
|
||||
, selected: false
|
||||
}
|
||||
, {
|
||||
name: 'groupRepetitions'
|
||||
, selected: false
|
||||
}
|
||||
, {
|
||||
name: 'groupOwner'
|
||||
, selected: false
|
||||
}
|
||||
, {
|
||||
name: 'groupOrigin'
|
||||
, selected: false
|
||||
}
|
||||
]
|
||||
|
||||
$scope.columns = defaultColumns
|
||||
|
|
|
@ -96,7 +96,6 @@ module.exports = function DeviceListIconsDirective(
|
|||
a.removeAttribute('href')
|
||||
li.classList.add('device-is-busy')
|
||||
}
|
||||
|
||||
return li
|
||||
}
|
||||
}
|
||||
|
@ -169,8 +168,7 @@ module.exports = function DeviceListIconsDirective(
|
|||
}
|
||||
|
||||
if (device.using) {
|
||||
if (e.target.classList.contains('btn') &&
|
||||
e.target.classList.contains('state-using')) {
|
||||
if (e.target.classList.contains('btn') && e.target.classList.contains('state-using')) {
|
||||
kickDevice(device)
|
||||
e.preventDefault()
|
||||
}
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/**
|
||||
* Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
module.exports = function DeviceListStatsDirective(
|
||||
UserService
|
||||
) {
|
||||
|
@ -73,8 +77,11 @@ module.exports = function DeviceListStatsDirective(
|
|||
var newStats = updateStats(device)
|
||||
|
||||
scope.counter.total -= 1
|
||||
scope.counter.busy += newStats.busy - oldStats.busy
|
||||
scope.counter.using += newStats.using - oldStats.using
|
||||
scope.counter.usable -= newStats.usable
|
||||
scope.counter.busy -= newStats.busy
|
||||
scope.counter.using -= newStats.using
|
||||
//scope.counter.busy += newStats.busy - oldStats.busy
|
||||
//scope.counter.using += newStats.using - oldStats.using
|
||||
|
||||
delete mapping[device.serial]
|
||||
|
||||
|
|
464
res/app/group-list/group-list-controller.js
Normal file
464
res/app/group-list/group-list-controller.js
Normal file
|
@ -0,0 +1,464 @@
|
|||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
const _ = require('lodash')
|
||||
|
||||
module.exports = function GroupListCtrl(
|
||||
$scope
|
||||
, $filter
|
||||
, GroupsService
|
||||
, UserService
|
||||
, UsersService
|
||||
, DevicesService
|
||||
, SettingsService
|
||||
, ItemsPerPageOptionsService
|
||||
, CommonService
|
||||
) {
|
||||
const users = []
|
||||
const usersByEmail = {}
|
||||
const devices = []
|
||||
const devicesBySerial = {}
|
||||
const groupsById = {}
|
||||
const groupsEnv = {}
|
||||
const groupUserToAdd = {}
|
||||
const userFields =
|
||||
'email,' +
|
||||
'name,' +
|
||||
'privilege'
|
||||
const deviceFields =
|
||||
'serial,' +
|
||||
'version,' +
|
||||
'manufacturer,' +
|
||||
'marketName,' +
|
||||
'sdk,' +
|
||||
'display.width,' +
|
||||
'display.height,' +
|
||||
'model'
|
||||
|
||||
function incrStateStats(group, incr) {
|
||||
if (group.isActive) {
|
||||
$scope.activeGroups += incr
|
||||
}
|
||||
else if (group.state === 'pending') {
|
||||
$scope.pendingGroups += incr
|
||||
}
|
||||
$scope.readyGroups = $scope.groups.length - $scope.activeGroups - $scope.pendingGroups
|
||||
}
|
||||
|
||||
function updateStateStats(oldGroup, newGroup) {
|
||||
if (oldGroup === null) {
|
||||
incrStateStats(newGroup, 1)
|
||||
}
|
||||
else if (newGroup === null) {
|
||||
incrStateStats(oldGroup, -1)
|
||||
}
|
||||
else {
|
||||
if (newGroup.isActive && !oldGroup.isActive) {
|
||||
incrStateStats(newGroup, 1)
|
||||
}
|
||||
else if (!newGroup.isActive && oldGroup.isActive) {
|
||||
incrStateStats(oldGroup, -1)
|
||||
}
|
||||
else if (newGroup.state === 'ready' && oldGroup.state === 'pending') {
|
||||
incrStateStats(oldGroup, -1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateGroupExtraProperties(group) {
|
||||
const status = {pending: 'Pending', waiting: 'Waiting', ready: 'Ready'}
|
||||
|
||||
group.status = group.isActive ? 'Active' : status[group.state]
|
||||
group.startTime = $filter('date')(group.dates[0].start, SettingsService.get('dateFormat'))
|
||||
group.stopTime = $filter('date')(group.dates[0].stop, SettingsService.get('dateFormat'))
|
||||
|
||||
}
|
||||
|
||||
function updateQuotaBar(bar, consumed, allocated) {
|
||||
bar.value = (consumed / allocated) * 100 | 0
|
||||
if (bar.value < 25) {
|
||||
bar.type = 'success'
|
||||
}
|
||||
else if (bar.value < 50) {
|
||||
bar.type = 'info'
|
||||
}
|
||||
else if (bar.value < 75) {
|
||||
bar.type = 'warning'
|
||||
}
|
||||
else {
|
||||
bar.type = 'danger'
|
||||
}
|
||||
}
|
||||
|
||||
function updateQuotaBars() {
|
||||
updateQuotaBar(
|
||||
$scope.numberBar
|
||||
, $scope.user.groups.quotas.consumed.number
|
||||
, $scope.user.groups.quotas.allocated.number
|
||||
)
|
||||
updateQuotaBar(
|
||||
$scope.durationBar
|
||||
, $scope.user.groups.quotas.consumed.duration
|
||||
, $scope.user.groups.quotas.allocated.duration
|
||||
)
|
||||
}
|
||||
|
||||
function addGroup(group, timeStamp) {
|
||||
if (CommonService.add(
|
||||
$scope.groups
|
||||
, groupsById
|
||||
, group
|
||||
, 'id'
|
||||
, timeStamp)) {
|
||||
$scope.groupsEnv[group.id] = {
|
||||
devices: []
|
||||
, users: []
|
||||
}
|
||||
groupsEnv[group.id] = {
|
||||
devicesBySerial: {}
|
||||
, usersByEmail: {}
|
||||
}
|
||||
updateStateStats(null, group)
|
||||
updateGroupExtraProperties(group)
|
||||
return group
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function updateGroup(group, timeStamp) {
|
||||
return CommonService.update(
|
||||
$scope.groups
|
||||
, groupsById
|
||||
, group
|
||||
, 'id'
|
||||
, timeStamp)
|
||||
}
|
||||
|
||||
function deleteGroup(id, timeStamp) {
|
||||
const group = CommonService.delete(
|
||||
$scope.groups
|
||||
, groupsById
|
||||
, id
|
||||
, timeStamp)
|
||||
|
||||
if (group) {
|
||||
updateStateStats(group, null)
|
||||
delete $scope.groupsEnv[group.id]
|
||||
delete groupsEnv[group.id]
|
||||
}
|
||||
return group
|
||||
}
|
||||
|
||||
function addUser(user, timeStamp) {
|
||||
if (CommonService.add(
|
||||
users
|
||||
, usersByEmail
|
||||
, user
|
||||
, 'email'
|
||||
, timeStamp
|
||||
) && typeof groupUserToAdd[user.email] !== 'undefined') {
|
||||
addGroupUser(
|
||||
groupUserToAdd[user.email].id
|
||||
, user.email
|
||||
, groupUserToAdd[user.email].timeStamp)
|
||||
delete groupUserToAdd[user.email]
|
||||
}
|
||||
}
|
||||
|
||||
function deleteUser(email, timeStamp) {
|
||||
return CommonService.delete(
|
||||
users
|
||||
, usersByEmail
|
||||
, email
|
||||
, timeStamp)
|
||||
}
|
||||
|
||||
function addDevice(device, timeStamp) {
|
||||
return CommonService.add(
|
||||
devices
|
||||
, devicesBySerial
|
||||
, device
|
||||
, 'serial'
|
||||
, timeStamp)
|
||||
}
|
||||
|
||||
function updateDevice(device, timeStamp) {
|
||||
return CommonService.update(
|
||||
devices
|
||||
, devicesBySerial
|
||||
, device
|
||||
, 'serial'
|
||||
, timeStamp)
|
||||
}
|
||||
|
||||
function deleteDevice(serial, timeStamp) {
|
||||
return CommonService.delete(
|
||||
devices
|
||||
, devicesBySerial
|
||||
, serial
|
||||
, timeStamp)
|
||||
}
|
||||
|
||||
function addGroupUser(id, email, timeStamp) {
|
||||
if (CommonService.isExisting(usersByEmail[email])) {
|
||||
CommonService.add(
|
||||
$scope.groupsEnv[id].users
|
||||
, groupsEnv[id].usersByEmail
|
||||
, users[usersByEmail[email].index]
|
||||
, 'email'
|
||||
, timeStamp)
|
||||
}
|
||||
else {
|
||||
groupUserToAdd[email] = {id: id, timeStamp: timeStamp}
|
||||
}
|
||||
}
|
||||
|
||||
function deleteGroupUser(id, email, timeStamp) {
|
||||
CommonService.delete(
|
||||
$scope.groupsEnv[id].users
|
||||
, groupsEnv[id].usersByEmail
|
||||
, email
|
||||
, timeStamp)
|
||||
}
|
||||
|
||||
function addGroupDevice(id, serial, timeStamp) {
|
||||
if (CommonService.isExisting(devicesBySerial[serial])) {
|
||||
CommonService.add(
|
||||
$scope.groupsEnv[id].devices
|
||||
, groupsEnv[id].devicesBySerial
|
||||
, devices[devicesBySerial[serial].index]
|
||||
, 'serial'
|
||||
, timeStamp)
|
||||
}
|
||||
else {
|
||||
GroupsService.getGroupDevice(id, serial, deviceFields)
|
||||
.then(function(response) {
|
||||
if (addDevice(response.data.device, timeStamp)) {
|
||||
CommonService.add(
|
||||
$scope.groupsEnv[id].devices
|
||||
, groupsEnv[id].devicesBySerial
|
||||
, devices[devicesBySerial[serial].index]
|
||||
, 'serial'
|
||||
, timeStamp)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function deleteGroupDevice(id, serial, timeStamp) {
|
||||
CommonService.delete(
|
||||
$scope.groupsEnv[id].devices
|
||||
, groupsEnv[id].devicesBySerial
|
||||
, serial
|
||||
, timeStamp)
|
||||
}
|
||||
|
||||
function updateGroupDevices(group, isAddedDevice, devices, timeStamp) {
|
||||
if (devices.length) {
|
||||
if (isAddedDevice) {
|
||||
devices.forEach(function(serial) {
|
||||
addGroupDevice(group.id, serial, timeStamp)
|
||||
})
|
||||
}
|
||||
else {
|
||||
devices.forEach(function(serial) {
|
||||
deleteGroupDevice(group.id, serial, timeStamp)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateGroupUsers(group, isAddedUser, users, timeStamp) {
|
||||
if (users.length) {
|
||||
if (isAddedUser) {
|
||||
users.forEach(function(email) {
|
||||
addGroupUser(group.id, email, timeStamp)
|
||||
})
|
||||
}
|
||||
else {
|
||||
users.forEach(function(email) {
|
||||
deleteGroupUser(group.id, email, timeStamp)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function initScope() {
|
||||
GroupsService.getOboeGroups(function(group) {
|
||||
addGroup(group, -1)
|
||||
})
|
||||
.done(function() {
|
||||
$scope.$digest()
|
||||
})
|
||||
|
||||
UserService.getUser().then(function(response) {
|
||||
$scope.user = response.data.user
|
||||
updateQuotaBars()
|
||||
})
|
||||
|
||||
UsersService.getOboeUsers(userFields, function(user) {
|
||||
addUser(user, -1)
|
||||
})
|
||||
}
|
||||
|
||||
$scope.scopeGroupListCtrl = $scope
|
||||
$scope.sortBy = CommonService.sortBy
|
||||
$scope.getDuration = CommonService.getDuration
|
||||
$scope.getClassName = CommonService.getClassName
|
||||
$scope.user = UserService.currentUser
|
||||
$scope.numberBar = {}
|
||||
$scope.durationBar = {}
|
||||
$scope.groupsEnv = {}
|
||||
$scope.groups = []
|
||||
$scope.activeGroups = $scope.readyGroups = $scope.pendingGroups = 0
|
||||
$scope.itemsPerPageOptions = ItemsPerPageOptionsService
|
||||
SettingsService.bind($scope, {
|
||||
target: 'groupItemsPerPage'
|
||||
, source: 'groupViewItemsPerPage'
|
||||
, defaultValue: $scope.itemsPerPageOptions[2]
|
||||
})
|
||||
$scope.groupColumns = [
|
||||
{name: 'Status', property: 'status'}
|
||||
, {name: 'Name', property: 'name'}
|
||||
, {name: 'Identifier', property: 'id'}
|
||||
, {name: 'Owner', property: 'owner.name'}
|
||||
, {name: 'Devices', property: 'devices.length'}
|
||||
, {name: 'Users', property: 'users.length'}
|
||||
, {name: 'Class', property: 'class'}
|
||||
, {name: 'Repetitions', property: 'repetitions'}
|
||||
, {name: 'Duration', property: 'duration'}
|
||||
, {name: 'Starting Date', property: 'startTime'}
|
||||
, {name: 'Expiration Date', property: 'stopTime'}
|
||||
]
|
||||
$scope.defaultGroupData = {
|
||||
columns: [
|
||||
{name: 'Status', selected: true, sort: 'none'}
|
||||
, {name: 'Name', selected: true, sort: 'sort-asc'}
|
||||
, {name: 'Identifier', selected: false, sort: 'none'}
|
||||
, {name: 'Owner', selected: true, sort: 'none'}
|
||||
, {name: 'Devices', selected: true, sort: 'none'}
|
||||
, {name: 'Users', selected: true, sort: 'none'}
|
||||
, {name: 'Class', selected: true, sort: 'none'}
|
||||
, {name: 'Repetitions', selected: true, sort: 'none'}
|
||||
, {name: 'Duration', selected: true, sort: 'none'}
|
||||
, {name: 'Starting Date', selected: true, sort: 'none'}
|
||||
, {name: 'Expiration Date', selected: true, sort: 'none'}
|
||||
]
|
||||
, sort: {index: 1, reverse: false}
|
||||
}
|
||||
SettingsService.bind($scope, {
|
||||
target: 'groupData'
|
||||
, source: 'groupData'
|
||||
, defaultValue: $scope.defaultGroupData
|
||||
})
|
||||
|
||||
$scope.mailToGroupOwners = function(groups) {
|
||||
CommonService.copyToClipboard(_.uniq(groups.map(function(group) {
|
||||
return group.owner.email
|
||||
}))
|
||||
.join(SettingsService.get('emailSeparator')))
|
||||
.url('mailto:?body=*** Paste the email addresses from the clipboard! ***')
|
||||
}
|
||||
|
||||
$scope.mailToGroupUsers = function(group, users) {
|
||||
// group unused actually..
|
||||
CommonService.copyToClipboard(users.map(function(user) {
|
||||
return user.email
|
||||
})
|
||||
.join(SettingsService.get('emailSeparator')))
|
||||
.url('mailto:?body=*** Paste the email addresses from the clipboard! ***')
|
||||
}
|
||||
|
||||
$scope.getTooltip = function(objects) {
|
||||
var tooltip = ''
|
||||
|
||||
objects.forEach(function(object) {
|
||||
tooltip += object + '\n'
|
||||
})
|
||||
return tooltip
|
||||
}
|
||||
|
||||
$scope.resetData = function() {
|
||||
$scope.groupData = JSON.parse(JSON.stringify($scope.defaultGroupData))
|
||||
}
|
||||
|
||||
$scope.initGroupUsers = function(group) {
|
||||
if (typeof $scope.groupsEnv[group.id].userCurrentPage === 'undefined') {
|
||||
$scope.groupsEnv[group.id].userCurrentPage = 1
|
||||
$scope.groupsEnv[group.id].userItemsPerPage = $scope.itemsPerPageOptions[1]
|
||||
}
|
||||
group.users.forEach(function(email) {
|
||||
addGroupUser(group.id, email, -1)
|
||||
})
|
||||
}
|
||||
|
||||
$scope.initGroupDevices = function(group) {
|
||||
if (typeof $scope.groupsEnv[group.id].deviceCurrentPage === 'undefined') {
|
||||
$scope.groupsEnv[group.id].deviceCurrentPage = 1
|
||||
$scope.groupsEnv[group.id].deviceItemsPerPage = $scope.itemsPerPageOptions[1]
|
||||
}
|
||||
GroupsService.getOboeGroupDevices(group.id, false, deviceFields, function(device) {
|
||||
addDevice(device, -1)
|
||||
addGroupDevice(group.id, device.serial, -1)
|
||||
})
|
||||
.done(function() {
|
||||
$scope.$digest()
|
||||
})
|
||||
}
|
||||
|
||||
$scope.$on('user.view.groups.created', function(event, message) {
|
||||
addGroup(message.group, message.timeStamp)
|
||||
})
|
||||
|
||||
$scope.$on('user.view.groups.deleted', function(event, message) {
|
||||
deleteGroup(message.group.id, message.timeStamp)
|
||||
})
|
||||
|
||||
$scope.$on('user.view.groups.updated', function(event, message) {
|
||||
if (CommonService.isExisting(groupsById[message.group.id])) {
|
||||
if (message.group.users.indexOf(UserService.currentUser.email) < 0) {
|
||||
deleteGroup(message.group.id, message.timeStamp)
|
||||
}
|
||||
else {
|
||||
updateStateStats($scope.groups[groupsById[message.group.id].index], message.group)
|
||||
updateGroupDevices(message.group, message.isAddedDevice, message.devices, message.timeStamp)
|
||||
updateGroupUsers(message.group, message.isAddedUser, message.users, message.timeStamp)
|
||||
updateGroup(message.group, message.timeStamp)
|
||||
updateGroupExtraProperties($scope.groups[groupsById[message.group.id].index])
|
||||
}
|
||||
}
|
||||
else {
|
||||
addGroup(message.group, message.timeStamp)
|
||||
}
|
||||
})
|
||||
|
||||
$scope.$on('user.settings.users.created', function(event, message) {
|
||||
addUser(message.user, message.timeStamp)
|
||||
})
|
||||
|
||||
$scope.$on('user.settings.users.deleted', function(event, message) {
|
||||
deleteUser(message.user.email, message.timeStamp)
|
||||
})
|
||||
|
||||
$scope.$on('user.view.users.updated', function(event, message) {
|
||||
if (message.user.email === $scope.user.email) {
|
||||
$scope.user = message.user
|
||||
updateQuotaBars()
|
||||
}
|
||||
})
|
||||
|
||||
$scope.$on('user.settings.devices.created', function(event, message) {
|
||||
addDevice(message.device, message.timeStamp)
|
||||
})
|
||||
|
||||
$scope.$on('user.settings.devices.deleted', function(event, message) {
|
||||
deleteDevice(message.device.serial, message.timeStamp)
|
||||
})
|
||||
|
||||
$scope.$on('user.settings.devices.updated', function(event, message) {
|
||||
updateDevice(message.device, message.timeStamp)
|
||||
})
|
||||
|
||||
initScope()
|
||||
}
|
196
res/app/group-list/group-list.css
Normal file
196
res/app/group-list/group-list.css
Normal file
|
@ -0,0 +1,196 @@
|
|||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
.stf-pager-group-devices-search {
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
.stf-pager-group-list-total-items {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.stf-group-list .selectable {
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.stf-group-list .group-list .stf-pager-group-devices-search i.stf-pager-group-devices-search-icon {
|
||||
font-size: 12px;
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
.stf-group-list .group-list {
|
||||
min-height: 600px;
|
||||
}
|
||||
|
||||
.stf-group-list .group-list-header {
|
||||
margin: 20px 0px 20px 15px;
|
||||
}
|
||||
|
||||
.stf-group-list .btn-devices, .btn-users {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.stf-group-list .group-devices {
|
||||
width: auto;
|
||||
min-width: 475px;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.stf-group-list .group-users {
|
||||
width: auto;
|
||||
min-width: 475px;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.stf-group-list .group-icon {
|
||||
font-size: 15px;
|
||||
vertical-align: middle;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.stf-group-list .group-device-icon {
|
||||
font-size: 25px;
|
||||
vertical-align: middle;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.stf-group-list .group-user-icon {
|
||||
font-size: 20px;
|
||||
vertical-align: middle;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.stf-group-list .group-device-details, .group-user-details {
|
||||
display: inline-block;
|
||||
line-height: 2;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.stf-group-list .group-device-name, .group-user-name {
|
||||
color: #007aff;
|
||||
font-size: 14px;
|
||||
font-weight: 300;
|
||||
margin: 2px 0 6px;
|
||||
}
|
||||
|
||||
.stf-group-list .group-device-id, .group-user-id {
|
||||
font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
|
||||
font-size: 10px;
|
||||
margin-bottom: 2px;
|
||||
color: #999999;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.stf-group-list td,span {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.stf-group-list .user-line, .device-line {
|
||||
padding: 9px 15px 9px 2px;
|
||||
margin-left: 14px;
|
||||
border-bottom: 1px solid #dddddd;
|
||||
}
|
||||
|
||||
.stf-group-list .mailto {
|
||||
padding-bottom: 1px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.stf-group-list i.mailto {
|
||||
vertical-align: initial;
|
||||
}
|
||||
|
||||
.group-stats {
|
||||
min-height: 100px;
|
||||
height: 100px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
.group-stats [class^="col-"],
|
||||
.group-stats [class*="col-"] {
|
||||
height: 100%;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.group-stats [class^="col-"]:last-child,
|
||||
.group-stats [class*="col-"]:last-child {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.group-stats [class^="col-"] .number,
|
||||
.group-stats [class*="col-"] .number {
|
||||
font-size: 3.4em;
|
||||
font-weight: 100;
|
||||
line-height: 1.5em;
|
||||
letter-spacing: -0.06em;
|
||||
}
|
||||
|
||||
.group-stats [class^="col-"] .number .icon,
|
||||
.group-stats [class*="col-"] .number .icon {
|
||||
width: 50px;
|
||||
height: 38px;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
margin: 20px 12px 0 0;
|
||||
}
|
||||
|
||||
.group-quota-stats {
|
||||
min-height: 75px;
|
||||
height: 75px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.group-quota-stats .bar {
|
||||
height: 20px;
|
||||
vertical-align: top;
|
||||
margin: 14px 12px 12px 12px;
|
||||
}
|
||||
|
||||
.group-quota-stats .text,
|
||||
.group-stats [class^="col-"] .text,
|
||||
.group-stats [class*="col-"] .text {
|
||||
font-weight: 300;
|
||||
color: #aeaeae;
|
||||
text-transform: uppercase;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.group-stats .fa {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.group-stats {
|
||||
min-height: 60px;
|
||||
height: 60px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.group-stats .fa {
|
||||
font-size: 0.6em;
|
||||
}
|
||||
|
||||
.group-stats [class^="col-"] .number,
|
||||
.group-stats [class*="col-"] .number {
|
||||
font-size: 1.8em;
|
||||
line-height: normal;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.group-stats [class^="col-"] .number .icon,
|
||||
.group-stats [class*="col-"] .number .icon {
|
||||
width: 25px;
|
||||
height: 19px;
|
||||
margin: 10px 6px 0 0;
|
||||
}
|
||||
|
||||
.group-stats [class^="col-"] .text,
|
||||
.group-stats [class*="col-"] .text {
|
||||
font-size: 0.8em;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
14
res/app/group-list/group-list.pug
Normal file
14
res/app/group-list/group-list.pug
Normal file
|
@ -0,0 +1,14 @@
|
|||
//
|
||||
Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
//
|
||||
|
||||
.stf-group-list
|
||||
.row.unselectable
|
||||
.col-md-12
|
||||
div(ng-include="'group-list/stats/group-stats.pug'")
|
||||
.row.unselectable
|
||||
.col-md-12
|
||||
div(ng-include="'group-list/stats/group-quota-stats.pug'")
|
||||
.row.unselectable
|
||||
.col-md-12
|
||||
div(ng-include="'group-list/groups/groups.pug'")
|
167
res/app/group-list/groups/groups.pug
Normal file
167
res/app/group-list/groups/groups.pug
Normal file
|
@ -0,0 +1,167 @@
|
|||
//
|
||||
Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
//
|
||||
|
||||
.widget-container.fluid-height.overflow-auto.group-list
|
||||
.heading
|
||||
nothing-to-show(
|
||||
icon='fa-object-group'
|
||||
message='{{"No Groups" | translate}}' ng-if='!groups.length')
|
||||
|
||||
div(ng-if='groups.length')
|
||||
form.form-inline
|
||||
.form-group.group-list-header
|
||||
stf-pager(
|
||||
tooltip-label="{{'Group selection' | translate}}"
|
||||
total-items='filteredGroups.length'
|
||||
total-items-style='stf-pager-group-list-total-items'
|
||||
items-per-page='scopeGroupListCtrl.groupItemsPerPage'
|
||||
items-per-page-options='itemsPerPageOptions'
|
||||
current-page='scopeGroupListCtrl.groupCurrentPage'
|
||||
items-search='search')
|
||||
|
||||
.form-group.group-list-header
|
||||
stf-column-choice(reset-data='resetData()' column-data='groupData.columns')
|
||||
|
||||
button.btn.btn-xs.btn-primary-outline.pull-right(
|
||||
type='button'
|
||||
uib-tooltip="{{'Write an email to the group owner selection' | translate}}"
|
||||
ng-disabled='!filteredGroups.length'
|
||||
ng-click='mailToGroupOwners(filteredGroups)'
|
||||
tooltip-placement='top'
|
||||
tooltip-popup-delay='500')
|
||||
i.fa.fa-envelope-o
|
||||
span(translate) Contact Owners
|
||||
|
||||
table.table.table-hover.dataTable.ng-table
|
||||
thead
|
||||
tr
|
||||
th.header.sortable(
|
||||
ng-class='[column.sort]'
|
||||
ng-repeat='column in groupData.columns | filter: {selected: true}'
|
||||
ng-click='sortBy(groupData, column)')
|
||||
div.strong(ng-bind-template='{{::column.name | translate}}')
|
||||
tbody
|
||||
tr(ng-repeat="group in groups \
|
||||
| filter:search \
|
||||
| orderBy:groupColumns[groupData.sort.index].property:groupData.sort.reverse \
|
||||
| pagedObjectsFilter:scopeGroupListCtrl:'groupCurrentPage':'groupItemsPerPage':'filteredGroups' \
|
||||
track by group.id")
|
||||
|
||||
td(ng-if='groupData.columns[0].selected'
|
||||
ng-class="{'color-green': group.status === 'Active', \
|
||||
'color-red': group.status === 'Pending', \
|
||||
'color-orange': group.status === 'Ready'}") {{group.status | translate}}
|
||||
td.selectable(ng-if='groupData.columns[1].selected')
|
||||
i.fa.fa-object-group.group-icon
|
||||
span {{group.name}}
|
||||
td.selectable(ng-if='groupData.columns[2].selected') {{::group.id}}
|
||||
td(ng-if='groupData.columns[3].selected')
|
||||
a(ng-href="{{::'mailto:' + group.owner.email}}") {{::group.owner.name}}
|
||||
|
||||
td(ng-if='groupData.columns[4].selected')
|
||||
.btn-group.btn-devices(uib-dropdown auto-close='outsideClick')
|
||||
button.btn.btn-sm.btn-primary-outline.btn-devices(
|
||||
type='button'
|
||||
ng-disabled='!group.devices.length'
|
||||
ng-click='initGroupDevices(group)'
|
||||
uib-dropdown-toggle)
|
||||
span {{group.devices.length}}
|
||||
|
||||
ul.dropdown-menu.group-devices(
|
||||
ng-if='groupsEnv[group.id].deviceCurrentPage && groupsEnv[group.id].devices.length'
|
||||
uib-dropdown-menu role='menu' ng-click='$event.stopPropagation()')
|
||||
li
|
||||
a
|
||||
form.form-inline
|
||||
.form-group
|
||||
stf-pager(
|
||||
items-search-style='stf-pager-group-devices-search'
|
||||
icon-style='stf-pager-group-devices-search-icon'
|
||||
tooltip-label="{{'Device selection' | translate}}"
|
||||
total-items='groupsEnv[group.id].filteredDevices.length'
|
||||
items-per-page='groupsEnv[group.id].deviceItemsPerPage'
|
||||
items-per-page-options='itemsPerPageOptions'
|
||||
current-page='groupsEnv[group.id].deviceCurrentPage'
|
||||
items-search='deviceSearch')
|
||||
|
||||
li(ng-repeat="device in groupsEnv[group.id].devices \
|
||||
| filter:deviceSearch \
|
||||
| orderBy: 'model' \
|
||||
| pagedObjectsFilter:groupsEnv[group.id]:'deviceCurrentPage':'deviceItemsPerPage':'filteredDevices' \
|
||||
track by device.serial")
|
||||
|
||||
.device-line
|
||||
i.fa.fa-mobile.group-device-icon
|
||||
.group-device-details.selectable
|
||||
a.group-device-name(ng-bind-template="{{device.manufacturer + ' ' + device.model + ' (' + device.marketName + ')'}}")
|
||||
.group-device-id
|
||||
span(translate) Serial
|
||||
span(ng-bind-template="{{::': ' + device.serial + ' - '}}")
|
||||
span(translate) OS
|
||||
span(ng-bind-template="{{': ' + device.version + ' - '}}")
|
||||
span(translate) Screen
|
||||
span(ng-bind-template="{{': ' + device.display.width + 'x' + device.display.height + ' - '}}")
|
||||
span(translate) SDK
|
||||
span(ng-bind-template="{{': ' + device.sdk}}")
|
||||
|
||||
td(ng-if='groupData.columns[5].selected')
|
||||
.btn-group.btn-users(uib-dropdown auto-close='outsideClick')
|
||||
button.btn.btn-sm.btn-primary-outline.btn-users(
|
||||
type='button'
|
||||
ng-disabled='!group.users.length'
|
||||
ng-click='initGroupUsers(group)'
|
||||
uib-dropdown-toggle)
|
||||
span {{group.users.length}}
|
||||
|
||||
ul.dropdown-menu.group-users(
|
||||
ng-if='groupsEnv[group.id].userCurrentPage && groupsEnv[group.id].users'
|
||||
uib-dropdown-menu role='menu' ng-click='$event.stopPropagation()')
|
||||
li
|
||||
.user-line
|
||||
form
|
||||
.form-group.mailto
|
||||
button.btn.btn-xs.btn-primary-outline.pull-right(
|
||||
type='button'
|
||||
uib-tooltip="{{'Write an email to the group user selection' | translate}}"
|
||||
ng-disabled='!groupsEnv[group.id].filteredUsers.length'
|
||||
ng-click='mailToGroupUsers(group, groupsEnv[group.id].filteredUsers)'
|
||||
tooltip-placement='top'
|
||||
tooltip-popup-delay='500')
|
||||
i.fa.fa-envelope-o
|
||||
span(translate) Contact Users
|
||||
|
||||
form.form-inline
|
||||
.form-group
|
||||
stf-pager(
|
||||
items-search-style='stf-pager-group-devices-search'
|
||||
icon-style='stf-pager-group-devices-search-icon'
|
||||
tooltip-label="{{'User selection' | translate}}"
|
||||
total-items='groupsEnv[group.id].filteredUsers.length'
|
||||
items-per-page='groupsEnv[group.id].userItemsPerPage'
|
||||
items-per-page-options='itemsPerPageOptions'
|
||||
current-page='groupsEnv[group.id].userCurrentPage'
|
||||
items-search='userSearch')
|
||||
|
||||
li(ng-repeat="user in groupsEnv[group.id].users \
|
||||
| filter:userSearch \
|
||||
| orderBy: 'name' \
|
||||
| pagedObjectsFilter:groupsEnv[group.id]:'userCurrentPage':'userItemsPerPage':'filteredUsers' \
|
||||
track by user.email")
|
||||
.user-line
|
||||
i.fa.fa-user.group-user-icon
|
||||
.group-user-details.selectable
|
||||
a.group-user-name(
|
||||
ng-href="{{::'mailto:' + user.email}}"
|
||||
ng-bind-template="{{::user.name}}")
|
||||
.group-user-id
|
||||
span(translate) Email
|
||||
span(ng-bind-template="{{::': ' + user.email + ' - '}}")
|
||||
span(translate) Privilege
|
||||
span(ng-bind-template="{{::': ' + user.privilege}}")
|
||||
|
||||
td(ng-if='groupData.columns[6].selected') {{getClassName(group.class) | translate}}
|
||||
td(ng-if='groupData.columns[7].selected') {{group.repetitions}}
|
||||
td(ng-if='groupData.columns[8].selected') {{getDuration(group.duration)}}
|
||||
td(ng-if='groupData.columns[9].selected') {{group.startTime}}
|
||||
td(ng-if='groupData.columns[10].selected') {{group.stopTime}}
|
35
res/app/group-list/index.js
Normal file
35
res/app/group-list/index.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
require('./group-list.css')
|
||||
|
||||
module.exports = angular.module('group-list', [
|
||||
require('stf/column-choice').name,
|
||||
require('stf/groups').name,
|
||||
require('stf/user').name,
|
||||
require('stf/users').name,
|
||||
require('stf/devices').name,
|
||||
require('stf/settings').name,
|
||||
require('stf/util/common').name,
|
||||
require('stf/common-ui').name
|
||||
])
|
||||
.config(['$routeProvider', function($routeProvider) {
|
||||
$routeProvider
|
||||
.when('/groups', {
|
||||
template: require('./group-list.pug'),
|
||||
controller: 'GroupListCtrl'
|
||||
})
|
||||
}])
|
||||
.run(['$templateCache', function($templateCache) {
|
||||
$templateCache.put(
|
||||
'group-list/stats/group-stats.pug', require('./stats/group-stats.pug')
|
||||
)
|
||||
$templateCache.put(
|
||||
'group-list/stats/group-quota-stats.pug', require('./stats/group-quota-stats.pug')
|
||||
)
|
||||
$templateCache.put(
|
||||
'group-list/groups/groups.pug', require('./groups/groups.pug')
|
||||
)
|
||||
}])
|
||||
.controller('GroupListCtrl', require('./group-list-controller'))
|
14
res/app/group-list/stats/group-quota-stats.pug
Normal file
14
res/app/group-list/stats/group-quota-stats.pug
Normal file
|
@ -0,0 +1,14 @@
|
|||
//
|
||||
Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
//
|
||||
|
||||
.widget-container.group-quota-stats
|
||||
.col-xs-6
|
||||
uib-progressbar.bar(class='progress-striped' value='numberBar.value' type='{{numberBar.type}}')
|
||||
b {{numberBar.value}}%
|
||||
.text(translate) {{user.name}} groups number use
|
||||
.col-xs-6
|
||||
uib-progressbar.bar(class='progress-striped' value='durationBar.value' type='{{durationBar.type}}')
|
||||
b {{durationBar.value}}%
|
||||
.text(translate) {{user.name}} groups duration use
|
||||
|
25
res/app/group-list/stats/group-stats.pug
Normal file
25
res/app/group-list/stats/group-stats.pug
Normal file
|
@ -0,0 +1,25 @@
|
|||
//
|
||||
Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
//
|
||||
|
||||
.widget-container.group-stats
|
||||
.col-xs-3
|
||||
.number.color-blue
|
||||
.icon.fa.fa-globe
|
||||
span(ng-bind='groups.length')
|
||||
.text(translate) Total groups
|
||||
.col-xs-3
|
||||
.number.color-green
|
||||
.icon.fa.fa-play
|
||||
span(ng-bind='activeGroups')
|
||||
.text(translate) Active groups
|
||||
.col-xs-3
|
||||
.number.color-orange
|
||||
.icon.fa.fa-pause
|
||||
span(ng-bind='readyGroups')
|
||||
.text(translate) Ready groups
|
||||
.col-xs-3
|
||||
.number.color-pink
|
||||
.icon.fa.fa-stop
|
||||
span(ng-bind='pendingGroups')
|
||||
.text(translate) Pending groups
|
|
@ -1,6 +1,14 @@
|
|||
/**
|
||||
* Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
require('./menu.css')
|
||||
require('angular-cookies')
|
||||
|
||||
module.exports = angular.module('stf.menu', [
|
||||
'ngCookies',
|
||||
require('stf/socket').name,
|
||||
require('stf/util/common').name,
|
||||
require('stf/nav-menu').name,
|
||||
require('stf/settings').name,
|
||||
require('stf/common-ui/modals/external-url-modal').name,
|
||||
|
|
|
@ -1,5 +1,18 @@
|
|||
module.exports = function MenuCtrl($scope, $rootScope, SettingsService,
|
||||
$location, LogcatService) {
|
||||
/**
|
||||
* Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
module.exports = function MenuCtrl(
|
||||
$scope
|
||||
, $rootScope
|
||||
, SettingsService
|
||||
, $location
|
||||
, $http
|
||||
, CommonService
|
||||
, LogcatService
|
||||
, socket
|
||||
, $cookies
|
||||
, $window) {
|
||||
|
||||
SettingsService.bind($scope, {
|
||||
target: 'lastUsedDevice'
|
||||
|
@ -15,4 +28,21 @@ module.exports = function MenuCtrl($scope, $rootScope, SettingsService,
|
|||
$scope.isControlRoute = $location.path().search('/control') !== -1
|
||||
})
|
||||
|
||||
$scope.mailToSupport = function() {
|
||||
CommonService.url('mailto:' + $scope.contactEmail)
|
||||
}
|
||||
|
||||
$http.get('/auth/contact').then(function(response) {
|
||||
$scope.contactEmail = response.data.contact.email
|
||||
})
|
||||
|
||||
$scope.logout = function() {
|
||||
$cookies.remove('XSRF-TOKEN', {path: '/'})
|
||||
$cookies.remove('ssid', {path: '/'})
|
||||
$cookies.remove('ssid.sig', {path: '/'})
|
||||
$window.location = '/'
|
||||
setTimeout(function() {
|
||||
socket.disconnect()
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
//
|
||||
Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
//
|
||||
|
||||
.navbar.stf-menu(ng-controller='MenuCtrl')
|
||||
.container-fluid.stf-top-bar
|
||||
a.stf-logo(ng-href="/#!/devices") STF
|
||||
|
@ -9,6 +13,9 @@
|
|||
a(ng-href='/#!/devices', accesskey='1')
|
||||
span.fa.fa-sitemap
|
||||
span(ng-if='!$root.basicMode', translate) Devices
|
||||
a(ng-href='/#!/groups')
|
||||
span.fa.fa-object-group
|
||||
span(ng-if='!$root.basicMode', translate) Groups
|
||||
a(ng-href='/#!/settings')
|
||||
span.fa.fa-gears
|
||||
span(ng-if='!$root.basicMode', translate) Settings
|
||||
|
@ -18,7 +25,22 @@
|
|||
button(type='button', ng-model='$root.platform', uib-btn-radio="'web'", translate).btn.btn-sm.btn-default-outline Web
|
||||
button(type='button', ng-model='$root.platform', uib-btn-radio="'native'", translate).btn.btn-sm.btn-default-outline Native
|
||||
|
||||
li.stf-nav-web-native-button(ng-if='!$root.basicMode')
|
||||
button.btn.btn-sm.btn-default-outline(
|
||||
type='button'
|
||||
ng-click='mailToSupport()')
|
||||
i.fa.fa-envelope-o
|
||||
span(translate) Contact Support
|
||||
|
||||
li.stf-nav-web-native-button(ng-if='!$root.basicMode')
|
||||
button.btn.btn-sm.btn-default-outline(
|
||||
type='button'
|
||||
ng-click='logout()')
|
||||
i.fa.fa-sign-out
|
||||
span(translate) Logout
|
||||
|
||||
li(ng-show='!$root.basicMode')
|
||||
a(ng-href='/#!/help', accesskey='6')
|
||||
i.fa.fa-question-circle.fa-fw
|
||||
| {{ "Help" | translate }}
|
||||
|
||||
|
|
169
res/app/settings/devices/devices-controller.js
Normal file
169
res/app/settings/devices/devices-controller.js
Normal file
|
@ -0,0 +1,169 @@
|
|||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
const _ = require('lodash')
|
||||
|
||||
module.exports = function DevicesCtrl(
|
||||
$scope
|
||||
, DevicesService
|
||||
, SettingsService
|
||||
, ItemsPerPageOptionsService
|
||||
, GenericModalService
|
||||
, CommonService
|
||||
) {
|
||||
const devicesBySerial = {}
|
||||
const deviceFields =
|
||||
'model,' +
|
||||
'serial,' +
|
||||
'version,' +
|
||||
'display.height,' +
|
||||
'display.width,' +
|
||||
'manufacturer,' +
|
||||
'sdk,' +
|
||||
'abi,' +
|
||||
'cpuPlatform,' +
|
||||
'openGLESVersion,' +
|
||||
'marketName,' +
|
||||
'phone.imei,' +
|
||||
'provider.name,' +
|
||||
'group.originName'
|
||||
|
||||
|
||||
function publishDevice(device) {
|
||||
if (!device.model) {
|
||||
device.display = {}
|
||||
}
|
||||
else {
|
||||
device.displayStr = device.display.width + 'x' + device.display.height
|
||||
}
|
||||
for (var i in device) {
|
||||
if (device[i] === null) {
|
||||
device[i] = ''
|
||||
}
|
||||
}
|
||||
return device
|
||||
}
|
||||
|
||||
function addDevice(device, timeStamp) {
|
||||
return CommonService.add(
|
||||
$scope.devices
|
||||
, devicesBySerial
|
||||
, device
|
||||
, 'serial'
|
||||
, timeStamp)
|
||||
}
|
||||
|
||||
function updateDevice(device, timeStamp) {
|
||||
return CommonService.update(
|
||||
$scope.devices
|
||||
, devicesBySerial
|
||||
, device
|
||||
, 'serial'
|
||||
, timeStamp)
|
||||
}
|
||||
|
||||
function deleteDevice(serial, timeStamp) {
|
||||
return CommonService.delete(
|
||||
$scope.devices
|
||||
, devicesBySerial
|
||||
, serial
|
||||
, timeStamp)
|
||||
}
|
||||
|
||||
function initScope() {
|
||||
DevicesService.getOboeDevices('user', deviceFields, function(device) {
|
||||
addDevice(publishDevice(device), -1)
|
||||
})
|
||||
.done(function() {
|
||||
$scope.$digest()
|
||||
})
|
||||
}
|
||||
|
||||
SettingsService.bind($scope, {
|
||||
target: 'removingFilters'
|
||||
, source: 'DevicesRemovingFilters'
|
||||
, defaultValue: {
|
||||
present: 'False'
|
||||
, booked: 'False'
|
||||
, annotated: 'False'
|
||||
, controlled: 'False'
|
||||
}
|
||||
})
|
||||
$scope.devices = []
|
||||
$scope.confirmRemove = {value: true}
|
||||
$scope.scopeDevicesCtrl = $scope
|
||||
$scope.itemsPerPageOptions = ItemsPerPageOptionsService
|
||||
SettingsService.bind($scope, {
|
||||
target: 'deviceItemsPerPage'
|
||||
, source: 'deviceItemsPerPage'
|
||||
, defaultValue: $scope.itemsPerPageOptions[2]
|
||||
})
|
||||
$scope.removingFilterOptions = ['True', 'False', 'Any']
|
||||
|
||||
$scope.removeDevice = function(serial, askConfirmation) {
|
||||
if (askConfirmation) {
|
||||
GenericModalService.open({
|
||||
message: 'Really delete this device?'
|
||||
, type: 'Warning'
|
||||
, size: 'sm'
|
||||
, cancel: true
|
||||
})
|
||||
.then(function() {
|
||||
CommonService.errorWrapper(
|
||||
DevicesService.removeDevice
|
||||
, [serial, $scope.removingFilters]
|
||||
)
|
||||
})
|
||||
}
|
||||
else {
|
||||
CommonService.errorWrapper(
|
||||
DevicesService.removeDevice
|
||||
, [serial, $scope.removingFilters]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
$scope.removeDevices = function(search, filteredDevices, askConfirmation) {
|
||||
function removeDevices() {
|
||||
CommonService.errorWrapper(
|
||||
DevicesService.removeDevices
|
||||
, search ?
|
||||
[$scope.removingFilters, filteredDevices.map(function(device) {
|
||||
return device.serial
|
||||
})
|
||||
.join()] :
|
||||
[$scope.removingFilters]
|
||||
)
|
||||
}
|
||||
|
||||
if (askConfirmation) {
|
||||
GenericModalService.open({
|
||||
message: 'Really delete this selection of devices?'
|
||||
, type: 'Warning'
|
||||
, size: 'sm'
|
||||
, cancel: true
|
||||
})
|
||||
.then(function() {
|
||||
removeDevices()
|
||||
})
|
||||
}
|
||||
else {
|
||||
removeDevices()
|
||||
}
|
||||
}
|
||||
|
||||
$scope.$on('user.settings.devices.created', function(event, message) {
|
||||
addDevice(publishDevice(message.device), message.timeStamp)
|
||||
})
|
||||
|
||||
$scope.$on('user.settings.devices.deleted', function(event, message) {
|
||||
deleteDevice(message.device.serial, message.timeStamp)
|
||||
})
|
||||
|
||||
$scope.$on('user.settings.devices.updated', function(event, message) {
|
||||
updateDevice(publishDevice(message.device), message.timeStamp)
|
||||
})
|
||||
|
||||
initScope()
|
||||
}
|
21
res/app/settings/devices/devices-spec.js
Normal file
21
res/app/settings/devices/devices-spec.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
describe('DevicesCtrl', function() {
|
||||
|
||||
beforeEach(angular.mock.module(require('./index').name))
|
||||
|
||||
var scope, ctrl
|
||||
|
||||
beforeEach(inject(function($rootScope, $controller) {
|
||||
scope = $rootScope.$new()
|
||||
ctrl = $controller('DevicesCtrl', {$scope: scope})
|
||||
}))
|
||||
|
||||
it('should ...', inject(function() {
|
||||
expect(1).toEqual(1)
|
||||
|
||||
}))
|
||||
|
||||
})
|
65
res/app/settings/devices/devices.css
Normal file
65
res/app/settings/devices/devices.css
Normal file
|
@ -0,0 +1,65 @@
|
|||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
.stf-devices .selectable {
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.stf-pager-devices-total-items {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.stf-devices .device-header {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.stf-devices .heading .device-header-icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.stf-devices .device-list-icon {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.stf-devices .device-filters-items {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.stf-devices .device-filters-item {
|
||||
margin: 0px 10px 15px 15px;
|
||||
}
|
||||
|
||||
.stf-devices .devices-list .device-line {
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid #dddddd;
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
.stf-devices .devices-list .device-line.device-actions {
|
||||
padding-bottom: 23px;
|
||||
}
|
||||
|
||||
.stf-devices .device-list-details {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.stf-devices .device-list-label {
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.stf-devices .device-list-name {
|
||||
color: #007aff;
|
||||
font-size: 14px;
|
||||
font-weight: 300;
|
||||
margin: 2px 0 6px;
|
||||
}
|
||||
|
||||
.stf-devices .device-list-id {
|
||||
font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
|
||||
font-size: 10px;
|
||||
margin-bottom: 2px;
|
||||
color: #999999;
|
||||
font-weight: 300;
|
||||
}
|
137
res/app/settings/devices/devices.pug
Normal file
137
res/app/settings/devices/devices.pug
Normal file
|
@ -0,0 +1,137 @@
|
|||
//
|
||||
Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
//
|
||||
|
||||
.widget-container.fluid-height.stf-devices(ng-controller='DevicesCtrl')
|
||||
.heading
|
||||
i.fa.fa-mobile.device-header-icon
|
||||
span(translate) Device list
|
||||
|
||||
a.pull-right.btn.btn-sm(ng-href='')
|
||||
i.fa.fa-question-circle.fa-fw(uib-tooltip='{{"More about Devices" | translate}}' tooltip-placement='left')
|
||||
|
||||
.widget-content.padded
|
||||
|
||||
nothing-to-show(icon='fa-mobile' message='{{"No Devices" | translate}}' ng-if='!devices.length')
|
||||
|
||||
div(ng-if='devices.length')
|
||||
ul.list-group.devices-list
|
||||
li.list-group-item
|
||||
.device-line.device-actions
|
||||
form.form-inline.device-header
|
||||
.form-group
|
||||
stf-pager(
|
||||
tooltip-label="{{'Device selection' | translate}}"
|
||||
total-items='filteredDevices.length'
|
||||
total-items-style='stf-pager-devices-total-items'
|
||||
items-per-page='scopeDevicesCtrl.deviceItemsPerPage'
|
||||
items-per-page-options='itemsPerPageOptions'
|
||||
current-page='scopeDevicesCtrl.deviceCurrentPage'
|
||||
items-search='search')
|
||||
|
||||
button.btn.btn-xs.btn-danger.pull-right(
|
||||
type='button'
|
||||
uib-tooltip="{{'Remove the device selection' | translate}}"
|
||||
tooltip-placement='bottom'
|
||||
tooltip-popup-delay='500'
|
||||
ng-disabled='!filteredDevices.length'
|
||||
ng-click='removeDevices(search, filteredDevices, confirmRemove.value)')
|
||||
i.fa.fa-trash-o
|
||||
span(translate) Remove
|
||||
|
||||
button.btn.btn-xs.btn-success.pull-right(
|
||||
type='button'
|
||||
uib-tooltip="{{'Enable/Disable confirmation for device removing' | translate}}"
|
||||
tooltip-placement='top'
|
||||
tooltip-popup-delay='500'
|
||||
ng-click='confirmRemove.value = !confirmRemove.value'
|
||||
ng-class='{"btn-warning-outline": !confirmRemove.value, "btn-success": confirmRemove.value}')
|
||||
i.fa.fa-lock(ng-if='confirmRemove.value')
|
||||
i.fa.fa-unlock(ng-if='!confirmRemove.value')
|
||||
span(translate) Confirm Remove
|
||||
|
||||
button.btn.btn-xs.btn-danger-outline.pull-right(
|
||||
type='button'
|
||||
uib-tooltip="{{'Set filters for device removing' | translate}}"
|
||||
tooltip-placement='top'
|
||||
tooltip-popup-delay='500'
|
||||
ng-click='showFilters = !showFilters'
|
||||
ng-class='{"btn-danger-outline": !showFilters, "btn-danger": showFilters}')
|
||||
i.fa.fa-trash-o
|
||||
span(translate) Filters
|
||||
|
||||
li.list-group-item(ng-if='showFilters')
|
||||
.device-line
|
||||
.heading
|
||||
i.fa.fa-trash-o
|
||||
span(translate) Removing filters
|
||||
|
||||
form.form-inline.device-filters-items
|
||||
.form-group.device-filters-item
|
||||
label.device-list-label(
|
||||
translate
|
||||
uib-tooltip="{{'Device presence state' | translate}}"
|
||||
tooltip-placement='top'
|
||||
tooltip-popup-delay='500') Present
|
||||
select(
|
||||
ng-model='removingFilters.present'
|
||||
ng-options='option for option in removingFilterOptions')
|
||||
|
||||
.form-group.device-filters-item
|
||||
label.device-list-label(
|
||||
translate
|
||||
uib-tooltip="{{'Device booking state' | translate}}"
|
||||
tooltip-placement='top'
|
||||
tooltip-popup-delay='500') Booked
|
||||
select(
|
||||
ng-model='removingFilters.booked'
|
||||
ng-options='option for option in removingFilterOptions')
|
||||
|
||||
.form-group.device-filters-item
|
||||
label.device-list-label(
|
||||
translate
|
||||
uib-tooltip="{{'Device notes state' | translate}}"
|
||||
tooltip-placement='top'
|
||||
tooltip-popup-delay='500') Annotated
|
||||
select(
|
||||
ng-model='removingFilters.annotated'
|
||||
ng-options='option for option in removingFilterOptions')
|
||||
|
||||
.form-group.device-filters-item
|
||||
label.device-list-label(
|
||||
translate
|
||||
uib-tooltip="{{'Device controlling state' | translate}}"
|
||||
tooltip-placement='top'
|
||||
tooltip-popup-delay='500') Controlled
|
||||
select(
|
||||
ng-model='removingFilters.controlled'
|
||||
ng-options='option for option in removingFilterOptions')
|
||||
|
||||
li.list-group-item(ng-repeat="device in devices \
|
||||
| filter:search \
|
||||
| orderBy: 'model' \
|
||||
| pagedObjectsFilter:scopeDevicesCtrl:'deviceCurrentPage':'deviceItemsPerPage':'filteredDevices' \
|
||||
track by device.serial")
|
||||
.device-line.device-actions
|
||||
i.fa.fa-mobile.fa-2x.fa-fw.device-list-icon
|
||||
.device-list-details.selectable
|
||||
.device-list-name(ng-bind-template="{{device.manufacturer + ' ' + device.model + ' (' + device.marketName + ')'}}")
|
||||
.device-list-id
|
||||
span(translate) Serial
|
||||
span(ng-bind-template="{{::': ' + device.serial + ' - '}}")
|
||||
span(translate) OS
|
||||
span(ng-bind-template="{{': ' + device.version + ' - '}}")
|
||||
span(translate) Screen
|
||||
span(ng-bind-template="{{': ' + device.displayStr + ' - '}}")
|
||||
span(translate) SDK
|
||||
span(ng-bind-template="{{': ' + device.sdk + ' - '}}")
|
||||
span(translate) Location
|
||||
span(ng-bind-template="{{': ' + device.provider.name + ' - '}}")
|
||||
span(translate) Group Origin
|
||||
span(ng-bind-template="{{': ' + device.group.originName}}")
|
||||
|
||||
button.btn.btn-xs.btn-danger-outline.pull-right(
|
||||
type='button'
|
||||
ng-click='removeDevice(device.serial, confirmRemove.value)')
|
||||
i.fa.fa-trash-o
|
||||
span(translate) Remove
|
18
res/app/settings/devices/index.js
Normal file
18
res/app/settings/devices/index.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
require('./devices.css')
|
||||
|
||||
module.exports = angular.module('stf.settings.devices', [
|
||||
require('stf/common-ui').name,
|
||||
require('stf/settings').name,
|
||||
require('stf/util/common').name,
|
||||
require('stf/devices').name
|
||||
])
|
||||
.run(['$templateCache', function($templateCache) {
|
||||
$templateCache.put(
|
||||
'settings/devices/devices.pug', require('./devices.pug')
|
||||
)
|
||||
}])
|
||||
.controller('DevicesCtrl', require('./devices-controller'))
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue