Send a bug report to one place, on purpose, after reading it
The page built its own markdown and nothing could send it; local and upstream are different people, so neither falls back to the other.
This commit is contained in:
+98
-39
@@ -319,6 +319,20 @@ if (is_dir('/var/log/varaverk')) {
|
||||
</div>
|
||||
<div id="vv-ai-bugs"></div>
|
||||
<!-- Manual-copy fallback for plain-http WebGUIs, where the clipboard API is unavailable. -->
|
||||
<!-- Review, then send. The box is readonly because the text shown here is the text that
|
||||
goes — the server re-renders from the store rather than accepting a body from the page,
|
||||
so an editable box would only create a gap between what was approved and what was sent.
|
||||
Sending is a separate press from building, and nothing leaves on its own. -->
|
||||
<div id="vv-ai-bug-send" style="display:none;margin-top:8px;">
|
||||
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
|
||||
<button class="vv-ai-btn" id="vv-ai-bug-local" type="button"
|
||||
style="display:none" onclick="vvAiBugSendLocal(this)">Send to Gitea</button>
|
||||
<button class="vv-ai-btn" id="vv-ai-bug-github" type="button"
|
||||
onclick="vvAiBugGithub()">Open a GitHub issue</button>
|
||||
<button class="vv-ai-btn ghost" type="button" onclick="vvAiBugCopy()">Copy</button>
|
||||
</div>
|
||||
<div class="vv-ai-set-d" id="vv-ai-bug-dest" style="margin-top:6px;"></div>
|
||||
</div>
|
||||
<textarea id="vv-ai-bug-copybox" class="vv-ai-input" rows="8" readonly spellcheck="false"
|
||||
style="display:none;margin-top:8px" onclick="this.select()"></textarea>
|
||||
</div>
|
||||
@@ -616,7 +630,8 @@ vv_ai_chat_markup('vv-ai', [
|
||||
|
||||
function renderBugs(bugs) {
|
||||
const wrap = $('vv-ai-bugs-wrap');
|
||||
vvBugs = bugs;
|
||||
// The list used to be cached here for the page's own markdown builder. The server renders the
|
||||
// report now, fetched by id, so there is nothing left to keep.
|
||||
if (!bugs.length) { wrap.style.display = 'none'; return; }
|
||||
wrap.style.display = '';
|
||||
$('vv-ai-bugs-sum').innerHTML =
|
||||
@@ -631,7 +646,7 @@ vv_ai_chat_markup('vv-ai', [
|
||||
<span class="vv-ai-bug-m">${esc(b.id)}${esc(seen)} · ${esc(when)}${
|
||||
b.context && b.context.verified === null
|
||||
? ' · <span class="vv-ai-warn">evidence unverified</span>' : ''}</span>
|
||||
<button class="vv-ai-btn ghost" onclick="vvAiBugCopy('${esc(b.id)}')">Copy issue</button>
|
||||
<button class="vv-ai-btn" onclick="vvAiBugReport('${esc(b.id)}')">Report…</button>
|
||||
<button class="vv-ai-btn ghost" onclick="vvAiBugClose('${esc(b.id)}')">Dismiss</button>
|
||||
</div>
|
||||
<div class="vv-ai-bug-s">${esc(b.summary)}</div>
|
||||
@@ -642,46 +657,90 @@ vv_ai_chat_markup('vv-ai', [
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// Copy out, never transmit. The operator reads the text before it goes anywhere, which is the
|
||||
// whole reason this is a button and not a webhook — evidence is quoted log lines, and Varaverk
|
||||
// logs carry share names, container names and paths. Redaction by inspection beats redaction
|
||||
// by rule, and it cannot be unpublished later.
|
||||
let vvBugs = [];
|
||||
// The report itself is rendered by the server — see api/ai.php?action=bug_report. This page
|
||||
// used to build its own markdown, which meant two formats for the same thing and no way to
|
||||
// tell which one a given issue had been filed with.
|
||||
//
|
||||
// Read-only on purpose. What is reviewed is byte for byte what is sent, so there is no gap
|
||||
// between the text approved and the text transmitted — and the server re-renders from the
|
||||
// store when sending rather than trusting anything the page hands back.
|
||||
window.vvAiBugReport = function (id) {
|
||||
const box = document.getElementById('vv-ai-bug-copybox');
|
||||
const bar = document.getElementById('vv-ai-bug-send');
|
||||
box.value = 'Loading…'; box.style.display = ''; bar.style.display = 'none';
|
||||
|
||||
function vvAiBugMarkdown(b) {
|
||||
const d = t => t ? new Date(t * 1000).toISOString().slice(0, 10) : '—';
|
||||
return `### ${b.component} — ${b.summary}\n\n`
|
||||
+ `| | |\n|---|---|\n`
|
||||
+ `| Component | \`${b.component}\` |\n`
|
||||
+ `| Varaverk | \`${b.commit || 'unknown'}\` |\n`
|
||||
+ `| Host slot | ${b.host || '—'} |\n`
|
||||
+ `| Seen | ${b.seen}× · ${d(b.first)} → ${d(b.last)} |\n\n`
|
||||
+ `**Evidence**\n\n\`\`\`\n${b.evidence}\n\`\`\`\n\n`
|
||||
+ (b.context && b.context.asked ? `**Asked while diagnosing:** ${b.context.asked}\n\n` : '')
|
||||
+ (b.context && b.context.log ? `**Log:** \`${b.context.log}\`\n\n` : '')
|
||||
+ `_Filed automatically by the Varaverk assistant. Report ${b.id}._\n`;
|
||||
}
|
||||
fetch(API + '?action=bug_report&id=' + encodeURIComponent(id))
|
||||
.then(r => r.json())
|
||||
.then(d => {
|
||||
if (!d.ok) throw new Error(d.error || 'could not build the report');
|
||||
box.value = d.markdown;
|
||||
bar.dataset.id = id;
|
||||
bar.dataset.url = d.github;
|
||||
bar.style.display = '';
|
||||
|
||||
window.vvAiBugCopy = function (id) {
|
||||
const b = vvBugs.find(x => x.id === id);
|
||||
if (!b) return;
|
||||
const text = vvAiBugMarkdown(b);
|
||||
const done = ok => {
|
||||
const btn = event && event.target;
|
||||
if (btn) { btn.textContent = ok ? 'Copied' : 'Select below'; setTimeout(() => btn.textContent = 'Copy issue', 1800); }
|
||||
if (!ok) {
|
||||
// The clipboard API needs a secure context and the WebGUI is often plain http on the
|
||||
// LAN. Falling back to a selectable box means the button always does something useful
|
||||
// rather than failing silently on exactly the setups this is built for.
|
||||
const box = document.getElementById('vv-ai-bug-copybox');
|
||||
box.style.display = ''; box.value = text; box.focus(); box.select();
|
||||
}
|
||||
};
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
navigator.clipboard.writeText(text).then(() => done(true)).catch(() => done(false));
|
||||
} else {
|
||||
done(false);
|
||||
const t = d.targets || {};
|
||||
const local = $('vv-ai-bug-local'), gh = $('vv-ai-bug-github');
|
||||
// Only offered where it can actually work. A button that reports "local reporting is
|
||||
// switched off" after being pressed is a button that should not have been drawn.
|
||||
local.style.display = (t.local_enabled && t.local_configured) ? '' : 'none';
|
||||
local.textContent = 'Send to ' + (t.local_repo || 'Gitea');
|
||||
// GitHub is the path when local is off — and stays available when it is on, because
|
||||
// "my own backlog" and "tell the maintainer" are different intentions, not a fallback.
|
||||
gh.style.display = '';
|
||||
$('vv-ai-bug-dest').textContent = t.local_enabled
|
||||
? (t.local_configured
|
||||
? 'Local reporting is on — this stays on your Gitea and does not reach the maintainer.'
|
||||
: 'Local reporting is on but URL, repo or token is blank, so only GitHub is available.')
|
||||
: 'Opens a prefilled issue you submit under your own GitHub account.';
|
||||
box.focus(); box.select();
|
||||
})
|
||||
.catch(e => { box.value = 'Could not build the report: ' + (e.message || e); });
|
||||
};
|
||||
|
||||
window.vvAiBugSendLocal = function (btn) {
|
||||
const bar = document.getElementById('vv-ai-bug-send');
|
||||
const out = $('vv-ai-bug-dest');
|
||||
btn.disabled = true;
|
||||
out.textContent = 'sending…';
|
||||
// URLSearchParams, not FormData — a multipart POST to this endpoint hangs with no status.
|
||||
fetch(API, { method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
|
||||
body: new URLSearchParams({ action: 'bug_send_local', id: bar.dataset.id }) })
|
||||
.then(r => r.json())
|
||||
.then(d => {
|
||||
btn.disabled = false;
|
||||
if (!d.ok) { out.innerHTML = `<span class="vv-ai-bad">${esc(d.error || 'failed')}</span>`; return; }
|
||||
// The issue URL is the receipt. Without it "sent" is just a word the page chose.
|
||||
out.innerHTML = d.url
|
||||
? `<span class="vv-ai-ok">filed:</span> <a href="${esc(d.url)}" target="_blank"
|
||||
rel="noopener noreferrer" style="color:#7d9be8">${esc(d.url)}</a>`
|
||||
: '<span class="vv-ai-ok">filed</span>';
|
||||
})
|
||||
.catch(e => { btn.disabled = false; out.innerHTML = `<span class="vv-ai-bad">${esc(String(e))}</span>`; });
|
||||
};
|
||||
|
||||
window.vvAiBugGithub = function () {
|
||||
const bar = document.getElementById('vv-ai-bug-send');
|
||||
const url = bar.dataset.url || '';
|
||||
// GitHub silently truncates an over-long query rather than refusing it, which would file half
|
||||
// a report and look like it worked. Past that length the copy box is the honest path.
|
||||
if (url.length > 7500) {
|
||||
$('vv-ai-bug-dest').innerHTML =
|
||||
'<span class="vv-ai-warn">Too long for a prefilled URL — copy the text above into a new '
|
||||
+ 'issue instead.</span>';
|
||||
return;
|
||||
}
|
||||
window.open(url, '_blank', 'noopener,noreferrer');
|
||||
};
|
||||
|
||||
// Kept because the clipboard API needs a secure context and the WebGUI is often plain http on
|
||||
// the LAN — on exactly the setups this is built for, the box is the path that works.
|
||||
window.vvAiBugCopy = function (id) {
|
||||
const box = document.getElementById('vv-ai-bug-copybox');
|
||||
if (!box.value || box.style.display === 'none') { vvAiBugReport(id); return; }
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
navigator.clipboard.writeText(box.value).catch(() => { box.focus(); box.select(); });
|
||||
} else { box.focus(); box.select(); }
|
||||
};
|
||||
|
||||
window.vvAiBugClose = function (id) {
|
||||
|
||||
Reference in New Issue
Block a user