├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── favicon.ico ├── go.mod ├── go.sum ├── main.go ├── redis-manager-ui ├── .env.development ├── .env.production ├── .gitignore ├── .vscode │ └── extensions.json ├── auto-imports.d.ts ├── components.d.ts ├── declaration.d.ts ├── index.html ├── package.json ├── pnpm-lock.yaml ├── public │ ├── favicon.svg │ └── vite.svg ├── src │ ├── App.vue │ ├── api │ │ ├── board.ts │ │ ├── cli.ts │ │ ├── cloud.ts │ │ ├── cluster.ts │ │ ├── codis.ts │ │ ├── history.ts │ │ ├── rule.ts │ │ ├── setting.ts │ │ └── user.ts │ ├── assets │ │ ├── img │ │ │ ├── favicon.ico │ │ │ ├── github-mark.png │ │ │ ├── logo.png │ │ │ └── sun.jpeg │ │ └── vue.svg │ ├── components │ │ ├── breadcrumb │ │ │ └── Breadcrumb.vue │ │ └── cli │ │ │ └── Cli.vue │ ├── main.ts │ ├── pages │ │ ├── command │ │ │ └── Index.vue │ │ ├── dashboard │ │ │ └── Index.vue │ │ ├── history │ │ │ └── Index.vue │ │ ├── home │ │ │ └── Index.vue │ │ ├── login │ │ │ └── Index.vue │ │ ├── redis │ │ │ ├── aliredis │ │ │ │ └── Index.vue │ │ │ ├── cluster │ │ │ │ └── Index.vue │ │ │ ├── codis │ │ │ │ └── Index.vue │ │ │ └── txredis │ │ │ │ └── Index.vue │ │ ├── setting │ │ │ ├── Index.vue │ │ │ └── Rule.vue │ │ └── user │ │ │ └── Index.vue │ ├── router │ │ └── index.ts │ ├── style.css │ ├── utils │ │ └── request │ │ │ ├── http.ts │ │ │ └── index.ts │ └── vite-env.d.ts ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts ├── src ├── cfg │ └── config.go ├── hsc │ ├── code.go │ └── msg.go ├── middleware │ ├── alicloud │ │ ├── alist.go │ │ └── connect.go │ ├── casbin │ │ ├── casbinstruct.go │ │ ├── initial.go │ │ └── ruleop.go │ ├── cluster │ │ ├── cluster.go │ │ └── nodes.go │ ├── codisapi │ │ ├── opapi.go │ │ └── opstruct.go │ ├── cosop │ │ └── cosop.go │ ├── httpapi │ │ ├── apistruct.go │ │ └── httpapi.go │ ├── jwt │ │ └── jwt.go │ ├── logger │ │ └── logger.go │ ├── model │ │ ├── aliresult.go │ │ ├── cfg.go │ │ ├── clusterresult.go │ │ ├── codisv1.go │ │ ├── path.go │ │ ├── txresult.go │ │ └── user.go │ ├── mysql │ │ ├── history.go │ │ ├── initial.go │ │ ├── mysqlstrcut.go │ │ ├── opcfg.go │ │ ├── opcloud.go │ │ ├── opcluster.go │ │ ├── opcodis.go │ │ └── opuser.go │ ├── opredis │ │ ├── all.go │ │ ├── analysisrdb.go │ │ ├── base.go │ │ ├── basecluster.go │ │ ├── big.go │ │ ├── click.go │ │ ├── connect.go │ │ ├── del.go │ │ ├── hot.go │ │ ├── lock.go │ │ ├── opcodis.go │ │ ├── opdilatation.go │ │ ├── opshrinkage.go │ │ ├── query.go │ │ ├── redisstruct.go │ │ ├── slow.go │ │ └── telnet.go │ ├── rcron │ │ ├── cloudrefresh.go │ │ └── clusterrefresh.go │ ├── tools │ │ ├── calculation.go │ │ ├── capacity.go │ │ ├── check.go │ │ ├── codisauth.go │ │ └── trans.go │ ├── txcloud │ │ ├── clist.go │ │ ├── connect.go │ │ └── opkey.go │ ├── useride │ │ ├── daoyou.go │ │ ├── password.go │ │ └── tocken.go │ └── util │ │ ├── cloud.go │ │ ├── converge.go │ │ ├── jwt.go │ │ ├── pagination.go │ │ ├── parameter.go │ │ └── utilstruct.go └── rhttp │ ├── router.go │ └── v1 │ ├── base.go │ ├── board.go │ ├── cfg.go │ ├── cli.go │ ├── cloud.go │ ├── cluster.go │ ├── codis.go │ ├── home.go │ ├── ophistory.go │ ├── rule.go │ ├── user.go │ └── v1struct.go ├── start-docker.sh ├── website ├── assets │ ├── Index-035ac3eb.css │ ├── Index-06dbdf44.js │ ├── Index-398d56ab.js │ ├── Index-3bca26e6.js │ ├── Index-425e1db0.js │ ├── Index-64bee138.js │ ├── Index-7aa9bc97.css │ ├── Index-8878f285.js │ ├── Index-88df60b4.js │ ├── Index-967c1091.js │ ├── Index-9ea35986.css │ ├── Index-a2a246be.js │ ├── Index-b3c9f82a.css │ ├── Index-b6741412.css │ ├── Index-bba6e235.css │ ├── Index-bfef1db5.js │ ├── Index-c6cadafd.js │ ├── Index-c74390c1.css │ ├── Index-d450f424.css │ ├── Index-d4718653.css │ ├── Index-dc641d83.css │ ├── Index-f5a41eb2.css │ ├── Rule-273f6791.css │ ├── Rule-d9da8097.js │ ├── cloud-3a6d1fb9.js │ ├── cluster-7426750c.js │ ├── codis-7a3bd4ff.js │ ├── github-mark-367d5cb2.png │ ├── index-04cccabd.js │ ├── index-056ef9e9.js │ ├── index-472d715e.css │ ├── moment-fbc5633a.js │ ├── setting-b5212552.js │ ├── sun-80b81115.jpeg │ └── user-d7605884.js ├── favicon.svg ├── index.html └── vite.svg └── yaml ├── dev.yaml └── online.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | .DS_Store 15 | # Dependency directories (remove the comment below to include it) 16 | # vendor/ 17 | 18 | # server file 19 | logs 20 | yaml/config.yaml 21 | redis-manager 22 | 23 | # See http://help.github.com/ignore-files/ for more about ignoring files. 24 | 25 | # redis-manager-ui 26 | # Logs 27 | redis-manager-ui/logs 28 | redis-manager-ui/*.log 29 | redis-manager-ui/npm-debug.log* 30 | redis-manager-ui/yarn-debug.log* 31 | redis-manager-ui/yarn-error.log* 32 | redis-manager-ui/pnpm-debug.log* 33 | redis-manager-ui/lerna-debug.log* 34 | 35 | redis-manager-ui/node_modules 36 | redis-manager-ui/dist 37 | redis-manager-ui/dist-ssr 38 | redis-manager-ui/*.local 39 | 40 | # Editor directories and files 41 | redis-manager-ui/.vscode/* 42 | redis-manager-ui/!.vscode/extensions.json 43 | redis-manager-ui/.idea 44 | redis-manager-ui/.DS_Store 45 | redis-manager-ui/*.suo 46 | redis-manager-ui/*.ntvs* 47 | redis-manager-ui/*.njsproj 48 | redis-manager-ui/*.sln 49 | redis-manager-ui/*.sw? 50 | 51 | # Compiled output 52 | redis-manager-ui/dist/ 53 | redis-manager-ui/tmp 54 | redis-manager-ui/out-tsc 55 | redis-manager-ui/bazel-out 56 | 57 | # Node 58 | redis-manager-ui/node_modules/ 59 | redis-manager-ui/npm-debug.log 60 | redis-manager-ui/yarn-error.log 61 | 62 | # IDEs and editors 63 | redis-manager-ui/.idea/ 64 | redis-manager-ui/.project 65 | redis-manager-ui/.classpath 66 | redis-manager-ui/.c9/ 67 | redis-manager-ui/*.launch 68 | redis-manager-ui/.settings/ 69 | redis-manager-ui/*.sublime-workspace 70 | 71 | # Visual Studio Code 72 | redis-manager-ui/.vscode/* 73 | redis-manager-ui/!.vscode/settings.json 74 | redis-manager-ui/!.vscode/tasks.json 75 | redis-manager-ui/!.vscode/launch.json 76 | redis-manager-ui/!.vscode/extensions.json 77 | redis-manager-ui/.history/* 78 | 79 | # Miscellaneous 80 | redis-manager-ui/.angular/cache 81 | redis-manager-ui/.sass-cache/ 82 | redis-manager-ui/connect.lock 83 | redis-manager-ui/coverage 84 | redis-manager-ui/libpeerconnection.log 85 | redis-manager-ui/testem.log 86 | redis-manager-ui/typings 87 | 88 | # System files 89 | redis-manager-ui/.DS_Store 90 | redis-manager-ui/Thumbs.db 91 | 92 | redis-manager-ui/npm-debug.log* 93 | redis-manager-ui/yarn-debug.log* 94 | redis-manager-ui/yarn-error.log* 95 | redis-manager-ui/**/*.log 96 | redis-manager-ui/test/unit/coverage/ 97 | redis-manager-ui/test/e2e/reports/ 98 | redis-manager-ui/selenium-debug.log 99 | redis-manager-ui/tests/**/coverage/ 100 | redis-manager-ui/tests/e2e/reports 101 | # Editor directories and files 102 | redis-manager-ui/*.suo 103 | redis-manager-ui/*.ntvs* 104 | redis-manager-ui/*.njsproj 105 | redis-manager-ui/*.sln 106 | redis-manager-ui/*.local 107 | redis-manager-ui/package-lock.json 108 | redis-manager-ui/yarn.lock 109 | 110 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM centos:7 2 | 3 | ENV LANG en_US.UTF-8 4 | ENV TZ Asia/Shanghai 5 | 6 | WORKDIR /data 7 | 8 | COPY ./redis-manager . 9 | COPY ./website ./website 10 | COPY ./yaml ./yaml 11 | CMD ["/data/redis-manager"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021-present, 元匠信息科技(上海)有限公司 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 13 | all 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 21 | THE SOFTWARE 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # redis-manager 2 | 这是一个可以操作[redis cluster/codis/腾讯云redis/阿里云redis]的web管理平台。 3 | _________________ 4 | 5 | ## 功能简介 6 | 1. **Cluster操作界面:** 支持Redis Cluster集群的添加,可以查看Redis Cluster的集群状态 7 | 2. **Codis操作界面:** 支持嵌入Codis Dashboard平台,可以查看codis平台信息,并且支持codis的扩缩容操作 8 | 3. **腾讯云Redis操作界面:** 支持腾讯云Redis的导入,可以查看腾讯Redis的基本信息 9 | 4. **阿里云Redis操作界面:** 支持阿里云Redis的导入,可以查看阿里Redis的基本信息(开发中...) 10 | 5. **数据查询界面:** 支持[string/list/hash/set/zset]类型的key的查询,以及查询[大key/热key/慢key/查询1万key]等功能,[阿里云redis暂时不支持] 11 | 6. **用户界面:** 支持用户的添加删除,可以管理平台用户 12 | 7. **系统设置界面:** 支持设置全局配置以及用户权限配置,可以管理平台系统配置 13 | 8. **历史记录界面:** 支持记载变更操作记录,方便审核回溯 14 | 15 | 16 | ## 项目启动 17 | ### 依赖语言 18 | - Golang1.19 + Vue3 19 | ### 环境依赖 20 | - mysql数据库 8.0版本以上 21 | - 云redis或者codis,或者单Redis,非cluster,2.8版本以上 22 | ### 启动 23 | - mysql需要创建 `redis_manager` 数据库:`create database redis_manager;` 24 | - 复制 yaml/dev.yaml 到 yaml/config.yaml 25 | - 编辑 yaml/config.yaml文件的[mysql、redis]配置 26 | - 命令行:`go run main.go` 27 | - docker模式启动: `sh start-docker.sh` 28 | - 浏览器访问:`http://127.0.0.1:8000` 29 | 30 | ## 项目依赖 31 | ### 大key查询依赖 32 | - 如果需要在自建Cluster和Codis上查询大key,则需要在每个redis机器上安装Agent,检测bgsave的dump文件,上传到cos里,才能进行使用;项目地址:[redis-agent](https://github.com/iguidao/redis-agent) 33 | 34 | 35 | ## 主要功能界面介绍 36 | 账号:iguidao
37 | 密码:123456 38 | ### 登陆 39 | > 账号密码登陆 40 | 41 | 42 | ### 概览 43 | > 展示集群基本信息 44 | 45 | 46 | ### Cluster集群页面 47 | > 展示Redis Cluster集群的信息 48 | 49 | 50 | ### Codis集群页面 51 | > 展示Codis集群信息 52 | 53 | 54 | ### 腾讯Redis集群界面 55 | > 展示腾讯Redis集群信息 56 | > 57 | 58 | ### 阿里Redis集群界面 59 | > 展示阿里Redis集群信息[未测试] 60 | 61 | 62 | ### 数据查询界面 63 | > 进行redis的key操作,支持[查key/大key/热key/慢key/查询1万key/删key]功能 64 | 65 | 66 | ### 用户管理界面 67 | > 进行用户的创建删除,身份更改功能 68 | 69 | 70 | ### 系统设置界面 71 | > 进行全局配置 72 | 73 | > 进行权限配置 74 | 75 | 76 | ### 历史记录界面 77 | > 所有变更操作都会记录 78 | 79 | 80 | ## 联系方式 81 | mail: xiaohui920@sina.cn 82 | 83 | 84 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iguidao/redis-manager/43400bf548ffb68767ce7067679c6128fc2e155b/favicon.ico -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/iguidao/redis-manager/src/cfg" 5 | "github.com/iguidao/redis-manager/src/middleware/casbin" 6 | "github.com/iguidao/redis-manager/src/middleware/logger" 7 | "github.com/iguidao/redis-manager/src/middleware/model" 8 | "github.com/iguidao/redis-manager/src/middleware/mysql" 9 | "github.com/iguidao/redis-manager/src/middleware/rcron" 10 | "github.com/iguidao/redis-manager/src/rhttp" 11 | "github.com/robfig/cron" 12 | ) 13 | 14 | func init() { 15 | if err := cfg.Init(""); err != nil { 16 | panic(err) 17 | } 18 | logger.SetupLogger() 19 | mysql.Connect(cfg.Get_Info_String("MYSQL")) 20 | mysql.Migrate() 21 | casbin.Connect() 22 | } 23 | 24 | func main() { 25 | c := cron.New() 26 | var calendarcrontime string 27 | calendarcrontime = mysql.DB.GetOneCfgValue(model.CLOUDREFRESH) 28 | if calendarcrontime == "" { 29 | calendarcrontime = "@every 10m" 30 | } 31 | c.AddFunc(calendarcrontime, func() { 32 | rcron.CloudRefresh() 33 | }) 34 | c.Start() 35 | listen := cfg.Get_Info_String("addr") 36 | if listen == "" { 37 | listen = ":8000" 38 | } 39 | rhttp.NewServer().Run(listen) 40 | } 41 | -------------------------------------------------------------------------------- /redis-manager-ui/.env.development: -------------------------------------------------------------------------------- 1 | VITE_MODE_NAME=development 2 | VITE_BASE_API='/api' 3 | VITE_APP_TITLE=Redis-Manager -------------------------------------------------------------------------------- /redis-manager-ui/.env.production: -------------------------------------------------------------------------------- 1 | VITE_MODE_NAME=production 2 | VITE_BASE_API='/redis-manager' 3 | VITE_APP_TITLE=Redis-Manager -------------------------------------------------------------------------------- /redis-manager-ui/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /redis-manager-ui/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"] 3 | } 4 | -------------------------------------------------------------------------------- /redis-manager-ui/auto-imports.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* prettier-ignore */ 3 | // @ts-nocheck 4 | // Generated by unplugin-auto-import 5 | export {} 6 | declare global { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /redis-manager-ui/components.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* prettier-ignore */ 3 | // @ts-nocheck 4 | // Generated by unplugin-vue-components 5 | // Read more: https://github.com/vuejs/core/pull/3399 6 | import '@vue/runtime-core' 7 | 8 | export {} 9 | 10 | declare module '@vue/runtime-core' { 11 | export interface GlobalComponents { 12 | Breadcrumb: typeof import('./src/components/breadcrumb/Breadcrumb.vue')['default'] 13 | ElAside: typeof import('element-plus/es')['ElAside'] 14 | ElAvatar: typeof import('element-plus/es')['ElAvatar'] 15 | ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb'] 16 | ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem'] 17 | ElButton: typeof import('element-plus/es')['ElButton'] 18 | ElCard: typeof import('element-plus/es')['ElCard'] 19 | ElCol: typeof import('element-plus/es')['ElCol'] 20 | ElContainer: typeof import('element-plus/es')['ElContainer'] 21 | ElDropdown: typeof import('element-plus/es')['ElDropdown'] 22 | ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem'] 23 | ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu'] 24 | ElForm: typeof import('element-plus/es')['ElForm'] 25 | ElFormItem: typeof import('element-plus/es')['ElFormItem'] 26 | ElHeader: typeof import('element-plus/es')['ElHeader'] 27 | ElIcon: typeof import('element-plus/es')['ElIcon'] 28 | ElImage: typeof import('element-plus/es')['ElImage'] 29 | ElInput: typeof import('element-plus/es')['ElInput'] 30 | ElMain: typeof import('element-plus/es')['ElMain'] 31 | ElMenu: typeof import('element-plus/es')['ElMenu'] 32 | ElMenuItem: typeof import('element-plus/es')['ElMenuItem'] 33 | ElRow: typeof import('element-plus/es')['ElRow'] 34 | ElSubMenu: typeof import('element-plus/es')['ElSubMenu'] 35 | Footer: typeof import('./src/components/footer/Footer.vue')['default'] 36 | RouterLink: typeof import('vue-router')['RouterLink'] 37 | RouterView: typeof import('vue-router')['RouterView'] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /redis-manager-ui/declaration.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'vue-json-viewer' { 2 | const vis: any; 3 | export default vis; 4 | } -------------------------------------------------------------------------------- /redis-manager-ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Redis Manager 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /redis-manager-ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redis-manager-ui", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vue-tsc && vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "@element-plus/icons-vue": "^2.1.0", 13 | "axios": "^1.3.5", 14 | "clipboard": "^2.0.11", 15 | "element-plus": "^2.3.3", 16 | "moment": "^2.29.4", 17 | "pnpm": "^8.3.0", 18 | "vue": "^3.2.47", 19 | "vue-json-viewer": "^3.0.4", 20 | "vue-router": "^4.1.6" 21 | }, 22 | "devDependencies": { 23 | "@vitejs/plugin-vue": "^4.1.0", 24 | "typescript": "^4.9.3", 25 | "unplugin-auto-import": "^0.15.2", 26 | "unplugin-vue-components": "^0.24.1", 27 | "vite": "^4.2.0", 28 | "vue-tsc": "^1.2.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /redis-manager-ui/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | 10 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /redis-manager-ui/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /redis-manager-ui/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 8 | 10 | -------------------------------------------------------------------------------- /redis-manager-ui/src/api/board.ts: -------------------------------------------------------------------------------- 1 | import RequestHttp from '../utils/request' 2 | 3 | namespace Rboard { 4 | 5 | // 登录成功后返回的token 6 | export interface ResData { 7 | data: { 8 | aliredis: number; 9 | codis: number; 10 | txredis: number; 11 | cluster: number; 12 | } 13 | errorCode: number; 14 | msg: string; 15 | } 16 | } 17 | // 用户登录 18 | export const BoardDesc = () => { 19 | // 返回的数据格式可以和服务端约定 20 | return RequestHttp.get('/board/v1/desc'); 21 | } 22 | -------------------------------------------------------------------------------- /redis-manager-ui/src/api/cli.ts: -------------------------------------------------------------------------------- 1 | import RequestHttp from '../utils/request' 2 | 3 | namespace Rcli { 4 | // 用户登录表单 5 | export interface CliReqForm { 6 | cache_type: string; 7 | cache_op: string; 8 | cluster_name: string; 9 | key_name: string; 10 | codis_url: string; 11 | group_name: string; 12 | 13 | } 14 | // 登录成功后返回的token 15 | export interface CliResData { 16 | data: {} 17 | errorCode: number; 18 | msg: string; 19 | } 20 | } 21 | // 用户登录 22 | export const cliRedisOpkey = (params: Rcli.CliReqForm) => { 23 | // 返回的数据格式可以和服务端约定 24 | return RequestHttp.post('/cli/v1/opkey', params); 25 | } 26 | -------------------------------------------------------------------------------- /redis-manager-ui/src/api/cloud.ts: -------------------------------------------------------------------------------- 1 | import RequestHttp from '../utils/request' 2 | 3 | namespace RCloud { 4 | export interface RegionReqForm { 5 | cloud: string; 6 | } 7 | export interface RegionResData { 8 | data: { 9 | region_list: [{ 10 | Region: string; 11 | RegionName: string; 12 | RegionState: string; 13 | RegionId: string; 14 | RegionEndpoint: string; 15 | LocalName: string; 16 | }] 17 | } 18 | errorCode: number; 19 | msg: string; 20 | } 21 | export interface ListReqForm { 22 | cloud: string; 23 | region: string; 24 | } 25 | export interface ListResData { 26 | data: { 27 | redis_list: [ 28 | { 29 | Cloud: string; 30 | InstanceName: string; 31 | InstanceId: string; 32 | PrivateIp: string; 33 | Port: string; 34 | Region: string; 35 | Createtime: string; 36 | Size: number; 37 | InstanceStatus: string; 38 | RedisShardSize: number; 39 | RedisShardNum: number; 40 | RedisReplicasNum: number; 41 | NoAuth: Boolean; 42 | PublicIp: string; 43 | } 44 | ] 45 | } 46 | errorCode: number; 47 | msg: string; 48 | } 49 | export interface PasswordReqForm { 50 | cloud: string; 51 | instanceid: string; 52 | password: string; 53 | } 54 | export interface PasswordResData { 55 | data: {} 56 | errorCode: number; 57 | msg: string; 58 | } 59 | } 60 | export const listCloudRegion = (params: RCloud.RegionReqForm) => { 61 | // 返回的数据格式可以和服务端约定 62 | return RequestHttp.get('/cloud/v1/region', {params: params}); 63 | } 64 | 65 | export const listCloudRedis = (params: RCloud.ListReqForm) => { 66 | // 返回的数据格式可以和服务端约定 67 | return RequestHttp.get('/cloud/v1/list', {params: params}); 68 | } 69 | 70 | 71 | export const changeCloudRedisPw = (params: RCloud.PasswordReqForm) => { 72 | // 返回的数据格式可以和服务端约定 73 | return RequestHttp.post('/cloud/v1/password', params); 74 | } 75 | -------------------------------------------------------------------------------- /redis-manager-ui/src/api/cluster.ts: -------------------------------------------------------------------------------- 1 | import RequestHttp from '../utils/request' 2 | 3 | // 新增codis平台 4 | namespace RCluster { 5 | export interface updateReqForm { 6 | name: string; 7 | nodes: string; 8 | password: string; 9 | } 10 | export interface updateResData { 11 | data: {} 12 | errorCode: number; 13 | msg: string; 14 | } 15 | export interface listResData { 16 | data: [{ 17 | ID: number; 18 | CreatedAt: string; 19 | UpdatedAt: string; 20 | DeletedAt: string; 21 | Name: string; 22 | Nodes: string; 23 | Password: string; 24 | }] 25 | errorCode: number; 26 | msg: string; 27 | } 28 | export interface nodeReqForm { 29 | cluster_id: string; 30 | } 31 | export interface Node { 32 | CreateTime: string; 33 | CluserId: string; 34 | NodeId: string; 35 | Address: string; 36 | Flags: string; 37 | LinkState: string; 38 | RunStatus: string; 39 | SlotRange: string; 40 | SlotNumber: string; 41 | Children: Node[]; 42 | } 43 | // 拿到配置数据 44 | export interface nodeResData { 45 | data: [{ 46 | CreateTime: string; 47 | CluserId: string; 48 | NodeId: string; 49 | Address: string; 50 | Flags: string; 51 | LinkState: string; 52 | RunStatus: string; 53 | SlotRange: string; 54 | SlotNumber: string; 55 | Children: Node[]; 56 | }] 57 | errorCode: number; 58 | msg: string; 59 | } 60 | export interface masterReqForm { 61 | cluster_id: string; 62 | } 63 | export interface masterResData { 64 | data: [{ 65 | CreatedAt: string; 66 | CluserId: string; 67 | NodeId: string; 68 | Ip: string; 69 | Port: string; 70 | SlotRange: string; 71 | }] 72 | errorCode: number; 73 | msg: string; 74 | } 75 | } 76 | // 删除 77 | export const delClusterCfg = (params: RCluster.updateReqForm) => { 78 | return RequestHttp.delete('/cluster/v1/del', {params: params}); 79 | } 80 | 81 | //新增 82 | export const addClusterCfg = (params: RCluster.updateReqForm) => { 83 | return RequestHttp.post('/cluster/v1/add', params); 84 | } 85 | 86 | // 配置获取 87 | export const listCluster = () => { 88 | // 返回的数据格式可以和服务端约定 89 | return RequestHttp.get('/cluster/v1/list'); 90 | } 91 | 92 | // 配置获取 93 | export const listClusterNodes = (params: RCluster.nodeReqForm) => { 94 | // 返回的数据格式可以和服务端约定 95 | return RequestHttp.get('/cluster/v1/nodes', { params: params}); 96 | } 97 | 98 | //获取master信息 99 | export const listClusterMaster = (params: RCluster.masterReqForm) => { 100 | return RequestHttp.get('/cluster/v1/masters', { params: params}); 101 | } -------------------------------------------------------------------------------- /redis-manager-ui/src/api/codis.ts: -------------------------------------------------------------------------------- 1 | import RequestHttp from '../utils/request' 2 | 3 | // 新增codis平台 4 | namespace Rcodis { 5 | export interface ReqForm { 6 | cname: string; 7 | curl: string; 8 | } 9 | export interface SettingResData { 10 | data: number 11 | errorCode: number; 12 | msg: string; 13 | } 14 | export interface ListResData { 15 | data: { 16 | lists: [{ 17 | ID: number; 18 | CreatedAt: string; 19 | UpdatedAt: string; 20 | DeletedAt: string; 21 | Curl: string; 22 | Cname: string; 23 | }]; 24 | total: number; 25 | } 26 | errorCode: number; 27 | msg: string; 28 | } 29 | export interface ListclusterReqForm { 30 | cname: string; 31 | curl: string; 32 | } 33 | export interface ListclusterResData { 34 | data: [string] 35 | errorCode: number; 36 | msg: string; 37 | } 38 | export interface ListgroupReqForm { 39 | cluster_name: string; 40 | curl: string; 41 | } 42 | export interface ListgroupResData { 43 | data: [string] 44 | errorCode: number; 45 | msg: string; 46 | } 47 | export interface opnodeReqForm { 48 | curl: string; 49 | cluster_name: string; 50 | add_proxy: string; 51 | add_server: string; 52 | del_proxy: number; 53 | del_group: number; 54 | op_type: string 55 | } 56 | export interface opnodeResData { 57 | data: string 58 | errorCode: number; 59 | msg: string; 60 | } 61 | } 62 | // 删除 63 | export const delCodisCfg = (params: Rcodis.ReqForm) => { 64 | return RequestHttp.delete('/cfg/v1/del', {params: params}); 65 | } 66 | 67 | //新增 68 | export const addCodisCfg = (params: Rcodis.ReqForm) => { 69 | return RequestHttp.post('/codis/v1/add', params); 70 | } 71 | 72 | // 配置获取 73 | export const listCodis = () => { 74 | // 返回的数据格式可以和服务端约定 75 | return RequestHttp.get('/codis/v1/list'); 76 | } 77 | //获取codis平台集群列表 78 | export const listCodisCluster = (params: Rcodis.ListclusterReqForm) => { 79 | // 返回的数据格式可以和服务端约定 80 | return RequestHttp.get('/codis/v1/cluster', {params: params}); 81 | } 82 | //获取codis集群的group列表 83 | export const listCodisGroup = (params: Rcodis.ListgroupReqForm) => { 84 | // 返回的数据格式可以和服务端约定 85 | return RequestHttp.get('/codis/v1/group', {params: params}); 86 | } 87 | //针对codis进行扩容缩容 88 | export const opCodisNode = (params: Rcodis.opnodeReqForm) => { 89 | return RequestHttp.post('/codis/v1/opnode', params); 90 | } -------------------------------------------------------------------------------- /redis-manager-ui/src/api/history.ts: -------------------------------------------------------------------------------- 1 | import RequestHttp from '../utils/request' 2 | 3 | namespace Rhistory { 4 | // 拿到配置数据 5 | export interface HistoryResData { 6 | data: [ 7 | { 8 | ID: number; 9 | CreatedAt: string; 10 | UpdatedAt: string; 11 | DeletedAt: string; 12 | UserId: number; 13 | OpInfo: string; 14 | OpParams: string; 15 | } 16 | ] 17 | errorCode: number; 18 | msg: string; 19 | } 20 | } 21 | // 配置获取 22 | export const list = () => { 23 | // 返回的数据格式可以和服务端约定 24 | return RequestHttp.get('/ophistory/v1/list'); 25 | } 26 | -------------------------------------------------------------------------------- /redis-manager-ui/src/api/rule.ts: -------------------------------------------------------------------------------- 1 | import RequestHttp from '../utils/request' 2 | 3 | namespace Rruledata { 4 | export interface AddReqForm { 5 | identity: string; 6 | path: string; 7 | method: string; 8 | } 9 | export interface AddResData { 10 | data: { 11 | result: string; 12 | } 13 | errorCode: number; 14 | msg: string; 15 | } 16 | export interface ListResData { 17 | data: [{ 18 | ID: number; 19 | identity: string; 20 | method: string; 21 | note: string; 22 | path: string; 23 | }] 24 | errorCode: number; 25 | msg: string; 26 | } 27 | export interface CfgResData { 28 | data: { 29 | method: [{ 30 | label: string; 31 | value: string; 32 | }] 33 | url: [{ 34 | label: string; 35 | value: string; 36 | }] 37 | } 38 | errorCode: number; 39 | msg: string; 40 | } 41 | } 42 | export const rulelist = () => { 43 | // 返回的数据格式可以和服务端约定 44 | return RequestHttp.get('/rule/v1/list'); 45 | } 46 | 47 | export const ruledel = (params: Rruledata.AddReqForm) => { 48 | return RequestHttp.delete('/rule/v1/del', {params: params}); 49 | } 50 | 51 | export const rulecfg = () => { 52 | return RequestHttp.get('/rule/v1/cfg'); 53 | } 54 | 55 | export const ruleadd = (params: Rruledata.AddReqForm) => { 56 | return RequestHttp.post('/rule/v1/add', params); 57 | } -------------------------------------------------------------------------------- /redis-manager-ui/src/api/setting.ts: -------------------------------------------------------------------------------- 1 | import RequestHttp from '../utils/request' 2 | 3 | namespace Rconfig { 4 | // 拿到配置数据 5 | export interface ListResData { 6 | data: { 7 | lists: [{ 8 | ID: number; 9 | CreatedAt: string; 10 | UpdatedAt: string; 11 | DeletedAt: string; 12 | Key: string; 13 | Name: string; 14 | Value: string; 15 | }]; 16 | total: number; 17 | } 18 | errorCode: number; 19 | msg: string; 20 | } 21 | export interface ResData { 22 | data: [{ 23 | label: string; 24 | value: string; 25 | }] 26 | errorCode: number; 27 | msg: string; 28 | } 29 | export interface ReqForm { 30 | key: string; 31 | value: string; 32 | } 33 | } 34 | // 已配置获取 35 | export const listCfg = () => { 36 | // 返回的数据格式可以和服务端约定 37 | return RequestHttp.get('/cfg/v1/list'); 38 | } 39 | 40 | // 默认配置获取 41 | export const listDefaultCfg = () => { 42 | // 返回的数据格式可以和服务端约定 43 | return RequestHttp.get('/cfg/v1/listdefault'); 44 | } 45 | 46 | // 删除 47 | export const delCfg = (params: Rconfig.ReqForm) => { 48 | return RequestHttp.delete('/cfg/v1/del', {params: params}); 49 | } 50 | 51 | //变更 52 | export const updateCfg = (params: Rconfig.ReqForm) => { 53 | return RequestHttp.post('/cfg/v1/update', params); 54 | } -------------------------------------------------------------------------------- /redis-manager-ui/src/api/user.ts: -------------------------------------------------------------------------------- 1 | import RequestHttp from '../utils/request' 2 | 3 | namespace Ruser { 4 | // 用户登录表单 5 | export interface LoginReqForm { 6 | username: string; 7 | password: string; 8 | } 9 | // 登录成功后返回的token 10 | export interface LoginResData { 11 | data: { 12 | result: string; 13 | token: string; 14 | username: string; 15 | usertype: string; 16 | } 17 | errorCode: number; 18 | msg: string; 19 | } 20 | export interface PasswordReqForm { 21 | old: string; 22 | new: string; 23 | } 24 | export interface PasswordResData { 25 | data: { 26 | result: string; 27 | } 28 | errorCode: number; 29 | msg: string; 30 | } 31 | export interface listResData { 32 | data: [{ 33 | ID: number; 34 | CreatedAt: string; 35 | UpdatedAt: string; 36 | UserName: string; 37 | Email: string; 38 | UserType: string; 39 | }] 40 | errorCode: number; 41 | msg: string; 42 | } 43 | export interface typeResData { 44 | data: [{ 45 | label: string; 46 | value: string; 47 | }] 48 | errorCode: number; 49 | msg: string; 50 | } 51 | export interface createReqForm { 52 | username: string; 53 | password: string; 54 | mail: string; 55 | } 56 | export interface createResData { 57 | data: { 58 | result: string; 59 | } 60 | errorCode: number; 61 | msg: string; 62 | } 63 | export interface changeReqForm { 64 | username: string; 65 | usertype: string; 66 | } 67 | export interface changeResData { 68 | data: { 69 | result: string; 70 | } 71 | errorCode: number; 72 | msg: string; 73 | } 74 | export interface delReqForm { 75 | username: string; 76 | } 77 | export interface delResData { 78 | data: { 79 | result: string; 80 | } 81 | errorCode: number; 82 | msg: string; 83 | } 84 | } 85 | // 用户登录 86 | export const login = (params: Ruser.LoginReqForm) => { 87 | // 返回的数据格式可以和服务端约定 88 | return RequestHttp.post('/auth/v1/sign-in', params); 89 | } 90 | 91 | export const userpassword = (params: Ruser.PasswordReqForm) => { 92 | // 返回的数据格式可以和服务端约定 93 | return RequestHttp.post('/auth/v1/password', params); 94 | } 95 | 96 | export const userlist = () => { 97 | // 返回的数据格式可以和服务端约定 98 | return RequestHttp.get('/user/v1/list'); 99 | } 100 | 101 | export const usertypelist = () => { 102 | // 返回的数据格式可以和服务端约定 103 | return RequestHttp.get('/user/v1/utype'); 104 | } 105 | 106 | export const useradd = (params: Ruser.createReqForm) => { 107 | // 返回的数据格式可以和服务端约定 108 | return RequestHttp.post('/user/v1/add', params); 109 | } 110 | 111 | export const userchange = (params: Ruser.changeReqForm) => { 112 | // 返回的数据格式可以和服务端约定 113 | return RequestHttp.post('/user/v1/change', params); 114 | } 115 | 116 | export const userdel = (params: Ruser.delReqForm) => { 117 | // 返回的数据格式可以和服务端约定 118 | return RequestHttp.delete('/user/v1/del', {params: params}); 119 | } 120 | 121 | -------------------------------------------------------------------------------- /redis-manager-ui/src/assets/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iguidao/redis-manager/43400bf548ffb68767ce7067679c6128fc2e155b/redis-manager-ui/src/assets/img/favicon.ico -------------------------------------------------------------------------------- /redis-manager-ui/src/assets/img/github-mark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iguidao/redis-manager/43400bf548ffb68767ce7067679c6128fc2e155b/redis-manager-ui/src/assets/img/github-mark.png -------------------------------------------------------------------------------- /redis-manager-ui/src/assets/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iguidao/redis-manager/43400bf548ffb68767ce7067679c6128fc2e155b/redis-manager-ui/src/assets/img/logo.png -------------------------------------------------------------------------------- /redis-manager-ui/src/assets/img/sun.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iguidao/redis-manager/43400bf548ffb68767ce7067679c6128fc2e155b/redis-manager-ui/src/assets/img/sun.jpeg -------------------------------------------------------------------------------- /redis-manager-ui/src/assets/vue.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /redis-manager-ui/src/components/breadcrumb/Breadcrumb.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 21 | 22 | -------------------------------------------------------------------------------- /redis-manager-ui/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import './style.css' 3 | import App from './App.vue' 4 | import router from './router/index' 5 | import * as ElementPlusIconsVue from '@element-plus/icons-vue' 6 | import ElementPlus from 'element-plus' 7 | import 'element-plus/dist/index.css' 8 | import JsonViewer from 'vue-json-viewer' 9 | import.meta.env.MODE 10 | 11 | const app = createApp(App) 12 | for (const [key, component] of Object.entries(ElementPlusIconsVue)) { 13 | app.component(key, component) 14 | } 15 | app.use(router).use(ElementPlus).use(JsonViewer).mount('#app') 16 | -------------------------------------------------------------------------------- /redis-manager-ui/src/pages/command/Index.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 31 | 36 | -------------------------------------------------------------------------------- /redis-manager-ui/src/pages/dashboard/Index.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 57 | 58 | -------------------------------------------------------------------------------- /redis-manager-ui/src/pages/history/Index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 42 | -------------------------------------------------------------------------------- /redis-manager-ui/src/pages/login/Index.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 67 | 68 | 119 | -------------------------------------------------------------------------------- /redis-manager-ui/src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router' 2 | const routes = [ 3 | { 4 | path: '/', 5 | redirect: '/login' 6 | }, 7 | { 8 | path: '/login', 9 | name: 'login', 10 | meta: { 11 | title: '登录' 12 | }, 13 | component: () => import('../pages/login/Index.vue') 14 | }, 15 | 16 | { 17 | path: '/home', 18 | meta: { 19 | title: 'Redis Manager' 20 | }, 21 | component: () => import('../pages/home/Index.vue'), 22 | redirect: '/dashboard', 23 | children: [{ 24 | path: '/dashboard', 25 | meta: { 26 | title: '概览' 27 | }, 28 | component: () => import('../pages/dashboard/Index.vue') 29 | }, 30 | { 31 | path: '/user/index', 32 | meta: { 33 | title: '用户管理' 34 | }, 35 | component: () => import('../pages/user/Index.vue'), 36 | }, 37 | { 38 | path: '/aliredis/index', 39 | meta: { 40 | title: '阿里redis' 41 | }, 42 | component: () => import('../pages/redis/aliredis/Index.vue'), 43 | }, 44 | { 45 | path: '/redis/index', 46 | meta: { 47 | title: '自建cluster' 48 | }, 49 | component: () => import('../pages/redis/cluster/Index.vue'), 50 | }, 51 | { 52 | path: '/codis/index', 53 | meta: { 54 | title: '自建codis' 55 | }, 56 | component: () => import('../pages/redis/codis/Index.vue'), 57 | }, 58 | { 59 | path: '/txredis/index', 60 | meta: { 61 | title: '腾讯redis' 62 | }, 63 | component: () => import('../pages/redis/txredis/Index.vue'), 64 | }, 65 | { 66 | path: '/command/query', 67 | meta: { 68 | title: '查询数据' 69 | }, 70 | component: () => import('../pages/command/Index.vue'), 71 | }, 72 | { 73 | path: '/history/index', 74 | meta: { 75 | title: '历史记录' 76 | }, 77 | component: () => import('../pages/history/Index.vue'), 78 | }, 79 | { 80 | path: '/setting/index', 81 | meta: { 82 | title: '系统设置' 83 | }, 84 | component: () => import('../pages/setting/Index.vue'), 85 | }, 86 | { 87 | path: '/setting/rule', 88 | meta: { 89 | title: '权限配置' 90 | }, 91 | component: () => import('../pages/setting/Rule.vue'), 92 | }, 93 | ] 94 | }, 95 | ] 96 | const router = createRouter({ 97 | history: createWebHistory(), 98 | // history: createWebHashHistory(), 99 | routes 100 | }) 101 | // 挂载路由导航守卫:to表示将要访问的路径,from表示从哪里来,next是下一个要做的操作 102 | router.beforeEach((to, from, next) => { 103 | // 修改页面 title 104 | // if (to.meta.title) { 105 | // document.title = 'Redis-Manager平台 - ' + to.meta.title 106 | // } 107 | // 放行登录页面 108 | if (to.path === '/login') { 109 | return next() 110 | } 111 | // 获取token 112 | const token = sessionStorage.getItem('Authorization') 113 | if (!token) { 114 | return next('/login') 115 | } else { 116 | next() 117 | } 118 | return next() 119 | }) 120 | 121 | // 导出路由 122 | export default router -------------------------------------------------------------------------------- /redis-manager-ui/src/style.css: -------------------------------------------------------------------------------- 1 | /* :root { 2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 3 | line-height: 1.5; 4 | font-weight: 400; 5 | 6 | color-scheme: light dark; 7 | color: rgba(255, 255, 255, 0.87); 8 | background-color: #242424; 9 | 10 | font-synthesis: none; 11 | text-rendering: optimizeLegibility; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | -webkit-text-size-adjust: 100%; 15 | } 16 | 17 | a { 18 | font-weight: 500; 19 | color: #646cff; 20 | text-decoration: inherit; 21 | } 22 | a:hover { 23 | color: #535bf2; 24 | } 25 | 26 | body { 27 | margin: 0; 28 | display: flex; 29 | place-items: center; 30 | min-width: 320px; 31 | min-height: 100vh; 32 | } 33 | 34 | h1 { 35 | font-size: 3.2em; 36 | line-height: 1.1; 37 | } 38 | 39 | button { 40 | border-radius: 8px; 41 | border: 1px solid transparent; 42 | padding: 0.6em 1.2em; 43 | font-size: 1em; 44 | font-weight: 500; 45 | font-family: inherit; 46 | background-color: #1a1a1a; 47 | cursor: pointer; 48 | transition: border-color 0.25s; 49 | } 50 | button:hover { 51 | border-color: #646cff; 52 | } 53 | button:focus, 54 | button:focus-visible { 55 | outline: 4px auto -webkit-focus-ring-color; 56 | } 57 | 58 | .card { 59 | padding: 2em; 60 | } 61 | 62 | #app { 63 | max-width: 1280px; 64 | margin: 0 auto; 65 | padding: 2rem; 66 | text-align: center; 67 | } 68 | 69 | @media (prefers-color-scheme: light) { 70 | :root { 71 | color: #213547; 72 | background-color: #ffffff; 73 | } 74 | a:hover { 75 | color: #747bff; 76 | } 77 | button { 78 | background-color: #f9f9f9; 79 | } 80 | } */ 81 | -------------------------------------------------------------------------------- /redis-manager-ui/src/utils/request/http.ts: -------------------------------------------------------------------------------- 1 | import axiosInstance from './index' 2 | 3 | // 数据返回的接口 4 | // 定义请求响应参数,不含data 5 | interface Result { 6 | errorCode: number; 7 | msg: string 8 | } 9 | 10 | // 请求响应参数,包含data 11 | interface ResultData extends Result { 12 | data?: T; 13 | } 14 | 15 | class RequestHttp { 16 | get(url: string, params?: object): Promise> { 17 | return axiosInstance.get(url, {params}); 18 | } 19 | post(url: string, params?: object): Promise> { 20 | return axiosInstance.post(url, params); 21 | } 22 | put(url: string, params?: object): Promise> { 23 | return axiosInstance.put(url, params); 24 | } 25 | delete(url: string, params?: object): Promise> { 26 | return axiosInstance.delete(url, {params}); 27 | } 28 | } 29 | // 导出一个实例对象 30 | export default RequestHttp -------------------------------------------------------------------------------- /redis-manager-ui/src/utils/request/index.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosRequestConfig, AxiosRequestHeaders } from "axios"; 2 | import {ElMessage} from 'element-plus' 3 | 4 | 5 | interface AdaptAxiosRequestConfig extends AxiosRequestConfig { 6 | headers: AxiosRequestHeaders 7 | } 8 | 9 | // config 10 | const axiosInstance = axios.create({ 11 | baseURL: import.meta.env.VITE_BASE_API, 12 | // baseURL: "/api", 13 | timeout: 30000 14 | }); 15 | 16 | // Interceptors 17 | axiosInstance.interceptors.request.use( 18 | (config): AdaptAxiosRequestConfig => { 19 | let token = sessionStorage.getItem('Authorization') 20 | if (token) { 21 | config.headers['Authorization'] = token 22 | } 23 | return config; 24 | }, 25 | (error): any => { 26 | return Promise.reject(error); 27 | } 28 | ); 29 | 30 | axiosInstance.interceptors.response.use( 31 | async (response): Promise => { 32 | return response; 33 | }, 34 | async (error): Promise => { 35 | if (error && error.response) { 36 | const status = error.response.status 37 | switch (status) { 38 | case 400: 39 | ElMessage.error("请求错误"); 40 | break; 41 | case 401: 42 | ElMessage.error("未授权,请重新登录"); 43 | break; 44 | case 403: 45 | ElMessage.error("拒绝访问"); 46 | break; 47 | case 404: 48 | ElMessage.error("请求错误,未找到相应的资源"); 49 | break; 50 | case 408: 51 | ElMessage.error("请求超时"); 52 | break; 53 | case 500: 54 | ElMessage.error("服务器内部错误"); 55 | break; 56 | case 501: 57 | ElMessage.error("网络未实现"); 58 | break; 59 | case 502: 60 | ElMessage.error("网络错误"); 61 | break; 62 | case 503: 63 | ElMessage.error("服务不可用"); 64 | break; 65 | case 504: 66 | ElMessage.error("网络超时"); 67 | break; 68 | case 505: 69 | ElMessage.error("HTTP版本不支持该请求"); 70 | break; 71 | default: 72 | ElMessage.error("请求失败"); 73 | } 74 | } else { 75 | if (JSON.stringify(error).includes("timeout")) { 76 | ElMessage.error("服务器响应超时,请刷新页面"); 77 | } 78 | ElMessage.error("连接服务器失败"); 79 | } 80 | return Promise.reject(error); 81 | } 82 | ); 83 | 84 | // 4.导出 axios 实例 85 | export default axiosInstance; -------------------------------------------------------------------------------- /redis-manager-ui/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | interface ImportMetaEnv { 4 | readonly VITE_BASE_API: string 5 | // 更多环境变量... 6 | } 7 | interface ImportMeta { 8 | readonly env: ImportMetaEnv 9 | } -------------------------------------------------------------------------------- /redis-manager-ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "moduleResolution": "Node", 7 | "strict": true, 8 | "jsx": "preserve", 9 | "resolveJsonModule": true, 10 | "isolatedModules": true, 11 | "esModuleInterop": true, 12 | "lib": ["ESNext", "DOM"], 13 | "skipLibCheck": true, 14 | "noEmit": true 15 | }, 16 | "include": ["src/**/*.ts", "src/**/*.d.ts", "**/*.d.ts", "src/**/*.tsx", "src/**/*.vue", "src/**/**/*.vue", "src/api/user.js"], 17 | "references": [{ "path": "./tsconfig.node.json" }] 18 | } 19 | -------------------------------------------------------------------------------- /redis-manager-ui/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /redis-manager-ui/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | // import AutoImport from 'unplugin-auto-import/vite' 4 | // import Components from 'unplugin-vue-components/vite' 5 | // import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' 6 | 7 | // https://vitejs.dev/config/ 8 | export default defineConfig({ 9 | plugins: [ 10 | vue(), 11 | // AutoImport({ 12 | // resolvers: [ElementPlusResolver()], 13 | // }), 14 | // Components({ 15 | // resolvers: [ElementPlusResolver()], 16 | // }), 17 | ], 18 | server: { 19 | open: false, 20 | port: 3000, 21 | proxy: { 22 | '/api': { 23 | target: 'http://127.0.0.1:8000/redis-manager', // 24 | changeOrigin: true, 25 | rewrite: (path) => path.replace(/^\/api/, '') 26 | } 27 | }, 28 | }, 29 | }) 30 | -------------------------------------------------------------------------------- /src/cfg/config.go: -------------------------------------------------------------------------------- 1 | package cfg 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/fsnotify/fsnotify" 7 | "github.com/spf13/viper" 8 | ) 9 | 10 | type Config struct { 11 | Name string 12 | } 13 | 14 | func Get_Info_Int(get_type string) int { 15 | switch get_type { 16 | case "allkeyfornum": 17 | rediscfg_allkeyfornum := viper.GetInt("rediscfg.allkeyfornum") 18 | return rediscfg_allkeyfornum 19 | case "locktime": 20 | rediscfg_locktime := viper.GetInt("rediscfg.locktime") 21 | return rediscfg_locktime 22 | case "biglocktime": 23 | rediscfg_biglocktime := viper.GetInt("rediscfg.biglocktime") 24 | return rediscfg_biglocktime 25 | case "checksize": 26 | rediscfg_checksize := viper.GetInt("rediscfg.checksize") 27 | return rediscfg_checksize 28 | default: 29 | return 0 30 | } 31 | } 32 | 33 | func Get_Info_String(get_type string) string { 34 | switch get_type { 35 | case "MYSQL": 36 | mysql_name := viper.GetString("mysql.name") 37 | mysql_addr := viper.GetString("mysql.addr") 38 | mysql_username := viper.GetString("mysql.username") 39 | mysql_password := viper.GetString("mysql.password") 40 | mysql_url := fmt.Sprintf("%s:%s@(%s)/%s?charset=utf8&parseTime=True&loc=Local", mysql_username, mysql_password, mysql_addr, mysql_name) 41 | return mysql_url 42 | case "REDIS": 43 | redis_addr := viper.GetString("redis.addr") 44 | redis_port := viper.GetString("redis.port") 45 | redis_url := fmt.Sprintf("%s:%s", redis_addr, redis_port) 46 | return redis_url 47 | case "redispw": 48 | local_redispw := viper.GetString("redis.password") 49 | return local_redispw 50 | case "addr": 51 | local_addr := viper.GetString("local.addr") 52 | return local_addr 53 | case "logapipath": 54 | local_logapipath := viper.GetString("local.logapipath") 55 | return local_logapipath 56 | case "logapppath": 57 | local_logapppath := viper.GetString("local.logapppath") 58 | return local_logapppath 59 | case "secretkey": 60 | rediscfg_secretkey := viper.GetString("local.secretkey") 61 | return rediscfg_secretkey 62 | case "cosaccesskey": 63 | cos_cosaccesskey := viper.GetString("cos.cosaccesskey") 64 | return cos_cosaccesskey 65 | case "cosaccesskeyid": 66 | cos_cosaccesskeyid := viper.GetString("cos.cosaccesskeyid") 67 | return cos_cosaccesskeyid 68 | case "cosendpointpub": 69 | cos_cosendpointpub := viper.GetString("cos.cosendpointpub") 70 | return cos_cosendpointpub 71 | default: 72 | return "noconfig" 73 | } 74 | } 75 | 76 | func Init(cfg string) error { 77 | c := Config{ 78 | Name: cfg, 79 | } 80 | // 初始化配置文件 81 | if err := c.initConfig(); err != nil { 82 | return err 83 | } 84 | c.watchConfig() 85 | 86 | return nil 87 | } 88 | 89 | func (c *Config) initConfig() error { 90 | if c.Name != "" { 91 | // 如果指定了配置文件,则解析指定的配置文件 92 | viper.SetConfigFile(c.Name) 93 | } else { 94 | // 如果没有指定配置文件,则解析默认的配置文件 95 | viper.AddConfigPath("yaml") 96 | viper.SetConfigName("config") 97 | } 98 | // 设置配置文件格式为YAML 99 | viper.SetConfigType("yaml") 100 | // viper解析配置文件 101 | if err := viper.ReadInConfig(); err != nil { 102 | return err 103 | } 104 | 105 | return nil 106 | } 107 | 108 | // 监听配置文件是否改变,用于热更新 109 | func (c *Config) watchConfig() { 110 | viper.WatchConfig() 111 | viper.OnConfigChange(func(e fsnotify.Event) { 112 | fmt.Printf("Config file changed: %s\n", e.Name) 113 | }) 114 | } 115 | -------------------------------------------------------------------------------- /src/hsc/code.go: -------------------------------------------------------------------------------- 1 | package hsc 2 | 3 | // 提供状态码 4 | const ( 5 | SUCCESS = 0 6 | ERROR = 500 7 | INVALID_PARAMS = 400 8 | NO_LOGIN = 401 9 | NOT_PROMISE = 403 10 | SERVER_ERROR = 544 11 | 12 | NOT_FOUND = 10001 13 | Method_FAILS = 10002 14 | ERROR_AUTH_CHECK_TOKEN_FAIL = 20001 15 | ERROR_AUTH_CHECK_TOKEN_TIMEOUT = 20002 16 | ERROR_AUTH_TOKEN = 20003 17 | ERROR_AUTH = 20004 18 | MESSAGE_RE = 400 19 | ERROR_NO_CONNEC = 50001 20 | ERROR_BACKGROUND = 50002 21 | ERROR_CLOUD_CONNECT = 50003 22 | ERROR_CLOUD_GET = 50004 23 | ERROR_WRITE_MYSQL = 50005 24 | WARN_CLICK_REPEATEDLY = 60000 25 | WARN_NO_USE = 60002 26 | WARN_BACKGROUND = 60004 27 | WARN_NOT_FOUND_CLOUD = 60005 28 | WARN_NOT_PROMISE_RULE = 60006 29 | WARN_USER_NAME_EXIST = 60007 30 | WARN_USER_MAIL_EXIST = 60008 31 | WARN_USER_PASSWORD_CHECK = 60009 32 | WARN_CODIS_NOT_CONNECT = 60010 33 | WARN_CODIS_IS_REBALANCE = 60011 34 | WARN_CODIS_NOT_OPTION = 60012 35 | WARN_CODIS_PROXY_MIN_NUMBER = 60013 36 | WARN_CODIS_GROUP_MIN_NUMBER = 60014 37 | WARN_CODIS_GROUP_MIN_CAPACITY = 60015 38 | WARN_CHECK_IPPORT_FAIL = 60016 39 | ) 40 | -------------------------------------------------------------------------------- /src/hsc/msg.go: -------------------------------------------------------------------------------- 1 | package hsc 2 | 3 | // 提供错误信息 4 | var MsgFlags = map[int]string{ 5 | SUCCESS: "成功", 6 | ERROR: "服务器出现错误", 7 | NOT_FOUND: "没有找到请求", 8 | INVALID_PARAMS: "请求参数异常", 9 | NOT_PROMISE: "鉴权失败", 10 | Method_FAILS: "Method 请求错误", 11 | ERROR_AUTH_CHECK_TOKEN_FAIL: "Token 检查异常", 12 | ERROR_AUTH_CHECK_TOKEN_TIMEOUT: "Token 超时了", 13 | ERROR_AUTH_TOKEN: "Token 创建异常", 14 | ERROR_AUTH: "认证失败", 15 | 16 | ERROR_NO_CONNEC: "链接目标Redis异常", 17 | ERROR_CLOUD_CONNECT: "链接云服务异常", 18 | ERROR_CLOUD_GET: "获取云资源异常", 19 | ERROR_WRITE_MYSQL: "数据库操作异常", 20 | ERROR_BACKGROUND: "后台加载异常,请联系管理员", 21 | WARN_NO_USE: "功能会造成慢查询,暂时下线", 22 | WARN_CLICK_REPEATEDLY: "兄弟,你点的太快了,上一个还没结束,等一下哈!", 23 | WARN_BACKGROUND: "没有找到这些数据,后台已经再加载,请稍后重试一下。", 24 | WARN_NOT_FOUND_CLOUD: "暂时不支持这个云操作", 25 | WARN_NOT_PROMISE_RULE: "没有权限,请找管理员开放", 26 | WARN_USER_NAME_EXIST: "用户名已经存在", 27 | WARN_USER_MAIL_EXIST: "用户邮箱已经存在", 28 | WARN_USER_PASSWORD_CHECK: "旧密码不对", 29 | WARN_CODIS_NOT_CONNECT: "获取codis信息失败", 30 | WARN_CODIS_IS_REBALANCE: "codis正在rebalance", 31 | WARN_CODIS_NOT_OPTION: "没有这个codis操作", 32 | WARN_CODIS_PROXY_MIN_NUMBER: "codis的proxy最小是2个,不能再少了", 33 | WARN_CODIS_GROUP_MIN_NUMBER: "codis的group最小是1个,不能再少了", 34 | WARN_CODIS_GROUP_MIN_CAPACITY: "剩余codis的group容量不足80%了", 35 | WARN_CHECK_IPPORT_FAIL: "IP和端口健康检查失败", 36 | } 37 | 38 | func GetMsg(code int) string { 39 | msg, ok := MsgFlags[code] 40 | if ok { 41 | return msg 42 | } 43 | 44 | return MsgFlags[ERROR] 45 | } 46 | -------------------------------------------------------------------------------- /src/middleware/alicloud/alist.go: -------------------------------------------------------------------------------- 1 | package alicloud 2 | 3 | import ( 4 | r_kvstore20150101 "github.com/alibabacloud-go/r-kvstore-20150101/v3/client" 5 | util "github.com/alibabacloud-go/tea-utils/v2/service" 6 | "github.com/alibabacloud-go/tea/tea" 7 | "github.com/iguidao/redis-manager/src/middleware/logger" 8 | ) 9 | 10 | func AliListRegion() (string, bool) { 11 | describeRegionsRequest := &r_kvstore20150101.DescribeRegionsRequest{} 12 | runtime := &util.RuntimeOptions{} 13 | response, err := AliRedisApi.DescribeRegionsWithOptions(describeRegionsRequest, runtime) 14 | if err != nil { 15 | logger.Error("Get ali region error: ", err) 16 | return "", false 17 | } 18 | return response.Body.GoString(), true 19 | } 20 | 21 | func AliListRedis(region string) (string, bool) { 22 | describeInstancesRequest := &r_kvstore20150101.DescribeInstancesRequest{ 23 | RegionId: tea.String(region), 24 | } 25 | runtime := &util.RuntimeOptions{} 26 | response, err := AliRedisApi.DescribeInstancesWithOptions(describeInstancesRequest, runtime) 27 | if err != nil { 28 | logger.Error("Get ali region error: ", err) 29 | return "", false 30 | } 31 | return response.Body.GoString(), true 32 | } 33 | -------------------------------------------------------------------------------- /src/middleware/alicloud/connect.go: -------------------------------------------------------------------------------- 1 | package alicloud 2 | 3 | import ( 4 | openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client" 5 | r_kvstore20150101 "github.com/alibabacloud-go/r-kvstore-20150101/v3/client" 6 | "github.com/alibabacloud-go/tea/tea" 7 | "github.com/iguidao/redis-manager/src/middleware/logger" 8 | "github.com/iguidao/redis-manager/src/middleware/model" 9 | "github.com/iguidao/redis-manager/src/middleware/mysql" 10 | ) 11 | 12 | var AliRedisApi *r_kvstore20150101.Client 13 | 14 | func CreateClient(accessKeyId *string, accessKeySecret *string) (_result *r_kvstore20150101.Client, _err error) { 15 | config := &openapi.Config{ 16 | // 必填,您的 AccessKey ID 17 | AccessKeyId: accessKeyId, 18 | // 必填,您的 AccessKey Secret 19 | AccessKeySecret: accessKeySecret, 20 | } 21 | 22 | // 访问的域名 23 | config.Endpoint = tea.String(mysql.DB.GetOneCfgValue(model.ALIAPIURL)) 24 | _result = &r_kvstore20150101.Client{} 25 | _result, _err = r_kvstore20150101.NewClient(config) 26 | return _result, _err 27 | } 28 | 29 | func AliRedisContent() bool { 30 | var err error 31 | // 工程代码泄露可能会导致AccessKey泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考,建议使用更安全的 STS 方式,更多鉴权访问方式请参见:https://help.aliyun.com/document_detail/378661.html 32 | AliRedisApi, err = CreateClient(tea.String(mysql.DB.GetOneCfgValue(model.ALIACCESSKEYID)), tea.String(mysql.DB.GetOneCfgValue(model.ALIALIACCESSKEYSECRET))) 33 | if err != nil { 34 | logger.Error("conenct ali cloud redis error: ", err) 35 | return false 36 | } 37 | return true 38 | } 39 | -------------------------------------------------------------------------------- /src/middleware/casbin/casbinstruct.go: -------------------------------------------------------------------------------- 1 | package casbin 2 | 3 | type CasbinRule struct { 4 | ID int `gorm:"primary_key"` 5 | Ptype string `gorm:"size:32;uniqueIndex:unique_index"` 6 | V0 string `gorm:"size:64;uniqueIndex:unique_index"` 7 | V1 string `gorm:"size:512;uniqueIndex:unique_index"` 8 | V2 string `gorm:"size:16;uniqueIndex:unique_index"` 9 | V3 string `gorm:"size:32;uniqueIndex:unique_index"` 10 | V4 string `gorm:"size:32;uniqueIndex:unique_index"` 11 | V5 string `gorm:"size:32;uniqueIndex:unique_index"` 12 | } 13 | 14 | func (CasbinRule) TableName() string { 15 | return "casbin_rule" 16 | } 17 | -------------------------------------------------------------------------------- /src/middleware/casbin/initial.go: -------------------------------------------------------------------------------- 1 | package casbin 2 | 3 | import ( 4 | "github.com/casbin/casbin/v2" 5 | cmodel "github.com/casbin/casbin/v2/model" 6 | gormadapter "github.com/casbin/gorm-adapter/v3" 7 | _ "github.com/go-sql-driver/mysql" 8 | "github.com/iguidao/redis-manager/src/middleware/logger" 9 | rmysql "github.com/iguidao/redis-manager/src/middleware/mysql" 10 | ) 11 | 12 | var Enforcer *casbin.Enforcer 13 | 14 | func Connect() { 15 | var err error 16 | a, _ := gormadapter.NewAdapterByDBWithCustomTable(rmysql.DB.DB, &CasbinRule{}) 17 | // a := gormadapter.NewAdapterByDB(db) 18 | m, err := cmodel.NewModelFromString(` 19 | [request_definition] 20 | r = sub, obj, act 21 | 22 | [policy_definition] 23 | p = sub, obj, act 24 | 25 | [policy_effect] 26 | e = some(where (p.eft == allow)) 27 | 28 | [matchers] 29 | m = r.sub == p.sub && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act) || r.sub == "admin" 30 | `) 31 | if err != nil { 32 | logger.Error("error: model: ", err) 33 | } 34 | 35 | Enforcer, err = casbin.NewEnforcer(m, a) 36 | if err != nil { 37 | logger.Error("error: enforcer: ", err) 38 | } 39 | // 开启权限认证日志 40 | Enforcer.EnableLog(true) 41 | // Load the policy from DB. 42 | err = Enforcer.LoadPolicy() 43 | if err != nil { 44 | logger.Error("loadPolicy error") 45 | panic(err) 46 | } 47 | } 48 | 49 | // // DB as the mysql client 50 | // var DB *gorm.DB 51 | 52 | // // Connect create db connection 53 | 54 | // func Connect(dsn string) { 55 | // // Increase the column size to 512. 56 | 57 | // // db, _ := gorm.Open(...) 58 | // DB, err := gorm.Open(mysql.New(mysql.Config{ 59 | // // DSN: "gorm:gorm@tcp(127.0.0.1:3306)/gorm?charset=utf8&parseTime=True&loc=Local", // DSN data source name 60 | // DSN: dsn, 61 | // DefaultStringSize: 512, // string 类型字段的默认长度 62 | // DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持 63 | // DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引 64 | // DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列 65 | // SkipInitializeWithVersion: false, // 根据当前 MySQL 版本自动配置 66 | // }), &gorm.Config{}) 67 | // if err != nil { 68 | // logger.Error("Cannot open mysql database: ", err.Error()) 69 | // panic(err) 70 | // } 71 | // // Initialize a Gorm adapter and use it in a Casbin enforcer: 72 | // // The adapter will use an existing gorm.DB instnace. 73 | // // a, _ := gormadapter.NewAdapterByDBWithCustomTable(db, &CasbinRule{}) 74 | // a, _ := gormadapter.NewAdapterByDBWithCustomTable(DB, &CasbinRule{}) 75 | // // a := gormadapter.NewAdapterByDB(db) 76 | // m, err := model.NewModelFromString(` 77 | // [request_definition] 78 | // r = sub, obj, act 79 | 80 | // [policy_definition] 81 | // p = sub, obj, act 82 | 83 | // [policy_effect] 84 | // e = some(where (p.eft == allow)) 85 | 86 | // [matchers] 87 | // m = r.sub == p.sub && r.obj == p.obj && r.act == p.act || r.sub == "iguidao" 88 | // `) 89 | // if err != nil { 90 | // logger.Error("error: model: ", err) 91 | // } 92 | 93 | // Enforcer, err = casbin.NewEnforcer(m, a) 94 | // if err != nil { 95 | // logger.Error("error: enforcer: ", err) 96 | // } 97 | // // 开启权限认证日志 98 | // Enforcer.EnableLog(true) 99 | // // Load the policy from DB. 100 | // err = Enforcer.LoadPolicy() 101 | // if err != nil { 102 | // logger.Error("loadPolicy error") 103 | // panic(err) 104 | // } 105 | 106 | // } 107 | -------------------------------------------------------------------------------- /src/middleware/casbin/ruleop.go: -------------------------------------------------------------------------------- 1 | package casbin 2 | 3 | import ( 4 | "github.com/iguidao/redis-manager/src/middleware/logger" 5 | "github.com/iguidao/redis-manager/src/middleware/mysql" 6 | ) 7 | 8 | func RuleCheck(identity, path, method string) bool { 9 | // 简单认证 10 | res, err := Enforcer.Enforce(identity, path, method) 11 | if err != nil { 12 | return false 13 | } 14 | return res 15 | } 16 | 17 | func RuleAdd(identity, path, method string) bool { 18 | res, err := Enforcer.AddPolicy(identity, path, method) 19 | if err != nil { 20 | logger.Error("create new rule error ", err) 21 | return false 22 | } 23 | return res 24 | } 25 | 26 | func RuleDel(identity, path, method string) bool { 27 | res, err := Enforcer.RemovePolicy(identity, path, method) 28 | if err != nil { 29 | return false 30 | } 31 | return res 32 | } 33 | 34 | func RuleGet() (casbinrule []CasbinRule) { 35 | mysql.DB.DB.Find(&casbinrule) 36 | // mysql.DB.DB.Offset(page).Limit(size).Find(&casbinrule) 37 | return 38 | } 39 | -------------------------------------------------------------------------------- /src/middleware/cluster/cluster.go: -------------------------------------------------------------------------------- 1 | package cluster 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | 7 | "github.com/iguidao/redis-manager/src/middleware/logger" 8 | "github.com/iguidao/redis-manager/src/middleware/mysql" 9 | ) 10 | 11 | func WriteCluster(clusterid int, v string) { 12 | var flags string 13 | var masterid string 14 | var linkstate string 15 | var slotrange string 16 | var slotnumber int 17 | nodelist := strings.Split(v, " ") 18 | nodeid := nodelist[0] 19 | nodeaddress := strings.Split(nodelist[1], "@") 20 | ip := strings.Split(nodeaddress[0], ":")[0] 21 | pord := strings.Split(nodeaddress[0], ":")[1] 22 | flags_info := nodelist[2] 23 | flags_list := strings.Split(flags_info, ",") 24 | if len(flags_list) == 1 { 25 | if flags_list[0] == "master" { 26 | flags = "master" 27 | linkstate = nodelist[7] 28 | slotrange = nodelist[8] 29 | slotnumber = getslotnumber(slotrange) 30 | } else { 31 | flags = "slave" 32 | masterid = nodelist[3] 33 | } 34 | } else { 35 | flags = flags_list[1] 36 | if flags == "master" { 37 | linkstate = nodelist[7] 38 | slotrange = nodelist[8] 39 | slotnumber = getslotnumber(slotrange) 40 | } else { 41 | masterid = nodelist[3] 42 | } 43 | } 44 | id, ok := mysql.DB.AddClusterNode(nodeid, ip, pord, flags, masterid, linkstate, slotrange, clusterid, slotnumber) 45 | if ok { 46 | logger.Info("write ", ip, " redis to mysql ok: ", id, " nodeid: ", nodeid) 47 | } else { 48 | logger.Error("write ", ip, " redis to mysql false: ", id, " nodeid: ", nodeid) 49 | } 50 | } 51 | 52 | func getslotnumber(slotrange string) int { 53 | var err error 54 | var start, end, slot int 55 | num := strings.Split(slotrange, "-") 56 | start, err = strconv.Atoi(num[0]) 57 | if err != nil { 58 | return slot 59 | } 60 | end, err = strconv.Atoi(num[1]) 61 | if err != nil { 62 | return slot 63 | } 64 | slot = end - start + 1 65 | return slot 66 | } 67 | -------------------------------------------------------------------------------- /src/middleware/cluster/nodes.go: -------------------------------------------------------------------------------- 1 | package cluster 2 | 3 | import ( 4 | "github.com/iguidao/redis-manager/src/middleware/model" 5 | "github.com/iguidao/redis-manager/src/middleware/mysql" 6 | ) 7 | 8 | func ClusterConvergeTree(node mysql.ClusterNode) *model.ClusterNodeTables { 9 | root := &model.ClusterNodeTables{ 10 | CreateTime: node.CreatedAt, 11 | CluserId: node.CluserId, 12 | Flags: node.Flags, 13 | Address: node.Ip + ":" + node.Port, 14 | LinkState: node.LinkState, 15 | RunStatus: node.RunStatus, 16 | NodeId: node.NodeId, 17 | SlotNumber: node.SlotNumber, 18 | SlotRange: node.SlotRange, 19 | } 20 | return root 21 | } 22 | func ClusterUpdateTree(mlist []*model.ClusterNodeTables, node mysql.ClusterNode) []*model.ClusterNodeTables { 23 | var res []*model.ClusterNodeTables 24 | for _, v := range mlist { 25 | if v.NodeId == node.MasterId { 26 | v.Children = append(v.Children, ClusterConvergeTree(node)) 27 | res = append(res, v) 28 | } else { 29 | res = append(res, v) 30 | } 31 | } 32 | return res 33 | } 34 | -------------------------------------------------------------------------------- /src/middleware/cosop/cosop.go: -------------------------------------------------------------------------------- 1 | package cosop 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "net/url" 7 | "os" 8 | 9 | "github.com/iguidao/redis-manager/src/middleware/logger" 10 | "github.com/iguidao/redis-manager/src/middleware/model" 11 | "github.com/iguidao/redis-manager/src/middleware/mysql" 12 | "github.com/tencentyun/cos-go-sdk-v5" 13 | ) 14 | 15 | const ( 16 | DefalutDisplayCount = 20 17 | ) 18 | 19 | func CosGet(srcname, dstname string) bool { 20 | AccessKey := mysql.DB.GetOneCfgValue(model.TXCOSACCESSKEY) 21 | AccessKeyID := mysql.DB.GetOneCfgValue(model.TXCOSACCESSKEYID) 22 | EndpointPub := mysql.DB.GetOneCfgValue(model.TXCOSENDPOINTPUB) 23 | u, _ := url.Parse(EndpointPub) 24 | b := &cos.BaseURL{BucketURL: u} 25 | 26 | client := cos.NewClient(b, &http.Client{ 27 | Transport: &cos.AuthorizationTransport{ 28 | SecretID: AccessKeyID, 29 | SecretKey: AccessKey, 30 | }, 31 | }) 32 | if _, err := os.Stat(dstname); err == nil { 33 | logger.Error("Not download because file exists: ", dstname) 34 | return false 35 | } 36 | rsp, err := client.Object.Head(context.Background(), srcname, nil) 37 | 38 | if err != nil { 39 | if rsp.StatusCode == 404 { 40 | logger.Error("file not found on cos...") 41 | } else { 42 | logger.Error("get object meta data failed ,err:", err.Error()) 43 | } 44 | return false 45 | } 46 | rsp, err = client.Object.GetToFile(context.Background(), srcname, dstname, nil) 47 | if err != nil { 48 | if rsp.StatusCode == 404 { 49 | logger.Error("download error,file not found,error:", err) 50 | } else { 51 | logger.Error("download error,", err) 52 | } 53 | return false 54 | } 55 | return true 56 | } 57 | -------------------------------------------------------------------------------- /src/middleware/httpapi/apistruct.go: -------------------------------------------------------------------------------- 1 | package httpapi 2 | -------------------------------------------------------------------------------- /src/middleware/httpapi/httpapi.go: -------------------------------------------------------------------------------- 1 | package httpapi 2 | 3 | import ( 4 | "bytes" 5 | "io/ioutil" 6 | "net/http" 7 | "time" 8 | ) 9 | 10 | func PostJson(url string, jsonStr []byte, HeaderData map[string]string) (bool, string) { 11 | 12 | req, _ := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr)) 13 | 14 | if HeaderData != nil { 15 | for headerkey, headervalue := range HeaderData { 16 | // req.Header.Set("Content-Type", "application/json") 17 | req.Header.Set(headerkey, headervalue) 18 | } 19 | } 20 | client := &http.Client{Timeout: 10000 * time.Millisecond} 21 | resp, err := client.Do(req) 22 | if err != nil { 23 | errinfo := "Post请求失败:" + err.Error() 24 | return false, errinfo 25 | } 26 | if resp.StatusCode != 200 { 27 | errinfo := "Get请求失败,状态码:" + resp.Status 28 | return false, errinfo 29 | } 30 | defer resp.Body.Close() 31 | 32 | // statuscode := resp.StatusCode 33 | // hea := resp.Header 34 | body, _ := ioutil.ReadAll(resp.Body) 35 | return true, string(body) 36 | 37 | } 38 | 39 | func GetDefault(url string, UriData map[string]string, HeaderData map[string]string) (bool, string) { 40 | req, _ := http.NewRequest("GET", url, nil) 41 | if HeaderData != nil { 42 | for headerkey, headervalue := range HeaderData { 43 | // req.Header.Set("Content-Type", "application/json") 44 | req.Header.Set(headerkey, headervalue) 45 | } 46 | } 47 | if UriData != nil { 48 | q := req.URL.Query() 49 | for urikey, urivalue := range UriData { 50 | q.Add(urikey, urivalue) 51 | } 52 | req.URL.RawQuery = q.Encode() 53 | } 54 | 55 | client := &http.Client{Timeout: 10000 * time.Millisecond} 56 | resp, err := client.Do(req) 57 | if err != nil { 58 | errinfo := "Get请求失败" + err.Error() 59 | return false, errinfo 60 | } 61 | if resp.StatusCode != 200 { 62 | errinfo := "Get请求失败,状态码:" + resp.Status 63 | return false, errinfo 64 | } 65 | defer resp.Body.Close() 66 | // statuscode := resp.StatusCode 67 | // hea := resp.Header 68 | body, _ := ioutil.ReadAll(resp.Body) 69 | return true, string(body) 70 | } 71 | func PutDefault(url string, UriData map[string]string, HeaderData map[string]string) (bool, string) { 72 | req, _ := http.NewRequest("PUT", url, nil) 73 | if HeaderData != nil { 74 | for headerkey, headervalue := range HeaderData { 75 | // req.Header.Set("Content-Type", "application/json") 76 | req.Header.Set(headerkey, headervalue) 77 | } 78 | } 79 | if UriData != nil { 80 | q := req.URL.Query() 81 | for urikey, urivalue := range UriData { 82 | q.Add(urikey, urivalue) 83 | } 84 | req.URL.RawQuery = q.Encode() 85 | } 86 | 87 | client := &http.Client{Timeout: 10000 * time.Millisecond} 88 | resp, err := client.Do(req) 89 | if err != nil { 90 | errinfo := "Put请求失败" + err.Error() 91 | return false, errinfo 92 | } 93 | if resp.StatusCode != 200 { 94 | errinfo := "Put请求失败,状态码:" + resp.Status 95 | return false, errinfo 96 | } 97 | defer resp.Body.Close() 98 | // statuscode := resp.StatusCode 99 | // hea := resp.Header 100 | body, _ := ioutil.ReadAll(resp.Body) 101 | return true, string(body) 102 | } 103 | 104 | func DeleteDefault(url string, UriData map[string]string, HeaderData map[string]string) (bool, string) { 105 | req, _ := http.NewRequest("DELETE", url, nil) 106 | if HeaderData != nil { 107 | for headerkey, headervalue := range HeaderData { 108 | // req.Header.Set("Content-Type", "application/json") 109 | req.Header.Set(headerkey, headervalue) 110 | } 111 | } 112 | if UriData != nil { 113 | q := req.URL.Query() 114 | for urikey, urivalue := range UriData { 115 | q.Add(urikey, urivalue) 116 | } 117 | req.URL.RawQuery = q.Encode() 118 | } 119 | 120 | client := &http.Client{Timeout: 10000 * time.Millisecond} 121 | resp, err := client.Do(req) 122 | if err != nil { 123 | errinfo := "DELETE请求失败" + err.Error() 124 | return false, errinfo 125 | } 126 | if resp.StatusCode != 200 { 127 | errinfo := "DELETE请求失败,状态码:" + resp.Status 128 | return false, errinfo 129 | } 130 | defer resp.Body.Close() 131 | // statuscode := resp.StatusCode 132 | // hea := resp.Header 133 | body, _ := ioutil.ReadAll(resp.Body) 134 | return true, string(body) 135 | } 136 | -------------------------------------------------------------------------------- /src/middleware/jwt/jwt.go: -------------------------------------------------------------------------------- 1 | package jwt 2 | 3 | import ( 4 | "net/http" 5 | "strings" 6 | "time" 7 | 8 | "github.com/iguidao/redis-manager/src/hsc" 9 | "github.com/iguidao/redis-manager/src/middleware/casbin" 10 | "github.com/iguidao/redis-manager/src/middleware/util" 11 | 12 | "github.com/gin-gonic/gin" 13 | ) 14 | 15 | func JWT() gin.HandlerFunc { 16 | return func(c *gin.Context) { 17 | var code int 18 | var token string 19 | var username string 20 | var usertype string 21 | var userid int 22 | Result := make(map[string]interface{}) 23 | code = hsc.SUCCESS 24 | urlpath := c.Request.URL.Path 25 | method := c.Request.Method 26 | auth := c.Request.Header.Get("Authorization") 27 | if strings.Contains(auth, "Bearer ") { 28 | token = strings.Split(auth, "Bearer ")[1] 29 | } 30 | if token == "" { 31 | Result["result"] = "没有Authorization" 32 | code = hsc.NO_LOGIN 33 | } else { 34 | claims, err := util.ParseToken(token) 35 | if err != nil { 36 | Result["result"] = "Token鉴权失败" 37 | code = hsc.ERROR_AUTH_CHECK_TOKEN_FAIL 38 | } else if time.Now().Unix() > claims.ExpiresAt { 39 | Result["result"] = "Token已超时" 40 | code = hsc.ERROR_AUTH_CHECK_TOKEN_TIMEOUT 41 | } else { 42 | result := casbin.RuleCheck(claims.UserType, urlpath, method) 43 | if !result { 44 | code = hsc.WARN_NOT_PROMISE_RULE 45 | Result["result"] = "权限不够呀,找管理员开下权限!" 46 | } else { 47 | username = claims.UserName 48 | usertype = claims.UserType 49 | userid = claims.UserId 50 | } 51 | } 52 | } 53 | if code == hsc.WARN_NOT_PROMISE_RULE { 54 | c.JSON(http.StatusOK, gin.H{ 55 | "errorCode": code, 56 | "msg": hsc.GetMsg(code), 57 | "data": Result, 58 | }) 59 | c.Abort() 60 | return 61 | } 62 | if code != hsc.SUCCESS { 63 | c.JSON(http.StatusUnauthorized, gin.H{ 64 | "errorCode": code, 65 | "msg": hsc.GetMsg(code), 66 | "data": Result, 67 | }) 68 | c.Abort() 69 | return 70 | } 71 | c.Set("UserName", username) 72 | c.Set("UserType", usertype) 73 | c.Set("UserId", userid) 74 | c.Next() 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/middleware/logger/logger.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | 8 | "github.com/iguidao/redis-manager/src/cfg" 9 | 10 | "go.uber.org/zap" 11 | "go.uber.org/zap/zapcore" 12 | "gopkg.in/natefinch/lumberjack.v2" 13 | ) 14 | 15 | // error logger 16 | var ErrorLogger *zap.SugaredLogger 17 | 18 | func SetupLogger() *zap.SugaredLogger { 19 | err := os.Mkdir("./logs/", os.ModePerm) 20 | if err != nil && !strings.Contains(err.Error(), "exists") { 21 | fmt.Println("create logs dir err, ", err.Error()) 22 | } 23 | fileName := "./logs/" + cfg.Get_Info_String("logapppath") 24 | syncWriter := zapcore.AddSync(&lumberjack.Logger{ 25 | Filename: fileName, 26 | MaxSize: 1024, 27 | LocalTime: true, 28 | Compress: true, 29 | }) 30 | encoder := zap.NewDevelopmentEncoderConfig() 31 | encoder.EncodeTime = zapcore.ISO8601TimeEncoder 32 | 33 | var level zapcore.Level 34 | level = zap.DebugLevel 35 | core := zapcore.NewCore( 36 | zapcore.NewConsoleEncoder(encoder), 37 | zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), 38 | syncWriter), 39 | level, 40 | ) 41 | 42 | logger := zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1)) 43 | ErrorLogger = logger.Sugar() 44 | return ErrorLogger 45 | } 46 | 47 | func Debug(args ...interface{}) { 48 | ErrorLogger.Debug(args...) 49 | } 50 | 51 | func Debugf(template string, args ...interface{}) { 52 | ErrorLogger.Debugf(template, args...) 53 | } 54 | 55 | func Info(args ...interface{}) { 56 | ErrorLogger.Info(args...) 57 | } 58 | 59 | func Infof(template string, args ...interface{}) { 60 | ErrorLogger.Infof(template, args...) 61 | } 62 | 63 | func Warn(args ...interface{}) { 64 | ErrorLogger.Warn(args...) 65 | } 66 | 67 | func Warnf(template string, args ...interface{}) { 68 | ErrorLogger.Warnf(template, args...) 69 | } 70 | 71 | func Error(args ...interface{}) { 72 | ErrorLogger.Error(args...) 73 | } 74 | 75 | func Errorf(template string, args ...interface{}) { 76 | ErrorLogger.Errorf(template, args...) 77 | } 78 | 79 | func DPanic(args ...interface{}) { 80 | ErrorLogger.DPanic(args...) 81 | } 82 | 83 | func DPanicf(template string, args ...interface{}) { 84 | ErrorLogger.DPanicf(template, args...) 85 | } 86 | 87 | func Panic(args ...interface{}) { 88 | ErrorLogger.Panic(args...) 89 | } 90 | 91 | func Panicf(template string, args ...interface{}) { 92 | ErrorLogger.Panicf(template, args...) 93 | } 94 | 95 | func Fatal(args ...interface{}) { 96 | ErrorLogger.Fatal(args...) 97 | } 98 | 99 | func Fatalf(template string, args ...interface{}) { 100 | ErrorLogger.Fatalf(template, args...) 101 | } 102 | -------------------------------------------------------------------------------- /src/middleware/model/aliresult.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | // Region ali result 4 | type AliRegion struct { 5 | RequestId string `json:"RequestId"` 6 | RegionIds AliRegionRegionIds `json:"RegionIds"` 7 | } 8 | type AliRegionRegionIds struct { 9 | KVStoreRegion []AliRegionRegionIdsKVStoreRegion `json:"KVStoreRegion"` 10 | } 11 | type AliRegionRegionIdsKVStoreRegion struct { 12 | RegionId string `json:"RegionId"` 13 | RegionEndpoint string `json:"RegionEndpoint"` 14 | LocalName string `json:"LocalName"` 15 | } 16 | 17 | // redis ali result 18 | type AliRedis struct { 19 | RequestId string `json:"RequestId"` 20 | TotalCount int `json:"TotalCount"` 21 | PageSize int `json:"PageSize"` 22 | PageNumber int `json:"PageNumber"` 23 | Instances AliRedisInstances `json:"RegionIds"` 24 | } 25 | type AliRedisInstances struct { 26 | KVStoreInstance []AliRedisInstancesKVStoreInstance `json:"KVStoreInstance"` 27 | } 28 | type AliRedisInstancesKVStoreInstance struct { 29 | Connections int `json:"Connections"` 30 | EndTime string `json:"EndTime"` 31 | ResourceGroupId string `json:"ResourceGroupId"` 32 | EditionType string `json:"EditionType"` 33 | Config string `json:"Config"` 34 | Port int `json:"Port"` 35 | GlobalInstanceId string `json:"GlobalInstanceId"` 36 | HasRenewChangeOrder string `json:"HasRenewChangeOrder"` 37 | ConnectionDomain string `json:"ConnectionDomain"` 38 | Capacity int `json:"Capacity"` 39 | QPS int `json:"QPS"` 40 | NetworkType string `json:"NetworkType"` 41 | InstanceStatus string `json:"InstanceStatus"` 42 | PackageType string `json:"PackageType"` 43 | Bandwidth int `json:"Bandwidth"` 44 | InstanceType string `json:"InstanceType"` 45 | ArchitectureType string `json:"ArchitectureType"` 46 | EngineVersion string `json:"EngineVersion"` 47 | UserName string `json:"UserName"` 48 | ZoneId string `json:"ZoneId"` 49 | InstanceId string `json:"InstanceId"` 50 | CreateTime string `json:"CreateTime"` 51 | VSwitchId string `json:"VSwitchId"` 52 | InstanceClass string `json:"InstanceClass"` 53 | IsRds bool `json:"IsRds"` 54 | InstanceName string `json:"InstanceName"` 55 | VpcId string `json:"VpcId"` 56 | ChargeType string `json:"ChargeType"` 57 | NodeType string `json:"NodeType"` 58 | RegionId string `json:"RegionId"` 59 | ShardCount int `json:"ShardCount"` 60 | } 61 | -------------------------------------------------------------------------------- /src/middleware/model/cfg.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | var ( 4 | DefaultName = make(map[string]string) 5 | CC = "custom_config" // 自行配置的key 6 | CN = "用户自定义默认key" // 自定义key备注note 7 | TXSECRETID = "tx_secretid" // 腾讯SECRETID,账号需要开启[QCloudFinanceFullAccess、QcloudRedisFullAccess、QcloudMonitorFullAccess、QcloudDBBRAINFullAccess]权限 8 | TXSECRETKEY = "tx_secretkey" // 腾讯SECRETKEY 9 | TXAPIURL = "tx_redis_api_url" // 腾讯APIURL 10 | TXCOSACCESSKEY = "tx_cos_accesskey" // 腾讯COS的ACCESSKEY 11 | TXCOSACCESSKEYID = "tx_cos_accesskeyid" // 腾讯COS的ACCESSKEYID 12 | TXCOSENDPOINTPUB = "tx_cos_endpointpub" // 腾讯COS的ENDPOINTPUB 13 | ALIAPIURL = "ali_redis_api_url" // 阿里PIURL 14 | ALIACCESSKEYID = "ali_accesskeyid" // 阿里accessKeyId 15 | ALIALIACCESSKEYSECRET = "ali_accesskeysecret" // 阿里accessKeySecret 16 | BGSAVECOMMAND = "redis_bgsave" // bgsave命令的别名 17 | CLOUDREFRESH = "cloud_refresh" // 云redis定时更新时间,使用cron格式 18 | BOARDCODIS = "board_codis" // 是否启动自建codis 19 | BOARDTXREDIS = "board_txredis" // 是否启动腾讯redis 20 | BOARDALIREDIS = "board_aliredis" // 是否启动阿里redis 21 | BOARDCLUSTER = "board_cluster" // 是否启动自建redis 22 | CfgDefault = [...]string{TXSECRETID, TXSECRETKEY, TXAPIURL, TXCOSACCESSKEY, TXCOSACCESSKEYID, TXCOSENDPOINTPUB} // 默认key列表 23 | ) 24 | 25 | func init() { 26 | DefaultName[TXSECRETID] = "腾讯SECRETID" 27 | DefaultName[TXSECRETKEY] = "腾讯TXSECRETKEY" 28 | DefaultName[TXAPIURL] = "腾讯REDIS的APIURL" 29 | DefaultName[TXCOSACCESSKEY] = "腾讯COS的ACCESSKEY" 30 | DefaultName[TXCOSACCESSKEYID] = "腾讯COS的ACCESSKEYID" 31 | DefaultName[TXCOSENDPOINTPUB] = "腾讯COS的ENDPOINTPUB" 32 | DefaultName[BGSAVECOMMAND] = "Redis命令bgsave别名" 33 | DefaultName[CLOUDREFRESH] = "云redis定时更新时间" 34 | DefaultName[ALIAPIURL] = "阿里REDIS的APIURL" 35 | DefaultName[ALIACCESSKEYID] = "阿里accessKeyId" 36 | DefaultName[ALIALIACCESSKEYSECRET] = "阿里accessKeySecret" 37 | } 38 | -------------------------------------------------------------------------------- /src/middleware/model/clusterresult.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "time" 4 | 5 | type ClusterNodeTables struct { 6 | CreateTime time.Time 7 | CluserId int 8 | NodeId string 9 | Address string 10 | Flags string 11 | LinkState string 12 | RunStatus bool 13 | SlotRange string 14 | SlotNumber int 15 | Children []*ClusterNodeTables 16 | } 17 | -------------------------------------------------------------------------------- /src/middleware/model/codisv1.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | // codis的proxy和server操作 4 | type CodisChangeNode struct { 5 | Curl string `json:"curl"` 6 | ClusterName string `json:"cluster_name"` 7 | AddProxy string `json:"add_proxy"` 8 | AddServer string `json:"add_server"` 9 | DelProxy int `json:"del_proxy"` 10 | DelGroup int `json:"del_group"` 11 | OpType string `json:"op_type"` 12 | } 13 | -------------------------------------------------------------------------------- /src/middleware/model/path.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | var ( 4 | DefaultPath = make(map[string]string) 5 | PATHCFG = "/redis-manager/cfg/v1" 6 | PATHBOARD = "/redis-manager/board/v1" 7 | PATHHISTORY = "/redis-manager/ophistory/v1" 8 | PATHCODIS = "/redis-manager/codis/v1" 9 | PATHCLOUD = "/redis-manager/cloud/v1" 10 | PATHCLUSTER = "/redis-manager/cluster/v1" 11 | PATHCLI = "/redis-manager/cli/v1" 12 | PATHUSER = "/redis-manager/user/v1" 13 | PATHRULE = "/redis-manager/rule/v1" 14 | PATHAUTH = "/redis-manager/auth/v1" 15 | DefaultMethod = make(map[string]string) 16 | METHODGET = "GET" 17 | METHODPOST = "POST" 18 | METHODDELETE = "DELETE" 19 | METHODPUT = "PUT" 20 | ) 21 | 22 | func init() { 23 | DefaultPath[PATHCFG+"/*"] = "系统配置页面权限" 24 | DefaultPath[PATHBOARD+"/*"] = "概览页面权限" 25 | DefaultPath[PATHHISTORY+"/*"] = "历史记录页面权限" 26 | DefaultPath[PATHCODIS+"/*"] = "Redis集群/Codis页面权限" 27 | DefaultPath[PATHCLOUD+"/*"] = "Redis集群/腾讯和阿里Redis页面权限" 28 | DefaultPath[PATHCLUSTER+"/*"] = "Redis集群/Cluster页面权限" 29 | DefaultPath[PATHCLI+"/*"] = "数据查询页面权限" 30 | DefaultPath[PATHUSER+"/*"] = "用户管理/用户列表页面权限" 31 | DefaultPath[PATHRULE+"/*"] = "用户管理/权限管理页面权限" 32 | DefaultPath[PATHAUTH+"/*"] = "用户修改密码权限" 33 | DefaultMethod[METHODGET] = "读权限" 34 | DefaultMethod[METHODPOST] = "写权限" 35 | DefaultMethod[METHODDELETE] = "删除权限" 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/middleware/model/user.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | var ( 4 | DefaultUser = make(map[string]string) 5 | USERTYPEADMIN = "admin" //管理员身份 6 | USERTYPEVISITOR = "visitor" //访客身份 7 | USERTYPEMEMBER = "member" //会员身份 8 | UserDefault = [...]string{USERTYPEADMIN, USERTYPEVISITOR, USERTYPEMEMBER} //列出员工身份 9 | ) 10 | 11 | func init() { 12 | DefaultUser[USERTYPEADMIN] = "管理员" 13 | DefaultUser[USERTYPEVISITOR] = "访客" 14 | DefaultUser[USERTYPEMEMBER] = "会员" 15 | } 16 | -------------------------------------------------------------------------------- /src/middleware/mysql/history.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import "github.com/iguidao/redis-manager/src/middleware/logger" 4 | 5 | func (m *MySQL) GetAllHistory() []OpHistory { 6 | var ophistory []OpHistory 7 | m.Find(&ophistory) 8 | return ophistory 9 | } 10 | 11 | // add cluster 12 | func (m *MySQL) AddHistory(userid int, opinfo, opparams string) (int, bool) { 13 | addcluster := &OpHistory{ 14 | UserId: userid, 15 | OpInfo: opinfo, 16 | OpParams: opparams, 17 | } 18 | result := m.Create(&addcluster) 19 | if result.Error != nil { 20 | logger.Error("Mysql add history error:", result.Error) 21 | return 0, false 22 | } 23 | return addcluster.ID, true 24 | // return gdarticle.ID.String(), true 25 | } 26 | -------------------------------------------------------------------------------- /src/middleware/mysql/initial.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import ( 4 | "github.com/iguidao/redis-manager/src/middleware/logger" 5 | "gorm.io/driver/mysql" 6 | "gorm.io/gorm" 7 | ) 8 | 9 | // MySQL refrence a mysql db 10 | type MySQL struct { 11 | *gorm.DB 12 | } 13 | 14 | // DB as the mysql client 15 | var DB MySQL 16 | 17 | // Connect create db connection 18 | func Connect(dsn string) { 19 | db, err := gorm.Open(mysql.New(mysql.Config{ 20 | // DSN: "gorm:gorm@tcp(127.0.0.1:3306)/gorm?charset=utf8&parseTime=True&loc=Local", // DSN data source name 21 | DSN: dsn, 22 | DefaultStringSize: 512, // string 类型字段的默认长度 23 | DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持 24 | DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引 25 | DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列 26 | SkipInitializeWithVersion: false, // 根据当前 MySQL 版本自动配置 27 | 28 | }), &gorm.Config{}) 29 | if err != nil { 30 | logger.Error("Mysql Cannot open mysql database: ", err.Error()) 31 | panic(err) 32 | } 33 | DB = MySQL{db} 34 | 35 | } 36 | 37 | // Migrate the db schema 38 | func Migrate() { 39 | logger.Info("Mysql start check data table exists...") 40 | if !DB.Migrator().HasTable(&UserInfo{}) { 41 | logger.Info("Mysql start create data table user migrate data schemas...") 42 | DB.AutoMigrate(&UserInfo{}) 43 | logger.Info("Mysql Add User to account:iguidao , password:123456") 44 | DB.CreatUser("iguidao", "iguidao@iguidao.com", "tXfP0JhWJgtaNQc/DcHF78yeI73RRR+35uFNDx4cIVA=") 45 | } 46 | if !DB.Migrator().HasTable(&UserGroup{}) { 47 | logger.Info("Mysql start create data table user_group migrate data schemas...") 48 | DB.AutoMigrate(&UserGroup{}) 49 | } 50 | if !DB.Migrator().HasTable(&GroupContain{}) { 51 | logger.Info("Mysql start create data table group_contain migrate data schemas...") 52 | DB.AutoMigrate(&GroupContain{}) 53 | } 54 | if !DB.Migrator().HasTable(&CloudInfo{}) { 55 | logger.Info("Mysql start create data table cloud_info migrate data schemas...") 56 | DB.AutoMigrate(&CloudInfo{}) 57 | } 58 | if !DB.Migrator().HasTable(&ClusterInfo{}) { 59 | logger.Info("Mysql start create data table cluster_info migrate data schemas...") 60 | DB.AutoMigrate(&ClusterInfo{}) 61 | } 62 | if !DB.Migrator().HasTable(&ClusterNode{}) { 63 | logger.Info("Mysql start create data table redis_node migrate data schemas...") 64 | DB.AutoMigrate(&ClusterNode{}) 65 | } 66 | if !DB.Migrator().HasTable(&OpHistory{}) { 67 | logger.Info("Mysql start create data table ophistory migrate data schemas...") 68 | DB.AutoMigrate(&OpHistory{}) 69 | } 70 | if !DB.Migrator().HasTable(&CodisInfo{}) { 71 | logger.Info("Mysql start create data table CodisInfo migrate data schemas...") 72 | DB.AutoMigrate(&CodisInfo{}) 73 | } 74 | if !DB.Migrator().HasTable(&Rconfig{}) { 75 | logger.Info("Mysql start create data table Rconfig migrate data schemas...") 76 | DB.AutoMigrate(&Rconfig{}) 77 | } 78 | logger.Info("Mysql auto check data table done.") 79 | } 80 | -------------------------------------------------------------------------------- /src/middleware/mysql/mysqlstrcut.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import ( 4 | "time" 5 | 6 | "gorm.io/gorm" 7 | ) 8 | 9 | // base 10 | type Base struct { 11 | ID int `gorm:"primary_key"` 12 | CreatedAt time.Time `gorm:"not null"` 13 | UpdatedAt time.Time 14 | DeletedAt gorm.DeletedAt `gorm:"index"` 15 | //创建索引`sql:"index"` 16 | } 17 | 18 | // 用户 19 | type UserInfo struct { 20 | Base 21 | UserName string `gorm:"not null;index;unique"` 22 | Password string `gorm:"not null"` 23 | Email string `gorm:"type:varchar(255)"` 24 | UserType string `gorm:"not null;index;type:varchar(50)"` //admin 管理员;visitor 访客;staff 员工 25 | Enable bool // 0是封禁False,1是可登录True 26 | } 27 | 28 | // 用户组 29 | type UserGroup struct { 30 | Base 31 | GroupName string `gorm:"not null;index;unique"` 32 | GroupDescribe string `gorm:"type:varchar(255)"` 33 | GroupType string `gorm:"not null;index;type:varchar(50)"` //admin 管理组;visitor 访客组;staff 员工组 34 | } 35 | 36 | // 组与用户的关系 37 | type GroupContain struct { 38 | Base 39 | GroupId int `gorm:"not null;index"` 40 | UserId int `gorm:"not null;index"` 41 | } 42 | 43 | // 集群信息 44 | type ClusterInfo struct { 45 | Base 46 | Name string `gorm:"not null;index;unique"` 47 | Nodes string `gorm:"type:varchar(255)"` //ip:port,ip:port 48 | Password string `gorm:"type:varchar(255)"` 49 | } 50 | 51 | // node信息 52 | type ClusterNode struct { 53 | Base 54 | CluserId int `gorm:"not null;index"` //集群ID 55 | NodeId string `gorm:"type:varchar(50);unique"` //node的ID 56 | Ip string `gorm:"type:varchar(50)"` //node的IP 57 | Port string `gorm:"type:varchar(25)"` //node的端口 58 | Flags string `gorm:"type:varchar(50)"` //node的身份 59 | MasterId string `gorm:"type:varchar(50)"` //如果是从的话master的ID 60 | LinkState string `gorm:"type:varchar(50)"` //链接状态 61 | RunStatus bool `gorm:"type:varchar(25)"` //运行状态 62 | SlotRange string `gorm:"type:varchar(50)"` //slot区间 63 | SlotNumber int `gorm:"type:varchar(25)"` //solt个数 64 | } 65 | 66 | // cloud redis信息 67 | type CloudInfo struct { 68 | Base 69 | Cloud string `gorm:"type:varchar(10)"` //云厂商 70 | InstanceId string `gorm:"not null;index;unique"` //实例ID 71 | InstanceName string `gorm:"type:varchar(100)"` //实例名称 72 | PrivateIp string `gorm:"type:varchar(20)"` //内网IP 73 | Port int `gorm:"type:varchar(10)"` //端口 74 | Region string `gorm:"type:varchar(20)"` //region 75 | Createtime string `gorm:"type:varchar(20)"` //创建时间 76 | Size int `gorm:"type:varchar(10)"` //实例大小 77 | InstanceStatus string `gorm:"type:varchar(10)"` //实例状态 78 | RedisShardSize int `gorm:"type:varchar(10)"` //分片大小 79 | RedisShardNum int `gorm:"type:varchar(10)"` //分练数量 80 | RedisReplicasNum int `gorm:"type:varchar(10)"` //副本个数 81 | NoAuth bool `gorm:"type:varchar(10)"` //是否需要密码 82 | PublicIp string `gorm:"type:varchar(20)"` //外网IP 83 | Password string `gorm:"type:varchar(50)"` //密码 84 | } 85 | 86 | // codis信息 87 | type CodisInfo struct { 88 | Base 89 | Curl string `gorm:"not null;index;unique"` 90 | Cname string `gorm:"type:varchar(50)"` 91 | } 92 | 93 | // config信息 94 | type Rconfig struct { 95 | Base 96 | Name string `gorm:"type:varchar(255)"` 97 | Key string `gorm:"not null:index:primary_key;unique"` 98 | Value string `gorm:"type:varchar(255)"` 99 | } 100 | 101 | // 操作历史 102 | type OpHistory struct { 103 | Base 104 | UserId int `gorm:"not null;index"` 105 | OpInfo string `gorm:"type:varchar(100)"` // 操作动作 106 | OpParams string `gorm:"type:text"` //操作参数属组或者对象 107 | } 108 | 109 | type Tabler interface { 110 | TableName() string 111 | } 112 | 113 | func (UserInfo) TableName() string { 114 | return "user_info" 115 | } 116 | 117 | func (UserGroup) TableName() string { 118 | return "user_group" 119 | } 120 | 121 | func (GroupContain) TableName() string { 122 | return "group_contain" 123 | } 124 | 125 | func (CloudInfo) TableName() string { 126 | return "cloud_info" 127 | } 128 | 129 | func (ClusterInfo) TableName() string { 130 | return "cluster_info" 131 | } 132 | func (CodisInfo) TableName() string { 133 | return "codis_info" 134 | } 135 | 136 | func (ClusterNode) TableName() string { 137 | return "cluster_node" 138 | } 139 | 140 | func (OpHistory) TableName() string { 141 | return "op_history" 142 | } 143 | 144 | func (Rconfig) TableName() string { 145 | return "rconfig" 146 | } 147 | -------------------------------------------------------------------------------- /src/middleware/mysql/opcfg.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import ( 4 | "github.com/iguidao/redis-manager/src/middleware/logger" 5 | ) 6 | 7 | // add cfg 8 | func (m *MySQL) AddCfg(name, key, value string) (int, bool) { 9 | addcfginfo := &Rconfig{ 10 | Key: key, 11 | Value: value, 12 | Name: name, 13 | } 14 | result := m.Create(&addcfginfo) 15 | if result.Error != nil { 16 | logger.Error("Mysql add cfg error:", result.Error) 17 | return 0, false 18 | } 19 | return addcfginfo.ID, true 20 | } 21 | func (m *MySQL) DelCfg(key string) bool { 22 | var cfg *Rconfig 23 | if err := m.Model(cfg).Where("`key` = ?", key).Delete(&cfg).Error; err != nil { 24 | logger.Error("Mysql del cfg error:", err) 25 | return false 26 | } 27 | return true 28 | } 29 | 30 | // update cfg 31 | func (m *MySQL) UpdateCfg(key, value string) bool { 32 | var cfg Rconfig 33 | result := m.Model(&cfg).Where("`key` = ?", key).Update("value", value) 34 | if result.Error != nil { 35 | logger.Error("Mysql result.Error: ", result.Error) 36 | } 37 | return result.Error == nil 38 | } 39 | 40 | // check cfg 41 | func (m *MySQL) ExistCfg(key string) bool { 42 | var cfg *Rconfig 43 | if err := m.Model(cfg).Where("`key` = ?", key).First(&cfg).Error; err != nil { 44 | logger.Error("Mysql exist cfg error:", err) 45 | return false 46 | } 47 | return true 48 | } 49 | 50 | // get all cfg 51 | func (m *MySQL) GetAllCfg() []Rconfig { 52 | var cfg []Rconfig 53 | m.Find(&cfg) 54 | return cfg 55 | } 56 | 57 | // get one cfg 58 | func (m *MySQL) GetOneCfg(key string) Rconfig { 59 | var cfg Rconfig 60 | m.Where("`key` = ?", key).First(&cfg) 61 | return cfg 62 | } 63 | 64 | // get one cfg value 65 | func (m *MySQL) GetOneCfgValue(key string) string { 66 | var cfg Rconfig 67 | m.Where("`key` = ?", key).First(&cfg) 68 | return cfg.Value 69 | } 70 | -------------------------------------------------------------------------------- /src/middleware/mysql/opcluster.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import "github.com/iguidao/redis-manager/src/middleware/logger" 4 | 5 | // get all cluster 6 | func (m *MySQL) GetAllCluster() []ClusterInfo { 7 | var clusters []ClusterInfo 8 | m.Find(&clusters) 9 | return clusters 10 | } 11 | func (m *MySQL) GetClusterNumber() int64 { 12 | var clusters []ClusterInfo 13 | var count int64 14 | m.Model(clusters).Find(&clusters).Count(&count) 15 | return count 16 | } 17 | func (m *MySQL) GetClusterAddress(id string) (string, string) { 18 | var clusterinfo *ClusterInfo 19 | m.Where("id = ?", id).First(&clusterinfo) 20 | return clusterinfo.Nodes, clusterinfo.Password 21 | } 22 | func (m *MySQL) GetClusterPassword(id string) string { 23 | var clusterinfo *ClusterInfo 24 | m.Where("id = ?", id).First(&clusterinfo) 25 | return clusterinfo.Password 26 | } 27 | 28 | // add cluster 29 | func (m *MySQL) AddCluster(name, nodes, password string) (int, bool) { 30 | addcluster := &ClusterInfo{ 31 | Name: name, 32 | Nodes: nodes, 33 | Password: password, 34 | } 35 | result := m.Create(&addcluster) 36 | if result.Error != nil { 37 | logger.Error("Mysql add cluster error:", result.Error) 38 | return 0, false 39 | } 40 | return addcluster.ID, true 41 | // return gdarticle.ID.String(), true 42 | } 43 | 44 | func (m *MySQL) GetClusterNode(cluster string) []ClusterNode { 45 | var nodes []ClusterNode 46 | m.Model(nodes).Where("cluser_id = ?", cluster).Find(&nodes) 47 | return nodes 48 | } 49 | func (m *MySQL) GetClusterNodeMaster(cluster string) []ClusterNode { 50 | var nodes []ClusterNode 51 | m.Model(nodes).Where("cluser_id = ? AND flags = ?", cluster, "master").Find(&nodes) 52 | return nodes 53 | } 54 | 55 | func (m *MySQL) GetClusterNodeMasterAddress(nodeid string) string { 56 | var nodes *ClusterNode 57 | m.Where("node_id = ?", nodeid).First(&nodes) 58 | return nodes.Ip + ":" + nodes.Port 59 | } 60 | func (m *MySQL) GetClusterNodeSlaverAddress(nodeid string) string { 61 | var nodes *ClusterNode 62 | m.Where("master_id = ?", nodeid).First(&nodes) 63 | return nodes.Ip + ":" + nodes.Port 64 | } 65 | func (m *MySQL) AddClusterNode(nodeid, ip, port, flags, masterid, linkstate, slotrange string, clusterid, slotnumber int) (int, bool) { 66 | addnode := &ClusterNode{ 67 | CluserId: clusterid, 68 | NodeId: nodeid, 69 | Ip: ip, 70 | Port: port, 71 | Flags: flags, 72 | MasterId: masterid, 73 | LinkState: linkstate, 74 | SlotRange: slotrange, 75 | SlotNumber: slotnumber, 76 | } 77 | result := m.Create(&addnode) 78 | if result.Error != nil { 79 | logger.Error("Mysql add cluster node error:", result.Error) 80 | return 0, false 81 | } 82 | return addnode.ID, true 83 | // return gdarticle.ID.String(), true 84 | } 85 | -------------------------------------------------------------------------------- /src/middleware/mysql/opcodis.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import "github.com/iguidao/redis-manager/src/middleware/logger" 4 | 5 | // add codis 6 | 7 | func (m *MySQL) AddCodis(curl, cname string) (int, bool) { 8 | addcodisinfo := &CodisInfo{ 9 | Curl: curl, 10 | Cname: cname, 11 | } 12 | result := m.Create(&addcodisinfo) 13 | if result.Error != nil { 14 | logger.Error("Mysql add Codis error:", result.Error) 15 | return 0, false 16 | } 17 | return addcodisinfo.ID, true 18 | // return gdarticle.ID.String(), true 19 | } 20 | 21 | // get codis 22 | func (m *MySQL) GetAllCodis() []CodisInfo { 23 | var clusters []CodisInfo 24 | m.Find(&clusters) 25 | return clusters 26 | } 27 | 28 | func (m *MySQL) GetCodisNumber() int64 { 29 | var clusters []CodisInfo 30 | var count int64 31 | m.Find(&clusters).Count(&count) 32 | return count 33 | } 34 | -------------------------------------------------------------------------------- /src/middleware/mysql/opuser.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import ( 4 | "github.com/iguidao/redis-manager/src/middleware/logger" 5 | "github.com/iguidao/redis-manager/src/middleware/model" 6 | "gorm.io/gorm" 7 | ) 8 | 9 | func (m *MySQL) GetAllUser() []UserInfo { 10 | var user []UserInfo 11 | m.Find(&user) 12 | return user 13 | } 14 | 15 | func (m *MySQL) UserInfo(username string) UserInfo { 16 | var user UserInfo 17 | m.Where("user_name = ?", username).First(&user) 18 | return user 19 | } 20 | 21 | func (m *MySQL) FindUserPassword(ruser string) (user UserInfo, err error) { 22 | err = m.Where("user_name = ?", ruser).Find(&user).Error 23 | if err != nil && err == gorm.ErrRecordNotFound { 24 | logger.Error("Mysql Find user password error:", err) 25 | } 26 | return 27 | } 28 | 29 | func (m *MySQL) FindEmail(email string) bool { 30 | var user UserInfo 31 | if m.Where("email = ?", email).First(&user).RowsAffected == 0 { 32 | return false 33 | } 34 | return true 35 | } 36 | 37 | func (m *MySQL) FindUser(ruser string) bool { 38 | var user UserInfo 39 | if m.Where("user_name = ?", ruser).First(&user).RowsAffected == 0 { 40 | return false 41 | } 42 | return true 43 | } 44 | func (m *MySQL) UpdateUserPassword(username string, password string) bool { 45 | var user *UserInfo 46 | if err := m.Model(user).Where("user_name = ?", username).Update("password", password).Error; err != nil { 47 | logger.Error("Mysql update user password error: ", err) 48 | return false 49 | } 50 | return true 51 | } 52 | func (m *MySQL) UpdateUserType(username string, usertype string) bool { 53 | var user *UserInfo 54 | if err := m.Model(user).Where("user_name = ?", username).Update("user_type", usertype).Error; err != nil { 55 | logger.Error("Mysql update user type error: ", err) 56 | return false 57 | } 58 | return true 59 | } 60 | func (m *MySQL) ExistUserId(id int) bool { 61 | var user *UserInfo 62 | if err := m.Model(user).Where("id = ?", id).First(&user).Error; err != nil { 63 | logger.Error("Mysql exist user id error: ", err) 64 | return false 65 | } 66 | return true 67 | } 68 | func (m *MySQL) ExistUserName(username string) bool { 69 | var user *UserInfo 70 | if err := m.Model(user).Where("user_name = ?", username).First(&user).Error; err != nil { 71 | logger.Error("Mysql exist user name error: ", err) 72 | return false 73 | } 74 | return true 75 | } 76 | func (m *MySQL) GetUserType(userid int) string { 77 | var user *UserInfo 78 | m.Where("id = ?", userid).First(&user) 79 | return user.UserType 80 | } 81 | func (m *MySQL) DelUser(userid int) bool { 82 | var user *UserInfo 83 | if err := m.Model(user).Where("id = ?", userid).Delete(&user).Error; err != nil { 84 | logger.Error("Mysql del user error:", err) 85 | return false 86 | } 87 | return true 88 | } 89 | func (m *MySQL) CreatUser(nick_name, email, password string) bool { 90 | var usertype string 91 | if nick_name == "iguidao" { 92 | usertype = model.USERTYPEADMIN 93 | } else { 94 | usertype = model.USERTYPEVISITOR 95 | } 96 | if result := m.Create(&UserInfo{ 97 | UserName: nick_name, 98 | Email: email, 99 | Password: password, 100 | UserType: usertype, 101 | Enable: true, 102 | }); result.Error != nil { 103 | logger.Error("Mysql create mysql user fails", result.Error) 104 | return false 105 | } 106 | return true 107 | } 108 | -------------------------------------------------------------------------------- /src/middleware/opredis/all.go: -------------------------------------------------------------------------------- 1 | package opredis 2 | 3 | import ( 4 | "sort" 5 | 6 | "github.com/iguidao/redis-manager/src/cfg" 7 | ) 8 | 9 | func AllKey() []string { 10 | var keylist []string 11 | val, num, scanok := GetScanKey(0, 1000) 12 | if !scanok { 13 | return nil 14 | } 15 | keylist = append(keylist, val...) 16 | var fornum = 1 17 | for { 18 | val, num, scanok = GetScanKey(num, 1000) 19 | if !scanok { 20 | sort.Strings(keylist) 21 | return keylist 22 | } 23 | fornum++ 24 | keylist = append(keylist, val...) 25 | if num == 0 || fornum >= cfg.Get_Info_Int("allkeyfornum") { 26 | break 27 | } 28 | } 29 | sort.Strings(keylist) 30 | return keylist 31 | } 32 | -------------------------------------------------------------------------------- /src/middleware/opredis/analysisrdb.go: -------------------------------------------------------------------------------- 1 | package opredis 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "io" 7 | "os" 8 | 9 | "sort" 10 | "time" 11 | 12 | "github.com/iguidao/redis-manager/src/cfg" 13 | "github.com/iguidao/redis-manager/src/middleware/logger" 14 | "github.com/tommy351/rdb-go" 15 | ) 16 | 17 | func Analysis(filename, serverip string) map[string]interface{} { 18 | checksize := cfg.Get_Info_Int("checksize") 19 | stringkeymap := make(map[string]int) 20 | listkeymap := make(map[string]int) 21 | hashkeymap := make(map[string]int) 22 | setkeymap := make(map[string]int) 23 | zsetkeymap := make(map[string]int) 24 | resultmap := make(map[string]interface{}) 25 | file, err := os.Open(filename) 26 | if err != nil { 27 | panic(err) 28 | } 29 | defer file.Close() 30 | parser := rdb.NewParser(file) 31 | 32 | for { 33 | data, err := parser.Next() 34 | if errors.Is(err, io.EOF) { 35 | break 36 | } 37 | if err != nil { 38 | panic(err) 39 | } 40 | switch data := data.(type) { 41 | case *rdb.StringData: 42 | stringkeysize := len(data.Value) 43 | if stringkeysize > checksize { 44 | stringkeymap[data.Key] = stringkeysize 45 | } 46 | case *rdb.ListData: 47 | listkeysize := 0 48 | for _, value := range data.Value { 49 | listkeysize = listkeysize + len(value) 50 | } 51 | if listkeysize > checksize { 52 | listkeymap[data.Key] = listkeysize 53 | } 54 | case *rdb.HashData: 55 | hashkeysize := 0 56 | for _, value := range data.Value { 57 | hashkeysize = hashkeysize + len(value) 58 | } 59 | if hashkeysize > checksize { 60 | hashkeymap[data.Key] = hashkeysize 61 | } 62 | case *rdb.SetData: 63 | setkeysize := 0 64 | for _, value := range data.Value { 65 | setkeysize = setkeysize + len(value) 66 | } 67 | if setkeysize > checksize { 68 | setkeymap[data.Key] = setkeysize 69 | } 70 | 71 | case *rdb.SortedSetData: 72 | sortsetkeysize := 0 73 | for _, value := range data.Value { 74 | sortsetkeysize = sortsetkeysize + len(value.Value) 75 | } 76 | if sortsetkeysize > checksize { 77 | zsetkeymap[data.Key] = sortsetkeysize 78 | } 79 | } 80 | } 81 | 82 | resultmap["String-Big-Key-Top10"] = SortTopkey(stringkeymap) 83 | resultmap["List-Big-Key-Top10"] = SortTopkey(listkeymap) 84 | resultmap["hash-Big-Key-Top10"] = SortTopkey(hashkeymap) 85 | resultmap["set-Big-Key-Top10"] = SortTopkey(setkeymap) 86 | resultmap["check-time"] = time.Now().Format("2006-01-02 15:04:05") 87 | jsonBody, _ := json.Marshal(resultmap) 88 | _, ok := SetStringKey(serverip, string(jsonBody)) 89 | if ok { 90 | logger.Info("bigkey ", serverip, "设置成功 ", string(jsonBody)) 91 | } 92 | return resultmap 93 | } 94 | 95 | type keyintperoson struct { 96 | Name string 97 | Age int 98 | } 99 | 100 | func SortTopkey(keymap map[string]int) map[string]int { 101 | var lstPerson []keyintperoson 102 | resultkeymap := make(map[string]int) 103 | for k, v := range keymap { 104 | lstPerson = append(lstPerson, keyintperoson{k, v}) 105 | } 106 | 107 | sort.Slice(lstPerson, func(i, j int) bool { 108 | return lstPerson[i].Age > lstPerson[j].Age // 降序 109 | }) 110 | for i, v := range lstPerson { 111 | if i == 10 { 112 | break 113 | } 114 | resultkeymap[v.Name] = v.Age 115 | } 116 | return resultkeymap 117 | 118 | } 119 | -------------------------------------------------------------------------------- /src/middleware/opredis/basecluster.go: -------------------------------------------------------------------------------- 1 | package opredis 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/iguidao/redis-manager/src/middleware/logger" 7 | ) 8 | 9 | func CTypeKey(keyname string) (string, bool) { 10 | ok, err := CRD.Type(ctx, keyname).Result() 11 | if err != nil { 12 | logger.Error("Redis Set key: ", keyname, " Error: ", err) 13 | return "", false 14 | } 15 | return ok, true 16 | } 17 | 18 | // String key op 19 | func CGetStringKey(keyname string) (string, bool) { 20 | val, err := CRD.Get(ctx, keyname).Result() 21 | if err != nil { 22 | logger.Error("Redis Get key: ", keyname, " Error: ", err) 23 | return "", false 24 | } 25 | return val, true 26 | } 27 | 28 | func CGetListKey(keyname string) ([]string, bool) { 29 | lnum, err := CRD.LLen(ctx, keyname).Result() 30 | if err != nil { 31 | logger.Error("Redis LLEN key: ", keyname, " Error: ", err) 32 | return nil, false 33 | } 34 | // if lnum > 100 { 35 | // lnum = 100 36 | // } 37 | val, err := CRD.LRange(ctx, keyname, 0, lnum).Result() 38 | if err != nil { 39 | logger.Error("Redis LRANGE key: ", keyname, " Error: ", err) 40 | return nil, false 41 | } 42 | return val, true 43 | } 44 | func CGetHashKey(keyname string) (map[string]string, bool) { 45 | val, err := CRD.HGetAll(ctx, keyname).Result() 46 | if err != nil { 47 | logger.Error("Redis HGETALL key: ", keyname, " Error: ", err) 48 | return nil, false 49 | } 50 | return val, true 51 | } 52 | func CGetSetKey(keyname string) ([]string, bool) { 53 | val, err := CRD.SMembers(ctx, keyname).Result() 54 | if err != nil { 55 | logger.Error("Redis SMEMBERS key: ", keyname, " Error: ", err) 56 | return nil, false 57 | } 58 | return val, true 59 | } 60 | func CGetZsetKey(keyname string) ([]string, bool) { 61 | znum, err := CRD.ZCard(ctx, keyname).Result() 62 | if err != nil { 63 | logger.Error("Redis ZCARD key: ", keyname, " Error: ", err) 64 | return nil, false 65 | } 66 | // if znum > 100 { 67 | // znum = 100 68 | // } 69 | val, err := CRD.ZRange(ctx, keyname, 0, znum).Result() 70 | if err != nil { 71 | logger.Error("Redis ZRANGE key: ", keyname, " Error: ", err) 72 | return nil, false 73 | } 74 | return val, true 75 | } 76 | func CDelKey(keyname string) (int64, bool) { 77 | val, err := CRD.Del(ctx, keyname).Result() 78 | if err != nil { 79 | logger.Error("Redis Del key: ", keyname, " Error: ", err) 80 | return -1, false 81 | } 82 | return val, true 83 | } 84 | func CGetClusterNode() []string { 85 | clusternode := CRD.ClusterNodes(ctx) 86 | nodeinfo := strings.Split(clusternode.Val(), "\n") 87 | return nodeinfo 88 | } 89 | -------------------------------------------------------------------------------- /src/middleware/opredis/big.go: -------------------------------------------------------------------------------- 1 | package opredis 2 | 3 | import ( 4 | "sort" 5 | "sync" 6 | ) 7 | 8 | var ( 9 | stringkeymap map[string]int64 10 | listkeymap map[string]int64 11 | hashkeymap map[string]int64 12 | setkeymap map[string]int64 13 | zsetkeymap map[string]int64 14 | wg sync.WaitGroup 15 | lock sync.Mutex 16 | ) 17 | 18 | func BigKey() map[string]interface{} { 19 | stringkeymap = make(map[string]int64) 20 | listkeymap = make(map[string]int64) 21 | hashkeymap = make(map[string]int64) 22 | setkeymap = make(map[string]int64) 23 | zsetkeymap = make(map[string]int64) 24 | resultmap := make(map[string]interface{}) 25 | // val, _ := BgsaveKey() 26 | val, num, scanok := GetScanKey(0, 1000) 27 | if !scanok { 28 | return nil 29 | } 30 | wg.Add(1) 31 | go Countkey(val) 32 | for { 33 | val, num, scanok = GetScanKey(num, 1000) 34 | if !scanok { 35 | break 36 | } 37 | if num != 0 { 38 | wg.Add(1) 39 | go Countkey(val) 40 | } else { 41 | break 42 | } 43 | } 44 | wg.Wait() 45 | resultmap["string-Top10"] = Sortkey(stringkeymap) 46 | resultmap["list-Top10"] = Sortkey(listkeymap) 47 | resultmap["hash-Top10"] = Sortkey(hashkeymap) 48 | resultmap["set-Top10"] = Sortkey(setkeymap) 49 | resultmap["zset-Top10"] = Sortkey(zsetkeymap) 50 | return resultmap 51 | } 52 | 53 | type keyperoson struct { 54 | Name string 55 | Age int64 56 | } 57 | 58 | func Sortkey(keymap map[string]int64) map[string]int64 { 59 | var lstPerson []keyperoson 60 | resultkeymap := make(map[string]int64) 61 | for k, v := range keymap { 62 | lstPerson = append(lstPerson, keyperoson{k, v}) 63 | } 64 | 65 | sort.Slice(lstPerson, func(i, j int) bool { 66 | return lstPerson[i].Age > lstPerson[j].Age // 降序 67 | }) 68 | for i, v := range lstPerson { 69 | if i == 10 { 70 | break 71 | } 72 | resultkeymap[v.Name] = v.Age 73 | } 74 | return resultkeymap 75 | 76 | } 77 | 78 | func Countkey(keylist []string) { 79 | cstringkeymap := make(map[string]int64) 80 | clistkeymap := make(map[string]int64) 81 | chashkeymap := make(map[string]int64) 82 | csetkeymap := make(map[string]int64) 83 | czsetkeymap := make(map[string]int64) 84 | for _, keyname := range keylist { 85 | keytype, ok := TypeKey(keyname) 86 | if !ok { 87 | continue 88 | } 89 | switch keytype { 90 | case "string": 91 | val, stringok := SizeStringKey(keyname) 92 | if stringok { 93 | cstringkeymap[keyname] = val 94 | } 95 | case "list": 96 | val, listok := SizeListKey(keyname) 97 | if listok { 98 | clistkeymap[keyname] = val 99 | } 100 | case "hash": 101 | val, hashok := SizeHashKey(keyname) 102 | if hashok { 103 | chashkeymap[keyname] = val 104 | } 105 | case "set": 106 | val, setok := SizeSetKey(keyname) 107 | if setok { 108 | csetkeymap[keyname] = val 109 | } 110 | case "zset": 111 | val, zsetok := SizeZsetKey(keyname) 112 | if zsetok { 113 | czsetkeymap[keyname] = val 114 | } 115 | case "none": 116 | continue 117 | default: 118 | continue 119 | } 120 | } 121 | stringkeymap = AppendMap(stringkeymap, cstringkeymap) 122 | listkeymap = AppendMap(listkeymap, clistkeymap) 123 | hashkeymap = AppendMap(hashkeymap, chashkeymap) 124 | setkeymap = AppendMap(setkeymap, csetkeymap) 125 | zsetkeymap = AppendMap(zsetkeymap, czsetkeymap) 126 | defer wg.Done() 127 | } 128 | 129 | func AppendMap(result, val map[string]int64) map[string]int64 { 130 | lock.Lock() 131 | for i, v := range val { 132 | if i != "" || v != 0 { 133 | result[i] = v 134 | } 135 | } 136 | lock.Unlock() 137 | return result 138 | } 139 | -------------------------------------------------------------------------------- /src/middleware/opredis/click.go: -------------------------------------------------------------------------------- 1 | package opredis 2 | 3 | import "github.com/iguidao/redis-manager/src/cfg" 4 | 5 | func BigKeyClick(clusterName, groupName, keyname string) (string, int) { 6 | // keyname := "Click-Bigkey-" + clusterName + "-" + groupName 7 | num, ok := IncrStringKey(keyname) 8 | if !ok { 9 | return "大key分析计数出错了执行出现了问题,请找管理员!!!", 0 10 | } 11 | if num == 1 { 12 | if !ExpireKey(keyname, cfg.Get_Info_Int("locktime")) { 13 | return "大key分析计数时间出错了执行出现了问题,请找管理员!!!", 0 14 | } 15 | } 16 | if num == 5 { 17 | return "成功激发隐藏功能,开始针对集群:" + clusterName + " 的组:" + groupName + " 执行大key分析操作!!!请等待,如若超过10min未查出结果,请找管理员..", 1 18 | } 19 | if num > 5 { 20 | return "嗯?已经在执行后台大key分析了,还点?别着急,请等待10min!,如若超过10min未查出结果,请找管理员..", 2 21 | } 22 | return "据听说1分钟点击5次查询,可以生成最新的大key分析数据", 3 23 | } 24 | -------------------------------------------------------------------------------- /src/middleware/opredis/connect.go: -------------------------------------------------------------------------------- 1 | package opredis 2 | 3 | import ( 4 | "github.com/iguidao/redis-manager/src/middleware/logger" 5 | 6 | "github.com/go-redis/redis/v9" 7 | ) 8 | 9 | // 单点和codis链接 10 | type ClientConnect struct { 11 | *redis.Client 12 | } 13 | 14 | var RD ClientConnect 15 | 16 | func ConnectRedis(addr, password string) bool { 17 | rd := redis.NewClient(&redis.Options{ 18 | Addr: addr, 19 | Password: password, // no password set 20 | // DB: 0, // use default DB 21 | }) 22 | RD = ClientConnect{rd} 23 | _, err := RD.Ping(ctx).Result() 24 | if err != nil { 25 | logger.Error("Redis Connect Error: ", err) 26 | return false 27 | } 28 | return true 29 | } 30 | 31 | // 集群链接 32 | type ClientClusterConnect struct { 33 | *redis.ClusterClient 34 | } 35 | 36 | var CRD ClientClusterConnect 37 | 38 | func ConnectRedisCluster(addr []string, password string) bool { 39 | rd := redis.NewClusterClient(&redis.ClusterOptions{ 40 | Addrs: addr, 41 | Password: password, 42 | // DialTimeout: 200 * time.Microsecond, 43 | // ReadTimeout: 200 * time.Microsecond, 44 | // WriteTimeout: 200 * time.Microsecond, 45 | }) 46 | CRD = ClientClusterConnect{rd} 47 | return true 48 | } 49 | -------------------------------------------------------------------------------- /src/middleware/opredis/del.go: -------------------------------------------------------------------------------- 1 | package opredis 2 | 3 | func DeleteKey(keyname string) string { 4 | val, stringok := DelKey(keyname) 5 | if stringok { 6 | if val == 1 { 7 | return "删除成功" 8 | } 9 | if val == -1 { 10 | return "删除失败" 11 | } 12 | if val == 0 { 13 | return "没有这个key" 14 | } 15 | } 16 | return "删除失败" 17 | } 18 | 19 | func CDeleteKey(keyname string) string { 20 | val, stringok := CDelKey(keyname) 21 | if stringok { 22 | if val == 1 { 23 | return "删除成功" 24 | } 25 | if val == -1 { 26 | return "删除失败" 27 | } 28 | if val == 0 { 29 | return "没有这个key" 30 | } 31 | } 32 | return "删除失败" 33 | } 34 | -------------------------------------------------------------------------------- /src/middleware/opredis/hot.go: -------------------------------------------------------------------------------- 1 | package opredis 2 | 3 | import ( 4 | "context" 5 | "regexp" 6 | "strconv" 7 | "strings" 8 | "time" 9 | 10 | "github.com/iguidao/redis-manager/src/middleware/logger" 11 | ) 12 | 13 | func HotKey(serverip, pw string) map[string]int { 14 | keydic := make(map[string]int) 15 | 16 | var monitor string 17 | var knowtime int64 18 | ch := make(chan string) 19 | timeout, cancel := context.WithTimeout(context.Background(), 3*time.Second) 20 | defer cancel() 21 | go func() { 22 | monitor, knowtime = TelnetCommond(serverip, "monitor", pw) 23 | ch <- "done" 24 | }() 25 | 26 | select { 27 | case res := <-ch: 28 | logger.Info("Telnet success: ", res) 29 | case <-timeout.Done(): 30 | logger.Error("Telnet timout: ", timeout.Err()) 31 | } 32 | 33 | re := regexp.MustCompile("(?m)[\r\n]+^.*\"PING\"|\"INFO\".*$") 34 | monitor = re.ReplaceAllString(monitor, "") 35 | str := "(?m)[\r\n]+^.*" + strconv.FormatInt(knowtime+1, 10) + ".*$" 36 | re = regexp.MustCompile(str) 37 | monitora := re.FindAllString(monitor, -1) 38 | for _, v := range monitora { 39 | vlist := strings.Split(v, " ") 40 | if len(vlist) > 4 { 41 | vstring := strings.Replace(vlist[4], " ", "", -1) 42 | vstring = strings.Replace(vstring, "\n", "", -1) 43 | vstring = strings.Replace(vstring, "\r", "", -1) 44 | vstring = strings.Replace(vstring, "\"", "", -1) 45 | vstring = strings.Replace(vstring, "\\", "", -1) 46 | if _, ok := keydic[vstring]; ok { 47 | keydic[vstring] = keydic[vstring] + 1 48 | } else { 49 | keydic[vstring] = 1 50 | } 51 | } 52 | } 53 | return keydic 54 | } 55 | -------------------------------------------------------------------------------- /src/middleware/opredis/lock.go: -------------------------------------------------------------------------------- 1 | package opredis 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/iguidao/redis-manager/src/cfg" 7 | ) 8 | 9 | func LockCheck(key string, keytime time.Duration) bool { 10 | if ConnectRedis(cfg.Get_Info_String("REDIS"), cfg.Get_Info_String("redispw")) { 11 | if LockOp("Lock-"+key, keytime) { 12 | return true 13 | } 14 | } 15 | 16 | return false 17 | } 18 | 19 | func LockRm(key string) bool { 20 | if ConnectRedis(cfg.Get_Info_String("REDIS"), cfg.Get_Info_String("redispw")) { 21 | if UnLockOp("Lock-" + key) { 22 | return true 23 | } 24 | } 25 | return false 26 | } 27 | -------------------------------------------------------------------------------- /src/middleware/opredis/opdilatation.go: -------------------------------------------------------------------------------- 1 | package opredis 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/iguidao/redis-manager/src/middleware/codisapi" 7 | "github.com/iguidao/redis-manager/src/middleware/logger" 8 | "github.com/iguidao/redis-manager/src/middleware/model" 9 | ) 10 | 11 | // Codis扩容操作 12 | func Cdilatation(codisnode model.CodisChangeNode, auth string, topom codisapi.Topom) map[string]interface{} { 13 | result := make(map[string]interface{}) 14 | ok, uplist := UpClusterHost(codisnode, topom, auth) 15 | 16 | if !ok { 17 | logger.Error("Codis Opnode dilatation: codis add host fails") 18 | } 19 | time.Sleep(time.Duration(5) * time.Second) 20 | if !CodisRebalanceAll(codisnode.Curl, codisnode.ClusterName, auth) { 21 | logger.Error("Codis Opnode dilatation: codis Reabalance fails") 22 | } 23 | result["status"] = ok 24 | result["downlist"] = uplist 25 | return result 26 | } 27 | -------------------------------------------------------------------------------- /src/middleware/opredis/opshrinkage.go: -------------------------------------------------------------------------------- 1 | package opredis 2 | 3 | import ( 4 | "github.com/iguidao/redis-manager/src/middleware/codisapi" 5 | "github.com/iguidao/redis-manager/src/middleware/logger" 6 | "github.com/iguidao/redis-manager/src/middleware/model" 7 | ) 8 | 9 | // 缩容操作 10 | func Cshrinkage(codisnode model.CodisChangeNode, auth string, topom codisapi.Topom) map[string]interface{} { 11 | result := make(map[string]interface{}) 12 | ok, downlist := DownClusterHost(codisnode, auth, topom.Stats) 13 | if !ok { 14 | logger.Error("Codis Opnode shrinkage: codis down host fails") 15 | } 16 | result["status"] = ok 17 | result["downlist"] = downlist 18 | return result 19 | } 20 | -------------------------------------------------------------------------------- /src/middleware/opredis/query.go: -------------------------------------------------------------------------------- 1 | package opredis 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | func CQueryKey(keyname string) QueryResult { 8 | keytype, ok := CTypeKey(keyname) 9 | var result QueryResult 10 | if ok { 11 | result.Value, result.Len = CQuery_value(keytype, keyname) 12 | ttl, tok := TtlKey(keyname) 13 | if tok { 14 | result.Ttl = ttl 15 | } 16 | result.Type = keytype 17 | } 18 | return result 19 | } 20 | func CQuery_value(keytype, keyname string) (interface{}, int) { 21 | switch keytype { 22 | case "string": 23 | val, stringok := CGetStringKey(keyname) 24 | if stringok { 25 | return val, strings.Count(val, "") 26 | } 27 | case "list": 28 | val, listok := CGetListKey(keyname) 29 | if listok { 30 | return val, len(val) 31 | } 32 | case "hash": 33 | val, hashok := CGetHashKey(keyname) 34 | if hashok { 35 | return val, len(val) 36 | } 37 | case "set": 38 | val, setok := CGetSetKey(keyname) 39 | if setok { 40 | return val, len(val) 41 | } 42 | case "zset": 43 | val, zsetok := CGetZsetKey(keyname) 44 | if zsetok { 45 | return val, len(val) 46 | } 47 | case "none": 48 | return "Not Found Key", 0 49 | default: 50 | return "This type " + keytype + " key, query is not supported", 0 51 | } 52 | return "Get Key Fail", 0 53 | } 54 | 55 | func QueryKey(keyname string) QueryResult { 56 | keytype, ok := TypeKey(keyname) 57 | var result QueryResult 58 | if ok { 59 | result.Value, result.Len = Query_value(keytype, keyname) 60 | ttl, tok := TtlKey(keyname) 61 | if tok { 62 | result.Ttl = ttl 63 | } 64 | result.Type = keytype 65 | } 66 | return result 67 | } 68 | 69 | func Query_value(keytype, keyname string) (interface{}, int) { 70 | switch keytype { 71 | case "string": 72 | val, stringok := GetStringKey(keyname) 73 | if stringok { 74 | return val, strings.Count(val, "") 75 | } 76 | case "list": 77 | val, listok := GetListKey(keyname) 78 | if listok { 79 | return val, len(val) 80 | } 81 | case "hash": 82 | val, hashok := GetHashKey(keyname) 83 | if hashok { 84 | return val, len(val) 85 | } 86 | case "set": 87 | val, setok := GetSetKey(keyname) 88 | if setok { 89 | return val, len(val) 90 | } 91 | case "zset": 92 | val, zsetok := GetZsetKey(keyname) 93 | if zsetok { 94 | return val, len(val) 95 | } 96 | case "none": 97 | return "Not Found Key", 0 98 | default: 99 | return "This type " + keytype + " key, query is not supported", 0 100 | } 101 | return "Get Key Fail", 0 102 | } 103 | -------------------------------------------------------------------------------- /src/middleware/opredis/redisstruct.go: -------------------------------------------------------------------------------- 1 | package opredis 2 | 3 | type QueryResult struct { 4 | Ttl string `json:"ttl"` 5 | Type string `json:"type"` 6 | Value interface{} `json:"value"` 7 | Len int `json:"len"` 8 | // Debug string `json:"debug"` 9 | } 10 | -------------------------------------------------------------------------------- /src/middleware/opredis/slow.go: -------------------------------------------------------------------------------- 1 | package opredis 2 | 3 | import ( 4 | "github.com/go-redis/redis/v9" 5 | ) 6 | 7 | func SlowKey() []redis.SlowLog { 8 | val, slowlogok := GetSlowLog() 9 | if !slowlogok { 10 | return nil 11 | } 12 | return val 13 | } 14 | -------------------------------------------------------------------------------- /src/middleware/opredis/telnet.go: -------------------------------------------------------------------------------- 1 | package opredis 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/iguidao/redis-manager/src/middleware/logger" 7 | "github.com/reiver/go-telnet" 8 | ) 9 | 10 | func ReaderTelnet(conn *telnet.Conn) (out string, knowtime int64) { 11 | var result string 12 | var buffer [100]byte 13 | recvData := buffer[:] 14 | knowtime = time.Now().Unix() 15 | for { 16 | comtime := time.Now().Unix() 17 | _, err := conn.Read(recvData) 18 | if nil != err { 19 | logger.Error("ReaderTelnet error: ", err) 20 | } 21 | result = result + string(recvData) 22 | if comtime-knowtime > 1 { 23 | break 24 | } 25 | } 26 | 27 | return result, knowtime 28 | } 29 | func SenderTelnet(conn *telnet.Conn, command string) { 30 | var crlfBuffer [2]byte = [2]byte{'\r', '\n'} 31 | crlf := crlfBuffer[:] 32 | conn.Write([]byte(command)) 33 | conn.Write(crlf) 34 | } 35 | 36 | func TelnetCommond(ip, command, pw string) (string, int64) { 37 | conn, err := telnet.DialTo(ip) 38 | if nil != err { 39 | logger.Error("TelnetCommond error: ", err) 40 | } 41 | defer conn.Close() 42 | if pw != "" { 43 | SenderTelnet(conn, "auth "+pw) 44 | } 45 | SenderTelnet(conn, command) 46 | monitor, knowtime := ReaderTelnet(conn) 47 | return monitor, knowtime 48 | } 49 | -------------------------------------------------------------------------------- /src/middleware/rcron/cloudrefresh.go: -------------------------------------------------------------------------------- 1 | package rcron 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/iguidao/redis-manager/src/middleware/logger" 7 | "github.com/iguidao/redis-manager/src/middleware/model" 8 | "github.com/iguidao/redis-manager/src/middleware/mysql" 9 | "github.com/iguidao/redis-manager/src/middleware/txcloud" 10 | "github.com/iguidao/redis-manager/src/middleware/util" 11 | ) 12 | 13 | func CloudRefresh() { 14 | logger.Info("定时任务:刷新云redis任务启动") 15 | cloudset := make(map[string]string) 16 | cloudinfo := mysql.DB.GetCloudRegion() 17 | if len(cloudinfo) == 0 { 18 | logger.Info("定时任务:数据库没有数据,不用获取最新的云redis数据") 19 | return 20 | } 21 | for _, v := range cloudinfo { 22 | cloudset[v.Region] = v.Cloud 23 | } 24 | for i, v := range cloudset { 25 | if v == "txredis" { 26 | if !txcloud.TxRedisContent(i) { 27 | logger.Error("定时任务:链接腾讯云redis失败") 28 | return 29 | } else { 30 | list, ok := txcloud.TxListRedis() 31 | var rlist model.TxL 32 | if ok { 33 | err := json.Unmarshal([]byte(list), &rlist) 34 | if err == nil { 35 | go util.TxWriteRedis(v, rlist) 36 | logger.Info("定时任务:开始更新腾讯云redis数据") 37 | } else { 38 | logger.Error("定时任务:json解析云redis数据失败", err) 39 | } 40 | } else { 41 | logger.Error("定时任务:获取云redis数据失败") 42 | } 43 | } 44 | } 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/middleware/rcron/clusterrefresh.go: -------------------------------------------------------------------------------- 1 | package rcron 2 | 3 | import "github.com/iguidao/redis-manager/src/middleware/logger" 4 | 5 | func ClusterRefresh() { 6 | logger.Info("定时任务:刷新cluster任务启动") 7 | } 8 | -------------------------------------------------------------------------------- /src/middleware/tools/calculation.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import "github.com/iguidao/redis-manager/src/middleware/codisapi" 4 | 5 | func CalculationGroup(slist []int, topomstats codisapi.TopomStats) int { 6 | var arrint []int 7 | for _, v := range topomstats.Group.Models { 8 | if !CheckInListInt(v.Id, slist) { 9 | arrint = append(arrint, v.Id) 10 | } 11 | 12 | } 13 | return CalculationArrMax(arrint) 14 | } 15 | func CalculationProxy(slist []int, topomstats codisapi.TopomStats) int { 16 | var arrint []int 17 | for _, v := range topomstats.Proxy.Models { 18 | if !CheckInListInt(v.Id, slist) { 19 | arrint = append(arrint, v.Id) 20 | } 21 | } 22 | return CalculationArrMax(arrint) 23 | } 24 | func CalculationArrMax(arrint []int) (max int) { 25 | max = arrint[0] 26 | for _, v := range arrint { 27 | if v > max { 28 | max = v 29 | } 30 | } 31 | return 32 | } 33 | 34 | func CheckInListInt(val int, slist []int) bool { 35 | for _, v := range slist { 36 | if v == val { 37 | return true 38 | } 39 | } 40 | return false 41 | } 42 | 43 | func CheckInListString(val string, slist []string) bool { 44 | for _, v := range slist { 45 | if v == val { 46 | return true 47 | } 48 | } 49 | return false 50 | } 51 | 52 | func DeleteListString(val string, slist []string) []string { 53 | var resultlist []string 54 | for _, v := range slist { 55 | if v != val { 56 | resultlist = append(resultlist, v) 57 | } 58 | } 59 | return resultlist 60 | } 61 | 62 | func DeleteListint(val int, slist []int) []int { 63 | var resultlist []int 64 | for _, v := range slist { 65 | if v != val { 66 | resultlist = append(resultlist, v) 67 | } 68 | } 69 | return resultlist 70 | } 71 | -------------------------------------------------------------------------------- /src/middleware/tools/capacity.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | 7 | "github.com/iguidao/redis-manager/src/middleware/codisapi" 8 | ) 9 | 10 | func CapacityGroup(gn int, topom codisapi.Topom) bool { 11 | //计算内存大小 12 | var maxmemory, usememory, onemax, oneuse, maxnum, usenum int 13 | for _, v := range topom.Stats.Group.Stats { 14 | max, err := strconv.Atoi(v.Stats.Maxmemory) 15 | if err == nil { 16 | maxmemory = maxmemory + max 17 | maxnum = maxnum + 1 18 | } 19 | use, err := strconv.Atoi(v.Stats.Used_memory) 20 | if err == nil { 21 | usememory = usememory + use 22 | usenum = usenum + 1 23 | } 24 | } 25 | onemax = maxmemory / maxnum 26 | oneuse = usememory / usenum 27 | fenzi := ((oneuse*gn)/(len(topom.Stats.Group.Models)-gn) + oneuse) 28 | fenmu := onemax 29 | val, _ := strconv.ParseFloat(fmt.Sprintf("%.2f", float64(fenzi)/float64(fenmu)), 64) 30 | if val < 0.8 { 31 | return true 32 | } 33 | return false 34 | 35 | } 36 | 37 | func CapacityProxy(pn int, topom codisapi.Topom) bool { 38 | // 计算proxy的qps 39 | var maxqps int 40 | for _, v := range topom.Stats.Proxy.Stats { 41 | maxqps = maxqps + v.Stats.Ops.Qps 42 | // log.Println(i, v.Stats.Ops.Qps) 43 | } 44 | val, _ := strconv.ParseFloat(fmt.Sprintf("%.2f", float64(maxqps)/float64(len(topom.Stats.Proxy.Models)-pn)), 64) 45 | if val < 40000 { 46 | return true 47 | } 48 | return false 49 | } 50 | -------------------------------------------------------------------------------- /src/middleware/tools/check.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "net" 5 | "time" 6 | ) 7 | 8 | func CheckStringInArray(target string, str_array []string) bool { 9 | for _, element := range str_array { 10 | if target == element { 11 | return true 12 | } 13 | } 14 | return false 15 | } 16 | 17 | func IsIP(ip string) bool { 18 | address := net.ParseIP(ip) 19 | if address == nil { 20 | return false 21 | } else { 22 | return true 23 | } 24 | } 25 | 26 | func CheckIpPort(addr string, timeout int) bool { 27 | conn, err := net.DialTimeout("tcp", addr, time.Duration(timeout)*time.Millisecond) 28 | if err != nil { 29 | return false 30 | } 31 | defer conn.Close() 32 | return true 33 | } 34 | -------------------------------------------------------------------------------- /src/middleware/tools/codisauth.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "bytes" 5 | "crypto/sha256" 6 | "fmt" 7 | ) 8 | 9 | func NewXAuth(segs ...string) string { 10 | t := &bytes.Buffer{} 11 | fmt.Fprintf(t, "Codis-XAuth") 12 | for _, s := range segs { 13 | fmt.Fprintf(t, "-[%s]", s) 14 | } 15 | b := sha256.Sum256(t.Bytes()) 16 | return fmt.Sprintf("%x", b[:16]) 17 | } 18 | -------------------------------------------------------------------------------- /src/middleware/tools/trans.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/iguidao/redis-manager/src/middleware/logger" 7 | ) 8 | 9 | func JsonToMap(jsonstr string) map[string]interface{} { 10 | m := make(map[string]interface{}) 11 | err := json.Unmarshal([]byte(jsonstr), &m) 12 | if err != nil { 13 | logger.Error("json to map fail: ", jsonstr, " error: ", err) 14 | return nil 15 | } 16 | return m 17 | } 18 | -------------------------------------------------------------------------------- /src/middleware/txcloud/clist.go: -------------------------------------------------------------------------------- 1 | package txcloud 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/iguidao/redis-manager/src/middleware/logger" 7 | "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors" 8 | cvm "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312" 9 | tredis "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/redis/v20180412" 10 | ) 11 | 12 | func TxListRedis() (string, bool) { 13 | // 实例化一个请求对象,每个接口都会对应一个request对象 14 | request := tredis.NewDescribeInstancesRequest() 15 | // 返回的resp是一个DescribeInstancesResponse的实例,与请求对象对应 16 | response, err := TxRedisApi.DescribeInstances(request) 17 | if _, ok := err.(*errors.TencentCloudSDKError); ok { 18 | logger.Error("An Redis API error has returned: ", err) 19 | return "", false 20 | } 21 | if err != nil { 22 | logger.Error("List Tx Cloud Redis Error: ", err) 23 | return "", false 24 | } 25 | // 输出json格式的字符串回包 26 | return response.ToJsonString(), true 27 | } 28 | 29 | func TxListRegion() (string, bool) { 30 | // 实例化一个请求对象,每个接口都会对应一个request对象 31 | request := cvm.NewDescribeRegionsRequest() 32 | // 返回的resp是一个DescribeRegionsResponse的实例,与请求对象对应 33 | response, err := TxCvmApi.DescribeRegions(request) 34 | if _, ok := err.(*errors.TencentCloudSDKError); ok { 35 | fmt.Printf("An Region API error has returned: %s", err) 36 | return "", false 37 | } 38 | if err != nil { 39 | logger.Error("List Tx Cloud Region Error: ", err) 40 | return "", false 41 | } 42 | // 输出json格式的字符串回包 43 | return response.ToJsonString(), true 44 | } 45 | -------------------------------------------------------------------------------- /src/middleware/txcloud/connect.go: -------------------------------------------------------------------------------- 1 | package txcloud 2 | 3 | import ( 4 | "github.com/iguidao/redis-manager/src/middleware/logger" 5 | "github.com/iguidao/redis-manager/src/middleware/model" 6 | "github.com/iguidao/redis-manager/src/middleware/mysql" 7 | "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" 8 | "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" 9 | cvm "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312" 10 | dbbrain "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dbbrain/v20210527" 11 | tredis "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/redis/v20180412" 12 | ) 13 | 14 | // DB as the mysql client 15 | var TxRedisApi *tredis.Client 16 | 17 | func TxRedisContent(region string) bool { 18 | var err error 19 | credential := common.NewCredential( 20 | mysql.DB.GetOneCfgValue(model.TXSECRETID), 21 | mysql.DB.GetOneCfgValue(model.TXSECRETKEY), 22 | ) 23 | // 实例化一个client选项,可选的,没有特殊需求可以跳过 24 | cpf := profile.NewClientProfile() 25 | cpf.HttpProfile.Endpoint = mysql.DB.GetOneCfgValue(model.TXAPIURL) 26 | // 实例化要请求产品的client对象,clientProfile是可选的 27 | TxRedisApi, err = tredis.NewClient(credential, region, cpf) 28 | if err != nil { 29 | logger.Error("conenct tx cloud redis error: ", err) 30 | return false 31 | } 32 | return true 33 | } 34 | 35 | // CVM 36 | var TxCvmApi *cvm.Client 37 | 38 | func TxCvmContent() bool { 39 | var err error 40 | credential := common.NewCredential( 41 | mysql.DB.GetOneCfgValue(model.TXSECRETID), 42 | mysql.DB.GetOneCfgValue(model.TXSECRETKEY), 43 | ) 44 | // 实例化一个client选项,可选的,没有特殊需求可以跳过 45 | cpf := profile.NewClientProfile() 46 | cpf.HttpProfile.Endpoint = "cvm.tencentcloudapi.com" 47 | // 实例化要请求产品的client对象,clientProfile是可选的 48 | TxCvmApi, err = cvm.NewClient(credential, "", cpf) 49 | if err != nil { 50 | logger.Error("conenct tx cloud region error: ", err) 51 | return false 52 | } 53 | return true 54 | } 55 | 56 | // Dbrain 57 | var TxDbrainApi *dbbrain.Client 58 | 59 | func TxDbrainContent(region string) bool { 60 | var err error 61 | credential := common.NewCredential( 62 | mysql.DB.GetOneCfgValue(model.TXSECRETID), 63 | mysql.DB.GetOneCfgValue(model.TXSECRETKEY), 64 | ) 65 | // 实例化一个client选项,可选的,没有特殊需求可以跳过 66 | cpf := profile.NewClientProfile() 67 | cpf.HttpProfile.Endpoint = "dbbrain.tencentcloudapi.com" 68 | // 实例化要请求产品的client对象,clientProfile是可选的 69 | TxDbrainApi, err = dbbrain.NewClient(credential, region, cpf) 70 | if err != nil { 71 | logger.Error("conenct tx cloud region error: ", err) 72 | return false 73 | } 74 | return true 75 | } 76 | -------------------------------------------------------------------------------- /src/middleware/txcloud/opkey.go: -------------------------------------------------------------------------------- 1 | package txcloud 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" 8 | "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors" 9 | dbbrain "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dbbrain/v20210527" 10 | tredis "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/redis/v20180412" 11 | ) 12 | 13 | func TxHostKey(instanceid string) (string, bool) { 14 | 15 | // 实例化一个请求对象,每个接口都会对应一个request对象 16 | request := tredis.NewDescribeInstanceMonitorHotKeyRequest() 17 | 18 | request.InstanceId = common.StringPtr(instanceid) 19 | request.SpanType = common.Int64Ptr(1) 20 | 21 | // 返回的resp是一个DescribeInstanceMonitorHotKeyResponse的实例,与请求对象对应 22 | response, err := TxRedisApi.DescribeInstanceMonitorHotKey(request) 23 | if _, ok := err.(*errors.TencentCloudSDKError); ok { 24 | fmt.Printf("An API error has returned: %s", err) 25 | return "", false 26 | } 27 | if err != nil { 28 | panic(err) 29 | } 30 | // 输出json格式的字符串回包 31 | return response.ToJsonString(), true 32 | } 33 | 34 | func TxProxySlowKey(instanceid, starttime, endtime string) (string, bool) { 35 | // todaynow := time.Now().Format("2006") + "-" + time.Now().Format("01") + "-" + time.Now().Format("02") 36 | request := tredis.NewDescribeProxySlowLogRequest() 37 | 38 | request.InstanceId = common.StringPtr(instanceid) 39 | request.BeginTime = common.StringPtr(starttime) 40 | request.EndTime = common.StringPtr(endtime) 41 | 42 | // 返回的resp是一个DescribeProxySlowLogResponse的实例,与请求对象对应 43 | response, err := TxRedisApi.DescribeProxySlowLog(request) 44 | if _, ok := err.(*errors.TencentCloudSDKError); ok { 45 | fmt.Printf("An API error has returned: %s", err) 46 | return "", false 47 | } 48 | if err != nil { 49 | panic(err) 50 | } 51 | // 输出json格式的字符串回包 52 | return response.ToJsonString(), true 53 | } 54 | func TxRedisSlowKey(instanceid, starttime, endtime string) (string, bool) { 55 | 56 | // 实例化一个请求对象,每个接口都会对应一个request对象 57 | request := tredis.NewDescribeSlowLogRequest() 58 | 59 | request.InstanceId = common.StringPtr(instanceid) 60 | request.BeginTime = common.StringPtr(starttime) 61 | request.EndTime = common.StringPtr(endtime) 62 | 63 | // 返回的resp是一个DescribeSlowLogResponse的实例,与请求对象对应 64 | response, err := TxRedisApi.DescribeSlowLog(request) 65 | if _, ok := err.(*errors.TencentCloudSDKError); ok { 66 | fmt.Printf("An API error has returned: %s", err) 67 | return "", false 68 | } 69 | if err != nil { 70 | panic(err) 71 | } 72 | // 输出json格式的字符串回包 73 | return response.ToJsonString(), true 74 | } 75 | 76 | func TxBigKey(instanceid string) (string, bool) { 77 | todaynow := time.Now().Format("2006") + "-" + time.Now().Format("01") + "-" + time.Now().Format("02") 78 | 79 | request := dbbrain.NewDescribeRedisTopBigKeysRequest() 80 | 81 | request.InstanceId = common.StringPtr(instanceid) 82 | request.Date = common.StringPtr(todaynow) 83 | request.Product = common.StringPtr("redis") 84 | 85 | // 返回的resp是一个DescribeRedisTopBigKeysResponse的实例,与请求对象对应 86 | response, err := TxDbrainApi.DescribeRedisTopBigKeys(request) 87 | if _, ok := err.(*errors.TencentCloudSDKError); ok { 88 | fmt.Printf("An API error has returned: %s", err) 89 | return "", false 90 | } 91 | if err != nil { 92 | panic(err) 93 | } 94 | // 输出json格式的字符串回包 return response.ToJsonString(), true 95 | return response.ToJsonString(), true 96 | } 97 | -------------------------------------------------------------------------------- /src/middleware/useride/daoyou.go: -------------------------------------------------------------------------------- 1 | package useride 2 | 3 | import ( 4 | "github.com/iguidao/redis-manager/src/middleware/logger" 5 | "github.com/iguidao/redis-manager/src/middleware/mysql" 6 | ) 7 | 8 | func Gd_login(username, password string) bool { 9 | scrypt_password := Get_scrypt(password) 10 | //mysql_password := mysql.Get_user(phone) 11 | mysql_password, err := mysql.DB.FindUserPassword(username) 12 | if err != nil { 13 | logger.Error("数据库查询错误: ", err, username) 14 | return false 15 | } 16 | 17 | if scrypt_password != mysql_password.Password { 18 | return false 19 | } else { 20 | return true 21 | } 22 | //return "ok" 23 | 24 | } 25 | 26 | // func CacheUserinfo(token string, Phone int64) { 27 | // opredis.RegisterAuthRedis(token) 28 | // userinfo := mysql.DB.UserInfo(Phone) 29 | // userconver := util.UserConverge(userinfo) 30 | // jsonBody, _ := json.Marshal(userconver) 31 | // opredis.WriteUserRedis(string(jsonBody), userinfo.Base.ID) 32 | // } 33 | // func RefreshUserinfo(token, oldtoken string) { 34 | // opredis.RegisterAuthRedis(token) 35 | // opredis.DelAuthRedis(oldtoken) 36 | // } 37 | 38 | // // 查mysql用户信息存redis 39 | // func GetUserToRedis(id string) util.UserInfo { 40 | // userinfo := mysql.DB.UserIdInfo(id) 41 | // userconver := util.UserConverge(userinfo) 42 | // jsonBody, _ := json.Marshal(userconver) 43 | // opredis.WriteUserRedis(string(jsonBody), userinfo.Base.ID) 44 | // return userconver 45 | // } 46 | -------------------------------------------------------------------------------- /src/middleware/useride/password.go: -------------------------------------------------------------------------------- 1 | package useride 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "log" 7 | 8 | "golang.org/x/crypto/scrypt" 9 | ) 10 | 11 | // 密码加密 12 | func Get_scrypt(password string) string { 13 | pw_new := string([]byte(password)[:3]) 14 | user_src := []byte(pw_new) 15 | other_src := []byte("redis") 16 | all_src := [][]byte{user_src, other_src} 17 | salt := bytes.Join(all_src, []byte{}) 18 | dk, err := scrypt.Key([]byte(password), salt, 32768, 8, 1, 32) 19 | if err != nil { 20 | log.Fatal(err) 21 | } 22 | user_password := base64.StdEncoding.EncodeToString(dk) 23 | return user_password 24 | } 25 | -------------------------------------------------------------------------------- /src/middleware/useride/tocken.go: -------------------------------------------------------------------------------- 1 | package useride 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/iguidao/redis-manager/src/cfg" 7 | "github.com/iguidao/redis-manager/src/middleware/logger" 8 | 9 | jwt_lib "github.com/golang-jwt/jwt/v4" 10 | ) 11 | 12 | type Token struct { 13 | Token string `json:"token"` 14 | } 15 | 16 | func Token_Get() string { 17 | SecretKey := cfg.Get_Info_String("secretkey") 18 | token := jwt_lib.New(jwt_lib.GetSigningMethod("HS256")) 19 | claims := make(jwt_lib.MapClaims) 20 | claims["exp"] = time.Now().Add(time.Hour * time.Duration(1)).Unix() 21 | claims["iat"] = time.Now().Unix() 22 | token.Claims = claims 23 | 24 | tokenString, err := token.SignedString([]byte(SecretKey)) 25 | if err != nil { 26 | logger.Error("Error while signing the token", err) 27 | } 28 | 29 | //response := Token{tokenString} 30 | return tokenString 31 | } 32 | -------------------------------------------------------------------------------- /src/middleware/util/cloud.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "github.com/iguidao/redis-manager/src/middleware/logger" 5 | "github.com/iguidao/redis-manager/src/middleware/model" 6 | "github.com/iguidao/redis-manager/src/middleware/mysql" 7 | ) 8 | 9 | func TxWriteRedis(cloud string, rlist model.TxL) { 10 | for _, v := range rlist.Response.InstanceSet { 11 | if !mysql.DB.ExistCloudredisId(cloud, v.InstanceId) { 12 | id, ok := mysql.DB.AddTxCloudRedis(cloud, v) 13 | if ok { 14 | logger.Info("write ", cloud, " redis to mysql ok: ", id, "instanceid: ", v.InstanceId) 15 | } else { 16 | logger.Error("write ", cloud, " redis to mysql false: ", id, "instanceid: ", v.InstanceId) 17 | } 18 | } else { 19 | ok := mysql.DB.UppdateTxCloudRedis(cloud, v) 20 | if ok { 21 | logger.Info("update ", cloud, " redis to mysql ok: ", "instanceid: ", v.InstanceId) 22 | } else { 23 | logger.Error("update ", cloud, " redis to mysql false: ", "instanceid: ", v.InstanceId) 24 | } 25 | } 26 | } 27 | } 28 | 29 | func AliWriteRedis(cloud string, rlist model.AliRedis) { 30 | for _, v := range rlist.Instances.KVStoreInstance { 31 | if !mysql.DB.ExistCloudredisId(cloud, v.InstanceId) { 32 | id, ok := mysql.DB.AddAliCloudRedis(cloud, v) 33 | if ok { 34 | logger.Info("write ", cloud, " redis to mysql ok: ", id, "instanceid: ", v.InstanceId) 35 | } else { 36 | logger.Error("write ", cloud, " redis to mysql false: ", id, "instanceid: ", v.InstanceId) 37 | } 38 | } else { 39 | ok := mysql.DB.UppdateAliCloudRedis(cloud, v) 40 | if ok { 41 | logger.Info("update ", cloud, " redis to mysql ok: ", "instanceid: ", v.InstanceId) 42 | } else { 43 | logger.Error("update ", cloud, " redis to mysql false: ", "instanceid: ", v.InstanceId) 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/middleware/util/converge.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | func ReturnDefaultModel(val string, list map[string]string) string { 4 | for i, v := range list { 5 | if val == i { 6 | return v 7 | } 8 | } 9 | return "自定义配置" 10 | } 11 | 12 | // func UserConverge(muserinfo mysql.RdUser) UserInfo { 13 | // var userinfo UserInfo 14 | // userinfo.Uid = muserinfo.Base.ID 15 | // userinfo.UserName = muserinfo.NickName 16 | // userinfo.Identity = muserinfo.Identity 17 | // // userinfo.Phone = muserinfo.UserPhone 18 | // userinfo.AvatarUrl = muserinfo.AvatarUrl 19 | // userinfo.CreatedAt = muserinfo.Base.CreatedAt 20 | // return userinfo 21 | // } 22 | 23 | // func claimsClaimsConverge(mclaims *Claims) UserJWTInfo { 24 | // var userinfo UserJWTInfo 25 | // userinfo.ID = mclaims.UserId 26 | // userinfo.UserName = mclaims.UserName 27 | // // userinfo.UserPhone = mclaims.UserPhone 28 | // // userinfo.CreatedAt = mclaims.CreaTime 29 | // return userinfo 30 | // } 31 | -------------------------------------------------------------------------------- /src/middleware/util/jwt.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "github.com/iguidao/redis-manager/src/cfg" 5 | "github.com/iguidao/redis-manager/src/middleware/mysql" 6 | 7 | "errors" 8 | "time" 9 | 10 | jwt "github.com/golang-jwt/jwt/v4" 11 | ) 12 | 13 | var jwtSecret = []byte(cfg.Get_Info_String("secretkey")) 14 | 15 | var ( 16 | TokenExpired error = errors.New("Token is expired") 17 | TokenNotValidYet error = errors.New("Token not active yet") 18 | TokenMalformed error = errors.New("That's not even a token") 19 | TokenInvalid error = errors.New("Couldn't handle this token:") 20 | SignKey string = "cfun" 21 | ) 22 | 23 | type Claims struct { 24 | UserId int `json:"userid"` 25 | UserName string `json:"username"` 26 | UserType string `json:"usertype"` 27 | // UserPhone int64 `json:"UserPhone"` 28 | // Password string `json:"Password"` 29 | // CreaTime time.Time `json:"CreaTime"` 30 | jwt.StandardClaims 31 | } 32 | 33 | // CreateToken 生成一个token 34 | func CreateToken(claims Claims) (string, error) { 35 | var SigningKey []byte 36 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) 37 | return token.SignedString(SigningKey) 38 | } 39 | 40 | func GenerateToken(Username string, Password string) (string, string, error) { 41 | 42 | nowTime := time.Now() 43 | expireTime := nowTime.Add(168 * time.Hour) 44 | muserinfo := mysql.DB.UserInfo(Username) 45 | username := muserinfo.UserName 46 | // userid := strconv.Itoa() 47 | usertype := muserinfo.UserType 48 | // CreaTime := muserinfo.Base.CreatedAt 49 | claims := Claims{ 50 | muserinfo.Base.ID, 51 | username, 52 | usertype, 53 | // UserPhone, 54 | // Password, 55 | // CreaTime, 56 | jwt.StandardClaims{ 57 | ExpiresAt: expireTime.Unix(), 58 | Issuer: "redis-manager", 59 | }, 60 | } 61 | token, err := CreateToken(claims) 62 | // tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) 63 | // token, err := tokenClaims.SignedString(jwtSecret) 64 | return token, usertype, err 65 | } 66 | 67 | func ParseToken(token string) (*Claims, error) { 68 | tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) { 69 | return jwtSecret, nil 70 | }) 71 | if tokenClaims != nil { 72 | if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid { 73 | return claims, nil 74 | } 75 | } 76 | 77 | return nil, err 78 | } 79 | 80 | func RefreshToken(tokenString string) (string, error) { 81 | // var username string 82 | jwt.TimeFunc = func() time.Time { 83 | return time.Unix(0, 0) 84 | } 85 | token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) { 86 | return jwtSecret, nil 87 | }) 88 | if err != nil { 89 | return "", err 90 | } 91 | if claims, ok := token.Claims.(*Claims); ok && token.Valid { 92 | // userinfo = mysql.DB.UserInfo(claims.UserPhone) 93 | // username = userinfo.UserName 94 | jwt.TimeFunc = time.Now 95 | claims.StandardClaims.ExpiresAt = time.Now().Add(3 * time.Hour).Unix() 96 | ctoekn, err := CreateToken(*claims) 97 | return ctoekn, err 98 | } 99 | return "", TokenInvalid 100 | } 101 | 102 | func GetUserInfo(tokenString string) (UserJWTInfo, error) { 103 | // var username string 104 | var userinfo UserJWTInfo 105 | var err error 106 | jwt.TimeFunc = func() time.Time { 107 | return time.Unix(0, 0) 108 | } 109 | token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) { 110 | return jwtSecret, nil 111 | }) 112 | if err != nil { 113 | return userinfo, err 114 | } 115 | if claims, ok := token.Claims.(*Claims); ok && token.Valid { 116 | userinfo = UserJWTInfo{ 117 | claims.UserId, 118 | claims.UserName, 119 | claims.UserType, 120 | } 121 | // muserinfo := mysql.DB.UserInfo(claims.UserPhone) 122 | // userinfo = UserConverge(claims) 123 | return userinfo, err 124 | } 125 | return userinfo, err 126 | } 127 | -------------------------------------------------------------------------------- /src/middleware/util/pagination.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | // GetPage get page parameters 10 | func GetPage(c *gin.Context) (int, int) { 11 | page, err := strconv.Atoi(c.DefaultQuery("page", "0")) 12 | if err != nil { 13 | panic(err) 14 | } 15 | size, err := strconv.Atoi(c.DefaultQuery("size", "10")) 16 | if err != nil { 17 | panic(err) 18 | } 19 | 20 | return page, size 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/middleware/util/parameter.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "regexp" 5 | ) 6 | 7 | //email verify 8 | func VerifyEmailFormat(email string) bool { 9 | //pattern := `\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*` //匹配电子邮箱 10 | pattern := `^[0-9a-z][_.0-9a-z-]{0,31}@([0-9a-z][0-9a-z-]{0,30}[0-9a-z]\.){1,4}[a-z]{2,4}$` 11 | 12 | reg := regexp.MustCompile(pattern) 13 | return reg.MatchString(email) 14 | } 15 | -------------------------------------------------------------------------------- /src/middleware/util/utilstruct.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "time" 4 | 5 | // user info tbale 6 | type UserJWTInfo struct { 7 | Uid int 8 | UserName string 9 | UserType string 10 | // UserPhone int64 11 | // CreatedAt time.Time 12 | } 13 | 14 | // user info tbale 15 | type UserInfo struct { 16 | Uid int 17 | UserName string 18 | Identity string 19 | // UserPhone int64 20 | AvatarUrl string 21 | CreatedAt time.Time 22 | } 23 | -------------------------------------------------------------------------------- /src/rhttp/router.go: -------------------------------------------------------------------------------- 1 | package rhttp 2 | 3 | import ( 4 | "io" 5 | "io/ioutil" 6 | "os" 7 | "strings" 8 | "time" 9 | 10 | "github.com/gin-contrib/cors" 11 | "github.com/gin-contrib/static" 12 | "github.com/gin-gonic/gin" 13 | "github.com/iguidao/redis-manager/src/cfg" 14 | "github.com/iguidao/redis-manager/src/middleware/jwt" 15 | "github.com/iguidao/redis-manager/src/middleware/model" 16 | v1 "github.com/iguidao/redis-manager/src/rhttp/v1" 17 | ) 18 | 19 | // NewServer return a configured http server of gin 20 | func NewServer() *gin.Engine { 21 | // 存储日志文件代码 22 | logpath := "./logs/" + cfg.Get_Info_String("logapipath") 23 | gin.DisableConsoleColor() 24 | f, _ := os.Create(logpath) 25 | gin.DefaultWriter = io.MultiWriter(f) 26 | r := gin.Default() 27 | 28 | // 跨域信息 29 | r.Use(cors.New(cors.Config{ 30 | AllowAllOrigins: true, 31 | AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTION"}, 32 | AllowHeaders: []string{"Origin", "Content-Length", "Content-Type", "Authorization"}, 33 | AllowCredentials: true, 34 | MaxAge: 12 * time.Hour, 35 | })) 36 | 37 | r.Use(static.Serve("/", static.LocalFile("website", true))) 38 | r.NoRoute(func(c *gin.Context) { 39 | accept := c.Request.Header.Get("Accept") 40 | flag := strings.Contains(accept, "text/html") 41 | if flag { 42 | content, err := ioutil.ReadFile("dist/index.html") 43 | if (err) != nil { 44 | c.Writer.WriteHeader(404) 45 | c.Writer.WriteString("Not Found") 46 | return 47 | } 48 | c.Writer.WriteHeader(200) 49 | c.Writer.Header().Add("Accept", "text/html") 50 | c.Writer.Write((content)) 51 | c.Writer.Flush() 52 | } 53 | }) 54 | // vue配置 55 | // r.Static("/assets", "./website/assets") 56 | // r.LoadHTMLFiles("./website/index.html") 57 | home := r.Group("") 58 | { 59 | home.GET("/", v1.Home) //主页接口 60 | } 61 | base := r.Group("/redis-manager/base/v1") 62 | { 63 | base.GET("/health", v1.HealthCheck) //自检接口 64 | } 65 | login := r.Group("/redis-manager/auth/v1") 66 | { 67 | login.POST("/sign-in", v1.Login) //登陆接口 68 | 69 | } 70 | public := r.Group("/redis-manager/public/v1") 71 | { 72 | public.POST("/analysisrdb", v1.AnalysisRdb) //分析dump文件 73 | } 74 | auth := r.Group("/redis-manager/auth/v1") 75 | auth.Use(jwt.JWT()) 76 | { 77 | auth.POST("/password", v1.ChangUserPassword) //更改用户密码 78 | auth.POST("/refresh", v1.Refresh) //刷新接口 79 | } 80 | board := r.Group(model.PATHBOARD) 81 | board.Use(jwt.JWT()) 82 | { 83 | board.GET("/desc", v1.BoardDesc) //board页面 84 | } 85 | history := r.Group(model.PATHHISTORY) 86 | history.Use(jwt.JWT()) 87 | { 88 | history.GET("/list", v1.OpHistory) //查看历史操作记录 89 | } 90 | cfg := r.Group(model.PATHCFG) 91 | cfg.Use(jwt.JWT()) 92 | { 93 | cfg.POST("/update", v1.CfgUpdate) // 添加配置信息 94 | cfg.GET("/list", v1.CfgList) // 获取配置信息 95 | cfg.DELETE("/del", v1.CfgDelete) //删除配置 96 | cfg.POST("/adddefault", v1.CfgAddDefault) //添加默认key 97 | cfg.GET("/listdefault", v1.CfgListDefault) //返回默认配置key 98 | } 99 | codis := r.Group(model.PATHCODIS) 100 | codis.Use(jwt.JWT()) 101 | { 102 | codis.POST("/add", v1.CodisAdd) //添加codis的平台地址 103 | codis.GET("/list", v1.CodisList) //列出有哪些平台地址 104 | codis.GET("/cluster", v1.CodisClusterList) //列出该平台地址有多少个集群 105 | codis.GET("/group", v1.CodisGroup) //列出该集群有多少个group 106 | codis.POST("/opnode", v1.CodisOpNode) //针对codis的proxy和server节点进行操作 107 | } 108 | cloud := r.Group(model.PATHCLOUD) 109 | cloud.Use(jwt.JWT()) 110 | { 111 | cloud.GET("/region", v1.RegionList) //列出云的地域 112 | cloud.GET("/list", v1.CloudList) //列出云的集群列表 113 | cloud.POST("/password", v1.ChangeCloudPassword) //修改数据库保存密码 114 | cloud.POST("/size", v1.ChangeSize) //修改集群大小 115 | cloud.POST("/add", v1.CloudAdd) // 添加集群 116 | cloud.DELETE("/del", v1.CloudDel) //删除集群 117 | } 118 | cluster := r.Group(model.PATHCLUSTER) 119 | cluster.Use(jwt.JWT()) 120 | { 121 | cluster.GET("/list", v1.ClusterList) //列出所有集群 122 | cluster.GET("/nodes", v1.NodeList) // 列出集群的node 123 | cluster.GET("/masters", v1.MasterList) //列出master地址 124 | cluster.POST("/add", v1.ClusterAdd) //添加集群 125 | } 126 | cli := r.Group(model.PATHCLI) 127 | cli.Use(jwt.JWT()) 128 | { 129 | cli.POST("/opkey", v1.OpKey) //对key进行操作 130 | } 131 | user := r.Group(model.PATHUSER) 132 | user.Use(jwt.JWT()) 133 | { 134 | user.POST("/add", v1.AddUser) //新增用户接口 135 | user.GET("/list", v1.ListUser) //列出所有用户 136 | user.DELETE("/del", v1.DelUser) //删除用户 137 | user.POST("/change", v1.ChangUserType) //更改用户属性 138 | user.GET("/utype", v1.ListUserType) //获取用户身份列表 139 | } 140 | rule := r.Group(model.PATHRULE) 141 | rule.Use(jwt.JWT()) 142 | { 143 | rule.POST("/add", v1.AddRule) //添加规则 144 | rule.DELETE("/del", v1.DelRule) //删除规则 145 | rule.GET("/list", v1.AllRule) //查看所有规则 146 | rule.GET("/cfg", v1.GetRuleCfg) //查看默认配置 147 | } 148 | 149 | r.NoMethod(v1.MethodFails) 150 | r.NoRoute(v1.RouterNotFound) 151 | return r 152 | } 153 | -------------------------------------------------------------------------------- /src/rhttp/v1/base.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | //"log" 5 | 6 | "io/ioutil" 7 | "net/http" 8 | "strings" 9 | 10 | "github.com/iguidao/redis-manager/src/hsc" 11 | 12 | "github.com/gin-gonic/gin" 13 | ) 14 | 15 | func HealthCheck(c *gin.Context) { 16 | code := hsc.SUCCESS 17 | c.JSON(http.StatusOK, gin.H{ 18 | "errorCode": code, 19 | "msg": hsc.GetMsg(code), 20 | "data": true, 21 | }) 22 | // c.JSON(http.StatusOK, gin.H{"ok": true}) 23 | } 24 | 25 | func HandleNotFound(c *gin.Context) { 26 | code := hsc.NOT_FOUND 27 | c.JSON(http.StatusOK, gin.H{ 28 | "errorCode": code, 29 | "msg": hsc.GetMsg(code), 30 | "data": false, 31 | }) 32 | } 33 | 34 | func MethodFails(c *gin.Context) { 35 | code := hsc.Method_FAILS 36 | c.JSON(http.StatusOK, gin.H{ 37 | "errorCode": code, 38 | "msg": hsc.GetMsg(code), 39 | "data": false, 40 | }) 41 | } 42 | 43 | func RouterNotFound(c *gin.Context) { 44 | accept := c.Request.Header.Get("Accept") 45 | flag := strings.Contains(accept, "text/html") 46 | if flag { 47 | content, err := ioutil.ReadFile("./website/index.html") 48 | if (err) != nil { 49 | c.Writer.WriteHeader(404) 50 | c.Writer.WriteString("Not Found") 51 | return 52 | } 53 | c.Writer.WriteHeader(200) 54 | c.Writer.Header().Add("Accept", "text/html") 55 | c.Writer.Write((content)) 56 | c.Writer.Flush() 57 | } 58 | } 59 | 60 | func HttpTemplate(c *gin.Context) { 61 | data := make(map[string]interface{}) 62 | code := hsc.SUCCESS 63 | c.JSON(http.StatusOK, gin.H{ 64 | "errorCode": code, 65 | "msg": hsc.GetMsg(code), 66 | "data": data, 67 | }) 68 | } 69 | 70 | func Cookie(c *gin.Context) { 71 | cookie, err := c.Cookie("gin_cookie") 72 | if err != nil { 73 | cookie = "NotSet" 74 | c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true) 75 | } 76 | c.JSON(http.StatusOK, gin.H{"cookie": cookie}) 77 | } 78 | -------------------------------------------------------------------------------- /src/rhttp/v1/board.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/iguidao/redis-manager/src/hsc" 8 | "github.com/iguidao/redis-manager/src/middleware/mysql" 9 | ) 10 | 11 | func BoardDesc(c *gin.Context) { 12 | code := hsc.SUCCESS 13 | result := make(map[string]int64) 14 | result["aliredis"] = mysql.DB.GetCloudNumber("aliredis") 15 | result["txredis"] = mysql.DB.GetCloudNumber("txredis") 16 | result["codis"] = mysql.DB.GetCodisNumber() 17 | result["cluster"] = mysql.DB.GetClusterNumber() 18 | c.JSON(http.StatusOK, gin.H{ 19 | "errorCode": code, 20 | "msg": hsc.GetMsg(code), 21 | "data": result, 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /src/rhttp/v1/cfg.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/iguidao/redis-manager/src/hsc" 9 | "github.com/iguidao/redis-manager/src/middleware/logger" 10 | "github.com/iguidao/redis-manager/src/middleware/model" 11 | "github.com/iguidao/redis-manager/src/middleware/mysql" 12 | ) 13 | 14 | func CfgListDefault(c *gin.Context) { 15 | code := hsc.SUCCESS 16 | listcfg := model.DefaultName 17 | var result []map[string]string 18 | for k, v := range listcfg { 19 | cfg := make(map[string]string) 20 | cfg["label"] = v 21 | cfg["value"] = k 22 | result = append(result, cfg) 23 | } 24 | c.JSON(http.StatusOK, gin.H{ 25 | "errorCode": code, 26 | "msg": hsc.GetMsg(code), 27 | "data": result, 28 | }) 29 | } 30 | 31 | func CfgAddDefault(c *gin.Context) { 32 | code := hsc.SUCCESS 33 | var cfg ConfigInfo 34 | err := c.BindJSON(&cfg) 35 | if err != nil { 36 | code = hsc.INVALID_PARAMS 37 | logger.Error("Config add Default error: ", err) 38 | } else { 39 | if mysql.DB.ExistCfg(model.CC) { 40 | custom := mysql.DB.GetOneCfg(model.CC) 41 | value := custom.Value + "," + cfg.Value 42 | if !mysql.DB.UpdateCfg(model.CC, value) { 43 | logger.Error("update cfg error") 44 | code = hsc.ERROR 45 | } 46 | } else { 47 | username, _ := c.Get("UserId") 48 | urlinfo := c.Request.URL 49 | jsonBody, _ := json.Marshal(cfg) 50 | method := c.Request.Method 51 | go mysql.DB.AddHistory(username.(int), method+":"+urlinfo.Path, string(jsonBody)) 52 | _, ok := mysql.DB.AddCfg(model.CC, cfg.Value, model.CN) 53 | if !ok { 54 | logger.Error("add cfg error") 55 | code = hsc.ERROR 56 | } 57 | } 58 | } 59 | 60 | c.JSON(http.StatusOK, gin.H{ 61 | "errorCode": code, 62 | "msg": hsc.GetMsg(code), 63 | "data": hsc.GetMsg(code), 64 | }) 65 | } 66 | 67 | func CfgList(c *gin.Context) { 68 | code := hsc.SUCCESS 69 | result := make(map[string]interface{}) 70 | cfglist := mysql.DB.GetAllCfg() 71 | result["lists"] = cfglist 72 | result["total"] = len(cfglist) 73 | c.JSON(http.StatusOK, gin.H{ 74 | "errorCode": code, 75 | "msg": hsc.GetMsg(code), 76 | "data": result, 77 | }) 78 | } 79 | 80 | func CfgUpdate(c *gin.Context) { 81 | code := hsc.SUCCESS 82 | var cfg ConfigInfo 83 | var result int 84 | var ok bool 85 | err := c.BindJSON(&cfg) 86 | if err != nil || cfg.Key == "" || cfg.Value == "" { 87 | code = hsc.INVALID_PARAMS 88 | logger.Error("Config add error: ", err) 89 | } else { 90 | username, _ := c.Get("UserId") 91 | urlinfo := c.Request.URL 92 | jsonBody, _ := json.Marshal(cfg) 93 | method := c.Request.Method 94 | go mysql.DB.AddHistory(username.(int), method+":"+urlinfo.Path, string(jsonBody)) 95 | name := model.DefaultName[cfg.Key] 96 | if mysql.DB.ExistCfg(cfg.Key) { 97 | if !mysql.DB.UpdateCfg(cfg.Key, cfg.Value) { 98 | code = hsc.ERROR 99 | } 100 | } else { 101 | result, ok = mysql.DB.AddCfg(name, cfg.Key, cfg.Value) 102 | if !ok { 103 | code = hsc.ERROR 104 | } 105 | } 106 | 107 | } 108 | c.JSON(http.StatusOK, gin.H{ 109 | "errorCode": code, 110 | "msg": hsc.GetMsg(code), 111 | "data": result, 112 | }) 113 | } 114 | 115 | func CfgDelete(c *gin.Context) { 116 | code := hsc.SUCCESS 117 | key := c.Query("key") 118 | result := false 119 | if key != "" { 120 | username, _ := c.Get("UserId") 121 | urlinfo := c.Request.URL 122 | jsonBody, _ := json.Marshal(key) 123 | method := c.Request.Method 124 | go mysql.DB.AddHistory(username.(int), method+":"+urlinfo.Path, string(jsonBody)) 125 | if !mysql.DB.DelCfg(key) { 126 | result = false 127 | code = hsc.SERVER_ERROR 128 | } 129 | } else { 130 | result = false 131 | code = hsc.INVALID_PARAMS 132 | } 133 | c.JSON(http.StatusOK, gin.H{ 134 | "errorCode": code, 135 | "msg": hsc.GetMsg(code), 136 | "data": result, 137 | }) 138 | } 139 | -------------------------------------------------------------------------------- /src/rhttp/v1/cluster.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | "strings" 7 | 8 | "github.com/gin-gonic/gin" 9 | "github.com/iguidao/redis-manager/src/hsc" 10 | "github.com/iguidao/redis-manager/src/middleware/cluster" 11 | "github.com/iguidao/redis-manager/src/middleware/logger" 12 | "github.com/iguidao/redis-manager/src/middleware/model" 13 | "github.com/iguidao/redis-manager/src/middleware/mysql" 14 | "github.com/iguidao/redis-manager/src/middleware/opredis" 15 | ) 16 | 17 | func ClusterList(c *gin.Context) { 18 | code := hsc.SUCCESS 19 | result := mysql.DB.GetAllCluster() 20 | c.JSON(http.StatusOK, gin.H{ 21 | "errorCode": code, 22 | "msg": hsc.GetMsg(code), 23 | "data": result, 24 | }) 25 | // c.JSON(http.StatusOK, gin.H{"ok": true}) 26 | } 27 | func NodeList(c *gin.Context) { 28 | code := hsc.SUCCESS 29 | var result []*model.ClusterNodeTables 30 | clusterid := c.Query("cluster_id") 31 | nodes := mysql.DB.GetClusterNode(clusterid) 32 | for _, v := range nodes { 33 | if v.Flags == "master" { 34 | result = append(result, cluster.ClusterConvergeTree(v)) 35 | } 36 | } 37 | for _, v := range nodes { 38 | if v.Flags == "slave" { 39 | result = cluster.ClusterUpdateTree(result, v) 40 | } 41 | } 42 | c.JSON(http.StatusOK, gin.H{ 43 | "errorCode": code, 44 | "msg": hsc.GetMsg(code), 45 | "data": result, 46 | }) 47 | } 48 | func MasterList(c *gin.Context) { 49 | code := hsc.SUCCESS 50 | clusterid := c.Query("cluster_id") 51 | nodes := mysql.DB.GetClusterNodeMaster(clusterid) 52 | c.JSON(http.StatusOK, gin.H{ 53 | "errorCode": code, 54 | "msg": hsc.GetMsg(code), 55 | "data": nodes, 56 | }) 57 | } 58 | func ClusterAdd(c *gin.Context) { 59 | var clusterinfo AddCluster 60 | result := make(map[string]interface{}) 61 | // staff_id, err := strconv.Atoi(UserId) 62 | err := c.BindJSON(&clusterinfo) 63 | code := hsc.ERROR 64 | if err != nil || clusterinfo.Name == "" || clusterinfo.Nodes == "" { 65 | logger.Error("Cluster add error: ", err) 66 | code = hsc.INVALID_PARAMS 67 | } else { 68 | username, _ := c.Get("UserId") 69 | urlinfo := c.Request.URL 70 | jsonBody, _ := json.Marshal(clusterinfo) 71 | method := c.Request.Method 72 | go mysql.DB.AddHistory(username.(int), method+":"+urlinfo.Path, string(jsonBody)) 73 | 74 | address := strings.Split(clusterinfo.Nodes, ",") 75 | connectok := false 76 | if opredis.ConnectRedisCluster(address, clusterinfo.Password) { 77 | connectok = true 78 | } 79 | if connectok { 80 | id, ok := mysql.DB.AddCluster(clusterinfo.Name, clusterinfo.Nodes, clusterinfo.Password) 81 | if ok || id != 0 { 82 | nodeinfo := opredis.CGetClusterNode() 83 | for _, v := range nodeinfo { 84 | if len(v) != 0 { 85 | cluster.WriteCluster(id, v) 86 | } 87 | } 88 | code = hsc.SUCCESS 89 | } else { 90 | logger.Error("添加集群到 cluster info 失败") 91 | code = hsc.ERROR_WRITE_MYSQL 92 | } 93 | } else { 94 | logger.Error("链接目标redis异常") 95 | code = hsc.ERROR_NO_CONNEC 96 | } 97 | 98 | } 99 | c.JSON(http.StatusOK, gin.H{ 100 | "errorCode": code, 101 | "msg": hsc.GetMsg(code), 102 | "data": result, 103 | }) 104 | } 105 | -------------------------------------------------------------------------------- /src/rhttp/v1/home.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | // 首页 10 | func Home(c *gin.Context) { 11 | c.HTML(http.StatusOK, "index.html", gin.H{ 12 | "title": "Redis Manager", 13 | }) 14 | } 15 | -------------------------------------------------------------------------------- /src/rhttp/v1/ophistory.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/iguidao/redis-manager/src/hsc" 7 | "github.com/iguidao/redis-manager/src/middleware/mysql" 8 | 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | func OpHistory(c *gin.Context) { 13 | code := hsc.SUCCESS 14 | result := mysql.DB.GetAllHistory() 15 | c.JSON(http.StatusOK, gin.H{ 16 | "errorCode": code, 17 | "msg": hsc.GetMsg(code), 18 | "data": result, 19 | }) 20 | // c.JSON(http.StatusOK, gin.H{"ok": true}) 21 | } 22 | -------------------------------------------------------------------------------- /src/rhttp/v1/rule.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/iguidao/redis-manager/src/hsc" 9 | "github.com/iguidao/redis-manager/src/middleware/casbin" 10 | "github.com/iguidao/redis-manager/src/middleware/model" 11 | "github.com/iguidao/redis-manager/src/middleware/mysql" 12 | "github.com/iguidao/redis-manager/src/middleware/util" 13 | ) 14 | 15 | func AllRule(c *gin.Context) { 16 | code := hsc.SUCCESS 17 | var result []interface{} 18 | casbinlist := casbin.RuleGet() 19 | if len(casbinlist) != 0 { 20 | for _, v := range casbinlist { 21 | rinfo := make(map[string]string) 22 | rinfo["identity"] = v.V0 23 | rinfo["path"] = v.V1 24 | rinfo["method"] = v.V2 25 | rinfo["note"] = util.ReturnDefaultModel(v.V1, model.DefaultPath) 26 | result = append(result, rinfo) 27 | } 28 | } 29 | c.JSON(http.StatusOK, gin.H{ 30 | "errorCode": code, 31 | "msg": hsc.GetMsg(code), 32 | "data": result, 33 | }) 34 | } 35 | func GetRuleCfg(c *gin.Context) { 36 | code := hsc.SUCCESS 37 | result := make(map[string]interface{}) 38 | var cfglist []map[string]string 39 | for k, v := range model.DefaultPath { 40 | cfg := make(map[string]string) 41 | cfg["label"] = v 42 | cfg["value"] = k 43 | cfglist = append(cfglist, cfg) 44 | } 45 | var methodlist []map[string]string 46 | for k, v := range model.DefaultMethod { 47 | method := make(map[string]string) 48 | method["label"] = v 49 | method["value"] = k 50 | methodlist = append(methodlist, method) 51 | } 52 | result["url"] = cfglist 53 | result["method"] = methodlist 54 | c.JSON(http.StatusOK, gin.H{ 55 | "errorCode": code, 56 | "msg": hsc.GetMsg(code), 57 | "data": result, 58 | }) 59 | } 60 | func AddRule(c *gin.Context) { 61 | data := make(map[string]interface{}) 62 | code := hsc.SUCCESS 63 | var Policy CasbinPolicyJson 64 | err := c.BindJSON(&Policy) 65 | if err != nil { 66 | code = hsc.INVALID_PARAMS 67 | } else { 68 | username, _ := c.Get("UserId") 69 | urlinfo := c.Request.URL 70 | jsonBody, _ := json.Marshal(Policy) 71 | method := c.Request.Method 72 | go mysql.DB.AddHistory(username.(int), method+":"+urlinfo.Path, string(jsonBody)) 73 | if !casbin.RuleAdd(Policy.Identity, Policy.Path, Policy.Method) { 74 | code = hsc.ERROR 75 | } 76 | } 77 | c.JSON(http.StatusOK, gin.H{ 78 | "errorCode": code, 79 | "msg": hsc.GetMsg(code), 80 | "data": data, 81 | }) 82 | } 83 | 84 | func DelRule(c *gin.Context) { 85 | data := make(map[string]interface{}) 86 | code := hsc.SUCCESS 87 | ppath := c.Query("path") 88 | pmethod := c.Query("method") 89 | pidentity := c.Query("identity") 90 | if ppath == "" || pmethod == "" || pidentity == "" { 91 | code = hsc.INVALID_PARAMS 92 | } else { 93 | username, _ := c.Get("UserId") 94 | urlinfo := c.Request.URL 95 | jsonBody, _ := json.Marshal("Path:" + ppath + " method:" + pmethod + " identity:" + pidentity) 96 | method := c.Request.Method 97 | go mysql.DB.AddHistory(username.(int), method+":"+urlinfo.Path, string(jsonBody)) 98 | if !casbin.RuleDel(pidentity, ppath, pmethod) { 99 | code = hsc.ERROR 100 | } 101 | } 102 | c.JSON(http.StatusOK, gin.H{ 103 | "errorCode": code, 104 | "msg": hsc.GetMsg(code), 105 | "data": data, 106 | }) 107 | } 108 | 109 | func RootCheck(c *gin.Context) { 110 | // var result auth.Result 111 | userinfo, ok := c.Get("UserName") 112 | if !ok { 113 | data := make(map[string]interface{}) 114 | code := hsc.NOT_PROMISE 115 | c.JSON(http.StatusOK, gin.H{ 116 | "errorCode": code, 117 | "msg": hsc.GetMsg(code), 118 | "data": data, 119 | }) 120 | return 121 | } 122 | 123 | // 请求用户id 124 | userid := userinfo.(string) 125 | // 请求的path 126 | path := c.Request.URL.Path 127 | // 请求的方法 128 | method := c.Request.Method 129 | if !casbin.RuleCheck(userid, path, method) { 130 | data := make(map[string]interface{}) 131 | code := hsc.ERROR 132 | c.JSON(http.StatusOK, gin.H{ 133 | "errorCode": code, 134 | "msg": hsc.GetMsg(code), 135 | "data": data, 136 | }) 137 | return 138 | } 139 | c.Next() 140 | } 141 | -------------------------------------------------------------------------------- /src/rhttp/v1/v1struct.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | // codis信息的操作 4 | type CodisInfo struct { 5 | Curl string `json:"curl"` 6 | Cname string `json:"cname"` 7 | ClusterName string `json:"cluster_name"` 8 | } 9 | 10 | // config配置信息 11 | type ConfigInfo struct { 12 | // Name string `json:"name"` 13 | Key string `json:"key"` 14 | Value string `json:"value"` 15 | } 16 | 17 | // 操作缓存的指令 18 | type CliQuery struct { 19 | CacheType string `json:"cache_type"` 20 | CacheOp string `json:"cache_op"` 21 | ClusterName string `json:"cluster_name"` 22 | KeyName string `json:"key_name"` 23 | CodisUrl string `json:"codis_url"` 24 | GroupName string `json:"group_name"` 25 | Region string `json:"region"` 26 | InstanceId string `json:"instance_id"` 27 | ClusterId string `json:"cluster_id"` 28 | NodeId string `json:"node_id"` 29 | } 30 | 31 | // 分析大key 32 | type CliRdb struct { 33 | RdbName string `json:"rdbname"` 34 | ServerIp string `json:"serverip"` 35 | } 36 | 37 | // old 38 | type AddCluster struct { 39 | Name string `json:"name"` 40 | Nodes string `json:"nodes"` 41 | Password string `json:"password"` 42 | } 43 | 44 | // user 45 | type UserInfo struct { 46 | UserId int `json:"userid"` 47 | UserName string `json:"username"` 48 | Password string `json:"password"` 49 | Mail string `json:"mail"` 50 | UserType string `json:"usertype"` 51 | } 52 | type UserPassword struct { 53 | Old string `json:"old"` 54 | New string `json:"new"` 55 | } 56 | 57 | // casbin rule 58 | type CasbinPolicyJson struct { 59 | Identity string `json:"identity"` 60 | Path string `json:"path"` 61 | Method string `json:"method"` 62 | } 63 | 64 | // cloud 65 | type CloudPassword struct { 66 | Cloud string `json:"cloud"` 67 | Instanceid string `json:"instanceid"` 68 | Password string `json:"password"` 69 | } 70 | 71 | type TxShardCfg struct { 72 | Cloud string `json:"cloud"` 73 | TxShardType string `json:"txshardtype"` 74 | TxShardValue string `json:"txshardvalue"` 75 | } 76 | 77 | // cluster nodes table 78 | -------------------------------------------------------------------------------- /start-docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | git pull 3 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o redis-manager main.go 4 | 5 | docker build -f Dockerfile -t redis-manager . 6 | rediswebmanager=`docker ps -a | grep redis-manager |wc -l` 7 | 8 | if [ ${rediswebmanager} == 1 ] 9 | then 10 | id=`docker ps -a| grep redis-manager| awk '{print $1}'` 11 | echo "docker stop $id" 12 | docker stop $id 13 | echo "docker rm $id" 14 | docker rm $id 15 | echo "docker run redis-manager" 16 | docker run -d -it -v logs:/data/logs -p 8000:8000 --restart=always --name redis-manager redis-manager 17 | else 18 | echo "docker run redis-manager" 19 | docker run -d -it -v logs:/data/logs -p 8000:8000 --restart=always --name redis-manager redis-manager 20 | fi 21 | -------------------------------------------------------------------------------- /website/assets/Index-035ac3eb.css: -------------------------------------------------------------------------------- 1 | .content[data-v-88bc7fc3]{margin:20px 8px} 2 | -------------------------------------------------------------------------------- /website/assets/Index-06dbdf44.js: -------------------------------------------------------------------------------- 1 | import{d as z,r as g,a as C,b as t,o as E,c as I,e as o,w as a,u as m,f as U,g as d,h as v,E as y,p as h,i as V,_ as b}from"./index-056ef9e9.js";import{l as N}from"./user-d7605884.js";import"./index-04cccabd.js";const T="/assets/github-mark-367d5cb2.png",k="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAABRBJREFUWEfNV1+IVFUY/31XJ2ZrXUmoHkpb1kQwdt093xiZRUpGD1pJFqYZpUEv0T9SH4zoQTRMgqzooUilNLWlslQoKlzDrXA8Z2dtAjdXCxTSJEh2VMbduV982x07O3vvzAhBnbd7zznf+Z3f9/v+HEKdg5nHA5gHYI6ItBNRC4CGaPsFETlORDkA+wDstdaercc01VrU3t4+JQiC1UQ0T0S+AfAlEVkAx6y153U/M18JYLKIMIB7iOguEdkbhuG6XC53tNoZiQAio2tEZBERbSgUCpv6+voGagHW+alTp45rbGxcLiIriWgngJfKYCv3xwIwxtxERLtE5DsiWlkvnZXG1W0isoGIbhORBc65/poApk+f3jF27NjPRWQ9gGcAjCeigyLyQ6lU2jI4OHgmnU7PV5qVdgBXATgDoDsIgm3ZbPZUDJAlANYPDQ3d19vb2+PPj2CAmdVgVxiGy0TkijFjxuytMNYXCW9SgisuAHjFWrsWQOiv6ejomBsEwWYAs621x8pzlwBEPj9IRGsPHTq0nZkNABXbZQ8R2eacexSA+JszmcxiEXkRwC1lTfgAXhORcc65J3XTzJkzG4rFYoGIgjgEIhIS0TkAjQBGaUlEnnfOvV651xjzDhENWGtf0LnhjVGo7SOim33BGWNOE9G1vhER2S8iawYGBg709/cXW1tbr06lUo8rc15e0C1n0+n0xO7u7hGREwnzpzAM52iIDgMwxmzWJGKt3egfxsxHNKq8fydbWlqaOzs7SzFCewjARxVglzrntsWsfVaTmXNuGUUZ7mihUJhcGefGmCwRZcoGRKTfOTclSRTMrGGmQi6PN621GkkjRpQnVIhTyBjzCID5zrnFMUi/BXBHvQCMMbuJaL5n531r7WNxgI0x2wHsUQbeFZFu59yWGMF8RURz6wXAzAcAzPLWv+WcezoBgOpmljKgNC+31v4Yw8CeqAANT1VzwYwZMyaGYai0pjw7T1lr344DwMytIrJJGfgDwMS4XM3MuwDc7xk4JyJPOOdUbJdivK2t7YZUKtUJ4Fbv9mEYhpNzudyvCQC0gJ1QAOettfoxahhjPiaiB2KmfgHwPQAFNImI7gSQ9teJyE7n3MNJgtX/enZVAMyst3qwmpGEuZNRtvutHgCJLjDG7CCiRZcDQER6iWihn++ruqCaCDVUiMin8ZSIdKl6ReT6ijT9s2bIpqamHV1dXUO1QPsiTAxDZv4QgJ8f8tba1sh/KwBs8A76s1gstuXz+RO1Dtd5Y8zfYcjMS0Tk3oREpGlUa3l5+ABUuMcBXOcpf39TU9PcehiI2N1dNRUzcyIAPTSTyayKGhf/0huttc9VY2FEKo7oiC1GxpitRKSpehQD+mPatGmNDQ0NysI1FSG4yjnnu2cEHmb+pxjpTJVy/AERLU0CkKCF8vItQRCsyWazCvDSiC3HkaERDUnETE0Azc3N6QkTJhwhohtjaNdseaxUKt1dzoixDUkEQEV1EMA6a62qXzPVVgCJLigfyMwLAHwa53cR+cQ5tzCyp4JeHduSRTfWdnyfNqU9PT1fM7MaVePl0W+tje0HjDHvaVGrAHF0cHDw9sOHD/9ebkpFZI7fno/q5cptORGpwvWwlwEM94X+bSpvy8xaBd8QkWXa84nIFxcvXlyRz+dPR83oqzXbco9S7Wo++zcfJlpV49Lz//Np5tP7nz1OY3ys7zx9ks2u9jyPitWeet+TfwG94//HwNlKlQAAAABJRU5ErkJggg==",A=l=>(h("data-v-8a551a48"),l=l(),V(),l),J={class:"login"},Q=A(()=>d("p",{class:"login_title"},"登 录",-1)),j=A(()=>d("p",{class:"login_desc"},"欢迎登录Redis-Manager平台",-1)),S=z({__name:"Index",setup(l){const s=g({username:"",password:""}),r=C(),f=g({username:[{required:!0,message:"账号不能为空",trigger:"blur"}],password:[{required:!0,message:"密码不能为空",trigger:"blur"}]}),_=async()=>{r&&r.value.validate(async c=>{if(c){const e=await N(s);console.log("res: ",e.data.data),e.data.errorCode===0?(sessionStorage.setItem("Authorization",e.data.data.token),sessionStorage.setItem("user_name",e.data.data.username),sessionStorage.setItem("user_type",e.data.data.usertype),v.push("/home")):y(e.data.msg)}else return!1})},w=()=>{window.open("https://github.com/iguidao/redis-manager","_blank")};return(c,e)=>{const p=t("el-image"),u=t("el-input"),i=t("el-form-item"),R=t("el-button"),x=t("el-form"),G=t("el-card");return E(),I("div",J,[o(G,{class:"login_card",shadow:"always"},{default:a(()=>[o(p,{class:"logo_image",src:m(k),fit:"cover"},null,8,["src"]),Q,j,o(x,{ref_key:"ruleFormRef",ref:r,model:s,rules:f},{default:a(()=>[o(i,{prop:"username"},{default:a(()=>[o(u,{placeholder:"请输入账号",modelValue:s.username,"onUpdate:modelValue":e[0]||(e[0]=n=>s.username=n),"prefix-icon":"User"},null,8,["modelValue"])]),_:1}),o(i,{prop:"password"},{default:a(()=>[o(u,{type:"password",placeholder:"请输入密码",modelValue:s.password,"onUpdate:modelValue":e[1]||(e[1]=n=>s.password=n),"prefix-icon":"Lock"},null,8,["modelValue"])]),_:1}),o(i,null,{default:a(()=>[o(R,{loading:!1,type:"primary",onClick:e[2]||(e[2]=n=>_())},{default:a(()=>[U("登录")]),_:1})]),_:1})]),_:1},8,["model","rules"])]),_:1}),d("div",null,[o(p,{class:"github_logo",src:m(T),fut:"civer",onClick:e[3]||(e[3]=n=>w())},null,8,["src"])])])}}});const D=b(S,[["__scopeId","data-v-8a551a48"]]);export{D as default}; 2 | -------------------------------------------------------------------------------- /website/assets/Index-398d56ab.js: -------------------------------------------------------------------------------- 1 | import{l as q,a as O,d as T,u as W}from"./setting-b5212552.js";import{h as j}from"./moment-fbc5633a.js";import{d as G,a as m,r as J,q as P,b as n,s as Q,o as v,c as V,e as t,w as l,f as _,v as R,l as x,g as h,F as X,k as Z,E as r,p as ee,i as te,_ as le}from"./index-056ef9e9.js";import"./index-04cccabd.js";const oe=p=>(ee("data-v-c3fd483f"),p=p(),te(),p),ae={class:"content"},ne=oe(()=>h("span",null,"系统配置",-1)),se={class:"dialog-footer"},re=G({__name:"Index",setup(p){const f=m(!1),b=m([]),g=(s,e)=>{const i=s[e.property];return i===void 0?"":j(i).utcOffset(8).format("YYYY-MM-DD HH:mm")},w=m([]),d=m(!1),C="100px",o=J({key:"",value:""}),y=async()=>{let s=(await q()).data;s.errorCode===0?(f.value=!1,b.value=s.data.lists):(f.value=!1,r.error(s.msg));let e=(await O()).data;e.errorCode===0?w.value=e.data:r.error(e.msg)},D=(s,e)=>{d.value=!0,o.key=e.Key,o.value=e.Value},I=async s=>{if(!s.Key)r.error("未获取到的删除的内容");else{o.key=s.Key;const e=await T(o);e.data.errorCode===0?(r.success("删除成功"),await y()):r.error(e.data.msg)}},$=async()=>{if(console.log(o),!o.key)r.error("未获取到的变更配置项");else if(!o.value)r.error("未获取到的变更配置值");else{const s=await W(o);s.data.errorCode===0?(r.success("变更成功"),o.key="",o.value="",await y()):r.error(s.data.msg),d.value=!1}},B=()=>{d.value=!1,o.key="",o.value=""};return P(async()=>{f.value=!0,await y()}),(s,e)=>{const i=n("el-col"),u=n("el-button"),E=n("el-row"),F=n("el-divider"),c=n("el-table-column"),K=n("el-popconfirm"),M=n("el-table"),N=n("el-card"),U=n("el-option"),Y=n("el-select"),k=n("el-form-item"),z=n("el-input"),S=n("el-form"),A=n("el-dialog"),H=Q("loading");return v(),V("div",ae,[t(N,{shadow:"never"},{default:l(()=>[t(E,null,{default:l(()=>[t(i,{span:2},{default:l(()=>[ne]),_:1}),t(i,{offset:18,span:3,style:{"min-width":"120px"}},{default:l(()=>[t(u,{size:"small",type:"primary",onClick:e[0]||(e[0]=a=>d.value=!0)},{default:l(()=>[_("添加集群")]),_:1})]),_:1})]),_:1}),t(F),R((v(),x(M,{data:b.value,stripe:"",style:{width:"100%"}},{default:l(()=>[t(c,{prop:"Name",label:"名称",width:"180"}),t(c,{prop:"Key",label:"key",width:"180"}),t(c,{prop:"Value",label:"value"}),t(c,{prop:"CreatedAt",formatter:g,label:"创建时间"}),t(c,{prop:"UpdatedAt",formatter:g,label:"修改时间"}),t(c,{label:"操作"},{default:l(a=>[t(u,{size:"small",onClick:L=>D(a.$index,a.row)},{default:l(()=>[_("修改")]),_:2},1032,["onClick"]),t(K,{title:"确定要删除吗?","confirm-button-text":"确认","cancel-button-text":"取消","confirm-button-type":"danger","cancel-button-type":"primary",onConfirm:L=>I(a.row)},{reference:l(()=>[t(u,{size:"small",type:"danger"},{default:l(()=>[_("删除")]),_:1})]),_:2},1032,["onConfirm"])]),_:1})]),_:1},8,["data"])),[[H,f.value]])]),_:1}),t(A,{modelValue:d.value,"onUpdate:modelValue":e[5]||(e[5]=a=>d.value=a),title:"配置变更",width:"30%","align-center":""},{footer:l(()=>[h("span",se,[t(u,{onClick:e[3]||(e[3]=a=>B())},{default:l(()=>[_("取消")]),_:1}),t(u,{type:"primary",onClick:e[4]||(e[4]=a=>$())},{default:l(()=>[_("确定")]),_:1})])]),default:l(()=>[t(S,{model:o},{default:l(()=>[t(k,{label:"配置项","label-width":C},{default:l(()=>[t(Y,{modelValue:o.key,"onUpdate:modelValue":e[1]||(e[1]=a=>o.key=a),placeholder:"请选择需要变更的配置"},{default:l(()=>[(v(!0),V(X,null,Z(w.value,a=>(v(),x(U,{key:a.value,label:a.label,value:a.value},null,8,["label","value"]))),128))]),_:1},8,["modelValue"])]),_:1}),t(k,{label:"配置值","label-width":C},{default:l(()=>[t(z,{modelValue:o.value,"onUpdate:modelValue":e[2]||(e[2]=a=>o.value=a),autocomplete:"off"},null,8,["modelValue"])]),_:1})]),_:1},8,["model"])]),_:1},8,["modelValue"])])}}});const _e=le(re,[["__scopeId","data-v-c3fd483f"]]);export{_e as default}; 2 | -------------------------------------------------------------------------------- /website/assets/Index-7aa9bc97.css: -------------------------------------------------------------------------------- 1 | .content[data-v-d5b3e431]{margin:20px 8px}.el-button--text[data-v-d5b3e431]{margin-right:15px}.el-select[data-v-d5b3e431],.el-input[data-v-d5b3e431]{width:300px}.dialog-footer button[data-v-d5b3e431]:first-child{margin-right:10px} 2 | -------------------------------------------------------------------------------- /website/assets/Index-8878f285.js: -------------------------------------------------------------------------------- 1 | import{a as j,b as G,c as J,d as K,e as P}from"./user-d7605884.js";import{h as Q}from"./moment-fbc5633a.js";import{d as R,a as v,r as X,q as Z,b as n,s as ee,o as V,c as D,e as a,w as o,f as i,v as le,l as $,g as C,F as ae,k as te,E as r,p as oe,i as se,_ as ne}from"./index-056ef9e9.js";import"./index-04cccabd.js";const re=b=>(oe("data-v-d79bf60f"),b=b(),se(),b),de={class:"content"},ue=re(()=>C("span",null,"用户列表",-1)),ie={class:"dialog-footer"},me={class:"dialog-footer"},pe=R({__name:"Index",setup(b){const w=v(!1),x=v([]),k=v([]),U=(s,e)=>{const c=s[e.property];return c===void 0?"":Q(c).utcOffset(8).format("YYYY-MM-DD HH:mm")},m=v(!1),p=v(!1),f="100px",l=X({username:"",password:"",mail:"",usertype:""}),y=async()=>{let s=(await j()).data;s.errorCode===0?(w.value=!1,x.value=s.data):(w.value=!1,r.error(s.msg));let e=(await G()).data;e.errorCode===0?k.value=e.data:r.error(e.msg)},N=(s,e)=>{p.value=!0,l.username=e.UserName,l.usertype=e.UserType},A=async s=>{if(!s.ID)r.error("未获取到的删除的内容");else{l.username=s.UserName;const e=await J(l);e.data.errorCode===0?(r.success("删除成功"),await y()):r.error(e.data.msg)}},E=async()=>{if(console.log(l),!l.username)r.error("未获取到的变更用户");else if(!l.usertype)r.error("未获取到的变更值");else{const s=await K(l);s.data.errorCode===0?(r.success("变更成功"),l.username="",l.usertype="",await y()):r.error(s.data.msg),p.value=!1}},F=async()=>{if(console.log(l),!l.username)r.error("未获取到的用户名称");else if(!l.password)r.error("未获取到的用户密码");else if(!l.mail)r.error("未获取到的用户邮箱");else{const s=await P(l);s.data.errorCode===0?(r.success("变更成功"),l.username="",l.password="",l.mail="",await y()):r.error(s.data.msg),m.value=!1}},B=()=>{p.value=!1,l.username="",l.usertype=""},M=()=>{m.value=!1,l.username="",l.password="",l.mail=""};return Z(async()=>{w.value=!0,await y()}),(s,e)=>{const c=n("el-col"),d=n("el-button"),Y=n("el-row"),z=n("el-divider"),u=n("el-table-column"),S=n("el-popconfirm"),T=n("el-table"),H=n("el-card"),g=n("el-input"),_=n("el-form-item"),L=n("el-option"),q=n("el-select"),h=n("el-form"),I=n("el-dialog"),O=ee("loading");return V(),D("div",de,[a(H,{shadow:"never"},{default:o(()=>[a(Y,null,{default:o(()=>[a(c,{span:2},{default:o(()=>[ue]),_:1}),a(c,{offset:18,span:3,style:{"min-width":"120px"}},{default:o(()=>[a(d,{size:"small",type:"primary",onClick:e[0]||(e[0]=t=>m.value=!0)},{default:o(()=>[i("添加用户")]),_:1})]),_:1})]),_:1}),a(z),le((V(),$(T,{data:x.value,stripe:"",style:{width:"100%"}},{default:o(()=>[a(u,{prop:"ID",label:"用户ID",width:"180"}),a(u,{prop:"UserName",label:"用户名",width:"180"}),a(u,{prop:"UserType",label:"用户身份",width:"180"}),a(u,{prop:"Email",label:"邮箱"}),a(u,{prop:"CreatedAt",formatter:U,label:"创建时间"}),a(u,{prop:"UpdatedAt",formatter:U,label:"修改时间"}),a(u,{label:"操作"},{default:o(t=>[a(d,{size:"small",onClick:W=>N(t.$index,t.row)},{default:o(()=>[i("修改")]),_:2},1032,["onClick"]),a(S,{title:"确定要删除吗?","confirm-button-text":"确认","cancel-button-text":"取消","confirm-button-type":"danger","cancel-button-type":"primary",onConfirm:W=>A(t.row)},{reference:o(()=>[a(d,{size:"small",type:"danger"},{default:o(()=>[i("删除")]),_:1})]),_:2},1032,["onConfirm"])]),_:1})]),_:1},8,["data"])),[[O,w.value]])]),_:1}),a(I,{modelValue:p.value,"onUpdate:modelValue":e[5]||(e[5]=t=>p.value=t),title:"修改用户",width:"30%","align-center":""},{footer:o(()=>[C("span",ie,[a(d,{onClick:e[3]||(e[3]=t=>B())},{default:o(()=>[i("取消")]),_:1}),a(d,{type:"primary",onClick:e[4]||(e[4]=t=>E())},{default:o(()=>[i("确定")]),_:1})])]),default:o(()=>[a(h,{model:l},{default:o(()=>[a(_,{label:"用户名","label-width":f},{default:o(()=>[a(g,{modelValue:l.username,"onUpdate:modelValue":e[1]||(e[1]=t=>l.username=t),autocomplete:"off"},null,8,["modelValue"])]),_:1}),a(_,{label:"用户身份","label-width":f},{default:o(()=>[a(q,{modelValue:l.usertype,"onUpdate:modelValue":e[2]||(e[2]=t=>l.usertype=t),placeholder:"请选择需要变更的配置"},{default:o(()=>[(V(!0),D(ae,null,te(k.value,t=>(V(),$(L,{key:t.value,label:t.label,value:t.value},null,8,["label","value"]))),128))]),_:1},8,["modelValue"])]),_:1})]),_:1},8,["model"])]),_:1},8,["modelValue"]),a(I,{modelValue:m.value,"onUpdate:modelValue":e[11]||(e[11]=t=>m.value=t),title:"添加用户",width:"30%","align-center":""},{footer:o(()=>[C("span",me,[a(d,{onClick:e[9]||(e[9]=t=>M())},{default:o(()=>[i("取消")]),_:1}),a(d,{type:"primary",onClick:e[10]||(e[10]=t=>F())},{default:o(()=>[i("确定")]),_:1})])]),default:o(()=>[a(h,{model:l},{default:o(()=>[a(_,{label:"用户名称","label-width":f},{default:o(()=>[a(g,{modelValue:l.username,"onUpdate:modelValue":e[6]||(e[6]=t=>l.username=t),autocomplete:"off"},null,8,["modelValue"])]),_:1}),a(_,{label:"用户密码","label-width":f},{default:o(()=>[a(g,{modelValue:l.password,"onUpdate:modelValue":e[7]||(e[7]=t=>l.password=t),type:"password",autocomplete:"off"},null,8,["modelValue"])]),_:1}),a(_,{label:"用户邮箱","label-width":f},{default:o(()=>[a(g,{modelValue:l.mail,"onUpdate:modelValue":e[8]||(e[8]=t=>l.mail=t),autocomplete:"off"},null,8,["modelValue"])]),_:1})]),_:1},8,["model"])]),_:1},8,["modelValue"])])}}});const be=ne(pe,[["__scopeId","data-v-d79bf60f"]]);export{be as default}; 2 | -------------------------------------------------------------------------------- /website/assets/Index-9ea35986.css: -------------------------------------------------------------------------------- 1 | .content[data-v-9ad5b82c]{margin-top:20px;display:flex;flex-direction:column;height:70%}.main-info[data-v-9ad5b82c]{flex:1;display:flex;justify-content:space-around;background:rgb(255,255,255);min-width:700px;border-radius:8px}.info[data-v-9ad5b82c]{background:white;width:18%;height:80%;align-self:center;text-align:center;box-sizing:border-box;display:flex;align-items:center;justify-content:center}.info .num-info[data-v-9ad5b82c]{margin:10px 0}.info .desc[data-v-9ad5b82c]{font-size:10px;color:gray}.chart[data-v-9ad5b82c]{flex:1;margin-top:10px;display:flex;justify-content:space-between;border-radius:8px;width:100%;height:100%}.e-chart[data-v-9ad5b82c]{padding-top:10px;width:49%;height:auto;min-height:300px;min-width:300px} 2 | -------------------------------------------------------------------------------- /website/assets/Index-a2a246be.js: -------------------------------------------------------------------------------- 1 | import{a as f}from"./index-04cccabd.js";import{h as i}from"./moment-fbc5633a.js";import{d as m,a as r,q as u,b as o,o as b,c as h,e as a,w as c,E as v,_ as w}from"./index-056ef9e9.js";const x=()=>f.get("/ophistory/v1/list"),I={class:"content"},y=m({__name:"Index",setup(g){const s=r([]);r("");const l=async()=>{let t=(await x()).data;t.errorCode===0?s.value=t.data:v.error(t.msg)},_=(t,n)=>{const e=t[n.property];return e===void 0?"":i(e).utcOffset(8).format("YYYY-MM-DD HH:mm:ss")};return u(async()=>{await l()}),(t,n)=>{const e=o("el-table-column"),d=o("el-table"),p=o("el-card");return b(),h("div",I,[a(p,{shadow:"never"},{default:c(()=>[a(d,{data:s.value,stripe:"",style:{width:"100%"}},{default:c(()=>[a(e,{prop:"CreatedAt",formatter:_,label:"时间",width:"170",sortable:""}),a(e,{prop:"UserId",label:"用户ID",width:"100"}),a(e,{prop:"OpInfo",label:"地址",width:"400"}),a(e,{prop:"OpParams",label:"事件"})]),_:1},8,["data"])]),_:1})])}}});const Y=w(y,[["__scopeId","data-v-0ccf9bff"]]);export{Y as default}; 2 | -------------------------------------------------------------------------------- /website/assets/Index-b3c9f82a.css: -------------------------------------------------------------------------------- 1 | .content[data-v-0d2aba58]{margin:20px 8px}.el-button--text[data-v-0d2aba58]{margin-right:15px}.el-select[data-v-0d2aba58],.el-input[data-v-0d2aba58]{width:300px}.dialog-footer button[data-v-0d2aba58]:first-child{margin-right:10px} 2 | -------------------------------------------------------------------------------- /website/assets/Index-b6741412.css: -------------------------------------------------------------------------------- 1 | .content[data-v-c3fd483f]{margin:20px 8px}.el-button--text[data-v-c3fd483f]{margin-right:15px}.el-select[data-v-c3fd483f],.el-input[data-v-c3fd483f]{width:300px}.dialog-footer button[data-v-c3fd483f]:first-child{margin-right:10px} 2 | -------------------------------------------------------------------------------- /website/assets/Index-bba6e235.css: -------------------------------------------------------------------------------- 1 | .content[data-v-0ccf9bff]{margin:20px 8px} 2 | -------------------------------------------------------------------------------- /website/assets/Index-bfef1db5.js: -------------------------------------------------------------------------------- 1 | import{a as h}from"./index-04cccabd.js";import{d as f,a as m,q as v,b as u,o as e,c as o,g as a,e as c,w as _,t as r,E as y,p as x,i as k,_ as g}from"./index-056ef9e9.js";const I=()=>h.get("/board/v1/desc"),t=n=>(x("data-v-9ad5b82c"),n=n(),k(),n),b={class:"content"},w={class:"main-info"},C={key:0,class:"num-info"},B={key:1,class:"num-info"},S=t(()=>a("p",{class:"desc"},"腾讯Redis",-1)),E={key:0,class:"num-info"},D={key:1,class:"num-info"},M=t(()=>a("p",{class:"desc"},"阿里Redis",-1)),N={key:0,class:"num-info"},R={key:1,class:"num-info"},V=t(()=>a("p",{class:"desc"},"自建Codis",-1)),q={key:0,class:"num-info"},j={key:1,class:"num-info"},z=t(()=>a("p",{class:"desc"},"自建Cluster",-1)),A=t(()=>a("div",{class:"chart"},null,-1)),F=f({__name:"Index",setup(n){const s=m({}),p=async()=>{let i=(await I()).data;i.errorCode===0?s.value=i.data:y.error(i.msg)};return v(async()=>{await p()}),(i,G)=>{const d=u("el-button"),l=u("el-card");return e(),o("div",b,[a("div",w,[c(l,{class:"info"},{default:_(()=>[c(d,{type:"primary",icon:"el-icon-user-solid",circle:""}),s.value.txredis?(e(),o("h2",C,r(s.value.txredis),1)):(e(),o("h2",B,"0")),S]),_:1}),c(l,{class:"info"},{default:_(()=>[c(d,{type:"success",icon:"el-icon-s-data",circle:""}),s.value.aliredis?(e(),o("h2",E,r(s.value.aliredis),1)):(e(),o("h2",D,"0")),M]),_:1}),c(l,{class:"info"},{default:_(()=>[c(d,{type:"danger",icon:"el-icon-coin",circle:""}),s.value.codis?(e(),o("h2",N,r(s.value.codis),1)):(e(),o("h2",R,"0")),V]),_:1}),c(l,{class:"info"},{default:_(()=>[c(d,{type:"warning",icon:"el-icon-data-line",circle:""}),s.value.cluster?(e(),o("h2",q,r(s.value.cluster),1)):(e(),o("h2",j,"0")),z]),_:1})]),A])}}});const K=g(F,[["__scopeId","data-v-9ad5b82c"]]);export{K as default}; 2 | -------------------------------------------------------------------------------- /website/assets/Index-c6cadafd.js: -------------------------------------------------------------------------------- 1 | import{l as z,a as A,b as F}from"./cluster-7426750c.js";import{h as L}from"./moment-fbc5633a.js";import{d as $,a as f,r as S,q as H,b as d,s as T,o as n,c as p,g as w,e,w as l,f as V,v as q,l as G,n as O,E as i,p as W,i as j,_ as J}from"./index-056ef9e9.js";import"./index-04cccabd.js";const K=_=>(W("data-v-88bc7fc3"),_=_(),j(),_),Q={class:"content"},X=K(()=>w("span",null,"自建Cluster集群",-1)),Z={key:0},ee={key:0},te={key:1},le={key:1},oe={key:0},ae={class:"dialog-footer"},se=$({__name:"Index",setup(_){const m=f(!1),b="100px",k=f([]),x=f([]),u=f(!1),r=S({name:"",nodes:"",password:""}),v=S({cluster_id:""}),N=(s,t)=>{const c=s[t.property];return c===void 0?"":L(c).utcOffset(8).format("YYYY-MM-DD HH:mm")},h=async()=>{let s=(await z()).data;s.errorCode===0?(m.value=!1,k.value=s.data):(m.value=!1,i.error(s.msg))},D=async s=>{if(v.cluster_id=s.ID,!v.cluster_id)i.error("未获取到的添加cluster_id");else{const t=(await A(v)).data;t.errorCode===0?x.value=t.data:i.error(t.msg),u.value=!1}},P=async()=>{if(!r.name)i.error("未获取到的添加cluster集群的名称");else if(!r.nodes)i.error("未获取到的添加cluster集群的地址");else{const s=await F(r);s.data.errorCode===0?(i.success("添加成功"),await h()):i.error(s.data.msg),u.value=!1}};return H(async()=>{m.value=!0,await h()}),(s,t)=>{const c=d("el-col"),C=d("el-button"),B=d("el-row"),E=d("el-divider"),a=d("el-table-column"),I=d("el-table"),M=d("el-card"),y=d("el-input"),g=d("el-form-item"),R=d("el-form"),U=d("el-dialog"),Y=T("loading");return n(),p("div",Q,[w("div",null,[e(M,{shadow:"never"},{default:l(()=>[e(B,null,{default:l(()=>[e(c,{span:3},{default:l(()=>[X]),_:1}),e(c,{offset:18,span:3,style:{"min-width":"120px"}},{default:l(()=>[e(C,{size:"small",type:"primary",onClick:t[0]||(t[0]=o=>u.value=!0)},{default:l(()=>[V("添加集群")]),_:1})]),_:1})]),_:1}),e(E),q((n(),G(I,{data:k.value,stripe:"",style:{width:"100%"},lazy:"",onExpandChange:D},{default:l(()=>[e(a,{label:"查看",type:"expand",width:"60"},{default:l(o=>[e(I,{data:x.value,border:!0,style:{width:"100%"},"row-key":"id",lazy:"",load:h,"tree-props":{children:"Children",hasChildren:"hasChildren"}},{default:l(()=>[e(a,{prop:"Flags",label:"身份",width:"160"}),e(a,{prop:"Address",label:"地址",width:"160"}),e(a,{prop:"LinkState",label:"LinkState",width:"160"}),e(a,{prop:"RunStatus",label:"RunStatus",width:"160"}),e(a,{prop:"SlotRange",label:"SlotRange",width:"160"}),e(a,{prop:"SlotNumber",label:"SlotNumber",width:"160"}),e(a,{prop:"CreateTime",formatter:N,label:"创建时间",width:"160"})]),_:1},8,["data"])]),_:1}),e(a,{prop:"ID",label:"ID",width:"60"}),e(a,{prop:"Name",label:"名称",width:"100"}),e(a,{prop:"Nodes",label:"内网IP"}),e(a,{prop:"Password",label:"密码",width:"120"},{default:l(o=>[o.row.NoAuth?(n(),p("div",le,[o.row.Password===""?(n(),p("span",oe,"免密登陆")):O("",!0)])):(n(),p("div",Z,[o.row.Password===""?(n(),p("span",ee,"未设置密码")):(n(),p("span",te,"已设置密码"))]))]),_:1}),e(a,{prop:"CreatedAt",formatter:N,label:"创建时间",width:"150"})]),_:1},8,["data"])),[[Y,m.value]])]),_:1})]),w("div",null,[e(U,{modelValue:u.value,"onUpdate:modelValue":t[6]||(t[6]=o=>u.value=o),title:"添加集群",width:"30%","align-center":""},{footer:l(()=>[w("span",ae,[e(C,{onClick:t[4]||(t[4]=o=>u.value=!1)},{default:l(()=>[V("取消")]),_:1}),e(C,{type:"primary",onClick:t[5]||(t[5]=o=>P())},{default:l(()=>[V("Confirm")]),_:1})])]),default:l(()=>[e(R,{model:r},{default:l(()=>[e(g,{label:"集群名称","label-width":b},{default:l(()=>[e(y,{modelValue:r.name,"onUpdate:modelValue":t[1]||(t[1]=o=>r.name=o),placeholder:"Cluster Name",autocomplete:"off"},null,8,["modelValue"])]),_:1}),e(g,{label:"集群地址","label-width":b},{default:l(()=>[e(y,{modelValue:r.nodes,"onUpdate:modelValue":t[2]||(t[2]=o=>r.nodes=o),placeholder:"127.0.0.1:6379,127.0.0.1:6380",autocomplete:"off"},null,8,["modelValue"])]),_:1}),e(g,{label:"集群密码","label-width":b},{default:l(()=>[e(y,{modelValue:r.password,"onUpdate:modelValue":t[3]||(t[3]=o=>r.password=o),type:"password",placeholder:"Cluster Password",autocomplete:"off"},null,8,["modelValue"])]),_:1})]),_:1},8,["model"])]),_:1},8,["modelValue"])])])}}});const ue=J(se,[["__scopeId","data-v-88bc7fc3"]]);export{ue as default}; 2 | -------------------------------------------------------------------------------- /website/assets/Index-c74390c1.css: -------------------------------------------------------------------------------- 1 | .content[data-v-d79bf60f]{margin:20px 8px}.el-button--text[data-v-d79bf60f]{margin-right:15px}.el-select[data-v-d79bf60f],.el-input[data-v-d79bf60f]{width:300px}.dialog-footer button[data-v-d79bf60f]:first-child{margin-right:10px} 2 | -------------------------------------------------------------------------------- /website/assets/Index-d450f424.css: -------------------------------------------------------------------------------- 1 | .content[data-v-ee0055ce]{margin:20px 8px}.home-container[data-v-9f6cd060]{position:absolute;height:100%;top:0px;left:0px;width:100%;background:#f2f3f5}.el-header[data-v-9f6cd060]{background:#ffffff;padding:0 10px;overflow:hidden}.grid-content[data-v-9f6cd060]{min-height:40px;display:flex;justify-content:space-between;align-items:center}.logo-wrapper[data-v-9f6cd060]{display:flex;justify-content:space-between;align-items:center;width:150px;cursor:pointer;padding-left:22px}.system_name[data-v-9f6cd060]{color:#000;font-size:18px}.el-aside[data-v-9f6cd060]{background:#545c64;width:auto!important}.el-menu-vertical-demo[data-v-9f6cd060]:not(.el-menu--collapse){width:200px;min-height:400px}.el-menu-item.is-active[data-v-9f6cd060]{color:#fff!important;font-size:15px;font-weight:700;background-color:#a7a7a7!important;border-radius:2px;height:50px;line-height:50px;box-sizing:border-box;margin:2px 5px 0 2px} 2 | -------------------------------------------------------------------------------- /website/assets/Index-d4718653.css: -------------------------------------------------------------------------------- 1 | .login[data-v-8a551a48]{width:100%;height:100%;background-image:url(/assets/sun-80b81115.jpeg);position:absolute;left:0;top:0;background-size:100% 100%}.login_card[data-v-8a551a48]{position:absolute;left:0;right:0;top:0;bottom:0;margin:auto;width:20%;min-width:300px;height:450px;min-height:450px;border-radius:10px;text-align:center}.logo_image[data-v-8a551a48]{width:50px;height:50px;margin-top:40px}.login_title[data-v-8a551a48]{font-size:25px;font-weight:700}.login_desc[data-v-8a551a48]{letter-spacing:2px;color:#999a9a}.el-button[data-v-8a551a48]{width:100%}.github_logo[data-v-8a551a48]{width:40px;height:40px;position:fixed;bottom:60px;right:20px} 2 | -------------------------------------------------------------------------------- /website/assets/Index-dc641d83.css: -------------------------------------------------------------------------------- 1 | .content[data-v-c36ae760]{margin:20px 8px}.el-button--text[data-v-c36ae760]{margin-right:15px}.el-select[data-v-c36ae760]{width:130px}.el-input[data-v-c36ae760]{width:300px}.dialog-footer button[data-v-c36ae760]:first-child{margin-right:10px}.el-link[data-v-c36ae760]{margin-right:8px}.el-link .el-icon--right.el-icon[data-v-c36ae760]{vertical-align:text-bottom}.codis_dashboard[data-v-c36ae760]{width:100%;height:70vh} 2 | -------------------------------------------------------------------------------- /website/assets/Index-f5a41eb2.css: -------------------------------------------------------------------------------- 1 | .content[data-v-1b6ebbad]{margin:20px 8px}.el-button--text[data-v-1b6ebbad]{margin-right:15px}.el-select[data-v-1b6ebbad]{width:130px}.el-input[data-v-1b6ebbad]{width:300px}.cli_commend[data-v-1b6ebbad]{width:100%;height:70vh}.el-col[data-v-1b6ebbad]{border-radius:1px}.grid-content[data-v-1b6ebbad]{border-radius:1px;min-height:10px}.content[data-v-5dc9fb7f]{margin:20px 8px} 2 | -------------------------------------------------------------------------------- /website/assets/Rule-273f6791.css: -------------------------------------------------------------------------------- 1 | .content[data-v-e7746445]{margin:20px 8px}.el-button--text[data-v-e7746445]{margin-right:15px}.el-select[data-v-e7746445],.el-input[data-v-e7746445]{width:300px}.dialog-footer button[data-v-e7746445]:first-child{margin-right:10px} 2 | -------------------------------------------------------------------------------- /website/assets/Rule-d9da8097.js: -------------------------------------------------------------------------------- 1 | import{a as y}from"./index-04cccabd.js";import{b as A}from"./user-d7605884.js";import"./moment-fbc5633a.js";import{d as G,a as c,r as H,q as J,b as r,s as K,o as i,c as v,e as t,w as a,f as h,v as O,l as b,g as $,F as k,k as I,E as s,p as P,i as Q,_ as X}from"./index-056ef9e9.js";const Y=()=>y.get("/rule/v1/list"),Z=d=>y.delete("/rule/v1/del",{params:d}),ee=()=>y.get("/rule/v1/cfg"),te=d=>y.post("/rule/v1/add",d),le=d=>(P("data-v-e7746445"),d=d(),Q(),d),oe={class:"content"},ae=le(()=>$("span",null,"权限配置",-1)),ne={class:"dialog-footer"},re=G({__name:"Rule",setup(d){const m=c(!1),B=c([]),D=c([]),M=c([]),U=c([]),p=c(!1),g="100px",l=H({identity:"",path:"",method:""}),w=async()=>{let n=(await Y()).data;n.errorCode===0?(m.value=!1,B.value=n.data):(m.value=!1,s.error(n.msg));let o=(await A()).data;o.errorCode===0?D.value=o.data:s.error(o.msg);let u=(await ee()).data;u.errorCode===0?(M.value=u.data.url,U.value=u.data.method):s.error(u.msg)},E=async()=>{if(console.log(l),!l.identity)s.error("未获取到的添加的配置");else if(!l.path)s.error("未获取到的添加的配置");else if(!l.method)s.error("未获取到的添加的配置");else{const n=await te(l);n.data.errorCode===0?(s.success("添加成功"),l.identity="",l.path="",l.method="",await w()):s.error(n.data.msg),p.value=!1}},F=async n=>{if(!n.identity)s.error("未获取到的删除的配置");else if(!n.path)s.error("未获取到的删除的配置");else if(!n.method)s.error("未获取到的删除的配置");else{l.identity=n.identity,l.path=n.path,l.method=n.method;const o=await Z(l);o.data.errorCode===0?(s.success("删除成功"),await w()):s.error(o.data.msg)}},N=()=>{p.value=!1,l.identity="",l.path="",l.method=""};return J(async()=>{m.value=!0,await w()}),(n,o)=>{const u=r("el-col"),f=r("el-button"),R=r("el-row"),S=r("el-divider"),_=r("el-table-column"),z=r("el-popconfirm"),L=r("el-table"),q=r("el-card"),V=r("el-option"),C=r("el-select"),x=r("el-form-item"),T=r("el-form"),W=r("el-dialog"),j=K("loading");return i(),v("div",oe,[t(q,{shadow:"never"},{default:a(()=>[t(R,null,{default:a(()=>[t(u,{span:2},{default:a(()=>[ae]),_:1}),t(u,{offset:18,span:3,style:{"min-width":"120px"}},{default:a(()=>[t(f,{size:"small",type:"primary",onClick:o[0]||(o[0]=e=>p.value=!0)},{default:a(()=>[h("添加权限")]),_:1})]),_:1})]),_:1}),t(S),O((i(),b(L,{data:B.value,stripe:"",style:{width:"100%"}},{default:a(()=>[t(_,{prop:"identity",label:"身份",width:"180"}),t(_,{prop:"path",label:"接口"}),t(_,{prop:"method",label:"meth",width:"180"}),t(_,{prop:"note",label:"备注"}),t(_,{label:"操作"},{default:a(e=>[t(z,{title:"确定要删除吗?","confirm-button-text":"确认","cancel-button-text":"取消","confirm-button-type":"danger","cancel-button-type":"primary",onConfirm:se=>F(e.row)},{reference:a(()=>[t(f,{size:"small",type:"danger"},{default:a(()=>[h("删除")]),_:1})]),_:2},1032,["onConfirm"])]),_:1})]),_:1},8,["data"])),[[j,m.value]])]),_:1}),t(W,{modelValue:p.value,"onUpdate:modelValue":o[6]||(o[6]=e=>p.value=e),title:"权限添加",width:"30%","align-center":""},{footer:a(()=>[$("span",ne,[t(f,{onClick:o[4]||(o[4]=e=>N())},{default:a(()=>[h("取消")]),_:1}),t(f,{type:"primary",onClick:o[5]||(o[5]=e=>E())},{default:a(()=>[h("确定")]),_:1})])]),default:a(()=>[t(T,{model:l},{default:a(()=>[t(x,{label:"身份","label-width":g},{default:a(()=>[t(C,{modelValue:l.identity,"onUpdate:modelValue":o[1]||(o[1]=e=>l.identity=e),placeholder:"选择身份"},{default:a(()=>[(i(!0),v(k,null,I(D.value,e=>(i(),b(V,{key:e.value,label:e.label,value:e.value},null,8,["label","value"]))),128))]),_:1},8,["modelValue"])]),_:1}),t(x,{label:"接口","label-width":g},{default:a(()=>[t(C,{modelValue:l.path,"onUpdate:modelValue":o[2]||(o[2]=e=>l.path=e),placeholder:"选择接口"},{default:a(()=>[(i(!0),v(k,null,I(M.value,e=>(i(),b(V,{key:e.value,label:e.label,value:e.value},null,8,["label","value"]))),128))]),_:1},8,["modelValue"])]),_:1}),t(x,{label:"Method","label-width":g},{default:a(()=>[t(C,{modelValue:l.method,"onUpdate:modelValue":o[3]||(o[3]=e=>l.method=e),placeholder:"选择Method"},{default:a(()=>[(i(!0),v(k,null,I(U.value,e=>(i(),b(V,{key:e.value,label:e.label,value:e.value},null,8,["label","value"]))),128))]),_:1},8,["modelValue"])]),_:1})]),_:1},8,["model"])]),_:1},8,["modelValue"])])}}});const pe=X(re,[["__scopeId","data-v-e7746445"]]);export{pe as default}; 2 | -------------------------------------------------------------------------------- /website/assets/cloud-3a6d1fb9.js: -------------------------------------------------------------------------------- 1 | import{a as s}from"./index-04cccabd.js";const e=o=>s.get("/cloud/v1/region",{params:o}),n=o=>s.get("/cloud/v1/list",{params:o}),r=o=>s.post("/cloud/v1/password",o);export{e as a,r as c,n as l}; 2 | -------------------------------------------------------------------------------- /website/assets/cluster-7426750c.js: -------------------------------------------------------------------------------- 1 | import{a as t}from"./index-04cccabd.js";const r=s=>t.post("/cluster/v1/add",s),a=()=>t.get("/cluster/v1/list"),l=s=>t.get("/cluster/v1/nodes",{params:s}),u=s=>t.get("/cluster/v1/masters",{params:s});export{l as a,r as b,u as c,a as l}; 2 | -------------------------------------------------------------------------------- /website/assets/codis-7a3bd4ff.js: -------------------------------------------------------------------------------- 1 | import{a as o}from"./index-04cccabd.js";const r=s=>o.post("/codis/v1/add",s),d=()=>o.get("/codis/v1/list"),i=s=>o.get("/codis/v1/cluster",{params:s}),e=s=>o.get("/codis/v1/group",{params:s}),c=s=>o.post("/codis/v1/opnode",s);export{i as a,r as b,e as c,d as l,c as o}; 2 | -------------------------------------------------------------------------------- /website/assets/github-mark-367d5cb2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iguidao/redis-manager/43400bf548ffb68767ce7067679c6128fc2e155b/website/assets/github-mark-367d5cb2.png -------------------------------------------------------------------------------- /website/assets/setting-b5212552.js: -------------------------------------------------------------------------------- 1 | import{a as t}from"./index-04cccabd.js";const a=()=>t.get("/cfg/v1/list"),f=()=>t.get("/cfg/v1/listdefault"),r=e=>t.delete("/cfg/v1/del",{params:e}),g=e=>t.post("/cfg/v1/update",e);export{f as a,r as d,a as l,g as u}; 2 | -------------------------------------------------------------------------------- /website/assets/sun-80b81115.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iguidao/redis-manager/43400bf548ffb68767ce7067679c6128fc2e155b/website/assets/sun-80b81115.jpeg -------------------------------------------------------------------------------- /website/assets/user-d7605884.js: -------------------------------------------------------------------------------- 1 | import{a as e}from"./index-04cccabd.js";const r=s=>e.post("/auth/v1/sign-in",s),u=s=>e.post("/auth/v1/password",s),n=()=>e.get("/user/v1/list"),a=()=>e.get("/user/v1/utype"),o=s=>e.post("/user/v1/add",s),c=s=>e.post("/user/v1/change",s),d=s=>e.delete("/user/v1/del",{params:s});export{n as a,a as b,d as c,c as d,o as e,r as l,u}; 2 | -------------------------------------------------------------------------------- /website/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | 10 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /website/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Redis Manager 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /website/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /yaml/dev.yaml: -------------------------------------------------------------------------------- 1 | local: 2 | addr: 0.0.0.0:8000 3 | runmode: debug 4 | pagesize: 10 5 | logapipath: "./logs/api.log" 6 | logapppath: "./logs/app.log" 7 | 8 | rediscfg: 9 | allkeyfornum: 10 10 | locktime: 60 11 | biglocktime: 600 12 | checksize: 4000 13 | 14 | mysql: 15 | name: redis_manager 16 | addr: 127.0.0.1:3308 17 | username: root 18 | password: 123456 19 | 20 | redis: 21 | addr: 10.2.74.82 22 | port: 6379 23 | password: xxxxxxxxxx 24 | db: 0 25 | -------------------------------------------------------------------------------- /yaml/online.yaml: -------------------------------------------------------------------------------- 1 | local: 2 | addr: 0.0.0.0:8000 3 | runmode: debug 4 | pagesize: 10 5 | logapipath: "./logs/api.log" 6 | logapppath: "./logs/app.log" 7 | 8 | rediscfg: 9 | allkeyfornum: 10 10 | locktime: 60 11 | biglocktime: 600 12 | checksize: 4000 13 | 14 | mysql: 15 | name: dev_redis_manager 16 | addr: 127.0.0.1:3308 17 | username: root 18 | password: 123456 19 | 20 | redis: 21 | addr: 10.2.74.82 22 | port: 6379 23 | password: xxxxxxxxxx 24 | db: 0 25 | --------------------------------------------------------------------------------