├── .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 | [](https://github.com/geek-fun/dockit/actions/workflows/node.yml)
9 | [](https://github.com/geek-fun/dockit/actions/workflows/release.yml)
10 | [](https://snyk.io/test/github/geek-fun/dockit)
11 | [](https://codecov.io/gh/geek-fun/dockit)
12 | [](https://badge.fury.io/gh/geek-fun%2Fdockit)
13 | [](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 | 
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 |
65 |
66 | ### Sponsor
67 | If this project helpful for you, feel free to buy me a cup of coffee ☕️.
68 |
69 | * Github Sponsor
70 | [](https://github.com/sponsors/[geek-fun])
71 |
72 | * Wechat Sponsor
73 |
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 |
2 |
3 |
4 |
5 |
6 |
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 |
2 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
55 |
--------------------------------------------------------------------------------
/src/components/RouterMain.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
12 |
--------------------------------------------------------------------------------
/src/components/VersionDetect.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 | {{ $t('version.message') }} ({{ version }})?
12 |
13 |
14 |
15 | {{ $t('version.skip') }}
16 | {{ $t('version.later') }}
17 | {{ $t('version.download') }}
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
88 |
89 |
106 |
--------------------------------------------------------------------------------
/src/components/markdown-render.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
92 |
93 |
127 |
--------------------------------------------------------------------------------
/src/components/path-breadcrumb.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 | {{ path }}
10 |
11 |
16 | {{ path }}
17 |
18 |
19 |
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 |
2 |
3 |
4 |
5 |
6 | {{ popoverContent }}
7 |
8 |
9 |
12 |
--------------------------------------------------------------------------------
/src/layout/components/the-aside.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
28 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
154 |
155 |
211 |
--------------------------------------------------------------------------------
/src/layout/components/tool-bar-right.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
20 |
21 |
22 |
46 |
47 |
87 |
--------------------------------------------------------------------------------
/src/layout/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
18 |
19 |
42 |
--------------------------------------------------------------------------------
/src/views/connect/components/floating-menu.vue:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/src/views/connect/index.vue:
--------------------------------------------------------------------------------
1 |
2 | handleTabChange(value, 'CLOSE')"
8 | @update:value="value => handleTabChange(value, 'CHANGE')"
9 | >
10 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
30 |
31 |
32 |
33 |
34 |
35 |
107 |
108 |
156 |
--------------------------------------------------------------------------------
/src/views/editor/dynamo-editor/components/sql-editor.vue:
--------------------------------------------------------------------------------
1 |
2 | SQL editor
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/views/editor/dynamo-editor/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/views/editor/es-editor/display-editor.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/src/views/file/components/context-menu.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
52 |
53 |
78 |
--------------------------------------------------------------------------------
/src/views/file/components/file-list.vue:
--------------------------------------------------------------------------------
1 |
2 |
27 |
28 |
29 |
135 |
136 |
171 |
--------------------------------------------------------------------------------
/src/views/file/components/tool-bar.vue:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
58 |
59 |
80 |
--------------------------------------------------------------------------------
/src/views/file/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
21 |
--------------------------------------------------------------------------------
/src/views/history/components/history-empty.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
{{ $t('history.empty') }}
9 |
{{ $t('history.emptyDesc') }}
10 |
11 |
12 |
13 |
16 |
17 |
42 |
--------------------------------------------------------------------------------
/src/views/history/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
history
7 |
8 |
9 |
10 |
13 |
14 |
31 |
--------------------------------------------------------------------------------
/src/views/login/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
DocKit
5 |
6 |
7 |
13 |
14 |
15 |
22 |
23 |
24 | {{ $t('login.title') }}
25 |
26 |
27 |
28 |
{{ $t('login.forget') }}
29 |
{{ $t('login.register') }}
30 |
31 |
32 |
33 |
34 |
35 |
78 |
79 |
106 |
--------------------------------------------------------------------------------
/src/views/manage/components/cluster-state.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 | {{ $t('manage.cluster') }}
13 |
14 |
15 |
16 |
17 |
18 |
19 | name: {{ props.cluster?.cluster_name }}
20 | id: {{ props.cluster?.cluster_uuid }}
21 | version: {{ props.cluster?.nodes.versions }}
22 |
23 |
24 | {{ $t('manage.nodes') }}: {{ props.cluster?.nodes.count.total }}
25 | master: {{ props.cluster?.nodes.count.master }}
26 | data: {{ props.cluster?.nodes.count.data }}
27 |
28 |
29 | {{ $t('manage.shards') }}: {{ props.cluster?.indices.shards.total }}
30 | primaries: {{ props.cluster?.indices.shards.primaries }}
31 |
32 | replicas:
33 | {{
34 | (props.cluster?.indices?.shards?.total || 0) -
35 | (props.cluster?.indices.shards?.primaries || 0)
36 | }}
37 |
38 |
39 |
40 | {{ $t('manage.indices') }}: {{ props.cluster?.indices.count }}
41 | docs: {{ props.cluster?.indices.docs.count }}
42 | size: {{ prettyBytes(props.cluster?.indices.store.size_in_bytes || 0) }}
43 |
44 |
45 |
46 |
47 |
54 |
55 |
96 |
--------------------------------------------------------------------------------
/src/views/manage/components/switch-alias-dialog.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
25 |
26 |
27 |
28 |
29 |
30 |
34 |
35 |
36 |
37 |
38 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
60 |
61 |
62 |
63 |
64 |
65 |
171 |
172 |
185 |
--------------------------------------------------------------------------------
/src/views/manage/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
84 |
85 |
106 |
--------------------------------------------------------------------------------
/src/views/setting/components/about-us.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
About Us
4 |
5 |
DocKit
6 |
7 | DocKit is a modern cross-platform NoSQL/NewSQL GUI client. Explore your data any time from
8 | your Mac, Windows, and Linux.
9 |
10 |
11 |
12 |
Features
13 |
14 | -
15 | Full-featured editor, powered by monaco-editor, the backbone of VSCode, providing a
16 | familiar editor environment for developers.
17 |
18 | - Keep your connections in desktop apps, moving the dependencies of dashboard tools.
19 | -
20 | File persistence, allowing you to save your code in your machine as a file, never lost.
21 |
22 | - Supports multiple engines including Elasticsearch, OpenSearch, and more to come.
23 |
24 |
25 |
26 |
License
27 |
DocKit is an open-source project under the Apache 2.0 License.
28 |
29 |
30 |
31 |
32 |
35 |
36 |
74 |
--------------------------------------------------------------------------------
/src/views/setting/components/basic.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ $t('setting.theme') }}
6 |
7 |
14 |
15 |
![]()
16 |
17 |
{{ $t(`setting.${theme.name}`) }}
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | {{ $t('setting.language') }}
28 |
29 |
34 |
35 | {{ langItem.name === 'auto' ? $t('setting.auto') : langItem.name }}
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
78 |
79 |
148 |
--------------------------------------------------------------------------------
/src/views/setting/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
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 |
--------------------------------------------------------------------------------