Add folder label in addition to ID (fixes #966)

An auto generated ID is suggested on folder creation to reduce conflicts with
folders created on other devices.
This commit is contained in:
Lars K.W. Gohlke 2016-03-11 09:48:46 +00:00 committed by Jakob Borg
parent 1875f7287e
commit 16c3d39fd2
13 changed files with 160 additions and 104 deletions

View File

@ -141,6 +141,10 @@ table.table-condensed td {
white-space: nowrap; white-space: nowrap;
} }
table.table-condensed td.no-overflow-ellipse {
white-space: normal;
}
.folder-advanced{ .folder-advanced{
background-color: hsla(0,0%,99%,1); background-color: hsla(0,0%,99%,1);
border: 1px solid hsla(0, 0%, 95%, 1); border: 1px solid hsla(0, 0%, 95%, 1);

View File

@ -165,16 +165,19 @@
</div> </div>
<div class="panel-body"> <div class="panel-body">
<p> <p>
<span translate translate-value-device="{{ deviceName(findDevice(event.data.device)) }}" translate-value-folder="{{ event.data.folder }}"> <span ng-if="event.data.folderLabel.length == 0" translate translate-value-device="{{ deviceName(findDevice(event.data.device)) }}" translate-value-folder="{{ event.data.folder }}">
{%device%} wants to share folder "{%folder%}". {%device%} wants to share folder "{%folder%}".
</span> </span>
<span ng-if="event.data.folderLabel.length != 0" translate translate-value-device="{{ deviceName(findDevice(event.data.device)) }}" translate-value-folder="{{ event.data.folder }}" translate-value-folderLabel="{{ event.data.folderLabel }}">
{%device%} wants to share folder "{%folderLabel%}" ({%folder%}).
</span>
<span translate ng-if="folders[event.data.folder]">Share this folder?</span> <span translate ng-if="folders[event.data.folder]">Share this folder?</span>
<span translate ng-if="!folders[event.data.folder]">Add new folder?</span> <span translate ng-if="!folders[event.data.folder]">Add new folder?</span>
</p> </p>
</div> </div>
<div class="panel-footer clearfix"> <div class="panel-footer clearfix">
<div class="pull-right"> <div class="pull-right">
<button type="button" class="btn btn-sm btn-success" ng-click="addFolderAndShare(event.data.folder, event.data.device)" ng-if="!folders[event.data.folder]"> <button type="button" class="btn btn-sm btn-success" ng-click="addFolderAndShare(event.data.folder, event.data.folderLabel, event.data.device)" ng-if="!folders[event.data.folder]">
<span class="fa fa-check"></span>&nbsp;<span translate>Add</span> <span class="fa fa-check"></span>&nbsp;<span translate>Add</span>
</button> </button>
<button type="button" class="btn btn-sm btn-success" ng-click="shareFolderWithDevice(event.data.folder, event.data.device)" ng-if="folders[event.data.folder]"> <button type="button" class="btn btn-sm btn-success" ng-click="shareFolderWithDevice(event.data.folder, event.data.device)" ng-if="folders[event.data.folder]">
@ -222,7 +225,9 @@
<div class="panel-progress" ng-show="folderStatus(folder) == 'syncing'" ng-attr-style="width: {{syncPercentage(folder.id)}}%"></div> <div class="panel-progress" ng-show="folderStatus(folder) == 'syncing'" ng-attr-style="width: {{syncPercentage(folder.id)}}%"></div>
<div class="panel-progress" ng-show="folderStatus(folder) == 'scanning' && scanProgress[folder.id] != undefined" ng-attr-style="width: {{scanPercentage(folder.id)}}%"></div> <div class="panel-progress" ng-show="folderStatus(folder) == 'scanning' && scanProgress[folder.id] != undefined" ng-attr-style="width: {{scanPercentage(folder.id)}}%"></div>
<h3 class="panel-title"> <h3 class="panel-title">
<span class="fa hidden-xs fa-fw" ng-class="[folder.readOnly ? 'fa-lock' : 'fa-folder']"></span>{{folder.id}} <span class="fa hidden-xs fa-fw" ng-class="[folder.readOnly ? 'fa-lock' : 'fa-folder']"></span>
<span ng-show="folder.label.length == 0">{{folder.id}}</span>
<span title="{{folder.id}}" ng-show="folder.label.length != 0">{{folder.label}}</span>
<span class="pull-right text-{{folderClass(folder)}}" ng-switch="folderStatus(folder)"> <span class="pull-right text-{{folderClass(folder)}}" ng-switch="folderStatus(folder)">
<span ng-switch-when="unknown"><span class="hidden-xs" translate>Unknown</span><span class="visible-xs">&#9724;</span></span> <span ng-switch-when="unknown"><span class="hidden-xs" translate>Unknown</span><span class="visible-xs">&#9724;</span></span>
<span ng-switch-when="unshared"><span class="hidden-xs" translate>Unshared</span><span class="visible-xs">&#9724;</span></span> <span ng-switch-when="unshared"><span class="hidden-xs" translate>Unshared</span><span class="visible-xs">&#9724;</span></span>
@ -247,6 +252,10 @@
<div class="panel-body"> <div class="panel-body">
<table class="table table-condensed table-striped"> <table class="table table-condensed table-striped">
<tbody> <tbody>
<tr ng-show="folder.label != undefined && folder.label.length > 0">
<th><span class="fa fa-fw fa-folder-open"></span>&nbsp;<span translate>Folder ID</span></th>
<td class="text-right no-overflow-ellipse">{{folder.id}}</td>
</tr>
<tr> <tr>
<th><span class="fa fa-fw fa-folder-open"></span>&nbsp;<span translate>Folder Path</span></th> <th><span class="fa fa-fw fa-folder-open"></span>&nbsp;<span translate>Folder Path</span></th>
<td class="text-right" title="{{folder.path}}">{{folder.path}}</td> <td class="text-right" title="{{folder.path}}">{{folder.path}}</td>
@ -583,6 +592,7 @@
<script src="vendor/jquery/jquery-2.0.3.min.js"></script> <script src="vendor/jquery/jquery-2.0.3.min.js"></script>
<script src="vendor/angular/angular.min.js"></script> <script src="vendor/angular/angular.min.js"></script>
<script src="vendor/angular/angular-translate.min.js"></script> <script src="vendor/angular/angular-translate.min.js"></script>
<script src="vendor/angular/angular-translate-handler-log.min.js"></script>
<script src="vendor/angular/angular-translate-loader.min.js"></script> <script src="vendor/angular/angular-translate-loader.min.js"></script>
<script src="vendor/angular/angular-dirPagination.js"></script> <script src="vendor/angular/angular-dirPagination.js"></script>
<script src="vendor/bootstrap/js/bootstrap.min.js"></script> <script src="vendor/bootstrap/js/bootstrap.min.js"></script>

View File

@ -29,8 +29,6 @@ syncthing.config(function ($httpProvider, $translateProvider, LocaleServiceProvi
return { return {
response: function onResponse(response) { response: function onResponse(response) {
var headers = response.headers(); var headers = response.headers();
var responseVersion;
var deviceIdShort;
// angular template cache sends no headers // angular template cache sends no headers
if(Object.keys(headers).length === 0) { if(Object.keys(headers).length === 0) {
@ -40,7 +38,7 @@ syncthing.config(function ($httpProvider, $translateProvider, LocaleServiceProvi
if (!deviceId) { if (!deviceId) {
deviceId = headers['x-syncthing-id']; deviceId = headers['x-syncthing-id'];
if (deviceId) { if (deviceId) {
deviceIdShort = deviceId.substring(0, 5); var deviceIdShort = deviceId.substring(0, 5);
$httpProvider.defaults.xsrfHeaderName = 'X-CSRF-Token-' + deviceIdShort; $httpProvider.defaults.xsrfHeaderName = 'X-CSRF-Token-' + deviceIdShort;
$httpProvider.defaults.xsrfCookieName = 'CSRF-Token-' + deviceIdShort; $httpProvider.defaults.xsrfCookieName = 'CSRF-Token-' + deviceIdShort;
} }
@ -57,6 +55,7 @@ syncthing.config(function ($httpProvider, $translateProvider, LocaleServiceProvi
prefix: 'assets/lang/lang-', prefix: 'assets/lang/lang-',
suffix: '.json' suffix: '.json'
}); });
$translateProvider.useMissingTranslationHandlerLog();
LocaleServiceProvider.setAvailableLocales(validLangs); LocaleServiceProvider.setAvailableLocales(validLangs);
LocaleServiceProvider.setDefaultLocale('en'); LocaleServiceProvider.setDefaultLocale('en');
@ -114,9 +113,14 @@ function decimals(val, num) {
} }
function randomString(len) { function randomString(len) {
var i, result = '', chars = '01234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-'; var chars = '01234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-';
for (i = 0; i < len; i++) { return randomStringFromCharset(len, chars);
result += chars[Math.round(Math.random() * (chars.length - 1))]; }
function randomStringFromCharset(len, charset) {
var result = '';
for (var i = 0; i < len; i++) {
result += charset[Math.round(Math.random() * (charset.length - 1))];
} }
return result; return result;
} }
@ -150,7 +154,6 @@ function debounce(func, wait) {
context = this; context = this;
args = arguments; args = arguments;
timestamp = Date.now(); timestamp = Date.now();
var callNow = !timeout;
if (!timeout) { if (!timeout) {
timeout = setTimeout(later, wait); timeout = setTimeout(later, wait);
result = func.apply(context, args); result = func.apply(context, args);

View File

@ -90,7 +90,7 @@ angular.module('syncthing.core')
refreshFolderStats(); refreshFolderStats();
$http.get(urlbase + '/system/version').success(function (data) { $http.get(urlbase + '/system/version').success(function (data) {
if ($scope.version.version && $scope.version.version != data.version) { if ($scope.version.version && $scope.version.version !== data.version) {
// We already have a version response, but it differs from // We already have a version response, but it differs from
// the new one. Reload the full GUI in case it's changed. // the new one. Reload the full GUI in case it's changed.
document.location.reload(true); document.location.reload(true);
@ -271,10 +271,10 @@ angular.module('syncthing.core')
} }
for (var folder in $scope.progress) { for (var folder in $scope.progress) {
if (!(folder in progress)) { if (!(folder in progress)) {
if ($scope.neededFolder == folder) { if ($scope.neededFolder === folder) {
refreshNeed(folder); refreshNeed(folder);
} }
} else if ($scope.neededFolder == folder) { } else if ($scope.neededFolder === folder) {
for (file in $scope.progress[folder]) { for (file in $scope.progress[folder]) {
if (!(file in progress[folder])) { if (!(file in progress[folder])) {
refreshNeed(folder); refreshNeed(folder);
@ -322,7 +322,7 @@ angular.module('syncthing.core')
$scope.scanProgress[data.folder] = { $scope.scanProgress[data.folder] = {
current: data.current, current: data.current,
total: data.total, total: data.total,
rate: data.rate, rate: data.rate
}; };
console.log("FolderScanProgress", data); console.log("FolderScanProgress", data);
}); });
@ -374,8 +374,8 @@ angular.module('syncthing.core')
// authentication configured, and the magic setting to silence the // authentication configured, and the magic setting to silence the
// warning isn't set, then yell at the user. // warning isn't set, then yell at the user.
var guiCfg = $scope.config.gui; var guiCfg = $scope.config.gui;
$scope.openNoAuth = guiCfg.address.substr(0, 4) != "127." $scope.openNoAuth = guiCfg.address.substr(0, 4) !== "127."
&& guiCfg.address.substr(0, 6) != "[::1]:" && guiCfg.address.substr(0, 6) !== "[::1]:"
&& (!guiCfg.user || !guiCfg.password) && (!guiCfg.user || !guiCfg.password)
&& !guiCfg.insecureAdminAccess; && !guiCfg.insecureAdminAccess;
@ -509,7 +509,7 @@ angular.module('syncthing.core')
url += "&page=" + $scope.neededCurrentPage; url += "&page=" + $scope.neededCurrentPage;
url += "&perpage=" + $scope.neededPageSize; url += "&perpage=" + $scope.neededPageSize;
$http.get(url).success(function (data) { $http.get(url).success(function (data) {
if ($scope.neededFolder == folder) { if ($scope.neededFolder === folder) {
console.log("refreshNeed", folder, data); console.log("refreshNeed", folder, data);
parseNeeded(data); parseNeeded(data);
} }
@ -631,10 +631,10 @@ angular.module('syncthing.core')
$scope.folderClass = function (folderCfg) { $scope.folderClass = function (folderCfg) {
var status = $scope.folderStatus(folderCfg); var status = $scope.folderStatus(folderCfg);
if (status == 'idle') { if (status === 'idle') {
return 'success'; return 'success';
} }
if (status == 'syncing' || status == 'scanning') { if (status === 'syncing' || status === 'scanning') {
return 'primary'; return 'primary';
} }
if (status === 'unknown') { if (status === 'unknown') {
@ -668,14 +668,14 @@ angular.module('syncthing.core')
} }
var pct = 100 * $scope.scanProgress[folder].current / $scope.scanProgress[folder].total; var pct = 100 * $scope.scanProgress[folder].current / $scope.scanProgress[folder].total;
return Math.floor(pct); return Math.floor(pct);
} };
$scope.scanRate = function (folder) { $scope.scanRate = function (folder) {
if (!$scope.scanProgress[folder]) { if (!$scope.scanProgress[folder]) {
return 0; return 0;
} }
return $scope.scanProgress[folder].rate; return $scope.scanProgress[folder].rate;
} };
$scope.scanRemaining = function (folder) { $scope.scanRemaining = function (folder) {
// Formats the remaining scan time as a string. Includes days and // Formats the remaining scan time as a string. Includes days and
@ -685,23 +685,22 @@ angular.module('syncthing.core')
// 2h 32m // 2h 32m
// 4d 2h // 4d 2h
var res = [];
if (!$scope.scanProgress[folder]) { if (!$scope.scanProgress[folder]) {
return ""; return "";
} }
// Calculate remaining bytes and seconds based on our current // Calculate remaining bytes and seconds based on our current
// rate. // rate.
var remainingBytes = $scope.scanProgress[folder].total - $scope.scanProgress[folder].current; var remainingBytes = $scope.scanProgress[folder].total - $scope.scanProgress[folder].current;
var seconds = remainingBytes / $scope.scanProgress[folder].rate; var seconds = remainingBytes / $scope.scanProgress[folder].rate;
// Round up to closest ten seconds to avoid flapping too much to // Round up to closest ten seconds to avoid flapping too much to
// and fro. // and fro.
seconds = Math.ceil(seconds / 10) * 10; seconds = Math.ceil(seconds / 10) * 10;
// Separate out the number of days. // Separate out the number of days.
var days = 0; var days = 0;
var res = [];
if (seconds >= 86400) { if (seconds >= 86400) {
days = Math.floor(seconds / 86400); days = Math.floor(seconds / 86400);
res.push('' + days + 'd') res.push('' + days + 'd')
@ -718,20 +717,20 @@ angular.module('syncthing.core')
var d = new Date(1970, 0, 1).setSeconds(seconds); var d = new Date(1970, 0, 1).setSeconds(seconds);
if (days == 0) { if (days === 0) {
// Format minutes only if we're within a day of completion. // Format minutes only if we're within a day of completion.
var f = $filter('date')(d, "m'm'"); var f = $filter('date')(d, "m'm'");
res.push(f); res.push(f);
} }
if (days == 0 && hours == 0) { if (days === 0 && hours === 0) {
// Format seconds only when we're within an hour of completion. // Format seconds only when we're within an hour of completion.
var f = $filter('date')(d, "ss's'"); var f = $filter('date')(d, "ss's'");
res.push(f); res.push(f);
} }
return res.join(' '); return res.join(' ');
} };
$scope.deviceStatus = function (deviceCfg) { $scope.deviceStatus = function (deviceCfg) {
if ($scope.deviceFolders(deviceCfg).length === 0) { if ($scope.deviceFolders(deviceCfg).length === 0) {
@ -802,9 +801,9 @@ angular.module('syncthing.core')
$scope.findDevice = function (deviceID) { $scope.findDevice = function (deviceID) {
var matches = $scope.devices.filter(function (n) { var matches = $scope.devices.filter(function (n) {
return n.deviceID == deviceID; return n.deviceID === deviceID;
}); });
if (matches.length != 1) { if (matches.length !== 1) {
return undefined; return undefined;
} }
return matches[0]; return matches[0];
@ -1029,17 +1028,15 @@ angular.module('syncthing.core')
}; };
$scope.saveDeviceConfig = function (deviceCfg) { $scope.saveDeviceConfig = function (deviceCfg) {
var done, i;
deviceCfg.addresses = deviceCfg._addressesStr.split(',').map(function (x) { deviceCfg.addresses = deviceCfg._addressesStr.split(',').map(function (x) {
return x.trim(); return x.trim();
}); });
done = false; var done = false;
for (i = 0; i < $scope.devices.length; i++) { for (var i = 0; i < $scope.devices.length && !done; i++) {
if ($scope.devices[i].deviceID === deviceCfg.deviceID) { if ($scope.devices[i].deviceID === deviceCfg.deviceID) {
$scope.devices[i] = deviceCfg; $scope.devices[i] = deviceCfg;
done = true; done = true;
break;
} }
} }
@ -1059,7 +1056,7 @@ angular.module('syncthing.core')
if (deviceCfg.selectedFolders[id]) { if (deviceCfg.selectedFolders[id]) {
var found = false; var found = false;
for (i = 0; i < $scope.folders[id].devices.length; i++) { for (i = 0; i < $scope.folders[id].devices.length; i++) {
if ($scope.folders[id].devices[i].deviceID == deviceCfg.deviceID) { if ($scope.folders[id].devices[i].deviceID === deviceCfg.deviceID) {
found = true; found = true;
break; break;
} }
@ -1072,7 +1069,7 @@ angular.module('syncthing.core')
} }
} else { } else {
$scope.folders[id].devices = $scope.folders[id].devices.filter(function (n) { $scope.folders[id].devices = $scope.folders[id].devices.filter(function (n) {
return n.deviceID != deviceCfg.deviceID; return n.deviceID !== deviceCfg.deviceID;
}); });
} }
} }
@ -1097,10 +1094,8 @@ angular.module('syncthing.core')
}; };
$scope.thisDevice = function () { $scope.thisDevice = function () {
var i, n; for (var i = 0; i < $scope.devices.length; i++) {
var n = $scope.devices[i];
for (i = 0; i < $scope.devices.length; i++) {
n = $scope.devices[i];
if (n.deviceID === $scope.myID) { if (n.deviceID === $scope.myID) {
return n; return n;
} }
@ -1142,7 +1137,7 @@ angular.module('syncthing.core')
$scope.directoryList = []; $scope.directoryList = [];
$scope.$watch('currentFolder.path', function (newvalue) { $scope.$watch('currentFolder.path', function (newvalue) {
if (newvalue && newvalue.trim().charAt(0) == '~') { if (newvalue && newvalue.trim().charAt(0) === '~') {
$scope.currentFolder.path = $scope.system.tilde + newvalue.trim().substring(1); $scope.currentFolder.path = $scope.system.tilde + newvalue.trim().substring(1);
} }
$http.get(urlbase + '/system/browse', { $http.get(urlbase + '/system/browse', {
@ -1154,7 +1149,7 @@ angular.module('syncthing.core')
$scope.editFolder = function (folderCfg) { $scope.editFolder = function (folderCfg) {
$scope.currentFolder = angular.copy(folderCfg); $scope.currentFolder = angular.copy(folderCfg);
if ($scope.currentFolder.path.slice(-1) == $scope.system.pathSeparator) { if ($scope.currentFolder.path.slice(-1) === $scope.system.pathSeparator) {
$scope.currentFolder.path = $scope.currentFolder.path.slice(0, -1); $scope.currentFolder.path = $scope.currentFolder.path.slice(0, -1);
} }
$scope.currentFolder.selectedDevices = {}; $scope.currentFolder.selectedDevices = {};
@ -1202,30 +1197,8 @@ angular.module('syncthing.core')
$scope.addFolder = function () { $scope.addFolder = function () {
$scope.currentFolder = { $scope.currentFolder = {
selectedDevices: {}
};
$scope.currentFolder.rescanIntervalS = 60;
$scope.currentFolder.minDiskFreePct = 1;
$scope.currentFolder.maxConflicts = 10;
$scope.currentFolder.order = "random";
$scope.currentFolder.fileVersioningSelector = "none";
$scope.currentFolder.trashcanClean = 0;
$scope.currentFolder.simpleKeep = 5;
$scope.currentFolder.staggeredMaxAge = 365;
$scope.currentFolder.staggeredCleanInterval = 3600;
$scope.currentFolder.staggeredVersionsPath = "";
$scope.currentFolder.externalCommand = "";
$scope.currentFolder.autoNormalize = true;
$scope.editingExisting = false;
$scope.folderEditor.$setPristine();
$('#editFolder').modal();
};
$scope.addFolderAndShare = function (folder, device) {
$scope.dismissFolderRejection(folder, device);
$scope.currentFolder = {
id: folder,
selectedDevices: {}, selectedDevices: {},
id: $scope.createRandomFolderId(),
rescanIntervalS: 60, rescanIntervalS: 60,
minDiskFreePct: 1, minDiskFreePct: 1,
maxConflicts: 10, maxConflicts: 10,
@ -1239,6 +1212,33 @@ angular.module('syncthing.core')
externalCommand: "", externalCommand: "",
autoNormalize: true autoNormalize: true
}; };
$scope.editingExisting = false;
$scope.folderEditor.$setPristine();
$('#editFolder').modal();
};
$scope.addFolderAndShare = function (folder, folderLabel, device) {
$scope.dismissFolderRejection(folder, device);
$scope.currentFolder = {
id: folder,
label: folderLabel,
selectedDevices: {},
rescanIntervalS: 60,
minDiskFreePct: 1,
maxConflicts: 10,
order: "random",
fileVersioningSelector: "none",
trashcanClean: 0,
simpleKeep: 5,
staggeredMaxAge: 365,
staggeredCleanInterval: 3600,
staggeredVersionsPath: "",
externalCommand: "",
autoNormalize: true,
viewFlags: {
importFromOtherDevice: true
}
};
$scope.currentFolder.selectedDevices[device] = true; $scope.currentFolder.selectedDevices[device] = true;
$scope.editingExisting = false; $scope.editingExisting = false;
@ -1256,10 +1256,8 @@ angular.module('syncthing.core')
}; };
$scope.saveFolder = function () { $scope.saveFolder = function () {
var folderCfg, done, i;
$('#editFolder').modal('hide'); $('#editFolder').modal('hide');
folderCfg = $scope.currentFolder; var folderCfg = $scope.currentFolder;
folderCfg.devices = []; folderCfg.devices = [];
folderCfg.selectedDevices[$scope.myID] = true; folderCfg.selectedDevices[$scope.myID] = true;
for (var deviceID in folderCfg.selectedDevices) { for (var deviceID in folderCfg.selectedDevices) {
@ -1329,7 +1327,7 @@ angular.module('syncthing.core')
$scope.sharesFolder = function (folderCfg) { $scope.sharesFolder = function (folderCfg) {
var names = []; var names = [];
folderCfg.devices.forEach(function (device) { folderCfg.devices.forEach(function (device) {
if (device.deviceID != $scope.myID) { if (device.deviceID !== $scope.myID) {
names.push($scope.deviceName($scope.findDevice(device.deviceID))); names.push($scope.deviceName($scope.findDevice(device.deviceID)));
} }
}); });
@ -1342,8 +1340,9 @@ angular.module('syncthing.core')
for (var folderID in $scope.folders) { for (var folderID in $scope.folders) {
var devices = $scope.folders[folderID].devices; var devices = $scope.folders[folderID].devices;
for (var i = 0; i < devices.length; i++) { for (var i = 0; i < devices.length; i++) {
if (devices[i].deviceID == deviceCfg.deviceID) { if (devices[i].deviceID === deviceCfg.deviceID) {
folders.push(folderID); var label = $scope.folders[folderID].label;
folders.push(label.length > 0 ? label : folderID);
break; break;
} }
} }
@ -1445,7 +1444,7 @@ angular.module('syncthing.core')
}; };
$scope.showFailed = function (folder) { $scope.showFailed = function (folder) {
$scope.failedCurrent = $scope.failed[folder] $scope.failedCurrent = $scope.failed[folder];
$('#failed').modal().on('hidden.bs.modal', function () { $('#failed').modal().on('hidden.bs.modal', function () {
$scope.failedCurrent = undefined; $scope.failedCurrent = undefined;
}); });
@ -1455,10 +1454,10 @@ angular.module('syncthing.core')
if (!$scope.failed[folder]) { if (!$scope.failed[folder]) {
return false; return false;
} }
if ($scope.failed[folder].length == 0) { if ($scope.failed[folder].length === 0) {
return false; return false;
} }
return true return true;
}; };
$scope.override = function (folder) { $scope.override = function (folder) {
@ -1492,7 +1491,7 @@ angular.module('syncthing.core')
url += "&page=" + $scope.neededCurrentPage; url += "&page=" + $scope.neededCurrentPage;
url += "&perpage=" + $scope.neededPageSize; url += "&perpage=" + $scope.neededPageSize;
$http.post(url).success(function (data) { $http.post(url).success(function (data) {
if ($scope.neededFolder == folder) { if ($scope.neededFolder === folder) {
console.log("bumpFile", folder, data); console.log("bumpFile", folder, data);
parseNeeded(data); parseNeeded(data);
} }
@ -1512,7 +1511,7 @@ angular.module('syncthing.core')
'netbsd': 'NetBSD', 'netbsd': 'NetBSD',
'linux': 'Linux', 'linux': 'Linux',
'windows': 'Windows', 'windows': 'Windows',
'solaris': 'Solaris', 'solaris': 'Solaris'
}[$scope.version.os] || $scope.version.os; }[$scope.version.os] || $scope.version.os;
var arch ={ var arch ={
@ -1521,7 +1520,7 @@ angular.module('syncthing.core')
'arm': 'ARM', 'arm': 'ARM',
'arm64': 'AArch64', 'arm64': 'AArch64',
'ppc64': 'PowerPC', 'ppc64': 'PowerPC',
'ppc64le': 'PowerPC (LE)', 'ppc64le': 'PowerPC (LE)'
}[$scope.version.arch] || $scope.version.arch; }[$scope.version.arch] || $scope.version.arch;
return $scope.version.version + ', ' + os + ' (' + arch + ')'; return $scope.version.version + ', ' + os + ' (' + arch + ')';
@ -1546,6 +1545,11 @@ angular.module('syncthing.core')
return 'text'; return 'text';
}; };
$scope.createRandomFolderId = function(){
var charset = '2345679abcdefghijkmnopqrstuvwxyzACDEFGHJKLMNPQRSTUVWXYZ';
return randomStringFromCharset(5, charset) + "-" + randomStringFromCharset(5, charset);
};
// pseudo main. called on all definitions assigned // pseudo main. called on all definitions assigned
initController(); initController();
}); });

View File

@ -60,8 +60,11 @@
<div class="row"> <div class="row">
<div class="col-md-4" ng-repeat="folder in folderList()"> <div class="col-md-4" ng-repeat="folder in folderList()">
<div class="checkbox"> <div class="checkbox">
<label> <label ng-if="folder.label.length == 0">
<input type="checkbox" ng-model="currentDevice.selectedFolders[folder.id]"> {{folder.id}} <input type="checkbox" ng-model="currentDevice.selectedFolders[folder.id]">&nbsp;{{folder.id}}
</label>
<label ng-if="folder.label.length != 0">
<input type="checkbox" ng-model="currentDevice.selectedFolders[folder.id]">&nbsp;<span title="{{folder.id}}">{{folder.label}}</span>
</label> </label>
</div> </div>
</div> </div>

View File

@ -11,11 +11,18 @@
<form role="form" name="folderEditor"> <form role="form" name="folderEditor">
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<div class="form-group" ng-class="{'has-error': folderEditor.folderLabel.$invalid && folderEditor.folderLabel.$dirty}">
<label for="folderLabel"><span translate>Folder Label</span></label>
<input name="folderLabel" id="folderLabel" class="form-control" type="text" ng-model="currentFolder.label" value="{{currentFolder.label}}"/>
<p class="help-block">
<span translate ng-if="folderEditor.folderLabel.$valid || folderEditor.folderLabel.$pristine">Optional descriptive label for the folder. Can be different on each device.</span>
</p>
</div>
<div class="form-group" ng-class="{'has-error': folderEditor.folderID.$invalid && folderEditor.folderID.$dirty}"> <div class="form-group" ng-class="{'has-error': folderEditor.folderID.$invalid && folderEditor.folderID.$dirty}">
<label for="folderID"><span translate>Folder ID</span></label> <label for="folderID"><span translate>Folder ID</span></label>
<input name="folderID" ng-readonly="editingExisting" id="folderID" class="form-control" type="text" ng-model="currentFolder.id" required unique-folder> <input name="folderID" ng-readonly="editingExisting || (!editingExisting && currentFolder.viewFlags.importFromOtherDevice)" id="folderID" class="form-control" type="text" ng-model="currentFolder.id" required unique-folder value="{{currentFolder.id}}"/>
<p class="help-block"> <p class="help-block">
<span translate ng-if="folderEditor.folderID.$valid || folderEditor.folderID.$pristine">Short identifier for the folder. Must be the same on all cluster devices.</span> <span translate ng-if="folderEditor.folderID.$valid || folderEditor.folderID.$pristine">Required identifier for the folder. Must be the same on all cluster devices.</span>
<span translate ng-if="folderEditor.folderID.$error.uniqueFolder">The folder ID must be unique.</span> <span translate ng-if="folderEditor.folderID.$error.uniqueFolder">The folder ID must be unique.</span>
<span translate ng-if="folderEditor.folderID.$error.required && folderEditor.folderID.$dirty">The folder ID cannot be blank.</span> <span translate ng-if="folderEditor.folderID.$error.required && folderEditor.folderID.$dirty">The folder ID cannot be blank.</span>
</p> </p>

View File

@ -51,8 +51,11 @@
<div class="panel panel-default" ng-repeat="folder in advancedConfig.folders"> <div class="panel panel-default" ng-repeat="folder in advancedConfig.folders">
<div class="panel-heading" role="tab" id="folder{{$index}}Heading" data-toggle="collapse" data-parent="#advancedAccordion" href="#folder{{$index}}Config" aria-expanded="true" aria-controls="folder{{$index}}Config" style="cursor: pointer"> <div class="panel-heading" role="tab" id="folder{{$index}}Heading" data-toggle="collapse" data-parent="#advancedAccordion" href="#folder{{$index}}Config" aria-expanded="true" aria-controls="folder{{$index}}Config" style="cursor: pointer">
<h4 class="panel-title"> <h4 ng-if="folder.label.length == 0" class="panel-title">
<span translate>Folder</span> "{{folder.id}}" <span translate>Folder</span> "{{folder.id}}"
</h4>
<h4 ng-if="folder.label.length != 0" class="panel-title">
<span translate>Folder</span> "{{folder.label}}" ({{folder.id}})
</h4> </h4>
</div> </div>
<div id="folder{{$index}}Config" class="panel-collapse collapse" role="tabpanel" aria-labelledby="folder{{$index}}Heading"> <div id="folder{{$index}}Config" class="panel-collapse collapse" role="tabpanel" aria-labelledby="folder{{$index}}Heading">

View File

@ -0,0 +1,6 @@
/*!
* angular-translate - v2.10.0 - 2016-02-28
*
* Copyright (c) 2016 The angular-translate team, Pascal Precht; Licensed MIT
*/
!function(a,b){"function"==typeof define&&define.amd?define([],function(){return b()}):"object"==typeof exports?module.exports=b():b()}(this,function(){function a(a){"use strict";return function(b){a.warn("Translation for "+b+" doesn't exist")}}return angular.module("pascalprecht.translate").factory("$translateMissingTranslationHandlerLog",a),a.$inject=["$log"],a.displayName="$translateMissingTranslationHandlerLog","pascalprecht.translate"});

File diff suppressed because one or more lines are too long

View File

@ -18,6 +18,7 @@ import (
type FolderConfiguration struct { type FolderConfiguration struct {
ID string `xml:"id,attr" json:"id"` ID string `xml:"id,attr" json:"id"`
Label string `xml:"label,attr" json:"label"`
RawPath string `xml:"path,attr" json:"path"` RawPath string `xml:"path,attr" json:"path"`
Devices []FolderDeviceConfiguration `xml:"device" json:"devices"` Devices []FolderDeviceConfiguration `xml:"device" json:"devices"`
ReadOnly bool `xml:"ro,attr" json:"readOnly"` ReadOnly bool `xml:"ro,attr" json:"readOnly"`

View File

@ -650,8 +650,9 @@ nextFolder:
if !m.folderSharedWithUnlocked(folder.ID, deviceID) { if !m.folderSharedWithUnlocked(folder.ID, deviceID) {
events.Default.Log(events.FolderRejected, map[string]string{ events.Default.Log(events.FolderRejected, map[string]string{
"folder": folder.ID, "folder": folder.ID,
"device": deviceID.String(), "folderLabel": folder.Label,
"device": deviceID.String(),
}) })
l.Infof("Unexpected folder ID %q sent from device %q; ensure that the folder exists and that this device is selected under \"Share With\" in the folder configuration.", folder.ID, deviceID) l.Infof("Unexpected folder ID %q sent from device %q; ensure that the folder exists and that this device is selected under \"Share With\" in the folder configuration.", folder.ID, deviceID)
continue continue
@ -1530,7 +1531,7 @@ func (m *Model) numHashers(folder string) int {
// generateClusterConfig returns a ClusterConfigMessage that is correct for // generateClusterConfig returns a ClusterConfigMessage that is correct for
// the given peer device // the given peer device
func (m *Model) generateClusterConfig(device protocol.DeviceID) protocol.ClusterConfigMessage { func (m *Model) generateClusterConfig(device protocol.DeviceID) protocol.ClusterConfigMessage {
cm := protocol.ClusterConfigMessage{ message := protocol.ClusterConfigMessage{
DeviceName: m.deviceName, DeviceName: m.deviceName,
ClientName: m.clientName, ClientName: m.clientName,
ClientVersion: m.clientVersion, ClientVersion: m.clientVersion,
@ -1539,8 +1540,9 @@ func (m *Model) generateClusterConfig(device protocol.DeviceID) protocol.Cluster
m.fmut.RLock() m.fmut.RLock()
for _, folder := range m.deviceFolders[device] { for _, folder := range m.deviceFolders[device] {
folderCfg := m.cfg.Folders()[folder] folderCfg := m.cfg.Folders()[folder]
cr := protocol.Folder{ protocolFolder := protocol.Folder{
ID: folder, ID: folder,
Label: folderCfg.Label,
} }
var flags uint32 var flags uint32
if folderCfg.ReadOnly { if folderCfg.ReadOnly {
@ -1552,7 +1554,7 @@ func (m *Model) generateClusterConfig(device protocol.DeviceID) protocol.Cluster
if folderCfg.IgnoreDelete { if folderCfg.IgnoreDelete {
flags |= protocol.FlagFolderIgnoreDelete flags |= protocol.FlagFolderIgnoreDelete
} }
cr.Flags = flags protocolFolder.Flags = flags
for _, device := range m.folderDevices[folder] { for _, device := range m.folderDevices[folder] {
// DeviceID is a value type, but with an underlying array. Copy it // DeviceID is a value type, but with an underlying array. Copy it
// so we don't grab aliases to the same array later on in device[:] // so we don't grab aliases to the same array later on in device[:]
@ -1560,7 +1562,7 @@ func (m *Model) generateClusterConfig(device protocol.DeviceID) protocol.Cluster
// TODO: Set read only bit when relevant, and when we have per device // TODO: Set read only bit when relevant, and when we have per device
// access controls. // access controls.
deviceCfg := m.cfg.Devices()[device] deviceCfg := m.cfg.Devices()[device]
cn := protocol.Device{ protocolDevice := protocol.Device{
ID: device[:], ID: device[:],
Name: deviceCfg.Name, Name: deviceCfg.Name,
Addresses: deviceCfg.Addresses, Addresses: deviceCfg.Addresses,
@ -1570,15 +1572,15 @@ func (m *Model) generateClusterConfig(device protocol.DeviceID) protocol.Cluster
} }
if deviceCfg.Introducer { if deviceCfg.Introducer {
cn.Flags |= protocol.FlagIntroducer protocolDevice.Flags |= protocol.FlagIntroducer
} }
cr.Devices = append(cr.Devices, cn) protocolFolder.Devices = append(protocolFolder.Devices, protocolDevice)
} }
cm.Folders = append(cm.Folders, cr) message.Folders = append(message.Folders, protocolFolder)
} }
m.fmut.RUnlock() m.fmut.RUnlock()
return cm return message
} }
func (m *Model) State(folder string) (string, time.Time, error) { func (m *Model) State(folder string) (string, time.Time, error) {

View File

@ -143,6 +143,7 @@ func (o *ClusterConfigMessage) GetOption(key string) string {
type Folder struct { type Folder struct {
ID string // max:256 ID string // max:256
Label string // max:256
Devices []Device // max:1000000 Devices []Device // max:1000000
Flags uint32 Flags uint32
Options []Option // max:64 Options []Option // max:64

View File

@ -656,6 +656,10 @@ Folder Structure:
\ ID (length + padded data) \ \ ID (length + padded data) \
/ / / /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
\ Label (length + padded data) \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Number of Devices | | Number of Devices |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ / / /
@ -674,6 +678,7 @@ Folder Structure:
struct Folder { struct Folder {
string ID<256>; string ID<256>;
string Label<256>;
Device Devices<1000000>; Device Devices<1000000>;
unsigned int Flags; unsigned int Flags;
Option Options<64>; Option Options<64>;
@ -683,6 +688,7 @@ struct Folder {
func (o Folder) XDRSize() int { func (o Folder) XDRSize() int {
return 4 + len(o.ID) + xdr.Padding(len(o.ID)) + return 4 + len(o.ID) + xdr.Padding(len(o.ID)) +
4 + len(o.Label) + xdr.Padding(len(o.Label)) +
4 + xdr.SizeOfSlice(o.Devices) + 4 + 4 + xdr.SizeOfSlice(o.Devices) + 4 +
4 + xdr.SizeOfSlice(o.Options) 4 + xdr.SizeOfSlice(o.Options)
} }
@ -706,6 +712,10 @@ func (o Folder) MarshalXDRInto(m *xdr.Marshaller) error {
return xdr.ElementSizeExceeded("ID", l, 256) return xdr.ElementSizeExceeded("ID", l, 256)
} }
m.MarshalString(o.ID) m.MarshalString(o.ID)
if l := len(o.Label); l > 256 {
return xdr.ElementSizeExceeded("Label", l, 256)
}
m.MarshalString(o.Label)
if l := len(o.Devices); l > 1000000 { if l := len(o.Devices); l > 1000000 {
return xdr.ElementSizeExceeded("Devices", l, 1000000) return xdr.ElementSizeExceeded("Devices", l, 1000000)
} }
@ -734,6 +744,7 @@ func (o *Folder) UnmarshalXDR(bs []byte) error {
} }
func (o *Folder) UnmarshalXDRFrom(u *xdr.Unmarshaller) error { func (o *Folder) UnmarshalXDRFrom(u *xdr.Unmarshaller) error {
o.ID = u.UnmarshalStringMax(256) o.ID = u.UnmarshalStringMax(256)
o.Label = u.UnmarshalStringMax(256)
_DevicesSize := int(u.UnmarshalUint32()) _DevicesSize := int(u.UnmarshalUint32())
if _DevicesSize < 0 { if _DevicesSize < 0 {
return xdr.ElementSizeExceeded("Devices", _DevicesSize, 1000000) return xdr.ElementSizeExceeded("Devices", _DevicesSize, 1000000)