From 517f4ab58f437909869ae6e43efa0d90babeac2a Mon Sep 17 00:00:00 2001 From: Emma Broman Date: Fri, 10 Apr 2026 16:24:38 +0200 Subject: [PATCH 1/5] Disable the upgrade candidate button after it has been clicked --- .../frontend/src/components/TestHistory.tsx | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/backend/frontend/src/components/TestHistory.tsx b/backend/frontend/src/components/TestHistory.tsx index f0cf41e..ebf5d58 100644 --- a/backend/frontend/src/components/TestHistory.tsx +++ b/backend/frontend/src/components/TestHistory.tsx @@ -4,6 +4,7 @@ import { TestData, TestRecord } from '../types'; import { diffDisplay, diffStyle, timingDisplay } from '../utils'; import { ImageThumbnail } from './ImageThumbnail'; +import { useState } from 'react'; interface Props { record: TestRecord; @@ -11,6 +12,8 @@ interface Props { } export function TestHistory({ record, onUpdateReference }: Props) { + const [wasUpdated, setWasUpdated] = useState(false); + const testData = [...record.data].reverse(); const ImageWidth = 250; @@ -111,9 +114,18 @@ export function TestHistory({ record, onUpdateReference }: Props) { {i === 0 && ( - + <> + + )} From e6fce8312c5c453a8c5433acb87733e01d5e7803 Mon Sep 17 00:00:00 2001 From: Emma Broman Date: Fri, 10 Apr 2026 16:25:09 +0200 Subject: [PATCH 2/5] Add filtering for group and name column --- .../src/components/SortableHeader.tsx | 21 ++++++--- backend/frontend/src/pages/Home.tsx | 44 +++++++++++++++++-- 2 files changed, 54 insertions(+), 11 deletions(-) diff --git a/backend/frontend/src/components/SortableHeader.tsx b/backend/frontend/src/components/SortableHeader.tsx index d3d5903..ba608dd 100644 --- a/backend/frontend/src/components/SortableHeader.tsx +++ b/backend/frontend/src/components/SortableHeader.tsx @@ -1,4 +1,4 @@ -import { Table } from '@mantine/core'; +import { Stack, Table, UnstyledButton } from '@mantine/core'; import { SortColumn, SortDirection } from '../types'; @@ -6,6 +6,7 @@ interface Props { sortKey: SortColumn; label: string; onSort: (col: SortColumn) => void; + filter?: React.ReactNode; activeColumn?: SortColumn; direction?: SortDirection; } @@ -14,18 +15,24 @@ export function SortableHeader({ sortKey, label, onSort, + filter, activeColumn, direction }: Props) { const isActive = activeColumn === sortKey; const indicator = isActive ? (direction === 'asc' ? ' ↑' : ' ↓') : ''; return ( - onSort(sortKey)} - > - {label} - {indicator} + + + onSort(sortKey)} + > + {label} + {indicator} + + {filter} + ); } diff --git a/backend/frontend/src/pages/Home.tsx b/backend/frontend/src/pages/Home.tsx index 0cf8e75..39d6702 100644 --- a/backend/frontend/src/pages/Home.tsx +++ b/backend/frontend/src/pages/Home.tsx @@ -1,6 +1,7 @@ import { useEffect, useMemo, useState } from 'react'; import { Link } from 'react-router-dom'; import { + ActionIcon, Anchor, Box, Checkbox, @@ -11,6 +12,7 @@ import { Select, Table, Text, + TextInput, Title } from '@mantine/core'; @@ -29,6 +31,9 @@ export default function Home() { const [sortCol, setSortCol] = useState('pixelError'); const [sortDir, setSortDir] = useState('desc'); + const [groupFilter, setGroupFilter] = useState(''); + const [nameFilter, setNameFilter] = useState(''); + useEffect(() => { fetch('/api/test-records') .then((res) => { @@ -92,10 +97,15 @@ export default function Home() { const visibleRecords = useMemo( () => - sortRecords(records, sortCol, sortDir).filter((r) => - selectedHardware.has(r.hardware) - ), - [records, sortCol, sortDir, selectedHardware] + sortRecords(records, sortCol, sortDir).filter((r) => { + if (!selectedHardware.has(r.hardware)) return false; + if (groupFilter && !r.group.toLowerCase().includes(groupFilter.toLowerCase())) + return false; + if (nameFilter && !r.name.toLowerCase().includes(nameFilter.toLowerCase())) + return false; + return true; + }), + [records, sortCol, sortDir, selectedHardware, groupFilter, nameFilter] ); return ( @@ -177,6 +187,19 @@ export default function Home() { onSort={handleSort} activeColumn={sortCol} direction={sortDir} + filter={ + setGroupFilter(e.target.value)} + rightSection={ + groupFilter && ( + setGroupFilter('')} size={'xs'}> + × + + ) + } + /> + } /> setNameFilter(e.target.value)} + rightSection={ + nameFilter && ( + setNameFilter('')} size={'xs'}> + × + + ) + } + /> + } /> Date: Fri, 10 Apr 2026 16:25:18 +0200 Subject: [PATCH 3/5] Blue password field on enter --- backend/frontend/src/pages/Home.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/backend/frontend/src/pages/Home.tsx b/backend/frontend/src/pages/Home.tsx index 39d6702..5a1a1cf 100644 --- a/backend/frontend/src/pages/Home.tsx +++ b/backend/frontend/src/pages/Home.tsx @@ -166,6 +166,11 @@ export default function Home() { w={180} value={adminToken} onChange={(e) => setAdminToken(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter') { + e.currentTarget.blur(); + } + }} /> From 031093fbdd446f923565d454e58afd0095d1a6dd Mon Sep 17 00:00:00 2001 From: Emma Broman Date: Fri, 10 Apr 2026 17:38:43 +0200 Subject: [PATCH 4/5] Add confirm modal on upgrade test image, and component for the pixel diff --- .../src/components/PixelDiffNumber.tsx | 28 ++ .../frontend/src/components/TestHistory.tsx | 289 +++++++++++------- backend/frontend/src/components/TestRow.tsx | 18 +- backend/frontend/src/pages/Compare.tsx | 12 +- backend/frontend/src/utils.ts | 17 -- 5 files changed, 209 insertions(+), 155 deletions(-) create mode 100644 backend/frontend/src/components/PixelDiffNumber.tsx diff --git a/backend/frontend/src/components/PixelDiffNumber.tsx b/backend/frontend/src/components/PixelDiffNumber.tsx new file mode 100644 index 0000000..158fca2 --- /dev/null +++ b/backend/frontend/src/components/PixelDiffNumber.tsx @@ -0,0 +1,28 @@ +import { Box, Text } from '@mantine/core'; + +export function PixelDiffNumber({ value }: { value: number }) { + const style = diffStyle(value); + + function diffDisplay(diff: number): string { + return `${Math.round(diff * 100000) / 1000}%`; + } + + function diffStyle(diff: number): { backgroundColor: string; color: string } { + if (diff === 0.0) return { backgroundColor: '#eeeeee', color: '#111111' }; + else if (diff < 0.001) return { backgroundColor: '#4ce600', color: '#111111' }; + else if (diff < 0.01) return { backgroundColor: '#55cc00', color: '#111111' }; + else if (diff < 0.05) return { backgroundColor: '#66cc00', color: '#111111' }; + else if (diff < 0.1) return { backgroundColor: '#ede621', color: '#111111' }; + else if (diff < 0.25) return { backgroundColor: '#edc421', color: '#111111' }; + else if (diff < 0.5) return { backgroundColor: '#cc5000', color: '#ffffff' }; + else if (diff < 0.75) return { backgroundColor: '#cc4400', color: '#ffffff' }; + else if (diff < 1.0) return { backgroundColor: '#cc2200', color: '#ffffff' }; + else return { backgroundColor: '#cc0000', color: '#ffffff' }; + } + + return ( + + {diffDisplay(value)} + + ); +} diff --git a/backend/frontend/src/components/TestHistory.tsx b/backend/frontend/src/components/TestHistory.tsx index ebf5d58..270a46d 100644 --- a/backend/frontend/src/components/TestHistory.tsx +++ b/backend/frontend/src/components/TestHistory.tsx @@ -1,10 +1,12 @@ -import { Anchor, Box, Button, Table, Text } from '@mantine/core'; +import { Anchor, Box, Button, Group, Modal, Stack, Table, Text } from '@mantine/core'; import { TestData, TestRecord } from '../types'; -import { diffDisplay, diffStyle, timingDisplay } from '../utils'; +import { timingDisplay } from '../utils'; import { ImageThumbnail } from './ImageThumbnail'; import { useState } from 'react'; +import { useDisclosure } from '@mantine/hooks'; +import { PixelDiffNumber } from './PixelDiffNumber'; interface Props { record: TestRecord; @@ -13,125 +15,184 @@ interface Props { export function TestHistory({ record, onUpdateReference }: Props) { const [wasUpdated, setWasUpdated] = useState(false); + const [confirmOpened, { open, close }] = useDisclosure(false); const testData = [...record.data].reverse(); const ImageWidth = 250; return ( - - + - - - Timestamp - Error - Commit - Timing - Log - Candidate - Reference - Difference - - - - - {testData.map((d: TestData, i: number) => ( - - - - {new Date(d.timeStamp).toISOString().split('T')[0]} -
- {new Date(d.timeStamp).toISOString().split('T')[1]?.replace('Z', '')} -
-
- - - {diffDisplay(d.pixelError)} - - - - - {d.commitHash.substring(0, 8)} - - - - {timingDisplay(d.timing)} - - - - Log ({d.nErrors} errors) - - - - - - - - - - - - - {i === 0 && ( - <> - - - )} - + + + Are you sure you want to update the reference image to the candidate for the + test: {record.name}?{' '} + + This action cannot be undone. + + + + + + Candidate + + + + + Reference + + + + + Error: + + + + After updating, you may have to wait a few seconds and refresh the page to see + the updated reference. + + + + + + +
+ + +
+ + + Timestamp + Error + Commit + Timing + Log + Candidate + Reference + Difference + - ))} - -
-
+ + + {testData.map((d: TestData, i: number) => ( + + + + {new Date(d.timeStamp).toISOString().split('T')[0]} +
+ {new Date(d.timeStamp).toISOString().split('T')[1]?.replace('Z', '')} +
+
+ + + + + + {d.commitHash.substring(0, 8)} + + + + {timingDisplay(d.timing)} + + + + Log ({d.nErrors} errors) + + + + + + + + + + + + + {i === 0 && ( + <> + + + )} + +
+ ))} +
+ + + ); } diff --git a/backend/frontend/src/components/TestRow.tsx b/backend/frontend/src/components/TestRow.tsx index 6eca0d3..363bf59 100644 --- a/backend/frontend/src/components/TestRow.tsx +++ b/backend/frontend/src/components/TestRow.tsx @@ -1,9 +1,10 @@ -import { Anchor, Box, Table, Text } from '@mantine/core'; +import { Anchor, Table, Text } from '@mantine/core'; import { TestRecord } from '../types'; -import { diffDisplay, diffStyle, timingDisplay } from '../utils'; +import { timingDisplay } from '../utils'; import { ImageThumbnail } from './ImageThumbnail'; +import { PixelDiffNumber } from './PixelDiffNumber'; interface Props { record: TestRecord; @@ -19,18 +20,7 @@ export function TestRow({ record, onOpen }: Props) { return ( onOpen(record)}> - - {diffDisplay(latestData.pixelError)} - + {record.group} {record.name} diff --git a/backend/frontend/src/pages/Compare.tsx b/backend/frontend/src/pages/Compare.tsx index 989f738..214312c 100644 --- a/backend/frontend/src/pages/Compare.tsx +++ b/backend/frontend/src/pages/Compare.tsx @@ -13,7 +13,7 @@ import { } from '@mantine/core'; import { TestRecord } from '../types'; -import { diffDisplay, diffStyle } from '../utils'; +import { PixelDiffNumber } from '../components/PixelDiffNumber'; type ImageType = 'reference' | 'candidate'; @@ -55,15 +55,7 @@ function CompareCell({ type, group, name, hardware1, hardware2 }: Props) { style={{ width: 150, height: 84.375 }} /> - {pixelError !== null && ( - - {diffDisplay(pixelError)} - - )} + {pixelError !== null && } ); } diff --git a/backend/frontend/src/utils.ts b/backend/frontend/src/utils.ts index df493e0..a1a4ed3 100644 --- a/backend/frontend/src/utils.ts +++ b/backend/frontend/src/utils.ts @@ -1,26 +1,9 @@ import { SortColumn, SortDirection, TestRecord } from './types'; -export function diffDisplay(diff: number): string { - return `${Math.round(diff * 100000) / 1000}%`; -} - export function timingDisplay(timing: number): string { return `${Math.round(timing * 1000) / 1000}s`; } -export function diffStyle(diff: number): { backgroundColor: string; color: string } { - if (diff === 0.0) return { backgroundColor: '#eeeeee', color: '#111111' }; - else if (diff < 0.001) return { backgroundColor: '#4ce600', color: '#111111' }; - else if (diff < 0.01) return { backgroundColor: '#55cc00', color: '#111111' }; - else if (diff < 0.05) return { backgroundColor: '#66cc00', color: '#111111' }; - else if (diff < 0.1) return { backgroundColor: '#ede621', color: '#111111' }; - else if (diff < 0.25) return { backgroundColor: '#edc421', color: '#111111' }; - else if (diff < 0.5) return { backgroundColor: '#cc5000', color: '#ffffff' }; - else if (diff < 0.75) return { backgroundColor: '#cc4400', color: '#ffffff' }; - else if (diff < 1.0) return { backgroundColor: '#cc2200', color: '#ffffff' }; - else return { backgroundColor: '#cc0000', color: '#ffffff' }; -} - export function sortRecords( records: TestRecord[], column: SortColumn, From a0e36ff0fb321d99264e5c21b5cdbca22539423e Mon Sep 17 00:00:00 2001 From: Emma Broman Date: Fri, 10 Apr 2026 17:52:16 +0200 Subject: [PATCH 5/5] Fix some issues --- backend/frontend/src/components/TestHistory.tsx | 10 ++-------- backend/frontend/src/pages/Home.tsx | 3 ++- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/backend/frontend/src/components/TestHistory.tsx b/backend/frontend/src/components/TestHistory.tsx index 270a46d..614d3ae 100644 --- a/backend/frontend/src/components/TestHistory.tsx +++ b/backend/frontend/src/components/TestHistory.tsx @@ -78,19 +78,13 @@ export function TestHistory({ record, onUpdateReference }: Props) { onClick={() => { onUpdateReference(record); setWasUpdated(true); + close(); }} color="red" > Yes, update reference - diff --git a/backend/frontend/src/pages/Home.tsx b/backend/frontend/src/pages/Home.tsx index 5a1a1cf..6bc602b 100644 --- a/backend/frontend/src/pages/Home.tsx +++ b/backend/frontend/src/pages/Home.tsx @@ -215,11 +215,12 @@ export default function Home() { filter={ setNameFilter(e.target.value)} rightSection={ nameFilter && ( setNameFilter('')} size={'xs'}> - × + x ) }