2 * Used in or for console
4 * @package phpMyAdmin-Console
7 /* global debugSQLInfo */ // libraries/classes/Footer.php
14 * @var object, jQuery object, selector is '#pma_console>.content'
17 $consoleContent: null,
19 * @var object, jQuery object, selector is '#pma_console .content',
23 $consoleAllContents: null,
25 * @var object, jQuery object, selector is '#pma_console .toolbar'
28 $consoleToolbar: null,
30 * @var object, jQuery object, selector is '#pma_console .template'
33 $consoleTemplates: null,
35 * @var object, jQuery object, form for submit
40 * @var object, contain console config
45 * @var bool, if console element exist, it'll be true
50 * @var bool, make sure console events bind only once
55 * Used for console initialize, reinit is ok, just some variable assignment
59 initialize: function () {
60 if ($('#pma_console').length === 0) {
64 Console.config = Functions.configGet('Console', false);
66 Console.isEnabled = true;
69 Console.$consoleToolbar = $('#pma_console').find('>.toolbar');
70 Console.$consoleContent = $('#pma_console').find('>.content');
71 Console.$consoleAllContents = $('#pma_console').find('.content');
72 Console.$consoleTemplates = $('#pma_console').find('>.templates');
74 // Generate a from for post
75 Console.$requestForm = $('<form method="post" action="index.php?route=/import">' +
76 '<input name="is_js_confirmed" value="0">' +
77 '<textarea name="sql_query"></textarea>' +
78 '<input name="console_message_id" value="0">' +
79 '<input name="server" value="">' +
80 '<input name="db" value="">' +
81 '<input name="table" value="">' +
82 '<input name="token" value="">' +
85 Console.$requestForm.children('[name=token]').val(CommonParams.get('token'));
86 Console.$requestForm.on('submit', AJAX.requestHandler);
88 // Event binds shouldn't run again
89 if (Console.isInitialized === false) {
91 if (Console.config.AlwaysExpand === true) {
92 $('#pma_console_options input[name=always_expand]').prop('checked', true);
94 if (Console.config.StartHistory === true) {
95 $('#pma_console_options').find('input[name=start_history]').prop('checked', true);
97 if (Console.config.CurrentQuery === true) {
98 $('#pma_console_options').find('input[name=current_query]').prop('checked', true);
100 if (Console.config.EnterExecutes === true) {
101 $('#pma_console_options').find('input[name=enter_executes]').prop('checked', true);
103 if (Console.config.DarkTheme === true) {
104 $('#pma_console_options').find('input[name=dark_theme]').prop('checked', true);
105 $('#pma_console').find('>.content').addClass('console_dark_theme');
108 ConsoleResizer.initialize();
109 ConsoleInput.initialize();
110 ConsoleMessages.initialize();
111 ConsoleBookmarks.initialize();
112 ConsoleDebug.initialize();
114 Console.$consoleToolbar.children('.console_switch').on('click', Console.toggle);
116 $('#pma_console').find('.toolbar').children().on('mousedown', function (event) {
117 event.preventDefault();
118 event.stopImmediatePropagation();
121 $('#pma_console').find('.button.clear').on('click', function () {
122 ConsoleMessages.clear();
125 $('#pma_console').find('.button.history').on('click', function () {
126 ConsoleMessages.showHistory();
129 $('#pma_console').find('.button.options').on('click', function () {
130 Console.showCard('#pma_console_options');
133 $('#pma_console').find('.button.debug').on('click', function () {
134 Console.showCard('#debug_console');
137 Console.$consoleContent.on('click', function (event) {
138 if (event.target === this) {
139 ConsoleInput.focus();
143 $('#pma_console').find('.mid_layer').on('click', function () {
144 Console.hideCard($(this).parent().children('.card'));
146 $('#debug_console').find('.switch_button').on('click', function () {
147 Console.hideCard($(this).closest('.card'));
149 $('#pma_bookmarks').find('.switch_button').on('click', function () {
150 Console.hideCard($(this).closest('.card'));
152 $('#pma_console_options').find('.switch_button').on('click', function () {
153 Console.hideCard($(this).closest('.card'));
156 $('#pma_console_options').find('input[type=checkbox]').on('change', function () {
157 Console.updateConfig();
160 $('#pma_console_options').find('.button.default').on('click', function () {
161 $('#pma_console_options input[name=always_expand]').prop('checked', false);
162 $('#pma_console_options').find('input[name=start_history]').prop('checked', false);
163 $('#pma_console_options').find('input[name=current_query]').prop('checked', true);
164 $('#pma_console_options').find('input[name=enter_executes]').prop('checked', false);
165 $('#pma_console_options').find('input[name=dark_theme]').prop('checked', false);
166 Console.updateConfig();
169 $('#pma_console_options').find('input[name=enter_executes]').on('change', function () {
170 ConsoleMessages.showInstructions(Console.config.EnterExecutes);
173 $(document).on('ajaxComplete', function (event, xhr, ajaxOptions) {
174 if (ajaxOptions.dataType && ajaxOptions.dataType.indexOf('json') !== -1) {
177 if (xhr.status !== 200) {
181 var data = JSON.parse(xhr.responseText);
182 Console.ajaxCallback(data);
184 // eslint-disable-next-line no-console, compat/compat
186 // eslint-disable-next-line no-console
187 console.log('Failed to parse JSON: ' + e.message);
191 Console.isInitialized = true;
194 // Change console mode from cookie
195 switch (Console.config.Mode) {
204 Console.scrollBottom();
207 Console.setConfig('Mode', 'info');
212 * Execute query and show results in console
216 execute: function (queryString, options) {
217 if (typeof(queryString) !== 'string' || ! /[a-z]|[A-Z]/.test(queryString)) {
220 Console.$requestForm.children('textarea').val(queryString);
221 Console.$requestForm.children('[name=server]').attr('value', CommonParams.get('server'));
222 if (options && options.db) {
223 Console.$requestForm.children('[name=db]').val(options.db);
225 Console.$requestForm.children('[name=table]').val(options.table);
227 Console.$requestForm.children('[name=table]').val('');
230 Console.$requestForm.children('[name=db]').val(
231 (CommonParams.get('db').length > 0 ? CommonParams.get('db') : ''));
233 Console.$requestForm.find('[name=profiling]').remove();
234 if (options && options.profiling === true) {
235 Console.$requestForm.append('<input name="profiling" value="on">');
237 if (! Functions.confirmQuery(Console.$requestForm[0], Console.$requestForm.children('textarea')[0].value)) {
240 Console.$requestForm.children('[name=console_message_id]')
241 .val(ConsoleMessages.appendQuery({ 'sql_query': queryString }).message_id);
242 Console.$requestForm.trigger('submit');
243 ConsoleInput.clear();
246 ajaxCallback: function (data) {
247 if (data && data.console_message_id) {
248 ConsoleMessages.updateQuery(data.console_message_id, data.success,
249 (data.reloadQuerywindow ? data.reloadQuerywindow : false));
250 } else if (data && data.reloadQuerywindow) {
251 if (data.reloadQuerywindow.sql_query.length > 0) {
252 ConsoleMessages.appendQuery(data.reloadQuerywindow, 'successed')
253 .$message.addClass(Console.config.CurrentQuery ? '' : 'hide');
258 * Change console to collapse mode
262 collapse: function () {
263 Console.setConfig('Mode', 'collapse');
264 var pmaConsoleHeight = Math.max(92, Console.config.Height);
266 Console.$consoleToolbar.addClass('collapsed');
267 Console.$consoleAllContents.height(pmaConsoleHeight);
268 Console.$consoleContent.stop();
269 Console.$consoleContent.animate({ 'margin-bottom': -1 * Console.$consoleContent.outerHeight() + 'px' },
270 'fast', 'easeOutQuart', function () {
271 Console.$consoleContent.css({ display:'none' });
272 $(window).trigger('resize');
279 * @param bool inputFocus If true, focus the input line after show()
282 show: function (inputFocus) {
283 Console.setConfig('Mode', 'show');
285 var pmaConsoleHeight = Math.max(92, Console.config.Height);
286 pmaConsoleHeight = Math.min(Console.config.Height, (window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight) - 25);
287 Console.$consoleContent.css({ display:'block' });
288 if (Console.$consoleToolbar.hasClass('collapsed')) {
289 Console.$consoleToolbar.removeClass('collapsed');
291 Console.$consoleAllContents.height(pmaConsoleHeight);
292 Console.$consoleContent.stop();
293 Console.$consoleContent.animate({ 'margin-bottom': 0 },
294 'fast', 'easeOutQuart', function () {
295 $(window).trigger('resize');
297 ConsoleInput.focus();
302 * Change console to SQL information mode
303 * this mode shows current SQL query
304 * This mode is the default mode
309 // Under construction
313 * Toggle console mode between collapse/show
314 * Used for toggle buttons and shortcuts
318 toggle: function () {
319 switch (Console.config.Mode) {
330 * Scroll console to bottom
334 scrollBottom: function () {
335 Console.$consoleContent.scrollTop(Console.$consoleContent.prop('scrollHeight'));
340 * @param string cardSelector Selector, select string will be "#pma_console " + cardSelector
341 * this param also can be JQuery object, if you need.
345 showCard: function (cardSelector) {
347 if (typeof(cardSelector) !== 'string') {
348 if (cardSelector.length > 0) {
349 $card = cardSelector;
354 $card = $('#pma_console ' + cardSelector);
356 if ($card.length === 0) {
359 $card.parent().children('.mid_layer').show().fadeTo(0, 0.15);
360 $card.addClass('show');
362 if ($card.parents('.card').length > 0) {
363 Console.showCard($card.parents('.card'));
367 * Scroll console to bottom
369 * @param object $targetCard Target card JQuery object, if it's empty, function will hide all cards
372 hideCard: function ($targetCard) {
374 $('#pma_console').find('.mid_layer').fadeOut(140);
375 $('#pma_console').find('.card').removeClass('show');
376 } else if ($targetCard.length > 0) {
377 $targetCard.parent().find('.mid_layer').fadeOut(140);
378 $targetCard.find('.card').removeClass('show');
379 $targetCard.removeClass('show');
383 * Used for update console config
387 updateConfig: function () {
388 Console.setConfig('AlwaysExpand', $('#pma_console_options input[name=always_expand]').prop('checked'));
389 Console.setConfig('StartHistory', $('#pma_console_options').find('input[name=start_history]').prop('checked'));
390 Console.setConfig('CurrentQuery', $('#pma_console_options').find('input[name=current_query]').prop('checked'));
391 Console.setConfig('EnterExecutes', $('#pma_console_options').find('input[name=enter_executes]').prop('checked'));
392 Console.setConfig('DarkTheme', $('#pma_console_options').find('input[name=dark_theme]').prop('checked'));
393 /* Setting the dark theme of the console*/
394 if (Console.config.DarkTheme) {
395 $('#pma_console').find('>.content').addClass('console_dark_theme');
397 $('#pma_console').find('>.content').removeClass('console_dark_theme');
400 setConfig: function (key, value) {
401 Console.config[key] = value;
402 Functions.configSet('Console/' + key, value);
404 isSelect: function (queryString) {
405 var regExp = /^SELECT\s+/i;
406 return regExp.test(queryString);
412 * Careful: this object UI logics highly related with functions under Console
413 * Resizing min-height is 32, if small than it, console will collapse
415 var ConsoleResizer = {
420 * Mousedown event handler for bind to resizer
424 mouseDown: function (event) {
425 if (Console.config.Mode !== 'show') {
428 ConsoleResizer.posY = event.pageY;
429 ConsoleResizer.height = Console.$consoleContent.height();
430 $(document).on('mousemove', ConsoleResizer.mouseMove);
431 $(document).on('mouseup', ConsoleResizer.mouseUp);
432 // Disable text selection while resizing
433 $(document).on('selectstart', function () {
438 * Mousemove event handler for bind to resizer
442 mouseMove: function (event) {
443 if (event.pageY < 35) {
446 ConsoleResizer.resultHeight = ConsoleResizer.height + (ConsoleResizer.posY - event.pageY);
447 // Content min-height is 32, if adjusting height small than it we'll move it out of the page
448 if (ConsoleResizer.resultHeight <= 32) {
449 Console.$consoleAllContents.height(32);
450 Console.$consoleContent.css('margin-bottom', ConsoleResizer.resultHeight - 32);
452 // Logic below makes viewable area always at bottom when adjusting height and content already at bottom
453 if (Console.$consoleContent.scrollTop() + Console.$consoleContent.innerHeight() + 16
454 >= Console.$consoleContent.prop('scrollHeight')) {
455 Console.$consoleAllContents.height(ConsoleResizer.resultHeight);
456 Console.scrollBottom();
458 Console.$consoleAllContents.height(ConsoleResizer.resultHeight);
463 * Mouseup event handler for bind to resizer
467 mouseUp: function () {
468 Console.setConfig('Height', ConsoleResizer.resultHeight);
470 $(document).off('mousemove');
471 $(document).off('mouseup');
472 $(document).off('selectstart');
475 * Used for console resizer initialize
479 initialize: function () {
480 $('#pma_console').find('.toolbar').off('mousedown');
481 $('#pma_console').find('.toolbar').on('mousedown', ConsoleResizer.mouseDown);
486 * Console input object
490 * @var array, contains Codemirror objects or input jQuery objects
495 * @var bool, if codemirror enabled
500 * @var int, count for history navigation, 0 for current input
505 * @var string, current input when navigating through history
508 historyPreserveCurrent: null,
510 * Used for console input initialize
514 initialize: function () {
515 // _cm object can't be reinitialize
516 if (ConsoleInput.inputs !== null) {
519 if (typeof CodeMirror !== 'undefined') {
520 ConsoleInput.codeMirror = true;
522 ConsoleInput.inputs = [];
523 if (ConsoleInput.codeMirror) {
524 // eslint-disable-next-line new-cap
525 ConsoleInput.inputs.console = CodeMirror($('#pma_console').find('.console_query_input')[0], {
529 extraKeys: { 'Ctrl-Space': 'autocomplete' },
530 hintOptions: { 'completeSingle': false, 'completeOnSingleClick': true },
531 gutters: ['CodeMirror-lint-markers'],
533 'getAnnotations': CodeMirror.sqlLint,
537 ConsoleInput.inputs.console.on('inputRead', Functions.codeMirrorAutoCompleteOnInputRead);
538 ConsoleInput.inputs.console.on('keydown', function (instance, event) {
539 ConsoleInput.historyNavigate(event);
541 if ($('#pma_bookmarks').length !== 0) {
542 // eslint-disable-next-line new-cap
543 ConsoleInput.inputs.bookmark = CodeMirror($('#pma_console').find('.bookmark_add_input')[0], {
547 extraKeys: { 'Ctrl-Space': 'autocomplete' },
548 hintOptions: { 'completeSingle': false, 'completeOnSingleClick': true },
549 gutters: ['CodeMirror-lint-markers'],
551 'getAnnotations': CodeMirror.sqlLint,
555 ConsoleInput.inputs.bookmark.on('inputRead', Functions.codeMirrorAutoCompleteOnInputRead);
558 ConsoleInput.inputs.console =
559 $('<textarea>').appendTo('#pma_console .console_query_input')
560 .on('keydown', ConsoleInput.historyNavigate);
561 if ($('#pma_bookmarks').length !== 0) {
562 ConsoleInput.inputs.bookmark =
563 $('<textarea>').appendTo('#pma_console .bookmark_add_input');
566 $('#pma_console').find('.console_query_input').on('keydown', ConsoleInput.keyDown);
568 historyNavigate: function (event) {
569 if (event.keyCode === 38 || event.keyCode === 40) {
570 var upPermitted = false;
571 var downPermitted = false;
572 var editor = ConsoleInput.inputs.console;
575 if (ConsoleInput.codeMirror) {
576 cursorLine = editor.getCursor().line;
577 totalLine = editor.lineCount();
579 // Get cursor position from textarea
580 var text = ConsoleInput.getText();
581 cursorLine = text.substr(0, editor.prop('selectionStart')).split('\n').length - 1;
582 totalLine = text.split(/\r*\n/).length;
584 if (cursorLine === 0) {
587 if (cursorLine === totalLine - 1) {
588 downPermitted = true;
591 var queryString = false;
592 if (upPermitted && event.keyCode === 38) {
593 // Navigate up in history
594 if (ConsoleInput.historyCount === 0) {
595 ConsoleInput.historyPreserveCurrent = ConsoleInput.getText();
597 nextCount = ConsoleInput.historyCount + 1;
598 queryString = ConsoleMessages.getHistory(nextCount);
599 } else if (downPermitted && event.keyCode === 40) {
600 // Navigate down in history
601 if (ConsoleInput.historyCount === 0) {
604 nextCount = ConsoleInput.historyCount - 1;
605 if (nextCount === 0) {
606 queryString = ConsoleInput.historyPreserveCurrent;
608 queryString = ConsoleMessages.getHistory(nextCount);
611 if (queryString !== false) {
612 ConsoleInput.historyCount = nextCount;
613 ConsoleInput.setText(queryString, 'console');
614 if (ConsoleInput.codeMirror) {
615 editor.setCursor(editor.lineCount(), 0);
617 event.preventDefault();
622 * Mousedown event handler for bind to input
623 * Shortcut is Ctrl+Enter key or just ENTER, depending on console's
628 keyDown: function (event) {
630 if (Console.config.EnterExecutes) {
631 // Enter, but not in combination with Shift (which writes a new line).
632 if (!event.shiftKey && event.keyCode === 13) {
633 ConsoleInput.execute();
637 if (event.ctrlKey && event.keyCode === 13) {
638 ConsoleInput.execute();
642 if (event.ctrlKey && event.keyCode === 76) {
643 ConsoleInput.clear();
646 if (event.ctrlKey && event.keyCode === 85) {
647 ConsoleMessages.clear();
651 * Used for send text to Console.execute()
655 execute: function () {
656 if (ConsoleInput.codeMirror) {
657 Console.execute(ConsoleInput.inputs.console.getValue());
659 Console.execute(ConsoleInput.inputs.console.val());
663 * Used for clear the input
665 * @param string target, default target is console input
668 clear: function (target) {
669 ConsoleInput.setText('', target);
672 * Used for set focus to input
677 ConsoleInput.inputs.console.focus();
680 * Used for blur input
685 if (ConsoleInput.codeMirror) {
686 ConsoleInput.inputs.console.getInputField().blur();
688 ConsoleInput.inputs.console.blur();
692 * Used for set text in input
695 * @param string target
698 setText: function (text, target) {
699 if (ConsoleInput.codeMirror) {
702 Console.execute(ConsoleInput.inputs.bookmark.setValue(text));
706 Console.execute(ConsoleInput.inputs.console.setValue(text));
711 Console.execute(ConsoleInput.inputs.bookmark.val(text));
715 Console.execute(ConsoleInput.inputs.console.val(text));
719 getText: function (target) {
720 if (ConsoleInput.codeMirror) {
723 return ConsoleInput.inputs.bookmark.getValue();
726 return ConsoleInput.inputs.console.getValue();
731 return ConsoleInput.inputs.bookmark.val();
734 return ConsoleInput.inputs.console.val();
742 * Console messages, and message items management object
744 var ConsoleMessages = {
746 * Used for clear the messages
751 $('#pma_console').find('.content .console_message_container .message:not(.welcome)').addClass('hide');
752 $('#pma_console').find('.content .console_message_container .message.failed').remove();
753 $('#pma_console').find('.content .console_message_container .message.expanded').find('.action.collapse').trigger('click');
756 * Used for show history messages
760 showHistory: function () {
761 $('#pma_console').find('.content .console_message_container .message.hide').removeClass('hide');
764 * Used for getting a perticular history query
766 * @param int nthLast get nth query message from latest, i.e 1st is last
767 * @return string message
769 getHistory: function (nthLast) {
770 var $queries = $('#pma_console').find('.content .console_message_container .query');
771 var length = $queries.length;
772 var $query = $queries.eq(length - nthLast);
773 if (!$query || (length - nthLast) < 0) {
776 return $query.text();
780 * Used to show the correct message depending on which key
781 * combination executes the query (Ctrl+Enter or Enter).
783 * @param bool enterExecutes Only Enter has to be pressed to execute query.
786 showInstructions: function (enterExecutes) {
787 var enter = +enterExecutes || 0; // conversion to int
788 var $welcomeMsg = $('#pma_console').find('.content .console_message_container .message.welcome span');
789 $welcomeMsg.children('[id^=instructions]').hide();
790 $welcomeMsg.children('#instructions-' + enter).show();
793 * Used for log new message
795 * @param string msgString Message to show
796 * @param string msgType Message type
797 * @return object, {message_id, $message}
799 append: function (msgString, msgType) {
800 if (typeof(msgString) !== 'string') {
803 // Generate an ID for each message, we can find them later
804 var msgId = Math.round(Math.random() * (899999999999) + 100000000000);
805 var now = new Date();
807 $('<div class="message ' +
808 (Console.config.AlwaysExpand ? 'expanded' : 'collapsed') +
809 '" msgid="' + msgId + '"><div class="action_content"></div></div>');
812 $newMessage.append('<div class="query highlighted"></div>');
813 if (ConsoleInput.codeMirror) {
814 CodeMirror.runMode(msgString,
815 'text/x-sql', $newMessage.children('.query')[0]);
817 $newMessage.children('.query').text(msgString);
819 $newMessage.children('.action_content')
820 .append(Console.$consoleTemplates.children('.query_actions').html());
824 $newMessage.append('<div>' + msgString + '</div>');
826 ConsoleMessages.messageEventBinds($newMessage);
827 $newMessage.find('span.text.query_time span')
828 .text(now.getHours() + ':' + now.getMinutes() + ':' + now.getSeconds())
829 .parent().attr('title', now);
832 $message: $newMessage.appendTo('#pma_console .content .console_message_container')
836 * Used for log new query
838 * @param string queryData Struct should be
839 * {sql_query: "Query string", db: "Target DB", table: "Target Table"}
840 * @param string state Message state
841 * @return object, {message_id: string message id, $message: JQuery object}
843 appendQuery: function (queryData, state) {
844 var targetMessage = ConsoleMessages.append(queryData.sql_query, 'query');
845 if (! targetMessage) {
848 if (queryData.db && queryData.table) {
849 targetMessage.$message.attr('targetdb', queryData.db);
850 targetMessage.$message.attr('targettable', queryData.table);
851 targetMessage.$message.find('.text.targetdb span').text(queryData.db);
853 if (Console.isSelect(queryData.sql_query)) {
854 targetMessage.$message.addClass('select');
858 targetMessage.$message.addClass('failed');
861 targetMessage.$message.addClass('successed');
865 targetMessage.$message.addClass('pending');
867 return targetMessage;
869 messageEventBinds: function ($target) {
870 // Leave unbinded elements, remove binded.
871 var $targetMessage = $target.filter(':not(.binded)');
872 if ($targetMessage.length === 0) {
875 $targetMessage.addClass('binded');
877 $targetMessage.find('.action.expand').on('click', function () {
878 $(this).closest('.message').removeClass('collapsed');
879 $(this).closest('.message').addClass('expanded');
881 $targetMessage.find('.action.collapse').on('click', function () {
882 $(this).closest('.message').addClass('collapsed');
883 $(this).closest('.message').removeClass('expanded');
885 $targetMessage.find('.action.edit').on('click', function () {
886 ConsoleInput.setText($(this).parent().siblings('.query').text());
887 ConsoleInput.focus();
889 $targetMessage.find('.action.requery').on('click', function () {
890 var query = $(this).parent().siblings('.query').text();
891 var $message = $(this).closest('.message');
892 if (confirm(Messages.strConsoleRequeryConfirm + '\n' +
893 (query.length < 100 ? query : query.slice(0, 100) + '...'))
895 Console.execute(query, { db: $message.attr('targetdb'), table: $message.attr('targettable') });
898 $targetMessage.find('.action.bookmark').on('click', function () {
899 var query = $(this).parent().siblings('.query').text();
900 var $message = $(this).closest('.message');
901 ConsoleBookmarks.addBookmark(query, $message.attr('targetdb'));
902 Console.showCard('#pma_bookmarks .card.add');
904 $targetMessage.find('.action.edit_bookmark').on('click', function () {
905 var query = $(this).parent().siblings('.query').text();
906 var $message = $(this).closest('.message');
907 var isShared = $message.find('span.bookmark_label').hasClass('shared');
908 var label = $message.find('span.bookmark_label').text();
909 ConsoleBookmarks.addBookmark(query, $message.attr('targetdb'), label, isShared);
910 Console.showCard('#pma_bookmarks .card.add');
912 $targetMessage.find('.action.delete_bookmark').on('click', function () {
913 var $message = $(this).closest('.message');
914 if (confirm(Messages.strConsoleDeleteBookmarkConfirm + '\n' + $message.find('.bookmark_label').text())) {
915 $.post('index.php?route=/import',
917 'server': CommonParams.get('server'),
918 'action_bookmark': 2,
919 'ajax_request': true,
920 'id_bookmark': $message.attr('bookmarkid')
923 ConsoleBookmarks.refresh();
927 $targetMessage.find('.action.profiling').on('click', function () {
928 var $message = $(this).closest('.message');
929 Console.execute($(this).parent().siblings('.query').text(),
930 { db: $message.attr('targetdb'),
931 table: $message.attr('targettable'),
934 $targetMessage.find('.action.explain').on('click', function () {
935 var $message = $(this).closest('.message');
936 Console.execute('EXPLAIN ' + $(this).parent().siblings('.query').text(),
937 { db: $message.attr('targetdb'),
938 table: $message.attr('targettable') });
940 $targetMessage.find('.action.dbg_show_trace').on('click', function () {
941 var $message = $(this).closest('.message');
942 if (!$message.find('.trace').length) {
943 ConsoleDebug.getQueryDetails(
944 $message.data('queryInfo'),
945 $message.data('totalTime'),
948 ConsoleMessages.messageEventBinds($message.find('.message:not(.binded)'));
950 $message.addClass('show_trace');
951 $message.removeClass('hide_trace');
953 $targetMessage.find('.action.dbg_hide_trace').on('click', function () {
954 var $message = $(this).closest('.message');
955 $message.addClass('hide_trace');
956 $message.removeClass('show_trace');
958 $targetMessage.find('.action.dbg_show_args').on('click', function () {
959 var $message = $(this).closest('.message');
960 $message.addClass('show_args expanded');
961 $message.removeClass('hide_args collapsed');
963 $targetMessage.find('.action.dbg_hide_args').on('click', function () {
964 var $message = $(this).closest('.message');
965 $message.addClass('hide_args collapsed');
966 $message.removeClass('show_args expanded');
968 if (ConsoleInput.codeMirror) {
969 $targetMessage.find('.query:not(.highlighted)').each(function (index, elem) {
970 CodeMirror.runMode($(elem).text(),
972 $(this).addClass('highlighted');
976 msgAppend: function (msgId, msgString) {
977 var $targetMessage = $('#pma_console').find('.content .console_message_container .message[msgid=' + msgId + ']');
978 if ($targetMessage.length === 0 || isNaN(parseInt(msgId)) || typeof(msgString) !== 'string') {
981 $targetMessage.append('<div>' + msgString + '</div>');
983 updateQuery: function (msgId, isSuccessed, queryData) {
984 var $targetMessage = $('#pma_console').find('.console_message_container .message[msgid=' + parseInt(msgId) + ']');
985 if ($targetMessage.length === 0 || isNaN(parseInt(msgId))) {
988 $targetMessage.removeClass('pending failed successed');
990 $targetMessage.addClass('successed');
992 $targetMessage.children('.query').text('');
993 $targetMessage.removeClass('select');
994 if (Console.isSelect(queryData.sql_query)) {
995 $targetMessage.addClass('select');
997 if (ConsoleInput.codeMirror) {
998 CodeMirror.runMode(queryData.sql_query, 'text/x-sql', $targetMessage.children('.query')[0]);
1000 $targetMessage.children('.query').text(queryData.sql_query);
1002 $targetMessage.attr('targetdb', queryData.db);
1003 $targetMessage.attr('targettable', queryData.table);
1004 $targetMessage.find('.text.targetdb span').text(queryData.db);
1007 $targetMessage.addClass('failed');
1011 * Used for console messages initialize
1015 initialize: function () {
1016 ConsoleMessages.messageEventBinds($('#pma_console').find('.message:not(.binded)'));
1017 if (Console.config.StartHistory) {
1018 ConsoleMessages.showHistory();
1020 ConsoleMessages.showInstructions(Console.config.EnterExecutes);
1025 * Console bookmarks card, and bookmarks items management object
1027 var ConsoleBookmarks = {
1029 addBookmark: function (queryString, targetDb, label, isShared) {
1030 $('#pma_bookmarks').find('.add [name=shared]').prop('checked', false);
1031 $('#pma_bookmarks').find('.add [name=label]').val('');
1032 $('#pma_bookmarks').find('.add [name=targetdb]').val('');
1033 $('#pma_bookmarks').find('.add [name=id_bookmark]').val('');
1034 ConsoleInput.setText('', 'bookmark');
1036 if (typeof queryString !== 'undefined') {
1037 ConsoleInput.setText(queryString, 'bookmark');
1039 if (typeof targetDb !== 'undefined') {
1040 $('#pma_bookmarks').find('.add [name=targetdb]').val(targetDb);
1042 if (typeof label !== 'undefined') {
1043 $('#pma_bookmarks').find('.add [name=label]').val(label);
1045 if (typeof isShared !== 'undefined') {
1046 $('#pma_bookmarks').find('.add [name=shared]').prop('checked', isShared);
1049 refresh: function () {
1050 $.get('index.php?route=/import',
1052 'ajax_request': true,
1053 'server': CommonParams.get('server'),
1054 'console_bookmark_refresh': 'refresh'
1057 if (data.console_message_bookmark) {
1058 $('#pma_bookmarks').find('.content.bookmark').html(data.console_message_bookmark);
1059 ConsoleMessages.messageEventBinds($('#pma_bookmarks').find('.message:not(.binded)'));
1064 * Used for console bookmarks initialize
1065 * message events are already binded by ConsoleMsg.messageEventBinds
1069 initialize: function () {
1070 if ($('#pma_bookmarks').length === 0) {
1073 $('#pma_console').find('.button.bookmarks').on('click', function () {
1074 Console.showCard('#pma_bookmarks');
1076 $('#pma_bookmarks').find('.button.add').on('click', function () {
1077 Console.showCard('#pma_bookmarks .card.add');
1079 $('#pma_bookmarks').find('.card.add [name=submit]').on('click', function () {
1080 if ($('#pma_bookmarks').find('.card.add [name=label]').val().length === 0
1081 || ConsoleInput.getText('bookmark').length === 0) {
1082 alert(Messages.strFormEmpty);
1085 $(this).prop('disabled', true);
1086 $.post('index.php?route=/import',
1088 'ajax_request': true,
1089 'console_bookmark_add': 'true',
1090 'label': $('#pma_bookmarks').find('.card.add [name=label]').val(),
1091 'server': CommonParams.get('server'),
1092 'db': $('#pma_bookmarks').find('.card.add [name=targetdb]').val(),
1093 'bookmark_query': ConsoleInput.getText('bookmark'),
1094 'shared': $('#pma_bookmarks').find('.card.add [name=shared]').prop('checked')
1097 ConsoleBookmarks.refresh();
1098 $('#pma_bookmarks').find('.card.add [name=submit]').prop('disabled', false);
1099 Console.hideCard($('#pma_bookmarks').find('.card.add'));
1102 $('#pma_console').find('.button.refresh').on('click', function () {
1103 ConsoleBookmarks.refresh();
1108 var ConsoleDebug = {
1110 groupQueries: false,
1111 orderBy: 'exec', // Possible 'exec' => Execution order, 'time' => Time taken, 'count'
1112 order: 'asc' // Possible 'asc', 'desc'
1118 initialize: function () {
1119 // Try to get debug info after every AJAX request
1120 $(document).on('ajaxSuccess', function (event, xhr, settings, data) {
1122 ConsoleDebug.showLog(data.debug, settings.url);
1126 if (Console.config.GroupQueries) {
1127 $('#debug_console').addClass('grouped');
1129 $('#debug_console').addClass('ungrouped');
1130 if (Console.config.OrderBy === 'count') {
1131 $('#debug_console').find('.button.order_by.sort_exec').addClass('active');
1134 var orderBy = Console.config.OrderBy;
1135 var order = Console.config.Order;
1136 $('#debug_console').find('.button.order_by.sort_' + orderBy).addClass('active');
1137 $('#debug_console').find('.button.order.order_' + order).addClass('active');
1139 // Initialize actions in toolbar
1140 $('#debug_console').find('.button.group_queries').on('click', function () {
1141 $('#debug_console').addClass('grouped');
1142 $('#debug_console').removeClass('ungrouped');
1143 Console.setConfig('GroupQueries', true);
1144 ConsoleDebug.refresh();
1145 if (Console.config.OrderBy === 'count') {
1146 $('#debug_console').find('.button.order_by.sort_exec').removeClass('active');
1149 $('#debug_console').find('.button.ungroup_queries').on('click', function () {
1150 $('#debug_console').addClass('ungrouped');
1151 $('#debug_console').removeClass('grouped');
1152 Console.setConfig('GroupQueries', false);
1153 ConsoleDebug.refresh();
1154 if (Console.config.OrderBy === 'count') {
1155 $('#debug_console').find('.button.order_by.sort_exec').addClass('active');
1158 $('#debug_console').find('.button.order_by').on('click', function () {
1159 var $this = $(this);
1160 $('#debug_console').find('.button.order_by').removeClass('active');
1161 $this.addClass('active');
1162 if ($this.hasClass('sort_time')) {
1163 Console.setConfig('OrderBy', 'time');
1164 } else if ($this.hasClass('sort_exec')) {
1165 Console.setConfig('OrderBy', 'exec');
1166 } else if ($this.hasClass('sort_count')) {
1167 Console.setConfig('OrderBy', 'count');
1169 ConsoleDebug.refresh();
1171 $('#debug_console').find('.button.order').on('click', function () {
1172 var $this = $(this);
1173 $('#debug_console').find('.button.order').removeClass('active');
1174 $this.addClass('active');
1175 if ($this.hasClass('order_asc')) {
1176 Console.setConfig('Order', 'asc');
1177 } else if ($this.hasClass('order_desc')) {
1178 Console.setConfig('Order', 'desc');
1180 ConsoleDebug.refresh();
1183 // Show SQL debug info for first page load
1184 if (typeof debugSQLInfo !== 'undefined' && debugSQLInfo !== 'null') {
1185 $('#pma_console').find('.button.debug').removeClass('hide');
1189 ConsoleDebug.showLog(debugSQLInfo);
1191 formatFunctionCall: function (dbgStep) {
1192 var functionName = '';
1193 if ('class' in dbgStep) {
1194 functionName += dbgStep.class;
1195 functionName += dbgStep.type;
1197 functionName += dbgStep.function;
1198 if (dbgStep.args && dbgStep.args.length) {
1199 functionName += '(...)';
1201 functionName += '()';
1203 return functionName;
1205 formatFunctionArgs: function (dbgStep) {
1206 var $args = $('<div>');
1207 if (dbgStep.args.length) {
1208 $args.append('<div class="message welcome">')
1210 $('<div class="message welcome">')
1213 Messages.strConsoleDebugArgsSummary,
1218 for (var i = 0; i < dbgStep.args.length; i++) {
1220 $('<div class="message">')
1223 Functions.escapeHtml(JSON.stringify(dbgStep.args[i], null, ' ')) +
1231 formatFileName: function (dbgStep) {
1233 if ('file' in dbgStep) {
1234 fileName += dbgStep.file;
1235 fileName += '#' + dbgStep.line;
1239 formatBackTrace: function (dbgTrace) {
1240 var $traceElem = $('<div class="trace">');
1242 $('<div class="message welcome">')
1246 for (var stepId in dbgTrace) {
1247 if (dbgTrace.hasOwnProperty(stepId)) {
1248 step = dbgTrace[stepId];
1249 if (!Array.isArray(step) && typeof step !== 'object') {
1251 $('<div class="message traceStep collapsed hide_args">')
1253 $('<span>').text(step)
1256 if (typeof step.args === 'string' && step.args) {
1257 step.args = [step.args];
1260 $('<div class="message traceStep collapsed hide_args">')
1262 $('<span class="function">').text(this.formatFunctionCall(step))
1265 $('<span class="file">').text(this.formatFileName(step))
1267 if (step.args && step.args.length) {
1270 $('<span class="args">').html(this.formatFunctionArgs(step))
1273 $('<div class="action_content">')
1275 '<span class="action dbg_show_args">' +
1276 Messages.strConsoleDebugShowArgs +
1280 '<span class="action dbg_hide_args">' +
1281 Messages.strConsoleDebugHideArgs +
1287 $traceElem.append($stepElem);
1292 formatQueryOrGroup: function (queryInfo, totalTime) {
1298 if (Array.isArray(queryInfo)) {
1302 queryText = queryInfo[0].query;
1305 for (i in queryInfo) {
1306 queryTime += queryInfo[i].time;
1309 count = queryInfo.length;
1311 queryText = queryInfo.query;
1312 queryTime = queryInfo.time;
1315 var $query = $('<div class="message collapsed hide_trace">')
1317 $('#debug_console').find('.templates .debug_query').clone()
1320 $('<div class="query">')
1323 .data('queryInfo', queryInfo)
1324 .data('totalTime', totalTime);
1326 $query.find('.text.count').removeClass('hide');
1327 $query.find('.text.count span').text(count);
1329 $query.find('.text.time span').text(queryTime + 's (' + ((queryTime * 100) / totalTime).toFixed(3) + '%)');
1333 appendQueryExtraInfo: function (query, $elem) {
1334 if ('error' in query) {
1336 $('<div>').html(query.error)
1339 $elem.append(this.formatBackTrace(query.trace));
1341 getQueryDetails: function (queryInfo, totalTime, $query) {
1342 if (Array.isArray(queryInfo)) {
1344 for (var i in queryInfo) {
1345 $singleQuery = $('<div class="message welcome trace">')
1346 .text((parseInt(i) + 1) + '.')
1348 $('<span class="time">').text(
1349 Messages.strConsoleDebugTimeTaken +
1350 ' ' + queryInfo[i].time + 's' +
1351 ' (' + ((queryInfo[i].time * 100) / totalTime).toFixed(3) + '%)'
1354 this.appendQueryExtraInfo(queryInfo[i], $singleQuery);
1356 .append('<div class="message welcome trace">')
1357 .append($singleQuery);
1360 this.appendQueryExtraInfo(queryInfo, $query);
1363 showLog: function (debugInfo, url) {
1364 this.lastDebugInfo.debugInfo = debugInfo;
1365 this.lastDebugInfo.url = url;
1367 $('#debug_console').find('.debugLog').empty();
1368 $('#debug_console').find('.debug>.welcome').empty();
1370 var debugJson = false;
1372 if (typeof debugInfo === 'object' && 'queries' in debugInfo) {
1373 // Copy it to debugJson, so that it doesn't get changed
1374 if (!('queries' in debugInfo)) {
1377 debugJson = { queries: [] };
1378 for (i in debugInfo.queries) {
1379 debugJson.queries[i] = debugInfo.queries[i];
1382 } else if (typeof debugInfo === 'string') {
1384 debugJson = JSON.parse(debugInfo);
1388 if (debugJson && !('queries' in debugJson)) {
1392 if (debugJson === false) {
1393 $('#debug_console').find('.debug>.welcome').text(
1394 Messages.strConsoleDebugError
1398 var allQueries = debugJson.queries;
1399 var uniqueQueries = {};
1401 var totalExec = allQueries.length;
1403 // Calculate total time and make unique query array
1405 for (i = 0; i < totalExec; ++i) {
1406 totalTime += allQueries[i].time;
1407 if (!(allQueries[i].hash in uniqueQueries)) {
1408 uniqueQueries[allQueries[i].hash] = [];
1410 uniqueQueries[allQueries[i].hash].push(allQueries[i]);
1412 // Count total unique queries, convert uniqueQueries to Array
1413 var totalUnique = 0;
1414 var uniqueArray = [];
1415 for (var hash in uniqueQueries) {
1416 if (uniqueQueries.hasOwnProperty(hash)) {
1418 uniqueArray.push(uniqueQueries[hash]);
1421 uniqueQueries = uniqueArray;
1423 $('#debug_console').find('.debug>.welcome').append(
1424 $('<span class="debug_summary">').text(
1426 Messages.strConsoleDebugSummary,
1434 $('#debug_console').find('.debug>.welcome').append(
1435 $('<span class="script_name">').text(url.split('?')[0])
1439 // For sorting queries
1440 function sortByTime (a, b) {
1441 var order = ((Console.config.Order === 'asc') ? 1 : -1);
1442 if (Array.isArray(a) && Array.isArray(b)) {
1453 return (timeA - timeB) * order;
1455 return (a.time - b.time) * order;
1459 function sortByCount (a, b) {
1460 var order = ((Console.config.Oorder === 'asc') ? 1 : -1);
1461 return (a.length - b.length) * order;
1464 var orderBy = Console.config.OrderBy;
1465 var order = Console.config.Order;
1467 if (Console.config.GroupQueries) {
1469 if (orderBy === 'time') {
1470 uniqueQueries.sort(sortByTime);
1471 } else if (orderBy === 'count') {
1472 uniqueQueries.sort(sortByCount);
1473 } else if (orderBy === 'exec' && order === 'desc') {
1474 uniqueQueries.reverse();
1476 for (i in uniqueQueries) {
1477 if (orderBy === 'time') {
1478 uniqueQueries[i].sort(sortByTime);
1479 } else if (orderBy === 'exec' && order === 'desc') {
1480 uniqueQueries[i].reverse();
1482 $('#debug_console').find('.debugLog').append(this.formatQueryOrGroup(uniqueQueries[i], totalTime));
1485 if (orderBy === 'time') {
1486 allQueries.sort(sortByTime);
1487 } else if (order === 'desc') {
1488 allQueries.reverse();
1490 for (i = 0; i < totalExec; ++i) {
1491 $('#debug_console').find('.debugLog').append(this.formatQueryOrGroup(allQueries[i], totalTime));
1495 ConsoleMessages.messageEventBinds($('#debug_console').find('.message:not(.binded)'));
1497 refresh: function () {
1498 var last = this.lastDebugInfo;
1499 ConsoleDebug.showLog(last.debugInfo, last.url);
1504 * Executed on page load
1507 Console.initialize();