Skip to content

Commit a485b64

Browse files
authored
show incidents on monitor details (#233)
* show incidents on monitor details * make detekt happy
1 parent 03ad7d9 commit a485b64

File tree

10 files changed

+249
-222
lines changed

10 files changed

+249
-222
lines changed

app/src/main/kotlin/com/kuvaszuptime/kuvasz/controllers/ui/WebUIController.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ class WebUIController(
6969
@ExecuteOn(TaskExecutors.IO)
7070
fun incidents(@QueryValue period: Duration?): String {
7171
val effectivePeriod = period ?: Duration.ofDays(UIDefaults.INCIDENTS_PERIOD_DAYS)
72-
return renderIncidents(
72+
return renderIncidentsPage(
7373
globals = appGlobals,
7474
period = effectivePeriod,
7575
incidents = incidentRepository.getIncidents(

app/src/main/kotlin/com/kuvaszuptime/kuvasz/controllers/ui/WebUIHttpMonitorController.kt

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import com.kuvaszuptime.kuvasz.jooq.enums.SslStatus
55
import com.kuvaszuptime.kuvasz.jooq.enums.UptimeStatus
66
import com.kuvaszuptime.kuvasz.jooq.tables.HttpMonitor.HTTP_MONITOR
77
import com.kuvaszuptime.kuvasz.repositories.HttpMonitorRepository
8+
import com.kuvaszuptime.kuvasz.repositories.IncidentRepository
89
import com.kuvaszuptime.kuvasz.security.ui.WebSecured
910
import com.kuvaszuptime.kuvasz.services.StatCalculator
1011
import com.kuvaszuptime.kuvasz.services.check.http.HttpMonitorCrudService
@@ -29,13 +30,9 @@ class WebUIHttpMonitorController(
2930
private val appGlobals: AppGlobals,
3031
private val statCalculator: StatCalculator,
3132
private val monitorRepository: HttpMonitorRepository,
33+
private val incidentRepository: IncidentRepository,
3234
) {
3335

34-
companion object {
35-
private const val SSL_EVENTS_COUNT = 5
36-
private const val UPTIME_EVENTS_COUNT = 5
37-
}
38-
3936
@Get("/http-monitors/fragments/stats")
4037
@WebSecured
4138
@ExecuteOn(TaskExecutors.IO)
@@ -111,27 +108,33 @@ class WebUIHttpMonitorController(
111108
}
112109
}
113110

114-
@Get("/http-monitors/fragments/details-uptime-events/{monitorId}")
111+
@Get("/http-monitors/fragments/details-uptime-incidents/{monitorId}")
115112
@WebSecured
116113
@ExecuteOn(TaskExecutors.IO)
117114
@Produces(MediaType.TEXT_HTML)
118-
fun httpMonitorUptimeEvents(@PathVariable monitorId: Long) =
115+
fun httpMonitorUptimeIncidents(@PathVariable monitorId: Long) =
119116
monitorRepository.findById(monitorId)?.let { monitor ->
120-
renderHttpUptimeEvents(
121-
isMonitorEnabled = monitor.enabled,
122-
events = monitorCrudService.getUptimeEventsByMonitorId(monitorId, UPTIME_EVENTS_COUNT)
117+
renderIncidents(
118+
incidents = incidentRepository.getHttpUptimeIncidents(
119+
monitor.id,
120+
period = Duration.ofDays(UIDefaults.INCIDENTS_PERIOD_DAYS),
121+
includeResolved = true,
122+
)
123123
)
124124
}
125125

126-
@Get("/http-monitors/fragments/details-ssl-events/{monitorId}")
126+
@Get("/http-monitors/fragments/details-ssl-incidents/{monitorId}")
127127
@WebSecured
128128
@ExecuteOn(TaskExecutors.IO)
129129
@Produces(MediaType.TEXT_HTML)
130-
fun httpMonitorSSLEvents(@PathVariable monitorId: Long) =
130+
fun httpMonitorSSLIncidents(@PathVariable monitorId: Long) =
131131
monitorRepository.findById(monitorId)?.let { monitor ->
132-
renderSSLEvents(
133-
isSSLCheckEnabled = monitor.enabled && monitor.sslCheckEnabled,
134-
events = monitorCrudService.getSSLEventsByMonitorId(monitorId, SSL_EVENTS_COUNT)
132+
renderIncidents(
133+
incidents = incidentRepository.getSslIncidents(
134+
monitor.id,
135+
period = Duration.ofDays(UIDefaults.INCIDENTS_PERIOD_DAYS),
136+
includeResolved = true,
137+
)
135138
)
136139
}
137140
}

app/src/main/kotlin/com/kuvaszuptime/kuvasz/repositories/IncidentRepository.kt

Lines changed: 102 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import org.jooq.DSLContext
1414
import org.jooq.impl.DSL
1515
import org.jooq.kotlin.and
1616
import java.time.Duration
17-
import java.time.OffsetDateTime
1817

1918
@Singleton
2019
class IncidentRepository(private val dslContext: DSLContext) {
@@ -30,73 +29,117 @@ class IncidentRepository(private val dslContext: DSLContext) {
3029
*
3130
* @return List of [IncidentDto] matching the criteria.
3231
*/
33-
@Suppress("IgnoredReturnValue")
3432
fun getIncidents(
3533
monitorId: Long? = null,
3634
period: Duration? = null,
3735
includeResolved: Boolean,
3836
): List<IncidentDto> {
39-
val periodStart: OffsetDateTime? = period?.let { getCurrentTimestamp().minus(it) }
4037
val orderFieldName = DSL.name(IncidentDto::updatedAt.name)
38+
4139
return dslContext
4240
// HTTP incidents
43-
.select(
44-
HTTP_MONITOR.ID.`as`(IncidentDto::monitorId.name),
45-
HTTP_MONITOR.NAME.`as`(IncidentDto::monitorName.name),
46-
HTTP_MONITOR.ENABLED.`as`(IncidentDto::isMonitorEnabled.name),
47-
DSL.inline(IncidentType.HTTP.name).`as`(IncidentDto::incidentType.name),
48-
DSL.`when`(HTTP_UPTIME_EVENT.ENDED_AT.isNull, IncidentStatus.ONGOING.name)
49-
.otherwise(IncidentStatus.RESOLVED.name).`as`(IncidentDto::status.name),
50-
HTTP_UPTIME_EVENT.ERROR.`as`(IncidentDto::details.name),
51-
HTTP_UPTIME_EVENT.STARTED_AT.`as`(IncidentDto::startedAt.name),
52-
HTTP_UPTIME_EVENT.ENDED_AT.`as`(IncidentDto::endedAt.name),
53-
HTTP_UPTIME_EVENT.UPDATED_AT.`as`(IncidentDto::updatedAt.name),
54-
)
55-
.from(HTTP_UPTIME_EVENT)
56-
.join(HTTP_MONITOR).on(HTTP_UPTIME_EVENT.MONITOR_ID.eq(HTTP_MONITOR.ID))
57-
.where(HTTP_UPTIME_EVENT.STATUS.eq(UptimeStatus.DOWN))
58-
.and(HTTP_MONITOR.ENABLED.isTrue)
59-
.apply {
60-
// Filter for monitors
61-
monitorId?.let { and(HTTP_MONITOR.ID.eq(it)) }
62-
// Filter for events that were open at any point during the specified period
63-
periodStart?.let { and(DSL.coalesce(HTTP_UPTIME_EVENT.ENDED_AT, DSL.now()).greaterThan(it)) }
64-
// Filter out resolved incidents if not requested
65-
if (!includeResolved) {
66-
and(HTTP_UPTIME_EVENT.ENDED_AT.isNull)
67-
}
68-
}
41+
.httpUptimeIncidentSelect(monitorId, period, includeResolved)
6942
// SSL incidents
70-
.unionAll(
71-
dslContext
72-
.select(
73-
HTTP_MONITOR.ID.`as`(IncidentDto::monitorId.name),
74-
HTTP_MONITOR.NAME.`as`(IncidentDto::monitorName.name),
75-
DSL.field(HTTP_MONITOR.ENABLED.and(HTTP_MONITOR.SSL_CHECK_ENABLED))
76-
.`as`(IncidentDto::isMonitorEnabled.name),
77-
DSL.inline(IncidentType.SSL.name).`as`(IncidentDto::incidentType.name),
78-
DSL.`when`(SSL_EVENT.ENDED_AT.isNull, IncidentStatus.ONGOING.name)
79-
.otherwise(IncidentStatus.RESOLVED.name).`as`(IncidentDto::status.name),
80-
SSL_EVENT.ERROR.`as`(IncidentDto::details.name),
81-
SSL_EVENT.STARTED_AT.`as`(IncidentDto::startedAt.name),
82-
SSL_EVENT.ENDED_AT.`as`(IncidentDto::endedAt.name),
83-
SSL_EVENT.UPDATED_AT.`as`(IncidentDto::updatedAt.name),
84-
)
85-
.from(SSL_EVENT)
86-
.join(HTTP_MONITOR).on(SSL_EVENT.MONITOR_ID.eq(HTTP_MONITOR.ID))
87-
.where(SSL_EVENT.STATUS.eq(SslStatus.INVALID))
88-
.and(HTTP_MONITOR.ENABLED.isTrue)
89-
.apply {
90-
// Filter for monitors
91-
monitorId?.let { and(HTTP_MONITOR.ID.eq(it)) }
92-
// Filter for events that were open at any point during the specified period
93-
periodStart?.let { and(DSL.coalesce(SSL_EVENT.ENDED_AT, DSL.now()).greaterThan(it)) }
94-
// Filter out resolved incidents if not requested
95-
if (!includeResolved) {
96-
and(SSL_EVENT.ENDED_AT.isNull)
97-
}
98-
}
99-
)
43+
.unionAll(dslContext.sslIncidentsSelect(monitorId, period, includeResolved))
44+
.orderBy(DSL.field(orderFieldName).desc())
45+
.fetchInto(IncidentDto::class.java)
46+
}
47+
48+
@Suppress("IgnoredReturnValue")
49+
private fun DSLContext.httpUptimeIncidentSelect(
50+
monitorId: Long? = null,
51+
period: Duration? = null,
52+
includeResolved: Boolean
53+
) = this
54+
.select(
55+
HTTP_MONITOR.ID.`as`(IncidentDto::monitorId.name),
56+
HTTP_MONITOR.NAME.`as`(IncidentDto::monitorName.name),
57+
HTTP_MONITOR.ENABLED.`as`(IncidentDto::isMonitorEnabled.name),
58+
DSL.inline(IncidentType.HTTP.name).`as`(IncidentDto::incidentType.name),
59+
DSL.`when`(HTTP_UPTIME_EVENT.ENDED_AT.isNull, IncidentStatus.ONGOING.name)
60+
.otherwise(IncidentStatus.RESOLVED.name).`as`(IncidentDto::status.name),
61+
HTTP_UPTIME_EVENT.ERROR.`as`(IncidentDto::details.name),
62+
HTTP_UPTIME_EVENT.STARTED_AT.`as`(IncidentDto::startedAt.name),
63+
HTTP_UPTIME_EVENT.ENDED_AT.`as`(IncidentDto::endedAt.name),
64+
HTTP_UPTIME_EVENT.UPDATED_AT.`as`(IncidentDto::updatedAt.name),
65+
)
66+
.from(HTTP_UPTIME_EVENT)
67+
.join(HTTP_MONITOR).on(HTTP_UPTIME_EVENT.MONITOR_ID.eq(HTTP_MONITOR.ID))
68+
.where(HTTP_UPTIME_EVENT.STATUS.eq(UptimeStatus.DOWN))
69+
.and(HTTP_MONITOR.ENABLED.isTrue)
70+
.apply {
71+
// Filter for monitors
72+
monitorId?.let { and(HTTP_MONITOR.ID.eq(it)) }
73+
// Filter for events that were open at any point during the specified period
74+
period?.let {
75+
val periodStart = getCurrentTimestamp().minus(period)
76+
and(DSL.coalesce(HTTP_UPTIME_EVENT.ENDED_AT, DSL.now()).greaterThan(periodStart))
77+
}
78+
// Filter out resolved incidents if not requested
79+
if (!includeResolved) {
80+
and(HTTP_UPTIME_EVENT.ENDED_AT.isNull)
81+
}
82+
}
83+
84+
@Suppress("IgnoredReturnValue")
85+
private fun DSLContext.sslIncidentsSelect(
86+
monitorId: Long? = null,
87+
period: Duration? = null,
88+
includeResolved: Boolean,
89+
) = this
90+
.select(
91+
HTTP_MONITOR.ID.`as`(IncidentDto::monitorId.name),
92+
HTTP_MONITOR.NAME.`as`(IncidentDto::monitorName.name),
93+
DSL.field(HTTP_MONITOR.ENABLED.and(HTTP_MONITOR.SSL_CHECK_ENABLED))
94+
.`as`(IncidentDto::isMonitorEnabled.name),
95+
DSL.inline(IncidentType.SSL.name).`as`(IncidentDto::incidentType.name),
96+
DSL.`when`(SSL_EVENT.ENDED_AT.isNull, IncidentStatus.ONGOING.name)
97+
.otherwise(IncidentStatus.RESOLVED.name).`as`(IncidentDto::status.name),
98+
SSL_EVENT.ERROR.`as`(IncidentDto::details.name),
99+
SSL_EVENT.STARTED_AT.`as`(IncidentDto::startedAt.name),
100+
SSL_EVENT.ENDED_AT.`as`(IncidentDto::endedAt.name),
101+
SSL_EVENT.UPDATED_AT.`as`(IncidentDto::updatedAt.name),
102+
)
103+
.from(SSL_EVENT)
104+
.join(HTTP_MONITOR).on(SSL_EVENT.MONITOR_ID.eq(HTTP_MONITOR.ID))
105+
.where(SSL_EVENT.STATUS.eq(SslStatus.INVALID))
106+
.and(HTTP_MONITOR.ENABLED.isTrue)
107+
.apply {
108+
// Filter for monitors
109+
monitorId?.let { and(HTTP_MONITOR.ID.eq(it)) }
110+
// Filter for events that were open at any point during the specified period
111+
period?.let {
112+
val periodStart = getCurrentTimestamp().minus(period)
113+
and(DSL.coalesce(SSL_EVENT.ENDED_AT, DSL.now()).greaterThan(periodStart))
114+
}
115+
// Filter out resolved incidents if not requested
116+
if (!includeResolved) {
117+
and(SSL_EVENT.ENDED_AT.isNull)
118+
}
119+
}
120+
121+
fun getHttpUptimeIncidents(
122+
monitorId: Long? = null,
123+
period: Duration? = null,
124+
includeResolved: Boolean,
125+
): List<IncidentDto> {
126+
val orderFieldName = DSL.name(IncidentDto::updatedAt.name)
127+
128+
return dslContext
129+
.httpUptimeIncidentSelect(monitorId, period, includeResolved)
130+
.orderBy(DSL.field(orderFieldName).desc())
131+
.fetchInto(IncidentDto::class.java)
132+
}
133+
134+
fun getSslIncidents(
135+
monitorId: Long? = null,
136+
period: Duration? = null,
137+
includeResolved: Boolean,
138+
): List<IncidentDto> {
139+
val orderFieldName = DSL.name(IncidentDto::updatedAt.name)
140+
141+
return dslContext
142+
.sslIncidentsSelect(monitorId, period, includeResolved)
100143
.orderBy(DSL.field(orderFieldName).desc())
101144
.fetchInto(IncidentDto::class.java)
102145
}

app/src/test/kotlin/com/kuvaszuptime/kuvasz/security/DisabledWebUIAuthenticationTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ class DisabledWebUIAuthenticationTest(
3333
row("/http-monitors/${monitor.id}"),
3434
row("/http-monitors/fragments/list"),
3535
row("/http-monitors/fragments/details-heading/${monitor.id}"),
36-
row("/http-monitors/fragments/details-uptime-events/${monitor.id}"),
37-
row("/http-monitors/fragments/details-ssl-events/${monitor.id}"),
36+
row("/http-monitors/fragments/details-uptime-incidents/${monitor.id}"),
37+
row("/http-monitors/fragments/details-ssl-incidents/${monitor.id}"),
3838
row("/http-monitors/fragments/stats"),
3939
row("/settings"),
4040
row("/integrations"),

app/src/test/kotlin/com/kuvaszuptime/kuvasz/security/WebUIAuthenticationTest.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ class WebUIAuthenticationTest(
4242
row("/http-monitors/1"),
4343
row("/http-monitors/fragments/list"),
4444
row("/http-monitors/fragments/details-heading/1"),
45-
row("/http-monitors/fragments/details-uptime-events/1"),
46-
row("/http-monitors/fragments/details-ssl-events/1"),
45+
row("/http-monitors/fragments/details-uptime-incidents/1"),
46+
row("/http-monitors/fragments/details-ssl-incidents/1"),
4747
row("/http-monitors/fragments/stats"),
4848
row("/settings"),
4949
row("/integrations"),
@@ -67,8 +67,8 @@ class WebUIAuthenticationTest(
6767
row("/http-monitors/1"),
6868
row("/http-monitors/fragments/list"),
6969
row("/http-monitors/fragments/details-heading/1"),
70-
row("/http-monitors/fragments/details-uptime-events/1"),
71-
row("/http-monitors/fragments/details-ssl-events/1"),
70+
row("/http-monitors/fragments/details-uptime-incidents/1"),
71+
row("/http-monitors/fragments/details-ssl-incidents/1"),
7272
row("/http-monitors/fragments/stats"),
7373
row("/settings"),
7474
row("/integrations"),
@@ -106,8 +106,8 @@ class WebUIAuthenticationTest(
106106
row("/http-monitors/${monitor.id}"),
107107
row("/http-monitors/fragments/list"),
108108
row("/http-monitors/fragments/details-heading/${monitor.id}"),
109-
row("/http-monitors/fragments/details-uptime-events/${monitor.id}"),
110-
row("/http-monitors/fragments/details-ssl-events/${monitor.id}"),
109+
row("/http-monitors/fragments/details-uptime-incidents/${monitor.id}"),
110+
row("/http-monitors/fragments/details-ssl-incidents/${monitor.id}"),
111111
row("/http-monitors/fragments/stats"),
112112
row("/settings"),
113113
row("/integrations"),

ui/src/main/kotlin/com/kuvaszuptime/kuvasz/ui/fragments/monitor/http/DetailsContent.kt

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ import com.kuvaszuptime.kuvasz.models.dto.HttpMonitorDetailsDto
88
import com.kuvaszuptime.kuvasz.ui.CSSClass.*
99
import com.kuvaszuptime.kuvasz.ui.utils.*
1010
import com.kuvaszuptime.kuvasz.util.UIDefaults
11+
import com.kuvaszuptime.kuvasz.util.formatAsSimpleInterval
1112
import kotlinx.html.*
13+
import java.time.Duration
1214
import kotlin.time.Duration.Companion.seconds
1315

1416
internal fun FlowContent.httpMonitorDetailsContent(monitor: HttpMonitorDetailsDto, stats: HistoricalUptimeStatsDto) {
@@ -17,13 +19,21 @@ internal fun FlowContent.httpMonitorDetailsContent(monitor: HttpMonitorDetailsDt
1719
// Uptime summary
1820
h2 { +Messages.uptimeBlockTitle() }
1921
detailsUptimeSummary(monitor, stats)
20-
// Uptime events
21-
h3 { +Messages.recentEventsBlockTitle() }
22+
// Uptime incidents
23+
h3 {
24+
+Messages.incidents()
25+
span {
26+
classes(BADGE)
27+
+Messages.lastX(
28+
Duration.ofDays(UIDefaults.INCIDENTS_PERIOD_DAYS).formatAsSimpleInterval()
29+
)
30+
}
31+
}
2232
div {
2333
classes(ROW, ROW_CARDS, MB_3)
2434
id = "monitor-details-uptime-events"
2535
hx {
26-
get("/http-monitors/fragments/details-uptime-events/${monitor.id}")
36+
get("/http-monitors/fragments/details-uptime-incidents/${monitor.id}")
2737
trigger {
2838
load()
2939
every(15.seconds)
@@ -47,12 +57,21 @@ internal fun FlowContent.httpMonitorDetailsContent(monitor: HttpMonitorDetailsDt
4757
if (monitor.sslCheckEnabled) {
4858
h2 { +Messages.sslBlockTitle() }
4959
detailsSSLSummary(monitor)
50-
h3 { +Messages.recentEventsBlockTitle() }
60+
// SSL incidents
61+
h3 {
62+
+Messages.incidents()
63+
span {
64+
classes(BADGE)
65+
+Messages.lastX(
66+
Duration.ofDays(UIDefaults.INCIDENTS_PERIOD_DAYS).formatAsSimpleInterval()
67+
)
68+
}
69+
}
5170
div {
5271
classes(ROW, ROW_CARDS, MB_3)
5372
id = "monitor-details-ssl-events"
5473
hx {
55-
get("/http-monitors/fragments/details-ssl-events/${monitor.id}")
74+
get("/http-monitors/fragments/details-ssl-incidents/${monitor.id}")
5675
trigger {
5776
load()
5877
every(15.seconds)

0 commit comments

Comments
 (0)