Add Nina Warnings widget and integrate BBK warning API

This commit is contained in:
Arindy 2025-12-29 17:56:50 +01:00
parent 52d3b1196b
commit d638a8ae97
4 changed files with 223 additions and 45 deletions

89
apis/nina/main.go Normal file
View File

@ -0,0 +1,89 @@
package nina
import (
"ArinDash/config"
"encoding/json"
"io"
"log"
"net/http"
)
const apiBaseURL = "https://warnung.bund.de/api31"
type configFile struct {
Nina ninaConfig
}
type ninaConfig struct {
GebietsCode string
}
type Warning struct {
Id string `json:"id"`
Identifier string `json:"identifier"`
Sender string `json:"sender"`
Sent string `json:"sent"`
Status string `json:"status"`
MsgType string `json:"msgType"`
Scope string `json:"scope"`
Code []string `json:"code"`
Reference string `json:"reference"`
Info []Info `json:"info"`
}
type Info struct {
Language string `json:"language"`
Category []string `json:"category"`
Event string `json:"event"`
Urgency string `json:"urgency"`
Severity string `json:"severity"`
Expires string `json:"expires"`
Headline string `json:"headline"`
Description string `json:"description"`
}
func FetchWarnings() []Warning {
cfg := &configFile{}
config.LoadConfig(cfg)
client := &http.Client{}
req, err := http.NewRequest("GET", apiBaseURL+"/dashboard/"+cfg.Nina.GebietsCode+".json", nil)
if err != nil {
panic(err)
}
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
warnings := make([]Warning, 0)
err = json.Unmarshal(respBody, &warnings)
if err != nil {
log.Fatal(err)
}
result := make([]Warning, 0)
for _, warning := range warnings {
req, err := http.NewRequest("GET", apiBaseURL+"/warnings/"+warning.Id+".json", nil)
if err != nil {
panic(err)
}
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
warning := Warning{}
err = json.Unmarshal(respBody, &warning)
result = append(result, warning)
}
return result
}

View File

@ -13,3 +13,7 @@ Password = "YourPassword"
#ID from http://bulk.openweathermap.org/sample/city.list.json.gz; unzip the gz file and find your city
LocationId = "0000000"
ApiKey = "YourApiKey"
[nina]
#Code from https://www.xrepository.de/api/xrepository/urn:de:bund:destatis:bevoelkerungsstatistik:schluessel:rs_2021-07-31/download/Regionalschl_ssel_2021-07-31.json
gebietsCode = "000000000000"

38
main.go
View File

