├── .gitignore
├── .idea
├── .gitignore
├── etcd-manage-server.iml
├── misc.xml
├── modules.xml
├── vcs.xml
└── watcherTasks.xml
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── bin
└── config
│ └── cfg.toml
├── data
├── README.md
└── etcd-manage.sql
├── docker
├── cluster-ssl
│ ├── docker-compose.yml
│ └── tlskey
│ │ ├── ca.pem
│ │ ├── server.key
│ │ └── server.pem
└── default
│ ├── docker-compose.yml
│ └── mysqld.cnf
├── etcd-manage-ui
├── .babelrc
├── .editorconfig
├── .postcssrc.js
├── LICENSE
├── Makefile
├── README.md
├── build
│ ├── build.js
│ ├── check-versions.js
│ ├── logo.png
│ ├── utils.js
│ ├── vue-loader.conf.js
│ ├── webpack.base.conf.js
│ ├── webpack.dev.conf.js
│ └── webpack.prod.conf.js
├── config
│ ├── dev.env.js
│ ├── index.js
│ └── prod.env.js
├── go.mod
├── go.sum
├── index.html
├── package-lock.json
├── package.json
├── src
│ ├── App.vue
│ ├── api
│ │ ├── api.js
│ │ ├── kv.js
│ │ ├── logs.js
│ │ ├── passport.js
│ │ ├── role.js
│ │ ├── server.js
│ │ └── user.js
│ ├── assets
│ │ ├── css
│ │ │ └── base.css
│ │ ├── favicon.png
│ │ ├── img
│ │ │ ├── narrow-hover.png
│ │ │ ├── narrow.png
│ │ │ ├── unfold-hover.png
│ │ │ └── unfold.png
│ │ └── imgs
│ │ │ ├── file.png
│ │ │ └── folder.png
│ ├── common
│ │ ├── CloudContainer.vue
│ │ ├── CloudHeader.vue
│ │ └── CloudSideBar.vue
│ ├── components
│ │ ├── KvCard.vue
│ │ ├── KvGrid.vue
│ │ ├── KvList.vue
│ │ └── MonacoEditor.vue
│ ├── config
│ │ └── index.js
│ ├── i18n
│ │ ├── en-US.js
│ │ └── zh-CN.js
│ ├── main.js
│ ├── page
│ │ ├── CloudHome.vue
│ │ ├── EtcdServers.vue
│ │ ├── KV.vue
│ │ ├── Logs.vue
│ │ ├── Members.vue
│ │ ├── Role.vue
│ │ ├── User.vue
│ │ └── bus.js
│ └── router
│ │ └── index.js
├── static
│ ├── .gitkeep
│ ├── etcdui.png
│ └── favicon.ico
└── tpls
│ ├── compile.sh
│ └── tpls.go
├── etcdsdk
├── etcdsdk.go
├── main
│ └── main.go
└── model
│ ├── config.go
│ ├── error.go
│ ├── interface.go
│ └── model.go
├── go.mod
├── go.sum
├── logs
└── access.log
├── main.go
├── program
├── api
│ ├── api.go
│ └── v1
│ │ ├── keys
│ │ └── keys.go
│ │ ├── passport
│ │ └── passport.go
│ │ ├── router.go
│ │ ├── server
│ │ └── server.go
│ │ ├── setings
│ │ ├── role
│ │ │ └── role.go
│ │ └── user
│ │ │ └── user.go
│ │ └── upload
│ │ └── upload.go
├── cache
│ ├── cache.go
│ └── default.go
├── common
│ └── common.go
├── config
│ └── config.go
├── http_ui.go
├── logger
│ └── logger.go
├── middleware.go
├── models
│ ├── client.go
│ ├── etcdServers.go
│ ├── roleEtcdServers.go
│ ├── roles.go
│ ├── time.go
│ └── users.go
├── program.go
└── publicFunc
│ └── publicFunc.go
└── tupian
├── etcd key树以及value展示图.png
├── etcd-key树图.png
├── etcd管理页面.png
├── 多个etcd集群健康图.png
└── 添加etcd界面.png
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.exe~
4 | *.dll
5 | *.so
6 | *.dylib
7 |
8 | # Test binary, build with `go test -c`
9 | *.test
10 |
11 | # Output of the go coverage tool, specifically when used with LiteIDE
12 | *.out
13 |
14 | default.etcd
15 | bin/ems*
16 | bin/logs/*
17 | bin/etcd*
18 | /ca.pem
19 | /cert.pem
20 | /key.pem
21 |
22 | etcd-manage-ui/dist/
23 | etcd-manage-ui/node_modules/
24 | etcd-manage-ui/*.log
25 | etcd-manage-ui/.DS_Store
26 |
27 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Datasource local storage ignored files
5 | /dataSources/
6 | /dataSources.local.xml
7 | # Editor-based HTTP Client requests
8 | /httpRequests/
9 |
--------------------------------------------------------------------------------
/.idea/etcd-manage-server.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/watcherTasks.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM alpine:latest
2 | # 解决go 时区和https请求证书错误问题
3 | RUN apk update \
4 | && apk add ca-certificates \
5 | && update-ca-certificates \
6 | && apk add tzdata
7 | COPY ["./bin", "/app/"]
8 | EXPOSE 10280
9 | WORKDIR /app
10 | CMD ["./ems"]
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 etcd-manage
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | default:
2 | @echo 'Usage of make: [ build | linux_build | windows_build | docker_build | docker_run | clean ]'
3 |
4 | build:
5 | @go build -o ./bin/ems ./
6 |
7 | linux_build:
8 | @CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ./bin/ems ./
9 |
10 | windows_build:
11 | @CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o ./bin/ems.exe ./
12 |
13 | docker_build: linux_build
14 | docker build -t shiguanghuxian/etcd-manage .
15 |
16 | docker_run: docker_build
17 | docker-compose up --force-recreate
18 |
19 | run: build
20 | @./bin/ems
21 |
22 | install: build
23 | @mv ./bin/ems $(GOPATH)/bin/ems
24 |
25 | clean:
26 | @rm -f ./bin/ems*
27 | @rm -f ./bin/logs/*
28 |
29 | .PHONY: default build linux_build windows_build docker_build docker_run clean
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # k8s Etcd manage Server
2 |
3 |
4 | ## 功能介绍
5 | etcd-manage-server 是一个用go编写的etcd可视化管理工具,具有友好的界面,管理key就像管理本地文件一样方便。支持简单权限管理区分只读和读写权限。
6 |
7 | **备注**
8 |
9 | 1. 本项目适配k8s-etcd的乱码问题
10 | 2. 将sql文件导入到mysql数据库(sql文件路径 ./data/etcd-manage.sql),默认用户 admin/111111
11 | 3. 当前只实现了etcd v3 api管理
12 | 4. 在使用时可直接修改默认的两个etcd连接地址为真实可用地址即可开始体验。
13 |
14 | ## 使用
15 | ```
16 | # 1. sql数据导入到mysql数据库
17 |
18 | # 2. 打包、运行
19 | go mod tidy
20 | go mod download
21 | go build .
22 |
23 | ./etcd-manage-server
24 | ```
25 | ## 效果演示
26 |
27 | etcd管理页面
28 | 
29 |
30 | 多个etcd集群健康图
31 | 
32 |
33 | key树以及value展示图
34 | 
35 |
36 | 添加etcd界面
37 | 
38 |
39 | etcd-key树图
40 | 
41 |
42 |
43 |
--------------------------------------------------------------------------------
/bin/config/cfg.toml:
--------------------------------------------------------------------------------
1 | # debug模式
2 | debug = false
3 | # 日志文件路径
4 | log_path = ""
5 |
6 | # http 监听端口
7 | [http]
8 | # 监听地址
9 | address = "127.0.0.1"
10 | # 监听端口
11 | port = 10280
12 |
13 | # 使用 Let's Encrypt 证书 - tls_enable为true优先使用本地证书模式
14 | tls_encrypt_enable = false
15 | # 域名列表
16 | tls_encrypt_domain_names = ["your-domain.com"]
17 |
18 | # 是否启用tls
19 | tls_enable = false
20 | # tls证书文件
21 | [http.tls_config]
22 | cert_file = "cert_file"
23 | key_file = "key_file"
24 |
25 |
26 | ## mysql 配置
27 | [db]
28 | ## 是否调试模式
29 | debug = true
30 | ## 数据库连接地址 - make docker_run 不可以是127.0.0.1
31 | address = "127.0.0.1"
32 | ## 数据库端口
33 | port = 3306
34 | ## 连接池最大连接数
35 | max_idle_conns = 64
36 | ## 默认打开连接数
37 | max_open_conns = 24
38 | ## 数据库用户名
39 | user = "root"
40 | ## 数据库密码
41 | passwd = "123456"
42 | ## 数据库名
43 | db_name = "etcd_servers"
44 |
--------------------------------------------------------------------------------
/data/README.md:
--------------------------------------------------------------------------------
1 |
2 | # mysql相关命令
3 |
4 | ## mysql数据备份语句
5 | ```
6 | mysqldump -u root -p123456 -h127.0.0.1 -B --add-drop-database --column-statistics=0 etcd_servers > etcd-manage.sql
7 | ```
8 | ## mysql导入语句
9 | ```
10 | mysql -uroot -p123456 -h127.0.0.1 < etcd-manage.sql
11 | ```
12 |
13 |
--------------------------------------------------------------------------------
/data/etcd-manage.sql:
--------------------------------------------------------------------------------
1 | -- MySQL dump 10.13 Distrib 8.0.17, for osx10.14 (x86_64)
2 | --
3 | -- Host: 127.0.0.1 Database: etcd_servers
4 | -- ------------------------------------------------------
5 | -- Server version 5.7.30
6 |
7 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
8 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
9 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
10 | /*!50503 SET NAMES utf8mb4 */;
11 | /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
12 | /*!40103 SET TIME_ZONE='+00:00' */;
13 | /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
14 | /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
15 | /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
16 | /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
17 |
18 | --
19 | -- Current Database: `etcd_servers`
20 | --
21 |
22 | /*!40000 DROP DATABASE IF EXISTS `etcd_servers`*/;
23 |
24 | CREATE DATABASE /*!32312 IF NOT EXISTS*/ `etcd_servers` /*!40100 DEFAULT CHARACTER SET latin1 */;
25 |
26 | USE `etcd_servers`;
27 |
28 | --
29 | -- Table structure for table `etcd_servers`
30 | --
31 |
32 | DROP TABLE IF EXISTS `etcd_servers`;
33 | /*!40101 SET @saved_cs_client = @@character_set_client */;
34 | /*!50503 SET character_set_client = utf8mb4 */;
35 | CREATE TABLE `etcd_servers` (
36 | `id` int(11) NOT NULL AUTO_INCREMENT,
37 | `version` varchar(3) NOT NULL DEFAULT 'v3' COMMENT 'etcd版本',
38 | `name` varchar(30) NOT NULL DEFAULT '' COMMENT 'etcd服务名字',
39 | `address` varchar(600) NOT NULL COMMENT 'etcd地址列表',
40 | `prefix` varchar(100) NOT NULL DEFAULT '' COMMENT 'key前缀,建议不为空,防止大量key',
41 | `tls_enable` varchar(5) NOT NULL DEFAULT 'true' COMMENT '是否启用tls连接',
42 | `cert_file` text NOT NULL COMMENT '证书',
43 | `key_file` text NOT NULL COMMENT '证书',
44 | `ca_file` text NOT NULL COMMENT '证书',
45 | `username` varchar(60) NOT NULL DEFAULT '' COMMENT '用户名',
46 | `password` varchar(60) NOT NULL DEFAULT '' COMMENT '密码',
47 | `desc` varchar(300) NOT NULL COMMENT '描述信息',
48 | `created_at` datetime DEFAULT NULL COMMENT '添加时间',
49 | `updated_at` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
50 | PRIMARY KEY (`id`) USING BTREE
51 | ) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COMMENT='etched server列表';
52 | /*!40101 SET character_set_client = @saved_cs_client */;
53 |
54 | --
55 | -- Dumping data for table `etcd_servers`
56 | --
57 |
58 | LOCK TABLES `etcd_servers` WRITE;
59 | /*!40000 ALTER TABLE `etcd_servers` DISABLE KEYS */;
60 | INSERT INTO `etcd_servers` VALUES (8,'v3','lqx','https://10.20.3.102:2379','','true','LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURJRENDQWdpZ0F3SUJBZ0lJQ1dRWENPM1A3QUF3RFFZSktvWklodmNOQVFFTEJRQXdFakVRTUE0R0ExVUUKQXhNSFpYUmpaQzFqWVRBZUZ3MHlNREE0TURRd01qVXlNelZhRncweU1UQTRNRFF3TWpVeU16VmFNQmd4RmpBVQpCZ05WQkFNVERXeHhlQzF0WVhOMFpYSXRNREV3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLCkFvSUJBUURoNWJqUDNXN2NzVlhEaHdKWHlrWUR6RjRLaldrYXFQc0xNNmVFRGhYZWJXMGVaZi9RT3QrcUFxaWgKbTdPZ3BISFp6VFhFODVVSHV5QkFTM3daQU0wZDRqODY1bHFwaTJuTU41Vm5GTkNMOEpmdlRiTVdocFpleS9DVApPc1J3amhLQ0JHc2kvbHE5SEt0WnJtdmd0TjRNVlhyanB4cE5Td1BaeDFjNENBUXJPOFk1MkFpRlU5cFJoNmtHCmZ1TC82NlFGaDlpTnN2eExOUnFmVFpLY3oxUWxrZEdyWUdqVXE1blVrV3MwQnM3NmpmU3hQRFBydG1XN1lhdnEKbHo5RjVQSzZaSTY3Mm84MWNzOTVQR0VKV2JRTWpBaHRnckpKMGdoenVoTU15YlRYQWFmUCtQZkUxMEdQWlRMUgpvWit5c1U2dWI2eHNwVm1NZzR0b1lxMWhVWW4zQWdNQkFBR2pkREJ5TUE0R0ExVWREd0VCL3dRRUF3SUZvREFkCkJnTlZIU1VFRmpBVUJnZ3JCZ0VGQlFjREFRWUlLd1lCQlFVSEF3SXdRUVlEVlIwUkJEb3dPSUlOYkhGNExXMWgKYzNSbGNpMHdNWUlKYkc5allXeG9iM04waHdRS0ZBTm1od1IvQUFBQmh4QUFBQUFBQUFBQUFBQUFBQUFBQUFBQgpNQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUJBUUFOODNxZFJ2eWl1ZVZkd09ZOWozVG0raFVxdFN5anV3eWI3aGJSCnR2UDNJb3RLbm50ZzNpRndYZVpNQkxTWUhvWU9jVXJ6ZndCUzZHOUgrdjBkRFh0Vi8vR3ljKzQ4QUVLbjhjZU8KT2xIRnJJQkRUMkE5VVBZTUhkVmNMcEZjOHJvdGhPQmJjd3RGSFB6aStqUlI5dE9oYjhQY0l0WFhTL29nOXVNMQpqaE5zU282VHlhYlA0U3BVSUhVNXpkbEMxaHBVTXk4b2V0SnI0d1c3SEpvK00xd2pjWmNNTU5VTEE4UEllb2xICndtR1dFa1g2OFZVQzhGU0FDK1ZyeUlLM20vTWVjQkEwMFFseGtBZmprbWt2b2NqbHhvVTJ1ZzVLTHYyWUthb0YKeC9mVjFuOUNuTFlpdmU3VWI5T2pGTmxjazJZOHpMNjRhTXlnVzZvOWRWTzBDcFVNCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K','LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBNGVXNHo5MXUzTEZWdzRjQ1Y4cEdBOHhlQ28xcEdxajdDek9uaEE0VjNtMXRIbVgvCjBEcmZxZ0tvb1p1em9LUngyYzAxeFBPVkI3c2dRRXQ4R1FETkhlSS9PdVphcVl0cHpEZVZaeFRRaS9DWDcwMnoKRm9hV1hzdndrenJFY0k0U2dnUnJJdjVhdlJ5cldhNXI0TFRlREZWNjQ2Y2FUVXNEMmNkWE9BZ0VLenZHT2RnSQpoVlBhVVllcEJuN2kvK3VrQllmWWpiTDhTelVhbjAyU25NOVVKWkhScTJCbzFLdVoxSkZyTkFiTytvMzBzVHd6CjY3Wmx1MkdyNnBjL1JlVHl1bVNPdTlxUE5YTFBlVHhoQ1ZtMERJd0liWUt5U2RJSWM3b1RETW0wMXdHbnovajMKeE5kQmoyVXkwYUdmc3JGT3JtK3NiS1ZaaklPTGFHS3RZVkdKOXdJREFRQUJBb0lCQUFuQzg0bUgrQkp4VjFOcgpzaDQ1RDIxNmwxVzlacDFRVUFqYjRwRkNTbytpQ3VVVlkwaU1RcjRGLzJOOFp2YTZKSEZVL00zVitNcXN1MmdMCjJ3RDVsK09DczFqSU80SzRFNHBQbkpVbndSdEsra1hOQmNBamNMd0g5QTFvckxSd2J6eFBGSkllaTYxQVgrY3cKTWxremQ2VHd6SzZwdWVrOUpKMTBqOEJNR0dJRnpuSnlBMm5EYjU4WFJCM2UySnFRQlFEQ1BaM1BJaHE3UlJrNQp3Q1ExMG9jbXdKbUQwTGkydUlqRW5JOTU4ZHRzVVdBSmI5Z1FoNTVIeFphSEU2R05pTkRQdlpxZCtyc1ZJSGRsClFqR2MvQTY0NVpvSnNIQWczeGVyWnArdytvT0pmc253SEJkQ1ZpZlFTR0lWa1g0a243QXVzZFZrSDFWN1JqYjEKT2NFWWd3RUNnWUVBNzM3YnpVT3RDRWUvNnNsaHhMYm9JYXVLQXk3bDFDdk16MWNYMXJDaDV2Z2V3bW4yd3RzRAowZE9xSWdGcXZ1MVIvTTltVkpsZzNYeTRRc01oc2Vabk9BRTJIeGZIcFByVkoyOGJlMm1paWdZQjI3ZTBnY0FpCnhKb3h2L2pSazlZRTZzZnJYN3lOU2pIQWJBWGszWjh0Y1BnU0pxRktDUUp3YmgyR2QrcTdBZWtDZ1lFQThYYjMKWE9kY05aczhteEFDZjhqMUZjNTlRZGNqOGJlQ1YrU0ViM09YMkhxMlZobjJkbzltbFcyaTA2ejFuVTU3NVdqUQpHUW43NDNpUjYzb3pqSmZLNU5EKzJidWRSMnZOeVo2VkxzUHlySlZIUUxHSDBhT1pvL3RMdTZoQStCazJZWm83ClZOWFBReHFIMVF1UG1UNWFIbHpXMVBrWnpZQndFWVVnSHpKbjROOENnWUI3QnczZW1mOVBHNXlJL2I1TmtUQVoKNjhiUDc4MThlcXVSYjBuOXJXcmQvV08vdHpOZDBhaGFwNExrU1JvT3psYXBxZGtGYUcwTUdqK0ZmRHZZNldUbwpyaWFoUGxQK2VpUDVSK2cwTTRXZHBZeGExRG5UMVdHRzRUYmhTTzVRSlVjTlhIbWJDbjhDT0NDQzNWdytSTURSCklYNGhmZ1ZNTDRhVjZuRGpOUit2MFFLQmdRQ3BGTGdEK3hJTGk0ZDF3VkV1cjhaR25kQUNBYWR1eENSbWJXTDcKTkFNNUdEeURzQ3h3T3R3SGVMMFM0a01mQXUwbzZDc0h6WUR2ZU9jYzcvWVcxZGZDUUVLa3JvWmtrNjJIS0IrbwpucGRZbUROTHJzUy9YSUxpVzc1ZFNtVXNGV09LRnRqQy8vRGhPVHV5U0NVbWxvMitReDVBQmFvMngyQXlOSGtZCnYxQVVHd0tCZ0JNMklPWDIzc3VSd0o1UHU1eGowQzFkVzh1QzFaYjEzeWVoZm93ZjdueFFxWmhtRHk4cGlOUmUKUnFKQlFhaFpnYTdKWi9uZWtKZTh5S3hueGhtQWdJSy9hTmxJMkQ3NThEL1d5NENQZVluQ2hKS0VZTzh5bHFwMgovVk5JeXpIOHNLNWs1M1E0UzNmdThlNlVyM29KcndFQWtkVmZSeGFsbzU5V1dwWjY2bkp0Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==','LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN3akNDQWFxZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFTTVJBd0RnWURWUVFERXdkbGRHTmsKTFdOaE1CNFhEVEl3TURnd05EQXlOVEl6TlZvWERUTXdNRGd3TWpBeU5USXpOVm93RWpFUU1BNEdBMVVFQXhNSApaWFJqWkMxallUQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU5sN0J6YzZzcWFNCjJvbnAwOVMxTndVK0xKRkFFeXdLN2lqUHV4TlRSV2VQSzFwajF5TmxiUVlDV1pHZUExMUFQRVFUdEVzc0c1dlUKeWJTNHUydzFkM3Q2WEpwb2FpZ3pOVkVPaWVlUkgxOE9lb2pVNWdWM2hZTlNUTVByQUIrZXJ0YVcxNkMzQmhTRgprVmFrc2I5UHIxNU0ramE3WUpCQnhnL2hNOEtIbzU2NVl3Z0hJUXoxM253R3JqMUVPc2x0YkVRN0xValFRdU9wCkpmYU82WElCZWVhUlFJWm9yZjZLTVdNY29ScG9nbFFwcXRXWVh1cTUwOUNWd3NqTWNmZDEwV3dQUHE0aWt3RE0KYVFYS0JXTWhwczR4azY4SWcrVVNLRGkxZXdzOThrOFo5UlQ5b001Z3FBVVQ1RzRyTGpWNzZSZDQrR2xiblFpdwpkUHFxdzdMMkwrOENBd0VBQWFNak1DRXdEZ1lEVlIwUEFRSC9CQVFEQWdLa01BOEdBMVVkRXdFQi93UUZNQU1CCkFmOHdEUVlKS29aSWh2Y05BUUVMQlFBRGdnRUJBSDdkRXNiSWZWN3RLNTlEZjBIRFNLd1ZLS3dkWGlzbTlqTkYKYUI2SG5ST1RvaE5lb2NNYXFWRlo5N0RWZytlTzh0UUJVdFhrVlg1N08vVzY2dFMrckNIcW04cVJYNzJFMVRzRQpmQzI2QW9tdXdEbnBHOGxvUXdLV2x4L2YrY1BEdkVhZ0MrLytDZVI3dmZrc09TbktGbVVhS0l4M0MxSWVYT1RKCk1jOWpVQ1Z0N05xbzBjY3dDOUplMkVqd3lnYkVXUEVOZnczMGRtZ1B4UTNKdXJRSmhLZlhFU21lZ0hOTUw4R08KQnFHY3YzY1A5TWQrbzhJdUVpby9BUkdCWjE2QXFrSmozTDY2TGc1bjBlVldLVWZpOE9hd3BvL2F3Q2JuaXhCMgoyQ3lZRkFiNHR0VkVZT3dpT2FjTkRiaXQvc0xuejFMcHpkT2xlQy96WjZTd2c4eWVBbE09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K','','','','2020-08-05 11:18:41','2020-08-05 11:18:41'),(9,'v3','local','http://127.0.0.1:2379','','false','','','','','','','2020-08-06 15:07:59','2020-08-06 15:16:35');
61 | /*!40000 ALTER TABLE `etcd_servers` ENABLE KEYS */;
62 | UNLOCK TABLES;
63 |
64 | --
65 | -- Table structure for table `role_etcd_servers`
66 | --
67 |
68 | DROP TABLE IF EXISTS `role_etcd_servers`;
69 | /*!40101 SET @saved_cs_client = @@character_set_client */;
70 | /*!50503 SET character_set_client = utf8mb4 */;
71 | CREATE TABLE `role_etcd_servers` (
72 | `id` int(11) NOT NULL AUTO_INCREMENT,
73 | `etcd_server_id` int(11) NOT NULL DEFAULT '0' COMMENT 'etcd服务id',
74 | `role_id` int(11) NOT NULL COMMENT '角色id',
75 | `type` tinyint(4) NOT NULL DEFAULT '0' COMMENT '0读 1写 -1无任何权限',
76 | `created_at` datetime NOT NULL COMMENT '添加时间',
77 | `updated_at` datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
78 | PRIMARY KEY (`id`),
79 | KEY `idx_role_id` (`role_id`),
80 | KEY `idx_etcd_server_id` (`etcd_server_id`)
81 | ) ENGINE=InnoDB AUTO_INCREMENT=39 DEFAULT CHARSET=utf8mb4 COMMENT='角色权限表';
82 | /*!40101 SET character_set_client = @saved_cs_client */;
83 |
84 | --
85 | -- Dumping data for table `role_etcd_servers`
86 | --
87 |
88 | LOCK TABLES `role_etcd_servers` WRITE;
89 | /*!40000 ALTER TABLE `role_etcd_servers` DISABLE KEYS */;
90 | INSERT INTO `role_etcd_servers` VALUES (37,8,1,1,'2020-08-05 11:18:41','2020-08-05 11:18:41'),(38,9,1,1,'2020-08-06 15:07:59','2020-08-06 15:07:59');
91 | /*!40000 ALTER TABLE `role_etcd_servers` ENABLE KEYS */;
92 | UNLOCK TABLES;
93 |
94 | --
95 | -- Table structure for table `roles`
96 | --
97 |
98 | DROP TABLE IF EXISTS `roles`;
99 | /*!40101 SET @saved_cs_client = @@character_set_client */;
100 | /*!50503 SET character_set_client = utf8mb4 */;
101 | CREATE TABLE `roles` (
102 | `id` int(11) NOT NULL AUTO_INCREMENT,
103 | `name` varchar(60) NOT NULL DEFAULT '' COMMENT '角色名',
104 | `created_at` datetime NOT NULL COMMENT '添加时间',
105 | `updated_at` datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
106 | PRIMARY KEY (`id`) USING BTREE
107 | ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COMMENT='角色表';
108 | /*!40101 SET character_set_client = @saved_cs_client */;
109 |
110 | --
111 | -- Dumping data for table `roles`
112 | --
113 |
114 | LOCK TABLES `roles` WRITE;
115 | /*!40000 ALTER TABLE `roles` DISABLE KEYS */;
116 | INSERT INTO `roles` VALUES (1,'高级管理员','2019-08-15 03:43:44','2020-07-10 18:18:37'),(2,'开发只读','2019-08-18 04:14:42','2019-08-18 04:32:02'),(3,'开发管理','2019-08-18 04:25:05','2019-08-18 04:32:21');
117 | /*!40000 ALTER TABLE `roles` ENABLE KEYS */;
118 | UNLOCK TABLES;
119 |
120 | --
121 | -- Table structure for table `users`
122 | --
123 |
124 | DROP TABLE IF EXISTS `users`;
125 | /*!40101 SET @saved_cs_client = @@character_set_client */;
126 | /*!50503 SET character_set_client = utf8mb4 */;
127 | CREATE TABLE `users` (
128 | `id` int(11) NOT NULL AUTO_INCREMENT,
129 | `username` varchar(60) NOT NULL DEFAULT '' COMMENT '用户名',
130 | `password` char(32) NOT NULL DEFAULT '' COMMENT '密码',
131 | `email` varchar(300) NOT NULL DEFAULT '' COMMENT '邮箱',
132 | `role_id` int(11) NOT NULL DEFAULT '0' COMMENT '角色id',
133 | `created_at` datetime NOT NULL COMMENT '添加时间',
134 | `updated_at` datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
135 | PRIMARY KEY (`id`)
136 | ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
137 | /*!40101 SET character_set_client = @saved_cs_client */;
138 |
139 | --
140 | -- Dumping data for table `users`
141 | --
142 |
143 | LOCK TABLES `users` WRITE;
144 | /*!40000 ALTER TABLE `users` DISABLE KEYS */;
145 | INSERT INTO `users` VALUES (1,'admin','94d9484ada1e5639810258b515fa61f1','',1,'2019-08-12 20:19:15','2020-07-10 18:18:43');
146 | /*!40000 ALTER TABLE `users` ENABLE KEYS */;
147 | UNLOCK TABLES;
148 | /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
149 |
150 | /*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
151 | /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
152 | /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
153 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
154 | /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
155 | /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
156 | /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
157 |
158 | -- Dump completed on 2020-08-06 23:40:32
159 |
--------------------------------------------------------------------------------
/docker/cluster-ssl/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | etcd0:
4 | image: quay.io/coreos/etcd:v3.3
5 | ports:
6 | - 2379:2379
7 | - 2380
8 | volumes:
9 | - ./tlskey:/etc/etcd/etcdSSL
10 | environment:
11 | - ETCD_NAME=etcd0
12 | - ETCD_CLIENT_CERT_AUTH=true
13 | - ETCD_TRUSTED_CA_FILE=/etc/etcd/etcdSSL/ca.pem
14 | - ETCD_CERT_FILE=/etc/etcd/etcdSSL/server.pem
15 | - ETCD_KEY_FILE=/etc/etcd/etcdSSL/server.key
16 | - ETCD_ADVERTISE_CLIENT_URLS=https://127.0.0.1:2379 # 此地址告诉客户端访问此服务url
17 | - ETCD_LISTEN_CLIENT_URLS=https://0.0.0.0:2379
18 | - ETCD_LISTEN_PEER_URLS=https://0.0.0.0:2380
19 | - ETCD_INITIAL_CLUSTER_TOKEN=etcd-cluster
20 | - ETCD_INITIAL_CLUSTER=etcd0=https://etcd0:2380,etcd1=https://etcd1:2380,etcd2=https://etcd2:2380
21 | - ETCD_INITIAL_CLUSTER_STATE=new
22 | - ETCD_INITIAL_ADVERTISE_PEER_URLS=https://etcd0:2380
23 | - ETCD_PEER_CERT_FILE=/etc/etcd/etcdSSL/server.pem
24 | - ETCD_PEER_KEY_FILE=/etc/etcd/etcdSSL/server.key
25 | - ETCD_PEER_TRUSTED_CA_FILE=/etc/etcd/etcdSSL/ca.pem
26 |
27 | etcd1:
28 | image: quay.io/coreos/etcd:v3.3
29 | ports:
30 | - 12379:2379
31 | - 2380
32 | volumes:
33 | - ./tlskey:/etc/etcd/etcdSSL
34 | environment:
35 | - ETCD_NAME=etcd1
36 | - ETCD_CLIENT_CERT_AUTH=true
37 | - ETCD_TRUSTED_CA_FILE=/etc/etcd/etcdSSL/ca.pem
38 | - ETCD_CERT_FILE=/etc/etcd/etcdSSL/server.pem
39 | - ETCD_KEY_FILE=/etc/etcd/etcdSSL/server.key
40 | - ETCD_ADVERTISE_CLIENT_URLS=https://127.0.0.1:12379 # 此地址告诉客户端访问此服务url
41 | - ETCD_LISTEN_CLIENT_URLS=https://0.0.0.0:2379
42 | - ETCD_LISTEN_PEER_URLS=https://0.0.0.0:2380
43 | - ETCD_INITIAL_CLUSTER_TOKEN=etcd-cluster
44 | - ETCD_INITIAL_CLUSTER=etcd0=https://etcd0:2380,etcd1=https://etcd1:2380,etcd2=https://etcd2:2380
45 | - ETCD_INITIAL_CLUSTER_STATE=new
46 | - ETCD_INITIAL_ADVERTISE_PEER_URLS=https://etcd1:2380
47 | - ETCD_PEER_CERT_FILE=/etc/etcd/etcdSSL/server.pem
48 | - ETCD_PEER_KEY_FILE=/etc/etcd/etcdSSL/server.key
49 | - ETCD_PEER_TRUSTED_CA_FILE=/etc/etcd/etcdSSL/ca.pem
50 |
51 | etcd2:
52 | image: quay.io/coreos/etcd:v3.3
53 | ports:
54 | - 22379:2379
55 | - 2380
56 | volumes:
57 | - ./tlskey:/etc/etcd/etcdSSL
58 | environment:
59 | - ETCD_NAME=etcd2
60 | - ETCD_CLIENT_CERT_AUTH=true
61 | - ETCD_TRUSTED_CA_FILE=/etc/etcd/etcdSSL/ca.pem
62 | - ETCD_CERT_FILE=/etc/etcd/etcdSSL/server.pem
63 | - ETCD_KEY_FILE=/etc/etcd/etcdSSL/server.key
64 | - ETCD_ADVERTISE_CLIENT_URLS=https://127.0.0.1:22379 # 此地址告诉客户端访问此服务url
65 | - ETCD_LISTEN_CLIENT_URLS=https://0.0.0.0:2379
66 | - ETCD_LISTEN_PEER_URLS=https://0.0.0.0:2380
67 | - ETCD_INITIAL_CLUSTER_TOKEN=etcd-cluster
68 | - ETCD_INITIAL_CLUSTER=etcd0=https://etcd0:2380,etcd1=https://etcd1:2380,etcd2=https://etcd2:2380
69 | - ETCD_INITIAL_CLUSTER_STATE=new
70 | - ETCD_INITIAL_ADVERTISE_PEER_URLS=https://etcd2:2380
71 | - ETCD_PEER_CERT_FILE=/etc/etcd/etcdSSL/server.pem
72 | - ETCD_PEER_KEY_FILE=/etc/etcd/etcdSSL/server.key
73 | - ETCD_PEER_TRUSTED_CA_FILE=/etc/etcd/etcdSSL/ca.pem
74 |
--------------------------------------------------------------------------------
/docker/cluster-ssl/tlskey/ca.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | 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
32 | Tv9qqO+FUmzVK4MPJG4USAR9
33 | -----END CERTIFICATE-----
34 |
--------------------------------------------------------------------------------
/docker/cluster-ssl/tlskey/server.key:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | 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
51 | -----END RSA PRIVATE KEY-----
52 |
--------------------------------------------------------------------------------
/docker/cluster-ssl/tlskey/server.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | 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
34 | LAV+fqGI6tQ8L3gwFoy9S11dIsSM9f1vvjU=
35 | -----END CERTIFICATE-----
36 |
--------------------------------------------------------------------------------
/docker/default/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 |
3 | services:
4 | etcd:
5 | image: "quay.azk8s.cn/coreos/etcd:v3.3"
6 | container_name: "db-etcd"
7 | environment:
8 | ETCD_ADVERTISE_CLIENT_URLS: "http://0.0.0.0:2379"
9 | ETCD_LISTEN_CLIENT_URLS: "http://0.0.0.0:2379"
10 | ETCDCTL_API: "3"
11 | volumes:
12 | - ./default.etcd:/default.etcd
13 | ports:
14 | - 2379:2379
15 | - 2380:2380
16 | - 4001:4001
17 | mysql:
18 | image: mysql:5.7
19 | container_name: db-mysql
20 | ports:
21 | - 3306:3306
22 | volumes:
23 | - ./mysql/data:/var/lib/mysql
24 | - ./mysql/conf/mysqld.cnf:/etc/mysql/mysql.conf.d/mysqld.cnf
25 | environment:
26 | MYSQL_ROOT_PASSWORD: "123456"
27 |
--------------------------------------------------------------------------------
/docker/default/mysqld.cnf:
--------------------------------------------------------------------------------
1 | [mysqld_safe]
2 | socket = /var/run/mysqld/mysqld.sock
3 | nice = 0
4 | [mysqld]
5 | user = mysql
6 | pid-file = /var/run/mysqld/mysqld.pid
7 | socket = /var/run/mysqld/mysqld.sock
8 | port = 3306
9 | basedir = /usr
10 | datadir = /var/lib/mysql
11 | tmpdir = /tmp
12 | lc-messages-dir = /usr/share/mysql
13 | skip-external-locking
14 | bind-address = 0.0.0.0
15 | key_buffer_size = 16M
16 | max_allowed_packet = 16M
17 | thread_stack = 192K
18 | thread_cache_size = 8
19 | myisam-recover-options = BACKUP
20 | query_cache_limit = 1M
21 | query_cache_size = 16M
22 | log_error = /var/log/mysql/error.log
23 | expire_logs_days = 10
24 | max_binlog_size = 100M
25 |
--------------------------------------------------------------------------------
/etcd-manage-ui/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["env", {
4 | "modules": false,
5 | "targets": {
6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
7 | }
8 | }],
9 | "stage-2"
10 | ],
11 | "plugins": ["transform-vue-jsx", "transform-runtime"]
12 | }
13 |
--------------------------------------------------------------------------------
/etcd-manage-ui/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/etcd-manage-ui/.postcssrc.js:
--------------------------------------------------------------------------------
1 | // https://github.com/michael-ciniawsky/postcss-load-config
2 |
3 | module.exports = {
4 | "plugins": {
5 | "postcss-import": {},
6 | "postcss-url": {},
7 | // to edit target browsers: use "browserslist" field in package.json
8 | "autoprefixer": {}
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/etcd-manage-ui/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 etcd-manage
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/etcd-manage-ui/Makefile:
--------------------------------------------------------------------------------
1 | default:
2 | @echo 'Usage of make: [ build | run | go | clean ]'
3 |
4 | build:
5 | npm run build
6 |
7 | run:
8 | npm run dev
9 |
10 | go: build
11 | @cp -r dist tpls && cd tpls && ./compile.sh
12 |
13 | clean:
14 | @rm -f ./dist
15 | @rm -f ./tpls/dist
16 | @rm -f ./tpls/tpls.go
17 |
18 | .PHONY: default build run go clean
--------------------------------------------------------------------------------
/etcd-manage-ui/README.md:
--------------------------------------------------------------------------------
1 | # Etcd UI
2 |
3 |
4 | ## 功能介绍
5 |
6 | etcd-manage 是一个用go编写的etcd管理工具,具有友好的界面(类似阿里云后台),管理key就像管理本地文件一样方便。支持简单权限管理区分只读和读写权限。
7 |
8 | 开源地址: [https://github.com/etcd-manage](https://github.com/etcd-manage)
9 |
10 | **备注**
11 |
12 | 1. 用helm部署时注意修改mysql连接相关信息,可不修改使用默认安装直接使用
13 | 2. 将sql文件导入到mysql数据库,默认用户 admin/111111 [etcd-manage.sql](https://raw.githubusercontent.com/cloudnativeapp/charts/master/submitted/etcd-manage/sql/etcd-manage.sql)
14 | 3. 此程序为2.0版本,实现1.0功能 1.0项目地址 [https://github.com/shiguanghuxian/etcd-manage](https://github.com/shiguanghuxian/etcd-manage)
15 | 4. 下一步开发对中英双语言做全面支持,当前对中文支持友好。
16 | 5. 当前只实现了etcd v3 api管理key v2在路上。
17 | 6. 在使用时可直接修改默认的两个etcd连接地址为真实可用地址即可开始体验。
18 |
19 |
20 | ## HELM 安装使用
21 |
22 | 提示:安装后通过kubectl get pods看到两个pod专题都是Running表示服务可用,首次mysql需要初始化会慢一些,大概1分钟。
23 |
24 | ```shell
25 | helm install my-etcd-manage etcd-manage
26 | 或
27 | cd path/etcd-manage
28 | helm package .
29 | helm install my-etcd-manage etcd-manage-1.0.0.tgz
30 |
31 | ```
32 |
33 | 运行后看到输出:
34 |
35 | ```shell
36 | NAME: my-etcd-manage
37 | LAST DEPLOYED: 2019-08-26 20:16:23.182943 +0800 CST m=+0.068774555
38 | NAMESPACE: default
39 | STATUS: deployed
40 |
41 | NOTES:
42 | 1. Get the application URL by running these commands:
43 | export POD_NAME=$(kubectl get pods -l "app=etcd-manage,release=my-etcd-manage" -o jsonpath="{.items[0].metadata.name}")
44 | echo "Visit http://127.0.0.1:10280 to use your application"
45 | kubectl port-forward $POD_NAME 10280:10280
46 |
47 | # kubectl get pods
48 | NAME READY STATUS RESTARTS AGE
49 | my-etcd-manage-f4bc496f5-bpg99 1/1 Running 2 25s
50 | my-etcd-manage-mysql-5577cd9b-4nqr2 1/1 Running 0 25s
51 |
52 | ```
53 |
54 | 执行完 NOTES 中提示命令的命令即可在浏览器中访问 `http://127.0.0.1:10280/ui` 查看。注意url端口后边路径为/ui
55 |
56 | 默认用户 admin/111111
57 |
58 | 如果NOTES命令执行错误可执行
59 |
60 | ```shell
61 | kubectl port-forward my-etcd-manage-f4bc496f5-bpg99 10280:10280 // my-etcd-manage-f4bc496f5-bpg99 为 kubectl get pods 中获取的值
62 | ```
63 |
64 | ## HELM 使用参数
65 |
66 | 使用数据库参数可使用自己mysql服务,默认使用依赖的charts中mysql服务,如果使用自己mysql请导入sql文件 [etcd-manage.sql](https://raw.githubusercontent.com/cloudnativeapp/charts/master/submitted/etcd-manage/sql/etcd-manage.sql)
67 |
68 | ```shell
69 | helm install my-etcd-manage etcd-manage --set database.address="你的数据库ip地址" --set database.port=3306 --set database.user="user" --set database.passwd="密码" --set database.db_name="etcd-manage"
70 | ```
71 |
72 | **参数介绍**
73 |
74 | | 参数名 | 简述 | 示例 |
75 | | ----- | ----- | ---|
76 | | database.address | mysql数据库地址 | 192.168.1.88 |
77 | | database.port | mysql数据库端口 | 3306 |
78 | | database.user | mysql用户名 | root |
79 | | database.passwd | mysql用户密码 | z123456 |
80 | | database.db_name | 导入etcd-manage.sql的数据库 | etcd-manage |
81 |
82 |
83 | ## 效果演示
84 |
85 | etcd服务列表管理
86 |
87 | 
88 |
89 | key 管理
90 |
91 | 
92 |
93 | key 编辑
94 |
95 | 
96 |
97 | key 查看
98 |
99 | 
100 |
101 | 用户管理
102 |
103 | 
104 |
--------------------------------------------------------------------------------
/etcd-manage-ui/build/build.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | require('./check-versions')()
3 |
4 | process.env.NODE_ENV = 'production'
5 |
6 | const ora = require('ora')
7 | const rm = require('rimraf')
8 | const path = require('path')
9 | const chalk = require('chalk')
10 | const webpack = require('webpack')
11 | const config = require('../config')
12 | const webpackConfig = require('./webpack.prod.conf')
13 |
14 | const spinner = ora('building for production...')
15 | spinner.start()
16 |
17 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
18 | if (err) throw err
19 | webpack(webpackConfig, (err, stats) => {
20 | spinner.stop()
21 | if (err) throw err
22 | process.stdout.write(stats.toString({
23 | colors: true,
24 | modules: false,
25 | children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build.
26 | chunks: false,
27 | chunkModules: false
28 | }) + '\n\n')
29 |
30 | if (stats.hasErrors()) {
31 | console.log(chalk.red(' Build failed with errors.\n'))
32 | process.exit(1)
33 | }
34 |
35 | console.log(chalk.cyan(' Build complete.\n'))
36 | console.log(chalk.yellow(
37 | ' Tip: built files are meant to be served over an HTTP server.\n' +
38 | ' Opening index.html over file:// won\'t work.\n'
39 | ))
40 | })
41 | })
42 |
--------------------------------------------------------------------------------
/etcd-manage-ui/build/check-versions.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const chalk = require('chalk')
3 | const semver = require('semver')
4 | const packageConfig = require('../package.json')
5 | const shell = require('shelljs')
6 |
7 | function exec (cmd) {
8 | return require('child_process').execSync(cmd).toString().trim()
9 | }
10 |
11 | const versionRequirements = [
12 | {
13 | name: 'node',
14 | currentVersion: semver.clean(process.version),
15 | versionRequirement: packageConfig.engines.node
16 | }
17 | ]
18 |
19 | if (shell.which('npm')) {
20 | versionRequirements.push({
21 | name: 'npm',
22 | currentVersion: exec('npm --version'),
23 | versionRequirement: packageConfig.engines.npm
24 | })
25 | }
26 |
27 | module.exports = function () {
28 | const warnings = []
29 |
30 | for (let i = 0; i < versionRequirements.length; i++) {
31 | const mod = versionRequirements[i]
32 |
33 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
34 | warnings.push(mod.name + ': ' +
35 | chalk.red(mod.currentVersion) + ' should be ' +
36 | chalk.green(mod.versionRequirement)
37 | )
38 | }
39 | }
40 |
41 | if (warnings.length) {
42 | console.log('')
43 | console.log(chalk.yellow('To use this template, you must update following to modules:'))
44 | console.log()
45 |
46 | for (let i = 0; i < warnings.length; i++) {
47 | const warning = warnings[i]
48 | console.log(' ' + warning)
49 | }
50 |
51 | console.log()
52 | process.exit(1)
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/etcd-manage-ui/build/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/comqx/k8s-etcd-ui/e860824e994da8c48aea5cd3e9283f374828a8c9/etcd-manage-ui/build/logo.png
--------------------------------------------------------------------------------
/etcd-manage-ui/build/utils.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const path = require('path')
3 | const config = require('../config')
4 | const ExtractTextPlugin = require('extract-text-webpack-plugin')
5 | const packageConfig = require('../package.json')
6 |
7 | exports.assetsPath = function (_path) {
8 | const assetsSubDirectory = process.env.NODE_ENV === 'production'
9 | ? config.build.assetsSubDirectory
10 | : config.dev.assetsSubDirectory
11 |
12 | return path.posix.join(assetsSubDirectory, _path)
13 | }
14 |
15 | exports.cssLoaders = function (options) {
16 | options = options || {}
17 |
18 | const cssLoader = {
19 | loader: 'css-loader',
20 | options: {
21 | sourceMap: options.sourceMap
22 | }
23 | }
24 |
25 | const postcssLoader = {
26 | loader: 'postcss-loader',
27 | options: {
28 | sourceMap: options.sourceMap
29 | }
30 | }
31 |
32 | // generate loader string to be used with extract text plugin
33 | function generateLoaders (loader, loaderOptions) {
34 | const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]
35 |
36 | if (loader) {
37 | loaders.push({
38 | loader: loader + '-loader',
39 | options: Object.assign({}, loaderOptions, {
40 | sourceMap: options.sourceMap
41 | })
42 | })
43 | }
44 |
45 | // Extract CSS when that option is specified
46 | // (which is the case during production build)
47 | if (options.extract) {
48 | return ExtractTextPlugin.extract({
49 | use: loaders,
50 | fallback: 'vue-style-loader'
51 | })
52 | } else {
53 | return ['vue-style-loader'].concat(loaders)
54 | }
55 | }
56 |
57 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html
58 | return {
59 | css: generateLoaders(),
60 | postcss: generateLoaders(),
61 | less: generateLoaders('less'),
62 | sass: generateLoaders('sass', { indentedSyntax: true }),
63 | scss: generateLoaders('sass'),
64 | stylus: generateLoaders('stylus'),
65 | styl: generateLoaders('stylus')
66 | }
67 | }
68 |
69 | // Generate loaders for standalone style files (outside of .vue)
70 | exports.styleLoaders = function (options) {
71 | const output = []
72 | const loaders = exports.cssLoaders(options)
73 |
74 | for (const extension in loaders) {
75 | const loader = loaders[extension]
76 | output.push({
77 | test: new RegExp('\\.' + extension + '$'),
78 | use: loader
79 | })
80 | }
81 |
82 | return output
83 | }
84 |
85 | exports.createNotifierCallback = () => {
86 | const notifier = require('node-notifier')
87 |
88 | return (severity, errors) => {
89 | if (severity !== 'error') return
90 |
91 | const error = errors[0]
92 | const filename = error.file && error.file.split('!').pop()
93 |
94 | notifier.notify({
95 | title: packageConfig.name,
96 | message: severity + ': ' + error.name,
97 | subtitle: filename || '',
98 | icon: path.join(__dirname, 'logo.png')
99 | })
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/etcd-manage-ui/build/vue-loader.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const utils = require('./utils')
3 | const config = require('../config')
4 | const isProduction = process.env.NODE_ENV === 'production'
5 | const sourceMapEnabled = isProduction
6 | ? config.build.productionSourceMap
7 | : config.dev.cssSourceMap
8 |
9 | module.exports = {
10 | loaders: utils.cssLoaders({
11 | sourceMap: sourceMapEnabled,
12 | extract: isProduction
13 | }),
14 | cssSourceMap: sourceMapEnabled,
15 | cacheBusting: config.dev.cacheBusting,
16 | transformToRequire: {
17 | video: ['src', 'poster'],
18 | source: 'src',
19 | img: 'src',
20 | image: 'xlink:href'
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/etcd-manage-ui/build/webpack.base.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const path = require('path')
3 | const utils = require('./utils')
4 | const config = require('../config')
5 | const vueLoaderConfig = require('./vue-loader.conf')
6 | const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
7 |
8 |
9 | function resolve (dir) {
10 | return path.join(__dirname, '..', dir)
11 | }
12 |
13 |
14 | module.exports = {
15 | context: path.resolve(__dirname, '../'),
16 | entry: {
17 | app: './src/main.js'
18 | },
19 | output: {
20 | path: config.build.assetsRoot,
21 | filename: '[name].js',
22 | publicPath: process.env.NODE_ENV === 'production'
23 | ? config.build.assetsPublicPath
24 | : config.dev.assetsPublicPath
25 | },
26 | resolve: {
27 | extensions: ['.js', '.vue', '.json'],
28 | alias: {
29 | 'vue$': 'vue/dist/vue.esm.js',
30 | '@': resolve('src'),
31 | }
32 | },
33 | plugins: [
34 | new MonacoWebpackPlugin()
35 | ],
36 | module: {
37 | rules: [
38 | {
39 | test: /\.vue$/,
40 | loader: 'vue-loader',
41 | options: vueLoaderConfig
42 | },
43 | {
44 | test: /\.js$/,
45 | loader: 'babel-loader',
46 | include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
47 | },
48 | {
49 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
50 | loader: 'url-loader',
51 | options: {
52 | limit: 10000,
53 | name: utils.assetsPath('img/[name].[hash:7].[ext]')
54 | }
55 | },
56 | {
57 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
58 | loader: 'url-loader',
59 | options: {
60 | limit: 10000,
61 | name: utils.assetsPath('media/[name].[hash:7].[ext]')
62 | }
63 | },
64 | {
65 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
66 | loader: 'url-loader',
67 | options: {
68 | limit: 10000,
69 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
70 | }
71 | },
72 | {
73 | test: /\.sass$/,
74 | loaders: ['style', 'css', 'sass']
75 | }
76 | ]
77 | },
78 | node: {
79 | // prevent webpack from injecting useless setImmediate polyfill because Vue
80 | // source contains it (although only uses it if it's native).
81 | setImmediate: false,
82 | // prevent webpack from injecting mocks to Node native modules
83 | // that does not make sense for the client
84 | dgram: 'empty',
85 | fs: 'empty',
86 | net: 'empty',
87 | tls: 'empty',
88 | child_process: 'empty'
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/etcd-manage-ui/build/webpack.dev.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const utils = require('./utils')
3 | const webpack = require('webpack')
4 | const config = require('../config')
5 | const merge = require('webpack-merge')
6 | const path = require('path')
7 | const baseWebpackConfig = require('./webpack.base.conf')
8 | const CopyWebpackPlugin = require('copy-webpack-plugin')
9 | const HtmlWebpackPlugin = require('html-webpack-plugin')
10 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
11 | const portfinder = require('portfinder')
12 |
13 | const HOST = process.env.HOST
14 | const PORT = process.env.PORT && Number(process.env.PORT)
15 |
16 | const devWebpackConfig = merge(baseWebpackConfig, {
17 | module: {
18 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
19 | },
20 | // cheap-module-eval-source-map is faster for development
21 | devtool: config.dev.devtool,
22 |
23 | // these devServer options should be customized in /config/index.js
24 | devServer: {
25 | clientLogLevel: 'warning',
26 | historyApiFallback: {
27 | rewrites: [
28 | { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
29 | ],
30 | },
31 | hot: true,
32 | contentBase: false, // since we use CopyWebpackPlugin.
33 | compress: true,
34 | host: HOST || config.dev.host,
35 | port: PORT || config.dev.port,
36 | open: config.dev.autoOpenBrowser,
37 | overlay: config.dev.errorOverlay
38 | ? { warnings: false, errors: true }
39 | : false,
40 | publicPath: config.dev.assetsPublicPath,
41 | proxy: config.dev.proxyTable,
42 | quiet: true, // necessary for FriendlyErrorsPlugin
43 | watchOptions: {
44 | poll: config.dev.poll,
45 | }
46 | },
47 | plugins: [
48 | new webpack.DefinePlugin({
49 | 'process.env': require('../config/dev.env')
50 | }),
51 | new webpack.HotModuleReplacementPlugin(),
52 | new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
53 | new webpack.NoEmitOnErrorsPlugin(),
54 | // https://github.com/ampedandwired/html-webpack-plugin
55 | new HtmlWebpackPlugin({
56 | filename: 'index.html',
57 | template: 'index.html',
58 | inject: true
59 | }),
60 | // copy custom static assets
61 | new CopyWebpackPlugin([
62 | {
63 | from: path.resolve(__dirname, '../static'),
64 | to: config.dev.assetsSubDirectory,
65 | ignore: ['.*']
66 | }
67 | ])
68 | ]
69 | })
70 |
71 | module.exports = new Promise((resolve, reject) => {
72 | portfinder.basePort = process.env.PORT || config.dev.port
73 | portfinder.getPort((err, port) => {
74 | if (err) {
75 | reject(err)
76 | } else {
77 | // publish the new Port, necessary for e2e tests
78 | process.env.PORT = port
79 | // add port to devServer config
80 | devWebpackConfig.devServer.port = port
81 |
82 | // Add FriendlyErrorsPlugin
83 | devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
84 | compilationSuccessInfo: {
85 | messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
86 | },
87 | onErrors: config.dev.notifyOnErrors
88 | ? utils.createNotifierCallback()
89 | : undefined
90 | }))
91 |
92 | resolve(devWebpackConfig)
93 | }
94 | })
95 | })
96 |
--------------------------------------------------------------------------------
/etcd-manage-ui/build/webpack.prod.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const path = require('path')
3 | const utils = require('./utils')
4 | const webpack = require('webpack')
5 | const config = require('../config')
6 | const merge = require('webpack-merge')
7 | const baseWebpackConfig = require('./webpack.base.conf')
8 | const CopyWebpackPlugin = require('copy-webpack-plugin')
9 | const HtmlWebpackPlugin = require('html-webpack-plugin')
10 | const ExtractTextPlugin = require('extract-text-webpack-plugin')
11 | const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
12 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
13 |
14 | const env = require('../config/prod.env')
15 |
16 | const webpackConfig = merge(baseWebpackConfig, {
17 | module: {
18 | rules: utils.styleLoaders({
19 | sourceMap: config.build.productionSourceMap,
20 | extract: true,
21 | usePostCSS: true
22 | })
23 | },
24 | devtool: config.build.productionSourceMap ? config.build.devtool : false,
25 | output: {
26 | path: config.build.assetsRoot,
27 | filename: utils.assetsPath('js/[name].[chunkhash].js'),
28 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
29 | },
30 | plugins: [
31 | // http://vuejs.github.io/vue-loader/en/workflow/production.html
32 | new webpack.DefinePlugin({
33 | 'process.env': env
34 | }),
35 | new UglifyJsPlugin({
36 | uglifyOptions: {
37 | compress: {
38 | warnings: false
39 | }
40 | },
41 | sourceMap: config.build.productionSourceMap,
42 | parallel: true
43 | }),
44 | // extract css into its own file
45 | new ExtractTextPlugin({
46 | filename: utils.assetsPath('css/[name].[contenthash].css'),
47 | // Setting the following option to `false` will not extract CSS from codesplit chunks.
48 | // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.
49 | // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`,
50 | // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
51 | allChunks: true,
52 | }),
53 | // Compress extracted CSS. We are using this plugin so that possible
54 | // duplicated CSS from different components can be deduped.
55 | new OptimizeCSSPlugin({
56 | cssProcessorOptions: config.build.productionSourceMap
57 | ? { safe: true, map: { inline: false } }
58 | : { safe: true }
59 | }),
60 | // generate dist index.html with correct asset hash for caching.
61 | // you can customize output by editing /index.html
62 | // see https://github.com/ampedandwired/html-webpack-plugin
63 | new HtmlWebpackPlugin({
64 | filename: config.build.index,
65 | template: 'index.html',
66 | inject: true,
67 | minify: {
68 | removeComments: true,
69 | collapseWhitespace: true,
70 | removeAttributeQuotes: true
71 | // more options:
72 | // https://github.com/kangax/html-minifier#options-quick-reference
73 | },
74 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin
75 | chunksSortMode: 'dependency'
76 | }),
77 | // keep module.id stable when vendor modules does not change
78 | new webpack.HashedModuleIdsPlugin(),
79 | // enable scope hoisting
80 | new webpack.optimize.ModuleConcatenationPlugin(),
81 | // split vendor js into its own file
82 | new webpack.optimize.CommonsChunkPlugin({
83 | name: 'vendor',
84 | minChunks (module) {
85 | // any required modules inside node_modules are extracted to vendor
86 | return (
87 | module.resource &&
88 | /\.js$/.test(module.resource) &&
89 | module.resource.indexOf(
90 | path.join(__dirname, '../node_modules')
91 | ) === 0
92 | )
93 | }
94 | }),
95 | // extract webpack runtime and module manifest to its own file in order to
96 | // prevent vendor hash from being updated whenever app bundle is updated
97 | new webpack.optimize.CommonsChunkPlugin({
98 | name: 'manifest',
99 | minChunks: Infinity
100 | }),
101 | // This instance extracts shared chunks from code splitted chunks and bundles them
102 | // in a separate chunk, similar to the vendor chunk
103 | // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
104 | new webpack.optimize.CommonsChunkPlugin({
105 | name: 'app',
106 | async: 'vendor-async',
107 | children: true,
108 | minChunks: 3
109 | }),
110 |
111 | // copy custom static assets
112 | new CopyWebpackPlugin([
113 | {
114 | from: path.resolve(__dirname, '../static'),
115 | to: config.build.assetsSubDirectory,
116 | ignore: ['.*']
117 | }
118 | ])
119 | ]
120 | })
121 |
122 | if (config.build.productionGzip) {
123 | const CompressionWebpackPlugin = require('compression-webpack-plugin')
124 |
125 | webpackConfig.plugins.push(
126 | new CompressionWebpackPlugin({
127 | asset: '[path].gz[query]',
128 | algorithm: 'gzip',
129 | test: new RegExp(
130 | '\\.(' +
131 | config.build.productionGzipExtensions.join('|') +
132 | ')$'
133 | ),
134 | threshold: 10240,
135 | minRatio: 0.8
136 | })
137 | )
138 | }
139 |
140 | if (config.build.bundleAnalyzerReport) {
141 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
142 | webpackConfig.plugins.push(new BundleAnalyzerPlugin())
143 | }
144 |
145 | module.exports = webpackConfig
146 |
--------------------------------------------------------------------------------
/etcd-manage-ui/config/dev.env.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const merge = require('webpack-merge')
3 | const prodEnv = require('./prod.env')
4 |
5 | module.exports = merge(prodEnv, {
6 | NODE_ENV: '"development"'
7 | })
8 |
--------------------------------------------------------------------------------
/etcd-manage-ui/config/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | // Template version: 1.3.1
3 | // see http://vuejs-templates.github.io/webpack for documentation.
4 |
5 | const path = require('path')
6 |
7 | module.exports = {
8 | dev: {
9 |
10 | // Paths
11 | assetsSubDirectory: 'static',
12 | assetsPublicPath: '/',
13 | proxyTable: {},
14 |
15 | // Various Dev Server settings
16 | host: 'localhost', // can be overwritten by process.env.HOST
17 | port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
18 | autoOpenBrowser: false,
19 | errorOverlay: true,
20 | notifyOnErrors: true,
21 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
22 |
23 |
24 | /**
25 | * Source Maps
26 | */
27 |
28 | // https://webpack.js.org/configuration/devtool/#development
29 | devtool: 'cheap-module-eval-source-map',
30 |
31 | // If you have problems debugging vue-files in devtools,
32 | // set this to false - it *may* help
33 | // https://vue-loader.vuejs.org/en/options.html#cachebusting
34 | cacheBusting: true,
35 |
36 | cssSourceMap: true
37 | },
38 |
39 | build: {
40 | // Template for index.html
41 | index: path.resolve(__dirname, '../dist/index.html'),
42 |
43 | // Paths
44 | assetsRoot: path.resolve(__dirname, '../dist'),
45 | assetsSubDirectory: 'static',
46 | assetsPublicPath: '/ui/',
47 |
48 | /**
49 | * Source Maps
50 | */
51 |
52 | productionSourceMap: true,
53 | // https://webpack.js.org/configuration/devtool/#production
54 | devtool: '#source-map',
55 |
56 | // Gzip off by default as many popular static hosts such as
57 | // Surge or Netlify already gzip all static assets for you.
58 | // Before setting to `true`, make sure to:
59 | // npm install --save-dev compression-webpack-plugin
60 | productionGzip: false,
61 | productionGzipExtensions: ['js', 'css'],
62 |
63 | // Run the build command with an extra argument to
64 | // View the bundle analyzer report after build finishes:
65 | // `npm run build --report`
66 | // Set to `true` or `false` to always turn it on or off
67 | bundleAnalyzerReport: process.env.npm_config_report
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/etcd-manage-ui/config/prod.env.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | module.exports = {
3 | NODE_ENV: '"production"'
4 | }
5 |
--------------------------------------------------------------------------------
/etcd-manage-ui/go.mod:
--------------------------------------------------------------------------------
1 | module etcd-manage-ui
2 |
3 | go 1.13
4 |
5 | require github.com/jteeuwen/go-bindata v3.0.7+incompatible // indirect
6 |
--------------------------------------------------------------------------------
/etcd-manage-ui/go.sum:
--------------------------------------------------------------------------------
1 | github.com/jteeuwen/go-bindata v3.0.7+incompatible h1:91Uy4d9SYVr1kyTJ15wJsog+esAZZl7JmEfTkwmhJts=
2 | github.com/jteeuwen/go-bindata v3.0.7+incompatible/go.mod h1:JVvhzYOiGBnFSYRyV00iY8q7/0PThjIYav1p9h5dmKs=
3 |
--------------------------------------------------------------------------------
/etcd-manage-ui/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Etcd-Manager
8 |
9 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/etcd-manage-ui/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "navbar",
3 | "version": "1.0.0",
4 | "description": "A Vue.js project",
5 | "author": "曾杰霖 <248717607@qq.com>",
6 | "private": true,
7 | "scripts": {
8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
9 | "start": "npm run dev",
10 | "build": "node build/build.js"
11 | },
12 | "dependencies": {
13 | "axios": "^0.18.0",
14 | "iview": "^3.2.2",
15 | "monaco-editor": "^0.20.0",
16 | "vue": "^2.5.2",
17 | "vue-i18n": "^8.8.2",
18 | "vue-router": "^3.0.1"
19 | },
20 | "devDependencies": {
21 | "autoprefixer": "^7.1.2",
22 | "babel-core": "^6.22.1",
23 | "babel-helper-vue-jsx-merge-props": "^2.0.3",
24 | "babel-loader": "^7.1.1",
25 | "babel-plugin-syntax-jsx": "^6.18.0",
26 | "babel-plugin-transform-runtime": "^6.22.0",
27 | "babel-plugin-transform-vue-jsx": "^3.5.0",
28 | "babel-preset-env": "^1.3.2",
29 | "babel-preset-stage-2": "^6.22.0",
30 | "chalk": "^2.0.1",
31 | "copy-webpack-plugin": "^4.0.1",
32 | "css-loader": "^0.28.0",
33 | "extract-text-webpack-plugin": "^3.0.0",
34 | "file-loader": "^1.1.4",
35 | "friendly-errors-webpack-plugin": "^1.6.1",
36 | "html-webpack-plugin": "^2.30.1",
37 | "monaco-editor-webpack-plugin": "^1.7.0",
38 | "node-notifier": "^5.1.2",
39 | "node-sass": "^4.9.0",
40 | "optimize-css-assets-webpack-plugin": "^3.2.0",
41 | "ora": "^1.2.0",
42 | "portfinder": "^1.0.13",
43 | "postcss-import": "^11.0.0",
44 | "postcss-loader": "^2.0.8",
45 | "postcss-url": "^7.2.1",
46 | "rimraf": "^2.6.0",
47 | "sass-loader": "^7.0.3",
48 | "semver": "^5.3.0",
49 | "shelljs": "^0.7.6",
50 | "uglifyjs-webpack-plugin": "^1.1.1",
51 | "url-loader": "^0.5.8",
52 | "vue-loader": "^13.3.0",
53 | "vue-style-loader": "^3.0.1",
54 | "vue-template-compiler": "^2.5.2",
55 | "webpack": "^3.6.0",
56 | "webpack-bundle-analyzer": "^2.9.0",
57 | "webpack-dev-server": "^2.9.1",
58 | "webpack-merge": "^4.1.0"
59 | },
60 | "engines": {
61 | "node": ">= 6.0.0",
62 | "npm": ">= 3.0.0"
63 | },
64 | "browserslist": [
65 | "> 1%",
66 | "last 2 versions",
67 | "not ie <= 8"
68 | ]
69 | }
70 |
--------------------------------------------------------------------------------
/etcd-manage-ui/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
23 |
--------------------------------------------------------------------------------
/etcd-manage-ui/src/api/api.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import iView from 'iview'
3 | import { Message } from 'iview'
4 | import { bus } from "@/page/bus.js";
5 | import { Config } from "@/config"
6 |
7 | // 请求前缀
8 | axios.defaults.baseURL = Config.BaseUrl
9 |
10 | // 请求之前拦截
11 | axios.interceptors.request.use(
12 | config => {
13 | // 添加用户认证信息
14 | let loginInfoStr = sessionStorage.getItem('login-info');
15 | if(loginInfoStr){
16 | let loginInfo = JSON.parse(loginInfoStr);
17 | config.headers.Token = loginInfo.token;
18 | }
19 | // etcd服务
20 | let etcdID = localStorage.getItem('EtcdID') || ''; // 读取当前选中的etcd server
21 | config.headers.EtcdID = etcdID;
22 | iView.LoadingBar.start();
23 | return config
24 | },
25 | err => {
26 | return Promise.reject(err)
27 | })
28 |
29 |
30 | // 请求相应拦截器
31 | axios.interceptors.response.use(
32 | response => {
33 | // console.log(response)
34 | // 未登录情况
35 | if (response.status == 401) {
36 | ShowLogin();
37 | }
38 | if (response.status == 400) {
39 | Message.error('req err')
40 | }
41 | iView.LoadingBar.finish();
42 | return response
43 | },
44 | error => {
45 | iView.LoadingBar.error();
46 | // console.log(error.response);
47 | if (error.response) {
48 | if (error.response.status == 400) {
49 | Message.error(error.response.data.msg);
50 | }
51 | } else {
52 | Message.error('请求错误' + JSON.stringify(error));
53 | }
54 |
55 | if (error.response) {
56 | switch (error.response.status) {
57 | case 401:
58 | ShowLogin();
59 | break
60 | }
61 | }
62 |
63 | return Promise.reject(error)
64 | });
65 |
66 | const ShowLogin = () => {
67 | // todo 删除本地用户缓存
68 | sessionStorage.removeItem('login-info');
69 | // window.location.href = '/login'
70 | bus.$emit('show-login', true);
71 | Message.error('未登录,或登录失效,请登录')
72 | }
73 |
74 | export default axios
--------------------------------------------------------------------------------
/etcd-manage-ui/src/api/kv.js:
--------------------------------------------------------------------------------
1 | import axios from './api'
2 |
3 | const KV = {
4 | /**
5 | * 查询指定目录的key列表
6 | * @param {string} dir 查询key的目录
7 | */
8 | GetKeyList(dir) {
9 | return axios.get(`/v1/keys?path=${dir}`);
10 | },
11 |
12 | /**
13 | * 获取一个key的详细信息
14 | * @param {string} dir
15 | */
16 | GetKeyInfo(dir) {
17 | return axios.get(`/v1/keys/val?path=${dir}`)
18 | },
19 |
20 | /**
21 | * 添加key
22 | * @param {*} data 添加keybody信息
23 | */
24 | AddKey(data){
25 | return axios.post('/v1/keys', data)
26 | },
27 |
28 | /**
29 | * 修改key
30 | * @param {} data 修改时的body
31 | */
32 | SaveKey(data){
33 | return axios.put(`/v1/keys`, data)
34 | },
35 |
36 | /**
37 | * 删除key
38 | * @param {string} dir 要删除的key
39 | */
40 | DelKey(dir){
41 | return axios.delete(`/v1/keys?path=${dir}`)
42 | }
43 | }
44 |
45 | // 获取指定目录的key列表
46 | export {
47 | KV
48 | }
--------------------------------------------------------------------------------
/etcd-manage-ui/src/api/logs.js:
--------------------------------------------------------------------------------
1 | import axios from './api'
2 |
3 | const Logs = {
4 | /**
5 | * 日志列表
6 | */
7 | GetLogsList(dateStr, page, pageSize, user, logType) {
8 | return axios.get(`/v1/logs?date=${dateStr}&page=${page}&page_size=${pageSize}&user=${user}&log_type=${logType}`);
9 | },
10 |
11 | /**
12 | * 获取所有用户列表
13 | */
14 | GetAllUser(){
15 | return axios.get(`/v1/users`);
16 | },
17 |
18 | /**
19 | * 获取所有日志类型
20 | */
21 | GetLogTypes(){
22 | return axios.get(`/v1/logtypes`);
23 | }
24 |
25 | }
26 |
27 | export {
28 | Logs
29 | }
30 |
--------------------------------------------------------------------------------
/etcd-manage-ui/src/api/passport.js:
--------------------------------------------------------------------------------
1 | import axios from './api'
2 |
3 | const Passport = {
4 |
5 | /**
6 | * 登录
7 | * @param {*} username 用户名
8 | * @param {*} password 密码
9 | */
10 | Login(username, password) {
11 | return axios.post(`/v1/passport/login`, {
12 | username: username,
13 | password: password
14 | });
15 | },
16 |
17 | }
18 |
19 | // 获取指定目录的key列表
20 | export {
21 | Passport
22 | }
--------------------------------------------------------------------------------
/etcd-manage-ui/src/api/role.js:
--------------------------------------------------------------------------------
1 | import axios from './api'
2 |
3 | const Role = {
4 | /**
5 | * 全部角色
6 | */
7 | GetAll() {
8 | return axios.get(`/v1/role`);
9 | },
10 |
11 | /**
12 | * 添加
13 | * @param {*} data 添加角色信息
14 | */
15 | Add(data){
16 | return axios.post('/v1/role', data)
17 | },
18 |
19 | /**
20 | * 删除角色
21 | * @param {*} id
22 | */
23 | Del(id){
24 | return axios.delete(`/v1/role?id=${id}`)
25 | },
26 |
27 | /**
28 | * 修改角色
29 | * @param {*} data
30 | */
31 | Save(data){
32 | return axios.put('/v1/role', data)
33 | },
34 |
35 | }
36 |
37 | export {
38 | Role
39 | }
40 |
--------------------------------------------------------------------------------
/etcd-manage-ui/src/api/server.js:
--------------------------------------------------------------------------------
1 | import axios from './api'
2 |
3 | const SERVER = {
4 | /**
5 | * 查询指定目录的key列表
6 | */
7 | GetMemberList() {
8 | return axios.get(`/v1/keys/members`);
9 | },
10 |
11 | /**
12 | * 获取所有etcd服务列表
13 | */
14 | GetEtcdServerList(name){
15 | name = name || '';
16 | return axios.get(`/v1/server?name=${name}`);
17 | },
18 |
19 | /**
20 | * 添加
21 | * @param {Object} info 添加详情
22 | */
23 | AddEtcdServer(info){
24 | return axios.post(`/v1/server`, info);
25 | },
26 |
27 | /**
28 | * 修改
29 | * @param {Object} edit 修改详情
30 | */
31 | UpdateEtcdServer(edit){
32 | return axios.put(`/v1/server`, edit);
33 | },
34 |
35 | /**
36 | * 修复etcd key目录结构
37 | */
38 | RestoreEtcdServer(id){
39 | if (!id){
40 | return
41 | }
42 | return axios.get(`/v1/server/restore?etcd_id=${id}`);
43 | },
44 |
45 | /**
46 | * 获取权限列表
47 | * @param {*} id etcd服务id
48 | */
49 | GetRoles(id){
50 | if (!id){
51 | return
52 | }
53 | return axios.get(`/v1/server/roles?etcd_id=${id}`);
54 | },
55 |
56 | /**
57 | * 设置etcd服务权限配置
58 | * @param {*} data
59 | */
60 | SetRoles(data){
61 | if (!data){
62 | return
63 | }
64 | return axios.post(`/v1/server/roles`, data);
65 | },
66 |
67 | /**
68 | * 删除
69 | * @param {*} id
70 | */
71 | Del(id){
72 | return axios.delete(`/v1/server?id=${id}`)
73 | },
74 |
75 |
76 | }
77 |
78 | export {
79 | SERVER
80 | }
81 |
--------------------------------------------------------------------------------
/etcd-manage-ui/src/api/user.js:
--------------------------------------------------------------------------------
1 | import axios from './api'
2 |
3 | const User = {
4 | /**
5 | * 全部角色
6 | */
7 | GetList(userId, username, roleId, page, pageSize) {
8 | return axios.get(`/v1/user?user_id=${userId}&name=${username}&role_id=${roleId}&page=${page}&page_size=${pageSize}`);
9 | },
10 |
11 | /**
12 | * 添加
13 | * @param {*} data 添加角色信息
14 | */
15 | Add(data){
16 | return axios.post('/v1/user', data)
17 | },
18 |
19 | /**
20 | * 删除角色
21 | * @param {*} id
22 | */
23 | Del(id){
24 | return axios.delete(`/v1/user?id=${id}`)
25 | },
26 |
27 | /**
28 | * 修改角色
29 | * @param {*} data
30 | */
31 | Save(data){
32 | return axios.put('/v1/user', data)
33 | },
34 |
35 | }
36 |
37 | export {
38 | User
39 | }
40 |
--------------------------------------------------------------------------------
/etcd-manage-ui/src/assets/css/base.css:
--------------------------------------------------------------------------------
1 | body,dl,dd,ul,ol,h1,h2,h3,h4,h5,h6,pre,form,input,textarea,p,hr,thead,tbody,tfoot,th,td{margin:0;padding:0;}
2 | ul,ol{list-style:none;}
3 | a{text-decoration:none;}
4 | html{-ms-text-size-adjust:none;-webkit-text-size-adjust:none;text-size-adjust:none;}
5 | body{line-height:1.5; font-size:14px;}
6 | body,button,input,select,textarea{font-family:'helvetica neue',tahoma,'hiragino sans gb',stheiti,'wenquanyi micro hei',\5FAE\8F6F\96C5\9ED1,\5B8B\4F53,sans-serif;}
7 | b,strong{font-weight:bold;}
8 | i,em{font-style:normal;}
9 | table{border-collapse:collapse;border-spacing:0;}
10 | /* table th,table td{border:1px solid #ddd;padding:5px;} */
11 | /* table th{font-weight:inherit;border-bottom-width:2px;border-bottom-color:#ccc;} */
12 | /* img{border:0 none;width:auto\9;max-width:100%;vertical-align:top; height:auto;} */
13 | button,input,select,textarea{font-family:inherit;font-size:100%;margin:0;vertical-align:baseline;}
14 | button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;}
15 | button[disabled],input[disabled]{cursor:default;}
16 | input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0;}
17 | input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;}
18 | input[type="search"]::-webkit-search-decoration{-webkit-appearance:none;}
19 | input:focus{outline:none;}
20 | select[size],select[multiple],select[size][multiple]{border:1px solid #AAA;padding:0;}
21 | article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block;}
22 | audio,canvas,video,progress{display:inline-block;}
23 | html{width: 100%;height: 100%;}
24 | body{background:#fff;width: 100%;height: 100%;}
25 | input::-webkit-input-speech-button {display: none}
26 | button,input,textarea{
27 | -webkit-tap-highlight-color: rgba(0,0,0,0);
28 | }
29 | input::-webkit-input-placeholder, textarea::-webkit-input-placeholder {
30 | color: #CECDD0;
31 | font-size: 14px;
32 | }
33 | input:-moz-placeholder, textarea:-moz-placeholder {
34 | font-size: 14px;
35 | color: #CECDD0;
36 | }
37 | input::-moz-placeholder, textarea::-moz-placeholder {
38 | font-size: 14px;
39 | color: #CECDD0;
40 | }
41 | input:-ms-input-placeholder, textarea:-ms-input-placeholder {
42 | font-size: 14px;
43 | color: #CECDD0;
44 | }
--------------------------------------------------------------------------------
/etcd-manage-ui/src/assets/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/comqx/k8s-etcd-ui/e860824e994da8c48aea5cd3e9283f374828a8c9/etcd-manage-ui/src/assets/favicon.png
--------------------------------------------------------------------------------
/etcd-manage-ui/src/assets/img/narrow-hover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/comqx/k8s-etcd-ui/e860824e994da8c48aea5cd3e9283f374828a8c9/etcd-manage-ui/src/assets/img/narrow-hover.png
--------------------------------------------------------------------------------
/etcd-manage-ui/src/assets/img/narrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/comqx/k8s-etcd-ui/e860824e994da8c48aea5cd3e9283f374828a8c9/etcd-manage-ui/src/assets/img/narrow.png
--------------------------------------------------------------------------------
/etcd-manage-ui/src/assets/img/unfold-hover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/comqx/k8s-etcd-ui/e860824e994da8c48aea5cd3e9283f374828a8c9/etcd-manage-ui/src/assets/img/unfold-hover.png
--------------------------------------------------------------------------------
/etcd-manage-ui/src/assets/img/unfold.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/comqx/k8s-etcd-ui/e860824e994da8c48aea5cd3e9283f374828a8c9/etcd-manage-ui/src/assets/img/unfold.png
--------------------------------------------------------------------------------
/etcd-manage-ui/src/assets/imgs/file.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/comqx/k8s-etcd-ui/e860824e994da8c48aea5cd3e9283f374828a8c9/etcd-manage-ui/src/assets/imgs/file.png
--------------------------------------------------------------------------------
/etcd-manage-ui/src/assets/imgs/folder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/comqx/k8s-etcd-ui/e860824e994da8c48aea5cd3e9283f374828a8c9/etcd-manage-ui/src/assets/imgs/folder.png
--------------------------------------------------------------------------------
/etcd-manage-ui/src/common/CloudContainer.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
22 |
--------------------------------------------------------------------------------
/etcd-manage-ui/src/common/CloudHeader.vue:
--------------------------------------------------------------------------------
1 |
2 |
47 |
48 |
49 |
162 |
163 |
208 |
--------------------------------------------------------------------------------
/etcd-manage-ui/src/common/CloudSideBar.vue:
--------------------------------------------------------------------------------
1 |
2 |
60 |
61 |
62 |
209 |
210 |
302 |
--------------------------------------------------------------------------------
/etcd-manage-ui/src/components/KvCard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
18 |
19 |
20 |
21 |
22 | {{ item.path }}
23 | {{item.value}}
24 |
25 |
{{ item.path }}
26 |
{{item.value}}
27 |
28 |
29 |
30 |
31 |
37 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
96 |
97 |
145 |
--------------------------------------------------------------------------------
/etcd-manage-ui/src/components/KvGrid.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |

8 |

9 |
10 | {{item.name}}
11 |
12 |
{{ item.path }}
13 |
{{item.value}}
14 |
15 |
16 |
17 |
18 |
19 |
64 |
65 |
96 |
--------------------------------------------------------------------------------
/etcd-manage-ui/src/components/KvList.vue:
--------------------------------------------------------------------------------
1 |
2 |
20 |
21 |
226 |
227 |
232 |
--------------------------------------------------------------------------------
/etcd-manage-ui/src/components/MonacoEditor.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
19 |
127 |
--------------------------------------------------------------------------------
/etcd-manage-ui/src/config/index.js:
--------------------------------------------------------------------------------
1 | // 开发环境配置
2 | const DEV = {
3 | BaseUrl: 'http://127.0.0.1:10280'
4 | }
5 |
6 | // 正式环境
7 | const PRO = {
8 | BaseUrl: ''
9 | }
10 |
11 | export const Config = process.env.NODE_ENV === 'development' ? DEV : PRO
12 |
--------------------------------------------------------------------------------
/etcd-manage-ui/src/i18n/en-US.js:
--------------------------------------------------------------------------------
1 | const lang = {
2 | sideBar: {
3 | oneEtcd: 'One Etcd Server',
4 | KV: 'Key / Value',
5 | MEMBERS: 'MEMBERS',
6 | Other: 'Other',
7 | EtcdServers: 'Etcd Servers',
8 | Logs: 'Logs',
9 | Setings: 'Setings',
10 | Role: 'Role',
11 | User: 'User'
12 | },
13 | login:{
14 | title: 'Login',
15 | username: 'Username',
16 | password: 'Password',
17 | loginBtn: 'Login'
18 | },
19 | public:{
20 | add:'Add',
21 | edit:"Edit",
22 | confirmDelete:'Confirm delete',
23 | delete:"Delete",
24 | multiDelete:'Multi Delete',
25 | hide:'Hide',
26 | save:'Save',
27 | close:'Close',
28 | screen:'Local screening',
29 | role: "Role"
30 | },
31 | header:{
32 | 'personalCenter':'Personal Center',
33 | 'logout':'Logout',
34 | },
35 | key:{
36 | currentPathTips:'Key can only be a path that cannot be a value.',
37 | delConfirm:'Are you sure you want to delete the selected key?',
38 | notSubKey:'No child level list!',
39 | editKey:'Editor Key',
40 | addKey:'Add Key',
41 | open:'Open',
42 | show:'Show',
43 | keyNotEmpty:'Key can not be empty',
44 | addSuccessfully:'Added successfully!',
45 | saveSuccessfully:'Saved successfully!',
46 | showTree: 'Show tree',
47 | keyPlaceholder: "Key path, which can contain multiple'/'",
48 | keyNotPrefixBar: "Key must begin with '/'"
49 | },
50 | logs:{
51 | date:'Date',
52 | user:'User',
53 | typeof:'Type',
54 | filter:'Filter',
55 | selectDate:'Please select a date',
56 | selectDateShowLog:'Select date to view the log',
57 | },
58 | etcdServer:{
59 | repairDirectory:'Repair directory',
60 | determineRepairDirectory:'Determine the directory problem for repairing the etcd key',
61 | }
62 |
63 | }
64 |
65 | export default lang;
--------------------------------------------------------------------------------
/etcd-manage-ui/src/i18n/zh-CN.js:
--------------------------------------------------------------------------------
1 | const lang = {
2 | sideBar: {
3 | oneEtcd: '一个Etcd服务',
4 | KV: '键 / 值',
5 | MEMBERS: '集群信息',
6 | Other: '其它',
7 | EtcdServers: 'Etcd 服务列表',
8 | Logs: '日志',
9 | Setings: '设置',
10 | Role: '角色',
11 | User: '用户'
12 | },
13 | login:{
14 | title: '登 录',
15 | username: '用户名',
16 | password: '密 码',
17 | loginBtn: '登 录'
18 | },
19 | public:{
20 | add:'添加',
21 | edit:"编辑",
22 | confirmDelete:'确定删除',
23 | delete:"删除",
24 | multiDelete:'批删除',
25 | hide:'隐藏',
26 | save:'保存',
27 | close:'关闭',
28 | screen:'本地筛选',
29 | role: '权限'
30 | },
31 | header:{
32 | 'personalCenter':'个人中心',
33 | 'logout':'退出',
34 | },
35 | key:{
36 | currentPathTips:'key路径,只能是路径不能是值',
37 | delConfirm:'确定删除选中的key?',
38 | notSubKey:'没有子级列表!',
39 | addKey:'添加key',
40 | editKey:'编辑key',
41 | open:'打开',
42 | show:'查看',
43 | keyNotEmpty:'key不能为空',
44 | addSuccessfully:'添加成功!',
45 | saveSuccessfully:'保存成功!',
46 | showTree: '显示树形',
47 | keyPlaceholder: "key路径可以包含多个'/'",
48 | keyNotPrefixBar: "key必须以 '/' 开头"
49 | },
50 | logs:{
51 | date:'日期',
52 | user:'用户',
53 | typeof:'类型',
54 | filter:'筛选',
55 | selectDate:'请选择日期',
56 | selectDateShowLog:'选择日期查看日志',
57 | },
58 | etcdServer:{
59 | repairDirectory:'修复目录',
60 | determineRepairDirectory:'确定修复etcd key的目录问题',
61 | }
62 | }
63 |
64 | export default lang;
--------------------------------------------------------------------------------
/etcd-manage-ui/src/main.js:
--------------------------------------------------------------------------------
1 | // The Vue build version to load with the `import` command
2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias.
3 | import Vue from 'vue'
4 | import App from './App'
5 | import router from './router'
6 | import iView from 'iview';
7 | import 'iview/dist/styles/iview.css';
8 | import './assets/css/base.css'
9 |
10 | import VueI18n from 'vue-i18n';
11 | import en from 'iview/dist/locale/en-US';
12 | import zh from 'iview/dist/locale/zh-CN';
13 |
14 | import myen from './i18n/en-US';
15 | import myzh from './i18n/zh-CN';
16 |
17 |
18 | Vue.use(VueI18n);
19 |
20 | Vue.config.productionTip = false
21 |
22 | Vue.use(iView, {
23 | transfer: true
24 | });
25 |
26 |
27 | // 语言
28 | Vue.locale = () => {};
29 |
30 | const messages = {
31 | en: Object.assign(myen, en),
32 | zh: Object.assign(myzh, zh)
33 | };
34 |
35 | let mylang = localStorage.getItem("etcd-language") || 'en';
36 |
37 | // Create VueI18n instance with options
38 | const i18n = new VueI18n({
39 | locale: mylang, // set locale
40 | messages // set locale messages
41 | });
42 |
43 | // 判断字符串是否是以xx为前缀
44 | Vue.prototype.HasPrefix = (str, prefix) => {
45 | console.log(str, prefix,str.substring(str.length - prefix.length));
46 |
47 | if(prefix == null || prefix == "" || str.length == 0 || prefix.length > str.length)
48 | return false
49 | if(str.substring(0, prefix.length) == prefix)
50 | return true
51 | else
52 | return false
53 | }
54 |
55 |
56 | /* eslint-disable no-new */
57 | new Vue({
58 | el: '#app',
59 | router,
60 | i18n,
61 | components: {
62 | App
63 | },
64 | template: ''
65 | })
66 |
--------------------------------------------------------------------------------
/etcd-manage-ui/src/page/CloudHome.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
22 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
84 |
85 |
106 |
--------------------------------------------------------------------------------
/etcd-manage-ui/src/page/Logs.vue:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
37 |
38 |
39 |
42 |
43 |
44 |
45 |
46 |
153 |
--------------------------------------------------------------------------------
/etcd-manage-ui/src/page/Members.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
79 |
85 |
--------------------------------------------------------------------------------
/etcd-manage-ui/src/page/Role.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
19 |
20 |
21 |
22 |
27 |
28 |
29 |
30 |
31 |
32 |
180 |
181 |
--------------------------------------------------------------------------------
/etcd-manage-ui/src/page/User.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
52 |
53 |
54 |
55 |
56 |
72 |
73 |
74 |
75 |
76 |
77 |
93 |
94 |
95 |
96 |
97 |
287 |
288 |
--------------------------------------------------------------------------------
/etcd-manage-ui/src/page/bus.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | export const bus = new Vue()
3 |
--------------------------------------------------------------------------------
/etcd-manage-ui/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 | import CloudHome from '@/page/CloudHome'
4 |
5 | Vue.use(Router)
6 |
7 | export default new Router({
8 | routes: [{
9 | path: '/',
10 | name: 'CloudHome',
11 | component: CloudHome,
12 | children: [
13 | {
14 | path: '/key/kv',
15 | name: 'KV',
16 | component: () => import('@/page/KV'),
17 | },
18 | {
19 | path: '/server/members',
20 | name: 'Members',
21 | component: () => import('@/page/Members'),
22 | },
23 | {
24 | path: '/setings/EtcdServers',
25 | name: 'EtcdServers',
26 | component: () => import('@/page/EtcdServers'),
27 | },
28 | {
29 | path: '/setings/user',
30 | name: 'User',
31 | component: () => import('@/page/User'),
32 | },
33 | {
34 | path: '/setings/role',
35 | name: 'Role',
36 | component: () => import('@/page/Role'),
37 | }
38 |
39 | ]
40 | }]
41 | })
42 |
--------------------------------------------------------------------------------
/etcd-manage-ui/static/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/comqx/k8s-etcd-ui/e860824e994da8c48aea5cd3e9283f374828a8c9/etcd-manage-ui/static/.gitkeep
--------------------------------------------------------------------------------
/etcd-manage-ui/static/etcdui.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/comqx/k8s-etcd-ui/e860824e994da8c48aea5cd3e9283f374828a8c9/etcd-manage-ui/static/etcdui.png
--------------------------------------------------------------------------------
/etcd-manage-ui/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/comqx/k8s-etcd-ui/e860824e994da8c48aea5cd3e9283f374828a8c9/etcd-manage-ui/static/favicon.ico
--------------------------------------------------------------------------------
/etcd-manage-ui/tpls/compile.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | npm run build
4 | go-bindata -o tpls.go ../dist/...
5 |
6 | sed -i "" "s/package main/package tpls/g" tpls.go
7 |
--------------------------------------------------------------------------------
/etcdsdk/main/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "time"
7 |
8 | "github.com/etcd-manage/etcd-manage-server/etcdsdk/model"
9 | "go.etcd.io/etcd/clientv3"
10 | )
11 |
12 | func main() {
13 | var (
14 | client3Config clientv3.Config
15 | //cert tls.Certificate
16 | resp *clientv3.GetResponse
17 | //err error
18 | )
19 | cfg := model.Config{
20 | EtcdId: 8,
21 | Version: "v3",
22 | Address: []string{"http://127.0.0.1:2379"},
23 | TlsEnable: "false",
24 | CertFile: nil,
25 | KeyFile: nil,
26 | CaFile: nil,
27 | }
28 | //cfg.CertFile = common.Base64Decode("LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURJRENDQWdpZ0F3SUJBZ0lJQ1dRWENPM1A3QUF3RFFZSktvWklodmNOQVFFTEJRQXdFakVRTUE0R0ExVUUKQXhNSFpYUmpaQzFqWVRBZUZ3MHlNREE0TURRd01qVXlNelZhRncweU1UQTRNRFF3TWpVeU16VmFNQmd4RmpBVQpCZ05WQkFNVERXeHhlQzF0WVhOMFpYSXRNREV3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLCkFvSUJBUURoNWJqUDNXN2NzVlhEaHdKWHlrWUR6RjRLaldrYXFQc0xNNmVFRGhYZWJXMGVaZi9RT3QrcUFxaWgKbTdPZ3BISFp6VFhFODVVSHV5QkFTM3daQU0wZDRqODY1bHFwaTJuTU41Vm5GTkNMOEpmdlRiTVdocFpleS9DVApPc1J3amhLQ0JHc2kvbHE5SEt0WnJtdmd0TjRNVlhyanB4cE5Td1BaeDFjNENBUXJPOFk1MkFpRlU5cFJoNmtHCmZ1TC82NlFGaDlpTnN2eExOUnFmVFpLY3oxUWxrZEdyWUdqVXE1blVrV3MwQnM3NmpmU3hQRFBydG1XN1lhdnEKbHo5RjVQSzZaSTY3Mm84MWNzOTVQR0VKV2JRTWpBaHRnckpKMGdoenVoTU15YlRYQWFmUCtQZkUxMEdQWlRMUgpvWit5c1U2dWI2eHNwVm1NZzR0b1lxMWhVWW4zQWdNQkFBR2pkREJ5TUE0R0ExVWREd0VCL3dRRUF3SUZvREFkCkJnTlZIU1VFRmpBVUJnZ3JCZ0VGQlFjREFRWUlLd1lCQlFVSEF3SXdRUVlEVlIwUkJEb3dPSUlOYkhGNExXMWgKYzNSbGNpMHdNWUlKYkc5allXeG9iM04waHdRS0ZBTm1od1IvQUFBQmh4QUFBQUFBQUFBQUFBQUFBQUFBQUFBQgpNQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUJBUUFOODNxZFJ2eWl1ZVZkd09ZOWozVG0raFVxdFN5anV3eWI3aGJSCnR2UDNJb3RLbm50ZzNpRndYZVpNQkxTWUhvWU9jVXJ6ZndCUzZHOUgrdjBkRFh0Vi8vR3ljKzQ4QUVLbjhjZU8KT2xIRnJJQkRUMkE5VVBZTUhkVmNMcEZjOHJvdGhPQmJjd3RGSFB6aStqUlI5dE9oYjhQY0l0WFhTL29nOXVNMQpqaE5zU282VHlhYlA0U3BVSUhVNXpkbEMxaHBVTXk4b2V0SnI0d1c3SEpvK00xd2pjWmNNTU5VTEE4UEllb2xICndtR1dFa1g2OFZVQzhGU0FDK1ZyeUlLM20vTWVjQkEwMFFseGtBZmprbWt2b2NqbHhvVTJ1ZzVLTHYyWUthb0YKeC9mVjFuOUNuTFlpdmU3VWI5T2pGTmxjazJZOHpMNjRhTXlnVzZvOWRWTzBDcFVNCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K")
29 | //cfg.KeyFile = common.Base64Decode("LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBNGVXNHo5MXUzTEZWdzRjQ1Y4cEdBOHhlQ28xcEdxajdDek9uaEE0VjNtMXRIbVgvCjBEcmZxZ0tvb1p1em9LUngyYzAxeFBPVkI3c2dRRXQ4R1FETkhlSS9PdVphcVl0cHpEZVZaeFRRaS9DWDcwMnoKRm9hV1hzdndrenJFY0k0U2dnUnJJdjVhdlJ5cldhNXI0TFRlREZWNjQ2Y2FUVXNEMmNkWE9BZ0VLenZHT2RnSQpoVlBhVVllcEJuN2kvK3VrQllmWWpiTDhTelVhbjAyU25NOVVKWkhScTJCbzFLdVoxSkZyTkFiTytvMzBzVHd6CjY3Wmx1MkdyNnBjL1JlVHl1bVNPdTlxUE5YTFBlVHhoQ1ZtMERJd0liWUt5U2RJSWM3b1RETW0wMXdHbnovajMKeE5kQmoyVXkwYUdmc3JGT3JtK3NiS1ZaaklPTGFHS3RZVkdKOXdJREFRQUJBb0lCQUFuQzg0bUgrQkp4VjFOcgpzaDQ1RDIxNmwxVzlacDFRVUFqYjRwRkNTbytpQ3VVVlkwaU1RcjRGLzJOOFp2YTZKSEZVL00zVitNcXN1MmdMCjJ3RDVsK09DczFqSU80SzRFNHBQbkpVbndSdEsra1hOQmNBamNMd0g5QTFvckxSd2J6eFBGSkllaTYxQVgrY3cKTWxremQ2VHd6SzZwdWVrOUpKMTBqOEJNR0dJRnpuSnlBMm5EYjU4WFJCM2UySnFRQlFEQ1BaM1BJaHE3UlJrNQp3Q1ExMG9jbXdKbUQwTGkydUlqRW5JOTU4ZHRzVVdBSmI5Z1FoNTVIeFphSEU2R05pTkRQdlpxZCtyc1ZJSGRsClFqR2MvQTY0NVpvSnNIQWczeGVyWnArdytvT0pmc253SEJkQ1ZpZlFTR0lWa1g0a243QXVzZFZrSDFWN1JqYjEKT2NFWWd3RUNnWUVBNzM3YnpVT3RDRWUvNnNsaHhMYm9JYXVLQXk3bDFDdk16MWNYMXJDaDV2Z2V3bW4yd3RzRAowZE9xSWdGcXZ1MVIvTTltVkpsZzNYeTRRc01oc2Vabk9BRTJIeGZIcFByVkoyOGJlMm1paWdZQjI3ZTBnY0FpCnhKb3h2L2pSazlZRTZzZnJYN3lOU2pIQWJBWGszWjh0Y1BnU0pxRktDUUp3YmgyR2QrcTdBZWtDZ1lFQThYYjMKWE9kY05aczhteEFDZjhqMUZjNTlRZGNqOGJlQ1YrU0ViM09YMkhxMlZobjJkbzltbFcyaTA2ejFuVTU3NVdqUQpHUW43NDNpUjYzb3pqSmZLNU5EKzJidWRSMnZOeVo2VkxzUHlySlZIUUxHSDBhT1pvL3RMdTZoQStCazJZWm83ClZOWFBReHFIMVF1UG1UNWFIbHpXMVBrWnpZQndFWVVnSHpKbjROOENnWUI3QnczZW1mOVBHNXlJL2I1TmtUQVoKNjhiUDc4MThlcXVSYjBuOXJXcmQvV08vdHpOZDBhaGFwNExrU1JvT3psYXBxZGtGYUcwTUdqK0ZmRHZZNldUbwpyaWFoUGxQK2VpUDVSK2cwTTRXZHBZeGExRG5UMVdHRzRUYmhTTzVRSlVjTlhIbWJDbjhDT0NDQzNWdytSTURSCklYNGhmZ1ZNTDRhVjZuRGpOUit2MFFLQmdRQ3BGTGdEK3hJTGk0ZDF3VkV1cjhaR25kQUNBYWR1eENSbWJXTDcKTkFNNUdEeURzQ3h3T3R3SGVMMFM0a01mQXUwbzZDc0h6WUR2ZU9jYzcvWVcxZGZDUUVLa3JvWmtrNjJIS0IrbwpucGRZbUROTHJzUy9YSUxpVzc1ZFNtVXNGV09LRnRqQy8vRGhPVHV5U0NVbWxvMitReDVBQmFvMngyQXlOSGtZCnYxQVVHd0tCZ0JNMklPWDIzc3VSd0o1UHU1eGowQzFkVzh1QzFaYjEzeWVoZm93ZjdueFFxWmhtRHk4cGlOUmUKUnFKQlFhaFpnYTdKWi9uZWtKZTh5S3hueGhtQWdJSy9hTmxJMkQ3NThEL1d5NENQZVluQ2hKS0VZTzh5bHFwMgovVk5JeXpIOHNLNWs1M1E0UzNmdThlNlVyM29KcndFQWtkVmZSeGFsbzU5V1dwWjY2bkp0Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==")
30 | //cfg.CaFile = common.Base64Decode("LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN3akNDQWFxZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFTTVJBd0RnWURWUVFERXdkbGRHTmsKTFdOaE1CNFhEVEl3TURnd05EQXlOVEl6TlZvWERUTXdNRGd3TWpBeU5USXpOVm93RWpFUU1BNEdBMVVFQXhNSApaWFJqWkMxallUQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU5sN0J6YzZzcWFNCjJvbnAwOVMxTndVK0xKRkFFeXdLN2lqUHV4TlRSV2VQSzFwajF5TmxiUVlDV1pHZUExMUFQRVFUdEVzc0c1dlUKeWJTNHUydzFkM3Q2WEpwb2FpZ3pOVkVPaWVlUkgxOE9lb2pVNWdWM2hZTlNUTVByQUIrZXJ0YVcxNkMzQmhTRgprVmFrc2I5UHIxNU0ramE3WUpCQnhnL2hNOEtIbzU2NVl3Z0hJUXoxM253R3JqMUVPc2x0YkVRN0xValFRdU9wCkpmYU82WElCZWVhUlFJWm9yZjZLTVdNY29ScG9nbFFwcXRXWVh1cTUwOUNWd3NqTWNmZDEwV3dQUHE0aWt3RE0KYVFYS0JXTWhwczR4azY4SWcrVVNLRGkxZXdzOThrOFo5UlQ5b001Z3FBVVQ1RzRyTGpWNzZSZDQrR2xiblFpdwpkUHFxdzdMMkwrOENBd0VBQWFNak1DRXdEZ1lEVlIwUEFRSC9CQVFEQWdLa01BOEdBMVVkRXdFQi93UUZNQU1CCkFmOHdEUVlKS29aSWh2Y05BUUVMQlFBRGdnRUJBSDdkRXNiSWZWN3RLNTlEZjBIRFNLd1ZLS3dkWGlzbTlqTkYKYUI2SG5ST1RvaE5lb2NNYXFWRlo5N0RWZytlTzh0UUJVdFhrVlg1N08vVzY2dFMrckNIcW04cVJYNzJFMVRzRQpmQzI2QW9tdXdEbnBHOGxvUXdLV2x4L2YrY1BEdkVhZ0MrLytDZVI3dmZrc09TbktGbVVhS0l4M0MxSWVYT1RKCk1jOWpVQ1Z0N05xbzBjY3dDOUplMkVqd3lnYkVXUEVOZnczMGRtZ1B4UTNKdXJRSmhLZlhFU21lZ0hOTUw4R08KQnFHY3YzY1A5TWQrbzhJdUVpby9BUkdCWjE2QXFrSmozTDY2TGc1bjBlVldLVWZpOE9hd3BvL2F3Q2JuaXhCMgoyQ3lZRkFiNHR0VkVZT3dpT2FjTkRiaXQvc0xuejFMcHpkT2xlQy96WjZTd2c4eWVBbE09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K")
31 |
32 | //if cert, err = tls.X509KeyPair(cfg.CertFile, cfg.KeyFile); err != nil {
33 | // return
34 | //}
35 | //pool := x509.NewCertPool()
36 | //pool.AppendCertsFromPEM(cfg.CaFile)
37 | //_tlsConfig := &tls.Config{
38 | // Certificates: []tls.Certificate{cert},
39 | // RootCAs: pool,
40 | //}
41 | client3Config = clientv3.Config{
42 | Endpoints: cfg.Address,
43 | DialTimeout: 5 * time.Second,
44 | }
45 | cliNew, _ := clientv3.New(client3Config)
46 | resp, _ = clientv3.NewKV(cliNew).Get(context.Background(), "/", clientv3.WithFromKey(), clientv3.WithKeysOnly())
47 | fmt.Println(resp)
48 | }
49 |
--------------------------------------------------------------------------------
/etcdsdk/model/config.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "strconv"
5 | )
6 |
7 | const (
8 | ETCD_VERSION_V3 = "v3"
9 | )
10 |
11 | // Config etcd 连接配置
12 | type Config struct {
13 | EtcdId int32 `json:"etcd_id,omitempty"`
14 | Version string `json:"version,omitempty"`
15 | Address []string `json:"address,omitempty"`
16 | TlsEnable string `json:"tls_enable,omitempty"`
17 | CertFile []byte `json:"cert_file,omitempty"`
18 | KeyFile []byte `json:"key_file,omitempty"`
19 | CaFile []byte `json:"ca_file,omitempty"`
20 | Username string `json:"username,omitempty"`
21 | Password string `json:"password,omitempty"`
22 | }
23 |
24 | func (c *Config) String() string {
25 | return strconv.Itoa(int(c.EtcdId))
26 | }
27 |
--------------------------------------------------------------------------------
/etcdsdk/model/error.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import "errors"
4 |
5 | // etcd 错误定义
6 | var (
7 | ERR_CONFIG_ISNIL = errors.New("Config is nil")
8 | ERR_TLS_CONFIG_ISNIL = errors.New("TLSConfig is nil")
9 | ERR_ETCD_ADDRESS_EMPTY = errors.New("Etcd connection address cannot be empty")
10 | ERR_UNSUPPORTED_VERSION = errors.New("Unsupported etcd version")
11 |
12 | ERR_ADD_KEY = errors.New("Add key error")
13 | ERR_KEY_NOT_FOUND = errors.New("Key does not exist")
14 | ERR_KEY_NOT_DIR = errors.New("Key is not a directory")
15 | )
16 |
--------------------------------------------------------------------------------
/etcdsdk/model/interface.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | // EtcdSdk 统一操作etcd接口
4 | type EtcdSdk interface {
5 | List(path string) (list []*Node, err error) // 显示当前path下所有key
6 | Val(path string) (data *Node, err error) // 获取path的值
7 | Add(path string, data []byte) (err error) // 添加key
8 | Put(path string, data []byte) (err error) // 修改key
9 | Del(path string) (err error) // 删除key
10 | Members() (members []*Member, err error) // 获取节点列表
11 | Close() (err error) // 关闭连接
12 | }
13 |
--------------------------------------------------------------------------------
/etcdsdk/model/model.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | // 这里保存sdk使用到的所有结构图
4 |
5 | type ListNodes struct {
6 | IsDir bool `json:"is_dir"`
7 | Path string `json:"path"`
8 | Name string `json:"name"`
9 | Version int64 `json:"version"`
10 | Lease int64 `json:"lease"`
11 | }
12 |
13 | // Node 一个key 目录或文件
14 | type Node struct {
15 | IsDir bool `json:"is_dir"`
16 | Path string `json:"path"`
17 | Name string `json:"name"`
18 | Value string `json:"value"`
19 | Version int64 `json:"version"`
20 | Lease int64 `json:"lease"`
21 | }
22 |
23 | const (
24 | ROLE_LEADER = "leader"
25 | ROLE_FOLLOWER = "follower"
26 |
27 | STATUS_HEALTHY = "healthy"
28 | STATUS_UNHEALTHY = "unhealthy"
29 | )
30 |
31 | // Member 节点信息
32 | type Member struct {
33 | // *etcdserverpb.Member
34 | ID string `json:"ID"`
35 | Name string `json:"name"`
36 | PeerURLs []string `json:"peerURLs"`
37 | ClientURLs []string `json:"clientURLs"`
38 | Role string `json:"role"`
39 | Status string `json:"status"`
40 | DbSize int64 `json:"db_size"`
41 | }
42 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/etcd-manage/etcd-manage-server
2 |
3 | go 1.13
4 |
5 | replace (
6 | github.com/coreos/bbolt v1.3.5 => go.etcd.io/bbolt v1.3.5
7 | github.com/etcd-manage/etcd-manage-ui v0.0.0-00010101000000-000000000000 => ./etcd-manage-ui
8 | go.etcd.io/bbolt v1.3.5 => github.com/coreos/bbolt v1.3.5
9 | google.golang.org/grpc => google.golang.org/grpc v1.26.0
10 | )
11 |
12 | require (
13 | github.com/coreos/bbolt v1.3.5 // indirect
14 | github.com/coreos/etcd v3.3.22+incompatible // indirect
15 | github.com/coreos/go-semver v0.3.0 // indirect
16 | github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf // indirect
17 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect
18 | github.com/docker/go-metrics v0.0.1 // indirect
19 | github.com/dustin/go-humanize v1.0.0 // indirect
20 | github.com/etcd-manage/etcd-manage-ui v0.0.0-00010101000000-000000000000
21 | github.com/gin-gonic/autotls v0.0.0-20200518075542-45033372a9ad
22 | github.com/gin-gonic/gin v1.6.3
23 | github.com/go-sql-driver/mysql v1.5.0
24 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
25 | github.com/google/uuid v1.1.1
26 | github.com/gorilla/mux v1.7.4 // indirect
27 | github.com/gorilla/websocket v1.4.2 // indirect
28 | github.com/grpc-ecosystem/go-grpc-middleware v1.2.0 // indirect
29 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
30 | github.com/grpc-ecosystem/grpc-gateway v1.14.6 // indirect
31 | github.com/jinzhu/gorm v1.9.14
32 | github.com/jonboulle/clockwork v0.2.0 // indirect
33 | github.com/kylelemons/godebug v1.1.0 // indirect
34 | github.com/naoina/go-stringutil v0.1.0 // indirect
35 | github.com/naoina/toml v0.1.1
36 | github.com/opencontainers/image-spec v1.0.1 // indirect
37 | github.com/patrickmn/go-cache v2.1.0+incompatible
38 | github.com/prometheus/client_golang v1.7.1 // indirect
39 | github.com/shiguanghuxian/etcd-manage v2.0.0-beta+incompatible
40 | github.com/soheilhy/cmux v0.1.4 // indirect
41 | github.com/tmc/grpc-websocket-proxy v0.0.0-20200427203606-3cfed13b9966 // indirect
42 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect
43 | go.etcd.io/bbolt v1.3.5 // indirect
44 | go.etcd.io/etcd v3.3.22+incompatible
45 | go.uber.org/zap v1.15.0
46 | golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect
47 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f // indirect
48 | k8s.io/apimachinery v0.18.6
49 | k8s.io/kubectl v0.18.6
50 | )
51 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "os"
6 |
7 | "github.com/etcd-manage/etcd-manage-server/program"
8 | "os/signal"
9 | )
10 |
11 | func main() {
12 | // 系统日志显示文件和行号
13 | log.SetFlags(log.Lshortfile | log.LstdFlags)
14 | // 服务对象
15 | p, err := program.New()
16 | if err != nil {
17 | log.Println(err)
18 | os.Exit(1)
19 | }
20 | err = p.Run()
21 | if err != nil {
22 | log.Println(err)
23 | os.Exit(1)
24 | }
25 |
26 | // 监听退出信号
27 | c := make(chan os.Signal)
28 | signal.Notify(c, os.Interrupt, os.Kill) // , syscall.SIGUSR1, syscall.SIGUSR2
29 | <-c
30 | log.Println("Exit")
31 | }
32 |
--------------------------------------------------------------------------------
/program/api/api.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | gin "github.com/gin-gonic/gin"
5 | )
6 |
7 | // API api 路由注册
8 | type API interface {
9 | Register(router *gin.RouterGroup)
10 | }
11 |
--------------------------------------------------------------------------------
/program/api/v1/keys/keys.go:
--------------------------------------------------------------------------------
1 | package keys
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "net/http"
7 | "strconv"
8 |
9 | "github.com/etcd-manage/etcd-manage-server/etcdsdk/model"
10 |
11 | "github.com/etcd-manage/etcd-manage-server/etcdsdk"
12 | "github.com/etcd-manage/etcd-manage-server/program/publicFunc"
13 |
14 | "github.com/gin-gonic/gin"
15 | )
16 |
17 | // KeyController key控制器
18 | type KeyController struct {
19 | publicFunc.PublicF
20 | client etcdsdk.EtcdV3
21 | etcdIdNum int
22 | }
23 |
24 | // List 获取目录下key列表
25 | // /v1/keys?path=
26 | func (api *KeyController) List(c *gin.Context) {
27 | var (
28 | path string
29 | err error
30 | list []*model.Node
31 | )
32 | defer func() {
33 | api.client.Close()
34 | if err != nil {
35 | c.JSON(http.StatusBadRequest, gin.H{
36 | "msg": err.Error(),
37 | })
38 | }
39 | }()
40 |
41 | path = c.DefaultQuery("path", "/")
42 | if api.etcdIdNum, err = strconv.Atoi(c.GetHeader("EtcdID")); err != nil {
43 | fmt.Printf("获取etcdid失败:%s\n", err.Error())
44 | }
45 |
46 | if api.client, err = api.GetEtcdClient(api.etcdIdNum); err != nil {
47 | fmt.Println("获取client失败,err", err.Error())
48 | return
49 | }
50 |
51 | if list, err = api.client.List(path); err != nil {
52 | fmt.Println("获取etcd 数据失败,err", err.Error())
53 | return
54 | }
55 | c.JSON(http.StatusOK, list)
56 | }
57 |
58 | // Val 获取一个key的值
59 | func (api *KeyController) Val(c *gin.Context) {
60 | var (
61 | err error
62 | list *model.Node
63 | )
64 | defer func() {
65 | api.client.Close()
66 | if err != nil {
67 | c.JSON(http.StatusBadRequest, gin.H{
68 | "msg": err.Error(),
69 | })
70 | }
71 | }()
72 | path := c.Query("path")
73 | if api.etcdIdNum, err = strconv.Atoi(c.GetHeader("EtcdID")); err != nil {
74 | fmt.Printf("获取etcdId 失败:%s\n", err.Error())
75 | }
76 |
77 | if api.client, err = api.GetEtcdClient(api.etcdIdNum); err != nil {
78 | fmt.Println("获取etcd 数据失败,err", err.Error())
79 | return
80 | }
81 |
82 | if list, err = api.client.Val(path); err != nil {
83 | fmt.Println("获取etcd 数据失败,err", err.Error())
84 | return
85 | }
86 | c.JSON(http.StatusOK, list)
87 | }
88 |
89 | // ReqKeyBody 添加和修改key请求body
90 | type ReqKeyBody struct {
91 | Path string `json:"path"`
92 | Value string `json:"value"`
93 | }
94 |
95 | // Add 添加key
96 | func (api *KeyController) Add(c *gin.Context) {
97 | var (
98 | req ReqKeyBody
99 | err error
100 | )
101 | defer func() {
102 | api.client.Close()
103 | if err != nil {
104 | c.JSON(http.StatusBadRequest, gin.H{
105 | "msg": err.Error(),
106 | })
107 | }
108 | }()
109 | if err = c.Bind(req); err != nil {
110 | return
111 | }
112 | if api.etcdIdNum, err = strconv.Atoi(c.GetHeader("EtcdID")); err != nil {
113 | fmt.Printf("获取etcdId 失败:%s\n", err.Error())
114 | }
115 |
116 | if api.client, err = api.GetEtcdClient(api.etcdIdNum); err != nil {
117 | fmt.Println("获取etcd 数据失败,err", err.Error())
118 | return
119 | }
120 |
121 | if err = api.client.Add(req.Path, []byte(req.Value)); err != nil {
122 | return
123 | }
124 | c.JSON(http.StatusOK, "ok")
125 | }
126 |
127 | // Put 修改key
128 | func (api *KeyController) Put(c *gin.Context) {
129 | var (
130 | req ReqKeyBody
131 | err error
132 | )
133 | defer func() {
134 | api.client.Close()
135 | if err != nil {
136 | c.JSON(http.StatusBadRequest, gin.H{
137 | "msg": err.Error(),
138 | })
139 | }
140 | }()
141 |
142 | if err = c.Bind(req); err != nil {
143 | return
144 | }
145 | if api.etcdIdNum, err = strconv.Atoi(c.GetHeader("EtcdID")); err != nil {
146 | fmt.Printf("获取etcdId 失败:%s\n", err.Error())
147 | }
148 |
149 | if api.client, err = api.GetEtcdClient(api.etcdIdNum); err != nil {
150 | fmt.Println("获取etcd 数据失败,err", err.Error())
151 | return
152 | }
153 |
154 | if err = api.client.Put(req.Path, []byte(req.Value)); err != nil {
155 | return
156 | }
157 | c.JSON(http.StatusOK, "ok")
158 | }
159 |
160 | // Del 删除key
161 | func (api *KeyController) Del(c *gin.Context) {
162 | var (
163 | err error
164 | path string
165 | )
166 | defer func() {
167 | api.client.Close()
168 | if err != nil {
169 | c.JSON(http.StatusBadRequest, gin.H{
170 | "msg": err.Error(),
171 | })
172 | }
173 | }()
174 | if path = c.Query("path"); path == "" {
175 | err = errors.New("Path cannot be empty")
176 | return
177 | }
178 | if api.etcdIdNum, err = strconv.Atoi(c.GetHeader("EtcdID")); err != nil {
179 | fmt.Printf("获取etcdId 失败:%s\n", err.Error())
180 | return
181 | }
182 |
183 | if api.client, err = api.GetEtcdClient(api.etcdIdNum); err != nil {
184 | fmt.Println("获取etcd 数据失败,err", err.Error())
185 | return
186 | }
187 | if err = api.client.Del(path); err != nil {
188 | return
189 | }
190 | c.JSON(http.StatusOK, "ok")
191 | }
192 |
193 | // Members 获取etcd服务节点
194 | func (api *KeyController) Members(c *gin.Context) {
195 | var (
196 | err error
197 | members []*model.Member
198 | )
199 | defer func() {
200 | api.client.Close()
201 | if err != nil {
202 | c.JSON(http.StatusBadRequest, gin.H{
203 | "msg": err.Error(),
204 | })
205 | }
206 | }()
207 |
208 | if api.etcdIdNum, err = strconv.Atoi(c.GetHeader("EtcdID")); err != nil {
209 | fmt.Printf("获取etcdId 失败:%s\n", err.Error())
210 | return
211 | }
212 |
213 | if api.client, err = api.GetEtcdClient(api.etcdIdNum); err != nil {
214 | fmt.Println("获取etcd 数据失败,err", err.Error())
215 | return
216 | }
217 |
218 | if members, err = api.client.Members(); err != nil {
219 | return
220 | }
221 | c.JSON(http.StatusOK, members)
222 | }
223 |
--------------------------------------------------------------------------------
/program/api/v1/passport/passport.go:
--------------------------------------------------------------------------------
1 | package passport
2 |
3 | import (
4 | "encoding/json"
5 | "log"
6 | "net/http"
7 | "time"
8 |
9 | "github.com/etcd-manage/etcd-manage-server/program/cache"
10 | "github.com/etcd-manage/etcd-manage-server/program/common"
11 | "github.com/etcd-manage/etcd-manage-server/program/logger"
12 | "github.com/etcd-manage/etcd-manage-server/program/models"
13 | "github.com/gin-gonic/gin"
14 | "github.com/google/uuid"
15 | )
16 |
17 | // PassportController 登录退出
18 | type PassportController struct {
19 | }
20 |
21 | // LoginReq 登录请求参数
22 | type LoginReq struct {
23 | Username string `json:"username,omitempty"`
24 | Password string `json:"password,omitempty"`
25 | }
26 |
27 | func (api *PassportController) Login(c *gin.Context) {
28 | req := new(LoginReq)
29 | err := c.Bind(req)
30 | if err != nil {
31 | c.JSON(http.StatusBadRequest, gin.H{
32 | "msg": err.Error(),
33 | })
34 | return
35 | }
36 | if req.Username == "" || req.Password == "" {
37 | c.JSON(http.StatusBadRequest, gin.H{
38 | "msg": "参数错误",
39 | })
40 | return
41 | }
42 | // 查询数据库登录
43 | req.Password = common.Md5Password(req.Password)
44 | log.Println(req.Password)
45 | user := new(models.UsersModel)
46 | err = user.FirstByUsernameAndPassword(req.Username, req.Password)
47 | if err != nil {
48 | logger.Log.Errorw("登录查询用户错误", "err", err)
49 | c.JSON(http.StatusBadRequest, gin.H{
50 | "msg": "用户名或密码错误",
51 | })
52 | return
53 | }
54 | user.Password = ""
55 | user.Token = uuid.New().String()
56 | // 存储Token
57 | jsUser, _ := json.Marshal(user)
58 | key := cache.GetLoginKey(user.Token)
59 | cache.DefaultMemCache.Set(key, string(jsUser), 7*24*time.Hour)
60 |
61 | c.JSON(http.StatusOK, user)
62 | }
63 |
--------------------------------------------------------------------------------
/program/api/v1/router.go:
--------------------------------------------------------------------------------
1 | package v1
2 |
3 | import (
4 | "github.com/etcd-manage/etcd-manage-server/program/api/v1/keys"
5 | "github.com/etcd-manage/etcd-manage-server/program/api/v1/passport"
6 | "github.com/etcd-manage/etcd-manage-server/program/api/v1/server"
7 | "github.com/etcd-manage/etcd-manage-server/program/api/v1/setings/role"
8 | "github.com/etcd-manage/etcd-manage-server/program/api/v1/setings/user"
9 | "github.com/etcd-manage/etcd-manage-server/program/api/v1/upload"
10 | "github.com/gin-gonic/gin"
11 | )
12 |
13 | // APIV1 v1版接口
14 | type APIV1 struct {
15 | }
16 |
17 | // Register 注册路由
18 | func (v1 *APIV1) Register(router *gin.RouterGroup) {
19 | // etcd key 管理
20 | gx := router.Group("/keys")
21 | keysController := new(keys.KeyController)
22 | gx.GET("", keysController.List)
23 | gx.GET("/val", keysController.Val)
24 | gx.POST("", keysController.Add)
25 | gx.PUT("", keysController.Put)
26 | gx.DELETE("", keysController.Del)
27 |
28 | // 一个etcd服务集群信息
29 | gx.GET("/members", keysController.Members)
30 |
31 | // etcd服务列表
32 | serverController := new(server.ServerController)
33 | gs := router.Group("/server")
34 | gs.GET("", serverController.List)
35 | gs.POST("", serverController.Add)
36 | gs.PUT("", serverController.Update)
37 | gs.DELETE("", serverController.Del)
38 | gs.GET("/restore", serverController.Restore)
39 | gs.POST("/roles", serverController.SetRoles)
40 | gs.GET("/roles", serverController.GetRoles)
41 |
42 | // 认证中心
43 | passportController := new(passport.PassportController)
44 | gp := router.Group("/passport")
45 | gp.POST("/login", passportController.Login)
46 |
47 | // 角色
48 | roleController := new(role.RoleController)
49 | rs := router.Group("/role")
50 | rs.GET("", roleController.All)
51 | rs.POST("", roleController.Add)
52 | rs.PUT("", roleController.Update)
53 | rs.DELETE("", roleController.Del)
54 |
55 | // 用户
56 | userController := new(user.UserController)
57 | us := router.Group("/user")
58 | us.GET("", userController.List)
59 | us.POST("", userController.Add)
60 | us.PUT("", userController.Update)
61 | us.DELETE("", userController.Del)
62 |
63 | // 文件上传
64 | uploadController := new(upload.UploadController)
65 | uus := router.Group("/upload")
66 | uus.POST("/content", uploadController.UploadOutContent)
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/program/api/v1/server/server.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "net/http"
7 | "strconv"
8 | "strings"
9 |
10 | "github.com/etcd-manage/etcd-manage-server/etcdsdk"
11 | "github.com/etcd-manage/etcd-manage-server/etcdsdk/model"
12 | "github.com/etcd-manage/etcd-manage-server/program/common"
13 | "github.com/etcd-manage/etcd-manage-server/program/logger"
14 | "github.com/etcd-manage/etcd-manage-server/program/models"
15 | "github.com/gin-gonic/gin"
16 |
17 | //"strings"
18 | "time"
19 | )
20 |
21 | // ServerController etcd服务列表相关操作
22 | type ServerController struct {
23 | }
24 |
25 | // List 获取etcd服务列表,全部
26 | func (api *ServerController) List(c *gin.Context) {
27 | name := c.Query("name")
28 | // 查询当前角色权限
29 | userinfoObj, exist := c.Get("userinfo")
30 | if exist == false {
31 | logger.Log.Warnw("用户登录信息不存在")
32 | c.AbortWithStatus(http.StatusUnauthorized)
33 | return
34 | }
35 | userinfo := userinfoObj.(*models.UsersModel)
36 |
37 | list, err := new(models.EtcdServersModel).All(name, userinfo.RoleId)
38 | if err != nil {
39 | c.JSON(http.StatusBadRequest, gin.H{
40 | "msg": err.Error(),
41 | })
42 | }
43 | c.JSON(http.StatusOK, list)
44 | }
45 |
46 | // Add 添加服务
47 | func (api *ServerController) Add(c *gin.Context) {
48 | var err error
49 | defer func() {
50 | if err != nil {
51 | c.JSON(http.StatusBadRequest, gin.H{
52 | "msg": err.Error(),
53 | })
54 | }
55 | }()
56 | // 添加
57 | req := new(models.EtcdServersModel)
58 | err = c.Bind(req)
59 | if err != nil {
60 | return
61 | }
62 | now := models.JSONTime(time.Now())
63 | req.CreatedAt = now
64 | err = req.Insert()
65 | if err != nil {
66 | return
67 | }
68 | // 添加超级管理员权限
69 | re := &models.RoleEtcdServersModel{
70 | EtcdServerId: req.ID,
71 | Type: 1,
72 | RoleId: 1,
73 | CreatedAt: now,
74 | UpdatedAt: now,
75 | }
76 |
77 | if err = re.Save(); err != nil {
78 | return
79 | }
80 | c.JSON(http.StatusOK, "ok")
81 | }
82 |
83 | // Update 修改服务
84 | func (api *ServerController) Update(c *gin.Context) {
85 | var err error
86 | if err != nil {
87 | c.JSON(http.StatusBadRequest, gin.H{
88 | "msg": err.Error(),
89 | })
90 | }
91 | // 添加
92 | req := new(models.EtcdServersModel)
93 | err = c.Bind(req)
94 | if err != nil {
95 | return
96 | }
97 | req.UpdatedAt = models.JSONTime(time.Now())
98 | err = req.Update()
99 | if err != nil {
100 | return
101 | }
102 |
103 | c.JSON(http.StatusOK, "ok")
104 | }
105 |
106 | // Restore 修复v1版本或e3w对目录的标记
107 | // /v1/server/restore?etcd_id=6"
108 | func (api *ServerController) Restore(c *gin.Context) {
109 | etcdId := c.Query("etcd_id")
110 | var err error
111 | defer func() {
112 | if err != nil {
113 | c.JSON(http.StatusBadRequest, gin.H{
114 | "msg": err.Error(),
115 | })
116 | }
117 | }()
118 |
119 | etcdIdNum, _ := strconv.Atoi(etcdId)
120 | etcdOne := new(models.EtcdServersModel)
121 | etcdOne, err = etcdOne.FirstById(int32(etcdIdNum))
122 | if err != nil {
123 | return
124 | }
125 | if etcdOne.Version != model.ETCD_VERSION_V3 {
126 | err = errors.New("Only V3 version is allowed to be repaired")
127 | return
128 | }
129 | // 连接etcd
130 | cfg := model.Config{
131 | Version: etcdOne.Version,
132 | Address: strings.Split(etcdOne.Address, ","),
133 | TlsEnable: etcdOne.TlsEnable,
134 | CertFile: common.Base64Decode(etcdOne.CaFile),
135 | KeyFile: common.Base64Decode(etcdOne.KeyFile),
136 | CaFile: common.Base64Decode(etcdOne.CaFile),
137 | Username: etcdOne.Username,
138 | Password: etcdOne.Password,
139 | }
140 | //cfg := &model.Config{
141 | // Version: "v3",
142 | // Address: []string{"https://127.0.0.1:2379"},
143 | // TlsEnable: "true",
144 | // CertFile: "/Users/liuqixiang/project/goStudyProject/etcd-ui/etcd-manage-server/0_cert.pem",
145 | // KeyFile: "/Users/liuqixiang/project/goStudyProject/etcd-ui/etcd-manage-server/0_key.pem",
146 | // CaFile: "/Users/liuqixiang/project/goStudyProject/etcd-ui/etcd-manage-server/0_ca.pem",
147 | //}
148 |
149 | if _, err = etcdsdk.NewClient(cfg); err != nil {
150 | fmt.Println("etcdv3.NewClient(cfg)>>>", err)
151 | return
152 | }
153 | //clientV3, ok := client.(*etcdsdk.EtcdV3)
154 | //if ok == false {
155 | // err = errors.New("Connecting etcd V3 service error")
156 | // return
157 | //}
158 | //err = client.Restore()
159 | //if err != nil {
160 | // fmt.Println("clientV3.Restore()>>>", err)
161 | // return
162 | //}
163 | c.JSON(http.StatusOK, "ok")
164 | }
165 |
166 | // SetRoles 设置etcd服务角色
167 | func (api *ServerController) SetRoles(c *gin.Context) {
168 | req := make([]*models.AllByEtcdIdData, 0)
169 | err := c.Bind(&req)
170 | if err != nil {
171 | c.JSON(http.StatusBadRequest, gin.H{
172 | "msg": err.Error(),
173 | })
174 | return
175 | }
176 | if len(req) == 0 {
177 | c.JSON(http.StatusBadRequest, gin.H{
178 | "msg": "未设置任何权限",
179 | })
180 | return
181 | }
182 | m := new(models.RoleEtcdServersModel)
183 | err = m.UpByEtcdId(req)
184 | if err != nil {
185 | c.JSON(http.StatusBadRequest, gin.H{
186 | "msg": err.Error(),
187 | })
188 | return
189 | }
190 |
191 | c.JSON(http.StatusOK, "ok")
192 | }
193 |
194 | // GetRoles 获取etcd服务权限列表
195 | func (api *ServerController) GetRoles(c *gin.Context) {
196 | etcdId := common.GetHttpToInt(c, "etcd_id")
197 | if etcdId <= 0 {
198 | c.JSON(http.StatusBadRequest, gin.H{
199 | "msg": "参数错误",
200 | })
201 | return
202 | }
203 | list, err := new(models.RoleEtcdServersModel).AllByEtcdId(int32(etcdId))
204 | if err != nil {
205 | c.JSON(http.StatusBadRequest, gin.H{
206 | "msg": err.Error(),
207 | })
208 | return
209 | }
210 |
211 | c.JSON(http.StatusOK, list)
212 | }
213 |
214 | // Del 删除
215 | func (s *ServerController) Del(c *gin.Context) {
216 | id := c.Query("id")
217 | idNum, _ := strconv.Atoi(id)
218 | if idNum == 0 {
219 | c.JSON(http.StatusBadRequest, gin.H{
220 | "msg": "参数错误",
221 | })
222 | return
223 | }
224 | err := new(models.EtcdServersModel).Del(int32(idNum))
225 | if err != nil {
226 | c.JSON(http.StatusBadRequest, gin.H{
227 | "msg": err.Error(),
228 | })
229 | return
230 | }
231 |
232 | c.JSON(http.StatusOK, nil)
233 | }
234 |
--------------------------------------------------------------------------------
/program/api/v1/setings/role/role.go:
--------------------------------------------------------------------------------
1 | package role
2 |
3 | import (
4 | "net/http"
5 | "strconv"
6 | "time"
7 |
8 | "github.com/etcd-manage/etcd-manage-server/program/models"
9 | "github.com/gin-gonic/gin"
10 | )
11 |
12 | /* 角色管理 */
13 |
14 | // RoleController 系统管理设置相关
15 | type RoleController struct {
16 | }
17 |
18 | // All 获取所有角色
19 | func (s *RoleController) All(c *gin.Context) {
20 | list, err := new(models.RolesModel).All()
21 | if err != nil {
22 | c.JSON(http.StatusBadRequest, gin.H{
23 | "msg": err.Error(),
24 | })
25 | return
26 | }
27 | c.JSON(http.StatusOK, list)
28 | }
29 |
30 | // Add 添加角色
31 | func (s *RoleController) Add(c *gin.Context) {
32 | req := new(models.RolesModel)
33 | err := c.Bind(req)
34 | if err != nil {
35 | c.JSON(http.StatusBadRequest, gin.H{
36 | "msg": err.Error(),
37 | })
38 | return
39 | }
40 | now := models.JSONTime(time.Now())
41 | req.CreatedAt = now
42 | req.UpdatedAt = now
43 | req.Id = 0
44 | err = req.Save()
45 | if err != nil {
46 | c.JSON(http.StatusBadRequest, gin.H{
47 | "msg": err.Error(),
48 | })
49 | return
50 | }
51 |
52 | c.JSON(http.StatusOK, nil)
53 | }
54 |
55 | // Del 删除角色
56 | func (s *RoleController) Del(c *gin.Context) {
57 | id := c.Query("id")
58 | idNum, _ := strconv.Atoi(id)
59 | if idNum == 0 {
60 | c.JSON(http.StatusBadRequest, gin.H{
61 | "msg": "参数错误",
62 | })
63 | return
64 | }
65 | err := new(models.RolesModel).Del(int32(idNum))
66 | if err != nil {
67 | c.JSON(http.StatusBadRequest, gin.H{
68 | "msg": err.Error(),
69 | })
70 | return
71 | }
72 | c.JSON(http.StatusOK, nil)
73 | }
74 |
75 | // Up 修改角色
76 | func (s *RoleController) Update(c *gin.Context) {
77 | req := new(models.RolesModel)
78 | err := c.Bind(req)
79 | if err != nil {
80 | c.JSON(http.StatusBadRequest, gin.H{
81 | "msg": err.Error(),
82 | })
83 | return
84 | }
85 | if req.Id <= 0 {
86 | c.JSON(http.StatusBadRequest, gin.H{
87 | "msg": "参数错误",
88 | })
89 | return
90 | }
91 | now := models.JSONTime(time.Now())
92 | req.UpdatedAt = now
93 | err = req.Save()
94 | if err != nil {
95 | c.JSON(http.StatusBadRequest, gin.H{
96 | "msg": err.Error(),
97 | })
98 | return
99 | }
100 |
101 | c.JSON(http.StatusOK, nil)
102 | }
103 |
--------------------------------------------------------------------------------
/program/api/v1/setings/user/user.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "net/http"
5 | "strconv"
6 | "strings"
7 | "time"
8 |
9 | "github.com/etcd-manage/etcd-manage-server/program/common"
10 | "github.com/etcd-manage/etcd-manage-server/program/logger"
11 | "github.com/etcd-manage/etcd-manage-server/program/models"
12 | "github.com/gin-gonic/gin"
13 | )
14 |
15 | /* 用户管理 */
16 |
17 | // UserController 用户管理
18 | type UserController struct {
19 | }
20 |
21 | // List 分页获取
22 | func (s *UserController) List(c *gin.Context) {
23 | var err error
24 | defer func() {
25 | if err != nil {
26 | c.JSON(http.StatusBadRequest, gin.H{
27 | "msg": err.Error(),
28 | })
29 | }
30 | }()
31 | // 读取条件
32 | name := c.Query("name")
33 | userId := common.GetHttpToInt(c, "user_id")
34 | roleId := common.GetHttpToInt(c, "role_id")
35 | page := common.GetHttpToInt(c, "page")
36 | pageSize := common.GetHttpToInt(c, "page_size")
37 | if page == 0 {
38 | page = 1
39 | }
40 | offset := (page - 1) * pageSize
41 | // 查询列表
42 | user := new(models.UsersModel)
43 | list, err := user.List(int32(userId), int32(roleId), name, offset, pageSize)
44 | if err != nil {
45 | logger.Log.Errorw("查询用户列表错误", "err", err)
46 | return
47 | }
48 | _c, err := user.ListCount(int32(userId), int32(roleId), name, offset, pageSize)
49 | if err != nil {
50 | logger.Log.Errorw("查询用户总数错误", "err", err)
51 | return
52 | }
53 | // 去除密码
54 | for _, v := range list {
55 | v.Password = ""
56 | }
57 |
58 | c.JSON(http.StatusOK, gin.H{
59 | "list": list,
60 | "total": _c,
61 | })
62 | }
63 |
64 | // Add 添加
65 | func (s *UserController) Add(c *gin.Context) {
66 | req := new(models.UsersModel)
67 | err := c.Bind(req)
68 | if err != nil {
69 | c.JSON(http.StatusBadRequest, gin.H{
70 | "msg": err.Error(),
71 | })
72 | return
73 | }
74 | if req.RoleId <= 0 {
75 | c.JSON(http.StatusBadRequest, gin.H{
76 | "msg": "请选择角色角色",
77 | })
78 | return
79 | }
80 | req.Password = strings.TrimSpace(req.Password)
81 | if len(req.Password) < 6 {
82 | c.JSON(http.StatusBadRequest, gin.H{
83 | "msg": "密码长度不能小于6",
84 | })
85 | return
86 | }
87 | req.Password = common.Md5Password(req.Password)
88 | now := models.JSONTime(time.Now())
89 | req.CreatedAt = now
90 | req.UpdatedAt = now
91 | req.Id = 0
92 | err = req.Save()
93 | if err != nil {
94 | c.JSON(http.StatusBadRequest, gin.H{
95 | "msg": err.Error(),
96 | })
97 | return
98 | }
99 |
100 | c.JSON(http.StatusOK, nil)
101 | }
102 |
103 | // Del 删除
104 | func (s *UserController) Del(c *gin.Context) {
105 | id := c.Query("id")
106 | idNum, _ := strconv.Atoi(id)
107 | if idNum == 0 {
108 | c.JSON(http.StatusBadRequest, gin.H{
109 | "msg": "参数错误",
110 | })
111 | return
112 | }
113 | err := new(models.UsersModel).Del(int32(idNum))
114 | if err != nil {
115 | c.JSON(http.StatusBadRequest, gin.H{
116 | "msg": err.Error(),
117 | })
118 | return
119 | }
120 | c.JSON(http.StatusOK, nil)
121 | }
122 |
123 | // Up 修改
124 | func (s *UserController) Update(c *gin.Context) {
125 | req := new(models.UsersModel)
126 | err := c.Bind(req)
127 | if err != nil {
128 | c.JSON(http.StatusBadRequest, gin.H{
129 | "msg": err.Error(),
130 | })
131 | return
132 | }
133 | if req.Id <= 0 {
134 | c.JSON(http.StatusBadRequest, gin.H{
135 | "msg": "参数错误",
136 | })
137 | return
138 | }
139 | req.Password = strings.TrimSpace(req.Password)
140 | omit := make([]string, 0)
141 | omit = append(omit, "created_at")
142 | if req.Password != "" {
143 | if len(req.Password) < 6 {
144 | c.JSON(http.StatusBadRequest, gin.H{
145 | "msg": "密码长度不能小于6",
146 | })
147 | return
148 | }
149 | req.Password = common.Md5Password(req.Password)
150 | } else {
151 | omit = append(omit, "password")
152 | }
153 |
154 | now := models.JSONTime(time.Now())
155 | req.UpdatedAt = now
156 | err = req.Save(omit...)
157 | if err != nil {
158 | c.JSON(http.StatusBadRequest, gin.H{
159 | "msg": err.Error(),
160 | })
161 | return
162 | }
163 |
164 | c.JSON(http.StatusOK, nil)
165 | }
166 |
--------------------------------------------------------------------------------
/program/api/v1/upload/upload.go:
--------------------------------------------------------------------------------
1 | package upload
2 |
3 | import (
4 | "errors"
5 | "io/ioutil"
6 | "net/http"
7 |
8 | "github.com/gin-gonic/gin"
9 | )
10 |
11 | type UploadController struct {
12 | }
13 |
14 | // UploadOutContent 上传文件,返回内容
15 | func (api *UploadController) UploadOutContent(c *gin.Context) {
16 | var err error
17 | defer func() {
18 | if err != nil {
19 | c.JSON(http.StatusBadRequest, gin.H{
20 | "msg": err.Error(),
21 | })
22 | }
23 | }()
24 |
25 | fileInfo, err := c.FormFile("file")
26 | if err != nil {
27 | return
28 | }
29 | if fileInfo.Size > 1*1024*1024 {
30 | err = errors.New("上传文件大于1Mb")
31 | return
32 | }
33 | f, err := fileInfo.Open()
34 | if err != nil {
35 | return
36 | }
37 |
38 | content, err := ioutil.ReadAll(f)
39 | if err != nil {
40 | return
41 | }
42 | c.JSON(http.StatusOK, gin.H{
43 | "content": content,
44 | })
45 | }
46 |
--------------------------------------------------------------------------------
/program/cache/cache.go:
--------------------------------------------------------------------------------
1 | package cache
2 |
3 | import (
4 | "fmt"
5 | "time"
6 | )
7 |
8 | /* 缓存对象 */
9 |
10 | // Cache 缓存接口 - 用户缓存登录信息
11 | type Cache interface {
12 | // Get 获取一个缓存
13 | Get(key string) (val string, exist bool)
14 | // Set 设置一个值
15 | Set(key, val string, expiration time.Duration)
16 | // Del 删除知道keys
17 | Del(key ...string) (err error)
18 | }
19 |
20 | // 常量
21 | const (
22 | LoginKey = "login:%s"
23 | )
24 |
25 | // GetLoginKey 获取登录key
26 | func GetLoginKey(token string) string {
27 | return fmt.Sprintf(LoginKey, token)
28 | }
29 |
--------------------------------------------------------------------------------
/program/cache/default.go:
--------------------------------------------------------------------------------
1 | package cache
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/patrickmn/go-cache"
7 | )
8 |
9 | /* 默认缓存 - 内存 */
10 |
11 | var (
12 | // DefaultMemCache 默认缓存对象
13 | DefaultMemCache Cache
14 | )
15 |
16 | func init() {
17 | cli := cache.New(7*24*time.Hour, 10*time.Second)
18 | DefaultMemCache = &MemCache{
19 | cli: cli,
20 | }
21 | }
22 |
23 | // MemCache 内存缓存 https://github.com/patrickmn/go-cache
24 | type MemCache struct {
25 | cli *cache.Cache
26 | }
27 |
28 | // Get 获取一个缓存
29 | func (mem *MemCache) Get(key string) (val string, exist bool) {
30 | valI, exist := mem.cli.Get(key)
31 | if exist == true {
32 | val = valI.(string)
33 | }
34 | return
35 | }
36 |
37 | // Set 设置一个值
38 | func (mem *MemCache) Set(key, val string, expiration time.Duration) {
39 | mem.cli.Set(key, val, expiration)
40 | }
41 |
42 | // Del 删除知道keys
43 | func (mem *MemCache) Del(key ...string) (err error) {
44 | for _, k := range key {
45 | mem.cli.Delete(k)
46 | }
47 | return
48 | }
49 |
--------------------------------------------------------------------------------
/program/common/common.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | import (
4 | "crypto/md5"
5 | "encoding/base64"
6 | "encoding/hex"
7 | "errors"
8 | "fmt"
9 | "os"
10 | "path/filepath"
11 | "strconv"
12 |
13 | "github.com/etcd-manage/etcd-manage-server/etcdsdk/model"
14 | "github.com/gin-gonic/gin"
15 | )
16 |
17 | // GetRootDir 获取执行路径
18 | func GetRootDir() string {
19 | // 文件不存在获取执行路径
20 | file, err := filepath.Abs(filepath.Dir(os.Args[0]))
21 | if err != nil {
22 | file = fmt.Sprintf(".%s", string(os.PathSeparator))
23 | } else {
24 | file = fmt.Sprintf("%s%s", file, string(os.PathSeparator))
25 | }
26 | return file
27 | }
28 |
29 | // PathExists 判断文件或目录是否存在
30 | func PathExists(path string) (bool, error) {
31 | _, err := os.Stat(path)
32 | if err == nil {
33 | return true, nil
34 | }
35 | if os.IsNotExist(err) {
36 | return false, nil
37 | }
38 | return false, err
39 | }
40 |
41 | // GetEtcdClientByGinContext 获取一个etcd客户端 从gin请求上下文
42 | func GetEtcdClientByGinContext(c *gin.Context) (client model.EtcdSdk, err error) {
43 | clientI, exists := c.Get("CLIENT")
44 |
45 | if exists == false || clientI == nil {
46 | err = errors.New("Etcd service connection error")
47 | return
48 | }
49 | client = clientI.(model.EtcdSdk)
50 | return
51 | }
52 |
53 | // Md5Password 密码生成
54 | func Md5Password(password string) string {
55 | salt := "etcd-manage"
56 | return Md5(Md5(password) + Md5(salt))
57 | }
58 |
59 | // Md5 计算字符串md5值
60 | func Md5(s string) string {
61 | h := md5.New()
62 | h.Write([]byte(s))
63 | return hex.EncodeToString(h.Sum(nil))
64 | }
65 |
66 | // GetHttpToInt 获取请求参数,转为int
67 | func GetHttpToInt(c *gin.Context, name string) int {
68 | valStr := c.Query(name)
69 | val, _ := strconv.Atoi(valStr)
70 | return val
71 | }
72 |
73 | func Base64Decode(base64String string) []byte {
74 | var (
75 | decoded []byte
76 | err error
77 | )
78 | if decoded, err = base64.StdEncoding.DecodeString(base64String); err != nil {
79 | fmt.Printf("解密失败:%s\n", err.Error())
80 | }
81 | return decoded
82 | }
83 |
--------------------------------------------------------------------------------
/program/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "os"
5 |
6 | //"github.com/etcd-manage/etcd-manage-server/program/common"
7 | "github.com/naoina/toml"
8 | )
9 |
10 | // Config 配置
11 | type Config struct {
12 | Debug bool `toml:"debug"`
13 | LogPath string `toml:"log_path"`
14 | HTTP *HTTP `toml:"http"`
15 | DB *MySQLConfig `toml:"db"`
16 | }
17 |
18 | // HTTP http 件套配置
19 | type HTTP struct {
20 | Address string `toml:"address"`
21 | Port int `toml:"port"`
22 | TLSEnable bool `toml:"tls_enable"` // 是否启用tls连接
23 | TLSConfig *HTTPTls `toml:"tls_config"` // 启用tls时必须配置此内容
24 | TLSEncryptEnable bool `toml:"tls_encrypt_enable"` // 是否启用 Let's Encrypt tls
25 | TLSEncryptDomainNames []string `toml:"tls_encrypt_domain_names"` // 启用 Let's Encrypt 时的域名列表
26 | }
27 |
28 | // HTTPTls http tls配置
29 | type HTTPTls struct {
30 | CertFile string `toml:"cert_file"`
31 | KeyFile string `toml:"key_file"`
32 | }
33 |
34 | // MySQLConfig 数据库配置
35 | type MySQLConfig struct {
36 | Debug bool `toml:"debug"` // 是否调试模式
37 | Address string `toml:"address"` // 数据库连接地址
38 | Port int `toml:"port"` // 数据库端口
39 | MaxIdleConns int `toml:"max_idle_conns"` // 连接池最大连接数
40 | MaxOpenConns int `toml:"max_open_conns"` // 默认打开连接数
41 | User string `toml:"user"` // 数据库用户名
42 | Passwd string `toml:"passwd"` // 数据库密码
43 | DbName string `toml:"db_name"` // 数据库名
44 | }
45 |
46 | var (
47 | cfg *Config
48 | )
49 |
50 | // GetCfg 获取配置
51 | func GetCfg() *Config {
52 | if cfg == nil {
53 | LoadConfig("")
54 | }
55 | return cfg
56 | }
57 |
58 | // LoadConfig 读取配置
59 | func LoadConfig(cfgPath string) (*Config, error) {
60 | cfgPath = getCfgPath(cfgPath)
61 | f, err := os.Open(cfgPath)
62 | if err != nil {
63 | return nil, err
64 | }
65 | defer f.Close()
66 | cfg = new(Config)
67 | if err := toml.NewDecoder(f).Decode(cfg); err != nil {
68 | return nil, err
69 | }
70 |
71 | return cfg, nil
72 | }
73 |
74 | func getCfgPath(cfgPath string) string {
75 | if cfgPath == "" {
76 | cfgPath="/Users/liuqixiang/project/goStudyProject/etcd-ui/etcd-manage-server/bin/config/cfg.toml"
77 | //cfgPath = common.GetRootDir() + "bin/config/cfg.toml"
78 | }
79 | return cfgPath
80 | }
81 |
--------------------------------------------------------------------------------
/program/http_ui.go:
--------------------------------------------------------------------------------
1 | package program
2 |
3 | import (
4 | "log"
5 | "mime"
6 | "net/http"
7 | "path"
8 | "strings"
9 |
10 | "github.com/etcd-manage/etcd-manage-server/program/logger"
11 | "github.com/etcd-manage/etcd-manage-ui/tpls"
12 | gin "github.com/gin-gonic/gin"
13 | )
14 |
15 | // ui 界面
16 | // 处理静态文件
17 | func (p *Program) handlerStatic(c *gin.Context) {
18 | uri := strings.TrimLeft(c.Request.RequestURI, "/")
19 | uri = strings.TrimRight(uri, "?")
20 | log.Println(uri)
21 | if uri == "ui/" || uri == "ui" {
22 | uri = "dist/index.html"
23 | } else {
24 | uri = strings.Replace(uri, "ui", "dist", 1)
25 | }
26 | // log.Println(uri)
27 | // 读取模版内容
28 | body, err := tpls.Asset(uri)
29 | if err != nil {
30 | logger.Log.Errorw("UI static file reading error", "err", err)
31 | c.Status(http.StatusNotFound)
32 | return
33 | }
34 | mimetype := mime.TypeByExtension(path.Ext(uri))
35 | if mimetype != "" {
36 | c.Header("Content-Type", mimetype)
37 | }
38 |
39 | c.Writer.Write(body)
40 | }
41 |
--------------------------------------------------------------------------------
/program/logger/logger.go:
--------------------------------------------------------------------------------
1 | package logger
2 |
3 | import (
4 | "net/url"
5 | "os"
6 | "runtime"
7 | "strings"
8 |
9 | "github.com/shiguanghuxian/etcd-manage/program/common"
10 | "go.uber.org/zap"
11 | "go.uber.org/zap/zapcore"
12 | )
13 |
14 | // 日志对象
15 | var (
16 | Log *zap.SugaredLogger
17 | )
18 |
19 | // InitLogger 日志初始化,用于记录操作日志
20 | func InitLogger(logPath string, isDebug bool) (*zap.SugaredLogger, error) {
21 | infoLogPath := ""
22 | // errorLogPath := ""
23 | if logPath == "" {
24 | logRoot := common.GetRootDir() + "logs" + string(os.PathSeparator)
25 | if isExt, _ := common.PathExists(logRoot); isExt == false {
26 | os.MkdirAll(logRoot, os.ModePerm)
27 | }
28 | infoLogPath = logRoot + "access.log"
29 | } else {
30 | logPath = strings.TrimRight(logPath, string(os.PathSeparator))
31 | infoLogPath = logPath + string(os.PathSeparator) + "access.log"
32 | }
33 |
34 | // 兼容win根完整路径问题
35 | zap.RegisterSink("winfile", func(u *url.URL) (zap.Sink, error) {
36 | return os.OpenFile(u.Path[1:], os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
37 | })
38 |
39 | cfg := &zap.Config{
40 | Encoding: "json",
41 | }
42 | cfg.EncoderConfig = zap.NewProductionEncoderConfig()
43 | atom := zap.NewAtomicLevel()
44 | if isDebug == true {
45 | atom.SetLevel(zapcore.DebugLevel)
46 | cfg.OutputPaths = []string{"stdout"}
47 | } else {
48 | atom.SetLevel(zapcore.InfoLevel)
49 | if runtime.GOOS == "windows" {
50 | cfg.OutputPaths = []string{"winfile:///" + infoLogPath}
51 | } else {
52 | cfg.OutputPaths = []string{infoLogPath}
53 | }
54 | }
55 | cfg.Level = atom
56 | logger, err := cfg.Build()
57 | if err != nil {
58 | return nil, err
59 | }
60 | Log = logger.Sugar()
61 | return Log, nil
62 | }
63 |
--------------------------------------------------------------------------------
/program/middleware.go:
--------------------------------------------------------------------------------
1 | package program
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "log"
7 | "net/http"
8 | "net/url"
9 | "strconv"
10 | "strings"
11 | "time"
12 |
13 | "github.com/jinzhu/gorm"
14 |
15 | "github.com/etcd-manage/etcd-manage-server/program/cache"
16 | "github.com/etcd-manage/etcd-manage-server/program/logger"
17 | "github.com/etcd-manage/etcd-manage-server/program/models"
18 | "github.com/gin-gonic/autotls"
19 | gin "github.com/gin-gonic/gin"
20 | )
21 |
22 | // http服务
23 |
24 | func (p *Program) startAPI() {
25 | router := gin.Default()
26 |
27 | // 跨域问题
28 | router.Use(p.middlewareCORS())
29 | router.Use(p.middlewareAuth())
30 | router.Use(p.middlewareRoleAuth())
31 |
32 | // 设置静态文件目录
33 | router.GET("/ui/*w", p.handlerStatic)
34 | router.GET("/", func(c *gin.Context) {
35 | c.Redirect(301, "/ui")
36 | })
37 |
38 | // 启动所有版本api
39 | for key, val := range p.vApis {
40 | vAPI := router.Group("/" + key)
41 | vAPI.Use()
42 | val.Register(vAPI)
43 | }
44 |
45 | addr := fmt.Sprintf("%s:%d", p.cfg.HTTP.Address, p.cfg.HTTP.Port)
46 | // 监听
47 | s := &http.Server{
48 | Addr: addr,
49 | Handler: router,
50 | ReadTimeout: 10 * time.Second,
51 | WriteTimeout: 60 * time.Second,
52 | }
53 |
54 | log.Println("Start HTTP the service:", addr)
55 | var err error
56 | if p.cfg.HTTP.TLSEnable == true {
57 | if p.cfg.HTTP.TLSConfig == nil || p.cfg.HTTP.TLSConfig.CertFile == "" || p.cfg.HTTP.TLSConfig.KeyFile == "" {
58 | log.Fatalln("Enable tls must configure certificate file path")
59 | }
60 | err = s.ListenAndServeTLS(p.cfg.HTTP.TLSConfig.CertFile, p.cfg.HTTP.TLSConfig.KeyFile)
61 | } else if p.cfg.HTTP.TLSEncryptEnable == true {
62 | if len(p.cfg.HTTP.TLSEncryptDomainNames) == 0 {
63 | log.Fatalln("The domain name list cannot be empty")
64 | }
65 | err = autotls.Run(router, p.cfg.HTTP.TLSEncryptDomainNames...)
66 | } else {
67 | err = s.ListenAndServe()
68 | }
69 | if err != nil {
70 | log.Fatalln(err)
71 | }
72 |
73 | }
74 |
75 | // middlewareAuth 获取是否登录
76 | func (p *Program) middlewareAuth() gin.HandlerFunc {
77 | return func(c *gin.Context) {
78 | u, _ := url.ParseRequestURI(c.Request.RequestURI)
79 | if strings.HasPrefix(u.Path, "/v1/passport") == false && strings.HasPrefix(u.Path, "/ui") == false && strings.HasPrefix(u.Path, "/v1/upload") == false {
80 | // log.Println(u.Path)
81 | token := c.Request.Header.Get("Token")
82 | if token == "" {
83 | c.AbortWithStatus(http.StatusUnauthorized)
84 | return
85 | }
86 | // 获取用户信息
87 | key := cache.GetLoginKey(token)
88 | val, exist := cache.DefaultMemCache.Get(key)
89 | if exist == false {
90 | logger.Log.Warnw("用户登录信息不存在")
91 | c.AbortWithStatus(http.StatusUnauthorized)
92 | return
93 | }
94 | // 解析为用户登录信息
95 | user := new(models.UsersModel)
96 | err := json.Unmarshal([]byte(val), user)
97 | if err != nil {
98 | logger.Log.Warnw("用户登录信息解析json错误", "err", err)
99 | c.AbortWithStatus(http.StatusUnauthorized)
100 | return
101 | }
102 | // 存储用户信息到上下文
103 | c.Set("userinfo", user)
104 | }
105 | }
106 | }
107 |
108 | // 跨域中间件
109 | func (p *Program) middlewareCORS() gin.HandlerFunc {
110 | return func(c *gin.Context) {
111 | origin := c.Request.Header.Get("origin")
112 | method := c.Request.Method
113 |
114 | c.Header("Access-Control-Allow-Origin", origin)
115 | c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token, EtcdID")
116 | c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT")
117 | c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type")
118 | c.Header("Access-Control-Allow-Credentials", "true")
119 |
120 | //放行所有OPTIONS方法
121 | if method == "OPTIONS" {
122 | c.AbortWithStatus(http.StatusNoContent)
123 | return
124 | }
125 | // 处理请求
126 | c.Next()
127 | }
128 | }
129 |
130 | // 是否有权限
131 | func (p *Program) middlewareRoleAuth() gin.HandlerFunc {
132 | return func(c *gin.Context) {
133 | // 过滤认证模块
134 | u, _ := url.ParseRequestURI(c.Request.RequestURI)
135 | if strings.HasPrefix(u.Path, "/v1/passport") == true ||
136 | strings.HasPrefix(u.Path, "/ui") == true ||
137 | strings.HasPrefix(u.Path, "/v1/upload") == true ||
138 | strings.HasPrefix(u.Path, "/v1/server") == true {
139 | return
140 | }
141 | // 读取etcdID
142 | etcdId := c.GetHeader("EtcdID")
143 | etcdIdNum, _ := strconv.Atoi(etcdId)
144 | if etcdIdNum == 0 {
145 | c.JSON(http.StatusBadRequest, gin.H{
146 | "msg": "请选择Etcd服务",
147 | })
148 | c.Abort()
149 | return
150 | }
151 | // 查询角色权限 GET 请求为只读
152 | userinfoObj, exist := c.Get("userinfo")
153 | if exist == false {
154 | logger.Log.Warnw("用户登录信息不存在")
155 | c.AbortWithStatus(http.StatusUnauthorized)
156 | return
157 | }
158 | userinfo := userinfoObj.(*models.UsersModel)
159 | // get请求为读操作
160 | typ := 1
161 | if c.Request.Method == "GET" {
162 | typ = 0
163 | }
164 | roleEtcdServer := new(models.RoleEtcdServersModel)
165 | err := roleEtcdServer.FirstByRoleIdAndEtcdServerIdAndType(userinfo.RoleId, int32(etcdIdNum), int32(typ))
166 | if err != nil {
167 | if err == gorm.ErrRecordNotFound {
168 | c.JSON(http.StatusBadRequest, gin.H{
169 | "msg": "无权限操作此etcd,请更换etcd",
170 | })
171 |
172 | } else {
173 | c.JSON(http.StatusBadRequest, gin.H{
174 | "msg": "数据库查询错误",
175 | })
176 | }
177 | c.Abort()
178 | return
179 | }
180 | c.Next()
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/program/models/client.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "log"
7 | "time"
8 |
9 | "github.com/etcd-manage/etcd-manage-server/program/config"
10 | _ "github.com/go-sql-driver/mysql"
11 | "github.com/jinzhu/gorm"
12 | )
13 |
14 | var (
15 | client *gorm.DB
16 | )
17 |
18 | // InitClient 客户端创建
19 | func InitClient(dbCfg *config.MySQLConfig) (err error) {
20 | if dbCfg == nil {
21 | err = errors.New("Config is nil")
22 | return
23 | }
24 | // 拼接连接数据库字符串
25 | connStr := fmt.Sprintf("%s:%s@(%s:%d)/%s?charset=utf8&parseTime=True&loc=UTC",
26 | dbCfg.User,
27 | dbCfg.Passwd,
28 | dbCfg.Address,
29 | dbCfg.Port,
30 | dbCfg.DbName)
31 | // 连接数据库
32 | db, err := gorm.Open("mysql", connStr)
33 | if err != nil {
34 | return
35 | }
36 | if dbCfg.Debug == true {
37 | db.Debug()
38 | }
39 | // 禁用表名多元化
40 | db.SingularTable(true)
41 | // 连接池最大连接数
42 | db.DB().SetMaxIdleConns(dbCfg.MaxIdleConns)
43 | // 默认打开连接数
44 | db.DB().SetMaxOpenConns(dbCfg.MaxOpenConns)
45 | // 开启协程ping MySQL数据库查看连接状态
46 | go func() {
47 | for {
48 | // ping
49 | err = db.DB().Ping()
50 | if err != nil {
51 | log.Println("pingdb error ", err)
52 | }
53 | // 间隔5s ping一次
54 | time.Sleep(30 * time.Second)
55 | }
56 | }()
57 |
58 | // 全局变量
59 | client = db
60 | return
61 | }
62 |
--------------------------------------------------------------------------------
/program/models/etcdServers.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | )
7 |
8 | // EtcdServersModel etcd 服务列表
9 | type EtcdServersModel struct {
10 | ID int32 `json:"id" gorm:"column:id;primary_key"`
11 | Version string `json:"version" gorm:"column:version"`
12 | Name string `json:"name" gorm:"column:name"`
13 | Address string `json:"address" gorm:"column:address"`
14 | TlsEnable string `json:"tls_enable" gorm:"column:tls_enable"`
15 | CertFile string `json:"cert_file" gorm:"column:cert_file"`
16 | KeyFile string `json:"key_file" gorm:"column:key_file"`
17 | CaFile string `json:"ca_file" gorm:"column:ca_file"`
18 | Username string `json:"username" gorm:"column:username"`
19 | Password string `json:"password" gorm:"column:password"`
20 | Desc string `json:"desc" gorm:"column:desc"`
21 | CreatedAt JSONTime `gorm:"column:created_at" json:"created_at"` // 添加时间
22 | UpdatedAt JSONTime `gorm:"column:updated_at" json:"updated_at"` // 更新时间
23 | }
24 |
25 | // TableName 表名
26 | func (EtcdServersModel) TableName() string {
27 | return "etcd_servers"
28 | }
29 |
30 | // All 获取全部
31 | func (m *EtcdServersModel) All(name string, roleId int32) (list []*EtcdServersModel, err error) {
32 | re := new(RoleEtcdServersModel).TableName()
33 | err = client.Table(m.TableName()+" as e").Select("DISTINCT e.id, e.*").
34 | Joins("LEFT JOIN "+re+" as re on re.etcd_server_id = e.id").
35 | Where("e.name like ? and re.role_id = ?", fmt.Sprintf("%%%s%%", name), roleId).
36 | Scan(&list).Error
37 | return
38 | }
39 |
40 | // FirstById 根据id查询一个etcd服务
41 | func (m *EtcdServersModel) FirstById(id int32) (one *EtcdServersModel, err error) {
42 | one = new(EtcdServersModel)
43 | err = client.Model(m).Where("id = ?", id).First(one).Error
44 | return
45 | }
46 |
47 | // Insert 添加
48 | func (m *EtcdServersModel) Insert() (err error) {
49 | err = client.Create(m).Error
50 | return
51 | }
52 |
53 | // Update 修改
54 | func (m *EtcdServersModel) Update() (err error) {
55 | edit := make(map[string]interface{}, 0)
56 | js, _ := json.Marshal(m)
57 | json.Unmarshal(js, &edit)
58 | err = client.Model(new(EtcdServersModel)).Where("id = ?", m.ID).Updates(edit).Error
59 | return
60 | }
61 |
62 | // Del 删除
63 | func (m *EtcdServersModel) Del(id int32) (err error) {
64 | err = client.Table(m.TableName()).Where("id = ?", id).Delete(m).Error
65 | new(RoleEtcdServersModel).DelByEtcdId(id)
66 | return
67 | }
68 |
--------------------------------------------------------------------------------
/program/models/roleEtcdServers.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/jinzhu/gorm"
7 | )
8 |
9 | const (
10 | ReadOnly = iota
11 | ReadWrite
12 | )
13 |
14 | // RoleEtcdServersModel 角色权限表
15 | type RoleEtcdServersModel struct {
16 | Id int32 `gorm:"column:id;primary_key" json:"id"` //
17 | EtcdServerId int32 `gorm:"column:etcd_server_id" json:"etcd_server_id"` // etcd服务id
18 | Type int32 `gorm:"column:type" json:"type"` // 0读 1写
19 | RoleId int32 `gorm:"column:role_id" json:"role_id"` // 角色id
20 | CreatedAt JSONTime `gorm:"column:created_at" json:"created_at"` // 添加时间
21 | UpdatedAt JSONTime `gorm:"column:updated_at" json:"updated_at"` // 更新时间
22 |
23 | }
24 |
25 | // TableName 获取表名
26 | func (RoleEtcdServersModel) TableName() string {
27 | return gorm.DefaultTableNameHandler(nil, "role_etcd_servers")
28 | }
29 |
30 | // FirstByRoleIdAndEtcdServerIdAndType 根据role_id、etcd_server_id和type查询一条数据
31 | func (m *RoleEtcdServersModel) FirstByRoleIdAndEtcdServerIdAndType(roleId, etcdServerId, typ int32) (err error) {
32 | err = client.Table(m.TableName()).Where("role_id = ? and etcd_server_id = ? and type >= ?", roleId, etcdServerId, typ).First(m).Error
33 | return
34 | }
35 |
36 | // UpByEtcdId 根据etcd_id更新角色
37 | func (m *RoleEtcdServersModel) UpByEtcdId(list []*AllByEtcdIdData) (err error) {
38 | tx := client.Begin()
39 | defer func() {
40 | if err != nil {
41 | tx.Rollback()
42 | } else {
43 | tx.Commit()
44 | }
45 | }()
46 | // 先删除再插入
47 | err = tx.Table(m.TableName()).Where("etcd_server_id = ?", list[0].EtcdServerId).Delete(m).Error
48 | if err != nil {
49 | return
50 | }
51 | now := JSONTime(time.Now())
52 | for _, v := range list {
53 | typ := -1
54 | if v.Write == 1 {
55 | typ = 1
56 | } else if v.Read == 1 {
57 | typ = 0
58 | }
59 | one := &RoleEtcdServersModel{
60 | EtcdServerId: v.EtcdServerId,
61 | Type: int32(typ),
62 | RoleId: v.RoleId,
63 | CreatedAt: now,
64 | UpdatedAt: now,
65 | }
66 | err = tx.Create(one).Error
67 | if err != nil {
68 | return
69 | }
70 | }
71 | return
72 | }
73 |
74 | type AllByEtcdIdData struct {
75 | EtcdServerId int32 `gorm:"column:etcd_server_id" json:"etcd_server_id"` // etcd服务id
76 | RoleId int32 `gorm:"column:role_id" json:"role_id"` // 角色id
77 | Name string `gorm:"column:name" json:"name"`
78 | Type int32 `gorm:"column:type" json:"type"`
79 | Read int32 `gorm:"column:read" json:"read"`
80 | Write int32 `gorm:"column:write" json:"write"`
81 | }
82 |
83 | // AllByEtcdId 查询etcd服务权限配置列表
84 | func (m *RoleEtcdServersModel) AllByEtcdId(etcdId int32) (list []*AllByEtcdIdData, err error) {
85 | err = client.Table(m.TableName()+" as re").Select("r.name, re.type, re.etcd_server_id, re.role_id").
86 | Joins(" join "+new(RolesModel).TableName()+" as r on r.id = re.role_id").
87 | Where("etcd_server_id = ?", etcdId).
88 | Scan(&list).Error
89 | if err == nil {
90 | for _, v := range list {
91 | if v.Type == 1 {
92 | v.Write = 1
93 | v.Read = 1
94 | } else if v.Type == 0 {
95 | v.Write = 0
96 | v.Read = 1
97 | } else {
98 | v.Write = 0
99 | v.Read = 0
100 | }
101 | }
102 | }
103 | rList, _err := new(RolesModel).All()
104 | if _err != nil {
105 | err = _err
106 | return
107 | }
108 | if len(rList) > len(list) {
109 | for _, v := range rList {
110 | exist := false
111 | for _, v1 := range list {
112 | if v1.RoleId == v.Id {
113 | exist = true
114 | }
115 | }
116 | if exist == false {
117 | list = append(list, &AllByEtcdIdData{
118 | EtcdServerId: etcdId,
119 | RoleId: v.Id,
120 | Name: v.Name,
121 | Type: -1,
122 | Read: 0,
123 | Write: 0,
124 | })
125 | }
126 | }
127 | }
128 | return
129 | }
130 |
131 | // Save 保存角色信息
132 | func (m *RoleEtcdServersModel) Save() (err error) {
133 | err = client.Table(m.TableName()).Create(m).Error
134 | return
135 | }
136 |
137 | // DelByEtcdId 删除
138 | func (m *RoleEtcdServersModel) DelByEtcdId(etcdId int32) (err error) {
139 | err = client.Table(m.TableName()).Where("etcd_server_id = ?", etcdId).Delete(m).Error
140 | return
141 | }
142 |
--------------------------------------------------------------------------------
/program/models/roles.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "github.com/jinzhu/gorm"
5 | )
6 |
7 | // RolesModel 角色表
8 | type RolesModel struct {
9 | Id int32 `gorm:"column:id;primary_key" json:"id"` //
10 | Name string `gorm:"column:name" json:"name"` // 角色名
11 | CreatedAt JSONTime `gorm:"column:created_at" json:"created_at"` // 添加时间
12 | UpdatedAt JSONTime `gorm:"column:updated_at" json:"updated_at"` // 更新时间
13 |
14 | }
15 |
16 | // TableName 获取表名
17 | func (RolesModel) TableName() string {
18 | return gorm.DefaultTableNameHandler(nil, "roles")
19 | }
20 |
21 | // All 查询全部角色
22 | func (m *RolesModel) All() (list []*RolesModel, err error) {
23 | err = client.Table(m.TableName()).Scan(&list).Error
24 | return
25 | }
26 |
27 | // Save 保存
28 | func (m *RolesModel) Save() (err error) {
29 | err = client.Table(m.TableName()).Save(m).Error
30 | return
31 | }
32 |
33 | // Del 删除
34 | func (m *RolesModel) Del(id int32) (err error) {
35 | err = client.Table(m.TableName()).Where("id = ?", id).Delete(m).Error
36 | return
37 | }
38 |
--------------------------------------------------------------------------------
/program/models/time.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "database/sql/driver"
5 | "fmt"
6 | "time"
7 | )
8 |
9 | /* 自定义时间格式 */
10 | var timeFormart = "2006-01-02 15:04:05" // time.RFC3339
11 |
12 | // JSONTime 时间格式别名
13 | type JSONTime time.Time
14 |
15 | // UnmarshalJSON 字节转为JSONTime对象
16 | func (t *JSONTime) UnmarshalJSON(data []byte) (err error) {
17 | now, err := time.ParseInLocation(`"`+timeFormart+`"`, string(data), time.UTC)
18 | *t = JSONTime(now)
19 | return
20 | }
21 |
22 | // MarshalJSON 将时间对象转为字节
23 | func (t JSONTime) MarshalJSON() ([]byte, error) {
24 | b := make([]byte, 0, len(timeFormart)+2)
25 | b = append(b, '"')
26 | b = time.Time(t).Local().AppendFormat(b, timeFormart)
27 | b = append(b, '"')
28 | return b, nil
29 | }
30 |
31 | // String 格式化为文本
32 | func (t JSONTime) String() string {
33 | return time.Time(t).Format(timeFormart)
34 | }
35 |
36 | // Format 格式化函数
37 | func (t JSONTime) Format(format string) string {
38 | return time.Time(t).Format(timeFormart)
39 | }
40 |
41 | // Value insert timestamp into mysql need this function.
42 | func (t JSONTime) Value() (driver.Value, error) {
43 | var zeroTime time.Time
44 | var ti = time.Time(t)
45 | if ti.UnixNano() == zeroTime.UnixNano() {
46 | return nil, nil
47 | }
48 | return ti, nil
49 | }
50 |
51 | // Scan valueof time.Time
52 | func (t *JSONTime) Scan(v interface{}) error {
53 | value, ok := v.(time.Time)
54 | if ok {
55 | *t = JSONTime(value)
56 | return nil
57 | }
58 | return fmt.Errorf("can not convert %v to timestamp", v)
59 | }
60 |
--------------------------------------------------------------------------------
/program/models/users.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/jinzhu/gorm"
7 | )
8 |
9 | // UsersModel 用户表
10 | type UsersModel struct {
11 | Id int32 `gorm:"column:id;primary_key" json:"id"` //
12 | Username string `gorm:"column:username" json:"username"` // 用户名
13 | Password string `gorm:"column:password" json:"password"` // 密码
14 | Email string `gorm:"column:email" json:"email"` // 邮箱
15 | RoleId int32 `gorm:"column:role_id" json:"role_id"` // 角色id
16 | CreatedAt JSONTime `gorm:"column:created_at" json:"created_at"` // 添加时间
17 | UpdatedAt JSONTime `gorm:"column:updated_at" json:"updated_at"` // 更新时间
18 | Token string `gorm:"-" json:"token"` // 登录token
19 | }
20 |
21 | // TableName 获取表名
22 | func (UsersModel) TableName() string {
23 | return gorm.DefaultTableNameHandler(nil, "users")
24 | }
25 |
26 | // UsersJoinRoleModel 用户列表带角色名
27 | type UsersJoinRoleModel struct {
28 | RoleName string `gorm:"column:role_name" json:"role_name"`
29 | UsersModel
30 | }
31 |
32 | // FirstByUsernameAndPassword 根据用户名密码查询数据
33 | func (m *UsersModel) FirstByUsernameAndPassword(username, password string) (err error) {
34 | err = client.Table(m.TableName()).Where("username = ? and password = ?", username, password).First(m).Error
35 | return
36 | }
37 |
38 | // List 分页列表
39 | func (m *UsersModel) List(userId, roleId int32, name string, offset, limit int) (list []*UsersJoinRoleModel, err error) {
40 | where, params := m.listWhere(userId, roleId, name, offset, limit)
41 | err = client.Table(m.TableName()+" as u").Select("u.*, r.name as role_name").
42 | Joins(fmt.Sprintf("left join %s as r on u.role_id = r.id", new(RolesModel).TableName())).
43 | Where(where, params...).
44 | Offset(offset).
45 | Limit(limit).
46 | Scan(&list).Error
47 | return
48 | }
49 |
50 | // ListCount 分页总数据量
51 | func (m *UsersModel) ListCount(userId, roleId int32, name string, offset, limit int) (_c int32, err error) {
52 | where, params := m.listWhere(userId, roleId, name, offset, limit)
53 | err = client.Table(m.TableName()+" as u").Where(where, params...).Count(&_c).Error
54 | return
55 | }
56 |
57 | // 分页查询条件组织
58 | func (m *UsersModel) listWhere(userId, roleId int32, name string, offset, limit int) (where string, params []interface{}) {
59 | where = ""
60 | params = make([]interface{}, 0)
61 | if userId > 0 {
62 | where = "u.id = ? "
63 | params = append(params, userId)
64 | }
65 | if roleId > 0 {
66 | if where != "" {
67 | where += " and "
68 | }
69 | where += " u.role_id = ? "
70 | params = append(params, roleId)
71 | }
72 | if name != "" {
73 | if where != "" {
74 | where += " and "
75 | }
76 | where += " u.username like ?"
77 | params = append(params, "%"+name+"%")
78 | }
79 | return
80 | }
81 |
82 | // Save 保存
83 | func (m *UsersModel) Save(omit ...string) (err error) {
84 | err = client.Table(m.TableName()).Omit(omit...).Save(m).Error
85 | return
86 | }
87 |
88 | // Del 删除
89 | func (m *UsersModel) Del(id int32) (err error) {
90 | err = client.Table(m.TableName()).Where("id = ?", id).Delete(m).Error
91 | return
92 | }
93 |
--------------------------------------------------------------------------------
/program/program.go:
--------------------------------------------------------------------------------
1 | package program
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | "os/exec"
7 | "runtime"
8 | "time"
9 |
10 | "github.com/etcd-manage/etcd-manage-server/program/api"
11 | v1 "github.com/etcd-manage/etcd-manage-server/program/api/v1"
12 | "github.com/etcd-manage/etcd-manage-server/program/config"
13 | "github.com/etcd-manage/etcd-manage-server/program/logger"
14 | "github.com/etcd-manage/etcd-manage-server/program/models"
15 | )
16 |
17 | // Program 主程序
18 | type Program struct {
19 | cfg *config.Config // 配置
20 | s *http.Server // http服务-用于程序结束stop
21 | vApis map[string]api.API // 多版本api启动运行
22 | }
23 |
24 | // New 创建主程序
25 | func New() (*Program, error) {
26 | // 配置文件
27 | cfg, err := config.LoadConfig("")
28 | if err != nil {
29 | fmt.Println(err)
30 | return nil, err
31 | }
32 |
33 | // 日志对象
34 | _, err = logger.InitLogger(cfg.LogPath, cfg.Debug)
35 | if err != nil {
36 | return nil, err
37 | }
38 |
39 | // 连接数据库
40 | err = models.InitClient(cfg.DB)
41 | if err != nil {
42 | return nil, err
43 | }
44 |
45 | // 注册api对象
46 | apis := make(map[string]api.API, 0)
47 | apis["v1"] = new(v1.APIV1)
48 |
49 | // js, _ := json.Marshal(cfg)
50 | // fmt.Println(string(js))
51 |
52 | return &Program{
53 | cfg: cfg,
54 | vApis: apis,
55 | }, nil
56 | }
57 |
58 | // Run 启动程序
59 | func (p *Program) Run() error {
60 | // 启动http服务
61 | go p.startAPI()
62 |
63 | // 打开浏览器
64 | go func() {
65 | time.Sleep(100 * time.Millisecond)
66 | openUrl(fmt.Sprintf("http://127.0.0.1:%d/ui/", p.cfg.HTTP.Port))
67 | }()
68 |
69 | return nil
70 | }
71 |
72 | // Stop 停止服务
73 | func (p *Program) Stop() {
74 | if p.s != nil {
75 | p.s.Close()
76 | }
77 | }
78 |
79 | func openUrl(uri string) error {
80 | var (
81 | commands = map[string]string{
82 | "windows": "start",
83 | "darwin": "open",
84 | "linux": "xdg-open",
85 | }
86 | )
87 | run, ok := commands[runtime.GOOS] //获取平台信息
88 |
89 | if !ok {
90 | return fmt.Errorf("don't know how to open things on %s platform", runtime.GOOS)
91 | }
92 | cmd := exec.Command(run, uri)
93 | return cmd.Start()
94 | }
95 |
--------------------------------------------------------------------------------
/program/publicFunc/publicFunc.go:
--------------------------------------------------------------------------------
1 | package publicFunc
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/etcd-manage/etcd-manage-server/program/common"
7 |
8 | "github.com/etcd-manage/etcd-manage-server/etcdsdk"
9 | "github.com/etcd-manage/etcd-manage-server/etcdsdk/model"
10 | "github.com/etcd-manage/etcd-manage-server/program/logger"
11 | "github.com/etcd-manage/etcd-manage-server/program/models"
12 | )
13 |
14 | type PublicF struct {
15 | }
16 |
17 | func (p PublicF) GetEtcdClient(etcdId int) (client etcdsdk.EtcdV3, err error) {
18 | // 查询etcd服务信息
19 | etcdOne := new(models.EtcdServersModel)
20 | etcdOne, err = etcdOne.FirstById(int32(etcdId))
21 | if err != nil {
22 | logger.Log.Errorw("获取etcd服务信息错误", "EtcdID", etcdId, "err", err)
23 | }
24 | if etcdOne.TlsEnable == "true" {
25 | if !strings.HasPrefix(etcdOne.Address, "https") {
26 | etcdOne.Address = strings.ReplaceAll(etcdOne.Address, "http", "https")
27 | }
28 | } else {
29 | if strings.HasPrefix(etcdOne.Address, "https") {
30 | etcdOne.Address = strings.ReplaceAll(etcdOne.Address, "https", "http")
31 | }
32 | }
33 | // 连接etcd
34 | cfg := model.Config{
35 | EtcdId: int32(etcdId),
36 | Version: etcdOne.Version,
37 | Address: strings.Split(etcdOne.Address, ","),
38 | TlsEnable: etcdOne.TlsEnable,
39 | CertFile: common.Base64Decode(etcdOne.CertFile),
40 | KeyFile: common.Base64Decode(etcdOne.KeyFile),
41 | CaFile: common.Base64Decode(etcdOne.CaFile),
42 | }
43 | if client, err = etcdsdk.NewClient(cfg); err != nil {
44 | logger.Log.Errorf("初始化etcd client 失败:%s", err.Error())
45 | }
46 | return
47 | }
48 |
--------------------------------------------------------------------------------
/tupian/etcd key树以及value展示图.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/comqx/k8s-etcd-ui/e860824e994da8c48aea5cd3e9283f374828a8c9/tupian/etcd key树以及value展示图.png
--------------------------------------------------------------------------------
/tupian/etcd-key树图.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/comqx/k8s-etcd-ui/e860824e994da8c48aea5cd3e9283f374828a8c9/tupian/etcd-key树图.png
--------------------------------------------------------------------------------
/tupian/etcd管理页面.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/comqx/k8s-etcd-ui/e860824e994da8c48aea5cd3e9283f374828a8c9/tupian/etcd管理页面.png
--------------------------------------------------------------------------------
/tupian/多个etcd集群健康图.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/comqx/k8s-etcd-ui/e860824e994da8c48aea5cd3e9283f374828a8c9/tupian/多个etcd集群健康图.png
--------------------------------------------------------------------------------
/tupian/添加etcd界面.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/comqx/k8s-etcd-ui/e860824e994da8c48aea5cd3e9283f374828a8c9/tupian/添加etcd界面.png
--------------------------------------------------------------------------------