diff --git a/gui/default/assets/lang/lang-en.json b/gui/default/assets/lang/lang-en.json index 4d58f890b..0b03c2d55 100644 --- a/gui/default/assets/lang/lang-en.json +++ b/gui/default/assets/lang/lang-en.json @@ -234,6 +234,7 @@ "Release Notes": "Release Notes", "Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.", "Remote Devices": "Remote Devices", + "Remote GUI": "Remote GUI", "Remove": "Remove", "Remove Device": "Remove Device", "Remove Folder": "Remove Folder", diff --git a/gui/default/index.html b/gui/default/index.html index c965153cf..b980ccf5c 100644 --- a/gui/default/index.html +++ b/gui/default/index.html @@ -806,8 +806,8 @@  Remote GUI - {{idToRemoteGUI[deviceCfg.deviceID]}} - Unreachable + {{remoteGUIAddress(deviceCfg)}} + Unknown diff --git a/gui/default/syncthing/core/syncthingController.js b/gui/default/syncthing/core/syncthingController.js index 7dd60ef5f..7462315e4 100755 --- a/gui/default/syncthing/core/syncthingController.js +++ b/gui/default/syncthing/core/syncthingController.js @@ -24,9 +24,6 @@ angular.module('syncthing.core') $scope.config = {}; $scope.configInSync = true; $scope.connections = {}; - $scope.idToRemoteGUI = {}; - $scope.remoteGUICache = {}; - $scope.showRemoteGUI = true; $scope.errors = []; $scope.model = {}; $scope.myID = ''; @@ -64,10 +61,6 @@ angular.module('syncthing.core') $scope.metricRates = (window.localStorage["metricRates"] == "true"); } catch (exception) { } - if ("showRemoteGUI" in window.localStorage) { - $scope.showRemoteGUI = (window.localStorage["showRemoteGUI"] == "true"); - } - $scope.folderDefaults = { devices: [], type: "sendreceive", @@ -382,7 +375,6 @@ angular.module('syncthing.core') $scope.config.options._globalAnnounceServersStr = $scope.config.options.globalAnnounceServers.join(', '); $scope.config.options._urAcceptedStr = "" + $scope.config.options.urAccepted; - $scope.config.gui["showRemoteGUI"] = $scope.showRemoteGUI; $scope.devices = deviceMap($scope.config.devices); for (var id in $scope.devices) { $scope.completion[id] = { @@ -525,16 +517,6 @@ angular.module('syncthing.core') console.log("recalcCompletion", device, $scope.completion[device]); } - function replaceAddressPort(address, newPort) { - var lastColonIndex = address.length; - for (var index = 0; index < address.length; index++) { - if (address[index] === ":") { - lastColonIndex = index; - } - } - return address.substr(0, lastColonIndex) + ":" + newPort.toString(); - } - function refreshCompletion(device, folder) { if (device === $scope.myID) { return; @@ -581,50 +563,9 @@ angular.module('syncthing.core') } $scope.connections = data; console.log("refreshConnections", data); - - refreshRemoteGUI(data); }).error($scope.emitHTTPError); } - function refreshRemoteGUI(connections) { - if (!$scope.showRemoteGUI) { - $scope.idToRemoteGUI = {} - return - } - var newCache = {}; - for (var id in connections) { - if (!(id in $scope.devices)) { - // Avoid errors when called before first updateLocalConfig() - continue; - } - var port = $scope.devices[id].remoteGUIPort; - if (port <= 0 - || !connections[id].address - || connections[id].type.includes("relay")) { - // Relay connections never work as desired here, nor incomplete addresses - $scope.idToRemoteGUI[id] = ""; - continue; - } - var newAddress = "http://" + replaceAddressPort(connections[id].address, port); - if (!(newAddress in $scope.remoteGUICache)) { - // No cached result, trigger a new port probing asynchronously - $scope.probeRemoteGUIAddress(id, newAddress); - } else { - newCache[newAddress] = $scope.remoteGUICache[newAddress]; - // Copy cached probing result in the corner case of duplicate GUI - // addresses for different devices. Which is useless, but - // possible when behind the same NAT router. - if (newCache[newAddress]) { - $scope.idToRemoteGUI[id] = newAddress; - } else { - $scope.idToRemoteGUI[id] = ""; - } - } - } - // Replace the cache to discard stale addresses - $scope.remoteGUICache = newCache; - } - function refreshErrors() { $http.get(urlbase + '/system/error').success(function (data) { $scope.errors = data.errors; @@ -643,22 +584,6 @@ angular.module('syncthing.core') }).error($scope.emitHTTPError); } - $scope.probeRemoteGUIAddress = function (deviceId, address) { - // Strip off possible IPv6 link-local zone identifier, as Angular chokes on it - // with an (ugly, unjustified) console error message. - var urlAddress = address.replace(/%[a-zA-Z0-9_\.\-]*\]/, ']'); - $http({ - method: "OPTIONS", - url: urlAddress, - }).success(function (data) { - $scope.remoteGUICache[address] = true; - $scope.idToRemoteGUI[deviceId] = address; - }).error(function (err) { - $scope.remoteGUICache[address] = false; - $scope.idToRemoteGUI[deviceId] = ""; - }); - } - $scope.refreshNeed = function (page, perpage) { if (!$scope.neededFolder) { return; @@ -1140,6 +1065,28 @@ angular.module('syncthing.core') return '?'; }; + $scope.hasRemoteGUIAddress = function (deviceCfg) { + if (!deviceCfg.remoteGUIPort) + return false; + var conn = $scope.connections[deviceCfg.deviceID]; + return conn && conn.connected && conn.address && conn.type.indexOf('Relay') == -1; + }; + + $scope.remoteGUIAddress = function (deviceCfg) { + // Assume hasRemoteGUIAddress is true or we would not be here + var conn = $scope.connections[deviceCfg.deviceID]; + return 'http://' + replaceAddressPort(conn.address, deviceCfg.remoteGUIPort); + }; + + function replaceAddressPort(address, newPort) { + for (var index = address.length - 1; index >= 0; index--) { + if (address[index] === ":") { + return address.substr(0, index) + ":" + newPort.toString(); + } + } + return address; + } + $scope.friendlyNameFromShort = function (shortID) { var matches = Object.keys($scope.devices).filter(function (id) { return id.substr(0, 7) === shortID; @@ -1317,11 +1264,6 @@ angular.module('syncthing.core') }; $scope.saveConfig = function (callback) { - // set local storage feature and delete from post request - window.localStorage.setItem("showRemoteGUI", $scope.config.gui.showRemoteGUI ? "true" : "false"); - $scope.showRemoteGUI = $scope.config.gui.showRemoteGUI; - delete $scope.config.gui.showRemoteGUI; - var cfg = JSON.stringify($scope.config); var opts = { headers: { diff --git a/gui/default/untrusted/index.html b/gui/default/untrusted/index.html index 83b65286f..b2a95c9f9 100644 --- a/gui/default/untrusted/index.html +++ b/gui/default/untrusted/index.html @@ -815,12 +815,12 @@ {{deviceFolders(deviceCfg).map(folderLabel).join(", ")}} -  Remote GUI - +  Remote GUI + - {{idToRemoteGUI[deviceCfg.deviceID]}} - Unreachable - + {{remoteGUIAddress(deviceCfg)}} + Unknown + diff --git a/gui/default/untrusted/syncthing/core/syncthingController.js b/gui/default/untrusted/syncthing/core/syncthingController.js index 74d8dc8c7..4f2a20e74 100755 --- a/gui/default/untrusted/syncthing/core/syncthingController.js +++ b/gui/default/untrusted/syncthing/core/syncthingController.js @@ -24,9 +24,6 @@ angular.module('syncthing.core') $scope.config = {}; $scope.configInSync = true; $scope.connections = {}; - $scope.idToRemoteGUI = {}; - $scope.remoteGUICache = {}; - $scope.showRemoteGUI = true; $scope.errors = []; $scope.model = {}; $scope.myID = ''; @@ -64,10 +61,6 @@ angular.module('syncthing.core') $scope.metricRates = (window.localStorage["metricRates"] == "true"); } catch (exception) { } - if ("showRemoteGUI" in window.localStorage) { - $scope.showRemoteGUI = (window.localStorage["showRemoteGUI"] == "true"); - } - $scope.folderDefaults = { devices: [], type: "sendreceive", @@ -382,7 +375,6 @@ angular.module('syncthing.core') $scope.config.options._globalAnnounceServersStr = $scope.config.options.globalAnnounceServers.join(', '); $scope.config.options._urAcceptedStr = "" + $scope.config.options.urAccepted; - $scope.config.gui["showRemoteGUI"] = $scope.showRemoteGUI; $scope.devices = deviceMap($scope.config.devices); for (var id in $scope.devices) { $scope.completion[id] = { @@ -525,16 +517,6 @@ angular.module('syncthing.core') console.log("recalcCompletion", device, $scope.completion[device]); } - function replaceAddressPort(address, newPort) { - var lastColonIndex = address.length; - for (var index = 0; index < address.length; index++) { - if (address[index] === ":") { - lastColonIndex = index; - } - } - return address.substr(0, lastColonIndex) + ":" + newPort.toString(); - } - function refreshCompletion(device, folder) { if (device === $scope.myID) { return; @@ -581,50 +563,9 @@ angular.module('syncthing.core') } $scope.connections = data; console.log("refreshConnections", data); - - refreshRemoteGUI(data); }).error($scope.emitHTTPError); } - function refreshRemoteGUI(connections) { - if (!$scope.showRemoteGUI) { - $scope.idToRemoteGUI = {} - return - } - var newCache = {}; - for (var id in connections) { - if (!(id in $scope.devices)) { - // Avoid errors when called before first updateLocalConfig() - continue; - } - var port = $scope.devices[id].remoteGUIPort; - if (port <= 0 - || !connections[id].address - || connections[id].type.includes("relay")) { - // Relay connections never work as desired here, nor incomplete addresses - $scope.idToRemoteGUI[id] = ""; - continue; - } - var newAddress = "http://" + replaceAddressPort(connections[id].address, port); - if (!(newAddress in $scope.remoteGUICache)) { - // No cached result, trigger a new port probing asynchronously - $scope.probeRemoteGUIAddress(id, newAddress); - } else { - newCache[newAddress] = $scope.remoteGUICache[newAddress]; - // Copy cached probing result in the corner case of duplicate GUI - // addresses for different devices. Which is useless, but - // possible when behind the same NAT router. - if (newCache[newAddress]) { - $scope.idToRemoteGUI[id] = newAddress; - } else { - $scope.idToRemoteGUI[id] = ""; - } - } - } - // Replace the cache to discard stale addresses - $scope.remoteGUICache = newCache; - } - function refreshErrors() { $http.get(urlbase + '/system/error').success(function (data) { $scope.errors = data.errors; @@ -643,23 +584,6 @@ angular.module('syncthing.core') }).error($scope.emitHTTPError); } - $scope.probeRemoteGUIAddress = function (deviceId, address) { - // Strip off possible IPv6 link-local zone identifier, as Angular chokes on it - // with an (ugly, unjustified) console error message. - var urlAddress = address.replace(/%[a-zA-Z0-9_\.\-]*\]/, ']'); - console.log(urlAddress); - $http({ - method: "OPTIONS", - url: urlAddress, - }).success(function (data) { - $scope.remoteGUICache[address] = true; - $scope.idToRemoteGUI[deviceId] = address; - }).error(function (err) { - $scope.remoteGUICache[address] = false; - $scope.idToRemoteGUI[deviceId] = ""; - }); - } - $scope.refreshNeed = function (page, perpage) { if (!$scope.neededFolder) { return; @@ -1145,6 +1069,28 @@ angular.module('syncthing.core') return '?'; }; + $scope.hasRemoteGUIAddress = function (deviceCfg) { + if (!deviceCfg.remoteGUIPort) + return false; + var conn = $scope.connections[deviceCfg.deviceID]; + return conn && conn.connected && conn.address && conn.type.indexOf('Relay') == -1; + }; + + $scope.remoteGUIAddress = function (deviceCfg) { + // Assume hasRemoteGUIAddress is true or we would not be here + var conn = $scope.connections[deviceCfg.deviceID]; + return 'http://' + replaceAddressPort(conn.address, deviceCfg.remoteGUIPort); + }; + + function replaceAddressPort(address, newPort) { + for (var index = address.length - 1; index >= 0; index--) { + if (address[index] === ":") { + return address.substr(0, index) + ":" + newPort.toString(); + } + } + return address; + } + $scope.friendlyNameFromShort = function (shortID) { var matches = Object.keys($scope.devices).filter(function (id) { return id.substr(0, 7) === shortID; @@ -1322,11 +1268,6 @@ angular.module('syncthing.core') }; $scope.saveConfig = function (callback) { - // set local storage feature and delete from post request - window.localStorage.setItem("showRemoteGUI", $scope.config.gui.showRemoteGUI ? "true" : "false"); - $scope.showRemoteGUI = $scope.config.gui.showRemoteGUI; - delete $scope.config.gui.showRemoteGUI; - var cfg = JSON.stringify($scope.config); var opts = { headers: {