From 5ddac405faa2f0d0bef16a0fc9eeec3d47b0cedf Mon Sep 17 00:00:00 2001 From: mgeissen Date: Thu, 18 Apr 2024 13:01:43 +0200 Subject: [PATCH] show status detail indicator infos on status page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matthias Geißendörfer --- CHANGELOG.md | 6 + .../kotlin/babbage.conventions.gradle.kts | 1 + .../core/config/ThymeleafConfiguration.kt | 15 ++ .../babbage/core/status/StatusController.kt | 38 ++- .../de/otto/babbage/core/status/StatusData.kt | 7 +- .../contributors/SystemInfoContributor.kt | 41 +++- .../babbage/core/status/indicators/Status.kt | 9 +- core/src/main/resources/static/main.css | 27 +++ .../resources/templates/fragments/head.html | 1 + .../templates/fragments/status-badge.html | 13 + core/src/main/resources/templates/status.html | 65 ++++- .../main/resources/templates/status_new.html | 225 ------------------ .../example/BathroomStatusDetailIndicator.kt | 24 ++ .../example/KitchenStatusDetailIndicator.kt | 32 +++ 14 files changed, 252 insertions(+), 252 deletions(-) create mode 100644 core/src/main/kotlin/de/otto/babbage/core/config/ThymeleafConfiguration.kt create mode 100644 core/src/main/resources/static/main.css create mode 100644 core/src/main/resources/templates/fragments/status-badge.html delete mode 100644 core/src/main/resources/templates/status_new.html create mode 100644 example/src/main/kotlin/de/otto/babbage/example/BathroomStatusDetailIndicator.kt create mode 100644 example/src/main/kotlin/de/otto/babbage/example/KitchenStatusDetailIndicator.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ae801b..ed51c1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog + +## 0.2.1-SNAPSHOT +* _core_: + * Upgrade to bootstrap 5.3.3 + * Add status detail indicator infos to status page + ## 0.2.0 * Upgrade all modules to spring boot 3.2.2 diff --git a/buildSrc/src/main/kotlin/babbage.conventions.gradle.kts b/buildSrc/src/main/kotlin/babbage.conventions.gradle.kts index ae3e986..d23b09b 100644 --- a/buildSrc/src/main/kotlin/babbage.conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/babbage.conventions.gradle.kts @@ -25,6 +25,7 @@ dependencies { * These do not have to be added again in the build.gradle.kts of the module. */ implementation("org.springframework.boot:spring-boot-starter-webflux") + implementation("org.thymeleaf.extras:thymeleaf-extras-java8time:3.0.4.RELEASE") implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") diff --git a/core/src/main/kotlin/de/otto/babbage/core/config/ThymeleafConfiguration.kt b/core/src/main/kotlin/de/otto/babbage/core/config/ThymeleafConfiguration.kt new file mode 100644 index 0000000..c470324 --- /dev/null +++ b/core/src/main/kotlin/de/otto/babbage/core/config/ThymeleafConfiguration.kt @@ -0,0 +1,15 @@ +package de.otto.babbage.core.config + +import org.springframework.context.annotation.Configuration +import org.thymeleaf.extras.java8time.dialect.Java8TimeDialect +import org.thymeleaf.spring6.SpringTemplateEngine + + +@Configuration +class ThymeleafConfiguration(springTemplateEngine: SpringTemplateEngine) { + + init { + springTemplateEngine.addDialect(Java8TimeDialect()) + } + +} \ No newline at end of file diff --git a/core/src/main/kotlin/de/otto/babbage/core/status/StatusController.kt b/core/src/main/kotlin/de/otto/babbage/core/status/StatusController.kt index 46b09a0..b81f012 100644 --- a/core/src/main/kotlin/de/otto/babbage/core/status/StatusController.kt +++ b/core/src/main/kotlin/de/otto/babbage/core/status/StatusController.kt @@ -2,6 +2,11 @@ package de.otto.babbage.core.status import de.otto.babbage.core.management.ManagementController import de.otto.babbage.core.status.config.StatusProperties +import de.otto.babbage.core.status.contributors.SystemInfoContributor +import de.otto.babbage.core.status.indicators.Status +import de.otto.babbage.core.status.indicators.Status.* +import de.otto.babbage.core.status.indicators.StatusDetail +import de.otto.babbage.core.status.indicators.StatusDetailIndicator import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Value import org.springframework.boot.actuate.info.InfoEndpoint @@ -18,17 +23,16 @@ class StatusController( @Autowired(required = false) private val gitProperties: GitProperties?, private val statusProperties: StatusProperties, - @Value("\${info.app.name}") private val applicationName: String + @Value("\${info.app.name}") private val applicationName: String, + private val statusDetailIndicators: List, + private val systemInfoContributor: SystemInfoContributor ) : ManagementController { @GetMapping("\${management.endpoints.web.base-path}/status") @Suppress("FunctionOnlyReturningConstant", "UnusedPrivateMember") suspend fun status(model: Model): String { val statusData = statusJson() - model.addAttribute("applicationStatus", statusData.application.status) - model.addAttribute("applicationCommit", statusData.application.commit) - model.addAttribute("applicationVersion", statusData.application.version) - + model.addAttribute("statusData", statusData) return "status" } @@ -37,16 +41,28 @@ class StatusController( suspend fun statusJson(): StatusData { val commit = gitProperties?.commitId ?: "unavailable" val version = if (statusProperties.useCommitAsVersion) commit else getVersion() + val indicators = statusDetailIndicators.groupBy { it.getGroup() } + .map { it.key to it.value.flatMap { indicator -> indicator.getDetails() } } + .toMap() return StatusData( application = Application( name = applicationName, version = version, status = ApplicationStatus.OK, // dummy status because HealthEndpoint is blocking and causing errors commit = commit - ) + ), + serviceStatus = getWorstStatus(indicators), + indicators = indicators, + system = systemInfoContributor.getSystemInfo() ) } + private fun getWorstStatus(statusDetails: Map>): Status { + return statusDetails.values.flatMap { statusDetail -> statusDetail.map { it.status } } + .toSet() + .worstStatus() + } + @Suppress("UNCHECKED_CAST", "ReturnCount") private fun getVersion(): String { val gitInfos = infoEndpoint.info()["git"]?.let { it as Map } ?: return "" @@ -54,3 +70,13 @@ class StatusController( return commitInfos["time"]?.toString() ?: "" } } + +private fun Set.worstStatus(): Status { + return if (this.contains(ERROR)) { + ERROR + } else if (this.contains(WARNING)) { + WARNING + } else { + OK + } +} diff --git a/core/src/main/kotlin/de/otto/babbage/core/status/StatusData.kt b/core/src/main/kotlin/de/otto/babbage/core/status/StatusData.kt index 8d907f6..dab6c0c 100644 --- a/core/src/main/kotlin/de/otto/babbage/core/status/StatusData.kt +++ b/core/src/main/kotlin/de/otto/babbage/core/status/StatusData.kt @@ -1,9 +1,14 @@ package de.otto.babbage.core.status +import de.otto.babbage.core.status.contributors.SystemInfo +import de.otto.babbage.core.status.indicators.StatusDetail import org.springframework.boot.actuate.health.Status data class StatusData( - val application: Application + val application: Application, + val indicators: Map>, + val system: SystemInfo, + val serviceStatus: de.otto.babbage.core.status.indicators.Status ) data class Application( diff --git a/core/src/main/kotlin/de/otto/babbage/core/status/contributors/SystemInfoContributor.kt b/core/src/main/kotlin/de/otto/babbage/core/status/contributors/SystemInfoContributor.kt index 544cdf5..ed72195 100644 --- a/core/src/main/kotlin/de/otto/babbage/core/status/contributors/SystemInfoContributor.kt +++ b/core/src/main/kotlin/de/otto/babbage/core/status/contributors/SystemInfoContributor.kt @@ -5,6 +5,7 @@ import org.springframework.boot.actuate.info.Info import org.springframework.boot.actuate.info.InfoContributor import org.springframework.stereotype.Component import java.time.Duration +import java.time.OffsetDateTime import java.time.OffsetDateTime.now import java.time.format.DateTimeFormatter import java.util.Locale @@ -27,17 +28,21 @@ class SystemInfoContributor( override fun contribute(builder: Info.Builder) { builder.withDetail( "system", - mapOf( - "startTime" to START_TIME.format(DateTimeFormatter.ISO_DATE_TIME), - "upTime" to getSystemUpTime(), - "systemTime" to now().format(DateTimeFormatter.ISO_DATE_TIME), - "port" to port, - "hostname" to hostname - ) + getSystemInfo().asMap() ) } - fun getSystemUpTime(): String { + fun getSystemInfo(): SystemInfo { + return SystemInfo( + startTime = START_TIME, + upTime = getSystemUpTime(), + systemTime = now(), + port = port, + hostname = hostname + ) + } + + private fun getSystemUpTime(): String { val seconds = Duration.between(START_TIME, now()).seconds return String.format( Locale.getDefault(), @@ -48,3 +53,23 @@ class SystemInfoContributor( ) } } + +data class SystemInfo( + val startTime: OffsetDateTime, + val upTime: String, + val systemTime: OffsetDateTime, + val port: Int, + val hostname: String +) { + + fun asMap(): Map { + return mapOf( + "startTime" to startTime.format(DateTimeFormatter.ISO_DATE_TIME), + "upTime" to upTime, + "systemTime" to systemTime.format(DateTimeFormatter.ISO_DATE_TIME), + "port" to port.toString(), + "hostname" to hostname + ) + } + +} diff --git a/core/src/main/kotlin/de/otto/babbage/core/status/indicators/Status.kt b/core/src/main/kotlin/de/otto/babbage/core/status/indicators/Status.kt index 2e75141..51eb325 100644 --- a/core/src/main/kotlin/de/otto/babbage/core/status/indicators/Status.kt +++ b/core/src/main/kotlin/de/otto/babbage/core/status/indicators/Status.kt @@ -1,5 +1,12 @@ package de.otto.babbage.core.status.indicators enum class Status { - OK, WARNING, ERROR + OK, WARNING, ERROR; + + fun isOk() = this == OK + + fun isWarning() = this == WARNING + + fun isError() = this == ERROR + } diff --git a/core/src/main/resources/static/main.css b/core/src/main/resources/static/main.css new file mode 100644 index 0000000..a8931fb --- /dev/null +++ b/core/src/main/resources/static/main.css @@ -0,0 +1,27 @@ +.status-container { + margin-top: 16px; + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 16px; +} + +.cards { + display: flex; + flex-direction: column; + gap: 16px; + max-width: 400px; +} + +.status-details { + flex: 1; + + .status-detail-group-container { + border-top: 1px solid #d2d2d2; + padding: 16px; + + .status-column { + width: 100px; + } + } +} diff --git a/core/src/main/resources/templates/fragments/head.html b/core/src/main/resources/templates/fragments/head.html index 4bfd733..84c8e83 100644 --- a/core/src/main/resources/templates/fragments/head.html +++ b/core/src/main/resources/templates/fragments/head.html @@ -11,6 +11,7 @@ + diff --git a/core/src/main/resources/templates/fragments/status-badge.html b/core/src/main/resources/templates/fragments/status-badge.html new file mode 100644 index 0000000..4d26021 --- /dev/null +++ b/core/src/main/resources/templates/fragments/status-badge.html @@ -0,0 +1,13 @@ + + + + +
+
+ OK + WARNING + ERROR +
+
+ + \ No newline at end of file diff --git a/core/src/main/resources/templates/status.html b/core/src/main/resources/templates/status.html index f007aed..57cdf9b 100644 --- a/core/src/main/resources/templates/status.html +++ b/core/src/main/resources/templates/status.html @@ -4,17 +4,60 @@
-
-
-
-
-
Status:
-
Status
-
Commit:
-
Commit
-
Version:
-
Version
-
+
+ +
+
+
+
Version
+
+
Version:
+
Commit:
+
+
+
+ +
+
+
System
+
+
Started at: +
+
Up-Time:
+
System-Time:
+
Port:
+
Hostname:
+
+
+
+
+ +
+
Status: +
+
+
+
+ + + + + + + + + + + + + + + +
StatusNameMessage
+
+
diff --git a/core/src/main/resources/templates/status_new.html b/core/src/main/resources/templates/status_new.html deleted file mode 100644 index 89cb1dd..0000000 --- a/core/src/main/resources/templates/status_new.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - - - - -
- -
-
-
-
-
-

