Added confirmation page for monitoring + a few tweaks.
This commit is contained in:
parent
1b173b79d4
commit
0bdce5debb
|
@ -50,8 +50,10 @@
|
||||||
<script src="scripts/controllers/newNodeCtrl.js"></script>
|
<script src="scripts/controllers/newNodeCtrl.js"></script>
|
||||||
<script src="scripts/controllers/updateNodeCtrl.js"></script>
|
<script src="scripts/controllers/updateNodeCtrl.js"></script>
|
||||||
<script src="scripts/controllers/deleteNodeCtrl.js"></script>
|
<script src="scripts/controllers/deleteNodeCtrl.js"></script>
|
||||||
|
<script src="scripts/controllers/confirmMonitoringCtrl.js"></script>
|
||||||
|
|
||||||
<script src="scripts/services/nodeService.js"></script>
|
<script src="scripts/services/nodeService.js"></script>
|
||||||
|
<script src="scripts/services/monitoringService.js"></script>
|
||||||
|
|
||||||
<script src="scripts/directives/help.js"></script>
|
<script src="scripts/directives/help.js"></script>
|
||||||
<script src="scripts/directives/navbar.js"></script>
|
<script src="scripts/directives/navbar.js"></script>
|
||||||
|
|
|
@ -31,6 +31,11 @@ angular.module('ffffng', [
|
||||||
controller: 'DeleteNodeCtrl',
|
controller: 'DeleteNodeCtrl',
|
||||||
title: 'Knoten löschen'
|
title: 'Knoten löschen'
|
||||||
})
|
})
|
||||||
|
.when('/monitoring/confirm', {
|
||||||
|
templateUrl: 'views/confirmMonitoring.html',
|
||||||
|
controller: 'ConfirmMonitoringCtrl',
|
||||||
|
title: 'Versand von Status-E-Mails bestätigen'
|
||||||
|
})
|
||||||
.otherwise({
|
.otherwise({
|
||||||
redirectTo: '/'
|
redirectTo: '/'
|
||||||
});
|
});
|
||||||
|
|
35
app/scripts/controllers/confirmMonitoringCtrl.js
Normal file
35
app/scripts/controllers/confirmMonitoringCtrl.js
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
angular.module('ffffng')
|
||||||
|
.controller('ConfirmMonitoringCtrl', function ($scope, Navigator, MonitoringService, $routeParams, config) {
|
||||||
|
if (!config.monitoring.enabled) {
|
||||||
|
Navigator.home();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.config = config;
|
||||||
|
|
||||||
|
$scope.monitoringInfo = {};
|
||||||
|
$scope.monitoringStatus = 'loading';
|
||||||
|
|
||||||
|
MonitoringService.confirm($routeParams['mac'], $routeParams['token'])
|
||||||
|
.then(
|
||||||
|
function (response) {
|
||||||
|
// success
|
||||||
|
$scope.monitoringInfo = response.data;
|
||||||
|
$scope.monitoringStatus = 'confirmed';
|
||||||
|
},
|
||||||
|
function () {
|
||||||
|
// error
|
||||||
|
$scope.monitoringStatus = 'error';
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
$scope.goHome = function () {
|
||||||
|
Navigator.home();
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.goToUpdate = function () {
|
||||||
|
Navigator.updateNode();
|
||||||
|
};
|
||||||
|
});
|
|
@ -123,6 +123,10 @@ angular.module('ffffng')
|
||||||
return $scope.nodeForm && $scope.nodeForm[field].$invalid && submitted;
|
return $scope.nodeForm && $scope.nodeForm[field].$invalid && submitted;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.monitoringActive = function () {
|
||||||
|
return $scope.node.monitoring && monitoringConfirmed && $scope.node.email === initialEmail;
|
||||||
|
};
|
||||||
|
|
||||||
$scope.monitoringInitialConfirmationRequired = function () {
|
$scope.monitoringInitialConfirmationRequired = function () {
|
||||||
return $scope.node.monitoring
|
return $scope.node.monitoring
|
||||||
&& ($scope.action === 'create' || $scope.node.email !== initialEmail || !initialMonitoring);
|
&& ($scope.action === 'create' || $scope.node.email !== initialEmail || !initialMonitoring);
|
||||||
|
@ -151,7 +155,8 @@ angular.module('ffffng')
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.error = null;
|
$scope.error = null;
|
||||||
$scope.save(node).error(function (response, code) {
|
$scope.save(node).catch(function (response, code) {
|
||||||
|
// error
|
||||||
switch (code) {
|
switch (code) {
|
||||||
case 409: // conflict
|
case 409: // conflict
|
||||||
$scope.error = duplicateError[response.field];
|
$scope.error = duplicateError[response.field];
|
||||||
|
|
|
@ -15,7 +15,7 @@ angular.module('ffffng')
|
||||||
|
|
||||||
$scope.error = null;
|
$scope.error = null;
|
||||||
$scope.onSubmit(token)
|
$scope.onSubmit(token)
|
||||||
.error(function (response, code) {
|
.catch(function (response, code) {
|
||||||
switch (code) {
|
switch (code) {
|
||||||
case 404: // not found
|
case 404: // not found
|
||||||
$scope.error = 'Zum Token wurde kein passender Eintrag gefunden.';
|
$scope.error = 'Zum Token wurde kein passender Eintrag gefunden.';
|
||||||
|
|
13
app/scripts/services/monitoringService.js
Normal file
13
app/scripts/services/monitoringService.js
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
angular.module('ffffng')
|
||||||
|
.service('MonitoringService', function ($http, $q) {
|
||||||
|
return {
|
||||||
|
'confirm': function (mac, token) {
|
||||||
|
if (!mac || !token) {
|
||||||
|
return $q.reject({});
|
||||||
|
}
|
||||||
|
return $http.put('/api/monitoring/confirm/' + mac + '?token=' + token);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
|
@ -179,9 +179,9 @@ $state-warning-border: darken(adjust-hue($state-warning-bg, -10), 5%);
|
||||||
$state-danger-text: darken($brand-danger, 10%);
|
$state-danger-text: darken($brand-danger, 10%);
|
||||||
$state-danger-bg: lighten($brand-danger, 30%);
|
$state-danger-bg: lighten($brand-danger, 30%);
|
||||||
$state-danger-border: darken(adjust-hue($state-danger-bg, -10), 3%);
|
$state-danger-border: darken(adjust-hue($state-danger-bg, -10), 3%);
|
||||||
$state-success-text: darken($brand-success, 10%);
|
$state-success-text: darken($brand-success, 20%);
|
||||||
$state-success-bg: lighten($brand-success, 30%);
|
$state-success-bg: lighten($brand-success, 35%);
|
||||||
$state-success-border: darken(adjust-hue($state-success-bg, -10), 5%);
|
$state-success-border: darken(adjust-hue($state-success-bg, -10), 7%);
|
||||||
$state-info-text: darken($brand-info, 20%);
|
$state-info-text: darken($brand-info, 20%);
|
||||||
$state-info-bg: lighten($brand-info, 30%);
|
$state-info-bg: lighten($brand-info, 30%);
|
||||||
$state-info-border: darken(adjust-hue($state-info-bg, -10), 10%);
|
$state-info-border: darken(adjust-hue($state-info-bg, -10), 10%);
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
@import "views/_newNodeForm";
|
@import "views/_newNodeForm";
|
||||||
@import "views/_updateNodeForm";
|
@import "views/_updateNodeForm";
|
||||||
@import "views/_deleteNodeForm";
|
@import "views/_deleteNodeForm";
|
||||||
|
@import "views/_confirmMonitoring";
|
||||||
@import "views/directives/_help";
|
@import "views/directives/_help";
|
||||||
@import "views/directives/_nodeForm";
|
@import "views/directives/_nodeForm";
|
||||||
@import "views/directives/_nodeSaved";
|
@import "views/directives/_nodeSaved";
|
||||||
|
|
55
app/styles/views/_confirmMonitoring.scss
Normal file
55
app/styles/views/_confirmMonitoring.scss
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
.confirm-monitoring {
|
||||||
|
@extend .jumbotron, .container;
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-button {
|
||||||
|
@extend .btn, .btn-lg, .btn-info;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary {
|
||||||
|
@extend .well;
|
||||||
|
|
||||||
|
display: table;
|
||||||
|
margin: {
|
||||||
|
left: auto;
|
||||||
|
right: auto;
|
||||||
|
top: 30px;
|
||||||
|
bottom: 30px;
|
||||||
|
};
|
||||||
|
|
||||||
|
border: 3px dashed $gray;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
@extend .fa, .fa-heartbeat;
|
||||||
|
|
||||||
|
display: table-cell;
|
||||||
|
|
||||||
|
color: $brand-primary;
|
||||||
|
font-size: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node {
|
||||||
|
@extend .dl-horizontal;
|
||||||
|
|
||||||
|
display: table-cell;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 24px;
|
||||||
|
|
||||||
|
dt {
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
dd {
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
margin-left: 120px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,7 +10,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.actions {
|
.actions {
|
||||||
margin-bottom: 10px;
|
margin-top: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.token-hint {
|
.token-hint {
|
||||||
|
@ -24,18 +24,32 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.summary {
|
.summary {
|
||||||
.node {
|
@extend .well;
|
||||||
@extend .well;
|
|
||||||
|
|
||||||
border: 3px dashed $gray;
|
display: table;
|
||||||
font-family: monospace;
|
|
||||||
font-size: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
margin: {
|
margin: {
|
||||||
|
left: auto;
|
||||||
|
right: auto;
|
||||||
top: 30px;
|
top: 30px;
|
||||||
bottom: 30px;
|
bottom: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
border: 3px dashed $gray;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 32px;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
@extend .fa, .fa-trash;
|
||||||
|
|
||||||
|
display: table-cell;
|
||||||
|
|
||||||
|
padding-right: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node {
|
||||||
|
display: table-cell;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.back-button {
|
.back-button {
|
||||||
|
|
|
@ -55,26 +55,47 @@ f-node-form {
|
||||||
color: $brand-primary;
|
color: $brand-primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.monitoring-active-info {
|
||||||
|
@extend .alert-success;
|
||||||
|
}
|
||||||
|
|
||||||
.monitoring-confirmation-info {
|
.monitoring-confirmation-info {
|
||||||
@extend .alert, .alert-info;
|
@extend .alert-info;
|
||||||
}
|
}
|
||||||
|
|
||||||
.monitoring-confirmation-pending-info {
|
.monitoring-confirmation-pending-info {
|
||||||
@extend .alert, .alert-warning;
|
@extend .alert-warning;
|
||||||
}
|
}
|
||||||
|
|
||||||
.monitoring-confirmation-info, .monitoring-confirmation-pending-info {
|
.monitoring-active-info,
|
||||||
|
.monitoring-confirmation-info,
|
||||||
|
.monitoring-confirmation-pending-info {
|
||||||
|
@extend .alert;
|
||||||
|
|
||||||
display: table;
|
display: table;
|
||||||
|
|
||||||
|
.icon, .message {
|
||||||
|
display: table-cell;
|
||||||
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
@extend .fa, .fa-exclamation-triangle;
|
@extend .fa;
|
||||||
|
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
padding-right: 15px;
|
padding-right: 15px;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.icon, .message {
|
.monitoring-active-info {
|
||||||
display: table-cell;
|
.icon {
|
||||||
|
@extend .fa-heartbeat;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitoring-confirmation-info,
|
||||||
|
.monitoring-confirmation-pending-info {
|
||||||
|
.icon {
|
||||||
|
@extend .fa-exclamation-triangle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
82
app/views/confirmMonitoring.html
Normal file
82
app/views/confirmMonitoring.html
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
<div class="confirm-monitoring" ng-switch="monitoringStatus">
|
||||||
|
<h2 ng-switch-when="loading">Einen Moment bitte...</h2>
|
||||||
|
|
||||||
|
<div ng-switch-when="confirmed">
|
||||||
|
<h2>Der Versand von Status-E-Mails ist nun aktiv!</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Deine E-Mail-Adresse konnte erfolgreich bestätigt werden. Sollte Dein Knoten nun längere Zeit offline sein,
|
||||||
|
so wirst Du ab sofort per E-Mail darüber benachrichtigt.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="summary">
|
||||||
|
<i class="icon"></i>
|
||||||
|
<dl class="node">
|
||||||
|
<dt>Name</dt>
|
||||||
|
<dd>{{monitoringInfo.hostname}}</dd>
|
||||||
|
|
||||||
|
<dt>MAC</dt>
|
||||||
|
<dd>{{monitoringInfo.mac}}</dd>
|
||||||
|
|
||||||
|
<dt><i class="fa fa-envelope"></i></dt>
|
||||||
|
<dd>{{monitoringInfo.email}}</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<em>
|
||||||
|
Möchtest Du keine Status-E-Mails zu Deinem Knoten mehr erhalten, so kannst Du den Versand
|
||||||
|
jederzeit unter <a href="javascript:" ng-click="goToUpdate()">„Knotendaten ändern“</a> abschalten.
|
||||||
|
Gib dazu dort Dein Token ein und scrolle dann ganz nach unten. Bitte habe Verständnis dafür, dass
|
||||||
|
das An- und Abschalten des Versands für jeden Deiner Knoten einzeln erfolgt.
|
||||||
|
</em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Bei Fragen wende Dich gerne an
|
||||||
|
<a href="mailto:{{ config.community.contactEmail }}">{{ config.community.contactEmail }}</a>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<button class="back-button" ng-click="goHome()"><i class="fa fa-reply"></i> Zurück zum Anfang</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div ng-switch-when="error">
|
||||||
|
<h2>Die Bestätigung Deiner E-Mail-Adresse ist fehlgeschlagen</h2>
|
||||||
|
|
||||||
|
<div class="alert alert-danger" role="alert">
|
||||||
|
<p><strong>Dies kann mehrere Gründe haben:</strong></p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Du hast zwischenzeitlich die E-Mail-Adresse für den Knoten geändert.</li>
|
||||||
|
<li>Du hast den Link nicht vollständig aus der E-Mail kopiert.</li>
|
||||||
|
<li>Es ist ein interner Fehler aufgetreten.</li>
|
||||||
|
<li>Der Versand von Status-E-Mail ist bereits aktiv.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Um zu prüfen, ob der Versand bereits aktiv ist, gehe bitte auf
|
||||||
|
<a href="javascript:" ng-click="goToUpdate()">„Knotendaten ändern“</a>, gib dort Dein Token ein
|
||||||
|
und scrolle dann ganz nach unten. Mit einem Klick auf „Daten ändern“ kannst Du dir bei Bedarf
|
||||||
|
einen neuen Link zuschicken lassen.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Solltest du Deine E-Mail-Adresse zwischenzeitlich geändert haben, prüfe bitte ob Du
|
||||||
|
über die neue E-Mail-Adresse bereits eine E-Mail mit Bestätigungs-Link erhalten hast.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<strong>
|
||||||
|
Sollte das Problem so nicht zu beheben sein, wende Dich gerne an
|
||||||
|
<a href="mailto:{{ config.community.contactEmail }}">{{ config.community.contactEmail }}</a>.
|
||||||
|
</strong>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<button class="back-button" ng-click="goHome()"><i class="fa fa-reply"></i> Zurück zum Anfang</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -35,16 +35,12 @@
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="summary">
|
<div class="summary">
|
||||||
|
<i class="icon"></i>
|
||||||
<span class="node">
|
<span class="node">
|
||||||
<i class="fa fa-trash"></i>
|
|
||||||
{{hostname}}
|
{{hostname}}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="actions">
|
|
||||||
<button class="back-button" ng-click="goHome()"><i class="fa fa-reply"></i> Zurück zum Anfang</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<em>
|
<em>
|
||||||
Hinweis: Nach dem Löschen kann der Knoten ggf. weiterhin in der Knotenkarte angezeigt werden. Dies
|
Hinweis: Nach dem Löschen kann der Knoten ggf. weiterhin in der Knotenkarte angezeigt werden. Dies
|
||||||
|
@ -53,4 +49,13 @@
|
||||||
(Konfigurationsoberfläche des Routers) hinterlegten.
|
(Konfigurationsoberfläche des Routers) hinterlegten.
|
||||||
</em>
|
</em>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Bei Fragen wende Dich gerne an
|
||||||
|
<a href="mailto:{{ config.community.contactEmail }}">{{ config.community.contactEmail }}</a>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<button class="back-button" ng-click="goHome()"><i class="fa fa-reply"></i> Zurück zum Anfang</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -83,7 +83,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="monitoring-data">
|
<div class="monitoring-data" ng-if="config.monitoring.enabled">
|
||||||
<h3>Möchtest Du Status-E-Mails bekommen?</h3>
|
<h3>Möchtest Du Status-E-Mails bekommen?</h3>
|
||||||
<i class="monitoring-icon"></i>
|
<i class="monitoring-icon"></i>
|
||||||
<p class="help-block">
|
<p class="help-block">
|
||||||
|
@ -98,6 +98,20 @@
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-12" ng-if="monitoringActive()">
|
||||||
|
<div class="monitoring-active-info" role="alert">
|
||||||
|
<i class="icon"></i>
|
||||||
|
<div class="message">
|
||||||
|
<p>
|
||||||
|
Der Versand von Status-E-Mails ist für Deinen Knoten aktiv. Möchtest Du keine Status-E-Mails
|
||||||
|
mehr erhalten, entferne einfach das Häkchen oberhalb dieser Box und klicke unten auf „Daten ändern“.
|
||||||
|
</p>
|
||||||
|
<p ng-if="node.email">
|
||||||
|
<i class="fa fa-envelope-o"></i> <strong>{{node.email}}</strong>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="col-md-12" ng-if="monitoringInitialConfirmationRequired()">
|
<div class="col-md-12" ng-if="monitoringInitialConfirmationRequired()">
|
||||||
<div class="monitoring-confirmation-info" role="alert">
|
<div class="monitoring-confirmation-info" role="alert">
|
||||||
<i class="icon"></i>
|
<i class="icon"></i>
|
||||||
|
@ -128,7 +142,7 @@
|
||||||
<p>
|
<p>
|
||||||
Zur Bestätigung Deiner E-Mail-Adresse schicken wir Dir nach einem Klick auf „Daten ändern“ unten
|
Zur Bestätigung Deiner E-Mail-Adresse schicken wir Dir nach einem Klick auf „Daten ändern“ unten
|
||||||
nochmal eine E-Mail mit einem Bestätiguns-Link. Möchtest Du keine Status-E-Mails erhalten,
|
nochmal eine E-Mail mit einem Bestätiguns-Link. Möchtest Du keine Status-E-Mails erhalten,
|
||||||
entferne einfach das Häkchen oberhalb dieser Box.
|
entferne einfach das Häkchen oberhalb dieser Box und klicke dann unten auf „Daten ändern“.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Die Inbetriebnahme Deines Knotens kannst Du selbstverständlich unabhängig von der Bestätigung
|
Die Inbetriebnahme Deines Knotens kannst Du selbstverständlich unabhängig von der Bestätigung
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
{{token}}
|
{{token}}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button class="back-button" ng-click="goHome()"><i class="fa fa-reply"></i> Zurück zum Anfang</button>
|
<button class="back-button" ng-click="goHome()"><i class="fa fa-reply"></i> Zurück zum Anfang</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"server": {
|
"server": {
|
||||||
|
"baseUrl": "http://localhost:8080",
|
||||||
"port": 8080,
|
"port": 8080,
|
||||||
"peersPath": "/tmp/peers"
|
"peersPath": "/tmp/peers"
|
||||||
},
|
},
|
||||||
|
@ -13,6 +14,9 @@
|
||||||
"graphUrl": "http://graph.hamburg.freifunk.net/graph.html",
|
"graphUrl": "http://graph.hamburg.freifunk.net/graph.html",
|
||||||
"mapUrl": "http://graph.hamburg.freifunk.net/geomap.html"
|
"mapUrl": "http://graph.hamburg.freifunk.net/geomap.html"
|
||||||
},
|
},
|
||||||
|
"monitoring": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
"coordsSelector": {
|
"coordsSelector": {
|
||||||
"lat": 53.565278,
|
"lat": 53.565278,
|
||||||
"lng": 10.001389,
|
"lng": 10.001389,
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
angular.module('ffffng').factory('config', function (fs, deepExtend) {
|
angular.module('ffffng').factory('config', function (fs, deepExtend) {
|
||||||
var defaultConfig = {
|
var defaultConfig = {
|
||||||
server: {
|
server: {
|
||||||
|
baseUrl: 'http://localhost:8080',
|
||||||
port: 8080,
|
port: 8080,
|
||||||
peersPath: '/tmp/peers'
|
peersPath: '/tmp/peers'
|
||||||
},
|
},
|
||||||
|
@ -16,7 +17,13 @@ angular.module('ffffng').factory('config', function (fs, deepExtend) {
|
||||||
graphUrl: 'http://graph.musterstadt.freifunk.net/graph.html',
|
graphUrl: 'http://graph.musterstadt.freifunk.net/graph.html',
|
||||||
mapUrl: 'http://graph.musterstadt.freifunk.net/geomap.html'
|
mapUrl: 'http://graph.musterstadt.freifunk.net/geomap.html'
|
||||||
},
|
},
|
||||||
|
monitoring: {
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
coordsSelector: {
|
coordsSelector: {
|
||||||
|
showInfo: false,
|
||||||
|
showBorderForDebugging: false,
|
||||||
|
localCommunityPolygon: [],
|
||||||
lat: 53.565278,
|
lat: 53.565278,
|
||||||
lng: 10.001389,
|
lng: 10.001389,
|
||||||
defaultZoom: 10
|
defaultZoom: 10
|
||||||
|
|
|
@ -13,11 +13,14 @@ require('./router');
|
||||||
require('./libs');
|
require('./libs');
|
||||||
|
|
||||||
require('./utils/errorTypes');
|
require('./utils/errorTypes');
|
||||||
|
require('./utils/resources');
|
||||||
require('./utils/strings');
|
require('./utils/strings');
|
||||||
|
|
||||||
require('./resources/nodeResource');
|
require('./resources/nodeResource');
|
||||||
|
require('./resources/monitoringResource');
|
||||||
|
|
||||||
require('./services/nodeService');
|
require('./services/nodeService');
|
||||||
|
require('./services/monitoringService');
|
||||||
|
|
||||||
require('../shared/validation/constraints');
|
require('../shared/validation/constraints');
|
||||||
require('./validation/validator');
|
require('./validation/validator');
|
||||||
|
|
42
server/resources/monitoringResource.js
Normal file
42
server/resources/monitoringResource.js
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
angular.module('ffffng').factory('MonitoringResource', function (
|
||||||
|
Constraints,
|
||||||
|
Validator,
|
||||||
|
MonitoringService,
|
||||||
|
_,
|
||||||
|
Strings,
|
||||||
|
Resources,
|
||||||
|
ErrorTypes
|
||||||
|
) {
|
||||||
|
var isValidMac = Validator.forConstraint(Constraints.node.mac);
|
||||||
|
var isValidToken = Validator.forConstraint(Constraints.token);
|
||||||
|
|
||||||
|
return {
|
||||||
|
confirm: function (req, res) {
|
||||||
|
var data = Resources.getData(req);
|
||||||
|
|
||||||
|
var mac = Strings.normalizeMac(data.mac);
|
||||||
|
if (!isValidMac(mac)) {
|
||||||
|
return Resources.error(res, {data: 'Invalid MAC.', type: ErrorTypes.badRequest});
|
||||||
|
}
|
||||||
|
|
||||||
|
var token = Strings.normalizeString(data.token);
|
||||||
|
if (!isValidToken(token)) {
|
||||||
|
return Resources.error(res, {data: 'Invalid token.', type: ErrorTypes.badRequest});
|
||||||
|
}
|
||||||
|
|
||||||
|
return MonitoringService.confirm(mac, token, function (err, node) {
|
||||||
|
if (err) {
|
||||||
|
return Resources.error(res, err);
|
||||||
|
}
|
||||||
|
return Resources.success(res, {
|
||||||
|
hostname: node.hostname,
|
||||||
|
mac: node.mac,
|
||||||
|
email: node.email,
|
||||||
|
monitoringConfirmed: node.monitoringConfirmed
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
|
@ -6,12 +6,9 @@ angular.module('ffffng').factory('NodeResource', function (
|
||||||
NodeService,
|
NodeService,
|
||||||
_,
|
_,
|
||||||
Strings,
|
Strings,
|
||||||
|
Resources,
|
||||||
ErrorTypes
|
ErrorTypes
|
||||||
) {
|
) {
|
||||||
function getData(req) {
|
|
||||||
return _.extend({}, req.body, req.params);
|
|
||||||
}
|
|
||||||
|
|
||||||
var nodeFields = ['hostname', 'key', 'email', 'nickname', 'mac', 'coords', 'monitoring'];
|
var nodeFields = ['hostname', 'key', 'email', 'nickname', 'mac', 'coords', 'monitoring'];
|
||||||
|
|
||||||
function getValidNodeData(reqData) {
|
function getValidNodeData(reqData) {
|
||||||
|
@ -26,87 +23,74 @@ angular.module('ffffng').factory('NodeResource', function (
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
function respond(res, httpCode, data) {
|
|
||||||
res.writeHead(httpCode, {'Content-Type': 'application/json'});
|
|
||||||
res.end(JSON.stringify(data));
|
|
||||||
}
|
|
||||||
|
|
||||||
function success(res, data) {
|
|
||||||
respond(res, 200, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
function error(res, err) {
|
|
||||||
respond(res, err.type.code, err.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
var isValidNode = Validator.forConstraints(Constraints.node);
|
var isValidNode = Validator.forConstraints(Constraints.node);
|
||||||
var isValidToken = Validator.forConstraint(Constraints.token);
|
var isValidToken = Validator.forConstraint(Constraints.token);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
create: function (req, res) {
|
create: function (req, res) {
|
||||||
var data = getData(req);
|
var data = Resources.getData(req);
|
||||||
|
|
||||||
var node = getValidNodeData(data);
|
var node = getValidNodeData(data);
|
||||||
if (!isValidNode(node)) {
|
if (!isValidNode(node)) {
|
||||||
return error(res, {data: 'Invalid node data.', type: ErrorTypes.badRequest});
|
return Resources.error(res, {data: 'Invalid node data.', type: ErrorTypes.badRequest});
|
||||||
}
|
}
|
||||||
|
|
||||||
return NodeService.createNode(node, function (err, token, node) {
|
return NodeService.createNode(node, function (err, token, node) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return error(res, err);
|
return Resources.error(res, err);
|
||||||
}
|
}
|
||||||
return success(res, {token: token, node: node});
|
return Resources.success(res, {token: token, node: node});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
update: function (req, res) {
|
update: function (req, res) {
|
||||||
var data = getData(req);
|
var data = Resources.getData(req);
|
||||||
|
|
||||||
var token = Strings.normalizeString(data.token);
|
var token = Strings.normalizeString(data.token);
|
||||||
if (!isValidToken(token)) {
|
if (!isValidToken(token)) {
|
||||||
return error(res, {data: 'Invalid token.', type: ErrorTypes.badRequest});
|
return Resources.error(res, {data: 'Invalid token.', type: ErrorTypes.badRequest});
|
||||||
}
|
}
|
||||||
|
|
||||||
var node = getValidNodeData(data);
|
var node = getValidNodeData(data);
|
||||||
if (!isValidNode(node)) {
|
if (!isValidNode(node)) {
|
||||||
return error(res, {data: 'Invalid node data.', type: ErrorTypes.badRequest});
|
return Resources.error(res, {data: 'Invalid node data.', type: ErrorTypes.badRequest});
|
||||||
}
|
}
|
||||||
|
|
||||||
return NodeService.updateNode(token, node, function (err, token, node) {
|
return NodeService.updateNode(token, node, function (err, token, node) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return error(res, err);
|
return Resources.error(res, err);
|
||||||
}
|
}
|
||||||
return success(res, {token: token, node: node});
|
return Resources.success(res, {token: token, node: node});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
delete: function (req, res) {
|
delete: function (req, res) {
|
||||||
var data = getData(req);
|
var data = Resources.getData(req);
|
||||||
|
|
||||||
var token = Strings.normalizeString(data.token);
|
var token = Strings.normalizeString(data.token);
|
||||||
if (!isValidToken(token)) {
|
if (!isValidToken(token)) {
|
||||||
return error(res, {data: 'Invalid token.', type: ErrorTypes.badRequest});
|
return Resources.error(res, {data: 'Invalid token.', type: ErrorTypes.badRequest});
|
||||||
}
|
}
|
||||||
|
|
||||||
return NodeService.deleteNode(token, function (err) {
|
return NodeService.deleteNode(token, function (err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return error(res, err);
|
return Resources.error(res, err);
|
||||||
}
|
}
|
||||||
return success(res, {});
|
return Resources.success(res, {});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
get: function (req, res) {
|
get: function (req, res) {
|
||||||
var token = Strings.normalizeString(getData(req).token);
|
var token = Strings.normalizeString(Resources.getData(req).token);
|
||||||
if (!isValidToken(token)) {
|
if (!isValidToken(token)) {
|
||||||
return error(res, {data: 'Invalid token.', type: ErrorTypes.badRequest});
|
return Resources.error(res, {data: 'Invalid token.', type: ErrorTypes.badRequest});
|
||||||
}
|
}
|
||||||
|
|
||||||
return NodeService.getNodeData(token, function (err, node) {
|
return NodeService.getNodeDataByToken(token, function (err, node) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return error(res, err);
|
return Resources.error(res, err);
|
||||||
}
|
}
|
||||||
return success(res, node);
|
return Resources.success(res, node);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('ffffng').factory('Router', function (app, NodeResource) {
|
angular.module('ffffng').factory('Router', function (app, NodeResource, MonitoringResource) {
|
||||||
return {
|
return {
|
||||||
init: function () {
|
init: function () {
|
||||||
app.post('/api/node', NodeResource.create);
|
app.post('/api/node', NodeResource.create);
|
||||||
app.put('/api/node/:token', NodeResource.update);
|
app.put('/api/node/:token', NodeResource.update);
|
||||||
app.delete('/api/node/:token', NodeResource.delete);
|
app.delete('/api/node/:token', NodeResource.delete);
|
||||||
app.get('/api/node/:token', NodeResource.get);
|
app.get('/api/node/:token', NodeResource.get);
|
||||||
|
|
||||||
|
app.put('/api/monitoring/confirm/:mac', MonitoringResource.confirm);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
30
server/services/monitoringService.js
Normal file
30
server/services/monitoringService.js
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
angular.module('ffffng')
|
||||||
|
.service('MonitoringService', function (NodeService, ErrorTypes) {
|
||||||
|
return {
|
||||||
|
confirm: function (mac, token, callback) {
|
||||||
|
NodeService.getNodeDataByMac(mac, function (err, node, nodeSecrets) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!node.monitoring || !nodeSecrets.monitoringToken || nodeSecrets.monitoringToken !== token) {
|
||||||
|
return callback({data: 'Invalid token.', type: ErrorTypes.badRequest});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.monitoringConfirmed) {
|
||||||
|
return callback(null, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
node.monitoringConfirmed = true;
|
||||||
|
NodeService.internalUpdateNode(node.token, node, nodeSecrets, function (err, token, node) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
callback(null, node);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
|
@ -185,6 +185,17 @@ angular.module('ffffng')
|
||||||
callback(null, node, nodeSecrets);
|
callback(null, node, nodeSecrets);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getNodeDataByFilePattern(pattern, callback) {
|
||||||
|
var files = findNodeFiles(pattern);
|
||||||
|
|
||||||
|
if (files.length !== 1) {
|
||||||
|
return callback({data: 'Node not found.', type: ErrorTypes.notFound});
|
||||||
|
}
|
||||||
|
|
||||||
|
var file = files[0];
|
||||||
|
return parseNodeFile(file, callback);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
createNode: function (node, callback) {
|
createNode: function (node, callback) {
|
||||||
var token = generateToken();
|
var token = generateToken();
|
||||||
|
@ -210,7 +221,7 @@ angular.module('ffffng')
|
||||||
},
|
},
|
||||||
|
|
||||||
updateNode: function (token, node, callback) {
|
updateNode: function (token, node, callback) {
|
||||||
this.getNodeData(token, function (err, currentNode, nodeSecrets) {
|
this.getNodeDataByToken(token, function (err, currentNode, nodeSecrets) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
|
@ -233,9 +244,9 @@ angular.module('ffffng')
|
||||||
monitoringToken = generateToken();
|
monitoringToken = generateToken();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// email unchanged, keep token and confirmation state
|
// email unchanged, keep token (fix if not set) and confirmation state
|
||||||
monitoringConfirmed = currentNode.monitoringConfirmed;
|
monitoringConfirmed = currentNode.monitoringConfirmed;
|
||||||
monitoringToken = nodeSecrets.monitoringToken;
|
monitoringToken = nodeSecrets.monitoringToken || generateToken();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -257,19 +268,20 @@ angular.module('ffffng')
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
internalUpdateNode: function (token, node, nodeSecrets, callback) {
|
||||||
|
writeNodeFile(true, token, node, nodeSecrets, callback);
|
||||||
|
},
|
||||||
|
|
||||||
deleteNode: function (token, callback) {
|
deleteNode: function (token, callback) {
|
||||||
deleteNodeFile(token, callback);
|
deleteNodeFile(token, callback);
|
||||||
},
|
},
|
||||||
|
|
||||||
getNodeData: function (token, callback) {
|
getNodeDataByToken: function (token, callback) {
|
||||||
var files = findNodeFiles('*@*@*@' + token);
|
return getNodeDataByFilePattern('*@*@*@' + token, callback);
|
||||||
|
},
|
||||||
|
|
||||||
if (files.length !== 1) {
|
getNodeDataByMac: function (mac, callback) {
|
||||||
return callback({data: 'Node not found.', type: ErrorTypes.notFound});
|
return getNodeDataByFilePattern('*@' + mac + '@*@*', callback);
|
||||||
}
|
|
||||||
|
|
||||||
var file = files[0];
|
|
||||||
return parseNodeFile(file, callback);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
22
server/utils/resources.js
Normal file
22
server/utils/resources.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
angular.module('ffffng').factory('Resources', function (_) {
|
||||||
|
function respond(res, httpCode, data) {
|
||||||
|
res.writeHead(httpCode, {'Content-Type': 'application/json'});
|
||||||
|
res.end(JSON.stringify(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
getData: function (req) {
|
||||||
|
return _.extend({}, req.body, req.params, req.query);
|
||||||
|
},
|
||||||
|
|
||||||
|
success: function (res, data) {
|
||||||
|
respond(res, 200, data);
|
||||||
|
},
|
||||||
|
|
||||||
|
error: function (res, err) {
|
||||||
|
respond(res, err.type.code, err.data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
Loading…
Reference in a new issue