feat: Report Bug button that creates Gitea issues

Adds a 'Report Bug' button to the web UI (top bar) that files an issue
on Gitea via the daemon. The issue body is enriched with auto-collected
context: nvcurve version, hostname, GPU name, applied offset summary,
active profile, and a collapsed tail of the recent server log.

Security model:
- The Gitea token (nvcurve_bot, scope write:issue) is embedded as a
  built-in default so the feature works out of the box for every
  install of this public repo — no configuration required.
- The token is only ever used server-side; it never reaches the
  browser, logs, or API responses.
- The endpoint is behind the existing session auth when enabled, and
  rate-limited to one report per 5 minutes per client IP.
- config.json can override gitea_url/gitea_repo/gitea_token (e.g.
  forks pointing at their own repo); gitea_token: '' disables it.
This commit is contained in:
ARIA committed 2026-10-03 21:06:50 +02:00
1 parent e89135ef94
commit 1bafbc80b5
6 files changed
+331 -2

No files matched your search

+26 -1
View File
@@ -7,10 +7,13 @@ import {
ChevronDown,
LogOut,
User,
Bug,
} from "lucide-react";
import type { GpuInfo, MonitoringSample } from "../../types.js";
import { fmt } from "../../utils/units.js";
import { useCurveStore } from "../../store/curveStore.js";
import { api } from "../../api/client.js";
import { ReportBugDialog } from "../common/ReportBugDialog.js";
import { useState, useRef, useEffect } from "react";
interface Props {
@@ -43,8 +46,17 @@ export function StatusBar({
const { availableGpus, selectedGpuIndex, setSelectedGpuIndex } =
useCurveStore();
const [isOpen, setIsOpen] = useState(false);
const [bugReportConfigured, setBugReportConfigured] = useState(false);
const [reportBugOpen, setReportBugOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
useEffect(() => {
api
.reportBugStatus()
.then((s) => setBugReportConfigured(s.configured))
.catch(() => {});
}, []);
useEffect(() => {
function handleClickOutside(event: MouseEvent) {
if (
@@ -169,8 +181,18 @@ export function StatusBar({
/>
</div>
{/* Right: user + connection status */}
{/* Right: report bug + user + connection status */}
<div className="flex items-center gap-3 shrink-0">
{bugReportConfigured && (
<button
onClick={() => setReportBugOpen(true)}
title="Report a bug (creates a Gitea issue)"
className="flex items-center gap-1.5 px-2 py-1 rounded hover:bg-zinc-800 text-zinc-400 hover:text-zinc-200 transition-colors"
>
<Bug size={14} />
<span className="hidden sm:inline text-xs">Report Bug</span>
</button>
)}
{user && (
<div className="flex items-center gap-1.5 text-zinc-400">
<User size={14} className="text-zinc-500" />
@@ -192,6 +214,9 @@ export function StatusBar({
</div>
</div>
</div>
{reportBugOpen && (
<ReportBugDialog onClose={() => setReportBugOpen(false)} />
)}
</header>
);
}