diff --git a/package.json b/package.json index 75350f1f..1cfa1894 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,6 @@ "lodash": "^4.17.21", "loglevel": "^1.8.0", "loglevel-plugin-remote": "^0.6.8", - "maxun-core": "^0.0.15", "minio": "^8.0.1", "moment-timezone": "^0.5.45", "node-cron": "^3.0.3", diff --git a/public/locales/de.json b/public/locales/de.json index 1256be22..54c009f0 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -543,16 +543,15 @@ "captured_data": { "title": "Erfasste Daten", "download_csv": "CSV herunterladen", - "download_all_json": "Gesamte JSON herunterladen", "view_full": "Vollständige Daten anzeigen", "items": "Elemente", - "schema_title": "Text erfassen", - "list_title": "Liste erfassen" + "schema_title": "Erfasste Texte", + "list_title": "Erfasste Listen" }, "captured_screenshot": { "title": "Erfasste Screenshots", - "download": "Screenshot herunterladen", - "render_failed": "Screenshot konnte nicht gerendert werden" + "download": "Herunterladen", + "render_failed": "Fehler beim Rendern des Screenshots" } }, "navbar": { diff --git a/public/locales/en.json b/public/locales/en.json index b06de383..ee2e2e92 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -556,15 +556,14 @@ "captured_data": { "title": "Captured Data", "download_csv": "Download CSV", - "download_all_json": "Download All JSON", "view_full": "View Full Data", "items": "items", - "schema_title": "Capture Text", - "list_title": "Capture List" + "schema_title": "Captured Texts", + "list_title": "Captured Lists" }, "captured_screenshot": { "title": "Captured Screenshots", - "download": "Download Screenshot", + "download": "Download", "render_failed": "Failed to render screenshot" } }, diff --git a/public/locales/es.json b/public/locales/es.json index b28fc5cf..d2d487c1 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -542,18 +542,17 @@ "loading": "Cargando datos...", "empty_output": "No hay datos de salida disponibles", "captured_data": { - "title": "Datos Capturados", + "title": "Datos capturados", "download_csv": "Descargar CSV", - "download_all_json": "Descargar Todo JSON", - "view_full": "Ver Datos Completos", + "view_full": "Ver datos completos", "items": "elementos", - "schema_title": "Capturar Texto", - "list_title": "Capturar Lista" + "schema_title": "Textos capturados", + "list_title": "Listas capturadas" }, "captured_screenshot": { - "title": "Capturas de Pantalla", - "download": "Descargar Captura", - "render_failed": "Error al renderizar la captura" + "title": "Capturas de pantalla", + "download": "Descargar", + "render_failed": "Error al renderizar la captura de pantalla" } }, "navbar": { diff --git a/public/locales/ja.json b/public/locales/ja.json index 52fed446..833494ee 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -542,17 +542,16 @@ "loading": "データを読み込み中...", "empty_output": "出力データがありません", "captured_data": { - "title": "キャプチャされたデータ", + "title": "キャプチャしたデータ", "download_csv": "CSVをダウンロード", - "download_all_json": "すべてのJSONをダウンロード", - "view_full": "すべてのデータを表示", + "view_full": "完全なデータを表示", "items": "アイテム", - "schema_title": "テキストをキャプチャ", - "list_title": "リストをキャプチャ" + "schema_title": "キャプチャしたテキスト", + "list_title": "キャプチャしたリスト" }, "captured_screenshot": { - "title": "キャプチャされたスクリーンショット", - "download": "スクリーンショットをダウンロード", + "title": "キャプチャしたスクリーンショット", + "download": "ダウンロード", "render_failed": "スクリーンショットのレンダリングに失敗しました" } }, diff --git a/public/locales/zh.json b/public/locales/zh.json index b4512bb3..27bb1e63 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -544,15 +544,14 @@ "captured_data": { "title": "已捕获的数据", "download_csv": "下载CSV", - "download_all_json": "下载所有JSON", "view_full": "查看完整数据", "items": "项目", - "schema_title": "捕获文本", - "list_title": "捕获列表" + "schema_title": "已捕获的文本", + "list_title": "已捕获的列表" }, "captured_screenshot": { "title": "已捕获的截图", - "download": "下载截图", + "download": "下载", "render_failed": "渲染截图失败" } }, diff --git a/src/components/run/RunContent.tsx b/src/components/run/RunContent.tsx index 781fba70..e554eaac 100644 --- a/src/components/run/RunContent.tsx +++ b/src/components/run/RunContent.tsx @@ -1,20 +1,21 @@ -import { Box, Tabs, Typography, Tab, Paper, Button, CircularProgress, Accordion, AccordionSummary, AccordionDetails, Divider, Card, CardHeader, CardContent, Grid, IconButton, Chip, ButtonGroup } from "@mui/material"; +import { + Box, + Tabs, + Typography, + Tab, + Paper, + Button, + CircularProgress, + Accordion, + AccordionSummary, + AccordionDetails, + ButtonGroup +} from "@mui/material"; import Highlight from "react-highlight"; import * as React from "react"; import { Data } from "./RunsTable"; import { TabPanel, TabContext } from "@mui/lab"; -import ArticleIcon from '@mui/icons-material/Article'; -import ImageIcon from '@mui/icons-material/Image'; -import ListIcon from '@mui/icons-material/List'; -import SchemaIcon from '@mui/icons-material/Schema'; -import MoreHorizIcon from '@mui/icons-material/MoreHoriz'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; -import CloudDownloadIcon from '@mui/icons-material/CloudDownload'; -import DownloadIcon from '@mui/icons-material/Download'; -import FullscreenIcon from '@mui/icons-material/Fullscreen'; -import ViewModuleIcon from '@mui/icons-material/ViewModule'; -import ViewListIcon from '@mui/icons-material/ViewList'; -import DataObjectIcon from '@mui/icons-material/DataObject'; import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; import { useEffect, useState } from "react"; @@ -38,17 +39,16 @@ interface RunContentProps { export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRef, abortRunHandler }: RunContentProps) => { const { t } = useTranslation(); const [tab, setTab] = React.useState('output'); - + const [schemaData, setSchemaData] = useState([]); const [schemaColumns, setSchemaColumns] = useState([]); - + const [listData, setListData] = useState([]); const [listColumns, setListColumns] = useState([]); const [currentListIndex, setCurrentListIndex] = useState(0); - const [expandedView, setExpandedView] = useState(null); - - const [viewMode, setViewMode] = useState<'horizontal' | 'vertical'>('vertical'); + const [screenshotKeys, setScreenshotKeys] = useState([]); + const [currentScreenshotIndex, setCurrentScreenshotIndex] = useState(0); const [legacyData, setLegacyData] = useState([]); const [legacyColumns, setLegacyColumns] = useState([]); @@ -61,10 +61,10 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe useEffect(() => { if (!row.serializableOutput) return; - if (!row.serializableOutput.scrapeSchema && - !row.serializableOutput.scrapeList && - Object.keys(row.serializableOutput).length > 0) { - + if (!row.serializableOutput.scrapeSchema && + !row.serializableOutput.scrapeList && + Object.keys(row.serializableOutput).length > 0) { + setIsLegacyData(true); processLegacyData(row.serializableOutput); return; @@ -75,15 +75,22 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe if (row.serializableOutput.scrapeSchema && Object.keys(row.serializableOutput.scrapeSchema).length > 0) { processDataCategory(row.serializableOutput.scrapeSchema, setSchemaData, setSchemaColumns); } - + if (row.serializableOutput.scrapeList) { processScrapeList(row.serializableOutput.scrapeList); } }, [row.serializableOutput]); + useEffect(() => { + if (row.binaryOutput && Object.keys(row.binaryOutput).length > 0) { + setScreenshotKeys(Object.keys(row.binaryOutput)); + setCurrentScreenshotIndex(0); + } + }, [row.binaryOutput]); + const processLegacyData = (legacyOutput: Record) => { let allData: any[] = []; - + Object.keys(legacyOutput).forEach(key => { const data = legacyOutput[key]; if (Array.isArray(data)) { @@ -93,13 +100,13 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe allData = [...allData, ...filteredData]; } }); - + if (allData.length > 0) { const allColumns = new Set(); allData.forEach(item => { Object.keys(item).forEach(key => allColumns.add(key)); }); - + setLegacyData(allData); setLegacyColumns(Array.from(allColumns)); } @@ -111,7 +118,7 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe setColumns: React.Dispatch> ) => { let allData: any[] = []; - + Object.keys(categoryData).forEach(key => { const data = categoryData[key]; if (Array.isArray(data)) { @@ -121,13 +128,13 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe allData = [...allData, ...filteredData]; } }); - + if (allData.length > 0) { const allColumns = new Set(); allData.forEach(item => { Object.keys(item).forEach(key => allColumns.add(key)); }); - + setData(allData); setColumns(Array.from(allColumns)); } @@ -136,22 +143,22 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe const processScrapeList = (scrapeListData: any) => { const tablesList: any[][] = []; const columnsList: string[][] = []; - + if (Array.isArray(scrapeListData)) { scrapeListData.forEach(tableData => { if (Array.isArray(tableData) && tableData.length > 0) { const filteredData = tableData.filter(row => Object.values(row).some(value => value !== undefined && value !== "") ); - + if (filteredData.length > 0) { tablesList.push(filteredData); - + const tableColumns = new Set(); filteredData.forEach(item => { Object.keys(item).forEach(key => tableColumns.add(key)); }); - + columnsList.push(Array.from(tableColumns)); } } @@ -163,21 +170,21 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe const filteredData = tableData.filter(row => Object.values(row).some(value => value !== undefined && value !== "") ); - + if (filteredData.length > 0) { tablesList.push(filteredData); - + const tableColumns = new Set(); filteredData.forEach(item => { Object.keys(item).forEach(key => tableColumns.add(key)); }); - + columnsList.push(Array.from(tableColumns)); } } }); } - + setListData(tablesList); setListColumns(columnsList); setCurrentListIndex(0); @@ -217,35 +224,12 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe document.body.appendChild(link); link.click(); document.body.removeChild(link); - + setTimeout(() => { URL.revokeObjectURL(url); }, 100); }; - const downloadAllJSON = () => { - let allData; - - if (isLegacyData) { - allData = { data: legacyData }; - } else { - allData = { - schema: schemaData, - list: listData.flat(), - }; - } - - const blob = new Blob([JSON.stringify(allData, null, 2)], { type: 'application/json;charset=utf-8;' }); - const url = URL.createObjectURL(blob); - - const link = document.createElement("a"); - link.href = url; - link.setAttribute("download", "all_data.json"); - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - }; - const navigateListTable = (direction: 'next' | 'prev') => { if (direction === 'next' && currentListIndex < listData.length - 1) { setCurrentListIndex(currentListIndex + 1); @@ -254,23 +238,30 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe } }; + const navigateScreenshots = (direction: 'next' | 'prev') => { + if (direction === 'next' && currentScreenshotIndex < screenshotKeys.length - 1) { + setCurrentScreenshotIndex(currentScreenshotIndex + 1); + } else if (direction === 'prev' && currentScreenshotIndex > 0) { + setCurrentScreenshotIndex(currentScreenshotIndex - 1); + } + }; + const renderDataTable = ( data: any[], columns: string[], title: string, - icon: React.ReactNode, csvFilename: string, jsonFilename: string, isPaginatedList: boolean = false ) => { if (!isPaginatedList && data.length === 0) return null; if (isPaginatedList && (listData.length === 0 || currentListIndex >= listData.length)) return null; - + const currentData = isPaginatedList ? listData[currentListIndex] : data; const currentColumns = isPaginatedList ? listColumns[currentListIndex] : columns; - + if (!currentData || currentData.length === 0) return null; - + return ( - {icon} - + {title} - {isPaginatedList ? ( - 1 - ? `Table ${currentListIndex + 1} of ${listData.length} (${currentData.length} ${currentData.length === 1 ? 'item' : 'items'})` - : `${currentData.length} ${currentData.length === 1 ? 'item' : 'items'}` - } - size="small" - sx={{ ml: 2, backgroundColor: '#FF00C3', color: 'white' }} - /> - ) : ( - - )} - - - - - + + {isPaginatedList && listData.length > 1 && ( - - - )} - - - - - - {previewColumns.map((column) => ( - {column} - ))} - {showMoreColumns && ...} - - - - {previewData.map((row, index) => ( - - {previewColumns.map((column) => ( - - {row[column] === undefined || row[column] === "" ? "-" : row[column]} - - ))} - {showMoreColumns && ...} - - ))} - {currentData.length > 1 && ( - - - - - - )} - -
-
- - - ); - }; - - const renderExpandedView = (dataTypeWithIndex: string) => { - if (expandedView !== dataTypeWithIndex) return null; - - let data: any[] = []; - let columns: string[] = []; - let title = ""; - let csvFilename = ""; - let jsonFilename = ""; - - if (dataTypeWithIndex.startsWith('list-')) { - const indexStr = dataTypeWithIndex.split('-')[1]; - const index = parseInt(indexStr, 10); - - if (index >= 0 && index < listData.length) { - data = listData[index]; - columns = listColumns[index]; - title = `${t('run_content.captured_data.list_title')} - Table ${index+1}`; - csvFilename = `list_table_${index+1}.csv`; - jsonFilename = `list_table_${index+1}.json`; - } - } else { - switch (dataTypeWithIndex) { - case 'schema': - data = schemaData; - columns = schemaColumns; - title = t('run_content.captured_data.schema_title'); - csvFilename = 'schema_data.csv'; - jsonFilename = 'schema_data.json'; - break; - case 'list': - if (listData.length > 0 && listColumns.length > 0) { - data = listData[currentListIndex]; - columns = listColumns[currentListIndex]; - } - title = t('run_content.captured_data.list_title'); - csvFilename = 'list_data.csv'; - jsonFilename = 'list_data.json'; - break; - case 'legacy': - data = legacyData; - columns = legacyColumns; - title = t('run_content.captured_data.title'); - csvFilename = 'data.csv'; - jsonFilename = 'data.json'; - break; - } - } - - return ( - - - - {title} - - - - - - - - - - - - - - {columns.map((column) => ( - {column} - ))} - - - - {data.map((row, index) => ( - - {columns.map((column) => ( - - {row[column] === undefined || row[column] === "" ? "-" : row[column]} - - ))} - - ))} - -
-
-
-
- ); - }; - const hasData = schemaData.length > 0 || listData.length > 0 || legacyData.length > 0; const hasScreenshots = row.binaryOutput && Object.keys(row.binaryOutput).length > 0; @@ -780,288 +455,127 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe {t('run_content.loading')} ) : (!hasData && !hasScreenshots - ? {t('run_content.empty_output')} + ? {t('run_content.empty_output')} : null)} {hasData && ( - - - - {t('run_content.captured_data.title')} - - - setViewMode('horizontal')} - color={viewMode === 'horizontal' ? 'primary' : 'default'} - sx={{ color: viewMode === 'horizontal' ? '#FF00C3' : 'inherit' }} - > - - - setViewMode('vertical')} - color={viewMode === 'vertical' ? 'primary' : 'default'} - sx={{ color: viewMode === 'vertical' ? '#FF00C3' : 'inherit' }} - > - - - - - - {isLegacyData && ( - viewMode === 'vertical' ? ( - renderDataTable( - legacyData, - legacyColumns, - t('run_content.captured_data.title'), - , - 'data.csv', - 'data.json' - ) - ) : ( - - - {renderDataCard( - legacyData, - legacyColumns, - t('run_content.captured_data.title'), - , - 'legacy', - 'data.csv', - 'data.json' - )} - - + renderDataTable( + legacyData, + legacyColumns, + t('run_content.captured_data.title'), + 'data.csv', + 'data.json' ) )} - + {!isLegacyData && ( - viewMode === 'vertical' ? ( - <> - {renderDataTable( - schemaData, - schemaColumns, - t('run_content.captured_data.schema_title'), - , - 'schema_data.csv', - 'schema_data.json' - )} - - {listData.length > 0 && renderDataTable( - [], - [], - t('run_content.captured_data.list_title'), - , - 'list_data.csv', - 'list_data.json', - true - )} - - ) : ( - - {(() => { - const dataCategoriesCount = [ - schemaData.length > 0, - listData.length > 0, - ].filter(Boolean).length; - - const columnWidth = dataCategoriesCount === 1 ? 12 : dataCategoriesCount === 2 ? 6 : 4; - - return ( - <> - {schemaData.length > 0 && ( - - {renderDataCard( - schemaData, - schemaColumns, - t('run_content.captured_data.schema_title'), - , - 'schema', - 'schema_data.csv', - 'schema_data.json' - )} - - )} - - {listData.length > 0 && ( - - {renderDataCard( - [], - [], - t('run_content.captured_data.list_title'), - , - 'list', - 'list_data.csv', - 'list_data.json', - true - )} - - )} - - ); - })()} - - ) + <> + {renderDataTable( + schemaData, + schemaColumns, + t('run_content.captured_data.schema_title'), + 'schema_data.csv', + 'schema_data.json' + )} + + {listData.length > 0 && renderDataTable( + [], + [], + t('run_content.captured_data.list_title'), + 'list_data.csv', + 'list_data.json', + true + )} + )} - - {renderExpandedView('schema')} - {renderExpandedView('legacy')} - - {listData.map((_, index) => renderExpandedView(`list-${index}`))} )} - + {hasScreenshots && ( <> - - - - - {t('run_content.captured_screenshot.title')} - - - - setViewMode('horizontal')} - color={viewMode === 'horizontal' ? 'primary' : 'default'} - sx={{ color: viewMode === 'horizontal' ? '#FF00C3' : 'inherit' }} - > - - - setViewMode('vertical')} - color={viewMode === 'vertical' ? 'primary' : 'default'} - sx={{ color: viewMode === 'vertical' ? '#FF00C3' : 'inherit' }} - > - - + + } + aria-controls="screenshot-content" + id="screenshot-header" + > + + + {t('run_content.captured_screenshot.title', 'Screenshots')} + - - - - {viewMode === 'vertical' ? ( - <> - {Object.keys(row.binaryOutput).map((key, index) => { - try { - const imageUrl = row.binaryOutput[key]; - return ( - - } - aria-controls={`screenshot-${key}-content`} - id={`screenshot-${key}-header`} - > - - - - Screenshot {index+1} - - - - - - - - - - - {`Screenshot - - - - ); - } catch (e) { - console.log(e); - return ( - - {key}: {t('run_content.captured_screenshot.render_failed')} - - ); - } - })} - - ) : ( - - {Object.keys(row.binaryOutput).map((key) => { - try { - const imageUrl = row.binaryOutput[key]; - return ( - - - } - title={`Screenshot ${key}`} - action={ - - - - } - /> - - - {`Screenshot - - - - - ); - } catch (e) { - console.log(e); - return ( - - - {key}: {t('run_content.captured_screenshot.render_failed')} - - - ); - } - })} - - )} + + + + + + {screenshotKeys.length > 1 && ( + + + + + )} + + + + + {`Screenshot + + + +
)}