2014-11-26 13:39:59 +01:00
|
|
|
angular.module('syncthing.core')
|
2015-03-22 15:35:08 +00:00
|
|
|
.config(function($locationProvider) {
|
2016-03-28 10:46:51 +00:00
|
|
|
$locationProvider.html5Mode({enabled: true, requireBase: false}).hashPrefix('!');
|
2015-03-22 15:35:08 +00:00
|
|
|
})
|
2018-01-01 14:39:23 +00:00
|
|
|
.controller('SyncthingController', function ($scope, $http, $location, LocaleService, Events, $filter, $q, $compile, $timeout, $rootScope) {
|
2014-11-26 13:39:59 +01:00
|
|
|
'use strict';
|
|
|
|
|
|
2014-11-28 19:39:33 +01:00
|
|
|
// private/helper definitions
|
|
|
|
|
|
2014-11-26 13:39:59 +01:00
|
|
|
var prevDate = 0;
|
|
|
|
|
var navigatingAway = false;
|
|
|
|
|
var online = false;
|
|
|
|
|
var restarting = false;
|
|
|
|
|
|
2014-11-28 19:39:33 +01:00
|
|
|
function initController() {
|
|
|
|
|
LocaleService.autoConfigLocale();
|
|
|
|
|
setInterval($scope.refresh, 10000);
|
2015-06-14 17:17:48 +03:00
|
|
|
Events.start();
|
2014-11-28 19:39:33 +01:00
|
|
|
}
|
|
|
|
|
|
2015-06-26 14:22:52 +02:00
|
|
|
// public/scope definitions
|
2014-11-28 19:39:33 +01:00
|
|
|
|
2014-11-26 13:39:59 +01:00
|
|
|
$scope.completion = {};
|
|
|
|
|
$scope.config = {};
|
|
|
|
|
$scope.configInSync = true;
|
|
|
|
|
$scope.connections = {};
|
|
|
|
|
$scope.errors = [];
|
|
|
|
|
$scope.model = {};
|
|
|
|
|
$scope.myID = '';
|
|
|
|
|
$scope.devices = [];
|
2014-12-27 23:12:12 +00:00
|
|
|
$scope.deviceRejections = {};
|
2016-06-26 10:47:23 +00:00
|
|
|
$scope.discoveryCache = {};
|
2014-12-27 23:12:12 +00:00
|
|
|
$scope.folderRejections = {};
|
2014-11-26 13:39:59 +01:00
|
|
|
$scope.protocolChanged = false;
|
|
|
|
|
$scope.reportData = {};
|
2017-10-15 07:45:15 +00:00
|
|
|
$scope.reportDataPreview = '';
|
2017-10-12 06:16:46 +00:00
|
|
|
$scope.reportDataPreviewVersion = '';
|
2017-10-15 07:45:15 +00:00
|
|
|
$scope.reportDataPreviewDiff = false;
|
2014-11-26 13:39:59 +01:00
|
|
|
$scope.reportPreview = false;
|
|
|
|
|
$scope.folders = {};
|
|
|
|
|
$scope.seenError = '';
|
|
|
|
|
$scope.upgradeInfo = null;
|
2014-12-07 20:21:12 +00:00
|
|
|
$scope.deviceStats = {};
|
|
|
|
|
$scope.folderStats = {};
|
2014-11-26 13:39:59 +01:00
|
|
|
$scope.progress = {};
|
2015-04-09 11:32:54 +02:00
|
|
|
$scope.version = {};
|
2015-04-25 22:53:44 +01:00
|
|
|
$scope.needed = [];
|
|
|
|
|
$scope.neededCurrentPage = 1;
|
|
|
|
|
$scope.neededPageSize = 10;
|
2015-06-26 14:22:52 +02:00
|
|
|
$scope.failed = {};
|
2015-08-26 23:49:06 +01:00
|
|
|
$scope.scanProgress = {};
|
2016-06-07 07:46:45 +00:00
|
|
|
$scope.themes = [];
|
2016-12-21 16:35:20 +00:00
|
|
|
$scope.globalChangeEvents = {};
|
2017-03-31 06:32:54 +00:00
|
|
|
$scope.metricRates = false;
|
2017-05-23 19:54:56 +00:00
|
|
|
$scope.folderPathErrors = {};
|
2017-12-15 20:01:56 +00:00
|
|
|
resetRemoteNeed();
|
2017-03-31 06:32:54 +00:00
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
$scope.metricRates = (window.localStorage["metricRates"] == "true");
|
|
|
|
|
} catch (exception) { }
|
2014-11-26 13:39:59 +01:00
|
|
|
|
2017-04-01 09:58:06 +00:00
|
|
|
$scope.folderDefaults = {
|
2017-05-23 19:54:56 +00:00
|
|
|
selectedDevices: {},
|
|
|
|
|
type: "readwrite",
|
|
|
|
|
rescanIntervalS: 60,
|
2017-10-20 14:52:55 +00:00
|
|
|
fsWatcherDelayS: 10,
|
2017-05-23 19:54:56 +00:00
|
|
|
minDiskFree: {value: 1, unit: "%"},
|
|
|
|
|
maxConflicts: 10,
|
|
|
|
|
fsync: true,
|
|
|
|
|
order: "random",
|
|
|
|
|
fileVersioningSelector: "none",
|
|
|
|
|
trashcanClean: 0,
|
|
|
|
|
simpleKeep: 5,
|
|
|
|
|
staggeredMaxAge: 365,
|
|
|
|
|
staggeredCleanInterval: 3600,
|
|
|
|
|
staggeredVersionsPath: "",
|
|
|
|
|
externalCommand: "",
|
2017-07-20 13:16:54 +00:00
|
|
|
autoNormalize: true,
|
|
|
|
|
path: ""
|
2017-04-01 09:58:06 +00:00
|
|
|
};
|
|
|
|
|
|
2015-07-20 14:48:03 +02:00
|
|
|
$scope.localStateTotal = {
|
|
|
|
|
bytes: 0,
|
2017-06-07 10:34:45 +00:00
|
|
|
directories: 0,
|
2015-07-20 14:48:03 +02:00
|
|
|
files: 0
|
|
|
|
|
};
|
|
|
|
|
|
2014-11-26 13:39:59 +01:00
|
|
|
$(window).bind('beforeunload', function () {
|
|
|
|
|
navigatingAway = true;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
$scope.$on("$locationChangeSuccess", function () {
|
2014-11-28 19:39:33 +01:00
|
|
|
LocaleService.useLocale($location.search().lang);
|
2014-11-26 13:39:59 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
$scope.needActions = {
|
|
|
|
|
'rm': 'Del',
|
|
|
|
|
'rmdir': 'Del (dir)',
|
|
|
|
|
'sync': 'Sync',
|
|
|
|
|
'touch': 'Update'
|
|
|
|
|
};
|
|
|
|
|
$scope.needIcons = {
|
2015-08-10 11:22:37 +02:00
|
|
|
'rm': 'trash-o',
|
|
|
|
|
'rmdir': 'trash-o',
|
|
|
|
|
'sync': 'arrow-circle-o-down',
|
2014-11-26 13:39:59 +01:00
|
|
|
'touch': 'asterisk'
|
|
|
|
|
};
|
|
|
|
|
|
2015-06-14 17:17:48 +03:00
|
|
|
$scope.$on(Events.ONLINE, function () {
|
2014-11-26 13:39:59 +01:00
|
|
|
if (online && !restarting) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log('UIOnline');
|
2015-01-12 12:14:24 +01:00
|
|
|
|
|
|
|
|
refreshSystem();
|
2016-06-26 10:47:23 +00:00
|
|
|
refreshDiscoveryCache();
|
2015-01-12 12:14:24 +01:00
|
|
|
refreshConfig();
|
|
|
|
|
refreshConnectionStats();
|
|
|
|
|
refreshDeviceStats();
|
|
|
|
|
refreshFolderStats();
|
2016-12-30 01:33:27 +00:00
|
|
|
refreshGlobalChanges();
|
2016-06-07 07:46:45 +00:00
|
|
|
refreshThemes();
|
2015-01-12 12:14:24 +01:00
|
|
|
|
2015-04-06 10:23:27 +02:00
|
|
|
$http.get(urlbase + '/system/version').success(function (data) {
|
2016-03-11 09:48:46 +00:00
|
|
|
if ($scope.version.version && $scope.version.version !== data.version) {
|
2015-08-25 15:38:28 +02:00
|
|
|
// We already have a version response, but it differs from
|
|
|
|
|
// the new one. Reload the full GUI in case it's changed.
|
|
|
|
|
document.location.reload(true);
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-09 11:32:54 +02:00
|
|
|
$scope.version = data;
|
2016-04-05 06:36:53 +00:00
|
|
|
$scope.version.isDevelopmentVersion = data.version.indexOf('-')>0;
|
2015-01-12 12:14:24 +01:00
|
|
|
}).error($scope.emitHTTPError);
|
|
|
|
|
|
2015-04-07 13:32:33 +02:00
|
|
|
$http.get(urlbase + '/svc/report').success(function (data) {
|
2017-10-15 07:45:15 +00:00
|
|
|
$scope.reportData = data;
|
2017-10-21 18:46:07 +00:00
|
|
|
if ($scope.system && $scope.config.options.urAccepted > -1 && $scope.config.options.urSeen < $scope.system.urVersionMax && $scope.config.options.urAccepted < $scope.system.urVersionMax) {
|
2017-10-12 06:16:46 +00:00
|
|
|
// Usage reporting format has changed, prompt the user to re-accept.
|
|
|
|
|
$('#ur').modal();
|
|
|
|
|
}
|
2015-01-12 12:14:24 +01:00
|
|
|
}).error($scope.emitHTTPError);
|
|
|
|
|
|
2015-04-06 10:23:27 +02:00
|
|
|
$http.get(urlbase + '/system/upgrade').success(function (data) {
|
2015-01-12 12:14:24 +01:00
|
|
|
$scope.upgradeInfo = data;
|
|
|
|
|
}).error(function () {
|
|
|
|
|
$scope.upgradeInfo = null;
|
|
|
|
|
});
|
|
|
|
|
|
2014-11-26 13:39:59 +01:00
|
|
|
online = true;
|
|
|
|
|
restarting = false;
|
|
|
|
|
$('#networkError').modal('hide');
|
|
|
|
|
$('#restarting').modal('hide');
|
|
|
|
|
$('#shutdown').modal('hide');
|
|
|
|
|
});
|
|
|
|
|
|
2015-06-14 17:17:48 +03:00
|
|
|
$scope.$on(Events.OFFLINE, function () {
|
2014-11-26 13:39:59 +01:00
|
|
|
if (navigatingAway || !online) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log('UIOffline');
|
|
|
|
|
online = false;
|
|
|
|
|
if (!restarting) {
|
|
|
|
|
$('#networkError').modal();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2015-01-05 15:42:27 +01:00
|
|
|
$scope.$on('HTTPError', function (event, arg) {
|
|
|
|
|
// Emitted when a HTTP call fails. We use the status code to try
|
|
|
|
|
// to figure out what's wrong.
|
|
|
|
|
|
|
|
|
|
if (navigatingAway || !online) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log('HTTPError', arg);
|
|
|
|
|
online = false;
|
|
|
|
|
if (!restarting) {
|
|
|
|
|
if (arg.status === 0) {
|
|
|
|
|
// A network error, not an HTTP error
|
2015-06-14 17:17:48 +03:00
|
|
|
$scope.$emit(Events.OFFLINE);
|
2015-01-05 15:42:27 +01:00
|
|
|
} else if (arg.status >= 400 && arg.status <= 599) {
|
|
|
|
|
// A genuine HTTP error
|
|
|
|
|
$('#networkError').modal('hide');
|
|
|
|
|
$('#restarting').modal('hide');
|
|
|
|
|
$('#shutdown').modal('hide');
|
|
|
|
|
$('#httpError').modal();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2015-06-14 17:17:48 +03:00
|
|
|
$scope.$on(Events.STATE_CHANGED, function (event, arg) {
|
2014-11-26 13:39:59 +01:00
|
|
|
var data = arg.data;
|
|
|
|
|
if ($scope.model[data.folder]) {
|
|
|
|
|
$scope.model[data.folder].state = data.to;
|
2015-06-13 19:10:11 +01:00
|
|
|
$scope.model[data.folder].error = data.error;
|
2015-06-26 14:22:52 +02:00
|
|
|
|
2015-08-26 23:49:06 +01:00
|
|
|
// If a folder has started scanning, then any scan progress is
|
|
|
|
|
// also obsolete.
|
|
|
|
|
if (data.to === 'scanning') {
|
|
|
|
|
delete $scope.scanProgress[data.folder];
|
|
|
|
|
}
|
2016-06-02 19:26:52 +00:00
|
|
|
|
|
|
|
|
// If a folder finished scanning, then refresh folder stats
|
|
|
|
|
// to update last scan time.
|
|
|
|
|
if(data.from === 'scanning' && data.to === 'idle') {
|
|
|
|
|
refreshFolderStats();
|
|
|
|
|
}
|
2014-11-26 13:39:59 +01:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2015-06-14 17:17:48 +03:00
|
|
|
$scope.$on(Events.LOCAL_INDEX_UPDATED, function (event, arg) {
|
2014-12-07 20:21:12 +00:00
|
|
|
refreshFolderStats();
|
2016-12-21 16:35:20 +00:00
|
|
|
refreshGlobalChanges();
|
2014-11-26 13:39:59 +01:00
|
|
|
});
|
|
|
|
|
|
2015-06-14 17:17:48 +03:00
|
|
|
$scope.$on(Events.DEVICE_DISCONNECTED, function (event, arg) {
|
2015-08-23 21:56:10 +02:00
|
|
|
$scope.connections[arg.data.id].connected = false;
|
2014-11-26 13:39:59 +01:00
|
|
|
refreshDeviceStats();
|
|
|
|
|
});
|
|
|
|
|
|
2015-06-14 17:17:48 +03:00
|
|
|
$scope.$on(Events.DEVICE_CONNECTED, function (event, arg) {
|
2014-11-26 13:39:59 +01:00
|
|
|
if (!$scope.connections[arg.data.id]) {
|
|
|
|
|
$scope.connections[arg.data.id] = {
|
|
|
|
|
inbps: 0,
|
|
|
|
|
outbps: 0,
|
2015-03-10 23:45:43 +01:00
|
|
|
inBytesTotal: 0,
|
|
|
|
|
outBytesTotal: 0,
|
2015-07-17 21:22:07 +01:00
|
|
|
type: arg.data.type,
|
2015-03-10 23:45:43 +01:00
|
|
|
address: arg.data.addr
|
2014-11-26 13:39:59 +01:00
|
|
|
};
|
|
|
|
|
$scope.completion[arg.data.id] = {
|
2017-07-05 09:19:29 +00:00
|
|
|
_total: 100,
|
2017-12-15 20:01:56 +00:00
|
|
|
_needBytes: 0,
|
|
|
|
|
_needItems: 0
|
2014-11-26 13:39:59 +01:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2015-06-14 17:17:48 +03:00
|
|
|
$scope.$on('ConfigLoaded', function () {
|
2015-03-10 23:45:43 +01:00
|
|
|
if ($scope.config.options.urAccepted === 0) {
|
2014-11-26 13:39:59 +01:00
|
|
|
// If usage reporting has been neither accepted nor declined,
|
|
|
|
|
// we want to ask the user to make a choice. But we don't want
|
|
|
|
|
// to bug them during initial setup, so we set a cookie with
|
|
|
|
|
// the time of the first visit. When that cookie is present
|
|
|
|
|
// and the time is more than four hours ago, we ask the
|
|
|
|
|
// question.
|
|
|
|
|
|
|
|
|
|
var firstVisit = document.cookie.replace(/(?:(?:^|.*;\s*)firstVisit\s*\=\s*([^;]*).*$)|^.*$/, "$1");
|
|
|
|
|
if (!firstVisit) {
|
|
|
|
|
document.cookie = "firstVisit=" + Date.now() + ";max-age=" + 30 * 24 * 3600;
|
|
|
|
|
} else {
|
|
|
|
|
if (+firstVisit < Date.now() - 4 * 3600 * 1000) {
|
2016-06-17 06:44:55 +00:00
|
|
|
$('#ur').modal();
|
2014-11-26 13:39:59 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2015-06-14 17:17:48 +03:00
|
|
|
$scope.$on(Events.DEVICE_REJECTED, function (event, arg) {
|
2014-12-27 23:12:12 +00:00
|
|
|
$scope.deviceRejections[arg.data.device] = arg;
|
|
|
|
|
});
|
|
|
|
|
|
2015-06-14 17:17:48 +03:00
|
|
|
$scope.$on(Events.FOLDER_REJECTED, function (event, arg) {
|
2014-12-27 23:12:12 +00:00
|
|
|
$scope.folderRejections[arg.data.folder + "-" + arg.data.device] = arg;
|
|
|
|
|
});
|
|
|
|
|
|
2015-06-14 17:17:48 +03:00
|
|
|
$scope.$on(Events.CONFIG_SAVED, function (event, arg) {
|
2014-11-26 13:39:59 +01:00
|
|
|
updateLocalConfig(arg.data);
|
|
|
|
|
|
2015-04-06 10:23:27 +02:00
|
|
|
$http.get(urlbase + '/system/config/insync').success(function (data) {
|
2014-11-26 13:39:59 +01:00
|
|
|
$scope.configInSync = data.configInSync;
|
2015-01-05 15:42:27 +01:00
|
|
|
}).error($scope.emitHTTPError);
|
2014-11-26 13:39:59 +01:00
|
|
|
});
|
|
|
|
|
|
2015-06-14 17:17:48 +03:00
|
|
|
$scope.$on(Events.DOWNLOAD_PROGRESS, function (event, arg) {
|
2014-11-26 13:39:59 +01:00
|
|
|
var stats = arg.data;
|
|
|
|
|
var progress = {};
|
2014-11-28 19:39:33 +01:00
|
|
|
for (var folder in stats) {
|
2014-11-26 13:39:59 +01:00
|
|
|
progress[folder] = {};
|
2014-11-28 19:39:33 +01:00
|
|
|
for (var file in stats[folder]) {
|
2014-11-26 13:39:59 +01:00
|
|
|
var s = stats[folder][file];
|
2015-03-10 23:45:43 +01:00
|
|
|
var reused = 100 * s.reused / s.total;
|
|
|
|
|
var copiedFromOrigin = 100 * s.copiedFromOrigin / s.total;
|
|
|
|
|
var copiedFromElsewhere = 100 * s.copiedFromElsewhere / s.total;
|
|
|
|
|
var pulled = 100 * s.pulled / s.total;
|
|
|
|
|
var pulling = 100 * s.pulling / s.total;
|
2015-04-28 18:34:55 +03:00
|
|
|
// We try to round up pulling to at least a percent so that it would be at least a bit visible.
|
2014-12-03 23:44:39 +00:00
|
|
|
if (pulling < 1 && pulled + copiedFromElsewhere + copiedFromOrigin + reused <= 99) {
|
2014-11-26 13:39:59 +01:00
|
|
|
pulling = 1;
|
|
|
|
|
}
|
|
|
|
|
progress[folder][file] = {
|
2015-03-10 23:45:43 +01:00
|
|
|
reused: reused,
|
|
|
|
|
copiedFromOrigin: copiedFromOrigin,
|
|
|
|
|
copiedFromElsewhere: copiedFromElsewhere,
|
|
|
|
|
pulled: pulled,
|
|
|
|
|
pulling: pulling,
|
|
|
|
|
bytesTotal: s.bytesTotal,
|
|
|
|
|
bytesDone: s.bytesDone,
|
2014-11-26 13:39:59 +01:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
2014-11-28 19:39:33 +01:00
|
|
|
for (var folder in $scope.progress) {
|
2014-11-26 13:39:59 +01:00
|
|
|
if (!(folder in progress)) {
|
2016-03-11 09:48:46 +00:00
|
|
|
if ($scope.neededFolder === folder) {
|
2014-11-26 13:39:59 +01:00
|
|
|
refreshNeed(folder);
|
|
|
|
|
}
|
2016-03-11 09:48:46 +00:00
|
|
|
} else if ($scope.neededFolder === folder) {
|
2014-11-28 19:39:33 +01:00
|
|
|
for (file in $scope.progress[folder]) {
|
2014-11-26 13:39:59 +01:00
|
|
|
if (!(file in progress[folder])) {
|
|
|
|
|
refreshNeed(folder);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
$scope.progress = progress;
|
|
|
|
|
console.log("DownloadProgress", $scope.progress);
|
|
|
|
|
});
|
|
|
|
|
|
2015-06-14 17:17:48 +03:00
|
|
|
$scope.$on(Events.FOLDER_SUMMARY, function (event, arg) {
|
2015-03-26 23:26:51 +01:00
|
|
|
var data = arg.data;
|
|
|
|
|
$scope.model[data.folder] = data.summary;
|
2015-07-20 14:48:03 +02:00
|
|
|
recalcLocalStateTotal();
|
2015-03-26 23:26:51 +01:00
|
|
|
});
|
|
|
|
|
|
2015-06-14 17:17:48 +03:00
|
|
|
$scope.$on(Events.FOLDER_COMPLETION, function (event, arg) {
|
2015-03-26 23:26:51 +01:00
|
|
|
var data = arg.data;
|
|
|
|
|
if (!$scope.completion[data.device]) {
|
|
|
|
|
$scope.completion[data.device] = {};
|
|
|
|
|
}
|
2016-08-12 06:41:43 +00:00
|
|
|
$scope.completion[data.device][data.folder] = data;
|
|
|
|
|
recalcCompletion(data.device);
|
2015-03-26 23:26:51 +01:00
|
|
|
});
|
|
|
|
|
|
2015-06-26 14:22:52 +02:00
|
|
|
$scope.$on(Events.FOLDER_ERRORS, function (event, arg) {
|
2018-01-14 17:01:06 +00:00
|
|
|
$scope.model[arg.data.folder].pullErrors = arg.data.errors.length;
|
2015-06-26 14:22:52 +02:00
|
|
|
});
|
|
|
|
|
|
2015-08-26 23:49:06 +01:00
|
|
|
$scope.$on(Events.FOLDER_SCAN_PROGRESS, function (event, arg) {
|
|
|
|
|
var data = arg.data;
|
|
|
|
|
$scope.scanProgress[data.folder] = {
|
|
|
|
|
current: data.current,
|
2015-11-17 21:08:36 +01:00
|
|
|
total: data.total,
|
2016-03-11 09:48:46 +00:00
|
|
|
rate: data.rate
|
2015-08-26 23:49:06 +01:00
|
|
|
};
|
|
|
|
|
console.log("FolderScanProgress", data);
|
|
|
|
|
});
|
|
|
|
|
|
2015-01-05 15:42:27 +01:00
|
|
|
$scope.emitHTTPError = function (data, status, headers, config) {
|
|
|
|
|
$scope.$emit('HTTPError', {data: data, status: status, headers: headers, config: config});
|
|
|
|
|
};
|
|
|
|
|
|
2014-11-26 13:39:59 +01:00
|
|
|
var debouncedFuncs = {};
|
|
|
|
|
|
|
|
|
|
function refreshFolder(folder) {
|
|
|
|
|
var key = "refreshFolder" + folder;
|
|
|
|
|
if (!debouncedFuncs[key]) {
|
|
|
|
|
debouncedFuncs[key] = debounce(function () {
|
2015-04-06 10:23:27 +02:00
|
|
|
$http.get(urlbase + '/db/status?folder=' + encodeURIComponent(folder)).success(function (data) {
|
2014-11-26 13:39:59 +01:00
|
|
|
$scope.model[folder] = data;
|
2015-07-20 14:48:03 +02:00
|
|
|
recalcLocalStateTotal();
|
2014-11-26 13:39:59 +01:00
|
|
|
console.log("refreshFolder", folder, data);
|
2015-01-05 15:42:27 +01:00
|
|
|
}).error($scope.emitHTTPError);
|
2014-11-26 13:39:59 +01:00
|
|
|
}, 1000, true);
|
|
|
|
|
}
|
|
|
|
|
debouncedFuncs[key]();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function updateLocalConfig(config) {
|
|
|
|
|
var hasConfig = !isEmptyObject($scope.config);
|
|
|
|
|
|
|
|
|
|
$scope.config = config;
|
2016-05-04 19:38:12 +00:00
|
|
|
$scope.config.options._listenAddressesStr = $scope.config.options.listenAddresses.join(', ');
|
2015-07-03 14:07:38 +02:00
|
|
|
$scope.config.options._globalAnnounceServersStr = $scope.config.options.globalAnnounceServers.join(', ');
|
2017-10-12 06:16:46 +00:00
|
|
|
$scope.config.options._urAcceptedStr = "" + $scope.config.options.urAccepted;
|
2014-11-26 13:39:59 +01:00
|
|
|
|
2015-03-10 23:45:43 +01:00
|
|
|
$scope.devices = $scope.config.devices;
|
2014-11-26 13:39:59 +01:00
|
|
|
$scope.devices.forEach(function (deviceCfg) {
|
2015-03-10 23:45:43 +01:00
|
|
|
$scope.completion[deviceCfg.deviceID] = {
|
2017-07-05 09:19:29 +00:00
|
|
|
_total: 100,
|
2017-12-15 20:01:56 +00:00
|
|
|
_needBytes: 0,
|
|
|
|
|
_needItems: 0
|
2014-11-26 13:39:59 +01:00
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
$scope.devices.sort(deviceCompare);
|
2015-03-10 23:45:43 +01:00
|
|
|
$scope.folders = folderMap($scope.config.folders);
|
2014-11-26 13:39:59 +01:00
|
|
|
Object.keys($scope.folders).forEach(function (folder) {
|
|
|
|
|
refreshFolder(folder);
|
2015-03-10 23:45:43 +01:00
|
|
|
$scope.folders[folder].devices.forEach(function (deviceCfg) {
|
|
|
|
|
refreshCompletion(deviceCfg.deviceID, folder);
|
2014-11-26 13:39:59 +01:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2015-11-16 21:33:55 +01:00
|
|
|
// If we're not listening on localhost, and there is no
|
|
|
|
|
// authentication configured, and the magic setting to silence the
|
|
|
|
|
// warning isn't set, then yell at the user.
|
|
|
|
|
var guiCfg = $scope.config.gui;
|
2016-03-11 09:48:46 +00:00
|
|
|
$scope.openNoAuth = guiCfg.address.substr(0, 4) !== "127."
|
|
|
|
|
&& guiCfg.address.substr(0, 6) !== "[::1]:"
|
2015-11-16 21:33:55 +01:00
|
|
|
&& (!guiCfg.user || !guiCfg.password)
|
|
|
|
|
&& !guiCfg.insecureAdminAccess;
|
|
|
|
|
|
2014-11-26 13:39:59 +01:00
|
|
|
if (!hasConfig) {
|
|
|
|
|
$scope.$emit('ConfigLoaded');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function refreshSystem() {
|
2015-04-06 10:23:27 +02:00
|
|
|
$http.get(urlbase + '/system/status').success(function (data) {
|
2014-11-26 13:39:59 +01:00
|
|
|
$scope.myID = data.myID;
|
|
|
|
|
$scope.system = data;
|
2015-07-17 21:22:07 +01:00
|
|
|
|
2017-10-12 06:16:46 +00:00
|
|
|
if ($scope.reportDataPreviewVersion === '') {
|
|
|
|
|
$scope.reportDataPreviewVersion = $scope.system.urVersionMax;
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-04 19:38:12 +00:00
|
|
|
var listenersFailed = [];
|
|
|
|
|
for (var address in data.connectionServiceStatus) {
|
|
|
|
|
if (data.connectionServiceStatus[address].error) {
|
|
|
|
|
listenersFailed.push(address + ": " + data.connectionServiceStatus[address].error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
$scope.listenersFailed = listenersFailed;
|
2017-12-15 20:01:56 +00:00
|
|
|
$scope.listenersTotal = $scope.sizeOf(data.connectionServiceStatus);
|
2016-05-04 19:38:12 +00:00
|
|
|
|
2015-09-20 15:30:25 +02:00
|
|
|
$scope.discoveryTotal = data.discoveryMethods;
|
|
|
|
|
var discoveryFailed = [];
|
|
|
|
|
for (var disco in data.discoveryErrors) {
|
|
|
|
|
if (data.discoveryErrors[disco]) {
|
|
|
|
|
discoveryFailed.push(disco + ": " + data.discoveryErrors[disco]);
|
2015-07-17 21:22:07 +01:00
|
|
|
}
|
|
|
|
|
}
|
2015-09-20 15:30:25 +02:00
|
|
|
$scope.discoveryFailed = discoveryFailed;
|
2014-11-26 13:39:59 +01:00
|
|
|
console.log("refreshSystem", data);
|
2015-01-05 15:42:27 +01:00
|
|
|
}).error($scope.emitHTTPError);
|
2014-11-26 13:39:59 +01:00
|
|
|
}
|
|
|
|
|
|
2016-06-26 10:47:23 +00:00
|
|
|
function refreshDiscoveryCache() {
|
|
|
|
|
$http.get(urlbase + '/system/discovery').success(function (data) {
|
|
|
|
|
for (var device in data) {
|
|
|
|
|
for (var i = 0; i < data[device].addresses.length; i++) {
|
|
|
|
|
// Relay addresses are URLs with
|
|
|
|
|
// .../?foo=barlongstuff that we strip away here. We
|
|
|
|
|
// remove the final slash as well for symmetry with
|
|
|
|
|
// tcp://192.0.2.42:1234 type addresses.
|
|
|
|
|
data[device].addresses[i] = data[device].addresses[i].replace(/\/\?.*/, '');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
$scope.discoveryCache = data;
|
|
|
|
|
console.log("refreshDiscoveryCache", data);
|
|
|
|
|
}).error($scope.emitHTTPError);
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-20 14:48:03 +02:00
|
|
|
function recalcLocalStateTotal () {
|
|
|
|
|
$scope.localStateTotal = {
|
|
|
|
|
bytes: 0,
|
2017-06-07 10:34:45 +00:00
|
|
|
directories: 0,
|
2015-07-20 14:48:03 +02:00
|
|
|
files: 0
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for (var f in $scope.model) {
|
|
|
|
|
$scope.localStateTotal.bytes += $scope.model[f].localBytes;
|
|
|
|
|
$scope.localStateTotal.files += $scope.model[f].localFiles;
|
2017-06-07 10:34:45 +00:00
|
|
|
$scope.localStateTotal.directories += $scope.model[f].localDirectories;
|
2015-07-20 14:48:03 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-12 06:41:43 +00:00
|
|
|
function recalcCompletion(device) {
|
2017-12-15 20:01:56 +00:00
|
|
|
var total = 0, needed = 0, deletes = 0, items = 0;
|
2016-08-12 06:41:43 +00:00
|
|
|
for (var folder in $scope.completion[device]) {
|
2017-12-15 20:01:56 +00:00
|
|
|
if (folder === "_total" || folder === '_needBytes' || folder === '_needItems') {
|
2016-08-12 06:41:43 +00:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
total += $scope.completion[device][folder].globalBytes;
|
|
|
|
|
needed += $scope.completion[device][folder].needBytes;
|
2017-12-15 20:01:56 +00:00
|
|
|
items += $scope.completion[device][folder].needItems;
|
2016-09-02 06:45:46 +00:00
|
|
|
deletes += $scope.completion[device][folder].needDeletes;
|
2016-08-12 06:41:43 +00:00
|
|
|
}
|
2016-08-12 08:49:16 +02:00
|
|
|
if (total == 0) {
|
|
|
|
|
$scope.completion[device]._total = 100;
|
2017-07-05 09:19:29 +00:00
|
|
|
$scope.completion[device]._needBytes = 0;
|
2017-12-15 20:01:56 +00:00
|
|
|
$scope.completion[device]._needItems = 0;
|
2016-08-12 08:49:16 +02:00
|
|
|
} else {
|
2017-08-24 04:26:12 +00:00
|
|
|
$scope.completion[device]._total = Math.floor(100 * (1 - needed / total));
|
2017-07-05 09:19:29 +00:00
|
|
|
$scope.completion[device]._needBytes = needed
|
2018-01-14 12:08:40 +00:00
|
|
|
$scope.completion[device]._needItems = items + deletes;
|
2016-08-12 08:49:16 +02:00
|
|
|
}
|
2016-08-12 06:41:43 +00:00
|
|
|
|
2016-09-02 06:45:46 +00:00
|
|
|
if (needed == 0 && deletes > 0) {
|
|
|
|
|
// We don't need any data, but we have deletes that we need
|
|
|
|
|
// to do. Drop down the completion percentage to indicate
|
|
|
|
|
// that we have stuff to do.
|
|
|
|
|
$scope.completion[device]._total = 95;
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-12 06:41:43 +00:00
|
|
|
console.log("recalcCompletion", device, $scope.completion[device]);
|
|
|
|
|
}
|
|
|
|
|
|
2014-11-26 13:39:59 +01:00
|
|
|
function refreshCompletion(device, folder) {
|
|
|
|
|
if (device === $scope.myID) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-06 10:23:27 +02:00
|
|
|
$http.get(urlbase + '/db/completion?device=' + device + '&folder=' + encodeURIComponent(folder)).success(function (data) {
|
2015-03-26 23:26:51 +01:00
|
|
|
if (!$scope.completion[device]) {
|
|
|
|
|
$scope.completion[device] = {};
|
|
|
|
|
}
|
2016-08-12 06:41:43 +00:00
|
|
|
$scope.completion[device][folder] = data;
|
|
|
|
|
recalcCompletion(device);
|
2015-03-26 23:26:51 +01:00
|
|
|
}).error($scope.emitHTTPError);
|
2014-11-26 13:39:59 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function refreshConnectionStats() {
|
2015-04-06 10:23:27 +02:00
|
|
|
$http.get(urlbase + '/system/connections').success(function (data) {
|
2014-11-26 13:39:59 +01:00
|
|
|
var now = Date.now(),
|
|
|
|
|
td = (now - prevDate) / 1000,
|
|
|
|
|
id;
|
|
|
|
|
|
|
|
|
|
prevDate = now;
|
2015-04-09 11:14:15 +01:00
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
data.total.inbps = Math.max(0, (data.total.inBytesTotal - $scope.connectionsTotal.inBytesTotal) / td);
|
|
|
|
|
data.total.outbps = Math.max(0, (data.total.outBytesTotal - $scope.connectionsTotal.outBytesTotal) / td);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
data.total.inbps = 0;
|
|
|
|
|
data.total.outbps = 0;
|
|
|
|
|
}
|
|
|
|
|
$scope.connectionsTotal = data.total;
|
|
|
|
|
|
2015-04-07 13:57:26 +01:00
|
|
|
data = data.connections;
|
2014-11-26 13:39:59 +01:00
|
|
|
for (id in data) {
|
|
|
|
|
if (!data.hasOwnProperty(id)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
try {
|
2015-03-10 23:45:43 +01:00
|
|
|
data[id].inbps = Math.max(0, (data[id].inBytesTotal - $scope.connections[id].inBytesTotal) / td);
|
|
|
|
|
data[id].outbps = Math.max(0, (data[id].outBytesTotal - $scope.connections[id].outBytesTotal) / td);
|
2014-11-26 13:39:59 +01:00
|
|
|
} catch (e) {
|
|
|
|
|
data[id].inbps = 0;
|
|
|
|
|
data[id].outbps = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
$scope.connections = data;
|
|
|
|
|
console.log("refreshConnections", data);
|
2015-01-05 15:42:27 +01:00
|
|
|
}).error($scope.emitHTTPError);
|
2014-11-26 13:39:59 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function refreshErrors() {
|
2015-04-07 13:46:39 +02:00
|
|
|
$http.get(urlbase + '/system/error').success(function (data) {
|
2014-11-26 13:39:59 +01:00
|
|
|
$scope.errors = data.errors;
|
|
|
|
|
console.log("refreshErrors", data);
|
2015-01-05 15:42:27 +01:00
|
|
|
}).error($scope.emitHTTPError);
|
2014-11-26 13:39:59 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function refreshConfig() {
|
2015-04-06 10:23:27 +02:00
|
|
|
$http.get(urlbase + '/system/config').success(function (data) {
|
2014-11-26 13:39:59 +01:00
|
|
|
updateLocalConfig(data);
|
|
|
|
|
console.log("refreshConfig", data);
|
2015-01-05 15:42:27 +01:00
|
|
|
}).error($scope.emitHTTPError);
|
2014-11-26 13:39:59 +01:00
|
|
|
|
2015-04-06 10:23:27 +02:00
|
|
|
$http.get(urlbase + '/system/config/insync').success(function (data) {
|
2014-11-26 13:39:59 +01:00
|
|
|
$scope.configInSync = data.configInSync;
|
2015-01-05 15:42:27 +01:00
|
|
|
}).error($scope.emitHTTPError);
|
2014-11-26 13:39:59 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function refreshNeed(folder) {
|
2015-04-25 22:53:44 +01:00
|
|
|
var url = urlbase + "/db/need?folder=" + encodeURIComponent(folder);
|
|
|
|
|
url += "&page=" + $scope.neededCurrentPage;
|
|
|
|
|
url += "&perpage=" + $scope.neededPageSize;
|
|
|
|
|
$http.get(url).success(function (data) {
|
2016-03-11 09:48:46 +00:00
|
|
|
if ($scope.neededFolder === folder) {
|
2014-11-26 13:39:59 +01:00
|
|
|
console.log("refreshNeed", folder, data);
|
2015-04-25 22:53:44 +01:00
|
|
|
parseNeeded(data);
|
2014-11-26 13:39:59 +01:00
|
|
|
}
|
2015-01-05 15:42:27 +01:00
|
|
|
}).error($scope.emitHTTPError);
|
2014-11-26 13:39:59 +01:00
|
|
|
}
|
|
|
|
|
|
2015-04-25 22:53:44 +01:00
|
|
|
function needAction(file) {
|
|
|
|
|
var fDelete = 4096;
|
|
|
|
|
var fDirectory = 16384;
|
|
|
|
|
|
|
|
|
|
if ((file.flags & (fDelete + fDirectory)) === fDelete + fDirectory) {
|
|
|
|
|
return 'rmdir';
|
|
|
|
|
} else if ((file.flags & fDelete) === fDelete) {
|
|
|
|
|
return 'rm';
|
|
|
|
|
} else if ((file.flags & fDirectory) === fDirectory) {
|
|
|
|
|
return 'touch';
|
|
|
|
|
} else {
|
|
|
|
|
return 'sync';
|
|
|
|
|
}
|
2015-06-14 17:17:48 +03:00
|
|
|
}
|
2015-04-25 22:53:44 +01:00
|
|
|
|
|
|
|
|
function parseNeeded(data) {
|
|
|
|
|
var merged = [];
|
|
|
|
|
data.progress.forEach(function (item) {
|
|
|
|
|
item.type = "progress";
|
|
|
|
|
item.action = needAction(item);
|
|
|
|
|
merged.push(item);
|
|
|
|
|
});
|
|
|
|
|
data.queued.forEach(function (item) {
|
|
|
|
|
item.type = "queued";
|
|
|
|
|
item.action = needAction(item);
|
|
|
|
|
merged.push(item);
|
|
|
|
|
});
|
|
|
|
|
data.rest.forEach(function (item) {
|
|
|
|
|
item.type = "rest";
|
|
|
|
|
item.action = needAction(item);
|
|
|
|
|
merged.push(item);
|
|
|
|
|
});
|
|
|
|
|
$scope.needed = merged;
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-20 13:16:54 +00:00
|
|
|
function pathJoin(base, name) {
|
|
|
|
|
base = expandTilde(base);
|
|
|
|
|
if (base[base.length - 1] !== $scope.system.pathSeparator) {
|
|
|
|
|
return base + $scope.system.pathSeparator + name;
|
|
|
|
|
}
|
|
|
|
|
return base + name;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function expandTilde(path) {
|
|
|
|
|
if (path && path.trim().charAt(0) === '~') {
|
|
|
|
|
return $scope.system.tilde + path.trim().substring(1);
|
|
|
|
|
}
|
|
|
|
|
return path;
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-10 15:31:25 +00:00
|
|
|
function shouldSetDefaultFolderPath() {
|
|
|
|
|
return $scope.config.options && $scope.config.options.defaultFolderPath && !$scope.editingExisting && $scope.folderEditor.folderPath.$pristine
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-15 20:01:56 +00:00
|
|
|
function resetRemoteNeed() {
|
|
|
|
|
$scope.remoteNeed = {};
|
|
|
|
|
$scope.remoteNeedFolders = [];
|
|
|
|
|
$scope.remoteNeedDevice = undefined;
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-25 22:53:44 +01:00
|
|
|
$scope.neededPageChanged = function (page) {
|
|
|
|
|
$scope.neededCurrentPage = page;
|
|
|
|
|
refreshNeed($scope.neededFolder);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$scope.neededChangePageSize = function (perpage) {
|
|
|
|
|
$scope.neededPageSize = perpage;
|
|
|
|
|
refreshNeed($scope.neededFolder);
|
2015-06-14 17:17:48 +03:00
|
|
|
};
|
2015-04-25 22:53:44 +01:00
|
|
|
|
2018-01-14 17:01:06 +00:00
|
|
|
$scope.refreshFailed = function (page, perpage) {
|
|
|
|
|
var url = urlbase + '/folder/pullerrors?folder=' + encodeURIComponent($scope.failed.folder);
|
|
|
|
|
url += "&page=" + page + "&perpage=" + perpage;
|
|
|
|
|
$http.get(url).success(function (data) {
|
|
|
|
|
$scope.failed = data;
|
|
|
|
|
}).error($scope.emitHTTPError);
|
2015-06-26 14:22:52 +02:00
|
|
|
};
|
|
|
|
|
|
2017-12-15 20:01:56 +00:00
|
|
|
$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);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2014-11-26 13:39:59 +01:00
|
|
|
var refreshDeviceStats = debounce(function () {
|
|
|
|
|
$http.get(urlbase + "/stats/device").success(function (data) {
|
2014-12-07 20:21:12 +00:00
|
|
|
$scope.deviceStats = data;
|
|
|
|
|
for (var device in $scope.deviceStats) {
|
2015-03-10 23:45:43 +01:00
|
|
|
$scope.deviceStats[device].lastSeen = new Date($scope.deviceStats[device].lastSeen);
|
|
|
|
|
$scope.deviceStats[device].lastSeenDays = (new Date() - $scope.deviceStats[device].lastSeen) / 1000 / 86400;
|
2014-11-26 13:39:59 +01:00
|
|
|
}
|
|
|
|
|
console.log("refreshDeviceStats", data);
|
2015-01-05 15:42:27 +01:00
|
|
|
}).error($scope.emitHTTPError);
|
2015-03-26 23:26:51 +01:00
|
|
|
}, 2500);
|
2014-11-26 13:39:59 +01:00
|
|
|
|
2014-12-07 20:21:12 +00:00
|
|
|
var refreshFolderStats = debounce(function () {
|
|
|
|
|
$http.get(urlbase + "/stats/folder").success(function (data) {
|
|
|
|
|
$scope.folderStats = data;
|
|
|
|
|
for (var folder in $scope.folderStats) {
|
2015-03-10 23:45:43 +01:00
|
|
|
if ($scope.folderStats[folder].lastFile) {
|
|
|
|
|
$scope.folderStats[folder].lastFile.at = new Date($scope.folderStats[folder].lastFile.at);
|
2014-12-07 20:21:12 +00:00
|
|
|
}
|
2016-06-02 19:26:52 +00:00
|
|
|
|
|
|
|
|
$scope.folderStats[folder].lastScan = new Date($scope.folderStats[folder].lastScan);
|
|
|
|
|
$scope.folderStats[folder].lastScanDays = (new Date() - $scope.folderStats[folder].lastScan) / 1000 / 86400;
|
2014-12-07 20:21:12 +00:00
|
|
|
}
|
|
|
|
|
console.log("refreshfolderStats", data);
|
2015-01-05 15:42:27 +01:00
|
|
|
}).error($scope.emitHTTPError);
|
2015-03-26 23:26:51 +01:00
|
|
|
}, 2500);
|
2014-12-07 20:21:12 +00:00
|
|
|
|
2016-06-07 07:46:45 +00:00
|
|
|
var refreshThemes = debounce(function () {
|
|
|
|
|
$http.get("themes.json").success(function (data) { // no urlbase here as this is served by the asset handler
|
|
|
|
|
$scope.themes = data.themes;
|
|
|
|
|
}).error($scope.emitHTTPError);
|
|
|
|
|
}, 2500);
|
|
|
|
|
|
2016-12-21 16:35:20 +00:00
|
|
|
var refreshGlobalChanges = debounce(function () {
|
2016-12-30 01:33:27 +00:00
|
|
|
$http.get(urlbase + "/events/disk?limit=25").success(function (data) {
|
2016-12-21 16:35:20 +00:00
|
|
|
data = data.reverse();
|
|
|
|
|
$scope.globalChangeEvents = data;
|
|
|
|
|
|
|
|
|
|
console.log("refreshGlobalChanges", data);
|
|
|
|
|
}).error($scope.emitHTTPError);
|
|
|
|
|
}, 2500);
|
|
|
|
|
|
2014-11-26 13:39:59 +01:00
|
|
|
$scope.refresh = function () {
|
|
|
|
|
refreshSystem();
|
2016-06-26 10:47:23 +00:00
|
|
|
refreshDiscoveryCache();
|
2014-11-26 13:39:59 +01:00
|
|
|
refreshConnectionStats();
|
|
|
|
|
refreshErrors();
|
|
|
|
|
};
|
|
|
|
|
|
2014-11-29 09:18:52 +01:00
|
|
|
$scope.folderStatus = function (folderCfg) {
|
2015-03-10 23:45:43 +01:00
|
|
|
if (typeof $scope.model[folderCfg.id] === 'undefined') {
|
2014-11-26 13:39:59 +01:00
|
|
|
return 'unknown';
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-21 18:41:25 +00:00
|
|
|
if (folderCfg.paused) {
|
|
|
|
|
return 'paused';
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-26 14:22:52 +02:00
|
|
|
// after restart syncthing process state may be empty
|
|
|
|
|
if (!$scope.model[folderCfg.id].state) {
|
|
|
|
|
return 'unknown';
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-13 05:12:01 +09:00
|
|
|
if ($scope.model[folderCfg.id].invalid) {
|
2014-11-26 13:39:59 +01:00
|
|
|
return 'stopped';
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-26 14:22:52 +02:00
|
|
|
var state = '' + $scope.model[folderCfg.id].state;
|
|
|
|
|
if (state === 'error') {
|
2015-04-13 05:12:01 +09:00
|
|
|
return 'stopped'; // legacy, the state is called "stopped" in the GUI
|
|
|
|
|
}
|
2016-12-09 10:38:36 +01:00
|
|
|
if (state === 'idle' && $scope.neededItems(folderCfg.id) > 0) {
|
2015-06-26 14:22:52 +02:00
|
|
|
return 'outofsync';
|
2015-06-10 16:48:16 +03:00
|
|
|
}
|
2016-06-02 00:17:48 +00:00
|
|
|
if (state === 'scanning') {
|
2016-06-07 07:46:45 +00:00
|
|
|
return state;
|
2016-06-02 00:17:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (folderCfg.devices.length <= 1) {
|
|
|
|
|
return 'unshared';
|
|
|
|
|
}
|
2015-04-13 05:12:01 +09:00
|
|
|
|
2015-06-26 14:22:52 +02:00
|
|
|
return state;
|
2014-11-26 13:39:59 +01:00
|
|
|
};
|
|
|
|
|
|
2014-11-29 09:18:52 +01:00
|
|
|
$scope.folderClass = function (folderCfg) {
|
2015-06-26 14:22:52 +02:00
|
|
|
var status = $scope.folderStatus(folderCfg);
|
2014-11-26 13:39:59 +01:00
|
|
|
|
2016-03-11 09:48:46 +00:00
|
|
|
if (status === 'idle') {
|
2014-11-26 13:39:59 +01:00
|
|
|
return 'success';
|
|
|
|
|
}
|
2016-12-21 18:41:25 +00:00
|
|
|
if (status == 'paused') {
|
|
|
|
|
return 'default';
|
|
|
|
|
}
|
2016-03-11 09:48:46 +00:00
|
|
|
if (status === 'syncing' || status === 'scanning') {
|
2014-11-26 13:39:59 +01:00
|
|
|
return 'primary';
|
|
|
|
|
}
|
2015-06-26 14:22:52 +02:00
|
|
|
if (status === 'unknown') {
|
|
|
|
|
return 'info';
|
|
|
|
|
}
|
|
|
|
|
if (status === 'stopped' || status === 'outofsync' || status === 'error') {
|
2015-04-13 05:12:01 +09:00
|
|
|
return 'danger';
|
|
|
|
|
}
|
2016-06-02 18:34:25 +00:00
|
|
|
if (status === 'unshared') {
|
|
|
|
|
return 'warning';
|
|
|
|
|
}
|
2015-06-26 14:22:52 +02:00
|
|
|
|
2014-11-26 13:39:59 +01:00
|
|
|
return 'info';
|
|
|
|
|
};
|
|
|
|
|
|
2016-10-17 16:07:58 +02:00
|
|
|
$scope.neededItems = function (folderID) {
|
|
|
|
|
if (!$scope.model[folderID]) {
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $scope.model[folderID].needFiles + $scope.model[folderID].needDirectories +
|
|
|
|
|
$scope.model[folderID].needSymlinks + $scope.model[folderID].needDeletes;
|
|
|
|
|
};
|
|
|
|
|
|
2014-11-26 13:39:59 +01:00
|
|
|
$scope.syncPercentage = function (folder) {
|
|
|
|
|
if (typeof $scope.model[folder] === 'undefined') {
|
|
|
|
|
return 100;
|
|
|
|
|
}
|
|
|
|
|
if ($scope.model[folder].globalBytes === 0) {
|
|
|
|
|
return 100;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var pct = 100 * $scope.model[folder].inSyncBytes / $scope.model[folder].globalBytes;
|
|
|
|
|
return Math.floor(pct);
|
|
|
|
|
};
|
|
|
|
|
|
2017-01-26 09:39:48 +00:00
|
|
|
$scope.syncRemaining = function (folder) {
|
|
|
|
|
// Remaining sync bytes
|
|
|
|
|
if (typeof $scope.model[folder] === 'undefined') {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
if ($scope.model[folder].globalBytes === 0) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var bytes = $scope.model[folder].globalBytes - $scope.model[folder].inSyncBytes;
|
|
|
|
|
if (isNaN(bytes) || bytes < 0) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
return bytes;
|
|
|
|
|
};
|
|
|
|
|
|
2015-08-26 23:49:06 +01:00
|
|
|
$scope.scanPercentage = function (folder) {
|
|
|
|
|
if (!$scope.scanProgress[folder]) {
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
var pct = 100 * $scope.scanProgress[folder].current / $scope.scanProgress[folder].total;
|
|
|
|
|
return Math.floor(pct);
|
2016-03-11 09:48:46 +00:00
|
|
|
};
|
2015-08-26 23:49:06 +01:00
|
|
|
|
2015-11-17 21:08:36 +01:00
|
|
|
$scope.scanRate = function (folder) {
|
|
|
|
|
if (!$scope.scanProgress[folder]) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
return $scope.scanProgress[folder].rate;
|
2016-03-11 09:48:46 +00:00
|
|
|
};
|
2015-11-17 21:08:36 +01:00
|
|
|
|
2015-11-18 10:57:11 +01:00
|
|
|
$scope.scanRemaining = function (folder) {
|
|
|
|
|
// Formats the remaining scan time as a string. Includes days and
|
|
|
|
|
// hours only when relevant, resulting in time stamps like:
|
|
|
|
|
// 00m 40s
|
|
|
|
|
// 32m 40s
|
|
|
|
|
// 2h 32m
|
|
|
|
|
// 4d 2h
|
|
|
|
|
|
|
|
|
|
if (!$scope.scanProgress[folder]) {
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
// Calculate remaining bytes and seconds based on our current
|
|
|
|
|
// rate.
|
2016-03-11 09:48:46 +00:00
|
|
|
|
2015-11-18 10:57:11 +01:00
|
|
|
var remainingBytes = $scope.scanProgress[folder].total - $scope.scanProgress[folder].current;
|
|
|
|
|
var seconds = remainingBytes / $scope.scanProgress[folder].rate;
|
|
|
|
|
// Round up to closest ten seconds to avoid flapping too much to
|
|
|
|
|
// and fro.
|
2016-03-11 09:48:46 +00:00
|
|
|
|
2015-11-18 10:57:11 +01:00
|
|
|
seconds = Math.ceil(seconds / 10) * 10;
|
|
|
|
|
|
|
|
|
|
// Separate out the number of days.
|
|
|
|
|
var days = 0;
|
2016-03-11 09:48:46 +00:00
|
|
|
var res = [];
|
2015-11-18 10:57:11 +01:00
|
|
|
if (seconds >= 86400) {
|
|
|
|
|
days = Math.floor(seconds / 86400);
|
|
|
|
|
res.push('' + days + 'd')
|
|
|
|
|
seconds = seconds % 86400;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Separate out the number of hours.
|
|
|
|
|
var hours = 0;
|
|
|
|
|
if (seconds > 3600) {
|
|
|
|
|
hours = Math.floor(seconds / 3600);
|
|
|
|
|
res.push('' + hours + 'h')
|
|
|
|
|
seconds = seconds % 3600;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var d = new Date(1970, 0, 1).setSeconds(seconds);
|
|
|
|
|
|
2016-03-11 09:48:46 +00:00
|
|
|
if (days === 0) {
|
2015-11-18 10:57:11 +01:00
|
|
|
// Format minutes only if we're within a day of completion.
|
|
|
|
|
var f = $filter('date')(d, "m'm'");
|
|
|
|
|
res.push(f);
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-11 09:48:46 +00:00
|
|
|
if (days === 0 && hours === 0) {
|
2015-11-18 10:57:11 +01:00
|
|
|
// Format seconds only when we're within an hour of completion.
|
|
|
|
|
var f = $filter('date')(d, "ss's'");
|
|
|
|
|
res.push(f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return res.join(' ');
|
2016-03-11 09:48:46 +00:00
|
|
|
};
|
2015-11-18 10:57:11 +01:00
|
|
|
|
2014-11-29 09:42:16 +01:00
|
|
|
$scope.deviceStatus = function (deviceCfg) {
|
|
|
|
|
if ($scope.deviceFolders(deviceCfg).length === 0) {
|
|
|
|
|
return 'unused';
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-23 21:56:10 +02:00
|
|
|
if (typeof $scope.connections[deviceCfg.deviceID] === 'undefined') {
|
|
|
|
|
return 'unknown';
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-21 18:41:25 +00:00
|
|
|
if (deviceCfg.paused) {
|
2015-08-23 21:56:10 +02:00
|
|
|
return 'paused';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($scope.connections[deviceCfg.deviceID].connected) {
|
2015-03-10 23:45:43 +01:00
|
|
|
if ($scope.completion[deviceCfg.deviceID] && $scope.completion[deviceCfg.deviceID]._total === 100) {
|
2014-11-29 09:42:16 +01:00
|
|
|
return 'insync';
|
|
|
|
|
} else {
|
|
|
|
|
return 'syncing';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Disconnected
|
|
|
|
|
return 'disconnected';
|
|
|
|
|
};
|
|
|
|
|
|
2014-11-26 13:39:59 +01:00
|
|
|
$scope.deviceClass = function (deviceCfg) {
|
2014-11-29 09:42:16 +01:00
|
|
|
if ($scope.deviceFolders(deviceCfg).length === 0) {
|
|
|
|
|
// Unused
|
|
|
|
|
return 'warning';
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-23 21:56:10 +02:00
|
|
|
if (typeof $scope.connections[deviceCfg.deviceID] === 'undefined') {
|
|
|
|
|
return 'info';
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-21 18:41:25 +00:00
|
|
|
if (deviceCfg.paused) {
|
2015-08-23 21:56:10 +02:00
|
|
|
return 'default';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($scope.connections[deviceCfg.deviceID].connected) {
|
2015-03-10 23:45:43 +01:00
|
|
|
if ($scope.completion[deviceCfg.deviceID] && $scope.completion[deviceCfg.deviceID]._total === 100) {
|
2014-11-26 13:39:59 +01:00
|
|
|
return 'success';
|
|
|
|
|
} else {
|
|
|
|
|
return 'primary';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-11-29 09:42:16 +01:00
|
|
|
// Disconnected
|
2014-11-26 13:39:59 +01:00
|
|
|
return 'info';
|
|
|
|
|
};
|
|
|
|
|
|
2016-06-01 19:06:36 +00:00
|
|
|
$scope.syncthingStatus = function () {
|
|
|
|
|
var syncCount = 0;
|
|
|
|
|
var notifyCount = 0;
|
|
|
|
|
var pauseCount = 0;
|
2016-06-01 20:24:43 +00:00
|
|
|
|
2016-06-01 19:06:36 +00:00
|
|
|
// loop through all folders
|
|
|
|
|
var folderListCache = $scope.folderList();
|
|
|
|
|
for (var i = 0; i < folderListCache.length; i++) {
|
|
|
|
|
var status = $scope.folderStatus(folderListCache[i]);
|
|
|
|
|
switch (status) {
|
|
|
|
|
case 'syncing':
|
|
|
|
|
syncCount++;
|
|
|
|
|
break;
|
|
|
|
|
case 'stopped':
|
|
|
|
|
case 'unknown':
|
|
|
|
|
case 'outofsync':
|
|
|
|
|
case 'error':
|
|
|
|
|
notifyCount++;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// loop through all devices
|
|
|
|
|
var deviceCount = $scope.devices.length;
|
|
|
|
|
for (var i = 0; i < $scope.devices.length; i++) {
|
|
|
|
|
var status = $scope.deviceStatus({
|
|
|
|
|
deviceID:$scope.devices[i].deviceID
|
|
|
|
|
});
|
|
|
|
|
switch (status) {
|
|
|
|
|
case 'unknown':
|
|
|
|
|
notifyCount++;
|
|
|
|
|
break;
|
|
|
|
|
case 'paused':
|
|
|
|
|
pauseCount++;
|
|
|
|
|
break;
|
|
|
|
|
case 'unused':
|
|
|
|
|
deviceCount--;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// enumerate notifications
|
2017-12-15 20:01:56 +00:00
|
|
|
if ($scope.openNoAuth || !$scope.configInSync || $scope.sizeOf($scope.deviceRejections) > 0 || $scope.sizeOf($scope.folderRejections) > 0 || $scope.errorList().length > 0 || !online) {
|
2016-06-01 19:06:36 +00:00
|
|
|
notifyCount++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// at least one folder is syncing
|
|
|
|
|
if (syncCount > 0) {
|
|
|
|
|
return 'sync';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// a device is unknown or a folder is stopped/unknown/outofsync/error or some other notification is open or gui offline
|
|
|
|
|
if (notifyCount > 0) {
|
|
|
|
|
return 'notify';
|
|
|
|
|
}
|
2016-06-01 20:24:43 +00:00
|
|
|
|
2016-06-01 19:06:36 +00:00
|
|
|
// all used devices are paused except (this) one
|
|
|
|
|
if (pauseCount === deviceCount-1) {
|
|
|
|
|
return 'pause';
|
|
|
|
|
}
|
2016-06-01 20:24:43 +00:00
|
|
|
|
2016-06-01 19:06:36 +00:00
|
|
|
return 'default';
|
|
|
|
|
};
|
|
|
|
|
|
2014-11-26 13:39:59 +01:00
|
|
|
$scope.deviceAddr = function (deviceCfg) {
|
2015-03-10 23:45:43 +01:00
|
|
|
var conn = $scope.connections[deviceCfg.deviceID];
|
2015-08-23 21:56:10 +02:00
|
|
|
if (conn && conn.connected) {
|
2015-03-10 23:45:43 +01:00
|
|
|
return conn.address;
|
2014-11-26 13:39:59 +01:00
|
|
|
}
|
|
|
|
|
return '?';
|
|
|
|
|
};
|
|
|
|
|
|
2016-12-21 16:35:20 +00:00
|
|
|
$scope.friendlyNameFromShort = function (shortID) {
|
|
|
|
|
var matches = $scope.devices.filter(function (n) {
|
|
|
|
|
return n.deviceID.substr(0, 7) === shortID;
|
|
|
|
|
});
|
|
|
|
|
if (matches.length !== 1) {
|
|
|
|
|
return shortID;
|
|
|
|
|
}
|
|
|
|
|
return matches[0].name;
|
|
|
|
|
};
|
|
|
|
|
|
2014-11-26 13:39:59 +01:00
|
|
|
$scope.findDevice = function (deviceID) {
|
|
|
|
|
var matches = $scope.devices.filter(function (n) {
|
2016-03-11 09:48:46 +00:00
|
|
|
return n.deviceID === deviceID;
|
2014-11-26 13:39:59 +01:00
|
|
|
});
|
2016-03-11 09:48:46 +00:00
|
|
|
if (matches.length !== 1) {
|
2014-11-26 13:39:59 +01:00
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
return matches[0];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$scope.deviceName = function (deviceCfg) {
|
2016-04-05 06:36:53 +00:00
|
|
|
if (typeof deviceCfg === 'undefined' || typeof deviceCfg.deviceID === 'undefined') {
|
2014-11-26 13:39:59 +01:00
|
|
|
return "";
|
|
|
|
|
}
|
2015-03-10 23:45:43 +01:00
|
|
|
if (deviceCfg.name) {
|
|
|
|
|
return deviceCfg.name;
|
2014-11-26 13:39:59 +01:00
|
|
|
}
|
2015-03-10 23:45:43 +01:00
|
|
|
return deviceCfg.deviceID.substr(0, 6);
|
2014-11-26 13:39:59 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$scope.thisDeviceName = function () {
|
|
|
|
|
var device = $scope.thisDevice();
|
|
|
|
|
if (typeof device === 'undefined') {
|
|
|
|
|
return "(unknown device)";
|
|
|
|
|
}
|
2015-03-10 23:45:43 +01:00
|
|
|
if (device.name) {
|
|
|
|
|
return device.name;
|
2014-11-26 13:39:59 +01:00
|
|
|
}
|
2015-03-10 23:45:43 +01:00
|
|
|
return device.deviceID.substr(0, 6);
|
2014-11-26 13:39:59 +01:00
|
|
|
};
|
|
|
|
|
|
2016-12-21 18:41:25 +00:00
|
|
|
$scope.setDevicePause = function (device, pause) {
|
|
|
|
|
$scope.devices.forEach(function (cfg) {
|
|
|
|
|
if (cfg.deviceID == device) {
|
|
|
|
|
cfg.paused = pause;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
$scope.config.devices = $scope.devices;
|
|
|
|
|
$scope.saveConfig();
|
2015-08-23 21:56:10 +02:00
|
|
|
};
|
|
|
|
|
|
2016-12-21 18:41:25 +00:00
|
|
|
$scope.setFolderPause = function (folder, pause) {
|
|
|
|
|
var cfg = $scope.folders[folder];
|
|
|
|
|
if (cfg) {
|
|
|
|
|
cfg.paused = pause;
|
|
|
|
|
$scope.config.folders = folderList($scope.folders);
|
|
|
|
|
$scope.saveConfig();
|
|
|
|
|
}
|
2015-08-23 21:56:10 +02:00
|
|
|
};
|
|
|
|
|
|
2017-03-20 13:55:08 +00:00
|
|
|
$scope.showDiscoveryFailures = function () {
|
|
|
|
|
$('#discovery-failures').modal();
|
|
|
|
|
};
|
|
|
|
|
|
2017-12-24 22:26:05 +00:00
|
|
|
$scope.logging = {
|
|
|
|
|
facilities: {},
|
|
|
|
|
refreshFacilities: function() {
|
|
|
|
|
$http.get(urlbase + '/system/debug').success(function (data) {
|
|
|
|
|
var facilities = {};
|
|
|
|
|
data.enabled = data.enabled || [];
|
|
|
|
|
$.each(data.facilities, function(key, value) {
|
|
|
|
|
facilities[key] = {
|
|
|
|
|
description: value,
|
|
|
|
|
enabled: data.enabled.indexOf(key) > -1
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
$scope.logging.facilities = facilities;
|
|
|
|
|
}).error($scope.emitHTTPError);
|
|
|
|
|
},
|
|
|
|
|
show: function() {
|
|
|
|
|
$scope.logging.refreshFacilities();
|
2018-01-01 14:39:23 +00:00
|
|
|
$scope.logging.timer = $timeout($scope.logging.fetch);
|
2017-12-24 22:26:05 +00:00
|
|
|
$('#logViewer').modal().on('hidden.bs.modal', function () {
|
2018-01-01 14:39:23 +00:00
|
|
|
$timeout.cancel($scope.logging.timer);
|
2017-12-24 22:26:05 +00:00
|
|
|
$scope.logging.timer = null;
|
|
|
|
|
$scope.logging.entries = [];
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
onFacilityChange: function(facility) {
|
|
|
|
|
var enabled = $scope.logging.facilities[facility].enabled;
|
|
|
|
|
// Disable checkboxes while we're in flight.
|
|
|
|
|
$.each($scope.logging.facilities, function(key) {
|
|
|
|
|
$scope.logging.facilities[key].enabled = null;
|
|
|
|
|
})
|
|
|
|
|
$http.post(urlbase + '/system/debug?' + (enabled ? 'enable=':'disable=') + facility)
|
|
|
|
|
.success($scope.logging.refreshFacilities)
|
|
|
|
|
.error($scope.emitHTTPError);
|
|
|
|
|
},
|
|
|
|
|
timer: null,
|
|
|
|
|
entries: [],
|
|
|
|
|
paused: false,
|
|
|
|
|
content: function() {
|
|
|
|
|
var content = "";
|
|
|
|
|
$.each($scope.logging.entries, function (idx, entry) {
|
|
|
|
|
content += entry.when.split('.')[0].replace('T', ' ') + ' ' + entry.message + "\n";
|
|
|
|
|
});
|
|
|
|
|
return content;
|
|
|
|
|
},
|
|
|
|
|
fetch: function() {
|
|
|
|
|
var textArea = $('#logViewerText');
|
|
|
|
|
if (textArea.is(":focus")) {
|
|
|
|
|
if (!$scope.logging.timer) return;
|
2018-01-01 14:39:23 +00:00
|
|
|
$scope.logging.timer = $timeout($scope.logging.fetch, 500);
|
2017-12-24 22:26:05 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var last = null;
|
|
|
|
|
if ($scope.logging.entries.length > 0) {
|
|
|
|
|
last = $scope.logging.entries[$scope.logging.entries.length-1].when;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$http.get(urlbase + '/system/log' + (last ? '?since=' + encodeURIComponent(last) : '')).success(function (data) {
|
|
|
|
|
if (!$scope.logging.timer) return;
|
2018-01-01 14:39:23 +00:00
|
|
|
$scope.logging.timer = $timeout($scope.logging.fetch, 2000);
|
2017-12-24 22:26:05 +00:00
|
|
|
if (!textArea.is(":focus")) {
|
|
|
|
|
if (data.messages) {
|
|
|
|
|
$scope.logging.entries.push.apply($scope.logging.entries, data.messages);
|
|
|
|
|
}
|
|
|
|
|
textArea.scrollTop(textArea[0].scrollHeight);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-11-26 13:39:59 +01:00
|
|
|
$scope.editSettings = function () {
|
|
|
|
|
// Make a working copy
|
2015-03-10 23:45:43 +01:00
|
|
|
$scope.tmpOptions = angular.copy($scope.config.options);
|
|
|
|
|
$scope.tmpOptions.deviceName = $scope.thisDevice().name;
|
2017-01-27 12:17:06 +00:00
|
|
|
$scope.tmpOptions.upgrades = "none";
|
|
|
|
|
if ($scope.tmpOptions.autoUpgradeIntervalH > 0) {
|
|
|
|
|
$scope.tmpOptions.upgrades = "stable";
|
|
|
|
|
}
|
|
|
|
|
if ($scope.tmpOptions.upgradeToPreReleases) {
|
|
|
|
|
$scope.tmpOptions.upgrades = "candidate";
|
|
|
|
|
}
|
2015-03-10 23:45:43 +01:00
|
|
|
$scope.tmpGUI = angular.copy($scope.config.gui);
|
2014-11-26 13:39:59 +01:00
|
|
|
$('#settings').modal();
|
|
|
|
|
};
|
|
|
|
|
|
2017-04-24 08:51:08 +09:00
|
|
|
$scope.saveConfig = function (cb) {
|
2014-11-26 13:39:59 +01:00
|
|
|
var cfg = JSON.stringify($scope.config);
|
|
|
|
|
var opts = {
|
|
|
|
|
headers: {
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
}
|
|
|
|
|
};
|
2015-04-06 10:23:27 +02:00
|
|
|
$http.post(urlbase + '/system/config', cfg, opts).success(function () {
|
|
|
|
|
$http.get(urlbase + '/system/config/insync').success(function (data) {
|
2014-11-26 13:39:59 +01:00
|
|
|
$scope.configInSync = data.configInSync;
|
2017-04-24 08:51:08 +09:00
|
|
|
if (cb) {
|
|
|
|
|
cb();
|
|
|
|
|
}
|
2014-11-26 13:39:59 +01:00
|
|
|
});
|
2015-01-05 15:42:27 +01:00
|
|
|
}).error($scope.emitHTTPError);
|
2014-11-26 13:39:59 +01:00
|
|
|
};
|
|
|
|
|
|
2017-10-12 06:16:46 +00:00
|
|
|
$scope.urVersions = function() {
|
|
|
|
|
var result = [];
|
|
|
|
|
if ($scope.system) {
|
|
|
|
|
for (var i = $scope.system.urVersionMax; i >= 2; i--) {
|
|
|
|
|
result.push("" + i);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
};
|
|
|
|
|
|
2014-11-26 13:39:59 +01:00
|
|
|
$scope.saveSettings = function () {
|
|
|
|
|
// Make sure something changed
|
2015-03-10 23:45:43 +01:00
|
|
|
var changed = !angular.equals($scope.config.options, $scope.tmpOptions) || !angular.equals($scope.config.gui, $scope.tmpGUI);
|
2016-01-10 15:37:31 +00:00
|
|
|
var themeChanged = $scope.config.gui.theme !== $scope.tmpGUI.theme;
|
2014-11-26 13:39:59 +01:00
|
|
|
if (changed) {
|
2017-10-12 06:16:46 +00:00
|
|
|
// Angular has issues with selects with numeric values, so we handle strings here.
|
|
|
|
|
$scope.tmpOptions.urAccepted = parseInt($scope.tmpOptions._urAcceptedStr);
|
2017-01-27 12:17:06 +00:00
|
|
|
// Check if auto-upgrade has been enabled or disabled. This
|
|
|
|
|
// also has an effect on usage reporting, so do the check
|
|
|
|
|
// for that later.
|
|
|
|
|
if ($scope.tmpOptions.upgrades == "candidate") {
|
|
|
|
|
$scope.tmpOptions.autoUpgradeIntervalH = $scope.tmpOptions.autoUpgradeIntervalH || 12;
|
|
|
|
|
$scope.tmpOptions.upgradeToPreReleases = true;
|
2017-10-12 06:16:46 +00:00
|
|
|
$scope.tmpOptions.urAccepted = $scope.system.urVersionMax;
|
|
|
|
|
$scope.tmpOptions.urSeen = $scope.system.urVersionMax;
|
2017-01-27 12:17:06 +00:00
|
|
|
} else if ($scope.tmpOptions.upgrades == "stable") {
|
|
|
|
|
$scope.tmpOptions.autoUpgradeIntervalH = $scope.tmpOptions.autoUpgradeIntervalH || 12;
|
|
|
|
|
$scope.tmpOptions.upgradeToPreReleases = false;
|
|
|
|
|
} else {
|
|
|
|
|
$scope.tmpOptions.autoUpgradeIntervalH = 0;
|
|
|
|
|
}
|
|
|
|
|
|
2014-11-26 13:39:59 +01:00
|
|
|
// Check if protocol will need to be changed on restart
|
2015-03-10 23:45:43 +01:00
|
|
|
if ($scope.config.gui.useTLS !== $scope.tmpGUI.useTLS) {
|
2014-11-26 13:39:59 +01:00
|
|
|
$scope.protocolChanged = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Apply new settings locally
|
2015-03-10 23:45:43 +01:00
|
|
|
$scope.thisDevice().name = $scope.tmpOptions.deviceName;
|
|
|
|
|
$scope.config.options = angular.copy($scope.tmpOptions);
|
|
|
|
|
$scope.config.gui = angular.copy($scope.tmpGUI);
|
2014-11-18 22:57:21 +00:00
|
|
|
|
2016-05-04 19:38:12 +00:00
|
|
|
['listenAddresses', 'globalAnnounceServers'].forEach(function (key) {
|
2015-07-03 14:07:38 +02:00
|
|
|
$scope.config.options[key] = $scope.config.options["_" + key + "Str"].split(/[ ,]+/).map(function (x) {
|
2014-11-18 22:57:21 +00:00
|
|
|
return x.trim();
|
|
|
|
|
});
|
2014-11-26 13:39:59 +01:00
|
|
|
});
|
|
|
|
|
|
2017-05-03 19:34:24 +02:00
|
|
|
$scope.saveConfig(function () {
|
|
|
|
|
if (themeChanged) {
|
|
|
|
|
document.location.reload(true);
|
|
|
|
|
}
|
|
|
|
|
});
|
2014-11-26 13:39:59 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$('#settings').modal("hide");
|
|
|
|
|
};
|
|
|
|
|
|
2015-07-03 14:07:38 +02:00
|
|
|
$scope.saveAdvanced = function () {
|
2015-07-09 21:58:58 +01:00
|
|
|
$scope.config = $scope.advancedConfig;
|
2015-07-03 14:07:38 +02:00
|
|
|
$scope.saveConfig();
|
|
|
|
|
$('#advanced').modal("hide");
|
|
|
|
|
};
|
|
|
|
|
|
2014-11-26 13:39:59 +01:00
|
|
|
$scope.restart = function () {
|
|
|
|
|
restarting = true;
|
|
|
|
|
$('#restarting').modal();
|
2015-04-06 10:23:27 +02:00
|
|
|
$http.post(urlbase + '/system/restart');
|
2014-11-26 13:39:59 +01:00
|
|
|
$scope.configInSync = true;
|
|
|
|
|
|
|
|
|
|
// Switch webpage protocol if needed
|
|
|
|
|
if ($scope.protocolChanged) {
|
|
|
|
|
var protocol = 'http';
|
|
|
|
|
|
2015-03-10 23:45:43 +01:00
|
|
|
if ($scope.config.gui.useTLS) {
|
2014-11-26 13:39:59 +01:00
|
|
|
protocol = 'https';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setTimeout(function () {
|
|
|
|
|
window.location.protocol = protocol;
|
|
|
|
|
}, 2500);
|
|
|
|
|
|
|
|
|
|
$scope.protocolChanged = false;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$scope.upgrade = function () {
|
|
|
|
|
restarting = true;
|
2015-04-22 21:41:08 +09:00
|
|
|
$('#majorUpgrade').modal('hide');
|
2014-11-26 13:39:59 +01:00
|
|
|
$('#upgrading').modal();
|
2015-04-06 10:23:27 +02:00
|
|
|
$http.post(urlbase + '/system/upgrade').success(function () {
|
2014-11-26 13:39:59 +01:00
|
|
|
$('#restarting').modal();
|
|
|
|
|
$('#upgrading').modal('hide');
|
|
|
|
|
}).error(function () {
|
|
|
|
|
$('#upgrading').modal('hide');
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$scope.shutdown = function () {
|
|
|
|
|
restarting = true;
|
2015-04-06 10:23:27 +02:00
|
|
|
$http.post(urlbase + '/system/shutdown').success(function () {
|
2014-11-26 13:39:59 +01:00
|
|
|
$('#shutdown').modal();
|
2015-01-05 15:42:27 +01:00
|
|
|
}).error($scope.emitHTTPError);
|
2014-11-26 13:39:59 +01:00
|
|
|
$scope.configInSync = true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$scope.editDevice = function (deviceCfg) {
|
|
|
|
|
$scope.currentDevice = $.extend({}, deviceCfg);
|
|
|
|
|
$scope.editingExisting = true;
|
2016-12-02 21:07:02 +00:00
|
|
|
$scope.willBeReintroducedBy = undefined;
|
|
|
|
|
if (deviceCfg.introducedBy) {
|
|
|
|
|
var introducerDevice = $scope.findDevice(deviceCfg.introducedBy);
|
|
|
|
|
if (introducerDevice && introducerDevice.introducer) {
|
|
|
|
|
$scope.willBeReintroducedBy = $scope.deviceName(introducerDevice);
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-07-03 14:07:38 +02:00
|
|
|
$scope.currentDevice._addressesStr = deviceCfg.addresses.join(', ');
|
2015-07-20 14:48:03 +02:00
|
|
|
$scope.currentDevice.selectedFolders = {};
|
|
|
|
|
$scope.deviceFolders($scope.currentDevice).forEach(function (folder) {
|
|
|
|
|
$scope.currentDevice.selectedFolders[folder] = true;
|
|
|
|
|
});
|
2014-11-26 13:39:59 +01:00
|
|
|
$scope.deviceEditor.$setPristine();
|
|
|
|
|
$('#editDevice').modal();
|
|
|
|
|
};
|
|
|
|
|
|
2016-03-25 20:29:07 +00:00
|
|
|
$scope.addDevice = function (deviceID, name) {
|
2015-12-18 15:33:11 -05:00
|
|
|
return $http.get(urlbase + '/system/discovery')
|
2014-11-26 13:39:59 +01:00
|
|
|
.success(function (registry) {
|
2017-06-16 15:27:09 +00:00
|
|
|
$scope.discovery = [];
|
2017-06-29 13:47:42 +00:00
|
|
|
outer:
|
2017-06-16 15:27:09 +00:00
|
|
|
for (var id in registry) {
|
|
|
|
|
if ($scope.discovery.length === 5) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
2017-06-29 13:47:42 +00:00
|
|
|
for (var i = 0; i < $scope.devices.length; i++) {
|
|
|
|
|
if ($scope.devices[i].deviceID === id) {
|
|
|
|
|
continue outer;
|
|
|
|
|
}
|
2017-06-16 15:27:09 +00:00
|
|
|
}
|
2017-06-29 13:47:42 +00:00
|
|
|
$scope.discovery.push(id);
|
2017-06-16 15:27:09 +00:00
|
|
|
}
|
2014-11-26 13:39:59 +01:00
|
|
|
})
|
|
|
|
|
.then(function () {
|
|
|
|
|
$scope.currentDevice = {
|
2016-03-25 20:29:07 +00:00
|
|
|
name: name,
|
2015-12-18 15:33:11 -05:00
|
|
|
deviceID: deviceID,
|
2015-07-03 14:07:38 +02:00
|
|
|
_addressesStr: 'dynamic',
|
2015-03-10 23:45:43 +01:00
|
|
|
compression: 'metadata',
|
|
|
|
|
introducer: false,
|
2014-12-05 00:22:16 +00:00
|
|
|
selectedFolders: {}
|
2014-11-26 13:39:59 +01:00
|
|
|
};
|
|
|
|
|
$scope.editingExisting = false;
|
|
|
|
|
$scope.deviceEditor.$setPristine();
|
|
|
|
|
$('#editDevice').modal();
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$scope.deleteDevice = function () {
|
|
|
|
|
$('#editDevice').modal('hide');
|
|
|
|
|
if (!$scope.editingExisting) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$scope.devices = $scope.devices.filter(function (n) {
|
2015-03-10 23:45:43 +01:00
|
|
|
return n.deviceID !== $scope.currentDevice.deviceID;
|
2014-11-26 13:39:59 +01:00
|
|
|
});
|
2015-03-10 23:45:43 +01:00
|
|
|
$scope.config.devices = $scope.devices;
|
2014-12-27 23:12:12 +00:00
|
|
|
// In case we later added the device manually, remove the ignoral
|
|
|
|
|
// record.
|
2015-03-10 23:45:43 +01:00
|
|
|
$scope.config.ignoredDevices = $scope.config.ignoredDevices.filter(function (id) {
|
|
|
|
|
return id !== $scope.currentDevice.deviceID;
|
2014-12-27 23:12:12 +00:00
|
|
|
});
|
2014-11-26 13:39:59 +01:00
|
|
|
|
|
|
|
|
for (var id in $scope.folders) {
|
2015-03-10 23:45:43 +01:00
|
|
|
$scope.folders[id].devices = $scope.folders[id].devices.filter(function (n) {
|
|
|
|
|
return n.deviceID !== $scope.currentDevice.deviceID;
|
2014-11-26 13:39:59 +01:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$scope.saveConfig();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$scope.saveDevice = function () {
|
|
|
|
|
$('#editDevice').modal('hide');
|
2014-12-27 23:12:12 +00:00
|
|
|
$scope.saveDeviceConfig($scope.currentDevice);
|
2015-12-18 15:33:11 -05:00
|
|
|
$scope.dismissDeviceRejection($scope.currentDevice.deviceID);
|
2014-12-27 23:12:12 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$scope.saveDeviceConfig = function (deviceCfg) {
|
2015-07-03 14:07:38 +02:00
|
|
|
deviceCfg.addresses = deviceCfg._addressesStr.split(',').map(function (x) {
|
2014-11-26 13:39:59 +01:00
|
|
|
return x.trim();
|
|
|
|
|
});
|
|
|
|
|
|
2016-03-11 09:48:46 +00:00
|
|
|
var done = false;
|
|
|
|
|
for (var i = 0; i < $scope.devices.length && !done; i++) {
|
2015-03-10 23:45:43 +01:00
|
|
|
if ($scope.devices[i].deviceID === deviceCfg.deviceID) {
|
2014-11-26 13:39:59 +01:00
|
|
|
$scope.devices[i] = deviceCfg;
|
|
|
|
|
done = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!done) {
|
|
|
|
|
$scope.devices.push(deviceCfg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$scope.devices.sort(deviceCompare);
|
2015-03-10 23:45:43 +01:00
|
|
|
$scope.config.devices = $scope.devices;
|
2014-12-27 23:12:12 +00:00
|
|
|
// In case we are adding the device manually, remove the ignoral
|
|
|
|
|
// record.
|
2015-03-10 23:45:43 +01:00
|
|
|
$scope.config.ignoredDevices = $scope.config.ignoredDevices.filter(function (id) {
|
|
|
|
|
return id !== deviceCfg.deviceID;
|
2014-12-27 23:12:12 +00:00
|
|
|
});
|
2014-11-26 13:39:59 +01:00
|
|
|
|
2015-07-20 14:48:03 +02:00
|
|
|
for (var id in deviceCfg.selectedFolders) {
|
|
|
|
|
if (deviceCfg.selectedFolders[id]) {
|
|
|
|
|
var found = false;
|
|
|
|
|
for (i = 0; i < $scope.folders[id].devices.length; i++) {
|
2016-03-11 09:48:46 +00:00
|
|
|
if ($scope.folders[id].devices[i].deviceID === deviceCfg.deviceID) {
|
2015-07-20 14:48:03 +02:00
|
|
|
found = true;
|
|
|
|
|
break;
|
2014-12-05 00:22:16 +00:00
|
|
|
}
|
2015-07-20 14:48:03 +02:00
|
|
|
}
|
2014-12-05 00:22:16 +00:00
|
|
|
|
2015-07-20 14:48:03 +02:00
|
|
|
if (!found) {
|
|
|
|
|
$scope.folders[id].devices.push({
|
|
|
|
|
deviceID: deviceCfg.deviceID
|
2014-12-05 00:22:16 +00:00
|
|
|
});
|
|
|
|
|
}
|
2015-07-20 14:48:03 +02:00
|
|
|
} else {
|
|
|
|
|
$scope.folders[id].devices = $scope.folders[id].devices.filter(function (n) {
|
2016-03-11 09:48:46 +00:00
|
|
|
return n.deviceID !== deviceCfg.deviceID;
|
2015-07-20 14:48:03 +02:00
|
|
|
});
|
2014-12-05 00:22:16 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-11-26 13:39:59 +01:00
|
|
|
$scope.saveConfig();
|
|
|
|
|
};
|
|
|
|
|
|
2014-12-27 23:12:12 +00:00
|
|
|
$scope.dismissDeviceRejection = function (device) {
|
|
|
|
|
delete $scope.deviceRejections[device];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$scope.ignoreRejectedDevice = function (device) {
|
2015-03-10 23:45:43 +01:00
|
|
|
$scope.config.ignoredDevices.push(device);
|
2014-12-27 23:12:12 +00:00
|
|
|
$scope.saveConfig();
|
|
|
|
|
$scope.dismissDeviceRejection(device);
|
|
|
|
|
};
|
|
|
|
|
|
2014-11-26 13:39:59 +01:00
|
|
|
$scope.otherDevices = function () {
|
|
|
|
|
return $scope.devices.filter(function (n) {
|
2015-03-10 23:45:43 +01:00
|
|
|
return n.deviceID !== $scope.myID;
|
2014-11-26 13:39:59 +01:00
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$scope.thisDevice = function () {
|
2016-03-11 09:48:46 +00:00
|
|
|
for (var i = 0; i < $scope.devices.length; i++) {
|
|
|
|
|
var n = $scope.devices[i];
|
2015-03-10 23:45:43 +01:00
|
|
|
if (n.deviceID === $scope.myID) {
|
2014-11-26 13:39:59 +01:00
|
|
|
return n;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$scope.allDevices = function () {
|
|
|
|
|
var devices = $scope.otherDevices();
|
|
|
|
|
devices.push($scope.thisDevice());
|
|
|
|
|
return devices;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$scope.errorList = function () {
|
2015-10-12 10:12:57 +09:00
|
|
|
if (!$scope.errors) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
2014-11-26 13:39:59 +01:00
|
|
|
return $scope.errors.filter(function (e) {
|
2015-10-12 14:18:40 +09:00
|
|
|
return e.when > $scope.seenError;
|
2014-11-26 13:39:59 +01:00
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$scope.clearErrors = function () {
|
2015-10-12 14:18:40 +09:00
|
|
|
$scope.seenError = $scope.errors[$scope.errors.length - 1].when;
|
2015-04-06 10:23:27 +02:00
|
|
|
$http.post(urlbase + '/system/error/clear');
|
2014-11-26 13:39:59 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$scope.friendlyDevices = function (str) {
|
|
|
|
|
for (var i = 0; i < $scope.devices.length; i++) {
|
|
|
|
|
var cfg = $scope.devices[i];
|
2015-03-10 23:45:43 +01:00
|
|
|
str = str.replace(cfg.deviceID, $scope.deviceName(cfg));
|
2014-11-26 13:39:59 +01:00
|
|
|
}
|
|
|
|
|
return str;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$scope.folderList = function () {
|
|
|
|
|
return folderList($scope.folders);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$scope.directoryList = [];
|
|
|
|
|
|
2015-03-10 23:45:43 +01:00
|
|
|
$scope.$watch('currentFolder.path', function (newvalue) {
|
2017-07-20 13:16:54 +00:00
|
|
|
if (!newvalue) {
|
|
|
|
|
return;
|
2015-04-10 15:34:24 +08:00
|
|
|
}
|
2017-07-20 13:16:54 +00:00
|
|
|
$scope.currentFolder.path = expandTilde(newvalue);
|
2015-04-06 10:23:27 +02:00
|
|
|
$http.get(urlbase + '/system/browse', {
|
2014-11-26 13:39:59 +01:00
|
|
|
params: { current: newvalue }
|
|
|
|
|
}).success(function (data) {
|
|
|
|
|
$scope.directoryList = data;
|
2015-01-05 15:42:27 +01:00
|
|
|
}).error($scope.emitHTTPError);
|
2014-11-26 13:39:59 +01:00
|
|
|
});
|
|
|
|
|
|
2017-07-20 13:16:54 +00:00
|
|
|
$scope.$watch('currentFolder.label', function (newvalue) {
|
2017-08-10 15:31:25 +00:00
|
|
|
if (!newvalue || !shouldSetDefaultFolderPath()) {
|
2017-07-20 13:16:54 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
$scope.currentFolder.path = pathJoin($scope.config.options.defaultFolderPath, newvalue);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
$scope.$watch('currentFolder.id', function (newvalue) {
|
2017-08-10 15:31:25 +00:00
|
|
|
if (!newvalue || !shouldSetDefaultFolderPath() || $scope.currentFolder.label) {
|
2017-07-20 13:16:54 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
$scope.currentFolder.path = pathJoin($scope.config.options.defaultFolderPath, newvalue);
|
|
|
|
|
});
|
|
|
|
|
|
2016-06-12 14:06:48 +00:00
|
|
|
$scope.loadFormIntoScope = function (form) {
|
|
|
|
|
console.log('loadFormIntoScope',form.$name);
|
|
|
|
|
switch (form.$name) {
|
|
|
|
|
case 'deviceEditor':
|
|
|
|
|
$scope.deviceEditor = form;
|
|
|
|
|
break;
|
|
|
|
|
case 'folderEditor':
|
|
|
|
|
$scope.folderEditor = form;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2016-12-21 16:35:20 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$scope.globalChanges = function () {
|
|
|
|
|
$('#globalChanges').modal();
|
|
|
|
|
};
|
2016-06-12 14:06:48 +00:00
|
|
|
|
2017-06-12 08:54:08 +02:00
|
|
|
$scope.editFolderModal = function () {
|
2017-06-09 05:41:19 +00:00
|
|
|
$scope.folderPathErrors = {};
|
|
|
|
|
$scope.folderEditor.$setPristine();
|
|
|
|
|
$('#editIgnores textarea').val("");
|
|
|
|
|
$('#editFolder').modal();
|
|
|
|
|
};
|
|
|
|
|
|
2014-12-27 23:12:12 +00:00
|
|
|
$scope.editFolder = function (folderCfg) {
|
2017-07-20 13:16:54 +00:00
|
|
|
$scope.editingExisting = true;
|
2014-12-27 23:12:12 +00:00
|
|
|
$scope.currentFolder = angular.copy(folderCfg);
|
2016-03-11 09:48:46 +00:00
|
|
|
if ($scope.currentFolder.path.slice(-1) === $scope.system.pathSeparator) {
|
2015-03-10 23:45:43 +01:00
|
|
|
$scope.currentFolder.path = $scope.currentFolder.path.slice(0, -1);
|
2014-12-14 23:12:12 +00:00
|
|
|
}
|
2014-11-26 13:39:59 +01:00
|
|
|
$scope.currentFolder.selectedDevices = {};
|
2015-03-10 23:45:43 +01:00
|
|
|
$scope.currentFolder.devices.forEach(function (n) {
|
|
|
|
|
$scope.currentFolder.selectedDevices[n.deviceID] = true;
|
2014-11-26 13:39:59 +01:00
|
|
|
});
|
2015-06-12 13:04:00 +02:00
|
|
|
if ($scope.currentFolder.versioning && $scope.currentFolder.versioning.type === "trashcan") {
|
|
|
|
|
$scope.currentFolder.trashcanFileVersioning = true;
|
|
|
|
|
$scope.currentFolder.fileVersioningSelector = "trashcan";
|
|
|
|
|
$scope.currentFolder.trashcanClean = +$scope.currentFolder.versioning.params.cleanoutDays;
|
|
|
|
|
} else if ($scope.currentFolder.versioning && $scope.currentFolder.versioning.type === "simple") {
|
2014-11-26 13:39:59 +01:00
|
|
|
$scope.currentFolder.simpleFileVersioning = true;
|
2015-03-10 23:45:43 +01:00
|
|
|
$scope.currentFolder.fileVersioningSelector = "simple";
|
|
|
|
|
$scope.currentFolder.simpleKeep = +$scope.currentFolder.versioning.params.keep;
|
|
|
|
|
} else if ($scope.currentFolder.versioning && $scope.currentFolder.versioning.type === "staggered") {
|
2014-11-26 13:39:59 +01:00
|
|
|
$scope.currentFolder.staggeredFileVersioning = true;
|
2015-03-10 23:45:43 +01:00
|
|
|
$scope.currentFolder.fileVersioningSelector = "staggered";
|
|
|
|
|
$scope.currentFolder.staggeredMaxAge = Math.floor(+$scope.currentFolder.versioning.params.maxAge / 86400);
|
|
|
|
|
$scope.currentFolder.staggeredCleanInterval = +$scope.currentFolder.versioning.params.cleanInterval;
|
|
|
|
|
$scope.currentFolder.staggeredVersionsPath = $scope.currentFolder.versioning.params.versionsPath;
|
2015-03-19 11:31:21 +01:00
|
|
|
} else if ($scope.currentFolder.versioning && $scope.currentFolder.versioning.type === "external") {
|
|
|
|
|
$scope.currentFolder.externalFileVersioning = true;
|
|
|
|
|
$scope.currentFolder.fileVersioningSelector = "external";
|
|
|
|
|
$scope.currentFolder.externalCommand = $scope.currentFolder.versioning.params.command;
|
2014-11-26 13:39:59 +01:00
|
|
|
} else {
|
2015-03-10 23:45:43 +01:00
|
|
|
$scope.currentFolder.fileVersioningSelector = "none";
|
2014-11-26 13:39:59 +01:00
|
|
|
}
|
2015-06-12 13:04:00 +02:00
|
|
|
$scope.currentFolder.trashcanClean = $scope.currentFolder.trashcanClean || 0; // weeds out nulls and undefineds
|
2014-11-26 13:39:59 +01:00
|
|
|
$scope.currentFolder.simpleKeep = $scope.currentFolder.simpleKeep || 5;
|
|
|
|
|
$scope.currentFolder.staggeredCleanInterval = $scope.currentFolder.staggeredCleanInterval || 3600;
|
|
|
|
|
$scope.currentFolder.staggeredVersionsPath = $scope.currentFolder.staggeredVersionsPath || "";
|
|
|
|
|
|
|
|
|
|
// staggeredMaxAge can validly be zero, which we should not replace
|
|
|
|
|
// with the default value of 365. So only set the default if it's
|
|
|
|
|
// actually undefined.
|
|
|
|
|
if (typeof $scope.currentFolder.staggeredMaxAge === 'undefined') {
|
|
|
|
|
$scope.currentFolder.staggeredMaxAge = 365;
|
|
|
|
|
}
|
2015-03-19 11:31:21 +01:00
|
|
|
$scope.currentFolder.externalCommand = $scope.currentFolder.externalCommand || "";
|
2015-03-21 15:33:31 +01:00
|
|
|
|
2017-06-09 05:41:19 +00:00
|
|
|
$scope.editFolderModal();
|
2014-11-26 13:39:59 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$scope.addFolder = function () {
|
2016-05-26 07:25:34 +00:00
|
|
|
$http.get(urlbase + '/svc/random/string?length=10').success(function (data) {
|
2017-07-20 13:16:54 +00:00
|
|
|
$scope.editingExisting = false;
|
2017-06-09 05:41:19 +00:00
|
|
|
$scope.currentFolder = angular.copy($scope.folderDefaults);
|
2016-06-27 09:39:25 +00:00
|
|
|
$scope.currentFolder.id = (data.random.substr(0, 5) + '-' + data.random.substr(5, 5)).toLowerCase();
|
2017-06-09 05:41:19 +00:00
|
|
|
$scope.editFolderModal();
|
2016-05-26 07:25:34 +00:00
|
|
|
});
|
2014-11-26 13:39:59 +01:00
|
|
|
};
|
|
|
|
|
|
2016-03-11 09:48:46 +00:00
|
|
|
$scope.addFolderAndShare = function (folder, folderLabel, device) {
|
2014-12-27 23:12:12 +00:00
|
|
|
$scope.dismissFolderRejection(folder, device);
|
2017-07-20 13:16:54 +00:00
|
|
|
$scope.editingExisting = false;
|
2017-04-01 09:58:06 +00:00
|
|
|
$scope.currentFolder = angular.copy($scope.folderDefaults);
|
|
|
|
|
$scope.currentFolder.id = folder;
|
|
|
|
|
$scope.currentFolder.label = folderLabel;
|
|
|
|
|
$scope.currentFolder.viewFlags = {
|
|
|
|
|
importFromOtherDevice: true
|
2014-12-27 23:12:12 +00:00
|
|
|
};
|
|
|
|
|
$scope.currentFolder.selectedDevices[device] = true;
|
|
|
|
|
|
2017-06-09 05:41:19 +00:00
|
|
|
$scope.editFolderModal();
|
2014-12-27 23:12:12 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$scope.shareFolderWithDevice = function (folder, device) {
|
2015-03-10 23:45:43 +01:00
|
|
|
$scope.folders[folder].devices.push({
|
|
|
|
|
deviceID: device
|
2014-12-27 23:12:12 +00:00
|
|
|
});
|
2015-03-10 23:45:43 +01:00
|
|
|
$scope.config.folders = folderList($scope.folders);
|
2014-12-27 23:12:12 +00:00
|
|
|
$scope.saveConfig();
|
|
|
|
|
$scope.dismissFolderRejection(folder, device);
|
|
|
|
|
};
|
|
|
|
|
|
2014-11-26 13:39:59 +01:00
|
|
|
$scope.saveFolder = function () {
|
|
|
|
|
$('#editFolder').modal('hide');
|
2016-03-11 09:48:46 +00:00
|
|
|
var folderCfg = $scope.currentFolder;
|
2015-03-10 23:45:43 +01:00
|
|
|
folderCfg.devices = [];
|
2014-11-26 13:39:59 +01:00
|
|
|
folderCfg.selectedDevices[$scope.myID] = true;
|
|
|
|
|
for (var deviceID in folderCfg.selectedDevices) {
|
|
|
|
|
if (folderCfg.selectedDevices[deviceID] === true) {
|
2015-03-10 23:45:43 +01:00
|
|
|
folderCfg.devices.push({
|
|
|
|
|
deviceID: deviceID
|
2014-11-26 13:39:59 +01:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
delete folderCfg.selectedDevices;
|
|
|
|
|
|
2015-06-12 13:04:00 +02:00
|
|
|
if (folderCfg.fileVersioningSelector === "trashcan") {
|
|
|
|
|
folderCfg.versioning = {
|
|
|
|
|
'Type': 'trashcan',
|
|
|
|
|
'Params': {
|
|
|
|
|
'cleanoutDays': '' + folderCfg.trashcanClean
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
delete folderCfg.trashcanFileVersioning;
|
|
|
|
|
delete folderCfg.trashcanClean;
|
|
|
|
|
} else if (folderCfg.fileVersioningSelector === "simple") {
|
2015-03-10 23:45:43 +01:00
|
|
|
folderCfg.versioning = {
|
2014-11-26 13:39:59 +01:00
|
|
|
'Type': 'simple',
|
|
|
|
|
'Params': {
|
|
|
|
|
'keep': '' + folderCfg.simpleKeep
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
delete folderCfg.simpleFileVersioning;
|
|
|
|
|
delete folderCfg.simpleKeep;
|
2015-03-10 23:45:43 +01:00
|
|
|
} else if (folderCfg.fileVersioningSelector === "staggered") {
|
|
|
|
|
folderCfg.versioning = {
|
|
|
|
|
'type': 'staggered',
|
|
|
|
|
'params': {
|
2014-11-26 13:39:59 +01:00
|
|
|
'maxAge': '' + (folderCfg.staggeredMaxAge * 86400),
|
|
|
|
|
'cleanInterval': '' + folderCfg.staggeredCleanInterval,
|
|
|
|
|
'versionsPath': '' + folderCfg.staggeredVersionsPath
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
delete folderCfg.staggeredFileVersioning;
|
|
|
|
|
delete folderCfg.staggeredMaxAge;
|
|
|
|
|
delete folderCfg.staggeredCleanInterval;
|
|
|
|
|
delete folderCfg.staggeredVersionsPath;
|
|
|
|
|
|
2015-03-19 11:31:21 +01:00
|
|
|
} else if (folderCfg.fileVersioningSelector === "external") {
|
|
|
|
|
folderCfg.versioning = {
|
|
|
|
|
'Type': 'external',
|
|
|
|
|
'Params': {
|
|
|
|
|
'command': '' + folderCfg.externalCommand
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
delete folderCfg.externalFileVersioning;
|
|
|
|
|
delete folderCfg.externalCommand;
|
2014-11-26 13:39:59 +01:00
|
|
|
} else {
|
2015-03-10 23:45:43 +01:00
|
|
|
delete folderCfg.versioning;
|
2014-11-26 13:39:59 +01:00
|
|
|
}
|
|
|
|
|
|
2017-04-01 09:58:06 +00:00
|
|
|
var ignores = $('#editIgnores textarea').val().trim();
|
|
|
|
|
if (!$scope.editingExisting && ignores) {
|
|
|
|
|
folderCfg.paused = true;
|
|
|
|
|
};
|
|
|
|
|
|
2015-03-10 23:45:43 +01:00
|
|
|
$scope.folders[folderCfg.id] = folderCfg;
|
|
|
|
|
$scope.config.folders = folderList($scope.folders);
|
2014-11-26 13:39:59 +01:00
|
|
|
|
2017-04-24 08:51:08 +09:00
|
|
|
$scope.saveConfig(function () {
|
|
|
|
|
if (!$scope.editingExisting && ignores) {
|
|
|
|
|
$scope.saveIgnores(function () {
|
|
|
|
|
$scope.setFolderPause(folderCfg.id, false);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
2014-11-26 13:39:59 +01:00
|
|
|
};
|
2014-12-27 23:12:12 +00:00
|
|
|
|
|
|
|
|
$scope.dismissFolderRejection = function (folder, device) {
|
|
|
|
|
delete $scope.folderRejections[folder + "-" + device];
|
|
|
|
|
};
|
2014-11-26 13:39:59 +01:00
|
|
|
|
2017-05-31 18:04:00 +00:00
|
|
|
$scope.ignoreRejectedFolder = function (folder, device) {
|
|
|
|
|
$scope.config.ignoredFolders.push(folder);
|
|
|
|
|
$scope.saveConfig();
|
|
|
|
|
$scope.dismissFolderRejection(folder, device);
|
|
|
|
|
};
|
|
|
|
|
|
2014-12-01 20:50:27 +01:00
|
|
|
$scope.sharesFolder = function (folderCfg) {
|
|
|
|
|
var names = [];
|
2015-03-10 23:45:43 +01:00
|
|
|
folderCfg.devices.forEach(function (device) {
|
2016-03-11 09:48:46 +00:00
|
|
|
if (device.deviceID !== $scope.myID) {
|
2015-03-10 23:45:43 +01:00
|
|
|
names.push($scope.deviceName($scope.findDevice(device.deviceID)));
|
2014-12-01 20:50:27 +01:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
names.sort();
|
|
|
|
|
return names.join(", ");
|
2015-06-14 17:17:48 +03:00
|
|
|
};
|
2014-12-01 20:50:27 +01:00
|
|
|
|
2014-11-29 09:42:16 +01:00
|
|
|
$scope.deviceFolders = function (deviceCfg) {
|
|
|
|
|
var folders = [];
|
2017-12-15 20:01:56 +00:00
|
|
|
$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);
|
2014-12-04 21:11:30 +01:00
|
|
|
break;
|
2014-11-29 09:42:16 +01:00
|
|
|
}
|
2014-11-26 13:39:59 +01:00
|
|
|
}
|
2017-12-15 20:01:56 +00:00
|
|
|
});
|
2014-11-29 09:42:16 +01:00
|
|
|
return folders;
|
2014-11-26 13:39:59 +01:00
|
|
|
};
|
|
|
|
|
|
2016-06-01 20:24:43 +00:00
|
|
|
$scope.folderLabel = function (folderID) {
|
|
|
|
|
var label = $scope.folders[folderID].label;
|
|
|
|
|
return label.length > 0 ? label : folderID;
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-20 14:48:03 +02:00
|
|
|
$scope.deleteFolder = function (id) {
|
2014-11-26 13:39:59 +01:00
|
|
|
$('#editFolder').modal('hide');
|
|
|
|
|
if (!$scope.editingExisting) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-20 14:48:03 +02:00
|
|
|
delete $scope.folders[id];
|
|
|
|
|
delete $scope.model[id];
|
2015-03-10 23:45:43 +01:00
|
|
|
$scope.config.folders = folderList($scope.folders);
|
2015-07-20 14:48:03 +02:00
|
|
|
recalcLocalStateTotal();
|
2014-11-26 13:39:59 +01:00
|
|
|
|
|
|
|
|
$scope.saveConfig();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$scope.editIgnores = function () {
|
|
|
|
|
if (!$scope.editingExisting) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$('#editIgnoresButton').attr('disabled', 'disabled');
|
2015-04-06 10:23:27 +02:00
|
|
|
$http.get(urlbase + '/db/ignores?folder=' + encodeURIComponent($scope.currentFolder.id))
|
2014-11-26 13:39:59 +01:00
|
|
|
.success(function (data) {
|
|
|
|
|
data.ignore = data.ignore || [];
|
2016-06-12 14:06:48 +00:00
|
|
|
var textArea = $('#editIgnores textarea');
|
|
|
|
|
textArea.val(data.ignore.join('\n'));
|
|
|
|
|
$('#editIgnores').modal()
|
|
|
|
|
.one('shown.bs.modal', function () {
|
|
|
|
|
textArea.focus();
|
2014-11-26 13:39:59 +01:00
|
|
|
});
|
|
|
|
|
})
|
|
|
|
|
.then(function () {
|
|
|
|
|
$('#editIgnoresButton').removeAttr('disabled');
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2018-01-01 14:39:23 +00:00
|
|
|
function resetRestoreVersions() {
|
|
|
|
|
$scope.restoreVersions = {
|
|
|
|
|
folder: null,
|
|
|
|
|
selections: {},
|
|
|
|
|
versions: null,
|
|
|
|
|
tree: null,
|
|
|
|
|
errors: null,
|
|
|
|
|
filters: {},
|
|
|
|
|
massAction: function (name, action) {
|
|
|
|
|
$.each($scope.restoreVersions.versions, function(key) {
|
|
|
|
|
if (key.startsWith(name + '/') && (!$scope.restoreVersions.filters.text || key.indexOf($scope.restoreVersions.filters.text) > -1)) {
|
|
|
|
|
if (action == 'unset') {
|
|
|
|
|
delete $scope.restoreVersions.selections[key];
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var availableVersions = [];
|
|
|
|
|
$.each($scope.restoreVersions.filterVersions($scope.restoreVersions.versions[key]), function(idx, version) {
|
|
|
|
|
availableVersions.push(version.versionTime);
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if (availableVersions.length) {
|
|
|
|
|
availableVersions.sort(function (a, b) { return a - b; });
|
|
|
|
|
if (action == 'latest') {
|
|
|
|
|
$scope.restoreVersions.selections[key] = availableVersions.pop();
|
|
|
|
|
} else if (action == 'oldest') {
|
|
|
|
|
$scope.restoreVersions.selections[key] = availableVersions.shift();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
filterVersions: function(versions) {
|
|
|
|
|
var filteredVersions = [];
|
|
|
|
|
$.each(versions, function (idx, version) {
|
|
|
|
|
if (moment(version.versionTime).isBetween($scope.restoreVersions.filters['start'], $scope.restoreVersions.filters['end'], null, '[]')) {
|
|
|
|
|
filteredVersions.push(version);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return filteredVersions;
|
|
|
|
|
},
|
|
|
|
|
selectionCount: function() {
|
|
|
|
|
var count = 0;
|
|
|
|
|
$.each($scope.restoreVersions.selections, function(key, value) {
|
|
|
|
|
if (value) {
|
|
|
|
|
count++;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return count;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
restore: function() {
|
|
|
|
|
$scope.restoreVersions.tree.clear();
|
|
|
|
|
$scope.restoreVersions.tree = null;
|
|
|
|
|
$scope.restoreVersions.versions = null;
|
|
|
|
|
var selections = {};
|
|
|
|
|
$.each($scope.restoreVersions.selections, function(key, value) {
|
|
|
|
|
if (value) {
|
|
|
|
|
selections[key] = value;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
$scope.restoreVersions.selections = {};
|
|
|
|
|
|
|
|
|
|
$http.post(urlbase + '/folder/versions?folder=' + encodeURIComponent($scope.restoreVersions.folder), selections).success(function (data) {
|
|
|
|
|
if (Object.keys(data).length == 0) {
|
|
|
|
|
$('#restoreVersions').modal('hide');
|
|
|
|
|
} else {
|
|
|
|
|
$scope.restoreVersions.errors = data;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
show: function(folder) {
|
|
|
|
|
$scope.restoreVersions.folder = folder;
|
|
|
|
|
|
|
|
|
|
var closed = false;
|
|
|
|
|
var modalShown = $q.defer();
|
|
|
|
|
$('#restoreVersions').modal().on('hidden.bs.modal', function () {
|
|
|
|
|
closed = true;
|
|
|
|
|
resetRestoreVersions();
|
|
|
|
|
}).on('shown.bs.modal', function() {
|
|
|
|
|
modalShown.resolve();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
var dataReceived = $http.get(urlbase + '/folder/versions?folder=' + encodeURIComponent($scope.restoreVersions.folder))
|
|
|
|
|
.success(function (data) {
|
|
|
|
|
$.each(data, function(key, values) {
|
|
|
|
|
$.each(values, function(idx, value) {
|
|
|
|
|
value.modTime = new Date(value.modTime);
|
|
|
|
|
value.versionTime = new Date(value.versionTime);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
if (closed) return;
|
|
|
|
|
$scope.restoreVersions.versions = data;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
$q.all([dataReceived, modalShown.promise]).then(function() {
|
|
|
|
|
if (closed) {
|
|
|
|
|
resetRestoreVersions();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$scope.restoreVersions.tree = $("#restoreTree").fancytree({
|
|
|
|
|
extensions: ["table", "filter"],
|
|
|
|
|
quicksearch: true,
|
|
|
|
|
filter: {
|
|
|
|
|
autoApply: true,
|
|
|
|
|
counter: true,
|
|
|
|
|
hideExpandedCounter: true,
|
|
|
|
|
hideExpanders: true,
|
|
|
|
|
highlight: true,
|
|
|
|
|
leavesOnly: false,
|
|
|
|
|
nodata: true,
|
|
|
|
|
mode: "hide"
|
|
|
|
|
},
|
|
|
|
|
table: {
|
|
|
|
|
indentation: 20,
|
|
|
|
|
nodeColumnIdx: 0,
|
|
|
|
|
},
|
|
|
|
|
debugLevel: 2,
|
|
|
|
|
source: buildTree($scope.restoreVersions.versions),
|
|
|
|
|
renderColumns: function(event, data) {
|
|
|
|
|
var node = data.node,
|
|
|
|
|
$tdList = $(node.tr).find(">td"),
|
|
|
|
|
template;
|
|
|
|
|
if (node.folder) {
|
|
|
|
|
template = '<div ng-include="\'syncthing/folder/restoreVersionsMassActions.html\'" class="pull-right"/>';
|
|
|
|
|
} else {
|
|
|
|
|
template = '<div ng-include="\'syncthing/folder/restoreVersionsVersionSelector.html\'" class="pull-right"/>';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var scope = $rootScope.$new(true);
|
|
|
|
|
scope.key = node.key;
|
|
|
|
|
scope.restoreVersions = $scope.restoreVersions;
|
|
|
|
|
|
|
|
|
|
$tdList.eq(1).html(
|
|
|
|
|
$compile(template)(scope)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Force angular to redraw.
|
|
|
|
|
$timeout(function() {
|
|
|
|
|
$scope.$apply();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}).fancytree("getTree");
|
|
|
|
|
|
|
|
|
|
var minDate = moment(),
|
|
|
|
|
maxDate = moment(0, 'X'),
|
|
|
|
|
date;
|
|
|
|
|
|
|
|
|
|
// Find version window.
|
|
|
|
|
$.each($scope.restoreVersions.versions, function(key) {
|
|
|
|
|
$.each($scope.restoreVersions.versions[key], function(idx, version) {
|
|
|
|
|
date = moment(version.versionTime);
|
|
|
|
|
if (date.isBefore(minDate)) {
|
|
|
|
|
minDate = date;
|
|
|
|
|
}
|
|
|
|
|
if (date.isAfter(maxDate)) {
|
|
|
|
|
maxDate = date;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
$scope.restoreVersions.filters['start'] = minDate;
|
|
|
|
|
$scope.restoreVersions.filters['end'] = maxDate;
|
|
|
|
|
|
|
|
|
|
var ranges = {
|
|
|
|
|
'All time': [minDate, maxDate],
|
|
|
|
|
'Today': [moment(), moment()],
|
|
|
|
|
'Yesterday': [moment().subtract(1, 'days'), moment().subtract(1, 'days')],
|
|
|
|
|
'Last 7 Days': [moment().subtract(6, 'days'), moment()],
|
|
|
|
|
'Last 30 Days': [moment().subtract(29, 'days'), moment()],
|
|
|
|
|
'This Month': [moment().startOf('month'), moment().endOf('month')],
|
|
|
|
|
'Last Month': [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')]
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Filter out invalid ranges.
|
|
|
|
|
$.each(ranges, function(key, range) {
|
|
|
|
|
if (!range[0].isBetween(minDate, maxDate, null, '[]') && !range[1].isBetween(minDate, maxDate, null, '[]')) {
|
|
|
|
|
delete ranges[key];
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
$("#restoreVersionDateRange").daterangepicker({
|
|
|
|
|
timePicker: true,
|
|
|
|
|
timePicker24Hour: true,
|
|
|
|
|
timePickerSeconds: true,
|
|
|
|
|
autoUpdateInput: true,
|
|
|
|
|
opens: "left",
|
|
|
|
|
drops: "up",
|
|
|
|
|
startDate: minDate,
|
|
|
|
|
endDate: maxDate,
|
|
|
|
|
minDate: minDate,
|
|
|
|
|
maxDate: maxDate,
|
|
|
|
|
ranges: ranges,
|
|
|
|
|
locale: {
|
|
|
|
|
format: 'YYYY/MM/DD HH:mm:ss',
|
|
|
|
|
}
|
|
|
|
|
}).on('apply.daterangepicker', function(ev, picker) {
|
|
|
|
|
$scope.restoreVersions.filters['start'] = picker.startDate;
|
|
|
|
|
$scope.restoreVersions.filters['end'] = picker.endDate;
|
|
|
|
|
// Events for this UI element are not managed by angular.
|
|
|
|
|
// Force angular to wake up.
|
|
|
|
|
$timeout(function() {
|
|
|
|
|
$scope.$apply();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
resetRestoreVersions();
|
|
|
|
|
|
|
|
|
|
$scope.$watchCollection('restoreVersions.filters', function() {
|
|
|
|
|
if (!$scope.restoreVersions.tree) return;
|
|
|
|
|
|
|
|
|
|
$scope.restoreVersions.tree.filterNodes(function (node) {
|
|
|
|
|
if (node.folder) return false;
|
|
|
|
|
if ($scope.restoreVersions.filters.text && node.key.indexOf($scope.restoreVersions.filters.text) < 0) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if ($scope.restoreVersions.filterVersions(node.data.versions).length == 0) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2017-04-01 09:58:06 +00:00
|
|
|
$scope.editIgnoresOnAddingFolder = function () {
|
|
|
|
|
if ($scope.editingExisting) {
|
2014-11-26 13:39:59 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-01 09:58:06 +00:00
|
|
|
if ($scope.currentFolder.path.endsWith($scope.system.pathSeparator)) {
|
|
|
|
|
$scope.currentFolder.path = $scope.currentFolder.path.slice(0, -1);
|
|
|
|
|
};
|
|
|
|
|
$('#editIgnores').modal().one('shown.bs.modal', function () {
|
|
|
|
|
textArea.focus();
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2017-04-24 08:51:08 +09:00
|
|
|
$scope.saveIgnores = function (cb) {
|
2015-04-06 10:23:27 +02:00
|
|
|
$http.post(urlbase + '/db/ignores?folder=' + encodeURIComponent($scope.currentFolder.id), {
|
2014-11-26 13:39:59 +01:00
|
|
|
ignore: $('#editIgnores textarea').val().split('\n')
|
2017-04-24 08:51:08 +09:00
|
|
|
}).success(function () {
|
|
|
|
|
if (cb) {
|
|
|
|
|
cb();
|
|
|
|
|
}
|
2014-11-26 13:39:59 +01:00
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$scope.setAPIKey = function (cfg) {
|
2016-05-26 07:25:34 +00:00
|
|
|
$http.get(urlbase + '/svc/random/string?length=32').success(function (data) {
|
|
|
|
|
cfg.apiKey = data.random;
|
|
|
|
|
});
|
2014-11-26 13:39:59 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$scope.acceptUR = function () {
|
2017-10-12 06:16:46 +00:00
|
|
|
$scope.config.options.urAccepted = $scope.system.urVersionMax;
|
|
|
|
|
$scope.config.options.urSeen = $scope.system.urVersionMax;
|
2014-11-26 13:39:59 +01:00
|
|
|
$scope.saveConfig();
|
|
|
|
|
$('#ur').modal('hide');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$scope.declineUR = function () {
|
2017-10-12 06:16:46 +00:00
|
|
|
if ($scope.config.options.urAccepted === 0) {
|
|
|
|
|
$scope.config.options.urAccepted = -1;
|
|
|
|
|
}
|
|
|
|
|
$scope.config.options.urSeen = $scope.system.urVersionMax;
|
2014-11-26 13:39:59 +01:00
|
|
|
$scope.saveConfig();
|
|
|
|
|
$('#ur').modal('hide');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$scope.showNeed = function (folder) {
|
|
|
|
|
$scope.neededFolder = folder;
|
|
|
|
|
refreshNeed(folder);
|
2014-11-28 19:39:33 +01:00
|
|
|
$('#needed').modal().on('hidden.bs.modal', function () {
|
2014-11-26 13:39:59 +01:00
|
|
|
$scope.neededFolder = undefined;
|
|
|
|
|
$scope.needed = undefined;
|
2015-04-25 22:53:44 +01:00
|
|
|
$scope.neededCurrentPage = 1;
|
2014-11-26 13:39:59 +01:00
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2017-12-15 20:01:56 +00:00
|
|
|
$scope.showRemoteNeed = function (device) {
|
|
|
|
|
resetRemoteNeed();
|
|
|
|
|
$scope.remoteNeedDevice = device;
|
|
|
|
|
$scope.deviceFolders(device).forEach(function(folder) {
|
2018-01-14 12:08:40 +00:00
|
|
|
var comp = $scope.completion[device.deviceID][folder];
|
|
|
|
|
if (comp !== undefined && comp.needItems + comp.needDeletes === 0) {
|
2017-12-15 20:01:56 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
$scope.remoteNeedFolders.push(folder);
|
|
|
|
|
$scope.refreshRemoteNeed(folder, 1, 10);
|
|
|
|
|
});
|
|
|
|
|
$('#remoteNeed').modal().on('hidden.bs.modal', function () {
|
|
|
|
|
resetRemoteNeed();
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2015-06-26 14:22:52 +02:00
|
|
|
$scope.showFailed = function (folder) {
|
2018-01-14 17:01:06 +00:00
|
|
|
$scope.failed.folder = folder;
|
|
|
|
|
$scope.failed = $scope.refreshFailed(1, 10);
|
2015-06-26 14:22:52 +02:00
|
|
|
$('#failed').modal().on('hidden.bs.modal', function () {
|
2018-01-14 17:01:06 +00:00
|
|
|
$scope.failed = {};
|
2015-06-26 14:22:52 +02:00
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$scope.hasFailedFiles = function (folder) {
|
2018-01-14 17:01:06 +00:00
|
|
|
if (!$scope.model[folder]) {
|
2015-06-26 14:22:52 +02:00
|
|
|
return false;
|
|
|
|
|
}
|
2018-01-14 17:01:06 +00:00
|
|
|
return $scope.model[folder].pullErrors !== 0;
|
2015-06-26 14:22:52 +02:00
|
|
|
};
|
|
|
|
|
|
2014-11-26 13:39:59 +01:00
|
|
|
$scope.override = function (folder) {
|
2015-04-06 10:23:27 +02:00
|
|
|
$http.post(urlbase + "/db/override?folder=" + encodeURIComponent(folder));
|
2014-11-26 13:39:59 +01:00
|
|
|
};
|
|
|
|
|
|
2015-07-03 14:07:38 +02:00
|
|
|
$scope.advanced = function () {
|
2015-07-09 21:58:58 +01:00
|
|
|
$scope.advancedConfig = angular.copy($scope.config);
|
2015-07-03 14:07:38 +02:00
|
|
|
$('#advanced').modal('show');
|
|
|
|
|
};
|
|
|
|
|
|
2014-11-26 13:39:59 +01:00
|
|
|
$scope.showReportPreview = function () {
|
|
|
|
|
$scope.reportPreview = true;
|
|
|
|
|
};
|
|
|
|
|
|
2017-10-12 06:16:46 +00:00
|
|
|
$scope.refreshReportDataPreview = function () {
|
|
|
|
|
$scope.reportDataPreview = '';
|
2017-10-15 07:45:15 +00:00
|
|
|
if (!$scope.reportDataPreviewVersion) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
var version = parseInt($scope.reportDataPreviewVersion);
|
|
|
|
|
if ($scope.reportDataPreviewDiff && version > 2) {
|
|
|
|
|
$q.all([
|
|
|
|
|
$http.get(urlbase + '/svc/report?version=' + version),
|
|
|
|
|
$http.get(urlbase + '/svc/report?version=' + (version-1)),
|
|
|
|
|
]).then(function (responses) {
|
|
|
|
|
var newReport = responses[0].data;
|
|
|
|
|
var oldReport = responses[1].data;
|
|
|
|
|
angular.forEach(oldReport, function(_, key) {
|
|
|
|
|
delete newReport[key];
|
|
|
|
|
});
|
|
|
|
|
$scope.reportDataPreview = newReport;
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
$http.get(urlbase + '/svc/report?version=' + version).success(function (data) {
|
|
|
|
|
$scope.reportDataPreview = data;
|
|
|
|
|
}).error($scope.emitHTTPError);
|
|
|
|
|
}
|
2017-10-12 06:16:46 +00:00
|
|
|
};
|
|
|
|
|
|
2015-02-11 19:52:59 +01:00
|
|
|
$scope.rescanAllFolders = function () {
|
2015-04-06 10:23:27 +02:00
|
|
|
$http.post(urlbase + "/db/scan");
|
2015-02-11 19:52:59 +01:00
|
|
|
};
|
|
|
|
|
|
2014-11-26 13:39:59 +01:00
|
|
|
$scope.rescanFolder = function (folder) {
|
2015-04-06 10:23:27 +02:00
|
|
|
$http.post(urlbase + "/db/scan?folder=" + encodeURIComponent(folder));
|
2014-11-26 13:39:59 +01:00
|
|
|
};
|
|
|
|
|
|
2017-02-09 20:31:23 +00:00
|
|
|
$scope.setAllFoldersPause = function(pause) {
|
|
|
|
|
var folderListCache = $scope.folderList();
|
|
|
|
|
|
|
|
|
|
for (var i = 0; i < folderListCache.length; i++) {
|
|
|
|
|
folderListCache[i].paused = pause;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$scope.config.folders = folderList(folderListCache);
|
|
|
|
|
$scope.saveConfig();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$scope.isAtleastOneFolderPausedStateSetTo = function(pause) {
|
|
|
|
|
var folderListCache = $scope.folderList();
|
|
|
|
|
|
|
|
|
|
for (var i = 0; i < folderListCache.length; i++) {
|
|
|
|
|
if (folderListCache[i].paused == pause) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
|
Add job queue (fixes #629)
Request to terminate currently ongoing downloads and jump to the bumped file
incoming in 3, 2, 1.
Also, has a slightly strange effect where we pop a job off the queue, but
the copyChannel is still busy and blocks, though it gets moved to the
progress slice in the jobqueue, and looks like it's in progress which it isn't
as it's waiting to be picked up from the copyChan.
As a result, the progress emitter doesn't register on the task, and hence the file
doesn't have a progress bar, but cannot be replaced by a bump.
I guess I can fix progress bar issue by moving the progressEmiter.Register just
before passing the file to the copyChan, but then we are back to the initial
problem of a file with a progress bar, but no progress happening as it's stuck
on write to copyChan
I checked if there is a way to check for channel writeability (before popping)
but got struck by lightning just for bringing the idea up in #go-nuts.
My ideal scenario would be to check if copyChan is writeable, pop job from the
queue and shove it down handleFile. This way jobs would stay in the queue while
they cannot be handled, meaning that the `Bump` could bring your file up higher.
2014-12-01 19:23:06 +00:00
|
|
|
$scope.bumpFile = function (folder, file) {
|
2015-04-25 22:53:44 +01:00
|
|
|
var url = urlbase + "/db/prio?folder=" + encodeURIComponent(folder) + "&file=" + encodeURIComponent(file);
|
|
|
|
|
// In order to get the right view of data in the response.
|
|
|
|
|
url += "&page=" + $scope.neededCurrentPage;
|
|
|
|
|
url += "&perpage=" + $scope.neededPageSize;
|
|
|
|
|
$http.post(url).success(function (data) {
|
2016-03-11 09:48:46 +00:00
|
|
|
if ($scope.neededFolder === folder) {
|
Add job queue (fixes #629)
Request to terminate currently ongoing downloads and jump to the bumped file
incoming in 3, 2, 1.
Also, has a slightly strange effect where we pop a job off the queue, but
the copyChannel is still busy and blocks, though it gets moved to the
progress slice in the jobqueue, and looks like it's in progress which it isn't
as it's waiting to be picked up from the copyChan.
As a result, the progress emitter doesn't register on the task, and hence the file
doesn't have a progress bar, but cannot be replaced by a bump.
I guess I can fix progress bar issue by moving the progressEmiter.Register just
before passing the file to the copyChan, but then we are back to the initial
problem of a file with a progress bar, but no progress happening as it's stuck
on write to copyChan
I checked if there is a way to check for channel writeability (before popping)
but got struck by lightning just for bringing the idea up in #go-nuts.
My ideal scenario would be to check if copyChan is writeable, pop job from the
queue and shove it down handleFile. This way jobs would stay in the queue while
they cannot be handled, meaning that the `Bump` could bring your file up higher.
2014-12-01 19:23:06 +00:00
|
|
|
console.log("bumpFile", folder, data);
|
2015-04-25 22:53:44 +01:00
|
|
|
parseNeeded(data);
|
Add job queue (fixes #629)
Request to terminate currently ongoing downloads and jump to the bumped file
incoming in 3, 2, 1.
Also, has a slightly strange effect where we pop a job off the queue, but
the copyChannel is still busy and blocks, though it gets moved to the
progress slice in the jobqueue, and looks like it's in progress which it isn't
as it's waiting to be picked up from the copyChan.
As a result, the progress emitter doesn't register on the task, and hence the file
doesn't have a progress bar, but cannot be replaced by a bump.
I guess I can fix progress bar issue by moving the progressEmiter.Register just
before passing the file to the copyChan, but then we are back to the initial
problem of a file with a progress bar, but no progress happening as it's stuck
on write to copyChan
I checked if there is a way to check for channel writeability (before popping)
but got struck by lightning just for bringing the idea up in #go-nuts.
My ideal scenario would be to check if copyChan is writeable, pop job from the
queue and shove it down handleFile. This way jobs would stay in the queue while
they cannot be handled, meaning that the `Bump` could bring your file up higher.
2014-12-01 19:23:06 +00:00
|
|
|
}
|
2015-01-05 15:42:27 +01:00
|
|
|
}).error($scope.emitHTTPError);
|
Add job queue (fixes #629)
Request to terminate currently ongoing downloads and jump to the bumped file
incoming in 3, 2, 1.
Also, has a slightly strange effect where we pop a job off the queue, but
the copyChannel is still busy and blocks, though it gets moved to the
progress slice in the jobqueue, and looks like it's in progress which it isn't
as it's waiting to be picked up from the copyChan.
As a result, the progress emitter doesn't register on the task, and hence the file
doesn't have a progress bar, but cannot be replaced by a bump.
I guess I can fix progress bar issue by moving the progressEmiter.Register just
before passing the file to the copyChan, but then we are back to the initial
problem of a file with a progress bar, but no progress happening as it's stuck
on write to copyChan
I checked if there is a way to check for channel writeability (before popping)
but got struck by lightning just for bringing the idea up in #go-nuts.
My ideal scenario would be to check if copyChan is writeable, pop job from the
queue and shove it down handleFile. This way jobs would stay in the queue while
they cannot be handled, meaning that the `Bump` could bring your file up higher.
2014-12-01 19:23:06 +00:00
|
|
|
};
|
|
|
|
|
|
2015-04-09 11:32:54 +02:00
|
|
|
$scope.versionString = function () {
|
|
|
|
|
if (!$scope.version.version) {
|
|
|
|
|
return '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var os = {
|
|
|
|
|
'darwin': 'Mac OS X',
|
|
|
|
|
'dragonfly': 'DragonFly BSD',
|
|
|
|
|
'freebsd': 'FreeBSD',
|
|
|
|
|
'openbsd': 'OpenBSD',
|
|
|
|
|
'netbsd': 'NetBSD',
|
|
|
|
|
'linux': 'Linux',
|
|
|
|
|
'windows': 'Windows',
|
2016-03-11 09:48:46 +00:00
|
|
|
'solaris': 'Solaris'
|
2015-04-10 12:45:47 +02:00
|
|
|
}[$scope.version.os] || $scope.version.os;
|
2015-04-09 11:32:54 +02:00
|
|
|
|
|
|
|
|
var arch ={
|
|
|
|
|
'386': '32 bit',
|
|
|
|
|
'amd64': '64 bit',
|
2015-04-10 12:45:47 +02:00
|
|
|
'arm': 'ARM',
|
2016-01-01 21:19:15 +01:00
|
|
|
'arm64': 'AArch64',
|
|
|
|
|
'ppc64': 'PowerPC',
|
2016-03-11 09:48:46 +00:00
|
|
|
'ppc64le': 'PowerPC (LE)'
|
2015-04-10 12:45:47 +02:00
|
|
|
}[$scope.version.arch] || $scope.version.arch;
|
2015-04-09 11:32:54 +02:00
|
|
|
|
|
|
|
|
return $scope.version.version + ', ' + os + ' (' + arch + ')';
|
|
|
|
|
};
|
|
|
|
|
|
2015-07-03 14:07:38 +02:00
|
|
|
$scope.inputTypeFor = function (key, value) {
|
|
|
|
|
if (key.substr(0, 1) === '_') {
|
|
|
|
|
return 'skip';
|
|
|
|
|
}
|
2016-01-27 22:41:39 +08:00
|
|
|
if (value === null) {
|
|
|
|
|
return 'null';
|
|
|
|
|
}
|
2015-07-03 14:07:38 +02:00
|
|
|
if (typeof value === 'number') {
|
|
|
|
|
return 'number';
|
|
|
|
|
}
|
|
|
|
|
if (typeof value === 'boolean') {
|
|
|
|
|
return 'checkbox';
|
|
|
|
|
}
|
2017-03-30 14:32:58 +00:00
|
|
|
if (value instanceof Array) {
|
|
|
|
|
return 'list';
|
|
|
|
|
}
|
2015-07-03 14:07:38 +02:00
|
|
|
if (typeof value === 'object') {
|
|
|
|
|
return 'skip';
|
|
|
|
|
}
|
|
|
|
|
return 'text';
|
|
|
|
|
};
|
|
|
|
|
|
2016-03-27 06:40:50 +00:00
|
|
|
$scope.themeName = function (theme) {
|
|
|
|
|
return theme.replace('-', ' ').replace(/(?:^|\s)\S/g, function (a) {
|
|
|
|
|
return a.toUpperCase();
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2016-06-17 06:44:55 +00:00
|
|
|
$scope.modalLoaded = function () {
|
|
|
|
|
// once all modal elements have been processed
|
|
|
|
|
if ($('modal').length === 0) {
|
|
|
|
|
|
|
|
|
|
// pseudo main. called on all definitions assigned
|
|
|
|
|
initController();
|
|
|
|
|
}
|
2017-12-15 20:01:56 +00:00
|
|
|
};
|
2016-06-17 06:44:55 +00:00
|
|
|
|
2017-03-31 06:32:54 +00:00
|
|
|
$scope.toggleUnits = function () {
|
|
|
|
|
$scope.metricRates = !$scope.metricRates;
|
|
|
|
|
try {
|
|
|
|
|
window.localStorage["metricRates"] = $scope.metricRates;
|
|
|
|
|
} catch (exception) { }
|
2017-12-15 20:01:56 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$scope.sizeOf = function (dict) {
|
|
|
|
|
return Object.keys(dict).length;
|
|
|
|
|
};
|
2014-11-26 13:39:59 +01:00
|
|
|
});
|