Skip to content

Commit

Permalink
show status detail indicator infos on status page
Browse files Browse the repository at this point in the history
Co-authored-by: Matthias Geißendörfer <[email protected]>
  • Loading branch information
mgeissen committed Apr 18, 2024
1 parent a89fdee commit 5ddac40
Show file tree
Hide file tree
Showing 14 changed files with 252 additions and 252 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
1 change: 1 addition & 0 deletions buildSrc/src/main/kotlin/babbage.conventions.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
@@ -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())
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<StatusDetailIndicator>,
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"
}

Expand All @@ -37,20 +41,42 @@ 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<String, List<StatusDetail>>): 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<String, Any> } ?: return ""
val commitInfos = gitInfos["commit"]?.let { it as Map<String, Any> } ?: return ""
return commitInfos["time"]?.toString() ?: ""
}
}

private fun Set<Status>.worstStatus(): Status {
return if (this.contains(ERROR)) {
ERROR
} else if (this.contains(WARNING)) {
WARNING
} else {
OK
}
}
Original file line number Diff line number Diff line change
@@ -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<String, List<StatusDetail>>,
val system: SystemInfo,
val serviceStatus: de.otto.babbage.core.status.indicators.Status
)

data class Application(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(),
Expand All @@ -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<String, String> {
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
)
}

}
Original file line number Diff line number Diff line change
@@ -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

}
27 changes: 27 additions & 0 deletions core/src/main/resources/static/main.css
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
1 change: 1 addition & 0 deletions core/src/main/resources/templates/fragments/head.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<!-- Bootstrap -->
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<link rel="stylesheet" th:href="${basePath} + '/main.css'"></link>
</head>
<body>
</body>
Expand Down
13 changes: 13 additions & 0 deletions core/src/main/resources/templates/fragments/status-badge.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head></head>
<body>
<div th:fragment="status_badge(status)">
<div>
<span th:if="${status.isOk}" class="badge text-bg-success">OK</span>
<span th:if="${status.isWarning}" class="badge text-bg-warning">WARNING</span>
<span th:if="${status.isError}" class="badge text-bg-danger">ERROR</span>
</div>
</div>
</body>
</html>
65 changes: 54 additions & 11 deletions core/src/main/resources/templates/status.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,60 @@
<body>
<div th:replace="~{fragments/navigation :: navigation}"></div>

<div class="container">
<div class="row">
<div class="col-md-6">
<dl>
<dt>Status:</dt>
<dd th:text="${applicationStatus}">Status</dd>
<dt>Commit:</dt>
<dd th:text="${applicationCommit}">Commit</dd>
<dt>Version:</dt>
<dd th:text="${applicationVersion}">Version</dd>
</dl>
<div class="container-fluid status-container">

<div class="cards">
<div class="card" th:with="version=${statusData.application}">
<div class="card-body">
<h5 class="card-title">Version</h5>
<div class="card-text">
<div>Version: <span th:text="${version.version}"></span></div>
<div>Commit: <span th:text="${version.commit}"></span></div>
</div>
</div>
</div>

<div class="card" th:with="system=${statusData.system}">
<div class="card-body">
<h5 class="card-title">System</h5>
<div class="card-text">
<div>Started at: <span th:text="${#temporals.format(system.startTime, 'dd-MM-yyyy HH:mm')}"></span>
</div>
<div>Up-Time: <span th:text="${system.upTime}"></span></div>
<div>System-Time: <span
th:text="${#temporals.format(system.systemTime, 'dd-MM-yyyy HH:mm')}"></span></div>
<div>Port: <span th:text="${system.port}"></span></div>
<div>Hostname: <span th:text="${system.hostname}"></span></div>
</div>
</div>
</div>
</div>

<div class="status-details">
<h5>Status:
<div style="display: inline-block"
th:replace="~{ fragments/status-badge :: status_badge(status=${statusData.serviceStatus}) }"></div>
</h5>
<div th:each="statusGroup : ${statusData.indicators}" class="status-detail-group-container">
<h6 th:text="${statusGroup.key}"></h6>
<table class="table table-bordered">
<thead>
<tr>
<th scope="col" class="status-column">Status</th>
<th scope="col">Name</th>
<th scope="col">Message</th>
</tr>
</thead>
<tbody>
<tr th:each="statusDetail : ${statusGroup.value}">
<td>
<div th:replace="~{ fragments/status-badge :: status_badge(status=${statusDetail.status}) }"></div>
</td>
<td th:text="${statusDetail.name}"></td>
<td th:text="${statusDetail.message}"></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
Expand Down
Loading

0 comments on commit 5ddac40

Please sign in to comment.