gui, lib/model: Display list of files needed by remote (fixes #4369)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4559 LGTM: AudriusButkevicius, calmh
This commit is contained in:
@@ -59,6 +59,7 @@
|
||||
"Device ID": "Device ID",
|
||||
"Device Identification": "Device Identification",
|
||||
"Device Name": "Device Name",
|
||||
"Device that last modified the item": "Device that last modified the item",
|
||||
"Devices": "Devices",
|
||||
"Disabled": "Disabled",
|
||||
"Disconnected": "Disconnected",
|
||||
@@ -130,6 +131,7 @@
|
||||
"Latest Change": "Latest Change",
|
||||
"Learn more": "Learn more",
|
||||
"Listeners": "Listeners",
|
||||
"Loading data...": "Loading data...",
|
||||
"Local Discovery": "Local Discovery",
|
||||
"Local State": "Local State",
|
||||
"Local State (Total)": "Local State (Total)",
|
||||
@@ -138,6 +140,8 @@
|
||||
"Maximum Age": "Maximum Age",
|
||||
"Metadata Only": "Metadata Only",
|
||||
"Minimum Free Disk Space": "Minimum Free Disk Space",
|
||||
"Mod. Device": "Mod. Device",
|
||||
"Mod. Time": "Mod. Time",
|
||||
"Move to top of queue": "Move to top of queue",
|
||||
"Multi level wildcard (matches multiple directory levels)": "Multi level wildcard (matches multiple directory levels)",
|
||||
"Never": "Never",
|
||||
@@ -221,6 +225,7 @@
|
||||
"Shutdown Complete": "Shutdown Complete",
|
||||
"Simple File Versioning": "Simple File Versioning",
|
||||
"Single level wildcard (matches within a directory only)": "Single level wildcard (matches within a directory only)",
|
||||
"Size": "Size",
|
||||
"Smallest First": "Smallest First",
|
||||
"Source Code": "Source Code",
|
||||
"Stable releases and release candidates": "Stable releases and release candidates",
|
||||
@@ -268,6 +273,7 @@
|
||||
"This is a major version upgrade.": "This is a major version upgrade.",
|
||||
"This setting controls the free space required on the home (i.e., index database) disk.": "This setting controls the free space required on the home (i.e., index database) disk.",
|
||||
"Time": "Time",
|
||||
"Time the item was last modified": "Time the item was last modified",
|
||||
"Trash Can File Versioning": "Trash Can File Versioning",
|
||||
"Type": "Type",
|
||||
"Unavailable": "Unavailable",
|
||||
|
||||
@@ -603,6 +603,12 @@
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="deviceStatus(deviceCfg) == 'syncing'">
|
||||
<th><span class="fa fa-fw fa-exchange"></span> <span translate>Out of Sync Items</span></th>
|
||||
<td class="text-right">
|
||||
<a href="" ng-click="showRemoteNeed(deviceCfg)">{{completion[deviceCfg.deviceID]._needItems | alwaysNumber}} <span translate>items</span>, ~{{completion[deviceCfg.deviceID]._needBytes | binary}}B</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><span class="fa fa-fw fa-link"></span> <span translate>Address</span></th>
|
||||
<td ng-if="connections[deviceCfg.deviceID].connected" class="text-right">
|
||||
@@ -722,6 +728,7 @@
|
||||
<ng-include src="'syncthing/usagereport/usageReportPreviewModalView.html'"></ng-include>
|
||||
<ng-include src="'syncthing/transfer/neededFilesModalView.html'"></ng-include>
|
||||
<ng-include src="'syncthing/transfer/failedFilesModalView.html'"></ng-include>
|
||||
<ng-include src="'syncthing/transfer/remoteNeededFilesModalView.html'"></ng-include>
|
||||
<ng-include src="'syncthing/core/majorUpgradeModalView.html'"></ng-include>
|
||||
<ng-include src="'syncthing/core/aboutModalView.html'"></ng-include>
|
||||
<ng-include src="'syncthing/core/discoveryFailuresModalView.html'"></ng-include>
|
||||
|
||||
@@ -45,7 +45,6 @@ angular.module('syncthing.core')
|
||||
$scope.progress = {};
|
||||
$scope.version = {};
|
||||
$scope.needed = [];
|
||||
$scope.neededTotal = 0;
|
||||
$scope.neededCurrentPage = 1;
|
||||
$scope.neededPageSize = 10;
|
||||
$scope.failed = {};
|
||||
@@ -56,6 +55,7 @@ angular.module('syncthing.core')
|
||||
$scope.globalChangeEvents = {};
|
||||
$scope.metricRates = false;
|
||||
$scope.folderPathErrors = {};
|
||||
resetRemoteNeed();
|
||||
|
||||
try {
|
||||
$scope.metricRates = (window.localStorage["metricRates"] == "true");
|
||||
@@ -241,7 +241,8 @@ angular.module('syncthing.core')
|
||||
};
|
||||
$scope.completion[arg.data.id] = {
|
||||
_total: 100,
|
||||
_needBytes: 0
|
||||
_needBytes: 0,
|
||||
_needItems: 0
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -389,7 +390,8 @@ angular.module('syncthing.core')
|
||||
$scope.devices.forEach(function (deviceCfg) {
|
||||
$scope.completion[deviceCfg.deviceID] = {
|
||||
_total: 100,
|
||||
_needBytes: 0
|
||||
_needBytes: 0,
|
||||
_needItems: 0
|
||||
};
|
||||
});
|
||||
$scope.devices.sort(deviceCompare);
|
||||
@@ -431,7 +433,7 @@ angular.module('syncthing.core')
|
||||
}
|
||||
}
|
||||
$scope.listenersFailed = listenersFailed;
|
||||
$scope.listenersTotal = Object.keys(data.connectionServiceStatus).length;
|
||||
$scope.listenersTotal = $scope.sizeOf(data.connectionServiceStatus);
|
||||
|
||||
$scope.discoveryTotal = data.discoveryMethods;
|
||||
var discoveryFailed = [];
|
||||
@@ -476,21 +478,24 @@ angular.module('syncthing.core')
|
||||
}
|
||||
|
||||
function recalcCompletion(device) {
|
||||
var total = 0, needed = 0, deletes = 0;
|
||||
var total = 0, needed = 0, deletes = 0, items = 0;
|
||||
for (var folder in $scope.completion[device]) {
|
||||
if (folder === "_total" || folder === '_needBytes') {
|
||||
if (folder === "_total" || folder === '_needBytes' || folder === '_needItems') {
|
||||
continue;
|
||||
}
|
||||
total += $scope.completion[device][folder].globalBytes;
|
||||
needed += $scope.completion[device][folder].needBytes;
|
||||
items += $scope.completion[device][folder].needItems;
|
||||
deletes += $scope.completion[device][folder].needDeletes;
|
||||
}
|
||||
if (total == 0) {
|
||||
$scope.completion[device]._total = 100;
|
||||
$scope.completion[device]._needBytes = 0;
|
||||
$scope.completion[device]._needItems = 0;
|
||||
} else {
|
||||
$scope.completion[device]._total = Math.floor(100 * (1 - needed / total));
|
||||
$scope.completion[device]._needBytes = needed
|
||||
$scope.completion[device]._needItems = items;
|
||||
}
|
||||
|
||||
if (needed == 0 && deletes > 0) {
|
||||
@@ -498,7 +503,6 @@ angular.module('syncthing.core')
|
||||
// to do. Drop down the completion percentage to indicate
|
||||
// that we have stuff to do.
|
||||
$scope.completion[device]._total = 95;
|
||||
$scope.completion[device]._needBytes = 0;
|
||||
}
|
||||
|
||||
console.log("recalcCompletion", device, $scope.completion[device]);
|
||||
@@ -616,7 +620,6 @@ angular.module('syncthing.core')
|
||||
merged.push(item);
|
||||
});
|
||||
$scope.needed = merged;
|
||||
$scope.neededTotal = data.total;
|
||||
}
|
||||
|
||||
function pathJoin(base, name) {
|
||||
@@ -638,6 +641,12 @@ angular.module('syncthing.core')
|
||||
return $scope.config.options && $scope.config.options.defaultFolderPath && !$scope.editingExisting && $scope.folderEditor.folderPath.$pristine
|
||||
}
|
||||
|
||||
function resetRemoteNeed() {
|
||||
$scope.remoteNeed = {};
|
||||
$scope.remoteNeedFolders = [];
|
||||
$scope.remoteNeedDevice = undefined;
|
||||
}
|
||||
|
||||
$scope.neededPageChanged = function (page) {
|
||||
$scope.neededCurrentPage = page;
|
||||
refreshNeed($scope.neededFolder);
|
||||
@@ -656,6 +665,20 @@ angular.module('syncthing.core')
|
||||
$scope.failedPageSize = perpage;
|
||||
};
|
||||
|
||||
$scope.refreshRemoteNeed = function (folder, page, perpage) {
|
||||
var url = urlbase + '/db/remoteneed?device=' + $scope.remoteNeedDevice.deviceID;
|
||||
url += '&folder=' + encodeURIComponent(folder);
|
||||
url += "&page=" + page + "&perpage=" + perpage;
|
||||
$http.get(url).success(function (data) {
|
||||
if ($scope.remoteNeedDevice !== '') {
|
||||
$scope.remoteNeed[folder] = data;
|
||||
}
|
||||
}).error(function (err) {
|
||||
$scope.remoteNeed[folder] = undefined;
|
||||
$scope.emitHTTPError(err);
|
||||
});
|
||||
};
|
||||
|
||||
var refreshDeviceStats = debounce(function () {
|
||||
$http.get(urlbase + "/stats/device").success(function (data) {
|
||||
$scope.deviceStats = data;
|
||||
@@ -965,7 +988,7 @@ angular.module('syncthing.core')
|
||||
}
|
||||
|
||||
// enumerate notifications
|
||||
if ($scope.openNoAuth || !$scope.configInSync || Object.keys($scope.deviceRejections).length > 0 || Object.keys($scope.folderRejections).length > 0 || $scope.errorList().length > 0 || !online) {
|
||||
if ($scope.openNoAuth || !$scope.configInSync || $scope.sizeOf($scope.deviceRejections) > 0 || $scope.sizeOf($scope.folderRejections) > 0 || $scope.errorList().length > 0 || !online) {
|
||||
notifyCount++;
|
||||
}
|
||||
|
||||
@@ -1623,17 +1646,14 @@ angular.module('syncthing.core')
|
||||
|
||||
$scope.deviceFolders = function (deviceCfg) {
|
||||
var folders = [];
|
||||
for (var folderID in $scope.folders) {
|
||||
var devices = $scope.folders[folderID].devices;
|
||||
for (var i = 0; i < devices.length; i++) {
|
||||
if (devices[i].deviceID === deviceCfg.deviceID) {
|
||||
folders.push(folderID);
|
||||
$scope.folderList().forEach(function (folder) {
|
||||
for (var i = 0; i < folder.devices.length; i++) {
|
||||
if (folder.devices[i].deviceID === deviceCfg.deviceID) {
|
||||
folders.push(folder.id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
folders.sort(folderCompare);
|
||||
});
|
||||
return folders;
|
||||
};
|
||||
|
||||
@@ -1729,11 +1749,25 @@ angular.module('syncthing.core')
|
||||
$('#needed').modal().on('hidden.bs.modal', function () {
|
||||
$scope.neededFolder = undefined;
|
||||
$scope.needed = undefined;
|
||||
$scope.neededTotal = 0;
|
||||
$scope.neededCurrentPage = 1;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.showRemoteNeed = function (device) {
|
||||
resetRemoteNeed();
|
||||
$scope.remoteNeedDevice = device;
|
||||
$scope.deviceFolders(device).forEach(function(folder) {
|
||||
if ($scope.completion[device.deviceID][folder].needItems === 0) {
|
||||
return;
|
||||
}
|
||||
$scope.remoteNeedFolders.push(folder);
|
||||
$scope.refreshRemoteNeed(folder, 1, 10);
|
||||
});
|
||||
$('#remoteNeed').modal().on('hidden.bs.modal', function () {
|
||||
resetRemoteNeed();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.showFailed = function (folder) {
|
||||
$scope.failedCurrent = $scope.failed[folder];
|
||||
$scope.failedFolderPath = $scope.folders[folder].path;
|
||||
@@ -1900,12 +1934,16 @@ angular.module('syncthing.core')
|
||||
// pseudo main. called on all definitions assigned
|
||||
initController();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$scope.toggleUnits = function () {
|
||||
$scope.metricRates = !$scope.metricRates;
|
||||
try {
|
||||
window.localStorage["metricRates"] = $scope.metricRates;
|
||||
} catch (exception) { }
|
||||
}
|
||||
};
|
||||
|
||||
$scope.sizeOf = function (dict) {
|
||||
return Object.keys(dict).length;
|
||||
};
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
<table class="table table-striped table-condensed">
|
||||
|
||||
<tr dir-paginate="f in needed | itemsPerPage: neededPageSize" current-page="neededCurrentPage" total-items="neededTotal" pagination-id="needed">
|
||||
<tr dir-paginate="f in needed | itemsPerPage: neededPageSize" current-page="neededCurrentPage" total-items="neededItems(neededFolder)" pagination-id="needed">
|
||||
|
||||
<!-- Icon -->
|
||||
<td class="small-data col-xs-2">
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
é<modal id="remoteNeed" status="info" icon="exchange" heading="{{'Out of Sync Items' | translate}} - {{deviceName(remoteNeedDevice)}}" large="yes" closeable="yes">
|
||||
<div class="modal-body">
|
||||
<div ng-if="sizeOf(remoteNeed) == 0">
|
||||
<span translate>Loading data...</span>
|
||||
</div>
|
||||
<div ng-if="sizeOf(remoteNeed) > 0">
|
||||
<div class="panel panel-default" ng-repeat="folder in remoteNeedFolders" ng-if="remoteNeed[folder] && remoteNeed[folder].files.length > 0">
|
||||
<button class="btn panel-heading" data-toggle="collapse" data-target="#remoteNeed-{{folder}}" aria-expanded="false">
|
||||
<h4 class="panel-title">
|
||||
<span>{{folderLabel(folder)}}</span>
|
||||
</h4>
|
||||
</button>
|
||||
<div id="remoteNeed-{{folder}}" class="panel-collapse collapse">
|
||||
<div class="panel-body">
|
||||
<table class="table table-striped table-dynamic">
|
||||
<thead>
|
||||
<tr>
|
||||
<th translate>Path</th>
|
||||
<th translate>Size</th>
|
||||
<th><span tooltip data-original-title="{{'Time the item was last modified' | translate}}" translate>Mod. Time</span></th>
|
||||
<th><span tooltip data-original-title="{{'Device that last modified the item' | translate}}" translate>Mod. Device</span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr dir-paginate="file in remoteNeed[folder].files | itemsPerPage: remoteNeed[folder].perpage" current-page="remoteNeed[folder].page" total-items="completion[remoteNeedDevice.deviceID][folder].needItems" pagination-id="'remoteNeed-' + folder">
|
||||
<td>{{file.name}}</td>
|
||||
<td><span ng-hide="file.type == 'DIRECTORY'">{{file.size | binary}}B</span></td>
|
||||
<td>{{file.modified | date:"yyyy-MM-dd HH:mm:ss"}}</td>
|
||||
<td ng-if="file.modifiedBy">{{friendlyNameFromShort(file.modifiedBy)}}</td>
|
||||
<td ng-if="!file.modifiedBy"><span translate>Unknown</span></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<dir-pagination-controls on-page-change="refreshRemoteNeed(folder, newPageNumber, remoteNeed[folder].perpage)" pagination-id="'remoteNeed-' + folder"></dir-pagination-controls>
|
||||
<ul class="pagination pull-right">
|
||||
<li ng-repeat="option in [10, 25, 50]" ng-class="{ active: remoteNeed[folder].perpage == option }">
|
||||
<a href="#" ng-click="refreshRemoteNeed(folder, remoteNeed[folder].page, option)">{{option}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</modal>
|
||||
Reference in New Issue
Block a user