Skip to content

Commit 8f529cd

Browse files
authored
Merge pull request #40 from KiraCore/feature/tx_send
Feature/tx send
2 parents 167605f + ef1df16 commit 8f529cd

15 files changed

+410
-145
lines changed

Diff for: go.mod

+6-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ require (
88
github.com/cosmos/go-bip39 v1.0.0
99
github.com/fyne-io/terminal v0.0.0-20240422094903-6a6996b84c7e
1010
github.com/kiracore/tools/bip39gen v0.0.0-20240502110212-fd9aae04a1a7
11-
golang.org/x/crypto v0.23.0
11+
github.com/pkg/sftp v1.10.1
12+
golang.org/x/crypto v0.24.0
1213
)
1314

1415
require (
@@ -30,6 +31,8 @@ require (
3031
github.com/gopherjs/gopherjs v1.17.2 // indirect
3132
github.com/inconshreveable/mousetrap v1.0.1 // indirect
3233
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect
34+
github.com/kr/fs v0.1.0 // indirect
35+
github.com/pkg/errors v0.8.1 // indirect
3336
github.com/pmezard/go-difflib v1.0.0 // indirect
3437
github.com/spf13/cobra v1.6.1 // indirect
3538
github.com/spf13/pflag v1.0.5 // indirect
@@ -41,8 +44,8 @@ require (
4144
golang.org/x/image v0.16.0 // indirect
4245
golang.org/x/mobile v0.0.0-20230531173138-3c911d8e3eda // indirect
4346
golang.org/x/net v0.25.0 // indirect
44-
golang.org/x/sys v0.20.0 // indirect
45-
golang.org/x/text v0.15.0 // indirect
47+
golang.org/x/sys v0.21.0 // indirect
48+
golang.org/x/text v0.16.0 // indirect
4649
gopkg.in/yaml.v3 v3.0.1 // indirect
4750
honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2 // indirect
4851
)

Diff for: go.sum

+11-8
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ github.com/kiracore/tools/bip39gen v0.0.0-20240502110212-fd9aae04a1a7 h1:xQxPyeQ
223223
github.com/kiracore/tools/bip39gen v0.0.0-20240502110212-fd9aae04a1a7/go.mod h1:AvRV4iiU7iYB0kZ6fkviLCdP9zi6xbClPx2+Ivijuq0=
224224
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
225225
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
226+
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
226227
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
227228
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
228229
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@@ -249,9 +250,11 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb
249250
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
250251
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
251252
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
253+
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
252254
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
253255
github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA=
254256
github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo=
257+
github.com/pkg/sftp v1.10.1 h1:VasscCm72135zRysgrJDKsntdmPN+OuU3+nnHYA9wyc=
255258
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
256259
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
257260
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -325,8 +328,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
325328
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
326329
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
327330
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
328-
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
329-
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
331+
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
332+
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
330333
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
331334
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
332335
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -477,11 +480,11 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
477480
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
478481
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
479482
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
480-
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
481-
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
483+
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
484+
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
482485
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
483-
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
484-
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
486+
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
487+
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
485488
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
486489
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
487490
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -490,8 +493,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
490493
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
491494
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
492495
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
493-
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
494-
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
496+
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
497+
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
495498
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
496499
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
497500
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=

Diff for: gui/dialog_connect.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ func (g *Gui) ShowConnect() {
238238

239239
// / test ui block
240240
testButton := widget.NewButton("connect to tested env", func() {
241-
ipEntry.Text = "192.168.1.101"
241+
ipEntry.Text = "192.168.1.102"
242242
userEntry.Text = "d"
243243
passwordEntry.Text = "d"
244244
passphraseCheck.SetChecked(false)

Diff for: gui/dialog_deploy.go

+35-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"encoding/json"
55
"fmt"
66
"log"
7+
"path/filepath"
78
"strconv"
89
"time"
910

@@ -13,6 +14,7 @@ import (
1314
"fyne.io/fyne/v2/theme"
1415
"fyne.io/fyne/v2/widget"
1516
dialogWizard "github.com/KiraCore/kensho/gui/dialogs"
17+
"github.com/KiraCore/kensho/helper/gssh"
1618
"github.com/KiraCore/kensho/helper/httph"
1719
"github.com/KiraCore/kensho/types"
1820
)
@@ -136,15 +138,46 @@ func showDeployDialog(g *Gui, doneListener binding.DataListener, shidaiInfra bin
136138
sP, _ := sudoPasswordBinding.Get()
137139
sInfra, _ := shidaiInfra.Get()
138140
if !sInfra {
139-
cmdForDeploy := fmt.Sprintf(`echo '%v' | sudo -S sh -c "$(curl -s --show-error --fail %v 2>&1)"`, sP, types.BOOTSTRAP_SCRIPT)
140-
showCmdExecDialogAndRunCmdV4(g, "Deploying", cmdForDeploy, true, deployErrorBinding, errorMessageBinding)
141+
sekaiVersion, interxVersion, err := httph.GetBinariesVersionsFromTrustedNode(payload.Args.IP, strconv.Itoa(payload.Args.RPCPort), strconv.Itoa(payload.Args.InterxPort))
142+
if err != nil {
143+
g.showErrorDialog(err, binding.NewDataListener(func() {}))
144+
return
145+
}
141146

147+
bootstrapFileUrl := types.BOOTSTRAP_SCRIPT
148+
filePathToSaveOnRemote := filepath.Join("/home/", g.sshClient.User(), "bootstrap.sh")
149+
log.Println("Bootstrap file save path:", filePathToSaveOnRemote)
150+
f, err := httph.MakeHttpRequest(bootstrapFileUrl, "GET")
151+
if err != nil {
152+
log.Println(err.Error())
153+
g.showErrorDialog(fmt.Errorf("error when downloading bootstrap script: %v ", err.Error()), binding.NewDataListener(func() {}))
154+
return
155+
}
156+
err = gssh.SendFileSFTP(g.sshClient, f, filePathToSaveOnRemote)
157+
if err != nil {
158+
log.Println(err.Error())
159+
g.showErrorDialog(fmt.Errorf("error when sending bootstrap script to remove host: %v ", err.Error()), binding.NewDataListener(func() {}))
160+
return
161+
}
162+
163+
cmdForChmod := fmt.Sprintf(`chmod +x %v 2>&1`, filePathToSaveOnRemote)
164+
showCmdExecDialogAndRunCmdV4(g, "Deploying", cmdForChmod, true, deployErrorBinding, errorMessageBinding)
142165
errB, _ := deployErrorBinding.Get()
143166
if errB {
144167
errMsg, _ := errorMessageBinding.Get()
145168
g.showErrorDialog(fmt.Errorf("error while checking the sudo password: %v ", errMsg), binding.NewDataListener(func() {}))
146169
return
147170
}
171+
172+
cmdForDeploy := fmt.Sprintf(`echo '%v' | sudo -S sh -c "%v --sekai=%v --interx=%v 2>&1"`, sP, filePathToSaveOnRemote, sekaiVersion, interxVersion)
173+
showCmdExecDialogAndRunCmdV4(g, "Deploying", cmdForDeploy, true, deployErrorBinding, errorMessageBinding)
174+
175+
errB, _ = deployErrorBinding.Get()
176+
if errB {
177+
errMsg, _ := errorMessageBinding.Get()
178+
g.showErrorDialog(fmt.Errorf("error while checking the sudo password: %v ", errMsg), binding.NewDataListener(func() {}))
179+
return
180+
}
148181
time.Sleep(time.Second * 3)
149182
}
150183

Diff for: gui/dialog_mnemoicManager.go

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ func showMnemonicManagerDialog(g *Gui, mnemonicBinding binding.String, doneActio
2020
var wizard *dialogWizard.Wizard
2121
mnemonicDisplay := container.NewGridWithColumns(2)
2222
localMnemonicBinding := binding.NewString()
23+
oldM, _ := mnemonicBinding.Get()
24+
localMnemonicBinding.Set(oldM)
2325
warningConfirmDataListener := binding.NewDataListener(func() {
2426
lMnemonic, err := localMnemonicBinding.Get()
2527
if err != nil {

Diff for: gui/dialogs.go

+36-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"log"
66
"regexp"
7+
"strings"
78
"sync"
89

910
"fyne.io/fyne/v2"
@@ -92,7 +93,7 @@ func showCmdExecDialogAndRunCmdV4(g *Gui, infoMSG string, cmd string, autoHideCh
9293
outputChannel := make(chan string)
9394
errorChannel := make(chan gssh.ResultV2)
9495
// go gssh.ExecuteSSHCommandV2(g.sshClient, cmd, outputChannel, errorChannel)
95-
go gssh.ExecuteSSHCommandV2(g.sshClient, cmd, outputChannel, errorChannel)
96+
go gssh.ExecuteSSHCommandV3(g.sshClient, cmd, outputChannel, errorChannel)
9697

9798
var wizard *dialogWizard.Wizard
9899
outputMsg := binding.NewString()
@@ -187,3 +188,37 @@ func showWarningMessageWithConfirmation(g *Gui, warningMessage string, confirmAc
187188
wizard.Show(g.Window)
188189
wizard.Resize(fyne.NewSize(500, 400))
189190
}
191+
192+
func showMonikerEntryDialog(g *Gui, monikerBinding binding.String, confirmAction binding.DataListener) {
193+
var wizard *dialogWizard.Wizard
194+
195+
monikerEntry := widget.NewEntry()
196+
monikerEntry.Wrapping = fyne.TextWrap(fyne.TextTruncateClip)
197+
monikerEntry.MultiLine = true
198+
199+
doneButton := widget.NewButton("Claim", func() {
200+
moniker := monikerEntry.Text
201+
trimmed := strings.TrimSpace(moniker)
202+
if trimmed == "" {
203+
monikerEntry.SetValidationError(fmt.Errorf("moniker cannot be empty"))
204+
return
205+
}
206+
monikerBinding.Set(moniker)
207+
confirmAction.DataChanged()
208+
wizard.Hide()
209+
})
210+
doneButton.Importance = widget.HighImportance
211+
212+
cancelButton := widget.NewButton("Cancel", func() { wizard.Hide() })
213+
214+
content := container.NewBorder(
215+
nil,
216+
container.NewVBox(doneButton, cancelButton),
217+
nil, nil,
218+
container.NewVScroll(monikerEntry),
219+
)
220+
221+
wizard = dialogWizard.NewWizard("Enter your Moniker", content)
222+
wizard.Show(g.Window)
223+
wizard.Resize(fyne.NewSize(500, 400))
224+
}

Diff for: gui/gui.go

+43-6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"fmt"
66
"log"
7+
"time"
78

89
"fyne.io/fyne/v2"
910
"fyne.io/fyne/v2/container"
@@ -26,6 +27,14 @@ type Gui struct {
2627
LogCtx context.Context
2728
LogCtxCancel context.CancelFunc
2829
NodeInfo nodeInfoScreen
30+
TxExec TxExecBinding
31+
32+
Version string
33+
}
34+
35+
type TxExecBinding struct {
36+
TxExecutionStatusBinding binding.Bool
37+
TxDoneListener binding.DataListener
2938
}
3039
type Host struct {
3140
IP string
@@ -42,14 +51,39 @@ func (g *Gui) MakeGui() fyne.CanvasObject {
4251
reconnectButton.Hide()
4352
reconnectButton.Importance = widget.DangerImportance
4453

45-
tab := container.NewBorder(container.NewVBox(title, info), reconnectButton, nil, nil, mainWindow)
54+
loadingData := binding.NewFloat()
55+
56+
loadWidget := widget.NewProgressBarWithData(loadingData)
57+
g.TxExec.TxDoneListener = binding.NewDataListener(func() {})
58+
txExecLoadingWidget := container.NewStack(loadWidget)
59+
txExecLoadingWidget.Hide()
60+
g.TxExec.TxExecutionStatusBinding = binding.NewBool()
61+
g.TxExec.TxExecutionStatusBinding.AddListener(binding.NewDataListener(func() {
62+
state, _ := g.TxExec.TxExecutionStatusBinding.Get()
63+
log.Println("TxExecutionStatusBinding state:", state)
64+
if state {
65+
txExecLoadingWidget.Show()
66+
var maxRange float64 = 40
67+
for i := range int(maxRange) {
68+
time.Sleep(time.Second * 1)
69+
var percentage float64 = ((float64(i) * 100) / maxRange) * 0.01
70+
// log.Println("wait tx state:", percentage)
71+
loadingData.Set(float64(percentage))
72+
// txExecLoadingWidget.Refresh()
73+
loadWidget.SetValue(float64(percentage))
74+
loadWidget.Refresh()
75+
}
76+
g.TxExec.TxDoneListener.DataChanged()
77+
g.TxExec.TxExecutionStatusBinding.Set(false)
78+
txExecLoadingWidget.Hide()
79+
}
80+
}))
81+
82+
tab := container.NewBorder(container.NewVBox(title, info), container.NewVBox(txExecLoadingWidget, reconnectButton), nil, nil, mainWindow)
4683

4784
g.ConnectionStatusBinding = binding.NewBool()
4885
g.ConnectionStatusBinding.AddListener(binding.NewDataListener(func() {
49-
state, err := g.ConnectionStatusBinding.Get()
50-
if err != nil {
51-
log.Printf("error when getting connection status binding: %v", err)
52-
}
86+
state, _ := g.ConnectionStatusBinding.Get()
5387
if state {
5488
g.Window.SetTitle(fmt.Sprintf("%v (connected)", appName))
5589
reconnectButton.Hide()
@@ -62,12 +96,15 @@ func (g *Gui) MakeGui() fyne.CanvasObject {
6296
}
6397
}
6498
}))
99+
65100
setTab := func(t Tab) {
66101
title.SetText(t.Title)
67102
info.SetText(t.Info)
68103
mainWindow.Objects = []fyne.CanvasObject{t.View(g.Window, g)}
69104
}
70-
menuAndTab := container.NewHSplit(g.makeNav(setTab), tab)
105+
versionData := binding.NewString()
106+
versionData.Set(fmt.Sprintf("Version: %v", g.Version))
107+
menuAndTab := container.NewHSplit(container.NewBorder(nil, widget.NewLabelWithData(versionData), nil, nil, g.makeNav(setTab)), tab)
71108
menuAndTab.Offset = 0.2
72109
return menuAndTab
73110

Diff for: gui/screen_networkVisor.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,7 @@ func makeNetworkTreeScreen(_ fyne.Window, g *Gui) fyne.CanvasObject {
7070
nodes, _, err = networkparser.GetAllNodesV3(context.Background(), g.Host.IP, 3, false)
7171

7272
// TODO: for testing
73-
nodes, _, err = networkparser.GetAllNodesV3(context.Background(), "148.251.69.56", 4, false)
74-
// nodes, _, err = networkparser.GetAllNodesV3(context.Background(), "148.251.69.561", 4, false)
73+
// nodes, _, err = networkparser.GetAllNodesV3(context.Background(), "148.251.69.56", 4, false)
7574
//
7675
if err != nil {
7776
log.Println(err)

0 commit comments

Comments
 (0)