From 82648439b945cc5d049886f7e79c2f0dd9d14ff9 Mon Sep 17 00:00:00 2001 From: Udo Walter Date: Mon, 26 Nov 2018 19:12:31 +0000 Subject: [webapp+server] Add first implementation of a websocket to alert webclients of events and to synchronize notification across multiple webapp instances of the same user --- server/api/clients.js | 2 + server/api/users.js | 9 +- server/bin/www | 7 + server/lib/authentication.js | 8 +- server/lib/socketio.js | 41 ++++ server/package-lock.json | 290 ++++++++++++++++++++++- server/package.json | 1 + webapp/config/index.js | 6 +- webapp/package-lock.json | 227 +++++++++++++++++- webapp/package.json | 1 + webapp/src/components/AccountModule.vue | 9 +- webapp/src/components/DashboardPage.vue | 6 +- webapp/src/components/GroupModuleDialog.vue | 34 ++- webapp/src/components/LoginPage.vue | 2 + webapp/src/components/NotificationsAlerts.vue | 37 ++- webapp/src/components/NotificationsSnackbars.vue | 2 +- webapp/src/main.js | 25 +- webapp/src/store/groups.js | 18 +- webapp/src/store/notifications.js | 7 +- 19 files changed, 688 insertions(+), 44 deletions(-) create mode 100644 server/lib/socketio.js diff --git a/server/api/clients.js b/server/api/clients.js index a436213..54832eb 100644 --- a/server/api/clients.js +++ b/server/api/clients.js @@ -1,6 +1,7 @@ /* global __appdir */ var path = require('path') var db = require(path.join(__appdir, 'lib', 'sequelize')) +var io = require(path.join(__appdir, 'lib', 'socketio')) const backendHelper = require(path.join(__appdir, 'lib', 'external-backends', 'backendhelper')) // GET Requests @@ -35,6 +36,7 @@ module.exports.post = { }) } else { db.client.create(req.body.info).then(client => { + io.in('broadcast newClient').emit('notifications newAlert', { type: 'info', text: 'New client!' }) if (req.body.groupIds) client.setGroups(req.body.groupIds).then(() => { res.send({ id: client.id }) }) }) } diff --git a/server/api/users.js b/server/api/users.js index 98c72f9..35da1db 100644 --- a/server/api/users.js +++ b/server/api/users.js @@ -7,13 +7,10 @@ var jwt = require('jsonwebtoken') module.exports.get = { getUserInfo: function (req, res) { - // Because veryfyToken was succesfully excecuted the request has the attribute token. - const token = req.token - // Decode the token. - var decoded = jwt.decode(token, { complete: true }) - var userid = decoded.payload.user.id + var decodedToken = jwt.decode(req.token, { complete: true }) + var userId = decodedToken.payload.user.id - db.user.findOne({ where: { id: userid } }).then(userDb => { + db.user.findOne({ where: { id: userId } }).then(userDb => { var user = { } user.id = userDb.id user.username = userDb.username diff --git a/server/bin/www b/server/bin/www index 3618506..3cfebbd 100755 --- a/server/bin/www +++ b/server/bin/www @@ -31,6 +31,13 @@ var options = { } var server = https.createServer(options, app) +/** + * Setup socket.io. + */ + +var io = require(path.join(__appdir, 'lib', 'socketio')) +io.attach(server) + /** * Listen on provided port, on all network interfaces. */ diff --git a/server/lib/authentication.js b/server/lib/authentication.js index 5623b1e..0681011 100644 --- a/server/lib/authentication.js +++ b/server/lib/authentication.js @@ -92,11 +92,15 @@ module.exports = { } else if (req.cookies.jwt_hp && req.cookies.jwt_s) { token = req.cookies.jwt_hp + '.' + req.cookies.jwt_s } else { - return res.status(403).send({ auth: false, status: 'TOKEN_MISSING', error_message: 'This service requires a token.' }) + if (res) return res.status(403).send({ auth: false, status: 'TOKEN_MISSING', error_message: 'This service requires a token.' }) + else return next(new Error('TOKEN_MISSING')) } // Verify the token with the secret. jwt.verify(token, config.secret, function (err) { - if (err) return res.status(500).send({ auth: false, status: 'TOKEN_INVALID', error_message: 'The provided token is invalid.' }) + if (err) { + if (res) return res.status(500).send({ auth: false, status: 'TOKEN_INVALID', error_message: 'The provided token is invalid.' }) + else return next(new Error('TOKEN_INVALID')) + } req.token = token next() }) diff --git a/server/lib/socketio.js b/server/lib/socketio.js new file mode 100644 index 0000000..37d20cd --- /dev/null +++ b/server/lib/socketio.js @@ -0,0 +1,41 @@ +var path = require('path') +var cookie = require('cookie') +var jwt = require('jsonwebtoken') +var io = require('socket.io')({ serveClient: false }); + +// ############################################################################ +// ####################### websocket middleware ############################# + +/* global __appdir */ +var auth = require(path.join(__appdir, 'lib', 'authentication')) +io.use(function (socket, next){ + socket.request.cookies = cookie.parse(socket.request.headers.cookie || '') + auth.verifyToken(socket.request, null, next); +}) + +// ############################################################################ +// ####################### websocket listeners ############################## + +io.on('connection', socket => { + console.log('Socket.io: A user connected.') + socket.on('disconnect', () => console.log('Socket.io: A user disconnected.')) + + var decodedToken = jwt.decode(socket.request.token, { complete: true }) + var userId = decodedToken.payload.user.id + + socket.join(userId) + + // Join broadcast rooms (TODO: check if user has permission to join those rooms) + socket.join('broadcast newClient') + + // Notification broadcasts + socket.on('notifications clearAll', () => socket.to(userId).emit('notifications clearAll')) + socket.on('notifications newAlert', data => socket.to(userId).emit('notifications newAlert', data)) + socket.on('notifications closeAlert', id => socket.to(userId).emit('notifications closeAlert', id)) + socket.on('notifications resetNewAlertCount', () => socket.to(userId).emit('notifications resetNewAlertCount')) +}) + +// ############################################################################ +// ############################################################################ + +module.exports = io \ No newline at end of file diff --git a/server/package-lock.json b/server/package-lock.json index a2024ac..0e58316 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -83,6 +83,11 @@ "acorn": "^5.0.3" } }, + "after": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", + "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=" + }, "ajv": { "version": "5.5.2", "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", @@ -158,6 +163,11 @@ "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" }, + "arraybuffer.slice": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", + "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==" + }, "arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", @@ -176,6 +186,11 @@ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" }, + "async-limiter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -217,7 +232,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "requires": { "ansi-styles": "^2.2.1", @@ -243,11 +258,26 @@ "regenerator-runtime": "^0.11.0" } }, + "backo2": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", + "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, + "base64-arraybuffer": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", + "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=" + }, + "base64id": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", + "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=" + }, "basic-auth": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.0.tgz", @@ -265,11 +295,24 @@ "tweetnacl": "^0.14.3" } }, + "better-assert": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", + "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", + "requires": { + "callsite": "1.0.0" + } + }, "bignumber.js": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-4.1.0.tgz", "integrity": "sha512-eJzYkFYy9L4JzXsbymsFn3p54D+llV27oTQ+ziJG7WFRheJcNZilgVXMG0LoZtlQSKBsJdWtLFqOD0u+U0jZKA==" }, + "blob": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", + "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==" + }, "bluebird": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", @@ -350,6 +393,11 @@ "callsites": "^0.2.0" } }, + "callsite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=" + }, "callsites": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", @@ -488,6 +536,21 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.18.0.tgz", "integrity": "sha512-6CYPa+JP2ftfRU2qkDK+UTVeQYosOg/2GbcjIcKPHfinyOLPVGXu/ovN86RP49Re5ndJK1N0kuiidFFuepc4ZQ==" }, + "component-bind": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", + "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=" + }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + }, + "component-inherit": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", + "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=" + }, "compressible": { "version": "2.0.15", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.15.tgz", @@ -768,6 +831,69 @@ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" }, + "engine.io": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.2.1.tgz", + "integrity": "sha512-+VlKzHzMhaU+GsCIg4AoXF1UdDFjHHwMmMKqMJNDNLlUlejz58FCy4LBqB2YVJskHGYl06BatYWKP2TVdVXE5w==", + "requires": { + "accepts": "~1.3.4", + "base64id": "1.0.0", + "cookie": "0.3.1", + "debug": "~3.1.0", + "engine.io-parser": "~2.1.0", + "ws": "~3.3.1" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + } + } + }, + "engine.io-client": { + "version": "3.2.1", + "resolved": "http://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz", + "integrity": "sha512-y5AbkytWeM4jQr7m/koQLc5AxpRKC1hEVUb/s1FUAWEJq5AzJJ4NLvzuKPuxtDi5Mq755WuDvZ6Iv2rXj4PTzw==", + "requires": { + "component-emitter": "1.2.1", + "component-inherit": "0.0.3", + "debug": "~3.1.0", + "engine.io-parser": "~2.1.1", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "ws": "~3.3.1", + "xmlhttprequest-ssl": "~1.5.4", + "yeast": "0.1.2" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + } + } + }, + "engine.io-parser": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.3.tgz", + "integrity": "sha512-6HXPre2O4Houl7c4g7Ic/XzPnHBvaEmN90vtRO9uLmwtRqQmTOw0QMevL1TOfL2Cpu1VzsaTmMotQgMdkzGkVA==", + "requires": { + "after": "0.8.2", + "arraybuffer.slice": "~0.0.7", + "base64-arraybuffer": "0.1.5", + "blob": "0.0.5", + "has-binary2": "~1.0.2" + } + }, "error-ex": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", @@ -1550,6 +1676,26 @@ "ansi-regex": "^2.0.0" } }, + "has-binary2": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", + "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", + "requires": { + "isarray": "2.0.1" + }, + "dependencies": { + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" + } + } + }, + "has-cors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", + "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=" + }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -1604,6 +1750,11 @@ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" }, + "indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=" + }, "inflection": { "version": "1.12.0", "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz", @@ -2325,6 +2476,11 @@ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, + "object-component": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", + "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=" + }, "object-keys": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", @@ -2435,6 +2591,22 @@ "error-ex": "^1.2.0" } }, + "parseqs": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", + "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", + "requires": { + "better-assert": "~1.0.0" + } + }, + "parseuri": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", + "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", + "requires": { + "better-assert": "~1.0.0" + } + }, "parseurl": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", @@ -3079,6 +3251,90 @@ } } }, + "socket.io": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.1.1.tgz", + "integrity": "sha512-rORqq9c+7W0DAK3cleWNSyfv/qKXV99hV4tZe+gGLfBECw3XEhBy7x85F3wypA9688LKjtwO9pX9L33/xQI8yA==", + "requires": { + "debug": "~3.1.0", + "engine.io": "~3.2.0", + "has-binary2": "~1.0.2", + "socket.io-adapter": "~1.1.0", + "socket.io-client": "2.1.1", + "socket.io-parser": "~3.2.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + } + } + }, + "socket.io-adapter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz", + "integrity": "sha1-KoBeihTWNyEk3ZFZrUUC+MsH8Gs=" + }, + "socket.io-client": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.1.1.tgz", + "integrity": "sha512-jxnFyhAuFxYfjqIgduQlhzqTcOEQSn+OHKVfAxWaNWa7ecP7xSNk2Dx/3UEsDcY7NcFafxvNvKPmmO7HTwTxGQ==", + "requires": { + "backo2": "1.0.2", + "base64-arraybuffer": "0.1.5", + "component-bind": "1.0.0", + "component-emitter": "1.2.1", + "debug": "~3.1.0", + "engine.io-client": "~3.2.0", + "has-binary2": "~1.0.2", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "object-component": "0.0.3", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "socket.io-parser": "~3.2.0", + "to-array": "0.1.4" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + } + } + }, + "socket.io-parser": { + "version": "3.2.0", + "resolved": "http://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz", + "integrity": "sha512-FYiBx7rc/KORMJlgsXysflWx/RIvtqZbyGLlHZvjfmPTPeuD/I8MaW7cfFrj5tRltICJdgwflhfZ3NVVbVLFQA==", + "requires": { + "component-emitter": "1.2.1", + "debug": "~3.1.0", + "isarray": "2.0.1" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" + } + } + }, "sodium-native": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-2.2.1.tgz", @@ -3534,7 +3790,7 @@ }, "through": { "version": "2.3.8", - "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, "timers-ext": { @@ -3554,6 +3810,11 @@ "os-tmpdir": "~1.0.2" } }, + "to-array": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", + "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=" + }, "toidentifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", @@ -3615,6 +3876,11 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, + "ultron": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", + "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==" + }, "umzug": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/umzug/-/umzug-2.1.0.tgz", @@ -3762,6 +4028,21 @@ "mkdirp": "^0.5.1" } }, + "ws": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "requires": { + "async-limiter": "~1.0.0", + "safe-buffer": "~5.1.0", + "ultron": "~1.1.0" + } + }, + "xmlhttprequest-ssl": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", + "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=" + }, "xtend": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", @@ -3804,6 +4085,11 @@ "requires": { "camelcase": "^4.1.0" } + }, + "yeast": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", + "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=" } } } diff --git a/server/package.json b/server/package.json index 09bbb42..7061c78 100644 --- a/server/package.json +++ b/server/package.json @@ -23,6 +23,7 @@ "sequelize": "^4.38.1", "sequelize-cli": "^4.1.1", "shelljs": "^0.8.2", + "socket.io": "^2.1.1", "standard": "^11.0.1" }, "devDependencies": { diff --git a/webapp/config/index.js b/webapp/config/index.js index e283935..c1ed721 100644 --- a/webapp/config/index.js +++ b/webapp/config/index.js @@ -42,9 +42,13 @@ module.exports = { cssSourceMap: true, proxyTable: { - // proxy all requests starting with /api to express server + // proxy all requests starting with /api and /socket.io to the express server '/api': { target: 'https://localhost:' + process.env.API_PORT + '/' + }, + '/socket.io': { + target: 'https://localhost:' + process.env.API_PORT + '/', + ws: true } } }, diff --git a/webapp/package-lock.json b/webapp/package-lock.json index 83b8532..1989e9f 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -216,6 +216,11 @@ } } }, + "after": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", + "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=" + }, "ajv": { "version": "5.5.2", "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", @@ -369,6 +374,11 @@ "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", "dev": true }, + "arraybuffer.slice": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", + "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==" + }, "arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", @@ -419,8 +429,7 @@ "async-limiter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", - "dev": true + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" }, "atob": { "version": "2.1.1", @@ -1306,6 +1315,11 @@ "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", "dev": true }, + "backo2": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", + "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -1372,6 +1386,11 @@ } } }, + "base64-arraybuffer": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", + "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=" + }, "base64-js": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", @@ -1384,6 +1403,14 @@ "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", "dev": true }, + "better-assert": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", + "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", + "requires": { + "callsite": "1.0.0" + } + }, "bfj-node4": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/bfj-node4/-/bfj-node4-5.3.1.tgz", @@ -1407,6 +1434,11 @@ "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=", "dev": true }, + "blob": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", + "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==" + }, "bluebird": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", @@ -1705,6 +1737,11 @@ "callsites": "^0.2.0" } }, + "callsite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=" + }, "callsites": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", @@ -2084,11 +2121,20 @@ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, + "component-bind": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", + "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=" + }, "component-emitter": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", - "dev": true + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + }, + "component-inherit": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", + "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=" }, "compressible": { "version": "2.0.15", @@ -3034,6 +3080,56 @@ "once": "^1.4.0" } }, + "engine.io-client": { + "version": "3.2.1", + "resolved": "http://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz", + "integrity": "sha512-y5AbkytWeM4jQr7m/koQLc5AxpRKC1hEVUb/s1FUAWEJq5AzJJ4NLvzuKPuxtDi5Mq755WuDvZ6Iv2rXj4PTzw==", + "requires": { + "component-emitter": "1.2.1", + "component-inherit": "0.0.3", + "debug": "~3.1.0", + "engine.io-parser": "~2.1.1", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "ws": "~3.3.1", + "xmlhttprequest-ssl": "~1.5.4", + "yeast": "0.1.2" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "ws": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "requires": { + "async-limiter": "~1.0.0", + "safe-buffer": "~5.1.0", + "ultron": "~1.1.0" + } + } + } + }, + "engine.io-parser": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.3.tgz", + "integrity": "sha512-6HXPre2O4Houl7c4g7Ic/XzPnHBvaEmN90vtRO9uLmwtRqQmTOw0QMevL1TOfL2Cpu1VzsaTmMotQgMdkzGkVA==", + "requires": { + "after": "0.8.2", + "arraybuffer.slice": "~0.0.7", + "base64-arraybuffer": "0.1.5", + "blob": "0.0.5", + "has-binary2": "~1.0.2" + } + }, "enhanced-resolve": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz", @@ -4863,6 +4959,26 @@ "ansi-regex": "^2.0.0" } }, + "has-binary2": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", + "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", + "requires": { + "isarray": "2.0.1" + }, + "dependencies": { + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" + } + } + }, + "has-cors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", + "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=" + }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -5330,8 +5446,7 @@ "indexof": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", - "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", - "dev": true + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=" }, "inflight": { "version": "1.0.6", @@ -6598,6 +6713,11 @@ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true }, + "object-component": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", + "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=" + }, "object-copy": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", @@ -6897,6 +7017,22 @@ "error-ex": "^1.2.0" } }, + "parseqs": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", + "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", + "requires": { + "better-assert": "~1.0.0" + } + }, + "parseuri": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", + "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", + "requires": { + "better-assert": "~1.0.0" + } + }, "parseurl": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", @@ -9930,8 +10066,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-regex": { "version": "1.1.0", @@ -10264,6 +10399,62 @@ "kind-of": "^3.2.0" } }, + "socket.io-client": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.1.1.tgz", + "integrity": "sha512-jxnFyhAuFxYfjqIgduQlhzqTcOEQSn+OHKVfAxWaNWa7ecP7xSNk2Dx/3UEsDcY7NcFafxvNvKPmmO7HTwTxGQ==", + "requires": { + "backo2": "1.0.2", + "base64-arraybuffer": "0.1.5", + "component-bind": "1.0.0", + "component-emitter": "1.2.1", + "debug": "~3.1.0", + "engine.io-client": "~3.2.0", + "has-binary2": "~1.0.2", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "object-component": "0.0.3", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "socket.io-parser": "~3.2.0", + "to-array": "0.1.4" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + } + } + }, + "socket.io-parser": { + "version": "3.2.0", + "resolved": "http://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz", + "integrity": "sha512-FYiBx7rc/KORMJlgsXysflWx/RIvtqZbyGLlHZvjfmPTPeuD/I8MaW7cfFrj5tRltICJdgwflhfZ3NVVbVLFQA==", + "requires": { + "component-emitter": "1.2.1", + "debug": "~3.1.0", + "isarray": "2.0.1" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" + } + } + }, "sockjs": { "version": "0.3.19", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz", @@ -10692,6 +10883,11 @@ "os-tmpdir": "~1.0.2" } }, + "to-array": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", + "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=" + }, "to-arraybuffer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", @@ -10890,6 +11086,11 @@ } } }, + "ultron": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", + "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==" + }, "union-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", @@ -11931,6 +12132,11 @@ "safe-buffer": "~5.1.0" } }, + "xmlhttprequest-ssl": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", + "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=" + }, "xtend": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", @@ -12042,6 +12248,11 @@ "dev": true } } + }, + "yeast": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", + "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=" } } } diff --git a/webapp/package.json b/webapp/package.json index 73d6809..d27666a 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "axios": "^0.18.0", + "socket.io-client": "^2.1.1", "vue": "^2.5.17", "vue-codemirror": "^4.0.6", "vue-i18n": "^7.8.1", diff --git a/webapp/src/components/AccountModule.vue b/webapp/src/components/AccountModule.vue index 8d93dbe..e9a4cce 100644 --- a/webapp/src/components/AccountModule.vue +++ b/webapp/src/components/AccountModule.vue @@ -9,7 +9,9 @@ @@ -19,9 +21,14 @@ export default { name: 'AccountPage', data () { return { + testId: 0 } }, methods: { + newAlert () { + this.$alert({ type: 'success', text: 'test ' + this.testId }) + this.testId++ + } } } diff --git a/webapp/src/components/DashboardPage.vue b/webapp/src/components/DashboardPage.vue index 89c8c0c..53d19be 100644 --- a/webapp/src/components/DashboardPage.vue +++ b/webapp/src/components/DashboardPage.vue @@ -65,7 +65,7 @@ language invert_colors - + @@ -125,7 +125,8 @@ export default { drawerTranslateX: 0, drawerMini: localStorage.getItem('drawerMini') === 'true', drawerOpen: localStorage.getItem('drawerOpen') !== 'false', - userFullName: '' + userFullName: '', + clientCount: 0 } }, computed: { @@ -190,6 +191,7 @@ export default { this.$http.post('/api/logout').then(response => { this.setLoginRedirect(this.$route.fullPath) this.$router.push('/login') + this.$socket.close() }) } }, diff --git a/webapp/src/components/GroupModuleDialog.vue b/webapp/src/components/GroupModuleDialog.vue index 7c0905d..8f88f29 100644 --- a/webapp/src/components/GroupModuleDialog.vue +++ b/webapp/src/components/GroupModuleDialog.vue @@ -15,6 +15,20 @@ "client": "Add clients | Add this client? | Add these {0} clients?" } }, + "success": { + "delete": { + "client": "Successfully deleted client | Successfully deleted {0} clients", + "group": "Successfully deleted group | Successfully deleted {0} groups" + }, + "remove": { + "client": "Successfully removed client | Successfully removed {0} clients", + "group": "Successfully removed group | Successfully removed {0} groups" + }, + "add": { + "client": "Successfully added client | Successfully added {0} clients", + "group": "Successfully added group | Successfully added {0} groups" + } + }, "deletePermanently": { "group": "Permanently delete group | Permanently delete groups", "client": "Permanently delete client | Permanently delete clients" @@ -38,6 +52,20 @@ "client": "Clienten hinzufügen | Diesen Clienten hinzufügen? | Diese {0} Clienten hinzufügen?" } }, + "success": { + "delete": { + "client": "Client erfolgreich gelöscht | {0} Clienten erfolgreich gelöscht", + "group": "Gruppe erfolgreich gelöscht | {0} Gruppen erfolgreich gelöscht" + }, + "remove": { + "client": "Client erfolgreich entfernt | {0} Clienten erfolgreich entfernt", + "group": "Gruppe erfolgreich entfernt | {0} Gruppen erfolgreich entfernt" + }, + "add": { + "client": "Client erfolgreich hinzugefügt | {0} Clienten erfolgreich hinzugefügt", + "group": "Gruppe erfolgreich hinzugefügt | {0} Gruppen erfolgreich hinzugefügt" + } + }, "deletePermanently": { "group": "Gruppe dauerhaft löschen | Gruppen dauerhaft löschen", "client": "Client dauerhaft löschen | Clienten dauerhaft löschen" @@ -157,7 +185,11 @@ export default { 'remove': { 'group': 'removeSubroups', 'client': 'removeClients' }, 'add': { 'group': 'addSubgroups', 'client': 'addClients' } } - var data = { ...this.dialog.info } + var count = this.action === 'add' ? this.selected.length : this.dialog.info.selected.length + var data = { ...this.dialog.info, callback: () => this.$snackbar({ + color: 'success', + text: this.$tc('success.' + this.action + '.' + this.dialog.info.type, count, [count]) + }) } if (this.action === 'add') data.selected = this.selected this.$store.dispatch('groups/' + actionMap[this.action][this.dialog.info.type], data) this.setDialog({ show: false }) diff --git a/webapp/src/components/LoginPage.vue b/webapp/src/components/LoginPage.vue index aeaf66e..40b08ee 100644 --- a/webapp/src/components/LoginPage.vue +++ b/webapp/src/components/LoginPage.vue @@ -48,6 +48,7 @@ diff --git a/webapp/src/components/NotificationsSnackbars.vue b/webapp/src/components/NotificationsSnackbars.vue index ce768d6..c01c5c4 100644 --- a/webapp/src/components/NotificationsSnackbars.vue +++ b/webapp/src/components/NotificationsSnackbars.vue @@ -14,7 +14,7 @@ v-model="showSnackbar" bottom right - :timeout="currentSnackbar.timeout || 2000" + :timeout="currentSnackbar.timeout || 4000" :color="currentSnackbar.color" > {{ currentSnackbar.text }} diff --git a/webapp/src/main.js b/webapp/src/main.js index 3f59ede..8637c96 100644 --- a/webapp/src/main.js +++ b/webapp/src/main.js @@ -10,6 +10,7 @@ import Vuex from 'vuex' import globalStore from '@/store/global' import storeModules from '@/config/store' import axios from 'axios' +import io from 'socket.io-client' import { router, registerRouterGuards } from '@/router' import VueI18n from 'vue-i18n' import i18nMessages from '@/config/i18n' @@ -60,8 +61,30 @@ axios.interceptors.response.use(null, error => { return Promise.reject(error) }) +const socket = io({ + autoConnect: false +}); + +socket.on('error', function(err) { + console.log('Socket.io error: ' + err); + if (err === 'TOKEN_MISSING' || err === 'TOKEN_INVALID') { + socket.close() + console.log('Closing socket.'); + } +}); + +socket.on('hello', data => { + console.log(data) +}) + +socket.open() + Vue.prototype.$http = axios -Vue.prototype.$alert = function (data) { +Vue.prototype.$socket = socket + +Vue.prototype.$alert = function (data, emit = true) { + if (!data.id) data.id = '_' + Math.random().toString(36).substr(2, 9) + if (emit) socket.emit('notifications newAlert', data) store.commit('notifications/newAlert', data) } Vue.prototype.$snackbar = function (data) { diff --git a/webapp/src/store/groups.js b/webapp/src/store/groups.js index 1bbded9..adc78f8 100644 --- a/webapp/src/store/groups.js +++ b/webapp/src/store/groups.js @@ -130,7 +130,7 @@ export default { } }) }, - deleteGroups (context, { selected }) { + deleteGroups (context, { selected, callback }) { const ids = selected.map(x => x.id) axios.post('/api/groups/delete', { ids }).then(() => { var i = 1 @@ -142,18 +142,20 @@ export default { i++ } context.dispatch('reload') + if (callback) callback() }) }, - deleteClients (context, { selected }) { + deleteClients (context, { selected, callback }) { const ids = selected.map(x => x.id) axios.post('/api/clients/delete', { ids }).then(() => { const index = context.state.tabChain.length - 1 const item = context.state.tabChain[index] if (item.tabType === 'client' && ids.includes(item.id)) context.commit('deleteFromTabChain', { index, count: 1 }) context.dispatch('reload') + if (callback) callback() }) }, - removeSubroups (context, { tabIndex, selected }) { + removeSubroups (context, { tabIndex, selected,callback }) { const id = context.state.tabChain[tabIndex].id const ids = selected.map(x => x.id) axios.post('/api/groups/removeSubgroups', { id, ids }).then(() => { @@ -163,9 +165,10 @@ export default { context.commit('deleteFromTabChain', { index: tabIndex + 1, count: context.state.tabChain.length - (tabIndex + 1) }) } context.dispatch('reload') + if (callback) callback() }) }, - removeClients (context, { tabIndex, selected }) { + removeClients (context, { tabIndex, selected, callback }) { const id = context.state.tabChain[tabIndex].id const ids = selected.map(x => x.id) axios.post('/api/groups/removeClients', { id, ids }).then(() => { @@ -175,20 +178,23 @@ export default { context.commit('deleteFromTabChain', { index: tabIndex + 1, count: 1 }) } context.dispatch('reload') + if (callback) callback() }) }, - addSubgroups (context, { tabIndex, selected }) { + addSubgroups (context, { tabIndex, selected, callback }) { const id = context.state.tabChain[tabIndex].id const ids = selected.map(x => x.id) axios.post('/api/groups/addSubgroups', { id, ids }).then(() => { context.dispatch('reload') + if (callback) callback() }) }, - addClients (context, { tabIndex, selected }) { + addClients (context, { tabIndex, selected, callback }) { const id = context.state.tabChain[tabIndex].id const ids = selected.map(x => x.id) axios.post('/api/groups/addClients', { id, ids }).then(() => { context.dispatch('reload') + if (callback) callback() }) } } diff --git a/webapp/src/store/notifications.js b/webapp/src/store/notifications.js index 9f41d07..4687b78 100644 --- a/webapp/src/store/notifications.js +++ b/webapp/src/store/notifications.js @@ -23,10 +23,13 @@ export default { state.newAlertCount++ state.alerts.unshift(data) }, - removeAlert (state, a) { + removeAlert (state, id) { + const a = state.alerts.find(el => el.id === id) a.show = false setTimeout(function () { - state.alerts.splice(state.alerts.indexOf(a), 1) + var index = state.alerts.indexOf(a) + state.alerts.splice(index, 1) + if (index < state.newAlertCount && index >= 0) state.newAlertCount-- }, 200) }, resetNewAlertCount (state) { -- cgit v1.2.3-55-g7522