implemented watcher and rerun logic
This commit is contained in:
@@ -121,8 +121,7 @@ export default function Calendar({ scheduleData = {}, daytimeDateKey = null, wee
|
||||
<div className="flex gap-4 text-xs text-gray-400">
|
||||
<span className="flex items-center gap-1.5"><span className="w-2.5 h-2.5 rounded-sm bg-gray-500 inline-block" />Pending</span>
|
||||
<span className="flex items-center gap-1.5"><span className="w-2.5 h-2.5 rounded-sm bg-green-700 inline-block" />Completed</span>
|
||||
<span className="flex items-center gap-1.5"><span className="w-2.5 h-2.5 rounded-sm bg-red-700 inline-block" />Failed</span>
|
||||
<span className="flex items-center gap-1.5"><span className="w-2.5 h-2.5 rounded-sm bg-yellow-700 inline-block" />Invalid</span>
|
||||
<span className="flex items-center gap-1.5"><span className="w-2.5 h-2.5 rounded-sm bg-red-700 inline-block" />Rerun Required</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
export default function FailedBanner({ failedTests, estimatedMinutes, onDecision }) {
|
||||
if (!failedTests || failedTests.length === 0) return null
|
||||
export default function FailedBanner({ rerunTests, onDecision }) {
|
||||
if (!rerunTests || rerunTests.length === 0) return null
|
||||
|
||||
const hours = Math.floor(estimatedMinutes / 60)
|
||||
const mins = estimatedMinutes % 60
|
||||
const totalMinutes = rerunTests.reduce((sum, t) => sum + (t.estimated_minutes ?? 0), 0)
|
||||
const hours = Math.floor(totalMinutes / 60)
|
||||
const mins = totalMinutes % 60
|
||||
const timeStr = hours > 0 ? `${hours}h ${mins}m` : `${mins}m`
|
||||
const testIds = rerunTests.map(t => t.test_id).join(', ')
|
||||
|
||||
return (
|
||||
<div className="flex items-start gap-4 px-6 py-3 bg-red-900/70 border-b border-red-700 text-red-100">
|
||||
@@ -23,13 +25,13 @@ export default function FailedBanner({ failedTests, estimatedMinutes, onDecision
|
||||
</svg>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium">
|
||||
<span className="font-semibold">{failedTests.length} test{failedTests.length !== 1 ? 's' : ''}</span>
|
||||
{' '}failed last night and require a rerun —{' '}
|
||||
<span className="font-mono text-red-200">{failedTests.join(', ')}</span>
|
||||
<span className="font-semibold">{rerunTests.length} test{rerunTests.length !== 1 ? 's' : ''}</span>
|
||||
{' '}require a rerun — not completed in last overnight window:{' '}
|
||||
<span className="font-mono text-red-200">{testIds}</span>
|
||||
</p>
|
||||
<p className="text-sm text-red-300 mt-0.5">
|
||||
Estimated rerun time: <span className="font-semibold text-red-100">{timeStr}</span>
|
||||
{' '}— Rerun these tests during the day today?
|
||||
{' '}— Schedule rerun during daytime today?
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-2 shrink-0">
|
||||
|
||||
@@ -14,6 +14,8 @@ function formatCompletionDate(value) {
|
||||
|
||||
export default function RightPanel({
|
||||
completionDate,
|
||||
startDateOverride,
|
||||
onStartDateOverrideChange,
|
||||
daytimeEnabled,
|
||||
onDaytimeEnabledChange,
|
||||
topPriority,
|
||||
@@ -75,6 +77,18 @@ export default function RightPanel({
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-[11px] font-semibold uppercase tracking-wider text-gray-500 mb-1.5">
|
||||
Start Date Override
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
value={startDateOverride}
|
||||
onChange={(e) => onStartDateOverrideChange(e.target.value)}
|
||||
className="w-full bg-gray-900 border border-gray-600 rounded-md px-2.5 py-1.5 text-xs text-gray-200 focus:outline-none focus:border-blue-500 font-mono"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Remake schedule */}
|
||||
<button
|
||||
onClick={onRemakeSchedule}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
function Field({ label, hint, children }) {
|
||||
function Field({ label, hint, required = false, children }) {
|
||||
return (
|
||||
<div>
|
||||
<label className="block text-xs font-semibold text-gray-300 mb-1">
|
||||
{label}
|
||||
{required && <span className="ml-1 text-red-400">*</span>}
|
||||
{hint && <span className="ml-1.5 text-gray-500 font-normal">{hint}</span>}
|
||||
</label>
|
||||
{children}
|
||||
@@ -15,8 +16,6 @@ function Field({ label, hint, children }) {
|
||||
const INPUT_CLS =
|
||||
'w-full bg-gray-900 border border-gray-600 rounded-md px-3 py-1.5 text-sm text-gray-200 placeholder-gray-600 focus:outline-none focus:border-blue-500 font-mono'
|
||||
|
||||
const TEXTAREA_CLS = INPUT_CLS + ' resize-none'
|
||||
|
||||
export default function SettingsModal({ isOpen, onClose, settings, onSave }) {
|
||||
const [form, setForm] = useState({ ...settings })
|
||||
|
||||
@@ -69,7 +68,7 @@ export default function SettingsModal({ isOpen, onClose, settings, onSave }) {
|
||||
File Paths
|
||||
</p>
|
||||
<div className="flex flex-col gap-3">
|
||||
<Field label="P2P / COE CSV">
|
||||
<Field label="P2P / COE CSV" required>
|
||||
<input
|
||||
type="text"
|
||||
className={INPUT_CLS}
|
||||
@@ -78,7 +77,7 @@ export default function SettingsModal({ isOpen, onClose, settings, onSave }) {
|
||||
onChange={(e) => set('p2pCoeCsvPath', e.target.value)}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="P3P CSV">
|
||||
<Field label="P3P CSV" required>
|
||||
<input
|
||||
type="text"
|
||||
className={INPUT_CLS}
|
||||
@@ -87,7 +86,7 @@ export default function SettingsModal({ isOpen, onClose, settings, onSave }) {
|
||||
onChange={(e) => set('p3pCsvPath', e.target.value)}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="REF Result Directory">
|
||||
<Field label="REF Result Directory" required>
|
||||
<input
|
||||
type="text"
|
||||
className={INPUT_CLS}
|
||||
@@ -96,7 +95,7 @@ export default function SettingsModal({ isOpen, onClose, settings, onSave }) {
|
||||
onChange={(e) => set('refResultDir', e.target.value)}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="DUT Result Directory">
|
||||
<Field label="DUT Result Directory" required>
|
||||
<input
|
||||
type="text"
|
||||
className={INPUT_CLS}
|
||||
@@ -110,6 +109,88 @@ export default function SettingsModal({ isOpen, onClose, settings, onSave }) {
|
||||
|
||||
<hr className="border-gray-700" />
|
||||
|
||||
{/* Section: Network Shares */}
|
||||
<div>
|
||||
<p className="text-[11px] font-bold uppercase tracking-widest text-gray-500 mb-3">
|
||||
Network Shares
|
||||
</p>
|
||||
<div className="flex flex-col gap-3">
|
||||
<Field label="SMB Username">
|
||||
<input
|
||||
type="text"
|
||||
className={INPUT_CLS}
|
||||
placeholder="username"
|
||||
value={form.smbUsername ?? ''}
|
||||
onChange={(e) => set('smbUsername', e.target.value)}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="SMB Password">
|
||||
<input
|
||||
type="password"
|
||||
className={INPUT_CLS}
|
||||
placeholder="password"
|
||||
value={form.smbPassword ?? ''}
|
||||
onChange={(e) => set('smbPassword', e.target.value)}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="SMB Domain">
|
||||
<input
|
||||
type="text"
|
||||
className={INPUT_CLS}
|
||||
placeholder="WORKGROUP or DOMAIN"
|
||||
value={form.smbDomain ?? ''}
|
||||
onChange={(e) => set('smbDomain', e.target.value)}
|
||||
/>
|
||||
</Field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr className="border-gray-700" />
|
||||
|
||||
{/* Section: Runtime Overrides */}
|
||||
<div>
|
||||
<p className="text-[11px] font-bold uppercase tracking-widest text-gray-500 mb-3">
|
||||
Runtime Overrides
|
||||
</p>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
|
||||
<Field label="P2P Minutes">
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
step="1"
|
||||
className={INPUT_CLS}
|
||||
placeholder="80"
|
||||
value={form.p2pRuntimeMinutes ?? ''}
|
||||
onChange={(e) => set('p2pRuntimeMinutes', e.target.value)}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="COE Minutes">
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
step="1"
|
||||
className={INPUT_CLS}
|
||||
placeholder="115"
|
||||
value={form.coeRuntimeMinutes ?? ''}
|
||||
onChange={(e) => set('coeRuntimeMinutes', e.target.value)}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="P3P Minutes">
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
step="1"
|
||||
className={INPUT_CLS}
|
||||
placeholder="105"
|
||||
value={form.p3pRuntimeMinutes ?? ''}
|
||||
onChange={(e) => set('p3pRuntimeMinutes', e.target.value)}
|
||||
/>
|
||||
</Field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr className="border-gray-700" />
|
||||
|
||||
{/* Section: Test Exclusion */}
|
||||
<div>
|
||||
<p className="text-[11px] font-bold uppercase tracking-widest text-gray-500 mb-3">
|
||||
@@ -143,23 +224,6 @@ export default function SettingsModal({ isOpen, onClose, settings, onSave }) {
|
||||
/>
|
||||
</Field>
|
||||
</div>
|
||||
|
||||
<hr className="border-gray-700" />
|
||||
|
||||
{/* Section: Schedule */}
|
||||
<div>
|
||||
<p className="text-[11px] font-bold uppercase tracking-widest text-gray-500 mb-3">
|
||||
Schedule
|
||||
</p>
|
||||
<Field label="Start Date Override" hint="(YYYY-MM-DD, leave blank for today)">
|
||||
<input
|
||||
type="date"
|
||||
className={INPUT_CLS}
|
||||
value={form.startDateOverride ?? ''}
|
||||
onChange={(e) => set('startDateOverride', e.target.value)}
|
||||
/>
|
||||
</Field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
const STATUS_STYLES = {
|
||||
pending: 'bg-gray-600/60 text-gray-200 border-gray-500',
|
||||
completed: 'bg-green-800/60 text-green-200 border-green-600',
|
||||
failed: 'bg-red-800/60 text-red-200 border-red-600',
|
||||
invalid: 'bg-yellow-800/60 text-yellow-200 border-yellow-600',
|
||||
rerun: 'bg-red-800/60 text-red-200 border-red-600',
|
||||
}
|
||||
|
||||
function formatConfig(config) {
|
||||
|
||||
Reference in New Issue
Block a user