Remove idle connection timeout handling

Remove error handling for closed connections

Add progress watchdog to detect stalled accounts

Improve error modal styling and pagination
This commit is contained in:
2026-07-05 14:50:25 +07:00
parent 95ddbf5619
commit e84366eb0c
7 changed files with 191 additions and 145 deletions
+66 -5
View File
@@ -327,14 +327,75 @@
white-space: nowrap;
}
/* error text inside the per-account errors modal: wrap long messages */
.err-cell {
max-width: 420px;
/* per-account errors modal: a paginated list so long messages wrap on the
full modal width instead of squeezing into narrow table columns. */
.err-empty {
padding: 24px 8px;
text-align: center;
color: var(--fg-dim);
}
.err-list {
list-style: none;
margin: 0;
padding: 0;
max-height: 60vh;
overflow-y: auto;
}
.err-item {
padding: 10px 4px;
border-bottom: 1px solid var(--border);
}
.err-item-head {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 8px;
font-size: 11px;
letter-spacing: 0.04em;
margin-bottom: 4px;
}
.err-kind {
color: var(--accent);
text-transform: uppercase;
}
.err-folder {
color: var(--fg);
}
.err-ref {
color: var(--fg-dim);
word-break: break-word;
}
.err-time {
margin-left: auto;
color: var(--fg-dim);
}
.err-text {
font-size: 12px;
line-height: 1.45;
color: var(--fail);
white-space: normal;
word-break: break-word;
color: var(--fail);
}
.err-pager {
display: flex;
align-items: center;
justify-content: center;
gap: 14px;
padding-top: 12px;
}
.err-pageinfo {
font-size: 12px;
line-height: 1.4;
color: var(--fg-dim);
}
/* clear-log button: mirrors the .panel-label tab on the right edge */
+59 -33
View File
@@ -3,6 +3,7 @@ import { Modal } from './Modal'
import { listAccountErrors, type AccountError } from '../api'
const fmt = (iso: string) => (iso ? new Date(iso).toLocaleString() : '—')
const PAGE_SIZE = 50
export function AccountErrorsModal({
taskId,
@@ -15,51 +16,76 @@ export function AccountErrorsModal({
}) {
const [errors, setErrors] = useState<AccountError[] | null>(null)
const [failed, setFailed] = useState(false)
const [page, setPage] = useState(0)
const open = account !== null
useEffect(() => {
if (!account) return
setErrors(null)
setFailed(false)
setPage(0)
listAccountErrors(taskId, account.id)
.then((e) => setErrors(e ?? []))
.catch(() => setFailed(true))
}, [taskId, account])
const total = errors?.length ?? 0
const pageCount = Math.max(1, Math.ceil(total / PAGE_SIZE))
const current = Math.min(page, pageCount - 1)
const slice = errors?.slice(current * PAGE_SIZE, current * PAGE_SIZE + PAGE_SIZE) ?? []
const title = account
? `Errors — ${account.src_login}${total ? ` (${total})` : ''}`
: 'Errors'
return (
<Modal open={open} title={account ? `Errors — ${account.src_login}` : 'Errors'} onClose={onClose} size="lg">
<div className="tbl-wrap">
<table className="tbl">
<thead>
<tr>
<th>Time</th>
<th>Kind</th>
<th>Folder</th>
<th>Message</th>
<th>Error</th>
</tr>
</thead>
<tbody>
{failed ? (
<tr className="empty-row"><td colSpan={5}>failed to load errors</td></tr>
) : errors === null ? (
<tr className="empty-row"><td colSpan={5}>loading</td></tr>
) : errors.length === 0 ? (
<tr className="empty-row"><td colSpan={5}>no errors recorded</td></tr>
) : (
errors.map((e) => (
<tr key={e.id}>
<td>{fmt(e.created_at)}</td>
<td>{e.kind}</td>
<td>{e.folder || '—'}</td>
<td>{e.message_ref || '—'}</td>
<td className="err-cell">{e.error}</td>
</tr>
))
)}
</tbody>
</table>
</div>
<Modal open={open} title={title} onClose={onClose} size="lg">
{failed ? (
<div className="err-empty">failed to load errors</div>
) : errors === null ? (
<div className="err-empty">loading</div>
) : total === 0 ? (
<div className="err-empty">no errors recorded</div>
) : (
<>
<ul className="err-list">
{slice.map((e) => (
<li key={e.id} className="err-item">
<div className="err-item-head">
<span className="err-kind">{e.kind}</span>
{e.folder && <span className="err-folder">{e.folder}</span>}
{e.message_ref && <span className="err-ref">{e.message_ref}</span>}
<span className="err-time">{fmt(e.created_at)}</span>
</div>
<div className="err-text">{e.error}</div>
</li>
))}
</ul>
{pageCount > 1 && (
<div className="err-pager">
<button
type="button"
className="btn"
disabled={current === 0}
onClick={() => setPage(current - 1)}
>
prev
</button>
<span className="err-pageinfo">
page {current + 1} / {pageCount}
</span>
<button
type="button"
className="btn"
disabled={current >= pageCount - 1}
onClick={() => setPage(current + 1)}
>
next
</button>
</div>
)}
</>
)}
</Modal>
)
}