minor adjustments
This commit is contained in:
parent
e663c743ab
commit
749194f9e1
@ -1,10 +1,20 @@
|
||||
package no.iktdev.mediaprocessing.ui.socket
|
||||
|
||||
import no.iktdev.eventi.data.referenceId
|
||||
import no.iktdev.eventi.database.toEpochSeconds
|
||||
import no.iktdev.eventi.database.withDirtyRead
|
||||
import no.iktdev.eventi.database.withTransaction
|
||||
import no.iktdev.mediaprocessing.shared.common.contract.Events
|
||||
import no.iktdev.mediaprocessing.shared.common.contract.ProcessType
|
||||
import no.iktdev.mediaprocessing.shared.common.contract.data.*
|
||||
import no.iktdev.mediaprocessing.shared.common.contract.dto.OperationEvents
|
||||
import no.iktdev.mediaprocessing.shared.common.contract.dto.ProcesserEventInfo
|
||||
import no.iktdev.mediaprocessing.shared.common.database.cal.toEvent
|
||||
import no.iktdev.mediaprocessing.shared.common.database.cal.toTask
|
||||
import no.iktdev.mediaprocessing.shared.common.database.tables.events
|
||||
import no.iktdev.mediaprocessing.shared.common.database.tables.tasks
|
||||
import no.iktdev.mediaprocessing.shared.common.task.Task
|
||||
import no.iktdev.mediaprocessing.shared.common.task.TaskType
|
||||
import no.iktdev.mediaprocessing.ui.WebSocketMonitoringService
|
||||
import no.iktdev.mediaprocessing.ui.eventDatabase
|
||||
import no.iktdev.mediaprocessing.ui.socket.a2a.ProcesserListenerService
|
||||
@ -13,25 +23,33 @@ import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.messaging.handler.annotation.MessageMapping
|
||||
import org.springframework.messaging.simp.SimpMessagingTemplate
|
||||
import org.springframework.stereotype.Service
|
||||
import java.io.File
|
||||
|
||||
@Service
|
||||
class ProcesserTasksTopic(
|
||||
@Autowired a2AProcesserService: ProcesserListenerService,
|
||||
@Autowired private val webSocketMonitoringService: WebSocketMonitoringService,
|
||||
@Autowired override var template: SimpMessagingTemplate?,
|
||||
): SocketListener(template) {
|
||||
@Autowired private val message: SimpMessagingTemplate?,
|
||||
): SocketListener(message) {
|
||||
|
||||
|
||||
final val a2a = object : ProcesserListenerService.A2AProcesserListener {
|
||||
override fun onExtractProgress(info: ProcesserEventInfo) {
|
||||
message?.convertAndSend("/topic/processer/extract/progress", info)
|
||||
pullAllTasks()
|
||||
}
|
||||
|
||||
override fun onEncodeProgress(info: ProcesserEventInfo) {
|
||||
message?.convertAndSend("/topic/processer/encode/progress", info)
|
||||
pullAllTasks()
|
||||
}
|
||||
|
||||
override fun onEncodeAssigned() {
|
||||
pullAllTasks()
|
||||
}
|
||||
|
||||
override fun onExtractAssigned() {
|
||||
pullAllTasks()
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,18 +57,96 @@ class ProcesserTasksTopic(
|
||||
a2AProcesserService.attachListener(a2a)
|
||||
}
|
||||
|
||||
data class TaskGroup(
|
||||
enum class Status {
|
||||
Skipped,
|
||||
Awaiting, // Waiting for tasks to be created
|
||||
NeedsApproval,
|
||||
Pending,
|
||||
InProgress,
|
||||
Completed,
|
||||
Failed,
|
||||
}
|
||||
|
||||
data class ContentEventState(
|
||||
val referenceId: String,
|
||||
val tasks: List<Task>
|
||||
)
|
||||
val title: String,
|
||||
val encode: Status = Status.Skipped,
|
||||
val extract: Status = Status.Skipped,
|
||||
val convert: Status = Status.Skipped,
|
||||
val created: Long
|
||||
) {}
|
||||
|
||||
|
||||
@MessageMapping("/tasks/all")
|
||||
fun pullAllTasks() {
|
||||
fun pullAllTaskss() {
|
||||
val states = update()
|
||||
template?.convertAndSend("/topic/tasks/all", states)
|
||||
}
|
||||
|
||||
fun getOperationState(tasks: List<Task>, hasOperation: Boolean, canStart: Boolean): Status {
|
||||
if (!hasOperation) return Status.Skipped
|
||||
if (tasks.isEmpty()) return Status.Awaiting
|
||||
|
||||
if (!canStart) return Status.NeedsApproval
|
||||
|
||||
if (tasks.any { it.consumed }) {
|
||||
return Status.Completed
|
||||
}
|
||||
if (tasks.any { it.claimed }) {
|
||||
return Status.InProgress
|
||||
}
|
||||
if (tasks.any{ it.status == "ERROR"}) {
|
||||
return Status.Failed
|
||||
}
|
||||
return Status.Pending
|
||||
|
||||
}
|
||||
|
||||
|
||||
fun update(): MutableList<ContentEventState> {
|
||||
val eventStates: MutableList<ContentEventState> = mutableListOf()
|
||||
|
||||
val tasks = pullAllTasks()
|
||||
val availableEvents = pullAllEvents()
|
||||
|
||||
for ((referenceId, events) in availableEvents) {
|
||||
val startEvent = events.findFirstEventOf<MediaProcessStartEvent>() ?: continue
|
||||
val startData = startEvent.data ?: continue
|
||||
val title = events.findFirstEventOf<BaseInfoEvent>()?.data?.sanitizedName ?: startData.file.let { File(it).nameWithoutExtension }
|
||||
val canStart = if (startData.type == ProcessType.FLOW) true else {
|
||||
events.findEventsOf<PermitWorkCreationEvent>().isNotEmpty()
|
||||
}
|
||||
val tasksCreated = tasks[referenceId]
|
||||
val encode = tasksCreated?.filter { it.task == TaskType.Encode } ?: emptyList()
|
||||
val extract = tasksCreated?.filter { it.task == TaskType.Extract } ?: emptyList()
|
||||
val convert = tasksCreated?.filter { it.task == TaskType.Convert } ?: emptyList()
|
||||
|
||||
eventStates.add(ContentEventState(
|
||||
title = title,
|
||||
referenceId = referenceId,
|
||||
encode = getOperationState(encode, startData.operations.contains(OperationEvents.ENCODE), canStart),
|
||||
extract = getOperationState(extract, startData.operations.contains(OperationEvents.EXTRACT), canStart),
|
||||
convert = getOperationState(convert, startData.operations.contains(OperationEvents.CONVERT), canStart),
|
||||
created = startEvent.metadata.created.toEpochSeconds() * 1000L
|
||||
))
|
||||
}
|
||||
return eventStates
|
||||
}
|
||||
|
||||
fun pullAllTasks(): Map<String, List<Task>> {
|
||||
val result = withTransaction(eventDatabase.database) {
|
||||
tasks.selectAll().toTask()
|
||||
.groupBy { it.referenceId }.map { g -> TaskGroup(g.key, g.value) }
|
||||
} ?: emptyList()
|
||||
template?.convertAndSend("/topic/tasks/all", result)
|
||||
.groupBy { it.referenceId }
|
||||
} ?: emptyMap()
|
||||
return result
|
||||
}
|
||||
|
||||
fun pullAllEvents(): Map<String, List<Event>> {
|
||||
val result = withDirtyRead(eventDatabase.database) {
|
||||
events.selectAll().toEvent()
|
||||
.groupBy { it.referenceId() }
|
||||
} ?: emptyMap()
|
||||
return result
|
||||
}
|
||||
|
||||
}
|
||||
@ -68,7 +68,7 @@ class UnprocessedFilesTopic(
|
||||
FileInfo(
|
||||
it[files.baseName],
|
||||
it[files.fileName],
|
||||
it[files.checksum]
|
||||
it[files.checksum],
|
||||
)
|
||||
}
|
||||
unprocessedFiles = found
|
||||
|
||||
@ -15,7 +15,6 @@ import org.springframework.stereotype.Service
|
||||
@Service
|
||||
class ProcesserListenerService(
|
||||
@Autowired private val webSocketMonitoringService: WebSocketMonitoringService,
|
||||
@Autowired private val message: SimpMessagingTemplate?,
|
||||
) {
|
||||
private val logger = KotlinLogging.logger {}
|
||||
private val listeners: MutableList<A2AProcesserListener> = mutableListOf()
|
||||
@ -50,9 +49,11 @@ class ProcesserListenerService(
|
||||
private val encodeProcessMessage = object : SocketMessageHandler() {
|
||||
override fun onMessage(socketMessage: String) {
|
||||
super.onMessage(socketMessage)
|
||||
message?.convertAndSend("/topic/processer/encode/progress", socketMessage)
|
||||
val response = gson.fromJson(socketMessage, ProcesserEventInfo::class.java)
|
||||
if (webSocketMonitoringService.anyListening()) {
|
||||
listeners.forEach { listener ->
|
||||
run {
|
||||
listener.onEncodeProgress(response)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -61,11 +62,14 @@ class ProcesserListenerService(
|
||||
private val extractProcessFrameHandler = object : SocketMessageHandler() {
|
||||
override fun onMessage(socketMessage: String) {
|
||||
super.onMessage(socketMessage)
|
||||
message?.convertAndSend("/topic/processer/extract/progress", socketMessage)
|
||||
if (webSocketMonitoringService.anyListening()) {
|
||||
}
|
||||
//val stringPayload = (if (payload is ByteArray) String(payload) else payload as String)
|
||||
//val response = gson.fromJson(stringPayload, ProcesserEventInfo::class.java)
|
||||
val response = gson.fromJson(socketMessage, ProcesserEventInfo::class.java)
|
||||
listeners.forEach { listener ->
|
||||
run {
|
||||
listener.onEncodeProgress(response)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
63
apps/ui/web/package-lock.json
generated
63
apps/ui/web/package-lock.json
generated
@ -574,13 +574,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helpers": {
|
||||
"version": "7.22.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.6.tgz",
|
||||
"integrity": "sha512-YjDs6y/fVOYFV8hAf1rxd1QvR9wJe1pDBZ2AREKq/SDayfPzgk0PBnVuTCE5X1acEpMMNOVUqoe+OwiZGJ+OaA==",
|
||||
"version": "7.26.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.10.tgz",
|
||||
"integrity": "sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/template": "^7.22.5",
|
||||
"@babel/traverse": "^7.22.6",
|
||||
"@babel/types": "^7.22.5"
|
||||
"@babel/template": "^7.26.9",
|
||||
"@babel/types": "^7.26.10"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@ -2085,9 +2085,10 @@
|
||||
"integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA=="
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz",
|
||||
"integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==",
|
||||
"version": "7.26.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz",
|
||||
"integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.14.0"
|
||||
},
|
||||
@ -2133,9 +2134,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.26.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.9.tgz",
|
||||
"integrity": "sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==",
|
||||
"version": "7.26.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.10.tgz",
|
||||
"integrity": "sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.25.9",
|
||||
@ -18009,11 +18010,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/use-sync-external-store": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
|
||||
"integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz",
|
||||
"integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/utf-8-validate": {
|
||||
@ -19510,13 +19512,12 @@
|
||||
}
|
||||
},
|
||||
"@babel/helpers": {
|
||||
"version": "7.22.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.6.tgz",
|
||||
"integrity": "sha512-YjDs6y/fVOYFV8hAf1rxd1QvR9wJe1pDBZ2AREKq/SDayfPzgk0PBnVuTCE5X1acEpMMNOVUqoe+OwiZGJ+OaA==",
|
||||
"version": "7.26.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.10.tgz",
|
||||
"integrity": "sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==",
|
||||
"requires": {
|
||||
"@babel/template": "^7.22.5",
|
||||
"@babel/traverse": "^7.22.6",
|
||||
"@babel/types": "^7.22.5"
|
||||
"@babel/template": "^7.26.9",
|
||||
"@babel/types": "^7.26.10"
|
||||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
@ -20481,9 +20482,9 @@
|
||||
"integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA=="
|
||||
},
|
||||
"@babel/runtime": {
|
||||
"version": "7.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz",
|
||||
"integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==",
|
||||
"version": "7.26.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz",
|
||||
"integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.14.0"
|
||||
},
|
||||
@ -20520,9 +20521,9 @@
|
||||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.26.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.9.tgz",
|
||||
"integrity": "sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==",
|
||||
"version": "7.26.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.10.tgz",
|
||||
"integrity": "sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==",
|
||||
"requires": {
|
||||
"@babel/helper-string-parser": "^7.25.9",
|
||||
"@babel/helper-validator-identifier": "^7.25.9"
|
||||
@ -31684,9 +31685,9 @@
|
||||
}
|
||||
},
|
||||
"use-sync-external-store": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
|
||||
"integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz",
|
||||
"integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==",
|
||||
"requires": {}
|
||||
},
|
||||
"utf-8-validate": {
|
||||
|
||||
@ -12,7 +12,6 @@ import { updateItems } from './app/store/composed-slice';
|
||||
import ExplorePage from './app/page/ExplorePage';
|
||||
import { ThemeProvider } from '@mui/material';
|
||||
import theme from './theme';
|
||||
import { simpleEventsUpdate } from './app/store/kafka-items-flat-slice';
|
||||
import { EventDataObject, SimpleEventDataObject } from './types';
|
||||
import EventsChainPage from './app/page/EventsChainPage';
|
||||
import UnprocessedFilesPage from './app/page/UnprocessedFilesPage';
|
||||
@ -22,6 +21,14 @@ import QueueIcon from '@mui/icons-material/Queue';
|
||||
import AppsIcon from '@mui/icons-material/Apps';
|
||||
import ConstructionIcon from '@mui/icons-material/Construction';
|
||||
import ProcesserTasksPage from './app/page/ProcesserTasksPage';
|
||||
import DashboardIcon from '@mui/icons-material/Dashboard';
|
||||
import GraphicEqIcon from '@mui/icons-material/GraphicEq';
|
||||
import HomeRepairServiceIcon from '@mui/icons-material/HomeRepairService';
|
||||
import InboxIcon from '@mui/icons-material/Inbox';
|
||||
import InputIcon from '@mui/icons-material/Input';
|
||||
import NotStartedIcon from '@mui/icons-material/NotStarted';
|
||||
import EventsPage from './app/page/EventsPage';
|
||||
import TableChartIcon from '@mui/icons-material/TableChart';
|
||||
|
||||
function App() {
|
||||
const client = useStompClient();
|
||||
@ -31,9 +38,6 @@ function App() {
|
||||
dispatch(updateItems(response))
|
||||
});
|
||||
|
||||
useWsSubscription<Array<SimpleEventDataObject>>("/topic/event/flat", (response) => {
|
||||
dispatch(simpleEventsUpdate(response))
|
||||
});
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
@ -71,7 +75,12 @@ function App() {
|
||||
}}>
|
||||
<AppsIcon />
|
||||
</IconButton>
|
||||
<IconButton onClick={() => window.location.href = "/events"} sx={{
|
||||
<IconButton onClick={() => window.location.href = "/processer"} sx={{
|
||||
...iconHeight
|
||||
}}>
|
||||
<GraphicEqIcon />
|
||||
</IconButton>
|
||||
<IconButton onClick={() => window.location.href = "/eventsflow"} sx={{
|
||||
...iconHeight
|
||||
}}>
|
||||
<AccountTreeIcon />
|
||||
@ -89,7 +98,7 @@ function App() {
|
||||
<IconButton onClick={() => window.location.href = "/tasks"} sx={{
|
||||
...iconHeight
|
||||
}}>
|
||||
<ConstructionIcon />
|
||||
<TableChartIcon />
|
||||
</IconButton>
|
||||
</Box>
|
||||
<Box sx={{
|
||||
@ -102,9 +111,10 @@ function App() {
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path='/tasks' element={<ProcesserTasksPage />} />
|
||||
<Route path='/processer' element={<EventsPage />} />
|
||||
<Route path='/unprocessed' element={<UnprocessedFilesPage />} />
|
||||
<Route path='/files' element={<ExplorePage />} />
|
||||
<Route path='/events' element={<EventsChainPage />} />
|
||||
<Route path='/eventsflow' element={<EventsChainPage />} />
|
||||
<Route path='/' element={<LaunchPage />} />
|
||||
</Routes>
|
||||
<Footer />
|
||||
|
||||
@ -10,16 +10,31 @@ export interface ExpandableItem<T> {
|
||||
}
|
||||
|
||||
export type ExpandableRender<T> = (item: T) => ExpandableItem<T> | null;
|
||||
export interface ExpandableTableItem {
|
||||
rowId: string
|
||||
}
|
||||
|
||||
|
||||
export default function ExpandableTable<T>({ items, columns, cellCustomizer: customizer, expandableRender, onRowClickedEvent }: { items: Array<T>, columns: Array<TablePropetyConfig>, cellCustomizer?: TableCellCustomizer<T>, expandableRender: ExpandableRender<T>, onRowClickedEvent?: TableRowActionEvents<T> }) {
|
||||
export default function ExpandableTable<T extends ExpandableTableItem>({ items, columns, cellCustomizer: customizer, expandableRender, onRowClickedEvent }: { items: Array<T>, columns: Array<TablePropetyConfig>, cellCustomizer?: TableCellCustomizer<T>, expandableRender: ExpandableRender<T>, onRowClickedEvent?: TableRowActionEvents<T> }) {
|
||||
const muiTheme = useTheme();
|
||||
|
||||
const [order, setOrder] = useState<'asc' | 'desc'>('asc');
|
||||
const [orderBy, setOrderBy] = useState<string>('');
|
||||
const [expandedRowIds, setExpandedRowIds] = useState<Set<string>>(new Set());
|
||||
const [selectedRow, setSelectedRow] = useState<T | null>(null);
|
||||
|
||||
const tableRowSingleClicked = (row: T | null) => {
|
||||
if (row != null && 'rowId' in row) {
|
||||
setExpandedRowIds(prev => {
|
||||
const newExpandedRows = new Set(prev);
|
||||
if (newExpandedRows.has(row.rowId)) {
|
||||
newExpandedRows.delete(row.rowId);
|
||||
} else {
|
||||
newExpandedRows.add(row.rowId);
|
||||
}
|
||||
return newExpandedRows;
|
||||
})
|
||||
}
|
||||
|
||||
if (row === selectedRow) {
|
||||
setSelectedRow(null);
|
||||
} else {
|
||||
@ -28,6 +43,7 @@ export default function ExpandableTable<T>({ items, columns, cellCustomizer: cus
|
||||
onRowClickedEvent.click(row);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
const tableRowDoubleClicked = (row: T | null) => {
|
||||
setSelectedRow(row);
|
||||
@ -127,7 +143,7 @@ export default function ExpandableTable<T>({ items, columns, cellCustomizer: cus
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
{(selectedRow == row) ?
|
||||
{(expandedRowIds.has(row.rowId)) ?
|
||||
(<TableRow key={rowIndex + "_1"}>
|
||||
<TableCell colSpan={columns.length}>
|
||||
{
|
||||
|
||||
@ -59,12 +59,10 @@ export default function EventsChainPage() {
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (Object.keys(cursor).length === 0) {
|
||||
client?.publish({
|
||||
destination: "/app/chained/all"
|
||||
});
|
||||
}
|
||||
}, [cursor, client, dispatch]);
|
||||
client?.publish({
|
||||
destination: "/app/chained/all"
|
||||
});
|
||||
}, [client, dispatch]);
|
||||
|
||||
const onRefresh = () => {
|
||||
client?.publish({
|
||||
|
||||
@ -16,6 +16,7 @@ import ContextMenu, { ContextMenuActionEvent, ContextMenuItem } from '../feature
|
||||
import { canConvert, canEncode, canExtract } from '../../fileUtil';
|
||||
import SimpleTable from '../features/table/sortableTable';
|
||||
import TagIcon from '@mui/icons-material/Tag';
|
||||
import { CoordinatorOperationRequest } from '../features/types';
|
||||
|
||||
|
||||
const createTableCell: TableCellCustomizer<ExplorerItem> = (accessor, data) => {
|
||||
@ -195,13 +196,6 @@ function getContextMenuFileActionMenuItems(row: ExplorerItem | null): ContextMen
|
||||
return items;
|
||||
}
|
||||
|
||||
interface ExplorerOperationRequest {
|
||||
destination: string;
|
||||
file: string;
|
||||
source: string;
|
||||
mode: "FLOW" | "MANUAL";
|
||||
}
|
||||
|
||||
export default function ExplorePage() {
|
||||
const muiTheme = useTheme();
|
||||
const dispatch = useDispatch();
|
||||
@ -254,7 +248,7 @@ export default function ExplorePage() {
|
||||
file: value.path,
|
||||
source: `Web UI @ ${window.location.href}`,
|
||||
mode: "FLOW"
|
||||
} as ExplorerOperationRequest
|
||||
} as CoordinatorOperationRequest
|
||||
}
|
||||
case 1: {
|
||||
return {
|
||||
@ -262,7 +256,7 @@ export default function ExplorePage() {
|
||||
file: value.path,
|
||||
source: `Web UI @ ${window.location.href}`,
|
||||
mode: "FLOW"
|
||||
} as ExplorerOperationRequest
|
||||
} as CoordinatorOperationRequest
|
||||
}
|
||||
case 2: {
|
||||
return {
|
||||
@ -270,7 +264,7 @@ export default function ExplorePage() {
|
||||
file: value.path,
|
||||
source: `Web UI @ ${window.location.href}`,
|
||||
mode: "FLOW"
|
||||
} as ExplorerOperationRequest
|
||||
} as CoordinatorOperationRequest
|
||||
}
|
||||
case 3: {
|
||||
return {
|
||||
@ -278,7 +272,7 @@ export default function ExplorePage() {
|
||||
file: value.path,
|
||||
source: `Web UI @ ${window.location.href}`,
|
||||
mode: "FLOW"
|
||||
} as ExplorerOperationRequest
|
||||
} as CoordinatorOperationRequest
|
||||
}
|
||||
default: {
|
||||
return null;
|
||||
|
||||
@ -22,15 +22,14 @@ export default function LaunchPage() {
|
||||
const dispatch = useDispatch();
|
||||
const muiTheme = useTheme();
|
||||
const client = useStompClient();
|
||||
const simpleList = useSelector((state: RootState) => state.kafkaComposedFlat)
|
||||
useEffect(() => {
|
||||
/*useEffect(() => {
|
||||
if (simpleList.items.filter((item) => item.encodingTimeLeft !== null).length > 0) {
|
||||
columns.push({
|
||||
label: "Completion",
|
||||
accessor: "encodingTimeLeft"
|
||||
})
|
||||
}
|
||||
}, [simpleList, dispatch])
|
||||
}, [simpleList, dispatch])*/
|
||||
|
||||
const onRefresh = () => {
|
||||
client?.publish({
|
||||
@ -82,7 +81,6 @@ export default function LaunchPage() {
|
||||
position: "absolute",
|
||||
width: "100%"
|
||||
}}>
|
||||
<SimpleTable items={simpleList.items} columns={columns} />
|
||||
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@ -3,13 +3,16 @@ import { useDispatch, useSelector } from "react-redux";
|
||||
import { useStompClient } from "react-stomp-hooks";
|
||||
import { Box, Button, Grid, TextField, Typography, useTheme } from '@mui/material';
|
||||
import { ExplorerItem } from "../../types";
|
||||
import { ContextMenuItem } from "../features/ContextMenu";
|
||||
import ContextMenu, { ContextMenuActionEvent, ContextMenuItem } from "../features/ContextMenu";
|
||||
import { RootState } from "../store";
|
||||
import { useWsSubscription } from "../ws/subscriptions";
|
||||
import { FileInfo, FileInfoGroup, IncomingUnprocessedFiles, update, } from "../store/unprocessed-files-slice";
|
||||
import SimpleTable from "../features/table/sortableTable";
|
||||
import { TablePropetyConfig } from "../features/table/table";
|
||||
import { TablePropetyConfig, TableRowActionEvents } from "../features/table/table";
|
||||
import MultiListSortedTable from "../features/table/multiListSortedTable";
|
||||
import { setContextMenuVisible, setContextMenuPosition } from "../store/context-menu-slice";
|
||||
import { canConvert, canEncode, canExtract } from "../../fileUtil";
|
||||
import { CoordinatorOperationRequest } from "../features/types";
|
||||
|
||||
|
||||
const columns: Array<TablePropetyConfig> = [
|
||||
@ -24,13 +27,94 @@ export default function UnprocessedFilesPage() {
|
||||
const files = useSelector((state: RootState) => state.unprocessedFiles);
|
||||
const [tableItems, setTableItems] = useState<Array<FileInfoGroup>>([]);
|
||||
|
||||
const [selectedRow, setSelectedRow] = useState<ExplorerItem|null>(null);
|
||||
const [selectedRow, setSelectedRow] = useState<FileInfo|null>(null);
|
||||
const [actionableItems, setActionableItems] = useState<Array<ContextMenuItem>>([]);
|
||||
|
||||
useWsSubscription<IncomingUnprocessedFiles>("/topic/files/unprocessed", (response) => {
|
||||
dispatch(update(response))
|
||||
});
|
||||
|
||||
|
||||
const onItemSelectedEvent: TableRowActionEvents<FileInfo> = {
|
||||
contextMenu: (row: FileInfo, x: number, y: number) => {
|
||||
dispatch(setContextMenuVisible(true));
|
||||
dispatch(setContextMenuPosition({ x: x, y: y }));
|
||||
setActionableItems(getContextMenuFileActionMenuItems(row));
|
||||
},
|
||||
click: function (row: FileInfo): void {
|
||||
},
|
||||
doubleClick: function (row: FileInfo): void {
|
||||
}
|
||||
};
|
||||
|
||||
function getContextMenuFileActionMenuItems(row: FileInfo | null): ContextMenuItem[] {
|
||||
const items: Array<ContextMenuItem> = [
|
||||
{
|
||||
actionIndex: 0,
|
||||
icon: null,
|
||||
text: "All available"
|
||||
},
|
||||
{
|
||||
actionIndex: 1,
|
||||
icon: null,
|
||||
text: "Encode"
|
||||
},
|
||||
{
|
||||
actionIndex: 2,
|
||||
icon: null,
|
||||
text: "Extract"
|
||||
}
|
||||
|
||||
];
|
||||
return items;
|
||||
}
|
||||
|
||||
|
||||
const onContextMenuItemClickedEvent: ContextMenuActionEvent<FileInfo> = {
|
||||
selected: function (actionIndex: number | null, value: FileInfo | null): void {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
const payload = (() => {
|
||||
switch(actionIndex) {
|
||||
case 0: {
|
||||
return {
|
||||
destination: "request/all",
|
||||
file: value.fileName,
|
||||
source: `Web UI @ ${window.location.href}`,
|
||||
mode: "FLOW"
|
||||
} as CoordinatorOperationRequest
|
||||
}
|
||||
case 1: {
|
||||
return {
|
||||
destination: "request/encode",
|
||||
file: value.fileName,
|
||||
source: `Web UI @ ${window.location.href}`,
|
||||
mode: "FLOW"
|
||||
} as CoordinatorOperationRequest
|
||||
}
|
||||
case 2: {
|
||||
return {
|
||||
destination: "request/extract",
|
||||
file: value.fileName,
|
||||
source: `Web UI @ ${window.location.href}`,
|
||||
mode: "FLOW"
|
||||
} as CoordinatorOperationRequest
|
||||
}
|
||||
default: {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
})();
|
||||
if (payload) {
|
||||
client?.publish({
|
||||
destination: "/app/"+payload.destination,
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const pullData = () => {
|
||||
client?.publish({
|
||||
@ -99,9 +183,10 @@ export default function UnprocessedFilesPage() {
|
||||
position: "absolute",
|
||||
width: "100%"
|
||||
}}>
|
||||
<MultiListSortedTable items={tableItems ?? []} columns={columns} />
|
||||
<MultiListSortedTable items={tableItems ?? []} columns={columns} onRowClickedEvent={onItemSelectedEvent} />
|
||||
</Box>
|
||||
</Box>
|
||||
<ContextMenu row={selectedRow} actionItems={actionableItems} onContextMenuItemClicked={onContextMenuItemClickedEvent} />
|
||||
|
||||
</>
|
||||
)
|
||||
|
||||
@ -1,24 +1,23 @@
|
||||
import { configureStore, ThunkAction, Action } from '@reduxjs/toolkit';
|
||||
import composedSlice from './store/composed-slice';
|
||||
import explorerSlice from './store/explorer-slice';
|
||||
import kafkaItemsFlatSlice from './store/kafka-items-flat-slice';
|
||||
import contextMenuSlice from './store/context-menu-slice';
|
||||
import persistentEventsSlice from './store/persistent-events-slice';
|
||||
import chainedEventsSlice from './store/chained-events-slice';
|
||||
import unprocessedFilesSlice from './store/unprocessed-files-slice';
|
||||
import tasksSlice from './store/tasks-slice';
|
||||
|
||||
import workSlice from './store/work-slice';
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
composed: composedSlice,
|
||||
explorer: explorerSlice,
|
||||
kafkaComposedFlat: kafkaItemsFlatSlice,
|
||||
contextMenu: contextMenuSlice,
|
||||
persistentEvents: persistentEventsSlice,
|
||||
chained: chainedEventsSlice,
|
||||
unprocessedFiles: unprocessedFilesSlice,
|
||||
tasks: tasksSlice,
|
||||
work: workSlice
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit"
|
||||
import { ExpandableTableItem } from "../features/table/expandableTable";
|
||||
|
||||
export interface EventGroup {
|
||||
export interface EventGroup extends ExpandableTableItem {
|
||||
referenceId: string,
|
||||
created: number,
|
||||
fileName: string|null,
|
||||
|
||||
@ -1,23 +0,0 @@
|
||||
import { PayloadAction, createSlice } from "@reduxjs/toolkit"
|
||||
import { SimpleEventDataObject } from "../../types"
|
||||
|
||||
interface ComposedState {
|
||||
items: Array<SimpleEventDataObject>
|
||||
}
|
||||
|
||||
const initialState: ComposedState = {
|
||||
items: []
|
||||
}
|
||||
|
||||
const kafkaComposedFlat = createSlice({
|
||||
name: "Composed",
|
||||
initialState,
|
||||
reducers: {
|
||||
simpleEventsUpdate(state, action: PayloadAction<Array<SimpleEventDataObject>>) {
|
||||
state.items = action.payload
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export const { simpleEventsUpdate } = kafkaComposedFlat.actions;
|
||||
export default kafkaComposedFlat.reducer;
|
||||
@ -5,7 +5,9 @@ import no.iktdev.eventi.data.eventId
|
||||
import no.iktdev.eventi.data.referenceId
|
||||
import no.iktdev.eventi.data.toJson
|
||||
import no.iktdev.eventi.database.*
|
||||
import no.iktdev.mediaprocessing.shared.common.contract.Events
|
||||
import no.iktdev.mediaprocessing.shared.common.contract.data.Event
|
||||
import no.iktdev.mediaprocessing.shared.common.contract.jsonToEvent
|
||||
import no.iktdev.mediaprocessing.shared.common.database.tables.events
|
||||
import no.iktdev.mediaprocessing.shared.common.database.tables.tasks
|
||||
import no.iktdev.mediaprocessing.shared.common.task.Task
|
||||
@ -195,4 +197,8 @@ fun Query?.toTask(): List<Task> {
|
||||
val dz = TaskDoz()
|
||||
val res = this?.mapNotNull { dz.deserializeTask(it) } ?: emptyList()
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
fun Query?.toEvent(): List<Event> {
|
||||
return this?.mapNotNull { it[events.data].jsonToEvent(it[events.event]) } ?: emptyList()
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user