diff --git a/cmd/v2/FyneApp.toml b/cmd/v2/FyneApp.toml index 5642e3312..4bb08fb2d 100644 --- a/cmd/v2/FyneApp.toml +++ b/cmd/v2/FyneApp.toml @@ -4,5 +4,5 @@ Website = "http://legu.cc" Icon = "app.png" Name = "RobotGUI" ID = "cc.legu.app" - Version = "1.0.12" - Build = 15 + Version = "1.0.13" + Build = 16 diff --git a/cmd/v2/lib/common/lang.go b/cmd/v2/lib/common/lang.go index d0d04273a..bb0f3ed4b 100644 --- a/cmd/v2/lib/common/lang.go +++ b/cmd/v2/lib/common/lang.go @@ -172,4 +172,6 @@ const ( USERINFO_MODINAME = "免费改名次数" USERINFO_ACTIVE_DAY = "日活" USERINFO_ACTIVE_WEEK = "周活" + USERINFO_CREATETM = "创建" + USERINFO_UPDATETM = "更新" ) diff --git a/cmd/v2/lib/common/utils.go b/cmd/v2/lib/common/utils.go index 4f6bf4c1d..b524d658e 100644 --- a/cmd/v2/lib/common/utils.go +++ b/cmd/v2/lib/common/utils.go @@ -4,8 +4,10 @@ import ( "bytes" "encoding/json" "io" + "math" "os" "path/filepath" + "strconv" "strings" "github.com/sirupsen/logrus" @@ -117,3 +119,40 @@ func RemoveContents(dir string) error { } return nil } + +func ConvertFileSize(size int64) string { + if size == 0 { + return "0" + } + + kb := float64(size) / float64(1024) + + if kb < 1024 { + f := math.Ceil(kb) + return strconv.Itoa(int(f)) + " KB" + } + + mb := kb / float64(1024) + if mb < 1024 { + f := math.Ceil(mb) + return strconv.Itoa(int(f)) + " MB" + } + + gb := mb / float64(1024) + if gb < 1024 { + f := math.Ceil(mb) + return strconv.Itoa(int(f)) + " G" + } + + t := gb / float64(1024) + if t < 1024 { + f := math.Ceil(mb) + return strconv.Itoa(int(f)) + " T" + } + + if t >= 1024 { + return "VeryBig" + } + + return "0" +} diff --git a/cmd/v2/lib/common/utils_test.go b/cmd/v2/lib/common/utils_test.go index a43bfde6b..5184da103 100644 --- a/cmd/v2/lib/common/utils_test.go +++ b/cmd/v2/lib/common/utils_test.go @@ -10,3 +10,9 @@ func TestSubStr(t *testing.T) { r := SubStr(str, 20, len(str)) fmt.Println(r) } + +func TestConver(t *testing.T) { + + s := ConvertFileSize(25325466) + fmt.Println(s) +} diff --git a/cmd/v2/main.go b/cmd/v2/main.go index b6e45c4a9..71d718cd1 100644 --- a/cmd/v2/main.go +++ b/cmd/v2/main.go @@ -81,12 +81,12 @@ func main() { w.SetContent(container.NewGridWithColumns(2, widget.NewButton("工具", func() { toolWindow := ui.NewToolWindow(appUI, w) - toolWindow.CreateWindow(common.APP_NAME, 1366, 768, true) + toolWindow.CreateWindow(common.APP_NAME, 1499, 800, true) w.Hide() }), widget.NewButton("登服", func() { mainWindow := ui.NewMainWindow(appUI, w) - mainWindow.CreateWindow(common.APP_NAME, 1366, 768, true) + mainWindow.CreateWindow(common.APP_NAME, 1499, 800, true) w.Hide() }))) w.SetFixedSize(true) diff --git a/cmd/v2/model/ssh.go b/cmd/v2/model/ssh.go index 989262c9b..5c4229e13 100644 --- a/cmd/v2/model/ssh.go +++ b/cmd/v2/model/ssh.go @@ -13,4 +13,9 @@ type SSHModel struct { LubanCli string DataDir string JsonDir string + + // + SaveDir string //保存目录 + LogDir string //远程日志目录 + Editor string //编辑器 } diff --git a/cmd/v2/service/sshService.go b/cmd/v2/service/sshService.go index 532d3ab42..8c8d90d69 100644 --- a/cmd/v2/service/sshService.go +++ b/cmd/v2/service/sshService.go @@ -2,6 +2,8 @@ package service import ( "fmt" + "io" + "io/fs" "io/ioutil" "log" "net" @@ -281,7 +283,91 @@ func (ss *SSHService) Scp(targetDir, srcFileName string) (int, error) { return n, nil } +//Download +func (ss *SSHService) ScpDownload(localDir, remoteFilePath string) error { + sftpCli, err = ss.getSftp() + if err != nil { + return fmt.Errorf("new sftp client error: %w", err) + } + + remoteFile, err := sftpCli.Open(remoteFilePath) + if err != nil { + log.Println("scpCopy:", err) + return err + } + defer remoteFile.Close() + + fileName := path.Base(remoteFile.Name()) + + if err := os.MkdirAll(localDir, fs.ModePerm); err != nil { + return err + } + + target, err := os.OpenFile(localDir+fileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC, fs.ModePerm) + if err != nil { + return fmt.Errorf("open local file error: %w", err) + } + defer target.Close() + + _, err = io.Copy(target, remoteFile) + if err != nil { + return fmt.Errorf("write file error: %w", err) + } + return nil +} + +func (ss *SSHService) GetRemoteDir(remoteDir string) (files []File, err error) { + sftpCli, err = ss.getSftp() + if err != nil { + return nil, fmt.Errorf("new sftp client error: %w", err) + } + + remoteFiles, err := sftpCli.ReadDir(remoteDir) + if err != nil { + log.Println("read remote Dir:", err) + return nil, err + } + + for _, f := range remoteFiles { + fi := File{ + FileName: f.Name(), + FilePath: filepath.Join(remoteDir, f.Name()), + Size: f.Size(), + } + files = append(files, fi) + // logrus.WithFields(logrus.Fields{"name": f.Name(), "size": f.Size()}).Debug("远程日志文件") + } + return +} + +func (ss *SSHService) BatchScpDownload(localDir, remoteDir string) error { + sftpCli, err = ss.getSftp() + if err != nil { + return fmt.Errorf("new sftp client error: %w", err) + } + + remoteFiles, err := sftpCli.ReadDir(remoteDir) + if err != nil { + log.Println("read remote Dir:", err) + return err + } + + for _, f := range remoteFiles { + if err := ss.ScpDownload(localDir, filepath.Join(remoteDir, f.Name())); err != nil { + return err + } + } + + return nil +} + type CopyFiles struct { Dir string FileName string } + +type File struct { + FileName string + FilePath string + Size int64 +} diff --git a/cmd/v2/service/ssh_test.go b/cmd/v2/service/ssh_test.go index 86a1b264a..8674219b8 100644 --- a/cmd/v2/service/ssh_test.go +++ b/cmd/v2/service/ssh_test.go @@ -2,6 +2,7 @@ package service import ( "fmt" + "log" "path" "path/filepath" "testing" @@ -56,3 +57,31 @@ func TestMath(t *testing.T) { a := float64(10) / float64(3) fmt.Println(a) } + +func TestScpDown(t *testing.T) { + ciphers := []string{} + ssh := &SSHService{} + + err := ssh.Connect(username, password, ip, "", port, ciphers) + if err != nil { + t.Fatal(err) + } + + sftpCli, err = ssh.getSftp() + if err != nil { + fmt.Errorf("new sftp client error: %w", err) + } + + remoteFiles, err := sftpCli.ReadDir("/home/liwei/go_dreamfactory/bin/log") + if err != nil { + log.Println("scpCopy:", err) + } + + for _, v := range remoteFiles { + fmt.Println(v.Name()) + if err := ssh.ScpDownload("G:/log/", "/home/liwei/go_dreamfactory/bin/log/"+v.Name()); err != nil { + fmt.Println(err) + } + } + +} diff --git a/cmd/v2/ui/app_monitor.go b/cmd/v2/ui/app_monitor.go index c840a1d6e..c21e3a06b 100644 --- a/cmd/v2/ui/app_monitor.go +++ b/cmd/v2/ui/app_monitor.go @@ -78,7 +78,7 @@ func (this *appMonitor) Run() { data := d.(*model.PushModel) this.monitorData.DataList = append(this.monitorData.DataList, data) this.reloadMonitorData() - showTip("收到新的数据推送,请打开[监控]页面") + showCanvasTip("收到新的数据推送,请打开[推送]页面") }, }) } diff --git a/cmd/v2/ui/list.go b/cmd/v2/ui/list.go index 722600133..849ed31e3 100644 --- a/cmd/v2/ui/list.go +++ b/cmd/v2/ui/list.go @@ -57,10 +57,11 @@ func (l List) Less(i, j int) bool { } type Item struct { - Title string `json:"title"` - Text string `json:"text"` - Quantity int `json:"quantity"` - Checked bool `json:"checked"` + Title string `json:"title"` + Text string `json:"text"` + Quantity int `json:"quantity"` + Checked bool `json:"checked"` + Size int64 `json:"size"` } func NewList(name string) List { diff --git a/cmd/v2/ui/mainwindow.go b/cmd/v2/ui/mainwindow.go index 4c1a1a9c9..0c7b7eba7 100644 --- a/cmd/v2/ui/mainwindow.go +++ b/cmd/v2/ui/mainwindow.go @@ -1,6 +1,7 @@ package ui import ( + "errors" "fmt" "go_dreamfactory/cmd/v2/lib/common" "go_dreamfactory/cmd/v2/service" @@ -107,7 +108,7 @@ func (ui *MainWindowImpl) SetStatusMsg(msg string) { } func (ui *MainWindowImpl) quiteHandle() { - dialog.ShowConfirm("推出系统", "确定退出吗", func(b bool) { + dialog.ShowConfirm("提示", "确定退出吗", func(b bool) { if !b { return } @@ -265,6 +266,20 @@ func (ui *MainWindowImpl) createLoginWin(sid, sname string) { // create role ui.createRoleWindowPopUp() } + } else if msg.MainType == "notify" && msg.SubType == "errornotify" { + push := &pb.NotifyErrorNotifyPush{} + if !comm.ProtoUnmarshal(msg, push) { + logrus.Error("unmarsh err") + return + } + + if push.Code == pb.ErrorCode_UserLogined { + cf := dialog.NewError(errors.New("【"+account.Text+"】账号已在其它终端登录,退出重新登录"), ui.w) + cf.SetOnClosed(func() { + ui.app.Quit() + }) + cf.Show() + } } }, }) diff --git a/cmd/v2/ui/tip.go b/cmd/v2/ui/tip.go index 89ad4236c..13b16a040 100644 --- a/cmd/v2/ui/tip.go +++ b/cmd/v2/ui/tip.go @@ -1,9 +1,11 @@ package ui import ( + "image/color" "time" "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/driver/desktop" "fyne.io/fyne/v2/widget" ) @@ -22,3 +24,17 @@ func showTip(content string) { }() } } + +func showCanvasTip(content string) { + drv := fyne.CurrentApp().Driver() + if drv, ok := drv.(desktop.Driver); ok { + w := drv.CreateSplashWindow() + w.SetContent(canvas.NewText(content, color.RGBA{255, 0, 0, 255})) + w.Show() + + go func() { + time.Sleep(time.Millisecond * 1500) + w.Close() + }() + } +} diff --git a/cmd/v2/ui/tool_gen.go b/cmd/v2/ui/tool_gen.go index fb56b230f..177db34b2 100644 --- a/cmd/v2/ui/tool_gen.go +++ b/cmd/v2/ui/tool_gen.go @@ -100,17 +100,17 @@ func (this *appGen) LazyInit(obs observer.Observer) error { form := widget.NewForm( widget.NewFormItem("服务地址", serverAddr), widget.NewFormItem("项目目录", container.NewBorder(nil, nil, nil, widget.NewButtonWithIcon("", theme.FolderIcon(), func() { - openFolder(projectDir) + openFolder(projectDir, toolWin.w) }), projectDir)), widget.NewFormItem("工作目录", container.NewBorder(nil, nil, nil, widget.NewButtonWithIcon("", theme.FolderIcon(), func() { - openFolder(workDir) + openFolder(workDir, toolWin.w) }), workDir)), widget.NewFormItem("LuBan Cli", client), widget.NewFormItem("输入目录", inputDir), widget.NewFormItem("输出Code目录", outputCodeDir), widget.NewFormItem("输出JSON目录", outputJsonDir), widget.NewFormItem("临时目录", container.NewBorder(nil, nil, nil, widget.NewButtonWithIcon("", theme.FolderIcon(), func() { - openFolder(tmpDir) + openFolder(tmpDir, toolWin.w) }), tmpDir)), widget.NewFormItem("生成类型", genType), ) @@ -151,7 +151,7 @@ func (this *appGen) LazyInit(obs observer.Observer) error { this.goList.titleLabel = widget.NewLabel("Go文件") this.goList.titleLabel.Hide() // 复选列表 - this.goList.itemList = this.goList.createList() + this.goList.itemList = this.goList.CreateDefaultList() // 覆盖 -go go_allSelBtn := &widget.Button{Icon: theme.CheckButtonIcon()} @@ -216,7 +216,7 @@ func (this *appGen) LazyInit(obs observer.Observer) error { this.jsonList.titleLabel = widget.NewLabel("Json文件") this.jsonList.titleLabel.Hide() // 复选列表 - this.jsonList.itemList = this.jsonList.createList() + this.jsonList.itemList = this.jsonList.CreateDefaultList() // 覆盖 -go json_allSelBtn := &widget.Button{Icon: theme.CheckButtonIcon()} @@ -375,14 +375,14 @@ func (this *appGen) LazyInit(obs observer.Observer) error { } // 打开目录 -func openFolder(entry *widget.Entry) { +func openFolder(entry *widget.Entry, w fyne.Window) { dConf := dialog.NewFolderOpen(func(lu fyne.ListableURI, err error) { if lu == nil { return } entry.Text = lu.Path() entry.Refresh() - }, toolWin.w) + }, w) luri, _ := storage.ListerForURI(storage.NewFileURI(".")) dConf.SetLocation(luri) dConf.SetConfirmText("打开") @@ -391,6 +391,21 @@ func openFolder(entry *widget.Entry) { dConf.Show() } +func openFile(entry *widget.Entry, w fyne.Window) { + dConf := dialog.NewFileOpen(func(lu fyne.URIReadCloser, err error) { + if lu == nil { + return + } + entry.SetText(lu.URI().Path()) + entry.Refresh() + }, w) + dConf.SetConfirmText("打开") + dConf.SetDismissText("取消") + dConf.SetFilter(storage.NewExtensionFileFilter([]string{".exe"})) + dConf.Resize(fyne.NewSize(750, 500)) + dConf.Show() +} + type fileList struct { selItemIds []string //选择的ID fileTotal int //文件总数 @@ -413,7 +428,8 @@ func (f *fileList) reset() { f.cachedList = NewList("") } -func (f *fileList) createList() *widget.List { +// 创建默认的列表 +func (f *fileList) CreateDefaultList() *widget.List { f.itemList = widget.NewList( func() int { return len(f.cachedList.Items) @@ -443,7 +459,50 @@ func (f *fileList) createList() *widget.List { return f.itemList } -func (f *fileList) addItem(val string) { +// 创建可扩展属性的列表 +func (f *fileList) CreateDownloadList() *widget.List { + f.itemList = widget.NewList( + func() int { + return len(f.cachedList.Items) + }, + func() fyne.CanvasObject { + chk := widget.NewCheck("Template", func(bool) {}) + lb := widget.NewLabel("Template") + items := container.NewHBox(chk, &layout.Spacer{}, lb) + return items + }, + func(id widget.ListItemID, item fyne.CanvasObject) { + c, _ := item.(*fyne.Container) + chk := c.Objects[0].(*widget.Check) + data := f.cachedList.Items[id] + chk.Text = data.Text + chk.Checked = data.Checked + chk.OnChanged = func(b bool) { + if b { + f.selItemIds = append(f.selItemIds, chk.Text) + } else { + f.selItemIds = utils.DeleteString(f.selItemIds, chk.Text) + } + f.cachedList.Items[id].Checked = b + // sort.Sort(f.cachedList) + f.itemList.Refresh() + } + + lb := c.Objects[2].(*widget.Label) + lb.Text = common.ConvertFileSize(data.Size) + c.Refresh() + }, + ) + return f.itemList +} + +func (f *fileList) AddItem(item Item) { + f.cachedList.Items = append(f.cachedList.Items, item) + // sort.Sort(f.cachedList) + f.itemList.Refresh() +} + +func (f *fileList) AddItemWithText(val string) { val = strings.TrimSpace(val) if len(val) == 0 { return @@ -480,7 +539,7 @@ func (f *fileList) loadItem(dirPath string) { for _, file := range files { if !file.IsDir() { - f.addItem(file.Name()) + f.AddItemWithText(file.Name()) // f.selItemIds = append(f.selItemIds, file.Name()) f.fileTotal++ // logrus.Debugf("%v", file.Name()) @@ -527,7 +586,7 @@ func (f *fileList) changeItem(tmpDir, projectDir string) { continue } } - f.addItem(file.Name()) + f.AddItemWithText(file.Name()) f.selItemIds = append(f.selItemIds, file.Name()) f.fileTotal++ logrus.Debugf("%v", file.Name()) diff --git a/cmd/v2/ui/tool_pb.go b/cmd/v2/ui/tool_pb.go index 20eff78a1..315ee92f1 100644 --- a/cmd/v2/ui/tool_pb.go +++ b/cmd/v2/ui/tool_pb.go @@ -15,7 +15,6 @@ import ( "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" - "fyne.io/fyne/v2/data/binding" "fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/storage" @@ -27,13 +26,11 @@ import ( type appPbGen struct { appAdapter folderList *folderList - folderChk *widget.List } func (this *appPbGen) LazyInit(obs observer.Observer) error { this.tabItem = container.NewTabItemWithIcon(common.TOOLBAR_PB, theme.ContentAddIcon(), nil) this.folderList = NewFolderList() - this.folderChk = this.folderList.createList() countLabel := widget.NewLabel("") @@ -155,12 +152,15 @@ func (this *appPbGen) LazyInit(obs observer.Observer) error { } logrus.Debug("save pb conf") } + + this.folderList.itemList = this.folderList.createList() + // layout c := container.NewBorder( form, container.NewHBox(confBtn, genBtn, layout.NewSpacer(), countLabel), nil, nil, container.NewMax( - container.NewVScroll(this.folderChk), + container.NewVScroll(this.folderList.itemList), ), ) @@ -174,46 +174,47 @@ func (a *appPbGen) GetAppName() string { } type folderList struct { - dataBinding binding.UntypedList - selItemIds []string //选择的ID - itemListData *model.ItemModelList - fileTotal int //文件总数 + selItemIds []string //选择的ID + cachedList List + itemList *widget.List + fileTotal int //文件总数 } func NewFolderList() *folderList { return &folderList{ - dataBinding: binding.NewUntypedList(), + cachedList: NewList(""), } } func (f *folderList) createList() *widget.List { - return widget.NewListWithData(f.dataBinding, - func() fyne.CanvasObject { - return container.NewHBox( - // &widget.Check{Checked: true}, - widget.NewCheck("", func(b bool) {}), - widget.NewLabelWithStyle("", fyne.TextAlignCenter, fyne.TextStyle{}), - widget.NewLabel(""), - ) + return widget.NewList( + func() int { + return len(f.cachedList.Items) }, - func(data binding.DataItem, item fyne.CanvasObject) { - o, _ := data.(binding.Untyped).Get() - pd := o.(*model.ItemModel) - item.(*fyne.Container).Objects[0].(*widget.Check).OnChanged = func(b bool) { + func() fyne.CanvasObject { + return widget.NewCheck("Template", func(b bool) {}) + }, + func(id widget.ListItemID, item fyne.CanvasObject) { + c, _ := item.(*widget.Check) + im := f.cachedList.Items[id] + c.Text = im.Text + c.Checked = im.Checked + + c.OnChanged = func(b bool) { if b { - f.selItemIds = append(f.selItemIds, pd.Id) + f.selItemIds = append(f.selItemIds, c.Text) } else { - f.selItemIds = utils.DeleteString(f.selItemIds, pd.Id) + f.selItemIds = utils.DeleteString(f.selItemIds, c.Text) } + f.cachedList.Items[id].Checked = b + f.itemList.Refresh() } - item.(*fyne.Container).Objects[1].(*widget.Label).SetText(pd.Label) + c.Refresh() }, ) } func (f *folderList) initItem(dir string) { - f.itemListData = model.NewItemModelList() - files, err := ioutil.ReadDir(dir) if err != nil { logrus.Error(err) @@ -225,23 +226,15 @@ func (f *folderList) initItem(dir string) { if file.Name() == ".vscode" { continue } - fm := &model.ItemModel{ - Id: file.Name(), - Label: file.Name(), + fm := Item{ + Text: file.Name(), + Checked: false, } - f.itemListData.DataList = append(f.itemListData.DataList, fm) + f.cachedList.Items = append(f.cachedList.Items, fm) // f.selItemIds = append(f.selItemIds, fm.Id) f.fileTotal++ // logrus.Debugf("%v", fm.Id) } } - f.reloadListData() -} - -func (f *folderList) reloadListData() { - if f.itemListData != nil { - d := f.itemListData.AsInterfaceArray() - f.dataBinding.Set(d) - } } diff --git a/cmd/v2/ui/tool_term.go b/cmd/v2/ui/tool_term.go index d6f22914a..849e829d1 100644 --- a/cmd/v2/ui/tool_term.go +++ b/cmd/v2/ui/tool_term.go @@ -1,7 +1,6 @@ package ui import ( - "errors" "fmt" "go_dreamfactory/cmd/v2/lib/common" "go_dreamfactory/cmd/v2/model" @@ -14,6 +13,8 @@ import ( "strings" "sync" + "github.com/pkg/errors" + "fyne.io/fyne/v2" "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/container" @@ -35,6 +36,8 @@ type appTerm struct { cProgress *widget.ProgressBarInfinite //连接进度条进度条 upProgress *widget.ProgressBar //上传进度条 endProgress sync.WaitGroup + + downloadList *fileList //download列表 } func (this *appTerm) LazyInit(obs observer.Observer) error { @@ -83,7 +86,7 @@ func (this *appTerm) LazyInit(obs observer.Observer) error { &widget.FormItem{Text: "用户名:", Widget: userName}, passwordItem, widget.NewFormItem("Json目录", container.NewBorder(nil, nil, nil, widget.NewButtonWithIcon("", theme.FolderIcon(), func() { - openFolder(localDir) + openFolder(localDir, toolWin.w) }), localDir)), widget.NewFormItem("远程目录", remoteDir), ) @@ -114,7 +117,7 @@ func (this *appTerm) LazyInit(obs observer.Observer) error { svnForm := widget.NewForm( &widget.FormItem{Text: "服务地址", Widget: lubanAddr}, &widget.FormItem{Text: "工作目录", Widget: container.NewBorder(nil, nil, nil, widget.NewButtonWithIcon("", theme.FolderIcon(), func() { - openFolder(workDir) + openFolder(workDir, toolWin.w) }), workDir)}, &widget.FormItem{Text: "LubanCli", Widget: lubanCli}, &widget.FormItem{Text: "Data目录", Widget: dataDir}, @@ -135,6 +138,18 @@ func (this *appTerm) LazyInit(obs observer.Observer) error { lubanCli.Text = sshConf.LubanCli dataDir.Text = sshConf.DataDir jsonDir.Text = sshConf.JsonDir + } else { + sshConf = &model.SSHModel{ + Ip: "10.0.0.9", + UserName: "root", + Password: "Legu.cc()123", + RemoteDir: "/home/liwei/go_dreamfactory/bin/json/", + + ServerIp: "10.0.1.11", + LubanCli: `Luban.Client\Luban.Client.exe`, + DataDir: "Datas", + JsonDir: `output\json`, + } } // 解决文本没显示的问题 @@ -145,20 +160,18 @@ func (this *appTerm) LazyInit(obs observer.Observer) error { // save func saveFunc := func() { - if err := service.GetDbService().SaveSSHConf(&model.SSHModel{ - Ip: ip.Text, - UserName: userName.Text, - Password: password.Text, - Port: port.Text, - LocalDir: localDir.Text, - RemoteDir: remoteDir.Text, - //// - ServerIp: lubanAddr.Text, - WorkDir: workDir.Text, - LubanCli: lubanCli.Text, - DataDir: dataDir.Text, - JsonDir: jsonDir.Text, - }); err != nil { + sshConf.Ip = ip.Text + sshConf.UserName = userName.Text + sshConf.Password = password.Text + sshConf.Port = port.Text + sshConf.LocalDir = localDir.Text + sshConf.RemoteDir = remoteDir.Text + sshConf.ServerIp = lubanAddr.Text + sshConf.WorkDir = workDir.Text + sshConf.LubanCli = lubanCli.Text + sshConf.DataDir = dataDir.Text + sshConf.JsonDir = jsonDir.Text + if err := service.GetDbService().SaveSSHConf(sshConf); err != nil { logrus.WithField("err", err).Debug("保存配置") } } @@ -172,7 +185,10 @@ func (this *appTerm) LazyInit(obs observer.Observer) error { syncBtn := &widget.Button{Text: "同步JSON", Icon: theme.ConfirmIcon()} refreshBtn := &widget.Button{Text: "刷新", Icon: theme.ViewRefreshIcon()} svnBtn := &widget.Button{Text: "SVN更新", Icon: theme.ConfirmIcon()} + excelBtn := &widget.Button{Text: "更新Excel", Icon: theme.FolderIcon()} explorBtn := &widget.Button{Text: "资源管理器", Icon: theme.FolderIcon()} + dlBtn := widget.NewButtonWithIcon("", theme.DownloadIcon(), nil) + searchEntry := widget.NewEntry() // 全选/全取消 @@ -232,6 +248,7 @@ func (this *appTerm) LazyInit(obs observer.Observer) error { allCancelBtn.Show() allSelBtn.Hide() refreshBtn.Enable() + dlBtn.Enable() } } @@ -258,20 +275,27 @@ func (this *appTerm) LazyInit(obs observer.Observer) error { allSelBtn.Hide() allCancelBtn.Show() refreshBtn.Disable() + dlBtn.Disable() }() this.sshService.Close() } //资源管理器 - explorBtn.OnTapped = func() { - logrus.Debug(localDir.Text) + openExplor := func(dir string) { if runtime.GOOS == "windows" { if err := exec.Command("explorer", filepath.Join(localDir.Text)).Start(); err != nil { - dialog.ShowError(err, toolWin.w) + dialog.ShowError(errors.WithMessage(err, "请确认Json目录是否填写正确"), toolWin.w) return } } - + } + explorBtn.OnTapped = func() { + // logrus.Debug(localDir.Text) + if localDir.Text == "" { + showTip("Json目录必须填写") + } else { + openExplor(localDir.Text) + } } //使用说明 @@ -321,18 +345,13 @@ func (this *appTerm) LazyInit(obs observer.Observer) error { //同步JSON syncBtn.Disable() - syncBtn.OnTapped = func() { - syncBtn.Disable() + syncNext := func() { defer func() { syncBtn.Enable() this.upProgress.Hide() + dialog.ShowConfirm("提示", "所有文件均上传完毕,静等1-2分钟", func(b bool) {}, toolWin.w) }() - - if this.sshService.Client == nil { - dialog.ShowError(errors.New("请先连接终端"), toolWin.w) - return - } - + syncBtn.Disable() this.upProgress.Show() this.upProgress.SetValue(0) @@ -362,17 +381,50 @@ func (this *appTerm) LazyInit(obs observer.Observer) error { this.endProgress.Wait() this.upProgress.SetValue(1) } + syncBtn.OnTapped = func() { + if this.sshService.Client == nil { + dialog.ShowError(errors.New("请先连接终端"), toolWin.w) + return + } + + //SVN更新提示 + dc := dialog.NewConfirm("提示", "是否要进行SVN更新?", func(b bool) { + if b { + showTip("单击【SVN更新】按钮进行更新") + svnBtn.FocusGained() + return + } else { + if len(this.jsonList.selItemIds) == 0 { + showTip("没有选择任何文件,或尝试点击【刷新】") + return + } + syncNext() + } + }, toolWin.w) + dc.SetConfirmText("必须的") + dc.SetDismissText("我拿生命担保无需更新") + dc.Show() + } + + // excel更新 + excelBtn.OnTapped = func() { + if workDir.Text == "" { + showTip("工作目录必须填写") + } else { + openExplor(workDir.Text) + } + } // SVN更新 - svnBtn.OnTapped = func() { - this.cProgress.Show() - this.cProgress.Start() - svnBtn.Disable() + svnNext := func() { defer func() { this.cProgress.Hide() this.cProgress.Stop() svnBtn.Enable() }() + svnBtn.Disable() + this.cProgress.Show() + this.cProgress.Start() commandStr := `%s -h %s -j cfg -- -d %s --input_data_dir %s --output_data_dir %s --gen_types data_json -s server` arg := fmt.Sprintf(commandStr, filepath.Join(workDir.Text, lubanCli.Text), @@ -389,6 +441,22 @@ func (this *appTerm) LazyInit(obs observer.Observer) error { return } } + svnBtn.OnTapped = func() { + + //提示更新excel + dc := dialog.NewConfirm("提示", "是否要更新excel?", func(b bool) { + if b { + //打开资源管理器 + openExplor(workDir.Text) + return + } else { + svnNext() + } + }, toolWin.w) + dc.SetConfirmText("必须的") + dc.SetDismissText("我拿生命担保无需更新") + dc.Show() + } // 全部取消 allCancelBtn.OnTapped = func() { @@ -403,7 +471,7 @@ func (this *appTerm) LazyInit(obs observer.Observer) error { } this.jsonList.itemList.Refresh() } - + // 全选 allSelBtn.OnTapped = func() { defer func() { @@ -421,6 +489,10 @@ func (this *appTerm) LazyInit(obs observer.Observer) error { // 搜索 searchEntry.PlaceHolder = "搜索" searchEntry.OnChanged = func(s string) { + if this.sshService.Client == nil { + dialog.ShowError(errors.New("请先连接终端"), toolWin.w) + return + } if s == "" { reloadItem() } else { @@ -437,11 +509,23 @@ func (this *appTerm) LazyInit(obs observer.Observer) error { } } - // 创建json列表 - this.jsonList.itemList = this.jsonList.createList() + // 下载日志 + dlBtn.Disable() + dlBtn.OnTapped = func() { + w := this.createDownloadWindow() + w.Show() + w.SetCloseIntercept(func() { + dlBtn.Enable() + w.Close() + }) + dlBtn.Disable() + } - btns1 := container.NewHBox(helpBtn1, &layout.Spacer{}, saveBtn1, connBtn, disConnBtn) - btns2 := container.NewHBox(helpBtn2, &layout.Spacer{}, saveBtn2, svnBtn) + // 创建json列表 + this.jsonList.itemList = this.jsonList.CreateDefaultList() + + btns1 := container.NewHBox(helpBtn1, dlBtn, &layout.Spacer{}, saveBtn1, connBtn, disConnBtn) + btns2 := container.NewHBox(helpBtn2, &layout.Spacer{}, saveBtn2, excelBtn, svnBtn) split := container.NewHSplit(container.NewVBox(canvas.NewText("---只能在非上产环境!!!同步Json的操作仅限于数值热更,非结构热更---", color.RGBA{255, 0, 0, 255}), configForm, btns1, svnForm, btns2, this.cProgress), container.NewBorder( @@ -459,6 +543,19 @@ func (a *appTerm) GetAppName() string { return common.TOOLBAR_TERM } +func OpenExplor(dir string) { + if dir == "" { + showTip("资源管理器路径错误") + return + } + if runtime.GOOS == "windows" { + if err := exec.Command("explorer", filepath.Join(dir)).Start(); err != nil { + dialog.ShowError(errors.WithMessage(err, "请确认Json目录是否填写正确"), toolWin.w) + return + } + } +} + func (a *appTerm) OnClose() bool { dialog.ShowConfirm("关闭终端", "你希望断开连接吗?", func(b bool) { if !b { @@ -470,3 +567,202 @@ func (a *appTerm) OnClose() bool { }, toolWin.w) return true } + +func (a *appTerm) createDownloadWindow() fyne.Window { + w := toolWin.app.NewWindow("日志") + + a.downloadList = NewFileList() + + a.downloadList.itemList = a.downloadList.CreateDownloadList() + + remoteLogDirEntry := widget.NewEntry() + remoteLogDirEntry.PlaceHolder = "下载到" + remoteLogDirEntry.Text = "/home/liwei/go_dreamfactory/bin/log/" + + saveDirEntry := widget.NewEntry() + saveDirEntry.PlaceHolder = "保存目录" + + editorEntry := widget.NewEntry() + editorEntry.PlaceHolder = "编辑器可执行文件路径" + + // config + confForm := widget.NewForm( + widget.NewFormItem("远程目录", remoteLogDirEntry), + widget.NewFormItem("下载目录", saveDirEntry), + widget.NewFormItem("编辑器", container.NewBorder(nil, nil, nil, widget.NewButtonWithIcon("", theme.FolderIcon(), func() { + openFile(editorEntry, w) + }), editorEntry)), + ) + + // 进度条 + downloadProgress := widget.NewProgressBarInfinite() + + var sshConf *model.SSHModel + defer func() { + downloadProgress.Hide() + //加载配置 + sshConf = service.GetDbService().GetSSHConf(common.BUCKET_SSHCONF) + if sshConf != nil { + if sshConf.LogDir == "" { + remoteLogDirEntry.Text = "/home/liwei/go_dreamfactory/bin/log/" + } else { + remoteLogDirEntry.Text = sshConf.LogDir + } + + saveDirEntry.Text = sshConf.SaveDir + editorEntry.Text = sshConf.Editor + } else { + sshConf = &model.SSHModel{} + } + + remoteLogDirEntry.Refresh() + saveDirEntry.Refresh() + editorEntry.Refresh() + + //load list + if remoteLogDirEntry.Text == "" { + return + } + files, err := a.sshService.GetRemoteDir(remoteLogDirEntry.Text) + if err != nil { + dialog.ShowError(err, toolWin.w) + return + } + + for _, f := range files { + a.downloadList.AddItem(Item{Text: f.FileName, Size: f.Size}) + } + }() + + //下载 + allDownloadBtn := widget.NewButtonWithIcon("下载", theme.DownloadIcon(), nil) + DownloadDirBtn := widget.NewButtonWithIcon("打开下载目录", theme.FolderIcon(), nil) + EditorBtn := widget.NewButtonWithIcon("打开编辑器", theme.FolderIcon(), nil) + saveConfBtn := widget.NewButtonWithIcon("保存配置", theme.DocumentSaveIcon(), func() { + // 保存配置 + sshConf.LogDir = remoteLogDirEntry.Text + sshConf.SaveDir = saveDirEntry.Text + sshConf.Editor = editorEntry.Text + if err := service.GetDbService().SaveSSHConf(sshConf); err != nil { + logrus.WithField("err", err).Debug("保存日志配置") + } + }) + + allSelBtn := &widget.Button{Icon: theme.CheckButtonCheckedIcon()} + allCancelBtn := &widget.Button{Icon: theme.CheckButtonIcon()} + allSelBtn.Hide() + + // 全部取消 + allCancelBtn.OnTapped = func() { + defer func() { + allCancelBtn.Hide() + allSelBtn.Show() + }() + dlist := a.downloadList + for i, v := range dlist.cachedList.Items { + dlist.cachedList.Items[i].Checked = true + dlist.selItemIds = append(dlist.selItemIds, v.Text) + dlist.itemList.UpdateItem(i, container.NewHBox( + widget.NewCheck(v.Text, nil), + &layout.Spacer{}, + widget.NewLabel(common.ConvertFileSize(v.Size)))) + } + dlist.itemList.Refresh() + } + + // 全选 + allSelBtn.OnTapped = func() { + defer func() { + allCancelBtn.Show() + allSelBtn.Hide() + }() + dlist := a.downloadList + dlist.selItemIds = []string{} + for i, v := range dlist.cachedList.Items { + dlist.cachedList.Items[i].Checked = false + dlist.itemList.UpdateItem(i, container.NewHBox( + widget.NewCheck(v.Text, nil), + &layout.Spacer{}, + widget.NewLabel(common.ConvertFileSize(v.Size)))) + } + dlist.itemList.Refresh() + } + + // 打开下载目录 + DownloadDirBtn.OnTapped = func() { + OpenExplor(saveDirEntry.Text) + } + + EditorBtn.OnTapped = func() { + if editorEntry.Text == "" { + showTip("请配置编辑器") + return + } + if runtime.GOOS == "windows" { + for _, v := range a.downloadList.selItemIds { + if err := exec.Command(editorEntry.Text, filepath.Join(saveDirEntry.Text, v)).Start(); err != nil { + dialog.ShowError(errors.WithMessage(err, "请确认编辑器目录是否填写正确"), toolWin.w) + return + } + } + + } + } + + //使用说明 + helpBtn := widget.NewButtonWithIcon("", theme.QuestionIcon(), func() { + quesWin := fyne.CurrentApp().NewWindow("日志使用说明") + quesWin.SetContent(widget.NewRichTextFromMarkdown( + ` + * 下载远程服务日志到本地分析 + * 提示: + * 远程目录 (保持一致) + * 下载目录:填写本地任意目录 + * 编辑器:选择已安装的编辑器(例如:notepad++、Sublime) + * 操作: + * 选择文件,点击【打开编辑器】 + `)) + quesWin.Resize(fyne.NewSize(350, 200)) + quesWin.SetFixedSize(true) + quesWin.CenterOnScreen() + quesWin.Show() + }) + + var wg sync.WaitGroup + //download + allDownloadBtn.OnTapped = func() { + downloadProgress.Show() + downloadProgress.Start() + selItems := a.downloadList.selItemIds + if len(selItems) == 0 { + showTip("请选择下载的文件") + return + } else { + for _, item := range selItems { + wg.Add(1) + go func(name string) { + defer func() { + downloadProgress.Hide() + wg.Done() + }() + logrus.WithField("filepath", remoteLogDirEntry.Text+name).Debug("下载") + if err := a.sshService.ScpDownload(saveDirEntry.Text, remoteLogDirEntry.Text+name); err != nil { + showTip(name + " 下载失败") + } + }(item) + } + wg.Wait() + } + } + + btns := container.NewHBox(allSelBtn, allCancelBtn, allDownloadBtn, DownloadDirBtn, EditorBtn, helpBtn) + //toolbar + toolbar := container.NewBorder(nil, btns, nil, saveConfBtn, confForm) + + //layout + w.SetContent(container.NewBorder(toolbar, downloadProgress, nil, nil, a.downloadList.itemList)) + + w.Resize(fyne.NewSize(800, 450)) + w.CenterOnScreen() + return w +} diff --git a/cmd/v2/ui/toolwindow.go b/cmd/v2/ui/toolwindow.go index 08b9e9c6e..cdae1cb20 100644 --- a/cmd/v2/ui/toolwindow.go +++ b/cmd/v2/ui/toolwindow.go @@ -1,6 +1,7 @@ package ui import ( + "fmt" "go_dreamfactory/cmd/v2/lib/common" "fyne.io/fyne/v2" @@ -64,7 +65,7 @@ func NewToolWindow(ui *UIImpl, parent fyne.Window) ToolWindow { } func (ui *ToolWindowImpl) CreateWindow(title string, width, height float32, _ bool) { - w := ui.app.NewWindow(title) + w := ui.app.NewWindow(fmt.Sprintf("工具箱 %s %s", title, ui.app.Metadata().Version)) ui.AddWindow("tool", w) ui.w = w diff --git a/cmd/v2/ui/toy_userinfo.go b/cmd/v2/ui/toy_userinfo.go index 70ca8232b..ce82de21f 100644 --- a/cmd/v2/ui/toy_userinfo.go +++ b/cmd/v2/ui/toy_userinfo.go @@ -115,7 +115,10 @@ func (this *toyUserInfo) dataListener() { _ = this.data.Append(this.getActiveWeek()) //11 _ = this.data.Append(this.getFriendPoint()) //12 _ = this.data.Append(this.getModiNameCount()) //13 - _ = this.data.Append(this.getSign()) //14 + _ = this.data.Append(this.getCreateTime()) + _ = this.data.Append(this.getSign()) + + this.setProp(2, common.USERINFO_NAME, this.userInfo.DbUser.Name) }, }) @@ -155,7 +158,6 @@ func (this *toyUserInfo) dataListener() { this.setProp(11, common.USERINFO_ACTIVE_WEEK, rsp.Ex.Activeweek) this.setProp(12, common.USERINFO_FRIENDPOINT, rsp.Ex.FriendPoint) this.setProp(13, common.USERINFO_MODINAME, rsp.Ex.ModifynameCount) - this.setProp(14, common.USERINFO_SIGN, rsp.Ex.Sign) } }, }) @@ -171,9 +173,9 @@ func (this *toyUserInfo) dataListener() { logrus.Error("unmarshal err") return } - this.setProp(6, common.USERINFO_GOLD, rsp.Gold) - this.setProp(7, common.USERINFO_EXP, rsp.Exp) - this.setProp(8, common.USERINFO_DIAMOND, rsp.Diamond) + // this.setProp(6, common.USERINFO_GOLD, rsp.Gold) + // this.setProp(7, common.USERINFO_EXP, rsp.Exp) + // this.setProp(8, common.USERINFO_DIAMOND, rsp.Diamond) } // listener exp // if data.Msg.MainType == string(comm.ModuleUser) && @@ -186,9 +188,13 @@ func (this *toyUserInfo) dataListener() { } func (this *toyUserInfo) setProp(idx int, label string, val interface{}) { + // v, _ := this.data.GetValue(idx) + // if v != "" { + logrus.WithFields(logrus.Fields{"idx": idx, "lbl": label, "val": val}).Debug("更新Prop") if err := this.data.SetValue(idx, fmt.Sprintf("%-3s\t: %v", label, val)); err != nil { logrus.WithFields(logrus.Fields{"idx": idx, "val": val}).Error(err) } + // } } func (this *toyUserInfo) getAcc() string { @@ -265,3 +271,13 @@ func (this *toyUserInfo) getSign() string { } return fmt.Sprintf("%-3s\t: %s", common.USERINFO_SIGN, cast.ToString(this.userInfo.DbUserExpand.Sign)) } + +func (this *toyUserInfo) getCreateTime() string { + ctime := this.userInfo.DbUser.Ctime + if ctime <= 0 { + return "" + } + tt := time.Unix(ctime, 0) + + return fmt.Sprintf("%-3s\t: %s", common.USERINFO_CREATETM, tt.Format(time.RFC3339)) +}