Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

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