Application

-
-
-
-
Name:
-
Name
-
Title:
-
Title
-
Description:
-
Name
-
Group:
-
-
Environment:
-
-
Status:
-
Status
-
-
-
-
-
-
-
-

System

-
-
-
-
Host:
-
Hostname
-
Port:
-
Port
-
System Time:
-
Systemtime
-
System Started
-
Systemstarttime
-
System Uptime
-
SystemUptime
-
-
-
-
-
-
-
-
-
-

Version

-
-
-
-
Version:
-
Version
-
Commit Message:
-
Short Message
-
Commit ID:
-
Git commit
-
Commit Time:
-
Git time
-
Branch:
-
master
-
User:
-
-
-
VCS:
-
- Open VCS UI - Vcs Url -
-
-
-
-
-
-
-
-

Team

-
-
-
-
Name:
-
-
Technical Contact:
-
-
Business Contact:
-
-
-
-
-
-
-
-
-
-
-

Criticality

-
-
-
-
Level:
-
-
Disaster Impact:
-
-
-
-
-
-
-
-
-

Cluster Info

-
-
-
-
Color:
-
-
Color State:
-
-
-
-
-
-
-
-
-
-
-

Details

-
-
- -
-
-

List group item heading

-
-
Status:
-
-
Message:
-
- -
Message:
-
-
- -
-
- Open -
-
-
-
-
- -
-
-
-
-
-
-
-
-

