Add account selection checkboxes and selective run button
This commit is contained in:
@@ -90,6 +90,7 @@ export function TaskDetail({ id }: { id: number }) {
|
||||
const [live, setLive] = useState<Record<number, LiveProgress>>({})
|
||||
const [showRuns, setShowRuns] = useState(false)
|
||||
const [errorsFor, setErrorsFor] = useState<{ id: number; src_login: string } | null>(null)
|
||||
const [selected, setSelected] = useState<Set<number>>(new Set())
|
||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
function reload() {
|
||||
@@ -332,7 +333,8 @@ export function TaskDetail({ id }: { id: number }) {
|
||||
setBusy('run')
|
||||
setError(null)
|
||||
try {
|
||||
await runTask(id)
|
||||
const ids = accounts.filter((a) => selected.has(a.id)).map((a) => a.id)
|
||||
await runTask(id, ids.length ? ids : undefined)
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to start run')
|
||||
} finally {
|
||||
@@ -366,7 +368,33 @@ export function TaskDetail({ id }: { id: number }) {
|
||||
}
|
||||
|
||||
const { task, accounts } = data
|
||||
const allTested = accounts.length > 0 && accounts.every((a) => a.test_src_status === 'ok' && a.test_dst_status === 'ok')
|
||||
const isRunning = task.status === 'running'
|
||||
// A row is selectable only when both connection tests pass and no run is live.
|
||||
const selectableIds = accounts
|
||||
.filter((a) => a.test_src_status === 'ok' && a.test_dst_status === 'ok')
|
||||
.map((a) => a.id)
|
||||
const selectableSet = new Set(selectableIds)
|
||||
// Effective set: the checked accounts, or all accounts when nothing is checked.
|
||||
const effectiveSelected = accounts.filter((a) => selected.has(a.id))
|
||||
const runSet = effectiveSelected.length > 0 ? effectiveSelected : accounts
|
||||
const runReady =
|
||||
runSet.length > 0 && runSet.every((a) => a.test_src_status === 'ok' && a.test_dst_status === 'ok')
|
||||
const allSelectableChecked =
|
||||
selectableIds.length > 0 && selectableIds.every((id) => selected.has(id))
|
||||
const someSelectableChecked = selectableIds.some((id) => selected.has(id))
|
||||
|
||||
function toggleOne(accId: number, checked: boolean) {
|
||||
setSelected((prev) => {
|
||||
const next = new Set(prev)
|
||||
if (checked) next.add(accId)
|
||||
else next.delete(accId)
|
||||
return next
|
||||
})
|
||||
}
|
||||
|
||||
function toggleAll(checked: boolean) {
|
||||
setSelected(checked ? new Set(selectableIds) : new Set())
|
||||
}
|
||||
// Prefer live (WS) copied/skipped over the DB values, which only advance per
|
||||
// folder — so the summary moves in real time during a large folder.
|
||||
const totals = accounts.reduce(
|
||||
@@ -418,10 +446,20 @@ export function TaskDetail({ id }: { id: number }) {
|
||||
<button className="btn" onClick={onTest} disabled={busy !== null || accounts.length === 0}>
|
||||
{busy === 'test' ? 'Testing…' : 'Test connections'}
|
||||
</button>
|
||||
<button className="btn btn-primary" onClick={onRun} disabled={busy !== null || !allTested || task.status === 'running'}>
|
||||
{busy === 'run' ? 'Starting…' : 'Run migration'}
|
||||
<button className="btn btn-primary" onClick={onRun} disabled={busy !== null || !runReady || isRunning}>
|
||||
{busy === 'run'
|
||||
? 'Starting…'
|
||||
: effectiveSelected.length > 0
|
||||
? `Run selected (${effectiveSelected.length})`
|
||||
: 'Run migration'}
|
||||
</button>
|
||||
{!allTested && accounts.length > 0 && <span className="hint">run unlocks once every account tests OK on both sides</span>}
|
||||
{!runReady && accounts.length > 0 && (
|
||||
<span className="hint">
|
||||
{effectiveSelected.length > 0
|
||||
? 'selected accounts must pass both connection tests'
|
||||
: 'run unlocks once every account tests OK on both sides'}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="sched-row">
|
||||
<label htmlFor="sched">Schedule</label>
|
||||
@@ -540,6 +578,18 @@ export function TaskDetail({ id }: { id: number }) {
|
||||
<table className="tbl">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="chk-col">
|
||||
<input
|
||||
type="checkbox"
|
||||
aria-label="Select all accounts"
|
||||
checked={allSelectableChecked}
|
||||
ref={(el) => {
|
||||
if (el) el.indeterminate = !allSelectableChecked && someSelectableChecked
|
||||
}}
|
||||
disabled={isRunning || selectableIds.length === 0}
|
||||
onChange={(e) => toggleAll(e.target.checked)}
|
||||
/>
|
||||
</th>
|
||||
<th>Account</th>
|
||||
<th>Src test</th>
|
||||
<th>Dst test</th>
|
||||
@@ -554,11 +604,20 @@ export function TaskDetail({ id }: { id: number }) {
|
||||
<tbody>
|
||||
{accounts.length === 0 ? (
|
||||
<tr className="empty-row">
|
||||
<td colSpan={9}>no accounts yet — add one or import a CSV above</td>
|
||||
<td colSpan={10}>no accounts yet — add one or import a CSV above</td>
|
||||
</tr>
|
||||
) : (
|
||||
accounts.map((a) => (
|
||||
<tr key={a.id}>
|
||||
<td className="chk-col">
|
||||
<input
|
||||
type="checkbox"
|
||||
aria-label={`Select ${a.src_login}`}
|
||||
checked={selected.has(a.id)}
|
||||
disabled={isRunning || !selectableSet.has(a.id)}
|
||||
onChange={(e) => toggleOne(a.id, e.target.checked)}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<div className="acct-ident">
|
||||
<span>{a.src_login}</span>
|
||||
|
||||
Reference in New Issue
Block a user