Basic node management in admin panel.

This commit is contained in:
baldo 2016-06-07 00:21:26 +02:00
parent 6cab6371d1
commit d5c69fa78f
10 changed files with 180 additions and 38 deletions

View file

@ -13,6 +13,18 @@
color: lightcoral; color: lightcoral;
text-decoration: line-through; text-decoration: line-through;
} }
.monitoring-active {
color: green;
}
.monitoring-confirmation-pending {
color: lightcoral;
}
.monitoring-disabled {
color: grey;
}
</style> </style>
</head> </head>
<body ng-app="ffffngAdmin"> <body ng-app="ffffngAdmin">
@ -20,7 +32,9 @@
<script src="js/moment-with-locales.min.js"></script> <script src="js/moment-with-locales.min.js"></script>
<script src="js/ng-admin.min.js"></script> <script src="js/ng-admin.min.js"></script>
<script src="js/app.js"></script>
<script src="js/validation/constraints.js"></script>
<script src="js/views/taskActionButton.js"></script>
<script src="js/config.js"></script> <script src="js/config.js"></script>
<script src="js/taskActionButton.js"></script>
</body> </body>
</html> </html>

4
admin/js/app.js Normal file
View file

@ -0,0 +1,4 @@
'use strict';
angular.module('ffffng', []);
angular.module('ffffngAdmin', ['ng-admin', 'ffffng']);

View file

@ -1,12 +1,21 @@
'use strict'; 'use strict';
var myApp = angular.module('ffffngAdmin', ['ng-admin']); angular.module('ffffngAdmin').config(function(NgAdminConfigurationProvider, Constraints) {
myApp.config(['NgAdminConfigurationProvider', function(NgAdminConfigurationProvider) {
function formatMoment(unix) { function formatMoment(unix) {
return unix ? moment.unix(unix).fromNow() : 'N/A'; return unix ? moment.unix(unix).fromNow() : 'N/A';
} }
function nodeConstraint(field) {
var constraint = Constraints.node[field];
var result = {
required: !constraint.optional
};
if (constraint.type === 'string') {
result.pattern = constraint.regex;
}
return result;
}
var nga = NgAdminConfigurationProvider; var nga = NgAdminConfigurationProvider;
var admin = nga.application('Knotenverwaltung - Admin-Panel'); var admin = nga.application('Knotenverwaltung - Admin-Panel');
@ -14,6 +23,64 @@ myApp.config(['NgAdminConfigurationProvider', function(NgAdminConfigurationProvi
.baseApiUrl('/internal/api/') .baseApiUrl('/internal/api/')
.debug(true); .debug(true);
function nodeClasses(node) {
if (!node) {
return;
}
return;
}
var nodes = nga.entity('nodes').label('Nodes').identifier(nga.field('token'));
nodes
.listView()
.title('Nodes')
.actions([])
.batchActions([])
.exportFields([])
.fields([
nga.field('token').cssClasses(nodeClasses),
nga.field('hostname').cssClasses(nodeClasses),
nga.field('monitoring').cssClasses(nodeClasses).template(function (node) {
if (!node.values.monitoring) {
return '<span class="glyphicon glyphicon-remove monitoring-disabled" title="disabled"></span>';
}
return node.values.monitoringConfirmed
? '<span class="glyphicon glyphicon-ok monitoring-active" title="active"></span>'
: '<span class="glyphicon glyphicon-envelope monitoring-confirmation-pending" ' +
'title="confirmation pending"></span>';
})
])
.listActions([
'edit',
'delete'
])
;
nodes
.editionView()
.title('Edit node')
.actions(['list', 'delete'])
.addField(nga.field('token').editable(false))
.addField(nga.field('hostname').label('Name').validation(nodeConstraint('hostname')))
.addField(nga.field('key').label('Key').validation(nodeConstraint('key')))
.addField(nga.field('mac').label('MAC').validation(nodeConstraint('mac')))
.addField(nga.field('coords').label('GPS').validation(nodeConstraint('coords')))
.addField(nga.field('nickname').validation(nodeConstraint('nickname')))
.addField(nga.field('email').validation(nodeConstraint('email')))
.addField(nga.field('monitoring', 'boolean').validation(nodeConstraint('monitoring')))
.addField(nga.field('monitoringConfirmed').label('Monitoring confirmation').editable(false).map(
function (monitoringConfirmed, node) {
if (!node.monitoring) {
return 'N/A';
}
return monitoringConfirmed ? 'confirmed' : 'pending';
}
))
;
admin.addEntity(nodes);
function taskClasses(task) { function taskClasses(task) {
if (!task) { if (!task) {
return; return;
@ -40,13 +107,17 @@ myApp.config(['NgAdminConfigurationProvider', function(NgAdminConfigurationProvi
'<fa-task-action-button action="run" task="entry" button="primary" label="run" size="sm"></fa-task-action-button> ' + '<fa-task-action-button action="run" task="entry" button="primary" label="run" size="sm"></fa-task-action-button> ' +
'<fa-task-action-button ng-if="!entry.values.enabled" button="success" action="enable" icon="off" task="entry" label="enable" size="sm"></fa-task-action-button> ' + '<fa-task-action-button ng-if="!entry.values.enabled" button="success" action="enable" icon="off" task="entry" label="enable" size="sm"></fa-task-action-button> ' +
'<fa-task-action-button ng-if="entry.values.enabled" button="warning" action="disable" icon="off" task="entry" label="disable" size="sm"></fa-task-action-button>' '<fa-task-action-button ng-if="entry.values.enabled" button="warning" action="disable" icon="off" task="entry" label="disable" size="sm"></fa-task-action-button>'
) )
; ;
admin.addEntity(tasks); admin.addEntity(tasks);
admin.menu( admin.menu(
nga.menu() nga.menu()
.addChild(nga
.menu(nodes)
.icon('<span class="glyphicon glyphicon-record"></span>')
)
.addChild(nga .addChild(nga
.menu(tasks) .menu(tasks)
.icon('<span class="glyphicon glyphicon-cog"></span>') .icon('<span class="glyphicon glyphicon-cog"></span>')
@ -62,4 +133,4 @@ myApp.config(['NgAdminConfigurationProvider', function(NgAdminConfigurationProvi
); );
nga.configure(admin); nga.configure(admin);
}]); });

1
admin/js/validation Symbolic link
View file

@ -0,0 +1 @@
../../shared/validation

View file

@ -92,6 +92,18 @@ angular.module('ffffng').factory('NodeResource', function (
} }
return Resources.success(res, node); return Resources.success(res, node);
}); });
},
getAll: function (req, res) {
// TODO: Paging + Sort + Filter
return NodeService.getAllNodes(function (err, nodes) {
if (err) {
return Resources.error(res, err);
}
return Resources.success(res, nodes);
});
} }
}; };
}); });

View file

@ -20,6 +20,11 @@ angular.module('ffffng').factory('Router', function (
app.put('/internal/api/tasks/run/:id', TaskResource.run); app.put('/internal/api/tasks/run/:id', TaskResource.run);
app.put('/internal/api/tasks/enable/:id', TaskResource.enable); app.put('/internal/api/tasks/enable/:id', TaskResource.enable);
app.put('/internal/api/tasks/disable/:id', TaskResource.disable); app.put('/internal/api/tasks/disable/:id', TaskResource.disable);
app.put('/internal/api/nodes/:token', NodeResource.update);
app.delete('/internal/api/nodes/:token', NodeResource.delete);
app.get('/internal/api/nodes', NodeResource.getAll);
app.get('/internal/api/nodes/:token', NodeResource.get);
} }
}; };
}); });

