Files
occweb/js/index.js
T

186 lines
6.4 KiB
JavaScript

(function (OC, window, $, undefined) {
'use strict';
$(function() {
function scrollToBottom(){
var html = $('html');
html.scrollTop(html.prop('scrollHeight'));
}
var baseUrl = OC.generateUrl('/apps/occweb');
// Текущий режим терминала: 'occ' (обычные occ-команды) или 'sql'
// (выполнение произвольных SQL-запросов через /db/query).
var mode = 'occ';
var OCC_PROMPT = 'occ $ ';
var SQL_PROMPT = '[[;#ff5555;]sql]# ';
// Быстрая клиентская проверка на DELETE — только для UX (чтобы не
// делать лишний запрос к серверу). Итоговое решение всё равно
// принимает бэкенд (requiresConfirmation), это лишь подсказка.
function scriptHasDelete(sql) {
return sql.split(';').some(function (part) {
var normalized = part.replace(/^(\s*--[^\n]*\n)*\s*/, '');
return /^DELETE\b/i.test(normalized);
});
}
function renderSqlResponse(term, response) {
if (!response) {
term.echo('[[;#ff5555;]Empty response from server]');
return;
}
if (response.success === false) {
term.echo('[[;#ff5555;]Error: ]' + $.terminal.escape_formatting(response.error || 'unknown error'));
return;
}
var results = response.results || [];
if (!results.length) {
term.echo('[[;yellow;]No statements were executed]');
return;
}
results.forEach(function (r) {
term.echo('[[;#009ae3;]> ]' + $.terminal.escape_formatting(r.query || ''));
if (r.type === 'error') {
term.echo('[[;#ff5555;] Error: ]' + $.terminal.escape_formatting(r.error || 'unknown error'));
} else if (r.type === 'select') {
term.echo('[[;gray;] ' + r.count + ' row(s)]');
if (r.count > 0) {
term.echo($.terminal.escape_formatting(JSON.stringify(r.data, null, 2)));
}
} else if (r.type === 'set') {
term.echo('[[;green;] OK (session variable set)]');
} else if (r.type === 'delete') {
term.echo('[[;#ff9900;] DELETED ' + r.affected_rows + ' row(s)]');
} else {
term.echo('[[;green;] OK, ' + r.affected_rows + ' row(s) affected]');
}
});
}
function enterSqlMode(term) {
mode = 'sql';
term.set_prompt(SQL_PROMPT);
term.echo('[[;yellow;]Switched to SQL mode. Admin only — statements run directly against the database.]');
term.echo('[[;gray;]Separate statements with ";". Shift+Enter for a new line, Enter to run. Type "occ" to go back.]');
}
function exitSqlMode(term) {
mode = 'occ';
term.set_prompt(OCC_PROMPT);
term.echo('[[;yellow;]Switched back to OCC mode.]');
}
function sendSqlQuery(term, sql, confirmed) {
term.pause();
$.ajax({
url: baseUrl + '/db/query',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({ sql: sql, confirm: !!confirmed })
}).done(function (response) {
if (response && response.requiresConfirmation) {
term.resume();
askDeleteConfirmation(term, sql, response.error);
return;
}
renderSqlResponse(term, response);
term.resume();
}).fail(function (xhr) {
term.echo('[[;#ff5555;]Request failed: ]' + $.terminal.escape_formatting(xhr.status + ' ' + xhr.statusText));
term.resume();
});
}
function askDeleteConfirmation(term, sql, message) {
var prompt = '[[;#ff5555;]' + (message || 'This script contains DELETE statement(s).') + ' Type "yes" to run it: ]';
term.read(prompt).then(function (answer) {
if ((answer || '').trim().toLowerCase() === 'yes') {
sendSqlQuery(term, sql, true);
} else {
term.echo('[[;yellow;]Cancelled — nothing was executed.]');
}
}, function () {
term.echo('[[;yellow;]Cancelled — nothing was executed.]');
});
}
$.get(baseUrl + '/cmd', function(response){
$('#app-content').terminal(function(command, term) {
if (mode === 'sql') {
var trimmed = command.trim();
if (trimmed === 'occ') {
exitSqlMode(term);
return;
}
if (trimmed === 'c') {
term.clear();
return;
}
if (trimmed === 'exit') {
exitSqlMode(term);
term.reset();
return;
}
if (!trimmed) {
return;
}
if (scriptHasDelete(command)) {
askDeleteConfirmation(term, command);
} else {
sendSqlQuery(term, command, false);
}
return;
}
switch (command) {
case "c":
this.clear();
break;
case "exit":
this.reset();
break;
case "sql":
enterSqlMode(term);
break;
default:
var occCommand = {
command: command
};
term.pause();
$.ajax({
url: baseUrl + '/cmd',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(occCommand)
}).done(function (response) {
term.echo('\n' + response).resume();
}).fail(function (response, code) {
term.echo('\n' + response).resume();
});
}
}, {
greetings: function (callback) {
callback('[[;green;]' + new Date().toString().slice(0, 24) + "]\n\nPress [[;#ff5e99;]Enter] for more information on [[;#009ae3;]occ] commands.\nType [[;#ff5e99;]sql] to switch to SQL query mode.\n")
},
name: 'occ',
prompt: OCC_PROMPT,
completion: response,
keydown: function (e) {
// Shift+Enter вставляет перевод строки вместо выполнения команды,
// это позволяет набирать многострочные SQL-скрипты в режиме sql.
if (e.shiftKey && e.key === 'Enter') {
this.insert('\n');
return false;
}
},
onResize: function(){
scrollToBottom()
}
});
});
$('html').keypress(function(){
scrollToBottom()
})
});
})(OC, window, jQuery);