feat: edit SQLite databases in the file manager - the agent opens them through SQLite: values by a click, rows added and deleted, an SQL tab; changes go to the audit log

This commit is contained in:
redtea committed 2026-10-15 16:50:56 +03:00
1 parent 0279c1d887
commit 0fa3de757c
7 files changed
+947 -13

No files matched your search

+61
View File
@@ -207,6 +207,53 @@
.find #frow2, .find #frow2b { grid-row:2; }
/* Documents and tables, drawn in the panel's colours. */
.vdoc { height:75vh; overflow:auto; background:#0c0f13; border-radius:0 0 12px 12px; }
/* SQLite editor. */
.dbx { display:flex; flex-direction:column; height:100%; transition:opacity .2s; }
.dbx.busy { opacity:.75; }
.dbx-tabs { display:flex; gap:4px; padding:8px 10px 0; overflow-x:auto; flex:none; border-bottom:1px solid var(--border); }
.dbx-tabs button { flex:none; background:transparent; color:var(--muted); border:1px solid transparent; border-bottom:0; border-radius:8px 8px 0 0;
padding:6px 12px; font-size:13px; transition:background .15s, color .15s; }
.dbx-tabs button:hover { color:var(--text); background:#151a21; }
.dbx-tabs button.on { color:var(--text); background:var(--panel); border-color:var(--border); }
.dbx-tabs .dbx-sqltab { margin-left:auto; font-family:ui-monospace,Menlo,monospace; }
.dbx-tools { display:flex; align-items:center; gap:8px; padding:8px 10px; flex:none; flex-wrap:wrap; font-size:13px; }
.dbx-tools[hidden], .dbx-pager[hidden], .dbx-grid[hidden], .dbx-sql[hidden], .dbx-tools button[hidden] { display:none; }
.dbx-pager { display:inline-flex; align-items:center; gap:6px; }
.dbx-sp { flex:1; }
.dbx-grid { flex:1; min-height:0; overflow:auto; }
.dbx-t { width:auto; border-collapse:separate; border-spacing:0; font-size:13px; font-variant-numeric:tabular-nums; }
.dbx-t th, .dbx-t td { border-right:1px solid var(--border); border-bottom:1px solid var(--border); padding:5px 10px; text-align:left;
max-width:420px; white-space:pre-wrap; overflow-wrap:anywhere; vertical-align:top; }
.dbx-t thead th { position:sticky; top:0; z-index:1; background:var(--panel); color:var(--text); font-weight:500; white-space:nowrap; }
.dbx-t thead th small { display:block; color:var(--muted); font-weight:400; font-size:11px; }
.dbx-t td.num { text-align:right; }
.dbx-t .dbx-k { color:var(--muted); text-align:right; }
.dbx-t .dbx-ck { width:1%; text-align:center; }
.dbx-t td.ed { cursor:text; transition:background .15s; }
.dbx-t td.ed:hover { background:#151a21; }
.dbx-t td.editing { padding:0; background:#12202f; }
.dbx-t td.saving { opacity:.6; }
.dbx-t td.flash-ok { animation:dbx-ok 1.2s ease; }
.dbx-t td.flash-bad { animation:dbx-bad 1.2s ease; }
@keyframes dbx-ok { 0% { background:rgba(55,200,113,.28); } 100% { background:transparent; } }
@keyframes dbx-bad { 0% { background:rgba(255,92,92,.3); } 100% { background:transparent; } }
.dbx-null { color:var(--muted); font-size:12px; }
.dbx-edit { position:relative; }
.dbx-edit textarea { display:block; width:100%; min-height:30px; resize:vertical; box-sizing:border-box; margin:0; padding:5px 28px 5px 10px; border:1px solid var(--accent);
border-radius:0; background:#0c0f13; color:var(--text); font:inherit; outline:none; }
.dbx-nullb { position:absolute; top:4px; right:4px; width:20px; height:20px; padding:0; font-size:12px; line-height:1;
background:#1b222c; color:var(--muted); border:1px solid var(--border); border-radius:5px; }
.dbx-nullb:hover { color:var(--text); background:#232b36; }
.dbx-t tr.dbx-new td { padding:3px; background:#12202f; }
.dbx-t tr.dbx-new input { width:100%; min-width:90px; height:28px; padding:3px 8px; font-size:13px; }
.dbx-t tr.dbx-newbar td { background:#12202f; padding:6px 8px; white-space:nowrap; }
.dbx-sql { flex:1; min-height:0; display:flex; flex-direction:column; gap:8px; padding:10px; }
.dbx-q { flex:none; height:110px; resize:vertical; font:13px/1.5 ui-monospace,Menlo,monospace; background:#0c0f13; color:var(--text);
border:1px solid var(--border); border-radius:8px; padding:8px 10px; }
.dbx-q:focus { border-color:var(--accent); outline:none; }
.dbx-sqlbar { display:flex; align-items:center; gap:10px; font-size:13px; }
.dbx-sqlmsg.bad { color:var(--danger); }
.dbx-sqlres { flex:1; min-height:0; overflow:auto; }
.vdoc .dv-pdf { display:block; width:100%; height:100%; border:0; background:#525659; }
.vdoc .dv-html { display:block; width:100%; height:100%; border:0; background:#fff; }
.dv-doc { max-width:820px; margin:0 auto; padding:28px 32px 40px; font-size:15px; line-height:1.65; color:var(--text); overflow-wrap:anywhere; }
@@ -468,6 +515,7 @@
<script src="{{asset "codemirror/codemirror.js"}}"></script>
<script src="{{asset "docview.js"}}"></script>
<script src="{{asset "dbedit.js"}}"></script>
<script>
(function () {
const agentID = {{.Agent.ID}};
@@ -865,6 +913,19 @@
if (r.data.truncated) $('vmsg').textContent += ' · показаны первые ' + fmtSize(EDIT_LIMIT);
return;
}
if (/^(db|sqlite3?|db3)$/.test(ext)) {
// The agent opens the database and edits it; one that predates that
// gets the file read here, for viewing only.
box.innerHTML = '<p class="dv-note">загрузка…</p>';
const r = await DBEdit.open(box, path, (op, args) => api(op, args), toast);
if (V.path !== path || V.kind !== 'doc') return;
if (r && r.ok) return;
if (!/неизвестная операция/.test((r && r.error) || '')) {
box.innerHTML = '<p class="dv-note">не удалось открыть: ' + esc((r && r.error) || 'ошибка') + '</p>';
return;
}
$('vmsg').append(' · только просмотр: обновите агента, чтобы править');
}
if (size > DOC_CAP) { box.innerHTML = '<p class="dv-note">файл больше ' + fmtSize(DOC_CAP) + ' - для просмотра скачайте его</p>'; return; }
box.innerHTML = '<p class="dv-note">загрузка…</p>';
let html;