mirror of
https://github.com/atlanticbiomedical/biomedjs.git
synced 2025-07-02 00:47:26 -04:00
Added a bunch of stuff
This commit is contained in:
73
app/controllers/checkLists.js
Normal file
73
app/controllers/checkLists.js
Normal file
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
||||
|
73
app/controllers/testRuns.js
Normal file
73
app/controllers/testRuns.js
Normal file
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
});
|
||||
},
|
||||
|
11
app/model/checkList.js
Normal file
11
app/model/checkList.js
Normal file
@ -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);
|
@ -11,6 +11,8 @@ var deviceSchema = new Schema({
|
||||
purchaseDate: Date,
|
||||
warrantyExpiration: Date,
|
||||
location: String,
|
||||
frequencyType: String,
|
||||
frequencySchedule: [],
|
||||
deleted: { type: Boolean, default: false }
|
||||
});
|
||||
|
||||
|
@ -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 }
|
||||
});
|
||||
|
||||
|
14
app/model/testRun.js
Normal file
14
app/model/testRun.js
Normal file
@ -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);
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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 = '/';
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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() {
|
||||
|
102
public/js/controllers/checkLists.js
Normal file
102
public/js/controllers/checkLists.js
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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',
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
58
public/js/controllers/testRuns.js
Normal file
58
public/js/controllers/testRuns.js
Normal file
@ -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);
|
||||
}
|
@ -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);
|
||||
});
|
||||
}
|
||||
};
|
||||
})
|
352
public/js/lib/hashids.js
Normal file
352
public/js/lib/hashids.js
Normal file
@ -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;
|
||||
|
||||
}());
|
@ -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" },
|
||||
|
71
public/partials/checkLists/add.html
Normal file
71
public/partials/checkLists/add.html
Normal file
@ -0,0 +1,71 @@
|
||||
<ul class="breadcrumb">
|
||||
<li><a href="/checkLists"><i class="icon-briefcase"></i> Check Lists</a><span class="divider"></span><li>
|
||||
<li class="active">
|
||||
<span ng-if="isEdit">Edit</span>
|
||||
<span ng-if="!isEdit">New</span> Check List
|
||||
<li>
|
||||
</ul>
|
||||
<header>
|
||||
<h1>New Check List</h1>
|
||||
</header>
|
||||
<form name="form" class="form">
|
||||
<div class="form-section">
|
||||
<div class="section-label">General</div>
|
||||
<div class="section-container">
|
||||
<div class="form-editor">
|
||||
<div class="control-group">
|
||||
<label class="control-label">Name</label>
|
||||
<div class="controls">
|
||||
<input ng-model="model.name" type="text" class="input-xlarge" required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-section" ng-repeat="field in model.fields">
|
||||
<div class="section-label">Field {{$index + 1}}</div>
|
||||
<div class="section-container">
|
||||
<div class="form-editor">
|
||||
<div class="control-group">
|
||||
<label class="control-label">Label</label>
|
||||
<div class="controls">
|
||||
<input ng-model="field.label" type="text" class="input-xlarge" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">Field Type</label>
|
||||
<div class="controls">
|
||||
<select ng-model="field.type" class="input-xlarge" required>
|
||||
<option value="boolean">Pass / Fail</option>
|
||||
<option value="range">Range</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="field.type == 'range'">
|
||||
<div class="control-group">
|
||||
<label class="control-label">Minimum (Inclusive)</label>
|
||||
<div class="controls">
|
||||
<input ng-model="field.min" type="number" class="input-xlarge" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">Maximum (Inclusive)</label>
|
||||
<div class="controls">
|
||||
<input ng-model="field.max" type="number" class="input-xlarge" required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button ng-click="removeField($index)" ng-disabled="model.fields.length <= 1" class="btn" type="button">Remove Field</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-section">
|
||||
<div class="section-label"> </div>
|
||||
<div class="section-container">
|
||||
<button ng-click="addField()" type="button" class="btn">Add Field</button>
|
||||
<button ng-click="save()" ng-disabled="form.$invalid" type="button" class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
36
public/partials/checkLists/index.html
Normal file
36
public/partials/checkLists/index.html
Normal file
@ -0,0 +1,36 @@
|
||||
<ul class="breadcrumb">
|
||||
<li><a href="/check_lists"><i class="icon-briefcase"></i> Check Lists</a><li>
|
||||
</ul>
|
||||
<header>
|
||||
<h1>Check Lists</h1>
|
||||
</header>
|
||||
|
||||
<div class="row-fluid">
|
||||
<div class="span12">
|
||||
<div class="toolbelt">
|
||||
<a href="/checkLists/add" class="btn btn-primary" ng-show="accountHasPermission('system.edit')">Create new Check List</a>
|
||||
<div class="pull-right">
|
||||
<span class="toolbelt-text">Search:</span>
|
||||
<div class="input-append">
|
||||
<input type="text" ng-model="query" class="input-large" placeholder="Search">
|
||||
<span class="add-on"><i class="icon-search"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<table class="biomed-table" infinite-scroll="addItems()" can-load="canLoad" threshold="300">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 33%" ng-class="selectedCls('name')" ng-click="changeSorting('name')">Name</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-show="loading"><td colspan="4" class="table-loading"><i class="loader"></i></td></tr>
|
||||
<tr ng-hide="loading || checkLists.length"><td colspan="4" class="table-message">There is no information to display.</td></tr>
|
||||
<tr ng-hide="loading" ng-repeat="checkList in checkLists">
|
||||
<td><a href="/checkLists/{{checkList._id}}">{{checkList.name}}</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
@ -299,7 +299,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 7%">Device ID</th>
|
||||
<th style="width: 6%">Device</th>
|
||||
<th style="width: 6%">Category</th>
|
||||
<th style="width: 5%">Make</th>
|
||||
<th style="width: 5%">Model</th>
|
||||
<th style="width: 5%">Serial No.</th>
|
||||
@ -313,7 +313,7 @@
|
||||
<tr ng-hide="devices.length"><td colspan="11" class="table-message">There is no information to display.</td></tr>
|
||||
<tr ng-repeat="device in devices">
|
||||
<td><a href="/devices/{{device._id}}">{{device.biomedId}}</a></td>
|
||||
<td></td>
|
||||
<td>{{device.deviceType.category}}</td>
|
||||
<td>{{device.deviceType.make}}</td>
|
||||
<td>{{device.deviceType.model}}</td>
|
||||
<td>{{device.serialNumber}}</td>
|
||||
|
@ -57,6 +57,22 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-section">
|
||||
<div class="section-label">Checklist</div>
|
||||
<div class="section-container">
|
||||
<div class="form-editor">
|
||||
<div class="control-group">
|
||||
<label class="control-label">Checklist</label>
|
||||
<div class="controls">
|
||||
<select ng-model="model.checkList" class="input-xlarge">
|
||||
<option value="">None</option>
|
||||
<option ng-repeat="checkList in checkLists" value="{{checkList._id}}">{{checkList.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-section">
|
||||
<div class="section-label">Images</div>
|
||||
<div class="section-container">
|
||||
|
@ -6,75 +6,127 @@
|
||||
<h1>Edit Device</h1>
|
||||
</header>
|
||||
|
||||
<form name="form" class="form">
|
||||
<div class="form-section">
|
||||
<div class="section-label">Device Identification</div>
|
||||
<div class="section-container">
|
||||
<div class="form-editor">
|
||||
<div class="control-group">
|
||||
<label class="control-label">Device Type</label>
|
||||
<div class="controls">
|
||||
<input type="hidden" ng-model="categoryPicker" ui-select2="categoryOpts" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">Make</label>
|
||||
<div class="controls">
|
||||
<input type="hidden" ng-model="makePicker" ui-select2="makeOpts" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">Model</label>
|
||||
<div class="controls">
|
||||
<input ng-model="model.model" type="text" class="input-xlarge">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-section">
|
||||
<div class="section-label">Device Details</div>
|
||||
<div class="section-container">
|
||||
<div class="form-editor">
|
||||
<div class="control-group">
|
||||
<label class="control-label">Technical Data</label>
|
||||
<div class="controls">
|
||||
<textarea ng-model="model.technicalData" type="text" class="input-xxlarge"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">Links</label>
|
||||
<div class="controls">
|
||||
<textarea ng-model="model.links" type="text" class="input-xxlarge"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">Recommended Parts</label>
|
||||
<div class="controls">
|
||||
<textarea ng-model="model.partsRecommended" type="text" class="input-xxlarge"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-section">
|
||||
<div class="section-label">Images</div>
|
||||
<div class="section-container">
|
||||
<div class="form-editor">
|
||||
<div class="control-group">
|
||||
<label class="control-label">Images</label>
|
||||
<div class="controls">
|
||||
<div class="dropzone" dropzone="imageOpts" existing="existingImages" prefix="devices/"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-hide="loading" class="tabbable">
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active form" title="Details">
|
||||
|
||||
<div class="form-section">
|
||||
<div class="section-label"> </div>
|
||||
<div class="section-container">
|
||||
<button ng-click="save()" ng-disabled="form.$invalid" type="button" class="btn btn-primary">Save</button>
|
||||
<form name="form" class="form">
|
||||
<div class="form-section">
|
||||
<div class="section-label">Device Identification</div>
|
||||
<div class="section-container">
|
||||
<div class="form-editor">
|
||||
<div class="control-group">
|
||||
<label class="control-label">Device Type</label>
|
||||
<div class="controls">
|
||||
<input type="hidden" ng-model="categoryPicker" ui-select2="categoryOpts" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">Make</label>
|
||||
<div class="controls">
|
||||
<input type="hidden" ng-model="makePicker" ui-select2="makeOpts" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">Model</label>
|
||||
<div class="controls">
|
||||
<input ng-model="model.model" type="text" class="input-xlarge">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-section">
|
||||
<div class="section-label">Device Details</div>
|
||||
<div class="section-container">
|
||||
<div class="form-editor">
|
||||
<div class="control-group">
|
||||
<label class="control-label">Technical Data</label>
|
||||
<div class="controls">
|
||||
<textarea ng-model="model.technicalData" type="text" class="input-xxlarge"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">Links</label>
|
||||
<div class="controls">
|
||||
<textarea ng-model="model.links" type="text" class="input-xxlarge"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">Recommended Parts</label>
|
||||
<div class="controls">
|
||||
<textarea ng-model="model.partsRecommended" type="text" class="input-xxlarge"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-section">
|
||||
<div class="section-label">Checklist</div>
|
||||
<div class="section-container">
|
||||
<div class="form-editor">
|
||||
<div class="control-group">
|
||||
<label class="control-label">Checklist</label>
|
||||
<div class="controls">
|
||||
<select ng-model="model.checkList" class="input-xlarge">
|
||||
<option value="">None</option>
|
||||
<option ng-repeat="checkList in checkLists" value="{{checkList._id}}">{{checkList.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-section">
|
||||
<div class="section-label">Images</div>
|
||||
<div class="section-container">
|
||||
<div class="form-editor">
|
||||
<div class="control-group">
|
||||
<label class="control-label">Images</label>
|
||||
<div class="controls">
|
||||
<div class="dropzone" dropzone="imageOpts" existing="existingImages" prefix="devices/"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-section">
|
||||
<div class="section-label"> </div>
|
||||
<div class="section-container">
|
||||
<button ng-click="save()" ng-disabled="form.$invalid" type="button" class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="tab-pane" title="Devices">
|
||||
<div class="row-fluid">
|
||||
<div class="span12">
|
||||
<table class="biomed-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 7%">Device ID</th>
|
||||
<th style="width: 6%">Client</th>
|
||||
<th style="width: 5%">Serial No.</th>
|
||||
<th style="width: 8%">Purchase Date</th>
|
||||
<th style="width: 6%">Warranty Expiration</th>
|
||||
<th style="width: 4%">Location</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-hide="devices.length"><td colspan="11" class="table-message">There is no information to display.</td></tr>
|
||||
<tr ng-repeat="device in devices">
|
||||
<td><a href="/devices/{{device._id}}">{{device.biomedId}}</a></td>
|
||||
<td><a ng-href="/clients/{{device.client._id}}">{{device.client.name}} ({{device.client.identifier}})</a></td>
|
||||
<td>{{device.serialNumber}}</td>
|
||||
<td>{{device.purchaseDate | date}}</td>
|
||||
<td>{{device.warrantyExpiration | date}}</td>
|
||||
<td>{{device.location}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -8,25 +8,54 @@
|
||||
|
||||
<form name="form" class="form">
|
||||
<div class="form-section">
|
||||
<div class="section-label">Device Identifieion</div>
|
||||
<div class="section-label">Device Type</div>
|
||||
<div class="section-container">
|
||||
<div class="form-editor">
|
||||
<div class="control-group">
|
||||
<label class="control-label">Device Type</label>
|
||||
<label class="control-label">Category</label>
|
||||
<div class="controls">
|
||||
<input type="hidden" ng-model="categoryPicker" ui-select2="categoryOpts" />
|
||||
<input type="hidden" ng-model="deviceTypes.category.picker" ui-select2="deviceTypes.category.opts" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">Make</label>
|
||||
<div class="controls">
|
||||
<input type="hidden" ng-model="makePicker" ui-select2="makeOpts" />
|
||||
<input type="hidden" ng-model="deviceTypes.make.picker" ui-select2="deviceTypes.make.opts" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">Model</label>
|
||||
<div class="controls">
|
||||
<input type="hidden" ng-model="modelPicker" ui-select2="modelOpts">
|
||||
<input type="hidden" ng-model="deviceTypes.model.picker" ui-select2="deviceTypes.model.opts" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label"></label>
|
||||
<div class="controls">
|
||||
<button ng-click="deviceTypes.reset()" type="button" class="btn">Reset</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-section">
|
||||
<div class="section-label">Identifier</div>
|
||||
<div class="section-container">
|
||||
<div class="form-editor">
|
||||
<div
|
||||
ng-class="{ 'error': form.identifier.$invalid && !form.identifier.$pristine }"
|
||||
class="control-group">
|
||||
|
||||
<label class="control-label">Identifier</label>
|
||||
<div class="controls">
|
||||
<input
|
||||
ng-model="model.biomedId"
|
||||
required
|
||||
ab-device-unique="{field: 'biomedId'}"
|
||||
type="text"
|
||||
name="identifier"
|
||||
class="input-xlarge">
|
||||
<span ng-show="form.identifier.$error.unique" class="help-inline">Identifier must be unique</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -37,44 +66,101 @@
|
||||
<div class="section-container">
|
||||
<div class="form-editor">
|
||||
<div class="control-group">
|
||||
<label class="control-label">Technical Data</label>
|
||||
<label class="control-label">Serial Number</label>
|
||||
<div class="controls">
|
||||
<textarea ng-model="model.technicalData" type="text" class="input-xxlarge"></textarea>
|
||||
<input ng-model="model.serialNumber" type="text" class="input-large">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">Links</label>
|
||||
<label class="control-label">Purchase Date</label>
|
||||
<div class="controls">
|
||||
<textarea ng-model="model.links" type="text" class="input-xxlarge"></textarea>
|
||||
<input ng-model="model.purchaseDate" datepicker type="text" class="input-small">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">Recommended Parts</label>
|
||||
<label class="control-label">Warranty Expiration</label>
|
||||
<div class="controls">
|
||||
<textarea ng-model="model.partsRecommended" type="text" class="input-xxlarge"></textarea>
|
||||
<input ng-model="model.warrantyExpiration" datepicker type="text" class="input-small">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">Location</label>
|
||||
<div class="controls">
|
||||
<input ng-model="model.location" type="text" class="input-large">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-section">
|
||||
<div class="section-label">Images</div>
|
||||
<div class="section-label">Frequency</div>
|
||||
<div class="section-container">
|
||||
<div class="form-editor">
|
||||
<div class="control-group">
|
||||
<label class="control-label">Images</label>
|
||||
<label class="control-label">Frequency</label>
|
||||
<div class="controls">
|
||||
<div class="dropzone" dropzone="imageOpts"></div>
|
||||
<select ng-model="model.frequencyType" class="input-xlarge">
|
||||
<option value="">None</option>
|
||||
<option value="Anesthesia">Anesthesia</option>
|
||||
<option value="Annual">Annual</option>
|
||||
<option value="DLLR">DLLR</option>
|
||||
<option value="ERT">ERT</option>
|
||||
<option value="Ice Maker">Ice Maker</option>
|
||||
<option value="Imaging">Imaging</option>
|
||||
<option value="Medical Device">Medical Device</option>
|
||||
<option value="Medical Gas Systems">Medical Gas Systems</option>
|
||||
<option value="N2O Trace Gas">N2O Trace Gas</option>
|
||||
<option value="Quarterly">Quarterly</option>
|
||||
<option value="RAE">RAE</option>
|
||||
<option value="Semi">Semi</option>
|
||||
<option value="Sterilizer - Cleaning">Sterilizer - Cleaning</option>
|
||||
<option value="Sterilizer - F">Sterilizer - F</option>
|
||||
<option value="Sterilizer - TT">Sterilizer - TT</option>
|
||||
<option value="Vaporizer">Vaporizer</option>
|
||||
<option value="Waste Management System">Waste Management System</option>
|
||||
<option value="legacy" disabled>legacy</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group" ng-show="model.frequencyType">
|
||||
<label class="control-label">Schedule</label>
|
||||
<div class="controls">
|
||||
<table class="table frequency" style="margin-right: 30px">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>JAN</th>
|
||||
<th>FEB</th>
|
||||
<th>MAR</th>
|
||||
<th>APR</th>
|
||||
<th>MAY</th>
|
||||
<th>JUN</th>
|
||||
<th>JUL</th>
|
||||
<th>AUG</th>
|
||||
<th>SEP</th>
|
||||
<th>OCT</th>
|
||||
<th>NOV</th>
|
||||
<th>DEC</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td ng-repeat="i in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]">
|
||||
<a ng-click="toggleFrequency(i)" class="{{ model.frequencySchedule[i] }}">
|
||||
<i ng-class="{ 'icon-ok': model.frequencySchedule[i], 'icon-remove': !model.frequencySchedule[i] }"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-section">
|
||||
<div class="section-label"> </div>
|
||||
<div class="section-container">
|
||||
<button ng-click="save()" ng-disabled="form.$invalid" type="button" class="btn btn-primary">Save</button>
|
||||
<button ng-click="create()" ng-disabled="form.$invalid || !model.deviceType" type="button" class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -6,75 +6,196 @@
|
||||
<h1>Edit Device</h1>
|
||||
</header>
|
||||
|
||||
<form name="form" class="form">
|
||||
<div class="form-section">
|
||||
<div class="section-label">Device Identification</div>
|
||||
<div class="section-container">
|
||||
<div class="form-editor">
|
||||
<div class="control-group">
|
||||
<label class="control-label">Device Type</label>
|
||||
<div class="controls">
|
||||
<input type="hidden" ng-model="categoryPicker" ui-select2="categoryOpts" />
|
||||
<div ng-hide="loading" class="tabbable">
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active form" title="Details">
|
||||
<form name="form" class="form">
|
||||
<div class="form-section">
|
||||
<div class="section-label">Device Type</div>
|
||||
<div class="section-container">
|
||||
<div class="form-editor">
|
||||
<div class="control-group">
|
||||
<label class="control-label">Category</label>
|
||||
<div class="controls">
|
||||
<input type="hidden" ng-model="deviceTypes.category.picker" ui-select2="deviceTypes.category.opts" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">Make</label>
|
||||
<div class="controls">
|
||||
<input type="hidden" ng-model="deviceTypes.make.picker" ui-select2="deviceTypes.make.opts" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">Model</label>
|
||||
<div class="controls">
|
||||
<input type="hidden" ng-model="deviceTypes.model.picker" ui-select2="deviceTypes.model.opts" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label"></label>
|
||||
<div class="controls">
|
||||
<button ng-click="deviceTypes.reset()" type="button" class="btn">Reset</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">Make</label>
|
||||
<div class="controls">
|
||||
<input type="hidden" ng-model="makePicker" ui-select2="makeOpts" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">Model</label>
|
||||
<div class="controls">
|
||||
<input ng-model="model.model" type="text" class="input-xlarge">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-section">
|
||||
<div class="section-label">Device Details</div>
|
||||
<div class="section-container">
|
||||
<div class="form-editor">
|
||||
<div class="control-group">
|
||||
<label class="control-label">Technical Data</label>
|
||||
<div class="controls">
|
||||
<textarea ng-model="model.technicalData" type="text" class="input-xxlarge"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">Links</label>
|
||||
<div class="controls">
|
||||
<textarea ng-model="model.links" type="text" class="input-xxlarge"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">Recommended Parts</label>
|
||||
<div class="controls">
|
||||
<textarea ng-model="model.partsRecommended" type="text" class="input-xxlarge"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-section">
|
||||
<div class="section-label">Images</div>
|
||||
<div class="section-container">
|
||||
<div class="form-editor">
|
||||
<div class="control-group">
|
||||
<label class="control-label">Images</label>
|
||||
<div class="controls">
|
||||
<div class="dropzone" dropzone="imageOpts" existing="existingImages" prefix="devices/"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-section">
|
||||
<div class="section-label">Identifier</div>
|
||||
<div class="section-container">
|
||||
<div class="form-editor">
|
||||
<div
|
||||
ng-class="{ 'error': form.identifier.$invalid && !form.identifier.$pristine }"
|
||||
class="control-group">
|
||||
|
||||
<div class="form-section">
|
||||
<div class="section-label"> </div>
|
||||
<div class="section-container">
|
||||
<button ng-click="save()" ng-disabled="form.$invalid" type="button" class="btn btn-primary">Save</button>
|
||||
<label class="control-label">Identifier</label>
|
||||
<div class="controls">
|
||||
<input
|
||||
ng-model="model.biomedId"
|
||||
required
|
||||
ab-device-unique="{field: 'biomedId', key: model._id}"
|
||||
type="text"
|
||||
name="identifier"
|
||||
class="input-xlarge">
|
||||
<span ng-show="form.identifier.$error.unique" class="help-inline">Identifier must be unique</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-section">
|
||||
<div class="section-label">Device Details</div>
|
||||
<div class="section-container">
|
||||
<div class="form-editor">
|
||||
<div class="control-group">
|
||||
<label class="control-label">Serial Number</label>
|
||||
<div class="controls">
|
||||
<input ng-model="model.serialNumber" type="text" class="input-large">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">Purchase Date</label>
|
||||
<div class="controls">
|
||||
<input ng-model="model.purchaseDate" datepicker type="text" class="input-small">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">Warranty Expiration</label>
|
||||
<div class="controls">
|
||||
<input ng-model="model.warrantyExpiration" datepicker type="text" class="input-small">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">Location</label>
|
||||
<div class="controls">
|
||||
<input ng-model="model.location" type="text" class="input-large">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-section">
|
||||
<div class="section-label">Frequency</div>
|
||||
<div class="section-container">
|
||||
<div class="form-editor">
|
||||
<div class="control-group">
|
||||
<label class="control-label">Frequency</label>
|
||||
<div class="controls">
|
||||
<select ng-model="model.frequencyType" class="input-xlarge">
|
||||
<option value="">None</option>
|
||||
<option value="Anesthesia">Anesthesia</option>
|
||||
<option value="Annual">Annual</option>
|
||||
<option value="DLLR">DLLR</option>
|
||||
<option value="ERT">ERT</option>
|
||||
<option value="Ice Maker">Ice Maker</option>
|
||||
<option value="Imaging">Imaging</option>
|
||||
<option value="Medical Device">Medical Device</option>
|
||||
<option value="Medical Gas Systems">Medical Gas Systems</option>
|
||||
<option value="N2O Trace Gas">N2O Trace Gas</option>
|
||||
<option value="Quarterly">Quarterly</option>
|
||||
<option value="RAE">RAE</option>
|
||||
<option value="Semi">Semi</option>
|
||||
<option value="Sterilizer - Cleaning">Sterilizer - Cleaning</option>
|
||||
<option value="Sterilizer - F">Sterilizer - F</option>
|
||||
<option value="Sterilizer - TT">Sterilizer - TT</option>
|
||||
<option value="Vaporizer">Vaporizer</option>
|
||||
<option value="Waste Management System">Waste Management System</option>
|
||||
<option value="legacy" disabled>legacy</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group" ng-show="model.frequencyType">
|
||||
<label class="control-label">Schedule</label>
|
||||
<div class="controls">
|
||||
<table class="table frequency" style="margin-right: 30px">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>JAN</th>
|
||||
<th>FEB</th>
|
||||
<th>MAR</th>
|
||||
<th>APR</th>
|
||||
<th>MAY</th>
|
||||
<th>JUN</th>
|
||||
<th>JUL</th>
|
||||
<th>AUG</th>
|
||||
<th>SEP</th>
|
||||
<th>OCT</th>
|
||||
<th>NOV</th>
|
||||
<th>DEC</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td ng-repeat="i in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]">
|
||||
<a ng-click="toggleFrequency(i)" class="{{ model.frequencySchedule[i] }}">
|
||||
<i ng-class="{ 'icon-ok': model.frequencySchedule[i], 'icon-remove': !model.frequencySchedule[i] }"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-section">
|
||||
<div class="section-label"> </div>
|
||||
<div class="section-container">
|
||||
<button ng-click="update()" ng-disabled="form.$invalid || !model.deviceType" type="button" class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="tab-pane form" title="Test Runs">
|
||||
<div class="row-fluid">
|
||||
<div class="span12">
|
||||
<a class="btn" href="/testRuns/add?deviceId={{model._id}}" ng-show="accountHasPermission('system.edit')">New Test Run</a>
|
||||
|
||||
<table class="biomed-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 7%">Run</th>
|
||||
<th style="width: 6%">Result</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-hide="testRuns.length"><td colspan="11" class="table-message">There is no information to display.</td></tr>
|
||||
<tr ng-repeat="testRun in testRuns">
|
||||
<td><a href="/testRuns/{{testRun._id}}">{{testRun.date | date}}</a></td>
|
||||
<td>
|
||||
<span ng-if="testRun.result">Passed</span>
|
||||
<span ng-if="!testRun.result">Failed</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -1,41 +0,0 @@
|
||||
<ul class="breadcrumb">
|
||||
<li><a href="/devices"><i class="icon-briefcase"></i> Devices</a><li>
|
||||
</ul>
|
||||
<header>
|
||||
<h1>Devices</h1>
|
||||
</header>
|
||||
|
||||
<div class="row-fluid">
|
||||
<div class="span12">
|
||||
<div class="toolbelt">
|
||||
<a href="/devices/add" class="btn btn-primary" ng-show="accountHasPermission('system.edit')">Create new Device</a>
|
||||
<div class="pull-right">
|
||||
<span class="toolbelt-text">Search:</span>
|
||||
<div class="input-append">
|
||||
<input type="text" ng-model="query" class="input-large" placeholder="Search">
|
||||
<span class="add-on"><i class="icon-search"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<table class="biomed-table" infinite-scroll="addItems()" can-load="canLoad" threshold="300">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 33%" ng-class="selectedCls('category')" ng-click="changeSorting('category')">Device Type</th>
|
||||
<th style="width: 33%" ng-class="selectedCls('make')" ng-click="changeSorting('make')">Make</th>
|
||||
<th style="width: 33%" ng-class="selectedCls('model')" ng-click="changeSorting('model')">Model</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-show="loading"><td colspan="4" class="table-loading"><i class="loader"></i></td></tr>
|
||||
<tr ng-hide="loading || devices.length"><td colspan="4" class="table-message">There is no information to display.</td></tr>
|
||||
<tr ng-hide="loading" ng-repeat="device in devices">
|
||||
<td>{{device.category}}</td>
|
||||
<td>{{device.make}}</td>
|
||||
<td>{{device.model}}</td>
|
||||
<td><a href="/devices/{{device._id}}">Edit</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
70
public/partials/testRuns/add.html
Normal file
70
public/partials/testRuns/add.html
Normal file
@ -0,0 +1,70 @@
|
||||
<ul class="breadcrumb">
|
||||
<li><a href="/checkLists"><i class="icon-briefcase"></i> Check Lists</a><span class="divider"></span><li>
|
||||
<li class="active">New Test Run</li>
|
||||
</ul>
|
||||
<header>
|
||||
<h1>New Test Run</h1>
|
||||
</header>
|
||||
<form name="form" class="form">
|
||||
<div class="form-section" ng-repeat="field in model.fields">
|
||||
<div class="section-label">{{$index + 1}}.</div>
|
||||
<div class="section-container {{field.result}}">
|
||||
<div class="form-editor">
|
||||
<div class="control-group">
|
||||
<label class="control-label">Test</label>
|
||||
<div class="controls">
|
||||
<p>{{field.label}}</p>
|
||||
<p ng-if="field.type == 'range'">Passing Values: {{field.min}} - {{field.max}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group" ng-if="field.type == 'boolean'">
|
||||
<label class="control-label">Result</label>
|
||||
<div class="controls">
|
||||
<select ng-model="field.value" class="input-xlarge" required>
|
||||
<option value="true">Pass</option>
|
||||
<option value="false">Fail</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group" ng-if="field.type == 'range'">
|
||||
<label class="control-label">Result</label>
|
||||
<div class="controls">
|
||||
<input ng-model="field.value" type="number" class="input-xlarge" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">Comments</label>
|
||||
<div class="controls">
|
||||
<textarea ng-model="field.comments" type="text" class="input-xxlarge" ng-required="!field.result"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-section">
|
||||
<div class="section-label">Summary</div>
|
||||
<div class="section-container">
|
||||
<div class="form-editor">
|
||||
<div class="control-group">
|
||||
<label class="control-label">Result</label>
|
||||
<div class="controls">
|
||||
<span ng-if="model.result">Passed</span>
|
||||
<span ng-if="!model.result">Failed</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">Comments</label>
|
||||
<div class="controls">
|
||||
<textarea ng-model="model.comments" class="input-xlarge"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-section">
|
||||
<div class="section-label"> </div>
|
||||
<div class="section-container">
|
||||
<button ng-click="save()" ng-disabled="form.$invalid" type="button" class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
65
public/partials/testRuns/view.html
Normal file
65
public/partials/testRuns/view.html
Normal file
@ -0,0 +1,65 @@
|
||||
<ul class="breadcrumb">
|
||||
<li class="active">Test Results</li>
|
||||
</ul>
|
||||
<header>
|
||||
<h1>Test Results</h1>
|
||||
</header>
|
||||
<form name="form" class="form">
|
||||
<div class="form-section" ng-repeat="field in model.fields">
|
||||
<div class="section-label">{{$index + 1}}.</div>
|
||||
<div class="section-container {{field.result}}">
|
||||
<div class="form-editor">
|
||||
<div class="control-group">
|
||||
<label class="control-label">Test</label>
|
||||
<div class="controls">
|
||||
<p>{{field.label}}</p>
|
||||
<p ng-if="field.type == 'range'">Passing Values: {{field.min}} - {{field.max}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group" ng-if="field.type == 'boolean'">
|
||||
<label class="control-label">Result</label>
|
||||
<div class="controls">
|
||||
<p style="padding-top: 5px">
|
||||
<span ng-if="field.value" style="color: green">Passed</span>
|
||||
<span ng-if="!field.value" style="color: red">Failed</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group" ng-if="field.type == 'range'">
|
||||
<label class="control-label">Result</label>
|
||||
<div class="controls">
|
||||
<p style="padding-top: 5px">{{field.value}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">Comments</label>
|
||||
<div class="controls">
|
||||
<p style="padding-top: 5px">{{field.comments}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-section">
|
||||
<div class="section-label">Summary</div>
|
||||
<div class="section-container">
|
||||
<div class="form-editor">
|
||||
<div class="control-group">
|
||||
<label class="control-label">Result</label>
|
||||
<div class="controls">
|
||||
<p style="padding-top: 5px">
|
||||
<span ng-if="model.result" style="color: green">Passed</span>
|
||||
<span ng-if="!model.result" style="color: red">Failed</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">Comments</label>
|
||||
<div class="controls">
|
||||
<p>{{model.comments}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
@ -116,6 +116,23 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-section" ng-show="workorderType != 'shipment'">
|
||||
<div class="section-label">Devices</div>
|
||||
<div class="section-container">
|
||||
<div class="form-editor">
|
||||
<div class="control-group">
|
||||
<label class="control-label">Devices</label>
|
||||
<div class="controls">
|
||||
<select multiple ui-select2 ng-model="model.devices" data-placeholder="Choose Devices(s)" class="input-xxlarge">
|
||||
<option ng-repeat="device in devices" value="{{device._id}}">
|
||||
{{device.biomedId}} - {{device.deviceType.make}} {{device.deviceType.model}}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-section" ng-show="workorderType != 'shipment'">
|
||||
<div class="section-label">Scheduling</div>
|
||||
<div class="section-container">
|
||||
|
@ -121,6 +121,33 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-section" ng-show="master.reason != 'Shipment'">
|
||||
<div class="section-label">Devices</div>
|
||||
<div class="section-container">
|
||||
<div ng-hide="devices.visible" class="form-preview">
|
||||
<span>Devices</span>: <strong>{{master.devices.length}}</strong><br>
|
||||
<a ng-click="devices.edit()" ng-show="accountHasPermission('system.edit')">Edit</a>
|
||||
</div>
|
||||
<div ng-show="devices.visible" class="form-editor">
|
||||
<div class="control-group">
|
||||
<label class="control-label"><span>Devices</span></label>
|
||||
<div class="controls">
|
||||
{{devices.devices}}
|
||||
<select multiple ui-select2 ng-model="devices.model.devices" data-placeholder="Choose Devices(s)" class="input-xxlarge">
|
||||
<option ng-repeat="device in allDevices" value="{{device._id}}">
|
||||
{{device.biomedId}} - {{device.deviceType.make}} {{device.deviceType.model}}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button ng-click="devices.save(false)" type="button" class="btn btn-primary">Save</button>
|
||||
<button ng-click="devices.save(true)" type="button" class="btn">Save & Notify</button>
|
||||
<button ng-click="devices.reset()" type="button" class="btn">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-section" ng-show="master.reason != 'Shipment'">
|
||||
<div class="section-label">Scheduling</div>
|
||||
<div class="section-container">
|
||||
|
124
server.js
124
server.js
@ -1,40 +1,36 @@
|
||||
|
||||
var PUSHOVER_ENABLED = true;
|
||||
|
||||
var env = 'prod';
|
||||
|
||||
var pushover = require('pushover-notifications');
|
||||
|
||||
var express = require('express')
|
||||
fs = require('fs'),
|
||||
passport = require('passport');
|
||||
|
||||
var env = 'prod',
|
||||
config = require('./config/config')[env],
|
||||
mongoose = require('mongoose'),
|
||||
Promise = require('bluebird');
|
||||
|
||||
var express = require('express');
|
||||
var fs = require('fs');
|
||||
var passport = require('passport');
|
||||
var config = require('./config/config')[env];
|
||||
var mongoose = require('mongoose');
|
||||
var Promise = require('bluebird');
|
||||
var log = require('log4node');
|
||||
|
||||
Promise.promisifyAll(mongoose);
|
||||
|
||||
process.on('uncaughtException', function(err) {
|
||||
console.log('Uncaught Exception:', err);
|
||||
console.log(err.stack);
|
||||
var pushoverApi = new pushover({
|
||||
user: 'aJmPD4KigO0vLwim76n3WqWKwbKA3k',
|
||||
token: 'YxspDLz3WinbPmwBThuZXCME9QmkDb'
|
||||
});
|
||||
|
||||
var p = new pushover({
|
||||
user: 'aJmPD4KigO0vLwim76n3WqWKwbKA3k',
|
||||
token: 'YxspDLz3WinbPmwBThuZXCME9QmkDb'
|
||||
});
|
||||
process.on('uncaughtException', function(err) {
|
||||
console.log('Uncaught Exception:', err);
|
||||
console.log(err.stack);
|
||||
|
||||
var message = {
|
||||
title: 'Unhandled error in portal',
|
||||
message: 'Process was reset on ' + new Date(),
|
||||
sound: 'falling'
|
||||
};
|
||||
p.send(message, function(err, result) {
|
||||
if (err) {
|
||||
log.emergency('Error while sending pushover notification');
|
||||
log.emergency(err);
|
||||
}
|
||||
process.exit(1);
|
||||
});
|
||||
});
|
||||
sendPushOver(
|
||||
'Unhandled error in portal',
|
||||
'Process was reset on ' + new Date(),
|
||||
'falling',
|
||||
function() {
|
||||
process.exit(1);
|
||||
});
|
||||
});
|
||||
|
||||
log.info("----- Server Started -----");
|
||||
|
||||
@ -45,15 +41,15 @@ mongoose.connect(config.database);
|
||||
// bootstrap model
|
||||
var modelPath = __dirname + '/app/model'
|
||||
fs.readdirSync(modelPath).forEach(function (file) {
|
||||
require(modelPath + '/' + file)
|
||||
require(modelPath + '/' + file)
|
||||
})
|
||||
|
||||
require('./config/passport')(passport, config);
|
||||
|
||||
var app = express(),
|
||||
http = require('http'),
|
||||
server = http.createServer(app),
|
||||
io = require('socket.io').listen(server);
|
||||
http = require('http'),
|
||||
server = http.createServer(app),
|
||||
io = require('socket.io').listen(server);
|
||||
|
||||
// Configure piler
|
||||
var piler = require('./config/piler')(app, server, io, config);
|
||||
@ -75,35 +71,43 @@ GLOBAL.health = 'OK'
|
||||
var port = process.env.PORT || 9000
|
||||
|
||||
server.on('error', function(e) {
|
||||
if (e.code == 'EADDRINUSE') {
|
||||
console.log('Address in use, retrying...');
|
||||
setTimeout(function() {
|
||||
server.close();
|
||||
server.listen(port, onListen);
|
||||
}, 1000);
|
||||
}
|
||||
if (e.code == 'EADDRINUSE') {
|
||||
console.log('Address in use, retrying...');
|
||||
setTimeout(function() {
|
||||
server.close();
|
||||
server.listen(port, onListen);
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
server.listen(port, onListen);
|
||||
|
||||
|
||||
|
||||
function onListen() {
|
||||
var p = new pushover({
|
||||
user: 'aJmPD4KigO0vLwim76n3WqWKwbKA3k',
|
||||
token: 'YxspDLz3WinbPmwBThuZXCME9QmkDb'
|
||||
});
|
||||
|
||||
var message = {
|
||||
title: 'Portal is running',
|
||||
message: 'Process was reset on ' + new Date(),
|
||||
sound: 'bugle'
|
||||
};
|
||||
p.send(message, function(err, result) {
|
||||
if (err) {
|
||||
log.emergency('Error while sending pushover notification');
|
||||
log.emergency(err);
|
||||
}
|
||||
});
|
||||
sendPushOver(
|
||||
'Portal is running',
|
||||
'Process was reset on ' + new Date(),
|
||||
'bugle');
|
||||
}
|
||||
|
||||
function sendPushOver(title, message, sound, callback) {
|
||||
if (!PUSHOVER_ENABLED) {
|
||||
return;
|
||||
}
|
||||
|
||||
var data = {
|
||||
title: title,
|
||||
message: message,
|
||||
sound: sound
|
||||
};
|
||||
|
||||
pushoverApi.send(data, function(err, result) {
|
||||
if (err) {
|
||||
log.emergency('Error while sending pushover notification');
|
||||
log.emergency(err);
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
Reference in New Issue
Block a user