app.js 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. var syncthing = angular.module('syncthing', []);
  2. syncthing.controller('SyncthingCtrl', function ($scope, $http) {
  3. var prevDate = 0;
  4. var modelGetOK = true;
  5. $scope.connections = {};
  6. $scope.config = {};
  7. $scope.myID = "";
  8. $scope.nodes = [];
  9. // Strings before bools look better
  10. $scope.settings = [
  11. {id: 'ListenAddress', descr:"Sync Protocol Listen Address", type: 'string', restart: true},
  12. {id: 'GUIAddress', descr: "GUI Listen Address", type: 'string', restart: true},
  13. {id: 'MaxSendKbps', descr: "Outgoing Rate Limit (KBps)", type: 'string', restart: true},
  14. {id: 'RescanIntervalS', descr: "Rescan Interval (s)", type: 'string', restart: true},
  15. {id: 'ReconnectIntervalS', descr: "Reconnect Interval (s)", type: 'string', restart: true},
  16. {id: 'ParallelRequests', descr: "Max Outstanding Requests", type: 'string', restart: true},
  17. {id: 'MaxChangeKbps', descr: "Max File Change Rate (KBps)", type: 'string', restart: true},
  18. {id: 'ReadOnly', descr: "Read Only", type: 'bool', restart: true},
  19. {id: 'AllowDelete', descr: "Allow Delete", type: 'bool', restart: true},
  20. {id: 'FollowSymlinks', descr: "Follow Symlinks", type: 'bool', restart: true},
  21. {id: 'GlobalAnnEnabled', descr: "Global Announce", type: 'bool', restart: true},
  22. {id: 'LocalAnnEnabled', descr: "Local Announce", type: 'bool', restart: true},
  23. ];
  24. function modelGetSucceeded() {
  25. if (!modelGetOK) {
  26. $('#networkError').modal('hide');
  27. modelGetOK = true;
  28. }
  29. }
  30. function modelGetFailed() {
  31. if (modelGetOK) {
  32. $('#networkError').modal({backdrop: 'static', keyboard: false});
  33. modelGetOK = false;
  34. }
  35. }
  36. $http.get("/rest/version").success(function (data) {
  37. $scope.version = data;
  38. });
  39. $http.get("/rest/system").success(function (data) {
  40. $scope.system = data;
  41. $scope.myID = data.myID;
  42. $http.get("/rest/config").success(function (data) {
  43. $scope.config = data;
  44. var nodes = $scope.config.Repositories[0].Nodes;
  45. nodes = nodes.filter(function (x) { return x.NodeID != $scope.myID; });
  46. nodes.sort(function (a, b) {
  47. if (a.NodeID < b.NodeID)
  48. return -1;
  49. return a.NodeID > b.NodeID;
  50. })
  51. $scope.nodes = nodes;
  52. });
  53. });
  54. $scope.refresh = function () {
  55. $http.get("/rest/system").success(function (data) {
  56. $scope.system = data;
  57. });
  58. $http.get("/rest/model").success(function (data) {
  59. $scope.model = data;
  60. modelGetSucceeded();
  61. }).error(function () {
  62. modelGetFailed();
  63. });
  64. $http.get("/rest/connections").success(function (data) {
  65. var now = Date.now();
  66. var td = (now - prevDate) / 1000;
  67. prevDate = now;
  68. $scope.inbps = 0
  69. $scope.outbps = 0
  70. for (var id in data) {
  71. try {
  72. data[id].inbps = Math.max(0, 8 * (data[id].InBytesTotal - $scope.connections[id].InBytesTotal) / td);
  73. data[id].outbps = Math.max(0, 8 * (data[id].OutBytesTotal - $scope.connections[id].OutBytesTotal) / td);
  74. } catch (e) {
  75. data[id].inbps = 0;
  76. data[id].outbps = 0;
  77. }
  78. $scope.inbps += data[id].outbps;
  79. $scope.outbps += data[id].inbps;
  80. }
  81. $scope.connections = data;
  82. });
  83. $http.get("/rest/need").success(function (data) {
  84. var i, name;
  85. for (i = 0; i < data.length; i++) {
  86. name = data[i].Name.split("/");
  87. data[i].ShortName = name[name.length-1];
  88. }
  89. data.sort(function (a, b) {
  90. if (a.ShortName < b.ShortName) {
  91. return -1;
  92. }
  93. if (a.ShortName > b.ShortName) {
  94. return 1;
  95. }
  96. return 0;
  97. });
  98. $scope.need = data;
  99. });
  100. };
  101. $scope.nodeIcon = function (nodeCfg) {
  102. if ($scope.connections[nodeCfg.NodeID]) {
  103. return "ok";
  104. }
  105. return "minus";
  106. };
  107. $scope.nodeClass = function (nodeCfg) {
  108. var conn = $scope.connections[nodeCfg.NodeID];
  109. if (conn) {
  110. return "success";
  111. }
  112. return "info";
  113. };
  114. $scope.nodeAddr = function (nodeCfg) {
  115. var conn = $scope.connections[nodeCfg.NodeID];
  116. if (conn) {
  117. return conn.Address;
  118. }
  119. return nodeCfg.Addresses.join(", ");
  120. };
  121. $scope.nodeVer = function (nodeCfg) {
  122. if (nodeCfg.NodeID === $scope.myID) {
  123. return $scope.version;
  124. }
  125. var conn = $scope.connections[nodeCfg.NodeID];
  126. if (conn) {
  127. return conn.ClientVersion;
  128. }
  129. return "";
  130. };
  131. $scope.saveSettings = function () {
  132. $http.post('/rest/config', JSON.stringify($scope.config), {headers: {'Content-Type': 'application/json'}});
  133. $('#settingsTable').collapse('hide');
  134. };
  135. $scope.editNode = function (nodeCfg) {
  136. $scope.currentNode = nodeCfg;
  137. $scope.editingExisting = true;
  138. $scope.currentNode.AddressesStr = nodeCfg.Addresses.join(", ")
  139. $('#editNode').modal({backdrop: 'static', keyboard: false});
  140. };
  141. $scope.addNode = function () {
  142. $scope.currentNode = {NodeID: "", AddressesStr: "dynamic"};
  143. $scope.editingExisting = false;
  144. $('#editNode').modal({backdrop: 'static', keyboard: false});
  145. };
  146. $scope.deleteNode = function () {
  147. $('#editNode').modal('hide');
  148. if (!$scope.editingExisting)
  149. return;
  150. var newNodes = [];
  151. for (var i = 0; i < $scope.nodes.length; i++) {
  152. if ($scope.nodes[i].NodeID !== $scope.currentNode.NodeID) {
  153. newNodes.push($scope.nodes[i]);
  154. }
  155. }
  156. $scope.nodes = newNodes;
  157. $scope.config.Repositories[0].Nodes = newNodes;
  158. $http.post('/rest/config', JSON.stringify($scope.config), {headers: {'Content-Type': 'application/json'}})
  159. }
  160. $scope.saveNode = function () {
  161. $('#editNode').modal('hide');
  162. nodeCfg = $scope.currentNode;
  163. nodeCfg.Addresses = nodeCfg.AddressesStr.split(',').map(function (x) { return x.trim(); });
  164. var done = false;
  165. for (var i = 0; i < $scope.nodes.length; i++) {
  166. if ($scope.nodes[i].NodeID === nodeCfg.NodeID) {
  167. $scope.nodes[i] = nodeCfg;
  168. done = true;
  169. break;
  170. }
  171. }
  172. if (!done) {
  173. $scope.nodes.push(nodeCfg);
  174. }
  175. $scope.nodes.sort(function (a, b) {
  176. if (a.NodeID < b.NodeID)
  177. return -1;
  178. return a.NodeID > b.NodeID;
  179. })
  180. $scope.config.Repositories[0].Nodes = $scope.nodes;
  181. $http.post('/rest/config', JSON.stringify($scope.config), {headers: {'Content-Type': 'application/json'}})
  182. };
  183. $scope.refresh();
  184. setInterval($scope.refresh, 10000);
  185. });
  186. function decimals(val, num) {
  187. if (val === 0) { return 0; }
  188. var digits = Math.floor(Math.log(Math.abs(val))/Math.log(10));
  189. var decimals = Math.max(0, num - digits);
  190. return decimals;
  191. }
  192. syncthing.filter('natural', function() {
  193. return function(input, valid) {
  194. return input.toFixed(decimals(input, valid));
  195. }
  196. });
  197. syncthing.filter('binary', function() {
  198. return function(input) {
  199. if (input === undefined) {
  200. return '0 '
  201. }
  202. if (input > 1024 * 1024 * 1024) {
  203. input /= 1024 * 1024 * 1024;
  204. return input.toFixed(decimals(input, 2)) + ' Gi';
  205. }
  206. if (input > 1024 * 1024) {
  207. input /= 1024 * 1024;
  208. return input.toFixed(decimals(input, 2)) + ' Mi';
  209. }
  210. if (input > 1024) {
  211. input /= 1024;
  212. return input.toFixed(decimals(input, 2)) + ' Ki';
  213. }
  214. return Math.round(input) + ' ';
  215. }
  216. });
  217. syncthing.filter('metric', function() {
  218. return function(input) {
  219. if (input === undefined) {
  220. return '0 '
  221. }
  222. if (input > 1000 * 1000 * 1000) {
  223. input /= 1000 * 1000 * 1000;
  224. return input.toFixed(decimals(input, 2)) + ' G';
  225. }
  226. if (input > 1000 * 1000) {
  227. input /= 1000 * 1000;
  228. return input.toFixed(decimals(input, 2)) + ' M';
  229. }
  230. if (input > 1000) {
  231. input /= 1000;
  232. return input.toFixed(decimals(input, 2)) + ' k';
  233. }
  234. return Math.round(input) + ' ';
  235. }
  236. });
  237. syncthing.filter('short', function() {
  238. return function(input) {
  239. return input.substr(0, 6);
  240. }
  241. });
  242. syncthing.filter('alwaysNumber', function() {
  243. return function(input) {
  244. if (input === undefined) {
  245. return 0;
  246. }
  247. return input;
  248. }
  249. });
  250. syncthing.directive('optionEditor', function() {
  251. return {
  252. restrict: 'C',
  253. replace: true,
  254. transclude: true,
  255. scope: {
  256. setting: '=setting',
  257. },
  258. template: '<input type="text" ng-model="config.Options[setting.id]"></input>',
  259. };
  260. })