Skip to content

Commit 51652e6

Browse files
authored
Discord integration (#188)
* add integration config * add integrationtype * Add discordwebhook msg, and discordwebhookservice * Add text formatter for discord * Add event handler * add discord dto * Add discord to repository * add discord option to settings * remove import * change message text to content * remove comment * add discord icon * fix formatting issues * Add test cases for integraiton tests * falkyt test expected from true to false * Old docker compose configuration * Add discord into the documentation * dd missing application integrations yml * change back flaky test to true * update changelog with discord feature * remove discord form coming soon * version 2.0.0 -> 2.3.0 * remove kuvasz.yml * Add back whitespace
1 parent 1998c2f commit 51652e6

File tree

23 files changed

+965
-31
lines changed

23 files changed

+965
-31
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
## ℹ️ What is Kuvasz?
1515

16-
**Kuvasz** [ˈkuvɒs], an open-source, self-hosted uptime & SSL monitoring service, designed to help you keep track of your websites and services. It provides a modern, user-friendly interface, a powerful REST API, and supports multiple notification channels like email, Slack, Telegram, and PagerDuty.
16+
**Kuvasz** [ˈkuvɒs], an open-source, self-hosted uptime & SSL monitoring service, designed to help you keep track of your websites and services. It provides a modern, user-friendly interface, a powerful REST API, and supports multiple notification channels like email, Discord, Slack, Telegram, and PagerDuty.
1717

1818
![Kuvasz](docs/docs/images/feature_carousel.webp)
1919

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.kuvaszuptime.kuvasz.handlers
2+
3+
import com.kuvaszuptime.kuvasz.models.events.formatters.DiscordTextFormatter
4+
import com.kuvaszuptime.kuvasz.models.handlers.DiscordNotificationConfig
5+
import com.kuvaszuptime.kuvasz.models.handlers.IntegrationType
6+
import com.kuvaszuptime.kuvasz.services.DiscordWebhookService
7+
import com.kuvaszuptime.kuvasz.services.EventDispatcher
8+
import com.kuvaszuptime.kuvasz.services.IntegrationRepository
9+
import io.micronaut.context.annotation.Context
10+
import io.micronaut.context.annotation.Requires
11+
import org.slf4j.LoggerFactory
12+
13+
@Context
14+
@Requires(bean = DiscordNotificationConfig::class)
15+
class DiscordEventHandler(
16+
discordWebhookService: DiscordWebhookService,
17+
eventDispatcher: EventDispatcher,
18+
integrationRepository: IntegrationRepository,
19+
) : RTCMessageEventHandler(eventDispatcher, discordWebhookService, integrationRepository) {
20+
override val logger = LoggerFactory.getLogger(DiscordEventHandler::class.java)
21+
22+
override val formatter = DiscordTextFormatter
23+
24+
override val integrationType: IntegrationType = IntegrationType.DISCORD
25+
}

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

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import com.kuvaszuptime.kuvasz.config.AppConfig
55
import com.kuvaszuptime.kuvasz.config.SMTPMailerConfig
66
import com.kuvaszuptime.kuvasz.metrics.MetricsExportConfig
77
import com.kuvaszuptime.kuvasz.models.dto.SettingsDto
8+
import com.kuvaszuptime.kuvasz.models.handlers.DiscordNotificationConfig
89
import com.kuvaszuptime.kuvasz.models.handlers.EmailNotificationConfig
910
import com.kuvaszuptime.kuvasz.models.handlers.IntegrationConfig
1011
import com.kuvaszuptime.kuvasz.models.handlers.IntegrationID
@@ -59,6 +60,10 @@ class SettingsRepository(
5960
{ id, config ->
6061
SettingsDto.SlackNotificationConfigDto(id, config)
6162
},
63+
discord = getIntegrationConfigs<DiscordNotificationConfig, SettingsDto.DiscordNotificationConfigDto>
64+
{ id, config ->
65+
SettingsDto.DiscordNotificationConfigDto(id, config)
66+
},
6267
telegram = getIntegrationConfigs<TelegramNotificationConfig, SettingsDto.TelegramNotificationConfigDto>
6368
{ id, config ->
6469
SettingsDto.TelegramNotificationConfigDto(id, config)
@@ -72,27 +77,29 @@ class SettingsRepository(
7277
SettingsDto.PagerdutyConfigDto(id, config)
7378
}
7479
),
75-
metricsExport = SettingsDto.MetricsExportSettingsDto(
76-
exportEnabled = metricsExportEnabled,
77-
meters = SettingsDto.MetricsExportSettingsDto.MeterSettingsDto(
78-
sslExpiry = exportConfig.sslExpiry,
79-
latestLatency = exportConfig.latestLatency,
80-
uptimeStatus = exportConfig.uptimeStatus,
81-
sslStatus = exportConfig.sslStatus,
82-
),
83-
exporters = SettingsDto.MetricsExportSettingsDto.ExporterSettingsDto(
84-
prometheus = SettingsDto.MetricsExportSettingsDto.ExporterSettingsDto.PrometheusSettingsDto(
85-
enabled = prometheusSettings.exportEnabled,
86-
descriptions = prometheusSettings.descriptionsEnabled,
87-
),
88-
openTelemetry = SettingsDto.MetricsExportSettingsDto.ExporterSettingsDto.OTLPSettingsDto(
89-
enabled = otlpSettings.exportEnabled,
90-
url = otlpSettings.url,
91-
step = otlpSettings.step,
92-
)
93-
)
80+
metricsExport = metricsExportSettingsDto()
81+
)
82+
83+
private fun metricsExportSettingsDto() = SettingsDto.MetricsExportSettingsDto(
84+
exportEnabled = metricsExportEnabled,
85+
meters = SettingsDto.MetricsExportSettingsDto.MeterSettingsDto(
86+
sslExpiry = exportConfig.sslExpiry,
87+
latestLatency = exportConfig.latestLatency,
88+
uptimeStatus = exportConfig.uptimeStatus,
89+
sslStatus = exportConfig.sslStatus,
90+
),
91+
exporters = SettingsDto.MetricsExportSettingsDto.ExporterSettingsDto(
92+
prometheus = SettingsDto.MetricsExportSettingsDto.ExporterSettingsDto.PrometheusSettingsDto(
93+
enabled = prometheusSettings.exportEnabled,
94+
descriptions = prometheusSettings.descriptionsEnabled,
95+
),
96+
openTelemetry = SettingsDto.MetricsExportSettingsDto.ExporterSettingsDto.OTLPSettingsDto(
97+
enabled = otlpSettings.exportEnabled,
98+
url = otlpSettings.url,
99+
step = otlpSettings.step,
94100
)
95101
)
102+
)
96103

97104
private inline fun <reified C : IntegrationConfig, T> getIntegrationConfigs(
98105
transform: (IntegrationID, C) -> T
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.kuvaszuptime.kuvasz.services
2+
3+
import com.kuvaszuptime.kuvasz.models.handlers.DiscordNotificationConfig
4+
import com.kuvaszuptime.kuvasz.models.handlers.DiscordWebhookMessage
5+
import com.kuvaszuptime.kuvasz.models.handlers.IntegrationConfig
6+
import com.kuvaszuptime.kuvasz.util.toUri
7+
import io.micronaut.context.annotation.Requires
8+
import io.micronaut.http.HttpRequest
9+
import io.micronaut.http.client.HttpClient
10+
import io.micronaut.http.client.annotation.Client
11+
import io.micronaut.retry.annotation.Retryable
12+
import io.reactivex.rxjava3.core.Single
13+
import jakarta.inject.Singleton
14+
import java.net.URI
15+
16+
@Singleton
17+
@Requires(property = DiscordNotificationConfig.CONFIG_PREFIX)
18+
class DiscordWebhookClient(@Client private val client: HttpClient) {
19+
20+
@Retryable
21+
fun sendMessage(webhookUrl: URI, message: DiscordWebhookMessage): Single<String> {
22+
val req = HttpRequest.POST(webhookUrl, message)
23+
return Single.fromPublisher(client.retrieve(req, String::class.java))
24+
}
25+
}
26+
27+
@Singleton
28+
@Requires(bean = DiscordWebhookClient::class)
29+
class DiscordWebhookService(private val client: DiscordWebhookClient) : TextMessageService {
30+
31+
override fun sendMessage(integrationConfig: IntegrationConfig, content: String): Single<String> {
32+
val webhookUrl = (integrationConfig as DiscordNotificationConfig).webhookUrl.toUri()
33+
return client.sendMessage(webhookUrl, DiscordWebhookMessage(content = content))
34+
}
35+
}

app/src/test/kotlin/com/kuvaszuptime/kuvasz/controllers/MonitorControllerTest.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ class MonitorControllerTest(
8484
val setUpIntegrations = listOf(
8585
IntegrationID(IntegrationType.SLACK, "test_implicitly_enabled"),
8686
IntegrationID(IntegrationType.EMAIL, "disabled"),
87+
IntegrationID(IntegrationType.DISCORD, "global"),
8788
IntegrationID(IntegrationType.TELEGRAM, "global"),
8889
IntegrationID(IntegrationType.PAGERDUTY, "test_implicitly_enabled"),
8990
)
@@ -153,6 +154,13 @@ class MonitorControllerTest(
153154
type = IntegrationType.EMAIL,
154155
global = false,
155156
),
157+
IntegrationDetailsDto(
158+
id = "discord:global",
159+
enabled = true,
160+
name = "global",
161+
type = IntegrationType.DISCORD,
162+
global = true,
163+
),
156164
IntegrationDetailsDto(
157165
id = "telegram:global",
158166
enabled = true,
@@ -459,6 +467,7 @@ class MonitorControllerTest(
459467
val setUpIntegrations = listOf(
460468
IntegrationID(IntegrationType.SLACK, "test_implicitly_enabled"),
461469
IntegrationID(IntegrationType.EMAIL, "disabled"),
470+
IntegrationID(IntegrationType.DISCORD, "global"),
462471
IntegrationID(IntegrationType.TELEGRAM, "global"),
463472
IntegrationID(IntegrationType.PAGERDUTY, "test_implicitly_enabled"),
464473
)
@@ -526,6 +535,13 @@ class MonitorControllerTest(
526535
type = IntegrationType.EMAIL,
527536
global = false,
528537
),
538+
IntegrationDetailsDto(
539+
id = "discord:global",
540+
enabled = true,
541+
name = "global",
542+
type = IntegrationType.DISCORD,
543+
global = true,
544+
),
529545
IntegrationDetailsDto(
530546
id = "telegram:global",
531547
enabled = true,

0 commit comments

Comments
 (0)