syncthingController.js 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441
  1. angular.module('syncthing.core')
  2. .config(function($locationProvider) {
  3. $locationProvider.html5Mode(true).hashPrefix('!');
  4. })
  5. .controller('SyncthingController', function ($scope, $http, $location, LocaleService, Events) {
  6. 'use strict';
  7. // private/helper definitions
  8. var prevDate = 0;
  9. var navigatingAway = false;
  10. var online = false;
  11. var restarting = false;
  12. function initController() {
  13. LocaleService.autoConfigLocale();
  14. setInterval($scope.refresh, 10000);
  15. Events.start();
  16. }
  17. // public/scope definitions
  18. $scope.completion = {};
  19. $scope.config = {};
  20. $scope.configInSync = true;
  21. $scope.connections = {};
  22. $scope.errors = [];
  23. $scope.model = {};
  24. $scope.myID = '';
  25. $scope.devices = [];
  26. $scope.deviceRejections = {};
  27. $scope.folderRejections = {};
  28. $scope.protocolChanged = false;
  29. $scope.reportData = {};
  30. $scope.reportPreview = false;
  31. $scope.folders = {};
  32. $scope.seenError = '';
  33. $scope.upgradeInfo = null;
  34. $scope.deviceStats = {};
  35. $scope.folderStats = {};
  36. $scope.progress = {};
  37. $scope.version = {};
  38. $scope.needed = [];
  39. $scope.neededTotal = 0;
  40. $scope.neededCurrentPage = 1;
  41. $scope.neededPageSize = 10;
  42. $scope.failed = {};
  43. $scope.failedCurrentPage = 1;
  44. $scope.failedCurrentFolder = undefined;
  45. $scope.failedPageSize = 10;
  46. $scope.localStateTotal = {
  47. bytes: 0,
  48. files: 0
  49. };
  50. $(window).bind('beforeunload', function () {
  51. navigatingAway = true;
  52. });
  53. $scope.$on("$locationChangeSuccess", function () {
  54. LocaleService.useLocale($location.search().lang);
  55. });
  56. $scope.needActions = {
  57. 'rm': 'Del',
  58. 'rmdir': 'Del (dir)',
  59. 'sync': 'Sync',
  60. 'touch': 'Update'
  61. };
  62. $scope.needIcons = {
  63. 'rm': 'trash-o',
  64. 'rmdir': 'trash-o',
  65. 'sync': 'arrow-circle-o-down',
  66. 'touch': 'asterisk'
  67. };
  68. $scope.$on(Events.ONLINE, function () {
  69. if (online && !restarting) {
  70. return;
  71. }
  72. console.log('UIOnline');
  73. refreshSystem();
  74. refreshConfig();
  75. refreshConnectionStats();
  76. refreshDeviceStats();
  77. refreshFolderStats();
  78. $http.get(urlbase + '/system/version').success(function (data) {
  79. if ($scope.version.version && $scope.version.version != data.version) {
  80. // We already have a version response, but it differs from
  81. // the new one. Reload the full GUI in case it's changed.
  82. document.location.reload(true);
  83. }
  84. $scope.version = data;
  85. }).error($scope.emitHTTPError);
  86. $http.get(urlbase + '/svc/report').success(function (data) {
  87. $scope.reportData = data;
  88. }).error($scope.emitHTTPError);
  89. $http.get(urlbase + '/system/upgrade').success(function (data) {
  90. $scope.upgradeInfo = data;
  91. }).error(function () {
  92. $scope.upgradeInfo = null;
  93. });
  94. online = true;
  95. restarting = false;
  96. $('#networkError').modal('hide');
  97. $('#restarting').modal('hide');
  98. $('#shutdown').modal('hide');
  99. });
  100. $scope.$on(Events.OFFLINE, function () {
  101. if (navigatingAway || !online) {
  102. return;
  103. }
  104. console.log('UIOffline');
  105. online = false;
  106. if (!restarting) {
  107. $('#networkError').modal();
  108. }
  109. });
  110. $scope.$on('HTTPError', function (event, arg) {
  111. // Emitted when a HTTP call fails. We use the status code to try
  112. // to figure out what's wrong.
  113. if (navigatingAway || !online) {
  114. return;
  115. }
  116. console.log('HTTPError', arg);
  117. online = false;
  118. if (!restarting) {
  119. if (arg.status === 0) {
  120. // A network error, not an HTTP error
  121. $scope.$emit(Events.OFFLINE);
  122. } else if (arg.status >= 400 && arg.status <= 599) {
  123. // A genuine HTTP error
  124. $('#networkError').modal('hide');
  125. $('#restarting').modal('hide');
  126. $('#shutdown').modal('hide');
  127. $('#httpError').modal();
  128. }
  129. }
  130. });
  131. $scope.$on(Events.STATE_CHANGED, function (event, arg) {
  132. var data = arg.data;
  133. if ($scope.model[data.folder]) {
  134. $scope.model[data.folder].state = data.to;
  135. $scope.model[data.folder].error = data.error;
  136. // If a folder has started syncing, then any old list of
  137. // errors is obsolete. We may get a new list of errors very
  138. // shortly though.
  139. if (data.to === 'syncing') {
  140. $scope.failed[data.folder] = [];
  141. }
  142. }
  143. });
  144. $scope.$on(Events.LOCAL_INDEX_UPDATED, function (event, arg) {
  145. refreshFolderStats();
  146. });
  147. $scope.$on(Events.DEVICE_DISCONNECTED, function (event, arg) {
  148. $scope.connections[arg.data.id].connected = false;
  149. refreshDeviceStats();
  150. });
  151. $scope.$on(Events.DEVICE_CONNECTED, function (event, arg) {
  152. if (!$scope.connections[arg.data.id]) {
  153. $scope.connections[arg.data.id] = {
  154. inbps: 0,
  155. outbps: 0,
  156. inBytesTotal: 0,
  157. outBytesTotal: 0,
  158. type: arg.data.type,
  159. address: arg.data.addr
  160. };
  161. $scope.completion[arg.data.id] = {
  162. _total: 100
  163. };
  164. }
  165. });
  166. $scope.$on('ConfigLoaded', function () {
  167. if ($scope.config.options.urAccepted === 0) {
  168. // If usage reporting has been neither accepted nor declined,
  169. // we want to ask the user to make a choice. But we don't want
  170. // to bug them during initial setup, so we set a cookie with
  171. // the time of the first visit. When that cookie is present
  172. // and the time is more than four hours ago, we ask the
  173. // question.
  174. var firstVisit = document.cookie.replace(/(?:(?:^|.*;\s*)firstVisit\s*\=\s*([^;]*).*$)|^.*$/, "$1");
  175. if (!firstVisit) {
  176. document.cookie = "firstVisit=" + Date.now() + ";max-age=" + 30 * 24 * 3600;
  177. } else {
  178. if (+firstVisit < Date.now() - 4 * 3600 * 1000) {
  179. $('#ur').modal();
  180. }
  181. }
  182. }
  183. });
  184. $scope.$on(Events.DEVICE_REJECTED, function (event, arg) {
  185. $scope.deviceRejections[arg.data.device] = arg;
  186. });
  187. $scope.$on(Events.DEVICE_PAUSED, function (event, arg) {
  188. $scope.connections[arg.data.device].paused = true;
  189. });
  190. $scope.$on(Events.DEVICE_RESUMED, function (event, arg) {
  191. $scope.connections[arg.data.device].paused = false;
  192. });
  193. $scope.$on(Events.FOLDER_REJECTED, function (event, arg) {
  194. $scope.folderRejections[arg.data.folder + "-" + arg.data.device] = arg;
  195. });
  196. $scope.$on(Events.CONFIG_SAVED, function (event, arg) {
  197. updateLocalConfig(arg.data);
  198. $http.get(urlbase + '/system/config/insync').success(function (data) {
  199. $scope.configInSync = data.configInSync;
  200. }).error($scope.emitHTTPError);
  201. });
  202. $scope.$on(Events.DOWNLOAD_PROGRESS, function (event, arg) {
  203. var stats = arg.data;
  204. var progress = {};
  205. for (var folder in stats) {
  206. progress[folder] = {};
  207. for (var file in stats[folder]) {
  208. var s = stats[folder][file];
  209. var reused = 100 * s.reused / s.total;
  210. var copiedFromOrigin = 100 * s.copiedFromOrigin / s.total;
  211. var copiedFromElsewhere = 100 * s.copiedFromElsewhere / s.total;
  212. var pulled = 100 * s.pulled / s.total;
  213. var pulling = 100 * s.pulling / s.total;
  214. // We try to round up pulling to at least a percent so that it would be at least a bit visible.
  215. if (pulling < 1 && pulled + copiedFromElsewhere + copiedFromOrigin + reused <= 99) {
  216. pulling = 1;
  217. }
  218. progress[folder][file] = {
  219. reused: reused,
  220. copiedFromOrigin: copiedFromOrigin,
  221. copiedFromElsewhere: copiedFromElsewhere,
  222. pulled: pulled,
  223. pulling: pulling,
  224. bytesTotal: s.bytesTotal,
  225. bytesDone: s.bytesDone,
  226. };
  227. }
  228. }
  229. for (var folder in $scope.progress) {
  230. if (!(folder in progress)) {
  231. if ($scope.neededFolder == folder) {
  232. refreshNeed(folder);
  233. }
  234. } else if ($scope.neededFolder == folder) {
  235. for (file in $scope.progress[folder]) {
  236. if (!(file in progress[folder])) {
  237. refreshNeed(folder);
  238. break;
  239. }
  240. }
  241. }
  242. }
  243. $scope.progress = progress;
  244. console.log("DownloadProgress", $scope.progress);
  245. });
  246. $scope.$on(Events.FOLDER_SUMMARY, function (event, arg) {
  247. var data = arg.data;
  248. $scope.model[data.folder] = data.summary;
  249. recalcLocalStateTotal();
  250. });
  251. $scope.$on(Events.FOLDER_COMPLETION, function (event, arg) {
  252. var data = arg.data;
  253. if (!$scope.completion[data.device]) {
  254. $scope.completion[data.device] = {};
  255. }
  256. $scope.completion[data.device][data.folder] = data.completion;
  257. var tot = 0,
  258. cnt = 0;
  259. for (var cmp in $scope.completion[data.device]) {
  260. if (cmp === "_total") {
  261. continue;
  262. }
  263. tot += $scope.completion[data.device][cmp];
  264. cnt += 1;
  265. }
  266. $scope.completion[data.device]._total = tot / cnt;
  267. });
  268. $scope.$on(Events.FOLDER_ERRORS, function (event, arg) {
  269. var data = arg.data;
  270. $scope.failed[data.folder] = data.errors;
  271. });
  272. $scope.emitHTTPError = function (data, status, headers, config) {
  273. $scope.$emit('HTTPError', {data: data, status: status, headers: headers, config: config});
  274. };
  275. var debouncedFuncs = {};
  276. function refreshFolder(folder) {
  277. var key = "refreshFolder" + folder;
  278. if (!debouncedFuncs[key]) {
  279. debouncedFuncs[key] = debounce(function () {
  280. $http.get(urlbase + '/db/status?folder=' + encodeURIComponent(folder)).success(function (data) {
  281. $scope.model[folder] = data;
  282. recalcLocalStateTotal();
  283. console.log("refreshFolder", folder, data);
  284. }).error($scope.emitHTTPError);
  285. }, 1000, true);
  286. }
  287. debouncedFuncs[key]();
  288. }
  289. function updateLocalConfig(config) {
  290. var hasConfig = !isEmptyObject($scope.config);
  291. $scope.config = config;
  292. $scope.config.options._listenAddressStr = $scope.config.options.listenAddress.join(', ');
  293. $scope.config.options._globalAnnounceServersStr = $scope.config.options.globalAnnounceServers.join(', ');
  294. $scope.devices = $scope.config.devices;
  295. $scope.devices.forEach(function (deviceCfg) {
  296. $scope.completion[deviceCfg.deviceID] = {
  297. _total: 100
  298. };
  299. });
  300. $scope.devices.sort(deviceCompare);
  301. $scope.folders = folderMap($scope.config.folders);
  302. Object.keys($scope.folders).forEach(function (folder) {
  303. refreshFolder(folder);
  304. $scope.folders[folder].devices.forEach(function (deviceCfg) {
  305. refreshCompletion(deviceCfg.deviceID, folder);
  306. });
  307. });
  308. if (!hasConfig) {
  309. $scope.$emit('ConfigLoaded');
  310. }
  311. }
  312. function refreshSystem() {
  313. $http.get(urlbase + '/system/status').success(function (data) {
  314. $scope.myID = data.myID;
  315. $scope.system = data;
  316. $scope.announceServersTotal = data.extAnnounceOK ? Object.keys(data.extAnnounceOK).length : 0;
  317. var failedAnnounce = [];
  318. for (var server in data.extAnnounceOK) {
  319. if (!data.extAnnounceOK[server]) {
  320. failedAnnounce.push(server);
  321. }
  322. }
  323. $scope.announceServersFailed = failedAnnounce;
  324. $scope.relayClientsTotal = data.relayClientStatus ? Object.keys(data.relayClientStatus).length : 0;
  325. var failedRelays = [];
  326. for (var relay in data.relayClientStatus) {
  327. if (!data.relayClientStatus[relay]) {
  328. failedRelays.push(relay);
  329. }
  330. }
  331. $scope.relayClientsFailed = failedRelays;
  332. console.log("refreshSystem", data);
  333. }).error($scope.emitHTTPError);
  334. }
  335. function recalcLocalStateTotal () {
  336. $scope.localStateTotal = {
  337. bytes: 0,
  338. files: 0
  339. };
  340. for (var f in $scope.model) {
  341. $scope.localStateTotal.bytes += $scope.model[f].localBytes;
  342. $scope.localStateTotal.files += $scope.model[f].localFiles;
  343. }
  344. }
  345. function refreshCompletion(device, folder) {
  346. if (device === $scope.myID) {
  347. return;
  348. }
  349. $http.get(urlbase + '/db/completion?device=' + device + '&folder=' + encodeURIComponent(folder)).success(function (data) {
  350. if (!$scope.completion[device]) {
  351. $scope.completion[device] = {};
  352. }
  353. $scope.completion[device][folder] = data.completion;
  354. var tot = 0,
  355. cnt = 0;
  356. for (var cmp in $scope.completion[device]) {
  357. if (cmp === "_total") {
  358. continue;
  359. }
  360. tot += $scope.completion[device][cmp];
  361. cnt += 1;
  362. }
  363. $scope.completion[device]._total = tot / cnt;
  364. console.log("refreshCompletion", device, folder, $scope.completion[device]);
  365. }).error($scope.emitHTTPError);
  366. }
  367. function refreshConnectionStats() {
  368. $http.get(urlbase + '/system/connections').success(function (data) {
  369. var now = Date.now(),
  370. td = (now - prevDate) / 1000,
  371. id;
  372. prevDate = now;
  373. try {
  374. data.total.inbps = Math.max(0, (data.total.inBytesTotal - $scope.connectionsTotal.inBytesTotal) / td);
  375. data.total.outbps = Math.max(0, (data.total.outBytesTotal - $scope.connectionsTotal.outBytesTotal) / td);
  376. } catch (e) {
  377. data.total.inbps = 0;
  378. data.total.outbps = 0;
  379. }
  380. $scope.connectionsTotal = data.total;
  381. data = data.connections;
  382. for (id in data) {
  383. if (!data.hasOwnProperty(id)) {
  384. continue;
  385. }
  386. try {
  387. data[id].inbps = Math.max(0, (data[id].inBytesTotal - $scope.connections[id].inBytesTotal) / td);
  388. data[id].outbps = Math.max(0, (data[id].outBytesTotal - $scope.connections[id].outBytesTotal) / td);
  389. } catch (e) {
  390. data[id].inbps = 0;
  391. data[id].outbps = 0;
  392. }
  393. }
  394. $scope.connections = data;
  395. console.log("refreshConnections", data);
  396. }).error($scope.emitHTTPError);
  397. }
  398. function refreshErrors() {
  399. $http.get(urlbase + '/system/error').success(function (data) {
  400. $scope.errors = data.errors;
  401. console.log("refreshErrors", data);
  402. }).error($scope.emitHTTPError);
  403. }
  404. function refreshConfig() {
  405. $http.get(urlbase + '/system/config').success(function (data) {
  406. updateLocalConfig(data);
  407. console.log("refreshConfig", data);
  408. }).error($scope.emitHTTPError);
  409. $http.get(urlbase + '/system/config/insync').success(function (data) {
  410. $scope.configInSync = data.configInSync;
  411. }).error($scope.emitHTTPError);
  412. }
  413. function refreshNeed(folder) {
  414. var url = urlbase + "/db/need?folder=" + encodeURIComponent(folder);
  415. url += "&page=" + $scope.neededCurrentPage;
  416. url += "&perpage=" + $scope.neededPageSize;
  417. $http.get(url).success(function (data) {
  418. if ($scope.neededFolder == folder) {
  419. console.log("refreshNeed", folder, data);
  420. parseNeeded(data);
  421. }
  422. }).error($scope.emitHTTPError);
  423. }
  424. function needAction(file) {
  425. var fDelete = 4096;
  426. var fDirectory = 16384;
  427. if ((file.flags & (fDelete + fDirectory)) === fDelete + fDirectory) {
  428. return 'rmdir';
  429. } else if ((file.flags & fDelete) === fDelete) {
  430. return 'rm';
  431. } else if ((file.flags & fDirectory) === fDirectory) {
  432. return 'touch';
  433. } else {
  434. return 'sync';
  435. }
  436. }
  437. function parseNeeded(data) {
  438. var merged = [];
  439. data.progress.forEach(function (item) {
  440. item.type = "progress";
  441. item.action = needAction(item);
  442. merged.push(item);
  443. });
  444. data.queued.forEach(function (item) {
  445. item.type = "queued";
  446. item.action = needAction(item);
  447. merged.push(item);
  448. });
  449. data.rest.forEach(function (item) {
  450. item.type = "rest";
  451. item.action = needAction(item);
  452. merged.push(item);
  453. });
  454. $scope.needed = merged;
  455. $scope.neededTotal = data.total;
  456. }
  457. $scope.neededPageChanged = function (page) {
  458. $scope.neededCurrentPage = page;
  459. refreshNeed($scope.neededFolder);
  460. };
  461. $scope.neededChangePageSize = function (perpage) {
  462. $scope.neededPageSize = perpage;
  463. refreshNeed($scope.neededFolder);
  464. };
  465. $scope.failedPageChanged = function (page) {
  466. $scope.failedCurrentPage = page;
  467. };
  468. $scope.failedChangePageSize = function (perpage) {
  469. $scope.failedPageSize = perpage;
  470. };
  471. var refreshDeviceStats = debounce(function () {
  472. $http.get(urlbase + "/stats/device").success(function (data) {
  473. $scope.deviceStats = data;
  474. for (var device in $scope.deviceStats) {
  475. $scope.deviceStats[device].lastSeen = new Date($scope.deviceStats[device].lastSeen);
  476. $scope.deviceStats[device].lastSeenDays = (new Date() - $scope.deviceStats[device].lastSeen) / 1000 / 86400;
  477. }
  478. console.log("refreshDeviceStats", data);
  479. }).error($scope.emitHTTPError);
  480. }, 2500);
  481. var refreshFolderStats = debounce(function () {
  482. $http.get(urlbase + "/stats/folder").success(function (data) {
  483. $scope.folderStats = data;
  484. for (var folder in $scope.folderStats) {
  485. if ($scope.folderStats[folder].lastFile) {
  486. $scope.folderStats[folder].lastFile.at = new Date($scope.folderStats[folder].lastFile.at);
  487. }
  488. }
  489. console.log("refreshfolderStats", data);
  490. }).error($scope.emitHTTPError);
  491. }, 2500);
  492. $scope.refresh = function () {
  493. refreshSystem();
  494. refreshConnectionStats();
  495. refreshErrors();
  496. };
  497. $scope.folderStatus = function (folderCfg) {
  498. if (typeof $scope.model[folderCfg.id] === 'undefined') {
  499. return 'unknown';
  500. }
  501. // after restart syncthing process state may be empty
  502. if (!$scope.model[folderCfg.id].state) {
  503. return 'unknown';
  504. }
  505. if (folderCfg.devices.length <= 1) {
  506. return 'unshared';
  507. }
  508. if ($scope.model[folderCfg.id].invalid) {
  509. return 'stopped';
  510. }
  511. var state = '' + $scope.model[folderCfg.id].state;
  512. if (state === 'error') {
  513. return 'stopped'; // legacy, the state is called "stopped" in the GUI
  514. }
  515. if (state === 'idle' && $scope.model[folderCfg.id].needFiles > 0) {
  516. return 'outofsync';
  517. }
  518. return state;
  519. };
  520. $scope.folderClass = function (folderCfg) {
  521. var status = $scope.folderStatus(folderCfg);
  522. if (status == 'idle') {
  523. return 'success';
  524. }
  525. if (status == 'syncing' || status == 'scanning') {
  526. return 'primary';
  527. }
  528. if (status === 'unknown') {
  529. return 'info';
  530. }
  531. if (status === 'unshared') {
  532. return 'warning';
  533. }
  534. if (status === 'stopped' || status === 'outofsync' || status === 'error') {
  535. return 'danger';
  536. }
  537. return 'info';
  538. };
  539. $scope.syncPercentage = function (folder) {
  540. if (typeof $scope.model[folder] === 'undefined') {
  541. return 100;
  542. }
  543. if ($scope.model[folder].globalBytes === 0) {
  544. return 100;
  545. }
  546. var pct = 100 * $scope.model[folder].inSyncBytes / $scope.model[folder].globalBytes;
  547. return Math.floor(pct);
  548. };
  549. $scope.deviceStatus = function (deviceCfg) {
  550. if ($scope.deviceFolders(deviceCfg).length === 0) {
  551. return 'unused';
  552. }
  553. if (typeof $scope.connections[deviceCfg.deviceID] === 'undefined') {
  554. return 'unknown';
  555. }
  556. if ($scope.connections[deviceCfg.deviceID].paused) {
  557. return 'paused';
  558. }
  559. if ($scope.connections[deviceCfg.deviceID].connected) {
  560. if ($scope.completion[deviceCfg.deviceID] && $scope.completion[deviceCfg.deviceID]._total === 100) {
  561. return 'insync';
  562. } else {
  563. return 'syncing';
  564. }
  565. }
  566. // Disconnected
  567. return 'disconnected';
  568. };
  569. $scope.deviceClass = function (deviceCfg) {
  570. if ($scope.deviceFolders(deviceCfg).length === 0) {
  571. // Unused
  572. return 'warning';
  573. }
  574. if (typeof $scope.connections[deviceCfg.deviceID] === 'undefined') {
  575. return 'info';
  576. }
  577. if ($scope.connections[deviceCfg.deviceID].paused) {
  578. return 'default';
  579. }
  580. if ($scope.connections[deviceCfg.deviceID].connected) {
  581. if ($scope.completion[deviceCfg.deviceID] && $scope.completion[deviceCfg.deviceID]._total === 100) {
  582. return 'success';
  583. } else {
  584. return 'primary';
  585. }
  586. }
  587. // Disconnected
  588. return 'info';
  589. };
  590. $scope.deviceAddr = function (deviceCfg) {
  591. var conn = $scope.connections[deviceCfg.deviceID];
  592. if (conn && conn.connected) {
  593. return conn.address;
  594. }
  595. return '?';
  596. };
  597. $scope.deviceCompletion = function (deviceCfg) {
  598. var conn = $scope.connections[deviceCfg.deviceID];
  599. if (conn) {
  600. return conn.completion + '%';
  601. }
  602. return '';
  603. };
  604. $scope.findDevice = function (deviceID) {
  605. var matches = $scope.devices.filter(function (n) {
  606. return n.deviceID == deviceID;
  607. });
  608. if (matches.length != 1) {
  609. return undefined;
  610. }
  611. return matches[0];
  612. };
  613. $scope.deviceName = function (deviceCfg) {
  614. if (typeof deviceCfg === 'undefined') {
  615. return "";
  616. }
  617. if (deviceCfg.name) {
  618. return deviceCfg.name;
  619. }
  620. return deviceCfg.deviceID.substr(0, 6);
  621. };
  622. $scope.thisDeviceName = function () {
  623. var device = $scope.thisDevice();
  624. if (typeof device === 'undefined') {
  625. return "(unknown device)";
  626. }
  627. if (device.name) {
  628. return device.name;
  629. }
  630. return device.deviceID.substr(0, 6);
  631. };
  632. $scope.pauseDevice = function (device) {
  633. $http.post(urlbase + "/system/pause?device=" + device);
  634. };
  635. $scope.resumeDevice = function (device) {
  636. $http.post(urlbase + "/system/resume?device=" + device);
  637. };
  638. $scope.editSettings = function () {
  639. // Make a working copy
  640. $scope.tmpOptions = angular.copy($scope.config.options);
  641. $scope.tmpOptions.urEnabled = ($scope.tmpOptions.urAccepted > 0);
  642. $scope.tmpOptions.deviceName = $scope.thisDevice().name;
  643. $scope.tmpOptions.autoUpgradeEnabled = ($scope.tmpOptions.autoUpgradeIntervalH > 0);
  644. $scope.tmpGUI = angular.copy($scope.config.gui);
  645. $('#settings').modal();
  646. };
  647. $scope.saveConfig = function () {
  648. var cfg = JSON.stringify($scope.config);
  649. var opts = {
  650. headers: {
  651. 'Content-Type': 'application/json'
  652. }
  653. };
  654. $http.post(urlbase + '/system/config', cfg, opts).success(function () {
  655. $http.get(urlbase + '/system/config/insync').success(function (data) {
  656. $scope.configInSync = data.configInSync;
  657. });
  658. }).error($scope.emitHTTPError);
  659. };
  660. $scope.saveSettings = function () {
  661. // Make sure something changed
  662. var changed = !angular.equals($scope.config.options, $scope.tmpOptions) || !angular.equals($scope.config.gui, $scope.tmpGUI);
  663. if (changed) {
  664. // Check if usage reporting has been enabled or disabled
  665. if ($scope.tmpOptions.urEnabled && $scope.tmpOptions.urAccepted <= 0) {
  666. $scope.tmpOptions.urAccepted = 1000;
  667. } else if (!$scope.tmpOptions.urEnabled && $scope.tmpOptions.urAccepted > 0) {
  668. $scope.tmpOptions.urAccepted = -1;
  669. }
  670. // Check if auto-upgrade has been enabled or disabled
  671. if ($scope.tmpOptions.autoUpgradeEnabled) {
  672. $scope.tmpOptions.autoUpgradeIntervalH = $scope.tmpOptions.autoUpgradeIntervalH || 12;
  673. } else {
  674. $scope.tmpOptions.autoUpgradeIntervalH = 0;
  675. }
  676. // Check if protocol will need to be changed on restart
  677. if ($scope.config.gui.useTLS !== $scope.tmpGUI.useTLS) {
  678. $scope.protocolChanged = true;
  679. }
  680. // Apply new settings locally
  681. $scope.thisDevice().name = $scope.tmpOptions.deviceName;
  682. $scope.config.options = angular.copy($scope.tmpOptions);
  683. $scope.config.gui = angular.copy($scope.tmpGUI);
  684. ['listenAddress', 'globalAnnounceServers'].forEach(function (key) {
  685. $scope.config.options[key] = $scope.config.options["_" + key + "Str"].split(/[ ,]+/).map(function (x) {
  686. return x.trim();
  687. });
  688. });
  689. $scope.saveConfig();
  690. }
  691. $('#settings').modal("hide");
  692. };
  693. $scope.saveAdvanced = function () {
  694. $scope.config = $scope.advancedConfig;
  695. $scope.saveConfig();
  696. $('#advanced').modal("hide");
  697. };
  698. $scope.restart = function () {
  699. restarting = true;
  700. $('#restarting').modal();
  701. $http.post(urlbase + '/system/restart');
  702. $scope.configInSync = true;
  703. // Switch webpage protocol if needed
  704. if ($scope.protocolChanged) {
  705. var protocol = 'http';
  706. if ($scope.config.gui.useTLS) {
  707. protocol = 'https';
  708. }
  709. setTimeout(function () {
  710. window.location.protocol = protocol;
  711. }, 2500);
  712. $scope.protocolChanged = false;
  713. }
  714. };
  715. $scope.upgrade = function () {
  716. restarting = true;
  717. $('#majorUpgrade').modal('hide');
  718. $('#upgrading').modal();
  719. $http.post(urlbase + '/system/upgrade').success(function () {
  720. $('#restarting').modal();
  721. $('#upgrading').modal('hide');
  722. }).error(function () {
  723. $('#upgrading').modal('hide');
  724. });
  725. };
  726. $scope.upgradeMajor = function () {
  727. $('#majorUpgrade').modal();
  728. };
  729. $scope.shutdown = function () {
  730. restarting = true;
  731. $http.post(urlbase + '/system/shutdown').success(function () {
  732. $('#shutdown').modal();
  733. }).error($scope.emitHTTPError);
  734. $scope.configInSync = true;
  735. };
  736. $scope.editDevice = function (deviceCfg) {
  737. $scope.currentDevice = $.extend({}, deviceCfg);
  738. $scope.editingExisting = true;
  739. $scope.currentDevice._addressesStr = deviceCfg.addresses.join(', ');
  740. $scope.currentDevice.selectedFolders = {};
  741. $scope.deviceFolders($scope.currentDevice).forEach(function (folder) {
  742. $scope.currentDevice.selectedFolders[folder] = true;
  743. });
  744. $scope.deviceEditor.$setPristine();
  745. $('#editDevice').modal();
  746. };
  747. $scope.idDevice = function () {
  748. $('#idqr').modal('show');
  749. };
  750. $scope.addDevice = function () {
  751. $http.get(urlbase + '/system/discovery')
  752. .success(function (registry) {
  753. $scope.discovery = registry;
  754. })
  755. .then(function () {
  756. $scope.currentDevice = {
  757. _addressesStr: 'dynamic',
  758. compression: 'metadata',
  759. introducer: false,
  760. selectedFolders: {}
  761. };
  762. $scope.editingExisting = false;
  763. $scope.deviceEditor.$setPristine();
  764. $('#editDevice').modal();
  765. });
  766. };
  767. $scope.deleteDevice = function () {
  768. $('#editDevice').modal('hide');
  769. if (!$scope.editingExisting) {
  770. return;
  771. }
  772. $scope.devices = $scope.devices.filter(function (n) {
  773. return n.deviceID !== $scope.currentDevice.deviceID;
  774. });
  775. $scope.config.devices = $scope.devices;
  776. // In case we later added the device manually, remove the ignoral
  777. // record.
  778. $scope.config.ignoredDevices = $scope.config.ignoredDevices.filter(function (id) {
  779. return id !== $scope.currentDevice.deviceID;
  780. });
  781. for (var id in $scope.folders) {
  782. $scope.folders[id].devices = $scope.folders[id].devices.filter(function (n) {
  783. return n.deviceID !== $scope.currentDevice.deviceID;
  784. });
  785. }
  786. $scope.saveConfig();
  787. };
  788. $scope.saveDevice = function () {
  789. $('#editDevice').modal('hide');
  790. $scope.saveDeviceConfig($scope.currentDevice);
  791. };
  792. $scope.addNewDeviceID = function (device) {
  793. var deviceCfg = {
  794. deviceID: device,
  795. _addressesStr: 'dynamic',
  796. compression: 'metadata',
  797. introducer: false,
  798. selectedFolders: {}
  799. };
  800. $scope.saveDeviceConfig(deviceCfg);
  801. $scope.dismissDeviceRejection(device);
  802. };
  803. $scope.saveDeviceConfig = function (deviceCfg) {
  804. var done, i;
  805. deviceCfg.addresses = deviceCfg._addressesStr.split(',').map(function (x) {
  806. return x.trim();
  807. });
  808. done = false;
  809. for (i = 0; i < $scope.devices.length; i++) {
  810. if ($scope.devices[i].deviceID === deviceCfg.deviceID) {
  811. $scope.devices[i] = deviceCfg;
  812. done = true;
  813. break;
  814. }
  815. }
  816. if (!done) {
  817. $scope.devices.push(deviceCfg);
  818. }
  819. $scope.devices.sort(deviceCompare);
  820. $scope.config.devices = $scope.devices;
  821. // In case we are adding the device manually, remove the ignoral
  822. // record.
  823. $scope.config.ignoredDevices = $scope.config.ignoredDevices.filter(function (id) {
  824. return id !== deviceCfg.deviceID;
  825. });
  826. for (var id in deviceCfg.selectedFolders) {
  827. if (deviceCfg.selectedFolders[id]) {
  828. var found = false;
  829. for (i = 0; i < $scope.folders[id].devices.length; i++) {
  830. if ($scope.folders[id].devices[i].deviceID == deviceCfg.deviceID) {
  831. found = true;
  832. break;
  833. }
  834. }
  835. if (!found) {
  836. $scope.folders[id].devices.push({
  837. deviceID: deviceCfg.deviceID
  838. });
  839. }
  840. } else {
  841. $scope.folders[id].devices = $scope.folders[id].devices.filter(function (n) {
  842. return n.deviceID != deviceCfg.deviceID;
  843. });
  844. }
  845. }
  846. $scope.saveConfig();
  847. };
  848. $scope.dismissDeviceRejection = function (device) {
  849. delete $scope.deviceRejections[device];
  850. };
  851. $scope.ignoreRejectedDevice = function (device) {
  852. $scope.config.ignoredDevices.push(device);
  853. $scope.saveConfig();
  854. $scope.dismissDeviceRejection(device);
  855. };
  856. $scope.otherDevices = function () {
  857. return $scope.devices.filter(function (n) {
  858. return n.deviceID !== $scope.myID;
  859. });
  860. };
  861. $scope.thisDevice = function () {
  862. var i, n;
  863. for (i = 0; i < $scope.devices.length; i++) {
  864. n = $scope.devices[i];
  865. if (n.deviceID === $scope.myID) {
  866. return n;
  867. }
  868. }
  869. };
  870. $scope.allDevices = function () {
  871. var devices = $scope.otherDevices();
  872. devices.push($scope.thisDevice());
  873. return devices;
  874. };
  875. $scope.errorList = function () {
  876. return $scope.errors.filter(function (e) {
  877. return e.time > $scope.seenError;
  878. });
  879. };
  880. $scope.clearErrors = function () {
  881. $scope.seenError = $scope.errors[$scope.errors.length - 1].time;
  882. $http.post(urlbase + '/system/error/clear');
  883. };
  884. $scope.friendlyDevices = function (str) {
  885. for (var i = 0; i < $scope.devices.length; i++) {
  886. var cfg = $scope.devices[i];
  887. str = str.replace(cfg.deviceID, $scope.deviceName(cfg));
  888. }
  889. return str;
  890. };
  891. $scope.folderList = function () {
  892. return folderList($scope.folders);
  893. };
  894. $scope.directoryList = [];
  895. $scope.$watch('currentFolder.path', function (newvalue) {
  896. if (newvalue && newvalue.trim().charAt(0) == '~') {
  897. $scope.currentFolder.path = $scope.system.tilde + newvalue.trim().substring(1);
  898. }
  899. $http.get(urlbase + '/system/browse', {
  900. params: { current: newvalue }
  901. }).success(function (data) {
  902. $scope.directoryList = data;
  903. }).error($scope.emitHTTPError);
  904. });
  905. $scope.editFolder = function (folderCfg) {
  906. $scope.currentFolder = angular.copy(folderCfg);
  907. if ($scope.currentFolder.path.slice(-1) == $scope.system.pathSeparator) {
  908. $scope.currentFolder.path = $scope.currentFolder.path.slice(0, -1);
  909. }
  910. $scope.currentFolder.selectedDevices = {};
  911. $scope.currentFolder.devices.forEach(function (n) {
  912. $scope.currentFolder.selectedDevices[n.deviceID] = true;
  913. });
  914. if ($scope.currentFolder.versioning && $scope.currentFolder.versioning.type === "trashcan") {
  915. $scope.currentFolder.trashcanFileVersioning = true;
  916. $scope.currentFolder.fileVersioningSelector = "trashcan";
  917. $scope.currentFolder.trashcanClean = +$scope.currentFolder.versioning.params.cleanoutDays;
  918. } else if ($scope.currentFolder.versioning && $scope.currentFolder.versioning.type === "simple") {
  919. $scope.currentFolder.simpleFileVersioning = true;
  920. $scope.currentFolder.fileVersioningSelector = "simple";
  921. $scope.currentFolder.simpleKeep = +$scope.currentFolder.versioning.params.keep;
  922. } else if ($scope.currentFolder.versioning && $scope.currentFolder.versioning.type === "staggered") {
  923. $scope.currentFolder.staggeredFileVersioning = true;
  924. $scope.currentFolder.fileVersioningSelector = "staggered";
  925. $scope.currentFolder.staggeredMaxAge = Math.floor(+$scope.currentFolder.versioning.params.maxAge / 86400);
  926. $scope.currentFolder.staggeredCleanInterval = +$scope.currentFolder.versioning.params.cleanInterval;
  927. $scope.currentFolder.staggeredVersionsPath = $scope.currentFolder.versioning.params.versionsPath;
  928. } else if ($scope.currentFolder.versioning && $scope.currentFolder.versioning.type === "external") {
  929. $scope.currentFolder.externalFileVersioning = true;
  930. $scope.currentFolder.fileVersioningSelector = "external";
  931. $scope.currentFolder.externalCommand = $scope.currentFolder.versioning.params.command;
  932. } else {
  933. $scope.currentFolder.fileVersioningSelector = "none";
  934. }
  935. $scope.currentFolder.trashcanClean = $scope.currentFolder.trashcanClean || 0; // weeds out nulls and undefineds
  936. $scope.currentFolder.simpleKeep = $scope.currentFolder.simpleKeep || 5;
  937. $scope.currentFolder.staggeredCleanInterval = $scope.currentFolder.staggeredCleanInterval || 3600;
  938. $scope.currentFolder.staggeredVersionsPath = $scope.currentFolder.staggeredVersionsPath || "";
  939. // staggeredMaxAge can validly be zero, which we should not replace
  940. // with the default value of 365. So only set the default if it's
  941. // actually undefined.
  942. if (typeof $scope.currentFolder.staggeredMaxAge === 'undefined') {
  943. $scope.currentFolder.staggeredMaxAge = 365;
  944. }
  945. $scope.currentFolder.externalCommand = $scope.currentFolder.externalCommand || "";
  946. $scope.editingExisting = true;
  947. $scope.folderEditor.$setPristine();
  948. $('#editFolder').modal();
  949. };
  950. $scope.addFolder = function () {
  951. $scope.currentFolder = {
  952. selectedDevices: {}
  953. };
  954. $scope.currentFolder.rescanIntervalS = 60;
  955. $scope.currentFolder.minDiskFreePct = 1;
  956. $scope.currentFolder.order = "random";
  957. $scope.currentFolder.fileVersioningSelector = "none";
  958. $scope.currentFolder.trashcanClean = 0;
  959. $scope.currentFolder.simpleKeep = 5;
  960. $scope.currentFolder.staggeredMaxAge = 365;
  961. $scope.currentFolder.staggeredCleanInterval = 3600;
  962. $scope.currentFolder.staggeredVersionsPath = "";
  963. $scope.currentFolder.externalCommand = "";
  964. $scope.currentFolder.autoNormalize = true;
  965. $scope.editingExisting = false;
  966. $scope.folderEditor.$setPristine();
  967. $('#editFolder').modal();
  968. };
  969. $scope.addFolderAndShare = function (folder, device) {
  970. $scope.dismissFolderRejection(folder, device);
  971. $scope.currentFolder = {
  972. id: folder,
  973. selectedDevices: {},
  974. rescanIntervalS: 60,
  975. minDiskFreePct: 1,
  976. order: "random",
  977. fileVersioningSelector: "none",
  978. trashcanClean: 0,
  979. simpleKeep: 5,
  980. staggeredMaxAge: 365,
  981. staggeredCleanInterval: 3600,
  982. staggeredVersionsPath: "",
  983. externalCommand: "",
  984. autoNormalize: true
  985. };
  986. $scope.currentFolder.selectedDevices[device] = true;
  987. $scope.editingExisting = false;
  988. $scope.folderEditor.$setPristine();
  989. $('#editFolder').modal();
  990. };
  991. $scope.shareFolderWithDevice = function (folder, device) {
  992. $scope.folders[folder].devices.push({
  993. deviceID: device
  994. });
  995. $scope.config.folders = folderList($scope.folders);
  996. $scope.saveConfig();
  997. $scope.dismissFolderRejection(folder, device);
  998. };
  999. $scope.saveFolder = function () {
  1000. var folderCfg, done, i;
  1001. $('#editFolder').modal('hide');
  1002. folderCfg = $scope.currentFolder;
  1003. folderCfg.devices = [];
  1004. folderCfg.selectedDevices[$scope.myID] = true;
  1005. for (var deviceID in folderCfg.selectedDevices) {
  1006. if (folderCfg.selectedDevices[deviceID] === true) {
  1007. folderCfg.devices.push({
  1008. deviceID: deviceID
  1009. });
  1010. }
  1011. }
  1012. delete folderCfg.selectedDevices;
  1013. if (folderCfg.fileVersioningSelector === "trashcan") {
  1014. folderCfg.versioning = {
  1015. 'Type': 'trashcan',
  1016. 'Params': {
  1017. 'cleanoutDays': '' + folderCfg.trashcanClean
  1018. }
  1019. };
  1020. delete folderCfg.trashcanFileVersioning;
  1021. delete folderCfg.trashcanClean;
  1022. } else if (folderCfg.fileVersioningSelector === "simple") {
  1023. folderCfg.versioning = {
  1024. 'Type': 'simple',
  1025. 'Params': {
  1026. 'keep': '' + folderCfg.simpleKeep
  1027. }
  1028. };
  1029. delete folderCfg.simpleFileVersioning;
  1030. delete folderCfg.simpleKeep;
  1031. } else if (folderCfg.fileVersioningSelector === "staggered") {
  1032. folderCfg.versioning = {
  1033. 'type': 'staggered',
  1034. 'params': {
  1035. 'maxAge': '' + (folderCfg.staggeredMaxAge * 86400),
  1036. 'cleanInterval': '' + folderCfg.staggeredCleanInterval,
  1037. 'versionsPath': '' + folderCfg.staggeredVersionsPath
  1038. }
  1039. };
  1040. delete folderCfg.staggeredFileVersioning;
  1041. delete folderCfg.staggeredMaxAge;
  1042. delete folderCfg.staggeredCleanInterval;
  1043. delete folderCfg.staggeredVersionsPath;
  1044. } else if (folderCfg.fileVersioningSelector === "external") {
  1045. folderCfg.versioning = {
  1046. 'Type': 'external',
  1047. 'Params': {
  1048. 'command': '' + folderCfg.externalCommand
  1049. }
  1050. };
  1051. delete folderCfg.externalFileVersioning;
  1052. delete folderCfg.externalCommand;
  1053. } else {
  1054. delete folderCfg.versioning;
  1055. }
  1056. $scope.folders[folderCfg.id] = folderCfg;
  1057. $scope.config.folders = folderList($scope.folders);
  1058. $scope.saveConfig();
  1059. };
  1060. $scope.dismissFolderRejection = function (folder, device) {
  1061. delete $scope.folderRejections[folder + "-" + device];
  1062. };
  1063. $scope.sharesFolder = function (folderCfg) {
  1064. var names = [];
  1065. folderCfg.devices.forEach(function (device) {
  1066. if (device.deviceID != $scope.myID) {
  1067. names.push($scope.deviceName($scope.findDevice(device.deviceID)));
  1068. }
  1069. });
  1070. names.sort();
  1071. return names.join(", ");
  1072. };
  1073. $scope.deviceFolders = function (deviceCfg) {
  1074. var folders = [];
  1075. for (var folderID in $scope.folders) {
  1076. var devices = $scope.folders[folderID].devices;
  1077. for (var i = 0; i < devices.length; i++) {
  1078. if (devices[i].deviceID == deviceCfg.deviceID) {
  1079. folders.push(folderID);
  1080. break;
  1081. }
  1082. }
  1083. }
  1084. folders.sort();
  1085. return folders;
  1086. };
  1087. $scope.deleteFolder = function (id) {
  1088. $('#editFolder').modal('hide');
  1089. if (!$scope.editingExisting) {
  1090. return;
  1091. }
  1092. delete $scope.folders[id];
  1093. delete $scope.model[id];
  1094. $scope.config.folders = folderList($scope.folders);
  1095. recalcLocalStateTotal();
  1096. $scope.saveConfig();
  1097. };
  1098. $scope.editIgnores = function () {
  1099. if (!$scope.editingExisting) {
  1100. return;
  1101. }
  1102. $('#editIgnoresButton').attr('disabled', 'disabled');
  1103. $http.get(urlbase + '/db/ignores?folder=' + encodeURIComponent($scope.currentFolder.id))
  1104. .success(function (data) {
  1105. data.ignore = data.ignore || [];
  1106. $('#editFolder').modal('hide')
  1107. .one('hidden.bs.modal', function() {
  1108. var textArea = $('#editIgnores textarea');
  1109. textArea.val(data.ignore.join('\n'));
  1110. $('#editIgnores').modal()
  1111. .one('hidden.bs.modal', function () {
  1112. $('#editFolder').modal();
  1113. })
  1114. .one('shown.bs.modal', function () {
  1115. textArea.focus();
  1116. });
  1117. });
  1118. })
  1119. .then(function () {
  1120. $('#editIgnoresButton').removeAttr('disabled');
  1121. });
  1122. };
  1123. $scope.saveIgnores = function () {
  1124. if (!$scope.editingExisting) {
  1125. return;
  1126. }
  1127. $http.post(urlbase + '/db/ignores?folder=' + encodeURIComponent($scope.currentFolder.id), {
  1128. ignore: $('#editIgnores textarea').val().split('\n')
  1129. });
  1130. };
  1131. $scope.setAPIKey = function (cfg) {
  1132. cfg.apiKey = randomString(32);
  1133. };
  1134. $scope.showURPreview = function () {
  1135. $('#settings').modal('hide');
  1136. $('#urPreview').modal().on('hidden.bs.modal', function () {
  1137. $('#settings').modal();
  1138. });
  1139. };
  1140. $scope.acceptUR = function () {
  1141. $scope.config.options.urAccepted = 1000; // Larger than the largest existing report version
  1142. $scope.saveConfig();
  1143. $('#ur').modal('hide');
  1144. };
  1145. $scope.declineUR = function () {
  1146. $scope.config.options.urAccepted = -1;
  1147. $scope.saveConfig();
  1148. $('#ur').modal('hide');
  1149. };
  1150. $scope.showNeed = function (folder) {
  1151. $scope.neededFolder = folder;
  1152. refreshNeed(folder);
  1153. $('#needed').modal().on('hidden.bs.modal', function () {
  1154. $scope.neededFolder = undefined;
  1155. $scope.needed = undefined;
  1156. $scope.neededTotal = 0;
  1157. $scope.neededCurrentPage = 1;
  1158. });
  1159. };
  1160. $scope.showFailed = function (folder) {
  1161. $scope.failedCurrent = $scope.failed[folder]
  1162. $('#failed').modal().on('hidden.bs.modal', function () {
  1163. $scope.failedCurrent = undefined;
  1164. });
  1165. };
  1166. $scope.hasFailedFiles = function (folder) {
  1167. if (!$scope.failed[folder]) {
  1168. return false;
  1169. }
  1170. if ($scope.failed[folder].length == 0) {
  1171. return false;
  1172. }
  1173. return true
  1174. };
  1175. $scope.override = function (folder) {
  1176. $http.post(urlbase + "/db/override?folder=" + encodeURIComponent(folder));
  1177. };
  1178. $scope.about = function () {
  1179. $('#about').modal('show');
  1180. };
  1181. $scope.advanced = function () {
  1182. $scope.advancedConfig = angular.copy($scope.config);
  1183. $('#advanced').modal('show');
  1184. };
  1185. $scope.showReportPreview = function () {
  1186. $scope.reportPreview = true;
  1187. };
  1188. $scope.rescanAllFolders = function () {
  1189. $http.post(urlbase + "/db/scan");
  1190. };
  1191. $scope.rescanFolder = function (folder) {
  1192. $http.post(urlbase + "/db/scan?folder=" + encodeURIComponent(folder));
  1193. };
  1194. $scope.bumpFile = function (folder, file) {
  1195. var url = urlbase + "/db/prio?folder=" + encodeURIComponent(folder) + "&file=" + encodeURIComponent(file);
  1196. // In order to get the right view of data in the response.
  1197. url += "&page=" + $scope.neededCurrentPage;
  1198. url += "&perpage=" + $scope.neededPageSize;
  1199. $http.post(url).success(function (data) {
  1200. if ($scope.neededFolder == folder) {
  1201. console.log("bumpFile", folder, data);
  1202. parseNeeded(data);
  1203. }
  1204. }).error($scope.emitHTTPError);
  1205. };
  1206. $scope.versionString = function () {
  1207. if (!$scope.version.version) {
  1208. return '';
  1209. }
  1210. var os = {
  1211. 'darwin': 'Mac OS X',
  1212. 'dragonfly': 'DragonFly BSD',
  1213. 'freebsd': 'FreeBSD',
  1214. 'openbsd': 'OpenBSD',
  1215. 'netbsd': 'NetBSD',
  1216. 'linux': 'Linux',
  1217. 'windows': 'Windows',
  1218. 'solaris': 'Solaris',
  1219. }[$scope.version.os] || $scope.version.os;
  1220. var arch ={
  1221. '386': '32 bit',
  1222. 'amd64': '64 bit',
  1223. 'arm': 'ARM',
  1224. }[$scope.version.arch] || $scope.version.arch;
  1225. return $scope.version.version + ', ' + os + ' (' + arch + ')';
  1226. };
  1227. $scope.inputTypeFor = function (key, value) {
  1228. if (key.substr(0, 1) === '_') {
  1229. return 'skip';
  1230. }
  1231. if (typeof value === 'number') {
  1232. return 'number';
  1233. }
  1234. if (typeof value === 'boolean') {
  1235. return 'checkbox';
  1236. }
  1237. if (typeof value === 'object') {
  1238. return 'skip';
  1239. }
  1240. return 'text';
  1241. };
  1242. // pseudo main. called on all definitions assigned
  1243. initController();
  1244. });