Update ui

This commit is contained in:
bskjon 2024-08-06 01:15:01 +02:00
parent 9c7e42ae29
commit 4d21d06781
31 changed files with 480 additions and 1219 deletions

2
.idea/gradle.xml generated
View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings"> <component name="GradleSettings">
<option name="linkedExternalProjectsSettings"> <option name="linkedExternalProjectsSettings">
<GradleProjectSettings> <GradleProjectSettings>
@ -14,7 +15,6 @@
<option value="$PROJECT_DIR$/apps/ui" /> <option value="$PROJECT_DIR$/apps/ui" />
<option value="$PROJECT_DIR$/shared" /> <option value="$PROJECT_DIR$/shared" />
<option value="$PROJECT_DIR$/shared/common" /> <option value="$PROJECT_DIR$/shared/common" />
<option value="$PROJECT_DIR$/shared/contract" />
<option value="$PROJECT_DIR$/shared/eventi" /> <option value="$PROJECT_DIR$/shared/eventi" />
</set> </set>
</option> </option>

2
.idea/misc.xml generated
View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="azul-17" project-jdk-type="JavaSDK" /> <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="azul-17" project-jdk-type="JavaSDK" />
</project> </project>

1078
.idea/workspace.xml generated

File diff suppressed because it is too large Load Diff

View File

@ -7,6 +7,7 @@ import no.iktdev.exfl.coroutines.CoroutinesIO
import no.iktdev.exfl.observable.Observables import no.iktdev.exfl.observable.Observables
import no.iktdev.mediaprocessing.shared.common.* import no.iktdev.mediaprocessing.shared.common.*
import no.iktdev.eventi.database.MySqlDataSource import no.iktdev.eventi.database.MySqlDataSource
import no.iktdev.mediaprocessing.shared.common.database.cal.EventsManager
import no.iktdev.mediaprocessing.shared.common.database.cal.RunnerManager import no.iktdev.mediaprocessing.shared.common.database.cal.RunnerManager
import no.iktdev.mediaprocessing.shared.common.database.cal.TasksManager import no.iktdev.mediaprocessing.shared.common.database.cal.TasksManager
import no.iktdev.streamit.library.db.tables.* import no.iktdev.streamit.library.db.tables.*

View File

