From bf6c8743c592f49db06881c22c258f22bc1d6fc0 Mon Sep 17 00:00:00 2001 From: Mumshad Mannambeth Date: Tue, 11 Jul 2017 11:58:56 -0400 Subject: [PATCH] Feature - Integrate System API to view logs in UI --- client/app/admin/admin.controller.js | 29 +++++- client/app/admin/admin.html | 44 ++++++--- client/app/app.js | 3 +- client/app/services/system/system.service.js | 19 ++++ .../services/system/system.service.spec.js | 16 ++++ gulpfile.babel.js | 3 +- server/api/ansible/index.spec.js | 8 -- .../custom_module/custom_module.controller.js | 8 +- server/api/system/index.js | 11 +++ server/api/system/index.spec.js | 49 ++++++++++ server/api/system/system.controller.js | 90 +++++++++++++++++++ server/api/system/system.events.js | 35 ++++++++ server/api/system/system.integration.js | 57 ++++++++++++ server/app.js | 6 +- server/routes.js | 1 + 15 files changed, 353 insertions(+), 26 deletions(-) create mode 100644 client/app/services/system/system.service.js create mode 100644 client/app/services/system/system.service.spec.js create mode 100644 server/api/system/index.js create mode 100644 server/api/system/index.spec.js create mode 100644 server/api/system/system.controller.js create mode 100644 server/api/system/system.events.js create mode 100644 server/api/system/system.integration.js diff --git a/client/app/admin/admin.controller.js b/client/app/admin/admin.controller.js index c2b47d9..fda6dd3 100644 --- a/client/app/admin/admin.controller.js +++ b/client/app/admin/admin.controller.js @@ -2,9 +2,36 @@ export default class AdminController { /*@ngInject*/ - constructor(User) { + constructor($scope, $sce, User, system, ansi2html) { + 'ngInject'; + const admin_ctrl = this; + // Use the User $resource to fetch all users this.users = User.query(); + + /** + * Fetch Server Logs + */ + this.fetchServerLogs = function(){ + system.getLogs('server', (response) => { + admin_ctrl.logsServer = $scope.result = $sce.trustAsHtml(ansi2html.toHtml(response.data).replace(/\n/g, "
")); + }, (response) => { + admin_ctrl.logsServer = $scope.result = $sce.trustAsHtml(ansi2html.toHtml(response.data).replace(/\n/g, "
")); + }); + }; + + + /** + * Fetch API Logs + */ + this.fetchAPILogs = function(){ + system.getLogs('api', (response) => { + admin_ctrl.logsAPI = $scope.result = $sce.trustAsHtml(ansi2html.toHtml(response.data).replace(/\n/g, "
")); + }, (response) => { + admin_ctrl.logsAPI = $scope.result = $sce.trustAsHtml(ansi2html.toHtml(response.data).replace(/\n/g, "
")); + }) + } + } delete(user) { diff --git a/client/app/admin/admin.html b/client/app/admin/admin.html index cbbe68a..8802b98 100644 --- a/client/app/admin/admin.html +++ b/client/app/admin/admin.html @@ -1,12 +1,36 @@
-

The delete user and user index api routes are restricted to users with the 'admin' role.

- + + + +

The delete user and user index api routes are restricted to users with the 'admin' role.

+
    +
  • + + +
  • +
+
+ + + + +
+

+
+
+ +
+

+
+
+
+ + +
+ +
+
diff --git a/client/app/app.js b/client/app/app.js index d5db67a..cf5c7b0 100644 --- a/client/app/app.js +++ b/client/app/app.js @@ -54,6 +54,7 @@ import Projects from './services/projects/projects.service'; import ansible from './services/ansible/ansible.service'; import YAML from './providers/yaml/yaml.service'; import yamlFile from './services/yamlFile/yamlFile.service'; +import system from './services/system/system.service'; import customModules from './custom_modules/custom_modules.service'; @@ -101,7 +102,7 @@ angular.module('app2App', [ngCookies, ngResource, ngSanitize, uiRouter, uiBootst // Components DesignerComponent, ProjectComponent, InventoryComponent, PlaybookComponent, FileBrowserComponent, RolesComponent, RunsComponent, CustomModulesComponent, // Services - YAML, yamlFile, Projects, ansible, ansi2html, editor, customModules, + YAML, yamlFile, Projects, ansible, ansi2html, editor, customModules, system, // Controllers NewInventoryController, NewGroupController, NewHostController, ComplexVarController, NewPlaybookController, ExecutionController, NewPlayController, NewTaskController, ComplexVarModalController, NewFileController, NewRoleController, SearchRoleController, NewModuleController, diff --git a/client/app/services/system/system.service.js b/client/app/services/system/system.service.js new file mode 100644 index 0000000..a805508 --- /dev/null +++ b/client/app/services/system/system.service.js @@ -0,0 +1,19 @@ +'use strict'; +const angular = require('angular'); + +/*@ngInject*/ +export function systemService($http) { + // AngularJS will instantiate a singleton by calling "new" on this function + + const api_system = '/api/system'; + const api_get_logs = api_system + '/logs'; + + this.getLogs = function(type, successCallback, errorCallback){ + $http.get(api_get_logs + '/' + type).then(successCallback, errorCallback); + } + +} + +export default angular.module('webAppApp.system', []) + .service('system', systemService) + .name; diff --git a/client/app/services/system/system.service.spec.js b/client/app/services/system/system.service.spec.js new file mode 100644 index 0000000..1245cb9 --- /dev/null +++ b/client/app/services/system/system.service.spec.js @@ -0,0 +1,16 @@ +'use strict'; + +describe('Service: system', function() { + // load the service's module + beforeEach(module('webAppApp.system')); + + // instantiate service + var system; + beforeEach(inject(function(_system_) { + system = _system_; + })); + + it('should do something', function() { + expect(!!system).to.be.true; + }); +}); diff --git a/gulpfile.babel.js b/gulpfile.babel.js index 7a382c9..1b51e96 100644 --- a/gulpfile.babel.js +++ b/gulpfile.babel.js @@ -395,8 +395,9 @@ gulp.task('mocha:integration', () => { .pipe(mocha()); }); +// Run as gulp env:all env:test mocha:integration:one gulp.task('mocha:integration:one', () => { - return gulp.src([`${serverPath}/**/custom_module.integration.js`, 'mocha.global.js']) + return gulp.src([`${serverPath}/**/system.integration.js`, 'mocha.global.js']) .pipe(mocha()); }); diff --git a/server/api/ansible/index.spec.js b/server/api/ansible/index.spec.js index 0d4aa0c..719a8e0 100644 --- a/server/api/ansible/index.spec.js +++ b/server/api/ansible/index.spec.js @@ -27,14 +27,6 @@ var routerStub = { patch: sinon.spy(), post: sinon.spy(), delete: sinon.spy(), - modules: sinon.spy(), - command: sinon.spy(), - execute: sinon.spy(), - project_files: sinon.spy(), - playbook_get: sinon.spy(), - playbook_create: sinon.spy(), - playbook_delete: sinon.spy(), - playbook_list: sinon.spy() }; // require the index with our stubbed out modules diff --git a/server/api/custom_module/custom_module.controller.js b/server/api/custom_module/custom_module.controller.js index d51f099..92d497d 100644 --- a/server/api/custom_module/custom_module.controller.js +++ b/server/api/custom_module/custom_module.controller.js @@ -15,6 +15,8 @@ import CustomModule from './custom_module.model'; var ssh2_exec = require('../../components/ssh/ssh2_exec'); var scp2_exec = require('../../components/scp/scp_exec'); +const logger = require('../../components/logger/logger'); + function respondWithResult(res, statusCode) { statusCode = statusCode || 200; return function(entity) { @@ -88,20 +90,18 @@ export function index(req, res) { ansibleEngine ); - /*return CustomModule.find().exec() - .then(respondWithResult(res)) - .catch(handleError(res));*/ } // Gets a single CustomModule or a module_template from DB export function show(req, res) { - console.log("Show " + req.params.custom_module); var ansibleEngine = req.body.ansibleEngine; if(!ansibleEngine.customModules){ return res.status(500).send("Custom Modules Folder not defined in Ansible Engine") } + logger.info('Show custom module '); + var command = 'cat "' + ansibleEngine.customModules + '"/' + req.params.custom_module; // If request is for module template, return module_template from default path diff --git a/server/api/system/index.js b/server/api/system/index.js new file mode 100644 index 0000000..32f256f --- /dev/null +++ b/server/api/system/index.js @@ -0,0 +1,11 @@ +'use strict'; + +var express = require('express'); +var controller = require('./system.controller'); + +var router = express.Router(); + +router.get('/logs/server', controller.serverLogs); +router.get('/logs/api', controller.apiLogs); + +module.exports = router; diff --git a/server/api/system/index.spec.js b/server/api/system/index.spec.js new file mode 100644 index 0000000..e683816 --- /dev/null +++ b/server/api/system/index.spec.js @@ -0,0 +1,49 @@ +'use strict'; + +/* globals sinon, describe, expect, it */ + +var proxyquire = require('proxyquire').noPreserveCache(); + +var systemCtrlStub = { + serverLogs: 'systemCtrl.serverLogs', + apiLogs: 'systemCtrl.apiLogs' +}; + +var routerStub = { + get: sinon.spy(), + serverLogs: sinon.spy(), + apiLogs: sinon.spy() +}; + +// require the index with our stubbed out modules +var systemIndex = proxyquire('./index.js', { + express: { + Router() { + return routerStub; + } + }, + './system.controller': systemCtrlStub +}); + +describe('System API Router:', function() { + it('should return an express router instance', function() { + expect(systemIndex).to.equal(routerStub); + }); + + describe('GET /api/system/logs/server', function() { + it('should route to system.controller.serverLogs', function() { + expect(routerStub.get + .withArgs('/logs/server', 'systemCtrl.serverLogs') + ).to.have.been.calledOnce; + }); + }); + + describe('GET /api/system/logs/api', function() { + it('should route to system.controller.apiLogs', function() { + expect(routerStub.get + .withArgs('/logs/api', 'systemCtrl.apiLogs') + ).to.have.been.calledOnce; + }); + }); + +}); diff --git a/server/api/system/system.controller.js b/server/api/system/system.controller.js new file mode 100644 index 0000000..6f0c943 --- /dev/null +++ b/server/api/system/system.controller.js @@ -0,0 +1,90 @@ +/** + * Using Rails-like standard naming convention for endpoints. + * GET /api/system -> index + * POST /api/system -> create + * GET /api/system/:id -> show + * PUT /api/system/:id -> upsert + * PATCH /api/system/:id -> patch + * DELETE /api/system/:id -> destroy + */ + +'use strict'; + +import jsonpatch from 'fast-json-patch'; +import config from '../../config/environment'; + +function respondWithResult(res, statusCode) { + statusCode = statusCode || 200; + return function(entity) { + if(entity) { + return res.status(statusCode).json(entity); + } + return null; + }; +} + +function patchUpdates(patches) { + return function(entity) { + try { + // eslint-disable-next-line prefer-reflect + jsonpatch.apply(entity, patches, /*validate*/ true); + } catch(err) { + return Promise.reject(err); + } + + return entity.save(); + }; +} + +function removeEntity(res) { + return function(entity) { + if(entity) { + return entity.remove() + .then(() => { + res.status(204).end(); + }); + } + }; +} + +function handleEntityNotFound(res) { + return function(entity) { + if(!entity) { + res.status(404).end(); + return null; + } + return entity; + }; +} + +function handleError(res, statusCode) { + statusCode = statusCode || 500; + return function(err) { + res.status(statusCode).send(err); + }; +} + + +exports.serverLogs = function(req,res){ + + const fs = require('fs'); + + console.log("Server logs") + + fs.readFile(config.paths.local_server_logfile, function(err, data){ + if(err)res.status(500).send(err); + else res.send(data); + }) + +}; + +exports.apiLogs = function(req,res){ + + const fs = require('fs'); + + fs.readFile(config.paths.local_express_server_logfile, function(err, data){ + if(err)res.status(500).send(err); + else res.send(data); + }) + +}; diff --git a/server/api/system/system.events.js b/server/api/system/system.events.js new file mode 100644 index 0000000..8c7d130 --- /dev/null +++ b/server/api/system/system.events.js @@ -0,0 +1,35 @@ +/** + * System model events + */ + +'use strict'; + +import {EventEmitter} from 'events'; +var SystemEvents = new EventEmitter(); + +// Set max event listeners (0 == unlimited) +SystemEvents.setMaxListeners(0); + +// Model events +var events = { + save: 'save', + remove: 'remove' +}; + +// Register the event emitter to the model events +function registerEvents(System) { + for(var e in events) { + let event = events[e]; + System.post(e, emitEvent(event)); + } +} + +function emitEvent(event) { + return function(doc) { + SystemEvents.emit(event + ':' + doc._id, doc); + SystemEvents.emit(event, doc); + }; +} + +export {registerEvents}; +export default SystemEvents; diff --git a/server/api/system/system.integration.js b/server/api/system/system.integration.js new file mode 100644 index 0000000..ccfc2a2 --- /dev/null +++ b/server/api/system/system.integration.js @@ -0,0 +1,57 @@ +'use strict'; + +/* globals describe, expect, it, beforeEach, afterEach */ + +var app = require('../..'); +import request from 'supertest'; + +var newSystem; + +describe('System API:', function() { + describe('GET /api/system/logs/server', function() { + var systems; + + beforeEach(function(done) { + request(app) + .get('/api/system/logs/server') + .expect(200) + //.expect('Content-Type', /json/) + .end((err, res) => { + if(err) { + return done(err); + } + systems = res.text; + done(); + }); + }); + + it('should respond with String containing "Express server listening"', function() { + expect(systems).to.contain('Express server listening'); + }); + }); + + + describe('GET /api/system/logs/api', function() { + var systems; + + beforeEach(function(done) { + request(app) + .get('/api/system/logs/api') + .expect(200) + //.expect('Content-Type', /json/) + .end((err, res) => { + if(err) { + return done(err); + } + console.log(JSON.stringify(res)); + systems = res.text; + done(); + }); + }); + + it('should respond with String', function() { + expect(systems).to.contain('/api/system/logs'); + }); + }); + +}); diff --git a/server/app.js b/server/app.js index d743048..6c41928 100644 --- a/server/app.js +++ b/server/app.js @@ -10,6 +10,7 @@ mongoose.Promise = require('bluebird'); import config from './config/environment'; import http from 'http'; import seedDatabaseIfNeeded from './config/seed'; +const logger = require('./components/logger/logger'); // Connect to MongoDB mongoose.connect(config.mongo.uri, config.mongo.options); @@ -27,7 +28,10 @@ require('./routes').default(app); // Start server function startServer() { app.angularFullstack = server.listen(config.port, config.ip, function() { - console.log('Express server listening on %d, in %s mode', config.port, app.get('env')); + logger.info('\n'); + logger.info('---------------------------------------------------------------'); + logger.info('Express server listening on %d, in %s mode', config.port, app.get('env')); + logger.info('---------------------------------------------------------------\n'); }); } diff --git a/server/routes.js b/server/routes.js index 6d609f3..0396c61 100644 --- a/server/routes.js +++ b/server/routes.js @@ -10,6 +10,7 @@ import * as auth from './auth/auth.service'; export default function(app) { // Insert routes below + app.use('/api/system', require('./api/system')); app.use('/api/custom_modules', auth.isAuthenticated(), require('./api/custom_module')); app.use('/api/ansible', auth.isAuthenticated(), require('./api/ansible')); app.use('/api/projects', auth.isAuthenticated(), require('./api/project'));