Update index.js

This commit is contained in:
fanategorius
2026-07-06 16:28:22 +03:00
committed by GitHub
parent b6ea7b9d92
commit b6849053d2
+137 -91
View File
@@ -1,101 +1,147 @@
<?php
namespace OCA\OCCWeb\Controller;
use OCP\AppFramework\Controller;
use OCP\IRequest;
use OCP\IDBConnection;
use OCP\AppFramework\Http\JSONResponse;
use OCP\IGroupManager;
use OCP\IUserSession;
class DbController extends Controller
{
private $db;
private $groupManager;
private $userSession;
public function __construct(
$AppName,
IRequest $request,
IDBConnection $db,
IGroupManager $groupManager,
IUserSession $userSession
) {
parent::__construct($AppName, $request);
$this->db = $db;
$this->groupManager = $groupManager;
$this->userSession = $userSession;
(function (OC, window, $, undefined) {
'use strict';
$(function() {
function scrollToBottom(){
var html = $('html');
html.scrollTop(html.prop('scrollHeight'));
}
var baseUrl = OC.generateUrl('/apps/occweb');
/**
* @NoCSRFRequired
*/
public function query()
{
// Проверка прав администратора
$user = $this->userSession->getUser();
if (!$user) {
return new JSONResponse(['error' => 'Not authenticated'], 401);
// Текущий режим терминала: 'occ' (обычные occ-команды) или 'sql'
// (выполнение произвольных SQL-запросов через /db/query).
var mode = 'occ';
var OCC_PROMPT = 'occ $ ';
var SQL_PROMPT = '[[;#ff5555;]sql]# ';
function renderSqlResponse(term, response) {
if (!response) {
term.echo('[[;#ff5555;]Empty response from server]');
return;
}
if (!$this->groupManager->isAdmin($user->getUID())) {
return new JSONResponse(['error' => 'Admin privileges required'], 403);
if (response.success === false) {
term.echo('[[;#ff5555;]Error: ]' + $.terminal.escape_formatting(response.error || 'unknown error'));
return;
}
$sql = $this->request->getParam('sql', '');
if (empty(trim($sql))) {
return new JSONResponse(['success' => false, 'error' => 'Empty query']);
var results = response.results || [];
if (!results.length) {
term.echo('[[;yellow;]No statements were executed]');
return;
}
// Разделяем запросы по точке с запятой. Все запросы выполняются
// последовательно на одном и том же соединении с БД (в рамках
// одного HTTP-запроса), поэтому SET сохраняет своё значение
// для current_setting() в последующих запросах пачки.
$queries = array_values(array_filter(array_map('trim', explode(';', $sql)), function ($q) {
return $q !== '';
}));
$results = [];
foreach ($queries as $query) {
$isSelect = stripos($query, 'SELECT') === 0;
$isSet = stripos($query, 'SET ') === 0;
try {
$stmt = $this->db->prepare($query);
$stmt->execute();
if ($isSelect) {
$rows = $stmt->fetchAll();
$results[] = [
'query' => $query,
'type' => 'select',
'count' => count($rows),
'data' => $rows
];
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 {
$affected = $stmt->rowCount();
$results[] = [
'query' => $query,
'type' => $isSet ? 'set' : 'write',
'affected_rows' => $affected
];
}
} catch (\Exception $e) {
$results[] = [
'query' => $query,
'type' => 'error',
'error' => $e->getMessage()
];
// Останавливаемся на первой ошибке: не продолжаем выполнять
// оставшиеся запросы пачки (например, серию DELETE),
// если один из предыдущих шагов не выполнился.
break;
term.echo('[[;green;] OK, ' + r.affected_rows + ' row(s) affected]');
}
});
}
return new JSONResponse(['success' => true, 'results' => $results]);
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.]');
}
$.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;
}
term.pause();
$.ajax({
url: baseUrl + '/db/query',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({ sql: command })
}).done(function (response) {
renderSqlResponse(term, response);
term.resume();
}).fail(function (xhr) {
term.echo('[[;#ff5555;]Request failed: ]' + $.terminal.escape_formatting(xhr.status + ' ' + xhr.statusText));
term.resume();
});
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);