├── .eslintrc-auto-import.json ├── .eslintrc.cjs ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── CONTRIBUTION.md │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── node.yml │ └── release.yml ├── .gitignore ├── .husky └── pre-commit ├── .nvmrc ├── .prettierrc ├── CONTRIBUTION.md ├── LICENSE ├── README.md ├── app-icon.png ├── auto-import.d.ts ├── auto-imports.d.ts ├── components.d.ts ├── docs ├── dockit-actions.md ├── images │ ├── wechat_official.png │ └── wechat_ponsor.jpg ├── index.md └── privacy-policy.md ├── eslint.config.js ├── index.html ├── jest.config.cjs ├── package-lock.json ├── package.json ├── public ├── client-ui.png └── dockit.png ├── scripts ├── build-pkg.sh └── collect-binaries.sh ├── src-tauri ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── build.rs ├── capabilities │ └── desktop.json ├── icons │ ├── 128x128.png │ ├── 128x128@2x.png │ ├── 32x32.png │ ├── Square107x107Logo.png │ ├── Square142x142Logo.png │ ├── Square150x150Logo.png │ ├── Square284x284Logo.png │ ├── Square30x30Logo.png │ ├── Square310x310Logo.png │ ├── Square44x44Logo.png │ ├── Square71x71Logo.png │ ├── Square89x89Logo.png │ ├── StoreLogo.png │ ├── icon.icns │ ├── icon.ico │ └── icon.png ├── src │ ├── common │ │ ├── http_client.rs │ │ ├── json_utils.rs │ │ └── mod.rs │ ├── dynamo │ │ ├── create_item.rs │ │ ├── describe_table.rs │ │ ├── mod.rs │ │ ├── query_table.rs │ │ ├── scan_table.rs │ │ └── types.rs │ ├── dynamo_client.rs │ ├── fetch_client.rs │ ├── main.rs │ ├── menu.rs │ └── openai_client.rs └── tauri.conf.json ├── src ├── App.vue ├── assets │ ├── img │ │ ├── theme-auto.png │ │ ├── theme-dark.png │ │ └── theme-light.png │ ├── styles │ │ ├── normalize.css │ │ └── theme.scss │ ├── svg │ │ ├── dynamoDB.svg │ │ └── elasticsearch.svg │ └── theme │ │ └── naive-theme-overrides.ts ├── common │ ├── base64.ts │ ├── crypto.ts │ ├── customError.ts │ ├── debounceThrottle.ts │ ├── debug.ts │ ├── index.ts │ ├── jsonify.ts │ ├── monaco │ │ ├── completion.ts │ │ ├── dsql │ │ │ ├── compoundQueries.ts │ │ │ ├── fullTextQueries.ts │ │ │ ├── geoQueries.ts │ │ │ ├── index.ts │ │ │ ├── joiningQueries.ts │ │ │ ├── matchAllQueries.ts │ │ │ ├── shapeQueries.ts │ │ │ ├── spanQueries.ts │ │ │ ├── specializedQueries.ts │ │ │ ├── termLevelQueries.ts │ │ │ └── vectorQueries.ts │ │ ├── environment.ts │ │ ├── index.ts │ │ ├── keywords.ts │ │ ├── lexerRules.ts │ │ ├── referDoc.ts │ │ ├── tokenlizer.ts │ │ └── type.ts │ ├── pureObject.ts │ ├── requestUtil.ts │ └── valueConversion.ts ├── components │ ├── AppProvider.vue │ ├── RouterMain.vue │ ├── VersionDetect.vue │ ├── markdown-render.vue │ ├── path-breadcrumb.vue │ └── tool-bar.vue ├── datasources │ ├── ApiClients.ts │ ├── chatBotApi.ts │ ├── dynamoApi.ts │ ├── esApi.ts │ ├── fetchApi.ts │ ├── index.ts │ ├── sourceFileApi.ts │ └── storeApi.ts ├── lang │ ├── enUS.ts │ ├── index.ts │ └── zhCN.ts ├── layout │ ├── components │ │ ├── chatbot-box.vue │ │ ├── the-aside-icon.vue │ │ ├── the-aside.vue │ │ └── tool-bar-right.vue │ └── index.vue ├── main.ts ├── router │ └── index.ts ├── store │ ├── appStore.ts │ ├── backupRestoreStore.ts │ ├── chatStore.ts │ ├── clusterManageStore.ts │ ├── connectionStore.ts │ ├── fileStore.ts │ ├── index.ts │ ├── tabStore.ts │ └── userStore.ts ├── views │ ├── backup-restore │ │ ├── components │ │ │ ├── backup.vue │ │ │ └── restore.vue │ │ └── index.vue │ ├── connect │ │ ├── components │ │ │ ├── connect-list.vue │ │ │ ├── dynamodb-connect-dialog.vue │ │ │ ├── es-connect-dialog.vue │ │ │ └── floating-menu.vue │ │ └── index.vue │ ├── editor │ │ ├── dynamo-editor │ │ │ ├── components │ │ │ │ ├── create-item.vue │ │ │ │ ├── sql-editor.vue │ │ │ │ └── ui-editor.vue │ │ │ └── index.vue │ │ └── es-editor │ │ │ ├── display-editor.vue │ │ │ └── index.vue │ ├── file │ │ ├── components │ │ │ ├── context-menu.vue │ │ │ ├── file-list.vue │ │ │ ├── new-file-dialog.vue │ │ │ └── tool-bar.vue │ │ └── index.vue │ ├── history │ │ ├── components │ │ │ └── history-empty.vue │ │ └── index.vue │ ├── login │ │ └── index.vue │ ├── manage │ │ ├── components │ │ │ ├── alias-dialog.vue │ │ │ ├── cluster-state.vue │ │ │ ├── index-dialog.vue │ │ │ ├── index-manage.vue │ │ │ ├── node-state.vue │ │ │ ├── shard-manage.vue │ │ │ ├── switch-alias-dialog.vue │ │ │ └── template-dialog.vue │ │ └── index.vue │ └── setting │ │ ├── components │ │ ├── about-us.vue │ │ ├── aigc.vue │ │ └── basic.vue │ │ └── index.vue └── vite-env.d.ts ├── tests ├── common │ └── jsonify.test.ts ├── fixtures │ └── index.ts └── index.test.ts ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /.eslintrc-auto-import.json: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "Component": true, 4 | "ComponentPublicInstance": true, 5 | "ComputedRef": true, 6 | "EffectScope": true, 7 | "ExtractDefaultPropTypes": true, 8 | "ExtractPropTypes": true, 9 | "ExtractPublicPropTypes": true, 10 | "InjectionKey": true, 11 | "PropType": true, 12 | "Ref": true, 13 | "VNode": true, 14 | "WritableComputedRef": true, 15 | "computed": true, 16 | "createApp": true, 17 | "customRef": true, 18 | "defineAsyncComponent": true, 19 | "defineComponent": true, 20 | "effectScope": true, 21 | "getCurrentInstance": true, 22 | "getCurrentScope": true, 23 | "h": true, 24 | "inject": true, 25 | "isProxy": true, 26 | "isReactive": true, 27 | "isReadonly": true, 28 | "isRef": true, 29 | "markRaw": true, 30 | "nextTick": true, 31 | "onActivated": true, 32 | "onBeforeMount": true, 33 | "onBeforeRouteLeave": true, 34 | "onBeforeRouteUpdate": true, 35 | "onBeforeUnmount": true, 36 | "onBeforeUpdate": true, 37 | "onDeactivated": true, 38 | "onErrorCaptured": true, 39 | "onMounted": true, 40 | "onRenderTracked": true, 41 | "onRenderTriggered": true, 42 | "onScopeDispose": true, 43 | "onServerPrefetch": true, 44 | "onUnmounted": true, 45 | "onUpdated": true, 46 | "provide": true, 47 | "reactive": true, 48 | "readonly": true, 49 | "ref": true, 50 | "resolveComponent": true, 51 | "shallowReactive": true, 52 | "shallowReadonly": true, 53 | "shallowRef": true, 54 | "toRaw": true, 55 | "toRef": true, 56 | "toRefs": true, 57 | "toValue": true, 58 | "triggerRef": true, 59 | "unref": true, 60 | "useAttrs": true, 61 | "useCssModule": true, 62 | "useCssVars": true, 63 | "useDialog": true, 64 | "useLink": true, 65 | "useLoadingBar": true, 66 | "useMessage": true, 67 | "useNotification": true, 68 | "useRoute": true, 69 | "useRouter": true, 70 | "useSlots": true, 71 | "watch": true, 72 | "watchEffect": true, 73 | "watchPostEffect": true, 74 | "watchSyncEffect": true, 75 | "DirectiveBinding": true, 76 | "MaybeRef": true, 77 | "MaybeRefOrGetter": true, 78 | "Slot": true, 79 | "Slots": true, 80 | "onWatcherCleanup": true, 81 | "useId": true, 82 | "useModel": true, 83 | "useTemplateRef": true 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | node: true, 4 | es2022: true, 5 | }, 6 | extends: [ 7 | 'plugin:vue/base', 8 | 'eslint:recommended', 9 | 'plugin:vue/vue3-recommended', 10 | 'plugin:@typescript-eslint/recommended', 11 | 'plugin:prettier/recommended', 12 | './.eslintrc-auto-import.json', 13 | ], 14 | parser: 'vue-eslint-parser', 15 | parserOptions: { 16 | parser: '@typescript-eslint/parser', 17 | ecmaVersion: 'latest', 18 | sourceType: 'module', 19 | }, 20 | plugins: ['@typescript-eslint', 'prettier', 'eslint-plugin-vue'], 21 | rules: { 22 | 'prettier/prettier': 'error', 23 | 'vue/multi-word-component-names': 'off', 24 | 'arrow-parens': [2, 'as-needed'], 25 | 'arrow-spacing': [ 26 | 2, 27 | { 28 | before: true, 29 | after: true, 30 | }, 31 | ], 32 | 'key-spacing': [ 33 | 2, 34 | { 35 | beforeColon: false, 36 | afterColon: true, 37 | }, 38 | ], 39 | 'no-var': 'error', 40 | 'no-console': 'error', 41 | 'no-debugger': process.env === 'development' ? 'warn' : 'error', 42 | }, 43 | ignores: ['node_modules', 'dist', 'index.html', 'src-tauri/target', 'coverage'], 44 | }; 45 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: geek-fun 4 | patreon: # 5 | open_collective: # 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | custom: # Replace with a single custom sponsorship URL 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/CONTRIBUTION.md: -------------------------------------------------------------------------------- 1 | # Contributing to Dockit 2 | 3 | ## Introduction 4 | First off, thank you for considering contributing to Dockit! It's people like you that make this project great. 5 | 6 | ## How to Contribute 7 | 1. Fork the repository. 8 | 2. Create a new branch (`git checkout -b [feat|doc|fix|refactor]/your-branch`). 9 | 3. Make your changes. 10 | 4. Commit your changes (`git commit -am 'Add new feature'`). 11 | 5. Push to the branch (`git push origin feature-branch`). 12 | 6. Create a new Pull Request. 13 | 14 | ## Code of Conduct 15 | Please read our [Code of Conduct](CODE_OF_CONDUCT.md) to understand the expectations for behavior when contributing to this project. 16 | 17 | ## Reporting Issues 18 | If you find a bug, please create an issue using the [bug report template](.github/ISSUE_TEMPLATE/bug_report.md). Provide as much detail as possible to help us understand and resolve the issue. 19 | 20 | ## Submitting Pull Requests 21 | - Ensure your code follows the project's coding standards. 22 | - Write clear, concise commit messages. 23 | - Include tests for any new functionality. 24 | - Ensure all tests pass before submitting your pull request. 25 | 26 | ## Coding Standards 27 | - Follow the typescript style Guide and pass all syle checkes 28 | - Use [Prettier](https://prettier.io/) for code formatting. 29 | - Ensure your code passes linting (`npm run lint:check`). 30 | 31 | ## Running Tests 32 | - Run tests with `npm test`. 33 | - Ensure all tests pass before submitting your pull request. 34 | 35 | ## Additional Resources 36 | - [Project Documentation](https://dockit.geekfun.club) 37 | - [GitHub Repository](https://github.com/geek-fun/dockit) -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG] " 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **Desktop (please complete the following information):** 14 | - OS: [e.g. macOS] 15 | - Server [e.g. elasticsearch 8.1, safari] 16 | - Version [e.g. 0.4.2] 17 | 18 | **Reproduce Steps** 19 | Steps to reproduce the behavior: 20 | 1. Go to '...' 21 | 2. Click on '....' 22 | 3. Scroll down to '....' 23 | 4. See error 24 | 25 | **Expected behavior** 26 | A clear and concise description of what you expected to happen. 27 | 28 | **Screenshots** 29 | If applicable, add screenshots to help explain your problem. 30 | 31 | **Additional context** 32 | Add any other context about the problem here. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[FEATURE] " 5 | labels: feature 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | feat|fix|refactor: summary title of the PR 2 | 3 | Introduce a request id and a reference to latest request. Dismiss 4 | incoming responses other than from latest request. 5 | 6 | Remove timeouts which were used to mitigate the racing issue but are 7 | obsolete now. 8 | 9 | Refs: #123 10 | -------------------------------------------------------------------------------- /.github/workflows/node.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | run-name: ${{ github.event.head_commit.message }} 3 | 4 | on: 5 | pull_request: 6 | branches: [master] 7 | 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.ref }} 10 | cancel-in-progress: true 11 | 12 | jobs: 13 | build: 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | os: [macos-latest, ubuntu-latest, windows-latest] 18 | node-version: [20.x] 19 | 20 | runs-on: ${{ matrix.os }} 21 | timeout-minutes: 20 22 | 23 | steps: 24 | - name: Github checkout 25 | uses: actions/checkout@v4 26 | - name: install Rust stable 27 | uses: dtolnay/rust-toolchain@stable 28 | if: matrix.os == 'ubuntu-latest' 29 | - name: Use Node.js ${{ matrix.node-version }} 30 | uses: actions/setup-node@v4 31 | with: 32 | node-version: ${{ matrix.node-version }} 33 | cache: 'npm' 34 | - name: install dependencies (ubuntu only) 35 | if: matrix.os == 'ubuntu-latest' 36 | run: | 37 | echo "deb http://gb.archive.ubuntu.com/ubuntu jammy main" | sudo tee -a /etc/apt/sources.list 38 | sudo apt-get update 39 | sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf 40 | - name: install frontend dependencies 41 | run: npm ci 42 | - run: npm run lint:check 43 | - run: npm audit --audit-level=critical 44 | - run: npm run test:ci 45 | - name: Upload coverage reports to Codecov 46 | uses: codecov/codecov-action@v3 47 | env: 48 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 49 | - run: npm run build 50 | - uses: tauri-apps/tauri-action@v0 51 | env: 52 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 53 | with: 54 | includeRelease: false 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | .DS_Store 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # TypeScript cache 43 | *.tsbuildinfo 44 | 45 | # Optional npm cache directory 46 | .npm 47 | 48 | # Optional eslint cache 49 | .eslintcache 50 | 51 | # Optional REPL history 52 | .node_repl_history 53 | 54 | # Output of 'npm pack' 55 | *.tgz 56 | 57 | # Yarn Integrity file 58 | .yarn-integrity 59 | 60 | # dotenv environment variables file 61 | .env 62 | .env.test 63 | 64 | # parcel-bundler cache (https://parceljs.org/) 65 | .cache 66 | 67 | # next.js build output 68 | .next 69 | 70 | # nuxt.js build output 71 | .nuxt 72 | 73 | # vuepress build output 74 | .vuepress/dist 75 | 76 | # Serverless directories 77 | .serverless/ 78 | 79 | # FuseBox cache 80 | .fusebox/ 81 | 82 | # DynamoDB Local files 83 | .dynamodb/ 84 | 85 | # Webpack 86 | .webpack/ 87 | 88 | # Vite 89 | .vite/ 90 | 91 | # Electron-Forge 92 | out/ 93 | data/ 94 | /.idea/ 95 | /dist/ 96 | /.vscode/ 97 | # Logs 98 | node_modules 99 | dist 100 | dist-ssr 101 | *.local 102 | 103 | # Editor directories and files 104 | .vscode/* 105 | !.vscode/extensions.json 106 | .idea 107 | *.iml 108 | *.suo 109 | *.ntvs* 110 | *.njsproj 111 | *.sln 112 | *.sw? 113 | /artifacts/ 114 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npm run lint:check 5 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v20.15.0 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "printWidth": 100, 4 | "tabWidth": 2, 5 | "useTabs": false, 6 | "semi": true, 7 | "bracketSpacing": true, 8 | "arrowParens": "avoid", 9 | "endOfLine": "auto" 10 | } 11 | -------------------------------------------------------------------------------- /CONTRIBUTION.md: -------------------------------------------------------------------------------- 1 | # Contributing to Dockit 2 | 3 | ## Introduction 4 | First off, thank you for considering contributing to Dockit! It's people like you that make this project great. 5 | 6 | ## How to Contribute 7 | 1. Fork the repository. 8 | 2. Create a new branch (`git checkout -b [feat|doc|fix|refactor]/your-branch`). 9 | 3. Make your changes. 10 | 4. Commit your changes (`git commit -am 'Add new feature'`). 11 | 5. Push to the branch (`git push origin feature-branch`). 12 | 6. Create a new Pull Request. 13 | 14 | ## Code of Conduct 15 | Please read our [Code of Conduct](CODE_OF_CONDUCT.md) to understand the expectations for behavior when contributing to this project. 16 | 17 | ## Reporting Issues 18 | If you find a bug, please create an issue using the [bug report template](.github/ISSUE_TEMPLATE/bug_report.md). Provide as much detail as possible to help us understand and resolve the issue. 19 | 20 | ## Submitting Pull Requests 21 | - Ensure your code follows the project's coding standards. 22 | - Write clear, concise commit messages. 23 | - Include tests for any new functionality. 24 | - Ensure all tests pass before submitting your pull request. 25 | 26 | ## Coding Standards 27 | - Follow the typescript style Guide and pass all syle checkes 28 | - Use [Prettier](https://prettier.io/) for code formatting. 29 | - Ensure your code passes linting (`npm run lint:check`). 30 | 31 | ## Running Tests 32 | - Run tests with `npm test`. 33 | - Ensure all tests pass before submitting your pull request. 34 | 35 | ## Additional Resources 36 | - [Project Documentation](https://dockit.geekfun.club) 37 | - [GitHub Repository](https://github.com/geek-fun/dockit) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |

DocKit

