├── .env.example ├── .eslintrc.cjs ├── .github └── workflows │ ├── build.yml │ └── release.yml ├── .gitignore ├── .prettierrc.json ├── .vscode └── extensions.json ├── Dockerfile ├── LICENSE ├── README.md ├── auto-imports.d.ts ├── components.d.ts ├── dist-wrap ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md └── src │ └── lib.rs ├── docker-compose.yaml ├── index.html ├── package-lock.json ├── package.json ├── pnpm-lock.yaml ├── public └── rnacos │ └── server.svg ├── src ├── App.vue ├── api │ ├── cluster.ts │ ├── config.ts │ ├── metrics.ts │ ├── namespace.ts │ ├── naming.ts │ └── user.ts ├── components │ ├── Boo.jsx │ ├── Foo.vue │ ├── LangSwitch.vue │ ├── MoreSetting.vue │ ├── cluster │ │ └── ClusterInfoColumns.jsx │ ├── common │ │ └── SubContentFullPage.vue │ ├── config │ │ ├── CodeMirror.ts │ │ ├── ConfigColumns.jsx │ │ ├── DiffComponent.vue │ │ └── cm6theme.js │ ├── namespace │ │ └── NamespacePopSelect.vue │ ├── naming │ │ ├── InstanceListColumns.jsx │ │ ├── ServiceListColumns.jsx │ │ └── SuberscriberListColumns.jsx │ └── user │ │ ├── ResetPassword.vue │ │ └── UserListColumns.jsx ├── data │ ├── appdata.ts │ ├── lang.ts │ ├── namespace.ts │ ├── resources.ts │ └── role.ts ├── hooks │ └── setting │ │ └── useProjectSetting.ts ├── i18n │ ├── index.js │ └── lang │ │ ├── en-US.js │ │ └── zh-CN.js ├── layout │ ├── components │ │ ├── Header │ │ │ ├── index.ts │ │ │ └── index.vue │ │ ├── Logo │ │ │ ├── index.ts │ │ │ └── index.vue │ │ └── Menu │ │ │ ├── index.ts │ │ │ └── index.vue │ └── index.vue ├── main.ts ├── pages │ ├── About.vue │ ├── AppMonitor.vue │ ├── ChartDemo.vue │ ├── ClusterPage.vue │ ├── Config.tsx │ ├── ConfigDetail.vue │ ├── ConfigHistoryListPage.vue │ ├── ConfigListPage.vue │ ├── DiffDemo.vue │ ├── Login.vue │ ├── NamespacePage.vue │ ├── NoPermission.vue │ ├── NotFound.vue │ ├── ServiceDetail.vue │ ├── ServiceInstanceDetail.vue │ ├── ServiceInstanceListPage.vue │ ├── ServiceListPage.vue │ ├── SubscriberListPage.vue │ ├── Tmp.vue │ ├── Transfer.vue │ ├── UserDetail.vue │ └── UserListPage.vue ├── route │ ├── router.js │ └── routes.js ├── settings │ └── projectSetting.ts ├── store │ └── modules │ │ └── projectSetting.ts ├── style.css ├── types │ ├── base.d.ts │ ├── cluster.d.ts │ ├── config.d.ts │ ├── constant.ts │ ├── metrics.ts │ ├── namespace.d.ts │ └── service.d.ts ├── utils │ ├── CryptoUtils.js │ ├── EchartsUtils.js │ ├── EchartsWrap.js │ ├── date.ts │ ├── index.ts │ ├── request.ts │ └── utils.js └── vite-env.d.ts ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /.env.example: -------------------------------------------------------------------------------- 1 | # 开发环境配置 2 | VITE_PORT=5173 3 | VITE_PROXY_URL=http://127.0.0.1:10848 4 | VITE_BASE_URL=/ -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | require('@rushstack/eslint-patch/modern-module-resolution') 3 | 4 | module.exports = { 5 | root: true, 6 | 'extends': [ 7 | 'plugin:vue/vue3-essential', 8 | 'eslint:recommended', 9 | '@vue/eslint-config-prettier/skip-formatting' 10 | ], 11 | parserOptions: { 12 | ecmaVersion: 'latest' 13 | }, 14 | env: { 15 | 'vue/setup-compiler-macros': true 16 | } 17 | } -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | pull_request: 5 | branches: [ "master" ] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | 11 | strategy: 12 | matrix: 13 | node-version: [18.x] 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - name: Use Node.js ${{ matrix.node-version }} 19 | uses: actions/setup-node@v4 20 | with: 21 | node-version: ${{ matrix.node-version }} 22 | 23 | - name: Build 24 | run: | 25 | npm install 26 | npm run build 27 | 28 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: [ 'v*' ] 6 | workflow_dispatch: 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | if: "startsWith(github.ref, 'refs/tags/')" 15 | 16 | strategy: 17 | matrix: 18 | node-version: [18.x] 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | 23 | - name: Use Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@v4 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | 28 | - name: Build 29 | run: | 30 | npm install 31 | npm run build 32 | - name: cargo login 33 | run: cargo login ${{ secrets.CRATES_IO_TOKEN }} 34 | - name: cargo publish 35 | run: | 36 | cp -rf dist dist-wrap/src 37 | cd dist 38 | zip ../dist.zip -r ./ 39 | cd - 40 | rm -rf .git 41 | ls -al dist-wrap/src/dist/ 42 | cd dist-wrap 43 | cargo publish 44 | cd - 45 | - name: Upload binary artifacts 46 | uses: actions/upload-artifact@v4 47 | with: 48 | name: binaries 49 | path: | 50 | *.zip 51 | 52 | 53 | release-github: 54 | permissions: 55 | # Used to sign the release's artifacts with sigstore-python. 56 | id-token: write 57 | # Used to upload release artifacts. 58 | contents: write 59 | name: Publish to GitHub releases 60 | runs-on: ubuntu-latest 61 | if: "startsWith(github.ref, 'refs/tags/')" 62 | needs: [ build ] 63 | steps: 64 | - uses: actions/download-artifact@v4 65 | with: 66 | name: binaries 67 | #- name: Sigstore Sign 68 | # uses: sigstore/gh-action-sigstore-python@v1.2.3 69 | # with: 70 | # inputs: ./*.zip 71 | # upload-signing-artifacts: true 72 | - name: Release 73 | uses: softprops/action-gh-release@v1 74 | with: 75 | files: | 76 | *.zip 77 | *.sig 78 | *.crt 79 | prerelease: ${{ contains(github.ref, 'alpha') || contains(github.ref, 'beta') }} 80 | generate_release_notes: true 81 | 82 | 83 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | .env 15 | 16 | # Editor directories and files 17 | .vscode/* 18 | !.vscode/extensions.json 19 | .idea 20 | .DS_Store 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | yarn.lock 27 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema":"https://json.schemastore.org/prettierrc", 3 | "semi": true, 4 | "tabWidth": 2, 5 | "singleQuote": true, 6 | "printWidth": 80, 7 | "trailingComma": "none" 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"] 3 | } 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18-alpine 2 | 3 | WORKDIR /app 4 | # Install git 5 | # RUN apk add --no-cache git 6 | 7 | COPY package*.json ./ 8 | 9 | RUN npm install 10 | 11 | COPY . . 12 | 13 | ENV VITE_PORT=${VITE_PORT} 14 | ENV VITE_PROXY_URL=${VITE_PROXY_URL} 15 | ENV VITE_BASE_URL=${VITE_BASE_URL} 16 | 17 | EXPOSE ${VITE_PORT} 18 | 19 | CMD ["npm", "run", "dev"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 heqingpan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rnacos-console 2 | 3 | r-nacos server's console front-end project 4 | 5 | r-nacos 控制台前端,r-nacos中通过引用发布后的前端文件使用。 6 | 7 | ## 本工程开发流程 8 | 9 | ### 1、node环境 10 | 11 | 1. 安装node,版本号建议 >= v18.x ; 12 | 13 | ### 2. 配置文件 14 | 15 | 本功能运行时需要请求对应的后端接口,默认为`http://127.0.0.1:10848`。 16 | ``` 17 | cp .env.example .env 18 | ``` 19 | 20 | ### 3. 安装 NPM 21 | 22 | ``` 23 | npm install 24 | ``` 25 | 26 | 在本机启动r-nacos服务后,即可请求到对应的控制台后端接口。 27 | 28 | r-nacos服务启动方式参考 [r-nacos readme](https://github.com/nacos-group/r-nacos) 29 | 30 | ### 4. 运行开发工程 31 | 32 | 在项目目录运行`npm run dev`后即可启动项目测试服务,然后通过`http://localhost:5173/rnacos/`进入控制台。 33 | 34 | ### 5. Docker 开发方式 35 | 36 | #### 通过 Docker 运行开发环境: 37 | 38 | 1. 复制环境变量文件 39 | ```bash 40 | cp .env.example .env 41 | ``` 42 | 43 | 2. 修改.env文件中的配置(如需访问宿主机服务,建议设置VITE_PROXY_URL=http://host.docker.internal:10848) 44 | 45 | 3. 启动 Docker 容器 46 | ```bash 47 | docker-compose up 48 | ``` 49 | 50 | 4. 访问开发服务 51 | ``` 52 | http://localhost:5173/rnacos/ 53 | ``` 54 | 55 | #### 容器特性: 56 | - 支持热更新(修改代码自动重载) 57 | - 文件双向同步(容器内外修改实时同步) 58 | - 环境变量通过docker-compose.yaml注入 59 | 60 | ### 6. 开发 61 | 62 | 使用vscode 或者其它IDE 开始编码开发。 63 | 64 | ### 7. 代码格式化 65 | 66 | 提交代码前需要运行 `npm run format` 对代码进行统一的格式化。 67 | 68 | ### 8. 提交代码 69 | 70 | 提交代码,并提PR。 71 | -------------------------------------------------------------------------------- /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 onBeforeUnmount: typeof import('vue')['onBeforeUnmount'] 28 | const onBeforeUpdate: typeof import('vue')['onBeforeUpdate'] 29 | const onDeactivated: typeof import('vue')['onDeactivated'] 30 | const onErrorCaptured: typeof import('vue')['onErrorCaptured'] 31 | const onMounted: typeof import('vue')['onMounted'] 32 | const onRenderTracked: typeof import('vue')['onRenderTracked'] 33 | const onRenderTriggered: typeof import('vue')['onRenderTriggered'] 34 | const onScopeDispose: typeof import('vue')['onScopeDispose'] 35 | const onServerPrefetch: typeof import('vue')['onServerPrefetch'] 36 | const onUnmounted: typeof import('vue')['onUnmounted'] 37 | const onUpdated: typeof import('vue')['onUpdated'] 38 | const provide: typeof import('vue')['provide'] 39 | const reactive: typeof import('vue')['reactive'] 40 | const readonly: typeof import('vue')['readonly'] 41 | const ref: typeof import('vue')['ref'] 42 | const resolveComponent: typeof import('vue')['resolveComponent'] 43 | const shallowReactive: typeof import('vue')['shallowReactive'] 44 | const shallowReadonly: typeof import('vue')['shallowReadonly'] 45 | const shallowRef: typeof import('vue')['shallowRef'] 46 | const toRaw: typeof import('vue')['toRaw'] 47 | const toRef: typeof import('vue')['toRef'] 48 | const toRefs: typeof import('vue')['toRefs'] 49 | const toValue: typeof import('vue')['toValue'] 50 | const triggerRef: typeof import('vue')['triggerRef'] 51 | const unref: typeof import('vue')['unref'] 52 | const useAttrs: typeof import('vue')['useAttrs'] 53 | const useCssModule: typeof import('vue')['useCssModule'] 54 | const useCssVars: typeof import('vue')['useCssVars'] 55 | const useDialog: typeof import('naive-ui')['useDialog'] 56 | const useLoadingBar: typeof import('naive-ui')['useLoadingBar'] 57 | const useMessage: typeof import('naive-ui')['useMessage'] 58 | const useNotification: typeof import('naive-ui')['useNotification'] 59 | const useSlots: typeof import('vue')['useSlots'] 60 | const watch: typeof import('vue')['watch'] 61 | const watchEffect: typeof import('vue')['watchEffect'] 62 | const watchPostEffect: typeof import('vue')['watchPostEffect'] 63 | const watchSyncEffect: typeof import('vue')['watchSyncEffect'] 64 | } 65 | // for type re-export 66 | declare global { 67 | // @ts-ignore 68 | export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue' 69 | } 70 | -------------------------------------------------------------------------------- /components.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* prettier-ignore */ 3 | // @ts-nocheck 4 | // Generated by unplugin-vue-components 5 | // Read more: https://github.com/vuejs/core/pull/3399 6 | export {} 7 | 8 | declare module 'vue' { 9 | export interface GlobalComponents { 10 | DiffComponent: typeof import('./src/components/config/DiffComponent.vue')['default'] 11 | Foo: typeof import('./src/components/Foo.vue')['default'] 12 | LangSwitch: typeof import('./src/components/LangSwitch.vue')['default'] 13 | MoreSetting: typeof import('./src/components/MoreSetting.vue')['default'] 14 | NamespacePopSelect: typeof import('./src/components/namespace/NamespacePopSelect.vue')['default'] 15 | NButton: typeof import('naive-ui')['NButton'] 16 | NCard: typeof import('naive-ui')['NCard'] 17 | NConfigProvider: typeof import('naive-ui')['NConfigProvider'] 18 | NDataTable: typeof import('naive-ui')['NDataTable'] 19 | NDrawer: typeof import('naive-ui')['NDrawer'] 20 | NDrawerContent: typeof import('naive-ui')['NDrawerContent'] 21 | NDropdown: typeof import('naive-ui')['NDropdown'] 22 | NForm: typeof import('naive-ui')['NForm'] 23 | NFormItem: typeof import('naive-ui')['NFormItem'] 24 | NGi: typeof import('naive-ui')['NGi'] 25 | NGrid: typeof import('naive-ui')['NGrid'] 26 | NIcon: typeof import('naive-ui')['NIcon'] 27 | NInput: typeof import('naive-ui')['NInput'] 28 | NLayout: typeof import('naive-ui')['NLayout'] 29 | NLayoutContent: typeof import('naive-ui')['NLayoutContent'] 30 | NLayoutHeader: typeof import('naive-ui')['NLayoutHeader'] 31 | NLayoutSider: typeof import('naive-ui')['NLayoutSider'] 32 | NMenu: typeof import('naive-ui')['NMenu'] 33 | NMessageProvider: typeof import('naive-ui')['NMessageProvider'] 34 | NRadio: typeof import('naive-ui')['NRadio'] 35 | NRadioGroup: typeof import('naive-ui')['NRadioGroup'] 36 | NSelect: typeof import('naive-ui')['NSelect'] 37 | NSpace: typeof import('naive-ui')['NSpace'] 38 | NSwitch: typeof import('naive-ui')['NSwitch'] 39 | NUpload: typeof import('naive-ui')['NUpload'] 40 | ResetPassword: typeof import('./src/components/user/ResetPassword.vue')['default'] 41 | RouterLink: typeof import('vue-router')['RouterLink'] 42 | RouterView: typeof import('vue-router')['RouterView'] 43 | SubContentFullPage: typeof import('./src/components/common/SubContentFullPage.vue')['default'] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /dist-wrap/.gitignore: -------------------------------------------------------------------------------- 1 | src/dist/ 2 | target/ 3 | -------------------------------------------------------------------------------- /dist-wrap/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "block-buffer" 7 | version = "0.10.4" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 10 | dependencies = [ 11 | "generic-array", 12 | ] 13 | 14 | [[package]] 15 | name = "cfg-if" 16 | version = "1.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 19 | 20 | [[package]] 21 | name = "cpufeatures" 22 | version = "0.2.7" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" 25 | dependencies = [ 26 | "libc", 27 | ] 28 | 29 | [[package]] 30 | name = "crypto-common" 31 | version = "0.1.6" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 34 | dependencies = [ 35 | "generic-array", 36 | "typenum", 37 | ] 38 | 39 | [[package]] 40 | name = "digest" 41 | version = "0.10.6" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" 44 | dependencies = [ 45 | "block-buffer", 46 | "crypto-common", 47 | ] 48 | 49 | [[package]] 50 | name = "generic-array" 51 | version = "0.14.7" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 54 | dependencies = [ 55 | "typenum", 56 | "version_check", 57 | ] 58 | 59 | [[package]] 60 | name = "libc" 61 | version = "0.2.143" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "edc207893e85c5d6be840e969b496b53d94cec8be2d501b214f50daa97fa8024" 64 | 65 | [[package]] 66 | name = "proc-macro2" 67 | version = "1.0.56" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" 70 | dependencies = [ 71 | "unicode-ident", 72 | ] 73 | 74 | [[package]] 75 | name = "quote" 76 | version = "1.0.26" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" 79 | dependencies = [ 80 | "proc-macro2", 81 | ] 82 | 83 | [[package]] 84 | name = "rnacos-web-dist-wrap" 85 | version = "0.3.13" 86 | dependencies = [ 87 | "rust-embed", 88 | ] 89 | 90 | [[package]] 91 | name = "rust-embed" 92 | version = "6.6.1" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "1b68543d5527e158213414a92832d2aab11a84d2571a5eb021ebe22c43aab066" 95 | dependencies = [ 96 | "rust-embed-impl", 97 | "rust-embed-utils", 98 | "walkdir", 99 | ] 100 | 101 | [[package]] 102 | name = "rust-embed-impl" 103 | version = "6.5.0" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "4d4e0f0ced47ded9a68374ac145edd65a6c1fa13a96447b873660b2a568a0fd7" 106 | dependencies = [ 107 | "proc-macro2", 108 | "quote", 109 | "rust-embed-utils", 110 | "syn", 111 | "walkdir", 112 | ] 113 | 114 | [[package]] 115 | name = "rust-embed-utils" 116 | version = "7.5.0" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "512b0ab6853f7e14e3c8754acb43d6f748bb9ced66aa5915a6553ac8213f7731" 119 | dependencies = [ 120 | "sha2", 121 | "walkdir", 122 | ] 123 | 124 | [[package]] 125 | name = "same-file" 126 | version = "1.0.6" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 129 | dependencies = [ 130 | "winapi-util", 131 | ] 132 | 133 | [[package]] 134 | name = "sha2" 135 | version = "0.10.6" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" 138 | dependencies = [ 139 | "cfg-if", 140 | "cpufeatures", 141 | "digest", 142 | ] 143 | 144 | [[package]] 145 | name = "syn" 146 | version = "1.0.109" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 149 | dependencies = [ 150 | "proc-macro2", 151 | "quote", 152 | "unicode-ident", 153 | ] 154 | 155 | [[package]] 156 | name = "typenum" 157 | version = "1.16.0" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" 160 | 161 | [[package]] 162 | name = "unicode-ident" 163 | version = "1.0.8" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" 166 | 167 | [[package]] 168 | name = "version_check" 169 | version = "0.9.4" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 172 | 173 | [[package]] 174 | name = "walkdir" 175 | version = "2.3.3" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" 178 | dependencies = [ 179 | "same-file", 180 | "winapi-util", 181 | ] 182 | 183 | [[package]] 184 | name = "winapi" 185 | version = "0.3.9" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 188 | dependencies = [ 189 | "winapi-i686-pc-windows-gnu", 190 | "winapi-x86_64-pc-windows-gnu", 191 | ] 192 | 193 | [[package]] 194 | name = "winapi-i686-pc-windows-gnu" 195 | version = "0.4.0" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 198 | 199 | [[package]] 200 | name = "winapi-util" 201 | version = "0.1.5" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 204 | dependencies = [ 205 | "winapi", 206 | ] 207 | 208 | [[package]] 209 | name = "winapi-x86_64-pc-windows-gnu" 210 | version = "0.4.0" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 213 | -------------------------------------------------------------------------------- /dist-wrap/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rnacos-web-dist-wrap" 3 | version = "0.6.0" 4 | edition = "2018" 5 | authors = ["heqingpan "] 6 | license = "MIT/Apache-2.0" 7 | description = "rnacos server's console front-end dist embed wrap project." 8 | 9 | readme = "README.md" 10 | keywords = ["rnacos"] 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | rust-embed="6.6.1" 16 | -------------------------------------------------------------------------------- /dist-wrap/README.md: -------------------------------------------------------------------------------- 1 | # rnacos-web-dist-wrap 2 | 3 | rnacos server's console front-end dist embed wrap project. -------------------------------------------------------------------------------- /dist-wrap/src/lib.rs: -------------------------------------------------------------------------------- 1 | use rust_embed::{RustEmbed, EmbeddedFile}; 2 | 3 | 4 | 5 | #[derive(RustEmbed)] 6 | #[folder = "src/dist"] 7 | struct Asset; 8 | 9 | pub fn get_embedded_file(path: &str) -> Option{ 10 | Asset::get(path) 11 | } -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | app: 3 | build: . 4 | volumes: 5 | - .:/app 6 | - /app/node_modules 7 | ports: 8 | - "${VITE_PORT}:${VITE_PORT}" 9 | environment: 10 | - VITE_PORT=${VITE_PORT} 11 | - VITE_PROXY_URL=${VITE_PROXY_URL} 12 | - VITE_BASE_URL=${VITE_BASE_URL} 13 | command: npm run dev 14 | networks: 15 | - r-nacos-simple_default 16 | 17 | networks: 18 | r-nacos-simple_default: 19 | external: true -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | r-nacos console 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rnacos-console", 3 | "author": "heqingpan@126.com", 4 | "homepage": "https://github.com/heqingpan/rnacos-console-web", 5 | "repository": "https://github.com/heqingpan/rnacos-console-web.git", 6 | "keywords": [ 7 | "nacos", 8 | "rnacos", 9 | "console", 10 | "admin", 11 | "vue3" 12 | ], 13 | "private": true, 14 | "version": "0.6.0", 15 | "type": "module", 16 | "scripts": { 17 | "dev": "vite", 18 | "build": "vue-tsc && vite build", 19 | "preview": "vite preview", 20 | "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore", 21 | "format": "prettier --write src/", 22 | "fmt": "prettier --write src/" 23 | }, 24 | "dependencies": { 25 | "@codemirror/lang-html": "^6.4.8", 26 | "@codemirror/lang-json": "^6.0.1", 27 | "@codemirror/lang-xml": "^6.1.0", 28 | "@codemirror/lang-yaml": "^6.0.0", 29 | "@tailwindcss/vite": "^4.0.7", 30 | "axios": "^1.3.6", 31 | "codemirror": "^6.0.1", 32 | "crypto-js": "^4.2.0", 33 | "diff": "^5.1.0", 34 | "echarts": "^5.5.1", 35 | "pinia": "^2.0.35", 36 | "qs": "^6.11.2", 37 | "tailwindcss": "^4.0.7", 38 | "template_js": "^3.1.4", 39 | "vue": "^3.2.47", 40 | "vue-demi": "^0.14.7", 41 | "vue-i18n": "^10.0.4", 42 | "vue-router": "^4.1.6" 43 | }, 44 | "devDependencies": { 45 | "@rushstack/eslint-patch": "1.5.1", 46 | "@types/node": "^18.16.1", 47 | "@vicons/antd": "^0.13.0", 48 | "@vicons/ionicons5": "^0.12.0", 49 | "@vitejs/plugin-vue": "^5.2.1", 50 | "@vitejs/plugin-vue-jsx": "^4.1.1", 51 | "@vue/eslint-config-prettier": "^8.0.0", 52 | "browserslist": "^4.22.1", 53 | "eslint": "^8.54.0", 54 | "eslint-plugin-vue": "9.18.1", 55 | "lightningcss": "^1.22.0", 56 | "naive-ui": "^2.34.3", 57 | "prettier": "^3.1.0", 58 | "typescript": "^5.0.2", 59 | "unplugin-auto-import": "^0.16.6", 60 | "unplugin-vue-components": "^0.25.2", 61 | "vite": "^6.1.0", 62 | "vue-tsc": "^1.4.2" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /public/rnacos/server.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 46 | -------------------------------------------------------------------------------- /src/api/cluster.ts: -------------------------------------------------------------------------------- 1 | import { AxiosResponse } from 'axios'; 2 | import request from '../utils/request'; 3 | import { IClusterNode } from '@/types/cluster'; 4 | import { IApiResult, IConsoleResult } from '@/types/base'; 5 | let axios = request; 6 | 7 | class ClusterApi { 8 | queryNodeList(): Promise>>> { 9 | return axios.request({ 10 | method: 'get', 11 | url: '/rnacos/api/console/v2/cluster/cluster_node_list' 12 | }); 13 | } 14 | } 15 | export const clusterApi = new ClusterApi(); 16 | -------------------------------------------------------------------------------- /src/api/config.ts: -------------------------------------------------------------------------------- 1 | import { AxiosResponse } from 'axios'; 2 | import request from '../utils/request'; 3 | import { IApiResult, IPageResult } from '@/types/base'; 4 | let axios = request; 5 | 6 | export interface IConfig { 7 | tenant?: string; 8 | group: string; 9 | dataId: string; 10 | content?: string; 11 | md5?: string; 12 | modifiedTime?: number; 13 | desc?: string; 14 | configType?: string; 15 | } 16 | 17 | export interface IConfigValue { 18 | value?: string; 19 | md5?: string; 20 | desc?: string; 21 | configType?: string; 22 | } 23 | 24 | export interface IConfigKey { 25 | tenant?: string; 26 | group: string; 27 | dataId: string; 28 | } 29 | 30 | export interface IConfigQueryParam { 31 | tenant: string; 32 | groupParam: string; 33 | dataParam: string; 34 | pageNo: Number; 35 | pageSize: Number; 36 | } 37 | 38 | export interface IConfigQueryHistoryParam { 39 | tenant: string; 40 | group: string; 41 | dataId: string; 42 | pageNo: Number; 43 | pageSize: Number; 44 | } 45 | 46 | class ConfigApi { 47 | setConfigV2(config: IConfig): Promise>> { 48 | return axios.requestJSON({ 49 | method: 'post', 50 | url: '/rnacos/api/console/v2/config/update', 51 | data: JSON.stringify(config) 52 | }); 53 | } 54 | 55 | getConfigV2( 56 | configKey: IConfigKey 57 | ): Promise>> { 58 | return axios.request({ 59 | method: 'get', 60 | url: '/rnacos/api/console/v2/config/info', 61 | params: { 62 | ...configKey 63 | } 64 | }); 65 | } 66 | 67 | removeConfigV2(configKey: IConfigKey): Promise { 68 | return axios.requestJSON({ 69 | method: 'post', 70 | url: '/rnacos/api/console/v2/config/remove', 71 | data: JSON.stringify(configKey) 72 | }); 73 | } 74 | 75 | queryConfigPage( 76 | queryParam: IConfigQueryParam 77 | ): Promise>>> { 78 | return axios.request({ 79 | method: 'get', 80 | url: '/rnacos/api/console/v2/config/list', 81 | params: { 82 | ...queryParam 83 | } 84 | }); 85 | } 86 | 87 | queryConfigHistoryPage( 88 | queryParam: IConfigQueryHistoryParam 89 | ): Promise>>> { 90 | return axios.request({ 91 | method: 'get', 92 | url: '/rnacos/api/console/v2/config/history', 93 | params: { 94 | ...queryParam 95 | } 96 | }); 97 | } 98 | } 99 | export const configApi = new ConfigApi(); 100 | -------------------------------------------------------------------------------- /src/api/metrics.ts: -------------------------------------------------------------------------------- 1 | import { AxiosResponse } from 'axios'; 2 | import request from '../utils/request'; 3 | import { IApiResult } from '@/types/base'; 4 | import { TimelineQueryParam, TimelineQueryResponse } from '@/types/metrics'; 5 | let axios = request; 6 | 7 | /* 8 | export const ALL_KEYS = [ 9 | 'sys_total_memory', 10 | 'app_rss_memory', 11 | 'app_vms_memory', 12 | 'app_memory_usage', 13 | 'app_cpu_usage', 14 | 'config_data_size', 15 | 'config_listener_client_size', 16 | 'config_listener_key_size', 17 | 'config_subscriber_listener_key_size', 18 | 'config_subscriber_listener_value_size', 19 | 'config_subscriber_client_size', 20 | 'config_subscriber_client_value_size', 21 | 'config_index_tenant_size', 22 | 'config_index_config_size', 23 | 'naming_service_size', 24 | 'naming_instance_size', 25 | 'naming_subscriber_listener_key_size', 26 | 'naming_subscriber_listener_value_size', 27 | 'naming_subscriber_client_size', 28 | 'naming_subscriber_client_value_size', 29 | 'naming_empty_service_set_size', 30 | 'naming_empty_service_set_item_size', 31 | 'naming_instance_meta_set_size', 32 | 'naming_instance_meta_set_item_size', 33 | 'naming_healthy_timeout_set_size', 34 | 'naming_healthy_timeout_set_item_size', 35 | 'naming_unhealthy_timeout_set_size', 36 | 'naming_unhealthy_timeout_set_item_size', 37 | 'naming_client_instance_set_key_size', 38 | 'naming_client_instance_set_value_size', 39 | 'naming_index_tenant_size', 40 | 'naming_index_group_size', 41 | 'naming_index_service_size', 42 | 'grpc_conn_size', 43 | 'grpc_conn_active_timeout_set_item_size', 44 | 'grpc_conn_response_timeout_set_item_size', 45 | 'grpc_request_handle_rt_histogram', 46 | 'grpc_request_handle_rt_summary', 47 | 'grpc_request_total_count', 48 | 'http_request_handle_rt_histogram', 49 | 'http_request_handle_rt_summary', 50 | 'http_request_total_count' 51 | ]; 52 | 53 | //keys cpu使用率、内存使用量、内存使用率、配置数量、服务数量、实例数量、请求rt百分位、请求qps、请求平均rt 54 | export const DEFAULT_KEYS = [ 55 | 'app_cpu_usage', 56 | 'app_memory_usage', 57 | 'app_rss_memory', 58 | 'config_data_size', 59 | 'naming_service_size', 60 | 'naming_instance_size', 61 | 'grpc_request_handle_rt_summary', 62 | 'http_request_handle_rt_summary' 63 | ]; 64 | */ 65 | 66 | class MetricsApi { 67 | queryTimeLine( 68 | param: TimelineQueryParam 69 | ): Promise>> { 70 | return axios.requestJSON({ 71 | method: 'post', 72 | url: '/rnacos/api/console/v2/metrics/timeline', 73 | data: JSON.stringify(param) 74 | }); 75 | } 76 | } 77 | export const metricsApi = new MetricsApi(); 78 | -------------------------------------------------------------------------------- /src/api/namespace.ts: -------------------------------------------------------------------------------- 1 | import { AxiosResponse } from 'axios'; 2 | import request from '../utils/request'; 3 | import { INamespace } from '@/types/namespace'; 4 | import { IApiResult } from '@/types/base'; 5 | let axios = request; 6 | 7 | class NamespaceApi { 8 | queryList(): Promise>>> { 9 | return axios.request({ 10 | method: 'get', 11 | url: '/rnacos/api/console/v2/namespaces/list' 12 | }); 13 | } 14 | add(namespace: INamespace): Promise>> { 15 | return axios.requestJSON({ 16 | method: 'post', 17 | url: '/rnacos/api/console/v2/namespaces/add', 18 | data: { 19 | ...namespace 20 | } 21 | }); 22 | } 23 | update(namespace: INamespace): Promise>> { 24 | return axios.requestJSON({ 25 | method: 'post', 26 | url: '/rnacos/api/console/v2/namespaces/update', 27 | data: { 28 | ...namespace 29 | } 30 | }); 31 | } 32 | delete(namespace: INamespace): Promise>> { 33 | return axios.requestJSON({ 34 | method: 'post', 35 | url: '/rnacos/api/console/v2/namespaces/remove', 36 | data: { 37 | namespaceId: namespace.namespaceId 38 | } 39 | }); 40 | } 41 | } 42 | const namespaceApi = new NamespaceApi(); 43 | export default namespaceApi; 44 | -------------------------------------------------------------------------------- /src/api/naming.ts: -------------------------------------------------------------------------------- 1 | import { AxiosResponse } from 'axios'; 2 | import request from '../utils/request'; 3 | import { IServiceInfo, IServiceInstance, IServiceKey } from '@/types/service'; 4 | import { IApiResult, IPageResult } from '@/types/base'; 5 | let axios = request; 6 | 7 | export interface IServiceQueryPageParam { 8 | namespaceId: string; 9 | serviceNameParam: string; 10 | groupNameParam: string; 11 | pageNo: Number; 12 | pageSize: Number; 13 | } 14 | 15 | export interface IServiceQueryItem { 16 | name: string; 17 | groupName: string; 18 | clusterCount: Number; 19 | ip_count: Number; 20 | healthy_instance_count: Number; 21 | trigger_flag: Boolean; 22 | metadata?: string; 23 | protect_threshold?: Number; 24 | } 25 | 26 | export interface IServiceQueryPageResult { 27 | count: Number; 28 | service_list: Array; 29 | } 30 | 31 | class NamingApi { 32 | queryServicePage( 33 | param: IServiceQueryPageParam 34 | ): Promise>>> { 35 | return axios.request({ 36 | method: 'get', 37 | url: '/rnacos/api/console/v2/service/list', 38 | params: param 39 | }); 40 | } 41 | querySubscriberPage( 42 | param: IServiceQueryPageParam 43 | ): Promise>> { 44 | return axios.request({ 45 | method: 'get', 46 | url: '/rnacos/api/console/ns/service/subscribers', 47 | params: param 48 | }); 49 | } 50 | createService(info: IServiceInfo): Promise { 51 | return axios.requestJSON({ 52 | method: 'post', 53 | url: '/rnacos/api/console/v2/service/add', 54 | data: JSON.stringify(info) 55 | }); 56 | } 57 | updateService(info: IServiceInfo): Promise>> { 58 | return axios.requestJSON({ 59 | method: 'post', 60 | url: '/rnacos/api/console/v2/service/update', 61 | data: JSON.stringify(info) 62 | }); 63 | } 64 | removeService(key: IServiceKey): Promise>> { 65 | return axios.requestJSON({ 66 | method: 'post', 67 | url: '/rnacos/api/console/v2/service/remove', 68 | data: JSON.stringify(key) 69 | }); 70 | } 71 | queryServiceInstances( 72 | key: IServiceKey 73 | ): Promise>>> { 74 | return axios.request({ 75 | method: 'get', 76 | url: '/rnacos/api/console/v2/instance/list', 77 | params: key 78 | }); 79 | } 80 | updateInstance( 81 | instance: IServiceInstance 82 | ): Promise>> { 83 | return axios.requestJSON({ 84 | method: 'post', 85 | url: '/rnacos/api/console/v2/instance/update', 86 | data: instance 87 | }); 88 | } 89 | } 90 | export const namingApi = new NamingApi(); 91 | -------------------------------------------------------------------------------- /src/api/user.ts: -------------------------------------------------------------------------------- 1 | import { AxiosResponse } from 'axios'; 2 | import request from '../utils/request'; 3 | import { 4 | IApiResult, 5 | WebResource, 6 | IPrivilegeGroup, 7 | IPageResult 8 | } from '@/types/base'; 9 | let axios = request; 10 | 11 | export interface ILoginParam { 12 | username: string; 13 | password: string; 14 | captcha: string; 15 | } 16 | 17 | export interface IResetPasswordParam { 18 | oldPassword: string; 19 | newPassword: string; 20 | } 21 | 22 | export interface IUserPageParam { 23 | likeUsername?: string; 24 | pageNo: Number; 25 | pageSize: Number; 26 | isRev: boolean; 27 | } 28 | 29 | export interface IUserInfo { 30 | username: string; 31 | nickname: string; 32 | password?: string; 33 | gmtCreate: number; 34 | gmtModified: number; 35 | enable: boolean; 36 | roles: Array; 37 | extendInfo?: Map; 38 | namespacePrivilege?: IPrivilegeGroup; 39 | } 40 | 41 | export interface IUpdateUserParam { 42 | username: string; 43 | nickname?: string; 44 | password?: string; 45 | enable?: boolean; 46 | roles?: string; 47 | namespacePrivilegeParam?: IPrivilegeGroup; 48 | } 49 | 50 | class UserApi { 51 | login(info: ILoginParam): Promise>> { 52 | return axios.request({ 53 | method: 'post', 54 | url: '/rnacos/api/console/v2/login/login', 55 | data: info 56 | }); 57 | } 58 | genCaptcha(): Promise>> { 59 | return axios.request({ 60 | method: 'get', 61 | url: '/rnacos/api/console/v2/login/captcha' 62 | }); 63 | } 64 | logout(): Promise>> { 65 | return axios.request({ 66 | method: 'post', 67 | url: '/rnacos/api/console/v2/login/logout' 68 | }); 69 | } 70 | resetPassword( 71 | info: IResetPasswordParam 72 | ): Promise>> { 73 | return axios.requestJSON({ 74 | method: 'post', 75 | url: '/rnacos/api/console/v2/user/reset_password', 76 | data: info 77 | }); 78 | } 79 | getUserList( 80 | param: IUserPageParam 81 | ): Promise>>> { 82 | return axios.request({ 83 | method: 'get', 84 | url: '/rnacos/api/console/v2/user/list', 85 | params: { 86 | ...param 87 | } 88 | }); 89 | } 90 | getUserWebResources(): Promise>> { 91 | return axios.request({ 92 | method: 'get', 93 | url: '/rnacos/api/console/v2/user/web_resources', 94 | params: {} 95 | }); 96 | } 97 | addUser(info: IUpdateUserParam): Promise>> { 98 | return axios.requestJSON({ 99 | method: 'post', 100 | url: '/rnacos/api/console/v2/user/add', 101 | data: info 102 | }); 103 | } 104 | updateUser( 105 | info: IUpdateUserParam 106 | ): Promise>> { 107 | return axios.requestJSON({ 108 | method: 'post', 109 | url: '/rnacos/api/console/v2/user/update', 110 | data: info 111 | }); 112 | } 113 | removeUser( 114 | info: IUpdateUserParam 115 | ): Promise>> { 116 | return axios.requestJSON({ 117 | method: 'post', 118 | url: '/rnacos/api/console/v2/user/remove', 119 | data: info 120 | }); 121 | } 122 | } 123 | 124 | export const userApi = new UserApi(); 125 | -------------------------------------------------------------------------------- /src/components/Boo.jsx: -------------------------------------------------------------------------------- 1 | import { defineComponent } from 'vue'; 2 | 3 | export default defineComponent({ 4 | data() { 5 | return { 6 | name: 'boo' 7 | }; 8 | }, 9 | methods: {}, 10 | render() { 11 | return
show: {this.name}
; 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /src/components/Foo.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/components/LangSwitch.vue: -------------------------------------------------------------------------------- 1 | 13 | 48 | -------------------------------------------------------------------------------- /src/components/MoreSetting.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 133 | -------------------------------------------------------------------------------- /src/components/cluster/ClusterInfoColumns.jsx: -------------------------------------------------------------------------------- 1 | import { NButton } from 'naive-ui'; 2 | import { useI18n } from 'vue-i18n'; 3 | 4 | export const createColumns = function () { 5 | const { t } = useI18n(); 6 | const columns = [ 7 | { 8 | title: t('cluster.node') + 'Id', 9 | key: 'nodeId', 10 | render(row) { 11 | if (row.currentNode) { 12 | return ( 13 | 14 | {row.nodeId} 15 | 16 | 【{t('common.query')} {t('cluster.node')}】 17 | 18 | 19 | ); 20 | } 21 | return row.nodeId; 22 | } 23 | }, 24 | { 25 | title: t('cluster.node') + ' ' + t('cluster.address') + '(grpc)', 26 | key: 'addr' 27 | }, 28 | { 29 | title: 'raft' + t('cluster.masternode'), 30 | key: 'raftLeader', 31 | render(row) { 32 | if (row.raftLeader) { 33 | return ( 34 | 35 | {t('common.yes')} 36 | 37 | ); 38 | } else { 39 | return ( 40 | 41 | {t('common.no')} 42 | 43 | ); 44 | } 45 | } 46 | }, 47 | { 48 | title: t('cluster.node') + ' ' + t('common.status'), 49 | key: 'distroValid', 50 | render(row) { 51 | if (row.distroValid) { 52 | return ( 53 | 54 | {t('common.enabled')} 55 | 56 | ); 57 | } else { 58 | return ( 59 | 60 | {t('common.disabled')} 61 | 62 | ); 63 | } 64 | } 65 | } 66 | ]; 67 | return columns; 68 | }; 69 | -------------------------------------------------------------------------------- /src/components/common/SubContentFullPage.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 | 113 | -------------------------------------------------------------------------------- /src/components/config/ConfigColumns.jsx: -------------------------------------------------------------------------------- 1 | import { NButton, NPopconfirm } from 'naive-ui'; 2 | import { toDatetime } from '@/utils/date'; 3 | import { useI18n } from 'vue-i18n'; 4 | import template from 'template_js'; 5 | 6 | export const createColumns = function ( 7 | detail, 8 | showHistory, 9 | showUpdate, 10 | showClone, 11 | remove, 12 | webResources 13 | ) { 14 | const { t } = useI18n(); 15 | const removeConfirmSlots = { 16 | trigger: () => { 17 | return ( 18 | 19 | {t('common.delete')} 20 | 21 | ); 22 | } 23 | }; 24 | 25 | const columns = [ 26 | { 27 | title: t('config.config') + ' ID', 28 | key: 'dataId' 29 | }, 30 | { 31 | title: t('config.config_group'), 32 | key: 'group' 33 | }, 34 | { 35 | title: t('config.desc'), 36 | key: 'desc' 37 | }, 38 | { 39 | title: t('common.operation'), 40 | key: 'type', 41 | fixed: 'right', 42 | width: 120, 43 | render(row) { 44 | let editButton; 45 | let removePopconfirm; 46 | let cloneButton; 47 | if (webResources.canUpdateConfig) { 48 | editButton = ( 49 | showUpdate(row)} 54 | > 55 | {t('common.edit')} 56 | 57 | ); 58 | cloneButton = ( 59 | showClone(row)} 64 | > 65 | {t('common.clone')} 66 | 67 | ); 68 | removePopconfirm = ( 69 | remove(row)} 71 | v-slots={removeConfirmSlots} 72 | > 73 | 74 | {template(t('config.confirm_delete_config_action'), { 75 | group: row.group, 76 | dataId: row.dataId 77 | })} 78 | 79 | 80 | ); 81 | } else { 82 | editButton = ; 83 | removePopconfirm = editButton; 84 | } 85 | return ( 86 |
87 | detail(row)} 92 | > 93 | {t('common.detail')} 94 | 95 | showHistory(row)} 100 | > 101 | {t('common.history')} 102 | 103 | {editButton} 104 | {cloneButton} 105 | {removePopconfirm} 106 |
107 | ); 108 | } 109 | } 110 | ]; 111 | return columns; 112 | }; 113 | 114 | export const createHistoryColumns = function (detail, rollback, webResources) { 115 | const { t } = useI18n(); 116 | const rollbackConfirmSlots = { 117 | trigger: () => { 118 | return {t('common.recover')}; 119 | } 120 | }; 121 | 122 | const columns = [ 123 | { 124 | title: 'ID', 125 | key: 'id' 126 | }, 127 | { 128 | title: t('config.config') + ' ID', 129 | key: 'dataId' 130 | }, 131 | { 132 | title: t('config.config_group'), 133 | key: 'group' 134 | }, 135 | { 136 | title: t('common.updatedtime'), 137 | key: 'modifiedTime', 138 | render(row) { 139 | var value = ''; 140 | if (row.modifiedTime) { 141 | var date = new Date(row.modifiedTime); 142 | value = toDatetime(date); 143 | } 144 | return {value}; 145 | } 146 | }, 147 | { 148 | title: t('common.operation'), 149 | key: 'type', 150 | fixed: 'right', 151 | /* 152 | rollback(row)} v-slots={rollbackConfirmSlots} > 153 | {template(t("config.confirm_recover_config_action"),{id:row.id})} 154 | 155 | */ 156 | render(row) { 157 | let rollbackButton; 158 | if (webResources.canUpdateConfig) { 159 | rollbackButton = ( 160 | rollback(row)} 165 | > 166 | {t('common.recover')} 167 | 168 | ); 169 | } else { 170 | rollbackButton = ; 171 | } 172 | return ( 173 |
174 |
175 | detail(row)} 180 | > 181 | {t('common.detail')} 182 | 183 | {rollbackButton} 184 |
185 |
186 | ); 187 | } 188 | } 189 | ]; 190 | return columns; 191 | }; 192 | -------------------------------------------------------------------------------- /src/components/config/DiffComponent.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 45 | -------------------------------------------------------------------------------- /src/components/config/cm6theme.js: -------------------------------------------------------------------------------- 1 | // Modified based on cm6-theme-solarized-dark 2 | // 3 | import { EditorView } from '@codemirror/view'; 4 | import { HighlightStyle, syntaxHighlighting } from '@codemirror/language'; 5 | import { tags } from '@lezer/highlight'; 6 | 7 | const base00 = '#002b36', 8 | base01 = '#073642', 9 | base02 = '#586e75', 10 | base03 = '#657b83', 11 | base04 = '#839496', 12 | base05 = '#93a1a1', 13 | base06 = '#eee8d5', 14 | base07 = '#fdf6e3', 15 | base_red = '#dc322f', 16 | base_orange = '#cb4b16', 17 | base_yellow = '#b58900', 18 | base_green = '#859900', 19 | base_cyan = '#2aa198', 20 | base_blue = '#268bd2', 21 | base_violet = '#6c71c4', 22 | base_magenta = '#d33682'; 23 | const invalid = '#d30102', 24 | stone = base04, 25 | darkBackground = '#00252f', 26 | highlightBackground = '#27455190', 27 | background = base00, 28 | tooltipBackground = base01, 29 | selection = '#324c57', 30 | cursor = base04; 31 | //const invalid = '#d30102', stone = base04, darkBackground = '#00252f', highlightBackground = '#173541', background = base00, tooltipBackground = base01, selection = '#173541', cursor = base04; 32 | /** 33 | The editor theme styles for Solarized Dark. 34 | */ 35 | const solarizedDarkTheme = /*@__PURE__*/ EditorView.theme( 36 | { 37 | '&': { 38 | color: base05, 39 | backgroundColor: background 40 | }, 41 | '.cm-content': { 42 | caretColor: cursor 43 | }, 44 | '.cm-cursor, .cm-dropCursor': { borderLeftColor: cursor }, 45 | //'&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': { backgroundColor: selection }, 46 | '&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': 47 | { backgroundColor: selection }, 48 | '.cm-panels': { backgroundColor: darkBackground, color: base03 }, 49 | '.cm-panels.cm-panels-top': { borderBottom: '2px solid black' }, 50 | '.cm-panels.cm-panels-bottom': { borderTop: '2px solid black' }, 51 | '.cm-searchMatch': { 52 | backgroundColor: '#72a1ff59', 53 | outline: '1px solid #457dff' 54 | }, 55 | '.cm-searchMatch.cm-searchMatch-selected': { 56 | backgroundColor: '#6199ff2f' 57 | }, 58 | '.cm-activeLine': { backgroundColor: highlightBackground }, 59 | '.cm-selectionMatch': { backgroundColor: '#aafe661a' }, 60 | '&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': { 61 | outline: `1px solid ${base06}` 62 | }, 63 | '.cm-gutters': { 64 | backgroundColor: darkBackground, 65 | color: stone, 66 | border: 'none' 67 | }, 68 | '.cm-activeLineGutter': { 69 | backgroundColor: highlightBackground 70 | }, 71 | '.cm-foldPlaceholder': { 72 | backgroundColor: 'transparent', 73 | border: 'none', 74 | color: '#ddd' 75 | }, 76 | '.cm-tooltip': { 77 | border: 'none', 78 | backgroundColor: tooltipBackground 79 | }, 80 | '.cm-tooltip .cm-tooltip-arrow:before': { 81 | borderTopColor: 'transparent', 82 | borderBottomColor: 'transparent' 83 | }, 84 | '.cm-tooltip .cm-tooltip-arrow:after': { 85 | borderTopColor: tooltipBackground, 86 | borderBottomColor: tooltipBackground 87 | }, 88 | '.cm-tooltip-autocomplete': { 89 | '& > ul > li[aria-selected]': { 90 | backgroundColor: highlightBackground, 91 | color: base03 92 | } 93 | } 94 | }, 95 | { dark: true } 96 | ); 97 | /** 98 | The highlighting style for code in the Solarized Dark theme. 99 | */ 100 | const solarizedDarkHighlightStyle = /*@__PURE__*/ HighlightStyle.define([ 101 | { tag: tags.keyword, color: base_green }, 102 | { 103 | tag: [ 104 | tags.name, 105 | tags.deleted, 106 | tags.character, 107 | tags.propertyName, 108 | tags.macroName 109 | ], 110 | color: base_cyan 111 | }, 112 | { tag: [tags.variableName], color: base05 }, 113 | { tag: [/*@__PURE__*/ tags.function(tags.variableName)], color: base_blue }, 114 | { tag: [tags.labelName], color: base_magenta }, 115 | { 116 | tag: [ 117 | tags.color, 118 | /*@__PURE__*/ tags.constant(tags.name), 119 | /*@__PURE__*/ tags.standard(tags.name) 120 | ], 121 | color: base_yellow 122 | }, 123 | { 124 | tag: [/*@__PURE__*/ tags.definition(tags.name), tags.separator], 125 | color: base_cyan 126 | }, 127 | { tag: [tags.brace], color: base_magenta }, 128 | { 129 | tag: [tags.annotation], 130 | color: invalid 131 | }, 132 | { 133 | tag: [ 134 | tags.number, 135 | tags.changed, 136 | tags.annotation, 137 | tags.modifier, 138 | tags.self, 139 | tags.namespace 140 | ], 141 | color: base_magenta 142 | }, 143 | { 144 | tag: [tags.typeName, tags.className], 145 | color: base_orange 146 | }, 147 | { 148 | tag: [tags.operator, tags.operatorKeyword], 149 | color: base_violet 150 | }, 151 | { 152 | tag: [tags.tagName], 153 | color: base_blue 154 | }, 155 | { 156 | tag: [tags.squareBracket], 157 | color: base_red 158 | }, 159 | { 160 | tag: [tags.angleBracket], 161 | color: base02 162 | }, 163 | { 164 | tag: [tags.attributeName], 165 | color: base05 166 | }, 167 | { 168 | tag: [tags.regexp], 169 | color: invalid 170 | }, 171 | { 172 | tag: [tags.quote], 173 | color: base_green 174 | }, 175 | { tag: [tags.string], color: base_yellow }, 176 | { 177 | tag: tags.link, 178 | color: base_cyan, 179 | textDecoration: 'underline', 180 | textUnderlinePosition: 'under' 181 | }, 182 | { 183 | tag: [tags.url, tags.escape, /*@__PURE__*/ tags.special(tags.string)], 184 | color: base_yellow 185 | }, 186 | { tag: [tags.meta], color: base_red }, 187 | { tag: [tags.comment], color: base02, fontStyle: 'italic' }, 188 | { tag: tags.strong, fontWeight: 'bold', color: base06 }, 189 | { tag: tags.emphasis, fontStyle: 'italic', color: base_green }, 190 | { tag: tags.strikethrough, textDecoration: 'line-through' }, 191 | { tag: tags.heading, fontWeight: 'bold', color: base_yellow }, 192 | { tag: tags.heading1, fontWeight: 'bold', color: base07 }, 193 | { 194 | tag: [tags.heading2, tags.heading3, tags.heading4], 195 | fontWeight: 'bold', 196 | color: base06 197 | }, 198 | { 199 | tag: [tags.heading5, tags.heading6], 200 | color: base06 201 | }, 202 | { 203 | tag: [tags.atom, tags.bool, /*@__PURE__*/ tags.special(tags.variableName)], 204 | color: base_magenta 205 | }, 206 | { 207 | tag: [tags.processingInstruction, tags.inserted, tags.contentSeparator], 208 | color: base_red 209 | }, 210 | { 211 | tag: [tags.contentSeparator], 212 | color: base_yellow 213 | }, 214 | { tag: tags.invalid, color: base02, borderBottom: `1px dotted ${base_red}` } 215 | ]); 216 | /** 217 | Extension to enable the Solarized Dark theme (both the editor theme and 218 | the highlight style). 219 | */ 220 | const solarizedDark = [ 221 | solarizedDarkTheme, 222 | /*@__PURE__*/ syntaxHighlighting(solarizedDarkHighlightStyle) 223 | ]; 224 | 225 | export { solarizedDark, solarizedDarkHighlightStyle, solarizedDarkTheme }; 226 | -------------------------------------------------------------------------------- /src/components/namespace/NamespacePopSelect.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 104 | -------------------------------------------------------------------------------- /src/components/naming/InstanceListColumns.jsx: -------------------------------------------------------------------------------- 1 | import { NButton, NSwitch } from 'naive-ui'; 2 | import { useI18n } from 'vue-i18n'; 3 | import { toDatetime } from '@/utils/date'; 4 | /* 5 | let slots ={ 6 | "checked":()=> { 7 | return 上线 8 | }, 9 | "unchecked":()=> { 10 | return 下线 11 | }, 12 | }; 13 | */ 14 | 15 | export const createColumns = function ( 16 | showUpdate, 17 | onLine, 18 | offLine, 19 | webResources 20 | ) { 21 | const { t } = useI18n(); 22 | const columns = [ 23 | { 24 | title: 'IP', 25 | key: 'ip' 26 | }, 27 | { 28 | title: t('instance.port'), 29 | key: 'port' 30 | }, 31 | { 32 | title: t('instance.ephemeral'), 33 | key: 'ephemeral', 34 | render(row) { 35 | return {row.ephemeral.toString()}; 36 | } 37 | }, 38 | { 39 | title: t('instance.weight'), 40 | key: 'weight' 41 | }, 42 | { 43 | title: t('instance.healthy'), 44 | key: 'healthy', 45 | render(row) { 46 | return {row.healthy.toString()}; 47 | } 48 | }, 49 | { 50 | title: t('instance.registerTime'), 51 | key: 'registerTime', 52 | render(row) { 53 | var value = ''; 54 | if (row.registerTime) { 55 | var date = new Date(row.registerTime); 56 | value = toDatetime(date); 57 | } 58 | return {value}; 59 | } 60 | }, 61 | { 62 | title: t('instance.metadata'), 63 | key: 'metadata', 64 | width: 200, 65 | render(row) { 66 | return {JSON.stringify(row.metadata)}; 67 | } 68 | } 69 | ]; 70 | let optColumn = { 71 | title: t('common.operation'), 72 | key: '_type', 73 | fixed: 'right', 74 | width: 120, 75 | render(row) { 76 | const onOffLine = () => { 77 | // v-slots={slots} 78 | return ( 79 | { 83 | if (enabled == row.enabled) { 84 | //操作中 85 | return; 86 | } 87 | if (enabled) { 88 | onLine(row); 89 | } else { 90 | offLine(row); 91 | } 92 | }} 93 | /> 94 | ); 95 | }; 96 | return ( 97 |
98 | {onOffLine()} 99 | showUpdate(row)} 104 | > 105 | {t('common.edit')} 106 | 107 |
108 | ); 109 | } 110 | }; 111 | if (webResources.canUpdateService) { 112 | columns.push(optColumn); 113 | } 114 | return columns; 115 | }; 116 | -------------------------------------------------------------------------------- /src/components/naming/ServiceListColumns.jsx: -------------------------------------------------------------------------------- 1 | import { NButton, NPopconfirm } from 'naive-ui'; 2 | import { useI18n } from 'vue-i18n'; 3 | import template from 'template_js'; 4 | export const createColumns = function ( 5 | showInstances, 6 | detail, 7 | showUpdate, 8 | remove, 9 | showSubscribers, 10 | webResources 11 | ) { 12 | const { t } = useI18n(); 13 | const removeConfirmSlots = { 14 | trigger: () => { 15 | return ( 16 | 17 | {t('common.delete')} 18 | 19 | ); 20 | } 21 | }; 22 | 23 | const columns = [ 24 | { 25 | title: t('service.name'), 26 | key: 'name' 27 | }, 28 | { 29 | title: t('service.groupName'), 30 | key: 'groupName' 31 | }, 32 | { 33 | title: t('service.ipCount'), 34 | key: 'ipCount' 35 | }, 36 | { 37 | title: t('service.healthyInstanceCount'), 38 | key: 'healthyInstanceCount' 39 | }, 40 | { 41 | title: t('common.operation'), 42 | key: 'type', 43 | fixed: 'right', 44 | width: 120, 45 | render(row) { 46 | let editButton; 47 | let removePopconfirm; 48 | if (webResources.canUpdateService) { 49 | editButton = ( 50 | showUpdate(row)} 55 | > 56 | {t('common.edit')} 57 | 58 | ); 59 | removePopconfirm = ( 60 | remove(row)} 62 | v-slots={removeConfirmSlots} 63 | > 64 | 65 | {template(t('service.confirm_delete_service_action'), { 66 | name: row.name, 67 | groupName: row.groupName 68 | })} 69 | 70 | 71 | ); 72 | } else { 73 | editButton = ; 74 | removePopconfirm = editButton; 75 | } 76 | return ( 77 |
78 | showInstances(row)} 83 | > 84 | {t('service.instance')} 85 | 86 | detail(row)} 91 | > 92 | {t('common.detail')} 93 | 94 | {editButton} 95 | showSubscribers(row)} 100 | > 101 | {t('service.subscriber')} 102 | 103 | {removePopconfirm} 104 |
105 | ); 106 | } 107 | } 108 | ]; 109 | return columns; 110 | }; 111 | -------------------------------------------------------------------------------- /src/components/naming/SuberscriberListColumns.jsx: -------------------------------------------------------------------------------- 1 | import { useI18n } from 'vue-i18n'; 2 | export const createColumns = function () { 3 | const { t } = useI18n(); 4 | 5 | const columns = [ 6 | { 7 | title: t('service.name'), 8 | key: 'serviceName' 9 | }, 10 | { 11 | title: t('service.groupName'), 12 | key: 'groupName' 13 | }, 14 | { 15 | title: t('client.address'), 16 | key: 'address', 17 | render(row) { 18 | return `${row.ip}:${row.port}`; 19 | } 20 | } 21 | ]; 22 | return columns; 23 | }; 24 | -------------------------------------------------------------------------------- /src/components/user/ResetPassword.vue: -------------------------------------------------------------------------------- 1 | 40 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /src/components/user/UserListColumns.jsx: -------------------------------------------------------------------------------- 1 | import { NButton, NSwitch, NTag, NPopconfirm } from 'naive-ui'; 2 | 3 | import { toDatetime } from '@/utils/date'; 4 | import { arrayCount } from '@/utils/utils.js'; 5 | import { getRoleNameByCode } from '@/data/role'; 6 | import { useI18n } from 'vue-i18n'; 7 | import template from 'template_js'; 8 | export const defaultNamespacePrivilege = { 9 | enabled: true, 10 | whitelistIsAll: true, 11 | whitelist: null, 12 | blacklistIsAll: false, 13 | blacklist: null 14 | }; 15 | 16 | export const createColumns = function (showDetail, showUpdate, remove) { 17 | const { t } = useI18n(); 18 | const removeConfirmSlots = { 19 | trigger: () => { 20 | return ( 21 | 22 | {t('common.delete')} 23 | 24 | ); 25 | } 26 | }; 27 | const columns = [ 28 | { 29 | title: t('user.username'), 30 | key: 'username' 31 | }, 32 | { 33 | title: t('user.nickname'), 34 | key: 'nickname' 35 | }, 36 | { 37 | title: t('user.gmtCreate'), 38 | key: 'gmtCreate', 39 | render(row) { 40 | var value = ''; 41 | if (row.gmtCreate) { 42 | var date = new Date(row.gmtCreate); 43 | value = toDatetime(date); 44 | } 45 | return {value}; 46 | } 47 | }, 48 | { 49 | title: t('user.gmtModified'), 50 | key: 'gmtModified', 51 | render(row) { 52 | var value = ''; 53 | if (row.gmtModified) { 54 | var date = new Date(row.gmtModified); 55 | value = toDatetime(date); 56 | } 57 | return {value}; 58 | } 59 | }, 60 | { 61 | title: t('user.roles'), 62 | key: 'roles', 63 | render(row) { 64 | const roleItems = row.roles.map((item) => ( 65 | {getRoleNameByCode(item)} 66 | )); 67 | return <>{roleItems}; 68 | } 69 | }, 70 | { 71 | title: t('user.enable'), 72 | key: 'enable', 73 | render(row) { 74 | var v = t('common.yes'); 75 | if (!row.enable) { 76 | v = t('common.no'); 77 | } 78 | return {v}; 79 | } 80 | }, 81 | { 82 | title: t('menu.namespace') + t('common.join') + t('common.whitelist'), 83 | key: 'namespaceWhitelist', 84 | render(row) { 85 | let namespacePrivilege = 86 | row.namespacePrivilege || defaultNamespacePrivilege; 87 | let v = t('common.all'); 88 | if (namespacePrivilege.enabled && !namespacePrivilege.whitelistIsAll) { 89 | v = 90 | t('common.part') + 91 | '(' + 92 | arrayCount(namespacePrivilege.whitelist) + 93 | ')'; 94 | } 95 | return {v}; 96 | } 97 | }, 98 | { 99 | title: t('menu.namespace') + t('common.join') + t('common.blacklist'), 100 | key: 'namespaceBlacklist', 101 | render(row) { 102 | let namespacePrivilege = 103 | row.namespacePrivilege || defaultNamespacePrivilege; 104 | var v = 105 | t('common.part') + 106 | '(' + 107 | arrayCount(namespacePrivilege.blacklist) + 108 | ')'; 109 | if (namespacePrivilege.enabled && namespacePrivilege.blacklistIsAll) { 110 | v = t('common.all'); 111 | } 112 | return {v}; 113 | } 114 | }, 115 | { 116 | title: t('common.operation'), 117 | key: 'actions', 118 | fixed: 'right', 119 | width: 120, 120 | render(row) { 121 | /* 122 | showDetail(row)} 127 | > 128 | {t('common.detail')} 129 | 130 | */ 131 | return ( 132 |
133 | showUpdate(row)} 138 | > 139 | {t('common.edit')} 140 | 141 | remove(row)} 143 | v-slots={removeConfirmSlots} 144 | > 145 | 146 | {template(t('user.confirm_delete_user_action'), { 147 | username: row.username 148 | })} 149 | 150 | 151 |
152 | ); 153 | } 154 | } 155 | ]; 156 | return columns; 157 | }; 158 | -------------------------------------------------------------------------------- /src/data/appdata.ts: -------------------------------------------------------------------------------- 1 | import { ISize } from '@/types/base'; 2 | import { defineStore } from 'pinia'; 3 | import { useProjectSettingStore } from '@/store/modules/projectSetting'; 4 | import setting from '@/settings/projectSetting'; 5 | 6 | interface LayoutSizeState { 7 | headerHeight: number; 8 | footerHeight: number; 9 | siderWidth: number; 10 | contentHeight: number; 11 | contentWidth: number; 12 | windowSize: ISize; 13 | } 14 | 15 | export const useLayoutSize = defineStore('layoutSize', { 16 | state: (): LayoutSizeState => ({ 17 | headerHeight: setting.headerSetting.height, 18 | footerHeight: 0, 19 | siderWidth: setting.menuSetting.menuWidth, 20 | contentHeight: 0, 21 | contentWidth: 0, 22 | windowSize: { 23 | height: 0, 24 | width: 0 25 | } 26 | }), 27 | actions: { 28 | updateLayoutSize( 29 | this: { 30 | $state: LayoutSizeState; 31 | $patch: (fn: (state: LayoutSizeState) => void) => void; 32 | }, 33 | windowSize?: ISize 34 | ) { 35 | this.$patch((state) => { 36 | if (windowSize) { 37 | state.windowSize = windowSize; 38 | } else { 39 | state.windowSize = { 40 | height: window.innerHeight, 41 | width: window.innerWidth 42 | }; 43 | } 44 | state.contentHeight = 45 | state.windowSize.height - state.headerHeight - state.footerHeight; 46 | const projectSettingStore = useProjectSettingStore(); 47 | const currentWidth = window.innerWidth; 48 | if (projectSettingStore.getIsMobile) { 49 | state.contentWidth = currentWidth; 50 | } else { 51 | state.contentWidth = 52 | currentWidth - 53 | (projectSettingStore.getMenuCollapsed 54 | ? setting.menuSetting.minMenuWidth 55 | : setting.menuSetting.menuWidth); 56 | } 57 | }); 58 | } 59 | } 60 | }); 61 | -------------------------------------------------------------------------------- /src/data/lang.ts: -------------------------------------------------------------------------------- 1 | import { Ref, UnwrapRef, ref } from 'vue'; 2 | import { ILangStore } from '@/types/base'; 3 | 4 | // 前期没有使用 pinia,后继调整时考虑迁移到pinia 5 | const LANG_STORAGE_KEY = 'lang'; 6 | 7 | function createStore(): ILangStore { 8 | const currentRef: Ref> = ref('en'); 9 | const setCurrent = function (current: string) { 10 | currentRef.value = current; 11 | localStorage.setItem(LANG_STORAGE_KEY, current); 12 | }; 13 | const value = 14 | localStorage.getItem(LANG_STORAGE_KEY) || 15 | (navigator.language || 'en').toLocaleLowerCase().split('-')[0] || 16 | 'en'; 17 | setCurrent(value); 18 | return { 19 | current: currentRef, 20 | setCurrent 21 | }; 22 | } 23 | 24 | export const langStore: ILangStore = createStore(); 25 | -------------------------------------------------------------------------------- /src/data/namespace.ts: -------------------------------------------------------------------------------- 1 | import { Ref, UnwrapRef, reactive, ref } from 'vue'; 2 | import namespaceApi from '../api/namespace'; 3 | import { INamespace, INamespaceStore } from '@/types/namespace'; 4 | import { ILabelItem } from '@/types/base'; 5 | import { handleApiResult } from '@/utils/request.ts'; 6 | 7 | // 前期没有使用 pinia,后继调整时考虑迁移到pinia 8 | const NAMESPACE_STORAGE_KEY = 'RNACOS_NAMESPACE_KEY'; 9 | 10 | function createStore(): INamespaceStore { 11 | const currentRef: Ref> = ref({ 12 | namespaceId: '', 13 | namespaceName: 'public' 14 | }); 15 | const listListRef: Ref>> = ref([ 16 | { 17 | namespaceId: '', 18 | namespaceName: 'public' 19 | } 20 | ]); 21 | const optionListRef: Ref>> = ref([ 22 | { 23 | label: 'public', 24 | value: '' 25 | } 26 | ]); 27 | const loadRef = ref(false); 28 | const setCurrent = function (current: INamespace) { 29 | currentRef.value = current; 30 | localStorage.setItem(NAMESPACE_STORAGE_KEY, JSON.stringify(current)); 31 | }; 32 | const setLastList = function (list: Array) { 33 | var optionList = []; 34 | for (var item of list) { 35 | var obj = { 36 | label: item.namespaceName, 37 | value: item.namespaceId 38 | } as ILabelItem; 39 | optionList.push(obj); 40 | } 41 | listListRef.value = list; 42 | optionListRef.value = optionList; 43 | loadRef.value = true; 44 | }; 45 | const initLoad = function () { 46 | if (!loadRef.value) { 47 | namespaceApi 48 | .queryList() 49 | .then(handleApiResult) 50 | .then((list) => { 51 | setLastList(list || []); 52 | }); 53 | } 54 | }; 55 | //load value from localStorage 56 | try { 57 | let storageValue = localStorage.getItem(NAMESPACE_STORAGE_KEY); 58 | if (storageValue) { 59 | let obj = JSON.parse(storageValue); 60 | setCurrent(obj); 61 | } 62 | } catch (err) { 63 | console.warn('load value from localStorage error', err); 64 | } 65 | return { 66 | current: currentRef, 67 | listList: listListRef, 68 | optionList: optionListRef, 69 | setCurrent, 70 | setLastList, 71 | initLoad 72 | }; 73 | } 74 | 75 | export const namespaceStore: INamespaceStore = createStore(); 76 | -------------------------------------------------------------------------------- /src/data/resources.ts: -------------------------------------------------------------------------------- 1 | import { ISize, WebResource } from '@/types/base'; 2 | import { sideAllMenu } from '@/route/routes'; 3 | import { defineStore } from 'pinia'; 4 | 5 | function sideMenu(resource: Set, isOldConsole: boolean) { 6 | var items = []; 7 | for (var item of sideAllMenu) { 8 | var subItems = []; 9 | for (var subItem of item.children || []) { 10 | if (resource.has(subItem.path)) { 11 | subItems.push(subItem); 12 | } else if (isOldConsole && subItem.path != '/manage/user') { 13 | subItems.push(subItem); 14 | } 15 | } 16 | if (subItems.length == 0) { 17 | continue; 18 | } 19 | let newItem = { ...item }; 20 | newItem.children = subItems; 21 | items.push(newItem); 22 | } 23 | return items; 24 | } 25 | 26 | export const useWebResources = defineStore('webResources', { 27 | state: () => ({ 28 | resource: new Set(), 29 | isOldConsole: true, 30 | fromRequest: false, 31 | username: '', 32 | version: 'x', 33 | canUpdateConfig: true, 34 | canUpdateService: true, 35 | canUpdateNamespace: true, 36 | sideMenu: sideMenu(new Set(), true) 37 | }), 38 | getters: {}, 39 | actions: { 40 | update(webResource: WebResource) { 41 | let resource = new Set(webResource.resources); 42 | this.resource = resource; 43 | this.isOldConsole = webResource.from === 'OLD_CONSOLE'; 44 | this.fromRequest = true; 45 | this.canUpdateConfig = this.resource.has('CONFIG_UPDATE'); 46 | this.canUpdateService = this.resource.has('SERVICE_UPDATE'); 47 | this.canUpdateNamespace = this.resource.has('NAMESPACE_UPDATE'); 48 | this.version = 'v' + webResource.version; 49 | this.username = webResource.username || ''; 50 | this.sideMenu = sideMenu(resource, this.isOldConsole); 51 | return this.sideMenu; 52 | }, 53 | clear() { 54 | this.resource = new Set(); 55 | this.isOldConsole = true; 56 | this.fromRequest = false; 57 | this.canUpdateConfig = true; 58 | this.canUpdateService = true; 59 | this.canUpdateNamespace = true; 60 | this.username = ''; 61 | this.version = 'x'; 62 | } 63 | } 64 | }); 65 | -------------------------------------------------------------------------------- /src/data/role.ts: -------------------------------------------------------------------------------- 1 | import i18n from '@/i18n/index'; //我本地的i18n实例 2 | let _t: any = i18n.global; 3 | 4 | export interface Item { 5 | value: string; 6 | label: string; 7 | } 8 | export const roleOptions: Array = [ 9 | { 10 | value: '0', 11 | label: _t.t('role.admin') 12 | }, 13 | { 14 | value: '1', 15 | label: _t.t('role.developer') 16 | }, 17 | { 18 | value: '2', 19 | label: _t.t('role.guest') 20 | } 21 | ]; 22 | 23 | export const getRoleNameByCode = function (value: String) { 24 | for (var item of roleOptions) { 25 | if (value == item.value) { 26 | return item.label; 27 | } 28 | } 29 | return value; 30 | }; 31 | -------------------------------------------------------------------------------- /src/hooks/setting/useProjectSetting.ts: -------------------------------------------------------------------------------- 1 | import { computed } from 'vue'; 2 | import { useProjectSettingStore } from '@/store/modules/projectSetting'; 3 | 4 | export function useProjectSetting() { 5 | const projectStore = useProjectSettingStore(); 6 | 7 | const navMode = computed(() => projectStore.navMode); 8 | 9 | const navTheme = computed(() => projectStore.navTheme); 10 | 11 | const isMobile = computed(() => projectStore.isMobile); 12 | 13 | const headerSetting = computed(() => projectStore.headerSetting); 14 | 15 | const multiTabsSetting = computed(() => projectStore.multiTabsSetting); 16 | 17 | const menuSetting = computed(() => projectStore.menuSetting); 18 | 19 | const crumbsSetting = computed(() => projectStore.crumbsSetting); 20 | 21 | // const permissionMode = computed(() => projectStore.permissionMode); 22 | 23 | const showFooter = computed(() => projectStore.showFooter); 24 | 25 | // const isPageAnimate = computed(() => projectStore.isPageAnimate); 26 | 27 | // const pageAnimateType = computed(() => projectStore.pageAnimateType); 28 | 29 | return { 30 | navMode, 31 | navTheme, 32 | isMobile, 33 | headerSetting, 34 | multiTabsSetting, 35 | menuSetting, 36 | crumbsSetting, 37 | // permissionMode, 38 | showFooter 39 | // isPageAnimate, 40 | // pageAnimateType, 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /src/i18n/index.js: -------------------------------------------------------------------------------- 1 | import { createI18n } from 'vue-i18n'; 2 | import zh from './lang/zh-CN'; 3 | import en from './lang/en-US'; 4 | 5 | const messages = { 6 | zh, 7 | en 8 | }; 9 | const language = (navigator.language || 'en').toLocaleLowerCase(); // 这是获取浏览器的语言 10 | const locale = localStorage.getItem('lang') || language.split('-')[0] || 'en'; // 首先从缓存里拿,没有的话就用浏览器语言, 11 | const i18n = createI18n({ 12 | locale, 13 | fallbackLocale: 'zh', // 设置备用语言 14 | messages 15 | }); 16 | 17 | /** 18 | * 根据key获取对应语言的message 19 | * key='common.query' => message['common']['query'] 20 | * @param key 21 | * @returns {*|string} 22 | */ 23 | export const getMessage = function (key) { 24 | var obj = messages[locale] || en; 25 | var items = key.split('.'); 26 | for (var subKey of items) { 27 | obj = obj[subKey]; 28 | } 29 | return obj || ''; 30 | }; 31 | 32 | export default i18n; 33 | -------------------------------------------------------------------------------- /src/i18n/lang/zh-CN.js: -------------------------------------------------------------------------------- 1 | import zhCN from 'naive-ui/lib/locales/common/zhCN.js'; 2 | const message = { 3 | common: { 4 | query: '查询', 5 | status: '状态', 6 | yes: '是', 7 | no: '否', 8 | enabled: '正常', 9 | disabled: '失效', 10 | confirm: '确认', 11 | title: '标题', 12 | return: '返回', 13 | back: '返回', 14 | confirm_action: '是否确认操作?', 15 | delete: '删除', 16 | edit: '编辑', 17 | detail: '详情', 18 | history: '历史记录', 19 | operation: '操作', 20 | clone: '克隆', 21 | updatedtime: '更新时间', 22 | recover: '恢复', 23 | request_fail: '请求失败', 24 | refresh: '刷新', 25 | total: '总行数', 26 | add: '新建', 27 | preInput: '输入', 28 | join: '', 29 | whitelist: '白名单', 30 | blacklist: '黑名单', 31 | all: '全部', 32 | part: '部分', 33 | permission: '权限', 34 | submitSuccess: '提交成功', 35 | success: '成功', 36 | home: '首页' 37 | }, 38 | cluster: { 39 | node: '节点', 40 | address: '地址', 41 | masternode: '主节点', 42 | cluster_info: '集群信息' 43 | }, 44 | client: { 45 | address: '客户端地址' 46 | }, 47 | config: { 48 | config: '配置', 49 | config_id: '配置ID', 50 | config_group: '配置组', 51 | confirm_delete_config_action: 52 | '确认要删配置组为:<%:=group%>,ID为:<%:=dataId%>的配置吗?', 53 | confirm_recover_config_action: '确认要恢复ID为 <%:=id%> 的历史配置内容吗?', 54 | comparison_of_configuration_changes: '配置变更比较', 55 | current_configuration: '当前配置', 56 | new_configurations_to_be_submitted: '待提交的新配置', 57 | set: '设置', 58 | get: '获取', 59 | dataId: '配置ID', 60 | input_dataId: '输入配置ID', 61 | need_input_dataId: '需要输入配置ID', 62 | input_config_group: '输入配置组', 63 | desc: '描述', 64 | input_desc: '输入描述备注信息', 65 | configType: '配置格式', 66 | content: '配置内容', 67 | input_content: '输入配置内容', 68 | check_fail: '检验不通过', 69 | config_history: '配置历史记录列表', 70 | confirm_change: '确认变更', 71 | diff_content: '配置内容比较', 72 | recover_fail: '恢复失败', 73 | recover_success: '恢复成功', 74 | history_record_content: '历史记录内容', 75 | recover_history: '恢复历史记录', 76 | config_list: '配置列表', 77 | export_config: '导出配置', 78 | import_config: '导入配置' 79 | }, 80 | namespace: { 81 | namespace: '命名空间', 82 | the_namespace_id_has_been_copied: '已复制命名空间id!', 83 | namespaceName: '命名空间名称', 84 | namespaceId: '命名空间Id', 85 | new_namespace: '创建命名空间', 86 | add_namespace: '新增命名空间', 87 | edit_namespace: '修改命名空间', 88 | retain_space: '保留空间', 89 | confirm_delete_info: "确认要删除 '<%:=name%>'(ID: <%:=id%>) 命名空间吗?", 90 | namespaceId_or: '命名空间ID,不填则自动生成' 91 | }, 92 | instance: { 93 | list: '服务实例列表', 94 | port: '端口', 95 | ephemeral: '是否临时实例', 96 | weight: '权重', 97 | healthy: '健康状态', 98 | online: '是否上线', 99 | onlineText: '上线', 100 | offlineText: '下线', 101 | editTitle: '编辑实例', 102 | registerTime: '注册时间', 103 | metadata: '元数据' 104 | }, 105 | service: { 106 | name: '服务名称', 107 | subscriber: '订阅者', 108 | inputName: '输入服务名称', 109 | groupName: '服务组', 110 | inputGroupName: '输入服务组', 111 | protectThreshold: '保护阀值', 112 | metadata: '元数据', 113 | ipCount: '实例数', 114 | healthyInstanceCount: '健康实例数', 115 | instance: '服务实例', 116 | editTitle: '编辑服务', 117 | addTitle: '新增服务', 118 | detailTitle: '服务详情', 119 | confirm_delete_service_action: 120 | '确认要删服务名称为:<%:=name%>,服务组为:<%:=groupName%>,的配置吗?' 121 | }, 122 | passwordpanel: { 123 | input_old_password: '输入旧密码', 124 | input_new_password: '输入新密码', 125 | old_password: '旧密码', 126 | new_password: '新密码', 127 | new_password_confirm: '新密码确认', 128 | input_new_password_confirm: '输入新密码确认', 129 | need_input_new_password: '需要输入新密码', 130 | need_input_old_password: '需要输入旧密码', 131 | you_will_need_to_enter_a_new_password_for_a_second_confirmation: 132 | '需要输入新密码二次确认', 133 | confirm_that_the_content_does_not_match_the_new_password: 134 | '确认内容与新密码不一致', 135 | reset_password: '修改密码', 136 | logout: '退出登录', 137 | logout_success: '退出登录成功', 138 | reset_password_success: '修改密码成功!', 139 | the_input_cannot_be_empty: '输入内容不能为空' 140 | }, 141 | user: { 142 | name: '用户', 143 | username: '用户名', 144 | nickname: '用户昵称', 145 | password: '密码', 146 | resetPassword: '重置密码(空则不调整)', 147 | list: '用户列表', 148 | gmtCreate: '创建时间', 149 | gmtModified: '更新时间', 150 | roles: '角色', 151 | enable: '是否启用', 152 | confirm_delete_user_action: '确认要删服务名称为:<%:=username%> 的用户吗?' 153 | }, 154 | role: { 155 | admin: '管理员', 156 | developer: '开发者', 157 | guest: '访客' 158 | }, 159 | monitor: { 160 | system_monitor: '系统监控', 161 | service_node: '服务节点', 162 | interval_type: '视图间隔类型', 163 | auto_refresh: '自动刷新', 164 | app_cpu_usage: 'CPU使用率', 165 | app_cpu_usage_percent: 'CPU使用率-单核占比(%)', 166 | app_memory_usage: '内存使用率', 167 | app_memory_usage_percent: '内存使用率(%)', 168 | app_rss_memory: 'RSS 内存', 169 | app_rss_memory_m: 'RSS 内存(M)', 170 | http_request_rps: 'http请求rps', 171 | http_request_count: 'http请求数量', 172 | http_request_rt: 'http请求平均处理时长', 173 | http_request_rt_ms: 'http请求平均处理时长(ms)', 174 | grpc_request_rps: 'grpc请求rps', 175 | grpc_request_count: 'grpc请求数量', 176 | grpc_request_rt: 'grpc请求平均处理时长', 177 | grpc_request_rt_ms: 'grpc请求平均处理时长(ms)', 178 | config_data_size: '配置数量', 179 | config_data_size_n: '配置数量(个)', 180 | config_listener_client_size: 'http监听配置链接数量', 181 | config_listener_client_size_n: 'http监听配置链接数量(个)', 182 | config_subscriber_client_size: 'grpc监听配置链接数量', 183 | config_subscriber_client_size_n: 'grpc监听配置链接数量(个)', 184 | naming_service_size: '服务数量', 185 | naming_service_size_n: '服务数量(个)', 186 | naming_instance_size: '服务实例数量', 187 | naming_instance_size_n: '服务实例数量(个)', 188 | naming_subscriber_client_size: 'grpc监听服务链接数量', 189 | naming_subscriber_client_size_n: 'grpc监听服务链接数量(个)', 190 | http_request_handle_rt_summary: 'http请求处理时长统计', 191 | http_request_handle_rt_summary_percent_ms: 'http请求处理时长百分位统计(ms)', 192 | grpc_request_handle_rt_summary: 'grpc请求处理时长统计', 193 | grpc_request_handle_rt_summary_percent_ms: 'grpc请求处理时长百分位统计(ms)', 194 | LEAST: '最小间隔', 195 | MINUTE: '分钟', 196 | HOUR: '小时', 197 | DIRECT_NODE: '直连节点' 198 | }, 199 | login: { 200 | password: '密码', 201 | captcha: '验证码', 202 | login: '登录', 203 | need_username: '需要输入用户名', 204 | need_password: '需要输入密码', 205 | need_captcha: '需要输入验证码', 206 | get_captcha_fail: '获取验证码失败', 207 | USER_CHECK_ERROR: '登录失败,用户名或密码错误!', 208 | CAPTCHA_CHECK_ERROR: '验证码校验不通过!', 209 | LOGIN_LIMITE_ERROR: '登录校验太频繁,稍后再试!', 210 | LOGIN_UNKNOWN_ERROR: '登录失败,未知错误' 211 | }, 212 | about: { 213 | intro_title: '系统简介', 214 | intro_p01: 215 | 'r-nacos是一个用rust实现的nacos服务。相较于java nacos来说,是一个提供相同功能,启动更快、占用系统资源更小(初始内存小于10M)、性能更高、运行更稳定的服务。', 216 | intro_p02: 217 | 'r-nacos设计上完全兼容最新版本nacos面向client sdk 的协议(包含1.x的http OpenApi,和2.x的grpc协议),支持使用nacos服务的应用平迁到 r-nacos。', 218 | intro_p03: '使用过程有什么问题可以到 github提issue。', 219 | version_title: '系统版本号', 220 | user_title: '当前用户' 221 | }, 222 | transfer: { 223 | export_title: '导出', 224 | export_button: '导出数据', 225 | export_p01: '从r-nacos导出配置、命名空间、用户数据到文件。', 226 | import_title: '导入', 227 | import_button: '导入数据', 228 | import_p01: '将迁移文件中的数据导入到r-nacos系统。', 229 | data_manage_title: '数据管理', 230 | data_manage_p01: '从r-nacos导出数据文件支持与sqlite相互转化。', 231 | data_manage_p02: 232 | '可以使用命令 `rnacos data-to-sqlite export.data sqlite.db` 把导出的中间数据转化成sqlite数据库文件,方便对数据做进一步处理。', 233 | data_manage_p03: 234 | '可以使用命令 `rnacos sqlite-to-data sqlite.db export.data` 把处理后的sqlite数据转化成迁移格式数据文件,之后即可再把数据导入到r-nacos系统。', 235 | from_nacos_title: '从nacos迁移数据', 236 | from_nacos_p01: 237 | '为了方便用户从nacos迁移,v0.6.3后r-nacos支持把nacos数据导出到r-nacos迁移格式数据文件。', 238 | from_nacos_p02: 239 | '使用命令 `rnacos openapi-to-data -u nacos -p nacos 127.0.0.1:8848 export.data` 通过openapi把nacos配置数据转化成迁移格式数据文件,之后即可在本页面把数据导入到r-nacos系统。\n(把127.0.0.1:8848信息换成实际nacos地址;如果nacos没有开启鉴权,则用户与密码参数可以不设置。)', 240 | from_nacos_p03: 241 | '使用命令 `rnacos mysql-to-data mysql://$user:$password@127.0.0.1:3306/nacos export.data` 把nacos mysql数据转化成迁移格式数据文件,之后即可在本页面把数据导入到r-nacos系统。' 242 | }, 243 | menu: { 244 | config_management: '配置管理', 245 | config_list: '配置列表', 246 | config_history: '配置历史记录', 247 | service_management: '服务管理', 248 | service_list: '服务列表', 249 | subscriber_list: '订阅者列表', 250 | service_instance_list: '服务实例列表', 251 | system_management: '系统管理', 252 | user_management: '用户管理', 253 | namespace: '命名空间', 254 | data_transfer: '数据迁移', 255 | cluster_info: '集群信息', 256 | system_monitor: '系统监控', 257 | about: '关于' 258 | }, 259 | error: { 260 | NO_PERMISSION: '没有权限', 261 | NO_NAMESPACE_PERMISSION: '没有命名空间权限', 262 | SYSTEM_ERROR: '系统异常' 263 | } 264 | }; 265 | export default { 266 | ...message, 267 | ...zhCN 268 | }; 269 | -------------------------------------------------------------------------------- /src/layout/components/Header/index.ts: -------------------------------------------------------------------------------- 1 | import PageHeader from './index.vue'; 2 | 3 | export { PageHeader }; 4 | -------------------------------------------------------------------------------- /src/layout/components/Header/index.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 52 | -------------------------------------------------------------------------------- /src/layout/components/Logo/index.ts: -------------------------------------------------------------------------------- 1 | import Logo from './index.vue'; 2 | 3 | export { Logo }; 4 | -------------------------------------------------------------------------------- /src/layout/components/Logo/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 17 | -------------------------------------------------------------------------------- /src/layout/components/Menu/index.ts: -------------------------------------------------------------------------------- 1 | import AsideMenu from './index.vue'; 2 | 3 | export { AsideMenu }; 4 | -------------------------------------------------------------------------------- /src/layout/components/Menu/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 122 | -------------------------------------------------------------------------------- /src/layout/index.vue: -------------------------------------------------------------------------------- 1 | 58 | 59 | 124 | 125 | 136 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import { createPinia } from 'pinia'; 3 | import './style.css'; 4 | import App from './App.vue'; 5 | import i18n from './i18n'; 6 | import router from '@/route/router.js'; 7 | 8 | const app = createApp(App); 9 | const pinia = createPinia(); 10 | app.use(i18n); 11 | app.use(router); 12 | app.use(pinia); 13 | app.mount('#app'); 14 | -------------------------------------------------------------------------------- /src/pages/About.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 53 | -------------------------------------------------------------------------------- /src/pages/ChartDemo.vue: -------------------------------------------------------------------------------- 1 | 15 | 134 | 135 | 176 | -------------------------------------------------------------------------------- /src/pages/ClusterPage.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 59 | -------------------------------------------------------------------------------- /src/pages/Config.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent } from 'vue'; 2 | 3 | import { 4 | configApi, 5 | IConfig, 6 | IConfigKey, 7 | IConfigQueryParam 8 | } from '@/api/config'; 9 | import { AxiosError } from 'axios'; 10 | import { useI18n } from 'vue-i18n'; 11 | import { handleApiResult, printApiError } from '@/utils/request.ts'; 12 | import * as constant from '@/types/constant.ts'; 13 | export default defineComponent({ 14 | name: 'Config', 15 | data() { 16 | return { 17 | tenant: '', 18 | group: 'foo', 19 | dataId: '001', 20 | content: '', 21 | message: '' 22 | }; 23 | }, 24 | methods: { 25 | setConfig() { 26 | //console.log('setConfig', this.group, this.dataId); 27 | let config: IConfig = { 28 | tenant: this.tenant, 29 | group: this.group, 30 | dataId: this.dataId, 31 | content: this.content 32 | }; 33 | configApi 34 | .setConfigV2(config) 35 | .then(handleApiResult) 36 | .then(() => { 37 | this.content = ''; 38 | }) 39 | .catch(printApiError); 40 | }, 41 | getConfig() { 42 | //console.log('getConfig', this.group, this.dataId); 43 | let configKey: IConfigKey = { 44 | tenant: this.tenant, 45 | group: this.group, 46 | dataId: this.dataId 47 | }; 48 | configApi 49 | .getConfigV2(configKey) 50 | .then(handleApiResult) 51 | .then((data) => { 52 | if (data != null) { 53 | this.content = data.value || ''; 54 | } 55 | }) 56 | .catch(printApiError); 57 | } 58 | }, 59 | render() { 60 | const { t } = useI18n(); 61 | return ( 62 |
63 |
64 |
65 | tenant: 66 | { 70 | //console.log('tenant change'); 71 | this.tenant = (e.target as HTMLInputElement).value; 72 | }} 73 | /> 74 |
75 |
76 | group: 77 | { 81 | this.group = (e.target as HTMLInputElement).value; 82 | }} 83 | /> 84 |
85 |
86 | dataId: 87 | { 91 | this.dataId = (e.target as HTMLInputElement).value; 92 | }} 93 | /> 94 |
95 |
96 | content: 97 | { 101 | this.content = (e.target as HTMLInputElement).value; 102 | }} 103 | /> 104 |
105 |
106 | 107 | 108 |
109 |
110 |
111 | ); 112 | } 113 | }); 114 | -------------------------------------------------------------------------------- /src/pages/ConfigDetail.vue: -------------------------------------------------------------------------------- 1 | 112 | 113 | 307 | 308 | 320 | -------------------------------------------------------------------------------- /src/pages/DiffDemo.vue: -------------------------------------------------------------------------------- 1 |