code moved to the cloudflare package

use the cloudflare package
    add a protobuf attempt
    better change detection, but formatting is broken
    don't redraw widgets if they are not visible
    create new dns entry worked
    attempting a DNS RR create
    attempt a create API call
    use cloudflare recommended ENV vars
    turn off debugging

Signed-off-by: Jeff Carr <jcarr@wit.com>
This commit is contained in:
Jeff Carr 2023-12-20 05:58:33 -06:00
parent f67e03db16
commit 2d147a9115
19 changed files with 407 additions and 156 deletions

3
.gitignore vendored
View File

@ -11,6 +11,9 @@ examples/textbox/textbox
examples/cloudflare/cloudflare examples/cloudflare/cloudflare
examples/*/helloconsole examples/*/helloconsole
# protobuf compiled files
protobuf/*.pb.go
# temporary files when building debian packages # temporary files when building debian packages
/*.deb /*.deb
/files /files

View File

@ -170,6 +170,13 @@ func (n *Node) Unpad() *Node {
return n return n
} }
func (n *Node) Expand() *Node {
a := newAction(n, toolkit.Pad)
a.Expand = true
sendAction(a)
return n
}
// is this better? // is this better?
// yes, this is better. it allows Internationalization very easily // yes, this is better. it allows Internationalization very easily
// me.window = myGui.New2().Window("DNS and IPv6 Control Panel").Standard() // me.window = myGui.New2().Window("DNS and IPv6 Control Panel").Standard()
@ -182,8 +189,9 @@ func (n *Node) Window(title string) *Node {
// This should not really do anything. as per the docs, the "Standard()" way // This should not really do anything. as per the docs, the "Standard()" way
// should be the default way // should be the default way
/*
func (n *Node) Standard() *Node { func (n *Node) Standard() *Node {
log(debugError, "Standard() not implemented yet") log(debugInfo, "Standard() not implemented yet")
return n return n
} }
@ -191,3 +199,4 @@ func (n *Node) SetMargin() *Node {
log(debugError, "DoMargin() not implemented yet") log(debugError, "DoMargin() not implemented yet")
return n return n
} }
*/

View File

@ -4,10 +4,11 @@ package main
import ( import (
"os" "os"
"log" "log"
"fmt"
"encoding/json" "encoding/json"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"strconv" // "strconv"
"bytes" "bytes"
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
@ -19,7 +20,17 @@ func doChange(dnsRow *RRT) {
log.Println("Content", dnsRow.Content, "vs", dnsRow.valueNode.S) log.Println("Content", dnsRow.Content, "vs", dnsRow.valueNode.S)
if (dnsRow.Content != dnsRow.valueNode.S) { if (dnsRow.Content != dnsRow.valueNode.S) {
log.Println("UPDATE VALUE", dnsRow.nameNode.Name, dnsRow.typeNode.Name, "to", dnsRow.valueNode.S) log.Println("UPDATE VALUE", dnsRow.nameNode.Name, dnsRow.typeNode.Name, "to", dnsRow.valueNode.S)
httpPut(dnsRow) stuff, result := httpPut(dnsRow)
if (dnsRow.curlNode != nil) {
pretty, _ := formatJSON(stuff)
log.Println("http PUT curl =", pretty)
dnsRow.curlNode.SetText(pretty)
}
if (dnsRow.resultNode != nil) {
pretty, _ := formatJSON(result)
log.Println("http PUT result =", pretty)
dnsRow.resultNode.SetText(pretty)
}
} }
dnsRow.saveNode.Disable() dnsRow.saveNode.Disable()
} }
@ -73,24 +84,26 @@ func getZonefile(c *configT) *DNSRecords {
go.wit.com. 3600 IN A 1.1.1.9 go.wit.com. 3600 IN A 1.1.1.9
test.wit.com. 3600 IN NS ns1.wit.com. test.wit.com. 3600 IN NS ns1.wit.com.
*/ */
func httpPut(dnsRow *RRT) { func httpPut(dnsRow *RRT) (string, string) {
var url string = cloudflareURL + os.Getenv("CLOUDFLARE_ZONEID") + "/dns_records/" + dnsRow.ID var url string = cloudflareURL + os.Getenv("CF_API_ZONEID") + "/dns_records/" + dnsRow.ID
var authKey string = os.Getenv("CLOUDFLARE_AUTHKEY") var authKey string = os.Getenv("CF_API_KEY")
var email string = os.Getenv("CLOUDFLARE_EMAIL") var email string = os.Getenv("CF_API_EMAIL")
// make a json record to send on port 90 to cloudflare // make a json record to send on port 80 to cloudflare
var tmp string var tmp string
tmp = `{"content": "` + dnsRow.valueNode.S + `", ` tmp = `{"content": "` + dnsRow.valueNode.S + `", `
tmp += `"name": "` + dnsRow.Name + `", ` tmp += `"name": "` + dnsRow.Name + `", `
tmp += `"type": "` + dnsRow.Type + `", ` tmp += `"type": "` + dnsRow.Type + `", `
tmp+= `"ttl": "` + strconv.Itoa(dnsRow.TTL) + `", ` tmp+= `"ttl": "` + "1" + `", `
tmp += `"comment": "WIT DNS Control Panel"` tmp += `"comment": "WIT DNS Control Panel"`
tmp += `}` tmp += `}`
data := []byte(tmp) data := []byte(tmp)
log.Println("http PUT url =", url) log.Println("http PUT url =", url)
log.Println("http PUT data =", data) // log.Println("http PUT data =", data)
spew.Dump(data) // spew.Dump(data)
pretty, _ := formatJSON(string(data))
log.Println("http PUT data =", pretty)
req, err := http.NewRequest(http.MethodPut, url, bytes.NewBuffer(data)) req, err := http.NewRequest(http.MethodPut, url, bytes.NewBuffer(data))
@ -103,19 +116,19 @@ func httpPut(dnsRow *RRT) {
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
return return tmp, fmt.Sprintf("blah err =", err)
} }
defer resp.Body.Close() defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body) body, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
return return tmp, fmt.Sprintf("blah err =", err)
} }
log.Println("http PUT body =", body) // log.Println("http PUT body =", body)
spew.Dump(body) // spew.Dump(body)
return return tmp, string(body)
} }
// https://api.cloudflare.com/client/v4/zones // https://api.cloudflare.com/client/v4/zones
@ -185,3 +198,22 @@ func getZones(auth, email string) *DNSRecords {
return &records return &records
} }
// formatJSON takes an unformatted JSON string and returns a formatted version.
func formatJSON(unformattedJSON string) (string, error) {
var jsonData interface{}
// Decode the JSON string into an interface
err := json.Unmarshal([]byte(unformattedJSON), &jsonData)
if err != nil {
return "", err
}
// Re-encode the JSON with indentation for formatting
formattedJSON, err := json.MarshalIndent(jsonData, "", " ")
if err != nil {
return "", err
}
return string(formattedJSON), nil
}

