|
|
|
@@ -24,6 +24,10 @@ open class ClientPresenceService( |
|
|
|
@Value("\${fpsms.client-presence.history-max-range-days:30}") |
|
|
|
private var historyMaxRangeDays: Long = 30 |
|
|
|
|
|
|
|
/** Omit from live monitor and purge rows with no heartbeat for longer than this. */ |
|
|
|
@Value("\${fpsms.client-presence.live-presence-retention-days:7}") |
|
|
|
private var livePresenceRetentionDays: Long = 7 |
|
|
|
|
|
|
|
data class HeartbeatRequest( |
|
|
|
val deviceId: String, |
|
|
|
val displayName: String? = null, |
|
|
|
@@ -201,13 +205,16 @@ open class ClientPresenceService( |
|
|
|
mapOf("cutoff" to cutoff), |
|
|
|
) |
|
|
|
for (row in rows) { |
|
|
|
maybeRecordEvent(row, "offline", deriveStatus(row, now), now) |
|
|
|
val lastHb = parseDateTime(row["lastHeartbeat"]) |
|
|
|
val statusBeforeOffline = if (lastHb != null) deriveStatus(row, lastHb) else null |
|
|
|
maybeRecordEvent(row, "offline", statusBeforeOffline, now) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@Transactional(readOnly = true) |
|
|
|
open fun listActive(clientTypeFilter: String?): List<Map<String, Any>> { |
|
|
|
val type = clientTypeFilter?.trim()?.lowercase()?.takeIf { it.isNotBlank() && it != "all" } |
|
|
|
val minHeartbeat = LocalDateTime.now().minusDays(livePresenceRetentionDays.coerceAtLeast(1)) |
|
|
|
val sql = buildString { |
|
|
|
append( |
|
|
|
""" |
|
|
|
@@ -226,7 +233,7 @@ open class ClientPresenceService( |
|
|
|
last_heartbeat AS lastHeartbeat, |
|
|
|
last_activity AS lastActivity |
|
|
|
FROM client_presence |
|
|
|
WHERE 1=1 |
|
|
|
WHERE last_heartbeat >= :minHeartbeat |
|
|
|
""".trimIndent(), |
|
|
|
) |
|
|
|
if (type != null) { |
|
|
|
@@ -234,12 +241,28 @@ open class ClientPresenceService( |
|
|
|
} |
|
|
|
append(" ORDER BY last_heartbeat DESC") |
|
|
|
} |
|
|
|
val args = if (type != null) mapOf("clientType" to type) else emptyMap<String, Any>() |
|
|
|
val args = mutableMapOf<String, Any>("minHeartbeat" to minHeartbeat) |
|
|
|
if (type != null) { |
|
|
|
args["clientType"] = type |
|
|
|
} |
|
|
|
val rows = jdbcDao.queryForList(sql, args) |
|
|
|
val now = LocalDateTime.now() |
|
|
|
return rows.map { row -> enrichWithStatus(row, now) } |
|
|
|
} |
|
|
|
|
|
|
|
@Transactional |
|
|
|
open fun purgeStalePresence(): Int { |
|
|
|
val days = livePresenceRetentionDays.coerceAtLeast(1) |
|
|
|
val cutoff = LocalDateTime.now().minusDays(days) |
|
|
|
return jdbcDao.executeUpdate( |
|
|
|
""" |
|
|
|
DELETE FROM client_presence |
|
|
|
WHERE last_heartbeat < :cutoff |
|
|
|
""".trimIndent(), |
|
|
|
mapOf("cutoff" to cutoff), |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
@Transactional(readOnly = true) |
|
|
|
open fun listHistory( |
|
|
|
from: LocalDateTime, |
|
|
|
|