Added delete feature for nodes.

This commit is contained in:
baldo 2016-05-16 18:27:03 +02:00
parent 39e7af6238
commit 79aadc85c2
20 changed files with 300 additions and 20 deletions

View file

@ -49,6 +49,7 @@
<script src="scripts/controllers/main.js"></script> <script src="scripts/controllers/main.js"></script>
<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/services/nodeService.js"></script> <script src="scripts/services/nodeService.js"></script>
@ -58,6 +59,7 @@
<script src="scripts/directives/nodeSaved.js"></script> <script src="scripts/directives/nodeSaved.js"></script>
<script src="scripts/directives/tokenForm.js"></script> <script src="scripts/directives/tokenForm.js"></script>
<script src="scripts/dialogs/confirmDeletionDialog.js"></script>
<script src="scripts/dialogs/outsideOfCommunityDialog.js"></script> <script src="scripts/dialogs/outsideOfCommunityDialog.js"></script>
<script src="scripts/validation/constraints.js"></script> <script src="scripts/validation/constraints.js"></script>

View file

@ -25,6 +25,11 @@ angular.module('ffffng', [
controller: 'UpdateNodeCtrl', controller: 'UpdateNodeCtrl',
title: 'Knotendaten ändern' title: 'Knotendaten ändern'
}) })
.when('/delete', {
templateUrl: 'views/deleteNodeForm.html',
controller: 'DeleteNodeCtrl',
title: 'Knoten löschen'
})
.otherwise({ .otherwise({
redirectTo: '/' redirectTo: '/'
}); });
@ -39,6 +44,9 @@ angular.module('ffffng', [
}, },
updateNode: function () { updateNode: function () {
$location.url('/update'); $location.url('/update');
},
deleteNode: function () {
$location.url('/delete');
} }
}; };
}) })

View file

@ -0,0 +1,26 @@
'use strict';
angular.module('ffffng')
.controller('DeleteNodeCtrl', function ($scope, Navigator, NodeService, config, ConfirmDeletionDialog) {
$scope.config = config;
$scope.token = undefined;
$scope.deleted = false;
$scope.onSubmitToken = function (token) {
$scope.token = token;
return NodeService.getNode(token)
.success(function (node) {
ConfirmDeletionDialog.open(node).result.then(function () {
NodeService.deleteNode(token)
.success(function () {
$scope.deleted = true;
$scope.hostname = node.hostname;
});
});
});
};
$scope.cancel = function () {
Navigator.home();
};
});

View file

@ -11,4 +11,8 @@ angular.module('ffffng')
$scope.updateNode = function () { $scope.updateNode = function () {
Navigator.updateNode(); Navigator.updateNode();
}; };
$scope.deleteNode = function () {
Navigator.deleteNode();
};
}); });

View file

@ -0,0 +1,29 @@
'use strict';
angular.module('ffffng')
.factory('ConfirmDeletionDialog', function ($uibModal, config) {
var ctrl = function ($scope, $uibModalInstance, node) {
$scope.node = node;
$scope.config = config;
$scope.proceed = function () {
$uibModalInstance.close();
};
$scope.cancel = function () {
$uibModalInstance.dismiss('cancel');
};
};
return {
open: function (node) {
return $uibModal.open({
controller: ctrl,
templateUrl: 'views/dialogs/confirmDeletionDialog.html',
resolve: {
node: function () { return node; }
}
});
}
};
});

View file

@ -33,7 +33,9 @@ angular.module('ffffng')
'templateUrl': 'views/directives/tokenForm.html', 'templateUrl': 'views/directives/tokenForm.html',
'scope': { 'scope': {
'onSubmit': '=fSubmit', 'onSubmit': '=fSubmit',
'onCancel': '=fCancel' 'onCancel': '=fCancel',
'submitIcon': '@fSubmitIcon',
'submitLabel': '@fSubmitLabel'
} }
}; };
}); });

View file

@ -11,6 +11,10 @@ angular.module('ffffng')
return $http.put('/api/node/' + token, node); return $http.put('/api/node/' + token, node);
}, },
'deleteNode': function (token) {
return $http.delete('/api/node/' + token);
},
'getNode': function (token) { 'getNode': function (token) {
return $http.get('/api/node/' + token); return $http.get('/api/node/' + token);
} }

View file

