Skip to content

Commit

Permalink
Feature/7 status indicator in dashboard (#8)
Browse files Browse the repository at this point in the history
* renaming of handler file + env file example + utility for env file reading

* added gotenv

* work on progress on health indicator in header bar based on backend connection

* progress on health indicator in dashboard

* scope change on indicator

* fix reportingdashboard

* fixed issue on make file + finished healthy indicator wrote handler and right polling

* added styles auto gen to gitignore

* removed styles from repo as auto generated

* bump templ version

* fix templ version
  • Loading branch information
RabbITCybErSeC committed Jul 9, 2024
1 parent 1626377 commit 8a5cea8
Show file tree
Hide file tree
Showing 13 changed files with 199 additions and 22 deletions.
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
SOARCA_URI: http://localhost:8080
GIN_MODE: "release"
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,6 @@ docs/package-lock.json
docs/.hugo_build.lock
**.hugo_build.lock
**_templ.go
tmp/*
tmp/*

public/public/styles.css
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ lint: build-templ
GOFLAGS=-buildvcs=false golangci-lint run --timeout 5m0s -v

clean:
find . -type f -name '*_templ.go' -exec rm -f {} ls **/*_templ.go
find . -type f -name '*_templ.go' -exec rm -f {} \;



.DEFAULT_GOAL := dev
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module soarca-gui
go 1.22.2

require (
github.com/a-h/templ v0.2.707
github.com/a-h/templ v0.2.747
github.com/gin-gonic/gin v1.10.0
)

Expand All @@ -18,6 +18,7 @@ require (
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/joho/godotenv v1.5.1
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
Expand All @@ -30,7 +31,7 @@ require (
golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/text v0.15.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down
10 changes: 6 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
github.com/a-h/templ v0.2.707 h1:T1Gkd2ugbRglZ9rYw/VBchWOSZVKmetDbBkm4YubM7U=
github.com/a-h/templ v0.2.707/go.mod h1:5cqsugkq9IerRNucNsI4DEamdHPsoGMQy99DzydLhM8=
github.com/a-h/templ v0.2.747 h1:D0dQ2lxC3W7Dxl6fxQ/1zZHBQslSkTSvl5FxP/CfdKg=
github.com/a-h/templ v0.2.747/go.mod h1:69ObQIbrcuwPCU32ohNaWce3Cb7qM5GMiqN1K+2yop4=
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
Expand Down Expand Up @@ -30,6 +30,8 @@ github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MG
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
Expand Down Expand Up @@ -74,8 +76,8 @@ golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
Expand Down
1 change: 1 addition & 0 deletions handlers/dashboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ func ReportingCard(context *gin.Context) {
Value: 10,
Name: "Executed Playbooks",
}

render := utils.NewTempl(context, http.StatusOK, components.ReportingCard(updatedCard))

context.Render(http.StatusOK, render)
Expand Down
62 changes: 62 additions & 0 deletions handlers/status.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package handlers

import (
"fmt"
"io"
"net/http"

"soarca-gui/utils"
"soarca-gui/views/components"

"github.com/gin-gonic/gin"
)

const (
statusPingPath = "/status/ping"
)

type statusHandler struct {
Host string
}

func NewStatusHandler(host string) statusHandler {
return statusHandler{Host: host}
}

func (s *statusHandler) HealthComponentHandler(context *gin.Context) {
response, err := s.getPongFromStatus()
indicatorData := components.HealthIndicatorData{Loaded: true}

switch {
case err != nil:
indicatorData.Healthy = false
indicatorData.Message = "error on backend call"
case response == "pong":
indicatorData.Healthy = true
indicatorData.Message = "connected"
default:
indicatorData.Healthy = false
indicatorData.Message = "wrong msg backend"
}

render := utils.NewTempl(context, http.StatusOK, components.HealthIndicator(indicatorData))
context.Render(http.StatusOK, render)
}

func (s *statusHandler) getPongFromStatus() (string, error) {
response, err := http.Get(fmt.Sprintf("%s%s", s.Host, statusPingPath))
if err != nil {
return "", fmt.Errorf("failed to make GET request: %w", err)
}
defer response.Body.Close()

if response.StatusCode != http.StatusOK {
return "", fmt.Errorf("unexpected status code: %d", response.StatusCode)
}
body, err := io.ReadAll(response.Body)
if err != nil {
return "", fmt.Errorf("failed to read response body: %w", err)
}

return string(body), nil
}
1 change: 0 additions & 1 deletion public/public/styles.css

This file was deleted.

11 changes: 11 additions & 0 deletions routes/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package routes
import (
"soarca-gui/handlers"
"soarca-gui/public"
"soarca-gui/utils"

"github.com/gin-gonic/gin"
)
Expand All @@ -17,6 +18,7 @@ func Setup(app *gin.Engine) {

PublicRoutes(publicRoutes)
Reporting(publicRoutes)
StatusGroup(publicRoutes)
}

func PublicRoutes(app *gin.RouterGroup) {
Expand All @@ -40,3 +42,12 @@ func Reporting(app *gin.RouterGroup) {
reportingRoute.GET("/reportingcard/:id", handlers.ReportingCard)
}
}

func StatusGroup(app *gin.RouterGroup) {
statusHandler := handlers.NewStatusHandler(utils.GetEnv("SOARCA_URI", "http://localhost:8080"))

statusRoute := app.Group("/status")
{
statusRoute.GET("/indicator/card", statusHandler.HealthComponentHandler)
}
}
9 changes: 9 additions & 0 deletions server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,23 @@ import (
"soarca-gui/routes"

"github.com/gin-gonic/gin"
"github.com/joho/godotenv"
)

var (
Version string
Buildtime string
)

// @title SOARCA-GUI
// @version 0.0.1
func main() {
errenv := godotenv.Load(".env")

if errenv != nil {
fmt.Println("Failed to read env variable, but will continue")
}

app := gin.Default()
routes.Setup(app)

Expand Down
10 changes: 10 additions & 0 deletions utils/env_reader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package utils

import "os"

func GetEnv(key, fallback string) string {
if value, ok := os.LookupEnv(key); ok {
return value
}
return fallback
}
30 changes: 17 additions & 13 deletions views/components/headbar.templ
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,23 @@ package components


templ HeaderBar() {
<header class="w-full items-center bg-slate-50 py-2 px-6 hidden sm:flex">
<div class="w-1/2"></div>
<header class="w-full items-center bg-slate-50 py-2 px-6 sm:flex">

<div x-data="{ isOpen: false }" class="relative w-1/2 flex justify-end">
<button @click="isOpen = !isOpen" class="realtive z-10 w-12 h-12 rounded-full overflow-hidden border-4 border-gray-400 hover:border-blue-600 focus:border-blue-600 focus:outline-none">
<img src="https://source.unsplash.com/uJ8LNVCBjFQ/400x400">
</button>
<button x-show="isOpen" @click="isOpen = false" class="h-full w-full fixed inset-0 cursor-default"></button>
<div x-show="isOpen" class="absolute w-32 bg-white rounded-lg shadow-lg py-2 mt-16 z-50">
<a href="#" class="block px-4 py-2 account-link hover:text-blue-600">Account</a>
<a href="#" class="block px-4 py-2 account-link hover:text-blue-600">Settings</a>
<a href="#" class="block px-4 py-2 account-link hover:text-blue-600">Sign Out</a>
</div>
@HealthIndicator(HealthIndicatorData{Loaded: false})

<div class="w-1/2"></div>
<div x-data="{ isOpen: false }" class="relative w-1/2 flex justify-end">
<button @click="isOpen = !isOpen"
class="relative z-10 w-12 h-12 rounded-full overflow-hidden border-4 border-gray-400 hover:border-blue-600 focus:border-blue-600 focus:outline-none">
<img src="https://source.unsplash.com/uJ8LNVCBjFQ/400x400" alt="Profile image">
</button>
<button x-show="isOpen" @click="isOpen = false" class="h-full w-full fixed inset-0 cursor-default"></button>
<div x-show="isOpen" class="absolute w-32 bg-white rounded-lg shadow-lg py-2 mt-16 z-50">
<a href="#" class="block px-4 py-2 account-link hover:text-blue-600">Account</a>
<a href="#" class="block px-4 py-2 account-link hover:text-blue-600">Settings</a>
<a href="#" class="block px-4 py-2 account-link hover:text-blue-600">Sign Out</a>
</div>
</header>
</div>
</header>

}
73 changes: 73 additions & 0 deletions views/components/health_indicator.templ
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package components



import "fmt"


const (
statusCardEndpoint = "/status/indicator/card"
pollInterval = "every 30s"
)
type HealthIndicatorData struct {
Loaded bool
Message string
Healthy bool
}




templ HealthIndicator(data HealthIndicatorData) {

if !data.Loaded {
<div
hx-get={ string(templ.URL(fmt.Sprintf(statusCardEndpoint))) }
hx-trigger="load"
hx-swap="outerHTML"
class="relative"
>
@indicator(false, "not loaded")
</div>

} else {
<div
hx-get={ string(templ.URL(fmt.Sprintf(statusCardEndpoint))) }
hx-trigger={ pollInterval }
hx-swap="innerHTML"
class="relative"
>
@indicator(data.Healthy, data.Message)
</div>
}

}

templ indicator(healthOk bool, message string) {
<span x-data="{ showTooltip: false }"
@mouseover="showTooltip = true"
@mouseout="showTooltip = false"
class="relative flex items-center"
x-bind:title="showTooltip ? '' : ''"
>
if healthOk {
<div class="relative inline-flex items-center">
<span class="relative inline-flex rounded-full h-3 w-3 bg-green-500"></span>
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"></span>

<div x-show="showTooltip" class="absolute left-2 top-1/2 transform -translate-y-1/2 mt-5 p-1 bg-white text-center rounded-br-lg rounded-bl-lg rounded-tr-lg shadow-lg">
<h1 class="text-sm text-green-500 whitespace-nowrap">{ message }</h1>
</div>
</div>
} else {
<div class="relative inline-flex items-center">
<span class="relative inline-flex rounded-full h-3 w-3 bg-red-500"></span>
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-400 opacity-75"></span>

<div x-show="showTooltip" class="absolute left-2 top-1/2 transform -translate-y-1/2 mt-5 p-1 bg-white text-center rounded-br-lg rounded-bl-lg rounded-tr-lg shadow-lg">
<h1 class="text-sm text-red-500 whitespace-nowrap">{ message }</h1>
</div>
</div>
}
</span>
}

0 comments on commit 8a5cea8

Please sign in to comment.