Add a copy-out button that formats a report as a pasteable issue
This commit is contained in:
@@ -824,10 +824,23 @@ function vv_ai_bug_write(string $component, string $summary, string $evidence, a
|
|||||||
if ($component === '' || $summary === '' || $evidence === '') return ['ok' => false];
|
if ($component === '' || $summary === '' || $evidence === '') return ['ok' => false];
|
||||||
if (!vv_ai_scope_ok($component)) return ['ok' => false];
|
if (!vv_ai_scope_ok($component)) return ['ok' => false];
|
||||||
|
|
||||||
|
// The commit as it was when the fault was seen, not when the report is read. "Is this already
|
||||||
|
// fixed" is the first question any maintainer asks, and a version captured later answers a
|
||||||
|
// different question. Slot id (host1/host2) rather than the hostname — this text is written
|
||||||
|
// to be pasted into a public issue.
|
||||||
|
$commit = '';
|
||||||
|
$head = @file_get_contents(SCRIPTS_DIR . '/.git/HEAD');
|
||||||
|
if (is_string($head) && preg_match('#^ref:\s*(\S+)#', trim($head), $hm)) {
|
||||||
|
$commit = substr(trim((string)@file_get_contents(SCRIPTS_DIR . '/.git/' . $hm[1])), 0, 12);
|
||||||
|
} elseif (is_string($head)) {
|
||||||
|
$commit = substr(trim($head), 0, 12);
|
||||||
|
}
|
||||||
|
|
||||||
$rec = [
|
$rec = [
|
||||||
'component' => mb_substr($component, 0, 120),
|
'component' => mb_substr($component, 0, 120),
|
||||||
'summary' => mb_substr($summary, 0, 300),
|
'summary' => mb_substr($summary, 0, 300),
|
||||||
'evidence' => mb_substr($evidence, 0, 2000),
|
'evidence' => mb_substr($evidence, 0, 2000),
|
||||||
|
'commit' => $commit,
|
||||||
'host' => vv_detect_host(),
|
'host' => vv_detect_host(),
|
||||||
'first' => time(),
|
'first' => time(),
|
||||||
'last' => time(),
|
'last' => time(),
|
||||||
|
|||||||
@@ -278,6 +278,9 @@ if (is_dir('/var/log/varaverk')) {
|
|||||||
<span class="vv-ai-set-sum" style="margin-left:auto">filed from the Scheduler troubleshooter</span>
|
<span class="vv-ai-set-sum" style="margin-left:auto">filed from the Scheduler troubleshooter</span>
|
||||||
</div>
|
</div>
|
||||||
<div id="vv-ai-bugs"></div>
|
<div id="vv-ai-bugs"></div>
|
||||||
|
<!-- Manual-copy fallback for plain-http WebGUIs, where the clipboard API is unavailable. -->
|
||||||
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -536,6 +539,7 @@ if (is_dir('/var/log/varaverk')) {
|
|||||||
|
|
||||||
function renderBugs(bugs) {
|
function renderBugs(bugs) {
|
||||||
const wrap = $('vv-ai-bugs-wrap');
|
const wrap = $('vv-ai-bugs-wrap');
|
||||||
|
vvBugs = bugs;
|
||||||
if (!bugs.length) { wrap.style.display = 'none'; return; }
|
if (!bugs.length) { wrap.style.display = 'none'; return; }
|
||||||
wrap.style.display = '';
|
wrap.style.display = '';
|
||||||
$('vv-ai-bugs-sum').innerHTML =
|
$('vv-ai-bugs-sum').innerHTML =
|
||||||
@@ -548,6 +552,7 @@ if (is_dir('/var/log/varaverk')) {
|
|||||||
<div class="vv-ai-bug-h">
|
<div class="vv-ai-bug-h">
|
||||||
<span class="vv-ai-bug-c">${esc(b.component)}</span>
|
<span class="vv-ai-bug-c">${esc(b.component)}</span>
|
||||||
<span class="vv-ai-bug-m">${esc(b.id)}${esc(seen)} · ${esc(when)}</span>
|
<span class="vv-ai-bug-m">${esc(b.id)}${esc(seen)} · ${esc(when)}</span>
|
||||||
|
<button class="vv-ai-btn ghost" onclick="vvAiBugCopy('${esc(b.id)}')">Copy issue</button>
|
||||||
<button class="vv-ai-btn ghost" onclick="vvAiBugClose('${esc(b.id)}')">Dismiss</button>
|
<button class="vv-ai-btn ghost" onclick="vvAiBugClose('${esc(b.id)}')">Dismiss</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="vv-ai-bug-s">${esc(b.summary)}</div>
|
<div class="vv-ai-bug-s">${esc(b.summary)}</div>
|
||||||
@@ -558,6 +563,48 @@ if (is_dir('/var/log/varaverk')) {
|
|||||||
}).join('');
|
}).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 = [];
|
||||||
|
|
||||||
|
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`;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
window.vvAiBugClose = function (id) {
|
window.vvAiBugClose = function (id) {
|
||||||
fetch(API, { method: 'POST',
|
fetch(API, { method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
|
||||||
|
|||||||
Reference in New Issue
Block a user