├── .editorconfig ├── .env.development ├── .env.production ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── babel.config.js ├── mock ├── index.js ├── license.js ├── mock-server.js ├── result-holder.js ├── user-management.js ├── user-token.js ├── user.js └── utils.js ├── nginx.conf ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── font_2474164_6bnyueo2zk9.css ├── font_2474164_6bnyueo2zk9.ttf ├── font_2474164_6bnyueo2zk9.woff ├── font_2474164_6bnyueo2zk9.woff2 └── index.html ├── src ├── App.vue ├── api │ ├── auth.js │ ├── authorization.js │ ├── backup-account.js │ ├── cluster-member.js │ ├── cluster-resource.js │ ├── cluster.js │ ├── cluster │ │ ├── backup.js │ │ ├── cluster.js │ │ ├── grade.js │ │ ├── log.js │ │ ├── monitor.js │ │ ├── node.js │ │ ├── security.js │ │ ├── storage.js │ │ ├── tasks.js │ │ └── tool.js │ ├── credentials.js │ ├── hosts.js │ ├── ip-pool.js │ ├── license.js │ ├── manifest.js │ ├── msg-subscribe.js │ ├── personal-setting.js │ ├── plan.js │ ├── project-member.js │ ├── project-resource.js │ ├── projects.js │ ├── region.js │ ├── system-log.js │ ├── system-setting.js │ ├── template-config.js │ ├── theme.js │ ├── user-management.js │ ├── user-message.js │ ├── user-token.js │ ├── user.js │ ├── vm-config.js │ ├── xpack │ │ └── multi-cluster.js │ └── zone.js ├── assets │ ├── KobeOperator-login.jpg │ ├── KubeOperator-about-background.png │ ├── KubeOperator-assist-white.png │ ├── KubeOperator-black.png │ ├── KubeOperator-red.png │ ├── KubeOperator-white.png │ ├── font │ │ └── Roboto │ │ │ ├── Roboto-Regular.ttf │ │ │ └── index.css │ ├── iconfont │ │ ├── alicdn │ │ │ ├── font_985780_km7mi63cihi.eot │ │ │ ├── font_985780_km7mi63cihi.svg │ │ │ ├── font_985780_km7mi63cihi.ttf │ │ │ ├── font_985780_km7mi63cihi.woff │ │ │ └── font_985780_km7mi63cihi_iefix.eot │ │ ├── demo.css │ │ ├── demo_index.html │ │ ├── iconfont.css │ │ ├── iconfont.js │ │ ├── iconfont.json │ │ ├── iconfont.ttf │ │ ├── iconfont.woff │ │ └── iconfont.woff2 │ ├── images │ │ └── tools │ │ │ ├── chartmuseum.png │ │ │ ├── docker-registry.png │ │ │ ├── elasticsearch.png │ │ │ ├── gatekeeper.jpg │ │ │ ├── grafana.png │ │ │ ├── kubeapps.png │ │ │ ├── kubepi.png │ │ │ ├── kubernetes.png │ │ │ ├── loki.png │ │ │ ├── prometheus.png │ │ │ └── registry.png │ └── login-desc.png ├── business │ ├── app-layout │ │ ├── header-components │ │ │ ├── Help.vue │ │ │ ├── LanguageSwitch.vue │ │ │ ├── PersonalSetting.vue │ │ │ ├── ProjectSwitch.vue │ │ │ └── StationLetter.vue │ │ └── horizontal-layout │ │ │ ├── HorizontalHeader.vue │ │ │ └── index.vue │ ├── authorization │ │ ├── index.vue │ │ ├── management │ │ │ ├── index.vue │ │ │ ├── members │ │ │ │ └── index.vue │ │ │ └── resources │ │ │ │ └── index.vue │ │ └── projects │ │ │ ├── create │ │ │ └── index.vue │ │ │ ├── edit │ │ │ └── index.vue │ │ │ └── index.vue │ ├── automatic │ │ ├── ip-pools │ │ │ ├── create │ │ │ │ └── index.vue │ │ │ ├── index.vue │ │ │ └── ips │ │ │ │ ├── create │ │ │ │ └── index.vue │ │ │ │ └── index.vue │ │ ├── plans │ │ │ ├── create │ │ │ │ └── index.vue │ │ │ ├── edit │ │ │ │ └── index.vue │ │ │ └── index.vue │ │ ├── regions │ │ │ ├── create │ │ │ │ └── index.vue │ │ │ ├── edit │ │ │ │ └── index.vue │ │ │ └── index.vue │ │ ├── template-configs │ │ │ ├── index.vue │ │ │ └── operate │ │ │ │ └── index.vue │ │ ├── vm-configs │ │ │ ├── create │ │ │ │ └── index.vue │ │ │ ├── edit │ │ │ │ └── index.vue │ │ │ └── index.vue │ │ └── zones │ │ │ ├── create │ │ │ └── index.vue │ │ │ ├── edit │ │ │ └── index.vue │ │ │ └── index.vue │ ├── backup-account │ │ ├── create │ │ │ └── index.vue │ │ ├── edit │ │ │ └── index.vue │ │ └── index.vue │ ├── clusters │ │ ├── create │ │ │ └── index.vue │ │ ├── detail │ │ │ ├── backup │ │ │ │ ├── index.vue │ │ │ │ ├── logs │ │ │ │ │ └── index.vue │ │ │ │ ├── velero_backup.vue │ │ │ │ ├── velero_config.vue │ │ │ │ └── velero_restore.vue │ │ │ ├── component │ │ │ │ ├── index.vue │ │ │ │ ├── istio │ │ │ │ │ ├── detail.vue │ │ │ │ │ └── index.vue │ │ │ │ └── metallb │ │ │ │ │ └── index.vue │ │ │ ├── grade │ │ │ │ └── index.vue │ │ │ ├── index.vue │ │ │ ├── log │ │ │ │ ├── index.vue │ │ │ │ ├── logging │ │ │ │ │ └── index.vue │ │ │ │ └── loki │ │ │ │ │ └── index.vue │ │ │ ├── monitor │ │ │ │ └── index.vue │ │ │ ├── node │ │ │ │ ├── detail │ │ │ │ │ └── index.vue │ │ │ │ └── index.vue │ │ │ ├── overview │ │ │ │ ├── index.vue │ │ │ │ └── ko-detail.vue │ │ │ ├── security │ │ │ │ └── index.vue │ │ │ ├── storage │ │ │ │ ├── class-create │ │ │ │ │ └── index.vue │ │ │ │ ├── index.vue │ │ │ │ ├── provisioner-create │ │ │ │ │ └── index.vue │ │ │ │ └── provisioner-detail │ │ │ │ │ └── index.vue │ │ │ ├── task │ │ │ │ ├── detail │ │ │ │ │ └── index.vue │ │ │ │ └── index.vue │ │ │ └── tool │ │ │ │ └── index.vue │ │ ├── import │ │ │ ├── index.vue │ │ │ └── ko-import.vue │ │ ├── index.vue │ │ └── upgrade │ │ │ └── index.vue │ ├── dashboard │ │ └── index.vue │ ├── directive │ │ ├── ClickOutsideDemo.vue │ │ └── PermissionDemo.vue │ ├── hosts │ │ ├── create │ │ │ └── index.vue │ │ ├── edit │ │ │ └── index.vue │ │ └── index.vue │ ├── login │ │ └── index.vue │ ├── manifest │ │ └── index.vue │ ├── msg-subscribe │ │ └── index.vue │ ├── system-log │ │ └── index.vue │ ├── system-setting │ │ ├── credential │ │ │ ├── create │ │ │ │ └── index.vue │ │ │ ├── edit │ │ │ │ └── index.vue │ │ │ └── index.vue │ │ ├── index.vue │ │ ├── kubepi │ │ │ └── index.vue │ │ ├── ldap │ │ │ └── index.vue │ │ ├── license │ │ │ └── index.vue │ │ ├── message │ │ │ └── index.vue │ │ ├── ntp │ │ │ └── index.vue │ │ └── registry │ │ │ ├── create │ │ │ └── index.vue │ │ │ ├── edit │ │ │ └── index.vue │ │ │ └── index.vue │ ├── users │ │ ├── create │ │ │ └── index.vue │ │ ├── edit │ │ │ └── index.vue │ │ └── index.vue │ ├── xpack │ │ ├── index.vue │ │ ├── multi-cluster │ │ │ ├── create │ │ │ │ └── index.vue │ │ │ ├── dialog │ │ │ │ ├── RelationsManagement.vue │ │ │ │ ├── RepositoryErrorMessage.vue │ │ │ │ └── SyncLogDetail.vue │ │ │ ├── edit │ │ │ │ └── index.vue │ │ │ ├── index.vue │ │ │ └── log │ │ │ │ └── index.vue │ │ └── theme │ │ │ └── index.vue │ └── xterm │ │ └── index.vue ├── components │ ├── back-button │ │ └── index.vue │ ├── batch-delete │ │ └── index.vue │ ├── cloud-providers │ │ └── index.vue │ ├── complex-table │ │ └── index.vue │ ├── detail-card │ │ ├── ItemValue.vue │ │ └── index.vue │ ├── k8s-page │ │ └── index.vue │ ├── ko-logs │ │ └── index.vue │ ├── ko-status │ │ └── index.vue │ ├── layout │ │ ├── LayoutContent.vue │ │ ├── LayoutHeader.vue │ │ ├── LayoutMain.vue │ │ ├── LayoutSidebar.vue │ │ ├── LayoutView.vue │ │ ├── index.vue │ │ └── sidebar │ │ │ ├── FixiOSBug.js │ │ │ ├── Item.vue │ │ │ ├── Link.vue │ │ │ ├── Logo.vue │ │ │ ├── SidebarItem.vue │ │ │ ├── SidebarToggleButton.vue │ │ │ └── index.vue │ └── redirect │ │ └── index.vue ├── directive │ ├── click-outside │ │ └── index.js │ ├── index.js │ └── permission │ │ └── index.js ├── filters │ └── index.js ├── i18n │ ├── index.js │ ├── lang │ │ ├── en-US.js │ │ ├── zh-CN.js │ │ └── zh-TW.js │ └── 国际化规范.md ├── icons │ └── index.js ├── main.js ├── permission.js ├── plugins │ ├── index.js │ ├── message.js │ └── request.js ├── router │ ├── index.js │ └── modules │ │ ├── authorization.js │ │ ├── automatic.js │ │ ├── backup-account.js │ │ ├── clusters.js │ │ ├── hosts.js │ │ ├── manifest.js │ │ ├── msg-subscribe.js │ │ ├── setting.js │ │ ├── system-log.js │ │ ├── users.js │ │ └── x-pack.js ├── store │ ├── getters.js │ ├── index.js │ └── modules │ │ ├── app.js │ │ ├── license.js │ │ ├── permission.js │ │ ├── theme.js │ │ ├── user-token.js │ │ └── user.js ├── styles │ ├── business │ │ ├── app.scss │ │ └── header-menu.scss │ ├── common │ │ ├── mixins.scss │ │ └── variables.scss │ └── index.scss └── utils │ ├── format_ansible_err.js │ ├── format_conversion.js │ ├── format_date.js │ ├── global_variable.js │ ├── permisstion.js │ ├── rules.js │ ├── sort.js │ ├── token.js │ └── validate.js └── vue.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | insert_final_newline = false 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | # just a flag 2 | ENV = 'development' 3 | 4 | # base api, e.g., '/dev' 5 | VUE_APP_PUBLIC_PATH = '/ui' 6 | -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | # just a flag 2 | ENV = 'production' 3 | 4 | # base api, e.g., '/prod' 5 | VUE_APP_PUBLIC_PATH = '/ui' 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | 25 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:14-alpine3.15 as stage-build 2 | 3 | ARG NPM_REGISTRY="https://registry.npmmirror.com" 4 | ENV NPM_REGISTY=$NPM_REGISTRY 5 | 6 | RUN set -ex \ 7 | && npm config set registry ${NPM_REGISTRY} 8 | 9 | WORKDIR /data 10 | 11 | RUN apk update && \ 12 | apk upgrade 13 | 14 | COPY ./package.json /data/package.json 15 | COPY ./package-lock.json /data/package-lock.json 16 | RUN npm install 17 | COPY . /data 18 | RUN npm run-script build 19 | 20 | FROM nginx:alpine 21 | 22 | COPY --from=stage-build /data/dist /opt/neeko 23 | COPY nginx.conf /etc/nginx/conf.d/default.conf 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NEEKO 2 | 3 | Neeko 是 KubeOperator 的新版前端 UI 项目, 主要使用 [Vue](https://cn.vuejs.org/), [Element UI](https://github.com/fit2cloud-ui/fit2cloud-ui/) 完成。 4 | 5 | ## 开发运行 6 | 7 | ``` 8 | 0. 前置条件: 部署运行好 KubeOperator API 服务器 9 | 10 | 1. 安装依赖 11 | $ npm install 12 | 13 | 2. 运行 14 | $ npm run serve 15 | 16 | 3. 构建 17 | $ npm run build 18 | ``` 19 | 20 | ## 致谢 21 | - [Vue](https://cn.vuejs.org) 前端框架 22 | - [FIT2CLOUD UI](https://github.com/fit2cloud-ui/fit2cloud-ui/) FIT2CLOUD UI组件库 23 | - [Vue-element-admin](https://github.com/PanJiaChen/vue-element-admin) 项目脚手架 24 | 25 | 26 | ## Copyright 27 | Copyright (c) 2014-2024 飞致云 FIT2CLOUD, All rights reserved. 28 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /mock/index.js: -------------------------------------------------------------------------------- 1 | const Mock = require('mockjs') 2 | const {param2Obj} = require('./utils') 3 | 4 | const user = require('./user') 5 | const userManagement = require('./user-management') 6 | 7 | const mocks = [ 8 | ...user, 9 | ...userManagement, 10 | ] 11 | 12 | // for front mock 13 | // please use it cautiously, it will redefine XMLHttpRequest, 14 | // which will cause many of your third-party libraries to be invalidated(like progress event). 15 | function mockXHR() { 16 | // mock patch 17 | // https://github.com/nuysoft/Mock/issues/300 18 | Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send 19 | Mock.XHR.prototype.send = function () { 20 | if (this.custom.xhr) { 21 | this.custom.xhr.withCredentials = this.withCredentials || false 22 | 23 | if (this.responseType) { 24 | this.custom.xhr.responseType = this.responseType 25 | } 26 | } 27 | this.proxy_send(...arguments) 28 | } 29 | 30 | function XHR2ExpressReqWrap(respond) { 31 | return function (options) { 32 | let result; 33 | if (respond instanceof Function) { 34 | const {body, type, url} = options 35 | // https://expressjs.com/en/4x/api.html#req 36 | result = respond({ 37 | method: type, 38 | body: JSON.parse(body), 39 | query: param2Obj(url) 40 | }) 41 | } else { 42 | result = respond 43 | } 44 | return Mock.mock(result) 45 | } 46 | } 47 | 48 | for (const i of mocks) { 49 | Mock.mock(new RegExp(i.url), i.type || 'get', XHR2ExpressReqWrap(i.response)) 50 | } 51 | } 52 | 53 | module.exports = { 54 | mocks, 55 | mockXHR 56 | } 57 | -------------------------------------------------------------------------------- /mock/license.js: -------------------------------------------------------------------------------- 1 | const {success, error} = require("./result-holder") 2 | 3 | const licenses = { 4 | valid: { 5 | status: "valid", 6 | license: { 7 | "corporation": "xxxxxxxxxxxx", 8 | "expired": "2030-07-03", 9 | "licenseVersion": "v2", 10 | "product": "cmp", 11 | "generateTime": "1593763389356", 12 | "edition": "Enterprise", 13 | "count": 11 14 | }, 15 | message: "" 16 | }, 17 | invalid: { 18 | status: "invalid", 19 | license: {}, 20 | message: "license has invalid" 21 | }, 22 | expired: { 23 | status: "expired", 24 | license: { 25 | "corporation": "xxxxxxxxxxxx", 26 | "expired": "2020-07-03", 27 | "licenseVersion": "v2", 28 | "product": "cmp", 29 | "generateTime": "1593763389356", 30 | "edition": "Enterprise", 31 | "count": 11 32 | }, 33 | message: "license has expired since 2020-07-03" 34 | }, 35 | } 36 | 37 | module.exports = [ 38 | { 39 | url: '/samples/license/save', 40 | type: 'post', 41 | response: config => { 42 | const {license} = config.body 43 | const data = licenses[license]; 44 | 45 | if (!data) { 46 | return success(licenses.invalid) 47 | } 48 | return success(data) 49 | } 50 | }, 51 | ] 52 | -------------------------------------------------------------------------------- /mock/mock-server.js: -------------------------------------------------------------------------------- 1 | const chokidar = require('chokidar') 2 | const bodyParser = require('body-parser') 3 | const chalk = require('chalk') 4 | const path = require('path') 5 | const Mock = require('mockjs') 6 | 7 | const mockDir = path.join(process.cwd(), 'mock') 8 | 9 | function registerRoutes(app) { 10 | let mockLastIndex 11 | const { mocks } = require('./index.js') 12 | const mocksForServer = mocks.map(route => { 13 | return responseFake(route.url, route.type, route.response) 14 | }) 15 | for (const mock of mocksForServer) { 16 | app[mock.type](mock.url, mock.response) 17 | mockLastIndex = app._router.stack.length 18 | } 19 | const mockRoutesLength = Object.keys(mocksForServer).length 20 | return { 21 | mockRoutesLength: mockRoutesLength, 22 | mockStartIndex: mockLastIndex - mockRoutesLength 23 | } 24 | } 25 | 26 | function unregisterRoutes() { 27 | Object.keys(require.cache).forEach(i => { 28 | if (i.includes(mockDir)) { 29 | delete require.cache[require.resolve(i)] 30 | } 31 | }) 32 | } 33 | 34 | // for mock server 35 | const responseFake = (url, type, respond) => { 36 | return { 37 | url: new RegExp(`${url}`), 38 | type: type || 'get', 39 | response(req, res) { 40 | console.log('request invoke:' + req.path) 41 | res.json(Mock.mock(respond instanceof Function ? respond(req, res) : respond)) 42 | } 43 | } 44 | } 45 | 46 | module.exports = app => { 47 | // parse app.body 48 | // https://expressjs.com/en/4x/api.html#req.body 49 | app.use(bodyParser.json()) 50 | app.use(bodyParser.urlencoded({ 51 | extended: true 52 | })) 53 | 54 | const mockRoutes = registerRoutes(app) 55 | var mockRoutesLength = mockRoutes.mockRoutesLength 56 | var mockStartIndex = mockRoutes.mockStartIndex 57 | 58 | // watch files, hot reload mock server 59 | chokidar.watch(mockDir, { 60 | ignored: /mock-server/, 61 | ignoreInitial: true 62 | }).on('all', (event, path) => { 63 | if (event === 'change' || event === 'add') { 64 | try { 65 | // remove mock routes stack 66 | app._router.stack.splice(mockStartIndex, mockRoutesLength) 67 | 68 | // clear routes cache 69 | unregisterRoutes() 70 | 71 | const mockRoutes = registerRoutes(app) 72 | mockRoutesLength = mockRoutes.mockRoutesLength 73 | mockStartIndex = mockRoutes.mockStartIndex 74 | 75 | console.log(chalk.magentaBright(`\n > Mock Server hot reload success! changed ${path}`)) 76 | } catch (error) { 77 | console.log(chalk.redBright(error)) 78 | } 79 | } 80 | }) 81 | } 82 | -------------------------------------------------------------------------------- /mock/result-holder.js: -------------------------------------------------------------------------------- 1 | class ResultHolder { 2 | constructor(success, data, message) { 3 | this.success = success; 4 | this.data = data; 5 | this.message = message; 6 | } 7 | } 8 | 9 | const success = data => { 10 | return new ResultHolder(true, data) 11 | } 12 | 13 | const error = message => { 14 | return new ResultHolder(false, undefined, message) 15 | } 16 | 17 | module.exports = { 18 | success, 19 | error 20 | } 21 | -------------------------------------------------------------------------------- /mock/user-management.js: -------------------------------------------------------------------------------- 1 | const {success} = require("./result-holder") 2 | const users = [ 3 | { 4 | id: "admin", 5 | name: 'Administrator', 6 | email: "admin@fit2cloud.com", 7 | roles: ['admin'], 8 | language: "zh-CN" 9 | }, { 10 | id: "editor", 11 | name: 'Editor', 12 | email: "editor@fit2cloud.com", 13 | roles: ['editor'], 14 | language: "zh-CN" 15 | }, { 16 | id: "readonly", 17 | name: 'Readonly User', 18 | email: "readonly@fit2cloud.com", 19 | roles: ['readonly'], 20 | language: "zh-CN" 21 | } 22 | ] 23 | 24 | for (let i = 0; i < 300; i++) { 25 | let id = "user-" + i 26 | let name = id; 27 | let email = id + "@fit2cloud.com" 28 | let roles = ['readonly'] 29 | let language = "zh-CN" 30 | let createTime = new Date().getTime() 31 | users.push({id, name, email, roles, language, createTime}) 32 | } 33 | 34 | module.exports = [ 35 | // get user list 36 | { 37 | url: '/samples/user-management/list/\.*', 38 | type: 'get', 39 | response: (config) => { 40 | let path = config.originalUrl.split("/") 41 | let page = Number.parseInt(path[4]) 42 | let size = Number.parseInt(path[5]) 43 | let start = (page - 1) * size 44 | let end = page * size 45 | 46 | let data = { 47 | items: users.slice(start, end), 48 | total: users.length 49 | } 50 | return success(data) 51 | } 52 | }, 53 | ] 54 | 55 | -------------------------------------------------------------------------------- /mock/user-token.js: -------------------------------------------------------------------------------- 1 | const {success, error} = require("./result-holder") 2 | const TOKEN_KEY = "App-Token" 3 | 4 | /* 前后端分离,用Token验证登录*/ 5 | const tokens = { 6 | admin: { 7 | token: 'admin-token' 8 | }, 9 | editor: { 10 | token: 'editor-token' 11 | }, 12 | readonly: { 13 | token: 'readonly-token' 14 | } 15 | } 16 | 17 | const users = { 18 | 'admin-token': { 19 | id: "admin", 20 | name: 'Administrator', 21 | email: "admin@fit2cloud.com", 22 | roles: ['admin'], 23 | language: "zh-CN" 24 | }, 25 | 'editor-token': { 26 | id: "editor", 27 | name: 'Editor', 28 | email: "editor@fit2cloud.com", 29 | roles: ['editor'], 30 | language: "zh-CN" 31 | }, 32 | 'readonly-token': { 33 | id: "readonly", 34 | name: 'Readonly User', 35 | email: "readonly@fit2cloud.com", 36 | roles: ['readonly'], 37 | language: "zh-CN" 38 | } 39 | } 40 | 41 | module.exports = [ 42 | // user login 43 | { 44 | url: '/samples/user-token/login', 45 | type: 'post', 46 | response: config => { 47 | const {username} = config.body 48 | const {token} = tokens[username]; 49 | 50 | // mock error 51 | if (!token) { 52 | return error("用户名或密码错误") 53 | } 54 | return success(token) 55 | } 56 | }, 57 | 58 | // get user info 59 | { 60 | url: '/samples/user-token/info', 61 | type: 'get', 62 | response: (config) => { 63 | let token = config.headers[TOKEN_KEY] 64 | const info = users[token] 65 | 66 | // mock error 67 | if (!info) { 68 | return error("无法获取用户[" + token + "]详细信息") 69 | } 70 | 71 | return success(info) 72 | } 73 | }, 74 | 75 | // update user info 76 | { 77 | url: '/samples/user/info/update', 78 | type: 'put', 79 | response: config => { 80 | let token = config.headers[TOKEN_KEY] 81 | const {language} = config.body 82 | users[token].language = language; 83 | 84 | return success(users[token]) 85 | } 86 | }, 87 | 88 | // user logout 89 | { 90 | url: '/samples/user/logout', 91 | type: 'post', 92 | response: () => { 93 | // do something 94 | return success() 95 | } 96 | } 97 | ] 98 | -------------------------------------------------------------------------------- /mock/user.js: -------------------------------------------------------------------------------- 1 | const {success, error} = require("./result-holder") 2 | 3 | /* 前后端不分离的接口,用Session验证登录*/ 4 | let currentUser 5 | 6 | const users = { 7 | admin: { 8 | id: "admin", 9 | name: 'Administrator', 10 | email: "admin@fit2cloud.com", 11 | roles: ['admin'], 12 | language: "zh-CN" 13 | }, 14 | editor: { 15 | id: "editor", 16 | name: 'Editor', 17 | email: "editor@fit2cloud.com", 18 | roles: ['editor'], 19 | language: "zh-CN" 20 | }, 21 | readonly: { 22 | id: "readonly", 23 | name: 'Readonly User', 24 | email: "readonly@fit2cloud.com", 25 | roles: ['readonly'], 26 | language: "zh-CN" 27 | } 28 | } 29 | 30 | module.exports = [ 31 | // user login 32 | { 33 | url: '/samples/user/login', 34 | type: 'post', 35 | response: config => { 36 | const {username} = config.body 37 | const user = users[username]; 38 | 39 | // mock error 40 | if (!user) { 41 | return error("用户名或密码错误") 42 | } 43 | currentUser = user; 44 | return success(user) 45 | } 46 | }, 47 | 48 | { 49 | url: '/samples/user/is-login', 50 | type: 'get', 51 | response: () => { 52 | if (currentUser) { 53 | return success() 54 | } else { 55 | return error() 56 | } 57 | } 58 | }, 59 | 60 | // get user info 61 | { 62 | url: '/samples/user/current', 63 | type: 'get', 64 | response: () => { 65 | const info = currentUser 66 | 67 | // mock error 68 | if (!info) { 69 | return error("用户未登录") 70 | } 71 | 72 | return success(info) 73 | } 74 | }, 75 | 76 | // update user info 77 | { 78 | url: '/samples/user/info/update', 79 | type: 'put', 80 | response: config => { 81 | const {language} = config.body 82 | if (currentUser) { 83 | currentUser.language = language; 84 | } 85 | return success(currentUser) 86 | } 87 | }, 88 | 89 | // user logout 90 | { 91 | url: '/samples/user/logout', 92 | type: 'post', 93 | response: () => { 94 | currentUser = undefined; 95 | return success() 96 | } 97 | } 98 | ] 99 | -------------------------------------------------------------------------------- /mock/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} url 3 | * @returns {Object} 4 | */ 5 | function param2Obj(url) { 6 | const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ') 7 | if (!search) { 8 | return {} 9 | } 10 | const obj = {} 11 | const searchArr = search.split('&') 12 | searchArr.forEach(v => { 13 | const index = v.indexOf('=') 14 | if (index !== -1) { 15 | const name = v.substring(0, index) 16 | const val = v.substring(index + 1, v.length) 17 | obj[name] = val 18 | } 19 | }) 20 | return obj 21 | } 22 | 23 | /** 24 | * This is just a simple version of deep copy 25 | * Has a lot of edge cases bug 26 | * If you want to use a perfect deep copy, use lodash's _.cloneDeep 27 | * @param {Object} source 28 | * @returns {Object} 29 | */ 30 | function deepClone(source) { 31 | if (!source && typeof source !== 'object') { 32 | throw new Error('error arguments', 'deepClone') 33 | } 34 | const targetObj = source.constructor === Array ? [] : {} 35 | Object.keys(source).forEach(keys => { 36 | if (source[keys] && typeof source[keys] === 'object') { 37 | targetObj[keys] = deepClone(source[keys]) 38 | } else { 39 | targetObj[keys] = source[keys] 40 | } 41 | }) 42 | return targetObj 43 | } 44 | 45 | module.exports = { 46 | param2Obj, 47 | deepClone 48 | } 49 | -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | 4 | location /ui/ { 5 | try_files $uri / /index.html; 6 | alias /opt/neeko/; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "neeko", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "@fortawesome/fontawesome-svg-core": "^1.2.34", 12 | "@fortawesome/free-brands-svg-icons": "^5.15.2", 13 | "@fortawesome/free-regular-svg-icons": "^5.15.2", 14 | "@fortawesome/free-solid-svg-icons": "^5.15.2", 15 | "@fortawesome/vue-fontawesome": "^2.0.2", 16 | "axios": "^0.21.1", 17 | "core-js": "^3.6.5", 18 | "echarts": "^4.9.0", 19 | "element-ui": "2.15.8", 20 | "fit2cloud-ui": "^1.8.0", 21 | "ipaddr.js": "^2.0.0", 22 | "js-cookie": "^2.2.1", 23 | "normalize.css": "^8.0.1", 24 | "nprogress": "^0.2.0", 25 | "sass": "^1.53.0", 26 | "vue": "^2.6.11", 27 | "vue-codemirror": "^4.0.6", 28 | "vue-i18n": "^8.22.4", 29 | "vuex": "^3.6.0", 30 | "xterm": "^4.11.0" 31 | }, 32 | "devDependencies": { 33 | "@vue/cli-plugin-babel": "~4.5.0", 34 | "@vue/cli-plugin-eslint": "~4.5.0", 35 | "@vue/cli-service": "~4.5.0", 36 | "babel-eslint": "^10.1.0", 37 | "eslint": "^6.7.2", 38 | "eslint-plugin-vue": "^6.2.2", 39 | "mockjs": "^1.1.0", 40 | "sass-loader": "^10.1.0", 41 | "vue-template-compiler": "^2.6.11" 42 | }, 43 | "eslintConfig": { 44 | "root": true, 45 | "env": { 46 | "node": true 47 | }, 48 | "extends": [ 49 | "plugin:vue/essential", 50 | "eslint:recommended" 51 | ], 52 | "parserOptions": { 53 | "parser": "babel-eslint" 54 | }, 55 | "rules": {} 56 | }, 57 | "browserslist": [ 58 | "> 1%", 59 | "last 2 versions", 60 | "not dead" 61 | ] 62 | } 63 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KubeOperator/neeko/028abed4e888dbef472d83c6a33aaea4bb6050ed/public/favicon.ico -------------------------------------------------------------------------------- /public/font_2474164_6bnyueo2zk9.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KubeOperator/neeko/028abed4e888dbef472d83c6a33aaea4bb6050ed/public/font_2474164_6bnyueo2zk9.ttf -------------------------------------------------------------------------------- /public/font_2474164_6bnyueo2zk9.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KubeOperator/neeko/028abed4e888dbef472d83c6a33aaea4bb6050ed/public/font_2474164_6bnyueo2zk9.woff -------------------------------------------------------------------------------- /public/font_2474164_6bnyueo2zk9.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KubeOperator/neeko/028abed4e888dbef472d83c6a33aaea4bb6050ed/public/font_2474164_6bnyueo2zk9.woff2 -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | KubeOperator 8 | 9 | 10 | 11 | 15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | -------------------------------------------------------------------------------- /src/api/auth.js: -------------------------------------------------------------------------------- 1 | import {get, post, del} from "@/plugins/request" 2 | 3 | const authUrl = "/api/v1/auth/session" 4 | const codeUrl = "/api/v1/captcha" 5 | const forgetPassword = "/api/v1/user/forgot/password" 6 | 7 | export function login(data) { 8 | return post(authUrl, data) 9 | } 10 | 11 | export function logout() { 12 | return del(authUrl) 13 | } 14 | 15 | export function isLogin() { 16 | return get(`${authUrl}/status`) 17 | } 18 | 19 | export function getSession() { 20 | return get(authUrl) 21 | } 22 | 23 | export function getCaptcha() { 24 | return get(`${codeUrl}`) 25 | } 26 | 27 | export function resetPassword(data) { 28 | return post(`${forgetPassword}`,data) 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization.js: -------------------------------------------------------------------------------- 1 | import {get} from "@/plugins/request" 2 | 3 | const authorizationUrl = "/api/v1/projects" 4 | 5 | export function getResourceTree () { 6 | return get(`${authorizationUrl}/tree`) 7 | } 8 | -------------------------------------------------------------------------------- /src/api/backup-account.js: -------------------------------------------------------------------------------- 1 | import {del, get, patch, post} from "@/plugins/request" 2 | 3 | const backupAccountUrl="/api/v1/backupaccounts" 4 | 5 | 6 | export function getBackupAccounts(currentPage,pageSize) { 7 | return get(`${backupAccountUrl}/?pageNum=${currentPage}&pageSize=${pageSize}`) 8 | } 9 | 10 | export function createBackupAccounts(data) { 11 | return post(`${backupAccountUrl}`,data) 12 | } 13 | 14 | export function listBuckets(data) { 15 | return post(`${backupAccountUrl}/buckets`,data) 16 | } 17 | 18 | export function updateBackupAccounts(name, data) { 19 | return patch(`${backupAccountUrl}/${name}`,data) 20 | } 21 | 22 | export function deleteBackupAccounts(name) { 23 | return del(`${backupAccountUrl}/${name}`) 24 | } 25 | 26 | export function searchBackupAccounts(currentPage, pageSize, conditions) { 27 | return post(`${backupAccountUrl}/search?pageNum=${currentPage}&pageSize=${pageSize}`, conditions) 28 | } 29 | 30 | export function getBackupAccountByName(name) { 31 | return get(`${backupAccountUrl}/${name}`) 32 | } 33 | -------------------------------------------------------------------------------- /src/api/cluster-member.js: -------------------------------------------------------------------------------- 1 | import {del, get, post} from "@/plugins/request" 2 | 3 | const clusterMemberUrl = (projectName, clusterName) => { 4 | return `/api/v1/projects/${projectName}/clusters/${clusterName}/members` 5 | } 6 | 7 | export function listClusterMembers (projectName, clusterName, currentPage, pageSize) { 8 | return get(`${clusterMemberUrl(projectName, clusterName)}?pageNum=${currentPage}&pageSize=${pageSize}`) 9 | } 10 | 11 | export function createClusterMember (projectName, clusterName, data) { 12 | return post(`${clusterMemberUrl(projectName, clusterName)}`, data) 13 | } 14 | 15 | export function deleteClusterMember (projectName, clusterName, name) { 16 | return del(`${clusterMemberUrl(projectName, clusterName)}/${name}`) 17 | } 18 | 19 | export function searchUsers (projectName, name) { 20 | return get(`${clusterMemberUrl(projectName)}/users?name=${name}`) 21 | } 22 | 23 | export function searchUsersByProject (projectName, clusterName, name) { 24 | return get(`${clusterMemberUrl(projectName, clusterName)}/search?name=${name}`) 25 | } 26 | -------------------------------------------------------------------------------- /src/api/cluster-resource.js: -------------------------------------------------------------------------------- 1 | import {del, get, post} from "@/plugins/request" 2 | 3 | const clusterResourceUrl = (projectName,clusterName) => { 4 | return `/api/v1/projects/${projectName}/clusters/${clusterName}/resources` 5 | } 6 | 7 | export function listClusterResources (projectName,clusterName, resourceType, currentPage, pageSize) { 8 | return get(`${clusterResourceUrl(projectName,clusterName)}?pageNum=${currentPage}&pageSize=${pageSize}&resourceType=${resourceType}`) 9 | } 10 | 11 | export function listClusterResourcesAll (projectName,clusterName, resourceType) { 12 | return get(`${clusterResourceUrl(projectName,clusterName)}?resourceType=${resourceType}`) 13 | } 14 | 15 | export function createClusterResource (projectName,clusterName, data) { 16 | return post(`${clusterResourceUrl(projectName,clusterName)}`, data) 17 | } 18 | 19 | export function deleteClusterResource (projectName,clusterName, name, resourceType) { 20 | return del(`${clusterResourceUrl(projectName,clusterName)}/${name}?resourceType=${resourceType}`) 21 | } 22 | 23 | export function getResourceAddList (projectName,clusterName, resourceType) { 24 | return get(`${clusterResourceUrl(projectName,clusterName)}/list?resourceType=${resourceType}`) 25 | } 26 | -------------------------------------------------------------------------------- /src/api/cluster.js: -------------------------------------------------------------------------------- 1 | import {get, post, del} from "@/plugins/request" 2 | 3 | const clusterUrl = "/api/v1/clusters" 4 | const componentUrl = "/api/v1/components" 5 | 6 | export function getClusterByName(clusterName) { 7 | return get(`${clusterUrl}/${clusterName}`) 8 | } 9 | 10 | export function getClusterByProject(projectNames) { 11 | return get(`${clusterUrl}/name/${projectNames}`) 12 | } 13 | 14 | export function checkClusterNameExistence(clusterName) { 15 | return get(`${clusterUrl}/existence/${clusterName}`) 16 | } 17 | 18 | export function listClusters(page, size) { 19 | return get(`${clusterUrl}?pageNum=${page}&pageSize=${size}`) 20 | } 21 | 22 | export function searchClusters(page, size, condition, isPolling) { 23 | return post(`${clusterUrl}/search?pageNum=${page}&pageSize=${size}&isPolling=${isPolling}`, condition) 24 | } 25 | 26 | export function createCluster(data) { 27 | return post(`${clusterUrl}`, data) 28 | } 29 | 30 | export function healthCheck(clusterName) { 31 | return get(`${clusterUrl}/health/${clusterName}`) 32 | } 33 | 34 | export function clusterRecover(clusterName, data) { 35 | return post(`${clusterUrl}/recover/${clusterName}`, data) 36 | } 37 | 38 | export function deleteCluster(clusterName, force, uninstall) { 39 | return del(`${clusterUrl}/${clusterName}?force=${force}&uninstall=${uninstall}`) 40 | } 41 | 42 | export function importCluster(data) { 43 | return post(`${clusterUrl}/import/`, data) 44 | } 45 | 46 | export function allClusters() { 47 | return get(`${clusterUrl}`) 48 | } 49 | 50 | export function initCluster(clusterName) { 51 | return post(`${clusterUrl}/init/${clusterName}/`, {}) 52 | } 53 | 54 | export function upgradeCluster(clusterName, version) { 55 | let req = { 56 | clusterName: clusterName, 57 | version: version, 58 | } 59 | return post(`${clusterUrl}/upgrade/`, req) 60 | } 61 | 62 | export function getClusterStatus(clusterName) { 63 | return get(`${clusterUrl}/status/${clusterName}`) 64 | } 65 | 66 | export function getSecret(clusterName) { 67 | return get(`${clusterUrl}/secret/${clusterName}`) 68 | } 69 | 70 | export function getClusterInfo(data) { 71 | return post(clusterUrl + "/load", data) 72 | } 73 | 74 | export function handleGpu(clusterName, handle) { 75 | return post(`${clusterUrl}/gpu/${clusterName}/${handle}`) 76 | } 77 | 78 | export function getGpuStatu(clusterName) { 79 | return get(`${clusterUrl}/gpu/${clusterName}`) 80 | } 81 | 82 | export function getComponents(cluster) { 83 | return get(`${componentUrl}?cluster=${cluster}`) 84 | } 85 | 86 | export function syncComponents(data) { 87 | return post(`${componentUrl}/sync`, data) 88 | } 89 | 90 | export function createComponent(data) { 91 | return post(`${componentUrl}`, data) 92 | } 93 | 94 | export function deleteComponent(cluster, name) { 95 | return del(`${componentUrl}/${cluster}/${name}`) 96 | } -------------------------------------------------------------------------------- /src/api/cluster/backup.js: -------------------------------------------------------------------------------- 1 | import {del, get, post} from "@/plugins/request" 2 | 3 | const fileUrl = "/api/v1/clusters/backup/files" 4 | const logUrl = "/api/v1/clusters/log" 5 | const strategyUrl = "/api/v1/clusters/backup/strategy" 6 | const clusterUrl = "/api/v1/clusters/backupaccounts" 7 | const VeleroUrl = "/api/v1/clusters/velero" 8 | 9 | 10 | 11 | export function listBackupByPage (clusterName, page, size) { 12 | const itemUrl = `${fileUrl}?pageNum=${page}&pageSize=${size}&clusterName=${clusterName}` 13 | return get(itemUrl) 14 | } 15 | 16 | export function getStrategy (clusterName) { 17 | const itemUrl = `${strategyUrl}/${clusterName}` 18 | return get(itemUrl) 19 | } 20 | 21 | export function getBackupLog (clusterName) { 22 | return get(`${logUrl}/${clusterName}`) 23 | } 24 | 25 | export function createStrategy (data) { 26 | const itemUrl = `${strategyUrl}` 27 | return post(itemUrl, data) 28 | } 29 | 30 | export function startBackup (data) { 31 | const itemUrl = `${fileUrl}/backup` 32 | return post(itemUrl, data) 33 | } 34 | 35 | export function startRestore (data) { 36 | const itemUrl = `${fileUrl}/restore` 37 | return post(itemUrl, data) 38 | } 39 | 40 | export function localRestore (data) { 41 | const itemUrl = `${fileUrl}/restore/local` 42 | return post(itemUrl, data) 43 | } 44 | 45 | export function listBackupAccounts (name) { 46 | return get(`${clusterUrl}/${name}`) 47 | } 48 | 49 | export function deleteBackupFile(name) { 50 | return del(`${fileUrl}/${name}`) 51 | } 52 | 53 | export function getVeleroBackups(cluster) { 54 | return get(`${VeleroUrl}/${cluster}/backups`) 55 | } 56 | 57 | export function getVeleroBackupLogs(cluster,backup){ 58 | return get(`${VeleroUrl}/${cluster}/backup/logs?name=${backup}`) 59 | } 60 | 61 | export function createVeleroBackup(cluster, type, item) { 62 | return post(`${VeleroUrl}/${cluster}/${type}/create`,item) 63 | } 64 | 65 | export function deleteVeleroBackup(cluster, type, backup) { 66 | return del(`${VeleroUrl}/${cluster}/${type}/del?name=${backup}`) 67 | } 68 | 69 | export function getRestores(cluster){ 70 | return get(`${VeleroUrl}/${cluster}/restore`) 71 | } 72 | 73 | export function getVeleroRestoreDescribe(cluster,restore){ 74 | return get(`${VeleroUrl}/${cluster}/restore/describe?name=${restore}`) 75 | } 76 | 77 | export function getVeleroRestoreLogs(cluster,restore){ 78 | return get(`${VeleroUrl}/${cluster}/restore/logs?name=${restore}`) 79 | } 80 | 81 | export function delVeleroRestore(cluster,restore) { 82 | return del(`${VeleroUrl}/${cluster}/restore/del?name=${restore}`) 83 | } 84 | 85 | export function restore(cluster,item) { 86 | return post(`${VeleroUrl}/${cluster}/restore/create`,item) 87 | } 88 | 89 | export function veleroInstall(cluster,item) { 90 | return post(`${VeleroUrl}/${cluster}/velero/install/config`,item) 91 | } 92 | 93 | export function getVeleroConfig(cluster) { 94 | return get(`${VeleroUrl}/${cluster}/velero/install/config`) 95 | } 96 | 97 | export function veleroUninstall(cluster) { 98 | return del(`${VeleroUrl}/${cluster}/velero/uninstall`) 99 | } 100 | -------------------------------------------------------------------------------- /src/api/cluster/cluster.js: -------------------------------------------------------------------------------- 1 | import {get, post} from "@/plugins/request" 2 | 3 | const kubernetesUrl = "/api/v1/kubernetes/search" 4 | const kubernetesCreateUrl = "/api/v1/kubernetes/create" 5 | const kubernetesDeleteUrl = "/api/v1/kubernetes/delete" 6 | const metricUrl = "/api/v1/kubernetes/search/metric/{cluster_name}" 7 | 8 | export function listResource(data) { 9 | return post(kubernetesUrl, data) 10 | } 11 | 12 | export function deleteResoure(data) { 13 | return post(kubernetesDeleteUrl, data) 14 | } 15 | 16 | export function listNamespace(clusterName) { 17 | let search = { 18 | kind: "namespacelist", 19 | cluster: clusterName, 20 | continue: "", 21 | limit: 0, 22 | namespace: "", 23 | name: "", 24 | } 25 | return post(kubernetesUrl, search) 26 | } 27 | 28 | export function listDeployment(clusterName, namespace) { 29 | let search = { 30 | kind: "deploymentlist", 31 | cluster: clusterName, 32 | continue: "", 33 | limit: 0, 34 | namespace: namespace, 35 | name: "", 36 | } 37 | return post(kubernetesUrl, search) 38 | } 39 | 40 | export function listPod(clusterName, namespace) { 41 | let search = { 42 | kind: "podlist", 43 | cluster: clusterName, 44 | continue: "", 45 | limit: 0, 46 | namespace: namespace, 47 | name: "", 48 | } 49 | return post(kubernetesUrl, search) 50 | } 51 | 52 | export function listNode(clusterName) { 53 | let search = { 54 | kind: "nodelist", 55 | cluster: clusterName, 56 | continue: "", 57 | limit: 0, 58 | namespace: "", 59 | name: "", 60 | } 61 | return post(kubernetesUrl, search) 62 | } 63 | 64 | export function listStorageClass(clusterName) { 65 | let search = { 66 | kind: "storageclasslist", 67 | cluster: clusterName, 68 | continue: "", 69 | limit: 0, 70 | namespace: "", 71 | name: "", 72 | } 73 | return post(kubernetesUrl, search) 74 | } 75 | 76 | export function getMetric(clusterName) { 77 | return post(metricUrl.replace("{cluster_name}", clusterName)) 78 | } 79 | 80 | export function createStorageClass(data) { 81 | return post(kubernetesCreateUrl + "/sc", data) 82 | } 83 | 84 | export function createSecret(data) { 85 | return post(kubernetesCreateUrl + "/secret", data) 86 | } 87 | 88 | export function getClusterToken(clusterName) { 89 | return get(`/api/v1/clusters/webkubectl/${clusterName}?l=zh-CN`) 90 | } 91 | 92 | export function getDashboard(clusterName){ 93 | return get(`/api/v1/clusters/dashboard/${clusterName}?l=zh-CN`) 94 | } 95 | -------------------------------------------------------------------------------- /src/api/cluster/grade.js: -------------------------------------------------------------------------------- 1 | import {get} from "@/plugins/request" 2 | 3 | const baseUrl = "/api/v1/clusters/grade" 4 | 5 | export function getGrade(clusterName) { 6 | const gradeUrl = `${baseUrl}/${clusterName}` 7 | return get(gradeUrl) 8 | } 9 | -------------------------------------------------------------------------------- /src/api/cluster/log.js: -------------------------------------------------------------------------------- 1 | import {post} from "@/plugins/request" 2 | 3 | const efBaseUrl = "/proxy/logging/{cluster_name}/{index_name}/_search?pretty=true" 4 | const lokiBaseUrl = "/proxy/loki/{cluster_name}/" 5 | 6 | export function EfSearch(clusterName, queryArry, queryIndex, beginDate, endDate, pageFrom, pageSize) { 7 | const index = queryIndex 8 | const query = { 9 | from: (pageFrom - 1) * pageSize, 10 | size: pageSize, 11 | query: { 12 | bool: { 13 | must: queryArry, 14 | filter: { 15 | range: { 16 | "@timestamp": { 17 | gte: beginDate, 18 | lte: endDate, 19 | format: "yyyy.MM.dd", 20 | time_zone: "+08:00" 21 | } 22 | } 23 | } 24 | } 25 | }, 26 | sort: [ 27 | {"@timestamp": "desc"}, 28 | ] 29 | } 30 | return post(efBaseUrl.replace("{cluster_name}", clusterName).replace("{index_name}", index) + "&ignore_unavailable=true", query) 31 | } 32 | export function LokiLabels(clusterName) { 33 | return post(lokiBaseUrl.replace("{cluster_name}", clusterName) + "loki/api/v1/labels", "") 34 | } 35 | export function LokiLabelValues(clusterName, label) { 36 | return post(lokiBaseUrl.replace("{cluster_name}", clusterName) + "loki/api/v1/label/{label}/values".replace("{label}", label), "") 37 | } 38 | export function LokiSearch(clusterName, params) { 39 | return post(lokiBaseUrl.replace("{cluster_name}", clusterName) + "loki/api/v1/query_range?" + params, "") 40 | } 41 | -------------------------------------------------------------------------------- /src/api/cluster/monitor.js: -------------------------------------------------------------------------------- 1 | import {post} from "@/plugins/request" 2 | 3 | const baseUrl = "/api/v1/clusters/monitor/search/{cluster_name}" 4 | 5 | export function Monitor(clusterName, data) { 6 | return post(baseUrl.replace("{cluster_name}", clusterName), data) 7 | } 8 | -------------------------------------------------------------------------------- /src/api/cluster/node.js: -------------------------------------------------------------------------------- 1 | import {get, post} from "@/plugins/request" 2 | const kubernetesUrl = "/api/v1/kubernetes" 3 | 4 | 5 | const clusterUrl = "/api/v1/clusters" 6 | const baseUrl = "/api/v1/clusters/node/{clusterName}" 7 | const detailUrl = "/api/v1/clusters/node/detail/{clusterName}/{node}" 8 | const batchUrl = "/api/v1/clusters/node/batch/{clusterName}" 9 | const recreateUrl = "/api/v1/clusters/node/recreate/{clusterName}" 10 | 11 | export function cordonNode(data) { 12 | let headers = {"Content-Type": "application/strategic-merge-patch+json"} 13 | return post(kubernetesUrl + "/cordon", data, headers) 14 | } 15 | 16 | export function evictionNode(data) { 17 | return post(kubernetesUrl + "/evict", data) 18 | } 19 | 20 | export function listNodeInDB(clusterName) { 21 | return get(`/api/v1/clusters/node/${clusterName}`) 22 | } 23 | 24 | export function listNodesByPage(clusterName, pageNum, pageSize, isPolling) { 25 | return get(baseUrl.replace("{clusterName}", clusterName) + `?pageNum=${pageNum}&pageSize=${pageSize}&isPolling=${isPolling}`) 26 | } 27 | 28 | export function nodeBatchOperation(clusterName, data) { 29 | return post(batchUrl.replace("{clusterName}", clusterName), data) 30 | } 31 | 32 | export function getNodeStatus(clusterName, node) { 33 | return get(`${clusterUrl}/node/status/${clusterName}/${node}`) 34 | } 35 | 36 | export function getNodeByName(clusterName, node) { 37 | return get(detailUrl.replace("{clusterName}", clusterName).replace("{node}", node)) 38 | } 39 | 40 | export function nodeReCreate(clusterName, data) { 41 | return post(recreateUrl.replace("{clusterName}", clusterName), data) 42 | } -------------------------------------------------------------------------------- /src/api/cluster/security.js: -------------------------------------------------------------------------------- 1 | import {get, post, del} from "@/plugins/request" 2 | 3 | const url = "/api/v1/clusters/cis/{cluster_name}" 4 | const detailUrl = "/api/v1/clusters/cisdetail/{cluster_name}" 5 | const reportlUrl = "/api/v1/clusters/cisreport/{cluster_name}" 6 | 7 | export function listCisByPage(clusterName, page, size) { 8 | return get(url.replace("{cluster_name}", clusterName) + `?pageNum=${page}&pageSize=${size}`) 9 | } 10 | 11 | export function cisCreate(clusterName, data) { 12 | return post(url.replace("{cluster_name}", clusterName), data) 13 | } 14 | 15 | export function cisDelete(clusterName, id) { 16 | return del(url.replace("{cluster_name}", clusterName) + `/${id}`) 17 | } 18 | 19 | export function getCisDetail(clusterName, id) { 20 | return get(detailUrl.replace("{cluster_name}", clusterName) + `/${id}`) 21 | } 22 | 23 | export function getCisReport(clusterName, id, format) { 24 | let url = reportlUrl.replace("{cluster_name}", clusterName) + `/${id}` 25 | if (format) { 26 | url += `?format=${format}` 27 | } 28 | return window.open(url) 29 | } 30 | -------------------------------------------------------------------------------- /src/api/cluster/storage.js: -------------------------------------------------------------------------------- 1 | import {get, post} from "@/plugins/request" 2 | 3 | const provisionerUrl = "/api/v1/clusters/provisioner/{cluster_name}" 4 | 5 | export function listProvisioner(clusterName) { 6 | return get(provisionerUrl.replace("{cluster_name}", clusterName)) 7 | } 8 | 9 | export function createProvisioner(clusterName, item) { 10 | return post(provisionerUrl.replace("{cluster_name}", clusterName), item) 11 | } 12 | 13 | export function syncProvisioner(clusterName, hosts) { 14 | const syncUrl = "/api/v1/clusters/provisioner/sync/{cluster_name}" 15 | const url = syncUrl.replace("{cluster_name}", clusterName) 16 | return post(url, hosts) 17 | } 18 | 19 | export function deleteProvisioner(clusterName, item) { 20 | const deleteUrl = "/api/v1/clusters/provisioner/delete/{cluster_name}" 21 | const url = deleteUrl.replace("{cluster_name}", clusterName) 22 | return post(url, item) 23 | } -------------------------------------------------------------------------------- /src/api/cluster/tasks.js: -------------------------------------------------------------------------------- 1 | import {get, post} from "@/plugins/request" 2 | 3 | const taskUrl = "/api/v1/tasks" 4 | const taskLoggerUrl = "/api/v1/tasks" 5 | 6 | export function getTasks(page, size, cluster, logtype) { 7 | return post(`${taskUrl}?cluster=${cluster}&logtype=${logtype}&pageNum=${page}&pageSize=${size}`) 8 | } 9 | 10 | export function openLoggerWithID(clusterId, logId) { 11 | window.open(`/ui/#/logger?clusterId=${clusterId}&logId=${logId}`, "_blank", "height=865, width=800, top=0, left=0, toolbar=no, menubar=no, scrollbars=no, resizable=yes,location=no, status=no") 12 | } 13 | 14 | export function openLoggerWithName(clusterName, logId) { 15 | window.open(`/ui/#/logger?clusterName=${clusterName}&logId=${logId}`, "_blank", "height=865, width=800, top=0, left=0, toolbar=no, menubar=no, scrollbars=no, resizable=yes,location=no, status=no") 16 | } 17 | 18 | export function getLogById(clusterId, logId) { 19 | return get(`${taskLoggerUrl}/log1/${clusterId}/${logId}`) 20 | } 21 | 22 | export function getDetailById(logId) { 23 | return get(`${taskLoggerUrl}/detail/${logId}`) 24 | } 25 | 26 | export function getLogByName(clusterName, logId) { 27 | return get(`${taskLoggerUrl}/log2/${clusterName}/${logId}`) 28 | } 29 | 30 | export function getBackupLogs(page, size, clusterName) { 31 | return get(`${taskLoggerUrl}/backup/logs/${clusterName}?pageNum=${page}&pageSize=${size}`) 32 | } 33 | 34 | -------------------------------------------------------------------------------- /src/api/cluster/tool.js: -------------------------------------------------------------------------------- 1 | import {get, post} from "@/plugins/request" 2 | 3 | const toolUrl = "/api/v1/clusters/tool" 4 | 5 | export function listTool(clusterName) { 6 | return get(`${toolUrl}/${clusterName}`) 7 | } 8 | 9 | export function enableTool(clusterName, data) { 10 | return post(`${toolUrl}/enable/${clusterName}`, data) 11 | } 12 | 13 | export function disableTool(clusterName, data) { 14 | return post(`${toolUrl}/disable/${clusterName}`, data) 15 | } 16 | 17 | export function upgradeTool(clusterName, data) { 18 | return post(`${toolUrl}/upgrade/${clusterName}`, data) 19 | } 20 | 21 | export function getNodePort(clusterName, toolName) { 22 | return get(`${toolUrl}/port/${clusterName}/${toolName}`) 23 | } 24 | 25 | export function syncTool(clusterName, data) { 26 | return post(`${toolUrl}/sync/${clusterName}`, data) 27 | } 28 | 29 | export function getFlex(clusterName) { 30 | return get(`${toolUrl}/flex/${clusterName}`) 31 | } 32 | 33 | export function enableFlex(clusterName) { 34 | return post(`${toolUrl}/flex/enable/${clusterName}`) 35 | } 36 | 37 | export function disableFlex(clusterName) { 38 | return post(`${toolUrl}/flex/disable/${clusterName}`) 39 | } 40 | -------------------------------------------------------------------------------- /src/api/credentials.js: -------------------------------------------------------------------------------- 1 | import {get, patch, post, del} from "@/plugins/request" 2 | 3 | const settingUrl = "/api/v1/credentials" 4 | 5 | // Registry 6 | export function listCredentials(currentPage, pageSize) { 7 | return get(`${settingUrl}/?pageNum=${currentPage}&pageSize=${pageSize}`) 8 | } 9 | 10 | export function listCredentialAll() { 11 | return get(`${settingUrl}`) 12 | } 13 | 14 | export function searchCredential(currentPage, pageSize, conditions) { 15 | return post(`${settingUrl}/search?pageNum=${currentPage}&pageSize=${pageSize}`, conditions) 16 | } 17 | 18 | export function createCredentials(data) { 19 | return post(settingUrl,data) 20 | } 21 | 22 | export function updateCredentials(name, data) { 23 | return patch(`${settingUrl}/${name}`,data) 24 | } 25 | 26 | export function deleteCredentials(name) { 27 | return del(`${settingUrl}/${name}`) 28 | } 29 | 30 | export function listAllCredentials() { 31 | return get(`${settingUrl}`) 32 | } 33 | 34 | export function getCredentialByName(name) { 35 | return get(`${settingUrl}/${name}`) 36 | } 37 | 38 | -------------------------------------------------------------------------------- /src/api/hosts.js: -------------------------------------------------------------------------------- 1 | import {get, post, del, patch} from "@/plugins/request" 2 | 3 | const hostUrl = "/api/v1/hosts" 4 | 5 | export function createHost(data) { 6 | return post(hostUrl, data) 7 | } 8 | 9 | export function deleteHost(name) { 10 | return del(`${hostUrl}/${name}`) 11 | } 12 | 13 | export function listHosts(currentPage, pageSize) { 14 | return get(`${hostUrl}?pageNum=${currentPage}&pageSize=${pageSize}`) 15 | } 16 | 17 | export function searchHosts(currentPage, pageSize,condition) { 18 | return post(`${hostUrl}/search?pageNum=${currentPage}&pageSize=${pageSize}`,condition) 19 | } 20 | 21 | export function getHostByName(name) { 22 | return get(`${hostUrl}/${name}`) 23 | } 24 | 25 | export function updateHost(host) { 26 | return patch(`${hostUrl}/`, host) 27 | } 28 | 29 | export function syncHosts(hosts) { 30 | const itemUrl = `${hostUrl}/sync/` 31 | return post(itemUrl, hosts) 32 | } 33 | 34 | export function importHosts(file) { 35 | const itemUrl = `${hostUrl}/upload` 36 | return post(itemUrl, file) 37 | } 38 | 39 | export function batchHosts(data) { 40 | const itemUrl = `${hostUrl}/batch` 41 | return post(itemUrl, data) 42 | } -------------------------------------------------------------------------------- /src/api/ip-pool.js: -------------------------------------------------------------------------------- 1 | import {get, del, post, patch} from "@/plugins/request" 2 | 3 | const ipPoolUrl = "/api/v1/ippools" 4 | 5 | export function listIpPools (page, size) { 6 | return get(`${ipPoolUrl}?pageNum=${page}&pageSize=${size}`) 7 | } 8 | 9 | export function listAllIpPools () { 10 | return get(`${ipPoolUrl}`) 11 | } 12 | 13 | export function deleteIpPoolBy (name) { 14 | return del(`${ipPoolUrl}/${name}`) 15 | } 16 | 17 | export function createIpPool (data) { 18 | return post(ipPoolUrl, data) 19 | } 20 | 21 | export function searchIpPool (currentPage, pageSize, conditions) { 22 | return post(`${ipPoolUrl}/search?pageNum=${currentPage}&pageSize=${pageSize}`, conditions) 23 | 24 | } 25 | 26 | export function listIps (ipPoolName, page, size) { 27 | return get(`ippools/${ipPoolName}/ips?pageNum=${page}&pageSize=${size}`) 28 | } 29 | 30 | export function deleteIpBy (ipPoolName, ip) { 31 | return del(`${ipPoolUrl}/${ipPoolName}/ips/${ip}`) 32 | } 33 | 34 | export function createIp (ipPoolName, data) { 35 | return post(`${ipPoolUrl}/${ipPoolName}/ips`, data) 36 | } 37 | 38 | export function syncIp (ipPoolName) { 39 | return patch(`${ipPoolUrl}/${ipPoolName}/ips/sync`) 40 | } 41 | 42 | export function searchIp (currentPage, pageSize, ipPoolName, conditions) { 43 | return post(`${ipPoolUrl}/${ipPoolName}/ips/search?pageNum=${currentPage}&pageSize=${pageSize}`, conditions) 44 | } 45 | 46 | export function updateIp (ipPoolName, name, data) { 47 | return patch(`${ipPoolUrl}/${ipPoolName}/ips/${name}`,data) 48 | } 49 | -------------------------------------------------------------------------------- /src/api/license.js: -------------------------------------------------------------------------------- 1 | import {get, post} from "@/plugins/request" 2 | 3 | const licenseUrl = "/api/v1/license" 4 | 5 | export function getLicense() { 6 | return get(`${licenseUrl}`) 7 | } 8 | 9 | export function importLicense(data) { 10 | return post(licenseUrl,data) 11 | } 12 | -------------------------------------------------------------------------------- /src/api/manifest.js: -------------------------------------------------------------------------------- 1 | import {get, patch} from "@/plugins/request" 2 | 3 | const baseUrl = "/api/v1/manifests"; 4 | 5 | export function manifestGroup() { 6 | return get(`${baseUrl}/group`) 7 | } 8 | 9 | export function listActive() { 10 | return get(`${baseUrl}/active`); 11 | } 12 | 13 | export function changeStatus(versionName, manifest) { 14 | return patch(`${baseUrl}/${versionName}`, manifest) 15 | } 16 | -------------------------------------------------------------------------------- /src/api/msg-subscribe.js: -------------------------------------------------------------------------------- 1 | import {post, get} from "@/plugins/request" 2 | 3 | const subscribeUrl = "/api/v1/msg/subscribes" 4 | 5 | export function searchMsgSubscribe (currentPage, pageSize, type, resourceName, conditions) { 6 | return post(`${subscribeUrl}/search?type=${type}&resourceName=${resourceName}&pageNum=${currentPage}&pageSize=${pageSize}`, conditions) 7 | } 8 | 9 | export function updateMsgSubScribe (data) { 10 | return post(`${subscribeUrl}/update`, data) 11 | } 12 | 13 | export function addSubscribeUser (data) { 14 | return post(`${subscribeUrl}/user`, data) 15 | } 16 | 17 | export function deleteSubscribeUser (data) { 18 | return post(`${subscribeUrl}/delete/user`, data) 19 | } 20 | 21 | export function searchMsgSubscribeByName (type, resourceName, name) { 22 | return post(`${subscribeUrl}/search?type=${type}&resourceName=${resourceName}`, { 23 | quick: { 24 | field: "quick", 25 | value: name 26 | } 27 | }) 28 | } 29 | 30 | export function getAddSubscribeUsers (search, subscribeId, resourceName) { 31 | return get(`${subscribeUrl}/users?user=${search}&subscribeId=${subscribeId}&resourceName=${resourceName}`) 32 | } 33 | 34 | export function getSubScribeUsers (currentPage, pageSize, subscribeId, conditions) { 35 | return post(`${subscribeUrl}/users?subscribeId=${subscribeId}&pageNum=${currentPage}&pageSize=${pageSize}`, conditions) 36 | } 37 | -------------------------------------------------------------------------------- /src/api/personal-setting.js: -------------------------------------------------------------------------------- 1 | import {post} from "@/plugins/request" 2 | 3 | export function changePassword(data) { 4 | return post('/api/v1/users/change/password', data) 5 | } 6 | -------------------------------------------------------------------------------- /src/api/plan.js: -------------------------------------------------------------------------------- 1 | import {get, del, post, patch} from "@/plugins/request" 2 | 3 | const planUrl = "/api/v1/plans" 4 | 5 | export function listPlans (page, size) { 6 | return get(`${planUrl}?pageNum=${page}&pageSize=${size}`) 7 | } 8 | 9 | export function deletePlanBy (name) { 10 | return del(planUrl + "/" + name) 11 | } 12 | 13 | export function searchPlans (page, size, condition) { 14 | return post(`${planUrl}/search?pageNum=${page}&pageSize=${size}`, condition) 15 | } 16 | 17 | export function listVmConfigs (regionName) { 18 | return get(`${planUrl}/configs/${regionName}`) 19 | } 20 | 21 | export function createPlan (data) { 22 | return post(`${planUrl}`, data) 23 | } 24 | 25 | export function getPlanBy (name) { 26 | return get(`${planUrl}/${name}`) 27 | } 28 | 29 | export function updatePlanBy (name, data) { 30 | return patch(`${planUrl}/${name}`, data) 31 | } 32 | -------------------------------------------------------------------------------- /src/api/project-member.js: -------------------------------------------------------------------------------- 1 | import {del, get, post} from "@/plugins/request" 2 | 3 | const projectMemberUrl = (project_name) => { 4 | return `/api/v1/projects/${project_name}/members` 5 | } 6 | 7 | 8 | export function listProjectMembers(project_name, currentPage, pageSize) { 9 | return get(`${projectMemberUrl(project_name)}?pageNum=${currentPage}&pageSize=${pageSize}`) 10 | } 11 | 12 | export function createProjectMember(project_name, data) { 13 | return post(`${projectMemberUrl(project_name)}`, data) 14 | } 15 | 16 | export function deleteProjectMember(project_name, name) { 17 | return del(`${projectMemberUrl(project_name)}/${name}`) 18 | } 19 | 20 | export function updateProjectMember(project_name, name, data) { 21 | return get(`${projectMemberUrl(project_name)}/${name}`, data) 22 | } 23 | 24 | export function listUsers(project_name, name) { 25 | return get(`${projectMemberUrl(project_name)}/users?name=${name}`) 26 | } 27 | -------------------------------------------------------------------------------- /src/api/project-resource.js: -------------------------------------------------------------------------------- 1 | import {del, get, post} from "@/plugins/request" 2 | 3 | 4 | const projectResourceUrl = (project_name) => { 5 | return `/api/v1/projects/${project_name}/resources` 6 | } 7 | 8 | export function listProjectResources (project_name, resourceType, currentPage, pageSize) { 9 | return get(`${projectResourceUrl(project_name)}?pageNum=${currentPage}&pageSize=${pageSize}&resourceType=${resourceType}`) 10 | } 11 | 12 | export function listProjectResourcesAll (project_name, resourceType) { 13 | return get(`${projectResourceUrl(project_name)}?resourceType=${resourceType}`) 14 | } 15 | 16 | export function getResourceList (project_name, resourceType) { 17 | return get(`${projectResourceUrl(project_name)}/list?resourceType=${resourceType}`) 18 | } 19 | 20 | export function createProjectResource (project_name, data) { 21 | return post(`${projectResourceUrl(project_name)}`, data) 22 | } 23 | 24 | export function deleteProjectResource (project_name, name,resourceType) { 25 | return del(`${projectResourceUrl(project_name)}/${name}?resourceType=${resourceType}`) 26 | } 27 | -------------------------------------------------------------------------------- /src/api/projects.js: -------------------------------------------------------------------------------- 1 | import {get, post, del, patch} from "@/plugins/request" 2 | 3 | const projectUrl = "/api/v1/projects" 4 | 5 | export function createProject (data) { 6 | return post(projectUrl, data) 7 | } 8 | 9 | export function listProjects (currentPage, pageSize) { 10 | return get(`${projectUrl}?pageNum=${currentPage}&pageSize=${pageSize}`) 11 | } 12 | 13 | export function getProjectsHasClusters() { 14 | return get(`${projectUrl}/clusters`) 15 | } 16 | 17 | export function getProject (name) { 18 | return get(`${projectUrl}/${name}`) 19 | } 20 | 21 | export function updateProject (name, data) { 22 | return patch(`${projectUrl}/${name}`, data) 23 | } 24 | 25 | export function deleteProject (name) { 26 | return del(`${projectUrl}/${name}`) 27 | } 28 | 29 | export function allProjects () { 30 | return get(`${projectUrl}`) 31 | } 32 | 33 | export function searchProject (currentPage, pageSize, condition) { 34 | return post(`${projectUrl}/search?pageNum=${currentPage}&pageSize=${pageSize}`, condition) 35 | } 36 | -------------------------------------------------------------------------------- /src/api/region.js: -------------------------------------------------------------------------------- 1 | import {get, del, post, patch} from "@/plugins/request" 2 | 3 | const regionUrl = "/api/v1/regions" 4 | 5 | export function listRegions (page, size) { 6 | return get(`${regionUrl}/search?pageNum=${page}&pageSize=${size}`) 7 | } 8 | 9 | export function deleteRegionBy (name) { 10 | return del(regionUrl + "/" + name) 11 | } 12 | 13 | export function searchRegion (page, size, condition) { 14 | return post(`${regionUrl}/search?pageNum=${page}&pageSize=${size}`, condition) 15 | } 16 | 17 | export function listAllRegions () { 18 | return get(`${regionUrl}`) 19 | } 20 | 21 | export function listDatacenter (data) { 22 | return post(`${regionUrl}/datacenter`, data) 23 | } 24 | 25 | export function createRegion (data) { 26 | return post(`${regionUrl}`, data) 27 | } 28 | 29 | export function getRegionBy (name) { 30 | return get(`${regionUrl}/${name}`) 31 | } 32 | 33 | export function updateRegion (name, data) { 34 | return patch(`${regionUrl}/${name}`, data) 35 | } 36 | -------------------------------------------------------------------------------- /src/api/system-log.js: -------------------------------------------------------------------------------- 1 | import {post} from "@/plugins/request" 2 | 3 | export function systemQuery(page, size, conditions) { 4 | return post(`/api/v1/logs?pageNum=${page}&pageSize=${size}`, conditions) 5 | } 6 | -------------------------------------------------------------------------------- /src/api/system-setting.js: -------------------------------------------------------------------------------- 1 | import {get, patch, post, del} from "@/plugins/request" 2 | 3 | const settingUrl = "/api/v1/settings" 4 | const ntpUrl = "/api/v1/ntp" 5 | const messageUrl = "/api/v1/message/setting" 6 | const msgUrl = "/api/v1/msg" 7 | 8 | //msg 9 | export function getMsgAccount (name) { 10 | return get(`${msgUrl}/accounts/${name}`) 11 | } 12 | 13 | export function createMsgAccount (data) { 14 | return post(`${msgUrl}/accounts`, data) 15 | } 16 | export function verifyMsgAccount (data) { 17 | return post(`${msgUrl}/accounts/verify`, data) 18 | } 19 | 20 | // Settings 21 | export function getSetting (tabName) { 22 | return get(`${settingUrl}/${tabName}`) 23 | } 24 | 25 | export function check (tabName, data) { 26 | return post(`${settingUrl}/check/${tabName}`, data) 27 | } 28 | 29 | export function createSetting (data) { 30 | return post(settingUrl, data) 31 | } 32 | 33 | // Registry 34 | export function listRegistry (currentPage, pageSize) { 35 | return get(`${settingUrl}/registry?pageNum=${currentPage}&pageSize=${pageSize}`) 36 | } 37 | 38 | export function listRegistryAll () { 39 | return get(`${settingUrl}/registry`) 40 | } 41 | 42 | export function createRegistry (data) { 43 | return post(`${settingUrl}/registry`, data) 44 | } 45 | 46 | export function changePassword (data) { 47 | return post(`${settingUrl}/registry/change/password`, data) 48 | } 49 | 50 | export function updateRegistry (arch, data) { 51 | return patch(`${settingUrl}/registry/${arch}`, data) 52 | } 53 | 54 | export function testConnection (data) { 55 | return post(`${settingUrl}/registry/check/conn`, data) 56 | } 57 | 58 | export function searchRegistry (currentPage, pageSize, conditions) { 59 | return post(`${settingUrl}/registry/search?pageNum=${currentPage}&pageSize=${pageSize}`, conditions) 60 | } 61 | 62 | export function getRegistry (id) { 63 | return get(`${settingUrl}/registry/${id}`) 64 | } 65 | 66 | export function deleteRegistry (id) { 67 | return del(`${settingUrl}/registry/${id}`) 68 | } 69 | 70 | // ntp 71 | export function searchNtp (currentPage, pageSize) { 72 | return get(`${ntpUrl}/?pageNum=${currentPage}&pageSize=${pageSize}`) 73 | } 74 | 75 | export function deleteNtp (name) { 76 | return del(`${ntpUrl}/${name}`) 77 | } 78 | 79 | export function createNtp (data) { 80 | return post(`${ntpUrl}`, data) 81 | } 82 | 83 | export function updateNtp (name, data) { 84 | return patch(`${ntpUrl}/${name}`, data) 85 | } 86 | 87 | // Message 88 | export function getMessageSetting (tabName) { 89 | return get(`${messageUrl}/${tabName}`) 90 | } 91 | 92 | export function checkMessage (tabName, data) { 93 | return post(`${messageUrl}/check/${tabName}`, data) 94 | } 95 | 96 | export function createMessageSetting (tabName, data) { 97 | return post(`${messageUrl}/${tabName}`, data) 98 | } 99 | 100 | // LDAP 101 | export function sync () { 102 | return get(`/api/v1/ldap/sync`) 103 | } 104 | 105 | export function createLDAP (data) { 106 | return post(`/api/v1/ldap/`, data) 107 | } 108 | 109 | export function testConnect (data) { 110 | return post(`/api/v1/ldap/test/connect`, data) 111 | } 112 | 113 | export function testLogin (data) { 114 | return post(`/api/v1/ldap/test/login`, data) 115 | } 116 | 117 | export function importUsers (data) { 118 | return post(`/api/v1/ldap/import/users`, data) 119 | } 120 | 121 | // kubepi 122 | export function bindUser (data) { 123 | return post(`/api/v1/dashboard/bind`, data) 124 | } 125 | 126 | export function getUser (data) { 127 | return get(`/api/v1/dashboard/user`, data) 128 | } 129 | 130 | export function getBindInfo (data) { 131 | return post(`/api/v1/dashboard/search`, data) 132 | } 133 | 134 | export function testKubepiConn (data) { 135 | return post(`/api/v1/dashboard/check/conn`, data) 136 | } 137 | 138 | export function jumpTo (project, cluster) { 139 | return get(`/api/v1/dashboard/jump/${project}/${cluster}`) 140 | } 141 | -------------------------------------------------------------------------------- /src/api/template-config.js: -------------------------------------------------------------------------------- 1 | import {del, get, patch, post} from "@/plugins/request" 2 | 3 | const templateUrl = "/api/v1/templates" 4 | 5 | export function searchTemplates (page, size, condition) { 6 | return post(`${templateUrl}/search?pageNum=${page}&pageSize=${size}`, condition) 7 | } 8 | 9 | export function createTemplate (data) { 10 | return post(`${templateUrl}/create`, data) 11 | } 12 | 13 | export function getTemplate(name) { 14 | return get(`${templateUrl}/${name}`) 15 | } 16 | 17 | export function delTemplate(name) { 18 | return del(`${templateUrl}/${name}`) 19 | } 20 | 21 | export function updateTemplate(name,data) { 22 | return patch(`${templateUrl}/${name}`, data) 23 | } 24 | 25 | export function listTemplateConfigs() { 26 | return get(`${templateUrl}`) 27 | } 28 | -------------------------------------------------------------------------------- /src/api/theme.js: -------------------------------------------------------------------------------- 1 | import {get,post} from "@/plugins/request" 2 | 3 | const userUrl = "/api/v1/theme" 4 | 5 | export function getTheme() { 6 | return get(userUrl) 7 | } 8 | 9 | export function importTheme(data) { 10 | return post(userUrl,data) 11 | } 12 | -------------------------------------------------------------------------------- /src/api/user-management.js: -------------------------------------------------------------------------------- 1 | import {get} from "@/plugins/request"; 2 | 3 | export function listUsers(page, size) { 4 | return get(`/api/v1/samples/user-management/list/${page}/${size}`) 5 | } 6 | -------------------------------------------------------------------------------- /src/api/user-message.js: -------------------------------------------------------------------------------- 1 | import {get, post} from "@/plugins/request" 2 | 3 | export function listUserMessages (currentPage, pageSize) { 4 | return get(`/api/v1/user/messages?pageNum=${currentPage}&pageSize=${pageSize}`) 5 | } 6 | 7 | export function readUserMessage (id) { 8 | return post(`/api/v1/user/messages/read/${id}`, {}) 9 | } 10 | 11 | export function markAllRead() { 12 | return post(`/api/v1/user/messages/read/all`, {}) 13 | } 14 | -------------------------------------------------------------------------------- /src/api/user-token.js: -------------------------------------------------------------------------------- 1 | /* 前后端分离的登录方式 */ 2 | import {get, post, put} from "@/plugins/request" 3 | 4 | export function login(data) { 5 | return post("/api/v1/samples/user-token/login", data) 6 | } 7 | 8 | export function logout() { 9 | return post("/samples/user-token/logout") 10 | } 11 | 12 | export function getCurrentUser() { 13 | return get("/samples/user-token/current") 14 | } 15 | 16 | export function updateInfo(data) { 17 | return put("/samples/user-token/update", data) 18 | } 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/api/user.js: -------------------------------------------------------------------------------- 1 | import {get, patch, post, del} from "@/plugins/request" 2 | 3 | const userUrl = "/api/v1/users" 4 | const userSettingUrl = "/api/v1/user/settings" 5 | 6 | 7 | export function updateUser (name, data) { 8 | return patch(`${userUrl}/${name}`, data) 9 | } 10 | 11 | export function listUsers (currentPage, pageSize) { 12 | return get(`${userUrl}?pageNum=${currentPage}&pageSize=${pageSize}`) 13 | } 14 | 15 | export function createUser (data) { 16 | return post(userUrl, data) 17 | } 18 | 19 | export function searchUsers (currentPage, pageSize, conditions) { 20 | return post(`${userUrl}/search?pageNum=${currentPage}&pageSize=${pageSize}`, conditions) 21 | } 22 | 23 | export function getUser (name) { 24 | return get(`${userUrl}/${name}`) 25 | } 26 | 27 | export function deleteUser (name) { 28 | return del(`${userUrl}/${name}`) 29 | } 30 | 31 | export function getUserSetting (name) { 32 | return get(`${userSettingUrl}/${name}`) 33 | } 34 | 35 | export function updateUserSetting (data) { 36 | return post(`${userSettingUrl}/update`, data) 37 | } 38 | 39 | export function searchUsersByName (name) { 40 | return post(`${userUrl}/search?`, {quick: {field: "quick",value: name} }) 41 | } 42 | -------------------------------------------------------------------------------- /src/api/vm-config.js: -------------------------------------------------------------------------------- 1 | import {get, del, post, patch} from "@/plugins/request" 2 | 3 | const vmConfigUrl = "/api/v1/vmconfigs" 4 | 5 | export function listVmConfigs (page, size) { 6 | return get(`${vmConfigUrl}/search?pageNum=${page}&pageSize=${size}`) 7 | } 8 | 9 | export function deleteVmConfigBy (name) { 10 | return del(vmConfigUrl + "/" + name) 11 | } 12 | 13 | export function createVmConfig (data) { 14 | return post(vmConfigUrl, data) 15 | } 16 | 17 | export function getVmConfig (name) { 18 | return get(`${vmConfigUrl}/${name}`) 19 | } 20 | 21 | 22 | export function updateVmConfig (name, data) { 23 | return patch(`${vmConfigUrl}/${name}`, data) 24 | } 25 | 26 | export function searchVmConfigs (page, size, conditions) { 27 | return post(`${vmConfigUrl}/search?pageNum=${page}&pageSize=${size}`, conditions) 28 | } 29 | -------------------------------------------------------------------------------- /src/api/xpack/multi-cluster.js: -------------------------------------------------------------------------------- 1 | import {get, post, del, patch} from "@/plugins/request" 2 | 3 | const baseUrl = "/api/v1/multicluster" 4 | 5 | export function listMultiClusterRepositories(currentPage, pageSize) { 6 | return get(`${baseUrl}/repositories?pageNum=${currentPage}&pageSize=${pageSize}`,) 7 | } 8 | 9 | 10 | export function getMultiClusterRepository(name) { 11 | return get(`${baseUrl}/repositories/${name}`,) 12 | } 13 | 14 | export function createMultiClusterRepository(data) { 15 | return post(`${baseUrl}/repositories`, data) 16 | } 17 | 18 | export function deleteMultiClusterRepository(name) { 19 | return del(`${baseUrl}/repositories/${name}`) 20 | } 21 | 22 | export function updateMultiClusterRepository(name, data) { 23 | return patch(`${baseUrl}/repositories/${name}`, data) 24 | } 25 | 26 | 27 | export function listMultiClusterRepositoryRelations(name) { 28 | return get(`${baseUrl}/repositories/relations/${name}`,) 29 | } 30 | 31 | export function updateMultiClusterRepositoryRelations(name, data) { 32 | return post(`${baseUrl}/repositories/relations/${name}`, data) 33 | } 34 | 35 | export function getMultiClusterSyncLogs(name, currentPage, pageSize) { 36 | return get(`${baseUrl}/repositories/logs/${name}?pageNum=${currentPage}&pageSize=${pageSize}`,) 37 | } 38 | 39 | export function getMultiClusterSyncLogsDetail(name, logId) { 40 | return get(`${baseUrl}/repositories/logs/detail/${name}/${logId}`,) 41 | } 42 | -------------------------------------------------------------------------------- /src/api/zone.js: -------------------------------------------------------------------------------- 1 | import {get, del, post, patch} from "@/plugins/request" 2 | 3 | const zoneUrl = "/api/v1/zones" 4 | 5 | export function listZones (page, size) { 6 | return get(`${zoneUrl}?pageNum=${page}&pageSize=${size}`) 7 | } 8 | 9 | export function deleteZoneBy (name) { 10 | return del(zoneUrl + "/" + name) 11 | } 12 | 13 | export function searchZone (page, size, condition) { 14 | return post(`${zoneUrl}/search?pageNum=${page}&pageSize=${size}`, condition) 15 | } 16 | 17 | export function listCloudZones (data) { 18 | return post(`${zoneUrl}/clusters`, data) 19 | } 20 | 21 | export function listDatastores (data) { 22 | return post(`${zoneUrl}/datastores`, data) 23 | } 24 | 25 | export function listFolders (data) { 26 | return post(`${zoneUrl}/folders`, data) 27 | } 28 | 29 | export function listTemplates (data) { 30 | return post(`${zoneUrl}/templates`, data) 31 | } 32 | 33 | export function createZone (data) { 34 | return post(`${zoneUrl}`, data) 35 | } 36 | 37 | export function listAllZones () { 38 | return get(`${zoneUrl}`) 39 | } 40 | 41 | export function listByRegion(regionName) { 42 | return get(`${zoneUrl}/list/${regionName}`) 43 | } 44 | 45 | export function getZone(name) { 46 | return get(`${zoneUrl}/${name}`) 47 | } 48 | 49 | export function updateZone(name,data) { 50 | return patch(`${zoneUrl}/${name}`, data) 51 | } 52 | 53 | export function uploadImage(data){ 54 | return post(`${zoneUrl}/upload/image`, data) 55 | } 56 | -------------------------------------------------------------------------------- /src/assets/KobeOperator-login.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KubeOperator/neeko/028abed4e888dbef472d83c6a33aaea4bb6050ed/src/assets/KobeOperator-login.jpg -------------------------------------------------------------------------------- /src/assets/KubeOperator-about-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KubeOperator/neeko/028abed4e888dbef472d83c6a33aaea4bb6050ed/src/assets/KubeOperator-about-background.png -------------------------------------------------------------------------------- /src/assets/KubeOperator-assist-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KubeOperator/neeko/028abed4e888dbef472d83c6a33aaea4bb6050ed/src/assets/KubeOperator-assist-white.png -------------------------------------------------------------------------------- /src/assets/KubeOperator-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KubeOperator/neeko/028abed4e888dbef472d83c6a33aaea4bb6050ed/src/assets/KubeOperator-black.png -------------------------------------------------------------------------------- /src/assets/KubeOperator-red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KubeOperator/neeko/028abed4e888dbef472d83c6a33aaea4bb6050ed/src/assets/KubeOperator-red.png -------------------------------------------------------------------------------- /src/assets/KubeOperator-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KubeOperator/neeko/028abed4e888dbef472d83c6a33aaea4bb6050ed/src/assets/KubeOperator-white.png -------------------------------------------------------------------------------- /src/assets/font/Roboto/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KubeOperator/neeko/028abed4e888dbef472d83c6a33aaea4bb6050ed/src/assets/font/Roboto/Roboto-Regular.ttf -------------------------------------------------------------------------------- /src/assets/font/Roboto/index.css: -------------------------------------------------------------------------------- 1 | /* cyrillic-ext */ 2 | @font-face { 3 | font-family: 'Roboto'; 4 | font-style: normal; 5 | font-display: swap; 6 | src: url(Roboto-Regular.ttf) format('truetype'); 7 | unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; 8 | } 9 | /* cyrillic */ 10 | @font-face { 11 | font-family: 'Roboto'; 12 | font-style: normal; 13 | font-display: swap; 14 | src: url(Roboto-Regular.ttf) format('truetype'); 15 | unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; 16 | } 17 | /* greek-ext */ 18 | @font-face { 19 | font-family: 'Roboto'; 20 | font-style: normal; 21 | font-display: swap; 22 | src: url(Roboto-Regular.ttf) format('truetype'); 23 | unicode-range: U+1F00-1FFF; 24 | } 25 | /* greek */ 26 | @font-face { 27 | font-family: 'Roboto'; 28 | font-style: normal; 29 | font-display: swap; 30 | src: url(Roboto-Regular.ttf) format('truetype'); 31 | unicode-range: U+0370-03FF; 32 | } 33 | /* vietnamese */ 34 | @font-face { 35 | font-family: 'Roboto'; 36 | font-style: normal; 37 | font-display: swap; 38 | src: url(Roboto-Regular.ttf) format('truetype'); 39 | unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB; 40 | } 41 | /* latin-ext */ 42 | @font-face { 43 | font-family: 'Roboto'; 44 | font-style: normal; 45 | font-display: swap; 46 | src: url(Roboto-Regular.ttf) format('truetype'); 47 | unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; 48 | } 49 | /* latin */ 50 | @font-face { 51 | font-family: 'Roboto'; 52 | font-style: normal; 53 | font-display: swap; 54 | src: url(Roboto-Regular.ttf) format('truetype'); 55 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; 56 | } 57 | 58 | -------------------------------------------------------------------------------- /src/assets/iconfont/alicdn/font_985780_km7mi63cihi.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KubeOperator/neeko/028abed4e888dbef472d83c6a33aaea4bb6050ed/src/assets/iconfont/alicdn/font_985780_km7mi63cihi.eot -------------------------------------------------------------------------------- /src/assets/iconfont/alicdn/font_985780_km7mi63cihi.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Created by iconfont 9 | 10 | 11 | 12 | 13 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/assets/iconfont/alicdn/font_985780_km7mi63cihi.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KubeOperator/neeko/028abed4e888dbef472d83c6a33aaea4bb6050ed/src/assets/iconfont/alicdn/font_985780_km7mi63cihi.ttf -------------------------------------------------------------------------------- /src/assets/iconfont/alicdn/font_985780_km7mi63cihi.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KubeOperator/neeko/028abed4e888dbef472d83c6a33aaea4bb6050ed/src/assets/iconfont/alicdn/font_985780_km7mi63cihi.woff -------------------------------------------------------------------------------- /src/assets/iconfont/alicdn/font_985780_km7mi63cihi_iefix.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KubeOperator/neeko/028abed4e888dbef472d83c6a33aaea4bb6050ed/src/assets/iconfont/alicdn/font_985780_km7mi63cihi_iefix.eot -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KubeOperator/neeko/028abed4e888dbef472d83c6a33aaea4bb6050ed/src/assets/iconfont/iconfont.ttf -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KubeOperator/neeko/028abed4e888dbef472d83c6a33aaea4bb6050ed/src/assets/iconfont/iconfont.woff -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KubeOperator/neeko/028abed4e888dbef472d83c6a33aaea4bb6050ed/src/assets/iconfont/iconfont.woff2 -------------------------------------------------------------------------------- /src/assets/images/tools/chartmuseum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KubeOperator/neeko/028abed4e888dbef472d83c6a33aaea4bb6050ed/src/assets/images/tools/chartmuseum.png -------------------------------------------------------------------------------- /src/assets/images/tools/docker-registry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KubeOperator/neeko/028abed4e888dbef472d83c6a33aaea4bb6050ed/src/assets/images/tools/docker-registry.png -------------------------------------------------------------------------------- /src/assets/images/tools/elasticsearch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KubeOperator/neeko/028abed4e888dbef472d83c6a33aaea4bb6050ed/src/assets/images/tools/elasticsearch.png -------------------------------------------------------------------------------- /src/assets/images/tools/gatekeeper.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KubeOperator/neeko/028abed4e888dbef472d83c6a33aaea4bb6050ed/src/assets/images/tools/gatekeeper.jpg -------------------------------------------------------------------------------- /src/assets/images/tools/grafana.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KubeOperator/neeko/028abed4e888dbef472d83c6a33aaea4bb6050ed/src/assets/images/tools/grafana.png -------------------------------------------------------------------------------- /src/assets/images/tools/kubeapps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KubeOperator/neeko/028abed4e888dbef472d83c6a33aaea4bb6050ed/src/assets/images/tools/kubeapps.png -------------------------------------------------------------------------------- /src/assets/images/tools/kubepi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KubeOperator/neeko/028abed4e888dbef472d83c6a33aaea4bb6050ed/src/assets/images/tools/kubepi.png -------------------------------------------------------------------------------- /src/assets/images/tools/kubernetes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KubeOperator/neeko/028abed4e888dbef472d83c6a33aaea4bb6050ed/src/assets/images/tools/kubernetes.png -------------------------------------------------------------------------------- /src/assets/images/tools/loki.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KubeOperator/neeko/028abed4e888dbef472d83c6a33aaea4bb6050ed/src/assets/images/tools/loki.png -------------------------------------------------------------------------------- /src/assets/images/tools/prometheus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KubeOperator/neeko/028abed4e888dbef472d83c6a33aaea4bb6050ed/src/assets/images/tools/prometheus.png -------------------------------------------------------------------------------- /src/assets/images/tools/registry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KubeOperator/neeko/028abed4e888dbef472d83c6a33aaea4bb6050ed/src/assets/images/tools/registry.png -------------------------------------------------------------------------------- /src/assets/login-desc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KubeOperator/neeko/028abed4e888dbef472d83c6a33aaea4bb6050ed/src/assets/login-desc.png -------------------------------------------------------------------------------- /src/business/app-layout/header-components/Help.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 36 | 37 | 42 | -------------------------------------------------------------------------------- /src/business/app-layout/header-components/LanguageSwitch.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 59 | 60 | 80 | -------------------------------------------------------------------------------- /src/business/app-layout/header-components/ProjectSwitch.vue: -------------------------------------------------------------------------------- 1 | 16 | 48 | 49 | 54 | -------------------------------------------------------------------------------- /src/business/app-layout/horizontal-layout/HorizontalHeader.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 39 | 40 | 72 | -------------------------------------------------------------------------------- /src/business/app-layout/horizontal-layout/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 19 | -------------------------------------------------------------------------------- /src/business/authorization/projects/create/index.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 83 | 84 | 87 | -------------------------------------------------------------------------------- /src/business/authorization/projects/edit/index.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 73 | 74 | 77 | -------------------------------------------------------------------------------- /src/business/automatic/vm-configs/create/index.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 95 | 96 | 99 | -------------------------------------------------------------------------------- /src/business/automatic/vm-configs/edit/index.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 91 | 92 | 95 | -------------------------------------------------------------------------------- /src/business/clusters/detail/backup/logs/index.vue: -------------------------------------------------------------------------------- 1 | 52 | 53 | 88 | 89 | -------------------------------------------------------------------------------- /src/business/clusters/detail/index.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 67 | 68 | 70 | -------------------------------------------------------------------------------- /src/business/clusters/detail/log/index.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 61 | 62 | -------------------------------------------------------------------------------- /src/business/clusters/detail/node/detail/index.vue: -------------------------------------------------------------------------------- 1 | 81 | 82 | 90 | 91 | -------------------------------------------------------------------------------- /src/business/dashboard/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 16 | -------------------------------------------------------------------------------- /src/business/directive/ClickOutsideDemo.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 31 | 32 | 35 | -------------------------------------------------------------------------------- /src/business/directive/PermissionDemo.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 32 | 33 | 53 | -------------------------------------------------------------------------------- /src/business/system-log/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 77 | 78 | -------------------------------------------------------------------------------- /src/business/system-setting/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 39 | 40 | 42 | -------------------------------------------------------------------------------- /src/business/system-setting/kubepi/index.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 95 | 96 | 98 | -------------------------------------------------------------------------------- /src/business/xpack/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | 13 | -------------------------------------------------------------------------------- /src/business/xpack/multi-cluster/dialog/RelationsManagement.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 85 | -------------------------------------------------------------------------------- /src/business/xpack/multi-cluster/dialog/RepositoryErrorMessage.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | 45 | -------------------------------------------------------------------------------- /src/business/xpack/multi-cluster/dialog/SyncLogDetail.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 31 | 71 | -------------------------------------------------------------------------------- /src/business/xpack/multi-cluster/edit/index.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 72 | 73 | 75 | -------------------------------------------------------------------------------- /src/business/xterm/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | -------------------------------------------------------------------------------- /src/components/back-button/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 29 | 30 | 44 | -------------------------------------------------------------------------------- /src/components/batch-delete/index.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | -------------------------------------------------------------------------------- /src/components/cloud-providers/index.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 25 | -------------------------------------------------------------------------------- /src/components/complex-table/index.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 81 | 82 | 108 | -------------------------------------------------------------------------------- /src/components/detail-card/ItemValue.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 41 | -------------------------------------------------------------------------------- /src/components/detail-card/index.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 48 | 49 | 55 | -------------------------------------------------------------------------------- /src/components/k8s-page/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 57 | 58 | -------------------------------------------------------------------------------- /src/components/ko-status/index.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 62 | 63 | 65 | -------------------------------------------------------------------------------- /src/components/layout/LayoutContent.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 40 | 41 | 67 | -------------------------------------------------------------------------------- /src/components/layout/LayoutHeader.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 15 | -------------------------------------------------------------------------------- /src/components/layout/LayoutMain.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | 17 | -------------------------------------------------------------------------------- /src/components/layout/LayoutSidebar.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 26 | -------------------------------------------------------------------------------- /src/components/layout/LayoutView.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 33 | -------------------------------------------------------------------------------- /src/components/layout/index.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 26 | 27 | 67 | -------------------------------------------------------------------------------- /src/components/layout/sidebar/FixiOSBug.js: -------------------------------------------------------------------------------- 1 | export default { 2 | computed: { 3 | device() { 4 | return this.$store.state.app.device 5 | } 6 | }, 7 | mounted() { 8 | // In order to fix the click on menu on the ios device will trigger the mouseleave bug 9 | // https://github.com/PanJiaChen/vue-element-admin/issues/1135 10 | this.fixBugIniOS() 11 | }, 12 | methods: { 13 | fixBugIniOS() { 14 | const $subMenu = this.$refs.subMenu 15 | if ($subMenu) { 16 | const handleMouseleave = $subMenu.handleMouseleave 17 | $subMenu.handleMouseleave = (e) => { 18 | if (this.device === 'mobile') { 19 | return 20 | } 21 | handleMouseleave(e) 22 | } 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/components/layout/sidebar/Item.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 38 | -------------------------------------------------------------------------------- /src/components/layout/sidebar/Link.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 44 | -------------------------------------------------------------------------------- /src/components/layout/sidebar/Logo.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 46 | 47 | 104 | -------------------------------------------------------------------------------- /src/components/layout/sidebar/SidebarItem.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 98 | -------------------------------------------------------------------------------- /src/components/layout/sidebar/SidebarToggleButton.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 25 | 26 | 34 | -------------------------------------------------------------------------------- /src/components/redirect/index.vue: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /src/directive/click-outside/index.js: -------------------------------------------------------------------------------- 1 | import ClickOutside from "element-ui/src/utils/clickoutside"; 2 | 3 | const install = function (Vue) { 4 | Vue.directive("click-outside", ClickOutside) 5 | } 6 | 7 | ClickOutside.install = install 8 | export default ClickOutside 9 | -------------------------------------------------------------------------------- /src/directive/index.js: -------------------------------------------------------------------------------- 1 | import ClickOutside from "element-ui/src/utils/clickoutside"; 2 | import permission from "./permission"; 3 | 4 | export default { 5 | install(Vue) { 6 | Vue.directive('click-outside', ClickOutside); 7 | Vue.directive('permission', permission); 8 | } 9 | } 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/directive/permission/index.js: -------------------------------------------------------------------------------- 1 | import store from '@/store' 2 | 3 | function checkPermission(el, binding) { 4 | const {value} = binding 5 | const roles = store.getters && store.getters.roles 6 | 7 | if (value && value instanceof Array) { 8 | if (value.length > 0) { 9 | const permissionRoles = value 10 | 11 | const hasPermission = roles.some(role => { 12 | return permissionRoles.includes(role) 13 | }) 14 | 15 | if (!hasPermission) { 16 | el.parentNode && el.parentNode.removeChild(el) 17 | } 18 | } 19 | } 20 | } 21 | 22 | export default { 23 | inserted(el, binding) { 24 | checkPermission(el, binding) 25 | }, 26 | update(el, binding) { 27 | checkPermission(el, binding) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/filters/index.js: -------------------------------------------------------------------------------- 1 | import {dateFormat, datetimeFormat} from "fit2cloud-ui/src/filters/time"; 2 | 3 | export function errorFormat(value) { 4 | if (value !== null) { 5 | let errItem = value; 6 | errItem = errItem.replace(/\\n/gi,'\n'); 7 | errItem = errItem.replace(/\\u/gi,'%u'); 8 | errItem = errItem.replace(/\\/gi,''); 9 | errItem = unescape(errItem) 10 | return errItem 11 | } 12 | return value 13 | } 14 | 15 | export function emailFormat(value) { 16 | let result = ''; 17 | if (value.indexOf('@') === -1 || value.indexOf('.') === -1) { 18 | return value 19 | } 20 | const aiteIndex = value.indexOf('@') 21 | const pointIndex = value.lastIndexOf('.') 22 | const mail = value.substring(0, aiteIndex) 23 | if (mail.length <= 3) { 24 | result += '***' 25 | } else { 26 | result += value.substring(0, 3) + '***' 27 | } 28 | result += value.substring(pointIndex+1, value.length) 29 | return result; 30 | } 31 | 32 | export function timeStampFormat(value) { 33 | return datetimeFormat(new Date(value * 1000)) 34 | } 35 | 36 | const filters = { 37 | "dateFormat": dateFormat, 38 | "datetimeFormat": datetimeFormat, 39 | "timeStampFormat": timeStampFormat, 40 | "errorFormat": errorFormat, 41 | "emailFormat": emailFormat, 42 | }; 43 | 44 | export default { 45 | install(Vue) { 46 | Object.keys(filters).forEach(key => { 47 | Vue.filter(key, filters[key]) 48 | }); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/i18n/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import VueI18n from "vue-i18n"; 3 | 4 | Vue.use(VueI18n); 5 | 6 | // 直接加载翻译的语言文件 7 | const LOADED_LANGUAGES = ['zh-CN', 'en-US']; 8 | const LANG_FILES = require.context('./lang', true, /\.js$/) 9 | // 自动加载lang目录下语言文件,默认只加载LOADED_LANGUAGES中规定的语言文件,其他的语言动态加载 10 | const messages = LANG_FILES.keys().reduce((messages, path) => { 11 | const value = LANG_FILES(path) 12 | const lang = path.replace(/^\.\/(.*)\.\w+$/, '$1'); 13 | if (LOADED_LANGUAGES.includes(lang)) { 14 | messages[lang] = value.default 15 | } 16 | return messages; 17 | }, {}) 18 | 19 | export const getLanguage = () => { 20 | let language = localStorage.getItem('language') 21 | if (!language) { 22 | language = "zh-CN" 23 | } 24 | return language; 25 | } 26 | 27 | const i18n = new VueI18n({ 28 | locale: getLanguage(), 29 | messages, 30 | }); 31 | 32 | const importLanguage = lang => { 33 | if (!LOADED_LANGUAGES.includes(lang)) { 34 | return import(`./lang/${lang}`).then(response => { 35 | i18n.mergeLocaleMessage(lang, response.default); 36 | LOADED_LANGUAGES.push(lang); 37 | return Promise.resolve(lang) 38 | }) 39 | } 40 | return Promise.resolve(lang) 41 | } 42 | 43 | const setLang = lang => { 44 | localStorage.setItem('language', lang) 45 | i18n.locale = lang; 46 | } 47 | 48 | export const setLanguage = lang => { 49 | if (i18n.locale !== lang) { 50 | importLanguage(lang).then(setLang) 51 | } 52 | } 53 | 54 | // 组合翻译,例如key为'请输入{0}',keys为login.username,则自动将keys翻译并替换到{0} {1}... 55 | Vue.prototype.$tm = function (key, ...keys) { 56 | let values = []; 57 | for (const k of keys) { 58 | values.push(i18n.t(k)) 59 | } 60 | return i18n.t(key, values); 61 | }; 62 | 63 | // 忽略警告,即:不存在Key直接返回Key 64 | Vue.prototype.$tk = function (key) { 65 | const hasKey = i18n.te(key) 66 | if (hasKey) { 67 | return i18n.t(key) 68 | } 69 | return key 70 | }; 71 | 72 | // 设置当前语言,LOADED_LANGUAGES以外的翻译文件会自动从lang目录获取(如果有的话), 如果不需要动态加载语言文件,直接用setLang 73 | Vue.prototype.$setLang = setLanguage; 74 | 75 | export default i18n; 76 | -------------------------------------------------------------------------------- /src/i18n/lang/zh-TW.js: -------------------------------------------------------------------------------- 1 | import el from "element-ui/lib/locale/lang/zh-TW"; 2 | 3 | const message = { 4 | // TODO 5 | } 6 | 7 | export default { 8 | ...el, 9 | ...message 10 | }; 11 | -------------------------------------------------------------------------------- /src/i18n/国际化规范.md: -------------------------------------------------------------------------------- 1 | # 国际化文件书写规范 2 | 3 | ### 文件内容 4 | 5 | 每个语言文件由element-ui的国际化内容和自定义国际化内容组成,以zh_CN.js为例: 6 | 7 | ```js 8 | import el from "element-ui/lib/locale/lang/zh-CN"; 9 | 10 | const message = { 11 | ... 12 | } 13 | 14 | export default { 15 | ...el, // element-ui的国际化内容 16 | ...message // 自定义内容 17 | }; 18 | ``` 19 | 20 | ### 自定义内容 21 | 22 | 自定义部分按照业务模块划分,通用的写在commons内,例如 23 | 24 | ```js 25 | const message = { 26 | commons: { // 通用 27 | ... 28 | }, 29 | login: { // 登录 30 | ... 31 | }, 32 | ... // 其他模块 33 | } 34 | 35 | ``` 36 | 37 | ### 层级结构 38 | 39 | 按照业务模块划分后,仍然可以按照子业务或功能再进行划分,但每个业务模块下不要超过3层,例如: 40 | 41 | ```js 42 | const message = { 43 | user_manager: { 44 | user_list: { // 用户列表 45 | name: "姓名", 46 | search: { 47 | ... // 用户列表查询 48 | }, 49 | ... // 用户列表 50 | }, 51 | user_edit: { 52 | ... // 编辑用户 53 | } 54 | }, 55 | ... // 其他模块 56 | } 57 | 58 | ``` 59 | 60 | ### Key命名 61 | 62 | 所有Key的命名必须采用英文单词的方式命名,多个单词之间用下划线( _ )连接,尽量让人一看就知道这个key代表的意思, 例如:user_list 63 | 64 | ```js 65 | const message = { 66 | user_manager: { 67 | user_list: { 68 | ... 69 | }, 70 | user_edit: {} 71 | }, 72 | } 73 | 74 | ``` 75 | -------------------------------------------------------------------------------- /src/icons/index.js: -------------------------------------------------------------------------------- 1 | import {library} from '@fortawesome/fontawesome-svg-core' 2 | import {fas} from '@fortawesome/free-solid-svg-icons' 3 | import {far} from '@fortawesome/free-regular-svg-icons' 4 | import {fab} from '@fortawesome/free-brands-svg-icons' 5 | import {FontAwesomeIcon} from '@fortawesome/vue-fontawesome' 6 | 7 | export default { 8 | install(Vue) { 9 | library.add(fas, far, fab); 10 | Vue.component('font-awesome-icon', FontAwesomeIcon); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import "@/styles/index.scss" 3 | import "@/assets/iconfont/iconfont" 4 | import '@/assets/iconfont/iconfont.css' 5 | import Fit2CloudUI from 'fit2cloud-ui' 6 | // import Fit2CloudUI from './external/fit2cloud-ui.common'; 7 | import {library} from '@fortawesome/fontawesome-svg-core' 8 | import {fas} from '@fortawesome/free-solid-svg-icons' 9 | import {far} from '@fortawesome/free-regular-svg-icons' 10 | import {fab} from '@fortawesome/free-brands-svg-icons' 11 | import {FontAwesomeIcon, FontAwesomeLayers, FontAwesomeLayersText} from '@fortawesome/vue-fontawesome' 12 | 13 | 14 | import ElementUI from 'element-ui'; 15 | import App from './App.vue' 16 | import i18n from "./i18n"; 17 | import router from './router' 18 | import store from './store' 19 | import icons from './icons' 20 | import plugins from "./plugins"; 21 | import directives from "./directive"; 22 | import filters from "./filters"; 23 | import "./permission" 24 | import VueCodemirror from 'vue-codemirror'; 25 | import 'codemirror/lib/codemirror.css'; 26 | Vue.config.productionTip = false 27 | 28 | import echarts from "echarts"; 29 | Vue.prototype.$echarts = echarts; 30 | 31 | Vue.use(ElementUI, { 32 | size: 'small', 33 | i18n: (key, value) => i18n.t(key, value) 34 | }); 35 | Vue.use(Fit2CloudUI, { 36 | i18n: (key, value) => i18n.t(key, value) 37 | }); 38 | library.add(fas, far, fab) 39 | 40 | Vue.component('font-awesome-icon', FontAwesomeIcon) 41 | Vue.component('font-awesome-layers', FontAwesomeLayers) 42 | Vue.component('font-awesome-layers-text', FontAwesomeLayersText) 43 | Vue.use(icons); 44 | Vue.use(plugins); 45 | Vue.use(directives); 46 | Vue.use(filters); 47 | Vue.use(VueCodemirror) 48 | 49 | Vue.directive("preventReClick", { 50 | inserted(el, binding) { 51 | el.addEventListener("click", () => { 52 | el.style.pointerEvents = "none" 53 | if (!el.disabled) { 54 | setTimeout(() => { 55 | el.style.pointerEvents = "auto" 56 | }, binding.value || 1000) 57 | } 58 | }) 59 | } 60 | }) 61 | 62 | new Vue({ 63 | el: '#app', 64 | i18n, 65 | router, 66 | store, 67 | render: h => h(App), 68 | }) 69 | -------------------------------------------------------------------------------- /src/permission.js: -------------------------------------------------------------------------------- 1 | import router from './router' 2 | import store from './store' 3 | import NProgress from 'nprogress' 4 | import 'nprogress/nprogress.css' 5 | 6 | NProgress.configure({showSpinner: false}) // NProgress Configuration 7 | 8 | const whiteList = ['/login'] // no redirect whitelist 9 | 10 | const generateRoutes = async (to, from, next) => { 11 | const hasRoles = store.getters.roles && store.getters.roles.length > 0 12 | if (hasRoles) { 13 | next() 14 | } else { 15 | try { 16 | const {roles} = await store.dispatch('user/getCurrentUser') 17 | const license = await store.dispatch('license/getLicense') 18 | const accessRoutes = await store.dispatch('permission/generateRoutes', {license, roles}) 19 | router.addRoutes(accessRoutes) 20 | next({...to, replace: true}) 21 | } catch (error) { 22 | await store.dispatch('user/logout') 23 | next(`/login?redirect=${to.path}`) 24 | NProgress.done() 25 | } 26 | } 27 | } 28 | 29 | // 路由前置钩子,根据实际需求修改 30 | router.beforeEach(async (to, from, next) => { 31 | NProgress.start() 32 | 33 | const isLogin = await store.dispatch('user/isLogin') 34 | 35 | if (isLogin) { 36 | if (to.path === '/login') { 37 | next({path: '/'}) 38 | NProgress.done() 39 | } else { 40 | await generateRoutes(to, from, next) 41 | } 42 | } else { 43 | /* has not login*/ 44 | if (whiteList.indexOf(to.path) !== -1) { 45 | // in the free login whitelist, go directly 46 | next() 47 | } else { 48 | // other pages that do not have permission to access are redirected to the login page. 49 | next(`/login?redirect=${to.path}`) 50 | NProgress.done() 51 | } 52 | } 53 | }) 54 | 55 | router.afterEach(() => { 56 | // finish progress bar 57 | NProgress.done() 58 | }) 59 | -------------------------------------------------------------------------------- /src/plugins/index.js: -------------------------------------------------------------------------------- 1 | import message from "@/plugins/message"; 2 | import request from "@/plugins/request"; 3 | 4 | export default { 5 | install(Vue) { 6 | Vue.use(message); 7 | Vue.use(request); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/plugins/message.js: -------------------------------------------------------------------------------- 1 | import {MessageBox, Message} from 'element-ui'; 2 | import i18n from "@/i18n"; 3 | 4 | export const $alert = (message, callback, options) => { 5 | let title = i18n.t("commons.message_box.alert"); 6 | MessageBox.alert(message, title, options).then(() => { 7 | callback(); 8 | }); 9 | } 10 | 11 | export const $confirm = (message, callback, options = {}) => { 12 | let defaultOptions = { 13 | confirmButtonText: i18n.t("commons.button.ok"), 14 | cancelButtonText: i18n.t("commons.button.cancel"), 15 | type: 'warning', 16 | ...options 17 | } 18 | let title = i18n.t("commons.message_box.confirm"); 19 | MessageBox.confirm(message, title, defaultOptions).then(() => { 20 | callback(); 21 | }); 22 | } 23 | 24 | export const $success = (message, duration) => { 25 | Message.success({ 26 | message: message, 27 | type: "success", 28 | showClose: true, 29 | duration: duration || 1500 30 | }) 31 | } 32 | 33 | export const $info = (message, duration) => { 34 | Message.info({ 35 | message: message, 36 | type: "info", 37 | showClose: true, 38 | duration: duration || 3000 39 | }) 40 | } 41 | 42 | export const $warning = (message, duration) => { 43 | Message.warning({ 44 | message: message, 45 | type: "warning", 46 | showClose: true, 47 | duration: duration || 5000 48 | }) 49 | } 50 | 51 | export const $error = (message, duration) => { 52 | Message.error({ 53 | message: message, 54 | type: "error", 55 | showClose: true, 56 | duration: duration || 10000 57 | }) 58 | } 59 | 60 | export default { 61 | install(Vue) { 62 | // 使用$$前缀,避免与Element UI的冲突 63 | Vue.prototype.$$confirm = $confirm; 64 | Vue.prototype.$$alert = $alert; 65 | 66 | Vue.prototype.$success = $success; 67 | Vue.prototype.$info = $info; 68 | Vue.prototype.$warning = $warning; 69 | Vue.prototype.$error = $error; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/plugins/request.js: -------------------------------------------------------------------------------- 1 | import axios from "axios" 2 | import {$alert, $error} from "./message" 3 | import store from "@/store" 4 | import i18n, {getLanguage} from "@/i18n" 5 | 6 | const instance = axios.create({ 7 | baseURL: "", // url = base url + request url 8 | withCredentials: true, 9 | timeout: 60000 // request timeout, default 1 min 10 | }) 11 | 12 | let whiteList = [ 13 | "/api/v1/kubernetes/search/metric/", 14 | "/api/v1/kubernetes/evict", 15 | ] 16 | 17 | instance.interceptors.request.use( 18 | config => { 19 | config.headers["lang"] = getLanguage() 20 | return config 21 | }, 22 | error => { 23 | return Promise.reject(error) 24 | } 25 | ) 26 | 27 | const checkAuth = response => { 28 | // 请根据实际需求修改 29 | if (response.headers["authentication-status"] === "invalid" || response.status === 401) { 30 | let message = i18n.t("login.expires") 31 | $alert(message, () => { 32 | store.dispatch("user/logout").then(() => { 33 | location.reload() 34 | }) 35 | }) 36 | } 37 | } 38 | 39 | const checkPermission = response => { 40 | // 请根据实际需求修改 41 | if (response.status === 403) { 42 | location.href = "/403" 43 | } 44 | } 45 | 46 | // 请根据实际需求修改 47 | instance.interceptors.response.use(response => { 48 | checkAuth(response) 49 | return response 50 | }, error => { 51 | let msg 52 | if (error.response) { 53 | checkAuth(error.response) 54 | checkPermission(error.response) 55 | msg = error.response.data.msg || error.response.data 56 | } else { 57 | msg = error.message 58 | } 59 | 60 | let isWhite = false 61 | for (const whiteUrl of whiteList) { 62 | if (error.response.config.url.indexOf(whiteUrl) !== -1) { 63 | isWhite = true 64 | break 65 | } 66 | } 67 | if (!isWhite) { 68 | $error(msg) 69 | } 70 | return Promise.reject(error) 71 | }) 72 | 73 | export const request = instance 74 | 75 | /* 简化请求方法,统一处理返回结果,并增加loading处理,这里以{success,data,message}格式的返回值为例,具体项目根据实际需求修改 */ 76 | const promise = (request, loading = {}) => { 77 | return new Promise((resolve, reject) => { 78 | loading.status = true 79 | request.then(response => { 80 | // if (response.data.success) { 81 | resolve(response.data) 82 | // } 83 | // else { 84 | // reject(response.data) 85 | // } 86 | loading.status = false 87 | }).catch(error => { 88 | reject(error) 89 | loading.status = false 90 | }) 91 | }) 92 | } 93 | 94 | export const get = (url, data, loading) => { 95 | return promise(request({ url: url, method: "get", params: data }), loading) 96 | } 97 | 98 | export const post = (url, data, headers, loading) => { 99 | if (headers) { 100 | return promise(request({ url: url, headers: headers, method: "post", data }), loading) 101 | } 102 | return promise(request({ url: url, method: "post", data }), loading) 103 | } 104 | 105 | export const put = (url, data, loading) => { 106 | return promise(request({ url: url, method: "put", data }), loading) 107 | } 108 | 109 | export const del = (url, loading) => { 110 | return promise(request({ url: url, method: "delete" }), loading) 111 | } 112 | 113 | export const patch = (url, data, headers, loading) => { 114 | if (headers) { 115 | return promise(request({ url: url, headers: headers, method: "patch", data }), loading) 116 | } 117 | return promise(request({ url: url, method: "patch", data }), loading) 118 | } 119 | 120 | export default { 121 | install (Vue) { 122 | Vue.prototype.$get = get 123 | Vue.prototype.$post = post 124 | Vue.prototype.$put = put 125 | Vue.prototype.$delete = del 126 | Vue.prototype.$request = request 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue" 2 | import Router from "vue-router" 3 | 4 | // 加载modules中的路由 5 | const modules = require.context("./modules", true, /\.js$/) 6 | 7 | // 修复路由变更后报错的问题 8 | const routerPush = Router.prototype.push; 9 | Router.prototype.push = function push(location) { 10 | return routerPush.call(this, location).catch(error => error) 11 | } 12 | 13 | Vue.use(Router) 14 | 15 | import Layout from "@/business/app-layout/horizontal-layout" 16 | 17 | export const constantRoutes = [ 18 | { 19 | path: "/redirect", 20 | component: Layout, 21 | hidden: true, 22 | children: [ 23 | { 24 | path: "/redirect/:path(.*)", 25 | component: () => import("@/components/redirect") 26 | } 27 | ] 28 | }, 29 | { 30 | path: "/login", 31 | component: () => import("@/business/login"), 32 | hidden: true 33 | }, 34 | { 35 | path: "/logger", 36 | component: () => import("@/business/xterm"), 37 | hidden: true 38 | }, 39 | { 40 | path: "/", 41 | component: Layout, 42 | redirect: "/clusters", 43 | } 44 | ] 45 | 46 | /** 47 | * 用户登录后根据角色加载的路由 48 | */ 49 | export const rolesRoutes = [ 50 | // 先按sort排序 51 | ...modules.keys().map(key => modules(key).default).sort((r1, r2) => { 52 | r1.sort ??= Number.MAX_VALUE 53 | r2.sort ??= Number.MAX_VALUE 54 | return r1.sort - r2.sort 55 | }), 56 | {path: "*", redirect: "/", hidden: true} 57 | ] 58 | 59 | const createRouter = () => new Router({ 60 | scrollBehavior: () => ({y: 0}), 61 | routes: constantRoutes 62 | }) 63 | 64 | const router = createRouter() 65 | 66 | export function resetRouter() { 67 | const newRouter = createRouter() 68 | router.matcher = newRouter.matcher // reset router 69 | } 70 | 71 | export default router 72 | -------------------------------------------------------------------------------- /src/router/modules/authorization.js: -------------------------------------------------------------------------------- 1 | import Layout from "@/business/app-layout/horizontal-layout" 2 | import AuthorizationComponent from "@/business/authorization" 3 | 4 | 5 | const Authorization = { 6 | sort: 7, 7 | path: "/authorization", 8 | component: Layout, 9 | name: "Authorization", 10 | children: [ 11 | { 12 | path: "list", 13 | component: () => import("@/business/authorization"), 14 | name: "ProjectAuthorizationList", 15 | props: true, 16 | meta: { 17 | title: "route.project_management", 18 | icon: "iconfont iconproject", 19 | roles: ["ADMIN", "PROJECT_MANAGER"] 20 | } 21 | }, 22 | { 23 | path: "resource", 24 | component: AuthorizationComponent, 25 | name: "Resource", 26 | hidden: true, 27 | meta: { 28 | activeMenu: "/authorization/list", 29 | roles: ["ADMIN", "PROJECT_MANAGER"] 30 | }, 31 | }, 32 | { 33 | path: "project/create", 34 | component: () => import("@/business/authorization/projects/create"), 35 | name: "ProjectCreate", 36 | hidden: true, 37 | meta: { 38 | activeMenu: "/authorization/list", 39 | roles: ["ADMIN", "PROJECT_MANAGER"] 40 | } 41 | }, 42 | { 43 | path: "project/edit/:name", 44 | props: true, 45 | component: () => import("@/business/authorization/projects/edit"), 46 | name: "ProjectEdit", 47 | hidden: true, 48 | meta: { 49 | activeMenu: "/authorization/list", 50 | roles: ["ADMIN", "PROJECT_MANAGER"] 51 | } 52 | } 53 | ] 54 | } 55 | 56 | export default Authorization 57 | -------------------------------------------------------------------------------- /src/router/modules/backup-account.js: -------------------------------------------------------------------------------- 1 | import Layout from "@/business/app-layout/horizontal-layout" 2 | 3 | const BackupAccount = { 4 | sort: 5, 5 | path: "/backup_account", 6 | component: Layout, 7 | name: "BackupAccount", 8 | props: true, 9 | meta: { 10 | title: "route.backup_account", 11 | icon: "iconfont iconbackup", 12 | roles: ["ADMIN","PROJECT_MANAGER"] 13 | }, 14 | redirect: to => { 15 | return { 16 | name: 'BackupAccountList', 17 | params: to.params, 18 | } 19 | }, 20 | children: [ 21 | { 22 | path: "/backup_account", 23 | component: () => import("@/business/backup-account/index"), 24 | name: "BackupAccountList", 25 | props: true, 26 | hidden: true, 27 | meta: { 28 | activeMenu: "/backup_account", 29 | title: "route.backup_account", 30 | roles: ["ADMIN","PROJECT_MANAGER"] 31 | } 32 | }, 33 | { 34 | path: "create", 35 | component: () => import("@/business/backup-account/create"), 36 | name: "BackupAccountCreate", 37 | hidden: true, 38 | props: true, 39 | meta: { 40 | activeMenu: "/backup_account", 41 | title: "route.backup_account", 42 | roles: ["ADMIN","PROJECT_MANAGER"] 43 | } 44 | }, 45 | { 46 | path: "edit/:name", 47 | component: () => import("@/business/backup-account/edit"), 48 | name: "BackupAccountEdit", 49 | props: true, 50 | hidden: true, 51 | meta: { 52 | activeMenu: "/backup_account", 53 | title: "route.backup_account", 54 | roles: ["ADMIN","PROJECT_MANAGER"] 55 | } 56 | } 57 | ] 58 | } 59 | 60 | export default BackupAccount 61 | -------------------------------------------------------------------------------- /src/router/modules/hosts.js: -------------------------------------------------------------------------------- 1 | import Layout from "@/business/app-layout/horizontal-layout"; 2 | 3 | const Host = { 4 | sort: 2, 5 | path: '/hosts', 6 | component: Layout, 7 | name: 'Host', 8 | children: [ 9 | { 10 | path: 'list', 11 | component: () => import('@/business/hosts'), 12 | name: "HostList", 13 | meta: { 14 | title: "route.host", 15 | icon: 'iconfont iconhost', 16 | roles: ['ADMIN', "PROJECT_MANAGER"] 17 | }, 18 | }, 19 | { 20 | path: "create", 21 | hidden: true, 22 | name: "HostCreate", 23 | component: () => import('@/business/hosts/create'), 24 | meta: { 25 | activeMenu: "/hosts/list", 26 | roles: ['ADMIN'] 27 | }, 28 | }, 29 | { 30 | path: "edit/:name", 31 | props: true, 32 | hidden: true, 33 | name: "HostEdit", 34 | component: () => import('@/business/hosts/edit'), 35 | meta: { 36 | activeMenu: "/hosts/list", 37 | roles: ['ADMIN'] 38 | }, 39 | } 40 | ] 41 | } 42 | export default Host 43 | -------------------------------------------------------------------------------- /src/router/modules/manifest.js: -------------------------------------------------------------------------------- 1 | import Layout from "@/business/app-layout/horizontal-layout" 2 | 3 | const Manifest = { 4 | path: "/manifest", 5 | sort: 6, 6 | component: Layout, 7 | name: "Manifest", 8 | meta: { 9 | title: "route.manifest", 10 | icon: "iconfont iconmanifest", 11 | roles: ["ADMIN","PROJECT_MANAGER"] 12 | }, 13 | children: [ 14 | { 15 | path: "manifests", 16 | component: () => import("@/business/manifest/index"), 17 | name: "Manifests", 18 | meta: { 19 | title: "route.manifest", 20 | roles: ["ADMIN","PROJECT_MANAGER"] 21 | } 22 | } 23 | ] 24 | } 25 | export default Manifest 26 | -------------------------------------------------------------------------------- /src/router/modules/msg-subscribe.js: -------------------------------------------------------------------------------- 1 | import Layout from "@/business/app-layout/horizontal-layout" 2 | 3 | const MsgSubscribe = { 4 | sort: 7, 5 | path: "/msg_subscribe", 6 | component: Layout, 7 | name: "MsgSubscribe", 8 | props: true, 9 | meta: { 10 | icon: "iconfont iconweidu1", 11 | roles: ["ADMIN", "PROJECT_MANAGER"] 12 | }, 13 | children: [ 14 | { 15 | path: "msgSubscribe", 16 | component: () => import("@/business/msg-subscribe/index"), 17 | name: "MsgSubscribeList", 18 | meta: { 19 | title: "message.message_subscribe", 20 | roles: ["ADMIN","PROJECT_MANAGER"] 21 | } 22 | }, 23 | ] 24 | } 25 | 26 | export default MsgSubscribe 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/router/modules/system-log.js: -------------------------------------------------------------------------------- 1 | import Layout from "@/business/app-layout/horizontal-layout"; 2 | 3 | const SystemLog = { 4 | sort: 10, 5 | path: '/system-log', 6 | component: Layout, 7 | name: 'SystemLog', 8 | meta: { 9 | title: "route.system_log", 10 | icon: 'iconfont iconxitongrizhi', 11 | roles: ['ADMIN', "PROJECT_MANAGER","CLUSTER_MANAGER"] 12 | }, 13 | children: [ 14 | { 15 | path: 'logs', 16 | component: () => import('@/business/system-log/index'), 17 | name: "Logs", 18 | meta: { 19 | title: "route.system_log", 20 | roles: ['ADMIN', "PROJECT_MANAGER","CLUSTER_MANAGER"] 21 | } 22 | } 23 | ] 24 | } 25 | export default SystemLog 26 | -------------------------------------------------------------------------------- /src/router/modules/users.js: -------------------------------------------------------------------------------- 1 | import Layout from "@/business/app-layout/horizontal-layout" 2 | 3 | const Users = { 4 | sort: 8, 5 | path: "/users", 6 | component: Layout, 7 | name: "User", 8 | meta: { 9 | roles: ["ADMIN"] 10 | }, 11 | children: [ 12 | { 13 | path: "list", 14 | component: () => import("@/business/users"), 15 | name: "UserList", 16 | meta: { 17 | title: "route.user", 18 | icon: "iconfont iconyonghuguanli", 19 | roles: ["ADMIN"] 20 | } 21 | }, 22 | { 23 | path: "create", 24 | hidden: true, 25 | name: "UserCreate", 26 | component: () => import("@/business/users/create"), 27 | meta: { 28 | activeMenu: "/users/list", 29 | roles: ["ADMIN"] 30 | }, 31 | }, { 32 | props: true, 33 | path: "edit/:name", 34 | hidden: true, 35 | name: "UserEdit", 36 | component: () => import("@/business/users/edit"), 37 | meta: { 38 | activeMenu: "/users/list", 39 | roles: ["ADMIN"] 40 | }, 41 | } 42 | ] 43 | } 44 | export default Users 45 | -------------------------------------------------------------------------------- /src/router/modules/x-pack.js: -------------------------------------------------------------------------------- 1 | import Layout from "@/business/app-layout/horizontal-layout" 2 | 3 | const XPack = { 4 | path: "/xpack", 5 | sort: 9, 6 | name: "XPack", 7 | component: Layout, 8 | meta: { 9 | title: "X-Pack", 10 | icon: "iconfont iconx-pack", 11 | requireLicense: true 12 | }, 13 | children: [ 14 | { 15 | path: "multi-cluster", 16 | component: () => import("@/business/xpack"), 17 | name: "MultiCluster", 18 | meta: { 19 | title: "route.multi_cluster", 20 | roles: ["ADMIN", "PROJECT_MANAGER"] 21 | }, 22 | redirect: () => { 23 | return { 24 | name: 'MultiClusterRepositoriesList', 25 | } 26 | }, 27 | children: [ 28 | { 29 | path: "list", 30 | hidden: true, 31 | component: () => import("@/business/xpack/multi-cluster"), 32 | name: "MultiClusterRepositoriesList", 33 | meta: { 34 | title: "route.multi_cluster", 35 | activeMenu: "/xpack/multi-cluster", 36 | roles: ["ADMIN", "PROJECT_MANAGER"] 37 | }, 38 | }, 39 | { 40 | path: "create", 41 | hidden: true, 42 | component: () => import("@/business/xpack/multi-cluster/create"), 43 | name: "MultiClusterRepositoryCreate", 44 | meta: { 45 | activeMenu: "/xpack/multi-cluster", 46 | roles: ["ADMIN", "PROJECT_MANAGER"] 47 | }, 48 | }, 49 | { 50 | path: "edit/:name", 51 | props: true, 52 | hidden: true, 53 | component: () => import("@/business/xpack/multi-cluster/edit"), 54 | name: "MultiClusterRepositoryEdit", 55 | meta: { 56 | activeMenu: "/xpack/multi-cluster", 57 | roles: ["ADMIN", "PROJECT_MANAGER"] 58 | } 59 | }, 60 | { 61 | path: "log/:name", 62 | props: true, 63 | hidden: true, 64 | component: () => import("@/business/xpack/multi-cluster/log"), 65 | name: "MultiClusterRepositoryLog", 66 | meta: { 67 | activeMenu: "/xpack/multi-cluster", 68 | roles: ["ADMIN", "PROJECT_MANAGER"] 69 | } 70 | }, 71 | ], 72 | }, 73 | { 74 | path: "theme", 75 | name: "Theme", 76 | props: true, 77 | component: () => import('@/business/xpack/theme'), 78 | meta: { 79 | requireLicense: true, 80 | title: "route.theme", 81 | activeMenu: "/xpack/theme", 82 | roles: ['ADMIN'] 83 | } 84 | }, 85 | ] 86 | } 87 | export default XPack 88 | -------------------------------------------------------------------------------- /src/store/getters.js: -------------------------------------------------------------------------------- 1 | // 根据实际需要修改 2 | const getters = { 3 | sidebar: state => state.app.sidebar, 4 | name: state => state.user.name, 5 | currentProject: state => state.user.currentProject, 6 | language: state => state.user.language, 7 | roles: state => state.user.roles, 8 | permission_routes: state => state.permission.routes, 9 | license: state => state.license, 10 | theme: state => state.theme.theme, 11 | } 12 | export default getters 13 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import getters from './getters' 4 | 5 | Vue.use(Vuex) 6 | 7 | // 自动从modules目录下获取模块 8 | const MODULES_FILES = require.context('./modules', true, /\.js$/) 9 | 10 | // 模块名为js文件名,例如user.js 则模块名为user 11 | const modules = MODULES_FILES.keys().reduce((modules, modulePath) => { 12 | const value = MODULES_FILES(modulePath) 13 | const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1') 14 | modules[moduleName] = value.default 15 | return modules 16 | }, {}) 17 | 18 | const store = new Vuex.Store({ 19 | modules, 20 | getters 21 | }) 22 | 23 | export default store 24 | -------------------------------------------------------------------------------- /src/store/modules/app.js: -------------------------------------------------------------------------------- 1 | const get = () => { 2 | return localStorage.getItem('sidebarStatus') 3 | } 4 | const set = value => { 5 | localStorage.setItem('sidebarStatus', value) 6 | } 7 | const state = { 8 | sidebar: { 9 | opened: get() ? !!+get() : true 10 | }, 11 | device: 'desktop' 12 | } 13 | 14 | const mutations = { 15 | TOGGLE_SIDEBAR: state => { 16 | state.sidebar.opened = !state.sidebar.opened 17 | if (state.sidebar.opened) { 18 | set(1) 19 | } else { 20 | set(0) 21 | } 22 | }, 23 | OPEN_SIDEBAR: (state) => { 24 | set('sidebarStatus', 1) 25 | state.sidebar.opened = true 26 | }, 27 | CLOSE_SIDEBAR: (state) => { 28 | set('sidebarStatus', 0) 29 | state.sidebar.opened = false 30 | } 31 | } 32 | 33 | const actions = { 34 | toggleSideBar({commit}) { 35 | commit('TOGGLE_SIDEBAR') 36 | }, 37 | openSideBar({commit}) { 38 | commit('OPEN_SIDEBAR') 39 | }, 40 | closeSideBar({commit}) { 41 | commit('CLOSE_SIDEBAR') 42 | } 43 | } 44 | 45 | export default { 46 | namespaced: true, 47 | state, 48 | mutations, 49 | actions 50 | } 51 | -------------------------------------------------------------------------------- /src/store/modules/license.js: -------------------------------------------------------------------------------- 1 | import {getLicense} from "@/api/license" 2 | 3 | const LicenseKey = "X-License"; 4 | 5 | const Status = { 6 | valid: "valid", 7 | invalid: "invalid", 8 | expired: "expired", 9 | } 10 | 11 | const get = () => { 12 | return localStorage.getItem(LicenseKey) 13 | } 14 | const set = value => { 15 | localStorage.setItem(LicenseKey, value) 16 | } 17 | const state = { 18 | status: get(), 19 | license: {}, 20 | message: "" 21 | } 22 | 23 | const mutations = { 24 | SET_STATUS: (state, status) => { 25 | set(LicenseKey, status) 26 | state.status = status; 27 | }, 28 | SET_LICENSE: (state, license) => { 29 | state.license = license; 30 | }, 31 | SET_MESSAGE: (state, message) => { 32 | state.message = message; 33 | } 34 | } 35 | 36 | const actions = { 37 | getLicense({commit}) { 38 | return new Promise((resolve, reject) => { 39 | getLicense().then(data => { 40 | const {status, license, message} = data; 41 | commit('SET_STATUS', status) 42 | commit('SET_LICENSE', license) 43 | commit('SET_MESSAGE', message) 44 | resolve(data) 45 | }).catch(error => { 46 | commit('SET_STATUS', Status.invalid) 47 | reject(error) 48 | }) 49 | }) 50 | }, 51 | isValid({state}) { 52 | return state.status === Status.valid 53 | }, 54 | isExpired({state}) { 55 | return state.status === Status.expired 56 | } 57 | } 58 | 59 | export default { 60 | namespaced: true, 61 | state, 62 | mutations, 63 | actions 64 | } 65 | -------------------------------------------------------------------------------- /src/store/modules/permission.js: -------------------------------------------------------------------------------- 1 | import {rolesRoutes, constantRoutes} from '@/router' 2 | 3 | function hasPermission(roles, license, route) { 4 | if (route.meta && route.meta.requireLicense) { 5 | if (license.status !== 'valid') { 6 | return false 7 | } 8 | } 9 | if (route.meta && route.meta.roles) { 10 | return roles.some(role => route.meta.roles.includes(role)) 11 | } else { 12 | return true 13 | } 14 | } 15 | 16 | 17 | export function filterRolesRoutes(routes, license, roles) { 18 | const res = [] 19 | routes.forEach(route => { 20 | const tmp = {...route} 21 | if (hasPermission(roles, license, tmp)) { 22 | if (tmp.children) { 23 | tmp.children = filterRolesRoutes(tmp.children, license, roles) 24 | } 25 | res.push(tmp) 26 | } 27 | }) 28 | 29 | return res 30 | } 31 | 32 | const state = { 33 | routes: [], 34 | addRoutes: [] 35 | } 36 | 37 | const mutations = { 38 | SET_ROUTES: (state, routes) => { 39 | state.addRoutes = routes 40 | state.routes = constantRoutes.concat(routes) 41 | } 42 | } 43 | 44 | const actions = { 45 | generateRoutes({commit}, p) { 46 | return new Promise(resolve => { 47 | const {license, roles} = p 48 | let accessedRoutes 49 | accessedRoutes = filterRolesRoutes(rolesRoutes, license, roles) 50 | commit('SET_ROUTES', accessedRoutes) 51 | resolve(accessedRoutes) 52 | }) 53 | } 54 | } 55 | 56 | export default { 57 | namespaced: true, 58 | state, 59 | mutations, 60 | actions 61 | } 62 | -------------------------------------------------------------------------------- /src/store/modules/theme.js: -------------------------------------------------------------------------------- 1 | import { getTheme } from "@/api/theme"; 2 | 3 | const state = { 4 | theme: null 5 | }; 6 | 7 | const mutations = { 8 | SET_THEME: (state, data) => { 9 | state.theme = data; 10 | state.theme.systemName = state.theme.systemName === "" ? "KubeOperator" : state.theme.systemName; 11 | let link = document.querySelector("link[rel*='icon']"); 12 | if (!link) { 13 | link = document.createElement('link'); 14 | link.type = 'image/x-icon'; 15 | link.rel = 'shortcut icon'; 16 | document.getElementsByTagName('head')[0].appendChild(link); 17 | } 18 | if (data.icon !== '') { 19 | link.href = data.icon; 20 | } else { 21 | link.href = "ui/favicon.ico" 22 | } 23 | document.title = state.theme.systemName; 24 | } 25 | }; 26 | 27 | const actions = { 28 | getThemeInfo({ commit }) { 29 | return new Promise((resolve, reject) => { 30 | getTheme() 31 | .then(data => { 32 | commit("SET_THEME", data); 33 | resolve(data); 34 | }) 35 | .catch(error => { 36 | commit("SET_THEME", { 37 | systemName: "KubeOperator", 38 | logo: "", 39 | logoWithText: "", 40 | loginImage: "", 41 | icon: "", 42 | logoAbout: "" 43 | }); 44 | reject(error); 45 | }); 46 | }); 47 | } 48 | }; 49 | 50 | export default { 51 | namespaced: true, 52 | state, 53 | mutations, 54 | actions 55 | }; 56 | -------------------------------------------------------------------------------- /src/store/modules/user-token.js: -------------------------------------------------------------------------------- 1 | import {login, getCurrentUser, updateInfo, logout} from "@/api/user-token" 2 | import {resetRouter} from "@/router" 3 | import {getToken, setToken, removeToken} from "@/utils/token" 4 | import {getLanguage, setLanguage} from "@/i18n" 5 | 6 | /* 前后端不分离的登录办法*/ 7 | const state = { 8 | token: getToken(), 9 | name: "", 10 | language: getLanguage(), 11 | roles: [] 12 | } 13 | 14 | const mutations = { 15 | SET_TOKEN: (state, token) => { 16 | state.token = token 17 | }, 18 | SET_NAME: (state, name) => { 19 | state.name = name 20 | }, 21 | SET_LANGUAGE: (state, language) => { 22 | state.language = language 23 | setLanguage(language) 24 | }, 25 | SET_ROLES: (state, roles) => { 26 | state.roles = roles 27 | } 28 | } 29 | 30 | const actions = { 31 | login ({ commit }, userInfo) { 32 | const { username, password, captchaId, code } = userInfo 33 | return new Promise((resolve, reject) => { 34 | login({ username: username.trim(), password: password, captchaId: captchaId, code: code }).then(response => { 35 | let token = response.data 36 | commit("SET_TOKEN", token) 37 | setToken(token) 38 | resolve(response) 39 | }).catch(error => { 40 | reject(error) 41 | }) 42 | }) 43 | }, 44 | 45 | isLogin ({ commit }) { 46 | return new Promise((resolve, reject) => { 47 | let token = getToken() 48 | if (token) { 49 | commit("SET_TOKEN", token) 50 | resolve(true) 51 | } else { 52 | reject(false) 53 | } 54 | }) 55 | }, 56 | 57 | getCurrentUser ({ commit }) { 58 | return new Promise((resolve, reject) => { 59 | getCurrentUser().then(response => { 60 | const { name, roles, language } = response.data 61 | commit("SET_NAME", name) 62 | commit("SET_ROLES", roles) 63 | commit("SET_LANGUAGE", language) 64 | resolve(response.data) 65 | }).catch(error => { 66 | reject(error) 67 | }) 68 | }) 69 | }, 70 | 71 | setLanguage ({ commit, state }, language) { 72 | commit("SET_LANGUAGE", language) 73 | return new Promise((resolve, reject) => { 74 | updateInfo(state.id, { language: language }).then(response => { 75 | resolve(response) 76 | }).catch(error => { 77 | reject(error) 78 | }) 79 | }) 80 | }, 81 | 82 | logout ({ commit }) { 83 | logout().then(() => { 84 | commit("SET_TOKEN", "") 85 | commit("SET_ROLES", []) 86 | removeToken() 87 | resetRouter() 88 | }) 89 | }, 90 | } 91 | 92 | export default { 93 | namespaced: true, 94 | state, 95 | mutations, 96 | actions 97 | } 98 | -------------------------------------------------------------------------------- /src/store/modules/user.js: -------------------------------------------------------------------------------- 1 | /* 前后端不分离的登录方式*/ 2 | import {updateUser} from "@/api/user" 3 | import {login, logout, isLogin, getSession} from "@/api/auth" 4 | import {resetRouter} from "@/router" 5 | import {getLanguage, setLanguage} from "@/i18n" 6 | 7 | const state = { 8 | login: false, 9 | name: "", 10 | currentProject: "", 11 | language: getLanguage(), 12 | roles: [] 13 | } 14 | 15 | const mutations = { 16 | LOGIN: (state) => { 17 | state.login = true 18 | }, 19 | LOGOUT: (state) => { 20 | state.login = false 21 | }, 22 | SET_NAME: (state, name) => { 23 | state.name = name 24 | }, 25 | SET_LANGUAGE: (state, language) => { 26 | state.language = language 27 | setLanguage(language) 28 | }, 29 | SET_ROLES: (state, roles) => { 30 | state.roles = roles 31 | }, 32 | SET_CURRENT_PROJECT: (state, project) => { 33 | state.currentProject = project 34 | } 35 | } 36 | 37 | const actions = { 38 | login ({ commit }, userInfo) { 39 | const { username, password, captchaId, code } = userInfo 40 | return new Promise((resolve, reject) => { 41 | login({ username: username.trim(), password: password, captchaId: captchaId, code: code }).then(response => { 42 | commit("LOGIN") 43 | resolve(response) 44 | }).catch(error => { 45 | reject(error) 46 | }) 47 | }) 48 | }, 49 | 50 | isLogin ({ commit }) { 51 | return new Promise((resolve) => { 52 | if (state.isLogin) { 53 | resolve(true) 54 | return 55 | } 56 | isLogin().then((data) => { 57 | if (data.isLogin) { 58 | commit("LOGIN") 59 | resolve(true) 60 | } else { 61 | resolve(false) 62 | } 63 | }).catch(() => { 64 | resolve(false) 65 | }) 66 | }) 67 | }, 68 | 69 | getCurrentUser ({ commit }) { 70 | return new Promise((resolve, reject) => { 71 | getSession().then(data => { 72 | const user = data.user 73 | const { name, roles, language, currentProject } = user 74 | commit("SET_NAME", name) 75 | commit("SET_ROLES", roles) 76 | commit("SET_LANGUAGE", language) 77 | commit("SET_CURRENT_PROJECT", currentProject) 78 | resolve(user) 79 | }).catch(error => { 80 | reject(error) 81 | }) 82 | }) 83 | }, 84 | 85 | setLanguage ({ commit, state }, language) { 86 | commit("SET_LANGUAGE", language) 87 | return new Promise((resolve, reject) => { 88 | updateUser(state.name, { language: language }).then(response => { 89 | resolve(response) 90 | }).catch(error => { 91 | reject(error) 92 | }) 93 | }) 94 | }, 95 | setCurrentProject ({ commit, state }, project) { 96 | commit("SET_CURRENT_PROJECT", project) 97 | return new Promise((resolve, reject) => { 98 | updateUser(state.name, { currentProject: project }).then(response => { 99 | resolve(response) 100 | }).catch(error => { 101 | reject(error) 102 | }) 103 | }) 104 | }, 105 | 106 | logout ({ commit }) { 107 | logout().then(() => { 108 | commit("LOGOUT") 109 | commit("SET_ROLES", []) 110 | resetRouter() 111 | }) 112 | }, 113 | } 114 | 115 | export default { 116 | namespaced: true, 117 | state, 118 | mutations, 119 | actions 120 | } 121 | -------------------------------------------------------------------------------- /src/styles/business/header-menu.scss: -------------------------------------------------------------------------------- 1 | @import "~@/styles/common/variables.scss"; 2 | 3 | .header-menu { 4 | min-width: 100px; 5 | color: #3E3E3D; 6 | 7 | &.el-menu { 8 | background-color: transparent; 9 | 10 | &.el-menu--horizontal { 11 | border: none; 12 | 13 | .el-submenu__title { 14 | border: none; 15 | min-width: 120px; 16 | height: 40px; 17 | line-height: 40px; 18 | padding: 0 3px; 19 | } 20 | } 21 | } 22 | } 23 | 24 | .header-menu-popper { 25 | color: #3E3E3D; 26 | 27 | .el-menu--popup { 28 | min-width: 100px; 29 | } 30 | 31 | .el-menu-item { 32 | &.is-active { 33 | color: $--color-primary; 34 | } 35 | 36 | &:hover { 37 | background-color: #D5D5D5; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/styles/common/mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin flex-row($justify: flex-start, $align: stretch) { 2 | display: flex; 3 | @if $justify != flex-start { 4 | justify-content: $justify; 5 | } 6 | @if $align != stretch { 7 | align-items: $align; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/styles/common/variables.scss: -------------------------------------------------------------------------------- 1 | /* Element 变量 */ 2 | $--color-primary: #FA5D50; 3 | $--color-success: #87CB16; 4 | $--color-warning: #FFA534; 5 | $--color-danger: #FB404B; 6 | $--color-info: #0033FF; 7 | $--color-ko: #FA5D50; 8 | 9 | $--box-shadow-light: 0 1px 4px 0 rgba(0, 0, 0, .14); 10 | 11 | $--color-text-primary: #3c4858; 12 | 13 | /* layout */ 14 | $layout-bg-color: #F2F2F2; 15 | 16 | /* sidebar */ 17 | $sidebar-open-width: 260px; 18 | $sidebar-close-width: 60px; 19 | $sidebar-bg-color: #30373d; 20 | $sidebar-bg-gradient: linear-gradient(to bottom right, #30373D, #3E3E3D); 21 | 22 | /* menu */ 23 | $menu-height: 50px; // 菜单项高度 24 | $menu-bg-color: transparent; // 菜单项背景 25 | $menu-bg-color-hover: mix($sidebar-bg-color, #000, 90%); // 菜单项hover背景 26 | $menu-color: #B6C0CD; // 菜单项字体颜色 27 | $menu-open-bg-color: #252B2F; // 菜单项展开背景 28 | $menu-active-color: #FFF; // 菜单项激活时颜色 29 | $menu-active-bg-color: transparent; // 菜单项激活时背景 30 | 31 | $submenu-height: 40px; // 子菜单项高度 32 | $submenu-bg-color-hover: mix($menu-open-bg-color, #000, 80%); // 子菜单项hover背景 33 | $submenu-active-color: $menu-active-color; // 子菜单项激活时颜色 34 | $submenu-active-bg-color: transparent; // 子菜单项激活时背景 35 | 36 | $menu-active-prefix-color: $--color-ko; // 菜单激活前缀颜色 37 | $menu-active-prefix-width: 4px; // 菜单激活前缀宽度 38 | 39 | /* logo */ 40 | $logo-height: 40px; 41 | $logo-bg-color: #4E5051; 42 | 43 | /* header */ 44 | $header-height: 60px; 45 | $header-padding: 30px; 46 | 47 | /* main */ 48 | $view-padding: 15px; 49 | 50 | /* fit2cloud-ui的variables加载了element-ui的变量 */ 51 | @import "~fit2cloud-ui/src/styles/common/variables"; 52 | 53 | :export { 54 | theme: $--color-primary; 55 | } 56 | 57 | #nprogress .bar { 58 | background: $--color-ko !important; //自定义颜色 59 | } 60 | 61 | //.statusFailed { 62 | // color: #0033FF; 63 | // text-decoration: underline; 64 | //} 65 | 66 | .koLink { 67 | color: #0033FF; 68 | text-decoration: underline; 69 | } 70 | 71 | .linkStyle { 72 | color: #0033FF; 73 | } 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /src/styles/index.scss: -------------------------------------------------------------------------------- 1 | @import '~normalize.css/normalize.css'; 2 | @import "./common/variables"; 3 | @import "~fit2cloud-ui/src/styles"; 4 | @import "./business/app"; 5 | -------------------------------------------------------------------------------- /src/utils/format_ansible_err.js: -------------------------------------------------------------------------------- 1 | export function ansibleErrFormat(str) { 2 | let formatMsgs = []; 3 | if (!isJson(str)) { 4 | return [ 5 | { name: "Error Message", info: str, failed: false, type: "unFormat" } 6 | ]; 7 | } 8 | var json1 = JSON.parse(str); 9 | for (const key in json1) { 10 | var itemMsg = { name: "", info: {}, failed: false }; 11 | itemMsg.name = key; 12 | if (isJson(json1[key])) { 13 | var json2 = JSON.parse(json1[key]); 14 | itemMsg.info = json2; 15 | if (itemMsg.info.msg) { 16 | itemMsg.info.msg = itemMsg.info.msg.replace(/\t/g, "").trim(); 17 | } 18 | if (itemMsg.info.stdout) { 19 | itemMsg.info.stdout = itemMsg.info.stdout.replace(/\t/g, "").trim(); 20 | } 21 | if (itemMsg.info.stderr) { 22 | itemMsg.info.stderr = itemMsg.info.stderr.replace(/\t/g, "").trim(); 23 | } 24 | if (itemMsg.info.unreachable) { 25 | itemMsg.failed = true; 26 | } else { 27 | itemMsg.failed = json2.failed; 28 | } 29 | } else { 30 | itemMsg.type = "unFormat"; 31 | itemMsg.info = json1[key]; 32 | } 33 | if (itemMsg.info.length !== 0) { 34 | formatMsgs.push(itemMsg); 35 | } 36 | } 37 | return formatMsgs; 38 | } 39 | 40 | function isJson(str) { 41 | try { 42 | if (typeof JSON.parse(str) === "object") { 43 | return true; 44 | } 45 | } catch { 46 | return false; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/utils/format_conversion.js: -------------------------------------------------------------------------------- 1 | export function changeUnderLineToPoint(obj) { 2 | for (var item in obj) { 3 | if (item.indexOf("_") !== -1) { 4 | var newParam = item.replace(new RegExp("__", 'g'), "-").replace(new RegExp("_", 'g'), "."); 5 | obj[newParam] = obj[item]; 6 | delete obj[item]; 7 | } 8 | } 9 | return obj; 10 | } 11 | 12 | export function changePointToUnderLine(obj) { 13 | for (var item in obj) { 14 | if (item.indexOf("-") !== -1 || item.indexOf(".") !== -1) { 15 | var newParam = item.replace(new RegExp("-", 'g'), "__").replace(/\./g, "_"); 16 | obj[newParam] = obj[item]; 17 | delete obj[item]; 18 | } 19 | } 20 | return obj; 21 | } 22 | -------------------------------------------------------------------------------- /src/utils/format_date.js: -------------------------------------------------------------------------------- 1 | export function formatDate(date, fmt) { 2 | if (/(y+)/.test(fmt)) { 3 | fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)); 4 | } 5 | let o = { 6 | 'M+': date.getMonth() + 1, 7 | 'd+': date.getDate(), 8 | 'h+': date.getHours(), 9 | 'm+': date.getMinutes(), 10 | 's+': date.getSeconds() 11 | }; 12 | for (let k in o) { 13 | if (new RegExp(`(${k})`).test(fmt)) { 14 | let str = o[k] + ''; 15 | fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? str : padLeftZero(str)); 16 | } 17 | } 18 | return fmt; 19 | } 20 | 21 | function padLeftZero(str) { 22 | return ('00' + str).substr(str.length); 23 | } -------------------------------------------------------------------------------- /src/utils/global_variable.js: -------------------------------------------------------------------------------- 1 | const IpEeg = /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/ 2 | const PasswordPattern = /^(?=.*\d)(?=.*[a-zA-Z])[\da-zA-Z~!@#$%^&*]{8,30}$/ 3 | const NamePattern = /^[a-zA-Z0-9\u4e00-\u9fa5]{1}[a-zA-Z0-9_.\u4e00-\u9fa5-]{0,30}$/ 4 | 5 | // 支持小写英文、数字和- 不能以数字开头 6 | const ClusterNamePattern = /^[a-z]([-a-z0-9]{0,48})[a-z0-9]$/ 7 | // 支持小写英文、数字和- 8 | const StorageNamePattern = /^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$/ 9 | 10 | const EsIndexPattern = /^[a-zA-Z]{1}[a-zA-Z0-9]{0,30}$/ 11 | const VmConfigPattern = /[a-zA-Z0-9]{1}[a-zA-Z0-9]{0,30}$/ 12 | 13 | 14 | export default { 15 | IpEeg, PasswordPattern, NamePattern, EsIndexPattern, VmConfigPattern, ClusterNamePattern, StorageNamePattern 16 | } 17 | -------------------------------------------------------------------------------- /src/utils/permisstion.js: -------------------------------------------------------------------------------- 1 | import store from "@/store"; 2 | 3 | export const checkPermission = function (permissionRoles) { 4 | const roles = store.getters && store.getters.roles 5 | 6 | return roles.some(role => { 7 | return permissionRoles.includes(role) 8 | }) 9 | } 10 | -------------------------------------------------------------------------------- /src/utils/rules.js: -------------------------------------------------------------------------------- 1 | import Global from "@/utils/global_variable" 2 | import i18n from "@/i18n" 3 | 4 | var ipaddr = require("ipaddr.js") 5 | 6 | var checkName = (rule, value, callback) => { 7 | if (!value) { 8 | return callback(new Error(i18n.t("commons.validate.required_msg"))) 9 | } 10 | if (!Global.NamePattern.test(value)) { 11 | return callback(new Error(i18n.t("commons.validate.name_not_compliant"))) 12 | } 13 | callback() 14 | } 15 | 16 | var checkIp = (rule, value, callback) => { 17 | if (!value) { 18 | return callback(new Error(i18n.t("commons.validate.required_msg"))) 19 | } 20 | if (!ipaddr.isValid(value)) { 21 | return callback(new Error(i18n.t("commons.validate.ip_error"))) 22 | } 23 | callback() 24 | } 25 | 26 | 27 | const RequiredRule = { required: true, trigger: "blur", message: i18n.t("commons.validate.required_msg") } 28 | const SelectRequiredRule = { required: true, trigger: "change", message: i18n.t("commons.validate.cannot_be_empty") } 29 | const NameRule = { validator: checkName, required: true, trigger: "blur" } 30 | const IpRule = { validator: checkIp, required: true, trigger: "blur" } 31 | const EmailRule = { type: "email", message: i18n.t("commons.validate.email") } 32 | 33 | // 非零正整数 34 | const NumberRule = { 35 | required: true, 36 | trigger: "blur", 37 | min: 1, 38 | type: "number", 39 | message: i18n.t("commons.validate.number_limit") 40 | } 41 | 42 | // 含零正整数 43 | const NumberWithZeroRule = { 44 | required: true, 45 | trigger: "blur", 46 | min: 0, 47 | type: "number", 48 | message: i18n.t("commons.validate.number_limit") 49 | } 50 | 51 | // 支持小写英文、数字和- 不能以数字开头 52 | const ClusterNameRule = { 53 | required: true, 54 | pattern: Global.ClusterNamePattern, 55 | message: i18n.t("commons.validate.name_not_compliant"), 56 | trigger: "blur" 57 | } 58 | // 支持小写英文、数字和- 59 | const CommonNameRule = { 60 | required: true, 61 | pattern: Global.StorageNamePattern, 62 | message: i18n.t("commons.validate.name_not_compliant"), 63 | trigger: "blur" 64 | } 65 | // 密码规范 66 | const PasswordRule = { 67 | required: true, 68 | pattern: Global.PasswordPattern, 69 | message: i18n.t("commons.validate.password_help"), 70 | trigger: "blur" 71 | } 72 | 73 | const LengthRule = { 74 | min: 1, 75 | max: 30, 76 | message: i18n.t("commons.validate.limit", [1, 30]), 77 | trigger: "blur" 78 | } 79 | export default { 80 | NameRule, RequiredRule, SelectRequiredRule, EmailRule, IpRule, NumberWithZeroRule, NumberRule, ClusterNameRule, CommonNameRule, PasswordRule, LengthRule 81 | } -------------------------------------------------------------------------------- /src/utils/sort.js: -------------------------------------------------------------------------------- 1 | export const sortCommon = function (str1, str2) { 2 | return str1 - str2 3 | } -------------------------------------------------------------------------------- /src/utils/token.js: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie' 2 | 3 | export const TokenKey = 'App-Token' // 自行修改 4 | 5 | export function getToken() { 6 | return Cookies.get(TokenKey) 7 | } 8 | 9 | export function setToken(token) { 10 | return Cookies.set(TokenKey, token) 11 | } 12 | 13 | export function removeToken() { 14 | return Cookies.remove(TokenKey) 15 | } 16 | -------------------------------------------------------------------------------- /src/utils/validate.js: -------------------------------------------------------------------------------- 1 | export function isExternal(path) { 2 | return /^(https?:|mailto:|tel:)/.test(path) 3 | } 4 | 5 | export function isJson(str) { 6 | try { 7 | if (typeof JSON.parse(str) === "object") { 8 | return true 9 | } 10 | } catch { 11 | return false 12 | } 13 | } -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | function resolve(dir) { 4 | return path.join(__dirname, dir) 5 | } 6 | 7 | module.exports = { 8 | productionSourceMap: true, 9 | devServer: { 10 | port: 4200, 11 | open: true, 12 | overlay: { 13 | warnings: false, 14 | errors: true 15 | }, 16 | proxy: { 17 | '/kubepi': { 18 | target: 'http://0.0.0.0:80', 19 | ws: true, 20 | secure: false 21 | }, 22 | '/api': { 23 | target: 'http://127.0.0.1:8080', 24 | ws: true, 25 | secure: false, 26 | }, 27 | '/proxy': { 28 | target: 'http://127.0.0.1:8080', 29 | ws: true, 30 | secure: false, 31 | }, 32 | } 33 | }, 34 | configureWebpack: { 35 | devtool: 'source-map', 36 | resolve: { 37 | alias: { 38 | '@': resolve('src') 39 | } 40 | } 41 | }, 42 | publicPath: '/ui/', 43 | }; 44 | --------------------------------------------------------------------------------