Skip to content

Commit 004c9b7

Browse files
authored
introduce request & response headers, request body + new http methods (#218)
* add new columns & http methods to the monitor DB schema * improve the openapi docs generation * integrate new props into the API & the YAML config * integrate body & headers into the request configurator * integrate headers into the response evaluation * make CI smarter by checking translations and OpenAPI spec * ui: redesign the monitor upsert modal & integrate the new props * docs: introduce the new props + add uptimerobot comparison * docs: add more lines to uptimerobot comparison
1 parent fc336c6 commit 004c9b7

File tree

75 files changed

+2474
-412
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

75 files changed

+2474
-412
lines changed

.github/workflows/main.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ jobs:
2323
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
2424
restore-keys: |
2525
${{ runner.os }}-gradle-
26+
- name: Validate translations
27+
run: ./gradlew :shared:validateI18n
28+
- name: Generate OpenAPI spec
29+
run: ./gradlew :app:updateApiDoc
30+
- name: Validate that generated sources are committed
31+
run: git diff --quiet --exit-code HEAD --
2632
- name: Run detekt
2733
run: ./gradlew detektAll
2834
- name: Run check

README.md

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ Use the following credentials to log in:
3030

3131
Kuvasz (pronounce as [ˈkuvɒs]) is an ancient hungarian breed of livestock & guard dog. You can read more about them on [Wikipedia](https://en.wikipedia.org/wiki/Kuvasz).
3232

33+
## ⚡️ Quick start guide
34+
35+
If you want to get started quickly, please refer to the [**Installation guide**](https://kuvasz-uptime.dev/setup/installation/) in the documentation.
36+
3337
## ✨ Features
3438

3539
- **HTTP(S) monitoring**: Monitor the availability and performance of your websites and services by sending HTTP(S) requests.
@@ -38,11 +42,53 @@ Kuvasz (pronounce as [ˈkuvɒs]) is an ancient hungarian breed of livestock & gu
3842
- **Sleek UI**: Kuvasz has a modern, responsive, and user-friendly interface that makes it easy to manage your monitors.
3943
- **Full-fledged REST API**: Manage your monitors, check their status, and more through a powerful API.
4044
- **Metrics exporters**: Export your metrics to _OpenTelemetry_ and _Prometheus_ for better observability and integration with your existing monitoring stack.
41-
- More to come: _Kuvasz_ is under active development, and more features are planned for the future, such as **POST requests** with arbitrary payload, **ICMP monitoring**, **heartbeat monitors** and more.
42-
43-
## ⚡️ Quick start guide
44-
45-
If you want to get started quickly, please refer to the [**Installation guide**](https://kuvasz-uptime.dev/setup/installation/) in the documentation.
45+
- More to come: _Kuvasz_ is under active development, and more features are planned for the future, such as **ICMP monitoring**, **heartbeat monitors** and more.
46+
47+
## 🚀 Kuvasz vs. UptimeRobot
48+
49+
| | Kuvasz | UptimeRobot Free | UptimeRobot Solo |
50+
|-------------------------------------------|:-------------:|:----------------:|:----------------:|
51+
| Price | Free | Free | $84/year |
52+
| Monitoring interval | **5 seconds** | 5 minutes | 60 seconds |
53+
| Monitors limit | **unlimited** | 50 | 10 |
54+
| Location-specific monitoring |\* |||
55+
| Translations ||||
56+
| Custom data retention || 3 months | 12 months |
57+
| REST API ||||
58+
| Prometheus & OpenTelemetry exporters ||||
59+
| Backups & YAML configuration ||||
60+
| Status pages | 📆 | only 1 | only 3 |
61+
| Maintenance windows | 📆 |||
62+
| **HTTPs monitoring** | | | |
63+
| Keyword matching ||||
64+
| Header matching ||||
65+
| Slow response alerts ||||
66+
| Custom HTTP methods || ❌ (HEAD only) ||
67+
| Custom status matcher ||||
68+
| Custom headers ||||
69+
| Custom request body ||||
70+
| **SSL monitoring** ||||
71+
| **Ping (ICMP) monitoring** | 📆 |||
72+
| **Heartbeat monitoring** | 📆 |||
73+
| **Port monitoring** ||||
74+
| **DNS monitoring** ||||
75+
| **Domain expiration monitoring** ||||
76+
| **Notifications** | | | |
77+
| Email ||||
78+
| Discord ||||
79+
| Slack ||||
80+
| Telegram ||||
81+
| Pagerduty ||||
82+
| MS Teams | 📆 |||
83+
| Webhook | 📆 |||
84+
| SMS / Voice call | 📆\** || 10 incl./month |
85+
| Google Chat, Pushover, Pushbullet, Splunk ||||
86+
| Mattermost ||||
87+
88+
✅ Supported | ❌ Not supported | 📆 Planned
89+
90+
- \* You can deploy _Kuvasz_ to multiple locations and monitor your services from those locations, but it does not support location-specific monitoring out of the box.
91+
- \** _Kuvasz_ will only provide the integration, but you will need to pay for the SMS or voice call service yourself
4692

4793
## 📣 Don't miss out on the latest updates!
4894

app/build.gradle.kts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ tasks.withType<JavaExec> {
121121
}
122122

123123
tasks.withType<ShadowJar> {
124+
dependsOn("updateApiDoc")
124125
mergeServiceFiles()
125126
}
126127

@@ -162,10 +163,8 @@ jib {
162163
}
163164
}
164165

165-
val updateApiDoc by tasks.registering(type = Copy::class) {
166+
val updateApiDoc by tasks.registering {
166167
dependsOn("kaptKotlin")
167-
from(layout.buildDirectory.file("tmp/kapt3/classes/main/META-INF/swagger/kuvasz-latest.yml"))
168-
into("$rootDir/docs/docs/api-docs")
169168
}
170169

171170
buildConfig {

app/src/main/kotlin/com/kuvaszuptime/kuvasz/Application.kt

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,20 @@ import io.micronaut.runtime.Micronaut.build
55
import io.swagger.v3.oas.annotations.OpenAPIDefinition
66
import io.swagger.v3.oas.annotations.enums.SecuritySchemeIn
77
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType
8-
import io.swagger.v3.oas.annotations.info.Contact
98
import io.swagger.v3.oas.annotations.info.Info
109
import io.swagger.v3.oas.annotations.security.SecurityScheme
1110
import io.swagger.v3.oas.annotations.security.SecuritySchemes
1211
import io.swagger.v3.oas.annotations.tags.Tag
1312

1413
@OpenAPIDefinition(
1514
info = Info(
16-
title = "kuvasz",
15+
title = "Kuvasz Uptime",
1716
version = "latest",
18-
description = "Kuvasz [pronounce as 'koovas'] is an open-source uptime and SSL monitoring service",
19-
contact = Contact(
20-
url = "https://github.com/kuvasz-uptime/kuvasz"
21-
)
2217
),
2318
tags = [
24-
Tag(name = "Management operations"),
25-
Tag(name = "Monitor operations"),
26-
Tag(name = "Settings operations"),
19+
Tag(name = "Management"),
20+
Tag(name = "HTTP monitors"),
21+
Tag(name = "Settings"),
2722
]
2823
)
2924
@SecuritySchemes(

app/src/main/kotlin/com/kuvaszuptime/kuvasz/config/MonitorConfig.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,8 @@ interface MonitorConfig : MonitorCreatorLike {
5151

5252
@get:Bindable(defaultValue = MonitorDefaults.EXPECTED_KEYWORD_NEGATED.toString())
5353
override val expectedKeywordNegated: Boolean
54+
55+
override val requestHeaders: Map<String, String>?
56+
override val expectedHeaders: Map<String, String>?
57+
override val requestBody: String?
5458
}

app/src/main/kotlin/com/kuvaszuptime/kuvasz/controllers/MonitorController.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ const val API_V1_PREFIX = "/api/v1"
4545

4646
@Controller("$API_V1_PREFIX/monitors", produces = [MediaType.APPLICATION_JSON])
4747
@Validated
48-
@Tag(name = "Monitor operations")
48+
@Tag(name = "HTTP monitors")
4949
@SecurityRequirements(
5050
SecurityRequirement(name = "apiKey"),
5151
SecurityRequirement(name = "bearerAuth")

app/src/main/kotlin/com/kuvaszuptime/kuvasz/controllers/SettingsController.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import io.swagger.v3.oas.annotations.tags.Tag
1717

1818
@Controller("$API_V1_PREFIX/settings", produces = [MediaType.APPLICATION_JSON])
1919
@Validated
20-
@Tag(name = "Settings operations")
20+
@Tag(name = "Settings")
2121
@SecurityRequirements(
2222
SecurityRequirement(name = "apiKey"),
2323
SecurityRequirement(name = "bearerAuth")

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.kuvaszuptime.kuvasz.repositories
22

33
import arrow.core.Either
4+
import com.kuvaszuptime.kuvasz.jooq.JsonNodeToMapConverter
45
import com.kuvaszuptime.kuvasz.jooq.Keys.UNIQUE_MONITOR_NAME
56
import com.kuvaszuptime.kuvasz.jooq.enums.SslStatus
67
import com.kuvaszuptime.kuvasz.jooq.enums.UptimeStatus
@@ -111,6 +112,9 @@ class MonitorRepository(private val dslContext: DSLContext) {
111112
.set(MONITOR.EXPECTED_KEYWORD, updatedMonitor.expectedKeyword)
112113
.set(MONITOR.EXPECTED_KEYWORD_CASE_SENSITIVE, updatedMonitor.expectedKeywordCaseSensitive)
113114
.set(MONITOR.EXPECTED_KEYWORD_NEGATED, updatedMonitor.expectedKeywordNegated)
115+
.set(MONITOR.REQUEST_HEADERS, updatedMonitor.requestHeaders)
116+
.set(MONITOR.EXPECTED_HEADERS, updatedMonitor.expectedHeaders)
117+
.set(MONITOR.REQUEST_BODY, updatedMonitor.requestBody)
114118
.where(MONITOR.ID.eq(updatedMonitor.id))
115119
.returning(MONITOR.asterisk())
116120
.fetchOneOrThrow<MonitorRecord>()
@@ -179,6 +183,9 @@ class MonitorRepository(private val dslContext: DSLContext) {
179183
MONITOR.EXPECTED_KEYWORD.`as`(MonitorDetailsDto::expectedKeyword.name),
180184
MONITOR.EXPECTED_KEYWORD_CASE_SENSITIVE.`as`(MonitorDetailsDto::expectedKeywordCaseSensitive.name),
181185
MONITOR.EXPECTED_KEYWORD_NEGATED.`as`(MonitorDetailsDto::expectedKeywordNegated.name),
186+
MONITOR.REQUEST_HEADERS.`as`(MonitorDetailsDto::requestHeaders.name).convert(JsonNodeToMapConverter()),
187+
MONITOR.EXPECTED_HEADERS.`as`(MonitorDetailsDto::expectedHeaders.name).convert(JsonNodeToMapConverter()),
188+
MONITOR.REQUEST_BODY.`as`(MonitorDetailsDto::requestBody.name),
182189
)
183190
.from(MONITOR)
184191
.leftJoin(UPTIME_EVENT).on(MONITOR.ID.eq(UPTIME_EVENT.MONITOR_ID).and(UPTIME_EVENT.ENDED_AT.isNull))

app/src/main/kotlin/com/kuvaszuptime/kuvasz/services/check/http/HttpCheckRequestConfigurator.kt

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ package com.kuvaszuptime.kuvasz.services.check.http
22

33
import com.kuvaszuptime.kuvasz.jooq.enums.HttpMethod
44
import com.kuvaszuptime.kuvasz.jooq.tables.records.MonitorRecord
5+
import com.kuvaszuptime.kuvasz.models.dto.requestHeadersAsMap
56
import io.micronaut.http.HttpHeaders
67
import io.micronaut.http.HttpRequest
8+
import io.micronaut.http.MediaType
79
import io.micronaut.http.MutableHttpRequest
810
import jakarta.inject.Singleton
911
import java.net.URI
@@ -20,13 +22,31 @@ class HttpCheckRequestConfigurator {
2022
* this URI may differ from the one stored in the monitor.
2123
* @return A configured [io.micronaut.http.MutableHttpRequest].
2224
*/
23-
fun fromMonitor(monitor: MonitorRecord, uri: URI): MutableHttpRequest<*> = HttpRequest
24-
.create<String>(
25-
monitor.requestMethod.toMicronautHttpMethod(),
26-
uri.toString()
27-
)
28-
.initializeHeaders()
29-
.decorateWithHeaders(monitor)
25+
fun fromMonitor(monitor: MonitorRecord, uri: URI): MutableHttpRequest<*> =
26+
provisionRequestWithMethodAndBody(monitor.requestMethod, uri, monitor.requestBody)
27+
.initializeHeaders()
28+
.decorateWithHeaders(monitor)
29+
30+
private fun provisionRequestWithMethodAndBody(
31+
method: HttpMethod,
32+
uri: URI,
33+
body: String?,
34+
): MutableHttpRequest<*> {
35+
val effectiveBody = body?.ifBlank { null } ?: FALLBACK_EMPTY_BODY
36+
val mediaType = MediaType.APPLICATION_JSON
37+
// Using application/json by default for requests with a body, currently this is the only supported content type
38+
val request = when (method) {
39+
HttpMethod.GET -> HttpRequest.GET<String>(uri)
40+
HttpMethod.HEAD -> HttpRequest.HEAD(uri)
41+
HttpMethod.DELETE -> HttpRequest.DELETE<String>(uri)
42+
HttpMethod.OPTIONS -> HttpRequest.OPTIONS(uri)
43+
HttpMethod.POST -> HttpRequest.POST(uri, effectiveBody).contentType(mediaType)
44+
HttpMethod.PUT -> HttpRequest.PUT(uri, effectiveBody).contentType(mediaType)
45+
HttpMethod.PATCH -> HttpRequest.PATCH(uri, effectiveBody).contentType(mediaType)
46+
}
47+
48+
return request
49+
}
3050

3151
/**
3252
* Initializes the common headers for the HTTP request.
@@ -48,16 +68,14 @@ class HttpCheckRequestConfigurator {
4868
if (monitor.expectedKeyword.isNullOrEmpty()) {
4969
header(HttpHeaders.ACCEPT_ENCODING, "gzip, deflate, br")
5070
}
71+
// Adding the custom headers as a last step to make sure that they override the default ones
72+
monitor.requestHeadersAsMap().forEach { header ->
73+
headers.set(header.key, header.value)
74+
}
5175
}
5276

53-
private fun HttpMethod.toMicronautHttpMethod(): io.micronaut.http.HttpMethod {
54-
return when (this) {
55-
HttpMethod.GET -> io.micronaut.http.HttpMethod.GET
56-
HttpMethod.HEAD -> io.micronaut.http.HttpMethod.HEAD
57-
}
58-
}
59-
6077
companion object {
6178
const val USER_AGENT = "Kuvasz Uptime Checker/2 https://github.com/kuvasz-uptime/kuvasz"
79+
private const val FALLBACK_EMPTY_BODY = "{}"
6280
}
6381
}

app/src/main/kotlin/com/kuvaszuptime/kuvasz/services/check/http/HttpCheckResponseEvaluator.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ class HttpCheckResponseEvaluator(
1919
private val responseStatusChecker: HttpResponseStatusChecker,
2020
private val responseTimeChecker: HttpResponseTimeChecker,
2121
private val responseBodyChecker: HttpResponseBodyChecker,
22+
private val responseHeaderChecker: HttpResponseHeaderChecker,
2223
private val noOpUpDispatcher: NoOpUpDispatcher,
2324
) {
2425
private fun getPreviousEvent(monitor: MonitorRecord): UptimeEventRecord? =
@@ -57,6 +58,7 @@ class HttpCheckResponseEvaluator(
5758
return HttpCheckResult.Continue
5859
.finishOrContinueWith(responseStatusChecker)
5960
.finishOrContinueWith(responseTimeChecker)
61+
.finishOrContinueWith(responseHeaderChecker)
6062
.finishOrContinueWith(responseBodyChecker)
6163
// As the last step we always dispatch an UP event
6264
.finishOrContinueWith(noOpUpDispatcher)

0 commit comments

Comments
 (0)