'use strict'; angular.module('ffffngAdmin').config(function(NgAdminConfigurationProvider, RestangularProvider, Constraints, config) { RestangularProvider.addFullRequestInterceptor(function(element, operation, what, url, headers, params) { if (operation === 'getList') { if (params._filters) { // flatten filter query params for (var filter in params._filters) { params[filter] = params._filters[filter]; } delete params._filters; } } return { params: params }; }); function nullable(value) { return value ? value : 'N/A'; } function formatMoment(unix) { 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 admin = nga.application('Knotenverwaltung - Admin-Panel'); admin .header( '<div class="navbar-header">' + '<a class="navbar-brand" href="#" ng-click="appController.displayHome()">' + 'Knotenverwaltung - Admin-Panel <small style="font-size: 0.7em;">(<fa-version></fa-version>)</small>' + '</a>' + '</div>' + '<p class="navbar-text navbar-right">' + '<a href="https://github.com/freifunkhamburg/ffffng/issues" target="_blank">' + '<i class="fa fa-bug" aria-hidden="true"></i> Report Error' + '</a>' + '</p>' + '<p class="navbar-text navbar-right">' + '<a href="https://github.com/freifunkhamburg/ffffng" target="_blank">' + '<i class="fa fa-code" aria-hidden="true"></i> Source Code' + '</a>' + '</p>' + '<p class="navbar-text navbar-right">' + '<a href="/" target="_blank">' + '<i class="fa fa-external-link" aria-hidden="true"></i> Frontend' + '</a>' + '</p>' ) .baseApiUrl('/internal/api/') .debug(true); function nodeClasses(node) { if (!node) { return; } switch (node.values.onlineState) { case 'ONLINE': return 'node-online'; case 'OFFLINE': return 'node-offline'; default: return; } } var nodes = nga.entity('nodes').label('Nodes').identifier(nga.field('token')); nodes .listView() .title('Nodes') .perPage(30) .sortDir('ASC') .sortField('hostname') .actions([]) .batchActions([]) .exportFields([]) .fields([ nga.field('hostname').cssClasses(nodeClasses), nga.field('nickname').cssClasses(nodeClasses), nga.field('email').cssClasses(nodeClasses), nga.field('token').cssClasses(nodeClasses), nga.field('mac').cssClasses(nodeClasses), nga.field('key').label('VPN').cssClasses(nodeClasses).template(function (node) { return node.values.key ? '<i class="fa fa-lock vpn-key-set" aria-hidden="true" title="VPN key set"></i>' : '<i class="fa fa-times vpn-key-unset" aria-hidden="true" title="no VPN key"></i>'; }), nga.field('coords').label('GPS').cssClasses(nodeClasses).template(function (node) { return node.values.coords ? '<i class="fa fa-map-marker coords-set" aria-hidden="true" title="coordinates set"></i>' : '<i class="fa fa-times coords-unset" aria-hidden="true" title="no coordinates"></i>'; }), nga.field('onlineState').map(nullable).cssClasses(nodeClasses), nga.field('monitoringState').cssClasses(nodeClasses).template(function (node) { switch (node.values.monitoringState) { case 'active': return '<i class="fa fa-heartbeat monitoring-active" title="active"></i>'; case 'pending': return '<i class="fa fa-envelope monitoring-confirmation-pending" title="confirmation pending"></i>'; default: return '<i class="fa fa-times monitoring-disabled" title="disabled"></i>'; } }) ]) .filters([ nga.field('q') .label('') .pinned(true) .template( '<div class="input-group">' + '<input type="text" ng-model="value" placeholder="Search" class="form-control"></input>' + '<span class="input-group-addon"><i class="fa fa-search"></i></span></div>'), ]) .listActions( '<ma-edit-button entry="entry" entity="entity" size="sm"></ma-edit-button> ' + '<ma-delete-button entry="entry" entity="entity" size="sm"></ma-delete-button> ' + '<form style="display: inline-block" action="/#/update" method="POST" target="_blank">' + '<input type="hidden" name="token" value="{{entry.values.token}}"/>' + '<button class="btn btn-primary btn-sm" type="submit"><i class="fa fa-external-link"></i> Open</button>' + '</form> ' + '<a class="btn btn-success btn-sm" href="' + config.map.mapUrl + '/#!v:m;n:{{entry.values.mapId}}" target="_blank"><i class="fa fa-map-o"></i> Map</a>' ) ; 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 monitoringStateClasses(monitoringState) { if (!monitoringState) { return; } switch (monitoringState.values.state) { case 'ONLINE': return 'monitoring-state-online'; case 'OFFLINE': return 'monitoring-state-offline'; default: return; } } var monitoringStates = nga.entity('monitoring').label('Monitoring'); monitoringStates .listView() .title('Monitoring') .perPage(30) .sortDir('ASC') .sortField('id') .actions([]) .batchActions([]) .exportFields([]) .fields([ nga.field('id').cssClasses(monitoringStateClasses), nga.field('hostname').cssClasses(monitoringStateClasses), nga.field('mac').cssClasses(monitoringStateClasses), nga.field('monitoring_state').cssClasses(monitoringStateClasses).template(function (monitoringState) { switch (monitoringState.values.monitoring_state) { case 'active': return '<i class="fa fa-heartbeat monitoring-active" title="active"></i>'; case 'pending': return '<i class="fa fa-envelope monitoring-confirmation-pending" title="confirmation pending"></i>'; default: return '<i class="fa fa-times monitoring-disabled" title="disabled"></i>'; } }), nga.field('state').cssClasses(monitoringStateClasses), nga.field('last_seen').map(formatMoment).cssClasses(monitoringStateClasses), nga.field('import_timestamp').label('Imported').map(formatMoment).cssClasses(monitoringStateClasses), nga.field('last_status_mail_type').map(nullable).cssClasses(monitoringStateClasses), nga.field('last_status_mail_sent').map(formatMoment).cssClasses(monitoringStateClasses), nga.field('created_at').map(formatMoment).cssClasses(monitoringStateClasses), nga.field('modified_at').map(formatMoment).cssClasses(monitoringStateClasses) ]) .filters([ nga.field('q') .label('') .pinned(true) .template( '<div class="input-group">' + '<input type="text" ng-model="value" placeholder="Search" class="form-control"></input>' + '<span class="input-group-addon"><i class="fa fa-search"></i></span></div>'), ]) .listActions( '<a class="btn btn-success btn-sm" href="' + config.map.mapUrl + '/#!v:m;n:{{entry.values.mapId}}" target="_blank"><i class="fa fa-map-o"></i> Map</a>' ) ; admin.addEntity(monitoringStates); function mailClasses(mail) { if (!mail) { return; } var failures = mail.values.failures; if (failures === 0) { return 'mails-pending'; } if (failures >= 5) { return 'mails-failed-max'; } return 'mails-failed'; } var mails = nga.entity('mails').label('Mail-Queue'); mails .listView() .title('Mail-Queue') .perPage(30) .sortDir('ASC') .sortField('id') .actions([]) .batchActions([]) .exportFields([]) .fields([ nga.field('id').cssClasses(mailClasses), nga.field('failures').cssClasses(mailClasses), nga.field('sender').cssClasses(mailClasses), nga.field('recipient').cssClasses(mailClasses), nga.field('email').cssClasses(mailClasses), nga.field('created_at').map(formatMoment).cssClasses(mailClasses), nga.field('modified_at').map(formatMoment).cssClasses(mailClasses) ]) .filters([ nga.field('q') .label('') .pinned(true) .template( '<div class="input-group">' + '<input type="text" ng-model="value" placeholder="Search" class="form-control"></input>' + '<span class="input-group-addon"><i class="fa fa-search"></i></span></div>'), ]) .listActions( '<fa-mail-action-button disabled="entry.values.failures === 0" action="reset" icon="refresh" label="Retry" mail="entry" button="success" label="run" size="sm"></fa-mail-action-button> ' + '<ma-delete-button entry="entry" entity="entity" size="sm"></ma-delete-button>' ) ; admin.addEntity(mails); function taskClasses(field) { return function(task) { if (!task) { return; } return 'task-' + field + ' ' + (task.values.enabled ? 'task-enabled' : 'task-disabled'); }; } var tasks = nga.entity('tasks').label('Background-Jobs'); tasks .listView() .title('Background-Jobs') .perPage(30) .sortDir('ASC') .sortField('id') .actions([]) .batchActions([]) .exportFields([]) .fields([ nga.field('id').cssClasses(taskClasses('id')), nga.field('name').cssClasses(taskClasses('name')), nga.field('description').cssClasses(taskClasses('description')), nga.field('schedule').cssClasses(taskClasses('schedule')), nga.field('state').cssClasses(taskClasses('state')), nga.field('runningSince').map(formatMoment).cssClasses(taskClasses('runningSince')), nga.field('lastRunStarted').map(formatMoment).cssClasses(taskClasses('lastRunStarted')) ]) .filters([ nga.field('q') .label('') .pinned(true) .template( '<div class="input-group">' + '<input type="text" ng-model="value" placeholder="Search" class="form-control"></input>' + '<span class="input-group-addon"><i class="fa fa-search"></i></span></div>'), ]) .listActions( '<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="power-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="power-off" task="entry" label="disable" size="sm"></fa-task-action-button>' ) ; admin.addEntity(tasks); admin.menu( nga.menu() .addChild(nga .menu(nodes) .icon('<i class="fa fa-dot-circle-o"></i>') ) .addChild(nga .menu(monitoringStates) .icon('<span class="fa fa-heartbeat"></span>') ) .addChild(nga .menu(mails) .icon('<span class="fa fa-envelope"></span>') ) .addChild(nga .menu(tasks) .icon('<span class="fa fa-cog"></span>') ) .addChild(nga .menu() .template( '<a href="/internal/logs" target="_blank">' + '<span class="fa fa-list"></span> Logs' + '</a>' ) ) ); nga.configure(admin); });