@ -1,178 +0,0 @@
package no.iktdev.mediaprocessing.coordinator
import no.iktdev.eventi.core.PersistentMessageHelper
import no.iktdev.eventi.data.eventId
import no.iktdev.eventi.data.referenceId
import no.iktdev.eventi.data.toJson
import no.iktdev.eventi.database.DataSource
import no.iktdev.eventi.database.isCausedByDuplicateError
import no.iktdev.eventi.database.isExposedSqlException
import no.iktdev.mediaprocessing.shared.common.database.tables.allEvents
import no.iktdev.mediaprocessing.shared.common.database.tables.events
import no.iktdev.mediaprocessing.shared.common.contract.Events
import no.iktdev.mediaprocessing.shared.common.contract.EventsManagerContract
import no.iktdev.mediaprocessing.shared.common.contract.data.Event
import no.iktdev.mediaprocessing.shared.common.contract.fromJsonWithDeserializer
import org.jetbrains.exposed.exceptions.ExposedSQLException
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
class EventsManager(dataSource: DataSource) : EventsManagerContract(dataSource) {
override fun storeEvent(event: Event): Boolean {
no.iktdev.eventi.database.withTransaction(dataSource.database) {
allEvents.insert {
it[referenceId] = event.referenceId()
it[eventId] = event.eventId()
it[events.event] = event.eventType.event
it[data] = event.toJson()
}
}
val existing = getEventsWith(event.referenceId())
val derivedId = event.metadata.derivedFromEventId
if (derivedId != null) {
val isNewEventOrphan = existing.none { it.eventId() == derivedId }
if (isNewEventOrphan) {
log.warn { "Message not saved! ${event.referenceId()} with eventId(${event.eventId()}) for event ${event.eventType} has derivedEventId($derivedId) which does not exist!" }
return false
}
}
val exception = no.iktdev.eventi.database.executeOrException(dataSource.database) {
events.insert {
it[referenceId] = event.referenceId()
it[eventId] = event.eventId()
it[events.event] = event.eventType.event
it[data] = event.toJson()
}
}
val success = if (exception != null) {
if (exception.isExposedSqlException()) {
if ((exception as ExposedSQLException).isCausedByDuplicateError()) {
log.debug { "Error is of SQLIntegrityConstraintViolationException" }
log.error { exception.message }
exception.printStackTrace()
} else {
log.debug { "Error code is: ${exception.errorCode}" }
log.error { exception.message }
exception.printStackTrace()
}
} else {
log.error { exception.message }
exception.printStackTrace()
}
false
} else {
true
}
if (success) {
//deleteSupersededEvents(referenceId = event.referenceId(), eventId = event.eventId(), event = event.eventType, derivedFromId = event.derivedFromEventId())
}
return success
}
private val exemptedFromSingleEvent = listOf(
Events.EventWorkConvertCreated,
Events.EventWorkExtractCreated,
Events.EventWorkConvertPerformed,
Events.EventWorkExtractPerformed
)
private fun isExempted(event: Events): Boolean {
return event in exemptedFromSingleEvent
}
override fun readAvailableEvents(): List<List<Event>> {
return no.iktdev.eventi.database.withTransaction(dataSource.database) {
events.selectAll()
.groupBy { it[events.referenceId] }
.mapNotNull { it.value.mapNotNull { v -> v.toEvent() } }.filter { it.none { e -> e.eventType == Events.EventMediaProcessCompleted } }
} ?: emptyList()
}
override fun readAvailableEventsFor(referenceId: String): List<Event> {
val events = no.iktdev.eventi.database.withTransaction(dataSource.database) {
events.select { events.referenceId eq referenceId }
.mapNotNull { it.toEvent() }
} ?: emptyList()
return if (events.any { it.eventType == Events.EventMediaProcessCompleted }) emptyList() else events
}
override fun getAllEvents(): List<List<Event>> {
val events = no.iktdev.eventi.database.withTransaction(dataSource.database) {
events.selectAll()
.groupBy { it[events.referenceId] }
.mapNotNull { it.value.mapNotNull { v -> v.toEvent() } }
} ?: emptyList()
return events.filter { it.none { it.eventType == Events.EventMediaProcessCompleted } }
}
override fun getEventsWith(referenceId: String): List<Event> {
return no.iktdev.eventi.database.withTransaction(dataSource.database) {
events.select {
(events.referenceId eq referenceId)
}
.orderBy(events.created, SortOrder.ASC)
.mapNotNull { it.toEvent() }
} ?: emptyList()
}
/**
* @param referenceId Reference
* @param eventId Current eventId for the message, required to prevent deletion of itself
* @param event Current event for the message
*/
private fun deleteSupersededEvents(referenceId: String, eventId: String, event: Events, derivedFromId: String?) {
val forRemoval = mutableListOf<Event>()
val present = getEventsWith(referenceId).filter { it.metadata.derivedFromEventId != null }
val helper = PersistentMessageHelper<Event>(present)
val replaced = if (!isExempted(event)) present.find { it.eventId() != eventId && it.eventType == event } else null
val orphaned = replaced?.let { helper.getEventsRelatedTo(it.eventId()) }?.toMutableSet() ?: mutableSetOf()
//orphaned.addAll(helper.findOrphanedEvents())
forRemoval.addAll(orphaned)
deleteSupersededEvents(forRemoval)
}
/**
* Deletes the events
*/
private fun deleteSupersededEvents(superseded: List<Event>) {
no.iktdev.eventi.database.withTransaction(dataSource) {
superseded.forEach { duplicate ->
events.deleteWhere {
(referenceId eq duplicate.referenceId()) and
(eventId eq duplicate.eventId()) and
(event eq duplicate.eventType.event)
}
}
}
}
private fun ResultRow.toEvent(): Event? {
val kev = try {
Events.toEvent(this[events.event])
} catch (e: IllegalArgumentException) {
e.printStackTrace()
return null
}?: return null
return this[events.data].fromJsonWithDeserializer(kev)
}
}

View File

