first commit
This commit is contained in:
57
widgets/clock.go
Normal file
57
widgets/clock.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package widgets
|
||||
|
||||
import (
|
||||
"ArinDash/util"
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mum4k/termdash/align"
|
||||
"github.com/mum4k/termdash/cell"
|
||||
"github.com/mum4k/termdash/terminal/terminalapi"
|
||||
"github.com/mum4k/termdash/widgetapi"
|
||||
"github.com/mum4k/termdash/widgets/segmentdisplay"
|
||||
)
|
||||
|
||||
type ClockOptions struct {
|
||||
}
|
||||
|
||||
func Clock() ClockOptions {
|
||||
widgetOptions["ClockOptions"] = createClock
|
||||
return ClockOptions{}
|
||||
|
||||
}
|
||||
|
||||
func createClock(ctx context.Context, _ terminalapi.Terminal, _ interface{}) widgetapi.Widget {
|
||||
widget := util.PanicOnErrorWithResult(segmentdisplay.New())
|
||||
|
||||
go util.Periodic(ctx, util.RedrawInterval, func() error {
|
||||
ticker := time.NewTicker(1 * time.Second)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
now := time.Now()
|
||||
nowStr := now.Format("15 04")
|
||||
parts := strings.Split(nowStr, " ")
|
||||
|
||||
spacer := " "
|
||||
if now.Second()%2 == 0 {
|
||||
spacer = ":"
|
||||
}
|
||||
chunks := []*segmentdisplay.TextChunk{
|
||||
segmentdisplay.NewChunk(parts[0], segmentdisplay.WriteCellOpts(cell.FgColor(cell.ColorWhite))),
|
||||
segmentdisplay.NewChunk(spacer),
|
||||
segmentdisplay.NewChunk(parts[1], segmentdisplay.WriteCellOpts(cell.FgColor(cell.ColorWhite))),
|
||||
}
|
||||
if err := widget.Write(chunks, segmentdisplay.AlignHorizontal(align.HorizontalCenter), segmentdisplay.AlignVertical(align.VerticalBottom)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
})
|
||||
return widget
|
||||
}
|
||||
97
widgets/dockerlist.go
Normal file
97
widgets/dockerlist.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package widgets
|
||||
|
||||
import (
|
||||
"ArinDash/apis/docker"
|
||||
"ArinDash/util"
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mum4k/termdash/cell"
|
||||
"github.com/mum4k/termdash/terminal/terminalapi"
|
||||
"github.com/mum4k/termdash/widgetapi"
|
||||
"github.com/mum4k/termdash/widgets/text"
|
||||
)
|
||||
|
||||
type DockerListOptions struct {
|
||||
}
|
||||
|
||||
func DockerList() DockerListOptions {
|
||||
widgetOptions["DockerListOptions"] = createDockerList
|
||||
return DockerListOptions{}
|
||||
}
|
||||
|
||||
func createDockerList(ctx context.Context, _ terminalapi.Terminal, _ interface{}) widgetapi.Widget {
|
||||
list := util.PanicOnErrorWithResult(text.New())
|
||||
|
||||
go util.Periodic(ctx, 1*time.Second, func() error {
|
||||
ms, err := docker.FetchDockerMetrics(ctx)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
sort.Slice(ms, func(i, j int) bool { return ms[i].CPUPercent > ms[j].CPUPercent })
|
||||
if err := list.Write(fmt.Sprintf("%-20s | %-6s | %-6s | %s\n", "Name", "CPU", "MEM", "Status"), text.WriteReplace()); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := list.Write(fmt.Sprintf("─────────────────────┼────────┼────────┼─────────────────────\n")); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, m := range ms {
|
||||
status := cell.ColorWhite
|
||||
if strings.Contains(m.Status, "Exited") {
|
||||
status = cell.ColorRed
|
||||
}
|
||||
if strings.Contains(m.Status, "unhealthy") {
|
||||
status = cell.ColorYellow
|
||||
}
|
||||
if strings.Contains(m.Status, "Restarting") {
|
||||
status = cell.ColorYellow
|
||||
}
|
||||
if strings.Contains(m.Status, "Paused") {
|
||||
status = cell.ColorBlue
|
||||
}
|
||||
if err := list.Write(fmt.Sprintf("%-20s", m.Name), text.WriteCellOpts(cell.FgColor(status))); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := list.Write(fmt.Sprint(" | ")); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writePercent(m.CPUPercent, list); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := list.Write(fmt.Sprint(" | ")); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writePercent(m.MemPercent, list); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := list.Write(fmt.Sprint(" | ")); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := list.Write(fmt.Sprintf("%s\n", m.Status), text.WriteCellOpts(cell.FgColor(status))); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
func writePercent(p float64, list *text.Text) error {
|
||||
color := cell.ColorWhite
|
||||
if p > 75 {
|
||||
color = cell.ColorRed
|
||||
}
|
||||
if p > 50 {
|
||||
color = cell.ColorYellow
|
||||
}
|
||||
if err := list.Write(fmt.Sprintf("%-5.1f%%", p), text.WriteCellOpts(cell.FgColor(color))); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
48
widgets/piholestats.go
Normal file
48
widgets/piholestats.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package widgets
|
||||
|
||||
import (
|
||||
"ArinDash/apis/pihole"
|
||||
"ArinDash/util"
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/mum4k/termdash/terminal/terminalapi"
|
||||
"github.com/mum4k/termdash/widgetapi"
|
||||
"github.com/mum4k/termdash/widgets/text"
|
||||
)
|
||||
|
||||
type PiholeStatsOptions struct {
|
||||
}
|
||||
|
||||
func PiholeStats() PiholeStatsOptions {
|
||||
widgetOptions["PiholeStatsOptions"] = createPiholeStats
|
||||
return PiholeStatsOptions{}
|
||||
}
|
||||
|
||||
func createPiholeStats(ctx context.Context, _ terminalapi.Terminal, _ interface{}) widgetapi.Widget {
|
||||
list := util.PanicOnErrorWithResult(text.New())
|
||||
|
||||
ph := pihole.Connect()
|
||||
go util.Periodic(ctx, 1*time.Minute, func() error {
|
||||
summary := ph.Summary()
|
||||
if err := list.Write(fmt.Sprintf("Blocked Domains: %d\n", summary.Gravity.DomainsBeingBlocked), text.WriteReplace()); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := list.Write(fmt.Sprint("---------------------------\n")); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := list.Write(fmt.Sprintf("Total Queries: %d\n", summary.Queries.Total)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := list.Write(fmt.Sprintf("Blocked Queries: %d\n", summary.Queries.Blocked)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := list.Write(fmt.Sprintf("Unique Domains: %d\n", summary.Queries.UniqueDomains)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return list
|
||||
}
|
||||
54
widgets/qrcode.go
Normal file
54
widgets/qrcode.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package widgets
|
||||
|
||||
import (
|
||||
"ArinDash/util"
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/mum4k/termdash/terminal/terminalapi"
|
||||
"github.com/mum4k/termdash/widgetapi"
|
||||
"github.com/mum4k/termdash/widgets/text"
|
||||
"github.com/skip2/go-qrcode"
|
||||
)
|
||||
|
||||
type QRCodeOptions struct {
|
||||
data string
|
||||
}
|
||||
|
||||
func QRCode(data string) QRCodeOptions {
|
||||
widgetOptions["QRCodeOptions"] = createQRCode
|
||||
return QRCodeOptions{
|
||||
data: data,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func QrCodeASCII(data string) string {
|
||||
bitmap := util.PanicOnErrorWithResult(qrcode.New(data, qrcode.Low)).Bitmap()
|
||||
|
||||
var stringBuilder strings.Builder
|
||||
for y := range bitmap {
|
||||
for x := range bitmap[y] {
|
||||
if bitmap[y][x] {
|
||||
stringBuilder.WriteString("██")
|
||||
} else {
|
||||
stringBuilder.WriteString(" ")
|
||||
}
|
||||
}
|
||||
stringBuilder.WriteByte('\n')
|
||||
}
|
||||
return stringBuilder.String()
|
||||
}
|
||||
|
||||
func createQRCode(_ context.Context, _ terminalapi.Terminal, opt interface{}) widgetapi.Widget {
|
||||
options, ok := opt.(QRCodeOptions)
|
||||
if !ok {
|
||||
panic("invalid options type")
|
||||
}
|
||||
|
||||
widget := util.PanicOnErrorWithResult(text.New())
|
||||
|
||||
util.PanicOnError(widget.Write(QrCodeASCII(options.data)))
|
||||
|
||||
return widget
|
||||
}
|
||||
112
widgets/segmentDisplay.go
Normal file
112
widgets/segmentDisplay.go
Normal file
@@ -0,0 +1,112 @@
|
||||
package widgets
|
||||
|
||||
import (
|
||||
"ArinDash/util"
|
||||
"context"
|
||||
"image"
|
||||
"time"
|
||||
|
||||
"github.com/mum4k/termdash/cell"
|
||||
"github.com/mum4k/termdash/terminal/terminalapi"
|
||||
"github.com/mum4k/termdash/widgetapi"
|
||||
"github.com/mum4k/termdash/widgets/segmentdisplay"
|
||||
)
|
||||
|
||||
type RollingSegmentDisplayOptions struct {
|
||||
UpdateText <-chan string
|
||||
}
|
||||
|
||||
func RollingSegmentDisplay(options RollingSegmentDisplayOptions) RollingSegmentDisplayOptions {
|
||||
widgetOptions["RollingSegmentDisplayOptions"] = createRollingSegmentDisplay
|
||||
|
||||
return RollingSegmentDisplayOptions{
|
||||
UpdateText: options.UpdateText,
|
||||
}
|
||||
}
|
||||
|
||||
func createRollingSegmentDisplay(ctx context.Context, t terminalapi.Terminal, opt interface{}) widgetapi.Widget {
|
||||
options, ok := opt.(RollingSegmentDisplayOptions)
|
||||
if !ok {
|
||||
panic("invalid options type")
|
||||
}
|
||||
segmentDisplayWidget := util.PanicOnErrorWithResult(segmentdisplay.New())
|
||||
|
||||
colors := []cell.Color{
|
||||
cell.ColorNumber(33),
|
||||
cell.ColorRed,
|
||||
cell.ColorYellow,
|
||||
cell.ColorNumber(33),
|
||||
cell.ColorGreen,
|
||||
cell.ColorRed,
|
||||
cell.ColorGreen,
|
||||
cell.ColorRed,
|
||||
}
|
||||
|
||||
textVal := "ArinDash"
|
||||
step := 0
|
||||
|
||||
go util.Periodic(ctx, util.RedrawInterval, func() error {
|
||||
ticker := time.NewTicker(500 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
|
||||
capacity := 0
|
||||
termSize := t.Size()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if capacity == 0 {
|
||||
capacity = segmentDisplayWidget.Capacity()
|
||||
}
|
||||
if t.Size().Eq(image.Point{}) || !t.Size().Eq(termSize) {
|
||||
termSize = t.Size()
|
||||
capacity = segmentDisplayWidget.Capacity()
|
||||
}
|
||||
|
||||
state := TextState(textVal, capacity, step)
|
||||
var chunks []*segmentdisplay.TextChunk
|
||||
for i := 0; i < capacity; i++ {
|
||||
if i >= len(state) {
|
||||
break
|
||||
}
|
||||
|
||||
color := colors[i%len(colors)]
|
||||
chunks = append(chunks, segmentdisplay.NewChunk(
|
||||
string(state[i]),
|
||||
segmentdisplay.WriteCellOpts(cell.FgColor(color)),
|
||||
))
|
||||
}
|
||||
if len(chunks) == 0 {
|
||||
continue
|
||||
}
|
||||
if err := segmentDisplayWidget.Write(chunks); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
step++
|
||||
|
||||
case t := <-options.UpdateText:
|
||||
textVal = t
|
||||
segmentDisplayWidget.Reset()
|
||||
step = 0
|
||||
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
})
|
||||
return segmentDisplayWidget
|
||||
|
||||
}
|
||||
|
||||
func TextState(text string, capacity, step int) []rune {
|
||||
if capacity == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var state []rune
|
||||
for i := 0; i < capacity; i++ {
|
||||
state = append(state, ' ')
|
||||
}
|
||||
state = append(state, []rune(text)...)
|
||||
step = step % len(state)
|
||||
return append(state[step:], state[:step]...)
|
||||
}
|
||||
55
widgets/textInput.go
Normal file
55
widgets/textInput.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package widgets
|
||||
|
||||
import (
|
||||
"ArinDash/util"
|
||||
"context"
|
||||
|
||||
"github.com/mum4k/termdash/cell"
|
||||
"github.com/mum4k/termdash/terminal/terminalapi"
|
||||
"github.com/mum4k/termdash/widgetapi"
|
||||
"github.com/mum4k/termdash/widgets/textinput"
|
||||
)
|
||||
|
||||
type TextInputOptions struct {
|
||||
updateText chan<- string
|
||||
options []textinput.Option
|
||||
}
|
||||
|
||||
var defaultOptions = []textinput.Option{
|
||||
textinput.Label("Enter Text", cell.FgColor(cell.ColorNumber(33))),
|
||||
textinput.MaxWidthCells(20),
|
||||
textinput.PlaceHolder("enter any text"),
|
||||
}
|
||||
|
||||
func TextInput(updateText chan<- string, options ...textinput.Option) TextInputOptions {
|
||||
widgetOptions["TextInputOptions"] = createTextInput
|
||||
return TextInputOptions{
|
||||
options: createOptions(updateText, options),
|
||||
}
|
||||
}
|
||||
|
||||
func createOptions(updateText chan<- string, options []textinput.Option) []textinput.Option {
|
||||
result := defaultOptions
|
||||
if options != nil && len(options) != 0 {
|
||||
for _, option := range options {
|
||||
result = append(result, option)
|
||||
}
|
||||
}
|
||||
|
||||
result = append(result, textinput.ClearOnSubmit())
|
||||
result = append(result, textinput.OnSubmit(func(text string) error {
|
||||
updateText <- text
|
||||
return nil
|
||||
}))
|
||||
return result
|
||||
}
|
||||
|
||||
func createTextInput(_ context.Context, _ terminalapi.Terminal, opt interface{}) widgetapi.Widget {
|
||||
options, ok := opt.(TextInputOptions)
|
||||
if !ok {
|
||||
panic("invalid options type")
|
||||
}
|
||||
return util.PanicOnErrorWithResult(textinput.New(
|
||||
options.options...,
|
||||
))
|
||||
}
|
||||
38
widgets/widgets.go
Normal file
38
widgets/widgets.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package widgets
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/mum4k/termdash/terminal/terminalapi"
|
||||
"github.com/mum4k/termdash/widgetapi"
|
||||
)
|
||||
|
||||
var Get = make(map[string]widgetapi.Widget)
|
||||
|
||||
var widgetOptions = make(map[string]func(ctx context.Context, t terminalapi.Terminal, options any) widgetapi.Widget)
|
||||
|
||||
type Widget struct {
|
||||
name string
|
||||
options interface{}
|
||||
}
|
||||
|
||||
func New(name string, options interface{}) Widget {
|
||||
return Widget{name, options}
|
||||
}
|
||||
|
||||
func Add(name string, widget widgetapi.Widget) {
|
||||
Get[name] = widget
|
||||
}
|
||||
|
||||
func Create(ctx context.Context, t terminalapi.Terminal, widgets ...Widget) {
|
||||
for _, widget := range widgets {
|
||||
typeName := reflect.TypeOf(widget.options).Name()
|
||||
factory := widgetOptions[typeName]
|
||||
if factory == nil {
|
||||
panic(fmt.Errorf("%s: widget factory not found for type: %s", widget.name, typeName))
|
||||
}
|
||||
Add(widget.name, widgetOptions[reflect.TypeOf(widget.options).Name()](ctx, t, widget.options))
|
||||
}
|
||||
}
|
||||
63
widgets/wifiqr.go
Normal file
63
widgets/wifiqr.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package widgets
|
||||
|
||||
import (
|
||||
"ArinDash/config"
|
||||
"ArinDash/util"
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/mum4k/termdash/terminal/terminalapi"
|
||||
"github.com/mum4k/termdash/widgetapi"
|
||||
"github.com/mum4k/termdash/widgets/text"
|
||||
)
|
||||
|
||||
type configFile struct {
|
||||
Wifi wifiConfig
|
||||
}
|
||||
|
||||
type wifiConfig struct {
|
||||
Auth string
|
||||
SSID string
|
||||
Password string
|
||||
}
|
||||
|
||||
const template = "WIFI:T:%s;S:%s;P:%s;;"
|
||||
|
||||
type WifiQRCodeOptions struct {
|
||||
}
|
||||
|
||||
func WifiQRCode() WifiQRCodeOptions {
|
||||
widgetOptions["WifiQRCodeOptions"] = createWifiQRCode
|
||||
return WifiQRCodeOptions{}
|
||||
}
|
||||
|
||||
func createWifiQRCode(_ context.Context, _ terminalapi.Terminal, _ interface{}) widgetapi.Widget {
|
||||
cfg := &configFile{}
|
||||
config.LoadConfig(cfg)
|
||||
|
||||
widget := util.PanicOnErrorWithResult(text.New())
|
||||
|
||||
util.PanicOnError(widget.Write(QrCodeASCII(fmt.Sprintf(template, cfg.Wifi.Auth, escapeSpecialChars(cfg.Wifi.SSID), escapeSpecialChars(cfg.Wifi.Password)))))
|
||||
|
||||
return widget
|
||||
}
|
||||
|
||||
func escapeSpecialChars(s string) string {
|
||||
return strings.ReplaceAll(
|
||||
strings.ReplaceAll(
|
||||
strings.ReplaceAll(
|
||||
strings.ReplaceAll(
|
||||
strings.ReplaceAll(
|
||||
s,
|
||||
"\\", "\\\\",
|
||||
),
|
||||
"\"", "\\\"",
|
||||
),
|
||||
",", "\\,",
|
||||
),
|
||||
";", "\\;",
|
||||
),
|
||||
":", "\\:",
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user