@ -43,12 +43,14 @@ func initWidgets(ctx context.Context, term *tcell.Terminal) {
widgets.New("HTTPProber", widgets.HTTPProber()),
widgets.New("PiHole", widgets.PiholeStats()),
widgets.New("PiHoleBlocked", widgets.PiholeBlocked()),
widgets.New("NinaWarnings", widgets.NinaWarnings()),
)
}
func layout() []container.Option {
builder := grid.New()
builder.Add(
grid.ColWidthFixed(84,
grid.RowHeightFixed(44,
grid.Widget(widgets.Get["Wifi"],
@ -56,7 +58,7 @@ func layout() []container.Option {
container.Border(linestyle.Light),
container.BorderColor(cell.ColorWhite)),
),
grid.RowHeightFixed(44,
grid.RowHeightFixed(46,
grid.Widget(widgets.Get["NetworkDevices"],
container.BorderTitle("Network Devices"),
container.Border(linestyle.Light),
@ -66,7 +68,10 @@ func layout() []container.Option {
grid.Widget(widgets.Get["empty"]),
),
),
grid.ColWidthPerc(20,
grid.ColWidthPerc(58,
grid.RowHeightFixed(44,
grid.ColWidthFixed(40,
grid.RowHeightFixed(8,
grid.Widget(widgets.Get["Clock"],
container.AlignHorizontal(align.HorizontalCenter),
@ -82,29 +87,31 @@ func layout() []container.Option {
container.BorderColor(cell.ColorWhite),
),
),
grid.RowHeightFixed(24,
grid.RowHeightFixed(40,
grid.Widget(widgets.Get["PiHoleBlocked"],
container.BorderTitle("pi-hole (Blocked Percent)"),
container.Border(linestyle.Light),
container.BorderColor(cell.ColorWhite),
),
),
grid.RowHeightPerc(85,
grid.RowHeightFixed(85,
grid.Widget(widgets.Get["empty"]),
),
),
grid.ColWidthPerc(13,
grid.ColWidthFixed(26,
grid.RowHeightFixed(20,
grid.Widget(widgets.Get["Calendar"],
container.BorderTitle("Calendar"),
container.Border(linestyle.Light),
container.BorderColor(cell.ColorWhite)),
),
grid.RowHeightPerc(25,
grid.RowHeightFixed(25,
grid.Widget(widgets.Get["empty"]),
),
),
grid.ColWidthPerc(25,
grid.ColWidthFixed(25,
grid.RowHeightFixed(20,
grid.Widget(widgets.Get["Weather"],
container.BorderTitle("Weather"),
@ -112,10 +119,25 @@ func layout() []container.Option {
container.BorderColor(cell.ColorWhite),
),
),
grid.RowHeightPerc(25,
grid.RowHeightFixed(85,
grid.Widget(widgets.Get["empty"]),
),
),
),
grid.RowHeightFixed(20,
grid.RowHeightFixed(25,
grid.Widget(widgets.Get["empty"]),
),
grid.RowHeightFixed(20,
grid.Widget(widgets.Get["NinaWarnings"],
container.BorderTitle("BBK Warnings"),
container.Border(linestyle.Light),
container.BorderColor(cell.ColorWhite),
),
),
),
),
grid.ColWidthPerc(35,
grid.RowHeightPerc(20,
grid.Widget(widgets.Get["HTTPProber"],

63
widgets/ninaWarnings.go Normal file
View File

@ -0,0 +1,63 @@
package widgets
import (
"ArinDash/apis/nina"
"ArinDash/util"
"context"
"fmt"
"regexp"
"time"
"github.com/mum4k/termdash/cell"
"github.com/mum4k/termdash/terminal/terminalapi"
"github.com/mum4k/termdash/widgetapi"
"github.com/mum4k/termdash/widgets/text"
)
type NinaWarningsOptions struct {
}
func NinaWarnings() NinaWarningsOptions {
widgetOptions["NinaWarningsOptions"] = createNinaWarnings
return NinaWarningsOptions{}
}
func createNinaWarnings(ctx context.Context, _ terminalapi.Terminal, _ interface{}) widgetapi.Widget {
widget := util.PanicOnErrorWithResult(text.New(text.WrapAtWords()))
go util.Periodic(ctx, 1*time.Minute, func() error {
warnings := nina.FetchWarnings()
widget.Reset()
for _, warning := range warnings {
for _, info := range warning.Info {
var options []cell.Option
if info.Severity == "Moderate" {
options = append(options, cell.FgColor(cell.ColorRed))
} else if info.Severity == "Minor" {
options = append(options, cell.FgColor(cell.ColorYellow))
} else if info.Severity == "Fine" {
options = append(options, cell.FgColor(cell.ColorGray))
} else if info.Severity == "Cancel" {
options = append(options, cell.FgColor(cell.ColorGreen))
} else {
options = append(options, cell.FgColor(cell.ColorRed))
options = append(options, cell.Blink())
}
if err := widget.Write(fmt.Sprintf("%s\n\n", info.Headline), text.WriteCellOpts(append(options, cell.Bold())...)); err != nil {
return err
}
if err := widget.Write(fmt.Sprintf("%s\n\n\n", removeElements(info.Description)), text.WriteCellOpts(options...)); err != nil {
return err
}
}
}
return nil
})
return widget
}
func removeElements(input string) string {
// Pattern to match <tag>content</tag> or <tag/>
re := regexp.MustCompile(`<a .*>.*</a>|<br>|&nbsp;`)
return re.ReplaceAllString(input, "")
}