Allow setting a monitoring flag. Confirmation mail missing.

This commit is contained in:
baldo 2016-05-18 19:32:19 +02:00
parent 2fb4e9a227
commit 1b173b79d4
8 changed files with 254 additions and 19 deletions

View file

@ -13,6 +13,10 @@ angular.module('ffffng')
geolib, geolib,
OutsideOfCommunityDialog OutsideOfCommunityDialog
) { ) {
var initialEmail = $scope.node.email;
var initialMonitoring = $scope.node.monitoring;
var monitoringConfirmed = $scope.node.monitoringConfirmed;
$scope.config = config; $scope.config = config;
angular.extend($scope, { angular.extend($scope, {
center: { center: {
@ -119,6 +123,15 @@ angular.module('ffffng')
return $scope.nodeForm && $scope.nodeForm[field].$invalid && submitted; return $scope.nodeForm && $scope.nodeForm[field].$invalid && submitted;
}; };
$scope.monitoringInitialConfirmationRequired = function () {
return $scope.node.monitoring
&& ($scope.action === 'create' || $scope.node.email !== initialEmail || !initialMonitoring);
};
$scope.monitoringConfirmationPending = function () {
return $scope.node.monitoring && initialMonitoring && !monitoringConfirmed;
};
var duplicateError = { var duplicateError = {
hostname: 'Der Knotenname ist bereits vergeben. Bitte wähle einen anderen.', hostname: 'Der Knotenname ist bereits vergeben. Bitte wähle einen anderen.',
key: 'Für den VPN-Schlüssel gibt es bereits einen Eintrag.', key: 'Für den VPN-Schlüssel gibt es bereits einen Eintrag.',

11
app/styles/_mixins.scss Normal file
View file

@ -0,0 +1,11 @@
@mixin not-selectable {
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
}
%not-selectable {
@include not-selectable;
}

View file

@ -173,9 +173,9 @@ $pager-disabled-color: $gray;
// Form States // Form States
$state-warning-text: darken($brand-warning, 10%); $state-warning-text: darken($brand-warning, 30%);
$state-warning-bg: lighten($brand-warning, 30%); $state-warning-bg: lighten($brand-warning, 20%);
$state-warning-border: darken(adjust-hue($state-warning-bg, -10), 3%); $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%);
@ -184,7 +184,7 @@ $state-success-bg: lighten($brand-success, 30%);
$state-success-border: darken(adjust-hue($state-success-bg, -10), 5%); $state-success-border: darken(adjust-hue($state-success-bg, -10), 5%);
$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), 7%); $state-info-border: darken(adjust-hue($state-info-bg, -10), 10%);
// ToolTip // ToolTip

View file

@ -2,6 +2,8 @@
@import "_variables"; @import "_variables";
@import "_mixins";
@import "../bower_components/sass-bootstrap/lib/bootstrap"; @import "../bower_components/sass-bootstrap/lib/bootstrap";
@import "../bower_components/font-awesome/scss/font-awesome"; @import "../bower_components/font-awesome/scss/font-awesome";
@ -26,7 +28,7 @@ input {
} }
label { label {
@extend .control-label; @extend .control-label, %not-selectable;
margin-top: 10px; margin-top: 10px;
cursor: pointer; cursor: pointer;

View file

@ -7,11 +7,11 @@ f-node-form {
@extend .alert, .alert-danger; @extend .alert, .alert-danger;
} }
.node-data, .contact-data, .node-position { .node-data, .contact-data, .node-position, .monitoring-data {
@extend .well; @extend .well;
} }
.hostname, .key, .mac, .nickname, .email, .coords { .hostname, .key, .mac, .nickname, .email, .coords, .monitoring {
@extend .form-group; @extend .form-group;
.feedback { .feedback {
@ -28,6 +28,56 @@ f-node-form {
} }
} }
.monitoring {
display: table;
label {
display: table-cell;
}
input {
display: table-cell;
width: 18px;
height: 18px;
margin: {
top: 0;
right: 10px;
}
}
}
.monitoring-icon {
@extend .fa, .fa-heartbeat, .pull-left;
font-size: 36px;
color: $brand-primary;
}
.monitoring-confirmation-info {
@extend .alert, .alert-info;
}
.monitoring-confirmation-pending-info {
@extend .alert, .alert-warning;
}
.monitoring-confirmation-info, .monitoring-confirmation-pending-info {
display: table;
.icon {
@extend .fa, .fa-exclamation-triangle;
font-size: 24px;
padding-right: 15px;
}
.icon, .message {
display: table-cell;
}
}
.coords input.has-coords { .coords input.has-coords {
padding-right: 25px; padding-right: 25px;
} }

View file

@ -83,6 +83,65 @@
</div> </div>
</div> </div>
</div> </div>
<div class="monitoring-data">
<h3>Möchtest Du Status-E-Mails bekommen?</h3>
<i class="monitoring-icon"></i>
<p class="help-block">
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
</p>
<div class="row clearfix">
<div class="col-md-12">
<div class="monitoring">
<input type="checkbox" id="monitoring" name="monitoring" ng-model="node.monitoring" />
<label for="monitoring">
Informiert mich, wenn mein Knoten offline ist
</label>
</div>
</div>
<div class="col-md-12" ng-if="monitoringInitialConfirmationRequired()">
<div class="monitoring-confirmation-info" role="alert">
<i class="icon"></i>
<div class="message">
<p>
Zur Bestätigung Deiner E-Mail-Adresse schicken wir Dir nach dem Speichern Deiner Knotendaten
eine E-Mail mit einem Bestätiguns-Link. Erst nach der Bestätigung deiner E-Mail-Adresse
wirst Du informiert, falls Dein Knoten längere Zeit offline ist.
</p>
<p>
Die Inbetriebnahme Deines Knotens kannst Du selbstverständlich unabhängig von der Bestätigung
immer sofort duchführen.
</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="monitoringConfirmationPending() && !monitoringInitialConfirmationRequired()">
<div class="monitoring-confirmation-pending-info" role="alert">
<i class="icon"></i>
<div class="message">
<p>
Deine E-Mail-Adresse ist noch nicht bestätigt. Erst nach der Bestätigung deiner E-Mail-Adresse
wirst Du informiert, falls Dein Knoten längere Zeit offline ist.
</p>
<p>
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,
entferne einfach das Häkchen oberhalb dieser Box.
</p>
<p>
Die Inbetriebnahme Deines Knotens kannst Du selbstverständlich unabhängig von der Bestätigung
immer sofort duchführen.
</p>
<p ng-if="node.email">
<i class="fa fa-envelope-o"></i> <strong>{{node.email}}</strong>
</p>
</div>
</div>
</div>
</div>
</div>
<div class="buttons"> <div class="buttons">
<button class="save {{action}}" type="submit" ng-switch="action"> <button class="save {{action}}" type="submit" ng-switch="action">
<span ng-switch-when="create"><i class="fa fa-dot-circle-o"></i> Knoten anmelden</span> <span ng-switch-when="create"><i class="fa fa-dot-circle-o"></i> Knoten anmelden</span>

View file

@ -12,7 +12,7 @@ angular.module('ffffng').factory('NodeResource', function (
return _.extend({}, req.body, req.params); return _.extend({}, req.body, req.params);
} }
var nodeFields = ['hostname', 'key', 'email', 'nickname', 'mac', 'coords']; var nodeFields = ['hostname', 'key', 'email', 'nickname', 'mac', 'coords', 'monitoring'];
function getValidNodeData(reqData) { function getValidNodeData(reqData) {
var node = {}; var node = {};

View file

@ -8,7 +8,9 @@ angular.module('ffffng')
email: '# Kontakt: ', email: '# Kontakt: ',
coords: '# Koordinaten: ', coords: '# Koordinaten: ',
mac: '# MAC: ', mac: '# MAC: ',
token: '# Token: ' token: '# Token: ',
monitoring: '# Monitoring: ',
monitoringToken: '# Monitoring-Token: '
}; };
function generateToken() { function generateToken() {
@ -50,15 +52,34 @@ angular.module('ffffng')
return null; return null;
} }
function writeNodeFile(isUpdate, token, node, callback) { function writeNodeFile(isUpdate, token, node, nodeSecrets, callback) {
var filename = var filename =
config.server.peersPath + '/' + (node.hostname + '@' + node.mac + '@' + node.key + '@' + token).toLowerCase(); config.server.peersPath + '/' + (node.hostname + '@' + node.mac + '@' + node.key + '@' + token).toLowerCase();
var data = ''; var data = '';
_.each(linePrefixes, function (prefix, key) { _.each(linePrefixes, function (prefix, key) {
var value = key === 'token' ? token : node[key]; var value;
if (_.isUndefined(value)) { switch (key) {
value = ''; case 'monitoring':
if (node.monitoring && node.monitoringConfirmed) {
value = 'aktiv';
} else if (node.monitoring && !node.monitoringConfirmed) {
value = 'pending';
} else {
value = '';
}
break;
case 'monitoringToken':
value = nodeSecrets.monitoringToken || '';
break;
default:
value = key === 'token' ? token : node[key];
if (_.isUndefined(value)) {
value = _.isUndefined(nodeSecrets[key]) ? '' : nodeSecrets[key];
}
break;
} }
data += prefix + value + '\n'; data += prefix + value + '\n';
}); });
@ -81,8 +102,14 @@ angular.module('ffffng')
return callback(error); return callback(error);
} }
var file = files[0]; try {
fs.unlinkSync(file); var file = files[0];
fs.unlinkSync(file);
}
catch (error) {
console.log(error);
return callback({data: 'Could not remove old node data.', type: ErrorTypes.internalError});
}
} else { } else {
error = checkNoDuplicates(null, node); error = checkNoDuplicates(null, node);
if (error) { if (error) {
@ -122,6 +149,7 @@ angular.module('ffffng')
var lines = fs.readFileSync(file).toString(); var lines = fs.readFileSync(file).toString();
var node = {}; var node = {};
var nodeSecrets = {};
_.each(lines.split('\n'), function (line) { _.each(lines.split('\n'), function (line) {
var entries = {}; var entries = {};
@ -141,20 +169,92 @@ angular.module('ffffng')
} }
_.each(entries, function (value, key) { _.each(entries, function (value, key) {
node[key] = value; if (key === 'monitoring') {
var active = value === 'aktiv';
var pending = value === 'pending';
node.monitoring = active || pending;
node.monitoringConfirmed = active;
} else if (key === 'monitoringToken') {
nodeSecrets.monitoringToken = value;
} else {
node[key] = value;
}
}); });
}); });
callback(null, node);
callback(null, node, nodeSecrets);
} }
return { return {
createNode: function (node, callback) { createNode: function (node, callback) {
var token = generateToken(); var token = generateToken();
writeNodeFile(false, token, node, callback); var nodeSecrets = {};
node.monitoringConfirmed = false;
if (node.monitoring) {
nodeSecrets.monitoringToken = generateToken();
}
writeNodeFile(false, token, node, nodeSecrets, function (err, token, node) {
if (err) {
return callback(err);
}
if (node.monitoring && !node.monitoringConfirmed) {
// TODO: Send mail...
}
return callback(null, token, node);
});
}, },
updateNode: function (token, node, callback) { updateNode: function (token, node, callback) {
writeNodeFile(true, token, node, callback); this.getNodeData(token, function (err, currentNode, nodeSecrets) {
if (err) {
return callback(err);
}
var monitoringConfirmed = false;
var monitoringToken = '';
if (node.monitoring) {
if (!currentNode.monitoring) {
// monitoring just has been enabled
monitoringConfirmed = false;
monitoringToken = generateToken();
} else {
// monitoring is still enabled
if (currentNode.email != node.email) {
// new email so we need a new token and a reconfirmation
monitoringConfirmed = false;
monitoringToken = generateToken();
} else {
// email unchanged, keep token and confirmation state
monitoringConfirmed = currentNode.monitoringConfirmed;
monitoringToken = nodeSecrets.monitoringToken;
}
}
}
node.monitoringConfirmed = monitoringConfirmed;
nodeSecrets.monitoringToken = monitoringToken;
writeNodeFile(true, token, node, nodeSecrets, function (err, token, node) {
if (err) {
return callback(err);
}
if (node.monitoring && !node.monitoringConfirmed) {
// TODO: Send mail...
}
return callback(null, token, node);
});
});
}, },
deleteNode: function (token, callback) { deleteNode: function (token, callback) {