feat(errors): per-account error modal with persisted error list
Accounts finishing done_with_errors showed only a count and a single
last_error. This adds a modal listing every concrete error of the
account's most recent run.
- migration 0005: account_errors table (kind folder|message|account,
folder, message_ref, error, created_at; ON DELETE CASCADE; indexed)
- store: AddAccountError / ClearAccountErrors / ListAccountErrors
- copy: OnError callback captures per-message error text (previously
only counted), with a "UID N: subject" reference
- orchestrator: clear errors at run start; persist folder/message/
account errors; cap 500 rows/account/run with a suppressed-note row
- api: GET /api/tasks/{id}/accounts/{accountId}/errors
- web: AccountErrorsModal, clickable ERRORS count, api + styles
Verified: migration applies on Postgres 18; store add/list/clear and
cascade tests pass against real pg; backend build/vet/test green; web
tsc+vite build and oxlint clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U9Eq4JtWjyNTv5qat3B3mM
This commit is contained in:
@@ -5,6 +5,7 @@ import { StatusBadge } from '../components/StatusBadge'
|
||||
import { useConfirm } from '../components/ConfirmProvider'
|
||||
import { FolderMappingModal } from '../components/FolderMappingModal'
|
||||
import { RunLogModal } from '../components/RunLogModal'
|
||||
import { AccountErrorsModal } from '../components/AccountErrorsModal'
|
||||
|
||||
const emptyAccount = { src_login: '', src_pass: '', dst_login: '', dst_pass: '' }
|
||||
|
||||
@@ -87,6 +88,7 @@ export function TaskDetail({ id }: { id: number }) {
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [live, setLive] = useState<Record<number, LiveProgress>>({})
|
||||
const [showRuns, setShowRuns] = useState(false)
|
||||
const [errorsFor, setErrorsFor] = useState<{ id: number; src_login: string } | null>(null)
|
||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
function reload() {
|
||||
@@ -609,7 +611,19 @@ export function TaskDetail({ id }: { id: number }) {
|
||||
</td>
|
||||
<td className="num-cell">{live[a.id]?.copied ?? a.copied}</td>
|
||||
<td className="num-cell">{live[a.id]?.skipped ?? a.skipped}</td>
|
||||
<td className="num-cell">{a.errors}</td>
|
||||
<td className="num-cell">
|
||||
{a.errors > 0 ? (
|
||||
<button
|
||||
type="button"
|
||||
className="link-btn danger"
|
||||
onClick={() => setErrorsFor({ id: a.id, src_login: a.src_login })}
|
||||
>
|
||||
{a.errors}
|
||||
</button>
|
||||
) : (
|
||||
a.errors
|
||||
)}
|
||||
</td>
|
||||
<td className="num-cell">
|
||||
<div className="row-actions">
|
||||
{a.status !== 'running' && data?.task.status !== 'running' && (
|
||||
@@ -671,6 +685,7 @@ export function TaskDetail({ id }: { id: number }) {
|
||||
/>
|
||||
)}
|
||||
<RunLogModal taskId={id} open={showRuns} onClose={() => setShowRuns(false)} />
|
||||
<AccountErrorsModal taskId={id} account={errorsFor} onClose={() => setErrorsFor(null)} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user