Repository: Bronya0/ES-King Branch: wails Commit: d53ffad3f0dc Files: 44 Total size: 233.7 KB Directory structure: gitextract_q841ds1i/ ├── .github/ │ └── workflows/ │ └── build.yaml ├── .gitignore ├── LICENSE ├── app/ │ ├── app.go │ ├── backend/ │ │ ├── common/ │ │ │ └── vars.go │ │ ├── config/ │ │ │ └── app.go │ │ ├── service/ │ │ │ └── es.go │ │ ├── system/ │ │ │ └── update.go │ │ └── types/ │ │ └── resp.go │ ├── build/ │ │ ├── README.md │ │ ├── darwin/ │ │ │ ├── Info.dev.plist │ │ │ └── Info.plist │ │ └── windows/ │ │ ├── info.json │ │ ├── installer/ │ │ │ ├── project.nsi │ │ │ └── wails_tools.nsh │ │ └── wails.exe.manifest │ ├── dev.bat │ ├── frontend/ │ │ ├── index.html │ │ ├── package.json │ │ ├── src/ │ │ │ ├── App.vue │ │ │ ├── assets/ │ │ │ │ └── fonts/ │ │ │ │ └── OFL.txt │ │ │ ├── components/ │ │ │ │ ├── About.vue │ │ │ │ ├── Aside.vue │ │ │ │ ├── Conn.vue │ │ │ │ ├── Core.vue │ │ │ │ ├── Header.vue │ │ │ │ ├── Health.vue │ │ │ │ ├── Index.vue │ │ │ │ ├── Nodes.vue │ │ │ │ ├── Rest.vue │ │ │ │ ├── Settings.vue │ │ │ │ ├── Snapshot.vue │ │ │ │ └── Task.vue │ │ │ ├── main.js │ │ │ ├── style.css │ │ │ └── utils/ │ │ │ ├── common.js │ │ │ └── eventBus.js │ │ └── vite.config.js │ ├── go.mod │ ├── go.sum │ ├── main.go │ └── wails.json ├── readme-en.md └── readme.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/build.yaml ================================================ name: Wails build on: release: types: [ created ] env: NODE_OPTIONS: "--max-old-space-size=4096" APP_NAME: 'ES-King' APP_WORKING_DIRECTORY: 'app' GO_VERSION: '1.24' NODE_VERSION: "22.x" jobs: build-windows: runs-on: ${{ matrix.os }} strategy: matrix: os: [windows-latest] # amd64/x64 steps: - name: Checkout uses: actions/checkout@v2 with: submodules: recursive - name: Install 7-Zip run: choco install 7zip - name: GoLang uses: actions/setup-go@v4 with: check-latest: true go-version: ${{ env.GO_VERSION }} - name: NodeJS uses: actions/setup-node@v3 with: node-version: ${{ env.NODE_VERSION }} - name: Build & Compress run: | go install github.com/wailsapp/wails/v2/cmd/wails@latest cd ${{ env.APP_WORKING_DIRECTORY }} wails build -ldflags="-X 'app/backend/common.Version=${{ github.ref_name }}'" -webview2 download -o ${{ env.APP_NAME }}.exe cd .. copy readme.md app\build\bin\ copy LICENSE app\build\bin\ & "C:\Program Files\7-Zip\7z.exe" a -t7z "${{ env.APP_NAME }}-${{ github.ref_name }}-windows-x64.7z" ".\app\build\bin\*" -r - name: Upload Release Asset uses: softprops/action-gh-release@v1 with: files: "*.7z" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} build-macos: runs-on: ${{ matrix.os }} strategy: matrix: os: [macos-latest] # macos-13是amd64,macos-latest是m1芯片 steps: - name: Checkout uses: actions/checkout@v2 with: submodules: recursive - name: GoLang uses: actions/setup-go@v4 with: check-latest: true go-version: ${{ env.GO_VERSION }} - name: NodeJS uses: actions/setup-node@v3 with: node-version: ${{ env.NODE_VERSION }} - name: Build & Compress shell: bash run: | go install github.com/wailsapp/wails/v2/cmd/wails@latest cd ${{ env.APP_WORKING_DIRECTORY }} wails build -ldflags "-X 'app/backend/common.Version=${{ github.ref_name }}'" -platform darwin/universal -o ${{ env.APP_NAME }} chmod +x build/bin/*/Contents/MacOS/* mkdir -p _build/ _dist/ cp -r ./build/bin/${{ env.APP_NAME }}.app _build/ brew install create-dmg create-dmg \ --no-internet-enable \ --volname "${{ env.APP_NAME }}" \ --volicon "_build/${{ env.APP_NAME }}.app/Contents/Resources/iconfile.icns" \ --window-pos 400 400 \ --window-size 660 450 \ --icon "${{ env.APP_NAME }}.app" 180 180 \ --icon-size 100 \ --hide-extension "${{ env.APP_NAME }}.app" \ --app-drop-link 480 180 \ "_dist/${{ env.APP_NAME }}-${{ github.ref_name }}-macos.dmg" \ "_build/" - name: Upload Release Assets uses: softprops/action-gh-release@v1 with: files: ${{ env.APP_WORKING_DIRECTORY }}/_dist/* fail_on_unmatched_files: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} build-linux: runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-22.04] steps: - name: Checkout uses: actions/checkout@v2 with: submodules: recursive - name: GoLang uses: actions/setup-go@v4 with: check-latest: true go-version: ${{ env.GO_VERSION }} - name: NodeJS uses: actions/setup-node@v3 with: node-version: ${{ env.NODE_VERSION }} - name: Build & Compress run: | ARCH=$(uname -m) sudo apt-get update && sudo apt-get install libgtk-3-dev libwebkit2gtk-4.0-dev build-essential go install github.com/wailsapp/wails/v2/cmd/wails@latest cd ${{ env.APP_WORKING_DIRECTORY }} wails build -ldflags="-X 'app/backend/common.Version=${{ github.ref_name }}'" -webview2 download -o ${{ env.APP_NAME }} cd .. mkdir _temp_dist cp readme.md _temp_dist/ cp LICENSE _temp_dist/ cp -r ${{ env.APP_WORKING_DIRECTORY }}/build/bin/* _temp_dist/ chmod +x _temp_dist/* cd _temp_dist/ tar -zcvf ${{ env.APP_NAME }}-${{ github.ref_name }}-ubuntu-$ARCH.tar.gz * mv ${{ env.APP_NAME }}-${{ github.ref_name }}-ubuntu-$ARCH.tar.gz .. - name: Upload Release Assets uses: softprops/action-gh-release@v1 with: files: "*.tar.gz" fail_on_unmatched_files: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .gitignore ================================================ build/bin .vscode .idea /app/frontend/node_modules /app/frontend/wailsjs /app/frontend/dist /app/frontend/package.json.md5 ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: app/app.go ================================================ /* * Copyright 2025 Bronya0 . * Author Github: https://github.com/Bronya0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package main import ( "app/backend/common" "context" "crypto/tls" "fmt" "github.com/go-resty/resty/v2" "log" "runtime" "runtime/debug" ) // App struct type App struct { ctx context.Context } // NewApp creates a new App application struct func NewApp() *App { return &App{} } // Start is called at application startup func (a *App) Start(ctx context.Context) { a.ctx = ctx log.Println("===注意,接下来前端执行onMounted,后端初始化必须在此处完成===") } // domReady is called after front-end resources have been loaded func (a *App) domReady(ctx context.Context) { log.Println("===最后一步,页面即将显示……可以执行后端异步任务===") // 统计版本使用情况 go func() { defer func() { if err := recover(); err != nil { fmt.Println(string(debug.Stack())) } }() client := resty.New().SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}) body := map[string]any{ "name": common.AppName, "version": common.Version, "platform": runtime.GOOS, } _, _ = client.R().SetBody(body).Post(common.PingUrl) }() } // beforeClose is called when the application is about to quit, // either by clicking the window close button or calling runtime.Quit. // Returning true will cause the application to continue, false will continue shutdown as normal. func (a *App) beforeClose(ctx context.Context) (prevent bool) { return false } // shutdown is called at application termination func (a *App) shutdown(ctx context.Context) { // Perform your teardown here } // Greet returns a greeting for the given name func (a *App) Greet(name string) string { return fmt.Sprintf("Hello %s, It's show time!", name) } ================================================ FILE: app/backend/common/vars.go ================================================ /* * Copyright 2025 Bronya0 . * Author Github: https://github.com/Bronya0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package common import "fmt" var ( // Version 会在编译时注入 -ldflags="-X 'app/backend/common.Version=${{ github.event.release.tag_name }}'" Version = "" ) const ( AppName = "ES-King" Width = 1600 Height = 870 Theme = "dark" ConfigDir = ".es-king" ConfigPath = "config.yaml" HistoryPath = "history.yaml" ErrLogPath = "error.log" Language = "zh-CN" PingUrl = "https://ysboke.cn/api/kingTool/ping" ) var ( Project = "Bronya0/ES-King" GITHUB_URL = fmt.Sprintf("https://github.com/%s", Project) GITHUB_REPOS_URL = fmt.Sprintf("https://api.github.com/repos/%s", Project) UPDATE_URL = fmt.Sprintf("https://api.github.com/repos/%s/releases/latest", Project) ISSUES_URL = fmt.Sprintf("https://github.com/%s/issues", Project) ISSUES_API_URL = fmt.Sprintf("https://api.github.com/repos/%s/issues?state=open", Project) ) ================================================ FILE: app/backend/config/app.go ================================================ /* * Copyright 2025 Bronya0 . * Author Github: https://github.com/Bronya0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package config import ( "app/backend/common" "app/backend/types" "context" "fmt" "github.com/wailsapp/wails/v2/pkg/runtime" "gopkg.in/yaml.v3" "log" "os" "path/filepath" "sync" ) type AppConfig struct { ctx context.Context mu sync.Mutex } func (a *AppConfig) Start(ctx context.Context) { a.ctx = ctx } func (a *AppConfig) GetConfig() *types.Config { var defaultConfig = &types.Config{ Width: common.Width, Height: common.Height, Language: common.Language, Theme: common.Theme, Connects: make([]types.Connect, 0), } configPath := a.getConfigPath() data, err := os.ReadFile(configPath) if err != nil { return defaultConfig } err = yaml.Unmarshal(data, defaultConfig) if err != nil { return defaultConfig } return defaultConfig } func (a *AppConfig) SaveConfig(config *types.Config) string { a.mu.Lock() defer a.mu.Unlock() configPath := a.getConfigPath() fmt.Println(configPath) data, err := yaml.Marshal(config) if err != nil { return err.Error() } err = os.WriteFile(configPath, data, 0644) if err != nil { return err.Error() } return "" } func (a *AppConfig) SaveTheme(theme string) string { a.mu.Lock() defer a.mu.Unlock() config := a.GetConfig() config.Theme = theme data, err := yaml.Marshal(config) if err != nil { return err.Error() } configPath := a.getConfigPath() err = os.WriteFile(configPath, data, 0644) if err != nil { return err.Error() } return "" } func (a *AppConfig) getConfigPath() string { homeDir, err := os.UserHomeDir() if err != nil { log.Printf("os.UserHomeDir() error: %s", err.Error()) return common.ConfigPath } configDir := filepath.Join(homeDir, common.ConfigDir) _, err = os.Stat(configDir) if os.IsNotExist(err) { err = os.Mkdir(configDir, os.ModePerm) if err != nil { log.Printf("create configDir %s error: %s", configDir, err.Error()) return common.ConfigPath } } return filepath.Join(configDir, common.ConfigPath) } func (a *AppConfig) GetHistory() []*types.History { historyPath := a.getHistoryPath() data, err := os.ReadFile(historyPath) history := make([]*types.History, 0, 200) if err != nil { return history } err = yaml.Unmarshal(data, &history) if err != nil { return history } return history } func (a *AppConfig) SaveHistory(histories []types.History) string { a.mu.Lock() defer a.mu.Unlock() historyPath := a.getHistoryPath() data, err := yaml.Marshal(histories) if err != nil { return err.Error() } err = os.WriteFile(historyPath, data, 0644) if err != nil { return err.Error() } return "" } func (a *AppConfig) getHistoryPath() string { homeDir, err := os.UserHomeDir() if err != nil { log.Printf("os.UserHomeDir() error: %s", err.Error()) return common.HistoryPath } configDir := filepath.Join(homeDir, common.ConfigDir) _, err = os.Stat(configDir) if os.IsNotExist(err) { err = os.Mkdir(configDir, os.ModePerm) if err != nil { log.Printf("create configDir %s error: %s", configDir, err.Error()) return common.HistoryPath } } return filepath.Join(configDir, common.HistoryPath) } // GetVersion returns the application version func (a *AppConfig) GetVersion() string { return common.Version } func (a *AppConfig) GetAppName() string { return common.AppName } func (a *AppConfig) OpenFileDialog(options runtime.OpenDialogOptions) (string, error) { return runtime.OpenFileDialog(a.ctx, options) } func (a *AppConfig) LogErrToFile(message string) { file, err := os.OpenFile(common.ErrLogPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { log.Println("Failed to open log file:", err) return } defer file.Close() if _, err := file.WriteString(message); err != nil { log.Println("Failed to write to log file:", err) } } ================================================ FILE: app/backend/service/es.go ================================================ /* * Copyright 2025 Bronya0 . * Author Github: https://github.com/Bronya0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package service import ( "app/backend/types" "bufio" "crypto/tls" "encoding/json" "fmt" "log" "net/http" "net/url" "os" "strings" "sync" "time" "github.com/go-resty/resty/v2" ) const ( FORMAT = "?format=json&pretty" StatsApi = "/_cluster/stats" + FORMAT AddDoc = "/_doc" HealthApi = "/_cluster/health" NodesApi = "/_nodes/stats/indices,os,fs,process,jvm" AllIndexApi = "/_cat/indices?format=json&pretty&bytes=b" ClusterSettings = "/_cluster/settings" ForceMerge = "/_forcemerge?wait_for_completion=false" REFRESH = "/_refresh" FLUSH = "/_flush" CacheClear = "/_cache/clear" TasksApi = "/_tasks" + FORMAT CancelTasksApi = "/_tasks/%s/_cancel" ) type ESService struct { ConnectObj *types.Connect Client *resty.Client mu sync.RWMutex } func NewESService() *ESService { client := resty.New() client.SetTimeout(30 * time.Second) client.SetRetryCount(0) client.SetHeader("Content-Type", "application/json") return &ESService{ Client: client, ConnectObj: &types.Connect{}, } } func ConfigureSSL(UseSSL, SkipSSLVerify bool, client *resty.Client, CACert string) { // Configure SSL // CACert是证书内容 if UseSSL { client.SetScheme("https") if SkipSSLVerify { client.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}) } if CACert != "" { client.SetRootCertificateFromString(CACert) } } else { client.SetScheme("http") } } func (es *ESService) SetConnect(key, host, username, password, CACert string, UseSSL, SkipSSLVerify bool) { es.mu.Lock() // 加写锁 defer es.mu.Unlock() // 方法结束时解锁 es.ConnectObj = &types.Connect{ Name: key, Host: host, Username: username, Password: password, UseSSL: UseSSL, SkipSSLVerify: SkipSSLVerify, CACert: CACert, } if username != "" && password != "" { es.Client.SetBasicAuth(username, password) } ConfigureSSL(UseSSL, SkipSSLVerify, es.Client, CACert) fmt.Println("设置当前连接:", es.ConnectObj.Host) } func (es *ESService) TestClient(host, username, password, CACert string, UseSSL, SkipSSLVerify bool) string { client := resty.New() if username != "" && password != "" { client.SetBasicAuth(username, password) } // Configure SSL ConfigureSSL(UseSSL, SkipSSLVerify, client, CACert) resp, err := client.R().Get(host + HealthApi) if err != nil { return err.Error() } if resp.StatusCode() != http.StatusOK { return string(resp.Body()) } return "" } // AddDocument 添加文档 func (es *ESService) AddDocument(index, doc string) *types.ResultResp { if es.ConnectObj.Host == "" { return &types.ResultResp{Err: "请先选择一个集群"} } var result map[string]any resp, err := es.Client.R(). SetBody(doc). SetResult(&result). Post(es.ConnectObj.Host + "/" + index + AddDoc) if err != nil { return &types.ResultResp{Err: err.Error()} } if resp.StatusCode() != http.StatusCreated { return &types.ResultResp{Err: string(resp.Body())} } return &types.ResultResp{Result: result} } func (es *ESService) GetNodes() *types.ResultResp { if es.ConnectObj.Host == "" { return &types.ResultResp{Err: "请先选择一个集群"} } var result any resp, err := es.Client.R().SetResult(&result).Get(es.ConnectObj.Host + NodesApi) if err != nil { return &types.ResultResp{Err: err.Error()} } if resp.StatusCode() != http.StatusOK { return &types.ResultResp{Err: string(resp.Body())} } return &types.ResultResp{Result: result} } func (es *ESService) GetHealth() *types.ResultResp { if es.ConnectObj.Host == "" { return &types.ResultResp{Err: "请先选择一个集群"} } var result map[string]any resp, err := es.Client.R().SetResult(&result).Get(es.ConnectObj.Host + HealthApi) if err != nil { return &types.ResultResp{Err: err.Error()} } if resp.StatusCode() != http.StatusOK { return &types.ResultResp{Err: string(resp.Body())} } return &types.ResultResp{Result: result} } func (es *ESService) GetStats() *types.ResultResp { if es.ConnectObj.Host == "" { return &types.ResultResp{Err: "请先选择一个集群"} } var result map[string]any resp, err := es.Client.R().SetResult(&result).Get(es.ConnectObj.Host + StatsApi) if err != nil { return &types.ResultResp{Err: err.Error()} } if resp.StatusCode() != http.StatusOK { return &types.ResultResp{Err: string(resp.Body())} } return &types.ResultResp{Result: result} } func (es *ESService) GetIndexes(name string) *types.ResultsResp { if es.ConnectObj.Host == "" { return &types.ResultsResp{Err: "请先选择一个集群"} } newUrl := es.ConnectObj.Host + AllIndexApi if name != "" { newUrl += "&index=" + "*" + name + "*" } log.Println(newUrl) var result []any resp, err := es.Client.R().SetResult(&result).Get(newUrl) if err != nil { return &types.ResultsResp{Err: err.Error()} } if resp.StatusCode() != http.StatusOK { return &types.ResultsResp{Err: string(resp.Body())} } return &types.ResultsResp{Results: result} } func (es *ESService) CreateIndex(name string, numberOfShards, numberOfReplicas int, mapping string) *types.ResultResp { if es.ConnectObj.Host == "" { return &types.ResultResp{Err: "请先选择一个集群"} } indexConfig := types.H{ "settings": types.H{ "number_of_shards": numberOfShards, "number_of_replicas": numberOfReplicas, }, } if mapping != "" { var mappings types.H err := json.Unmarshal([]byte(mapping), &mappings) if err != nil { return &types.ResultResp{Err: err.Error()} } indexConfig["mappings"] = mappings } resp, err := es.Client.R(). SetBody(indexConfig). Put(es.ConnectObj.Host + "/" + name) if err != nil { return &types.ResultResp{Err: err.Error()} } if resp.StatusCode() != http.StatusOK { return &types.ResultResp{Err: string(resp.Body())} } return &types.ResultResp{} } func (es *ESService) GetIndexInfo(indexName string) *types.ResultResp { if es.ConnectObj.Host == "" { return &types.ResultResp{Err: "请先选择一个集群"} } var result map[string]any resp, err := es.Client.R().SetResult(&result).Get(es.ConnectObj.Host + "/" + indexName) if err != nil { return &types.ResultResp{Err: err.Error()} } if resp.StatusCode() != http.StatusOK { return &types.ResultResp{Err: string(resp.Body())} } return &types.ResultResp{Result: result} } func (es *ESService) DeleteIndex(indexName string) *types.ResultResp { if es.ConnectObj.Host == "" { return &types.ResultResp{Err: "请先选择一个集群"} } var result map[string]any resp, err := es.Client.R().SetResult(&result).Delete(es.ConnectObj.Host + "/" + indexName) if err != nil { return &types.ResultResp{Err: err.Error()} } if resp.StatusCode() != http.StatusOK { return &types.ResultResp{Err: string(resp.Body())} } return &types.ResultResp{Result: result} } func (es *ESService) OpenCloseIndex(indexName, now string) *types.ResultResp { if es.ConnectObj.Host == "" { return &types.ResultResp{Err: "请先选择一个集群"} } var result map[string]any action, ok := map[string]string{ "open": "_close", "close": "_open", }[now] if !ok { return &types.ResultResp{Err: "无效的状态参数: " + now} } resp, err := es.Client.R().SetResult(&result).Post(es.ConnectObj.Host + "/" + indexName + "/" + action) if err != nil { return &types.ResultResp{Err: err.Error()} } if resp.StatusCode() != http.StatusOK { return &types.ResultResp{Err: string(resp.Body())} } return &types.ResultResp{Result: result} } func (es *ESService) GetIndexMappings(indexName string) *types.ResultResp { if es.ConnectObj.Host == "" { return &types.ResultResp{Err: "请先选择一个集群"} } var result map[string]any resp, err := es.Client.R().SetResult(&result).Get(es.ConnectObj.Host + "/" + indexName) if err != nil { return &types.ResultResp{Err: err.Error()} } if resp.StatusCode() != http.StatusOK { return &types.ResultResp{Err: string(resp.Body())} } return &types.ResultResp{Result: result} } func (es *ESService) MergeSegments(indexName string) *types.ResultResp { if es.ConnectObj.Host == "" { return &types.ResultResp{Err: "请先选择一个集群"} } var result map[string]any resp, err := es.Client.R().SetResult(&result).Post(es.ConnectObj.Host + "/" + indexName + ForceMerge) if err != nil { return &types.ResultResp{Err: err.Error()} } if resp.StatusCode() != http.StatusOK { return &types.ResultResp{Err: string(resp.Body())} } return &types.ResultResp{Result: result} } func (es *ESService) Refresh(indexName string) *types.ResultResp { if es.ConnectObj.Host == "" { return &types.ResultResp{Err: "请先选择一个集群"} } var result map[string]any resp, err := es.Client.R().SetResult(&result).Post(es.ConnectObj.Host + "/" + indexName + REFRESH) if err != nil { return &types.ResultResp{Err: err.Error()} } if resp.StatusCode() != http.StatusOK { return &types.ResultResp{Err: string(resp.Body())} } return &types.ResultResp{Result: result} } func (es *ESService) Flush(indexName string) *types.ResultResp { if es.ConnectObj.Host == "" { return &types.ResultResp{Err: "请先选择一个集群"} } var result map[string]any resp, err := es.Client.R().SetResult(&result).Post(es.ConnectObj.Host + "/" + indexName + FLUSH) if err != nil { return &types.ResultResp{Err: err.Error()} } if resp.StatusCode() != http.StatusOK { return &types.ResultResp{Err: string(resp.Body())} } return &types.ResultResp{Result: result} } func (es *ESService) CacheClear(indexName string) *types.ResultResp { if es.ConnectObj.Host == "" { return &types.ResultResp{Err: "请先选择一个集群"} } var result map[string]any resp, err := es.Client.R().SetResult(&result).Post(es.ConnectObj.Host + "/" + indexName + CacheClear) if err != nil { return &types.ResultResp{Err: err.Error()} } if resp.StatusCode() != http.StatusOK { return &types.ResultResp{Err: string(resp.Body())} } return &types.ResultResp{Result: result} } func (es *ESService) GetDoc10(indexName string) *types.ResultResp { if es.ConnectObj.Host == "" { return &types.ResultResp{Err: "请先选择一个集群"} } body := map[string]any{ "query": map[string]any{ "query_string": map[string]any{ "query": "*", }, }, "size": 10, "from": 0, "sort": []any{}, } var result map[string]any resp, err := es.Client.R(). SetBody(body). SetResult(&result). Post(es.ConnectObj.Host + "/" + indexName + "/_search") if err != nil { return &types.ResultResp{Err: err.Error()} } if resp.StatusCode() != http.StatusOK { return &types.ResultResp{Err: string(resp.Body())} } return &types.ResultResp{Result: result} } func (es *ESService) Search(method, path string, body any) *types.ResultResp { if es.ConnectObj.Host == "" { return &types.ResultResp{Err: "请先选择一个集群"} } var result any req := es.Client.R().SetResult(&result) if body != nil { req = req.SetBody(body) } resp, err := req.Execute(method, es.ConnectObj.Host+path) if err != nil { return &types.ResultResp{Err: err.Error()} } if resp.StatusCode() != http.StatusOK { return &types.ResultResp{Err: string(resp.Body())} } return &types.ResultResp{Result: result} } func (es *ESService) GetClusterSettings() *types.ResultResp { if es.ConnectObj.Host == "" { return &types.ResultResp{Err: "请先选择一个集群"} } var result map[string]any resp, err := es.Client.R().SetResult(&result).Get(es.ConnectObj.Host + ClusterSettings) if err != nil { return &types.ResultResp{Err: err.Error()} } if resp.StatusCode() != http.StatusOK { return &types.ResultResp{Err: string(resp.Body())} } return &types.ResultResp{Result: result} } func (es *ESService) GetIndexSettings(indexName string) *types.ResultResp { if es.ConnectObj.Host == "" { return &types.ResultResp{Err: "请先选择一个集群"} } var result map[string]any resp, err := es.Client.R().SetResult(&result).Get(es.ConnectObj.Host + "/" + indexName) if err != nil { return &types.ResultResp{Err: err.Error()} } if resp.StatusCode() != http.StatusOK { return &types.ResultResp{Err: string(resp.Body())} } return &types.ResultResp{Result: result} } func (es *ESService) GetIndexAliases(indexNameList []string) *types.ResultResp { if es.ConnectObj.Host == "" { return &types.ResultResp{Err: "请先选择一个集群"} } var result map[string]any indexNames := strings.Join(indexNameList, ",") resp, err := es.Client.R().SetResult(&result).Get(es.ConnectObj.Host + "/" + indexNames + "/_alias") if err != nil { return &types.ResultResp{Err: err.Error()} } if resp.StatusCode() != http.StatusOK { return &types.ResultResp{Err: string(resp.Body())} } alias := make(map[string]any) for name, obj := range result { if aliases, ok := obj.(map[string]any)["aliases"]; ok { names := make([]string, 0) aliases, ok := aliases.(map[string]any) if !ok { continue } for aliasName := range aliases { names = append(names, aliasName) } if len(names) > 0 { alias[name] = strings.Join(names, ",") } } } return &types.ResultResp{Result: alias} } func (es *ESService) GetIndexSegments(indexName string) *types.ResultResp { if es.ConnectObj.Host == "" { return &types.ResultResp{Err: "请先选择一个集群"} } var result map[string]any resp, err := es.Client.R().SetResult(&result).Get(es.ConnectObj.Host + "/" + indexName) if err != nil { return &types.ResultResp{Err: err.Error()} } if resp.StatusCode() != http.StatusOK { return &types.ResultResp{Err: string(resp.Body())} } return &types.ResultResp{Result: result} } func (es *ESService) GetTasks() *types.ResultsResp { if es.ConnectObj.Host == "" { return &types.ResultsResp{Err: "请先选择一个集群"} } var result map[string]any resp, err := es.Client.R().SetResult(&result).Get(es.ConnectObj.Host + TasksApi) if err != nil { return &types.ResultsResp{Err: err.Error()} } if resp.StatusCode() != http.StatusOK { return &types.ResultsResp{Err: string(resp.Body())} } nodes, ok := result["nodes"].(map[string]any) if !ok { return &types.ResultsResp{Err: "获取任务列表失败"} } var data []any for _, nodeObj := range nodes { nodeTasks, ok := nodeObj.(map[string]any)["tasks"].(map[string]any) if !ok { continue } for taskID, taskInfo := range nodeTasks { taskInfoMap, ok := taskInfo.(map[string]any) if !ok { continue } nodeName, ok := nodeObj.(map[string]any) if !ok { continue } nodeIp, ok := nodeObj.(map[string]any) if !ok { continue } data = append(data, map[string]any{ "task_id": taskID, "node_name": nodeName["name"], "node_ip": nodeIp["ip"], "type": taskInfoMap["type"], "action": taskInfoMap["action"], "start_time_in_millis": taskInfoMap["start_time_in_millis"], "running_time_in_nanos": taskInfoMap["running_time_in_nanos"], "cancellable": taskInfoMap["cancellable"], "parent_task_id": taskInfoMap["parent_task_id"], }) } } return &types.ResultsResp{Results: data} } func (es *ESService) CancelTasks(taskID string) *types.ResultResp { if es.ConnectObj.Host == "" { return &types.ResultResp{Err: "请先选择一个集群"} } newUrl := fmt.Sprintf(es.ConnectObj.Host+CancelTasksApi, url.PathEscape(taskID)) var result map[string]any resp, err := es.Client.R().SetResult(&result).Post(newUrl) if err != nil { return &types.ResultResp{Err: err.Error()} } if resp.StatusCode() != http.StatusOK { return &types.ResultResp{Err: string(resp.Body())} } return &types.ResultResp{Result: result} } // GetSnapshots 获取ES快照列表 func (es *ESService) GetSnapshots() *types.ResultsResp { if es.ConnectObj.Host == "" { return &types.ResultsResp{Err: "请先选择一个集群"} } // 1. 首先获取所有仓库列表 var repositories map[string]interface{} // 修改目标类型 reposResp, err := es.Client.R().Get(es.ConnectObj.Host + "/_snapshot") if err != nil { return &types.ResultsResp{Err: err.Error()} } if reposResp.StatusCode() != http.StatusOK { return &types.ResultsResp{Err: string(reposResp.Body())} } err = json.Unmarshal(reposResp.Body(), &repositories) // 直接解析到 map if err != nil { return &types.ResultsResp{Err: err.Error()} } // 2. 并发遍历每个仓库获取其快照 var ( mu sync.Mutex allSnapshots []any wg sync.WaitGroup ) for repoName := range repositories { wg.Add(1) go func(repo string) { defer wg.Done() var repoResult map[string]interface{} resp, err := es.Client.R().SetResult(&repoResult).Get(es.ConnectObj.Host + "/_snapshot/" + repo + "/_all") if err != nil || resp.StatusCode() != http.StatusOK { return } // 3. 处理每个快照的数据(带安全类型断言) snapshots, ok := repoResult["snapshots"].([]interface{}) if !ok { return } var items []any for _, snap := range snapshots { snapshot, ok := snap.(map[string]interface{}) if !ok { continue } state, _ := snapshot["state"].(string) items = append(items, map[string]interface{}{ "snapshot": snapshot["snapshot"], "repository": repo, "state": strings.ToUpper(state), "start_time": snapshot["start_time"], "end_time": snapshot["end_time"], "indices": snapshot["indices"], "total_shards": snapshot["shards_total"], "successful_shards": snapshot["shards_successful"], }) } mu.Lock() allSnapshots = append(allSnapshots, items...) mu.Unlock() }(repoName) } wg.Wait() return &types.ResultsResp{Results: allSnapshots} } // GetSnapshotRepositories 获取所有快照仓库 func (es *ESService) GetSnapshotRepositories() *types.ResultsResp { if es.ConnectObj.Host == "" { return &types.ResultsResp{Err: "请先选择一个集群"} } var repositories map[string]any resp, err := es.Client.R().Get(es.ConnectObj.Host + "/_snapshot") if err != nil { return &types.ResultsResp{Err: err.Error()} } if resp.StatusCode() != http.StatusOK { return &types.ResultsResp{Err: string(resp.Body())} } err = json.Unmarshal(resp.Body(), &repositories) if err != nil { return &types.ResultsResp{Err: err.Error()} } var data []any for name, info := range repositories { repoInfo, ok := info.(map[string]any) if !ok { continue } item := map[string]any{ "name": name, "type": repoInfo["type"], "settings": repoInfo["settings"], } data = append(data, item) } return &types.ResultsResp{Results: data} } // CreateSnapshotRepository 创建快照仓库 func (es *ESService) CreateSnapshotRepository(name, repoType, settings string) *types.ResultResp { if es.ConnectObj.Host == "" { return &types.ResultResp{Err: "请先选择一个集群"} } if name == "" || repoType == "" { return &types.ResultResp{Err: "仓库名称和类型不能为空"} } var settingsMap map[string]any if settings != "" { if err := json.Unmarshal([]byte(settings), &settingsMap); err != nil { return &types.ResultResp{Err: "settings JSON 格式无效: " + err.Error()} } } body := map[string]any{ "type": repoType, "settings": settingsMap, } var result map[string]any resp, err := es.Client.R(). SetBody(body). SetResult(&result). Put(es.ConnectObj.Host + "/_snapshot/" + url.PathEscape(name)) if err != nil { return &types.ResultResp{Err: err.Error()} } if resp.StatusCode() != http.StatusOK { return &types.ResultResp{Err: string(resp.Body())} } return &types.ResultResp{Result: result} } // DeleteSnapshotRepository 删除快照仓库 func (es *ESService) DeleteSnapshotRepository(name string) *types.ResultResp { if es.ConnectObj.Host == "" { return &types.ResultResp{Err: "请先选择一个集群"} } if name == "" { return &types.ResultResp{Err: "仓库名称不能为空"} } var result map[string]any resp, err := es.Client.R(). SetResult(&result). Delete(es.ConnectObj.Host + "/_snapshot/" + url.PathEscape(name)) if err != nil { return &types.ResultResp{Err: err.Error()} } if resp.StatusCode() != http.StatusOK { return &types.ResultResp{Err: string(resp.Body())} } return &types.ResultResp{Result: result} } // VerifySnapshotRepository 验证快照仓库 func (es *ESService) VerifySnapshotRepository(name string) *types.ResultResp { if es.ConnectObj.Host == "" { return &types.ResultResp{Err: "请先选择一个集群"} } if name == "" { return &types.ResultResp{Err: "仓库名称不能为空"} } var result map[string]any resp, err := es.Client.R(). SetResult(&result). Post(es.ConnectObj.Host + "/_snapshot/" + url.PathEscape(name) + "/_verify") if err != nil { return &types.ResultResp{Err: err.Error()} } if resp.StatusCode() != http.StatusOK { return &types.ResultResp{Err: string(resp.Body())} } return &types.ResultResp{Result: result} } // CreateSnapshot 创建快照(异步) func (es *ESService) CreateSnapshot(repository, snapshot, indices string, includeGlobalState bool) *types.ResultResp { if es.ConnectObj.Host == "" { return &types.ResultResp{Err: "请先选择一个集群"} } if repository == "" || snapshot == "" { return &types.ResultResp{Err: "仓库名称和快照名称不能为空"} } body := map[string]any{ "include_global_state": includeGlobalState, } if indices != "" { body["indices"] = indices } var result map[string]any resp, err := es.Client.R(). SetBody(body). SetResult(&result). Put(es.ConnectObj.Host + "/_snapshot/" + url.PathEscape(repository) + "/" + url.PathEscape(snapshot) + "?wait_for_completion=false") if err != nil { return &types.ResultResp{Err: err.Error()} } if resp.StatusCode() != http.StatusOK && resp.StatusCode() != http.StatusAccepted { return &types.ResultResp{Err: string(resp.Body())} } return &types.ResultResp{Result: result} } // DeleteSnapshot 删除快照 func (es *ESService) DeleteSnapshot(repository, snapshot string) *types.ResultResp { if es.ConnectObj.Host == "" { return &types.ResultResp{Err: "请先选择一个集群"} } if repository == "" || snapshot == "" { return &types.ResultResp{Err: "仓库名称和快照名称不能为空"} } var result map[string]any resp, err := es.Client.R(). SetResult(&result). Delete(es.ConnectObj.Host + "/_snapshot/" + url.PathEscape(repository) + "/" + url.PathEscape(snapshot)) if err != nil { return &types.ResultResp{Err: err.Error()} } if resp.StatusCode() != http.StatusOK { return &types.ResultResp{Err: string(resp.Body())} } return &types.ResultResp{Result: result} } // GetSnapshotDetail 获取快照详情 func (es *ESService) GetSnapshotDetail(repository, snapshot string) *types.ResultResp { if es.ConnectObj.Host == "" { return &types.ResultResp{Err: "请先选择一个集群"} } if repository == "" || snapshot == "" { return &types.ResultResp{Err: "仓库名称和快照名称不能为空"} } var result map[string]any resp, err := es.Client.R(). SetResult(&result). Get(es.ConnectObj.Host + "/_snapshot/" + url.PathEscape(repository) + "/" + url.PathEscape(snapshot)) if err != nil { return &types.ResultResp{Err: err.Error()} } if resp.StatusCode() != http.StatusOK { return &types.ResultResp{Err: string(resp.Body())} } return &types.ResultResp{Result: result} } // RestoreSnapshot 恢复快照(异步) func (es *ESService) RestoreSnapshot(repository, snapshot, indices, renamePattern, renameReplacement string, includeGlobalState bool) *types.ResultResp { if es.ConnectObj.Host == "" { return &types.ResultResp{Err: "请先选择一个集群"} } if repository == "" || snapshot == "" { return &types.ResultResp{Err: "仓库名称和快照名称不能为空"} } body := map[string]any{ "include_global_state": includeGlobalState, } if indices != "" { body["indices"] = indices } if renamePattern != "" { body["rename_pattern"] = renamePattern } if renameReplacement != "" { body["rename_replacement"] = renameReplacement } var result map[string]any resp, err := es.Client.R(). SetBody(body). SetResult(&result). Post(es.ConnectObj.Host + "/_snapshot/" + url.PathEscape(repository) + "/" + url.PathEscape(snapshot) + "/_restore?wait_for_completion=false") if err != nil { return &types.ResultResp{Err: err.Error()} } if resp.StatusCode() != http.StatusOK && resp.StatusCode() != http.StatusAccepted { return &types.ResultResp{Err: string(resp.Body())} } return &types.ResultResp{Result: result} } // GetSnapshotRestoreStatus 获取快照恢复状态 func (es *ESService) GetSnapshotRestoreStatus() *types.ResultResp { if es.ConnectObj.Host == "" { return &types.ResultResp{Err: "请先选择一个集群"} } var result map[string]any resp, err := es.Client.R(). SetResult(&result). Get(es.ConnectObj.Host + "/_recovery?active_only=true&format=json") if err != nil { return &types.ResultResp{Err: err.Error()} } if resp.StatusCode() != http.StatusOK { return &types.ResultResp{Err: string(resp.Body())} } return &types.ResultResp{Result: result} } // GetSLMPolicies 获取所有SLM策略 func (es *ESService) GetSLMPolicies() *types.ResultsResp { if es.ConnectObj.Host == "" { return &types.ResultsResp{Err: "请先选择一个集群"} } var result []any resp, err := es.Client.R().Get(es.ConnectObj.Host + "/_slm/policy") if err != nil { return &types.ResultsResp{Err: err.Error()} } if resp.StatusCode() != http.StatusOK { return &types.ResultsResp{Err: string(resp.Body())} } // SLM API 返回的是一个对象 {policyId: {...}, ...} var policiesMap map[string]any if err := json.Unmarshal(resp.Body(), &policiesMap); err != nil { return &types.ResultsResp{Err: err.Error()} } for name, info := range policiesMap { policyInfo, ok := info.(map[string]any) if !ok { continue } policyInfo["name"] = name result = append(result, policyInfo) } return &types.ResultsResp{Results: result} } // CreateSLMPolicy 创建SLM策略 func (es *ESService) CreateSLMPolicy(policyId, name, schedule, repository, indices, expireAfter string, minCount, maxCount int) *types.ResultResp { if es.ConnectObj.Host == "" { return &types.ResultResp{Err: "请先选择一个集群"} } if policyId == "" || schedule == "" || repository == "" { return &types.ResultResp{Err: "策略ID、调度计划和仓库名称不能为空"} } body := map[string]any{ "schedule": schedule, "name": name, "repository": repository, "config": map[string]any{ "indices": []string{"*"}, "include_global_state": false, }, } if indices != "" { body["config"].(map[string]any)["indices"] = strings.Split(indices, ",") } retention := map[string]any{} if expireAfter != "" { retention["expire_after"] = expireAfter } if minCount > 0 { retention["min_count"] = minCount } if maxCount > 0 { retention["max_count"] = maxCount } if len(retention) > 0 { body["retention"] = retention } var result map[string]any resp, err := es.Client.R(). SetBody(body). SetResult(&result). Put(es.ConnectObj.Host + "/_slm/policy/" + url.PathEscape(policyId)) if err != nil { return &types.ResultResp{Err: err.Error()} } if resp.StatusCode() != http.StatusOK { return &types.ResultResp{Err: string(resp.Body())} } return &types.ResultResp{Result: result} } // DeleteSLMPolicy 删除SLM策略 func (es *ESService) DeleteSLMPolicy(policyId string) *types.ResultResp { if es.ConnectObj.Host == "" { return &types.ResultResp{Err: "请先选择一个集群"} } if policyId == "" { return &types.ResultResp{Err: "策略ID不能为空"} } var result map[string]any resp, err := es.Client.R(). SetResult(&result). Delete(es.ConnectObj.Host + "/_slm/policy/" + url.PathEscape(policyId)) if err != nil { return &types.ResultResp{Err: err.Error()} } if resp.StatusCode() != http.StatusOK { return &types.ResultResp{Err: string(resp.Body())} } return &types.ResultResp{Result: result} } // ExecuteSLMPolicy 手动执行SLM策略 func (es *ESService) ExecuteSLMPolicy(policyId string) *types.ResultResp { if es.ConnectObj.Host == "" { return &types.ResultResp{Err: "请先选择一个集群"} } if policyId == "" { return &types.ResultResp{Err: "策略ID不能为空"} } var result map[string]any resp, err := es.Client.R(). SetResult(&result). Post(es.ConnectObj.Host + "/_slm/policy/" + url.PathEscape(policyId) + "/_execute") if err != nil { return &types.ResultResp{Err: err.Error()} } if resp.StatusCode() != http.StatusOK { return &types.ResultResp{Err: string(resp.Body())} } return &types.ResultResp{Result: result} } // SearchResponse 定义 ES 搜索响应的结构 type SearchResponse struct { ScrollID string `json:"_scroll_id"` Hits struct { Total struct { Value int `json:"value"` } `json:"total"` Hits []struct { Source json.RawMessage `json:"_source"` } `json:"hits"` } `json:"hits"` } // DownloadESIndex 使用 Resty 客户端从 ES 下载指定索引的数据 func (es *ESService) DownloadESIndex(index string, queryDSL string, filePath string) *types.ResultResp { if es.ConnectObj.Host == "" { return &types.ResultResp{Err: "请先选择一个集群"} } res := &types.ResultResp{} // 如果 queryDSL 为空,默认使用 match_all 查询 if queryDSL == "" { queryDSL = `{"match_all": {}}` } // 创建本地文件 file, err := os.Create(filePath) if err != nil { res.Err = fmt.Sprintf("创建文件失败: %v", err) return res } success := false defer func() { file.Close() if !success { os.Remove(filePath) // 下载失败时清理残缺文件 } }() // 构造初始搜索请求的 body,设置每批次大小为 10000 bodyStr := fmt.Sprintf(`{"size": 10000, "query": %s}`, queryDSL) resp, err := es.Client.R().SetBody(bodyStr).Post(es.ConnectObj.Host + "/" + index + "/_search?scroll=3m") if err != nil { res.Err = fmt.Sprintf("初始搜索请求失败: %v", err) return res } if resp.StatusCode() != 200 { res.Err = "初始搜索请求返回非 200 状态码" return res } // 解析初始响应 var searchResponse SearchResponse err = json.Unmarshal(resp.Body(), &searchResponse) if err != nil { res.Err = fmt.Sprintf("解析初始响应失败: %v", err) return res } // 使用 bufio.Writer 进行缓冲写入 writer := bufio.NewWriter(file) defer writer.Flush() // 确保缓冲区数据在函数结束时写入文件 // 写入 JSON 数组的开头 _, _ = writer.WriteString("[") // 标志变量,用于控制逗号分隔符 isFirst := true // 循环处理滚动下载 for { // 如果当前批次没有文档,则退出循环 if len(searchResponse.Hits.Hits) == 0 { break } // 遍历当前批次的每个文档 for _, hit := range searchResponse.Hits.Hits { if !isFirst { // 除了第一个文档前,其他文档前添加逗号 _, _ = writer.WriteString(",") } isFirst = false // 直接写入文档的 _source 字段(json.RawMessage 是 []byte 类型) _, _ = writer.Write(hit.Source) } // 发送滚动请求获取下一批数据 scrollBody := map[string]interface{}{ "scroll": "3m", // 滚动上下文有效期 1 分钟 "scroll_id": searchResponse.ScrollID, } resp, err = es.Client.R().SetBody(scrollBody).Post(es.ConnectObj.Host + "/_search/scroll") if err != nil { res.Err = fmt.Sprintf("滚动请求失败: %v", err) return res } if resp.StatusCode() != 200 { res.Err = fmt.Sprintf("滚动请求返回非 200 状态码 %v", resp.StatusCode()) return res } // 解析滚动响应 err = json.Unmarshal(resp.Body(), &searchResponse) if err != nil { res.Err = fmt.Sprintf("解析滚动响应失败: %v", err) return res } } // 写入 JSON 数组的结尾 _, _ = writer.WriteString("]") success = true return res } ================================================ FILE: app/backend/system/update.go ================================================ /* * Copyright 2025 Bronya0 . * Author Github: https://github.com/Bronya0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package system import ( "app/backend/common" "app/backend/types" "context" "fmt" "github.com/go-resty/resty/v2" "runtime" "runtime/debug" "strings" "time" ) type Update struct { ctx context.Context } func (obj *Update) Start(ctx context.Context) { obj.ctx = ctx } func (obj *Update) CheckUpdate() *types.Tag { client := resty.New() tag := &types.Tag{} resp, err := client.R().SetResult(tag).Get(common.UPDATE_URL) if err != nil || resp.StatusCode() != 200 { return nil } tag.TagName = strings.TrimSpace(tag.TagName) return tag } func (obj *Update) GetProcessInfo() string { // 获取内存统计信息 var memStats runtime.MemStats runtime.ReadMemStats(&memStats) // 获取构建信息 var goVersion string if buildInfo, ok := debug.ReadBuildInfo(); ok { goVersion = buildInfo.GoVersion } else { goVersion = runtime.Version() } // 格式化输出详细信息 info := fmt.Sprintf( "基本信息:\n"+ "- Go版本: %s\n"+ "- 操作系统: %s\n"+ "- 体系结构: %s\n"+ "- CPU数量: %d\n"+ "- 协程数量: %d\n"+ "- 当前时间戳: %s\n\n"+ "内存统计:\n"+ "- 已分配内存: %.2f MB\n"+ "- 总分配内存: %.2f MB\n"+ "- 系统内存: %.2f MB\n"+ "- 堆分配: %.2f MB\n"+ "- 堆系统内存: %.2f MB\n"+ "- 堆空闲: %.2f MB\n"+ "- 堆使用中: %.2f MB\n"+ "- 栈使用中: %.2f MB\n"+ "- 堆对象数量: %d\n"+ "- 内存分配次数: %d\n"+ "- 内存释放次数: %d\n\n"+ "垃圾回收统计:\n"+ "- 垃圾回收运行次数: %d\n"+ "- 上次垃圾回收时间: %s\n"+ "- 下次垃圾回收限制: %.2f MB\n"+ "- 垃圾回收CPU占比: %.4f%%\n"+ "- 垃圾回收总暂停时间: %v\n", goVersion, // Go版本 runtime.GOOS, // 操作系统 runtime.GOARCH, // 体系结构 runtime.NumCPU(), // CPU数量 runtime.NumGoroutine(), // 协程数量 time.Now().Format(time.DateTime), // 当前时间戳 float64(memStats.Alloc)/1024/1024, // 已分配内存 float64(memStats.TotalAlloc)/1024/1024, // 总分配内存 float64(memStats.Sys)/1024/1024, // 系统内存 float64(memStats.HeapAlloc)/1024/1024, // 堆分配 float64(memStats.HeapSys)/1024/1024, // 堆系统内存 float64(memStats.HeapIdle)/1024/1024, // 堆空闲 float64(memStats.HeapInuse)/1024/1024, // 堆使用中 float64(memStats.StackInuse)/1024/1024, // 栈使用中 memStats.HeapObjects, // 堆对象数量 memStats.Mallocs, // 内存分配次数 memStats.Frees, // 内存释放次数 memStats.NumGC, // 垃圾回收运行次数 time.Unix(0, int64(memStats.LastGC)).Format(time.DateTime), // 上次垃圾回收时间 float64(memStats.NextGC)/1024/1024, // 下次垃圾回收限制 memStats.GCCPUFraction*100, // 垃圾回收CPU占比 time.Duration(memStats.PauseTotalNs), // 垃圾回收总暂停时间 ) return info } ================================================ FILE: app/backend/types/resp.go ================================================ /* * Copyright 2025 Bronya0 . * Author Github: https://github.com/Bronya0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package types type Tag struct { Name string `json:"name"` TagName string `json:"tag_name"` Body string `json:"body"` } type Config struct { Width int `json:"width"` Height int `json:"height"` Language string `json:"language"` Theme string `json:"theme"` Connects []Connect `json:"connects"` } type History struct { Time int `json:"timestamp"` Method string `json:"method"` Path string `json:"path"` DSL string `json:"dsl"` } type ResultsResp struct { Results []any `json:"results"` Err string `json:"err"` } type ResultResp struct { Result any `json:"result"` Err string `json:"err"` } type Connect struct { Id int `json:"id"` Name string `json:"name"` Host string `json:"host"` Username string `json:"username"` Password string `json:"password"` UseSSL bool `json:"useSSL"` SkipSSLVerify bool `json:"skipSSLVerify"` CACert string `json:"caCert"` } type H map[string]any ================================================ FILE: app/build/README.md ================================================ # Build Directory The build directory is used to house all the build files and assets for your application. The structure is: * bin - Output directory * darwin - macOS specific files * windows - Windows specific files ## Mac The `darwin` directory holds files specific to Mac builds. These may be customised and used as part of the build. To return these files to the default state, simply delete them and build with `wails build`. The directory contains the following files: - `Info.plist` - the main plist file used for Mac builds. It is used when building using `wails build`. - `Info.dev.plist` - same as the main plist file but used when building using `wails dev`. ## Windows The `windows` directory contains the manifest and rc files used when building with `wails build`. These may be customised for your application. To return these files to the default state, simply delete them and build with `wails build`. - `icon.ico` - The icon used for the application. This is used when building using `wails build`. If you wish to use a different icon, simply replace this file with your own. If it is missing, a new `icon.ico` file will be created using the `appicon.png` file in the build directory. - `installer/*` - The files used to create the Windows installer. These are used when building using `wails build`. - `info.json` - Application details used for Windows builds. The data here will be used by the Windows installer, as well as the application itself (right click the exe -> properties -> details) - `wails.exe.manifest` - The main application manifest file. ================================================ FILE: app/build/darwin/Info.dev.plist ================================================ CFBundlePackageType APPL CFBundleName {{.Info.ProductName}} CFBundleExecutable {{.Name}} CFBundleIdentifier com.wails.{{.Name}} CFBundleVersion {{.Info.ProductVersion}} CFBundleGetInfoString {{.Info.Comments}} CFBundleShortVersionString {{.Info.ProductVersion}} CFBundleIconFile iconfile LSMinimumSystemVersion 10.13.0 NSHighResolutionCapable true NSHumanReadableCopyright {{.Info.Copyright}} {{if .Info.FileAssociations}} CFBundleDocumentTypes {{range .Info.FileAssociations}} CFBundleTypeExtensions {{.Ext}} CFBundleTypeName {{.Name}} CFBundleTypeRole {{.Role}} CFBundleTypeIconFile {{.IconName}} {{end}} {{end}} {{if .Info.Protocols}} CFBundleURLTypes {{range .Info.Protocols}} CFBundleURLName com.wails.{{.Scheme}} CFBundleURLSchemes {{.Scheme}} CFBundleTypeRole {{.Role}} {{end}} {{end}} NSAppTransportSecurity NSAllowsLocalNetworking ================================================ FILE: app/build/darwin/Info.plist ================================================ CFBundlePackageType APPL CFBundleName {{.Info.ProductName}} CFBundleExecutable {{.Name}} CFBundleIdentifier com.github.Bronya0.{{.Name}} CFBundleVersion {{.Info.ProductVersion}} CFBundleGetInfoString {{.Info.Comments}} CFBundleShortVersionString {{.Info.ProductVersion}} CFBundleIconFile iconfile LSMinimumSystemVersion 10.13.0 NSHighResolutionCapable true NSHumanReadableCopyright {{.Info.Copyright}} {{if .Info.FileAssociations}} CFBundleDocumentTypes {{range .Info.FileAssociations}} CFBundleTypeExtensions {{.Ext}} CFBundleTypeName {{.Name}} CFBundleTypeRole {{.Role}} CFBundleTypeIconFile {{.IconName}} {{end}} {{end}} {{if .Info.Protocols}} CFBundleURLTypes {{range .Info.Protocols}} CFBundleURLName com.wails.{{.Scheme}} CFBundleURLSchemes {{.Scheme}} CFBundleTypeRole {{.Role}} {{end}} {{end}} ================================================ FILE: app/build/windows/info.json ================================================ { "fixed": { "file_version": "{{.Info.ProductVersion}}" }, "info": { "0000": { "ProductVersion": "{{.Info.ProductVersion}}", "CompanyName": "{{.Info.CompanyName}}", "FileDescription": "{{.Info.ProductName}}", "LegalCopyright": "{{.Info.Copyright}}", "ProductName": "{{.Info.ProductName}}", "Comments": "{{.Info.Comments}}" } } } ================================================ FILE: app/build/windows/installer/project.nsi ================================================ Unicode true #### ## Please note: Template replacements don't work in this file. They are provided with default defines like ## mentioned underneath. ## If the keyword is not defined, "wails_tools.nsh" will populate them with the values from ProjectInfo. ## If they are defined here, "wails_tools.nsh" will not touch them. This allows to use this project.nsi manually ## from outside of Wails for debugging and development of the installer. ## ## For development first make a wails nsis build to populate the "wails_tools.nsh": ## > wails build --target windows/amd64 --nsis ## Then you can call makensis on this file with specifying the path to your binary: ## For a AMD64 only installer: ## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app.exe ## For a ARM64 only installer: ## > makensis -DARG_WAILS_ARM64_BINARY=..\..\bin\app.exe ## For a installer with both architectures: ## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app-amd64.exe -DARG_WAILS_ARM64_BINARY=..\..\bin\app-arm64.exe #### ## The following information is taken from the ProjectInfo file, but they can be overwritten here. #### ## !define INFO_PROJECTNAME "MyProject" # Default "{{.Name}}" ## !define INFO_COMPANYNAME "MyCompany" # Default "{{.Info.CompanyName}}" ## !define INFO_PRODUCTNAME "MyProduct" # Default "{{.Info.ProductName}}" ## !define INFO_PRODUCTVERSION "1.0.0" # Default "{{.Info.ProductVersion}}" ## !define INFO_COPYRIGHT "Copyright" # Default "{{.Info.Copyright}}" ### ## !define PRODUCT_EXECUTABLE "Application.exe" # Default "${INFO_PROJECTNAME}.exe" ## !define UNINST_KEY_NAME "UninstKeyInRegistry" # Default "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}" #### ## !define REQUEST_EXECUTION_LEVEL "admin" # Default "admin" see also https://nsis.sourceforge.io/Docs/Chapter4.html #### ## Include the wails tools #### !include "wails_tools.nsh" # The version information for this two must consist of 4 parts VIProductVersion "${INFO_PRODUCTVERSION}.0" VIFileVersion "${INFO_PRODUCTVERSION}.0" VIAddVersionKey "CompanyName" "${INFO_COMPANYNAME}" VIAddVersionKey "FileDescription" "${INFO_PRODUCTNAME} Installer" VIAddVersionKey "ProductVersion" "${INFO_PRODUCTVERSION}" VIAddVersionKey "FileVersion" "${INFO_PRODUCTVERSION}" VIAddVersionKey "LegalCopyright" "${INFO_COPYRIGHT}" VIAddVersionKey "ProductName" "${INFO_PRODUCTNAME}" # Enable HiDPI support. https://nsis.sourceforge.io/Reference/ManifestDPIAware ManifestDPIAware true !include "MUI.nsh" !define MUI_ICON "..\icon.ico" !define MUI_UNICON "..\icon.ico" # !define MUI_WELCOMEFINISHPAGE_BITMAP "resources\leftimage.bmp" #Include this to add a bitmap on the left side of the Welcome Page. Must be a size of 164x314 !define MUI_FINISHPAGE_NOAUTOCLOSE # Wait on the INSTFILES page so the user can take a look into the details of the installation steps !define MUI_ABORTWARNING # This will warn the user if they exit from the installer. !insertmacro MUI_PAGE_WELCOME # Welcome to the installer page. # !insertmacro MUI_PAGE_LICENSE "resources\eula.txt" # Adds a EULA page to the installer !insertmacro MUI_PAGE_DIRECTORY # In which folder install page. !insertmacro MUI_PAGE_INSTFILES # Installing page. !insertmacro MUI_PAGE_FINISH # Finished installation page. !insertmacro MUI_UNPAGE_INSTFILES # Uinstalling page !insertmacro MUI_LANGUAGE "English" # Set the Language of the installer ## The following two statements can be used to sign the installer and the uninstaller. The path to the binaries are provided in %1 #!uninstfinalize 'signtool --file "%1"' #!finalize 'signtool --file "%1"' Name "${INFO_PRODUCTNAME}" OutFile "..\..\bin\${INFO_PROJECTNAME}-${ARCH}-installer.exe" # Name of the installer's file. InstallDir "$PROGRAMFILES64\${INFO_COMPANYNAME}\${INFO_PRODUCTNAME}" # Default installing folder ($PROGRAMFILES is Program Files folder). ShowInstDetails show # This will always show the installation details. Function .onInit !insertmacro wails.checkArchitecture FunctionEnd Section !insertmacro wails.setShellContext !insertmacro wails.webview2runtime SetOutPath $INSTDIR !insertmacro wails.files CreateShortcut "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" CreateShortCut "$DESKTOP\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" !insertmacro wails.associateFiles !insertmacro wails.associateCustomProtocols !insertmacro wails.writeUninstaller SectionEnd Section "uninstall" !insertmacro wails.setShellContext RMDir /r "$AppData\${PRODUCT_EXECUTABLE}" # Remove the WebView2 DataPath RMDir /r $INSTDIR Delete "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" Delete "$DESKTOP\${INFO_PRODUCTNAME}.lnk" !insertmacro wails.unassociateFiles !insertmacro wails.unassociateCustomProtocols !insertmacro wails.deleteUninstaller SectionEnd ================================================ FILE: app/build/windows/installer/wails_tools.nsh ================================================ # DO NOT EDIT - Generated automatically by `wails build` !include "x64.nsh" !include "WinVer.nsh" !include "FileFunc.nsh" !ifndef INFO_PROJECTNAME !define INFO_PROJECTNAME "demo-app" !endif !ifndef INFO_COMPANYNAME !define INFO_COMPANYNAME "demo-app" !endif !ifndef INFO_PRODUCTNAME !define INFO_PRODUCTNAME "demo-app" !endif !ifndef INFO_PRODUCTVERSION !define INFO_PRODUCTVERSION "1.0.0" !endif !ifndef INFO_COPYRIGHT !define INFO_COPYRIGHT "Copyright........." !endif !ifndef PRODUCT_EXECUTABLE !define PRODUCT_EXECUTABLE "${INFO_PROJECTNAME}.exe" !endif !ifndef UNINST_KEY_NAME !define UNINST_KEY_NAME "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}" !endif !define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINST_KEY_NAME}" !ifndef REQUEST_EXECUTION_LEVEL !define REQUEST_EXECUTION_LEVEL "admin" !endif RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}" !ifdef ARG_WAILS_AMD64_BINARY !define SUPPORTS_AMD64 !endif !ifdef ARG_WAILS_ARM64_BINARY !define SUPPORTS_ARM64 !endif !ifdef SUPPORTS_AMD64 !ifdef SUPPORTS_ARM64 !define ARCH "amd64_arm64" !else !define ARCH "amd64" !endif !else !ifdef SUPPORTS_ARM64 !define ARCH "arm64" !else !error "Wails: Undefined ARCH, please provide at least one of ARG_WAILS_AMD64_BINARY or ARG_WAILS_ARM64_BINARY" !endif !endif !macro wails.checkArchitecture !ifndef WAILS_WIN10_REQUIRED !define WAILS_WIN10_REQUIRED "This product is only supported on Windows 10 (Server 2016) and later." !endif !ifndef WAILS_ARCHITECTURE_NOT_SUPPORTED !define WAILS_ARCHITECTURE_NOT_SUPPORTED "This product can't be installed on the current Windows architecture. Supports: ${ARCH}" !endif ${If} ${AtLeastWin10} !ifdef SUPPORTS_AMD64 ${if} ${IsNativeAMD64} Goto ok ${EndIf} !endif !ifdef SUPPORTS_ARM64 ${if} ${IsNativeARM64} Goto ok ${EndIf} !endif IfSilent silentArch notSilentArch silentArch: SetErrorLevel 65 Abort notSilentArch: MessageBox MB_OK "${WAILS_ARCHITECTURE_NOT_SUPPORTED}" Quit ${else} IfSilent silentWin notSilentWin silentWin: SetErrorLevel 64 Abort notSilentWin: MessageBox MB_OK "${WAILS_WIN10_REQUIRED}" Quit ${EndIf} ok: !macroend !macro wails.files !ifdef SUPPORTS_AMD64 ${if} ${IsNativeAMD64} File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_AMD64_BINARY}" ${EndIf} !endif !ifdef SUPPORTS_ARM64 ${if} ${IsNativeARM64} File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_ARM64_BINARY}" ${EndIf} !endif !macroend !macro wails.writeUninstaller WriteUninstaller "$INSTDIR\uninstall.exe" SetRegView 64 WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "${INFO_COMPANYNAME}" WriteRegStr HKLM "${UNINST_KEY}" "DisplayName" "${INFO_PRODUCTNAME}" WriteRegStr HKLM "${UNINST_KEY}" "DisplayVersion" "${INFO_PRODUCTVERSION}" WriteRegStr HKLM "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${PRODUCT_EXECUTABLE}" WriteRegStr HKLM "${UNINST_KEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\"" WriteRegStr HKLM "${UNINST_KEY}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S" ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 IntFmt $0 "0x%08X" $0 WriteRegDWORD HKLM "${UNINST_KEY}" "EstimatedSize" "$0" !macroend !macro wails.deleteUninstaller Delete "$INSTDIR\uninstall.exe" SetRegView 64 DeleteRegKey HKLM "${UNINST_KEY}" !macroend !macro wails.setShellContext ${If} ${REQUEST_EXECUTION_LEVEL} == "admin" SetShellVarContext all ${else} SetShellVarContext current ${EndIf} !macroend # Install webview2 by launching the bootstrapper # See https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution#online-only-deployment !macro wails.webview2runtime !ifndef WAILS_INSTALL_WEBVIEW_DETAILPRINT !define WAILS_INSTALL_WEBVIEW_DETAILPRINT "Installing: WebView2 Runtime" !endif SetRegView 64 # If the admin key exists and is not empty then webview2 is already installed ReadRegStr $0 HKLM "SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" ${If} $0 != "" Goto ok ${EndIf} ${If} ${REQUEST_EXECUTION_LEVEL} == "user" # If the installer is run in user level, check the user specific key exists and is not empty then webview2 is already installed ReadRegStr $0 HKCU "Software\Microsoft\EdgeUpdate\Clients{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" ${If} $0 != "" Goto ok ${EndIf} ${EndIf} SetDetailsPrint both DetailPrint "${WAILS_INSTALL_WEBVIEW_DETAILPRINT}" SetDetailsPrint listonly InitPluginsDir CreateDirectory "$pluginsdir\webview2bootstrapper" SetOutPath "$pluginsdir\webview2bootstrapper" File "tmp\MicrosoftEdgeWebview2Setup.exe" ExecWait '"$pluginsdir\webview2bootstrapper\MicrosoftEdgeWebview2Setup.exe" /silent /install' SetDetailsPrint both ok: !macroend # Copy of APP_ASSOCIATE and APP_UNASSOCIATE macros from here https://gist.github.com/nikku/281d0ef126dbc215dd58bfd5b3a5cd5b !macro APP_ASSOCIATE EXT FILECLASS DESCRIPTION ICON COMMANDTEXT COMMAND ; Backup the previously associated file class ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" "" WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "${FILECLASS}_backup" "$R0" WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "${FILECLASS}" WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}" "" `${DESCRIPTION}` WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\DefaultIcon" "" `${ICON}` WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell" "" "open" WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open" "" `${COMMANDTEXT}` WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open\command" "" `${COMMAND}` !macroend !macro APP_UNASSOCIATE EXT FILECLASS ; Backup the previously associated file class ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" `${FILECLASS}_backup` WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "$R0" DeleteRegKey SHELL_CONTEXT `Software\Classes\${FILECLASS}` !macroend !macro wails.associateFiles ; Create file associations !macroend !macro wails.unassociateFiles ; Delete app associations !macroend !macro CUSTOM_PROTOCOL_ASSOCIATE PROTOCOL DESCRIPTION ICON COMMAND DeleteRegKey SHELL_CONTEXT "Software\Classes\${PROTOCOL}" WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}" "" "${DESCRIPTION}" WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}" "URL Protocol" "" WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\DefaultIcon" "" "${ICON}" WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell" "" "" WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell\open" "" "" WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell\open\command" "" "${COMMAND}" !macroend !macro CUSTOM_PROTOCOL_UNASSOCIATE PROTOCOL DeleteRegKey SHELL_CONTEXT "Software\Classes\${PROTOCOL}" !macroend !macro wails.associateCustomProtocols ; Create custom protocols associations !macroend !macro wails.unassociateCustomProtocols ; Delete app custom protocol associations !macroend ================================================ FILE: app/build/windows/wails.exe.manifest ================================================ true/pm permonitorv2,permonitor ================================================ FILE: app/dev.bat ================================================ chcp 65001 wails dev ================================================ FILE: app/frontend/index.html ================================================ wails-naive-demo
================================================ FILE: app/frontend/package.json ================================================ { "name": "app", "version": "1.0.0", "description": "", "main": "", "scripts": { "dev": "vite", "build": "vite build", "preview": "vite preview" }, "dependencies": { "ace-builds": "1.4.12", "highlight.js": "^11.10.0", "jsoneditor": "^10.1.0", "mitt": "^3.0.1", "vue": "^3.5.8" }, "devDependencies": { "@vicons/material": "^0.12.0", "@vitejs/plugin-vue": "^6.0.0", "naive-ui": "^2.39.0", "vite": "7.1.5" }, "keywords": [], "author": "bronya0" } ================================================ FILE: app/frontend/src/App.vue ================================================ ================================================ FILE: app/frontend/src/assets/fonts/OFL.txt ================================================ Copyright 2016 The Nunito Project Authors (contact@sansoxygen.com), This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL ----------------------------------------------------------- SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ----------------------------------------------------------- PREAMBLE The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. DEFINITIONS "Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. "Reserved Font Name" refers to any names specified as such after the copyright statement(s). "Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). "Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. "Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. PERMISSION & CONDITIONS Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. 2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. 3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. 5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. TERMINATION This license becomes null and void if any of the above conditions are not met. DISCLAIMER THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. ================================================ FILE: app/frontend/src/components/About.vue ================================================ ================================================ FILE: app/frontend/src/components/Aside.vue ================================================ ================================================ FILE: app/frontend/src/components/Conn.vue ================================================ ================================================ FILE: app/frontend/src/components/Core.vue ================================================ ================================================ FILE: app/frontend/src/components/Header.vue ================================================ ================================================ FILE: app/frontend/src/components/Health.vue ================================================ ================================================ FILE: app/frontend/src/components/Index.vue ================================================ ================================================ FILE: app/frontend/src/components/Nodes.vue ================================================ ================================================ FILE: app/frontend/src/components/Rest.vue ================================================ ================================================ FILE: app/frontend/src/components/Settings.vue ================================================ ================================================ FILE: app/frontend/src/components/Snapshot.vue ================================================ ================================================ FILE: app/frontend/src/components/Task.vue ================================================ ================================================ FILE: app/frontend/src/main.js ================================================ /* * Copyright 2025 Bronya0 . * Author Github: https://github.com/Bronya0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import {createApp} from 'vue' import naive from 'naive-ui' import App from './App.vue' const app = createApp(App) app.use(naive) app.mount('#app') ================================================ FILE: app/frontend/src/style.css ================================================ /* * Copyright 2025 Bronya0 . * Author Github: https://github.com/Bronya0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ html { background-color: rgba(27, 38, 54, 1); text-align: center; color: white; } body { margin: 0; color: white; font-family: "Nunito", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; } @font-face { font-family: "Nunito"; font-style: normal; font-weight: 400; src: local(""), url("assets/fonts/nunito-v16-latin-regular.woff2") format("woff2"); } #app { height: 100vh; text-align: center; } h2 { text-align: left } ================================================ FILE: app/frontend/src/utils/common.js ================================================ /* * Copyright 2025 Bronya0 . * Author Github: https://github.com/Bronya0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // 渲染图标给菜单 import {h} from "vue"; import {NIcon} from "naive-ui"; import {BrowserOpenURL} from "../../wailsjs/runtime"; // 渲染图标 export function renderIcon(icon) { return () => h(NIcon, null, {default: () => h(icon)}); } // 打开链接 export function openUrl(url) { BrowserOpenURL(url) } // 压扁json export function flattenObject(obj, parentKey = '') { let flatResult = {}; for (let key in obj) { if (obj.hasOwnProperty(key)) { let newKey = parentKey ? `${parentKey}.${key}` : key; if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) { // 如果当前值也是一个对象,则递归调用 Object.assign(flatResult, flattenObject(obj[key], newKey)); } else { // 否则直接赋值 flatResult[newKey] = obj[key]; } } } return flatResult; } // 格式化的 JSON 字符串 export function formattedJson(value) { if (!value) return '' return JSON.stringify(value, null, 1) } // 验证json export function isValidJson(jsonString) { try { // 尝试解析 JSON 字符串 JSON.parse(jsonString); return true; // 解析成功,是有效的 JSON } catch (error) { // 解析失败,不是有效的 JSON return false; } } // 单位处理 export function formatBytes(bytes, decimals = 2) { if (bytes === 0) return '0 Bytes'; if (bytes === null) return ''; const k = 1024; const dm = decimals < 0 ? 0 : decimals; const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; } // 创建 CSV 内容的函数 export function createCsvContent(allData, columns) { // 过滤掉没有 title 的列 columns = columns.filter(col => col.title !== undefined); const headers = columns.map(col => col.title).join(','); const rows = allData.map(row => columns.map(col => { // 如果定义了自定义的 getCsvValue 函数,则使用它 if (col.getCsvValue) { return `"${col.getCsvValue(row)}"`; // 加上引号防止内容中的逗号导致换列 } // 否则,使用默认的 key 来取值 const value = row[col.key]; // 对于可能包含逗号的值,最好也用引号包起来 return typeof value === 'string' ? `"${value}"` : value; }).join(',') ).join('\n'); return `${headers}\n${rows}`; } // 下载件的函数,csv type:'text/csv;charset=utf-8;' export function download_file(content, fileName, type) { const blob = new Blob([content], {type: type}) const link = document.createElement('a') if (link.download !== undefined) { const url = URL.createObjectURL(blob) link.setAttribute('href', url) link.setAttribute('download', fileName) link.style.visibility = 'hidden' document.body.appendChild(link) link.click() document.body.removeChild(link) } } // 日期格式化函数 export function formatDate(date) { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); const hours = String(date.getHours()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0'); const seconds = String(date.getSeconds()).padStart(2, '0'); return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; } export function formatTimestamp(timestamp) { return new Date(timestamp).toLocaleString() } /** * 智能处理表格列配置 * minWidth 不会主动影响列的初始宽度,它只是作为拖拽调整时的最小宽度限制。 * 初始宽度由以下优先级决定: * width > 内容自适应宽度 > 表格容器的等分分配。 * 1. 自动计算未配置 minWidth 的列(基于 title 长度) * 2. 默认允许拖动(除非显式禁用) * 3. 自动添加 ellipsis 省略效果 * 4. 当允许拖动时,移除默认 width 配置(避免冲突) */ export function refColumns(columns) { return columns.map((column) => { // 深拷贝原始列配置(避免修改原对象) const processed = { ...column }; if (processed.type === 'selection'){ return processed; } if ("_ignore" in processed){ delete processed._ignore; return processed; } if (!('sorter' in processed)) { processed.sorter = 'default'; } // ===== 处理 resizable ===== if (!('resizable' in processed)) { processed.resizable = true; // 默认允许拖动 } if (processed.resizable){ // ===== 处理 minWidth ===== if (!('width' in processed)) { processed.width = calculateWidthByTitle(processed.title); } } // ===== 处理 ellipsis ===== if (!('ellipsis' in processed)) { processed.ellipsis = { tooltip: { scrollable: true, style: { maxWidth: '800px' } // 自定义提示框最大宽度 } }; } return processed; }); } /** * 根据标题文本计算建议宽度(中英文混合) */ export function calculateWidthByTitle(title) { let width = 0; for (const char of title) { width += /[\u4e00-\u9fa5]/.test(char) ? 16 : 8; // 中文16px,英文8px } return Math.max(width + 24, 80); // 加 padding 且不低于 80px } /** * 格式化毫秒为可读时长 */ export function formatMillis(ms) { if (!+ms) return '0s'; const seconds = Math.floor(ms / 1000); const d = Math.floor(seconds / (3600 * 24)); const h = Math.floor((seconds % (3600 * 24)) / 3600); const m = Math.floor((seconds % 3600) / 60); const s = Math.floor(seconds % 60); let result = ''; if (d > 0) result += `${d}d `; if (h > 0) result += `${h}h `; if (m > 0) result += `${m}m `; if (s > 0 || result === '') result += `${s}s`; return result.trim(); } export function formatMillisToDays(ms){ if (!+ms) return '0天'; const days = Math.floor(ms / (1000 * 60 * 60 * 24)); return `${days}天`; } /** * 格式化大数字(加逗号) */ export function formatNumber(num) { return num?.toLocaleString() ?? '0'; } ================================================ FILE: app/frontend/src/utils/eventBus.js ================================================ /* * Copyright 2025 Bronya0 . * Author Github: https://github.com/Bronya0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // eventBus.js import mitt from 'mitt' const emitter = mitt() export default emitter ================================================ FILE: app/frontend/vite.config.js ================================================ /* * Copyright 2025 Bronya0 . * Author Github: https://github.com/Bronya0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import {defineConfig} from 'vite' import vue from '@vitejs/plugin-vue' // https://vitejs.dev/config/ export default defineConfig({ server: { port: 5174, }, plugins: [vue()] }) ================================================ FILE: app/go.mod ================================================ module app go 1.22.0 toolchain go1.24.0 require ( github.com/go-resty/resty/v2 v2.16.2 github.com/wailsapp/wails/v2 v2.11.0 gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/bep/debounce v1.2.1 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect github.com/labstack/echo/v4 v4.13.3 // indirect github.com/labstack/gommon v0.4.2 // indirect github.com/leaanthony/go-ansi-parser v1.6.1 // indirect github.com/leaanthony/gosod v1.0.4 // indirect github.com/leaanthony/slicer v1.6.0 // indirect github.com/leaanthony/u v1.1.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pkg/errors v0.9.1 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/samber/lo v1.49.1 // indirect github.com/tkrajina/go-reflector v0.5.8 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect github.com/wailsapp/go-webview2 v1.0.22 // indirect github.com/wailsapp/mimetype v1.4.1 // indirect golang.org/x/crypto v0.33.0 // indirect golang.org/x/net v0.35.0 // indirect golang.org/x/sys v0.30.0 // indirect golang.org/x/text v0.22.0 // indirect ) // replace github.com/wailsapp/wails/v2 v2.9.2 => F:\coding\go\pkg\mod ================================================ FILE: app/go.sum ================================================ github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-resty/resty/v2 v2.16.2 h1:CpRqTjIzq/rweXUt9+GxzzQdlkqMdt8Lm/fuK/CAbAg= github.com/go-resty/resty/v2 v2.16.2/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck= github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY= github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc= github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA= github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A= github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= github.com/leaanthony/gosod v1.0.4 h1:YLAbVyd591MRffDgxUOU1NwLhT9T1/YiwjKZpkNFeaI= github.com/leaanthony/gosod v1.0.4/go.mod h1:GKuIL0zzPj3O1SdWQOdgURSuhkF+Urizzxh26t9f1cw= github.com/leaanthony/slicer v1.6.0 h1:1RFP5uiPJvT93TAHi+ipd3NACobkW53yUiBqZheE/Js= github.com/leaanthony/slicer v1.6.0/go.mod h1:o/Iz29g7LN0GqH3aMjWAe90381nyZlDNquK+mtH2Fj8= github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M= github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew= github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tkrajina/go-reflector v0.5.8 h1:yPADHrwmUbMq4RGEyaOUpz2H90sRsETNVpjzo3DLVQQ= github.com/tkrajina/go-reflector v0.5.8/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/wailsapp/go-webview2 v1.0.22 h1:YT61F5lj+GGaat5OB96Aa3b4QA+mybD0Ggq6NZijQ58= github.com/wailsapp/go-webview2 v1.0.22/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc= github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= github.com/wailsapp/wails/v2 v2.11.0 h1:seLacV8pqupq32IjS4Y7V8ucab0WZwtK6VvUVxSBtqQ= github.com/wailsapp/wails/v2 v2.11.0/go.mod h1:jrf0ZaM6+GBc1wRmXsM8cIvzlg0karYin3erahI4+0k= golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= ================================================ FILE: app/main.go ================================================ /* * Copyright 2025 Bronya0 . * Author Github: https://github.com/Bronya0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package main import ( "app/backend/common" "app/backend/config" "app/backend/service" "app/backend/system" "context" "embed" "fmt" "github.com/wailsapp/wails/v2" "github.com/wailsapp/wails/v2/pkg/options" "github.com/wailsapp/wails/v2/pkg/options/assetserver" "github.com/wailsapp/wails/v2/pkg/options/linux" "github.com/wailsapp/wails/v2/pkg/options/mac" "github.com/wailsapp/wails/v2/pkg/options/windows" ) // 在开发模式下使用 wails dev 命令,资产从磁盘加载,任何更改都会导致“实时重新加载”。 资产的位置将从 embed.FS 推断。 // //go:embed frontend/dist var assets embed.FS //go:embed build/appicon.png var icon []byte func main() { app := NewApp() appConfig := &config.AppConfig{} configInfo := appConfig.GetConfig() update := &system.Update{} esService := service.NewESService() // 主应用程序由对 wails.Run() 的调用组成。 它接受描述应用程序窗口大小、窗口标题、要使用的资源等应用程序配置 // 完整说明:https://wails.io/zh-Hans/docs/reference/options/ err := wails.Run(&options.App{ Title: common.AppName, Width: configInfo.Width, Height: configInfo.Height, //MinWidth: 1024, //MinHeight: 768, //MaxWidth: 1440, //MaxHeight: 920, //DisableResize: false, Frameless: true, //无边框 //HideWindowOnClose: false, //关闭时隐藏窗口 BackgroundColour: &options.RGBA{R: 0, G: 0, B: 0}, AssetServer: &assetserver.Options{ Assets: assets, }, Menu: nil, //EnableDefaultContextMenu: true, //Logger: nil, //LogLevel: logger.DEBUG, //OnStartup 此回调在前端创建之后调用,但在 index.html 加载之前调用。 它提供了应用程序上下文。 // 传递 ctx OnStartup: func(ctx context.Context) { app.Start(ctx) appConfig.Start(ctx) update.Start(ctx) }, //在前端加载完毕 index.html 及其资源后调用此回调 OnDomReady: app.domReady, //在前端被销毁之后,应用程序终止之前,调用此回调。 它提供了应用程序上下文。 OnBeforeClose: app.beforeClose, //应用关闭前回调 OnShutdown: app.shutdown, //WindowStartState: options.Normal, //指定向前端暴露哪些结构体方法 Bind: []any{ app, appConfig, update, esService, }, Windows: &windows.Options{ WebviewIsTransparent: false, WindowIsTranslucent: false, DisableFramelessWindowDecorations: false, ResizeDebounceMS: 2, }, Linux: &linux.Options{ ProgramName: common.AppName, Icon: icon, WebviewGpuPolicy: linux.WebviewGpuPolicyOnDemand, WindowIsTranslucent: true, }, // Mac platform specific options Mac: &mac.Options{ TitleBar: mac.TitleBarHiddenInset(), About: &mac.AboutInfo{ Title: fmt.Sprintf("%s %s", common.AppName, common.Version), Message: "", Icon: icon, }, WebviewIsTransparent: false, WindowIsTranslucent: false, }, }) if err != nil { appConfig.LogErrToFile(err.Error()) } } ================================================ FILE: app/wails.json ================================================ { "name": "ES-King", "outputfilename": "ES-King", "frontend:install": "npm install", "frontend:build": "npm run build", "frontend:dev:watcher": "npm run dev", "frontend:dev:serverUrl": "http://localhost:5174", "author": { "name": "bronya0", "email": "tangssst@qq.com" }, "info": { "companyName": "bronya0", "productName": "ES-King", "productVersion": "1.0.0", "copyright": "Copyright © 2024", "comments": "comments" } } ================================================ FILE: readme-en.md ================================================ ![](docs/snap/2.png)

ES-King

简体中文 | English

![License](https://img.shields.io/github/license/Bronya0/ES-King) ![GitHub release](https://img.shields.io/github/release/Bronya0/ES-King) ![GitHub All Releases](https://img.shields.io/github/downloads/Bronya0/ES-King/total) ![GitHub stars](https://img.shields.io/github/stars/Bronya0/ES-King) ![GitHub forks](https://img.shields.io/github/forks/Bronya0/ES-King) A modern, practical, lightweight ES GUI client that supports multiple platforms and the installation package is less than 10mb.
The same Kafka client has been developed and has been downloaded by more than a thousand people: [Kafka-King](https:// github.com/Bronya0/Kafka-King) If you need to raise requirements, bugs and improvement suggestions, please raise an issue. Click a star to support the author's hard work in open source. Thank you❤❤ Join the group and communicate with the author: R&D technical exchange group: 964440643 # Function list - Detailed cluster information: node information, heap memory usage, total memory usage, cpu usage, disk usage, network traffic, node role, cluster Health, 5-minute load, field cache per node, segment cache, query cache, request cache, total number of segments metrics - Metrics: total number of active shards, number of shards being initialized, number of delayed unallocated shards (possibly because allocation strategy waiting conditions are not met), percentage of active shards (possibly frozen, closed, faulty, etc.) - Index index, document index, memory index, node index, storage index, segment index... - Support cluster viewing- Support index search, management, and export csv - Support index operations: index management, sample viewing of 10 document contents, index alias, View index settings, refresh index, merge index segments, delete index, close or open index, flush index, clean index cache... - Comes with rest window (of course you can use postman if you like) # Download [Download address](https ://github.com/Bronya0/ES-King/releases), click [Assets], and choose the platform of your office computer to download. It supports Windows, MacOS, and Linux. # Screenshot ![](docs/snap/1.png) ![](docs/snap/3.png) ![](docs/snap/4.png) ![](docs/snap/5.png) # Build is only needed to study the source code. Install wails, refer to: https://wails.io/docs/gettingstarted/installation ``` cd app wails dev ``` # Star [![Stargazers over time](https:/ /starchart.cc/Bronya0/ES-King.svg)](https://starchart.cc/Bronya0/ES-King) # Thanks - wails: https://wails.io/docs/gettingstarted/installation - naive ui : https://www.naiveui.com/ ================================================ FILE: readme.md ================================================

图片标题

ES-King

简体中文 | English

![License](https://img.shields.io/github/license/Bronya0/ES-King) ![GitHub release](https://img.shields.io/github/release/Bronya0/ES-King) ![GitHub All Releases](https://img.shields.io/github/downloads/Bronya0/ES-King/total) ![GitHub stars](https://img.shields.io/github/stars/Bronya0/ES-King) ![GitHub forks](https://img.shields.io/github/forks/Bronya0/ES-King) 一个现代、实用、轻量的ES GUI客户端,支持多平台,安装包不到10mb。
让ES更好用,make es great again! 该桌面软件用于操作、查询ES,适配各大桌面系统(除了win7),通信方式为REST API,一般无ES版本兼容问题。通过集成大量监控指标、索引操作、优化过的便捷查询界面,提升ES的使用体验和效率。 如需提出需求、bug和改进建议,请提issue。 点个star支持作者辛苦开源 谢谢❤❤ 加群和作者一起交流: 研发技术交流群:964440643 **同款Kafka客户端,已有上万人下载**:[Kafka-King](https://github.com/Bronya0/Kafka-King) **使用&开发文档(AI生成)**:[https://zread.ai/Bronya0/ES-King](https://zread.ai/Bronya0/ES-King) # 功能清单 - 详尽的集群信息:节点信息、堆内存占用、总内存占用、cpu占用、磁盘占用、网络流量、节点角色、集群健康、5分钟负载、每个节点的字段缓存、段缓存、查询缓存、请求缓存、段总数指标 - 指标查看:活跃的分片总数、初始化中的分片数量、延迟未分配的分片数量量(可能因为分配策略等待条件未满足)、活跃分片占比 (可能冻结、关闭、故障等) - 索引指标、文档指标、内存指标、节点指标、存储指标、段指标…… - 支持集群查看 - 支持索引搜索、管理,导出csv - 支持索引操作:索引管理、抽样查看10条文档内容、索引别名、索引设置查看、索引刷新、索引段合并、删除索引、关闭or打开索引、flush索引、清理索引缓存…… - 自带rest窗口(当然你喜欢也可以自己用postman),自动存储历史查询,一键恢复,查询结果支持搜索导出;支持es dsl的关键字悬浮提示 - 支持索引备份下载到本地 # 下载 [下载地址](https://github.com/Bronya0/ES-King/releases),点击【Assets】,选择自己办公电脑的平台下载,支持windows、macos、linux。 # 截图 ![2025-04-25_16-26-00](https://github.com/user-attachments/assets/45284be3-bc18-49f2-bc77-95729735a31a) ![](docs/snap/1.png) ![](docs/snap/3.png) ![](docs/snap/4.png) ![](docs/snap/5.png) # 捐赠 有条件可以请作者喝杯咖啡,支持项目发展,感谢💕 ![image](https://github.com/user-attachments/assets/da6d46da-4e24-41e3-843d-495c6cd32065) # 参与开发 安装golang、node.js、npm,运行 go install github.com/wailsapp/wails/v2/cmd/wails@latest 安装 Wails CLI。 ``` cd app wails dev ``` # 星 [![Stargazers over time](https://starchart.cc/Bronya0/ES-King.svg)](https://starchart.cc/Bronya0/ES-King) # 感谢 - wails:https://wails.io/docs/gettingstarted/installation - naive ui:https://www.naiveui.com/