diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..a1f66a2 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,147 @@ +name: CI Create Release + +on: + push: + tags: + - 'v*' + +jobs: + + frontend-build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up Node + uses: actions/setup-node@v3 + with: + node-version: 16 + + - name: Setup pnpm + run: npm install -g pnpm + + - name: Build + run: | + cd ui && pnpm i && pnpm run build + + - name: Upload frontend build + uses: actions/upload-artifact@v3 + with: + name: frontend-artifact + path: ./ui/dist + + + server-build: + runs-on: ubuntu-latest + needs: frontend-build + steps: + - uses: actions/checkout@v3 + + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: 1.19 + cache: true + + - name: Setup GF + run: wget -O gf https://github.com/gogf/gf/releases/latest/download/gf_$(go env GOOS)_$(go env GOARCH) && chmod +x gf && ./gf install -y && rm ./gf + + - name: Download frontend build + uses: actions/download-artifact@v3 + with: + name: frontend-artifact + path: ./ui/dist + + - name: Build + run: | + gf pack ./ui/dist ./cmd/server/packed/dist.go + ls ./cmd/server/packed + ls ./ui/dist + go run build.go + cat ./temp/changelog.md + mv ./temp/changelog.md ./build/changelog.md + + + - name: Upload server build + uses: actions/upload-artifact@v3 + with: + name: server-artifact + path: | + ./build/* + + desktop-build: + strategy: + matrix: + go-version: [ 1.19 ] + platform: [windows-latest] +# platform: [ macos-latest, windows-latest, ubuntu-latest ] + runs-on: ${{matrix.platform}} + needs: frontend-build + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Download frontend build + uses: actions/download-artifact@v3 + with: + name: frontend-artifact + path: ./cmd/desktop/dist + + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: ${{matrix.go-version}} + cache: true + + - run: | + go install github.com/wailsapp/wails/v2/cmd/wails@latest + + - name: Build package windows + if: matrix.platform == 'windows-latest' + run: | + cd ./cmd/desktop + # wails doctor + wails build -s -ldflags '-s -w' + dir + cd ../../ + mkdir ./build + go run build.go zip ./cmd/desktop/build/bin/RedisMan.exe ./build + dir + + - name: Upload desktop build + uses: actions/upload-artifact@v3 + with: + name: desktop-artifact + path: | + ./build/* + + release: + runs-on: ubuntu-latest + needs: [server-build, desktop-build] + steps: + - uses: actions/checkout@v3 + + - name: Download server build + uses: actions/download-artifact@v3 + with: + name: server-artifact + path: ./build + + - name: Download desktop build + uses: actions/download-artifact@v3 + with: + name: desktop-artifact + path: ./build + + - name: Create Release + uses: softprops/action-gh-release@v1 + with: + token: ${{secrets.TOKEN}} + tag_name: ${{ github.ref }} + name: Release ${{ github.ref_name }} + body_path: ./build/changelog.md + draft: false + prerelease: false + files: | + ./build/* diff --git a/.gitignore b/.gitignore index 12f0290..e079aa7 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ cmd/desktop/frontend cmd/desktop/build/bin *.exe log +build/ \ No newline at end of file diff --git a/README.md b/README.md index ba77235..7a70903 100644 --- a/README.md +++ b/README.md @@ -43,8 +43,13 @@ ## log -- 0.5.0 (?) +- 0.6.0 (?) - support lua + - support ssh-agent + +- 0.5.0 (?) + - support update check + - support github action - 0.4.0 (2023-02-21) - support terminal diff --git a/README.zh-CN.md b/README.zh-CN.md index 4ee41a9..f20f5f8 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -42,8 +42,13 @@ ## 版本日志 -- 0.5.0 (?) +- 0.6.0 (?) - support lua + - support ssh-agent + +- 0.5.0 (?) + - support update check + - support github action - 0.4.0 (2023-02-21) - support terminal diff --git a/build.go b/build.go new file mode 100644 index 0000000..4c7d456 --- /dev/null +++ b/build.go @@ -0,0 +1,230 @@ +package main + +import ( + "archive/tar" + "compress/gzip" + "fmt" + "github.com/gogf/gf/v2/encoding/gcompress" + "github.com/gogf/gf/v2/encoding/gjson" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/os/gproc" + "io" + "log" + "os" + "path/filepath" + "runtime" + "strings" +) + +var configTemp = ` +gfcli: + build: + name: "redisman" + arch: "arm64,amd64" + system: "windows,linux,darwin" + output: "./temp" + extra: "-trimpath" + varMap: + version: {{version}} +` + +var version = "" + +var buildDir = "./build" + +func main() { + + if len(os.Args) > 1 { + if os.Args[1] == "zip" { + gcompress.ZipPath(os.Args[2], filepath.Join(os.Args[3], fmt.Sprintf("redisman_desktop_%s_%s.zip", runtime.GOOS, runtime.GOARCH))) + return + } + } + + buildDir, _ = filepath.Abs(buildDir) + + gfile.Mkdir(buildDir) + + out, err := gproc.ShellExec(nil, "git describe --abbrev=0 --tags") + if err != nil { + panic(err) + } + version = strings.TrimSpace(out) + log.Println("build redisman server :" + version) + server() + log.Println("build redisman desktop :" + version) + //desktop() + + gfile.PutContents(filepath.Join(buildDir, "release_info.json"), gjson.New(g.Map{ + "Version": version, + }).MustToJsonString()) + + repository := os.Getenv("GITHUB_REPOSITORY") + + url := "https://api.github.com/repos/" + repository + "/commits/" + version + g.Log().Info(nil, url) + + res, err := g.Client().Get(nil, url) + handleError(err) + + msg := gjson.New(res.ReadAllString()).Get("commit.message").String() + if msg == "" { + msg = "can't get message" + } + + gfile.PutContents(filepath.Join("", "./temp/changelog.md"), msg) +} + +func server() { + os.Chdir("./cmd/server") + configTemp = strings.ReplaceAll(configTemp, "{{version}}", version) + gfile.PutContents("./config.yaml", configTemp) + out, err := gproc.ShellExec(nil, "gf build") + if err != nil { + panic(err) + } + fmt.Println(out) + + list, err := gfile.Glob("./temp/*/*") + if err != nil { + panic(err) + } + + for _, item := range list { + newPath := filepath.Join(buildDir, "redisman_server_"+filepath.Base(filepath.Dir(item))) + if strings.Contains(item, "linux") { + newPath += ".tar.gz" + TarGz(filepath.Join("", item), newPath) + } else { + newPath += ".zip" + gcompress.ZipPath(filepath.Join("", item), newPath) + } + + } + + os.Chdir("../../") +} + +func desktop() { + os.Chdir("./cmd/desktop") + out, err := gproc.ShellExec(nil, "wails build -trimpath ") + if err != nil { + panic(err) + } + fmt.Println(out) + + list, err := gfile.Glob("./build/bin/*") + if err != nil { + panic(err) + } + + for _, item := range list { + gcompress.ZipPath(item, filepath.Join(buildDir, fmt.Sprintf("redisman_desktop_%s_%s.zip", runtime.GOOS, runtime.GOARCH))) + + } + + os.Chdir("../../") +} +func handleError(err error) { + if err != nil { + log.Fatalln(err) + } +} + +func TarGz(srcDirPath string, destFilePath string) { + fw, err := os.Create(destFilePath) + handleError(err) + defer fw.Close() + + // Gzip writer + gw := gzip.NewWriter(fw) + defer gw.Close() + + // Tar writer + tw := tar.NewWriter(gw) + defer tw.Close() + + // Check if it's a file or a directory + f, err := os.Open(srcDirPath) + handleError(err) + fi, err := f.Stat() + handleError(err) + if fi.IsDir() { + // handle source directory + fmt.Println("Cerating tar.gz from directory...") + tarGzDir(srcDirPath, srcDirPath, tw) + } else { + // handle file directly + fmt.Println("Cerating tar.gz from " + fi.Name() + "...") + tarGzFile(srcDirPath, fi.Name(), tw, fi) + } + fmt.Println("Well done!") +} + +func tarGzDir(srcDirPath string, recPath string, tw *tar.Writer) { + // Open source diretory + dir, err := os.Open(srcDirPath) + handleError(err) + defer dir.Close() + + // Get file info slice + fis, err := dir.Readdir(0) + handleError(err) + for _, fi := range fis { + // Append path + curPath := srcDirPath + "/" + fi.Name() + // Check it is directory or file + if fi.IsDir() { + // Directory + // (Directory won't add unitl all subfiles are added) + fmt.Printf("Adding path...%s\\n", curPath) + tarGzDir(curPath, recPath+"/"+fi.Name(), tw) + } else { + // File + fmt.Printf("Adding file...%s\\n", curPath) + } + + tarGzFile(curPath, recPath+"/"+fi.Name(), tw, fi) + } +} + +// Deal with files +func tarGzFile(srcFile string, recPath string, tw *tar.Writer, fi os.FileInfo) { + if fi.IsDir() { + // Create tar header + hdr := new(tar.Header) + // if last character of header name is '/' it also can be directory + // but if you don't set Typeflag, error will occur when you untargz + hdr.Name = recPath + "/" + hdr.Typeflag = tar.TypeDir + hdr.Size = 0 + //hdr.Mode = 0755 | c_ISDIR + hdr.Mode = int64(fi.Mode()) + hdr.ModTime = fi.ModTime() + + // Write hander + err := tw.WriteHeader(hdr) + handleError(err) + } else { + // File reader + fr, err := os.Open(srcFile) + handleError(err) + defer fr.Close() + + // Create tar header + hdr := new(tar.Header) + hdr.Name = filepath.Base(recPath) + hdr.Size = fi.Size() + hdr.Mode = int64(fi.Mode()) + hdr.ModTime = fi.ModTime() + + // Write hander + err = tw.WriteHeader(hdr) + handleError(err) + + // Write file data + _, err = io.Copy(tw, fr) + handleError(err) + } +} diff --git a/cmd/desktop/main.go b/cmd/desktop/main.go index 6cd162d..2d9cd30 100644 --- a/cmd/desktop/main.go +++ b/cmd/desktop/main.go @@ -2,6 +2,7 @@ package main import ( "embed" + "github.com/glennliao/redisman/server/version" "github.com/wailsapp/wails/v2" "github.com/wailsapp/wails/v2/pkg/options" "github.com/wailsapp/wails/v2/pkg/options/assetserver" @@ -12,6 +13,8 @@ var assets embed.FS func main() { + version.VersionAction() + app := NewApp() err := wails.Run(&options.App{ diff --git a/cmd/server/main.go b/cmd/server/main.go index 9aa4716..ce0a04c 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -4,9 +4,10 @@ import ( "context" "github.com/glennliao/apijson-go/config" "github.com/glennliao/apijson-go/framework/handler" - _ "github.com/glennliao/redisman/packed" + _ "github.com/glennliao/redisman/cmd/server/packed" "github.com/glennliao/redisman/server" "github.com/glennliao/redisman/server/api" + "github.com/glennliao/redisman/server/version" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/frame/gins" "github.com/gogf/gf/v2/net/ghttp" @@ -15,6 +16,9 @@ import ( ) func main() { + + version.VersionAction() + server.Init() config.AccessVerify = false diff --git a/packed/.gitkeep b/cmd/server/packed/.gitkepp similarity index 100% rename from packed/.gitkeep rename to cmd/server/packed/.gitkepp diff --git a/cmd/server/packed/data.go b/cmd/server/packed/data.go new file mode 100644 index 0000000..f1a6633 --- /dev/null +++ b/cmd/server/packed/data.go @@ -0,0 +1,3 @@ +package packed + +// just keep a go file diff --git a/server/api/action/check_version.go b/server/api/action/check_version.go new file mode 100644 index 0000000..ef68f93 --- /dev/null +++ b/server/api/action/check_version.go @@ -0,0 +1,73 @@ +package action + +import ( + "context" + "github.com/glennliao/redisman/server/api/ws" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/gclient" + "github.com/gogf/gf/v2/util/gconv" +) + +func init() { + ws.RegAction("check-version", checkVersion) +} + +type ReleaseInfo struct { + Version string + Remark map[string]string +} + +const CountryCN = "CN" +const LatestReleasePrefix = "https://github.com/glennliao/redisman/releases/latest/download/" +const GithubProxy = "https://ghproxy.com/" + +const ReleaseInfoJson = "release_info.json" + +func checkVersion(ctx context.Context, req *ws.Req, reply func(ctx context.Context, ret any, err error)) { + country, err := getCountry(ctx) + if err != nil { + reply(ctx, nil, err) + return + } + + urlPrefix := LatestReleasePrefix + if country == CountryCN { // proxy for china + urlPrefix = GithubProxy + urlPrefix + } + + info, err := checkLatestVersion(ctx, urlPrefix) + if err != nil { + reply(ctx, nil, err) + return + } + + reply(ctx, g.Map{"latest": info}, nil) + +} + +func checkLatestVersion(ctx context.Context, urlPrefix string) (info *ReleaseInfo, err error) { + resp, err := gclient.New().Get(ctx, urlPrefix+ReleaseInfoJson) + if err != nil { + return nil, err + } + + err = gconv.Scan(resp.ReadAllString(), &info) + if err != nil { + return nil, err + } + + return info, err +} + +func getCountry(ctx context.Context) (country string, err error) { + res, err := gclient.New().Get(ctx, "https://ipinfo.io/json") + if err != nil { + return "", err + } + type IpInfo struct { + Country string + } + var info IpInfo + err = gconv.Scan(res.ReadAllString(), &info) + return info.Country, err +} diff --git a/server/api/action/relaunch.go b/server/api/action/relaunch.go new file mode 100644 index 0000000..35e39a7 --- /dev/null +++ b/server/api/action/relaunch.go @@ -0,0 +1,25 @@ +package action + +// +//func init() { +// ws.RegAction("relaunch", relaunch) +//} +// +//const UpgradeTempWaitDelFile = "_tmp_wait_del" +// +//func relaunch(ctx context.Context, req *ws.Req, reply func(ctx context.Context, ret any, err error)) { +// err := gfile.Rename(os.Args[0], UpgradeTempWaitDelFile) +// if err != nil { +// reply(ctx, nil, err) +// return +// } +// +// err = gfile.Remove(os.Args[0]) +// if err != nil { +// reply(ctx, nil, err) +// return +// } +// +// cmd := exec.Command(os.Args[0]) +// cmd.Run() +//} diff --git a/server/api/action/upgrade.go b/server/api/action/upgrade.go new file mode 100644 index 0000000..01a6098 --- /dev/null +++ b/server/api/action/upgrade.go @@ -0,0 +1,95 @@ +package action + +// +//func init() { +// ws.RegAction("upgrade", upgrade) +//} +// +//type ReleaseInfo struct { +// Version string +// Remark map[string]string +//} +// +//const CountryCN = "CN" +//const LatestReleasePrefix = "https://github.com/glennliao/redisman/releases/latest/download/" +//const GithubProxy = "https://ghproxy.com/" +// +//const ReleaseInfoJson = "release_info.json" +// +//func upgrade(ctx context.Context, req *ws.Req, reply func(ctx context.Context, ret any, err error)) { +// country, err := getCountry(ctx) +// if err != nil { +// reply(ctx, nil, err) +// return +// } +// +// urlPrefix := LatestReleasePrefix +// if country == CountryCN { // proxy for china +// urlPrefix = GithubProxy + urlPrefix +// } +// +// info, err := checkLatestVersion(ctx, urlPrefix) +// if err != nil { +// reply(ctx, nil, err) +// return +// } +// +// const version = "0.3.0" +// if info.Version > version { +// reply(ctx, g.Map{"canUpgrade": false, "version": info.Version, "remark": info.Remark}, nil) +// return +// } +// +// err = downloadNewVersion(ctx, urlPrefix) +// if err != nil { +// reply(ctx, nil, err) +// return +// } +// +// reply(ctx, g.Map{"hadDownloadNewVersion": true}, nil) +// +//} +// +//func checkLatestVersion(ctx context.Context, urlPrefix string) (info *ReleaseInfo, err error) { +// resp, err := gclient.New().Get(ctx, urlPrefix+ReleaseInfoJson) +// if err != nil { +// return nil, err +// } +// +// err = gconv.Scan(resp, &info) +// if err != nil { +// return nil, err +// } +// +// return info, err +//} +// +//func downloadNewVersion(ctx context.Context, urlPrefix string) error { +// +// name := "RedisMan.exe.zip" +// +// resp, err := gclient.New().Get(ctx, urlPrefix+name) +// if err != nil { +// return err +// } +// +// err = gcompress.UnZipContent(resp.ReadAll(), "./upgrade") +// if err != nil { +// return err +// } +// +// return nil +//} +// +//func getCountry(ctx context.Context) (country string, err error) { +// res, err := gclient.New().Get(ctx, "https://ipinfo.io/json") +// if err != nil { +// return "", err +// } +// type IpInfo struct { +// Country string +// } +// var info IpInfo +// err = gconv.Scan(res.ReadAllString(), &info) +// return info.Country, err +//} diff --git a/server/inits.go b/server/inits.go index ab58d27..2734b86 100644 --- a/server/inits.go +++ b/server/inits.go @@ -22,6 +22,11 @@ func Init() { InitConfig() initDb() framework.Init() + + //if gfile.Exists(action.UpgradeTempWaitDelFile) { + // gfile.Remove(action.UpgradeTempWaitDelFile) + //} + } func InitConfig() { diff --git a/server/version/version.go b/server/version/version.go new file mode 100644 index 0000000..6f105c7 --- /dev/null +++ b/server/version/version.go @@ -0,0 +1,17 @@ +package version + +import ( + "fmt" + "github.com/gogf/gf/v2/os/gbuild" + "os" +) + +func VersionAction() { + if len(os.Args) == 2 && os.Args[1] == "-v" { + fmt.Println(gbuild.Get("version")) + fmt.Println(gbuild.Info()) + fmt.Println(gbuild.Data()) + panic(1) + os.Exit(0) + } +} diff --git a/ui/build.js b/ui/build.js index e956360..1a88da4 100644 --- a/ui/build.js +++ b/ui/build.js @@ -1,8 +1,7 @@ -const fs = require("fs") +const fs = require("fs"); const fsExtra = require("fs-extra"); -const desktopDist = "../cmd/desktop/dist" +const desktopDist = "../cmd/desktop/dist"; +fs.rmSync(desktopDist, { force: true, recursive: true }); +fsExtra.copy("./dist", desktopDist); -fs.rmSync(desktopDist,{force:true,recursive:true}) - -fsExtra.copy("./dist",desktopDist) diff --git a/ui/src/api/index.ts b/ui/src/api/index.ts index 489d1a7..7f3aca4 100644 --- a/ui/src/api/index.ts +++ b/ui/src/api/index.ts @@ -21,3 +21,8 @@ export const apiJson = { put: (p: Record) => http("/put", "POST", p), delete: (p: Record) => http("/delete", "POST", p), }; + + +export const checkVersion = ()=>{ + return action({action:"check-version"}) +} diff --git a/ui/src/components/editor/codemirror/index.vue b/ui/src/components/editor/codemirror/index.vue index 63e4db9..791bfb8 100644 --- a/ui/src/components/editor/codemirror/index.vue +++ b/ui/src/components/editor/codemirror/index.vue @@ -32,7 +32,8 @@ export default defineComponent({ props:{ value:{ - } + }, + contentMode:{} }, setup(props,{emit}) { const code = ref(``) @@ -50,6 +51,8 @@ export default defineComponent({ + + // Codemirror EditorView instance ref const view = shallowRef() const handleReady = (payload) => { @@ -78,6 +81,9 @@ export default defineComponent({ value:item,label:item } }) + watch(()=>props.contentMode, (val)=>{ + viewMode.value = val + }) watch(()=>viewMode.value,()=>{ viewModeChangeLoading.value = true diff --git a/ui/src/views/redis/components/VersionInfoModal.vue b/ui/src/views/redis/components/VersionInfoModal.vue new file mode 100644 index 0000000..20245f1 --- /dev/null +++ b/ui/src/views/redis/components/VersionInfoModal.vue @@ -0,0 +1,81 @@ + + + + + diff --git a/ui/src/views/redis/conn.vue b/ui/src/views/redis/conn.vue index 3ccc556..9a0bc8c 100644 --- a/ui/src/views/redis/conn.vue +++ b/ui/src/views/redis/conn.vue @@ -130,15 +130,20 @@ async function handleSelect(e: string) { } else { let k = contextMenuSelectedKey.value - k = k.substring(4) + + console.log(k) + if(k.startsWith("pid_e:")){ + k = k.substring(4) + } if (contextMenuSelectedKeyIsLeaf.value) { delKeys = [k]; } else { delKeys = await scanKeys(`${k}*`); - } } + console.log(delKeys) + if (delKeys.length) { del({ key: delKeys, diff --git a/ui/src/views/redis/hook/conn.ts b/ui/src/views/redis/hook/conn.ts index fe9fbda..fcf2cb1 100644 --- a/ui/src/views/redis/hook/conn.ts +++ b/ui/src/views/redis/hook/conn.ts @@ -95,7 +95,7 @@ function connect(id: number) { } statusTimer = setInterval(()=>{ status() - }, 5* 1000) + }, 30 * 1000) }).catch((err)=>{ if(statusTimer !== null){ clearInterval(statusTimer) diff --git a/ui/src/views/redis/hook/keys.ts b/ui/src/views/redis/hook/keys.ts index 122b9ae..1f46f32 100644 --- a/ui/src/views/redis/hook/keys.ts +++ b/ui/src/views/redis/hook/keys.ts @@ -41,6 +41,10 @@ async function scan(pattern = "*") { function scanKeys(pattern = "*",cur:string): Promise { return _scan(pattern,"0") + .then((data) => { + // @ts-ignore + return data[0][1].sort(); + }); } export function useKeysHook() { diff --git a/ui/src/views/redis/index.vue b/ui/src/views/redis/index.vue index 6d7bac2..6070c89 100644 --- a/ui/src/views/redis/index.vue +++ b/ui/src/views/redis/index.vue @@ -3,6 +3,7 @@ import {AddOutline, TrashOutline,CreateOutline} from "@vicons/ionicons5"; import {apiJson} from "~/api"; import {useConn} from "~/views/redis/hook/conn"; import AddConnectionModal from "~/views/redis/components/AddConnectionModal.vue"; +import VersionInfoModal from "~/views/redis/components/VersionInfoModal.vue"; const {connect} = useConn(); @@ -67,6 +68,8 @@ function delConn(id: number) { function updateConn(id: number) { addConnectionModalRef.value && addConnectionModalRef.value.open({id}); } + +