debug.js 51 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615
  1. const LOCALE = undefined;
  2. const DATETIME_FORMAT = {
  3. year: "numeric",
  4. month: "2-digit",
  5. day: "2-digit",
  6. hour: "2-digit",
  7. minute: "2-digit",
  8. second: "2-digit"
  9. };
  10. $(document).ready(function() {
  11. // Parse seconds ago to date
  12. // Get "now" timestamp
  13. var ts_now = Math.round((new Date()).getTime() / 1000);
  14. $('.parse_s_ago').each(function(i, parse_s_ago) {
  15. var started_s_ago = parseInt($(this).text(), 10);
  16. if (typeof started_s_ago != 'NaN') {
  17. var started_date = new Date((ts_now - started_s_ago) * 1000);
  18. if (started_date instanceof Date && !isNaN(started_date)) {
  19. var started_local_date = started_date.toLocaleDateString(LOCALE, DATETIME_FORMAT);
  20. $(this).text(started_local_date);
  21. } else {
  22. $(this).text('-');
  23. }
  24. }
  25. });
  26. // Parse general dates
  27. $('.parse_date').each(function(i, parse_date) {
  28. var started_date = new Date(Date.parse($(this).text()));
  29. if (typeof started_date != 'NaN') {
  30. var started_local_date = started_date.toLocaleDateString(LOCALE, DATETIME_FORMAT);
  31. $(this).text(started_local_date);
  32. }
  33. });
  34. // set update loop container list
  35. containersToUpdate = {}
  36. // set default ChartJs Font Color
  37. Chart.defaults.color = '#999';
  38. // create host cpu and mem charts
  39. createHostCpuAndMemChart();
  40. // check for new version
  41. if (mailcow_info.branch === "master"){
  42. check_update(mailcow_info.version_tag, mailcow_info.project_url);
  43. }
  44. $("#maiclow_version").click(function(){
  45. if (mailcow_cc_role !== "admin" && mailcow_cc_role !== "domainadmin" ||
  46. mailcow_info.branch !== "master")
  47. return;
  48. showVersionModal("Version " + mailcow_info.version_tag, mailcow_info.version_tag);
  49. })
  50. // get public ips
  51. $("#host_show_ip").click(function(){
  52. $("#host_show_ip").find(".text").addClass("d-none");
  53. $("#host_show_ip").find(".spinner-border").removeClass("d-none");
  54. window.fetch("/api/v1/get/status/host/ip", { method:'GET', cache:'no-cache' }).then(function(response) {
  55. return response.json();
  56. }).then(function(data) {
  57. console.log(data);
  58. // display host ips
  59. if (data.ipv4)
  60. $("#host_ipv4").text(data.ipv4);
  61. if (data.ipv6)
  62. $("#host_ipv6").text(data.ipv6);
  63. $("#host_show_ip").addClass("d-none");
  64. $("#host_show_ip").find(".text").removeClass("d-none");
  65. $("#host_show_ip").find(".spinner-border").addClass("d-none");
  66. $("#host_ipv4").removeClass("d-none");
  67. $("#host_ipv6").removeClass("d-none");
  68. $("#host_ipv6").removeClass("text-danger");
  69. $("#host_ipv4").addClass("d-block");
  70. $("#host_ipv6").addClass("d-block");
  71. }).catch(function(error){
  72. console.log(error);
  73. $("#host_ipv6").removeClass("d-none");
  74. $("#host_ipv6").addClass("d-block");
  75. $("#host_ipv6").addClass("text-danger");
  76. $("#host_ipv6").text(lang_debug.error_show_ip);
  77. $("#host_show_ip").find(".text").removeClass("d-none");
  78. $("#host_show_ip").find(".spinner-border").addClass("d-none");
  79. });
  80. });
  81. update_container_stats();
  82. });
  83. jQuery(function($){
  84. if (localStorage.getItem("current_page") === null) {
  85. var current_page = {};
  86. } else {
  87. var current_page = JSON.parse(localStorage.getItem('current_page'));
  88. }
  89. // http://stackoverflow.com/questions/24816/escaping-html-strings-with-jquery
  90. var entityMap={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","/":"&#x2F;","`":"&#x60;","=":"&#x3D;"};
  91. function escapeHtml(n){return String(n).replace(/[&<>"'`=\/]/g,function(n){return entityMap[n]})}
  92. function humanFileSize(i){if(Math.abs(i)<1024)return i+" B";var B=["KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"],e=-1;do{i/=1024,++e}while(Math.abs(i)>=1024&&e<B.length-1);return i.toFixed(1)+" "+B[e]}
  93. function hashCode(t){for(var n=0,r=0;r<t.length;r++)n=t.charCodeAt(r)+((n<<5)-n);return n}
  94. function intToRGB(t){var n=(16777215&t).toString(16).toUpperCase();return"00000".substring(0,6-n.length)+n}
  95. $(".refresh_table").on('click', function(e) {
  96. e.preventDefault();
  97. var table_name = $(this).data('table');
  98. $('#' + table_name).DataTable().ajax.reload();
  99. });
  100. function createSortableDate(td, cellData) {
  101. $(td).attr({
  102. "data-order": cellData,
  103. "data-sort": cellData
  104. });
  105. $(td).html(convertTimestampToLocalFormat(cellData));
  106. }
  107. function draw_autodiscover_logs() {
  108. // just recalc width if instance already exists
  109. if ($.fn.DataTable.isDataTable('#autodiscover_log') ) {
  110. $('#autodiscover_log').DataTable().columns.adjust().responsive.recalc();
  111. return;
  112. }
  113. $('#autodiscover_log').DataTable({
  114. responsive: true,
  115. processing: true,
  116. serverSide: false,
  117. stateSave: true,
  118. dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
  119. "tr" +
  120. "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
  121. language: lang_datatables,
  122. order: [[0, 'desc']],
  123. ajax: {
  124. type: "GET",
  125. url: "/api/v1/get/logs/autodiscover/100",
  126. dataSrc: function(data){
  127. return process_table_data(data, 'autodiscover_log');
  128. }
  129. },
  130. columns: [
  131. {
  132. title: lang.time,
  133. data: 'time',
  134. defaultContent: '',
  135. responsivePriority: 1,
  136. createdCell: function(td, cellData) {
  137. createSortableDate(td, cellData)
  138. }
  139. },
  140. {
  141. title: 'User-Agent',
  142. data: 'ua',
  143. defaultContent: '',
  144. className: 'dtr-col-md',
  145. responsivePriority: 5
  146. },
  147. {
  148. title: 'Username',
  149. data: 'user',
  150. defaultContent: '',
  151. responsivePriority: 4
  152. },
  153. {
  154. title: 'IP',
  155. data: 'ip',
  156. defaultContent: '',
  157. responsivePriority: 2
  158. },
  159. {
  160. title: 'Service',
  161. data: 'service',
  162. defaultContent: '',
  163. responsivePriority: 3
  164. }
  165. ]
  166. });
  167. }
  168. function draw_postfix_logs() {
  169. // just recalc width if instance already exists
  170. if ($.fn.DataTable.isDataTable('#postfix_log') ) {
  171. $('#postfix_log').DataTable().columns.adjust().responsive.recalc();
  172. return;
  173. }
  174. $('#postfix_log').DataTable({
  175. responsive: true,
  176. processing: true,
  177. serverSide: false,
  178. stateSave: true,
  179. dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
  180. "tr" +
  181. "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
  182. language: lang_datatables,
  183. order: [[0, 'desc']],
  184. ajax: {
  185. type: "GET",
  186. url: "/api/v1/get/logs/postfix",
  187. dataSrc: function(data){
  188. return process_table_data(data, 'general_syslog');
  189. }
  190. },
  191. columns: [
  192. {
  193. title: lang.time,
  194. data: 'time',
  195. defaultContent: '',
  196. createdCell: function(td, cellData) {
  197. createSortableDate(td, cellData)
  198. }
  199. },
  200. {
  201. title: lang.priority,
  202. data: 'priority',
  203. defaultContent: ''
  204. },
  205. {
  206. title: lang.message,
  207. data: 'message',
  208. defaultContent: '',
  209. className: 'dtr-col-md text-break'
  210. }
  211. ]
  212. });
  213. }
  214. function draw_watchdog_logs() {
  215. // just recalc width if instance already exists
  216. if ($.fn.DataTable.isDataTable('#watchdog_log') ) {
  217. $('#watchdog_log').DataTable().columns.adjust().responsive.recalc();
  218. return;
  219. }
  220. $('#watchdog_log').DataTable({
  221. responsive: true,
  222. processing: true,
  223. serverSide: false,
  224. stateSave: true,
  225. dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
  226. "tr" +
  227. "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
  228. language: lang_datatables,
  229. order: [[0, 'desc']],
  230. ajax: {
  231. type: "GET",
  232. url: "/api/v1/get/logs/watchdog",
  233. dataSrc: function(data){
  234. return process_table_data(data, 'watchdog');
  235. }
  236. },
  237. columns: [
  238. {
  239. title: lang.time,
  240. data: 'time',
  241. defaultContent: '',
  242. createdCell: function(td, cellData) {
  243. createSortableDate(td, cellData)
  244. }
  245. },
  246. {
  247. title: 'Service',
  248. data: 'service',
  249. defaultContent: ''
  250. },
  251. {
  252. title: 'Trend',
  253. data: 'trend',
  254. defaultContent: ''
  255. },
  256. {
  257. title: lang.message,
  258. data: 'message',
  259. defaultContent: ''
  260. }
  261. ]
  262. });
  263. }
  264. function draw_api_logs() {
  265. // just recalc width if instance already exists
  266. if ($.fn.DataTable.isDataTable('#api_log') ) {
  267. $('#api_log').DataTable().columns.adjust().responsive.recalc();
  268. return;
  269. }
  270. $('#api_log').DataTable({
  271. responsive: true,
  272. processing: true,
  273. serverSide: false,
  274. stateSave: true,
  275. dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
  276. "tr" +
  277. "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
  278. language: lang_datatables,
  279. order: [[0, 'desc']],
  280. ajax: {
  281. type: "GET",
  282. url: "/api/v1/get/logs/api",
  283. dataSrc: function(data){
  284. return process_table_data(data, 'apilog');
  285. }
  286. },
  287. columns: [
  288. {
  289. title: lang.time,
  290. data: 'time',
  291. defaultContent: '',
  292. createdCell: function(td, cellData) {
  293. createSortableDate(td, cellData)
  294. }
  295. },
  296. {
  297. title: 'URI',
  298. data: 'uri',
  299. defaultContent: '',
  300. className: 'dtr-col-md dtr-break-all'
  301. },
  302. {
  303. title: 'Method',
  304. data: 'method',
  305. defaultContent: ''
  306. },
  307. {
  308. title: 'IP',
  309. data: 'remote',
  310. defaultContent: ''
  311. },
  312. {
  313. title: 'Data',
  314. data: 'data',
  315. defaultContent: '',
  316. className: 'dtr-col-md dtr-break-all'
  317. }
  318. ]
  319. });
  320. }
  321. function draw_rl_logs() {
  322. // just recalc width if instance already exists
  323. if ($.fn.DataTable.isDataTable('#rl_log') ) {
  324. $('#rl_log').DataTable().columns.adjust().responsive.recalc();
  325. return;
  326. }
  327. $('#rl_log').DataTable({
  328. responsive: true,
  329. processing: true,
  330. serverSide: false,
  331. stateSave: true,
  332. dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
  333. "tr" +
  334. "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
  335. language: lang_datatables,
  336. order: [[0, 'desc']],
  337. ajax: {
  338. type: "GET",
  339. url: "/api/v1/get/logs/ratelimited",
  340. dataSrc: function(data){
  341. return process_table_data(data, 'rllog');
  342. }
  343. },
  344. columns: [
  345. {
  346. title: ' ',
  347. data: 'indicator',
  348. defaultContent: ''
  349. },
  350. {
  351. title: lang.time,
  352. data: 'time',
  353. defaultContent: '',
  354. createdCell: function(td, cellData) {
  355. createSortableDate(td, cellData)
  356. }
  357. },
  358. {
  359. title: lang.rate_name,
  360. data: 'rl_name',
  361. defaultContent: ''
  362. },
  363. {
  364. title: lang.sender,
  365. data: 'from',
  366. defaultContent: ''
  367. },
  368. {
  369. title: lang.recipients,
  370. data: 'rcpt',
  371. defaultContent: ''
  372. },
  373. {
  374. title: lang.authed_user,
  375. data: 'user',
  376. defaultContent: ''
  377. },
  378. {
  379. title: 'Msg ID',
  380. data: 'message_id',
  381. defaultContent: ''
  382. },
  383. {
  384. title: 'Header From',
  385. data: 'header_from',
  386. defaultContent: ''
  387. },
  388. {
  389. title: 'Subject',
  390. data: 'header_subject',
  391. defaultContent: ''
  392. },
  393. {
  394. title: 'Hash',
  395. data: 'rl_hash',
  396. defaultContent: ''
  397. },
  398. {
  399. title: 'Rspamd QID',
  400. data: 'qid',
  401. defaultContent: ''
  402. },
  403. {
  404. title: 'IP',
  405. data: 'ip',
  406. defaultContent: ''
  407. },
  408. {
  409. title: lang.action,
  410. data: 'action',
  411. defaultContent: ''
  412. }
  413. ]
  414. });
  415. }
  416. function draw_ui_logs() {
  417. // just recalc width if instance already exists
  418. if ($.fn.DataTable.isDataTable('#ui_logs') ) {
  419. $('#ui_logs').DataTable().columns.adjust().responsive.recalc();
  420. return;
  421. }
  422. $('#ui_logs').DataTable({
  423. responsive: true,
  424. processing: true,
  425. serverSide: false,
  426. stateSave: true,
  427. dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
  428. "tr" +
  429. "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
  430. language: lang_datatables,
  431. order: [[0, 'desc']],
  432. ajax: {
  433. type: "GET",
  434. url: "/api/v1/get/logs/ui",
  435. dataSrc: function(data){
  436. return process_table_data(data, 'mailcow_ui');
  437. }
  438. },
  439. columns: [
  440. {
  441. title: lang.time,
  442. data: 'time',
  443. defaultContent: '',
  444. createdCell: function(td, cellData) {
  445. createSortableDate(td, cellData)
  446. }
  447. },
  448. {
  449. title: 'Type',
  450. data: 'type',
  451. defaultContent: ''
  452. },
  453. {
  454. title: 'Task',
  455. data: 'task',
  456. defaultContent: ''
  457. },
  458. {
  459. title: 'User',
  460. data: 'user',
  461. defaultContent: '',
  462. className: 'dtr-col-sm'
  463. },
  464. {
  465. title: 'Role',
  466. data: 'role',
  467. defaultContent: '',
  468. className: 'dtr-col-sm'
  469. },
  470. {
  471. title: 'IP',
  472. data: 'remote',
  473. defaultContent: '',
  474. className: 'dtr-col-md dtr-break-all'
  475. },
  476. {
  477. title: lang.message,
  478. data: 'msg',
  479. defaultContent: '',
  480. className: 'dtr-col-md dtr-break-all'
  481. },
  482. {
  483. title: 'Call',
  484. data: 'call',
  485. defaultContent: '',
  486. className: 'none dtr-col-md dtr-break-all'
  487. }
  488. ]
  489. });
  490. }
  491. function draw_sasl_logs() {
  492. // just recalc width if instance already exists
  493. if ($.fn.DataTable.isDataTable('#sasl_logs') ) {
  494. $('#sasl_logs').DataTable().columns.adjust().responsive.recalc();
  495. return;
  496. }
  497. $('#sasl_logs').DataTable({
  498. responsive: true,
  499. processing: true,
  500. serverSide: false,
  501. stateSave: true,
  502. dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
  503. "tr" +
  504. "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
  505. language: lang_datatables,
  506. order: [[0, 'desc']],
  507. ajax: {
  508. type: "GET",
  509. url: "/api/v1/get/logs/sasl",
  510. dataSrc: function(data){
  511. return process_table_data(data, 'sasl_log_table');
  512. }
  513. },
  514. columns: [
  515. {
  516. title: lang.username,
  517. data: 'username',
  518. defaultContent: ''
  519. },
  520. {
  521. title: lang.service,
  522. data: 'service',
  523. defaultContent: ''
  524. },
  525. {
  526. title: 'IP',
  527. data: 'real_rip',
  528. defaultContent: '',
  529. className: 'dtr-col-md text-break'
  530. },
  531. {
  532. title: lang.login_time,
  533. data: 'datetime',
  534. defaultContent: '',
  535. createdCell: function(td, cellData) {
  536. cellData = Math.floor((new Date(data.replace(/-/g, "/"))).getTime() / 1000);
  537. createSortableDate(td, cellData)
  538. }
  539. }
  540. ]
  541. });
  542. }
  543. function draw_acme_logs() {
  544. // just recalc width if instance already exists
  545. if ($.fn.DataTable.isDataTable('#acme_log') ) {
  546. $('#acme_log').DataTable().columns.adjust().responsive.recalc();
  547. return;
  548. }
  549. $('#acme_log').DataTable({
  550. responsive: true,
  551. processing: true,
  552. serverSide: false,
  553. stateSave: true,
  554. dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
  555. "tr" +
  556. "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
  557. language: lang_datatables,
  558. order: [[0, 'desc']],
  559. ajax: {
  560. type: "GET",
  561. url: "/api/v1/get/logs/acme",
  562. dataSrc: function(data){
  563. return process_table_data(data, 'general_syslog');
  564. }
  565. },
  566. columns: [
  567. {
  568. title: lang.time,
  569. data: 'time',
  570. defaultContent: '',
  571. createdCell: function(td, cellData) {
  572. createSortableDate(td, cellData)
  573. }
  574. },
  575. {
  576. title: lang.message,
  577. data: 'message',
  578. defaultContent: '',
  579. className: 'dtr-col-md dtr-break-all'
  580. }
  581. ]
  582. });
  583. }
  584. function draw_netfilter_logs() {
  585. // just recalc width if instance already exists
  586. if ($.fn.DataTable.isDataTable('#netfilter_log') ) {
  587. $('#netfilter_log').DataTable().columns.adjust().responsive.recalc();
  588. return;
  589. }
  590. $('#netfilter_log').DataTable({
  591. responsive: true,
  592. processing: true,
  593. serverSide: false,
  594. stateSave: true,
  595. dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
  596. "tr" +
  597. "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
  598. language: lang_datatables,
  599. order: [[0, 'desc']],
  600. ajax: {
  601. type: "GET",
  602. url: "/api/v1/get/logs/netfilter",
  603. dataSrc: function(data){
  604. return process_table_data(data, 'general_syslog');
  605. }
  606. },
  607. columns: [
  608. {
  609. title: lang.time,
  610. data: 'time',
  611. defaultContent: '',
  612. createdCell: function(td, cellData) {
  613. createSortableDate(td, cellData)
  614. }
  615. },
  616. {
  617. title: lang.priority,
  618. data: 'priority',
  619. defaultContent: ''
  620. },
  621. {
  622. title: lang.message,
  623. data: 'message',
  624. defaultContent: '',
  625. className: 'dtr-col-md text-break'
  626. }
  627. ]
  628. });
  629. }
  630. function draw_sogo_logs() {
  631. // just recalc width if instance already exists
  632. if ($.fn.DataTable.isDataTable('#sogo_log') ) {
  633. $('#sogo_log').DataTable().columns.adjust().responsive.recalc();
  634. return;
  635. }
  636. $('#sogo_log').DataTable({
  637. responsive: true,
  638. processing: true,
  639. serverSide: false,
  640. stateSave: true,
  641. dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
  642. "tr" +
  643. "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
  644. language: lang_datatables,
  645. order: [[0, 'desc']],
  646. ajax: {
  647. type: "GET",
  648. url: "/api/v1/get/logs/sogo",
  649. dataSrc: function(data){
  650. return process_table_data(data, 'general_syslog');
  651. }
  652. },
  653. columns: [
  654. {
  655. title: lang.time,
  656. data: 'time',
  657. defaultContent: '',
  658. createdCell: function(td, cellData) {
  659. createSortableDate(td, cellData)
  660. }
  661. },
  662. {
  663. title: lang.priority,
  664. data: 'priority',
  665. defaultContent: ''
  666. },
  667. {
  668. title: lang.message,
  669. data: 'message',
  670. defaultContent: '',
  671. className: 'dtr-col-md text-break'
  672. }
  673. ]
  674. });
  675. }
  676. function draw_dovecot_logs() {
  677. // just recalc width if instance already exists
  678. if ($.fn.DataTable.isDataTable('#dovecot_log') ) {
  679. $('#dovecot_log').DataTable().columns.adjust().responsive.recalc();
  680. return;
  681. }
  682. $('#dovecot_log').DataTable({
  683. responsive: true,
  684. processing: true,
  685. serverSide: false,
  686. stateSave: true,
  687. dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
  688. "tr" +
  689. "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
  690. language: lang_datatables,
  691. order: [[0, 'desc']],
  692. ajax: {
  693. type: "GET",
  694. url: "/api/v1/get/logs/dovecot",
  695. dataSrc: function(data){
  696. return process_table_data(data, 'general_syslog');
  697. }
  698. },
  699. columns: [
  700. {
  701. title: lang.time,
  702. data: 'time',
  703. defaultContent: '',
  704. createdCell: function(td, cellData) {
  705. createSortableDate(td, cellData)
  706. }
  707. },
  708. {
  709. title: lang.priority,
  710. data: 'priority',
  711. defaultContent: ''
  712. },
  713. {
  714. title: lang.message,
  715. data: 'message',
  716. defaultContent: '',
  717. className: 'dtr-col-md text-break'
  718. }
  719. ]
  720. });
  721. }
  722. function rspamd_pie_graph() {
  723. $.ajax({
  724. url: '/api/v1/get/rspamd/actions',
  725. async: true,
  726. success: function(data){
  727. console.log(data);
  728. var total = 0;
  729. $(data).map(function(){total += this[1];});
  730. var labels = $.makeArray($(data).map(function(){return this[0] + ' ' + Math.round(this[1]/total * 100) + '%';}));
  731. var values = $.makeArray($(data).map(function(){return this[1];}));
  732. console.log(values);
  733. var graphdata = {
  734. labels: labels,
  735. datasets: [{
  736. data: values,
  737. backgroundColor: ['#DC3023', '#59ABE3', '#FFA400', '#FFA400', '#26A65B']
  738. }]
  739. };
  740. var options = {
  741. responsive: true,
  742. maintainAspectRatio: false,
  743. plugins: {
  744. datalabels: {
  745. color: '#FFF',
  746. font: {
  747. weight: 'bold'
  748. },
  749. display: function(context) {
  750. return context.dataset.data[context.dataIndex] !== 0;
  751. },
  752. formatter: function(value, context) {
  753. return Math.round(value/total*100) + '%';
  754. }
  755. }
  756. }
  757. };
  758. var chartcanvas = document.getElementById('rspamd_donut');
  759. Chart.register('ChartDataLabels');
  760. if(typeof chart == 'undefined') {
  761. chart = new Chart(chartcanvas.getContext("2d"), {
  762. plugins: [ChartDataLabels],
  763. type: 'doughnut',
  764. data: graphdata,
  765. options: options
  766. });
  767. }
  768. else {
  769. chart.destroy();
  770. chart = new Chart(chartcanvas.getContext("2d"), {
  771. plugins: [ChartDataLabels],
  772. type: 'doughnut',
  773. data: graphdata,
  774. options: options
  775. });
  776. }
  777. }
  778. });
  779. }
  780. function draw_rspamd_history() {
  781. // just recalc width if instance already exists
  782. if ($.fn.DataTable.isDataTable('#rspamd_history') ) {
  783. $('#rspamd_history').DataTable().columns.adjust().responsive.recalc();
  784. return;
  785. }
  786. $('#rspamd_history').DataTable({
  787. responsive: true,
  788. processing: true,
  789. serverSide: false,
  790. stateSave: true,
  791. dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
  792. "tr" +
  793. "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
  794. language: lang_datatables,
  795. order: [[0, 'desc']],
  796. ajax: {
  797. type: "GET",
  798. url: "/api/v1/get/logs/rspamd-history",
  799. dataSrc: function(data){
  800. return process_table_data(data, 'rspamd_history');
  801. }
  802. },
  803. columns: [
  804. {
  805. title: lang.time,
  806. data: 'unix_time',
  807. defaultContent: '',
  808. createdCell: function(td, cellData) {
  809. createSortableDate(td, cellData)
  810. }
  811. },
  812. {
  813. title: 'IP address',
  814. data: 'ip',
  815. defaultContent: ''
  816. },
  817. {
  818. title: 'From',
  819. data: 'sender_mime',
  820. defaultContent: ''
  821. },
  822. {
  823. title: 'To',
  824. data: 'rcpt',
  825. defaultContent: ''
  826. },
  827. {
  828. title: 'Subject',
  829. data: 'subject',
  830. defaultContent: ''
  831. },
  832. {
  833. title: 'Action',
  834. data: 'action',
  835. defaultContent: ''
  836. },
  837. {
  838. title: 'Score',
  839. data: 'score',
  840. defaultContent: '',
  841. createdCell: function(td, cellData) {
  842. $(td).attr({
  843. "data-order": cellData.sortBy,
  844. "data-sort": cellData.sortBy
  845. });
  846. $(td).html(cellData.value);
  847. }
  848. },
  849. {
  850. title: 'Symbols',
  851. data: 'symbols',
  852. defaultContent: '',
  853. className: 'none dtr-col-md'
  854. },
  855. {
  856. title: 'Msg size',
  857. data: 'size',
  858. defaultContent: ''
  859. },
  860. {
  861. title: 'Scan Time',
  862. data: 'scan_time',
  863. defaultContent: '',
  864. createdCell: function(td, cellData) {
  865. $(td).attr({
  866. "data-order": cellData.sortBy,
  867. "data-sort": cellData.sortBy
  868. });
  869. $(td).html(cellData.value);
  870. }
  871. },
  872. {
  873. title: 'ID',
  874. data: 'message-id',
  875. defaultContent: ''
  876. },
  877. {
  878. title: 'Authenticated user',
  879. data: 'user',
  880. defaultContent: ''
  881. }
  882. ]
  883. });
  884. }
  885. function process_table_data(data, table) {
  886. if (table == 'rspamd_history') {
  887. $.each(data, function (i, item) {
  888. if (item.rcpt_mime != "") {
  889. item.rcpt = escapeHtml(item.rcpt_mime.join(", "));
  890. }
  891. else {
  892. item.rcpt = escapeHtml(item.rcpt_smtp.join(", "));
  893. }
  894. item.symbols = Object.keys(item.symbols).sort(function (a, b) {
  895. if (item.symbols[a].score === 0) return 1
  896. if (item.symbols[b].score === 0) return -1
  897. if (item.symbols[b].score < 0 && item.symbols[a].score < 0) {
  898. return item.symbols[a].score - item.symbols[b].score
  899. }
  900. if (item.symbols[b].score > 0 && item.symbols[a].score > 0) {
  901. return item.symbols[b].score - item.symbols[a].score
  902. }
  903. return item.symbols[b].score - item.symbols[a].score
  904. }).map(function(key) {
  905. var sym = item.symbols[key];
  906. if (sym.score < 0) {
  907. sym.score_formatted = '(<span class="text-success"><b>' + sym.score + '</b></span>)'
  908. }
  909. else if (sym.score === 0) {
  910. sym.score_formatted = '(<span><b>' + sym.score + '</b></span>)'
  911. }
  912. else {
  913. sym.score_formatted = '(<span class="text-danger"><b>' + sym.score + '</b></span>)'
  914. }
  915. var str = '<strong>' + key + '</strong> ' + sym.score_formatted;
  916. if (sym.options) {
  917. str += ' [' + escapeHtml(sym.options.join(", ")) + "]";
  918. }
  919. return str
  920. }).join('<br>\n');
  921. item.subject = escapeHtml(item.subject);
  922. var scan_time = item.time_real.toFixed(3);
  923. if (item.time_virtual) {
  924. scan_time += ' / ' + item.time_virtual.toFixed(3);
  925. }
  926. item.scan_time = {
  927. "sortBy": item.time_real,
  928. "value": scan_time
  929. };
  930. if (item.action === 'clean' || item.action === 'no action') {
  931. item.action = "<div class='badge fs-6 bg-success'>" + item.action + "</div>";
  932. } else if (item.action === 'rewrite subject' || item.action === 'add header' || item.action === 'probable spam') {
  933. item.action = "<div class='badge fs-6 bg-warning'>" + item.action + "</div>";
  934. } else if (item.action === 'spam' || item.action === 'reject') {
  935. item.action = "<div class='badge fs-6 bg-danger'>" + item.action + "</div>";
  936. } else {
  937. item.action = "<div class='badge fs-6 bg-info'>" + item.action + "</div>";
  938. }
  939. var score_content;
  940. if (item.score < item.required_score) {
  941. score_content = "[ <span class='text-success'>" + item.score.toFixed(2) + " / " + item.required_score + "</span> ]";
  942. } else {
  943. score_content = "[ <span class='text-danger'>" + item.score.toFixed(2) + " / " + item.required_score + "</span> ]";
  944. }
  945. item.score = {
  946. "sortBy": item.score,
  947. "value": score_content
  948. };
  949. if (item.user == null) {
  950. item.user = "none";
  951. }
  952. });
  953. } else if (table == 'autodiscover_log') {
  954. $.each(data, function (i, item) {
  955. if (item.ua == null) {
  956. item.ua = 'unknown';
  957. } else {
  958. item.ua = escapeHtml(item.ua);
  959. }
  960. item.ua = '<span style="font-size:small">' + item.ua + '</span>';
  961. if (item.service == "activesync") {
  962. item.service = '<span class="badge fs-6 bg-info">ActiveSync</span>';
  963. }
  964. else if (item.service == "imap") {
  965. item.service = '<span class="badge fs-6 bg-success">IMAP, SMTP, Cal-/CardDAV</span>';
  966. }
  967. else {
  968. item.service = '<span class="badge fs-6 bg-danger">' + escapeHtml(item.service) + '</span>';
  969. }
  970. });
  971. } else if (table == 'watchdog') {
  972. $.each(data, function (i, item) {
  973. if (item.message == null) {
  974. item.message = 'Health level: ' + item.lvl + '% (' + item.hpnow + '/' + item.hptotal + ')';
  975. if (item.hpdiff < 0) {
  976. item.trend = '<span class="badge fs-6 bg-danger"><i class="bi bi-caret-down-fill"></i> ' + item.hpdiff + '</span>';
  977. }
  978. else if (item.hpdiff == 0) {
  979. item.trend = '<span class="badge fs-6 bg-info"><i class="bi bi-caret-right-fill"></i> ' + item.hpdiff + '</span>';
  980. }
  981. else {
  982. item.trend = '<span class="badge fs-6 bg-success"><i class="bi bi-caret-up-fill"></i> ' + item.hpdiff + '</span>';
  983. }
  984. }
  985. else {
  986. item.trend = '';
  987. item.service = '';
  988. }
  989. });
  990. } else if (table == 'mailcow_ui') {
  991. $.each(data, function (i, item) {
  992. if (item === null) { return true; }
  993. item.user = escapeHtml(item.user);
  994. item.call = escapeHtml(item.call);
  995. item.task = '<code>' + item.task + '</code>';
  996. item.type = '<span class="badge fs-6 bg-' + item.type + '">' + item.type + '</span>';
  997. });
  998. } else if (table == 'sasl_log_table') {
  999. $.each(data, function (i, item) {
  1000. if (item === null) { return true; }
  1001. item.username = escapeHtml(item.username);
  1002. item.service = '<div class="badge fs-6 bg-secondary">' + item.service.toUpperCase() + '</div>';
  1003. });
  1004. } else if (table == 'general_syslog') {
  1005. $.each(data, function (i, item) {
  1006. if (item === null) { return true; }
  1007. if (item.message.match("^base64,")) {
  1008. try {
  1009. item.message = atob(item.message.slice(7)).replace(/\\n/g, "<br />");
  1010. } catch(e) {
  1011. item.message = item.message.slice(7);
  1012. }
  1013. } else {
  1014. item.message = escapeHtml(item.message);
  1015. }
  1016. item.call = escapeHtml(item.call);
  1017. var danger_class = ["emerg", "alert", "crit", "err"];
  1018. var warning_class = ["warning", "warn"];
  1019. var info_class = ["notice", "info", "debug"];
  1020. if (jQuery.inArray(item.priority, danger_class) !== -1) {
  1021. item.priority = '<span class="badge fs-6 bg-danger">' + item.priority + '</span>';
  1022. } else if (jQuery.inArray(item.priority, warning_class) !== -1) {
  1023. item.priority = '<span class="badge fs-6 bg-warning">' + item.priority + '</span>';
  1024. } else if (jQuery.inArray(item.priority, info_class) !== -1) {
  1025. item.priority = '<span class="badge fs-6 bg-info">' + item.priority + '</span>';
  1026. }
  1027. });
  1028. } else if (table == 'apilog') {
  1029. $.each(data, function (i, item) {
  1030. if (item === null) { return true; }
  1031. if (item.method == 'GET') {
  1032. item.method = '<span class="badge fs-6 bg-success">' + item.method + '</span>';
  1033. } else if (item.method == 'POST') {
  1034. item.method = '<span class="badge fs-6 bg-warning">' + item.method + '</span>';
  1035. }
  1036. item.data = escapeHtml(item.data);
  1037. });
  1038. } else if (table == 'rllog') {
  1039. $.each(data, function (i, item) {
  1040. if (item.user == null) {
  1041. item.user = "none";
  1042. }
  1043. if (item.rl_hash == null) {
  1044. item.rl_hash = "err";
  1045. }
  1046. item.indicator = '<span style="border-right:6px solid #' + intToRGB(hashCode(item.rl_hash)) + ';padding-left:5px;">&nbsp;</span>';
  1047. if (item.rl_hash != 'err') {
  1048. item.action = '<a href="#" data-action="delete_selected" data-id="single-hash" data-api-url="delete/rlhash" data-item="' + encodeURI(item.rl_hash) + '" class="btn btn-xs btn-danger"><i class="bi bi-trash"></i> ' + lang.reset_limit + '</a>';
  1049. }
  1050. });
  1051. }
  1052. return data
  1053. };
  1054. $('.add_log_lines').on('click', function (e) {
  1055. e.preventDefault();
  1056. var log_table= $(this).data("table")
  1057. var new_nrows = $(this).data("nrows")
  1058. var post_process = $(this).data("post-process")
  1059. var log_url = $(this).data("log-url")
  1060. if (log_table === undefined || new_nrows === undefined || post_process === undefined || log_url === undefined) {
  1061. console.log("no data-table or data-nrows or log_url or data-post-process attr found");
  1062. return;
  1063. }
  1064. if (table = $('#' + log_table).DataTable()) {
  1065. var heading = $('#' + log_table).closest('.card').find('.card-header');
  1066. var load_rows = (table.page.len() + 1) + '-' + (table.page.len() + new_nrows)
  1067. $.get('/api/v1/get/logs/' + log_url + '/' + load_rows).then(function(data){
  1068. if (data.length === undefined) { mailcow_alert_box(lang.no_new_rows, "info"); return; }
  1069. var rows = process_table_data(data, post_process);
  1070. var rows_now = (table.page.len() + data.length);
  1071. $(heading).children('.table-lines').text(rows_now)
  1072. mailcow_alert_box(data.length + lang.additional_rows, "success");
  1073. table.rows.add(rows).draw();
  1074. });
  1075. }
  1076. })
  1077. // detect element visibility changes
  1078. function onVisible(element, callback) {
  1079. $(document).ready(function() {
  1080. element_object = document.querySelector(element);
  1081. if (element_object === null) return;
  1082. new IntersectionObserver((entries, observer) => {
  1083. entries.forEach(entry => {
  1084. if(entry.intersectionRatio > 0) {
  1085. callback(element_object);
  1086. }
  1087. });
  1088. }).observe(element_object);
  1089. });
  1090. }
  1091. // Draw Table if tab is active
  1092. onVisible("[id^=postfix_log]", () => draw_postfix_logs());
  1093. onVisible("[id^=dovecot_log]", () => draw_dovecot_logs());
  1094. onVisible("[id^=sogo_log]", () => draw_sogo_logs());
  1095. onVisible("[id^=watchdog_log]", () => draw_watchdog_logs());
  1096. onVisible("[id^=autodiscover_log]", () => draw_autodiscover_logs());
  1097. onVisible("[id^=acme_log]", () => draw_acme_logs());
  1098. onVisible("[id^=api_log]", () => draw_api_logs());
  1099. onVisible("[id^=rl_log]", () => draw_rl_logs());
  1100. onVisible("[id^=ui_logs]", () => draw_ui_logs());
  1101. onVisible("[id^=sasl_logs]", () => draw_sasl_logs());
  1102. onVisible("[id^=netfilter_log]", () => draw_netfilter_logs());
  1103. onVisible("[id^=rspamd_history]", () => draw_rspamd_history());
  1104. onVisible("[id^=rspamd_donut]", () => rspamd_pie_graph());
  1105. // start polling host stats if tab is active
  1106. onVisible("[id^=tab-containers]", () => update_stats());
  1107. // start polling container stats if collapse is active
  1108. var containerElements = document.querySelectorAll(".container-details-collapse");
  1109. for (let i = 0; i < containerElements.length; i++){
  1110. new IntersectionObserver((entries, observer) => {
  1111. entries.forEach(entry => {
  1112. if(entry.intersectionRatio > 0) {
  1113. if (!containerElements[i].classList.contains("show")){
  1114. var container = containerElements[i].id.replace("Collapse", "");
  1115. var container_id = containerElements[i].getAttribute("data-id");
  1116. // check if chart exists or needs to be created
  1117. if (!Chart.getChart(container + "_DiskIOChart"))
  1118. createReadWriteChart(container + "_DiskIOChart", "Read", "Write");
  1119. if (!Chart.getChart(container + "_NetIOChart"))
  1120. createReadWriteChart(container + "_NetIOChart", "Recv", "Sent");
  1121. // add container to polling list
  1122. containersToUpdate[container] = {
  1123. id: container_id,
  1124. state: "idle"
  1125. }
  1126. // stop polling if collapse is closed
  1127. containerElements[i].addEventListener('hidden.bs.collapse', function () {
  1128. var diskIOCtx = Chart.getChart(container + "_DiskIOChart");
  1129. var netIOCtx = Chart.getChart(container + "_NetIOChart");
  1130. diskIOCtx.data.datasets[0].data = [];
  1131. diskIOCtx.data.datasets[1].data = [];
  1132. diskIOCtx.data.labels = [];
  1133. netIOCtx.data.datasets[0].data = [];
  1134. netIOCtx.data.datasets[1].data = [];
  1135. netIOCtx.data.labels = [];
  1136. diskIOCtx.update();
  1137. netIOCtx.update();
  1138. delete containersToUpdate[container];
  1139. });
  1140. }
  1141. }
  1142. });
  1143. }).observe(containerElements[i]);
  1144. }
  1145. });
  1146. // update system stats - every 5 seconds if system & container tab is active
  1147. function update_stats(timeout=5){
  1148. if (!$('#tab-containers').hasClass('active')) {
  1149. // tab not active - dont fetch stats - run again in n seconds
  1150. return;
  1151. }
  1152. window.fetch("/api/v1/get/status/host", {method:'GET',cache:'no-cache'}).then(function(response) {
  1153. return response.json();
  1154. }).then(function(data) {
  1155. console.log(data);
  1156. if (data){
  1157. // display table data
  1158. $("#host_date").text(data.system_time);
  1159. $("#host_uptime").text(formatUptime(data.uptime));
  1160. $("#host_cpu_cores").text(data.cpu.cores);
  1161. $("#host_cpu_usage").text(parseInt(data.cpu.usage).toString() + "%");
  1162. $("#host_memory_total").text((data.memory.total / (1024 ** 3)).toFixed(2).toString() + "GB");
  1163. $("#host_memory_usage").text(parseInt(data.memory.usage).toString() + "%");
  1164. // update cpu and mem chart
  1165. var cpu_chart = Chart.getChart("host_cpu_chart");
  1166. var mem_chart = Chart.getChart("host_mem_chart");
  1167. cpu_chart.data.labels.push(data.system_time.split(" ")[1]);
  1168. if (cpu_chart.data.labels.length > 30) cpu_chart.data.labels.shift();
  1169. mem_chart.data.labels.push(data.system_time.split(" ")[1]);
  1170. if (mem_chart.data.labels.length > 30) mem_chart.data.labels.shift();
  1171. cpu_chart.data.datasets[0].data.push(data.cpu.usage);
  1172. if (cpu_chart.data.datasets[0].data.length > 30) cpu_chart.data.datasets[0].data.shift();
  1173. mem_chart.data.datasets[0].data.push(data.memory.usage);
  1174. if (mem_chart.data.datasets[0].data.length > 30) mem_chart.data.datasets[0].data.shift();
  1175. cpu_chart.update();
  1176. mem_chart.update();
  1177. }
  1178. // run again in n seconds
  1179. setTimeout(update_stats, timeout * 1000);
  1180. });
  1181. }
  1182. // update specific container stats - every n (default 5s) seconds
  1183. function update_container_stats(timeout=5){
  1184. if ($('#tab-containers').hasClass('active')) {
  1185. for (let container in containersToUpdate){
  1186. container_id = containersToUpdate[container].id;
  1187. // check if container update stats is already running
  1188. if (containersToUpdate[container].state == "running")
  1189. continue;
  1190. containersToUpdate[container].state = "running";
  1191. window.fetch("/api/v1/get/status/container/" + container_id, {method:'GET',cache:'no-cache'}).then(function(response) {
  1192. return response.json();
  1193. }).then(function(data) {
  1194. var diskIOCtx = Chart.getChart(container + "_DiskIOChart");
  1195. var netIOCtx = Chart.getChart(container + "_NetIOChart");
  1196. console.log(container);
  1197. console.log(data);
  1198. prev_stats = null;
  1199. if (data.length >= 2){
  1200. prev_stats = data[data.length -2];
  1201. // hide spinners if we collected enough data
  1202. $('#' + container + "_DiskIOChart").removeClass('d-none');
  1203. $('#' + container + "_DiskIOChart").prev().addClass('d-none');
  1204. $('#' + container + "_NetIOChart").removeClass('d-none');
  1205. $('#' + container + "_NetIOChart").prev().addClass('d-none');
  1206. }
  1207. data = data[data.length -1];
  1208. if (prev_stats != null){
  1209. // calc time diff
  1210. var time_diff = (new Date(data.read) - new Date(prev_stats.read)) / 1000;
  1211. // calc disk io b/s
  1212. if ('io_service_bytes_recursive' in prev_stats.blkio_stats && prev_stats.blkio_stats.io_service_bytes_recursive !== null){
  1213. var prev_read_bytes = 0;
  1214. var prev_write_bytes = 0;
  1215. for (var i = 0; i < prev_stats.blkio_stats.io_service_bytes_recursive.length; i++){
  1216. if (prev_stats.blkio_stats.io_service_bytes_recursive[i].op == "read")
  1217. prev_read_bytes = prev_stats.blkio_stats.io_service_bytes_recursive[i].value;
  1218. else if (prev_stats.blkio_stats.io_service_bytes_recursive[i].op == "write")
  1219. prev_write_bytes = prev_stats.blkio_stats.io_service_bytes_recursive[i].value;
  1220. }
  1221. var read_bytes = 0;
  1222. var write_bytes = 0;
  1223. for (var i = 0; i < data.blkio_stats.io_service_bytes_recursive.length; i++){
  1224. if (data.blkio_stats.io_service_bytes_recursive[i].op == "read")
  1225. read_bytes = data.blkio_stats.io_service_bytes_recursive[i].value;
  1226. else if (data.blkio_stats.io_service_bytes_recursive[i].op == "write")
  1227. write_bytes = data.blkio_stats.io_service_bytes_recursive[i].value;
  1228. }
  1229. var diff_bytes_read = (read_bytes - prev_read_bytes) / time_diff;
  1230. var diff_bytes_write = (write_bytes - prev_write_bytes) / time_diff;
  1231. }
  1232. // calc net io b/s
  1233. if ('networks' in prev_stats){
  1234. var prev_recv_bytes = 0;
  1235. var prev_sent_bytes = 0;
  1236. for (var key in prev_stats.networks){
  1237. prev_recv_bytes += prev_stats.networks[key].rx_bytes;
  1238. prev_sent_bytes += prev_stats.networks[key].tx_bytes;
  1239. }
  1240. var recv_bytes = 0;
  1241. var sent_bytes = 0;
  1242. for (var key in data.networks){
  1243. recv_bytes += data.networks[key].rx_bytes;
  1244. sent_bytes += data.networks[key].tx_bytes;
  1245. }
  1246. var diff_bytes_recv = (recv_bytes - prev_recv_bytes) / time_diff;
  1247. var diff_bytes_sent = (sent_bytes - prev_sent_bytes) / time_diff;
  1248. }
  1249. addReadWriteChart(diskIOCtx, diff_bytes_read, diff_bytes_write, "");
  1250. addReadWriteChart(netIOCtx, diff_bytes_recv, diff_bytes_sent, "");
  1251. }
  1252. // run again in n seconds
  1253. containersToUpdate[container].state = "idle";
  1254. }).catch(err => {
  1255. console.log(err);
  1256. });
  1257. }
  1258. }
  1259. // run again in n seconds
  1260. setTimeout(update_container_stats, timeout * 1000);
  1261. }
  1262. // format hosts uptime seconds to readable string
  1263. function formatUptime(seconds){
  1264. seconds = Number(seconds);
  1265. var d = Math.floor(seconds / (3600*24));
  1266. var h = Math.floor(seconds % (3600*24) / 3600);
  1267. var m = Math.floor(seconds % 3600 / 60);
  1268. var s = Math.floor(seconds % 60);
  1269. var dFormat = d > 0 ? d + "D " : "";
  1270. var hFormat = h > 0 ? h + "H " : "";
  1271. var mFormat = m > 0 ? m + "M " : "";
  1272. var sFormat = s > 0 ? s + "S" : "";
  1273. return dFormat + hFormat + mFormat + sFormat;
  1274. }
  1275. // format bytes to readable string
  1276. function formatBytes(bytes){
  1277. // b
  1278. if (bytes < 1000) return bytes.toFixed(2).toString()+' B/s';
  1279. // b to kb
  1280. bytes = bytes / 1024;
  1281. if (bytes < 1000) return bytes.toFixed(2).toString()+' KB/s';
  1282. // kb to mb
  1283. bytes = bytes / 1024;
  1284. if (bytes < 1000) return bytes.toFixed(2).toString()+' MB/s';
  1285. // final mb to gb
  1286. return (bytes / 1024).toFixed(2).toString()+' GB/s';
  1287. }
  1288. // create read write line chart
  1289. function createReadWriteChart(chart_id, read_lable, write_lable){
  1290. var ctx = document.getElementById(chart_id);
  1291. var dataNet = {
  1292. labels: [],
  1293. datasets: [{
  1294. label: read_lable,
  1295. backgroundColor: "rgba(41, 187, 239, 0.3)",
  1296. borderColor: "rgba(41, 187, 239, 0.6)",
  1297. pointRadius: 1,
  1298. pointHitRadius: 6,
  1299. borderWidth: 2,
  1300. fill: true,
  1301. tension: 0.2,
  1302. data: []
  1303. }, {
  1304. label: write_lable,
  1305. backgroundColor: "rgba(239, 60, 41, 0.3)",
  1306. borderColor: "rgba(239, 60, 41, 0.6)",
  1307. pointRadius: 1,
  1308. pointHitRadius: 6,
  1309. borderWidth: 2,
  1310. fill: true,
  1311. tension: 0.2,
  1312. data: []
  1313. }]
  1314. };
  1315. var optionsNet = {
  1316. interaction: {
  1317. mode: 'index'
  1318. },
  1319. scales: {
  1320. yAxis: {
  1321. min: 0,
  1322. grid: {
  1323. display: false
  1324. },
  1325. ticks: {
  1326. callback: function(i, index, ticks) {
  1327. return formatBytes(i);
  1328. }
  1329. }
  1330. },
  1331. xAxis: {
  1332. grid: {
  1333. display: false
  1334. }
  1335. }
  1336. }
  1337. };
  1338. return new Chart(ctx, {
  1339. type: 'line',
  1340. data: dataNet,
  1341. options: optionsNet
  1342. });
  1343. }
  1344. // add to read write line chart
  1345. function addReadWriteChart(chart_context, read_point, write_point, time, limit = 30){
  1346. // push time label for x-axis
  1347. chart_context.data.labels.push(time);
  1348. if (chart_context.data.labels.length > limit) chart_context.data.labels.shift();
  1349. // push datapoints
  1350. chart_context.data.datasets[0].data.push(read_point);
  1351. chart_context.data.datasets[1].data.push(write_point);
  1352. // shift data if more than 20 entires exists
  1353. if (chart_context.data.datasets[0].data.length > limit) chart_context.data.datasets[0].data.shift();
  1354. if (chart_context.data.datasets[1].data.length > limit) chart_context.data.datasets[1].data.shift();
  1355. chart_context.update();
  1356. }
  1357. // create host cpu and mem chart
  1358. function createHostCpuAndMemChart(){
  1359. var cpu_ctx = document.getElementById("host_cpu_chart");
  1360. var mem_ctx = document.getElementById("host_mem_chart");
  1361. var dataCpu = {
  1362. labels: [],
  1363. datasets: [{
  1364. label: "CPU %",
  1365. backgroundColor: "rgba(41, 187, 239, 0.3)",
  1366. borderColor: "rgba(41, 187, 239, 0.6)",
  1367. pointRadius: 1,
  1368. pointHitRadius: 6,
  1369. borderWidth: 2,
  1370. fill: true,
  1371. tension: 0.2,
  1372. data: []
  1373. }]
  1374. };
  1375. var optionsCpu = {
  1376. interaction: {
  1377. mode: 'index'
  1378. },
  1379. scales: {
  1380. yAxis: {
  1381. min: 0,
  1382. grid: {
  1383. display: false
  1384. },
  1385. ticks: {
  1386. callback: function(i, index, ticks) {
  1387. return i.toFixed(0).toString() + "%";
  1388. }
  1389. }
  1390. },
  1391. xAxis: {
  1392. grid: {
  1393. display: false
  1394. }
  1395. }
  1396. }
  1397. };
  1398. var dataMem = {
  1399. labels: [],
  1400. datasets: [{
  1401. label: "MEM %",
  1402. backgroundColor: "rgba(41, 187, 239, 0.3)",
  1403. borderColor: "rgba(41, 187, 239, 0.6)",
  1404. pointRadius: 1,
  1405. pointHitRadius: 6,
  1406. borderWidth: 2,
  1407. fill: true,
  1408. tension: 0.2,
  1409. data: []
  1410. }]
  1411. };
  1412. var optionsMem = {
  1413. interaction: {
  1414. mode: 'index'
  1415. },
  1416. scales: {
  1417. yAxis: {
  1418. min: 0,
  1419. grid: {
  1420. display: false
  1421. },
  1422. ticks: {
  1423. callback: function(i, index, ticks) {
  1424. return i.toFixed(0).toString() + "%";
  1425. }
  1426. }
  1427. },
  1428. xAxis: {
  1429. grid: {
  1430. display: false
  1431. }
  1432. }
  1433. }
  1434. };
  1435. var net_io_chart = new Chart(cpu_ctx, {
  1436. type: 'line',
  1437. data: dataCpu,
  1438. options: optionsCpu
  1439. });
  1440. var disk_io_chart = new Chart(mem_ctx, {
  1441. type: 'line',
  1442. data: dataMem,
  1443. options: optionsMem
  1444. });
  1445. }
  1446. // check for mailcow updates
  1447. function check_update(current_version, github_repo_url){
  1448. if (!current_version || !github_repo_url) return false;
  1449. var github_account = github_repo_url.split("/")[3];
  1450. var github_repo_name = github_repo_url.split("/")[4];
  1451. // get details about latest release
  1452. window.fetch("https://api.github.com/repos/"+github_account+"/"+github_repo_name+"/releases/latest", {method:'GET',cache:'no-cache'}).then(function(response) {
  1453. return response.json();
  1454. }).then(function(latest_data) {
  1455. // get details about current release
  1456. window.fetch("https://api.github.com/repos/"+github_account+"/"+github_repo_name+"/releases/tags/"+current_version, {method:'GET',cache:'no-cache'}).then(function(response) {
  1457. return response.json();
  1458. }).then(function(current_data) {
  1459. // compare releases
  1460. var date_current = new Date(current_data.created_at);
  1461. var date_latest = new Date(latest_data.created_at);
  1462. if (date_latest.getTime() <= date_current.getTime()){
  1463. // no update available
  1464. $("#mailcow_update").removeClass("text-warning text-danger").addClass("text-success");
  1465. $("#mailcow_update").html("<b>" + lang_debug.no_update_available + "</b>");
  1466. } else {
  1467. // update available
  1468. $("#mailcow_update").removeClass("text-danger text-success").addClass("text-warning");
  1469. $("#mailcow_update").html(lang_debug.update_available + ` <a href="#" id="mailcow_update_changelog">`+latest_data.tag_name+`</a>`);
  1470. $("#mailcow_update_changelog").click(function(){
  1471. if (mailcow_cc_role !== "admin" && mailcow_cc_role !== "domainadmin")
  1472. return;
  1473. showVersionModal("New Release " + latest_data.tag_name, latest_data.tag_name);
  1474. })
  1475. }
  1476. }).catch(err => {
  1477. // err
  1478. console.log(err);
  1479. $("#mailcow_update").removeClass("text-success text-warning").addClass("text-danger");
  1480. $("#mailcow_update").html("<b>"+ lang_debug.update_failed +"</b>");
  1481. });
  1482. }).catch(err => {
  1483. // err
  1484. console.log(err);
  1485. $("#mailcow_update").removeClass("text-success text-warning").addClass("text-danger");
  1486. $("#mailcow_update").html("<b>"+ lang_debug.update_failed +"</b>");
  1487. });
  1488. }
  1489. // show version changelog modal
  1490. function showVersionModal(title, version){
  1491. $.ajax({
  1492. type: 'GET',
  1493. url: 'https://api.github.com/repos/' + mailcow_info.project_owner + '/' + mailcow_info.project_repo + '/releases/tags/' + version,
  1494. dataType: 'json',
  1495. success: function (data) {
  1496. var md = window.markdownit();
  1497. var result = md.render(data.body);
  1498. result = parseGithubMarkdownLinks(result);
  1499. $('#showVersionModal').find(".modal-title").html(title);
  1500. $('#showVersionModal').find(".modal-body").html(`
  1501. <h3>` + data.name + `</h3>
  1502. <span class="mt-4">` + result + `</span>
  1503. <span><b>Github Link:</b>
  1504. <a target="_blank" href="https://github.com/` + mailcow_info.project_owner + `/` + mailcow_info.project_repo + `/releases/tag/` + version + `">` + version + `</a>
  1505. </span>
  1506. `);
  1507. new bootstrap.Modal(document.getElementById("showVersionModal"), {
  1508. backdrop: 'static',
  1509. keyboard: false
  1510. }).show();
  1511. }
  1512. });
  1513. }
  1514. function parseGithubMarkdownLinks(inputText) {
  1515. var replacedText, replacePattern1;
  1516. replacePattern1 = /(\b(https?):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim;
  1517. replacedText = inputText.replace(replacePattern1, (matched, index, original, input_string) => {
  1518. if (matched.includes('github.com')){
  1519. // return short link if it's github link
  1520. last_uri_path = matched.split('/');
  1521. last_uri_path = last_uri_path[last_uri_path.length - 1];
  1522. // adjust Full Changelog link to match last git version and new git version, if link is a compare link
  1523. if (matched.includes('/compare/') && mailcow_info.last_version_tag !== ''){
  1524. matched = matched.replace(last_uri_path, mailcow_info.last_version_tag + '...' + mailcow_info.version_tag);
  1525. last_uri_path = mailcow_info.last_version_tag + '...' + mailcow_info.version_tag;
  1526. }
  1527. return '<a href="' + matched + '" target="_blank">' + last_uri_path + '</a><br>';
  1528. };
  1529. // if it's not a github link, return complete link
  1530. return '<a href="' + matched + '" target="_blank">' + matched + '</a>';
  1531. });
  1532. return replacedText;
  1533. }
  1534. function convertTimestampToLocalFormat(timestamp) {
  1535. var date = new Date(timestamp ? timestamp * 1000 : 0);
  1536. return date.toLocaleDateString(LOCALE, DATETIME_FORMAT);
  1537. }