31
examples/cloudflare/curl.sh Normal file → Executable file
View File

@ -1,3 +1,28 @@
curl -X GET "https://api.cloudflare.com/client/v4/user/tokens/verify" \ #curl -X GET "https://api.cloudflare.com/client/v4/user/tokens/verify" \
-H "Authorization: Bearer AAAPutYourTokenInHereSoYouCanTestItL5Cl3" \ # -H "Authorization: Bearer AAAPutYourTokenInHereSoYouCanTestItL5Cl3" \
-H "Content-Type:application/json" # -H "Content-Type:application/json"
# https://api.cloudflare.com/client/v4/zones/27b900d9e05cfb9f3a64fecff2497f90/dns_records
#
# {
# "comment": "WIT DNS Control Panel",
# "content": "2001:4860:4860::8888",
# "name": "www",
# "proxied": false,
# "ttl": 3600,
# "type": "AAAA"
#}
curl --request POST \
--url https://api.cloudflare.com/client/v4/zones/27b900d9e05cfb9f3a64fecff2497f90/dns_records \
--header 'Content-Type: application/json' \
--header 'X-Auth-Key: e08806ad85ef97aebaacd2d7fa462a7d417a7x' \
--header 'X-Auth-Email: basilarchia@gmail.com' \
--data '{
"comment": "WIT DNS Control Panel",
"content": "2001:4860:4860::5555",
"name": "www5",
"proxied": false,
"ttl": 3600,
"type": "AAAA"
}'

View File

