├── .eslintignore ├── .eslintrc ├── .gitignore ├── .prettierrc ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── config-overrides.js ├── cypress.json ├── docs └── images │ └── dashboard-ui.png ├── nginx.config ├── package-lock.json ├── package.json ├── public ├── favicon.png ├── index.html ├── logo.png ├── manifest.json └── robots.txt ├── src ├── components │ ├── ClusterFeatureModal.tsx │ ├── CodeMirrorEditor.tsx │ ├── Layout.css │ ├── Layout.tsx │ └── YamlModal.tsx ├── configs │ └── menu.ts ├── index.css ├── index.tsx ├── languages │ ├── i18n.ts │ ├── locales │ │ ├── en-US │ │ │ └── en-US.json │ │ └── zh-CN │ │ │ └── zh-CN.json │ └── supportedLocales.ts ├── logo.png ├── models │ └── menu.ts ├── pages │ ├── app │ │ ├── App.css │ │ └── App.tsx │ ├── backup │ │ ├── Backup.tsx │ │ └── index.ts │ ├── cluster │ │ ├── Add.tsx │ │ ├── Cluster.tsx │ │ ├── Create.tsx │ │ ├── Update.tsx │ │ └── index.ts │ ├── login │ │ ├── Login.tsx │ │ ├── ResetPassword.tsx │ │ └── index.ts │ ├── monitor │ │ ├── Monitor.tsx │ │ └── index.ts │ └── visualization │ │ ├── Visualization.tsx │ │ └── index.ts ├── react-app-env.d.ts ├── reportWebVitals.ts ├── setupProxy.js ├── setupTests.ts └── utils │ ├── common.ts │ ├── cookies.ts │ ├── fromHelper.ts │ └── http.ts ├── tsconfig.json └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | **.log 3 | **.lock 4 | tsconfig.json 5 | build/* -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "extends": ["plugin:@typescript-eslint/recommended", "react-app"], 4 | "plugins": ["@typescript-eslint", "react"], 5 | "rules": { 6 | "no-restricted-globals": 1, 7 | "@typescript-eslint/ban-types": 1, 8 | "prefer-const": 1, 9 | "@typescript-eslint/no-var-requires": 1, 10 | "@typescript-eslint/no-inferrable-types": 1, 11 | "@typescript-eslint/no-empty-function": 1, 12 | "@typescript-eslint/no-empty-interface": 1, 13 | "@typescript-eslint/no-this-alias": 1, 14 | "@typescript-eslint/no-explicit-any": 0, 15 | "prefer-spread": 1, 16 | "prefer-rest-params": 1, 17 | "@typescript-eslint/triple-slash-reference": 1 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_STORE 2 | node_modules 3 | scripts/flow/*/.flowconfig 4 | .flowconfig 5 | *~ 6 | *.pyc 7 | .grunt 8 | _SpecRunner.html 9 | __benchmarks__ 10 | build/ 11 | remote-repo/ 12 | coverage/ 13 | .module-cache 14 | fixtures/dom/public/react-dom.js 15 | fixtures/dom/public/react.js 16 | test/the-files-to-test.generated.js 17 | *.log* 18 | chrome-user-data 19 | *.sublime-project 20 | *.sublime-workspace 21 | .idea 22 | *.iml 23 | .vscode 24 | *.swp 25 | *.swo 26 | 27 | packages/react-devtools-core/dist 28 | packages/react-devtools-extensions/chrome/build 29 | packages/react-devtools-extensions/chrome/*.crx 30 | packages/react-devtools-extensions/chrome/*.pem 31 | packages/react-devtools-extensions/firefox/build 32 | packages/react-devtools-extensions/firefox/*.xpi 33 | packages/react-devtools-extensions/firefox/*.pem 34 | packages/react-devtools-extensions/shared/build 35 | packages/react-devtools-extensions/.tempUserDataDir 36 | packages/react-devtools-inline/dist 37 | packages/react-devtools-shell/dist 38 | packages/react-devtools-timeline/dist -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "printWidth": 80, 5 | "overrides": [ 6 | { 7 | "files": ".prettierrc", 8 | "options": { "parser": "json" } 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ## [0.5.0](https://git.woa.com/etcd/kstone-dashboard/compare/v0.4.0...v0.5.0) (2021-12-08) 6 | 7 | ## [0.4.0](https://git.woa.com/etcd/kstone-dashboard/compare/v0.3.0...v0.4.0) (2021-12-08) 8 | 9 | ## [0.3.0](https://git.woa.com/etcd/kstone-dashboard/compare/v0.2.3...v0.3.0) (2021-12-08) 10 | 11 | ### [0.2.3](https://git.woa.com/etcd/kstone-dashboard/compare/v0.2.2...v0.2.3) (2021-12-08) 12 | 13 | ### [0.2.2](https://git.woa.com/etcd/kstone-dashboard/compare/v0.2.1...v0.2.2) (2021-12-08) 14 | 15 | ### [0.2.1](https://git.woa.com/etcd/kstone-dashboard/compare/v0.2.0...v0.2.1) (2021-12-08) 16 | 17 | ## [0.2.0](https://git.woa.com/etcd/kstone-dashboard/compare/v0.1.6...v0.2.0) (2021-12-08) 18 | 19 | ### [0.1.6](https://git.woa.com/etcd/kstone-dashboard/compare/v0.1.5...v0.1.6) (2021-12-08) 20 | 21 | ### [0.1.5](https://git.woa.com/etcd/kstone-dashboard/compare/v0.1.4...v0.1.5) (2021-12-08) 22 | 23 | ### [0.1.4](https://git.woa.com/etcd/kstone-dashboard/compare/v0.1.3...v0.1.4) (2021-12-08) 24 | 25 | ### 0.1.3 (2021-12-08) 26 | 27 | 28 | ### Bug Fixes 29 | 30 | * 监控和巡检 ([1d2c59a](https://git.woa.com/etcd/kstone-dashboard/commit/1d2c59a8f819cf76904684e8d687c53e17727d0c)) 31 | * add eslint && lint code ([e2061cd](https://git.woa.com/etcd/kstone-dashboard/commit/e2061cdb3e5e048c55d6042fe243ccc1c6add33c)) 32 | * format & add comment ([4c983f0](https://git.woa.com/etcd/kstone-dashboard/commit/4c983f0661e171e6858c765a68541e78d3883c03)) 33 | * update logo ([686513a](https://git.woa.com/etcd/kstone-dashboard/commit/686513afc64f4270165524515677240adaaad6f5)) 34 | * use new log ([5219263](https://git.woa.com/etcd/kstone-dashboard/commit/5219263bc4db9bc06b92f2692c1628ebbe35f4b0)) 35 | 36 | ### [0.1.2](https://github.com/tkestack/kstone-dashboard/kstone-dashboard/compare/v0.1.1...v0.1.2) (2021-11-25) 37 | 38 | 39 | ### Bug Fixes 40 | 41 | * lint ([ddae85b](https://github.com/tkestack/kstone-dashboard/kstone-dashboard/commit/ddae85b605d7a51593bbafd52f974b8e9877f21b)) 42 | 43 | ### 0.1.1 (2021-11-25) 44 | 45 | 46 | ### Bug Fixes 47 | 48 | * add eslint && lint code ([e2061cd](https://github.com/tkestack/kstone-dashboard/kstone-dashboard/commit/e2061cdb3e5e048c55d6042fe243ccc1c6add33c)) 49 | * add standard-version ([f8c8b44](https://github.com/tkestack/kstone-dashboard/kstone-dashboard/commit/f8c8b448c5e50abb572c2d5bf384c1eec7a4321a)) 50 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16.13.0-alpine as build-deps 2 | WORKDIR /usr/src/app 3 | COPY package.json yarn.lock ./ 4 | RUN yarn 5 | COPY . ./ 6 | RUN export NODE_OPTIONS="--max-old-space-size=8192"; yarn build 7 | 8 | FROM nginx:1.21.3-alpine 9 | ADD ./nginx.config /etc/nginx/conf.d/default.conf 10 | COPY --from=build-deps /usr/src/app/build /usr/share/nginx/html 11 | EXPOSE 80 12 | CMD ["nginx", "-g", "daemon off;"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PROJECT := "kstone-dashboard" 2 | DOCKERDIR := "." 3 | RegistryNamespace := "tkestack" 4 | IMAGE := ${RegistryNamespace}/${PROJECT} 5 | 6 | TAG := $(shell git describe --dirty --always --tags) 7 | IMAGEID := ${IMAGE}:${TAG} 8 | 9 | .PHONY: build 10 | build: 11 | @docker build --network=host --no-cache -t ${IMAGEID} -f ${DOCKERDIR}/Dockerfile ${DOCKERDIR} 12 | 13 | .PHONY: push 14 | push: build 15 | @docker push ${IMAGEID} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kstone Dashboard 2 | 3 | Kstone Dashboard is a web-based UI for managing Kstone. It can help users to manage etcd based kstone and enjoy monitoring, inspection, visualization, backup and other features. 4 | 5 | ![Dashboard UI cluster page](docs/images/dashboard-ui.png) 6 | 7 | ## Getting Started 8 | 9 | ### Install 10 | 11 | Please read [detailed install document](https://github.com/tkestack/kstone/blob/master/charts/install.md), 12 | You can quickly install kstone through HELM. 13 | 14 | ### Develope 15 | 16 | ``` shell 17 | yarn 18 | yarn start 19 | ``` 20 | 21 | ### Build 22 | 23 | ``` shell 24 | make build 25 | ``` 26 | 27 | ### Push Image 28 | 29 | ``` shell 30 | make push 31 | ``` 32 | 33 | ## Community 34 | 35 | * You are encouraged to communicate most things via GitHub [issues](https://github.com/tkestack/kstone-dashboard/issues/new/choose) or [pull requests](https://github.com/tkestack/kstone-dashboard/pulls). 36 | 37 | ## Licensing 38 | 39 | Kstone is licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) for the full license text. -------------------------------------------------------------------------------- /config-overrides.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making TKEStack 3 | * available. 4 | * 5 | * Copyright (C) 2012-2023 Tencent. All Rights Reserved. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 8 | * this file except in compliance with the License. You may obtain a copy of the 9 | * License at 10 | * 11 | * https://opensource.org/licenses/Apache-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15 | * WARRANTIES OF ANY KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations under the License. 17 | */ 18 | 19 | const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); 20 | 21 | module.exports = function override(config, env) { 22 | config.plugins.push(new MonacoWebpackPlugin({ 23 | languages: ['json', 'javascript', 'yaml'] 24 | })); 25 | return config; 26 | } -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /docs/images/dashboard-ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kstone-io/kstone-dashboard/73eb1bdb253de71c4f96fca907523c6f75a44f52/docs/images/dashboard-ui.png -------------------------------------------------------------------------------- /nginx.config: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name _; 4 | 5 | root /usr/share/nginx/html; 6 | index index.html; 7 | 8 | location / { 9 | try_files $uri /index.html; 10 | } 11 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kstone-dashboard", 3 | "version": "0.0.1", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.4", 7 | "@testing-library/react": "^11.1.0", 8 | "@testing-library/user-event": "^12.1.10", 9 | "@types/jest": "^26.0.15", 10 | "@types/js-cookie": "^3.0.1", 11 | "@types/js-yaml": "^4.0.4", 12 | "@types/lodash": "^4.14.176", 13 | "@types/node": "^12.0.0", 14 | "@types/react": "^17.0.0", 15 | "@types/react-dom": "^17.0.0", 16 | "@types/react-router-dom": "^5.3.2", 17 | "@types/validator": "^13.6.6", 18 | "antd": "^4.17.0-alpha.9", 19 | "axios": "^0.24.0", 20 | "codemirror": "^5.63.3", 21 | "final-form": "^4.20.4", 22 | "history": "5", 23 | "http-proxy-middleware": "^2.0.1", 24 | "i18next": "^21.6.3", 25 | "i18next-browser-languagedetector": "^6.1.2", 26 | "js-base64": "^3.7.2", 27 | "js-cookie": "^3.0.1", 28 | "js-yaml": "^4.1.0", 29 | "lodash": "^4.17.21", 30 | "monaco-editor": "^0.29.1", 31 | "monaco-editor-webpack-plugin": "^5.0.0", 32 | "prop-types": "^15.7.2", 33 | "react": "^17.0.2", 34 | "react-codemirror2": "^7.2.1", 35 | "react-dom": "^17.0.2", 36 | "react-final-form-hooks": "^2.0.2", 37 | "react-i18next": "^11.15.1", 38 | "react-monaco-editor": "^0.46.0", 39 | "react-router-dom": "6", 40 | "react-scripts": "4.0.3", 41 | "standard-version": "^9.3.2", 42 | "typescript": "^4.1.2", 43 | "validator": "^13.7.0", 44 | "web-vitals": "^1.0.1" 45 | }, 46 | "scripts": { 47 | "start": "react-app-rewired start", 48 | "build": "react-app-rewired build", 49 | "test": "react-app-rewired test", 50 | "eject": "react-app-rewired eject", 51 | "release": "standard-version" 52 | }, 53 | "eslintConfig": { 54 | "extends": [ 55 | "react-app", 56 | "react-app/jest" 57 | ] 58 | }, 59 | "browserslist": { 60 | "production": [ 61 | ">0.2%", 62 | "not dead", 63 | "not op_mini all" 64 | ], 65 | "development": [ 66 | "last 1 chrome version", 67 | "last 1 firefox version", 68 | "last 1 safari version" 69 | ] 70 | }, 71 | "devDependencies": { 72 | "@types/react-helmet": "^6.1.5", 73 | "@typescript-eslint/eslint-plugin": "^5.4.0", 74 | "@typescript-eslint/parser": "^5.4.0", 75 | "babel-eslint": "^10.1.0", 76 | "cypress": "^9.0.0", 77 | "eslint": "^7.11.0", 78 | "eslint-config-react-app": "^6.0.0", 79 | "eslint-loader": "^4.0.2", 80 | "eslint-plugin-flowtype": "^8.0.3", 81 | "eslint-plugin-import": "^2.25.3", 82 | "eslint-plugin-jsx-a11y": "^6.5.1", 83 | "eslint-plugin-react": "^7.27.1", 84 | "eslint-plugin-react-hooks": "^4.3.0", 85 | "lint-staged": "^12.1.2", 86 | "react-app-rewired": "^2.1.8" 87 | }, 88 | "license": "Apache-2.0" 89 | } 90 | -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kstone-io/kstone-dashboard/73eb1bdb253de71c4f96fca907523c6f75a44f52/public/favicon.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 30 | 31 | 35 | 36 | 45 | Kstone 46 | 47 | 48 | 49 |
50 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kstone-io/kstone-dashboard/73eb1bdb253de71c4f96fca907523c6f75a44f52/public/logo.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "kstone", 3 | "name": "kstone", 4 | "icons": [ 5 | { 6 | "src": "favicon.png", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/components/ClusterFeatureModal.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making TKEStack 3 | * available. 4 | * 5 | * Copyright (C) 2012-2023 Tencent. All Rights Reserved. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 8 | * this file except in compliance with the License. You may obtain a copy of the 9 | * License at 10 | * 11 | * https://opensource.org/licenses/Apache-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15 | * WARRANTIES OF ANY KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations under the License. 17 | */ 18 | 19 | import { Modal, Form, Card, Input, Switch, Spin, InputNumber, Row, Col } from 'antd'; 20 | import { useEffect, useState } from 'react'; 21 | import * as _ from 'lodash'; 22 | import http from 'src/utils/http'; 23 | import { encode, decode } from 'js-base64'; 24 | import { useTranslation } from 'react-i18next'; 25 | import { GenerateOwnerReferences } from 'src/utils/common'; 26 | 27 | // form style 28 | const formItemLayout = { 29 | labelCol: { span: 10 }, 30 | wrapperCol: { span: 12 }, 31 | }; 32 | 33 | const FeatureGatesKey = 'featureGates'; 34 | // page to edit feature gates 35 | export const ClusterFeatureModal = ({ 36 | visible, 37 | data, 38 | close, 39 | }: { 40 | visible: any; 41 | data: any; 42 | close: any; 43 | }): JSX.Element => { 44 | const [isLoading, setIsLoading] = useState(false); 45 | const { t } = useTranslation(); 46 | const [featureMap, setFeatureMap] = useState({} as any); 47 | 48 | const [form] = Form.useForm(); 49 | 50 | const generateFeatureAnnotation = (): string => { 51 | let result = ''; 52 | Object.keys(featureMap).sort().map((feature: string) => { 53 | result += `${feature}=${featureMap[feature].toString()},`; 54 | return feature; 55 | }); 56 | return result.substring(0, result.lastIndexOf(',')); 57 | }; 58 | 59 | // load info 60 | useEffect(() => { 61 | setIsLoading(true); 62 | http.get('/apis/features').then(featureResp => { 63 | (async () => { 64 | if (data?.metadata?.labels) { 65 | const map: any = {}; 66 | featureResp.data.map((item: string) => { 67 | map[item] = (data.metadata.labels[item] === 'true'); 68 | return item; 69 | }); 70 | setFeatureMap(map); 71 | 72 | if (data.metadata.labels.backup === 'true') { 73 | const backupValue = JSON.parse( 74 | data?.metadata?.annotations?.backup ?? '{}', 75 | ); 76 | const res = await http.get(`/apis/secrets/cos-${data.metadata.name}`); 77 | form.setFieldsValue({ 78 | backupIntervalInSecond: backupValue?.backupPolicy?.backupIntervalInSecond, 79 | maxBackups: backupValue?.backupPolicy?.maxBackups, 80 | timeoutInSecond: backupValue?.backupPolicy?.timeoutInSecond, 81 | secretId: decode(res?.data?.data['secret-id'] ?? ''), 82 | secretKey: decode(res?.data?.data['secret-key'] ?? ''), 83 | path: backupValue?.cos?.path 84 | }); 85 | } 86 | setIsLoading(false); 87 | } 88 | })(); 89 | }); 90 | }, [data, form]); 91 | // handle finish 92 | const onFinish = (values: any) => { 93 | const backupInfo = { 94 | backupIntervalInSecond: values.backupIntervalInSecond, 95 | maxBackups: values.maxBackups, 96 | timeoutInSecond: values.timeoutInSecond, 97 | secretId: values.secretId, 98 | secretKey: values.secretKey, 99 | path: values.path, 100 | }; 101 | 102 | updateCluster(backupInfo); 103 | createSecret(backupInfo); 104 | close(); 105 | }; 106 | // update feature gates setting 107 | const updateCluster = async (values?: any) => { 108 | const model = _.cloneDeep(data); 109 | if (values && featureMap['backup']) { 110 | const backup = ` 111 | { 112 | "backupPolicy": { 113 | "backupIntervalInSecond": ${values.backupIntervalInSecond}, 114 | "maxBackups": ${values.maxBackups}, 115 | "timeoutInSecond": ${values.timeoutInSecond} 116 | }, 117 | "cos": { 118 | "cosSecret": "cos-${data.metadata.name}", 119 | "path": "${values.path}" 120 | }, 121 | "storageType": "COS" 122 | } 123 | `; 124 | model.metadata.annotations.backup = backup; 125 | } 126 | const gates = generateFeatureAnnotation(); 127 | model.metadata.annotations[FeatureGatesKey] = gates; 128 | await http.put(`/apis/etcdclusters/${data.metadata.name}`, model); 129 | }; 130 | // create secret for cos 131 | const createSecret = async (values: any) => { 132 | if (!featureMap['backup']) { 133 | return; 134 | } 135 | const model = { 136 | apiVersion: 'v1', 137 | data: { 138 | 'secret-id': encode(values.secretId), 139 | 'secret-key': encode(values.secretKey), 140 | }, 141 | kind: 'Secret', 142 | metadata: { 143 | name: `cos-${data.metadata.name}`, 144 | ownerReferences: GenerateOwnerReferences(data.metadata.name, data.metadata.uid), 145 | }, 146 | type: 'Opaque', 147 | }; 148 | http 149 | .get(`/apis/secrets/cos-${data.metadata.name}`) 150 | .then((resp) => { 151 | if (resp.data.code === 404) { 152 | http.post('/apis/secrets', model); 153 | } else { 154 | http.put(`/apis/secrets/cos-${data.metadata.name}`, model); 155 | } 156 | }) 157 | .catch(() => { 158 | http.post('/apis/secrets', model); 159 | }); 160 | }; 161 | 162 | return ( 163 | 169 |
174 | 175 | 176 | 177 | 178 | { 179 | Object.keys(featureMap).sort().map((feature: string) => { 180 | console.log(feature); 181 | return 182 | 183 | { 187 | featureMap[feature] = value; 188 | setFeatureMap({ 189 | ...featureMap 190 | }); 191 | }} 192 | /> 193 | 194 | ; 195 | }) 196 | } 197 | 198 | 199 | 200 | {featureMap['backup'] ? ( 201 | 205 | {/*
*/} 206 | 207 | 210 | 211 | 212 | 215 | 216 | 217 | 220 | 221 | 232 | 237 | 238 | 249 | 254 | 255 | 266 | 271 | 272 | 273 | ) : null} 274 | 275 |
276 |
277 | ); 278 | }; 279 | -------------------------------------------------------------------------------- /src/components/CodeMirrorEditor.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making TKEStack 3 | * available. 4 | * 5 | * Copyright (C) 2012-2023 Tencent. All Rights Reserved. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 8 | * this file except in compliance with the License. You may obtain a copy of the 9 | * License at 10 | * 11 | * https://opensource.org/licenses/Apache-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15 | * WARRANTIES OF ANY KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations under the License. 17 | */ 18 | 19 | import { insertCSS } from '../../src/utils/common'; 20 | import * as React from 'react'; 21 | import { UnControlled as CodeMirror } from 'react-codemirror2'; 22 | 23 | // 这里是对editor一些配置 24 | require('codemirror/mode/yaml/yaml'); 25 | 26 | insertCSS( 27 | 'YamlEditorPanel', 28 | ` 29 | .cm-s-monokai.CodeMirror{background:#272822;color:#f8f8f2;height:100%;}.cm-s-monokai div.CodeMirror-selected{background:#49483e}.cm-s-monokai .CodeMirror-line::selection,.cm-s-monokai .CodeMirror-line>span::selection,.cm-s-monokai .CodeMirror-line>span>span::selection{background:rgba(73,72,62,.99)}.cm-s-monokai .CodeMirror-line::-moz-selection,.cm-s-monokai .CodeMirror-line>span::-moz-selection,.cm-s-monokai .CodeMirror-line>span>span::-moz-selection{background:rgba(73,72,62,.99)}.cm-s-monokai .CodeMirror-gutters{background:#272822;border-right:0}.cm-s-monokai .CodeMirror-guttermarker{color:white}.cm-s-monokai .CodeMirror-guttermarker-subtle{color:#d0d0d0}.cm-s-monokai .CodeMirror-linenumber{color:#d0d0d0}.cm-s-monokai .CodeMirror-cursor{border-left:1px solid #f8f8f0}.cm-s-monokai span.cm-comment{color:#75715e}.cm-s-monokai span.cm-atom{color:#ae81ff}.cm-s-monokai span.cm-number{color:#ae81ff}.cm-s-monokai span.cm-property,.cm-s-monokai span.cm-attribute{color:#a6e22e}.cm-s-monokai span.cm-keyword{color:#f92672}.cm-s-monokai span.cm-builtin{color:#66d9ef}.cm-s-monokai span.cm-string{color:#e6db74}.cm-s-monokai span.cm-variable{color:#f8f8f2}.cm-s-monokai span.cm-variable-2{color:#9effff}.cm-s-monokai span.cm-variable-3,.cm-s-monokai span.cm-type{color:#66d9ef}.cm-s-monokai span.cm-def{color:#fd971f}.cm-s-monokai span.cm-bracket{color:#f8f8f2}.cm-s-monokai span.cm-tag{color:#f92672}.cm-s-monokai span.cm-header{color:#ae81ff}.cm-s-monokai span.cm-link{color:#ae81ff}.cm-s-monokai span.cm-error{background:#f92672;color:#f8f8f0}.cm-s-monokai .CodeMirror-activeline-background{background:#373831}.cm-s-monokai .CodeMirror-matchingbracket{text-decoration:underline;color:white!important}.CodeMirror{font-family:monospace;height:560px;color:black;border-radius:2px;direction:ltr}.CodeMirror-lines{padding:4px 0}.CodeMirror pre{padding:0 4px}.CodeMirror-scrollbar-filler,.CodeMirror-gutter-filler{background-color:white}.CodeMirror-gutters{border-right:1px solid #ddd;background-color:#f7f7f7;white-space:nowrap}.CodeMirror-linenumber{padding:0 3px 0 5px;min-width:20px;text-align:right;color:#999;white-space:nowrap}.CodeMirror-guttermarker{color:black}.CodeMirror-guttermarker-subtle{color:#999}.CodeMirror-cursor{border-left:1px solid black;border-right:0;width:0}.CodeMirror div.CodeMirror-secondarycursor{border-left:1px solid silver}.cm-fat-cursor .CodeMirror-cursor{width:auto;border:0!important;background:#7e7}.cm-fat-cursor div.CodeMirror-cursors{z-index:1}.cm-animate-fat-cursor{width:auto;border:0;-webkit-animation:blink 1.06s steps(1) infinite;-moz-animation:blink 1.06s steps(1) infinite;animation:blink 1.06s steps(1) infinite;background-color:#7e7}@-moz-keyframes blink{50%{background-color:transparent}}@-webkit-keyframes blink{50%{background-color:transparent}}@keyframes blink{50%{background-color:transparent}}.cm-tab{display:inline-block;text-decoration:inherit}.CodeMirror-rulers{position:absolute;left:0;right:0;top:-50px;bottom:-20px;overflow:hidden}.CodeMirror-ruler{border-left:1px solid #ccc;top:0;bottom:0;position:absolute}.cm-s-default .cm-header{color:blue}.cm-s-default .cm-quote{color:#090}.cm-negative{color:#d44}.cm-positive{color:#292}.cm-header,.cm-strong{font-weight:bold}.cm-em{font-style:italic}.cm-link{text-decoration:underline}.cm-strikethrough{text-decoration:line-through}.cm-s-default .cm-keyword{color:#708}.cm-s-default .cm-atom{color:#219}.cm-s-default .cm-number{color:#164}.cm-s-default .cm-def{color:#00f}.cm-s-default .cm-variable-2{color:#05a}.cm-s-default .cm-variable-3,.cm-s-default .cm-type{color:#085}.cm-s-default .cm-comment{color:#a50}.cm-s-default .cm-string{color:#a11}.cm-s-default .cm-string-2{color:#f50}.cm-s-default .cm-meta{color:#555}.cm-s-default .cm-qualifier{color:#555}.cm-s-default .cm-builtin{color:#30a}.cm-s-default .cm-bracket{color:#997}.cm-s-default .cm-tag{color:#170}.cm-s-default .cm-attribute{color:#00c}.cm-s-default .cm-hr{color:#999}.cm-s-default .cm-link{color:#00c}.cm-s-default .cm-error{color:#f00}.cm-invalidchar{color:#f00}.CodeMirror-composing{border-bottom:2px solid}div.CodeMirror span.CodeMirror-matchingbracket{color:#0f0}div.CodeMirror span.CodeMirror-nonmatchingbracket{color:#f22}.CodeMirror-matchingtag{background:rgba(255,150,0,.3)}.CodeMirror-activeline-background{background:#e8f2ff}.CodeMirror{position:relative;overflow:hidden;background:white}.CodeMirror-scroll{overflow:scroll!important;margin-bottom:-30px;margin-right:-30px;padding-bottom:30px;height:100%;outline:0;position:relative}.CodeMirror-sizer{position:relative;border-right:30px solid transparent}.CodeMirror-vscrollbar,.CodeMirror-hscrollbar,.CodeMirror-scrollbar-filler,.CodeMirror-gutter-filler{position:absolute;z-index:6;display:none}.CodeMirror-vscrollbar{right:0;top:0;overflow-x:hidden;overflow-y:scroll}.CodeMirror-hscrollbar{bottom:0;left:0;overflow-y:hidden;overflow-x:scroll}.CodeMirror-scrollbar-filler{right:0;bottom:0}.CodeMirror-gutter-filler{left:0;bottom:0}.CodeMirror-gutters{position:absolute;left:0;top:0;min-height:100%;z-index:3} 30 | .CodeMirror-gutter{white-space:normal;height:100%;display:inline-block;vertical-align:top;margin-bottom:-30px}.CodeMirror-gutter-wrapper{position:absolute;z-index:4;background:none!important;border:none!important}.CodeMirror-gutter-background{position:absolute;top:0;bottom:0;z-index:4}.CodeMirror-gutter-elt{position:absolute;cursor:default;z-index:4}.CodeMirror-gutter-wrapper ::selection{background-color:transparent}.CodeMirror-gutter-wrapper ::-moz-selection{background-color:transparent}.CodeMirror-lines{cursor:text;min-height:1px}.CodeMirror pre{-moz-border-radius:0;-webkit-border-radius:0;border-radius:0;border-width:0;background:transparent;font-family:inherit;font-size:inherit;margin:0;white-space:pre;word-wrap:normal;line-height:inherit;color:inherit;z-index:2;position:relative;overflow:visible;-webkit-tap-highlight-color:transparent;-webkit-font-variant-ligatures:contextual;font-variant-ligatures:contextual}.CodeMirror-wrap pre{word-wrap:break-word;white-space:pre-wrap;word-break:normal}.CodeMirror-linebackground{position:absolute;left:0;right:0;top:0;bottom:0;z-index:0}.CodeMirror-linewidget{position:relative;z-index:2;overflow:auto}.CodeMirror-rtl pre{direction:rtl}.CodeMirror-code{outline:0}.CodeMirror-scroll,.CodeMirror-sizer,.CodeMirror-gutter,.CodeMirror-gutters,.CodeMirror-linenumber{-moz-box-sizing:content-box;box-sizing:content-box}.CodeMirror-measure{position:absolute;width:100%;height:0;overflow:hidden;visibility:hidden}.CodeMirror-cursor{position:absolute;pointer-events:none}.CodeMirror-measure pre{position:static}div.CodeMirror-cursors{visibility:hidden;position:relative;z-index:3}div.CodeMirror-dragcursors{visibility:visible}.CodeMirror-focused div.CodeMirror-cursors{visibility:visible}.CodeMirror-selected{background:#d9d9d9}.CodeMirror-focused .CodeMirror-selected{background:#d7d4f0}.CodeMirror-crosshair{cursor:crosshair}.CodeMirror-line::selection,.CodeMirror-line>span::selection,.CodeMirror-line>span>span::selection{background:#d7d4f0}.CodeMirror-line::-moz-selection,.CodeMirror-line>span::-moz-selection,.CodeMirror-line>span>span::-moz-selection{background:#d7d4f0}.cm-searching{background-color:#ffa;background-color:rgba(255,255,0,.4)}.cm-force-border{padding-right:.1px}@media print{.CodeMirror div.CodeMirror-cursors{visibility:hidden}}.cm-tab-wrap-hack:after{content:''}span.CodeMirror-selectedtext{background:0} 31 | .codeMirrorHeight{height:600px;overflow:auto;} 32 | `, 33 | ); 34 | 35 | interface YamlEditorPanelProps { 36 | /** 输入的内容 */ 37 | config?: string; 38 | 39 | /** 是否只读 */ 40 | readOnly?: boolean; 41 | 42 | /** 回调函数,处理输入数据 */ 43 | handleInputForEditor?: (config: string) => void; 44 | 45 | /** 当前的mode */ 46 | mode?: string; 47 | } 48 | 49 | export class YamlEditorPanel extends React.Component { 50 | render(): JSX.Element { 51 | const { readOnly, mode = 'yaml', handleInputForEditor } = this.props; 52 | 53 | const codeOptions = { 54 | lineNumbers: true, 55 | mode, 56 | theme: 'monokai', 57 | readOnly: readOnly ? readOnly : false, 58 | lineWrapping: true, // 自动换行 59 | styleActiveLine: true, // 当前行背景高亮 60 | }; 61 | 62 | return ( 63 | { 68 | !readOnly && 69 | handleInputForEditor !== undefined && 70 | handleInputForEditor(value); 71 | }} 72 | /> 73 | ); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/components/Layout.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making TKEStack 3 | * available. 4 | * 5 | * Copyright (C) 2012-2023 Tencent. All Rights Reserved. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 8 | * this file except in compliance with the License. You may obtain a copy of the 9 | * License at 10 | * 11 | * https://opensource.org/licenses/Apache-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15 | * WARRANTIES OF ANY KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations under the License. 17 | */ 18 | 19 | .logo { 20 | float: left; 21 | margin: 0px 10px; 22 | padding: 0px 50px 0 0px; 23 | width: 100%; 24 | display: flex; 25 | justify-content: space-between; 26 | align-items: center; 27 | } 28 | 29 | .site-layout .site-layout-background { 30 | background: #fff; 31 | } -------------------------------------------------------------------------------- /src/components/Layout.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making TKEStack 3 | * available. 4 | * 5 | * Copyright (C) 2012-2023 Tencent. All Rights Reserved. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 8 | * this file except in compliance with the License. You may obtain a copy of the 9 | * License at 10 | * 11 | * https://opensource.org/licenses/Apache-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15 | * WARRANTIES OF ANY KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations under the License. 17 | */ 18 | 19 | import { useState } from 'react'; 20 | import { Outlet, Link } from 'react-router-dom'; 21 | import { 22 | ConfigProvider, 23 | Dropdown, 24 | Layout as AntdLayout, 25 | Menu, 26 | Button, 27 | } from 'antd'; 28 | import { ClusterOutlined } from '@ant-design/icons'; 29 | import logo from '../../src/logo.png'; 30 | import './Layout.css'; 31 | import DownOutlined from '@ant-design/icons/lib/icons/DownOutlined'; 32 | import { useTranslation } from 'react-i18next'; 33 | import zhCN from 'antd/lib/locale/zh_CN'; 34 | import enUS from 'antd/lib/locale/en_US'; 35 | import cookies from 'src/utils/cookies'; 36 | import { t } from 'i18next'; 37 | 38 | const { Header, Content, Sider } = AntdLayout; 39 | const { SubMenu } = Menu; 40 | 41 | const Layout = ({ menu }: { menu: any }): JSX.Element => { 42 | const [collapsed, setCollapsed] = useState(false); 43 | const { i18n } = useTranslation(); 44 | const lang = localStorage.getItem('locale'); 45 | 46 | const handleMenuClick = (e: any) => { 47 | if (e.key === '1') { 48 | i18n.changeLanguage('zh-CN'); 49 | localStorage.setItem('locale', 'zh-CN'); 50 | } 51 | if (e.key === '2') { 52 | i18n.changeLanguage('en-US'); 53 | localStorage.setItem('locale', 'en-US'); 54 | } 55 | window.location.reload(); 56 | }; 57 | 58 | const handleLogout = () => { 59 | cookies.remove("token"); 60 | window.location.href = "/login"; 61 | }; 62 | 63 | const menus = ( 64 | 65 | 中文 66 | English 67 | 68 | ); 69 | 70 | return ( 71 | 72 |
73 |
74 | logo 75 | { 76 | cookies.get('token') !== '' ? : null 79 | } 80 | 81 | 84 | 85 |
86 |
87 | 88 | 89 | setCollapsed(!collapsed)} 93 | width={250} 94 | style={{ 95 | overflow: 'auto', 96 | height: '100vh', 97 | position: 'fixed', 98 | left: 0, 99 | }} 100 | > 101 | item.key)} 104 | style={{ height: '100%' }} 105 | theme="dark" 106 | > 107 | {menu.items.map((item: any) => { 108 | if ('items' in item) { 109 | return ( 110 | } 113 | title={item.title} 114 | > 115 | {item.items.map((subItem: any) => { 116 | return ( 117 | 118 | {subItem.title} 119 | 120 | ); 121 | })} 122 | 123 | ); 124 | } else { 125 | return ( 126 | 127 | {item.title} 128 | 129 | ); 130 | } 131 | })} 132 | 133 | 134 | { 137 | if (collapsed) { 138 | return { marginLeft: '80px', minHeight: '100%' }; 139 | } else { 140 | return { marginLeft: '250px', minHeight: '100%' }; 141 | } 142 | })()} 143 | > 144 | 145 | 146 | 147 | 148 | 149 | 150 |
151 | ); 152 | }; 153 | 154 | export default Layout; 155 | -------------------------------------------------------------------------------- /src/components/YamlModal.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making TKEStack 3 | * available. 4 | * 5 | * Copyright (C) 2012-2023 Tencent. All Rights Reserved. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 8 | * this file except in compliance with the License. You may obtain a copy of the 9 | * License at 10 | * 11 | * https://opensource.org/licenses/Apache-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15 | * WARRANTIES OF ANY KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations under the License. 17 | */ 18 | 19 | import { Modal } from 'antd'; 20 | import * as yaml from 'js-yaml'; 21 | import { useTranslation } from 'react-i18next'; 22 | 23 | import { YamlEditorPanel } from './CodeMirrorEditor'; 24 | 25 | export const YamlModal = ({ 26 | visible, 27 | data, 28 | onclose, 29 | }: { 30 | visible: any; 31 | data: any; 32 | onclose: any; 33 | }): JSX.Element => { 34 | const { t } = useTranslation(); 35 | 36 | const getNodeMaster = () => { 37 | return yaml.dump(data); 38 | }; 39 | 40 | return ( 41 | 47 |
48 | 49 |
50 |
51 | ); 52 | }; 53 | -------------------------------------------------------------------------------- /src/configs/menu.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making TKEStack 3 | * available. 4 | * 5 | * Copyright (C) 2012-2023 Tencent. All Rights Reserved. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 8 | * this file except in compliance with the License. You may obtain a copy of the 9 | * License at 10 | * 11 | * https://opensource.org/licenses/Apache-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15 | * WARRANTIES OF ANY KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations under the License. 17 | */ 18 | 19 | import { AppMenu } from '../models/menu'; 20 | import i18n from 'src/languages/i18n'; 21 | 22 | /** 23 | * 定义导航菜单 24 | */ 25 | export const menu: AppMenu = { 26 | title: 'kstone', 27 | items: [ 28 | { 29 | key: 'clusterManager', 30 | title: i18n.t('ClusterManagement'), 31 | items: [ 32 | { 33 | key: 'addCluster', 34 | route: '/cluster/add', 35 | title: i18n.t('ImortCluster'), 36 | }, 37 | { 38 | key: 'cluster', 39 | route: '/cluster', 40 | title: i18n.t('ClusterManagement'), 41 | }, 42 | ], 43 | }, 44 | { 45 | title: i18n.t('OperationCenter'), 46 | key: 'operation', 47 | items: [ 48 | { 49 | key: 'visualization', 50 | route: '/visualization', 51 | title: i18n.t('VisualizationTool'), 52 | }, 53 | { 54 | key: 'monitor', 55 | route: '/monitor', 56 | title: i18n.t('MonitorAndInspection'), 57 | }, 58 | { 59 | key: 'backup', 60 | route: '/backup', 61 | title: i18n.t('BackupManagement'), 62 | }, 63 | ], 64 | }, 65 | ], 66 | }; 67 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making TKEStack 3 | * available. 4 | * 5 | * Copyright (C) 2012-2023 Tencent. All Rights Reserved. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 8 | * this file except in compliance with the License. You may obtain a copy of the 9 | * License at 10 | * 11 | * https://opensource.org/licenses/Apache-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15 | * WARRANTIES OF ANY KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations under the License. 17 | */ 18 | 19 | body { 20 | margin: 0; 21 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 22 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 23 | sans-serif; 24 | -webkit-font-smoothing: antialiased; 25 | -moz-osx-font-smoothing: grayscale; 26 | } 27 | 28 | code { 29 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 30 | monospace; 31 | } -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making TKEStack 3 | * available. 4 | * 5 | * Copyright (C) 2012-2023 Tencent. All Rights Reserved. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 8 | * this file except in compliance with the License. You may obtain a copy of the 9 | * License at 10 | * 11 | * https://opensource.org/licenses/Apache-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15 | * WARRANTIES OF ANY KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations under the License. 17 | */ 18 | 19 | import React from 'react'; 20 | import ReactDOM from 'react-dom'; 21 | import './index.css'; 22 | import App from './pages/app/App'; 23 | import reportWebVitals from './reportWebVitals'; 24 | import { BrowserRouter } from 'react-router-dom'; 25 | import { ConfigProvider } from 'antd'; 26 | import './languages/i18n'; 27 | ReactDOM.render( 28 | 29 | 30 | 31 | 32 | 33 | 34 | , 35 | document.getElementById('root'), 36 | ); 37 | // If you want to start measuring performance in your app, pass a function 38 | // to log results (for example: reportWebVitals(console.log)) 39 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 40 | reportWebVitals(); 41 | -------------------------------------------------------------------------------- /src/languages/i18n.ts: -------------------------------------------------------------------------------- 1 | import i18n from 'i18next'; 2 | import { initReactI18next } from 'react-i18next'; 3 | import LanguageDetector from 'i18next-browser-languagedetector'; 4 | import { defaultLocale, supportedLocales } from './supportedLocales'; 5 | 6 | i18n 7 | .use(LanguageDetector) 8 | .use(initReactI18next) 9 | .init({ 10 | resources: supportedLocales, 11 | fallbackLng: defaultLocale, 12 | lng: defaultLocale, 13 | interpolation: { 14 | escapeValue: false, 15 | }, 16 | }); 17 | 18 | export default i18n; 19 | -------------------------------------------------------------------------------- /src/languages/locales/en-US/en-US.json: -------------------------------------------------------------------------------- 1 | { 2 | "Cluster": "Cluster", 3 | "HasNotYetEnabledTheBackupFeature": "has not yet enabled the backup feature", 4 | "Core": "Core", 5 | "PleaseCheckTheNetworkOrContactTheAdministrator": "please check the network or contact the administrator", 6 | "BackupParameterSettings": "Backup Parameter Settings", 7 | "BackupManagement": "Backup", 8 | "Edit": "Edit", 9 | "EditCluster": "Edit Cluster", 10 | "CACertificate": "CA Certificate", 11 | "Operation": "Operation", 12 | "ViewYaml": "View Yaml", 13 | "AccessMethod": "Access Method", 14 | "StorageLevel": "Storage Level", 15 | "FeatureGates": "Feature Gates", 16 | "ImortCluster": "Import Cluster", 17 | "EnvironmentVariableName": "Environment Variable Name", 18 | "EnvironmentVariableValue": "Environment Variable Value", 19 | "EnvironmentVariableConfiguration": "Environment Variable Configuration", 20 | "HasNotYetEnabledTheMonitorFeature": "has not yet enabled the monitor feature", 21 | "Create": "Create", 22 | "DiskSize": "Disk size", 23 | "DiskType": "Disk type", 24 | "DiskConfiguration": "Disk Configuration", 25 | "CPUCores": "CPU Cores", 26 | "EtcdVersion": "Etcd Version", 27 | "EtcdEnvironmentConfigurations": "Etcd Environment Configurations?", 28 | "Address": "Address", 29 | "ClusterDescription": "Cluster Description", 30 | "ClusterFeature": "Cluster Feature", 31 | "ClusterManagement": "Cluster Management", 32 | "ClusterSize": "Cluster Size", 33 | "ClusterMonitor": "Cluster Monitor", 34 | "ClusterNodeMapping": "Cluster Node Mapping", 35 | "ClusterType": "Cluster Type", 36 | "ClusterName": "Cluster Name", 37 | "MonitorAndInspection": "Monitor/Inspection", 38 | "Node": "Node", 39 | "NodeNumber": "Node Number", 40 | "ClientPrivateKey": "Client Private Key", 41 | "ClientCertificate": "Client Certificate", 42 | "VisualizationTool": "Visualization Tool", 43 | "ConnectionError": "Connection error", 44 | "Description": "Description", 45 | "Name": "Name", 46 | "MemorySize": "Memory Size", 47 | "PrivateAccessAddress": "Private Access Address", 48 | "Configurations": "Configurations", 49 | "HardDrive": "Hard Drive", 50 | "TheServerIsAbnormal": "The Server is Abnormal", 51 | "HTTPVersionNotSupported": "HTTP Version Not Supported", 52 | "InternalServerError": "Internal Server Error", 53 | "ServiceUnavailable": "Service Unavailable", 54 | "NotImplemented": "Not Implemented", 55 | "TheRequestTimedOutOrTheServerWasAbnormal": "The request timed out or the server was abnormal", 56 | "403Forbidden": "403 Forbidden", 57 | "408RequestTimeout": "408 Request Timeout", 58 | "404NotFound": "404 Not Found", 59 | "400BadRequest": "400 Bad Request", 60 | "401Unauthorized": "401 Unauthorized", 61 | "502BadGateway": "502 Bad Gateway", 62 | "504GatewayTimeout": "504 Gateway Timeout", 63 | "PleaseInput": "Please input", 64 | "PleaseInputTheCorrespondingName": "Please input the corresponding name", 65 | "PleaseInputTheEtcdAccessPort": "Please input the etcd access port", 66 | "PleaseInputTheCorrectEtcdAccessPort": "Please input the correct etcd access port", 67 | "PleaseInputTheAccessAddress": "Please input the access address", 68 | "PleaseSelectEtcdCluster": "Please select etcd cluster", 69 | "ConfirmDelete": "Confirm delete", 70 | "FailedToCreateSecret": "Failed to create secret", 71 | "AreYouSureToDeleteTheCluster": "Are you sure to delete the cluster ", 72 | "Delete": "Delete", 73 | "TheInputNameDoesNotMeetTheRulesPleaseReferToTheRegularRules": "The input name does not meet the rules, please refer to the regular rules:", 74 | "SearchViewKeyValueData": "Search: View Key-Value Data", 75 | "Submit": "Submit", 76 | "PublicNetworkAddress": "Public Network Address", 77 | "FileSize": "File Size", 78 | "FileName": "File Name", 79 | "CreateCluster": "Create Cluster", 80 | "ChooseCluster": "Choose Cluster", 81 | "Exception": "Exception", 82 | "Forkubernetes": "For Kubernetes", 83 | "AuthEnable": "Auth Enable", 84 | "OperationCenter": "Operation Center", 85 | "Normal": "Normal", 86 | "Status": "Status", 87 | "StatusDescription": "Status Description", 88 | "LastUpdateTime": "Last update time", 89 | "User": "Username", 90 | "Password": "Password", 91 | "Login": "Login", 92 | "Logout": "Logout", 93 | "ResetPassword": "Reset Password", 94 | "ConfirmPassword": "Confirm Password", 95 | "ConfirmPasswordMessage": "The two passwords that you entered do not match!", 96 | "PasswordMessage": "Please input your password!", 97 | "ConfirmMessage": "Please confirm your password!", 98 | "PasswordStrengthMessage": "Password strength requires at least 8 characters in length and contains at least numbers, lowercase letters, uppercase letters, and special characters.", 99 | "SignIn": "Sign in", 100 | "SignOut": "Sign out", 101 | "UserMessage": "Please input your username!", 102 | "Memory": "Memory", 103 | "StorageBackend": "Storage Backend" 104 | } 105 | -------------------------------------------------------------------------------- /src/languages/locales/zh-CN/zh-CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "Cluster": "集群", 3 | "HasNotYetEnabledTheBackupFeature": "暂未开启备份功能", 4 | "Core": "核", 5 | "PleaseCheckTheNetworkOrContactTheAdministrator": "请检查网络或联系管理员", 6 | "BackupParameterSettings": "Backup参数设置", 7 | "BackupManagement": "备份管理", 8 | "Edit": "编辑", 9 | "EditCluster": "编辑集群", 10 | "CACertificate": "CA证书", 11 | "Operation": "操作", 12 | "ViewYaml": "查看YAML", 13 | "AccessMethod": "访问方式", 14 | "StorageLevel": "存储级别", 15 | "FeatureGates": "功能项开关", 16 | "ImortCluster": "导入集群", 17 | "EnvironmentVariableName": "环境变量名称", 18 | "EnvironmentVariableValue": "环境变量值", 19 | "EnvironmentVariableConfiguration": "环境变量配置", 20 | "HasNotYetEnabledTheMonitorFeature": "尚未开启监控功能", 21 | "Create": "创建集群", 22 | "DiskSize": "磁盘大小", 23 | "DiskType": "磁盘类型", 24 | "DiskConfiguration": "磁盘配置", 25 | "CPUCores": "CPU核数", 26 | "EtcdVersion": "etcd版本", 27 | "EtcdEnvironmentConfigurations": "etcd环境变量配置?", 28 | "Address": "访问地址", 29 | "ClusterDescription": "集群备注", 30 | "ClusterFeature": "集群功能项", 31 | "ClusterManagement": "集群管理", 32 | "ClusterSize": "集群规模", 33 | "ClusterMonitor": "集群监控", 34 | "ClusterNodeMapping": "集群节点映射", 35 | "ClusterType": "集群类型", 36 | "ClusterName": "集群名称", 37 | "MonitorAndInspection": "监控和巡检", 38 | "Node": "节点", 39 | "NodeNumber": "节点个数", 40 | "ClientPrivateKey": "客户端私钥", 41 | "ClientCertificate": "客户端证书", 42 | "VisualizationTool": "可视化工具", 43 | "ConnectionError": "连接出错", 44 | "Description": "描述", 45 | "Name": "名称", 46 | "MemorySize": "内存大小", 47 | "PrivateAccessAddress": "内网访问地址", 48 | "Configurations": "配置", 49 | "HardDrive": "普通硬盘", 50 | "TheServerIsAbnormal": "服务器异常!", 51 | "HTTPVersionNotSupported": "HTTP版本不受支持(505)", 52 | "InternalServerError": "服务器错误(500)", 53 | "ServiceUnavailable": "服务不可用(503)", 54 | "NotImplemented": "服务未实现(501)", 55 | "TheRequestTimedOutOrTheServerWasAbnormal": "请求超时或服务器异常!", 56 | "403Forbidden": "拒绝访问(403)", 57 | "408RequestTimeout": "请求超时(408)", 58 | "404NotFound": "请求出错(404)", 59 | "400BadRequest": "请求错误(400)", 60 | "401Unauthorized": "未授权,请重新登录(401)", 61 | "502BadGateway": "网络错误(502)", 62 | "504GatewayTimeout": "网络超时(504)", 63 | "PleaseInput": "请输入", 64 | "PleaseInputTheCorrespondingName": "请输入对应名称", 65 | "PleaseInputTheEtcdAccessPort": "请输入etcd访问端口", 66 | "PleaseInputTheCorrectEtcdAccessPort": "请输入正确的etcd访问端口", 67 | "PleaseInputTheAccessAddress": "请填写访问地址", 68 | "PleaseSelectEtcdCluster": "请选择etcd集群", 69 | "ConfirmDelete": "确认删除", 70 | "FailedToCreateSecret": "Secret创建失败", 71 | "AreYouSureToDeleteTheCluster": "确定删除集群 ", 72 | "Delete": "删除", 73 | "TheInputNameDoesNotMeetTheRulesPleaseReferToTheRegularRules": "输入名称不符合规则,请参考正则: ", 74 | "SearchViewKeyValueData": "搜索:查看Key-Value数据", 75 | "Submit": "提交", 76 | "PublicNetworkAddress": "外网访问地址", 77 | "FileSize": "文件大小", 78 | "FileName": "文件名称", 79 | "CreateCluster": "新建集群", 80 | "ChooseCluster": "选择集群:", 81 | "Exception": "异常", 82 | "Forkubernetes": "用于Kubernetes", 83 | "AuthEnable": "开启鉴权", 84 | "OperationCenter": "运维中心", 85 | "Normal": "正常", 86 | "Status": "状态", 87 | "StatusDescription": "状态描述", 88 | "LastUpdateTime": "最后更新时间", 89 | "User": "用户名", 90 | "Password": "密码", 91 | "Login": "登录", 92 | "Logout": "登出", 93 | "ResetPassword": "重置密码", 94 | "ConfirmPassword": "确认密码", 95 | "ConfirmPasswordMessage": "两次输入的密码不匹配!", 96 | "PasswordMessage": "请输入密码!", 97 | "ConfirmMessage": "请确认密码!", 98 | "PasswordStrengthMessage": "密码强度要求长度至少8位,至少包含数字、小写字母、大写字母和特殊字符。", 99 | "SignIn": "登入", 100 | "SignOut": "登出", 101 | "UserMessage": "请输入你的用户名!", 102 | "Memory": "内存", 103 | "StorageBackend": "存储版本" 104 | } 105 | -------------------------------------------------------------------------------- /src/languages/supportedLocales.ts: -------------------------------------------------------------------------------- 1 | import en_US from './locales/en-US/en-US.json'; 2 | import zh_CN from './locales/zh-CN/zh-CN.json'; 3 | if (!localStorage.getItem('locale')) { 4 | localStorage.setItem('locale', 'en-US'); 5 | } 6 | const defaultLocale = localStorage.getItem('locale') as string; 7 | const supportedLocales = { 8 | 'en-US': { 9 | translation: en_US, 10 | }, 11 | 'zh-CN': { 12 | translation: zh_CN, 13 | }, 14 | }; 15 | 16 | export { defaultLocale, supportedLocales }; 17 | -------------------------------------------------------------------------------- /src/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kstone-io/kstone-dashboard/73eb1bdb253de71c4f96fca907523c6f75a44f52/src/logo.png -------------------------------------------------------------------------------- /src/models/menu.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making TKEStack 3 | * available. 4 | * 5 | * Copyright (C) 2012-2023 Tencent. All Rights Reserved. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 8 | * this file except in compliance with the License. You may obtain a copy of the 9 | * License at 10 | * 11 | * https://opensource.org/licenses/Apache-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15 | * WARRANTIES OF ANY KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations under the License. 17 | */ 18 | 19 | export interface AppMenu { 20 | title: string; 21 | icon?: string; 22 | items: (AppMenuItem | AppMenuLabel | AppSubMenu)[]; 23 | } 24 | 25 | /** 26 | * 一个可路由的菜单项,至少包括 `title` 和 `route` 属性 27 | */ 28 | export interface AppMenuItem { 29 | /** 30 | * 菜单的标题 31 | */ 32 | title: string; 33 | 34 | /** 35 | * 菜单的路由 36 | */ 37 | route: string; 38 | 39 | /** 40 | * 菜单显示的图标 41 | */ 42 | icon?: string; 43 | 44 | /** 45 | * 菜单激活时显示的图标 46 | */ 47 | iconActive?: string; 48 | 49 | /** 50 | * 菜单打开的目标位置,默认为 _self 在当前页面打开,配置为 _blank 51 | */ 52 | target?: '_self' | '_blank'; 53 | 54 | /** 55 | * 是否渲染一个“外部链接”图标 56 | */ 57 | outerLinkIcon?: boolean; 58 | 59 | key?: string; 60 | } 61 | 62 | /** 63 | * 一个菜单分类标签,仅包含 `label` 属性 64 | */ 65 | export interface AppMenuLabel { 66 | label: string; 67 | } 68 | 69 | /** 70 | * 一个子菜单项,包括 `title` 和 `items` 属性 71 | */ 72 | export interface AppSubMenu { 73 | /** 74 | * 子菜单明名称 75 | */ 76 | title: string; 77 | 78 | /** 79 | * 包含的子菜单项 80 | */ 81 | items: AppMenuItem[]; 82 | 83 | /** 84 | * 子菜单图标 85 | */ 86 | icon?: string; 87 | 88 | /** 89 | * 子菜单激活图标 90 | */ 91 | iconActive?: string; 92 | } 93 | -------------------------------------------------------------------------------- /src/pages/app/App.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making TKEStack 3 | * available. 4 | * 5 | * Copyright (C) 2012-2023 Tencent. All Rights Reserved. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 8 | * this file except in compliance with the License. You may obtain a copy of the 9 | * License at 10 | * 11 | * https://opensource.org/licenses/Apache-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15 | * WARRANTIES OF ANY KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations under the License. 17 | */ 18 | 19 | @import '~antd/dist/antd.css'; 20 | /* @import '~antd/dist/antd.variable.min.css'; */ 21 | Content { 22 | padding: 24; 23 | margin: 0; 24 | } -------------------------------------------------------------------------------- /src/pages/app/App.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making TKEStack 3 | * available. 4 | * 5 | * Copyright (C) 2012-2023 Tencent. All Rights Reserved. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 8 | * this file except in compliance with the License. You may obtain a copy of the 9 | * License at 10 | * 11 | * https://opensource.org/licenses/Apache-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15 | * WARRANTIES OF ANY KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations under the License. 17 | */ 18 | 19 | import { Navigate, useRoutes } from 'react-router-dom'; 20 | import Layout from 'src/components/Layout'; 21 | import { 22 | Cluster, 23 | Add as AddCluster, 24 | Update as UpdateCluster, 25 | Create as CreateCluster, 26 | } from 'src/pages/cluster'; 27 | import { menu } from 'src/configs/menu'; 28 | import { Visualization } from 'src/pages/visualization'; 29 | import { Monitor } from 'src/pages/monitor'; 30 | import { Backup } from 'src/pages/backup'; 31 | import { Login, ResetPassword } from 'src/pages/login'; 32 | 33 | import './App.css'; 34 | 35 | function App(): JSX.Element { 36 | const mainRoutes = [ 37 | { 38 | path: '/', 39 | element: , 40 | children: [ 41 | { path: '*', element: }, 42 | { path: '/', element: }, 43 | { path: 'cluster', element: }, 44 | { path: 'cluster/add', element: }, 45 | { path: 'cluster/create', element: }, 46 | { path: 'cluster/:name', element: }, 47 | { path: 'visualization', element: }, 48 | { path: 'monitor', element: }, 49 | { path: 'backup', element: }, 50 | ], 51 | }, 52 | { path: '/reset', element: }, 53 | { path: '/login', element: } 54 | ]; 55 | 56 | const routing = useRoutes(mainRoutes); 57 | return <>{routing}; 58 | } 59 | 60 | export default App; 61 | -------------------------------------------------------------------------------- /src/pages/backup/Backup.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making TKEStack 3 | * available. 4 | * 5 | * Copyright (C) 2012-2023 Tencent. All Rights Reserved. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 8 | * this file except in compliance with the License. You may obtain a copy of the 9 | * License at 10 | * 11 | * https://opensource.org/licenses/Apache-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15 | * WARRANTIES OF ANY KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations under the License. 17 | */ 18 | 19 | import { useState, useEffect, useCallback } from 'react'; 20 | import { Layout, Typography, Select, Table, Empty } from 'antd'; 21 | import http from 'src/utils/http'; 22 | import { FormatBytes } from 'src/utils/common'; 23 | import { useTranslation } from 'react-i18next'; 24 | 25 | const { Text } = Typography; 26 | const { Header, Content } = Layout; 27 | 28 | const BackupAnnotationKey = 'backup'; 29 | 30 | export function Backup(): JSX.Element { 31 | const [clusterList, setClusterList] = useState([]); 32 | const [clusterName, setClusterName] = useState(''); 33 | const [backupInfo, setBackupInfo] = useState([] as any); 34 | const [isLoading, setIsLoading] = useState(false); 35 | const { t } = useTranslation(); 36 | 37 | const columnList = [ 38 | { 39 | key: 'Key', 40 | header: t('FileName'), 41 | width: 200, 42 | render: (item: any) => ( 43 | <> 44 |