Dependencies

-
-
- - - - - - - - - - - - - - -
TypeNameDescriptionRequirements
TypeName -

Datasources:
-
-

-

Service:
- -

-

Description:
- -

-

Methods:
- -

-

Mediatypes:
-
-

-

Authentication:
- -

-
-

Criticality:
Criticality

-

Impact:
Impact

-

Availability:

-

Performance:

-
-
-
-
-
-
- - - - \ No newline at end of file diff --git a/example/src/main/kotlin/de/otto/babbage/example/BathroomStatusDetailIndicator.kt b/example/src/main/kotlin/de/otto/babbage/example/BathroomStatusDetailIndicator.kt new file mode 100644 index 0000000..3f600b0 --- /dev/null +++ b/example/src/main/kotlin/de/otto/babbage/example/BathroomStatusDetailIndicator.kt @@ -0,0 +1,24 @@ +package de.otto.babbage.example + +import de.otto.babbage.core.status.indicators.Status +import de.otto.babbage.core.status.indicators.StatusDetail +import de.otto.babbage.core.status.indicators.StatusDetailIndicator +import org.springframework.stereotype.Component + +@Component +class BathroomStatusDetailIndicator : StatusDetailIndicator { + override fun getGroup() = "bathroom" + + override fun getDetails(): List { + return listOf( + StatusDetail( + name = "shower", + status = Status.ERROR, + message = "The water is cold.", + additionalInfos = mapOf( + "water-temperature" to "4°C" + ) + ) + ) + } +} \ No newline at end of file diff --git a/example/src/main/kotlin/de/otto/babbage/example/KitchenStatusDetailIndicator.kt b/example/src/main/kotlin/de/otto/babbage/example/KitchenStatusDetailIndicator.kt new file mode 100644 index 0000000..3b1670c --- /dev/null +++ b/example/src/main/kotlin/de/otto/babbage/example/KitchenStatusDetailIndicator.kt @@ -0,0 +1,32 @@ +package de.otto.babbage.example + +import de.otto.babbage.core.status.indicators.Status +import de.otto.babbage.core.status.indicators.StatusDetail +import de.otto.babbage.core.status.indicators.StatusDetailIndicator +import org.springframework.stereotype.Component + +@Component +class KitchenStatusDetailIndicator : StatusDetailIndicator { + override fun getGroup() = "kitchen" + + override fun getDetails(): List { + return listOf( + StatusDetail( + name = "toaster", + status = Status.OK, + message = "The toast is ready.", + additionalInfos = mapOf( + "required-watt" to "1000W" + ) + ), + StatusDetail( + name = "fridge", + status = Status.WARNING, + message = "Fridge is not cold enough.", + additionalInfos = mapOf( + "temperature" to "8°C" + ) + ) + ) + } +} \ No newline at end of file