diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 000000000..950dbbba7 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,5 @@ +{ + "globals": { "sails": true }, + "node" : true, + "asi" : true +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 06ea85266..424972273 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -95,3 +95,24 @@ All notable changes to this project will be documented in this file. * Relative paths support for assets and sails.io. * Local db path is now configurable and persistent in docker image with it's own volume. + +## [0.7.1](https://github.com/pantsel/konga/releases/tag/v0.7.1) - 16-5-2017 + +* Fixed bug that updated localStorage user when performing a user update even if the acting user wasn't the same. +* Added the --harmony parameter on start.sh fixing Node version compatibility issues with some modules (nodemailer) + + +## [0.7.2](https://github.com/pantsel/konga/releases/tag/v0.7.2) - 17-5-2017 + +* UI/UX improvements. +* User sign up. +* More configurable application settings. + + +## [0.7.3](https://github.com/pantsel/konga/releases/tag/v0.7.3) - 23-5-2017 + +* UI/UX revamp. +* Massive refactoring and logic improvements. +* Configurable user permissions. +* Various bug fixes and improvements. + diff --git a/Dockerfile b/Dockerfile index cbc9a012b..18c5f198d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,3 @@ -# Note that if you want to build Konga from here, -# you must < cd frontend && gulp dist > first FROM mhart/alpine-node diff --git a/README.md b/README.md index c39f8ffda..ee89f288a 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,6 @@ [Site and working demo here](https://pantsel.github.io/konga/) -> Konga [0.7.0](https://github.com/pantsel/konga/tree/konga-070) is underway and introduces new cool features like consumers exporting, Node and API health checks as well as Email notifications. Whoever wants to test it and drop some feedback is most welcome to do so. Don't forget to run Konga in dev mode once so that the db changes will be applied. ## Summary diff --git a/api/base/KongController.js b/api/base/KongController.js index 543bb6c55..986cbd7cc 100644 --- a/api/base/KongController.js +++ b/api/base/KongController.js @@ -1,8 +1,10 @@ 'use strict'; -var KongService = require('../services/KongService') +var KongService = require('../services/KongService') +var unirest = require("unirest") module.exports = { + create : function(req,res) { return KongService.create(req,res) }, diff --git a/api/controllers/AuthController.js b/api/controllers/AuthController.js index 71037aab8..54846b708 100644 --- a/api/controllers/AuthController.js +++ b/api/controllers/AuthController.js @@ -2,173 +2,264 @@ var async = require('async'); var _ = require('lodash'); +var uuid = require('node-uuid'); +var UserSignUp = require("../events/user-events") /** * Authentication Controller - * - * This is merely meant as an example of how your Authentication controller should look. It currently - * includes the minimum amount of functionality for the basics of Passport.js to work. */ var AuthController = { - /** - * Log out a user and return them to the homepage - * - * Passport exposes a logout() function on request (also aliased as logOut()) that can be - * called from any route handler which needs to terminate a login session. Invoking logout() - * will remove the request.user property and clear the login session (if any). - * - * For more information on logging out users in Passport.js, check out: - * http://passportjs.org/guide/logout/ - * - * @param {Request} request Request object - * @param {Response} response Response object - */ - logout: function logout(request, response) { - request.logout(); - - response.json(200, true); - }, - - /** - * Create a third-party authentication endpoint - * - * @param {Request} request Request object - * @param {Response} response Response object - */ - provider: function provider(request, response) { - sails.services['passport'].endpoint(request, response); - }, - - /** - * Simple action to check current auth status of user. Note that this will always send - * HTTP status 200 and actual data will contain either user object or boolean false in - * cases that user is not authenticated. - * - * @todo Hmmm, I think that this will return always false, because of missing of - * actual sessions here... - * - * @param {Request} request Request object - * @param {Response} response Response object - */ - authenticated: function authenticated(request, response) { - if (request.isAuthenticated()) { - response.json(200, request.user); - } else { - response.json(200, false); - } - }, - - /** - * Create a authentication callback endpoint - * - * This endpoint handles everything related to creating and verifying Passports - * and users, both locally and from third-party providers. - * - * Passport exposes a login() function on request (also aliased as logIn()) that - * can be used to establish a login session. When the login operation completes, - * user will be assigned to request.user. - * - * For more information on logging in users in Passport.js, check out: - * http://passportjs.org/guide/login/ - * - * @param {Request} request Request object - * @param {Response} response Response object - */ - callback: function callback(request, response) { - sails.services['passport'].callback(request, response, function callback(error, user) { - request.login(user, function callback(error) { - // If an error was thrown, redirect the user to the login which should - // take care of rendering the error messages. - if (error) { - sails.log.verbose('User authentication failed'); - sails.log.verbose(error); - - response.json(401, error); - } else { // Upon successful login, send back user data and JWT token - - - response.json(200, { - user: user, - token: sails.services['token'].issue(_.isObject(user.id) ? JSON.stringify(user.id) : user.id) - }); + + + signup: function (req, res) { + + var data = req.allParams() + var passports = data.passports + delete data.passports; + delete data.password_confirmation + + + // Assign activation token + data.activationToken = uuid.v4(); + + // Check settings as to what to do after signup + sails.models.settings + .find() + .limit(1) + .exec(function(err,settings){ + if(err) return res.negotiate(err) + var _settings = settings[0].data; + + if(!_settings.signup_require_activation) { + data.active = true; // Activate user automatically + } + + + sails.models.user + .create(data) + .exec(function (err, user) { + if (err) return res.negotiate(err) + + sails.models.passport + .create({ + protocol: passports.protocol, + password: passports.password, + user: user.id + }).exec(function (err, passport) { + if (err) return res.negotiate(err) + + // Emit signUp event + UserSignUp.emit('user.signUp',{ + user : user, + sendActivationEmail : _settings.signup_require_activation + }); + + return res.json(user) + }) + }) + + }) + + + }, + + + + activate : function (req,res) { + + + + var token = req.param('token') + if(!token) { + return res.badRequest('Token is required.') } - }); - }); - }, - - /** - * Action to check if given password is same as current user password. Note that - * this action is only allowed authenticated users. And by default given password - * is checked against to current user. - * - * @param {Request} request Request object - * @param {Response} response Response object - */ - checkPassword: function checkPassword(request, response) { + + sails.models.user.findOne({ + activationToken : token, + activated : false + }).exec(function (err,user) { + if(err) return res.negotiate(err) + if(!user) return res.notFound('Invalid token') + + sails.models.user.update({ + id:user.id + },{active:true}) + .exec(function (err,updated) { + if(err) return res.negotiate(err) + return res.redirect('/#!/login?activated=' + req.param('token')) + }) + }) + + }, + /** - * Job to fetch current user local passport data. This is needed - * to validate given password. + * Log out a user and return them to the homepage * - * @param {Function} next Callback function + * Passport exposes a logout() function on request (also aliased as logOut()) that can be + * called from any route handler which needs to terminate a login session. Invoking logout() + * will remove the request.user property and clear the login session (if any). + * + * For more information on logging out users in Passport.js, check out: + * http://passportjs.org/guide/logout/ + * + * @param {Request} request Request object + * @param {Response} response Response object */ - var findPassport = function findPassport(next) { - var where = { - user: request.token, - protocol: 'local' - }; - - sails.models['passport'] - .findOne(where) - .exec(function callback(error, passport) { - if (error) { - next(error); - } else if (!passport) { - next({message: 'Given authorization token is not valid'}); - } else { - next(null, passport); - } - }) - ; - }; + logout: function logout(request, response) { + request.logout(); + + response.json(200, true); + }, /** - * Job to validate given password against user passport object. + * Create a third-party authentication endpoint * - * @param {sails.model.passport} passport Passport object - * @param {Function} next Callback function + * @param {Request} request Request object + * @param {Response} response Response object */ - var validatePassword = function validatePassword(passport, next) { - var password = request.param('password'); + provider: function provider(request, response) { + sails.services.passport.endpoint(request, response); + }, - passport.validatePassword(password, function callback(error, matched) { - if (error) { - next({message: 'Invalid password'}); + /** + * Simple action to check current auth status of user. Note that this will always send + * HTTP status 200 and actual data will contain either user object or boolean false in + * cases that user is not authenticated. + * + * @todo Hmmm, I think that this will return always false, because of missing of + * actual sessions here... + * + * @param {Request} request Request object + * @param {Response} response Response object + */ + authenticated: function authenticated(request, response) { + if (request.isAuthenticated()) { + response.json(200, request.user); } else { - next(null, matched); + response.json(200, false); } - }); - }; + }, /** - * Main callback function which is called when all specified jobs are - * processed or an error has occurred while processing. + * Create a authentication callback endpoint + * + * This endpoint handles everything related to creating and verifying Passports + * and users, both locally and from third-party providers. + * + * Passport exposes a login() function on request (also aliased as logIn()) that + * can be used to establish a login session. When the login operation completes, + * user will be assigned to request.user. + * + * For more information on logging in users in Passport.js, check out: + * http://passportjs.org/guide/login/ * - * @param {null|Error} error Possible error - * @param {null|boolean} result If passport was valid or not + * @param {Request} request Request object + * @param {Response} response Response object */ - var callback = function callback(error, result) { - if (error) { - response.json(401, error); - } else if (result) { - response.json(200, result); - } else { - response.json(400, {message: 'Given password does not match.'}); - } - }; - - // Run necessary tasks and handle results - async.waterfall([findPassport, validatePassword], callback); - } + callback: function callback(request, response) { + sails.services.passport.callback(request, response, function callback(error, user) { + + // User must be active + if(user && !user.active) { + return response.forbidden({ + message : 'Account is not activated.' + }); + } + + + request.login(user, function callback(error) { + // If an error was thrown, redirect the user to the login which should + // take care of rendering the error messages. + if (error) { + sails.log.verbose('User authentication failed'); + sails.log.verbose(error); + + response.json(401, error); + } else { // Upon successful login, send back user data and JWT token + + + response.json(200, { + user: user, + token: sails.services.token.issue(_.isObject(user.id) ? JSON.stringify(user.id) : user.id) + }); + } + }); + }); + }, + + /** + * Action to check if given password is same as current user password. Note that + * this action is only allowed authenticated users. And by default given password + * is checked against to current user. + * + * @param {Request} request Request object + * @param {Response} response Response object + */ + checkPassword: function checkPassword(request, response) { + /** + * Job to fetch current user local passport data. This is needed + * to validate given password. + * + * @param {Function} next Callback function + */ + var findPassport = function findPassport(next) { + var where = { + user: request.token, + protocol: 'local' + }; + + sails.models.passport + .findOne(where) + .exec(function callback(error, passport) { + if (error) { + next(error); + } else if (!passport) { + next({message: 'Given authorization token is not valid'}); + } else { + next(null, passport); + } + }) + ; + }; + + /** + * Job to validate given password against user passport object. + * + * @param {sails.model.passport} passport Passport object + * @param {Function} next Callback function + */ + var validatePassword = function validatePassword(passport, next) { + var password = request.param('password'); + + passport.validatePassword(password, function callback(error, matched) { + if (error) { + next({message: 'Invalid password'}); + } else { + next(null, matched); + } + }); + }; + + /** + * Main callback function which is called when all specified jobs are + * processed or an error has occurred while processing. + * + * @param {null|Error} error Possible error + * @param {null|boolean} result If passport was valid or not + */ + var callback = function callback(error, result) { + if (error) { + response.json(401, error); + } else if (result) { + response.json(200, result); + } else { + response.json(400, {message: 'Given password does not match.'}); + } + }; + + // Run necessary tasks and handle results + async.waterfall([findPassport, validatePassword], callback); + } }; module.exports = AuthController; diff --git a/api/controllers/ConsumerController.js b/api/controllers/ConsumerController.js deleted file mode 100644 index 72a393ada..000000000 --- a/api/controllers/ConsumerController.js +++ /dev/null @@ -1,22 +0,0 @@ -'use strict'; -var _ = require('lodash') - -/** - * UserController - * - * @description :: Server-side logic for managing Users - * @help :: See http://links.sailsjs.org/docs/controllers - */ -module.exports = { - /** - * Proxy requests to native Kong Admin API - * @param req - * @param res - */ - proxy : function(req,res) { - global.$proxy.web(req, res, { - target: sails.config.kong_admin_url - }); - }, - -}; diff --git a/api/controllers/KongApiController.js b/api/controllers/KongApiController.js deleted file mode 100644 index 2105d0832..000000000 --- a/api/controllers/KongApiController.js +++ /dev/null @@ -1,8 +0,0 @@ -'use strict'; - -var _ = require('lodash'); - -var KongApiController = _.merge(_.cloneDeep(require('../base/KongController')), { -}); - -module.exports = KongApiController; diff --git a/api/controllers/KongConsumerController.js b/api/controllers/KongConsumerController.js deleted file mode 100644 index 06cd36532..000000000 --- a/api/controllers/KongConsumerController.js +++ /dev/null @@ -1,134 +0,0 @@ -'use strict'; - -var unirest = require('unirest'); -var _ = require('lodash'); - -var KongService = require('../services/KongService') -var ConsumerCredentialsService = require('../services/ConsumerCredentialsService') - -var KongConsumerController = _.merge(_.cloneDeep(require('../base/KongController')), { - - create : function(req,res) { - - var import_id; - if(req.body.import_id) { - //import_id = req.body.import_id - delete req.body.import_id - } - KongService.createCb(req,res,function(error,consumer){ - if(error) return res.kongError(error) - - // Insert created consumer to Konga - //consumer.import_id = import_id || '' - //consumer.node_id = req.node_id - //sails.models.consumer.create(consumer).exec(function (err, record) { - // if(err) return res.kongError(err) - // return res.json(record) - //}); - - return res.json(consumer) - }) - }, - - update : function(req,res) { - KongService.updateCb(req,res,function(error,consumer){ - if(error) return res.kongError(error) - - return res.json(consumer); - - //// Update consumer Konga consumer - //sails.models.consumer - // .update({id:req.params.id - // },{ - // username: consumer.username, - // custom_id : consumer.custom_id - // }).exec(function afterwards(err, updated){ - // - // if(err) return res.kongError(err) - // return res.json(consumer); - //}); - }) - }, - - delete : function(req,res) { - KongService.deleteCb(req,res,function(error,deleted){ - if(error) return res.kongError(error) - - return res.ok(); - - //// Delete consumer from Konga - //sails.models.consumer.destroy({ - // id: req.params.id - //}).exec(function (err){ - // if(err) return res.kongError(err) - // console.log("Destroyed consumer with id " + req.params.id) - // return res.ok(); - //}); - }) - }, - - createCredential : function(req,res) { - unirest.post(sails.config.kong_admin_url + '/consumers/' + req.params.id + "/" + req.params.credential) - .send(req.body) - .end(function(response){ - if(response.error) return res.kongError(response) - - return res.json(response.body) - }) - }, - - removeCredential : function(req,res) { - unirest.delete(sails.config.kong_admin_url + '/consumers/' + req.params.id + "/" + req.params.credential + "/" + req.params.credential_id) - .end(function(response){ - if(response.error) return res.kongError(response) - - return res.json(response.body) - }) - }, - - listCredentials : function(req,res) { - - ConsumerCredentialsService.listCredentials(req.params.id,function(err,result){ - if(err) return res.negotiate(err) - return res.json(result) - }) - }, - - - - retrieveCredentials : function(req,res) { - unirest.get(sails.config.kong_admin_url + '/consumers/' + req.params.id + "/" + req.params.credential) - .end(function(response){ - if(response.error) return res.kongError(response) - return res.json(response.body) - }) - }, - - addAcl : function(req,res) { - unirest.post(sails.config.kong_admin_url + '/consumers/' + req.params.id + "/acls") - .send({ - group : req.body.group - }) - .end(function(response){ - if(response.error) return res.kongError(response); - return res.json(response.body) - }) - }, - - retrieveAcls : function(req,res) { - unirest.get(sails.config.kong_admin_url + '/consumers/' + req.params.id + "/acls") - .end(function(response){ - if(response.error) return res.kongError(response); - return res.json(response.body) - }) - }, - - deleteAcl : function(req,res) { - unirest.delete(sails.config.kong_admin_url + '/consumers/' + req.params.id + "/acls/" + req.params.aclId) - .end(function(response){ - if(response.error) return res.kongError(response); - return res.json(response.body) - }) - } -}) -module.exports = KongConsumerController; diff --git a/api/controllers/KongInfoController.js b/api/controllers/KongInfoController.js deleted file mode 100644 index 544e5af8a..000000000 --- a/api/controllers/KongInfoController.js +++ /dev/null @@ -1,42 +0,0 @@ -'use strict'; - -var unirest = require('unirest'); - -/** - * Authentication Controller - * - * This is merely meant as an example of how your Authentication controller should look. It currently - * includes the minimum amount of functionality for the basics of Passport.js to work. - */ -var KongInfoController = { - info : function(req,res) { - console.log("sails.config.kong_admin_url",sails.config.kong_admin_url) - unirest.get(sails.config.kong_admin_url) - .end(function(response){ - if(response.error) return res.negotiate(response.error) - return res.json(response.body) - }) - }, - - status : function(req,res) { - unirest.get(( req.query.kong_admin_url || sails.config.kong_admin_url ) + "/status") - .end(function(response){ - if(response.error) return res.negotiate(response.error) - return res.json(response.body) - }) - }, - - cluster : function(req,res) { - unirest.get(sails.config.kong_admin_url + "/cluster") - .end(function(response){ - if(response.error) return res.negotiate(response.error) - return res.json(response.body) - }) - }, - - deleteCluster : function(req,res) { - // ToDo - } -}; - -module.exports = KongInfoController; diff --git a/api/controllers/KongNodeController.js b/api/controllers/KongNodeController.js index 786f26f3e..155fa6147 100644 --- a/api/controllers/KongNodeController.js +++ b/api/controllers/KongNodeController.js @@ -2,12 +2,6 @@ var _ = require('lodash'); -/** - * UserController - * - * @description :: Server-side logic for managing Users - * @help :: See http://links.sailsjs.org/docs/controllers - */ module.exports = _.merge(_.cloneDeep(require('../base/Controller')), { subscribeHealthChecks: function(req, res) { diff --git a/api/controllers/KongPluginController.js b/api/controllers/KongPluginController.js deleted file mode 100644 index 19257730d..000000000 --- a/api/controllers/KongPluginController.js +++ /dev/null @@ -1,39 +0,0 @@ -'use strict'; - -var unirest = require("unirest") -var _ = require('lodash'); -var KongPluginService = require('../services/KongPluginService') - -var KongPluginController = _.merge(_.cloneDeep(require('../base/KongController')), { - - create : function(req,res) { - return KongPluginService.create(req,res) - - }, - - retrieveEnabled : function(req,res) { - unirest.get(sails.config.kong_admin_url + '/plugins/enabled/') - .end(function(response){ - if(response.error) return res.kongError(response) - return res.json(response.body) - }) - }, - - retrieveSchema : function(req,res) { - unirest.get(sails.config.kong_admin_url + '/plugins/schema/' + req.params.plugin) - .end(function(response){ - if(response.error) return res.kongError(response) - return res.json(response.body) - }) - }, - - listApi : function(req,res) { - unirest.get(sails.config.kong_admin_url + '/apis/' + req.params.api + '/plugins/') - .end(function(response){ - if(response.error) return res.kongError(response) - return res.json(response.body) - }) - }, -}); - -module.exports = KongPluginController; diff --git a/api/controllers/KongPluginsController.js b/api/controllers/KongPluginsController.js new file mode 100644 index 000000000..5f09b5dfe --- /dev/null +++ b/api/controllers/KongPluginsController.js @@ -0,0 +1,13 @@ +/** + * Created by user on 18/06/2017. + */ + +'use strict'; + +var KongPluginService = require('../services/KongPluginService'); + +module.exports = { + list : function (req,res) { + return KongPluginService.richList(req,res); + } +} \ No newline at end of file diff --git a/api/controllers/ApiController.js b/api/controllers/KongProxyController.js similarity index 62% rename from api/controllers/ApiController.js rename to api/controllers/KongProxyController.js index a576d84f5..75f12435d 100644 --- a/api/controllers/ApiController.js +++ b/api/controllers/KongProxyController.js @@ -1,5 +1,5 @@ /** - * ApiController + * RemoteApiController */ var unirest = require("unirest") @@ -14,23 +14,30 @@ module.exports = { */ proxy : function(req,res) { - - req.url = req.url.replace('/api','') // Remove the /api prefix + req.url = req.url.replace('/kong','') // Remove the /api prefix sails.log("req.url",req.url) - // Fix upstream method - console.log("req.url.split('/')",req.url.split('/')) - if(req.url.split('/')[1] == 'upstreams' && req.method.toLowerCase() == 'put') { + // Fix update method by setting it to "PATCH" + // as Kong requires + + if(req.method.toLowerCase() == 'put') { req.method = "PATCH" } - sails.log("ApiController",sails.config.kong_admin_url + req.url) + sails.log("KongProxyController",req.node_id + req.url) sails.log("req.method",req.method) - var request = unirest[req.method.toLowerCase()](sails.config.kong_admin_url + req.url) - request.headers({'Content-Type': 'application/json'}) + var headers = {'Content-Type': 'application/json'} + + // If apikey is set in headers, use it + if(req.kong_api_key) { + headers['apikey'] = req.kong_api_key + } + + var request = unirest[req.method.toLowerCase()](req.node_id + req.url) + request.headers(headers) if(['post','put','patch'].indexOf(req.method.toLowerCase()) > -1) { @@ -56,14 +63,5 @@ module.exports = { if (response.error) return res.negotiate(response) return res.json(response.body) }) - - - //if(req.body) { - // req.body = JSON.stringify(req.body) - //} - // - //global.$proxy.web(req, res, { - // target: sails.config.kong_admin_url - //}); } }; \ No newline at end of file diff --git a/api/controllers/KongUpstreamsController.js b/api/controllers/KongUpstreamsController.js deleted file mode 100644 index 15ddc2d06..000000000 --- a/api/controllers/KongUpstreamsController.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -var _ = require('lodash'); - -var KongUpstreamsController = _.merge(_.cloneDeep(require('../base/KongController')), { - - -}) -module.exports = KongUpstreamsController; diff --git a/api/controllers/RemoteStorageController.js b/api/controllers/RemoteStorageController.js index 99ee04a07..09aa737e7 100644 --- a/api/controllers/RemoteStorageController.js +++ b/api/controllers/RemoteStorageController.js @@ -1,14 +1,13 @@ 'use strict'; +'use strict' + var unirest = require('unirest'); var RemoteStorageService = require('../services/remote/RemoteStorageService') var adapters = require('../services/remote/adapters') /** - * Authentication Controller - * - * This is merely meant as an example of how your Authentication controller should look. It currently - * includes the minimum amount of functionality for the basics of Passport.js to work. + * RemoteStorageController */ var RemoteStorageController = { diff --git a/api/controllers/SettingsController.js b/api/controllers/SettingsController.js index fca8000b6..0946d0397 100644 --- a/api/controllers/SettingsController.js +++ b/api/controllers/SettingsController.js @@ -10,4 +10,15 @@ var _ = require('lodash'); */ module.exports = _.merge(_.cloneDeep(require('../base/Controller')), { + find : function(req,res) { + + sails.models.settings.find().limit(1) + .exec(function(err,settings){ + + if(err) return res.negotiate(err) + // Store settings in memory + sails.KONGA_CONFIG = settings[0].data || {} + return res.json(settings[0] ? settings[0] : {}) + }) + } }); diff --git a/api/controllers/SnapshotController.js b/api/controllers/SnapshotController.js index 2819e8937..86f445ba2 100644 --- a/api/controllers/SnapshotController.js +++ b/api/controllers/SnapshotController.js @@ -8,9 +8,24 @@ var KongService = require('../services/KongService') var _ = require('lodash') var async = require('async'); +var fs = require('fs'); module.exports = _.merge(_.cloneDeep(require('../base/Controller')), { + subscribe: function(req, res) { + + if (!req.isSocket) { + sails.log.error("SnapshotsController:subscribe failed") + return res.badRequest('Only a client socket can subscribe.'); + } + + var roomName = 'events.snapshots'; + sails.sockets.join(req.socket, roomName); + res.json({ + room: roomName + }); + }, + takeSnapShot : function(req,res) { @@ -24,6 +39,7 @@ module.exports = _.merge(_.cloneDeep(require('../base/Controller')), { }) + res.ok() // Reply directly because snapshot creation may take some time var result = {} @@ -50,7 +66,6 @@ module.exports = _.merge(_.cloneDeep(require('../base/Controller')), { async.series(fns,function(err,data){ if(err) return res.negotiate(err) - // Foreach consumer get it's acls var consumerFns = [] result.consumers.forEach(function(consumer){ @@ -120,8 +135,15 @@ module.exports = _.merge(_.cloneDeep(require('../base/Controller')), { kong_version : node.kong_version, data : result }).exec(function(err,created){ - if(err) return res.negotiate(err) - return res.json(created) + if(err) { + sails.sockets.blast('events.snapshots', { + verb : 'failed', + data : { + name : req.param("name") + } + }); + } + }) }) }else{ @@ -131,14 +153,19 @@ module.exports = _.merge(_.cloneDeep(require('../base/Controller')), { kong_version : node.kong_version, data : result }).exec(function(err,created){ - if(err) return res.negotiate(err) - return res.json(created) + if(err) { + sails.sockets.blast('events.snapshots', { + verb : 'failed', + data : { + name : req.param("name") + } + }); + } + }) } }) - - }); }) @@ -166,7 +193,7 @@ module.exports = _.merge(_.cloneDeep(require('../base/Controller')), { snapshot.data[key].forEach(function(item){ fns.push(function(cb){ - // For consumers, we need to import their acls and credentials as well + // For consumers, we need to import their ACLSs and credentials as well var consumerAcls = [] var consumerCredentials = [] @@ -185,7 +212,7 @@ module.exports = _.merge(_.cloneDeep(require('../base/Controller')), { } - KongService.createFromEndpointCb("/" + key,item,function(err,created){ + KongService.createFromEndpointCb("/" + key,item,req,function(err,created){ if(!responseData[key]) { responseData[key] = { @@ -211,7 +238,7 @@ module.exports = _.merge(_.cloneDeep(require('../base/Controller')), { // Import acls consumerAcls.forEach(function(acl){ consumerFns.push(function(cb){ - KongService.createFromEndpointCb("/" + key + "/" + item.id + "/acls",acl,function(err,created){ + KongService.createFromEndpointCb("/" + key + "/" + item.id + "/acls",acl,req,function(err,created){ if(err) { responseData[key].failed.count++ @@ -231,8 +258,9 @@ module.exports = _.merge(_.cloneDeep(require('../base/Controller')), { Object.keys(consumerCredentials).forEach(function(credentialKey){ credentialKey,consumerCredentials[credentialKey].forEach(function(credentialData){ + consumerFns.push(function(cb){ - KongService.createFromEndpointCb("/" + key + "/" + item.id + "/" + credentialKey,credentialData,function(err,created){ + KongService.createFromEndpointCb("/" + key + "/" + item.id + "/" + credentialKey,credentialData,req,function(err,created){ if(err) { responseData[key].failed.count++ @@ -272,6 +300,38 @@ module.exports = _.merge(_.cloneDeep(require('../base/Controller')), { return res.ok(responseData) }) + }) + }, + + download : function (req,res) { + var id = req.param('id'); + var SkipperDisk = require('skipper-disk'); + var fileAdapter = SkipperDisk(/* optional opts */); + + + + sails.models.snapshot.findOne({ + id : id + }).exec(function (err,data) { + if(err) return res.negotiate(err) + if(!data) return res.notFound() + + var location = sails.config.paths.uploads + "snapshot_" + data.id + ".json"; + + if (fs.existsSync(location)){ + fileAdapter.read(location).on('error', function (err) { + return res.negotiate(err); + }).pipe(res); + }else{ + fs.writeFile(location, JSON.stringify(data), 'utf8', + function(err,file){ + if(err) return res.negotiate(err) + fileAdapter.read(location).on('error', function (err) { + return res.negotiate(err); + }).pipe(res); + }); + } + }) } diff --git a/api/controllers/UserController.js b/api/controllers/UserController.js index 13268e364..e08cb7079 100644 --- a/api/controllers/UserController.js +++ b/api/controllers/UserController.js @@ -9,4 +9,66 @@ var _ = require('lodash'); * @help :: See http://links.sailsjs.org/docs/controllers */ module.exports = _.merge(_.cloneDeep(require('../base/Controller')), { + + + + subscribe: function(req, res) { + + if (!req.isSocket) { + sails.log.error("UserController:subscribe failed") + return res.badRequest('Only a client socket can subscribe.'); + } + + var roomName = 'user.' + req.param("id") + '.updated'; + sails.sockets.join(req.socket, roomName); + res.json({ + room: roomName + }); + }, + + update : function(req,res) { + + console.log(req.body) + + var user = req.body; + var passports = req.body.passports + + // Delete unwanted properties + delete user.passports + delete user.password_confirmation + + + sails.models.user + .update({id : req.param('id')},user) + .exec(function(err,updated){ + if(err) return res.negotiate(err); + + var user = updated[0]; + + if(user.node) { + sails.models.kongnode + .findOne({id : user.node}) + .exec(function(err,node){ + if(err) return res.negotiate(err) + user.node = node; + sails.sockets.blast('user.' + user.id + '.updated', user); + }) + }else{ + sails.sockets.blast('user.' + user.id + '.updated', user); + } + + + if(!passports) return res.json(updated) + + sails.models.passport + .update({user:req.param('id')},{password:passports.password}) + .exec(function(err,updatedPassport){ + if(err) return res.negotiate(err); + return res.json(updated) + }) + + + + }) + } }); diff --git a/api/events/api-health-checks.js b/api/events/api-health-checks.js index 7515efae9..b597346da 100644 --- a/api/events/api-health-checks.js +++ b/api/events/api-health-checks.js @@ -155,7 +155,7 @@ module.exports = { sails.log("helath_checks:createTransporter:settings =>",settings) if(!settings.length || !settings[0].data - || !settings[0].data.email_notifications + //|| !settings[0].data.email_notifications || !settings[0].data.notify_when.api_down.active) return cb() sails.log("health_checks:createTransporter => trying to get transport",{ "notifications_enabled" : settings[0].data.email_notifications, @@ -170,7 +170,7 @@ module.exports = { if(!transport) return cb() var result = { - transport : settings[0].data.default_transport, + settings : settings[0].data, } switch(settings[0].data.default_transport) { @@ -200,6 +200,7 @@ module.exports = { sails.log("health_check:failed to create transporter. No notification will be sent.",err) }else{ var transporter = result.transporter + var settings = result.settings var html = self.makeNotificationHTML(hc) self.getAdminEmailsList(function(err,receivers){ @@ -207,13 +208,13 @@ module.exports = { if(!err && receivers.length) { var mailOptions = { - from: '"Konga" ', // sender address + from: '"' + settings.email_default_sender_name + '" <' + settings.email_default_sender + '>', // sender address to: receivers.join(","), // list of receivers subject: 'An API is down or unresponsive', // Subject line html: html }; - if(result.transport == 'sendmail') { + if(settings.default_transport == 'sendmail') { sendmail(mailOptions, function(err, reply) { if(err){ sails.log.error("Health_checks:notify:error",err) diff --git a/api/events/node-health-checks.js b/api/events/node-health-checks.js index ac4a78dc1..9db9cd2de 100644 --- a/api/events/node-health-checks.js +++ b/api/events/node-health-checks.js @@ -132,7 +132,7 @@ module.exports = { sails.log("helath_checks:createTransporter:settings =>",settings) if(!settings.length || !settings[0].data - || !settings[0].data.email_notifications + //|| !settings[0].data.email_notifications || !settings[0].data.notify_when.node_down.active) return cb() sails.log("health_checks:createTransporter => trying to get transport",{ "notifications_enabled" : settings[0].data.email_notifications, @@ -147,7 +147,7 @@ module.exports = { if(!transport) return cb() var result = { - transport : settings[0].data.default_transport, + settings : settings[0].data, } switch(settings[0].data.default_transport) { @@ -176,19 +176,20 @@ module.exports = { }else{ var transporter = result.transporter var html = self.makeNotificationHTML(node) + var settings = result.settings self.getAdminEmailsList(function(err,receivers){ sails.log("health_checks:notify:receivers => ", receivers) if(!err && receivers.length) { var mailOptions = { - from: '"Konga" ', // sender address + from: '"' + settings.email_default_sender_name + '" <' + settings.email_default_sender + '>', // sender address to: receivers.join(","), // list of receivers subject: 'A node is down or unresponsive', // Subject line html: html }; - if(result.transport == 'sendmail') { + if(settings.default_transport == 'sendmail') { sendmail(mailOptions, function(err, reply) { if(err){ sails.log.error("Health_checks:notify:error",err) diff --git a/api/events/user-events.js b/api/events/user-events.js new file mode 100644 index 000000000..2f36a0a96 --- /dev/null +++ b/api/events/user-events.js @@ -0,0 +1,105 @@ +'use strict'; + +var events = require('events'); +var eventEmitter = new events.EventEmitter(); +var nodemailer = require('nodemailer'); +var mg = require('nodemailer-mailgun-transport'); +var sendmail = require('sendmail')({ + logger: { + debug: console.log, + info: console.info, + warn: console.warn, + error: console.error + }, + silent: false +}); + + +module.exports = { + emit : function(event,data) { + eventEmitter.emit(event,data) + }, + + addListener : function(event,fn) { + eventEmitter.addListener(event, fn); + }, + + createTransporter : function(cb) { + + // Get active transport + sails.models.settings.find().limit(1) + .exec(function(err,settings){ + if(err) return cb(err) + sails.log("helath_checks:createTransporter:settings =>",settings) + if(!settings.length || !settings[0].data ) return cb() + + sails.models.emailtransport.findOne({ + name : settings[0].data.default_transport + }).exec(function(err,transport){ + if(err) return cb(err) + + sails.log("user-events:createTransporter:transport =>",transport) + if(!transport) return cb() + + var result = { + settings : settings[0].data, + } + + switch(settings[0].data.default_transport) { + case "smtp": + result.transporter = nodemailer.createTransport(transport.settings) + break; + case "mailgun": + result.transporter = nodemailer.createTransport(mg(transport.settings)) + break; + } + + return cb(null,result); + + }) + }) + + }, + + notify : function(user) { + + var self = this + + self.createTransporter(function(err,result){ + if(err || !result) { + sails.log("user-events:failed to create transporter. No notification will be sent.",err) + }else{ + var transporter = result.transporter + var settings = result.settings + + if(!err) { + + var link = settings.baseUrl + '/auth/activate/' + user.activationToken || 'http://' + require("ip").address() + ':' + sails.config.port + '/auth/activate/' + user.activationToken; + + var mailOptions = { + from: '"' + settings.email_default_sender_name + '" <' + settings.email_default_sender + '>', // sender address + to: user.email, // list of receivers + subject: 'Welcome to Konga', // Subject line + html: '

Welcome to Konga!

' + + '

In order to activate your account, follow the link:

' + + '

' + link + '

' + }; + + if(settings.default_transport == 'sendmail') { + sendmail(mailOptions, function(err, reply) { + if(err){ + sails.log.error("user-events:notify:error",err) + } + }); + }else{ + transporter.sendMail(mailOptions, function(error, info){ + if(error){ + sails.log.error("user-events:notify:error",error) + } + }); + } + } + } + }) + } +} diff --git a/api/hooks/load-db.js b/api/hooks/load-db.js index 518cfceef..b5710fa08 100644 --- a/api/hooks/load-db.js +++ b/api/hooks/load-db.js @@ -1,6 +1,7 @@ 'use strict'; var async = require('async'); +var _ = require('lodash') /** * load-db.js @@ -45,12 +46,43 @@ module.exports = function hook(sails) { }) } + + async.series([ sails.models.user.seed, seedPassports, sails.models.kongnode.seed, sails.models.emailtransport.seed, - sails.models.settings.seed + function seedOrMergeSettings(cb) { + var seeds = sails.models.settings.seedData[0] + sails.models.settings.find().limit(1) + .exec(function(err,data){ + if(err) return cb(err) + var _data = _.merge(seeds,data[0] || {}) + sails.models.settings.updateOrCreate({ + id : _data.id + },_data,function(err,coa){ + if(err) return cb(err) + return cb() + }) + }) + }, + function activateExistingUsers(cb) { + + // Assign the same activation token to existing users + // for simplicity. + // After all, it's not like they're + // going to use it again. + var uuid = require('node-uuid'); + + sails.models.user + .update({ + activationToken : undefined + + },{active : true,activationToken : uuid.v4()}) + .exec(cb) + } + //sails.models.settings.seed ],next); }else{ sails.models.user diff --git a/api/hooks/node-health-checks.js b/api/hooks/node-health-checks.js index e9f51e39a..1173abb4a 100644 --- a/api/hooks/node-health-checks.js +++ b/api/hooks/node-health-checks.js @@ -33,14 +33,14 @@ module.exports = function hook(sails) { }) HealthCheckEvents.addListener('health_checks.start', function(node){ - sails.log("Hook:health_checks:on:health_checks.start",node) + //sails.log("Hook:health_checks:on:health_checks.start",node) HealthCheckEvents.start(node) }); HealthCheckEvents.addListener('health_checks.stop', function(node){ - sails.log("Hook:health_checks:on:health_checks.stop",node) + //sails.log("Hook:health_checks:on:health_checks.stop",node) HealthCheckEvents.stop(node) }); diff --git a/api/hooks/user-events-hook.js b/api/hooks/user-events-hook.js new file mode 100644 index 000000000..51beeba5e --- /dev/null +++ b/api/hooks/user-events-hook.js @@ -0,0 +1,54 @@ + + +var userEvents = require("../events/user-events") + + + +/** + * load-db.js + * + * This file contains a custom hook, that will be run after sails.js orm hook is loaded. Purpose of this hook is to + * check that database contains necessary initial data for application. + */ +module.exports = function hook(sails) { + return { + /** + * Private hook method to subscribe to health check events + * + * @param {Function} next Callback function to call after all is done + */ + process: function process(next) { + + sails.log("Hook:user_events_hook:process() called") + + + userEvents.addListener('user.signUp', function(data){ + sails.log("Hook:user_events_hook:on:user.signUp",data) + + var user = data.user; + var sendActivationEmail = data.sendActivationEmail; + if(sendActivationEmail) { + userEvents.notify(user) + } + + }); + + next() + + }, + + /** + * Method that runs automatically when the hook initializes itself. + * + * @param {Function} next Callback function to call after all is done + */ + initialize: function initialize(next) { + var self = this; + + // Wait for sails orm hook to be loaded + sails.after('hook:orm:loaded', function onAfter() { + self.process(next); + }); + } + }; +}; diff --git a/api/models/KongNode.js b/api/models/KongNode.js index ab2d7bce2..79440ae81 100644 --- a/api/models/KongNode.js +++ b/api/models/KongNode.js @@ -27,6 +27,10 @@ var defaultModel = _.merge(_.cloneDeep(require('../base/Model')), { type: 'string', required : true }, + kong_api_key: { + type: 'string', + defaultsTo : '' + }, kong_version: { type: 'string', required : true, @@ -45,6 +49,20 @@ var defaultModel = _.merge(_.cloneDeep(require('../base/Model')), { defaultsTo : false } }, + + afterDestroy: function (values, cb) { + + sails.log("KongNode:afterDestroy:called => ",values); + + // Stop health checks + values.forEach(function(node){ + HealthCheckEvents.emit('health_checks.stop',node); + }) + + cb(); + + }, + afterUpdate: function (values, cb) { sails.log("KongNode:afterUpdate:called()") diff --git a/api/models/Passport.js b/api/models/Passport.js index e5863426c..dcaa89d7d 100644 --- a/api/models/Passport.js +++ b/api/models/Passport.js @@ -105,6 +105,13 @@ var defaultModel = { } }, + //model validation messages definitions + validationMessages: { //hand for i18n & l10n + password: { + minLength: 'The password must be at least 6 character long' + } + }, + /** * Callback to be run before creating a Passport. * @@ -130,7 +137,11 @@ var defaultModel = { * @param {Function} next */ beforeUpdate: function beforeUpdate(passport, next) { + + console.log("########################################",passport) + if (passport.hasOwnProperty('password')) { + bcrypt.hash(passport.password, 10, function callback(error, hash) { passport.password = hash; diff --git a/api/models/Settings.js b/api/models/Settings.js index eb85530f0..f5ceb520c 100644 --- a/api/models/Settings.js +++ b/api/models/Settings.js @@ -9,47 +9,97 @@ var _ = require('lodash'); * @docs :: http://sailsjs.org/#!documentation/models */ var defaultModel = _.merge(_.cloneDeep(require('../base/Model')), { - tableName : "konga_settings", - autoPK : false, - attributes: { - id : { - type: 'integer', - primaryKey: true, - unique: true, - autoIncrement : true + tableName: "konga_settings", + autoPK: false, + attributes: { + id: { + type: 'integer', + primaryKey: true, + unique: true, + autoIncrement: true + }, + data: { + type: 'json' + }, }, - data: { - type: 'json' - }, - }, - seedData : [ - { - "data" : { - email_notifications : false, - default_transport : 'sendmail', - notify_when : { - node_down : { - title: "A node is down or unresponsive", - description : "Health checks must be enabled for the nodes that need to be monitored.", - active : false - }, - api_down : { - title : "An API is down or unresponsive", - description : "Health checks must be enabled for the APIs that need to be monitored.", - active : false - } - } - } - }, - ] + seedData: [ + { + "data": { + signup_enable: true, + signup_require_activation: false, + info_polling_interval: 5000, + email_default_sender_name: 'KONGA', + email_default_sender: 'konga@konga.test', + email_notifications: false, + default_transport: 'sendmail', + notify_when: { + node_down: { + title: "A node is down or unresponsive", + description: "Health checks must be enabled for the nodes that need to be monitored.", + active: false + }, + api_down: { + title: "An API is down or unresponsive", + description: "Health checks must be enabled for the APIs that need to be monitored.", + active: false + } + }, + + user_permissions: { + apis: { + create: false, + read: true, + update: false, + delete: false + }, + consumers: { + create: false, + read: true, + update: false, + delete: false + }, + plugins: { + create: false, + read: true, + update: false, + delete: false + }, + upstreams: { + create: false, + read: true, + update: false, + delete: false + }, + certificates: { + create: false, + read: true, + update: false, + delete: false + }, + connections: { + create: false, + read: true, + update: false, + delete: false + }, + users: { + create: false, + read: true, + update: false, + delete: false + } + } + } + }, + ] }); -var mongoModel = function() { - var obj = _.cloneDeep(defaultModel) - delete obj.autoPK - delete obj.attributes.id - return obj; +var mongoModel = function () { + var obj = _.cloneDeep(defaultModel) + delete obj.autoPK + delete obj.attributes.id + return obj; } module.exports = sails.config.models.connection == 'mongo' ? mongoModel() : defaultModel diff --git a/api/models/Snapshot.js b/api/models/Snapshot.js index 14d2c3f77..234150b77 100644 --- a/api/models/Snapshot.js +++ b/api/models/Snapshot.js @@ -9,42 +9,50 @@ var _ = require('lodash'); * @docs :: http://sailsjs.org/#!documentation/models */ var defaultModel = _.merge(_.cloneDeep(require('../base/Model')), { - tableName : "konga_kong_snapshots", - autoPK : false, - attributes: { - id : { - type: 'integer', - primaryKey: true, - unique: true, - autoIncrement : true + tableName: "konga_kong_snapshots", + autoPK: false, + attributes: { + id: { + type: 'integer', + primaryKey: true, + unique: true, + autoIncrement: true + }, + name: { + type: 'string', + required: true, + unique: true + }, + kong_node_name: { + type: 'string' + }, + kong_node_url: { + type: 'string' + }, + kong_version: { + type: 'string' + }, + data: { + type: 'json', + required: true + } }, - name: { - type: 'string', - required : true, - unique: true - }, - kong_node_name: { - type : 'string' - }, - kong_node_url: { - type : 'string' - }, - kong_version: { - type : 'string' - }, - data : { - type : 'json', - required : true + afterCreate: function (values, cb) { + sails.log("Snapshot created!!!!!!!!!!!!!!!!!!!!!!!!!") + sails.sockets.blast('events.snapshots', { + verb : 'created', + data : values + }); + cb() } - } }); -var mongoModel = function() { - var obj = _.cloneDeep(defaultModel) - delete obj.autoPK - delete obj.attributes.id - return obj; +var mongoModel = function () { + var obj = _.cloneDeep(defaultModel) + delete obj.autoPK + delete obj.attributes.id + return obj; } module.exports = sails.config.models.connection == 'mongo' ? mongoModel() : defaultModel diff --git a/api/models/User.js b/api/models/User.js index 9a161f0ee..c33017e5f 100644 --- a/api/models/User.js +++ b/api/models/User.js @@ -1,6 +1,8 @@ 'use strict'; var _ = require('lodash'); +var async = require('async'); +var uuid = require('node-uuid'); /** * User.js @@ -11,77 +13,120 @@ var _ = require('lodash'); var defaultModel = _.merge(_.cloneDeep(require('../base/Model')), { - tableName : "konga_users", - autoPK: false, - attributes: { - id : { - type: 'integer', - primaryKey: true, - unique: true, - autoIncrement : true - }, - username: { - type: 'string', - unique: true - }, - email: { - type: 'email', - unique: true - }, - firstName: { - type: 'string', - required: true - }, - lastName: { - type: 'string', - required: true - }, - admin: { - type: 'boolean', - defaultsTo: false - }, + tableName: "konga_users", + autoPK: false, + attributes: { + id: { + type: 'integer', + primaryKey: true, + unique: true, + autoIncrement: true + }, + username: { + type: 'string', + unique: true, + required: true + }, + email: { + type: 'email', + unique: true, + required: true + }, + firstName: { + type: 'string' + }, + lastName: { + type: 'string' + }, + admin: { + type: 'boolean', + defaultsTo: false + }, - node_id : { - type : 'string', - defaultsTo: '' - }, + node_id: { + type: 'string', + defaultsTo: '' + }, + + active: { + type: 'boolean', + defaultsTo: false + }, + + activationToken : { + type : 'string' + }, - node : { - model : 'kongnode' + node: { + model: 'kongnode' + }, + + // Passport configurations + passports: { + collection: 'Passport', + via: 'user' + }, }, - // Passport configurations - passports: { - collection: 'Passport', - via: 'user' + afterDestroy: function (values, cb) { + + sails.log("User:afterDestroy:called => ",values); + + + var fns = []; + + values.forEach(function(user){ + fns.push(function(callback){ + // Delete passports + sails.models.passport.destroy({user : user.id}) + .exec(callback) + }) + }) + + async.series(fns,cb); + }, - }, - - seedData:[ - { - "username": "admin", - "email": "admin@some.domain", - "firstName": "Arnold", - "lastName": "Administrator", - "node_id" : "http://kong:8001", - "admin": true + + //model validation messages definitions + validationMessages: { + email: { + required: 'Email is required', + email: 'The email address is not valid', + unique: 'Email address is already taken' + }, + username: { + required: 'Username is required', + unique: 'Username is already taken' + } }, - { - "username": "demo", - "email": "demo@some.domain", - "firstName": "John", - "lastName": "Doe", - "node_id" : "http://kong:8001", - "admin": false - } - ] + + seedData: [ + { + "username": "admin", + "email": "admin@some.domain", + "firstName": "Arnold", + "lastName": "Administrator", + "node_id": "http://kong:8001", + "admin": true, + "active" : true + }, + { + "username": "demo", + "email": "demo@some.domain", + "firstName": "John", + "lastName": "Doe", + "node_id": "http://kong:8001", + "admin": false, + "active" : true + } + ] }); -var mongoModel = function() { - var obj = _.cloneDeep(defaultModel) - delete obj.autoPK - delete obj.attributes.id - return obj; +var mongoModel = function () { + var obj = _.cloneDeep(defaultModel) + delete obj.autoPK + delete obj.attributes.id + return obj; } module.exports = sails.config.models.connection == 'mongo' ? mongoModel() : defaultModel diff --git a/api/policies/activeNodeData.js b/api/policies/activeNodeData.js index c69b3c76b..46a268580 100644 --- a/api/policies/activeNodeData.js +++ b/api/policies/activeNodeData.js @@ -14,14 +14,14 @@ module.exports = function activeNodeData(request, response, next) { sails.log.verbose(__filename + ':' + __line + ' [Policy.activeNodeData() called]'); - sails.config.kong_admin_url = request.headers['kong-admin-url'] || sails.config.kong_admin_url + // sails.config.kong_admin_url = request.headers['kong-admin-url'] || sails.config.kong_admin_url var c = actionUtil.parseCriteria(request) if(c.hasOwnProperty('or')){ - c['and'] = [{node_id : sails.config.kong_admin_url}] + c['and'] = [{node_id : request.node_id}] }else{ - c['node_id'] = sails.config.kong_admin_url + c['node_id'] = request.node_id } request.query.where = JSON.stringify(c) diff --git a/api/policies/addDataUpdate.js b/api/policies/addDataUpdate.js index b569b202d..759eea75e 100644 --- a/api/policies/addDataUpdate.js +++ b/api/policies/addDataUpdate.js @@ -11,8 +11,6 @@ var _ = require('lodash'); */ module.exports = function addDataUpdate(request, response, next) { sails.log.verbose(__filename + ':' + __line + ' [Policy.addDataUpdate() called]'); - - console.log('[Policy.addDataUpdate()] -> body : ',request.body); if (request.token) { var itemsToRemove = [ 'id', diff --git a/api/policies/createUser.js b/api/policies/createUser.js index b2766b7a4..95e25f213 100644 --- a/api/policies/createUser.js +++ b/api/policies/createUser.js @@ -8,96 +8,87 @@ * @param {Function} next Callback function */ -function alphanum(value){ - if( /[^a-zA-Z0-9]/.test( value ) ) { - return false; - } - return true; +function alphanum(value) { + if (/[^a-zA-Z0-9]/.test(value)) { + return false; + } + return true; } module.exports = function createUser(request, response, next) { - sails.log.verbose(__filename + ':' + __line + ' [Policy.createUser() called]'); + sails.log.verbose(__filename + ':' + __line + ' [Policy.createUser() called]'); - if(!request.body.passports) return next() + if (!request.body.passports) return next() - var password = request.body.passports.password - var confirmation = request.body.password_confirmation + var password = request.body.passports.password + var confirmation = request.body.password_confirmation - console.log('[Policy.createUser()] -> password : ' + password); - console.log('[Policy.createUser()] -> confirmation : ' + confirmation); + console.log('[Policy.createUser()] -> password : ' + password); + console.log('[Policy.createUser()] -> confirmation : ' + confirmation); - if(!password) { - var error = new Error(); + if (!password) { + var error = new Error(); - error.raw = [ - { - err : { - invalidAttributes : { - "password" : [{ - message : 'The password field is required.' - }] - } + error.Errors = { + password: [ + { + message: 'The password field is required.' + } + ] } - } - ] - error.status = 400; + error.status = 400; - return next(error); - } + return next(error); + } - if(password.length < 6) { - var error = new Error(); + if (password.length < 7) { + var error = new Error(); - error.raw = [ - { - err : { - invalidAttributes : { - "password" : [{ - message : 'The password must be at least 6 characters long.' - }] - } + error.Errors = { + password: [ + { + message: 'The password must be at least 7 characters long.' + } + ] } - } - ] - error.status = 400; + error.status = 400; - return next(error); - } + return next(error); + } - if(!alphanum(password)) { - var error = new Error(); + if (!alphanum(password)) { + var error = new Error(); - error.raw = [ - { - err : { - invalidAttributes : { - "password" : [{ - message : 'Only alphanumeric characters are accepted.' - }] - } + error.Errors = { + password: [ + { + message: 'Only alphanumeric characters are allowed' + } + ] } - } - ] - error.status = 400; + error.status = 400; + + return next(error); + } + + if (password != confirmation) { + var error = new Error(); - return next(error); - } + error.Errors = { + password_confirmation: [ + { + message: 'Password and password confirmation don\'t match' + } + ] + } - if(password != confirmation) { - var error = new Error(); + error.status = 400; - error.invalidAttributes = { - "password_confirmation" : [{ - message : 'Password and password confirmation don\'t match.' - }] + return next(error); + } else { + next(); } - error.status = 400; - - return next(error); - }else{ - next(); - } }; diff --git a/api/policies/dynamicNode.js b/api/policies/dynamicNode.js index fcaa18785..cb6c77ede 100644 --- a/api/policies/dynamicNode.js +++ b/api/policies/dynamicNode.js @@ -13,30 +13,35 @@ module.exports = function dynamicNode(request, response, next) { sails.log.debug(__filename + ':' + __line + ' [Policy.dynamicNode() called]',request.headers['kong-admin-url']); - // Get the default node from user - sails.models.user.findOne({ - id:request.token - }).populate('node').exec(function(err,user) { - if(err) return next(err); - if(!user) return response.notFound({ - message : "user not found" - }) - - if(user.node) { - sails.config.kong_admin_url = user.node.kong_admin_url - request.node_id = sails.config.kong_admin_url - return next() - }else{ - if(!request.headers['kong-admin-url'] && ! request.query.kong_admin_url) return response.badRequest({ - message : "No connection is selected. Please activate a connection in settings" - }) - - sails.config.kong_admin_url = request.headers['kong-admin-url'] || request.query.kong_admin_url - request.node_id = sails.config.kong_admin_url - return next() - } - }) + // If kong-admin-url is set in headers or qs, use that, else get node from user + if(request.headers['kong-admin-url'] || request.query.kong_admin_url) { + // sails.config.kong_admin_url = request.headers['kong-admin-url'] || request.query.kong_admin_url + request.node_id = request.headers['kong-admin-url'] || request.query.kong_admin_url + request.kong_api_key = request.headers['kong_api_key'] || request.query.kong_api_key + return next() + }else{ + // Get the default node from user + sails.models.user.findOne({ + id:request.token + }).populate('node').exec(function(err,user) { + if(err) return next(err); + if(!user) return response.notFound({ + message : "user not found" + }) + + if(user.node) { + // sails.config.kong_admin_url = user.node.kong_admin_url + request.kong_api_key = user.node.kong_api_key + request.node_id = user.node.kong_admin_url + return next() + }else{ + return response.badRequest({ + message : "No connection is selected. Please activate a connection in settings" + }) + } + }) + } }; diff --git a/api/policies/signup.js b/api/policies/signup.js new file mode 100644 index 000000000..812a764cf --- /dev/null +++ b/api/policies/signup.js @@ -0,0 +1,13 @@ +'use strict'; + + +module.exports = function signup(request, response, next) { + sails.log.verbose(__filename + ':' + __line + ' [Policy.signup() called]'); + + sails.models.settings.find().limit(1) + .exec(function(err,data){ + if(err) return next(err) + if(!data || !data[0] || !data[0].data || !data[0].data.signup_enable) return response.forbidden("forbidden") + return next() + }) +}; diff --git a/api/policies/updateUser.js b/api/policies/updateUser.js index bca01a43e..d0b89b9e2 100644 --- a/api/policies/updateUser.js +++ b/api/policies/updateUser.js @@ -7,34 +7,32 @@ * @param {Response} response Response object * @param {Function} next Callback function */ -module.exports = function addDataCreate(request, response, next) { - sails.log.verbose(__filename + ':' + __line + ' [Policy.updateUser() called]'); - console.log('[Policy.updateUser()] -> body : ',request.body); +module.exports = function updateUser(request, response, next) { + sails.log.verbose(__filename + ':' + __line + ' [Policy.updateUser() called]'); - var password = request.body.passports ? request.body.passports.password : null - var confirmation = request.body.password_confirmation + var password = request.body.passports ? request.body.passports.password : null + var confirmation = request.body.password_confirmation - console.log('[Policy.updateUser()] -> password : ' + password); - console.log('[Policy.updateUser()] -> confirmation : ' + confirmation); + if (password && password != "") { + if (password != confirmation) { + var error = new Error(); - if(password && password != "") { - if(password != confirmation) { - var error = new Error(); + error.Errors = { + password_confirmation: [ + { + message: 'Password and password confirmation don\'t match' + } + ] + } + error.status = 400; - error.invalidAttributes = { - "password_confirmation" : [{ - message : 'Password and password confirmation don\'t match.' - }] - } - error.status = 400; - - next(error); - }else{ - next(); + next(error); + } else { + next(); + } + } else { + if (!password) delete request.body.passports; // We don't need passports for update if password is not set + next(); } - }else{ - if(!password) delete request.body.passports; // We don't need passports for update if password is not set - next(); - } }; diff --git a/api/services/ConsumerCredentialsService.js b/api/services/ConsumerCredentialsService.js deleted file mode 100644 index 5e1504619..000000000 --- a/api/services/ConsumerCredentialsService.js +++ /dev/null @@ -1,54 +0,0 @@ -'use strict'; - -var _ = require("lodash") -var unirest = require('unirest') -var async = require('async') - - -var ConsumerCredentialsService = { - - listCredentials : function(consumer_id,cb) { - - var credentials = ['jwt','key-auth','basic-auth','hmac-auth','oauth2'] - var promises = [] - - credentials.forEach(function(credential){ - promises.push(function(cb) { - unirest.get(sails.config.kong_admin_url + '/consumers/' + consumer_id + "/" + credential) - .end(function(response){ - if(response.error) return cb({ - status : response.error.status, - message : response.body.message - }) - return cb(null,{ - name : credential, - data : response.body.data, - total : response.body.total - }) - }) - }) - }) - - async.series(promises, function(err,result) { - if (err) return cb(err) - - var obj = { - credentials : [] - } - var sum_total = 0 - result.forEach(function(result){ - sum_total = sum_total + result.total - if(result.total > 0) obj.credentials.push(result) - }) - - - obj.total = sum_total - - return cb(null,obj) - }); - - }, - -} - -module.exports = ConsumerCredentialsService diff --git a/api/services/ConsumerService.js b/api/services/ConsumerService.js deleted file mode 100644 index 731d50a2b..000000000 --- a/api/services/ConsumerService.js +++ /dev/null @@ -1,79 +0,0 @@ -'use strict'; - -var _ = require("lodash") -var unirest = require('unirest') -var async = require('async') - - -var ConsumerService = { - - sync : function(cb) { - var handleConsumers = function (kongConsumers) { - sails.models.consumer.find({}) - .exec(function (err, consumers) { - if (err) return cb(err) - - sails.log.debug('ConsumerService:sync => Konga consumers',consumers); - - // Find the consumers that only exist in Kong's db - var onlyInKong = kongConsumers.filter(function (current) { - return consumers.filter(function (current_b) { - return current_b.username == current.username - }).length == 0 - }); - - // Find the consumers that only exists in Konga's db - var onlyInKonga = consumers.filter(function (current) { - return kongConsumers.filter(function (current_a) { - return current_a.username == current.username - }).length == 0 - }); - - async.series([ - function (callback) { - sails.models.consumer.create(onlyInKong) - .exec(function (err, docs) { - if (err) return callback(err) - return callback() - }) - }, - function (callback) { - sails.log.info('Deleting absent consumers from Konga'); - var ids = onlyInKonga.map(function (item) { - return item.id - }) - - sails.models.consumer.destroy({ - id: ids - }).exec(function (err) { - if (err) return callback(err) - return callback() - }); - } - ], function (err) { - if (err) return cb(err) - return cb(null,true); - }); - - }) - - }; - var getConsumers = function (prevConsumers,url) { - unirest.get(url) - .end(function(response) { - if (response.error) return cb(response); - var kongConsumers = prevConsumers.concat(response.body.data); - if (response.body.next) { - getConsumers(kongConsumers,response.body.next); - } - else { - handleConsumers(kongConsumers); - } - - }); - }; - getConsumers([],sails.config.kong_admin_url + '/consumers'); - } -} - -module.exports = ConsumerService diff --git a/api/services/KongPluginService.js b/api/services/KongPluginService.js index 7b29d1858..42887d19e 100644 --- a/api/services/KongPluginService.js +++ b/api/services/KongPluginService.js @@ -30,7 +30,7 @@ var KongPluginService = _.merge(_.cloneDeep(require('./KongService')), { }, addDynamicSSLPlugin : function(fds,req, res) { - return unirest.post(sails.config.kong_admin_url + req.url.replace('/kong','')) + return unirest.post(req.node_id + req.url.replace('/kong','')) .headers({'Content-Type': 'multipart/form-data'}) .field('name', req.body.name) .field('config.only_https', req.body['config.only_https'] || false) @@ -44,21 +44,26 @@ var KongPluginService = _.merge(_.cloneDeep(require('./KongService')), { }, addCertificates : function(fds,req, res) { - return unirest.post(sails.config.kong_admin_url + req.url.replace('/kong','')) - .headers({'Content-Type': 'multipart/form-data'}) - .field('snis', req.body['snis'] || '') - .attach('cert', fds[0]) - .attach('key', fds[1]) - .end(function (response) { + var request = unirest.post(req.node_id + req.url.replace('/kong','')) + + if(req.kong_api_key) { + request.headers({'apikey': req.kong_api_key}) + } + request.field('snis', req.body['snis'] || '') + request.attach('cert', fds[0]) + request.attach('key', fds[1]) + return request.end(function (response) { if (response.error) return res.kongError(response) return res.json(response.body) }); }, updateCertificates : function(fds,req, res) { + var request = unirest.patch(req.node_id + req.url.replace('/kong','')) + if(req.kong_api_key) { + request.headers({'apikey': req.kong_api_key}) + } - var request = unirest.patch(sails.config.kong_admin_url + req.url.replace('/kong','')) - request.headers({'Content-Type': 'multipart/form-data'}) request.field('snis', req.body['snis'] || '') if(fds[0]) request.attach('cert', fds[0]) if(fds[1]) request.attach('key', fds[1]) @@ -71,7 +76,7 @@ var KongPluginService = _.merge(_.cloneDeep(require('./KongService')), { }, addPlugin : function(req,res) { - return unirest.post(sails.config.kong_admin_url + req.url.replace('/kong','')) + return unirest.post(req.node_id + req.url.replace('/kong','')) .send(req.body) .end(function (response) { if (response.error) return res.kongError(response) @@ -81,7 +86,7 @@ var KongPluginService = _.merge(_.cloneDeep(require('./KongService')), { createCb: function (req, res, cb) { - unirest.post(sails.config.kong_admin_url + req.url.replace('/kong','')) + unirest.post(req.node_id + req.url.replace('/kong','')) .send(req.body) .end(function (response) { if (response.error) return cb(response) @@ -116,7 +121,7 @@ var KongPluginService = _.merge(_.cloneDeep(require('./KongService')), { }, retrieve: function (req, res) { - unirest.get(sails.config.kong_admin_url + req.url.replace('/kong','')) + unirest.get(req.node_id + req.url.replace('/kong','')) .end(function (response) { if (response.error) return res.kongError(response) return res.json(response.body) @@ -124,15 +129,31 @@ var KongPluginService = _.merge(_.cloneDeep(require('./KongService')), { }, list: function (req, res) { - unirest.get(sails.config.kong_admin_url + req.url.replace('/kong','')) + unirest.get(req.node_id + req.url.replace('/kong','')) .end(function (response) { if (response.error) return res.kongError(response) return res.json(response.body) }) }, + + richList : function(req,res) { + var _this = this; + unirest.get(req.node_id + '/plugins/enabled') + .end(function (response) { + if (response.error) return res.kongError(response) + + console.log("*******************************",response.body) + var enabledPlugins = response.body.enabled_plugins; + + return res.json(_this.makeGroups(enabledPlugins)) + + }) + + }, + update: function (req, res) { - unirest.patch(sails.config.kong_admin_url + req.url.replace('/kong','')) + unirest.patch(req.node_id + req.url.replace('/kong','')) .send(req.body) .end(function (response) { if (response.error) return res.kongError(response) @@ -141,7 +162,7 @@ var KongPluginService = _.merge(_.cloneDeep(require('./KongService')), { }, updateCb: function (req, res,cb) { - unirest.patch(sails.config.kong_admin_url + req.url.replace('/kong','')) + unirest.patch(req.node_id + req.url.replace('/kong','')) .send(req.body) .end(function (response) { if (response.error) return cb(response) @@ -150,7 +171,7 @@ var KongPluginService = _.merge(_.cloneDeep(require('./KongService')), { }, updateOrCreate: function (req, res) { - unirest.put(sails.config.kong_admin_url + req.url.replace('/kong','')) + unirest.put(req.node_id + req.url.replace('/kong','')) .send(req.body) .end(function (response) { if (response.error) return res.kongError(response) @@ -159,7 +180,7 @@ var KongPluginService = _.merge(_.cloneDeep(require('./KongService')), { }, delete: function (req, res) { - unirest.delete(sails.config.kong_admin_url + req.url.replace('/kong','')) + unirest.delete(req.node_id + req.url.replace('/kong','')) .end(function (response) { if (response.error) return res.kongError(response) return res.json(response.body) @@ -167,11 +188,868 @@ var KongPluginService = _.merge(_.cloneDeep(require('./KongService')), { }, deleteCb: function (req, res,cb) { - unirest.delete(sails.config.kong_admin_url + req.url.replace('/kong','')) + unirest.delete(req.node_id + req.url.replace('/kong','')) .end(function (response) { if (response.error) return cb(response) return cb(null,response.body) }) + }, + + makeGroups : function(_enabledPlugins) { + + var _groups = this.groups(); + var enabledPlugins = _.clone(_enabledPlugins); + var _this = this; + + _groups.forEach(function(group) { + Object.keys(group.plugins).forEach(function (key) { + var index = enabledPlugins.indexOf(key); + if(index > -1) { + group.plugins[key].enabled = true; // Mark plugin as enabled so it will be shown in the list + enabledPlugins.splice(index, 1); // Remove found plugin from array + + } + + // Add extra data to plugins + if(_this.pluginsData()[key]) { + group.plugins[key].customData = _this.pluginsData()[key] + } + }) + + }) + + // If there are any enabledPlugins left, + // add them to the custom plugins group + if(enabledPlugins.length) { + enabledPlugins.forEach(function(plugin) { + _groups[_groups.length - 1].plugins[plugin] = {} + }) + } + + return _groups; + + }, + + groups : function() { + return [ + { + name: "Authentication", + description: "Protect your services with an authentication layer", + icon: "mdi-account-outline", + plugins: { + "basic-auth": { + description: "Add Basic Authentication to your APIs" + }, + "key-auth": { + description: "Add a key authentication to your APIs" + }, + "oauth2": { + description: "Add an OAuth 2.0 authentication to your APIs" + }, + "hmac-auth": { + description: "Add HMAC Authentication to your APIs" + }, + "jwt": { + description: "Verify and authenticate JSON Web Tokens" + }, + "ldap-auth": { + description: "Integrate Kong with a LDAP server" + }, + } + }, + { + name: "Security", + icon: "mdi-security", + description: "Protect your services with additional security layers", + plugins: { + "acl": { + description: "Control which consumers can access APIs" + }, + "cors": { + description: "Allow developers to make requests from the browser" + }, + "ssl": { + description: "Add an SSL certificate for an underlying service" + }, + "ip-restriction": { + description: "Whitelist or blacklist IPs that can make requests" + }, + "bot-detection": { + description: "Detects and blocks bots or custom clients" + } + } + }, + { + name: "Traffic Control", + icon: "mdi-traffic-light", + description: "Manage, throttle and restrict inbound and outbound API traffic", + plugins: { + "rate-limiting": { + description: "Rate-limit how many HTTP requests a developer can make" + }, + "response-ratelimiting": { + description: "Rate-Limiting based on a custom response header value" + }, + "request-size-limiting": { + description: "Block requests with bodies greater than a specific size" + }, + "request-termination": { + description: "This plugin terminates incoming requests with a specified status code and message. This allows to (temporarily) block an API or Consumer." + }, + } + }, + { + name: "Serverless", + description: "Invoke serverless functions in combination with other plugins:", + icon: "mdi-cloud-sync", + plugins: { + "aws-lambda": { + description: "Invoke an AWS Lambda function from Kong. It can be used in combination with other request plugins to secure, manage or extend the function." + } + } + }, + { + name: "Analytics & Monitoring", + icon: "mdi-chart-bar", + description: "Visualize, inspect and monitor APIs and microservices traffic", + plugins: { + "galileo": { + description: "Business Intelligence Platform for APIs" + }, + "datadog": { + description: "Visualize API metrics on Datadog" + }, + "runscope": { + description: "API Performance Testing and Monitoring" + }, + + } + }, + { + name: "Transformations", + icon: "mdi-nfc-tap", + description: "Transform request and responses on the fly on Kong", + plugins: { + "request-transformer": { + description: "Modify the request before hitting the upstream server" + }, + "response-transformer": { + description: "Modify the upstream response before returning it to the client" + }, + "correlation-id": { + description: "Correlate requests and responses using a unique ID" + }, + } + }, + { + name: "Logging", + icon: "mdi-content-paste", + description: "Log requests and response data using the best transport for your infrastructure", + plugins: { + "tcp-log": { + description: "Send request and response logs to a TCP server" + }, + "udp-log": { + description: "Send request and response logs to an UDP server" + }, + "http-log": { + description: "Send request and response logs to an HTTP server" + }, + "file-log": { + description: "Append request and response data to a log file on disk" + }, + "syslog": { + description: "Send request and response logs to Syslog" + }, + "statsd": { + description: "Send request and response logs to StatsD" + }, + "loggly": { + description: "Send request and response logs to Loggly" + }, + + } + }, + { + name: "Custom", + description: "Custom Plugins", + icon: "mdi-account-box-outline", + plugins: {} + } + ] + }, + + + pluginsData : function() { + return { + "ssl": { + "meta": { + description: 'Dynamically binds a specific SSL certificate to the request_host value of a service. ' + + 'In case you want to setup a global SSL certificate for every API, take a look at the Kong SSL configuration options.' + + '
If no .cert and .key files are provided, Konga will create self-signed certificates and send them to Kong ( openssl library must be available on your machine ).' + }, + "cert": { + type: "file", + help: "Upload the certificate.crt file." + }, + "key": { + type: "file", + help: "Upload the certificate.key file." + }, + "only_https": { + type: "boolean", + value: false, + help: "Specify if the service should only be available through an https protocol." + }, + "accept_http_if_already_terminated": { + type: "boolean", + value: false, + help: "If only_https is true, accepts HTTPs requests that have already been terminated by a proxy or load balancer and the x-forwarded-proto: https header has been added to the request. Only enable this option if the Kong server cannot be publicly accessed and the only entry-point is such proxy or load balancer." + }, + }, + "request-size-limiting": { + meta: { + description: 'Block incoming requests whose body is greater than a specific size in megabytes.' + }, + + 'consumer_id': { + displayName: "Apply to", + type: 'search', + value: null, + help: 'The CONSUMER ID that this plugin configuration will target. ' + + 'This value can only be used if authentication has been enabled ' + + 'so that the system can identify the user making the request.' + + ' If left blank, the plugin will be applied to all consumers.' + }, + 'allowed_payload_size': { + type: 'number', + value: 128, + help: 'Allowed request payload size in megabytes, default is 128 (128000000 Bytes)' + }, + }, + "request-termination": { + meta: { + description: 'This plugin terminates incoming requests with a specified status code and message. This allows to (temporarily) block an API or Consumer.' + }, + content_type : { + type : 'text', + value : 'application/json; charset=utf-8', + help : 'Content type of the raw response configured with config.body.' + }, + status_code : { + type : 'number', + value : 503, + help : 'The response code to send.' + }, + message : { + type : 'text', + help : 'The message to send, if using the default response generator.' + }, + body : { + type : 'text', + help : 'The raw response body to send, this is mutually exclusive with the config.message field.' + } + + }, + "bot-detection": { + 'meta': { + description: 'Protects your API from most common bots and has the capability of whitelisting and blacklisting custom clients.' + }, + 'whitelist': { + type: 'text', + value: null, + help: 'A comma separated array of regular expressions that should be whitelisted. The regular expressions will be checked against the User-Agent header.' + }, + 'blacklist': { + type: 'text', + value: null, + help: 'A comma separated array of regular expressions that should be blacklisted. The regular expressions will be checked against the User-Agent header.' + }, + }, + "ip-restriction": { + meta: { + description: 'Restrict access to an API by either whitelisting or blacklisting IP addresses. Single IPs, multiple IPs or ranges in CIDR notation like 10.10.10.0/24 can be used.' + }, + 'consumer_id': { + displayName: "Apply to", + type: 'text', + value: null, + help: 'The CONSUMER ID that this plugin configuration will target. ' + + 'This value can only be used if authentication has been enabled ' + + 'so that the system can identify the user making the request.' + + ' If left blank, the plugin will be applied to all consumers.' + }, + 'whitelist': { + type: 'text', + value: '', + help: 'Comma separated list of IPs or CIDR ranges to whitelist. At least one between whitelist or blacklist must be specified.' + }, + 'blacklist': { + type: 'text', + value: '', + help: 'Comma separated list of IPs or CIDR ranges to blacklist. At least one between whitelist or blacklist must be specified.' + }, + }, + "cors": { + meta: { + description: 'Easily add Cross-origin resource sharing (CORS) to your API by enabling this plugin.' + }, + 'origin': { + type: 'text', + value: '', + help: 'Value for the Access-Control-Allow-Origin header, expects a String.' + }, + 'methods': { + type: 'text', + value: 'GET,HEAD,PUT,PATCH,POST,DELETE', + help: 'Value for the Access-Control-Allow-Methods header, expects a comma delimited string (e.g. GET,POST).' + }, + 'headers': { + type: 'text', + value: '', + help: 'Value for the Access-Control-Allow-Headers header, expects a comma delimited string (e.g. Origin, Authorization).' + }, + 'exposed_headers': { + type: 'text', + value: '', + help: 'Value for the Access-Control-Expose-Headers header, expects a comma delimited string (e.g. Origin, Authorization). If not specified, no custom headers are exposed.' + }, + 'max_age': { + type: 'number', + help: 'Indicated how long the results of the preflight request can be cached, in seconds.' + }, + 'preflight_continue': { + type: "boolean", + value: false, + help: 'Flag to determine whether the Access-Control-Allow-Credentials header should be sent with true as the value.' + }, + 'credentials': { + type: "boolean", + value: false, + help: 'A boolean value that instructs the plugin to proxy the OPTIONS preflight request to the upstream API.' + } + }, + "acl": { + meta: { + description: 'Restrict access to an API by whitelisting or blacklisting consumers using arbitrary ACL group names. This plugin requires an authentication plugin to have been already enabled on the API.' + }, + 'whitelist': { + type: 'text', + value: '', + help: 'Comma separated list of arbitrary group names that are allowed to consume the API. At least one between whitelist or blacklist must be specified.' + }, + 'blacklist': { + type: 'text', + value: '', + help: 'Comma separated list of arbitrary group names that are allowed to consume the API. At least one between whitelist or blacklist must be specified.' + } + }, + "ldap-auth": { + meta: { + description: 'Add LDAP Bind Authentication to your APIs, with username and password protection. The plugin will check for valid credentials in the Proxy-Authorization and Authorization header (in this order).' + }, + 'hide_credentials': { + type: 'boolean', + value: false, + help: 'An optional boolean value telling the plugin to hide the credential to the upstream API server. It will be removed by Kong before proxying the request' + }, + 'ldap_host': { + type: 'text', + value: '', + help: 'Host on which the LDAP server is running.' + }, + 'ldap_port': { + type: 'number', + value: '', + help: 'TCP port where the LDAP server is listening.' + }, + 'start_tls': { + type: 'boolean', + value: false, + help: 'Set it to true to issue StartTLS (Transport Layer Security) extended operation over ldap connection.' + }, + 'base_dn': { + type: 'text', + value: '', + help: 'Base DN as the starting point for the search.' + }, + 'verify_ldap_host': { + type: 'boolean', + value: false, + help: 'Set it to true to authenticate LDAP server. The server certificate will be verified according to the CA certificates specified by the lua_ssl_trusted_certificate directive.' + }, + 'attribute': { + type: 'text', + value: '', + help: 'Attribute to be used to search the user.' + }, + 'cache_ttl': { + type: 'number', + value: 60, + help: 'Cache expiry time in seconds.' + }, + 'timeout': { + type: 'number', + value: 10000, + help: 'An optional timeout in milliseconds when waiting for connection with LDAP server.' + }, + 'keepalive': { + type: 'number', + value: 60000, + help: 'An optional value in milliseconds that defines for how long an idle connection to LDAP server will live before being closed.' + }, + }, + "hmac-auth": { + meta: { + description: 'Add HMAC Signature Authentication to your APIs to establish the identity of the consumer. The plugin will check for valid signature in the Proxy-Authorization and Authorization header (in this order). This plugin implementation follows the draft-cavage-http-signatures-00 draft with slightly changed signature scheme.' + }, + 'hide_credentials': { + type: 'boolean', + value: false, + help: 'An optional boolean value telling the plugin to hide the credential to the upstream API server. It will be removed by Kong before proxying the request' + }, + 'clock_skew': { + type: 'number', + value: 300, + help: 'Clock Skew in seconds to prevent replay attacks' + } + }, + "basic-auth": { + meta: { + description: 'Add Basic Authentication to your APIs, with username and password protection. The plugin will check for valid credentials in the Proxy-Authorization and Authorization header (in this order).', + }, + 'hide_credentials': { + type: 'boolean', + value: false, + help: 'An optional boolean value telling the plugin to hide the credential to the upstream API server. It will be removed by Kong before proxying the request' + } + }, + "key-auth": { + meta: { + description: 'Add Key Authentication (also referred to as an API key) to your APIs. Consumers then add their key either in a querystring parameter or a header to authenticate their requests.' + }, + 'key_names': { + type: 'text', + value: 'apikey', + help: 'Describes an array of comma separated parameter names where the plugin will look for a key. The client must send the authentication key in one of those key names, and the plugin will try to read the credential from a header or the querystring parameter with the same name.' + }, + 'hide_credentials': { + type: 'boolean', + value: false, + help: 'An optional boolean value telling the plugin to hide the credential to the upstream API server. It will be removed by Kong before proxying the request.' + } + }, + "oauth2": { + meta: { + description: 'Add an OAuth 2.0 authentication layer with the Authorization Code Grant, Client Credentials, Implicit Grant or Resource Owner Password Credentials Grant flow. This plugin requires the SSL Plugin with the only_https parameter set to true to be already installed on the API, failing to do so will result in a security weakness.' + }, + 'scopes': { + type: 'text', + value: '', + help: 'Describes an array of comma separated scope names that will be available to the end user' + }, + 'mandatory_scope': { + type: 'boolean', + value: false, + help: 'An optional boolean value telling the plugin to require at least one scope to be authorized by the end user' + }, + 'token_expiration': { + type: 'number', + value: 7200, + help: 'An optional integer value telling the plugin how long should a token last, after which the client will need to refresh the token. Set to 0 to disable the expiration.' + + }, + 'enable_authorization_code': { + type: 'boolean', + value: false, + help: 'An optional boolean value to enable the three-legged Authorization Code flow (RFC 6742 Section 4.1)' + }, + 'enable_client_credentials': { + type: 'boolean', + value: false, + help: 'An optional boolean value to enable the Client Credentials Grant flow (RFC 6742 Section 4.4)' + }, + 'enable_implicit_grant': { + type: 'boolean', + value: false, + help: 'An optional boolean value to enable the Implicit Grant flow which allows to provision a token as a result of the authorization process (RFC 6742 Section 4.2)' + }, + 'enable_password_grant': { + type: 'boolean', + value: false, + help: 'An optional boolean value to enable the Resource Owner Password Credentials Grant flow (RFC 6742 Section 4.3)' + }, + 'hide_credentials': { + type: 'boolean', + value: false, + help: 'An optional boolean value telling the plugin to hide the credential to the upstream API server. It will be removed by Kong before proxying the request' + }, + 'accept_http_if_already_terminated': { + type: 'boolean', + value: false, + help: 'Accepts HTTPs requests that have already been terminated by a proxy or load balancer and the x-forwarded-proto: https header has been added to the request. Only enable this option if the Kong server cannot be publicly accessed and the only entry-point is such proxy or load balancer.' + }, + }, + jwt: { + meta: { + description: 'Verify requests containing HS256 or RS256 signed JSON Web Tokens (as specified in RFC 7519). Each of your Consumers will have JWT credentials (public and secret keys) which must be used to sign their JWTs. A token can then be passed through the Authorization header or in the request\'s URI and Kong will either proxy the request to your upstream services if the token\'s signature is verified, or discard the request if not. Kong can also perform verifications on some of the registered claims of RFC 7519 (exp and nbf).' + }, + 'uri_param_names': { + type: 'text', + value: 'jwt', + help: 'A list of querystring parameters that Kong will inspect to retrieve JWTs.' + }, + 'claims_to_verify': { + type: 'text', + value: '', + help: 'A list of registered claims (according to RFC 7519) that Kong can verify as well. Accepted values: exp, nbf.' + }, + 'key_claim_name': { + type: 'text', + value: 'iss', + help: 'The name of the claim in which the key identifying the secret must be passed.' + }, + 'secret_is_base64': { + type: 'boolean', + value: false, + help: 'If true, the plugin assumes the credential\'s secret to be base64 encoded. You will need to create a base64 encoded secret for your consumer, and sign your JWT with the original secret.' + } + }, + "correlation-id": { + meta: { + description: 'Correlate requests and responses using a unique ID transmitted over an HTTP header.' + }, + 'header_name': { + type: 'text', + value: 'Kong-Request-ID', + help: 'The HTTP header name to use for the correlation ID.' + }, + 'generator': { + type: 'select', + options: ['uuid', 'uuid#counter', 'tracker'], + value: 'uuid#counter', + help: 'The generator to use for the correlation ID.' + }, + 'echo_downstream': { + type: 'boolean', + value: false, + help: 'Whether to echo the header back to downstream (the client).' + } + }, + "datadog": { + meta: { + description: 'Log API metrics like request count, request size, response status and latency to the local Datadog agent.' + }, + 'consumer_id': { + displayName: "Apply to", + type: 'search', + value: null, + help: 'The CONSUMER ID that this plugin configuration will target. ' + + 'This value can only be used if authentication has been enabled ' + + 'so that the system can identify the user making the request.' + + ' If left blank, the plugin will be applied to all consumers.' + }, + 'host': { + type: 'text', + value: '127.0.0.1', + help: 'The IP address or host name to send data to' + }, + 'port': { + type: 'number', + value: 8125, + help: 'The port to send data to on the upstream server' + }, + 'metrics': { + type: 'chips', + options: ['request_count', 'request_size', 'response_size', 'latency', 'status_count', 'unique_users', 'request_per_user'], + value: ['request_count', 'request_size', 'response_size', 'latency', 'status_count', 'unique_users', 'request_per_user'], + help: 'The metrics to be logged.', + filters: { + removeSelected: function (values) { + return function (item) { + if (values instanceof Array) + return values.indexOf(item) < 0; + else + return values + }; + } + } + }, + 'timeout': { + type: 'number', + value: 10000, + help: 'An optional timeout in milliseconds when sending data to the upstream server' + }, + }, + "runscope": { + meta: { + description: 'Logs request and response data to Runscope.' + }, + "access_token": { + type: "text", + value: "", + help: "The Runscope access token (or personal access token) for the Runscope API." + }, + "bucket_key": { + type: "text", + value: "", + help: "Your Runscope bucket ID where traffic data will be stored." + }, + "log_body": { + type: "boolean", + value: false, + help: "Whether or not the request and response bodies should be sent to Runscope." + }, + "api_endpoint": { + type: "text", + value: "https://api.runscope.com", + help: "URL for the Runscope API." + }, + "timeout": { + type: "number", + value: 10000, + help: "An optional timeout in milliseconds when sending data to Runscope.", + }, + "keepalive": { + type: "number", + value: 30, + help: "An optional value in milliseconds that defines for how long an idle connection will live before being closed.", + } + }, + "galileo": { + meta: { + description: 'Logs request and response data to Galileo, the analytics platform for monitoring, visualizing and inspecting API & microservice traffic.' + }, + "consumer_id": { + displayName: "Apply to", + type: 'search', + value: "", + help: "The CONSUMER ID that this plugin configuration will target. " + + "This value can only be used if authentication has been enabled " + + "so that the system can identify the user making the request." + + " If left blank, the plugin will be applied to all consumers.", + + }, + "service_token": { + type: "text", + value: "", + help: "The service token provided to you by Galileo." + }, + "environment": { + type: "text", + value: "", + help: "Slug of your Galileo environment name. None by default." + }, + "log_bodies": { + type: "boolean", + value: false, + help: "Capture and send request/response bodies." + }, + "retry_count": { + type: "number", + value: 10, + help: "Number of retries in case of failure to send data to Galileo." + }, + "connection_timeout": { + type: "number", + value: 30, + help: "Timeout in seconds before aborting a connection to Galileo." + }, + "flush_timeout": { + type: "number", + value: 2, + help: "Timeout in seconds before flushing the current data to Galileo in case of inactivity." + }, + "queue_size": { + type: "number", + value: 1000, + help: "Number of calls to trigger a flush of the buffered data to Galileo." + }, + "host": { + type: "text", + value: "collector.galileo.mashape.com", + help: "Host address of the Galileo collector." + }, + "port": { + type: "number", + value: 443, + help: "Port of the Galileo collector." + }, + "https": { + type: "boolean", + value: true, + help: "Use of HTTPs to connect with the Galileo collector." + } + + }, + "rate-limiting": { + meta: { + description: 'Rate limit how many HTTP requests a developer can make in a given period of seconds, minutes, hours, days, months or years. If the API has no authentication layer, the Client IP address will be used, otherwise the Consumer will be used if an authentication plugin has been configured.' + }, + "consumer_id": { + displayName: "Apply to", + type: 'search', + value: "", + help: "The CONSUMER ID that this plugin configuration will target. " + + "This value can only be used if authentication has been enabled " + + "so that the system can identify the user making the request." + + " If left blank, the plugin will target all consumers." + }, + "second": { + type: "number", + value: 0, + help: "The amount of HTTP requests the developer can make per second. At least one limit must exist.", + + }, + "minute": { + type: "number", + value: 0, + help: "The amount of HTTP requests the developer can make per minute. At least one limit must exist.", + }, + "hour": { + type: "number", + value: 0, + help: "The amount of HTTP requests the developer can make per hour. At least one limit must exist.", + }, + "day": { + type: "number", + value: 0, + help: "The amount of HTTP requests the developer can make per day. At least one limit must exist.", + }, + "month": { + type: "number", + value: 0, + help: "The amount of HTTP requests the developer can make per month. At least one limit must exist.", + }, + "year": { + type: "number", + value: 0, + help: "The amount of HTTP requests the developer can make per year. At least one limit must exist.", + }, + "limit_by": { + type: "select", + options: ["consumer", "credential", "ip"], + value: "consumer", + help: "The entity that will be used when aggregating the limits: consumer, credential, ip. If the consumer or the credential cannot be determined, the system will always fallback to ip.", + }, + "policy": { + type: "select", + options: ["local", "cluster", "redis"], + value: "cluster", + help: "The rate-limiting policies to use for retrieving and incrementing the limits. Available values are local (counters will be stored locally in-memory on the node), cluster (counters are stored in the datastore and shared across the nodes) and redis (counters are stored on a Redis server and will be shared across the nodes).", + }, + "fault_tolerant": { + type: "boolean", + value: true, + help: "A boolean value that determines if the requests should be proxied even if Kong has troubles connecting a third-party datastore. If true requests will be proxied anyways effectively disabling the rate-limiting function until the datastore is working again. If false then the clients will see 500 errors." + }, + "redis_host": { + type: "text", + value: "", + help: "When using the redis policy, this property specifies the address to the Redis server." + }, + "redis_port": { + type: "number", + value: 6379, + help: "When using the redis policy, this property specifies the port of the Redis server. By default is 6379." + }, + "redis_timeout": { + type: "number", + value: 200, + help: "When using the redis policy, this property specifies the timeout in milliseconds of any command submitted to the Redis server." + } + }, + "request-transformer": { + meta: { + description: 'Transform the request sent by a client on the fly on Kong, before hitting the upstream server.' + }, + "http_method": { + help: "Changes the HTTP method for the upstream request." + }, + "remove": { + "headers": { + help: "List of header names. Unset the headers with the given name." + }, + "querystring": { + help: "List of querystring names. Remove the querystring if it is present." + }, + "body": { + help: "List of parameter names. Remove the parameter if and only if content-type is one the following [application/json, multipart/form-data, application/x-www-form-urlencoded] and parameter is present." + }, + }, + "replace": { + "headers": { + help: "List of headername:value pairs. If and only if the header is already set, replace its old value with the new one. Ignored if the header is not already set." + }, + "querystring": { + help: "List of queryname:value pairs. If and only if the header is already set, replace its old value with the new one. Ignored if the header is not already set." + }, + "body": { + help: "List of paramname:value pairs. If and only if content-type is one the following [application/json, multipart/form-data, application/x-www-form-urlencoded] and the parameter is already present, replace its old value with the new one. Ignored if the parameter is not already present." + }, + }, + "add": { + "headers": { + help: "List of headername:value pairs. If and only if the header is not already set, set a new header with the given value. Ignored if the header is already set." + }, + "querystring": { + help: "List of queryname:value pairs. If and only if the querystring is not already set, set a new querystring with the given value. Ignored if the header is already set." + }, + "body": { + help: "List of pramname:value pairs. If and only if content-type is one the following [application/json, multipart/form-data, application/x-www-form-urlencoded] and the parameter is not present, add a new parameter with the given value to form-encoded body. Ignored if the parameter is already present." + }, + }, + "append": { + "headers": { + help: "List of headername:value pairs. If the header is not set, set it with the given value. If it is already set, a new header with the same name and the new value will be set." + }, + "querystring": { + help: "List of queryname:value pairs. If the querystring is not set, set it with the given value. If it is already set, a new querystring with the same name and the new value will be set." + }, + "body": { + help: "List of paramname:value pairs. If the content-type is one the following [application/json, application/x-www-form-urlencoded], add a new parameter with the given value if the parameter is not present, otherwise if it is already present, the two values (old and new) will be aggregated in an array." + } + }, + }, + "response-transformer": { + meta: { + description: 'Transform the response sent by the upstream server on the fly on Kong, before returning the response to the client.' + }, + "remove": { + "headers": { + help: "List of header names. Unset the header(s) with the given name." + }, + "json": { + help: "List of property names. Remove the property from the JSON body if it is present." + }, + }, + "replace": { + "headers": { + help: "List of headername:value pairs. If and only if the header is already set, replace its old value with the new one. Ignored if the header is not already set." + }, + "json": { + help: "List of property:value pairs. If and only if the parameter is already present, replace its old value with the new one. Ignored if the parameter is not already present." + }, + }, + "add": { + "headers": { + help: "List of headername:value pairs. If and only if the header is not already set, set a new header with the given value. Ignored if the header is already set." + }, + "json": { + help: "List of property:value pairs. If and only if the property is not present, add a new property with the given value to the JSON body. Ignored if the property is already present." + }, + }, + "append": { + "headers": { + help: "List of headername:value pairs. If the header is not set, set it with the given value. If it is already set, a new header with the same name and the new value will be set." + }, + "json": { + help: "List of property:value pairs. If the property is not present in the JSON body, add it with the given value. If it is already present, the two values (old and new) will be aggregated in an array." + }, + }, + + } + } } }) diff --git a/api/services/KongService.js b/api/services/KongService.js index 672b001a2..af6aae333 100644 --- a/api/services/KongService.js +++ b/api/services/KongService.js @@ -9,7 +9,7 @@ var KongService = { create: function (req, res) { - unirest.post(sails.config.kong_admin_url + req.url.replace('/kong','')) + unirest.post(req.node_id + req.url.replace('/kong','')) .header('Content-Type', 'application/json') .send(req.body) .end(function (response) { @@ -20,7 +20,7 @@ var KongService = { createCb: function (req, res, cb) { - unirest.post(sails.config.kong_admin_url + req.url.replace('/kong','')) + unirest.post(req.node_id + req.url.replace('/kong','')) .header('Content-Type', 'application/json') .send(req.body) .end(function (response) { @@ -29,10 +29,17 @@ var KongService = { }) }, - createFromEndpointCb: function (endpoint,data, cb) { + createFromEndpointCb: function (endpoint,data, req, cb) { - unirest.post(sails.config.kong_admin_url + endpoint) - .header('Content-Type', 'application/json') + var headers = {'Content-Type': 'application/json'} + + // If apikey is set in headers, use it + if(req.kong_api_key) { + headers['apikey'] = req.kong_api_key + } + + unirest.post(req.node_id + endpoint) + .headers(headers) .send(data) .end(function (response) { //if(data.name == "request-transformer") { @@ -45,7 +52,7 @@ var KongService = { retrieve: function (req, res) { - unirest.get(sails.config.kong_admin_url + req.url.replace('/kong','')) + unirest.get(req.node_id + req.url.replace('/kong','')) .header('Content-Type', 'application/json') .end(function (response) { if (response.error) return res.kongError(response) @@ -54,8 +61,15 @@ var KongService = { }, nodeStatus : function(node,cb) { + + var headers = {'Content-Type': 'application/json'} + + if(node.kong_api_key) { + headers.apikey = node.kong_api_key + } + unirest.get(node.kong_admin_url + "/status") - .header('Content-Type', 'application/json') + .headers(headers) .end(function (response) { if (response.error) return cb(response) return cb(null,response.body) @@ -63,9 +77,17 @@ var KongService = { }, listAllCb: function (req, endpoint, cb) { + + var headers = {'Content-Type': 'application/json'} + + // If apikey is set in headers, use it + if(req.kong_api_key) { + headers['apikey'] = req.kong_api_key + } + var getData = function (previousData,url) { unirest.get(url) - .header('Content-Type', 'application/json') + .headers(headers) .end(function (response) { if (response.error) return cb(response) var data = previousData.concat(response.body.data); @@ -78,7 +100,7 @@ var KongService = { } }) }; - getData([],sails.config.kong_admin_url + endpoint); + getData([],req.node_id + endpoint); }, @@ -98,11 +120,11 @@ var KongService = { } }) }; - getData([],sails.config.kong_admin_url + req.url.replace('/kong','')); + getData([],req.node_id + req.url.replace('/kong','')); }, update: function (req, res) { - unirest.patch(sails.config.kong_admin_url + req.url.replace('/kong','')) + unirest.patch(req.node_id + req.url.replace('/kong','')) .header('Content-Type', 'application/json') .send(req.body) .end(function (response) { @@ -120,7 +142,7 @@ var KongService = { }, updateCb: function (req, res,cb) { - unirest.patch(sails.config.kong_admin_url + req.url.replace('/kong','')) + unirest.patch(req.node_id + req.url.replace('/kong','')) .header('Content-Type', 'application/json') .send(req.body) .end(function (response) { @@ -139,7 +161,7 @@ var KongService = { }, updateOrCreate: function (req, res) { - unirest.put(sails.config.kong_admin_url + req.url.replace('/kong','')) + unirest.put(req.node_id + req.url.replace('/kong','')) .header('Content-Type', 'application/json') .send(req.body) .end(function (response) { @@ -149,7 +171,7 @@ var KongService = { }, delete: function (req, res) { - unirest.delete(sails.config.kong_admin_url + req.url.replace('/kong','')) + unirest.delete(req.node_id + req.url.replace('/kong','')) .header('Content-Type', 'application/json') .end(function (response) { if (response.error) return res.kongError(response) @@ -169,7 +191,7 @@ var KongService = { }, deleteCb: function (req, res,cb) { - unirest.delete(sails.config.kong_admin_url + req.url.replace('/kong','')) + unirest.delete(req.node_id + req.url.replace('/kong','')) .header('Content-Type', 'application/json') .end(function (response) { if (response.error) return cb(response) diff --git a/assets/favicon.ico b/assets/favicon.ico index 0092ec9ca..10d4a92aa 100644 Binary files a/assets/favicon.ico and b/assets/favicon.ico differ diff --git a/assets/fonts/Montserrat-Light.otf b/assets/fonts/Montserrat-Light.otf new file mode 100644 index 000000000..a01805ff0 Binary files /dev/null and b/assets/fonts/Montserrat-Light.otf differ diff --git a/assets/fonts/Montserrat-Regular.otf b/assets/fonts/Montserrat-Regular.otf new file mode 100644 index 000000000..85d0c1e8f Binary files /dev/null and b/assets/fonts/Montserrat-Regular.otf differ diff --git a/assets/fonts/Montserrat-SemiBold.otf b/assets/fonts/Montserrat-SemiBold.otf new file mode 100644 index 000000000..aee9dd293 Binary files /dev/null and b/assets/fonts/Montserrat-SemiBold.otf differ diff --git a/assets/fonts/materialdesignicons-webfont.eot b/assets/fonts/materialdesignicons-webfont.eot new file mode 100644 index 000000000..291c66a61 Binary files /dev/null and b/assets/fonts/materialdesignicons-webfont.eot differ diff --git a/assets/fonts/materialdesignicons-webfont.svg b/assets/fonts/materialdesignicons-webfont.svg new file mode 100644 index 000000000..b415782d3 --- /dev/null +++ b/assets/fonts/materialdesignicons-webfont.svg @@ -0,0 +1,5808 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/fonts/materialdesignicons-webfont.ttf b/assets/fonts/materialdesignicons-webfont.ttf new file mode 100644 index 000000000..832a57b3d Binary files /dev/null and b/assets/fonts/materialdesignicons-webfont.ttf differ diff --git a/assets/fonts/materialdesignicons-webfont.woff b/assets/fonts/materialdesignicons-webfont.woff new file mode 100644 index 000000000..300ab30af Binary files /dev/null and b/assets/fonts/materialdesignicons-webfont.woff differ diff --git a/assets/fonts/materialdesignicons-webfont.woff2 b/assets/fonts/materialdesignicons-webfont.woff2 new file mode 100644 index 000000000..df23bdbd2 Binary files /dev/null and b/assets/fonts/materialdesignicons-webfont.woff2 differ diff --git a/assets/fonts/proximanova-regular.woff b/assets/fonts/proximanova-regular.woff new file mode 100644 index 000000000..ce70b076a Binary files /dev/null and b/assets/fonts/proximanova-regular.woff differ diff --git a/assets/images/attention.png b/assets/images/attention.png new file mode 100644 index 000000000..cf938236c Binary files /dev/null and b/assets/images/attention.png differ diff --git a/assets/images/conn_sync-100.png b/assets/images/conn_sync-100.png new file mode 100644 index 000000000..ca9710e3c Binary files /dev/null and b/assets/images/conn_sync-100.png differ diff --git a/assets/images/k.png b/assets/images/k.png new file mode 100644 index 000000000..644b3d4a4 Binary files /dev/null and b/assets/images/k.png differ diff --git a/assets/images/konga-logo-white-no-icon.png b/assets/images/konga-logo-white-no-icon.png new file mode 100644 index 000000000..65041e52f Binary files /dev/null and b/assets/images/konga-logo-white-no-icon.png differ diff --git a/assets/js/app/apis/00_apis.js b/assets/js/app/apis/00_apis.js index 71c879934..90e2256b8 100644 --- a/assets/js/app/apis/00_apis.js +++ b/assets/js/app/apis/00_apis.js @@ -25,7 +25,7 @@ }, views: { 'content@': { - templateUrl: 'js/app/apis/apis.html', + templateUrl: 'js/app/apis/views/apis.html', controller: 'ApisController', } } @@ -34,12 +34,13 @@ url: '/:api_id/edit', data : { pageName : "Edit API", - displayName : "edit", - prefix : 'edit' + pageDescription : "", + displayName : "edit API", + prefix : '' }, views: { 'content@': { - templateUrl: 'js/app/apis/edit-api.html', + templateUrl: 'js/app/apis/views/edit-api.html', controller: 'ApiController', resolve : { _api: [ @@ -58,11 +59,11 @@ }, 'details@apis.edit': { - templateUrl: 'js/app/apis/api-details.html', + templateUrl: 'js/app/apis/views/api-details.html', controller: 'ApiDetailsController', }, 'plugins@apis.edit': { - templateUrl: 'js/app/apis/api-plugins.html', + templateUrl: 'js/app/apis/views/api-plugins.html', controller: 'ApiPluginsController', resolve : { _plugins : [ @@ -74,7 +75,7 @@ } }, 'healthchecks@apis.edit': { - templateUrl: 'js/app/apis/api-health-checks.html', + templateUrl: 'js/app/apis/views/api-health-checks.html', controller: 'ApiHealthChecksController', } } @@ -90,7 +91,7 @@ }, views: { 'content@': { - templateUrl: 'js/app/apis/api-plugins.html', + templateUrl: 'js/app/apis/views/api-plugins.html', controller: 'ApiPluginsController', resolve : { _api : [ @@ -126,7 +127,7 @@ }, views: { 'content@': { - templateUrl: 'js/app/apis/plugins/manage/manage-api-plugins.html', + templateUrl: 'js/app/apis/views/plugins/manage/manage-api-plugins.html', controller: 'ManageApiPluginsController', resolve : { _api: [ diff --git a/assets/js/app/apis/add-api-modal.html b/assets/js/app/apis/add-api-modal.html deleted file mode 100644 index 422dde4ea..000000000 --- a/assets/js/app/apis/add-api-modal.html +++ /dev/null @@ -1,29 +0,0 @@ - - - \ No newline at end of file diff --git a/assets/js/app/apis/add-api-plugin-modal.html b/assets/js/app/apis/add-api-plugin-modal.html deleted file mode 100644 index d3645178c..000000000 --- a/assets/js/app/apis/add-api-plugin-modal.html +++ /dev/null @@ -1,54 +0,0 @@ - - - diff --git a/assets/js/app/apis/api-model.js b/assets/js/app/apis/api-model.js new file mode 100644 index 000000000..92d3a6b92 --- /dev/null +++ b/assets/js/app/apis/api-model.js @@ -0,0 +1,29 @@ +(function() { + 'use strict'; + + /** + * Model for Author API, this is used to wrap all Author objects specified actions and data change actions. + */ + angular.module('frontend.apis') + .service('ApiModel', [ + 'DataModel', + function(DataModel) { + + var model = new DataModel('kong/apis',true); + + model.handleError = function($scope,err) { + $scope.errors = {} + if(err.data){ + + for(var key in err.data.invalidAttributes){ + $scope.errors[key] = err.data.invalidAttributes[key][0].message + } + } + } + + return model; + + } + ]) + ; +}()); \ No newline at end of file diff --git a/assets/js/app/apis/apis-controller.js b/assets/js/app/apis/apis-controller.js deleted file mode 100644 index 1efbdb589..000000000 --- a/assets/js/app/apis/apis-controller.js +++ /dev/null @@ -1,135 +0,0 @@ -/** - * This file contains all necessary Angular controller definitions for 'frontend.login-history' module. - * - * Note that this file should only contain controllers and nothing else. - */ -(function() { - 'use strict'; - - angular.module('frontend.apis') - .controller('ApisController', [ - '$scope','$rootScope', '$log', '$state','ApiService','UserService','$uibModal','DialogService','SettingsService','ApiHCModel', - function controller($scope,$rootScope, $log, $state, ApiService, UserService,$uibModal,DialogService,SettingsService,ApiHCModel ) { - - $scope.user = UserService.user() - $scope.settings = SettingsService.getSettings() - - - - $scope.toggleStripRequestPathOrUri = function(api) { - - if($rootScope.Gateway.version.indexOf("0.9.") > -1){ - api.strip_request_path=!api.strip_request_path - }else{ - api.strip_uri=!api.strip_uri - } - - $scope.updateApi(api) - } - - $scope.isRequestPathOrUriStripped = function(api) { - if($rootScope.Gateway && $rootScope.Gateway.version.indexOf("0.9.") > -1){ - return api.strip_request_path - } - - return api.strip_uri - } - - $scope.$on('api.created',function(){ - getApis() - }) - - - $scope.$on('user.node.updated',function(node){ - getApis() - }) - - $scope.deleteApi = function($index,api) { - DialogService.prompt( - "Delete API","Really want to delete the selected API?", - ['No don\'t','Yes! delete it'], - function accept(){ - ApiService.delete(api) - .then(function(res){ - $scope.apis.data.splice($scope.apis.data.indexOf(api),1); - }).catch(function(err){ - - }) - },function decline(){}) - - } - - $scope.openAddApiModal = function() { - $uibModal.open({ - animation: true, - ariaLabelledBy: 'modal-title', - ariaDescribedBy: 'modal-body', - templateUrl: 'js/app/apis/add-api-modal.html', - controller: 'AddApiModalController', - controllerAs: '$ctrl', - size: 'lg' - }); - } - - - - $scope.updateApi = function(api) { - - $scope.loading = true - ApiService.update(api) - .then(function(res){ - $log.debug("Update Api: ",res) - $scope.loading = false - getApis() - }).catch(function(err){ - $log.error("Update Api: ",err) - $scope.loading = false - }) - - } - - - function getApis(){ - $scope.loading = true; - ApiService.all() - .then(function(res){ - $scope.apis = res.data - assginApisHealthChecks(); - $scope.loading= false; - }).catch(function(err){ - $scope.loading= false; - }) - - } - - - function assginApisHealthChecks() { - $scope.apis.data.forEach(function(api){ - if(!api.health_checks){ - ApiHCModel.load({ - api_id : api.id, - limit : 1 - }).then(function(data){ - if(data[0]) api.health_checks = data[0] - }) - } - }) - } - - - getApis(); - - - $scope.$on('api.health_checks',function(event,data){ - $scope.apis.data.forEach(function(api){ - if(api.health_checks && data.hc_id == api.health_checks.id) { - api.health_checks.data = data - $scope.$apply() - } - }) - }) - - } - ]) - ; -}()); diff --git a/assets/js/app/apis/apis.html b/assets/js/app/apis/apis.html deleted file mode 100644 index d3adcaa55..000000000 --- a/assets/js/app/apis/apis.html +++ /dev/null @@ -1,97 +0,0 @@ - - -
-
-
-
-
- -
- -
- -
-
-
- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - -
NameUpstream URLCreated
- - - font_download - - - - {{api.preserve_host ? 'cloud_done' : 'cloud_off'}} - - - - - - - - - - {{ api.name || "-" }} - - {{ api.upstream_url || "-" }}{{api.created_at | date : format : timezone}} - - - configuration - - - -
-
- -
-
- diff --git a/assets/js/app/apis/add-api-modal-controller.js b/assets/js/app/apis/controllers/add-api-modal-controller.js similarity index 80% rename from assets/js/app/apis/add-api-modal-controller.js rename to assets/js/app/apis/controllers/add-api-modal-controller.js index 82937befe..56071e911 100644 --- a/assets/js/app/apis/add-api-modal-controller.js +++ b/assets/js/app/apis/controllers/add-api-modal-controller.js @@ -35,9 +35,13 @@ $uibModalInstance.dismiss() }).catch(function(err){ $log.error("Create new api error:", err) - $scope.errors = err.data.customMessage || {} - - + MessageService.error("Submission failed. Make sure you have completed all required fields.") + $scope.errors = {} + if(err.data && err.data.body){ + Object.keys(err.data.body).forEach(function(key){ + $scope.errors[key] = err.data.body[key] + }) + } }) } diff --git a/assets/js/app/apis/add-api-plugin-modal-controller.js b/assets/js/app/apis/controllers/add-api-plugin-modal-controller.js similarity index 81% rename from assets/js/app/apis/add-api-plugin-modal-controller.js rename to assets/js/app/apis/controllers/add-api-plugin-modal-controller.js index 0f5c6017b..58a826df5 100644 --- a/assets/js/app/apis/add-api-plugin-modal-controller.js +++ b/assets/js/app/apis/controllers/add-api-plugin-modal-controller.js @@ -11,11 +11,11 @@ '_','$scope', '$rootScope','$log', '$state','ApiService','MessageService','DialogService', 'KongPluginsService','PluginsService','$uibModal','$uibModalInstance', - '_api','_plugins', + '_api', function controller(_,$scope,$rootScope, $log, $state, ApiService, MessageService, DialogService, KongPluginsService,PluginsService, $uibModal,$uibModalInstance, - _api,_plugins ) { + _api ) { var pluginOptions = new KongPluginsService().pluginOptions() @@ -62,7 +62,7 @@ } function onAddPlugin(name) { - $uibModal.open({ + var modalInstance = $uibModal.open({ animation: true, ariaLabelledBy: 'modal-title', ariaDescribedBy: 'modal-body', @@ -81,17 +81,19 @@ } } }); - } + modalInstance.result.then(function (data) { + + }, function (data) { + if(data && data.name && $scope.existingPlugins.indexOf(data.name) < 0) { + $scope.existingPlugins.push(data.name) + } + }); + } - function fetchPlugins() { - PluginsService.load() - .then(function(res){ - }) - } // Listeners $scope.$on('plugin.added',function(){ @@ -112,6 +114,27 @@ }) + function fetchPlugins() { + PluginsService.load() + .then(function(res){ + + }) + } + + function getApiPlugins() { + ApiService.plugins($scope.api.id) + .then(function(response){ + $scope.existingPlugins = response.data.data.map(function(item){ + return item.name + }) + }) + .catch(function(err){ + + }) + } + + + getApiPlugins(); } ]) diff --git a/assets/js/app/apis/api-controller.js b/assets/js/app/apis/controllers/api-controller.js similarity index 90% rename from assets/js/app/apis/api-controller.js rename to assets/js/app/apis/controllers/api-controller.js index fdf09673b..9dd60f668 100644 --- a/assets/js/app/apis/api-controller.js +++ b/assets/js/app/apis/controllers/api-controller.js @@ -16,8 +16,7 @@ // Fix empty object properties fixProperties() - $log.debug("API",$scope.api) - $state.current.data.pageName = "Edit API : " + ( $scope.api.name || $scope.api.id ) + $state.current.data.pageName = "API " + ( $scope.api.name || $scope.api.id ) $scope.activeSection = 0; $scope.sections = [ { @@ -26,7 +25,7 @@ isVisible : true }, { - name : 'Assigned plugins', + name : 'Plugins', icon : 'mdi mdi-power-plug', isVisible : true }, diff --git a/assets/js/app/apis/api-details-controller.js b/assets/js/app/apis/controllers/api-details-controller.js similarity index 86% rename from assets/js/app/apis/api-details-controller.js rename to assets/js/app/apis/controllers/api-details-controller.js index edb36bfa9..a919122e6 100644 --- a/assets/js/app/apis/api-details-controller.js +++ b/assets/js/app/apis/controllers/api-details-controller.js @@ -22,11 +22,11 @@ $scope.loading = false MessageService.success('API updated successfully!') }).catch(function(err){ + console.log("err",err) $scope.loading = false var errors = {} - Object.keys(err.data.customMessage).forEach(function(key){ - errors[key.replace('config.','')] = err.data.customMessage[key] - MessageService.error(key + " : " + err.data.customMessage[key]) + Object.keys(err.data.body).forEach(function(key){ + MessageService.error(key + " : " + err.data.body[key]) }) $scope.errors = errors }) diff --git a/assets/js/app/apis/api-health-checks-controller.js b/assets/js/app/apis/controllers/api-health-checks-controller.js similarity index 100% rename from assets/js/app/apis/api-health-checks-controller.js rename to assets/js/app/apis/controllers/api-health-checks-controller.js diff --git a/assets/js/app/apis/controllers/apis-controller.js b/assets/js/app/apis/controllers/apis-controller.js new file mode 100644 index 000000000..b10d9c830 --- /dev/null +++ b/assets/js/app/apis/controllers/apis-controller.js @@ -0,0 +1,171 @@ +(function() { + 'use strict'; + + angular.module('frontend.apis') + .controller('ApisController', [ + '$scope','$rootScope', '$log', '$state','ApiService','ListConfig','ApiModel', + 'UserService','$uibModal','DialogService','ApiHCModel', + function controller($scope,$rootScope, $log, $state, ApiService,ListConfig,ApiModel, + UserService,$uibModal,DialogService,ApiHCModel ) { + + ApiModel.setScope($scope, false, 'items', 'itemCount'); + $scope = angular.extend($scope, angular.copy(ListConfig.getConfig('api',ApiModel))); + $scope.user = UserService.user() + $scope.toggleStripRequestPathOrUri = toggleStripRequestPathOrUri + $scope.isRequestPathOrUriStripped = isRequestPathOrUriStripped + $scope.openAddApiModal = openAddApiModal + $scope.updateApi = updateApi + + + /** + * ----------------------------------------------------------------------------------------------------------- + * Internal Functions + * ----------------------------------------------------------------------------------------------------------- + */ + + function updateApi(id,data) { + + $scope.loading = true + + ApiModel.update(id,data) + .then(function(res){ + $log.debug("Update Api: ",res) + $scope.loading = false + _fetchData() + }).catch(function(err){ + $log.error("Update Api: ",err) + $scope.loading = false + }) + + } + + function toggleStripRequestPathOrUri(api) { + + if($rootScope.Gateway.version.indexOf("0.9.") > -1){ + api.strip_request_path=!api.strip_request_path + }else{ + api.strip_uri=!api.strip_uri + } + + $scope.updateApi(api.id,{ + strip_uri : api.strip_uri + }) + } + + + function isRequestPathOrUriStripped(api) { + if($rootScope.Gateway && $rootScope.Gateway.version.indexOf("0.9.") > -1){ + return api.strip_request_path + } + + return api.strip_uri + } + + + function openAddApiModal() { + $uibModal.open({ + animation: true, + ariaLabelledBy: 'modal-title', + ariaDescribedBy: 'modal-body', + templateUrl: 'js/app/apis/views/add-api-modal.html', + controller: 'AddApiModalController', + controllerAs: '$ctrl', + size: 'lg' + }); + } + + + function _fetchData(){ + $scope.loading = true; + ApiModel.load({ + size : $scope.itemsFetchSize + }).then(function(response){ + $scope.items = response; + $scope.loading= false; + }) + + } + + + function loadApiHealthChecks(callback) { + ApiHCModel.load({ + limit : $scope.items.total + }).then(function(response){ + $scope.healthChecks = response + callback(null,response); + }) + } + + + function assignApiHealthChecks(apis, hcs) { + apis.forEach(function (api) { + + hcs.forEach(function (hc) { + if (api.id == hc.api_id) { + api.health_checks = hc + } + }) + }) + } + + function onFilteredItemsChanged(apis) { + + if(!$scope.healthChecks) { + loadApiHealthChecks(function(err,hcs){ + if(hcs) { + assignApiHealthChecks(apis, hcs); + } + }) + }else{ + assignApiHealthChecks(apis, $scope.healthChecks); + } + + } + + + /** + * ----------------------------------------------------------------------------------------------------------- + * Watchers and Listeners + * ----------------------------------------------------------------------------------------------------------- + */ + + $scope.$on('api.health_checks',function(event,data){ + $scope.items.data.forEach(function(api){ + if(api.health_checks && data.hc_id == api.health_checks.id) { + api.health_checks.data = data + $scope.$apply() + } + }) + }) + + $scope.$on('api.created',function(){ + _fetchData() + }) + + + $scope.$on('user.node.updated',function(node){ + _fetchData() + }) + + + // Assign API health checks to filtered items only + // so that the DOM is not overencumbered + // when dealing with large datasets + + $scope.$watch('filteredItems',function(newValue,oldValue){ + + if(newValue && (JSON.stringify(newValue) !== JSON.stringify(oldValue))){ + onFilteredItemsChanged(newValue) + } + }) + + + + // Init + + _fetchData(); + + } + ]) + ; +}()); diff --git a/assets/js/app/apis/add-api-certificates-modal.html b/assets/js/app/apis/views/add-api-certificates-modal.html similarity index 97% rename from assets/js/app/apis/add-api-certificates-modal.html rename to assets/js/app/apis/views/add-api-certificates-modal.html index 2ef4c9943..536ed7a3a 100644 --- a/assets/js/app/apis/add-api-certificates-modal.html +++ b/assets/js/app/apis/views/add-api-certificates-modal.html @@ -2,7 +2,7 @@ diff --git a/assets/js/app/apis/views/add-api-modal.html b/assets/js/app/apis/views/add-api-modal.html new file mode 100644 index 000000000..65d192a15 --- /dev/null +++ b/assets/js/app/apis/views/add-api-modal.html @@ -0,0 +1,24 @@ + + + \ No newline at end of file diff --git a/assets/js/app/apis/views/add-api-plugin-modal.html b/assets/js/app/apis/views/add-api-plugin-modal.html new file mode 100644 index 000000000..246f11e9c --- /dev/null +++ b/assets/js/app/apis/views/add-api-plugin-modal.html @@ -0,0 +1,74 @@ + + + diff --git a/assets/js/app/apis/api-details.html b/assets/js/app/apis/views/api-details.html similarity index 92% rename from assets/js/app/apis/api-details.html rename to assets/js/app/apis/views/api-details.html index 02a398342..cf5e0e5c3 100644 --- a/assets/js/app/apis/api-details.html +++ b/assets/js/app/apis/views/api-details.html @@ -12,7 +12,7 @@
diff --git a/assets/js/app/apis/api-hc-model.js b/assets/js/app/apis/views/api-hc-model.js similarity index 100% rename from assets/js/app/apis/api-hc-model.js rename to assets/js/app/apis/views/api-hc-model.js diff --git a/assets/js/app/apis/api-health-checks.html b/assets/js/app/apis/views/api-health-checks.html similarity index 96% rename from assets/js/app/apis/api-health-checks.html rename to assets/js/app/apis/views/api-health-checks.html index 2691193a9..0b335af3f 100644 --- a/assets/js/app/apis/api-health-checks.html +++ b/assets/js/app/apis/views/api-health-checks.html @@ -3,20 +3,17 @@
Health Checks -
- -
@@ -43,12 +40,13 @@

Konga will perform a POST request to the specified endpoint the first time a health check fails and one every 15 minutes the API stays down or unresponsive.

+
-
+ @@ -70,11 +68,11 @@
No info available yet...
Last known status - done + Healthy - warning + Down or unresponsive diff --git a/assets/js/app/apis/api-plugins.html b/assets/js/app/apis/views/api-plugins.html similarity index 81% rename from assets/js/app/apis/api-plugins.html rename to assets/js/app/apis/views/api-plugins.html index 86111d9bc..82ef3a3fd 100644 --- a/assets/js/app/apis/api-plugins.html +++ b/assets/js/app/apis/views/api-plugins.html @@ -3,8 +3,8 @@
Assigned plugins -
@@ -12,7 +12,7 @@
- +
@@ -26,7 +26,6 @@ Consumer Created - @@ -48,24 +47,20 @@ - {{item.name}} + + + {{item.name}} + + {{item.consumer_id}} All consumers {{item.created_at | date : format : timezone}} - - - - - diff --git a/assets/js/app/apis/views/apis.html b/assets/js/app/apis/views/apis.html new file mode 100644 index 000000000..5b824e126 --- /dev/null +++ b/assets/js/app/apis/views/apis.html @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + + + + + + + + + + + + {{ api.name || "-" }} + + {{ api.upstream_url || "-" }}{{api.created_at | date : format : timezone}} + +
+
+ +
+
+ diff --git a/assets/js/app/apis/edit-api.html b/assets/js/app/apis/views/edit-api.html similarity index 100% rename from assets/js/app/apis/edit-api.html rename to assets/js/app/apis/views/edit-api.html diff --git a/assets/js/app/app.js b/assets/js/app/app.js index 56306c8c5..416a03da8 100644 --- a/assets/js/app/app.js +++ b/assets/js/app/app.js @@ -19,9 +19,14 @@ 'frontend.certificates', 'frontend.users', 'frontend.consumers', - 'frontend.apis' + 'frontend.apis', + 'frontend.connections', + 'frontend.snapshots', + 'frontend.cluster' + ]); + /** * Configuration for frontend application, this contains following main sections: * @@ -34,6 +39,30 @@ .config(function($logProvider){ $logProvider.debugEnabled(window.enableLogs); }) + + .value('HttpTimeout',20000) + + // Provider to disable UI routers template caching + .config(['$provide', function($provide){ + // Set a suffix outside the decorator function + var cacheBuster = Date.now().toString(); + + function templateFactoryDecorator($delegate) { + var fromUrl = angular.bind($delegate, $delegate.fromUrl); + $delegate.fromUrl = function (url, params) { + if (url !== null && angular.isDefined(url) && angular.isString(url)) { + url += (url.indexOf("?") === -1 ? "?" : "&"); + url += "v=" + cacheBuster; + } + + return fromUrl(url, params); + }; + + return $delegate; + } + + $provide.decorator('$templateFactory', ['$delegate', templateFactoryDecorator]); + }]) .config(['$provide',function($provide) { $provide.decorator('$state', function($delegate) { var originalTransitionTo = $delegate.transitionTo; @@ -64,9 +93,10 @@ $httpProvider.interceptors.push('AuthInterceptor'); $httpProvider.interceptors.push('ErrorInterceptor'); $httpProvider.interceptors.push('timeoutHttpIntercept'); + //$httpProvider.interceptors.push('CsrfInterceptor'); - // $httpProvider.interceptors.push('TemplateCacheInterceptor'); - $httpProvider.interceptors.push('KongaInterceptor'); + //$httpProvider.interceptors.push('TemplateCacheInterceptor'); + // $httpProvider.interceptors.push('KongaInterceptor'); // Iterate $httpProvider interceptors and add those to $sailsSocketProvider angular.forEach($httpProvider.interceptors, function iterator(interceptor) { @@ -97,24 +127,9 @@ }) .hashPrefix('!'); - // Routes that needs authenticated user - //$stateProvider - // .state('profile', { - // abstract: true, - // template: '', - // data: { - // access: AccessLevels.user - // } - // }) - // .state('profile.edit', { - // url: '/profile', - // templateUrl: 'js/app/profile/profile.html', - // controller: 'ProfileController' - // }) - //; - // Main state provider for frontend application $stateProvider + .state('frontend', { abstract: true, data: { @@ -125,6 +140,10 @@ templateUrl: 'js/app/core/layout/partials/header.html', controller: 'HeaderController' }, + sidenav: { + templateUrl: 'js/app/core/layout/partials/sidenav.html', + controller: 'SidenavController' + }, footer: { templateUrl: 'js/app/core/layout/partials/footer.html', controller: 'FooterController' @@ -134,7 +153,7 @@ ; // For any unmatched url, redirect to /dashboard - $urlRouterProvider.otherwise('/dashboard'); + $urlRouterProvider.otherwise('/error'); } ]) ; @@ -146,18 +165,27 @@ */ angular.module('frontend') .run([ - '$rootScope', '$state', '$injector', + '$rootScope', '$state', '$stateParams','$injector', 'editableOptions','editableThemes','$templateCache','NodesService', - 'AuthService','cfpLoadingBar', + 'AuthService','cfpLoadingBar','UserService', function run( - $rootScope, $state, $injector, + $rootScope, $state,$stateParams, $injector, editableOptions,editableThemes,$templateCache,NodesService, - AuthService,cfpLoadingBar + AuthService,cfpLoadingBar,UserService ) { + $rootScope.$on('$routeChangeStart', function(event, next, current) { + if (typeof(current) !== 'undefined'){ + $templateCache.remove(current.templateUrl); + } + }); + editableThemes.bs3.buttonsClass = 'btn-sm btn-link'; - $rootScope.moment = window.moment + $rootScope.moment = window.moment; + $rootScope.KONGA_CONFIG = window.KONGA_CONFIG; + $rootScope.$stateParams = $stateParams; + // Set usage of Bootstrap 3 CSS with angular-xeditable editableOptions.theme = 'bs3'; @@ -166,19 +194,50 @@ * Route state change start event, this is needed for following: * 1) Check if user is authenticated to access page, and if not redirect user back to login page */ - $rootScope.$on('$stateChangeStart', function stateChangeStart(event, toState, params) { + $rootScope.$on('$stateChangeStart', function stateChangeStart(event, toState, params, fromState, fromParams) { cfpLoadingBar.start(); + if (!AuthService.authorize(toState.data.access)) { + event.preventDefault(); + $state.go('auth.login', params) + } + if(toState.name == 'auth.login' && AuthService.isAuthenticated()) { event.preventDefault(); $state.go('dashboard', params, {location: 'replace'}) } - // - //if (!AuthService.authorize(toState.data.access)) { - // event.preventDefault(); - // $state.go('auth.login', params) - //} + + + if(toState.data.needsSignupEnabled && !$rootScope.KONGA_CONFIG.signup_enable) { + event.preventDefault(); + $state.go('auth.login', params, {location: 'replace'}) + } + + + + + // Handle Permissions + + var routeNameParts = toState.name.split("."); + var context = routeNameParts[0]; + var action = routeNameParts[1]; + + // Special exception that allows a user to edits his/hers own profile + if(!(context == 'users' && action == 'show' && params.id == UserService.user().id)) { + if(!AuthService.hasPermission(context,action)) { + console.log(fromState) + event.preventDefault(); + cfpLoadingBar.complete(); + + // Redirect to dashboard if coming from nowhere ex. refresh or direct link to page + if(!fromState.name) { + $state.go('dashboard', params, {location: 'replace'}) + } + } + } + + // //if (toState.redirectTo) { // event.preventDefault(); @@ -219,9 +278,9 @@ } ]) .controller('MainController',['$log','$scope','$rootScope','Settings','NodeModel', - 'UserService','InfoService','AuthService', + 'UserService','InfoService','AuthService','SubscriptionsService','NotificationsService', function($log,$scope,$rootScope,Settings,NodeModel, - UserService,InfoService,AuthService){ + UserService,InfoService,AuthService,SubscriptionsService,NotificationsService){ $rootScope.user = UserService.user() $rootScope.konga_version = window.konga_version @@ -240,12 +299,6 @@ }) - - if(AuthService.isAuthenticated()) { - _fetchGatewayInfo() - _fetchSettings() - } - function _fetchSettings() { Settings.load() .then(function(settings){ @@ -260,9 +313,18 @@ .then(function(response){ $rootScope.Gateway = response.data $log.debug("MainController:onUserNodeUpdated:Gateway Info =>",$rootScope.Gateway) + }).catch(function(err){ + $rootScope.Gateway = null; }) } + if(AuthService.isAuthenticated()) { + _fetchGatewayInfo() + _fetchSettings() + + } + + SubscriptionsService.init() }]) ; }()); diff --git a/assets/js/app/certificates/add-certificates-modal.html b/assets/js/app/certificates/add-certificates-modal.html index 698affd55..8e5fec46d 100644 --- a/assets/js/app/certificates/add-certificates-modal.html +++ b/assets/js/app/certificates/add-certificates-modal.html @@ -1,10 +1,10 @@ -