@ -4,6 +4,8 @@ package main
import ( import (
"log" "log"
"strconv" "strconv"
"git.wit.org/jcarr/control-panel-dns/cloudflare"
) )
func loadDNS(c *configT) { func loadDNS(c *configT) {
@ -11,7 +13,8 @@ func loadDNS(c *configT) {
log.Println("adding DNS record", hostname) log.Println("adding DNS record", hostname)
newt := mainWindow.NewTab(hostname) newt := mainWindow.NewTab(hostname)
newg := newt.NewGroup("more") vb := newt.NewBox("vBox", false)
newg := vb.NewGroup("more zoneID = " + c.zoneID)
// make a grid 6 things wide // make a grid 6 things wide
grid := newg.NewGrid("gridnuts", 6, gridH) grid := newg.NewGrid("gridnuts", 6, gridH)
@ -19,41 +22,22 @@ func loadDNS(c *configT) {
// grid.NewButton("Type", func () { // grid.NewButton("Type", func () {
// log.Println("sort by Type") // log.Println("sort by Type")
// }) // })
typedrop := grid.NewDropdown("type") grid.NewLabel("RR type")
typedrop.AddText("A") grid.NewLabel("hostname")
typedrop.AddText("AAAA")
typedrop.AddText("CNAME")
typedrop.Custom = func () {
log.Println("custom dropdown() a =", typedrop.Name, typedrop.S)
}
nb := grid.NewButton("Name", func () {
log.Println("sort by Name")
})
nb.Disable()
grid.NewButton("Protection", func () { grid.NewLabel("Proxy")
log.Println("sort proxied") grid.NewLabel("TTL")
}) grid.NewLabel("Value")
grid.NewButton("TTL", func () { grid.NewLabel("Save")
log.Println("sort by TTL")
})
nb = grid.NewButton("Value", func () {
log.Println("sort by Value")
})
nb.Disable()
nb = grid.NewButton("Save", func () {
log.Println("click below to save")
})
nb.Disable()
masterSave = newt.NewButton("Master Save", func () { masterSave = vb.NewButton("Master Save", func () {
log.Println("save stuff to cloudflare") log.Println("save stuff to cloudflare")
}) })
masterSave.Disable() masterSave.Disable()
records := getZonefile(c) records := getZonefile(c)
for _, record := range records.Result { for _, record := range records.Result {
var rr RRT // dns zonefile resource record var rr cloudflare.RRT // dns zonefile resource record
// copy all the JSON values into the row record. // copy all the JSON values into the row record.
rr.ID = record.ID rr.ID = record.ID
@ -62,59 +46,34 @@ func loadDNS(c *configT) {
rr.Content = record.Content rr.Content = record.Content
rr.Proxied = record.Proxied rr.Proxied = record.Proxied
rr.Proxiable = record.Proxiable rr.Proxiable = record.Proxiable
rr.TTL = record.TTL // rr.Ttl = record.TTL
rr.typeNode = grid.NewLabel(record.Type) grid.NewLabel(record.Type)
rr.nameNode = grid.NewEntryLine(record.Name) grid.NewLabel(record.Name)
rr.nameNode.SetText(record.Name)
rr.nameNode.Disable()
// set proxy or unproxied proxy := grid.NewLabel("proxy")
rr.proxyNode = grid.NewDropdown("proxy")
if (record.Proxied) { if (record.Proxied) {
rr.proxyNode.AddText("Proxied") proxy.SetText("On")
rr.proxyNode.AddText("DNS")
} else { } else {
rr.proxyNode.AddText("DNS") proxy.SetText("Off")
rr.proxyNode.AddText("Proxied")
}
rr.proxyNode.Custom = func () {
log.Println("proxy dropdown() a =", rr.proxyNode.Name, rr.proxyNode.S, rr.ID)
rr.saveNode.Enable()
masterSave.Enable()
} }
var ttl, short string var ttl string
if (record.TTL == 1) { if (record.TTL == 1) {
ttl = "Auto" ttl = "Auto"
} else { } else {
ttl = strconv.Itoa(record.TTL) ttl = strconv.Itoa(record.TTL)
} }
rr.ttlNode = grid.NewLabel(ttl) grid.NewLabel(ttl)
// short = fmt.Sprintf("%80s", record.Content)
short = record.Content
if len(short) > 40 {
short = short[:40] // Slice the first 20 characters
}
rr.valueNode = grid.NewEntryLine(short) val := grid.NewLabel("Value")
rr.valueNode.SetText(record.Content) val.SetText(record.Content)
rr.valueNode.Custom = func () { load := grid.NewButton("Load", nil)
log.Println("value changed =", rr.valueNode.Name, rr.proxyNode.S, rr.ID) load.Custom = func () {
rr.saveNode.Enable()
masterSave.Enable()
}
// fmt.Printf("ID: %s, Type: %s, Name: %s, short Content: %s\n", record.ID, record.Type, record.Name, short)
// fmt.Printf("\tproxied: %b, %b, string TTL: %i\n", record.Proxied, record.Proxiable, ttl)
rr.saveNode = grid.NewButton("Save", nil)
rr.saveNode.Disable()
rr.saveNode.Custom = func () {
name := "save stuff to cloudflare for " + rr.ID name := "save stuff to cloudflare for " + rr.ID
log.Println(name) log.Println(name)
doChange(&rr) // doChange(&rr)
} }
} }

View File

@ -7,7 +7,9 @@ import (
"log" "log"
"bufio" "bufio"
"strings" "strings"
"git.wit.org/wit/gui" "git.wit.org/wit/gui"
"git.wit.org/jcarr/control-panel-dns/cloudflare"
) )
var title string = "Cloudflare DNS Control Panel" var title string = "Cloudflare DNS Control Panel"
@ -44,7 +46,8 @@ func makeCloudflareWindow() {
makeConfigTab(mainWindow) makeConfigTab(mainWindow)
t = mainWindow.NewTab("Zones") t = mainWindow.NewTab("Zones")
g1 := t.NewGroup("zones") vb := t.NewBox("vBox", false)
g1 := vb.NewGroup("zones")
// make dropdown list of zones // make dropdown list of zones
zonedrop = g1.NewDropdown("zone") zonedrop = g1.NewDropdown("zone")
@ -52,6 +55,7 @@ func makeCloudflareWindow() {
for d, _ := range config { for d, _ := range config {
zonedrop.AddText(d) zonedrop.AddText(d)
} }
zonedrop.AddText("stablesid.org")
zonedrop.Custom = func () { zonedrop.Custom = func () {
domain := zonedrop.S domain := zonedrop.S
@ -82,18 +86,18 @@ func makeConfigTab(window *gui.Node) {
vb := t.NewBox("vBox", false) vb := t.NewBox("vBox", false)
g1 := vb.NewGroup("Cloudflare API Config") g1 := vb.NewGroup("Cloudflare API Config")
g1.NewLabel("If you have an API key with access to list all of /n your zone files, enter it here. \n \n Alternatively, you can set the enviroment variables: \n env $CLOUDFLARE_AUTHKEY \n env $CLOUDFLARE_EMAIL \n env $CLOUDFLARE_URL \n") g1.NewLabel("If you have an API key with access to list all of /n your zone files, enter it here. \n \n Alternatively, you can set the enviroment variables: \n env $CF_API_KEY \n env $CF_API_EMAIL\n")
// make grid to display credentials // make grid to display credentials
grid := g1.NewGrid("credsGrid", 2, 4) // width = 2 grid := g1.NewGrid("credsGrid", 2, 4) // width = 2
grid.NewLabel("Auth Key") grid.NewLabel("Auth Key")
aw := grid.NewEntryLine("CLOUDFLARE_AUTHKEY") aw := grid.NewEntryLine("CF_API_KEY")
aw.SetText(os.Getenv("CLOUDFLARE_AUTHKEY")) aw.SetText(os.Getenv("CF_API_KEY"))
grid.NewLabel("Email") grid.NewLabel("Email")
ew := grid.NewEntryLine("CLOUDFLARE_EMAIL") ew := grid.NewEntryLine("CF_API_EMAIL")
ew.SetText(os.Getenv("CLOUDFLARE_EMAIL")) ew.SetText(os.Getenv("CF_API_EMAIL"))
var url string = "https://api.cloudflare.com/client/v4/zones/" var url string = "https://api.cloudflare.com/client/v4/zones/"
grid.NewLabel("Cloudflare API") grid.NewLabel("Cloudflare API")
@ -106,6 +110,10 @@ func makeConfigTab(window *gui.Node) {
getZones(aw.S, ew.S) getZones(aw.S, ew.S)
}) })
vb.NewButton("cloudflare wit.com", func () {
cloudflare.CreateRR(myGui, "wit.com", "3777302ac4a78cd7fa4f6d3f72086d06")
})
t.Pad() t.Pad()
t.Margin() t.Margin()
vb.Pad() vb.Pad()
@ -144,16 +152,16 @@ func showCloudflareCredentials(box *gui.Node) {
grid := box.NewGrid("credsGrid", 2, 4) // width = 2 grid := box.NewGrid("credsGrid", 2, 4) // width = 2
grid.NewLabel("Domain") grid.NewLabel("Domain")
domainWidget = grid.NewEntryLine("CLOUDFLARE_DOMAIN") domainWidget = grid.NewEntryLine("CF_API_DOMAIN")
grid.NewLabel("Zone ID") grid.NewLabel("Zone ID")
zoneWidget = grid.NewEntryLine("CLOUDFLARE_ZONEID") zoneWidget = grid.NewEntryLine("CF_API_ZONEID")
grid.NewLabel("Auth Key") grid.NewLabel("Auth Key")
authWidget = grid.NewEntryLine("CLOUDFLARE_AUTHKEY") authWidget = grid.NewEntryLine("CF_API_KEY")
grid.NewLabel("Email") grid.NewLabel("Email")
emailWidget = grid.NewEntryLine("CLOUDFLARE_EMAIL") emailWidget = grid.NewEntryLine("CF_API_EMAIL")
var url string = "https://api.cloudflare.com/client/v4/zones/" var url string = "https://api.cloudflare.com/client/v4/zones/"
grid.NewLabel("Cloudflare API") grid.NewLabel("Cloudflare API")
@ -161,10 +169,6 @@ func showCloudflareCredentials(box *gui.Node) {
grid.Pad() grid.Pad()
saveButton = box.NewButton("Save to config", func () {
})
saveButton.Disable()
loadButton = box.NewButton("Load Cloudflare DNS zonefile", func () { loadButton = box.NewButton("Load Cloudflare DNS zonefile", func () {
var domain configT var domain configT
domain.domain = domainWidget.S domain.domain = domainWidget.S

View File

@ -34,20 +34,23 @@ var zonedrop *gui.Node
// Resource Record (used in a DNS zonefile) // Resource Record (used in a DNS zonefile)
type RRT struct { type RRT struct {
typeNode *gui.Node typeNode *gui.Node // CNAME, A, AAAA, ...
nameNode *gui.Node nameNode *gui.Node // www, mail, ...
proxyNode *gui.Node proxyNode *gui.Node // If cloudflare is a port 80 & 443 proxy
ttlNode *gui.Node ttlNode *gui.Node // just set to 1 which means automatic to cloudflare
valueNode *gui.Node valueNode *gui.Node // 4.2.2.2, "dkim stuff", etc
saveNode *gui.Node curlNode *gui.Node // shows you what you could run via curl
resultNode *gui.Node // what the cloudflare API returned
saveNode *gui.Node // button to send it to cloudflare
ID string ID string
Type string Type string
Name string Name string
Content string Content string
ProxyS string
Proxied bool Proxied bool
Proxiable bool Proxiable bool
TTL int Ttl string
} }
/* /*

30
main.go
View File

@ -37,17 +37,17 @@ func init() {
func watchCallback() { func watchCallback() {
log(logInfo, "watchCallback() START") log(logInfo, "watchCallback() START")
for { for {
log(logNow, "watchCallback() restarted select for toolkit user events") log(logInfo, "watchCallback() restarted select for toolkit user events")
select { select {
case a := <-me.guiChan: case a := <-me.guiChan:
if (a.ActionType == toolkit.UserQuit) { if (a.ActionType == toolkit.UserQuit) {
log(logNow, "doUserEvent() User sent Quit()") log(logInfo, "doUserEvent() User sent Quit()")
me.rootNode.doCustom() me.rootNode.doCustom()
exit("wit/gui toolkit.UserQuit") exit("wit/gui toolkit.UserQuit")
break break
} }
if (a.ActionType == toolkit.EnableDebug) { if (a.ActionType == toolkit.EnableDebug) {
log(logNow, "doUserEvent() Enable Debugging Window") log(logInfo, "doUserEvent() Enable Debugging Window")
DebugWindow() DebugWindow()
break break
} }
@ -56,7 +56,7 @@ func watchCallback() {
if (n == nil) { if (n == nil) {
log(logError, "watchCallback() UNKNOWN widget id =", a.WidgetId, a.Name) log(logError, "watchCallback() UNKNOWN widget id =", a.WidgetId, a.Name)
} else { } else {
log(logNow, "watchCallback() FOUND widget id =", n.id, n.Name) log(logInfo, "watchCallback() FOUND widget id =", n.id, n.Name)
n.doUserEvent(a) n.doUserEvent(a)
} }
// this maybe a good idea? // this maybe a good idea?
@ -67,7 +67,7 @@ func watchCallback() {
} }
func (n *Node) doCustom() { func (n *Node) doCustom() {
log(logNow, "doUserEvent() widget =", n.id, n.Name, n.WidgetType, n.B) log(logInfo, "doUserEvent() widget =", n.id, n.Name, n.WidgetType, n.B)
if (n.Custom == nil) { if (n.Custom == nil) {
log(debugError, "Custom() = nil. SKIPPING") log(debugError, "Custom() = nil. SKIPPING")
return return
@ -76,40 +76,40 @@ func (n *Node) doCustom() {
} }
func (n *Node) doUserEvent(a toolkit.Action) { func (n *Node) doUserEvent(a toolkit.Action) {
log(logNow, "doUserEvent() node =", n.id, n.Name) log(logInfo, "doUserEvent() node =", n.id, n.Name)
switch n.WidgetType { switch n.WidgetType {
case toolkit.Checkbox: case toolkit.Checkbox:
n.B = a.B n.B = a.B
log(logNow, "doUserEvent() node =", n.id, n.Name, "set to:", n.B) log(logInfo, "doUserEvent() node =", n.id, n.Name, "set to:", n.B)
n.doCustom() n.doCustom()
case toolkit.Button: case toolkit.Button:
log(logNow, "doUserEvent() node =", n.id, n.Name, "button clicked") log(logInfo, "doUserEvent() node =", n.id, n.Name, "button clicked")
n.doCustom() n.doCustom()
case toolkit.Combobox: case toolkit.Combobox:
n.S = a.S n.S = a.S
log(logNow, "doUserEvent() node =", n.id, n.Name, "set to:", n.S) log(logInfo, "doUserEvent() node =", n.id, n.Name, "set to:", n.S)
n.doCustom() n.doCustom()
case toolkit.Dropdown: case toolkit.Dropdown:
n.S = a.S n.S = a.S
log(logNow, "doUserEvent() node =", n.id, n.Name, "set to:", n.S) log(logInfo, "doUserEvent() node =", n.id, n.Name, "set to:", n.S)
n.doCustom() n.doCustom()
case toolkit.Textbox: case toolkit.Textbox:
n.S = a.S n.S = a.S
log(logNow, "doUserEvent() node =", n.id, n.Name, "set to:", n.S) log(logInfo, "doUserEvent() node =", n.id, n.Name, "set to:", n.S)
n.doCustom() n.doCustom()
case toolkit.Spinner: case toolkit.Spinner:
n.I = a.I n.I = a.I
log(logNow, "doUserEvent() node =", n.id, n.Name, "set to:", n.I) log(logInfo, "doUserEvent() node =", n.id, n.Name, "set to:", n.I)
n.doCustom() n.doCustom()
case toolkit.Slider: case toolkit.Slider:
n.I = a.I n.I = a.I
log(logNow, "doUserEvent() node =", n.id, n.Name, "set to:", n.I) log(logInfo, "doUserEvent() node =", n.id, n.Name, "set to:", n.I)
n.doCustom() n.doCustom()
case toolkit.Window: case toolkit.Window:
log(logNow, "doUserEvent() node =", n.id, n.Name, "window closed") log(logInfo, "doUserEvent() node =", n.id, n.Name, "window closed")
n.doCustom() n.doCustom()
default: default:
log(logNow, "doUserEvent() type =", n.WidgetType) log(logInfo, "doUserEvent() type =", n.WidgetType)
} }
} }

34
protobuf/Makefile Normal file
View File

@ -0,0 +1,34 @@
all:
# You must use the current protoc-gen-go
# protoc --version 3.6++ does not mean that protoc will generate version3 .go files
#
# apt remove golang-goprotobuf-dev
# apt install protobuf-compiler
#
# Then:
# go get -u github.com/golang/protobuf/protoc-gen-go
# cd ~/go/src/github.com/golang/protobuf/protoc-gen-go
# go install
#
# Then:
protoc --version
make widget.pb.go
clean:
rm -f *.pb.go
widget.pb.go: widget.proto
protoc --go_out=. widget.proto
compile:
protoc --go_out=. *.proto
deps:
apt install golang-goprotobuf-dev
apt install protobuf-compiler
push:
git pull
git add --all
git commit -a -s
git push

117
protobuf/widget.proto Normal file
View File

@ -0,0 +1,117 @@
syntax = "proto3";
package guiProtobuf;
message Action {
WidgetType widgetType = 1;
ActionType actionType = 2;
int64 widgetId = 3;
int64 parentId = 4;
string text = 5; // what is visable to the user
string name = 6; // a name useful for programming
// This is how the values are passed back and forth
// values from things like checkboxes & dropdown's
bool b = 7;
int64 i = 8;
string s = 9;
// This is used for things like a slider(0,100)
int64 x = 10;
int64 y = 11;
// This is for the grid size & widget position
int64 w = 12;
int64 h = 13;
int64 atw = 14;
int64 ath = 15;
bool margin = 16; // Put space around elements to improve look & feel
bool expand = 17; // Make widgets fill up the space available
repeated Response results = 18;
repeated Network networks = 19;
repeated VM vms = 20;
enum WidgetType {
Unknown = 0;
Root = 1; // the master 'root' node of the binary tree
Flag = 2; // used to send configuration values to plugins
Window = 3; // in certain gui's (ncurses), these are tabs
Tab = 4; // internally, this is a window
Frame = 5; // deprecate?
Grid = 6; // like drawers in a chest
Group = 7; // like the 'Appetizers' section on a menu
Box = 8; // a vertical or horizontal stack of widgets
Button = 9;
Checkbox = 10; // select 'on' or 'off'
Dropdown = 11;
Combobox = 12; // dropdown with edit=true
Label = 13;
Textbox = 14; // is this a Label with edit=true
Slider = 15; // like a progress bar
Spinner = 16; // like setting the oven temperature
Separator = 17; // TODO
Image = 18; // TODO
Area = 19; // TODO
Form = 20; // TODO
Font = 21; // TODO
Color = 22; // TODO
Dialog = 23; // TODO
Stdout = 24; // can be used to capture and display log output
}
enum ActionType {
Health = 0;
Add = 1;
Delete = 2;
Get = 3;
Set = 4;
GetText = 5;
SetText = 6;
AddText = 7;
Show = 8;
Hide = 9;
Enable = 10;
Disable = 11;
Margin = 12;
Unmargin = 13;
Pad = 14;
Unpad = 15;
Append = 16;
Move = 17;
Dump = 18;
User = 19; // the user did something (mouse, keyboard, etc)
InitToolkit = 20; // initializes the toolkit
CloseToolkit = 21; // closes the toolkit
UserQuit = 22; // the user closed the GUI
EnableDebug = 23; // open the debugging window
}
message Response {
// ActionType type = 1;
int64 id = 2;
string name = 3;
string error = 4;
repeated string snippets = 5;
}
message Network {
int64 id = 1;
string name = 2;
int64 total_cpu = 3;
int64 total_mem = 4;
string login_url = 5;
}
message VM {
int64 id = 1;
string name = 2;
string hostname = 3;
int64 cpus = 4;
int64 memory = 5;
int64 disk = 6;
string IPv6 = 7;
string role = 8;
string baseImage = 9;
}
}

View File

@ -38,11 +38,11 @@ func (n *node) enable(b bool) {
} }
func (n *node) pad(at toolkit.ActionType) { func (n *node) pad(at toolkit.ActionType) {
log(debugError, "pad()") log(logInfo, "pad() on WidgetId =", n.WidgetId)
t := n.tk t := n.tk
if (t == nil) { if (t == nil) {
log(debugError, "pad() toolkit struct == nil. for", n.WidgetId) log(logError, "pad() toolkit struct == nil. for", n.WidgetId)
return return
} }

View File

@ -127,15 +127,16 @@ func (p *node) place(n *node) bool {
return true return true
case toolkit.Tab: case toolkit.Tab:
if (p.tk.uiTab == nil) { if (p.tk.uiTab == nil) {
log(logError, "p.tk.uiTab == nil", p.tk) log(logError, "p.tk.uiTab == nil for n.WidgetId =", n.WidgetId, "p.tk =", p.tk)
panic("p.tk.uiTab == nil") panic("p.tk.uiTab == nil")
} }
if (n.tk.uiControl == nil) { if (n.tk.uiControl == nil) {
log(logError, "n.tk.uiControl == nil", n.tk) log(logError, "n.tk.uiControl == nil for n.WidgetId =", n.WidgetId, "n.tk =", n.tk)
panic("n.tk.uiControl == nil") panic("n.tk.uiControl == nil")
} }
log(logError, "THIS SHOULD NEVER HAPPEN ??????? trying to place() node=", n.WidgetId, n.Name, n.Text, n.WidgetType) log(logError, "CHECK LOGIC ON THIS. APPENDING directly into a window without a tab")
log(logError, "THIS SHOULD NEVER HAPPEN ??????? trying to place() on parent=", p.WidgetId, p.Name, p.Text, p.WidgetType) // log(logError, "THIS SHOULD NEVER HAPPEN ??????? trying to place() node=", n.WidgetId, n.Name, n.Text, n.WidgetType)
// log(logError, "THIS SHOULD NEVER HAPPEN ??????? trying to place() on parent=", p.WidgetId, p.Name, p.Text, p.WidgetType)
// panic("n.tk.uiControl == nil") // panic("n.tk.uiControl == nil")
p.tk.uiTab.Append(n.Text, n.tk.uiControl) p.tk.uiTab.Append(n.Text, n.tk.uiControl)
p.tk.boxC += 1 p.tk.boxC += 1

View File

@ -14,12 +14,12 @@ var stretchy bool // expand things like buttons to the maximum size
var padded bool // add space between things like buttons var padded bool // add space between things like buttons
var margin bool // add space around the frames of windows var margin bool // add space around the frames of windows
var debugToolkit bool = true var debugToolkit bool = false
var debugChange bool = true var debugChange bool = false
var debugPlugin bool = true var debugPlugin bool = false
var debugAction bool = true var debugAction bool = false
var debugFlags bool = true var debugFlags bool = false
var debugGrid bool = true var debugGrid bool = false
var debugNow bool = true var debugNow bool = true
var debugError bool = true var debugError bool = true

View File

@ -40,13 +40,18 @@ func init() {
// log(debugToolkit, "init() Setting defaultBehavior = true") // log(debugToolkit, "init() Setting defaultBehavior = true")
setDefaultBehavior(true) setDefaultBehavior(true)
// TODO: this is messed up. run ui.Main() from the first add? Initialize it with an empty thing first?
// fake out the OS toolkit by making a fake window. This is probably needed for macos & windows
// actually, this probably breaks the macos build
go ui.Main(func() {
demoUI()
})
// andlabs = make(map[int]*andlabsT) // andlabs = make(map[int]*andlabsT)
pluginChan = make(chan toolkit.Action, 1) pluginChan = make(chan toolkit.Action, 1)
log(logNow, "Init() start channel reciever") log(logNow, "Init() start channel reciever")
go ui.Main(func() {
demoUI()
})
go catchActionChannel() go catchActionChannel()
log(logNow, "Init() END") log(logNow, "Init() END")
} }

View File

@ -86,7 +86,8 @@ func demoUI() {
messageLabel.SetText("") messageLabel.SetText("")
}) })
mainWindow.Show() // this is messed up.
// mainWindow.Show()
} }
/* /*

View File

@ -45,7 +45,8 @@ func (n *node) showWidgetPlacement(b bool, s string) {
s1 += fmt.Sprintf("At(%2d,%2d) ", n.AtW, n.AtH) s1 += fmt.Sprintf("At(%2d,%2d) ", n.AtW, n.AtH)
} }
} }
log(b, s1, s, n.WidgetType, ",", n.Name) // , "text=", w.text) tmp := "." + n.Name + "."
log(b, s1, s, n.WidgetType, ",", tmp) // , "text=", w.text)
} }
func (n *node) dumpWidget(pad string) { func (n *node) dumpWidget(pad string) {

View File

@ -43,7 +43,7 @@ func catchActionChannel() {
log(logError,"ERROR: console did not initialize") log(logError,"ERROR: console did not initialize")
continue continue
} }
log(logNow, "catchActionChannel()", a.WidgetId, a.ActionType, a.WidgetType, a.Name) log(logInfo, "catchActionChannel()", a.WidgetId, a.ActionType, a.WidgetType, a.Name)
action(&a) action(&a)
} }
} }

View File

@ -51,8 +51,22 @@ func action(a *toolkit.Action) {
case toolkit.CloseToolkit: case toolkit.CloseToolkit:
log(logNow, "attempting to close the plugin and release stdout and stderr") log(logNow, "attempting to close the plugin and release stdout and stderr")
standardExit() standardExit()
case toolkit.Enable:
if n.Visible() {
// widget was already shown
} else {
log(logInfo, "Setting Visable to true", a.Name)
n.SetVisible(true)
}
case toolkit.Disable:
if n.Visible() {
log(logInfo, "Setting Visable to false", a.Name)
n.SetVisible(false)
} else {
// widget was already hidden
}
default: default:
log(logError, "action() Unknown =", a.ActionType, a.WidgetType, a.Name) log(logError, "action() ActionType =", a.ActionType, "WidgetType =", a.WidgetType, "Name =", a.Name)
} }
log(logInfo, "action() END") log(logInfo, "action() END")
} }
@ -70,16 +84,28 @@ func (n *node) AddText(text string) {
} }
func (n *node) SetText(text string) { func (n *node) SetText(text string) {
var changed bool = false
if (n == nil) { if (n == nil) {
log(logNow, "widget is nil") log(logNow, "widget is nil")
return return
} }
n.S = text if (n.Text != text) {
n.Text = text n.Text = text
changed = true
}
if (n.S != text) {
n.S = text
changed = true
}
if (! changed) {
return
}
n.textResize() if (n.Visible()) {
n.deleteView() n.textResize()
n.showView() n.deleteView()
n.showView()
}
} }
func (n *node) Set(val any) { func (n *node) Set(val any) {

View File

@ -20,20 +20,30 @@ func splitLines(s string) []string {
return lines return lines
} }
func (n *node) textResize() { func (n *node) textResize() bool {
w := n.tk w := n.tk
var width, height int = 0, 0 var width, height int = 0, 0
var changed bool = false
for i, s := range splitLines(n.Text) { for i, s := range splitLines(n.Text) {
log(logNow, "textResize() len =", len(s), i, s) log(logInfo, "textResize() len =", len(s), i, s)
if (width < len(s)) { if (width < len(s)) {
width = len(s) width = len(s)
} }
height += 1 height += 1
} }
w.gocuiSize.w1 = w.gocuiSize.w0 + width + me.FramePadW if (w.gocuiSize.w1 != w.gocuiSize.w0 + width + me.FramePadW) {
w.gocuiSize.h1 = w.gocuiSize.h0 + height + me.FramePadH w.gocuiSize.w1 = w.gocuiSize.w0 + width + me.FramePadW
n.showWidgetPlacement(logNow, "textResize()") changed = true
}
if (w.gocuiSize.h1 != w.gocuiSize.h0 + height + me.FramePadH) {
w.gocuiSize.h1 = w.gocuiSize.h0 + height + me.FramePadH
changed = true
}
if (changed) {
n.showWidgetPlacement(logNow, "textResize() changed")
}
return changed
} }
func (n *node) hideView() { func (n *node) hideView() {
@ -58,17 +68,38 @@ func (n *node) showView() {
x0, y0, x1, y1, err := me.baseGui.ViewPosition(w.cuiName) x0, y0, x1, y1, err := me.baseGui.ViewPosition(w.cuiName)
log(logInfo, "showView() w.v already defined for widget", n.Name, err) log(logInfo, "showView() w.v already defined for widget", n.Name, err)
// n.smartGocuiSize()
changed := n.textResize()
if (changed) {
log(logNow, "showView() textResize() changed. Should recreateView here wId =", w.cuiName)
} else {
log(logNow, "showView() Clear() and Fprint() here wId =", w.cuiName)
w.v.Clear()
fmt.Fprint(w.v, n.Text)
n.SetVisible(false)
n.SetVisible(true)
return
}
// if the gocui element has changed where it is supposed to be on the screen // if the gocui element has changed where it is supposed to be on the screen
// recreate it // recreate it
if (x0 != w.gocuiSize.w0) || (y0 != w.gocuiSize.h0) { if (x0 != w.gocuiSize.w0) {
log(logError, "showView() w.v.w0 != x0", n.Name, w.gocuiSize.w0, x0)
log(logError, "showView() w.v.h0 != y0", n.Name, w.gocuiSize.h0, y0)
n.recreateView() n.recreateView()
return return
} }
if (x1 != w.gocuiSize.w1) || (y1 != w.gocuiSize.h1) { if (y0 != w.gocuiSize.h0) {
log(logError, "showView() w.v.w1 != x1", n.Name, w.gocuiSize.w1, x1) log(logError, "showView() start hight mismatch id=", w.cuiName, "gocui h vs computed h =", w.gocuiSize.h0, y0)
log(logError, "showView() w.v.h1 != y1", n.Name, w.gocuiSize.h1, y1) n.recreateView()
return
}
if (x1 != w.gocuiSize.w1) {
log(logError, "showView() too wide", w.cuiName, "w,w", w.gocuiSize.w1, x1)
n.recreateView()
return
}
if (y1 != w.gocuiSize.h1) {
log(logError, "showView() too high", w.cuiName, "h,h", w.gocuiSize.h1, y1)
n.recreateView() n.recreateView()
return return
} }