diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml new file mode 100644 index 0000000..6092f4a --- /dev/null +++ b/.github/workflows/go.yml @@ -0,0 +1,28 @@ +# This workflow will build a golang project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go + +name: Go + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.23' + + - name: Build + run: go build -v ./... + + - name: Test + run: go test -v ./... diff --git a/.gitignore b/.gitignore index 6f72f89..60c2343 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,9 @@ go.work.sum # env file .env + +# Visual Studio settings +.vs/ + +# Visual Studio Code settings +.vscode/ diff --git a/core/api.go b/core/api.go index 65bc92e..15d5446 100644 --- a/core/api.go +++ b/core/api.go @@ -74,6 +74,19 @@ type ( XMLName xml.Name `json:"-" yaml:"-" xml:"ret"` List []clubitem `json:"list" yaml:"list" xml:"list>club" form:"list"` } + ArgClubInfo struct { + XMLName xml.Name `json:"-" yaml:"-" xml:"arg"` + CID uint64 `json:"cid" yaml:"cid" xml:"cid,attr"` + } + RetClubInfo struct { + XMLName xml.Name `json:"-" yaml:"-" xml:"ret"` + Name string `json:"name,omitempty" yaml:"name,omitempty" xml:"name,omitempty"` + Bank float64 `json:"bank" yaml:"bank" xml:"bank"` // users win/lost balance, in coins + Fund float64 `json:"fund" yaml:"fund" xml:"fund"` // jackpot fund, in coins + Lock float64 `json:"lock" yaml:"lock" xml:"lock"` // not changed deposit within games + Rate float64 `json:"rate" yaml:"rate" xml:"rate"` // jackpot rate for games with progressive jackpot + MRTP float64 `json:"mrtp" yaml:"mrtp" xml:"mrtp"` // master RTP + } ) func ApiClubList() (ret RetClubList, err error) { @@ -81,6 +94,14 @@ func ApiClubList() (ret RetClubList, err error) { return } +func ApiClubInfo(cid uint64) (ret RetClubInfo, err error) { + var arg = ArgClubInfo{ + CID: cid, + } + ret, _, err = HttpPost[ArgClubInfo, RetClubInfo]("/club/info", admin.Access, &arg) + return +} + type ( ArgPropGet struct { XMLName xml.Name `json:"-" yaml:"-" xml:"arg"` @@ -97,6 +118,26 @@ type ( XMLName xml.Name `json:"-" yaml:"-" xml:"ret"` Wallet float64 `json:"wallet" yaml:"wallet" xml:"wallet"` } + ArgAccessGet struct { + XMLName xml.Name `json:"-" yaml:"-" xml:"arg"` + CID uint64 `json:"cid" yaml:"cid" xml:"cid,attr"` + UID uint64 `json:"uid" yaml:"uid" xml:"uid,attr"` + All bool `json:"all" yaml:"all" xml:"all,attr"` + } + RetAccessGet struct { + XMLName xml.Name `json:"-" yaml:"-" xml:"ret"` + Access AL `json:"access" yaml:"access" xml:"access"` + } + ArgRtpGet struct { + XMLName xml.Name `json:"-" yaml:"-" xml:"arg"` + CID uint64 `json:"cid" yaml:"cid" xml:"cid,attr"` + UID uint64 `json:"uid" yaml:"uid" xml:"uid,attr"` + All bool `json:"all" yaml:"all" xml:"all,attr"` + } + RetRtpGet struct { + XMLName xml.Name `json:"-" yaml:"-" xml:"ret"` + MRTP float64 `json:"mrtp" yaml:"mrtp" xml:"mrtp"` + } ) func ApiPropGet(cid, uid uint64) (p Props, err error) { @@ -125,3 +166,27 @@ func ApiWalletGet(cid, uid uint64) (sum float64, err error) { sum = ret.Wallet return } + +func ApiAccessGet(cid, uid uint64, all bool) (al AL, err error) { + var arg = ArgAccessGet{ + CID: cid, + UID: uid, + All: all, + } + var ret RetAccessGet + ret, _, err = HttpPost[ArgAccessGet, RetAccessGet]("/prop/al/get", admin.Access, &arg) + al = ret.Access + return +} + +func ApiRtpGet(cid, uid uint64, all bool) (mrtp float64, err error) { + var arg = ArgRtpGet{ + CID: cid, + UID: uid, + All: all, + } + var ret RetRtpGet + ret, _, err = HttpPost[ArgRtpGet, RetRtpGet]("/prop/rtp/get", admin.Access, &arg) + mrtp = ret.MRTP + return +} diff --git a/core/logic.go b/core/logic.go index 8a32a2f..9b4c663 100644 --- a/core/logic.go +++ b/core/logic.go @@ -1,6 +1,7 @@ package core import ( + "strings" "time" cfg "github.com/slotopol/balance/config" @@ -52,3 +53,23 @@ func GetProp(cid uint64, user *User) (p Props, err error) { user.props[curcid] = p return } + +func FormatAL(al AL) string { + var items = make([]string, 0, 5) + if al&ALmem != 0 { + items = append(items, "member") + } + if al&ALgame != 0 { + items = append(items, "game") + } + if al&ALuser != 0 { + items = append(items, "user") + } + if al&ALclub != 0 { + items = append(items, "club") + } + if al&ALadmin != 0 { + items = append(items, "admin") + } + return strings.Join(items, ", ") +} diff --git a/core/startup.go b/core/startup.go index 03404b2..2f6ff4f 100644 --- a/core/startup.go +++ b/core/startup.go @@ -3,6 +3,7 @@ package core import ( "fmt" "log" + "strconv" "time" "fyne.io/fyne/v2" @@ -12,13 +13,37 @@ import ( cfg "github.com/slotopol/balance/config" ) +var Foreground bool + var ( admwnd = widget.NewLabel("not logined yet") clubtabs = &container.AppTabs{} - userlist *widget.List + userlist *widget.Table curcid uint64 + cural AL ) +var colhdr = []string{ + "email", "wallet", "MRTP", "access", +} + +func RefreshContent() { + var err error + + userlist.Refresh() + + var label = clubtabs.Selected().Content.(*widget.Label) + var bank, fund, deposit = "N/A", "N/A", "N/A" + if cural&ALclub != 0 { + var info RetClubInfo + if info, err = ApiClubInfo(curcid); err != nil { + return + } + bank, fund, deposit = fmt.Sprintf("%.2f", info.Bank), fmt.Sprintf("%.2f", info.Fund), fmt.Sprintf("%.2f", info.Lock) + } + label.SetText(fmt.Sprintf("bank: %s, jackpot fund: %s, deposit: %s", bank, fund, deposit)) +} + func MakeSignIn() (err error) { if err = cfg.ReadCredentials(); err != nil { log.Printf("failure on reading credentials, using default: %s\n", err.Error()) @@ -43,19 +68,25 @@ func MakeClubList() (err error) { var tabs = make([]*container.TabItem, len(cl.List)) for i, item := range cl.List { Clubs[item.Name] = item.CID - tabs[i] = container.NewTabItem(item.Name, widget.NewLabel("Content of tab "+item.Name)) + tabs[i] = container.NewTabItem(item.Name, widget.NewLabel("")) } clubtabs.SetItems(tabs) - curcid = Clubs[clubtabs.Selected().Text] clubtabs.OnSelected = func(tab *container.TabItem) { - var uid, ok = Clubs[tab.Text] - if !ok { + var err error + + var ok bool + if curcid, ok = Clubs[tab.Text]; !ok { + return + } + if cural, err = ApiAccessGet(curcid, admin.UID, true); err != nil { return } - curcid = uid - userlist.Refresh() + + RefreshContent() } + clubtabs.OnSelected(clubtabs.Selected()) + log.Printf("clubs list ready, %d clubs", len(Clubs)) return } @@ -72,7 +103,7 @@ func MakeUserList() (err error) { } if user.UID == 0 { cfg.UserList = append(cfg.UserList[:i], cfg.UserList[i+1:]...) - log.Printf("user with email '%s' presents in yaml list but absent in server database, skipped") + log.Printf("user with email '%s' presents in yaml list but absent in server database, skipped", email) continue } user.props = map[uint64]Props{} // make new empty map @@ -81,7 +112,9 @@ func MakeUserList() (err error) { go func() { var c = time.Tick(Cfg.PropUpdateTick) for range c { - userlist.Refresh() + if Foreground { + RefreshContent() + } } }() log.Printf("users list ready, %d users", len(Users)) @@ -116,39 +149,88 @@ func StartupChain() { } } +func Lifecycle(a fyne.App) { + var l = a.Lifecycle() + l.SetOnStarted(func() { + log.Println("lifecycle: started") + }) + l.SetOnStopped(func() { + log.Println("lifecycle: stopped") + }) + l.SetOnEnteredForeground(func() { + Foreground = true + log.Println("lifecycle: entered foreground") + }) + l.SetOnExitedForeground(func() { + Foreground = false + log.Println("lifecycle: exited foreground") + }) +} + func CreateMainWindow(a fyne.App) fyne.Window { var w = a.NewWindow("Balance") - userlist = widget.NewList( - func() int { - return len(cfg.UserList) - }, + userlist = widget.NewTableWithHeaders( + func() (int, int) { return len(cfg.UserList), 4 }, func() fyne.CanvasObject { - return container.NewHBox(widget.NewLabel("email"), widget.NewLabelWithStyle("wallet", fyne.TextAlignLeading, fyne.TextStyle{ - Bold: true, - Monospace: true, - })) + var label = widget.NewLabel("") + label.Truncation = fyne.TextTruncateClip + return label }, - func(id widget.ListItemID, item fyne.CanvasObject) { + func(id widget.TableCellID, cell fyne.CanvasObject) { var err error - var mailwid = item.(*fyne.Container).Objects[0].(*widget.Label) - mailwid.SetText(cfg.UserList[id]) - - var wallwid = item.(*fyne.Container).Objects[1].(*widget.Label) - var user, ok = Users[cfg.UserList[id]] + var label = cell.(*widget.Label) + var user, ok = Users[cfg.UserList[id.Row]] if !ok { - wallwid.SetText("error") + label.SetText("error") + return + } + if id.Col == 0 { // email + label.SetText(cfg.UserList[id.Row]) + return + } + if cural&ALuser == 0 { + label.SetText("N/A") return } var prop Props if prop, err = GetProp(curcid, &user); err != nil { - wallwid.SetText("error") + label.SetText("error") return } - wallwid.SetText(fmt.Sprintf("%.2f", prop.Wallet)) - }, - ) + switch id.Col { + case 1: // wallet + label.SetText(fmt.Sprintf("%.2f", prop.Wallet)) + case 2: // mtrp + if prop.MRTP > 0 { + label.SetText(fmt.Sprintf("%g%%", prop.MRTP)) + } else { + label.SetText("-") + } + case 3: // access + label.SetText(FormatAL(prop.Access)) + } + }) + userlist.SetColumnWidth(0, 180) // email + userlist.SetColumnWidth(1, 100) // wallet + userlist.SetColumnWidth(2, 50) // mtrp + userlist.SetColumnWidth(3, 150) // access + userlist.UpdateHeader = func(id widget.TableCellID, cell fyne.CanvasObject) { + var label = cell.(*widget.Label) + if id.Row < 0 { + label.SetText(colhdr[id.Col]) + } else if id.Col < 0 { + var user, ok = Users[cfg.UserList[id.Row]] + if !ok { + label.SetText("error") + return + } + label.SetText(strconv.Itoa(int(user.UID))) + } else { + label.SetText("") + } + } var frame = container.NewBorder( container.NewVBox(admwnd, clubtabs), diff --git a/main.go b/main.go index a3875eb..4e1b71b 100644 --- a/main.go +++ b/main.go @@ -1,32 +1,14 @@ package main import ( - "log" - - "fyne.io/fyne/v2" "fyne.io/fyne/v2/app" "github.com/slotopol/balance/core" ) -func logLifecycle(a fyne.App) { - a.Lifecycle().SetOnStarted(func() { - log.Println("Lifecycle: Started") - }) - a.Lifecycle().SetOnStopped(func() { - log.Println("Lifecycle: Stopped") - }) - a.Lifecycle().SetOnEnteredForeground(func() { - log.Println("Lifecycle: Entered Foreground") - }) - a.Lifecycle().SetOnExitedForeground(func() { - log.Println("Lifecycle: Exited Foreground") - }) -} - func main() { var a = app.NewWithID("slotopol.balance") - logLifecycle(a) + core.Lifecycle(a) var w = core.CreateMainWindow(a) w.ShowAndRun() }