@ -8,10 +8,12 @@
@import "views/_main"; @import "views/_main";
@import "views/_newNodeForm"; @import "views/_newNodeForm";
@import "views/_updateNodeForm"; @import "views/_updateNodeForm";
@import "views/_deleteNodeForm";
@import "views/directives/_help"; @import "views/directives/_help";
@import "views/directives/_nodeForm"; @import "views/directives/_nodeForm";
@import "views/directives/_nodeSaved"; @import "views/directives/_nodeSaved";
@import "views/directives/_tokenForm"; @import "views/directives/_tokenForm";
@import "views/dialogs/_confirmDeletionDialog";
@import "views/dialogs/_outsideOfCommunityDialog"; @import "views/dialogs/_outsideOfCommunityDialog";
body { body {

View file

@ -0,0 +1,44 @@
.delete-node-form {
@extend .container;
}
.node-deleted {
@extend .jumbotron, .container;
.summary, .actions {
text-align: center;
}
.actions {
margin-bottom: 10px;
}
.token-hint {
font: {
weight: bold;
size: 32px;
}
text-align: center;
margin-top: 32px;
}
.summary {
.node {
@extend .well;
border: 3px dashed $gray;
font-family: monospace;
font-size: 32px;
}
margin: {
top: 30px;
bottom: 30px;
}
}
.back-button {
@extend .btn, .btn-lg, .btn-info;
}
}

View file

@ -9,11 +9,19 @@
bottom: 25px; bottom: 25px;
} }
.new-node, .update-node { .new-node, .update-node, .delete-node {
@extend .col-md-5; @extend .col-md-4;
button { button {
@extend .btn, .btn-lg, .btn-block; @extend .btn, .btn-lg, .btn-block;
text-overflow: ellipsis;
overflow: hidden;
padding: {
left: 10px;
right: 10px;
};
} }
} }
@ -29,10 +37,10 @@
} }
} }
.or { .delete-node {
@extend .col-md-2; button {
//@extend .btn-link;
text-align: center; }
} }
} }
} }

View file

@ -0,0 +1,33 @@
.confirm-deletion-dialog {
.modal-header {
h3 {
@extend .modal-title;
}
.cancel-icon {
@extend .fa, .fa-times;
position: relative;
float: right;
top: 10px;
right: 0;
margin-left: 15px;
cursor: pointer;
color: $gray;
}
}
.modal-footer {
.proceed {
@extend .btn;
margin-left: 5px;
&.proceed {
@extend .btn-primary;
}
}
}
}

View file