5 |
6 | 7 | 8 | [![Node.js CI](https://github.com/geek-fun/dockit/actions/workflows/node.yml/badge.svg)](https://github.com/geek-fun/dockit/actions/workflows/node.yml) 9 | [![package release](https://github.com/geek-fun/dockit/actions/workflows/release.yml/badge.svg)](https://github.com/geek-fun/dockit/actions/workflows/release.yml) 10 | [![Known Vulnerabilities](https://snyk.io/test/github/geek-fun/dockit/badge.svg)](https://snyk.io/test/github/geek-fun/dockit) 11 | [![codecov](https://codecov.io/gh/geek-fun/dockit/branch/master/graph/badge.svg?token=GqlkEVgMvR)](https://codecov.io/gh/geek-fun/dockit) 12 | [![GitHub version](https://badge.fury.io/gh/geek-fun%2Fdockit.svg)](https://badge.fury.io/gh/geek-fun%2Fdockit) 13 | [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 14 | 15 | 16 | DocKit is a desktop client designed for NoSQL database, support Elasticsearch and OpenSearch across Mac, windows and Linux. 17 |
18 | 19 | ## Client 20 | ![client UI](public/client-ui.png) 21 | 22 | ## Feature 23 | 24 | * Full-featured editor, Powered by monaco-editor the backbones of vscode, provide familiar editor environment for developers 25 | * Keep your connections, Keep your connections in desktop apps, move the dependencies of dashboard tools 26 | * File persistence, Save your code in your machine as file, never lost 27 | * Multi engines support,Support Elasticsearch, OpenSearch, and more to come 28 | ## Roadmap 29 | 30 | - [ ] MongoDB support 31 | - [ ] DynamoDB support 32 | - [ ] TBC 33 | 34 | ## Installation 35 | 36 | Available to download for free from [here](https://github.com/geek-fun/dockit/releases). 37 | 38 | ## Build Guidelines 39 | 40 | ### Prerequisites 41 | 42 | * Node.js >= 20 43 | * NPM >= 10 44 | 45 | ### Clone the code 46 | 47 | ```bash 48 | git clone https://github.com/geek-fun/dockit.git --depth=1 49 | ``` 50 | 51 | ### Install dependencies 52 | 53 | ```bash 54 | npm install 55 | ``` 56 | 57 | ### Compile and run 58 | 59 | ```bash 60 | npm run tauri dev 61 | ``` 62 | ## About 63 | ### Wechat Official Account 64 | wechat official account qr code 65 | 66 | ### Sponsor 67 | If this project helpful for you, feel free to buy me a cup of coffee ☕️. 68 | 69 | * Github Sponsor 70 | [![Sponsor](https://img.shields.io/badge/-Sponsor-fafbfc?logo=GitHub-Sponsors)](https://github.com/sponsors/[geek-fun]) 71 | 72 | * Wechat Sponsor 73 | wechat sponsor qr code 74 | -------------------------------------------------------------------------------- /app-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geek-fun/dockit/662308de83b8b294a25617ebd9bba66763309a7a/app-icon.png -------------------------------------------------------------------------------- /auto-import.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* prettier-ignore */ 3 | // @ts-nocheck 4 | // noinspection JSUnusedGlobalSymbols 5 | // Generated by unplugin-auto-import 6 | // biome-ignore lint: disable 7 | export {} 8 | declare global { 9 | const EffectScope: typeof import('vue')['EffectScope'] 10 | const computed: typeof import('vue')['computed'] 11 | const createApp: typeof import('vue')['createApp'] 12 | const customRef: typeof import('vue')['customRef'] 13 | const defineAsyncComponent: typeof import('vue')['defineAsyncComponent'] 14 | const defineComponent: typeof import('vue')['defineComponent'] 15 | const effectScope: typeof import('vue')['effectScope'] 16 | const getCurrentInstance: typeof import('vue')['getCurrentInstance'] 17 | const getCurrentScope: typeof import('vue')['getCurrentScope'] 18 | const h: typeof import('vue')['h'] 19 | const inject: typeof import('vue')['inject'] 20 | const isProxy: typeof import('vue')['isProxy'] 21 | const isReactive: typeof import('vue')['isReactive'] 22 | const isReadonly: typeof import('vue')['isReadonly'] 23 | const isRef: typeof import('vue')['isRef'] 24 | const markRaw: typeof import('vue')['markRaw'] 25 | const nextTick: typeof import('vue')['nextTick'] 26 | const onActivated: typeof import('vue')['onActivated'] 27 | const onBeforeMount: typeof import('vue')['onBeforeMount'] 28 | const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave'] 29 | const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate'] 30 | const onBeforeUnmount: typeof import('vue')['onBeforeUnmount'] 31 | const onBeforeUpdate: typeof import('vue')['onBeforeUpdate'] 32 | const onDeactivated: typeof import('vue')['onDeactivated'] 33 | const onErrorCaptured: typeof import('vue')['onErrorCaptured'] 34 | const onMounted: typeof import('vue')['onMounted'] 35 | const onRenderTracked: typeof import('vue')['onRenderTracked'] 36 | const onRenderTriggered: typeof import('vue')['onRenderTriggered'] 37 | const onScopeDispose: typeof import('vue')['onScopeDispose'] 38 | const onServerPrefetch: typeof import('vue')['onServerPrefetch'] 39 | const onUnmounted: typeof import('vue')['onUnmounted'] 40 | const onUpdated: typeof import('vue')['onUpdated'] 41 | const onWatcherCleanup: typeof import('vue')['onWatcherCleanup'] 42 | const provide: typeof import('vue')['provide'] 43 | const reactive: typeof import('vue')['reactive'] 44 | const readonly: typeof import('vue')['readonly'] 45 | const ref: typeof import('vue')['ref'] 46 | const resolveComponent: typeof import('vue')['resolveComponent'] 47 | const shallowReactive: typeof import('vue')['shallowReactive'] 48 | const shallowReadonly: typeof import('vue')['shallowReadonly'] 49 | const shallowRef: typeof import('vue')['shallowRef'] 50 | const toRaw: typeof import('vue')['toRaw'] 51 | const toRef: typeof import('vue')['toRef'] 52 | const toRefs: typeof import('vue')['toRefs'] 53 | const toValue: typeof import('vue')['toValue'] 54 | const triggerRef: typeof import('vue')['triggerRef'] 55 | const unref: typeof import('vue')['unref'] 56 | const useAttrs: typeof import('vue')['useAttrs'] 57 | const useCssModule: typeof import('vue')['useCssModule'] 58 | const useCssVars: typeof import('vue')['useCssVars'] 59 | const useDialog: typeof import('naive-ui')['useDialog'] 60 | const useId: typeof import('vue')['useId'] 61 | const useLink: typeof import('vue-router')['useLink'] 62 | const useLoadingBar: typeof import('naive-ui')['useLoadingBar'] 63 | const useMessage: typeof import('naive-ui')['useMessage'] 64 | const useModel: typeof import('vue')['useModel'] 65 | const useNotification: typeof import('naive-ui')['useNotification'] 66 | const useRoute: typeof import('vue-router')['useRoute'] 67 | const useRouter: typeof import('vue-router')['useRouter'] 68 | const useSlots: typeof import('vue')['useSlots'] 69 | const useTemplateRef: typeof import('vue')['useTemplateRef'] 70 | const watch: typeof import('vue')['watch'] 71 | const watchEffect: typeof import('vue')['watchEffect'] 72 | const watchPostEffect: typeof import('vue')['watchPostEffect'] 73 | const watchSyncEffect: typeof import('vue')['watchSyncEffect'] 74 | } 75 | // for type re-export 76 | declare global { 77 | // @ts-ignore 78 | export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue' 79 | import('vue') 80 | } 81 | -------------------------------------------------------------------------------- /auto-imports.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* prettier-ignore */ 3 | // @ts-nocheck 4 | // noinspection JSUnusedGlobalSymbols 5 | // Generated by unplugin-auto-import 6 | export {} 7 | declare global { 8 | const EffectScope: typeof import('vue')['EffectScope'] 9 | const computed: typeof import('vue')['computed'] 10 | const createApp: typeof import('vue')['createApp'] 11 | const customRef: typeof import('vue')['customRef'] 12 | const defineAsyncComponent: typeof import('vue')['defineAsyncComponent'] 13 | const defineComponent: typeof import('vue')['defineComponent'] 14 | const effectScope: typeof import('vue')['effectScope'] 15 | const getCurrentInstance: typeof import('vue')['getCurrentInstance'] 16 | const getCurrentScope: typeof import('vue')['getCurrentScope'] 17 | const h: typeof import('vue')['h'] 18 | const inject: typeof import('vue')['inject'] 19 | const isProxy: typeof import('vue')['isProxy'] 20 | const isReactive: typeof import('vue')['isReactive'] 21 | const isReadonly: typeof import('vue')['isReadonly'] 22 | const isRef: typeof import('vue')['isRef'] 23 | const markRaw: typeof import('vue')['markRaw'] 24 | const nextTick: typeof import('vue')['nextTick'] 25 | const onActivated: typeof import('vue')['onActivated'] 26 | const onBeforeMount: typeof import('vue')['onBeforeMount'] 27 | const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave'] 28 | const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate'] 29 | const onBeforeUnmount: typeof import('vue')['onBeforeUnmount'] 30 | const onBeforeUpdate: typeof import('vue')['onBeforeUpdate'] 31 | const onDeactivated: typeof import('vue')['onDeactivated'] 32 | const onErrorCaptured: typeof import('vue')['onErrorCaptured'] 33 | const onMounted: typeof import('vue')['onMounted'] 34 | const onRenderTracked: typeof import('vue')['onRenderTracked'] 35 | const onRenderTriggered: typeof import('vue')['onRenderTriggered'] 36 | const onScopeDispose: typeof import('vue')['onScopeDispose'] 37 | const onServerPrefetch: typeof import('vue')['onServerPrefetch'] 38 | const onUnmounted: typeof import('vue')['onUnmounted'] 39 | const onUpdated: typeof import('vue')['onUpdated'] 40 | const provide: typeof import('vue')['provide'] 41 | const reactive: typeof import('vue')['reactive'] 42 | const readonly: typeof import('vue')['readonly'] 43 | const ref: typeof import('vue')['ref'] 44 | const resolveComponent: typeof import('vue')['resolveComponent'] 45 | const shallowReactive: typeof import('vue')['shallowReactive'] 46 | const shallowReadonly: typeof import('vue')['shallowReadonly'] 47 | const shallowRef: typeof import('vue')['shallowRef'] 48 | const toRaw: typeof import('vue')['toRaw'] 49 | const toRef: typeof import('vue')['toRef'] 50 | const toRefs: typeof import('vue')['toRefs'] 51 | const toValue: typeof import('vue')['toValue'] 52 | const triggerRef: typeof import('vue')['triggerRef'] 53 | const unref: typeof import('vue')['unref'] 54 | const useAttrs: typeof import('vue')['useAttrs'] 55 | const useCssModule: typeof import('vue')['useCssModule'] 56 | const useCssVars: typeof import('vue')['useCssVars'] 57 | const useDialog: typeof import('naive-ui')['useDialog'] 58 | const useLink: typeof import('vue-router')['useLink'] 59 | const useLoadingBar: typeof import('naive-ui')['useLoadingBar'] 60 | const useMessage: typeof import('naive-ui')['useMessage'] 61 | const useNotification: typeof import('naive-ui')['useNotification'] 62 | const useRoute: typeof import('vue-router')['useRoute'] 63 | const useRouter: typeof import('vue-router')['useRouter'] 64 | const useSlots: typeof import('vue')['useSlots'] 65 | const watch: typeof import('vue')['watch'] 66 | const watchEffect: typeof import('vue')['watchEffect'] 67 | const watchPostEffect: typeof import('vue')['watchPostEffect'] 68 | const watchSyncEffect: typeof import('vue')['watchSyncEffect'] 69 | } 70 | // for type re-export 71 | declare global { 72 | // @ts-ignore 73 | export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue' 74 | import('vue') 75 | } 76 | -------------------------------------------------------------------------------- /components.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // @ts-nocheck 3 | // Generated by unplugin-vue-components 4 | // Read more: https://github.com/vuejs/core/pull/3399 5 | // biome-ignore lint: disable 6 | export {} 7 | 8 | /* prettier-ignore */ 9 | declare module 'vue' { 10 | export interface GlobalComponents { 11 | AppProvider: typeof import('./src/components/AppProvider.vue')['default'] 12 | MarkdownRender: typeof import('./src/components/markdown-render.vue')['default'] 13 | NAlert: typeof import('naive-ui')['NAlert'] 14 | NBreadcrumb: typeof import('naive-ui')['NBreadcrumb'] 15 | NBreadcrumbItem: typeof import('naive-ui')['NBreadcrumbItem'] 16 | NButton: typeof import('naive-ui')['NButton'] 17 | NButtonGroup: typeof import('naive-ui')['NButtonGroup'] 18 | NCard: typeof import('naive-ui')['NCard'] 19 | NCollapse: typeof import('naive-ui')['NCollapse'] 20 | NCollapseItem: typeof import('naive-ui')['NCollapseItem'] 21 | NConfigProvider: typeof import('naive-ui')['NConfigProvider'] 22 | NDataTable: typeof import('naive-ui')['NDataTable'] 23 | NDialogProvider: typeof import('naive-ui')['NDialogProvider'] 24 | NDivider: typeof import('naive-ui')['NDivider'] 25 | NEmpty: typeof import('naive-ui')['NEmpty'] 26 | NFloatButton: typeof import('naive-ui')['NFloatButton'] 27 | NForm: typeof import('naive-ui')['NForm'] 28 | NFormItem: typeof import('naive-ui')['NFormItem'] 29 | NFormItemRow: typeof import('naive-ui')['NFormItemRow'] 30 | NGi: typeof import('naive-ui')['NGi'] 31 | NGrid: typeof import('naive-ui')['NGrid'] 32 | NGridItem: typeof import('naive-ui')['NGridItem'] 33 | NIcon: typeof import('naive-ui')['NIcon'] 34 | NInfiniteScroll: typeof import('naive-ui')['NInfiniteScroll'] 35 | NInput: typeof import('naive-ui')['NInput'] 36 | NInputGroup: typeof import('naive-ui')['NInputGroup'] 37 | NInputGroupLabel: typeof import('naive-ui')['NInputGroupLabel'] 38 | NInputNumber: typeof import('naive-ui')['NInputNumber'] 39 | NLoadingBarProvider: typeof import('naive-ui')['NLoadingBarProvider'] 40 | NMessageProvider: typeof import('naive-ui')['NMessageProvider'] 41 | NModal: typeof import('naive-ui')['NModal'] 42 | NNotificationProvider: typeof import('naive-ui')['NNotificationProvider'] 43 | NP: typeof import('naive-ui')['NP'] 44 | NPopover: typeof import('naive-ui')['NPopover'] 45 | NProgress: typeof import('naive-ui')['NProgress'] 46 | NRadio: typeof import('naive-ui')['NRadio'] 47 | NRadioGroup: typeof import('naive-ui')['NRadioGroup'] 48 | NScrollbar: typeof import('naive-ui')['NScrollbar'] 49 | NSelect: typeof import('naive-ui')['NSelect'] 50 | NSpace: typeof import('naive-ui')['NSpace'] 51 | NSplit: typeof import('naive-ui')['NSplit'] 52 | NSwitch: typeof import('naive-ui')['NSwitch'] 53 | NTabPane: typeof import('naive-ui')['NTabPane'] 54 | NTabs: typeof import('naive-ui')['NTabs'] 55 | NText: typeof import('naive-ui')['NText'] 56 | NTooltip: typeof import('naive-ui')['NTooltip'] 57 | PathBreadcrumb: typeof import('./src/components/path-breadcrumb.vue')['default'] 58 | RouterLink: typeof import('vue-router')['RouterLink'] 59 | RouterMain: typeof import('./src/components/RouterMain.vue')['default'] 60 | RouterView: typeof import('vue-router')['RouterView'] 61 | ToolBar: typeof import('./src/components/tool-bar.vue')['default'] 62 | VersionDetect: typeof import('./src/components/VersionDetect.vue')['default'] 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /docs/images/wechat_official.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geek-fun/dockit/662308de83b8b294a25617ebd9bba66763309a7a/docs/images/wechat_official.png -------------------------------------------------------------------------------- /docs/images/wechat_ponsor.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geek-fun/dockit/662308de83b8b294a25617ebd9bba66763309a7a/docs/images/wechat_ponsor.jpg -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Index page 2 | this docs folder contains the documentation for the project. 3 | 4 | 5 | ## Useful links 6 | Monaco-editor actions: https://stackoverflow.com/questions/64430041/get-a-list-of-monaco-commands-actions-ids 7 | -------------------------------------------------------------------------------- /docs/privacy-policy.md: -------------------------------------------------------------------------------- 1 | # Privacy Policy for DocKit 2 | 3 | ### Introduction 4 | 5 | Welcome to DocKit. Your privacy is important to us. This Privacy Policy explains how we collect, use, disclose, and safeguard your information when you use our application. Please read this privacy policy carefully. If you do not agree with the terms of this privacy policy, please do not access the application. 6 | 7 | ### Information We Collect 8 | 9 | We may collect and store the following personal information: 10 | 11 | 1. **Technical Information** 12 | - Device type 13 | - Operating system version 14 | 2. **Usage Data** 15 | - Information on how you use the application 16 | - Interaction with application features and functionalities 17 | 3. **Access Disk** 18 | - Access to the disk to store the files 19 | - Access to the disk to read the files 20 | - Access to the disk to delete the files 21 | - Access to the disk to update the files 22 | 4. **Access Network** 23 | - Access to the network to send the request 24 | - Access to the network to receive the response 25 | ### How We Use Your Information 26 | We use the information we collect in the following ways: 27 | 28 | 1. **To Provide and Maintain our Service** 29 | - To deliver and improve our application services 30 | - To troubleshoot and provide technical support 31 | 32 | 2. **To Personalize User Experience** 33 | - To understand and analyze user preferences and usage 34 | - To enhance user experience and tailor our services 35 | 36 | 3. **To Communicate with You** 37 | - To send you updates, notifications, and other information 38 | - To respond to your comments, questions, and requests 39 | 40 | 4. **To Ensure Compliance and Security** 41 | - To monitor and ensure the security of our application 42 | - To comply with legal obligations and prevent misuse 43 | 44 | ### Sharing Your Information 45 | 46 | We do not share your personal information with third parties except in the following circumstances: 47 | 48 | 1. **With Your Consent** 49 | - When you have given us explicit permission to share your information 50 | 51 | 2. **For Legal Reasons** 52 | - To comply with legal obligations, court orders, or government requests 53 | 54 | 3. **For Business Transfers** 55 | - If we are involved in a merger, acquisition, or sale of assets, your information may be transferred 56 | 57 | ### Data Security 58 | 59 | We implement appropriate technical and organizational measures to protect your personal data from unauthorized access, use, or disclosure. However, no internet or email transmission is ever fully secure or error-free. Please take special care in deciding what information you send to us. 60 | 61 | ### Your Data Protection Rights 62 | 63 | Depending on your location, you may have the following rights regarding your personal data: 64 | 65 | 1. **The Right to Access** 66 | - You have the right to request copies of your personal data 67 | 68 | 2. **The Right to Rectification** 69 | - You have the right to request correction of any information you believe is inaccurate 70 | 71 | 3. **The Right to Erasure** 72 | - You have the right to request that we delete your personal data, under certain conditions 73 | 74 | 4. **The Right to Restrict Processing** 75 | - You have the right to request that we restrict the processing of your personal data, under certain conditions 76 | 77 | 5. **The Right to Data Portability** 78 | - You have the right to request that we transfer the data that we have collected to another organization, or directly to you, under certain conditions 79 | 80 | ### Changes to This Privacy Policy 81 | 82 | We may update our Privacy Policy from time to time. We will notify you of any changes by posting the new Privacy Policy on this page. You are advised to review this Privacy Policy periodically for any changes. Changes to this Privacy Policy are effective when they are posted on this page. 83 | 84 | ### Contact Us 85 | 86 | If you have any questions about this Privacy Policy, please contact us: 87 | 88 | - **By email:** geekfun 89 | - **By visiting this page on our website:** https://dockit.geekfun.club 90 | 91 | This privacy policy was last updated on 2024-06-02 92 | 93 | --- 94 | 95 | By using our application, you acknowledge that you have read and understood this Privacy Policy and agree to its terms. 96 | 97 | --- 98 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | ignores: ['node_modules', 'dist', 'index.html', 'src-tauri/target', 'coverage'], 3 | }; 4 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | DocKit 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /jest.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | testPathIgnorePatterns: ['/node_modules/', '/dist/'], 5 | }; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dockit", 3 | "private": true, 4 | "type": "module", 5 | "version": "0.6.9", 6 | "description": "DocKit is a desktop client designed for NoSQL database, support Elasticsearch and OpenSearch across Mac, windows and Linux", 7 | "author": "geekfun ", 8 | "homepage": "ttps://dockit.geekfun.club", 9 | "license": "Apache-2.0", 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/geek-fun/dockit.git" 13 | }, 14 | "scripts": { 15 | "dev": "vite", 16 | "build": "vue-tsc --noEmit && vite build", 17 | "preview": "vite preview", 18 | "tauri": "tauri", 19 | "lint:fix": "eslint --fix ./", 20 | "lint:check": "eslint ./", 21 | "test": "jest --runInBand --coverage --coverageReporters json-summary text html lcov", 22 | "test:ci": "jest --runInBand --ci --coverage --coverageReporters json-summary text html lcov", 23 | "build:macos": "tauri build -t universal-apple-darwin" 24 | }, 25 | "dependencies": { 26 | "@tauri-apps/api": "^2.5.0", 27 | "@tauri-apps/plugin-dialog": "^2.2.1", 28 | "@tauri-apps/plugin-fs": "^2.2.1", 29 | "@tauri-apps/plugin-global-shortcut": "^2.2.0", 30 | "@tauri-apps/plugin-os": "^2.2.1", 31 | "@tauri-apps/plugin-shell": "^2.2.1", 32 | "@tauri-apps/plugin-store": "^2.2.0", 33 | "debug": "^4.4.0", 34 | "highlight.js": "^11.11.1", 35 | "json-with-bigint": "^3.4.4", 36 | "json5": "^2.2.3", 37 | "lodash": "^4.17.21", 38 | "markdown-it": "^14.1.0", 39 | "monaco-editor": "^0.52.2", 40 | "pinia": "^3.0.2", 41 | "pinia-plugin-persistedstate": "^4.2.0", 42 | "pretty-bytes": "^6.1.1", 43 | "tauri-plugin-system-info-api": "^2.0.10", 44 | "ulidx": "^2.4.1", 45 | "vue": "^3.5.13", 46 | "vue-i18n": "^11.1.3", 47 | "vue-router": "^4.5.1" 48 | }, 49 | "devDependencies": { 50 | "@tauri-apps/cli": "^2.4.1", 51 | "@types/debug": "^4.1.12", 52 | "@types/jest": "^29.5.14", 53 | "@types/lodash": "^4.17.16", 54 | "@types/markdown-it": "^14.1.2", 55 | "@typescript-eslint/eslint-plugin": "^8.29.1", 56 | "@typescript-eslint/parser": "^8.29.1", 57 | "@vicons/antd": "^0.13.0", 58 | "@vicons/carbon": "^0.13.0", 59 | "@vicons/fa": "^0.13.0", 60 | "@vitejs/plugin-vue": "^5.2.3", 61 | "eslint": "^9.24.0", 62 | "eslint-config-prettier": "^10.1.2", 63 | "eslint-plugin-import": "^2.31.0", 64 | "eslint-plugin-prettier": "^5.2.6", 65 | "eslint-plugin-vue": "^10.0.0", 66 | "husky": "^9.1.7", 67 | "jest": "^29.7.0", 68 | "naive-ui": "^2.41.0", 69 | "prettier": "^3.5.3", 70 | "sass": "^1.86.3", 71 | "ts-jest": "^29.3.2", 72 | "typescript": "^5.8.3", 73 | "unplugin-auto-import": "^19.1.2", 74 | "unplugin-vue-components": "^28.4.1", 75 | "vite": "^6.2.6", 76 | "vite-svg-loader": "^5.1.0", 77 | "vue-tsc": "^2.2.8" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /public/client-ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geek-fun/dockit/662308de83b8b294a25617ebd9bba66763309a7a/public/client-ui.png -------------------------------------------------------------------------------- /public/dockit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geek-fun/dockit/662308de83b8b294a25617ebd9bba66763309a7a/public/dockit.png -------------------------------------------------------------------------------- /scripts/build-pkg.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | set -o pipefail 3 | 4 | cd "$(dirname "$0")/.." || exit 5 | 6 | #source .env 7 | 8 | unset APPLE_SIGNING_IDENTITY 9 | 10 | unset APPLE_CERTIFICATE 11 | 12 | APP_NAME="DocKit" 13 | 14 | SIGN_APP="Apple Distribution: Lisheng Zi (Z44247ZSR9)" 15 | 16 | SIGN_INSTALL="3rd Party Mac Developer Installer: Lisheng Zi (Z44247ZSR9)" 17 | 18 | 19 | TARGET="universal-apple-darwin" 20 | 21 | 22 | DOCKIT_DISTRIBUTION="APP_STORE" npx tauri build --target "${TARGET}" --verbose 23 | 24 | # cargo tauri build --target "${target}" --verbose 25 | 26 | 27 | APP_PATH="src-tauri/target/${TARGET}/release/bundle/macos/${APP_NAME}.app" 28 | 29 | BUILD_NAME="src-tauri/target/${TARGET}/release/bundle/macos/${APP_NAME}.pkg" 30 | 31 | CP_DIR="src-tauri/target/${TARGET}/release/bundle/macos/${APP_NAME}.app/Contents/embedded.provisionprofile" 32 | 33 | ENTITLEMENTS="src-tauri/entitlements/${APP_NAME}.entitlements" 34 | 35 | PROFILE="src-tauri/entitlements/${APP_NAME}_Distribution.provisionprofile" 36 | 37 | cp "${PROFILE}" "${CP_DIR}" 38 | 39 | 40 | codesign --deep --force -s "${SIGN_APP}" --entitlements ${ENTITLEMENTS} "${APP_PATH}" 41 | 42 | productbuild --component "${APP_PATH}" /Applications/ --sign "${SIGN_INSTALL}" "${BUILD_NAME}" 43 | -------------------------------------------------------------------------------- /scripts/collect-binaries.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | set -o pipefail 3 | 4 | cd "$(dirname "$0")/.." || exit 5 | 6 | 7 | VERSION=$(node -p "require('./package.json').version") 8 | PLATFORM=${PLATFORM:-"macos-latest"} 9 | 10 | if [ ! -d artifacts ]; then 11 | mkdir artifacts 12 | fi 13 | 14 | if [[ $PLATFORM == "windows-latest" ]]; then 15 | mv src-tauri/target/x86_64-pc-windows-msvc/release/bundle/nsis/*.exe artifacts/ 16 | fi 17 | 18 | if [[ $PLATFORM == "macos-latest" ]]; then 19 | mv src-tauri/target/universal-apple-darwin/release/bundle/dmg/*.dmg artifacts/ 20 | fi 21 | 22 | if [[ $PLATFORM == "ubuntu-latest" ]]; then 23 | mv src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/deb/*.deb artifacts/DocKit_"${VERSION}"_amd64.deb 24 | mv src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/appimage/*.AppImage artifacts/DocKit_"${VERSION}"_amd64.AppImage 25 | fi 26 | -------------------------------------------------------------------------------- /src-tauri/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Generated by Tauri 6 | # will have schema files for capabilities auto-completion 7 | /gen/schemas 8 | /entitlements/* 9 | -------------------------------------------------------------------------------- /src-tauri/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dockit" 3 | version = "0.0.0" 4 | description = "DocKit is a desktop client designed for NoSQL database, support Elasticsearch and OpenSearch across Mac, windows and Linux" 5 | authors = ["geekfun"] 6 | edition = "2021" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [build-dependencies] 11 | tauri-build = { version = "2", features = [] } 12 | 13 | [dependencies] 14 | tauri = { version = "2", features = [] } 15 | tauri-plugin-store = "2" 16 | tauri-plugin-shell = "2" 17 | tauri-plugin-dialog = "2" 18 | tauri-plugin-fs = "2" 19 | tauri-plugin-os = "2" 20 | serde = { version = "1.0", features = ["derive"] } 21 | serde_json = "1" 22 | 23 | tauri-plugin-system-info = { git = "https://github.com/HuakunShen/tauri-plugin-system-info", branch = "v2" } # use v2 branch for Tauri v2 plugin 24 | 25 | async-openai = { version = "0.28.0" } 26 | reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } 27 | tokio = { version = "1", features = ["full"] } 28 | http = "0.2.12" 29 | log = "0.4.22" 30 | futures = "0.3.30" 31 | aws-config = "1.6.1" 32 | aws-sdk-dynamodb = "1.71.2" 33 | base64 = "0.22.1" 34 | [features] 35 | # This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!! 36 | custom-protocol = ["tauri/custom-protocol"] 37 | 38 | [target."cfg(not(any(target_os = \"android\", target_os = \"ios\")))".dependencies] 39 | tauri-plugin-global-shortcut = "2" 40 | 41 | -------------------------------------------------------------------------------- /src-tauri/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | tauri_build::build() 3 | } 4 | -------------------------------------------------------------------------------- /src-tauri/capabilities/desktop.json: -------------------------------------------------------------------------------- 1 | { 2 | "identifier": "desktop-capability", 3 | "platforms": [ 4 | "macOS", 5 | "windows", 6 | "linux" 7 | ], 8 | "windows": [ 9 | "main" 10 | ], 11 | "permissions": [ 12 | "os:default", 13 | "core:default", 14 | "store:default", 15 | "shell:default", 16 | "dialog:default", 17 | "global-shortcut:default", 18 | "fs:default", 19 | "fs:allow-home-read-recursive", 20 | "fs:allow-home-write-recursive", 21 | "global-shortcut:allow-is-registered", 22 | "global-shortcut:allow-register", 23 | "global-shortcut:allow-unregister" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /src-tauri/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geek-fun/dockit/662308de83b8b294a25617ebd9bba66763309a7a/src-tauri/icons/128x128.png -------------------------------------------------------------------------------- /src-tauri/icons/128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geek-fun/dockit/662308de83b8b294a25617ebd9bba66763309a7a/src-tauri/icons/128x128@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geek-fun/dockit/662308de83b8b294a25617ebd9bba66763309a7a/src-tauri/icons/32x32.png -------------------------------------------------------------------------------- /src-tauri/icons/Square107x107Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geek-fun/dockit/662308de83b8b294a25617ebd9bba66763309a7a/src-tauri/icons/Square107x107Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square142x142Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geek-fun/dockit/662308de83b8b294a25617ebd9bba66763309a7a/src-tauri/icons/Square142x142Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square150x150Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geek-fun/dockit/662308de83b8b294a25617ebd9bba66763309a7a/src-tauri/icons/Square150x150Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square284x284Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geek-fun/dockit/662308de83b8b294a25617ebd9bba66763309a7a/src-tauri/icons/Square284x284Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square30x30Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geek-fun/dockit/662308de83b8b294a25617ebd9bba66763309a7a/src-tauri/icons/Square30x30Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square310x310Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geek-fun/dockit/662308de83b8b294a25617ebd9bba66763309a7a/src-tauri/icons/Square310x310Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square44x44Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geek-fun/dockit/662308de83b8b294a25617ebd9bba66763309a7a/src-tauri/icons/Square44x44Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square71x71Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geek-fun/dockit/662308de83b8b294a25617ebd9bba66763309a7a/src-tauri/icons/Square71x71Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square89x89Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geek-fun/dockit/662308de83b8b294a25617ebd9bba66763309a7a/src-tauri/icons/Square89x89Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geek-fun/dockit/662308de83b8b294a25617ebd9bba66763309a7a/src-tauri/icons/StoreLogo.png -------------------------------------------------------------------------------- /src-tauri/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geek-fun/dockit/662308de83b8b294a25617ebd9bba66763309a7a/src-tauri/icons/icon.icns -------------------------------------------------------------------------------- /src-tauri/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geek-fun/dockit/662308de83b8b294a25617ebd9bba66763309a7a/src-tauri/icons/icon.ico -------------------------------------------------------------------------------- /src-tauri/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geek-fun/dockit/662308de83b8b294a25617ebd9bba66763309a7a/src-tauri/icons/icon.png -------------------------------------------------------------------------------- /src-tauri/src/common/http_client.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | fn get_proxy(http_proxy: Option) -> Option { 4 | let sys_proxy = env::var("HTTPS_PROXY") 5 | .ok() 6 | .or(env::var("https_proxy").ok()); 7 | let proxy_url = match http_proxy { 8 | Some(proxy) => { 9 | if proxy.is_empty() { 10 | sys_proxy 11 | } else { 12 | Some(proxy.clone()) 13 | } 14 | } 15 | None => sys_proxy, 16 | }; 17 | return proxy_url; 18 | } 19 | 20 | pub fn create_http_client(proxy: Option, ssl: Option) -> reqwest::Client { 21 | let mut builder = 22 | reqwest::ClientBuilder::new().danger_accept_invalid_certs(!ssl.unwrap_or(true)); 23 | 24 | if let Some(proxy_url) = get_proxy(proxy) { 25 | match reqwest::Proxy::https(&proxy_url) { 26 | Ok(proxy) => { 27 | builder = builder.proxy(proxy); 28 | } 29 | Err(e) => { 30 | println!("Failed to create proxy: {}", e); 31 | } 32 | }; 33 | } 34 | 35 | return builder.build().unwrap(); 36 | } 37 | -------------------------------------------------------------------------------- /src-tauri/src/common/json_utils.rs: -------------------------------------------------------------------------------- 1 | use aws_sdk_dynamodb::types::AttributeValue; 2 | 3 | pub fn convert_json_to_attr_value(value: &serde_json::Value) -> Option { 4 | match value { 5 | serde_json::Value::String(s) => Some(AttributeValue::S(s.clone())), 6 | serde_json::Value::Number(n) => Some(AttributeValue::N(n.to_string())), 7 | serde_json::Value::Bool(b) => Some(AttributeValue::Bool(*b)), 8 | serde_json::Value::Null => Some(AttributeValue::Null(true)), 9 | serde_json::Value::Array(arr) => Some(AttributeValue::L( 10 | arr.iter() 11 | .filter_map(|v| convert_json_to_attr_value(v)) 12 | .collect(), 13 | )), 14 | serde_json::Value::Object(map) => Some(AttributeValue::M( 15 | map.iter() 16 | .filter_map(|(k, v)| convert_json_to_attr_value(v).map(|av| (k.clone(), av))) 17 | .collect(), 18 | )), 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src-tauri/src/common/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod http_client; 2 | pub mod json_utils; 3 | -------------------------------------------------------------------------------- /src-tauri/src/dynamo/create_item.rs: -------------------------------------------------------------------------------- 1 | use crate::common::json_utils::convert_json_to_attr_value; 2 | use crate::dynamo::types::ApiResponse; 3 | use aws_sdk_dynamodb::types::AttributeValue; 4 | use aws_sdk_dynamodb::Client; 5 | use serde_json::Value; 6 | 7 | pub struct CreateItemInput<'a> { 8 | pub table_name: &'a str, 9 | pub payload: &'a Value, 10 | } 11 | 12 | pub async fn create_item( 13 | client: &Client, 14 | input: CreateItemInput<'_>, 15 | ) -> Result { 16 | if let Some(attributes) = input.payload.get("attributes").and_then(|v| v.as_array()) { 17 | let mut put_item = client.put_item().table_name(input.table_name); 18 | 19 | for attr in attributes { 20 | if let (Some(key), Some(value), Some(attr_type)) = ( 21 | attr.get("key").and_then(|v| v.as_str()), 22 | attr.get("value"), 23 | attr.get("type").and_then(|v| v.as_str()), 24 | ) { 25 | let attr_value = match attr_type { 26 | "S" => value.as_str().map(|s| AttributeValue::S(s.to_string())), 27 | "N" => value.as_f64().map(|n| AttributeValue::N(n.to_string())), 28 | "B" => value.as_str().map(|s| { 29 | AttributeValue::B(aws_sdk_dynamodb::primitives::Blob::new( 30 | base64::decode(s).unwrap_or_default(), 31 | )) 32 | }), 33 | "BOOL" => value.as_bool().map(AttributeValue::Bool), 34 | "NULL" => Some(AttributeValue::Null(true)), 35 | "SS" => value.as_array().map(|arr| { 36 | AttributeValue::Ss( 37 | arr.iter() 38 | .filter_map(|v| v.as_str().map(|s| s.to_string())) 39 | .collect(), 40 | ) 41 | }), 42 | "NS" => value.as_array().map(|arr| { 43 | AttributeValue::Ns( 44 | arr.iter() 45 | .filter_map(|v| v.as_f64().map(|n| n.to_string())) 46 | .collect(), 47 | ) 48 | }), 49 | "BS" => value.as_array().map(|arr| { 50 | AttributeValue::Bs( 51 | arr.iter() 52 | .filter_map(|v| { 53 | v.as_str().map(|s| { 54 | aws_sdk_dynamodb::primitives::Blob::new( 55 | base64::decode(s).unwrap_or_default(), 56 | ) 57 | }) 58 | }) 59 | .collect(), 60 | ) 61 | }), 62 | "L" => value.as_array().map(|arr| { 63 | AttributeValue::L( 64 | arr.iter() 65 | .filter_map(|v| { 66 | // Recursively convert each element 67 | convert_json_to_attr_value(v) 68 | }) 69 | .collect(), 70 | ) 71 | }), 72 | "M" => value.as_object().map(|map| { 73 | AttributeValue::M( 74 | map.iter() 75 | .filter_map(|(k, v)| { 76 | convert_json_to_attr_value(v).map(|av| (k.clone(), av)) 77 | }) 78 | .collect(), 79 | ) 80 | }), 81 | _ => None, 82 | }; 83 | if let Some(av) = attr_value { 84 | put_item = put_item.item(key, av); 85 | } 86 | } 87 | } 88 | 89 | match put_item.send().await { 90 | Ok(_) => Ok(ApiResponse { 91 | status: 200, 92 | message: "Item created successfully".to_string(), 93 | data: None, 94 | }), 95 | Err(e) => Ok(ApiResponse { 96 | status: 500, 97 | message: format!("Failed to create item: {}", e), 98 | data: None, 99 | }), 100 | } 101 | } else { 102 | Ok(ApiResponse { 103 | status: 400, 104 | message: "Attributes array is required".to_string(), 105 | data: None, 106 | }) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src-tauri/src/dynamo/describe_table.rs: -------------------------------------------------------------------------------- 1 | use crate::dynamo::types::ApiResponse; 2 | use aws_sdk_dynamodb::Client; 3 | use serde_json::json; 4 | 5 | pub async fn describe_table(client: &Client, table_name: &str) -> Result { 6 | match client 7 | .describe_table() 8 | .table_name(table_name) 9 | .send() 10 | .await 11 | { 12 | Ok(response) => { 13 | // Create a custom serializable structure with the data we need 14 | let table_info = json!({ 15 | "id": response.table().and_then(|t| t.table_id()), 16 | "name": response.table().map(|t| t.table_name()), 17 | "status": response.table().and_then(|t| t.table_status().map(|s| s.as_str().to_string())), 18 | "itemCount": response.table().and_then(|t| t.item_count()), 19 | "sizeBytes": response.table().and_then(|t| t.table_size_bytes()), 20 | "keySchema": response.table().and_then(|t| { 21 | Some(t.key_schema().iter().map(|k| { 22 | json!({ 23 | "attributeName": k.attribute_name(), 24 | "keyType": format!("{:?}", k.key_type()) 25 | }) 26 | }).collect::>()) 27 | }), 28 | "attributeDefinitions": response.table().and_then(|t| { 29 | Some(t.attribute_definitions().iter().map(|a| { 30 | json!({ 31 | "attributeName": a.attribute_name(), 32 | "attributeType": format!("{:?}", a.attribute_type()) 33 | }) 34 | }).collect::>()) 35 | }), 36 | "indices": response.table().map(|t| { 37 | let mut indices = Vec::new(); 38 | 39 | // Add Global Secondary Indexes 40 | let gsi_list = t.global_secondary_indexes(); 41 | if !gsi_list.is_empty() { 42 | for gsi in gsi_list { 43 | let index_info = json!({ 44 | "type": "GSI", 45 | "name": gsi.index_name(), 46 | "status": gsi.index_status().map(|s| s.as_str().to_string()), 47 | "keySchema": gsi.key_schema().iter().map(|k| { 48 | json!({ 49 | "attributeName": k.attribute_name(), 50 | "keyType": format!("{:?}", k.key_type()) 51 | }) 52 | }).collect::>(), 53 | "provisionedThroughput": gsi.provisioned_throughput().map(|pt| json!({ 54 | "readCapacityUnits": pt.read_capacity_units(), 55 | "writeCapacityUnits": pt.write_capacity_units() 56 | })) 57 | }); 58 | indices.push(index_info); 59 | } 60 | } 61 | 62 | // Add Local Secondary Indexes 63 | let lsi_list = t.local_secondary_indexes(); 64 | if !lsi_list.is_empty() { 65 | for lsi in lsi_list { 66 | let index_info = json!({ 67 | "type": "LSI", 68 | "name": lsi.index_name(), 69 | "keySchema": lsi.key_schema().iter().map(|k| { 70 | json!({ 71 | "attributeName": k.attribute_name(), 72 | "keyType": format!("{:?}", k.key_type()) 73 | }) 74 | }).collect::>() 75 | }); 76 | indices.push(index_info); 77 | } 78 | } 79 | 80 | indices 81 | }), 82 | "creationDateTime": response.table().and_then(|t| 83 | t.creation_date_time().map(|dt| dt.to_string())), 84 | }); 85 | 86 | Ok(ApiResponse { 87 | status: 200, 88 | message: "Table described successfully".to_string(), 89 | data: Some(table_info), 90 | }) 91 | } 92 | Err(e) => Ok(ApiResponse { 93 | status: 500, 94 | message: format!("Failed to describe table: {}", e), 95 | data: None, 96 | }), 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src-tauri/src/dynamo/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod create_item; 2 | pub mod describe_table; 3 | pub mod query_table; 4 | pub mod scan_table; 5 | pub mod types; 6 | -------------------------------------------------------------------------------- /src-tauri/src/dynamo/types.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | 3 | #[derive(Debug, Serialize)] 4 | pub struct ApiResponse { 5 | pub status: u16, 6 | pub message: String, 7 | pub data: Option, 8 | } 9 | 10 | -------------------------------------------------------------------------------- /src-tauri/src/dynamo_client.rs: -------------------------------------------------------------------------------- 1 | use crate::dynamo::create_item::{create_item, CreateItemInput}; 2 | use crate::dynamo::describe_table::describe_table; 3 | use crate::dynamo::query_table::{query_table, QueryTableInput}; 4 | use crate::dynamo::scan_table::{scan_table, ScanTableInput}; 5 | use crate::dynamo::types::ApiResponse; 6 | use aws_config::meta::region::RegionProviderChain; 7 | use aws_config::Region; 8 | use aws_sdk_dynamodb::{config::Credentials, Client}; 9 | use serde::Deserialize; 10 | 11 | #[derive(Debug, Deserialize)] 12 | pub struct DynamoCredentials { 13 | pub region: String, 14 | pub access_key_id: String, // AWS access key ID 15 | pub secret_access_key: String, // AWS secret access key 16 | } 17 | 18 | #[derive(Debug, Deserialize)] 19 | pub struct DynamoOptions { 20 | pub table_name: String, 21 | pub operation: String, 22 | pub payload: Option, 23 | } 24 | 25 | #[tauri::command] 26 | pub async fn dynamo_api( 27 | credentials: DynamoCredentials, 28 | options: DynamoOptions, 29 | ) -> Result { 30 | // Parse region 31 | let region_provider = RegionProviderChain::first_try(Region::new(credentials.region.clone())) 32 | .or_default_provider() 33 | .or_else("us-east-1"); 34 | 35 | // Create credentials provider 36 | let creds = Credentials::new( 37 | credentials.access_key_id, 38 | credentials.secret_access_key, 39 | None, // session token 40 | None, // expiry 41 | // &options.table_name.clone() 42 | "dockit-client", 43 | ); 44 | 45 | // Configure AWS SDK 46 | let config = aws_config::defaults(aws_config::BehaviorVersion::latest()) 47 | .region(region_provider) 48 | .credentials_provider(creds) 49 | .load() 50 | .await; 51 | 52 | let client = Client::new(&config); 53 | 54 | // Process operation 55 | let result = match options.operation.as_str() { 56 | "DESCRIBE_TABLE" => describe_table(&client, &options.table_name).await, 57 | "CREATE_ITEM" => { 58 | if let Some(payload) = &options.payload { 59 | let input = CreateItemInput { 60 | table_name: &options.table_name, 61 | payload, 62 | }; 63 | create_item(&client, input).await 64 | } else { 65 | Ok(ApiResponse { 66 | status: 400, 67 | message: "Item payload is required".to_string(), 68 | data: None, 69 | }) 70 | } 71 | } 72 | "QUERY_TABLE" => { 73 | if let Some(payload) = &options.payload { 74 | let input = QueryTableInput { 75 | table_name: &options.table_name, 76 | payload, 77 | }; 78 | query_table(&client, input).await 79 | } else { 80 | Ok(ApiResponse { 81 | status: 400, 82 | message: "Query parameters are required".to_string(), 83 | data: None, 84 | }) 85 | } 86 | } 87 | "SCAN_TABLE" => { 88 | // Extract scan parameters from payload 89 | if let Some(payload) = &options.payload { 90 | let input = ScanTableInput { 91 | table_name: &options.table_name, 92 | payload, 93 | }; 94 | scan_table(&client, input).await 95 | } else { 96 | Ok(ApiResponse { 97 | status: 400, 98 | message: "Scan parameters are required".to_string(), 99 | data: None, 100 | }) 101 | } 102 | } 103 | // Add more operations as needed 104 | _ => Ok(ApiResponse { 105 | status: 400, 106 | message: format!("Unsupported operation: {}", options.operation), 107 | data: None, 108 | }), 109 | }; 110 | 111 | match result { 112 | Ok(response) => Ok(serde_json::to_string(&response).map_err(|e| e.to_string())?), 113 | Err(e) => { 114 | println!("Error: {}", e); 115 | let error_response = ApiResponse { 116 | status: 500, 117 | message: e, 118 | data: None, 119 | }; 120 | Ok(serde_json::to_string(&error_response).map_err(|e| e.to_string())?) 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src-tauri/src/fetch_client.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::option::Option; 3 | use std::str::FromStr; 4 | 5 | use reqwest::header::{HeaderMap, HeaderName, HeaderValue}; 6 | use serde::Deserialize; 7 | use serde_json::json; 8 | 9 | use crate::common::http_client::create_http_client; 10 | 11 | static mut FETCH_SECURE_CLIENT: Option = None; 12 | static mut FETCH_INSECURE_CLIENT: Option = None; 13 | 14 | #[derive(Deserialize)] 15 | struct Agent { 16 | ssl: bool, 17 | http_proxy: Option, 18 | } 19 | 20 | #[derive(Deserialize)] 21 | pub struct FetchApiOptions { 22 | method: String, 23 | headers: HashMap, 24 | body: Option, 25 | agent: Agent, 26 | } 27 | 28 | fn headermap_from_hashmap<'a, I, S>(headers: I) -> HeaderMap 29 | where 30 | I: Iterator + 'a, 31 | S: AsRef + 'a, 32 | { 33 | headers 34 | .map(|(name, val)| { 35 | ( 36 | HeaderName::from_str(name.as_ref()), 37 | HeaderValue::from_str(val.as_ref()), 38 | ) 39 | }) 40 | // We ignore the errors here. If you want to get a list of failed conversions, you can use Iterator::partition 41 | // to help you out here 42 | .filter(|(k, v)| k.is_ok() && v.is_ok()) 43 | .map(|(k, v)| (k.unwrap(), v.unwrap())) 44 | .collect() 45 | } 46 | 47 | #[tauri::command] 48 | pub async fn fetch_api(url: String, options: FetchApiOptions) -> Result { 49 | let client = unsafe { 50 | match options.agent.ssl { 51 | true => { 52 | if FETCH_SECURE_CLIENT.is_none() { 53 | FETCH_SECURE_CLIENT = Option::from(create_http_client( 54 | options.agent.http_proxy, 55 | Some(options.agent.ssl), 56 | )); 57 | } 58 | FETCH_SECURE_CLIENT.as_ref().unwrap() 59 | } 60 | false => { 61 | if FETCH_INSECURE_CLIENT.is_none() { 62 | FETCH_INSECURE_CLIENT = Option::from(create_http_client( 63 | options.agent.http_proxy, 64 | Some(options.agent.ssl), 65 | )); 66 | } 67 | FETCH_INSECURE_CLIENT.as_ref().unwrap() 68 | } 69 | } 70 | }; 71 | 72 | let response = client 73 | .request( 74 | reqwest::Method::from_bytes(options.method.as_bytes()).unwrap(), 75 | &url, 76 | ) 77 | .headers(headermap_from_hashmap(options.headers.iter())) 78 | .body(options.body.unwrap_or_default()) 79 | .send() 80 | .await; 81 | 82 | match response { 83 | Ok(resp) => { 84 | let status_code = resp.status().as_u16(); 85 | let is_success = resp.status().is_success(); 86 | let body = resp.text().await; 87 | match body { 88 | Ok(body) => { 89 | let data: serde_json::Value = 90 | serde_json::from_str(&body).unwrap_or(json!(&body)); 91 | let message = if is_success { 92 | "Success".to_string() 93 | } else { 94 | "Failed to fetch API".to_string() 95 | }; 96 | let result = json!({ 97 | "status": status_code, 98 | "message": message, 99 | "data": data 100 | }); 101 | Ok(result.to_string()) 102 | } 103 | Err(e) => { 104 | let result = json!({ 105 | "status": 500, 106 | "message": format!("Failed to read response body {}", e), 107 | "data": Option::::None, 108 | }); 109 | Err(result.to_string()) 110 | } 111 | } 112 | } 113 | Err(e) => { 114 | let result = json!({ 115 | "status": 500, 116 | "message": format!("Failed to fetch API {}", e), 117 | "data": Option::::None, 118 | }); 119 | Err(result.to_string()) 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src-tauri/src/main.rs: -------------------------------------------------------------------------------- 1 | // Prevents additional console window on Windows in release, DO NOT REMOVE!! 2 | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] 3 | 4 | mod common; 5 | mod fetch_client; 6 | mod menu; 7 | mod openai_client; 8 | mod dynamo_client; 9 | mod dynamo; 10 | 11 | use fetch_client::fetch_api; 12 | use openai_client::{chat_stream, create_openai_client}; 13 | use dynamo_client::dynamo_api; 14 | 15 | fn main() { 16 | tauri::Builder::default() 17 | .plugin(tauri_plugin_os::init()) 18 | .plugin(tauri_plugin_fs::init()) 19 | .plugin(tauri_plugin_dialog::init()) 20 | .plugin(tauri_plugin_shell::init()) 21 | .plugin(tauri_plugin_store::Builder::new().build()) 22 | .plugin(tauri_plugin_global_shortcut::Builder::new().build()) 23 | .plugin(tauri_plugin_system_info::init()) 24 | .invoke_handler(tauri::generate_handler![ 25 | create_openai_client, 26 | fetch_api, 27 | chat_stream, 28 | dynamo_api, 29 | ]) 30 | .setup(|app| { 31 | menu::create_menu(app)?; 32 | Ok(()) 33 | }) 34 | .run(tauri::generate_context!()) 35 | .expect("error while running tauri application"); 36 | } 37 | -------------------------------------------------------------------------------- /src-tauri/src/menu.rs: -------------------------------------------------------------------------------- 1 | use tauri::menu::{MenuBuilder, MenuItem, SubmenuBuilder}; 2 | use tauri::{App, Emitter, Error, Manager}; 3 | 4 | pub fn create_menu(app: &App) -> Result<(), Error> { 5 | let about_menu = SubmenuBuilder::new(app, "DocKit") 6 | .about(None) // Provide the required argument 7 | .separator() 8 | .services() 9 | .separator() 10 | .hide() 11 | .hide_others() 12 | .show_all() 13 | .separator() 14 | .quit() 15 | .build()?; // Unwrap the Result 16 | 17 | let file_menu = SubmenuBuilder::new(app, "File") 18 | .item( 19 | &MenuItem::with_id( 20 | app, 21 | "save", 22 | &"Save".to_string(), 23 | true, 24 | Some("CommandOrControl+S"), 25 | ) 26 | .unwrap(), 27 | ) 28 | .build()?; // Unwrap the Result 29 | 30 | let edit_menu = SubmenuBuilder::new(app, "Edit") 31 | .undo() 32 | .redo() 33 | .separator() 34 | .cut() 35 | .copy() 36 | .paste() 37 | .select_all() 38 | .build()?; // Unwrap the Result 39 | 40 | let window_menu = SubmenuBuilder::new(app, "Window") 41 | .minimize() 42 | .fullscreen() 43 | .close_window() 44 | .separator() 45 | .build()?; // Unwrap the Result 46 | 47 | let developer_menu = SubmenuBuilder::new(app, "Developer") 48 | .item( 49 | &MenuItem::with_id( 50 | app, 51 | "toggle_dev_tools", 52 | &"Toggle Developer Tools".to_string(), 53 | true, 54 | Some("F12"), 55 | ) 56 | .unwrap(), 57 | ) 58 | .build()?; // Unwrap the Result 59 | 60 | let menu = MenuBuilder::new(app) 61 | .item(&about_menu) 62 | .item(&file_menu) 63 | .item(&edit_menu) 64 | .item(&window_menu) 65 | .item(&developer_menu) 66 | .build()?; // Use the `?` operator 67 | 68 | app.set_menu(menu)?; // Set the built menu 69 | 70 | app.on_menu_event(move |app_handle: &tauri::AppHandle, event| { 71 | let window = app_handle.get_webview_window("main").unwrap(); 72 | 73 | match event.id().0.as_str() { 74 | "save" => { 75 | window.emit("saveFile", ()).unwrap(); 76 | } 77 | "toggle_dev_tools" => 78 | { 79 | #[cfg(debug_assertions)] 80 | if window.is_devtools_open() { 81 | window.close_devtools(); 82 | } else { 83 | window.open_devtools(); 84 | } 85 | } 86 | _ => {} 87 | } 88 | }); 89 | 90 | Ok(()) 91 | } 92 | -------------------------------------------------------------------------------- /src-tauri/tauri.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.tauri.app/config/2", 3 | "productName": "DocKit", 4 | "mainBinaryName": "DocKit", 5 | "version": "../package.json", 6 | "identifier": "club.geekfun.dockit", 7 | "build": { 8 | "beforeDevCommand": "npm run dev", 9 | "beforeBuildCommand": "npm run build", 10 | "frontendDist": "../dist", 11 | "devUrl": "http://localhost:1420" 12 | }, 13 | "bundle": { 14 | "active": true, 15 | "targets": "all", 16 | "windows": { 17 | "webviewInstallMode": { 18 | "type": "skip" 19 | } 20 | }, 21 | "category": "DeveloperTool", 22 | "icon": [ 23 | "icons/32x32.png", 24 | "icons/128x128.png", 25 | "icons/128x128@2x.png", 26 | "icons/icon.icns", 27 | "icons/icon.ico" 28 | ] 29 | }, 30 | "plugins": { 31 | "fs": { 32 | "requireLiteralLeadingDot": false 33 | } 34 | }, 35 | "app": { 36 | "macOSPrivateApi": false, 37 | "security": { 38 | "csp": null, 39 | "capabilities": ["desktop-capability"] 40 | }, 41 | "windows": [ 42 | { 43 | "fullscreen": false, 44 | "resizable": true, 45 | "title": "DocKit", 46 | "width": 1600, 47 | "height": 1000, 48 | "useHttpsScheme": true 49 | } 50 | ] 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 13 | -------------------------------------------------------------------------------- /src/assets/img/theme-auto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geek-fun/dockit/662308de83b8b294a25617ebd9bba66763309a7a/src/assets/img/theme-auto.png -------------------------------------------------------------------------------- /src/assets/img/theme-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geek-fun/dockit/662308de83b8b294a25617ebd9bba66763309a7a/src/assets/img/theme-dark.png -------------------------------------------------------------------------------- /src/assets/img/theme-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geek-fun/dockit/662308de83b8b294a25617ebd9bba66763309a7a/src/assets/img/theme-light.png -------------------------------------------------------------------------------- /src/assets/styles/theme.scss: -------------------------------------------------------------------------------- 1 | html, 2 | body, 3 | #app, 4 | .n-config-provider { 5 | height: 100%; 6 | width: 100%; 7 | } 8 | :root { 9 | --theme-color: #36ad6a; 10 | --theme-color-hover: #19934e; 11 | --dange-color: #cd2158; 12 | --tool-bar-height: 40px; 13 | } 14 | :root[theme='light'] { 15 | --bg-color: #f5f7f9; 16 | --bg-color-secondary: #fFfFfF; 17 | --text-color: #333; 18 | --border-color: #d1d1d1; 19 | --gray-color: #999; 20 | --connect-list-hover-bg: rgba(0, 0, 0, 0.05); 21 | --card-bg-color: #FFF; 22 | } 23 | :root[theme='dark'] { 24 | --bg-color: #101014; 25 | --bg-color-secondary: #1b1b1f; 26 | --text-color: #f1f1f1; 27 | --border-color: #363b41; 28 | --gray-color: #c1c1c1; 29 | --connect-list-hover-bg: rgba(255, 255, 255, 0.05); 30 | --card-bg-color: rgb(38, 38, 42); 31 | } 32 | 33 | body { 34 | background-color: var(--bg-color); 35 | color: var(--text-color); 36 | transition: .3s; 37 | font-size: 14px; 38 | } 39 | -------------------------------------------------------------------------------- /src/assets/svg/dynamoDB.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/svg/elasticsearch.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/theme/naive-theme-overrides.ts: -------------------------------------------------------------------------------- 1 | export const naiveThemeOverrides = { 2 | common: { 3 | primaryColor: '#36ad6a', 4 | primaryColorHover: '#19934e', 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /src/common/base64.ts: -------------------------------------------------------------------------------- 1 | const strToBytes = (base64: string) => { 2 | const binString = atob(base64); 3 | // @ts-ignore 4 | return Uint8Array.from(binString, m => m.codePointAt(0)); 5 | }; 6 | 7 | function bytesToBase64(bytes: Uint8Array) { 8 | const binString = Array.from(bytes, byte => String.fromCodePoint(byte)).join(''); 9 | return btoa(binString); 10 | } 11 | 12 | const base64Encode = (str: string) => bytesToBase64(new TextEncoder().encode(str)); 13 | const base64Decode = (base64: string) => new TextDecoder().decode(strToBytes(base64)); 14 | 15 | export { strToBytes, base64Encode, base64Decode }; 16 | -------------------------------------------------------------------------------- /src/common/crypto.ts: -------------------------------------------------------------------------------- 1 | import { networks } from 'tauri-plugin-system-info-api'; 2 | import { strToBytes } from './base64.ts'; 3 | 4 | const generateKey = async () => { 5 | const macAddress = (await networks()).find( 6 | ({ interface_name }) => interface_name === 'en0', 7 | )?.mac_address_str; 8 | const encoder = new TextEncoder(); 9 | const baseKey = await window.crypto.subtle.importKey( 10 | 'raw', 11 | encoder.encode(macAddress), 12 | 'PBKDF2', 13 | false, 14 | ['deriveKey'], 15 | ); 16 | const iv = window.crypto.getRandomValues(new Uint8Array(16)); 17 | const cryptoKey = await window.crypto.subtle.deriveKey( 18 | { 19 | name: 'PBKDF2', 20 | salt: iv, 21 | iterations: 1000, 22 | hash: 'SHA-256', 23 | }, 24 | baseKey, 25 | { name: 'AES-CBC', length: 256 }, 26 | false, 27 | ['encrypt', 'decrypt'], 28 | ); 29 | 30 | return { cryptoKey, iv }; 31 | }; 32 | 33 | const encryptValue = async (value: string) => { 34 | const encoder = new TextEncoder(); 35 | const data = encoder.encode(value); 36 | const { cryptoKey, iv } = await generateKey(); 37 | 38 | const encrypted = await window.crypto.subtle.encrypt({ name: 'AES-CBC', iv }, cryptoKey, data); 39 | 40 | return Array.from(new Uint8Array(encrypted)) 41 | .map(b => b.toString(16).padStart(2, '0')) 42 | .join(''); 43 | }; 44 | 45 | const decryptValue = async (encryptedValue: string) => { 46 | const decoder = new TextDecoder(); 47 | const { cryptoKey, iv } = await generateKey(); 48 | // const data = encryptedValue.match(/.{1,2}/g)?.map(byte => parseInt(byte, 16)); 49 | const decrypted = await window.crypto.subtle.decrypt( 50 | { name: 'AES-CBC', iv }, 51 | cryptoKey, 52 | strToBytes(encryptedValue), 53 | ); 54 | 55 | return decoder.decode(decrypted); 56 | }; 57 | 58 | export { encryptValue, decryptValue }; 59 | -------------------------------------------------------------------------------- /src/common/customError.ts: -------------------------------------------------------------------------------- 1 | export class CustomError extends Error { 2 | constructor( 3 | public readonly status: number, 4 | public readonly details: string, 5 | ) { 6 | super(); 7 | } 8 | } 9 | 10 | export enum ErrorCodes { 11 | MISSING_GPT_CONFIG = 999, 12 | OPENAI_CLIENT_ERROR = 1000, 13 | } 14 | -------------------------------------------------------------------------------- /src/common/debounceThrottle.ts: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue'; 2 | 3 | // Throttle 4 | export function useThrottle(fn: () => void, delay = 500): () => void { 5 | const throttled = ref(false); 6 | return function throttledFn() { 7 | if (!throttled.value) { 8 | fn(); 9 | throttled.value = true; 10 | setTimeout(() => { 11 | throttled.value = false; 12 | }, delay); 13 | } 14 | }; 15 | } 16 | 17 | // Debounce 18 | export function useDebounce(fn: () => void, delay = 500): () => void { 19 | let timer: NodeJS.Timeout; 20 | return function debouncedFn() { 21 | clearTimeout(timer); 22 | timer = setTimeout(fn, delay); 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /src/common/debug.ts: -------------------------------------------------------------------------------- 1 | import getDebug from 'debug'; 2 | 3 | export const debug = getDebug('dockit'); 4 | -------------------------------------------------------------------------------- /src/common/index.ts: -------------------------------------------------------------------------------- 1 | export * from './customError'; 2 | export * from './debounceThrottle'; 3 | export * from './debug'; 4 | export * from './pureObject'; 5 | export * from './base64'; 6 | export * from './crypto'; 7 | export * from './valueConversion'; 8 | export * from './requestUtil'; 9 | export * from './jsonify.ts'; 10 | -------------------------------------------------------------------------------- /src/common/jsonify.ts: -------------------------------------------------------------------------------- 1 | import JSON5 from 'json5'; 2 | import { JSONParse, JSONStringify } from 'json-with-bigint'; 3 | import { get } from 'lodash'; 4 | 5 | type Replacer = null | Array | ((this: any, key: string, value: any) => any); 6 | 7 | const bigIntReplacer = (originalReplacer: unknown, key: string, value: unknown) => { 8 | if (Array.isArray(originalReplacer) && !originalReplacer.includes(key)) { 9 | return; 10 | } 11 | 12 | let newVal = value; 13 | 14 | if (typeof originalReplacer === 'function') { 15 | newVal = originalReplacer(key, value); 16 | } 17 | 18 | if (typeof newVal === 'bigint') { 19 | newVal = `${newVal}n`; 20 | } 21 | 22 | return newVal; 23 | }; 24 | 25 | const bigIntReviver = (originalReviver: unknown, key: string, value: unknown) => { 26 | if (Array.isArray(originalReviver) && !originalReviver.includes(key)) { 27 | return; 28 | } 29 | let newVal = value; 30 | if (typeof originalReviver === 'function') { 31 | newVal = originalReviver(key, value); 32 | } 33 | 34 | const bigIntVal = get(/^([-+]?\d+)n$/.exec(String(value)), '[1]', undefined); 35 | if (bigIntVal) { 36 | newVal = BigInt(bigIntVal); 37 | } 38 | 39 | return newVal; 40 | }; 41 | 42 | const bigIntStringify = (text: string) => 43 | text.replace(/([-+]?\d+)\b/g, match => { 44 | return Number.isSafeInteger(Number(match)) ? match : `"${match}n"`; 45 | }); 46 | 47 | const bigIntParse = (text: string) => { 48 | return text.replace(/(?<=([:,\[]\s*))["']([-+]?\d+)n["']/g, '$2'); 49 | }; 50 | 51 | export const string5 = (value: any, replacer?: Replacer, space?: string | number): string => 52 | bigIntParse(JSON5.stringify(value, (key, value) => bigIntReplacer(replacer, key, value), space)); 53 | 54 | export const parse5 = (text: string, reviver?: (this: any, key: string, value: any) => any) => 55 | JSON5.parse(bigIntStringify(text), (key, value: string) => bigIntReviver(reviver, key, value)); 56 | 57 | export const jsonify = { 58 | stringify: JSONStringify, 59 | parse: JSONParse, 60 | parse5, 61 | string5, 62 | } as unknown as { 63 | stringify: typeof JSON.stringify; 64 | parse: typeof JSON.parse; 65 | parse5: typeof JSON5.parse; 66 | string5: typeof JSON5.stringify; 67 | }; 68 | -------------------------------------------------------------------------------- /src/common/monaco/completion.ts: -------------------------------------------------------------------------------- 1 | import * as monaco from 'monaco-editor'; 2 | import { paths } from './keywords.ts'; 3 | import { searchTokens } from './tokenlizer.ts'; 4 | import { getSubDsqlTree } from './dsql'; 5 | 6 | const providePathCompletionItems = (lineContent: string) => { 7 | const methods = new Map([ 8 | [/^ge?t?$/gi, 'GET '], 9 | [/^put?$/gi, 'PUT '], 10 | [/^pos?t?$/gi, 'POST '], 11 | [/^de?l?e?t?e?$/gi, 'DELETE '], 12 | ]); 13 | const matchedMethodKey = Array.from(methods.keys()).find(regex => regex.test(lineContent)); 14 | if (matchedMethodKey) { 15 | const method = methods.get(matchedMethodKey); 16 | return { 17 | suggestions: [ 18 | { 19 | label: method, 20 | kind: monaco.languages.CompletionItemKind.Constant, 21 | insertText: method, 22 | }, 23 | ], 24 | }; 25 | } 26 | const isPathMatch = /^(GET|POST|PUT|DELETE)(\s+[a-zA-Z0-9_\/-?\-&,*]*)$/.test(lineContent); 27 | const word = lineContent.split(/[ /]+/).pop() || ''; 28 | if (isPathMatch) { 29 | return { 30 | suggestions: paths 31 | .filter(p => p.startsWith(word)) 32 | .map(keyword => ({ 33 | label: keyword, 34 | kind: monaco.languages.CompletionItemKind.Unit, 35 | insertText: keyword, 36 | })), 37 | }; 38 | } 39 | }; 40 | 41 | const getQueryTreePath = (actionBlockContent: string) => { 42 | const pathStack: string[] = []; 43 | actionBlockContent 44 | .replace(/['"]/g, '') 45 | .replace(/\/\/.*?\n|\/\*[\s\S]*?\*\//g, '') 46 | 47 | .split(/[{\[]/) 48 | .forEach(item => { 49 | const pureItem = item.replace(/\s+/g, ''); 50 | 51 | /[}\]]/.test(pureItem) && pathStack.pop(); 52 | /[\w.]+:$/.test(pureItem) && pathStack.push(pureItem.split(',').pop() || ''); 53 | }); 54 | 55 | return pathStack.map(path => path.replace(/[:},\s]+/g, '')); 56 | }; 57 | 58 | const provideQDSLCompletionItems = ( 59 | textUntilPosition: string, 60 | lineContent: string, 61 | position: monaco.Position, 62 | model: monaco.editor.ITextModel, 63 | ) => { 64 | // const word = textUntilPosition.split(/[ /]+/).pop() || ''; 65 | const closureIndex = isReplaceCompletion(lineContent, textUntilPosition); 66 | 67 | const action = searchTokens.find( 68 | ({ position: { startLineNumber, endLineNumber } }) => 69 | position.lineNumber > startLineNumber && position.lineNumber < endLineNumber, 70 | ); 71 | if (!action) { 72 | return; 73 | } 74 | 75 | const actionBlockContent = model.getValueInRange({ 76 | startLineNumber: action?.position.startLineNumber + 1, 77 | endLineNumber: position.lineNumber, 78 | startColumn: 1, 79 | endColumn: position.column, 80 | }); 81 | const queryAction = action.path.split('/')?.pop()?.replace(/\?.*/g, ''); 82 | 83 | if (!queryAction) { 84 | return; 85 | } 86 | 87 | const queryTreePath = getQueryTreePath(actionBlockContent); 88 | const dsqlSubTree = getSubDsqlTree(queryAction, queryTreePath); 89 | if (!dsqlSubTree) { 90 | return; 91 | } 92 | 93 | const suggestions = Object.entries(dsqlSubTree?.children ?? {}) 94 | .filter(([key]) => key !== '*') 95 | .map(([, value]) => ({ 96 | label: value.label, 97 | kind: monaco.languages.CompletionItemKind.Keyword, 98 | ...{ 99 | insertText: closureIndex < 0 ? value.snippet : value.label, 100 | insertTextRules: 101 | closureIndex < 0 102 | ? monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet 103 | : monaco.languages.CompletionItemInsertTextRule.None, 104 | range: 105 | closureIndex < 0 106 | ? undefined 107 | : new monaco.Range( 108 | position.lineNumber, 109 | position.column, 110 | position.lineNumber, 111 | closureIndex, 112 | ), 113 | }, 114 | })); 115 | 116 | return { suggestions }; 117 | }; 118 | 119 | const isReplaceCompletion = (lineContent: string, textUntilPosition: string) => { 120 | const matches = lineContent?.substring(textUntilPosition.length)?.match(/[,":]/); 121 | if (matches && matches[0]) { 122 | return ( 123 | textUntilPosition.length + 124 | lineContent?.substring(textUntilPosition.length).indexOf(matches[0]) + 125 | 1 126 | ); 127 | } else { 128 | return -1; 129 | } 130 | }; 131 | 132 | const searchCompletionProvider = (model: monaco.editor.ITextModel, position: monaco.Position) => { 133 | const textUntilPosition = model.getValueInRange({ 134 | startLineNumber: position.lineNumber, 135 | endLineNumber: position.lineNumber, 136 | startColumn: 1, 137 | endColumn: position.column, 138 | }); 139 | const lineContent = model.getLineContent(position.lineNumber); 140 | 141 | const methodCompletions = providePathCompletionItems(textUntilPosition); 142 | if (methodCompletions) { 143 | return methodCompletions; 144 | } 145 | 146 | const keywordCompletions = provideQDSLCompletionItems( 147 | textUntilPosition, 148 | lineContent, 149 | position, 150 | model, 151 | ); 152 | 153 | if (keywordCompletions) { 154 | return keywordCompletions; 155 | } 156 | }; 157 | 158 | export { searchCompletionProvider }; 159 | -------------------------------------------------------------------------------- /src/common/monaco/dsql/compoundQueries.ts: -------------------------------------------------------------------------------- 1 | import { matchAllQueries } from './matchAllQueries.ts'; 2 | import { termLevelQueries } from './termLevelQueries.ts'; 3 | import { specializedQueries } from './specializedQueries.ts'; 4 | import { fullTextQueries } from './fullTextQueries.ts'; 5 | 6 | export const compoundQueries = { 7 | dis_max: { 8 | label: 'dis_max', 9 | snippet: `dis_max: {\n\ttie_breaker: 0.7,\n\tboost: 1.2,\n\tqueries: [$0]\n},`, 10 | children: { 11 | '*': { 12 | label: '*', 13 | snippet: `*: {\n\t$0\n},`, 14 | children: { 15 | tie_breaker: { 16 | label: 'tie_breaker', 17 | snippet: 'tie_breaker: $0', 18 | }, 19 | boost: { 20 | label: 'boost', 21 | snippet: 'boost: $0', 22 | }, 23 | }, 24 | }, 25 | queries: { 26 | label: 'queries', 27 | snippet: 'queries: $0', 28 | children: { 29 | ...matchAllQueries, 30 | ...termLevelQueries, 31 | ...specializedQueries, 32 | ...fullTextQueries, 33 | }, 34 | }, 35 | }, 36 | }, 37 | bool: { 38 | label: 'bool', 39 | snippet: `bool: {\n\t$0\n},`, 40 | children: { 41 | must: { 42 | label: 'must', 43 | snippet: `must: [\n\t{\n\t\t$0\n\t}\n],`, 44 | children: { 45 | ...matchAllQueries, 46 | ...termLevelQueries, 47 | ...specializedQueries, 48 | ...fullTextQueries, 49 | }, 50 | }, 51 | filter: { 52 | label: 'filter', 53 | snippet: `filter: [\n\t{\n\t\t$0\n\t}\n],`, 54 | children: { 55 | ...matchAllQueries, 56 | ...termLevelQueries, 57 | ...specializedQueries, 58 | ...fullTextQueries, 59 | }, 60 | }, 61 | should: { 62 | label: 'should', 63 | snippet: 'should: [\n\t{\n\t\t$0\n\t}\n],', 64 | }, 65 | must_not: { 66 | label: 'must_not', 67 | snippet: 'must_not: [\n\t{\n\t\t$0\n\t}\n]', 68 | }, 69 | minimum_should_match: { 70 | label: 'minimum_should_match', 71 | snippet: 'minimum_should_match: 1$0', 72 | }, 73 | boost: { 74 | label: 'boost', 75 | snippet: 'boost: 1$0', 76 | }, 77 | }, 78 | }, 79 | boosting: { 80 | label: 'boosting', 81 | snippet: `boosting: {\n\t$0\n},`, 82 | }, 83 | constant_score: { 84 | label: 'constant_score', 85 | snippet: `constant_score: {\n\tfilter: {$0},\n\tboost: 1.2\n},`, 86 | }, 87 | }; 88 | -------------------------------------------------------------------------------- /src/common/monaco/dsql/fullTextQueries.ts: -------------------------------------------------------------------------------- 1 | export const fullTextQueries = { 2 | match: { 3 | label: 'match', 4 | snippet: `match: {\n\t$0FIELD:'TEXT'\n},`, 5 | children: { 6 | '*': { 7 | label: '*', 8 | snippet: `*: {\n\t$0\n},`, 9 | children: { 10 | operator: { 11 | label: 'operator', 12 | snippet: 'operator: $0', 13 | }, 14 | fuzziness: { 15 | label: 'fuzziness', 16 | snippet: 'fuzziness: $0', 17 | }, 18 | analyzer: { 19 | label: 'analyzer', 20 | snippet: 'analyzer: $0', 21 | }, 22 | prefix_length: { 23 | label: 'prefix_length', 24 | snippet: 'prefix_length: $0', 25 | }, 26 | max_expansions: { 27 | label: 'max_expansions', 28 | snippet: 'max_expansions: $0', 29 | }, 30 | cutoff_frequency: { 31 | label: 'cutoff_frequency', 32 | snippet: 'cutoff_frequency: $0', 33 | }, 34 | query: { 35 | label: 'query', 36 | snippet: 'query: $0', 37 | }, 38 | }, 39 | }, 40 | }, 41 | }, 42 | match_phrase: { 43 | label: 'match_phrase', 44 | snippet: `match_phrase: {\n\t$0\n},`, 45 | children: { 46 | '*': { 47 | label: '*', 48 | snippet: `*: {\n\t$0\n},`, 49 | children: { 50 | query: { 51 | label: 'query', 52 | snippet: 'query: $0', 53 | }, 54 | slop: { 55 | label: 'slop', 56 | snippet: 'slop: $0', 57 | }, 58 | analyzer: { 59 | label: 'analyzer', 60 | snippet: 'analyzer: $0', 61 | }, 62 | }, 63 | }, 64 | }, 65 | }, 66 | match_phrase_prefix: { 67 | label: 'match_phrase_prefix', 68 | snippet: `match_phrase_prefix: {\n\t$0\n},`, 69 | children: { 70 | '*': { 71 | label: '*', 72 | snippet: `*: {\n\t$0\n},`, 73 | children: { 74 | max_expansions: { 75 | label: 'max_expansions', 76 | snippet: 'max_expansions: $0', 77 | }, 78 | query: { 79 | label: 'query', 80 | snippet: 'query: $0', 81 | }, 82 | analyzer: { 83 | label: 'analyzer', 84 | snippet: 'analyzer: $0', 85 | }, 86 | }, 87 | }, 88 | }, 89 | }, 90 | multi_match: { 91 | label: 'multi_match', 92 | snippet: `multi_match: {\n\tquery: '$0', \n\tfields: [],\n},`, 93 | children: { 94 | fields: { 95 | label: 'fields', 96 | snippet: 'fields: [$0]', 97 | }, 98 | use_dis_max: { 99 | label: 'use_dis_max', 100 | snippet: 'use_dis_max: $0', 101 | }, 102 | tie_breaker: { 103 | label: 'tie_breaker', 104 | snippet: 'tie_breaker: $0', 105 | }, 106 | boost: { 107 | label: 'boost', 108 | snippet: 'boost: $0', 109 | }, 110 | query: { 111 | label: 'query', 112 | snippet: 'query: $0', 113 | }, 114 | }, 115 | }, 116 | query_string: { 117 | label: 'query_string', 118 | snippet: `query_string: {\n\tquery: '$0',\n\tdefault_field: 'FIELD'\n},`, 119 | children: { 120 | query: { 121 | label: 'query', 122 | snippet: 'query: $0', 123 | }, 124 | default_field: { 125 | label: 'default_field', 126 | snippet: 'default_field: $0', 127 | }, 128 | default_operator: { 129 | label: 'default_operator', 130 | snippet: 'default_operator: $0', 131 | }, 132 | df: { 133 | label: 'df', 134 | snippet: 'df: $0', 135 | }, 136 | analyzer: { 137 | label: 'analyzer', 138 | snippet: 'analyzer: $0', 139 | }, 140 | sort: { 141 | label: 'sort', 142 | snippet: 'sort: $0', 143 | }, 144 | boost: { 145 | label: 'boost', 146 | snippet: 'boost: $0', 147 | }, 148 | }, 149 | }, 150 | }; 151 | -------------------------------------------------------------------------------- /src/common/monaco/dsql/geoQueries.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geek-fun/dockit/662308de83b8b294a25617ebd9bba66763309a7a/src/common/monaco/dsql/geoQueries.ts -------------------------------------------------------------------------------- /src/common/monaco/dsql/index.ts: -------------------------------------------------------------------------------- 1 | import { DsqlTreeItem } from '../type.ts'; 2 | import { get } from 'lodash'; 3 | import { termLevelQueries } from './termLevelQueries.ts'; 4 | import { specializedQueries } from './specializedQueries.ts'; 5 | import { fullTextQueries } from './fullTextQueries.ts'; 6 | import { matchAllQueries } from './matchAllQueries.ts'; 7 | import { compoundQueries } from './compoundQueries.ts'; 8 | 9 | const dsqlTree: { 10 | [key: string]: { 11 | label: string; 12 | children?: { 13 | [key: string]: DsqlTreeItem; 14 | }; 15 | }; 16 | } = { 17 | _search: { 18 | label: '_search', 19 | children: { 20 | query: { 21 | label: 'query', 22 | snippet: `query: {\n\t$0\n},`, 23 | children: { 24 | ...matchAllQueries, 25 | ...termLevelQueries, 26 | ...specializedQueries, 27 | ...fullTextQueries, 28 | ...compoundQueries, 29 | default_operator: { 30 | label: 'default_operator', 31 | snippet: 'default_operator: $0', 32 | }, 33 | df: { 34 | label: 'df', 35 | snippet: 'df: $0', 36 | }, 37 | analyzer: { 38 | label: 'analyzer', 39 | snippet: 'analyzer: $0', 40 | }, 41 | sort: { 42 | label: 'sort', 43 | snippet: 'sort: $0', 44 | }, 45 | boost: { 46 | label: 'boost', 47 | snippet: 'boost: $0', 48 | }, 49 | }, 50 | }, 51 | from: { 52 | label: 'from', 53 | snippet: 'from: $0', 54 | }, 55 | size: { 56 | label: 'size', 57 | snippet: 'size: $0', 58 | }, 59 | aggs: { 60 | label: 'aggs', 61 | snippet: `aggs: {\n\t$0\n},`, 62 | }, 63 | sort: { 64 | label: 'sort', 65 | snippet: 'sort: $0', 66 | }, 67 | type: { 68 | label: 'type', 69 | snippet: 'type: $0', 70 | }, 71 | version: { 72 | label: 'version', 73 | snippet: 'version: $0', 74 | }, 75 | min_score: { 76 | label: 'min_score', 77 | snippet: 'min_score: $0', 78 | }, 79 | fields: { 80 | label: 'fields', 81 | snippet: 'fields: $0', 82 | }, 83 | script_fields: { 84 | label: 'script_fields', 85 | snippet: 'script_fields: ```\n\t$0\n```', 86 | }, 87 | partial_fields: { 88 | label: 'partial_fields', 89 | snippet: 'partial_fields: ```\n\t$0\n```', 90 | }, 91 | highlight: { 92 | label: 'highlight', 93 | snippet: `highlight: {\n\t$0\n},`, 94 | }, 95 | }, 96 | }, 97 | }; 98 | 99 | const getSubDsqlTree = (action: string, path: Array) => { 100 | let subTree = get(dsqlTree, action); 101 | if (!subTree) { 102 | return; 103 | } 104 | for (const key of path) { 105 | const newSubTree = get(subTree, `children.${key}`) || get(subTree, 'children.*'); 106 | if (newSubTree) { 107 | subTree = newSubTree; 108 | } else { 109 | return; 110 | } 111 | } 112 | 113 | return subTree; 114 | }; 115 | const getKeywordsFromDsqlTree = (tree: typeof dsqlTree): Array => { 116 | return Array.from( 117 | new Set( 118 | Object.entries(tree) 119 | .map(([key, value]) => { 120 | if (key === '*') { 121 | return getKeywordsFromDsqlTree(value.children ?? {}); 122 | } 123 | if (value.children) { 124 | return [key, ...getKeywordsFromDsqlTree(value.children)]; 125 | } 126 | return [key]; 127 | }) 128 | .flat(), 129 | ), 130 | ); 131 | }; 132 | 133 | export { dsqlTree, getSubDsqlTree, getKeywordsFromDsqlTree }; 134 | -------------------------------------------------------------------------------- /src/common/monaco/dsql/joiningQueries.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geek-fun/dockit/662308de83b8b294a25617ebd9bba66763309a7a/src/common/monaco/dsql/joiningQueries.ts -------------------------------------------------------------------------------- /src/common/monaco/dsql/matchAllQueries.ts: -------------------------------------------------------------------------------- 1 | export const matchAllQueries = { 2 | match_all: { 3 | label: 'match_all', 4 | snippet: `match_all: {$0},`, 5 | children: { 6 | boost: { 7 | label: 'boost', 8 | snippet: 'boost: $0', 9 | }, 10 | }, 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /src/common/monaco/dsql/shapeQueries.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geek-fun/dockit/662308de83b8b294a25617ebd9bba66763309a7a/src/common/monaco/dsql/shapeQueries.ts -------------------------------------------------------------------------------- /src/common/monaco/dsql/spanQueries.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geek-fun/dockit/662308de83b8b294a25617ebd9bba66763309a7a/src/common/monaco/dsql/spanQueries.ts -------------------------------------------------------------------------------- /src/common/monaco/dsql/specializedQueries.ts: -------------------------------------------------------------------------------- 1 | export const specializedQueries = { 2 | more_like_this: { 3 | label: 'more_like_this', 4 | snippet: `more_like_this: {\n\tfields: ['$0'],\n\tlike: 'text like this',\n\tmin_term_freq: 1,\n\tmax_query_terms: 12\n},`, 5 | children: { 6 | '*': { 7 | label: '*', 8 | snippet: `*: {\n\t$0\n},`, 9 | children: { 10 | like: { 11 | label: 'like', 12 | snippet: 'like: $0', 13 | }, 14 | min_term_freq: { 15 | label: 'min_term_freq', 16 | snippet: 'min_term_freq: $0', 17 | }, 18 | max_query_terms: { 19 | label: 'max_query_terms', 20 | snippet: 'max_query_terms: $0', 21 | }, 22 | fields: { 23 | label: 'fields', 24 | snippet: 'fields: $0', 25 | }, 26 | }, 27 | }, 28 | }, 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /src/common/monaco/dsql/termLevelQueries.ts: -------------------------------------------------------------------------------- 1 | export const termLevelQueries = { 2 | // exists: {}, //@TODO 3 | fuzzy: { 4 | label: 'fuzzy', 5 | snippet: `fuzzy: {\n\t$0FIELD: {\n\t\tvalue: "VALUE"\n\t}\n},`, 6 | children: { 7 | '*': { 8 | label: '*', 9 | snippet: `*: {\n\t$0\n},`, 10 | children: { 11 | value: { 12 | label: 'value', 13 | snippet: 'value: $0', 14 | }, 15 | boost: { 16 | label: 'boost', 17 | snippet: 'boost: $0', 18 | }, 19 | min_similarity: { 20 | label: 'min_similarity', 21 | snippet: 'min_similarity: $0', 22 | }, 23 | prefix_length: { 24 | label: 'prefix_length', 25 | snippet: 'prefix_length: $0', 26 | }, 27 | max_expansions: { 28 | label: 'max_expansions', 29 | snippet: 'max_expansions: $0', 30 | }, 31 | }, 32 | }, 33 | }, 34 | }, 35 | ids: { 36 | label: 'ids', 37 | snippet: `ids: {\n\tvalues : ['$0']\n},`, 38 | }, 39 | prefix: { 40 | label: 'prefix', 41 | snippet: `prefix: {\n\t$0FIELD: {\n\t\tvalue: "VALUE"\n\t}\n},`, 42 | }, 43 | range: { 44 | label: 'range', 45 | snippet: `range: {\n\t$0FIELD: {\n\t\tgte: 10,\n\t\tlte: 20\n\t}\n},`, 46 | children: { 47 | '*': { 48 | label: '*', 49 | snippet: `*: {\n\t$0\n},`, 50 | children: { 51 | gte: { 52 | label: 'gte', 53 | snippet: 'gte: $0', 54 | }, 55 | gt: { 56 | label: 'gt', 57 | snippet: 'gt: $0', 58 | }, 59 | lte: { 60 | label: 'lte', 61 | snippet: 'lte: $0', 62 | }, 63 | lt: { 64 | label: 'lt', 65 | snippet: 'lt: $0', 66 | }, 67 | format: { 68 | label: 'format', 69 | snippet: 'format: $0', 70 | }, 71 | time_zone: { 72 | label: 'time_zone', 73 | snippet: 'time_zone: $0', 74 | }, 75 | boost: { 76 | label: 'boost', 77 | snippet: 'boost: $0', 78 | }, 79 | }, 80 | }, 81 | }, 82 | }, 83 | regexp: { 84 | label: 'regexp', 85 | snippet: `regexp: {\n\t$0FIELD: 'REGEXP'\n},`, 86 | }, 87 | term: { 88 | label: 'term', 89 | snippet: `term: {\n\t$0FIELD: {\n\t\tvalue: 'VALUE'\n\t},\n},`, 90 | children: { 91 | '*': { 92 | label: '*', 93 | snippet: `*: {\n\t$0\n},`, 94 | children: { 95 | boost: { 96 | label: 'boost', 97 | snippet: 'boost: $0', 98 | }, 99 | }, 100 | }, 101 | }, 102 | }, 103 | terms: { 104 | label: 'terms', 105 | snippet: `terms: {\n\t$0FIELD: []\n},`, 106 | }, 107 | 108 | wildcard: { 109 | label: 'wildcard', 110 | snippet: `wildcard: {\n\t$0FIELD: {\n\t\tvalue: "VALUE"\n\t}\n},`, 111 | children: { 112 | '*': { 113 | label: '*', 114 | snippet: `*: {\n\t$0\n},`, 115 | children: { 116 | value: { 117 | label: 'value', 118 | snippet: 'value: $0', 119 | }, 120 | boost: { 121 | label: 'boost', 122 | snippet: 'boost: $0', 123 | }, 124 | rewrite: { 125 | label: 'rewrite', 126 | snippet: 'rewrite: $0', 127 | }, 128 | }, 129 | }, 130 | }, 131 | }, 132 | }; 133 | -------------------------------------------------------------------------------- /src/common/monaco/dsql/vectorQueries.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geek-fun/dockit/662308de83b8b294a25617ebd9bba66763309a7a/src/common/monaco/dsql/vectorQueries.ts -------------------------------------------------------------------------------- /src/common/monaco/environment.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * refer https://github.com/wobsoriano/codeplayground 3 | * https://github.com/wobsoriano/codeplayground/blob/master/src/components/MonacoEditor.vue 4 | */ 5 | export const monacoEnvironment = { 6 | // @ts-ignore 7 | async getWorker(_, label) { 8 | let worker; 9 | 10 | switch (label) { 11 | case 'json': 12 | // @ts-ignore 13 | worker = await import('monaco-editor/esm/vs/language/json/json.worker?worker'); 14 | break; 15 | case 'css': 16 | case 'scss': 17 | case 'less': 18 | // @ts-ignore 19 | worker = await import('monaco-editor/esm/vs/language/css/css.worker?worker'); 20 | break; 21 | case 'html': 22 | case 'handlebars': 23 | case 'razor': 24 | // @ts-ignore 25 | worker = await import('monaco-editor/esm/vs/language/html/html.worker?worker'); 26 | break; 27 | case 'typescript': 28 | case 'javascript': 29 | // @ts-ignore 30 | worker = await import('monaco-editor/esm/vs/language/typescript/ts.worker?worker'); 31 | break; 32 | default: 33 | // @ts-ignore 34 | worker = await import('monaco-editor/esm/vs/editor/editor.worker?worker'); 35 | } 36 | 37 | return new worker.default(); 38 | }, 39 | }; 40 | -------------------------------------------------------------------------------- /src/common/monaco/index.ts: -------------------------------------------------------------------------------- 1 | import * as monaco from 'monaco-editor'; 2 | 3 | import { executeActions, search } from './lexerRules.ts'; 4 | import { monacoEnvironment } from './environment.ts'; 5 | import { searchCompletionProvider } from './completion.ts'; 6 | 7 | self.MonacoEnvironment = monacoEnvironment; 8 | 9 | monaco.languages.typescript.typescriptDefaults.setEagerModelSync(true); 10 | monaco.languages.register({ id: search.id }); 11 | monaco.languages.setMonarchTokensProvider( 12 | search.id, 13 | search.rules as monaco.languages.IMonarchLanguage, 14 | ); 15 | monaco.languages.setLanguageConfiguration( 16 | search.id, 17 | search.languageConfiguration as monaco.languages.LanguageConfiguration, 18 | ); 19 | monaco.languages.registerCompletionItemProvider(search.id, { 20 | triggerCharacters: ['g', 'p', 'd', '"', "'", ' '], 21 | // @ts-ignore 22 | provideCompletionItems: searchCompletionProvider, 23 | // resolveCompletionItem: searchResolveCompletionItem, 24 | }); 25 | 26 | export * from './type.ts'; 27 | export { monaco, executeActions }; 28 | export * from './tokenlizer.ts'; 29 | export * from './referDoc.ts'; 30 | -------------------------------------------------------------------------------- /src/common/monaco/keywords.ts: -------------------------------------------------------------------------------- 1 | import { dsqlTree, getKeywordsFromDsqlTree } from './dsql'; 2 | 3 | const methods = ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS', 'PATCH', 'TRACE']; 4 | const paths = [ 5 | '_search', 6 | '_cat', 7 | '_count', 8 | '_mapping', 9 | '_cluster', 10 | '_nodes', 11 | '_aliases', 12 | '_doc', 13 | '_update', 14 | '_bulk', 15 | '_search_shards', 16 | '_validate/query', 17 | 'stats', 18 | 'indices', 19 | 'index', 20 | 'type', 21 | 'types', 22 | // query parameters & enum values 23 | 'search_type', 24 | 'query_then_fetch', 25 | 'query_and_fetch', 26 | 'dfs_query_then_fetch', 27 | 'dfs_query_and_fetch', 28 | 'count', 29 | 'scan', 30 | 'preference', 31 | '_primary', 32 | '_primary_first', 33 | '_local', 34 | '_only_node:', 35 | '_prefer_node:', 36 | '_shards:', 37 | ]; 38 | 39 | const dsqlKeywords = getKeywordsFromDsqlTree(dsqlTree); 40 | 41 | const keywords = Array.from(new Set([...methods, ...paths, ...dsqlKeywords])).filter(Boolean); 42 | 43 | export { keywords, paths }; 44 | -------------------------------------------------------------------------------- /src/common/monaco/type.ts: -------------------------------------------------------------------------------- 1 | import { monaco } from './index.ts'; 2 | 3 | export type Decoration = { 4 | id: number; 5 | range: monaco.Range; 6 | options: { isWholeLine: boolean; linesDecorationsClassName: string }; 7 | }; 8 | 9 | export type SearchAction = { 10 | qdsl: string; 11 | position: monaco.Range; 12 | method: string; 13 | index: string; 14 | path: string; 15 | queryParams: string | null; 16 | }; 17 | 18 | export type DsqlTreeItem = { 19 | label: string; 20 | snippet: string; 21 | children?: { [key: string]: DsqlTreeItem }; 22 | }; 23 | 24 | export enum ActionType { 25 | POST_INDEX = 'POST_INDEX', 26 | POST_SEARCH = 'POST_SEARCH', 27 | POST_COUNT = 'POST_COUNT', 28 | GET_SEARCH = 'GET_SEARCH', 29 | POST_UPDATE = 'POST_UPDATE', 30 | DELETE_DOC = 'DELETE_DOC', 31 | PUT_INDEX = 'PUT_INDEX', 32 | DELETE_INDEX = 'DELETE_INDEX', 33 | POST_BULK = 'POST_BULK', 34 | PUT_PUT_INDEX = 'PUT_PUT_INDEX', 35 | PUT_MAPPING = 'PUT_MAPPING', 36 | GET_MAPPING = 'GET_MAPPING', 37 | POST_ALIAS = 'POST_ALIAS', 38 | GET_HEALTH = 'GET_HEALTH', 39 | GET_STATE = 'GET_STATE', 40 | GET_INFO = 'GET_INFO', 41 | HEAD_INDEX = 'HEAD_INDEX', 42 | PUT_AUTO_FOLLOW = 'PUT_AUTO_FOLLOW', 43 | PUT_CCR_FOLLOW = 'PUT_CCR_FOLLOW', 44 | PUT_SLM_POLICY = 'PUT_SLM_POLICY', 45 | PUT_SECURITY_ROLE_MAPPING = 'PUT_SECURITY_ROLE_MAPPING', 46 | PUT_ROLLUP_JOB = 'PUT_ROLLUP_JOB', 47 | PUT_SECURITY_API_KEY = 'PUT_SECURITY_API_KEY', 48 | PUT_INGEST_PIPELINE = 'PUT_INGEST_PIPELINE', 49 | PUT_TRANSFORM = 'PUT_TRANSFORM', 50 | POST_ML_INFER = 'POST_ML_INFER', 51 | POST_MULTI_SEARCH = 'POST_MULTI_SEARCH', 52 | POST_OPEN_INDEX = 'POST_OPEN_INDEX', 53 | PUT_COMPONENT_TEMPLATE = 'PUT_COMPONENT_TEMPLATE', 54 | PUT_ENRICH_POLICY = 'PUT_ENRICH_POLICY', 55 | PUT_TEMPLATE = 'PUT_TEMPLATE', 56 | } 57 | 58 | export enum EngineType { 59 | ELASTICSEARCH = 'ELASTICSEARCH', 60 | OPENSEARCH = 'OPENSEARCH', 61 | } 62 | 63 | export type Monaco = typeof monaco.editor.create; 64 | export type Editor = ReturnType; 65 | -------------------------------------------------------------------------------- /src/common/pureObject.ts: -------------------------------------------------------------------------------- 1 | import { jsonify } from './jsonify.ts'; 2 | 3 | export const pureObject = (obj: unknown) => jsonify.parse(jsonify.stringify(obj)); 4 | 5 | export const inputProps = { 6 | autocapitalize: 'off', 7 | autocomplete: 'off', 8 | // @ts-ignore 9 | spellCheck: false, 10 | autocorrect: 'off', 11 | }; 12 | -------------------------------------------------------------------------------- /src/common/requestUtil.ts: -------------------------------------------------------------------------------- 1 | import { base64Encode } from './base64.ts'; 2 | 3 | export const buildAuthHeader = (username: string | undefined, password: string | undefined) => { 4 | const authorization = 5 | username || password ? `Basic ${base64Encode(`${username}:${password}`)}` : undefined; 6 | return authorization ? { authorization } : undefined; 7 | }; 8 | 9 | export const buildURL = ( 10 | host: string, 11 | port: number, 12 | path: string | undefined, 13 | queryParameters?: string | null, 14 | ) => { 15 | const trimmedPath = path?.startsWith('/') ? path.slice(1) : path; 16 | return `${host}:${port}${trimmedPath ? `/${trimmedPath}` : ''}${queryParameters ? `?${queryParameters}` : ''}`; 17 | }; 18 | -------------------------------------------------------------------------------- /src/common/valueConversion.ts: -------------------------------------------------------------------------------- 1 | export const optionalToNullableInt = (value: string | number | null | undefined) => { 2 | if (value === null || value === undefined) return null; 3 | 4 | const parsed = parseInt(`${value}`, 10); 5 | return isNaN(parsed) ? null : parsed; 6 | }; 7 | -------------------------------------------------------------------------------- /src/components/AppProvider.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 55 | -------------------------------------------------------------------------------- /src/components/RouterMain.vue: -------------------------------------------------------------------------------- 1 | 9 | 12 | -------------------------------------------------------------------------------- /src/components/VersionDetect.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 88 | 89 | 106 | -------------------------------------------------------------------------------- /src/components/markdown-render.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 92 | 93 | 127 | -------------------------------------------------------------------------------- /src/components/path-breadcrumb.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 53 | 54 | 69 | -------------------------------------------------------------------------------- /src/datasources/ApiClients.ts: -------------------------------------------------------------------------------- 1 | import { invoke, InvokeArgs } from '@tauri-apps/api/core'; 2 | import { jsonify } from '../common'; 3 | 4 | export class ApiClientError extends Error { 5 | public status: number; 6 | public details?: string; 7 | 8 | constructor(status: number, message: string, details?: string) { 9 | super(message); 10 | this.status = status; 11 | this.details = details; 12 | } 13 | } 14 | 15 | type ApiClientResponse = { 16 | status: number; 17 | message: string; 18 | data: { [key: string]: unknown }; 19 | }; 20 | export type AwsCredentials = { 21 | access_key_id: string; 22 | secret_access_key: string; 23 | region: string; 24 | }; 25 | export type DynamoApiOptions = { 26 | table_name: string; 27 | operation: string; 28 | payload: unknown; 29 | }; 30 | 31 | export const tauriClient = { 32 | invoke: async (command: string, payload: unknown): Promise => { 33 | try { 34 | const result = await invoke(command, payload as InvokeArgs); 35 | const { status, message, data } = jsonify.parse(result) as ApiClientResponse; 36 | return { status, message, data }; 37 | } catch (err) { 38 | const { status, message, data } = jsonify.parse(err as string) as ApiClientResponse; 39 | throw new ApiClientError(status, message, jsonify.stringify(data)); 40 | } 41 | }, 42 | 43 | invokeDynamoApi: async ( 44 | credentials: AwsCredentials, 45 | options: DynamoApiOptions, 46 | ): Promise => { 47 | try { 48 | const result = await invoke('dynamo_api', { credentials, options }); 49 | const { status, message, data } = jsonify.parse(result) as ApiClientResponse; 50 | return { status, message, data }; 51 | } catch (err) { 52 | const { status, message, data } = jsonify.parse(err as string) as ApiClientResponse; 53 | throw new ApiClientError(status, message, jsonify.stringify(data)); 54 | } 55 | }, 56 | }; 57 | -------------------------------------------------------------------------------- /src/datasources/chatBotApi.ts: -------------------------------------------------------------------------------- 1 | import { invoke } from '@tauri-apps/api/core'; 2 | import { listen } from '@tauri-apps/api/event'; 3 | import { tauriClient } from './ApiClients.ts'; 4 | import { jsonify } from '../common'; 5 | 6 | export enum ProviderEnum { 7 | OPENAI = 'OPENAI', 8 | DEEP_SEEK = 'DEEP_SEEK', 9 | } 10 | 11 | export enum ChatMessageStatus { 12 | SENDING = 'SENDING', 13 | SENT = 'SENT', 14 | FAILED = 'FAILED', 15 | RECEIVED = 'RECEIVED', 16 | } 17 | 18 | export enum ChatMessageRole { 19 | USER = 'USER', 20 | BOT = 'BOT', 21 | } 22 | 23 | export type ChatMessage = { 24 | id: string; 25 | status: ChatMessageStatus; 26 | content: string; 27 | role: ChatMessageRole; 28 | }; 29 | 30 | let receiveRegistration = false; 31 | 32 | const chatBotApi = { 33 | createAssistant: async ({ 34 | apiKey, 35 | prompt, 36 | model, 37 | httpProxy, 38 | }: { 39 | provider: ProviderEnum; 40 | apiKey: string; 41 | prompt: string; 42 | model: string; 43 | httpProxy?: string; 44 | }): Promise<{ assistantId: string; threadId: string }> => { 45 | try { 46 | const { 47 | data: { assistant_id, thread_id }, 48 | } = await tauriClient.invoke('create_assistant', { 49 | apiKey, 50 | model, 51 | instructions: prompt, 52 | httpProxy, 53 | }); 54 | 55 | return { assistantId: assistant_id as string, threadId: thread_id as string }; 56 | } catch (err) { 57 | throw err; 58 | } 59 | }, 60 | 61 | modifyAssistant: async ({ 62 | apiKey, 63 | prompt, 64 | model, 65 | assistantId, 66 | httpProxy, 67 | }: { 68 | provider: ProviderEnum; 69 | apiKey: string; 70 | prompt: string; 71 | model: string; 72 | httpProxy?: string; 73 | assistantId: string; 74 | }) => { 75 | const assistant = tauriClient.invoke('find_assistant', { 76 | apiKey, 77 | assistantId, 78 | model, 79 | httpProxy, 80 | }); 81 | 82 | if (!assistant) { 83 | throw new Error('Assistant not found'); 84 | } 85 | await tauriClient.invoke('modify_assistant', { 86 | apiKey, 87 | assistantId, 88 | model, 89 | instructions: prompt, 90 | httpProxy, 91 | }); 92 | }, 93 | findAssistant: async ({ 94 | apiKey, 95 | assistantId, 96 | model, 97 | httpProxy, 98 | }: { 99 | apiKey: string; 100 | assistantId: string; 101 | model: string; 102 | httpProxy?: string; 103 | provider: ProviderEnum; 104 | }) => { 105 | return await tauriClient.invoke('find_assistant', { 106 | apiKey, 107 | assistantId, 108 | model, 109 | httpProxy, 110 | }); 111 | }, 112 | chatAssistant: async ( 113 | { 114 | assistantId, 115 | threadId, 116 | question, 117 | }: { 118 | assistantId: string; 119 | threadId: string; 120 | question: string; 121 | }, 122 | callback: (event: { 123 | role: ChatMessageRole; 124 | content: Array<{ text: { value: string } }>; 125 | state: string; 126 | }) => void, 127 | ) => { 128 | if (!receiveRegistration) { 129 | await listen('chatbot-message', event => { 130 | callback(jsonify.parse(event.payload)); 131 | }); 132 | receiveRegistration = true; 133 | } 134 | await tauriClient.invoke('chat_assistant', { 135 | assistantId, 136 | threadId, 137 | question, 138 | }); 139 | }, 140 | validateConfig: async (config: { 141 | provider: ProviderEnum; 142 | apiKey: string; 143 | model: string; 144 | httpProxy?: string; 145 | }) => { 146 | try { 147 | await invoke('create_openai_client', config); 148 | return true; 149 | } catch (err) { 150 | return false; 151 | } 152 | }, 153 | createClient: async (config: { 154 | provider: ProviderEnum; 155 | apiKey: string; 156 | model: string; 157 | httpProxy?: string; 158 | }) => { 159 | try { 160 | await invoke('create_openai_client', config); 161 | } catch (err) { 162 | throw err; 163 | } 164 | }, 165 | chatStream: async ( 166 | config: { 167 | provider: ProviderEnum; 168 | model: string; 169 | question: string; 170 | history: Array; 171 | }, 172 | callback: (event: { 173 | role: ChatMessageRole; 174 | content: Array<{ text: { value: string } }>; 175 | state: string; 176 | }) => void, 177 | ) => { 178 | if (!receiveRegistration) { 179 | await listen('chatbot-message', event => { 180 | callback(jsonify.parse(event.payload)); 181 | }); 182 | receiveRegistration = true; 183 | } 184 | 185 | try { 186 | await invoke('chat_stream', config); 187 | } catch (err) { 188 | throw err; 189 | } 190 | }, 191 | }; 192 | 193 | export { chatBotApi }; 194 | -------------------------------------------------------------------------------- /src/datasources/fetchApi.ts: -------------------------------------------------------------------------------- 1 | import { buildAuthHeader, buildURL, CustomError, debug, jsonify } from '../common'; 2 | import { lang } from '../lang'; 3 | import { invoke } from '@tauri-apps/api/core'; 4 | import { get } from 'lodash'; 5 | 6 | type FetchApiOptions = { 7 | method: string; 8 | headers: { 9 | [key: string]: string | number | undefined; 10 | }; 11 | agent: { ssl: boolean } | undefined; 12 | payload?: string; 13 | }; 14 | 15 | const handleFetch = (result: { data: unknown; status: number; details: string | undefined }) => { 16 | if ([404, 400].includes(result.status) || (result.status >= 200 && result.status < 300)) { 17 | return result.data || jsonify.parse(result.details || ''); 18 | } 19 | if (result.status === 401) { 20 | throw new CustomError(result.status, lang.global.t('connection.unAuthorized')); 21 | } 22 | if (result.status === 403) { 23 | throw new CustomError(result.status, get(result, 'data.error.reason', result.details || '')); 24 | } 25 | throw new CustomError(result.status, result.details || ''); 26 | }; 27 | 28 | const fetchWrapper = async ({ 29 | method, 30 | path, 31 | queryParameters, 32 | payload, 33 | host, 34 | port, 35 | username, 36 | password, 37 | ssl, 38 | }: { 39 | method: string; 40 | path?: string; 41 | queryParameters?: string; 42 | payload?: string; 43 | username?: string; 44 | password?: string; 45 | host: string; 46 | port: number; 47 | ssl: boolean; 48 | }) => { 49 | try { 50 | const url = buildURL(host, port, path, queryParameters); 51 | const { data, status, details } = await fetchRequest(url, { 52 | method, 53 | headers: { ...buildAuthHeader(username, password) }, 54 | payload, 55 | agent: { ssl }, 56 | }); 57 | return handleFetch({ data, status, details }); 58 | } catch (err) { 59 | throw err; 60 | } 61 | }; 62 | 63 | const fetchRequest = async ( 64 | url: string, 65 | { method, headers: inputHeaders, payload, agent: agentSslConf }: FetchApiOptions, 66 | ) => { 67 | const agent = { ssl: url.startsWith('https') && agentSslConf?.ssl }; 68 | 69 | const headers = jsonify.parse( 70 | jsonify.stringify({ 'Content-Type': 'application/json', ...inputHeaders }), 71 | ); 72 | try { 73 | const response = await invoke('fetch_api', { 74 | url, 75 | options: { method, headers, body: payload ?? undefined, agent }, 76 | }); 77 | 78 | const { status, message, data } = jsonify.parse(response) as { 79 | status: number; 80 | message: string; 81 | data: unknown; 82 | }; 83 | 84 | if (status >= 200 && status < 500) { 85 | return { status, message, data }; 86 | } 87 | throw new CustomError(status, message); 88 | } catch (e) { 89 | const error = typeof e == 'string' ? new CustomError(500, e) : (e as CustomError); 90 | const details = error.details || error.message; 91 | debug('error encountered while node-fetch fetch target:', e); 92 | return { 93 | status: error.status || 500, 94 | details: typeof details === 'string' ? details : jsonify.stringify(details), 95 | }; 96 | } 97 | }; 98 | 99 | const loadHttpClient = (con: { 100 | host: string; 101 | port: number; 102 | username?: string; 103 | password?: string; 104 | sslCertVerification: boolean; 105 | }) => ({ 106 | get: async (path?: string, queryParameters?: string, payload?: string) => 107 | fetchWrapper({ 108 | ...con, 109 | method: 'GET', 110 | path, 111 | queryParameters, 112 | payload, 113 | ssl: con.sslCertVerification, 114 | }), 115 | post: async (path: string, queryParameters?: string, payload?: string) => 116 | fetchWrapper({ 117 | ...con, 118 | method: 'POST', 119 | path, 120 | queryParameters, 121 | payload, 122 | ssl: con.sslCertVerification, 123 | }), 124 | put: async (path: string, queryParameters?: string, payload?: string) => 125 | fetchWrapper({ 126 | ...con, 127 | method: 'PUT', 128 | path, 129 | queryParameters, 130 | payload, 131 | ssl: con.sslCertVerification, 132 | }), 133 | 134 | delete: async (path: string, queryParameters?: string, payload?: string) => 135 | fetchWrapper({ 136 | ...con, 137 | method: 'DELETE', 138 | path, 139 | queryParameters, 140 | payload, 141 | ssl: con.sslCertVerification, 142 | }), 143 | }); 144 | 145 | export { loadHttpClient }; 146 | -------------------------------------------------------------------------------- /src/datasources/index.ts: -------------------------------------------------------------------------------- 1 | export * from './storeApi'; 2 | export * from './sourceFileApi'; 3 | export * from './fetchApi'; 4 | export * from './chatBotApi'; 5 | export * from './dynamoApi.ts'; 6 | export * from './esApi.ts'; 7 | -------------------------------------------------------------------------------- /src/datasources/storeApi.ts: -------------------------------------------------------------------------------- 1 | import { LazyStore } from '@tauri-apps/plugin-store'; 2 | 3 | const store = new LazyStore('.store.dat'); 4 | const storeApi = { 5 | get: async (key: string, defaultValue: T): Promise => { 6 | // if (key === 'chats') { 7 | // await store.set(key, null); 8 | // await store.save(); 9 | // } 10 | const val = (await store.get(key)) ?? defaultValue; 11 | return val as T; 12 | }, 13 | 14 | set: async (key: string, value: T) => { 15 | await store.set(key, value); 16 | await store.save(); 17 | }, 18 | getSecret: async (key: string, defaultValue: T) => { 19 | const encryptedValue = (await store.get(key)) || defaultValue; 20 | return encryptedValue as T; 21 | }, 22 | setSecret: async (key: string, value: unknown) => { 23 | await store.set(key, value); 24 | await store.save(); 25 | }, 26 | }; 27 | 28 | export { storeApi }; 29 | -------------------------------------------------------------------------------- /src/lang/index.ts: -------------------------------------------------------------------------------- 1 | import { createI18n, useI18n } from 'vue-i18n'; 2 | import { enUS } from './enUS'; 3 | import { zhCN } from './zhCN'; 4 | 5 | const langType = localStorage.lang || 'auto'; 6 | let langName = langType; 7 | if (langType === 'auto') { 8 | langName = navigator.language === 'zh-CN' ? 'zhCN' : 'enUS'; 9 | } 10 | 11 | const lang = createI18n({ 12 | globalInjection: true, 13 | locale: langName, 14 | legacy: false, 15 | messages: { 16 | zhCN, 17 | enUS, 18 | }, 19 | }); 20 | const useLang = useI18n; 21 | 22 | export { lang, useLang }; 23 | -------------------------------------------------------------------------------- /src/layout/components/the-aside-icon.vue: -------------------------------------------------------------------------------- 1 | 9 | 12 | -------------------------------------------------------------------------------- /src/layout/components/the-aside.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 154 | 155 | 211 | -------------------------------------------------------------------------------- /src/layout/components/tool-bar-right.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 46 | 47 | 87 | -------------------------------------------------------------------------------- /src/layout/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 16 | 17 | 28 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import App from './App.vue'; 3 | import { createPinia } from 'pinia'; 4 | import { router } from './router'; 5 | import { lang } from './lang'; 6 | import piniaPluginPersistence from 'pinia-plugin-persistedstate'; 7 | 8 | import './assets/styles/normalize.css'; 9 | import './assets/styles/theme.scss'; 10 | 11 | 12 | const pinia = createPinia(); 13 | pinia.use(piniaPluginPersistence); 14 | 15 | const app = createApp(App); 16 | 17 | app.use(pinia); 18 | app.use(router); 19 | app.use(lang); 20 | 21 | app.mount('#app'); 22 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory } from 'vue-router'; 2 | import { useUserStore } from '../store'; 3 | 4 | const LOGIN_PATH = '/login'; 5 | 6 | const router = createRouter({ 7 | history: createWebHistory(), 8 | scrollBehavior: () => ({ left: 0, top: 0 }), 9 | routes: [ 10 | { 11 | path: '/login', 12 | name: 'Login', 13 | meta: { 14 | keepAlive: false, 15 | }, 16 | component: () => import('../views/login/index.vue'), 17 | }, 18 | { 19 | path: '/', 20 | name: 'Layout', 21 | meta: { 22 | keepAlive: false, 23 | }, 24 | component: () => import('../layout/index.vue'), 25 | redirect: '/connect/:filePath?', 26 | children: [ 27 | { 28 | name: 'Connect', 29 | path: '/connect/:filePath?', 30 | meta: { 31 | keepAlive: false, 32 | }, 33 | component: () => import('../views/connect/index.vue'), 34 | }, 35 | { 36 | name: 'Manage', 37 | path: '/manage', 38 | meta: { 39 | keepAlive: false, 40 | }, 41 | component: () => import('../views/manage/index.vue'), 42 | }, 43 | { 44 | name: 'History', 45 | path: '/history', 46 | meta: { 47 | keepAlive: false, 48 | }, 49 | component: () => import('../views/history/index.vue'), 50 | }, 51 | { 52 | name: 'Setting', 53 | path: '/setting', 54 | meta: { 55 | keepAlive: false, 56 | }, 57 | component: () => import('../views/setting/index.vue'), 58 | }, 59 | { 60 | name: 'File', 61 | path: '/file', 62 | meta: { 63 | keepAlive: false, 64 | }, 65 | component: () => import('../views/file/index.vue'), 66 | }, 67 | { 68 | name: 'BackupRestore', 69 | path: '/backup-restore', 70 | meta: { 71 | keepAlive: false, 72 | }, 73 | component: () => import('../views/backup-restore/index.vue'), 74 | }, 75 | ], 76 | }, 77 | ], 78 | }); 79 | 80 | router.beforeEach(async (to, _, next) => { 81 | const userStore = useUserStore(); 82 | const token = userStore.getToken; 83 | if (to.meta.requiresAuth && !token) { 84 | next(LOGIN_PATH); 85 | } else { 86 | next(); 87 | } 88 | }); 89 | 90 | export { router }; 91 | -------------------------------------------------------------------------------- /src/store/appStore.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | import { pureObject } from '../common'; 3 | import { chatBotApi, ProviderEnum, storeApi } from '../datasources'; 4 | import { lang } from '../lang'; 5 | 6 | export enum ThemeType { 7 | AUTO = 'auto', 8 | DARK = 'dark', 9 | LIGHT = 'light', 10 | } 11 | 12 | export enum LanguageType { 13 | AUTO = 'auto', 14 | ZH_CN = 'zhCN', 15 | EN_US = 'enUS', 16 | } 17 | 18 | export type AiConfig = { 19 | apiKey: string; 20 | model: string; 21 | prompt?: string; 22 | httpProxy?: string; 23 | enabled: boolean; 24 | provider: ProviderEnum; 25 | }; 26 | export const useAppStore = defineStore('app', { 27 | state: (): { 28 | themeType: ThemeType; 29 | languageType: LanguageType; 30 | connectPanel: boolean; 31 | uiThemeType: Exclude; 32 | skipVersion: string; 33 | aiConfigs: Array; 34 | } => { 35 | return { 36 | themeType: ThemeType.AUTO, 37 | languageType: LanguageType.AUTO, 38 | connectPanel: true, // 39 | uiThemeType: ThemeType.LIGHT, 40 | skipVersion: '', 41 | aiConfigs: [], 42 | }; 43 | }, 44 | persist: true, 45 | actions: { 46 | setConnectPanel() { 47 | this.connectPanel = !this.connectPanel; 48 | }, 49 | setThemeType(themeType: ThemeType) { 50 | const uiThemType = 51 | themeType === ThemeType.AUTO 52 | ? window.matchMedia('(prefers-color-scheme: light)').matches 53 | ? ThemeType.LIGHT 54 | : ThemeType.DARK 55 | : themeType; 56 | document.documentElement.setAttribute('theme', uiThemType); 57 | this.uiThemeType = uiThemType; 58 | this.themeType = themeType; 59 | }, 60 | setUiThemeType(sysPrefer: Exclude) { 61 | const uiThemType = this.themeType === ThemeType.AUTO ? sysPrefer : this.themeType; 62 | document.documentElement.setAttribute('theme', uiThemType); 63 | this.uiThemeType = uiThemType; 64 | }, 65 | getEditorTheme() { 66 | return this.uiThemeType === ThemeType.DARK ? 'vs-dark' : 'vs-light'; 67 | }, 68 | 69 | async fetchAiConfigs() { 70 | this.aiConfigs = await storeApi.get>('aiConfigs', []); 71 | }, 72 | 73 | async saveAiConfig(aiConfig?: AiConfig) { 74 | if (!aiConfig) { 75 | return; 76 | } 77 | 78 | if (aiConfig.enabled && !(await chatBotApi.validateConfig(aiConfig))) { 79 | throw new Error(lang.global.t('setting.ai.invalid')); 80 | } 81 | 82 | const config = this.aiConfigs.find(({ provider }) => provider === aiConfig.provider); 83 | if (config) { 84 | Object.assign(config, aiConfig); 85 | } else { 86 | this.aiConfigs.push(aiConfig); 87 | } 88 | 89 | await storeApi.setSecret('aiConfigs', pureObject(this.aiConfigs)); 90 | }, 91 | }, 92 | }); 93 | -------------------------------------------------------------------------------- /src/store/chatStore.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | import { ulid } from 'ulidx'; 3 | import { lang } from '../lang'; 4 | import { CustomError, ErrorCodes, pureObject } from '../common'; 5 | import { useTabStore } from './tabStore'; 6 | import { 7 | chatBotApi, 8 | ChatMessage, 9 | ChatMessageRole, 10 | ChatMessageStatus, 11 | ProviderEnum, 12 | storeApi, 13 | } from '../datasources'; 14 | import { AiConfig } from './appStore.ts'; 15 | import { ElasticsearchConnection } from './connectionStore.ts'; 16 | 17 | export const getOpenAiConfig = async () => { 18 | const aigcConfigs = await storeApi.getSecret('aiConfigs', []); 19 | const enabledAigc = aigcConfigs.find((config: AiConfig) => config.enabled); 20 | 21 | if (!enabledAigc) { 22 | throw new CustomError(ErrorCodes.MISSING_GPT_CONFIG, lang.global.t('setting.ai.missing')); 23 | } 24 | return enabledAigc; 25 | }; 26 | 27 | type Chat = { 28 | id: string; 29 | provider: ProviderEnum; 30 | messages: Array; 31 | }; 32 | 33 | export const useChatStore = defineStore('chat', { 34 | state: (): { activeChat: Chat | undefined; chats: Array; insertBoard: string } => { 35 | return { 36 | chats: [], 37 | activeChat: undefined, 38 | insertBoard: '', 39 | }; 40 | }, 41 | actions: { 42 | async fetchChats() { 43 | const { apiKey, httpProxy, model, provider } = await getOpenAiConfig(); 44 | 45 | const { chats = [], activeChat } = await storeApi.get<{ chats: Chat[]; activeChat: Chat }>( 46 | 'chatStore', 47 | {} as { chats: Chat[]; activeChat: Chat }, 48 | ); 49 | 50 | this.chats = chats; 51 | this.activeChat = activeChat ?? this.chats.reverse().find(chat => chat.provider === provider); 52 | 53 | if (!this.activeChat) { 54 | this.activeChat = { 55 | id: ulid(), 56 | provider: provider, 57 | messages: [], 58 | }; 59 | this.chats.push(this.activeChat); 60 | } 61 | 62 | try { 63 | await chatBotApi.createClient({ provider, apiKey, model, httpProxy }); 64 | this.activeChat.messages[0] = { 65 | id: ulid(), 66 | status: ChatMessageStatus.SENDING, 67 | role: ChatMessageRole.BOT, 68 | content: lang.global.t('setting.ai.firstMsg'), 69 | }; 70 | } catch (err) { 71 | throw new CustomError(ErrorCodes.OPENAI_CLIENT_ERROR, (err as Error).message); 72 | } 73 | 74 | await storeApi.set( 75 | 'chatStore', 76 | pureObject({ activeChat: this.activeChat, chats: this.chats }), 77 | ); 78 | }, 79 | 80 | async sendMessage(content: string) { 81 | if (!this.activeChat) { 82 | throw new CustomError(ErrorCodes.MISSING_GPT_CONFIG, lang.global.t('setting.ai.missing')); 83 | } 84 | 85 | const { messages } = this.activeChat; 86 | const requestMsg = { 87 | id: ulid(), 88 | status: ChatMessageStatus.SENDING, 89 | role: ChatMessageRole.USER, 90 | content, 91 | }; 92 | messages.push(requestMsg); 93 | await storeApi.set( 94 | 'chatStore', 95 | pureObject({ activeChat: this.activeChat, chats: this.chats }), 96 | ); 97 | 98 | const tabStore = useTabStore(); 99 | const {activeConnection} = tabStore; 100 | 101 | const index = (activeConnection as ElasticsearchConnection)?.activeIndex; 102 | 103 | const question = index 104 | ? `${lang.global.t('setting.ai.defaultPrompt')} 105 | database context: 106 | - database: ElasticSearch 107 | - indexName: ${index.index}, 108 | - indexMapping: ${index.mapping} 109 | user's question: ${content} ` 110 | : `${lang.global.t('setting.ai.defaultPrompt')} 111 | database context: 112 | - database: ElasticSearch 113 | user's question: ${content}`; 114 | 115 | try { 116 | const { model, provider } = await getOpenAiConfig(); 117 | const history = messages.filter(({ status }) => 118 | [ChatMessageStatus.RECEIVED, ChatMessageStatus.SENT].includes(status), 119 | ); 120 | await chatBotApi.chatStream({ provider, model, question, history }, event => { 121 | const receivedStr = event.content.map(({ text }) => text.value).join(''); 122 | if (event.state === 'CREATED') { 123 | this.activeChat!.messages[this.activeChat!.messages.length - 1].status = 124 | ChatMessageStatus.SENT; 125 | this.activeChat!.messages.push({ 126 | id: ulid(), 127 | status: ChatMessageStatus.RECEIVED, 128 | role: ChatMessageRole.BOT, 129 | content: receivedStr, 130 | }); 131 | } else if (event.state === 'IN_PROGRESS') { 132 | this.activeChat!.messages[this.activeChat!.messages.length - 1].content += receivedStr; 133 | } else if (event.state === 'COMPLETED') { 134 | storeApi.set( 135 | 'chatStore', 136 | pureObject({ activeChat: this.activeChat, chats: this.chats }), 137 | ); 138 | } 139 | }); 140 | } catch (err) { 141 | requestMsg.status = ChatMessageStatus.FAILED; 142 | await storeApi.set( 143 | 'chatStore', 144 | pureObject({ activeChat: this.activeChat, chats: this.chats }), 145 | ); 146 | throw new CustomError(ErrorCodes.OPENAI_CLIENT_ERROR, (err as Error).message); 147 | } 148 | }, 149 | 150 | async deleteChat() { 151 | if (!this.activeChat) { 152 | return; 153 | } 154 | 155 | const chatIndex = this.chats.findIndex(chat => chat.id === this.activeChat!.id); 156 | if (chatIndex !== -1) { 157 | this.chats.splice(chatIndex, 1); 158 | this.activeChat = undefined; 159 | await storeApi.set('chatStore', pureObject({ activeChat: undefined, chats: this.chats })); 160 | } 161 | }, 162 | }, 163 | }); 164 | -------------------------------------------------------------------------------- /src/store/fileStore.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | import { PathInfo, sourceFileApi } from '../datasources'; 3 | import { CustomError } from '../common'; 4 | import { get } from 'lodash'; 5 | 6 | export enum ToolBarAction { 7 | ADD_DOCUMENT = 'ADD_DOCUMENT', 8 | ADD_FOLDER = 'ADD_FOLDER', 9 | OPEN_FOLDER = 'OPEN_FOLDER', 10 | } 11 | 12 | export enum ContextMenuAction { 13 | CONTEXT_MENU_ACTION_OPEN = 'CONTEXT_MENU_ACTION_OPEN', 14 | CONTEXT_MENU_ACTION_RENAME = 'CONTEXT_MENU_ACTION_RENAME', 15 | CONTEXT_MENU_ACTION_DELETE = 'CONTEXT_MENU_ACTION_DELETE', 16 | CONTEXT_MENU_ACTION_NEW_FILE = 'CONTEXT_MENU_ACTION_NEW_FILE', 17 | CONTEXT_MENU_ACTION_NEW_FOLDER = 'CONTEXT_MENU_ACTION_NEW_FOLDER', 18 | } 19 | 20 | export const useFileStore = defineStore('fileStore', { 21 | state(): { 22 | fileContent: string; 23 | fileList: PathInfo[]; 24 | activePath: PathInfo | undefined; 25 | } { 26 | return { 27 | fileContent: '', 28 | activePath: undefined, 29 | fileList: [], 30 | }; 31 | }, 32 | persist: true, 33 | getters: { 34 | breadCrumbPath: (state): string => { 35 | return state.activePath?.displayPath ?? ''; 36 | }, 37 | }, 38 | actions: { 39 | async selectDirectory(path?: string) { 40 | try { 41 | const selectedPath = await sourceFileApi.selectFolder(path); 42 | 43 | const pathInfo = await sourceFileApi.getPathInfo( 44 | selectedPath ?? this.activePath?.path ?? '', 45 | ); 46 | 47 | await this.fetchFileList(pathInfo?.path); 48 | 49 | this.activePath = pathInfo; 50 | } catch (error) { 51 | throw new CustomError( 52 | get(error, 'status', 500), 53 | get(error, 'details', get(error, 'message', '')), 54 | ); 55 | } 56 | }, 57 | 58 | async changeDirectory(path?: string) { 59 | try { 60 | const pathInfo = await sourceFileApi.getPathInfo( 61 | path ?? this.activePath?.path ?? '.dockit', 62 | ); 63 | if (!pathInfo) { 64 | throw new CustomError(404, 'Folder not found'); 65 | } 66 | 67 | await this.fetchFileList(pathInfo.path); 68 | 69 | this.activePath = pathInfo; 70 | } catch (error) { 71 | throw new CustomError( 72 | get(error, 'status', 500), 73 | get(error, 'details', get(error, 'message', '')), 74 | ); 75 | } 76 | }, 77 | 78 | async createFileOrFolder(action: ToolBarAction, name: string) { 79 | const path = this.activePath?.path.endsWith('.search') 80 | ? this.activePath?.path.substring(0, this.activePath?.path.lastIndexOf('/')) 81 | : this.activePath?.path; 82 | 83 | const targetPath = `${path}/${name}`; 84 | 85 | if (action === ToolBarAction.ADD_DOCUMENT) { 86 | await sourceFileApi.saveFile(targetPath, ''); 87 | } else { 88 | await sourceFileApi.createFolder(targetPath); 89 | } 90 | await this.fetchFileList(this.activePath?.path); 91 | }, 92 | 93 | async deleteFileOrFolder(path: string) { 94 | await sourceFileApi.deleteFileOrFolder(path); 95 | await this.fetchFileList(this.activePath?.path); 96 | }, 97 | 98 | async renameFileOrFolder(oldPath: string, newPath: string) { 99 | await sourceFileApi.renameFileOrFolder(oldPath, newPath); 100 | await this.fetchFileList(this.activePath?.path); 101 | }, 102 | 103 | async fetchFileList(inputPath?: string) { 104 | try { 105 | this.fileList = await sourceFileApi.readDir(inputPath ?? this.activePath?.path); 106 | } catch (error) { 107 | throw new CustomError( 108 | get(error, 'status', 500), 109 | get(error, 'details', get(error, 'message', '')), 110 | ); 111 | } 112 | }, 113 | }, 114 | }); 115 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | export * from './appStore'; 2 | export * from './userStore'; 3 | export * from './connectionStore'; 4 | export * from './fileStore.ts'; 5 | export * from './chatStore'; 6 | export * from './clusterManageStore'; 7 | export * from './backupRestoreStore'; 8 | export * from './tabStore.ts'; 9 | -------------------------------------------------------------------------------- /src/store/userStore.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | 3 | export const useUserStore = defineStore('user', { 4 | state: () => ({ 5 | accessToken: '', // 访问令牌 6 | }), 7 | getters: { 8 | getToken: (state) => state.accessToken, 9 | }, 10 | actions: { 11 | setToken(accessToken: string): void { 12 | this.accessToken = accessToken; 13 | }, 14 | resetToken(): void { 15 | this.accessToken = ''; 16 | }, 17 | }, 18 | persist: { 19 | pick: ['accessToken'], 20 | storage: localStorage, 21 | }, 22 | }); 23 | -------------------------------------------------------------------------------- /src/views/backup-restore/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 18 | 19 | 42 | -------------------------------------------------------------------------------- /src/views/connect/components/floating-menu.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/views/connect/index.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 107 | 108 | 156 | -------------------------------------------------------------------------------- /src/views/editor/dynamo-editor/components/sql-editor.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/views/editor/dynamo-editor/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/views/editor/es-editor/display-editor.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/views/file/components/context-menu.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 52 | 53 | 78 | -------------------------------------------------------------------------------- /src/views/file/components/file-list.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 135 | 136 | 171 | -------------------------------------------------------------------------------- /src/views/file/components/tool-bar.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 58 | 59 | 80 | -------------------------------------------------------------------------------- /src/views/file/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 12 | 13 | 21 | -------------------------------------------------------------------------------- /src/views/history/components/history-empty.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 16 | 17 | 42 | -------------------------------------------------------------------------------- /src/views/history/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 13 | 14 | 31 | -------------------------------------------------------------------------------- /src/views/login/index.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 78 | 79 | 106 | -------------------------------------------------------------------------------- /src/views/manage/components/cluster-state.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 54 | 55 | 96 | -------------------------------------------------------------------------------- /src/views/manage/components/switch-alias-dialog.vue: -------------------------------------------------------------------------------- 1 | 64 | 65 | 171 | 172 | 185 | -------------------------------------------------------------------------------- /src/views/manage/index.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 84 | 85 | 106 | -------------------------------------------------------------------------------- /src/views/setting/components/about-us.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 35 | 36 | 74 | -------------------------------------------------------------------------------- /src/views/setting/components/basic.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 78 | 79 | 148 | -------------------------------------------------------------------------------- /src/views/setting/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 22 | 23 | 51 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module '*.vue' { 4 | import type { DefineComponent } from 'vue'; 5 | const component: DefineComponent<{}, {}, any>; 6 | export default component; 7 | } 8 | -------------------------------------------------------------------------------- /tests/fixtures/index.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geek-fun/dockit/662308de83b8b294a25617ebd9bba66763309a7a/tests/fixtures/index.ts -------------------------------------------------------------------------------- /tests/index.test.ts: -------------------------------------------------------------------------------- 1 | describe('test jest', () => { 2 | it('should true', () => { 3 | expect(true).toBe(true); 4 | }); 5 | }); 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": [ 7 | "ES2021", 8 | "DOM", 9 | "DOM.Iterable" 10 | ], 11 | "skipLibCheck": true, 12 | /* Bundler mode */ 13 | "moduleResolution": "bundler", 14 | "allowImportingTsExtensions": true, 15 | "resolveJsonModule": true, 16 | "isolatedModules": true, 17 | "noEmit": true, 18 | "jsx": "preserve", 19 | /* Linting */ 20 | "strict": true, 21 | "noUnusedLocals": true, 22 | "noUnusedParameters": true, 23 | "noFallthroughCasesInSwitch": true, 24 | "esModuleInterop": true 25 | }, 26 | "include": [ 27 | "src/**/*.ts", 28 | "src/**/*.d.ts", 29 | "*.d.ts", 30 | "src/**/*.tsx", 31 | "src/**/*.vue" 32 | ], 33 | "exclude": [ 34 | "node_modules" 35 | ], 36 | "references": [ 37 | { 38 | "path": "./tsconfig.node.json" 39 | } 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": [ 10 | "vite.config.ts" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import vue from '@vitejs/plugin-vue'; 3 | import AutoImport from 'unplugin-auto-import/vite'; 4 | import Components from 'unplugin-vue-components/vite'; 5 | import { NaiveUiResolver } from 'unplugin-vue-components/resolvers'; 6 | import svgLoader from 'vite-svg-loader'; 7 | 8 | // https://vitejs.dev/config/ 9 | export default defineConfig({ 10 | plugins: [ 11 | vue(), 12 | svgLoader(), 13 | AutoImport({ 14 | dts: './auto-import.d.ts', 15 | eslintrc: { 16 | enabled: true, 17 | filepath: './.eslintrc-auto-import.json', 18 | globalsPropValue: true, 19 | }, 20 | imports: [ 21 | 'vue', 22 | 'vue-router', 23 | { 24 | 'naive-ui': ['useDialog', 'useMessage', 'useNotification', 'useLoadingBar'], 25 | }, 26 | ], 27 | }), 28 | Components({ 29 | resolvers: [NaiveUiResolver()], 30 | }), 31 | ], 32 | 33 | envPrefix: [ 34 | 'VITE_', 35 | 'TAURI_PLATFORM', 36 | 'TAURI_ARCH', 37 | 'TAURI_FAMILY', 38 | 'TAURI_PLATFORM_VERSION', 39 | 'TAURI_PLATFORM_TYPE', 40 | 'TAURI_DEBUG', 41 | ], 42 | // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` 43 | // 44 | // 1. prevent vite from obscuring rust errors 45 | clearScreen: false, 46 | // 2. tauri expects a fixed port, fail if that port is not available 47 | server: { 48 | port: 1420, 49 | strictPort: true, 50 | watch: { 51 | // 3. tell vite to ignore watching `src-tauri` 52 | ignored: ['**/src-tauri/**'], 53 | }, 54 | }, 55 | build: { 56 | // Tauri uses Chromium on Windows and WebKit on macOS and Linux 57 | target: process.env.TAURI_PLATFORM == 'windows' ? 'chrome105' : 'safari13', 58 | // don't minify for debug builds 59 | minify: !process.env.TAURI_DEBUG ? 'esbuild' : false, 60 | // produce sourcemaps for debug builds 61 | sourcemap: !!process.env.TAURI_DEBUG, 62 | }, 63 | resolve: { 64 | alias: { 65 | '@': '/src' 66 | } 67 | } 68 | }); 69 | --------------------------------------------------------------------------------