fix: allow network paths

This commit is contained in:
2026-05-27 15:02:32 -04:00
parent 5996443f38
commit ba86f5830f
9 changed files with 342 additions and 196 deletions
+113 -17
View File
@@ -1,6 +1,7 @@
import { useState, useEffect } from 'react'
import DirectoryBrowser from './DirectoryBrowser'
import { useConfig, useSaveConfig } from '../hooks/useConfig'
import { apiFetch } from '../lib/api'
function fmtSeconds(s) {
if (s == null) return ''
@@ -16,15 +17,21 @@ export default function ConfigModal({ onClose }) {
const [browser, setBrowser] = useState(null) // 'target_dir' | 'results_dir' | null
const [scanResult, setScanResult] = useState(null) // { testCount, completedCount } | null
const [saveError, setSaveError] = useState(null)
const [isRescanning, setIsRescanning] = useState(false)
const [isSavingTimes, setIsSavingTimes] = useState(false)
useEffect(() => {
if (config) {
setForm({
target_dir: config.target_dir ?? '',
results_dir: config.results_dir ?? '',
target_dir: config.target_dir ?? '',
results_dir: config.results_dir ?? '',
results_dir_ref: config.results_dir_ref ?? '',
avg_time_coe: config.avg_time_coe ? fmtSeconds(config.avg_time_coe) : '',
avg_time_p2p: config.avg_time_p2p ? fmtSeconds(config.avg_time_p2p) : '',
avg_time_p3p: config.avg_time_p3p ? fmtSeconds(config.avg_time_p3p) : '',
smb_username: config.smb_username ?? '',
smb_password: config.smb_password ?? '',
smb_domain: config.smb_domain ?? '',
})
}
}, [config])
@@ -33,12 +40,16 @@ export default function ConfigModal({ onClose }) {
setSaveError(null)
setScanResult(null)
const payload = {
target_dir: form.target_dir || null,
results_dir: form.results_dir || null,
target_dir: form.target_dir || null,
results_dir: form.results_dir || null,
results_dir_ref: form.results_dir_ref || null,
// Convert minutes → seconds; empty/null clears the override
avg_time_coe: form.avg_time_coe ? String(parseFloat(form.avg_time_coe) * 60) : null,
avg_time_p2p: form.avg_time_p2p ? String(parseFloat(form.avg_time_p2p) * 60) : null,
avg_time_p3p: form.avg_time_p3p ? String(parseFloat(form.avg_time_p3p) * 60) : null,
smb_username: form.smb_username || null,
smb_password: form.smb_password || null,
smb_domain: form.smb_domain || null,
}
save(payload, {
onSuccess: (data) => {
@@ -56,6 +67,43 @@ export default function ConfigModal({ onClose }) {
})
}
async function handleSaveTimes() {
setSaveError(null)
setScanResult(null)
setIsSavingTimes(true)
try {
await apiFetch('/config', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
avg_time_coe: form.avg_time_coe ? String(parseFloat(form.avg_time_coe) * 60) : null,
avg_time_p2p: form.avg_time_p2p ? String(parseFloat(form.avg_time_p2p) * 60) : null,
avg_time_p3p: form.avg_time_p3p ? String(parseFloat(form.avg_time_p3p) * 60) : null,
}),
})
} catch (err) {
setSaveError(err?.message ?? 'Failed to save')
} finally {
setIsSavingTimes(false)
}
}
async function handleRescanResults() {
setSaveError(null)
setScanResult(null)
setIsRescanning(true)
try {
const data = await apiFetch('/config/rescan-results', { method: 'POST' })
if (data?.testCount !== null && data?.testCount !== undefined) {
setScanResult({ testCount: data.testCount, completedCount: data.completedCount })
}
} catch (err) {
setSaveError(err?.message ?? 'Rescan failed')
} finally {
setIsRescanning(false)
}
}
const AVG_FIELDS = [
{ key: 'avg_time_coe', label: 'COE avg time (min)' },
{ key: 'avg_time_p2p', label: 'P2P avg time (min)' },
@@ -104,8 +152,9 @@ export default function ConfigModal({ onClose }) {
</div>
<div className="space-y-3">
{[
{ key: 'target_dir', label: 'Target Tests Directory' },
{ key: 'results_dir', label: 'Results Directory' },
{ key: 'target_dir', label: 'Target Tests Directory' },
{ key: 'results_dir', label: 'Results Directory (DUT)' },
{ key: 'results_dir_ref', label: 'Results Directory (Reference)' },
].map(({ key, label }) => (
<div key={key}>
<label className="text-slate-400 text-xs block mb-1">{label}</label>
@@ -152,25 +201,72 @@ export default function ConfigModal({ onClose }) {
))}
</div>
</section>
{/* SMB Credentials */}
<section>
<h3 className="text-slate-300 text-xs uppercase tracking-widest mb-1">SMB Credentials</h3>
<p className="text-slate-500 text-xs mb-3">
Optional credentials for network shares used by target/results directories.
</p>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
<input
type="text"
value={form.smb_domain ?? ''}
onChange={e => setForm(f => ({ ...f, smb_domain: e.target.value }))}
placeholder="Domain"
className="w-full bg-slate-800 border border-slate-700 text-slate-200 text-sm rounded-lg px-3 py-2 focus:outline-none focus:border-blue-500"
/>
<input
type="text"
value={form.smb_username ?? ''}
onChange={e => setForm(f => ({ ...f, smb_username: e.target.value }))}
placeholder="Username"
className="w-full bg-slate-800 border border-slate-700 text-slate-200 text-sm rounded-lg px-3 py-2 focus:outline-none focus:border-blue-500"
/>
<input
type="password"
value={form.smb_password ?? ''}
onChange={e => setForm(f => ({ ...f, smb_password: e.target.value }))}
placeholder="Password"
className="w-full bg-slate-800 border border-slate-700 text-slate-200 text-sm rounded-lg px-3 py-2 focus:outline-none focus:border-blue-500"
/>
</div>
</section>
</>
)}
</div>
{/* Footer */}
<div className="flex justify-end gap-2 px-5 py-4 border-t border-slate-800">
<div className="flex justify-between gap-2 px-5 py-4 border-t border-slate-800">
<button
onClick={onClose}
className="px-4 py-2 text-sm rounded-lg border border-slate-700 text-slate-300 hover:bg-slate-800 transition-colors"
onClick={handleRescanResults}
disabled={isRescanning || isPending || isSavingTimes}
className="px-4 py-2 text-sm rounded-lg border border-slate-700 text-slate-300 hover:bg-slate-800 disabled:opacity-50 transition-colors"
>
Cancel
</button>
<button
onClick={handleSave}
disabled={isPending}
className="px-4 py-2 text-sm rounded-lg bg-blue-600 text-white hover:bg-blue-700 disabled:opacity-50 transition-colors"
>
{isPending ? 'Saving…' : 'Save & Rescan'}
{isRescanning ? 'Rescanning…' : 'Save Results'}
</button>
<div className="flex gap-2">
<button
onClick={handleSaveTimes}
disabled={isSavingTimes || isPending || isRescanning}
className="px-4 py-2 text-sm rounded-lg border border-slate-700 text-slate-300 hover:bg-slate-800 disabled:opacity-50 transition-colors"
>
{isSavingTimes ? 'Saving…' : 'Save Times'}
</button>
<button
onClick={onClose}
className="px-4 py-2 text-sm rounded-lg border border-slate-700 text-slate-300 hover:bg-slate-800 transition-colors"
>
Cancel
</button>
<button
onClick={handleSave}
disabled={isPending || isRescanning || isSavingTimes}
className="px-4 py-2 text-sm rounded-lg bg-blue-600 text-white hover:bg-blue-700 disabled:opacity-50 transition-colors"
>
{isPending ? 'Saving…' : 'Save All'}
</button>
</div>
</div>
</div>
</div>