1 /* vim: set expandtab sw=4 ts=4 sts=4: */
3 * Used in or for console
5 * @package phpMyAdmin-Console
8 /* global debugSQLInfo */ // libraries/classes/Footer.php
15 * @var object, jQuery object, selector is '#pma_console>.content'
18 $consoleContent: null,
20 * @var object, jQuery object, selector is '#pma_console .content',
24 $consoleAllContents: null,
26 * @var object, jQuery object, selector is '#pma_console .toolbar'
29 $consoleToolbar: null,
31 * @var object, jQuery object, selector is '#pma_console .template'
34 $consoleTemplates: null,
36 * @var object, jQuery object, form for submit
41 * @var object, contain console config
46 * @var bool, if console element exist, it'll be true
51 * @var bool, make sure console events bind only once
56 * Used for console initialize, reinit is ok, just some variable assignment
60 initialize: function () {
61 if ($('#pma_console').length === 0) {
65 Console.config = Functions.configGet('Console', false);
67 Console.isEnabled = true;
70 Console.$consoleToolbar = $('#pma_console').find('>.toolbar');
71 Console.$consoleContent = $('#pma_console').find('>.content');
72 Console.$consoleAllContents = $('#pma_console').find('.content');
73 Console.$consoleTemplates = $('#pma_console').find('>.templates');
75 // Generate a from for post
76 Console.$requestForm = $('<form method="post" action="import.php">' +
77 '<input name="is_js_confirmed" value="0">' +
78 '<textarea name="sql_query"></textarea>' +
79 '<input name="console_message_id" value="0">' +
80 '<input name="server" value="">' +
81 '<input name="db" value="">' +
82 '<input name="table" value="">' +
83 '<input name="token" value="">' +
86 Console.$requestForm.children('[name=token]').val(CommonParams.get('token'));
87 Console.$requestForm.on('submit', AJAX.requestHandler);
89 // Event binds shouldn't run again
90 if (Console.isInitialized === false) {
92 if (Console.config.AlwaysExpand === true) {
93 $('#pma_console_options input[name=always_expand]').prop('checked', true);
95 if (Console.config.StartHistory === true) {
96 $('#pma_console_options').find('input[name=start_history]').prop('checked', true);
98 if (Console.config.CurrentQuery === true) {
99 $('#pma_console_options').find('input[name=current_query]').prop('checked', true);
101 if (Console.config.EnterExecutes === true) {
102 $('#pma_console_options').find('input[name=enter_executes]').prop('checked', true);
104 if (Console.config.DarkTheme === true) {
105 $('#pma_console_options').find('input[name=dark_theme]').prop('checked', true);
106 $('#pma_console').find('>.content').addClass('console_dark_theme');
109 ConsoleResizer.initialize();
110 ConsoleInput.initialize();
111 ConsoleMessages.initialize();
112 ConsoleBookmarks.initialize();
113 ConsoleDebug.initialize();
115 Console.$consoleToolbar.children('.console_switch').on('click', Console.toggle);
117 $('#pma_console').find('.toolbar').children().on('mousedown', function (event) {
118 event.preventDefault();
119 event.stopImmediatePropagation();
122 $('#pma_console').find('.button.clear').on('click', function () {
123 ConsoleMessages.clear();
126 $('#pma_console').find('.button.history').on('click', function () {
127 ConsoleMessages.showHistory();
130 $('#pma_console').find('.button.options').on('click', function () {
131 Console.showCard('#pma_console_options');
134 $('#pma_console').find('.button.debug').on('click', function () {
135 Console.showCard('#debug_console');
138 Console.$consoleContent.on('click', function (event) {
139 if (event.target === this) {
140 ConsoleInput.focus();
144 $('#pma_console').find('.mid_layer').on('click', function () {
145 Console.hideCard($(this).parent().children('.card'));
147 $('#debug_console').find('.switch_button').on('click', function () {
148 Console.hideCard($(this).closest('.card'));
150 $('#pma_bookmarks').find('.switch_button').on('click', function () {
151 Console.hideCard($(this).closest('.card'));
153 $('#pma_console_options').find('.switch_button').on('click', function () {
154 Console.hideCard($(this).closest('.card'));
157 $('#pma_console_options').find('input[type=checkbox]').on('change', function () {
158 Console.updateConfig();
161 $('#pma_console_options').find('.button.default').on('click', function () {
162 $('#pma_console_options input[name=always_expand]').prop('checked', false);
163 $('#pma_console_options').find('input[name=start_history]').prop('checked', false);
164 $('#pma_console_options').find('input[name=current_query]').prop('checked', true);
165 $('#pma_console_options').find('input[name=enter_executes]').prop('checked', false);
166 $('#pma_console_options').find('input[name=dark_theme]').prop('checked', false);
167 Console.updateConfig();
170 $('#pma_console_options').find('input[name=enter_executes]').on('change', function () {
171 ConsoleMessages.showInstructions(Console.config.EnterExecutes);
174 $(document).ajaxComplete(function (event, xhr, ajaxOptions) {
175 if (ajaxOptions.dataType && ajaxOptions.dataType.indexOf('json') !== -1) {
178 if (xhr.status !== 200) {
182 var data = JSON.parse(xhr.responseText);
183 Console.ajaxCallback(data);
185 // eslint-disable-next-line no-console
187 // eslint-disable-next-line no-console
188 console.log('Failed to parse JSON: ' + e.message);
192 Console.isInitialized = true;
195 // Change console mode from cookie
196 switch (Console.config.Mode) {
205 Console.scrollBottom();
208 Console.setConfig('Mode', 'info');
213 * Execute query and show results in console
217 execute: function (queryString, options) {
218 if (typeof(queryString) !== 'string' || ! /[a-z]|[A-Z]/.test(queryString)) {
221 Console.$requestForm.children('textarea').val(queryString);
222 Console.$requestForm.children('[name=server]').attr('value', CommonParams.get('server'));
223 if (options && options.db) {
224 Console.$requestForm.children('[name=db]').val(options.db);
226 Console.$requestForm.children('[name=table]').val(options.table);
228 Console.$requestForm.children('[name=table]').val('');
231 Console.$requestForm.children('[name=db]').val(
232 (CommonParams.get('db').length > 0 ? CommonParams.get('db') : ''));
234 Console.$requestForm.find('[name=profiling]').remove();
235 if (options && options.profiling === true) {
236 Console.$requestForm.append('<input name="profiling" value="on">');
238 if (! Functions.confirmQuery(Console.$requestForm[0], Console.$requestForm.children('textarea')[0].value)) {
241 Console.$requestForm.children('[name=console_message_id]')
242 .val(ConsoleMessages.appendQuery({ 'sql_query': queryString }).message_id);
243 Console.$requestForm.trigger('submit');
244 ConsoleInput.clear();
247 ajaxCallback: function (data) {
248 if (data && data.console_message_id) {
249 ConsoleMessages.updateQuery(data.console_message_id, data.success,
250 (data.reloadQuerywindow ? data.reloadQuerywindow : false));
251 } else if (data && data.reloadQuerywindow) {
252 if (data.reloadQuerywindow.sql_query.length > 0) {
253 ConsoleMessages.appendQuery(data.reloadQuerywindow, 'successed')
254 .$message.addClass(Console.config.CurrentQuery ? '' : 'hide');
259 * Change console to collapse mode
263 collapse: function () {
264 Console.setConfig('Mode', 'collapse');
265 var pmaConsoleHeight = Math.max(92, Console.config.Height);
267 Console.$consoleToolbar.addClass('collapsed');
268 Console.$consoleAllContents.height(pmaConsoleHeight);
269 Console.$consoleContent.stop();
270 Console.$consoleContent.animate({ 'margin-bottom': -1 * Console.$consoleContent.outerHeight() + 'px' },
271 'fast', 'easeOutQuart', function () {
272 Console.$consoleContent.css({ display:'none' });
273 $(window).trigger('resize');
280 * @param bool inputFocus If true, focus the input line after show()
283 show: function (inputFocus) {
284 Console.setConfig('Mode', 'show');
286 var pmaConsoleHeight = Math.max(92, Console.config.Height);
287 pmaConsoleHeight = Math.min(Console.config.Height, (window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight) - 25);
288 Console.$consoleContent.css({ display:'block' });
289 if (Console.$consoleToolbar.hasClass('collapsed')) {
290 Console.$consoleToolbar.removeClass('collapsed');
292 Console.$consoleAllContents.height(pmaConsoleHeight);
293 Console.$consoleContent.stop();
294 Console.$consoleContent.animate({ 'margin-bottom': 0 },
295 'fast', 'easeOutQuart', function () {
296 $(window).trigger('resize');
298 ConsoleInput.focus();
303 * Change console to SQL information mode
304 * this mode shows current SQL query
305 * This mode is the default mode
310 // Under construction
314 * Toggle console mode between collapse/show
315 * Used for toggle buttons and shortcuts
319 toggle: function () {
320 switch (Console.config.Mode) {
331 * Scroll console to bottom
335 scrollBottom: function () {
336 Console.$consoleContent.scrollTop(Console.$consoleContent.prop('scrollHeight'));
341 * @param string cardSelector Selector, select string will be "#pma_console " + cardSelector
342 * this param also can be JQuery object, if you need.
346 showCard: function (cardSelector) {
348 if (typeof(cardSelector) !== 'string') {
349 if (cardSelector.length > 0) {
350 $card = cardSelector;
355 $card = $('#pma_console ' + cardSelector);
357 if ($card.length === 0) {
360 $card.parent().children('.mid_layer').show().fadeTo(0, 0.15);
361 $card.addClass('show');
363 if ($card.parents('.card').length > 0) {
364 Console.showCard($card.parents('.card'));
368 * Scroll console to bottom
370 * @param object $targetCard Target card JQuery object, if it's empty, function will hide all cards
373 hideCard: function ($targetCard) {
375 $('#pma_console').find('.mid_layer').fadeOut(140);
376 $('#pma_console').find('.card').removeClass('show');
377 } else if ($targetCard.length > 0) {
378 $targetCard.parent().find('.mid_layer').fadeOut(140);
379 $targetCard.find('.card').removeClass('show');
380 $targetCard.removeClass('show');
384 * Used for update console config
388 updateConfig: function () {
389 Console.setConfig('AlwaysExpand', $('#pma_console_options input[name=always_expand]').prop('checked'));
390 Console.setConfig('StartHistory', $('#pma_console_options').find('input[name=start_history]').prop('checked'));
391 Console.setConfig('CurrentQuery', $('#pma_console_options').find('input[name=current_query]').prop('checked'));
392 Console.setConfig('EnterExecutes', $('#pma_console_options').find('input[name=enter_executes]').prop('checked'));
393 Console.setConfig('DarkTheme', $('#pma_console_options').find('input[name=dark_theme]').prop('checked'));
394 /* Setting the dark theme of the console*/
395 if (Console.config.DarkTheme) {
396 $('#pma_console').find('>.content').addClass('console_dark_theme');
398 $('#pma_console').find('>.content').removeClass('console_dark_theme');
401 setConfig: function (key, value) {
402 Console.config[key] = value;
403 Functions.configSet('Console/' + key, value);
405 isSelect: function (queryString) {
406 var regExp = /^SELECT\s+/i;
407 return regExp.test(queryString);
413 * Careful: this object UI logics highly related with functions under Console
414 * Resizing min-height is 32, if small than it, console will collapse
416 var ConsoleResizer = {
421 * Mousedown event handler for bind to resizer
425 mouseDown: function (event) {
426 if (Console.config.Mode !== 'show') {
429 ConsoleResizer.posY = event.pageY;
430 ConsoleResizer.height = Console.$consoleContent.height();
431 $(document).on('mousemove', ConsoleResizer.mouseMove);
432 $(document).on('mouseup', ConsoleResizer.mouseUp);
433 // Disable text selection while resizing
434 $(document).on('selectstart', function () {
439 * Mousemove event handler for bind to resizer
443 mouseMove: function (event) {
444 if (event.pageY < 35) {
447 ConsoleResizer.resultHeight = ConsoleResizer.height + (ConsoleResizer.posY - event.pageY);
448 // Content min-height is 32, if adjusting height small than it we'll move it out of the page
449 if (ConsoleResizer.resultHeight <= 32) {
450 Console.$consoleAllContents.height(32);
451 Console.$consoleContent.css('margin-bottom', ConsoleResizer.resultHeight - 32);
453 // Logic below makes viewable area always at bottom when adjusting height and content already at bottom
454 if (Console.$consoleContent.scrollTop() + Console.$consoleContent.innerHeight() + 16
455 >= Console.$consoleContent.prop('scrollHeight')) {
456 Console.$consoleAllContents.height(ConsoleResizer.resultHeight);
457 Console.scrollBottom();
459 Console.$consoleAllContents.height(ConsoleResizer.resultHeight);
464 * Mouseup event handler for bind to resizer
468 mouseUp: function () {
469 Console.setConfig('Height', ConsoleResizer.resultHeight);
471 $(document).off('mousemove');
472 $(document).off('mouseup');
473 $(document).off('selectstart');
476 * Used for console resizer initialize
480 initialize: function () {
481 $('#pma_console').find('.toolbar').off('mousedown');
482 $('#pma_console').find('.toolbar').on('mousedown', ConsoleResizer.mouseDown);
487 * Console input object
491 * @var array, contains Codemirror objects or input jQuery objects
496 * @var bool, if codemirror enabled
501 * @var int, count for history navigation, 0 for current input
506 * @var string, current input when navigating through history
509 historyPreserveCurrent: null,
511 * Used for console input initialize
515 initialize: function () {
516 // _cm object can't be reinitialize
517 if (ConsoleInput.inputs !== null) {
520 if (typeof CodeMirror !== 'undefined') {
521 ConsoleInput.codeMirror = true;
523 ConsoleInput.inputs = [];
524 if (ConsoleInput.codeMirror) {
525 // eslint-disable-next-line new-cap
526 ConsoleInput.inputs.console = CodeMirror($('#pma_console').find('.console_query_input')[0], {
530 extraKeys: { 'Ctrl-Space': 'autocomplete' },
531 hintOptions: { 'completeSingle': false, 'completeOnSingleClick': true },
532 gutters: ['CodeMirror-lint-markers'],
534 'getAnnotations': CodeMirror.sqlLint,
538 ConsoleInput.inputs.console.on('inputRead', Functions.codeMirrorAutoCompleteOnInputRead);
539 ConsoleInput.inputs.console.on('keydown', function (instance, event) {
540 ConsoleInput.historyNavigate(event);
542 if ($('#pma_bookmarks').length !== 0) {
543 // eslint-disable-next-line new-cap
544 ConsoleInput.inputs.bookmark = CodeMirror($('#pma_console').find('.bookmark_add_input')[0], {
548 extraKeys: { 'Ctrl-Space': 'autocomplete' },
549 hintOptions: { 'completeSingle': false, 'completeOnSingleClick': true },
550 gutters: ['CodeMirror-lint-markers'],
552 'getAnnotations': CodeMirror.sqlLint,
556 ConsoleInput.inputs.bookmark.on('inputRead', Functions.codeMirrorAutoCompleteOnInputRead);
559 ConsoleInput.inputs.console =
560 $('<textarea>').appendTo('#pma_console .console_query_input')
561 .on('keydown', ConsoleInput.historyNavigate);
562 if ($('#pma_bookmarks').length !== 0) {
563 ConsoleInput.inputs.bookmark =
564 $('<textarea>').appendTo('#pma_console .bookmark_add_input');
567 $('#pma_console').find('.console_query_input').on('keydown', ConsoleInput.keyDown);
569 historyNavigate: function (event) {
570 if (event.keyCode === 38 || event.keyCode === 40) {
571 var upPermitted = false;
572 var downPermitted = false;
573 var editor = ConsoleInput.inputs.console;
576 if (ConsoleInput.codeMirror) {
577 cursorLine = editor.getCursor().line;
578 totalLine = editor.lineCount();
580 // Get cursor position from textarea
581 var text = ConsoleInput.getText();
582 cursorLine = text.substr(0, editor.prop('selectionStart')).split('\n').length - 1;
583 totalLine = text.split(/\r*\n/).length;
585 if (cursorLine === 0) {
588 if (cursorLine === totalLine - 1) {
589 downPermitted = true;
592 var queryString = false;
593 if (upPermitted && event.keyCode === 38) {
594 // Navigate up in history
595 if (ConsoleInput.historyCount === 0) {
596 ConsoleInput.historyPreserveCurrent = ConsoleInput.getText();
598 nextCount = ConsoleInput.historyCount + 1;
599 queryString = ConsoleMessages.getHistory(nextCount);
600 } else if (downPermitted && event.keyCode === 40) {
601 // Navigate down in history
602 if (ConsoleInput.historyCount === 0) {
605 nextCount = ConsoleInput.historyCount - 1;
606 if (nextCount === 0) {
607 queryString = ConsoleInput.historyPreserveCurrent;
609 queryString = ConsoleMessages.getHistory(nextCount);
612 if (queryString !== false) {
613 ConsoleInput.historyCount = nextCount;
614 ConsoleInput.setText(queryString, 'console');
615 if (ConsoleInput.codeMirror) {
616 editor.setCursor(editor.lineCount(), 0);
618 event.preventDefault();
623 * Mousedown event handler for bind to input
624 * Shortcut is Ctrl+Enter key or just ENTER, depending on console's
629 keyDown: function (event) {
631 if (Console.config.EnterExecutes) {
632 // Enter, but not in combination with Shift (which writes a new line).
633 if (!event.shiftKey && event.keyCode === 13) {
634 ConsoleInput.execute();
638 if (event.ctrlKey && event.keyCode === 13) {
639 ConsoleInput.execute();
643 if (event.ctrlKey && event.keyCode === 76) {
644 ConsoleInput.clear();
647 if (event.ctrlKey && event.keyCode === 85) {
648 ConsoleMessages.clear();
652 * Used for send text to Console.execute()
656 execute: function () {
657 if (ConsoleInput.codeMirror) {
658 Console.execute(ConsoleInput.inputs.console.getValue());
660 Console.execute(ConsoleInput.inputs.console.val());
664 * Used for clear the input
666 * @param string target, default target is console input
669 clear: function (target) {
670 ConsoleInput.setText('', target);
673 * Used for set focus to input
678 ConsoleInput.inputs.console.focus();
681 * Used for blur input
686 if (ConsoleInput.codeMirror) {
687 ConsoleInput.inputs.console.getInputField().blur();
689 ConsoleInput.inputs.console.blur();
693 * Used for set text in input
696 * @param string target
699 setText: function (text, target) {
700 if (ConsoleInput.codeMirror) {
703 Console.execute(ConsoleInput.inputs.bookmark.setValue(text));
707 Console.execute(ConsoleInput.inputs.console.setValue(text));
712 Console.execute(ConsoleInput.inputs.bookmark.val(text));
716 Console.execute(ConsoleInput.inputs.console.val(text));
720 getText: function (target) {
721 if (ConsoleInput.codeMirror) {
724 return ConsoleInput.inputs.bookmark.getValue();
727 return ConsoleInput.inputs.console.getValue();
732 return ConsoleInput.inputs.bookmark.val();
735 return ConsoleInput.inputs.console.val();
743 * Console messages, and message items management object
745 var ConsoleMessages = {
747 * Used for clear the messages
752 $('#pma_console').find('.content .console_message_container .message:not(.welcome)').addClass('hide');
753 $('#pma_console').find('.content .console_message_container .message.failed').remove();
754 $('#pma_console').find('.content .console_message_container .message.expanded').find('.action.collapse').trigger('click');
757 * Used for show history messages
761 showHistory: function () {
762 $('#pma_console').find('.content .console_message_container .message.hide').removeClass('hide');
765 * Used for getting a perticular history query
767 * @param int nthLast get nth query message from latest, i.e 1st is last
768 * @return string message
770 getHistory: function (nthLast) {
771 var $queries = $('#pma_console').find('.content .console_message_container .query');
772 var length = $queries.length;
773 var $query = $queries.eq(length - nthLast);
774 if (!$query || (length - nthLast) < 0) {
777 return $query.text();
781 * Used to show the correct message depending on which key
782 * combination executes the query (Ctrl+Enter or Enter).
784 * @param bool enterExecutes Only Enter has to be pressed to execute query.
787 showInstructions: function (enterExecutes) {
788 var enter = +enterExecutes || 0; // conversion to int
789 var $welcomeMsg = $('#pma_console').find('.content .console_message_container .message.welcome span');
790 $welcomeMsg.children('[id^=instructions]').hide();
791 $welcomeMsg.children('#instructions-' + enter).show();
794 * Used for log new message
796 * @param string msgString Message to show
797 * @param string msgType Message type
798 * @return object, {message_id, $message}
800 append: function (msgString, msgType) {
801 if (typeof(msgString) !== 'string') {
804 // Generate an ID for each message, we can find them later
805 var msgId = Math.round(Math.random() * (899999999999) + 100000000000);
806 var now = new Date();
808 $('<div class="message ' +
809 (Console.config.AlwaysExpand ? 'expanded' : 'collapsed') +
810 '" msgid="' + msgId + '"><div class="action_content"></div></div>');
813 $newMessage.append('<div class="query highlighted"></div>');
814 if (ConsoleInput.codeMirror) {
815 CodeMirror.runMode(msgString,
816 'text/x-sql', $newMessage.children('.query')[0]);
818 $newMessage.children('.query').text(msgString);
820 $newMessage.children('.action_content')
821 .append(Console.$consoleTemplates.children('.query_actions').html());
825 $newMessage.append('<div>' + msgString + '</div>');
827 ConsoleMessages.messageEventBinds($newMessage);
828 $newMessage.find('span.text.query_time span')
829 .text(now.getHours() + ':' + now.getMinutes() + ':' + now.getSeconds())
830 .parent().attr('title', now);
833 $message: $newMessage.appendTo('#pma_console .content .console_message_container')
837 * Used for log new query
839 * @param string queryData Struct should be
840 * {sql_query: "Query string", db: "Target DB", table: "Target Table"}
841 * @param string state Message state
842 * @return object, {message_id: string message id, $message: JQuery object}
844 appendQuery: function (queryData, state) {
845 var targetMessage = ConsoleMessages.append(queryData.sql_query, 'query');
846 if (! targetMessage) {
849 if (queryData.db && queryData.table) {
850 targetMessage.$message.attr('targetdb', queryData.db);
851 targetMessage.$message.attr('targettable', queryData.table);
852 targetMessage.$message.find('.text.targetdb span').text(queryData.db);
854 if (Console.isSelect(queryData.sql_query)) {
855 targetMessage.$message.addClass('select');
859 targetMessage.$message.addClass('failed');
862 targetMessage.$message.addClass('successed');
866 targetMessage.$message.addClass('pending');
868 return targetMessage;
870 messageEventBinds: function ($target) {
871 // Leave unbinded elements, remove binded.
872 var $targetMessage = $target.filter(':not(.binded)');
873 if ($targetMessage.length === 0) {
876 $targetMessage.addClass('binded');
878 $targetMessage.find('.action.expand').on('click', function () {
879 $(this).closest('.message').removeClass('collapsed');
880 $(this).closest('.message').addClass('expanded');
882 $targetMessage.find('.action.collapse').on('click', function () {
883 $(this).closest('.message').addClass('collapsed');
884 $(this).closest('.message').removeClass('expanded');
886 $targetMessage.find('.action.edit').on('click', function () {
887 ConsoleInput.setText($(this).parent().siblings('.query').text());
888 ConsoleInput.focus();
890 $targetMessage.find('.action.requery').on('click', function () {
891 var query = $(this).parent().siblings('.query').text();
892 var $message = $(this).closest('.message');
893 if (confirm(Messages.strConsoleRequeryConfirm + '\n' +
894 (query.length < 100 ? query : query.slice(0, 100) + '...'))
896 Console.execute(query, { db: $message.attr('targetdb'), table: $message.attr('targettable') });
899 $targetMessage.find('.action.bookmark').on('click', function () {
900 var query = $(this).parent().siblings('.query').text();
901 var $message = $(this).closest('.message');
902 ConsoleBookmarks.addBookmark(query, $message.attr('targetdb'));
903 Console.showCard('#pma_bookmarks .card.add');
905 $targetMessage.find('.action.edit_bookmark').on('click', function () {
906 var query = $(this).parent().siblings('.query').text();
907 var $message = $(this).closest('.message');
908 var isShared = $message.find('span.bookmark_label').hasClass('shared');
909 var label = $message.find('span.bookmark_label').text();
910 ConsoleBookmarks.addBookmark(query, $message.attr('targetdb'), label, isShared);
911 Console.showCard('#pma_bookmarks .card.add');
913 $targetMessage.find('.action.delete_bookmark').on('click', function () {
914 var $message = $(this).closest('.message');
915 if (confirm(Messages.strConsoleDeleteBookmarkConfirm + '\n' + $message.find('.bookmark_label').text())) {
918 'server': CommonParams.get('server'),
919 'action_bookmark': 2,
920 'ajax_request': true,
921 'id_bookmark': $message.attr('bookmarkid')
924 ConsoleBookmarks.refresh();
928 $targetMessage.find('.action.profiling').on('click', function () {
929 var $message = $(this).closest('.message');
930 Console.execute($(this).parent().siblings('.query').text(),
931 { db: $message.attr('targetdb'),
932 table: $message.attr('targettable'),
935 $targetMessage.find('.action.explain').on('click', function () {
936 var $message = $(this).closest('.message');
937 Console.execute('EXPLAIN ' + $(this).parent().siblings('.query').text(),
938 { db: $message.attr('targetdb'),
939 table: $message.attr('targettable') });
941 $targetMessage.find('.action.dbg_show_trace').on('click', function () {
942 var $message = $(this).closest('.message');
943 if (!$message.find('.trace').length) {
944 ConsoleDebug.getQueryDetails(
945 $message.data('queryInfo'),
946 $message.data('totalTime'),
949 ConsoleMessages.messageEventBinds($message.find('.message:not(.binded)'));
951 $message.addClass('show_trace');
952 $message.removeClass('hide_trace');
954 $targetMessage.find('.action.dbg_hide_trace').on('click', function () {
955 var $message = $(this).closest('.message');
956 $message.addClass('hide_trace');
957 $message.removeClass('show_trace');
959 $targetMessage.find('.action.dbg_show_args').on('click', function () {
960 var $message = $(this).closest('.message');
961 $message.addClass('show_args expanded');
962 $message.removeClass('hide_args collapsed');
964 $targetMessage.find('.action.dbg_hide_args').on('click', function () {
965 var $message = $(this).closest('.message');
966 $message.addClass('hide_args collapsed');
967 $message.removeClass('show_args expanded');
969 if (ConsoleInput.codeMirror) {
970 $targetMessage.find('.query:not(.highlighted)').each(function (index, elem) {
971 CodeMirror.runMode($(elem).text(),
973 $(this).addClass('highlighted');
977 msgAppend: function (msgId, msgString) {
978 var $targetMessage = $('#pma_console').find('.content .console_message_container .message[msgid=' + msgId + ']');
979 if ($targetMessage.length === 0 || isNaN(parseInt(msgId)) || typeof(msgString) !== 'string') {
982 $targetMessage.append('<div>' + msgString + '</div>');
984 updateQuery: function (msgId, isSuccessed, queryData) {
985 var $targetMessage = $('#pma_console').find('.console_message_container .message[msgid=' + parseInt(msgId) + ']');
986 if ($targetMessage.length === 0 || isNaN(parseInt(msgId))) {
989 $targetMessage.removeClass('pending failed successed');
991 $targetMessage.addClass('successed');
993 $targetMessage.children('.query').text('');
994 $targetMessage.removeClass('select');
995 if (Console.isSelect(queryData.sql_query)) {
996 $targetMessage.addClass('select');
998 if (ConsoleInput.codeMirror) {
999 CodeMirror.runMode(queryData.sql_query, 'text/x-sql', $targetMessage.children('.query')[0]);
1001 $targetMessage.children('.query').text(queryData.sql_query);
1003 $targetMessage.attr('targetdb', queryData.db);
1004 $targetMessage.attr('targettable', queryData.table);
1005 $targetMessage.find('.text.targetdb span').text(queryData.db);
1008 $targetMessage.addClass('failed');
1012 * Used for console messages initialize
1016 initialize: function () {
1017 ConsoleMessages.messageEventBinds($('#pma_console').find('.message:not(.binded)'));
1018 if (Console.config.StartHistory) {
1019 ConsoleMessages.showHistory();
1021 ConsoleMessages.showInstructions(Console.config.EnterExecutes);
1026 * Console bookmarks card, and bookmarks items management object
1028 var ConsoleBookmarks = {
1030 addBookmark: function (queryString, targetDb, label, isShared) {
1031 $('#pma_bookmarks').find('.add [name=shared]').prop('checked', false);
1032 $('#pma_bookmarks').find('.add [name=label]').val('');
1033 $('#pma_bookmarks').find('.add [name=targetdb]').val('');
1034 $('#pma_bookmarks').find('.add [name=id_bookmark]').val('');
1035 ConsoleInput.setText('', 'bookmark');
1037 if (typeof queryString !== 'undefined') {
1038 ConsoleInput.setText(queryString, 'bookmark');
1040 if (typeof targetDb !== 'undefined') {
1041 $('#pma_bookmarks').find('.add [name=targetdb]').val(targetDb);
1043 if (typeof label !== 'undefined') {
1044 $('#pma_bookmarks').find('.add [name=label]').val(label);
1046 if (typeof isShared !== 'undefined') {
1047 $('#pma_bookmarks').find('.add [name=shared]').prop('checked', isShared);
1050 refresh: function () {
1053 'ajax_request': true,
1054 'server': CommonParams.get('server'),
1055 'console_bookmark_refresh': 'refresh'
1058 if (data.console_message_bookmark) {
1059 $('#pma_bookmarks').find('.content.bookmark').html(data.console_message_bookmark);
1060 ConsoleMessages.messageEventBinds($('#pma_bookmarks').find('.message:not(.binded)'));
1065 * Used for console bookmarks initialize
1066 * message events are already binded by ConsoleMsg.messageEventBinds
1070 initialize: function () {
1071 if ($('#pma_bookmarks').length === 0) {
1074 $('#pma_console').find('.button.bookmarks').on('click', function () {
1075 Console.showCard('#pma_bookmarks');
1077 $('#pma_bookmarks').find('.button.add').on('click', function () {
1078 Console.showCard('#pma_bookmarks .card.add');
1080 $('#pma_bookmarks').find('.card.add [name=submit]').on('click', function () {
1081 if ($('#pma_bookmarks').find('.card.add [name=label]').val().length === 0
1082 || ConsoleInput.getText('bookmark').length === 0) {
1083 alert(Messages.strFormEmpty);
1086 $(this).prop('disabled', true);
1087 $.post('import.php',
1089 'ajax_request': true,
1090 'console_bookmark_add': 'true',
1091 'label': $('#pma_bookmarks').find('.card.add [name=label]').val(),
1092 'server': CommonParams.get('server'),
1093 'db': $('#pma_bookmarks').find('.card.add [name=targetdb]').val(),
1094 'bookmark_query': ConsoleInput.getText('bookmark'),
1095 'shared': $('#pma_bookmarks').find('.card.add [name=shared]').prop('checked')
1098 ConsoleBookmarks.refresh();
1099 $('#pma_bookmarks').find('.card.add [name=submit]').prop('disabled', false);
1100 Console.hideCard($('#pma_bookmarks').find('.card.add'));
1103 $('#pma_console').find('.button.refresh').on('click', function () {
1104 ConsoleBookmarks.refresh();
1109 var ConsoleDebug = {
1111 groupQueries: false,
1112 orderBy: 'exec', // Possible 'exec' => Execution order, 'time' => Time taken, 'count'
1113 order: 'asc' // Possible 'asc', 'desc'
1119 initialize: function () {
1120 // Try to get debug info after every AJAX request
1121 $(document).ajaxSuccess(function (event, xhr, settings, data) {
1123 ConsoleDebug.showLog(data.debug, settings.url);
1127 if (Console.config.GroupQueries) {
1128 $('#debug_console').addClass('grouped');
1130 $('#debug_console').addClass('ungrouped');
1131 if (Console.config.OrderBy === 'count') {
1132 $('#debug_console').find('.button.order_by.sort_exec').addClass('active');
1135 var orderBy = Console.config.OrderBy;
1136 var order = Console.config.Order;
1137 $('#debug_console').find('.button.order_by.sort_' + orderBy).addClass('active');
1138 $('#debug_console').find('.button.order.order_' + order).addClass('active');
1140 // Initialize actions in toolbar
1141 $('#debug_console').find('.button.group_queries').on('click', function () {
1142 $('#debug_console').addClass('grouped');
1143 $('#debug_console').removeClass('ungrouped');
1144 Console.setConfig('GroupQueries', true);
1145 ConsoleDebug.refresh();
1146 if (Console.config.OrderBy === 'count') {
1147 $('#debug_console').find('.button.order_by.sort_exec').removeClass('active');
1150 $('#debug_console').find('.button.ungroup_queries').on('click', function () {
1151 $('#debug_console').addClass('ungrouped');
1152 $('#debug_console').removeClass('grouped');
1153 Console.setConfig('GroupQueries', false);
1154 ConsoleDebug.refresh();
1155 if (Console.config.OrderBy === 'count') {
1156 $('#debug_console').find('.button.order_by.sort_exec').addClass('active');
1159 $('#debug_console').find('.button.order_by').on('click', function () {
1160 var $this = $(this);
1161 $('#debug_console').find('.button.order_by').removeClass('active');
1162 $this.addClass('active');
1163 if ($this.hasClass('sort_time')) {
1164 Console.setConfig('OrderBy', 'time');
1165 } else if ($this.hasClass('sort_exec')) {
1166 Console.setConfig('OrderBy', 'exec');
1167 } else if ($this.hasClass('sort_count')) {
1168 Console.setConfig('OrderBy', 'count');
1170 ConsoleDebug.refresh();
1172 $('#debug_console').find('.button.order').on('click', function () {
1173 var $this = $(this);
1174 $('#debug_console').find('.button.order').removeClass('active');
1175 $this.addClass('active');
1176 if ($this.hasClass('order_asc')) {
1177 Console.setConfig('Order', 'asc');
1178 } else if ($this.hasClass('order_desc')) {
1179 Console.setConfig('Order', 'desc');
1181 ConsoleDebug.refresh();
1184 // Show SQL debug info for first page load
1185 if (typeof debugSQLInfo !== 'undefined' && debugSQLInfo !== 'null') {
1186 $('#pma_console').find('.button.debug').removeClass('hide');
1190 ConsoleDebug.showLog(debugSQLInfo);
1192 formatFunctionCall: function (dbgStep) {
1193 var functionName = '';
1194 if ('class' in dbgStep) {
1195 functionName += dbgStep.class;
1196 functionName += dbgStep.type;
1198 functionName += dbgStep.function;
1199 if (dbgStep.args && dbgStep.args.length) {
1200 functionName += '(...)';
1202 functionName += '()';
1204 return functionName;
1206 formatFunctionArgs: function (dbgStep) {
1207 var $args = $('<div>');
1208 if (dbgStep.args.length) {
1209 $args.append('<div class="message welcome">')
1211 $('<div class="message welcome">')
1214 Messages.strConsoleDebugArgsSummary,
1219 for (var i = 0; i < dbgStep.args.length; i++) {
1221 $('<div class="message">')
1224 Functions.escapeHtml(JSON.stringify(dbgStep.args[i], null, ' ')) +
1232 formatFileName: function (dbgStep) {
1234 if ('file' in dbgStep) {
1235 fileName += dbgStep.file;
1236 fileName += '#' + dbgStep.line;
1240 formatBackTrace: function (dbgTrace) {
1241 var $traceElem = $('<div class="trace">');
1243 $('<div class="message welcome">')
1247 for (var stepId in dbgTrace) {
1248 if (dbgTrace.hasOwnProperty(stepId)) {
1249 step = dbgTrace[stepId];
1250 if (!Array.isArray(step) && typeof step !== 'object') {
1252 $('<div class="message traceStep collapsed hide_args">')
1254 $('<span>').text(step)
1257 if (typeof step.args === 'string' && step.args) {
1258 step.args = [step.args];
1261 $('<div class="message traceStep collapsed hide_args">')
1263 $('<span class="function">').text(this.formatFunctionCall(step))
1266 $('<span class="file">').text(this.formatFileName(step))
1268 if (step.args && step.args.length) {
1271 $('<span class="args">').html(this.formatFunctionArgs(step))
1274 $('<div class="action_content">')
1276 '<span class="action dbg_show_args">' +
1277 Messages.strConsoleDebugShowArgs +
1281 '<span class="action dbg_hide_args">' +
1282 Messages.strConsoleDebugHideArgs +
1288 $traceElem.append($stepElem);
1293 formatQueryOrGroup: function (queryInfo, totalTime) {
1299 if (Array.isArray(queryInfo)) {
1303 queryText = queryInfo[0].query;
1306 for (i in queryInfo) {
1307 queryTime += queryInfo[i].time;
1310 count = queryInfo.length;
1312 queryText = queryInfo.query;
1313 queryTime = queryInfo.time;
1316 var $query = $('<div class="message collapsed hide_trace">')
1318 $('#debug_console').find('.templates .debug_query').clone()
1321 $('<div class="query">')
1324 .data('queryInfo', queryInfo)
1325 .data('totalTime', totalTime);
1327 $query.find('.text.count').removeClass('hide');
1328 $query.find('.text.count span').text(count);
1330 $query.find('.text.time span').text(queryTime + 's (' + ((queryTime * 100) / totalTime).toFixed(3) + '%)');
1334 appendQueryExtraInfo: function (query, $elem) {
1335 if ('error' in query) {
1337 $('<div>').html(query.error)
1340 $elem.append(this.formatBackTrace(query.trace));
1342 getQueryDetails: function (queryInfo, totalTime, $query) {
1343 if (Array.isArray(queryInfo)) {
1345 for (var i in queryInfo) {
1346 $singleQuery = $('<div class="message welcome trace">')
1347 .text((parseInt(i) + 1) + '.')
1349 $('<span class="time">').text(
1350 Messages.strConsoleDebugTimeTaken +
1351 ' ' + queryInfo[i].time + 's' +
1352 ' (' + ((queryInfo[i].time * 100) / totalTime).toFixed(3) + '%)'
1355 this.appendQueryExtraInfo(queryInfo[i], $singleQuery);
1357 .append('<div class="message welcome trace">')
1358 .append($singleQuery);
1361 this.appendQueryExtraInfo(queryInfo, $query);
1364 showLog: function (debugInfo, url) {
1365 this.lastDebugInfo.debugInfo = debugInfo;
1366 this.lastDebugInfo.url = url;
1368 $('#debug_console').find('.debugLog').empty();
1369 $('#debug_console').find('.debug>.welcome').empty();
1371 var debugJson = false;
1373 if (typeof debugInfo === 'object' && 'queries' in debugInfo) {
1374 // Copy it to debugJson, so that it doesn't get changed
1375 if (!('queries' in debugInfo)) {
1378 debugJson = { queries: [] };
1379 for (i in debugInfo.queries) {
1380 debugJson.queries[i] = debugInfo.queries[i];
1383 } else if (typeof debugInfo === 'string') {
1385 debugJson = JSON.parse(debugInfo);
1389 if (debugJson && !('queries' in debugJson)) {
1393 if (debugJson === false) {
1394 $('#debug_console').find('.debug>.welcome').text(
1395 Messages.strConsoleDebugError
1399 var allQueries = debugJson.queries;
1400 var uniqueQueries = {};
1402 var totalExec = allQueries.length;
1404 // Calculate total time and make unique query array
1406 for (i = 0; i < totalExec; ++i) {
1407 totalTime += allQueries[i].time;
1408 if (!(allQueries[i].hash in uniqueQueries)) {
1409 uniqueQueries[allQueries[i].hash] = [];
1411 uniqueQueries[allQueries[i].hash].push(allQueries[i]);
1413 // Count total unique queries, convert uniqueQueries to Array
1414 var totalUnique = 0;
1415 var uniqueArray = [];
1416 for (var hash in uniqueQueries) {
1417 if (uniqueQueries.hasOwnProperty(hash)) {
1419 uniqueArray.push(uniqueQueries[hash]);
1422 uniqueQueries = uniqueArray;
1424 $('#debug_console').find('.debug>.welcome').append(
1425 $('<span class="debug_summary">').text(
1427 Messages.strConsoleDebugSummary,
1435 $('#debug_console').find('.debug>.welcome').append(
1436 $('<span class="script_name">').text(url.split('?')[0])
1440 // For sorting queries
1441 function sortByTime (a, b) {
1442 var order = ((Console.config.Order === 'asc') ? 1 : -1);
1443 if (Array.isArray(a) && Array.isArray(b)) {
1454 return (timeA - timeB) * order;
1456 return (a.time - b.time) * order;
1460 function sortByCount (a, b) {
1461 var order = ((Console.config.Oorder === 'asc') ? 1 : -1);
1462 return (a.length - b.length) * order;
1465 var orderBy = Console.config.OrderBy;
1466 var order = Console.config.Order;
1468 if (Console.config.GroupQueries) {
1470 if (orderBy === 'time') {
1471 uniqueQueries.sort(sortByTime);
1472 } else if (orderBy === 'count') {
1473 uniqueQueries.sort(sortByCount);
1474 } else if (orderBy === 'exec' && order === 'desc') {
1475 uniqueQueries.reverse();
1477 for (i in uniqueQueries) {
1478 if (orderBy === 'time') {
1479 uniqueQueries[i].sort(sortByTime);
1480 } else if (orderBy === 'exec' && order === 'desc') {
1481 uniqueQueries[i].reverse();
1483 $('#debug_console').find('.debugLog').append(this.formatQueryOrGroup(uniqueQueries[i], totalTime));
1486 if (orderBy === 'time') {
1487 allQueries.sort(sortByTime);
1488 } else if (order === 'desc') {
1489 allQueries.reverse();
1491 for (i = 0; i < totalExec; ++i) {
1492 $('#debug_console').find('.debugLog').append(this.formatQueryOrGroup(allQueries[i], totalTime));
1496 ConsoleMessages.messageEventBinds($('#debug_console').find('.message:not(.binded)'));
1498 refresh: function () {
1499 var last = this.lastDebugInfo;
1500 ConsoleDebug.showLog(last.debugInfo, last.url);
1505 * Executed on page load
1508 Console.initialize();