Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/7 status indicator in dashboard #8

Merged
merged 12 commits into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -33,6 +33,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 @@ -11,6 +12,7 @@ func Setup(app *gin.Engine) {
publicRoutes := app.Group("/")
PublicRoutes(publicRoutes)
Reporting(publicRoutes)
StatusGroup(publicRoutes)
}

func PublicRoutes(app *gin.RouterGroup) {
Expand All @@ -33,3 +35,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>
}