You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

rspamd.js 50KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250
  1. /*
  2. The MIT License (MIT)
  3. Copyright (C) 2012-2013 Anton Simonov <untone@gmail.com>
  4. Copyright (C) 2014-2015 Vsevolod Stakhov <vsevolod@highsecure.ru>
  5. Permission is hereby granted, free of charge, to any person obtaining a copy
  6. of this software and associated documentation files (the "Software"), to deal
  7. in the Software without restriction, including without limitation the rights
  8. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. copies of the Software, and to permit persons to whom the Software is
  10. furnished to do so, subject to the following conditions:
  11. The above copyright notice and this permission notice shall be included in
  12. all copies or substantial portions of the Software.
  13. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  19. THE SOFTWARE.
  20. */
  21. (function () {
  22. $(document).ready(function () {
  23. // begin
  24. //$.cookie.json = true;
  25. var pie;
  26. var history;
  27. var errors;
  28. var graph;
  29. var symbols;
  30. var read_only = false;
  31. var btn_class = "";
  32. var stat_timeout;
  33. var selected = []; // Keep graph selectors state
  34. // Bind event handlers to selectors
  35. $("#selData").change(function () {
  36. selected.selData = this.value;
  37. getGraphData(this.value);
  38. });
  39. $("#selConvert").change(function () {
  40. graph.convert(this.value);
  41. });
  42. $("#selType").change(function () {
  43. graph.type(this.value);
  44. });
  45. $("#selInterpolate").change(function () {
  46. graph.interpolate(this.value);
  47. });
  48. $('#disconnect').on('click', function (event) {
  49. if (pie) {
  50. pie.destroy();
  51. }
  52. if (graph) {
  53. graph.destroy();
  54. graph = undefined;
  55. }
  56. if (history) {
  57. history.destroy();
  58. }
  59. if (errors) {
  60. errors.destroy();
  61. }
  62. if (symbols) {
  63. symbols.destroy();
  64. symbols = null;
  65. }
  66. clearTimeout(stat_timeout);
  67. cleanCredentials();
  68. connectRSPAMD();
  69. // window.location.reload();
  70. return false;
  71. });
  72. $('#refresh').on('click', function (event) {
  73. statWidgets();
  74. getChart();
  75. getGraphData(selected.selData);
  76. });
  77. // @supports session storage
  78. function supportsSessionStorage() {
  79. return typeof (Storage) !== "undefined";
  80. }
  81. // @return password
  82. function getPassword() {
  83. if (sessionState()) {
  84. if (!supportsSessionStorage()) {
  85. return password = $.cookie('rspamdpasswd');
  86. } else {
  87. return password = sessionStorage.getItem('Password');
  88. }
  89. }
  90. }
  91. // @return session state
  92. function sessionState() {
  93. if ((supportsSessionStorage() && (sessionStorage.getItem('Password') !== null)) ||
  94. (!supportsSessionStorage() && ($.cookie('rspamdsession')) !== null)) {
  95. return true;
  96. } else {
  97. return false;
  98. }
  99. }
  100. // @detect session storate
  101. supportsSessionStorage();
  102. // @save credentials
  103. function saveCredentials(data, password) {
  104. if (!supportsSessionStorage()) {
  105. $.cookie('rspamdsession', data, {
  106. expires: 1
  107. }, {
  108. path: '/'
  109. });
  110. $.cookie('rspamdpasswd', password, {
  111. expires: 1
  112. }, {
  113. path: '/'
  114. });
  115. } else {
  116. sessionStorage.setItem('Password', password);
  117. sessionStorage.setItem('Credentials', JSON.stringify(data));
  118. }
  119. }
  120. // @update credentials
  121. function saveActions(data) {
  122. if (!supportsSessionStorage()) {
  123. $.cookie('rspamdactions', data);
  124. } else {
  125. sessionStorage.setItem('Actions', JSON.stringify(data));
  126. }
  127. }
  128. // @update credentials
  129. function saveMaps(data) {
  130. if (!supportsSessionStorage()) {
  131. $.cookie('rspamdmaps', data, {
  132. expires: 1
  133. }, {
  134. path: '/'
  135. });
  136. } else {
  137. sessionStorage.setItem('Maps', JSON.stringify(data));
  138. }
  139. }
  140. // @clean credentials
  141. function cleanCredentials() {
  142. if (!supportsSessionStorage()) {
  143. $.removeCookie('rspamdlogged');
  144. $.removeCookie('rspamdsession');
  145. $.removeCookie('rspamdpasswd');
  146. } else {
  147. sessionStorage.clear();
  148. }
  149. $('#statWidgets').empty();
  150. $('#listMaps').empty();
  151. $('#modalBody').empty();
  152. $('#historyLog tbody').remove();
  153. $('#errorsLog tbody').remove();
  154. $('#symbolsTable tbody').remove();
  155. password = '';
  156. }
  157. function isLogged() {
  158. if (!supportsSessionStorage()) {
  159. if ($.cookie('rspamdpasswd') != null) {
  160. return true;
  161. }
  162. } else {
  163. if (sessionStorage.getItem('Password') != null) {
  164. return true;
  165. }
  166. }
  167. return false;
  168. }
  169. // @alert popover
  170. function alertMessage(alertState, alertText) {
  171. if ($('.alert').is(':visible')) {
  172. $(alert).hide().remove();
  173. }
  174. var alert = $('<div class="alert ' + alertState + '" style="display:none">' +
  175. '<button type="button" class="close" data-dismiss="alert" tutle="Dismiss">&times;</button>' +
  176. '<strong>' + alertText + '</strong>')
  177. .prependTo('body');
  178. $(alert).show();
  179. setTimeout(function () {
  180. $(alert).remove();
  181. }, 3600);
  182. }
  183. // @get maps id
  184. function getMaps() {
  185. var items = [];
  186. $('#listMaps').closest('.widget-box').hide();
  187. $.ajax({
  188. dataType: 'json',
  189. url: 'maps',
  190. jsonp: false,
  191. beforeSend: function (xhr) {
  192. xhr.setRequestHeader('Password', getPassword());
  193. },
  194. error: function (data) {
  195. alertMessage('alert-modal alert-error', data.statusText);
  196. },
  197. success: function (data) {
  198. $('#listMaps').empty();
  199. saveMaps(data);
  200. getMapById();
  201. $.each(data, function (i, item) {
  202. var caption;
  203. var label;
  204. if ((item.editable == false || read_only)) {
  205. caption = 'View';
  206. label = '<span class="label label-default">Read</span>';
  207. } else {
  208. caption = 'Edit';
  209. label = '<span class="label label-default">Read</span>&nbsp;<span class="label label-success">Write</span>';
  210. }
  211. items.push('<tr>' +
  212. '<td class="col-md-2 maps-cell">' + label + '</td>' +
  213. '<td>' +
  214. '<span class="map-link" ' +
  215. 'data-source="#' + item.map + '" ' +
  216. 'data-editable="' + item.editable + '" ' +
  217. 'data-target="#modalDialog" ' +
  218. 'data-title="' + item.uri +
  219. '" data-toggle="modal">' + item.uri + '</span>' +
  220. '</td>' +
  221. '<td>' +
  222. item.description +
  223. '</td>' +
  224. '</tr>');
  225. });
  226. $('<tbody/>', {
  227. html: items.join('')
  228. }).appendTo('#listMaps');
  229. $('#listMaps').closest('.widget-box').show();
  230. }
  231. });
  232. }
  233. // @get map by id
  234. function getMapById(mode) {
  235. var data;
  236. if (!supportsSessionStorage()) {
  237. data = $.cookie('rspamdmaps', data, {
  238. expires: 1
  239. }, {
  240. path: '/'
  241. });
  242. } else {
  243. data = JSON.parse(sessionStorage.getItem('Maps'));
  244. }
  245. if (mode === 'update') {
  246. $('#modalBody').empty();
  247. getMaps();
  248. }
  249. $.each(data, function (i, item) {
  250. $.ajax({
  251. dataType: 'text',
  252. url: 'getmap',
  253. jsonp: false,
  254. beforeSend: function (xhr) {
  255. xhr.setRequestHeader('Password', getPassword());
  256. xhr.setRequestHeader('Map', item.map);
  257. },
  258. error: function () {
  259. alertMessage('alert-error', 'Cannot receive maps data');
  260. },
  261. success: function (text) {
  262. var disabled = '';
  263. if ((item.editable == false || read_only)) {
  264. disabled = 'disabled="disabled"';
  265. }
  266. $('<form class="form-horizontal form-map" method="post "action="/savemap" data-type="map" id="' +
  267. item.map + '" style="display:none">' +
  268. '<textarea class="list-textarea"' + disabled + '>' + text +
  269. '</textarea>' +
  270. '</form').appendTo('#modalBody');
  271. }
  272. });
  273. });
  274. }
  275. // @ ms to date
  276. function msToTime(seconds) {
  277. minutes = parseInt(seconds / 60);
  278. hours = parseInt(seconds / 3600);
  279. days = parseInt(seconds / 3600 / 24);
  280. weeks = parseInt(seconds / 3600 / 24 / 7);
  281. years = parseInt(seconds / 3600 / 168 / 365);
  282. if (weeks > 0) {
  283. years = years >= 10 ? years : '0' + years;
  284. weeks -= years * 168;
  285. weeks = weeks >= 10 ? weeks : '0' + weeks;
  286. // Return in format X years and Y weeks
  287. return years + ' years ' + weeks + ' weeks';
  288. }
  289. seconds -= minutes * 60;
  290. minutes -= hours * 60;
  291. hours -= days * 24;
  292. days = days >= 10 ? days : '0' + days;
  293. hours = hours >= 10 ? hours : '0' + hours;
  294. minutes = minutes >= 10 ? minutes : '0' + minutes;
  295. seconds = seconds >= 10 ? seconds : '0' + seconds;
  296. if (days > 0) {
  297. return days + ' days, ' + hours + ':' + minutes + ':' + seconds;
  298. } else {
  299. return hours + ':' + minutes + ':' + seconds;
  300. }
  301. }
  302. // @show widgets
  303. function statWidgets() {
  304. var widgets = $('#statWidgets');
  305. $(widgets).empty().hide();
  306. var data;
  307. if (!supportsSessionStorage()) {
  308. data = $.cookie('rspamdsession');
  309. } else {
  310. data = JSON.parse(sessionStorage.getItem('Credentials'));
  311. }
  312. var stat_w = [];
  313. $.each(data, function (i, item) {
  314. var widget = '';
  315. if (i == 'auth') {} else if (i == 'error') {} else if (i == 'version') {
  316. widget = '<div class="left"><strong>' + item + '</strong>' +
  317. i + '</div>';
  318. $(widget).appendTo(widgets);
  319. } else if (i == 'uptime') {
  320. widget = '<div class="right"><strong>' + msToTime(item) +
  321. '</strong>' + i + '</div>';
  322. $(widget).appendTo(widgets);
  323. } else {
  324. widget = '<li class="stat-box"><div class="widget"><strong>' +
  325. Humanize.compactInteger(item) + '</strong>' + i + '</div></li>';
  326. if (i == 'scanned') {
  327. stat_w[0] = widget;
  328. } else if (i == 'clean') {
  329. stat_w[1] = widget;
  330. } else if (i == 'greylist') {
  331. stat_w[2] = widget;
  332. } else if (i == 'probable') {
  333. stat_w[3] = widget;
  334. } else if (i == 'reject') {
  335. stat_w[4] = widget;
  336. } else if (i == 'learned') {
  337. stat_w[5] = widget;
  338. }
  339. }
  340. });
  341. $.each(stat_w, function (i, item) {
  342. $(item).appendTo(widgets);
  343. });
  344. $('#statWidgets .left,#statWidgets .right').wrapAll('<li class="stat-box pull-right"><div class="widget"></div></li>');
  345. $(widgets).show();
  346. stat_timeout = window.setTimeout(statWidgets, 10000);
  347. }
  348. // @opem modal with target form enabled
  349. $(document).on('click', '[data-toggle="modal"]', function (e) {
  350. var source = $(this).data('source');
  351. var editable = $(this).data('editable');
  352. var title = $(this).data('title');
  353. var caption = $('#modalTitle').html(title);
  354. var body = $('#modalBody ' + source).show();
  355. var target = $(this).data('target');
  356. var progress = $(target + ' .progress').hide();
  357. $(target).modal(show = true, backdrop = true, keyboard = show);
  358. if (editable === false) {
  359. $('#modalSave').hide();
  360. } else {
  361. $('#modalSave').show();
  362. }
  363. return false;
  364. });
  365. // close modal without saving
  366. $(document).on('click', '[data-dismiss="modal"]', function (e) {
  367. $('#modalBody form').hide();
  368. });
  369. function getChart() {
  370. $.ajax({
  371. dataType: 'json',
  372. type: 'GET',
  373. url: 'pie',
  374. jsonp: false,
  375. beforeSend: function (xhr) {
  376. xhr.setRequestHeader('Password', getPassword());
  377. },
  378. success: function (data) {
  379. if (pie) {
  380. pie.destroy();
  381. }
  382. pie = new d3pie("chart", {
  383. "header": {
  384. "title": {
  385. "text": "Rspamd filter stats",
  386. "fontSize": 24,
  387. "font": "open sans"
  388. },
  389. "subtitle": {
  390. "color": "#999999",
  391. "fontSize": 12,
  392. "font": "open sans"
  393. },
  394. "titleSubtitlePadding": 9
  395. },
  396. "footer": {
  397. "color": "#999999",
  398. "fontSize": 10,
  399. "font": "open sans",
  400. "location": "bottom-left"
  401. },
  402. "size": {
  403. "canvasWidth": 600,
  404. "canvasHeight": 400,
  405. "pieInnerRadius": "20%",
  406. "pieOuterRadius": "85%"
  407. },
  408. "data": {
  409. //"sortOrder": "value-desc",
  410. "content": data.filter(function (elt) {
  411. return elt.value > 0;
  412. })
  413. },
  414. "labels": {
  415. "outer": {
  416. "hideWhenLessThanPercentage": 1,
  417. "pieDistance": 30
  418. },
  419. "inner": {
  420. "hideWhenLessThanPercentage": 4
  421. },
  422. "mainLabel": {
  423. "fontSize": 14
  424. },
  425. "percentage": {
  426. "color": "#eeeeee",
  427. "fontSize": 14,
  428. "decimalPlaces": 0
  429. },
  430. "lines": {
  431. "enabled": true
  432. },
  433. "truncation": {
  434. "enabled": true
  435. }
  436. },
  437. "tooltips": {
  438. "enabled": true,
  439. "type": "placeholder",
  440. "string": "{label}: {value}, {percentage}%"
  441. },
  442. "effects": {
  443. "pullOutSegmentOnClick": {
  444. "effect": "back",
  445. "speed": 400,
  446. "size": 8
  447. },
  448. "load": {
  449. "speed": 500
  450. }
  451. },
  452. "misc": {
  453. "gradient": {
  454. "enabled": true,
  455. "percentage": 100
  456. }
  457. }
  458. });
  459. }
  460. });
  461. }
  462. function initGraph() {
  463. // Get selectors' current state
  464. var selIds = ["selData", "selConvert", "selType", "selInterpolate"];
  465. selIds.forEach(function (id) {
  466. var e = document.getElementById(id);
  467. selected[id] = e.options[e.selectedIndex].value;
  468. });
  469. var options = {
  470. title: "Rspamd throughput",
  471. width: 1060,
  472. height: 370,
  473. yAxisLabel: "Message rate, msg/s",
  474. type: selected.selType,
  475. interpolate: selected.selInterpolate,
  476. legend: {
  477. entries: [{
  478. label: "Rejected",
  479. color: "#FF0000"
  480. }, {
  481. label: "Probable spam",
  482. color: "#FFD700"
  483. }, {
  484. label: "Greylisted",
  485. color: "#436EEE"
  486. }, {
  487. label: "Clean",
  488. color: "#66cc00"
  489. }]
  490. }
  491. };
  492. graph = new D3Evolution("graph", options);
  493. }
  494. function getGraphData(type) {
  495. $.ajax({
  496. dataType: 'json',
  497. type: 'GET',
  498. url: 'graph',
  499. jsonp: false,
  500. data: {
  501. "type": type
  502. },
  503. beforeSend: function (xhr) {
  504. xhr.setRequestHeader('Password', getPassword());
  505. },
  506. success: function (data) {
  507. graph.data(data);
  508. },
  509. error: function (jqXHR, textStatus, errorThrown) {
  510. alertMessage('alert-error', 'Cannot receive throughput data: ' +
  511. textStatus + ' ' + jqXHR.status + ' ' + errorThrown);
  512. }
  513. });
  514. }
  515. // @get history log
  516. // function getChart() {
  517. // //console.log(data)
  518. // $.ajax({
  519. // dataType: 'json',
  520. // url: './pie',
  521. // beforeSend: function(xhr) {
  522. // xhr.setRequestHeader('Password', getPassword())
  523. // },
  524. // error: function() {
  525. // alertMessage('alert-error', 'Cannot receive history');
  526. // },
  527. // success: function(data) {
  528. // console.log(data);
  529. // }
  530. // });
  531. // }
  532. // @get history log
  533. function getHistory() {
  534. if (history) {
  535. var history_length = document.getElementsByName('historyLog_length')[0];
  536. if (history_length !== undefined) {
  537. history_length = parseInt(history_length.value);
  538. } else {
  539. history_length = 10;
  540. }
  541. history.destroy();
  542. $('#historyLog').children('tbody').remove();
  543. }
  544. var items = [];
  545. $.ajax({
  546. dataType: 'json',
  547. url: 'history',
  548. jsonp: false,
  549. beforeSend: function (xhr) {
  550. xhr.setRequestHeader('Password', getPassword());
  551. },
  552. error: function () {
  553. alertMessage('alert-error', 'Cannot receive history');
  554. },
  555. success: function (data) {
  556. $.each(data, function (i, item) {
  557. var action;
  558. if (item.action === 'clean' || item.action === 'no action') {
  559. action = 'label-success';
  560. } else if (item.action === 'rewrite subject' || item.action === 'add header' || item.action === 'probable spam') {
  561. action = 'label-warning';
  562. } else if (item.action === 'spam' || item.action === 'reject') {
  563. action = 'label-danger';
  564. } else {
  565. action = 'label-info';
  566. }
  567. var score;
  568. if (item.score < item.required_score) {
  569. score = 'label-success';
  570. } else {
  571. score = 'label-danger';
  572. }
  573. items.push(
  574. '<tr><td data-order="' + item.unix_time + '">' + item.time + '</td>' +
  575. '<td data-order="' + item.id + '"><div class="cell-overflow" tabindex="1" title="' + item.id + '">' + item.id + '</div></td>' +
  576. '<td data-order="' + item.ip + '"><div class="cell-overflow" tabindex="1" title="' + item.ip + '">' + item.ip + '</div></td>' +
  577. '<td data-order="' + item.action + '"><span class="label ' + action + '">' + item.action + '</span></td>' +
  578. '<td data-order="' + item.score + '"><span class="label ' + score + '">' + item.score.toFixed(2) + ' / ' + item.required_score.toFixed(2) + '</span></td>' +
  579. '<td data-order="' + item.symbols + '"><div class="cell-overflow" tabindex="1" title="' + item.symbols + '">' + item.symbols + '</div></td>' +
  580. '<td data-order="' + item.size + '">' + item.size + '</td>' +
  581. '<td data-order="' + item.scan_time + '">' + item.scan_time.toFixed(3) + '</td>' +
  582. '<td data-order="' + item.user + '"><div class="cell-overflow" tabindex="1" "title="' + item.user + '">' + item.user + '</div></td></tr>');
  583. });
  584. $('<tbody/>', {
  585. html: items.join('')
  586. }).insertAfter('#historyLog thead');
  587. history = $('#historyLog').DataTable({
  588. "aLengthMenu": [
  589. [100, 200, -1],
  590. [100, 200, "All"]
  591. ],
  592. "bStateSave": true,
  593. "order": [
  594. [0, "desc"]
  595. ],
  596. "pageLength": history_length
  597. });
  598. }
  599. });
  600. }
  601. function getErrors() {
  602. if (errors) {
  603. errors.destroy();
  604. $('#errorsLog').children('tbody').remove();
  605. }
  606. var items = [];
  607. $.ajax({
  608. dataType: 'json',
  609. url: 'errors',
  610. jsonp: false,
  611. beforeSend: function (xhr) {
  612. xhr.setRequestHeader('Password', getPassword());
  613. },
  614. error: function () {
  615. alertMessage('alert-error', 'Cannot receive errors');
  616. },
  617. success: function (data) {
  618. $.each(data, function (i, item) {
  619. items.push(
  620. '<tr><td data-order="' + item.ts + '">' + new Date(item.ts * 1000) + '</td>' +
  621. '<td data-order="' + item.type + '">' + item.type + '</td>' +
  622. '<td data-order="' + item.pid + '">' + item.pid + '</td>' +
  623. '<td data-order="' + item.module + '">' + item.module + '</td>' +
  624. '<td data-order="' + item.id + '">' + item.id + '</td>' +
  625. '<td data-order="' + item.message + '"><div class="cell-overflow" tabindex="1" title="' + item.message + '">' + item.message + '</div></td></tr>'
  626. );
  627. });
  628. $('<tbody/>', {
  629. html: items.join('')
  630. }).insertAfter('#errorsLog thead');
  631. errors = $('#errorsLog').DataTable({
  632. "paging": true,
  633. "orderMulti": false,
  634. "order": [
  635. [0, "desc"],
  636. ],
  637. "info": false,
  638. "columns": [
  639. {"width": "15%", "searchable": true, "orderable": true, "type": "num"},
  640. {"width": "5%", "searchable": true, "orderable": true},
  641. {"width": "5%", "searchable": true, "orderable": true},
  642. {"width": "3%", "searchable": true, "orderable": true},
  643. {"width": "3%", "searchable": true, "orderable": true},
  644. {"width": "65%", "searchable": true, "orderable": true},
  645. ],
  646. });
  647. errors.columns.adjust().draw();
  648. }
  649. });
  650. }
  651. function decimalStep(number) {
  652. var digits = ((+number).toFixed(20)).replace(/^-?\d*\.?|0+$/g, '').length;
  653. if (digits == 0 || digits > 4) {
  654. return 0.1;
  655. } else {
  656. return 1.0 / (Math.pow(10, digits));
  657. }
  658. }
  659. // @get symbols into modal form
  660. function getSymbols() {
  661. if (symbols) {
  662. symbols.destroy();
  663. symbols = null;
  664. $('#symbolsTable').children('tbody').remove();
  665. }
  666. var items = [];
  667. $.ajax({
  668. dataType: 'json',
  669. type: 'GET',
  670. url: 'symbols',
  671. jsonp: false,
  672. beforeSend: function (xhr) {
  673. xhr.setRequestHeader('Password', getPassword());
  674. },
  675. success: function (data) {
  676. $.each(data, function (i, group) {
  677. $.each(group.rules, function (i, item) {
  678. var max = 20;
  679. var min = -20;
  680. if (item.weight > max) {
  681. max = item.weight * 2;
  682. }
  683. if (item.weight < min) {
  684. min = item.weight * 2;
  685. }
  686. var label;
  687. if (item.weight < 0) {
  688. label_class = 'scorebar-ham';
  689. } else {
  690. label_class = 'scorebar-spam';
  691. }
  692. if (!item.time) {
  693. item.time = 0;
  694. }
  695. if (!item.frequency) {
  696. item.frequency = 0;
  697. }
  698. items.push('<tr>' +
  699. '<td data-order="' + group.group + '"><div class="cell-overflow" tabindex="1" title="' + group.group + '">' + group.group + '</div></td>' +
  700. '<td data-order="' + item.symbol + '"><strong>' + item.symbol + '</strong></td>' +
  701. '<td data-order="' + item.description + '"><div class="cell-overflow" tabindex="1" title="' + item.description + '">' + item.description + '</div></td>' +
  702. '<td data-order="' + item.weight + '"><input class="numeric ' + label_class +
  703. '" data-role="numerictextbox" autocomplete="off" "type="number" class="input" min="' +
  704. min + '" max="' +
  705. max + '" step="' + decimalStep(item.weight) +
  706. '" tabindex="1" value="' + Number(item.weight).toFixed(3) +
  707. '" id="_sym_' + item.symbol + '"></span></td>' +
  708. '<td data-order="' + item.frequency + '">' + item.frequency + '</td>' +
  709. '<td data-order="' + item.time + '">' + Number(item.time).toFixed(2) + 'ms</td>' +
  710. '<td><button type="button" class="btn btn-primary btn-sm ' + btn_class +
  711. '">Save</button></td></tr>');
  712. });
  713. });
  714. $('<tbody/>', {
  715. html: items.join('')
  716. }).insertAfter('#symbolsTable thead');
  717. symbols = $('#symbolsTable').DataTable({
  718. "paging": false,
  719. "orderMulti": true,
  720. "order": [
  721. [0, "asc"],
  722. [1, "asc"],
  723. [3, "desc"]
  724. ],
  725. "info": false,
  726. "columns": [
  727. {"width": "7%", "searchable": true, "orderable": true},
  728. {"width": "20%", "searchable": true, "orderable": true},
  729. {"width": "30%", "searchable": false, "orderable": false},
  730. {"width": "7%", "searchable": false, "orderable": true, "type": "num"},
  731. {"searchable": false, "orderable": true, "type": "num"},
  732. {"searchable": false, "orderable": true, "type": "num"},
  733. {"width": "5%", "searchable": false, "orderable": false, "type": "html"}
  734. ],
  735. });
  736. symbols.columns.adjust().draw();
  737. $('#symbolsTable :button').on('click',
  738. function(){saveSymbols("/savesymbols", "symbolsTable")});
  739. },
  740. error: function (data) {
  741. alertMessage('alert-modal alert-error', data.statusText);
  742. }
  743. });
  744. }
  745. // @reset history log
  746. $('#resetHistory').on('click', function () {
  747. if (!confirm("Are you sure you want to reset history log?")) {
  748. return
  749. };
  750. if (history) {
  751. history.destroy();
  752. $('#historyLog').children('tbody').remove();
  753. }
  754. $.ajax({
  755. dataType: 'json',
  756. type: 'GET',
  757. jsonp: false,
  758. url: 'historyreset',
  759. beforeSend: function (xhr) {
  760. xhr.setRequestHeader('Password', getPassword());
  761. },
  762. success: function (data) {
  763. getHistory();
  764. getErrors();
  765. },
  766. error: function (data) {
  767. alertMessage('alert-modal alert-error', data.statusText);
  768. }
  769. });
  770. });
  771. // @update history log
  772. $('#updateHistory').on('click', function () {
  773. getHistory();
  774. });
  775. $('#updateErrors').on('click', function () {
  776. getErrors();
  777. });
  778. $('#updateSymbols').on('click', function () {
  779. getSymbols();
  780. });
  781. // @upload text
  782. function uploadText(data, source, headers) {
  783. if (source === 'spam') {
  784. var url = 'learnspam';
  785. } else if (source === 'ham') {
  786. var url = 'learnham';
  787. } else if (source == 'fuzzy') {
  788. var url = 'fuzzyadd';
  789. } else if (source === 'scan') {
  790. var url = 'scan';
  791. }
  792. $.ajax({
  793. data: data,
  794. dataType: 'json',
  795. type: 'POST',
  796. url: url,
  797. processData: false,
  798. jsonp: false,
  799. beforeSend: function (xhr) {
  800. xhr.setRequestHeader('Password', getPassword());
  801. $.each(headers, function (name, value) {
  802. xhr.setRequestHeader(name, value);
  803. });
  804. },
  805. success: function (data) {
  806. cleanTextUpload(source);
  807. if (data.success) {
  808. alertMessage('alert-success', 'Data successfully uploaded');
  809. }
  810. },
  811. error: function (xhr, textStatus, errorThrown) {
  812. try {
  813. var json = $.parseJSON(xhr.responseText);
  814. var errorMsg = $('<a>').text(json.error).html();
  815. } catch (err) {
  816. var errorMsg = $('<a>').text("Error: [" + textStatus + "] " + errorThrown).html();
  817. }
  818. alertMessage('alert-error', errorMsg);
  819. }
  820. });
  821. }
  822. // @upload text
  823. function scanText(data) {
  824. var url = 'scan';
  825. var items = [];
  826. $.ajax({
  827. data: data,
  828. dataType: 'json',
  829. type: 'POST',
  830. url: url,
  831. processData: false,
  832. jsonp: false,
  833. beforeSend: function (xhr) {
  834. xhr.setRequestHeader('Password', getPassword());
  835. },
  836. success: function (input) {
  837. var data = input['default'];
  838. if (data.action) {
  839. alertMessage('alert-success', 'Data successfully scanned');
  840. if (data.action === 'clean' || 'no action') {
  841. var action = 'label-success';
  842. }
  843. if (data.action === 'rewrite subject' || 'add header' || 'probable spam') {
  844. var action = 'label-warning';
  845. }
  846. if (data.action === 'spam') {
  847. var action = 'label-danger';
  848. }
  849. if (data.score <= data.required_score) {
  850. var score = 'label-success';
  851. }
  852. if (data.score >= data.required_score) {
  853. var score = 'label-danger';
  854. }
  855. $('<tbody id="tmpBody"><tr>' +
  856. '<td><span class="label ' + action + '">' + data.action + '</span></td>' +
  857. '<td><span class="label ' + score + '">' + data.score.toFixed(2) + '/' + data.required_score.toFixed(2) + '</span></td>' +
  858. '</tr></tbody>')
  859. .insertAfter('#scanOutput thead');
  860. var sym_desc = {};
  861. var nsym = 0;
  862. $.each(data, function (i, item) {
  863. if (typeof item == 'object') {
  864. var sym_id = "sym_" + nsym;
  865. if (item.description) {
  866. sym_desc[sym_id] = item.description;
  867. }
  868. items.push('<div class="cell-overflow" tabindex="1"><abbr id="' + sym_id +
  869. '">' + item.name + '</abbr>: ' + item.score.toFixed(2) + '</div>');
  870. nsym++;
  871. }
  872. });
  873. $('<td/>', {
  874. id: 'tmpSymbols',
  875. html: items.join('')
  876. }).appendTo('#scanResult');
  877. $('#tmpSymbols').insertAfter('#tmpBody td:last').removeAttr('id');
  878. $('#tmpBody').removeAttr('id');
  879. $('#scanResult').show();
  880. // Show tooltips
  881. $.each(sym_desc, function (k, v) {
  882. $('#' + k).tooltip({
  883. "placement": "bottom",
  884. "title": v
  885. });
  886. });
  887. $('html, body').animate({
  888. scrollTop: $('#scanResult').offset().top
  889. }, 1000);
  890. } else {
  891. alertMessage('alert-error', 'Cannot scan data');
  892. }
  893. },
  894. error: function (jqXHR, textStatus, errorThrown) {
  895. alertMessage('alert-error', 'Cannot upload data: ' +
  896. textStatus + ", " + errorThrown);
  897. },
  898. statusCode: {
  899. 404: function () {
  900. alertMessage('alert-error', 'Cannot upload data, no server found');
  901. },
  902. 500: function () {
  903. alertMessage('alert-error', 'Cannot tokenize message: no text data');
  904. },
  905. 503: function () {
  906. alertMessage('alert-error', 'Cannot tokenize message: no text data');
  907. }
  908. }
  909. });
  910. }
  911. // @close scan output
  912. $('#scanClean').on('click', function () {
  913. $('#scanTextSource').val('');
  914. $('#scanResult').hide();
  915. $('#scanOutput tbody').remove();
  916. $('html, body').animate({
  917. scrollTop: 0
  918. }, 1000);
  919. });
  920. // @init upload
  921. $('[data-upload]').on('click', function () {
  922. var source = $(this).data('upload');
  923. var data;
  924. var headers = {};
  925. data = $('#' + source + 'TextSource').val();
  926. if (source == 'fuzzy') {
  927. //To access the proper
  928. headers.flag = $('#fuzzyFlagText').val();
  929. headers.weigth = $('#fuzzyWeightText').val();
  930. } else {
  931. data = $('#' + source + 'TextSource').val();
  932. }
  933. if (data.length > 0) {
  934. if (source == 'scan') {
  935. scanText(data);
  936. } else {
  937. uploadText(data, source, headers);
  938. }
  939. }
  940. return false;
  941. });
  942. // @empty textarea on upload complete
  943. function cleanTextUpload(source) {
  944. $('#' + source + 'TextSource').val('');
  945. }
  946. // @get acions
  947. function getActions() {
  948. var items = [];
  949. $.ajax({
  950. dataType: 'json',
  951. type: 'GET',
  952. url: 'actions',
  953. jsonp: false,
  954. beforeSend: function (xhr) {
  955. xhr.setRequestHeader('Password', getPassword());
  956. },
  957. success: function (data) {
  958. // Order of sliders greylist -> probable spam -> spam
  959. $('#actionsBody').empty();
  960. $('#actionsForm').empty();
  961. var items = [];
  962. var min = 0;
  963. var max = Number.MIN_VALUE;
  964. $.each(data, function (i, item) {
  965. var idx = -1;
  966. var label;
  967. if (item.action === 'add header') {
  968. label = 'Probably Spam';
  969. idx = 1;
  970. } else if (item.action === 'greylist') {
  971. label = 'Greylist';
  972. idx = 0;
  973. } else if (item.action === 'rewrite subject') {
  974. label = 'Rewrite subject';
  975. idx = 2;
  976. } else if (item.action === 'reject') {
  977. label = 'Spam';
  978. idx = 3;
  979. }
  980. if (idx >= 0) {
  981. items.push({
  982. idx: idx,
  983. html: '<div class="form-group">' +
  984. '<label class="control-label col-sm-2">' + label + '</label>' +
  985. '<div class="controls slider-controls col-sm-10">' +
  986. '<input class="slider" type="slider" value="' + item.value + '">' +
  987. '</div>' +
  988. '</div>'
  989. });
  990. }
  991. if (item.value > max) {
  992. max = item.value * 2;
  993. }
  994. if (item.value < min) {
  995. min = item.value;
  996. }
  997. });
  998. items.sort(function (a, b) {
  999. return a.idx - b.idx;
  1000. });
  1001. $('#actionsBody').html('<form id="actionsForm">' +
  1002. items.map(function (e) {
  1003. return e.html;
  1004. }).join('') +
  1005. '<br><div class="form-group">' +
  1006. '<button class="btn btn-primary ' + btn_class +
  1007. '" type="submit">Save actions</button></div></form>');
  1008. }
  1009. });
  1010. }
  1011. // @upload edited actions
  1012. $(document).on('submit', '#actionsForm', function () {
  1013. var inputs = $('#actionsForm :input[type="slider"]');
  1014. var url = 'saveactions';
  1015. var values = [];
  1016. // Rspamd order: [spam,probable_spam,greylist]
  1017. values[0] = parseFloat(inputs[2].value);
  1018. values[1] = parseFloat(inputs[1].value);
  1019. values[2] = parseFloat(inputs[0].value);
  1020. $.ajax({
  1021. data: JSON.stringify(values),
  1022. dataType: 'json',
  1023. type: 'POST',
  1024. url: url,
  1025. jsonp: false,
  1026. beforeSend: function (xhr) {
  1027. xhr.setRequestHeader('Password', getPassword());
  1028. },
  1029. success: function () {
  1030. alertMessage('alert-success', 'Actions successfully saved');
  1031. },
  1032. error: function (data) {
  1033. alertMessage('alert-modal alert-error', data.statusText);
  1034. }
  1035. });
  1036. return false;
  1037. });
  1038. // @catch changes of file upload form
  1039. $(window).resize(function (e) {
  1040. var form = $(this).attr('id');
  1041. var height = $(form).height();
  1042. });
  1043. // @watch textarea changes
  1044. $('textarea').change(function () {
  1045. if ($(this).val().length != '') {
  1046. $(this).closest('form').find('button').removeAttr('disabled').removeClass('disabled');
  1047. } else {
  1048. $(this).closest('form').find('button').attr('disabled').addClass('disabled');
  1049. }
  1050. });
  1051. // @save forms from modal
  1052. $(document).on('click', '#modalSave', function () {
  1053. var form = $('#modalBody').children().filter(':visible');
  1054. // var map = $(form).data('map');
  1055. // var type = $(form).data('type');
  1056. var action = $(form).attr('action');
  1057. var id = $(form).attr('id');
  1058. var type = $(form).data('type');
  1059. if (type === 'symbols') {
  1060. saveSymbols(action, id);
  1061. } else if (type === 'map') {
  1062. saveMap(action, id);
  1063. }
  1064. });
  1065. // @upload map from modal
  1066. function saveMap(action, id) {
  1067. var data = $('#' + id).find('textarea').val();
  1068. $.ajax({
  1069. data: data,
  1070. dataType: 'text',
  1071. type: 'POST',
  1072. jsonp: false,
  1073. url: action,
  1074. beforeSend: function (xhr) {
  1075. xhr.setRequestHeader('Password', getPassword());
  1076. xhr.setRequestHeader('Map', id);
  1077. xhr.setRequestHeader('Debug', true);
  1078. },
  1079. error: function (data) {
  1080. alertMessage('alert-modal alert-error', data.statusText);
  1081. },
  1082. success: function (data) {
  1083. alertMessage('alert-modal alert-success', 'Map data successfully saved');
  1084. $('#modalDialog').modal('hide');
  1085. }
  1086. });
  1087. }
  1088. // @upload symbols from modal
  1089. function saveSymbols(action, id) {
  1090. var inputs = $('#' + id + ' :input[data-role="numerictextbox"]');
  1091. var url = action;
  1092. var values = [];
  1093. $(inputs).each(function () {
  1094. values.push({
  1095. name: $(this).attr('id').substring(5),
  1096. value: parseFloat($(this).val())
  1097. });
  1098. });
  1099. $.ajax({
  1100. data: JSON.stringify(values),
  1101. dataType: 'json',
  1102. type: 'POST',
  1103. url: url,
  1104. jsonp: false,
  1105. beforeSend: function (xhr) {
  1106. xhr.setRequestHeader('Password', getPassword());
  1107. },
  1108. success: function () {
  1109. alertMessage('alert-modal alert-success', 'Symbols successfully saved');
  1110. },
  1111. error: function (data) {
  1112. alertMessage('alert-modal alert-error', data.statusText);
  1113. }
  1114. });
  1115. $('#modalDialog').modal('hide');
  1116. return false;
  1117. }
  1118. // @connect to server
  1119. function connectRSPAMD() {
  1120. if (isLogged()) {
  1121. displayUI();
  1122. return;
  1123. }
  1124. var nav = $('#navBar');
  1125. var ui = $('#mainUI');
  1126. var dialog = $('#connectDialog');
  1127. var backdrop = $('#backDrop');
  1128. var disconnect = $('#navBar .pull-right');
  1129. $(ui).hide();
  1130. $(dialog).show();
  1131. $('#connectHost').focus();
  1132. $(backdrop).show();
  1133. $('#connectForm').one('submit', function (e) {
  1134. e.preventDefault();
  1135. var password = $('#connectPassword').val();
  1136. document.getElementById('connectPassword').value = '';
  1137. $.ajax({
  1138. global: false,
  1139. jsonp: false,
  1140. dataType: 'json',
  1141. type: 'GET',
  1142. url: 'auth',
  1143. beforeSend: function (xhr) {
  1144. xhr.setRequestHeader('Password', password);
  1145. },
  1146. success: function (data) {
  1147. if (data.auth === 'failed') {
  1148. $(form).each(function () {
  1149. $('.form-group').addClass('error');
  1150. });
  1151. } else {
  1152. if (data.read_only) {
  1153. read_only = true;
  1154. btn_class = "disabled";
  1155. $('#learning_nav').parent().addClass('disabled');
  1156. }
  1157. else {
  1158. read_only = false;
  1159. btn_class = "";
  1160. $('#learning_nav').parent().removeClass('disabled')
  1161. }
  1162. saveCredentials(data, password);
  1163. $(dialog).hide();
  1164. $(backdrop).hide();
  1165. displayUI();
  1166. }
  1167. },
  1168. error: function (data) {
  1169. alertMessage('alert-modal alert-error', data.statusText);
  1170. }
  1171. });
  1172. });
  1173. }
  1174. function displayUI() {
  1175. // @toggle auth and main
  1176. var disconnect = $('#navBar .pull-right');
  1177. statWidgets();
  1178. $('#mainUI').show();
  1179. $('#progress').show();
  1180. getChart();
  1181. initGraph();
  1182. $('#progress').hide();
  1183. $(disconnect).show();
  1184. }
  1185. connectRSPAMD();
  1186. $('#configuration_nav').bind('click', function (e) {
  1187. getActions();
  1188. getMaps();
  1189. });
  1190. $(document).ajaxStart(function () {
  1191. $('#navBar').addClass('loading');
  1192. });
  1193. $(document).ajaxComplete(function () {
  1194. $('#navBar').removeClass('loading');
  1195. });
  1196. $('#status_nav').bind('click', function (e) {
  1197. getChart();
  1198. });
  1199. $('#throughput_nav').bind('click', function () {
  1200. getGraphData(selected.selData);
  1201. });
  1202. $('#history_nav').bind('click', function () {
  1203. getHistory();
  1204. getErrors();
  1205. });
  1206. $('#symbols_nav').bind('click', function () {
  1207. getSymbols();
  1208. });
  1209. });
  1210. })();