Ensure consistent error handling and improve resource cleanup across widgets and APIs.
This commit is contained in:
parent
af5a39ef75
commit
2a66278cae
16
README.md
16
README.md
@ -10,9 +10,10 @@ ArinDash is a customizable terminal dashboard (TUI) written in Go, powered by [`
|
||||
- **Docker Monitoring**: View the status of your Docker containers.
|
||||
- **HTTP Prober**: Check the availability and status codes of your favorite websites.
|
||||
- **Network Device Monitoring**: Track which devices are online in your local network.
|
||||
- **WiFi QR Code**: Display a QR code for easy WiFi connection sharing.
|
||||
- **Wi-Fi QR Code**: Display a QR code for easy Wi-Fi connection sharing.
|
||||
- **NINA Warnings**: Stay informed about emergency warnings in Germany via the NINA-Service.
|
||||
- **News**: Get the latest news headlines from various sources via News API.
|
||||
- **Every Day Mood**: Track your daily mood with an interactive calendar.
|
||||
- **Zukitchi**: A cute animated terminal pet/sprite to keep you company.
|
||||
- **Interactive TUI**: Built with `termdash` for a responsive and visually appealing terminal interface.
|
||||
|
||||
@ -46,7 +47,7 @@ ArinDash uses a `config.toml` file for its settings. A template is provided as `
|
||||
|
||||
2. Edit `config.toml` with your specific details:
|
||||
- **Pi-hole**: Set your host and API password.
|
||||
- **WiFi**: Configure your SSID and password for the QR code widget.
|
||||
- **Wi-Fi**: Configure your SSID and password for the QR code widget.
|
||||
- **Weather**: Provide your OpenWeatherMap API key and Location ID. You can find your Location ID in the `city.list.json.gz` from [OpenWeatherMap bulk data](http://bulk.openweathermap.org/sample/).
|
||||
- **NINA Warnings**: Set your `gebietsCode` for the area you want to monitor.
|
||||
- **News**: Provide your News API key and preferred sources.
|
||||
@ -57,7 +58,7 @@ ArinDash uses a `config.toml` file for its settings. A template is provided as `
|
||||
|
||||
## Usage
|
||||
|
||||
To run ArinDash, simply execute:
|
||||
To run ArinDash, execute:
|
||||
|
||||
```bash
|
||||
go run main.go
|
||||
@ -65,7 +66,12 @@ go run main.go
|
||||
|
||||
### Controls
|
||||
- **Esc / Ctrl+C**: Exit the application.
|
||||
- **Left / Right Arrow Keys**: Navigate through news articles (in the News widget).
|
||||
- **News Widget**:
|
||||
- **Left / Right Arrow Keys**: Navigate through news articles.
|
||||
- **Every Day Mood Widget**:
|
||||
- **Arrow Keys**: Navigate through the calendar days and months.
|
||||
- **1, 2, 3**: Assign a mood.
|
||||
- **Backspace**: Clear the mood for the selected day.
|
||||
|
||||
## Project Structure
|
||||
|
||||
@ -77,4 +83,4 @@ go run main.go
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
||||
This project is licensed under the MIT License – see the [LICENSE](LICENSE) file for details.
|
||||
|
||||
@ -3,6 +3,7 @@ package docker
|
||||
import (
|
||||
"ArinDash/util"
|
||||
"context"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
containertypes "github.com/docker/docker/api/types/container"
|
||||
@ -34,7 +35,12 @@ func FetchDockerMetrics(ctx context.Context) ([]ContainerMetrics, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cli.Close()
|
||||
defer func(cli *client.Client) {
|
||||
err := cli.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(cli)
|
||||
|
||||
_, err = cli.Ping(ctx)
|
||||
if err != nil {
|
||||
@ -60,7 +66,12 @@ func FetchDockerMetrics(ctx context.Context) ([]ContainerMetrics, error) {
|
||||
continue
|
||||
}
|
||||
func() {
|
||||
defer stats.Body.Close()
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
|
||||
}
|
||||
}(stats.Body)
|
||||
var sj containertypes.StatsResponse
|
||||
if err := util.DecodeJSON(stats.Body, &sj); err != nil {
|
||||
return
|
||||
@ -109,9 +120,7 @@ func cpuPercentFromStats(s containertypes.StatsResponse) float64 {
|
||||
func memoryFromStats(s containertypes.StatsResponse) (usage, limit uint64, percent float64) {
|
||||
usage = s.MemoryStats.Usage
|
||||
limit = s.MemoryStats.Limit
|
||||
// Optionally discount cached memory if stats present.
|
||||
if stats := s.MemoryStats.Stats; stats != nil {
|
||||
// The common Linux approach: usage - cache
|
||||
if cache, ok := stats["cache"]; ok && cache <= usage {
|
||||
usage -= cache
|
||||
}
|
||||
|
||||
@ -11,6 +11,8 @@ import (
|
||||
|
||||
const apiBaseURL = "https://newsapi.org/v2/top-headlines"
|
||||
|
||||
var forbiddenStrings = []string{"\r", "\\r", "\ufeff", "\u00A0"}
|
||||
|
||||
type configFile struct {
|
||||
News newsConfig
|
||||
}
|
||||
@ -65,7 +67,12 @@ func FetchNews() News {
|
||||
Status: err.Error(),
|
||||
}
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(resp.Body)
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return News{
|
||||
@ -75,7 +82,7 @@ func FetchNews() News {
|
||||
|
||||
news := News{}
|
||||
|
||||
err = json.Unmarshal([]byte(strings.ReplaceAll(strings.ReplaceAll(string(respBody), "\r", ""), "\u00A0", "")), &news)
|
||||
err = json.Unmarshal([]byte(removeForbiddenStrings(string(respBody))), &news)
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
@ -83,3 +90,11 @@ func FetchNews() News {
|
||||
|
||||
return news
|
||||
}
|
||||
|
||||
func removeForbiddenStrings(s string) string {
|
||||
result := s
|
||||
for _, forbidden := range forbiddenStrings {
|
||||
result = strings.ReplaceAll(result, forbidden, "")
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@ -57,7 +57,12 @@ func FetchWarnings() []Warning {
|
||||
Errormsg: err.Error(),
|
||||
})
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(resp.Body)
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return append(make([]Warning, 0), Warning{
|
||||
@ -86,7 +91,12 @@ func FetchWarnings() []Warning {
|
||||
Errormsg: err.Error(),
|
||||
})
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(resp.Body)
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return append(make([]Warning, 0), Warning{
|
||||
|
||||
@ -41,7 +41,12 @@ func FetchCurrentWeather() CurrentWeather {
|
||||
ErrorMessage: err.Error(),
|
||||
}
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(resp.Body)
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return CurrentWeather{
|
||||
|
||||
@ -59,7 +59,12 @@ func (ph *PiHConnector) do(method string, endpoint string, body io.Reader) []byt
|
||||
if err != nil {
|
||||
return make([]byte, 0)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(resp.Body)
|
||||
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
@ -90,7 +95,12 @@ func Connect() PiHConnector {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(resp.Body)
|
||||
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
|
||||
@ -5,7 +5,6 @@ import (
|
||||
"ArinDash/util"
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mum4k/termdash/cell"
|
||||
@ -55,33 +54,26 @@ func (widget *NewsText) drawNews() error {
|
||||
return nil
|
||||
}
|
||||
if err := widget.Write(fmt.Sprintf("%d/%d\n", selected+1, len(widget.newsArticles.Articles)), text.WriteCellOpts(cell.FgColor(cell.ColorWhite))); err != nil {
|
||||
widget.Write(fmt.Sprintf("Error: %s", err.Error()), text.WriteCellOpts(cell.FgColor(cell.ColorRed)))
|
||||
return err
|
||||
return widget.Write(fmt.Sprintf("Error: %s", err.Error()), text.WriteCellOpts(cell.FgColor(cell.ColorRed)))
|
||||
}
|
||||
article := widget.newsArticles.Articles[selected]
|
||||
if err := widget.Write(fmt.Sprintf("%s\n", article.Author), text.WriteCellOpts(cell.FgColor(cell.ColorGray))); err != nil {
|
||||
widget.Write(fmt.Sprintf("Error: %s", err.Error()), text.WriteCellOpts(cell.FgColor(cell.ColorRed)))
|
||||
return err
|
||||
return widget.Write(fmt.Sprintf("Error: %s", err.Error()), text.WriteCellOpts(cell.FgColor(cell.ColorRed)))
|
||||
}
|
||||
if err := widget.Write(fmt.Sprintf("%s\n\n", article.Title), text.WriteCellOpts(cell.FgColor(cell.ColorWhite), cell.Bold())); err != nil {
|
||||
widget.Write(fmt.Sprintf("Error: %s", err.Error()), text.WriteCellOpts(cell.FgColor(cell.ColorRed)))
|
||||
return err
|
||||
return widget.Write(fmt.Sprintf("Error: %s", err.Error()), text.WriteCellOpts(cell.FgColor(cell.ColorRed)))
|
||||
}
|
||||
if err := widget.Write(fmt.Sprintf("%s\n\n", article.Description), text.WriteCellOpts(cell.FgColor(cell.ColorWhite))); err != nil {
|
||||
widget.Write(fmt.Sprintf("Error: %s", err.Error()), text.WriteCellOpts(cell.FgColor(cell.ColorRed)))
|
||||
return err
|
||||
return widget.Write(fmt.Sprintf("Error: %s", err.Error()), text.WriteCellOpts(cell.FgColor(cell.ColorRed)))
|
||||
}
|
||||
if err := widget.Write(fmt.Sprintf("%s\n", strings.ReplaceAll(article.Content, "\r", "")), text.WriteCellOpts(cell.FgColor(cell.ColorWhite))); err != nil {
|
||||
widget.Write(fmt.Sprintf("Error: %s", err.Error()), text.WriteCellOpts(cell.FgColor(cell.ColorRed)))
|
||||
return err
|
||||
if err := widget.Write(fmt.Sprintf("%s\n", article.Content), text.WriteCellOpts(cell.FgColor(cell.ColorWhite))); err != nil {
|
||||
return widget.Write(fmt.Sprintf("Error: %s", err.Error()), text.WriteCellOpts(cell.FgColor(cell.ColorRed)))
|
||||
}
|
||||
if err := widget.Write(fmt.Sprintf("%s\n\n", article.PublishedAt), text.WriteCellOpts(cell.FgColor(cell.ColorGray))); err != nil {
|
||||
widget.Write(fmt.Sprintf("Error: %s", err.Error()), text.WriteCellOpts(cell.FgColor(cell.ColorRed)))
|
||||
return err
|
||||
return widget.Write(fmt.Sprintf("Error: %s", err.Error()), text.WriteCellOpts(cell.FgColor(cell.ColorRed)))
|
||||
}
|
||||
if err := widget.Write(fmt.Sprintf("%s\n", article.URL)); err != nil {
|
||||
widget.Write(fmt.Sprintf("Error: %s", err.Error()), text.WriteCellOpts(cell.FgColor(cell.ColorRed)))
|
||||
return err
|
||||
return widget.Write(fmt.Sprintf("Error: %s", err.Error()), text.WriteCellOpts(cell.FgColor(cell.ColorRed)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -28,8 +28,7 @@ func createWeather(ctx context.Context, _ terminalapi.Terminal, _ interface{}) w
|
||||
weather := openWeatherMap.FetchCurrentWeather()
|
||||
widget.Reset()
|
||||
if weather.ErrorMessage != "" {
|
||||
widget.Write(weather.ErrorMessage, text.WriteCellOpts(cell.FgColor(cell.ColorRed)))
|
||||
return nil
|
||||
return widget.Write(weather.ErrorMessage, text.WriteCellOpts(cell.FgColor(cell.ColorRed)))
|
||||
}
|
||||
|
||||
weatherIcon := getIcon(weather.Weather[0].Icon)
|
||||
|
||||
@ -289,25 +289,20 @@ func (widget *MoodText) Keyboard(k *terminalapi.Keyboard, _ *widgetapi.EventMeta
|
||||
}
|
||||
break
|
||||
case keyboard.KeyBackspace, keyboard.KeyBackspace2:
|
||||
widget.WriteMood(0)
|
||||
break
|
||||
return widget.WriteMood(0)
|
||||
case '1':
|
||||
widget.WriteMood(1)
|
||||
break
|
||||
return widget.WriteMood(1)
|
||||
case '2':
|
||||
widget.WriteMood(2)
|
||||
break
|
||||
return widget.WriteMood(2)
|
||||
case '3':
|
||||
widget.WriteMood(3)
|
||||
break
|
||||
return widget.WriteMood(3)
|
||||
default:
|
||||
widget.WriteMood(-1)
|
||||
break
|
||||
return widget.WriteMood(-1)
|
||||
}
|
||||
return widget.drawTable()
|
||||
}
|
||||
|
||||
func (widget *MoodText) WriteMood(mood int) {
|
||||
func (widget *MoodText) WriteMood(mood int) error {
|
||||
m := moods()
|
||||
if m[strconv.Itoa(time.Now().Year())] == nil {
|
||||
m[strconv.Itoa(time.Now().Year())] = make(map[string]map[string]int)
|
||||
@ -318,10 +313,11 @@ func (widget *MoodText) WriteMood(mood int) {
|
||||
m[strconv.Itoa(time.Now().Year())][strconv.Itoa(int(widget.selectedMonth))][strconv.Itoa(widget.selectedDay)] = mood
|
||||
newMoods, err := json.MarshalIndent(m, "", " ")
|
||||
if err != nil {
|
||||
widget.Write(fmt.Sprintf("Error: %s", err.Error()), text.WriteCellOpts(cell.FgColor(cell.ColorRed)))
|
||||
return widget.Write(fmt.Sprintf("Error: %s", err.Error()), text.WriteCellOpts(cell.FgColor(cell.ColorRed)))
|
||||
|
||||
}
|
||||
if err := os.WriteFile(moodsFile, newMoods, 0644); err != nil {
|
||||
widget.Write(fmt.Sprintf("Error: %s", err.Error()), text.WriteCellOpts(cell.FgColor(cell.ColorRed)))
|
||||
// ignore
|
||||
return widget.Write(fmt.Sprintf("Error: %s", err.Error()), text.WriteCellOpts(cell.FgColor(cell.ColorRed)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user