2016-06-04 14:58:11 +02:00
|
|
|
'use strict';
|
|
|
|
|
2016-06-07 14:08:04 +02:00
|
|
|
angular.module('ffffngAdmin').config(function(NgAdminConfigurationProvider, RestangularProvider, Constraints, config) {
|
2016-06-07 12:39:52 +02:00
|
|
|
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 };
|
|
|
|
});
|
|
|
|
|
2016-06-26 19:09:12 +02:00
|
|
|
function nullable(value) {
|
|
|
|
return value ? value : 'N/A';
|
|
|
|
}
|
|
|
|
|
2016-06-04 14:58:11 +02:00
|
|
|
function formatMoment(unix) {
|
|
|
|
return unix ? moment.unix(unix).fromNow() : 'N/A';
|
|
|
|
}
|
|
|
|
|
2016-06-07 00:21:26 +02:00
|
|
|
function nodeConstraint(field) {
|
|
|
|
var constraint = Constraints.node[field];
|
|
|
|
var result = {
|
|
|
|
required: !constraint.optional
|
|
|
|
};
|
|
|
|
if (constraint.type === 'string') {
|
|
|
|
result.pattern = constraint.regex;
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2016-06-04 14:58:11 +02:00
|
|
|
var nga = NgAdminConfigurationProvider;
|
|
|
|
var admin = nga.application('Knotenverwaltung - Admin-Panel');
|
|
|
|
|
|
|
|
admin
|
2016-06-26 20:21:45 +02:00
|
|
|
.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">' +
|
2016-06-26 22:00:58 +02:00
|
|
|
'<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">' +
|
2016-06-26 20:21:45 +02:00
|
|
|
'<a href="https://github.com/freifunkhamburg/ffffng" target="_blank">' +
|
2016-06-26 22:00:58 +02:00
|
|
|
'<i class="fa fa-code" aria-hidden="true"></i> Source Code' +
|
2016-06-26 20:21:45 +02:00
|
|
|
'</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>'
|
|
|
|
)
|
2016-06-04 14:58:11 +02:00
|
|
|
.baseApiUrl('/internal/api/')
|
|
|
|
.debug(true);
|
|
|
|
|
2016-06-07 00:21:26 +02:00
|
|
|
function nodeClasses(node) {
|
|
|
|
if (!node) {
|
|
|
|
return;
|
|
|
|
}
|
2016-06-26 19:09:12 +02:00
|
|
|
|
|
|
|
switch (node.values.onlineState) {
|
|
|
|
case 'ONLINE':
|
|
|
|
return 'node-online';
|
|
|
|
|
|
|
|
case 'OFFLINE':
|
|
|
|
return 'node-offline';
|
|
|
|
|
|
|
|
default:
|
|
|
|
return;
|
|
|
|
}
|
2016-06-07 00:21:26 +02:00
|
|
|
}
|
|
|
|
|
2016-06-26 19:09:12 +02:00
|
|
|
|
2016-06-07 00:21:26 +02:00
|
|
|
var nodes = nga.entity('nodes').label('Nodes').identifier(nga.field('token'));
|
|
|
|
nodes
|
|
|
|
.listView()
|
|
|
|
.title('Nodes')
|
2016-06-07 11:01:35 +02:00
|
|
|
.perPage(30)
|
2016-06-07 11:58:29 +02:00
|
|
|
.sortDir('ASC')
|
|
|
|
.sortField('hostname')
|
2016-06-07 00:21:26 +02:00
|
|
|
.actions([])
|
|
|
|
.batchActions([])
|
|
|
|
.exportFields([])
|
|
|
|
.fields([
|
2016-06-07 12:39:52 +02:00
|
|
|
nga.field('hostname').cssClasses(nodeClasses),
|
|
|
|
nga.field('nickname').cssClasses(nodeClasses),
|
|
|
|
nga.field('email').cssClasses(nodeClasses),
|
2016-06-07 00:21:26 +02:00
|
|
|
nga.field('token').cssClasses(nodeClasses),
|
2016-06-07 10:50:15 +02:00
|
|
|
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>';
|
|
|
|
}),
|
2016-06-26 19:39:39 +02:00
|
|
|
nga.field('onlineState').map(nullable).cssClasses(nodeClasses),
|
2016-06-07 11:58:29 +02:00
|
|
|
nga.field('monitoringState').cssClasses(nodeClasses).template(function (node) {
|
|
|
|
switch (node.values.monitoringState) {
|
|
|
|
case 'active':
|
2016-06-11 15:31:57 +02:00
|
|
|
return '<i class="fa fa-heartbeat monitoring-active" title="active"></i>';
|
2016-06-07 11:58:29 +02:00
|
|
|
|
|
|
|
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>';
|
2016-06-07 00:21:26 +02:00
|
|
|
}
|
|
|
|
})
|
|
|
|
])
|
2016-06-07 12:39:52 +02:00
|
|
|
.filters([
|
2016-07-23 11:30:41 +02:00
|
|
|
nga.field('q', 'template')
|
2016-06-07 12:39:52 +02:00
|
|
|
.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>'),
|
2016-07-23 11:30:41 +02:00
|
|
|
nga.field('hasKey', 'choice')
|
|
|
|
.label('VPN key')
|
|
|
|
.pinned(false)
|
|
|
|
.choices([
|
|
|
|
{ label: 'VPN key set', value: true },
|
|
|
|
{ label: 'no VPN key', value: false }
|
|
|
|
]),
|
|
|
|
nga.field('hasCoords', 'choice')
|
|
|
|
.label('GPS coordinates')
|
|
|
|
.pinned(false)
|
|
|
|
.choices([
|
|
|
|
{ label: 'coordinates set', value: true },
|
|
|
|
{ label: 'no coordinates', value: false }
|
|
|
|
]),
|
|
|
|
nga.field('onlineState', 'choice')
|
|
|
|
.label('Online state')
|
|
|
|
.pinned(false)
|
|
|
|
.choices([
|
|
|
|
{ label: 'online', value: 'ONLINE' },
|
|
|
|
{ label: 'offline', value: 'OFFLINE' }
|
|
|
|
]),
|
|
|
|
nga.field('monitoringState', 'choice')
|
|
|
|
.label('Monitoring')
|
|
|
|
.pinned(false)
|
|
|
|
.choices([
|
|
|
|
{ label: 'pending', value: 'pending' },
|
|
|
|
{ label: 'active', value: 'active' },
|
|
|
|
{ label: 'disabled', value: 'disabled' }
|
|
|
|
])
|
2016-06-07 12:39:52 +02:00
|
|
|
])
|
2016-07-23 11:30:41 +02:00
|
|
|
.actions(['<ma-filter-button filters="filters()" enabled-filters="enabledFilters" enable-filter="enableFilter()"></ma-filter-button>'])
|
2016-06-07 14:08:04 +02:00
|
|
|
.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> ' +
|
2016-06-20 22:28:01 +02:00
|
|
|
'<form style="display: inline-block" action="/#/update" method="POST" target="_blank">' +
|
2016-06-07 14:08:04 +02:00
|
|
|
'<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>'
|
|
|
|
)
|
2016-06-07 00:21:26 +02:00
|
|
|
;
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
2016-06-11 15:31:57 +02:00
|
|
|
function monitoringStateClasses(monitoringState) {
|
|
|
|
if (!monitoringState) {
|
|
|
|
return;
|
|
|
|
}
|
2016-06-11 18:13:37 +02:00
|
|
|
|
|
|
|
switch (monitoringState.values.state) {
|
|
|
|
case 'ONLINE':
|
|
|
|
return 'monitoring-state-online';
|
|
|
|
|
|
|
|
case 'OFFLINE':
|
|
|
|
return 'monitoring-state-offline';
|
|
|
|
|
|
|
|
default:
|
|
|
|
return;
|
|
|
|
}
|
2016-06-11 15:31:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
var monitoringStates = nga.entity('monitoring').label('Monitoring');
|
|
|
|
monitoringStates
|
|
|
|
.listView()
|
|
|
|
.title('Monitoring')
|
|
|
|
.perPage(30)
|
|
|
|
.sortDir('ASC')
|
|
|
|
.sortField('id')
|
|
|
|
.actions([])
|
|
|
|
.batchActions([])
|
|
|
|
.exportFields([])
|
|
|
|
.fields([
|
2016-06-11 18:13:37 +02:00
|
|
|
nga.field('id').cssClasses(monitoringStateClasses),
|
2016-06-26 19:09:12 +02:00
|
|
|
nga.field('hostname').cssClasses(monitoringStateClasses),
|
2016-06-11 18:13:37 +02:00
|
|
|
nga.field('mac').cssClasses(monitoringStateClasses),
|
2016-06-26 19:09:12 +02:00
|
|
|
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>';
|
|
|
|
}
|
|
|
|
}),
|
2016-06-11 18:13:37 +02:00
|
|
|
nga.field('state').cssClasses(monitoringStateClasses),
|
|
|
|
nga.field('last_seen').map(formatMoment).cssClasses(monitoringStateClasses),
|
|
|
|
nga.field('import_timestamp').label('Imported').map(formatMoment).cssClasses(monitoringStateClasses),
|
2016-06-26 19:09:12 +02:00
|
|
|
nga.field('last_status_mail_type').map(nullable).cssClasses(monitoringStateClasses),
|
2016-06-11 18:13:37 +02:00
|
|
|
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)
|
2016-06-11 15:31:57 +02:00
|
|
|
])
|
|
|
|
.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);
|
|
|
|
|
2016-06-11 12:08:50 +02:00
|
|
|
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);
|
|
|
|
|
2016-06-21 14:55:02 +02:00
|
|
|
function taskClasses(field) {
|
|
|
|
return function(task) {
|
|
|
|
if (!task) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
return 'task-' + field + ' ' + (task.values.enabled ? 'task-enabled' : 'task-disabled');
|
|
|
|
};
|
2016-06-05 12:18:37 +02:00
|
|
|
}
|
|
|
|
|
2016-06-04 14:58:11 +02:00
|
|
|
var tasks = nga.entity('tasks').label('Background-Jobs');
|
|
|
|
tasks
|
|
|
|
.listView()
|
|
|
|
.title('Background-Jobs')
|
2016-06-07 11:01:35 +02:00
|
|
|
.perPage(30)
|
2016-06-07 11:58:29 +02:00
|
|
|
.sortDir('ASC')
|
|
|
|
.sortField('id')
|
2016-06-04 14:58:11 +02:00
|
|
|
.actions([])
|
|
|
|
.batchActions([])
|
|
|
|
.exportFields([])
|
|
|
|
.fields([
|
2016-06-21 14:55:02 +02:00
|
|
|
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'))
|
2016-06-04 14:58:11 +02:00
|
|
|
])
|
2016-06-07 12:45:23 +02:00
|
|
|
.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>'),
|
|
|
|
])
|
2016-06-04 14:58:11 +02:00
|
|
|
.listActions(
|
2016-06-05 12:18:37 +02:00
|
|
|
'<fa-task-action-button action="run" task="entry" button="primary" label="run" size="sm"></fa-task-action-button> ' +
|
2016-06-07 10:50:15 +02:00
|
|
|
'<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>'
|
2016-06-07 00:21:26 +02:00
|
|
|
)
|
2016-06-04 14:58:11 +02:00
|
|
|
;
|
|
|
|
|
|
|
|
admin.addEntity(tasks);
|
|
|
|
|
|
|
|
admin.menu(
|
|
|
|
nga.menu()
|
2016-07-19 13:24:43 +02:00
|
|
|
.addChild(nga
|
|
|
|
.menu()
|
|
|
|
.template(
|
|
|
|
'<a href="/internal/admin">' +
|
|
|
|
'<span class="fa fa-dashboard"></span> Dashboard / Statistics' +
|
|
|
|
'</a>'
|
|
|
|
)
|
|
|
|
)
|
2016-06-07 00:21:26 +02:00
|
|
|
.addChild(nga
|
|
|
|
.menu(nodes)
|
2016-06-07 10:50:15 +02:00
|
|
|
.icon('<i class="fa fa-dot-circle-o"></i>')
|
2016-06-07 00:21:26 +02:00
|
|
|
)
|
2016-06-11 15:31:57 +02:00
|
|
|
.addChild(nga
|
|
|
|
.menu(monitoringStates)
|
|
|
|
.icon('<span class="fa fa-heartbeat"></span>')
|
|
|
|
)
|
2016-06-11 12:08:50 +02:00
|
|
|
.addChild(nga
|
|
|
|
.menu(mails)
|
|
|
|
.icon('<span class="fa fa-envelope"></span>')
|
|
|
|
)
|
2016-06-04 14:58:11 +02:00
|
|
|
.addChild(nga
|
|
|
|
.menu(tasks)
|
2016-06-07 10:50:15 +02:00
|
|
|
.icon('<span class="fa fa-cog"></span>')
|
2016-06-04 14:58:11 +02:00
|
|
|
)
|
|
|
|
.addChild(nga
|
|
|
|
.menu()
|
|
|
|
.template(
|
|
|
|
'<a href="/internal/logs" target="_blank">' +
|
2016-06-07 10:50:15 +02:00
|
|
|
'<span class="fa fa-list"></span> Logs' +
|
2016-06-04 14:58:11 +02:00
|
|
|
'</a>'
|
|
|
|
)
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
2016-07-19 13:24:43 +02:00
|
|
|
admin.dashboard(nga.dashboard()
|
|
|
|
.template(
|
|
|
|
'<div class="row dashboard-starter"></div>' +
|
|
|
|
'<fa-dashboard-stats></fa-dashboard-stats>' +
|
|
|
|
|
|
|
|
'<div class="row dashboard-content">' +
|
|
|
|
'<div class="col-lg-6">' +
|
|
|
|
'<div class="panel panel-default" ng-repeat="collection in dashboardController.collections | orderElement" ng-if="$even">' +
|
|
|
|
'<ma-dashboard-panel collection="collection" entries="dashboardController.entries[collection.name()]"></ma-dashboard-panel>' +
|
|
|
|
'</div>' +
|
|
|
|
'</div>' +
|
|
|
|
'</div>'
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
2016-06-04 14:58:11 +02:00
|
|
|
nga.configure(admin);
|
2016-06-07 00:21:26 +02:00
|
|
|
});
|