View file

@ -4,6 +4,7 @@ angular.module('ffffng')
.service('NodeService', function ( .service('NodeService', function (
config, config,
_, _,
async,
crypto, crypto,
fs, fs,
glob, glob,
@ -13,6 +14,8 @@ angular.module('ffffng')
ErrorTypes, ErrorTypes,
UrlBuilder UrlBuilder
) { ) {
var MAX_PARALLEL_NODES_PARSING = 10;
var linePrefixes = { var linePrefixes = {
hostname: '# Knotenname: ', hostname: '# Knotenname: ',
nickname: '# Ansprechpartner: ', nickname: '# Ansprechpartner: ',
@ -366,6 +369,24 @@ angular.module('ffffng')
deleteNodeFile(token, callback); deleteNodeFile(token, callback);
}, },
getAllNodes: function (callback) {
var files = findNodeFiles({});
async.mapLimit(
files,
MAX_PARALLEL_NODES_PARSING,
parseNodeFile,
function (err, nodes) {
if (err) {
Logger.tag('nodes').error('Error getting all nodes:', error);
return callback({data: 'Internal error.', type: ErrorTypes.internalError});
}
return callback(null, nodes);
}
);
},
findNodeDataByMac: function (mac, callback) { findNodeDataByMac: function (mac, callback) {
return findNodeDataByFilePattern({ mac: mac }, callback); return findNodeDataByFilePattern({ mac: mac }, callback);
}, },

View file

@ -6,6 +6,10 @@ angular.module('ffffng').factory('Validator', function (_) {
return acceptUndefined || constraint.optional; return acceptUndefined || constraint.optional;
} }
if (constraint.type === 'boolean') {
return _.isBoolean(value);
}
if (!_.isString(value)) { if (!_.isString(value)) {
return false; return false;
} }

View file

@ -1,40 +1,50 @@
'use strict'; 'use strict';
angular.module('ffffng').factory('Constraints', function () { angular.module('ffffng').constant('Constraints', {
return { id:{
id:{ type: 'string',
regex: /^[1-9][0-9]*/, regex: /^[1-9][0-9]*/,
optional: false
},
token:{
type: 'string',
regex: /^[0-9a-f]{16}$/i,
optional: false
},
node: {
hostname: {
type: 'string',
regex: /^[-a-z0-9_]{1,32}$/i,
optional: false optional: false
}, },
token:{ key: {
regex: /^[0-9a-f]{16}$/i, type: 'string',
regex: /^([a-f0-9]{64})$/i,
optional: true
},
email: {
type: 'string',
regex: /^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i,
optional: false optional: false
}, },
node: { nickname: {
hostname: { type: 'string',
regex: /^[-a-z0-9_]{1,32}$/i, regex: /^[-a-z0-9_ äöüß]{1,64}$/i,
optional: false optional: false
}, },
key: { mac: {
regex: /^([a-f0-9]{64})$/i, type: 'string',
optional: true regex: /^([a-f0-9]{12}|([a-f0-9]{2}:){5}[a-f0-9]{2})$/i,
}, optional: false
email: { },
regex: /^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i, coords: {
optional: false type: 'string',
}, regex: /^(-?[0-9]{1,3}(\.[0-9]{1,15})? -?[0-9]{1,3}(\.[0-9]{1,15})?)$/,
nickname: { optional: true
regex: /^[-a-z0-9_ äöüß]{1,64}$/i, },
optional: false monitoring: {
}, type: 'boolean',
mac: { optional: false
regex: /^([a-f0-9]{12}|([a-f0-9]{2}:){5}[a-f0-9]{2})$/i,
optional: false
},
coords: {
regex: /^(-?[0-9]{1,3}(\.[0-9]{1,15})? -?[0-9]{1,3}(\.[0-9]{1,15})?)$/,
optional: true
}
} }
}; }
}); });