{item.Key}

45 | 46 | ), 47 | }, 48 | { 49 | key: 'StorageClass', 50 | header: t('StorageLevel'), 51 | width: 100, 52 | render: (item: any) => ( 53 | <> 54 |

{item.StorageClass}

55 | 56 | ), 57 | }, 58 | { 59 | key: 'LastModified', 60 | header: t('LastUpdateTime'), 61 | width: 100, 62 | render: (item: any) => ( 63 | <> 64 |

{item.LastModified}

65 | 66 | ), 67 | }, 68 | { 69 | key: 'Size', 70 | header: t('FileSize'), 71 | width: 100, 72 | render: (item: any) => ( 73 | <> 74 |

{FormatBytes(item.Size, 3)}

75 | 76 | ), 77 | }, 78 | ]; 79 | 80 | const getEtcdCluster = useCallback(async () => { 81 | setIsLoading(true); 82 | http.get('/apis/etcdclusters').then((resp) => { 83 | const backupList: any = []; 84 | for (const item of resp.data.items) { 85 | if (item?.metadata?.labels?.backup === 'true') { 86 | backupList.push(item); 87 | } 88 | } 89 | if (backupList.length > 0) { 90 | setClusterList(backupList); 91 | setClusterName(backupList[0].metadata.name); 92 | updateBackupInfo(backupList[0].metadata.name, backupList); 93 | } 94 | setIsLoading(false); 95 | }); 96 | }, []); 97 | 98 | useEffect(() => { 99 | getEtcdCluster(); 100 | }, [getEtcdCluster]); 101 | 102 | const selectCluster = (clusterName: any) => { 103 | setClusterName(clusterName); 104 | updateBackupInfo(clusterName, clusterList); 105 | }; 106 | 107 | const updateBackupInfo = (clusterName: any, clusterList: any) => { 108 | let cluster: any = {}; 109 | clusterList.map((item: any) => { 110 | if (item.metadata.name === clusterName) { 111 | cluster = item; 112 | console.log(cluster); 113 | console.log(cluster.metadata.annotations[BackupAnnotationKey]); 114 | if (cluster.metadata.annotations[BackupAnnotationKey] === undefined) { 115 | setBackupInfo(undefined); 116 | } else { 117 | http.get(`/apis/backup/${clusterName}`).then((resp) => { 118 | let result = resp?.data; 119 | if (result === undefined) { 120 | result = []; 121 | } 122 | setBackupInfo(result); 123 | }); 124 | } 125 | } 126 | return item; 127 | }); 128 | }; 129 | 130 | return ( 131 | 132 |
133 | 137 | {t('BackupManagement')} 138 | 139 |
140 | 148 |
149 | 153 | {t('ChooseCluster')}: 154 | 155 | 172 |
173 | {backupInfo === undefined ? ( 174 | 179 | ) : ( 180 | 186 | )} 187 | 188 | 189 | ); 190 | } 191 | -------------------------------------------------------------------------------- /src/pages/backup/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making TKEStack 3 | * available. 4 | * 5 | * Copyright (C) 2012-2023 Tencent. All Rights Reserved. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 8 | * this file except in compliance with the License. You may obtain a copy of the 9 | * License at 10 | * 11 | * https://opensource.org/licenses/Apache-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15 | * WARRANTIES OF ANY KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations under the License. 17 | */ 18 | 19 | export * from './Backup'; 20 | -------------------------------------------------------------------------------- /src/pages/cluster/Add.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making TKEStack 3 | * available. 4 | * 5 | * Copyright (C) 2012-2023 Tencent. All Rights Reserved. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 8 | * this file except in compliance with the License. You may obtain a copy of the 9 | * License at 10 | * 11 | * https://opensource.org/licenses/Apache-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15 | * WARRANTIES OF ANY KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations under the License. 17 | */ 18 | 19 | import { useState } from 'react'; 20 | import { 21 | message, 22 | Layout, 23 | Typography, 24 | Form, 25 | List, 26 | InputNumber, 27 | Input, 28 | Switch, 29 | Radio, 30 | Button, 31 | Tag, 32 | } from 'antd'; 33 | import { PlusOutlined, MinusOutlined } from '@ant-design/icons'; 34 | import http from 'src/utils/http'; 35 | import { encode } from 'js-base64'; 36 | import { useTranslation } from 'react-i18next'; 37 | import { GenerateOwnerReferences, APIVersion } from 'src/utils/common'; 38 | 39 | const { Header, Content } = Layout; 40 | const { Text } = Typography; 41 | const { TextArea } = Input; 42 | 43 | const MappingSymbol = '->'; 44 | // form style 45 | const formItemLayout = { 46 | labelCol: { span: 4 }, 47 | wrapperCol: { span: 6 }, 48 | }; 49 | const sleep = (ms: any) => new Promise((resolve) => setTimeout(resolve, ms)); 50 | // page of adding cluster 51 | export function Add(): JSX.Element { 52 | const [scheme, setScheme] = useState('https'); 53 | const { t } = useTranslation(); 54 | 55 | const [memberList, setMemberList] = useState([ 56 | { 57 | key: '', 58 | value: '', 59 | }, 60 | ]); 61 | const title = t('ImortCluster'); 62 | 63 | const ensureSecret = async (values: any, clusterUID: string) => { 64 | const secret: any = { 65 | apiVersion: 'v1', 66 | data: undefined, 67 | kind: 'Secret', 68 | metadata: { 69 | name: values.name, 70 | // namespace: 'kstone', 71 | ownerReferences: GenerateOwnerReferences(values.name, clusterUID), 72 | }, 73 | type: 'Opaque', 74 | }; 75 | 76 | // init secret for https 77 | if (values.scheme === 'https' || values.authEnable) { 78 | if (secret.data === undefined) { 79 | secret.data = {}; 80 | } 81 | secret.data['ca.pem'] = encode(values.ca); 82 | secret.data['client.pem'] = encode(values.clientCa); 83 | secret.data['client-key.pem'] = encode(values.clientKey); 84 | } 85 | 86 | // init secret for auth 87 | if (values.user !== '' && values.password !== '' && values.authEnable) { 88 | if (secret.data === undefined) { 89 | secret.data = {}; 90 | } 91 | secret.data['username'] = encode(values.user); 92 | secret.data['password'] = encode(values.password); 93 | } 94 | 95 | if (secret.data !== undefined && (secret.data['ca.pem'] !== '' || secret.data['username'] !== '')) { 96 | // post etcd secret 97 | const resp = await http.post('/apis/secrets', secret); 98 | if (resp.statusText !== 'Created' && resp.status !== 409) { 99 | // handle error 100 | message.error({ 101 | content: t('FailedToCreateSecret'), 102 | }); 103 | sleep(2000); 104 | return; 105 | } 106 | } 107 | 108 | window.location.href = '/cluster'; 109 | }; 110 | 111 | // get form 112 | const [form] = Form.useForm(); 113 | // handle finish 114 | const onFinish = async (values: any) => { 115 | let certName = ''; 116 | // handle https 117 | if (values.scheme === 'https' || values.authEnable) { 118 | certName = `${values.name}`; 119 | } 120 | // transfer memberList to extClientURL 121 | let extClientURL = ''; 122 | memberList.map((item) => { 123 | if (item.key !== '' && item.value !== '') { 124 | extClientURL += `${item.key}${MappingSymbol}${item.value},`; 125 | } 126 | return item; 127 | }); 128 | if (extClientURL !== '') { 129 | extClientURL = extClientURL.substr(0, extClientURL.length - 1); 130 | } 131 | // init cluster info 132 | const cpuLimit = values.totalCpu; 133 | const cpuRequest = values.totalCpu; 134 | const memoryLimit = values.totalMem; 135 | const memoryRequest = values.totalMem; 136 | const data = { 137 | apiVersion: APIVersion, 138 | kind: 'EtcdCluster', 139 | metadata: { 140 | annotations: { 141 | importedAddr: `${values.scheme}://${values.endpoint}`, 142 | kubernetes: values.isKubernetes ? 'true' : 'false', 143 | }, 144 | name: values.name, 145 | // namespace: 'kstone', 146 | }, 147 | spec: { 148 | args: [], 149 | clusterType: 'imported', 150 | description: values.description, 151 | diskSize: values.diskSize, 152 | diskType: values.diskType, 153 | env: [], 154 | name: values.name, 155 | size: values.size, 156 | totalCpu: values.totalCpu, 157 | totalMem: values.totalMem, 158 | version: '', 159 | resources: { 160 | limits: { 161 | cpu: cpuLimit.toString(), 162 | memory: memoryLimit.toString() + "Mi", 163 | }, 164 | requests: { 165 | cpu: cpuRequest.toString(), 166 | memory: memoryRequest.toString() + "Mi", 167 | } 168 | }, 169 | storageBackend: values.storageBackend, 170 | }, 171 | } as any; 172 | if (extClientURL !== '') { 173 | data.metadata.annotations.extClientURL = extClientURL; 174 | } 175 | if (certName !== '') { 176 | data.metadata.annotations.certName = certName; 177 | } 178 | // post cluster 179 | http.post('/apis/etcdclusters', data).then((resp) => { 180 | if (resp.statusText === 'Created') { 181 | ensureSecret(values, resp.data.metadata.uid); 182 | } else { 183 | // handle error 184 | message.error({ 185 | content: resp.data.reason, 186 | }); 187 | sleep(2000); 188 | } 189 | }); 190 | }; 191 | 192 | return ( 193 | 194 |
195 | 196 | {title} 197 | 198 |
199 | 207 |
208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 223 | 224 | 225 | 226 | { 228 | setScheme(e.target.value); 229 | }} 230 | > 231 | HTTPS 232 | HTTP 233 | 234 | 235 | 238 | prevValues.scheme !== currentValues.scheme 239 | } 240 | > 241 | {({ getFieldValue }) => 242 | getFieldValue('scheme') === 'http' ? ( 243 | <> 244 | 248 | 249 | 250 | 251 | ) : null 252 | } 253 | 254 | 255 | 258 | prevValues.authEnable !== currentValues.authEnable 259 | } 260 | > 261 | {({ getFieldValue }) => 262 | getFieldValue('authEnable') ? ( 263 | <> 264 | 269 | 270 | 271 | 276 | 277 | 278 | 279 | ) : null 280 | } 281 | 282 | 287 | 288 | 289 | 290 | 291 | 292 | 297 | 298 | 299 | 300 | 301 | SSD 302 | {t('HardDrive')} 303 | 304 | 305 | 310 | 311 | 312 | 317 | 318 | 319 | 320 | 321 | v2 322 | v3 323 | 324 | 325 | 326 | 327 | {memberList.map((item, i) => { 328 | return ( 329 | 330 | { 333 | setMemberList((labels: any) => { 334 | labels[i].key = e.target.value; 335 | return [...labels]; 336 | }); 337 | }} 338 | placeholder={t('PrivateAccessAddress')} 339 | /> 340 | 346 | {MappingSymbol} 347 | 348 | { 351 | setMemberList((labels: any) => { 352 | labels[i].value = e.target.value; 353 | return [...labels]; 354 | }); 355 | }} 356 | placeholder={t('PublicNetworkAddress')} 357 | /> 358 | 369 | 370 | ); 371 | })} 372 | 373 | 383 | 384 | 385 | 386 | 389 | prevValues.scheme !== currentValues.scheme 390 | } 391 | > 392 | {({ getFieldValue }) => 393 | getFieldValue('scheme') === 'https' ? ( 394 | <> 395 | 400 |