@ -44,9 +44,8 @@ dependencies {
implementation ("mysql:mysql-connector-java:8.0.29") implementation ("mysql:mysql-connector-java:8.0.29")
implementation("no.iktdev:exfl:0.0.16-SNAPSHOT") implementation("no.iktdev:exfl:0.0.16-SNAPSHOT")
implementation(project(mapOf("path" to ":shared"))) implementation(project(mapOf("path" to ":shared:eventi")))
implementation(project(mapOf("path" to ":shared:common"))) implementation(project(mapOf("path" to ":shared:common")))
implementation(project(mapOf("path" to ":shared:contract")))
testImplementation(platform("org.junit:junit-bom:5.9.1")) testImplementation(platform("org.junit:junit-bom:5.9.1"))

View File

@ -2,9 +2,6 @@ package no.iktdev.mediaprocessing.ui
import no.iktdev.mediaprocessing.shared.common.Defaults import no.iktdev.mediaprocessing.shared.common.Defaults
import no.iktdev.mediaprocessing.shared.common.socket.SocketImplementation import no.iktdev.mediaprocessing.shared.common.socket.SocketImplementation
import no.iktdev.mediaprocessing.shared.kafka.core.CoordinatorProducer
import no.iktdev.mediaprocessing.shared.kafka.core.DefaultMessageListener
import no.iktdev.mediaprocessing.shared.kafka.core.KafkaImplementation
import org.springframework.beans.factory.annotation.Value import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory
import org.springframework.boot.web.server.WebServerFactoryCustomizer import org.springframework.boot.web.server.WebServerFactoryCustomizer
@ -67,8 +64,3 @@ class SocketImplemented: SocketImplementation() {
@Configuration @Configuration
class DefaultConfiguration: Defaults() class DefaultConfiguration: Defaults()
@Configuration
@Import(CoordinatorProducer::class, DefaultMessageListener::class)
class KafkaLocalInit: KafkaImplementation() {
}

View File

@ -2,40 +2,45 @@ package no.iktdev.mediaprocessing.ui
import mu.KotlinLogging import mu.KotlinLogging
import no.iktdev.eventi.database.MySqlDataSource
import no.iktdev.exfl.coroutines.CoroutinesDefault import no.iktdev.exfl.coroutines.CoroutinesDefault
import no.iktdev.exfl.coroutines.CoroutinesIO import no.iktdev.exfl.coroutines.CoroutinesIO
import no.iktdev.exfl.observable.ObservableMap import no.iktdev.exfl.observable.ObservableMap
import no.iktdev.exfl.observable.Observables import no.iktdev.exfl.observable.Observables
import no.iktdev.exfl.observable.observableMapOf import no.iktdev.exfl.observable.observableMapOf
import no.iktdev.mediaprocessing.shared.common.DatabaseEnvConfig import no.iktdev.mediaprocessing.shared.common.DatabaseEnvConfig
import no.iktdev.mediaprocessing.shared.common.datasource.MySqlDataSource import no.iktdev.mediaprocessing.shared.common.database.EventsDatabase
import no.iktdev.mediaprocessing.shared.common.persistance.PersistentDataStore import no.iktdev.mediaprocessing.shared.common.database.cal.EventsManager
import no.iktdev.mediaprocessing.shared.common.database.cal.TasksManager
import no.iktdev.mediaprocessing.shared.common.toEventsDatabase import no.iktdev.mediaprocessing.shared.common.toEventsDatabase
import no.iktdev.mediaprocessing.ui.dto.ExplorerItem import no.iktdev.mediaprocessing.ui.dto.explore.ExplorerItem
import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication import org.springframework.boot.runApplication
import org.springframework.context.ApplicationContext import org.springframework.context.ApplicationContext
import java.util.concurrent.CountDownLatch import org.springframework.context.annotation.Bean
private val logger = KotlinLogging.logger {} private val logger = KotlinLogging.logger {}
val ioCoroutine = CoroutinesIO() val ioCoroutine = CoroutinesIO()
val defaultCoroutine = CoroutinesDefault() val defaultCoroutine = CoroutinesDefault()
lateinit var eventsManager: EventsManager
@SpringBootApplication @SpringBootApplication
class UIApplication { class UIApplication {
@Bean
fun eventManager(): EventsManager {
return eventsManager
}
} }
private lateinit var eventsDatabase: MySqlDataSource private lateinit var eventsDatabase: EventsDatabase
fun getEventsDatabase(): MySqlDataSource {
return eventsDatabase lateinit var taskManager: TasksManager
}
lateinit var persistentReader: PersistentDataReader
lateinit var persistentWriter: PersistentDataStore
private var context: ApplicationContext? = null private var context: ApplicationContext? = null
private val kafkaClearedLatch = CountDownLatch(1)
@Suppress("unused") @Suppress("unused")
fun getContext(): ApplicationContext? { fun getContext(): ApplicationContext? {
@ -46,11 +51,10 @@ val fileRegister: ObservableMap<String, ExplorerItem> = observableMapOf()
fun main(args: Array<String>) { fun main(args: Array<String>) {
eventsDatabase = DatabaseEnvConfig.toEventsDatabase() eventsDatabase = EventsDatabase().also {
eventsDatabase.connect() eventsManager = EventsManager(it.database)
}
persistentReader = PersistentDataReader(eventsDatabase)
persistentWriter = PersistentDataStore(eventsDatabase)
ioCoroutine.addListener(listener = object: Observables.ObservableValue.ValueListener<Throwable> { ioCoroutine.addListener(listener = object: Observables.ObservableValue.ValueListener<Throwable> {
@ -63,32 +67,6 @@ fun main(args: Array<String>) {
value.printStackTrace() value.printStackTrace()
} }
}) })
try {
/*val admincli = AdminClient.create(mapOf(
AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG to KafkaEnv.servers,
AdminClientConfig.REQUEST_TIMEOUT_MS_CONFIG to "1000",
AdminClientConfig.DEFAULT_API_TIMEOUT_MS_CONFIG to "5000"
))
val go = admincli.listConsumerGroupOffsets("${KafkaEnv.consumerId}:UIDataComposer")
go.partitionsToOffsetAndMetadata().whenComplete { result, throwable ->
val partitions = result.entries.filter { it.key.topic() == SharedConfig.kafkaTopic }
.map { it.key }
val deleteResult = admincli.deleteConsumerGroupOffsets("${KafkaEnv.consumerId}:UIDataComposer", partitions.toSet())
deleteResult.all().whenComplete { result, throwable ->
kafkaClearedLatch.countDown()
}
}*/
} catch (e: Exception) {
e.printStackTrace()
// kafkaClearedLatch.countDown()
}
// logger.info { "Waiting for kafka to clear offset!" }
// kafkaClearedLatch.await(5, TimeUnit.MINUTES)
// logger.info { "Offset cleared!" }
// Thread.sleep(10000)
context = runApplication<UIApplication>(*args) context = runApplication<UIApplication>(*args)
} }

View File

@ -3,8 +3,6 @@ package no.iktdev.mediaprocessing.ui
import java.io.File import java.io.File
object UIEnv { object UIEnv {
var storedContent: File = if (!System.getenv("DIRECTORY_CONTENT_STORED").isNullOrBlank()) File(System.getenv("DIRECTORY_CONTENT_STORED")) else File("/src/output")
val socketEncoder: String = if (System.getenv("EncoderWs").isNullOrBlank()) System.getenv("EncoderWs") else "ws://encoder:8080" val socketEncoder: String = if (System.getenv("EncoderWs").isNullOrBlank()) System.getenv("EncoderWs") else "ws://encoder:8080"
val coordinatorUrl: String = if (System.getenv("Coordinator").isNullOrBlank()) System.getenv("Coordinator") else "http://coordinator" val coordinatorUrl: String = if (System.getenv("Coordinator").isNullOrBlank()) System.getenv("Coordinator") else "http://coordinator"
} }

View File

@ -0,0 +1,7 @@
package no.iktdev.mediaprocessing.ui.dto
data class EventChain(
val eventId: String,
val eventName: String,
val elements: MutableList<EventChain> = mutableListOf()
)

View File

@ -1,12 +1,12 @@
package no.iktdev.mediaprocessing.ui.dto package no.iktdev.mediaprocessing.ui.dto
import no.iktdev.mediaprocessing.shared.kafka.core.KafkaEvents import no.iktdev.mediaprocessing.shared.common.contract.Events
data class EventSummary( data class EventSummary(
val referenceId: String, val referenceId: String,
val baseName: String? = null, val baseName: String? = null,
val collection: String? = null, val collection: String? = null,
val events: List<KafkaEvents> = emptyList(), val events: List<Events> = emptyList(),
val status: SummaryState = SummaryState.Started, val status: SummaryState = SummaryState.Started,
val activeEvens: Map<String, EventSummarySubItem> val activeEvens: Map<String, EventSummarySubItem>
) )

View File

@ -1,4 +1,4 @@
package no.iktdev.mediaprocessing.ui.dto package no.iktdev.mediaprocessing.ui.dto.explore
interface ExplorerAttr { interface ExplorerAttr {
val created: Long val created: Long

View File

@ -1,4 +1,4 @@
package no.iktdev.mediaprocessing.ui.dto package no.iktdev.mediaprocessing.ui.dto.explore
data class ExplorerCursor ( data class ExplorerCursor (
val name: String, val name: String,

View File

@ -1,11 +1,10 @@
package no.iktdev.mediaprocessing.ui.explorer package no.iktdev.mediaprocessing.ui.explorer
import no.iktdev.mediaprocessing.shared.common.SharedConfig import no.iktdev.mediaprocessing.shared.common.SharedConfig
import no.iktdev.mediaprocessing.ui.UIEnv import no.iktdev.mediaprocessing.ui.dto.explore.ExplorerAttributes
import no.iktdev.mediaprocessing.ui.dto.ExplorerAttributes import no.iktdev.mediaprocessing.ui.dto.explore.ExplorerCursor
import no.iktdev.mediaprocessing.ui.dto.ExplorerCursor import no.iktdev.mediaprocessing.ui.dto.explore.ExplorerItem
import no.iktdev.mediaprocessing.ui.dto.ExplorerItem import no.iktdev.mediaprocessing.ui.dto.explore.ExplorerItemType
import no.iktdev.mediaprocessing.ui.dto.ExplorerItemType
import java.io.File import java.io.File
import java.io.FileFilter import java.io.FileFilter
import java.nio.file.Files import java.nio.file.Files

View File

@ -0,0 +1,23 @@
package no.iktdev.mediaprocessing.ui.service
import no.iktdev.mediaprocessing.shared.common.database.cal.EventsManager
import no.iktdev.mediaprocessing.ui.eventsManager
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.scheduling.annotation.EnableScheduling
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Service
@Service
@EnableScheduling
class AvailableEventsService(
@Autowired eventsManager: EventsManager
) {
fun pullAvailableEvents() {
eventsManager.readAvailableEvents().onEach {
}
}
}

View File

@ -0,0 +1,14 @@
package no.iktdev.mediaprocessing.ui.service
import no.iktdev.mediaprocessing.shared.common.database.cal.EventsManager
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.scheduling.annotation.EnableScheduling
import org.springframework.stereotype.Service
@Service
@EnableScheduling
class CompletedEventsService(
@Autowired eventsManager: EventsManager
) {
}

View File

@ -0,0 +1,49 @@
package no.iktdev.mediaprocessing.ui.service
import no.iktdev.eventi.data.derivedFromEventId
import no.iktdev.eventi.data.eventId
import no.iktdev.eventi.data.referenceId
import no.iktdev.mediaprocessing.shared.common.contract.data.Event
import no.iktdev.mediaprocessing.shared.common.database.cal.EventsManager
import no.iktdev.mediaprocessing.ui.dto.EventChain
import no.iktdev.mediaprocessing.ui.eventsManager
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.scheduling.annotation.EnableScheduling
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Service
@Service
@EnableScheduling
class EventExecutionOrderService(
@Autowired eventsManager: EventsManager
) {
val collections: MutableMap<String, List<EventChain>> = mutableMapOf()
@Scheduled(fixedDelay = 5_000)
fun pullAvailableEvents() {
eventsManager.getAllEvents().onEach { events ->
collections[events.first().referenceId()] = events.chained()
}
}
fun List<Event>.chained(): List<EventChain> {
val eventMap = this.associateBy { it.eventId() }
val chains = mutableMapOf<String, EventChain>()
this.forEach { event ->
val chain = EventChain(eventId = event.eventId(), eventName = event.eventType.name)
chains[event.eventId()] = chain
if (event.derivedFromEventId() != null && eventMap.containsKey(event.derivedFromEventId())) {
val parentChain = chains[event.derivedFromEventId()]
parentChain?.elements?.add(chain)
}
}
return chains.values.filter { it.elements.isNotEmpty() }.toList()
}
}

View File

@ -0,0 +1,49 @@
package no.iktdev.mediaprocessing.ui.socket
import no.iktdev.eventi.data.derivedFromEventId
import no.iktdev.eventi.data.eventId
import no.iktdev.eventi.data.referenceId
import no.iktdev.mediaprocessing.shared.common.contract.data.Event
import no.iktdev.mediaprocessing.ui.dto.EventChain
import no.iktdev.mediaprocessing.ui.eventsManager
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.messaging.handler.annotation.MessageMapping
import org.springframework.messaging.simp.SimpMessagingTemplate
import org.springframework.stereotype.Controller
@Controller
class ChainedEventsTopic(
@Autowired private val template: SimpMessagingTemplate?
) {
@MessageMapping("/chained/all")
fun sendAllChainedEvents() {
val collections: MutableMap<String, List<EventChain>> = mutableMapOf()
eventsManager.getAllEvents().onEach { events ->
collections[events.first().referenceId()] = events.chained()
}
template?.convertAndSend("/topic/chained/all",collections)
}
fun List<Event>.chained(): List<EventChain> {
val eventMap = this.associateBy { it.eventId() }
val chains = mutableMapOf<String, EventChain>()
val children = mutableSetOf<String>()
this.forEach { event ->
val eventId = event.metadata.eventId
val derivedFromEventId = event.metadata.derivedFromEventId
val chain = chains.getOrPut(eventId) { EventChain(eventId, event.eventType.toString()) }
if (derivedFromEventId != null && eventMap.containsKey(derivedFromEventId)) {
val parentChain = chains.getOrPut(derivedFromEventId) {
EventChain(derivedFromEventId, eventMap[derivedFromEventId]!!.eventType.toString())
}
parentChain.elements.add(chain)
children.add(eventId)
}
}
return chains.values.filter { it.eventId !in children }
}
}

View File

@ -8,12 +8,12 @@ import org.springframework.stereotype.Controller
@Controller @Controller
class EventsTableTopic( class EventsTableTopic(
@Autowired private val template: SimpMessagingTemplate?, @Autowired private val template: SimpMessagingTemplate?,
@Autowired private val persistentEventsTableService: PersistentEventsTableService //@Autowired private val persistentEventsTableService: PersistentEventsTableService
): TopicSupport() { ) {
@MessageMapping("/persistent/events") @MessageMapping("/persistent/events")
fun readbackEvents() { fun readbackEvents() {
template?.convertAndSend("/topic/persistent/events", persistentEventsTableService.cachedEvents) //template?.convertAndSend("/topic/persistent/events", persistentEventsTableService.cachedEvents)
} }
} }

View File

@ -1,7 +1,7 @@
package no.iktdev.mediaprocessing.ui.socket package no.iktdev.mediaprocessing.ui.socket
import mu.KotlinLogging import mu.KotlinLogging
import no.iktdev.mediaprocessing.shared.contract.dto.EventRequest import no.iktdev.mediaprocessing.shared.common.contract.dto.EventRequest
import no.iktdev.mediaprocessing.ui.UIEnv import no.iktdev.mediaprocessing.ui.UIEnv
import no.iktdev.mediaprocessing.ui.explorer.ExplorerCore import no.iktdev.mediaprocessing.ui.explorer.ExplorerCore
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
@ -17,7 +17,7 @@ class ExplorerTopic(
@Autowired private val template: SimpMessagingTemplate?, @Autowired private val template: SimpMessagingTemplate?,
@Autowired private val coordinatorTemplate: RestTemplate, @Autowired private val coordinatorTemplate: RestTemplate,
val explorer: ExplorerCore = ExplorerCore() val explorer: ExplorerCore = ExplorerCore()
): TopicSupport() { ) {
@MessageMapping("/explorer/home") @MessageMapping("/explorer/home")
fun goHome() { fun goHome() {

View File

@ -1,10 +0,0 @@
package no.iktdev.mediaprocessing.ui.socket
import com.google.gson.Gson
abstract class TopicSupport {
fun toJson(item: Any?): String? {
return if (item != null) Gson().toJson(item) else null
}
}

View File

@ -30,6 +30,7 @@
"react-scripts": "5.0.1", "react-scripts": "5.0.1",
"react-stomp": "^5.1.0", "react-stomp": "^5.1.0",
"react-stomp-hooks": "^2.1.0", "react-stomp-hooks": "^2.1.0",
"react-tree-graph": "^8.0.2",
"react-use-websocket": "^4.4.0", "react-use-websocket": "^4.4.0",
"redux": "^4.2.1", "redux": "^4.2.1",
"sockjs-client": "^1.6.1", "sockjs-client": "^1.6.1",
@ -2084,9 +2085,9 @@
"integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA=="
}, },
"node_modules/@babel/runtime": { "node_modules/@babel/runtime": {
"version": "7.22.15", "version": "7.25.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.15.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz",
"integrity": "sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==", "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==",
"dependencies": { "dependencies": {
"regenerator-runtime": "^0.14.0" "regenerator-runtime": "^0.14.0"
}, },
@ -7142,6 +7143,16 @@
"type": "^1.0.1" "type": "^1.0.1"
} }
}, },
"node_modules/d3-ease": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-2.0.0.tgz",
"integrity": "sha512-68/n9JWarxXkOWMshcT5IcjbB+agblQUaIsbnXmrzejn2O82n3p2A9R2zEB9HIEFWKFwPAEDDN8gR0VdSAyyAQ=="
},
"node_modules/d3-hierarchy": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-2.0.0.tgz",
"integrity": "sha512-SwIdqM3HxQX2214EG9GTjgmCc/mbSx4mQBn+DuEETubhOw6/U3fmnji4uCVrmzOydMHSO1nZle5gh6HB/wdOzw=="
},
"node_modules/damerau-levenshtein": { "node_modules/damerau-levenshtein": {
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
@ -15370,6 +15381,20 @@
"react-dom": ">=16.6.0" "react-dom": ">=16.6.0"
} }
}, },
"node_modules/react-tree-graph": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/react-tree-graph/-/react-tree-graph-8.0.2.tgz",
"integrity": "sha512-w3DWXisWvAvESAKO5WMCSIdqUb+LLXKX32ePwuVab98QUDUjrwoHSGsWG10ip6bsxuCyUTmP0YXwNyI8fSiOaQ==",
"dependencies": {
"@babel/runtime": "^7.24.5",
"d3-ease": "^2.0.0",
"d3-hierarchy": "^2.0.0",
"prop-types": "^15.8.1"
},
"peerDependencies": {
"react": "^16.8 || ^17 || ^18 || ^19"
}
},
"node_modules/react-use-websocket": { "node_modules/react-use-websocket": {
"version": "4.4.0", "version": "4.4.0",
"resolved": "https://registry.npmjs.org/react-use-websocket/-/react-use-websocket-4.4.0.tgz", "resolved": "https://registry.npmjs.org/react-use-websocket/-/react-use-websocket-4.4.0.tgz",
@ -19838,9 +19863,9 @@
"integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA=="
}, },
"@babel/runtime": { "@babel/runtime": {
"version": "7.22.15", "version": "7.25.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.15.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz",
"integrity": "sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==", "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==",
"requires": { "requires": {
"regenerator-runtime": "^0.14.0" "regenerator-runtime": "^0.14.0"
}, },
@ -23432,6 +23457,16 @@
"type": "^1.0.1" "type": "^1.0.1"
} }
}, },
"d3-ease": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-2.0.0.tgz",
"integrity": "sha512-68/n9JWarxXkOWMshcT5IcjbB+agblQUaIsbnXmrzejn2O82n3p2A9R2zEB9HIEFWKFwPAEDDN8gR0VdSAyyAQ=="
},
"d3-hierarchy": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-2.0.0.tgz",
"integrity": "sha512-SwIdqM3HxQX2214EG9GTjgmCc/mbSx4mQBn+DuEETubhOw6/U3fmnji4uCVrmzOydMHSO1nZle5gh6HB/wdOzw=="
},
"damerau-levenshtein": { "damerau-levenshtein": {
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
@ -29215,6 +29250,17 @@
"prop-types": "^15.6.2" "prop-types": "^15.6.2"
} }
}, },
"react-tree-graph": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/react-tree-graph/-/react-tree-graph-8.0.2.tgz",
"integrity": "sha512-w3DWXisWvAvESAKO5WMCSIdqUb+LLXKX32ePwuVab98QUDUjrwoHSGsWG10ip6bsxuCyUTmP0YXwNyI8fSiOaQ==",
"requires": {
"@babel/runtime": "^7.24.5",
"d3-ease": "^2.0.0",
"d3-hierarchy": "^2.0.0",
"prop-types": "^15.8.1"
}
},
"react-use-websocket": { "react-use-websocket": {
"version": "4.4.0", "version": "4.4.0",
"resolved": "https://registry.npmjs.org/react-use-websocket/-/react-use-websocket-4.4.0.tgz", "resolved": "https://registry.npmjs.org/react-use-websocket/-/react-use-websocket-4.4.0.tgz",

View File

@ -25,6 +25,7 @@
"react-scripts": "5.0.1", "react-scripts": "5.0.1",
"react-stomp": "^5.1.0", "react-stomp": "^5.1.0",
"react-stomp-hooks": "^2.1.0", "react-stomp-hooks": "^2.1.0",
"react-tree-graph": "^8.0.2",
"react-use-websocket": "^4.4.0", "react-use-websocket": "^4.4.0",
"redux": "^4.2.1", "redux": "^4.2.1",
"sockjs-client": "^1.6.1", "sockjs-client": "^1.6.1",

View File

@ -14,6 +14,7 @@ import { ThemeProvider } from '@mui/material';
import theme from './theme'; import theme from './theme';
import { simpleEventsUpdate } from './app/store/kafka-items-flat-slice'; import { simpleEventsUpdate } from './app/store/kafka-items-flat-slice';
import { EventDataObject, SimpleEventDataObject } from './types'; import { EventDataObject, SimpleEventDataObject } from './types';
import EventsChainPage from './app/page/EventsChainPage';
function App() { function App() {
const client = useStompClient(); const client = useStompClient();
@ -62,6 +63,7 @@ function App() {
<BrowserRouter> <BrowserRouter>
<Routes> <Routes>
<Route path='/files' element={<ExplorePage />} /> <Route path='/files' element={<ExplorePage />} />
<Route path='/events' element={<EventsChainPage />} />
<Route path='/' element={<LaunchPage />} /> <Route path='/' element={<LaunchPage />} />
</Routes> </Routes>
<Footer /> <Footer />

View File

@ -0,0 +1,58 @@
import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useStompClient } from 'react-stomp-hooks';
import { RootState } from "../store";
import { useWsSubscription } from "../ws/subscriptions";
import { set } from "../store/persistent-events-slice";
import { EventsObjectListResponse } from "../../types";
import IconRefresh from '@mui/icons-material/Refresh'
import { Button } from "@mui/material";
export default function EventsChainPage() {
const dispatch = useDispatch();
const client = useStompClient();
const cursor = useSelector((state: RootState) => state.persistentEvents)
function log(data: any) {
console.log(data)
}
useWsSubscription("/topic/chained/all", (response) => {
console.log(response)
});
useEffect(() => {
// Kjør din funksjon her når komponenten lastes inn for første gang
// Sjekk om cursor er null
if (cursor.items === null && client !== null) {
console.log(cursor)
// Kjør din funksjon her når cursor er null og client ikke er null
client?.publish({
destination: "/app/chained/all"
});
// Alternativt, du kan dispatche en Redux handling her
// dispatch(fetchDataAction()); // Eksempel på å dispatche en handling
}
}, [cursor, client, dispatch]);
const onRefresh = () => {
client?.publish({
"destination": "/app/chained/all",
"body": "Potato"
})
}
return (
<>
<Button
startIcon={ <IconRefresh /> }
onClick={onRefresh} sx={{
borderRadius: 5,
textTransform: 'none'
}}>Refresh</Button >
</>
)
}

View File

@ -58,6 +58,7 @@ function getSegmentedNaviagatablePath(navigateTo: (path: string | null) => void,
console.log(path); console.log(path);
const parts: Array<string> = path?.split(/\\|\//).map((value: string, index: number) => value.replaceAll(":", "")) ?? []; const parts: Array<string> = path?.split(/\\|\//).map((value: string, index: number) => value.replaceAll(":", "")) ?? [];
const segments = parts.map((name: string, index: number) => { const segments = parts.map((name: string, index: number) => {
console.log(name)
return ( return (
<Box key={index} sx={{ <Box key={index} sx={{
display: "flex", display: "flex",
@ -65,7 +66,8 @@ function getSegmentedNaviagatablePath(navigateTo: (path: string | null) => void,
alignItems: "center" alignItems: "center"
}}> }}>
<Button sx={{ <Button sx={{
borderRadius: 5 borderRadius: 5,
textTransform: 'none'
}} onClick={() => navigateTo(getPartFor(path!, index))}> }} onClick={() => navigateTo(getPartFor(path!, index))}>
<Typography>{name}</Typography> <Typography>{name}</Typography>
</Button> </Button>

View File

@ -3,9 +3,10 @@ import SimpleTable, { TableCellCustomizer, TablePropetyConfig } from "../feature
import { RootState } from "../store"; import { RootState } from "../store";
import { useEffect } from "react"; import { useEffect } from "react";
import { useStompClient } from "react-stomp-hooks"; import { useStompClient } from "react-stomp-hooks";
import { Box, Button, useTheme } from "@mui/material"; import { Box, Button, IconButton, Typography, useTheme } from "@mui/material";
import IconRefresh from '@mui/icons-material/Refresh' import IconRefresh from '@mui/icons-material/Refresh'
import IconCompleted from '@mui/icons-material/Check'
import IconWorking from '@mui/icons-material/Engineering';
const columns: Array<TablePropetyConfig> = [ const columns: Array<TablePropetyConfig> = [
{ label: "Title", accessor: "givenTitle" }, { label: "Title", accessor: "givenTitle" },
@ -53,11 +54,24 @@ export default function LaunchPage() {
<Box sx={{ <Box sx={{
display: "flex", display: "flex",
}}> }}>
<Button onClick={onRefresh} sx={{ <Button
borderRadius: 5 startIcon={ <IconRefresh /> }
}}> onClick={onRefresh} sx={{
<IconRefresh /> borderRadius: 5,
</Button> textTransform: 'none'
}}>Refresh</Button >
<Button
startIcon={ <IconCompleted /> }
onClick={onRefresh} sx={{
borderRadius: 5,
textTransform: 'none'
}}>Completed</Button >
<Button
startIcon={ <IconWorking /> }
onClick={onRefresh} sx={{
borderRadius: 5,
textTransform: 'none'
}}>Working</Button >
</Box> </Box>
</Box> </Box>
<Box sx={{ <Box sx={{

View File

@ -27,7 +27,7 @@ object EventToClazzTable {
Events.EventMediaReadStreamPerformed to MediaFileStreamsReadEvent::class.java, Events.EventMediaReadStreamPerformed to MediaFileStreamsReadEvent::class.java,
Events.EventMediaMetadataSearchPerformed to MediaMetadataReceivedEvent::class.java, Events.EventMediaMetadataSearchPerformed to MediaMetadataReceivedEvent::class.java,
Events.EventMediaReadOutNameAndType to MediaOutInformationConstructedEvent::class.java, Events.EventMediaReadOutNameAndType to MediaOutInformationConstructedEvent::class.java,
Events.EventMediaWorkProceedPermitted to no.iktdev.mediaprocessing.shared.common.contract.data.PermitWorkCreationEvent::class.java, Events.EventMediaWorkProceedPermitted to PermitWorkCreationEvent::class.java,
Events.EventMediaProcessCompleted to MediaProcessCompletedEvent::class.java Events.EventMediaProcessCompleted to MediaProcessCompletedEvent::class.java
) )

View File

@ -0,0 +1,25 @@
package no.iktdev.mediaprocessing.shared.common.database
import no.iktdev.mediaprocessing.shared.common.DatabaseEnvConfig
import no.iktdev.mediaprocessing.shared.common.database.tables.allEvents
import no.iktdev.mediaprocessing.shared.common.database.tables.events
import no.iktdev.mediaprocessing.shared.common.database.tables.runners
import no.iktdev.mediaprocessing.shared.common.database.tables.tasks
import no.iktdev.mediaprocessing.shared.common.toEventsDatabase
class EventsDatabase() {
val database = DatabaseEnvConfig.toEventsDatabase()
val tables = listOf(
events, // For kafka
allEvents,
tasks,
runners
)
init {
database.createDatabase()
database.createTables(*tables.toTypedArray())
}
}

View File

@ -8,20 +8,19 @@ import no.iktdev.eventi.data.toJson
import no.iktdev.eventi.database.DataSource import no.iktdev.eventi.database.DataSource
import no.iktdev.eventi.database.isCausedByDuplicateError import no.iktdev.eventi.database.isCausedByDuplicateError
import no.iktdev.eventi.database.isExposedSqlException import no.iktdev.eventi.database.isExposedSqlException
import no.iktdev.eventi.implementations.EventsManagerImpl
import no.iktdev.mediaprocessing.shared.common.database.tables.allEvents import no.iktdev.mediaprocessing.shared.common.database.tables.allEvents
import no.iktdev.mediaprocessing.shared.common.database.tables.events import no.iktdev.mediaprocessing.shared.common.database.tables.events
import no.iktdev.mediaprocessing.shared.common.contract.Events import no.iktdev.mediaprocessing.shared.common.contract.Events
import no.iktdev.mediaprocessing.shared.common.contract.EventsManagerContract
import no.iktdev.mediaprocessing.shared.common.contract.data.Event import no.iktdev.mediaprocessing.shared.common.contract.data.Event
import no.iktdev.mediaprocessing.shared.common.contract.fromJsonWithDeserializer import no.iktdev.mediaprocessing.shared.common.contract.fromJsonWithDeserializer
import org.jetbrains.exposed.exceptions.ExposedSQLException import org.jetbrains.exposed.exceptions.ExposedSQLException
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
open class EventsManager(dataSource: DataSource) : EventsManagerContract(dataSource) { class EventsManager(dataSource: DataSource) : EventsManagerImpl<Event>(dataSource) {
val log = KotlinLogging.logger {} val log = KotlinLogging.logger {}
override fun storeEvent(event: Event): Boolean { override fun storeEvent(event: Event): Boolean {
no.iktdev.eventi.database.withTransaction(dataSource.database) { no.iktdev.eventi.database.withTransaction(dataSource.database) {
@ -114,7 +113,7 @@ open class EventsManager(dataSource: DataSource) : EventsManagerContract(dataSou
.groupBy { it[events.referenceId] } .groupBy { it[events.referenceId] }
.mapNotNull { it.value.mapNotNull { v -> v.toEvent() } } .mapNotNull { it.value.mapNotNull { v -> v.toEvent() } }
} ?: emptyList() } ?: emptyList()
return events.filter { it.none { it.eventType == Events.EventMediaProcessCompleted } } return events
} }

View File

@ -185,7 +185,6 @@ abstract class EventCoordinator<T : EventImpl, E : EventsManagerImpl<T>> {
} }
} }
// TODO: Ikke implementert enda
enum class ActiveMode { enum class ActiveMode {
Active, Active,
Passive Passive