app.js 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  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.sort(function (a, b) {
  46. if (a.NodeID == $scope.myID)
  47. return -1;
  48. if (b.NodeID == $scope.myID)
  49. return 1;
  50. if (a.NodeID < b.NodeID)
  51. return -1;
  52. return a.NodeID > b.NodeID;
  53. })
  54. $scope.nodes = nodes;
  55. });
  56. });
  57. $scope.refresh = function () {
  58. $http.get("/rest/system").success(function (data) {
  59. $scope.system = data;
  60. });
  61. $http.get("/rest/model").success(function (data) {
  62. $scope.model = data;
  63. modelGetSucceeded();
  64. }).error(function () {
  65. modelGetFailed();
  66. });
  67. $http.get("/rest/connections").success(function (data) {
  68. var now = Date.now();
  69. var td = (now - prevDate) / 1000;
  70. prevDate = now;
  71. for (var id in data) {
  72. try {
  73. data[id].inbps = Math.max(0, 8 * (data[id].InBytesTotal - $scope.connections[id].InBytesTotal) / td);
  74. data[id].outbps = Math.max(0, 8 * (data[id].OutBytesTotal - $scope.connections[id].OutBytesTotal) / td);
  75. } catch (e) {
  76. data[id].inbps = 0;
  77. data[id].outbps = 0;
  78. }
  79. }
  80. $scope.connections = data;
  81. });
  82. $http.get("/rest/need").success(function (data) {
  83. var i, name;
  84. for (i = 0; i < data.length; i++) {
  85. name = data[i].Name.split("/");
  86. data[i].ShortName = name[name.length-1];
  87. }
  88. data.sort(function (a, b) {
  89. if (a.ShortName < b.ShortName) {
  90. return -1;
  91. }
  92. if (a.ShortName > b.ShortName) {
  93. return 1;
  94. }
  95. return 0;
  96. });
  97. $scope.need = data;
  98. });
  99. };
  100. $scope.nodeIcon = function (nodeCfg) {
  101. if (nodeCfg.NodeID === $scope.myID) {
  102. return "ok";
  103. }
  104. if ($scope.connections[nodeCfg.NodeID]) {
  105. return "ok";
  106. }
  107. return "minus";
  108. };
  109. $scope.nodeClass = function (nodeCfg) {
  110. if (nodeCfg.NodeID === $scope.myID) {
  111. return "default";
  112. }
  113. var conn = $scope.connections[nodeCfg.NodeID];
  114. if (conn) {
  115. return "success";
  116. }
  117. return "info";
  118. };
  119. $scope.nodeAddr = function (nodeCfg) {
  120. if (nodeCfg.NodeID === $scope.myID) {
  121. return "this node";
  122. }
  123. var conn = $scope.connections[nodeCfg.NodeID];
  124. if (conn) {
  125. return conn.Address;
  126. }
  127. return nodeCfg.Addresses.join(", ");
  128. };
  129. $scope.nodeVer = function (nodeCfg) {
  130. if (nodeCfg.NodeID === $scope.myID) {
  131. return $scope.version;
  132. }
  133. var conn = $scope.connections[nodeCfg.NodeID];
  134. if (conn) {
  135. return conn.ClientVersion;
  136. }
  137. return "";
  138. };
  139. $scope.saveSettings = function () {
  140. $http.post('/rest/config', JSON.stringify($scope.config), {headers: {'Content-Type': 'application/json'}});
  141. $('#settingsTable').collapse('hide');
  142. };
  143. $scope.editNode = function (nodeCfg) {
  144. $scope.currentNode = nodeCfg;
  145. $scope.editingExisting = true;
  146. $scope.currentNode.AddressesStr = nodeCfg.Addresses.join(", ")
  147. $('#editNode').modal({backdrop: 'static', keyboard: false});
  148. };
  149. $scope.addNode = function () {
  150. $scope.currentNode = {NodeID: "", AddressesStr: "dynamic"};
  151. $scope.editingExisting = false;
  152. $('#editNode').modal({backdrop: 'static', keyboard: false});
  153. };
  154. $scope.deleteNode = function () {
  155. $('#editNode').modal('hide');
  156. if (!$scope.editingExisting)
  157. return;
  158. var newNodes = [];
  159. for (var i = 0; i < $scope.nodes.length; i++) {
  160. if ($scope.nodes[i].NodeID !== $scope.currentNode.NodeID) {
  161. newNodes.push($scope.nodes[i]);
  162. }
  163. }
  164. $scope.nodes = newNodes;
  165. $scope.config.Repositories[0].Nodes = newNodes;
  166. $http.post('/rest/config', JSON.stringify($scope.config), {headers: {'Content-Type': 'application/json'}})
  167. }
  168. $scope.saveNode = function () {
  169. $('#editNode').modal('hide');
  170. nodeCfg = $scope.currentNode;
  171. nodeCfg.Addresses = nodeCfg.AddressesStr.split(',').map(function (x) { return x.trim(); });
  172. var done = false;
  173. for (var i = 0; i < $scope.nodes.length; i++) {
  174. if ($scope.nodes[i].NodeID === nodeCfg.NodeID) {
  175. $scope.nodes[i] = nodeCfg;
  176. done = true;
  177. break;
  178. }
  179. }
  180. if (!done) {
  181. $scope.nodes.push(nodeCfg);
  182. }
  183. $scope.nodes.sort(function (a, b) {
  184. if (a.NodeID == $scope.myID)
  185. return -1;
  186. if (b.NodeID == $scope.myID)
  187. return 1;
  188. if (a.NodeID < b.NodeID)
  189. return -1;
  190. return a.NodeID > b.NodeID;
  191. })
  192. $scope.config.Repositories[0].Nodes = $scope.nodes;
  193. $http.post('/rest/config', JSON.stringify($scope.config), {headers: {'Content-Type': 'application/json'}})
  194. };
  195. $scope.refresh();
  196. setInterval($scope.refresh, 10000);
  197. });
  198. function decimals(val, num) {
  199. if (val === 0) { return 0; }
  200. var digits = Math.floor(Math.log(Math.abs(val))/Math.log(10));
  201. var decimals = Math.max(0, num - digits);
  202. return decimals;
  203. }
  204. syncthing.filter('natural', function() {
  205. return function(input, valid) {
  206. return input.toFixed(decimals(input, valid));
  207. }
  208. });
  209. syncthing.filter('binary', function() {
  210. return function(input) {
  211. if (input === undefined) {
  212. return '0 '
  213. }
  214. if (input > 1024 * 1024 * 1024) {
  215. input /= 1024 * 1024 * 1024;
  216. return input.toFixed(decimals(input, 2)) + ' Gi';
  217. }
  218. if (input > 1024 * 1024) {
  219. input /= 1024 * 1024;
  220. return input.toFixed(decimals(input, 2)) + ' Mi';
  221. }
  222. if (input > 1024) {
  223. input /= 1024;
  224. return input.toFixed(decimals(input, 2)) + ' Ki';
  225. }
  226. return Math.round(input) + ' ';
  227. }
  228. });
  229. syncthing.filter('metric', function() {
  230. return function(input) {
  231. if (input === undefined) {
  232. return '0 '
  233. }
  234. if (input > 1000 * 1000 * 1000) {
  235. input /= 1000 * 1000 * 1000;
  236. return input.toFixed(decimals(input, 2)) + ' G';
  237. }
  238. if (input > 1000 * 1000) {
  239. input /= 1000 * 1000;
  240. return input.toFixed(decimals(input, 2)) + ' M';
  241. }
  242. if (input > 1000) {
  243. input /= 1000;
  244. return input.toFixed(decimals(input, 2)) + ' k';
  245. }
  246. return Math.round(input) + ' ';
  247. }
  248. });
  249. syncthing.filter('short', function() {
  250. return function(input) {
  251. return input.substr(0, 6);
  252. }
  253. });
  254. syncthing.filter('alwaysNumber', function() {
  255. return function(input) {
  256. if (input === undefined) {
  257. return 0;
  258. }
  259. return input;
  260. }
  261. });
  262. syncthing.directive('optionEditor', function() {
  263. return {
  264. restrict: 'C',
  265. replace: true,
  266. transclude: true,
  267. scope: {
  268. setting: '=setting',
  269. },
  270. template: '<input type="text" ng-model="config.Options[setting.id]"></input>',
  271. };
  272. })