Basic node management in admin panel.
This commit is contained in:
parent
6cab6371d1
commit
d5c69fa78f
10 changed files with 180 additions and 38 deletions
admin
server
shared/validation
|
@ -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
4
admin/js/app.js
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
angular.module('ffffng', []);
|
||||||
|
angular.module('ffffngAdmin', ['ng-admin', 'ffffng']);
|
|
@ -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
1
admin/js/validation
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../../shared/validation
|
|
@ -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);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -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);
|
||||||
},
|
},
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue