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 48KB

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