monitor.js 75 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157
  1. /**
  2. * @fileoverview Javascript functions used in server status monitor page
  3. * @name Server Status Monitor
  4. *
  5. * @requires jQuery
  6. * @requires jQueryUI
  7. * @requires js/functions.js
  8. */
  9. /* global isStorageSupported */ // js/config.js
  10. /* global codeMirrorEditor:writable */ // js/functions.js
  11. /* global firstDayOfCalendar, themeImagePath */ // templates/javascript/variables.twig
  12. /* global variableNames */ // templates/server/status/monitor/index.twig
  13. var runtime = {};
  14. var serverTimeDiff;
  15. var serverOs;
  16. var isSuperUser;
  17. var serverDbIsLocal;
  18. var chartSize;
  19. var monitorSettings;
  20. function serverResponseError() {
  21. var btns = {
  22. [Messages.strReloadPage]: {
  23. text: Messages.strReloadPage,
  24. class: 'btn btn-primary',
  25. click: function () {
  26. window.location.reload();
  27. }
  28. }
  29. };
  30. $('#emptyDialog').dialog({
  31. classes: {
  32. 'ui-dialog-titlebar-close': 'btn-close'
  33. },
  34. title: Messages.strRefreshFailed
  35. });
  36. $('#emptyDialog').html(Functions.getImage('s_attention') + Messages.strInvalidResponseExplanation);
  37. $('#emptyDialog').dialog({
  38. classes: {
  39. 'ui-dialog-titlebar-close': 'btn-close'
  40. },
  41. buttons: btns
  42. });
  43. }
  44. /**
  45. * Destroys all monitor related resources
  46. */
  47. function destroyGrid() {
  48. if (runtime.charts) {
  49. $.each(runtime.charts, function (key, value) {
  50. try {
  51. value.chart.destroy();
  52. } catch (err) {
  53. // continue regardless of error
  54. }
  55. });
  56. }
  57. try {
  58. runtime.refreshRequest.abort();
  59. } catch (err) {
  60. // continue regardless of error
  61. }
  62. try {
  63. clearTimeout(runtime.refreshTimeout);
  64. } catch (err) {
  65. // continue regardless of error
  66. }
  67. $('#chartGrid').html('');
  68. runtime.charts = null;
  69. runtime.chartAI = 0;
  70. monitorSettings = null;
  71. }
  72. AJAX.registerOnload('server/status/monitor.js', function () {
  73. var $jsDataForm = $('#js_data');
  74. serverTimeDiff = new Date().getTime() - $jsDataForm.find('input[name=server_time]').val();
  75. serverOs = $jsDataForm.find('input[name=server_os]').val();
  76. isSuperUser = $jsDataForm.find('input[name=is_superuser]').val();
  77. serverDbIsLocal = $jsDataForm.find('input[name=server_db_isLocal]').val();
  78. });
  79. /**
  80. * Unbind all event handlers before tearing down a page
  81. */
  82. AJAX.registerTeardown('server/status/monitor.js', function () {
  83. $('#emptyDialog').remove();
  84. $('a.popupLink').off('click');
  85. $('body').off('click');
  86. });
  87. /**
  88. * Popup behaviour
  89. */
  90. AJAX.registerOnload('server/status/monitor.js', function () {
  91. $('<div></div>').attr('id', 'emptyDialog').appendTo('#page_content');
  92. $('a.popupLink').on('click', function () {
  93. var $link = $(this);
  94. $('div.' + $link.attr('href').substr(1)).show().offset({
  95. top: $link.offset().top + $link.height() + 5,
  96. left: $link.offset().left
  97. }).addClass('openedPopup');
  98. return false;
  99. });
  100. $('body').on('click', function (event) {
  101. $('div.openedPopup').each(function () {
  102. var $cnt = $(this);
  103. var pos = $cnt.offset();
  104. // Hide if the mouseclick is outside the popupcontent
  105. if (event.pageX > pos.left + $cnt.outerWidth() || event.pageY > pos.top + $cnt.outerHeight()) {
  106. $cnt.hide().removeClass('openedPopup');
  107. }
  108. });
  109. });
  110. });
  111. AJAX.registerTeardown('server/status/monitor.js', function () {
  112. $('a[href="#rearrangeCharts"], a[href="#endChartEditMode"]').off('click');
  113. $('div.popupContent select[name="chartColumns"]').off('change');
  114. $('div.popupContent select[name="gridChartRefresh"]').off('change');
  115. $('a[href="#addNewChart"]').off('click');
  116. $('a[href="#exportMonitorConfig"]').off('click');
  117. $('a[href="#importMonitorConfig"]').off('click');
  118. $('a[href="#clearMonitorConfig"]').off('click');
  119. $('a[href="#pauseCharts"]').off('click');
  120. $('a[href="#monitorInstructionsDialog"]').off('click');
  121. $('input[name="chartType"]').off('click');
  122. $('input[name="useDivisor"]').off('click');
  123. $('input[name="useUnit"]').off('click');
  124. $('select[name="varChartList"]').off('click');
  125. $('a[href="#kibDivisor"]').off('click');
  126. $('a[href="#mibDivisor"]').off('click');
  127. $('a[href="#submitClearSeries"]').off('click');
  128. $('a[href="#submitAddSeries"]').off('click');
  129. // $("input#variableInput").destroy();
  130. $('#chartPreset').off('click');
  131. $('#chartStatusVar').off('click');
  132. destroyGrid();
  133. });
  134. AJAX.registerOnload('server/status/monitor.js', function () {
  135. // Show tab links
  136. $('div.tabLinks').show();
  137. $('#loadingMonitorIcon').remove();
  138. // Codemirror is loaded on demand so we might need to initialize it
  139. if (!codeMirrorEditor) {
  140. var $elm = $('#sqlquery');
  141. if ($elm.length > 0 && typeof CodeMirror !== 'undefined') {
  142. codeMirrorEditor = CodeMirror.fromTextArea($elm[0], {
  143. lineNumbers: true,
  144. matchBrackets: true,
  145. indentUnit: 4,
  146. mode: 'text/x-mysql',
  147. lineWrapping: true
  148. });
  149. }
  150. }
  151. // Timepicker is loaded on demand so we need to initialize
  152. // datetime fields from the 'load log' dialog
  153. $('#logAnalyseDialog').find('.datetimefield').each(function () {
  154. Functions.addDatepicker($(this));
  155. });
  156. /** ** Monitor charting implementation ****/
  157. /* Saves the previous ajax response for differential values */
  158. var oldChartData = null;
  159. // Holds about to be created chart
  160. var newChart = null;
  161. var chartSpacing;
  162. // Whenever the monitor object (runtime.charts) or the settings object
  163. // (monitorSettings) changes in a way incompatible to the previous version,
  164. // increase this number. It will reset the users monitor and settings object
  165. // in their localStorage to the default configuration
  166. var monitorProtocolVersion = '1.0';
  167. // Runtime parameter of the monitor, is being fully set in initGrid()
  168. runtime = {
  169. // Holds all visible charts in the grid
  170. charts: null,
  171. // Stores the timeout handler so it can be cleared
  172. refreshTimeout: null,
  173. // Stores the GET request to refresh the charts
  174. refreshRequest: null,
  175. // Chart auto increment
  176. chartAI: 0,
  177. // To play/pause the monitor
  178. redrawCharts: false,
  179. // Object that contains a list of nodes that need to be retrieved
  180. // from the server for chart updates
  181. dataList: [],
  182. // Current max points per chart (needed for auto calculation)
  183. gridMaxPoints: 20,
  184. // displayed time frame
  185. xmin: -1,
  186. xmax: -1
  187. };
  188. monitorSettings = null;
  189. var defaultMonitorSettings = {
  190. columns: 3,
  191. chartSize: {
  192. width: 295,
  193. height: 250
  194. },
  195. // Max points in each chart. Settings it to 'auto' sets
  196. // gridMaxPoints to (chartwidth - 40) / 12
  197. gridMaxPoints: 'auto',
  198. /* Refresh rate of all grid charts in ms */
  199. gridRefresh: 5000
  200. };
  201. // Allows drag and drop rearrange and print/edit icons on charts
  202. var editMode = false;
  203. /* List of preconfigured charts that the user may select */
  204. var presetCharts = {
  205. // Query cache efficiency
  206. 'qce': {
  207. title: Messages.strQueryCacheEfficiency,
  208. series: [{
  209. label: Messages.strQueryCacheEfficiency
  210. }],
  211. nodes: [{
  212. dataPoints: [{
  213. type: 'statusvar',
  214. name: 'Qcache_hits'
  215. }, {
  216. type: 'statusvar',
  217. name: 'Com_select'
  218. }],
  219. transformFn: 'qce'
  220. }],
  221. maxYLabel: 0
  222. },
  223. // Query cache usage
  224. 'qcu': {
  225. title: Messages.strQueryCacheUsage,
  226. series: [{
  227. label: Messages.strQueryCacheUsed
  228. }],
  229. nodes: [{
  230. dataPoints: [{
  231. type: 'statusvar',
  232. name: 'Qcache_free_memory'
  233. }, {
  234. type: 'servervar',
  235. name: 'query_cache_size'
  236. }],
  237. transformFn: 'qcu'
  238. }],
  239. maxYLabel: 0
  240. }
  241. };
  242. // time span selection
  243. var selectionTimeDiff = [];
  244. var selectionStartX;
  245. var selectionStartY;
  246. var drawTimeSpan = false;
  247. /* Add OS specific system info charts to the preset chart list */
  248. switch (serverOs) {
  249. case 'WINNT':
  250. $.extend(presetCharts, {
  251. 'cpu': {
  252. title: Messages.strSystemCPUUsage,
  253. series: [{
  254. label: Messages.strAverageLoad
  255. }],
  256. nodes: [{
  257. dataPoints: [{
  258. type: 'cpu',
  259. name: 'loadavg'
  260. }]
  261. }],
  262. maxYLabel: 100
  263. },
  264. 'memory': {
  265. title: Messages.strSystemMemory,
  266. series: [{
  267. dataType: 'memory',
  268. label: Messages.strUsedMemory,
  269. fill: true
  270. }, {
  271. label: Messages.strFreeMemory,
  272. fill: true
  273. }],
  274. nodes: [{
  275. dataPoints: [{
  276. type: 'memory',
  277. name: 'MemUsed'
  278. }],
  279. valueDivisor: 1024
  280. }, {
  281. dataPoints: [{
  282. type: 'memory',
  283. name: 'MemFree'
  284. }],
  285. valueDivisor: 1024
  286. }],
  287. maxYLabel: 0
  288. },
  289. 'swap': {
  290. title: Messages.strSystemSwap,
  291. series: [{
  292. label: Messages.strUsedSwap,
  293. fill: true
  294. }, {
  295. label: Messages.strFreeSwap,
  296. fill: true
  297. }],
  298. nodes: [{
  299. dataPoints: [{
  300. type: 'memory',
  301. name: 'SwapUsed'
  302. }],
  303. valueDivisor: 1024
  304. }, {
  305. dataPoints: [{
  306. type: 'memory',
  307. name: 'SwapFree'
  308. }],
  309. valueDivisor: 1024
  310. }],
  311. maxYLabel: 0
  312. }
  313. });
  314. break;
  315. case 'Linux':
  316. $.extend(presetCharts, {
  317. 'cpu': {
  318. title: Messages.strSystemCPUUsage,
  319. series: [{
  320. label: Messages.strAverageLoad
  321. }],
  322. nodes: [{
  323. dataPoints: [{
  324. type: 'cpu',
  325. name: 'irrelevant'
  326. }],
  327. transformFn: 'cpu-linux'
  328. }],
  329. maxYLabel: 0
  330. },
  331. 'memory': {
  332. title: Messages.strSystemMemory,
  333. series: [{
  334. label: Messages.strBufferedMemory,
  335. fill: true
  336. }, {
  337. label: Messages.strUsedMemory,
  338. fill: true
  339. }, {
  340. label: Messages.strCachedMemory,
  341. fill: true
  342. }, {
  343. label: Messages.strFreeMemory,
  344. fill: true
  345. }],
  346. nodes: [{
  347. dataPoints: [{
  348. type: 'memory',
  349. name: 'Buffers'
  350. }],
  351. valueDivisor: 1024
  352. }, {
  353. dataPoints: [{
  354. type: 'memory',
  355. name: 'MemUsed'
  356. }],
  357. valueDivisor: 1024
  358. }, {
  359. dataPoints: [{
  360. type: 'memory',
  361. name: 'Cached'
  362. }],
  363. valueDivisor: 1024
  364. }, {
  365. dataPoints: [{
  366. type: 'memory',
  367. name: 'MemFree'
  368. }],
  369. valueDivisor: 1024
  370. }],
  371. maxYLabel: 0
  372. },
  373. 'swap': {
  374. title: Messages.strSystemSwap,
  375. series: [{
  376. label: Messages.strCachedSwap,
  377. fill: true
  378. }, {
  379. label: Messages.strUsedSwap,
  380. fill: true
  381. }, {
  382. label: Messages.strFreeSwap,
  383. fill: true
  384. }],
  385. nodes: [{
  386. dataPoints: [{
  387. type: 'memory',
  388. name: 'SwapCached'
  389. }],
  390. valueDivisor: 1024
  391. }, {
  392. dataPoints: [{
  393. type: 'memory',
  394. name: 'SwapUsed'
  395. }],
  396. valueDivisor: 1024
  397. }, {
  398. dataPoints: [{
  399. type: 'memory',
  400. name: 'SwapFree'
  401. }],
  402. valueDivisor: 1024
  403. }],
  404. maxYLabel: 0
  405. }
  406. });
  407. break;
  408. case 'SunOS':
  409. $.extend(presetCharts, {
  410. 'cpu': {
  411. title: Messages.strSystemCPUUsage,
  412. series: [{
  413. label: Messages.strAverageLoad
  414. }],
  415. nodes: [{
  416. dataPoints: [{
  417. type: 'cpu',
  418. name: 'loadavg'
  419. }]
  420. }],
  421. maxYLabel: 0
  422. },
  423. 'memory': {
  424. title: Messages.strSystemMemory,
  425. series: [{
  426. label: Messages.strUsedMemory,
  427. fill: true
  428. }, {
  429. label: Messages.strFreeMemory,
  430. fill: true
  431. }],
  432. nodes: [{
  433. dataPoints: [{
  434. type: 'memory',
  435. name: 'MemUsed'
  436. }],
  437. valueDivisor: 1024
  438. }, {
  439. dataPoints: [{
  440. type: 'memory',
  441. name: 'MemFree'
  442. }],
  443. valueDivisor: 1024
  444. }],
  445. maxYLabel: 0
  446. },
  447. 'swap': {
  448. title: Messages.strSystemSwap,
  449. series: [{
  450. label: Messages.strUsedSwap,
  451. fill: true
  452. }, {
  453. label: Messages.strFreeSwap,
  454. fill: true
  455. }],
  456. nodes: [{
  457. dataPoints: [{
  458. type: 'memory',
  459. name: 'SwapUsed'
  460. }],
  461. valueDivisor: 1024
  462. }, {
  463. dataPoints: [{
  464. type: 'memory',
  465. name: 'SwapFree'
  466. }],
  467. valueDivisor: 1024
  468. }],
  469. maxYLabel: 0
  470. }
  471. });
  472. break;
  473. }
  474. // Default setting for the chart grid
  475. var defaultChartGrid = {
  476. 'c0': {
  477. title: Messages.strQuestions,
  478. series: [{
  479. label: Messages.strQuestions
  480. }],
  481. nodes: [{
  482. dataPoints: [{
  483. type: 'statusvar',
  484. name: 'Questions'
  485. }],
  486. display: 'differential'
  487. }],
  488. maxYLabel: 0
  489. },
  490. 'c1': {
  491. title: Messages.strChartConnectionsTitle,
  492. series: [{
  493. label: Messages.strConnections
  494. }, {
  495. label: Messages.strProcesses
  496. }],
  497. nodes: [{
  498. dataPoints: [{
  499. type: 'statusvar',
  500. name: 'Connections'
  501. }],
  502. display: 'differential'
  503. }, {
  504. dataPoints: [{
  505. type: 'proc',
  506. name: 'processes'
  507. }]
  508. }],
  509. maxYLabel: 0
  510. },
  511. 'c2': {
  512. title: Messages.strTraffic,
  513. series: [{
  514. label: Messages.strBytesSent
  515. }, {
  516. label: Messages.strBytesReceived
  517. }],
  518. nodes: [{
  519. dataPoints: [{
  520. type: 'statusvar',
  521. name: 'Bytes_sent'
  522. }],
  523. display: 'differential',
  524. valueDivisor: 1024
  525. }, {
  526. dataPoints: [{
  527. type: 'statusvar',
  528. name: 'Bytes_received'
  529. }],
  530. display: 'differential',
  531. valueDivisor: 1024
  532. }],
  533. maxYLabel: 0
  534. }
  535. };
  536. // Server is localhost => We can add cpu/memory/swap to the default chart
  537. if (serverDbIsLocal && typeof presetCharts.cpu !== 'undefined') {
  538. defaultChartGrid.c3 = presetCharts.cpu;
  539. defaultChartGrid.c4 = presetCharts.memory;
  540. defaultChartGrid.c5 = presetCharts.swap;
  541. }
  542. $('a[href="#rearrangeCharts"], a[href="#endChartEditMode"]').on('click', function (event) {
  543. event.preventDefault();
  544. editMode = !editMode;
  545. if ($(this).attr('href') === '#endChartEditMode') {
  546. editMode = false;
  547. }
  548. $('a[href="#endChartEditMode"]').toggle(editMode);
  549. if (editMode) {
  550. // Close the settings popup
  551. $('div.popupContent').hide().removeClass('openedPopup');
  552. $('#chartGrid').sortableTable({
  553. ignoreRect: {
  554. top: 8,
  555. left: chartSize.width - 63,
  556. width: 54,
  557. height: 24
  558. }
  559. });
  560. } else {
  561. $('#chartGrid').sortableTable('destroy');
  562. }
  563. saveMonitor(); // Save settings
  564. return false;
  565. });
  566. // global settings
  567. $('div.popupContent select[name="chartColumns"]').on('change', function () {
  568. monitorSettings.columns = parseInt(this.value, 10);
  569. calculateChartSize();
  570. // Empty cells should keep their size so you can drop onto them
  571. $('#chartGrid').find('tr td').css('width', chartSize.width + 'px');
  572. $('#chartGrid').find('.monitorChart').css({
  573. width: chartSize.width + 'px',
  574. height: chartSize.height + 'px'
  575. });
  576. /* Reorder all charts that it fills all column cells */
  577. var numColumns;
  578. var $tr = $('#chartGrid').find('tr').first();
  579. var tempManageCols = function () {
  580. if (numColumns > monitorSettings.columns) {
  581. if ($tr.next().length === 0) {
  582. $tr.after('<tr></tr>');
  583. }
  584. $tr.next().prepend($(this));
  585. }
  586. numColumns++;
  587. };
  588. var tempAddCol = function () {
  589. if ($(this).next().length !== 0) {
  590. $(this).append($(this).next().find('td').first());
  591. }
  592. };
  593. while ($tr.length !== 0) {
  594. numColumns = 1;
  595. // To many cells in one row => put into next row
  596. $tr.find('td').each(tempManageCols);
  597. // To little cells in one row => for each cell to little,
  598. // move all cells backwards by 1
  599. if ($tr.next().length > 0) {
  600. var cnt = monitorSettings.columns - $tr.find('td').length;
  601. for (var i = 0; i < cnt; i++) {
  602. $tr.append($tr.next().find('td').first());
  603. $tr.nextAll().each(tempAddCol);
  604. }
  605. }
  606. $tr = $tr.next();
  607. }
  608. if (monitorSettings.gridMaxPoints === 'auto') {
  609. runtime.gridMaxPoints = Math.round((chartSize.width - 40) / 12);
  610. }
  611. runtime.xmin = new Date().getTime() - serverTimeDiff - runtime.gridMaxPoints * monitorSettings.gridRefresh;
  612. runtime.xmax = new Date().getTime() - serverTimeDiff + monitorSettings.gridRefresh;
  613. if (editMode) {
  614. $('#chartGrid').sortableTable('refresh');
  615. }
  616. refreshChartGrid();
  617. saveMonitor(); // Save settings
  618. });
  619. $('div.popupContent select[name="gridChartRefresh"]').on('change', function () {
  620. monitorSettings.gridRefresh = parseInt(this.value, 10) * 1000;
  621. clearTimeout(runtime.refreshTimeout);
  622. if (runtime.refreshRequest) {
  623. runtime.refreshRequest.abort();
  624. }
  625. runtime.xmin = new Date().getTime() - serverTimeDiff - runtime.gridMaxPoints * monitorSettings.gridRefresh;
  626. // fixing chart shift towards left on refresh rate change
  627. // runtime.xmax = new Date().getTime() - serverTimeDiff + monitorSettings.gridRefresh;
  628. runtime.refreshTimeout = setTimeout(refreshChartGrid, monitorSettings.gridRefresh);
  629. saveMonitor(); // Save settings
  630. });
  631. $('a[href="#addNewChart"]').on('click', function (event) {
  632. event.preventDefault();
  633. $('#addChartButton').on('click', function () {
  634. var type = $('input[name="chartType"]:checked').val();
  635. if (type === 'preset') {
  636. newChart = presetCharts[$('#addChartModal').find('select[name="presetCharts"]').prop('value')];
  637. } else {
  638. // If user builds their own chart, it's being set/updated
  639. // each time they add a series
  640. // So here we only warn if they didn't add a series yet
  641. if (!newChart || !newChart.nodes || newChart.nodes.length === 0) {
  642. alert(Messages.strAddOneSeriesWarning);
  643. return;
  644. }
  645. }
  646. newChart.title = $('input[name="chartTitle"]').val();
  647. // Add a cloned object to the chart grid
  648. addChart($.extend(true, {}, newChart));
  649. newChart = null;
  650. saveMonitor(); // Save settings
  651. $('#closeModalButton').off('click');
  652. $('#addChartButton').off('click');
  653. });
  654. $('#closeModalButton').on('click', function () {
  655. newChart = null;
  656. $('span#clearSeriesLink').hide();
  657. $('#seriesPreview').html('');
  658. $('#closeModalButton').off('click');
  659. $('#addChartButton').off('click');
  660. });
  661. var $presetList = $('#addChartModal').find('select[name="presetCharts"]');
  662. if ($presetList.html().length === 0) {
  663. $.each(presetCharts, function (key, value) {
  664. $presetList.append('<option value="' + key + '">' + value.title + '</option>');
  665. });
  666. $presetList.on('change', function () {
  667. $('input[name="chartTitle"]').val($presetList.find(':selected').text());
  668. $('#chartPreset').prop('checked', true);
  669. });
  670. $('#chartPreset').on('click', function () {
  671. $('input[name="chartTitle"]').val($presetList.find(':selected').text());
  672. });
  673. $('#chartStatusVar').on('click', function () {
  674. $('input[name="chartTitle"]').val($('#chartSeries').find(':selected').text().replace(/_/g, ' '));
  675. });
  676. $('#chartSeries').on('change', function () {
  677. $('input[name="chartTitle"]').val($('#chartSeries').find(':selected').text().replace(/_/g, ' '));
  678. });
  679. }
  680. $('#addChartModal').modal('show');
  681. $('#seriesPreview').html('<i>' + Messages.strNone + '</i>');
  682. return false;
  683. });
  684. $('a[href="#exportMonitorConfig"]').on('click', function (event) {
  685. event.preventDefault();
  686. var gridCopy = {};
  687. $.each(runtime.charts, function (key, elem) {
  688. gridCopy[key] = {};
  689. gridCopy[key].nodes = elem.nodes;
  690. gridCopy[key].series = elem.series;
  691. gridCopy[key].settings = elem.settings;
  692. gridCopy[key].title = elem.title;
  693. gridCopy[key].maxYLabel = elem.maxYLabel;
  694. });
  695. var exportData = {
  696. monitorCharts: gridCopy,
  697. monitorSettings: monitorSettings
  698. };
  699. var blob = new Blob([JSON.stringify(exportData)], {
  700. type: 'application/octet-stream'
  701. });
  702. var url = null;
  703. var fileName = 'monitor-config.json';
  704. if (window.navigator && window.navigator.msSaveOrOpenBlob) {
  705. window.navigator.msSaveOrOpenBlob(blob, fileName);
  706. } else {
  707. url = URL.createObjectURL(blob);
  708. window.location.href = url;
  709. }
  710. setTimeout(function () {
  711. // For some browsers it is necessary to delay revoking the ObjectURL
  712. if (url !== null) {
  713. window.URL.revokeObjectURL(url);
  714. }
  715. url = undefined;
  716. blob = undefined;
  717. }, 100);
  718. });
  719. $('a[href="#importMonitorConfig"]').on('click', function (event) {
  720. event.preventDefault();
  721. $('#emptyDialog').dialog({
  722. classes: {
  723. 'ui-dialog-titlebar-close': 'btn-close'
  724. },
  725. title: Messages.strImportDialogTitle
  726. });
  727. $('#emptyDialog').html(Messages.strImportDialogMessage + '<br><form>' + '<input type="file" name="file" id="import_file"> </form>');
  728. var dlgBtns = {
  729. [Messages.strImport]: {
  730. text: Messages.strImport,
  731. class: 'btn btn-primary'
  732. },
  733. [Messages.strCancel]: {
  734. text: Messages.strCancel,
  735. class: 'btn btn-secondary'
  736. }
  737. };
  738. dlgBtns[Messages.strImport].click = function () {
  739. var input = $('#emptyDialog').find('#import_file')[0];
  740. var reader = new FileReader();
  741. reader.onerror = function (event) {
  742. alert(Messages.strFailedParsingConfig + '\n' + event.target.error.code);
  743. };
  744. reader.onload = function (e) {
  745. var data = e.target.result;
  746. var json = null;
  747. // Try loading config
  748. try {
  749. json = JSON.parse(data);
  750. } catch (err) {
  751. alert(Messages.strFailedParsingConfig);
  752. $('#emptyDialog').dialog('close');
  753. return;
  754. }
  755. // Basic check, is this a monitor config json?
  756. if (!json || !json.monitorCharts || !json.monitorCharts) {
  757. alert(Messages.strFailedParsingConfig);
  758. $('#emptyDialog').dialog('close');
  759. return;
  760. }
  761. // If json ok, try applying config
  762. try {
  763. if (isStorageSupported('localStorage')) {
  764. window.localStorage.monitorCharts = JSON.stringify(json.monitorCharts);
  765. window.localStorage.monitorSettings = JSON.stringify(json.monitorSettings);
  766. }
  767. rebuildGrid();
  768. } catch (err) {
  769. alert(Messages.strFailedBuildingGrid);
  770. // If an exception is thrown, load default again
  771. if (isStorageSupported('localStorage')) {
  772. window.localStorage.removeItem('monitorCharts');
  773. window.localStorage.removeItem('monitorSettings');
  774. }
  775. rebuildGrid();
  776. }
  777. $('#emptyDialog').dialog('close');
  778. };
  779. if (input.files[0]) {
  780. reader.readAsText(input.files[0]);
  781. }
  782. };
  783. dlgBtns[Messages.strCancel].click = function () {
  784. $(this).dialog('close');
  785. };
  786. $('#emptyDialog').dialog({
  787. classes: {
  788. 'ui-dialog-titlebar-close': 'btn-close'
  789. },
  790. width: 'auto',
  791. height: 'auto',
  792. buttons: dlgBtns
  793. });
  794. });
  795. $('a[href="#clearMonitorConfig"]').on('click', function (event) {
  796. event.preventDefault();
  797. if (isStorageSupported('localStorage')) {
  798. window.localStorage.removeItem('monitorCharts');
  799. window.localStorage.removeItem('monitorSettings');
  800. window.localStorage.removeItem('monitorVersion');
  801. }
  802. $(this).hide();
  803. rebuildGrid();
  804. });
  805. $('a[href="#pauseCharts"]').on('click', function (event) {
  806. event.preventDefault();
  807. runtime.redrawCharts = !runtime.redrawCharts;
  808. if (!runtime.redrawCharts) {
  809. $(this).html(Functions.getImage('play') + Messages.strResumeMonitor);
  810. } else {
  811. $(this).html(Functions.getImage('pause') + Messages.strPauseMonitor);
  812. if (!runtime.charts) {
  813. initGrid();
  814. $('a[href="#settingsPopup"]').show();
  815. }
  816. }
  817. return false;
  818. });
  819. $('a[href="#monitorInstructionsDialog"]').on('click', function (event) {
  820. event.preventDefault();
  821. var $dialog = $('#monitorInstructionsDialog');
  822. var dlgBtns = {
  823. [Messages.strClose]: {
  824. text: Messages.strClose,
  825. class: 'btn btn-primary',
  826. click: function () {
  827. $(this).dialog('close');
  828. }
  829. }
  830. };
  831. $dialog.dialog({
  832. classes: {
  833. 'ui-dialog-titlebar-close': 'btn-close'
  834. },
  835. width: '60%',
  836. height: 'auto',
  837. buttons: dlgBtns
  838. }).find('img.ajaxIcon').show();
  839. var loadLogVars = function (getvars) {
  840. var vars = {
  841. 'ajax_request': true,
  842. 'server': CommonParams.get('server')
  843. };
  844. if (getvars) {
  845. $.extend(vars, getvars);
  846. }
  847. $.post('index.php?route=/server/status/monitor/log-vars', vars, function (data) {
  848. var logVars;
  849. if (typeof data !== 'undefined' && data.success === true) {
  850. logVars = data.message;
  851. } else {
  852. return serverResponseError();
  853. }
  854. var icon = Functions.getImage('s_success');
  855. var msg = '';
  856. var str = '';
  857. if (logVars.general_log === 'ON') {
  858. if (logVars.slow_query_log === 'ON') {
  859. msg = Messages.strBothLogOn;
  860. } else {
  861. msg = Messages.strGenLogOn;
  862. }
  863. }
  864. if (msg.length === 0 && logVars.slow_query_log === 'ON') {
  865. msg = Messages.strSlowLogOn;
  866. }
  867. if (msg.length === 0) {
  868. icon = Functions.getImage('s_error');
  869. msg = Messages.strBothLogOff;
  870. }
  871. str = '<b>' + Messages.strCurrentSettings + '</b><br><div class="smallIndent">';
  872. str += icon + msg + '<br>';
  873. if (logVars.log_output !== 'TABLE') {
  874. str += Functions.getImage('s_error') + ' ' + Messages.strLogOutNotTable + '<br>';
  875. } else {
  876. str += Functions.getImage('s_success') + ' ' + Messages.strLogOutIsTable + '<br>';
  877. }
  878. if (logVars.slow_query_log === 'ON') {
  879. if (logVars.long_query_time > 2) {
  880. str += Functions.getImage('s_attention') + ' ';
  881. str += Functions.sprintf(Messages.strSmallerLongQueryTimeAdvice, logVars.long_query_time);
  882. str += '<br>';
  883. }
  884. if (logVars.long_query_time < 2) {
  885. str += Functions.getImage('s_success') + ' ';
  886. str += Functions.sprintf(Messages.strLongQueryTimeSet, logVars.long_query_time);
  887. str += '<br>';
  888. }
  889. }
  890. str += '</div>';
  891. if (isSuperUser) {
  892. str += '<p></p><b>' + Messages.strChangeSettings + '</b>';
  893. str += '<div class="smallIndent">';
  894. str += Messages.strSettingsAppliedGlobal + '<br>';
  895. var varValue = 'TABLE';
  896. if (logVars.log_output === 'TABLE') {
  897. varValue = 'FILE';
  898. }
  899. str += '- <a class="set" href="#log_output-' + varValue + '">';
  900. str += Functions.sprintf(Messages.strSetLogOutput, varValue);
  901. str += ' </a><br>';
  902. if (logVars.general_log !== 'ON') {
  903. str += '- <a class="set" href="#general_log-ON">';
  904. str += Functions.sprintf(Messages.strEnableVar, 'general_log');
  905. str += ' </a><br>';
  906. } else {
  907. str += '- <a class="set" href="#general_log-OFF">';
  908. str += Functions.sprintf(Messages.strDisableVar, 'general_log');
  909. str += ' </a><br>';
  910. }
  911. if (logVars.slow_query_log !== 'ON') {
  912. str += '- <a class="set" href="#slow_query_log-ON">';
  913. str += Functions.sprintf(Messages.strEnableVar, 'slow_query_log');
  914. str += ' </a><br>';
  915. } else {
  916. str += '- <a class="set" href="#slow_query_log-OFF">';
  917. str += Functions.sprintf(Messages.strDisableVar, 'slow_query_log');
  918. str += ' </a><br>';
  919. }
  920. varValue = 5;
  921. if (logVars.long_query_time > 2) {
  922. varValue = 1;
  923. }
  924. str += '- <a class="set" href="#long_query_time-' + varValue + '">';
  925. str += Functions.sprintf(Messages.setSetLongQueryTime, varValue);
  926. str += ' </a><br>';
  927. } else {
  928. str += Messages.strNoSuperUser + '<br>';
  929. }
  930. str += '</div>';
  931. $dialog.find('div.monitorUse').toggle(logVars.log_output === 'TABLE' && (logVars.slow_query_log === 'ON' || logVars.general_log === 'ON'));
  932. $dialog.find('div.ajaxContent').html(str);
  933. $dialog.find('img.ajaxIcon').hide();
  934. $dialog.find('a.set').on('click', function () {
  935. var nameValue = $(this).attr('href').split('-');
  936. loadLogVars({
  937. varName: nameValue[0].substr(1),
  938. varValue: nameValue[1]
  939. });
  940. $dialog.find('img.ajaxIcon').show();
  941. });
  942. });
  943. };
  944. loadLogVars();
  945. return false;
  946. });
  947. $('input[name="chartType"]').on('change', function () {
  948. $('#chartVariableSettings').toggle(this.checked && this.value === 'variable');
  949. var title = $('input[name="chartTitle"]').val();
  950. if (title === Messages.strChartTitle || title === $('label[for="' + $('input[name="chartTitle"]').data('lastRadio') + '"]').text()) {
  951. $('input[name="chartTitle"]').data('lastRadio', $(this).attr('id')).val($('label[for="' + $(this).attr('id') + '"]').text());
  952. }
  953. });
  954. $('input[name="useDivisor"]').on('change', function () {
  955. $('span.divisorInput').toggle(this.checked);
  956. });
  957. $('input[name="useUnit"]').on('change', function () {
  958. $('span.unitInput').toggle(this.checked);
  959. });
  960. $('select[name="varChartList"]').on('change', function () {
  961. if (this.selectedIndex !== 0) {
  962. $('#variableInput').val(this.value);
  963. }
  964. });
  965. $('a[href="#kibDivisor"]').on('click', function (event) {
  966. event.preventDefault();
  967. $('input[name="valueDivisor"]').val(1024);
  968. $('input[name="valueUnit"]').val(Messages.strKiB);
  969. $('span.unitInput').toggle(true);
  970. $('input[name="useUnit"]').prop('checked', true);
  971. return false;
  972. });
  973. $('a[href="#mibDivisor"]').on('click', function (event) {
  974. event.preventDefault();
  975. $('input[name="valueDivisor"]').val(1024 * 1024);
  976. $('input[name="valueUnit"]').val(Messages.strMiB);
  977. $('span.unitInput').toggle(true);
  978. $('input[name="useUnit"]').prop('checked', true);
  979. return false;
  980. });
  981. $('a[href="#submitClearSeries"]').on('click', function (event) {
  982. event.preventDefault();
  983. $('#seriesPreview').html('<i>' + Messages.strNone + '</i>');
  984. newChart = null;
  985. $('#clearSeriesLink').hide();
  986. });
  987. $('a[href="#submitAddSeries"]').on('click', function (event) {
  988. event.preventDefault();
  989. if ($('#variableInput').val() === '') {
  990. return false;
  991. }
  992. if (newChart === null) {
  993. $('#seriesPreview').html('');
  994. newChart = {
  995. title: $('input[name="chartTitle"]').val(),
  996. nodes: [],
  997. series: [],
  998. maxYLabel: 0
  999. };
  1000. }
  1001. var serie = {
  1002. dataPoints: [{
  1003. type: 'statusvar',
  1004. name: $('#variableInput').val()
  1005. }],
  1006. display: $('input[name="differentialValue"]').prop('checked') ? 'differential' : ''
  1007. };
  1008. if (serie.dataPoints[0].name === 'Processes') {
  1009. serie.dataPoints[0].type = 'proc';
  1010. }
  1011. if ($('input[name="useDivisor"]').prop('checked')) {
  1012. serie.valueDivisor = parseInt($('input[name="valueDivisor"]').val(), 10);
  1013. }
  1014. if ($('input[name="useUnit"]').prop('checked')) {
  1015. serie.unit = $('input[name="valueUnit"]').val();
  1016. }
  1017. var str = serie.display === 'differential' ? ', ' + Messages.strDifferential : '';
  1018. str += serie.valueDivisor ? ', ' + Functions.sprintf(Messages.strDividedBy, serie.valueDivisor) : '';
  1019. str += serie.unit ? ', ' + Messages.strUnit + ': ' + serie.unit : '';
  1020. var newSeries = {
  1021. label: $('#variableInput').val().replace(/_/g, ' ')
  1022. };
  1023. newChart.series.push(newSeries);
  1024. $('#seriesPreview').append('- ' + Functions.escapeHtml(newSeries.label + str) + '<br>');
  1025. newChart.nodes.push(serie);
  1026. $('#variableInput').val('');
  1027. $('input[name="differentialValue"]').prop('checked', true);
  1028. $('input[name="useDivisor"]').prop('checked', false);
  1029. $('input[name="useUnit"]').prop('checked', false);
  1030. $('input[name="useDivisor"]').trigger('change');
  1031. $('input[name="useUnit"]').trigger('change');
  1032. $('select[name="varChartList"]').get(0).selectedIndex = 0;
  1033. $('#clearSeriesLink').show();
  1034. return false;
  1035. });
  1036. $('#variableInput').autocomplete({
  1037. source: variableNames
  1038. });
  1039. /* Initializes the monitor, called only once */
  1040. function initGrid() {
  1041. var i;
  1042. /* Apply default values & config */
  1043. if (isStorageSupported('localStorage')) {
  1044. if (typeof window.localStorage.monitorCharts !== 'undefined') {
  1045. runtime.charts = JSON.parse(window.localStorage.monitorCharts);
  1046. }
  1047. if (typeof window.localStorage.monitorSettings !== 'undefined') {
  1048. monitorSettings = JSON.parse(window.localStorage.monitorSettings);
  1049. }
  1050. $('a[href="#clearMonitorConfig"]').toggle(runtime.charts !== null);
  1051. if (runtime.charts !== null && typeof window.localStorage.monitorVersion !== 'undefined' && monitorProtocolVersion !== window.localStorage.monitorVersion) {
  1052. $('#emptyDialog').dialog({
  1053. classes: {
  1054. 'ui-dialog-titlebar-close': 'btn-close'
  1055. },
  1056. title: Messages.strIncompatibleMonitorConfig
  1057. });
  1058. $('#emptyDialog').html(Messages.strIncompatibleMonitorConfigDescription);
  1059. var dlgBtns = {
  1060. [Messages.strClose]: {
  1061. text: Messages.strClose,
  1062. class: 'btn btn-primary',
  1063. click: function () {
  1064. $(this).dialog('close');
  1065. }
  1066. }
  1067. };
  1068. $('#emptyDialog').dialog({
  1069. classes: {
  1070. 'ui-dialog-titlebar-close': 'btn-close'
  1071. },
  1072. width: 400,
  1073. buttons: dlgBtns
  1074. });
  1075. }
  1076. }
  1077. if (runtime.charts === null) {
  1078. runtime.charts = defaultChartGrid;
  1079. }
  1080. if (monitorSettings === null) {
  1081. monitorSettings = defaultMonitorSettings;
  1082. }
  1083. $('select[name="gridChartRefresh"]').val(monitorSettings.gridRefresh / 1000);
  1084. $('select[name="chartColumns"]').val(monitorSettings.columns);
  1085. if (monitorSettings.gridMaxPoints === 'auto') {
  1086. runtime.gridMaxPoints = Math.round((monitorSettings.chartSize.width - 40) / 12);
  1087. } else {
  1088. runtime.gridMaxPoints = monitorSettings.gridMaxPoints;
  1089. }
  1090. runtime.xmin = new Date().getTime() - serverTimeDiff - runtime.gridMaxPoints * monitorSettings.gridRefresh;
  1091. runtime.xmax = new Date().getTime() - serverTimeDiff + monitorSettings.gridRefresh;
  1092. /* Calculate how much spacing there is between each chart */
  1093. $('#chartGrid').html('<tr><td></td><td></td></tr><tr><td></td><td></td></tr>');
  1094. chartSpacing = {
  1095. width: $('#chartGrid').find('td').eq(1).offset().left - $('#chartGrid').find('td').eq(0).offset().left,
  1096. height: $('#chartGrid').find('tr').eq(1).find('td').eq(1).offset().top - $('#chartGrid').find('tr').eq(0).find('td').eq(0).offset().top
  1097. };
  1098. $('#chartGrid').html('');
  1099. /* Add all charts - in correct order */
  1100. var keys = [];
  1101. $.each(runtime.charts, function (key) {
  1102. keys.push(key);
  1103. });
  1104. keys.sort();
  1105. for (i = 0; i < keys.length; i++) {
  1106. addChart(runtime.charts[keys[i]], true);
  1107. }
  1108. /* Fill in missing cells */
  1109. var numCharts = $('#chartGrid').find('.monitorChart').length;
  1110. var numMissingCells = (monitorSettings.columns - numCharts % monitorSettings.columns) % monitorSettings.columns;
  1111. for (i = 0; i < numMissingCells; i++) {
  1112. $('#chartGrid').find('tr').last().append('<td></td>');
  1113. }
  1114. // Empty cells should keep their size so you can drop onto them
  1115. calculateChartSize();
  1116. $('#chartGrid').find('tr td').css('width', chartSize.width + 'px');
  1117. buildRequiredDataList();
  1118. refreshChartGrid();
  1119. }
  1120. /* Calls destroyGrid() and initGrid(), but before doing so it saves the chart
  1121. * data from each chart and restores it after the monitor is initialized again */
  1122. function rebuildGrid() {
  1123. var oldData = null;
  1124. if (runtime.charts) {
  1125. oldData = {};
  1126. $.each(runtime.charts, function (key, chartObj) {
  1127. for (var i = 0, l = chartObj.nodes.length; i < l; i++) {
  1128. oldData[chartObj.nodes[i].dataPoint] = [];
  1129. for (var j = 0, ll = chartObj.chart.series[i].data.length; j < ll; j++) {
  1130. oldData[chartObj.nodes[i].dataPoint].push([chartObj.chart.series[i].data[j].x, chartObj.chart.series[i].data[j].y]);
  1131. }
  1132. }
  1133. });
  1134. }
  1135. destroyGrid();
  1136. initGrid();
  1137. }
  1138. /* Calculates the dynamic chart size that depends on the column width */
  1139. function calculateChartSize() {
  1140. var panelWidth;
  1141. if ($('body').height() > $(window).height()) {
  1142. // has vertical scroll bar
  1143. panelWidth = $('#logTable').innerWidth();
  1144. } else {
  1145. panelWidth = $('#logTable').innerWidth() - 10; // leave some space for vertical scroll bar
  1146. }
  1147. var wdt = panelWidth;
  1148. var windowWidth = $(window).width();
  1149. if (windowWidth > 768) {
  1150. wdt = (panelWidth - monitorSettings.columns * Math.abs(chartSpacing.width)) / monitorSettings.columns;
  1151. }
  1152. chartSize = {
  1153. width: Math.floor(wdt),
  1154. height: Math.floor(0.75 * wdt)
  1155. };
  1156. }
  1157. /* Adds a chart to the chart grid */
  1158. function addChart(chartObj, initialize) {
  1159. var i;
  1160. var settings = {
  1161. title: Functions.escapeHtml(chartObj.title),
  1162. grid: {
  1163. drawBorder: false,
  1164. shadow: false,
  1165. background: 'rgba(0,0,0,0)'
  1166. },
  1167. axes: {
  1168. xaxis: {
  1169. renderer: $.jqplot.DateAxisRenderer,
  1170. tickOptions: {
  1171. formatString: '%H:%M:%S',
  1172. showGridline: false
  1173. },
  1174. min: runtime.xmin,
  1175. max: runtime.xmax
  1176. },
  1177. yaxis: {
  1178. min: 0,
  1179. max: 100,
  1180. tickInterval: 20
  1181. }
  1182. },
  1183. seriesDefaults: {
  1184. rendererOptions: {
  1185. smooth: true
  1186. },
  1187. showLine: true,
  1188. lineWidth: 2,
  1189. markerOptions: {
  1190. size: 6
  1191. }
  1192. },
  1193. highlighter: {
  1194. show: true
  1195. }
  1196. };
  1197. if (settings.title === Messages.strSystemCPUUsage || settings.title === Messages.strQueryCacheEfficiency) {
  1198. settings.axes.yaxis.tickOptions = {
  1199. formatString: '%d %%'
  1200. };
  1201. } else if (settings.title === Messages.strSystemMemory || settings.title === Messages.strSystemSwap) {
  1202. settings.stackSeries = true;
  1203. settings.axes.yaxis.tickOptions = {
  1204. formatter: $.jqplot.byteFormatter(2) // MiB
  1205. };
  1206. } else if (settings.title === Messages.strTraffic) {
  1207. settings.axes.yaxis.tickOptions = {
  1208. formatter: $.jqplot.byteFormatter(1) // KiB
  1209. };
  1210. } else if (settings.title === Messages.strQuestions || settings.title === Messages.strConnections) {
  1211. settings.axes.yaxis.tickOptions = {
  1212. formatter: function (format, val) {
  1213. if (Math.abs(val) >= 1000000) {
  1214. return $.jqplot.sprintf('%.3g M', val / 1000000);
  1215. } else if (Math.abs(val) >= 1000) {
  1216. return $.jqplot.sprintf('%.3g k', val / 1000);
  1217. } else {
  1218. return $.jqplot.sprintf('%d', val);
  1219. }
  1220. }
  1221. };
  1222. }
  1223. settings.series = chartObj.series;
  1224. if ($('#' + 'gridchart' + runtime.chartAI).length === 0) {
  1225. var numCharts = $('#chartGrid').find('.monitorChart').length;
  1226. if (numCharts === 0 || numCharts % monitorSettings.columns === 0) {
  1227. $('#chartGrid').append('<tr></tr>');
  1228. }
  1229. if (!chartSize) {
  1230. calculateChartSize();
  1231. }
  1232. $('#chartGrid').find('tr').last().append('<td><div id="gridChartContainer' + runtime.chartAI + '" class="">' + '<div class="ui-state-default monitorChart"' + ' id="gridchart' + runtime.chartAI + '"' + ' style="width:' + chartSize.width + 'px; height:' + chartSize.height + 'px;"></div>' + '</div></td>');
  1233. }
  1234. // Set series' data as [0,0], smooth lines won't plot with data array having null values.
  1235. // also chart won't plot initially with no data and data comes on refreshChartGrid()
  1236. var series = [];
  1237. for (i in chartObj.series) {
  1238. series.push([[0, 0]]);
  1239. }
  1240. var tempTooltipContentEditor = function (str, seriesIndex, pointIndex, plot) {
  1241. var j;
  1242. // TODO: move style to theme CSS
  1243. var tooltipHtml = '<div id="tooltip_editor">';
  1244. // x value i.e. time
  1245. var timeValue = str.split(',')[0];
  1246. var seriesValue;
  1247. tooltipHtml += 'Time: ' + timeValue;
  1248. tooltipHtml += '<span id="tooltip_font">';
  1249. // Add y values to the tooltip per series
  1250. for (j in plot.series) {
  1251. // get y value if present
  1252. if (plot.series[j].data.length > pointIndex) {
  1253. seriesValue = plot.series[j].data[pointIndex][1];
  1254. } else {
  1255. return;
  1256. }
  1257. var seriesLabel = plot.series[j].label;
  1258. var seriesColor = plot.series[j].color;
  1259. // format y value
  1260. if (plot.series[0]._yaxis.tickOptions.formatter) {
  1261. // eslint-disable-line no-underscore-dangle
  1262. // using formatter function
  1263. // eslint-disable-next-line no-underscore-dangle
  1264. seriesValue = plot.series[0]._yaxis.tickOptions.formatter('%s', seriesValue);
  1265. } else if (plot.series[0]._yaxis.tickOptions.formatString) {
  1266. // eslint-disable-line no-underscore-dangle
  1267. // using format string
  1268. // eslint-disable-next-line no-underscore-dangle
  1269. seriesValue = Functions.sprintf(plot.series[0]._yaxis.tickOptions.formatString, seriesValue);
  1270. }
  1271. tooltipHtml += '<br><span style="color:' + seriesColor + '">' + seriesLabel + ': ' + seriesValue + '</span>';
  1272. }
  1273. tooltipHtml += '</span></div>';
  1274. return tooltipHtml;
  1275. };
  1276. // set Tooltip for each series
  1277. for (i in settings.series) {
  1278. settings.series[i].highlighter = {
  1279. show: true,
  1280. tooltipContentEditor: tempTooltipContentEditor
  1281. };
  1282. }
  1283. chartObj.chart = $.jqplot('gridchart' + runtime.chartAI, series, settings);
  1284. // remove [0,0] after plotting
  1285. for (i in chartObj.chart.series) {
  1286. chartObj.chart.series[i].data.shift();
  1287. }
  1288. var $legend = $('<div></div>').css('padding', '0.5em');
  1289. for (i in chartObj.chart.series) {
  1290. $legend.append($('<div></div>').append($('<div>').css({
  1291. width: '1em',
  1292. height: '1em',
  1293. background: chartObj.chart.seriesColors[i]
  1294. }).addClass('float-start')).append($('<div>').text(chartObj.chart.series[i].label).addClass('float-start')).append($('<div class="clearfloat">')).addClass('float-start'));
  1295. }
  1296. $('#gridchart' + runtime.chartAI).parent().append($legend);
  1297. if (initialize !== true) {
  1298. runtime.charts['c' + runtime.chartAI] = chartObj;
  1299. buildRequiredDataList();
  1300. }
  1301. // time span selection
  1302. $('#gridchart' + runtime.chartAI).on('jqplotMouseDown', function (ev, gridpos, datapos) {
  1303. drawTimeSpan = true;
  1304. selectionTimeDiff.push(datapos.xaxis);
  1305. if ($('#selection_box').length) {
  1306. $('#selection_box').remove();
  1307. }
  1308. var selectionBox = $('<div id="selection_box" >');
  1309. // eslint-disable-next-line compat/compat
  1310. $(document.body).append(selectionBox);
  1311. selectionStartX = ev.pageX;
  1312. selectionStartY = ev.pageY;
  1313. selectionBox.attr({
  1314. id: 'selection_box'
  1315. }).css({
  1316. top: selectionStartY - gridpos.y,
  1317. left: selectionStartX
  1318. }).fadeIn();
  1319. });
  1320. $('#gridchart' + runtime.chartAI).on('jqplotMouseUp', function (ev, gridpos, datapos) {
  1321. if (!drawTimeSpan || editMode) {
  1322. return;
  1323. }
  1324. selectionTimeDiff.push(datapos.xaxis);
  1325. if (selectionTimeDiff[1] <= selectionTimeDiff[0]) {
  1326. selectionTimeDiff = [];
  1327. return;
  1328. }
  1329. // get date from timestamp
  1330. var min = new Date(Math.ceil(selectionTimeDiff[0]));
  1331. var max = new Date(Math.ceil(selectionTimeDiff[1]));
  1332. getLogAnalyseDialog(min, max);
  1333. selectionTimeDiff = [];
  1334. drawTimeSpan = false;
  1335. });
  1336. $('#gridchart' + runtime.chartAI).on('jqplotMouseMove', function (ev) {
  1337. if (!drawTimeSpan || editMode) {
  1338. return;
  1339. }
  1340. if (selectionStartX !== undefined) {
  1341. $('#selection_box').css({
  1342. width: Math.ceil(ev.pageX - selectionStartX)
  1343. }).fadeIn();
  1344. }
  1345. });
  1346. $('#gridchart' + runtime.chartAI).on('jqplotMouseLeave', function () {
  1347. drawTimeSpan = false;
  1348. });
  1349. // eslint-disable-next-line compat/compat
  1350. $(document.body).on('mouseup', function () {
  1351. if ($('#selection_box').length) {
  1352. $('#selection_box').remove();
  1353. }
  1354. });
  1355. // Edit, Print icon only in edit mode
  1356. $('#chartGrid').find('div svg').find('*[zIndex=20], *[zIndex=21], *[zIndex=19]').toggle(editMode);
  1357. runtime.chartAI++;
  1358. }
  1359. function getLogAnalyseDialog(min, max) {
  1360. var $logAnalyseDialog = $('#logAnalyseDialog');
  1361. var $dateStart = $logAnalyseDialog.find('input[name="dateStart"]');
  1362. var $dateEnd = $logAnalyseDialog.find('input[name="dateEnd"]');
  1363. $dateStart.prop('readonly', true);
  1364. $dateEnd.prop('readonly', true);
  1365. var dlgBtns = {
  1366. [Messages.strFromSlowLog]: {
  1367. text: Messages.strFromSlowLog,
  1368. class: 'btn btn-secondary'
  1369. },
  1370. [Messages.strFromGeneralLog]: {
  1371. text: Messages.strFromGeneralLog,
  1372. class: 'btn btn-secondary'
  1373. }
  1374. };
  1375. dlgBtns[Messages.strFromSlowLog].click = function () {
  1376. loadLog('slow', min, max);
  1377. $(this).dialog('close');
  1378. };
  1379. dlgBtns[Messages.strFromGeneralLog].click = function () {
  1380. loadLog('general', min, max);
  1381. $(this).dialog('close');
  1382. };
  1383. $logAnalyseDialog.dialog({
  1384. classes: {
  1385. 'ui-dialog-titlebar-close': 'btn-close'
  1386. },
  1387. width: 'auto',
  1388. height: 'auto',
  1389. buttons: dlgBtns
  1390. });
  1391. Functions.addDatepicker($dateStart, 'datetime', {
  1392. showMillisec: false,
  1393. showMicrosec: false,
  1394. timeFormat: 'HH:mm:ss',
  1395. firstDay: firstDayOfCalendar
  1396. });
  1397. Functions.addDatepicker($dateEnd, 'datetime', {
  1398. showMillisec: false,
  1399. showMicrosec: false,
  1400. timeFormat: 'HH:mm:ss',
  1401. firstDay: firstDayOfCalendar
  1402. });
  1403. $dateStart.datepicker('setDate', min);
  1404. $dateEnd.datepicker('setDate', max);
  1405. }
  1406. function loadLog(type, min, max) {
  1407. var dateStart = Date.parse($('#logAnalyseDialog').find('input[name="dateStart"]').datepicker('getDate')) || min;
  1408. var dateEnd = Date.parse($('#logAnalyseDialog').find('input[name="dateEnd"]').datepicker('getDate')) || max;
  1409. loadLogStatistics({
  1410. src: type,
  1411. start: dateStart,
  1412. end: dateEnd,
  1413. removeVariables: $('#removeVariables').prop('checked'),
  1414. limitTypes: $('#limitTypes').prop('checked')
  1415. });
  1416. }
  1417. /* Called in regular intervals, this function updates the values of each chart in the grid */
  1418. function refreshChartGrid() {
  1419. /* Send to server */
  1420. runtime.refreshRequest = $.post('index.php?route=/server/status/monitor/chart', {
  1421. 'ajax_request': true,
  1422. 'requiredData': JSON.stringify(runtime.dataList),
  1423. 'server': CommonParams.get('server')
  1424. }, function (data) {
  1425. var chartData;
  1426. if (typeof data !== 'undefined' && data.success === true) {
  1427. chartData = data.message;
  1428. } else {
  1429. return serverResponseError();
  1430. }
  1431. var value;
  1432. var i = 0;
  1433. var diff;
  1434. var total;
  1435. /* Update values in each graph */
  1436. $.each(runtime.charts, function (orderKey, elem) {
  1437. var key = elem.chartID;
  1438. // If newly added chart, we have no data for it yet
  1439. if (!chartData[key]) {
  1440. return;
  1441. }
  1442. // Draw all series
  1443. total = 0;
  1444. for (var j = 0; j < elem.nodes.length; j++) {
  1445. // Update x-axis
  1446. if (i === 0 && j === 0) {
  1447. if (oldChartData === null) {
  1448. diff = chartData.x - runtime.xmax;
  1449. } else {
  1450. diff = parseInt(chartData.x - oldChartData.x, 10);
  1451. }
  1452. runtime.xmin += diff;
  1453. runtime.xmax += diff;
  1454. }
  1455. // elem.chart.xAxis[0].setExtremes(runtime.xmin, runtime.xmax, false);
  1456. /* Calculate y value */
  1457. // If transform function given, use it
  1458. if (elem.nodes[j].transformFn) {
  1459. value = chartValueTransform(elem.nodes[j].transformFn, chartData[key][j],
  1460. // Check if first iteration (oldChartData==null), or if newly added chart oldChartData[key]==null
  1461. oldChartData === null || oldChartData[key] === null || oldChartData[key] === undefined ? null : oldChartData[key][j]);
  1462. // Otherwise use original value and apply differential and divisor if given,
  1463. // in this case we have only one data point per series - located at chartData[key][j][0]
  1464. } else {
  1465. value = parseFloat(chartData[key][j][0].value);
  1466. if (elem.nodes[j].display === 'differential') {
  1467. if (oldChartData === null || oldChartData[key] === null || oldChartData[key] === undefined) {
  1468. continue;
  1469. }
  1470. value -= oldChartData[key][j][0].value;
  1471. }
  1472. if (elem.nodes[j].valueDivisor) {
  1473. value = value / elem.nodes[j].valueDivisor;
  1474. }
  1475. }
  1476. // Set y value, if defined
  1477. if (value !== undefined) {
  1478. elem.chart.series[j].data.push([chartData.x, value]);
  1479. if (value > elem.maxYLabel) {
  1480. elem.maxYLabel = value;
  1481. } else if (elem.maxYLabel === 0) {
  1482. elem.maxYLabel = 0.5;
  1483. }
  1484. // free old data point values and update maxYLabel
  1485. if (elem.chart.series[j].data.length > runtime.gridMaxPoints && elem.chart.series[j].data[0][0] < runtime.xmin) {
  1486. // check if the next freeable point is highest
  1487. if (elem.maxYLabel <= elem.chart.series[j].data[0][1]) {
  1488. elem.chart.series[j].data.splice(0, elem.chart.series[j].data.length - runtime.gridMaxPoints);
  1489. elem.maxYLabel = getMaxYLabel(elem.chart.series[j].data);
  1490. } else {
  1491. elem.chart.series[j].data.splice(0, elem.chart.series[j].data.length - runtime.gridMaxPoints);
  1492. }
  1493. }
  1494. if (elem.title === Messages.strSystemMemory || elem.title === Messages.strSystemSwap) {
  1495. total += value;
  1496. }
  1497. }
  1498. }
  1499. // update chart options
  1500. // keep ticks number/positioning consistent while refreshrate changes
  1501. var tickInterval = (runtime.xmax - runtime.xmin) / 5;
  1502. elem.chart.axes.xaxis.ticks = [runtime.xmax - tickInterval * 4, runtime.xmax - tickInterval * 3, runtime.xmax - tickInterval * 2, runtime.xmax - tickInterval, runtime.xmax];
  1503. if (elem.title !== Messages.strSystemCPUUsage && elem.title !== Messages.strQueryCacheEfficiency && elem.title !== Messages.strSystemMemory && elem.title !== Messages.strSystemSwap) {
  1504. elem.chart.axes.yaxis.max = Math.ceil(elem.maxYLabel * 1.1);
  1505. elem.chart.axes.yaxis.tickInterval = Math.ceil(elem.maxYLabel * 1.1 / 5);
  1506. } else if (elem.title === Messages.strSystemMemory || elem.title === Messages.strSystemSwap) {
  1507. elem.chart.axes.yaxis.max = Math.ceil(total * 1.1 / 100) * 100;
  1508. elem.chart.axes.yaxis.tickInterval = Math.ceil(total * 1.1 / 5);
  1509. }
  1510. i++;
  1511. if (runtime.redrawCharts) {
  1512. elem.chart.replot();
  1513. }
  1514. });
  1515. oldChartData = chartData;
  1516. runtime.refreshTimeout = setTimeout(refreshChartGrid, monitorSettings.gridRefresh);
  1517. });
  1518. }
  1519. /* Function to get highest plotted point's y label, to scale the chart,
  1520. * TODO: make jqplot's autoscale:true work here
  1521. */
  1522. function getMaxYLabel(dataValues) {
  1523. var maxY = dataValues[0][1];
  1524. $.each(dataValues, function (k, v) {
  1525. maxY = v[1] > maxY ? v[1] : maxY;
  1526. });
  1527. return maxY;
  1528. }
  1529. /* Function that supplies special value transform functions for chart values */
  1530. function chartValueTransform(name, cur, prev) {
  1531. switch (name) {
  1532. case 'cpu-linux':
  1533. if (prev === null) {
  1534. return undefined;
  1535. }
  1536. // cur and prev are datapoint arrays, but containing
  1537. // only 1 element for cpu-linux
  1538. var newCur = cur[0];
  1539. var newPrev = prev[0];
  1540. var diffTotal = newCur.busy + newCur.idle - (newPrev.busy + newPrev.idle);
  1541. var diffIdle = newCur.idle - newPrev.idle;
  1542. return 100 * (diffTotal - diffIdle) / diffTotal;
  1543. // Query cache efficiency (%)
  1544. case 'qce':
  1545. if (prev === null) {
  1546. return undefined;
  1547. }
  1548. // cur[0].value is Qcache_hits, cur[1].value is Com_select
  1549. var diffQHits = cur[0].value - prev[0].value;
  1550. // No NaN please :-)
  1551. if (cur[1].value - prev[1].value === 0) {
  1552. return 0;
  1553. }
  1554. return diffQHits / (cur[1].value - prev[1].value + diffQHits) * 100;
  1555. // Query cache usage (%)
  1556. case 'qcu':
  1557. if (cur[1].value === 0) {
  1558. return 0;
  1559. }
  1560. // cur[0].value is Qcache_free_memory, cur[1].value is query_cache_size
  1561. return 100 - cur[0].value / cur[1].value * 100;
  1562. }
  1563. return undefined;
  1564. }
  1565. /* Build list of nodes that need to be retrieved from server.
  1566. * It creates something like a stripped down version of the runtime.charts object.
  1567. */
  1568. function buildRequiredDataList() {
  1569. runtime.dataList = {};
  1570. // Store an own id, because the property name is subject of reordering,
  1571. // thus destroying our mapping with runtime.charts <=> runtime.dataList
  1572. var chartID = 0;
  1573. $.each(runtime.charts, function (key, chart) {
  1574. runtime.dataList[chartID] = [];
  1575. for (var i = 0, l = chart.nodes.length; i < l; i++) {
  1576. runtime.dataList[chartID][i] = chart.nodes[i].dataPoints;
  1577. }
  1578. runtime.charts[key].chartID = chartID;
  1579. chartID++;
  1580. });
  1581. }
  1582. /* Loads the log table data, generates the table and handles the filters */
  1583. function loadLogStatistics(opts) {
  1584. var logRequest = null;
  1585. if (!opts.removeVariables) {
  1586. opts.removeVariables = false;
  1587. }
  1588. if (!opts.limitTypes) {
  1589. opts.limitTypes = false;
  1590. }
  1591. $('#emptyDialog').dialog({
  1592. classes: {
  1593. 'ui-dialog-titlebar-close': 'btn-close'
  1594. },
  1595. title: Messages.strAnalysingLogsTitle
  1596. });
  1597. $('#emptyDialog').html(Messages.strAnalysingLogs + ' <img class="ajaxIcon" src="' + themeImagePath + 'ajax_clock_small.gif" alt="">');
  1598. var dlgBtns = {
  1599. [Messages.strCancelRequest]: {
  1600. text: Messages.strCancelRequest,
  1601. class: 'btn btn-primary'
  1602. }
  1603. };
  1604. dlgBtns[Messages.strCancelRequest].click = function () {
  1605. if (logRequest !== null) {
  1606. logRequest.abort();
  1607. }
  1608. $(this).dialog('close');
  1609. };
  1610. $('#emptyDialog').dialog({
  1611. classes: {
  1612. 'ui-dialog-titlebar-close': 'btn-close'
  1613. },
  1614. width: 'auto',
  1615. height: 'auto',
  1616. buttons: dlgBtns
  1617. });
  1618. var url = 'index.php?route=/server/status/monitor/slow-log';
  1619. if (opts.src === 'general') {
  1620. url = 'index.php?route=/server/status/monitor/general-log';
  1621. }
  1622. logRequest = $.post(url, {
  1623. 'ajax_request': true,
  1624. 'time_start': Math.round(opts.start / 1000),
  1625. 'time_end': Math.round(opts.end / 1000),
  1626. 'removeVariables': opts.removeVariables,
  1627. 'limitTypes': opts.limitTypes,
  1628. 'server': CommonParams.get('server')
  1629. }, function (data) {
  1630. var logData;
  1631. var dlgBtns = {
  1632. [Messages.strClose]: {
  1633. text: Messages.strClose,
  1634. class: 'btn btn-primary'
  1635. }
  1636. };
  1637. if (typeof data !== 'undefined' && data.success === true) {
  1638. logData = data.message;
  1639. } else {
  1640. return serverResponseError();
  1641. }
  1642. if (logData.rows.length === 0) {
  1643. $('#emptyDialog').dialog({
  1644. classes: {
  1645. 'ui-dialog-titlebar-close': 'btn-close'
  1646. },
  1647. title: Messages.strNoDataFoundTitle
  1648. });
  1649. $('#emptyDialog').html('<p>' + Messages.strNoDataFound + '</p>');
  1650. dlgBtns[Messages.strClose].click = function () {
  1651. $(this).dialog('close');
  1652. };
  1653. $('#emptyDialog').dialog('option', 'buttons', dlgBtns);
  1654. return;
  1655. }
  1656. runtime.logDataCols = buildLogTable(logData, opts.removeVariables);
  1657. /* Show some stats in the dialog */
  1658. $('#emptyDialog').dialog({
  1659. classes: {
  1660. 'ui-dialog-titlebar-close': 'btn-close'
  1661. },
  1662. title: Messages.strLoadingLogs
  1663. });
  1664. $('#emptyDialog').html('<p>' + Messages.strLogDataLoaded + '</p>');
  1665. $.each(logData.sum, function (key, value) {
  1666. var newKey = key.charAt(0).toUpperCase() + key.slice(1).toLowerCase();
  1667. if (newKey === 'Total') {
  1668. newKey = '<b>' + newKey + '</b>';
  1669. }
  1670. $('#emptyDialog').append(newKey + ': ' + value + '<br>');
  1671. });
  1672. /* Add filter options if more than a bunch of rows there to filter */
  1673. if (logData.numRows > 12) {
  1674. $('#logTable').prepend('<fieldset class="pma-fieldset" id="logDataFilter">' + ' <legend>' + Messages.strFiltersForLogTable + '</legend>' + ' <div class="formelement">' + ' <label for="filterQueryText">' + Messages.strFilterByWordRegexp + '</label>' + ' <input name="filterQueryText" type="text" id="filterQueryText">' + ' </div>' + (logData.numRows > 250 ? ' <div class="formelement"><button class="btn btn-secondary" name="startFilterQueryText" id="startFilterQueryText">' + Messages.strFilter + '</button></div>' : '') + ' <div class="formelement">' + ' <input type="checkbox" id="noWHEREData" name="noWHEREData" value="1"> ' + ' <label for="noWHEREData"> ' + Messages.strIgnoreWhereAndGroup + '</label>' + ' </div' + '</fieldset>');
  1675. $('#noWHEREData').on('change', function () {
  1676. filterQueries(true);
  1677. });
  1678. if (logData.numRows > 250) {
  1679. $('#startFilterQueryText').on('click', filterQueries);
  1680. } else {
  1681. $('#filterQueryText').on('keyup', filterQueries);
  1682. }
  1683. }
  1684. dlgBtns[Messages.strJumpToTable] = {
  1685. text: Messages.strJumpToTable,
  1686. class: 'btn btn-secondary',
  1687. click: function () {
  1688. $(this).dialog('close');
  1689. $(document).scrollTop($('#logTable').offset().top);
  1690. }
  1691. };
  1692. $('#emptyDialog').dialog('option', 'buttons', dlgBtns);
  1693. });
  1694. /**
  1695. * Handles the actions performed when the user uses any of the
  1696. * log table filters which are the filter by name and grouping
  1697. * with ignoring data in WHERE clauses
  1698. *
  1699. * @param {boolean} varFilterChange Should be true when the users enabled or disabled
  1700. * to group queries ignoring data in WHERE clauses
  1701. */
  1702. function filterQueries(varFilterChange) {
  1703. var textFilter;
  1704. var val = $('#filterQueryText').val();
  1705. if (val.length === 0) {
  1706. textFilter = null;
  1707. } else {
  1708. try {
  1709. textFilter = new RegExp(val, 'i');
  1710. $('#filterQueryText').removeClass('error');
  1711. } catch (e) {
  1712. if (e instanceof SyntaxError) {
  1713. $('#filterQueryText').addClass('error');
  1714. textFilter = null;
  1715. }
  1716. }
  1717. }
  1718. var rowSum = 0;
  1719. var totalSum = 0;
  1720. var i = 0;
  1721. var q;
  1722. var noVars = $('#noWHEREData').prop('checked');
  1723. var equalsFilter = /([^=]+)=(\d+|(('|"|).*?[^\\])\4((\s+)|$))/gi;
  1724. var functionFilter = /([a-z0-9_]+)\(.+?\)/gi;
  1725. var filteredQueries = {};
  1726. var filteredQueriesLines = {};
  1727. var hide = false;
  1728. var rowData;
  1729. var queryColumnName = runtime.logDataCols[runtime.logDataCols.length - 2];
  1730. var sumColumnName = runtime.logDataCols[runtime.logDataCols.length - 1];
  1731. var isSlowLog = opts.src === 'slow';
  1732. var columnSums = {};
  1733. // For the slow log we have to count many columns (query_time, lock_time, rows_examined, rows_sent, etc.)
  1734. var countRow = function (query, row) {
  1735. var cells = row.match(/<td>(.*?)<\/td>/gi);
  1736. if (!columnSums[query]) {
  1737. columnSums[query] = [0, 0, 0, 0];
  1738. }
  1739. // lock_time and query_time and displayed in timespan format
  1740. columnSums[query][0] += timeToSec(cells[2].replace(/(<td>|<\/td>)/gi, ''));
  1741. columnSums[query][1] += timeToSec(cells[3].replace(/(<td>|<\/td>)/gi, ''));
  1742. // rows_examind and rows_sent are just numbers
  1743. columnSums[query][2] += parseInt(cells[4].replace(/(<td>|<\/td>)/gi, ''), 10);
  1744. columnSums[query][3] += parseInt(cells[5].replace(/(<td>|<\/td>)/gi, ''), 10);
  1745. };
  1746. // We just assume the sql text is always in the second last column, and that the total count is right of it
  1747. $('#logTable').find('table tbody tr td.queryCell').each(function () {
  1748. var $t = $(this);
  1749. // If query is a SELECT and user enabled or disabled to group
  1750. // queries ignoring data in where statements, we
  1751. // need to re-calculate the sums of each row
  1752. if (varFilterChange && $t.html().match(/^SELECT/i)) {
  1753. if (noVars) {
  1754. // Group on => Sum up identical columns, and hide all but 1
  1755. q = $t.text().replace(equalsFilter, '$1=...$6').trim();
  1756. q = q.replace(functionFilter, ' $1(...)');
  1757. // Js does not specify a limit on property name length,
  1758. // so we can abuse it as index :-)
  1759. if (filteredQueries[q]) {
  1760. filteredQueries[q] += parseInt($t.next().text(), 10);
  1761. totalSum += parseInt($t.next().text(), 10);
  1762. hide = true;
  1763. } else {
  1764. filteredQueries[q] = parseInt($t.next().text(), 10);
  1765. filteredQueriesLines[q] = i;
  1766. $t.text(q);
  1767. }
  1768. if (isSlowLog) {
  1769. countRow(q, $t.parent().html());
  1770. }
  1771. } else {
  1772. // Group off: Restore original columns
  1773. rowData = $t.parent().data('query');
  1774. // Restore SQL text
  1775. $t.text(rowData[queryColumnName]);
  1776. // Restore total count
  1777. $t.next().text(rowData[sumColumnName]);
  1778. // Restore slow log columns
  1779. if (isSlowLog) {
  1780. $t.parent().children('td').eq(2).text(rowData.query_time);
  1781. $t.parent().children('td').eq(3).text(rowData.lock_time);
  1782. $t.parent().children('td').eq(4).text(rowData.rows_sent);
  1783. $t.parent().children('td').eq(5).text(rowData.rows_examined);
  1784. }
  1785. }
  1786. }
  1787. // If not required to be hidden, do we need
  1788. // to hide because of a not matching text filter?
  1789. if (!hide && textFilter !== null && !textFilter.exec($t.text())) {
  1790. hide = true;
  1791. }
  1792. // Now display or hide this column
  1793. if (hide) {
  1794. $t.parent().css('display', 'none');
  1795. } else {
  1796. totalSum += parseInt($t.next().text(), 10);
  1797. rowSum++;
  1798. $t.parent().css('display', '');
  1799. }
  1800. hide = false;
  1801. i++;
  1802. });
  1803. // We finished summarizing counts => Update count values of all grouped entries
  1804. if (varFilterChange) {
  1805. if (noVars) {
  1806. var numCol;
  1807. var row;
  1808. var $table = $('#logTable').find('table tbody');
  1809. $.each(filteredQueriesLines, function (key, value) {
  1810. if (filteredQueries[key] <= 1) {
  1811. return;
  1812. }
  1813. row = $table.children('tr').eq(value);
  1814. numCol = row.children().eq(runtime.logDataCols.length - 1);
  1815. numCol.text(filteredQueries[key]);
  1816. if (isSlowLog) {
  1817. row.children('td').eq(2).text(secToTime(columnSums[key][0]));
  1818. row.children('td').eq(3).text(secToTime(columnSums[key][1]));
  1819. row.children('td').eq(4).text(columnSums[key][2]);
  1820. row.children('td').eq(5).text(columnSums[key][3]);
  1821. }
  1822. });
  1823. }
  1824. $('#logTable').find('table').trigger('update');
  1825. setTimeout(function () {
  1826. $('#logTable').find('table').trigger('sorton', [[[runtime.logDataCols.length - 1, 1]]]);
  1827. }, 0);
  1828. }
  1829. // Display some stats at the bottom of the table
  1830. $('#logTable').find('table tfoot tr').html('<th colspan="' + (runtime.logDataCols.length - 1) + '">' + Messages.strSumRows + ' ' + rowSum + '<span class="float-end">' + Messages.strTotal + '</span></th><th class="text-end">' + totalSum + '</th>');
  1831. }
  1832. }
  1833. /* Turns a timespan (12:12:12) into a number */
  1834. function timeToSec(timeStr) {
  1835. var time = timeStr.split(':');
  1836. return parseInt(time[0], 10) * 3600 + parseInt(time[1], 10) * 60 + parseInt(time[2], 10);
  1837. }
  1838. /* Turns a number into a timespan (100 into 00:01:40) */
  1839. function secToTime(timeInt) {
  1840. var time = timeInt;
  1841. var hours = Math.floor(time / 3600);
  1842. time -= hours * 3600;
  1843. var minutes = Math.floor(time / 60);
  1844. time -= minutes * 60;
  1845. if (hours < 10) {
  1846. hours = '0' + hours;
  1847. }
  1848. if (minutes < 10) {
  1849. minutes = '0' + minutes;
  1850. }
  1851. if (time < 10) {
  1852. time = '0' + time;
  1853. }
  1854. return hours + ':' + minutes + ':' + time;
  1855. }
  1856. /* Constructs the log table out of the retrieved server data */
  1857. function buildLogTable(data, groupInserts) {
  1858. var rows = data.rows;
  1859. var cols = [];
  1860. var $table = $('<table class="table table-striped table-hover align-middle sortable"></table>');
  1861. var $tBody;
  1862. var $tRow;
  1863. var $tCell;
  1864. $('#logTable').html($table);
  1865. var tempPushKey = function (key) {
  1866. cols.push(key);
  1867. };
  1868. var formatValue = function (name, value) {
  1869. if (name === 'user_host') {
  1870. return value.replace(/(\[.*?\])+/g, '');
  1871. }
  1872. return Functions.escapeHtml(value);
  1873. };
  1874. for (var i = 0, l = rows.length; i < l; i++) {
  1875. if (i === 0) {
  1876. $.each(rows[0], tempPushKey);
  1877. $table.append('<thead>' + '<tr><th class="text-nowrap">' + cols.join('</th><th class="text-nowrap">') + '</th></tr>' + '</thead>');
  1878. $table.append($tBody = $('<tbody></tbody>'));
  1879. }
  1880. $tBody.append($tRow = $('<tr class="noclick"></tr>'));
  1881. for (var j = 0, ll = cols.length; j < ll; j++) {
  1882. // Assuming the query column is the second last
  1883. if (j === cols.length - 2 && rows[i][cols[j]].match(/^SELECT/i)) {
  1884. $tRow.append($tCell = $('<td class="linkElem queryCell">' + formatValue(cols[j], rows[i][cols[j]]) + '</td>'));
  1885. $tCell.on('click', openQueryAnalyzer);
  1886. } else {
  1887. $tRow.append('<td>' + formatValue(cols[j], rows[i][cols[j]]) + '</td>');
  1888. }
  1889. $tRow.data('query', rows[i]);
  1890. }
  1891. }
  1892. $table.append('<tfoot>' + '<tr><th colspan="' + (cols.length - 1) + '">' + Messages.strSumRows + ' ' + data.numRows + '<span class="float-end">' + Messages.strTotal + '</span></th><th class="text-end">' + data.sum.TOTAL + '</th></tr></tfoot>');
  1893. // Append a tooltip to the count column, if there exist one
  1894. if ($('#logTable').find('tr').first().find('th').last().text().indexOf('#') > -1) {
  1895. $('#logTable').find('tr').first().find('th').last().append('&nbsp;' + Functions.getImage('b_help', '', {
  1896. 'class': 'qroupedQueryInfoIcon'
  1897. }));
  1898. var tooltipContent = Messages.strCountColumnExplanation;
  1899. if (groupInserts) {
  1900. tooltipContent += '<p>' + Messages.strMoreCountColumnExplanation + '</p>';
  1901. }
  1902. Functions.tooltip($('img.qroupedQueryInfoIcon'), 'img', tooltipContent);
  1903. }
  1904. $('#logTable').find('table').tablesorter({
  1905. sortList: [[cols.length - 1, 1]],
  1906. widgets: ['fast-zebra']
  1907. });
  1908. $('#logTable').find('table thead th').append('<div class="sorticon"></div>');
  1909. return cols;
  1910. }
  1911. /* Opens the query analyzer dialog */
  1912. function openQueryAnalyzer() {
  1913. var rowData = $(this).parent().data('query');
  1914. var query = rowData.argument || rowData.sql_text;
  1915. if (codeMirrorEditor) {
  1916. // TODO: somehow Functions.sqlPrettyPrint messes up the query, needs be fixed
  1917. // query = Functions.sqlPrettyPrint(query);
  1918. codeMirrorEditor.setValue(query);
  1919. // Codemirror is bugged, it doesn't refresh properly sometimes.
  1920. // Following lines seem to fix that
  1921. setTimeout(function () {
  1922. codeMirrorEditor.refresh();
  1923. }, 50);
  1924. } else {
  1925. $('#sqlquery').val(query);
  1926. }
  1927. var profilingChart = null;
  1928. var dlgBtns = {
  1929. [Messages.strAnalyzeQuery]: {
  1930. text: Messages.strAnalyzeQuery,
  1931. class: 'btn btn-primary'
  1932. },
  1933. [Messages.strClose]: {
  1934. text: Messages.strClose,
  1935. class: 'btn btn-secondary'
  1936. }
  1937. };
  1938. dlgBtns[Messages.strAnalyzeQuery].click = function () {
  1939. profilingChart = loadQueryAnalysis(rowData);
  1940. };
  1941. dlgBtns[Messages.strClose].click = function () {
  1942. $(this).dialog('close');
  1943. };
  1944. $('#queryAnalyzerDialog').dialog({
  1945. classes: {
  1946. 'ui-dialog-titlebar-close': 'btn-close'
  1947. },
  1948. width: 'auto',
  1949. height: 'auto',
  1950. resizable: false,
  1951. buttons: dlgBtns,
  1952. close: function () {
  1953. if (profilingChart !== null) {
  1954. profilingChart.destroy();
  1955. }
  1956. $('#queryAnalyzerDialog').find('div.placeHolder').html('');
  1957. if (codeMirrorEditor) {
  1958. codeMirrorEditor.setValue('');
  1959. } else {
  1960. $('#sqlquery').val('');
  1961. }
  1962. }
  1963. });
  1964. }
  1965. /* Loads and displays the analyzed query data */
  1966. function loadQueryAnalysis(rowData) {
  1967. var db = rowData.db || '';
  1968. var profilingChart = null;
  1969. $('#queryAnalyzerDialog').find('div.placeHolder').html(Messages.strAnalyzing + ' <img class="ajaxIcon" src="' + themeImagePath + 'ajax_clock_small.gif" alt="">');
  1970. $.post('index.php?route=/server/status/monitor/query', {
  1971. 'ajax_request': true,
  1972. 'query': codeMirrorEditor ? codeMirrorEditor.getValue() : $('#sqlquery').val(),
  1973. 'database': db,
  1974. 'server': CommonParams.get('server')
  1975. }, function (responseData) {
  1976. var data = responseData;
  1977. var i;
  1978. var l;
  1979. if (typeof data !== 'undefined' && data.success === true) {
  1980. data = data.message;
  1981. }
  1982. if (data.error) {
  1983. if (data.error.indexOf('1146') !== -1 || data.error.indexOf('1046') !== -1) {
  1984. data.error = Messages.strServerLogError;
  1985. }
  1986. $('#queryAnalyzerDialog').find('div.placeHolder').html('<div class="alert alert-danger" role="alert">' + data.error + '</div>');
  1987. return;
  1988. }
  1989. var totalTime = 0;
  1990. // Float sux, I'll use table :(
  1991. $('#queryAnalyzerDialog').find('div.placeHolder').html('<table class="table table-borderless"><tr><td class="explain"></td><td class="chart"></td></tr></table>');
  1992. var explain = '<b>' + Messages.strExplainOutput + '</b> ' + $('#explain_docu').html();
  1993. if (data.explain.length > 1) {
  1994. explain += ' (';
  1995. for (i = 0; i < data.explain.length; i++) {
  1996. if (i > 0) {
  1997. explain += ', ';
  1998. }
  1999. explain += '<a href="#showExplain-' + i + '">' + i + '</a>';
  2000. }
  2001. explain += ')';
  2002. }
  2003. explain += '<p></p>';
  2004. var tempExplain = function (key, value) {
  2005. var newValue = value === null ? 'null' : Functions.escapeHtml(value);
  2006. if (key === 'type' && newValue.toLowerCase() === 'all') {
  2007. newValue = '<span class="text-danger">' + newValue + '</span>';
  2008. }
  2009. if (key === 'Extra') {
  2010. newValue = newValue.replace(/(using (temporary|filesort))/gi, '<span class="text-danger">$1</span>');
  2011. }
  2012. explain += key + ': ' + newValue + '<br>';
  2013. };
  2014. for (i = 0, l = data.explain.length; i < l; i++) {
  2015. explain += '<div class="explain-' + i + '"' + (i > 0 ? 'style="display:none;"' : '') + '>';
  2016. $.each(data.explain[i], tempExplain);
  2017. explain += '</div>';
  2018. }
  2019. explain += '<p><b>' + Messages.strAffectedRows + '</b> ' + data.affectedRows;
  2020. $('#queryAnalyzerDialog').find('div.placeHolder td.explain').append(explain);
  2021. $('#queryAnalyzerDialog').find('div.placeHolder a[href*="#showExplain"]').on('click', function () {
  2022. var id = $(this).attr('href').split('-')[1];
  2023. $(this).parent().find('div[class*="explain"]').hide();
  2024. $(this).parent().find('div[class*="explain-' + id + '"]').show();
  2025. });
  2026. if (data.profiling) {
  2027. var chartData = [];
  2028. var numberTable = '<table class="table table-sm table-striped table-hover w-auto queryNums"><thead><tr><th>' + Messages.strStatus + '</th><th>' + Messages.strTime + '</th></tr></thead><tbody>';
  2029. var duration;
  2030. var otherTime = 0;
  2031. for (i = 0, l = data.profiling.length; i < l; i++) {
  2032. duration = parseFloat(data.profiling[i].duration);
  2033. totalTime += duration;
  2034. numberTable += '<tr><td>' + data.profiling[i].state + ' </td><td> ' + Functions.prettyProfilingNum(duration, 2) + '</td></tr>';
  2035. }
  2036. // Only put those values in the pie which are > 2%
  2037. for (i = 0, l = data.profiling.length; i < l; i++) {
  2038. duration = parseFloat(data.profiling[i].duration);
  2039. if (duration / totalTime > 0.02) {
  2040. chartData.push([Functions.prettyProfilingNum(duration, 2) + ' ' + data.profiling[i].state, duration]);
  2041. } else {
  2042. otherTime += duration;
  2043. }
  2044. }
  2045. if (otherTime > 0) {
  2046. chartData.push([Functions.prettyProfilingNum(otherTime, 2) + ' ' + Messages.strOther, otherTime]);
  2047. }
  2048. numberTable += '<tr><td><b>' + Messages.strTotalTime + '</b></td><td>' + Functions.prettyProfilingNum(totalTime, 2) + '</td></tr>';
  2049. numberTable += '</tbody></table>';
  2050. $('#queryAnalyzerDialog').find('div.placeHolder td.chart').append('<b>' + Messages.strProfilingResults + ' ' + $('#profiling_docu').html() + '</b> ' + '(<a href="#showNums">' + Messages.strTable + '</a>, <a href="#showChart">' + Messages.strChart + '</a>)<br>' + numberTable + ' <div id="queryProfiling"></div>');
  2051. $('#queryAnalyzerDialog').find('div.placeHolder a[href="#showNums"]').on('click', function () {
  2052. $('#queryAnalyzerDialog').find('#queryProfiling').hide();
  2053. $('#queryAnalyzerDialog').find('table.queryNums').show();
  2054. return false;
  2055. });
  2056. $('#queryAnalyzerDialog').find('div.placeHolder a[href="#showChart"]').on('click', function () {
  2057. $('#queryAnalyzerDialog').find('#queryProfiling').show();
  2058. $('#queryAnalyzerDialog').find('table.queryNums').hide();
  2059. return false;
  2060. });
  2061. profilingChart = Functions.createProfilingChart('queryProfiling', chartData);
  2062. }
  2063. });
  2064. return profilingChart;
  2065. }
  2066. /* Saves the monitor to localstorage */
  2067. function saveMonitor() {
  2068. var gridCopy = {};
  2069. $.each(runtime.charts, function (key, elem) {
  2070. gridCopy[key] = {};
  2071. gridCopy[key].nodes = elem.nodes;
  2072. gridCopy[key].settings = elem.settings;
  2073. gridCopy[key].title = elem.title;
  2074. gridCopy[key].series = elem.series;
  2075. gridCopy[key].maxYLabel = elem.maxYLabel;
  2076. });
  2077. if (isStorageSupported('localStorage')) {
  2078. window.localStorage.monitorCharts = JSON.stringify(gridCopy);
  2079. window.localStorage.monitorSettings = JSON.stringify(monitorSettings);
  2080. window.localStorage.monitorVersion = monitorProtocolVersion;
  2081. }
  2082. $('a[href="#clearMonitorConfig"]').show();
  2083. }
  2084. });
  2085. // Run the monitor once loaded
  2086. AJAX.registerOnload('server/status/monitor.js', function () {
  2087. $('a[href="#pauseCharts"]').trigger('click');
  2088. });