diff --git a/app/controllers/checkLists.js b/app/controllers/checkLists.js new file mode 100644 index 0000000..0521fee --- /dev/null +++ b/app/controllers/checkLists.js @@ -0,0 +1,73 @@ +var mongoose = require('mongoose'), + CheckList = mongoose.model('CheckList'); + +var _ = require('lodash'); +var md5 = require('MD5'); + +var log = require('log4node'); + +exports.index = function(req, res, next) { + log.info('check_lists.index'); + + CheckList.find({ deleted: false }) + .exec(returnResult(res)); +}; + +exports.get = function(req, res, next) { + var id = req.param('check_list_id'); + + log.info("check_lists.get %s", id); + + CheckList.findById(id) + .exec(returnResult(res)); +}; + +exports.create = function(req, res, next) { + log.info("check_lists.create %j", res.body); + + var checkList = new CheckList(req.body); + checkList.save(returnResult(res)); +}; + +exports.update = function(req, res, next) { + var id = req.param('check_list_id'); + log.info('check_lists.update %s %j', id, req.body); + + CheckList.findById(id, function(err, checkList) { + if (err) { + log.error("Error: %s", err); + res.json(500, err); + } else if (!checkList) { + res.json(404, 'Unknown CheckList: %s', id); + } else { + _.assign(checkList, req.body); + checkList.save(returnResult(res, checkList)); + } + }); +}; + +function returnResult(res, result) { + return function(err, data) { + if (err) { + log.error("Error: %s", err); + res.json(500, err); + } else { + if (result) { + res.json(result); + } else { + res.json(data); + } + } + } +} + +function mutateResult(res, mutator) { + return function(err, results) { + if (err) { + log.error("Error: %s", err); + res.json(500, err); + } else { + res.json(mutator(results)); + } + } +} \ No newline at end of file diff --git a/app/controllers/devices.js b/app/controllers/devices.js index 7018885..9ad8ef4 100644 --- a/app/controllers/devices.js +++ b/app/controllers/devices.js @@ -1,5 +1,6 @@ var mongoose = require('mongoose'), - Device = mongoose.model('Device'); + Device = mongoose.model('Device'), + TestRun = mongoose.model('TestRun'); var _ = require('lodash'); var md5 = require('MD5'); @@ -9,7 +10,18 @@ var log = require('log4node'); exports.index = function(req, res, next) { log.info('devices.index'); - Device.find({ deleted: false }) + var query = { + deleted: false + }; + + var deviceType = req.param('deviceType'); + if (deviceType) { + query.deviceType = deviceType; + } + + Device.find(query) + .populate('deviceType', 'category make model') + .populate('client', 'name identifier') .exec(returnResult(res)); }; @@ -19,9 +31,25 @@ exports.get = function(req, res, next) { log.info("devices.get %s", id); Device.findById(id) + .populate('deviceType', 'category make model checkList') + .populate('deviceType.checkList', 'name fields') + .populate('client', 'name identifier') .exec(returnResult(res)); }; +exports.testRuns = function(req, res, next) { + var id = req.param('device_id'); + log.info("devices.testRuns %s", id); + + TestRun.find({device: id, deleted: false }) + .exec(function(err, devices) { + if (err) return next(err); + if (!devices) return next(new Error('Failed to load testRuns ' + id)); + + res.json(devices); + }); +} + exports.create = function(req, res, next) { log.info("devices.create %j", res.body); diff --git a/app/controllers/testRuns.js b/app/controllers/testRuns.js new file mode 100644 index 0000000..d733b28 --- /dev/null +++ b/app/controllers/testRuns.js @@ -0,0 +1,73 @@ +var mongoose = require('mongoose'), + TestRun = mongoose.model('TestRun'); + +var _ = require('lodash'); +var md5 = require('MD5'); + +var log = require('log4node'); + +exports.index = function(req, res, next) { + log.info('test_runs.index'); + + TestRun.find({ deleted: false }) + .exec(returnResult(res)); +}; + +exports.get = function(req, res, next) { + var id = req.param('test_run_id'); + + log.info("test_runs.get %s", id); + + TestRun.findById(id) + .exec(returnResult(res)); +}; + +exports.create = function(req, res, next) { + log.info("test_runs.create %j", res.body); + + var testRun = new TestRun(req.body); + testRun.save(returnResult(res)); +}; + +exports.update = function(req, res, next) { + var id = req.param('test_run_id'); + log.info('test_runs.update %s %j', id, req.body); + + TestRun.findById(id, function(err, testRun) { + if (err) { + log.error("Error: %s", err); + res.json(500, err); + } else if (!testRun) { + res.json(404, 'Unknown TestRun: %s', id); + } else { + _.assign(testRun, req.body); + testRun.save(returnResult(res, testRun)); + } + }); +}; + +function returnResult(res, result) { + return function(err, data) { + if (err) { + log.error("Error: %s", err); + res.json(500, err); + } else { + if (result) { + res.json(result); + } else { + res.json(data); + } + } + } +} + +function mutateResult(res, mutator) { + return function(err, results) { + if (err) { + log.error("Error: %s", err); + res.json(500, err); + } else { + res.json(mutator(results)); + } + } +} \ No newline at end of file diff --git a/app/controllers/workorders.js b/app/controllers/workorders.js index e667fa3..77e3784 100644 --- a/app/controllers/workorders.js +++ b/app/controllers/workorders.js @@ -77,7 +77,8 @@ module.exports = function(config, calendar) { scheduling: req.body.scheduling, techs: req.body.techs, alternativeContact: req.body.alternativeContact, - trackingNumber: req.body.trackingNumber + trackingNumber: req.body.trackingNumber, + devices: req.body.devices }); var notify = req.body._notify || ""; @@ -241,6 +242,7 @@ module.exports = function(config, calendar) { workorder.paidOn = req.body.paidOn; workorder.alternativeContact = req.body.alternativeContact; workorder.trackingNumber = req.body.trackingNumber; + workorder.devices = req.body.devices; callback(err); }); }, diff --git a/app/model/checkList.js b/app/model/checkList.js new file mode 100644 index 0000000..a857479 --- /dev/null +++ b/app/model/checkList.js @@ -0,0 +1,11 @@ +var mongoose = require('mongoose') + Schema = mongoose.Schema, + ObjectId = Schema.ObjectId; + +var checkListSchema = new Schema({ + name: String, + fields: [{}], + deleted: { type: Boolean, default: false } +}); + +module.exports = mongoose.model('CheckList', checkListSchema); diff --git a/app/model/device.js b/app/model/device.js index 1cbd44f..65aa8d7 100644 --- a/app/model/device.js +++ b/app/model/device.js @@ -11,6 +11,8 @@ var deviceSchema = new Schema({ purchaseDate: Date, warrantyExpiration: Date, location: String, + frequencyType: String, + frequencySchedule: [], deleted: { type: Boolean, default: false } }); diff --git a/app/model/deviceType.js b/app/model/deviceType.js index 9d3882c..5052b32 100644 --- a/app/model/deviceType.js +++ b/app/model/deviceType.js @@ -10,6 +10,7 @@ var deviceTypeSchema = new Schema({ links: String, partsRecommended: String, images: [{ type: String }], + checkList: { type: ObjectId, ref: 'CheckList' }, deleted: { type: Boolean, default: false } }); diff --git a/app/model/testRun.js b/app/model/testRun.js new file mode 100644 index 0000000..a083cea --- /dev/null +++ b/app/model/testRun.js @@ -0,0 +1,14 @@ +var mongoose = require('mongoose') + Schema = mongoose.Schema, + ObjectId = Schema.ObjectId; + +var testRunSchema = new Schema({ + device: { type: ObjectId, ref: 'Device' }, + fields: [{}], + date: Date, + result: Boolean, + comment: String, + deleted: { type: Boolean, default: false } +}); + +module.exports = mongoose.model('TestRun', testRunSchema); diff --git a/app/model/workorder.js b/app/model/workorder.js index 8615c74..dbc0d3e 100644 --- a/app/model/workorder.js +++ b/app/model/workorder.js @@ -30,7 +30,8 @@ var workorderSchema = new Schema({ checkNumber: String, paidOn: Date, alternativeContact: String, - trackingNumber: String + trackingNumber: String, + devices: [{ type: ObjectId, ref: 'Device' }] }); module.exports = mongoose.model('Workorder', workorderSchema); diff --git a/app/views/index.jade b/app/views/index.jade index 3b14b33..296554e 100644 --- a/app/views/index.jade +++ b/app/views/index.jade @@ -64,6 +64,10 @@ html(lang="en", ng-app="biomed", ng-controller="PageCtrl") a(href='/deviceTypes') | Devices + li(data-match-route='/checkLists.*') + a(href='/checkLists') + | Check Lists + li(data-match-route='/posts.*', ng-show="accountHasPermission('system.admin')") a(href='/posts') | Posts diff --git a/config/auth.js b/config/auth.js index 3277ce3..4b17735 100644 --- a/config/auth.js +++ b/config/auth.js @@ -15,8 +15,14 @@ module.exports = function(app, passport) { }); app.get('/auth/callback', function(req, res, next) { + + var callbackHost = req.headers['x-forwarded-host']; + if (!callbackHost) { + callbackHost = "localhost:9000"; + } + var options = { - callbackURL: 'http://' + req.headers['x-forwarded-host'] + '/auth/callback' + callbackURL: 'http://' + callbackHost + '/auth/callback' }; passport.authenticate('google', options, function(err, user, info) { var redirectUrl = '/'; diff --git a/config/passport.js b/config/passport.js index 275988b..bafd1ed 100644 --- a/config/passport.js +++ b/config/passport.js @@ -12,11 +12,11 @@ module.exports = function(passport, config) { done(err, user); }); }); - + console.log(config.auth.callback); passport.use(new GoogleStrategy({ clientID: config.auth.clientId, clientSecret: config.auth.clientSecret, -// callbackURL: config.auth.callback, + callbackURL: config.auth.callback, passReqToCallback: true }, function(req, accessToken, refreshToken, profile, done) { diff --git a/config/routes.js b/config/routes.js index e4bdf91..56d6620 100644 --- a/config/routes.js +++ b/config/routes.js @@ -9,18 +9,21 @@ module.exports = function(app, auth, piler, calendar, directory, config) { piler.addJsUrl("//ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.js"); piler.addJsUrl("//ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular-route.js"); piler.addJsUrl("//ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular-resource.js"); - - piler.addJsUrl("http://d3js.org/d3.v2.js"); + piler.addJsUrl("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.js") + piler.addJsFile("/js/lib/moment.js"); piler.addJsFile("/js/lib/bootstrap-datepicker.js"); piler.addJsFile("/js/lib/dialog.js"); piler.addJsFile("/js/lib/select2.js"); piler.addJsFile("/js/lib/dropzone.js"); + piler.addJsFile("/js/lib/hashids.js"); piler.addJsFile("/js/app.js"); piler.addJsFile("/js/controllers.js"); + piler.addJsFile("/js/controllers/checkLists.js"); piler.addJsFile("/js/controllers/devices.js"); piler.addJsFile("/js/controllers/deviceTypes.js"); + piler.addJsFile("/js/controllers/testRuns.js"); piler.addJsFile("/js/directives.js"); piler.addJsFile("/js/filters.js"); piler.addJsFile("/js/services.js"); @@ -65,6 +68,7 @@ module.exports = function(app, auth, piler, calendar, directory, config) { app.get('/api/devices', devices.index); app.get('/api/devices/isUnique', devices.isUnique); app.get('/api/devices/:device_id', devices.get); + app.get('/api/devices/:device_id/test_runs', devices.testRuns); app.post('/api/devices', devices.create); app.post('/api/devices/:device_id', devices.update); @@ -78,6 +82,18 @@ module.exports = function(app, auth, piler, calendar, directory, config) { app.post('/api/device_types', deviceTypes.create); app.post('/api/device_types/:device_type_id', deviceTypes.update); + var checkLists = require('../app/controllers/checkLists'); + app.get('/api/check_lists', checkLists.index); + app.get('/api/check_lists/:check_list_id', checkLists.get); + app.post('/api/check_lists', checkLists.create); + app.post('/api/check_lists/:check_list_id', checkLists.update); + + var testRuns = require('../app/controllers/testRuns'); + app.get('/api/test_runs', testRuns.index); + app.get('/api/test_runs/:test_run_id', testRuns.get); + app.post('/api/test_runs', testRuns.create); + app.post('/api/test_runs/:test_run_id', testRuns.update); + var pms = require('../app/controllers/pms'); app.get('/api/pms', pms.index); diff --git a/public/js/app.js b/public/js/app.js index 6c1d0c0..4952755 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -66,10 +66,6 @@ angular.module('biomed', ['biomed.filters', 'biomed.services', 'biomed.directive controller: "DeviceTypeIndexCtrl", reloadOnSearch: false }) - .when('/devices/add', { - templateUrl: '/partials/devices/add.html', - controller: "DeviceAddCtrl" - }) .when('/deviceTypes/add', { templateUrl: '/partials/deviceTypes/add.html', controller: "DeviceTypeAddCtrl" @@ -78,6 +74,35 @@ angular.module('biomed', ['biomed.filters', 'biomed.services', 'biomed.directive templateUrl: '/partials/deviceTypes/edit.html', controller: "DeviceTypeEditCtrl" }) + .when('/devices/add', { + templateUrl: '/partials/devices/add.html', + controller: "DeviceAddCtrl" + }) + .when('/devices/:id', { + templateUrl: '/partials/devices/edit.html', + controller: "DeviceEditCtrl" + }) + .when('/checkLists', { + templateUrl: '/partials/checkLists/index.html', + controller: "CheckListIndexCtrl", + reloadOnSearch: false + }) + .when('/checkLists/add', { + templateUrl: '/partials/checkLists/add.html', + controller: "CheckListAddCtrl" + }) + .when('/checkLists/:id', { + templateUrl: '/partials/checkLists/add.html', + controller: "CheckListEditCtrl" + }) + .when('/testRuns/add', { + templateUrl: '/partials/testRuns/add.html', + controller: "TestRunAddCtrl" + }) + .when('/testRuns/:id', { + templateUrl: '/partials/testRuns/view.html', + controller: "TestRunViewCtrl" + }) .when('/accounting', { templateUrl: '/partials/accounting/index.html', controller: "AccountingIndexCtrl", diff --git a/public/js/controllers.js b/public/js/controllers.js index 2b1a723..3c2dca4 100644 --- a/public/js/controllers.js +++ b/public/js/controllers.js @@ -936,6 +936,10 @@ angular.module('biomed') var client = $scope.clientPicker.data; $scope.model.client = client._id; $scope.currentClient = client; + + $scope.devices = Clients.devices({id: client._id}, function() { + console.log($scope.devices); + }); } else { $scope.model.client = null; $scope.currentClient = null; @@ -1082,7 +1086,7 @@ angular.module('biomed') } }) -.controller("WorkorderEditCtrl", function($scope, $routeParams, Workorders, Schedule, Users) { +.controller("WorkorderEditCtrl", function($scope, $routeParams, Workorders, Schedule, Users, Clients) { $scope.emailsOptions = { 'multiple': true, 'simple_tags': true, @@ -1099,17 +1103,24 @@ angular.module('biomed') $scope.$watch('group', updateUsers); - $scope.master = Workorders.get($routeParams, function() { - $scope.loading = false; + Workorders.get($routeParams, function(workorderData) { + Clients.devices({id: workorderData.client._id}, function(devicesData) { - if ($scope.master.reason == "Meeting") { - $scope.workorderType = "meeting"; - } + $scope.allDevices = devicesData; + $scope.master = workorderData; + + if ($scope.master.reason == "Meeting") { + $scope.workorderType = "meeting"; + } + + $scope.loading = false; + }); }); $scope.emails = createController(); $scope.status = createController(); $scope.remarks = createController(); + $scope.devices = createController(); $scope.scheduling = createSchedulingController(); function updateStatus() { diff --git a/public/js/controllers/checkLists.js b/public/js/controllers/checkLists.js new file mode 100644 index 0000000..a360843 --- /dev/null +++ b/public/js/controllers/checkLists.js @@ -0,0 +1,102 @@ +angular.module('biomed') +.controller("CheckListIndexCtrl", checkListsIndexController) +.controller("CheckListAddCtrl", checkListsControllerFactory(false)) +.controller("CheckListEditCtrl", checkListsControllerFactory(true)) + +function checkListsIndexController($scope, $filter, $routeParams, CheckLists, LocationBinder) { + $scope.loading = true; + + var allData = CheckLists.index(function() { + $scope.loading = false; + $scope.filter(); + }); + + var filteredData = []; + var index = 0; + var initialPageSize = 100; + var pageSize = 5; + + $scope.canLoad = true; + + $scope.$watch('query', function() { + $scope.filter(); + }); + + LocationBinder($scope, ['query']); + + $scope.filter = function() { + filteredData = $filter('orderBy')($filter('filter')(allData, $scope.query), $scope.sort.column, $scope.sort.descending); + index = initialPageSize; + $scope.canLoad = true; + $scope.checkLists = filteredData.slice(0, initialPageSize); + }; + + $scope.addItems = function() { + $scope.checkLists = $scope.checkLists.concat(filteredData.slice(index, index + pageSize)); + index += pageSize; + $scope.canLoad = index < filteredData.length; + } + + $scope.sort = { + column: 'name', + descending: false + }; + + $scope.selectedCls = function(column) { + return column == $scope.sort.column && 'sort-' + $scope.sort.descending; + } + + $scope.changeSorting = function(column) { + var sort = $scope.sort; + if (sort.column == column) { + sort.descending = !sort.descending; + } else { + sort.column = column; + sort.descending = false; + } + + $scope.filter(); + }; +} + +function checkListsControllerFactory(isEdit) { + return function($scope, CheckLists, $location, $filter, $routeParams) { + + function addField() { + $scope.model.fields.push({ type: 'boolean' }) + } + + function removeField(index) { + console.log('Index: ', index); + if (index != -1) { + $scope.model.fields.splice(index, 1); + } + } + + function save() { + if (isEdit) { + CheckLists.update({id: $scope.model._id}, $scope.model); + } else { + CheckLists.create($scope.model, function(result) { + $location.path("/checkLists/" + result._id); + }); + } + } + + $scope.addField = addField; + $scope.removeField = removeField; + $scope.save = save; + $scope.isEdit = isEdit; + + if (!isEdit) { + $scope.model = { + name: '', + fields: [] + }; + + addField(); + } else { + $scope.model = CheckLists.get($routeParams); + } + } +} \ No newline at end of file diff --git a/public/js/controllers/deviceTypes.js b/public/js/controllers/deviceTypes.js index fa5fa54..06349ec 100644 --- a/public/js/controllers/deviceTypes.js +++ b/public/js/controllers/deviceTypes.js @@ -56,12 +56,14 @@ angular.module('biomed') }; }) -.controller("DeviceTypeAddCtrl", function($scope, DeviceTypes, $location, $filter) { +.controller("DeviceTypeAddCtrl", function($scope, DeviceTypes, CheckLists, $location, $filter) { $scope.model = {}; $scope.categories = DeviceTypes.categories(); $scope.deviceMakes = DeviceTypes.makes(); + $scope.checkLists = CheckLists.index(); + $scope.categoryOpts = { containerCssClass: 'input-xxlarge', placeholder: 'Choose a Device Type', @@ -147,7 +149,7 @@ angular.module('biomed') }; }) -.controller("DeviceTypeEditCtrl", function($scope, DeviceTypes, $location, $filter, $routeParams) { +.controller("DeviceTypeEditCtrl", function($scope, DeviceTypes, Devices, CheckLists, $location, $filter, $routeParams) { var images = {}; $scope.model = DeviceTypes.get($routeParams, function() { @@ -164,9 +166,16 @@ angular.module('biomed') $scope.makePicker = {id: $scope.model.make, text: $scope.model.make}; }); + console.log($routeParams); + + $scope.devices = Devices.index({ deviceType: $routeParams.id }); + $scope.categories = DeviceTypes.categories(); $scope.deviceMakes = DeviceTypes.makes(); + $scope.checkLists = CheckLists.index(); + + $scope.categoryOpts = { containerCssClass: 'input-xxlarge', placeholder: 'Choose a Device Type', diff --git a/public/js/controllers/devices.js b/public/js/controllers/devices.js index 8b13789..79ecaf8 100644 --- a/public/js/controllers/devices.js +++ b/public/js/controllers/devices.js @@ -1 +1,160 @@ +angular.module('biomed') +.controller("DeviceAddCtrl", devicesControllerFactory(false)) +.controller("DeviceEditCtrl", devicesControllerFactory(true)) + +function devicesControllerFactory(isEdit) { + return function($scope, Devices, DeviceTypes, $location, $filter, $routeParams) { + function buildDeviceTypeFilterQuery(ignore) { + var query = {}; + + _.each(['category', 'make', 'model'], function(key) { + if (key == ignore) + return; + + if ($scope.deviceTypes[key].picker) { + query[key] = $scope.deviceTypes[key].picker.id; + } + }) + + return query; + } + + function filterDeviceTypeSelectors() { + console.log('Filtering Device Data'); + + var data = {}; + + _.each(['category', 'make', 'model'], function(key) { + var query = buildDeviceTypeFilterQuery(key); + var filteredResults = $filter('filter')(deviceTypesList, query); + + data[key] = []; + + _.each(filteredResults, function(entry) { + data[key].push(entry[key]); + }); + }); + + _.each(['category', 'make', 'model'], function(key) { + $scope.deviceTypes[key].data = _.uniq(data[key]); + + if (data[key].length == 1) { + var value = data[key][0]; + $scope.deviceTypes[key].picker = { id: value, text: value }; + } + }); + } + + function deviceTypePickerFactory(key, label) { + var optsKey = key + 'Opts'; + + $scope.deviceTypes[key] = {}; + $scope.deviceTypes[key].opts = { + containerCssClass: 'input-xxlarge', + placeholder: 'Choose a ' + label, + query: function(query) { + var data = $filter('filter')($scope.deviceTypes[key].data, query.term); + query.callback({ + results: _.map(data, function(entry) { + return { id: entry, text: entry } + }) + }); + } + }; + + $scope.$watch('deviceTypes.' + key + '.picker',function() { + filterDeviceTypeSelectors(); + updateDeviceTypeSelection(); + }, true); + } + + function clearDeviceTypePickers() { + _.each(['category', 'make', 'model'], function(key) { + $scope.deviceTypes[key].picker = null; + }); + } + + function updateDeviceTypeSelection() { + var query = buildDeviceTypeFilterQuery(); + var results = $filter('filter')(deviceTypesList, query); + + $scope.model.deviceType = (results.length == 1) ? results[0]._id : null; + } + + function generateRandomIdentifier() { + $scope.model.biomedId = hashids.encode(Date.now()); + } + + function toggleFrequency(index) { + $scope.model.frequencySchedule[index] = !$scope.model.frequencySchedule[index]; + + console.log($scope.model); + } + + function create() { + Devices.create($scope.model, function(result) { + console.log('here'); + $location.path("/devices/" + result._id); + }); + } + + function update() { + Devices.update({id: $scope.model._id}, $scope.model); + } + + var hashids = new Hashids("biomed"); + var search = $location.search(); + + $scope.model = { + frequencySchedule: [false, false, false, false, false, false, false, false, false, false, false, false] + }; + + $scope.toggleFrequency = toggleFrequency; + + $scope.deviceTypes = {}; + $scope.deviceTypes.reset = clearDeviceTypePickers; + + $scope.biomedId = {}; + $scope.biomedId.reset = generateRandomIdentifier; + + $scope.create = create; + $scope.update = update; + + deviceTypePickerFactory('category', 'Device Type'); + deviceTypePickerFactory('make', 'Make'); + deviceTypePickerFactory('model', 'Model'); + + var deviceTypesList = DeviceTypes.index(filterDeviceTypeSelectors) + + console.log((isEdit ? "Edit" : "Create") + " Mode"); + + if (isEdit) { + $scope.model = Devices.get($routeParams, function() { + $scope.loading = false; + + var deviceType = $scope.model.deviceType; + + _.each(['category', 'make', 'model'], function(key) { + $scope.deviceTypes[key].picker = { id: deviceType[key], text: deviceType[key] }; + }); + + $scope.model.client = $scope.model.client._id; + $scope.model.deviceType = $scope.model.deviceType._id; + + $scope.testRuns = Devices.testRuns($routeParams, function() { + console.log($scope.testRuns); + }); + }); + } else { + if (search.clientId) { + $scope.model.client = search.clientId; + } else { + $location.path("/deviceTypes"); + return; + } + + generateRandomIdentifier(); + } + } +} diff --git a/public/js/controllers/testRuns.js b/public/js/controllers/testRuns.js new file mode 100644 index 0000000..12cba84 --- /dev/null +++ b/public/js/controllers/testRuns.js @@ -0,0 +1,58 @@ +angular.module('biomed') +.controller("TestRunAddCtrl", testRunAddController) +.controller("TestRunViewCtrl", testRunViewController) + + +function testRunAddController($scope, Devices, CheckLists, TestRuns, $location, $filter, $routeParams) { + var search = $location.search(); + + console.log(search); + + $scope.device = Devices.get({id: search.deviceId}, function() { + console.log($scope.device); + + $scope.checkList = CheckLists.get({id: $scope.device.deviceType.checkList}, function() { + $scope.loading = false; + + $scope.model = { + device: $scope.device._id, + date: new Date(), + fields: [] + }; + + _.each($scope.checkList.fields, function(field) { + + if (field.type == 'boolean') { + field.value = 'false' + } + + field.result = false; + $scope.model.fields.push(field); + }); + }) + }); + + $scope.$watch('model', function() { + $scope.model.result = true; + + _.each($scope.model.fields, function(field) { + if (field.type == 'boolean') { + field.result = (field.value == 'true'); + } else if (field.type == 'range') { + field.result = field.value >= field.min && field.value <= field.max; + } + + $scope.model.result &= field.result; + }) + }, true); + + $scope.save = function() { + TestRuns.create($scope.model, function(result) { + $location.path("/testRuns/" + result._id); + }); + } +} + +function testRunViewController($scope, Devices, CheckLists, TestRuns, $location, $filter, $routeParams) { + $scope.model = TestRuns.get($routeParams); +} \ No newline at end of file diff --git a/public/js/directives.js b/public/js/directives.js index 363025c..7129b68 100644 --- a/public/js/directives.js +++ b/public/js/directives.js @@ -872,3 +872,36 @@ angular.module('biomed.directives', []) } }; }) +.directive('abDeviceUnique', function(Devices, $timeout) { + return { + restrict: 'A', + require: 'ngModel', + link: function(scope, element, attrs, ngModel) { + var stop_timeout; + return scope.$watch(function() { + return ngModel.$modelValue; + }, function(name) { + $timeout.cancel(stop_timeout); + + if (!name) { + ngModel.$setValidity('unique', true); + } + + stop_timeout = $timeout(function() { + var keyProperty = scope.$eval(attrs.abDeviceUnique); + + if (name) { + Devices.isUnique({ + key: keyProperty.key, + field: keyProperty.field, + value: name + }, function(result) { + console.log('unique = ' + result.isUnique); + ngModel.$setValidity('unique', result.isUnique); + }); + } + }, 200); + }); + } + }; +}) \ No newline at end of file diff --git a/public/js/lib/hashids.js b/public/js/lib/hashids.js new file mode 100644 index 0000000..703d0a4 --- /dev/null +++ b/public/js/lib/hashids.js @@ -0,0 +1,352 @@ + +/* + + Hashids + http://hashids.org/javascript + (c) 2013 Ivan Akimov + + https://github.com/ivanakimov/hashids.js + hashids may be freely distributed under the MIT license. + +*/ + +/*jslint plusplus: true, nomen: true, browser: true */ +/*global define */ + +var Hashids = (function () { + + "use strict"; + + function Hashids(salt, minHashLength, alphabet) { + + var uniqueAlphabet, i, j, len, sepsLength, diff, guardCount; + + this.version = "1.0.2"; + + /* internal settings */ + + this.minAlphabetLength = 16; + this.sepDiv = 3.5; + this.guardDiv = 12; + + /* error messages */ + + this.errorAlphabetLength = "error: alphabet must contain at least X unique characters"; + this.errorAlphabetSpace = "error: alphabet cannot contain spaces"; + + /* alphabet vars */ + + this.alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; + this.seps = "cfhistuCFHISTU"; + this.minHashLength = parseInt(minHashLength, 10) > 0 ? minHashLength : 0; + this.salt = (typeof salt === "string") ? salt : ""; + + if (typeof alphabet === "string") { + this.alphabet = alphabet; + } + + for (uniqueAlphabet = "", i = 0, len = this.alphabet.length; i !== len; i++) { + if (uniqueAlphabet.indexOf(this.alphabet.charAt(i)) === -1) { + uniqueAlphabet += this.alphabet.charAt(i); + } + } + + this.alphabet = uniqueAlphabet; + + if (this.alphabet.length < this.minAlphabetLength) { + throw this.errorAlphabetLength.replace("X", this.minAlphabetLength); + } + + if (this.alphabet.search(" ") !== -1) { + throw this.errorAlphabetSpace; + } + + /* seps should contain only characters present in alphabet; alphabet should not contains seps */ + + for (i = 0, len = this.seps.length; i !== len; i++) { + + j = this.alphabet.indexOf(this.seps.charAt(i)); + if (j === -1) { + this.seps = this.seps.substr(0, i) + " " + this.seps.substr(i + 1); + } else { + this.alphabet = this.alphabet.substr(0, j) + " " + this.alphabet.substr(j + 1); + } + + } + + this.alphabet = this.alphabet.replace(/ /g, ""); + + this.seps = this.seps.replace(/ /g, ""); + this.seps = this.consistentShuffle(this.seps, this.salt); + + if (!this.seps.length || (this.alphabet.length / this.seps.length) > this.sepDiv) { + + sepsLength = Math.ceil(this.alphabet.length / this.sepDiv); + + if (sepsLength === 1) { + sepsLength++; + } + + if (sepsLength > this.seps.length) { + + diff = sepsLength - this.seps.length; + this.seps += this.alphabet.substr(0, diff); + this.alphabet = this.alphabet.substr(diff); + + } else { + this.seps = this.seps.substr(0, sepsLength); + } + + } + + this.alphabet = this.consistentShuffle(this.alphabet, this.salt); + guardCount = Math.ceil(this.alphabet.length / this.guardDiv); + + if (this.alphabet.length < 3) { + this.guards = this.seps.substr(0, guardCount); + this.seps = this.seps.substr(guardCount); + } else { + this.guards = this.alphabet.substr(0, guardCount); + this.alphabet = this.alphabet.substr(guardCount); + } + + } + + Hashids.prototype.encode = function () { + + var ret = "", i, len, + numbers = Array.prototype.slice.call(arguments); + + if (!numbers.length) { + return ret; + } + + if (numbers[0] instanceof Array) { + numbers = numbers[0]; + } + + for (i = 0, len = numbers.length; i !== len; i++) { + if (typeof numbers[i] !== "number" || numbers[i] % 1 !== 0 || numbers[i] < 0) { + return ret; + } + } + + return this._encode(numbers); + + }; + + Hashids.prototype.decode = function (hash) { + + var ret = []; + + if (!hash.length || typeof hash !== "string") { + return ret; + } + + return this._decode(hash, this.alphabet); + + }; + + Hashids.prototype.encodeHex = function (str) { + + var i, len, numbers; + + str = str.toString(); + if (!/^[0-9a-fA-F]+$/.test(str)) { + return ""; + } + + numbers = str.match(/[\w\W]{1,12}/g); + + for (i = 0, len = numbers.length; i !== len; i++) { + numbers[i] = parseInt("1" + numbers[i], 16); + } + + return this.encode.apply(this, numbers); + + }; + + Hashids.prototype.decodeHex = function (hash) { + + var ret = [], i, len, + numbers = this.decode(hash); + + for (i = 0, len = numbers.length; i !== len; i++) { + ret += (numbers[i]).toString(16).substr(1); + } + + return ret; + + }; + + Hashids.prototype._encode = function (numbers) { + + var ret, lottery, i, len, number, buffer, last, sepsIndex, guardIndex, guard, halfLength, excess, + alphabet = this.alphabet, + numbersSize = numbers.length, + numbersHashInt = 0; + + for (i = 0, len = numbers.length; i !== len; i++) { + numbersHashInt += (numbers[i] % (i + 100)); + } + + lottery = ret = alphabet.charAt(numbersHashInt % alphabet.length); + for (i = 0, len = numbers.length; i !== len; i++) { + + number = numbers[i]; + buffer = lottery + this.salt + alphabet; + + alphabet = this.consistentShuffle(alphabet, buffer.substr(0, alphabet.length)); + last = this.hash(number, alphabet); + + ret += last; + + if (i + 1 < numbersSize) { + number %= (last.charCodeAt(0) + i); + sepsIndex = number % this.seps.length; + ret += this.seps.charAt(sepsIndex); + } + + } + + if (ret.length < this.minHashLength) { + + guardIndex = (numbersHashInt + ret[0].charCodeAt(0)) % this.guards.length; + guard = this.guards[guardIndex]; + + ret = guard + ret; + + if (ret.length < this.minHashLength) { + + guardIndex = (numbersHashInt + ret[2].charCodeAt(0)) % this.guards.length; + guard = this.guards[guardIndex]; + + ret += guard; + + } + + } + + halfLength = parseInt(alphabet.length / 2, 10); + while (ret.length < this.minHashLength) { + + alphabet = this.consistentShuffle(alphabet, alphabet); + ret = alphabet.substr(halfLength) + ret + alphabet.substr(0, halfLength); + + excess = ret.length - this.minHashLength; + if (excess > 0) { + ret = ret.substr(excess / 2, this.minHashLength); + } + + } + + return ret; + + }; + + Hashids.prototype._decode = function (hash, alphabet) { + + var ret = [], i = 0, + lottery, len, subHash, buffer, + r = new RegExp("[" + this.guards + "]", "g"), + hashBreakdown = hash.replace(r, " "), + hashArray = hashBreakdown.split(" "); + + if (hashArray.length === 3 || hashArray.length === 2) { + i = 1; + } + + hashBreakdown = hashArray[i]; + if (typeof hashBreakdown[0] !== "undefined") { + + lottery = hashBreakdown[0]; + hashBreakdown = hashBreakdown.substr(1); + + r = new RegExp("[" + this.seps + "]", "g"); + hashBreakdown = hashBreakdown.replace(r, " "); + hashArray = hashBreakdown.split(" "); + + for (i = 0, len = hashArray.length; i !== len; i++) { + + subHash = hashArray[i]; + buffer = lottery + this.salt + alphabet; + + alphabet = this.consistentShuffle(alphabet, buffer.substr(0, alphabet.length)); + ret.push(this.unhash(subHash, alphabet)); + + } + + if (this._encode(ret) !== hash) { + ret = []; + } + + } + + return ret; + + }; + + Hashids.prototype.consistentShuffle = function (alphabet, salt) { + + var integer, j, temp, i, v, p; + + if (!salt.length) { + return alphabet; + } + + for (i = alphabet.length - 1, v = 0, p = 0; i > 0; i--, v++) { + + v %= salt.length; + p += integer = salt.charAt(v).charCodeAt(0); + j = (integer + v + p) % i; + + temp = alphabet.charAt(j); + alphabet = alphabet.substr(0, j) + alphabet.charAt(i) + alphabet.substr(j + 1); + alphabet = alphabet.substr(0, i) + temp + alphabet.substr(i + 1); + + } + + return alphabet; + + }; + + Hashids.prototype.hash = function (input, alphabet) { + + var hash = "", + alphabetLength = alphabet.length; + + do { + hash = alphabet.charAt(input % alphabetLength) + hash; + input = parseInt(input / alphabetLength, 10); + } while (input); + + return hash; + + }; + + Hashids.prototype.unhash = function (input, alphabet) { + + var number = 0, pos, i; + + for (i = 0; i < input.length; i++) { + pos = alphabet.indexOf(input[i]); + number += pos * Math.pow(alphabet.length, input.length - i - 1); + } + + return number; + + }; + + /* require.js bit */ + + if (typeof define === "function" && typeof define.amd === "object" && define.amd) { + + define(function () { + return Hashids; + }); + + } + + return Hashids; + +}()); \ No newline at end of file diff --git a/public/js/services.js b/public/js/services.js index 39e9313..71562f0 100644 --- a/public/js/services.js +++ b/public/js/services.js @@ -29,6 +29,41 @@ angular.module('biomed.services', []) destroy: { method: 'DELETE', params: { id: 0 } }, }); }) +.factory("Devices", function($resource) { + return $resource('/api/devices/:id/:cmd', + { id: "@id", cmd: "@cmd" }, + { + index: { method: 'GET', params: {}, isArray: true }, + get: { method: 'GET', params: { id: 0} }, + create: { method: 'POST', params: {} }, + update: { method: 'POST', params: { id: 0} }, + destroy: { method: 'DELETE', params: { id: 0 } }, + testRuns: { method: 'GET', params: { id: 0, cmd: 'test_runs' }, isArray: true }, + isUnique: { method: 'GET', params: { cmd: 'isUnique' } }, + }); +}) +.factory("CheckLists", function($resource) { + return $resource('/api/check_lists/:id/:cmd', + { id: "@id", cmd: "@cmd" }, + { + index: { method: 'GET', params: {}, isArray: true }, + get: { method: 'GET', params: { id: 0} }, + create: { method: 'POST', params: {} }, + update: { method: 'POST', params: { id: 0} }, + destroy: { method: 'DELETE', params: { id: 0 } }, + }); +}) +.factory("TestRuns", function($resource) { + return $resource('/api/test_runs/:id/:cmd', + { id: "@id", cmd: "@cmd" }, + { + index: { method: 'GET', params: {}, isArray: true }, + get: { method: 'GET', params: { id: 0} }, + create: { method: 'POST', params: {} }, + update: { method: 'POST', params: { id: 0} }, + destroy: { method: 'DELETE', params: { id: 0 } }, + }); +}) .factory("Posts", function($resource) { return $resource('/api/posts/:id', { id: "@id" }, diff --git a/public/partials/checkLists/add.html b/public/partials/checkLists/add.html new file mode 100644 index 0000000..0d97f78 --- /dev/null +++ b/public/partials/checkLists/add.html @@ -0,0 +1,71 @@ +
+Name | ++ | ||
---|---|---|---|
{{checkList.name}} | +