first commit
This commit is contained in:
commit
658ab8e961
110
.gitignore
vendored
Normal file
110
.gitignore
vendored
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
### JetBrains template
|
||||||
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||||
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|
||||||
|
*.idea
|
||||||
|
|
||||||
|
# User-specific stuff
|
||||||
|
.idea/**/workspace.xml
|
||||||
|
.idea/**/tasks.xml
|
||||||
|
.idea/**/usage.statistics.xml
|
||||||
|
.idea/**/dictionaries
|
||||||
|
.idea/**/shelf
|
||||||
|
|
||||||
|
# AWS User-specific
|
||||||
|
.idea/**/aws.xml
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
.idea/**/contentModel.xml
|
||||||
|
|
||||||
|
# Sensitive or high-churn files
|
||||||
|
.idea/**/dataSources/
|
||||||
|
.idea/**/dataSources.ids
|
||||||
|
.idea/**/dataSources.local.xml
|
||||||
|
.idea/**/sqlDataSources.xml
|
||||||
|
.idea/**/dynamic.xml
|
||||||
|
.idea/**/uiDesigner.xml
|
||||||
|
.idea/**/dbnavigator.xml
|
||||||
|
|
||||||
|
# Gradle
|
||||||
|
.idea/**/gradle.xml
|
||||||
|
.idea/**/libraries
|
||||||
|
|
||||||
|
# Gradle and Maven with auto-import
|
||||||
|
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||||
|
# since they will be recreated, and may cause churn. Uncomment if using
|
||||||
|
# auto-import.
|
||||||
|
# .idea/artifacts
|
||||||
|
# .idea/compiler.xml
|
||||||
|
# .idea/jarRepositories.xml
|
||||||
|
# .idea/modules.xml
|
||||||
|
# .idea/*.iml
|
||||||
|
# .idea/modules
|
||||||
|
*.iml
|
||||||
|
# *.ipr
|
||||||
|
|
||||||
|
# CMake
|
||||||
|
cmake-build-*/
|
||||||
|
|
||||||
|
# Mongo Explorer plugin
|
||||||
|
.idea/**/mongoSettings.xml
|
||||||
|
|
||||||
|
# File-based project format
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
out/
|
||||||
|
|
||||||
|
# mpeltonen/sbt-idea plugin
|
||||||
|
.idea_modules/
|
||||||
|
|
||||||
|
# JIRA plugin
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
|
# Cursive Clojure plugin
|
||||||
|
.idea/replstate.xml
|
||||||
|
|
||||||
|
# SonarLint plugin
|
||||||
|
.idea/sonarlint/
|
||||||
|
|
||||||
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
|
||||||
|
# Editor-based Rest Client
|
||||||
|
.idea/httpRequests
|
||||||
|
|
||||||
|
# Android studio 3.1+ serialized cache file
|
||||||
|
.idea/caches/build_file_checksums.ser
|
||||||
|
|
||||||
|
### Go template
|
||||||
|
# If you prefer the allow list template instead of the deny list, see community template:
|
||||||
|
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||||
|
#
|
||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Test binary, built with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Dependency directories (remove the comment below to include it)
|
||||||
|
# vendor/
|
||||||
|
|
||||||
|
# Go workspace file
|
||||||
|
go.work
|
||||||
|
go.work.sum
|
||||||
|
|
||||||
|
# env file
|
||||||
|
.env
|
||||||
|
|
||||||
|
config.toml
|
||||||
|
old
|
||||||
146
apis/docker/docker.go
Normal file
146
apis/docker/docker.go
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
package docker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ArinDash/util"
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
containertypes "github.com/docker/docker/api/types/container"
|
||||||
|
"github.com/docker/docker/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ContainerMetrics struct {
|
||||||
|
ID string
|
||||||
|
Name string
|
||||||
|
Image string
|
||||||
|
Status string
|
||||||
|
CPUPercent float64
|
||||||
|
MemUsage uint64
|
||||||
|
MemLimit uint64
|
||||||
|
MemPercent float64
|
||||||
|
NetRx uint64
|
||||||
|
NetTx uint64
|
||||||
|
BlockRead uint64
|
||||||
|
BlockWrite uint64
|
||||||
|
PidsCurrent uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDockerClient() (*client.Client, error) {
|
||||||
|
return client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
||||||
|
}
|
||||||
|
|
||||||
|
func FetchDockerMetrics(ctx context.Context) ([]ContainerMetrics, error) {
|
||||||
|
cli, err := newDockerClient()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer cli.Close()
|
||||||
|
|
||||||
|
_, err = cli.Ping(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
containers, err := cli.ContainerList(ctx, containertypes.ListOptions{All: true})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
out := make([]ContainerMetrics, 0, len(containers))
|
||||||
|
for _, c := range containers {
|
||||||
|
m := ContainerMetrics{
|
||||||
|
ID: c.ID,
|
||||||
|
Image: c.Image,
|
||||||
|
Name: containerName(c.Names),
|
||||||
|
Status: c.Status,
|
||||||
|
}
|
||||||
|
|
||||||
|
stats, err := cli.ContainerStats(ctx, c.ID, false)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
func() {
|
||||||
|
defer stats.Body.Close()
|
||||||
|
var sj containertypes.StatsResponse
|
||||||
|
if err := util.DecodeJSON(stats.Body, &sj); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.CPUPercent = cpuPercentFromStats(sj)
|
||||||
|
m.MemUsage, m.MemLimit, m.MemPercent = memoryFromStats(sj)
|
||||||
|
m.NetRx, m.NetTx = networkTotals(sj)
|
||||||
|
m.BlockRead, m.BlockWrite = blockIOTotals(sj)
|
||||||
|
m.PidsCurrent = sj.PidsStats.Current
|
||||||
|
}()
|
||||||
|
|
||||||
|
out = append(out, m)
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func containerName(names []string) string {
|
||||||
|
for _, n := range names {
|
||||||
|
n = strings.TrimSpace(n)
|
||||||
|
n = strings.TrimPrefix(n, "/")
|
||||||
|
if n != "" {
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func cpuPercentFromStats(s containertypes.StatsResponse) float64 {
|
||||||
|
// Protect against zero or missing data.
|
||||||
|
if s.PreCPUStats.SystemUsage == 0 || s.CPUStats.SystemUsage == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
cpuDelta := float64(s.CPUStats.CPUUsage.TotalUsage - s.PreCPUStats.CPUUsage.TotalUsage)
|
||||||
|
systemDelta := float64(s.CPUStats.SystemUsage - s.PreCPUStats.SystemUsage)
|
||||||
|
if systemDelta <= 0 || cpuDelta < 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
// Number of CPUs available to the container.
|
||||||
|
numCPU := float64(len(s.CPUStats.CPUUsage.PercpuUsage))
|
||||||
|
if numCPU == 0 {
|
||||||
|
numCPU = 1
|
||||||
|
}
|
||||||
|
return (cpuDelta / systemDelta) * numCPU * 100.0
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if limit > 0 {
|
||||||
|
percent = (float64(usage) / float64(limit)) * 100
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func networkTotals(s containertypes.StatsResponse) (rx, tx uint64) {
|
||||||
|
if s.Networks == nil {
|
||||||
|
return 0, 0
|
||||||
|
}
|
||||||
|
for _, v := range s.Networks {
|
||||||
|
rx += v.RxBytes
|
||||||
|
tx += v.TxBytes
|
||||||
|
}
|
||||||
|
return rx, tx
|
||||||
|
}
|
||||||
|
|
||||||
|
func blockIOTotals(s containertypes.StatsResponse) (read, write uint64) {
|
||||||
|
for _, e := range s.BlkioStats.IoServiceBytesRecursive {
|
||||||
|
switch strings.ToLower(e.Op) {
|
||||||
|
case "read":
|
||||||
|
read += e.Value
|
||||||
|
case "write":
|
||||||
|
write += e.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return read, write
|
||||||
|
}
|
||||||
44
apis/pihole/info.go
Normal file
44
apis/pihole/info.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package pihole
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Version struct {
|
||||||
|
Version Versions `json:"version"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Versions struct {
|
||||||
|
Core ModuleVersion `json:"core"`
|
||||||
|
Web ModuleVersion `json:"web"`
|
||||||
|
FTL ModuleVersion `json:"FTL"`
|
||||||
|
Docker DockerVersion `json:"docker"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ModuleVersion struct {
|
||||||
|
Local LocalVersion `json:"local"`
|
||||||
|
Remote LocalVersion `json:"remote"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LocalVersion struct {
|
||||||
|
Branch string `json:"branch"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
Hash string `json:"hash"`
|
||||||
|
Date string `json:"date"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DockerVersion struct {
|
||||||
|
Local string `json:"local"`
|
||||||
|
Remote string `json:"remote"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ph *PiHConnector) Version() Version {
|
||||||
|
version := &Version{}
|
||||||
|
|
||||||
|
err := json.Unmarshal(ph.get("info/version"), version)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
return *version
|
||||||
|
}
|
||||||
100
apis/pihole/pihole.go
Normal file
100
apis/pihole/pihole.go
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
package pihole
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ArinDash/config"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type configFile struct {
|
||||||
|
Pihole piholeConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
type piholeConfig struct {
|
||||||
|
Host string
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
|
||||||
|
type PiHConnector struct {
|
||||||
|
Host string
|
||||||
|
Session PiHSession
|
||||||
|
}
|
||||||
|
|
||||||
|
type PiHAuth struct {
|
||||||
|
Session PiHSession `json:"session"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PiHSession struct {
|
||||||
|
SID string `json:"sid"`
|
||||||
|
CSRF string `json:"csrf"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ph *PiHConnector) get(endpoint string) []byte {
|
||||||
|
return ph.do("GET", endpoint, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ph *PiHConnector) post(endpoint string, body io.Reader) []byte {
|
||||||
|
return ph.do("POST", endpoint, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ph *PiHConnector) do(method string, endpoint string, body io.Reader) []byte {
|
||||||
|
var requestString = "http://" + ph.Host + "/api/" + endpoint
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
req, err := http.NewRequest(method, requestString, body)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Add("X-FTL-SID", ph.Session.SID)
|
||||||
|
req.Header.Add("X-FTL-CSRF", ph.Session.CSRF)
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
return respBody
|
||||||
|
}
|
||||||
|
|
||||||
|
func Connect() PiHConnector {
|
||||||
|
cfg := &configFile{}
|
||||||
|
config.LoadConfig(cfg)
|
||||||
|
client := &http.Client{}
|
||||||
|
req, err := http.NewRequest("POST", "http://"+cfg.Pihole.Host+"/api/auth", strings.NewReader("{\"password\": \""+cfg.Pihole.Password+"\"}"))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(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)
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &PiHAuth{}
|
||||||
|
|
||||||
|
err = json.Unmarshal(respBody, s)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
return PiHConnector{
|
||||||
|
Host: cfg.Pihole.Host,
|
||||||
|
Session: s.Session,
|
||||||
|
}
|
||||||
|
}
|
||||||
41
apis/pihole/stats.go
Normal file
41
apis/pihole/stats.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package pihole
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Summary struct {
|
||||||
|
Queries Queries `json:"queries"`
|
||||||
|
Clients Clients `json:"clients"`
|
||||||
|
Gravity Gravity `json:"gravity"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Queries struct {
|
||||||
|
Total int64 `json:"total"`
|
||||||
|
Blocked int64 `json:"blocked"`
|
||||||
|
PercentBlocked float64 `json:"percent_blocked"`
|
||||||
|
UniqueDomains int64 `json:"unique_domains"`
|
||||||
|
Forwarded int64 `json:"forwarded"`
|
||||||
|
Cached int64 `json:"cached"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Clients struct {
|
||||||
|
Active int64 `json:"active"`
|
||||||
|
Total int64 `json:"total"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Gravity struct {
|
||||||
|
DomainsBeingBlocked int64 `json:"domains_being_blocked"`
|
||||||
|
LastUpdated int64 `json:"last_updated"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ph *PiHConnector) Summary() Summary {
|
||||||
|
summary := &Summary{}
|
||||||
|
|
||||||
|
err := json.Unmarshal(ph.get("stats/summary"), summary)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
return *summary
|
||||||
|
}
|
||||||
21
config/config.go
Normal file
21
config/config.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/pelletier/go-toml/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func LoadConfig(config interface{}) {
|
||||||
|
if err := toml.Unmarshal(readFile("config.toml"), config); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func readFile(path string) []byte {
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
10
config_template.toml
Normal file
10
config_template.toml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#save this file as config.toml after configuring
|
||||||
|
|
||||||
|
[pihole]
|
||||||
|
Host = "pi.hole"
|
||||||
|
Password = "generate-an-app-password-in-pi-hole"
|
||||||
|
|
||||||
|
[wifi]
|
||||||
|
Auth = "WPA"
|
||||||
|
SSID = "YourSSID"
|
||||||
|
Password = "YourPassword"
|
||||||
48
go.mod
Normal file
48
go.mod
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
module ArinDash
|
||||||
|
|
||||||
|
go 1.25.5
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/docker/docker v28.5.2+incompatible
|
||||||
|
github.com/mum4k/termdash v0.20.0
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4
|
||||||
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
|
github.com/clipperhouse/stringish v0.1.1 // indirect
|
||||||
|
github.com/clipperhouse/uax29/v2 v2.3.0 // indirect
|
||||||
|
github.com/containerd/errdefs v1.0.0 // indirect
|
||||||
|
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||||
|
github.com/containerd/log v0.1.0 // indirect
|
||||||
|
github.com/distribution/reference v0.6.0 // indirect
|
||||||
|
github.com/docker/go-connections v0.6.0 // indirect
|
||||||
|
github.com/docker/go-units v0.5.0 // indirect
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
|
github.com/gdamore/encoding v1.0.0 // indirect
|
||||||
|
github.com/gdamore/tcell/v2 v2.7.4 // indirect
|
||||||
|
github.com/go-logr/logr v1.4.3 // indirect
|
||||||
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.19 // indirect
|
||||||
|
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||||
|
github.com/moby/sys/atomicwriter v0.1.0 // indirect
|
||||||
|
github.com/moby/term v0.5.2 // indirect
|
||||||
|
github.com/morikuni/aec v1.1.0 // indirect
|
||||||
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
|
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 // indirect
|
||||||
|
go.opentelemetry.io/otel v1.39.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/metric v1.39.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/trace v1.39.0 // indirect
|
||||||
|
golang.org/x/sys v0.39.0 // indirect
|
||||||
|
golang.org/x/term v0.17.0 // indirect
|
||||||
|
golang.org/x/text v0.31.0 // indirect
|
||||||
|
golang.org/x/time v0.14.0 // indirect
|
||||||
|
gotest.tools/v3 v3.5.2 // indirect
|
||||||
|
)
|
||||||
156
go.sum
Normal file
156
go.sum
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
|
||||||
|
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||||
|
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||||
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
|
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
|
||||||
|
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
|
||||||
|
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
|
||||||
|
github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4=
|
||||||
|
github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
|
||||||
|
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
|
||||||
|
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
||||||
|
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
||||||
|
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
||||||
|
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||||
|
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||||
|
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||||
|
github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=
|
||||||
|
github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
|
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
|
||||||
|
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
|
||||||
|
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||||
|
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
|
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
|
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||||
|
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||||
|
github.com/gdamore/tcell/v2 v2.7.4 h1:sg6/UnTM9jGpZU+oFYAsDahfchWAFW8Xx2yFinNSAYU=
|
||||||
|
github.com/gdamore/tcell/v2 v2.7.4/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg=
|
||||||
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
|
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||||
|
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4=
|
||||||
|
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
|
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
|
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
|
||||||
|
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||||
|
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||||
|
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||||
|
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
|
||||||
|
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
|
||||||
|
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
|
||||||
|
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
|
||||||
|
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
|
||||||
|
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
|
||||||
|
github.com/morikuni/aec v1.1.0 h1:vBBl0pUnvi/Je71dsRrhMBtreIqNMYErSAbEeb8jrXQ=
|
||||||
|
github.com/morikuni/aec v1.1.0/go.mod h1:xDRgiq/iw5l+zkao76YTKzKttOp2cwPEne25HDkJnBw=
|
||||||
|
github.com/mum4k/termdash v0.20.0 h1:g6yZvE7VJmuefJmDrSrv5Az8IFTTSCqG0x8xiOMPbyM=
|
||||||
|
github.com/mum4k/termdash v0.20.0/go.mod h1:/kPwGKcOhLawc2OmWJPLQ5nzR5PmcbiKMcVv9/413b4=
|
||||||
|
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||||
|
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||||
|
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||||
|
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||||
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||||
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||||
|
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y=
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ=
|
||||||
|
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
|
||||||
|
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 h1:Ckwye2FpXkYgiHX7fyVrN1uA/UYd9ounqqTuSNAv0k4=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0/go.mod h1:teIFJh5pW2y+AN7riv6IBPX2DuesS3HgP39mwOspKwU=
|
||||||
|
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
|
||||||
|
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
|
||||||
|
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
||||||
|
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
|
||||||
|
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
|
||||||
|
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
|
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||||
|
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||||
|
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
|
golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
|
||||||
|
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||||
|
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||||
|
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||||
|
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls=
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||||
|
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
||||||
|
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
||||||
|
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||||
|
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
||||||
|
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
||||||
103
main.go
Normal file
103
main.go
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ArinDash/util"
|
||||||
|
"ArinDash/widgets"
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/mum4k/termdash"
|
||||||
|
"github.com/mum4k/termdash/cell"
|
||||||
|
"github.com/mum4k/termdash/container"
|
||||||
|
"github.com/mum4k/termdash/container/grid"
|
||||||
|
"github.com/mum4k/termdash/keyboard"
|
||||||
|
"github.com/mum4k/termdash/linestyle"
|
||||||
|
"github.com/mum4k/termdash/terminal/tcell"
|
||||||
|
"github.com/mum4k/termdash/terminal/terminalapi"
|
||||||
|
"github.com/mum4k/termdash/widgets/textinput"
|
||||||
|
)
|
||||||
|
|
||||||
|
const rootID = "root"
|
||||||
|
|
||||||
|
func quitter(cancel context.CancelFunc) func(k *terminalapi.Keyboard) {
|
||||||
|
quitter := func(k *terminalapi.Keyboard) {
|
||||||
|
if k.Key == keyboard.KeyEsc || k.Key == keyboard.KeyCtrlC {
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return quitter
|
||||||
|
}
|
||||||
|
|
||||||
|
func initWidgets(ctx context.Context, term *tcell.Terminal) {
|
||||||
|
var titleUpdate = make(chan string)
|
||||||
|
|
||||||
|
widgets.Create(ctx, term,
|
||||||
|
widgets.New("Clock", widgets.Clock()),
|
||||||
|
widgets.New("ChangeTitle", widgets.TextInput(titleUpdate, textinput.Label("Update Title: "), textinput.PlaceHolder("New Title"))),
|
||||||
|
widgets.New("Wifi", widgets.WifiQRCode()),
|
||||||
|
widgets.New("Docker", widgets.DockerList()),
|
||||||
|
widgets.New("PiHole", widgets.PiholeStats()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func layout() []container.Option {
|
||||||
|
builder := grid.New()
|
||||||
|
builder.Add(
|
||||||
|
grid.ColWidthFixed(84,
|
||||||
|
grid.RowHeightFixed(44,
|
||||||
|
grid.Widget(widgets.Get["Wifi"],
|
||||||
|
container.BorderTitle("Wifi"),
|
||||||
|
container.Border(linestyle.Light),
|
||||||
|
container.BorderColor(cell.ColorWhite)),
|
||||||
|
),
|
||||||
|
grid.RowHeightFixed(1,
|
||||||
|
grid.Widget(widgets.Get["empty"]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
grid.ColWidthPerc(20,
|
||||||
|
grid.RowHeightFixed(8,
|
||||||
|
grid.Widget(widgets.Get["Clock"],
|
||||||
|
container.BorderTitle("Time"),
|
||||||
|
container.Border(linestyle.Light),
|
||||||
|
container.BorderColor(cell.ColorWhite),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
grid.RowHeightFixed(8,
|
||||||
|
grid.Widget(widgets.Get["PiHole"],
|
||||||
|
container.BorderTitle("pi-hole"),
|
||||||
|
container.Border(linestyle.Light),
|
||||||
|
container.BorderColor(cell.ColorWhite),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
grid.RowHeightPerc(85,
|
||||||
|
grid.Widget(widgets.Get["empty"]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
grid.ColWidthPerc(35,
|
||||||
|
grid.RowHeightPerc(25,
|
||||||
|
grid.Widget(widgets.Get["empty"]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
grid.ColWidthPerc(35,
|
||||||
|
grid.RowHeightPerc(25,
|
||||||
|
grid.Widget(widgets.Get["Docker"],
|
||||||
|
container.BorderTitle("Docker"),
|
||||||
|
container.Border(linestyle.Light),
|
||||||
|
container.BorderColor(cell.ColorWhite),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return util.PanicOnErrorWithResult(builder.Build())
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
term := util.PanicOnErrorWithResult(tcell.New(tcell.ColorMode(terminalapi.ColorMode256)))
|
||||||
|
defer term.Close()
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
initWidgets(ctx, term)
|
||||||
|
|
||||||
|
rootContainer := util.PanicOnErrorWithResult(container.New(term, container.ID(rootID)))
|
||||||
|
util.PanicOnError(rootContainer.Update(rootID, layout()...))
|
||||||
|
util.PanicOnError(termdash.Run(ctx, term, rootContainer, termdash.KeyboardSubscriber(quitter(cancel)), termdash.RedrawInterval(util.RedrawInterval)))
|
||||||
|
}
|
||||||
53
util/util.go
Normal file
53
util/util.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const RedrawInterval = 250 * time.Millisecond
|
||||||
|
|
||||||
|
func Periodic(ctx context.Context, interval time.Duration, fn func() error) {
|
||||||
|
if err := fn(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
ticker := time.NewTicker(interval)
|
||||||
|
defer ticker.Stop()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
if err := fn(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func PanicOnErrorWithResult[E any](result E, err error) E {
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func PanicOnError(err error) {
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func DecodeJSON(r io.Reader, v any) error {
|
||||||
|
dec := json.NewDecoder(r)
|
||||||
|
if err := dec.Decode(v); err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
return fmt.Errorf("empty stats stream")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
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,
|
||||||
|
"\\", "\\\\",
|
||||||
|
),
|
||||||
|
"\"", "\\\"",
|
||||||
|
),
|
||||||
|
",", "\\,",
|
||||||
|
),
|
||||||
|
";", "\\;",
|
||||||
|
),
|
||||||
|
":", "\\:",
|
||||||
|
)
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user