| # |
@@ -704,6 +772,9 @@ document.addEventListener('alpine:init', () => {
results: [],
history: [],
activeTab: 'results',
+ batchMode: false,
+ batchResults: [],
+ batchActiveIdx: 0,
statusMsg: '',
statusOk: true,
testMsg: '',
@@ -1374,6 +1445,10 @@ document.addEventListener('alpine:init', () => {
async runQuery() {
if (!this.selectedConxId || !this.sqlText.trim()) return;
+ if (this.batchMode) {
+ await this.runBatch();
+ return;
+ }
this.loadingQuery = true;
this.statusMsg = '';
this.results = [];
@@ -1407,6 +1482,65 @@ document.addEventListener('alpine:init', () => {
await this.loadHistory();
},
+ // ── Batch mode ──────────────────────────────────────────────
+ extractSQLStatements(text) {
+ // Split by semicolons, trim whitespace, remove empty
+ return text
+ .split(';')
+ .map(s => s.trim())
+ .filter(s => s.length > 0 && !s.toUpperCase().startsWith('--') && !s.startsWith('#'));
+ },
+
+ uploadSQLFile(event) {
+ const file = event.target.files[0];
+ if (!file) return;
+ const reader = new FileReader();
+ reader.onload = (e) => {
+ const content = e.target.result;
+ this.sqlText = content;
+ this.batchMode = true;
+ this.showToast(`📁 Archivo "${file.name}" cargado (${(content.length / 1024).toFixed(1)} KB)`);
+ };
+ reader.readAsText(file);
+ // Reset input so re-selecting same file triggers change
+ event.target.value = '';
+ },
+
+ async runBatch() {
+ if (!this.selectedConxId) return;
+ const sqls = this.extractSQLStatements(this.sqlText);
+ if (sqls.length === 0) {
+ this.showToast('No se encontraron sentencias SQL válidas', 'error');
+ return;
+ }
+ this.loadingQuery = true;
+ this.statusMsg = '';
+ this.batchResults = [];
+ this.batchActiveIdx = 0;
+ this.activeTab = 'results';
+ try {
+ const formData = new FormData();
+ formData.append('conx_db_id', this.selectedConxId);
+ formData.append('database', this.selectedDb || '');
+ sqls.forEach((sql, i) => formData.append('sqls[]', sql));
+ const res = await axios.post('/app/query-runner/run-batch', formData, {
+ headers: { 'Content-Type': 'multipart/form-data' }
+ });
+ this.batchResults = res.data.results || [];
+ const okCount = this.batchResults.filter(r => r.status === 'ok').length;
+ const errCount = this.batchResults.filter(r => r.status === 'error').length;
+ this.statusOk = errCount === 0;
+ this.statusMsg = `✓ ${okCount} consultas ejecutadas, ${errCount} con error — ${res.data.duration || ''}`;
+ } catch (e) {
+ this.statusOk = false;
+ this.statusMsg = '✗ ' + (e.response?.data?.error || 'Error al ejecutar batch');
+ if (e.response?.data?.results) {
+ this.batchResults = e.response.data.results;
+ }
+ }
+ this.loadingQuery = false;
+ },
+
async loadHistory() {
if (!this.selectedConxId) return;
try {
diff --git a/resources/views/roles.html b/resources/views/roles.html
index a7900ef..3c2dd72 100755
--- a/resources/views/roles.html
+++ b/resources/views/roles.html
@@ -120,6 +120,7 @@
Name = registro.name;
Description = registro.description;
Submodules = [...registro.submodules];
+ ConxDBs = [...(registro.conx_dbs || [])];
EsPortalCliente = registro.es_portal_cliente || false;
EsPortalPartner = registro.es_portal_partner || false;
HomeUrl = registro.home_url || '';
@@ -254,6 +255,20 @@
+
+
Bases de datos permitidas (Query Runner)
+
+
+
+ c.ID !== con.ID) }"
+ class="mr-2">
+
+
+
+
+
+
+
Bases de datos permitidas (Query Runner)
+
+
+
+ c.ID !== con.ID) }"
+ class="mr-2">
+
+
+
+
+