Add GUI validations for node and repo editors (fixes #153)

This commit is contained in:
Jakob Borg 2014-05-14 07:55:00 -03:00
parent 8b4282fe28
commit dc2f83e522
3 changed files with 108 additions and 63 deletions

File diff suppressed because one or more lines are too long

View File

@ -19,7 +19,7 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http) {
$scope.errors = [];
$scope.seenError = '';
$scope.model = {};
$scope.repos = [];
$scope.repos = {};
// Strings before bools look better
$scope.settings = [
@ -65,25 +65,6 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http) {
}
}
function nodeCompare(a, b) {
if (typeof a.Name !== 'undefined' && typeof b.Name !== 'undefined') {
if (a.Name < b.Name)
return -1;
return a.Name > b.Name;
}
if (a.NodeID < b.NodeID) {
return -1;
}
return a.NodeID > b.NodeID;
}
function repoCompare(a, b) {
if (a.Directory < b.Directory) {
return -1;
}
return a.Directory > b.Directory;
}
$scope.refresh = function () {
$http.get(urlbase + '/system').success(function (data) {
getSucceeded();
@ -91,9 +72,9 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http) {
}).error(function () {
getFailed();
});
$scope.repos.forEach(function (repo) {
$http.get(urlbase + '/model?repo=' + encodeURIComponent(repo.ID)).success(function (data) {
$scope.model[repo.ID] = data;
Object.keys($scope.repos).forEach(function (id) {
$http.get(urlbase + '/model?repo=' + encodeURIComponent(id)).success(function (data) {
$scope.model[id] = data;
});
});
$http.get(urlbase + '/connections').success(function (data) {
@ -272,6 +253,7 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http) {
$scope.editingExisting = true;
$scope.editingSelf = (nodeCfg.NodeID == $scope.myID);
$scope.currentNode.AddressesStr = nodeCfg.Addresses.join(', ');
$scope.nodeEditor.$setPristine();
$('#editNode').modal({backdrop: 'static', keyboard: true});
};
@ -279,6 +261,7 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http) {
$scope.currentNode = {AddressesStr: 'dynamic'};
$scope.editingExisting = false;
$scope.editingSelf = false;
$scope.nodeEditor.$setPristine();
$('#editNode').modal({backdrop: 'static', keyboard: true});
};
@ -293,8 +276,8 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http) {
});
$scope.config.Nodes = $scope.nodes;
for (var i = 0; i < $scope.repos.length; i++) {
$scope.repos[i].Nodes = $scope.repos[i].Nodes.filter(function (n) {
for (var id in repos) {
$scope.repos[id].Nodes = $scope.repos[id].Nodes.filter(function (n) {
return n.NodeID !== $scope.currentNode.NodeID;
});
}
@ -367,18 +350,24 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http) {
return str;
};
$scope.repoList = function () {
return repoList($scope.repos);
}
$scope.editRepo = function (nodeCfg) {
$scope.currentRepo = $.extend({selectedNodes: {}}, nodeCfg);
$scope.currentRepo.Nodes.forEach(function (n) {
$scope.currentRepo.selectedNodes[n.NodeID] = true;
});
$scope.editingExisting = true;
$scope.repoEditor.$setPristine();
$('#editRepo').modal({backdrop: 'static', keyboard: true});
};
$scope.addRepo = function () {
$scope.currentRepo = {selectedNodes: {}};
$scope.editingExisting = false;
$scope.repoEditor.$setPristine();
$('#editRepo').modal({backdrop: 'static', keyboard: true});
};
@ -397,20 +386,8 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http) {
}
delete repoCfg.selectedNodes;
done = false;
for (i = 0; i < $scope.repos.length; i++) {
if ($scope.repos[i].ID === repoCfg.ID) {
$scope.repos[i] = repoCfg;
done = true;
break;
}
}
if (!done) {
$scope.repos.push(repoCfg);
}
$scope.config.Repositories = $scope.repos;
$scope.repos[repoCfg.ID] = repoCfg;
$scope.config.Repositories = repoList($scope.repos);
$http.post(urlbase + '/config', JSON.stringify($scope.config), {headers: {'Content-Type': 'application/json'}});
};
@ -421,11 +398,8 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http) {
return;
}
$scope.repos = $scope.repos.filter(function (r) {
return r.ID !== $scope.currentRepo.ID;
});
$scope.config.Repositories = $scope.repos;
delete $scope.repos[$scope.currentRepo.ID];
$scope.config.Repositories = repoList($scope.repos);
$scope.configInSync = false;
$http.post(urlbase + '/config', JSON.stringify($scope.config), {headers: {'Content-Type': 'application/json'}});
@ -448,8 +422,7 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http) {
$scope.nodes = $scope.config.Nodes;
$scope.nodes.sort(nodeCompare);
$scope.repos = $scope.config.Repositories;
$scope.repos.sort(repoCompare);
$scope.repos = repoMap($scope.config.Repositories);
$scope.refresh();
});
@ -463,6 +436,42 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http) {
setInterval($scope.refresh, 10000);
});
function nodeCompare(a, b) {
if (typeof a.Name !== 'undefined' && typeof b.Name !== 'undefined') {
if (a.Name < b.Name)
return -1;
return a.Name > b.Name;
}
if (a.NodeID < b.NodeID) {
return -1;
}
return a.NodeID > b.NodeID;
}
function repoCompare(a, b) {
if (a.Directory < b.Directory) {
return -1;
}
return a.Directory > b.Directory;
}
function repoMap(l) {
var m = {};
l.forEach(function (r) {
m[r.ID] = r;
});
return m;
}
function repoList(m) {
var l = [];
for (var id in m) {
l.push(m[id])
}
l.sort(repoCompare);
return l;
}
function decimals(val, num) {
var digits, decs;
@ -540,7 +549,12 @@ syncthing.filter('alwaysNumber', function () {
syncthing.filter('chunkID', function () {
return function (input) {
return input.match(/.{1,6}/g).join(' ');
if (input === undefined)
return "";
var parts = input.match(/.{1,6}/g);
if (!parts)
return "";
return parts.join(' ');
}
});
@ -555,3 +569,24 @@ syncthing.directive('optionEditor', function () {
template: '<input type="text" ng-model="config.Options[setting.id]"></input>',
};
});
syncthing.directive('uniqueRepo', function() {
return {
require: 'ngModel',
link: function(scope, elm, attrs, ctrl) {
ctrl.$parsers.unshift(function(viewValue) {
if (scope.editingExisting) {
// we shouldn't validate
ctrl.$setValidity('uniqueRepo', true);
} else if (scope.repos[viewValue]) {
// the repo exists already
ctrl.$setValidity('uniqueRepo', false);
} else {
// the repo is unique
ctrl.$setValidity('uniqueRepo', true);
}
return viewValue;
});
}
};
});

View File

@ -131,7 +131,7 @@
<div class="panel panel-default">
<div class="panel-heading"><h3 class="panel-title">Repositories</h3></div>
<div class="panel-body">
<ul class="list-unstyled" ng-repeat="repo in repos">
<ul class="list-unstyled" ng-repeat="repo in repoList()">
<li>
<span class="text-monospace">{{repo.Directory}}</span>
<span ng-if="model[repo.ID].invalid" class="label label-danger">{{model[repo.ID].invalid}}</span>
@ -358,12 +358,15 @@
<h4 ng-show="editingExisting" class="modal-title">Edit Node</h4>
</div>
<div class="modal-body">
<form role="form">
<div class="form-group">
<form role="form" name="nodeEditor">
<div class="form-group" ng-class="{'has-error': nodeEditor.nodeID.$invalid && nodeEditor.nodeID.$dirty}">
<label for="nodeID">Node ID</label>
<input ng-if="!editingExisting" id="nodeID" class="form-control" type="text" ng-model="currentNode.NodeID"></input>
<input ng-if="!editingExisting" name="nodeID" id="nodeID" class="form-control" type="text" ng-model="currentNode.NodeID" required></input>
<div ng-if="editingExisting" class="well well-sm">{{currentNode.NodeID | chunkID}}</div>
<p class="help-block">The node ID can be found in the "Add Node" dialog on the other node. Spaces are ignored.</p>
<p class="help-block">
<span ng-if="nodeEditor.nodeID.$valid || nodeEditor.nodeID.$pristine">The node ID to enter here can be found in the "Add Node" dialog on the other node. Spaces are ignored.</span>
<span ng-if="nodeEditor.nodeID.$error.required && nodeEditor.nodeID.$dirty">The node ID cannot be blank.</span>
</p>
</div>
<div class="form-group">
<label for="name">Name</label>
@ -382,7 +385,7 @@
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" ng-click="saveNode()">Save</button>
<button type="button" class="btn btn-primary" ng-click="saveNode()" ng-disabled="nodeEditor.$invalid">Save</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button ng-if="editingExisting && !editingSelf" type="button" class="btn btn-danger pull-left" ng-click="deleteNode()">Delete</button>
</div>
@ -401,16 +404,23 @@
<h4 ng-show="editingExisting" class="modal-title">Edit Repository</h4>
</div>
<div class="modal-body">
<form role="form">
<div class="form-group">
<form role="form" name="repoEditor">
<div class="form-group" ng-class="{'has-error': repoEditor.repoID.$invalid && repoEditor.repoID.$dirty}">
<label for="repoID">Repository ID</label>
<input placeholder="documents" ng-disabled="editingExisting" id="repoID" class="form-control" type="text" ng-model="currentRepo.ID"></input>
<p class="help-block">Short, unique identifier for the repository. Must be the same on all cluster nodes.</p>
<input name="repoID" placeholder="documents" ng-disabled="editingExisting" id="repoID" class="form-control" type="text" ng-model="currentRepo.ID" required unique-repo></input>
<p class="help-block">
<span ng-if="repoEditor.repoID.$valid || repoEditor.repoID.$pristine">Short identifier for the repository. Must be the same on all cluster nodes.</span>
<span ng-if="repoEditor.repoID.$error.uniqueRepo">The repository ID must be unique.</span>
<span ng-if="repoEditor.repoID.$error.required && repoEditor.repoID.$dirty">The repository ID cannot be blank.</span>
</p>
</div>
<div class="form-group">
<div class="form-group" ng-class="{'has-error': repoEditor.repoPath.$invalid && repoEditor.repoPath.$dirty}">
<label for="repoPath">Repository Path</label>
<input placeholder="~/Documents" id="repoPath" class="form-control" type="text" ng-model="currentRepo.Directory"></input>
<p class="help-block">Path to the repository on the local computer. Will be created if it does not exist.</p>
<input name="repoPath" placeholder="~/Documents" id="repoPath" class="form-control" type="text" ng-model="currentRepo.Directory" required></input>
<p class="help-block">
<span ng-if="repoEditor.repoPath.$valid || repoEditor.repoPath.$pristine">Path to the repository on the local computer. Will be created if it does not exist.</span>
<span ng-if="repoEditor.repoPath.$error.required && repoEditor.repoPath.$dirty">The repository path cannot be blank.</span>
</p>
</div>
<div class="form-group">
<div class="checkbox">
@ -435,7 +445,7 @@
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" ng-click="saveRepo()">Save</button>
<button type="button" class="btn btn-primary" ng-click="saveRepo()" ng-disabled="repoEditor.$invalid">Save</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button ng-if="editingExisting" type="button" class="btn btn-danger pull-left" ng-click="deleteRepo()">Delete</button>
</div>