@ -1,4 +1,8 @@
f-token-form { f-token-form {
form {
margin-bottom: 10px;
}
.main-error { .main-error {
@extend .alert, .alert-danger; @extend .alert, .alert-danger;
} }

View file

@ -0,0 +1,56 @@
<div class="delete-node-form" ng-if="!deleted">
<h2>Knoten löschen</h2>
<div>
<p>
Um die Daten Deines Knotens zu löschen, benötigen wir den passenden Token (eine 16-stellige Folge aus
Ziffern und Buchstaben). Diesen hast Du beim ersten Anmelden Deines Knotens erhalten. Sinn des Tokens ist,
Dich davor zu schützen, dass Dritte unbefugt Deine Daten einsehen oder ändern können.
</p>
<p>
<strong>
Solltest Du den Token nicht mehr haben, wende Dich einfach per E-Mail an
<a href="mailto:{{ config.community.contactEmail }}">{{ config.community.contactEmail }}</a>.
</strong>
</p>
<f-token-form f-submit="onSubmitToken" f-cancel="cancel" f-submit-icon="fa-trash" f-submit-label="Knoten löschen"></f-token-form>
<p>
<em>
Hinweis: Nach dem Löschen kann der Knoten ggf. weiterhin in der Knotenkarte angezeigt werden. Dies
ist dann der Fall, wenn der Knoten eingeschaltet ist und in Reichweite eines anderen aktiven Knotens
steht. Die angezeigten Daten sind dann die während der Einrichtung des Knotens im Config-Mode
(Konfigurationsoberfläche des Routers) hinterlegten.
</em>
</p>
</div>
</div>
<div class="node-deleted" ng-if="deleted">
<h1>Erledigt!</h1>
<p>
Die Daten Deines Freifunk-Knotens sind gelöscht worden. Es kann jetzt noch bis zu 20 Minuten dauern,
bis die Änderungen überall wirksam werden und sich im <a href="{{ config.map.graphUrl }}" target="_blank">Knotengraph</a>
und in der <a href="{{ config.map.mapUrl }}" target="_blank">Knotenkarte</a> auswirken.
</p>
<div class="summary">
<span class="node">
<i class="fa fa-trash"></i>
{{hostname}}
</span>
</div>
<div class="actions">
<button class="back-button" ng-click="goHome()"><i class="fa fa-reply"></i> Zurück zum Anfang</button>
</div>
<p>
<em>
Hinweis: Nach dem Löschen kann der Knoten ggf. weiterhin in der Knotenkarte angezeigt werden. Dies
ist dann der Fall, wenn der Knoten eingeschaltet ist und in Reichweite eines anderen aktiven Knotens
steht. Die angezeigten Daten sind dann die während der Einrichtung des Knotens im Config-Mode
(Konfigurationsoberfläche des Routers) hinterlegten.
</em>
</p>
</div>

View file

@ -0,0 +1,18 @@
<div class="confirm-deletion-dialog">
<div class="modal-header">
<i class="cancel-icon" ng-click="cancel()"></i>
<h3>Soll der Knoten wirklich gelöscht werden?</h3>
</div>
<div class="modal-body">
<p>
Soll der Knoten „{{node.hostname}}“ wirklich endgültig gelöscht werden?
Du kannst ihn selbstverständlich später jederzeit erneut anmelden!
</p>
</div>
<div class="modal-footer">
<button class="cancel" ng-click="cancel()">Abbrechen</button>
<button class="proceed" ng-click="proceed()">
<i class="fa fa-trash"></i> Knoten löschen
</button>
</div>
</div>

View file

@ -10,7 +10,7 @@
</div> </div>
<div class="buttons"> <div class="buttons">
<button class="submit" type="submit"> <button class="submit" type="submit">
<i class="fa fa-pencil"></i> Knotendaten ändern <i class="fa {{submitIcon}}"></i> {{submitLabel}}
</button> </button>
<button class="cancel" type="reset" ng-click="onCancel()"> <button class="cancel" type="reset" ng-click="onCancel()">
<i class="fa fa-times"></i> Abbrechen <i class="fa fa-times"></i> Abbrechen

View file

@ -2,22 +2,24 @@
<h1>Willkommen!</h1> <h1>Willkommen!</h1>
<p> <p>
Du hast einen neuen {{ config.community.name }} Router (Knoten), den Du in Betrieb nehmen möchtest? Du hast Du hast einen neuen {{ config.community.name }} Router (Knoten), den Du in Betrieb nehmen möchtest? Du hast
schon einen Knoten in Betrieb und möchtest seine Daten ändern? Dann bist Du hier richtig! schon einen Knoten in Betrieb und möchtest seine Daten ändern? Oder Du möchtest einen Knoten, der nicht mehr
in Betrieb ist löschen? Dann bist Du hier richtig!
</p> </p>
<div class="select-action"> <div class="select-action">
<div class="new-node"> <div class="new-node">
<button ng-click="newNode()"><i class="fa fa-dot-circle-o"></i> Melde einen neuen Knoten an</button> <button ng-click="newNode()">
<i class="fa fa-dot-circle-o"></i> Neuen Knoten anmelden
</button>
</div> </div>
<div class="or">oder</div>
<div class="update-node"> <div class="update-node">
<button ng-click="updateNode()"><i class="fa fa-pencil"></i> Ändere die Daten Deines Knotens</button> <button ng-click="updateNode()">
<i class="fa fa-pencil"></i> Knotendaten ändern
</button>
</div>
<div class="delete-node">
<button ng-click="deleteNode()">
<i class="fa fa-trash"></i> Knoten löschen
</button>
</div> </div>
</div> </div>
<p>
<strong>
Hinweis:
Zum Entfernen von Knoten wende Dich bitte per E-Mail an
<a href="mailto:{{ config.community.contactEmail }}">{{ config.community.contactEmail }}</a>.
</strong>
</p>
</div> </div>

View file

@ -13,7 +13,7 @@
<a href="mailto:{{ config.community.contactEmail }}">{{ config.community.contactEmail }}</a>. <a href="mailto:{{ config.community.contactEmail }}">{{ config.community.contactEmail }}</a>.
</strong> </strong>
</p> </p>
<f-token-form f-submit="onSubmitToken" f-cancel="cancel"></f-token-form> <f-token-form f-submit="onSubmitToken" f-cancel="cancel" f-submit-icon="fa-pencil" f-submit-label="Knotendaten ändern"></f-token-form>
</div> </div>
<div ng-if="hasData()"> <div ng-if="hasData()">

View file

@ -80,6 +80,22 @@ angular.module('ffffng').factory('NodeResource', function (
}); });
}, },
delete: function (req, res) {
var data = getData(req);
var token = Strings.normalizeString(data.token);
if (!isValidToken(token)) {
return error(res, {data: 'Invalid token.', type: ErrorTypes.badRequest});
}
return NodeService.deleteNode(token, function (err) {
if (err) {
return error(res, err);
}
return success(res, {});
});
},
get: function (req, res) { get: function (req, res) {
var token = Strings.normalizeString(getData(req).token); var token = Strings.normalizeString(getData(req).token);
if (!isValidToken(token)) { if (!isValidToken(token)) {

View file

@ -5,6 +5,7 @@ angular.module('ffffng').factory('Router', function (app, NodeResource) {
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.get('/api/node/:token', NodeResource.get); app.get('/api/node/:token', NodeResource.get);
} }
}; };

View file

@ -101,6 +101,23 @@ angular.module('ffffng')
return callback(null, token, node); return callback(null, token, node);
} }
function deleteNodeFile(token, callback) {
var files = findNodeFiles('*@*@*@' + token);
if (files.length !== 1) {
return callback({data: 'Node not found.', type: ErrorTypes.notFound});
}
try {
fs.unlinkSync(files[0]);
}
catch (error) {
console.log(error);
return callback({data: 'Could not delete node.', type: ErrorTypes.internalError});
}
return callback(null);
}
function parseNodeFile(file, callback) { function parseNodeFile(file, callback) {
var lines = fs.readFileSync(file).toString(); var lines = fs.readFileSync(file).toString();
@ -140,6 +157,10 @@ angular.module('ffffng')
writeNodeFile(true, token, node, callback); writeNodeFile(true, token, node, callback);
}, },
deleteNode: function (token, callback) {
deleteNodeFile(token, callback);
},
getNodeData: function (token, callback) { getNodeData: function (token, callback) {
var files = findNodeFiles('*@*@*@' + token); var files = findNodeFiles('*@*@*@' + token);